@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
|
@@ -1,148 +1,145 @@
|
|
|
1
|
+
// Layer 6 orchestrator.
|
|
2
|
+
//
|
|
3
|
+
// Wires Layer 1 (entry load) -> source resolution -> Layer 2/3/4 iteration
|
|
4
|
+
// -> Layer 5 final render -> document.json + asset sync.
|
|
5
|
+
|
|
1
6
|
import fs from "node:fs/promises";
|
|
2
7
|
import path from "node:path";
|
|
3
|
-
import
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import { loadReactDocumentEntry, createReactSsrServer } from "./document-entry.mjs";
|
|
8
|
+
import { pathToFileURL } from "node:url";
|
|
9
|
+
import { documentRelativePath, pageToBlock } from "../output/page-block.mjs";
|
|
10
|
+
import { syncPublicAssets } from "../output/public-assets.mjs";
|
|
11
|
+
import { createCaptionNumberingState, numberCaptionsInHtml } from "./caption-numbering.mjs";
|
|
12
|
+
import { buildSectionScopedCss } from "./section-css.mjs";
|
|
13
|
+
import { CORE_ENTRY, createReactSsrServer, loadReactDocumentEntry } from "./document-entry.mjs";
|
|
10
14
|
import { buildReactMeasurementCss } from "./measurement-css.mjs";
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
13
|
-
import {
|
|
15
|
+
import { allocateChains } from "./pipeline/allocate.mjs";
|
|
16
|
+
import { measureFrames } from "./pipeline/frame-measurement.mjs";
|
|
17
|
+
import { renderFinalPress } from "./pipeline/final-render.mjs";
|
|
18
|
+
import { expandPressTree } from "./pipeline/press-tree.mjs";
|
|
19
|
+
import { resolveAllSources } from "./sources/mdx-resolver.mjs";
|
|
20
|
+
import { discoverSectionStyles } from "./style-discovery.mjs";
|
|
21
|
+
|
|
22
|
+
const MAX_ITERATIONS = 20;
|
|
14
23
|
|
|
15
|
-
export async function exportReactDocument(root = ".", { syncAssets = true
|
|
24
|
+
export async function exportReactDocument(root = ".", { syncAssets = true } = {}) {
|
|
16
25
|
const workspaceRoot = path.resolve(root);
|
|
17
|
-
|
|
18
|
-
|
|
26
|
+
// Quick existence check without opening an SSR server.
|
|
27
|
+
const fastCheck = await loadReactDocumentEntry(workspaceRoot);
|
|
28
|
+
if (!fastCheck) return null;
|
|
19
29
|
|
|
20
|
-
const workspace = await discoverReactWorkspace(workspaceRoot, entry.config);
|
|
21
|
-
const paginationOptions = normalizePaginationOptions(pagination);
|
|
22
|
-
if (paginationOptions.enabled && paginationOptions.needsMeasurementCss) {
|
|
23
|
-
paginationOptions.css = await buildReactMeasurementCss(workspaceRoot, entry.config, workspace);
|
|
24
|
-
}
|
|
25
30
|
const server = await createReactSsrServer(workspaceRoot);
|
|
26
31
|
try {
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
const
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
const chapterMeta = normalizeChapterMeta(chapter, chapterModule.meta);
|
|
36
|
-
const components = await loadComponentScope(server, chapter.componentScope);
|
|
37
|
-
const Page = typeof chapterModule.Page === "function" ? chapterModule.Page : components.Page ?? DefaultContentPage;
|
|
38
|
-
|
|
39
|
-
addShellPage(
|
|
40
|
-
pageJobs,
|
|
41
|
-
chapterModule.opener ?? null,
|
|
42
|
-
chapterSource(entry.config, chapter, {
|
|
43
|
-
chapterIndex,
|
|
44
|
-
kind: "chapter-opener",
|
|
45
|
-
slug: chapterMeta.slug,
|
|
46
|
-
title: chapterMeta.title,
|
|
47
|
-
}),
|
|
32
|
+
// Reload the entry through THIS server so the module identity matches
|
|
33
|
+
// what the rest of the pipeline (PressContext, hooks) sees.
|
|
34
|
+
const entry = await loadReactDocumentEntry(workspaceRoot, { server });
|
|
35
|
+
if (!entry) return null;
|
|
36
|
+
if (!entry.Press) {
|
|
37
|
+
throw new Error(
|
|
38
|
+
`OpenPress document entry ${entry.entryPath} must default-export a Press component (function) to export. ` +
|
|
39
|
+
`Legacy named exports (cover/toc/backCover) are not supported in v0.6 — see the Press Tree spec.`,
|
|
48
40
|
);
|
|
41
|
+
}
|
|
42
|
+
// Resolve PressContext + Frame markers from the engine's loaded core module.
|
|
43
|
+
// Use the absolute file path so the user's `import "@open-press/core"`
|
|
44
|
+
// (resolved via vite alias) and our load hit the same module cache entry.
|
|
45
|
+
const coreModule = await server.ssrLoadModule(CORE_ENTRY);
|
|
46
|
+
const PressContext = coreModule.PressContext;
|
|
47
|
+
if (!PressContext) {
|
|
48
|
+
throw new Error("Engine could not resolve PressContext from @open-press/core.");
|
|
49
|
+
}
|
|
49
50
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
source,
|
|
54
|
-
filePath: contentFile.absolutePath,
|
|
55
|
-
components,
|
|
56
|
-
chapterSlug: chapterMeta.slug,
|
|
57
|
-
});
|
|
58
|
-
const sourceRecord = chapterSource(entry.config, chapter, {
|
|
59
|
-
chapterIndex,
|
|
60
|
-
contentFile,
|
|
61
|
-
kind: "content",
|
|
62
|
-
slug: chapterMeta.slug,
|
|
63
|
-
title: chapterMeta.title,
|
|
64
|
-
});
|
|
65
|
-
const mdxBlocks = compiled.blocks.map((block) => sanitizeMdxBlock(entry.config, contentFile, block));
|
|
51
|
+
// Discover workspace for component scope and chapter-scoped style files.
|
|
52
|
+
const workspace = await discoverSectionStyles(workspaceRoot, entry.config);
|
|
53
|
+
const globalComponents = await loadComponentModules(server, workspace.globalComponents ?? []);
|
|
66
54
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
}));
|
|
75
|
-
continue;
|
|
76
|
-
}
|
|
55
|
+
// Resolve sources.
|
|
56
|
+
const documentRoot = entry.config.paths.documentRoot;
|
|
57
|
+
const { resolved: sources, renderData: renderRegistry } = await resolveAllSources({
|
|
58
|
+
sources: entry.sources,
|
|
59
|
+
documentRoot,
|
|
60
|
+
globalComponents,
|
|
61
|
+
});
|
|
77
62
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
{
|
|
81
|
-
pageIndex: 0,
|
|
82
|
-
totalPages: 1,
|
|
83
|
-
chapterSlug: chapterMeta.slug,
|
|
84
|
-
chapterTone: chapterMeta.tone,
|
|
85
|
-
},
|
|
86
|
-
React.createElement(compiled.Content),
|
|
87
|
-
));
|
|
88
|
-
const measured = await paginationOptions.measureBlocks({
|
|
89
|
-
html: measurementHtml,
|
|
90
|
-
blockIds: mdxBlocks.map((block) => block.id),
|
|
91
|
-
pageSafeHeightPx: paginationOptions.pageSafeHeightPx,
|
|
92
|
-
css: paginationOptions.css,
|
|
93
|
-
chapterSlug: chapterMeta.slug,
|
|
94
|
-
contentFile,
|
|
95
|
-
source: sourceRecord,
|
|
96
|
-
});
|
|
97
|
-
const blockLookup = Object.fromEntries(mdxBlocks.map((block) => [block.id, block]));
|
|
98
|
-
for (const warning of measured.warnings ?? []) {
|
|
99
|
-
paginationWarnings.push(enrichPaginationWarning(warning, blockLookup));
|
|
100
|
-
}
|
|
63
|
+
// Build measurement CSS.
|
|
64
|
+
const css = await buildReactMeasurementCss(workspaceRoot, entry.config, workspace);
|
|
101
65
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
66
|
+
// Iterative allocation loop.
|
|
67
|
+
let hints = null;
|
|
68
|
+
let allocation = null;
|
|
69
|
+
let lastFrames = null;
|
|
70
|
+
let warnings = [];
|
|
71
|
+
for (let iteration = 0; iteration < MAX_ITERATIONS; iteration++) {
|
|
72
|
+
const { html, frames } = expandPressTree({
|
|
73
|
+
Press: entry.Press,
|
|
74
|
+
PressContext,
|
|
75
|
+
sources,
|
|
76
|
+
hints,
|
|
77
|
+
});
|
|
78
|
+
lastFrames = frames;
|
|
79
|
+
validateAllChainsKnown(frames, sources);
|
|
80
|
+
const measurement = await measureFrames({
|
|
81
|
+
pressHtml: html,
|
|
82
|
+
sources,
|
|
83
|
+
renderRegistry,
|
|
84
|
+
css,
|
|
85
|
+
baseHref: pathToFileURL(`${documentRoot}${path.sep}`).href,
|
|
86
|
+
mediaDir: path.join(documentRoot, "media"),
|
|
87
|
+
captionNumbering: entry.config.captionNumbering,
|
|
88
|
+
});
|
|
89
|
+
const alloc = allocateChains({
|
|
90
|
+
frames,
|
|
91
|
+
mdxAreas: measurement.mdxAreas,
|
|
92
|
+
blockHeights: measurement.blockHeights,
|
|
93
|
+
sources,
|
|
94
|
+
});
|
|
95
|
+
if (process.env.OPENPRESS_DEBUG_ALLOC) {
|
|
96
|
+
const sample = measurement.mdxAreas
|
|
97
|
+
.slice(0, 5)
|
|
98
|
+
.map((a) => `${a.frameKey}#${a.indexInFrame} cap=${a.capacity.toFixed(0)} raw=${(a.rawHeight ?? 0).toFixed(0)}`);
|
|
99
|
+
const blocks = measurement.blockHeights
|
|
100
|
+
.slice(0, 8)
|
|
101
|
+
.map((b) => `${b.id} h=${b.height.toFixed(0)}`);
|
|
102
|
+
process.stderr.write(`[allocator iter ${iteration}]\n`);
|
|
103
|
+
process.stderr.write(` mdxAreas[0..4]: ${sample.join(" | ")}\n`);
|
|
104
|
+
process.stderr.write(` blocks[0..7]: ${blocks.join(" | ")}\n`);
|
|
105
|
+
process.stderr.write(` hints: ${JSON.stringify(alloc.hints.totalPagesPerChain)}\n`);
|
|
106
|
+
if (alloc.warnings.length > 0) {
|
|
107
|
+
process.stderr.write(` warnings: ${JSON.stringify(alloc.warnings)}\n`);
|
|
125
108
|
}
|
|
126
109
|
}
|
|
110
|
+
if (hintsEqual(hints, alloc.hints)) {
|
|
111
|
+
allocation = alloc.allocation;
|
|
112
|
+
warnings = alloc.warnings;
|
|
113
|
+
break;
|
|
114
|
+
}
|
|
115
|
+
hints = alloc.hints;
|
|
116
|
+
}
|
|
117
|
+
if (allocation == null) {
|
|
118
|
+
throw new Error(
|
|
119
|
+
`Allocation did not converge after ${MAX_ITERATIONS} iterations. ` +
|
|
120
|
+
`This usually means a chain keeps growing without fitting; check MdxArea capacities and block heights.`,
|
|
121
|
+
);
|
|
127
122
|
}
|
|
128
123
|
|
|
129
|
-
|
|
124
|
+
const toc = buildTocContext({ sources, frames: lastFrames ?? [], allocation });
|
|
130
125
|
|
|
131
|
-
|
|
132
|
-
const
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
return pageToBlock(index, page.html, page.source, entry.config);
|
|
126
|
+
// Final render.
|
|
127
|
+
const final = await renderFinalPress({
|
|
128
|
+
Press: entry.Press,
|
|
129
|
+
PressContext,
|
|
130
|
+
sources,
|
|
131
|
+
hints,
|
|
132
|
+
toc,
|
|
133
|
+
allocation,
|
|
134
|
+
renderRegistry,
|
|
141
135
|
});
|
|
142
|
-
|
|
136
|
+
|
|
137
|
+
// Write chapter-scoped CSS (under section-scoped rules but filename
|
|
138
|
+
// unchanged for now).
|
|
139
|
+
const chapterCss = await buildSectionScopedCss(workspace);
|
|
143
140
|
const styles = [];
|
|
141
|
+
await fs.mkdir(entry.config.paths.publicDir, { recursive: true });
|
|
144
142
|
if (chapterCss.trim()) {
|
|
145
|
-
await fs.mkdir(entry.config.paths.publicDir, { recursive: true });
|
|
146
143
|
await fs.writeFile(path.join(entry.config.paths.publicDir, "chapter-scoped.css"), chapterCss, "utf8");
|
|
147
144
|
styles.push({
|
|
148
145
|
kind: "chapter-scoped-css",
|
|
@@ -151,238 +148,197 @@ export async function exportReactDocument(root = ".", { syncAssets = true, pagin
|
|
|
151
148
|
});
|
|
152
149
|
}
|
|
153
150
|
|
|
151
|
+
// Build document.json. The reader filters blocks by kind === "htmlPage",
|
|
152
|
+
// so wrap each frame through `pageToBlock` to inherit that contract.
|
|
153
|
+
const blockMap = {};
|
|
154
|
+
const captionState = createCaptionNumberingState();
|
|
155
|
+
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
|
+
const source = {
|
|
160
|
+
file: "index.tsx",
|
|
161
|
+
path: "document/index.tsx",
|
|
162
|
+
kind: frame.role ?? "manuscript.content",
|
|
163
|
+
slug: frame.frameKey,
|
|
164
|
+
sectionIndex: index + 1,
|
|
165
|
+
};
|
|
166
|
+
const html = numberCaptionsInHtml(frame.html, entry.config.captionNumbering, captionState);
|
|
167
|
+
const block = pageToBlock(index, html, source, entry.config, {
|
|
168
|
+
idPrefix: "openpress-page",
|
|
169
|
+
anchorPrefix: "page",
|
|
170
|
+
titleFallback: "Page",
|
|
171
|
+
});
|
|
172
|
+
return {
|
|
173
|
+
...block,
|
|
174
|
+
frameKey: frame.frameKey,
|
|
175
|
+
role: frame.role ?? null,
|
|
176
|
+
chrome: frame.chrome ?? true,
|
|
177
|
+
blockIds: frame.blockIds,
|
|
178
|
+
};
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
// Enrich blockMap with source records from resolved chains so comment
|
|
182
|
+
// tooling can resolve block IDs back to MDX positions.
|
|
183
|
+
const sourceBlockIndex = buildSourceBlockIndex(sources);
|
|
184
|
+
for (const id of Object.keys(blockMap)) {
|
|
185
|
+
const sourceRecord = sourceBlockIndex.get(id);
|
|
186
|
+
if (sourceRecord) {
|
|
187
|
+
blockMap[id] = {
|
|
188
|
+
...blockMap[id],
|
|
189
|
+
kind: sourceRecord.kind,
|
|
190
|
+
name: sourceRecord.name,
|
|
191
|
+
path: sourceRecord.path,
|
|
192
|
+
source: sourceRecord.source,
|
|
193
|
+
chainId: sourceRecord.chainId,
|
|
194
|
+
sectionSlug: sourceRecord.sectionSlug,
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
154
199
|
const readerDocument = {
|
|
155
200
|
meta: {
|
|
156
201
|
title: trimmedString(entry.config.title) ?? "Untitled Document",
|
|
157
202
|
subtitle: trimmedString(entry.config.subtitle) ?? "",
|
|
158
203
|
organization: trimmedString(entry.config.organization) ?? "",
|
|
159
204
|
workspaceLabel: trimmedString(entry.config.workspaceLabel) ?? trimmedString(entry.config.title) ?? "Untitled Document",
|
|
160
|
-
version: "openpress-
|
|
205
|
+
version: "openpress-press-tree-v1",
|
|
161
206
|
},
|
|
162
207
|
source: {
|
|
163
|
-
type: "openpress-
|
|
208
|
+
type: "openpress-press-tree-mdx",
|
|
164
209
|
contentDir: documentRelativePath(entry.config, entry.config.sourceDir),
|
|
165
210
|
editable: true,
|
|
166
211
|
editMode: "source-mdx",
|
|
167
212
|
styles,
|
|
168
213
|
blockMap,
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
214
|
+
frames: final.frames.map((frame, index) => ({
|
|
215
|
+
frameKey: frame.frameKey,
|
|
216
|
+
role: frame.role ?? null,
|
|
217
|
+
pageIndex: index,
|
|
218
|
+
mdxAreas: frame.mdxAreas.map((area) => ({
|
|
219
|
+
chainId: area.chainId,
|
|
220
|
+
indexInFrame: area.indexInFrame,
|
|
221
|
+
blockIds: area.blockIds,
|
|
222
|
+
})),
|
|
223
|
+
})),
|
|
224
|
+
chains: Object.keys(sources).flatMap((id) => Object.keys(sources[id].chains)),
|
|
225
|
+
warnings,
|
|
176
226
|
},
|
|
177
227
|
blocks,
|
|
178
228
|
};
|
|
179
229
|
|
|
180
230
|
const documentPath = path.join(entry.config.paths.publicDir, "document.json");
|
|
181
|
-
await fs.mkdir(entry.config.paths.publicDir, { recursive: true });
|
|
182
231
|
await fs.writeFile(documentPath, JSON.stringify(readerDocument, null, 2), "utf8");
|
|
232
|
+
|
|
183
233
|
if (syncAssets) {
|
|
184
234
|
await syncPublicAssets(workspaceRoot, entry.config.paths.publicDir, entry.config);
|
|
185
235
|
}
|
|
236
|
+
|
|
186
237
|
return { documentPath, pageCount: blocks.length, document: readerDocument };
|
|
187
238
|
} finally {
|
|
188
239
|
await server.close();
|
|
189
240
|
}
|
|
190
241
|
}
|
|
191
242
|
|
|
192
|
-
function
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
243
|
+
async function loadComponentModules(server, components) {
|
|
244
|
+
const out = {};
|
|
245
|
+
for (const component of components) {
|
|
246
|
+
const mod = await server.ssrLoadModule(component.absolutePath);
|
|
247
|
+
if (typeof mod.default !== "function") {
|
|
248
|
+
throw new Error(
|
|
249
|
+
`OpenPress component module ${component.documentPath} must default-export a React component.`,
|
|
250
|
+
);
|
|
251
|
+
}
|
|
252
|
+
out[component.name] = mod.default;
|
|
198
253
|
}
|
|
199
|
-
return
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
function renderPageJobs(pageJobs, totalPages) {
|
|
203
|
-
return pageJobs.map((job, index) => ({
|
|
204
|
-
html: renderToStaticMarkup(job.render(index, totalPages)),
|
|
205
|
-
source: job.source,
|
|
206
|
-
mdxBlocks: job.mdxBlocks ?? [],
|
|
207
|
-
}));
|
|
254
|
+
return out;
|
|
208
255
|
}
|
|
209
256
|
|
|
210
|
-
function
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
html: injectedHtml[index],
|
|
215
|
-
}));
|
|
257
|
+
function validateAllChainsKnown(frames, sources) {
|
|
258
|
+
const known = new Set();
|
|
259
|
+
for (const source of Object.values(sources)) {
|
|
260
|
+
for (const chainId of Object.keys(source.chains)) known.add(chainId);
|
|
216
261
|
}
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
if (index <= tocIndex + extra) {
|
|
227
|
-
return {
|
|
228
|
-
html,
|
|
229
|
-
source: {
|
|
230
|
-
...records[tocIndex].source,
|
|
231
|
-
sectionIndex: index - tocIndex + 1,
|
|
232
|
-
},
|
|
233
|
-
mdxBlocks: [],
|
|
234
|
-
};
|
|
262
|
+
for (const frame of frames) {
|
|
263
|
+
for (const area of frame.mdxAreas) {
|
|
264
|
+
if (!known.has(area.chainId)) {
|
|
265
|
+
const list = [...known].sort().slice(0, 10).join(", ");
|
|
266
|
+
throw new Error(
|
|
267
|
+
`Unknown chainId "${area.chainId}" referenced by frame "${frame.frameKey}". ` +
|
|
268
|
+
`Known chains: ${list || "(none)"}${known.size > 10 ? ", ..." : ""}.`,
|
|
269
|
+
);
|
|
270
|
+
}
|
|
235
271
|
}
|
|
236
|
-
|
|
237
|
-
return {
|
|
238
|
-
...sourceRecord,
|
|
239
|
-
html,
|
|
240
|
-
};
|
|
241
|
-
});
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
function hasReaderPageKind(html, kind) {
|
|
245
|
-
const openingTag = String(html).match(/^<section[^>]*>/i)?.[0] ?? "";
|
|
246
|
-
return openingTag.match(/\bdata-page-kind="([^"]*)"/i)?.[1] === kind;
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
function addShellPage(pageJobs, element, source) {
|
|
250
|
-
if (element == null) return;
|
|
251
|
-
pageJobs.push({
|
|
252
|
-
source,
|
|
253
|
-
render() {
|
|
254
|
-
return element;
|
|
255
|
-
},
|
|
256
|
-
});
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
function mdxPageJob({ Page, Content, source, mdxBlocks, chapterMeta, pagination = null }) {
|
|
260
|
-
return {
|
|
261
|
-
source,
|
|
262
|
-
mdxBlocks,
|
|
263
|
-
pagination,
|
|
264
|
-
render(pageIndex, totalPages) {
|
|
265
|
-
return React.createElement(
|
|
266
|
-
Page,
|
|
267
|
-
{
|
|
268
|
-
pageIndex,
|
|
269
|
-
totalPages,
|
|
270
|
-
chapterSlug: chapterMeta.slug,
|
|
271
|
-
chapterTone: chapterMeta.tone,
|
|
272
|
-
},
|
|
273
|
-
React.createElement(Content),
|
|
274
|
-
);
|
|
275
|
-
},
|
|
276
|
-
};
|
|
272
|
+
}
|
|
277
273
|
}
|
|
278
274
|
|
|
279
|
-
|
|
280
|
-
if (
|
|
281
|
-
|
|
275
|
+
function hintsEqual(a, b) {
|
|
276
|
+
if (a === b) return true;
|
|
277
|
+
if (!a || !b) return false;
|
|
278
|
+
const aMap = a.totalPagesPerChain ?? {};
|
|
279
|
+
const bMap = b.totalPagesPerChain ?? {};
|
|
280
|
+
const keys = new Set([...Object.keys(aMap), ...Object.keys(bMap)]);
|
|
281
|
+
for (const key of keys) {
|
|
282
|
+
if (aMap[key] !== bMap[key]) return false;
|
|
283
|
+
}
|
|
284
|
+
return true;
|
|
282
285
|
}
|
|
283
286
|
|
|
284
|
-
|
|
285
|
-
const
|
|
286
|
-
for (const
|
|
287
|
-
const
|
|
288
|
-
|
|
289
|
-
|
|
287
|
+
function buildSourceBlockIndex(sources) {
|
|
288
|
+
const index = new Map();
|
|
289
|
+
for (const source of Object.values(sources)) {
|
|
290
|
+
for (const [chainId, blocks] of Object.entries(source.chains)) {
|
|
291
|
+
for (const block of blocks) {
|
|
292
|
+
index.set(block.id, { ...block, chainId });
|
|
293
|
+
}
|
|
290
294
|
}
|
|
291
|
-
components[name] = mod.default;
|
|
292
295
|
}
|
|
293
|
-
return
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
function normalizeChapterMeta(chapter, meta) {
|
|
297
|
-
const rawMeta = meta && typeof meta === "object" ? meta : {};
|
|
298
|
-
return {
|
|
299
|
-
slug: trimmedString(rawMeta.slug) ?? chapter.slug,
|
|
300
|
-
title: trimmedString(rawMeta.title) ?? chapter.slug,
|
|
301
|
-
tone: trimmedString(rawMeta.tone) ?? undefined,
|
|
302
|
-
};
|
|
303
|
-
}
|
|
304
|
-
|
|
305
|
-
function shellSource(config, kind) {
|
|
306
|
-
return {
|
|
307
|
-
file: "index.tsx",
|
|
308
|
-
path: documentRelativePath(config, "index.tsx"),
|
|
309
|
-
kind,
|
|
310
|
-
slug: kind,
|
|
311
|
-
sectionIndex: 1,
|
|
312
|
-
};
|
|
313
|
-
}
|
|
314
|
-
|
|
315
|
-
function chapterSource(config, chapter, { chapterIndex, contentFile, kind, slug, title }) {
|
|
316
|
-
const file = contentFile?.documentPath ?? chapter.chapterEntry?.documentPath ?? chapter.documentPath;
|
|
317
|
-
return {
|
|
318
|
-
file: path.basename(file),
|
|
319
|
-
path: documentRelativePath(config, file),
|
|
320
|
-
kind,
|
|
321
|
-
chapter: chapterIndex + 1,
|
|
322
|
-
slug,
|
|
323
|
-
title,
|
|
324
|
-
sectionIndex: 1,
|
|
325
|
-
};
|
|
296
|
+
return index;
|
|
326
297
|
}
|
|
327
298
|
|
|
328
|
-
function
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
} : {}),
|
|
347
|
-
};
|
|
348
|
-
}
|
|
349
|
-
|
|
350
|
-
function normalizePaginationOptions(pagination) {
|
|
351
|
-
if (!pagination?.enabled) {
|
|
352
|
-
return { enabled: false };
|
|
299
|
+
function buildTocContext({ sources, frames, allocation }) {
|
|
300
|
+
const toc = {};
|
|
301
|
+
for (const source of Object.values(sources)) {
|
|
302
|
+
for (const [tocChainId, tocBlocks] of Object.entries(source.chains).filter(([chainId]) => chainId.startsWith(`toc:${source.id}`))) {
|
|
303
|
+
if (tocBlocks.length === 0) continue;
|
|
304
|
+
toc[tocChainId] = tocBlocks.map((block) => ({
|
|
305
|
+
id: `${source.id}:${block.sectionSlug}`,
|
|
306
|
+
blockId: block.id,
|
|
307
|
+
sourceId: source.id,
|
|
308
|
+
sectionSlug: block.sectionSlug,
|
|
309
|
+
title: block.title,
|
|
310
|
+
href: block.href,
|
|
311
|
+
level: block.level,
|
|
312
|
+
label: block.label,
|
|
313
|
+
pageNumber: firstAllocatedPageNumberForBlock(frames, allocation, block.targetBlockId)
|
|
314
|
+
?? firstAllocatedPageNumber(frames, allocation, `${source.id}:${block.sectionSlug}`),
|
|
315
|
+
}));
|
|
316
|
+
}
|
|
353
317
|
}
|
|
354
|
-
|
|
355
|
-
return {
|
|
356
|
-
enabled: true,
|
|
357
|
-
pageSafeHeightPx,
|
|
358
|
-
needsMeasurementCss: typeof pagination.measureBlocks !== "function",
|
|
359
|
-
measureBlocks: pagination.measureBlocks ?? ((input) => measureBlocksInChromium({
|
|
360
|
-
html: input.html,
|
|
361
|
-
css: input.css,
|
|
362
|
-
pageSafeHeightPx,
|
|
363
|
-
})),
|
|
364
|
-
};
|
|
318
|
+
return toc;
|
|
365
319
|
}
|
|
366
320
|
|
|
367
|
-
function
|
|
368
|
-
|
|
369
|
-
|
|
321
|
+
function firstAllocatedPageNumberForBlock(frames, allocation, blockId) {
|
|
322
|
+
if (!blockId) return undefined;
|
|
323
|
+
for (let index = 0; index < frames.length; index += 1) {
|
|
324
|
+
const frameAllocation = allocation?.[frames[index].frameKey] ?? {};
|
|
325
|
+
for (const areaArr of Object.values(frameAllocation)) {
|
|
326
|
+
if (areaArr?.some((area) => Array.isArray(area) && area.includes(blockId))) return index + 1;
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
return undefined;
|
|
370
330
|
}
|
|
371
331
|
|
|
372
|
-
function
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
"data-chapter-tone": chapterTone,
|
|
383
|
-
},
|
|
384
|
-
children,
|
|
385
|
-
);
|
|
332
|
+
function firstAllocatedPageNumber(frames, allocation, chainId) {
|
|
333
|
+
for (let index = 0; index < frames.length; index += 1) {
|
|
334
|
+
const frame = frames[index];
|
|
335
|
+
const allocated = allocation?.[frame.frameKey]?.[chainId];
|
|
336
|
+
if (allocated?.some((area) => Array.isArray(area) && area.length > 0)) return index + 1;
|
|
337
|
+
}
|
|
338
|
+
for (let index = 0; index < frames.length; index += 1) {
|
|
339
|
+
if (frames[index].mdxAreas.some((area) => area.chainId === chainId)) return index + 1;
|
|
340
|
+
}
|
|
341
|
+
return undefined;
|
|
386
342
|
}
|
|
387
343
|
|
|
388
344
|
function trimmedString(value) {
|