@open-press/core 0.7.1 → 1.0.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 (144) hide show
  1. package/README.md +6 -3
  2. package/engine/cli.mjs +8 -8
  3. package/engine/commands/_shared.mjs +37 -15
  4. package/engine/commands/dev.mjs +2 -2
  5. package/engine/commands/image.mjs +29 -0
  6. package/engine/commands/skills-sync.mjs +71 -0
  7. package/engine/commands/typecheck.mjs +63 -1
  8. package/engine/commands/upgrade.mjs +3 -3
  9. package/engine/document-export.mjs +1 -1
  10. package/engine/output/chrome-pdf.mjs +110 -3
  11. package/engine/output/static-server.mjs +87 -9
  12. package/engine/react/comment-endpoint.mjs +13 -39
  13. package/engine/react/comment-marker.mjs +43 -19
  14. package/engine/react/document-entry.mjs +46 -28
  15. package/engine/react/document-export.mjs +328 -164
  16. package/engine/react/http-json.mjs +24 -0
  17. package/engine/react/mdx-compile.mjs +126 -3
  18. package/engine/react/measurement-css.mjs +114 -1
  19. package/engine/react/object-entities.mjs +204 -0
  20. package/engine/react/pagination/allocator.mjs +48 -3
  21. package/engine/react/pagination.mjs +1 -1
  22. package/engine/react/pipeline/allocate.mjs +41 -72
  23. package/engine/react/pipeline/frame-measurement.mjs +6 -0
  24. package/engine/react/press-tree-inspection.mjs +172 -0
  25. package/engine/react/project-asset-endpoint.mjs +6 -24
  26. package/engine/react/source-edit-endpoint.d.mts +10 -0
  27. package/engine/react/source-edit-endpoint.mjs +75 -0
  28. package/engine/react/sources/mdx-resolver.mjs +13 -15
  29. package/engine/react/style-discovery.mjs +23 -8
  30. package/engine/runtime/config.d.mts +8 -0
  31. package/engine/runtime/config.mjs +57 -60
  32. package/engine/runtime/file-utils.mjs +9 -1
  33. package/engine/runtime/file-walk.mjs +22 -0
  34. package/engine/runtime/inspection.mjs +1 -20
  35. package/engine/runtime/page-geometry.mjs +131 -0
  36. package/engine/runtime/path-utils.mjs +20 -0
  37. package/engine/runtime/source-text-tools.d.mts +102 -0
  38. package/engine/runtime/source-text-tools.mjs +551 -16
  39. package/engine/runtime/source-workspace.mjs +16 -34
  40. package/engine/runtime/validation.mjs +19 -10
  41. package/package.json +3 -5
  42. package/src/openpress/app/OpenPressApp.tsx +296 -0
  43. package/src/openpress/{renderer.tsx → app/OpenPressRuntime.tsx} +20 -9
  44. package/src/openpress/app/WorkspaceGalleryPage.tsx +219 -0
  45. package/src/openpress/app/index.ts +2 -0
  46. package/src/openpress/core/Frame.tsx +26 -15
  47. package/src/openpress/core/FrameContext.tsx +10 -3
  48. package/src/openpress/core/MdxArea.tsx +11 -12
  49. package/src/openpress/core/Press.tsx +25 -4
  50. package/src/openpress/core/Workspace.tsx +36 -0
  51. package/src/openpress/core/cn.ts +4 -0
  52. package/src/openpress/core/index.tsx +11 -3
  53. package/src/openpress/core/primitives.tsx +74 -6
  54. package/src/openpress/core/types.ts +94 -41
  55. package/src/openpress/core/useSource.ts +1 -1
  56. package/src/openpress/{anchorMap.ts → document-model/anchorMapModel.ts} +1 -1
  57. package/src/openpress/{indexes.ts → document-model/documentIndexes.ts} +1 -1
  58. package/src/openpress/{types.ts → document-model/documentTypes.ts} +51 -0
  59. package/src/openpress/document-model/index.ts +7 -0
  60. package/src/openpress/document-model/objectEntityModel.ts +55 -0
  61. package/src/openpress/{projectIdentity.ts → document-model/projectIdentityModel.ts} +1 -1
  62. package/src/openpress/{reactDocumentMetadata.ts → document-model/reactDocumentMetadataModel.ts} +1 -1
  63. package/src/openpress/document-model/workspaceManifestModel.ts +57 -0
  64. package/src/openpress/manuscript/index.tsx +49 -7
  65. package/src/openpress/mdx/index.ts +15 -7
  66. package/src/openpress/reader/PageThumbnailsPanel.tsx +168 -0
  67. package/src/openpress/{publicPage.tsx → reader/PublicReaderPage.tsx} +31 -51
  68. package/src/openpress/{workbenchPanels.tsx → reader/ReaderNavigationPanel.tsx} +6 -5
  69. package/src/openpress/reader/index.ts +11 -0
  70. package/src/openpress/reader/pageViewportScaleModel.ts +73 -0
  71. package/src/openpress/reader/readerTypes.ts +4 -0
  72. package/src/openpress/reader/usePageViewportScale.ts +119 -0
  73. package/src/openpress/reader/usePanelState.ts +56 -0
  74. package/src/openpress/reader/useReaderHashSync.ts +61 -0
  75. package/src/openpress/reader/useReaderKeyboardNav.ts +48 -0
  76. package/src/openpress/reader/useReaderRuntime.ts +146 -0
  77. package/src/openpress/reader/useReaderScrollAnchor.ts +64 -0
  78. package/src/openpress/shared/Panel.tsx +77 -0
  79. package/src/openpress/shared/index.ts +4 -0
  80. package/src/openpress/shared/numberUtils.ts +3 -0
  81. package/src/openpress/{runtimeMode.ts → shared/runtimeMode.ts} +0 -11
  82. package/src/openpress/workbench/Workbench.tsx +506 -0
  83. package/src/openpress/workbench/actions/DeploymentControl.tsx +157 -0
  84. package/src/openpress/workbench/actions/ExportImageControl.tsx +96 -0
  85. package/src/openpress/workbench/actions/PageZoomControl.tsx +182 -0
  86. package/src/openpress/workbench/actions/SearchControl.tsx +345 -0
  87. package/src/openpress/workbench/actions/deploymentStatusModel.ts +112 -0
  88. package/src/openpress/workbench/actions/index.ts +6 -0
  89. package/src/openpress/workbench/actions/useDeploymentWorkbench.ts +136 -0
  90. package/src/openpress/workbench/dialog/WorkbenchDialog.tsx +72 -0
  91. package/src/openpress/workbench/dialog/index.ts +1 -0
  92. package/src/openpress/workbench/document/components/DocumentPanel.tsx +127 -0
  93. package/src/openpress/workbench/document/components/InlineSourceEditorLayer.tsx +207 -0
  94. package/src/openpress/workbench/document/components/ReaderStage.tsx +9 -0
  95. package/src/openpress/workbench/document/hooks/useDocumentWorkbenchModel.ts +34 -0
  96. package/src/openpress/workbench/document/hooks/useInlineDocumentEditor.ts +525 -0
  97. package/src/openpress/workbench/document/index.ts +10 -0
  98. package/src/openpress/workbench/index.ts +2 -0
  99. package/src/openpress/workbench/inspector/InlineInspectorLayer.tsx +459 -0
  100. package/src/openpress/workbench/inspector/index.ts +5 -0
  101. package/src/openpress/workbench/inspector/inlineCommentModel.ts +125 -0
  102. package/src/openpress/workbench/inspector/inspectorGeometryModel.ts +160 -0
  103. package/src/openpress/workbench/inspector/inspectorModel.ts +408 -0
  104. package/src/openpress/workbench/inspector/useInspectorComments.ts +254 -0
  105. package/src/openpress/workbench/mentions/MentionSuggestionList.tsx +41 -0
  106. package/src/openpress/workbench/mentions/index.ts +2 -0
  107. package/src/openpress/{composerMentions.ts → workbench/mentions/useComposerMentions.ts} +1 -4
  108. package/src/openpress/workbench/panels/Panel.tsx +1 -0
  109. package/src/openpress/workbench/panels/PendingCommentsPanel.tsx +80 -0
  110. package/src/openpress/workbench/panels/WorkbenchControlPanel.tsx +29 -0
  111. package/src/openpress/workbench/panels/index.ts +3 -0
  112. package/src/openpress/workbench/project/ProjectEntryPanel.tsx +525 -0
  113. package/src/openpress/workbench/project/ProjectPreviewDialog.tsx +35 -0
  114. package/src/openpress/workbench/project/index.ts +2 -0
  115. package/src/openpress/workbench/project/projectPreviewTypes.ts +11 -0
  116. package/src/openpress/workbench/shell/WorkbenchShell.tsx +167 -0
  117. package/src/openpress/workbench/shell/index.ts +1 -0
  118. package/src/openpress/workbench/workbenchFormatters.ts +120 -0
  119. package/src/openpress/workbench/workbenchTypes.ts +35 -0
  120. package/src/styles/openpress/print-route.css +0 -2
  121. package/src/styles/openpress/{project-workspace.css → project-preview-panel.css} +13 -407
  122. package/src/styles/openpress/public-viewer.css +25 -320
  123. package/src/styles/openpress/reader-runtime.css +252 -55
  124. package/src/styles/openpress/responsive.css +145 -270
  125. package/src/styles/openpress/workbench-panels.css +327 -178
  126. package/src/styles/openpress/workbench.css +986 -451
  127. package/src/styles/openpress/workspace-gallery.css +300 -0
  128. package/src/styles/openpress.css +2 -1
  129. package/tsconfig.json +1 -1
  130. package/vite.config.ts +50 -0
  131. package/engine/commands/init.mjs +0 -24
  132. package/engine/init.mjs +0 -90
  133. package/src/openpress/App.tsx +0 -127
  134. package/src/openpress/inspector.ts +0 -282
  135. package/src/openpress/projectWorkspace.tsx +0 -919
  136. package/src/openpress/readerRuntime.ts +0 -230
  137. package/src/openpress/workbench.tsx +0 -1265
  138. package/src/openpress/workbenchTypes.ts +0 -4
  139. /package/src/openpress/{readerPageRegistry.ts → reader/readerPageRegistry.ts} +0 -0
  140. /package/src/openpress/{pageRoute.ts → reader/readerPageRoute.ts} +0 -0
  141. /package/src/openpress/{readerScroll.ts → reader/readerScroll.ts} +0 -0
  142. /package/src/openpress/{readerState.ts → reader/readerStateModel.ts} +0 -0
  143. /package/src/openpress/{frameScheduler.ts → shared/frameScheduler.ts} +0 -0
  144. /package/src/openpress/{projectSources.ts → workbench/project/projectSourceModel.ts} +0 -0
