@open-press/core 0.6.0 → 0.7.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.
- package/README.md +9 -5
- package/engine/cli.mjs +2 -5
- package/engine/commands/_shared.mjs +4 -4
- package/engine/commands/deploy.mjs +1 -1
- package/engine/commands/inspect.mjs +3 -3
- package/engine/commands/replace.mjs +1 -1
- package/engine/commands/search.mjs +1 -1
- package/engine/commands/upgrade.mjs +47 -5
- package/engine/commands/validate.mjs +2 -2
- package/engine/document-export.mjs +1 -1
- package/engine/{chrome-pdf.mjs → output/chrome-pdf.mjs} +1 -2
- package/engine/{deploy-sync.mjs → output/deploy-sync.mjs} +2 -2
- package/engine/{fonts.mjs → output/fonts.mjs} +1 -1
- package/engine/{public-assets.mjs → output/public-assets.mjs} +2 -2
- package/engine/{static-server.mjs → output/static-server.mjs} +2 -2
- package/engine/react/caption-numbering.mjs +73 -0
- package/engine/react/comment-marker.mjs +54 -10
- package/engine/react/document-entry.mjs +124 -64
- package/engine/react/document-export.mjs +266 -310
- package/engine/react/mdx-compile.mjs +214 -3
- package/engine/react/measurement-css.mjs +3 -3
- package/engine/react/pagination/allocator.mjs +122 -0
- package/engine/react/pagination/regions.mjs +81 -0
- package/engine/react/pagination.mjs +9 -121
- package/engine/react/pipeline/allocate.mjs +248 -0
- package/engine/react/pipeline/final-render.mjs +94 -0
- package/engine/react/pipeline/frame-measurement.mjs +300 -0
- package/engine/react/pipeline/press-tree.mjs +135 -0
- package/engine/react/project-asset-endpoint.mjs +2 -2
- package/engine/react/{chapter-css.mjs → section-css.mjs} +12 -9
- package/engine/react/sources/heading-numbering.mjs +132 -0
- package/engine/react/sources/mdx-resolver.mjs +441 -0
- package/engine/react/{workspace-discovery.mjs → style-discovery.mjs} +29 -40
- package/engine/{config.mjs → runtime/config.mjs} +15 -0
- package/engine/{file-utils.mjs → runtime/file-utils.mjs} +1 -1
- package/engine/{inspection.mjs → runtime/inspection.mjs} +3 -4
- package/engine/{source-text-tools.mjs → runtime/source-text-tools.mjs} +24 -7
- package/engine/runtime/source-workspace.mjs +186 -0
- package/engine/{validation.mjs → runtime/validation.mjs} +19 -17
- package/package.json +5 -2
- package/src/openpress/anchorMap.ts +27 -0
- package/src/openpress/core/Frame.tsx +80 -0
- package/src/openpress/core/FrameContext.tsx +19 -0
- package/src/openpress/core/MdxArea.tsx +35 -0
- package/src/openpress/core/Press.tsx +34 -0
- package/src/openpress/core/index.tsx +34 -15
- package/src/openpress/core/primitives.tsx +23 -0
- package/src/openpress/core/types.ts +131 -19
- package/src/openpress/core/useSource.ts +28 -0
- package/src/openpress/manuscript/index.tsx +196 -0
- package/src/openpress/mdx/index.ts +88 -0
- package/src/openpress/numbering/index.ts +294 -0
- package/src/openpress/publicPage.tsx +4 -186
- package/src/openpress/reactDocumentMetadata.ts +2 -16
- package/src/openpress/types.ts +0 -16
- package/src/openpress/workbench.tsx +2 -36
- package/src/styles/openpress/responsive.css +0 -14
- package/tsconfig.json +4 -1
- package/vite.config.ts +10 -3
- package/engine/commands/migrate-to-react.mjs +0 -27
- package/engine/page-renderer.mjs +0 -217
- package/engine/react/migrate-to-react.mjs +0 -355
- package/engine/source-workspace.mjs +0 -76
- package/src/openpress/core/basePages.tsx +0 -87
- package/src/openpress/pagination.ts +0 -845
- /package/engine/{chrome-pdf.d.mts → output/chrome-pdf.d.mts} +0 -0
- /package/engine/{katex-assets.mjs → output/katex-assets.mjs} +0 -0
- /package/engine/{page-block.mjs → output/page-block.mjs} +0 -0
- /package/engine/{pdf-media.mjs → output/pdf-media.mjs} +0 -0
- /package/engine/{config.d.mts → runtime/config.d.mts} +0 -0
- /package/engine/{issue-report.mjs → runtime/issue-report.mjs} +0 -0
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# @open-press/core
|
|
2
2
|
|
|
3
|
-
Framework runtime, CLI engine, and
|
|
3
|
+
Framework runtime, CLI engine, and Press Tree primitives for [open-press](https://github.com/quan0715/open-press) — an AI-first fixed-layout document workspace.
|
|
4
4
|
|
|
5
5
|
Most users do **not** install this package directly. Instead, scaffold a workspace with the CLI:
|
|
6
6
|
|
|
@@ -20,15 +20,19 @@ npm install @open-press/core
|
|
|
20
20
|
|
|
21
21
|
```tsx
|
|
22
22
|
import {
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
BaseBackCoverPage,
|
|
23
|
+
Press,
|
|
24
|
+
Frame,
|
|
25
|
+
MdxArea,
|
|
27
26
|
BaseFigure,
|
|
28
27
|
BaseCallout,
|
|
29
28
|
} from "@open-press/core";
|
|
29
|
+
|
|
30
|
+
import { mdxSource } from "@open-press/core/mdx";
|
|
31
|
+
import { Sections, Toc } from "@open-press/core/manuscript";
|
|
30
32
|
```
|
|
31
33
|
|
|
34
|
+
`document/index.tsx` default-exports a `<Press>` tree. `Frame` marks fixed-layout pages, `MdxArea` receives measured MDX blocks, and `mdxSource()` declares which MDX files participate in the render pipeline.
|
|
35
|
+
|
|
32
36
|
The CLI bin (`open-press`) supports dev / build / preview / validate / pdf / deploy / export commands. It requires a workspace with `openpress.config.mjs` and the surrounding framework files (which the scaffolder installs).
|
|
33
37
|
|
|
34
38
|
## License
|
package/engine/cli.mjs
CHANGED
|
@@ -6,7 +6,6 @@ import * as doctorCmd from "./commands/doctor.mjs";
|
|
|
6
6
|
import * as exportCmd from "./commands/export.mjs";
|
|
7
7
|
import * as initCmd from "./commands/init.mjs";
|
|
8
8
|
import * as inspectCmd from "./commands/inspect.mjs";
|
|
9
|
-
import * as migrateToReactCmd from "./commands/migrate-to-react.mjs";
|
|
10
9
|
import * as pdfCmd from "./commands/pdf.mjs";
|
|
11
10
|
import * as previewCmd from "./commands/preview.mjs";
|
|
12
11
|
import * as replaceCmd from "./commands/replace.mjs";
|
|
@@ -16,13 +15,12 @@ import * as typecheckCmd from "./commands/typecheck.mjs";
|
|
|
16
15
|
import * as upgradeCmd from "./commands/upgrade.mjs";
|
|
17
16
|
import * as validateCmd from "./commands/validate.mjs";
|
|
18
17
|
import { parseOptions } from "./commands/_shared.mjs";
|
|
19
|
-
import { loadConfig } from "./config.mjs";
|
|
18
|
+
import { loadConfig } from "./runtime/config.mjs";
|
|
20
19
|
import { listStylePackSkills } from "./init.mjs";
|
|
21
|
-
import { discoverWorkspace } from "./validation.mjs";
|
|
20
|
+
import { discoverWorkspace } from "./runtime/validation.mjs";
|
|
22
21
|
|
|
23
22
|
const COMMANDS = {
|
|
24
23
|
init: initCmd,
|
|
25
|
-
"migrate-to-react": migrateToReactCmd,
|
|
26
24
|
validate: validateCmd,
|
|
27
25
|
inspect: inspectCmd,
|
|
28
26
|
search: searchCmd,
|
|
@@ -79,7 +77,6 @@ async function printHelp() {
|
|
|
79
77
|
|
|
80
78
|
Commands:
|
|
81
79
|
init <target> [--skill <name>] [--force]
|
|
82
|
-
migrate-to-react [path] [--dry-run] [--force] [--json]
|
|
83
80
|
validate
|
|
84
81
|
inspect [--json] [--no-build] [--dry-run]
|
|
85
82
|
search [path] <query> [--json] [--scope content|all]
|
|
@@ -2,14 +2,14 @@ import { spawn, spawnSync } from "node:child_process";
|
|
|
2
2
|
import fs from "node:fs/promises";
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import { fileURLToPath } from "node:url";
|
|
5
|
-
import { printUrlToPdf, stopChildProcess, waitForPrintReady } from "../chrome-pdf.mjs";
|
|
6
|
-
import { loadConfig, publicPdfHref } from "../config.mjs";
|
|
5
|
+
import { printUrlToPdf, stopChildProcess, waitForPrintReady } from "../output/chrome-pdf.mjs";
|
|
6
|
+
import { loadConfig, publicPdfHref } from "../runtime/config.mjs";
|
|
7
7
|
import { exportDocument } from "../document-export.mjs";
|
|
8
|
-
import { optimizePdfMediaForStaticRoot } from "../pdf-media.mjs";
|
|
8
|
+
import { optimizePdfMediaForStaticRoot } from "../output/pdf-media.mjs";
|
|
9
9
|
|
|
10
10
|
export const ENGINE_DIR = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
|
|
11
11
|
export const CLI_ENTRY = path.join(ENGINE_DIR, "cli.mjs");
|
|
12
|
-
export const STATIC_SERVER = path.join(ENGINE_DIR, "static-server.mjs");
|
|
12
|
+
export const STATIC_SERVER = path.join(ENGINE_DIR, "output", "static-server.mjs");
|
|
13
13
|
|
|
14
14
|
export function parseOptions(argv) {
|
|
15
15
|
const options = {};
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
|
-
import { deploySync } from "../deploy-sync.mjs";
|
|
2
|
+
import { deploySync } from "../output/deploy-sync.mjs";
|
|
3
3
|
import { CLI_ENTRY, buildReactPdf, formatNodeScriptCommand, runCommand, writePdfStageDeployConfig } from "./_shared.mjs";
|
|
4
4
|
|
|
5
5
|
export async function run({ root, config, options, recurse }) {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { inspectWorkspace } from "../inspection.mjs";
|
|
2
|
-
import { exitCodeForIssueReport } from "../issue-report.mjs";
|
|
1
|
+
import { inspectWorkspace } from "../runtime/inspection.mjs";
|
|
2
|
+
import { exitCodeForIssueReport } from "../runtime/issue-report.mjs";
|
|
3
3
|
|
|
4
4
|
export async function run({ root, config, options, recurse }) {
|
|
5
5
|
const host = options.host ?? "127.0.0.1";
|
|
@@ -10,7 +10,7 @@ export async function run({ root, config, options, recurse }) {
|
|
|
10
10
|
if (!options.noBuild) {
|
|
11
11
|
console.log("Command: node engine/cli.mjs render . --renderer react");
|
|
12
12
|
}
|
|
13
|
-
console.log(`Command: node engine/static-server.mjs ${config.outputDir} --host ${host} --port ${port} --workspace .`);
|
|
13
|
+
console.log(`Command: node engine/output/static-server.mjs ${config.outputDir} --host ${host} --port ${port} --workspace .`);
|
|
14
14
|
console.log(`Chrome inspection URL: ${url}`);
|
|
15
15
|
return 0;
|
|
16
16
|
}
|
|
@@ -1,9 +1,15 @@
|
|
|
1
1
|
import { existsSync } from "node:fs";
|
|
2
|
-
import { readFile } from "node:fs/promises";
|
|
2
|
+
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import { diagnose } from "./doctor.mjs";
|
|
5
5
|
import { runCommand } from "./_shared.mjs";
|
|
6
6
|
|
|
7
|
+
// Migration notes live in the framework repo, not in scaffolded workspaces.
|
|
8
|
+
// `npx open-press upgrade` fetches the notes for each pending version and
|
|
9
|
+
// caches them under `.openpress/migrations/` so agents can read locally.
|
|
10
|
+
const MIGRATION_REMOTE_BASE = "https://raw.githubusercontent.com/quan0715/open-press/main/docs/migrations";
|
|
11
|
+
const MIGRATION_CACHE_DIR = path.join(".openpress", "migrations");
|
|
12
|
+
|
|
7
13
|
export async function run({ root, options }) {
|
|
8
14
|
const dryRun = Boolean(options?.dryRun);
|
|
9
15
|
const skipSkills = Boolean(options?.noSkills);
|
|
@@ -85,7 +91,11 @@ export async function run({ root, options }) {
|
|
|
85
91
|
process.stdout.write(" (no migration docs in this version range)\n\n");
|
|
86
92
|
} else {
|
|
87
93
|
for (const m of migrationContents) {
|
|
88
|
-
|
|
94
|
+
if (m.path) {
|
|
95
|
+
process.stdout.write(` ─ ${m.path}${m.fetched ? " (fetched from github)" : ""}\n`);
|
|
96
|
+
} else {
|
|
97
|
+
process.stdout.write(` ─ ${m.version}.md (not found locally or on github — check the repo manually)\n`);
|
|
98
|
+
}
|
|
89
99
|
}
|
|
90
100
|
process.stdout.write(
|
|
91
101
|
"\nAgent: open each file, identify document-level changes, grep document/ for affected patterns, propose edits before applying.\n",
|
|
@@ -107,11 +117,43 @@ async function hasCoreDep(root) {
|
|
|
107
117
|
|
|
108
118
|
async function loadMigrations(root, versions) {
|
|
109
119
|
const results = [];
|
|
120
|
+
const cacheDir = path.join(root, MIGRATION_CACHE_DIR);
|
|
121
|
+
await mkdir(cacheDir, { recursive: true });
|
|
122
|
+
|
|
110
123
|
for (const v of versions) {
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
124
|
+
// Framework repo has docs/migrations/ at root — prefer local if present
|
|
125
|
+
// (covers the open-press framework repo itself acting as a workspace).
|
|
126
|
+
const localDocsPath = path.join(root, "docs", "migrations", `${v}.md`);
|
|
127
|
+
if (existsSync(localDocsPath)) {
|
|
128
|
+
results.push({ version: v, path: path.relative(root, localDocsPath), fetched: false });
|
|
129
|
+
continue;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// Otherwise fetch from GitHub raw and cache to .openpress/migrations/.
|
|
133
|
+
const cachedPath = path.join(cacheDir, `${v}.md`);
|
|
134
|
+
if (existsSync(cachedPath)) {
|
|
135
|
+
results.push({ version: v, path: path.relative(root, cachedPath), fetched: false });
|
|
136
|
+
continue;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
const body = await fetchMigration(v);
|
|
140
|
+
if (body) {
|
|
141
|
+
await writeFile(cachedPath, body, "utf8");
|
|
142
|
+
results.push({ version: v, path: path.relative(root, cachedPath), fetched: true });
|
|
143
|
+
} else {
|
|
144
|
+
results.push({ version: v, path: null, fetched: false });
|
|
114
145
|
}
|
|
115
146
|
}
|
|
116
147
|
return results;
|
|
117
148
|
}
|
|
149
|
+
|
|
150
|
+
async function fetchMigration(version) {
|
|
151
|
+
const url = `${MIGRATION_REMOTE_BASE}/${version}.md`;
|
|
152
|
+
try {
|
|
153
|
+
const res = await fetch(url, { headers: { Accept: "text/plain" } });
|
|
154
|
+
if (!res.ok) return null;
|
|
155
|
+
return await res.text();
|
|
156
|
+
} catch {
|
|
157
|
+
return null;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { validateWorkspace } from "../validation.mjs";
|
|
2
|
-
import { exitCodeForIssueReport } from "../issue-report.mjs";
|
|
1
|
+
import { validateWorkspace } from "../runtime/validation.mjs";
|
|
2
|
+
import { exitCodeForIssueReport } from "../runtime/issue-report.mjs";
|
|
3
3
|
|
|
4
4
|
export async function run({ root, options }) {
|
|
5
5
|
const report = await validateWorkspace(root);
|
|
@@ -10,6 +10,6 @@ export async function exportDocument(root = ROOT) {
|
|
|
10
10
|
if (reactResult) return reactResult;
|
|
11
11
|
|
|
12
12
|
throw new Error(
|
|
13
|
-
"React/MDX document entry not found. Expected document/index.tsx
|
|
13
|
+
"React/MDX document entry not found. Expected document/index.tsx with a Press default export before exporting.",
|
|
14
14
|
);
|
|
15
15
|
}
|
|
@@ -203,8 +203,7 @@ export async function waitForPrintReady(client) {
|
|
|
203
203
|
awaitPromise: true,
|
|
204
204
|
expression: `Promise.resolve().then(async () => {
|
|
205
205
|
const root = document.querySelector('[data-openpress-print-document="true"]');
|
|
206
|
-
|
|
207
|
-
if (!ready) return 0;
|
|
206
|
+
if (!root || root.querySelectorAll('.openpress-html-page').length === 0) return 0;
|
|
208
207
|
|
|
209
208
|
await document.fonts?.ready;
|
|
210
209
|
await Promise.all(Array.from(document.images).map(async (img) => {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import fs from "node:fs/promises";
|
|
2
2
|
import path from "node:path";
|
|
3
|
-
import { loadConfig } from "
|
|
4
|
-
import { copyDirectory } from "
|
|
3
|
+
import { loadConfig } from "../runtime/config.mjs";
|
|
4
|
+
import { copyDirectory } from "../runtime/file-utils.mjs";
|
|
5
5
|
|
|
6
6
|
export async function deploySync(root, sourceDir, deployDir) {
|
|
7
7
|
const config = await loadConfig(root);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import fs from "node:fs/promises";
|
|
2
2
|
import path from "node:path";
|
|
3
|
-
import { copyDirectory } from "
|
|
3
|
+
import { copyDirectory } from "../runtime/file-utils.mjs";
|
|
4
4
|
|
|
5
5
|
export async function copyThemeFonts(root, publicOutputDir, config) {
|
|
6
6
|
const themeDir = config?.paths?.themeDir ?? path.join(path.resolve(root), "theme");
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import fs from "node:fs/promises";
|
|
2
2
|
import path from "node:path";
|
|
3
|
-
import { loadConfig } from "
|
|
4
|
-
import { copyDirectory, writeComponentsCss, writeContentCss } from "
|
|
3
|
+
import { loadConfig } from "../runtime/config.mjs";
|
|
4
|
+
import { copyDirectory, writeComponentsCss, writeContentCss } from "../runtime/file-utils.mjs";
|
|
5
5
|
import { copyThemeFonts } from "./fonts.mjs";
|
|
6
6
|
import { copyKatexFonts } from "./katex-assets.mjs";
|
|
7
7
|
|
|
@@ -2,8 +2,8 @@ import fs from "node:fs/promises";
|
|
|
2
2
|
import http from "node:http";
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import { spawn } from "node:child_process";
|
|
5
|
-
import { loadConfig, publicPdfHref } from "
|
|
6
|
-
import { handleProjectAssetRequest } from "
|
|
5
|
+
import { loadConfig, publicPdfHref } from "../runtime/config.mjs";
|
|
6
|
+
import { handleProjectAssetRequest } from "../react/project-asset-endpoint.mjs";
|
|
7
7
|
|
|
8
8
|
const [rootArg = "dist", ...rest] = process.argv.slice(2);
|
|
9
9
|
const host = valueAfter(rest, "--host") ?? "127.0.0.1";
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
const DEFAULT_CAPTION_NUMBERING = {
|
|
2
|
+
figure: "Figure",
|
|
3
|
+
table: "Table",
|
|
4
|
+
separator: " ",
|
|
5
|
+
};
|
|
6
|
+
|
|
7
|
+
export function normalizeCaptionNumbering(value = {}) {
|
|
8
|
+
const input = value && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
9
|
+
return {
|
|
10
|
+
figure: stringOption(input.figure, DEFAULT_CAPTION_NUMBERING.figure),
|
|
11
|
+
table: stringOption(input.table, DEFAULT_CAPTION_NUMBERING.table),
|
|
12
|
+
separator: typeof input.separator === "string" ? input.separator : DEFAULT_CAPTION_NUMBERING.separator,
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function createCaptionNumberingState() {
|
|
17
|
+
return {
|
|
18
|
+
figure: 0,
|
|
19
|
+
table: 0,
|
|
20
|
+
seenTables: new Set(),
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function numberCaptionsInHtml(html, numbering, state = createCaptionNumberingState()) {
|
|
25
|
+
if (!html) return html;
|
|
26
|
+
const options = normalizeCaptionNumbering(numbering);
|
|
27
|
+
let out = String(html);
|
|
28
|
+
out = numberTableCaptions(out, options, state);
|
|
29
|
+
out = numberFigureCaptions(out, options, state);
|
|
30
|
+
return out;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function numberTableCaptions(html, options, state) {
|
|
34
|
+
return html.replace(/<table\b([^>]*)>([\s\S]*?<caption\b([^>]*)>)([\s\S]*?)(<\/caption>[\s\S]*?<\/table>)/g, (match, tableAttrs, beforeCaptionText, captionAttrs, captionText, afterCaptionText) => {
|
|
35
|
+
if (captionText.includes("data-openpress-caption-label=")) return match;
|
|
36
|
+
const tableId = attrValue(tableAttrs, "data-openpress-table-id");
|
|
37
|
+
if (tableId && state.seenTables.has(tableId)) return match;
|
|
38
|
+
if (tableId) state.seenTables.add(tableId);
|
|
39
|
+
state.table += 1;
|
|
40
|
+
const label = captionLabel(options.table, state.table, options.separator);
|
|
41
|
+
return `<table${tableAttrs}>${beforeCaptionText}${captionLabelSpan("table", state.table, label)} ${captionText}${afterCaptionText}`;
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function numberFigureCaptions(html, options, state) {
|
|
46
|
+
return html.replace(/<figure\b([^>]*)>([\s\S]*?<figcaption\b([^>]*)>)([\s\S]*?)(<\/figcaption>[\s\S]*?<\/figure>)/g, (match, figureAttrs, beforeCaptionText, captionAttrs, captionText, afterCaptionText) => {
|
|
47
|
+
if (captionText.includes("data-openpress-caption-label=")) return match;
|
|
48
|
+
state.figure += 1;
|
|
49
|
+
const label = captionLabel(options.figure, state.figure, options.separator);
|
|
50
|
+
return `<figure${figureAttrs}>${beforeCaptionText}${captionLabelSpan("figure", state.figure, label)} ${captionText}${afterCaptionText}`;
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function captionLabel(noun, number, separator) {
|
|
55
|
+
return `${noun}${separator}${number}`;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function captionLabelSpan(kind, number, label) {
|
|
59
|
+
return `<span class="openpress-caption-label" data-openpress-caption-label="${kind}" data-openpress-caption-number="${number}">${escapeHtml(label)}</span>`;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function attrValue(attrs, name) {
|
|
63
|
+
const pattern = new RegExp(`${name}=(["'])(.*?)\\1`);
|
|
64
|
+
return attrs.match(pattern)?.[2] ?? "";
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function stringOption(value, fallback) {
|
|
68
|
+
return typeof value === "string" && value.trim() ? value.trim() : fallback;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function escapeHtml(value) {
|
|
72
|
+
return String(value).replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
73
|
+
}
|
|
@@ -1,15 +1,18 @@
|
|
|
1
1
|
import crypto from "node:crypto";
|
|
2
2
|
import fs from "node:fs/promises";
|
|
3
3
|
import path from "node:path";
|
|
4
|
-
import { loadConfig } from "../config.mjs";
|
|
5
|
-
import { collectSourceTextFiles } from "../source-text-tools.mjs";
|
|
6
|
-
|
|
4
|
+
import { loadConfig } from "../runtime/config.mjs";
|
|
5
|
+
import { collectSourceTextFiles } from "../runtime/source-text-tools.mjs";
|
|
6
|
+
|
|
7
|
+
// Any `.mdx` or `.tsx` file under `document/` is a legal comment target.
|
|
8
|
+
// The Press Tree allows arbitrary source layouts — `section-folders`,
|
|
9
|
+
// `section-files`, `file-list`, custom `root` paths, etc. — so we no
|
|
10
|
+
// longer hardcode `document/chapters/<slug>/content/*.mdx`. The boundary
|
|
11
|
+
// is "inside the workspace's authored `document/` directory" and "looks
|
|
12
|
+
// like an editable React/MDX source" by extension.
|
|
7
13
|
const EDITABLE_COMMENT_SOURCE_PATTERNS = [
|
|
8
|
-
/^document
|
|
9
|
-
/^document
|
|
10
|
-
/^document\/chapters\/[^/]+\/chapter\.tsx$/,
|
|
11
|
-
/^document\/chapters\/[^/]+\/components\/.+\.tsx$/,
|
|
12
|
-
/^document\/components\/.+\.tsx$/,
|
|
14
|
+
/^document\/.+\.mdx$/,
|
|
15
|
+
/^document\/.+\.tsx$/,
|
|
13
16
|
];
|
|
14
17
|
const COMMENT_MARKER_RE = /\{\/\*\s*@openpress-comment\b(?<attrs>[^*]*)\*\/\}/g;
|
|
15
18
|
const COMMENT_LINE_RE = /^\s*\{\/\*\s*@openpress-comment\b[^*]*\*\/\}\s*$/;
|
|
@@ -138,8 +141,14 @@ export function assertEditableCommentPath(relativePath) {
|
|
|
138
141
|
}
|
|
139
142
|
}
|
|
140
143
|
|
|
144
|
+
// Strict check against workspace-relative paths. Callers walking the
|
|
145
|
+
// workspace (`applyCommentMarker` via `collectSourceTextFiles`) already
|
|
146
|
+
// receive paths with the `document/` prefix and must not have system
|
|
147
|
+
// paths silently mapped into the editable set.
|
|
141
148
|
export function isEditableCommentPath(relativePath) {
|
|
142
|
-
|
|
149
|
+
if (typeof relativePath !== "string" || !relativePath) return false;
|
|
150
|
+
const trimmed = relativePath.trim().replaceAll("\\", "/").replace(/^\.\//, "");
|
|
151
|
+
return EDITABLE_COMMENT_SOURCE_PATTERNS.some((pattern) => pattern.test(trimmed));
|
|
143
152
|
}
|
|
144
153
|
|
|
145
154
|
function normalizeEditableSourcePath(value) {
|
|
@@ -150,7 +159,42 @@ function normalizeEditableSourcePath(value) {
|
|
|
150
159
|
if (path.posix.isAbsolute(normalized) || normalized.includes("\0") || normalized === "." || normalized.startsWith("../")) {
|
|
151
160
|
throw new Error(`OpenPress comment target path is invalid: ${value}`);
|
|
152
161
|
}
|
|
153
|
-
|
|
162
|
+
const posix = path.posix.normalize(normalized);
|
|
163
|
+
// The Press Tree source resolver emits paths relative to `document/`
|
|
164
|
+
// (e.g. "chapters/01-start/content/01-start.mdx"). The comment marker
|
|
165
|
+
// works in workspace-relative paths (with the `document/` prefix). If
|
|
166
|
+
// the incoming path is documentRoot-relative, prepend `document/`.
|
|
167
|
+
if (!posix.startsWith("document/") && looksDocumentRelative(posix)) {
|
|
168
|
+
return `document/${posix}`;
|
|
169
|
+
}
|
|
170
|
+
return posix;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// Identify paths the Press Tree source resolver emits — those are relative
|
|
174
|
+
// to `document/`. Match `.mdx` / `.tsx` files that don't already have the
|
|
175
|
+
// `document/` prefix and don't look like system / engine paths. The check
|
|
176
|
+
// is intentionally tight so we never silently rewrite engine internals
|
|
177
|
+
// (e.g. `src/openpress/...`) into "editable" workspace paths.
|
|
178
|
+
const SYSTEM_PATH_PREFIXES = [
|
|
179
|
+
"document/",
|
|
180
|
+
"src/",
|
|
181
|
+
"engine/",
|
|
182
|
+
"dist/",
|
|
183
|
+
"dist-react/",
|
|
184
|
+
"node_modules/",
|
|
185
|
+
"tests/",
|
|
186
|
+
"public/",
|
|
187
|
+
"packages/",
|
|
188
|
+
".openpress/",
|
|
189
|
+
".deploy/",
|
|
190
|
+
".changeset/",
|
|
191
|
+
".github/",
|
|
192
|
+
];
|
|
193
|
+
|
|
194
|
+
function looksDocumentRelative(posixPath) {
|
|
195
|
+
if (!/\.(mdx|tsx)$/.test(posixPath)) return false;
|
|
196
|
+
if (SYSTEM_PATH_PREFIXES.some((prefix) => posixPath.startsWith(prefix))) return false;
|
|
197
|
+
return true;
|
|
154
198
|
}
|
|
155
199
|
|
|
156
200
|
function normalizeLineNumber(value) {
|