@bendyline/squisq-formats 1.3.0 → 1.4.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.
Files changed (224) hide show
  1. package/dist/chunk-6M7Z25LA.js +46 -0
  2. package/dist/chunk-6M7Z25LA.js.map +1 -0
  3. package/dist/{chunk-S3Y7H2BK.js → chunk-APSIAEXL.js} +9 -8
  4. package/dist/chunk-APSIAEXL.js.map +1 -0
  5. package/dist/{chunk-UDS45KUJ.js → chunk-BJQIJ4S4.js} +36 -52
  6. package/dist/chunk-BJQIJ4S4.js.map +1 -0
  7. package/dist/{chunk-33YRFXZZ.js → chunk-CRTC6DEX.js} +259 -13
  8. package/dist/chunk-CRTC6DEX.js.map +1 -0
  9. package/dist/chunk-FMOIGA5T.js +201 -0
  10. package/dist/chunk-FMOIGA5T.js.map +1 -0
  11. package/dist/{chunk-VN2KEOYB.js → chunk-I2GFTEOV.js} +321 -91
  12. package/dist/chunk-I2GFTEOV.js.map +1 -0
  13. package/dist/chunk-JBNVE2GV.js +116 -0
  14. package/dist/chunk-JBNVE2GV.js.map +1 -0
  15. package/dist/chunk-L53YIGYS.js +141 -0
  16. package/dist/chunk-L53YIGYS.js.map +1 -0
  17. package/dist/{chunk-ERZ627GR.js → chunk-ON232HJR.js} +11 -26
  18. package/dist/chunk-ON232HJR.js.map +1 -0
  19. package/dist/chunk-PN52A5AA.js +32 -0
  20. package/dist/chunk-PN52A5AA.js.map +1 -0
  21. package/dist/{chunk-7DEUGIOO.js → chunk-QSE7EWE7.js} +12 -87
  22. package/dist/chunk-QSE7EWE7.js.map +1 -0
  23. package/dist/container/index.d.ts +7 -5
  24. package/dist/container/index.js +39 -70
  25. package/dist/container/index.js.map +1 -1
  26. package/dist/csv/index.d.ts +37 -0
  27. package/dist/csv/index.js +13 -0
  28. package/dist/csv/index.js.map +1 -0
  29. package/dist/docx/index.d.ts +128 -15
  30. package/dist/docx/index.js +19 -22
  31. package/dist/docx/index.js.map +1 -1
  32. package/dist/epub/index.d.ts +66 -11
  33. package/dist/epub/index.js +11 -18
  34. package/dist/epub/index.js.map +1 -1
  35. package/dist/html/index.d.ts +315 -15
  36. package/dist/html/index.js +32 -165
  37. package/dist/html/index.js.map +1 -1
  38. package/dist/index.d.ts +10 -38
  39. package/dist/index.js +80 -37
  40. package/dist/index.js.map +1 -1
  41. package/dist/ooxml/index.d.ts +353 -14
  42. package/dist/ooxml/index.js +116 -22
  43. package/dist/ooxml/index.js.map +1 -1
  44. package/dist/pdf/index.d.ts +143 -17
  45. package/dist/pdf/index.js +16 -25
  46. package/dist/pdf/index.js.map +1 -1
  47. package/dist/pptx/index.d.ts +98 -21
  48. package/dist/pptx/index.js +18 -34
  49. package/dist/pptx/index.js.map +1 -1
  50. package/dist/xlsx/index.d.ts +29 -25
  51. package/dist/xlsx/index.js +13 -44
  52. package/dist/xlsx/index.js.map +1 -1
  53. package/package.json +7 -2
  54. package/src/__tests__/csvImport.test.ts +51 -0
  55. package/src/__tests__/exportThemeReconciliation.test.ts +87 -0
  56. package/src/__tests__/htmlImport.test.ts +57 -0
  57. package/src/__tests__/plainHtml.test.ts +31 -0
  58. package/src/__tests__/pptxExport.test.ts +138 -0
  59. package/src/__tests__/pptxImport.test.ts +70 -0
  60. package/src/__tests__/roundTripMatrix.fixtures.ts +86 -0
  61. package/src/__tests__/roundTripMatrix.helpers.ts +154 -0
  62. package/src/__tests__/roundTripMatrix.test.ts +136 -0
  63. package/src/__tests__/xlsxImport.test.ts +80 -0
  64. package/src/csv/index.ts +165 -0
  65. package/src/docx/export.ts +27 -44
  66. package/src/docx/import.ts +1 -2
  67. package/src/epub/export.ts +10 -27
  68. package/src/html/import.ts +297 -0
  69. package/src/html/index.ts +4 -0
  70. package/src/html/plainHtml.ts +66 -16
  71. package/src/index.ts +10 -3
  72. package/src/ooxml/namespaces.ts +3 -0
  73. package/src/pdf/export.ts +10 -5
  74. package/src/pdf/import.ts +1 -1
  75. package/src/pptx/export.ts +225 -85
  76. package/src/pptx/import.ts +156 -0
  77. package/src/pptx/index.ts +10 -28
  78. package/src/shared/inlineRuns.ts +99 -0
  79. package/src/shared/text.ts +41 -0
  80. package/src/xlsx/import.ts +159 -0
  81. package/src/xlsx/index.ts +11 -28
  82. package/dist/__tests__/container.test.d.ts +0 -8
  83. package/dist/__tests__/container.test.d.ts.map +0 -1
  84. package/dist/__tests__/container.test.js +0 -147
  85. package/dist/__tests__/container.test.js.map +0 -1
  86. package/dist/__tests__/docxExport.test.d.ts +0 -8
  87. package/dist/__tests__/docxExport.test.d.ts.map +0 -1
  88. package/dist/__tests__/docxExport.test.js +0 -383
  89. package/dist/__tests__/docxExport.test.js.map +0 -1
  90. package/dist/__tests__/docxImport.test.d.ts +0 -8
  91. package/dist/__tests__/docxImport.test.d.ts.map +0 -1
  92. package/dist/__tests__/docxImport.test.js +0 -309
  93. package/dist/__tests__/docxImport.test.js.map +0 -1
  94. package/dist/__tests__/epub.test.d.ts +0 -8
  95. package/dist/__tests__/epub.test.d.ts.map +0 -1
  96. package/dist/__tests__/epub.test.js +0 -472
  97. package/dist/__tests__/epub.test.js.map +0 -1
  98. package/dist/__tests__/html.test.d.ts +0 -8
  99. package/dist/__tests__/html.test.d.ts.map +0 -1
  100. package/dist/__tests__/html.test.js +0 -317
  101. package/dist/__tests__/html.test.js.map +0 -1
  102. package/dist/__tests__/ooxml.test.d.ts +0 -5
  103. package/dist/__tests__/ooxml.test.d.ts.map +0 -1
  104. package/dist/__tests__/ooxml.test.js +0 -152
  105. package/dist/__tests__/ooxml.test.js.map +0 -1
  106. package/dist/__tests__/pdfExport.test.d.ts +0 -8
  107. package/dist/__tests__/pdfExport.test.d.ts.map +0 -1
  108. package/dist/__tests__/pdfExport.test.js +0 -275
  109. package/dist/__tests__/pdfExport.test.js.map +0 -1
  110. package/dist/__tests__/pdfImport.test.d.ts +0 -11
  111. package/dist/__tests__/pdfImport.test.d.ts.map +0 -1
  112. package/dist/__tests__/pdfImport.test.js +0 -254
  113. package/dist/__tests__/pdfImport.test.js.map +0 -1
  114. package/dist/__tests__/plainHtml.test.d.ts +0 -5
  115. package/dist/__tests__/plainHtml.test.d.ts.map +0 -1
  116. package/dist/__tests__/plainHtml.test.js +0 -327
  117. package/dist/__tests__/plainHtml.test.js.map +0 -1
  118. package/dist/__tests__/plainHtmlBundle.test.d.ts +0 -11
  119. package/dist/__tests__/plainHtmlBundle.test.d.ts.map +0 -1
  120. package/dist/__tests__/plainHtmlBundle.test.js +0 -210
  121. package/dist/__tests__/plainHtmlBundle.test.js.map +0 -1
  122. package/dist/__tests__/roundTrip.test.d.ts +0 -7
  123. package/dist/__tests__/roundTrip.test.d.ts.map +0 -1
  124. package/dist/__tests__/roundTrip.test.js +0 -173
  125. package/dist/__tests__/roundTrip.test.js.map +0 -1
  126. package/dist/chunk-33YRFXZZ.js.map +0 -1
  127. package/dist/chunk-67KIJHV2.js +0 -21
  128. package/dist/chunk-67KIJHV2.js.map +0 -1
  129. package/dist/chunk-7DEUGIOO.js.map +0 -1
  130. package/dist/chunk-ERZ627GR.js.map +0 -1
  131. package/dist/chunk-S3Y7H2BK.js.map +0 -1
  132. package/dist/chunk-UDS45KUJ.js.map +0 -1
  133. package/dist/chunk-VN2KEOYB.js.map +0 -1
  134. package/dist/chunk-YN5HFCEW.js +0 -120
  135. package/dist/chunk-YN5HFCEW.js.map +0 -1
  136. package/dist/container/index.d.ts.map +0 -1
  137. package/dist/docx/export.d.ts +0 -69
  138. package/dist/docx/export.d.ts.map +0 -1
  139. package/dist/docx/export.js +0 -985
  140. package/dist/docx/export.js.map +0 -1
  141. package/dist/docx/import.d.ts +0 -65
  142. package/dist/docx/import.d.ts.map +0 -1
  143. package/dist/docx/import.js +0 -779
  144. package/dist/docx/import.js.map +0 -1
  145. package/dist/docx/index.d.ts.map +0 -1
  146. package/dist/docx/styles.d.ts +0 -68
  147. package/dist/docx/styles.d.ts.map +0 -1
  148. package/dist/docx/styles.js +0 -125
  149. package/dist/docx/styles.js.map +0 -1
  150. package/dist/epub/export.d.ts +0 -72
  151. package/dist/epub/export.d.ts.map +0 -1
  152. package/dist/epub/export.js +0 -698
  153. package/dist/epub/export.js.map +0 -1
  154. package/dist/epub/index.d.ts.map +0 -1
  155. package/dist/html/docsHtmlBundle.d.ts +0 -53
  156. package/dist/html/docsHtmlBundle.d.ts.map +0 -1
  157. package/dist/html/docsHtmlBundle.js +0 -299
  158. package/dist/html/docsHtmlBundle.js.map +0 -1
  159. package/dist/html/htmlTemplate.d.ts +0 -70
  160. package/dist/html/htmlTemplate.d.ts.map +0 -1
  161. package/dist/html/htmlTemplate.js +0 -282
  162. package/dist/html/htmlTemplate.js.map +0 -1
  163. package/dist/html/imageUtils.d.ts +0 -29
  164. package/dist/html/imageUtils.d.ts.map +0 -1
  165. package/dist/html/imageUtils.js +0 -63
  166. package/dist/html/imageUtils.js.map +0 -1
  167. package/dist/html/index.d.ts.map +0 -1
  168. package/dist/html/plainHtml.d.ts +0 -79
  169. package/dist/html/plainHtml.d.ts.map +0 -1
  170. package/dist/html/plainHtml.js +0 -614
  171. package/dist/html/plainHtml.js.map +0 -1
  172. package/dist/html/plainHtmlBundle.d.ts +0 -93
  173. package/dist/html/plainHtmlBundle.d.ts.map +0 -1
  174. package/dist/html/plainHtmlBundle.js +0 -357
  175. package/dist/html/plainHtmlBundle.js.map +0 -1
  176. package/dist/index.d.ts.map +0 -1
  177. package/dist/ooxml/index.d.ts.map +0 -1
  178. package/dist/ooxml/namespaces.d.ts +0 -77
  179. package/dist/ooxml/namespaces.d.ts.map +0 -1
  180. package/dist/ooxml/namespaces.js +0 -104
  181. package/dist/ooxml/namespaces.js.map +0 -1
  182. package/dist/ooxml/reader.d.ts +0 -52
  183. package/dist/ooxml/reader.d.ts.map +0 -1
  184. package/dist/ooxml/reader.js +0 -190
  185. package/dist/ooxml/reader.js.map +0 -1
  186. package/dist/ooxml/types.d.ts +0 -82
  187. package/dist/ooxml/types.d.ts.map +0 -1
  188. package/dist/ooxml/types.js +0 -9
  189. package/dist/ooxml/types.js.map +0 -1
  190. package/dist/ooxml/writer.d.ts +0 -71
  191. package/dist/ooxml/writer.d.ts.map +0 -1
  192. package/dist/ooxml/writer.js +0 -200
  193. package/dist/ooxml/writer.js.map +0 -1
  194. package/dist/ooxml/xmlUtils.d.ts +0 -76
  195. package/dist/ooxml/xmlUtils.d.ts.map +0 -1
  196. package/dist/ooxml/xmlUtils.js +0 -108
  197. package/dist/ooxml/xmlUtils.js.map +0 -1
  198. package/dist/pdf/export.d.ts +0 -56
  199. package/dist/pdf/export.d.ts.map +0 -1
  200. package/dist/pdf/export.js +0 -768
  201. package/dist/pdf/export.js.map +0 -1
  202. package/dist/pdf/import.d.ts +0 -94
  203. package/dist/pdf/import.d.ts.map +0 -1
  204. package/dist/pdf/import.js +0 -796
  205. package/dist/pdf/import.js.map +0 -1
  206. package/dist/pdf/index.d.ts.map +0 -1
  207. package/dist/pdf/styles.d.ts +0 -142
  208. package/dist/pdf/styles.d.ts.map +0 -1
  209. package/dist/pdf/styles.js +0 -145
  210. package/dist/pdf/styles.js.map +0 -1
  211. package/dist/pptx/export.d.ts +0 -75
  212. package/dist/pptx/export.d.ts.map +0 -1
  213. package/dist/pptx/export.js +0 -724
  214. package/dist/pptx/export.js.map +0 -1
  215. package/dist/pptx/index.d.ts.map +0 -1
  216. package/dist/pptx/styles.d.ts +0 -54
  217. package/dist/pptx/styles.d.ts.map +0 -1
  218. package/dist/pptx/styles.js +0 -73
  219. package/dist/pptx/styles.js.map +0 -1
  220. package/dist/pptx/templates.d.ts +0 -24
  221. package/dist/pptx/templates.d.ts.map +0 -1
  222. package/dist/pptx/templates.js +0 -158
  223. package/dist/pptx/templates.js.map +0 -1
  224. package/dist/xlsx/index.d.ts.map +0 -1
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/html/index.ts","../src/html/htmlTemplate.ts","../src/html/plainHtml.ts","../src/html/plainHtmlBundle.ts","../src/html/docsHtmlBundle.ts","../src/html/import.ts"],"sourcesContent":["/**\n * HTML Export Module — @bendyline/squisq-formats/html\n *\n * Exports squisq documents as self-contained HTML files or ZIP archives.\n *\n * Two export modes:\n *\n * 1. **Single HTML** (`docToHtml`) — Everything embedded in one file:\n * - SquisqPlayer IIFE bundle inlined in `<script>`\n * - Images base64-encoded as data URIs\n * - Timer-based playback (no audio — too large for inline)\n *\n * 2. **ZIP Archive** (`docToHtmlZip`) — Multi-file package:\n * - `index.html` referencing external `squisq-player.js`\n * - `squisq-player.js` — the IIFE bundle\n * - `images/` — extracted image files\n * - `audio/` — optional audio segment files (enables full playback)\n *\n * @example\n * ```ts\n * import { PLAYER_BUNDLE } from '@bendyline/squisq-react/standalone-source';\n * import { docToHtml, docToHtmlZip } from '@bendyline/squisq-formats/html';\n *\n * // Single HTML file\n * const html = docToHtml(doc, { playerScript: PLAYER_BUNDLE, images });\n *\n * // ZIP archive\n * const zipBlob = await docToHtmlZip(doc, { playerScript: PLAYER_BUNDLE, images, audio });\n * ```\n */\n\nimport type { Doc } from '@bendyline/squisq/schemas';\nimport JSZip from 'jszip';\nimport {\n generateInlineHtml,\n generateExternalHtml,\n type HtmlExportOptions,\n} from './htmlTemplate.js';\nimport { extractFilename } from './imageUtils.js';\n\n// ── Public Types ───────────────────────────────────────────────────\n\nexport type { HtmlExportOptions };\n\n/**\n * Normalize a doc-referenced path for use as a zip entry name. Strips\n * leading slashes and rejects any path that tries to escape the archive\n * root via `..` segments (a malicious or malformed doc shouldn't be able\n * to write outside the zip when extracted). Backslashes are folded to\n * forward slashes so Windows-authored paths still produce a consistent\n * tree across extractors.\n */\nfunction sanitizeZipPath(path: string): string | null {\n const normalized = path.replace(/\\\\/g, '/').replace(/^\\/+/, '');\n if (!normalized) return null;\n if (normalized.split('/').some((seg) => seg === '..')) return null;\n return normalized;\n}\n\nexport interface HtmlZipExportOptions extends HtmlExportOptions {\n /**\n * Map of audio segment identifiers to binary audio data.\n * Keys should match the audio segment `name` or `url` fields in the Doc.\n * When provided, audio files are included in the ZIP and full playback is enabled.\n */\n audio?: Map<string, ArrayBuffer>;\n}\n\n// ── Single HTML Export ─────────────────────────────────────────────\n\n/**\n * Export a Doc as a single, self-contained HTML file.\n *\n * The player JS is embedded inline, images are base64-encoded as data URIs,\n * and playback uses a timer (no audio) for compact file size.\n *\n * @param doc - The Doc to export\n * @param options - Export options (must include `playerScript`)\n * @returns Complete HTML document as a string\n *\n * @example\n * ```ts\n * const html = docToHtml(myDoc, {\n * playerScript: PLAYER_BUNDLE,\n * images: imageMap,\n * title: 'My Document',\n * mode: 'slideshow',\n * });\n * const blob = new Blob([html], { type: 'text/html' });\n * ```\n */\nexport function docToHtml(doc: Doc, options: HtmlExportOptions): string {\n return generateInlineHtml(doc, options);\n}\n\n// ── ZIP Archive Export ─────────────────────────────────────────────\n\n/**\n * Export a Doc as a ZIP archive containing HTML, JS, images, and optionally audio.\n *\n * The archive structure:\n * ```\n * document.zip\n * ├── index.html # HTML page referencing squisq-player.js\n * ├── squisq-player.js # Standalone IIFE bundle\n * ├── images/ # Extracted image files\n * │ ├── hero.jpg\n * │ └── ...\n * └── audio/ # Optional audio segment files\n * ├── intro.mp3\n * └── ...\n * ```\n *\n * @param doc - The Doc to export\n * @param options - Export options (must include `playerScript`)\n * @returns A Promise resolving to a ZIP Blob\n *\n * @example\n * ```ts\n * const blob = await docToHtmlZip(myDoc, {\n * playerScript: PLAYER_BUNDLE,\n * images: imageMap,\n * audio: audioMap,\n * });\n * // Trigger browser download\n * const url = URL.createObjectURL(blob);\n * ```\n */\nexport async function docToHtmlZip(doc: Doc, options: HtmlZipExportOptions): Promise<Blob> {\n const { playerScript, images, audio, mode = 'slideshow', title, autoPlay } = options;\n\n const zip = new JSZip();\n\n // 1. Add player JS as a separate file\n zip.file('squisq-player.js', playerScript);\n\n // 2. Add images preserving their original relative paths.\n //\n // We used to flatten everything into `images/<basename>`, but that\n // requires the player to rewrite every `<img src>` at render time using\n // the path map — and the static (non-slideshow) renderer doesn't do\n // that, so its emitted `<img>` tags 404'd against the renamed assets.\n // Keeping the original layout means direct `src=\"folder/file.png\"`\n // references just work, and the exported zip mirrors how the source\n // folder is organized (e.g. pandoc-style `notes_files/` sidecars).\n const imagePathMap: Record<string, string> = {};\n if (images) {\n for (const [originalPath, buffer] of images.entries()) {\n const zipPath = sanitizeZipPath(originalPath);\n if (!zipPath) continue;\n zip.file(zipPath, buffer);\n imagePathMap[originalPath] = zipPath;\n }\n }\n\n // 3. Add audio to audio/ folder and build path mapping\n const audioPathMap: Record<string, string> = {};\n if (audio) {\n for (const [segmentKey, buffer] of audio.entries()) {\n const filename = extractFilename(segmentKey);\n // Ensure .mp3 extension\n const finalName = filename.includes('.') ? filename : `${filename}.mp3`;\n const zipPath = `audio/${finalName}`;\n zip.file(zipPath, buffer);\n audioPathMap[segmentKey] = zipPath;\n }\n }\n\n // 4. Generate HTML that references external files\n const html = generateExternalHtml(doc, {\n playerScriptPath: 'squisq-player.js',\n imagePathMap: Object.keys(imagePathMap).length > 0 ? imagePathMap : undefined,\n audioPathMap: Object.keys(audioPathMap).length > 0 ? audioPathMap : undefined,\n mode,\n title,\n autoPlay,\n });\n zip.file('index.html', html);\n\n // 5. Generate ZIP blob\n return zip.generateAsync({\n type: 'blob',\n compression: 'DEFLATE',\n compressionOptions: { level: 6 },\n });\n}\n\n// ── Re-exports ─────────────────────────────────────────────────────\n\nexport { collectImagePaths } from './htmlTemplate.js';\nexport { inferMimeType, arrayBufferToBase64DataUrl, extractFilename } from './imageUtils.js';\nexport { markdownDocToPlainHtml } from './plainHtml.js';\nexport type { PlainHtmlExportOptions } from './plainHtml.js';\nexport { markdownDocsToPlainHtmlBundle, collectLinkRefs } from './plainHtmlBundle.js';\nexport type { PlainHtmlBundleOptions } from './plainHtmlBundle.js';\nexport { markdownDocsToHtmlBundle } from './docsHtmlBundle.js';\nexport type { HtmlBundleOptions } from './docsHtmlBundle.js';\n\n// ── Import (HTML → MarkdownDocument) ────────────────────────────────\nexport { htmlToMarkdown, htmlToMarkdownDoc, htmlToMarkdownDocSync } from './import.js';\nexport type { HtmlImportOptions } from './import.js';\n","/**\n * HTML Template Generation for SquisqPlayer Exports\n *\n * Generates complete, self-contained HTML documents that load the SquisqPlayer\n * IIFE bundle and render a Doc as either an interactive slideshow or a static\n * scrollable document.\n *\n * Two variants:\n * 1. **Inline** — All JS, CSS, and images are embedded in the HTML (single file).\n * 2. **External** — JS is referenced via `<script src>`, images via relative paths.\n */\n\nimport type { Doc, Layer, Block } from '@bendyline/squisq/schemas';\nimport { arrayBufferToBase64DataUrl, inferMimeType } from './imageUtils.js';\n\n// ── Types ──────────────────────────────────────────────────────────\n\nexport interface HtmlExportOptions {\n /** The IIFE player bundle source code (from @bendyline/squisq-react/standalone-source) */\n playerScript: string;\n\n /**\n * Map of relative image paths (as they appear in the Doc) to binary image data.\n * For inline HTML export, these are converted to base64 data URIs.\n * For ZIP export, these are written as separate files.\n */\n images?: Map<string, ArrayBuffer>;\n\n /**\n * Map of audio segment identifiers to binary audio data.\n * Keys should match the audio segment `name` or `url` fields in the Doc.\n * Only used in ZIP exports — single HTML uses timer-based playback.\n */\n audio?: Map<string, ArrayBuffer>;\n\n /** Rendering mode: 'slideshow' (interactive, default) or 'static' (scrollable) */\n mode?: 'slideshow' | 'static';\n\n /** HTML page title (default: 'Squisq Document') */\n title?: string;\n\n /** Auto-play slideshow on load (default: false) */\n autoPlay?: boolean;\n\n /**\n * Squisq theme ID to apply (e.g., 'documentary', 'cinematic').\n * When set, the theme is assigned to the Doc before rendering,\n * so the SquisqPlayer renders with that theme's colors and typography.\n */\n themeId?: string;\n}\n\n// ── Image Path Collection ──────────────────────────────────────────\n\n/**\n * Collect all relative image paths referenced in a Doc's layers and template blocks.\n * Returns a Set of unique relative paths that need to be resolved.\n */\nexport function collectImagePaths(doc: Doc): Set<string> {\n const paths = new Set<string>();\n\n function addIfRelative(src: string | undefined) {\n if (\n !src ||\n src.startsWith('data:') ||\n src.startsWith('blob:') ||\n src.startsWith('http://') ||\n src.startsWith('https://')\n ) {\n return;\n }\n paths.add(src);\n }\n\n function scanLayers(layers: Layer[] | undefined) {\n if (!layers) return;\n for (const layer of layers) {\n if (layer.type === 'image') addIfRelative(layer.content.src);\n if (layer.type === 'video') {\n addIfRelative(layer.content.src);\n addIfRelative(layer.content.posterSrc);\n }\n if (layer.type === 'map') addIfRelative(layer.content.staticSrc);\n }\n }\n\n function scanBlock(block: Block) {\n scanLayers(block.layers);\n if (block.contents) scanMarkdownNodes(block.contents, addIfRelative);\n if (block.children) block.children.forEach(scanBlock);\n }\n\n // Scan all blocks\n doc.blocks.forEach(scanBlock);\n\n // Scan start block hero\n if (doc.startBlock?.heroSrc) addIfRelative(doc.startBlock.heroSrc);\n\n // Scan persistent layers\n if (doc.persistentLayers) {\n for (const pl of doc.persistentLayers.bottomLayers ?? []) {\n if ('src' in pl && typeof pl.src === 'string') addIfRelative(pl.src);\n }\n for (const pl of doc.persistentLayers.topLayers ?? []) {\n if ('src' in pl && typeof pl.src === 'string') addIfRelative(pl.src);\n }\n }\n\n // Scan template block fields that reference images\n for (const block of doc.blocks) {\n scanTemplateImageFields(block as unknown as Record<string, unknown>, paths);\n }\n\n return paths;\n}\n\n/**\n * Walk markdown body nodes for image references. Catches both shorthand\n * `![alt](url)` (type `image`, `url` field) and raw HTML `<img src>` tags\n * (type `htmlBlock`/`htmlInline`, `htmlChildren` sub-tree) — the latter is\n * how the WYSIWYG editor round-trips resized images, since markdown\n * shorthand has no width syntax.\n */\nfunction scanMarkdownNodes(nodes: unknown[], visit: (src: string | undefined) => void): void {\n for (const node of nodes) {\n if (!node || typeof node !== 'object') continue;\n const n = node as Record<string, unknown>;\n if (n.type === 'image' && typeof n.url === 'string') {\n visit(n.url);\n }\n if ((n.type === 'htmlBlock' || n.type === 'htmlInline') && Array.isArray(n.htmlChildren)) {\n scanHtmlNodes(n.htmlChildren, visit);\n }\n if (Array.isArray(n.children)) {\n scanMarkdownNodes(n.children, visit);\n }\n }\n}\n\n/** Walk a parsed HTML sub-tree for `<img>` src attributes. */\nfunction scanHtmlNodes(nodes: unknown[], visit: (src: string | undefined) => void): void {\n for (const node of nodes) {\n if (!node || typeof node !== 'object') continue;\n const n = node as Record<string, unknown>;\n if (n.type !== 'htmlElement') continue;\n if (n.tagName === 'img') {\n const attrs = n.attributes as Record<string, string> | undefined;\n if (attrs && typeof attrs.src === 'string') visit(attrs.src);\n }\n if (Array.isArray(n.children)) {\n scanHtmlNodes(n.children, visit);\n }\n }\n}\n\n/**\n * Recursively scan any object for known image-bearing field names.\n */\nfunction scanTemplateImageFields(obj: Record<string, unknown>, paths: Set<string>): void {\n if (!obj || typeof obj !== 'object') return;\n\n const imageFieldNames = [\n 'imageSrc',\n 'src',\n 'heroSrc',\n 'backgroundImage',\n 'posterSrc',\n 'staticSrc',\n 'videoSrc',\n 'thumbnailSrc',\n ];\n\n for (const key of imageFieldNames) {\n const val = obj[key];\n if (typeof val === 'string' && val && !val.startsWith('data:') && !val.startsWith('http')) {\n paths.add(val);\n }\n // Handle nested objects like { src: '...' }\n if (val && typeof val === 'object' && !Array.isArray(val)) {\n scanTemplateImageFields(val as Record<string, unknown>, paths);\n }\n }\n\n // Handle arrays (e.g., images: [{ src: '...' }])\n if (Array.isArray(obj)) {\n for (const item of obj) {\n if (item && typeof item === 'object') {\n scanTemplateImageFields(item as Record<string, unknown>, paths);\n }\n }\n }\n\n // Recurse into known array/object fields\n for (const key of ['images', 'accentImage', 'backgroundVideo', 'children', 'blocks']) {\n const val = obj[key];\n if (Array.isArray(val)) {\n for (const item of val) {\n if (item && typeof item === 'object') {\n scanTemplateImageFields(item as Record<string, unknown>, paths);\n }\n }\n } else if (val && typeof val === 'object') {\n scanTemplateImageFields(val as Record<string, unknown>, paths);\n }\n }\n}\n\n// ── HTML Generation ────────────────────────────────────────────────\n\n/**\n * Escape a string for safe embedding in a `<script>` tag.\n * Prevents `</script>` from closing the tag prematurely.\n */\nfunction escapeForScript(str: string): string {\n return str.replace(/<\\/(script)/gi, '<\\\\/$1');\n}\n\n/**\n * Generate a complete inline HTML document with all JS and images embedded.\n *\n * @param doc - The Doc to render\n * @param options - Export options\n * @returns Complete HTML string\n */\nexport function generateInlineHtml(doc: Doc, options: HtmlExportOptions): string {\n const {\n playerScript,\n images,\n mode = 'slideshow',\n title = 'Squisq Document',\n autoPlay = false,\n themeId,\n } = options;\n\n // Apply theme to doc if specified (the SquisqPlayer respects doc.themeId at render time)\n if (themeId) {\n doc = { ...doc, themeId };\n }\n\n // Build base64 image map\n const imageMap: Record<string, string> = {};\n if (images) {\n for (const [path, buffer] of images.entries()) {\n const mimeType = inferMimeType(path);\n imageMap[path] = arrayBufferToBase64DataUrl(buffer, mimeType);\n }\n }\n\n const docJson = escapeForScript(JSON.stringify(doc));\n const imageMapJson = escapeForScript(JSON.stringify(imageMap));\n\n return `<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n<meta charset=\"UTF-8\">\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n<title>${escapeHtml(title)}</title>\n<style>\n*,*::before,*::after{box-sizing:border-box}\nhtml,body{margin:0;padding:0;height:100%;background:#1a1a2e;color:#e0e0e0;font-family:system-ui,-apple-system,sans-serif}\n#squisq-root{width:100%;height:100%;display:flex;align-items:center;justify-content:center}\n${mode === 'static' ? '#squisq-root{align-items:flex-start;overflow-y:auto;background:#fff;color:#1f2937}' : ''}\n</style>\n</head>\n<body>\n<div id=\"squisq-root\"></div>\n<script>${escapeForScript(playerScript)}</script>\n<script>\n(function(){\n var doc = JSON.parse(${JSON.stringify(docJson)});\n var images = JSON.parse(${JSON.stringify(imageMapJson)});\n SquisqPlayer.mount(document.getElementById(\"squisq-root\"), doc, {\n mode: ${JSON.stringify(mode)},\n images: images,\n autoPlay: ${JSON.stringify(autoPlay)},\n basePath: \".\"\n });\n})();\n</script>\n</body>\n</html>`;\n}\n\n/**\n * Generate an HTML document that references external JS and image files.\n * Used for ZIP exports where files sit alongside the HTML.\n *\n * @param doc - The Doc to render (image/audio paths should already be rewritten to relative)\n * @param options - Export options (playerScript is not embedded, referenced via src)\n * @returns Complete HTML string\n */\nexport function generateExternalHtml(\n doc: Doc,\n options: Pick<HtmlExportOptions, 'mode' | 'title' | 'autoPlay'> & {\n /** Relative path to the player JS file (e.g., 'squisq-player.js') */\n playerScriptPath: string;\n /** Map of original image paths to their rewritten relative paths in the ZIP */\n imagePathMap?: Record<string, string>;\n /** Map of audio segment IDs to their rewritten relative paths in the ZIP */\n audioPathMap?: Record<string, string>;\n },\n): string {\n const {\n playerScriptPath,\n imagePathMap,\n audioPathMap,\n mode = 'slideshow',\n title = 'Squisq Document',\n autoPlay = false,\n } = options;\n\n const docJson = escapeForScript(JSON.stringify(doc));\n const imageMapJson = imagePathMap ? escapeForScript(JSON.stringify(imagePathMap)) : '{}';\n const audioMapJson = audioPathMap ? escapeForScript(JSON.stringify(audioPathMap)) : 'null';\n\n return `<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n<meta charset=\"UTF-8\">\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n<title>${escapeHtml(title)}</title>\n<style>\n*,*::before,*::after{box-sizing:border-box}\nhtml,body{margin:0;padding:0;height:100%;background:#1a1a2e;color:#e0e0e0;font-family:system-ui,-apple-system,sans-serif}\n#squisq-root{width:100%;height:100%;display:flex;align-items:center;justify-content:center}\n${mode === 'static' ? '#squisq-root{align-items:flex-start;overflow-y:auto;background:#fff;color:#1f2937}' : ''}\n</style>\n</head>\n<body>\n<div id=\"squisq-root\"></div>\n<script src=\"${escapeHtml(playerScriptPath)}\"></script>\n<script>\n(function(){\n var doc = JSON.parse(${JSON.stringify(docJson)});\n var images = JSON.parse(${JSON.stringify(imageMapJson)});\n var audio = ${audioMapJson};\n SquisqPlayer.mount(document.getElementById(\"squisq-root\"), doc, {\n mode: ${JSON.stringify(mode)},\n images: images,\n audio: audio,\n autoPlay: ${JSON.stringify(autoPlay)},\n basePath: \".\"\n });\n})();\n</script>\n</body>\n</html>`;\n}\n\n/**\n * Escape HTML special characters to prevent injection.\n */\nfunction escapeHtml(str: string): string {\n return str\n .replace(/&/g, '&amp;')\n .replace(/</g, '&lt;')\n .replace(/>/g, '&gt;')\n .replace(/\"/g, '&quot;')\n .replace(/'/g, '&#39;');\n}\n","/**\n * Plain HTML Export — semantic, player-free\n *\n * Renders a MarkdownDocument to a self-contained HTML string. Unlike\n * `docToHtml` (which bundles the SquisqPlayer IIFE and renders SVG block\n * cards), this output is what a reader-mode tool would produce: semantic\n * `<h1>`/`<p>`/`<ul>`/etc. with a small embedded stylesheet, no JS, no\n * runtime path-rewriting. Drives both the \"Page\" preview tab in the\n * editor and the plain-style branch of the export dialog so what users\n * see live matches the file they download.\n *\n * Image URLs default to the markdown's own paths. Pass `images` to\n * substitute a different value per source URL — typically pre-resolved\n * blob URLs (live preview) or data URIs (single-file export).\n */\n\nimport {\n sanitizeHtmlNodes,\n sanitizeUrl,\n type HtmlPolicy,\n type MarkdownDocument,\n type MarkdownNode,\n type HtmlNode,\n readFrontmatterThemeId,\n} from '@bendyline/squisq/markdown';\nimport type { Theme } from '@bendyline/squisq/schemas';\nimport { resolveFontFamily, buildGoogleFontsUrl } from '@bendyline/squisq/schemas';\nimport { resolveThemeForDoc } from '@bendyline/squisq/doc';\n\n// ── Public Types ───────────────────────────────────────────────────\n\nexport interface PlainHtmlExportOptions {\n /** Document title — populates `<title>` and is HTML-escaped. */\n title?: string;\n /**\n * Substitution map for image `src` URLs. Keys are the URL exactly as\n * it appears in the markdown source; values are the URL to emit in\n * the rendered `<img src>`. URLs not present in the map fall through\n * unchanged (so external `https://…` references still work).\n */\n images?: Map<string, string>;\n /**\n * Substitution map for anchor `href` URLs. Keys are the URL exactly\n * as it appears in the markdown source (e.g. `'resume.md'`,\n * `'resume.md#experience'`); values are the URL to emit. URLs not in\n * the map pass through unchanged. Used by the recursive bundle\n * exporter to rewrite `.md` references to `.html` so a static export\n * of a linked document tree is internally browsable.\n */\n links?: Map<string, string>;\n /**\n * Optional Squisq theme. When provided, the rendered page uses the\n * theme's colors and typography, and any Google-hosted fonts the\n * theme references are loaded via a `<link>` to fonts.googleapis.com\n * so the face renders correctly without host preloads.\n *\n * When omitted, the function falls back (in order) to {@link themeId}\n * and then to `doc.frontmatter.themeId` — so an authored\n * `themeId: warm-earth` in the doc's frontmatter styles the export\n * automatically, without the caller having to wire theme resolution\n * themselves.\n */\n theme?: Theme;\n /**\n * Optional theme id (e.g. `'warm-earth'`, `'gezellig'`). Convenient\n * for hosts whose export dialog tracks themes by id — they can pass\n * the id straight through instead of resolving to a `Theme` object.\n * When both `theme` and `themeId` are provided, `theme` wins.\n */\n themeId?: string;\n /**\n * Optional FontAwesome CSS text to inline into the rendered page,\n * replacing the default cross-origin `<link>` to cdnjs. Required for\n * sandboxed iframe previews where tracking prevention or stricter\n * origin policies can silently drop cross-origin font fetches —\n * inlining keeps the icons resolvable purely from same-origin\n * resources. Hosts typically gather this string by scraping\n * `document.styleSheets` for `@font-face` rules whose family starts\n * with `\"Font Awesome\"`. The CDN `<link>` is only emitted when this\n * option is not provided.\n */\n iconsCss?: string;\n /**\n * Raw HTML policy. Defaults to `sanitize`, which removes unsafe tags,\n * event handlers, and executable URL schemes before emitting HTML.\n */\n htmlPolicy?: HtmlPolicy;\n}\n\n/**\n * Internal render context — bundles the substitution maps so adding a\n * new one (e.g. `links`) doesn't require threading another parameter\n * through every node renderer.\n */\ninterface RenderCtx {\n images?: Map<string, string>;\n links?: Map<string, string>;\n htmlPolicy: HtmlPolicy;\n}\n\n// ── Public API ─────────────────────────────────────────────────────\n\n/**\n * Render a parsed markdown document as a complete, semantic HTML page.\n *\n * When `options.theme` is provided, the output adopts the theme's\n * colors and typography. Google-hosted fonts referenced by the theme\n * are loaded via a single `<link>` to fonts.googleapis.com so the page\n * renders consistently when opened standalone.\n */\nexport function markdownDocToPlainHtml(\n doc: MarkdownDocument,\n options: PlainHtmlExportOptions = {},\n): string {\n const { title = 'Document', images, links, themeId, iconsCss, htmlPolicy = 'sanitize' } = options;\n // Fall back chain for theme: explicit `theme` → explicit `themeId`\n // option → doc frontmatter `themeId`. Hosts whose export dialog\n // tracks themes by id can pass `themeId` straight through; authored\n // docs with `themeId: warm-earth` in frontmatter get styled\n // automatically when neither is supplied.\n // Resolution is doc-scoped via `resolveThemeForDoc`, so an inline\n // `squisq-custom-themes` id resolves here with no global registration —\n // and `readFrontmatterThemeId` picks up the editor's canonical\n // `squisq-theme` key. Stays undefined when nothing selects a theme so\n // un-themed exports render unstyled (unchanged behavior).\n const resolveId = themeId ?? readFrontmatterThemeId(doc.frontmatter);\n const theme = options.theme ?? (resolveId ? resolveThemeForDoc(doc, resolveId) : undefined);\n const ctx: RenderCtx = { images, links, htmlPolicy };\n const body = renderTopLevel(doc.children, ctx);\n const fontsLink = theme ? renderFontsLink(theme) : '';\n // Resolve how to load FontAwesome — only when the doc actually uses\n // icons. When the host supplies `iconsCss` (typical for sandboxed\n // iframe previews where cross-origin font fetches get blocked), we\n // inline it as a `<style>` block; otherwise we fall back to the\n // public cdnjs `<link>` which works for standalone HTML files.\n const usesIcons = docUsesIcons(doc);\n let iconsLink = '';\n if (usesIcons) {\n iconsLink = iconsCss ? `<style data-fa-inline>\\n${iconsCss}\\n</style>\\n` : FONT_AWESOME_LINK;\n }\n const themedCss = theme ? renderThemedCss(theme) : DEFAULT_CSS;\n return `<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n<meta charset=\"UTF-8\">\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n<title>${escapeHtml(title)}</title>\n${fontsLink}${iconsLink}<style>\n${themedCss}\n${FEATURE_CSS}\n</style>\n</head>\n<body>\n${body}\n</body>\n</html>`;\n}\n\n/**\n * Hosted FontAwesome Free CSS. Pinned to a specific release so the\n * integrity hash stays in sync — bump both fields together when\n * upgrading. Cdnjs serves the matching SRI hash on every release page.\n */\nconst FONT_AWESOME_LINK = `<link rel=\"stylesheet\" href=\"https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.2/css/all.min.css\" integrity=\"sha512-SnH5WK+bZxgPHs44uWIX+LLJAJ9/2PkPKZ5QiAj6Ta86w+fsb2TkcmfRyVX3pBnMFcV7oQPJkl9QevSCWr3W6A==\" crossorigin=\"anonymous\" referrerpolicy=\"no-referrer\">\n`;\n\n/** Walk the doc looking for any `inlineIcon` node. Cheap depth-first\n * traversal — bails out at the first hit. */\nfunction docUsesIcons(doc: MarkdownDocument): boolean {\n function visit(node: unknown): boolean {\n if (!node || typeof node !== 'object') return false;\n const n = node as Record<string, unknown>;\n if (n.type === 'inlineIcon') return true;\n if (Array.isArray(n.children)) {\n for (const child of n.children) if (visit(child)) return true;\n }\n return false;\n }\n return visit(doc);\n}\n\n// ── Top-level walk with feature-section grouping ───────────────────\n\n/**\n * Walk the document's top-level children, grouping headings that carry\n * a `leftFeature` / `rightFeature` template annotation with the body\n * blocks that follow them (up to the next sibling-or-higher heading).\n * Each group renders as a single `<section class=\"squisq-feature ...\">`\n * with a media column and a text column — the plain-HTML analogue of\n * the SVG layer layout produced by `getLayers` for the same templates.\n */\nfunction renderTopLevel(children: MarkdownNode[], ctx: RenderCtx | undefined): string {\n const out: string[] = [];\n for (let i = 0; i < children.length; i++) {\n const node = children[i] as { type?: string };\n if (node && node.type === 'heading') {\n const heading = node as MarkdownHeadingLike;\n const tpl = heading.templateAnnotation?.template;\n if (tpl === 'leftFeature' || tpl === 'rightFeature') {\n const end = findSectionEnd(children, i);\n const sectionBody = children.slice(i + 1, end);\n out.push(renderFeatureSection(heading, sectionBody, tpl, ctx));\n i = end - 1;\n continue;\n }\n }\n out.push(nodeToHtml(node as MarkdownNode, ctx));\n }\n return out.join('\\n');\n}\n\ninterface MarkdownHeadingLike {\n type: 'heading';\n depth: number;\n children?: MarkdownNode[];\n templateAnnotation?: { template?: string };\n}\n\n/**\n * Index of the next heading after `from`; otherwise the array length.\n * Feature sections greedily eat the heading's immediate body content\n * (paragraphs, lists, images) up to the next heading of any depth — a\n * nested sub-heading inside a feature would look chaotic in the\n * side-by-side layout, so we keep features short by design.\n */\nfunction findSectionEnd(nodes: MarkdownNode[], from: number): number {\n for (let i = from + 1; i < nodes.length; i++) {\n const n = nodes[i] as { type?: string };\n if (n && n.type === 'heading') return i;\n }\n return nodes.length;\n}\n\n/**\n * Render a feature section as `<section class=\"squisq-feature ...\">`.\n * The first image found in the body becomes the media column; the\n * heading + remaining content (with the image stripped, so it doesn't\n * appear twice) becomes the text column.\n */\nfunction renderFeatureSection(\n heading: MarkdownHeadingLike,\n bodyNodes: MarkdownNode[],\n side: 'leftFeature' | 'rightFeature',\n ctx: RenderCtx | undefined,\n): string {\n const headingTag = `h${Math.min(Math.max(heading.depth ?? 2, 1), 6)}`;\n const headingHtml = `<${headingTag}>${childrenToHtml({ children: heading.children }, ctx)}</${headingTag}>`;\n\n const featured = takeFirstImage(bodyNodes);\n const media = featured.image\n ? renderFeatureImage(featured.image, ctx)\n : '<div class=\"squisq-feature__media squisq-feature__media--empty\"></div>';\n\n const textHtml = [headingHtml, ...featured.remaining.map((n) => nodeToHtml(n, ctx))]\n .filter((s) => s.length > 0)\n .join('\\n');\n\n const sideClass = side === 'leftFeature' ? 'squisq-feature--left' : 'squisq-feature--right';\n\n return `<section class=\"squisq-feature ${sideClass}\">\n${media}\n<div class=\"squisq-feature__body\">\n${textHtml}\n</div>\n</section>`;\n}\n\ninterface FeaturedImage {\n src: string;\n alt: string;\n width?: number;\n height?: number;\n}\n\nfunction renderFeatureImage(img: FeaturedImage, ctx: RenderCtx | undefined): string {\n const resolved = ctx?.images?.get(img.src) ?? img.src;\n const safeSrc = sanitizeUrl(resolved, 'media');\n if (!safeSrc) return '<div class=\"squisq-feature__media squisq-feature__media--empty\"></div>';\n const attrs = [`src=\"${escapeAttr(safeSrc)}\"`, `alt=\"${escapeAttr(img.alt)}\"`];\n // Emit `width` / `height` attributes only when the source HTML had\n // them. The CSS rules then know to honor those values rather than\n // stretching the image to fill the column.\n if (typeof img.width === 'number') attrs.push(`width=\"${img.width}\"`);\n if (typeof img.height === 'number') attrs.push(`height=\"${img.height}\"`);\n const sizedClass =\n typeof img.width === 'number' || typeof img.height === 'number'\n ? ' squisq-feature__media--sized'\n : '';\n return `<div class=\"squisq-feature__media${sizedClass}\"><img ${attrs.join(' ')} /></div>`;\n}\n\n/**\n * Pull the first image reference out of a section's body — either a\n * markdown `image` node (possibly nested inside a paragraph) or a raw\n * HTML `<img>` (the WYSIWYG editor emits these for resized images).\n * Returns the image plus the body with that image removed (so it's not\n * rendered a second time inside the text column).\n */\nfunction takeFirstImage(nodes: MarkdownNode[]): {\n image: FeaturedImage | null;\n remaining: MarkdownNode[];\n} {\n for (let i = 0; i < nodes.length; i++) {\n const found = extractFirstImageFromBlock(nodes[i]);\n if (!found) continue;\n const remaining = [...nodes];\n if (found.replacement === null) {\n remaining.splice(i, 1);\n } else {\n remaining[i] = found.replacement;\n }\n return { image: found.image, remaining };\n }\n return { image: null, remaining: nodes };\n}\n\n/**\n * Look for the first image inside a single block node. If the block is\n * a paragraph that contains only the image (with optional whitespace),\n * we drop the whole paragraph; if there's surrounding text, we strip\n * the image from the paragraph's inline children and keep the rest.\n * For raw `<img>` html blocks we drop the block entirely.\n */\nfunction extractFirstImageFromBlock(\n block: MarkdownNode,\n): { image: FeaturedImage; replacement: MarkdownNode | null } | null {\n if (!block || typeof block !== 'object') return null;\n const b = block as unknown as Record<string, unknown>;\n if (b.type === 'paragraph' && Array.isArray(b.children)) {\n const kids = b.children as MarkdownNode[];\n const imgIdx = kids.findIndex(\n (k) =>\n (k as { type?: string }).type === 'image' &&\n typeof (k as { url?: unknown }).url === 'string',\n );\n if (imgIdx >= 0) {\n const img = kids[imgIdx] as { url: string; alt?: string };\n const remainingKids = [...kids.slice(0, imgIdx), ...kids.slice(imgIdx + 1)].filter(\n (k) => !isBlankInline(k),\n );\n const replacement =\n remainingKids.length === 0\n ? null\n : ({ ...(b as object), children: remainingKids } as MarkdownNode);\n return {\n image: { src: img.url, alt: img.alt ?? '' },\n replacement,\n };\n }\n }\n if (b.type === 'htmlBlock' && Array.isArray(b.htmlChildren)) {\n const hit = findHtmlImg(b.htmlChildren as HtmlNode[]);\n if (hit) return { image: hit, replacement: null };\n }\n return null;\n}\n\nfunction isBlankInline(node: MarkdownNode): boolean {\n if (!node || typeof node !== 'object') return false;\n const n = node as { type?: string; value?: unknown };\n return n.type === 'text' && typeof n.value === 'string' && n.value.trim().length === 0;\n}\n\nfunction findHtmlImg(nodes: HtmlNode[]): FeaturedImage | null {\n for (const n of nodes) {\n if (n.type !== 'htmlElement') continue;\n if (n.tagName.toLowerCase() === 'img') {\n const src = n.attributes.src;\n if (typeof src === 'string' && src) {\n return {\n src,\n alt: typeof n.attributes.alt === 'string' ? n.attributes.alt : '',\n width: parseHtmlDim(n.attributes.width),\n height: parseHtmlDim(n.attributes.height),\n };\n }\n }\n const nested = findHtmlImg(n.children);\n if (nested) return nested;\n }\n return null;\n}\n\nfunction parseHtmlDim(raw: string | undefined): number | undefined {\n if (raw === undefined) return undefined;\n const n = parseFloat(raw);\n return Number.isFinite(n) && n > 0 ? n : undefined;\n}\n\nconst FEATURE_CSS = ` .squisq-feature {\n display: flex;\n flex-wrap: wrap;\n gap: 1.5em;\n align-items: center;\n margin: 1.75em 0;\n }\n .squisq-feature--right { flex-direction: row-reverse; }\n .squisq-feature__media {\n flex: 0 0 42%;\n min-width: 0;\n }\n .squisq-feature__media--empty { display: none; }\n /* Default: image fills the media column. */\n .squisq-feature__media img {\n width: 100%;\n height: auto;\n display: block;\n border-radius: 6px;\n }\n /* \"Sized\" media: the image carried explicit width/height attrs (the\n WYSIWYG editor wrote them after a resize). Honor the attribute\n values and center the image inside the media column with padding. */\n .squisq-feature__media--sized {\n display: flex;\n align-items: center;\n justify-content: center;\n padding: 1em;\n box-sizing: border-box;\n }\n /* Leave width and height untouched here -- the HTML attributes set\n the intrinsic dimensions, and CSS overrides would silently discard\n the author's sizing. max-width: 100% still keeps the image from\n overflowing the column on narrow viewports; HTML5 derives the\n aspect ratio from the width/height pair so the scale stays right. */\n .squisq-feature__media--sized img {\n max-width: 100%;\n display: block;\n border-radius: 6px;\n }\n .squisq-feature__body {\n flex: 1 1 0;\n min-width: 0;\n }\n .squisq-feature__body > :first-child { margin-top: 0; }\n .squisq-feature__body > :last-child { margin-bottom: 0; }\n @media (max-width: 600px) {\n .squisq-feature, .squisq-feature--right { flex-direction: column; }\n .squisq-feature__media { flex-basis: auto; width: 100%; }\n }`;\n\n// ── Theme-driven CSS ───────────────────────────────────────────────\n\nconst DEFAULT_CSS = ` body { font-family: system-ui, -apple-system, sans-serif; max-width: 800px; margin: 2em auto; padding: 0 1em; line-height: 1.6; color: #1f2937; }\n h1, h2, h3, h4, h5, h6 { margin-top: 1.5em; margin-bottom: 0.5em; }\n pre { background: #f3f4f6; padding: 1em; border-radius: 4px; overflow-x: auto; }\n code { background: #f3f4f6; padding: 0.15em 0.3em; border-radius: 3px; font-size: 0.9em; }\n pre code { background: none; padding: 0; }\n blockquote { border-left: 3px solid #d1d5db; margin-left: 0; padding-left: 1em; color: #6b7280; }\n /* Images: cap at the container width so nothing overflows, but only\n force aspect-ratio height when the author didn't set explicit\n dimensions on the <img> tag. The WYSIWYG editor writes width/height\n attributes after a resize — overriding them here would silently\n ignore the user's sizing. */\n img { max-width: 100%; }\n img:not([width]):not([height]) { height: auto; }\n a { color: #3b82f6; }\n table { border-collapse: collapse; width: 100%; margin: 1em 0; }\n th, td { border: 1px solid #d1d5db; padding: 6px 10px; text-align: left; }\n th { background: #f3f4f6; font-weight: 600; }\n hr { border: none; border-top: 1px solid #d1d5db; margin: 1.5em 0; }`;\n\n/**\n * Build the stylesheet for a themed page. We resolve fonts and colors\n * to CSS-ready strings up front (rather than emitting `--squisq-*`\n * custom properties everywhere) so the output works in environments\n * that strip CSS variables.\n */\nfunction renderThemedCss(theme: Theme): string {\n const bodyFamily = resolveFontFamily(theme.typography.bodyFont, 'system-ui, sans-serif');\n const titleFamily = resolveFontFamily(theme.typography.titleFont, 'Georgia, serif');\n const monoFamily = resolveFontFamily(theme.typography.monoFont, 'Consolas, monospace');\n const lineHeight = theme.typography.lineHeight ?? 1.6;\n const titleLineHeight = theme.typography.titleLineHeight ?? 1.25;\n const titleWeight = theme.typography.titleWeight === 'normal' ? 400 : 700;\n const c = theme.colors;\n // A few derived colors so the page doesn't end up unreadable on\n // themes that swing dark (most code/table chrome reads as \"dim panel\n // on background\"). We do this in CSS, not in JS, by mixing with\n // `color-mix` so older browsers without it still get a sensible look.\n return ` :root {\n --plain-bg: ${c.background};\n --plain-text: ${c.text};\n --plain-muted: ${c.textMuted};\n --plain-primary: ${c.primary};\n --plain-secondary: ${c.secondary};\n --plain-accent: ${c.highlight};\n --plain-bg-light: ${c.backgroundLight};\n --plain-body-font: ${bodyFamily};\n --plain-title-font: ${titleFamily};\n --plain-mono-font: ${monoFamily};\n }\n body {\n font-family: var(--plain-body-font);\n max-width: 800px;\n margin: 2em auto;\n padding: 0 1em;\n line-height: ${lineHeight};\n color: var(--plain-text);\n background: var(--plain-bg);\n }\n h1, h2, h3, h4, h5, h6 {\n font-family: var(--plain-title-font);\n color: var(--plain-text);\n margin-top: 1.5em;\n margin-bottom: 0.5em;\n line-height: ${titleLineHeight};\n font-weight: ${titleWeight};\n }\n p { margin: 0.75em 0; }\n pre {\n background: var(--plain-bg-light);\n padding: 1em;\n border-radius: 4px;\n overflow-x: auto;\n font-family: var(--plain-mono-font);\n }\n code {\n background: var(--plain-bg-light);\n padding: 0.15em 0.3em;\n border-radius: 3px;\n font-size: 0.9em;\n font-family: var(--plain-mono-font);\n }\n pre code { background: none; padding: 0; }\n blockquote {\n border-left: 3px solid var(--plain-primary);\n margin-left: 0;\n padding-left: 1em;\n color: var(--plain-muted);\n }\n /* See DEFAULT_CSS comment: only auto-scale height when the author\n didn't set explicit width/height attributes. */\n img { max-width: 100%; }\n img:not([width]):not([height]) { height: auto; }\n /* Blend the theme primary toward the body text color so links stay\n theme-flavored but read clearly on every theme's background — some\n themes (e.g. Gezellig) pick a mid-tone primary that's almost\n invisible on a dark page when used neat. Underline makes the link\n unambiguous independent of the color contrast. */\n a {\n color: color-mix(in srgb, var(--plain-primary) 65%, var(--plain-text));\n text-decoration: underline;\n text-decoration-thickness: 1px;\n text-underline-offset: 2px;\n }\n a:hover { color: var(--plain-accent); }\n table { border-collapse: collapse; width: 100%; margin: 1em 0; }\n th, td {\n border: 1px solid color-mix(in srgb, var(--plain-muted) 30%, transparent);\n padding: 6px 10px;\n text-align: left;\n }\n th {\n background: var(--plain-bg-light);\n color: var(--plain-text);\n font-family: var(--plain-title-font);\n font-weight: 600;\n }\n hr {\n border: none;\n border-top: 1px solid color-mix(in srgb, var(--plain-muted) 40%, transparent);\n margin: 1.5em 0;\n }`;\n}\n\n/**\n * Emit the `<link rel=\"stylesheet\">` line(s) needed to load the theme's\n * fonts. Empty string when nothing in the theme is hosted on Google\n * Fonts (system stacks, custom self-hosted faces).\n */\nfunction renderFontsLink(theme: Theme): string {\n const url = buildGoogleFontsUrl([\n theme.typography.bodyFont,\n theme.typography.titleFont,\n theme.typography.monoFont,\n ]);\n if (!url) return '';\n // Preconnect to Google's CDN to shave a roundtrip off the font load.\n return `<link rel=\"preconnect\" href=\"https://fonts.googleapis.com\">\n<link rel=\"preconnect\" href=\"https://fonts.gstatic.com\" crossorigin>\n<link rel=\"stylesheet\" href=\"${escapeAttr(url)}\">\n`;\n}\n\n// ── Node → HTML ────────────────────────────────────────────────────\n\nfunction nodeToHtml(node: MarkdownNode | undefined | null, ctx?: RenderCtx): string {\n if (!node) return '';\n switch (node.type) {\n case 'heading': {\n const depth = Math.min(Math.max(node.depth ?? 1, 1), 6);\n return `<h${depth}>${childrenToHtml(node, ctx)}</h${depth}>`;\n }\n case 'paragraph':\n return `<p>${childrenToHtml(node, ctx)}</p>`;\n case 'text':\n return escapeHtml(node.value ?? '');\n case 'strong':\n return `<strong>${childrenToHtml(node, ctx)}</strong>`;\n case 'emphasis':\n return `<em>${childrenToHtml(node, ctx)}</em>`;\n case 'delete':\n return `<del>${childrenToHtml(node, ctx)}</del>`;\n case 'inlineCode':\n return `<code>${escapeHtml(node.value ?? '')}</code>`;\n case 'code': {\n const lang = node.lang ? ` class=\"language-${escapeAttr(node.lang)}\"` : '';\n return `<pre><code${lang}>${escapeHtml(node.value ?? '')}</code></pre>`;\n }\n case 'blockquote':\n return `<blockquote>${childrenToHtml(node, ctx)}</blockquote>`;\n case 'list': {\n const tag = node.ordered ? 'ol' : 'ul';\n const start =\n node.ordered && typeof node.start === 'number' && node.start !== 1\n ? ` start=\"${node.start}\"`\n : '';\n return `<${tag}${start}>${childrenToHtml(node, ctx)}</${tag}>`;\n }\n case 'listItem':\n return `<li>${childrenToHtml(node, ctx)}</li>`;\n case 'link': {\n const original = node.url ?? '';\n const rewritten = ctx?.links?.get(original) ?? original;\n const safeHref = sanitizeUrl(rewritten, 'link');\n if (!safeHref) return childrenToHtml(node, ctx);\n return `<a href=\"${escapeAttr(safeHref)}\">${childrenToHtml(node, ctx)}</a>`;\n }\n case 'image': {\n const original = node.url ?? '';\n const resolved = ctx?.images?.get(original) ?? original;\n const safeSrc = sanitizeUrl(resolved, 'media');\n if (!safeSrc) return escapeHtml(node.alt ?? '');\n return `<img src=\"${escapeAttr(safeSrc)}\" alt=\"${escapeAttr(node.alt ?? '')}\" />`;\n }\n case 'thematicBreak':\n return '<hr />';\n case 'table':\n return tableToHtml(node, ctx);\n case 'inlineIcon': {\n // Render via FontAwesome's `fa-<family> fa-<name>` class pair.\n // The `<link>` to the FA CSS is injected into <head> by\n // `markdownDocToPlainHtml` when the doc contains any icon.\n const family = escapeAttr(node.family ?? 'solid');\n const name = escapeAttr(node.name ?? '');\n const token = escapeAttr(node.token ?? `${node.family}:${node.name}`);\n return `<i class=\"fa-${family} fa-${name}\" data-icon=\"${token}\" aria-hidden=\"true\"></i>`;\n }\n case 'htmlBlock':\n case 'htmlInline':\n if (resolveHtmlPolicy(ctx) === 'strip') return '';\n // Resized images and other authored HTML survive the round-trip\n // as parsed `htmlChildren` — rewriting `<img src>` through the\n // image map keeps the preview consistent with the markdown-image\n // path. Other tags pass through unmodified.\n return htmlChildrenToHtml(resolveHtmlNodes(node.htmlChildren, ctx), ctx);\n default: {\n // Unknown / unhandled node — recurse into children if any so we\n // don't drop content (e.g. directives, footnotes).\n const withChildren = node as { children?: unknown; value?: unknown };\n if (Array.isArray(withChildren.children)) {\n return childrenToHtml(node as { children: MarkdownNode[] }, ctx);\n }\n if (typeof withChildren.value === 'string') {\n return escapeHtml(withChildren.value);\n }\n return '';\n }\n }\n}\n\nfunction childrenToHtml(\n node: { children?: MarkdownNode[]; value?: string },\n ctx?: RenderCtx,\n): string {\n if (!node.children) return node.value ? escapeHtml(node.value) : '';\n return node.children.map((child) => nodeToHtml(child, ctx)).join('');\n}\n\nfunction tableToHtml(\n node: { children: { children: { children: MarkdownNode[]; isHeader?: boolean }[] }[] },\n ctx?: RenderCtx,\n): string {\n const [headerRow, ...bodyRows] = node.children;\n const parts: string[] = ['<table>'];\n if (headerRow) {\n parts.push('<thead><tr>');\n for (const cell of headerRow.children) {\n parts.push(`<th>${childrenToHtml(cell, ctx)}</th>`);\n }\n parts.push('</tr></thead>');\n }\n if (bodyRows.length > 0) {\n parts.push('<tbody>');\n for (const row of bodyRows) {\n parts.push('<tr>');\n for (const cell of row.children) {\n parts.push(`<td>${childrenToHtml(cell, ctx)}</td>`);\n }\n parts.push('</tr>');\n }\n parts.push('</tbody>');\n }\n parts.push('</table>');\n return parts.join('');\n}\n\nfunction resolveHtmlPolicy(ctx: RenderCtx | undefined): HtmlPolicy {\n return ctx?.htmlPolicy ?? 'sanitize';\n}\n\nfunction resolveHtmlNodes(nodes: HtmlNode[] | undefined, ctx: RenderCtx | undefined): HtmlNode[] {\n if (!nodes || nodes.length === 0) return [];\n return resolveHtmlPolicy(ctx) === 'trusted' ? nodes : sanitizeHtmlNodes(nodes);\n}\n\nfunction htmlChildrenToHtml(nodes: HtmlNode[] | undefined, ctx?: RenderCtx): string {\n if (!nodes || nodes.length === 0) return '';\n const trusted = resolveHtmlPolicy(ctx) === 'trusted';\n const out: string[] = [];\n for (const node of nodes) {\n if (node.type === 'htmlText') {\n // HtmlText already represents authored HTML — emit verbatim so\n // entity references the user wrote (e.g. `&amp;`) survive.\n out.push(trusted ? node.value : escapeHtml(node.value));\n continue;\n }\n if (node.type === 'htmlComment') {\n if (trusted) out.push(`<!--${node.value}-->`);\n continue;\n }\n // htmlElement\n const tag = node.tagName.toLowerCase();\n const attrs = { ...node.attributes };\n // Route media `src` attrs through the export's `ctx.images` URL\n // map (which is actually a generic media map — see header comment).\n // Without this, raw <video>/<audio> tags inserted by the recorder\n // ship as relative paths that resolve against the export host, not\n // the original ContentContainer.\n if (\n (tag === 'img' || tag === 'video' || tag === 'audio' || tag === 'source') &&\n typeof attrs.src === 'string'\n ) {\n attrs.src = ctx?.images?.get(attrs.src) ?? attrs.src;\n }\n if ((tag === 'video' || tag === 'audio') && typeof attrs.poster === 'string') {\n attrs.poster = ctx?.images?.get(attrs.poster) ?? attrs.poster;\n }\n if (!trusted) {\n if (typeof attrs.src === 'string') {\n const safeSrc = sanitizeUrl(attrs.src, 'media');\n if (!safeSrc) delete attrs.src;\n else attrs.src = safeSrc;\n }\n if (typeof attrs.poster === 'string') {\n const safePoster = sanitizeUrl(attrs.poster, 'media');\n if (!safePoster) delete attrs.poster;\n else attrs.poster = safePoster;\n }\n if (typeof attrs.href === 'string') {\n const safeHref = sanitizeUrl(attrs.href, 'link');\n if (!safeHref) delete attrs.href;\n else attrs.href = safeHref;\n }\n }\n const attrStr = Object.entries(attrs)\n .map(([k, v]) => ` ${k}=\"${escapeAttr(v)}\"`)\n .join('');\n if (node.selfClosing) {\n out.push(`<${tag}${attrStr} />`);\n } else {\n out.push(`<${tag}${attrStr}>${htmlChildrenToHtml(node.children, ctx)}</${tag}>`);\n }\n }\n return out.join('');\n}\n\n// ── Escaping ───────────────────────────────────────────────────────\n\nfunction escapeHtml(s: string): string {\n return s.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');\n}\n\nfunction escapeAttr(s: string): string {\n return escapeHtml(s).replace(/\"/g, '&quot;');\n}\n","/**\n * Recursive plain-HTML bundle export.\n *\n * `markdownDocsToPlainHtmlBundle` starts from a single entry markdown\n * file, walks its relative `[…](other.md)` links, recursively pulls in\n * any sibling/child documents (scope-limited to the entry doc's\n * directory tree), renders every visited file via\n * `markdownDocToPlainHtml`, rewrites cross-doc references from `.md`\n * to `.html`, and ships everything as a single ZIP.\n *\n * The function is provider-agnostic — callers pass `readDocument` and\n * `readBinary` callbacks that resolve relative paths against whatever\n * storage they use (`FileSystemContentContainer`, `MemoryContent-\n * Container`, in-memory map for tests, …). Failure to read any\n * discovered file aborts the whole export with a thrown error.\n */\n\nimport JSZip from 'jszip';\nimport { parseMarkdown, inferDocumentTitle } from '@bendyline/squisq/markdown';\nimport type { MarkdownDocument, HtmlNode } from '@bendyline/squisq/markdown';\nimport type { Theme } from '@bendyline/squisq/schemas';\nimport { resolveTheme } from '@bendyline/squisq/schemas';\nimport { markdownDocToPlainHtml } from './plainHtml.js';\n\n// ── Public Types ───────────────────────────────────────────────────\n\nexport interface PlainHtmlBundleOptions {\n /** Entry document path relative to the container root (e.g. `'home.md'`). */\n entryPath: string;\n /** Reads a UTF-8 markdown file from the container. Returns null when absent. */\n readDocument: (path: string) => Promise<string | null>;\n /** Reads a binary asset (image) from the container. Returns null when absent. */\n readBinary: (path: string) => Promise<ArrayBuffer | null>;\n /** Optional document title for the entry. Others derive from filename. */\n title?: string;\n /** Optional theme applied uniformly to every page. Overrides {@link themeId}. */\n theme?: Theme;\n /**\n * Optional theme id (e.g. `'warm-earth'`, `'gezellig'`). Resolved via\n * `resolveTheme` and applied to every page. Convenient for callers\n * that track themes by id (like the host export dialog) without\n * having to resolve to a `Theme` object first. When both `theme` and\n * `themeId` are supplied, `theme` wins.\n */\n themeId?: string;\n /** Maximum recursion depth (default: unlimited; cycles always handled). */\n maxDepth?: number;\n /**\n * Emit the entry doc as `index.html` (preserving its parent directory)\n * instead of `<basename>.html`. Cross-doc links pointing at the entry\n * also rewrite to `index.html`, so a sibling `resume.md → home.md`\n * link doesn't 404 after the rename. Convenient for static-site\n * deploys where the landing page must be named `index.html`.\n * Default: false.\n */\n entryAsIndex?: boolean;\n}\n\n// ── Public API ─────────────────────────────────────────────────────\n\n/**\n * Render an entry markdown document and every reachable sibling/child\n * `.md` document it links to, bundled as a single ZIP with plain-HTML\n * pages, per-document asset folders, and cross-doc `<a href>`\n * references rewritten from `.md` to `.html`.\n */\nexport async function markdownDocsToPlainHtmlBundle(\n options: PlainHtmlBundleOptions,\n): Promise<Blob> {\n const {\n entryPath,\n readDocument,\n readBinary,\n title,\n theme,\n themeId,\n maxDepth = Infinity,\n entryAsIndex = false,\n } = options;\n const resolvedTheme = theme ?? (themeId ? resolveTheme(themeId) : undefined);\n const entry = normalizePath(entryPath);\n if (!entry) {\n throw new Error('markdownDocsToPlainHtmlBundle: entryPath is required');\n }\n const scopeRoot = posixDirname(entry); // '' for root-level files\n // When `entryAsIndex`, the entry doc writes to `<entryDir>/index.html`\n // and every cross-doc link pointing at it rewrites to that path too.\n // Other docs keep the `<basename>.html` convention.\n const entryHtmlPath = entryAsIndex\n ? scopeRoot\n ? `${scopeRoot}/index.html`\n : 'index.html'\n : entry.slice(0, -3) + '.html';\n const htmlPathFor = (mdPath: string): string =>\n mdPath === entry ? entryHtmlPath : mdPath.slice(0, -3) + '.html';\n\n const zip = new JSZip();\n const visited = new Set<string>();\n const queue: Array<{ path: string; depth: number }> = [{ path: entry, depth: 0 }];\n\n while (queue.length > 0) {\n const { path, depth } = queue.shift()!;\n if (visited.has(path)) continue;\n visited.add(path);\n\n const source = await readDocument(path);\n if (source === null) {\n throw new Error(`markdownDocsToPlainHtmlBundle: failed to read \"${path}\"`);\n }\n const mdDoc = parseMarkdown(source);\n\n // Discover this doc's relative .md links — both for enqueueing new\n // targets and for building the per-doc link rewrite map. We resolve\n // each link to a canonical container-relative path so the same doc\n // referenced two different ways (./resume.md vs resume.md) is\n // visited once and rewritten consistently.\n const docDir = posixDirname(path);\n const linkMap = new Map<string, string>();\n for (const raw of collectLinkRefs(mdDoc)) {\n const parsed = parseLinkRef(raw);\n if (!parsed) continue;\n const resolved = resolveRelative(docDir, parsed.path);\n if (resolved === null) continue; // escaped via ..\n if (!isInScope(resolved, scopeRoot)) continue;\n if (!resolved.toLowerCase().endsWith('.md')) continue;\n\n // Compute the .html replacement relative to the current doc so\n // the rewritten href stays local (e.g. `subdir/notes.html`, not\n // an absolute container path). `htmlPathFor` also handles the\n // entry-as-index case so a sibling linking to the entry lands\n // on `index.html` after rename.\n const htmlTarget = htmlPathFor(resolved);\n const relHref = relativeFrom(docDir, htmlTarget) + parsed.fragment;\n linkMap.set(raw, relHref);\n\n if (depth + 1 <= maxDepth && !visited.has(resolved)) {\n queue.push({ path: resolved, depth: depth + 1 });\n }\n }\n\n // Walk images, fetch each one, write at the original relative path\n // resolved against the container root (so `resume_files/hero.png`\n // sits next to `resume.html` in the zip). Failures here are *not*\n // fatal — images can be missing without breaking the document\n // structure — but we surface them as console warnings.\n const images = await readImagesForDoc(mdDoc, docDir, readBinary);\n for (const [, { data, zipPath }] of images) {\n const safe = sanitizeZipPath(zipPath);\n if (!safe) continue;\n zip.file(safe, data);\n }\n\n // Render the document. The image rewrite map keys are the URLs\n // exactly as authored (so `markdownDocToPlainHtml` can match them\n // verbatim), and the values are paths relative to the rendered\n // doc's own location — so `<img src>` works after unzip without\n // any post-processing.\n const imageRewriteMap = new Map<string, string>();\n for (const [authored, { zipPath }] of images) {\n imageRewriteMap.set(authored, relativeFrom(docDir, zipPath));\n }\n\n const docTitle = depth === 0 ? title : undefined;\n const html = markdownDocToPlainHtml(mdDoc, {\n title: docTitle ?? titleForFilename(path, mdDoc),\n images: imageRewriteMap,\n links: linkMap,\n theme: resolvedTheme,\n });\n\n const htmlPath = htmlPathFor(path);\n zip.file(htmlPath, html);\n }\n\n return zip.generateAsync({\n type: 'blob',\n compression: 'DEFLATE',\n compressionOptions: { level: 6 },\n });\n}\n\n// ── Link discovery ─────────────────────────────────────────────────\n\n/**\n * Collect every `<a>`-style link URL referenced in a document. Markdown\n * `link` nodes plus any raw HTML `<a href>` tags. Returns the raw URLs\n * as authored, so callers can use them as both the linkMap *key* and\n * the basis for resolution.\n */\nexport function collectLinkRefs(doc: MarkdownDocument): Set<string> {\n const refs = new Set<string>();\n\n function visitHtml(nodes: HtmlNode[]): void {\n for (const n of nodes) {\n if (n.type !== 'htmlElement') continue;\n if (n.tagName.toLowerCase() === 'a') {\n const href = n.attributes.href;\n if (typeof href === 'string' && href) refs.add(href);\n }\n visitHtml(n.children);\n }\n }\n\n function visit(node: unknown): void {\n if (!node || typeof node !== 'object') return;\n const n = node as Record<string, unknown>;\n if (n.type === 'link' && typeof n.url === 'string' && n.url) {\n refs.add(n.url);\n }\n if ((n.type === 'htmlBlock' || n.type === 'htmlInline') && Array.isArray(n.htmlChildren)) {\n visitHtml(n.htmlChildren as HtmlNode[]);\n }\n if (Array.isArray(n.children)) {\n for (const child of n.children) visit(child);\n }\n }\n\n for (const child of doc.children) visit(child);\n return refs;\n}\n\nfunction collectImageRefs(doc: MarkdownDocument): Set<string> {\n const refs = new Set<string>();\n function visitHtml(nodes: HtmlNode[]): void {\n for (const n of nodes) {\n if (n.type !== 'htmlElement') continue;\n const tag = n.tagName.toLowerCase();\n // <img>/<video>/<audio>/<source> all reference media via `src`;\n // we feed them into the same `images` map (effectively a generic\n // media map — see header comment) so the export pipeline rewrites\n // and bundles each one the same way.\n if (tag === 'img' || tag === 'video' || tag === 'audio' || tag === 'source') {\n const src = n.attributes.src;\n if (typeof src === 'string' && src) refs.add(src);\n }\n if (tag === 'video' || tag === 'audio') {\n const poster = n.attributes.poster;\n if (typeof poster === 'string' && poster) refs.add(poster);\n }\n visitHtml(n.children);\n }\n }\n function visit(node: unknown): void {\n if (!node || typeof node !== 'object') return;\n const n = node as Record<string, unknown>;\n if (n.type === 'image' && typeof n.url === 'string' && n.url) refs.add(n.url);\n if ((n.type === 'htmlBlock' || n.type === 'htmlInline') && Array.isArray(n.htmlChildren)) {\n visitHtml(n.htmlChildren as HtmlNode[]);\n }\n if (Array.isArray(n.children)) for (const c of n.children) visit(c);\n }\n for (const child of doc.children) visit(child);\n return refs;\n}\n\ninterface ParsedLinkRef {\n /** Pathname portion before any `#` or `?`. */\n path: string;\n /** `#fragment` suffix (with leading `#`) or empty string. */\n fragment: string;\n}\n\n/**\n * Split an authored URL into path + fragment, returning null for\n * external / non-document references (http(s), mailto, data, blob,\n * absolute paths, fragment-only). The fragment is preserved so a\n * link like `resume.md#experience` rewrites cleanly to\n * `resume.html#experience`.\n */\nfunction parseLinkRef(url: string): ParsedLinkRef | null {\n if (!url) return null;\n if (url.startsWith('#')) return null; // intra-doc anchor\n if (\n /^[a-z][a-z0-9+.-]*:/.test(url) || // http:, https:, mailto:, ftp:, …\n url.startsWith('//') ||\n url.startsWith('/')\n ) {\n return null;\n }\n const hashIdx = url.indexOf('#');\n const queryIdx = url.indexOf('?');\n const cut =\n hashIdx >= 0 && queryIdx >= 0 ? Math.min(hashIdx, queryIdx) : hashIdx >= 0 ? hashIdx : queryIdx;\n const path = cut >= 0 ? url.slice(0, cut) : url;\n const fragment = hashIdx >= 0 ? url.slice(hashIdx) : '';\n return { path, fragment };\n}\n\n// ── Image gathering ────────────────────────────────────────────────\n\n/**\n * Resolve every image referenced by a document to container-relative\n * paths and fetch their bytes. The returned map keys are the authored\n * URLs (so the renderer's `images` map can substitute them); the values\n * are tuples of `[bytes, zipPath]` where `zipPath` is the path used\n * inside the zip.\n *\n * Images that can't be read are silently dropped — the rendered HTML\n * will keep its `<img src>` pointing at the authored URL, which a\n * reader's browser will 404. That matches the user-facing \"abort on\n * missing linked doc\" rule which applies to `.md` links only.\n */\nasync function readImagesForDoc(\n mdDoc: MarkdownDocument,\n docDir: string,\n readBinary: (path: string) => Promise<ArrayBuffer | null>,\n): Promise<Map<string, { data: ArrayBuffer; zipPath: string }>> {\n const out = new Map<string, { data: ArrayBuffer; zipPath: string }>();\n for (const authored of collectImageRefs(mdDoc)) {\n if (/^[a-z][a-z0-9+.-]*:/.test(authored) || authored.startsWith('//')) continue;\n const cleanAuthored = authored.replace(/[#?].*$/, '');\n const resolved = resolveRelative(docDir, cleanAuthored);\n if (resolved === null) continue;\n const data = await readBinary(resolved);\n if (!data) continue;\n out.set(authored, { data, zipPath: resolved });\n }\n return out;\n}\n\n// ── Path helpers (POSIX-style, no Node `path`) ─────────────────────\n\n/**\n * Strip a path to its parent directory (POSIX-style). Returns `''`\n * for root-level files so subsequent joins read like a fresh path.\n */\nexport function posixDirname(p: string): string {\n const idx = p.lastIndexOf('/');\n return idx < 0 ? '' : p.slice(0, idx);\n}\n\n/**\n * Normalize a POSIX path: collapse `./`, resolve `..`, drop trailing\n * slashes, never produce a leading `/`. Returns null when the path\n * escapes the start (\"..\").\n */\nexport function normalizePath(p: string): string | null {\n const parts = p.split('/');\n const out: string[] = [];\n for (const segment of parts) {\n if (segment === '' || segment === '.') continue;\n if (segment === '..') {\n if (out.length === 0) return null;\n out.pop();\n continue;\n }\n out.push(segment);\n }\n return out.join('/');\n}\n\n/**\n * Resolve `rel` against `baseDir`. Returns null when the result\n * escapes the container root via `..`.\n */\nexport function resolveRelative(baseDir: string, rel: string): string | null {\n if (rel.startsWith('/')) return null; // absolute paths are out of scope\n const joined = baseDir ? `${baseDir}/${rel}` : rel;\n return normalizePath(joined);\n}\n\n/**\n * True when `target` is inside `root` (or equal to it). Empty root\n * means \"entire container is in scope\".\n */\nexport function isInScope(target: string, root: string): boolean {\n if (!root) return true;\n return target === root || target.startsWith(root + '/');\n}\n\n/**\n * Compute a relative path from `fromDir` to `toPath` (both POSIX-\n * normalized, container-relative). Used to emit hrefs that work after\n * unzipping anywhere — `home.md → subfolder/notes.md` becomes\n * `subfolder/notes.html`; `subfolder/intro.md → resume.md` becomes\n * `../resume.html`.\n */\nexport function relativeFrom(fromDir: string, toPath: string): string {\n const fromParts = fromDir ? fromDir.split('/') : [];\n const toParts = toPath.split('/');\n let common = 0;\n while (\n common < fromParts.length &&\n common < toParts.length - 1 &&\n fromParts[common] === toParts[common]\n ) {\n common++;\n }\n const up = fromParts.length - common;\n const down = toParts.slice(common);\n const prefix = Array(up).fill('..').join('/');\n if (!prefix) return down.join('/');\n return `${prefix}/${down.join('/')}`;\n}\n\n/**\n * Sanitize a path for use as a zip entry. Strips leading slashes,\n * normalizes backslashes (Windows-authored paths), and rejects any\n * path containing `..` segments after normalization (defensive — the\n * resolver above already filters those out for links).\n */\nfunction sanitizeZipPath(path: string): string | null {\n const normalized = path.replace(/\\\\/g, '/').replace(/^\\/+/, '');\n if (!normalized) return null;\n if (normalized.split('/').some((seg) => seg === '..')) return null;\n return normalized;\n}\n\n/**\n * Derive a default page title for a non-entry doc: prefer frontmatter\n * `title`, fall back to the shallowest heading text, then the filename\n * without extension.\n */\nfunction titleForFilename(path: string, mdDoc: MarkdownDocument): string {\n const inferred = inferDocumentTitle(mdDoc);\n if (inferred) return inferred;\n const base = path.split('/').pop() ?? path;\n return base.replace(/\\.md$/i, '');\n}\n","/**\n * Recursive rendered-HTML bundle export.\n *\n * `markdownDocsToHtmlBundle` is the rendered-pipeline counterpart of\n * `markdownDocsToPlainHtmlBundle`. Starting from a single entry markdown\n * file, it walks every relative `[…](other.md)` link, builds a Doc for\n * each visited page via `markdownToDoc`, rewrites cross-doc link URLs\n * inside the Doc tree from `.md` → relative `.html`, then renders each\n * Doc to a SquisqPlayer-backed HTML page sharing one `squisq-player.js`.\n * Images preserve their authored relative paths so per-doc folders\n * (`resume_files/hero.png`, etc.) sit beside the rendered HTML.\n *\n * The rewrite happens on the Doc, *before* JSON serialization, because\n * the rendered HTML embeds the Doc as JSON and SquisqPlayer emits the\n * `<a href>` at runtime — there is no post-render link rewrite hook in\n * the player path.\n */\n\nimport JSZip from 'jszip';\nimport { parseMarkdown, inferDocumentTitle } from '@bendyline/squisq/markdown';\nimport type {\n MarkdownDocument,\n MarkdownBlockNode,\n MarkdownInlineNode,\n HtmlNode,\n} from '@bendyline/squisq/markdown';\nimport { markdownToDoc } from '@bendyline/squisq/doc';\nimport type { Block, Doc } from '@bendyline/squisq/schemas';\nimport { generateExternalHtml } from './htmlTemplate.js';\nimport {\n collectLinkRefs,\n isInScope,\n normalizePath,\n posixDirname,\n relativeFrom,\n resolveRelative,\n} from './plainHtmlBundle.js';\n\n// ── Public Types ───────────────────────────────────────────────────\n\nexport interface HtmlBundleOptions {\n /** Entry document path relative to the container root (e.g. `'home.md'`). */\n entryPath: string;\n /** Reads a UTF-8 markdown file from the container. Returns null when absent. */\n readDocument: (path: string) => Promise<string | null>;\n /** Reads a binary asset (image) from the container. Returns null when absent. */\n readBinary: (path: string) => Promise<ArrayBuffer | null>;\n /** SquisqPlayer IIFE bundle. Written once as `squisq-player.js` in the zip. */\n playerScript: string;\n /** Optional document title for the entry. Other pages derive from filename. */\n title?: string;\n /** Theme id applied uniformly to every page. */\n themeId?: string;\n /** Rendering mode for every page (default: 'static' — scrollable, link-friendly). */\n mode?: 'slideshow' | 'static';\n /** Maximum recursion depth (default: unlimited; cycles always handled). */\n maxDepth?: number;\n /**\n * Emit the entry doc as `index.html` (preserving its parent directory)\n * instead of `<basename>.html`. Cross-doc links pointing at the entry\n * also rewrite to `index.html`, so a sibling `resume.md → home.md`\n * link doesn't 404 after the rename. Convenient for static-site\n * deploys where the landing page must be named `index.html`.\n * Default: false.\n */\n entryAsIndex?: boolean;\n}\n\n// ── Public API ─────────────────────────────────────────────────────\n\n/**\n * Render an entry markdown document and every reachable sibling/child\n * `.md` document it links to, bundled as a single ZIP with one rendered\n * HTML page per doc, per-document asset folders, a shared\n * `squisq-player.js`, and cross-doc `<a href>` references rewritten\n * from `.md` to `.html` inside each Doc's serialized AST.\n */\nexport async function markdownDocsToHtmlBundle(options: HtmlBundleOptions): Promise<Blob> {\n const {\n entryPath,\n readDocument,\n readBinary,\n playerScript,\n title,\n themeId,\n mode = 'static',\n maxDepth = Infinity,\n entryAsIndex = false,\n } = options;\n\n const entry = normalizePath(entryPath);\n if (!entry) {\n throw new Error('markdownDocsToHtmlBundle: entryPath is required');\n }\n const scopeRoot = posixDirname(entry);\n // When `entryAsIndex`, the entry doc writes to `<entryDir>/index.html`\n // and every cross-doc link pointing at it rewrites to that path too.\n // Other docs keep the `<basename>.html` convention.\n const entryHtmlPath = entryAsIndex\n ? scopeRoot\n ? `${scopeRoot}/index.html`\n : 'index.html'\n : entry.slice(0, -3) + '.html';\n const htmlPathFor = (mdPath: string): string =>\n mdPath === entry ? entryHtmlPath : mdPath.slice(0, -3) + '.html';\n\n const zip = new JSZip();\n zip.file('squisq-player.js', playerScript);\n\n const visited = new Set<string>();\n const queue: Array<{ path: string; depth: number }> = [{ path: entry, depth: 0 }];\n\n while (queue.length > 0) {\n const { path, depth } = queue.shift()!;\n if (visited.has(path)) continue;\n visited.add(path);\n\n const source = await readDocument(path);\n if (source === null) {\n throw new Error(`markdownDocsToHtmlBundle: failed to read \"${path}\"`);\n }\n const mdDoc = parseMarkdown(source);\n\n const docDir = posixDirname(path);\n const linkMap = new Map<string, string>();\n for (const raw of collectLinkRefs(mdDoc)) {\n const parsed = parseInternalRef(raw);\n if (!parsed) continue;\n const resolved = resolveRelative(docDir, parsed.path);\n if (resolved === null) continue;\n if (!isInScope(resolved, scopeRoot)) continue;\n if (!resolved.toLowerCase().endsWith('.md')) continue;\n\n const htmlTarget = htmlPathFor(resolved);\n const relHref = relativeFrom(docDir, htmlTarget) + parsed.fragment;\n linkMap.set(raw, relHref);\n\n if (depth + 1 <= maxDepth && !visited.has(resolved)) {\n queue.push({ path: resolved, depth: depth + 1 });\n }\n }\n\n // Gather and stash images per doc. Same convention as the plain\n // bundle: keep authored relative paths so `<img src>` paths inside\n // the Doc still resolve after unzip. Missing assets are non-fatal.\n const imageRewriteMap = new Map<string, string>();\n for (const authored of collectImageRefs(mdDoc)) {\n if (/^[a-z][a-z0-9+.-]*:/i.test(authored) || authored.startsWith('//')) continue;\n const clean = authored.replace(/[#?].*$/, '');\n const resolvedImg = resolveRelative(docDir, clean);\n if (resolvedImg === null) continue;\n const data = await readBinary(resolvedImg);\n if (!data) continue;\n const safe = sanitizeZipPath(resolvedImg);\n if (!safe) continue;\n zip.file(safe, data);\n imageRewriteMap.set(authored, relativeFrom(docDir, resolvedImg));\n }\n\n // markdownToDoc embeds the markdown AST in `block.contents`, so the\n // link rewrite must walk that tree on every Doc before we serialize\n // it into the rendered HTML.\n let doc = markdownToDoc(mdDoc);\n if (themeId) doc = { ...doc, themeId };\n if (linkMap.size > 0) {\n doc = rewriteDocLinks(doc, linkMap);\n }\n\n const pageTitle = depth === 0 ? (title ?? titleForDoc(path, mdDoc)) : titleForDoc(path, mdDoc);\n const htmlPath = htmlPathFor(path);\n const playerScriptPath = relativeFrom(docDir, 'squisq-player.js');\n\n const html = generateExternalHtml(doc, {\n playerScriptPath,\n // Convert Map → plain record for the template (which expects a\n // record). The renderer reads this map at runtime to swap image\n // sources before rendering each block.\n imagePathMap:\n imageRewriteMap.size > 0 ? Object.fromEntries(imageRewriteMap.entries()) : undefined,\n mode,\n title: pageTitle,\n });\n zip.file(htmlPath, html);\n }\n\n return zip.generateAsync({\n type: 'blob',\n compression: 'DEFLATE',\n compressionOptions: { level: 6 },\n });\n}\n\n// ── Link rewriting ─────────────────────────────────────────────────\n\n/**\n * Return a deep-cloned Doc with every `[text](url)` link URL inside\n * `block.contents` rewritten through `linkMap`, including raw HTML\n * `<a href=\"…\">` tags carried inside `htmlBlock`/`htmlInline` nodes.\n * Authoring keys that aren't in the map pass through untouched.\n */\nfunction rewriteDocLinks(doc: Doc, linkMap: Map<string, string>): Doc {\n if (linkMap.size === 0) return doc;\n\n function rewriteHtmlNodes(nodes: HtmlNode[]): HtmlNode[] {\n return nodes.map((n) => {\n if (n.type !== 'htmlElement') return n;\n const tag = n.tagName.toLowerCase();\n const nextAttrs =\n tag === 'a' && typeof n.attributes.href === 'string' && linkMap.has(n.attributes.href)\n ? { ...n.attributes, href: linkMap.get(n.attributes.href)! }\n : n.attributes;\n return {\n ...n,\n attributes: nextAttrs,\n children: rewriteHtmlNodes(n.children),\n };\n });\n }\n\n function rewriteInline(node: MarkdownInlineNode): MarkdownInlineNode {\n if (node.type === 'link') {\n const nextUrl = linkMap.get(node.url) ?? node.url;\n return {\n ...node,\n url: nextUrl,\n children: node.children.map(rewriteInline),\n };\n }\n if (node.type === 'htmlInline') {\n const withChildren = node as MarkdownInlineNode & { htmlChildren?: HtmlNode[] };\n if (Array.isArray(withChildren.htmlChildren)) {\n return {\n ...node,\n htmlChildren: rewriteHtmlNodes(withChildren.htmlChildren),\n } as MarkdownInlineNode;\n }\n return node;\n }\n if ('children' in node && Array.isArray((node as { children?: unknown }).children)) {\n const children = (node as { children: MarkdownInlineNode[] }).children.map(rewriteInline);\n return { ...node, children } as MarkdownInlineNode;\n }\n return node;\n }\n\n function rewriteBlockNode(node: MarkdownBlockNode): MarkdownBlockNode {\n if (node.type === 'htmlBlock') {\n const withChildren = node as MarkdownBlockNode & { htmlChildren?: HtmlNode[] };\n if (Array.isArray(withChildren.htmlChildren)) {\n return {\n ...node,\n htmlChildren: rewriteHtmlNodes(withChildren.htmlChildren),\n } as MarkdownBlockNode;\n }\n return node;\n }\n if ('children' in node && Array.isArray((node as { children?: unknown }).children)) {\n const rewritten = (node as { children: unknown[] }).children.map((c) => {\n const child = c as { type?: string };\n if (child && typeof child === 'object' && 'type' in child) {\n if (isBlockNodeType(child.type as string)) {\n return rewriteBlockNode(c as MarkdownBlockNode);\n }\n return rewriteInline(c as MarkdownInlineNode);\n }\n return c;\n });\n return { ...node, children: rewritten } as MarkdownBlockNode;\n }\n return node;\n }\n\n function rewriteBlock(block: Block): Block {\n const next: Block = { ...block };\n if (Array.isArray(block.contents)) {\n next.contents = block.contents.map(rewriteBlockNode);\n }\n if (Array.isArray(block.children)) {\n next.children = block.children.map(rewriteBlock);\n }\n return next;\n }\n\n return {\n ...doc,\n blocks: doc.blocks.map(rewriteBlock),\n };\n}\n\n/** Block-level markdown node `type` discriminator. Inline node types are\n * everything else (text, link, emphasis, strong, image, etc.). Keeps\n * the recursive rewrite from mistakenly applying block logic to inline\n * link descendants. */\nfunction isBlockNodeType(t: string): boolean {\n return (\n t === 'paragraph' ||\n t === 'heading' ||\n t === 'list' ||\n t === 'listItem' ||\n t === 'blockquote' ||\n t === 'code' ||\n t === 'thematicBreak' ||\n t === 'table' ||\n t === 'tableRow' ||\n t === 'tableCell' ||\n t === 'htmlBlock'\n );\n}\n\n// ── Helpers (kept local to avoid widening plainHtmlBundle's API) ───\n\ninterface ParsedRef {\n path: string;\n fragment: string;\n}\n\nfunction parseInternalRef(url: string): ParsedRef | null {\n if (!url) return null;\n if (url.startsWith('#')) return null;\n if (/^[a-z][a-z0-9+.-]*:/i.test(url) || url.startsWith('//') || url.startsWith('/')) {\n return null;\n }\n const hashIdx = url.indexOf('#');\n const queryIdx = url.indexOf('?');\n const cut =\n hashIdx >= 0 && queryIdx >= 0 ? Math.min(hashIdx, queryIdx) : hashIdx >= 0 ? hashIdx : queryIdx;\n const path = cut >= 0 ? url.slice(0, cut) : url;\n const fragment = hashIdx >= 0 ? url.slice(hashIdx) : '';\n return { path, fragment };\n}\n\nfunction collectImageRefs(doc: MarkdownDocument): Set<string> {\n const refs = new Set<string>();\n function visitHtml(nodes: HtmlNode[]): void {\n for (const n of nodes) {\n if (n.type !== 'htmlElement') continue;\n if (n.tagName.toLowerCase() === 'img') {\n const src = n.attributes.src;\n if (typeof src === 'string' && src) refs.add(src);\n }\n visitHtml(n.children);\n }\n }\n function visit(node: unknown): void {\n if (!node || typeof node !== 'object') return;\n const n = node as Record<string, unknown>;\n if (n.type === 'image' && typeof n.url === 'string' && n.url) refs.add(n.url);\n if ((n.type === 'htmlBlock' || n.type === 'htmlInline') && Array.isArray(n.htmlChildren)) {\n visitHtml(n.htmlChildren as HtmlNode[]);\n }\n if (Array.isArray(n.children)) for (const c of n.children) visit(c);\n }\n for (const child of doc.children) visit(child);\n return refs;\n}\n\nfunction sanitizeZipPath(path: string): string | null {\n const normalized = path.replace(/\\\\/g, '/').replace(/^\\/+/, '');\n if (!normalized) return null;\n if (normalized.split('/').some((seg) => seg === '..')) return null;\n return normalized;\n}\n\nfunction titleForDoc(path: string, mdDoc: MarkdownDocument): string {\n const inferred = inferDocumentTitle(mdDoc);\n if (inferred) return inferred;\n const base = path.split('/').pop() ?? path;\n return base.replace(/\\.md$/i, '');\n}\n","/**\n * HTML Import Module — @bendyline/squisq-formats/html\n *\n * Converts an HTML document (or fragment) into a squisq `MarkdownDocument`.\n * Built on the existing `parseHtmlToNodes` (parse5-backed) + `sanitizeHtmlNodes`\n * from `@bendyline/squisq/markdown`, so no new dependency is introduced.\n *\n * The primary consumer is email: HTML mail bodies are hostile input, so the\n * parse is sanitized by default (scripts/styles/event handlers/dangerous URLs\n * stripped) before the tree is walked into markdown nodes.\n *\n * Two entry points mirror the docx/pdf importers:\n * - `htmlToMarkdownDoc(data, options?)` → `MarkdownDocument`\n * - `htmlToMarkdown(html, options?)` → markdown string (convenience for the\n * string-in/string-out path email body conversion needs)\n */\n\nimport {\n type MarkdownBlockNode,\n type MarkdownDocument,\n type MarkdownInlineNode,\n type MarkdownListItem,\n type MarkdownTableCell,\n type MarkdownTableRow,\n type HtmlElement,\n type HtmlNode,\n parseHtmlToNodes,\n sanitizeHtmlNodes,\n stringifyMarkdown,\n} from '@bendyline/squisq/markdown';\n\nexport interface HtmlImportOptions {\n /**\n * Strip scripts / styles / event handlers / dangerous URLs before walking\n * the tree. Default `true` — HTML email is untrusted; only disable for\n * trusted input where you want raw fidelity.\n */\n sanitize?: boolean;\n}\n\n// ── tag classification ──────────────────────────────────────────────\n\nconst HEADINGS: Record<string, 1 | 2 | 3 | 4 | 5 | 6> = {\n h1: 1,\n h2: 2,\n h3: 3,\n h4: 4,\n h5: 5,\n h6: 6,\n};\n\n// Containers with no semantic block meaning — unwrap and walk their children.\nconst TRANSPARENT_BLOCK = new Set([\n 'div',\n 'section',\n 'article',\n 'header',\n 'footer',\n 'main',\n 'aside',\n 'nav',\n 'figure',\n 'figcaption',\n 'form',\n 'fieldset',\n 'body',\n 'html',\n 'center',\n]);\n\n// Elements that carry no content we want (and whose text must not leak).\nconst DROP = new Set(['script', 'style', 'head', 'title', 'noscript', 'template', 'svg']);\n\nconst INLINE_STRONG = new Set(['strong', 'b']);\nconst INLINE_EM = new Set(['em', 'i']);\nconst INLINE_DEL = new Set(['del', 's', 'strike']);\nconst INLINE_TRANSPARENT = new Set(['span', 'font', 'abbr', 'mark', 'small', 'sub', 'sup', 'u']);\n\nconst isElement = (n: HtmlNode): n is HtmlElement => n.type === 'htmlElement';\nconst isText = (n: HtmlNode): n is { type: 'htmlText'; value: string } => n.type === 'htmlText';\n\nconst collapseWs = (s: string): string => s.replace(/\\s+/g, ' ');\n\n// ── inline conversion ───────────────────────────────────────────────\n\nfunction inlinesFromNodes(nodes: HtmlNode[]): MarkdownInlineNode[] {\n const out: MarkdownInlineNode[] = [];\n for (const node of nodes) {\n if (isText(node)) {\n const value = collapseWs(node.value);\n if (value) out.push({ type: 'text', value });\n continue;\n }\n if (!isElement(node)) continue; // comment\n const tag = node.tagName.toLowerCase();\n if (DROP.has(tag)) continue;\n\n if (tag === 'br') {\n out.push({ type: 'break' });\n } else if (INLINE_STRONG.has(tag)) {\n out.push({ type: 'strong', children: inlinesFromNodes(node.children) });\n } else if (INLINE_EM.has(tag)) {\n out.push({ type: 'emphasis', children: inlinesFromNodes(node.children) });\n } else if (INLINE_DEL.has(tag)) {\n out.push({ type: 'delete', children: inlinesFromNodes(node.children) });\n } else if (tag === 'code' || tag === 'kbd' || tag === 'samp' || tag === 'tt') {\n out.push({ type: 'inlineCode', value: textContent(node) });\n } else if (tag === 'a') {\n const url = node.attributes.href ?? '';\n const children = inlinesFromNodes(node.children);\n out.push({\n type: 'link',\n url,\n children: children.length > 0 ? children : [{ type: 'text', value: url }],\n });\n } else if (tag === 'img') {\n const url = node.attributes.src ?? '';\n const alt = node.attributes.alt;\n if (url) out.push({ type: 'image', url, ...(alt ? { alt } : {}) });\n } else {\n // Unknown / transparent inline (span, font, …): flatten children.\n out.push(...inlinesFromNodes(node.children));\n }\n }\n return out;\n}\n\n/** Flatten an element's descendant text (for code blocks / inline code). */\nfunction textContent(node: HtmlNode): string {\n if (isText(node)) return node.value;\n if (isElement(node)) return node.children.map(textContent).join('');\n return '';\n}\n\nconst onlyWhitespace = (inlines: MarkdownInlineNode[]): boolean =>\n inlines.every((n) => n.type === 'text' && n.value.trim() === '');\n\n// ── block conversion ────────────────────────────────────────────────\n\nfunction blocksFromNodes(nodes: HtmlNode[]): MarkdownBlockNode[] {\n const out: MarkdownBlockNode[] = [];\n let inlineBuffer: HtmlNode[] = [];\n\n const flush = () => {\n if (inlineBuffer.length === 0) return;\n const inlines = inlinesFromNodes(inlineBuffer);\n inlineBuffer = [];\n if (inlines.length > 0 && !onlyWhitespace(inlines)) {\n out.push({ type: 'paragraph', children: inlines });\n }\n };\n\n for (const node of nodes) {\n if (isText(node)) {\n inlineBuffer.push(node);\n continue;\n }\n if (!isElement(node)) continue;\n const tag = node.tagName.toLowerCase();\n if (DROP.has(tag)) continue;\n\n const block = blockForElement(node, tag);\n if (block === 'inline') {\n inlineBuffer.push(node);\n } else if (block) {\n flush();\n out.push(...block);\n }\n }\n flush();\n return out;\n}\n\n/** Returns the block node(s) for a block element, `'inline'` for inline ones. */\nfunction blockForElement(node: HtmlElement, tag: string): MarkdownBlockNode[] | 'inline' | null {\n if (tag in HEADINGS) {\n const children = inlinesFromNodes(node.children);\n return [{ type: 'heading', depth: HEADINGS[tag]!, children }];\n }\n if (tag === 'p') {\n const children = inlinesFromNodes(node.children);\n return children.length > 0 && !onlyWhitespace(children)\n ? [{ type: 'paragraph', children }]\n : [];\n }\n if (tag === 'br') return 'inline';\n if (tag === 'hr') return [{ type: 'thematicBreak' }];\n if (tag === 'blockquote') {\n return [{ type: 'blockquote', children: blocksFromNodes(node.children) }];\n }\n if (tag === 'pre') {\n return [{ type: 'code', value: stripTrailingNewline(textContent(node)) }];\n }\n if (tag === 'ul' || tag === 'ol') {\n return [listFromElement(node, tag === 'ol')];\n }\n if (tag === 'table') {\n const table = tableFromElement(node);\n return table ? [table] : [];\n }\n if (TRANSPARENT_BLOCK.has(tag)) {\n return blocksFromNodes(node.children);\n }\n if (\n INLINE_STRONG.has(tag) ||\n INLINE_EM.has(tag) ||\n INLINE_DEL.has(tag) ||\n INLINE_TRANSPARENT.has(tag) ||\n tag === 'a' ||\n tag === 'img' ||\n tag === 'code' ||\n tag === 'kbd' ||\n tag === 'samp'\n ) {\n return 'inline';\n }\n // Unknown element: treat as a transparent container so its content survives.\n return blocksFromNodes(node.children);\n}\n\nfunction listFromElement(node: HtmlElement, ordered: boolean): MarkdownBlockNode {\n const items: MarkdownListItem[] = [];\n for (const child of node.children) {\n if (isElement(child) && child.tagName.toLowerCase() === 'li') {\n const blocks = blocksFromNodes(child.children);\n items.push({ type: 'listItem', children: blocks });\n }\n }\n const startAttr = node.attributes.start;\n const start = ordered && startAttr ? Number.parseInt(startAttr, 10) : undefined;\n return {\n type: 'list',\n ordered,\n ...(start !== undefined && Number.isFinite(start) ? { start } : {}),\n children: items,\n };\n}\n\nfunction tableFromElement(node: HtmlElement): MarkdownBlockNode | null {\n const rows: MarkdownTableRow[] = [];\n const collectRows = (n: HtmlElement) => {\n for (const child of n.children) {\n if (!isElement(child)) continue;\n const t = child.tagName.toLowerCase();\n if (t === 'tr') {\n const cells: MarkdownTableCell[] = [];\n for (const cell of child.children) {\n if (\n isElement(cell) &&\n (cell.tagName.toLowerCase() === 'td' || cell.tagName.toLowerCase() === 'th')\n ) {\n cells.push({ type: 'tableCell', children: inlinesFromNodes(cell.children) });\n }\n }\n if (cells.length > 0) rows.push({ type: 'tableRow', children: cells });\n } else if (t === 'thead' || t === 'tbody' || t === 'tfoot') {\n collectRows(child);\n }\n }\n };\n collectRows(node);\n if (rows.length === 0) return null;\n return { type: 'table', children: rows };\n}\n\nconst stripTrailingNewline = (s: string): string => s.replace(/\\n+$/, '');\n\n// ── public API ──────────────────────────────────────────────────────\n\nfunction toHtmlString(data: ArrayBuffer | Uint8Array | string): string {\n if (typeof data === 'string') return data;\n const bytes = data instanceof Uint8Array ? data : new Uint8Array(data);\n return new TextDecoder('utf-8').decode(bytes);\n}\n\n/** Parse HTML into a squisq `MarkdownDocument`. */\nexport function htmlToMarkdownDocSync(\n html: string,\n options: HtmlImportOptions = {},\n): MarkdownDocument {\n let nodes = parseHtmlToNodes(html);\n if (options.sanitize !== false) nodes = sanitizeHtmlNodes(nodes);\n return { type: 'document', children: blocksFromNodes(nodes) };\n}\n\n/** Async, ArrayBuffer-accepting entry mirroring docx/pdf importers. */\nexport async function htmlToMarkdownDoc(\n data: ArrayBuffer | Uint8Array | string,\n options: HtmlImportOptions = {},\n): Promise<MarkdownDocument> {\n return htmlToMarkdownDocSync(toHtmlString(data), options);\n}\n\n/** Convenience string→string conversion (used for email HTML bodies). */\nexport function htmlToMarkdown(html: string, options: HtmlImportOptions = {}): string {\n return stringifyMarkdown(htmlToMarkdownDocSync(html, options));\n}\n"],"mappings":"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","names":["JSZip","escapeHtml","JSZip","parseMarkdown","inferDocumentTitle","JSZip","parseMarkdown","collectImageRefs","sanitizeZipPath","inferDocumentTitle","sanitizeHtmlNodes","sanitizeZipPath","JSZip"]}
@@ -0,0 +1,201 @@
1
+ // src/ooxml/namespaces.ts
2
+ var NS_RELATIONSHIPS = "http://schemas.openxmlformats.org/package/2006/relationships";
3
+ var NS_CONTENT_TYPES = "http://schemas.openxmlformats.org/package/2006/content-types";
4
+ var REL_OFFICE_DOCUMENT = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument";
5
+ var REL_CORE_PROPERTIES = "http://schemas.openxmlformats.org/package/2006/relationships/metadata/core-properties";
6
+ var REL_EXTENDED_PROPERTIES = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/extended-properties";
7
+ var REL_STYLES = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/styles";
8
+ var REL_NUMBERING = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/numbering";
9
+ var REL_FONT_TABLE = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/fontTable";
10
+ var REL_SETTINGS = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/settings";
11
+ var REL_HYPERLINK = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/hyperlink";
12
+ var REL_IMAGE = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/image";
13
+ var REL_FOOTNOTES = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/footnotes";
14
+ var REL_THEME = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/theme";
15
+ var NS_WML = "http://schemas.openxmlformats.org/wordprocessingml/2006/main";
16
+ var NS_PML = "http://schemas.openxmlformats.org/presentationml/2006/main";
17
+ var NS_PML_2010 = "http://schemas.microsoft.com/office/powerpoint/2010/main";
18
+ var NS_SML = "http://schemas.openxmlformats.org/spreadsheetml/2006/main";
19
+ var NS_DRAWINGML = "http://schemas.openxmlformats.org/drawingml/2006/main";
20
+ var NS_WP_DRAWING = "http://schemas.openxmlformats.org/drawingml/2006/wordprocessingDrawing";
21
+ var NS_PICTURE = "http://schemas.openxmlformats.org/drawingml/2006/picture";
22
+ var NS_DC = "http://purl.org/dc/elements/1.1/";
23
+ var NS_DCTERMS = "http://purl.org/dc/terms/";
24
+ var NS_CORE_PROPERTIES = "http://schemas.openxmlformats.org/package/2006/metadata/core-properties";
25
+ var NS_XSI = "http://www.w3.org/2001/XMLSchema-instance";
26
+ var NS_MC = "http://schemas.openxmlformats.org/markup-compatibility/2006";
27
+ var NS_R = "http://schemas.openxmlformats.org/officeDocument/2006/relationships";
28
+ var CONTENT_TYPE_RELATIONSHIPS = "application/vnd.openxmlformats-package.relationships+xml";
29
+ var CONTENT_TYPE_CORE_PROPERTIES = "application/vnd.openxmlformats-package.core-properties+xml";
30
+ var CONTENT_TYPE_DOCX_DOCUMENT = "application/vnd.openxmlformats-officedocument.wordprocessingml.document.main+xml";
31
+ var CONTENT_TYPE_DOCX_STYLES = "application/vnd.openxmlformats-officedocument.wordprocessingml.styles+xml";
32
+ var CONTENT_TYPE_DOCX_NUMBERING = "application/vnd.openxmlformats-officedocument.wordprocessingml.numbering+xml";
33
+ var CONTENT_TYPE_DOCX_SETTINGS = "application/vnd.openxmlformats-officedocument.wordprocessingml.settings+xml";
34
+ var CONTENT_TYPE_DOCX_FONT_TABLE = "application/vnd.openxmlformats-officedocument.wordprocessingml.fontTable+xml";
35
+ var CONTENT_TYPE_DOCX_FOOTNOTES = "application/vnd.openxmlformats-officedocument.wordprocessingml.footnotes+xml";
36
+ var CONTENT_TYPE_PPTX_PRESENTATION = "application/vnd.openxmlformats-officedocument.presentationml.presentation.main+xml";
37
+ var CONTENT_TYPE_PPTX_SLIDE = "application/vnd.openxmlformats-officedocument.presentationml.slide+xml";
38
+ var CONTENT_TYPE_PPTX_SLIDE_LAYOUT = "application/vnd.openxmlformats-officedocument.presentationml.slideLayout+xml";
39
+ var CONTENT_TYPE_PPTX_SLIDE_MASTER = "application/vnd.openxmlformats-officedocument.presentationml.slideMaster+xml";
40
+ var CONTENT_TYPE_PPTX_THEME = "application/vnd.openxmlformats-officedocument.theme+xml";
41
+ var REL_SLIDE = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/slide";
42
+ var REL_SLIDE_LAYOUT = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/slideLayout";
43
+ var REL_SLIDE_MASTER = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/slideMaster";
44
+ var CONTENT_TYPE_XLSX_WORKBOOK = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.main+xml";
45
+
46
+ // src/ooxml/reader.ts
47
+ import JSZip from "jszip";
48
+ async function openPackage(data) {
49
+ const zip = await JSZip.loadAsync(data);
50
+ const contentTypes = await parseContentTypes(zip);
51
+ const rootRelationships = await parseRelationships(zip, "");
52
+ return { zip, contentTypes, rootRelationships };
53
+ }
54
+ async function parseContentTypes(zip) {
55
+ const overrides = /* @__PURE__ */ new Map();
56
+ const defaults = /* @__PURE__ */ new Map();
57
+ const file = zip.file("[Content_Types].xml");
58
+ if (!file) return { overrides, defaults };
59
+ const text = await file.async("text");
60
+ const doc = new DOMParser().parseFromString(text, "application/xml");
61
+ const defaultEls = doc.getElementsByTagName("Default");
62
+ for (let i = 0; i < defaultEls.length; i++) {
63
+ const el = defaultEls[i];
64
+ const ext = el.getAttribute("Extension");
65
+ const ct = el.getAttribute("ContentType");
66
+ if (ext && ct) defaults.set(ext, ct);
67
+ }
68
+ const overrideEls = doc.getElementsByTagName("Override");
69
+ for (let i = 0; i < overrideEls.length; i++) {
70
+ const el = overrideEls[i];
71
+ const partName = el.getAttribute("PartName");
72
+ const ct = el.getAttribute("ContentType");
73
+ if (partName && ct) {
74
+ overrides.set(partName.replace(/^\//, ""), ct);
75
+ }
76
+ }
77
+ return { overrides, defaults };
78
+ }
79
+ async function getPartRelationships(pkg, partPath) {
80
+ return parseRelationships(pkg.zip, partPath);
81
+ }
82
+ async function parseRelationships(zip, partPath) {
83
+ const relsPath = partPath === "" ? "_rels/.rels" : buildRelsPath(partPath);
84
+ const file = zip.file(relsPath);
85
+ if (!file) return [];
86
+ const text = await file.async("text");
87
+ const doc = new DOMParser().parseFromString(text, "application/xml");
88
+ const result = [];
89
+ const els = doc.getElementsByTagNameNS(NS_RELATIONSHIPS, "Relationship");
90
+ const fallbackEls = els.length > 0 ? els : doc.getElementsByTagName("Relationship");
91
+ for (let i = 0; i < fallbackEls.length; i++) {
92
+ const el = fallbackEls[i];
93
+ const id = el.getAttribute("Id");
94
+ const type = el.getAttribute("Type");
95
+ const target = el.getAttribute("Target");
96
+ if (id && type && target) {
97
+ const targetMode = el.getAttribute("TargetMode");
98
+ result.push({
99
+ id,
100
+ type,
101
+ target,
102
+ ...targetMode ? { targetMode } : {}
103
+ });
104
+ }
105
+ }
106
+ return result;
107
+ }
108
+ function buildRelsPath(partPath) {
109
+ const lastSlash = partPath.lastIndexOf("/");
110
+ if (lastSlash === -1) {
111
+ return `_rels/${partPath}.rels`;
112
+ }
113
+ const dir = partPath.substring(0, lastSlash);
114
+ const file = partPath.substring(lastSlash + 1);
115
+ return `${dir}/_rels/${file}.rels`;
116
+ }
117
+ async function getPartXml(pkg, partPath) {
118
+ const file = pkg.zip.file(partPath);
119
+ if (!file) return null;
120
+ const text = await file.async("text");
121
+ return new DOMParser().parseFromString(text, "application/xml");
122
+ }
123
+ async function getPartBinary(pkg, partPath) {
124
+ const file = pkg.zip.file(partPath);
125
+ if (!file) return null;
126
+ return file.async("arraybuffer");
127
+ }
128
+ async function getCoreProperties(pkg) {
129
+ const doc = await getPartXml(pkg, "docProps/core.xml");
130
+ if (!doc) return {};
131
+ function getText(ns, localName) {
132
+ const els = doc.getElementsByTagNameNS(ns, localName);
133
+ if (els.length > 0 && els[0].textContent) {
134
+ return els[0].textContent;
135
+ }
136
+ return void 0;
137
+ }
138
+ return {
139
+ title: getText(NS_DC, "title"),
140
+ subject: getText(NS_DC, "subject"),
141
+ creator: getText(NS_DC, "creator"),
142
+ description: getText(NS_DC, "description"),
143
+ keywords: getText(NS_CORE_PROPERTIES, "keywords"),
144
+ lastModifiedBy: getText(NS_CORE_PROPERTIES, "lastModifiedBy"),
145
+ revision: getText(NS_CORE_PROPERTIES, "revision"),
146
+ created: getText(NS_DCTERMS, "created"),
147
+ modified: getText(NS_DCTERMS, "modified")
148
+ };
149
+ }
150
+
151
+ export {
152
+ NS_RELATIONSHIPS,
153
+ NS_CONTENT_TYPES,
154
+ REL_OFFICE_DOCUMENT,
155
+ REL_CORE_PROPERTIES,
156
+ REL_EXTENDED_PROPERTIES,
157
+ REL_STYLES,
158
+ REL_NUMBERING,
159
+ REL_FONT_TABLE,
160
+ REL_SETTINGS,
161
+ REL_HYPERLINK,
162
+ REL_IMAGE,
163
+ REL_FOOTNOTES,
164
+ REL_THEME,
165
+ NS_WML,
166
+ NS_PML,
167
+ NS_PML_2010,
168
+ NS_SML,
169
+ NS_DRAWINGML,
170
+ NS_WP_DRAWING,
171
+ NS_PICTURE,
172
+ NS_DC,
173
+ NS_DCTERMS,
174
+ NS_CORE_PROPERTIES,
175
+ NS_XSI,
176
+ NS_MC,
177
+ NS_R,
178
+ CONTENT_TYPE_RELATIONSHIPS,
179
+ CONTENT_TYPE_CORE_PROPERTIES,
180
+ CONTENT_TYPE_DOCX_DOCUMENT,
181
+ CONTENT_TYPE_DOCX_STYLES,
182
+ CONTENT_TYPE_DOCX_NUMBERING,
183
+ CONTENT_TYPE_DOCX_SETTINGS,
184
+ CONTENT_TYPE_DOCX_FONT_TABLE,
185
+ CONTENT_TYPE_DOCX_FOOTNOTES,
186
+ CONTENT_TYPE_PPTX_PRESENTATION,
187
+ CONTENT_TYPE_PPTX_SLIDE,
188
+ CONTENT_TYPE_PPTX_SLIDE_LAYOUT,
189
+ CONTENT_TYPE_PPTX_SLIDE_MASTER,
190
+ CONTENT_TYPE_PPTX_THEME,
191
+ REL_SLIDE,
192
+ REL_SLIDE_LAYOUT,
193
+ REL_SLIDE_MASTER,
194
+ CONTENT_TYPE_XLSX_WORKBOOK,
195
+ openPackage,
196
+ getPartRelationships,
197
+ getPartXml,
198
+ getPartBinary,
199
+ getCoreProperties
200
+ };
201
+ //# sourceMappingURL=chunk-FMOIGA5T.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/ooxml/namespaces.ts","../src/ooxml/reader.ts"],"sourcesContent":["/**\n * OOXML Namespace Constants\n *\n * All Office Open XML namespace URIs used across DOCX, PPTX, and XLSX.\n * Organized by category for easy reference.\n */\n\n// ============================================\n// Package-level namespaces (shared by all formats)\n// ============================================\n\n/** Relationships namespace (used in _rels/*.rels files) */\nexport const NS_RELATIONSHIPS = 'http://schemas.openxmlformats.org/package/2006/relationships';\n\n/** Content Types namespace ([Content_Types].xml) */\nexport const NS_CONTENT_TYPES = 'http://schemas.openxmlformats.org/package/2006/content-types';\n\n// ============================================\n// Relationship type URIs\n// ============================================\n\n/** Relationship type: Office document (main part) */\nexport const REL_OFFICE_DOCUMENT =\n 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument';\n\n/** Relationship type: Core properties */\nexport const REL_CORE_PROPERTIES =\n 'http://schemas.openxmlformats.org/package/2006/relationships/metadata/core-properties';\n\n/** Relationship type: Extended properties (app.xml) */\nexport const REL_EXTENDED_PROPERTIES =\n 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/extended-properties';\n\n/** Relationship type: Styles */\nexport const REL_STYLES =\n 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/styles';\n\n/** Relationship type: Numbering */\nexport const REL_NUMBERING =\n 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/numbering';\n\n/** Relationship type: Font table */\nexport const REL_FONT_TABLE =\n 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/fontTable';\n\n/** Relationship type: Settings */\nexport const REL_SETTINGS =\n 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/settings';\n\n/** Relationship type: Hyperlink */\nexport const REL_HYPERLINK =\n 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/hyperlink';\n\n/** Relationship type: Image */\nexport const REL_IMAGE =\n 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/image';\n\n/** Relationship type: Footnotes */\nexport const REL_FOOTNOTES =\n 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/footnotes';\n\n/** Relationship type: Theme */\nexport const REL_THEME =\n 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/theme';\n\n// ============================================\n// WordprocessingML (DOCX)\n// ============================================\n\n/** WordprocessingML main namespace (w:) */\nexport const NS_WML = 'http://schemas.openxmlformats.org/wordprocessingml/2006/main';\n\n// ============================================\n// PresentationML (PPTX)\n// ============================================\n\n/** PresentationML main namespace (p:) */\nexport const NS_PML = 'http://schemas.openxmlformats.org/presentationml/2006/main';\n\n/** PowerPoint 2010 extension namespace (p14:) */\nexport const NS_PML_2010 = 'http://schemas.microsoft.com/office/powerpoint/2010/main';\n\n// ============================================\n// SpreadsheetML (XLSX)\n// ============================================\n\n/** SpreadsheetML main namespace */\nexport const NS_SML = 'http://schemas.openxmlformats.org/spreadsheetml/2006/main';\n\n// ============================================\n// DrawingML (shared across formats)\n// ============================================\n\n/** DrawingML main namespace (a:) */\nexport const NS_DRAWINGML = 'http://schemas.openxmlformats.org/drawingml/2006/main';\n\n/** DrawingML WordprocessingML drawing namespace (wp:) */\nexport const NS_WP_DRAWING =\n 'http://schemas.openxmlformats.org/drawingml/2006/wordprocessingDrawing';\n\n/** DrawingML picture namespace (pic:) */\nexport const NS_PICTURE = 'http://schemas.openxmlformats.org/drawingml/2006/picture';\n\n// ============================================\n// Dublin Core / Core Properties\n// ============================================\n\n/** Dublin Core elements namespace */\nexport const NS_DC = 'http://purl.org/dc/elements/1.1/';\n\n/** Dublin Core terms namespace */\nexport const NS_DCTERMS = 'http://purl.org/dc/terms/';\n\n/** Core properties namespace */\nexport const NS_CORE_PROPERTIES =\n 'http://schemas.openxmlformats.org/package/2006/metadata/core-properties';\n\n/** XML Schema Instance namespace */\nexport const NS_XSI = 'http://www.w3.org/2001/XMLSchema-instance';\n\n// ============================================\n// Markup Compatibility\n// ============================================\n\n/** Markup Compatibility namespace (mc:) */\nexport const NS_MC = 'http://schemas.openxmlformats.org/markup-compatibility/2006';\n\n/** Office relationships namespace (r:) */\nexport const NS_R = 'http://schemas.openxmlformats.org/officeDocument/2006/relationships';\n\n// ============================================\n// Content type strings\n// ============================================\n\nexport const CONTENT_TYPE_RELATIONSHIPS =\n 'application/vnd.openxmlformats-package.relationships+xml';\n\nexport const CONTENT_TYPE_CORE_PROPERTIES =\n 'application/vnd.openxmlformats-package.core-properties+xml';\n\nexport const CONTENT_TYPE_DOCX_DOCUMENT =\n 'application/vnd.openxmlformats-officedocument.wordprocessingml.document.main+xml';\n\nexport const CONTENT_TYPE_DOCX_STYLES =\n 'application/vnd.openxmlformats-officedocument.wordprocessingml.styles+xml';\n\nexport const CONTENT_TYPE_DOCX_NUMBERING =\n 'application/vnd.openxmlformats-officedocument.wordprocessingml.numbering+xml';\n\nexport const CONTENT_TYPE_DOCX_SETTINGS =\n 'application/vnd.openxmlformats-officedocument.wordprocessingml.settings+xml';\n\nexport const CONTENT_TYPE_DOCX_FONT_TABLE =\n 'application/vnd.openxmlformats-officedocument.wordprocessingml.fontTable+xml';\n\nexport const CONTENT_TYPE_DOCX_FOOTNOTES =\n 'application/vnd.openxmlformats-officedocument.wordprocessingml.footnotes+xml';\n\nexport const CONTENT_TYPE_PPTX_PRESENTATION =\n 'application/vnd.openxmlformats-officedocument.presentationml.presentation.main+xml';\n\nexport const CONTENT_TYPE_PPTX_SLIDE =\n 'application/vnd.openxmlformats-officedocument.presentationml.slide+xml';\n\nexport const CONTENT_TYPE_PPTX_SLIDE_LAYOUT =\n 'application/vnd.openxmlformats-officedocument.presentationml.slideLayout+xml';\n\nexport const CONTENT_TYPE_PPTX_SLIDE_MASTER =\n 'application/vnd.openxmlformats-officedocument.presentationml.slideMaster+xml';\n\nexport const CONTENT_TYPE_PPTX_THEME = 'application/vnd.openxmlformats-officedocument.theme+xml';\n\n/** Relationship type: Slide */\nexport const REL_SLIDE =\n 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/slide';\n\n/** Relationship type: Slide layout */\nexport const REL_SLIDE_LAYOUT =\n 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/slideLayout';\n\n/** Relationship type: Slide master */\nexport const REL_SLIDE_MASTER =\n 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/slideMaster';\n\nexport const CONTENT_TYPE_XLSX_WORKBOOK =\n 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.main+xml';\n","/**\n * OOXML Package Reader\n *\n * Opens OOXML archives (.docx, .pptx, .xlsx) and parses their\n * structural metadata: [Content_Types].xml, relationships, and\n * core properties.\n *\n * Uses JSZip to unzip and the browser's DOMParser to parse XML.\n */\n\nimport JSZip from 'jszip';\nimport type { OoxmlPackage, ContentTypeMap, Relationship, CoreProperties } from './types.js';\nimport { NS_RELATIONSHIPS, NS_DC, NS_DCTERMS, NS_CORE_PROPERTIES } from './namespaces.js';\n\n// ============================================\n// Package Opening\n// ============================================\n\n/**\n * Open an OOXML package from raw data.\n *\n * Parses the ZIP archive, [Content_Types].xml, and root relationships.\n *\n * @param data - The raw .docx/.pptx/.xlsx file as ArrayBuffer or Blob\n * @returns A parsed OoxmlPackage\n */\nexport async function openPackage(data: ArrayBuffer | Blob): Promise<OoxmlPackage> {\n const zip = await JSZip.loadAsync(data);\n const contentTypes = await parseContentTypes(zip);\n const rootRelationships = await parseRelationships(zip, '');\n\n return { zip, contentTypes, rootRelationships };\n}\n\n// ============================================\n// Content Types\n// ============================================\n\n/**\n * Parse [Content_Types].xml from the archive.\n */\nasync function parseContentTypes(zip: JSZip): Promise<ContentTypeMap> {\n const overrides = new Map<string, string>();\n const defaults = new Map<string, string>();\n\n const file = zip.file('[Content_Types].xml');\n if (!file) return { overrides, defaults };\n\n const text = await file.async('text');\n const doc = new DOMParser().parseFromString(text, 'application/xml');\n\n // Parse <Default Extension=\"rels\" ContentType=\"...\" />\n const defaultEls = doc.getElementsByTagName('Default');\n for (let i = 0; i < defaultEls.length; i++) {\n const el = defaultEls[i];\n const ext = el.getAttribute('Extension');\n const ct = el.getAttribute('ContentType');\n if (ext && ct) defaults.set(ext, ct);\n }\n\n // Parse <Override PartName=\"/word/document.xml\" ContentType=\"...\" />\n const overrideEls = doc.getElementsByTagName('Override');\n for (let i = 0; i < overrideEls.length; i++) {\n const el = overrideEls[i];\n const partName = el.getAttribute('PartName');\n const ct = el.getAttribute('ContentType');\n if (partName && ct) {\n // Normalize: strip leading slash\n overrides.set(partName.replace(/^\\//, ''), ct);\n }\n }\n\n return { overrides, defaults };\n}\n\n// ============================================\n// Relationships\n// ============================================\n\n/**\n * Parse relationships for a specific part.\n *\n * @param pkg - The OOXML package (or the zip directly)\n * @param partPath - The part path (e.g., \"word/document.xml\").\n * Use \"\" for root-level relationships (_rels/.rels).\n * @returns Array of relationship entries\n */\nexport async function getPartRelationships(\n pkg: OoxmlPackage,\n partPath: string,\n): Promise<Relationship[]> {\n return parseRelationships(pkg.zip, partPath);\n}\n\n/**\n * Parse a _rels/*.rels file from the ZIP.\n *\n * For root relationships, relsPath = \"_rels/.rels\".\n * For part relationships, relsPath = \"<dir>/_rels/<filename>.rels\".\n */\nasync function parseRelationships(zip: JSZip, partPath: string): Promise<Relationship[]> {\n const relsPath = partPath === '' ? '_rels/.rels' : buildRelsPath(partPath);\n\n const file = zip.file(relsPath);\n if (!file) return [];\n\n const text = await file.async('text');\n const doc = new DOMParser().parseFromString(text, 'application/xml');\n const result: Relationship[] = [];\n\n const els = doc.getElementsByTagNameNS(NS_RELATIONSHIPS, 'Relationship');\n // Fallback in case namespace isn't used (some generators omit it)\n const fallbackEls = els.length > 0 ? els : doc.getElementsByTagName('Relationship');\n\n for (let i = 0; i < fallbackEls.length; i++) {\n const el = fallbackEls[i];\n const id = el.getAttribute('Id');\n const type = el.getAttribute('Type');\n const target = el.getAttribute('Target');\n if (id && type && target) {\n const targetMode = el.getAttribute('TargetMode') as 'Internal' | 'External' | null;\n result.push({\n id,\n type,\n target,\n ...(targetMode ? { targetMode } : {}),\n });\n }\n }\n\n return result;\n}\n\n/**\n * Build the _rels path for a given part path.\n *\n * \"word/document.xml\" → \"word/_rels/document.xml.rels\"\n * \"xl/workbook.xml\" → \"xl/_rels/workbook.xml.rels\"\n */\nfunction buildRelsPath(partPath: string): string {\n const lastSlash = partPath.lastIndexOf('/');\n if (lastSlash === -1) {\n return `_rels/${partPath}.rels`;\n }\n const dir = partPath.substring(0, lastSlash);\n const file = partPath.substring(lastSlash + 1);\n return `${dir}/_rels/${file}.rels`;\n}\n\n// ============================================\n// Part Access\n// ============================================\n\n/**\n * Extract an XML part from the package and parse it as a DOM Document.\n *\n * @param pkg - The OOXML package\n * @param partPath - Path within the archive (e.g., \"word/document.xml\")\n * @returns Parsed XML Document, or null if the part doesn't exist\n */\nexport async function getPartXml(pkg: OoxmlPackage, partPath: string): Promise<Document | null> {\n const file = pkg.zip.file(partPath);\n if (!file) return null;\n\n const text = await file.async('text');\n return new DOMParser().parseFromString(text, 'application/xml');\n}\n\n/**\n * Extract a binary part from the package (e.g., an image from word/media/).\n *\n * @param pkg - The OOXML package\n * @param partPath - Path within the archive\n * @returns The binary content, or null if the part doesn't exist\n */\nexport async function getPartBinary(\n pkg: OoxmlPackage,\n partPath: string,\n): Promise<ArrayBuffer | null> {\n const file = pkg.zip.file(partPath);\n if (!file) return null;\n return file.async('arraybuffer');\n}\n\n// ============================================\n// Core Properties\n// ============================================\n\n/**\n * Parse core document properties from docProps/core.xml.\n *\n * @param pkg - The OOXML package\n * @returns Parsed core properties (all fields optional)\n */\nexport async function getCoreProperties(pkg: OoxmlPackage): Promise<CoreProperties> {\n const doc = await getPartXml(pkg, 'docProps/core.xml');\n if (!doc) return {};\n\n function getText(ns: string, localName: string): string | undefined {\n const els = doc!.getElementsByTagNameNS(ns, localName);\n if (els.length > 0 && els[0].textContent) {\n return els[0].textContent;\n }\n return undefined;\n }\n\n return {\n title: getText(NS_DC, 'title'),\n subject: getText(NS_DC, 'subject'),\n creator: getText(NS_DC, 'creator'),\n description: getText(NS_DC, 'description'),\n keywords: getText(NS_CORE_PROPERTIES, 'keywords'),\n lastModifiedBy: getText(NS_CORE_PROPERTIES, 'lastModifiedBy'),\n revision: getText(NS_CORE_PROPERTIES, 'revision'),\n created: getText(NS_DCTERMS, 'created'),\n modified: getText(NS_DCTERMS, 'modified'),\n };\n}\n"],"mappings":"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","names":[]}