@bendyline/squisq-formats 1.2.3 → 1.3.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.
Files changed (166) hide show
  1. package/dist/chunk-33YRFXZZ.js +1187 -0
  2. package/dist/chunk-33YRFXZZ.js.map +1 -0
  3. package/dist/{chunk-EUQQYBZZ.js → chunk-ERZ627GR.js} +4 -4
  4. package/dist/chunk-ERZ627GR.js.map +1 -0
  5. package/dist/{chunk-MEZF76JA.js → chunk-UDS45KUJ.js} +68 -25
  6. package/dist/chunk-UDS45KUJ.js.map +1 -0
  7. package/dist/{chunk-NGWHV77G.js → chunk-VN2KEOYB.js} +7 -6
  8. package/dist/chunk-VN2KEOYB.js.map +1 -0
  9. package/dist/container/index.d.ts +7 -5
  10. package/dist/container/index.js +39 -70
  11. package/dist/container/index.js.map +1 -1
  12. package/dist/docx/index.d.ts +128 -15
  13. package/dist/docx/index.js +17 -22
  14. package/dist/docx/index.js.map +1 -1
  15. package/dist/epub/index.d.ts +66 -11
  16. package/dist/epub/index.js +10 -18
  17. package/dist/epub/index.js.map +1 -1
  18. package/dist/html/index.d.ts +278 -9
  19. package/dist/html/index.js +26 -137
  20. package/dist/html/index.js.map +1 -1
  21. package/dist/index.d.ts +9 -38
  22. package/dist/index.js +62 -37
  23. package/dist/index.js.map +1 -1
  24. package/dist/ooxml/index.d.ts +353 -14
  25. package/dist/ooxml/index.js +116 -22
  26. package/dist/ooxml/index.js.map +1 -1
  27. package/dist/pdf/index.d.ts +143 -17
  28. package/dist/pdf/index.js +16 -25
  29. package/dist/pdf/index.js.map +1 -1
  30. package/dist/pptx/index.d.ts +83 -8
  31. package/dist/pptx/index.js +14 -34
  32. package/dist/pptx/index.js.map +1 -1
  33. package/dist/xlsx/index.d.ts +12 -9
  34. package/dist/xlsx/index.js +12 -44
  35. package/dist/xlsx/index.js.map +1 -1
  36. package/package.json +3 -2
  37. package/src/__tests__/html.test.ts +82 -4
  38. package/src/__tests__/pdfExport.test.ts +1 -1
  39. package/src/__tests__/plainHtml.test.ts +372 -0
  40. package/src/__tests__/plainHtmlBundle.test.ts +235 -0
  41. package/src/docx/export.ts +163 -22
  42. package/src/epub/export.ts +3 -3
  43. package/src/html/docsHtmlBundle.ts +369 -0
  44. package/src/html/htmlTemplate.ts +40 -0
  45. package/src/html/index.ts +32 -3
  46. package/src/html/plainHtml.ts +736 -0
  47. package/src/html/plainHtmlBundle.ts +419 -0
  48. package/src/pptx/export.ts +10 -9
  49. package/dist/__tests__/container.test.d.ts +0 -8
  50. package/dist/__tests__/container.test.d.ts.map +0 -1
  51. package/dist/__tests__/container.test.js +0 -147
  52. package/dist/__tests__/container.test.js.map +0 -1
  53. package/dist/__tests__/docxExport.test.d.ts +0 -8
  54. package/dist/__tests__/docxExport.test.d.ts.map +0 -1
  55. package/dist/__tests__/docxExport.test.js +0 -383
  56. package/dist/__tests__/docxExport.test.js.map +0 -1
  57. package/dist/__tests__/docxImport.test.d.ts +0 -8
  58. package/dist/__tests__/docxImport.test.d.ts.map +0 -1
  59. package/dist/__tests__/docxImport.test.js +0 -309
  60. package/dist/__tests__/docxImport.test.js.map +0 -1
  61. package/dist/__tests__/epub.test.d.ts +0 -8
  62. package/dist/__tests__/epub.test.d.ts.map +0 -1
  63. package/dist/__tests__/epub.test.js +0 -472
  64. package/dist/__tests__/epub.test.js.map +0 -1
  65. package/dist/__tests__/html.test.d.ts +0 -8
  66. package/dist/__tests__/html.test.d.ts.map +0 -1
  67. package/dist/__tests__/html.test.js +0 -243
  68. package/dist/__tests__/html.test.js.map +0 -1
  69. package/dist/__tests__/ooxml.test.d.ts +0 -5
  70. package/dist/__tests__/ooxml.test.d.ts.map +0 -1
  71. package/dist/__tests__/ooxml.test.js +0 -152
  72. package/dist/__tests__/ooxml.test.js.map +0 -1
  73. package/dist/__tests__/pdfExport.test.d.ts +0 -8
  74. package/dist/__tests__/pdfExport.test.d.ts.map +0 -1
  75. package/dist/__tests__/pdfExport.test.js +0 -275
  76. package/dist/__tests__/pdfExport.test.js.map +0 -1
  77. package/dist/__tests__/pdfImport.test.d.ts +0 -11
  78. package/dist/__tests__/pdfImport.test.d.ts.map +0 -1
  79. package/dist/__tests__/pdfImport.test.js +0 -254
  80. package/dist/__tests__/pdfImport.test.js.map +0 -1
  81. package/dist/__tests__/roundTrip.test.d.ts +0 -7
  82. package/dist/__tests__/roundTrip.test.d.ts.map +0 -1
  83. package/dist/__tests__/roundTrip.test.js +0 -173
  84. package/dist/__tests__/roundTrip.test.js.map +0 -1
  85. package/dist/chunk-EUQQYBZZ.js.map +0 -1
  86. package/dist/chunk-MEZF76JA.js.map +0 -1
  87. package/dist/chunk-NGWHV77G.js.map +0 -1
  88. package/dist/chunk-UM5V2XZG.js +0 -242
  89. package/dist/chunk-UM5V2XZG.js.map +0 -1
  90. package/dist/container/index.d.ts.map +0 -1
  91. package/dist/docx/export.d.ts +0 -69
  92. package/dist/docx/export.d.ts.map +0 -1
  93. package/dist/docx/export.js +0 -854
  94. package/dist/docx/export.js.map +0 -1
  95. package/dist/docx/import.d.ts +0 -65
  96. package/dist/docx/import.d.ts.map +0 -1
  97. package/dist/docx/import.js +0 -779
  98. package/dist/docx/import.js.map +0 -1
  99. package/dist/docx/index.d.ts.map +0 -1
  100. package/dist/docx/styles.d.ts +0 -68
  101. package/dist/docx/styles.d.ts.map +0 -1
  102. package/dist/docx/styles.js +0 -125
  103. package/dist/docx/styles.js.map +0 -1
  104. package/dist/epub/export.d.ts +0 -72
  105. package/dist/epub/export.d.ts.map +0 -1
  106. package/dist/epub/export.js +0 -698
  107. package/dist/epub/export.js.map +0 -1
  108. package/dist/epub/index.d.ts.map +0 -1
  109. package/dist/html/htmlTemplate.d.ts +0 -70
  110. package/dist/html/htmlTemplate.d.ts.map +0 -1
  111. package/dist/html/htmlTemplate.js +0 -239
  112. package/dist/html/htmlTemplate.js.map +0 -1
  113. package/dist/html/imageUtils.d.ts +0 -29
  114. package/dist/html/imageUtils.d.ts.map +0 -1
  115. package/dist/html/imageUtils.js +0 -63
  116. package/dist/html/imageUtils.js.map +0 -1
  117. package/dist/html/index.d.ts.map +0 -1
  118. package/dist/index.d.ts.map +0 -1
  119. package/dist/ooxml/index.d.ts.map +0 -1
  120. package/dist/ooxml/namespaces.d.ts +0 -77
  121. package/dist/ooxml/namespaces.d.ts.map +0 -1
  122. package/dist/ooxml/namespaces.js +0 -104
  123. package/dist/ooxml/namespaces.js.map +0 -1
  124. package/dist/ooxml/reader.d.ts +0 -52
  125. package/dist/ooxml/reader.d.ts.map +0 -1
  126. package/dist/ooxml/reader.js +0 -190
  127. package/dist/ooxml/reader.js.map +0 -1
  128. package/dist/ooxml/types.d.ts +0 -82
  129. package/dist/ooxml/types.d.ts.map +0 -1
  130. package/dist/ooxml/types.js +0 -9
  131. package/dist/ooxml/types.js.map +0 -1
  132. package/dist/ooxml/writer.d.ts +0 -71
  133. package/dist/ooxml/writer.d.ts.map +0 -1
  134. package/dist/ooxml/writer.js +0 -200
  135. package/dist/ooxml/writer.js.map +0 -1
  136. package/dist/ooxml/xmlUtils.d.ts +0 -76
  137. package/dist/ooxml/xmlUtils.d.ts.map +0 -1
  138. package/dist/ooxml/xmlUtils.js +0 -108
  139. package/dist/ooxml/xmlUtils.js.map +0 -1
  140. package/dist/pdf/export.d.ts +0 -56
  141. package/dist/pdf/export.d.ts.map +0 -1
  142. package/dist/pdf/export.js +0 -768
  143. package/dist/pdf/export.js.map +0 -1
  144. package/dist/pdf/import.d.ts +0 -94
  145. package/dist/pdf/import.d.ts.map +0 -1
  146. package/dist/pdf/import.js +0 -796
  147. package/dist/pdf/import.js.map +0 -1
  148. package/dist/pdf/index.d.ts.map +0 -1
  149. package/dist/pdf/styles.d.ts +0 -142
  150. package/dist/pdf/styles.d.ts.map +0 -1
  151. package/dist/pdf/styles.js +0 -145
  152. package/dist/pdf/styles.js.map +0 -1
  153. package/dist/pptx/export.d.ts +0 -75
  154. package/dist/pptx/export.d.ts.map +0 -1
  155. package/dist/pptx/export.js +0 -722
  156. package/dist/pptx/export.js.map +0 -1
  157. package/dist/pptx/index.d.ts.map +0 -1
  158. package/dist/pptx/styles.d.ts +0 -54
  159. package/dist/pptx/styles.d.ts.map +0 -1
  160. package/dist/pptx/styles.js +0 -73
  161. package/dist/pptx/styles.js.map +0 -1
  162. package/dist/pptx/templates.d.ts +0 -24
  163. package/dist/pptx/templates.d.ts.map +0 -1
  164. package/dist/pptx/templates.js +0 -158
  165. package/dist/pptx/templates.js.map +0 -1
  166. package/dist/xlsx/index.d.ts.map +0 -1
