@open-press/cli 1.0.0 → 1.1.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.
Files changed (175) hide show
  1. package/README.md +11 -12
  2. package/dist/cli.js +298 -79
  3. package/package.json +9 -7
  4. package/template/core/AGENTS.md +0 -130
  5. package/template/core/CHANGELOG.md +0 -218
  6. package/template/core/README.md +0 -43
  7. package/template/core/engine/cli.mjs +0 -96
  8. package/template/core/engine/commands/_shared.mjs +0 -199
  9. package/template/core/engine/commands/deploy.mjs +0 -31
  10. package/template/core/engine/commands/dev.mjs +0 -49
  11. package/template/core/engine/commands/doctor.mjs +0 -229
  12. package/template/core/engine/commands/export.mjs +0 -8
  13. package/template/core/engine/commands/image.mjs +0 -29
  14. package/template/core/engine/commands/inspect.mjs +0 -35
  15. package/template/core/engine/commands/pdf.mjs +0 -26
  16. package/template/core/engine/commands/preview.mjs +0 -26
  17. package/template/core/engine/commands/render.mjs +0 -17
  18. package/template/core/engine/commands/replace.mjs +0 -41
  19. package/template/core/engine/commands/search.mjs +0 -33
  20. package/template/core/engine/commands/skills-sync.mjs +0 -71
  21. package/template/core/engine/commands/typecheck.mjs +0 -67
  22. package/template/core/engine/commands/upgrade.mjs +0 -159
  23. package/template/core/engine/commands/validate.mjs +0 -17
  24. package/template/core/engine/document-export.mjs +0 -15
  25. package/template/core/engine/output/chrome-pdf.d.mts +0 -34
  26. package/template/core/engine/output/chrome-pdf.mjs +0 -450
  27. package/template/core/engine/output/deploy-sync.mjs +0 -15
  28. package/template/core/engine/output/fonts.mjs +0 -62
  29. package/template/core/engine/output/katex-assets.mjs +0 -45
  30. package/template/core/engine/output/page-block.mjs +0 -30
  31. package/template/core/engine/output/pdf-media.mjs +0 -45
  32. package/template/core/engine/output/public-assets.mjs +0 -19
  33. package/template/core/engine/output/static-server.mjs +0 -571
  34. package/template/core/engine/react/caption-numbering.mjs +0 -73
  35. package/template/core/engine/react/comment-endpoint.d.mts +0 -11
  36. package/template/core/engine/react/comment-endpoint.mjs +0 -102
  37. package/template/core/engine/react/comment-marker.mjs +0 -374
  38. package/template/core/engine/react/document-entry.mjs +0 -331
  39. package/template/core/engine/react/document-export.mjs +0 -512
  40. package/template/core/engine/react/http-json.mjs +0 -24
  41. package/template/core/engine/react/mdx-compile.mjs +0 -629
  42. package/template/core/engine/react/measurement-css.mjs +0 -157
  43. package/template/core/engine/react/object-entities.mjs +0 -204
  44. package/template/core/engine/react/pagination/allocator.mjs +0 -167
  45. package/template/core/engine/react/pagination/regions.mjs +0 -81
  46. package/template/core/engine/react/pagination-constants.mjs +0 -3
  47. package/template/core/engine/react/pagination.mjs +0 -9
  48. package/template/core/engine/react/pipeline/allocate.mjs +0 -217
  49. package/template/core/engine/react/pipeline/final-render.mjs +0 -94
  50. package/template/core/engine/react/pipeline/frame-measurement.mjs +0 -306
  51. package/template/core/engine/react/pipeline/press-tree.mjs +0 -135
  52. package/template/core/engine/react/press-tree-inspection.mjs +0 -172
  53. package/template/core/engine/react/project-asset-endpoint.d.mts +0 -10
  54. package/template/core/engine/react/project-asset-endpoint.mjs +0 -361
  55. package/template/core/engine/react/section-css.mjs +0 -56
  56. package/template/core/engine/react/source-edit-endpoint.d.mts +0 -10
  57. package/template/core/engine/react/source-edit-endpoint.mjs +0 -75
  58. package/template/core/engine/react/sources/heading-numbering.mjs +0 -132
  59. package/template/core/engine/react/sources/mdx-resolver.mjs +0 -439
  60. package/template/core/engine/react/style-discovery.mjs +0 -160
  61. package/template/core/engine/runtime/config.d.mts +0 -48
  62. package/template/core/engine/runtime/config.mjs +0 -172
  63. package/template/core/engine/runtime/file-utils.mjs +0 -114
  64. package/template/core/engine/runtime/file-walk.mjs +0 -22
  65. package/template/core/engine/runtime/inspection.mjs +0 -328
  66. package/template/core/engine/runtime/issue-report.mjs +0 -44
  67. package/template/core/engine/runtime/page-geometry.mjs +0 -131
  68. package/template/core/engine/runtime/path-utils.mjs +0 -20
  69. package/template/core/engine/runtime/source-text-tools.d.mts +0 -102
  70. package/template/core/engine/runtime/source-text-tools.mjs +0 -832
  71. package/template/core/engine/runtime/source-workspace.mjs +0 -168
  72. package/template/core/engine/runtime/validation.mjs +0 -183
  73. package/template/core/index.html +0 -13
  74. package/template/core/openpress.config.mjs +0 -8
  75. package/template/core/package.json +0 -89
  76. package/template/core/src/main.tsx +0 -16
  77. package/template/core/src/openpress/app/OpenPressApp.tsx +0 -296
  78. package/template/core/src/openpress/app/OpenPressRuntime.tsx +0 -102
  79. package/template/core/src/openpress/app/WorkspaceGalleryPage.tsx +0 -219
  80. package/template/core/src/openpress/app/index.ts +0 -2
  81. package/template/core/src/openpress/core/Frame.tsx +0 -91
  82. package/template/core/src/openpress/core/FrameContext.tsx +0 -26
  83. package/template/core/src/openpress/core/MdxArea.tsx +0 -34
  84. package/template/core/src/openpress/core/Press.tsx +0 -55
  85. package/template/core/src/openpress/core/Workspace.tsx +0 -36
  86. package/template/core/src/openpress/core/cn.ts +0 -4
  87. package/template/core/src/openpress/core/index.tsx +0 -47
  88. package/template/core/src/openpress/core/primitives.tsx +0 -91
  89. package/template/core/src/openpress/core/types.ts +0 -236
  90. package/template/core/src/openpress/core/useSource.ts +0 -28
  91. package/template/core/src/openpress/document-model/anchorMapModel.ts +0 -27
  92. package/template/core/src/openpress/document-model/documentIndexes.ts +0 -329
  93. package/template/core/src/openpress/document-model/documentTypes.ts +0 -147
  94. package/template/core/src/openpress/document-model/index.ts +0 -7
  95. package/template/core/src/openpress/document-model/objectEntityModel.ts +0 -55
  96. package/template/core/src/openpress/document-model/projectIdentityModel.ts +0 -15
  97. package/template/core/src/openpress/document-model/reactDocumentMetadataModel.ts +0 -27
  98. package/template/core/src/openpress/document-model/workspaceManifestModel.ts +0 -57
  99. package/template/core/src/openpress/manuscript/index.tsx +0 -238
  100. package/template/core/src/openpress/mdx/index.ts +0 -96
  101. package/template/core/src/openpress/numbering/index.ts +0 -294
  102. package/template/core/src/openpress/reader/PageThumbnailsPanel.tsx +0 -168
  103. package/template/core/src/openpress/reader/PublicReaderPage.tsx +0 -267
  104. package/template/core/src/openpress/reader/ReaderNavigationPanel.tsx +0 -123
  105. package/template/core/src/openpress/reader/index.ts +0 -11
  106. package/template/core/src/openpress/reader/pageViewportScaleModel.ts +0 -73
  107. package/template/core/src/openpress/reader/readerPageRegistry.ts +0 -41
  108. package/template/core/src/openpress/reader/readerPageRoute.ts +0 -21
  109. package/template/core/src/openpress/reader/readerScroll.ts +0 -92
  110. package/template/core/src/openpress/reader/readerStateModel.ts +0 -15
  111. package/template/core/src/openpress/reader/readerTypes.ts +0 -4
  112. package/template/core/src/openpress/reader/usePageViewportScale.ts +0 -119
  113. package/template/core/src/openpress/reader/usePanelState.ts +0 -56
  114. package/template/core/src/openpress/reader/useReaderHashSync.ts +0 -61
  115. package/template/core/src/openpress/reader/useReaderKeyboardNav.ts +0 -48
  116. package/template/core/src/openpress/reader/useReaderRuntime.ts +0 -146
  117. package/template/core/src/openpress/reader/useReaderScrollAnchor.ts +0 -64
  118. package/template/core/src/openpress/shared/Panel.tsx +0 -77
  119. package/template/core/src/openpress/shared/frameScheduler.ts +0 -32
  120. package/template/core/src/openpress/shared/index.ts +0 -4
  121. package/template/core/src/openpress/shared/numberUtils.ts +0 -3
  122. package/template/core/src/openpress/shared/runtimeMode.ts +0 -11
  123. package/template/core/src/openpress/workbench/Workbench.tsx +0 -506
  124. package/template/core/src/openpress/workbench/actions/DeploymentControl.tsx +0 -157
  125. package/template/core/src/openpress/workbench/actions/ExportImageControl.tsx +0 -96
  126. package/template/core/src/openpress/workbench/actions/PageZoomControl.tsx +0 -182
  127. package/template/core/src/openpress/workbench/actions/SearchControl.tsx +0 -345
  128. package/template/core/src/openpress/workbench/actions/deploymentStatusModel.ts +0 -112
  129. package/template/core/src/openpress/workbench/actions/index.ts +0 -6
  130. package/template/core/src/openpress/workbench/actions/useDeploymentWorkbench.ts +0 -136
  131. package/template/core/src/openpress/workbench/dialog/WorkbenchDialog.tsx +0 -72
  132. package/template/core/src/openpress/workbench/dialog/index.ts +0 -1
  133. package/template/core/src/openpress/workbench/document/components/DocumentPanel.tsx +0 -127
  134. package/template/core/src/openpress/workbench/document/components/InlineSourceEditorLayer.tsx +0 -207
  135. package/template/core/src/openpress/workbench/document/components/ReaderStage.tsx +0 -9
  136. package/template/core/src/openpress/workbench/document/hooks/useDocumentWorkbenchModel.ts +0 -34
  137. package/template/core/src/openpress/workbench/document/hooks/useInlineDocumentEditor.ts +0 -525
  138. package/template/core/src/openpress/workbench/document/index.ts +0 -10
  139. package/template/core/src/openpress/workbench/index.ts +0 -2
  140. package/template/core/src/openpress/workbench/inspector/InlineInspectorLayer.tsx +0 -459
  141. package/template/core/src/openpress/workbench/inspector/index.ts +0 -5
  142. package/template/core/src/openpress/workbench/inspector/inlineCommentModel.ts +0 -125
  143. package/template/core/src/openpress/workbench/inspector/inspectorGeometryModel.ts +0 -160
  144. package/template/core/src/openpress/workbench/inspector/inspectorModel.ts +0 -408
  145. package/template/core/src/openpress/workbench/inspector/useInspectorComments.ts +0 -254
  146. package/template/core/src/openpress/workbench/mentions/MentionSuggestionList.tsx +0 -41
  147. package/template/core/src/openpress/workbench/mentions/index.ts +0 -2
  148. package/template/core/src/openpress/workbench/mentions/useComposerMentions.ts +0 -185
  149. package/template/core/src/openpress/workbench/panels/Panel.tsx +0 -1
  150. package/template/core/src/openpress/workbench/panels/PendingCommentsPanel.tsx +0 -80
  151. package/template/core/src/openpress/workbench/panels/WorkbenchControlPanel.tsx +0 -29
  152. package/template/core/src/openpress/workbench/panels/index.ts +0 -3
  153. package/template/core/src/openpress/workbench/project/ProjectEntryPanel.tsx +0 -525
  154. package/template/core/src/openpress/workbench/project/ProjectPreviewDialog.tsx +0 -35
  155. package/template/core/src/openpress/workbench/project/index.ts +0 -2
  156. package/template/core/src/openpress/workbench/project/projectPreviewTypes.ts +0 -11
  157. package/template/core/src/openpress/workbench/project/projectSourceModel.ts +0 -24
  158. package/template/core/src/openpress/workbench/shell/WorkbenchShell.tsx +0 -167
  159. package/template/core/src/openpress/workbench/shell/index.ts +0 -1
  160. package/template/core/src/openpress/workbench/workbenchFormatters.ts +0 -120
  161. package/template/core/src/openpress/workbench/workbenchTypes.ts +0 -35
  162. package/template/core/src/styles/openpress/app-shell.css +0 -251
  163. package/template/core/src/styles/openpress/media-workspace.css +0 -230
  164. package/template/core/src/styles/openpress/print-route.css +0 -184
  165. package/template/core/src/styles/openpress/project-preview-panel.css +0 -924
  166. package/template/core/src/styles/openpress/public-viewer.css +0 -688
  167. package/template/core/src/styles/openpress/reader-runtime.css +0 -989
  168. package/template/core/src/styles/openpress/responsive.css +0 -245
  169. package/template/core/src/styles/openpress/workbench-panels.css +0 -707
  170. package/template/core/src/styles/openpress/workbench.css +0 -1255
  171. package/template/core/src/styles/openpress/workspace-gallery.css +0 -300
  172. package/template/core/src/styles/openpress.css +0 -15
  173. package/template/core/src/vite-env.d.ts +0 -9
  174. package/template/core/tsconfig.json +0 -40
  175. package/template/core/vite.config.ts +0 -584
