@open-press/cli 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +58 -0
- package/dist/cli.js +365 -0
- package/package.json +57 -0
- package/template/core/.turbo/turbo-test.log +341 -0
- package/template/core/CHANGELOG.md +11 -0
- package/template/core/README.md +36 -0
- package/template/core/engine/chrome-pdf.d.mts +34 -0
- package/template/core/engine/chrome-pdf.mjs +344 -0
- package/template/core/engine/cli.mjs +93 -0
- package/template/core/engine/commands/_shared.mjs +170 -0
- package/template/core/engine/commands/deploy.mjs +31 -0
- package/template/core/engine/commands/dev.mjs +26 -0
- package/template/core/engine/commands/export.mjs +8 -0
- package/template/core/engine/commands/init.mjs +24 -0
- package/template/core/engine/commands/inspect.mjs +35 -0
- package/template/core/engine/commands/migrate-to-react.mjs +27 -0
- package/template/core/engine/commands/pdf.mjs +26 -0
- package/template/core/engine/commands/preview.mjs +26 -0
- package/template/core/engine/commands/render.mjs +17 -0
- package/template/core/engine/commands/replace.mjs +41 -0
- package/template/core/engine/commands/search.mjs +33 -0
- package/template/core/engine/commands/typecheck.mjs +5 -0
- package/template/core/engine/commands/validate.mjs +17 -0
- package/template/core/engine/config.d.mts +40 -0
- package/template/core/engine/config.mjs +160 -0
- package/template/core/engine/deploy-sync.mjs +15 -0
- package/template/core/engine/document-export.mjs +15 -0
- package/template/core/engine/file-utils.mjs +106 -0
- package/template/core/engine/fonts.mjs +62 -0
- package/template/core/engine/init.mjs +90 -0
- package/template/core/engine/inspection.mjs +348 -0
- package/template/core/engine/issue-report.mjs +44 -0
- package/template/core/engine/katex-assets.mjs +45 -0
- package/template/core/engine/page-block.mjs +30 -0
- package/template/core/engine/page-renderer.mjs +217 -0
- package/template/core/engine/pdf-media.mjs +45 -0
- package/template/core/engine/public-assets.mjs +19 -0
- package/template/core/engine/react/chapter-css.mjs +53 -0
- package/template/core/engine/react/comment-endpoint.d.mts +11 -0
- package/template/core/engine/react/comment-endpoint.mjs +128 -0
- package/template/core/engine/react/comment-marker.mjs +306 -0
- package/template/core/engine/react/document-entry.mjs +253 -0
- package/template/core/engine/react/document-export.mjs +392 -0
- package/template/core/engine/react/mdx-compile.mjs +295 -0
- package/template/core/engine/react/measurement-css.mjs +44 -0
- package/template/core/engine/react/migrate-to-react.mjs +355 -0
- package/template/core/engine/react/pagination-constants.mjs +3 -0
- package/template/core/engine/react/pagination.mjs +121 -0
- package/template/core/engine/react/project-asset-endpoint.d.mts +10 -0
- package/template/core/engine/react/project-asset-endpoint.mjs +379 -0
- package/template/core/engine/react/workspace-discovery.mjs +156 -0
- package/template/core/engine/source-text-tools.mjs +280 -0
- package/template/core/engine/source-workspace.mjs +76 -0
- package/template/core/engine/static-server.mjs +493 -0
- package/template/core/engine/validation.mjs +172 -0
- package/template/core/index.html +13 -0
- package/template/core/openpress.config.mjs +12 -0
- package/template/core/package.json +86 -0
- package/template/core/src/main.tsx +16 -0
- package/template/core/src/openpress/App.tsx +127 -0
- package/template/core/src/openpress/composerMentions.ts +188 -0
- package/template/core/src/openpress/core/basePages.tsx +87 -0
- package/template/core/src/openpress/core/index.tsx +20 -0
- package/template/core/src/openpress/core/types.ts +71 -0
- package/template/core/src/openpress/frameScheduler.ts +32 -0
- package/template/core/src/openpress/indexes.ts +329 -0
- package/template/core/src/openpress/inspector.ts +282 -0
- package/template/core/src/openpress/pageRoute.ts +21 -0
- package/template/core/src/openpress/pagination.ts +845 -0
- package/template/core/src/openpress/projectIdentity.ts +15 -0
- package/template/core/src/openpress/projectSources.ts +24 -0
- package/template/core/src/openpress/projectWorkspace.tsx +919 -0
- package/template/core/src/openpress/publicPage.tsx +469 -0
- package/template/core/src/openpress/reactDocumentMetadata.ts +41 -0
- package/template/core/src/openpress/readerPageRegistry.ts +41 -0
- package/template/core/src/openpress/readerRuntime.ts +230 -0
- package/template/core/src/openpress/readerScroll.ts +92 -0
- package/template/core/src/openpress/readerState.ts +15 -0
- package/template/core/src/openpress/renderer.tsx +91 -0
- package/template/core/src/openpress/runtimeMode.ts +22 -0
- package/template/core/src/openpress/types.ts +112 -0
- package/template/core/src/openpress/workbench.tsx +1299 -0
- package/template/core/src/openpress/workbenchPanels.tsx +122 -0
- package/template/core/src/openpress/workbenchTypes.ts +4 -0
- package/template/core/src/styles/openpress/app-shell.css +251 -0
- package/template/core/src/styles/openpress/media-workspace.css +230 -0
- package/template/core/src/styles/openpress/print-route.css +186 -0
- package/template/core/src/styles/openpress/project-workspace.css +1318 -0
- package/template/core/src/styles/openpress/public-viewer.css +983 -0
- package/template/core/src/styles/openpress/reader-runtime.css +792 -0
- package/template/core/src/styles/openpress/responsive.css +384 -0
- package/template/core/src/styles/openpress/workbench-panels.css +558 -0
- package/template/core/src/styles/openpress/workbench.css +720 -0
- package/template/core/src/styles/openpress.css +14 -0
- package/template/core/src/vite-env.d.ts +9 -0
- package/template/core/tsconfig.json +37 -0
- package/template/core/vite.config.ts +512 -0
- package/template/skills/chinese-ai-writing-polish/SKILL.md +195 -0
- package/template/skills/claude-document/SKILL.md +66 -0
- package/template/skills/claude-document/starter/document/chapters/01-document-shape/chapter.tsx +30 -0
- package/template/skills/claude-document/starter/document/chapters/01-document-shape/content/01-document-shape.mdx +51 -0
- package/template/skills/claude-document/starter/document/chapters/02-review-loop/chapter.tsx +30 -0
- package/template/skills/claude-document/starter/document/chapters/02-review-loop/content/01-review-loop.mdx +31 -0
- package/template/skills/claude-document/starter/document/components/ChapterOpenerVisual.tsx +96 -0
- package/template/skills/claude-document/starter/document/components/Page.tsx +27 -0
- package/template/skills/claude-document/starter/document/design.md +142 -0
- package/template/skills/claude-document/starter/document/index.tsx +89 -0
- package/template/skills/claude-document/starter/document/media/README.md +13 -0
- package/template/skills/claude-document/starter/document/openpress.config.mjs +26 -0
- package/template/skills/claude-document/starter/document/theme/README.md +15 -0
- package/template/skills/claude-document/starter/document/theme/base/page-contract.css +525 -0
- package/template/skills/claude-document/starter/document/theme/base/print.css +93 -0
- package/template/skills/claude-document/starter/document/theme/base/typography.css +612 -0
- package/template/skills/claude-document/starter/document/theme/fonts.css +4 -0
- package/template/skills/claude-document/starter/document/theme/page-surfaces/back-cover.css +72 -0
- package/template/skills/claude-document/starter/document/theme/page-surfaces/chapter-opener.css +236 -0
- package/template/skills/claude-document/starter/document/theme/page-surfaces/cover.css +309 -0
- package/template/skills/claude-document/starter/document/theme/page-surfaces/toc.css +213 -0
- package/template/skills/claude-document/starter/document/theme/patterns/_chart-frame.css +53 -0
- package/template/skills/claude-document/starter/document/theme/patterns/figure-grid.css +68 -0
- package/template/skills/claude-document/starter/document/theme/patterns/table-utilities.css +66 -0
- package/template/skills/claude-document/starter/document/theme/shell/reader-controls.css +789 -0
- package/template/skills/claude-document/starter/document/theme/tokens.css +89 -0
- package/template/skills/claude-document/starter/openpress.config.mjs +5 -0
- package/template/skills/editorial-monograph/SKILL.md +73 -0
- package/template/skills/editorial-monograph/starter/document/chapters/01-product-and-use-cases/content/01-product-and-use-cases.mdx +31 -0
- package/template/skills/editorial-monograph/starter/document/chapters/02-workflow/content/01-workflow.mdx +89 -0
- package/template/skills/editorial-monograph/starter/document/chapters/03-agent-skills-contributors/content/01-agent-skills-contributors.mdx +52 -0
- package/template/skills/editorial-monograph/starter/document/chapters/04-validation-deploy/content/01-validation-deploy.mdx +39 -0
- package/template/skills/editorial-monograph/starter/document/components/ChapterOpenerVisual/index.tsx +76 -0
- package/template/skills/editorial-monograph/starter/document/components/Page.tsx +27 -0
- package/template/skills/editorial-monograph/starter/document/components/TokenSwatchGrid/index.tsx +46 -0
- package/template/skills/editorial-monograph/starter/document/components/TokenSwatchGrid/style.css +63 -0
- package/template/skills/editorial-monograph/starter/document/components/TypeSpecimen/index.tsx +38 -0
- package/template/skills/editorial-monograph/starter/document/components/TypeSpecimen/style.css +111 -0
- package/template/skills/editorial-monograph/starter/document/design.md +279 -0
- package/template/skills/editorial-monograph/starter/document/index.tsx +73 -0
- package/template/skills/editorial-monograph/starter/document/media/README.md +13 -0
- package/template/skills/editorial-monograph/starter/document/openpress.config.mjs +26 -0
- package/template/skills/editorial-monograph/starter/document/theme/README.md +11 -0
- package/template/skills/editorial-monograph/starter/document/theme/base/page-contract.css +505 -0
- package/template/skills/editorial-monograph/starter/document/theme/base/print.css +93 -0
- package/template/skills/editorial-monograph/starter/document/theme/base/typography.css +336 -0
- package/template/skills/editorial-monograph/starter/document/theme/fonts.css +3 -0
- package/template/skills/editorial-monograph/starter/document/theme/page-surfaces/back-cover.css +43 -0
- package/template/skills/editorial-monograph/starter/document/theme/page-surfaces/chapter-opener.css +205 -0
- package/template/skills/editorial-monograph/starter/document/theme/page-surfaces/cover.css +147 -0
- package/template/skills/editorial-monograph/starter/document/theme/page-surfaces/toc.css +139 -0
- package/template/skills/editorial-monograph/starter/document/theme/patterns/_chart-frame.css +49 -0
- package/template/skills/editorial-monograph/starter/document/theme/patterns/figure-grid.css +68 -0
- package/template/skills/editorial-monograph/starter/document/theme/patterns/table-utilities.css +66 -0
- package/template/skills/editorial-monograph/starter/document/theme/shell/reader-controls.css +761 -0
- package/template/skills/editorial-monograph/starter/document/theme/tokens.css +80 -0
- package/template/skills/editorial-monograph/starter/openpress.config.mjs +5 -0
- package/template/skills/openpress/SKILL.md +114 -0
- package/template/skills/openpress/references/cli-commands.md +31 -0
- package/template/skills/openpress/references/local-review.md +43 -0
- package/template/skills/openpress-deploy/SKILL.md +69 -0
- package/template/skills/openpress-deploy/references/cloudflare-pages.md +51 -0
- package/template/skills/openpress-design/SKILL.md +51 -0
- package/template/skills/openpress-design/references/pdf-safe-css.md +29 -0
- package/template/skills/openpress-design/references/responsive-fixed-layout.md +48 -0
- package/template/skills/openpress-design/references/theme-and-components.md +77 -0
- package/template/skills/openpress-diagram-drawing/SKILL.md +44 -0
- package/template/skills/openpress-diagram-drawing/references/diagram-patterns.md +93 -0
- package/template/skills/openpress-document-hierarchy/SKILL.md +81 -0
- package/template/skills/openpress-document-hierarchy/agents/openai.yaml +4 -0
- package/template/skills/openpress-document-hierarchy/references/data-structures-outline.md +115 -0
- package/template/skills/openpress-init/SKILL.md +84 -0
- package/template/skills/openpress-style-pack-contributor/SKILL.md +62 -0
- package/template/skills/openpress-style-pack-contributor/references/starter-contract.md +49 -0
- package/template/skills/openpress-update/SKILL.md +88 -0
- package/template/skills/openpress-writing/SKILL.md +68 -0
- package/template/skills/openpress-writing/references/source-and-writing-rules.md +120 -0
- package/template/skills/teaching-notes-writing/SKILL.md +54 -0
- package/template/skills/teaching-notes-writing/references/programming.md +65 -0
- package/template/skills/teaching-notes-writing/references/teaching-patterns.md +60 -0
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@open-press/core",
|
|
3
|
+
"version": "0.3.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"description": "open-press core — runtime primitives, CLI, and render pipeline for AI-first fixed-layout documents.",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"author": "quan0715",
|
|
8
|
+
"homepage": "https://github.com/quan0715/open-press#readme",
|
|
9
|
+
"repository": {
|
|
10
|
+
"type": "git",
|
|
11
|
+
"url": "git+https://github.com/quan0715/open-press.git",
|
|
12
|
+
"directory": "packages/core"
|
|
13
|
+
},
|
|
14
|
+
"bugs": {
|
|
15
|
+
"url": "https://github.com/quan0715/open-press/issues"
|
|
16
|
+
},
|
|
17
|
+
"keywords": [
|
|
18
|
+
"document",
|
|
19
|
+
"mdx",
|
|
20
|
+
"react",
|
|
21
|
+
"pdf",
|
|
22
|
+
"a4",
|
|
23
|
+
"fixed-layout",
|
|
24
|
+
"ai-first"
|
|
25
|
+
],
|
|
26
|
+
"main": "./src/openpress/core/index.tsx",
|
|
27
|
+
"types": "./src/openpress/core/index.tsx",
|
|
28
|
+
"exports": {
|
|
29
|
+
".": "./src/openpress/core/index.tsx"
|
|
30
|
+
},
|
|
31
|
+
"bin": {
|
|
32
|
+
"open-press": "engine/cli.mjs"
|
|
33
|
+
},
|
|
34
|
+
"files": [
|
|
35
|
+
"engine",
|
|
36
|
+
"src/openpress",
|
|
37
|
+
"src/styles",
|
|
38
|
+
"index.html",
|
|
39
|
+
"vite.config.ts",
|
|
40
|
+
"tsconfig.json"
|
|
41
|
+
],
|
|
42
|
+
"publishConfig": {
|
|
43
|
+
"access": "public"
|
|
44
|
+
},
|
|
45
|
+
"scripts": {
|
|
46
|
+
"dev": "node engine/cli.mjs dev . --renderer react",
|
|
47
|
+
"build": "node engine/cli.mjs render . --renderer react",
|
|
48
|
+
"preview": "node engine/cli.mjs preview . --renderer react",
|
|
49
|
+
"typecheck": "node engine/cli.mjs typecheck .",
|
|
50
|
+
"test": "pnpm run test:node && pnpm run test:react",
|
|
51
|
+
"test:e2e:reader": "playwright test --config playwright.reader.config.ts",
|
|
52
|
+
"test:node": "node --test tests/*.test.mjs",
|
|
53
|
+
"test:react": "vitest run",
|
|
54
|
+
"openpress:validate": "node engine/cli.mjs validate .",
|
|
55
|
+
"openpress:export": "node engine/cli.mjs export .",
|
|
56
|
+
"openpress:pdf": "node engine/cli.mjs pdf .",
|
|
57
|
+
"openpress:render": "node engine/cli.mjs render . --renderer react",
|
|
58
|
+
"openpress:preview": "node engine/cli.mjs preview . --renderer react",
|
|
59
|
+
"openpress:deploy": "node engine/cli.mjs deploy .",
|
|
60
|
+
"openpress:deploy:dry-run": "node engine/cli.mjs deploy . --confirm --dry-run"
|
|
61
|
+
},
|
|
62
|
+
"dependencies": {
|
|
63
|
+
"@mdx-js/mdx": "^3.1.1",
|
|
64
|
+
"@mdx-js/react": "^3.1.1",
|
|
65
|
+
"js-yaml": "^4.1.1",
|
|
66
|
+
"katex": "^0.16.47",
|
|
67
|
+
"lucide-react": "^1.16.0",
|
|
68
|
+
"react": "^19.2.6",
|
|
69
|
+
"react-dom": "^19.2.6",
|
|
70
|
+
"rehype-katex": "^7.0.1",
|
|
71
|
+
"remark-gfm": "^4.0.1",
|
|
72
|
+
"remark-math": "^6.0.0"
|
|
73
|
+
},
|
|
74
|
+
"devDependencies": {
|
|
75
|
+
"@playwright/test": "^1.60.0",
|
|
76
|
+
"@testing-library/react": "^16.3.2",
|
|
77
|
+
"@types/node": "^25.8.0",
|
|
78
|
+
"@types/react": "^19.2.14",
|
|
79
|
+
"@types/react-dom": "^19.2.3",
|
|
80
|
+
"@vitejs/plugin-react": "^6.0.2",
|
|
81
|
+
"jsdom": "^26.1.0",
|
|
82
|
+
"typescript": "^6.0.3",
|
|
83
|
+
"vite": "^8.0.13",
|
|
84
|
+
"vitest": "^4.1.6"
|
|
85
|
+
}
|
|
86
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { StrictMode } from "react";
|
|
2
|
+
import { createRoot } from "react-dom/client";
|
|
3
|
+
import { App } from "./openpress/App";
|
|
4
|
+
import "./styles/openpress.css";
|
|
5
|
+
|
|
6
|
+
const rootElement = document.getElementById("root");
|
|
7
|
+
|
|
8
|
+
if (!rootElement) {
|
|
9
|
+
throw new Error("OpenPress renderer requires a #root element.");
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
createRoot(rootElement).render(
|
|
13
|
+
<StrictMode>
|
|
14
|
+
<App />
|
|
15
|
+
</StrictMode>,
|
|
16
|
+
);
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import { useEffect, useState } from "react";
|
|
2
|
+
import { Renderer } from "./renderer";
|
|
3
|
+
import { isLocalWorkspaceHost } from "./runtimeMode";
|
|
4
|
+
import type { DeploymentInfo, ReaderDocument } from "./types";
|
|
5
|
+
|
|
6
|
+
type LoadState =
|
|
7
|
+
| { status: "loading" }
|
|
8
|
+
| {
|
|
9
|
+
status: "ready";
|
|
10
|
+
document: ReaderDocument;
|
|
11
|
+
deploymentInfo: DeploymentInfo;
|
|
12
|
+
}
|
|
13
|
+
| { status: "error"; message: string };
|
|
14
|
+
|
|
15
|
+
interface DeployConfig {
|
|
16
|
+
pdf?: string;
|
|
17
|
+
deployed_at?: string;
|
|
18
|
+
public_url?: string;
|
|
19
|
+
dirty?: boolean;
|
|
20
|
+
deploy_configured?: boolean;
|
|
21
|
+
deploy_adapter?: string;
|
|
22
|
+
deploy_source?: string;
|
|
23
|
+
deploy_project_name?: string | null;
|
|
24
|
+
deploy_setup_message?: string;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const offlineDeploymentInfo: DeploymentInfo = { online: false };
|
|
28
|
+
|
|
29
|
+
function LoadingScreen() {
|
|
30
|
+
return (
|
|
31
|
+
<div className="openpress-loading-screen" aria-label="載入中" role="status">
|
|
32
|
+
<div className="openpress-loading-screen__inner">
|
|
33
|
+
<div className="openpress-loading-dots" aria-hidden="true">
|
|
34
|
+
<span /><span /><span />
|
|
35
|
+
</div>
|
|
36
|
+
<span className="openpress-loading-screen__label">載入文件</span>
|
|
37
|
+
</div>
|
|
38
|
+
</div>
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function App() {
|
|
43
|
+
const [state, setState] = useState<LoadState>({ status: "loading" });
|
|
44
|
+
|
|
45
|
+
useEffect(() => {
|
|
46
|
+
let cancelled = false;
|
|
47
|
+
|
|
48
|
+
async function loadDocument() {
|
|
49
|
+
try {
|
|
50
|
+
const [response, deploymentInfo] = await Promise.all([
|
|
51
|
+
fetch("/openpress/document.json", { cache: "no-store" }),
|
|
52
|
+
loadDeploymentInfo(),
|
|
53
|
+
]);
|
|
54
|
+
if (!response.ok) {
|
|
55
|
+
throw new Error(`Unable to load /openpress/document.json (${response.status})`);
|
|
56
|
+
}
|
|
57
|
+
const document = (await response.json()) as ReaderDocument;
|
|
58
|
+
if (!cancelled) {
|
|
59
|
+
setState({ status: "ready", document, deploymentInfo });
|
|
60
|
+
}
|
|
61
|
+
} catch (error) {
|
|
62
|
+
if (!cancelled) {
|
|
63
|
+
setState({
|
|
64
|
+
status: "error",
|
|
65
|
+
message: error instanceof Error ? error.message : "Unable to load OpenPress document.",
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
void loadDocument();
|
|
72
|
+
return () => {
|
|
73
|
+
cancelled = true;
|
|
74
|
+
};
|
|
75
|
+
}, []);
|
|
76
|
+
|
|
77
|
+
if (state.status === "loading") return <LoadingScreen />;
|
|
78
|
+
|
|
79
|
+
if (state.status === "error") {
|
|
80
|
+
return <div className="openpress-load-state openpress-load-state--error">{state.message}</div>;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
return (
|
|
84
|
+
<Renderer
|
|
85
|
+
document={state.document}
|
|
86
|
+
deploymentInfo={state.deploymentInfo}
|
|
87
|
+
/>
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
async function loadDeploymentInfo(): Promise<DeploymentInfo> {
|
|
92
|
+
if (typeof window !== "undefined" && isLocalWorkspaceHost(window.location.hostname)) {
|
|
93
|
+
const localInfo = await loadDeploymentInfoFrom("/__openpress/status");
|
|
94
|
+
if (localInfo) return localInfo;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
return (await loadDeploymentInfoFrom("/openpress/deploy.json")) ?? offlineDeploymentInfo;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
async function loadDeploymentInfoFrom(path: string): Promise<DeploymentInfo | null> {
|
|
101
|
+
try {
|
|
102
|
+
const response = await fetch(path, { cache: "no-store" });
|
|
103
|
+
if (!response.ok) {
|
|
104
|
+
return null;
|
|
105
|
+
}
|
|
106
|
+
const config = (await response.json()) as DeployConfig;
|
|
107
|
+
return deploymentConfigToInfo(config);
|
|
108
|
+
} catch {
|
|
109
|
+
return null;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function deploymentConfigToInfo(config: DeployConfig): DeploymentInfo {
|
|
114
|
+
const configured = config.deploy_configured !== false;
|
|
115
|
+
return {
|
|
116
|
+
online: configured && Boolean(config.deployed_at || config.public_url),
|
|
117
|
+
deployedAt: config.deployed_at,
|
|
118
|
+
pdf: typeof config.pdf === "string" ? config.pdf : undefined,
|
|
119
|
+
publicUrl: typeof config.public_url === "string" ? config.public_url : undefined,
|
|
120
|
+
dirty: config.dirty === true,
|
|
121
|
+
configured,
|
|
122
|
+
adapter: typeof config.deploy_adapter === "string" ? config.deploy_adapter : undefined,
|
|
123
|
+
source: typeof config.deploy_source === "string" ? config.deploy_source : undefined,
|
|
124
|
+
projectName: typeof config.deploy_project_name === "string" ? config.deploy_project_name : undefined,
|
|
125
|
+
setupMessage: typeof config.deploy_setup_message === "string" ? config.deploy_setup_message : undefined,
|
|
126
|
+
};
|
|
127
|
+
}
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
import { useEffect, useMemo, useState, type KeyboardEvent, type RefObject } from "react";
|
|
2
|
+
|
|
3
|
+
export type ComposerMentionItem = {
|
|
4
|
+
trigger: "@" | "/";
|
|
5
|
+
value: string;
|
|
6
|
+
label: string;
|
|
7
|
+
meta: string;
|
|
8
|
+
kind: "media" | "component" | "skill" | "chapter" | "section" | "prefix";
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
export type ActiveComposerMention = {
|
|
12
|
+
trigger: "@" | "/";
|
|
13
|
+
query: string;
|
|
14
|
+
start: number;
|
|
15
|
+
end: number;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
export function useComposerMentions({
|
|
19
|
+
text,
|
|
20
|
+
items,
|
|
21
|
+
textareaRef,
|
|
22
|
+
onTextChange,
|
|
23
|
+
enabled = true,
|
|
24
|
+
maxSuggestions = 7,
|
|
25
|
+
}: {
|
|
26
|
+
text: string;
|
|
27
|
+
items: ComposerMentionItem[];
|
|
28
|
+
textareaRef: RefObject<HTMLTextAreaElement | null>;
|
|
29
|
+
onTextChange: (value: string) => void;
|
|
30
|
+
enabled?: boolean;
|
|
31
|
+
maxSuggestions?: number;
|
|
32
|
+
}) {
|
|
33
|
+
const [composerCursor, setComposerCursor] = useState(0);
|
|
34
|
+
const [highlightedMentionIndex, setHighlightedMentionIndex] = useState(0);
|
|
35
|
+
const [dismissedMentionKey, setDismissedMentionKey] = useState<string | null>(null);
|
|
36
|
+
const activeMention = enabled ? resolveComposerMention(text, composerCursor) : null;
|
|
37
|
+
const mentionKey = activeMention ? `${activeMention.trigger}:${activeMention.start}:${activeMention.query}` : null;
|
|
38
|
+
const mentionSuggestions = useMemo(() => {
|
|
39
|
+
if (!activeMention) return [];
|
|
40
|
+
if (mentionKey && dismissedMentionKey === mentionKey) return [];
|
|
41
|
+
return createMentionSuggestions(activeMention, items, maxSuggestions);
|
|
42
|
+
}, [activeMention, dismissedMentionKey, items, maxSuggestions, mentionKey]);
|
|
43
|
+
|
|
44
|
+
useEffect(() => {
|
|
45
|
+
setHighlightedMentionIndex(0);
|
|
46
|
+
}, [mentionKey, mentionSuggestions.length]);
|
|
47
|
+
|
|
48
|
+
const syncCursor = () => {
|
|
49
|
+
const textarea = textareaRef.current;
|
|
50
|
+
if (textarea) setComposerCursor(textarea.selectionStart ?? text.length);
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
const insertMention = (item: ComposerMentionItem) => {
|
|
54
|
+
if (!activeMention) return;
|
|
55
|
+
const suffix = item.kind === "prefix" ? "" : " ";
|
|
56
|
+
const nextText = `${text.slice(0, activeMention.start)}${item.value}${suffix}${text.slice(activeMention.end)}`;
|
|
57
|
+
const nextCursor = activeMention.start + item.value.length + suffix.length;
|
|
58
|
+
setDismissedMentionKey(null);
|
|
59
|
+
onTextChange(nextText);
|
|
60
|
+
if (typeof window === "undefined") return;
|
|
61
|
+
window.requestAnimationFrame(() => {
|
|
62
|
+
textareaRef.current?.focus();
|
|
63
|
+
textareaRef.current?.setSelectionRange(nextCursor, nextCursor);
|
|
64
|
+
setComposerCursor(nextCursor);
|
|
65
|
+
});
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
const handleMentionKeyDown = (event: KeyboardEvent<HTMLTextAreaElement>) => {
|
|
69
|
+
if (!activeMention || mentionSuggestions.length === 0) return false;
|
|
70
|
+
if (event.key === "ArrowDown") {
|
|
71
|
+
event.preventDefault();
|
|
72
|
+
setHighlightedMentionIndex((index) => (index + 1) % mentionSuggestions.length);
|
|
73
|
+
return true;
|
|
74
|
+
}
|
|
75
|
+
if (event.key === "ArrowUp") {
|
|
76
|
+
event.preventDefault();
|
|
77
|
+
setHighlightedMentionIndex((index) => (index - 1 + mentionSuggestions.length) % mentionSuggestions.length);
|
|
78
|
+
return true;
|
|
79
|
+
}
|
|
80
|
+
if (event.key === "Enter" || event.key === "Tab") {
|
|
81
|
+
event.preventDefault();
|
|
82
|
+
insertMention(mentionSuggestions[highlightedMentionIndex] ?? mentionSuggestions[0]);
|
|
83
|
+
return true;
|
|
84
|
+
}
|
|
85
|
+
if (event.key === "Escape") {
|
|
86
|
+
event.preventDefault();
|
|
87
|
+
setDismissedMentionKey(mentionKey);
|
|
88
|
+
return true;
|
|
89
|
+
}
|
|
90
|
+
return false;
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
return {
|
|
94
|
+
activeMention,
|
|
95
|
+
handleMentionKeyDown,
|
|
96
|
+
highlightedMentionIndex,
|
|
97
|
+
setHighlightedMentionIndex,
|
|
98
|
+
mentionSuggestions,
|
|
99
|
+
setComposerCursor,
|
|
100
|
+
syncCursor,
|
|
101
|
+
insertMention,
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export function appendComposerToken(text: string, token: string) {
|
|
106
|
+
const trimmedToken = token.trim();
|
|
107
|
+
if (!trimmedToken) return text;
|
|
108
|
+
if (!text.trim()) return `${trimmedToken} `;
|
|
109
|
+
return `${text.replace(/\s*$/, " ")}${trimmedToken} `;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
export function createMentionSuggestions(
|
|
113
|
+
activeMention: ActiveComposerMention,
|
|
114
|
+
items: ComposerMentionItem[],
|
|
115
|
+
maxSuggestions: number,
|
|
116
|
+
) {
|
|
117
|
+
if (activeMention.trigger === "@") {
|
|
118
|
+
const prefixItems = createMentionPrefixItems(items);
|
|
119
|
+
const normalizedQuery = activeMention.query.trim().toLowerCase();
|
|
120
|
+
if (!normalizedQuery) return prefixItems.slice(0, maxSuggestions);
|
|
121
|
+
if (!normalizedQuery.includes("/")) {
|
|
122
|
+
return uniqueMentionItems([
|
|
123
|
+
...prefixItems.filter((item) => mentionMatches(item, normalizedQuery)),
|
|
124
|
+
...items.filter((item) => item.trigger === "@" && mentionMatches(item, normalizedQuery)),
|
|
125
|
+
]).slice(0, maxSuggestions);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
return items
|
|
130
|
+
.filter((item) => item.trigger === activeMention.trigger && mentionMatches(item, activeMention.query))
|
|
131
|
+
.slice(0, maxSuggestions);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function createMentionPrefixItems(items: ComposerMentionItem[]): ComposerMentionItem[] {
|
|
135
|
+
const availableKinds = new Set(items.filter((item) => item.trigger === "@").map((item) => item.kind));
|
|
136
|
+
return MENTION_PREFIX_DEFINITIONS
|
|
137
|
+
.filter((item) => availableKinds.has(item.kind))
|
|
138
|
+
.map((item) => ({
|
|
139
|
+
trigger: "@" as const,
|
|
140
|
+
value: `@${item.kind}/`,
|
|
141
|
+
label: item.kind,
|
|
142
|
+
meta: item.meta,
|
|
143
|
+
kind: "prefix" as const,
|
|
144
|
+
}));
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
function uniqueMentionItems(items: ComposerMentionItem[]) {
|
|
148
|
+
const seen = new Set<string>();
|
|
149
|
+
return items.filter((item) => {
|
|
150
|
+
const key = `${item.kind}:${item.value}`;
|
|
151
|
+
if (seen.has(key)) return false;
|
|
152
|
+
seen.add(key);
|
|
153
|
+
return true;
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
function resolveComposerMention(text: string, cursor: number): ActiveComposerMention | null {
|
|
158
|
+
const safeCursor = clampNumber(cursor, 0, text.length);
|
|
159
|
+
const beforeCursor = text.slice(0, safeCursor);
|
|
160
|
+
const match = beforeCursor.match(/(^|\s)([@/])([^\s@]*)$/);
|
|
161
|
+
if (!match) return null;
|
|
162
|
+
const start = beforeCursor.length - match[0].length + match[1].length;
|
|
163
|
+
return {
|
|
164
|
+
trigger: match[2] as "@" | "/",
|
|
165
|
+
query: match[3] ?? "",
|
|
166
|
+
start,
|
|
167
|
+
end: safeCursor,
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
function mentionMatches(item: ComposerMentionItem, query: string) {
|
|
172
|
+
const normalizedQuery = query.trim().toLowerCase();
|
|
173
|
+
if (!normalizedQuery) return true;
|
|
174
|
+
return item.value.toLowerCase().includes(normalizedQuery)
|
|
175
|
+
|| item.label.toLowerCase().includes(normalizedQuery)
|
|
176
|
+
|| item.meta.toLowerCase().includes(normalizedQuery);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
function clampNumber(value: number, min: number, max: number) {
|
|
180
|
+
return Math.min(Math.max(value, min), Math.max(min, max));
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
const MENTION_PREFIX_DEFINITIONS: Array<{ kind: "media" | "chapter" | "section" | "component"; meta: string }> = [
|
|
184
|
+
{ kind: "media", meta: "prefix · images" },
|
|
185
|
+
{ kind: "chapter", meta: "prefix · chapters" },
|
|
186
|
+
{ kind: "section", meta: "prefix · sections" },
|
|
187
|
+
{ kind: "component", meta: "prefix · components" },
|
|
188
|
+
];
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
BaseCalloutProps,
|
|
3
|
+
BaseFigureProps,
|
|
4
|
+
BasePageProps,
|
|
5
|
+
BaseContentPageProps,
|
|
6
|
+
BaseShellPageProps,
|
|
7
|
+
} from "./types";
|
|
8
|
+
|
|
9
|
+
function classNames(...values: Array<string | undefined>) {
|
|
10
|
+
const joined = values.filter(Boolean).join(" ");
|
|
11
|
+
return joined.length > 0 ? joined : undefined;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function BasePage({ kind, footer = true, className, children, ...sectionProps }: BasePageProps) {
|
|
15
|
+
return (
|
|
16
|
+
<section
|
|
17
|
+
{...sectionProps}
|
|
18
|
+
className={classNames("reader-page", `reader-page--${kind}`, className)}
|
|
19
|
+
data-page-footer={footer ? "true" : "false"}
|
|
20
|
+
data-page-kind={kind}
|
|
21
|
+
>
|
|
22
|
+
{children}
|
|
23
|
+
</section>
|
|
24
|
+
);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function BaseCoverPage(props: BaseShellPageProps) {
|
|
28
|
+
return <BasePage {...props} footer={false} kind="cover" />;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function BaseTocPage(props: BaseShellPageProps) {
|
|
32
|
+
return <BasePage {...props} footer={false} kind="toc" />;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function BaseContentPage({
|
|
36
|
+
pageIndex,
|
|
37
|
+
totalPages,
|
|
38
|
+
chapterSlug,
|
|
39
|
+
chapterTone,
|
|
40
|
+
runningHeader,
|
|
41
|
+
footerLeft,
|
|
42
|
+
footerRight,
|
|
43
|
+
children,
|
|
44
|
+
...sectionProps
|
|
45
|
+
}: BaseContentPageProps) {
|
|
46
|
+
return (
|
|
47
|
+
<BasePage
|
|
48
|
+
{...sectionProps}
|
|
49
|
+
data-chapter-slug={chapterSlug}
|
|
50
|
+
data-chapter-tone={chapterTone}
|
|
51
|
+
data-page-index={pageIndex}
|
|
52
|
+
data-total-pages={totalPages}
|
|
53
|
+
footer
|
|
54
|
+
kind="content"
|
|
55
|
+
>
|
|
56
|
+
{runningHeader === undefined ? null : <header data-page-running-header>{runningHeader}</header>}
|
|
57
|
+
{children}
|
|
58
|
+
{footerLeft === undefined && footerRight === undefined ? null : (
|
|
59
|
+
<footer data-page-footer-content>
|
|
60
|
+
{footerLeft === undefined ? null : <span data-page-footer-left>{footerLeft}</span>}
|
|
61
|
+
{footerRight === undefined ? null : <span data-page-footer-right>{footerRight}</span>}
|
|
62
|
+
</footer>
|
|
63
|
+
)}
|
|
64
|
+
</BasePage>
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export function BaseBackCoverPage(props: BaseShellPageProps) {
|
|
69
|
+
return <BasePage {...props} footer={false} kind="back-cover" />;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export function BaseFigure({ caption, className, children, ...figureProps }: BaseFigureProps) {
|
|
73
|
+
return (
|
|
74
|
+
<figure {...figureProps} className={classNames("openpress-figure", className)}>
|
|
75
|
+
<div data-figure-body>{children}</div>
|
|
76
|
+
{caption === undefined ? null : <figcaption>{caption}</figcaption>}
|
|
77
|
+
</figure>
|
|
78
|
+
);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export function BaseCallout({ kind = "info", className, children, ...calloutProps }: BaseCalloutProps) {
|
|
82
|
+
return (
|
|
83
|
+
<aside {...calloutProps} className={classNames("openpress-callout", className)} data-callout-kind={kind}>
|
|
84
|
+
{children}
|
|
85
|
+
</aside>
|
|
86
|
+
);
|
|
87
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export {
|
|
2
|
+
BaseBackCoverPage,
|
|
3
|
+
BaseCallout,
|
|
4
|
+
BaseCoverPage,
|
|
5
|
+
BaseFigure,
|
|
6
|
+
BasePage,
|
|
7
|
+
BaseContentPage,
|
|
8
|
+
BaseTocPage,
|
|
9
|
+
} from "./basePages";
|
|
10
|
+
export type {
|
|
11
|
+
BaseCalloutKind,
|
|
12
|
+
BaseCalloutProps,
|
|
13
|
+
BaseFigureProps,
|
|
14
|
+
BasePageProps,
|
|
15
|
+
BaseContentPageProps,
|
|
16
|
+
BaseShellPageProps,
|
|
17
|
+
PageKind,
|
|
18
|
+
PageProps,
|
|
19
|
+
Manifest,
|
|
20
|
+
} from "./types";
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import type { HTMLAttributes, ReactNode } from "react";
|
|
2
|
+
|
|
3
|
+
export type PageKind = "cover" | "toc" | "content" | "back-cover";
|
|
4
|
+
|
|
5
|
+
export interface PageProps {
|
|
6
|
+
pageIndex: number;
|
|
7
|
+
totalPages: number;
|
|
8
|
+
chapterSlug?: string;
|
|
9
|
+
chapterTone?: string;
|
|
10
|
+
children: ReactNode;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export type BasePageProps = Omit<HTMLAttributes<HTMLElement>, "children"> & {
|
|
14
|
+
kind: PageKind;
|
|
15
|
+
footer?: boolean;
|
|
16
|
+
children?: ReactNode;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
export type BaseShellPageProps = Omit<BasePageProps, "kind" | "footer">;
|
|
20
|
+
|
|
21
|
+
export type BaseContentPageProps = Omit<BasePageProps, "kind" | "footer" | "children"> &
|
|
22
|
+
PageProps & {
|
|
23
|
+
runningHeader?: ReactNode;
|
|
24
|
+
footerLeft?: ReactNode;
|
|
25
|
+
footerRight?: ReactNode;
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
export type BaseFigureProps = Omit<HTMLAttributes<HTMLElement>, "children"> & {
|
|
29
|
+
caption?: ReactNode;
|
|
30
|
+
children: ReactNode;
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
export type BaseCalloutKind = "info" | "warn" | "success" | "error" | (string & {});
|
|
34
|
+
|
|
35
|
+
export type BaseCalloutProps = Omit<HTMLAttributes<HTMLElement>, "children"> & {
|
|
36
|
+
kind?: BaseCalloutKind;
|
|
37
|
+
children: ReactNode;
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
export interface Manifest {
|
|
41
|
+
title: string;
|
|
42
|
+
subtitle?: string;
|
|
43
|
+
organization?: string;
|
|
44
|
+
workspaceLabel?: string;
|
|
45
|
+
documentDir?: string;
|
|
46
|
+
sourceDir?: string;
|
|
47
|
+
componentsDir?: string;
|
|
48
|
+
mediaDir?: string;
|
|
49
|
+
themeDir?: string;
|
|
50
|
+
designDoc?: string;
|
|
51
|
+
publicDir?: string;
|
|
52
|
+
outputDir?: string;
|
|
53
|
+
pdf?: {
|
|
54
|
+
filename?: string;
|
|
55
|
+
};
|
|
56
|
+
deploy?: {
|
|
57
|
+
adapter?: string;
|
|
58
|
+
source?: string;
|
|
59
|
+
projectName?: string;
|
|
60
|
+
commitDirty?: boolean;
|
|
61
|
+
requiresConfirmation?: boolean;
|
|
62
|
+
};
|
|
63
|
+
paths?: {
|
|
64
|
+
chaptersDir?: string;
|
|
65
|
+
sourceDir?: string;
|
|
66
|
+
componentsDir?: string;
|
|
67
|
+
mediaDir?: string;
|
|
68
|
+
themeDir?: string;
|
|
69
|
+
designDoc?: string;
|
|
70
|
+
};
|
|
71
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
type BrowserFrameCallback = (timestamp: number) => void;
|
|
2
|
+
|
|
3
|
+
export function scheduleBrowserFrame(callback: BrowserFrameCallback) {
|
|
4
|
+
if (canUseAnimationFrame()) {
|
|
5
|
+
const frame = window.requestAnimationFrame(callback);
|
|
6
|
+
return () => window.cancelAnimationFrame(frame);
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
const timer = window.setTimeout(() => callback(now()), 0);
|
|
10
|
+
return () => window.clearTimeout(timer);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function waitForBrowserFrame() {
|
|
14
|
+
return new Promise<void>((resolve) => {
|
|
15
|
+
scheduleBrowserFrame(() => resolve());
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function canUseAnimationFrame() {
|
|
20
|
+
return (
|
|
21
|
+
typeof window !== "undefined" &&
|
|
22
|
+
typeof window.requestAnimationFrame === "function" &&
|
|
23
|
+
typeof document !== "undefined" &&
|
|
24
|
+
document.visibilityState !== "hidden"
|
|
25
|
+
);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function now() {
|
|
29
|
+
return typeof performance !== "undefined" && typeof performance.now === "function"
|
|
30
|
+
? performance.now()
|
|
31
|
+
: Date.now();
|
|
32
|
+
}
|