first commit
Copilot Setup Steps / copilot-setup-steps (push) Has been cancelled

This commit is contained in:
2026-04-22 19:51:20 +07:00
commit 93d1b7c3d3
579 changed files with 99797 additions and 0 deletions
+1
View File
@@ -0,0 +1 @@
tailwind.config.cjs
+73
View File
@@ -0,0 +1,73 @@
/* eslint-env node */
const tailwindCssConfig = `${__dirname}/../admin/src/index.css`;
module.exports = {
root: true,
extends: [
'plugin:ghost/ts',
'plugin:react/recommended',
'plugin:react-hooks/recommended'
],
plugins: [
'ghost',
'react-refresh',
'tailwindcss'
],
settings: {
react: {
version: 'detect'
},
tailwindcss: {
config: tailwindCssConfig
}
},
rules: {
'no-shadow': 'off',
'@typescript-eslint/no-shadow': 'error',
// Enforce kebab-case (lowercase with hyphens) for all filenames
'ghost/filenames/match-regex': ['error', '^[a-z0-9.-]+$', false],
// sort multiple import lines into alphabetical groups
'ghost/sort-imports-es6-autofix/sort-imports-es6': ['error', {
memberSyntaxSortOrder: ['none', 'all', 'single', 'multiple']
}],
'no-restricted-imports': ['error', {
paths: [{
name: '@tryghost/shade',
message: 'Import from layered subpaths instead (components/primitives/patterns/utils/app/tokens).'
}]
}],
// TODO: re-enable this (maybe fixed fast refresh?)
'react-refresh/only-export-components': 'off',
// suppress errors for missing 'import React' in JSX files, as we don't need it
'react/react-in-jsx-scope': 'off',
// ignore prop-types for now
'react/prop-types': 'off',
// TODO: re-enable these if deemed useful
'@typescript-eslint/no-non-null-assertion': 'off',
'@typescript-eslint/no-empty-function': 'off',
// custom react rules
'react/jsx-sort-props': ['error', {
reservedFirst: true,
callbacksLast: true,
shorthandLast: true,
locale: 'en'
}],
'react/button-has-type': 'error',
'react/no-array-index-key': 'error',
'react/jsx-key': 'off',
'tailwindcss/classnames-order': 'error',
'tailwindcss/enforces-negative-arbitrary-values': 'warn',
'tailwindcss/enforces-shorthand': 'warn',
'tailwindcss/migration-from-tailwind-2': 'warn',
'tailwindcss/no-arbitrary-value': 'off',
'tailwindcss/no-custom-classname': 'off',
'tailwindcss/no-contradicting-classname': 'error'
}
};
+4
View File
@@ -0,0 +1,4 @@
dist
types
playwright-report
test-results
+13
View File
@@ -0,0 +1,13 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>AdminX Standalone</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/standalone.tsx"></script>
</body>
</html>
+95
View File
@@ -0,0 +1,95 @@
{
"name": "@tryghost/activitypub",
"version": "3.1.13",
"license": "MIT",
"repository": {
"type": "git",
"url": "https://github.com/TryGhost/Ghost/tree/main/apps/activitypub"
},
"author": "Ghost Foundation",
"files": [
"LICENSE",
"README.md",
"dist/"
],
"main": "./dist/activitypub.umd.cjs",
"module": "./dist/activitypub.js",
"exports": {
".": {
"import": "./dist/activitypub.js",
"require": "./dist/activitypub.umd.cjs"
},
"./api": "./src/index.tsx"
},
"private": false,
"scripts": {
"dev": "vite build --watch",
"dev:start": "vite",
"build": "tsc && vite build",
"lint": "pnpm run lint:code && pnpm run lint:test",
"lint:code": "eslint --ext .js,.ts,.cjs,.tsx --cache src",
"lint:test": "eslint -c test/.eslintrc.cjs --ext .js,.ts,.cjs,.tsx --cache test",
"test": "pnpm test:unit",
"test:unit": "tsc --noEmit && vitest run",
"test:acceptance": "NODE_OPTIONS='--experimental-specifier-resolution=node --no-warnings' VITE_TEST=true playwright test",
"test:acceptance:slowmo": "TIMEOUT=100000 PLAYWRIGHT_SLOWMO=100 pnpm test:acceptance --headed",
"test:acceptance:full": "ALL_BROWSERS=1 pnpm test:acceptance",
"preview": "vite preview"
},
"devDependencies": {
"@playwright/test": "1.59.1",
"@testing-library/react": "14.3.1",
"@types/dompurify": "3.2.0",
"@types/jest": "29.5.14",
"@types/react": "18.3.28",
"@types/react-dom": "18.3.7",
"jest": "29.7.0",
"tailwindcss": "^4.2.2",
"ts-jest": "29.4.9",
"vite": "5.4.21",
"vitest": "1.6.1"
},
"nx": {
"targets": {
"build": {
"dependsOn": [
"^build"
]
},
"dev": {
"dependsOn": [
"^build"
]
},
"test:unit": {
"dependsOn": [
"^build",
"test:unit"
]
},
"test:acceptance": {
"dependsOn": [
"^build",
"test:acceptance"
]
}
}
},
"dependencies": {
"@hookform/resolvers": "5.2.2",
"@radix-ui/react-form": "0.1.8",
"@tanstack/react-query": "4.36.1",
"@tryghost/admin-x-framework": "workspace:*",
"@tryghost/shade": "workspace:*",
"clsx": "2.1.1",
"dompurify": "3.3.1",
"html2canvas-objectfit-fix": "1.2.0",
"react": "18.3.1",
"react-dom": "18.3.1",
"react-hook-form": "7.72.1",
"react-router": "7.14.0",
"sonner": "2.0.7",
"use-debounce": "10.1.1",
"zod": "4.1.12"
}
}
+3
View File
@@ -0,0 +1,3 @@
import {adminXPlaywrightConfig} from '@tryghost/admin-x-framework/playwright';
export default adminXPlaywrightConfig();
+29
View File
@@ -0,0 +1,29 @@
import {FeatureFlagsProvider} from './lib/feature-flags';
import {FrameworkProvider, Outlet, RouterProvider, TopLevelFrameworkProps} from '@tryghost/admin-x-framework';
import {ShadeApp} from '@tryghost/shade/app';
import {routes} from '@src/routes';
interface AppProps {
framework: TopLevelFrameworkProps;
activityPubEnabled?: boolean;
}
const App: React.FC<AppProps> = ({framework, activityPubEnabled}) => {
if (activityPubEnabled === false) {
return null;
}
return (
<FrameworkProvider {...framework}>
<RouterProvider prefix={'/'} routes={routes}>
<FeatureFlagsProvider>
<ShadeApp className="shade-activitypub" darkMode={false} fetchKoenigLexical={null}>
<Outlet />
</ShadeApp>
</FeatureFlagsProvider>
</RouterProvider>
</FrameworkProvider>
);
};
export default App;
+7
View File
@@ -0,0 +1,7 @@
import './styles/index.css';
export {default as AdminXApp} from './app';
export {routes} from './routes';
export {FeatureFlagsProvider} from './lib/feature-flags';
export {useNotificationsCountForUser} from './hooks/use-activity-pub-queries';
+142
View File
@@ -0,0 +1,142 @@
import AppError from '@components/layout/error';
import {Navigate, Outlet, RouteObject, lazyComponent} from '@tryghost/admin-x-framework';
const basePath = import.meta.env.VITE_TEST ? '' : 'activitypub';
export type CustomRouteObject = RouteObject & {
pageTitle?: string;
children?: CustomRouteObject[];
showBackButton?: boolean;
};
export const routes: CustomRouteObject[] = [
{
// Root route that defines the app's base path
path: basePath,
element: <Outlet />,
errorElement: <AppError />, // This will catch all errors in child routes
handle: 'activitypub-basepath',
children: [
{
index: true,
element: <Navigate to="reader" />
},
{
path: 'inbox',
element: <Navigate to="../reader" replace />
},
{
path: 'feed',
element: <Navigate to="../notes" replace />
},
{
path: 'reader',
lazy: lazyComponent(() => import('./views/inbox')),
pageTitle: 'Reader'
},
{
path: 'reader/:postId',
lazy: lazyComponent(() => import('./views/inbox')),
pageTitle: 'Reader'
},
{
path: 'notes',
lazy: lazyComponent(() => import('./views/feed/feed')),
pageTitle: 'Notes'
},
{
path: 'notes/:postId',
lazy: lazyComponent(() => import('./views/feed/note')),
pageTitle: 'Note'
},
{
path: 'notifications',
lazy: lazyComponent(() => import('./views/notifications')),
pageTitle: 'Notifications'
},
{
path: 'explore',
lazy: lazyComponent(() => import('./views/explore')),
pageTitle: 'Explore'
},
{
path: 'explore/:topic',
lazy: lazyComponent(() => import('./views/explore')),
pageTitle: 'Explore'
},
{
path: 'profile',
lazy: lazyComponent(() => import('./views/profile')),
pageTitle: 'Profile'
},
{
path: 'profile/likes',
lazy: lazyComponent(() => import('./views/profile')),
pageTitle: 'Profile'
},
{
path: 'profile/following',
lazy: lazyComponent(() => import('./views/profile')),
pageTitle: 'Profile'
},
{
path: 'profile/followers',
lazy: lazyComponent(() => import('./views/profile')),
pageTitle: 'Profile'
},
{
path: 'profile/:handle/:tab?',
lazy: lazyComponent(() => import('./views/profile')),
pageTitle: 'Profile'
},
{
path: 'preferences',
lazy: lazyComponent(() => import('./views/preferences')),
pageTitle: 'Preferences'
},
{
path: 'preferences/moderation',
lazy: lazyComponent(() => import('./views/preferences/components/moderation')),
pageTitle: 'Moderation',
showBackButton: true
},
{
path: 'preferences/bluesky-sharing',
lazy: lazyComponent(() => import('./views/preferences/components/bluesky-sharing')),
showBackButton: true
},
{
path: 'welcome',
lazy: lazyComponent(() => import('./components/layout/onboarding')),
pageTitle: 'Welcome',
children: [
{
path: '',
element: <Navigate to="1" replace />
},
{
path: '1',
lazy: lazyComponent(() => import('./components/layout/onboarding/step-1'))
},
{
path: '2',
lazy: lazyComponent(() => import('./components/layout/onboarding/step-2'))
},
{
path: '3',
lazy: lazyComponent(() => import('./components/layout/onboarding/step-3'))
},
{
path: '*',
element: <Navigate to="1" replace />
}
]
},
{
path: '*',
lazy: lazyComponent(() => import('./components/layout/error'))
}
]
}
];
+5
View File
@@ -0,0 +1,5 @@
import './styles/index.css';
import App from './app.tsx';
import renderStandaloneApp from '@tryghost/admin-x-framework/test/render';
renderStandaloneApp(App, {});
+10
View File
@@ -0,0 +1,10 @@
module.exports = {
plugins: ['ghost'],
extends: [
'plugin:ghost/ts-test'
],
rules: {
// Enforce kebab-case (lowercase with hyphens) for all filenames
'ghost/filenames/match-regex': ['error', '^[a-z0-9.-]+$', false]
}
};
@@ -0,0 +1,16 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"noEmit": false,
"composite": true,
"declaration": true,
"declarationMap": true,
"declarationDir": "./types",
"emitDeclarationOnly": true,
"tsBuildInfoFile": "./types/tsconfig.tsbuildinfo",
"rootDir": "./src"
},
"include": ["src"],
"exclude": ["src/**/*.stories.tsx", "src/**/*.test.ts", "src/**/*.test.tsx"]
}
+36
View File
@@ -0,0 +1,36 @@
{
"compilerOptions": {
"target": "ESNext",
"lib": ["DOM", "DOM.Iterable", "ESNext"],
"module": "ESNext",
"skipLibCheck": true,
"types": ["vite/client", "jest"],
/* Bundler mode */
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "react-jsx",
/* Linting */
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"erasableSyntaxOnly": true,
"noFallthroughCasesInSwitch": true,
/* Path aliases */
"baseUrl": "./src",
"paths": {
"@src/*": ["*"],
"@assets/*": ["assets/*"],
"@components/*": ["components/*"],
"@hooks/*": ["hooks/*"],
"@utils/*": ["utils/*"],
"@views/*": ["views/*"]
}
},
"include": ["src", "test"]
}
+72
View File
@@ -0,0 +1,72 @@
import adminXViteConfig from '@tryghost/admin-x-framework/vite';
import pkg from './package.json';
import {resolve} from 'path';
import fs from 'fs';
const GHOST_CARDS_PATH = resolve(__dirname, '../../ghost/core/core/frontend/src/cards');
const validateCardsDirectoryPlugin = (cardsPath) => {
return {
name: 'validate-cards-directory',
buildStart() {
const jsPath = resolve(cardsPath, 'js');
const cssPath = resolve(cardsPath, 'css');
if (!fs.existsSync(cardsPath)) {
throw new Error(`Ghost cards directory not found at: ${cardsPath}`);
}
if (!fs.existsSync(jsPath)) {
throw new Error(`Ghost cards JS directory not found at: ${jsPath}`);
}
if (!fs.existsSync(cssPath)) {
throw new Error(`Ghost cards CSS directory not found at: ${cssPath}`);
}
const jsFiles = fs.readdirSync(jsPath).filter(f => f.endsWith('.js'));
const cssFiles = fs.readdirSync(cssPath).filter(f => f.endsWith('.css'));
if (jsFiles.length === 0) {
throw new Error(`No JavaScript files found in Ghost cards directory: ${jsPath}`);
}
if (cssFiles.length === 0) {
throw new Error(`No CSS files found in Ghost cards directory: ${cssPath}`);
}
console.log(`✓ Found ${jsFiles.length} JS and ${cssFiles.length} CSS card files at: ${cardsPath}`);
}
};
};
export default (function viteConfig() {
const config = adminXViteConfig({
packageName: pkg.name,
entry: resolve(__dirname, 'src/index.tsx'),
overrides: {
test: {
include: [
'./test/unit/**/*',
'./src/**/*.test.ts'
]
},
resolve: {
alias: {
'@src': resolve(__dirname, './src'),
'@assets': resolve(__dirname, './src/assets'),
'@components': resolve(__dirname, './src/components'),
'@hooks': resolve(__dirname, './src/hooks'),
'@utils': resolve(__dirname, './src/utils'),
'@views': resolve(__dirname, './src/views'),
'@ghost-cards': GHOST_CARDS_PATH
}
},
plugins: [
validateCardsDirectoryPlugin(GHOST_CARDS_PATH)
]
}
});
return config;
});