@bendyline/squisq 2.11.13 → 2.11.15

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 (33) hide show
  1. package/README.md +20 -0
  2. package/dist/{Doc-BWtfp7dT.d.ts → Doc-C-cv1g4G.d.ts} +8 -0
  3. package/dist/{ImageEditDoc-CYRQK8so.d.ts → ImageEditDoc-3J4PRw8N.d.ts} +1 -1
  4. package/dist/{chunk-S4427XKY.js → chunk-4IQ2DHCI.js} +83 -10
  5. package/dist/{chunk-EWO2VLOS.js → chunk-4P557IPH.js} +284 -14
  6. package/dist/{chunk-X2TDC4E5.js → chunk-GQWIUBJU.js} +2 -2
  7. package/dist/{chunk-RLBHQSUG.js → chunk-UVQPK6A3.js} +123 -0
  8. package/dist/{chunk-GIYWXYCK.js → chunk-XSOIWNZL.js} +8 -0
  9. package/dist/{chunk-42IU2SJY.js → chunk-YPI2VJSB.js} +190 -45
  10. package/dist/{chunk-BR7ZMDWW.js → chunk-Z35TG4WR.js} +11 -6
  11. package/dist/doc/index.d.ts +17 -7
  12. package/dist/doc/index.js +4 -3
  13. package/dist/fence/index.d.ts +1 -1
  14. package/dist/generate/index.d.ts +1 -1
  15. package/dist/imageEdit/index.d.ts +3 -3
  16. package/dist/index.d.ts +6 -6
  17. package/dist/index.js +12 -8
  18. package/dist/jsonForm/index.d.ts +1 -1
  19. package/dist/markdown/index.d.ts +12 -3
  20. package/dist/markdown/index.js +3 -1
  21. package/dist/{materializePageSection-Db7pABI0.d.ts → materializePageSection-Dmy_-AUQ.d.ts} +1 -1
  22. package/dist/mediaEdit/index.d.ts +2 -2
  23. package/dist/narration/index.d.ts +28 -8
  24. package/dist/narration/index.js +5 -3
  25. package/dist/schemas/index.d.ts +4 -4
  26. package/dist/storage/index.js +1 -1
  27. package/dist/{themeLibrary-Bphz1CRp.d.ts → themeLibrary-CROfw3YK.d.ts} +1 -1
  28. package/dist/timing/index.d.ts +1 -1
  29. package/dist/timing/index.js +1 -1
  30. package/dist/transform/index.d.ts +8 -2
  31. package/dist/transform/index.js +2 -2
  32. package/package.json +1 -1
  33. /package/dist/{chunk-44BJ42GT.js → chunk-4OQFLZ6G.js} +0 -0
package/README.md CHANGED
@@ -67,6 +67,26 @@ console.log(doc.children); // AST nodes
67
67
  const md = stringifyMarkdown(doc);
