@open-press/cli 1.0.0 → 1.1.1

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,459 +0,0 @@
1
- import {
2
- memo,
3
- useCallback,
4
- useEffect,
5
- useLayoutEffect,
6
- useMemo,
7
- useRef,
8
- useState,
9
- type FormEvent,
10
- type RefObject,
11
- } from "react";
12
- import { ArrowUp, Pencil, Plus, Trash2 } from "lucide-react";
13
- import { MentionSuggestionList, useComposerMentions } from "../mentions";
14
- import type {
15
- InspectorState,
16
- ObjectSelection,
17
- } from "./inspectorModel";
18
- import type { ProjectMentionItem } from "../project";
19
- import {
20
- collectInspectorBlockElements,
21
- createInspectorComposerStyle,
22
- createInspectorInsertTargets,
23
- createInspectorMarkerStyle,
24
- rectToFixedStyle,
25
- resolveInspectorSelectionRect,
26
- syncInspectorSelectedBlock,
27
- } from "./inspectorGeometryModel";
28
- import {
29
- getInlineSavedCommentForTarget,
30
- getInlineSavedCommentMarkers,
31
- } from "./inlineCommentModel";
32
- import type {
33
- InlineSavedComment,
34
- InlineSavedCommentMarkerEntry,
35
- InspectorCommentStatus,
36
- InspectorInsertTargetView,
37
- InspectorLayerRect,
38
- } from "../workbenchTypes";
39
-
40
- type ComposerAction = "add" | "edit" | "delete";
41
-
42
- const COMPOSER_ACTIONS: Array<{ action: ComposerAction; label: string; icon: typeof Plus; prefix: string }> = [
43
- { action: "add", label: "Add", icon: Plus, prefix: "請新增:" },
44
- { action: "edit", label: "Edit", icon: Pencil, prefix: "請修改:" },
45
- { action: "delete", label: "Remove", icon: Trash2, prefix: "請刪除這個物件。" },
46
- ];
47
-
48
- export interface InlineCommentController {
49
- saved: InlineSavedComment[];
50
- active: InlineSavedComment | null;
51
- status: InspectorCommentStatus;
52
- statusMessage: string;
53
- totalCount?: number;
54
- onOpenSaved: (comment: InlineSavedComment) => void;
55
- onRemoveSaved: (comment: InlineSavedComment) => Promise<void>;
56
- }
57
-
58
- export interface InlineComposerController {
59
- text: string;
60
- submitDisabled: boolean;
61
- mentionItems: ProjectMentionItem[];
62
- onTextChange: (value: string) => void;
63
- onSubmit: (event?: FormEvent<HTMLFormElement>) => Promise<void>;
64
- }
65
-
66
- export interface InlineInspectorLayerProps {
67
- sourceContainerRef: RefObject<HTMLDivElement | null>;
68
- inspector: InspectorState;
69
- comments: InlineCommentController;
70
- composer: InlineComposerController;
71
- geometryVersion?: unknown;
72
- }
73
-
74
- function InlineInspectorLayerImpl({
75
- sourceContainerRef,
76
- inspector,
77
- comments,
78
- composer,
79
- geometryVersion,
80
- }: InlineInspectorLayerProps) {
81
- const savedComments = comments.saved;
82
- const savedCommentTotalCount = comments.totalCount ?? savedComments.length;
83
- const activeSavedComment = comments.active;
84
- const commentText = composer.text;
85
- const commentStatus = comments.status;
86
- const commentStatusMessage = comments.statusMessage;
87
- const submitDisabled = composer.submitDisabled;
88
- const mentionItems = composer.mentionItems;
89
- const onOpenSavedComment = comments.onOpenSaved;
90
- const onRemoveSavedComment = comments.onRemoveSaved;
91
- const onCommentTextChange = composer.onTextChange;
92
- const onSubmitComment = composer.onSubmit;
93
- const textareaRef = useRef<HTMLTextAreaElement | null>(null);
94
- const composerRef = useRef<HTMLFormElement | null>(null);
95
- const rafRef = useRef<number | null>(null);
96
- const active = inspector.enabled && inspector.inspectorMode;
97
- const selectedTarget = inspector.selectedTarget;
98
- const selectedTargetKey = objectSelectionKey(selectedTarget);
99
- const activeSavedCommentForTarget = selectedTarget && activeSavedComment
100
- && inlineCommentTargetKey(activeSavedComment) === selectedTargetKey
101
- && activeSavedComment.placement === selectedTarget.placement
102
- ? activeSavedComment
103
- : null;
104
- const savedCommentForTarget = activeSavedCommentForTarget
105
- ?? getInlineSavedCommentForTarget(savedComments, selectedTarget);
106
- const markerEntries = useMemo<InlineSavedCommentMarkerEntry[]>(
107
- () => getInlineSavedCommentMarkers(savedComments),
108
- [savedComments],
109
- );
110
- const savedCommentLabels = useMemo(() => {
111
- const labels = new Map<string, string>();
112
- savedComments.forEach((comment, index) => labels.set(comment.id, String(index + 1)));
113
- return labels;
114
- }, [savedComments]);
115
- const markerEntriesByTarget = useMemo(
116
- () => new Set(markerEntries.map(({ target }) => objectSelectionKey(target))),
117
- [markerEntries],
118
- );
119
- const markerDisplayEntries = useMemo(
120
- () => [
121
- ...markerEntries,
122
- ...(selectedTarget && !markerEntriesByTarget.has(selectedTargetKey ?? "")
123
- ? [{ target: selectedTarget, comments: [] }]
124
- : []),
125
- ],
126
- [markerEntries, markerEntriesByTarget, selectedTarget, selectedTargetKey],
127
- );
128
- const [insertTargets, setInsertTargets] = useState<InspectorInsertTargetView[]>([]);
129
- const [selectionRect, setSelectionRect] = useState<InspectorLayerRect | null>(null);
130
- const [composerTargetKey, setComposerTargetKey] = useState<string | null>(null);
131
- const composerOpen = Boolean(selectedTargetKey && composerTargetKey === selectedTargetKey);
132
- const markerOnly = Boolean(savedCommentForTarget && !composerOpen && !activeSavedCommentForTarget);
133
- const {
134
- activeMention,
135
- handleMentionKeyDown,
136
- highlightedMentionIndex,
137
- mentionSuggestions,
138
- setHighlightedMentionIndex,
139
- setComposerCursor,
140
- syncCursor,
141
- insertMention,
142
- } = useComposerMentions({
143
- text: commentText,
144
- items: mentionItems,
145
- textareaRef,
146
- onTextChange: onCommentTextChange,
147
- enabled: composerOpen,
148
- });
149
-
150
- const updateLayer = useCallback(() => {
151
- const root = sourceContainerRef.current;
152
- if (!active || !root) {
153
- setInsertTargets([]);
154
- setSelectionRect(null);
155
- if (root) syncInspectorSelectedBlock(root, null);
156
- return;
157
- }
158
-
159
- const blockElements = collectInspectorBlockElements(root);
160
- const nextInsertTargets = createInspectorInsertTargets(blockElements);
161
- setInsertTargets(nextInsertTargets);
162
- setSelectionRect(resolveInspectorSelectionRect(root, selectedTarget, nextInsertTargets));
163
- syncInspectorSelectedBlock(root, markerOnly ? null : selectedTarget);
164
- }, [active, geometryVersion, markerOnly, selectedTarget, sourceContainerRef]);
165
-
166
- const scheduleLayerUpdate = useCallback(() => {
167
- if (rafRef.current !== null) window.cancelAnimationFrame(rafRef.current);
168
- rafRef.current = window.requestAnimationFrame(() => {
169
- rafRef.current = null;
170
- updateLayer();
171
- });
172
- }, [updateLayer]);
173
-
174
- useLayoutEffect(() => {
175
- updateLayer();
176
- }, [updateLayer]);
177
-
178
- useEffect(() => {
179
- if (!active) return undefined;
180
- const root = sourceContainerRef.current;
181
- const resizeObserver = typeof ResizeObserver === "undefined" || !root
182
- ? null
183
- : new ResizeObserver(scheduleLayerUpdate);
184
- if (root && resizeObserver) resizeObserver.observe(root);
185
- window.addEventListener("resize", scheduleLayerUpdate);
186
- window.addEventListener("scroll", scheduleLayerUpdate, true);
187
-
188
- return () => {
189
- resizeObserver?.disconnect();
190
- window.removeEventListener("resize", scheduleLayerUpdate);
191
- window.removeEventListener("scroll", scheduleLayerUpdate, true);
192
- if (rafRef.current !== null) window.cancelAnimationFrame(rafRef.current);
193
- rafRef.current = null;
194
- };
195
- }, [active, scheduleLayerUpdate, sourceContainerRef]);
196
-
197
- useEffect(() => {
198
- if (!selectedTarget || composerTargetKey !== selectedTargetKey) return undefined;
199
- let innerFrame: number | null = null;
200
- const outerFrame = window.requestAnimationFrame(() => {
201
- innerFrame = window.requestAnimationFrame(() => textareaRef.current?.focus({ preventScroll: true }));
202
- });
203
- return () => {
204
- window.cancelAnimationFrame(outerFrame);
205
- if (innerFrame !== null) window.cancelAnimationFrame(innerFrame);
206
- };
207
- }, [composerTargetKey, selectedTarget, selectedTargetKey]);
208
-
209
- useEffect(() => {
210
- setComposerTargetKey(null);
211
- }, [selectedTargetKey]);
212
-
213
- useEffect(() => {
214
- if (commentStatus === "saved") setComposerTargetKey(null);
215
- }, [commentStatus]);
216
-
217
- useEffect(() => {
218
- if (!composerOpen) return undefined;
219
-
220
- const isInsideComposer = (target: EventTarget | null) => {
221
- const composerElement = composerRef.current;
222
- return Boolean(composerElement && target instanceof Node && composerElement.contains(target));
223
- };
224
- const blockOutsideComposer = (event: Event) => {
225
- if (isInsideComposer(event.target)) return;
226
- event.preventDefault();
227
- event.stopPropagation();
228
- };
229
- const blockScrollKeyOutsideComposer = (event: KeyboardEvent) => {
230
- if (!isScrollKey(event) || isInsideComposer(event.target)) return;
231
- event.preventDefault();
232
- event.stopPropagation();
233
- };
234
-
235
- window.addEventListener("wheel", blockOutsideComposer, { capture: true, passive: false });
236
- window.addEventListener("touchmove", blockOutsideComposer, { capture: true, passive: false });
237
- window.addEventListener("keydown", blockScrollKeyOutsideComposer, { capture: true });
238
- return () => {
239
- window.removeEventListener("wheel", blockOutsideComposer, true);
240
- window.removeEventListener("touchmove", blockOutsideComposer, true);
241
- window.removeEventListener("keydown", blockScrollKeyOutsideComposer, true);
242
- };
243
- }, [composerOpen]);
244
-
245
- if (!active) return null;
246
-
247
- const composerStyle = selectionRect ? createInspectorComposerStyle(selectionRect, composerOpen) : undefined;
248
- const visibleActionItems = savedCommentForTarget
249
- ? COMPOSER_ACTIONS.filter((item) => item.action !== "add")
250
- : COMPOSER_ACTIONS;
251
- const applyComposerAction = (action: ComposerAction) => {
252
- if (!selectedTargetKey) return;
253
- const item = COMPOSER_ACTIONS.find((entry) => entry.action === action);
254
- if (!item) return;
255
- setComposerTargetKey(selectedTargetKey);
256
- if (!commentText.trim()) onCommentTextChange(item.prefix);
257
- };
258
- const handleMarkerClick = (target: ObjectSelection, comments: InlineSavedComment[]) => {
259
- if (!target) return;
260
- inspector.selectSelection(target);
261
- setComposerTargetKey(null);
262
- if (comments.length === 0) {
263
- onCommentTextChange("");
264
- return;
265
- }
266
- onOpenSavedComment(comments[0]!);
267
- };
268
- const getMarkerRect = (target: ObjectSelection) => {
269
- const root = sourceContainerRef.current;
270
- if (!root) return null;
271
- return resolveInspectorSelectionRect(root, target, insertTargets);
272
- };
273
- const markerViews = markerDisplayEntries.flatMap((markerEntry) => {
274
- const markerRect = getMarkerRect(markerEntry.target);
275
- if (!markerRect) return [];
276
- if (!isMarkerRectNearViewport(markerRect)) return [];
277
- return [{
278
- markerEntry,
279
- markerRect,
280
- markerLabel: markerLabelForEntry(markerEntry, savedCommentLabels, savedCommentTotalCount),
281
- markerStyle: createInspectorMarkerStyle(markerRect),
282
- }];
283
- }).sort((left, right) => compareMarkerRects(left.markerRect, right.markerRect));
284
-
285
- return (
286
- <div
287
- className="openpress-inline-inspector-layer"
288
- data-openpress-inline-inspector-layer
289
- data-openpress-composer-lock-events={composerOpen ? "true" : "false"}
290
- >
291
- {insertTargets.map((target) => {
292
- const isSelected = selectedTarget?.blockId === target.blockId && selectedTarget.placement === "before";
293
- return (
294
- <button
295
- type="button"
296
- className={`openpress-inline-insert-target${isSelected ? " is-selected" : ""}`}
297
- data-openpress-insert-before-block-id={target.blockId}
298
- style={rectToFixedStyle(target.rect)}
299
- aria-label="在此新增註解"
300
- key={target.blockId}
301
- onClick={() => inspector.selectSelection({ blockId: target.blockId, placement: "before" })}
302
- />
303
- );
304
- })}
305
-
306
- {markerViews.map(({ markerEntry, markerLabel, markerStyle }) => {
307
- const markerCount = markerEntry.comments.length;
308
- const hasSavedComment = markerEntry.comments.length > 0;
309
- return (
310
- <button
311
- type="button"
312
- className="openpress-inline-comment-marker"
313
- data-openpress-inline-comment-marker
314
- data-openpress-inline-comment-marker-object-id={markerEntry.target.objectId}
315
- data-openpress-inline-comment-marker-block-id={markerEntry.target.blockId}
316
- data-openpress-inline-comment-marker-placement={markerEntry.target.placement}
317
- data-openpress-marker-label={markerLabel}
318
- data-openpress-marker-state={hasSavedComment ? "saved" : "draft"}
319
- style={markerStyle}
320
- aria-label={hasSavedComment ? `編輯註解 ${markerLabel},${markerCount} 則` : `目前選取區塊 ${markerLabel}`}
321
- key={objectSelectionKey(markerEntry.target) ?? markerEntry.target.placement}
322
- onClick={() => handleMarkerClick(markerEntry.target, markerEntry.comments)}
323
- >
324
- <span className="openpress-inline-comment-marker__index">{markerLabel}</span>
325
- </button>
326
- );
327
- })}
328
-
329
- {selectionRect && selectedTarget && !markerOnly ? (
330
- <form
331
- ref={composerRef}
332
- className="openpress-inline-comment-composer"
333
- data-openpress-inline-comment-composer
334
- data-openpress-comment-placement={selectedTarget.placement}
335
- data-openpress-composer-open={composerOpen ? "true" : "false"}
336
- data-openpress-composer-saved={savedCommentForTarget ? "true" : "false"}
337
- style={composerStyle}
338
- onSubmit={(event) => void onSubmitComment(event)}
339
- >
340
- {!composerOpen ? (
341
- <div className="openpress-inline-comment-composer__intents" aria-label="註解意圖">
342
- {visibleActionItems.map((item) => {
343
- const Icon = item.icon;
344
- return (
345
- <button
346
- type="button"
347
- aria-label={item.label}
348
- title={item.label}
349
- key={item.action}
350
- onClick={() => {
351
- if (savedCommentForTarget && item.action === "delete") {
352
- void onRemoveSavedComment(savedCommentForTarget);
353
- return;
354
- }
355
- applyComposerAction(item.action);
356
- }}
357
- >
358
- <Icon aria-hidden="true" />
359
- </button>
360
- );
361
- })}
362
- </div>
363
- ) : null}
364
- {composerOpen ? (
365
- <div className="openpress-inline-comment-composer__body">
366
- <textarea
367
- ref={textareaRef}
368
- value={commentText}
369
- disabled={commentStatus === "submitting"}
370
- onChange={(event) => {
371
- onCommentTextChange(event.target.value);
372
- setComposerCursor(event.target.selectionStart ?? event.target.value.length);
373
- }}
374
- onClick={syncCursor}
375
- onKeyUp={syncCursor}
376
- onKeyDown={(event) => {
377
- if (handleMentionKeyDown(event)) return;
378
- if ((event.metaKey || event.ctrlKey) && event.key === "Enter") {
379
- event.preventDefault();
380
- void onSubmitComment();
381
- }
382
- }}
383
- aria-label={savedCommentForTarget ? "編輯註解" : "新增註解"}
384
- placeholder="新增註解..."
385
- rows={3}
386
- />
387
- <button type="submit" disabled={submitDisabled} aria-label="送出註解">
388
- <ArrowUp aria-hidden="true" />
389
- </button>
390
- </div>
391
- ) : null}
392
- {composerOpen ? (
393
- <MentionSuggestionList
394
- className="openpress-inline-comment-composer__suggestions"
395
- suggestions={mentionSuggestions}
396
- highlightedIndex={highlightedMentionIndex}
397
- ariaLabel={activeMention?.trigger === "/" ? "Skill suggestions" : "Mention suggestions"}
398
- onHighlight={setHighlightedMentionIndex}
399
- onSelect={insertMention}
400
- />
401
- ) : null}
402
- {composerOpen && commentStatusMessage ? (
403
- <p role="status" aria-live="polite" data-openpress-inspector-comment-status={commentStatus}>
404
- {commentStatusMessage}
405
- </p>
406
- ) : null}
407
- </form>
408
- ) : null}
409
- </div>
410
- );
411
- }
412
-
413
- function objectSelectionKey(target: ObjectSelection | null) {
414
- if (!target) return null;
415
- return `${target.objectId ?? target.blockId ?? "unknown"}:${target.placement}`;
416
- }
417
-
418
- function inlineCommentTargetKey(comment: InlineSavedComment) {
419
- return `${comment.objectId ?? comment.blockId ?? "unknown"}:${comment.placement}`;
420
- }
421
-
422
- function isScrollKey(event: KeyboardEvent) {
423
- return event.key === " "
424
- || event.key === "Spacebar"
425
- || event.key === "PageDown"
426
- || event.key === "PageUp"
427
- || event.key === "Home"
428
- || event.key === "End"
429
- || event.key === "ArrowDown"
430
- || event.key === "ArrowUp";
431
- }
432
-
433
- function compareMarkerRects(left: InspectorLayerRect, right: InspectorLayerRect) {
434
- const topDelta = left.top - right.top;
435
- if (Math.abs(topDelta) > 1) return topDelta;
436
- return left.left - right.left;
437
- }
438
-
439
- function markerLabelForEntry(
440
- markerEntry: InlineSavedCommentMarkerEntry,
441
- savedCommentLabels: Map<string, string>,
442
- savedCommentCount: number,
443
- ) {
444
- const firstComment = markerEntry.comments[0];
445
- if (!firstComment) return String(savedCommentCount + 1);
446
- if (firstComment.markerLabel) return firstComment.markerLabel;
447
- return savedCommentLabels.get(firstComment.id) ?? String(savedCommentCount + 1);
448
- }
449
-
450
- function isMarkerRectNearViewport(rect: InspectorLayerRect, margin = 48) {
451
- if (typeof window === "undefined") return true;
452
- return rect.top + rect.height >= -margin
453
- && rect.top <= window.innerHeight + margin
454
- && rect.left + rect.width >= -margin
455
- && rect.left <= window.innerWidth + margin;
456
- }
457
-
458
- export const InlineInspectorLayer = memo(InlineInspectorLayerImpl);
459
- InlineInspectorLayer.displayName = "InlineInspectorLayer";
@@ -1,5 +0,0 @@
1
- export * from "./InlineInspectorLayer";
2
- export * from "./inlineCommentModel";
3
- export * from "./inspectorGeometryModel";
4
- export * from "./inspectorModel";
5
- export * from "./useInspectorComments";
@@ -1,125 +0,0 @@
1
- import type { ObjectSelection, PendingComment } from "./inspectorModel";
2
- import type { SourceBlock } from "../../document-model";
3
- import { parseCommentHint } from "../workbenchFormatters";
4
- import type { InlineSavedComment, InlineSavedCommentMarkerEntry } from "../workbenchTypes";
5
-
6
- export function getInlineSavedCommentForTarget(
7
- comments: InlineSavedComment[],
8
- target: ObjectSelection | null,
9
- preferredId?: string | null,
10
- ) {
11
- if (!target) return null;
12
- const targetKey = objectSelectionKey(target);
13
- const targetComments = comments.filter((comment) => inlineCommentTargetKey(comment) === targetKey);
14
- if (!targetComments.length) return null;
15
- if (preferredId) {
16
- const preferred = targetComments.find((comment) => comment.id === preferredId);
17
- if (preferred) return preferred;
18
- }
19
- return targetComments[0] ?? null;
20
- }
21
-
22
- export function getInlineSavedCommentMarkers(comments: InlineSavedComment[]) {
23
- const markerMap = new Map<string, InlineSavedCommentMarkerEntry>();
24
-
25
- for (const comment of comments) {
26
- const target: ObjectSelection = {
27
- objectId: comment.objectId,
28
- blockId: comment.blockId,
29
- placement: comment.placement,
30
- };
31
- const key = objectSelectionKey(target);
32
- if (!key) continue;
33
- const bucket = markerMap.get(key);
34
- if (bucket) {
35
- bucket.comments.push(comment);
36
- } else {
37
- markerMap.set(key, { target, comments: [comment] });
38
- }
39
- }
40
-
41
- return Array.from(markerMap.values()).map((entry) => ({
42
- ...entry,
43
- comments: [...entry.comments],
44
- })).sort((left, right) => {
45
- const leftId = left.target.objectId ?? left.target.blockId ?? "";
46
- const rightId = right.target.objectId ?? right.target.blockId ?? "";
47
- if (leftId === rightId) {
48
- if (left.target.placement === right.target.placement) return 0;
49
- return left.target.placement === "before" ? -1 : 1;
50
- }
51
- return leftId.localeCompare(rightId, "zh-Hant");
52
- });
53
- }
54
-
55
- export function resolveInlineSavedComment(comment: PendingComment, sourceBlocksByPath: Record<string, SourceBlock[]>) {
56
- const target = resolveInlineSavedCommentTarget(comment, sourceBlocksByPath);
57
- if (!target) return [];
58
- const hintMeta = parseCommentHint(comment.hint);
59
- return [{
60
- id: comment.id,
61
- objectId: hintMeta?.targetObjectId,
62
- blockId: target.id,
63
- placement: hintMeta?.placement ?? "block",
64
- note: comment.note,
65
- path: comment.path,
66
- line: comment.line,
67
- timestamp: comment.timestamp,
68
- }];
69
- }
70
-
71
- function objectSelectionKey(target: ObjectSelection | null) {
72
- if (!target) return null;
73
- return `${target.objectId ?? target.blockId ?? "unknown"}\u0000${target.placement}`;
74
- }
75
-
76
- function inlineCommentTargetKey(comment: InlineSavedComment) {
77
- return `${comment.objectId ?? comment.blockId ?? "unknown"}\u0000${comment.placement}`;
78
- }
79
-
80
- export function groupSourceBlocksByPath(sourceBlockMap: Record<string, SourceBlock>) {
81
- const grouped = Object.values(sourceBlockMap).reduce<Record<string, SourceBlock[]>>((accumulator, sourceBlock) => {
82
- const path = normalizeSourcePath(sourceBlock.path);
83
- if (!path) return accumulator;
84
- if (!accumulator[path]) accumulator[path] = [];
85
- accumulator[path].push(sourceBlock);
86
- return accumulator;
87
- }, {});
88
-
89
- Object.values(grouped).forEach((blocks) => {
90
- blocks.sort((left, right) => {
91
- const leftLine = left.source?.line ?? Number.POSITIVE_INFINITY;
92
- const rightLine = right.source?.line ?? Number.POSITIVE_INFINITY;
93
- if (leftLine === rightLine) return left.id.localeCompare(right.id, "zh-Hant");
94
- return leftLine - rightLine;
95
- });
96
- });
97
-
98
- return grouped;
99
- }
100
-
101
- function resolveInlineSavedCommentTarget(comment: PendingComment, sourceBlocksByPath: Record<string, SourceBlock[]>) {
102
- if (!comment.path || !comment.line) return null;
103
- const commentPath = normalizeSourcePath(comment.path);
104
- const candidateBlocks = sourceBlocksByPath[commentPath];
105
- if (!candidateBlocks?.length) return null;
106
-
107
- const normalizedLine = Number(comment.line);
108
- if (!Number.isInteger(normalizedLine) || normalizedLine < 1) return null;
109
-
110
- let selectedBlock: SourceBlock | null = null;
111
- for (const block of candidateBlocks) {
112
- if (typeof block.source?.line !== "number") continue;
113
- if (block.source.line <= normalizedLine) {
114
- selectedBlock = block;
115
- continue;
116
- }
117
- break;
118
- }
119
- if (selectedBlock) return selectedBlock;
120
- return candidateBlocks[0] ?? null;
121
- }
122
-
123
- function normalizeSourcePath(value: string) {
124
- return value.trim().replaceAll("\\", "/").replace(/^\.\//, "").replace(/^document\//, "");
125
- }