@bendyline/squisq 2.13.11 → 2.13.13
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.
- package/dist/{Doc-DqYt_b2b.d.ts → Doc-BhvotZx_.d.ts} +48 -2
- package/dist/{ImageEditDoc-CXXiXVJ-.d.ts → ImageEditDoc-D8hYio6b.d.ts} +1 -1
- package/dist/{chunk-S76W65BB.js → chunk-5DFFVW75.js} +1 -1
- package/dist/{chunk-DOOUVVOM.js → chunk-7MS26OXD.js} +4 -0
- package/dist/{chunk-XDOSE5M3.js → chunk-C4SB3NN4.js} +1 -1
- package/dist/{chunk-SKGMNBQX.js → chunk-CTT4XAGO.js} +2033 -375
- package/dist/{chunk-SG3HXPMF.js → chunk-DBVCSVNV.js} +2 -1
- package/dist/chunk-FCB6JLWB.js +125 -0
- package/dist/{chunk-WNEH3BZP.js → chunk-JNUNNQ74.js} +3 -3
- package/dist/chunk-L4AFSOT3.js +26 -0
- package/dist/{chunk-65WL5ILB.js → chunk-OIQ2GO2O.js} +1 -1
- package/dist/{chunk-3WUI2N6C.js → chunk-SGNWO7OF.js} +20 -1
- package/dist/{chunk-ESGKI6CD.js → chunk-TCGGJUKC.js} +25 -6
- package/dist/{chunk-GLWQTZ4C.js → chunk-XEQALK5B.js} +1 -1
- package/dist/{chunk-N7I6JP6Q.js → chunk-YQQWE4HG.js} +413 -216
- package/dist/doc/index.d.ts +225 -9
- package/dist/doc/index.js +32 -7
- package/dist/fence/index.d.ts +1 -1
- package/dist/generate/index.d.ts +1 -1
- package/dist/imageEdit/index.d.ts +3 -3
- package/dist/index.d.ts +7 -7
- package/dist/index.js +79 -11
- package/dist/jsonForm/index.d.ts +1 -1
- package/dist/jsonForm/index.js +3 -3
- package/dist/markdown/index.js +3 -3
- package/dist/{materializePageSection-S2hxZ_lO.d.ts → materializePageSection-CWxou4mX.d.ts} +62 -2
- package/dist/mediaEdit/index.d.ts +2 -2
- package/dist/narration/index.d.ts +1 -1
- package/dist/narration/index.js +5 -5
- package/dist/recommend/index.js +1 -1
- package/dist/schemas/index.d.ts +4 -4
- package/dist/schemas/index.js +2 -2
- package/dist/{themeLibrary-CFJbeItn.d.ts → themeLibrary-BIYE6aLw.d.ts} +1 -1
- package/dist/timing/index.d.ts +58 -2
- package/dist/timing/index.js +9 -1
- package/dist/transform/index.d.ts +173 -72
- package/dist/transform/index.js +43 -6
- package/package.json +1 -1
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import {
|
|
2
2
|
buildNarrationScript,
|
|
3
|
+
buildSourceText,
|
|
3
4
|
coerceTemplateParams,
|
|
5
|
+
conceptBuildStepTexts,
|
|
4
6
|
flattenRenderableBlocks,
|
|
5
7
|
getPinnedBlockMeta,
|
|
6
8
|
markdownToDoc
|
|
7
|
-
} from "./chunk-
|
|
9
|
+
} from "./chunk-CTT4XAGO.js";
|
|
8
10
|
import {
|
|
9
11
|
deriveTemplateInputs,
|
|
10
12
|
extractEmbeddedVideos,
|
|
@@ -19,7 +21,7 @@ import {
|
|
|
19
21
|
isStandaloneHtmlVideo,
|
|
20
22
|
isTreeFence,
|
|
21
23
|
matchNumberHighlight
|
|
22
|
-
} from "./chunk-
|
|
24
|
+
} from "./chunk-SGNWO7OF.js";
|
|
23
25
|
import {
|
|
24
26
|
extractContent,
|
|
25
27
|
mapElementToBlock,
|
|
@@ -29,13 +31,13 @@ import {
|
|
|
29
31
|
cloneAndFreezeData,
|
|
30
32
|
cloneData,
|
|
31
33
|
isTemplateBlock
|
|
32
|
-
} from "./chunk-
|
|
34
|
+
} from "./chunk-JNUNNQ74.js";
|
|
33
35
|
import {
|
|
34
36
|
parseMarkdown
|
|
35
|
-
} from "./chunk-
|
|
37
|
+
} from "./chunk-C4SB3NN4.js";
|
|
36
38
|
import {
|
|
37
39
|
isContainerTemplate
|
|
38
|
-
} from "./chunk-
|
|
40
|
+
} from "./chunk-7MS26OXD.js";
|
|
39
41
|
import {
|
|
40
42
|
extractPlainText,
|
|
41
43
|
findBlockVideoEmbed,
|
|
@@ -46,6 +48,9 @@ import {
|
|
|
46
48
|
import {
|
|
47
49
|
parseBuildSpec
|
|
48
50
|
} from "./chunk-P53Z4RYC.js";
|
|
51
|
+
import {
|
|
52
|
+
wordTimeAt
|
|
53
|
+
} from "./chunk-L4AFSOT3.js";
|
|
49
54
|
import {
|
|
50
55
|
estimateTimeFromText
|
|
51
56
|
} from "./chunk-GHOPHGOK.js";
|
|
@@ -67,7 +72,11 @@ var PRESENTATION_LAYOUTS = [
|
|
|
67
72
|
"list",
|
|
68
73
|
"comparison",
|
|
69
74
|
"steps",
|
|
70
|
-
"image"
|
|
75
|
+
"image",
|
|
76
|
+
"cards",
|
|
77
|
+
"process",
|
|
78
|
+
"cycle",
|
|
79
|
+
"layers"
|
|
71
80
|
];
|
|
72
81
|
var AUTHORED_PRESENTATION_LAYOUT = "authored";
|
|
73
82
|
var EMPHASIS_CHARACTERS = 60;
|
|
@@ -83,7 +92,12 @@ function presentationLayoutFits(layout, headline, points, passage) {
|
|
|
83
92
|
switch (layout) {
|
|
84
93
|
case "list":
|
|
85
94
|
case "steps":
|
|
95
|
+
case "cards":
|
|
96
|
+
case "process":
|
|
86
97
|
return points.length >= 2;
|
|
98
|
+
case "cycle":
|
|
99
|
+
case "layers":
|
|
100
|
+
return points.length >= 3;
|
|
87
101
|
case "comparison":
|
|
88
102
|
return points.length === 2;
|
|
89
103
|
case "definition":
|
|
@@ -289,96 +303,6 @@ function usesDynamicPresentation(doc) {
|
|
|
289
303
|
return typeof style === "string" && style.trim().toLowerCase().replace(/\s+/g, "-") === DYNAMIC_PRESENTATION_STYLE;
|
|
290
304
|
}
|
|
291
305
|
|
|
292
|
-
// src/transform/blockAnalyzer.ts
|
|
293
|
-
function contentsToPlainText(contents) {
|
|
294
|
-
return contents.map((node) => extractPlainText(node)).join("\n");
|
|
295
|
-
}
|
|
296
|
-
var PROSE_NODE_TYPES = /* @__PURE__ */ new Set(["paragraph", "blockquote", "heading"]);
|
|
297
|
-
function maskNonProse(stripped, contents) {
|
|
298
|
-
let masked = stripped;
|
|
299
|
-
let cursor = 0;
|
|
300
|
-
for (const node of contents) {
|
|
301
|
-
const text3 = stripMarkdown(extractPlainText(node));
|
|
302
|
-
if (!text3) continue;
|
|
303
|
-
const at = stripped.indexOf(text3, cursor);
|
|
304
|
-
if (at < 0) continue;
|
|
305
|
-
cursor = at + text3.length;
|
|
306
|
-
if (PROSE_NODE_TYPES.has(node.type)) continue;
|
|
307
|
-
masked = masked.slice(0, at) + text3.replace(/\S/g, " ") + masked.slice(cursor);
|
|
308
|
-
}
|
|
309
|
-
return masked;
|
|
310
|
-
}
|
|
311
|
-
function extractBlockImages(block, blockIndex) {
|
|
312
|
-
return extractImages(block.contents).map(({ src, alt }) => ({
|
|
313
|
-
src,
|
|
314
|
-
alt,
|
|
315
|
-
sourceBlockIndex: blockIndex
|
|
316
|
-
}));
|
|
317
|
-
}
|
|
318
|
-
function extractDocImages(blocks) {
|
|
319
|
-
const seen = /* @__PURE__ */ new Set();
|
|
320
|
-
const images = [];
|
|
321
|
-
function walk(blockList) {
|
|
322
|
-
for (const block of blockList) {
|
|
323
|
-
for (const img of extractImages(block.contents)) {
|
|
324
|
-
if (!seen.has(img.src)) {
|
|
325
|
-
seen.add(img.src);
|
|
326
|
-
images.push({ src: img.src, alt: img.alt });
|
|
327
|
-
}
|
|
328
|
-
}
|
|
329
|
-
if (block.children) {
|
|
330
|
-
walk(block.children);
|
|
331
|
-
}
|
|
332
|
-
}
|
|
333
|
-
}
|
|
334
|
-
walk(blocks);
|
|
335
|
-
return images;
|
|
336
|
-
}
|
|
337
|
-
function collectLeafBlocks(blocks) {
|
|
338
|
-
const result = [];
|
|
339
|
-
for (const block of blocks) {
|
|
340
|
-
if (block.children && block.children.length > 0) {
|
|
341
|
-
if (!block.contents || block.contents.length === 0) {
|
|
342
|
-
result.push(block);
|
|
343
|
-
}
|
|
344
|
-
result.push(...collectLeafBlocks(block.children));
|
|
345
|
-
} else {
|
|
346
|
-
result.push(block);
|
|
347
|
-
}
|
|
348
|
-
}
|
|
349
|
-
return result;
|
|
350
|
-
}
|
|
351
|
-
function analyzeBlocks(blocks, options) {
|
|
352
|
-
return analyzeFlatBlocks(collectLeafBlocks(blocks), options);
|
|
353
|
-
}
|
|
354
|
-
function analyzeFlatBlocks(flat, options) {
|
|
355
|
-
const results = [];
|
|
356
|
-
for (const block of flat) {
|
|
357
|
-
let plainText = "";
|
|
358
|
-
if (block.contents && block.contents.length > 0) {
|
|
359
|
-
plainText = contentsToPlainText(block.contents);
|
|
360
|
-
} else if (block.title) {
|
|
361
|
-
plainText = block.title;
|
|
362
|
-
}
|
|
363
|
-
const stripped = stripMarkdown(plainText);
|
|
364
|
-
const bodyWordCount = stripped.split(/\s+/).filter((w) => w.length > 0).length;
|
|
365
|
-
const extraction = extractContent(
|
|
366
|
-
block.contents && block.contents.length > 0 ? maskNonProse(stripped, block.contents) : stripped,
|
|
367
|
-
options
|
|
368
|
-
);
|
|
369
|
-
const images = extractBlockImages(block, results.length);
|
|
370
|
-
results.push({
|
|
371
|
-
block,
|
|
372
|
-
plainText: stripped,
|
|
373
|
-
extractions: extraction.elements,
|
|
374
|
-
hasChildren: !!(block.children && block.children.length > 0),
|
|
375
|
-
bodyWordCount,
|
|
376
|
-
images
|
|
377
|
-
});
|
|
378
|
-
}
|
|
379
|
-
return results;
|
|
380
|
-
}
|
|
381
|
-
|
|
382
306
|
// src/transform/styles/dynamicSlides.ts
|
|
383
307
|
var dynamicSlidesStyle = {
|
|
384
308
|
id: "dynamic-slides",
|
|
@@ -656,6 +580,96 @@ function getTransformStyleSummaries(registry) {
|
|
|
656
580
|
});
|
|
657
581
|
}
|
|
658
582
|
|
|
583
|
+
// src/transform/blockAnalyzer.ts
|
|
584
|
+
function contentsToPlainText(contents) {
|
|
585
|
+
return contents.map((node) => extractPlainText(node)).join("\n");
|
|
586
|
+
}
|
|
587
|
+
var PROSE_NODE_TYPES = /* @__PURE__ */ new Set(["paragraph", "blockquote", "heading"]);
|
|
588
|
+
function maskNonProse(stripped, contents) {
|
|
589
|
+
let masked = stripped;
|
|
590
|
+
let cursor = 0;
|
|
591
|
+
for (const node of contents) {
|
|
592
|
+
const text3 = stripMarkdown(extractPlainText(node));
|
|
593
|
+
if (!text3) continue;
|
|
594
|
+
const at = stripped.indexOf(text3, cursor);
|
|
595
|
+
if (at < 0) continue;
|
|
596
|
+
cursor = at + text3.length;
|
|
597
|
+
if (PROSE_NODE_TYPES.has(node.type)) continue;
|
|
598
|
+
masked = masked.slice(0, at) + text3.replace(/\S/g, " ") + masked.slice(cursor);
|
|
599
|
+
}
|
|
600
|
+
return masked;
|
|
601
|
+
}
|
|
602
|
+
function extractBlockImages(block, blockIndex) {
|
|
603
|
+
return extractImages(block.contents).map(({ src, alt }) => ({
|
|
604
|
+
src,
|
|
605
|
+
alt,
|
|
606
|
+
sourceBlockIndex: blockIndex
|
|
607
|
+
}));
|
|
608
|
+
}
|
|
609
|
+
function extractDocImages(blocks) {
|
|
610
|
+
const seen = /* @__PURE__ */ new Set();
|
|
611
|
+
const images = [];
|
|
612
|
+
function walk(blockList) {
|
|
613
|
+
for (const block of blockList) {
|
|
614
|
+
for (const img of extractImages(block.contents)) {
|
|
615
|
+
if (!seen.has(img.src)) {
|
|
616
|
+
seen.add(img.src);
|
|
617
|
+
images.push({ src: img.src, alt: img.alt });
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
if (block.children) {
|
|
621
|
+
walk(block.children);
|
|
622
|
+
}
|
|
623
|
+
}
|
|
624
|
+
}
|
|
625
|
+
walk(blocks);
|
|
626
|
+
return images;
|
|
627
|
+
}
|
|
628
|
+
function collectLeafBlocks(blocks) {
|
|
629
|
+
const result = [];
|
|
630
|
+
for (const block of blocks) {
|
|
631
|
+
if (block.children && block.children.length > 0) {
|
|
632
|
+
if (!block.contents || block.contents.length === 0) {
|
|
633
|
+
result.push(block);
|
|
634
|
+
}
|
|
635
|
+
result.push(...collectLeafBlocks(block.children));
|
|
636
|
+
} else {
|
|
637
|
+
result.push(block);
|
|
638
|
+
}
|
|
639
|
+
}
|
|
640
|
+
return result;
|
|
641
|
+
}
|
|
642
|
+
function analyzeBlocks(blocks, options) {
|
|
643
|
+
return analyzeFlatBlocks(collectLeafBlocks(blocks), options);
|
|
644
|
+
}
|
|
645
|
+
function analyzeFlatBlocks(flat, options) {
|
|
646
|
+
const results = [];
|
|
647
|
+
for (const block of flat) {
|
|
648
|
+
let plainText = "";
|
|
649
|
+
if (block.contents && block.contents.length > 0) {
|
|
650
|
+
plainText = contentsToPlainText(block.contents);
|
|
651
|
+
} else if (block.title) {
|
|
652
|
+
plainText = block.title;
|
|
653
|
+
}
|
|
654
|
+
const stripped = stripMarkdown(plainText);
|
|
655
|
+
const bodyWordCount = stripped.split(/\s+/).filter((w) => w.length > 0).length;
|
|
656
|
+
const extraction = extractContent(
|
|
657
|
+
block.contents && block.contents.length > 0 ? maskNonProse(stripped, block.contents) : stripped,
|
|
658
|
+
options
|
|
659
|
+
);
|
|
660
|
+
const images = extractBlockImages(block, results.length);
|
|
661
|
+
results.push({
|
|
662
|
+
block,
|
|
663
|
+
plainText: stripped,
|
|
664
|
+
extractions: extraction.elements,
|
|
665
|
+
hasChildren: !!(block.children && block.children.length > 0),
|
|
666
|
+
bodyWordCount,
|
|
667
|
+
images
|
|
668
|
+
});
|
|
669
|
+
}
|
|
670
|
+
return results;
|
|
671
|
+
}
|
|
672
|
+
|
|
659
673
|
// src/transform/timingAllocator.ts
|
|
660
674
|
var MIN_BLOCK_DURATION = 3;
|
|
661
675
|
var MAX_BLOCK_DURATION = 20;
|
|
@@ -1708,7 +1722,23 @@ function videoBlock(video, index, segmentId, duration) {
|
|
|
1708
1722
|
}
|
|
1709
1723
|
|
|
1710
1724
|
// src/transform/presentationMedia.ts
|
|
1725
|
+
function presentationBlockImages(doc) {
|
|
1726
|
+
const own = /* @__PURE__ */ new Map();
|
|
1727
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1728
|
+
const claim = (block) => [
|
|
1729
|
+
...extractImages(block.contents).filter((image) => {
|
|
1730
|
+
if (seen.has(image.src)) return false;
|
|
1731
|
+
seen.add(image.src);
|
|
1732
|
+
return true;
|
|
1733
|
+
}),
|
|
1734
|
+
...(block.children ?? []).flatMap(claim)
|
|
1735
|
+
];
|
|
1736
|
+
for (const block of [...flattenRenderableBlocks(doc.blocks)].reverse())
|
|
1737
|
+
own.set(block.id, claim(block));
|
|
1738
|
+
return own;
|
|
1739
|
+
}
|
|
1711
1740
|
function mediaKind(node) {
|
|
1741
|
+
if (node.type === "table") return { kind: "table", continuous: true };
|
|
1712
1742
|
if (node.type === "code")
|
|
1713
1743
|
return isPictureFence(node) || isExplicitDiagramLang(node.lang) || isExplicitTreeLang(node.lang) || isExplicitTimelineLang(node.lang) ? { kind: "diagram", continuous: true } : null;
|
|
1714
1744
|
if (isStandaloneHtmlVideo(node)) return { kind: "video", continuous: true };
|
|
@@ -1719,17 +1749,39 @@ function mediaKind(node) {
|
|
|
1719
1749
|
function collectPresentationMedia(doc, sectionIds) {
|
|
1720
1750
|
const sections = new Set(sectionIds);
|
|
1721
1751
|
const result = /* @__PURE__ */ new Map();
|
|
1752
|
+
const pictures = presentationBlockImages(doc);
|
|
1722
1753
|
let owner = sectionIds[0];
|
|
1723
1754
|
for (const block of flattenRenderableBlocks(doc.blocks)) {
|
|
1724
1755
|
if (sections.has(block.id)) owner = block.id;
|
|
1725
1756
|
if (!owner) continue;
|
|
1757
|
+
const items = result.get(owner) ?? [];
|
|
1758
|
+
const count = (kind) => items.filter((item) => item.kind === kind).length;
|
|
1726
1759
|
for (const node of block.contents ?? []) {
|
|
1727
1760
|
const kind = mediaKind(node);
|
|
1728
1761
|
if (!kind) continue;
|
|
1729
|
-
const
|
|
1730
|
-
items.push({ ...kind, ref
|
|
1731
|
-
result.set(owner, items);
|
|
1762
|
+
const ref = kind.kind === "table" ? `${owner}#table-${count("table")}` : `${owner}#${count("video") + count("diagram")}`;
|
|
1763
|
+
items.push({ ...kind, ref, node });
|
|
1732
1764
|
}
|
|
1765
|
+
for (const { src, alt, width, height } of pictures.get(block.id) ?? [])
|
|
1766
|
+
items.push({
|
|
1767
|
+
kind: "image",
|
|
1768
|
+
continuous: true,
|
|
1769
|
+
ref: `${owner}#image-${count("image")}`,
|
|
1770
|
+
src,
|
|
1771
|
+
node: {
|
|
1772
|
+
type: "paragraph",
|
|
1773
|
+
children: [
|
|
1774
|
+
{
|
|
1775
|
+
type: "image",
|
|
1776
|
+
url: src,
|
|
1777
|
+
alt,
|
|
1778
|
+
...width !== void 0 ? { width } : {},
|
|
1779
|
+
...height !== void 0 ? { height } : {}
|
|
1780
|
+
}
|
|
1781
|
+
]
|
|
1782
|
+
}
|
|
1783
|
+
});
|
|
1784
|
+
if (items.length) result.set(owner, items);
|
|
1733
1785
|
}
|
|
1734
1786
|
return result;
|
|
1735
1787
|
}
|
|
@@ -1759,7 +1811,23 @@ function presentationMediaNode(item, elapsed) {
|
|
|
1759
1811
|
// src/transform/presentationPlanner.ts
|
|
1760
1812
|
var SENTENCE_END = /[.!?][”"')\]]?$/u;
|
|
1761
1813
|
var SENTENCE_START = /^[\p{Lu}\p{N}"“‘'([]/u;
|
|
1762
|
-
var SEQUENCE_CUE = /^(?:(?:first|second|third|fourth)(?:ly)?|then|next|after that|
|
|
1814
|
+
var SEQUENCE_CUE = /^(?:(?:first|second|third|fourth)(?:ly)?|then|next|after\b|afterwards|at that point|eventually|finally|lastly)\b|^\d+[.)]\s/iu;
|
|
1815
|
+
var ORDINAL_OPENER = /^(?:(?:first|second|third|fourth)(?:ly)?|then|next|after that|afterwards|finally|lastly),?\s+/iu;
|
|
1816
|
+
function stagePoint(sentence) {
|
|
1817
|
+
const rest = sentence.replace(ORDINAL_OPENER, "");
|
|
1818
|
+
return pointText(rest === sentence ? sentence : `${rest[0].toUpperCase()}${rest.slice(1)}`);
|
|
1819
|
+
}
|
|
1820
|
+
var CYCLE_CUE = /\b(?:cycles?|cyclical|repeats?|over and over|again and again|loop)\b/iu;
|
|
1821
|
+
var DEFINES = /^(?:the |an? )?((?:[\p{Lu}][\p{L}'’-]*|[\p{Ll}][\p{L}'’-]*)(?: [\p{L}'’()-]+){0,3}?) (?:is|are|was|were) (?:an? |the )?(.{12,})$/u;
|
|
1822
|
+
var PRONOUN = /^(?:it|its|this|that|these|those|there|they|their|he|his|she|her|we|our|you|your|i|my|which|one|each|some|to|so|but|and|or)$/iu;
|
|
1823
|
+
function labelledPoint(sentence) {
|
|
1824
|
+
const match = sentence.replace(/\s+/gu, " ").trim().match(DEFINES);
|
|
1825
|
+
if (match && !PRONOUN.test(match[1].split(" ")[0])) {
|
|
1826
|
+
const label = match[1];
|
|
1827
|
+
return clip(`${label[0].toUpperCase()}${label.slice(1)}: ${match[2]}`, Infinity, 160);
|
|
1828
|
+
}
|
|
1829
|
+
return clip(sentence, Infinity, 160);
|
|
1830
|
+
}
|
|
1763
1831
|
function clip(text3, words, characters) {
|
|
1764
1832
|
const clean = text3.replace(/\s+/gu, " ").trim();
|
|
1765
1833
|
const parts = clean.split(" ");
|
|
@@ -1774,6 +1842,17 @@ function clip(text3, words, characters) {
|
|
|
1774
1842
|
var headlineText = (text3) => clip(text3, 12, 110);
|
|
1775
1843
|
var pointText = (text3) => clip(text3, Infinity, 180);
|
|
1776
1844
|
var captionText = (text3) => clip(text3, 18, 110);
|
|
1845
|
+
var FEATURE_POINTS = 2;
|
|
1846
|
+
var FEATURE_POINT_CHARACTERS = 100;
|
|
1847
|
+
function featurePointText(text3) {
|
|
1848
|
+
const clean = text3.replace(/\s+/gu, " ").trim();
|
|
1849
|
+
if (clean.length <= FEATURE_POINT_CHARACTERS) return clean;
|
|
1850
|
+
const head = clean.slice(0, FEATURE_POINT_CHARACTERS);
|
|
1851
|
+
const clause = Math.max(
|
|
1852
|
+
...[",", ";", ":", " \u2013", " \u2014", " - "].map((mark) => head.lastIndexOf(mark))
|
|
1853
|
+
);
|
|
1854
|
+
return clause > FEATURE_POINT_CHARACTERS * 0.4 ? clean.slice(0, clause).replace(/[\s,;:–—-]+$/u, "") + "\u2026" : clip(clean, Infinity, FEATURE_POINT_CHARACTERS);
|
|
1855
|
+
}
|
|
1777
1856
|
function sentencesIn(sourceText, start, end) {
|
|
1778
1857
|
const passage = sourceText.slice(start, end);
|
|
1779
1858
|
const sentences = [];
|
|
@@ -1816,24 +1895,31 @@ function chunkWords(words, limit) {
|
|
|
1816
1895
|
}
|
|
1817
1896
|
return chunks;
|
|
1818
1897
|
}
|
|
1819
|
-
function
|
|
1820
|
-
const
|
|
1821
|
-
|
|
1822
|
-
const
|
|
1823
|
-
|
|
1824
|
-
|
|
1825
|
-
|
|
1826
|
-
|
|
1827
|
-
|
|
1828
|
-
|
|
1829
|
-
|
|
1830
|
-
|
|
1831
|
-
|
|
1832
|
-
|
|
1833
|
-
|
|
1834
|
-
|
|
1898
|
+
function chunkPassage(words, limit, feature) {
|
|
1899
|
+
const prose = words.flatMap((word, index) => feature(word) ? [] : [index]);
|
|
1900
|
+
if (!prose.length) return words.length ? [[0, words.length]] : [];
|
|
1901
|
+
const chunks = chunkWords(
|
|
1902
|
+
prose.map((index) => words[index]),
|
|
1903
|
+
limit
|
|
1904
|
+
).map(([first, last]) => [prose[first], prose[last - 1] + 1]);
|
|
1905
|
+
chunks[0][0] = 0;
|
|
1906
|
+
chunks.forEach((chunk, index) => {
|
|
1907
|
+
chunk[1] = chunks[index + 1]?.[0] ?? words.length;
|
|
1908
|
+
});
|
|
1909
|
+
return chunks;
|
|
1910
|
+
}
|
|
1911
|
+
function featureSpans(sourceText, from, to, items) {
|
|
1912
|
+
const spans = [];
|
|
1913
|
+
let cursor = from;
|
|
1914
|
+
for (const item of items) {
|
|
1915
|
+
if (item.kind === "image") continue;
|
|
1916
|
+
const text3 = extractPlainText(item.node).trim();
|
|
1917
|
+
const at = text3 ? sourceText.indexOf(text3, cursor) : -1;
|
|
1918
|
+
if (at < 0 || at + text3.length > to) continue;
|
|
1919
|
+
spans.push([at, at + text3.length]);
|
|
1920
|
+
cursor = at + text3.length;
|
|
1835
1921
|
}
|
|
1836
|
-
return
|
|
1922
|
+
return spans;
|
|
1837
1923
|
}
|
|
1838
1924
|
function field(block, key) {
|
|
1839
1925
|
if (!block || typeof block !== "object") return void 0;
|
|
@@ -1848,7 +1934,7 @@ function createPresentationPlan(doc, hints = defaultPresentationHints()) {
|
|
|
1848
1934
|
throw new Error(
|
|
1849
1935
|
"This document is too long for one presentation. Split it into shorter documents."
|
|
1850
1936
|
);
|
|
1851
|
-
const
|
|
1937
|
+
const sectionMedia = collectPresentationMedia(
|
|
1852
1938
|
doc,
|
|
1853
1939
|
script.blocks.map((block) => block.blockId)
|
|
1854
1940
|
);
|
|
@@ -1881,8 +1967,22 @@ function createPresentationPlan(doc, hints = defaultPresentationHints()) {
|
|
|
1881
1967
|
continue;
|
|
1882
1968
|
}
|
|
1883
1969
|
const preferred = hint?.layout;
|
|
1884
|
-
const
|
|
1885
|
-
const
|
|
1970
|
+
const media = sectionMedia.get(block.blockId) ?? [];
|
|
1971
|
+
const spans = featureSpans(script.sourceText, block.charStart, block.charEnd, media);
|
|
1972
|
+
const inFeature = (from, to) => spans.some(([s, e]) => from < e && to > s);
|
|
1973
|
+
const chunks = chunkPassage(
|
|
1974
|
+
words,
|
|
1975
|
+
wordLimit,
|
|
1976
|
+
(word) => inFeature(block.charStart + word.index, block.charStart + word.index + word[0].length)
|
|
1977
|
+
);
|
|
1978
|
+
for (let k = chunks.length - 1; k > 0; k--) {
|
|
1979
|
+
const opening = script.sourceText.slice(block.charStart + words[chunks[k][0]].index);
|
|
1980
|
+
if (SEQUENCE_CUE.test(opening) && chunks[k][1] - chunks[k - 1][0] <= wordLimit * 2) {
|
|
1981
|
+
chunks[k - 1][1] = chunks[k][1];
|
|
1982
|
+
chunks.splice(k, 1);
|
|
1983
|
+
}
|
|
1984
|
+
}
|
|
1985
|
+
const images = media.filter((item) => item.kind === "image");
|
|
1886
1986
|
const opensDeck = beats.length === 0;
|
|
1887
1987
|
for (const [chunk, [first, last]] of chunks.entries()) {
|
|
1888
1988
|
const fromEnd = chunks.length - 1 - chunk;
|
|
@@ -1890,8 +1990,18 @@ function createPresentationPlan(doc, hints = defaultPresentationHints()) {
|
|
|
1890
1990
|
const start = block.charStart + words[first].index;
|
|
1891
1991
|
const end = block.charStart + words[last - 1].index + words[last - 1][0].length;
|
|
1892
1992
|
const passage = script.sourceText.slice(start, end);
|
|
1993
|
+
let prose = "";
|
|
1994
|
+
let cursor = start;
|
|
1995
|
+
for (const [s, e] of spans) {
|
|
1996
|
+
if (e <= cursor || s >= end) continue;
|
|
1997
|
+
prose += script.sourceText.slice(cursor, Math.max(cursor, s)) + "\n";
|
|
1998
|
+
cursor = Math.min(end, e);
|
|
1999
|
+
}
|
|
2000
|
+
prose = (prose + script.sourceText.slice(cursor, end)).trim() || passage;
|
|
1893
2001
|
const index = beats.length;
|
|
1894
|
-
const sentences = sentencesIn(script.sourceText, start, end)
|
|
2002
|
+
const sentences = sentencesIn(script.sourceText, start, end).filter(
|
|
2003
|
+
(sentence) => !inFeature(sentence.start, sentence.end)
|
|
2004
|
+
);
|
|
1895
2005
|
const body = sentences.filter((sentence) => sentence.start >= headingEnd);
|
|
1896
2006
|
let headline;
|
|
1897
2007
|
let shown;
|
|
@@ -1899,12 +2009,12 @@ function createPresentationPlan(doc, hints = defaultPresentationHints()) {
|
|
|
1899
2009
|
headline = headlineText(heading);
|
|
1900
2010
|
shown = body.filter((sentence) => comparable(sentence.text) !== comparable(heading));
|
|
1901
2011
|
} else {
|
|
1902
|
-
const
|
|
2012
|
+
const suggested2 = transformNarratedSegment(
|
|
1903
2013
|
{
|
|
1904
2014
|
articleId: doc.articleId,
|
|
1905
2015
|
segmentId: `presentation-${index}`,
|
|
1906
2016
|
title: "",
|
|
1907
|
-
text:
|
|
2017
|
+
text: prose,
|
|
1908
2018
|
duration: Math.max(4, (last - first) / 2.5),
|
|
1909
2019
|
audioSrc: "",
|
|
1910
2020
|
includeTitleBlock: false
|
|
@@ -1912,23 +2022,24 @@ function createPresentationPlan(doc, hints = defaultPresentationHints()) {
|
|
|
1912
2022
|
{ slidesPerMinute: 4, minConfidence: 0.25, style: "documentary" }
|
|
1913
2023
|
).doc.blocks[0];
|
|
1914
2024
|
headline = headlineText(
|
|
1915
|
-
field(
|
|
2025
|
+
field(suggested2, "fact") ?? field(suggested2, "description") ?? field(suggested2, "quote") ?? sentences[0]?.text ?? prose
|
|
1916
2026
|
);
|
|
1917
|
-
if (!headline) headline = headlineText(
|
|
2027
|
+
if (!headline) headline = headlineText(prose);
|
|
1918
2028
|
shown = body.filter((sentence) => !repeats(sentence.text, headline));
|
|
1919
2029
|
}
|
|
1920
2030
|
const steps = shown.filter((sentence) => SEQUENCE_CUE.test(sentence.text));
|
|
1921
2031
|
const sequence = steps.length >= 2;
|
|
1922
2032
|
const textPoints = shown.slice(0, 3).map((item) => pointText(item.text));
|
|
1923
|
-
const stepPoints = sequence ? steps.slice(0,
|
|
2033
|
+
const stepPoints = sequence ? steps.slice(0, 4).map((item) => stagePoint(item.text)) : textPoints;
|
|
2034
|
+
const cardPoints = shown.slice(0, 4).map((item) => labelledPoint(item.text));
|
|
1924
2035
|
const custom = chunk === 0 ? wording : void 0;
|
|
1925
2036
|
if (custom?.headline) headline = custom.headline;
|
|
1926
|
-
const pointsFor = (layout2) => custom?.points ?? (layout2 === "
|
|
2037
|
+
const pointsFor = (layout2) => custom?.points ?? (["steps", "process", "cycle"].includes(layout2) ? stepPoints : layout2 === "cards" ? cardPoints : textPoints);
|
|
1927
2038
|
const quote = custom?.headline ? void 0 : body.find((sentence) => sentence.text.length <= 110)?.text;
|
|
1928
2039
|
const headlineFor = (layout2) => layout2 === "quote" && quote ? quote : headline;
|
|
1929
|
-
let layout = index === 0 ? "title" : textPoints.length >= 2
|
|
1930
|
-
if (sequence) layout = "steps";
|
|
1931
|
-
else if (textPoints.length === 2 && /\b(versus|whereas|compared|instead)\b/iu.test(
|
|
2040
|
+
let layout = index === 0 ? "title" : textPoints.length >= 2 ? "cards" : "statement";
|
|
2041
|
+
if (sequence) layout = steps.length >= 3 && CYCLE_CUE.test(prose) ? "cycle" : "steps";
|
|
2042
|
+
else if (textPoints.length === 2 && /\b(versus|whereas|compared|instead)\b/iu.test(prose))
|
|
1932
2043
|
layout = "comparison";
|
|
1933
2044
|
if (image) layout = "image";
|
|
1934
2045
|
if (preferred && (preferred === "image" ? !!image : presentationLayoutFits(
|
|
@@ -1945,7 +2056,7 @@ function createPresentationPlan(doc, hints = defaultPresentationHints()) {
|
|
|
1945
2056
|
layout,
|
|
1946
2057
|
headline: headlineFor(layout),
|
|
1947
2058
|
points: pointsFor(layout),
|
|
1948
|
-
...layout === "image" && image ? { imageSrc: image.src } : {}
|
|
2059
|
+
...layout === "image" && image?.src ? { imageSrc: image.src } : {}
|
|
1949
2060
|
};
|
|
1950
2061
|
if (quote) quotes.set(beat.id, quote);
|
|
1951
2062
|
let split;
|
|
@@ -1997,28 +2108,6 @@ function createPresentationPlan(doc, hints = defaultPresentationHints()) {
|
|
|
1997
2108
|
))
|
|
1998
2109
|
beat.hidden = true;
|
|
1999
2110
|
}
|
|
2000
|
-
const sectionMedia = collectPresentationMedia(
|
|
2001
|
-
doc,
|
|
2002
|
-
script.blocks.map((block) => block.blockId)
|
|
2003
|
-
);
|
|
2004
|
-
for (const block of script.blocks) {
|
|
2005
|
-
const items = sectionMedia.get(block.blockId);
|
|
2006
|
-
if (!items?.length) continue;
|
|
2007
|
-
const own = beats.filter(
|
|
2008
|
-
(beat) => beat.sourceStart >= block.charStart && beat.sourceStart < block.charEnd && beat.layout !== AUTHORED_PRESENTATION_LAYOUT && !beat.hidden
|
|
2009
|
-
);
|
|
2010
|
-
if (!own.length) continue;
|
|
2011
|
-
const open = own.filter((beat) => beat !== beats[0] && beat.layout !== "image");
|
|
2012
|
-
const slots = open.length ? open : own;
|
|
2013
|
-
const shown = /* @__PURE__ */ new Set();
|
|
2014
|
-
slots.forEach((beat, slot) => {
|
|
2015
|
-
const from = Math.floor(slot * items.length / slots.length);
|
|
2016
|
-
const to = Math.max(from + 1, Math.floor((slot + 1) * items.length / slots.length));
|
|
2017
|
-
const refs = items.slice(from, to).filter((item) => item.continuous || !shown.has(item.ref)).slice(0, PRESENTATION_LIMITS.mediaPerBeat).map((item) => item.ref);
|
|
2018
|
-
for (const ref of refs) shown.add(ref);
|
|
2019
|
-
if (refs.length) beat.media = refs;
|
|
2020
|
-
});
|
|
2021
|
-
}
|
|
2022
2111
|
const closing = beats[beats.length - 1];
|
|
2023
2112
|
if (beats.length > 2 && closing?.layout === "statement" && !hints.blocks.some(
|
|
2024
2113
|
(hint) => hint.layout && script.blocks.some(
|
|
@@ -2026,19 +2115,24 @@ function createPresentationPlan(doc, hints = defaultPresentationHints()) {
|
|
|
2026
2115
|
)
|
|
2027
2116
|
))
|
|
2028
2117
|
closing.layout = "title";
|
|
2118
|
+
const sectionOf = (beat) => script.blocks.find(
|
|
2119
|
+
(block) => beat.sourceStart >= block.charStart && beat.sourceStart < block.charEnd
|
|
2120
|
+
);
|
|
2121
|
+
const hintFor = (beat) => hints.blocks.find((item) => item.blockId === sectionOf(beat)?.blockId);
|
|
2122
|
+
const worded = /* @__PURE__ */ new Set();
|
|
2123
|
+
const suggested = /* @__PURE__ */ new Set();
|
|
2029
2124
|
let origin = "automatic";
|
|
2030
2125
|
for (const beat of beats) {
|
|
2031
2126
|
if (beat.layout === AUTHORED_PRESENTATION_LAYOUT) continue;
|
|
2032
|
-
const section =
|
|
2033
|
-
|
|
2034
|
-
);
|
|
2035
|
-
const hint = hints.blocks.find((item) => item.blockId === section?.blockId);
|
|
2127
|
+
const section = sectionOf(beat);
|
|
2128
|
+
const hint = hintFor(beat);
|
|
2036
2129
|
const passage = script.sourceText.slice(beat.sourceStart, beat.sourceEnd);
|
|
2037
2130
|
const cached = hint?.ai?.find((item) => item.source === passage);
|
|
2038
2131
|
if (cached && (cached.layout !== "image" || beat.imageSrc)) {
|
|
2039
2132
|
beat.headline = cached.headline;
|
|
2040
2133
|
beat.points = [...cached.points];
|
|
2041
2134
|
beat.layout = cached.layout;
|
|
2135
|
+
suggested.add(beat.id);
|
|
2042
2136
|
origin = "ai";
|
|
2043
2137
|
}
|
|
2044
2138
|
const preferred = hint?.layout === AUTHORED_PRESENTATION_LAYOUT ? void 0 : hint?.layout;
|
|
@@ -2052,15 +2146,101 @@ function createPresentationPlan(doc, hints = defaultPresentationHints()) {
|
|
|
2052
2146
|
if (section && beat.sourceStart === section.charStart && hint?.wording?.source === script.sourceText.slice(section.charStart, section.charEnd)) {
|
|
2053
2147
|
beat.headline = hint.wording.headline ?? beat.headline;
|
|
2054
2148
|
beat.points = hint.wording.points ?? beat.points;
|
|
2149
|
+
if (hint.wording.points) worded.add(beat.id);
|
|
2055
2150
|
}
|
|
2056
|
-
if (beat.media && beat.layout === "steps" && preferred !== "steps") beat.layout = "list";
|
|
2057
2151
|
if (beat.layout !== "image" && !presentationLayoutFits(beat.layout, beat.headline, beat.points, passage))
|
|
2058
2152
|
beat.layout = "statement";
|
|
2059
2153
|
if (beat.layout !== "image") delete beat.imageSrc;
|
|
2060
2154
|
}
|
|
2155
|
+
const fullBleed = new Set(beats.flatMap((beat) => beat.imageSrc ? [beat.imageSrc] : []));
|
|
2156
|
+
for (const block of script.blocks) {
|
|
2157
|
+
const items = (sectionMedia.get(block.blockId) ?? []).filter(
|
|
2158
|
+
(item) => item.kind !== "image" || !fullBleed.has(item.src)
|
|
2159
|
+
);
|
|
2160
|
+
if (!items.length) continue;
|
|
2161
|
+
const own = beats.filter(
|
|
2162
|
+
(beat) => beat.sourceStart >= block.charStart && beat.sourceStart < block.charEnd && beat.layout !== AUTHORED_PRESENTATION_LAYOUT && !beat.hidden
|
|
2163
|
+
);
|
|
2164
|
+
if (!own.length) continue;
|
|
2165
|
+
const open = own.filter((beat) => beat !== beats[0] && beat.layout !== "image");
|
|
2166
|
+
const slots = open.length ? open : own;
|
|
2167
|
+
const shown = /* @__PURE__ */ new Set();
|
|
2168
|
+
slots.forEach((beat, slot) => {
|
|
2169
|
+
const from = Math.floor(slot * items.length / slots.length);
|
|
2170
|
+
const to = Math.max(from + 1, Math.floor((slot + 1) * items.length / slots.length));
|
|
2171
|
+
const refs = items.slice(from, to).filter((item) => item.continuous || !shown.has(item.ref)).slice(0, PRESENTATION_LIMITS.mediaPerBeat).map((item) => item.ref);
|
|
2172
|
+
for (const ref of refs) shown.add(ref);
|
|
2173
|
+
if (refs.length) beat.media = refs;
|
|
2174
|
+
});
|
|
2175
|
+
}
|
|
2176
|
+
for (const beat of beats) {
|
|
2177
|
+
if (!beat.media) continue;
|
|
2178
|
+
if (!worded.has(beat.id)) {
|
|
2179
|
+
const points = beat.points.slice(0, FEATURE_POINTS);
|
|
2180
|
+
beat.points = suggested.has(beat.id) ? points : points.map(featurePointText);
|
|
2181
|
+
}
|
|
2182
|
+
if (beat.layout === "steps" && hintFor(beat)?.layout !== "steps") beat.layout = "list";
|
|
2183
|
+
const passage = script.sourceText.slice(beat.sourceStart, beat.sourceEnd);
|
|
2184
|
+
if (!presentationLayoutFits(
|
|
2185
|
+
beat.layout,
|
|
2186
|
+
beat.headline,
|
|
2187
|
+
beat.points,
|
|
2188
|
+
passage
|
|
2189
|
+
))
|
|
2190
|
+
beat.layout = "statement";
|
|
2191
|
+
}
|
|
2061
2192
|
return { version: 1, sourceText: script.sourceText, origin, beats };
|
|
2062
2193
|
}
|
|
2063
2194
|
|
|
2195
|
+
// src/transform/conceptReveal.ts
|
|
2196
|
+
var CONCEPT_TEMPLATES = ["cards", "process", "cycle", "layers"];
|
|
2197
|
+
function conceptTemplate(template) {
|
|
2198
|
+
return CONCEPT_TEMPLATES.find((name) => name === template);
|
|
2199
|
+
}
|
|
2200
|
+
function stems(text3) {
|
|
2201
|
+
return (text3.toLowerCase().match(/[\p{L}\p{N}][\p{L}\p{N}'-]*/gu) ?? []).filter((word) => word.length >= 4).map((word) => word.slice(0, 5));
|
|
2202
|
+
}
|
|
2203
|
+
function matchPointsToSentences(points, sourceText, start, end) {
|
|
2204
|
+
const passage = sourceText.slice(start, end);
|
|
2205
|
+
const sentences = [...passage.matchAll(/[^.!?\n]+[.!?]*/gu)].map((match) => ({
|
|
2206
|
+
offset: start + match.index,
|
|
2207
|
+
words: new Set(stems(match[0]))
|
|
2208
|
+
}));
|
|
2209
|
+
let last = -1;
|
|
2210
|
+
const offsets = [];
|
|
2211
|
+
for (const point of points) {
|
|
2212
|
+
const words = stems(point);
|
|
2213
|
+
let best;
|
|
2214
|
+
for (const sentence of sentences) {
|
|
2215
|
+
const shared = words.filter((word) => sentence.words.has(word)).length;
|
|
2216
|
+
if (shared > (best?.shared ?? 1)) best = { offset: sentence.offset, shared };
|
|
2217
|
+
}
|
|
2218
|
+
if (!best || best.offset < last) return void 0;
|
|
2219
|
+
offsets.push(last = best.offset);
|
|
2220
|
+
}
|
|
2221
|
+
return offsets;
|
|
2222
|
+
}
|
|
2223
|
+
function conceptBuildSpec() {
|
|
2224
|
+
return { mode: "items", effect: "fadeInUp", previous: "keep" };
|
|
2225
|
+
}
|
|
2226
|
+
function conceptRevealTiming(template, items, itemTimes, start, end) {
|
|
2227
|
+
const build = conceptBuildSpec();
|
|
2228
|
+
const steps = conceptBuildStepTexts(template, items);
|
|
2229
|
+
if (!itemTimes || itemTimes.length !== steps.length) return { build };
|
|
2230
|
+
const lead = Math.min(0.5, (end - start) / 8);
|
|
2231
|
+
const starts = itemTimes.map(
|
|
2232
|
+
(time) => Math.min(Math.max(0, end - start - 0.1), Math.max(lead, time - start))
|
|
2233
|
+
);
|
|
2234
|
+
starts[0] = Math.min(starts[0], Math.max(lead, Math.min(1, (end - start) / 6)));
|
|
2235
|
+
return {
|
|
2236
|
+
build,
|
|
2237
|
+
buildTiming: {
|
|
2238
|
+
sourceText: buildSourceText(steps.map((text3, index) => ({ id: `build-${index}`, text: text3 }))),
|
|
2239
|
+
starts
|
|
2240
|
+
}
|
|
2241
|
+
};
|
|
2242
|
+
}
|
|
2243
|
+
|
|
2064
2244
|
// src/transform/presentationCompiler.ts
|
|
2065
2245
|
function compilePresentationPlan(doc, plan) {
|
|
2066
2246
|
const checked = parsePresentationPlan(plan);
|
|
@@ -2102,7 +2282,7 @@ function compilePresentationPlan(doc, plan) {
|
|
|
2102
2282
|
);
|
|
2103
2283
|
if (checked.beats.some((beat) => beat.media?.some((ref) => !media.has(ref))))
|
|
2104
2284
|
throw new Error(
|
|
2105
|
-
"A presentation video or diagram is no longer in the document. Make the presentation again."
|
|
2285
|
+
"A presentation table, picture, video or diagram is no longer in the document. Make the presentation again."
|
|
2106
2286
|
);
|
|
2107
2287
|
const duration = narration ? narration.startTime + narration.duration : Math.max(4, script.tokens.length / 2.5);
|
|
2108
2288
|
const bookmarks = (narration?.bookmarks ?? []).filter(
|
|
@@ -2129,20 +2309,16 @@ function compilePresentationPlan(doc, plan) {
|
|
|
2129
2309
|
const share = span > 0 ? (tokensBefore(offset) - tokensBefore(from.charOffset)) / span : 0;
|
|
2130
2310
|
return from.time + share * Math.max(0, to.time - from.time);
|
|
2131
2311
|
};
|
|
2312
|
+
const timeAt = (offset) => {
|
|
2313
|
+
const marked = markedTime(offset);
|
|
2314
|
+
if (marked !== void 0) return marked;
|
|
2315
|
+
if (narration && bookmarks.length) return wordTimeAt(bookmarks, offset, "next") ?? duration;
|
|
2316
|
+
return (narration?.startTime ?? 0) + tokensBefore(offset) / Math.max(1, script.tokens.length) * (narration?.duration ?? duration);
|
|
2317
|
+
};
|
|
2132
2318
|
let previous = 0;
|
|
2133
2319
|
const starts = checked.beats.map((beat, index) => {
|
|
2134
2320
|
if (!index) return 0;
|
|
2135
|
-
|
|
2136
|
-
const marked = markedTime(beat.sourceStart);
|
|
2137
|
-
if (marked !== void 0) {
|
|
2138
|
-
time = marked;
|
|
2139
|
-
} else if (narration && bookmarks.length) {
|
|
2140
|
-
time = bookmarks.find((word) => word.charOffset >= beat.sourceStart)?.time ?? duration;
|
|
2141
|
-
} else {
|
|
2142
|
-
const before = script.tokens.filter((token) => token.charOffset < beat.sourceStart).length;
|
|
2143
|
-
time = (narration?.startTime ?? 0) + before / Math.max(1, script.tokens.length) * (narration?.duration ?? duration);
|
|
2144
|
-
}
|
|
2145
|
-
previous = Math.min(duration, Math.max(previous, time));
|
|
2321
|
+
previous = Math.min(duration, Math.max(previous, timeAt(beat.sourceStart)));
|
|
2146
2322
|
return previous;
|
|
2147
2323
|
});
|
|
2148
2324
|
const authoredBlocks = flattenRenderableBlocks(doc.blocks);
|
|
@@ -2229,8 +2405,16 @@ function compilePresentationPlan(doc, plan) {
|
|
|
2229
2405
|
});
|
|
2230
2406
|
continue;
|
|
2231
2407
|
}
|
|
2232
|
-
const
|
|
2233
|
-
|
|
2408
|
+
const table = beat.layout !== "image" && (beat.media ?? []).some((ref) => media.get(ref).kind === "table");
|
|
2409
|
+
const block = table ? tableFeatureBlock(beat, beat.layout, base, contents) : visualBlock(beat, beat.layout, base, index);
|
|
2410
|
+
const concept = conceptTemplate(block.template);
|
|
2411
|
+
blocks.push({
|
|
2412
|
+
...block,
|
|
2413
|
+
...contents.length ? { summaryMode: "brief" } : {},
|
|
2414
|
+
// A concept slide reveals its ideas one at a time, each as the narration
|
|
2415
|
+
// reaches the sentence it summarizes (`conceptReveal`).
|
|
2416
|
+
...concept && !build ? conceptReveal(concept, beat, start, end, script.sourceText, timeAt) : {}
|
|
2417
|
+
});
|
|
2234
2418
|
}
|
|
2235
2419
|
return {
|
|
2236
2420
|
...doc,
|
|
@@ -2242,6 +2426,34 @@ function compilePresentationPlan(doc, plan) {
|
|
|
2242
2426
|
audio: doc.audio.segments.some((segment) => segment.src) ? doc.audio : { segments: [{ src: "", name: "presentation-clock", startTime: 0, duration }] }
|
|
2243
2427
|
};
|
|
2244
2428
|
}
|
|
2429
|
+
function conceptReveal(template, beat, start, end, sourceText, timeAt) {
|
|
2430
|
+
const steps = conceptBuildStepTexts(template, beat.points);
|
|
2431
|
+
const offsets = matchPointsToSentences(
|
|
2432
|
+
beat.points.slice(0, steps.length),
|
|
2433
|
+
sourceText,
|
|
2434
|
+
beat.sourceStart,
|
|
2435
|
+
beat.sourceEnd
|
|
2436
|
+
);
|
|
2437
|
+
return conceptRevealTiming(template, beat.points, offsets?.map(timeAt), start, end);
|
|
2438
|
+
}
|
|
2439
|
+
function tableFeatureBlock(beat, layout, base, features) {
|
|
2440
|
+
const paragraph = (text3) => ({
|
|
2441
|
+
type: "paragraph",
|
|
2442
|
+
children: [{ type: "text", value: text3 }]
|
|
2443
|
+
});
|
|
2444
|
+
const words = ["list", "steps", "comparison"].includes(layout) && beat.points.length > 1 ? [
|
|
2445
|
+
{
|
|
2446
|
+
type: "list",
|
|
2447
|
+
ordered: false,
|
|
2448
|
+
children: beat.points.map((point) => ({
|
|
2449
|
+
type: "listItem",
|
|
2450
|
+
children: [paragraph(point)]
|
|
2451
|
+
}))
|
|
2452
|
+
}
|
|
2453
|
+
] : beat.points.map(paragraph);
|
|
2454
|
+
const block = { ...base, template: "bottomFeature", title: beat.headline };
|
|
2455
|
+
return Object.assign(block, { contents: [...words, ...features] });
|
|
2456
|
+
}
|
|
2245
2457
|
function visualBlock(beat, layout, base, index) {
|
|
2246
2458
|
const colorScheme = ["blue", "green", "orange"][index % 3];
|
|
2247
2459
|
switch (layout) {
|
|
@@ -2250,52 +2462,40 @@ function visualBlock(beat, layout, base, index) {
|
|
|
2250
2462
|
case "divider":
|
|
2251
2463
|
return { ...base, template: "sectionHeader", title: beat.headline, colorScheme };
|
|
2252
2464
|
case "emphasis":
|
|
2253
|
-
return { ...base, template: "
|
|
2465
|
+
return { ...base, template: "cards", items: [beat.headline] };
|
|
2254
2466
|
case "quote":
|
|
2255
2467
|
return { ...base, template: "fullBleedQuote", text: beat.headline, colorScheme };
|
|
2256
2468
|
case "stat": {
|
|
2257
2469
|
const { match, line, index: at } = presentationStat(beat.headline, beat.points);
|
|
2258
|
-
const
|
|
2470
|
+
const others = beat.points.filter((_, i) => i !== at);
|
|
2471
|
+
const [description, detail] = at < 0 ? others : [line, others[0]];
|
|
2259
2472
|
return {
|
|
2260
2473
|
...base,
|
|
2261
2474
|
template: "statHighlight",
|
|
2475
|
+
headline: beat.headline,
|
|
2262
2476
|
stat: match.value,
|
|
2263
|
-
description:
|
|
2264
|
-
...detail ? { detail } : {}
|
|
2265
|
-
colorScheme
|
|
2477
|
+
description: description ?? "",
|
|
2478
|
+
...detail ? { detail } : {}
|
|
2266
2479
|
};
|
|
2267
2480
|
}
|
|
2268
2481
|
case "definition":
|
|
2269
2482
|
return {
|
|
2270
2483
|
...base,
|
|
2271
|
-
template: "
|
|
2272
|
-
|
|
2273
|
-
|
|
2274
|
-
colorScheme
|
|
2484
|
+
template: "cards",
|
|
2485
|
+
title: beat.headline,
|
|
2486
|
+
items: beat.points.length ? [beat.points.map((point) => point.trim().replace(/([^.!?])$/u, "$1.")).join(" ")] : []
|
|
2275
2487
|
};
|
|
2276
2488
|
case "statement":
|
|
2277
|
-
return {
|
|
2278
|
-
...base,
|
|
2279
|
-
template: "factCard",
|
|
2280
|
-
fact: beat.headline,
|
|
2281
|
-
explanation: beat.points.join("\n\n")
|
|
2282
|
-
};
|
|
2489
|
+
return beat.points.length ? { ...base, template: "cards", title: beat.headline, items: beat.points } : { ...base, template: "factCard", fact: beat.headline, explanation: "" };
|
|
2283
2490
|
case "list":
|
|
2491
|
+
return { ...base, template: "cards", title: beat.headline, items: beat.points };
|
|
2492
|
+
case "comparison":
|
|
2284
2493
|
return {
|
|
2285
2494
|
...base,
|
|
2286
|
-
template: "
|
|
2495
|
+
template: "cards",
|
|
2287
2496
|
title: beat.headline,
|
|
2288
2497
|
items: beat.points,
|
|
2289
|
-
|
|
2290
|
-
colorScheme
|
|
2291
|
-
};
|
|
2292
|
-
case "comparison":
|
|
2293
|
-
return {
|
|
2294
|
-
...base,
|
|
2295
|
-
template: "twoColumn",
|
|
2296
|
-
header: beat.headline,
|
|
2297
|
-
left: { label: beat.points[0] },
|
|
2298
|
-
right: { label: beat.points[1] }
|
|
2498
|
+
...beat.points.length === 2 ? { versus: true } : {}
|
|
2299
2499
|
};
|
|
2300
2500
|
case "image":
|
|
2301
2501
|
return {
|
|
@@ -2308,22 +2508,14 @@ function visualBlock(beat, layout, base, index) {
|
|
|
2308
2508
|
ambientMotion: "zoomIn"
|
|
2309
2509
|
};
|
|
2310
2510
|
case "steps":
|
|
2311
|
-
|
|
2312
|
-
|
|
2313
|
-
|
|
2314
|
-
|
|
2315
|
-
|
|
2316
|
-
|
|
2317
|
-
|
|
2318
|
-
|
|
2319
|
-
label: point,
|
|
2320
|
-
x: i * 320,
|
|
2321
|
-
y: 0,
|
|
2322
|
-
w: 260,
|
|
2323
|
-
h: 180
|
|
2324
|
-
})),
|
|
2325
|
-
edges: beat.points.slice(1).map((_, i) => ({ source: `step-${i}`, target: `step-${i + 1}`, directed: true }))
|
|
2326
|
-
};
|
|
2511
|
+
case "process":
|
|
2512
|
+
return { ...base, template: "process", title: beat.headline, items: beat.points };
|
|
2513
|
+
case "cards":
|
|
2514
|
+
return { ...base, template: "cards", title: beat.headline, items: beat.points };
|
|
2515
|
+
case "cycle":
|
|
2516
|
+
return { ...base, template: "cycle", title: beat.headline, items: beat.points };
|
|
2517
|
+
case "layers":
|
|
2518
|
+
return { ...base, template: "layers", title: beat.headline, items: beat.points };
|
|
2327
2519
|
}
|
|
2328
2520
|
}
|
|
2329
2521
|
function applySavedPresentation(doc) {
|
|
@@ -2371,18 +2563,23 @@ export {
|
|
|
2371
2563
|
serializePresentationHints,
|
|
2372
2564
|
presentationSections,
|
|
2373
2565
|
usesDynamicPresentation,
|
|
2374
|
-
extractDocImages,
|
|
2375
|
-
analyzeBlocks,
|
|
2376
2566
|
DEFAULT_TRANSFORM_STYLE_ID,
|
|
2377
2567
|
createTransformStyleRegistry,
|
|
2378
2568
|
resolveTransformStyle,
|
|
2379
2569
|
getTransformStyleIds,
|
|
2380
2570
|
getTransformStyleSummaries,
|
|
2571
|
+
extractDocImages,
|
|
2572
|
+
analyzeBlocks,
|
|
2381
2573
|
allocateTiming,
|
|
2382
2574
|
applyTransform,
|
|
2383
2575
|
transformNarratedSegment,
|
|
2384
2576
|
collectPresentationMedia,
|
|
2385
2577
|
createPresentationPlan,
|
|
2578
|
+
CONCEPT_TEMPLATES,
|
|
2579
|
+
conceptTemplate,
|
|
2580
|
+
matchPointsToSentences,
|
|
2581
|
+
conceptBuildSpec,
|
|
2582
|
+
conceptRevealTiming,
|
|
2386
2583
|
compilePresentationPlan,
|
|
2387
2584
|
applySavedPresentation
|
|
2388
2585
|
};
|