@bendyline/squisq 1.5.1 → 2.0.0
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/README.md +63 -10
- package/dist/{Doc-BBVGq1_9.d.ts → Doc-ylqWKDc_.d.ts} +248 -37
- package/dist/{ImageEditDoc-FuyGtt6o.d.ts → ImageEditDoc-D828IXJ1.d.ts} +1 -1
- package/dist/annotationCoercion-Dr_DY9Hx.d.ts +86 -0
- package/dist/{chunk-VWUFZ6ZG.js → chunk-3E5F2XMR.js} +2 -29
- package/dist/chunk-3E5F2XMR.js.map +1 -0
- package/dist/chunk-4X3JQXNM.js +35 -0
- package/dist/chunk-4X3JQXNM.js.map +1 -0
- package/dist/{chunk-2OIBZYKP.js → chunk-5DKBFXPY.js} +31 -121
- package/dist/chunk-5DKBFXPY.js.map +1 -0
- package/dist/{chunk-DLXZMS5K.js → chunk-5J4HKRJF.js} +51 -3
- package/dist/chunk-5J4HKRJF.js.map +1 -0
- package/dist/chunk-5UYI3RJB.js +2503 -0
- package/dist/chunk-5UYI3RJB.js.map +1 -0
- package/dist/{chunk-TEEEILMP.js → chunk-66QO7XFB.js} +19 -292
- package/dist/chunk-66QO7XFB.js.map +1 -0
- package/dist/{chunk-KKNUBQ6Y.js → chunk-6PJRB2CF.js} +156 -91
- package/dist/chunk-6PJRB2CF.js.map +1 -0
- package/dist/{chunk-HWVFJAAH.js → chunk-AMIR72JP.js} +28 -10
- package/dist/chunk-AMIR72JP.js.map +1 -0
- package/dist/chunk-FVJUUTEM.js +74 -0
- package/dist/chunk-FVJUUTEM.js.map +1 -0
- package/dist/{chunk-LH7I6SH7.js → chunk-GF3BVNLU.js} +6360 -5723
- package/dist/chunk-GF3BVNLU.js.map +1 -0
- package/dist/{chunk-Q3ROPT5H.js → chunk-GZY66SDN.js} +1 -1
- package/dist/{chunk-Q3ROPT5H.js.map → chunk-GZY66SDN.js.map} +1 -1
- package/dist/chunk-JOQYPRDZ.js +127 -0
- package/dist/chunk-JOQYPRDZ.js.map +1 -0
- package/dist/{chunk-A4O7GIWE.js → chunk-LYNJEOAC.js} +10 -73
- package/dist/chunk-LYNJEOAC.js.map +1 -0
- package/dist/{chunk-LDQ2HJIX.js → chunk-PXLVXTLW.js} +252 -54
- package/dist/chunk-PXLVXTLW.js.map +1 -0
- package/dist/{chunk-TQWLI6S2.js → chunk-Q7LROV2H.js} +4 -2
- package/dist/{chunk-TQWLI6S2.js.map → chunk-Q7LROV2H.js.map} +1 -1
- package/dist/{chunk-T5UK6YOB.js → chunk-QTSJOY54.js} +60 -17
- package/dist/chunk-QTSJOY54.js.map +1 -0
- package/dist/chunk-QVANWDPH.js +2358 -0
- package/dist/chunk-QVANWDPH.js.map +1 -0
- package/dist/{chunk-6ATE2PSM.js → chunk-R2GC6RLD.js} +7 -7
- package/dist/chunk-R2GC6RLD.js.map +1 -0
- package/dist/chunk-R2YZTCLO.js +292 -0
- package/dist/chunk-R2YZTCLO.js.map +1 -0
- package/dist/chunk-S7A5D7ZJ.js +174 -0
- package/dist/chunk-S7A5D7ZJ.js.map +1 -0
- package/dist/chunk-T3TI7GSZ.js +884 -0
- package/dist/chunk-T3TI7GSZ.js.map +1 -0
- package/dist/{chunk-RUDYOTA6.js → chunk-TUWMK62B.js} +110 -4
- package/dist/chunk-TUWMK62B.js.map +1 -0
- package/dist/{chunk-IMSCRLLV.js → chunk-VR7UPJ7C.js} +35 -25
- package/dist/chunk-VR7UPJ7C.js.map +1 -0
- package/dist/doc/index.d.ts +2334 -5
- package/dist/doc/index.js +134 -34
- package/dist/generate/index.d.ts +7 -2
- package/dist/generate/index.js +1 -1
- package/dist/imageEdit/index.d.ts +9 -6
- package/dist/imageEdit/index.js +3 -3
- package/dist/index.d.ts +12 -10
- package/dist/index.js +253 -81
- package/dist/jsonForm/index.d.ts +7 -3
- package/dist/jsonForm/index.js +9 -4
- package/dist/markdown/index.d.ts +6 -70
- package/dist/markdown/index.js +13 -9
- package/dist/narration/index.d.ts +618 -0
- package/dist/narration/index.js +82 -0
- package/dist/recommend/index.d.ts +15 -1
- package/dist/recommend/index.js +2 -2
- package/dist/schemas/index.d.ts +28 -6
- package/dist/schemas/index.js +15 -13
- package/dist/spatial/index.d.ts +0 -1
- package/dist/spatial/index.js +1 -1
- package/dist/storage/index.js +1 -1
- package/dist/{themeLibrary-CehcJhzz.d.ts → themeLibrary-RjVgLpUi.d.ts} +10 -11
- package/dist/timing/index.js +5 -3
- package/dist/transform/index.d.ts +35 -24
- package/dist/transform/index.js +9 -10
- package/dist/{types-DlAZ7MW4.d.ts → types-BwMPl2CB.d.ts} +1 -1
- package/dist/{types-D98li1gV.d.ts → types-C4kiRn_k.d.ts} +3 -1
- package/dist/versions/index.d.ts +2 -1
- package/dist/versions/index.js +2 -2
- package/package.json +6 -6
- package/src/__tests__/applyNarrationTiming.test.ts +190 -0
- package/src/__tests__/applyRenderStyle.test.ts +3 -3
- package/src/__tests__/asciiDiagramBattle.test.ts +246 -0
- package/src/__tests__/asciiDiagramDetect.test.ts +162 -0
- package/src/__tests__/asciiDiagramMapping.test.ts +38 -0
- package/src/__tests__/asciiDiagramParse.test.ts +233 -0
- package/src/__tests__/asciiDiagramPipeline.test.ts +273 -0
- package/src/__tests__/asciiDiagramRender.test.ts +162 -0
- package/src/__tests__/asciiDiagramRepair.test.ts +84 -0
- package/src/__tests__/asciiTimelineDetect.test.ts +157 -0
- package/src/__tests__/asciiTimelineParse.test.ts +205 -0
- package/src/__tests__/asciiTimelinePipeline.test.ts +349 -0
- package/src/__tests__/asciiTimelineRender.test.ts +376 -0
- package/src/__tests__/attrTokens.test.ts +5 -0
- package/src/__tests__/contentContainer.test.ts +43 -0
- package/src/__tests__/coverBlock.test.ts +33 -2
- package/src/__tests__/customTemplatesFrontmatter.test.ts +0 -15
- package/src/__tests__/dataTable.test.ts +25 -15
- package/src/__tests__/diagramBlock.test.ts +3 -3
- package/src/__tests__/diagramText.test.ts +26 -0
- package/src/__tests__/docToMarkdownMedia.test.ts +83 -0
- package/src/__tests__/drawingBlock.test.ts +3 -3
- package/src/__tests__/fixtures/asciiDiagramBattle.ts +512 -0
- package/src/__tests__/fixtures/asciiDiagrams.ts +287 -0
- package/src/__tests__/fixtures/asciiTimelines.ts +35 -0
- package/src/__tests__/fixtures/treeviewFixtures.ts +243 -0
- package/src/__tests__/getLayers.test.ts +32 -18
- package/src/__tests__/getLayersFallback.test.ts +8 -8
- package/src/__tests__/imageEditVersions.test.ts +2 -0
- package/src/__tests__/jsonForm.pointer.test.ts +47 -1
- package/src/__tests__/layoutBlock.test.ts +3 -3
- package/src/__tests__/majorApiTypes.test.ts +18 -0
- package/src/__tests__/markdown.test.ts +41 -0
- package/src/__tests__/markdownToDoc.test.ts +5 -2
- package/src/__tests__/matchFontFamily.test.ts +114 -0
- package/src/__tests__/materializeBlockLayers.test.ts +135 -0
- package/src/__tests__/materializeTestUtils.ts +17 -0
- package/src/__tests__/narrationAlign.test.ts +150 -0
- package/src/__tests__/narrationFeatures.test.ts +71 -0
- package/src/__tests__/narrationNuclei.test.ts +45 -0
- package/src/__tests__/narrationPacing.test.ts +189 -0
- package/src/__tests__/narrationScript.test.ts +149 -0
- package/src/__tests__/narrationSessionRepro.test.ts +62 -0
- package/src/__tests__/narrationSidecar.test.ts +88 -0
- package/src/__tests__/narrationSyllables.test.ts +65 -0
- package/src/__tests__/narrationTestSignals.ts +167 -0
- package/src/__tests__/narrationVad.test.ts +66 -0
- package/src/__tests__/resolveDocTheme.test.ts +16 -2
- package/src/__tests__/rootExports.test.ts +18 -0
- package/src/__tests__/scopedContentContainer.test.ts +20 -0
- package/src/__tests__/shapeGeometry.test.ts +49 -0
- package/src/__tests__/templateAnnotationParse.test.ts +10 -0
- package/src/__tests__/templateInputs.test.ts +69 -0
- package/src/__tests__/templateMetadata.test.ts +2 -1
- package/src/__tests__/templates.test.ts +143 -17
- package/src/__tests__/themeCompile.test.ts +49 -22
- package/src/__tests__/transformProvenance.test.ts +160 -0
- package/src/__tests__/transformV2.test.ts +40 -14
- package/src/__tests__/treeviewBattle.test.ts +76 -0
- package/src/__tests__/treeviewDetect.test.ts +168 -0
- package/src/__tests__/treeviewParse.test.ts +118 -0
- package/src/__tests__/treeviewPipeline.test.ts +183 -0
- package/src/__tests__/treeviewRender.test.ts +91 -0
- package/src/__tests__/versions.test.ts +44 -0
- package/src/doc/applyNarrationTiming.ts +244 -0
- package/src/doc/asciiDiagram/chars.ts +210 -0
- package/src/doc/asciiDiagram/detect.ts +175 -0
- package/src/doc/asciiDiagram/index.ts +38 -0
- package/src/doc/asciiDiagram/mapping.ts +225 -0
- package/src/doc/asciiDiagram/parse.ts +1095 -0
- package/src/doc/asciiDiagram/render.ts +963 -0
- package/src/doc/asciiDiagram/repair.ts +400 -0
- package/src/doc/asciiDiagram/types.ts +65 -0
- package/src/doc/asciiTimeline/detect.ts +138 -0
- package/src/doc/asciiTimeline/index.ts +26 -0
- package/src/doc/asciiTimeline/mapping.ts +43 -0
- package/src/doc/asciiTimeline/parse.ts +870 -0
- package/src/doc/asciiTimeline/render.ts +427 -0
- package/src/doc/asciiTimeline/types.ts +73 -0
- package/src/doc/asciiTimeline/wrappedFlow.ts +427 -0
- package/src/doc/audioMapping.ts +60 -46
- package/src/doc/customTemplatesFrontmatter.ts +0 -41
- package/src/doc/docToMarkdown.ts +126 -39
- package/src/doc/index.ts +76 -4
- package/src/doc/markdownToDoc.ts +135 -1
- package/src/doc/materializeBlockLayers.ts +304 -0
- package/src/doc/mediaAnnotations.ts +20 -0
- package/src/doc/resolveDocTheme.ts +8 -7
- package/src/doc/templateInputs.ts +185 -2
- package/src/doc/templates/__tests__/customTemplateRegistry.test.ts +22 -8
- package/src/doc/templates/coverBlock.ts +49 -13
- package/src/doc/templates/diagramBlock.ts +237 -38
- package/src/doc/templates/drawingBlock.ts +2 -2
- package/src/doc/templates/fallbackBlock.ts +1 -1
- package/src/doc/templates/index.ts +134 -279
- package/src/doc/templates/inputDescriptors.ts +20 -1
- package/src/doc/templates/metadata.ts +10 -0
- package/src/doc/templates/persistentLayers.ts +12 -126
- package/src/doc/templates/registry.ts +88 -0
- package/src/doc/templates/templateNames.ts +22 -0
- package/src/doc/templates/timelineBlock.ts +613 -0
- package/src/doc/templates/treeBlock.ts +131 -0
- package/src/doc/treeview/chars.ts +72 -0
- package/src/doc/treeview/detect.ts +145 -0
- package/src/doc/treeview/index.ts +30 -0
- package/src/doc/treeview/mapping.ts +95 -0
- package/src/doc/treeview/parse.ts +201 -0
- package/src/doc/treeview/render.ts +41 -0
- package/src/doc/treeview/types.ts +43 -0
- package/src/doc/utils/applyRenderStyle.ts +2 -9
- package/src/doc/utils/diagramText.ts +78 -0
- package/src/doc/utils/shapeGeometry.ts +157 -7
- package/src/doc/validate.ts +1 -1
- package/src/generate/templateMapper.ts +7 -0
- package/src/imageEdit/export.ts +6 -5
- package/src/imageEdit/versionPaths.ts +13 -26
- package/src/imageEdit/versions.ts +21 -100
- package/src/index.ts +1 -0
- package/src/internal/immutable.ts +58 -0
- package/src/jsonForm/index.ts +9 -1
- package/src/jsonForm/pointer.ts +68 -23
- package/src/markdown/attrTokens.ts +7 -5
- package/src/markdown/convert.ts +11 -1
- package/src/markdown/index.ts +1 -0
- package/src/markdown/stringify.ts +4 -9
- package/src/markdown/utils.ts +36 -14
- package/src/narration/align.ts +514 -0
- package/src/narration/features.ts +231 -0
- package/src/narration/index.ts +86 -0
- package/src/narration/nuclei.ts +118 -0
- package/src/narration/pacing.ts +211 -0
- package/src/narration/script.ts +239 -0
- package/src/narration/session.ts +100 -0
- package/src/narration/sidecar.ts +187 -0
- package/src/narration/syllables.ts +85 -0
- package/src/narration/trace.ts +50 -0
- package/src/narration/types.ts +299 -0
- package/src/narration/vad.ts +131 -0
- package/src/recommend/numberHighlight.ts +25 -0
- package/src/recommend/templates.ts +108 -12
- package/src/schemas/BlockTemplates.ts +156 -7
- package/src/schemas/Doc.ts +72 -9
- package/src/schemas/Media.ts +12 -2
- package/src/schemas/Theme.ts +45 -25
- package/src/schemas/Viewport.ts +2 -2
- package/src/schemas/fontStacks.ts +79 -0
- package/src/schemas/themeCompile.ts +15 -0
- package/src/schemas/themeConstants.ts +2 -0
- package/src/schemas/themeLibrary.ts +25 -19
- package/src/schemas/themeValidator.ts +1 -1
- package/src/spatial/Geohash.ts +0 -3
- package/src/storage/ContentContainer.ts +17 -9
- package/src/storage/ScopedContentContainer.ts +45 -15
- package/src/transform/applyTransform.ts +5 -5
- package/src/transform/index.ts +3 -1
- package/src/transform/registry.ts +169 -49
- package/src/transform/templateSelector.ts +21 -2
- package/src/transform/timingAllocator.ts +152 -21
- package/src/transform/types.ts +14 -1
- package/src/versions/gitignore.ts +34 -0
- package/src/versions/index.ts +2 -0
- package/src/versions/operations.ts +18 -104
- package/src/versions/paths.ts +13 -28
- package/src/versions/snapshotStore.ts +202 -0
- package/src/versions/types.ts +3 -1
- package/dist/chunk-2OIBZYKP.js.map +0 -1
- package/dist/chunk-6ATE2PSM.js.map +0 -1
- package/dist/chunk-A4O7GIWE.js.map +0 -1
- package/dist/chunk-DLXZMS5K.js.map +0 -1
- package/dist/chunk-HWVFJAAH.js.map +0 -1
- package/dist/chunk-IMSCRLLV.js.map +0 -1
- package/dist/chunk-JELIRVDP.js +0 -24
- package/dist/chunk-JELIRVDP.js.map +0 -1
- package/dist/chunk-KKNUBQ6Y.js.map +0 -1
- package/dist/chunk-LDQ2HJIX.js.map +0 -1
- package/dist/chunk-LH7I6SH7.js.map +0 -1
- package/dist/chunk-RUDYOTA6.js.map +0 -1
- package/dist/chunk-T5UK6YOB.js.map +0 -1
- package/dist/chunk-TEEEILMP.js.map +0 -1
- package/dist/chunk-UY7KGQ5R.js +0 -192
- package/dist/chunk-UY7KGQ5R.js.map +0 -1
- package/dist/chunk-VLJJHDAC.js +0 -218
- package/dist/chunk-VLJJHDAC.js.map +0 -1
- package/dist/chunk-VWUFZ6ZG.js.map +0 -1
- package/dist/story/index.d.ts +0 -1777
- package/dist/story/index.js +0 -273
- package/src/doc/getLayers.ts +0 -195
- /package/dist/{story → narration}/index.js.map +0 -0
|
@@ -29,10 +29,6 @@
|
|
|
29
29
|
* payload and a structured array, so older documents load unchanged.
|
|
30
30
|
* The first save migrates them to the compact form.
|
|
31
31
|
*
|
|
32
|
-
* The `encodeLayersForFrontmatter` / `decodeLayersFromFrontmatter` pair
|
|
33
|
-
* handles a single Layer array for the editor's `dataLayers="…"` Pandoc
|
|
34
|
-
* param path; that path stays base64 (it's a different, per-block
|
|
35
|
-
* mechanism) and is re-exported here for locality.
|
|
36
32
|
*/
|
|
37
33
|
|
|
38
34
|
import type { Layer } from '../schemas/Doc.js';
|
|
@@ -134,43 +130,6 @@ function isDefaultViewport(v: { width: number; height: number } | undefined): bo
|
|
|
134
130
|
return !v || (v.width === DEFAULT_VIEWPORT.width && v.height === DEFAULT_VIEWPORT.height);
|
|
135
131
|
}
|
|
136
132
|
|
|
137
|
-
// ─── Single-Layer base64 codec (legacy `dataLayers` path) ───────────
|
|
138
|
-
|
|
139
|
-
/**
|
|
140
|
-
* Base64-encode a Layer array as JSON, UTF-8 safe. Used by the editor's
|
|
141
|
-
* per-block `dataLayers="…"` Pandoc param (not the doc-level template
|
|
142
|
-
* list, which uses the compact JSON above).
|
|
143
|
-
*
|
|
144
|
-
* `btoa` only accepts Latin1 strings — any character outside that range
|
|
145
|
-
* (e.g. an em-dash in a description) throws — so we round through
|
|
146
|
-
* `TextEncoder`. Works in Node ≥18 and browsers.
|
|
147
|
-
*/
|
|
148
|
-
export function encodeLayersForFrontmatter(layers: readonly Layer[]): string {
|
|
149
|
-
return utf8ToBase64(JSON.stringify(layers));
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
/** Inverse of {@link encodeLayersForFrontmatter}. Returns [] on parse failure. */
|
|
153
|
-
export function decodeLayersFromFrontmatter(encoded: string): Layer[] {
|
|
154
|
-
try {
|
|
155
|
-
const json = base64ToUtf8(encoded);
|
|
156
|
-
const parsed = JSON.parse(json);
|
|
157
|
-
return Array.isArray(parsed) ? (parsed as Layer[]) : [];
|
|
158
|
-
} catch {
|
|
159
|
-
return [];
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
/** UTF-8 safe `btoa` — uses TextEncoder so arbitrary Unicode round-trips. */
|
|
164
|
-
function utf8ToBase64(str: string): string {
|
|
165
|
-
const bytes = new TextEncoder().encode(str);
|
|
166
|
-
if (typeof globalThis.btoa === 'function') {
|
|
167
|
-
let binary = '';
|
|
168
|
-
for (let i = 0; i < bytes.length; i++) binary += String.fromCharCode(bytes[i]);
|
|
169
|
-
return globalThis.btoa(binary);
|
|
170
|
-
}
|
|
171
|
-
return Buffer.from(bytes).toString('base64');
|
|
172
|
-
}
|
|
173
|
-
|
|
174
133
|
/** UTF-8 safe `atob`. */
|
|
175
134
|
function base64ToUtf8(b64: string): string {
|
|
176
135
|
if (typeof globalThis.atob === 'function') {
|
package/src/doc/docToMarkdown.ts
CHANGED
|
@@ -23,6 +23,7 @@
|
|
|
23
23
|
*/
|
|
24
24
|
|
|
25
25
|
import type { Doc, Block } from '../schemas/Doc.js';
|
|
26
|
+
import type { MediaClip } from '../schemas/Media.js';
|
|
26
27
|
import type {
|
|
27
28
|
MarkdownDocument,
|
|
28
29
|
MarkdownBlockNode,
|
|
@@ -30,6 +31,7 @@ import type {
|
|
|
30
31
|
MarkdownParagraph,
|
|
31
32
|
} from '../markdown/types.js';
|
|
32
33
|
import { serializeAnnotation } from '../markdown/attrTokens.js';
|
|
34
|
+
import { flattenBlocks } from './markdownToDoc.js';
|
|
33
35
|
import {
|
|
34
36
|
FRONTMATTER_CUSTOM_TEMPLATES_KEY,
|
|
35
37
|
writeCustomTemplatesToFrontmatter,
|
|
@@ -39,6 +41,8 @@ import {
|
|
|
39
41
|
writeCustomThemesToFrontmatter,
|
|
40
42
|
} from './customThemesFrontmatter.js';
|
|
41
43
|
|
|
44
|
+
const TRANSITION_PARAM_KEYS = ['transition', 'transitionDuration', 'transitionDirection'] as const;
|
|
45
|
+
|
|
42
46
|
/**
|
|
43
47
|
* Convert a Doc with heading-driven blocks back to a MarkdownDocument.
|
|
44
48
|
*
|
|
@@ -56,6 +60,29 @@ import {
|
|
|
56
60
|
export function docToMarkdown(doc: Doc): MarkdownDocument {
|
|
57
61
|
const children: MarkdownBlockNode[] = [];
|
|
58
62
|
|
|
63
|
+
// Media clips were LIFTED out of block contents at parse time
|
|
64
|
+
// (`extractMediaFromContents`); re-insert their authoring annotations so
|
|
65
|
+
// the round-trip doesn't silently drop narration/clip references.
|
|
66
|
+
// Doc-anchored clips whose home block still exists ride back into that
|
|
67
|
+
// block's body; homeless ones (programmatic clips, or a preamble block
|
|
68
|
+
// that was dropped once emptied) emit at the document top — the
|
|
69
|
+
// canonical authored position.
|
|
70
|
+
const blockIds = new Set(flattenBlocks(doc.blocks).map((b) => b.id));
|
|
71
|
+
const docMediaByBlock = new Map<string, MediaClip[]>();
|
|
72
|
+
const homelessDocMedia: MediaClip[] = [];
|
|
73
|
+
for (const clip of doc.documentMedia ?? []) {
|
|
74
|
+
if (clip.origin && blockIds.has(clip.origin.blockId)) {
|
|
75
|
+
const list = docMediaByBlock.get(clip.origin.blockId) ?? [];
|
|
76
|
+
list.push(clip);
|
|
77
|
+
docMediaByBlock.set(clip.origin.blockId, list);
|
|
78
|
+
} else {
|
|
79
|
+
homelessDocMedia.push(clip);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
for (const clip of homelessDocMedia) {
|
|
83
|
+
children.push(synthesizeMediaParagraph(clip));
|
|
84
|
+
}
|
|
85
|
+
|
|
59
86
|
function emitBlock(block: Block): void {
|
|
60
87
|
// Emit the heading node if present
|
|
61
88
|
if (block.sourceHeading) {
|
|
@@ -69,8 +96,29 @@ export function docToMarkdown(doc: Doc): MarkdownDocument {
|
|
|
69
96
|
children.push(synthesizeAnnotationParagraph(block));
|
|
70
97
|
}
|
|
71
98
|
|
|
72
|
-
// Emit body content
|
|
73
|
-
|
|
99
|
+
// Emit body content, with this block's media annotations re-inserted
|
|
100
|
+
// at their original positions. `origin.index` refers to the ORIGINAL
|
|
101
|
+
// contents array (which included every annotation), so ascending
|
|
102
|
+
// insertion into the stripped contents reproduces the source order.
|
|
103
|
+
const clips = [...(block.media ?? []), ...(docMediaByBlock.get(block.id) ?? [])];
|
|
104
|
+
if (clips.length > 0) {
|
|
105
|
+
const contents = [...(block.contents ?? [])];
|
|
106
|
+
const anchored = clips
|
|
107
|
+
.filter((clip) => clip.origin)
|
|
108
|
+
.sort((a, b) => a.origin!.index - b.origin!.index);
|
|
109
|
+
for (const clip of anchored) {
|
|
110
|
+
contents.splice(
|
|
111
|
+
Math.min(clip.origin!.index, contents.length),
|
|
112
|
+
0,
|
|
113
|
+
synthesizeMediaParagraph(clip),
|
|
114
|
+
);
|
|
115
|
+
}
|
|
116
|
+
// Origin-less block clips (programmatic) sit right under the heading.
|
|
117
|
+
for (const clip of clips.filter((c) => !c.origin).reverse()) {
|
|
118
|
+
contents.unshift(synthesizeMediaParagraph(clip));
|
|
119
|
+
}
|
|
120
|
+
children.push(...contents);
|
|
121
|
+
} else if (block.contents) {
|
|
74
122
|
children.push(...block.contents);
|
|
75
123
|
}
|
|
76
124
|
|
|
@@ -129,69 +177,108 @@ function synthesizeAnnotationParagraph(block: Block): MarkdownParagraph {
|
|
|
129
177
|
return { type: 'paragraph', children: [{ type: 'text', value: text }] };
|
|
130
178
|
}
|
|
131
179
|
|
|
180
|
+
/**
|
|
181
|
+
* Re-synthesize the authoring paragraph for a media clip. The extraction
|
|
182
|
+
* kept the exact source text (`origin.raw`) — emit it verbatim so quoting,
|
|
183
|
+
* param order, and `mm:ss` time forms stay byte-stable. Programmatic clips
|
|
184
|
+
* (no raw) serialize canonically: src, anchor, then timing params.
|
|
185
|
+
*/
|
|
186
|
+
function synthesizeMediaParagraph(clip: MediaClip): MarkdownParagraph {
|
|
187
|
+
if (clip.origin?.raw) {
|
|
188
|
+
return { type: 'paragraph', children: [{ type: 'text', value: clip.origin.raw }] };
|
|
189
|
+
}
|
|
190
|
+
const params: Record<string, string> = { src: clip.src };
|
|
191
|
+
if (clip.anchor === 'document') params.anchor = 'document';
|
|
192
|
+
if (clip.startAt) params.startAt = String(clip.startAt);
|
|
193
|
+
if (clip.clipStart != null) params.clipStart = String(clip.clipStart);
|
|
194
|
+
if (clip.clipEnd != null) params.clipEnd = String(clip.clipEnd);
|
|
195
|
+
if (clip.spillover) params.spillover = 'true';
|
|
196
|
+
return {
|
|
197
|
+
type: 'paragraph',
|
|
198
|
+
children: [{ type: 'text', value: serializeAnnotation(clip.kind, params) }],
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
|
|
132
202
|
/**
|
|
133
203
|
* Ensure the heading's `templateAnnotation` reflects the block's
|
|
134
204
|
* template and templateOverrides. Returns a (possibly cloned) heading.
|
|
135
205
|
*/
|
|
136
206
|
function ensureAnnotation(block: Block, heading: MarkdownHeading): MarkdownHeading {
|
|
137
|
-
const
|
|
207
|
+
const { attributes, transitionParams } = ensureTransitionMetadata(block, heading);
|
|
138
208
|
|
|
139
209
|
// If the block has a non-default template or overrides, inject an annotation
|
|
140
210
|
const hasExplicitTemplate =
|
|
141
211
|
block.template && block.template !== 'sectionHeader' && block.autoTemplate !== true;
|
|
142
212
|
const hasOverrides = block.templateOverrides && Object.keys(block.templateOverrides).length > 0;
|
|
143
213
|
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
214
|
+
let templateAnnotation = heading.templateAnnotation;
|
|
215
|
+
if (!templateAnnotation && (hasExplicitTemplate || hasOverrides || transitionParams)) {
|
|
216
|
+
templateAnnotation = {
|
|
217
|
+
...(hasExplicitTemplate ? { template: block.template ?? 'sectionHeader' } : {}),
|
|
218
|
+
...(hasOverrides ? { params: block.templateOverrides } : {}),
|
|
219
|
+
};
|
|
148
220
|
}
|
|
149
221
|
|
|
150
|
-
if (
|
|
151
|
-
|
|
222
|
+
if (transitionParams) {
|
|
223
|
+
templateAnnotation = {
|
|
224
|
+
...(templateAnnotation ?? {}),
|
|
225
|
+
params: {
|
|
226
|
+
...(templateAnnotation?.params ?? {}),
|
|
227
|
+
...transitionParams,
|
|
228
|
+
},
|
|
229
|
+
};
|
|
152
230
|
}
|
|
153
231
|
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
templateAnnotation: {
|
|
160
|
-
template: block.template ?? 'sectionHeader',
|
|
161
|
-
...(hasOverrides ? { params: block.templateOverrides } : {}),
|
|
162
|
-
},
|
|
163
|
-
};
|
|
232
|
+
if (attributes === heading.attributes && templateAnnotation === heading.templateAnnotation) {
|
|
233
|
+
return heading;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
return { ...heading, children: [...heading.children], attributes, templateAnnotation };
|
|
164
237
|
}
|
|
165
238
|
|
|
166
|
-
function
|
|
239
|
+
function ensureTransitionMetadata(
|
|
167
240
|
block: Block,
|
|
168
241
|
heading: MarkdownHeading,
|
|
169
|
-
):
|
|
242
|
+
): {
|
|
243
|
+
attributes: MarkdownHeading['attributes'];
|
|
244
|
+
transitionParams: Record<string, string> | undefined;
|
|
245
|
+
} {
|
|
170
246
|
const transition = block.transition;
|
|
171
|
-
if (!transition)
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
//
|
|
176
|
-
//
|
|
177
|
-
//
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
// would lose or resurrect the wrong transition.
|
|
181
|
-
const attrs = heading.attributes ?? {};
|
|
182
|
-
return {
|
|
247
|
+
if (!transition) {
|
|
248
|
+
return { attributes: heading.attributes, transitionParams: undefined };
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
// The transition is written to the squisq-native `{[…]}` params for
|
|
252
|
+
// author-facing markdown, and to `attributes.blockMeta` for the in-memory
|
|
253
|
+
// Doc → MarkdownDocument → Doc path (which does not re-parse string params).
|
|
254
|
+
const attrs = removeTransitionParams(heading.attributes) ?? {};
|
|
255
|
+
const attributes = {
|
|
183
256
|
...attrs,
|
|
184
|
-
|
|
185
|
-
...(attrs.
|
|
257
|
+
blockMeta: {
|
|
258
|
+
...(attrs.blockMeta ?? {}),
|
|
259
|
+
transition,
|
|
260
|
+
},
|
|
261
|
+
};
|
|
262
|
+
return {
|
|
263
|
+
attributes,
|
|
264
|
+
transitionParams: {
|
|
186
265
|
transition: transition.type,
|
|
187
266
|
...(transition.duration !== undefined
|
|
188
267
|
? { transitionDuration: String(transition.duration) }
|
|
189
268
|
: {}),
|
|
190
269
|
...(transition.direction ? { transitionDirection: transition.direction } : {}),
|
|
191
270
|
},
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
271
|
+
};
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
function removeTransitionParams(
|
|
275
|
+
attrs: MarkdownHeading['attributes'],
|
|
276
|
+
): MarkdownHeading['attributes'] {
|
|
277
|
+
if (!attrs?.params) return attrs;
|
|
278
|
+
const params = { ...attrs.params };
|
|
279
|
+
for (const key of TRANSITION_PARAM_KEYS) delete params[key];
|
|
280
|
+
return {
|
|
281
|
+
...attrs,
|
|
282
|
+
...(Object.keys(params).length > 0 ? { params } : { params: undefined }),
|
|
196
283
|
};
|
|
197
284
|
}
|
package/src/doc/index.ts
CHANGED
|
@@ -3,6 +3,7 @@ export * from './utils/animationUtils.js';
|
|
|
3
3
|
export * from './utils/themeUtils.js';
|
|
4
4
|
export * from './utils/applyRenderStyle.js';
|
|
5
5
|
export * from './utils/imageTreatment.js';
|
|
6
|
+
export * from './utils/diagramText.js';
|
|
6
7
|
export * from './templateInputs.js';
|
|
7
8
|
export * from './utils/shapeGeometry.js';
|
|
8
9
|
export {
|
|
@@ -11,12 +12,12 @@ export {
|
|
|
11
12
|
flattenRenderableBlocks,
|
|
12
13
|
countBlocks,
|
|
13
14
|
getBlockDepth,
|
|
15
|
+
getBlockBodyText,
|
|
16
|
+
getPinnedBlockMeta,
|
|
14
17
|
} from './markdownToDoc.js';
|
|
15
18
|
export type { MarkdownToDocOptions } from './markdownToDoc.js';
|
|
16
19
|
export { docToMarkdown } from './docToMarkdown.js';
|
|
17
20
|
export {
|
|
18
|
-
encodeLayersForFrontmatter,
|
|
19
|
-
decodeLayersFromFrontmatter,
|
|
20
21
|
readCustomTemplatesFromFrontmatter,
|
|
21
22
|
writeCustomTemplatesToFrontmatter,
|
|
22
23
|
FRONTMATTER_CUSTOM_TEMPLATES_KEY,
|
|
@@ -27,9 +28,8 @@ export {
|
|
|
27
28
|
FRONTMATTER_CUSTOM_THEMES_KEY,
|
|
28
29
|
} from './customThemesFrontmatter.js';
|
|
29
30
|
export { resolveThemeForDoc } from './resolveDocTheme.js';
|
|
30
|
-
export { getLayers } from './getLayers.js';
|
|
31
|
-
export type { RenderContext } from './getLayers.js';
|
|
32
31
|
export { resolveAudioMapping, scoreTextSimilarity } from './audioMapping.js';
|
|
32
|
+
export { applyNarrationTiming, type NarrationResolution } from './applyNarrationTiming.js';
|
|
33
33
|
export {
|
|
34
34
|
isDataFence,
|
|
35
35
|
parseDataFence,
|
|
@@ -53,3 +53,75 @@ export type {
|
|
|
53
53
|
TemplateInputDescriptor,
|
|
54
54
|
TemplateParamFinding,
|
|
55
55
|
} from './templates/inputDescriptors.js';
|
|
56
|
+
export {
|
|
57
|
+
ASCII_CHAR_H,
|
|
58
|
+
ASCII_CHAR_W,
|
|
59
|
+
ASCII_DIAGRAM_FENCE_LANGS,
|
|
60
|
+
asciiCellToCanvas,
|
|
61
|
+
asciiDiagramFromBlocks,
|
|
62
|
+
asciiDiagramFromTemplateData,
|
|
63
|
+
asciiDiagramToTemplateData,
|
|
64
|
+
canvasToAsciiCell,
|
|
65
|
+
detectAsciiDiagram,
|
|
66
|
+
isAsciiDiagramFence,
|
|
67
|
+
isEligibleAsciiFenceLang,
|
|
68
|
+
isExplicitDiagramLang,
|
|
69
|
+
isRepairableDiagram,
|
|
70
|
+
parseAsciiDiagram,
|
|
71
|
+
renderAsciiDiagram,
|
|
72
|
+
repairAsciiDiagram,
|
|
73
|
+
} from './asciiDiagram/index.js';
|
|
74
|
+
export type {
|
|
75
|
+
AsciiDiagram,
|
|
76
|
+
AsciiDiagramDetection,
|
|
77
|
+
AsciiDiagramEdge,
|
|
78
|
+
AsciiDiagramNode,
|
|
79
|
+
RenderAsciiDiagramOptions,
|
|
80
|
+
RepairResult,
|
|
81
|
+
} from './asciiDiagram/index.js';
|
|
82
|
+
export {
|
|
83
|
+
ASCII_TIMELINE_FENCE_LANGS,
|
|
84
|
+
asciiTimelineToTemplateData,
|
|
85
|
+
detectAsciiTimeline,
|
|
86
|
+
isAsciiTimelineFence,
|
|
87
|
+
isEligibleAsciiTimelineFenceLang,
|
|
88
|
+
isExplicitTimelineLang,
|
|
89
|
+
isWrappedFlowTimelineSource,
|
|
90
|
+
parseAsciiTimeline,
|
|
91
|
+
parseAsciiTimelineWithStats,
|
|
92
|
+
parseWrappedFlowTimeline,
|
|
93
|
+
renderAsciiTimeline,
|
|
94
|
+
} from './asciiTimeline/index.js';
|
|
95
|
+
export type {
|
|
96
|
+
AsciiTimeline,
|
|
97
|
+
AsciiTimelineDetection,
|
|
98
|
+
AsciiTimelineEvent,
|
|
99
|
+
AsciiTimelineLink,
|
|
100
|
+
AsciiTimelineMarker,
|
|
101
|
+
AsciiTimelineSide,
|
|
102
|
+
AsciiTimelineStats,
|
|
103
|
+
AsciiTimelineStyle,
|
|
104
|
+
AsciiTimelineTrack,
|
|
105
|
+
DetectAsciiTimelineOptions,
|
|
106
|
+
RenderAsciiTimelineOptions,
|
|
107
|
+
} from './asciiTimeline/index.js';
|
|
108
|
+
export {
|
|
109
|
+
TREE_FENCE_LANGS,
|
|
110
|
+
detectTree,
|
|
111
|
+
findFirstList,
|
|
112
|
+
isEligibleTreeFenceLang,
|
|
113
|
+
isExplicitTreeLang,
|
|
114
|
+
isTreeFence,
|
|
115
|
+
parseTree,
|
|
116
|
+
renderTree,
|
|
117
|
+
treeFromMarkdownList,
|
|
118
|
+
treeFromTemplateData,
|
|
119
|
+
treeToTemplateData,
|
|
120
|
+
} from './treeview/index.js';
|
|
121
|
+
export type {
|
|
122
|
+
RenderTreeOptions,
|
|
123
|
+
Tree,
|
|
124
|
+
TreeDetection,
|
|
125
|
+
TreeItem,
|
|
126
|
+
TreeNode,
|
|
127
|
+
} from './treeview/index.js';
|
package/src/doc/markdownToDoc.ts
CHANGED
|
@@ -35,6 +35,7 @@ import { readCustomThemesFromFrontmatter } from './customThemesFrontmatter.js';
|
|
|
35
35
|
import type {
|
|
36
36
|
MarkdownDocument,
|
|
37
37
|
MarkdownBlockNode,
|
|
38
|
+
MarkdownCodeBlock,
|
|
38
39
|
MarkdownHeading,
|
|
39
40
|
MarkdownNode,
|
|
40
41
|
HtmlNode,
|
|
@@ -49,6 +50,11 @@ import { extractTemplateBlocksFromContents } from './annotationBlocks.js';
|
|
|
49
50
|
import type { ParsedAnnotation } from './standaloneAnnotation.js';
|
|
50
51
|
import { profileBlockContents, pickAutoTemplate } from '../recommend/templates.js';
|
|
51
52
|
import { deriveTemplateInputs } from './templateInputs.js';
|
|
53
|
+
import { isEligibleAsciiFenceLang } from './asciiDiagram/detect.js';
|
|
54
|
+
import { parseAsciiDiagram } from './asciiDiagram/parse.js';
|
|
55
|
+
import { asciiDiagramToTemplateData } from './asciiDiagram/mapping.js';
|
|
56
|
+
import { isEligibleAsciiTimelineFenceLang } from './asciiTimeline/detect.js';
|
|
57
|
+
import { parseAsciiTimeline } from './asciiTimeline/parse.js';
|
|
52
58
|
import type { MediaClip } from '../schemas/Media.js';
|
|
53
59
|
|
|
54
60
|
// ============================================
|
|
@@ -222,6 +228,17 @@ function pinnedMeta(block: Block): CoercedBlockMeta {
|
|
|
222
228
|
return {};
|
|
223
229
|
}
|
|
224
230
|
|
|
231
|
+
/**
|
|
232
|
+
* Public accessor for {@link pinnedMeta}: the timing/meta values an author
|
|
233
|
+
* pinned on a block (heading `{key=value}` attrs or `{[key=value]}`
|
|
234
|
+
* annotation params). Consumers use this to tell "author pinned this"
|
|
235
|
+
* from "derive it" — e.g. narration timing never overwrites a pinned
|
|
236
|
+
* `duration=`/`startTime=`.
|
|
237
|
+
*/
|
|
238
|
+
export function getPinnedBlockMeta(block: Block): CoercedBlockMeta {
|
|
239
|
+
return pinnedMeta(block);
|
|
240
|
+
}
|
|
241
|
+
|
|
225
242
|
/**
|
|
226
243
|
* Rebuild a block list, lifting standalone `{[…]}` template annotations out of
|
|
227
244
|
* each block's body into heading-less sibling blocks (recursing into children
|
|
@@ -408,6 +425,7 @@ export function markdownToDoc(markdownDoc: MarkdownDocument, options?: MarkdownT
|
|
|
408
425
|
} = extractMediaFromContents(
|
|
409
426
|
block.contents,
|
|
410
427
|
(src, i) => `${block.id}-media-${i}-${src.replace(/[^a-zA-Z0-9]+/g, '-')}`,
|
|
428
|
+
block.id,
|
|
411
429
|
);
|
|
412
430
|
if (media.length > 0) block.media = media;
|
|
413
431
|
if (docMedia.length > 0) documentMedia.push(...docMedia);
|
|
@@ -463,6 +481,16 @@ export function markdownToDoc(markdownDoc: MarkdownDocument, options?: MarkdownT
|
|
|
463
481
|
applyStructuredData(block, diagnostics);
|
|
464
482
|
}
|
|
465
483
|
|
|
484
|
+
// Explicitly diagram-annotated blocks whose body is a single eligible
|
|
485
|
+
// ASCII-art fence get nodes/edges derived from it — no detection
|
|
486
|
+
// threshold, the author asked for a diagram. Children-driven diagrams
|
|
487
|
+
// and author `json data` node lists win over the fence.
|
|
488
|
+
for (const block of allBlocks) {
|
|
489
|
+
applyAsciiDiagramData(block, diagnostics);
|
|
490
|
+
applyTimelineData(block, diagnostics);
|
|
491
|
+
applyTreeData(block, diagnostics);
|
|
492
|
+
}
|
|
493
|
+
|
|
466
494
|
// Content-aware template auto-pick (default on): unannotated heading
|
|
467
495
|
// blocks whose body carries a strong signal get the matching template
|
|
468
496
|
// plus inputs derived from that body. Strict derivation — when the
|
|
@@ -472,6 +500,11 @@ export function markdownToDoc(markdownDoc: MarkdownDocument, options?: MarkdownT
|
|
|
472
500
|
applyAutoTemplates(rootBlocks, resolveTemplateName(defaultTemplate), { featureIndex: 0 });
|
|
473
501
|
}
|
|
474
502
|
|
|
503
|
+
// Timeline parsing is intentionally tolerant and can retain unresolved
|
|
504
|
+
// branch declarations as warnings. Surface those after auto-template
|
|
505
|
+
// selection so both explicit and auto-derived timeline blocks participate.
|
|
506
|
+
for (const block of allBlocks) collectTimelineWarnings(block, diagnostics);
|
|
507
|
+
|
|
475
508
|
// Duplicate ids make connections and navigation ambiguous. Generated
|
|
476
509
|
// slugs are already deduped; this catches author-pinned `{#id}` clashes.
|
|
477
510
|
const seenIds = new Map<string, Block>();
|
|
@@ -709,6 +742,94 @@ function applyStructuredData(block: Block, diagnostics: DocDiagnostic[]): void {
|
|
|
709
742
|
}
|
|
710
743
|
}
|
|
711
744
|
|
|
745
|
+
/**
|
|
746
|
+
* Fill `templateData.nodes`/`edges` for an explicitly `{[diagram]}`-annotated
|
|
747
|
+
* block whose body is exactly one eligible ASCII-art fence. Runs without the
|
|
748
|
+
* detection threshold (explicit annotation = author intent), unconditionally
|
|
749
|
+
* of the autoTemplates switches. The fence stays in `contents`, untouched.
|
|
750
|
+
*/
|
|
751
|
+
function applyAsciiDiagramData(block: Block, diagnostics: DocDiagnostic[]): void {
|
|
752
|
+
if (resolveTemplateName(block.template ?? '') !== 'diagram') return;
|
|
753
|
+
if (block.children && block.children.length > 0) return; // native children win
|
|
754
|
+
if (block.templateData && 'nodes' in block.templateData) return; // author data wins
|
|
755
|
+
const fences = (block.contents ?? []).filter((n): n is MarkdownCodeBlock => n.type === 'code');
|
|
756
|
+
if (fences.length !== 1 || !isEligibleAsciiFenceLang(fences[0].lang)) return;
|
|
757
|
+
const parsed = parseAsciiDiagram(fences[0].value);
|
|
758
|
+
if (parsed.nodes.length === 0) {
|
|
759
|
+
diagnostics.push({
|
|
760
|
+
severity: 'warning',
|
|
761
|
+
code: 'ascii-diagram-parse',
|
|
762
|
+
message: `Diagram block "${block.id}": code fence did not parse as an ASCII diagram`,
|
|
763
|
+
blockId: block.id,
|
|
764
|
+
});
|
|
765
|
+
return;
|
|
766
|
+
}
|
|
767
|
+
const { nodes, edges } = asciiDiagramToTemplateData(parsed);
|
|
768
|
+
block.templateData = { nodes, edges, ...block.templateData };
|
|
769
|
+
}
|
|
770
|
+
|
|
771
|
+
/**
|
|
772
|
+
* Fill `templateData.items` for an explicitly `{[tree]}`-annotated block
|
|
773
|
+
* whose body is one eligible ASCII tree fence (or a nested list). Runs
|
|
774
|
+
* without the detection threshold (explicit annotation = author intent).
|
|
775
|
+
*/
|
|
776
|
+
function applyTreeData(block: Block, diagnostics: DocDiagnostic[]): void {
|
|
777
|
+
if (resolveTemplateName(block.template ?? '') !== 'tree') return;
|
|
778
|
+
if (block.templateData && 'items' in block.templateData) return; // author data wins
|
|
779
|
+
const derived = deriveTemplateInputs('tree', block.title ?? '', block.contents);
|
|
780
|
+
if (!derived || !Array.isArray(derived.items) || derived.items.length === 0) {
|
|
781
|
+
diagnostics.push({
|
|
782
|
+
severity: 'warning',
|
|
783
|
+
code: 'tree-parse',
|
|
784
|
+
message: `Tree block "${block.id}": body did not parse as a tree`,
|
|
785
|
+
blockId: block.id,
|
|
786
|
+
});
|
|
787
|
+
return;
|
|
788
|
+
}
|
|
789
|
+
block.templateData = { ...derived, ...block.templateData };
|
|
790
|
+
}
|
|
791
|
+
|
|
792
|
+
/**
|
|
793
|
+
* Fill `templateData.tracks`/`links` for an explicitly `{[timeline]}`-
|
|
794
|
+
* annotated block whose body is one eligible ASCII timeline fence.
|
|
795
|
+
*/
|
|
796
|
+
function applyTimelineData(block: Block, diagnostics: DocDiagnostic[]): void {
|
|
797
|
+
if (resolveTemplateName(block.template ?? '') !== 'timeline') return;
|
|
798
|
+
if (block.templateData && 'tracks' in block.templateData) return; // author data wins
|
|
799
|
+
const derived = deriveTemplateInputs('timeline', block.title ?? '', block.contents);
|
|
800
|
+
if (!derived || !Array.isArray(derived.tracks) || derived.tracks.length === 0) {
|
|
801
|
+
diagnostics.push({
|
|
802
|
+
severity: 'warning',
|
|
803
|
+
code: 'timeline-parse',
|
|
804
|
+
message: `Timeline block "${block.id}": body did not parse as an ASCII timeline`,
|
|
805
|
+
blockId: block.id,
|
|
806
|
+
});
|
|
807
|
+
return;
|
|
808
|
+
}
|
|
809
|
+
block.templateData = { ...derived, ...block.templateData };
|
|
810
|
+
}
|
|
811
|
+
|
|
812
|
+
function collectTimelineWarnings(block: Block, diagnostics: DocDiagnostic[]): void {
|
|
813
|
+
if (resolveTemplateName(block.template ?? '') !== 'timeline') return;
|
|
814
|
+
if (!block.templateData || !('tracks' in block.templateData)) return;
|
|
815
|
+
const fences = (block.contents ?? []).filter(
|
|
816
|
+
(node): node is MarkdownCodeBlock =>
|
|
817
|
+
node.type === 'code' && isEligibleAsciiTimelineFenceLang(node.lang),
|
|
818
|
+
);
|
|
819
|
+
if (fences.length !== 1) return;
|
|
820
|
+
for (const warning of parseAsciiTimeline(fences[0].value).warnings) {
|
|
821
|
+
const unresolved = /^unresolved-link\((.+)\)$/.exec(warning);
|
|
822
|
+
diagnostics.push({
|
|
823
|
+
severity: 'warning',
|
|
824
|
+
code: unresolved ? 'timeline-unresolved-link' : 'timeline-parse-warning',
|
|
825
|
+
message: unresolved
|
|
826
|
+
? `Timeline block "${block.id}": unresolved branch ${unresolved[1]}`
|
|
827
|
+
: `Timeline block "${block.id}": ${warning}`,
|
|
828
|
+
blockId: block.id,
|
|
829
|
+
});
|
|
830
|
+
}
|
|
831
|
+
}
|
|
832
|
+
|
|
712
833
|
/** Truthiness of the `squisq-auto-templates` frontmatter kill-switch. */
|
|
713
834
|
function frontmatterDisablesAutoTemplates(markdownDoc: MarkdownDocument): boolean {
|
|
714
835
|
const v = markdownDoc.frontmatter?.['squisq-auto-templates'];
|
|
@@ -752,12 +873,25 @@ function applyAutoTemplates(
|
|
|
752
873
|
|
|
753
874
|
/**
|
|
754
875
|
* Extract the plain text from a block's body contents (excluding heading text).
|
|
876
|
+
*
|
|
877
|
+
* Exported because the narration script builder and the narration timing
|
|
878
|
+
* resolver must derive the *same* spoken text per block that captions and
|
|
879
|
+
* reading-time use — one source of truth for "what does this block say".
|
|
755
880
|
*/
|
|
756
|
-
function getBlockBodyText(block: Block): string {
|
|
881
|
+
export function getBlockBodyText(block: Block): string {
|
|
757
882
|
if (!block.contents || block.contents.length === 0) return '';
|
|
883
|
+
// A fence consumed as diagram data must not feed reading-time/captions —
|
|
884
|
+
// 20 lines of box-drawing art would add ~30s of duration and captions
|
|
885
|
+
// that "read" border runs aloud.
|
|
886
|
+
const tmpl = resolveTemplateName(block.template ?? '');
|
|
887
|
+
const consumedFence =
|
|
888
|
+
(tmpl === 'diagram' && block.templateData !== undefined && 'nodes' in block.templateData) ||
|
|
889
|
+
(tmpl === 'timeline' && block.templateData !== undefined && 'tracks' in block.templateData) ||
|
|
890
|
+
(tmpl === 'tree' && block.templateData !== undefined && 'items' in block.templateData);
|
|
758
891
|
// Join with newlines to preserve paragraph/list-item boundaries.
|
|
759
892
|
// splitIntoPhrases uses these newlines as natural split points.
|
|
760
893
|
return block.contents
|
|
894
|
+
.filter((node) => !(consumedFence && node.type === 'code'))
|
|
761
895
|
.map((node) => extractPlainText(node))
|
|
762
896
|
.join('\n')
|
|
763
897
|
.trim();
|