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
+70
View File
@@ -0,0 +1,70 @@
/* 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: {
// 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).'
}]
}],
// Enforce kebab-case (lowercase with hyphens) for all filenames
'ghost/filenames/match-regex': ['error', '^[a-z0-9.-]+$', false],
// 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
+16
View File
@@ -0,0 +1,16 @@
<!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>Posts</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/standalone.tsx"></script>
</body>
</html>
+87
View File
@@ -0,0 +1,87 @@
{
"name": "@tryghost/posts",
"version": "0.0.0",
"license": "MIT",
"repository": {
"type": "git",
"url": "https://github.com/TryGhost/Ghost/tree/main/apps/posts"
},
"author": "Ghost Foundation",
"files": [
"LICENSE",
"README.md",
"dist/"
],
"main": "./dist/posts.umd.cjs",
"module": "./dist/posts.js",
"exports": {
".": {
"import": "./dist/posts.js",
"require": "./dist/posts.umd.cjs"
},
"./api": "./src/api.ts",
"./members": "./src/views/members/members.tsx"
},
"private": true,
"scripts": {
"dev": "vite build --watch",
"dev:start": "vite",
"build": "tsc && vite build",
"test:unit": "vitest run test/unit",
"lint": "pnpm run lint:code && pnpm run lint:test",
"lint:code": "eslint --ext .js,.ts,.cjs,.tsx --cache src",
"lint:code:fix": "eslint --ext .js,.ts,.cjs,.tsx --cache --fix src",
"lint:test": "eslint -c test/.eslintrc.cjs --ext .js,.ts,.cjs,.tsx --cache test",
"lint:fix": "eslint --ext .js,.ts,.cjs,.tsx --cache src --fix",
"preview": "vite preview",
"test": "pnpm test:unit --coverage"
},
"devDependencies": {
"@playwright/test": "1.59.1",
"@tanstack/react-query": "4.36.1",
"@tanstack/react-virtual": "3.13.23",
"@testing-library/jest-dom": "^6",
"@testing-library/react": "14.3.1",
"@types/jest": "29.5.14",
"@types/react": "18.3.28",
"@vitest/coverage-v8": "^1.6.1",
"msw": "2.12.14",
"tailwindcss": "^4.2.2",
"vite": "5.4.21",
"vitest": "1.6.1"
},
"dependencies": {
"@tryghost/admin-x-framework": "workspace:*",
"@tryghost/nql-lang": "0.6.4",
"@tryghost/shade": "workspace:*",
"i18n-iso-countries": "7.14.0",
"moment": "2.24.0",
"moment-timezone": "0.5.45",
"papaparse": "5.5.3",
"react": "18.3.1",
"react-dom": "18.3.1",
"react-router": "7.14.0",
"sonner": "2.0.7",
"use-debounce": "10.1.1",
"zod": "4.1.12"
},
"nx": {
"targets": {
"dev": {
"dependsOn": [
"^build"
]
},
"test:unit": {
"dependsOn": [
"^build"
]
},
"test:acceptance": {
"dependsOn": [
"^build"
]
}
}
}
}
+3
View File
@@ -0,0 +1,3 @@
import {adminXPlaywrightConfig} from '@tryghost/admin-x-framework/playwright';
export default adminXPlaywrightConfig();
+8
View File
@@ -0,0 +1,8 @@
/**
* Public API for cross-package imports.
* Admin uses these exports instead of reaching into src/ directly.
*/
export {default as PostsAppContextProvider} from './providers/posts-app-context';
export {routes} from './routes';
export {parseAllSharedViewsJSON} from './views/members/shared-views';
export type {SharedView, AllSharedViewsParseResult} from './views/members/shared-views';
+43
View File
@@ -0,0 +1,43 @@
import PostsAppContextProvider, {PostsAppContextType} from './providers/posts-app-context';
import PostsErrorBoundary from '@components/errors/posts-error-boundary';
import React from 'react';
import {APP_ROUTE_PREFIX, routes} from '@src/routes';
import {BaseAppProps, FrameworkProvider, Outlet, RouterProvider} from '@tryghost/admin-x-framework';
import {ShadeApp} from '@tryghost/shade/app';
interface AppProps extends BaseAppProps {
fromAnalytics?: boolean;
}
const App: React.FC<AppProps> = ({framework, designSystem, fromAnalytics = false, appSettings}) => {
const appContextValue: PostsAppContextType = {
appSettings,
externalNavigate: (url: string) => {
window.location.href = url;
},
fromAnalytics
};
return (
<FrameworkProvider
{...framework}
queryClientOptions={{
staleTime: 0, // Always consider data stale (matches Ember admin route behavior)
refetchOnMount: true, // Always refetch when component mounts (matches Ember route model)
refetchOnWindowFocus: false // Disable window focus refetch (Ember admin doesn't have this)
}}
>
<PostsAppContextProvider value={appContextValue}>
<RouterProvider prefix={APP_ROUTE_PREFIX} routes={routes}>
<PostsErrorBoundary>
<ShadeApp className="shade-posts app-container" darkMode={designSystem.darkMode} fetchKoenigLexical={null}>
<Outlet />
</ShadeApp>
</PostsErrorBoundary>
</RouterProvider>
</PostsAppContextProvider>
</FrameworkProvider>
);
};
export default App;
+6
View File
@@ -0,0 +1,6 @@
import './styles/index.css';
import App from './app';
export {
App as AdminXApp
};
+7
View File
@@ -0,0 +1,7 @@
declare module '@tryghost/nql-lang' {
const nql: {
parse(input: string): unknown;
};
export default nql;
}
+80
View File
@@ -0,0 +1,80 @@
import {ErrorPage} from '@tryghost/shade/primitives';
import {RouteObject, lazyComponent} from '@tryghost/admin-x-framework';
// import {withFeatureFlag} from '@src/hooks/with-feature-flag';
export const APP_ROUTE_PREFIX = '/';
// Wrap components with feature flag protection where needed
// e.g.
// const ProtectedNewsletter = withFeatureFlag(Newsletter, 'trafficAnalyticsAlpha', '/analytics/', 'Newsletter');
export const routes: RouteObject[] = [
{
// Root route configuration
path: '',
errorElement: <ErrorPage onBackToDashboard={() => {}} />, // @TODO: add back to dashboard click handle
children: [
{
// Post Analytics
path: 'posts/analytics/:postId',
lazy: async () => {
const [
{default: PostAnalyticsProvider},
{default: PostAnalytics}
] = await Promise.all([
import('./providers/post-analytics-context'),
import('./views/PostAnalytics/post-analytics')
]);
return {
element: (
<PostAnalyticsProvider>
<PostAnalytics />
</PostAnalyticsProvider>
)
};
},
children: [
{
path: '',
lazy: lazyComponent(() => import('@views/PostAnalytics/Overview/overview'))
},
{
path: 'web',
lazy: lazyComponent(() => import('@views/PostAnalytics/Web/web'))
},
{
path: 'growth',
lazy: lazyComponent(() => import('@views/PostAnalytics/Growth/growth'))
},
{
path: 'newsletter',
lazy: lazyComponent(() => import('@views/PostAnalytics/Newsletter/newsletter'))
}
]
},
{
path: 'tags',
children: [
{
index: true,
lazy: lazyComponent(() => import('@views/Tags/tags'))
},
{
path: ':tagSlug',
element: null
}
]
},
{
path: 'comments',
lazy: lazyComponent(() => import('@views/comments/comments'))
},
// Error handling
{
path: '*',
element: <ErrorPage onBackToDashboard={() => {}} /> // @TODO: add back to dashboard click handle
}
]
}
];
+5
View File
@@ -0,0 +1,5 @@
import './styles/index.css';
import App from './app';
import renderShadeApp from '@tryghost/admin-x-framework/test/render-shade';
renderShadeApp(App, {});
+1
View File
@@ -0,0 +1 @@
declare module 'papaparse';
+10
View File
@@ -0,0 +1,10 @@
module.exports = {
plugins: ['ghost'],
extends: [
'plugin:ghost/ts-test'
],
rules: {
// We aren't using Mocha so we can disable some Ghost test rules.
'ghost/mocha/no-mocha-arrows': 'off'
}
};
+5
View File
@@ -0,0 +1,5 @@
import '@testing-library/jest-dom/vitest';
import {setupShadeMocks} from '@tryghost/admin-x-framework/test/setup';
// Set up common mocks for shade components
setupShadeMocks();
+15
View File
@@ -0,0 +1,15 @@
{
"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", "vitest/globals", "@testing-library/jest-dom/vitest"],
/* 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"]
}
+28
View File
@@ -0,0 +1,28 @@
import adminXViteConfig from '@tryghost/admin-x-framework/vite';
import pkg from './package.json';
import {resolve} from 'path';
export default (function viteConfig() {
return 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')
}
}
}
});
});
+3
View File
@@ -0,0 +1,3 @@
import {createVitestConfig} from '@tryghost/admin-x-framework/test/vitest-config';
export default createVitestConfig();