@bendyline/squisq-formats 2.8.8 → 2.8.10

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 (40) hide show
  1. package/README.md +15 -13
  2. package/dist/chunk-4LIFEQKX.js +358 -0
  3. package/dist/{chunk-UXEANVBI.js → chunk-5HUHSLGA.js} +1 -84
  4. package/dist/{chunk-BXWNU4T5.js → chunk-DTJQK7U7.js} +43 -0
  5. package/dist/{chunk-GDUZJ75A.js → chunk-MJTPWLVV.js} +59 -21
  6. package/dist/{chunk-6ALSRHD7.js → chunk-NCDY3EOL.js} +66 -9
  7. package/dist/{chunk-TMOXUUTG.js → chunk-NQNIFIDP.js} +6 -4
  8. package/dist/{chunk-LK4KCVN6.js → chunk-QPJHAMOW.js} +70 -28
  9. package/dist/{chunk-QU4EVC62.js → chunk-S5VXWSRR.js} +1 -1
  10. package/dist/{chunk-KROH7FNT.js → chunk-VKLJUHC2.js} +6 -4
  11. package/dist/chunk-YRFBWYAD.js +128 -0
  12. package/dist/{chunk-SVYTUYZM.js → chunk-ZJERBWTB.js} +6 -4
  13. package/dist/data/index.d.ts +27 -5
  14. package/dist/data/index.js +6 -2
  15. package/dist/diagrams/index.d.ts +19 -8
  16. package/dist/diagrams/index.js +7 -242
  17. package/dist/docx/index.d.ts +1 -1
  18. package/dist/docx/index.js +3 -3
  19. package/dist/epub/index.d.ts +1 -1
  20. package/dist/epub/index.js +3 -3
  21. package/dist/fontAwesome-C6FRDYTY.js +14 -0
  22. package/dist/html/icons.d.ts +29 -0
  23. package/dist/html/icons.js +62 -0
  24. package/dist/html/index.d.ts +3 -130
  25. package/dist/html/index.js +2 -2
  26. package/dist/html/prepare.d.ts +97 -0
  27. package/dist/html/prepare.js +85 -0
  28. package/dist/index.d.ts +2 -0
  29. package/dist/index.js +16 -16
  30. package/dist/{materialize-G4WC5UG3.js → materialize-ICXNO4S4.js} +4 -1
  31. package/dist/outside-in/index.d.ts +12 -0
  32. package/dist/outside-in/index.js +3 -3
  33. package/dist/pdf/index.d.ts +1 -1
  34. package/dist/pdf/index.js +3 -3
  35. package/dist/plainHtml-B6IXcSet.d.ts +148 -0
  36. package/dist/pptx/index.js +2 -2
  37. package/dist/readers-3P5A6A2A.js +17 -0
  38. package/dist/registry/index.js +1 -1
  39. package/package.json +12 -2
  40. /package/dist/{chunk-LBXBRFBZ.js → chunk-DDMPZZJF.js} +0 -0