@@ -1,296 +0,0 @@
1
- import { useCallback, useEffect, useState } from "react";
2
- import { OpenPressRuntime } from "./OpenPressRuntime";
3
- import { WorkspaceGalleryPage } from "./WorkspaceGalleryPage";
4
- import { isLocalWorkspaceHost } from "../shared";
5
- import type {
6
- DeploymentInfo,
7
- ReaderDocument,
8
- WorkspaceManifest,
9
- WorkspaceManifestPress,
10
- } from "../document-model";
11
- import { findManifestPress, manifestHasMultiplePresses } from "../document-model";
12
-
13
- type LoadState =
14
- | { status: "loading" }
15
- | {
16
- // Gallery state — shown for multi-Press workspaces at the root URL.
17
- // Single-Press workspaces never reach this state.
18
- status: "gallery";
19
- manifest: WorkspaceManifest;
20
- deploymentInfo: DeploymentInfo;
21
- }
22
- | {
23
- status: "ready";
24
- document: ReaderDocument;
25
- deploymentInfo: DeploymentInfo;
26
- manifest: WorkspaceManifest | null;
27
- // Empty string for single-Press workspaces (no slug routing needed)
28
- // or for the root entry of a multi-Press workspace. Otherwise the
29
- // active press's slug — used by refresh/back/forward to re-resolve.
30
- activeSlug: string;
31
- }
32
- | { status: "error"; message: string };
33
-
34
- interface DeployConfig {
35
- pdf?: string;
36
- deployed_at?: string;
37
- public_url?: string;
38
- dirty?: boolean;
39
- deploy_configured?: boolean;
40
- deploy_adapter?: string;
41
- deploy_source?: string;
42
- deploy_project_name?: string | null;
43
- deploy_setup_message?: string;
44
- }
45
-
46
- const offlineDeploymentInfo: DeploymentInfo = { online: false };
47
-
48
- function LoadingScreen() {
49
- return (
50
- <div className="openpress-loading-screen" aria-label="載入中" role="status">
51
- <div className="openpress-loading-screen__inner">
52
- <div className="openpress-loading-dots" aria-hidden="true">
53
- <span /><span /><span />
54
- </div>
55
- <span className="openpress-loading-screen__label">載入文件</span>
56
- </div>
57
- </div>
58
- );
59
- }
60
-
61
- export function OpenPressApp() {
62
- const [state, setState] = useState<LoadState>({ status: "loading" });
63
-
64
- // Single resolution function — same code path for "boot from URL",
65
- // "click gallery card", and "browser back button". Given a manifest
66
- // + slug, decides whether to render gallery or load a press.
67
- const resolveFromSlug = useCallback(async (
68
- manifest: WorkspaceManifest | null,
69
- slug: string,
70
- deploymentInfo: DeploymentInfo,
71
- ) => {
72
- // No manifest (legacy deploy): always load /openpress/document.json.
73
- if (!manifest || manifest.presses.length === 0) {
74
- const document = await loadReaderDocument("/openpress/document.json");
75
- setState({ status: "ready", document, deploymentInfo, manifest, activeSlug: "" });
76
- return;
77
- }
78
-
79
- // Empty slug + multi-Press: show gallery. Empty slug + single-Press:
80
- // load the only press. Same expression handles both — array length
81
- // is the only thing that matters.
82
- const normalizedSlug = normalizeSlug(slug);
83
- if (!normalizedSlug && manifestHasMultiplePresses(manifest)) {
84
- setState({ status: "gallery", manifest, deploymentInfo });
85
- return;
86
- }
87
-
88
- const press = normalizedSlug
89
- ? findManifestPress(manifest, normalizedSlug)
90
- : manifest.presses[0];
91
- if (!press) {
92
- setState({
93
- status: "error",
94
- message: `Unknown document slug "/${normalizedSlug}". Known: ${manifest.presses.map((p) => `/${p.slug}`).join(", ")}.`,
95
- });
96
- return;
97
- }
98
- const document = await loadReaderDocument(press.documentUrl);
99
- setState({ status: "ready", document, deploymentInfo, manifest, activeSlug: press.slug });
100
- }, []);
101
-
102
- const refreshDocument = useCallback(async () => {
103
- if (state.status !== "ready") return;
104
- const press = state.manifest
105
- ? findManifestPress(state.manifest, state.activeSlug)
106
- : null;
107
- const url = press?.documentUrl ?? "/openpress/document.json";
108
- const document = await loadReaderDocument(url);
109
- setState((latest) => {
110
- if (latest.status !== "ready") return latest;
111
- return { ...latest, document };
112
- });
113
- }, [state]);
114
-
115
- // Gallery click → pushState + load. Bypasses resolveFromSlug's
116
- // "empty slug + multi-Press → gallery" branch: an explicit click on
117
- // the unslugged root Press must enter it, not bounce back to gallery.
118
- const enterPress = useCallback(async (press: WorkspaceManifestPress) => {
119
- if (state.status !== "gallery") return;
120
- pushSlug(press.slug);
121
- setState({ status: "loading" });
122
- try {
123
- const document = await loadReaderDocument(press.documentUrl);
124
- setState({
125
- status: "ready",
126
- document,
127
- deploymentInfo: state.deploymentInfo,
128
- manifest: state.manifest,
129
- activeSlug: press.slug,
130
- });
131
- } catch (error) {
132
- setState({
133
- status: "error",
134
- message: error instanceof Error ? error.message : "Unable to load OpenPress document.",
135
- });
136
- }
137
- }, [state]);
138
-
139
- // Bootstrap: read URL → load manifest + deploy info → resolve.
140
- useEffect(() => {
141
- let cancelled = false;
142
-
143
- async function bootstrap() {
144
- try {
145
- const [manifest, deploymentInfo] = await Promise.all([
146
- loadWorkspaceManifest(),
147
- loadDeploymentInfo(),
148
- ]);
149
- if (cancelled) return;
150
- await resolveFromSlug(manifest, currentSlugFromLocation(), deploymentInfo);
151
- } catch (error) {
152
- if (!cancelled) {
153
- setState({
154
- status: "error",
155
- message: error instanceof Error ? error.message : "Unable to load OpenPress document.",
156
- });
157
- }
158
- }
159
- }
160
-
161
- void bootstrap();
162
- return () => {
163
- cancelled = true;
164
- };
165
- }, [resolveFromSlug]);
166
-
167
- // Back / forward button — re-resolve from the new URL.
168
- useEffect(() => {
169
- function onPopState() {
170
- if (state.status === "loading") return;
171
- const manifest = state.status === "gallery"
172
- ? state.manifest
173
- : state.status === "ready"
174
- ? state.manifest
175
- : null;
176
- const deploymentInfo = state.status === "gallery" || state.status === "ready"
177
- ? state.deploymentInfo
178
- : offlineDeploymentInfo;
179
- void resolveFromSlug(manifest, currentSlugFromLocation(), deploymentInfo);
180
- }
181
- window.addEventListener("popstate", onPopState);
182
- return () => window.removeEventListener("popstate", onPopState);
183
- }, [state, resolveFromSlug]);
184
-
185
- if (state.status === "loading") return <LoadingScreen />;
186
-
187
- if (state.status === "error") {
188
- return <div className="openpress-load-state openpress-load-state--error">{state.message}</div>;
189
- }
190
-
191
- if (state.status === "gallery") {
192
- return <WorkspaceGalleryPage manifest={state.manifest} onSelectPress={enterPress} />;
193
- }
194
-
195
- // Only multi-Press workspaces have a gallery to go back to. Single-Press
196
- // workspaces don't render the button (no destination exists).
197
- const backToWorkspace = state.manifest && manifestHasMultiplePresses(state.manifest)
198
- ? () => {
199
- if (state.status !== "ready" || !state.manifest) return;
200
- pushSlug("");
201
- setState({
202
- status: "gallery",
203
- manifest: state.manifest,
204
- deploymentInfo: state.deploymentInfo,
205
- });
206
- }
207
- : undefined;
208
-
209
- return (
210
- <OpenPressRuntime
211
- document={state.document}
212
- deploymentInfo={state.deploymentInfo}
213
- onDocumentRefresh={refreshDocument}
214
- onBackToWorkspace={backToWorkspace}
215
- />
216
- );
217
- }
218
-
219
- function currentSlugFromLocation(): string {
220
- if (typeof window === "undefined") return "";
221
- return normalizeSlug(window.location.pathname);
222
- }
223
-
224
- function normalizeSlug(raw: string): string {
225
- return raw.replace(/^\/+|\/+$/g, "");
226
- }
227
-
228
- function pushSlug(slug: string) {
229
- if (typeof window === "undefined") return;
230
- // Preserve the current query string (e.g. ?dev=1 keeps the workbench
231
- // chrome alive across gallery navigation). Drop the hash — it's a
232
- // page anchor that means nothing in a different document.
233
- const pathname = slug ? `/${normalizeSlug(slug)}` : "/";
234
- const target = `${pathname}${window.location.search}`;
235
- if (window.location.pathname === pathname) return;
236
- window.history.pushState({}, "", target);
237
- }
238
-
239
- async function loadWorkspaceManifest(): Promise<WorkspaceManifest | null> {
240
- // Optional — older deployments don't ship workspace.json. The reader
241
- // falls back to /openpress/document.json directly when missing, which
242
- // matches pre-v1.0 behavior.
243
- try {
244
- const response = await fetch("/openpress/workspace.json", { cache: "no-store" });
245
- if (!response.ok) return null;
246
- return (await response.json()) as WorkspaceManifest;
247
- } catch {
248
- return null;
249
- }
250
- }
251
-
252
- async function loadReaderDocument(url: string): Promise<ReaderDocument> {
253
- const response = await fetch(url, { cache: "no-store" });
254
- if (!response.ok) {
255
- throw new Error(`Unable to load ${url} (${response.status})`);
256
- }
257
- return (await response.json()) as ReaderDocument;
258
- }
259
-
260
- async function loadDeploymentInfo(): Promise<DeploymentInfo> {
261
- if (typeof window !== "undefined" && isLocalWorkspaceHost(window.location.hostname)) {
262
- const localInfo = await loadDeploymentInfoFrom("/__openpress/status");
263
- if (localInfo) return localInfo;
264
- }
265
-
266
- return (await loadDeploymentInfoFrom("/openpress/deploy.json")) ?? offlineDeploymentInfo;
267
- }
268
-
269
- async function loadDeploymentInfoFrom(path: string): Promise<DeploymentInfo | null> {
270
- try {
271
- const response = await fetch(path, { cache: "no-store" });
272
- if (!response.ok) {
273
- return null;
274
- }
275
- const config = (await response.json()) as DeployConfig;
276
- return deploymentConfigToInfo(config);
277
- } catch {
278
- return null;
279
- }
280
- }
281
-
282
- function deploymentConfigToInfo(config: DeployConfig): DeploymentInfo {
283
- const configured = config.deploy_configured !== false;
284
- return {
285
- online: configured && Boolean(config.deployed_at || config.public_url),
286
- deployedAt: config.deployed_at,
287
- pdf: typeof config.pdf === "string" ? config.pdf : undefined,
288
- publicUrl: typeof config.public_url === "string" ? config.public_url : undefined,
289
- dirty: config.dirty === true,
290
- configured,
291
- adapter: typeof config.deploy_adapter === "string" ? config.deploy_adapter : undefined,
292
- source: typeof config.deploy_source === "string" ? config.deploy_source : undefined,
293
- projectName: typeof config.deploy_project_name === "string" ? config.deploy_project_name : undefined,
294
- setupMessage: typeof config.deploy_setup_message === "string" ? config.deploy_setup_message : undefined,
295
- };
296
- }
@@ -1,102 +0,0 @@
1
- import { useMemo, type CSSProperties } from "react";
2
- import { PrintDocument, PublicViewer } from "../reader";
3
- import { isPrintModeLocation, isWorkspaceModeLocation } from "../shared";
4
- import { HtmlWorkbench } from "../workbench";
5
- import type {
6
- DeploymentInfo,
7
- ReaderDocument,
8
- HtmlPageBlock,
9
- Theme,
10
- } from "../document-model";
11
-
12
- interface OpenPressRuntimeProps {
13
- document: ReaderDocument;
14
- deploymentInfo?: DeploymentInfo;
15
- onDocumentRefresh?: () => void | Promise<void>;
16
- // Optional — supplied by OpenPressApp when this Press was entered from
17
- // a multi-Press gallery. Renders a "工作台" home button in the toolbar
18
- // that returns to the gallery without a full page reload.
19
- onBackToWorkspace?: () => void;
20
- }
21
-
22
- export function OpenPressRuntime({
23
- document,
24
- deploymentInfo = { online: false },
25
- onDocumentRefresh,
26
- onBackToWorkspace,
27
- }: OpenPressRuntimeProps) {
28
- const style = themeToCssVariables(document.theme);
29
- const htmlPages = document.blocks.filter((block): block is HtmlPageBlock => block.kind === "htmlPage");
30
- const workspaceMode = useMemo(() => {
31
- if (typeof window === "undefined") return false;
32
- return isWorkspaceModeLocation(window.location);
33
- }, []);
34
- const printMode = useMemo(() => {
35
- if (typeof window === "undefined") return false;
36
- return isPrintModeLocation(window.location);
37
- }, []);
38
-
39
- if (htmlPages.length > 0) {
40
- if (printMode) {
41
- return <PrintDocument document={document} pages={htmlPages} style={style} />;
42
- }
43
-
44
- if (!workspaceMode) {
45
- return <PublicViewer document={document} pages={htmlPages} style={style} deploymentInfo={deploymentInfo} />;
46
- }
47
-
48
- return (
49
- <HtmlWorkbench
50
- document={document}
51
- pages={htmlPages}
52
- style={style}
53
- devMode={workspaceMode}
54
- deploymentInfo={deploymentInfo}
55
- onDocumentRefresh={onDocumentRefresh}
56
- onBackToWorkspace={onBackToWorkspace}
57
- />
58
- );
59
- }
60
-
61
- return <EmptyState style={style} workspaceMode={workspaceMode} />;
62
- }
63
-
64
- function EmptyState({ style, workspaceMode }: { style: CSSProperties; workspaceMode: boolean }) {
65
- return (
66
- <main className="openpress-shell openpress-empty-state" style={style}>
67
- <section className="openpress-empty-state__panel">
68
- <p className="openpress-empty-state__eyebrow">OpenPress</p>
69
- <h1 className="openpress-empty-state__title">This document has no content yet.</h1>
70
- <p className="openpress-empty-state__body">
71
- Add React MDX chapter files under <code>press/chapters/**/content/</code>, then re-build.
72
- </p>
73
- {workspaceMode ? (
74
- <ol className="openpress-empty-state__steps">
75
- <li><code>npm run build</code> &nbsp;— validates and refreshes <code>public/openpress/document.json</code></li>
76
- <li>Reload this page</li>
77
- </ol>
78
- ) : (
79
- <p className="openpress-empty-state__body">
80
- (If you are the document author, run <code>npm run dev</code> locally to edit.)
81
- </p>
82
- )}
83
- </section>
84
- </main>
85
- );
86
- }
87
-
88
- function themeToCssVariables(theme?: Theme) {
89
- const style: CSSProperties & Record<`--${string}`, string> = {
90
- "--openpress-font-family": theme?.fontFamily ?? "'Noto Sans TC', 'PingFang TC', sans-serif",
91
- "--openpress-accent": theme?.accentColor ?? "#df4b21",
92
- "--openpress-text": theme?.textColor ?? "#20242a",
93
- };
94
-
95
- if (theme?.pageWidth) style["--openpress-page-width"] = theme.pageWidth;
96
- if (theme?.pageHeight) style["--openpress-page-height"] = theme.pageHeight;
97
- if (theme?.pageAspectRatio) style["--openpress-page-aspect-ratio"] = theme.pageAspectRatio;
98
- if (theme?.pageHeightRatio) style["--openpress-page-height-ratio"] = theme.pageHeightRatio;
99
- if (theme?.pagePadding) style["--openpress-page-padding"] = theme.pagePadding;
100
-
101
- return style;
102
- }
@@ -1,219 +0,0 @@
1
- import { useEffect, useRef, useState, type CSSProperties, type KeyboardEvent } from "react";
2
- import type { HtmlPageBlock, ReaderDocument, WorkspaceManifest, WorkspaceManifestPress } from "../document-model";
3
-
4
- interface Props {
5
- manifest: WorkspaceManifest;
6
- // Called when the reader navigates into a specific Press. The host
7
- // is responsible for routing (history.pushState, hash, etc.); the
8
- // gallery just emits the chosen slug.
9
- onSelectPress: (press: WorkspaceManifestPress) => void;
10
- }
11
-
12
- // Reader landing page for multi-Press workspaces. Shows a Figma-style
13
- // uniform-grid card per Press; each card lazily loads that Press's
14
- // document.json and renders the first page as a thumbnail preview.
15
- // Single-Press workspaces skip the gallery entirely.
16
- export function WorkspaceGalleryPage({ manifest, onSelectPress }: Props) {
17
- const heading = manifest.name ?? "Workspace";
18
- const pressCount = String(manifest.presses.length).padStart(2, "0");
19
-
20
- return (
21
- <main className="openpress-workspace-gallery" aria-labelledby="workspace-gallery-heading">
22
- <header className="openpress-workspace-gallery__header">
23
- <div className="openpress-workspace-gallery__headline">
24
- <p className="openpress-workspace-gallery__eyebrow">Workspace</p>
25
- <h1 id="workspace-gallery-heading">{heading}</h1>
26
- </div>
27
- <p className="openpress-workspace-gallery__count">
28
- <span>{pressCount}</span>
29
- <small>{manifest.presses.length === 1 ? "document" : "documents"}</small>
30
- </p>
31
- </header>
32
-
33
- <ul className="openpress-workspace-gallery__grid" role="list">
34
- {manifest.presses.map((press) => (
35
- <li key={press.slug || "root"} className="openpress-workspace-gallery__item">
36
- <PressCard press={press} onSelect={() => onSelectPress(press)} />
37
- </li>
38
- ))}
39
- </ul>
40
- </main>
41
- );
42
- }
43
-
44
- // Card is a div+role=button (not <button>) so it can contain the
45
- // rendered page HTML — buttons may only hold phrasing content, and
46
- // page HTML is block-level.
47
- function PressCard({ press, onSelect }: { press: WorkspaceManifestPress; onSelect: () => void }) {
48
- const handleKey = (event: KeyboardEvent<HTMLDivElement>) => {
49
- if (event.key === "Enter" || event.key === " ") {
50
- event.preventDefault();
51
- onSelect();
52
- }
53
- };
54
-
55
- return (
56
- <div
57
- role="button"
58
- tabIndex={0}
59
- className="openpress-workspace-gallery__card"
60
- onClick={onSelect}
61
- onKeyDown={handleKey}
62
- aria-label={`Open ${press.title}`}
63
- >
64
- <PressThumbnail press={press} />
65
- <div className="openpress-workspace-gallery__body">
66
- <div className="openpress-workspace-gallery__title">{press.title}</div>
67
- <div className="openpress-workspace-gallery__meta">
68
- {press.slug ? <span className="openpress-workspace-gallery__slug">{press.slug}</span> : null}
69
- {press.page?.pageLabel ? (
70
- <span className="openpress-workspace-gallery__geom">{press.page.pageLabel}</span>
71
- ) : null}
72
- </div>
73
- </div>
74
- </div>
75
- );
76
- }
77
-
78
- function PressThumbnail({ press }: { press: WorkspaceManifestPress }) {
79
- const [state, setState] = useState<ThumbnailState>({ status: "loading" });
80
-
81
- // Lazy-load each Press's document.json so the gallery doesn't block
82
- // on a network waterfall when there are many Press. Errors degrade
83
- // to the geometry-only placeholder used by the loading state.
84
- useEffect(() => {
85
- let cancelled = false;
86
- fetchFirstPage(press.documentUrl).then((page) => {
87
- if (cancelled) return;
88
- setState(page ? { status: "ready", page } : { status: "error" });
89
- }).catch(() => {
90
- if (!cancelled) setState({ status: "error" });
91
- });
92
- return () => { cancelled = true; };
93
- }, [press.documentUrl]);
94
-
95
- // Outer card is uniform 4:3 (set in CSS). The page itself letterboxes
96
- // inside via centered scale, so A4 portrait renders tall-and-narrow,
97
- // social square renders centered, 16:9 slide stretches edge-to-edge.
98
- return (
99
- <div className="openpress-workspace-gallery__thumb" aria-hidden="true">
100
- {state.status === "ready" ? (
101
- <PageMiniature page={state.page} press={press} />
102
- ) : (
103
- <div className="openpress-workspace-gallery__thumb-placeholder" data-state={state.status}>
104
- <div className="openpress-workspace-gallery__thumb-skel" style={skelAspectStyle(press)} />
105
- </div>
106
- )}
107
- </div>
108
- );
109
- }
110
-
111
- function skelAspectStyle(press: WorkspaceManifestPress): CSSProperties {
112
- const w = parsePxLength(press.page?.pageWidth);
113
- const h = parsePxLength(press.page?.pageHeight);
114
- if (w && h) return { aspectRatio: `${w} / ${h}`, height: "75%" };
115
- return { aspectRatio: "1 / 1.414", height: "75%" };
116
- }
117
-
118
- function PageMiniature({ page, press }: { page: HtmlPageBlock; press: WorkspaceManifestPress }) {
119
- const containerRef = useRef<HTMLDivElement>(null);
120
- const [scale, setScale] = useState<number | null>(null);
121
- const pageWidthPx = parsePxLength(press.page?.pageWidth) ?? 1080;
122
- const pageHeightPx = parsePxLength(press.page?.pageHeight) ?? pageWidthPx;
123
-
124
- useEffect(() => {
125
- const el = containerRef.current;
126
- if (!el) return;
127
- const update = () => {
128
- const w = el.clientWidth;
129
- const h = el.clientHeight;
130
- if (w > 0 && h > 0) {
131
- setScale(Math.min(w / pageWidthPx, h / pageHeightPx));
132
- }
133
- };
134
- update();
135
- if (typeof ResizeObserver === "undefined") return;
136
- const ro = new ResizeObserver(update);
137
- ro.observe(el);
138
- return () => ro.disconnect();
139
- }, [pageWidthPx, pageHeightPx]);
140
-
141
- const scaledWidth = scale ? pageWidthPx * scale : 0;
142
- const scaledHeight = scale ? pageHeightPx * scale : 0;
143
- const frameStyle: CSSProperties = {
144
- width: `${scaledWidth}px`,
145
- height: `${scaledHeight}px`,
146
- position: "relative",
147
- visibility: scale ? "visible" : "hidden",
148
- };
149
-
150
- // Match the wrapping used by PublicReaderPage so scoped CSS targeting
151
- // `.openpress-html-page__html` selectors lights up identically. The
152
- // outer frame owns centering; the page only scales from its top-left
153
- // origin, which avoids mixed translate/scale centering drift.
154
- const pageStyle: CSSProperties = {
155
- "--openpress-page-width": `${pageWidthPx}px`,
156
- "--openpress-page-height": `${pageHeightPx}px`,
157
- width: `${pageWidthPx}px`,
158
- height: `${pageHeightPx}px`,
159
- transform: scale ? `scale(${scale})` : undefined,
160
- transformOrigin: "top left",
161
- position: "absolute",
162
- top: 0,
163
- left: 0,
164
- } as CSSProperties;
165
- const pageClass = page.className
166
- ? `openpress-html-page ${page.className}`
167
- : "openpress-html-page";
168
-
169
- return (
170
- <div className="openpress-workspace-gallery__thumb-stage" ref={containerRef}>
171
- <div className="openpress-workspace-gallery__thumb-frame" style={frameStyle}>
172
- <div className={pageClass} style={pageStyle} data-openpress-thumb-page="true">
173
- <div
174
- className="openpress-html-page__html"
175
- // Trusted HTML — same source as the reader's main render path.
176
- dangerouslySetInnerHTML={{ __html: page.html }}
177
- />
178
- </div>
179
- </div>
180
- </div>
181
- );
182
- }
183
-
184
- type ThumbnailState =
185
- | { status: "loading" }
186
- | { status: "error" }
187
- | { status: "ready"; page: HtmlPageBlock };
188
-
189
- async function fetchFirstPage(url: string): Promise<HtmlPageBlock | null> {
190
- try {
191
- const response = await fetch(url, { cache: "no-store" });
192
- if (!response.ok) return null;
193
- const doc = (await response.json()) as ReaderDocument;
194
- const firstPage = doc.blocks.find((b): b is HtmlPageBlock => b.kind === "htmlPage");
195
- return firstPage ?? null;
196
- } catch {
197
- return null;
198
- }
199
- }
200
-
201
- // Convert a CSS length string (px / mm / cm / in) into device pixels
202
- // at 96 dpi. A4 pages are stored as "210mm" / "297mm" so the gallery
203
- // and thumbnail scalers need this to compute their fit ratio — using
204
- // the bare string would always fall back to the default fallback.
205
- function parsePxLength(value: string | undefined): number | null {
206
- if (!value) return null;
207
- const match = value.trim().match(/^([\d.]+)\s*(px|mm|cm|in)$/i);
208
- if (!match) return null;
209
- const n = Number(match[1]);
210
- if (!Number.isFinite(n) || n <= 0) return null;
211
- const unit = match[2].toLowerCase();
212
- switch (unit) {
213
- case "px": return n;
214
- case "mm": return n * (96 / 25.4);
215
- case "cm": return n * (96 / 2.54);
216
- case "in": return n * 96;
217
- default: return null;
218
- }
219
- }
@@ -1,2 +0,0 @@
1
- export { OpenPressApp } from "./OpenPressApp";
2
- export { OpenPressRuntime } from "./OpenPressRuntime";