@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
package/src/spatial/Geohash.ts
CHANGED
|
@@ -92,9 +92,8 @@ export function findDocumentPath(entries: ContentEntry[]): string | null {
|
|
|
92
92
|
const rootFiles = entries.filter((e) => !e.path.includes('/'));
|
|
93
93
|
|
|
94
94
|
for (const name of MARKDOWN_PRIORITY) {
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
}
|
|
95
|
+
const match = rootFiles.find((e) => e.path.toLowerCase() === name);
|
|
96
|
+
if (match) return match.path;
|
|
98
97
|
}
|
|
99
98
|
|
|
100
99
|
// Fallback: first .md file at root
|
|
@@ -120,21 +119,30 @@ export class MemoryContentContainer implements ContentContainer {
|
|
|
120
119
|
private files = new Map<string, MemoryFile>();
|
|
121
120
|
|
|
122
121
|
async readFile(path: string): Promise<ArrayBuffer | null> {
|
|
123
|
-
|
|
122
|
+
const stored = this.files.get(path)?.data;
|
|
123
|
+
// A container owns its bytes. Returning the backing buffer would let a
|
|
124
|
+
// caller mutate stored state without going through writeFile().
|
|
125
|
+
return stored ? stored.slice(0) : null;
|
|
124
126
|
}
|
|
125
127
|
|
|
126
128
|
async writeFile(path: string, data: ArrayBuffer | Uint8Array, mimeType?: string): Promise<void> {
|
|
127
129
|
// Copy to a standalone ArrayBuffer. Uint8Array/Buffer may share a larger
|
|
128
130
|
// backing buffer (e.g., Node's buffer pool), so we must slice to the
|
|
129
131
|
// exact byte range to avoid storing stale pool data.
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
132
|
+
// ArrayBuffer.isView is cross-realm-safe (unlike `instanceof
|
|
133
|
+
// ArrayBuffer`, which fails for values created in jsdom/iframes).
|
|
134
|
+
let source: Uint8Array;
|
|
135
|
+
if (ArrayBuffer.isView(data)) {
|
|
136
|
+
source = new Uint8Array(data.buffer, data.byteOffset, data.byteLength);
|
|
133
137
|
} else {
|
|
134
|
-
|
|
138
|
+
source = new Uint8Array(data);
|
|
135
139
|
}
|
|
140
|
+
// Always materialize in this realm, including for iframe ArrayBuffers and
|
|
141
|
+
// views backed by SharedArrayBuffer.
|
|
142
|
+
const owned = new Uint8Array(source.byteLength);
|
|
143
|
+
owned.set(source);
|
|
136
144
|
this.files.set(path, {
|
|
137
|
-
data: buffer,
|
|
145
|
+
data: owned.buffer,
|
|
138
146
|
mimeType: mimeType ?? guessMimeType(path),
|
|
139
147
|
});
|
|
140
148
|
}
|
|
@@ -16,20 +16,48 @@
|
|
|
16
16
|
|
|
17
17
|
import type { ContentContainer, ContentEntry } from './ContentContainer.js';
|
|
18
18
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
19
|
+
interface NormalizePathOptions {
|
|
20
|
+
allowEmpty?: boolean;
|
|
21
|
+
preserveTrailingSlash?: boolean;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Normalize a container-relative path without ever resolving traversal.
|
|
26
|
+
* Backslashes are rejected rather than translated because a filesystem-backed
|
|
27
|
+
* parent may interpret them as separators even when an in-memory parent does
|
|
28
|
+
* not. Dot segments are similarly rejected instead of collapsed so an invalid
|
|
29
|
+
* caller path can never escape after a later platform-specific normalization.
|
|
30
|
+
*/
|
|
31
|
+
function normalizeRelativePath(
|
|
32
|
+
path: string,
|
|
33
|
+
label: 'prefix' | 'path',
|
|
34
|
+
options: NormalizePathOptions = {},
|
|
35
|
+
): string {
|
|
36
|
+
const original = path;
|
|
37
|
+
let normalized = label === 'prefix' ? path.trim() : path;
|
|
38
|
+
if (normalized.includes('\\')) {
|
|
39
|
+
throw new Error(`ScopedContentContainer: ${label} must use forward slashes: ${original}`);
|
|
26
40
|
}
|
|
27
|
-
|
|
41
|
+
|
|
42
|
+
const hadTrailingSlash = normalized.endsWith('/');
|
|
43
|
+
normalized = normalized.replace(/^\/+|\/+$/g, '');
|
|
44
|
+
const segments = normalized === '' ? [] : normalized.split('/').filter(Boolean);
|
|
45
|
+
if (segments.some((segment) => segment === '.' || segment === '..')) {
|
|
28
46
|
throw new Error(
|
|
29
|
-
`ScopedContentContainer:
|
|
47
|
+
`ScopedContentContainer: ${label} must not contain '.' or '..' segments: ${original}`,
|
|
30
48
|
);
|
|
31
49
|
}
|
|
32
|
-
|
|
50
|
+
if (segments.length === 0 && !options.allowEmpty) {
|
|
51
|
+
throw new Error(`ScopedContentContainer: ${label} must not be empty`);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const result = segments.join('/');
|
|
55
|
+
return options.preserveTrailingSlash && hadTrailingSlash && result ? `${result}/` : result;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/** Normalize a prefix: strip leading/trailing slashes, ensure no `..` segments. */
|
|
59
|
+
function normalizePrefix(prefix: string): string {
|
|
60
|
+
return normalizeRelativePath(prefix, 'prefix');
|
|
33
61
|
}
|
|
34
62
|
|
|
35
63
|
/**
|
|
@@ -50,10 +78,9 @@ export class ScopedContentContainer implements ContentContainer {
|
|
|
50
78
|
this.prefixSlash = `${this.prefix}/`;
|
|
51
79
|
}
|
|
52
80
|
|
|
53
|
-
private toParent(path: string): string {
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
return this.prefixSlash + p;
|
|
81
|
+
private toParent(path: string, options: NormalizePathOptions = {}): string {
|
|
82
|
+
const normalized = normalizeRelativePath(path, 'path', options);
|
|
83
|
+
return this.prefixSlash + normalized;
|
|
57
84
|
}
|
|
58
85
|
|
|
59
86
|
/** Strip the prefix from a parent-space path. Returns null if the path is outside the scope. */
|
|
@@ -75,7 +102,10 @@ export class ScopedContentContainer implements ContentContainer {
|
|
|
75
102
|
}
|
|
76
103
|
|
|
77
104
|
async listFiles(prefix?: string): Promise<ContentEntry[]> {
|
|
78
|
-
const parentPrefix =
|
|
105
|
+
const parentPrefix =
|
|
106
|
+
prefix == null
|
|
107
|
+
? this.prefixSlash
|
|
108
|
+
: this.toParent(prefix, { allowEmpty: true, preserveTrailingSlash: true });
|
|
79
109
|
const entries = await this.parent.listFiles(parentPrefix);
|
|
80
110
|
const out: ContentEntry[] = [];
|
|
81
111
|
for (const e of entries) {
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
|
|
11
11
|
import type { Doc } from '../schemas/Doc.js';
|
|
12
12
|
import type { DocBlock, TitleBlockInput, SectionHeaderInput } from '../schemas/BlockTemplates.js';
|
|
13
|
-
import type {
|
|
13
|
+
import type { TransformStyleInput, TransformOptions, TransformResult } from './types.js';
|
|
14
14
|
import { resolveTransformStyle } from './registry.js';
|
|
15
15
|
import { analyzeBlocks, extractDocImages } from './blockAnalyzer.js';
|
|
16
16
|
import { selectAndBuild } from './templateSelector.js';
|
|
@@ -22,16 +22,16 @@ import { hashString } from '../random/SeededRandom.js';
|
|
|
22
22
|
* optimized for visual presentation.
|
|
23
23
|
*
|
|
24
24
|
* @param doc - The source Doc (not mutated).
|
|
25
|
-
* @param
|
|
26
|
-
* @param options - Optional seed, images, theme override.
|
|
25
|
+
* @param style - Built-in/registry id or a call-scoped declarative style.
|
|
26
|
+
* @param options - Optional seed, images, theme override, and explicit registry.
|
|
27
27
|
* @returns TransformResult with the new Doc and stats.
|
|
28
28
|
*/
|
|
29
29
|
export function applyTransform(
|
|
30
30
|
doc: Doc,
|
|
31
|
-
|
|
31
|
+
style: TransformStyleInput,
|
|
32
32
|
options?: TransformOptions,
|
|
33
33
|
): TransformResult {
|
|
34
|
-
const baseConfig = resolveTransformStyle(
|
|
34
|
+
const baseConfig = resolveTransformStyle(style, options?.registry);
|
|
35
35
|
const config = options?.overrides
|
|
36
36
|
? { ...baseConfig, ...options.overrides, id: baseConfig.id }
|
|
37
37
|
: baseConfig;
|
package/src/transform/index.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export { applyTransform } from './applyTransform.js';
|
|
2
|
-
export {
|
|
2
|
+
export { createTransformStyleRegistry } from './registry.js';
|
|
3
3
|
|
|
4
4
|
export {
|
|
5
5
|
resolveTransformStyle,
|
|
@@ -10,8 +10,10 @@ export {
|
|
|
10
10
|
|
|
11
11
|
export type {
|
|
12
12
|
TransformStyleId,
|
|
13
|
+
TransformStyleInput,
|
|
13
14
|
TransformStyleSummary,
|
|
14
15
|
TransformStyleConfig,
|
|
16
|
+
TransformStyleRegistry,
|
|
15
17
|
TransformImage,
|
|
16
18
|
TransformOptions,
|
|
17
19
|
TransformResult,
|
|
@@ -1,78 +1,198 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Transform Style Registry
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
* `registerTheme` — registered styles take precedence over built-ins,
|
|
7
|
-
* unknown ids fall back to the default.
|
|
4
|
+
* Immutable built-in lookup plus explicit caller-owned custom registries.
|
|
5
|
+
* There is deliberately no process-global mutable registry.
|
|
8
6
|
*/
|
|
9
7
|
|
|
10
|
-
import type { TransformStyleConfig,
|
|
8
|
+
import type { TransformStyleConfig, TransformStyleInput, TransformStyleRegistry } from './types.js';
|
|
11
9
|
import { documentaryStyle } from './styles/documentary.js';
|
|
12
10
|
import { magazineStyle } from './styles/magazine.js';
|
|
13
11
|
import { dataDrivenStyle } from './styles/dataDriven.js';
|
|
14
12
|
import { narrativeStyle } from './styles/narrative.js';
|
|
15
13
|
import { minimalStyle } from './styles/minimal.js';
|
|
14
|
+
import { cloneAndFreezeData } from '../internal/immutable.js';
|
|
16
15
|
|
|
17
16
|
/** All built-in transform styles, keyed by id. */
|
|
18
|
-
const TRANSFORM_STYLES: Record<string, TransformStyleConfig
|
|
19
|
-
[documentaryStyle.id]: documentaryStyle,
|
|
20
|
-
[magazineStyle.id]: magazineStyle,
|
|
21
|
-
[dataDrivenStyle.id]: dataDrivenStyle,
|
|
22
|
-
[narrativeStyle.id]: narrativeStyle,
|
|
23
|
-
[minimalStyle.id]: minimalStyle,
|
|
24
|
-
};
|
|
25
|
-
|
|
26
|
-
/**
|
|
27
|
-
* Id aliases: the data-driven style's file/export are named `dataDriven`
|
|
28
|
-
* but its registered id is hyphenated — accept both.
|
|
29
|
-
*/
|
|
30
|
-
const STYLE_ALIASES: Record<string, string> = {
|
|
31
|
-
dataDriven: 'data-driven',
|
|
32
|
-
};
|
|
33
|
-
|
|
34
|
-
/** Host-registered custom styles (registered ids win over built-ins). */
|
|
35
|
-
const registeredStyles = new Map<string, TransformStyleConfig>();
|
|
17
|
+
const TRANSFORM_STYLES: Readonly<Record<string, TransformStyleConfig>> = Object.freeze({
|
|
18
|
+
[documentaryStyle.id]: cloneAndFreezeData(documentaryStyle),
|
|
19
|
+
[magazineStyle.id]: cloneAndFreezeData(magazineStyle),
|
|
20
|
+
[dataDrivenStyle.id]: cloneAndFreezeData(dataDrivenStyle),
|
|
21
|
+
[narrativeStyle.id]: cloneAndFreezeData(narrativeStyle),
|
|
22
|
+
[minimalStyle.id]: cloneAndFreezeData(minimalStyle),
|
|
23
|
+
});
|
|
36
24
|
|
|
37
25
|
/** Default style used when no id is provided. */
|
|
38
26
|
export const DEFAULT_TRANSFORM_STYLE_ID = 'documentary';
|
|
39
27
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
28
|
+
const EXTRACTION_TYPES = new Set([
|
|
29
|
+
'stat',
|
|
30
|
+
'date',
|
|
31
|
+
'quote',
|
|
32
|
+
'comparison',
|
|
33
|
+
'fact',
|
|
34
|
+
'impactLine',
|
|
35
|
+
'list',
|
|
36
|
+
'definition',
|
|
37
|
+
]);
|
|
38
|
+
const TRANSITION_STYLES = new Set(['cut', 'fade', 'dissolve', 'mixed']);
|
|
39
|
+
|
|
40
|
+
function validateTransformStyle(style: TransformStyleConfig): void {
|
|
41
|
+
const candidate = style as unknown as Record<string, unknown>;
|
|
42
|
+
const fail = (message: string): never => {
|
|
43
|
+
throw new TypeError(`Invalid transform style: ${message}`);
|
|
44
|
+
};
|
|
45
|
+
for (const key of ['id', 'name', 'description']) {
|
|
46
|
+
if (typeof candidate[key] !== 'string' || !(candidate[key] as string).trim()) {
|
|
47
|
+
fail(`${key} must be a non-empty string`);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
for (const key of ['minConfidence', 'transformRatio']) {
|
|
51
|
+
const value = candidate[key];
|
|
52
|
+
if (typeof value !== 'number' || !Number.isFinite(value) || value < 0 || value > 1) {
|
|
53
|
+
fail(`${key} must be a finite number between 0 and 1`);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
if (
|
|
57
|
+
!Array.isArray(candidate.preferredTypes) ||
|
|
58
|
+
candidate.preferredTypes.some((type) => !EXTRACTION_TYPES.has(type))
|
|
59
|
+
) {
|
|
60
|
+
fail('preferredTypes contains an unknown extraction type');
|
|
61
|
+
}
|
|
62
|
+
if (
|
|
63
|
+
!Array.isArray(candidate.colorSchemes) ||
|
|
64
|
+
candidate.colorSchemes.length === 0 ||
|
|
65
|
+
candidate.colorSchemes.some((scheme) => typeof scheme !== 'string' || !scheme)
|
|
66
|
+
) {
|
|
67
|
+
fail('colorSchemes must contain at least one non-empty string');
|
|
68
|
+
}
|
|
69
|
+
for (const key of ['insertSectionHeaders', 'interleaveImages']) {
|
|
70
|
+
if (typeof candidate[key] !== 'boolean') fail(`${key} must be a boolean`);
|
|
71
|
+
}
|
|
72
|
+
const blocks = candidate.blocksPerSection as { max?: unknown } | undefined;
|
|
73
|
+
if (!blocks || !Number.isInteger(blocks.max) || (blocks.max as number) < 1) {
|
|
74
|
+
fail('blocksPerSection.max must be a positive integer');
|
|
75
|
+
}
|
|
76
|
+
if (
|
|
77
|
+
typeof candidate.transitionStyle !== 'string' ||
|
|
78
|
+
!TRANSITION_STYLES.has(candidate.transitionStyle)
|
|
79
|
+
) {
|
|
80
|
+
fail('transitionStyle must be cut, fade, dissolve, or mixed');
|
|
81
|
+
}
|
|
82
|
+
const templateMap = candidate.templateMap;
|
|
83
|
+
if (templateMap !== undefined) {
|
|
84
|
+
if (!templateMap || typeof templateMap !== 'object' || Array.isArray(templateMap)) {
|
|
85
|
+
fail('templateMap must be an object');
|
|
86
|
+
}
|
|
87
|
+
for (const [type, template] of Object.entries(templateMap as Record<string, unknown>)) {
|
|
88
|
+
if (!EXTRACTION_TYPES.has(type) || typeof template !== 'string' || !template) {
|
|
89
|
+
fail('templateMap must map known extraction types to non-empty template names');
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
if (
|
|
94
|
+
candidate.suggestedThemeId !== undefined &&
|
|
95
|
+
(typeof candidate.suggestedThemeId !== 'string' || !candidate.suggestedThemeId)
|
|
96
|
+
) {
|
|
97
|
+
fail('suggestedThemeId must be a non-empty string');
|
|
98
|
+
}
|
|
99
|
+
const pacingValue = candidate.pacing;
|
|
100
|
+
if (pacingValue !== undefined) {
|
|
101
|
+
if (!pacingValue || typeof pacingValue !== 'object' || Array.isArray(pacingValue)) {
|
|
102
|
+
fail('pacing must be an object');
|
|
103
|
+
}
|
|
104
|
+
const pacing = pacingValue as { intro?: unknown; outro?: unknown };
|
|
105
|
+
for (const key of ['intro', 'outro'] as const) {
|
|
106
|
+
if (pacing[key] !== undefined && typeof pacing[key] !== 'boolean') {
|
|
107
|
+
fail(`pacing.${key} must be a boolean`);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
const budget = candidate.budget as { slidesPerMinute?: unknown } | undefined;
|
|
112
|
+
if (
|
|
113
|
+
candidate.budget !== undefined &&
|
|
114
|
+
(!budget || typeof budget !== 'object' || Array.isArray(budget))
|
|
115
|
+
) {
|
|
116
|
+
fail('budget must be an object');
|
|
117
|
+
}
|
|
118
|
+
if (budget && budget.slidesPerMinute !== undefined) {
|
|
119
|
+
const value = budget.slidesPerMinute;
|
|
120
|
+
if (typeof value !== 'number' || !Number.isFinite(value) || value <= 0) {
|
|
121
|
+
fail('budget.slidesPerMinute must be a positive finite number');
|
|
122
|
+
}
|
|
123
|
+
}
|
|
47
124
|
}
|
|
48
125
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
126
|
+
function styleSnapshot(style: TransformStyleConfig): TransformStyleConfig {
|
|
127
|
+
validateTransformStyle(style);
|
|
128
|
+
return cloneAndFreezeData(style);
|
|
52
129
|
}
|
|
53
130
|
|
|
54
|
-
/**
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
131
|
+
/** Stored documents emitted before the canonical kebab-case id. Not exported. */
|
|
132
|
+
function normalizeBuiltinStyleId(id: string): string {
|
|
133
|
+
return id === 'dataDriven' ? 'data-driven' : id;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function resolveBuiltinStyle(id: string): TransformStyleConfig {
|
|
60
137
|
return (
|
|
61
|
-
|
|
62
|
-
TRANSFORM_STYLES[canonical] ??
|
|
63
|
-
TRANSFORM_STYLES[DEFAULT_TRANSFORM_STYLE_ID]
|
|
138
|
+
TRANSFORM_STYLES[normalizeBuiltinStyleId(id)] ?? TRANSFORM_STYLES[DEFAULT_TRANSFORM_STYLE_ID]
|
|
64
139
|
);
|
|
65
140
|
}
|
|
66
141
|
|
|
67
|
-
/**
|
|
68
|
-
|
|
69
|
-
|
|
142
|
+
/**
|
|
143
|
+
* Create a caller-owned registry. Custom styles are validated, cloned, and
|
|
144
|
+
* deeply frozen at registration time. Built-in resolution remains in the
|
|
145
|
+
* top-level helpers, separate from the registry's caller-owned state.
|
|
146
|
+
*/
|
|
147
|
+
export function createTransformStyleRegistry(
|
|
148
|
+
initialStyles: readonly TransformStyleConfig[] = [],
|
|
149
|
+
): TransformStyleRegistry {
|
|
150
|
+
const registeredStyles = new Map<string, TransformStyleConfig>();
|
|
151
|
+
const registry: TransformStyleRegistry = {
|
|
152
|
+
register(style): void {
|
|
153
|
+
const snapshot = styleSnapshot(style);
|
|
154
|
+
registeredStyles.set(snapshot.id, snapshot);
|
|
155
|
+
},
|
|
156
|
+
unregister(id): boolean {
|
|
157
|
+
return registeredStyles.delete(id);
|
|
158
|
+
},
|
|
159
|
+
get(id): TransformStyleConfig | undefined {
|
|
160
|
+
return registeredStyles.get(id);
|
|
161
|
+
},
|
|
162
|
+
list(): TransformStyleConfig[] {
|
|
163
|
+
return Array.from(registeredStyles.values());
|
|
164
|
+
},
|
|
165
|
+
};
|
|
166
|
+
for (const style of initialStyles) registry.register(style);
|
|
167
|
+
return Object.freeze(registry);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Resolve a built-in id, explicit-registry id, or call-scoped style config.
|
|
172
|
+
* Unknown ids fall back to the default style.
|
|
173
|
+
*/
|
|
174
|
+
export function resolveTransformStyle(
|
|
175
|
+
style: TransformStyleInput,
|
|
176
|
+
registry?: TransformStyleRegistry,
|
|
177
|
+
): TransformStyleConfig {
|
|
178
|
+
if (typeof style !== 'string') return styleSnapshot(style);
|
|
179
|
+
return registry?.get(style) ?? resolveBuiltinStyle(style);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/** Get built-in ids plus ids from an optional caller-owned registry. */
|
|
183
|
+
export function getTransformStyleIds(registry?: TransformStyleRegistry): string[] {
|
|
184
|
+
return Array.from(
|
|
185
|
+
new Set([
|
|
186
|
+
...Object.keys(TRANSFORM_STYLES),
|
|
187
|
+
...(registry?.list().map((style) => style.id) ?? []),
|
|
188
|
+
]),
|
|
189
|
+
);
|
|
70
190
|
}
|
|
71
191
|
|
|
72
|
-
/** Get
|
|
73
|
-
export function getTransformStyleSummaries()
|
|
74
|
-
return getTransformStyleIds().map((id) => {
|
|
75
|
-
const { name, description } = resolveTransformStyle(id);
|
|
192
|
+
/** Get summaries for built-ins plus an optional caller-owned registry. */
|
|
193
|
+
export function getTransformStyleSummaries(registry?: TransformStyleRegistry) {
|
|
194
|
+
return getTransformStyleIds(registry).map((id) => {
|
|
195
|
+
const { name, description } = resolveTransformStyle(id, registry);
|
|
76
196
|
return { id, name, description };
|
|
77
197
|
});
|
|
78
198
|
}
|
|
@@ -22,6 +22,7 @@ import type { TransformStyleConfig, TransformImage } from './types.js';
|
|
|
22
22
|
import type { AnalyzedBlock } from './blockAnalyzer.js';
|
|
23
23
|
import { mapElementToBlock } from '../generate/templateMapper.js';
|
|
24
24
|
import { SeededRandom } from '../random/SeededRandom.js';
|
|
25
|
+
import { estimateTimeFromText } from '../timing/narrationTiming.js';
|
|
25
26
|
|
|
26
27
|
/** Accent positions to rotate through. */
|
|
27
28
|
const ACCENT_POSITIONS: AccentPosition[] = [
|
|
@@ -219,6 +220,7 @@ function buildBlockSequence(
|
|
|
219
220
|
audioSegment: ab.block.audioSegment,
|
|
220
221
|
title: ab.block.title,
|
|
221
222
|
colorScheme: config.colorSchemes[colorIndex % config.colorSchemes.length],
|
|
223
|
+
sourceBlockId: ab.block.id,
|
|
222
224
|
};
|
|
223
225
|
blocks.push(headerBlock);
|
|
224
226
|
insertedCount++;
|
|
@@ -245,13 +247,27 @@ function buildBlockSequence(
|
|
|
245
247
|
accentPositionIndex++;
|
|
246
248
|
}
|
|
247
249
|
|
|
250
|
+
// `sourcePosition` is a CHAR OFFSET into the analyzed plain text;
|
|
251
|
+
// `sourceStartTime` is doc-timeline SECONDS. Convert via spoken-word
|
|
252
|
+
// proportion over the source block's timing — which, when the doc
|
|
253
|
+
// carries a narration take (applyNarrationTiming ran upstream), is
|
|
254
|
+
// the recorded voice: summarized slides land where the words are
|
|
255
|
+
// actually spoken.
|
|
256
|
+
const slideShare = ab.block.duration > 0 ? ab.block.duration / extractions.length : 6;
|
|
257
|
+
const offsetInBlock =
|
|
258
|
+
ab.block.duration > 0
|
|
259
|
+
? estimateTimeFromText(ab.plainText, sel.element.sourcePosition, ab.block.duration)
|
|
260
|
+
: 0;
|
|
248
261
|
let templateBlock = mapElementToBlock(sel.element, {
|
|
249
262
|
id: `transform-${blockIdCounter++}`,
|
|
250
|
-
duration:
|
|
263
|
+
duration: slideShare,
|
|
251
264
|
audioSegment: ab.block.audioSegment,
|
|
252
265
|
colorScheme,
|
|
253
266
|
accentImage,
|
|
254
|
-
sourceStartTime:
|
|
267
|
+
sourceStartTime: ab.block.startTime + offsetInBlock,
|
|
268
|
+
sourceDuration: slideShare,
|
|
269
|
+
sourceBlockId: ab.block.id,
|
|
270
|
+
sourceCharOffset: sel.element.sourcePosition,
|
|
255
271
|
});
|
|
256
272
|
|
|
257
273
|
// Style-level extraction→template remap (v2 contract). Only known
|
|
@@ -302,6 +318,7 @@ function buildBlockSequence(
|
|
|
302
318
|
imageSrc: img.src,
|
|
303
319
|
imageAlt: img.alt ?? '',
|
|
304
320
|
ambientMotion: rng.pick(AMBIENT_MOTIONS) ?? 'zoomIn',
|
|
321
|
+
sourceBlockId: ab.block.id,
|
|
305
322
|
};
|
|
306
323
|
blocks.push(imageBlock);
|
|
307
324
|
imageIndex++;
|
|
@@ -327,6 +344,8 @@ function translateTemplateBlock(block: TemplateBlock, target: string): TemplateB
|
|
|
327
344
|
audioSegment: block.audioSegment,
|
|
328
345
|
sourceStartTime: block.sourceStartTime,
|
|
329
346
|
sourceDuration: block.sourceDuration,
|
|
347
|
+
sourceBlockId: block.sourceBlockId,
|
|
348
|
+
sourceCharOffset: block.sourceCharOffset,
|
|
330
349
|
};
|
|
331
350
|
|
|
332
351
|
if (block.template === 'quote') {
|