@open-press/core 0.7.1 → 0.8.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/engine/commands/dev.mjs +2 -2
- package/engine/output/chrome-pdf.mjs +18 -3
- package/engine/output/static-server.mjs +39 -0
- package/engine/react/comment-endpoint.mjs +13 -39
- package/engine/react/comment-marker.mjs +30 -6
- package/engine/react/document-entry.mjs +11 -0
- package/engine/react/document-export.mjs +30 -5
- package/engine/react/http-json.mjs +24 -0
- package/engine/react/mdx-compile.mjs +96 -3
- package/engine/react/measurement-css.mjs +93 -1
- package/engine/react/object-entities.mjs +119 -0
- package/engine/react/pipeline/allocate.mjs +10 -7
- package/engine/react/pipeline/frame-measurement.mjs +2 -0
- package/engine/react/project-asset-endpoint.mjs +6 -24
- package/engine/react/source-edit-endpoint.d.mts +10 -0
- package/engine/react/source-edit-endpoint.mjs +75 -0
- package/engine/react/sources/mdx-resolver.mjs +12 -14
- package/engine/react/style-discovery.mjs +1 -4
- package/engine/runtime/file-walk.mjs +22 -0
- package/engine/runtime/inspection.mjs +1 -20
- package/engine/runtime/path-utils.mjs +20 -0
- package/engine/runtime/source-text-tools.d.mts +102 -0
- package/engine/runtime/source-text-tools.mjs +551 -16
- package/engine/runtime/source-workspace.mjs +4 -31
- package/package.json +1 -1
- package/src/openpress/{App.tsx → app/OpenPressApp.tsx} +25 -12
- package/src/openpress/{renderer.tsx → app/OpenPressRuntime.tsx} +10 -7
- package/src/openpress/app/index.ts +2 -0
- package/src/openpress/core/Frame.tsx +9 -11
- package/src/openpress/core/FrameContext.tsx +8 -3
- package/src/openpress/core/MdxArea.tsx +11 -12
- package/src/openpress/core/cn.ts +4 -0
- package/src/openpress/core/index.tsx +2 -1
- package/src/openpress/core/primitives.tsx +29 -8
- package/src/openpress/core/types.ts +8 -0
- package/src/openpress/{anchorMap.ts → document-model/anchorMapModel.ts} +1 -1
- package/src/openpress/{indexes.ts → document-model/documentIndexes.ts} +1 -1
- package/src/openpress/{types.ts → document-model/documentTypes.ts} +42 -0
- package/src/openpress/document-model/index.ts +6 -0
- package/src/openpress/document-model/objectEntityModel.ts +51 -0
- package/src/openpress/{projectIdentity.ts → document-model/projectIdentityModel.ts} +1 -1
- package/src/openpress/{reactDocumentMetadata.ts → document-model/reactDocumentMetadataModel.ts} +1 -1
- package/src/openpress/manuscript/index.tsx +49 -7
- package/src/openpress/{publicPage.tsx → reader/PublicReaderPage.tsx} +31 -51
- package/src/openpress/{workbenchPanels.tsx → reader/ReaderNavigationPanel.tsx} +6 -5
- package/src/openpress/reader/index.ts +10 -0
- package/src/openpress/reader/pageViewportScaleModel.ts +73 -0
- package/src/openpress/reader/readerTypes.ts +4 -0
- package/src/openpress/reader/usePageViewportScale.ts +119 -0
- package/src/openpress/reader/usePanelState.ts +56 -0
- package/src/openpress/reader/useReaderHashSync.ts +61 -0
- package/src/openpress/reader/useReaderKeyboardNav.ts +48 -0
- package/src/openpress/reader/useReaderRuntime.ts +146 -0
- package/src/openpress/reader/useReaderScrollAnchor.ts +64 -0
- package/src/openpress/shared/Panel.tsx +77 -0
- package/src/openpress/shared/index.ts +4 -0
- package/src/openpress/shared/numberUtils.ts +3 -0
- package/src/openpress/{runtimeMode.ts → shared/runtimeMode.ts} +0 -11
- package/src/openpress/workbench/Workbench.tsx +407 -0
- package/src/openpress/workbench/actions/DeploymentControl.tsx +157 -0
- package/src/openpress/workbench/actions/PageZoomControl.tsx +182 -0
- package/src/openpress/workbench/actions/SearchControl.tsx +345 -0
- package/src/openpress/workbench/actions/deploymentStatusModel.ts +112 -0
- package/src/openpress/workbench/actions/index.ts +5 -0
- package/src/openpress/workbench/actions/useDeploymentWorkbench.ts +136 -0
- package/src/openpress/workbench/dialog/WorkbenchDialog.tsx +72 -0
- package/src/openpress/workbench/dialog/index.ts +1 -0
- package/src/openpress/workbench/document/components/DocumentPanel.tsx +127 -0
- package/src/openpress/workbench/document/components/InlineSourceEditorLayer.tsx +207 -0
- package/src/openpress/workbench/document/components/ReaderStage.tsx +9 -0
- package/src/openpress/workbench/document/hooks/useDocumentWorkbenchModel.ts +34 -0
- package/src/openpress/workbench/document/hooks/useInlineDocumentEditor.ts +525 -0
- package/src/openpress/workbench/document/index.ts +10 -0
- package/src/openpress/workbench/index.ts +2 -0
- package/src/openpress/workbench/inspector/InlineInspectorLayer.tsx +459 -0
- package/src/openpress/workbench/inspector/index.ts +5 -0
- package/src/openpress/workbench/inspector/inlineCommentModel.ts +125 -0
- package/src/openpress/workbench/inspector/inspectorGeometryModel.ts +160 -0
- package/src/openpress/workbench/inspector/inspectorModel.ts +408 -0
- package/src/openpress/workbench/inspector/useInspectorComments.ts +248 -0
- package/src/openpress/workbench/mentions/MentionSuggestionList.tsx +41 -0
- package/src/openpress/workbench/mentions/index.ts +2 -0
- package/src/openpress/{composerMentions.ts → workbench/mentions/useComposerMentions.ts} +1 -4
- package/src/openpress/workbench/panels/Panel.tsx +1 -0
- package/src/openpress/workbench/panels/PendingCommentsPanel.tsx +76 -0
- package/src/openpress/workbench/panels/WorkbenchControlPanel.tsx +29 -0
- package/src/openpress/workbench/panels/index.ts +3 -0
- package/src/openpress/workbench/project/ProjectEntryPanel.tsx +523 -0
- package/src/openpress/workbench/project/ProjectPreviewDialog.tsx +35 -0
- package/src/openpress/workbench/project/index.ts +2 -0
- package/src/openpress/workbench/project/projectPreviewTypes.ts +11 -0
- package/src/openpress/workbench/shell/WorkbenchShell.tsx +167 -0
- package/src/openpress/workbench/shell/index.ts +1 -0
- package/src/openpress/workbench/workbenchFormatters.ts +120 -0
- package/src/openpress/workbench/workbenchTypes.ts +35 -0
- package/src/styles/openpress/print-route.css +0 -2
- package/src/styles/openpress/{project-workspace.css → project-preview-panel.css} +13 -407
- package/src/styles/openpress/public-viewer.css +25 -320
- package/src/styles/openpress/reader-runtime.css +243 -55
- package/src/styles/openpress/responsive.css +145 -270
- package/src/styles/openpress/workbench-panels.css +214 -178
- package/src/styles/openpress/workbench.css +986 -451
- package/src/styles/openpress.css +1 -1
- package/vite.config.ts +50 -0
- package/src/openpress/inspector.ts +0 -282
- package/src/openpress/projectWorkspace.tsx +0 -919
- package/src/openpress/readerRuntime.ts +0 -230
- package/src/openpress/workbench.tsx +0 -1265
- package/src/openpress/workbenchTypes.ts +0 -4
- /package/src/openpress/{readerPageRegistry.ts → reader/readerPageRegistry.ts} +0 -0
- /package/src/openpress/{pageRoute.ts → reader/readerPageRoute.ts} +0 -0
- /package/src/openpress/{readerScroll.ts → reader/readerScroll.ts} +0 -0
- /package/src/openpress/{readerState.ts → reader/readerStateModel.ts} +0 -0
- /package/src/openpress/{frameScheduler.ts → shared/frameScheduler.ts} +0 -0
- /package/src/openpress/{projectSources.ts → workbench/project/projectSourceModel.ts} +0 -0
package/engine/commands/dev.mjs
CHANGED
|
@@ -16,7 +16,7 @@ export async function run({ root, options }) {
|
|
|
16
16
|
if (!options.noBuild) {
|
|
17
17
|
console.log(`Command: ${formatNodeScriptCommand(root, CLI_ENTRY)} export .`);
|
|
18
18
|
}
|
|
19
|
-
console.log(`Command: npx vite --config vite.config.ts --host ${host} --port ${port}`);
|
|
19
|
+
console.log(`Command: npx vite --force --config vite.config.ts --host ${host} --port ${port}`);
|
|
20
20
|
return 0;
|
|
21
21
|
}
|
|
22
22
|
if (!options.noBuild) {
|
|
@@ -27,7 +27,7 @@ export async function run({ root, options }) {
|
|
|
27
27
|
await printDoctorNoticeIfStale(root);
|
|
28
28
|
|
|
29
29
|
console.log(`OpenPress dev: ${url}`);
|
|
30
|
-
return runCommand("npx", ["vite", "--config", "vite.config.ts", "--host", host, "--port", port], root);
|
|
30
|
+
return runCommand("npx", ["vite", "--force", "--config", "vite.config.ts", "--host", host, "--port", port], root);
|
|
31
31
|
}
|
|
32
32
|
|
|
33
33
|
async function printDoctorNoticeIfStale(root) {
|
|
@@ -91,6 +91,13 @@ const DEFAULT_PRINT_OPTIONS = {
|
|
|
91
91
|
marginLeft: 0,
|
|
92
92
|
};
|
|
93
93
|
|
|
94
|
+
export const DEFAULT_PRINT_VIEWPORT = Object.freeze({
|
|
95
|
+
width: 1200,
|
|
96
|
+
height: 1698,
|
|
97
|
+
deviceScaleFactor: 1,
|
|
98
|
+
mobile: false,
|
|
99
|
+
});
|
|
100
|
+
|
|
94
101
|
export async function printUrlToPdf({
|
|
95
102
|
root,
|
|
96
103
|
url,
|
|
@@ -98,6 +105,7 @@ export async function printUrlToPdf({
|
|
|
98
105
|
chrome,
|
|
99
106
|
waitForReady = waitForPrintReady,
|
|
100
107
|
printOptions = {},
|
|
108
|
+
viewport = DEFAULT_PRINT_VIEWPORT,
|
|
101
109
|
debuggingPortBase = 9600,
|
|
102
110
|
debuggingPortRange = 300,
|
|
103
111
|
profilePrefix = "chrome-pdf",
|
|
@@ -126,9 +134,7 @@ export async function printUrlToPdf({
|
|
|
126
134
|
const tab = await waitForChromeTab(debuggingPort);
|
|
127
135
|
const client = await connectChromeDevTools(tab.webSocketDebuggerUrl);
|
|
128
136
|
try {
|
|
129
|
-
await client
|
|
130
|
-
await client.send("Runtime.enable");
|
|
131
|
-
await client.send("Emulation.setEmulatedMedia", { media: "print" });
|
|
137
|
+
await preparePdfPage(client, { viewport });
|
|
132
138
|
await client.send("Page.navigate", { url });
|
|
133
139
|
const readyResult = await waitForReady(client);
|
|
134
140
|
const result = await client.send("Page.printToPDF", {
|
|
@@ -146,6 +152,15 @@ export async function printUrlToPdf({
|
|
|
146
152
|
}
|
|
147
153
|
}
|
|
148
154
|
|
|
155
|
+
export async function preparePdfPage(client, { viewport = DEFAULT_PRINT_VIEWPORT } = {}) {
|
|
156
|
+
await client.send("Page.enable");
|
|
157
|
+
await client.send("Runtime.enable");
|
|
158
|
+
if (viewport) {
|
|
159
|
+
await client.send("Emulation.setDeviceMetricsOverride", viewport);
|
|
160
|
+
}
|
|
161
|
+
await client.send("Emulation.setEmulatedMedia", { media: "print" });
|
|
162
|
+
}
|
|
163
|
+
|
|
149
164
|
export async function evaluateUrlWithChrome({
|
|
150
165
|
root,
|
|
151
166
|
url,
|
|
@@ -3,7 +3,9 @@ import http from "node:http";
|
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import { spawn } from "node:child_process";
|
|
5
5
|
import { loadConfig, publicPdfHref } from "../runtime/config.mjs";
|
|
6
|
+
import { searchSourceText } from "../runtime/source-text-tools.mjs";
|
|
6
7
|
import { handleProjectAssetRequest } from "../react/project-asset-endpoint.mjs";
|
|
8
|
+
import { handleSourceEditRequest } from "../react/source-edit-endpoint.mjs";
|
|
7
9
|
|
|
8
10
|
const [rootArg = "dist", ...rest] = process.argv.slice(2);
|
|
9
11
|
const host = valueAfter(rest, "--host") ?? "127.0.0.1";
|
|
@@ -32,6 +34,14 @@ const server = http.createServer(async (req, res) => {
|
|
|
32
34
|
await handleStatusRequest(req, res);
|
|
33
35
|
return;
|
|
34
36
|
}
|
|
37
|
+
if (url.pathname === "/__openpress/search") {
|
|
38
|
+
await handleSearchRequest(req, res, url);
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
if (url.pathname === "/__openpress/source-edit") {
|
|
42
|
+
await handleSourceEditRequest(req, res, { root: workspace });
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
35
45
|
if (url.pathname === "/__openpress/local-pdf-export") {
|
|
36
46
|
await handleLocalPdfExportRequest(req, res);
|
|
37
47
|
return;
|
|
@@ -103,6 +113,35 @@ async function handleStatusRequest(req, res) {
|
|
|
103
113
|
});
|
|
104
114
|
}
|
|
105
115
|
|
|
116
|
+
async function handleSearchRequest(req, res, url) {
|
|
117
|
+
if (req.method !== "GET") {
|
|
118
|
+
writeJson(res, 405, { ok: false, message: "Search endpoint requires GET." });
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
const query = (url.searchParams.get("q") ?? "").trim();
|
|
123
|
+
if (!query) {
|
|
124
|
+
writeJson(res, 400, { ok: false, message: "Search query is required." });
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
try {
|
|
129
|
+
const report = await searchSourceText({
|
|
130
|
+
config,
|
|
131
|
+
query,
|
|
132
|
+
scope: searchScopeFrom(url),
|
|
133
|
+
caseSensitive: url.searchParams.get("caseSensitive") === "true",
|
|
134
|
+
});
|
|
135
|
+
writeJson(res, 200, { ok: true, ...report });
|
|
136
|
+
} catch (error) {
|
|
137
|
+
writeJson(res, 500, { ok: false, message: error instanceof Error ? error.message : String(error) });
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function searchScopeFrom(url) {
|
|
142
|
+
return url.searchParams.get("scope") === "all" ? "all" : "content";
|
|
143
|
+
}
|
|
144
|
+
|
|
106
145
|
function valueAfter(args, flag) {
|
|
107
146
|
const index = args.indexOf(flag);
|
|
108
147
|
return index >= 0 ? args[index + 1] : undefined;
|
|
@@ -4,8 +4,7 @@ import {
|
|
|
4
4
|
listCommentMarkers,
|
|
5
5
|
updateCommentMarker,
|
|
6
6
|
} from "./comment-marker.mjs";
|
|
7
|
-
|
|
8
|
-
const MAX_COMMENT_BODY_BYTES = 64 * 1024;
|
|
7
|
+
import { readJsonBody, writeJson } from "./http-json.mjs";
|
|
9
8
|
|
|
10
9
|
export async function handleCommentRequest(req, res, {
|
|
11
10
|
root = ".",
|
|
@@ -16,17 +15,14 @@ export async function handleCommentRequest(req, res, {
|
|
|
16
15
|
try {
|
|
17
16
|
writeJson(res, 200, { ok: true, comments: await listCommentMarkers({ root }) });
|
|
18
17
|
} catch (error) {
|
|
19
|
-
|
|
20
|
-
ok: false,
|
|
21
|
-
message: error instanceof Error ? error.message : String(error),
|
|
22
|
-
});
|
|
18
|
+
writeErrorJson(res, error);
|
|
23
19
|
}
|
|
24
20
|
return;
|
|
25
21
|
}
|
|
26
22
|
|
|
27
23
|
if (req.method === "DELETE") {
|
|
28
24
|
try {
|
|
29
|
-
const body = await readJsonBody(req);
|
|
25
|
+
const body = await readJsonBody(req, { bodyLabel: "OpenPress comment request" });
|
|
30
26
|
const result = await clearCommentMarkers({
|
|
31
27
|
root,
|
|
32
28
|
id: body?.id,
|
|
@@ -34,17 +30,14 @@ export async function handleCommentRequest(req, res, {
|
|
|
34
30
|
});
|
|
35
31
|
writeJson(res, 200, { ok: true, ...result });
|
|
36
32
|
} catch (error) {
|
|
37
|
-
|
|
38
|
-
ok: false,
|
|
39
|
-
message: error instanceof Error ? error.message : String(error),
|
|
40
|
-
});
|
|
33
|
+
writeErrorJson(res, error);
|
|
41
34
|
}
|
|
42
35
|
return;
|
|
43
36
|
}
|
|
44
37
|
|
|
45
38
|
if (req.method === "PATCH") {
|
|
46
39
|
try {
|
|
47
|
-
const body = await readJsonBody(req);
|
|
40
|
+
const body = await readJsonBody(req, { bodyLabel: "OpenPress comment request" });
|
|
48
41
|
const result = await updateCommentMarker({
|
|
49
42
|
root,
|
|
50
43
|
id: body?.id,
|
|
@@ -64,10 +57,7 @@ export async function handleCommentRequest(req, res, {
|
|
|
64
57
|
},
|
|
65
58
|
});
|
|
66
59
|
} catch (error) {
|
|
67
|
-
|
|
68
|
-
ok: false,
|
|
69
|
-
message: error instanceof Error ? error.message : String(error),
|
|
70
|
-
});
|
|
60
|
+
writeErrorJson(res, error);
|
|
71
61
|
}
|
|
72
62
|
return;
|
|
73
63
|
}
|
|
@@ -78,7 +68,7 @@ export async function handleCommentRequest(req, res, {
|
|
|
78
68
|
}
|
|
79
69
|
|
|
80
70
|
try {
|
|
81
|
-
const body = await readJsonBody(req);
|
|
71
|
+
const body = await readJsonBody(req, { bodyLabel: "OpenPress comment request" });
|
|
82
72
|
const target = body?.target ?? {};
|
|
83
73
|
const result = await insertCommentMarker({
|
|
84
74
|
root,
|
|
@@ -100,29 +90,13 @@ export async function handleCommentRequest(req, res, {
|
|
|
100
90
|
},
|
|
101
91
|
});
|
|
102
92
|
} catch (error) {
|
|
103
|
-
|
|
104
|
-
ok: false,
|
|
105
|
-
message: error instanceof Error ? error.message : String(error),
|
|
106
|
-
});
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
async function readJsonBody(req) {
|
|
111
|
-
let body = "";
|
|
112
|
-
for await (const chunk of req) {
|
|
113
|
-
body += String(chunk);
|
|
114
|
-
if (Buffer.byteLength(body, "utf8") > MAX_COMMENT_BODY_BYTES) {
|
|
115
|
-
throw new Error("OpenPress comment request body is too large.");
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
try {
|
|
119
|
-
return JSON.parse(body || "{}");
|
|
120
|
-
} catch {
|
|
121
|
-
throw new Error("OpenPress comment request body must be valid JSON.");
|
|
93
|
+
writeErrorJson(res, error);
|
|
122
94
|
}
|
|
123
95
|
}
|
|
124
96
|
|
|
125
|
-
function
|
|
126
|
-
res
|
|
127
|
-
|
|
97
|
+
function writeErrorJson(res, error) {
|
|
98
|
+
writeJson(res, 400, {
|
|
99
|
+
ok: false,
|
|
100
|
+
message: error instanceof Error ? error.message : String(error),
|
|
101
|
+
});
|
|
128
102
|
}
|
|
@@ -14,8 +14,8 @@ const EDITABLE_COMMENT_SOURCE_PATTERNS = [
|
|
|
14
14
|
/^document\/.+\.mdx$/,
|
|
15
15
|
/^document\/.+\.tsx$/,
|
|
16
16
|
];
|
|
17
|
-
const COMMENT_MARKER_RE =
|
|
18
|
-
const COMMENT_LINE_RE = /^\s
|
|
17
|
+
const COMMENT_MARKER_RE = /(?:\{\/\*|\/\*)\s*@openpress-comment\b(?<attrs>[^*]*)\*\/\}?/g;
|
|
18
|
+
const COMMENT_LINE_RE = /^\s*(?:\{\/\*|\/\*)\s*@openpress-comment\b[^*]*\*\/\}?\s*$/;
|
|
19
19
|
|
|
20
20
|
export async function insertCommentMarker({
|
|
21
21
|
root = ".",
|
|
@@ -35,9 +35,15 @@ export async function insertCommentMarker({
|
|
|
35
35
|
}
|
|
36
36
|
|
|
37
37
|
const noteText = normalizedNote(note);
|
|
38
|
-
const marker = createCommentMarker({ id, timestamp, note: noteText, hint });
|
|
39
38
|
const line = normalizeLineNumber(source?.line);
|
|
40
39
|
const text = await fs.readFile(absolutePath, "utf8");
|
|
40
|
+
const marker = createCommentMarker({
|
|
41
|
+
id,
|
|
42
|
+
timestamp,
|
|
43
|
+
note: noteText,
|
|
44
|
+
hint,
|
|
45
|
+
syntax: commentMarkerSyntaxForInsert(text, line, relativePath),
|
|
46
|
+
});
|
|
41
47
|
const nextText = insertLineBefore(text, line, marker);
|
|
42
48
|
await fs.writeFile(absolutePath, nextText, "utf8");
|
|
43
49
|
|
|
@@ -51,9 +57,10 @@ export async function insertCommentMarker({
|
|
|
51
57
|
};
|
|
52
58
|
}
|
|
53
59
|
|
|
54
|
-
export function createCommentMarker({ id = createCommentId(), timestamp = new Date().toISOString(), note, hint } = {}) {
|
|
60
|
+
export function createCommentMarker({ id = createCommentId(), timestamp = new Date().toISOString(), note, hint, syntax = "jsx" } = {}) {
|
|
55
61
|
const payload = { note: normalizedNote(note), ...(typeof hint === "string" && hint.trim() ? { hint: hint.trim() } : {}) };
|
|
56
|
-
|
|
62
|
+
const body = `@openpress-comment id="${escapeMarkerAttribute(id)}" ts="${escapeMarkerAttribute(timestamp)}" text="${encodeCommentPayload(payload)}"`;
|
|
63
|
+
return syntax === "block" ? `/* ${body} */` : `{/* ${body} */}`;
|
|
57
64
|
}
|
|
58
65
|
|
|
59
66
|
export function decodeCommentMarkerText(marker) {
|
|
@@ -285,7 +292,7 @@ function replaceCommentMarkerLine(text, { id, note, hint, timestamp }) {
|
|
|
285
292
|
if (!COMMENT_LINE_RE.test(line)) continue;
|
|
286
293
|
const attrs = parseMarkerAttributes(line);
|
|
287
294
|
if (attrs.id !== id) continue;
|
|
288
|
-
const marker = createCommentMarker({ id, timestamp, note, hint });
|
|
295
|
+
const marker = createCommentMarker({ id, timestamp, note, hint, syntax: commentMarkerSyntaxForLine(line) });
|
|
289
296
|
lines[index] = `${line.match(/^\s*/)?.[0] ?? ""}${marker}`;
|
|
290
297
|
return {
|
|
291
298
|
text: `${lines.join(newline)}${hasTrailingNewline ? newline : ""}`,
|
|
@@ -311,6 +318,23 @@ function parseMarkerAttributes(value) {
|
|
|
311
318
|
return attrs;
|
|
312
319
|
}
|
|
313
320
|
|
|
321
|
+
function commentMarkerSyntaxForInsert(text, line, relativePath) {
|
|
322
|
+
if (!String(relativePath).endsWith(".tsx")) return "jsx";
|
|
323
|
+
const lines = String(text ?? "").split(/\r?\n/);
|
|
324
|
+
const index = Math.min(Math.max(line - 1, 0), lines.length);
|
|
325
|
+
const priorContent = lines.slice(0, index).some((entry) => entry.trim().length > 0);
|
|
326
|
+
if (!priorContent) return "block";
|
|
327
|
+
const targetLine = lines[index] ?? "";
|
|
328
|
+
if (/^\s*(import\b|export\b|function\b|const\b|let\b|var\b|type\b|interface\b|return\b)/.test(targetLine)) {
|
|
329
|
+
return "block";
|
|
330
|
+
}
|
|
331
|
+
return "jsx";
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
function commentMarkerSyntaxForLine(line) {
|
|
335
|
+
return /^\s*\/\*/.test(line) ? "block" : "jsx";
|
|
336
|
+
}
|
|
337
|
+
|
|
314
338
|
function lineStartOffsets(text) {
|
|
315
339
|
const starts = [0];
|
|
316
340
|
for (let index = 0; index < text.length; index += 1) {
|
|
@@ -68,6 +68,7 @@ export async function createReactSsrServer(workspaceRoot = ".") {
|
|
|
68
68
|
return createViteServer({
|
|
69
69
|
configFile: false,
|
|
70
70
|
root: FRAMEWORK_ROOT,
|
|
71
|
+
cacheDir: path.join(resolvedWorkspaceRoot, ".openpress", "vite-ssr"),
|
|
71
72
|
appType: "custom",
|
|
72
73
|
logLevel: "silent",
|
|
73
74
|
plugins: [reactRuntimePlugin(), react()],
|
|
@@ -82,6 +83,16 @@ export async function createReactSsrServer(workspaceRoot = ".") {
|
|
|
82
83
|
{ find: "@/components", replacement: path.join(resolvedWorkspaceRoot, "document", "components") },
|
|
83
84
|
],
|
|
84
85
|
},
|
|
86
|
+
optimizeDeps: {
|
|
87
|
+
include: [
|
|
88
|
+
"@mdx-js/react",
|
|
89
|
+
"react",
|
|
90
|
+
"react-dom",
|
|
91
|
+
"react-dom/server",
|
|
92
|
+
"react/jsx-dev-runtime",
|
|
93
|
+
"react/jsx-runtime",
|
|
94
|
+
],
|
|
95
|
+
},
|
|
85
96
|
server: {
|
|
86
97
|
middlewareMode: true,
|
|
87
98
|
fs: {
|
|
@@ -12,6 +12,7 @@ import { createCaptionNumberingState, numberCaptionsInHtml } from "./caption-num
|
|
|
12
12
|
import { buildSectionScopedCss } from "./section-css.mjs";
|
|
13
13
|
import { CORE_ENTRY, createReactSsrServer, loadReactDocumentEntry } from "./document-entry.mjs";
|
|
14
14
|
import { buildReactMeasurementCss } from "./measurement-css.mjs";
|
|
15
|
+
import { buildObjectEntities } from "./object-entities.mjs";
|
|
15
16
|
import { allocateChains } from "./pipeline/allocate.mjs";
|
|
16
17
|
import { measureFrames } from "./pipeline/frame-measurement.mjs";
|
|
17
18
|
import { renderFinalPress } from "./pipeline/final-render.mjs";
|
|
@@ -50,7 +51,14 @@ export async function exportReactDocument(root = ".", { syncAssets = true } = {}
|
|
|
50
51
|
|
|
51
52
|
// Discover workspace for component scope and chapter-scoped style files.
|
|
52
53
|
const workspace = await discoverSectionStyles(workspaceRoot, entry.config);
|
|
53
|
-
const
|
|
54
|
+
const coreAuthorComponents = {};
|
|
55
|
+
for (const name of ["MediaFigure", "ImageFigure"]) {
|
|
56
|
+
if (typeof coreModule[name] === "function") coreAuthorComponents[name] = coreModule[name];
|
|
57
|
+
}
|
|
58
|
+
const globalComponents = {
|
|
59
|
+
...coreAuthorComponents,
|
|
60
|
+
...(await loadComponentModules(server, workspace.globalComponents ?? [])),
|
|
61
|
+
};
|
|
54
62
|
|
|
55
63
|
// Resolve sources.
|
|
56
64
|
const documentRoot = entry.config.paths.documentRoot;
|
|
@@ -153,9 +161,6 @@ export async function exportReactDocument(root = ".", { syncAssets = true } = {}
|
|
|
153
161
|
const blockMap = {};
|
|
154
162
|
const captionState = createCaptionNumberingState();
|
|
155
163
|
const blocks = final.frames.map((frame, index) => {
|
|
156
|
-
for (const id of frame.blockIds) {
|
|
157
|
-
blockMap[id] = { id, pageIndex: index, pageNumber: index + 1 };
|
|
158
|
-
}
|
|
159
164
|
const source = {
|
|
160
165
|
file: "index.tsx",
|
|
161
166
|
path: "document/index.tsx",
|
|
@@ -164,6 +169,9 @@ export async function exportReactDocument(root = ".", { syncAssets = true } = {}
|
|
|
164
169
|
sectionIndex: index + 1,
|
|
165
170
|
};
|
|
166
171
|
const html = numberCaptionsInHtml(frame.html, entry.config.captionNumbering, captionState);
|
|
172
|
+
for (const id of collectFrameBlockIds(frame.blockIds, html)) {
|
|
173
|
+
blockMap[id] = { id, pageIndex: index, pageNumber: index + 1, frameKey: frame.frameKey };
|
|
174
|
+
}
|
|
167
175
|
const block = pageToBlock(index, html, source, entry.config, {
|
|
168
176
|
idPrefix: "openpress-page",
|
|
169
177
|
anchorPrefix: "page",
|
|
@@ -196,12 +204,18 @@ export async function exportReactDocument(root = ".", { syncAssets = true } = {}
|
|
|
196
204
|
}
|
|
197
205
|
}
|
|
198
206
|
|
|
207
|
+
const objectEntities = buildObjectEntities({
|
|
208
|
+
frames: final.frames.map((frame, index) => ({ ...frame, pageIndex: index })),
|
|
209
|
+
blocks,
|
|
210
|
+
blockMap,
|
|
211
|
+
});
|
|
212
|
+
|
|
199
213
|
const readerDocument = {
|
|
200
214
|
meta: {
|
|
201
215
|
title: trimmedString(entry.config.title) ?? "Untitled Document",
|
|
202
216
|
subtitle: trimmedString(entry.config.subtitle) ?? "",
|
|
203
217
|
organization: trimmedString(entry.config.organization) ?? "",
|
|
204
|
-
workspaceLabel: trimmedString(entry.config.workspaceLabel) ??
|
|
218
|
+
workspaceLabel: trimmedString(entry.config.workspaceLabel) ?? "",
|
|
205
219
|
version: "openpress-press-tree-v1",
|
|
206
220
|
},
|
|
207
221
|
source: {
|
|
@@ -211,6 +225,7 @@ export async function exportReactDocument(root = ".", { syncAssets = true } = {}
|
|
|
211
225
|
editMode: "source-mdx",
|
|
212
226
|
styles,
|
|
213
227
|
blockMap,
|
|
228
|
+
objectEntities,
|
|
214
229
|
frames: final.frames.map((frame, index) => ({
|
|
215
230
|
frameKey: frame.frameKey,
|
|
216
231
|
role: frame.role ?? null,
|
|
@@ -296,6 +311,16 @@ function buildSourceBlockIndex(sources) {
|
|
|
296
311
|
return index;
|
|
297
312
|
}
|
|
298
313
|
|
|
314
|
+
function collectFrameBlockIds(allocatedIds, html) {
|
|
315
|
+
const ids = new Set(allocatedIds ?? []);
|
|
316
|
+
const pattern = /\sdata-openpress-block-id="([^"]+)"/g;
|
|
317
|
+
let match;
|
|
318
|
+
while ((match = pattern.exec(String(html ?? "")))) {
|
|
319
|
+
if (match[1]) ids.add(match[1]);
|
|
320
|
+
}
|
|
321
|
+
return ids;
|
|
322
|
+
}
|
|
323
|
+
|
|
299
324
|
function buildTocContext({ sources, frames, allocation }) {
|
|
300
325
|
const toc = {};
|
|
301
326
|
for (const source of Object.values(sources)) {
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
const DEFAULT_MAX_BODY_BYTES = 64 * 1024;
|
|
2
|
+
|
|
3
|
+
export async function readJsonBody(req, {
|
|
4
|
+
maxBytes = DEFAULT_MAX_BODY_BYTES,
|
|
5
|
+
bodyLabel = "Request",
|
|
6
|
+
} = {}) {
|
|
7
|
+
let body = "";
|
|
8
|
+
for await (const chunk of req) {
|
|
9
|
+
body += String(chunk);
|
|
10
|
+
if (Buffer.byteLength(body, "utf8") > maxBytes) {
|
|
11
|
+
throw new Error(`${bodyLabel} body is too large.`);
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
try {
|
|
15
|
+
return JSON.parse(body || "{}");
|
|
16
|
+
} catch {
|
|
17
|
+
throw new Error(`${bodyLabel} body must be valid JSON.`);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function writeJson(res, status, body) {
|
|
22
|
+
res.writeHead(status, { "Content-Type": "application/json; charset=utf-8" });
|
|
23
|
+
res.end(`${JSON.stringify(body, null, 2)}\n`);
|
|
24
|
+
}
|
|
@@ -112,6 +112,7 @@ export function rehypeBlockIds(options = {}) {
|
|
|
112
112
|
if (includeBlockIds && !includeBlockIds.has(id)) return false;
|
|
113
113
|
|
|
114
114
|
setDataAttribute(node, "data-openpress-block-id", id);
|
|
115
|
+
setDataAttribute(node, "data-openpress-object-id", createBlockObjectEntityId(id));
|
|
115
116
|
const extraAttributes = blockAttributes.get(id);
|
|
116
117
|
if (extraAttributes) {
|
|
117
118
|
for (const [name, value] of Object.entries(extraAttributes)) {
|
|
@@ -142,9 +143,19 @@ function applyTableRowBlocks({
|
|
|
142
143
|
includeBlockIds,
|
|
143
144
|
}) {
|
|
144
145
|
const rows = tableBodyRows(node);
|
|
146
|
+
const header = tableHeaderRow(node);
|
|
147
|
+
const caption = tableCaption(node);
|
|
148
|
+
const captionRecord = caption ? { id: `${id}-caption`, node: caption } : null;
|
|
149
|
+
const headerRecord = header ? { id: `${id}-h0`, node: header } : null;
|
|
150
|
+
const selectedCaption = captionRecord && (!includeBlockIds || includeBlockIds.has(captionRecord.id));
|
|
151
|
+
const selectedHeader = headerRecord && (!includeBlockIds || includeBlockIds.has(headerRecord.id));
|
|
152
|
+
const firstSelectedRowIndex = selectedFirstTableRowIndex(rows, includeBlockIds, id);
|
|
153
|
+
const renderCaption = selectedCaption || (captionRecord && includeBlockIds && firstSelectedRowIndex === 0);
|
|
154
|
+
const renderHeader = Boolean(headerRecord && (!includeBlockIds || firstSelectedRowIndex === 0 || selectedHeader));
|
|
145
155
|
if (rows.length === 0) {
|
|
146
156
|
if (includeBlockIds && !includeBlockIds.has(id)) return false;
|
|
147
157
|
setDataAttribute(node, "data-openpress-block-id", id);
|
|
158
|
+
setDataAttribute(node, "data-openpress-object-id", createBlockObjectEntityId(id));
|
|
148
159
|
blocks.push({
|
|
149
160
|
id,
|
|
150
161
|
kind: "element",
|
|
@@ -165,15 +176,56 @@ function applyTableRowBlocks({
|
|
|
165
176
|
const selected = includeBlockIds
|
|
166
177
|
? rowRecords.filter((row) => includeBlockIds.has(row.id))
|
|
167
178
|
: rowRecords;
|
|
168
|
-
if (selected.length === 0) return false;
|
|
179
|
+
if (selected.length === 0 && !selectedCaption && !selectedHeader) return false;
|
|
169
180
|
|
|
170
181
|
setDataAttribute(node, "data-openpress-table-id", id);
|
|
182
|
+
if (headerRecord && renderHeader) {
|
|
183
|
+
setDataAttribute(headerRecord.node, "data-openpress-block-id", headerRecord.id);
|
|
184
|
+
setDataAttribute(headerRecord.node, "data-openpress-object-id", createBlockObjectEntityId(headerRecord.id));
|
|
185
|
+
setDataAttribute(headerRecord.node, "data-openpress-block-layout", "attached");
|
|
186
|
+
}
|
|
187
|
+
if (captionRecord) {
|
|
188
|
+
if (renderCaption) {
|
|
189
|
+
setDataAttribute(captionRecord.node, "data-openpress-block-id", captionRecord.id);
|
|
190
|
+
setDataAttribute(captionRecord.node, "data-openpress-object-id", createBlockObjectEntityId(captionRecord.id));
|
|
191
|
+
if (selectedCaption) {
|
|
192
|
+
blocks.push({
|
|
193
|
+
id: captionRecord.id,
|
|
194
|
+
kind: "element",
|
|
195
|
+
name: "caption",
|
|
196
|
+
text: textContent(captionRecord.node).trim() || undefined,
|
|
197
|
+
filePath,
|
|
198
|
+
chapterSlug,
|
|
199
|
+
tableId: id,
|
|
200
|
+
layout: "attached",
|
|
201
|
+
source: sourcePosition(captionRecord.node.position ?? node.position),
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
} else {
|
|
205
|
+
removeTableCaption(node);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
if (headerRecord && selectedHeader) {
|
|
209
|
+
blocks.push({
|
|
210
|
+
id: headerRecord.id,
|
|
211
|
+
kind: "table-row",
|
|
212
|
+
name: "table-header-row",
|
|
213
|
+
text: textContent(headerRecord.node).trim() || undefined,
|
|
214
|
+
filePath,
|
|
215
|
+
chapterSlug,
|
|
216
|
+
tableId: id,
|
|
217
|
+
rowIndex: -1,
|
|
218
|
+
layout: "attached",
|
|
219
|
+
source: sourcePosition(headerRecord.node.position ?? node.position),
|
|
220
|
+
});
|
|
221
|
+
}
|
|
171
222
|
const selectedNodes = new Set(selected.map((row) => row.node));
|
|
172
223
|
pruneUnselectedTableRows(node, new Set(rowRecords.map((row) => row.node)), selectedNodes);
|
|
173
|
-
if (
|
|
224
|
+
if (!renderHeader) stripTableHeader(node);
|
|
174
225
|
|
|
175
226
|
for (const row of selected) {
|
|
176
227
|
setDataAttribute(row.node, "data-openpress-block-id", row.id);
|
|
228
|
+
setDataAttribute(row.node, "data-openpress-object-id", createBlockObjectEntityId(row.id));
|
|
177
229
|
blocks.push({
|
|
178
230
|
id: row.id,
|
|
179
231
|
kind: "table-row",
|
|
@@ -229,6 +281,7 @@ function normalizeTableCaptions(node) {
|
|
|
229
281
|
type: "element",
|
|
230
282
|
tagName: "caption",
|
|
231
283
|
properties: {},
|
|
284
|
+
position: child.position,
|
|
232
285
|
children: [{ type: "text", value: captionText }],
|
|
233
286
|
});
|
|
234
287
|
}
|
|
@@ -272,8 +325,10 @@ function wrapMdxComponents(components) {
|
|
|
272
325
|
if (typeof Component !== "function") continue;
|
|
273
326
|
wrapped[name] = function ComponentBlock(props = {}) {
|
|
274
327
|
const blockId = props["data-openpress-block-id"];
|
|
328
|
+
const objectId = props["data-openpress-object-id"] || (blockId ? createBlockObjectEntityId(blockId) : undefined);
|
|
275
329
|
const rest = { ...props };
|
|
276
330
|
delete rest["data-openpress-block-id"];
|
|
331
|
+
delete rest["data-openpress-object-id"];
|
|
277
332
|
|
|
278
333
|
if (!blockId) return React.createElement(Component, rest);
|
|
279
334
|
|
|
@@ -281,6 +336,7 @@ function wrapMdxComponents(components) {
|
|
|
281
336
|
"div",
|
|
282
337
|
{
|
|
283
338
|
"data-openpress-block-id": blockId,
|
|
339
|
+
"data-openpress-object-id": objectId,
|
|
284
340
|
"data-openpress-component-block": name,
|
|
285
341
|
},
|
|
286
342
|
React.createElement(Component, rest),
|
|
@@ -336,6 +392,7 @@ function applyListItemBlocks({
|
|
|
336
392
|
if (items.length === 0) {
|
|
337
393
|
if (includeBlockIds && !includeBlockIds.has(id)) return false;
|
|
338
394
|
setDataAttribute(node, "data-openpress-block-id", id);
|
|
395
|
+
setDataAttribute(node, "data-openpress-object-id", createBlockObjectEntityId(id));
|
|
339
396
|
blocks.push({
|
|
340
397
|
id,
|
|
341
398
|
kind: "element",
|
|
@@ -375,6 +432,7 @@ function applyListItemBlocks({
|
|
|
375
432
|
|
|
376
433
|
for (const item of selected) {
|
|
377
434
|
setDataAttribute(item.node, "data-openpress-block-id", item.id);
|
|
435
|
+
setDataAttribute(item.node, "data-openpress-object-id", createBlockObjectEntityId(item.id));
|
|
378
436
|
blocks.push({
|
|
379
437
|
id: item.id,
|
|
380
438
|
kind: "list-item",
|
|
@@ -419,6 +477,28 @@ function tableBodyRows(table) {
|
|
|
419
477
|
return (table.children ?? []).filter((child) => child?.type === "element" && child.tagName === "tr");
|
|
420
478
|
}
|
|
421
479
|
|
|
480
|
+
function tableHeaderRow(table) {
|
|
481
|
+
if (table?.type !== "element" || table.tagName !== "table") return null;
|
|
482
|
+
for (const child of table.children ?? []) {
|
|
483
|
+
if (child?.type !== "element" || child.tagName !== "thead") continue;
|
|
484
|
+
return (child.children ?? []).find((row) => row?.type === "element" && row.tagName === "tr") ?? null;
|
|
485
|
+
}
|
|
486
|
+
return null;
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
function selectedFirstTableRowIndex(rows, includeBlockIds, tableId) {
|
|
490
|
+
if (!includeBlockIds) return 0;
|
|
491
|
+
for (let index = 0; index < rows.length; index += 1) {
|
|
492
|
+
if (includeBlockIds.has(`${tableId}-r${index}`)) return index;
|
|
493
|
+
}
|
|
494
|
+
return -1;
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
function tableCaption(table) {
|
|
498
|
+
if (table?.type !== "element" || table.tagName !== "table") return null;
|
|
499
|
+
return (table.children ?? []).find((child) => child?.type === "element" && child.tagName === "caption") ?? null;
|
|
500
|
+
}
|
|
501
|
+
|
|
422
502
|
function pruneUnselectedTableRows(node, rowNodes, selectedNodes) {
|
|
423
503
|
if (!Array.isArray(node?.children)) return;
|
|
424
504
|
node.children = node.children.filter((child) => {
|
|
@@ -432,10 +512,15 @@ function stripTableHeader(table) {
|
|
|
432
512
|
if (!Array.isArray(table?.children)) return;
|
|
433
513
|
table.children = table.children.filter((child) => {
|
|
434
514
|
if (child?.type !== "element") return true;
|
|
435
|
-
return child.tagName !== "
|
|
515
|
+
return child.tagName !== "thead";
|
|
436
516
|
});
|
|
437
517
|
}
|
|
438
518
|
|
|
519
|
+
function removeTableCaption(table) {
|
|
520
|
+
if (!Array.isArray(table?.children)) return;
|
|
521
|
+
table.children = table.children.filter((child) => child?.type !== "element" || child.tagName !== "caption");
|
|
522
|
+
}
|
|
523
|
+
|
|
439
524
|
function headingText(node) {
|
|
440
525
|
if (!/^h[1-6]$/.test(String(node?.tagName ?? ""))) return undefined;
|
|
441
526
|
return textContent(node).trim() || undefined;
|
|
@@ -470,6 +555,14 @@ function setDataAttribute(node, name, value) {
|
|
|
470
555
|
node.properties[name] = value;
|
|
471
556
|
}
|
|
472
557
|
|
|
558
|
+
function createObjectEntityId(kind, ...parts) {
|
|
559
|
+
return [kind, ...parts.map((part) => encodeURIComponent(String(part)))].join(":");
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
function createBlockObjectEntityId(blockId) {
|
|
563
|
+
return createObjectEntityId("mdx-block", blockId);
|
|
564
|
+
}
|
|
565
|
+
|
|
473
566
|
function visit(node, visitor) {
|
|
474
567
|
visitor(node);
|
|
475
568
|
if (!Array.isArray(node?.children)) return;
|