@@ -0,0 +1,419 @@
1
+ /**
2
+ * Recursive plain-HTML bundle export.
3
+ *
4
+ * `markdownDocsToPlainHtmlBundle` starts from a single entry markdown
5
+ * file, walks its relative `[…](other.md)` links, recursively pulls in
6
+ * any sibling/child documents (scope-limited to the entry doc's
7
+ * directory tree), renders every visited file via
8
+ * `markdownDocToPlainHtml`, rewrites cross-doc references from `.md`
9
+ * to `.html`, and ships everything as a single ZIP.
10
+ *
11
+ * The function is provider-agnostic — callers pass `readDocument` and
12
+ * `readBinary` callbacks that resolve relative paths against whatever
13
+ * storage they use (`FileSystemContentContainer`, `MemoryContent-
14
+ * Container`, in-memory map for tests, …). Failure to read any
15
+ * discovered file aborts the whole export with a thrown error.
16
+ */
17
+
18
+ import JSZip from 'jszip';
19
+ import { parseMarkdown, inferDocumentTitle } from '@bendyline/squisq/markdown';
20
+ import type { MarkdownDocument, HtmlNode } from '@bendyline/squisq/markdown';
21
+ import type { Theme } from '@bendyline/squisq/schemas';
22
+ import { resolveTheme } from '@bendyline/squisq/schemas';
23
+ import { markdownDocToPlainHtml } from './plainHtml.js';
24
+
25
+ // ── Public Types ───────────────────────────────────────────────────
26
+
27
+ export interface PlainHtmlBundleOptions {
28
+ /** Entry document path relative to the container root (e.g. `'home.md'`). */
29
+ entryPath: string;
30
+ /** Reads a UTF-8 markdown file from the container. Returns null when absent. */
31
+ readDocument: (path: string) => Promise<string | null>;
32
+ /** Reads a binary asset (image) from the container. Returns null when absent. */
33
+ readBinary: (path: string) => Promise<ArrayBuffer | null>;
34
+ /** Optional document title for the entry. Others derive from filename. */
35
+ title?: string;
36
+ /** Optional theme applied uniformly to every page. Overrides {@link themeId}. */
37
+ theme?: Theme;
38
+ /**
39
+ * Optional theme id (e.g. `'warm-earth'`, `'gezellig'`). Resolved via
40
+ * `resolveTheme` and applied to every page. Convenient for callers
41
+ * that track themes by id (like the host export dialog) without
42
+ * having to resolve to a `Theme` object first. When both `theme` and
43
+ * `themeId` are supplied, `theme` wins.
44
+ */
45
+ themeId?: string;
46
+ /** Maximum recursion depth (default: unlimited; cycles always handled). */
47
+ maxDepth?: number;
48
+ /**
49
+ * Emit the entry doc as `index.html` (preserving its parent directory)
50
+ * instead of `<basename>.html`. Cross-doc links pointing at the entry
51
+ * also rewrite to `index.html`, so a sibling `resume.md → home.md`
52
+ * link doesn't 404 after the rename. Convenient for static-site
53
+ * deploys where the landing page must be named `index.html`.
54
+ * Default: false.
55
+ */
56
+ entryAsIndex?: boolean;
57
+ }
58
+
59
+ // ── Public API ─────────────────────────────────────────────────────
60
+
61
+ /**
62
+ * Render an entry markdown document and every reachable sibling/child
63
+ * `.md` document it links to, bundled as a single ZIP with plain-HTML
64
+ * pages, per-document asset folders, and cross-doc `<a href>`
65
+ * references rewritten from `.md` to `.html`.
66
+ */
67
+ export async function markdownDocsToPlainHtmlBundle(
68
+ options: PlainHtmlBundleOptions,
69
+ ): Promise<Blob> {
70
+ const {
71
+ entryPath,
72
+ readDocument,
73
+ readBinary,
74
+ title,
75
+ theme,
76
+ themeId,
77
+ maxDepth = Infinity,
78
+ entryAsIndex = false,
79
+ } = options;
80
+ const resolvedTheme = theme ?? (themeId ? resolveTheme(themeId) : undefined);
81
+ const entry = normalizePath(entryPath);
82
+ if (!entry) {
83
+ throw new Error('markdownDocsToPlainHtmlBundle: entryPath is required');
84
+ }
85
+ const scopeRoot = posixDirname(entry); // '' for root-level files
86
+ // When `entryAsIndex`, the entry doc writes to `<entryDir>/index.html`
87
+ // and every cross-doc link pointing at it rewrites to that path too.
88
+ // Other docs keep the `<basename>.html` convention.
89
+ const entryHtmlPath = entryAsIndex
90
+ ? scopeRoot
91
+ ? `${scopeRoot}/index.html`
92
+ : 'index.html'
93
+ : entry.slice(0, -3) + '.html';
94
+ const htmlPathFor = (mdPath: string): string =>
95
+ mdPath === entry ? entryHtmlPath : mdPath.slice(0, -3) + '.html';
96
+
97
+ const zip = new JSZip();
98
+ const visited = new Set<string>();
99
+ const queue: Array<{ path: string; depth: number }> = [{ path: entry, depth: 0 }];
100
+
101
+ while (queue.length > 0) {
102
+ const { path, depth } = queue.shift()!;
103
+ if (visited.has(path)) continue;
104
+ visited.add(path);
105
+
106
+ const source = await readDocument(path);
107
+ if (source === null) {
108
+ throw new Error(`markdownDocsToPlainHtmlBundle: failed to read "${path}"`);
109
+ }
110
+ const mdDoc = parseMarkdown(source);
111
+
112
+ // Discover this doc's relative .md links — both for enqueueing new
113
+ // targets and for building the per-doc link rewrite map. We resolve
114
+ // each link to a canonical container-relative path so the same doc
115
+ // referenced two different ways (./resume.md vs resume.md) is
116
+ // visited once and rewritten consistently.
117
+ const docDir = posixDirname(path);
118
+ const linkMap = new Map<string, string>();
119
+ for (const raw of collectLinkRefs(mdDoc)) {
120
+ const parsed = parseLinkRef(raw);
121
+ if (!parsed) continue;
122
+ const resolved = resolveRelative(docDir, parsed.path);
123
+ if (resolved === null) continue; // escaped via ..
124
+ if (!isInScope(resolved, scopeRoot)) continue;
125
+ if (!resolved.toLowerCase().endsWith('.md')) continue;
126
+
127
+ // Compute the .html replacement relative to the current doc so
128
+ // the rewritten href stays local (e.g. `subdir/notes.html`, not
129
+ // an absolute container path). `htmlPathFor` also handles the
130
+ // entry-as-index case so a sibling linking to the entry lands
131
+ // on `index.html` after rename.
132
+ const htmlTarget = htmlPathFor(resolved);
133
+ const relHref = relativeFrom(docDir, htmlTarget) + parsed.fragment;
134
+ linkMap.set(raw, relHref);
135
+
136
+ if (depth + 1 <= maxDepth && !visited.has(resolved)) {
137
+ queue.push({ path: resolved, depth: depth + 1 });
138
+ }
139
+ }
140
+
141
+ // Walk images, fetch each one, write at the original relative path
142
+ // resolved against the container root (so `resume_files/hero.png`
143
+ // sits next to `resume.html` in the zip). Failures here are *not*
144
+ // fatal — images can be missing without breaking the document
145
+ // structure — but we surface them as console warnings.
146
+ const images = await readImagesForDoc(mdDoc, docDir, readBinary);
147
+ for (const [, { data, zipPath }] of images) {
148
+ const safe = sanitizeZipPath(zipPath);
149
+ if (!safe) continue;
150
+ zip.file(safe, data);
151
+ }
152
+
153
+ // Render the document. The image rewrite map keys are the URLs
154
+ // exactly as authored (so `markdownDocToPlainHtml` can match them
155
+ // verbatim), and the values are paths relative to the rendered
156
+ // doc's own location — so `<img src>` works after unzip without
157
+ // any post-processing.
158
+ const imageRewriteMap = new Map<string, string>();
159
+ for (const [authored, { zipPath }] of images) {
160
+ imageRewriteMap.set(authored, relativeFrom(docDir, zipPath));
161
+ }
162
+
163
+ const docTitle = depth === 0 ? title : undefined;
164
+ const html = markdownDocToPlainHtml(mdDoc, {
165
+ title: docTitle ?? titleForFilename(path, mdDoc),
166
+ images: imageRewriteMap,
167
+ links: linkMap,
168
+ theme: resolvedTheme,
169
+ });
170
+
171
+ const htmlPath = htmlPathFor(path);
172
+ zip.file(htmlPath, html);
173
+ }
174
+
175
+ return zip.generateAsync({
176
+ type: 'blob',
177
+ compression: 'DEFLATE',
178
+ compressionOptions: { level: 6 },
179
+ });
180
+ }
181
+
182
+ // ── Link discovery ─────────────────────────────────────────────────
183
+
184
+ /**
185
+ * Collect every `<a>`-style link URL referenced in a document. Markdown
186
+ * `link` nodes plus any raw HTML `<a href>` tags. Returns the raw URLs
187
+ * as authored, so callers can use them as both the linkMap *key* and
188
+ * the basis for resolution.
189
+ */
190
+ export function collectLinkRefs(doc: MarkdownDocument): Set<string> {
191
+ const refs = new Set<string>();
192
+
193
+ function visitHtml(nodes: HtmlNode[]): void {
194
+ for (const n of nodes) {
195
+ if (n.type !== 'htmlElement') continue;
196
+ if (n.tagName.toLowerCase() === 'a') {
197
+ const href = n.attributes.href;
198
+ if (typeof href === 'string' && href) refs.add(href);
199
+ }
200
+ visitHtml(n.children);
201
+ }
202
+ }
203
+
204
+ function visit(node: unknown): void {
205
+ if (!node || typeof node !== 'object') return;
206
+ const n = node as Record<string, unknown>;
207
+ if (n.type === 'link' && typeof n.url === 'string' && n.url) {
208
+ refs.add(n.url);
209
+ }
210
+ if ((n.type === 'htmlBlock' || n.type === 'htmlInline') && Array.isArray(n.htmlChildren)) {
211
+ visitHtml(n.htmlChildren as HtmlNode[]);
212
+ }
213
+ if (Array.isArray(n.children)) {
214
+ for (const child of n.children) visit(child);
215
+ }
216
+ }
217
+
218
+ for (const child of doc.children) visit(child);
219
+ return refs;
220
+ }
221
+
222
+ function collectImageRefs(doc: MarkdownDocument): Set<string> {
223
+ const refs = new Set<string>();
224
+ function visitHtml(nodes: HtmlNode[]): void {
225
+ for (const n of nodes) {
226
+ if (n.type !== 'htmlElement') continue;
227
+ const tag = n.tagName.toLowerCase();
228
+ // <img>/<video>/<audio>/<source> all reference media via `src`;
229
+ // we feed them into the same `images` map (effectively a generic
230
+ // media map — see header comment) so the export pipeline rewrites
231
+ // and bundles each one the same way.
232
+ if (tag === 'img' || tag === 'video' || tag === 'audio' || tag === 'source') {
233
+ const src = n.attributes.src;
234
+ if (typeof src === 'string' && src) refs.add(src);
235
+ }
236
+ if (tag === 'video' || tag === 'audio') {
237
+ const poster = n.attributes.poster;
238
+ if (typeof poster === 'string' && poster) refs.add(poster);
239
+ }
240
+ visitHtml(n.children);
241
+ }
242
+ }
243
+ function visit(node: unknown): void {
244
+ if (!node || typeof node !== 'object') return;
245
+ const n = node as Record<string, unknown>;
246
+ if (n.type === 'image' && typeof n.url === 'string' && n.url) refs.add(n.url);
247
+ if ((n.type === 'htmlBlock' || n.type === 'htmlInline') && Array.isArray(n.htmlChildren)) {
248
+ visitHtml(n.htmlChildren as HtmlNode[]);
249
+ }
250
+ if (Array.isArray(n.children)) for (const c of n.children) visit(c);
251
+ }
252
+ for (const child of doc.children) visit(child);
253
+ return refs;
254
+ }
255
+
256
+ interface ParsedLinkRef {
257
+ /** Pathname portion before any `#` or `?`. */
258
+ path: string;
259
+ /** `#fragment` suffix (with leading `#`) or empty string. */
260
+ fragment: string;
261
+ }
262
+
263
+ /**
264
+ * Split an authored URL into path + fragment, returning null for
265
+ * external / non-document references (http(s), mailto, data, blob,
266
+ * absolute paths, fragment-only). The fragment is preserved so a
267
+ * link like `resume.md#experience` rewrites cleanly to
268
+ * `resume.html#experience`.
269
+ */
270
+ function parseLinkRef(url: string): ParsedLinkRef | null {
271
+ if (!url) return null;
272
+ if (url.startsWith('#')) return null; // intra-doc anchor
273
+ if (
274
+ /^[a-z][a-z0-9+.-]*:/.test(url) || // http:, https:, mailto:, ftp:, …
275
+ url.startsWith('//') ||
276
+ url.startsWith('/')
277
+ ) {
278
+ return null;
279
+ }
280
+ const hashIdx = url.indexOf('#');
281
+ const queryIdx = url.indexOf('?');
282
+ const cut =
283
+ hashIdx >= 0 && queryIdx >= 0 ? Math.min(hashIdx, queryIdx) : hashIdx >= 0 ? hashIdx : queryIdx;
284
+ const path = cut >= 0 ? url.slice(0, cut) : url;
285
+ const fragment = hashIdx >= 0 ? url.slice(hashIdx) : '';
286
+ return { path, fragment };
287
+ }
288
+
289
+ // ── Image gathering ────────────────────────────────────────────────
290
+
291
+ /**
292
+ * Resolve every image referenced by a document to container-relative
293
+ * paths and fetch their bytes. The returned map keys are the authored
294
+ * URLs (so the renderer's `images` map can substitute them); the values
295
+ * are tuples of `[bytes, zipPath]` where `zipPath` is the path used
296
+ * inside the zip.
297
+ *
298
+ * Images that can't be read are silently dropped — the rendered HTML
299
+ * will keep its `<img src>` pointing at the authored URL, which a
300
+ * reader's browser will 404. That matches the user-facing "abort on
301
+ * missing linked doc" rule which applies to `.md` links only.
302
+ */
303
+ async function readImagesForDoc(
304
+ mdDoc: MarkdownDocument,
305
+ docDir: string,
306
+ readBinary: (path: string) => Promise<ArrayBuffer | null>,
307
+ ): Promise<Map<string, { data: ArrayBuffer; zipPath: string }>> {
308
+ const out = new Map<string, { data: ArrayBuffer; zipPath: string }>();
309
+ for (const authored of collectImageRefs(mdDoc)) {
310
+ if (/^[a-z][a-z0-9+.-]*:/.test(authored) || authored.startsWith('//')) continue;
311
+ const cleanAuthored = authored.replace(/[#?].*$/, '');
312
+ const resolved = resolveRelative(docDir, cleanAuthored);
313
+ if (resolved === null) continue;
314
+ const data = await readBinary(resolved);
315
+ if (!data) continue;
316
+ out.set(authored, { data, zipPath: resolved });
317
+ }
318
+ return out;
319
+ }
320
+
321
+ // ── Path helpers (POSIX-style, no Node `path`) ─────────────────────
322
+
323
+ /**
324
+ * Strip a path to its parent directory (POSIX-style). Returns `''`
325
+ * for root-level files so subsequent joins read like a fresh path.
326
+ */
327
+ export function posixDirname(p: string): string {
328
+ const idx = p.lastIndexOf('/');
329
+ return idx < 0 ? '' : p.slice(0, idx);
330
+ }
331
+
332
+ /**
333
+ * Normalize a POSIX path: collapse `./`, resolve `..`, drop trailing
334
+ * slashes, never produce a leading `/`. Returns null when the path
335
+ * escapes the start ("..").
336
+ */
337
+ export function normalizePath(p: string): string | null {
338
+ const parts = p.split('/');
339
+ const out: string[] = [];
340
+ for (const segment of parts) {
341
+ if (segment === '' || segment === '.') continue;
342
+ if (segment === '..') {
343
+ if (out.length === 0) return null;
344
+ out.pop();
345
+ continue;
346
+ }
347
+ out.push(segment);
348
+ }
349
+ return out.join('/');
350
+ }
351
+
352
+ /**
353
+ * Resolve `rel` against `baseDir`. Returns null when the result
354
+ * escapes the container root via `..`.
355
+ */
356
+ export function resolveRelative(baseDir: string, rel: string): string | null {
357
+ if (rel.startsWith('/')) return null; // absolute paths are out of scope
358
+ const joined = baseDir ? `${baseDir}/${rel}` : rel;
359
+ return normalizePath(joined);
360
+ }
361
+
362
+ /**
363
+ * True when `target` is inside `root` (or equal to it). Empty root
364
+ * means "entire container is in scope".
365
+ */
366
+ export function isInScope(target: string, root: string): boolean {
367
+ if (!root) return true;
368
+ return target === root || target.startsWith(root + '/');
369
+ }
370
+
371
+ /**
372
+ * Compute a relative path from `fromDir` to `toPath` (both POSIX-
373
+ * normalized, container-relative). Used to emit hrefs that work after
374
+ * unzipping anywhere — `home.md → subfolder/notes.md` becomes
375
+ * `subfolder/notes.html`; `subfolder/intro.md → resume.md` becomes
376
+ * `../resume.html`.
377
+ */
378
+ export function relativeFrom(fromDir: string, toPath: string): string {
379
+ const fromParts = fromDir ? fromDir.split('/') : [];
380
+ const toParts = toPath.split('/');
381
+ let common = 0;
382
+ while (
383
+ common < fromParts.length &&
384
+ common < toParts.length - 1 &&
385
+ fromParts[common] === toParts[common]
386
+ ) {
387
+ common++;
388
+ }
389
+ const up = fromParts.length - common;
390
+ const down = toParts.slice(common);
391
+ const prefix = Array(up).fill('..').join('/');
392
+ if (!prefix) return down.join('/');
393
+ return `${prefix}/${down.join('/')}`;
394
+ }
395
+
396
+ /**
397
+ * Sanitize a path for use as a zip entry. Strips leading slashes,
398
+ * normalizes backslashes (Windows-authored paths), and rejects any
399
+ * path containing `..` segments after normalization (defensive — the
400
+ * resolver above already filters those out for links).
401
+ */
402
+ function sanitizeZipPath(path: string): string | null {
403
+ const normalized = path.replace(/\\/g, '/').replace(/^\/+/, '');
404
+ if (!normalized) return null;
405
+ if (normalized.split('/').some((seg) => seg === '..')) return null;
406
+ return normalized;
407
+ }
408
+
409
+ /**
410
+ * Derive a default page title for a non-entry doc: prefer frontmatter
411
+ * `title`, fall back to the shallowest heading text, then the filename
412
+ * without extension.
413
+ */
414
+ function titleForFilename(path: string, mdDoc: MarkdownDocument): string {
415
+ const inferred = inferDocumentTitle(mdDoc);
416
+ if (inferred) return inferred;
417
+ const base = path.split('/').pop() ?? path;
418
+ return base.replace(/\.md$/i, '');
419
+ }
@@ -28,7 +28,7 @@
28
28
  */
29
29
 
30
30
  import type { Doc } from '@bendyline/squisq/schemas';
31
- import { resolveTheme } from '@bendyline/squisq/schemas';
31
+ import { resolveTheme, resolveFontFamily } from '@bendyline/squisq/schemas';
32
32
  import type { Theme } from '@bendyline/squisq/schemas';
33
33
  import { docToMarkdown } from '@bendyline/squisq/doc';
34
34
  import type {
@@ -48,6 +48,7 @@ import type {
48
48
  MarkdownLink,
49
49
  MarkdownImage,
50
50
  } from '@bendyline/squisq/markdown';
51
+ import { readFrontmatterThemeId } from '@bendyline/squisq/markdown';
51
52
 
52
53
  import { createPackage } from '../ooxml/writer.js';
53
54
  import { xmlDeclaration, escapeXml } from '../ooxml/xmlUtils.js';
@@ -137,11 +138,11 @@ export async function markdownDocToPptx(
137
138
  doc: MarkdownDocument,
138
139
  options: PptxExportOptions = {},
139
140
  ): Promise<ArrayBuffer> {
140
- // Resolve theme from options or frontmatter
141
- const themeId =
142
- options.themeId ??
143
- (doc.frontmatter?.themeId as string | undefined) ??
144
- (doc.frontmatter?.theme as string | undefined);
141
+ // Resolve theme from options or frontmatter. The frontmatter lookup
142
+ // accepts the editor's canonical `squisq-theme` key as well as the
143
+ // shorter legacy `themeId` / `theme` aliases — see
144
+ // `readFrontmatterThemeId` for the precedence.
145
+ const themeId = options.themeId ?? readFrontmatterThemeId(doc.frontmatter);
145
146
  const style = resolveSlideStyle(themeId, options);
146
147
 
147
148
  const slides = segmentIntoSlides(doc.children, options.slideBreak ?? 'h2');
@@ -218,9 +219,9 @@ function resolveSlideStyle(themeId: string | undefined, options: PptxExportOptio
218
219
  text: stripHash(c.text),
219
220
  titleColor: stripHash(c.highlight || c.secondary || c.text),
220
221
  mutedColor: stripHash(c.textMuted || c.text),
221
- titleFont: theme.typography?.titleFontFamily || DEFAULT_TITLE_FONT,
222
- bodyFont: theme.typography?.bodyFontFamily || options.defaultFont || DEFAULT_FONT,
223
- codeFont: theme.typography?.monoFontFamily || DEFAULT_CODE_FONT,
222
+ titleFont: resolveFontFamily(theme.typography?.titleFont, DEFAULT_TITLE_FONT),
223
+ bodyFont: resolveFontFamily(theme.typography?.bodyFont, options.defaultFont || DEFAULT_FONT),
224
+ codeFont: resolveFontFamily(theme.typography?.monoFont, DEFAULT_CODE_FONT),
224
225
  codeColor: stripHash(c.textMuted || c.text),
225
226
  hasTheme: true,
226
227
  };
@@ -1,8 +0,0 @@
1
- /**
2
- * Tests for container ZIP serialization: containerToZip, zipToContainer.
3
- *
4
- * Verifies round-trip fidelity: write files to a container, serialize to ZIP,
5
- * deserialize back, and confirm all files are intact.
6
- */
7
- export {};
8
- //# sourceMappingURL=container.test.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"container.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/container.test.ts"],"names":[],"mappings":"AAAA;;;;;GAKG"}
@@ -1,147 +0,0 @@
1
- /**
2
- * Tests for container ZIP serialization: containerToZip, zipToContainer.
3
- *
4
- * Verifies round-trip fidelity: write files to a container, serialize to ZIP,
5
- * deserialize back, and confirm all files are intact.
6
- */
7
- import { describe, it, expect } from 'vitest';
8
- import JSZip from 'jszip';
9
- import { MemoryContentContainer } from '@bendyline/squisq/storage';
10
- import { containerToZip, zipToContainer } from '../container/index';
11
- // ============================================
12
- // Helpers
13
- // ============================================
14
- /** Helper to read a Blob as ArrayBuffer (works in jsdom) */
15
- async function blobToArrayBuffer(blob) {
16
- return new Promise((resolve, reject) => {
17
- const reader = new FileReader();
18
- reader.onload = () => resolve(reader.result);
19
- reader.onerror = () => reject(reader.error);
20
- reader.readAsArrayBuffer(blob);
21
- });
22
- }
23
- // ============================================
24
- // containerToZip
25
- // ============================================
26
- describe('containerToZip', () => {
27
- it('creates a valid ZIP from an empty container', async () => {
28
- const container = new MemoryContentContainer();
29
- const blob = await containerToZip(container);
30
- expect(blob).toBeInstanceOf(Blob);
31
- expect(blob.size).toBeGreaterThan(0);
32
- // Should be parseable as a ZIP with no files
33
- const zip = await JSZip.loadAsync(await blobToArrayBuffer(blob));
34
- expect(Object.keys(zip.files)).toHaveLength(0);
35
- });
36
- it('includes all files from the container', async () => {
37
- const container = new MemoryContentContainer();
38
- await container.writeDocument('# Hello World');
39
- await container.writeFile('images/hero.jpg', new Uint8Array([0xff, 0xd8, 0xff]), 'image/jpeg');
40
- await container.writeFile('timing.json', new TextEncoder().encode('{"duration":10}'));
41
- const blob = await containerToZip(container);
42
- const zip = await JSZip.loadAsync(await blobToArrayBuffer(blob));
43
- const filenames = Object.keys(zip.files)
44
- .filter((f) => !zip.files[f].dir)
45
- .sort();
46
- expect(filenames).toEqual(['images/hero.jpg', 'index.md', 'timing.json']);
47
- });
48
- it('preserves file content', async () => {
49
- const container = new MemoryContentContainer();
50
- const markdown = '# Test Document\n\nSome content.';
51
- await container.writeDocument(markdown);
52
- const blob = await containerToZip(container);
53
- const zip = await JSZip.loadAsync(await blobToArrayBuffer(blob));
54
- const content = await zip.file('index.md').async('string');
55
- expect(content).toBe(markdown);
56
- });
57
- it('preserves binary content', async () => {
58
- const container = new MemoryContentContainer();
59
- const binaryData = new Uint8Array([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a]);
60
- await container.writeFile('image.png', binaryData, 'image/png');
61
- const blob = await containerToZip(container);
62
- const zip = await JSZip.loadAsync(await blobToArrayBuffer(blob));
63
- const data = await zip.file('image.png').async('uint8array');
64
- expect(data).toEqual(binaryData);
65
- });
66
- });
67
- // ============================================
68
- // zipToContainer
69
- // ============================================
70
- describe('zipToContainer', () => {
71
- it('loads files from a ZIP', async () => {
72
- const zip = new JSZip();
73
- zip.file('index.md', '# Hello');
74
- zip.file('images/photo.jpg', new Uint8Array([0xff, 0xd8]));
75
- const zipBlob = await zip.generateAsync({ type: 'blob' });
76
- const container = await zipToContainer(zipBlob);
77
- const entries = await container.listFiles();
78
- expect(entries.map((e) => e.path).sort()).toEqual(['images/photo.jpg', 'index.md']);
79
- });
80
- it('preserves text content', async () => {
81
- const zip = new JSZip();
82
- zip.file('index.md', '# My Document\n\nParagraph.');
83
- const zipBlob = await zip.generateAsync({ type: 'blob' });
84
- const container = await zipToContainer(zipBlob);
85
- expect(await container.readDocument()).toBe('# My Document\n\nParagraph.');
86
- });
87
- it('preserves binary content', async () => {
88
- const original = new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8]);
89
- const zip = new JSZip();
90
- zip.file('data.bin', original);
91
- const zipBlob = await zip.generateAsync({ type: 'blob' });
92
- const container = await zipToContainer(zipBlob);
93
- const data = await container.readFile('data.bin');
94
- expect(new Uint8Array(data)).toEqual(original);
95
- });
96
- it('skips directory entries', async () => {
97
- const zip = new JSZip();
98
- zip.folder('images');
99
- zip.file('images/photo.jpg', new Uint8Array([0xff]));
100
- const zipBlob = await zip.generateAsync({ type: 'blob' });
101
- const container = await zipToContainer(zipBlob);
102
- const entries = await container.listFiles();
103
- // Only the file, not the directory
104
- expect(entries).toHaveLength(1);
105
- expect(entries[0].path).toBe('images/photo.jpg');
106
- });
107
- it('works with ArrayBuffer input', async () => {
108
- const zip = new JSZip();
109
- zip.file('readme.md', '# Read me');
110
- const ab = await zip.generateAsync({ type: 'arraybuffer' });
111
- const container = await zipToContainer(ab);
112
- expect(await container.readDocument()).toBe('# Read me');
113
- });
114
- });
115
- // ============================================
116
- // Round-trip
117
- // ============================================
118
- describe('containerToZip / zipToContainer round-trip', () => {
119
- it('round-trips a container with markdown and media', async () => {
120
- // Create original container
121
- const original = new MemoryContentContainer();
122
- const markdown = '# Round Trip Test\n\n![hero](images/hero.jpg)\n\nSome text.';
123
- await original.writeDocument(markdown);
124
- await original.writeFile('images/hero.jpg', new Uint8Array([0xff, 0xd8, 0xff, 0xe0]), 'image/jpeg');
125
- await original.writeFile('audio/narration.mp3', new Uint8Array([0x49, 0x44, 0x33]), 'audio/mpeg');
126
- await original.writeFile('timing.json', new TextEncoder().encode('{"segments":[]}'));
127
- // Serialize to ZIP
128
- const zipBlob = await containerToZip(original);
129
- // Deserialize back
130
- const restored = await zipToContainer(zipBlob);
131
- // Verify document
132
- expect(await restored.readDocument()).toBe(markdown);
133
- expect(await restored.getDocumentPath()).toBe('index.md');
134
- // Verify all files present
135
- const entries = (await restored.listFiles()).map((e) => e.path).sort();
136
- expect(entries).toEqual(['audio/narration.mp3', 'images/hero.jpg', 'index.md', 'timing.json']);
137
- // Verify binary content
138
- const heroData = new Uint8Array((await restored.readFile('images/hero.jpg')));
139
- expect(heroData).toEqual(new Uint8Array([0xff, 0xd8, 0xff, 0xe0]));
140
- const audioData = new Uint8Array((await restored.readFile('audio/narration.mp3')));
141
- expect(audioData).toEqual(new Uint8Array([0x49, 0x44, 0x33]));
142
- // Verify JSON sidecar
143
- const timingData = new TextDecoder().decode((await restored.readFile('timing.json')));
144
- expect(timingData).toBe('{"segments":[]}');
145
- });
146
- });
147
- //# sourceMappingURL=container.test.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"container.test.js","sourceRoot":"","sources":["../../src/__tests__/container.test.ts"],"names":[],"mappings":"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"}
@@ -1,8 +0,0 @@
1
- /**
2
- * Tests for DOCX export: markdownDocToDocx, docToDocx.
3
- *
4
- * Exports a MarkdownDocument to .docx, then unzips and inspects
5
- * the OOXML structure to verify correctness.
6
- */
7
- export {};
8
- //# sourceMappingURL=docxExport.test.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"docxExport.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/docxExport.test.ts"],"names":[],"mappings":"AAAA;;;;;GAKG"}