This commit is contained in:
@@ -0,0 +1 @@
|
||||
tailwind.config.cjs
|
||||
@@ -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'
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,4 @@
|
||||
dist
|
||||
types
|
||||
playwright-report
|
||||
test-results
|
||||
@@ -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>
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
import {adminXPlaywrightConfig} from '@tryghost/admin-x-framework/playwright';
|
||||
|
||||
export default adminXPlaywrightConfig();
|
||||
@@ -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;
|
||||
@@ -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';
|
||||
@@ -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'))
|
||||
}
|
||||
]
|
||||
}
|
||||
];
|
||||
@@ -0,0 +1,5 @@
|
||||
import './styles/index.css';
|
||||
import App from './app.tsx';
|
||||
import renderStandaloneApp from '@tryghost/admin-x-framework/test/render';
|
||||
|
||||
renderStandaloneApp(App, {});
|
||||
@@ -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"]
|
||||
}
|
||||
|
||||
@@ -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"]
|
||||
}
|
||||
@@ -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;
|
||||
});
|
||||
Reference in New Issue
Block a user