@@ -251,7 +251,7 @@ async function collectContainerAssets(container, pattern, signal, flattenBasenam
251
251
  async function collectContainerImages(container, signal) {
252
252
  return collectContainerAssets(container, IMAGE_ASSET_RE, signal);
253
253
  }
254
- function dataReferenceWarnings(doc, formatLabel) {
254
+ function dataReferenceSources(doc) {
255
255
  const srcs = /* @__PURE__ */ new Set();
256
256
  const visit = (blocks) => {
257
257
  for (const block of blocks) {
@@ -261,10 +261,38 @@ function dataReferenceWarnings(doc, formatLabel) {
261
261
  }
262
262
  };
263
263
  visit(doc.blocks);
264
- return [...srcs].map(
265
- (src) => `${formatLabel} cannot embed the data sidecar "${src}"; the export carries its bounded preview and the link will not resolve outside the source container.`
264
+ return srcs;
265
+ }
266
+ function dataReferenceWarnings(doc, formatLabel) {
267
+ return [...dataReferenceSources(doc)].map(
268
+ (src) => `${formatLabel} cannot embed the data sidecar "${src}"; the export shows its first rows and the link will not resolve outside the source container.`
269
+ );
270
+ }
271
+ async function markdownWithDataPreview(input, warnings) {
272
+ const markdownDoc = await markdownOf(input);
273
+ if (dataReferenceSources(input.doc).size === 0) return markdownDoc;
274
+ const { materializeDataReferences, DATA_PREVIEW_ROWS } = await import("./materialize-ICXNO4S4.js");
275
+ return materializeDataReferences(
276
+ markdownDoc,
277
+ input.container,
278
+ (message) => warnings.push(message),
279
+ { maxRows: DATA_PREVIEW_ROWS }
266
280
  );
267
281
  }
282
+ async function docWithDataPreview(input, warnings) {
283
+ if (dataReferenceSources(input.doc).size === 0) return input.doc;
284
+ const [{ resolveDataReferences }, { defaultDataReaders }] = await Promise.all([
285
+ import("@bendyline/squisq/doc"),
286
+ import("./readers-3P5A6A2A.js")
287
+ ]);
288
+ const { doc, diagnostics } = await resolveDataReferences(input.doc, input.container, {
289
+ readers: defaultDataReaders()
290
+ });
291
+ for (const diagnostic of diagnostics) {
292
+ if (diagnostic.severity !== "info") warnings.push(diagnostic.message);
293
+ }
294
+ return doc;
295
+ }
268
296
  async function collectContainerDocxImages(container, signal) {
269
297
  const images = await collectContainerImages(container, signal);
270
298
  const { extToMime } = await import("./images-PQCYRJJ5.js");
@@ -351,7 +379,8 @@ function defaultFormats() {
351
379
  async exportDoc(input, options) {
352
380
  const { markdownDocToDocx } = await import("./docx/index.js");
353
381
  const raw = optionsFor(options, "docx");
354
- const markdownDoc = await markdownOf(input);
382
+ const warnings = [];
383
+ const markdownDoc = await markdownWithDataPreview(input, warnings);
355
384
  const containerImages = await collectContainerDocxImages(input.container, options.signal);
356
385
  const images = new Map([...containerImages, ...raw.images ?? /* @__PURE__ */ new Map()]);
357
386
  const buf = await markdownDocToDocx(markdownDoc, {
@@ -361,7 +390,10 @@ function defaultFormats() {
361
390
  themeRegistry: options.themeRegistry ?? raw.themeRegistry,
362
391
  images
363
392
  });
364
- return ok(await toBytes(buf), MIME.docx, markdownFidelityWarnings(markdownDoc, "docx"));
393
+ return ok(await toBytes(buf), MIME.docx, [
394
+ ...markdownFidelityWarnings(markdownDoc, "docx"),
395
+ ...warnings
396
+ ]);
365
397
  }
366
398
  };
367
399
  const pdf = {
@@ -381,8 +413,8 @@ function defaultFormats() {
381
413
  async exportDoc(input, options) {
382
414
  const { markdownDocToPdf } = await import("./pdf/index.js");
383
415
  const raw = optionsFor(options, "pdf");
384
- const markdownDoc = await markdownOf(input);
385
416
  const pdfWarnings = [];
417
+ const markdownDoc = await markdownWithDataPreview(input, pdfWarnings);
386
418
  const containerImages = await collectContainerDocxImages(input.container, options.signal);
387
419
  const images = new Map([...containerImages, ...raw.images ?? /* @__PURE__ */ new Map()]);
388
420
  const buf = await markdownDocToPdf(markdownDoc, {
@@ -420,7 +452,8 @@ function defaultFormats() {
420
452
  const containerImages = await collectContainerImages(input.container, options.signal);
421
453
  const images = new Map([...containerImages, ...raw.images ?? /* @__PURE__ */ new Map()]);
422
454
  const pptxWarnings = [];
423
- const buf = await docToPptx(input.doc, {
455
+ const doc = await docWithDataPreview(input, pptxWarnings);
456
+ const buf = await docToPptx(doc, {
424
457
  ...raw,
425
458
  ...options.title !== void 0 ? { title: options.title } : {},
426
459
  themeId: resolveThemeId(input, options),
@@ -456,7 +489,7 @@ function defaultFormats() {
456
489
  },
457
490
  async exportDoc(input, options) {
458
491
  const { markdownDocToXlsx } = await import("./xlsx/index.js");
459
- const { materializeDataReferences } = await import("./materialize-G4WC5UG3.js");
492
+ const { materializeDataReferences } = await import("./materialize-ICXNO4S4.js");
460
493
  const warnings = [];
461
494
  const markdownDoc = await materializeDataReferences(
462
495
  await markdownOf(input),
@@ -495,7 +528,7 @@ function defaultFormats() {
495
528
  },
496
529
  async exportDoc(input, options) {
497
530
  const { markdownDocToCsv } = await import("./csv/index.js");
498
- const { materializeDataReferences } = await import("./materialize-G4WC5UG3.js");
531
+ const { materializeDataReferences } = await import("./materialize-ICXNO4S4.js");
499
532
  const warnings = [];
500
533
  const markdownDoc = await materializeDataReferences(
501
534
  await markdownOf(input),
@@ -528,9 +561,11 @@ function defaultFormats() {
528
561
  // so there is nothing to extract into a container this wave.
529
562
  async exportDoc(input, options) {
530
563
  const raw = optionsFor(options, "html");
564
+ const warnings = [];
565
+ const doc = await docWithDataPreview(input, warnings);
531
566
  if (raw.playerScriptPath) {
532
567
  const { generateExternalHtml } = await import("./html/index.js");
533
- const htmlText2 = generateExternalHtml(input.doc, {
568
+ const htmlText2 = generateExternalHtml(doc, {
534
569
  ...raw,
535
570
  playerScriptPath: raw.playerScriptPath,
536
571
  title: options.title ?? input.baseName,
@@ -538,7 +573,7 @@ function defaultFormats() {
538
573
  themeId: resolveThemeId(input, options),
539
574
  themeRegistry: options.themeRegistry ?? raw.themeRegistry
540
575
  });
541
- return ok(new TextEncoder().encode(htmlText2), MIME.html);
576
+ return ok(new TextEncoder().encode(htmlText2), MIME.html, warnings);
542
577
  }
543
578
  const playerScript = await requirePlayerScript(options, "html");
544
579
  const { docToHtml } = await import("./html/index.js");
@@ -550,7 +585,7 @@ function defaultFormats() {
550
585
  false
551
586
  );
552
587
  const images = new Map([...containerImages, ...media, ...raw.images ?? /* @__PURE__ */ new Map()]);
553
- const htmlText = docToHtml(input.doc, {
588
+ const htmlText = docToHtml(doc, {
554
589
  ...raw,
555
590
  playerScript,
556
591
  images,
@@ -560,11 +595,10 @@ function defaultFormats() {
560
595
  themeId: resolveThemeId(input, options),
561
596
  themeRegistry: options.themeRegistry ?? raw.themeRegistry
562
597
  });
563
- return ok(
564
- new TextEncoder().encode(htmlText),
565
- MIME.html,
566
- dataReferenceWarnings(input.doc, "Single-file HTML")
567
- );
598
+ return ok(new TextEncoder().encode(htmlText), MIME.html, [
599
+ ...dataReferenceWarnings(input.doc, "Single-file HTML"),
600
+ ...warnings
601
+ ]);
568
602
  }
569
603
  };
570
604
  const htmlzip = {
@@ -596,7 +630,9 @@ function defaultFormats() {
596
630
  ...media,
597
631
  ...raw.images ?? /* @__PURE__ */ new Map()
598
632
  ]);
599
- const blob = await docToHtmlZip(input.doc, {
633
+ const warnings = [];
634
+ const doc = await docWithDataPreview(input, warnings);
635
+ const blob = await docToHtmlZip(doc, {
600
636
  ...raw,
601
637
  playerScript,
602
638
  images,
@@ -606,7 +642,7 @@ function defaultFormats() {
606
642
  themeId: resolveThemeId(input, options),
607
643
  themeRegistry: options.themeRegistry ?? raw.themeRegistry
608
644
  });
609
- return ok(await toBytes(blob), MIME.zip);
645
+ return ok(await toBytes(blob), MIME.zip, warnings);
610
646
  }
611
647
  };
612
648
  const epub = {
@@ -618,7 +654,8 @@ function defaultFormats() {
618
654
  async exportDoc(input, options) {
619
655
  const { markdownDocToEpub } = await import("./epub/index.js");
620
656
  const raw = optionsFor(options, "epub");
621
- const markdownDoc = await markdownOf(input);
657
+ const warnings = [];
658
+ const markdownDoc = await markdownWithDataPreview(input, warnings);
622
659
  const containerImages = await collectContainerImages(input.container, options.signal);
623
660
  const images = new Map([...containerImages, ...raw.images ?? /* @__PURE__ */ new Map()]);
624
661
  const buf = await markdownDocToEpub(markdownDoc, {
@@ -630,7 +667,8 @@ function defaultFormats() {
630
667
  });
631
668
  return ok(await toBytes(buf), MIME.epub, [
632
669
  ...markdownFidelityWarnings(markdownDoc, "epub"),
633
- ...dataReferenceWarnings(input.doc, "EPUB")
670
+ ...dataReferenceWarnings(input.doc, "EPUB"),
671
+ ...warnings
634
672
  ]);
635
673
  }
636
674
  };
@@ -1,15 +1,15 @@
1
+ import {
2
+ OUTSIDE_IN_FORMAT_IDS,
3
+ OUTSIDE_IN_OUTPUT_TEMPLATE_KEY,
4
+ resolveOutsideInLayout
5
+ } from "./chunk-WJTC5OD6.js";
1
6
  import {
2
7
  convert,
3
8
  defaultRegistry
4
- } from "./chunk-GDUZJ75A.js";
9
+ } from "./chunk-MJTPWLVV.js";
5
10
  import {
6
11
  ConversionError
7
12
  } from "./chunk-KXOZMWBS.js";
8
- import {
9
- OUTSIDE_IN_FORMAT_IDS,
10
- OUTSIDE_IN_OUTPUT_TEMPLATE_KEY,
11
- resolveOutsideInLayout
12
- } from "./chunk-WJTC5OD6.js";
13
13
 
14
14
  // src/outside-in/index.ts
15
15
  import {
@@ -188,7 +188,9 @@ async function renderOutsideInDocument(source, options = {}) {
188
188
  { format: "html" }
189
189
  );
190
190
  }
191
- const formatOptions = { ...options.formatOptions ?? {} };
191
+ const formatOptions = {
192
+ ...options.formatOptions ?? {}
193
+ };
192
194
  if (layout.format === "html" && options.html) {
193
195
  formatOptions.html = {
194
196
  ...formatOptions.html ?? {},
@@ -196,10 +198,19 @@ async function renderOutsideInDocument(source, options = {}) {
196
198
  basePath: options.html.basePath ?? layout.companionName
197
199
  };
198
200
  }
199
- return convert(
201
+ const warnings = [];
202
+ const markdown = await prepareOutsideInMarkdown(
203
+ source.markdown,
204
+ layout.format,
205
+ source.container,
206
+ options,
207
+ formatOptions,
208
+ warnings
209
+ );
210
+ const result = await convert(
200
211
  {
201
212
  kind: "markdown",
202
- markdown: source.markdown,
213
+ markdown,
203
214
  container: source.container,
204
215
  baseName: layout.stem
205
216
  },
@@ -210,6 +221,52 @@ async function renderOutsideInDocument(source, options = {}) {
210
221
  formatOptions
211
222
  }
212
223
  );
224
+ return warnings.length > 0 ? { ...result, warnings: [...warnings, ...result.warnings] } : result;
225
+ }
226
+ async function prepareOutsideInMarkdown(markdown, format, container, options, formatOptions, warnings) {
227
+ const render = options.renderDiagram;
228
+ if (!render || format !== "docx" && format !== "pdf" && format !== "pptx") return markdown;
229
+ const onWarning = (message) => {
230
+ warnings.push(message);
231
+ };
232
+ let doc = typeof markdown === "string" ? parseMarkdown(markdown) : markdown;
233
+ const { rasterizeDiagrams, EXPORT_DIAGRAM_KINDS } = await import("./diagrams/index.js");
234
+ const signal = options.signal;
235
+ if (format === "pptx") {
236
+ const { mermaid, svg } = await rasterizeDiagrams(doc, render, {
237
+ kinds: ["mermaid", "svg"],
238
+ signal,
239
+ onWarning
240
+ });
241
+ formatOptions.pptx = {
242
+ ...formatOptions.pptx,
243
+ diagramImages: new Map([...mermaid, ...svg, ...formatOptions.pptx?.diagramImages ?? []])
244
+ };
245
+ return doc;
246
+ }
247
+ const { materializeDataReferences, DATA_PREVIEW_ROWS } = await import("./materialize-ICXNO4S4.js");
248
+ doc = await materializeDataReferences(doc, container, onWarning, {
249
+ maxRows: DATA_PREVIEW_ROWS
250
+ });
251
+ const pictured = await rasterizeDiagrams(doc, render, {
252
+ kinds: format === "pdf" ? EXPORT_DIAGRAM_KINDS : void 0,
253
+ signal,
254
+ onWarning
255
+ });
256
+ if (format === "docx") {
257
+ const { svg } = await rasterizeDiagrams(doc, render, { kinds: ["svg"], signal, onWarning });
258
+ formatOptions.docx = {
259
+ ...formatOptions.docx,
260
+ images: new Map([...formatOptions.docx?.images ?? [], ...pictured.images]),
261
+ svgIslandFallbacks: new Map([...svg, ...formatOptions.docx?.svgIslandFallbacks ?? []])
262
+ };
263
+ } else {
264
+ formatOptions.pdf = {
265
+ ...formatOptions.pdf,
266
+ images: new Map([...formatOptions.pdf?.images ?? [], ...pictured.images])
267
+ };
268
+ }
269
+ return pictured.markdownDoc;
213
270
  }
214
271
 
215
272
  export {
@@ -2,8 +2,9 @@ import {
2
2
  upngEncoder
3
3
  } from "./chunk-34KECNZO.js";
4
4
  import {
5
- FootnoteIndex
6
- } from "./chunk-BXWNU4T5.js";
5
+ FootnoteIndex,
6
+ withoutBlockTags
7
+ } from "./chunk-DTJQK7U7.js";
7
8
  import {
8
9
  buildContainer
9
10
  } from "./chunk-IIQYS2YH.js";
@@ -11,7 +12,7 @@ import {
11
12
  collectInlineIconFamilies,
12
13
  fontAwesomeFaces,
13
14
  fontAwesomeGlyph
14
- } from "./chunk-LBXBRFBZ.js";
15
+ } from "./chunk-DDMPZZJF.js";
15
16
  import {
16
17
  authoredImageSize,
17
18
  fitWithin,
@@ -499,8 +500,9 @@ var IMPORT_TABLE_MIN_ROWS = 2;
499
500
  var IMPORT_URL_PATTERN = /https?:\/\/[^\s)>\]]+/g;
500
501
 
501
502
  // src/pdf/export.ts
502
- async function markdownDocToPdf(doc, options = {}) {
503
+ async function markdownDocToPdf(source, options = {}) {
503
504
  options.signal?.throwIfAborted();
505
+ const doc = withoutBlockTags(source);
504
506
  const pdfDoc = await PDFDocument.create();
505
507
  if (options.title) pdfDoc.setTitle(options.title);
506
508
  if (options.author) pdfDoc.setAuthor(options.author);
@@ -8,8 +8,9 @@ import {
8
8
  } from "./chunk-VCZUXQUL.js";
9
9
  import {
10
10
  FootnoteIndex,
11
- footnoteIds
12
- } from "./chunk-BXWNU4T5.js";
11
+ footnoteIds,
12
+ withoutBlockTags
13
+ } from "./chunk-DTJQK7U7.js";
13
14
  import {
14
15
  imageSizeAttributes
15
16
  } from "./chunk-JZR32FON.js";
@@ -294,7 +295,9 @@ function escapeHtml(str) {
294
295
  import {
295
296
  findBlockVideoEmbed,
296
297
  isSvgIslandLang,
298
+ mediaKindForUrl,
297
299
  parseSvgIsland,
300
+ parseTimeSeconds,
298
301
  sanitizeHtmlNodes,
299
302
  svgIslandDataUrl,
300
303
  svgSourceFromDataUrl,
@@ -304,7 +307,11 @@ import {
304
307
  readFrontmatterThemeId as readFrontmatterThemeId2
305
308
  } from "@bendyline/squisq/markdown";
306
309
  import { resolveFontFamily, buildGoogleFontsUrl } from "@bendyline/squisq/schemas";
307
- import { resolveThemeForDoc } from "@bendyline/squisq/doc";
310
+ import {
311
+ isMediaAnnotationName,
312
+ parseStandaloneAnnotation,
313
+ resolveThemeForDoc
314
+ } from "@bendyline/squisq/doc";
308
315
  function markdownDocToPlainHtml(doc, options = {}) {
309
316
  const { title, body, fontsLink, iconsLink, themedCss, featureCss } = renderPlainHtml(
310
317
  doc,
@@ -347,7 +354,8 @@ function renderPlainHtml(doc, options) {
347
354
  iconsCss,
348
355
  htmlPolicy = "sanitize",
349
356
  externalResources = "deny",
350
- videoEmbeds
357
+ videoEmbeds,
358
+ icons
351
359
  } = options;
352
360
  const resolveId = themeId ?? readFrontmatterThemeId2(doc.frontmatter);
353
361
  const theme = options.theme ?? (resolveId ? resolveThemeForDoc(doc, resolveId, options.themeRegistry) : void 0);
@@ -358,13 +366,14 @@ function renderPlainHtml(doc, options) {
358
366
  htmlPolicy,
359
367
  footnotes,
360
368
  videoEmbeds,
369
+ icons,
361
370
  textColor: theme?.colors.text
362
371
  };
363
- const body = renderTopLevel(doc.children, ctx) + renderFootnotesSection(footnotes, ctx);
372
+ const blocks = withoutBlockTags(doc, { keepMedia: true }).children;
373
+ const body = renderTopLevel(blocks, ctx) + renderFootnotesSection(footnotes, ctx);
364
374
  const fontsLink = theme && externalResources === "allow" ? renderFontsLink(theme) : "";
365
- const usesIcons = docUsesIcons(doc);
366
375
  let iconsLink = "";
367
- if (usesIcons) {
376
+ if (ctx.usesFontIcons) {
368
377
  iconsLink = iconsCss ? `<style data-fa-inline>
369
378
  ${escapeStyleText(iconsCss)}
370
379
  </style>
@@ -375,13 +384,17 @@ ${escapeStyleText(iconsCss)}
375
384
  ${VIDEO_EMBED_CSS}` : "";
376
385
  const svgCss = docUsesSvgIslands(doc) ? `
377
386
  ${SVG_ISLAND_CSS}` : "";
387
+ const mediaCss = ctx.usesMediaPlayers ? `
388
+ ${MEDIA_PLAYER_CSS}` : "";
389
+ const iconCss = ctx.usesSvgIcons ? `
390
+ ${SVG_ICON_CSS}` : "";
378
391
  return {
379
392
  title,
380
393
  body,
381
394
  fontsLink,
382
395
  iconsLink,
383
396
  themedCss,
384
- featureCss: `${FEATURE_CSS}${videoCss}${svgCss}`
397
+ featureCss: `${FEATURE_CSS}${videoCss}${svgCss}${mediaCss}${iconCss}`
385
398
  };
386
399
  }
387
400
  function escapeStyleText(css) {
@@ -389,18 +402,7 @@ function escapeStyleText(css) {
389
402
  }
390
403
  var FONT_AWESOME_LINK = `<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/7.3.1/css/all.min.css" crossorigin="anonymous" referrerpolicy="no-referrer">
391
404
  `;
392
- function docUsesIcons(doc) {
393
- function visit(node) {
394
- if (!node || typeof node !== "object") return false;
395
- const n = node;
396
- if (n.type === "inlineIcon") return true;
397
- if (Array.isArray(n.children)) {
398
- for (const child of n.children) if (visit(child)) return true;
399
- }
400
- return false;
401
- }
402
- return visit(doc);
403
- }
405
+ var SVG_ICON_CSS = ` .squisq-icon { display: inline-block; height: 1em; vertical-align: -0.125em; overflow: visible; }`;
404
406
  function docUsesVideoEmbeds(doc, htmlPolicy) {
405
407
  const iframeIn = (nodes) => nodes.some(
406
408
  (node) => node.type === "htmlElement" && (videoEmbedFromIframe(node) !== null || iframeIn(node.children))
@@ -441,15 +443,21 @@ function renderTopLevel(children, ctx) {
441
443
  continue;
442
444
  }
443
445
  }
444
- const video = node && node.type === "paragraph" ? findBlockVideoEmbed(node) : null;
445
- if (video) {
446
- out.push(videoEmbedToHtml(video.embed, video.title, ctx));
447
- continue;
448
- }
449
- out.push(nodeToHtml(node, ctx));
446
+ const html = topLevelBlockToHtml(node, ctx);
447
+ if (html !== null) out.push(html);
450
448
  }
451
449
  return out.join("\n");
452
450
  }
451
+ function topLevelBlockToHtml(node, ctx) {
452
+ if (node?.type !== "paragraph") return nodeToHtml(node, ctx);
453
+ const video = findBlockVideoEmbed(node);
454
+ if (video) return videoEmbedToHtml(video.embed, video.title, ctx);
455
+ const tag = parseStandaloneAnnotation(node);
456
+ if (tag?.template && isMediaAnnotationName(tag.template)) {
457
+ return mediaAnnotationToHtml(tag.template, tag.params, ctx);
458
+ }
459
+ return nodeToHtml(node, ctx);
460
+ }
453
461
  function renderFootnotesSection(footnotes, ctx) {
454
462
  if (footnotes.isEmpty) return "";
455
463
  const items = footnotes.ordered().map((fn) => {
@@ -484,7 +492,7 @@ function renderFeatureSection(heading, bodyNodes, side, ctx) {
484
492
  const headingHtml = `<${headingTag}>${childrenToHtml({ children: heading.children }, ctx)}</${headingTag}>`;
485
493
  const featured = takeFirstImage(bodyNodes);
486
494
  const media = featured.image ? renderFeatureImage(featured.image, ctx) : '<div class="squisq-feature__media squisq-feature__media--empty"></div>';
487
- const textHtml = [headingHtml, ...featured.remaining.map((n) => nodeToHtml(n, ctx))].filter((s) => s.length > 0).join("\n");
495
+ const textHtml = [headingHtml, ...featured.remaining.map((n) => topLevelBlockToHtml(n, ctx))].filter((s) => !!s).join("\n");
488
496
  const sideClass = side === "leftFeature" ? "squisq-feature--left" : "squisq-feature--right";
489
497
  return `<section class="squisq-feature ${sideClass}">
490
498
  ${media}
@@ -807,6 +815,11 @@ function nodeToHtml(node, ctx) {
807
815
  case "image": {
808
816
  const original = node.url ?? "";
809
817
  const resolved = ctx?.images?.get(original) ?? original;
818
+ const mediaKind = mediaKindForUrl(original);
819
+ if (mediaKind) {
820
+ const label = node.alt || node.title || void 0;
821
+ return mediaPlayerHtml(mediaKind, resolved, label, ctx) ?? escapeHtml2(node.alt ?? "");
822
+ }
810
823
  const safeSrc = sanitizeUrl(resolved, "media") ?? svgDataImageToHtmlSrc(resolved);
811
824
  if (!safeSrc) return escapeHtml2(node.alt ?? "");
812
825
  return `<img src="${escapeAttr(safeSrc)}" alt="${escapeAttr(node.alt ?? "")}"${imageSizeAttributes(node)} />`;
@@ -816,9 +829,16 @@ function nodeToHtml(node, ctx) {
816
829
  case "table":
817
830
  return tableToHtml(node, ctx);
818
831
  case "inlineIcon": {
832
+ const token = escapeAttr(node.token ?? `${node.family}:${node.name}`);
833
+ const svg = ctx?.icons?.get(`${node.family}:${node.name}`);
834
+ if (svg) {
835
+ ctx.usesSvgIcons = true;
836
+ const width = Number((svg.width / svg.height).toFixed(4));
837
+ return `<svg class="squisq-icon" viewBox="0 0 ${svg.width} ${svg.height}" width="${width}em" height="1em" fill="currentColor" aria-hidden="true" focusable="false" data-icon="${token}"><path d="${escapeAttr(svg.path)}"/></svg>`;
838
+ }
839
+ if (ctx) ctx.usesFontIcons = true;
819
840
  const family = escapeAttr(node.family ?? "solid");
820
841
  const name = escapeAttr(node.name ?? "");
821
- const token = escapeAttr(node.token ?? `${node.family}:${node.name}`);
822
842
  return `<i class="fa-${family} fa-${name}" data-icon="${token}" aria-hidden="true"></i>`;
823
843
  }
824
844
  case "htmlBlock":
@@ -955,6 +975,28 @@ var VIDEO_EMBED_CSS = ` .squisq-video-embed { display: block; max-width: 720px;
955
975
  .squisq-video-embed-play::after { content: ''; position: absolute; left: 26px; top: 12px; border-style: solid; border-width: 10px 0 10px 16px; border-color: transparent transparent transparent #fff; }
956
976
  .squisq-video-embed-provider { position: absolute; left: 12px; bottom: 10px; color: #fff; font-size: 0.8em; font-weight: 600; text-shadow: 0 1px 2px rgba(0, 0, 0, 0.8); }
957
977
  .squisq-video-embed-caption { margin-top: 0.4em; font-size: 0.875em; opacity: 0.8; }`;
978
+ function mediaPlayerHtml(kind, src, label, ctx) {
979
+ const safeSrc = sanitizeUrl(src, "media");
980
+ if (!safeSrc) return null;
981
+ if (ctx) ctx.usesMediaPlayers = true;
982
+ const aria = label ? ` aria-label="${escapeAttr(label)}"` : "";
983
+ return `<${kind} class="squisq-media" src="${escapeAttr(safeSrc)}" controls preload="metadata"${aria}></${kind}>`;
984
+ }
985
+ function mediaAnnotationToHtml(template, params, ctx) {
986
+ if (!params.src) return null;
987
+ const kind = template === "video" ? "video" : "audio";
988
+ let src = ctx?.images?.get(params.src) ?? params.src;
989
+ const start = parseTimeSeconds(params.clipStart ?? "");
990
+ const end = parseTimeSeconds(params.clipEnd ?? "");
991
+ if ((start !== null || end !== null) && !src.includes("#")) {
992
+ src += `#t=${start ?? ""}${end !== null ? `,${end}` : ""}`;
993
+ }
994
+ const player = mediaPlayerHtml(kind, src, void 0, ctx);
995
+ return player ? `<figure class="squisq-media-clip">${player}</figure>` : null;
996
+ }
997
+ var MEDIA_PLAYER_CSS = ` .squisq-media-clip { margin: 1.25em 0; }
998
+ video.squisq-media { display: block; width: 100%; max-width: 720px; height: auto; border-radius: 6px; }
999
+ audio.squisq-media { display: block; width: 100%; max-width: 480px; }`;
958
1000
  function svgDataImageToHtmlSrc(url) {
959
1001
  const source = svgSourceFromDataUrl(url);
960
1002
  const result = source === null ? null : parseSvgIsland(source);
@@ -60,7 +60,7 @@ import {
60
60
  fontAwesomeFace,
61
61
  fontAwesomeFaces,
62
62
  fontAwesomeGlyph
63
- } from "./chunk-LBXBRFBZ.js";
63
+ } from "./chunk-DDMPZZJF.js";
64
64
  import {
65
65
  extToMime,
66
66
  readImageDimensions
@@ -55,8 +55,9 @@ import {
55
55
  resolveTarget
56
56
  } from "./chunk-S5PCVMKU.js";
57
57
  import {
58
- renumberFootnotes
59
- } from "./chunk-BXWNU4T5.js";
58
+ renumberFootnotes,
59
+ withoutBlockTags
60
+ } from "./chunk-DTJQK7U7.js";
60
61
  import {
61
62
  buildContainer
62
63
  } from "./chunk-IIQYS2YH.js";
@@ -65,7 +66,7 @@ import {
65
66
  fontAwesomeFaces,
66
67
  fontAwesomeGlyph,
67
68
  obfuscateDocxFont
68
- } from "./chunk-LBXBRFBZ.js";
69
+ } from "./chunk-DDMPZZJF.js";
69
70
  import {
70
71
  authoredImageSize,
71
72
  extToMime,
@@ -214,8 +215,9 @@ function pointsToTwips(points) {
214
215
  }
215
216
 
216
217
  // src/docx/export.ts
217
- async function markdownDocToDocx(doc, options = {}) {
218
+ async function markdownDocToDocx(source, options = {}) {
218
219
  options.signal?.throwIfAborted();
220
+ const doc = withoutBlockTags(source);
219
221
  const resolvedOptions = options.themeId !== void 0 ? options : { ...options, themeId: readFrontmatterThemeId(doc.frontmatter) };
220
222
  const ctx = new ExportContext(resolvedOptions, doc);
221
223
  const stories = partitionDocumentStories(doc.children);