68
68
  ```
69
69
 
70
+ ### Condense imported Markdown
71
+
72
+ `condenseMarkdownSource(source)` removes table alignment padding, shortens
73
+ GFM delimiter rows, and reduces decorative thematic breaks to three markers.
74
+ It preserves table cells and alignment, code, math, HTML, links, footnotes,
75
+ prose, and frontmatter. The output is reparsed and structurally compared with
76
+ the original before returning; the API throws if equivalence cannot be proven.
77
+ It is idempotent and does not summarize articles or remove citations.
78
+
79
+ Run condensation after link/image edits, which can re-serialize padded tables.
80
+
81
+ ```ts
82
+ import { condenseMarkdownSource } from '@bendyline/squisq/markdown';
83
+
84
+ const compactMarkdown = condenseMarkdownSource(importedMarkdown);
85
+ ```
86
+
87
+ The shared source-transform registry also exposes `condense`, available from
88
+ the editor's Transform menu and `squisq transform file.md --ops condense`.
89
+
70
90
  ### Author an ASCII Timeline
71
91
 
72
92
  Timeline fences are source-of-truth markdown. Unicode marker rails are
@@ -739,6 +739,10 @@ interface BaseTemplateBlock {
739
739
  * Provenance only — NOT a time (see sourceStartTime for seconds).
740
740
  */
741
741
  sourceCharOffset?: number;
742
+ /** Projection-only summary policy; keeps visual features on their owning slide. */
743
+ summaryMode?: 'brief' | 'headings-and-features';
744
+ /** Automatic title-and-feature composition; omitted for authored templates. */
745
+ summaryLayout?: 'feature';
742
746
  /**
743
747
  * Per-block override for the theme's photographic image grade
744
748
  * (`theme.style.imageTreatment`). `'none'` opts this block's imagery out;
@@ -2482,6 +2486,10 @@ interface Block {
2482
2486
  sourceBlockIds?: string[];
2483
2487
  /** Char offset of the extraction inside the source block's plain text. */
2484
2488
  sourceCharOffset?: number;
2489
+ /** Projection-only summary policy; keeps visual features on their owning slide. */
2490
+ summaryMode?: 'brief' | 'headings-and-features';
2491
+ /** Automatic title-and-feature composition; omitted for authored templates. */
2492
+ summaryLayout?: 'feature';
2485
2493
  /**
2486
2494
  * Pre-computed visual layers, rendered back-to-front.
2487
2495
  *
@@ -1,4 +1,4 @@
1
- import { a2 as ImageLayer, bu as TextLayer, bh as ShapeLayer, aX as PathLayer } from './Doc-BWtfp7dT.js';
1
+ import { a2 as ImageLayer, bu as TextLayer, bh as ShapeLayer, aX as PathLayer } from './Doc-C-cv1g4G.js';
2
2
 
3
3
  /**
4
4
  * ImageEditDoc — JSON document describing a layered, editable image.
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  buildNarrationScript,
3
3
  parseNarrationTimingJson
4
- } from "./chunk-BR7ZMDWW.js";
4
+ } from "./chunk-Z35TG4WR.js";
5
5
  import {
6
6
  SHAPE_NAMES,
7
7
  buildChartData,
@@ -32,7 +32,7 @@ import {
32
32
  templateRegistry,
33
33
  writeCustomTemplatesToFrontmatter,
34
34
  writeCustomThemesToFrontmatter
35
- } from "./chunk-42IU2SJY.js";
35
+ } from "./chunk-YPI2VJSB.js";
36
36
  import {
37
37
  ASCII_TREE_VOCAB,
38
38
  ASCII_VOCAB,
@@ -98,6 +98,9 @@ import {
98
98
  parseHtmlToNodes,
99
99
  readFrontmatterThemeId
100
100
  } from "./chunk-X7BNDUNI.js";
101
+ import {
102
+ buildCaptionTrack
103
+ } from "./chunk-4OQFLZ6G.js";
101
104
  import {
102
105
  estimateTimeFromText
103
106
  } from "./chunk-GHOPHGOK.js";
@@ -954,8 +957,12 @@ function blockToSlide(block, index, knownTemplates, documentTitle) {
954
957
  // authored rich elements (Mermaid fences today; other media can follow)
955
958
  // independently of the selected visual template.
956
959
  ...block.contents ? { contents: block.contents } : {},
957
- // Custom templates additionally consume child blocks through tokens.
958
- ...isCustomTemplate && block.children ? { children: block.children } : {},
960
+ // Custom templates consume child blocks through tokens, and container
961
+ // templates (diagram, drawing, layout) draw their children as the canvas:
962
+ // without them a drawing renders as an empty frame in the slideshow,
963
+ // video export, and PPTX. They are never flattened into their own slides.
964
+ ...block.children && (isCustomTemplate || isContainerTemplate(template)) ? { children: block.children } : {},
965
+ ...block.summaryMode && block.layers ? { layers: block.layers } : {},
959
966
  ...defaults,
960
967
  ...extraFields,
961
968
  // Structured body data (```json data fences, GFM tables for dataTable)