@@ -1,230 +0,0 @@
1
- import { useCallback, useEffect, useRef, useState, type RefCallback } from "react";
2
- import { pageIndexFromHash, replacePageRoute } from "./pageRoute";
3
- import { createReaderPageRegistry } from "./readerPageRegistry";
4
- import { clampReaderPageIndex, formatReaderPageNumber, normalizeReaderPageCount } from "./readerState";
5
- import { createPageVisibilityObserver, scrollToPage } from "./readerScroll";
6
-
7
- export interface SetPageOptions {
8
- behavior?: ScrollBehavior;
9
- }
10
-
11
- interface UseReaderRuntimeOptions {
12
- pageCount: number;
13
- rightPanelBreakpoint?: number;
14
- }
15
-
16
- // Generous upper bound on a smooth scrollIntoView. If the target ref is gone or
17
- // the browser never settles on it, clear the guard so the IO observer regains
18
- // authority over currentPageIndex.
19
- const PROGRAMMATIC_SCROLL_FALLBACK_MS = 2500;
20
-
21
- export function useReaderRuntime({ pageCount, rightPanelBreakpoint = 1000 }: UseReaderRuntimeOptions) {
22
- const normalizedPageCount = normalizeReaderPageCount(pageCount);
23
- const stageRef = useRef<HTMLElement | null>(null);
24
- const [pageRegistrationVersion, setPageRegistrationVersion] = useState(0);
25
- const pageRegistry = useRef<ReturnType<typeof createReaderPageRegistry<HTMLElement>> | null>(null);
26
- if (!pageRegistry.current) {
27
- pageRegistry.current = createReaderPageRegistry<HTMLElement>(setPageRegistrationVersion);
28
- }
29
-
30
- const [currentPageIndex, setCurrentPageIndex] = useState(() => {
31
- if (typeof window === "undefined") return 0;
32
- const fromHash = pageIndexFromHash(window.location.hash, normalizedPageCount);
33
- return fromHash ?? 0;
34
- });
35
- const [rightPanelOpen, setRightPanelOpen] = useState(() =>
36
- typeof window === "undefined" ? true : window.innerWidth >= rightPanelBreakpoint,
37
- );
38
-
39
- const currentPageIndexRef = useRef(currentPageIndex);
40
- currentPageIndexRef.current = currentPageIndex;
41
-
42
- // While a programmatic scroll is in flight, the IntersectionObserver should
43
- // only accept the destination page (not the intermediates we sweep past).
44
- // The ref clears as soon as the destination becomes visible.
45
- const pendingScrollTargetRef = useRef<number | null>(null);
46
- const pendingScrollClearTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
47
-
48
- const armPendingScrollTarget = useCallback((target: number) => {
49
- pendingScrollTargetRef.current = target;
50
- if (pendingScrollClearTimerRef.current !== null) clearTimeout(pendingScrollClearTimerRef.current);
51
- pendingScrollClearTimerRef.current = setTimeout(() => {
52
- pendingScrollTargetRef.current = null;
53
- pendingScrollClearTimerRef.current = null;
54
- }, PROGRAMMATIC_SCROLL_FALLBACK_MS);
55
- }, []);
56
-
57
- const clearPendingScrollTarget = useCallback(() => {
58
- pendingScrollTargetRef.current = null;
59
- if (pendingScrollClearTimerRef.current !== null) {
60
- clearTimeout(pendingScrollClearTimerRef.current);
61
- pendingScrollClearTimerRef.current = null;
62
- }
63
- }, []);
64
-
65
- useEffect(() => () => clearPendingScrollTarget(), [clearPendingScrollTarget]);
66
-
67
- useEffect(() => {
68
- pageRegistry.current?.trim(normalizedPageCount);
69
- setCurrentPageIndex((idx) => clampReaderPageIndex(idx, normalizedPageCount));
70
- }, [normalizedPageCount]);
71
-
72
- useEffect(() => {
73
- const stage = stageRef.current;
74
- if (!stage) return undefined;
75
- const observer = createPageVisibilityObserver(stage, (pageIndex) => {
76
- // During a programmatic scroll, ignore intermediate pages the browser
77
- // sweeps past; only the destination counts as the new current page.
78
- if (pendingScrollTargetRef.current !== null) {
79
- if (pageIndex !== pendingScrollTargetRef.current) return;
80
- clearPendingScrollTarget();
81
- }
82
- setCurrentPageIndex((prev) => (prev === pageIndex ? prev : pageIndex));
83
- });
84
- if (!observer) return undefined;
85
- pageRegistry.current?.refs.forEach((el) => el && observer.observe(el));
86
- return () => observer.disconnect();
87
- }, [clearPendingScrollTarget, normalizedPageCount, pageRegistrationVersion]);
88
-
89
- useEffect(() => {
90
- if (typeof window === "undefined") return;
91
- replacePageRoute(currentPageIndex);
92
- }, [currentPageIndex]);
93
-
94
- // When refs change (initial mount, pagination kicks in), re-anchor the
95
- // stage to the page we already believe we're on. Otherwise scroll-snap
96
- // mandatory snaps to whichever page happens to sit closest to the current
97
- // scroll position. Only fire when we have somewhere non-default to land,
98
- // so the IO observer stays free to drive state during ordinary navigation.
99
- useEffect(() => {
100
- const refs = pageRegistry.current?.refs ?? [];
101
- const idx = currentPageIndexRef.current;
102
- if (idx === 0) return;
103
- if (!refs[idx]) return;
104
- armPendingScrollTarget(idx);
105
- scrollToPage(refs, idx, "instant", stageRef.current);
106
- }, [armPendingScrollTarget, pageRegistrationVersion]);
107
-
108
- useEffect(() => {
109
- if (typeof window === "undefined") return undefined;
110
-
111
- const syncFromHash = (behavior: ScrollBehavior) => {
112
- const refs = pageRegistry.current?.refs ?? [];
113
- const hashPage = pageIndexFromHash(window.location.hash, normalizedPageCount);
114
- if (hashPage === null) return;
115
- // Our own replacePageRoute call writes the hash to mirror state; skip
116
- // if the hash already matches so we don't fight ourselves.
117
- if (hashPage === currentPageIndexRef.current) return;
118
- armPendingScrollTarget(hashPage);
119
- setCurrentPageIndex(hashPage);
120
- scrollToPage(refs, hashPage, behavior, stageRef.current);
121
- };
122
-
123
- const onHashChange = () => syncFromHash("smooth");
124
- window.addEventListener("hashchange", onHashChange);
125
- window.addEventListener("popstate", onHashChange);
126
- return () => {
127
- window.removeEventListener("hashchange", onHashChange);
128
- window.removeEventListener("popstate", onHashChange);
129
- };
130
- }, [armPendingScrollTarget, normalizedPageCount]);
131
-
132
- useEffect(() => {
133
- if (typeof window === "undefined") return undefined;
134
-
135
- let frame: number | null = null;
136
- const reAnchorAfterPaint = () => {
137
- if (frame !== null) window.cancelAnimationFrame(frame);
138
- frame = window.requestAnimationFrame(() => {
139
- frame = null;
140
- const refs = pageRegistry.current?.refs ?? [];
141
- // If a programmatic scroll is in flight, re-anchor to its destination
142
- // so the snap doesn't pull us back to where we were before clicking.
143
- const target = pendingScrollTargetRef.current ?? currentPageIndexRef.current;
144
- scrollToPage(refs, target, "instant", stageRef.current);
145
- });
146
- };
147
-
148
- const handleResize = () => {
149
- setRightPanelOpen(window.innerWidth >= rightPanelBreakpoint);
150
- // scroll-snap-type: y mandatory re-aligns to the closest snap point on
151
- // viewport change, which can land one page off from where the reader was.
152
- // Pin to the IO-confirmed current page (or active programmatic target).
153
- reAnchorAfterPaint();
154
- };
155
-
156
- handleResize();
157
- window.addEventListener("resize", handleResize);
158
- window.visualViewport?.addEventListener("resize", handleResize);
159
- return () => {
160
- window.removeEventListener("resize", handleResize);
161
- window.visualViewport?.removeEventListener("resize", handleResize);
162
- if (frame !== null) window.cancelAnimationFrame(frame);
163
- };
164
- }, [rightPanelBreakpoint]);
165
-
166
- const setPage = useCallback(
167
- (pageIndex: number, options: SetPageOptions = {}) => {
168
- const refs = pageRegistry.current?.refs ?? [];
169
- const target = clampReaderPageIndex(pageIndex, normalizedPageCount);
170
- armPendingScrollTarget(target);
171
- setCurrentPageIndex(target);
172
- scrollToPage(refs, target, options.behavior ?? "smooth", stageRef.current);
173
- },
174
- [armPendingScrollTarget, normalizedPageCount],
175
- );
176
-
177
- const nextPage = useCallback(() => {
178
- setPage(currentPageIndexRef.current + 1);
179
- }, [setPage]);
180
-
181
- const prevPage = useCallback(() => {
182
- setPage(currentPageIndexRef.current - 1);
183
- }, [setPage]);
184
-
185
- useEffect(() => {
186
- const handleKeyDown = (event: KeyboardEvent) => {
187
- if (isEditableTarget(event.target)) return;
188
- if (event.key === "ArrowRight" || event.key === "PageDown") {
189
- event.preventDefault();
190
- nextPage();
191
- } else if (event.key === "ArrowLeft" || event.key === "PageUp") {
192
- event.preventDefault();
193
- prevPage();
194
- } else if (event.key === "Home") {
195
- event.preventDefault();
196
- setPage(0);
197
- } else if (event.key === "End") {
198
- event.preventDefault();
199
- setPage(Math.max(0, normalizedPageCount - 1));
200
- }
201
- };
202
- window.addEventListener("keydown", handleKeyDown);
203
- return () => window.removeEventListener("keydown", handleKeyDown);
204
- }, [nextPage, prevPage, setPage, normalizedPageCount]);
205
-
206
- const registerPage = useCallback<(pageIndex: number) => RefCallback<HTMLElement>>(
207
- (pageIndex) => pageRegistry.current?.registerPage(pageIndex) ?? (() => undefined),
208
- [],
209
- );
210
-
211
- const progressPercent =
212
- normalizedPageCount <= 1 ? 100 : ((currentPageIndex + 1) / normalizedPageCount) * 100;
213
-
214
- return {
215
- stageRef,
216
- currentPageIndex,
217
- currentPageLabel: formatReaderPageNumber(currentPageIndex + 1),
218
- totalPageLabel: formatReaderPageNumber(normalizedPageCount),
219
- progressPercent,
220
- rightPanelOpen,
221
- registerPage,
222
- setPage,
223
- toggleRightPanel: () => setRightPanelOpen((open) => !open),
224
- };
225
- }
226
-
227
- function isEditableTarget(target: EventTarget | null) {
228
- if (!(target instanceof HTMLElement)) return false;
229
- return Boolean(target.closest("input, textarea, select, button, [contenteditable='true']"));
230
- }