@bendyline/squisq-formats 2.0.1 → 2.2.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/LICENSE +21 -0
- package/NOTICE.md +20 -0
- package/README.md +1 -1
- package/dist/{chunk-CRAVSMPZ.js → chunk-26ISNJ7Y.js} +356 -114
- package/dist/{chunk-NKAJPJ4G.js → chunk-2JJ5RFDZ.js} +0 -1
- package/dist/{chunk-HTW2M27H.js → chunk-3NKXBZSR.js} +193 -42
- package/dist/{chunk-U32AG3G3.js → chunk-4V3KCHAP.js} +3 -4
- package/dist/{chunk-MLX2BOJC.js → chunk-6RQOV3B3.js} +1 -2
- package/dist/{chunk-QRVN6A6E.js → chunk-6S6GU3ZG.js} +5 -6
- package/dist/{chunk-FE6OJV6O.js → chunk-7AWFHP5U.js} +1 -1
- package/dist/{chunk-RFAPOKHJ.js → chunk-AD2WT564.js} +59 -9
- package/dist/{chunk-O3GVVND4.js → chunk-AONELFLA.js} +0 -1
- package/dist/{chunk-XKUMNGBW.js → chunk-EJTNGKEA.js} +5 -8
- package/dist/chunk-GX7RAUME.js +121 -0
- package/dist/{chunk-SSUPBUF5.js → chunk-IIQYS2YH.js} +0 -1
- package/dist/{chunk-ABVI556T.js → chunk-IPN56VLW.js} +83 -58
- package/dist/{chunk-LXYLOOST.js → chunk-JE6LSIHE.js} +83 -22
- package/dist/{chunk-U4MRIFKL.js → chunk-JU2RHXUB.js} +0 -1
- package/dist/{chunk-4VUWTSGM.js → chunk-K6XRMVPW.js} +64 -31
- package/dist/{chunk-ODL3SSPT.js → chunk-KXOZMWBS.js} +0 -1
- package/dist/chunk-OGS5VCGJ.js +446 -0
- package/dist/{chunk-GVS2XXV6.js → chunk-PJXJI2LY.js} +449 -57
- package/dist/{chunk-2KPARF2P.js → chunk-PU7REGWV.js} +5 -8
- package/dist/{chunk-PN52A5AA.js → chunk-SBUW7NHR.js} +0 -1
- package/dist/{chunk-VSYHZECT.js → chunk-TAAENIRB.js} +5 -8
- package/dist/{chunk-WC7WULGV.js → chunk-X2DEAXNK.js} +62 -2
- package/dist/container/index.js +1 -2
- package/dist/csv/index.d.ts +27 -2
- package/dist/csv/index.js +1 -2
- package/dist/docx/index.d.ts +5 -1
- package/dist/docx/index.js +9 -10
- package/dist/epub/index.d.ts +2 -0
- package/dist/epub/index.js +5 -6
- package/dist/{export-D2NkylDT.d.ts → export-D9msROJS.d.ts} +18 -6
- package/dist/extract-MN7LA3NL.js +13 -0
- package/dist/html/index.d.ts +11 -4
- package/dist/html/index.js +3 -4
- package/dist/images-ESPQKVTW.js +6 -0
- package/dist/{import-K8mfc0fz.d.ts → import-C3htUTss.d.ts} +5 -1
- package/dist/{import-DTkDxHmZ.d.ts → import-C8whCC7_.d.ts} +6 -0
- package/dist/index.d.ts +7 -7
- package/dist/index.js +28 -26
- package/dist/infer/index.d.ts +3 -3
- package/dist/infer/index.js +7 -9
- package/dist/{layouts-BHrgZ5FS.d.ts → layouts-CTdPlB-u.d.ts} +1 -1
- package/dist/layouts-DRWZGSPD.js +10 -0
- package/dist/{mapTheme-IR27S6IV.js → mapTheme-4TWH25FT.js} +1 -2
- package/dist/ooxml/index.d.ts +3 -3
- package/dist/ooxml/index.js +14 -13
- package/dist/pdf/index.d.ts +18 -0
- package/dist/pdf/index.js +2 -3
- package/dist/pptx/index.d.ts +4 -4
- package/dist/pptx/index.js +11 -13
- package/dist/{reader-B9L8Ucbj.d.ts → reader-B_m1aKZC.d.ts} +30 -1
- package/dist/registry/index.d.ts +21 -5
- package/dist/registry/index.js +9 -6
- package/dist/{themeReader-DJKErl_j.d.ts → themeReader-DCtwC83Q.d.ts} +1 -1
- package/dist/xlsx/index.d.ts +3 -3
- package/dist/xlsx/index.js +6 -7
- package/package.json +6 -3
- package/dist/chunk-2KPARF2P.js.map +0 -1
- package/dist/chunk-4VUWTSGM.js.map +0 -1
- package/dist/chunk-6M7Z25LA.js +0 -46
- package/dist/chunk-6M7Z25LA.js.map +0 -1
- package/dist/chunk-ABVI556T.js.map +0 -1
- package/dist/chunk-CRAVSMPZ.js.map +0 -1
- package/dist/chunk-FE6OJV6O.js.map +0 -1
- package/dist/chunk-GVS2XXV6.js.map +0 -1
- package/dist/chunk-HTW2M27H.js.map +0 -1
- package/dist/chunk-LXYLOOST.js.map +0 -1
- package/dist/chunk-MLX2BOJC.js.map +0 -1
- package/dist/chunk-NKAJPJ4G.js.map +0 -1
- package/dist/chunk-O3GVVND4.js.map +0 -1
- package/dist/chunk-ODL3SSPT.js.map +0 -1
- package/dist/chunk-PN52A5AA.js.map +0 -1
- package/dist/chunk-QRVN6A6E.js.map +0 -1
- package/dist/chunk-RFAPOKHJ.js.map +0 -1
- package/dist/chunk-SSUPBUF5.js.map +0 -1
- package/dist/chunk-U32AG3G3.js.map +0 -1
- package/dist/chunk-U4MRIFKL.js.map +0 -1
- package/dist/chunk-VJJM2SSH.js +0 -275
- package/dist/chunk-VJJM2SSH.js.map +0 -1
- package/dist/chunk-VSYHZECT.js.map +0 -1
- package/dist/chunk-WC7WULGV.js.map +0 -1
- package/dist/chunk-XKUMNGBW.js.map +0 -1
- package/dist/chunk-YRT7GQ5Y.js +0 -28
- package/dist/chunk-YRT7GQ5Y.js.map +0 -1
- package/dist/container/index.js.map +0 -1
- package/dist/csv/index.js.map +0 -1
- package/dist/docx/index.js.map +0 -1
- package/dist/epub/index.js.map +0 -1
- package/dist/extract-H6RXJMHP.js +0 -15
- package/dist/extract-H6RXJMHP.js.map +0 -1
- package/dist/html/index.js.map +0 -1
- package/dist/images-7FBWPKE3.js +0 -7
- package/dist/images-7FBWPKE3.js.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/infer/index.js.map +0 -1
- package/dist/layouts-QVPK3ZCU.js +0 -12
- package/dist/layouts-QVPK3ZCU.js.map +0 -1
- package/dist/mapTheme-IR27S6IV.js.map +0 -1
- package/dist/ooxml/index.js.map +0 -1
- package/dist/pdf/index.js.map +0 -1
- package/dist/pptx/index.js.map +0 -1
- package/dist/registry/index.js.map +0 -1
- package/dist/xlsx/index.js.map +0 -1
- package/src/__tests__/container.test.ts +0 -230
- package/src/__tests__/convert.test.ts +0 -495
- package/src/__tests__/csvImport.test.ts +0 -84
- package/src/__tests__/docxExport.test.ts +0 -457
- package/src/__tests__/docxImport.test.ts +0 -410
- package/src/__tests__/epub.test.ts +0 -649
- package/src/__tests__/exportThemeReconciliation.test.ts +0 -87
- package/src/__tests__/formatRegistry.test.ts +0 -174
- package/src/__tests__/html.test.ts +0 -435
- package/src/__tests__/htmlImport.test.ts +0 -57
- package/src/__tests__/inferTheme.test.ts +0 -135
- package/src/__tests__/lossyWarnings.test.ts +0 -146
- package/src/__tests__/ooxml.test.ts +0 -271
- package/src/__tests__/ooxmlCancellation.test.ts +0 -113
- package/src/__tests__/ooxmlThemeReader.test.ts +0 -92
- package/src/__tests__/pdfExport.test.ts +0 -322
- package/src/__tests__/pdfImport.test.ts +0 -384
- package/src/__tests__/plainHtml.test.ts +0 -417
- package/src/__tests__/plainHtmlBundle.test.ts +0 -253
- package/src/__tests__/pptxExport.test.ts +0 -138
- package/src/__tests__/pptxImport.test.ts +0 -145
- package/src/__tests__/pptxInferFixtures.ts +0 -314
- package/src/__tests__/pptxLayoutInfer.test.ts +0 -395
- package/src/__tests__/roundTrip.test.ts +0 -201
- package/src/__tests__/roundTripAssets.test.ts +0 -50
- package/src/__tests__/roundTripMatrix.fixtures.ts +0 -86
- package/src/__tests__/roundTripMatrix.helpers.ts +0 -154
- package/src/__tests__/roundTripMatrix.test.ts +0 -142
- package/src/__tests__/sharedContainer.test.ts +0 -41
- package/src/__tests__/sharedImages.test.ts +0 -61
- package/src/__tests__/xlsxExport.test.ts +0 -164
- package/src/__tests__/xlsxImport.test.ts +0 -80
- package/src/__tests__/zipSafety.test.ts +0 -317
- package/src/container/index.ts +0 -94
- package/src/csv/index.ts +0 -188
- package/src/docx/export.ts +0 -1267
- package/src/docx/import.ts +0 -995
- package/src/docx/index.ts +0 -26
- package/src/docx/styles.ts +0 -145
- package/src/epub/export.ts +0 -968
- package/src/epub/index.ts +0 -20
- package/src/html/docsHtmlBundle.ts +0 -373
- package/src/html/htmlTemplate.ts +0 -385
- package/src/html/imageUtils.ts +0 -61
- package/src/html/import.ts +0 -297
- package/src/html/index.ts +0 -212
- package/src/html/plainHtml.ts +0 -790
- package/src/html/plainHtmlBundle.ts +0 -421
- package/src/index.ts +0 -109
- package/src/infer/extract.ts +0 -127
- package/src/infer/index.ts +0 -199
- package/src/infer/mapTheme.ts +0 -176
- package/src/infer/types.ts +0 -27
- package/src/ooxml/index.ts +0 -111
- package/src/ooxml/namespaces.ts +0 -196
- package/src/ooxml/readUtils.ts +0 -44
- package/src/ooxml/reader.ts +0 -318
- package/src/ooxml/themeReader.ts +0 -197
- package/src/ooxml/types.ts +0 -103
- package/src/ooxml/writer.ts +0 -339
- package/src/ooxml/xmlUtils.ts +0 -123
- package/src/pdf/export.ts +0 -1084
- package/src/pdf/import.ts +0 -1164
- package/src/pdf/index.ts +0 -29
- package/src/pdf/styles.ts +0 -180
- package/src/pptx/export.ts +0 -1184
- package/src/pptx/import.ts +0 -455
- package/src/pptx/index.ts +0 -52
- package/src/pptx/layouts.ts +0 -1222
- package/src/pptx/styles.ts +0 -96
- package/src/pptx/templates.ts +0 -187
- package/src/registry/convert.ts +0 -433
- package/src/registry/defaultFormats.ts +0 -413
- package/src/registry/errors.ts +0 -46
- package/src/registry/index.ts +0 -43
- package/src/registry/registry.ts +0 -48
- package/src/registry/types.ts +0 -170
- package/src/shared/boundedZipArchive.ts +0 -383
- package/src/shared/container.ts +0 -28
- package/src/shared/fidelity.ts +0 -130
- package/src/shared/images.ts +0 -44
- package/src/shared/inlineRuns.ts +0 -99
- package/src/shared/text.ts +0 -41
- package/src/shared/zipEntryCount.ts +0 -151
- package/src/shared/zipLimits.ts +0 -296
- package/src/shared/zipSafety.ts +0 -19
- package/src/xlsx/export.ts +0 -253
- package/src/xlsx/import.ts +0 -160
- package/src/xlsx/index.ts +0 -35
package/src/epub/index.ts
DELETED
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @bendyline/squisq-formats EPUB Module
|
|
3
|
-
*
|
|
4
|
-
* Export squisq documents (MarkdownDocument / Doc) to EPUB 3 e-book files.
|
|
5
|
-
*
|
|
6
|
-
* Content is split into chapters at H1/H2 heading boundaries. Images
|
|
7
|
-
* referenced in the markdown are embedded in the archive when provided
|
|
8
|
-
* via the `images` option.
|
|
9
|
-
*
|
|
10
|
-
* @example
|
|
11
|
-
* ```ts
|
|
12
|
-
* import { markdownDocToEpub, docToEpub } from '@bendyline/squisq-formats/epub';
|
|
13
|
-
*
|
|
14
|
-
* const epub = await markdownDocToEpub(markdownDoc, { title: 'My Book' });
|
|
15
|
-
* const epub2 = await docToEpub(doc, { author: 'Jane Doe' });
|
|
16
|
-
* ```
|
|
17
|
-
*/
|
|
18
|
-
|
|
19
|
-
export { markdownDocToEpub, docToEpub } from './export.js';
|
|
20
|
-
export type { EpubExportOptions } from './export.js';
|
|
@@ -1,373 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Recursive rendered-HTML bundle export.
|
|
3
|
-
*
|
|
4
|
-
* `markdownDocsToHtmlBundle` is the rendered-pipeline counterpart of
|
|
5
|
-
* `markdownDocsToPlainHtmlBundle`. Starting from a single entry markdown
|
|
6
|
-
* file, it walks every relative `[…](other.md)` link, builds a Doc for
|
|
7
|
-
* each visited page via `markdownToDoc`, rewrites cross-doc link URLs
|
|
8
|
-
* inside the Doc tree from `.md` → relative `.html`, then renders each
|
|
9
|
-
* Doc to a SquisqPlayer-backed HTML page sharing one `squisq-player.js`.
|
|
10
|
-
* Images preserve their authored relative paths so per-doc folders
|
|
11
|
-
* (`resume_files/hero.png`, etc.) sit beside the rendered HTML.
|
|
12
|
-
*
|
|
13
|
-
* The rewrite happens on the Doc, *before* JSON serialization, because
|
|
14
|
-
* the rendered HTML embeds the Doc as JSON and SquisqPlayer emits the
|
|
15
|
-
* `<a href>` at runtime — there is no post-render link rewrite hook in
|
|
16
|
-
* the player path.
|
|
17
|
-
*/
|
|
18
|
-
|
|
19
|
-
import JSZip from 'jszip';
|
|
20
|
-
import { parseMarkdown, inferDocumentTitle } from '@bendyline/squisq/markdown';
|
|
21
|
-
import type {
|
|
22
|
-
MarkdownDocument,
|
|
23
|
-
MarkdownBlockNode,
|
|
24
|
-
MarkdownInlineNode,
|
|
25
|
-
HtmlNode,
|
|
26
|
-
} from '@bendyline/squisq/markdown';
|
|
27
|
-
import { markdownToDoc } from '@bendyline/squisq/doc';
|
|
28
|
-
import type { Block, Doc, ThemeRegistry } from '@bendyline/squisq/schemas';
|
|
29
|
-
import { generateExternalHtml } from './htmlTemplate.js';
|
|
30
|
-
import {
|
|
31
|
-
collectLinkRefs,
|
|
32
|
-
isInScope,
|
|
33
|
-
normalizePath,
|
|
34
|
-
posixDirname,
|
|
35
|
-
relativeFrom,
|
|
36
|
-
resolveRelative,
|
|
37
|
-
} from './plainHtmlBundle.js';
|
|
38
|
-
|
|
39
|
-
// ── Public Types ───────────────────────────────────────────────────
|
|
40
|
-
|
|
41
|
-
export interface HtmlBundleOptions {
|
|
42
|
-
/** Entry document path relative to the container root (e.g. `'home.md'`). */
|
|
43
|
-
entryPath: string;
|
|
44
|
-
/** Reads a UTF-8 markdown file from the container. Returns null when absent. */
|
|
45
|
-
readDocument: (path: string) => Promise<string | null>;
|
|
46
|
-
/** Reads a binary asset (image) from the container. Returns null when absent. */
|
|
47
|
-
readBinary: (path: string) => Promise<ArrayBuffer | null>;
|
|
48
|
-
/** SquisqPlayer IIFE bundle. Written once as `squisq-player.js` in the zip. */
|
|
49
|
-
playerScript: string;
|
|
50
|
-
/** Optional document title for the entry. Other pages derive from filename. */
|
|
51
|
-
title?: string;
|
|
52
|
-
/** Theme id applied uniformly to every page. */
|
|
53
|
-
themeId?: string;
|
|
54
|
-
/** Explicit caller-owned registry for non-document custom themes. */
|
|
55
|
-
themeRegistry?: ThemeRegistry;
|
|
56
|
-
/** Rendering mode for every page (default: 'static' — scrollable, link-friendly). */
|
|
57
|
-
mode?: 'slideshow' | 'static';
|
|
58
|
-
/** Maximum recursion depth (default: unlimited; cycles always handled). */
|
|
59
|
-
maxDepth?: number;
|
|
60
|
-
/**
|
|
61
|
-
* Emit the entry doc as `index.html` (preserving its parent directory)
|
|
62
|
-
* instead of `<basename>.html`. Cross-doc links pointing at the entry
|
|
63
|
-
* also rewrite to `index.html`, so a sibling `resume.md → home.md`
|
|
64
|
-
* link doesn't 404 after the rename. Convenient for static-site
|
|
65
|
-
* deploys where the landing page must be named `index.html`.
|
|
66
|
-
* Default: false.
|
|
67
|
-
*/
|
|
68
|
-
entryAsIndex?: boolean;
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
// ── Public API ─────────────────────────────────────────────────────
|
|
72
|
-
|
|
73
|
-
/**
|
|
74
|
-
* Render an entry markdown document and every reachable sibling/child
|
|
75
|
-
* `.md` document it links to, bundled as a single ZIP with one rendered
|
|
76
|
-
* HTML page per doc, per-document asset folders, a shared
|
|
77
|
-
* `squisq-player.js`, and cross-doc `<a href>` references rewritten
|
|
78
|
-
* from `.md` to `.html` inside each Doc's serialized AST.
|
|
79
|
-
*/
|
|
80
|
-
export async function markdownDocsToHtmlBundle(options: HtmlBundleOptions): Promise<Blob> {
|
|
81
|
-
const {
|
|
82
|
-
entryPath,
|
|
83
|
-
readDocument,
|
|
84
|
-
readBinary,
|
|
85
|
-
playerScript,
|
|
86
|
-
title,
|
|
87
|
-
themeId,
|
|
88
|
-
themeRegistry,
|
|
89
|
-
mode = 'static',
|
|
90
|
-
maxDepth = Infinity,
|
|
91
|
-
entryAsIndex = false,
|
|
92
|
-
} = options;
|
|
93
|
-
|
|
94
|
-
const entry = normalizePath(entryPath);
|
|
95
|
-
if (!entry) {
|
|
96
|
-
throw new Error('markdownDocsToHtmlBundle: entryPath is required');
|
|
97
|
-
}
|
|
98
|
-
const scopeRoot = posixDirname(entry);
|
|
99
|
-
// When `entryAsIndex`, the entry doc writes to `<entryDir>/index.html`
|
|
100
|
-
// and every cross-doc link pointing at it rewrites to that path too.
|
|
101
|
-
// Other docs keep the `<basename>.html` convention.
|
|
102
|
-
const entryHtmlPath = entryAsIndex
|
|
103
|
-
? scopeRoot
|
|
104
|
-
? `${scopeRoot}/index.html`
|
|
105
|
-
: 'index.html'
|
|
106
|
-
: entry.slice(0, -3) + '.html';
|
|
107
|
-
const htmlPathFor = (mdPath: string): string =>
|
|
108
|
-
mdPath === entry ? entryHtmlPath : mdPath.slice(0, -3) + '.html';
|
|
109
|
-
|
|
110
|
-
const zip = new JSZip();
|
|
111
|
-
zip.file('squisq-player.js', playerScript);
|
|
112
|
-
|
|
113
|
-
const visited = new Set<string>();
|
|
114
|
-
const queue: Array<{ path: string; depth: number }> = [{ path: entry, depth: 0 }];
|
|
115
|
-
|
|
116
|
-
while (queue.length > 0) {
|
|
117
|
-
const { path, depth } = queue.shift()!;
|
|
118
|
-
if (visited.has(path)) continue;
|
|
119
|
-
visited.add(path);
|
|
120
|
-
|
|
121
|
-
const source = await readDocument(path);
|
|
122
|
-
if (source === null) {
|
|
123
|
-
throw new Error(`markdownDocsToHtmlBundle: failed to read "${path}"`);
|
|
124
|
-
}
|
|
125
|
-
const mdDoc = parseMarkdown(source);
|
|
126
|
-
|
|
127
|
-
const docDir = posixDirname(path);
|
|
128
|
-
const linkMap = new Map<string, string>();
|
|
129
|
-
for (const raw of collectLinkRefs(mdDoc)) {
|
|
130
|
-
const parsed = parseInternalRef(raw);
|
|
131
|
-
if (!parsed) continue;
|
|
132
|
-
const resolved = resolveRelative(docDir, parsed.path);
|
|
133
|
-
if (resolved === null) continue;
|
|
134
|
-
if (!isInScope(resolved, scopeRoot)) continue;
|
|
135
|
-
if (!resolved.toLowerCase().endsWith('.md')) continue;
|
|
136
|
-
|
|
137
|
-
const htmlTarget = htmlPathFor(resolved);
|
|
138
|
-
const relHref = relativeFrom(docDir, htmlTarget) + parsed.fragment;
|
|
139
|
-
linkMap.set(raw, relHref);
|
|
140
|
-
|
|
141
|
-
if (depth + 1 <= maxDepth && !visited.has(resolved)) {
|
|
142
|
-
queue.push({ path: resolved, depth: depth + 1 });
|
|
143
|
-
}
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
// Gather and stash images per doc. Same convention as the plain
|
|
147
|
-
// bundle: keep authored relative paths so `<img src>` paths inside
|
|
148
|
-
// the Doc still resolve after unzip. Missing assets are non-fatal.
|
|
149
|
-
const imageRewriteMap = new Map<string, string>();
|
|
150
|
-
for (const authored of collectImageRefs(mdDoc)) {
|
|
151
|
-
if (/^[a-z][a-z0-9+.-]*:/i.test(authored) || authored.startsWith('//')) continue;
|
|
152
|
-
const clean = authored.replace(/[#?].*$/, '');
|
|
153
|
-
const resolvedImg = resolveRelative(docDir, clean);
|
|
154
|
-
if (resolvedImg === null) continue;
|
|
155
|
-
const data = await readBinary(resolvedImg);
|
|
156
|
-
if (!data) continue;
|
|
157
|
-
const safe = sanitizeZipPath(resolvedImg);
|
|
158
|
-
if (!safe) continue;
|
|
159
|
-
zip.file(safe, data);
|
|
160
|
-
imageRewriteMap.set(authored, relativeFrom(docDir, resolvedImg));
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
// markdownToDoc embeds the markdown AST in `block.contents`, so the
|
|
164
|
-
// link rewrite must walk that tree on every Doc before we serialize
|
|
165
|
-
// it into the rendered HTML.
|
|
166
|
-
let doc = markdownToDoc(mdDoc);
|
|
167
|
-
if (linkMap.size > 0) {
|
|
168
|
-
doc = rewriteDocLinks(doc, linkMap);
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
const pageTitle = depth === 0 ? (title ?? titleForDoc(path, mdDoc)) : titleForDoc(path, mdDoc);
|
|
172
|
-
const htmlPath = htmlPathFor(path);
|
|
173
|
-
const playerScriptPath = relativeFrom(docDir, 'squisq-player.js');
|
|
174
|
-
|
|
175
|
-
const html = generateExternalHtml(doc, {
|
|
176
|
-
playerScriptPath,
|
|
177
|
-
// Convert Map → plain record for the template (which expects a
|
|
178
|
-
// record). The renderer reads this map at runtime to swap image
|
|
179
|
-
// sources before rendering each block.
|
|
180
|
-
imagePathMap:
|
|
181
|
-
imageRewriteMap.size > 0 ? Object.fromEntries(imageRewriteMap.entries()) : undefined,
|
|
182
|
-
mode,
|
|
183
|
-
title: pageTitle,
|
|
184
|
-
themeId,
|
|
185
|
-
themeRegistry,
|
|
186
|
-
});
|
|
187
|
-
zip.file(htmlPath, html);
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
return zip.generateAsync({
|
|
191
|
-
type: 'blob',
|
|
192
|
-
compression: 'DEFLATE',
|
|
193
|
-
compressionOptions: { level: 6 },
|
|
194
|
-
});
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
// ── Link rewriting ─────────────────────────────────────────────────
|
|
198
|
-
|
|
199
|
-
/**
|
|
200
|
-
* Return a deep-cloned Doc with every `[text](url)` link URL inside
|
|
201
|
-
* `block.contents` rewritten through `linkMap`, including raw HTML
|
|
202
|
-
* `<a href="…">` tags carried inside `htmlBlock`/`htmlInline` nodes.
|
|
203
|
-
* Authoring keys that aren't in the map pass through untouched.
|
|
204
|
-
*/
|
|
205
|
-
function rewriteDocLinks(doc: Doc, linkMap: Map<string, string>): Doc {
|
|
206
|
-
if (linkMap.size === 0) return doc;
|
|
207
|
-
|
|
208
|
-
function rewriteHtmlNodes(nodes: HtmlNode[]): HtmlNode[] {
|
|
209
|
-
return nodes.map((n) => {
|
|
210
|
-
if (n.type !== 'htmlElement') return n;
|
|
211
|
-
const tag = n.tagName.toLowerCase();
|
|
212
|
-
const nextAttrs =
|
|
213
|
-
tag === 'a' && typeof n.attributes.href === 'string' && linkMap.has(n.attributes.href)
|
|
214
|
-
? { ...n.attributes, href: linkMap.get(n.attributes.href)! }
|
|
215
|
-
: n.attributes;
|
|
216
|
-
return {
|
|
217
|
-
...n,
|
|
218
|
-
attributes: nextAttrs,
|
|
219
|
-
children: rewriteHtmlNodes(n.children),
|
|
220
|
-
};
|
|
221
|
-
});
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
function rewriteInline(node: MarkdownInlineNode): MarkdownInlineNode {
|
|
225
|
-
if (node.type === 'link') {
|
|
226
|
-
const nextUrl = linkMap.get(node.url) ?? node.url;
|
|
227
|
-
return {
|
|
228
|
-
...node,
|
|
229
|
-
url: nextUrl,
|
|
230
|
-
children: node.children.map(rewriteInline),
|
|
231
|
-
};
|
|
232
|
-
}
|
|
233
|
-
if (node.type === 'htmlInline') {
|
|
234
|
-
const withChildren = node as MarkdownInlineNode & { htmlChildren?: HtmlNode[] };
|
|
235
|
-
if (Array.isArray(withChildren.htmlChildren)) {
|
|
236
|
-
return {
|
|
237
|
-
...node,
|
|
238
|
-
htmlChildren: rewriteHtmlNodes(withChildren.htmlChildren),
|
|
239
|
-
} as MarkdownInlineNode;
|
|
240
|
-
}
|
|
241
|
-
return node;
|
|
242
|
-
}
|
|
243
|
-
if ('children' in node && Array.isArray((node as { children?: unknown }).children)) {
|
|
244
|
-
const children = (node as { children: MarkdownInlineNode[] }).children.map(rewriteInline);
|
|
245
|
-
return { ...node, children } as MarkdownInlineNode;
|
|
246
|
-
}
|
|
247
|
-
return node;
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
function rewriteBlockNode(node: MarkdownBlockNode): MarkdownBlockNode {
|
|
251
|
-
if (node.type === 'htmlBlock') {
|
|
252
|
-
const withChildren = node as MarkdownBlockNode & { htmlChildren?: HtmlNode[] };
|
|
253
|
-
if (Array.isArray(withChildren.htmlChildren)) {
|
|
254
|
-
return {
|
|
255
|
-
...node,
|
|
256
|
-
htmlChildren: rewriteHtmlNodes(withChildren.htmlChildren),
|
|
257
|
-
} as MarkdownBlockNode;
|
|
258
|
-
}
|
|
259
|
-
return node;
|
|
260
|
-
}
|
|
261
|
-
if ('children' in node && Array.isArray((node as { children?: unknown }).children)) {
|
|
262
|
-
const rewritten = (node as { children: unknown[] }).children.map((c) => {
|
|
263
|
-
const child = c as { type?: string };
|
|
264
|
-
if (child && typeof child === 'object' && 'type' in child) {
|
|
265
|
-
if (isBlockNodeType(child.type as string)) {
|
|
266
|
-
return rewriteBlockNode(c as MarkdownBlockNode);
|
|
267
|
-
}
|
|
268
|
-
return rewriteInline(c as MarkdownInlineNode);
|
|
269
|
-
}
|
|
270
|
-
return c;
|
|
271
|
-
});
|
|
272
|
-
return { ...node, children: rewritten } as MarkdownBlockNode;
|
|
273
|
-
}
|
|
274
|
-
return node;
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
function rewriteBlock(block: Block): Block {
|
|
278
|
-
const next: Block = { ...block };
|
|
279
|
-
if (Array.isArray(block.contents)) {
|
|
280
|
-
next.contents = block.contents.map(rewriteBlockNode);
|
|
281
|
-
}
|
|
282
|
-
if (Array.isArray(block.children)) {
|
|
283
|
-
next.children = block.children.map(rewriteBlock);
|
|
284
|
-
}
|
|
285
|
-
return next;
|
|
286
|
-
}
|
|
287
|
-
|
|
288
|
-
return {
|
|
289
|
-
...doc,
|
|
290
|
-
blocks: doc.blocks.map(rewriteBlock),
|
|
291
|
-
};
|
|
292
|
-
}
|
|
293
|
-
|
|
294
|
-
/** Block-level markdown node `type` discriminator. Inline node types are
|
|
295
|
-
* everything else (text, link, emphasis, strong, image, etc.). Keeps
|
|
296
|
-
* the recursive rewrite from mistakenly applying block logic to inline
|
|
297
|
-
* link descendants. */
|
|
298
|
-
function isBlockNodeType(t: string): boolean {
|
|
299
|
-
return (
|
|
300
|
-
t === 'paragraph' ||
|
|
301
|
-
t === 'heading' ||
|
|
302
|
-
t === 'list' ||
|
|
303
|
-
t === 'listItem' ||
|
|
304
|
-
t === 'blockquote' ||
|
|
305
|
-
t === 'code' ||
|
|
306
|
-
t === 'thematicBreak' ||
|
|
307
|
-
t === 'table' ||
|
|
308
|
-
t === 'tableRow' ||
|
|
309
|
-
t === 'tableCell' ||
|
|
310
|
-
t === 'htmlBlock'
|
|
311
|
-
);
|
|
312
|
-
}
|
|
313
|
-
|
|
314
|
-
// ── Helpers (kept local to avoid widening plainHtmlBundle's API) ───
|
|
315
|
-
|
|
316
|
-
interface ParsedRef {
|
|
317
|
-
path: string;
|
|
318
|
-
fragment: string;
|
|
319
|
-
}
|
|
320
|
-
|
|
321
|
-
function parseInternalRef(url: string): ParsedRef | null {
|
|
322
|
-
if (!url) return null;
|
|
323
|
-
if (url.startsWith('#')) return null;
|
|
324
|
-
if (/^[a-z][a-z0-9+.-]*:/i.test(url) || url.startsWith('//') || url.startsWith('/')) {
|
|
325
|
-
return null;
|
|
326
|
-
}
|
|
327
|
-
const hashIdx = url.indexOf('#');
|
|
328
|
-
const queryIdx = url.indexOf('?');
|
|
329
|
-
const cut =
|
|
330
|
-
hashIdx >= 0 && queryIdx >= 0 ? Math.min(hashIdx, queryIdx) : hashIdx >= 0 ? hashIdx : queryIdx;
|
|
331
|
-
const path = cut >= 0 ? url.slice(0, cut) : url;
|
|
332
|
-
const fragment = hashIdx >= 0 ? url.slice(hashIdx) : '';
|
|
333
|
-
return { path, fragment };
|
|
334
|
-
}
|
|
335
|
-
|
|
336
|
-
function collectImageRefs(doc: MarkdownDocument): Set<string> {
|
|
337
|
-
const refs = new Set<string>();
|
|
338
|
-
function visitHtml(nodes: HtmlNode[]): void {
|
|
339
|
-
for (const n of nodes) {
|
|
340
|
-
if (n.type !== 'htmlElement') continue;
|
|
341
|
-
if (n.tagName.toLowerCase() === 'img') {
|
|
342
|
-
const src = n.attributes.src;
|
|
343
|
-
if (typeof src === 'string' && src) refs.add(src);
|
|
344
|
-
}
|
|
345
|
-
visitHtml(n.children);
|
|
346
|
-
}
|
|
347
|
-
}
|
|
348
|
-
function visit(node: unknown): void {
|
|
349
|
-
if (!node || typeof node !== 'object') return;
|
|
350
|
-
const n = node as Record<string, unknown>;
|
|
351
|
-
if (n.type === 'image' && typeof n.url === 'string' && n.url) refs.add(n.url);
|
|
352
|
-
if ((n.type === 'htmlBlock' || n.type === 'htmlInline') && Array.isArray(n.htmlChildren)) {
|
|
353
|
-
visitHtml(n.htmlChildren as HtmlNode[]);
|
|
354
|
-
}
|
|
355
|
-
if (Array.isArray(n.children)) for (const c of n.children) visit(c);
|
|
356
|
-
}
|
|
357
|
-
for (const child of doc.children) visit(child);
|
|
358
|
-
return refs;
|
|
359
|
-
}
|
|
360
|
-
|
|
361
|
-
function sanitizeZipPath(path: string): string | null {
|
|
362
|
-
const normalized = path.replace(/\\/g, '/').replace(/^\/+/, '');
|
|
363
|
-
if (!normalized) return null;
|
|
364
|
-
if (normalized.split('/').some((seg) => seg === '..')) return null;
|
|
365
|
-
return normalized;
|
|
366
|
-
}
|
|
367
|
-
|
|
368
|
-
function titleForDoc(path: string, mdDoc: MarkdownDocument): string {
|
|
369
|
-
const inferred = inferDocumentTitle(mdDoc);
|
|
370
|
-
if (inferred) return inferred;
|
|
371
|
-
const base = path.split('/').pop() ?? path;
|
|
372
|
-
return base.replace(/\.md$/i, '');
|
|
373
|
-
}
|