@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.
- package/README.md +11 -12
- package/dist/cli.js +298 -79
- package/package.json +9 -7
- package/template/core/AGENTS.md +0 -130
- package/template/core/CHANGELOG.md +0 -218
- package/template/core/README.md +0 -43
- package/template/core/engine/cli.mjs +0 -96
- package/template/core/engine/commands/_shared.mjs +0 -199
- package/template/core/engine/commands/deploy.mjs +0 -31
- package/template/core/engine/commands/dev.mjs +0 -49
- package/template/core/engine/commands/doctor.mjs +0 -229
- package/template/core/engine/commands/export.mjs +0 -8
- package/template/core/engine/commands/image.mjs +0 -29
- package/template/core/engine/commands/inspect.mjs +0 -35
- package/template/core/engine/commands/pdf.mjs +0 -26
- package/template/core/engine/commands/preview.mjs +0 -26
- package/template/core/engine/commands/render.mjs +0 -17
- package/template/core/engine/commands/replace.mjs +0 -41
- package/template/core/engine/commands/search.mjs +0 -33
- package/template/core/engine/commands/skills-sync.mjs +0 -71
- package/template/core/engine/commands/typecheck.mjs +0 -67
- package/template/core/engine/commands/upgrade.mjs +0 -159
- package/template/core/engine/commands/validate.mjs +0 -17
- package/template/core/engine/document-export.mjs +0 -15
- package/template/core/engine/output/chrome-pdf.d.mts +0 -34
- package/template/core/engine/output/chrome-pdf.mjs +0 -450
- package/template/core/engine/output/deploy-sync.mjs +0 -15
- package/template/core/engine/output/fonts.mjs +0 -62
- package/template/core/engine/output/katex-assets.mjs +0 -45
- package/template/core/engine/output/page-block.mjs +0 -30
- package/template/core/engine/output/pdf-media.mjs +0 -45
- package/template/core/engine/output/public-assets.mjs +0 -19
- package/template/core/engine/output/static-server.mjs +0 -571
- package/template/core/engine/react/caption-numbering.mjs +0 -73
- package/template/core/engine/react/comment-endpoint.d.mts +0 -11
- package/template/core/engine/react/comment-endpoint.mjs +0 -102
- package/template/core/engine/react/comment-marker.mjs +0 -374
- package/template/core/engine/react/document-entry.mjs +0 -331
- package/template/core/engine/react/document-export.mjs +0 -512
- package/template/core/engine/react/http-json.mjs +0 -24
- package/template/core/engine/react/mdx-compile.mjs +0 -629
- package/template/core/engine/react/measurement-css.mjs +0 -157
- package/template/core/engine/react/object-entities.mjs +0 -204
- package/template/core/engine/react/pagination/allocator.mjs +0 -167
- package/template/core/engine/react/pagination/regions.mjs +0 -81
- package/template/core/engine/react/pagination-constants.mjs +0 -3
- package/template/core/engine/react/pagination.mjs +0 -9
- package/template/core/engine/react/pipeline/allocate.mjs +0 -217
- package/template/core/engine/react/pipeline/final-render.mjs +0 -94
- package/template/core/engine/react/pipeline/frame-measurement.mjs +0 -306
- package/template/core/engine/react/pipeline/press-tree.mjs +0 -135
- package/template/core/engine/react/press-tree-inspection.mjs +0 -172
- package/template/core/engine/react/project-asset-endpoint.d.mts +0 -10
- package/template/core/engine/react/project-asset-endpoint.mjs +0 -361
- package/template/core/engine/react/section-css.mjs +0 -56
- package/template/core/engine/react/source-edit-endpoint.d.mts +0 -10
- package/template/core/engine/react/source-edit-endpoint.mjs +0 -75
- package/template/core/engine/react/sources/heading-numbering.mjs +0 -132
- package/template/core/engine/react/sources/mdx-resolver.mjs +0 -439
- package/template/core/engine/react/style-discovery.mjs +0 -160
- package/template/core/engine/runtime/config.d.mts +0 -48
- package/template/core/engine/runtime/config.mjs +0 -172
- package/template/core/engine/runtime/file-utils.mjs +0 -114
- package/template/core/engine/runtime/file-walk.mjs +0 -22
- package/template/core/engine/runtime/inspection.mjs +0 -328
- package/template/core/engine/runtime/issue-report.mjs +0 -44
- package/template/core/engine/runtime/page-geometry.mjs +0 -131
- package/template/core/engine/runtime/path-utils.mjs +0 -20
- package/template/core/engine/runtime/source-text-tools.d.mts +0 -102
- package/template/core/engine/runtime/source-text-tools.mjs +0 -832
- package/template/core/engine/runtime/source-workspace.mjs +0 -168
- package/template/core/engine/runtime/validation.mjs +0 -183
- package/template/core/index.html +0 -13
- package/template/core/openpress.config.mjs +0 -8
- package/template/core/package.json +0 -89
- package/template/core/src/main.tsx +0 -16
- package/template/core/src/openpress/app/OpenPressApp.tsx +0 -296
- package/template/core/src/openpress/app/OpenPressRuntime.tsx +0 -102
- package/template/core/src/openpress/app/WorkspaceGalleryPage.tsx +0 -219
- package/template/core/src/openpress/app/index.ts +0 -2
- package/template/core/src/openpress/core/Frame.tsx +0 -91
- package/template/core/src/openpress/core/FrameContext.tsx +0 -26
- package/template/core/src/openpress/core/MdxArea.tsx +0 -34
- package/template/core/src/openpress/core/Press.tsx +0 -55
- package/template/core/src/openpress/core/Workspace.tsx +0 -36
- package/template/core/src/openpress/core/cn.ts +0 -4
- package/template/core/src/openpress/core/index.tsx +0 -47
- package/template/core/src/openpress/core/primitives.tsx +0 -91
- package/template/core/src/openpress/core/types.ts +0 -236
- package/template/core/src/openpress/core/useSource.ts +0 -28
- package/template/core/src/openpress/document-model/anchorMapModel.ts +0 -27
- package/template/core/src/openpress/document-model/documentIndexes.ts +0 -329
- package/template/core/src/openpress/document-model/documentTypes.ts +0 -147
- package/template/core/src/openpress/document-model/index.ts +0 -7
- package/template/core/src/openpress/document-model/objectEntityModel.ts +0 -55
- package/template/core/src/openpress/document-model/projectIdentityModel.ts +0 -15
- package/template/core/src/openpress/document-model/reactDocumentMetadataModel.ts +0 -27
- package/template/core/src/openpress/document-model/workspaceManifestModel.ts +0 -57
- package/template/core/src/openpress/manuscript/index.tsx +0 -238
- package/template/core/src/openpress/mdx/index.ts +0 -96
- package/template/core/src/openpress/numbering/index.ts +0 -294
- package/template/core/src/openpress/reader/PageThumbnailsPanel.tsx +0 -168
- package/template/core/src/openpress/reader/PublicReaderPage.tsx +0 -267
- package/template/core/src/openpress/reader/ReaderNavigationPanel.tsx +0 -123
- package/template/core/src/openpress/reader/index.ts +0 -11
- package/template/core/src/openpress/reader/pageViewportScaleModel.ts +0 -73
- package/template/core/src/openpress/reader/readerPageRegistry.ts +0 -41
- package/template/core/src/openpress/reader/readerPageRoute.ts +0 -21
- package/template/core/src/openpress/reader/readerScroll.ts +0 -92
- package/template/core/src/openpress/reader/readerStateModel.ts +0 -15
- package/template/core/src/openpress/reader/readerTypes.ts +0 -4
- package/template/core/src/openpress/reader/usePageViewportScale.ts +0 -119
- package/template/core/src/openpress/reader/usePanelState.ts +0 -56
- package/template/core/src/openpress/reader/useReaderHashSync.ts +0 -61
- package/template/core/src/openpress/reader/useReaderKeyboardNav.ts +0 -48
- package/template/core/src/openpress/reader/useReaderRuntime.ts +0 -146
- package/template/core/src/openpress/reader/useReaderScrollAnchor.ts +0 -64
- package/template/core/src/openpress/shared/Panel.tsx +0 -77
- package/template/core/src/openpress/shared/frameScheduler.ts +0 -32
- package/template/core/src/openpress/shared/index.ts +0 -4
- package/template/core/src/openpress/shared/numberUtils.ts +0 -3
- package/template/core/src/openpress/shared/runtimeMode.ts +0 -11
- package/template/core/src/openpress/workbench/Workbench.tsx +0 -506
- package/template/core/src/openpress/workbench/actions/DeploymentControl.tsx +0 -157
- package/template/core/src/openpress/workbench/actions/ExportImageControl.tsx +0 -96
- package/template/core/src/openpress/workbench/actions/PageZoomControl.tsx +0 -182
- package/template/core/src/openpress/workbench/actions/SearchControl.tsx +0 -345
- package/template/core/src/openpress/workbench/actions/deploymentStatusModel.ts +0 -112
- package/template/core/src/openpress/workbench/actions/index.ts +0 -6
- package/template/core/src/openpress/workbench/actions/useDeploymentWorkbench.ts +0 -136
- package/template/core/src/openpress/workbench/dialog/WorkbenchDialog.tsx +0 -72
- package/template/core/src/openpress/workbench/dialog/index.ts +0 -1
- package/template/core/src/openpress/workbench/document/components/DocumentPanel.tsx +0 -127
- package/template/core/src/openpress/workbench/document/components/InlineSourceEditorLayer.tsx +0 -207
- package/template/core/src/openpress/workbench/document/components/ReaderStage.tsx +0 -9
- package/template/core/src/openpress/workbench/document/hooks/useDocumentWorkbenchModel.ts +0 -34
- package/template/core/src/openpress/workbench/document/hooks/useInlineDocumentEditor.ts +0 -525
- package/template/core/src/openpress/workbench/document/index.ts +0 -10
- package/template/core/src/openpress/workbench/index.ts +0 -2
- package/template/core/src/openpress/workbench/inspector/InlineInspectorLayer.tsx +0 -459
- package/template/core/src/openpress/workbench/inspector/index.ts +0 -5
- package/template/core/src/openpress/workbench/inspector/inlineCommentModel.ts +0 -125
- package/template/core/src/openpress/workbench/inspector/inspectorGeometryModel.ts +0 -160
- package/template/core/src/openpress/workbench/inspector/inspectorModel.ts +0 -408
- package/template/core/src/openpress/workbench/inspector/useInspectorComments.ts +0 -254
- package/template/core/src/openpress/workbench/mentions/MentionSuggestionList.tsx +0 -41
- package/template/core/src/openpress/workbench/mentions/index.ts +0 -2
- package/template/core/src/openpress/workbench/mentions/useComposerMentions.ts +0 -185
- package/template/core/src/openpress/workbench/panels/Panel.tsx +0 -1
- package/template/core/src/openpress/workbench/panels/PendingCommentsPanel.tsx +0 -80
- package/template/core/src/openpress/workbench/panels/WorkbenchControlPanel.tsx +0 -29
- package/template/core/src/openpress/workbench/panels/index.ts +0 -3
- package/template/core/src/openpress/workbench/project/ProjectEntryPanel.tsx +0 -525
- package/template/core/src/openpress/workbench/project/ProjectPreviewDialog.tsx +0 -35
- package/template/core/src/openpress/workbench/project/index.ts +0 -2
- package/template/core/src/openpress/workbench/project/projectPreviewTypes.ts +0 -11
- package/template/core/src/openpress/workbench/project/projectSourceModel.ts +0 -24
- package/template/core/src/openpress/workbench/shell/WorkbenchShell.tsx +0 -167
- package/template/core/src/openpress/workbench/shell/index.ts +0 -1
- package/template/core/src/openpress/workbench/workbenchFormatters.ts +0 -120
- package/template/core/src/openpress/workbench/workbenchTypes.ts +0 -35
- package/template/core/src/styles/openpress/app-shell.css +0 -251
- package/template/core/src/styles/openpress/media-workspace.css +0 -230
- package/template/core/src/styles/openpress/print-route.css +0 -184
- package/template/core/src/styles/openpress/project-preview-panel.css +0 -924
- package/template/core/src/styles/openpress/public-viewer.css +0 -688
- package/template/core/src/styles/openpress/reader-runtime.css +0 -989
- package/template/core/src/styles/openpress/responsive.css +0 -245
- package/template/core/src/styles/openpress/workbench-panels.css +0 -707
- package/template/core/src/styles/openpress/workbench.css +0 -1255
- package/template/core/src/styles/openpress/workspace-gallery.css +0 -300
- package/template/core/src/styles/openpress.css +0 -15
- package/template/core/src/vite-env.d.ts +0 -9
- package/template/core/tsconfig.json +0 -40
- package/template/core/vite.config.ts +0 -584
|
@@ -1,254 +0,0 @@
|
|
|
1
|
-
import { useCallback, useEffect, useMemo, useState, type FormEvent, type RefObject } from "react";
|
|
2
|
-
import type { SourceBlock } from "../../document-model";
|
|
3
|
-
import type { InlineSavedComment, InspectorCommentStatus, PendingCommentsStatus } from "../workbenchTypes";
|
|
4
|
-
import { formatInspectorCommentStatus } from "../workbenchFormatters";
|
|
5
|
-
import { clearInspectorComment, fetchInspectorComments } from "./inspectorModel";
|
|
6
|
-
import { createInspectorCommentDraft, submitInspectorComment, updateInspectorComment } from "./inspectorModel";
|
|
7
|
-
import type { InspectorState, PendingComment } from "./inspectorModel";
|
|
8
|
-
import { getInlineSavedCommentForTarget, resolveInlineSavedComment } from "./inlineCommentModel";
|
|
9
|
-
|
|
10
|
-
export interface UseInspectorCommentsOptions {
|
|
11
|
-
devMode: boolean;
|
|
12
|
-
inspector: InspectorState;
|
|
13
|
-
sourceBlockMap: Record<string, SourceBlock>;
|
|
14
|
-
sourceBlocksByPath: Record<string, SourceBlock[]>;
|
|
15
|
-
sourceContainerRef: RefObject<HTMLDivElement | null>;
|
|
16
|
-
onSelectWorkspacePage: (pageIndex: number, options?: { behavior?: ScrollBehavior }) => void;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
export interface InspectorComments {
|
|
20
|
-
pendingComments: PendingComment[];
|
|
21
|
-
commentsStatus: PendingCommentsStatus;
|
|
22
|
-
commentsError: string;
|
|
23
|
-
inspectorCommentText: string;
|
|
24
|
-
inspectorCommentStatus: InspectorCommentStatus;
|
|
25
|
-
inspectorCommentStatusMessage: string;
|
|
26
|
-
inspectorCommentDisabled: boolean;
|
|
27
|
-
inlineSavedComments: InlineSavedComment[];
|
|
28
|
-
activeInlineSavedComment: InlineSavedComment | null;
|
|
29
|
-
setInspectorCommentText: (value: string) => void;
|
|
30
|
-
refreshPendingComments: () => Promise<void>;
|
|
31
|
-
clearPendingComment: (id: string) => Promise<void>;
|
|
32
|
-
handleSubmitInspectorComment: (event?: FormEvent<HTMLFormElement>) => Promise<void>;
|
|
33
|
-
handleOpenInlineSavedComment: (comment: InlineSavedComment) => void;
|
|
34
|
-
handleRemoveInlineSavedComment: (comment: InlineSavedComment) => Promise<void>;
|
|
35
|
-
handleSelectPendingComment: (comment: PendingComment) => void;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
export function useInspectorComments({
|
|
39
|
-
devMode,
|
|
40
|
-
inspector,
|
|
41
|
-
sourceBlockMap,
|
|
42
|
-
sourceBlocksByPath,
|
|
43
|
-
sourceContainerRef,
|
|
44
|
-
onSelectWorkspacePage,
|
|
45
|
-
}: UseInspectorCommentsOptions): InspectorComments {
|
|
46
|
-
const [inspectorCommentText, setInspectorCommentText] = useState("");
|
|
47
|
-
const [inspectorCommentStatus, setInspectorCommentStatus] = useState<InspectorCommentStatus>("idle");
|
|
48
|
-
const [inspectorCommentError, setInspectorCommentError] = useState("");
|
|
49
|
-
const [inlineSavedCommentId, setInlineSavedCommentId] = useState<string | null>(null);
|
|
50
|
-
const [pendingComments, setPendingComments] = useState<PendingComment[]>([]);
|
|
51
|
-
const [commentsStatus, setCommentsStatus] = useState<PendingCommentsStatus>("idle");
|
|
52
|
-
const [commentsError, setCommentsError] = useState("");
|
|
53
|
-
|
|
54
|
-
const inlineSavedComments = useMemo(
|
|
55
|
-
() => pendingComments.flatMap((comment, index) => (
|
|
56
|
-
resolveInlineSavedComment(comment, sourceBlocksByPath)
|
|
57
|
-
.map((inlineComment) => ({ ...inlineComment, markerLabel: String(index + 1) }))
|
|
58
|
-
)),
|
|
59
|
-
[pendingComments, sourceBlocksByPath],
|
|
60
|
-
);
|
|
61
|
-
|
|
62
|
-
const activeInlineSavedComment = getInlineSavedCommentForTarget(
|
|
63
|
-
inlineSavedComments,
|
|
64
|
-
inspector.selectedTarget,
|
|
65
|
-
inlineSavedCommentId,
|
|
66
|
-
);
|
|
67
|
-
|
|
68
|
-
const inspectorCommentDisabled =
|
|
69
|
-
!inspector.selectedBlock || !inspectorCommentText.trim() || inspectorCommentStatus === "submitting";
|
|
70
|
-
// Memoize the status message so its identity is stable while only
|
|
71
|
-
// composer text changes — the toolbar and other consumers that depend
|
|
72
|
-
// on it can then memoize without keystrokes invalidating their cache.
|
|
73
|
-
const inspectorCommentStatusMessage = useMemo(
|
|
74
|
-
() => formatInspectorCommentStatus(inspectorCommentStatus, inspectorCommentError),
|
|
75
|
-
[inspectorCommentStatus, inspectorCommentError],
|
|
76
|
-
);
|
|
77
|
-
|
|
78
|
-
const refreshPendingComments = useCallback(async () => {
|
|
79
|
-
if (!devMode) return;
|
|
80
|
-
setCommentsStatus("loading");
|
|
81
|
-
setCommentsError("");
|
|
82
|
-
try {
|
|
83
|
-
const comments = await fetchInspectorComments();
|
|
84
|
-
setPendingComments(comments);
|
|
85
|
-
setCommentsStatus("ready");
|
|
86
|
-
} catch (error) {
|
|
87
|
-
setCommentsStatus("failed");
|
|
88
|
-
setCommentsError(error instanceof Error ? error.message : String(error));
|
|
89
|
-
}
|
|
90
|
-
}, [devMode]);
|
|
91
|
-
|
|
92
|
-
const clearPendingComment = useCallback(async (id: string) => {
|
|
93
|
-
setCommentsStatus("clearing");
|
|
94
|
-
setCommentsError("");
|
|
95
|
-
try {
|
|
96
|
-
await clearInspectorComment({ id });
|
|
97
|
-
setPendingComments((comments) => comments.filter((comment) => comment.id !== id));
|
|
98
|
-
setInlineSavedCommentId((currentId) => (currentId === id ? null : currentId));
|
|
99
|
-
setCommentsStatus("ready");
|
|
100
|
-
} catch (error) {
|
|
101
|
-
setCommentsStatus("failed");
|
|
102
|
-
setCommentsError(error instanceof Error ? error.message : String(error));
|
|
103
|
-
}
|
|
104
|
-
}, []);
|
|
105
|
-
|
|
106
|
-
const handleSubmitInspectorComment = useCallback(async (event?: FormEvent<HTMLFormElement>) => {
|
|
107
|
-
event?.preventDefault();
|
|
108
|
-
if (inspectorCommentDisabled || !inspector.selectedBlock) return;
|
|
109
|
-
setInspectorCommentStatus("submitting");
|
|
110
|
-
setInspectorCommentError("");
|
|
111
|
-
try {
|
|
112
|
-
const note = inspectorCommentText.trim();
|
|
113
|
-
const placement = inspector.selectedTarget?.placement ?? "block";
|
|
114
|
-
if (activeInlineSavedComment) {
|
|
115
|
-
const result = await updateInspectorComment({
|
|
116
|
-
id: activeInlineSavedComment.id,
|
|
117
|
-
note,
|
|
118
|
-
placement,
|
|
119
|
-
});
|
|
120
|
-
setInlineSavedCommentId(result.comment?.id ?? activeInlineSavedComment.id);
|
|
121
|
-
} else {
|
|
122
|
-
const draft = createInspectorCommentDraft({
|
|
123
|
-
block: inspector.selectedBlock,
|
|
124
|
-
entity: inspector.selectedObjectEntity,
|
|
125
|
-
target: inspector.selectedTarget,
|
|
126
|
-
note,
|
|
127
|
-
placement,
|
|
128
|
-
});
|
|
129
|
-
const result = await submitInspectorComment({ draft });
|
|
130
|
-
if (result.comment?.id) {
|
|
131
|
-
setInlineSavedCommentId(result.comment.id);
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
setInspectorCommentText("");
|
|
135
|
-
setInspectorCommentStatus("saved");
|
|
136
|
-
void refreshPendingComments();
|
|
137
|
-
} catch (error) {
|
|
138
|
-
setInspectorCommentStatus("failed");
|
|
139
|
-
setInspectorCommentError(error instanceof Error ? error.message : String(error));
|
|
140
|
-
}
|
|
141
|
-
}, [
|
|
142
|
-
activeInlineSavedComment,
|
|
143
|
-
inspector.selectedBlock,
|
|
144
|
-
inspector.selectedObjectEntity,
|
|
145
|
-
inspector.selectedTarget,
|
|
146
|
-
inspector.selectedTarget?.placement,
|
|
147
|
-
inspectorCommentDisabled,
|
|
148
|
-
inspectorCommentText,
|
|
149
|
-
refreshPendingComments,
|
|
150
|
-
]);
|
|
151
|
-
|
|
152
|
-
const handleOpenInlineSavedComment = useCallback((comment: InlineSavedComment) => {
|
|
153
|
-
setInlineSavedCommentId(comment.id);
|
|
154
|
-
setInspectorCommentText(comment.note);
|
|
155
|
-
setInspectorCommentStatus("idle");
|
|
156
|
-
setInspectorCommentError("");
|
|
157
|
-
}, []);
|
|
158
|
-
|
|
159
|
-
const handleRemoveInlineSavedComment = useCallback(async (comment: InlineSavedComment) => {
|
|
160
|
-
setInspectorCommentStatus("submitting");
|
|
161
|
-
setInspectorCommentError("");
|
|
162
|
-
try {
|
|
163
|
-
await clearInspectorComment({ id: comment.id });
|
|
164
|
-
setPendingComments((comments) => comments.filter((item) => item.id !== comment.id));
|
|
165
|
-
setInlineSavedCommentId((currentId) => (currentId === comment.id ? null : currentId));
|
|
166
|
-
setInspectorCommentText("");
|
|
167
|
-
setInspectorCommentStatus("idle");
|
|
168
|
-
inspector.selectTarget(null);
|
|
169
|
-
void refreshPendingComments();
|
|
170
|
-
} catch (error) {
|
|
171
|
-
setInspectorCommentStatus("failed");
|
|
172
|
-
setInspectorCommentError(error instanceof Error ? error.message : String(error));
|
|
173
|
-
}
|
|
174
|
-
}, [inspector, refreshPendingComments]);
|
|
175
|
-
|
|
176
|
-
const handleSelectPendingComment = useCallback((comment: PendingComment) => {
|
|
177
|
-
const inlineComment = inlineSavedComments.find((item) => item.id === comment.id)
|
|
178
|
-
?? resolveInlineSavedComment(comment, sourceBlocksByPath)[0];
|
|
179
|
-
if (!inlineComment?.blockId) return;
|
|
180
|
-
|
|
181
|
-
const sourceBlock = sourceBlockMap[inlineComment.blockId];
|
|
182
|
-
if (typeof sourceBlock?.pageIndex === "number") {
|
|
183
|
-
onSelectWorkspacePage(sourceBlock.pageIndex, { behavior: "smooth" });
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
inspector.selectSelection({
|
|
187
|
-
objectId: inlineComment.objectId,
|
|
188
|
-
blockId: inlineComment.blockId,
|
|
189
|
-
placement: inlineComment.placement,
|
|
190
|
-
});
|
|
191
|
-
handleOpenInlineSavedComment(inlineComment);
|
|
192
|
-
|
|
193
|
-
window.requestAnimationFrame(() => {
|
|
194
|
-
const selector = inlineComment.objectId
|
|
195
|
-
? `[data-openpress-object-id="${cssEscape(inlineComment.objectId)}"]`
|
|
196
|
-
: `[data-openpress-block-id="${cssEscape(inlineComment.blockId!)}"]`;
|
|
197
|
-
sourceContainerRef.current?.querySelector<HTMLElement>(selector)?.scrollIntoView({
|
|
198
|
-
behavior: "smooth",
|
|
199
|
-
block: "center",
|
|
200
|
-
});
|
|
201
|
-
});
|
|
202
|
-
}, [
|
|
203
|
-
handleOpenInlineSavedComment,
|
|
204
|
-
inlineSavedComments,
|
|
205
|
-
inspector,
|
|
206
|
-
onSelectWorkspacePage,
|
|
207
|
-
sourceBlockMap,
|
|
208
|
-
sourceBlocksByPath,
|
|
209
|
-
sourceContainerRef,
|
|
210
|
-
]);
|
|
211
|
-
|
|
212
|
-
// Reset composer state when the inspector selection changes.
|
|
213
|
-
useEffect(() => {
|
|
214
|
-
setInspectorCommentStatus("idle");
|
|
215
|
-
setInspectorCommentError("");
|
|
216
|
-
setInspectorCommentText("");
|
|
217
|
-
}, [inspector.selectedBlockId, inspector.selectedTarget?.placement]);
|
|
218
|
-
|
|
219
|
-
// Drop the inline saved id if its comment is no longer reachable.
|
|
220
|
-
useEffect(() => {
|
|
221
|
-
if (inlineSavedCommentId && !activeInlineSavedComment) {
|
|
222
|
-
setInlineSavedCommentId(null);
|
|
223
|
-
}
|
|
224
|
-
}, [activeInlineSavedComment, inlineSavedCommentId]);
|
|
225
|
-
|
|
226
|
-
// Initial + dev-mode refresh of pending comments.
|
|
227
|
-
useEffect(() => {
|
|
228
|
-
if (!devMode) return;
|
|
229
|
-
void refreshPendingComments();
|
|
230
|
-
}, [devMode, refreshPendingComments]);
|
|
231
|
-
|
|
232
|
-
return {
|
|
233
|
-
pendingComments,
|
|
234
|
-
commentsStatus,
|
|
235
|
-
commentsError,
|
|
236
|
-
inspectorCommentText,
|
|
237
|
-
inspectorCommentStatus,
|
|
238
|
-
inspectorCommentStatusMessage,
|
|
239
|
-
inspectorCommentDisabled,
|
|
240
|
-
inlineSavedComments,
|
|
241
|
-
activeInlineSavedComment,
|
|
242
|
-
setInspectorCommentText,
|
|
243
|
-
refreshPendingComments,
|
|
244
|
-
clearPendingComment,
|
|
245
|
-
handleSubmitInspectorComment,
|
|
246
|
-
handleOpenInlineSavedComment,
|
|
247
|
-
handleRemoveInlineSavedComment,
|
|
248
|
-
handleSelectPendingComment,
|
|
249
|
-
};
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
function cssEscape(value: string) {
|
|
253
|
-
return globalThis.CSS?.escape ? globalThis.CSS.escape(value) : value.replace(/["\\]/g, "\\$&");
|
|
254
|
-
}
|
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
import type { ComposerMentionItem } from "./useComposerMentions";
|
|
2
|
-
|
|
3
|
-
export interface MentionSuggestionListProps {
|
|
4
|
-
className: string;
|
|
5
|
-
suggestions: ComposerMentionItem[];
|
|
6
|
-
highlightedIndex: number;
|
|
7
|
-
ariaLabel: string;
|
|
8
|
-
onHighlight: (index: number) => void;
|
|
9
|
-
onSelect: (item: ComposerMentionItem) => void;
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
export function MentionSuggestionList({
|
|
13
|
-
className,
|
|
14
|
-
suggestions,
|
|
15
|
-
highlightedIndex,
|
|
16
|
-
ariaLabel,
|
|
17
|
-
onHighlight,
|
|
18
|
-
onSelect,
|
|
19
|
-
}: MentionSuggestionListProps) {
|
|
20
|
-
if (suggestions.length === 0) return null;
|
|
21
|
-
|
|
22
|
-
return (
|
|
23
|
-
<div className={className} role="listbox" aria-label={ariaLabel}>
|
|
24
|
-
{suggestions.map((item, index) => (
|
|
25
|
-
<button
|
|
26
|
-
type="button"
|
|
27
|
-
role="option"
|
|
28
|
-
aria-selected={index === highlightedIndex}
|
|
29
|
-
data-highlighted={index === highlightedIndex ? "true" : undefined}
|
|
30
|
-
key={`${item.kind}-${item.value}`}
|
|
31
|
-
onMouseDown={(event) => event.preventDefault()}
|
|
32
|
-
onMouseEnter={() => onHighlight(index)}
|
|
33
|
-
onClick={() => onSelect(item)}
|
|
34
|
-
>
|
|
35
|
-
<span>{item.label}</span>
|
|
36
|
-
<small>{item.meta}</small>
|
|
37
|
-
</button>
|
|
38
|
-
))}
|
|
39
|
-
</div>
|
|
40
|
-
);
|
|
41
|
-
}
|
|
@@ -1,185 +0,0 @@
|
|
|
1
|
-
import { useEffect, useMemo, useState, type KeyboardEvent, type RefObject } from "react";
|
|
2
|
-
import { clampNumber } from "../../shared";
|
|
3
|
-
|
|
4
|
-
export type ComposerMentionItem = {
|
|
5
|
-
trigger: "@" | "/";
|
|
6
|
-
value: string;
|
|
7
|
-
label: string;
|
|
8
|
-
meta: string;
|
|
9
|
-
kind: "media" | "component" | "skill" | "chapter" | "section" | "prefix";
|
|
10
|
-
};
|
|
11
|
-
|
|
12
|
-
export type ActiveComposerMention = {
|
|
13
|
-
trigger: "@" | "/";
|
|
14
|
-
query: string;
|
|
15
|
-
start: number;
|
|
16
|
-
end: number;
|
|
17
|
-
};
|
|
18
|
-
|
|
19
|
-
export function useComposerMentions({
|
|
20
|
-
text,
|
|
21
|
-
items,
|
|
22
|
-
textareaRef,
|
|
23
|
-
onTextChange,
|
|
24
|
-
enabled = true,
|
|
25
|
-
maxSuggestions = 7,
|
|
26
|
-
}: {
|
|
27
|
-
text: string;
|
|
28
|
-
items: ComposerMentionItem[];
|
|
29
|
-
textareaRef: RefObject<HTMLTextAreaElement | null>;
|
|
30
|
-
onTextChange: (value: string) => void;
|
|
31
|
-
enabled?: boolean;
|
|
32
|
-
maxSuggestions?: number;
|
|
33
|
-
}) {
|
|
34
|
-
const [composerCursor, setComposerCursor] = useState(0);
|
|
35
|
-
const [highlightedMentionIndex, setHighlightedMentionIndex] = useState(0);
|
|
36
|
-
const [dismissedMentionKey, setDismissedMentionKey] = useState<string | null>(null);
|
|
37
|
-
const activeMention = enabled ? resolveComposerMention(text, composerCursor) : null;
|
|
38
|
-
const mentionKey = activeMention ? `${activeMention.trigger}:${activeMention.start}:${activeMention.query}` : null;
|
|
39
|
-
const mentionSuggestions = useMemo(() => {
|
|
40
|
-
if (!activeMention) return [];
|
|
41
|
-
if (mentionKey && dismissedMentionKey === mentionKey) return [];
|
|
42
|
-
return createMentionSuggestions(activeMention, items, maxSuggestions);
|
|
43
|
-
}, [activeMention, dismissedMentionKey, items, maxSuggestions, mentionKey]);
|
|
44
|
-
|
|
45
|
-
useEffect(() => {
|
|
46
|
-
setHighlightedMentionIndex(0);
|
|
47
|
-
}, [mentionKey, mentionSuggestions.length]);
|
|
48
|
-
|
|
49
|
-
const syncCursor = () => {
|
|
50
|
-
const textarea = textareaRef.current;
|
|
51
|
-
if (textarea) setComposerCursor(textarea.selectionStart ?? text.length);
|
|
52
|
-
};
|
|
53
|
-
|
|
54
|
-
const insertMention = (item: ComposerMentionItem) => {
|
|
55
|
-
if (!activeMention) return;
|
|
56
|
-
const suffix = item.kind === "prefix" ? "" : " ";
|
|
57
|
-
const nextText = `${text.slice(0, activeMention.start)}${item.value}${suffix}${text.slice(activeMention.end)}`;
|
|
58
|
-
const nextCursor = activeMention.start + item.value.length + suffix.length;
|
|
59
|
-
setDismissedMentionKey(null);
|
|
60
|
-
onTextChange(nextText);
|
|
61
|
-
if (typeof window === "undefined") return;
|
|
62
|
-
window.requestAnimationFrame(() => {
|
|
63
|
-
textareaRef.current?.focus();
|
|
64
|
-
textareaRef.current?.setSelectionRange(nextCursor, nextCursor);
|
|
65
|
-
setComposerCursor(nextCursor);
|
|
66
|
-
});
|
|
67
|
-
};
|
|
68
|
-
|
|
69
|
-
const handleMentionKeyDown = (event: KeyboardEvent<HTMLTextAreaElement>) => {
|
|
70
|
-
if (!activeMention || mentionSuggestions.length === 0) return false;
|
|
71
|
-
if (event.key === "ArrowDown") {
|
|
72
|
-
event.preventDefault();
|
|
73
|
-
setHighlightedMentionIndex((index) => (index + 1) % mentionSuggestions.length);
|
|
74
|
-
return true;
|
|
75
|
-
}
|
|
76
|
-
if (event.key === "ArrowUp") {
|
|
77
|
-
event.preventDefault();
|
|
78
|
-
setHighlightedMentionIndex((index) => (index - 1 + mentionSuggestions.length) % mentionSuggestions.length);
|
|
79
|
-
return true;
|
|
80
|
-
}
|
|
81
|
-
if (event.key === "Enter" || event.key === "Tab") {
|
|
82
|
-
event.preventDefault();
|
|
83
|
-
insertMention(mentionSuggestions[highlightedMentionIndex] ?? mentionSuggestions[0]);
|
|
84
|
-
return true;
|
|
85
|
-
}
|
|
86
|
-
if (event.key === "Escape") {
|
|
87
|
-
event.preventDefault();
|
|
88
|
-
setDismissedMentionKey(mentionKey);
|
|
89
|
-
return true;
|
|
90
|
-
}
|
|
91
|
-
return false;
|
|
92
|
-
};
|
|
93
|
-
|
|
94
|
-
return {
|
|
95
|
-
activeMention,
|
|
96
|
-
handleMentionKeyDown,
|
|
97
|
-
highlightedMentionIndex,
|
|
98
|
-
setHighlightedMentionIndex,
|
|
99
|
-
mentionSuggestions,
|
|
100
|
-
setComposerCursor,
|
|
101
|
-
syncCursor,
|
|
102
|
-
insertMention,
|
|
103
|
-
};
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
export function appendComposerToken(text: string, token: string) {
|
|
107
|
-
const trimmedToken = token.trim();
|
|
108
|
-
if (!trimmedToken) return text;
|
|
109
|
-
if (!text.trim()) return `${trimmedToken} `;
|
|
110
|
-
return `${text.replace(/\s*$/, " ")}${trimmedToken} `;
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
export function createMentionSuggestions(
|
|
114
|
-
activeMention: ActiveComposerMention,
|
|
115
|
-
items: ComposerMentionItem[],
|
|
116
|
-
maxSuggestions: number,
|
|
117
|
-
) {
|
|
118
|
-
if (activeMention.trigger === "@") {
|
|
119
|
-
const prefixItems = createMentionPrefixItems(items);
|
|
120
|
-
const normalizedQuery = activeMention.query.trim().toLowerCase();
|
|
121
|
-
if (!normalizedQuery) return prefixItems.slice(0, maxSuggestions);
|
|
122
|
-
if (!normalizedQuery.includes("/")) {
|
|
123
|
-
return uniqueMentionItems([
|
|
124
|
-
...prefixItems.filter((item) => mentionMatches(item, normalizedQuery)),
|
|
125
|
-
...items.filter((item) => item.trigger === "@" && mentionMatches(item, normalizedQuery)),
|
|
126
|
-
]).slice(0, maxSuggestions);
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
return items
|
|
131
|
-
.filter((item) => item.trigger === activeMention.trigger && mentionMatches(item, activeMention.query))
|
|
132
|
-
.slice(0, maxSuggestions);
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
function createMentionPrefixItems(items: ComposerMentionItem[]): ComposerMentionItem[] {
|
|
136
|
-
const availableKinds = new Set(items.filter((item) => item.trigger === "@").map((item) => item.kind));
|
|
137
|
-
return MENTION_PREFIX_DEFINITIONS
|
|
138
|
-
.filter((item) => availableKinds.has(item.kind))
|
|
139
|
-
.map((item) => ({
|
|
140
|
-
trigger: "@" as const,
|
|
141
|
-
value: `@${item.kind}/`,
|
|
142
|
-
label: item.kind,
|
|
143
|
-
meta: item.meta,
|
|
144
|
-
kind: "prefix" as const,
|
|
145
|
-
}));
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
function uniqueMentionItems(items: ComposerMentionItem[]) {
|
|
149
|
-
const seen = new Set<string>();
|
|
150
|
-
return items.filter((item) => {
|
|
151
|
-
const key = `${item.kind}:${item.value}`;
|
|
152
|
-
if (seen.has(key)) return false;
|
|
153
|
-
seen.add(key);
|
|
154
|
-
return true;
|
|
155
|
-
});
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
function resolveComposerMention(text: string, cursor: number): ActiveComposerMention | null {
|
|
159
|
-
const safeCursor = clampNumber(cursor, 0, text.length);
|
|
160
|
-
const beforeCursor = text.slice(0, safeCursor);
|
|
161
|
-
const match = beforeCursor.match(/(^|\s)([@/])([^\s@]*)$/);
|
|
162
|
-
if (!match) return null;
|
|
163
|
-
const start = beforeCursor.length - match[0].length + match[1].length;
|
|
164
|
-
return {
|
|
165
|
-
trigger: match[2] as "@" | "/",
|
|
166
|
-
query: match[3] ?? "",
|
|
167
|
-
start,
|
|
168
|
-
end: safeCursor,
|
|
169
|
-
};
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
function mentionMatches(item: ComposerMentionItem, query: string) {
|
|
173
|
-
const normalizedQuery = query.trim().toLowerCase();
|
|
174
|
-
if (!normalizedQuery) return true;
|
|
175
|
-
return item.value.toLowerCase().includes(normalizedQuery)
|
|
176
|
-
|| item.label.toLowerCase().includes(normalizedQuery)
|
|
177
|
-
|| item.meta.toLowerCase().includes(normalizedQuery);
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
const MENTION_PREFIX_DEFINITIONS: Array<{ kind: "media" | "chapter" | "section" | "component"; meta: string }> = [
|
|
181
|
-
{ kind: "media", meta: "prefix · images" },
|
|
182
|
-
{ kind: "chapter", meta: "prefix · chapters" },
|
|
183
|
-
{ kind: "section", meta: "prefix · sections" },
|
|
184
|
-
{ kind: "component", meta: "prefix · components" },
|
|
185
|
-
];
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export { Panel } from "../../shared/Panel";
|
|
@@ -1,80 +0,0 @@
|
|
|
1
|
-
import { memo } from "react";
|
|
2
|
-
import { Trash2 } from "lucide-react";
|
|
3
|
-
import type { PendingComment } from "../inspector";
|
|
4
|
-
import {
|
|
5
|
-
formatCommentTimestamp,
|
|
6
|
-
formatCommentsCount,
|
|
7
|
-
} from "../workbenchFormatters";
|
|
8
|
-
import type { PendingCommentsStatus } from "../workbenchTypes";
|
|
9
|
-
import { Panel } from "./Panel";
|
|
10
|
-
|
|
11
|
-
function PendingCommentsPanelImpl({
|
|
12
|
-
comments,
|
|
13
|
-
status,
|
|
14
|
-
error,
|
|
15
|
-
onClear,
|
|
16
|
-
onSelect,
|
|
17
|
-
}: {
|
|
18
|
-
comments: PendingComment[];
|
|
19
|
-
status: PendingCommentsStatus;
|
|
20
|
-
error: string;
|
|
21
|
-
onClear: (id: string) => Promise<void>;
|
|
22
|
-
onSelect?: (comment: PendingComment) => void;
|
|
23
|
-
}) {
|
|
24
|
-
const busy = status === "loading" || status === "clearing";
|
|
25
|
-
|
|
26
|
-
return (
|
|
27
|
-
<Panel
|
|
28
|
-
className="openpress-comments-panel openpress-comments-panel--embedded openpress-panel--compact"
|
|
29
|
-
data-openpress-comments-panel
|
|
30
|
-
aria-label="待處理註解"
|
|
31
|
-
>
|
|
32
|
-
<Panel.Header>
|
|
33
|
-
<div className="openpress-panel-heading-stack">
|
|
34
|
-
<Panel.Kicker>Comments</Panel.Kicker>
|
|
35
|
-
<Panel.Title>待處理註解</Panel.Title>
|
|
36
|
-
<Panel.Description>{formatCommentsCount(comments.length, status)}</Panel.Description>
|
|
37
|
-
</div>
|
|
38
|
-
</Panel.Header>
|
|
39
|
-
|
|
40
|
-
<Panel.Body>
|
|
41
|
-
{error ? <Panel.Error>{error}</Panel.Error> : null}
|
|
42
|
-
|
|
43
|
-
{comments.length === 0 && status !== "loading" ? (
|
|
44
|
-
<Panel.Empty role="status">目前沒有註解</Panel.Empty>
|
|
45
|
-
) : (
|
|
46
|
-
<ol className="openpress-comments-list" aria-label="待處理註解列表">
|
|
47
|
-
{comments.map((comment) => (
|
|
48
|
-
<li className="openpress-comment-entry" data-openpress-comment-id={comment.id} key={comment.id}>
|
|
49
|
-
<button
|
|
50
|
-
type="button"
|
|
51
|
-
className="openpress-comment-entry__jump"
|
|
52
|
-
onClick={() => onSelect?.(comment)}
|
|
53
|
-
aria-label={`跳到註解 ${comment.id}`}
|
|
54
|
-
>
|
|
55
|
-
<p className="openpress-comment-entry__note" title={comment.note}>{comment.note}</p>
|
|
56
|
-
<p className="openpress-comment-entry__meta">
|
|
57
|
-
<code>{comment.path}:{comment.line}</code>
|
|
58
|
-
{comment.timestamp ? <span>{formatCommentTimestamp(comment.timestamp)}</span> : null}
|
|
59
|
-
</p>
|
|
60
|
-
</button>
|
|
61
|
-
<button
|
|
62
|
-
type="button"
|
|
63
|
-
className="openpress-comment-entry__clear"
|
|
64
|
-
onClick={() => void onClear(comment.id)}
|
|
65
|
-
disabled={busy}
|
|
66
|
-
aria-label={`清除註解 ${comment.id}`}
|
|
67
|
-
>
|
|
68
|
-
<Trash2 aria-hidden="true" />
|
|
69
|
-
</button>
|
|
70
|
-
</li>
|
|
71
|
-
))}
|
|
72
|
-
</ol>
|
|
73
|
-
)}
|
|
74
|
-
</Panel.Body>
|
|
75
|
-
</Panel>
|
|
76
|
-
);
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
export const PendingCommentsPanel = memo(PendingCommentsPanelImpl);
|
|
80
|
-
PendingCommentsPanel.displayName = "PendingCommentsPanel";
|
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
import { Fragment, type ReactNode } from "react";
|
|
2
|
-
|
|
3
|
-
// A WorkbenchPanel is a self-contained renderable slot in the workbench
|
|
4
|
-
// control surface. Each panel owns its own props by capturing them in render;
|
|
5
|
-
// the host (HtmlWorkbench) supplies the list and decides ordering.
|
|
6
|
-
export interface WorkbenchPanel {
|
|
7
|
-
id: string;
|
|
8
|
-
render: () => ReactNode;
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
export interface WorkbenchControlPanelProps {
|
|
12
|
-
panels: WorkbenchPanel[];
|
|
13
|
-
className?: string;
|
|
14
|
-
ariaLabel?: string;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
export function WorkbenchControlPanel({
|
|
18
|
-
panels,
|
|
19
|
-
className = "openpress-control-panel",
|
|
20
|
-
ariaLabel = "控制面板",
|
|
21
|
-
}: WorkbenchControlPanelProps) {
|
|
22
|
-
return (
|
|
23
|
-
<div className={className} data-openpress-control-panel aria-label={ariaLabel}>
|
|
24
|
-
{panels.map((panel) => (
|
|
25
|
-
<Fragment key={panel.id}>{panel.render()}</Fragment>
|
|
26
|
-
))}
|
|
27
|
-
</div>
|
|
28
|
-
);
|
|
29
|
-
}
|