@bendyline/squisq 1.4.1 → 1.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (254) hide show
  1. package/README.md +14 -1
  2. package/dist/{ContentContainer-DNx460y8.d.ts → ContentContainer-BXUlIf18.d.ts} +6 -2
  3. package/dist/{Doc-BeSeH2iX.d.ts → Doc-BBVGq1_9.d.ts} +922 -273
  4. package/dist/{ImageEditDoc-BTIvw0oq.d.ts → ImageEditDoc-FuyGtt6o.d.ts} +11 -4
  5. package/dist/{chunk-ZTX4PFFT.js → chunk-2OIBZYKP.js} +75 -2
  6. package/dist/chunk-2OIBZYKP.js.map +1 -0
  7. package/dist/chunk-2UQOHYE2.js +45 -0
  8. package/dist/chunk-2UQOHYE2.js.map +1 -0
  9. package/dist/chunk-6ATE2PSM.js +344 -0
  10. package/dist/chunk-6ATE2PSM.js.map +1 -0
  11. package/dist/{chunk-57CVQCFX.js → chunk-A4O7GIWE.js} +74 -23
  12. package/dist/chunk-A4O7GIWE.js.map +1 -0
  13. package/dist/{chunk-5WFRKURX.js → chunk-DLXZMS5K.js} +4 -2
  14. package/dist/chunk-DLXZMS5K.js.map +1 -0
  15. package/dist/chunk-ET53IIEP.js +1 -0
  16. package/dist/chunk-ET53IIEP.js.map +1 -0
  17. package/dist/chunk-HWVFJAAH.js +362 -0
  18. package/dist/chunk-HWVFJAAH.js.map +1 -0
  19. package/dist/{chunk-2TMAWB4Y.js → chunk-IMSCRLLV.js} +1 -1
  20. package/dist/chunk-IMSCRLLV.js.map +1 -0
  21. package/dist/{chunk-B4EEON3N.js → chunk-KKNUBQ6Y.js} +536 -103
  22. package/dist/chunk-KKNUBQ6Y.js.map +1 -0
  23. package/dist/chunk-LDQ2HJIX.js +596 -0
  24. package/dist/chunk-LDQ2HJIX.js.map +1 -0
  25. package/dist/chunk-LH7I6SH7.js +7208 -0
  26. package/dist/chunk-LH7I6SH7.js.map +1 -0
  27. package/dist/chunk-PGGGKYWG.js +1 -0
  28. package/dist/chunk-PGGGKYWG.js.map +1 -0
  29. package/dist/{chunk-7UDSRZKG.js → chunk-Q3ROPT5H.js} +35 -14
  30. package/dist/chunk-Q3ROPT5H.js.map +1 -0
  31. package/dist/chunk-QE7TWGVT.js +178 -0
  32. package/dist/chunk-QE7TWGVT.js.map +1 -0
  33. package/dist/chunk-RUDYOTA6.js +345 -0
  34. package/dist/chunk-RUDYOTA6.js.map +1 -0
  35. package/dist/{chunk-K32VJONL.js → chunk-T5UK6YOB.js} +41 -2
  36. package/dist/chunk-T5UK6YOB.js.map +1 -0
  37. package/dist/{chunk-3K5OG6KI.js → chunk-TEEEILMP.js} +412 -186
  38. package/dist/chunk-TEEEILMP.js.map +1 -0
  39. package/dist/{chunk-FR2RBTKO.js → chunk-TQWLI6S2.js} +93 -27
  40. package/dist/chunk-TQWLI6S2.js.map +1 -0
  41. package/dist/{chunk-WMOI7FN3.js → chunk-UY7KGQ5R.js} +12 -2
  42. package/dist/{chunk-WMOI7FN3.js.map → chunk-UY7KGQ5R.js.map} +1 -1
  43. package/dist/{chunk-3E5F2XMR.js → chunk-VWUFZ6ZG.js} +29 -2
  44. package/dist/chunk-VWUFZ6ZG.js.map +1 -0
  45. package/dist/chunk-ZLQKNOZQ.js +1 -0
  46. package/dist/chunk-ZLQKNOZQ.js.map +1 -0
  47. package/dist/doc/index.d.ts +5 -5
  48. package/dist/doc/index.js +135 -8
  49. package/dist/generate/index.d.ts +3 -74
  50. package/dist/generate/index.js +2 -7
  51. package/dist/icons/index.d.ts +3 -26
  52. package/dist/icons/index.js +13 -0
  53. package/dist/icons/inlineIconMarker.d.ts +1 -0
  54. package/dist/icons/inlineIconMarker.js +15 -0
  55. package/dist/icons/inlineIconMarker.js.map +1 -0
  56. package/dist/imageEdit/index.d.ts +12 -6
  57. package/dist/imageEdit/index.js +4 -1
  58. package/dist/index.d.ts +14 -11
  59. package/dist/index.js +265 -37
  60. package/dist/inlineIconMarker-CiE8Hlvz.d.ts +59 -0
  61. package/dist/jsonForm/index.d.ts +42 -1
  62. package/dist/jsonForm/index.js +9 -1
  63. package/dist/markdown/index.d.ts +228 -3
  64. package/dist/markdown/index.js +42 -7
  65. package/dist/recommend/index.d.ts +1 -1
  66. package/dist/recommend/index.js +3 -2
  67. package/dist/schemas/index.d.ts +28 -8
  68. package/dist/schemas/index.js +36 -8
  69. package/dist/spatial/index.d.ts +9 -4
  70. package/dist/spatial/index.js +1 -1
  71. package/dist/storage/index.d.ts +2 -2
  72. package/dist/storage/index.js +1 -1
  73. package/dist/story/index.d.ts +1021 -41
  74. package/dist/story/index.js +135 -8
  75. package/dist/{themeLibrary-BHjJ0Qx6.d.ts → themeLibrary-CehcJhzz.d.ts} +1 -1
  76. package/dist/timing/index.js +3 -5
  77. package/dist/transform/index.d.ts +48 -10
  78. package/dist/transform/index.js +17 -481
  79. package/dist/transform/index.js.map +1 -1
  80. package/dist/{types-BdZkdJ3z.d.ts → types-DlAZ7MW4.d.ts} +115 -1
  81. package/dist/versions/index.d.ts +1 -1
  82. package/package.json +6 -1
  83. package/src/__tests__/animationUtils.test.ts +40 -0
  84. package/src/__tests__/annotationCoercion.test.ts +243 -0
  85. package/src/__tests__/applyRenderStyle.test.ts +146 -0
  86. package/src/__tests__/atmosphereLayers.test.ts +108 -0
  87. package/src/__tests__/attrTokens.test.ts +162 -0
  88. package/src/__tests__/contentExtractor.test.ts +60 -0
  89. package/src/__tests__/coverBlock.test.ts +46 -1
  90. package/src/__tests__/customTemplateSample.test.ts +144 -0
  91. package/src/__tests__/customTemplatesFrontmatter.test.ts +236 -0
  92. package/src/__tests__/customThemesFrontmatter.test.ts +145 -0
  93. package/src/__tests__/diagramBlock.test.ts +122 -0
  94. package/src/__tests__/diagramLayout.test.ts +134 -0
  95. package/src/__tests__/drawing.test.ts +109 -0
  96. package/src/__tests__/drawingBlock.test.ts +124 -0
  97. package/src/__tests__/drawingLayout.test.ts +171 -0
  98. package/src/__tests__/geohash.test.ts +6 -5
  99. package/src/__tests__/getLayers.test.ts +6 -2
  100. package/src/__tests__/getLayersFallback.test.ts +103 -0
  101. package/src/__tests__/imageEditExportSvg.test.ts +70 -0
  102. package/src/__tests__/imageEditVersions.test.ts +72 -0
  103. package/src/__tests__/inlineIconMarker.test.ts +36 -0
  104. package/src/__tests__/inputDescriptors.test.ts +162 -0
  105. package/src/__tests__/jsonForm.tokens.test.ts +84 -0
  106. package/src/__tests__/layoutBlock.test.ts +83 -0
  107. package/src/__tests__/layoutLayout.test.ts +169 -0
  108. package/src/__tests__/markdown.test.ts +87 -0
  109. package/src/__tests__/markdownSanitize.test.ts +79 -0
  110. package/src/__tests__/markdownToDoc.test.ts +391 -2
  111. package/src/__tests__/mediaAnnotations.test.ts +91 -0
  112. package/src/__tests__/mediaSchedule.test.ts +118 -0
  113. package/src/__tests__/pandocAttrParse.test.ts +187 -0
  114. package/src/__tests__/resolveDocTheme.test.ts +55 -0
  115. package/src/__tests__/rootExports.test.ts +43 -0
  116. package/src/__tests__/shapeGeometry.test.ts +89 -0
  117. package/src/__tests__/shapeVocabulary.test.ts +133 -0
  118. package/src/__tests__/structuredData.test.ts +249 -0
  119. package/src/__tests__/templateAnnotationParse.test.ts +108 -0
  120. package/src/__tests__/templateMetadata.test.ts +31 -0
  121. package/src/__tests__/templates.test.ts +257 -4
  122. package/src/__tests__/themeCompile.test.ts +42 -0
  123. package/src/__tests__/themeValidator.test.ts +6 -0
  124. package/src/__tests__/transformV2.test.ts +137 -0
  125. package/src/__tests__/transitionNormalize.test.ts +61 -0
  126. package/src/__tests__/validateDoc.test.ts +260 -0
  127. package/src/doc/annotationBlocks.ts +117 -0
  128. package/src/doc/customTemplatesFrontmatter.ts +314 -0
  129. package/src/doc/customThemesFrontmatter.ts +95 -0
  130. package/src/doc/docToMarkdown.ts +104 -5
  131. package/src/doc/getLayers.ts +49 -17
  132. package/src/doc/index.ts +47 -1
  133. package/src/doc/markdownToDoc.ts +411 -13
  134. package/src/doc/mediaAnnotations.ts +100 -0
  135. package/src/doc/resolveDocTheme.ts +55 -0
  136. package/src/doc/standaloneAnnotation.ts +58 -0
  137. package/src/doc/structuredData.ts +301 -0
  138. package/src/doc/templateInputs.ts +240 -0
  139. package/src/doc/templates/__tests__/customTemplate.test.ts +170 -0
  140. package/src/doc/templates/__tests__/customTemplateRegistry.test.ts +110 -0
  141. package/src/doc/templates/accentImage.ts +30 -6
  142. package/src/doc/templates/captionUtils.ts +23 -0
  143. package/src/doc/templates/comparisonBar.ts +33 -18
  144. package/src/doc/templates/coverBlock.ts +35 -5
  145. package/src/doc/templates/customTemplate.ts +51 -0
  146. package/src/doc/templates/dataTable.ts +37 -19
  147. package/src/doc/templates/dateEvent.ts +41 -20
  148. package/src/doc/templates/definitionCard.ts +26 -18
  149. package/src/doc/templates/diagramBlock.ts +204 -0
  150. package/src/doc/templates/diagramLayout.ts +159 -0
  151. package/src/doc/templates/drawingBlock.ts +325 -0
  152. package/src/doc/templates/drawingLayout.ts +539 -0
  153. package/src/doc/templates/factCard.ts +54 -26
  154. package/src/doc/templates/fallbackBlock.ts +123 -0
  155. package/src/doc/templates/featureBlock.ts +20 -13
  156. package/src/doc/templates/fullBleedQuote.ts +18 -8
  157. package/src/doc/templates/imageWithCaption.ts +30 -15
  158. package/src/doc/templates/index.ts +225 -46
  159. package/src/doc/templates/inputDescriptors.ts +432 -0
  160. package/src/doc/templates/layoutBlock.ts +43 -0
  161. package/src/doc/templates/layoutLayout.ts +243 -0
  162. package/src/doc/templates/listBlock.ts +56 -30
  163. package/src/doc/templates/mapBlock.ts +15 -12
  164. package/src/doc/templates/metadata.ts +120 -0
  165. package/src/doc/templates/persistentLayers.ts +129 -11
  166. package/src/doc/templates/photoGrid.ts +22 -34
  167. package/src/doc/templates/pullQuote.ts +30 -12
  168. package/src/doc/templates/quoteBlock.ts +54 -29
  169. package/src/doc/templates/sectionHeader.ts +25 -11
  170. package/src/doc/templates/statHighlight.ts +39 -22
  171. package/src/doc/templates/titleBlock.ts +59 -21
  172. package/src/doc/templates/tokens/__tests__/resolveTokens.test.ts +487 -0
  173. package/src/doc/templates/tokens/resolveTokens.ts +453 -0
  174. package/src/doc/templates/twoColumn.ts +48 -30
  175. package/src/doc/templates/videoPullQuote.ts +22 -12
  176. package/src/doc/templates/videoWithCaption.ts +21 -15
  177. package/src/doc/utils/animationUtils.ts +267 -3
  178. package/src/doc/utils/applyRenderStyle.ts +113 -0
  179. package/src/doc/utils/imageTreatment.ts +70 -0
  180. package/src/doc/utils/nearest.ts +59 -0
  181. package/src/doc/utils/shapeGeometry.ts +464 -0
  182. package/src/doc/utils/themeUtils.ts +75 -1
  183. package/src/doc/validate.ts +575 -0
  184. package/src/generate/contentExtractor.ts +148 -35
  185. package/src/generate/index.ts +0 -3
  186. package/src/icons/index.ts +8 -0
  187. package/src/icons/inlineIconMarker.ts +73 -0
  188. package/src/imageEdit/export.ts +85 -2
  189. package/src/imageEdit/index.ts +1 -0
  190. package/src/imageEdit/persistence.ts +51 -0
  191. package/src/index.ts +2 -0
  192. package/src/jsonForm/index.ts +2 -0
  193. package/src/jsonForm/tokens.ts +78 -0
  194. package/src/markdown/annotationCoercion.ts +295 -0
  195. package/src/markdown/attrTokens.ts +241 -0
  196. package/src/markdown/convert.ts +229 -26
  197. package/src/markdown/index.ts +37 -0
  198. package/src/markdown/sanitize.ts +318 -0
  199. package/src/markdown/stringify.ts +96 -6
  200. package/src/markdown/types.ts +66 -0
  201. package/src/markdown/utils.ts +111 -2
  202. package/src/recommend/templates.ts +24 -0
  203. package/src/schemas/BlockTemplates.ts +133 -4
  204. package/src/schemas/CustomTemplates.ts +126 -0
  205. package/src/schemas/Doc.ts +387 -65
  206. package/src/schemas/ImageEditDoc.ts +11 -4
  207. package/src/schemas/Media.ts +169 -0
  208. package/src/schemas/Theme.ts +23 -1
  209. package/src/schemas/Transitions.ts +224 -0
  210. package/src/schemas/colorUtils.ts +33 -0
  211. package/src/schemas/fontStacks.ts +5 -2
  212. package/src/schemas/index.ts +3 -0
  213. package/src/schemas/themeCompile.ts +24 -5
  214. package/src/schemas/themeValidator.ts +49 -11
  215. package/src/schemas/themes/cinematic.json +69 -12
  216. package/src/schemas/themes/documentary.json +58 -11
  217. package/src/schemas/themes/gezellig.json +60 -11
  218. package/src/schemas/themes/magazine.json +54 -9
  219. package/src/schemas/themes/morning-light.json +52 -9
  220. package/src/schemas/themes/standard-dark.json +48 -13
  221. package/src/schemas/themes/standard.json +44 -12
  222. package/src/schemas/themes/tech-dark.json +65 -12
  223. package/src/spatial/Geohash.ts +52 -21
  224. package/src/storage/ContentContainer.ts +6 -2
  225. package/src/transform/applyTransform.ts +43 -4
  226. package/src/transform/index.ts +1 -0
  227. package/src/transform/registry.ts +44 -13
  228. package/src/transform/styles/dataDriven.ts +2 -1
  229. package/src/transform/styles/documentary.ts +2 -1
  230. package/src/transform/styles/magazine.ts +3 -1
  231. package/src/transform/styles/minimal.ts +2 -1
  232. package/src/transform/styles/narrative.ts +3 -1
  233. package/src/transform/templateSelector.ts +87 -11
  234. package/src/transform/types.ts +35 -2
  235. package/dist/chunk-2TMAWB4Y.js.map +0 -1
  236. package/dist/chunk-3E5F2XMR.js.map +0 -1
  237. package/dist/chunk-3K5OG6KI.js.map +0 -1
  238. package/dist/chunk-4X3JQXNM.js +0 -35
  239. package/dist/chunk-4X3JQXNM.js.map +0 -1
  240. package/dist/chunk-57CVQCFX.js.map +0 -1
  241. package/dist/chunk-5WFRKURX.js.map +0 -1
  242. package/dist/chunk-7UDSRZKG.js.map +0 -1
  243. package/dist/chunk-B4EEON3N.js.map +0 -1
  244. package/dist/chunk-FR2RBTKO.js.map +0 -1
  245. package/dist/chunk-H3AXU4MJ.js +0 -179
  246. package/dist/chunk-H3AXU4MJ.js.map +0 -1
  247. package/dist/chunk-K32VJONL.js.map +0 -1
  248. package/dist/chunk-KSWJR66U.js +0 -3706
  249. package/dist/chunk-KSWJR66U.js.map +0 -1
  250. package/dist/chunk-MYIH7FWD.js +0 -145
  251. package/dist/chunk-MYIH7FWD.js.map +0 -1
  252. package/dist/chunk-ZTX4PFFT.js.map +0 -1
  253. package/src/__tests__/slideshowGenerator.test.ts +0 -128
  254. package/src/generate/slideshowGenerator.ts +0 -273
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/recommend/templates.ts"],"sourcesContent":["/**\n * Block template recommendations.\n *\n * Given a slice of markdown block nodes (the body underneath a heading),\n * profile the content for image / video / quote / list / table / date /\n * stat signals, then map that profile to the set of templates that make\n * sense for the block. Used by the editor's template picker to surface a\n * \"Recommended for this block\" section above the full template list.\n */\n\nimport { extractPlainText, findNodesByType, walkMarkdownTree } from '../markdown/utils.js';\nimport type {\n HtmlElement,\n HtmlNode,\n MarkdownBlockNode,\n MarkdownHtmlBlock,\n MarkdownNode,\n} from '../markdown/types.js';\n\nexport interface BlockContentProfile {\n hasImage: boolean;\n imageCount: number;\n hasVideo: boolean;\n hasBlockquote: boolean;\n hasList: boolean;\n hasTable: boolean;\n hasDate: boolean;\n hasNumberHighlight: boolean;\n wordCount: number;\n}\n\nexport interface RecommendationResult {\n recommended: string[];\n rest: string[];\n}\n\nconst EMPTY_PROFILE: BlockContentProfile = Object.freeze({\n hasImage: false,\n imageCount: 0,\n hasVideo: false,\n hasBlockquote: false,\n hasList: false,\n hasTable: false,\n hasDate: false,\n hasNumberHighlight: false,\n wordCount: 0,\n});\n\nconst DATE_PATTERNS: RegExp[] = [\n /\\b(January|February|March|April|May|June|July|August|September|October|November|December)\\s+(\\d{1,2},?\\s+)?\\d{4}\\b/i,\n /\\b\\d{1,2}\\s+(January|February|March|April|May|June|July|August|September|October|November|December)\\s+\\d{4}\\b/i,\n /\\b(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Sept|Oct|Nov|Dec)\\.?\\s+\\d{1,2},?\\s+\\d{4}\\b/i,\n /\\b\\d{4}-\\d{2}-\\d{2}\\b/,\n /\\b\\d{1,2}\\/\\d{1,2}\\/\\d{2,4}\\b/,\n /\\bQ[1-4]\\s+\\d{4}\\b/,\n /\\b\\d{4}s\\b/,\n /\\b\\d{1,2}(st|nd|rd|th)\\s+century\\b/i,\n];\n\n/**\n * Matches a \"highlight number\" — a prominent figure suitable for a Stat\n * Highlight block. Qualifies on any of:\n * - currency prefix: `$2.3M`, `€500`, `¥1,234`\n * - unit suffix: `50%`, `100 years`, `5×`, `2.3M`\n * - standalone large: `1,234`, `5000`, `12,345`\n * Plain small numbers (`42`) intentionally don't qualify.\n */\nconst NUMBER_HIGHLIGHT_RE =\n /(?:[$€£¥]\\s?\\d+(?:[.,]\\d+)*(?:\\s?(?:[MBK]|million|billion|thousand))?|\\d+(?:[.,]\\d+)*\\s?(?:%|‰|x|×|[MBK]|million|billion|thousand|percent|years?|days?|hours?)|\\d{3,}(?:[.,]\\d+)*)/i;\n\nconst VIDEO_HOST_RE = /(youtube\\.com|youtu\\.be|vimeo\\.com|wistia\\.|loom\\.com)/i;\n\nfunction htmlElementsByTag(root: MarkdownNode, tagNames: Set<string>): HtmlElement[] {\n const blocks = findNodesByType<MarkdownHtmlBlock>(root, 'htmlBlock');\n // Inline HTML (htmlInline) also carries htmlChildren; include both.\n const inline = findNodesByType<MarkdownNode>(root, 'htmlInline') as Array<\n MarkdownNode & { htmlChildren?: HtmlNode[] }\n >;\n const out: HtmlElement[] = [];\n const visit = (n: HtmlNode): void => {\n if (n.type === 'htmlElement') {\n if (tagNames.has(n.tagName.toLowerCase())) out.push(n);\n for (const c of n.children) visit(c);\n }\n };\n for (const b of blocks) for (const c of b.htmlChildren) visit(c);\n for (const i of inline) {\n if (Array.isArray(i.htmlChildren)) for (const c of i.htmlChildren) visit(c);\n }\n return out;\n}\n\n/**\n * Walk a slice of block nodes and summarize what kinds of content it\n * contains. Cheap to run — no markdown parsing, just AST traversal.\n */\nexport function profileBlockContents(nodes: MarkdownBlockNode[]): BlockContentProfile {\n if (!nodes || nodes.length === 0) return { ...EMPTY_PROFILE };\n\n let imageCount = 0;\n let hasBlockquote = false;\n let hasList = false;\n let hasTable = false;\n\n const plainParts: string[] = [];\n\n for (const node of nodes) {\n imageCount += findNodesByType(node, 'image').length;\n imageCount += findNodesByType(node, 'imageReference').length;\n if (findNodesByType(node, 'blockquote').length > 0) hasBlockquote = true;\n if (findNodesByType(node, 'list').length > 0) hasList = true;\n if (findNodesByType(node, 'table').length > 0) hasTable = true;\n plainParts.push(extractPlainText(node));\n }\n\n // HTML embeds: <img>, <video>, <iframe src=…video host>\n const root: MarkdownNode = { type: 'document', children: nodes };\n const imgTags = htmlElementsByTag(root, new Set(['img']));\n imageCount += imgTags.length;\n\n const videoTags = htmlElementsByTag(root, new Set(['video', 'iframe', 'source']));\n let hasVideo = false;\n for (const el of videoTags) {\n if (el.tagName.toLowerCase() === 'video') {\n hasVideo = true;\n break;\n }\n const src = el.attributes.src || el.attributes.href || '';\n if (VIDEO_HOST_RE.test(src)) {\n hasVideo = true;\n break;\n }\n }\n\n // Link-only video embeds (markdown link to a known video host).\n if (!hasVideo) {\n const links = findNodesByType<MarkdownNode & { url?: string }>(root, 'link');\n for (const link of links) {\n if (link.url && VIDEO_HOST_RE.test(link.url)) {\n hasVideo = true;\n break;\n }\n }\n }\n\n const plainText = plainParts.join(' ').trim();\n const wordCount = plainText ? plainText.split(/\\s+/).length : 0;\n\n let hasDate = false;\n for (const re of DATE_PATTERNS) {\n if (re.test(plainText)) {\n hasDate = true;\n break;\n }\n }\n\n // Number highlight: a paragraph that's mostly a single prominent figure.\n let hasNumberHighlight = false;\n walkMarkdownTree(root, (n) => {\n if (hasNumberHighlight) return true;\n if (n.type !== 'paragraph') return;\n const text = extractPlainText(n).trim();\n if (!text) return true;\n const m = text.match(NUMBER_HIGHLIGHT_RE);\n if (!m) return true;\n // Paragraph qualifies when its word count is small AND a prominent\n // number occupies a meaningful chunk of it. Keeps \"the company grew\n // 50% last year\" from triggering, but accepts \"$2.3M raised\".\n const words = text.split(/\\s+/).length;\n if (words <= 8) hasNumberHighlight = true;\n return true;\n });\n\n return {\n hasImage: imageCount > 0,\n imageCount,\n hasVideo,\n hasBlockquote,\n hasList,\n hasTable,\n hasDate,\n hasNumberHighlight,\n wordCount,\n };\n}\n\n// ── Recommendation rules ───────────────────────────────────────────\n\nconst UNIVERSAL_DEFAULTS = ['title', 'sectionHeader', 'factCard', 'twoColumn'];\n\n/**\n * Given a content profile, return the canonical template-name list that\n * should appear in the \"Recommended\" bucket. Caller is responsible for\n * preserving deduplication / ordering when intersecting with the full\n * template list (see {@link recommendTemplatesForBlock}).\n */\nfunction recommendedNamesForProfile(profile: BlockContentProfile): string[] {\n const names = new Set<string>();\n let anyContentSignal = false;\n\n if (profile.hasImage) {\n anyContentSignal = true;\n names.add('imageWithCaption');\n names.add('leftFeature');\n names.add('rightFeature');\n if (profile.imageCount >= 2) names.add('photoGrid');\n }\n\n if (profile.hasVideo) {\n anyContentSignal = true;\n names.add('videoWithCaption');\n if (profile.hasBlockquote) names.add('videoPullQuote');\n }\n\n if (profile.hasBlockquote) {\n anyContentSignal = true;\n names.add('quote');\n names.add('pullQuote');\n names.add('fullBleedQuote');\n }\n\n if (profile.hasList) {\n anyContentSignal = true;\n names.add('list');\n }\n\n if (profile.hasTable) {\n anyContentSignal = true;\n names.add('dataTable');\n names.add('comparisonBar');\n }\n\n if (profile.hasDate) {\n anyContentSignal = true;\n names.add('dateEvent');\n }\n\n if (profile.hasNumberHighlight) {\n anyContentSignal = true;\n names.add('statHighlight');\n }\n\n if (anyContentSignal) {\n // Universal pair only — keep recommended focused.\n names.add('title');\n names.add('sectionHeader');\n } else {\n for (const n of UNIVERSAL_DEFAULTS) names.add(n);\n }\n\n return Array.from(names);\n}\n\n/**\n * Split a flat list of template names into a \"Recommended\" bucket\n * (matches the active block's content profile) and a \"rest\" bucket\n * (everything else). Both buckets preserve the input ordering of\n * `allNames`, so the visual layout stays predictable as templates are\n * added.\n */\nexport function recommendTemplatesForBlock(\n profile: BlockContentProfile,\n allNames: readonly string[],\n): RecommendationResult {\n const desired = new Set(recommendedNamesForProfile(profile));\n const recommended: string[] = [];\n const rest: string[] = [];\n for (const name of allNames) {\n if (desired.has(name)) recommended.push(name);\n else rest.push(name);\n }\n return { recommended, rest };\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/recommend/templates.ts"],"sourcesContent":["/**\n * Block template recommendations.\n *\n * Given a slice of markdown block nodes (the body underneath a heading),\n * profile the content for image / video / quote / list / table / date /\n * stat signals, then map that profile to the set of templates that make\n * sense for the block. Used by the editor's template picker to surface a\n * \"Recommended for this block\" section above the full template list.\n */\n\nimport { extractPlainText, findNodesByType, walkMarkdownTree } from '../markdown/utils.js';\nimport type {\n HtmlElement,\n HtmlNode,\n MarkdownBlockNode,\n MarkdownHtmlBlock,\n MarkdownNode,\n} from '../markdown/types.js';\n\nexport interface BlockContentProfile {\n hasImage: boolean;\n imageCount: number;\n hasVideo: boolean;\n hasBlockquote: boolean;\n hasList: boolean;\n hasTable: boolean;\n hasDate: boolean;\n hasNumberHighlight: boolean;\n wordCount: number;\n}\n\nexport interface RecommendationResult {\n recommended: string[];\n rest: string[];\n}\n\nconst EMPTY_PROFILE: BlockContentProfile = Object.freeze({\n hasImage: false,\n imageCount: 0,\n hasVideo: false,\n hasBlockquote: false,\n hasList: false,\n hasTable: false,\n hasDate: false,\n hasNumberHighlight: false,\n wordCount: 0,\n});\n\nconst DATE_PATTERNS: RegExp[] = [\n /\\b(January|February|March|April|May|June|July|August|September|October|November|December)\\s+(\\d{1,2},?\\s+)?\\d{4}\\b/i,\n /\\b\\d{1,2}\\s+(January|February|March|April|May|June|July|August|September|October|November|December)\\s+\\d{4}\\b/i,\n /\\b(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Sept|Oct|Nov|Dec)\\.?\\s+\\d{1,2},?\\s+\\d{4}\\b/i,\n /\\b\\d{4}-\\d{2}-\\d{2}\\b/,\n /\\b\\d{1,2}\\/\\d{1,2}\\/\\d{2,4}\\b/,\n /\\bQ[1-4]\\s+\\d{4}\\b/,\n /\\b\\d{4}s\\b/,\n /\\b\\d{1,2}(st|nd|rd|th)\\s+century\\b/i,\n];\n\n/**\n * Matches a \"highlight number\" — a prominent figure suitable for a Stat\n * Highlight block. Qualifies on any of:\n * - currency prefix: `$2.3M`, `€500`, `¥1,234`\n * - unit suffix: `50%`, `100 years`, `5×`, `2.3M`\n * - standalone large: `1,234`, `5000`, `12,345`\n * Plain small numbers (`42`) intentionally don't qualify.\n */\nconst NUMBER_HIGHLIGHT_RE =\n /(?:[$€£¥]\\s?\\d+(?:[.,]\\d+)*(?:\\s?(?:[MBK]|million|billion|thousand))?|\\d+(?:[.,]\\d+)*\\s?(?:%|‰|x|×|[MBK]|million|billion|thousand|percent|years?|days?|hours?)|\\d{3,}(?:[.,]\\d+)*)/i;\n\nconst VIDEO_HOST_RE = /(youtube\\.com|youtu\\.be|vimeo\\.com|wistia\\.|loom\\.com)/i;\n\nfunction htmlElementsByTag(root: MarkdownNode, tagNames: Set<string>): HtmlElement[] {\n const blocks = findNodesByType<MarkdownHtmlBlock>(root, 'htmlBlock');\n // Inline HTML (htmlInline) also carries htmlChildren; include both.\n const inline = findNodesByType<MarkdownNode>(root, 'htmlInline') as Array<\n MarkdownNode & { htmlChildren?: HtmlNode[] }\n >;\n const out: HtmlElement[] = [];\n const visit = (n: HtmlNode): void => {\n if (n.type === 'htmlElement') {\n if (tagNames.has(n.tagName.toLowerCase())) out.push(n);\n for (const c of n.children) visit(c);\n }\n };\n for (const b of blocks) for (const c of b.htmlChildren) visit(c);\n for (const i of inline) {\n if (Array.isArray(i.htmlChildren)) for (const c of i.htmlChildren) visit(c);\n }\n return out;\n}\n\n/**\n * Walk a slice of block nodes and summarize what kinds of content it\n * contains. Cheap to run — no markdown parsing, just AST traversal.\n */\nexport function profileBlockContents(nodes: MarkdownBlockNode[]): BlockContentProfile {\n if (!nodes || nodes.length === 0) return { ...EMPTY_PROFILE };\n\n let imageCount = 0;\n let hasBlockquote = false;\n let hasList = false;\n let hasTable = false;\n\n const plainParts: string[] = [];\n\n for (const node of nodes) {\n imageCount += findNodesByType(node, 'image').length;\n imageCount += findNodesByType(node, 'imageReference').length;\n if (findNodesByType(node, 'blockquote').length > 0) hasBlockquote = true;\n if (findNodesByType(node, 'list').length > 0) hasList = true;\n if (findNodesByType(node, 'table').length > 0) hasTable = true;\n plainParts.push(extractPlainText(node));\n }\n\n // HTML embeds: <img>, <video>, <iframe src=…video host>\n const root: MarkdownNode = { type: 'document', children: nodes };\n const imgTags = htmlElementsByTag(root, new Set(['img']));\n imageCount += imgTags.length;\n\n const videoTags = htmlElementsByTag(root, new Set(['video', 'iframe', 'source']));\n let hasVideo = false;\n for (const el of videoTags) {\n if (el.tagName.toLowerCase() === 'video') {\n hasVideo = true;\n break;\n }\n const src = el.attributes.src || el.attributes.href || '';\n if (VIDEO_HOST_RE.test(src)) {\n hasVideo = true;\n break;\n }\n }\n\n // Link-only video embeds (markdown link to a known video host).\n if (!hasVideo) {\n const links = findNodesByType<MarkdownNode & { url?: string }>(root, 'link');\n for (const link of links) {\n if (link.url && VIDEO_HOST_RE.test(link.url)) {\n hasVideo = true;\n break;\n }\n }\n }\n\n const plainText = plainParts.join(' ').trim();\n const wordCount = plainText ? plainText.split(/\\s+/).length : 0;\n\n let hasDate = false;\n for (const re of DATE_PATTERNS) {\n if (re.test(plainText)) {\n hasDate = true;\n break;\n }\n }\n\n // Number highlight: a paragraph that's mostly a single prominent figure.\n let hasNumberHighlight = false;\n walkMarkdownTree(root, (n) => {\n if (hasNumberHighlight) return true;\n if (n.type !== 'paragraph') return;\n const text = extractPlainText(n).trim();\n if (!text) return true;\n const m = text.match(NUMBER_HIGHLIGHT_RE);\n if (!m) return true;\n // Paragraph qualifies when its word count is small AND a prominent\n // number occupies a meaningful chunk of it. Keeps \"the company grew\n // 50% last year\" from triggering, but accepts \"$2.3M raised\".\n const words = text.split(/\\s+/).length;\n if (words <= 8) hasNumberHighlight = true;\n return true;\n });\n\n return {\n hasImage: imageCount > 0,\n imageCount,\n hasVideo,\n hasBlockquote,\n hasList,\n hasTable,\n hasDate,\n hasNumberHighlight,\n wordCount,\n };\n}\n\n// ── Recommendation rules ───────────────────────────────────────────\n\nconst UNIVERSAL_DEFAULTS = ['title', 'sectionHeader', 'factCard', 'twoColumn'];\n\n/**\n * Given a content profile, return the canonical template-name list that\n * should appear in the \"Recommended\" bucket. Caller is responsible for\n * preserving deduplication / ordering when intersecting with the full\n * template list (see {@link recommendTemplatesForBlock}).\n */\nfunction recommendedNamesForProfile(profile: BlockContentProfile): string[] {\n const names = new Set<string>();\n let anyContentSignal = false;\n\n if (profile.hasImage) {\n anyContentSignal = true;\n names.add('imageWithCaption');\n names.add('leftFeature');\n names.add('rightFeature');\n if (profile.imageCount >= 2) names.add('photoGrid');\n }\n\n if (profile.hasVideo) {\n anyContentSignal = true;\n names.add('videoWithCaption');\n if (profile.hasBlockquote) names.add('videoPullQuote');\n }\n\n if (profile.hasBlockquote) {\n anyContentSignal = true;\n names.add('quote');\n names.add('pullQuote');\n names.add('fullBleedQuote');\n }\n\n if (profile.hasList) {\n anyContentSignal = true;\n names.add('list');\n }\n\n if (profile.hasTable) {\n anyContentSignal = true;\n names.add('dataTable');\n names.add('comparisonBar');\n }\n\n if (profile.hasDate) {\n anyContentSignal = true;\n names.add('dateEvent');\n }\n\n if (profile.hasNumberHighlight) {\n anyContentSignal = true;\n names.add('statHighlight');\n }\n\n if (anyContentSignal) {\n // Universal pair only — keep recommended focused.\n names.add('title');\n names.add('sectionHeader');\n } else {\n for (const n of UNIVERSAL_DEFAULTS) names.add(n);\n }\n\n return Array.from(names);\n}\n\n/**\n * Deterministic single-template choice for an unannotated heading block,\n * from strong content signals only. Returns `undefined` when no signal is\n * decisive — the caller keeps its structural default (`sectionHeader`).\n *\n * Precedence favors the most specific/structured content: a table beats\n * an image beats a blockquote beats a stat-looking line. `blockIndex`\n * alternates left/right feature composition for single-image blocks so a\n * run of image sections doesn't stack the same layout.\n *\n * Used by `markdownToDoc`'s auto template picking (see its `autoTemplates`\n * option); the multi-candidate `recommendTemplatesForBlock` below serves\n * the editor's template-picker UI instead.\n */\nexport function pickAutoTemplate(profile: BlockContentProfile, blockIndex = 0): string | undefined {\n if (profile.hasTable) return 'dataTable';\n if (profile.imageCount >= 2) return 'photoGrid';\n if (profile.hasImage) return blockIndex % 2 === 0 ? 'leftFeature' : 'rightFeature';\n if (profile.hasBlockquote) return 'quote';\n if (profile.hasNumberHighlight) return 'statHighlight';\n if (profile.hasList) return 'list';\n return undefined;\n}\n\n/**\n * Split a flat list of template names into a \"Recommended\" bucket\n * (matches the active block's content profile) and a \"rest\" bucket\n * (everything else). Both buckets preserve the input ordering of\n * `allNames`, so the visual layout stays predictable as templates are\n * added.\n */\nexport function recommendTemplatesForBlock(\n profile: BlockContentProfile,\n allNames: readonly string[],\n): RecommendationResult {\n const desired = new Set(recommendedNamesForProfile(profile));\n const recommended: string[] = [];\n const rest: string[] = [];\n for (const name of allNames) {\n if (desired.has(name)) recommended.push(name);\n else rest.push(name);\n }\n return { recommended, rest };\n}\n"],"mappings":"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","names":[]}
@@ -118,12 +118,39 @@ function estimateNarrationDuration(text, wordsPerSecond = DEFAULT_WORDS_PER_SECO
118
118
  return spokenWords / wordsPerSecond;
119
119
  }
120
120
 
121
+ // src/timing/readingTime.ts
122
+ var DEFAULT_READING_WPM = 200;
123
+ function estimateReadingTime(text, options) {
124
+ const configuredWpm = options?.wordsPerMinute;
125
+ const wpm = typeof configuredWpm === "number" && configuredWpm > 0 ? configuredWpm : DEFAULT_READING_WPM;
126
+ const words = text.split(/\s+/).filter((w) => w.length > 0).length;
127
+ const minutes = words / wpm;
128
+ return {
129
+ words,
130
+ minutes,
131
+ seconds: Math.round(minutes * 60)
132
+ };
133
+ }
134
+ function estimateNarrationTime(text, options) {
135
+ const providedWps = options?.wordsPerSecond;
136
+ const wps = typeof providedWps === "number" && providedWps > 0 ? providedWps : DEFAULT_WORDS_PER_SECOND;
137
+ const spokenWords = countSpokenWords(text);
138
+ const totalSeconds = spokenWords / wps;
139
+ return {
140
+ spokenWords,
141
+ minutes: totalSeconds / 60,
142
+ seconds: Math.round(totalSeconds)
143
+ };
144
+ }
145
+
121
146
  export {
122
147
  DEFAULT_WORDS_PER_SECOND,
123
148
  estimateSpokenWordCount,
124
149
  countSpokenWords,
125
150
  estimateTimeFromText,
126
151
  calculatePrefixDuration,
127
- estimateNarrationDuration
152
+ estimateNarrationDuration,
153
+ estimateReadingTime,
154
+ estimateNarrationTime
128
155
  };
129
- //# sourceMappingURL=chunk-3E5F2XMR.js.map
156
+ //# sourceMappingURL=chunk-VWUFZ6ZG.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/timing/narrationTiming.ts","../src/timing/readingTime.ts"],"sourcesContent":["/**\n * Narration timing estimation.\n *\n * Estimates how long text takes to speak aloud, accounting for:\n * - Numbers spoken as multiple words (e.g., \"1910\" → \"nineteen ten\")\n * - Pauses at commas\n * - Year pronunciation conventions (1000–2099)\n *\n * Ported from qualla-internal's AudioTiming.ts (pure functions, no Node.js deps).\n */\n\n/** Numbers are spoken more slowly than regular words (1.3× multiplier). */\nconst NUMBER_SPEAKING_MULTIPLIER = 1.3;\n\n/** Comma pause in \"word equivalents\" — speakers pause ~0.3 words at commas. */\nconst COMMA_PAUSE_PENALTY = 0.3;\n\n/** Default speaking rate in words per second (~150 WPM). */\nexport const DEFAULT_WORDS_PER_SECOND = 2.5;\n\n/**\n * Estimate how many \"spoken word equivalents\" a single token represents.\n * Numbers get expanded (e.g., \"175,000\" → ~5 words × 1.3 multiplier).\n */\nexport function estimateSpokenWordCount(token: string): number {\n const cleaned = token.replace(/,/g, '');\n const num = parseFloat(cleaned);\n\n if (isNaN(num) || !isFinite(num)) {\n return 1; // Not a number — count as 1 word\n }\n\n return estimateNumberWordCount(cleaned, num) * NUMBER_SPEAKING_MULTIPLIER;\n}\n\n/**\n * Raw word count for a number (before the speaking multiplier).\n */\nfunction estimateNumberWordCount(cleaned: string, num: number): number {\n // Handle decimals: \"3.14\" → words(\"3\") + \"point\" + 2 digits\n if (cleaned.includes('.')) {\n const [whole, decimal] = cleaned.split('.');\n const wholeNum = parseFloat(whole) || 0;\n return estimateNumberWordCount(whole, wholeNum) + 1 + decimal.length;\n }\n\n const absNum = Math.abs(num);\n let words = num < 0 ? 1 : 0; // \"negative\" / \"minus\"\n\n if (absNum === 0) return 1; // \"zero\"\n\n // Years (1000–2099) are spoken as two two-digit groups:\n // \"1910\" → \"nineteen ten\", \"2024\" → \"twenty twenty-four\"\n if (absNum >= 1000 && absNum <= 2099 && Number.isInteger(absNum)) {\n const yearStr = absNum.toString();\n if (yearStr.length === 4) {\n const firstTwo = parseInt(yearStr.slice(0, 2));\n const lastTwo = parseInt(yearStr.slice(2));\n\n // First part\n if (firstTwo >= 10 && firstTwo <= 19) {\n words += 1;\n } else if (firstTwo >= 20 && firstTwo <= 99) {\n words += firstTwo % 10 === 0 ? 1 : 2;\n } else {\n words += 2; // \"two thousand\"\n }\n\n // Second part\n if (lastTwo === 0) {\n if (firstTwo >= 10 && firstTwo <= 99 && firstTwo !== 20) {\n words += 1; // \"hundred\"\n }\n } else if (lastTwo >= 1 && lastTwo <= 9) {\n words += 2; // \"oh five\"\n } else if (lastTwo >= 10 && lastTwo <= 19) {\n words += 1;\n } else {\n words += lastTwo % 10 === 0 ? 1 : 2;\n }\n\n return Math.max(words, 2);\n }\n }\n\n // General number handling — each magnitude group adds words\n let remaining = absNum;\n\n if (remaining >= 1_000_000_000) {\n const billions = Math.floor(remaining / 1_000_000_000);\n words += estimateNumberWordCount(String(billions), billions);\n words += 1; // \"billion\"\n remaining %= 1_000_000_000;\n }\n\n if (remaining >= 1_000_000) {\n const millions = Math.floor(remaining / 1_000_000);\n words += estimateNumberWordCount(String(millions), millions);\n words += 1; // \"million\"\n remaining %= 1_000_000;\n }\n\n if (remaining >= 1_000) {\n const thousands = Math.floor(remaining / 1_000);\n words += estimateNumberWordCount(String(thousands), thousands);\n words += 1; // \"thousand\"\n remaining %= 1_000;\n }\n\n if (remaining >= 100) {\n words += 2; // digit + \"hundred\"\n remaining %= 100;\n }\n\n if (remaining >= 1) {\n if (remaining >= 20) {\n words += remaining % 10 === 0 ? 1 : 2;\n } else {\n words += 1; // 1–19\n }\n }\n\n return Math.max(words, 1);\n}\n\n/**\n * Count \"spoken word equivalents\" in a block of text,\n * expanding numbers and adding comma pauses.\n */\nexport function countSpokenWords(text: string): number {\n const tokens = text.split(/\\s+/).filter((w) => w.length > 0);\n let count = 0;\n\n for (const token of tokens) {\n // Strip punctuation only at token boundaries so internal punctuation\n // (e.g., decimals like \"3.14\" or digit-grouping commas \"1,000\") is preserved.\n const stripped = token.replace(/^[.,!?;:'\"()[\\]{}]+|[.,!?;:'\"()[\\]{}]+$/g, '');\n count += estimateSpokenWordCount(stripped);\n\n // Count only punctuation commas for pauses: exclude digit-grouping commas\n // (i.e., commas with digits on both sides, like in \"1,234\").\n let punctuationCommaCount = 0;\n for (let i = 0; i < token.length; i++) {\n if (token[i] === ',') {\n const prev = i > 0 ? token[i - 1] : '';\n const next = i < token.length - 1 ? token[i + 1] : '';\n const prevIsDigit = prev >= '0' && prev <= '9';\n const nextIsDigit = next >= '0' && next <= '9';\n if (!(prevIsDigit && nextIsDigit)) {\n punctuationCommaCount++;\n }\n }\n }\n count += punctuationCommaCount * COMMA_PAUSE_PENALTY;\n }\n\n return count;\n}\n\n/**\n * Estimate audio time at a character offset using word-proportion mapping.\n *\n * Given the full source text and a character position, returns the\n * estimated time (in seconds) at which that position would be spoken.\n *\n * @param text - Full source text\n * @param charOffset - Character offset to estimate timing for\n * @param totalDuration - Total audio duration in seconds\n * @returns Estimated time in seconds\n */\nexport function estimateTimeFromText(\n text: string,\n charOffset: number,\n totalDuration: number,\n): number {\n const textBefore = text.slice(0, charOffset);\n const spokenWordsBefore = countSpokenWords(textBefore);\n const totalSpokenWords = countSpokenWords(text);\n\n if (totalSpokenWords === 0) return 0;\n return (spokenWordsBefore / totalSpokenWords) * totalDuration;\n}\n\n/**\n * Estimate how long a spoken prefix adds to audio duration.\n *\n * When audio includes a spoken title or header before the main content,\n * this calculates the extra duration to offset timing calculations.\n *\n * @param prefix - Prefix text (e.g., an article title spoken before the body)\n * @param wordsPerSecond - Speaking rate (default: 2.5)\n * @returns Estimated duration in seconds\n */\nexport function calculatePrefixDuration(\n prefix: string,\n wordsPerSecond: number = DEFAULT_WORDS_PER_SECOND,\n): number {\n if (!prefix) return 0;\n\n const words = prefix.split(/\\s+/).filter((w) => w.length > 0);\n const newlineCount = (prefix.match(/\\n\\n/g) || []).length;\n const pauseTime = newlineCount * 0.5;\n\n return words.length / wordsPerSecond + pauseTime;\n}\n\n/**\n * Estimate narration duration for a block of text.\n *\n * @param text - The text to estimate\n * @param wordsPerSecond - Speaking rate (default: 2.5, i.e. ~150 WPM)\n * @returns Duration in seconds\n */\nexport function estimateNarrationDuration(\n text: string,\n wordsPerSecond: number = DEFAULT_WORDS_PER_SECOND,\n): number {\n const spokenWords = countSpokenWords(text);\n return spokenWords / wordsPerSecond;\n}\n","/**\n * Reading time estimation.\n *\n * Estimates how long it takes to read text silently vs. narrate it aloud.\n * Uses configurable words-per-minute rates.\n */\n\nimport { countSpokenWords, DEFAULT_WORDS_PER_SECOND } from './narrationTiming.js';\n\n/** Default silent reading rate (adults average ~200–250 WPM). */\nconst DEFAULT_READING_WPM = 200;\n\n/** Result of a reading-time estimate. */\nexport interface ReadingTimeEstimate {\n /** Number of words (plain word count, not spoken equivalents). */\n words: number;\n /** Estimated minutes (decimal). */\n minutes: number;\n /** Estimated seconds (rounded). */\n seconds: number;\n}\n\n/** Options for reading-time estimation. */\nexport interface ReadingTimeOptions {\n /** Words per minute for silent reading (default: 200). */\n wordsPerMinute?: number;\n}\n\n/**\n * Estimate silent reading time for a block of text.\n *\n * @param text - The text to estimate\n * @param options - Optional configuration\n * @returns Word count + time estimate\n */\nexport function estimateReadingTime(\n text: string,\n options?: ReadingTimeOptions,\n): ReadingTimeEstimate {\n const configuredWpm = options?.wordsPerMinute;\n const wpm =\n typeof configuredWpm === 'number' && configuredWpm > 0 ? configuredWpm : DEFAULT_READING_WPM;\n const words = text.split(/\\s+/).filter((w) => w.length > 0).length;\n const minutes = words / wpm;\n\n return {\n words,\n minutes,\n seconds: Math.round(minutes * 60),\n };\n}\n\n/** Result of a narration-time estimate. */\nexport interface NarrationTimeEstimate {\n /** Spoken word equivalents (numbers expanded, comma pauses added). */\n spokenWords: number;\n /** Estimated minutes (decimal). */\n minutes: number;\n /** Estimated seconds (rounded). */\n seconds: number;\n}\n\n/** Options for narration-time estimation. */\nexport interface NarrationTimeOptions {\n /** Words per second for narration (default: 2.5 ≈ 150 WPM). */\n wordsPerSecond?: number;\n}\n\n/**\n * Estimate narration (spoken) time for a block of text.\n *\n * Unlike silent reading, this accounts for numbers being spoken as\n * multiple words and pauses at commas.\n *\n * @param text - The text to estimate\n * @param options - Optional configuration\n * @returns Spoken word equivalents + time estimate\n */\nexport function estimateNarrationTime(\n text: string,\n options?: NarrationTimeOptions,\n): NarrationTimeEstimate {\n const providedWps = options?.wordsPerSecond;\n const wps =\n typeof providedWps === 'number' && providedWps > 0 ? providedWps : DEFAULT_WORDS_PER_SECOND;\n const spokenWords = countSpokenWords(text);\n const totalSeconds = spokenWords / wps;\n\n return {\n spokenWords,\n minutes: totalSeconds / 60,\n seconds: Math.round(totalSeconds),\n };\n}\n"],"mappings":"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","names":[]}
@@ -0,0 +1 @@
1
+ //# sourceMappingURL=chunk-ZLQKNOZQ.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
@@ -1,5 +1,5 @@
1
- export { AccentLayout, AudioSegmentTiming, CoverBlockInput, DEFAULT_LAYOUT, DocStylePreset, ExpandDocBlocksOptions, MarkdownToDocOptions, RenderContext, TEMPLATE_ALIASES, adjustY, comparisonBar, countBlocks, coverBlock, createAccentLayers, dataTable, dateEvent, definitionCard, docToMarkdown, expandCoverBlock, expandDocBlocks, expandPersistentLayers, expandTemplateBlock, factCard, flattenBlocks, fullBleedQuote, getAccentLayout, getAnimationProgress, getAnimationStyle, getAvailableTemplates, getBlockDepth, getDefaultAnimation, getDefaultAnimationDuration, getDocStyleConfig, getLayers, getOverlayOpacity, getTemplateHint, getThemeFont, getTransitionClass, hasTemplate, imageWithCaption, leftFeature, listBlock, mapBlock, markdownToDoc, photoGrid, pullQuote, quoteBlock, resolveAudioMapping, resolveColorScheme, resolveTemplateName, rightFeature, scaleAnimationDuration, scoreTextSimilarity, sectionHeader, shouldUseShadow, statHighlight, templateRegistry, themedFontSize, titleBlock, twoColumn, videoPullQuote, videoWithCaption } from '../story/index.js';
2
- export { D as DEFAULT_THEME, g as getAvailableThemes, b as getThemeSummaries, r as resolveTheme } from '../themeLibrary-BHjJ0Qx6.js';
3
- export { t as DocBlock, E as LayoutHints, R as PersistentLayerConfig, _ as RenderStyle, a9 as TemplateBlock, aa as TemplateContext, af as Theme, ag as ThemeColorPalette, ah as ThemeColorScheme, ak as ThemeStyle, al as ThemeTypography, ar as VIEWPORT_PRESETS, av as ViewportConfig, aw as ViewportOrientation, ax as ViewportPreset, aB as createTemplateContext, aG as getLayoutHints, aK as getTwoColumnPositions, aL as getViewport, aM as getViewportOrientation, aO as isTemplateBlock, aS as scaledFontSize } from '../Doc-BeSeH2iX.js';
4
- import '../types-BdZkdJ3z.js';
5
- import '../ContentContainer-DNx460y8.js';
1
+ export { AccentLayout, AudioSegmentTiming, BASE_INPUT_DESCRIPTORS, CONTAINER_TEMPLATES, ClipBox, ConnectorRouting, CoverBlockInput, DEFAULT_LAYOUT, DataFenceParseResult, DeriveTemplateInputsOptions, DiagramEdge, DiagramLayout, DiagramLayoutOptions, DiagramNodePosition, DocStylePreset, DrawingConnector, DrawingLayout, DrawingLayoutOptions, DrawingShape, DrawingShapeKind, ExpandDocBlocksOptions, ExtractedTableData, FirstImage, InputCoercion, LayoutLayerDefaults, LayoutLayersResult, MarkdownToDocOptions, MarkdownValidationResult, PATH_SHAPE_KINDS, RenderContext, RuntimeTemplateRegistry, SHAPE_NAMES, TEMPLATE_ALIASES, TEMPLATE_INPUT_DESCRIPTORS, TEMPLATE_METADATA, TemplateInputDescriptor, TemplateMetadata, TemplateParamFinding, ValidateOptions, adjustY, applyRenderStyleToLayers, buildRegistry, clipEndpoints, clipPoint, coerceTemplateParams, comparisonBar, computeDiagramLayout, computeDrawingLayout, computeLayoutLayers, connectorPath, countBlocks, coverBlock, createAccentLayers, cssFilterForTreatment, dataTable, dateEvent, decodeLayersFromFrontmatter, definitionCard, deriveTemplateInputs, diagramBlock, docToMarkdown, drawingBlock, encodeLayersForFrontmatter, expandCoverBlock, expandDocBlocks, expandPersistentLayers, expandTemplateBlock, extractBlockquoteText, extractBodyPlainText, extractFirstImage, extractImages, extractListItems, extractTableData, extractTableFromContents, factCard, fallbackBlockLayers, findFirstTable, flattenBlocks, flattenRenderableBlocks, fullBleedQuote, getAccentLayout, getAnimationProgress, getAnimationStyle, getAvailableTemplates, getBlockDepth, getDefaultAnimation, getDefaultAnimationDuration, getDocStyleConfig, getLayers, getOverlayOpacity, getPersistentLayersFromTheme, getTemplateHint, getThemeFont, getTransitionClass, hasTemplate, imageWithCaption, isContainerTemplate, isDataFence, isShapeName, layoutBlock, leftFeature, lineStyleDasharray, lintTemplateParams, listBlock, mapBlock, markdownToDoc, markerPath, normalizeShapeKind, parseDataFence, parseYamlSubset, photoGrid, pullQuote, quoteBlock, readCustomTemplatesFromFrontmatter, readCustomThemesFromFrontmatter, replaceDataFence, resolveAudioMapping, resolveColorScheme, resolvePersistentLayers, resolveTemplateName, resolveThemeForDoc, rightFeature, scaleAnimationDuration, scoreTextSimilarity, sectionHeader, shapePath, shouldUseShadow, statHighlight, templateRegistry, themeWantsAmbientMotion, themedEntrance, themedFontSize, themedImageTreatment, themedScrim, themedSurfaceGradient, titleBlock, twoColumn, validateMarkdownDoc, validateMarkdownSource, videoPullQuote, videoWithCaption, wrapWithPersistentLayers, writeCustomTemplatesToFrontmatter, writeCustomThemesToFrontmatter } from '../story/index.js';
2
+ export { D as DEFAULT_THEME, g as getAvailableThemes, b as getThemeSummaries, r as resolveTheme } from '../themeLibrary-CehcJhzz.js';
3
+ export { z as DocBlock, G as FRONTMATTER_CUSTOM_TEMPLATES_KEY, H as FRONTMATTER_CUSTOM_THEMES_KEY, U as LayoutHints, a5 as PersistentLayerConfig, af as RenderStyle, at as TemplateBlock, au as TemplateContext, az as Theme, aA as ThemeColorPalette, aB as ThemeColorScheme, aE as ThemeStyle, aF as ThemeTypography, aJ as VIEWPORT_PRESETS, aN as ViewportConfig, aO as ViewportOrientation, aP as ViewportPreset, aU as createTemplateContext, a_ as getLayoutHints, b2 as getTwoColumnPositions, b3 as getViewport, b4 as getViewportOrientation, b6 as isTemplateBlock, bb as scaledFontSize } from '../Doc-BBVGq1_9.js';
4
+ import '../types-DlAZ7MW4.js';
5
+ import '../ContentContainer-BXUlIf18.js';
package/dist/doc/index.js CHANGED
@@ -1,21 +1,48 @@
1
1
  import {
2
+ BASE_INPUT_DESCRIPTORS,
3
+ CONTAINER_TEMPLATES,
2
4
  DEFAULT_LAYOUT,
5
+ SHAPE_NAMES,
3
6
  TEMPLATE_ALIASES,
7
+ TEMPLATE_INPUT_DESCRIPTORS,
8
+ TEMPLATE_METADATA,
4
9
  adjustY,
10
+ applyRenderStyleToLayers,
11
+ buildRegistry,
12
+ coerceTemplateParams,
5
13
  comparisonBar,
14
+ computeDiagramLayout,
15
+ computeDrawingLayout,
16
+ computeLayoutLayers,
6
17
  countBlocks,
7
18
  coverBlock,
8
19
  createAccentLayers,
20
+ cssFilterForTreatment,
9
21
  dataTable,
10
22
  dateEvent,
23
+ decodeLayersFromFrontmatter,
11
24
  definitionCard,
25
+ deriveTemplateInputs,
26
+ diagramBlock,
12
27
  docToMarkdown,
28
+ drawingBlock,
29
+ encodeLayersForFrontmatter,
13
30
  expandCoverBlock,
14
31
  expandDocBlocks,
15
32
  expandPersistentLayers,
16
33
  expandTemplateBlock,
34
+ extractBlockquoteText,
35
+ extractBodyPlainText,
36
+ extractFirstImage,
37
+ extractImages,
38
+ extractListItems,
39
+ extractTableData,
40
+ extractTableFromContents,
17
41
  factCard,
42
+ fallbackBlockLayers,
43
+ findFirstTable,
18
44
  flattenBlocks,
45
+ flattenRenderableBlocks,
19
46
  fullBleedQuote,
20
47
  getAccentLayout,
21
48
  getAnimationProgress,
@@ -27,21 +54,35 @@ import {
27
54
  getDocStyleConfig,
28
55
  getLayers,
29
56
  getOverlayOpacity,
57
+ getPersistentLayersFromTheme,
30
58
  getTemplateHint,
31
59
  getThemeFont,
32
60
  getTransitionClass,
33
61
  hasTemplate,
34
62
  imageWithCaption,
63
+ isContainerTemplate,
64
+ isDataFence,
65
+ isShapeName,
66
+ layoutBlock,
35
67
  leftFeature,
68
+ lintTemplateParams,
36
69
  listBlock,
37
70
  mapBlock,
38
71
  markdownToDoc,
72
+ normalizeShapeKind,
73
+ parseDataFence,
74
+ parseYamlSubset,
39
75
  photoGrid,
40
76
  pullQuote,
41
77
  quoteBlock,
78
+ readCustomTemplatesFromFrontmatter,
79
+ readCustomThemesFromFrontmatter,
80
+ replaceDataFence,
42
81
  resolveAudioMapping,
43
82
  resolveColorScheme,
83
+ resolvePersistentLayers,
44
84
  resolveTemplateName,
85
+ resolveThemeForDoc,
45
86
  rightFeature,
46
87
  scaleAnimationDuration,
47
88
  scoreTextSimilarity,
@@ -49,18 +90,42 @@ import {
49
90
  shouldUseShadow,
50
91
  statHighlight,
51
92
  templateRegistry,
93
+ themeWantsAmbientMotion,
94
+ themedEntrance,
52
95
  themedFontSize,
96
+ themedImageTreatment,
97
+ themedScrim,
98
+ themedSurfaceGradient,
53
99
  titleBlock,
54
100
  twoColumn,
101
+ validateMarkdownDoc,
102
+ validateMarkdownSource,
55
103
  videoPullQuote,
56
- videoWithCaption
57
- } from "../chunk-KSWJR66U.js";
104
+ videoWithCaption,
105
+ wrapWithPersistentLayers,
106
+ writeCustomTemplatesToFrontmatter,
107
+ writeCustomThemesToFrontmatter
108
+ } from "../chunk-LH7I6SH7.js";
109
+ import "../chunk-UY7KGQ5R.js";
110
+ import "../chunk-V543AI2F.js";
111
+ import "../chunk-FVTQSNR7.js";
112
+ import {
113
+ PATH_SHAPE_KINDS,
114
+ clipEndpoints,
115
+ clipPoint,
116
+ connectorPath,
117
+ lineStyleDasharray,
118
+ markerPath,
119
+ shapePath
120
+ } from "../chunk-RUDYOTA6.js";
58
121
  import {
59
122
  DEFAULT_THEME,
123
+ FRONTMATTER_CUSTOM_TEMPLATES_KEY,
124
+ FRONTMATTER_CUSTOM_THEMES_KEY,
60
125
  getAvailableThemes,
61
126
  getThemeSummaries,
62
127
  resolveTheme
63
- } from "../chunk-B4EEON3N.js";
128
+ } from "../chunk-KKNUBQ6Y.js";
64
129
  import {
65
130
  VIEWPORT_PRESETS,
66
131
  createTemplateContext,
@@ -70,31 +135,66 @@ import {
70
135
  getViewportOrientation,
71
136
  isTemplateBlock,
72
137
  scaledFontSize2 as scaledFontSize
73
- } from "../chunk-5WFRKURX.js";
74
- import "../chunk-H3AXU4MJ.js";
75
- import "../chunk-4X3JQXNM.js";
76
- import "../chunk-3E5F2XMR.js";
138
+ } from "../chunk-DLXZMS5K.js";
139
+ import "../chunk-TEEEILMP.js";
140
+ import "../chunk-HWVFJAAH.js";
141
+ import "../chunk-DBS6RIFL.js";
142
+ import "../chunk-QE7TWGVT.js";
143
+ import "../chunk-VWUFZ6ZG.js";
77
144
  export {
145
+ BASE_INPUT_DESCRIPTORS,
146
+ CONTAINER_TEMPLATES,
78
147
  DEFAULT_LAYOUT,
79
148
  DEFAULT_THEME,
149
+ FRONTMATTER_CUSTOM_TEMPLATES_KEY,
150
+ FRONTMATTER_CUSTOM_THEMES_KEY,
151
+ PATH_SHAPE_KINDS,
152
+ SHAPE_NAMES,
80
153
  TEMPLATE_ALIASES,
154
+ TEMPLATE_INPUT_DESCRIPTORS,
155
+ TEMPLATE_METADATA,
81
156
  VIEWPORT_PRESETS,
82
157
  adjustY,
158
+ applyRenderStyleToLayers,
159
+ buildRegistry,
160
+ clipEndpoints,
161
+ clipPoint,
162
+ coerceTemplateParams,
83
163
  comparisonBar,
164
+ computeDiagramLayout,
165
+ computeDrawingLayout,
166
+ computeLayoutLayers,
167
+ connectorPath,
84
168
  countBlocks,
85
169
  coverBlock,
86
170
  createAccentLayers,
87
171
  createTemplateContext,
172
+ cssFilterForTreatment,
88
173
  dataTable,
89
174
  dateEvent,
175
+ decodeLayersFromFrontmatter,
90
176
  definitionCard,
177
+ deriveTemplateInputs,
178
+ diagramBlock,
91
179
  docToMarkdown,
180
+ drawingBlock,
181
+ encodeLayersForFrontmatter,
92
182
  expandCoverBlock,
93
183
  expandDocBlocks,
94
184
  expandPersistentLayers,
95
185
  expandTemplateBlock,
186
+ extractBlockquoteText,
187
+ extractBodyPlainText,
188
+ extractFirstImage,
189
+ extractImages,
190
+ extractListItems,
191
+ extractTableData,
192
+ extractTableFromContents,
96
193
  factCard,
194
+ fallbackBlockLayers,
195
+ findFirstTable,
97
196
  flattenBlocks,
197
+ flattenRenderableBlocks,
98
198
  fullBleedQuote,
99
199
  getAccentLayout,
100
200
  getAnimationProgress,
@@ -108,6 +208,7 @@ export {
108
208
  getLayers,
109
209
  getLayoutHints,
110
210
  getOverlayOpacity,
211
+ getPersistentLayersFromTheme,
111
212
  getTemplateHint,
112
213
  getThemeFont,
113
214
  getThemeSummaries,
@@ -117,30 +218,56 @@ export {
117
218
  getViewportOrientation,
118
219
  hasTemplate,
119
220
  imageWithCaption,
221
+ isContainerTemplate,
222
+ isDataFence,
223
+ isShapeName,
120
224
  isTemplateBlock,
225
+ layoutBlock,
121
226
  leftFeature,
227
+ lineStyleDasharray,
228
+ lintTemplateParams,
122
229
  listBlock,
123
230
  mapBlock,
124
231
  markdownToDoc,
232
+ markerPath,
233
+ normalizeShapeKind,
234
+ parseDataFence,
235
+ parseYamlSubset,
125
236
  photoGrid,
126
237
  pullQuote,
127
238
  quoteBlock,
239
+ readCustomTemplatesFromFrontmatter,
240
+ readCustomThemesFromFrontmatter,
241
+ replaceDataFence,
128
242
  resolveAudioMapping,
129
243
  resolveColorScheme,
244
+ resolvePersistentLayers,
130
245
  resolveTemplateName,
131
246
  resolveTheme,
247
+ resolveThemeForDoc,
132
248
  rightFeature,
133
249
  scaleAnimationDuration,
134
250
  scaledFontSize,
135
251
  scoreTextSimilarity,
136
252
  sectionHeader,
253
+ shapePath,
137
254
  shouldUseShadow,
138
255
  statHighlight,
139
256
  templateRegistry,
257
+ themeWantsAmbientMotion,
258
+ themedEntrance,
140
259
  themedFontSize,
260
+ themedImageTreatment,
261
+ themedScrim,
262
+ themedSurfaceGradient,
141
263
  titleBlock,
142
264
  twoColumn,
265
+ validateMarkdownDoc,
266
+ validateMarkdownSource,
143
267
  videoPullQuote,
144
- videoWithCaption
268
+ videoWithCaption,
269
+ wrapWithPersistentLayers,
270
+ writeCustomTemplatesToFrontmatter,
271
+ writeCustomThemesToFrontmatter
145
272
  };
146
273
  //# sourceMappingURL=index.js.map
@@ -1,7 +1,7 @@
1
1
  import { E as ExtractedElement } from '../contentExtractor-BNfVJV2U.js';
2
2
  export { C as ComparisonData, D as DateData, a as DefinitionData, b as ExtractionOptions, c as ExtractionResult, d as ExtractionType, F as FactData, I as ImpactLineData, L as ListData, Q as QuoteData, S as StatData, e as extractContent, s as stripMarkdown } from '../contentExtractor-BNfVJV2U.js';
3
- import { j as ColorScheme, A as AccentImage, a9 as TemplateBlock, s as Doc, a as AccentPosition } from '../Doc-BeSeH2iX.js';
4
- import '../types-BdZkdJ3z.js';
3
+ import { m as ColorScheme, A as AccentImage, at as TemplateBlock } from '../Doc-BBVGq1_9.js';
4
+ import '../types-DlAZ7MW4.js';
5
5
 
6
6
  /**
7
7
  * Template Mapper
@@ -25,75 +25,4 @@ interface MapOptions {
25
25
  */
26
26
  declare function mapElementToBlock(element: ExtractedElement, options: MapOptions): TemplateBlock;
27
27
 
28
- /**
29
- * Slideshow Generator
30
- *
31
- * Turns plain text + optional images into a complete squisq `Doc`.
32
- *
33
- * Pipeline:
34
- * 1. Strip markdown (if needed)
35
- * 2. Extract compelling content (stats, dates, quotes, …)
36
- * 3. Estimate narration timing
37
- * 4. Map extractions → template blocks
38
- * 5. Interleave image slides
39
- * 6. Return a ready-to-render `Doc`
40
- */
41
-
42
- /** A Doc variant whose blocks are TemplateBlocks (no startTime required). */
43
- type SlideshowDoc = Omit<Doc, 'blocks'> & {
44
- blocks: TemplateBlock[];
45
- };
46
- /** Image metadata passed to the generator. */
47
- interface SlideshowImage {
48
- /** Image source path or URL. */
49
- src: string;
50
- /** Alt text for accessibility. */
51
- alt?: string;
52
- /** Photo credit. */
53
- credit?: string;
54
- /** License identifier. */
55
- license?: string;
56
- }
57
- /** Options for `generateSlideshow`. */
58
- interface SlideshowOptions {
59
- /** Deterministic seed (default: hash of the text). */
60
- seed?: number;
61
- /** Title slide text. Omit to skip the title slide. */
62
- title?: string;
63
- /** Subtitle for the title slide. */
64
- subtitle?: string;
65
- /** Explicit total duration in seconds. When omitted, estimated from text. */
66
- duration?: number;
67
- /** Target number of content slides (default: 5–8, auto-scaled). */
68
- targetSlides?: number;
69
- /** Color schemes to rotate (default: ['blue','green','purple','orange','red']). */
70
- colorSchemes?: ColorScheme[];
71
- /** Ambient motions to rotate on images. */
72
- ambientMotions?: Array<'zoomIn' | 'zoomOut' | 'panLeft' | 'panRight'>;
73
- /** Accent positions to rotate on text slides. */
74
- accentPositions?: AccentPosition[];
75
- /** Minimum slide duration in seconds (default: 4). */
76
- minSlideDuration?: number;
77
- /** Maximum slide duration in seconds (default: 15). */
78
- maxSlideDuration?: number;
79
- /** Theme identifier to embed in the Doc. */
80
- themeId?: string;
81
- /** Whether the input text is markdown (default: false). */
82
- isMarkdown?: boolean;
83
- /** Minimum extraction confidence (default: 0.3). */
84
- minConfidence?: number;
85
- }
86
- /**
87
- * Generate a slideshow `Doc` from text and optional images.
88
- *
89
- * ```ts
90
- * const doc = generateSlideshow(articleText, myImages, {
91
- * title: 'Mount Rainier',
92
- * themeId: 'documentary',
93
- * });
94
- * // doc is a complete Doc ready for DocPlayer / LinearDocView
95
- * ```
96
- */
97
- declare function generateSlideshow(text: string, images?: SlideshowImage[], options?: SlideshowOptions): SlideshowDoc;
98
-
99
- export { ExtractedElement, type MapOptions, type SlideshowDoc, type SlideshowImage, type SlideshowOptions, generateSlideshow, mapElementToBlock };
28
+ export { ExtractedElement, type MapOptions, mapElementToBlock };
@@ -1,16 +1,11 @@
1
- import {
2
- generateSlideshow
3
- } from "../chunk-MYIH7FWD.js";
1
+ import "../chunk-ET53IIEP.js";
4
2
  import {
5
3
  extractContent,
6
4
  mapElementToBlock,
7
5
  stripMarkdown
8
- } from "../chunk-FR2RBTKO.js";
9
- import "../chunk-FVTQSNR7.js";
10
- import "../chunk-3E5F2XMR.js";
6
+ } from "../chunk-TQWLI6S2.js";
11
7
  export {
12
8
  extractContent,
13
- generateSlideshow,
14
9
  mapElementToBlock,
15
10
  stripMarkdown
16
11
  };
@@ -1,28 +1,5 @@
1
- /**
2
- * iconData.ts GENERATED FILE
3
- *
4
- * FontAwesome Free icon catalog. Run `node scripts/generate-icon-data.mjs`
5
- * to regenerate after bumping @fortawesome/fontawesome-free.
6
- *
7
- * Entry count: 2141
8
- * Generated: 2026-05-14T03:38:13.863Z
9
- */
10
- type IconFamily = 'brands' | 'solid' | 'regular';
11
- interface IconEntry {
12
- /** Icon name without the `fa-` prefix (e.g. `github`). */
13
- name: string;
14
- /** Family bucket — drives the `fa-brands` / `fa-solid` / `fa-regular` class. */
15
- family: IconFamily;
16
- /** Human display label (used by the picker tooltip). */
17
- label: string;
18
- /** Lowercase, space-joined search keywords. */
19
- keywords: string;
20
- /** FontAwesome Private-Use-Area codepoint as a hex string (e.g. `'f09b'`).
21
- * Use `String.fromCodePoint(parseInt(unicode, 16))` to get the rendered
22
- * glyph when the FA font is loaded. */
23
- unicode: string;
24
- }
25
- declare const ICONS: IconEntry[];
1
+ import { a as IconEntry } from '../inlineIconMarker-CiE8Hlvz.js';
2
+ export { I as ICONS, b as IconFamily, c as IconTextRun, h as hasIconMarker, i as iconClass, d as iconMarker, s as splitIconMarkers, e as stripIconMarkers } from '../inlineIconMarker-CiE8Hlvz.js';
26
3
 
27
4
  /**
28
5
  * Icon Resolver
@@ -84,4 +61,4 @@ interface IconSuggestion {
84
61
  */
85
62
  declare function suggestIcons(query: string, limit?: number): IconSuggestion[];
86
63
 
87
- export { ICONS, type IconEntry, type IconFamily, type IconSuggestion, canonicalIconToken, iconGlyph, looksLikeIconToken, resolveIcon, suggestIcons };
64
+ export { IconEntry, type IconSuggestion, canonicalIconToken, iconGlyph, looksLikeIconToken, resolveIcon, suggestIcons };
@@ -1,3 +1,11 @@
1
+ import "../chunk-PGGGKYWG.js";
2
+ import {
3
+ hasIconMarker,
4
+ iconClass,
5
+ iconMarker,
6
+ splitIconMarkers,
7
+ stripIconMarkers
8
+ } from "../chunk-2UQOHYE2.js";
1
9
  import {
2
10
  ICONS,
3
11
  canonicalIconToken,
@@ -9,9 +17,14 @@ import {
9
17
  export {
10
18
  ICONS,
11
19
  canonicalIconToken,
20
+ hasIconMarker,
21
+ iconClass,
12
22
  iconGlyph,
23
+ iconMarker,
13
24
  looksLikeIconToken,
14
25
  resolveIcon,
26
+ splitIconMarkers,
27
+ stripIconMarkers,
15
28
  suggestIcons
16
29
  };
17
30
  //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ export { c as IconTextRun, h as hasIconMarker, i as iconClass, d as iconMarker, s as splitIconMarkers, e as stripIconMarkers } from '../inlineIconMarker-CiE8Hlvz.js';
@@ -0,0 +1,15 @@
1
+ import {
2
+ hasIconMarker,
3
+ iconClass,
4
+ iconMarker,
5
+ splitIconMarkers,
6
+ stripIconMarkers
7
+ } from "../chunk-2UQOHYE2.js";
8
+ export {
9
+ hasIconMarker,
10
+ iconClass,
11
+ iconMarker,
12
+ splitIconMarkers,
13
+ stripIconMarkers
14
+ };
15
+ //# sourceMappingURL=inlineIconMarker.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
@@ -1,9 +1,9 @@
1
- import { a as ImageEditDoc, b as ImageEditLayer } from '../ImageEditDoc-BTIvw0oq.js';
2
- export { E as EditorLayerMeta, I as ImageEditCanvas, c as ImageEditLayerKind, d as ImageEditMeta } from '../ImageEditDoc-BTIvw0oq.js';
3
- import { C as ContentContainer } from '../ContentContainer-DNx460y8.js';
1
+ import { a as ImageEditDoc, b as ImageEditLayer } from '../ImageEditDoc-FuyGtt6o.js';
2
+ export { E as EditorLayerMeta, I as ImageEditCanvas, c as ImageEditLayerKind, d as ImageEditMeta } from '../ImageEditDoc-FuyGtt6o.js';
3
+ import { C as ContentContainer } from '../ContentContainer-BXUlIf18.js';
4
4
  import { V as Version, C as CoalesceOptions, P as PrunePolicy } from '../types-D98li1gV.js';
5
- import '../Doc-BeSeH2iX.js';
6
- import '../types-BdZkdJ3z.js';
5
+ import '../Doc-BBVGq1_9.js';
6
+ import '../types-DlAZ7MW4.js';
7
7
 
8
8
  /**
9
9
  * Pure helpers for constructing and mutating {@link ImageEditDoc} values.
@@ -212,5 +212,11 @@ interface ImageEditExportOptions {
212
212
  * layers are read from `container` and inlined as data URLs.
213
213
  */
214
214
  declare function exportImageEditDoc(doc: ImageEditDoc, container: ContentContainer, options?: ImageEditExportOptions): Promise<Blob>;
215
+ /**
216
+ * Build the standalone SVG string that mirrors the canvas + layers. Exposed
217
+ * for tests (the raster step needs a browser); shape/text/path layers don't
218
+ * touch `container`, only image layers do (to inline asset bytes).
219
+ */
220
+ declare function buildSvgString(doc: ImageEditDoc, container: ContentContainer): Promise<string>;
215
221
 
216
- export { IMAGE_EDIT_ASSETS_PREFIX, IMAGE_EDIT_DEFAULT_BASENAME, IMAGE_EDIT_STATE_FILENAME, IMAGE_EDIT_VERSIONS_PREFIX, ImageEditDoc, type ImageEditExportFormat, type ImageEditExportOptions, ImageEditLayer, ImageEditVersionManager, type ImageEditVersionManagerOptions, type RevertImageEditOptions, type SaveImageEditVersionOptions, type SaveImageEditVersionResult, addLayer, buildImageEditVersionPath, coalesceImageEditVersions, createEmptyImageEditDoc, exportImageEditDoc, listImageEditVersions, parseImageEditVersionPath, pruneImageEditVersions, readImageEditDoc, readImageEditVersion, readImageEditVersionText, removeLayer, reorderLayer, revertToImageEditVersion, saveImageEditVersion, setCanvas, touch, updateLayer, writeImageEditDoc };
222
+ export { IMAGE_EDIT_ASSETS_PREFIX, IMAGE_EDIT_DEFAULT_BASENAME, IMAGE_EDIT_STATE_FILENAME, IMAGE_EDIT_VERSIONS_PREFIX, ImageEditDoc, type ImageEditExportFormat, type ImageEditExportOptions, ImageEditLayer, ImageEditVersionManager, type ImageEditVersionManagerOptions, type RevertImageEditOptions, type SaveImageEditVersionOptions, type SaveImageEditVersionResult, addLayer, buildImageEditVersionPath, buildSvgString, coalesceImageEditVersions, createEmptyImageEditDoc, exportImageEditDoc, listImageEditVersions, parseImageEditVersionPath, pruneImageEditVersions, readImageEditDoc, readImageEditVersion, readImageEditVersionText, removeLayer, reorderLayer, revertToImageEditVersion, saveImageEditVersion, setCanvas, touch, updateLayer, writeImageEditDoc };