@bendyline/squisq 1.5.2 → 2.0.1
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-T5UK6YOB.js → chunk-2PWOO4ZE.js} +60 -17
- package/dist/chunk-2PWOO4ZE.js.map +1 -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-P3K7NLHF.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-BEQODJWV.js → chunk-66QO7XFB.js} +11 -291
- package/dist/chunk-66QO7XFB.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-CY2R2VPR.js +884 -0
- package/dist/chunk-CY2R2VPR.js.map +1 -0
- package/dist/chunk-FVJUUTEM.js +74 -0
- package/dist/chunk-FVJUUTEM.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-MMPBOUK7.js} +10 -73
- package/dist/chunk-MMPBOUK7.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-GPLTCSXO.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-TFJVXUHH.js +2358 -0
- package/dist/chunk-TFJVXUHH.js.map +1 -0
- package/dist/{chunk-LTN6I7QW.js → chunk-TUWMK62B.js} +55 -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/{chunk-KKNUBQ6Y.js → chunk-W4O2TX5I.js} +157 -92
- package/dist/chunk-W4O2TX5I.js.map +1 -0
- package/dist/{chunk-5TYS5V3G.js → chunk-X6DUONGT.js} +4204 -3560
- package/dist/chunk-X6DUONGT.js.map +1 -0
- package/dist/doc/index.d.ts +2334 -5
- package/dist/doc/index.js +127 -33
- 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 +246 -80
- 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__/customTemplatesFrontmatter.test.ts +0 -15
- package/src/__tests__/customThemesFrontmatter.test.ts +1 -0
- 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 +54 -4
- package/src/__tests__/markdownSanitize.test.ts +7 -5
- 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 +12 -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 +70 -2
- package/src/doc/index.ts +76 -4
- package/src/doc/markdownToDoc.ts +138 -2
- 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/__tests__/listBlock.test.ts +64 -0
- package/src/doc/templates/diagramBlock.ts +237 -38
- 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/listBlock.ts +88 -30
- 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 +67 -5
- 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/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/schemas/themes/gezellig.json +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-5TYS5V3G.js.map +0 -1
- package/dist/chunk-A4O7GIWE.js.map +0 -1
- package/dist/chunk-BEQODJWV.js.map +0 -1
- package/dist/chunk-DLXZMS5K.js.map +0 -1
- package/dist/chunk-GPLTCSXO.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-LTN6I7QW.js.map +0 -1
- package/dist/chunk-P3K7NLHF.js.map +0 -1
- package/dist/chunk-T5UK6YOB.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 -1801
- package/dist/story/index.js +0 -279
- 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,
|
|
@@ -58,6 +60,29 @@ const TRANSITION_PARAM_KEYS = ['transition', 'transitionDuration', 'transitionDi
|
|
|
58
60
|
export function docToMarkdown(doc: Doc): MarkdownDocument {
|
|
59
61
|
const children: MarkdownBlockNode[] = [];
|
|
60
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
|
+
|
|
61
86
|
function emitBlock(block: Block): void {
|
|
62
87
|
// Emit the heading node if present
|
|
63
88
|
if (block.sourceHeading) {
|
|
@@ -71,8 +96,29 @@ export function docToMarkdown(doc: Doc): MarkdownDocument {
|
|
|
71
96
|
children.push(synthesizeAnnotationParagraph(block));
|
|
72
97
|
}
|
|
73
98
|
|
|
74
|
-
// Emit body content
|
|
75
|
-
|
|
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) {
|
|
76
122
|
children.push(...block.contents);
|
|
77
123
|
}
|
|
78
124
|
|
|
@@ -131,6 +177,28 @@ function synthesizeAnnotationParagraph(block: Block): MarkdownParagraph {
|
|
|
131
177
|
return { type: 'paragraph', children: [{ type: 'text', value: text }] };
|
|
132
178
|
}
|
|
133
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
|
+
|
|
134
202
|
/**
|
|
135
203
|
* Ensure the heading's `templateAnnotation` reflects the block's
|
|
136
204
|
* template and templateOverrides. Returns a (possibly cloned) heading.
|
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,11 +35,12 @@ 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,
|
|
41
42
|
} from '../markdown/types.js';
|
|
42
|
-
import { extractPlainText } from '../markdown/utils.js';
|
|
43
|
+
import { extractPlainText, readFrontmatterThemeId } from '../markdown/utils.js';
|
|
43
44
|
import { coerceAnnotationValues, type CoercedBlockMeta } from '../markdown/annotationCoercion.js';
|
|
44
45
|
import { estimateReadingTime } from '../timing/readingTime.js';
|
|
45
46
|
import { resolveTemplateName, isContainerTemplate } from './templates/index.js';
|
|
@@ -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>();
|
|
@@ -567,6 +600,7 @@ export function markdownToDoc(markdownDoc: MarkdownDocument, options?: MarkdownT
|
|
|
567
600
|
|
|
568
601
|
const customTemplates = readCustomTemplatesFromFrontmatter(markdownDoc.frontmatter);
|
|
569
602
|
const customThemes = readCustomThemesFromFrontmatter(markdownDoc.frontmatter);
|
|
603
|
+
const themeId = readFrontmatterThemeId(markdownDoc.frontmatter);
|
|
570
604
|
const doc: Doc = {
|
|
571
605
|
articleId,
|
|
572
606
|
duration: currentTime,
|
|
@@ -576,6 +610,7 @@ export function markdownToDoc(markdownDoc: MarkdownDocument, options?: MarkdownT
|
|
|
576
610
|
},
|
|
577
611
|
...(captions ? { captions } : {}),
|
|
578
612
|
...(markdownDoc.frontmatter ? { frontmatter: markdownDoc.frontmatter } : {}),
|
|
613
|
+
...(themeId ? { themeId } : {}),
|
|
579
614
|
...(customTemplates ? { customTemplates } : {}),
|
|
580
615
|
...(customThemes ? { customThemes } : {}),
|
|
581
616
|
...(diagnostics.length > 0 ? { diagnostics } : {}),
|
|
@@ -709,6 +744,94 @@ function applyStructuredData(block: Block, diagnostics: DocDiagnostic[]): void {
|
|
|
709
744
|
}
|
|
710
745
|
}
|
|
711
746
|
|
|
747
|
+
/**
|
|
748
|
+
* Fill `templateData.nodes`/`edges` for an explicitly `{[diagram]}`-annotated
|
|
749
|
+
* block whose body is exactly one eligible ASCII-art fence. Runs without the
|
|
750
|
+
* detection threshold (explicit annotation = author intent), unconditionally
|
|
751
|
+
* of the autoTemplates switches. The fence stays in `contents`, untouched.
|
|
752
|
+
*/
|
|
753
|
+
function applyAsciiDiagramData(block: Block, diagnostics: DocDiagnostic[]): void {
|
|
754
|
+
if (resolveTemplateName(block.template ?? '') !== 'diagram') return;
|
|
755
|
+
if (block.children && block.children.length > 0) return; // native children win
|
|
756
|
+
if (block.templateData && 'nodes' in block.templateData) return; // author data wins
|
|
757
|
+
const fences = (block.contents ?? []).filter((n): n is MarkdownCodeBlock => n.type === 'code');
|
|
758
|
+
if (fences.length !== 1 || !isEligibleAsciiFenceLang(fences[0].lang)) return;
|
|
759
|
+
const parsed = parseAsciiDiagram(fences[0].value);
|
|
760
|
+
if (parsed.nodes.length === 0) {
|
|
761
|
+
diagnostics.push({
|
|
762
|
+
severity: 'warning',
|
|
763
|
+
code: 'ascii-diagram-parse',
|
|
764
|
+
message: `Diagram block "${block.id}": code fence did not parse as an ASCII diagram`,
|
|
765
|
+
blockId: block.id,
|
|
766
|
+
});
|
|
767
|
+
return;
|
|
768
|
+
}
|
|
769
|
+
const { nodes, edges } = asciiDiagramToTemplateData(parsed);
|
|
770
|
+
block.templateData = { nodes, edges, ...block.templateData };
|
|
771
|
+
}
|
|
772
|
+
|
|
773
|
+
/**
|
|
774
|
+
* Fill `templateData.items` for an explicitly `{[tree]}`-annotated block
|
|
775
|
+
* whose body is one eligible ASCII tree fence (or a nested list). Runs
|
|
776
|
+
* without the detection threshold (explicit annotation = author intent).
|
|
777
|
+
*/
|
|
778
|
+
function applyTreeData(block: Block, diagnostics: DocDiagnostic[]): void {
|
|
779
|
+
if (resolveTemplateName(block.template ?? '') !== 'tree') return;
|
|
780
|
+
if (block.templateData && 'items' in block.templateData) return; // author data wins
|
|
781
|
+
const derived = deriveTemplateInputs('tree', block.title ?? '', block.contents);
|
|
782
|
+
if (!derived || !Array.isArray(derived.items) || derived.items.length === 0) {
|
|
783
|
+
diagnostics.push({
|
|
784
|
+
severity: 'warning',
|
|
785
|
+
code: 'tree-parse',
|
|
786
|
+
message: `Tree block "${block.id}": body did not parse as a tree`,
|
|
787
|
+
blockId: block.id,
|
|
788
|
+
});
|
|
789
|
+
return;
|
|
790
|
+
}
|
|
791
|
+
block.templateData = { ...derived, ...block.templateData };
|
|
792
|
+
}
|
|
793
|
+
|
|
794
|
+
/**
|
|
795
|
+
* Fill `templateData.tracks`/`links` for an explicitly `{[timeline]}`-
|
|
796
|
+
* annotated block whose body is one eligible ASCII timeline fence.
|
|
797
|
+
*/
|
|
798
|
+
function applyTimelineData(block: Block, diagnostics: DocDiagnostic[]): void {
|
|
799
|
+
if (resolveTemplateName(block.template ?? '') !== 'timeline') return;
|
|
800
|
+
if (block.templateData && 'tracks' in block.templateData) return; // author data wins
|
|
801
|
+
const derived = deriveTemplateInputs('timeline', block.title ?? '', block.contents);
|
|
802
|
+
if (!derived || !Array.isArray(derived.tracks) || derived.tracks.length === 0) {
|
|
803
|
+
diagnostics.push({
|
|
804
|
+
severity: 'warning',
|
|
805
|
+
code: 'timeline-parse',
|
|
806
|
+
message: `Timeline block "${block.id}": body did not parse as an ASCII timeline`,
|
|
807
|
+
blockId: block.id,
|
|
808
|
+
});
|
|
809
|
+
return;
|
|
810
|
+
}
|
|
811
|
+
block.templateData = { ...derived, ...block.templateData };
|
|
812
|
+
}
|
|
813
|
+
|
|
814
|
+
function collectTimelineWarnings(block: Block, diagnostics: DocDiagnostic[]): void {
|
|
815
|
+
if (resolveTemplateName(block.template ?? '') !== 'timeline') return;
|
|
816
|
+
if (!block.templateData || !('tracks' in block.templateData)) return;
|
|
817
|
+
const fences = (block.contents ?? []).filter(
|
|
818
|
+
(node): node is MarkdownCodeBlock =>
|
|
819
|
+
node.type === 'code' && isEligibleAsciiTimelineFenceLang(node.lang),
|
|
820
|
+
);
|
|
821
|
+
if (fences.length !== 1) return;
|
|
822
|
+
for (const warning of parseAsciiTimeline(fences[0].value).warnings) {
|
|
823
|
+
const unresolved = /^unresolved-link\((.+)\)$/.exec(warning);
|
|
824
|
+
diagnostics.push({
|
|
825
|
+
severity: 'warning',
|
|
826
|
+
code: unresolved ? 'timeline-unresolved-link' : 'timeline-parse-warning',
|
|
827
|
+
message: unresolved
|
|
828
|
+
? `Timeline block "${block.id}": unresolved branch ${unresolved[1]}`
|
|
829
|
+
: `Timeline block "${block.id}": ${warning}`,
|
|
830
|
+
blockId: block.id,
|
|
831
|
+
});
|
|
832
|
+
}
|
|
833
|
+
}
|
|
834
|
+
|
|
712
835
|
/** Truthiness of the `squisq-auto-templates` frontmatter kill-switch. */
|
|
713
836
|
function frontmatterDisablesAutoTemplates(markdownDoc: MarkdownDocument): boolean {
|
|
714
837
|
const v = markdownDoc.frontmatter?.['squisq-auto-templates'];
|
|
@@ -752,12 +875,25 @@ function applyAutoTemplates(
|
|
|
752
875
|
|
|
753
876
|
/**
|
|
754
877
|
* Extract the plain text from a block's body contents (excluding heading text).
|
|
878
|
+
*
|
|
879
|
+
* Exported because the narration script builder and the narration timing
|
|
880
|
+
* resolver must derive the *same* spoken text per block that captions and
|
|
881
|
+
* reading-time use — one source of truth for "what does this block say".
|
|
755
882
|
*/
|
|
756
|
-
function getBlockBodyText(block: Block): string {
|
|
883
|
+
export function getBlockBodyText(block: Block): string {
|
|
757
884
|
if (!block.contents || block.contents.length === 0) return '';
|
|
885
|
+
// A fence consumed as diagram data must not feed reading-time/captions —
|
|
886
|
+
// 20 lines of box-drawing art would add ~30s of duration and captions
|
|
887
|
+
// that "read" border runs aloud.
|
|
888
|
+
const tmpl = resolveTemplateName(block.template ?? '');
|
|
889
|
+
const consumedFence =
|
|
890
|
+
(tmpl === 'diagram' && block.templateData !== undefined && 'nodes' in block.templateData) ||
|
|
891
|
+
(tmpl === 'timeline' && block.templateData !== undefined && 'tracks' in block.templateData) ||
|
|
892
|
+
(tmpl === 'tree' && block.templateData !== undefined && 'items' in block.templateData);
|
|
758
893
|
// Join with newlines to preserve paragraph/list-item boundaries.
|
|
759
894
|
// splitIntoPhrases uses these newlines as natural split points.
|
|
760
895
|
return block.contents
|
|
896
|
+
.filter((node) => !(consumedFence && node.type === 'code'))
|
|
761
897
|
.map((node) => extractPlainText(node))
|
|
762
898
|
.join('\n')
|
|
763
899
|
.trim();
|