@bendyline/squisq 2.13.12 → 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/{chunk-QJQQO3K4.js → chunk-CTT4XAGO.js} +193 -6
- package/dist/chunk-FCB6JLWB.js +125 -0
- package/dist/chunk-L4AFSOT3.js +26 -0
- package/dist/{chunk-CQSCYJQU.js → chunk-OIQ2GO2O.js} +1 -1
- package/dist/{chunk-IFZZI4XY.js → chunk-TCGGJUKC.js} +2 -2
- package/dist/{chunk-OUSEEUEC.js → chunk-YQQWE4HG.js} +65 -40
- package/dist/doc/index.d.ts +104 -3
- package/dist/doc/index.js +16 -3
- package/dist/index.d.ts +4 -4
- package/dist/index.js +60 -4
- package/dist/{materializePageSection-bLfqIy6j.d.ts → materializePageSection-CWxou4mX.d.ts} +61 -1
- package/dist/narration/index.js +2 -2
- package/dist/timing/index.d.ts +57 -1
- package/dist/timing/index.js +9 -1
- package/dist/transform/index.d.ts +154 -62
- package/dist/transform/index.js +39 -2
- package/package.json +1 -1
|
@@ -5900,6 +5900,12 @@ function formatChartValue(value, unit) {
|
|
|
5900
5900
|
}
|
|
5901
5901
|
return unit ? `${text} ${unit}` : text;
|
|
5902
5902
|
}
|
|
5903
|
+
function formatChartDataValue(value, unit) {
|
|
5904
|
+
if (!Number.isFinite(value) || Math.abs(value) >= 1e4) return formatChartValue(value, unit);
|
|
5905
|
+
const sign = value < 0 ? "-" : "";
|
|
5906
|
+
const text = `${sign}${Math.abs(value).toLocaleString("en-US", { maximumFractionDigits: 2 })}`;
|
|
5907
|
+
return unit ? `${text} ${unit}` : text;
|
|
5908
|
+
}
|
|
5903
5909
|
function trimTrailingZero(text) {
|
|
5904
5910
|
return text.replace(/\.0$/, "");
|
|
5905
5911
|
}
|
|
@@ -6425,7 +6431,7 @@ function paintBars(paint) {
|
|
|
6425
6431
|
} = paint;
|
|
6426
6432
|
const scale = valueTicks("bar", data, input);
|
|
6427
6433
|
const labelGap = axisFontSize * 0.6;
|
|
6428
|
-
const labelWidthPx = (value) =>
|
|
6434
|
+
const labelWidthPx = (value) => formatChartDataValue(value, input.unit).length * axisFontSize * CHART_CHAR_WIDTH_EM;
|
|
6429
6435
|
const showValueLabels = input.showValues === true && !(input.stacked === true);
|
|
6430
6436
|
const reservedForLabels = showValueLabels ? data.series.flatMap((series) => series.values).filter((v) => v !== null).reduce((max, v) => Math.max(max, labelWidthPx(v)), 0) + labelGap : 0;
|
|
6431
6437
|
const axisWidth = Math.max(20, frame2.plot.width - reservedForLabels);
|
|
@@ -6768,7 +6774,7 @@ function valueLabel(paint, id, value, x, y, animation) {
|
|
|
6768
6774
|
type: "text",
|
|
6769
6775
|
id,
|
|
6770
6776
|
content: {
|
|
6771
|
-
text:
|
|
6777
|
+
text: formatChartDataValue(value, paint.input.unit),
|
|
6772
6778
|
style: {
|
|
6773
6779
|
fontSize: paint.axisFontSize,
|
|
6774
6780
|
fontFamily: paint.bodyFont,
|
|
@@ -11954,6 +11960,177 @@ var TEMPLATE_AUTHORING_METADATA = {
|
|
|
11954
11960
|
}
|
|
11955
11961
|
};
|
|
11956
11962
|
|
|
11963
|
+
// src/doc/templates/authoringGuide.ts
|
|
11964
|
+
var CHART = {
|
|
11965
|
+
fields: "title, headers [label, value], rows [[label, value], \u2026] 2\u20136 rows, showValues: true",
|
|
11966
|
+
use: "only values the {source} states"
|
|
11967
|
+
};
|
|
11968
|
+
var CONCEPT_BUILD = 'build: {mode: "items"} to reveal them as they are spoken';
|
|
11969
|
+
var TEMPLATE_GUIDES = {
|
|
11970
|
+
imageWithCaption: {
|
|
11971
|
+
fields: "imageSrc, imageAlt, caption \u2264 90 chars, ambientMotion: zoomIn|zoomOut|panLeft|panRight",
|
|
11972
|
+
use: "a photograph with the narration's idea as caption"
|
|
11973
|
+
},
|
|
11974
|
+
pullQuote: {
|
|
11975
|
+
fields: "text, backgroundImage {src, alt}, ambientMotion",
|
|
11976
|
+
use: "a VERBATIM sentence or clause from the {source} over a photograph"
|
|
11977
|
+
},
|
|
11978
|
+
fullBleedQuote: {
|
|
11979
|
+
fields: "text",
|
|
11980
|
+
use: "a VERBATIM short sentence from the {source}, text only"
|
|
11981
|
+
},
|
|
11982
|
+
factCard: {
|
|
11983
|
+
fields: "fact \u2264 4 words, explanation \u2264 160 chars, accentImage {src, alt, position: left-strip|right-strip}"
|
|
11984
|
+
},
|
|
11985
|
+
dateEvent: {
|
|
11986
|
+
fields: "date, description \u2264 160 chars, mood: neutral|somber|celebratory, accentImage optional",
|
|
11987
|
+
use: "a year or period the {source} states"
|
|
11988
|
+
},
|
|
11989
|
+
statHighlight: {
|
|
11990
|
+
fields: 'stat like "98%" or "36", description \u2264 60 chars, detail \u2264 80 chars, accentImage optional',
|
|
11991
|
+
use: "counts up; the number must be in the {source}"
|
|
11992
|
+
},
|
|
11993
|
+
comparisonBar: {
|
|
11994
|
+
fields: "leftLabel, leftValue, rightLabel, rightValue, unit optional",
|
|
11995
|
+
use: "two quantities the {source} gives in the same unit"
|
|
11996
|
+
},
|
|
11997
|
+
columnChart: CHART,
|
|
11998
|
+
barChart: CHART,
|
|
11999
|
+
pieChart: CHART,
|
|
12000
|
+
timeline: {
|
|
12001
|
+
fields: "title, tracks: [{ id, label, events: [{ id, label: year, position: 0..1 in date order, description \u2264 40 chars, marker: 'diamond' optional }] }]",
|
|
12002
|
+
use: "3\u20137 dated events the {source} states"
|
|
12003
|
+
},
|
|
12004
|
+
diagram: {
|
|
12005
|
+
fields: "title, nodeShape: 'pill', nodes: [{ id, label \u2264 3 words, x, y }] 3\u20136 nodes with x in {0, 280, 560} and y in {0, 160, 320}, edges: [{ source, target, label \u2264 3 words }]",
|
|
12006
|
+
use: "relationships the {source} describes"
|
|
12007
|
+
},
|
|
12008
|
+
list: {
|
|
12009
|
+
fields: "title, items: 2\u20135 short items taken from the {source}'s own words"
|
|
12010
|
+
},
|
|
12011
|
+
photoGrid: {
|
|
12012
|
+
fields: "images: [{src, alt}] \xD7 2\u20134, caption \u2264 90 chars",
|
|
12013
|
+
use: "several related photographs"
|
|
12014
|
+
},
|
|
12015
|
+
map: {
|
|
12016
|
+
fields: "center: {lat, lng}, zoom, mapStyle: 'terrain', title, ambientMotion",
|
|
12017
|
+
use: "where the place is, at its first mention"
|
|
12018
|
+
},
|
|
12019
|
+
sectionHeader: {
|
|
12020
|
+
fields: "title = the section title, imageSrc, imageAlt, ambientMotion",
|
|
12021
|
+
use: "opens a section on a photograph"
|
|
12022
|
+
},
|
|
12023
|
+
cards: {
|
|
12024
|
+
fields: `title, items: ["Label: detail", \u2026] 2\u20134, versus: true for exactly two set against each other, ${CONCEPT_BUILD}`,
|
|
12025
|
+
use: "parallel ideas side by side, such as kinds, causes or features"
|
|
12026
|
+
},
|
|
12027
|
+
process: {
|
|
12028
|
+
fields: `title, items: ["Label: detail", \u2026] 2\u20134 in order, ${CONCEPT_BUILD}`,
|
|
12029
|
+
use: "ordered stages or dated events, drawn as numbered steps joined by arrows"
|
|
12030
|
+
},
|
|
12031
|
+
cycle: {
|
|
12032
|
+
fields: `title, items: ["Label: detail", \u2026] 3\u20135, ${CONCEPT_BUILD}`,
|
|
12033
|
+
use: "stages that repeat in a loop, drawn on a ring"
|
|
12034
|
+
},
|
|
12035
|
+
layers: {
|
|
12036
|
+
fields: `title, items: ["Label: detail", \u2026] 3\u20135 innermost first, ${CONCEPT_BUILD}`,
|
|
12037
|
+
use: "parts nested inside one another, drawn as concentric rings"
|
|
12038
|
+
}
|
|
12039
|
+
};
|
|
12040
|
+
function guideText(text, wording = {}) {
|
|
12041
|
+
return text.replace(/\{source\}/gu, wording.source ?? "source");
|
|
12042
|
+
}
|
|
12043
|
+
function templateCatalog(entries, wording = {}) {
|
|
12044
|
+
return entries.map((entry) => {
|
|
12045
|
+
const [name, guide] = typeof entry === "string" ? [entry, TEMPLATE_GUIDES[entry]] : "templates" in entry ? [entry.templates.join(" | "), TEMPLATE_GUIDES[entry.templates[0]]] : [entry.template, entry];
|
|
12046
|
+
const use = guide.use ? ` \u2014 ${guide.use}` : "";
|
|
12047
|
+
return guideText(`- ${name} (${guide.fields})${use}`, wording);
|
|
12048
|
+
});
|
|
12049
|
+
}
|
|
12050
|
+
var PRESENTATION_LAYOUT_GUIDE = {
|
|
12051
|
+
title: "an opening",
|
|
12052
|
+
divider: "a new topic begins; only the headline shows",
|
|
12053
|
+
statement: "one idea",
|
|
12054
|
+
emphasis: "one striking takeaway; only the headline shows, so keep it under 60 characters",
|
|
12055
|
+
quote: "the headline is one sentence copied word for word from the passage, without quotation marks; only the headline shows",
|
|
12056
|
+
stat: "the passage\u2019s key figure, such as 40% or $3 million, stated in the headline or a point",
|
|
12057
|
+
definition: "the headline is a term and the points define it",
|
|
12058
|
+
list: "2\u20133 points",
|
|
12059
|
+
comparison: "exactly 2 genuinely contrasting points",
|
|
12060
|
+
steps: "2\u20133 ordered actions",
|
|
12061
|
+
image: 'only when the current layout is "image"; the headline is then its caption',
|
|
12062
|
+
cards: "2\u20134 parallel ideas side by side, such as kinds, causes or features",
|
|
12063
|
+
process: "2\u20134 ordered stages or dated events, drawn as numbered steps joined by arrows",
|
|
12064
|
+
cycle: "3\u20135 stages that repeat in a loop, drawn on a ring",
|
|
12065
|
+
layers: "3\u20135 parts nested inside one another, innermost first, such as a core and the layers around it, drawn as concentric rings"
|
|
12066
|
+
};
|
|
12067
|
+
var DRAWN = ["cards", "process", "cycle", "layers"];
|
|
12068
|
+
var NUMBERS = ["statHighlight", "comparisonBar", "columnChart", "barChart", "pieChart"];
|
|
12069
|
+
var ACCENTED = [
|
|
12070
|
+
"factCard",
|
|
12071
|
+
"statHighlight",
|
|
12072
|
+
"dateEvent",
|
|
12073
|
+
"quote",
|
|
12074
|
+
"list",
|
|
12075
|
+
"definitionCard"
|
|
12076
|
+
];
|
|
12077
|
+
var AUTHORING_TRICKS = [
|
|
12078
|
+
{
|
|
12079
|
+
id: "one-idea",
|
|
12080
|
+
text: "One idea per slide: each slide shows what the sentence being spoken is about. Captions and labels restate its idea briefly; they never add a fact."
|
|
12081
|
+
},
|
|
12082
|
+
{
|
|
12083
|
+
id: "numbers-as-numbers",
|
|
12084
|
+
text: "Draw numbers as numbers: one striking quantity \u2192 statHighlight (it counts up); two quantities in one unit \u2192 comparisonBar; three or more values in one unit \u2192 a chart; three to seven dated events \u2192 timeline.",
|
|
12085
|
+
templates: [...NUMBERS, "timeline"]
|
|
12086
|
+
},
|
|
12087
|
+
{
|
|
12088
|
+
id: "ranges",
|
|
12089
|
+
text: 'A range ("3 to 7 miles deep") is not one value: never chart it as a single bar or count up to one end. Write it as the {source} gives it, on a card or in a list.',
|
|
12090
|
+
templates: NUMBERS
|
|
12091
|
+
},
|
|
12092
|
+
{
|
|
12093
|
+
id: "draw-ideas",
|
|
12094
|
+
text: `Slides that draw their ideas explain best: several parallel ideas \u2192 cards, ordered stages \u2192 process, a loop that comes back to its start \u2192 cycle, parts nested inside one another \u2192 layers; prefer them to a list. Write each item as "Label: detail", a label of 1\u20134 words and a detail of at most 70 characters, in the {source}'s own words. For dated events, start each label with its date: a process of dated stages draws as a timeline.`,
|
|
12095
|
+
summary: `Slides that draw their ideas explain best: when the passage has several parallel ideas, ordered stages, a repeating loop or nested parts, choose ${DRAWN.map((layout) => `"${layout}"`).join(", ")} rather than "statement" or "list", and write each of their points (up to 4) as "Label: detail", a label of 1\u20134 words and a detail of at most 70 characters. For dated events, start each label with its date.`,
|
|
12096
|
+
templates: DRAWN
|
|
12097
|
+
},
|
|
12098
|
+
{
|
|
12099
|
+
id: "reveal-as-spoken",
|
|
12100
|
+
text: 'A drawn slide can reveal its items one at a time as the narration says them (build {mode: "items"}). Put the items in the order they are spoken and let each share its key words with the sentence that says it, so every reveal lands on its words.',
|
|
12101
|
+
templates: DRAWN
|
|
12102
|
+
},
|
|
12103
|
+
{
|
|
12104
|
+
id: "chain-not-diagram",
|
|
12105
|
+
text: "A chain \u2014 one thing leads to the next, which leads to the next \u2014 is a process, not a diagram. Keep diagrams for relationships that branch or meet: causes on the left and effects on the right, every edge labelled with the verb the {source} uses, and no edges that cross.",
|
|
12106
|
+
templates: ["diagram", "process"]
|
|
12107
|
+
},
|
|
12108
|
+
{
|
|
12109
|
+
id: "no-empty-frames",
|
|
12110
|
+
text: "A text card on its own is small type on an empty field. Give each factCard, statHighlight, dateEvent, quote, list and definitionCard an accentImage showing what its sentence is about, and alternate the side from card to card.",
|
|
12111
|
+
templates: ACCENTED
|
|
12112
|
+
},
|
|
12113
|
+
{
|
|
12114
|
+
id: "motion-follows-picture",
|
|
12115
|
+
text: "Motion comes from the theme's motion profile; never hand-author animations. On a photograph, choose the ambientMotion the picture suggests \u2014 zoomIn toward a detail, panLeft or panRight along a skyline or coast \u2014 and alternate directions between neighbouring slides.",
|
|
12116
|
+
templates: ["imageWithCaption", "pullQuote", "sectionHeader", "map"]
|
|
12117
|
+
},
|
|
12118
|
+
{
|
|
12119
|
+
id: "vary",
|
|
12120
|
+
text: "Vary the slides: when two templates fit equally well, choose the one not just shown. A long run of photo-and-caption slides is where a narrated story feels static."
|
|
12121
|
+
}
|
|
12122
|
+
];
|
|
12123
|
+
function authoringTricks(options = {}) {
|
|
12124
|
+
const offered = options.templates ? new Set(options.templates) : void 0;
|
|
12125
|
+
const selected = options.ids ? options.ids.flatMap((id) => AUTHORING_TRICKS.filter((trick) => trick.id === id)) : AUTHORING_TRICKS;
|
|
12126
|
+
return selected.filter(
|
|
12127
|
+
(trick) => !offered || !trick.templates || trick.templates.some((template) => offered.has(template))
|
|
12128
|
+
).flatMap((trick) => {
|
|
12129
|
+
const text = options.surface === "summary" ? trick.summary : trick.text;
|
|
12130
|
+
return text ? [guideText(text, options.wording)] : [];
|
|
12131
|
+
});
|
|
12132
|
+
}
|
|
12133
|
+
|
|
11957
12134
|
// src/doc/templates/coverBlock.ts
|
|
11958
12135
|
var COVER_TITLE_BASE_PX = 120;
|
|
11959
12136
|
var COVER_TITLE_COMFORTABLE_CHARS = 26;
|
|
@@ -12566,9 +12743,9 @@ function expandDocBlocks(blocks, options = {}) {
|
|
|
12566
12743
|
return expandedBlock;
|
|
12567
12744
|
});
|
|
12568
12745
|
}
|
|
12569
|
-
const
|
|
12570
|
-
|
|
12571
|
-
|
|
12746
|
+
const buildSegments = new Set(
|
|
12747
|
+
blocks.filter(hasBlockBuilds).map((block) => block.audioSegment ?? 0)
|
|
12748
|
+
);
|
|
12572
12749
|
const blocksBySegment = /* @__PURE__ */ new Map();
|
|
12573
12750
|
blocks.forEach((block, index) => {
|
|
12574
12751
|
const segmentIndex = block.audioSegment ?? 0;
|
|
@@ -12581,6 +12758,8 @@ function expandDocBlocks(blocks, options = {}) {
|
|
|
12581
12758
|
const splitParts = /* @__PURE__ */ new Map();
|
|
12582
12759
|
for (const [segmentIndex, segmentBlocks] of blocksBySegment) {
|
|
12583
12760
|
const audioSegment = audioSegments[segmentIndex];
|
|
12761
|
+
const splitLongBlocks = requestedSplit && !buildSegments.has(segmentIndex);
|
|
12762
|
+
const mergeShortBlocks = requestedMerge && !buildSegments.has(segmentIndex);
|
|
12584
12763
|
if (segmentBlocks.every(({ block }) => block.timelineLocked === true)) {
|
|
12585
12764
|
for (const { block, originalIndex } of segmentBlocks) {
|
|
12586
12765
|
const expanded = materializeScheduledBlock(
|
|
@@ -12797,7 +12976,9 @@ function expandDocBlocks(blocks, options = {}) {
|
|
|
12797
12976
|
}
|
|
12798
12977
|
expandedBlocks.splice(0, expandedBlocks.length, ...sequenced);
|
|
12799
12978
|
}
|
|
12800
|
-
return
|
|
12979
|
+
return requestedMerge ? expandedBlocks.filter(
|
|
12980
|
+
(block) => block && (block.duration > 0 || buildSegments.has(block.audioSegment ?? 0))
|
|
12981
|
+
) : expandedBlocks.filter(Boolean);
|
|
12801
12982
|
}
|
|
12802
12983
|
function getAvailableTemplates() {
|
|
12803
12984
|
return Object.keys(templateRegistry);
|
|
@@ -14133,6 +14314,12 @@ export {
|
|
|
14133
14314
|
materializeBlockLayersWithRuntime,
|
|
14134
14315
|
TEMPLATE_METADATA,
|
|
14135
14316
|
TEMPLATE_AUTHORING_METADATA,
|
|
14317
|
+
TEMPLATE_GUIDES,
|
|
14318
|
+
guideText,
|
|
14319
|
+
templateCatalog,
|
|
14320
|
+
PRESENTATION_LAYOUT_GUIDE,
|
|
14321
|
+
AUTHORING_TRICKS,
|
|
14322
|
+
authoringTricks,
|
|
14136
14323
|
coverBlock,
|
|
14137
14324
|
expandCoverBlock,
|
|
14138
14325
|
expandDocBlocks,
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
// src/transform/grounding.ts
|
|
2
|
+
var NUMBER = /\d[\d,]*(?:\.\d+)?/gu;
|
|
3
|
+
function normalizeGroundingText(text) {
|
|
4
|
+
return text.toLowerCase().replace(/[‘’ʼ`]/gu, "'").replace(/[“”]/gu, '"').replace(/[–—]/gu, "-").replace(/[^a-z0-9'%\s-]/gu, " ").replace(/\s+/gu, " ").trim();
|
|
5
|
+
}
|
|
6
|
+
function numbersIn(value) {
|
|
7
|
+
if (typeof value === "number") return Number.isFinite(value) ? [String(value)] : [];
|
|
8
|
+
return (value.match(NUMBER) ?? []).map((number) => number.replace(/,/gu, ""));
|
|
9
|
+
}
|
|
10
|
+
var UNIT_WORDS = ["one", "two", "three", "four", "five", "six", "seven", "eight", "nine"];
|
|
11
|
+
var TEEN_WORDS = [
|
|
12
|
+
"ten",
|
|
13
|
+
"eleven",
|
|
14
|
+
"twelve",
|
|
15
|
+
"thirteen",
|
|
16
|
+
"fourteen",
|
|
17
|
+
"fifteen",
|
|
18
|
+
"sixteen",
|
|
19
|
+
"seventeen",
|
|
20
|
+
"eighteen",
|
|
21
|
+
"nineteen"
|
|
22
|
+
];
|
|
23
|
+
var TENS_WORDS = ["twenty", "thirty", "forty", "fifty", "sixty", "seventy", "eighty", "ninety"];
|
|
24
|
+
var NUMBER_WORD = new RegExp(
|
|
25
|
+
`\\b(?:(${TENS_WORDS.join("|")})(?:[-\\s](${UNIT_WORDS.join("|")}))?|(${[...UNIT_WORDS.slice(1), ...TEEN_WORDS].join("|")}))\\b`,
|
|
26
|
+
"giu"
|
|
27
|
+
);
|
|
28
|
+
function spelledNumbers(source) {
|
|
29
|
+
return [...source.matchAll(NUMBER_WORD)].map((match) => {
|
|
30
|
+
const [, tens, unit, small] = match;
|
|
31
|
+
const value = small ? [...UNIT_WORDS, ...TEEN_WORDS].indexOf(small.toLowerCase()) + 1 : (TENS_WORDS.indexOf(tens.toLowerCase()) + 2) * 10 + (unit ? UNIT_WORDS.indexOf(unit.toLowerCase()) + 1 : 0);
|
|
32
|
+
return { value: String(value), index: match.index };
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
function sourceNumbers(source) {
|
|
36
|
+
return /* @__PURE__ */ new Set([...numbersIn(source), ...spelledNumbers(source).map(({ value }) => value)]);
|
|
37
|
+
}
|
|
38
|
+
function ungroundedNumber(values, source) {
|
|
39
|
+
const stated = typeof source === "string" ? sourceNumbers(source) : source;
|
|
40
|
+
const list = Array.isArray(values) ? values : [values];
|
|
41
|
+
for (const value of list) {
|
|
42
|
+
const missing = numbersIn(value).find((number) => !stated.has(number));
|
|
43
|
+
if (missing !== void 0) return missing;
|
|
44
|
+
}
|
|
45
|
+
return void 0;
|
|
46
|
+
}
|
|
47
|
+
function groundingSentences(text) {
|
|
48
|
+
return (text.match(/[^.!?\n]+[.!?]*/gu) ?? []).map((sentence) => sentence.trim()).filter(Boolean);
|
|
49
|
+
}
|
|
50
|
+
function isVerbatimQuote(quote, source, options = {}) {
|
|
51
|
+
const q = normalizeGroundingText(quote);
|
|
52
|
+
if (!q) return false;
|
|
53
|
+
if (normalizeGroundingText(source).includes(q)) return true;
|
|
54
|
+
const words = q.split(" ").filter((word) => word.length > 1);
|
|
55
|
+
if (words.length === 0) return false;
|
|
56
|
+
const minShare = options.minShare ?? 0.9;
|
|
57
|
+
for (const sentence of options.sentences ?? groundingSentences(source)) {
|
|
58
|
+
const present = new Set(normalizeGroundingText(sentence).split(" "));
|
|
59
|
+
const hits = words.filter((word) => present.has(word)).length;
|
|
60
|
+
if (hits / words.length >= minShare) return true;
|
|
61
|
+
}
|
|
62
|
+
return false;
|
|
63
|
+
}
|
|
64
|
+
var COMMON = new Set(
|
|
65
|
+
"about after also because been before being both could does each from have into just like more most much only other over same should some such than that their them then there these they this those through very what when where which while will with would your ours".split(
|
|
66
|
+
" "
|
|
67
|
+
)
|
|
68
|
+
);
|
|
69
|
+
function distinctiveStems(text) {
|
|
70
|
+
return (text.toLowerCase().replace(/[‘’]/gu, "'").replace(/[“”]/gu, '"').replace(/[‐‑]/gu, "-").replace(/[‒–—―]/gu, " ").match(/[\p{L}\p{N}][\p{L}\p{N}'-]*/gu) ?? []).filter((word) => word.length >= 4 && !COMMON.has(word)).map((word) => word.slice(0, 4));
|
|
71
|
+
}
|
|
72
|
+
function isGroundedParaphrase(text, source, options = {}) {
|
|
73
|
+
if (ungroundedNumber(text, source) !== void 0) return false;
|
|
74
|
+
const stems = new Set(distinctiveStems(source));
|
|
75
|
+
const words = distinctiveStems(text);
|
|
76
|
+
const minShare = options.minShare ?? 2 / 3;
|
|
77
|
+
return words.filter((word) => stems.has(word)).length >= Math.ceil(words.length * minShare);
|
|
78
|
+
}
|
|
79
|
+
function restatesHeadline(point, headline) {
|
|
80
|
+
const title = distinctiveStems(headline);
|
|
81
|
+
const words = distinctiveStems(point);
|
|
82
|
+
const shared = words.filter((word) => title.includes(word)).length;
|
|
83
|
+
return words.length > 0 && shared >= words.length * 0.6 || title.length >= 3 && title.every((word) => words.includes(word));
|
|
84
|
+
}
|
|
85
|
+
var RANGE = /(?:\bbetween\s+(\d[\d,]*(?:\.\d+)?)\s+and\s+(\d[\d,]*(?:\.\d+)?))|(?:(\d[\d,]*(?:\.\d+)?)\s*(?:[-–—]|\bto\b)\s*(\d[\d,]*(?:\.\d+)?))/giu;
|
|
86
|
+
function statedRanges(source) {
|
|
87
|
+
return [...source.matchAll(RANGE)].map((match) => ({
|
|
88
|
+
low: (match[1] ?? match[3]).replace(/,/gu, ""),
|
|
89
|
+
high: (match[2] ?? match[4]).replace(/,/gu, ""),
|
|
90
|
+
text: match[0],
|
|
91
|
+
index: match.index
|
|
92
|
+
}));
|
|
93
|
+
}
|
|
94
|
+
function rangeEndOnly(value, source) {
|
|
95
|
+
const [number] = numbersIn(value);
|
|
96
|
+
if (number === void 0) return void 0;
|
|
97
|
+
const ranges = statedRanges(source);
|
|
98
|
+
if (ranges.length === 0) return void 0;
|
|
99
|
+
if (spelledNumbers(source).some((spelled) => spelled.value === number)) return void 0;
|
|
100
|
+
let first;
|
|
101
|
+
for (const match of source.matchAll(NUMBER)) {
|
|
102
|
+
if (match[0].replace(/,/gu, "") !== number) continue;
|
|
103
|
+
const range = ranges.find(
|
|
104
|
+
(candidate) => match.index >= candidate.index && match.index < candidate.index + candidate.text.length
|
|
105
|
+
);
|
|
106
|
+
if (!range) return void 0;
|
|
107
|
+
first ?? (first = range);
|
|
108
|
+
}
|
|
109
|
+
return first;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
export {
|
|
113
|
+
normalizeGroundingText,
|
|
114
|
+
numbersIn,
|
|
115
|
+
spelledNumbers,
|
|
116
|
+
sourceNumbers,
|
|
117
|
+
ungroundedNumber,
|
|
118
|
+
groundingSentences,
|
|
119
|
+
isVerbatimQuote,
|
|
120
|
+
distinctiveStems,
|
|
121
|
+
isGroundedParaphrase,
|
|
122
|
+
restatesHeadline,
|
|
123
|
+
statedRanges,
|
|
124
|
+
rangeEndOnly
|
|
125
|
+
};
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
// src/timing/wordTiming.ts
|
|
2
|
+
function wordTimeAt(bookmarks, offset, snap = "containing") {
|
|
3
|
+
if (snap === "next") return bookmarks.find((word) => word.charOffset >= offset)?.time;
|
|
4
|
+
let best = bookmarks[0];
|
|
5
|
+
for (const bookmark of bookmarks) {
|
|
6
|
+
if (bookmark.charOffset <= offset) best = bookmark;
|
|
7
|
+
else break;
|
|
8
|
+
}
|
|
9
|
+
return best?.time;
|
|
10
|
+
}
|
|
11
|
+
function phraseOffset(timeline, phrase, from = 0) {
|
|
12
|
+
return phrase ? timeline.sourceText.indexOf(phrase, from) : -1;
|
|
13
|
+
}
|
|
14
|
+
function phraseStartTime(timeline, phrase, options = {}) {
|
|
15
|
+
const offset = phraseOffset(timeline, phrase, options.from ?? 0);
|
|
16
|
+
if (offset < 0) return null;
|
|
17
|
+
const time = wordTimeAt(timeline.bookmarks, offset, options.snap);
|
|
18
|
+
if (time === void 0) return null;
|
|
19
|
+
return Math.max(0, time - (options.lead ?? 0));
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export {
|
|
23
|
+
wordTimeAt,
|
|
24
|
+
phraseOffset,
|
|
25
|
+
phraseStartTime
|
|
26
|
+
};
|
|
@@ -3,7 +3,7 @@ import {
|
|
|
3
3
|
} from "./chunk-HKHOFTGM.js";
|
|
4
4
|
import {
|
|
5
5
|
applySavedPresentation
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-YQQWE4HG.js";
|
|
7
7
|
import {
|
|
8
8
|
SHAPE_NAMES,
|
|
9
9
|
buildChartData,
|
|
@@ -32,7 +32,7 @@ import {
|
|
|
32
32
|
templateRegistry,
|
|
33
33
|
writeCustomTemplatesToFrontmatter,
|
|
34
34
|
writeCustomThemesToFrontmatter
|
|
35
|
-
} from "./chunk-
|
|
35
|
+
} from "./chunk-CTT4XAGO.js";
|
|
36
36
|
import {
|
|
37
37
|
ASCII_TREE_VOCAB,
|
|
38
38
|
ASCII_VOCAB,
|
|
@@ -6,7 +6,7 @@ import {
|
|
|
6
6
|
flattenRenderableBlocks,
|
|
7
7
|
getPinnedBlockMeta,
|
|
8
8
|
markdownToDoc
|
|
9
|
-
} from "./chunk-
|
|
9
|
+
} from "./chunk-CTT4XAGO.js";
|
|
10
10
|
import {
|
|
11
11
|
deriveTemplateInputs,
|
|
12
12
|
extractEmbeddedVideos,
|
|
@@ -48,6 +48,9 @@ import {
|
|
|
48
48
|
import {
|
|
49
49
|
parseBuildSpec
|
|
50
50
|
} from "./chunk-P53Z4RYC.js";
|
|
51
|
+
import {
|
|
52
|
+
wordTimeAt
|
|
53
|
+
} from "./chunk-L4AFSOT3.js";
|
|
51
54
|
import {
|
|
52
55
|
estimateTimeFromText
|
|
53
56
|
} from "./chunk-GHOPHGOK.js";
|
|
@@ -2189,6 +2192,55 @@ function createPresentationPlan(doc, hints = defaultPresentationHints()) {
|
|
|
2189
2192
|
return { version: 1, sourceText: script.sourceText, origin, beats };
|
|
2190
2193
|
}
|
|
2191
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
|
+
|
|
2192
2244
|
// src/transform/presentationCompiler.ts
|
|
2193
2245
|
function compilePresentationPlan(doc, plan) {
|
|
2194
2246
|
const checked = parsePresentationPlan(plan);
|
|
@@ -2260,8 +2312,7 @@ function compilePresentationPlan(doc, plan) {
|
|
|
2260
2312
|
const timeAt = (offset) => {
|
|
2261
2313
|
const marked = markedTime(offset);
|
|
2262
2314
|
if (marked !== void 0) return marked;
|
|
2263
|
-
if (narration && bookmarks.length)
|
|
2264
|
-
return bookmarks.find((word) => word.charOffset >= offset)?.time ?? duration;
|
|
2315
|
+
if (narration && bookmarks.length) return wordTimeAt(bookmarks, offset, "next") ?? duration;
|
|
2265
2316
|
return (narration?.startTime ?? 0) + tokensBefore(offset) / Math.max(1, script.tokens.length) * (narration?.duration ?? duration);
|
|
2266
2317
|
};
|
|
2267
2318
|
let previous = 0;
|
|
@@ -2375,46 +2426,15 @@ function compilePresentationPlan(doc, plan) {
|
|
|
2375
2426
|
audio: doc.audio.segments.some((segment) => segment.src) ? doc.audio : { segments: [{ src: "", name: "presentation-clock", startTime: 0, duration }] }
|
|
2376
2427
|
};
|
|
2377
2428
|
}
|
|
2378
|
-
var CONCEPT_TEMPLATES = ["cards", "process", "cycle", "layers"];
|
|
2379
|
-
function conceptTemplate(template) {
|
|
2380
|
-
return CONCEPT_TEMPLATES.find((name) => name === template);
|
|
2381
|
-
}
|
|
2382
|
-
function stems(text3) {
|
|
2383
|
-
return (text3.toLowerCase().match(/[\p{L}\p{N}][\p{L}\p{N}'-]*/gu) ?? []).filter((word) => word.length >= 4).map((word) => word.slice(0, 5));
|
|
2384
|
-
}
|
|
2385
2429
|
function conceptReveal(template, beat, start, end, sourceText, timeAt) {
|
|
2386
|
-
const build = { mode: "items", effect: "fadeInUp", previous: "keep" };
|
|
2387
2430
|
const steps = conceptBuildStepTexts(template, beat.points);
|
|
2388
|
-
const
|
|
2389
|
-
|
|
2390
|
-
|
|
2391
|
-
|
|
2392
|
-
|
|
2393
|
-
let last = -1;
|
|
2394
|
-
const offsets = [];
|
|
2395
|
-
for (const point of beat.points.slice(0, steps.length)) {
|
|
2396
|
-
const words = stems(point);
|
|
2397
|
-
let best;
|
|
2398
|
-
for (const sentence of sentences) {
|
|
2399
|
-
const shared = words.filter((word) => sentence.words.has(word)).length;
|
|
2400
|
-
if (shared > (best?.shared ?? 1)) best = { offset: sentence.offset, shared };
|
|
2401
|
-
}
|
|
2402
|
-
if (!best || best.offset < last) return { build };
|
|
2403
|
-
offsets.push(last = best.offset);
|
|
2404
|
-
}
|
|
2405
|
-
if (offsets.length !== steps.length) return { build };
|
|
2406
|
-
const lead = Math.min(0.5, (end - start) / 8);
|
|
2407
|
-
const starts = offsets.map(
|
|
2408
|
-
(offset) => Math.min(Math.max(0, end - start - 0.1), Math.max(lead, timeAt(offset) - start))
|
|
2431
|
+
const offsets = matchPointsToSentences(
|
|
2432
|
+
beat.points.slice(0, steps.length),
|
|
2433
|
+
sourceText,
|
|
2434
|
+
beat.sourceStart,
|
|
2435
|
+
beat.sourceEnd
|
|
2409
2436
|
);
|
|
2410
|
-
|
|
2411
|
-
return {
|
|
2412
|
-
build,
|
|
2413
|
-
buildTiming: {
|
|
2414
|
-
sourceText: buildSourceText(steps.map((text3, index) => ({ id: `build-${index}`, text: text3 }))),
|
|
2415
|
-
starts
|
|
2416
|
-
}
|
|
2417
|
-
};
|
|
2437
|
+
return conceptRevealTiming(template, beat.points, offsets?.map(timeAt), start, end);
|
|
2418
2438
|
}
|
|
2419
2439
|
function tableFeatureBlock(beat, layout, base, features) {
|
|
2420
2440
|
const paragraph = (text3) => ({
|
|
@@ -2555,6 +2575,11 @@ export {
|
|
|
2555
2575
|
transformNarratedSegment,
|
|
2556
2576
|
collectPresentationMedia,
|
|
2557
2577
|
createPresentationPlan,
|
|
2578
|
+
CONCEPT_TEMPLATES,
|
|
2579
|
+
conceptTemplate,
|
|
2580
|
+
matchPointsToSentences,
|
|
2581
|
+
conceptBuildSpec,
|
|
2582
|
+
conceptRevealTiming,
|
|
2558
2583
|
compilePresentationPlan,
|
|
2559
2584
|
applySavedPresentation
|
|
2560
2585
|
};
|