@@ -1031,7 +1038,9 @@ function buildPreviewDoc(doc, options) {
1031
1038
  const block = flat[i];
1032
1039
  const blockImages = extractBlockImages(block.contents);
1033
1040
  const slide = blockToSlide(block, i, knownTemplates, documentTitle);
1034
- if (blockImages.length > 0 && slide.template === "sectionHeader") {
1041
+ if (block.summaryMode) {
1042
+ for (const img of blockImages) usedImageSrcs.add(img.src);
1043
+ } else if (blockImages.length > 0 && slide.template === "sectionHeader") {
1035
1044
  const img = blockImages[0];
1036
1045
  usedImageSrcs.add(img.src);
1037
1046
  slide.template = "imageWithCaption";
@@ -4293,6 +4302,34 @@ function mapRangesToPlayed(ranges, map2) {
4293
4302
  }
4294
4303
  return mapped;
4295
4304
  }
4305
+ function narrationCaptions(doc, clip, timing, timeMap, playedDuration) {
4306
+ if (timing.bookmarks.length === 0 || !(playedDuration > 0)) return null;
4307
+ const bookmarks = [];
4308
+ for (const bookmark of timing.bookmarks) {
4309
+ if (!timeMap) {
4310
+ if (bookmark.time <= playedDuration) bookmarks.push(bookmark);
4311
+ } else if (timeMap.sourceToPlayed(bookmark.time) !== null) {
4312
+ bookmarks.push({ ...bookmark, time: timeMap.snapSourceToPlayed(bookmark.time) });
4313
+ }
4314
+ }
4315
+ if (bookmarks.length === 0) return null;
4316
+ bookmarks.sort((a, b) => a.time - b.time);
4317
+ const track = buildCaptionTrack(
4318
+ [
4319
+ {
4320
+ segment: {
4321
+ src: clip.src,
4322
+ name: "narration",
4323
+ startTime: clip.startAt,
4324
+ duration: playedDuration
4325
+ },
4326
+ timing: { sourceText: timing.sourceText, bookmarks, duration: playedDuration }
4327
+ }
4328
+ ],
4329
+ doc.captions?.generatedAt ? { generatedAt: doc.captions.generatedAt } : {}
4330
+ );
4331
+ return track.phrases.length > 0 ? track : null;
4332
+ }
4296
4333
  function retimeBlocks(blocks, ctx) {
4297
4334
  return blocks.map((block) => {
4298
4335
  const range = ctx.ranges.get(block.id);
@@ -4349,10 +4386,12 @@ async function applyNarrationTiming(doc, container) {
4349
4386
  };
4350
4387
  const blocks = retimeBlocks(doc.blocks, ctx);
4351
4388
  const duration = Math.max(clip.startAt + playedDuration, ctx.cursor);
4389
+ const captions = narrationCaptions(doc, clip, timing, timeMap, playedDuration);
4352
4390
  const retimed = {
4353
4391
  ...doc,
4354
4392
  blocks,
4355
4393
  duration,
4394
+ ...captions ? { captions } : {},
4356
4395
  ...ctx.diagnostics.length > 0 ? { diagnostics: [...doc.diagnostics ?? [], ...ctx.diagnostics] } : {}
4357
4396
  };
4358
4397
  return { doc: retimed, applied: true, clipSrc: clip.src };
@@ -4809,6 +4848,10 @@ function collectKnownTemplates(doc, options) {
4809
4848
  function isDrawingBlock(block) {
4810
4849
  return resolveTemplateName(block.template ?? "") === "drawing";
4811
4850
  }
4851
+ function isLayoutBlock(block) {
4852
+ return resolveTemplateName(block.template ?? "") === "layout";
4853
+ }
4854
+ var LAYOUT_IMAGE_LAYERS = /* @__PURE__ */ new Set(["image", "img"]);
4812
4855
  function checkTemplate(block, parent, known, diagnostics) {
4813
4856
  const requested = block.sourceHeading?.templateAnnotation?.template ?? block.sourceAnnotation?.template;
4814
4857
  if (!requested) return;
@@ -4826,11 +4869,37 @@ function checkTemplate(block, parent, known, diagnostics) {
4826
4869
  });
4827
4870
  return;
4828
4871
  }
4872
+ if (parent && isLayoutBlock(parent)) {
4873
+ if (LAYOUT_IMAGE_LAYERS.has(requested.toLowerCase())) {
4874
+ if (!block.templateOverrides?.src) {
4875
+ diagnostics.push({
4876
+ severity: "warning",
4877
+ code: "layout-image-missing-src",
4878
+ message: `Layout image "${block.id}" has no src= \u2014 the layer will be skipped`,
4879
+ blockId: block.id,
4880
+ ...lineOf(block)
4881
+ });
4882
+ }
4883
+ return;
4884
+ }
4885
+ if (normalizeShapeKind(block.template ?? requested) || normalizeShapeKind(requested)) return;
4886
+ const suggestion2 = nearestName(requested, /* @__PURE__ */ new Set([...SHAPE_NAMES, "image"]), {
4887
+ map: resolveTemplateName
4888
+ });
4889
+ diagnostics.push({
4890
+ severity: "warning",
4891
+ code: "unknown-layer",
4892
+ message: `"${requested}" is not a layout layer \u2014 the child will be skipped. Layers are text, image, or a drawing shape` + (suggestion2 ? `. Did you mean "${suggestion2}"?` : ""),
4893
+ blockId: block.id,
4894
+ ...lineOf(block)
4895
+ });
4896
+ return;
4897
+ }
4829
4898
  if (isShapeName(requested)) {
4830
4899
  diagnostics.push({
4831
4900
  severity: "warning",
4832
4901
  code: "shape-outside-drawing",
4833
- message: `Shape "${requested}" is only recognized on a child heading of a {[drawing]} block`,
4902
+ message: `Shape "${requested}" is only recognized on a child heading of a {[drawing]} or {[layout]} block`,
4834
4903
  blockId: block.id,
4835
4904
  ...lineOf(block)
4836
4905
  });
@@ -4849,7 +4918,7 @@ function checkTemplate(block, parent, known, diagnostics) {
4849
4918
  function checkTemplateInputs(block, parent, customNames, diagnostics) {
4850
4919
  const requested = block.sourceHeading?.templateAnnotation?.template;
4851
4920
  if (!requested) return;
4852
- if (parent && isDrawingBlock(parent)) return;
4921
+ if (parent && (isDrawingBlock(parent) || isLayoutBlock(parent))) return;
4853
4922
  if (isShapeName(requested)) return;
4854
4923
  const canonical = resolveTemplateName(requested);
4855
4924
  if (customNames.has(requested) || customNames.has(canonical)) return;
@@ -4874,6 +4943,7 @@ function isInputSatisfiable(canonical, block, key) {
4874
4943
  return derived != null && key in derived;
4875
4944
  }
4876
4945
  var NUMERIC_SHAPE_KEYS = ["x", "y", "width", "height", "w", "h", "strokeWidth", "borderRadius"];
4946
+ var NUMERIC_LAYER_KEYS = [...NUMERIC_SHAPE_KEYS, "fontSize", "lineHeight", "padding"];
4877
4947
  function checkAttributeCoercion(block, parent, diagnostics) {
4878
4948
  const params = block.sourceHeading?.attributes?.params;
4879
4949
  if (params) {
@@ -4888,9 +4958,12 @@ function checkAttributeCoercion(block, parent, diagnostics) {
4888
4958
  });
4889
4959
  }
4890
4960
  }
4891
- if (parent && isDrawingBlock(parent) && isShapeName(block.template)) {
4961
+ const inDrawing = parent !== void 0 && isDrawingBlock(parent) && isShapeName(block.template);
4962
+ const inLayout = parent !== void 0 && isLayoutBlock(parent);
4963
+ if (inDrawing || inLayout) {
4892
4964
  const overrides = block.templateOverrides ?? {};
4893
- for (const key of NUMERIC_SHAPE_KEYS) {
4965
+ const label = inLayout ? "Layout layer" : "Drawing shape";
4966
+ for (const key of inLayout ? NUMERIC_LAYER_KEYS : NUMERIC_SHAPE_KEYS) {
4894
4967
  const raw = overrides[key];
4895
4968
  if (raw == null) continue;
4896
4969
  const value = raw.replace(/,\s*$/, "").trim();
@@ -4898,7 +4971,7 @@ function checkAttributeCoercion(block, parent, diagnostics) {
4898
4971
  diagnostics.push({
4899
4972
  severity: "warning",
4900
4973
  code: "invalid-attribute",
4901
- message: `Drawing shape "${block.id}": "${key}" is not a number (${JSON.stringify(raw)})`,
4974
+ message: `${label} "${block.id}": "${key}" is not a number (${JSON.stringify(raw)})`,
4902
4975
  blockId: block.id,
4903
4976
  ...lineOf(block)
4904
4977
  });
@@ -4,8 +4,16 @@ import {
4
4
  stripMarkdown
5
5
  } from "./chunk-SVN7T6BK.js";
6
6
  import {
7
+ coerceTemplateParams,
8
+ deriveTemplateInputs,
9
+ flattenRenderableBlocks,
7
10
  markdownToDoc
8
- } from "./chunk-42IU2SJY.js";
11
+ } from "./chunk-YPI2VJSB.js";
12
+ import {
13
+ isAsciiDiagramFence,
14
+ isAsciiTimelineFence,
15
+ isTreeFence
16
+ } from "./chunk-C3NAY5YP.js";
9
17
  import {
10
18
  cloneAndFreezeData,
11
19
  isTemplateBlock
@@ -13,9 +21,13 @@ import {
13
21
  import {
14
22
  parseMarkdown
15
23
  } from "./chunk-HUXK7EOT.js";
24
+ import {
25
+ isContainerTemplate
26
+ } from "./chunk-TMOHAFYE.js";
16
27
  import {
17
28
  extractPlainText,
18
- getChildren
29
+ getChildren,
30
+ stringifyHtmlNodes
19
31
  } from "./chunk-X7BNDUNI.js";
20
32
  import {
21
33
  estimateTimeFromText
@@ -99,26 +111,45 @@ var narrativeStyle = {
99
111
  var minimalStyle = {
100
112
  id: "minimal",
101
113
  name: "Minimal",
102
- description: "Light touch \u2014 only the strongest highlights, mostly text",
114
+ description: "Short highlights and a single brief line per section",
115
+ contentMode: "brief",
103
116
  minConfidence: 0.6,
104
117
  transformRatio: 0.2,
105
118
  preferredTypes: ["stat", "quote"],
106
119
  colorSchemes: ["blue", "green"],
107
120
  insertSectionHeaders: false,
108
121
  interleaveImages: false,
109
- blocksPerSection: { max: 2 },
122
+ blocksPerSection: { max: 1 },
110
123
  transitionStyle: "fade",
111
124
  suggestedThemeId: "minimalist",
112
125
  page: { spacing: "generous", emphasisCurve: "even" }
113
126
  };
114
127
 
128
+ // src/transform/styles/headingsAndFeatures.ts
129
+ var headingsAndFeaturesStyle = {
130
+ id: "headings-and-features",
131
+ name: "Headings and features",
132
+ description: "Headings with their images, videos, and diagrams; no body prose",
133
+ contentMode: "headings-and-features",
134
+ minConfidence: 1,
135
+ transformRatio: 0,
136
+ preferredTypes: [],
137
+ colorSchemes: ["blue", "green"],
138
+ insertSectionHeaders: false,
139
+ interleaveImages: false,
140
+ blocksPerSection: { max: 1 },
141
+ transitionStyle: "fade",
142
+ page: { spacing: "generous", emphasisCurve: "even" }
143
+ };
144
+
115
145
  // src/transform/registry.ts
116
146
  var TRANSFORM_STYLES = Object.freeze({
117
147
  [documentaryStyle.id]: cloneAndFreezeData(documentaryStyle),
118
148
  [magazineStyle.id]: cloneAndFreezeData(magazineStyle),
119
149
  [dataDrivenStyle.id]: cloneAndFreezeData(dataDrivenStyle),
120
150
  [narrativeStyle.id]: cloneAndFreezeData(narrativeStyle),
121
- [minimalStyle.id]: cloneAndFreezeData(minimalStyle)
151
+ [minimalStyle.id]: cloneAndFreezeData(minimalStyle),
152
+ [headingsAndFeaturesStyle.id]: cloneAndFreezeData(headingsAndFeaturesStyle)
122
153
  });
123
154
  var DEFAULT_TRANSFORM_STYLE_ID = "documentary";
124
155
  var EXTRACTION_TYPES = /* @__PURE__ */ new Set([
@@ -148,6 +179,9 @@ function validateTransformStyle(style) {
148
179
  fail(`${key} must be a finite number between 0 and 1`);
149
180
  }
150
181
  }
182
+ if (candidate.contentMode !== void 0 && candidate.contentMode !== "brief" && candidate.contentMode !== "headings-and-features") {
183
+ fail("contentMode must be brief or headings-and-features");
184
+ }
151
185
  if (!Array.isArray(candidate.preferredTypes) || candidate.preferredTypes.some((type) => !EXTRACTION_TYPES.has(type))) {
152
186
  fail("preferredTypes contains an unknown extraction type");
153
187
  }
@@ -328,7 +362,9 @@ function collectLeafBlocks(blocks) {
328
362
  return result;
329
363
  }
330
364
  function analyzeBlocks(blocks, options) {
331
- const flat = collectLeafBlocks(blocks);
365
+ return analyzeFlatBlocks(collectLeafBlocks(blocks), options);
366
+ }
367
+ function analyzeFlatBlocks(flat, options) {
332
368
  const results = [];
333
369
  for (const block of flat) {
334
370
  let plainText = "";
@@ -353,6 +389,223 @@ function analyzeBlocks(blocks, options) {
353
389
  return results;
354
390
  }
355
391
 
392
+ // src/transform/contentSummary.ts
393
+ var NATIVE_FEATURE_TEMPLATES = /* @__PURE__ */ new Set([
394
+ "diagram",
395
+ "tree",
396
+ "timeline",
397
+ "map",
398
+ "drawing",
399
+ "layout",
400
+ "dataTable",
401
+ "barChart",
402
+ "columnChart",
403
+ "pieChart",
404
+ "donutChart",
405
+ "lineChart",
406
+ "areaChart",
407
+ "scatterChart"
408
+ ]);
409
+ var VIDEO_FILE_RE = /\.(?:webm|mp4|mov|m4v|ogv)(?:[?#].*)?$/i;
410
+ var PROSE_KEYS = /* @__PURE__ */ new Set([
411
+ "body",
412
+ "subtitle",
413
+ "description",
414
+ "explanation",
415
+ "definition",
416
+ "detail",
417
+ "caption",
418
+ "quote",
419
+ "text"
420
+ ]);
421
+ function briefText(text) {
422
+ const normalized = text.replace(/\s+/g, " ").trim();
423
+ const sentence = normalized.match(/^.*?[.!?](?:["'”’])?(?=\s|$)/)?.[0] ?? normalized;
424
+ const words = sentence.split(/\s+/).filter(Boolean);
425
+ let brief = words.slice(0, 24).join(" ");
426
+ const truncated = words.length > 24 || brief.length > 180;
427
+ if (brief.length > 179 && truncated) {
428
+ brief = brief.slice(0, 179).replace(/\s+\S*$/, "").trimEnd();
429
+ }
430
+ return truncated ? `${brief.replace(/[.,;:!?]+$/, "")}\u2026` : brief;
431
+ }
432
+ function htmlFeatures(nodes) {
433
+ return nodes.flatMap((node) => {
434
+ if (node.type !== "htmlElement") return [];
435
+ const tag = node.tagName.toLowerCase();
436
+ if (tag === "img") return [node];
437
+ if (tag === "video") {
438
+ return [
439
+ {
440
+ ...node,
441
+ children: node.children.filter(
442
+ (child) => child.type === "htmlElement" && ["source", "track"].includes(child.tagName.toLowerCase())
443
+ )
444
+ }
445
+ ];
446
+ }
447
+ return htmlFeatures(node.children);
448
+ });
449
+ }
450
+ function extractFeatureContents(contents, keepData = false) {
451
+ const features = [];
452
+ const visit = (node) => {
453
+ if (node.type === "code") {
454
+ if (node.lang?.trim().toLowerCase() === "mermaid" || isAsciiDiagramFence(node) || isTreeFence(node) || isAsciiTimelineFence(node) || keepData && /^(json|ya?ml)$/i.test(node.lang ?? "") && /\bdata\b/i.test(node.meta ?? ""))
455
+ features.push(node);
456
+ return;
457
+ }
458
+ if (node.type === "table") {
459
+ if (keepData) features.push(node);
460
+ else for (const child of getChildren(node)) visit(child);
461
+ return;
462
+ }
463
+ if (node.type === "image" || node.type === "link" && VIDEO_FILE_RE.test(node.url)) {
464
+ features.push({ type: "paragraph", children: [node] });
465
+ return;
466
+ }
467
+ if (node.type === "htmlBlock" || node.type === "htmlInline") {
468
+ const htmlChildren = htmlFeatures(node.htmlChildren);
469
+ if (htmlChildren.length) {
470
+ features.push({
471
+ type: "htmlBlock",
472
+ htmlChildren,
473
+ rawHtml: stringifyHtmlNodes(htmlChildren)
474
+ });
475
+ }
476
+ return;
477
+ }
478
+ for (const child of getChildren(node)) visit(child);
479
+ };
480
+ for (const node of contents ?? []) visit(node);
481
+ return features;
482
+ }
483
+ function withoutProse(record) {
484
+ return record && Object.fromEntries(Object.entries(record).filter(([key]) => !PROSE_KEYS.has(key)));
485
+ }
486
+ function typedMediaContents(block) {
487
+ const record = {
488
+ ...block,
489
+ ...block.templateData,
490
+ ...coerceTemplateParams(block.template ?? "", block.templateOverrides ?? {}).input
491
+ };
492
+ const contents = [];
493
+ const addImage = (src, alt) => {
494
+ if (typeof src === "string" && src) {
495
+ contents.push({
496
+ type: "paragraph",
497
+ children: [{ type: "image", url: src, alt: typeof alt === "string" ? alt : "" }]
498
+ });
499
+ }
500
+ };
501
+ addImage(record.imageSrc, record.imageAlt);
502
+ for (const key of ["accentImage", "backgroundImage"]) {
503
+ const image = record[key];
504
+ if (image && typeof image === "object") {
505
+ const data = image;
506
+ addImage(data.src, data.alt);
507
+ }
508
+ }
509
+ if (Array.isArray(record.images)) {
510
+ for (const image of record.images) {
511
+ if (image && typeof image === "object") {
512
+ const data = image;
513
+ addImage(data.src, data.alt);
514
+ }
515
+ }
516
+ }
517
+ if (typeof record.videoSrc === "string" && record.videoSrc) {
518
+ const htmlChildren = [
519
+ {
520
+ type: "htmlElement",
521
+ tagName: "video",
522
+ selfClosing: false,
523
+ children: [],
524
+ attributes: {
525
+ src: record.videoSrc,
526
+ ...typeof record.posterSrc === "string" ? { poster: record.posterSrc } : {}
527
+ }
528
+ }
529
+ ];
530
+ contents.push({ type: "htmlBlock", htmlChildren, rawHtml: stringifyHtmlNodes(htmlChildren) });
531
+ }
532
+ return contents;
533
+ }
534
+ function summarizeBlock(block, mode) {
535
+ const title = block.sourceHeading ? extractPlainText(block.sourceHeading) : block.title ?? "";
536
+ const nativeFeature = NATIVE_FEATURE_TEMPLATES.has(block.template ?? "");
537
+ const authoredTemplate = mode === "headings-and-features" && !!block.template && !block.autoTemplate && (!block.sourceHeading || !!block.sourceHeading.templateAnnotation || !!block.promotedBodyAnnotation);
538
+ const keepTemplate = nativeFeature || authoredTemplate;
539
+ const tableInputs = keepTemplate ? null : deriveTemplateInputs("dataTable", title, block.contents);
540
+ const featureContents = extractFeatureContents(block.contents, keepTemplate || !!tableInputs);
541
+ const brief = mode === "brief" ? briefText(
542
+ (block.contents ?? []).filter((node) => ["paragraph", "list", "blockquote"].includes(node.type)).map(extractPlainText).join(" ")
543
+ ) : "";
544
+ const template = keepTemplate ? block.template : tableInputs ? "dataTable" : brief ? "content" : "sectionHeader";
545
+ const contents = [
546
+ ...brief && !nativeFeature && !tableInputs ? [{ type: "paragraph", children: [{ type: "text", value: brief }] }] : [],
547
+ ...featureContents,
548
+ ...typedMediaContents(block)
549
+ ];
550
+ const base = {
551
+ id: block.id,
552
+ startTime: block.startTime,
553
+ duration: block.duration,
554
+ audioSegment: block.audioSegment,
555
+ title,
556
+ template,
557
+ contents,
558
+ summaryMode: mode,
559
+ ...mode === "headings-and-features" && !keepTemplate && !tableInputs && contents.length ? { summaryLayout: "feature" } : {},
560
+ sourceBlockId: block.id,
561
+ sourceCharOffset: 0,
562
+ sourceStartTime: block.startTime,
563
+ sourceDuration: block.duration,
564
+ transition: block.transition,
565
+ media: block.media,
566
+ ...block.sourceHeading ? { sourceHeading: { ...block.sourceHeading, templateAnnotation: { template } } } : {}
567
+ };
568
+ if (!keepTemplate && !tableInputs) return base;
569
+ const headingInputs = authoredTemplate ? {
570
+ body: "",
571
+ subtitle: "",
572
+ description: template === "statHighlight" ? title : "",
573
+ explanation: "",
574
+ definition: "",
575
+ detail: "",
576
+ ...template === "list" ? { items: [] } : {},
577
+ ...template === "imageWithCaption" || template === "videoWithCaption" ? { caption: title } : {},
578
+ ...template === "quote" ? { quote: title } : {},
579
+ ...template === "pullQuote" || template === "fullBleedQuote" ? { text: title } : {}
580
+ } : {};
581
+ return {
582
+ ...keepTemplate ? withoutProse(block) : {},
583
+ ...base,
584
+ ...headingInputs,
585
+ children: keepTemplate && isContainerTemplate(template) ? block.children : void 0,
586
+ templateData: tableInputs ?? { ...withoutProse(block.templateData), ...headingInputs },
587
+ templateOverrides: keepTemplate ? withoutProse(
588
+ template === "list" ? Object.fromEntries(
589
+ Object.entries(block.templateOverrides ?? {}).filter(([key]) => key !== "items")
590
+ ) : block.templateOverrides
591
+ ) : void 0
592
+ };
593
+ }
594
+ function summarizeHighlight(block, source) {
595
+ const summary = {
596
+ ...block,
597
+ summaryMode: "brief",
598
+ contents: [...extractFeatureContents(source.contents), ...typedMediaContents(source)],
599
+ media: source.media
600
+ };
601
+ if (summary.template === "quote") return { ...summary, quote: briefText(summary.quote) };
602
+ if (summary.template === "statHighlight")
603
+ return { ...summary, description: briefText(summary.description), detail: void 0 };
604
+ if (summary.template === "fullBleedQuote" || summary.template === "pullQuote")
605
+ return { ...summary, text: briefText(summary.text) };
606
+ return summary;
607
+ }
608
+
356
609
  // src/transform/templateSelector.ts
357
610
  var ACCENT_POSITIONS = [
358
611
  "left-strip",
@@ -446,7 +699,8 @@ function buildBlockSequence(analyzed, selected, config, images, rng) {
446
699
  const ab = analyzed[i];
447
700
  const extractions = blockExtractions.get(i);
448
701
  if (!extractions || extractions.length === 0) {
449
- blocks.push(ab.block);
702
+ blocks.push(config.contentMode === "brief" ? summarizeBlock(ab.block, "brief") : ab.block);
703
+ if (config.contentMode === "brief") transformedCount++;
450
704
  continue;
451
705
  }
452
706
  if (config.insertSectionHeaders && ab.block.title) {
@@ -495,6 +749,9 @@ function buildBlockSequence(analyzed, selected, config, images, rng) {
495
749
  const translated = translateTemplateBlock(templateBlock, remapTarget);
496
750
  if (translated) templateBlock = translated;
497
751
  }
752
+ if (config.contentMode === "brief") {
753
+ templateBlock = summarizeHighlight(templateBlock, ab.block);
754
+ }
498
755
  if (config.transitionStyle !== "cut") {
499
756
  const transitionType = config.transitionStyle === "mixed" ? rng.pick(["fade", "dissolve"]) ?? "fade" : config.transitionStyle;
500
757
  templateBlock.transition = {
@@ -780,12 +1037,15 @@ function applyTransform(doc, style, options) {
780
1037
  const config = options?.overrides ? { ...baseConfig, ...options.overrides, id: baseConfig.id } : baseConfig;
781
1038
  const seed = options?.seed ?? hashString(doc.articleId || "transform");
782
1039
  const images = options?.images ?? extractDocImages(doc.blocks);
783
- const analyzed = analyzeBlocks(doc.blocks, {
784
- minConfidence: config.minConfidence,
785
- types: config.preferredTypes
786
- });
1040
+ const analyzed = (config.contentMode ? analyzeFlatBlocks : analyzeBlocks)(
1041
+ config.contentMode ? flattenRenderableBlocks(doc.blocks) : doc.blocks,
1042
+ {
1043
+ minConfidence: config.minConfidence,
1044
+ types: config.preferredTypes
1045
+ }
1046
+ );
787
1047
  const hasContent = analyzed.some((ab) => ab.bodyWordCount >= 5);
788
- if (!hasContent) {
1048
+ if (!hasContent && !config.contentMode) {
789
1049
  return {
790
1050
  doc: { ...doc },
791
1051
  stats: {
@@ -795,7 +1055,11 @@ function applyTransform(doc, style, options) {
795
1055
  }
796
1056
  };
797
1057
  }
798
- const selection = selectAndBuild(analyzed, config, images, seed);
1058
+ const selection = config.contentMode === "headings-and-features" ? {
1059
+ blocks: analyzed.filter((ab) => ab.block.sourceHeading || ab.block.title).map((ab) => summarizeBlock(ab.block, "headings-and-features")),
1060
+ transformedCount: analyzed.filter((ab) => ab.block.sourceHeading || ab.block.title).length,
1061
+ insertedCount: 0
1062
+ } : selectAndBuild(analyzed, config, images, seed);
799
1063
  let paced = selection.blocks;
800
1064
  let pacingInserted = 0;
801
1065
  const docTitle = firstTitle(doc);
@@ -826,7 +1090,13 @@ function applyTransform(doc, style, options) {
826
1090
  const transformedDoc = {
827
1091
  ...doc,
828
1092
  blocks: timedBlocks,
829
- themeId: options?.themeId ?? doc.themeId ?? config.suggestedThemeId
1093
+ themeId: options?.themeId ?? doc.themeId ?? config.suggestedThemeId,
1094
+ ...config.contentMode && doc.startBlock ? {
1095
+ startBlock: {
1096
+ ...doc.startBlock,
1097
+ subtitle: config.contentMode === "brief" ? briefText(doc.startBlock.subtitle ?? "") : void 0
1098
+ }
1099
+ } : {}
830
1100
  };
831
1101
  return {
832
1102
  doc: transformedDoc,
@@ -7,10 +7,10 @@ import {
7
7
  buildNarrationScript,
8
8
  expectedSyllablesAt,
9
9
  wordPosAtExpectedSyllables
10
- } from "./chunk-BR7ZMDWW.js";
10
+ } from "./chunk-Z35TG4WR.js";
11
11
  import {
12
12
  flattenRenderableBlocks
13
- } from "./chunk-42IU2SJY.js";
13
+ } from "./chunk-YPI2VJSB.js";
14
14
 
15
15
  // src/narration/types.ts
16
16
  var DEFAULT_FEATURE_CONFIG = Object.freeze({