@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
|
@@ -2,7 +2,7 @@ import {
|
|
|
2
2
|
DEFAULT_THEME,
|
|
3
3
|
applySurface,
|
|
4
4
|
resolveFontFamily
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-W4O2TX5I.js";
|
|
6
6
|
|
|
7
7
|
// src/jsonForm/chooseControl.ts
|
|
8
8
|
var ENUM_SEGMENTED_LIMIT = 4;
|
|
@@ -68,14 +68,48 @@ function hasFiniteRange(schema) {
|
|
|
68
68
|
}
|
|
69
69
|
|
|
70
70
|
// src/jsonForm/pointer.ts
|
|
71
|
+
function escapePointerSegment(segment) {
|
|
72
|
+
return segment.replace(/~/g, "~0").replace(/\//g, "~1");
|
|
73
|
+
}
|
|
71
74
|
function toPointer(path) {
|
|
72
|
-
if (path === ""
|
|
75
|
+
if (path === "") return "";
|
|
73
76
|
if (path.startsWith("/")) return path;
|
|
74
|
-
return "/" + path.split(".").map(
|
|
77
|
+
return "/" + path.split(".").map(escapePointerSegment).join("/");
|
|
78
|
+
}
|
|
79
|
+
function appendPointer(base, segment) {
|
|
80
|
+
const pointer = toPointer(base);
|
|
81
|
+
pointerSegments(pointer);
|
|
82
|
+
return `${pointer}/${escapePointerSegment(segment)}`;
|
|
75
83
|
}
|
|
76
84
|
function pointerSegments(pointer) {
|
|
77
85
|
if (pointer === "") return [];
|
|
78
|
-
|
|
86
|
+
if (!pointer.startsWith("/")) {
|
|
87
|
+
throw new TypeError(`Invalid JSON Pointer ${JSON.stringify(pointer)}: expected "" or "/..."`);
|
|
88
|
+
}
|
|
89
|
+
return pointer.slice(1).split("/").map((seg) => {
|
|
90
|
+
if (/~(?:[^01]|$)/.test(seg)) {
|
|
91
|
+
throw new TypeError(
|
|
92
|
+
`Invalid JSON Pointer ${JSON.stringify(pointer)}: "~" must be escaped as "~0"`
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
return seg.replace(/~1/g, "/").replace(/~0/g, "~");
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
function arrayIndex(segment) {
|
|
99
|
+
if (!/^(?:0|[1-9]\d*)$/.test(segment)) return void 0;
|
|
100
|
+
const index = Number(segment);
|
|
101
|
+
return Number.isSafeInteger(index) && index <= 4294967294 ? index : void 0;
|
|
102
|
+
}
|
|
103
|
+
function ownValue(object, key) {
|
|
104
|
+
return Object.prototype.hasOwnProperty.call(object, key) ? object[key] : void 0;
|
|
105
|
+
}
|
|
106
|
+
function setOwn(object, key, value) {
|
|
107
|
+
Object.defineProperty(object, key, {
|
|
108
|
+
value,
|
|
109
|
+
enumerable: true,
|
|
110
|
+
configurable: true,
|
|
111
|
+
writable: true
|
|
112
|
+
});
|
|
79
113
|
}
|
|
80
114
|
function getByPointer(data, path) {
|
|
81
115
|
const segments = pointerSegments(toPointer(path));
|
|
@@ -83,11 +117,11 @@ function getByPointer(data, path) {
|
|
|
83
117
|
for (const seg of segments) {
|
|
84
118
|
if (cur === null || cur === void 0) return void 0;
|
|
85
119
|
if (Array.isArray(cur)) {
|
|
86
|
-
const idx =
|
|
87
|
-
if (
|
|
120
|
+
const idx = arrayIndex(seg);
|
|
121
|
+
if (idx === void 0 || idx >= cur.length) return void 0;
|
|
88
122
|
cur = cur[idx];
|
|
89
123
|
} else if (typeof cur === "object") {
|
|
90
|
-
cur = cur
|
|
124
|
+
cur = ownValue(cur, seg);
|
|
91
125
|
} else {
|
|
92
126
|
return void 0;
|
|
93
127
|
}
|
|
@@ -103,28 +137,35 @@ function setRec(node, segments, i, value) {
|
|
|
103
137
|
const seg = segments[i];
|
|
104
138
|
const isLast = i === segments.length - 1;
|
|
105
139
|
const nextSeg = isLast ? void 0 : segments[i + 1];
|
|
106
|
-
const writingArrayChild = nextSeg !== void 0 &&
|
|
140
|
+
const writingArrayChild = nextSeg !== void 0 && arrayIndex(nextSeg) !== void 0;
|
|
107
141
|
if (Array.isArray(node)) {
|
|
108
|
-
const idx =
|
|
142
|
+
const idx = arrayIndex(seg);
|
|
143
|
+
if (idx === void 0) {
|
|
144
|
+
throw new TypeError(`Invalid array index ${JSON.stringify(seg)} in JSON Pointer`);
|
|
145
|
+
}
|
|
109
146
|
const next = node.slice();
|
|
110
147
|
next[idx] = isLast ? value : setRec(node[idx] ?? (writingArrayChild ? [] : {}), segments, i + 1, value);
|
|
111
148
|
return next;
|
|
112
149
|
}
|
|
113
150
|
const obj = node && typeof node === "object" ? { ...node } : {};
|
|
114
|
-
|
|
151
|
+
const previous = ownValue(obj, seg);
|
|
152
|
+
setOwn(
|
|
153
|
+
obj,
|
|
154
|
+
seg,
|
|
155
|
+
isLast ? value : setRec(previous ?? (writingArrayChild ? [] : {}), segments, i + 1, value)
|
|
156
|
+
);
|
|
115
157
|
return obj;
|
|
116
158
|
}
|
|
117
159
|
function resolveRef(schema, root) {
|
|
118
160
|
if (!schema.$ref) return schema;
|
|
119
161
|
const ref = schema.$ref;
|
|
162
|
+
if (ref === "#") return root;
|
|
120
163
|
if (!ref.startsWith("#/")) return void 0;
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
cur = cur[seg];
|
|
164
|
+
try {
|
|
165
|
+
return getByPointer(root, ref.slice(1));
|
|
166
|
+
} catch {
|
|
167
|
+
return void 0;
|
|
126
168
|
}
|
|
127
|
-
return cur ?? void 0;
|
|
128
169
|
}
|
|
129
170
|
|
|
130
171
|
// src/jsonForm/evaluateWhen.ts
|
|
@@ -218,7 +259,9 @@ export {
|
|
|
218
259
|
chooseControl,
|
|
219
260
|
primaryType,
|
|
220
261
|
arrayItemKind,
|
|
262
|
+
escapePointerSegment,
|
|
221
263
|
toPointer,
|
|
264
|
+
appendPointer,
|
|
222
265
|
pointerSegments,
|
|
223
266
|
getByPointer,
|
|
224
267
|
setByPointer,
|
|
@@ -229,4 +272,4 @@ export {
|
|
|
229
272
|
buildJsonFormTokens,
|
|
230
273
|
resolveJsonFormTheme
|
|
231
274
|
};
|
|
232
|
-
//# sourceMappingURL=chunk-
|
|
275
|
+
//# sourceMappingURL=chunk-2PWOO4ZE.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/jsonForm/chooseControl.ts","../src/jsonForm/pointer.ts","../src/jsonForm/evaluateWhen.ts","../src/jsonForm/inferSchema.ts","../src/jsonForm/tokens.ts"],"sourcesContent":["/**\n * Pick a `ControlKind` for a schema node. The single dispatcher used by\n * both `<JsonView>` and `<JsonEditor>` so view and edit modes always\n * agree on what each field _is_, even when their UIs differ.\n *\n * Order of precedence:\n * 1. Explicit `squisq.control` hint\n * 2. `format` keyword (color/date/time/datetime/markdown/textarea)\n * 3. Enum cardinality (segmented vs. combobox)\n * 4. Numeric range (slider when both min and max present)\n * 5. String length (multiline when maxLength > 200)\n * 6. Type-default (object/array/boolean/string/number)\n */\n\nimport type { ControlKind, SquisqAnnotatedSchema } from './types.js';\n\nconst ENUM_SEGMENTED_LIMIT = 4;\nconst MULTILINE_LENGTH_THRESHOLD = 200;\n\nexport function chooseControl(schema: SquisqAnnotatedSchema): ControlKind {\n const hint = schema.squisq?.control;\n if (hint) return hint;\n\n const type = primaryType(schema);\n\n if (schema.format && type === 'string') {\n switch (schema.format) {\n case 'color':\n return 'color';\n case 'date':\n return 'date';\n case 'time':\n return 'time';\n case 'date-time':\n return 'datetime';\n case 'markdown':\n return 'richtext';\n case 'textarea':\n return 'multiline';\n }\n }\n\n if (schema.enum && schema.enum.length > 0) {\n return schema.enum.length <= ENUM_SEGMENTED_LIMIT ? 'segmented' : 'combobox';\n }\n\n if (schema.oneOf || schema.anyOf) return 'tabs';\n\n switch (type) {\n case 'array':\n return arrayItemKind(schema) === 'object' ? 'card-stack' : 'chip-bin';\n case 'object':\n return 'group';\n case 'boolean':\n return 'toggle';\n case 'integer':\n case 'number':\n return hasFiniteRange(schema) ? 'slider' : 'stepper';\n case 'string':\n if (typeof schema.maxLength === 'number' && schema.maxLength > MULTILINE_LENGTH_THRESHOLD) {\n return 'multiline';\n }\n return 'text';\n default:\n return 'text';\n }\n}\n\n/** Return the first concrete type of a schema, ignoring `null` for nullable shorthand. */\nexport function primaryType(schema: SquisqAnnotatedSchema): string | undefined {\n const t = schema.type;\n if (typeof t === 'string') return t;\n if (Array.isArray(t)) return t.find((x) => x !== 'null');\n return undefined;\n}\n\n/** Inspect `items` to decide whether an array holds objects or primitives. */\nexport function arrayItemKind(schema: SquisqAnnotatedSchema): 'object' | 'primitive' {\n const items = Array.isArray(schema.items) ? schema.items[0] : schema.items;\n if (!items) return 'primitive';\n return primaryType(items) === 'object' ? 'object' : 'primitive';\n}\n\nfunction hasFiniteRange(schema: SquisqAnnotatedSchema): boolean {\n const min = schema.minimum ?? schema.exclusiveMinimum;\n const max = schema.maximum ?? schema.exclusiveMaximum;\n return typeof min === 'number' && typeof max === 'number';\n}\n","/**\n * JSON Pointer helpers (RFC 6901) plus immutable get/set, plus a tiny\n * dotted-path normalizer so hosts can write `user.role` instead of\n * `/user/role`. `$ref` resolution is local-only (`#/$defs/Foo`).\n */\n\nimport type { SquisqAnnotatedSchema } from './types.js';\n\n/** Escape one object-member name for use as an RFC 6901 pointer segment. */\nexport function escapePointerSegment(segment: string): string {\n return segment.replace(/~/g, '~0').replace(/\\//g, '~1');\n}\n\n/** Normalize a path to a JSON Pointer string. Accepts both forms. */\nexport function toPointer(path: string): string {\n if (path === '') return '';\n if (path.startsWith('/')) return path;\n // Dotted form: split on `.` and escape per RFC 6901.\n return '/' + path.split('.').map(escapePointerSegment).join('/');\n}\n\n/** Append one member name to a dotted path or validated JSON Pointer. */\nexport function appendPointer(base: string, segment: string): string {\n const pointer = toPointer(base);\n pointerSegments(pointer); // validate explicit pointer syntax before composing\n return `${pointer}/${escapePointerSegment(segment)}`;\n}\n\n/** Split a JSON Pointer into decoded segments. Empty string → []. */\nexport function pointerSegments(pointer: string): string[] {\n if (pointer === '') return [];\n if (!pointer.startsWith('/')) {\n throw new TypeError(`Invalid JSON Pointer ${JSON.stringify(pointer)}: expected \"\" or \"/...\"`);\n }\n return pointer\n .slice(1)\n .split('/')\n .map((seg) => {\n if (/~(?:[^01]|$)/.test(seg)) {\n throw new TypeError(\n `Invalid JSON Pointer ${JSON.stringify(pointer)}: \"~\" must be escaped as \"~0\"`,\n );\n }\n return seg.replace(/~1/g, '/').replace(/~0/g, '~');\n });\n}\n\n/** Parse the canonical array-index spelling used by JSON Pointer consumers. */\nfunction arrayIndex(segment: string): number | undefined {\n if (!/^(?:0|[1-9]\\d*)$/.test(segment)) return undefined;\n const index = Number(segment);\n // 2^32 - 1 is the Array length sentinel, not an element index.\n return Number.isSafeInteger(index) && index <= 0xffff_fffe ? index : undefined;\n}\n\nfunction ownValue(object: object, key: string): unknown {\n return Object.prototype.hasOwnProperty.call(object, key)\n ? (object as Record<string, unknown>)[key]\n : undefined;\n}\n\n/** Define a data member without invoking Object.prototype.__proto__'s setter. */\nfunction setOwn(object: Record<string, unknown>, key: string, value: unknown): void {\n Object.defineProperty(object, key, {\n value,\n enumerable: true,\n configurable: true,\n writable: true,\n });\n}\n\n/** Read a value from data by path (dotted or pointer). Returns undefined if missing. */\nexport function getByPointer(data: unknown, path: string): unknown {\n const segments = pointerSegments(toPointer(path));\n let cur: unknown = data;\n for (const seg of segments) {\n if (cur === null || cur === undefined) return undefined;\n if (Array.isArray(cur)) {\n const idx = arrayIndex(seg);\n if (idx === undefined || idx >= cur.length) return undefined;\n cur = cur[idx];\n } else if (typeof cur === 'object') {\n cur = ownValue(cur, seg);\n } else {\n return undefined;\n }\n }\n return cur;\n}\n\n/**\n * Return a structurally-shared copy of `data` with the value at `path`\n * replaced. Intermediate objects/arrays are cloned along the path; the\n * rest is shared by reference. Creates intermediate objects if missing.\n */\nexport function setByPointer<T>(data: T, path: string, value: unknown): T {\n const segments = pointerSegments(toPointer(path));\n if (segments.length === 0) return value as T;\n return setRec(data, segments, 0, value) as T;\n}\n\nfunction setRec(node: unknown, segments: string[], i: number, value: unknown): unknown {\n const seg = segments[i];\n const isLast = i === segments.length - 1;\n\n // Decide whether the slot we're about to write into is an array index.\n const nextSeg = isLast ? undefined : segments[i + 1];\n const writingArrayChild = nextSeg !== undefined && arrayIndex(nextSeg) !== undefined;\n\n if (Array.isArray(node)) {\n const idx = arrayIndex(seg);\n if (idx === undefined) {\n throw new TypeError(`Invalid array index ${JSON.stringify(seg)} in JSON Pointer`);\n }\n const next = node.slice();\n next[idx] = isLast\n ? value\n : setRec(node[idx] ?? (writingArrayChild ? [] : {}), segments, i + 1, value);\n return next;\n }\n\n const obj = (node && typeof node === 'object' ? { ...(node as object) } : {}) as Record<\n string,\n unknown\n >;\n const previous = ownValue(obj, seg);\n setOwn(\n obj,\n seg,\n isLast ? value : setRec(previous ?? (writingArrayChild ? [] : {}), segments, i + 1, value),\n );\n return obj;\n}\n\n/**\n * Resolve a local `$ref` (e.g. `#/$defs/Foo`) against the root schema.\n * Returns the original schema if no `$ref` is present, or `undefined`\n * if the reference cannot be resolved. Cross-document refs are not\n * supported.\n */\nexport function resolveRef(\n schema: SquisqAnnotatedSchema,\n root: SquisqAnnotatedSchema,\n): SquisqAnnotatedSchema | undefined {\n if (!schema.$ref) return schema;\n const ref = schema.$ref;\n if (ref === '#') return root;\n if (!ref.startsWith('#/')) return undefined;\n try {\n return getByPointer(root, ref.slice(1)) as SquisqAnnotatedSchema | undefined;\n } catch {\n return undefined;\n }\n}\n","/**\n * Evaluate a `SquisqWhen` rule against the current root data. Pure;\n * renderers call this on every render with fresh data.\n */\n\nimport type { SquisqWhen } from './types.js';\nimport { getByPointer } from './pointer.js';\n\nexport function evaluateWhen(when: SquisqWhen, rootData: unknown): boolean {\n const actual = getByPointer(rootData, when.field);\n\n if (when.equals !== undefined) {\n return deepEqual(actual, when.equals);\n }\n if (when.oneOf !== undefined) {\n return when.oneOf.some((v) => deepEqual(actual, v));\n }\n if (when.matches !== undefined) {\n if (typeof actual !== 'string') return false;\n try {\n return new RegExp(when.matches).test(actual);\n } catch {\n return false;\n }\n }\n if (when.truthy !== undefined) {\n return Boolean(actual) === when.truthy;\n }\n // No operator specified — defaults to \"field has any defined value\".\n return actual !== undefined && actual !== null;\n}\n\n/**\n * Resolve a `boolean | SquisqWhen` flag (used by `hidden`/`disabled`)\n * to a final boolean. Literal booleans pass through.\n */\nexport function resolveFlag(flag: boolean | SquisqWhen | undefined, rootData: unknown): boolean {\n if (flag === undefined) return false;\n if (typeof flag === 'boolean') return flag;\n return evaluateWhen(flag, rootData);\n}\n\nfunction deepEqual(a: unknown, b: unknown): boolean {\n if (a === b) return true;\n if (a === null || b === null) return false;\n if (typeof a !== typeof b) return false;\n if (typeof a !== 'object') return false;\n if (Array.isArray(a)) {\n if (!Array.isArray(b) || a.length !== b.length) return false;\n return a.every((v, i) => deepEqual(v, b[i]));\n }\n if (Array.isArray(b)) return false;\n const ao = a as Record<string, unknown>;\n const bo = b as Record<string, unknown>;\n const ak = Object.keys(ao);\n const bk = Object.keys(bo);\n if (ak.length !== bk.length) return false;\n return ak.every((k) => deepEqual(ao[k], bo[k]));\n}\n","/**\n * Infer a JSON Schema from an example JSON value. Thin facade over\n * `genson-js` so consumers don't depend on it directly and we can swap\n * the implementation later.\n */\n\nimport { createSchema, mergeSchemas } from 'genson-js';\nimport type { SquisqAnnotatedSchema } from './types.js';\n\nexport interface InferSchemaOptions {\n /** Additional samples merged into the inferred schema for broader coverage. */\n additionalSamples?: readonly unknown[];\n}\n\nexport function inferSchema(\n sample: unknown,\n options: InferSchemaOptions = {},\n): SquisqAnnotatedSchema {\n const base = createSchema(sample) as SquisqAnnotatedSchema;\n if (!options.additionalSamples || options.additionalSamples.length === 0) {\n return base;\n }\n const extras = options.additionalSamples.map((s) => createSchema(s) as SquisqAnnotatedSchema);\n return mergeSchemas([base, ...extras] as never[]) as SquisqAnnotatedSchema;\n}\n","/**\n * Pure CSS-custom-property builder shared by `<JsonView>` (read-only, in\n * `@bendyline/squisq-react`) and `<JsonEditor>` (editable, in\n * `@bendyline/squisq-editor-react`).\n *\n * Both components derive an identical token bag from a Theme + resolved\n * Surface; they differ only in the CSS-var prefix (`--squisq-json` vs\n * `--squisq-jsonform`) so the viewer and editor can coexist on one page.\n * This function is the single source of that derivation. It is pure and\n * framework-free: it performs no `window` / `matchMedia` access — the\n * surface must already be resolved to a concrete `SurfaceScheme` (or left\n * `undefined` to skip surface application) by the calling React hook.\n */\n\nimport { applySurface, DEFAULT_THEME, resolveFontFamily } from '../schemas/index.js';\nimport type { SurfaceScheme, Theme } from '../schemas/index.js';\n\nexport interface BuildJsonFormTokensOptions {\n /**\n * CSS-var prefix WITHOUT the trailing dash, e.g. `--squisq-json` or\n * `--squisq-jsonform`. Each token key is `${prefix}-${name}`.\n */\n prefix: string;\n}\n\n/**\n * Build the CSS custom-property map for a JSON form/view surface.\n *\n * @param theme The theme to render with (falls back to `DEFAULT_THEME`).\n * @param surface Already-resolved concrete surface, or `undefined` to leave\n * the theme un-surfaced. (The `'auto'` → concrete resolution\n * is the React hook's job, so this stays pure.)\n * @param options `{ prefix }` — the CSS-var namespace.\n */\nexport function buildJsonFormTokens(\n theme: Theme | undefined,\n surface: SurfaceScheme | undefined,\n options: BuildJsonFormTokensOptions,\n): Record<string, string> {\n const { prefix } = options;\n const baseTheme = theme ?? DEFAULT_THEME;\n const finalTheme = surface ? applySurface(baseTheme, surface) : baseTheme;\n\n const titleFont = resolveFontFamily(finalTheme.typography.titleFont, 'system-ui, sans-serif');\n const bodyFont = resolveFontFamily(finalTheme.typography.bodyFont, 'system-ui, sans-serif');\n const monoFont = resolveFontFamily(\n finalTheme.typography.monoFont,\n 'ui-monospace, Consolas, monospace',\n );\n\n return {\n [`${prefix}-bg`]: finalTheme.colors.background,\n [`${prefix}-text`]: finalTheme.colors.text,\n [`${prefix}-muted`]: finalTheme.colors.textMuted,\n [`${prefix}-primary`]: finalTheme.colors.primary,\n [`${prefix}-accent`]: finalTheme.colors.secondary,\n [`${prefix}-warning`]: finalTheme.colors.warning,\n [`${prefix}-border`]: `color-mix(in srgb, ${finalTheme.colors.textMuted} 35%, transparent)`,\n [`${prefix}-input-bg`]: finalTheme.colors.backgroundLight,\n [`${prefix}-title-font`]: titleFont,\n [`${prefix}-body-font`]: bodyFont,\n [`${prefix}-mono-font`]: monoFont,\n [`${prefix}-radius`]: `${finalTheme.style.borderRadius ?? 8}px`,\n };\n}\n\n/**\n * Resolve the effective theme after surface application — the companion to\n * `buildJsonFormTokens` for callers that also need the `Theme` object (both\n * hooks return it alongside the style bag).\n */\nexport function resolveJsonFormTheme(\n theme: Theme | undefined,\n surface: SurfaceScheme | undefined,\n): Theme {\n const baseTheme = theme ?? DEFAULT_THEME;\n return surface ? applySurface(baseTheme, surface) : baseTheme;\n}\n"],"mappings":";;;;;;;AAgBA,IAAM,uBAAuB;AAC7B,IAAM,6BAA6B;AAE5B,SAAS,cAAc,QAA4C;AACxE,QAAM,OAAO,OAAO,QAAQ;AAC5B,MAAI,KAAM,QAAO;AAEjB,QAAM,OAAO,YAAY,MAAM;AAE/B,MAAI,OAAO,UAAU,SAAS,UAAU;AACtC,YAAQ,OAAO,QAAQ;AAAA,MACrB,KAAK;AACH,eAAO;AAAA,MACT,KAAK;AACH,eAAO;AAAA,MACT,KAAK;AACH,eAAO;AAAA,MACT,KAAK;AACH,eAAO;AAAA,MACT,KAAK;AACH,eAAO;AAAA,MACT,KAAK;AACH,eAAO;AAAA,IACX;AAAA,EACF;AAEA,MAAI,OAAO,QAAQ,OAAO,KAAK,SAAS,GAAG;AACzC,WAAO,OAAO,KAAK,UAAU,uBAAuB,cAAc;AAAA,EACpE;AAEA,MAAI,OAAO,SAAS,OAAO,MAAO,QAAO;AAEzC,UAAQ,MAAM;AAAA,IACZ,KAAK;AACH,aAAO,cAAc,MAAM,MAAM,WAAW,eAAe;AAAA,IAC7D,KAAK;AACH,aAAO;AAAA,IACT,KAAK;AACH,aAAO;AAAA,IACT,KAAK;AAAA,IACL,KAAK;AACH,aAAO,eAAe,MAAM,IAAI,WAAW;AAAA,IAC7C,KAAK;AACH,UAAI,OAAO,OAAO,cAAc,YAAY,OAAO,YAAY,4BAA4B;AACzF,eAAO;AAAA,MACT;AACA,aAAO;AAAA,IACT;AACE,aAAO;AAAA,EACX;AACF;AAGO,SAAS,YAAY,QAAmD;AAC7E,QAAM,IAAI,OAAO;AACjB,MAAI,OAAO,MAAM,SAAU,QAAO;AAClC,MAAI,MAAM,QAAQ,CAAC,EAAG,QAAO,EAAE,KAAK,CAAC,MAAM,MAAM,MAAM;AACvD,SAAO;AACT;AAGO,SAAS,cAAc,QAAuD;AACnF,QAAM,QAAQ,MAAM,QAAQ,OAAO,KAAK,IAAI,OAAO,MAAM,CAAC,IAAI,OAAO;AACrE,MAAI,CAAC,MAAO,QAAO;AACnB,SAAO,YAAY,KAAK,MAAM,WAAW,WAAW;AACtD;AAEA,SAAS,eAAe,QAAwC;AAC9D,QAAM,MAAM,OAAO,WAAW,OAAO;AACrC,QAAM,MAAM,OAAO,WAAW,OAAO;AACrC,SAAO,OAAO,QAAQ,YAAY,OAAO,QAAQ;AACnD;;;AC9EO,SAAS,qBAAqB,SAAyB;AAC5D,SAAO,QAAQ,QAAQ,MAAM,IAAI,EAAE,QAAQ,OAAO,IAAI;AACxD;AAGO,SAAS,UAAU,MAAsB;AAC9C,MAAI,SAAS,GAAI,QAAO;AACxB,MAAI,KAAK,WAAW,GAAG,EAAG,QAAO;AAEjC,SAAO,MAAM,KAAK,MAAM,GAAG,EAAE,IAAI,oBAAoB,EAAE,KAAK,GAAG;AACjE;AAGO,SAAS,cAAc,MAAc,SAAyB;AACnE,QAAM,UAAU,UAAU,IAAI;AAC9B,kBAAgB,OAAO;AACvB,SAAO,GAAG,OAAO,IAAI,qBAAqB,OAAO,CAAC;AACpD;AAGO,SAAS,gBAAgB,SAA2B;AACzD,MAAI,YAAY,GAAI,QAAO,CAAC;AAC5B,MAAI,CAAC,QAAQ,WAAW,GAAG,GAAG;AAC5B,UAAM,IAAI,UAAU,wBAAwB,KAAK,UAAU,OAAO,CAAC,yBAAyB;AAAA,EAC9F;AACA,SAAO,QACJ,MAAM,CAAC,EACP,MAAM,GAAG,EACT,IAAI,CAAC,QAAQ;AACZ,QAAI,eAAe,KAAK,GAAG,GAAG;AAC5B,YAAM,IAAI;AAAA,QACR,wBAAwB,KAAK,UAAU,OAAO,CAAC;AAAA,MACjD;AAAA,IACF;AACA,WAAO,IAAI,QAAQ,OAAO,GAAG,EAAE,QAAQ,OAAO,GAAG;AAAA,EACnD,CAAC;AACL;AAGA,SAAS,WAAW,SAAqC;AACvD,MAAI,CAAC,mBAAmB,KAAK,OAAO,EAAG,QAAO;AAC9C,QAAM,QAAQ,OAAO,OAAO;AAE5B,SAAO,OAAO,cAAc,KAAK,KAAK,SAAS,aAAc,QAAQ;AACvE;AAEA,SAAS,SAAS,QAAgB,KAAsB;AACtD,SAAO,OAAO,UAAU,eAAe,KAAK,QAAQ,GAAG,IAClD,OAAmC,GAAG,IACvC;AACN;AAGA,SAAS,OAAO,QAAiC,KAAa,OAAsB;AAClF,SAAO,eAAe,QAAQ,KAAK;AAAA,IACjC;AAAA,IACA,YAAY;AAAA,IACZ,cAAc;AAAA,IACd,UAAU;AAAA,EACZ,CAAC;AACH;AAGO,SAAS,aAAa,MAAe,MAAuB;AACjE,QAAM,WAAW,gBAAgB,UAAU,IAAI,CAAC;AAChD,MAAI,MAAe;AACnB,aAAW,OAAO,UAAU;AAC1B,QAAI,QAAQ,QAAQ,QAAQ,OAAW,QAAO;AAC9C,QAAI,MAAM,QAAQ,GAAG,GAAG;AACtB,YAAM,MAAM,WAAW,GAAG;AAC1B,UAAI,QAAQ,UAAa,OAAO,IAAI,OAAQ,QAAO;AACnD,YAAM,IAAI,GAAG;AAAA,IACf,WAAW,OAAO,QAAQ,UAAU;AAClC,YAAM,SAAS,KAAK,GAAG;AAAA,IACzB,OAAO;AACL,aAAO;AAAA,IACT;AAAA,EACF;AACA,SAAO;AACT;AAOO,SAAS,aAAgB,MAAS,MAAc,OAAmB;AACxE,QAAM,WAAW,gBAAgB,UAAU,IAAI,CAAC;AAChD,MAAI,SAAS,WAAW,EAAG,QAAO;AAClC,SAAO,OAAO,MAAM,UAAU,GAAG,KAAK;AACxC;AAEA,SAAS,OAAO,MAAe,UAAoB,GAAW,OAAyB;AACrF,QAAM,MAAM,SAAS,CAAC;AACtB,QAAM,SAAS,MAAM,SAAS,SAAS;AAGvC,QAAM,UAAU,SAAS,SAAY,SAAS,IAAI,CAAC;AACnD,QAAM,oBAAoB,YAAY,UAAa,WAAW,OAAO,MAAM;AAE3E,MAAI,MAAM,QAAQ,IAAI,GAAG;AACvB,UAAM,MAAM,WAAW,GAAG;AAC1B,QAAI,QAAQ,QAAW;AACrB,YAAM,IAAI,UAAU,uBAAuB,KAAK,UAAU,GAAG,CAAC,kBAAkB;AAAA,IAClF;AACA,UAAM,OAAO,KAAK,MAAM;AACxB,SAAK,GAAG,IAAI,SACR,QACA,OAAO,KAAK,GAAG,MAAM,oBAAoB,CAAC,IAAI,CAAC,IAAI,UAAU,IAAI,GAAG,KAAK;AAC7E,WAAO;AAAA,EACT;AAEA,QAAM,MAAO,QAAQ,OAAO,SAAS,WAAW,EAAE,GAAI,KAAgB,IAAI,CAAC;AAI3E,QAAM,WAAW,SAAS,KAAK,GAAG;AAClC;AAAA,IACE;AAAA,IACA;AAAA,IACA,SAAS,QAAQ,OAAO,aAAa,oBAAoB,CAAC,IAAI,CAAC,IAAI,UAAU,IAAI,GAAG,KAAK;AAAA,EAC3F;AACA,SAAO;AACT;AAQO,SAAS,WACd,QACA,MACmC;AACnC,MAAI,CAAC,OAAO,KAAM,QAAO;AACzB,QAAM,MAAM,OAAO;AACnB,MAAI,QAAQ,IAAK,QAAO;AACxB,MAAI,CAAC,IAAI,WAAW,IAAI,EAAG,QAAO;AAClC,MAAI;AACF,WAAO,aAAa,MAAM,IAAI,MAAM,CAAC,CAAC;AAAA,EACxC,QAAQ;AACN,WAAO;AAAA,EACT;AACF;;;ACjJO,SAAS,aAAa,MAAkB,UAA4B;AACzE,QAAM,SAAS,aAAa,UAAU,KAAK,KAAK;AAEhD,MAAI,KAAK,WAAW,QAAW;AAC7B,WAAO,UAAU,QAAQ,KAAK,MAAM;AAAA,EACtC;AACA,MAAI,KAAK,UAAU,QAAW;AAC5B,WAAO,KAAK,MAAM,KAAK,CAAC,MAAM,UAAU,QAAQ,CAAC,CAAC;AAAA,EACpD;AACA,MAAI,KAAK,YAAY,QAAW;AAC9B,QAAI,OAAO,WAAW,SAAU,QAAO;AACvC,QAAI;AACF,aAAO,IAAI,OAAO,KAAK,OAAO,EAAE,KAAK,MAAM;AAAA,IAC7C,QAAQ;AACN,aAAO;AAAA,IACT;AAAA,EACF;AACA,MAAI,KAAK,WAAW,QAAW;AAC7B,WAAO,QAAQ,MAAM,MAAM,KAAK;AAAA,EAClC;AAEA,SAAO,WAAW,UAAa,WAAW;AAC5C;AAMO,SAAS,YAAY,MAAwC,UAA4B;AAC9F,MAAI,SAAS,OAAW,QAAO;AAC/B,MAAI,OAAO,SAAS,UAAW,QAAO;AACtC,SAAO,aAAa,MAAM,QAAQ;AACpC;AAEA,SAAS,UAAU,GAAY,GAAqB;AAClD,MAAI,MAAM,EAAG,QAAO;AACpB,MAAI,MAAM,QAAQ,MAAM,KAAM,QAAO;AACrC,MAAI,OAAO,MAAM,OAAO,EAAG,QAAO;AAClC,MAAI,OAAO,MAAM,SAAU,QAAO;AAClC,MAAI,MAAM,QAAQ,CAAC,GAAG;AACpB,QAAI,CAAC,MAAM,QAAQ,CAAC,KAAK,EAAE,WAAW,EAAE,OAAQ,QAAO;AACvD,WAAO,EAAE,MAAM,CAAC,GAAG,MAAM,UAAU,GAAG,EAAE,CAAC,CAAC,CAAC;AAAA,EAC7C;AACA,MAAI,MAAM,QAAQ,CAAC,EAAG,QAAO;AAC7B,QAAM,KAAK;AACX,QAAM,KAAK;AACX,QAAM,KAAK,OAAO,KAAK,EAAE;AACzB,QAAM,KAAK,OAAO,KAAK,EAAE;AACzB,MAAI,GAAG,WAAW,GAAG,OAAQ,QAAO;AACpC,SAAO,GAAG,MAAM,CAAC,MAAM,UAAU,GAAG,CAAC,GAAG,GAAG,CAAC,CAAC,CAAC;AAChD;;;ACpDA,SAAS,cAAc,oBAAoB;AAQpC,SAAS,YACd,QACA,UAA8B,CAAC,GACR;AACvB,QAAM,OAAO,aAAa,MAAM;AAChC,MAAI,CAAC,QAAQ,qBAAqB,QAAQ,kBAAkB,WAAW,GAAG;AACxE,WAAO;AAAA,EACT;AACA,QAAM,SAAS,QAAQ,kBAAkB,IAAI,CAAC,MAAM,aAAa,CAAC,CAA0B;AAC5F,SAAO,aAAa,CAAC,MAAM,GAAG,MAAM,CAAY;AAClD;;;ACUO,SAAS,oBACd,OACA,SACA,SACwB;AACxB,QAAM,EAAE,OAAO,IAAI;AACnB,QAAM,YAAY,SAAS;AAC3B,QAAM,aAAa,UAAU,aAAa,WAAW,OAAO,IAAI;AAEhE,QAAM,YAAY,kBAAkB,WAAW,WAAW,WAAW,uBAAuB;AAC5F,QAAM,WAAW,kBAAkB,WAAW,WAAW,UAAU,uBAAuB;AAC1F,QAAM,WAAW;AAAA,IACf,WAAW,WAAW;AAAA,IACtB;AAAA,EACF;AAEA,SAAO;AAAA,IACL,CAAC,GAAG,MAAM,KAAK,GAAG,WAAW,OAAO;AAAA,IACpC,CAAC,GAAG,MAAM,OAAO,GAAG,WAAW,OAAO;AAAA,IACtC,CAAC,GAAG,MAAM,QAAQ,GAAG,WAAW,OAAO;AAAA,IACvC,CAAC,GAAG,MAAM,UAAU,GAAG,WAAW,OAAO;AAAA,IACzC,CAAC,GAAG,MAAM,SAAS,GAAG,WAAW,OAAO;AAAA,IACxC,CAAC,GAAG,MAAM,UAAU,GAAG,WAAW,OAAO;AAAA,IACzC,CAAC,GAAG,MAAM,SAAS,GAAG,sBAAsB,WAAW,OAAO,SAAS;AAAA,IACvE,CAAC,GAAG,MAAM,WAAW,GAAG,WAAW,OAAO;AAAA,IAC1C,CAAC,GAAG,MAAM,aAAa,GAAG;AAAA,IAC1B,CAAC,GAAG,MAAM,YAAY,GAAG;AAAA,IACzB,CAAC,GAAG,MAAM,YAAY,GAAG;AAAA,IACzB,CAAC,GAAG,MAAM,SAAS,GAAG,GAAG,WAAW,MAAM,gBAAgB,CAAC;AAAA,EAC7D;AACF;AAOO,SAAS,qBACd,OACA,SACO;AACP,QAAM,YAAY,SAAS;AAC3B,SAAO,UAAU,aAAa,WAAW,OAAO,IAAI;AACtD;","names":[]}
|
|
@@ -118,39 +118,12 @@ function estimateNarrationDuration(text, wordsPerSecond = DEFAULT_WORDS_PER_SECO
|
|
|
118
118
|
return spokenWords / wordsPerSecond;
|
|
119
119
|
}
|
|
120
120
|
|
|
121
|
-
// src/timing/readingTime.ts
|
|
122
|
-
var DEFAULT_READING_WPM = 200;
|
|
123
|
-
function estimateReadingTime(text, options) {
|
|
124
|
-
const configuredWpm = options?.wordsPerMinute;
|
|
125
|
-
const wpm = typeof configuredWpm === "number" && configuredWpm > 0 ? configuredWpm : DEFAULT_READING_WPM;
|
|
126
|
-
const words = text.split(/\s+/).filter((w) => w.length > 0).length;
|
|
127
|
-
const minutes = words / wpm;
|
|
128
|
-
return {
|
|
129
|
-
words,
|
|
130
|
-
minutes,
|
|
131
|
-
seconds: Math.round(minutes * 60)
|
|
132
|
-
};
|
|
133
|
-
}
|
|
134
|
-
function estimateNarrationTime(text, options) {
|
|
135
|
-
const providedWps = options?.wordsPerSecond;
|
|
136
|
-
const wps = typeof providedWps === "number" && providedWps > 0 ? providedWps : DEFAULT_WORDS_PER_SECOND;
|
|
137
|
-
const spokenWords = countSpokenWords(text);
|
|
138
|
-
const totalSeconds = spokenWords / wps;
|
|
139
|
-
return {
|
|
140
|
-
spokenWords,
|
|
141
|
-
minutes: totalSeconds / 60,
|
|
142
|
-
seconds: Math.round(totalSeconds)
|
|
143
|
-
};
|
|
144
|
-
}
|
|
145
|
-
|
|
146
121
|
export {
|
|
147
122
|
DEFAULT_WORDS_PER_SECOND,
|
|
148
123
|
estimateSpokenWordCount,
|
|
149
124
|
countSpokenWords,
|
|
150
125
|
estimateTimeFromText,
|
|
151
126
|
calculatePrefixDuration,
|
|
152
|
-
estimateNarrationDuration
|
|
153
|
-
estimateReadingTime,
|
|
154
|
-
estimateNarrationTime
|
|
127
|
+
estimateNarrationDuration
|
|
155
128
|
};
|
|
156
|
-
//# sourceMappingURL=chunk-
|
|
129
|
+
//# sourceMappingURL=chunk-3E5F2XMR.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/timing/narrationTiming.ts"],"sourcesContent":["/**\n * Narration timing estimation.\n *\n * Estimates how long text takes to speak aloud, accounting for:\n * - Numbers spoken as multiple words (e.g., \"1910\" → \"nineteen ten\")\n * - Pauses at commas\n * - Year pronunciation conventions (1000–2099)\n *\n * Ported from qualla-internal's AudioTiming.ts (pure functions, no Node.js deps).\n */\n\n/** Numbers are spoken more slowly than regular words (1.3× multiplier). */\nconst NUMBER_SPEAKING_MULTIPLIER = 1.3;\n\n/** Comma pause in \"word equivalents\" — speakers pause ~0.3 words at commas. */\nconst COMMA_PAUSE_PENALTY = 0.3;\n\n/** Default speaking rate in words per second (~150 WPM). */\nexport const DEFAULT_WORDS_PER_SECOND = 2.5;\n\n/**\n * Estimate how many \"spoken word equivalents\" a single token represents.\n * Numbers get expanded (e.g., \"175,000\" → ~5 words × 1.3 multiplier).\n */\nexport function estimateSpokenWordCount(token: string): number {\n const cleaned = token.replace(/,/g, '');\n const num = parseFloat(cleaned);\n\n if (isNaN(num) || !isFinite(num)) {\n return 1; // Not a number — count as 1 word\n }\n\n return estimateNumberWordCount(cleaned, num) * NUMBER_SPEAKING_MULTIPLIER;\n}\n\n/**\n * Raw word count for a number (before the speaking multiplier).\n */\nfunction estimateNumberWordCount(cleaned: string, num: number): number {\n // Handle decimals: \"3.14\" → words(\"3\") + \"point\" + 2 digits\n if (cleaned.includes('.')) {\n const [whole, decimal] = cleaned.split('.');\n const wholeNum = parseFloat(whole) || 0;\n return estimateNumberWordCount(whole, wholeNum) + 1 + decimal.length;\n }\n\n const absNum = Math.abs(num);\n let words = num < 0 ? 1 : 0; // \"negative\" / \"minus\"\n\n if (absNum === 0) return 1; // \"zero\"\n\n // Years (1000–2099) are spoken as two two-digit groups:\n // \"1910\" → \"nineteen ten\", \"2024\" → \"twenty twenty-four\"\n if (absNum >= 1000 && absNum <= 2099 && Number.isInteger(absNum)) {\n const yearStr = absNum.toString();\n if (yearStr.length === 4) {\n const firstTwo = parseInt(yearStr.slice(0, 2));\n const lastTwo = parseInt(yearStr.slice(2));\n\n // First part\n if (firstTwo >= 10 && firstTwo <= 19) {\n words += 1;\n } else if (firstTwo >= 20 && firstTwo <= 99) {\n words += firstTwo % 10 === 0 ? 1 : 2;\n } else {\n words += 2; // \"two thousand\"\n }\n\n // Second part\n if (lastTwo === 0) {\n if (firstTwo >= 10 && firstTwo <= 99 && firstTwo !== 20) {\n words += 1; // \"hundred\"\n }\n } else if (lastTwo >= 1 && lastTwo <= 9) {\n words += 2; // \"oh five\"\n } else if (lastTwo >= 10 && lastTwo <= 19) {\n words += 1;\n } else {\n words += lastTwo % 10 === 0 ? 1 : 2;\n }\n\n return Math.max(words, 2);\n }\n }\n\n // General number handling — each magnitude group adds words\n let remaining = absNum;\n\n if (remaining >= 1_000_000_000) {\n const billions = Math.floor(remaining / 1_000_000_000);\n words += estimateNumberWordCount(String(billions), billions);\n words += 1; // \"billion\"\n remaining %= 1_000_000_000;\n }\n\n if (remaining >= 1_000_000) {\n const millions = Math.floor(remaining / 1_000_000);\n words += estimateNumberWordCount(String(millions), millions);\n words += 1; // \"million\"\n remaining %= 1_000_000;\n }\n\n if (remaining >= 1_000) {\n const thousands = Math.floor(remaining / 1_000);\n words += estimateNumberWordCount(String(thousands), thousands);\n words += 1; // \"thousand\"\n remaining %= 1_000;\n }\n\n if (remaining >= 100) {\n words += 2; // digit + \"hundred\"\n remaining %= 100;\n }\n\n if (remaining >= 1) {\n if (remaining >= 20) {\n words += remaining % 10 === 0 ? 1 : 2;\n } else {\n words += 1; // 1–19\n }\n }\n\n return Math.max(words, 1);\n}\n\n/**\n * Count \"spoken word equivalents\" in a block of text,\n * expanding numbers and adding comma pauses.\n */\nexport function countSpokenWords(text: string): number {\n const tokens = text.split(/\\s+/).filter((w) => w.length > 0);\n let count = 0;\n\n for (const token of tokens) {\n // Strip punctuation only at token boundaries so internal punctuation\n // (e.g., decimals like \"3.14\" or digit-grouping commas \"1,000\") is preserved.\n const stripped = token.replace(/^[.,!?;:'\"()[\\]{}]+|[.,!?;:'\"()[\\]{}]+$/g, '');\n count += estimateSpokenWordCount(stripped);\n\n // Count only punctuation commas for pauses: exclude digit-grouping commas\n // (i.e., commas with digits on both sides, like in \"1,234\").\n let punctuationCommaCount = 0;\n for (let i = 0; i < token.length; i++) {\n if (token[i] === ',') {\n const prev = i > 0 ? token[i - 1] : '';\n const next = i < token.length - 1 ? token[i + 1] : '';\n const prevIsDigit = prev >= '0' && prev <= '9';\n const nextIsDigit = next >= '0' && next <= '9';\n if (!(prevIsDigit && nextIsDigit)) {\n punctuationCommaCount++;\n }\n }\n }\n count += punctuationCommaCount * COMMA_PAUSE_PENALTY;\n }\n\n return count;\n}\n\n/**\n * Estimate audio time at a character offset using word-proportion mapping.\n *\n * Given the full source text and a character position, returns the\n * estimated time (in seconds) at which that position would be spoken.\n *\n * @param text - Full source text\n * @param charOffset - Character offset to estimate timing for\n * @param totalDuration - Total audio duration in seconds\n * @returns Estimated time in seconds\n */\nexport function estimateTimeFromText(\n text: string,\n charOffset: number,\n totalDuration: number,\n): number {\n const textBefore = text.slice(0, charOffset);\n const spokenWordsBefore = countSpokenWords(textBefore);\n const totalSpokenWords = countSpokenWords(text);\n\n if (totalSpokenWords === 0) return 0;\n return (spokenWordsBefore / totalSpokenWords) * totalDuration;\n}\n\n/**\n * Estimate how long a spoken prefix adds to audio duration.\n *\n * When audio includes a spoken title or header before the main content,\n * this calculates the extra duration to offset timing calculations.\n *\n * @param prefix - Prefix text (e.g., an article title spoken before the body)\n * @param wordsPerSecond - Speaking rate (default: 2.5)\n * @returns Estimated duration in seconds\n */\nexport function calculatePrefixDuration(\n prefix: string,\n wordsPerSecond: number = DEFAULT_WORDS_PER_SECOND,\n): number {\n if (!prefix) return 0;\n\n const words = prefix.split(/\\s+/).filter((w) => w.length > 0);\n const newlineCount = (prefix.match(/\\n\\n/g) || []).length;\n const pauseTime = newlineCount * 0.5;\n\n return words.length / wordsPerSecond + pauseTime;\n}\n\n/**\n * Estimate narration duration for a block of text.\n *\n * @param text - The text to estimate\n * @param wordsPerSecond - Speaking rate (default: 2.5, i.e. ~150 WPM)\n * @returns Duration in seconds\n */\nexport function estimateNarrationDuration(\n text: string,\n wordsPerSecond: number = DEFAULT_WORDS_PER_SECOND,\n): number {\n const spokenWords = countSpokenWords(text);\n return spokenWords / wordsPerSecond;\n}\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import {
|
|
2
|
+
DEFAULT_WORDS_PER_SECOND,
|
|
3
|
+
countSpokenWords
|
|
4
|
+
} from "./chunk-3E5F2XMR.js";
|
|
5
|
+
|
|
6
|
+
// src/timing/readingTime.ts
|
|
7
|
+
var DEFAULT_READING_WPM = 200;
|
|
8
|
+
function estimateReadingTime(text, options) {
|
|
9
|
+
const configuredWpm = options?.wordsPerMinute;
|
|
10
|
+
const wpm = typeof configuredWpm === "number" && configuredWpm > 0 ? configuredWpm : DEFAULT_READING_WPM;
|
|
11
|
+
const words = text.split(/\s+/).filter((w) => w.length > 0).length;
|
|
12
|
+
const minutes = words / wpm;
|
|
13
|
+
return {
|
|
14
|
+
words,
|
|
15
|
+
minutes,
|
|
16
|
+
seconds: Math.round(minutes * 60)
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
function estimateNarrationTime(text, options) {
|
|
20
|
+
const providedWps = options?.wordsPerSecond;
|
|
21
|
+
const wps = typeof providedWps === "number" && providedWps > 0 ? providedWps : DEFAULT_WORDS_PER_SECOND;
|
|
22
|
+
const spokenWords = countSpokenWords(text);
|
|
23
|
+
const totalSeconds = spokenWords / wps;
|
|
24
|
+
return {
|
|
25
|
+
spokenWords,
|
|
26
|
+
minutes: totalSeconds / 60,
|
|
27
|
+
seconds: Math.round(totalSeconds)
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export {
|
|
32
|
+
estimateReadingTime,
|
|
33
|
+
estimateNarrationTime
|
|
34
|
+
};
|
|
35
|
+
//# sourceMappingURL=chunk-4X3JQXNM.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/timing/readingTime.ts"],"sourcesContent":["/**\n * Reading time estimation.\n *\n * Estimates how long it takes to read text silently vs. narrate it aloud.\n * Uses configurable words-per-minute rates.\n */\n\nimport { countSpokenWords, DEFAULT_WORDS_PER_SECOND } from './narrationTiming.js';\n\n/** Default silent reading rate (adults average ~200–250 WPM). */\nconst DEFAULT_READING_WPM = 200;\n\n/** Result of a reading-time estimate. */\nexport interface ReadingTimeEstimate {\n /** Number of words (plain word count, not spoken equivalents). */\n words: number;\n /** Estimated minutes (decimal). */\n minutes: number;\n /** Estimated seconds (rounded). */\n seconds: number;\n}\n\n/** Options for reading-time estimation. */\nexport interface ReadingTimeOptions {\n /** Words per minute for silent reading (default: 200). */\n wordsPerMinute?: number;\n}\n\n/**\n * Estimate silent reading time for a block of text.\n *\n * @param text - The text to estimate\n * @param options - Optional configuration\n * @returns Word count + time estimate\n */\nexport function estimateReadingTime(\n text: string,\n options?: ReadingTimeOptions,\n): ReadingTimeEstimate {\n const configuredWpm = options?.wordsPerMinute;\n const wpm =\n typeof configuredWpm === 'number' && configuredWpm > 0 ? configuredWpm : DEFAULT_READING_WPM;\n const words = text.split(/\\s+/).filter((w) => w.length > 0).length;\n const minutes = words / wpm;\n\n return {\n words,\n minutes,\n seconds: Math.round(minutes * 60),\n };\n}\n\n/** Result of a narration-time estimate. */\nexport interface NarrationTimeEstimate {\n /** Spoken word equivalents (numbers expanded, comma pauses added). */\n spokenWords: number;\n /** Estimated minutes (decimal). */\n minutes: number;\n /** Estimated seconds (rounded). */\n seconds: number;\n}\n\n/** Options for narration-time estimation. */\nexport interface NarrationTimeOptions {\n /** Words per second for narration (default: 2.5 ≈ 150 WPM). */\n wordsPerSecond?: number;\n}\n\n/**\n * Estimate narration (spoken) time for a block of text.\n *\n * Unlike silent reading, this accounts for numbers being spoken as\n * multiple words and pauses at commas.\n *\n * @param text - The text to estimate\n * @param options - Optional configuration\n * @returns Spoken word equivalents + time estimate\n */\nexport function estimateNarrationTime(\n text: string,\n options?: NarrationTimeOptions,\n): NarrationTimeEstimate {\n const providedWps = options?.wordsPerSecond;\n const wps =\n typeof providedWps === 'number' && providedWps > 0 ? providedWps : DEFAULT_WORDS_PER_SECOND;\n const spokenWords = countSpokenWords(text);\n const totalSeconds = spokenWords / wps;\n\n return {\n spokenWords,\n minutes: totalSeconds / 60,\n seconds: Math.round(totalSeconds),\n };\n}\n"],"mappings":";;;;;;AAUA,IAAM,sBAAsB;AAyBrB,SAAS,oBACd,MACA,SACqB;AACrB,QAAM,gBAAgB,SAAS;AAC/B,QAAM,MACJ,OAAO,kBAAkB,YAAY,gBAAgB,IAAI,gBAAgB;AAC3E,QAAM,QAAQ,KAAK,MAAM,KAAK,EAAE,OAAO,CAAC,MAAM,EAAE,SAAS,CAAC,EAAE;AAC5D,QAAM,UAAU,QAAQ;AAExB,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA,SAAS,KAAK,MAAM,UAAU,EAAE;AAAA,EAClC;AACF;AA4BO,SAAS,sBACd,MACA,SACuB;AACvB,QAAM,cAAc,SAAS;AAC7B,QAAM,MACJ,OAAO,gBAAgB,YAAY,cAAc,IAAI,cAAc;AACrE,QAAM,cAAc,iBAAiB,IAAI;AACzC,QAAM,eAAe,cAAc;AAEnC,SAAO;AAAA,IACL;AAAA,IACA,SAAS,eAAe;AAAA,IACxB,SAAS,KAAK,MAAM,YAAY;AAAA,EAClC;AACF;","names":[]}
|
|
@@ -1,11 +1,16 @@
|
|
|
1
1
|
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
2
|
+
buildSnapshotPath,
|
|
3
|
+
coalesceSnapshots,
|
|
4
|
+
listSnapshots,
|
|
5
|
+
parseSnapshotPath,
|
|
6
|
+
pruneSnapshots,
|
|
7
|
+
readTextSnapshot,
|
|
8
|
+
saveTextSnapshot
|
|
9
|
+
} from "./chunk-S7A5D7ZJ.js";
|
|
5
10
|
import {
|
|
6
11
|
markerPath,
|
|
7
12
|
shapePath
|
|
8
|
-
} from "./chunk-
|
|
13
|
+
} from "./chunk-TUWMK62B.js";
|
|
9
14
|
|
|
10
15
|
// src/imageEdit/state.ts
|
|
11
16
|
var IMAGE_EDIT_STATE_FILENAME = "state.json";
|
|
@@ -153,66 +158,23 @@ function assertLayer(value, index, filename) {
|
|
|
153
158
|
// src/imageEdit/versionPaths.ts
|
|
154
159
|
var IMAGE_EDIT_VERSIONS_PREFIX = ".versions/";
|
|
155
160
|
var IMAGE_EDIT_DEFAULT_BASENAME = "state";
|
|
161
|
+
var IMAGE_EDIT_VERSION_PATHS = Object.freeze({
|
|
162
|
+
prefix: IMAGE_EDIT_VERSIONS_PREFIX,
|
|
163
|
+
extension: "json"
|
|
164
|
+
});
|
|
156
165
|
function buildImageEditVersionPath(basename, date, collision = 0) {
|
|
157
|
-
|
|
158
|
-
const suffix = collision > 0 ? `-${collision + 1}` : "";
|
|
159
|
-
return `${IMAGE_EDIT_VERSIONS_PREFIX}${basename}.${stamp}${suffix}.json`;
|
|
166
|
+
return buildSnapshotPath(IMAGE_EDIT_VERSION_PATHS, basename, date, collision);
|
|
160
167
|
}
|
|
161
168
|
function parseImageEditVersionPath(path) {
|
|
162
|
-
|
|
163
|
-
const rest = path.slice(IMAGE_EDIT_VERSIONS_PREFIX.length);
|
|
164
|
-
if (!rest.endsWith(".json")) return null;
|
|
165
|
-
const stem = rest.slice(0, -".json".length);
|
|
166
|
-
const lastDot = stem.lastIndexOf(".");
|
|
167
|
-
if (lastDot <= 0) return null;
|
|
168
|
-
const basename = stem.slice(0, lastDot);
|
|
169
|
-
const tail = stem.slice(lastDot + 1);
|
|
170
|
-
const dash = tail.indexOf("-");
|
|
171
|
-
let stamp;
|
|
172
|
-
let collision = 0;
|
|
173
|
-
if (dash >= 0) {
|
|
174
|
-
stamp = tail.slice(0, dash);
|
|
175
|
-
const n = Number(tail.slice(dash + 1));
|
|
176
|
-
if (!Number.isInteger(n) || n < 2) return null;
|
|
177
|
-
collision = n - 1;
|
|
178
|
-
} else {
|
|
179
|
-
stamp = tail;
|
|
180
|
-
}
|
|
181
|
-
const timestamp = parseVersionTimestamp(stamp);
|
|
182
|
-
if (!timestamp) return null;
|
|
183
|
-
return { basename, timestamp, collision };
|
|
169
|
+
return parseSnapshotPath(IMAGE_EDIT_VERSION_PATHS, path);
|
|
184
170
|
}
|
|
185
171
|
|
|
186
172
|
// src/imageEdit/versions.ts
|
|
187
|
-
var decoder2 = new TextDecoder();
|
|
188
|
-
var encoder2 = new TextEncoder();
|
|
189
173
|
async function listImageEditVersions(container, basename) {
|
|
190
|
-
|
|
191
|
-
const versions = [];
|
|
192
|
-
for (const entry of entries) {
|
|
193
|
-
const parsed = parseImageEditVersionPath(entry.path);
|
|
194
|
-
if (!parsed) continue;
|
|
195
|
-
if (basename !== void 0 && parsed.basename !== basename) continue;
|
|
196
|
-
versions.push({
|
|
197
|
-
path: entry.path,
|
|
198
|
-
basename: parsed.basename,
|
|
199
|
-
timestamp: parsed.timestamp,
|
|
200
|
-
size: entry.size,
|
|
201
|
-
collision: parsed.collision
|
|
202
|
-
});
|
|
203
|
-
}
|
|
204
|
-
versions.sort((a, b) => {
|
|
205
|
-
const dt = b.timestamp.getTime() - a.timestamp.getTime();
|
|
206
|
-
if (dt !== 0) return dt;
|
|
207
|
-
return b.collision - a.collision;
|
|
208
|
-
});
|
|
209
|
-
return versions;
|
|
174
|
+
return listSnapshots(container, IMAGE_EDIT_VERSION_PATHS, basename);
|
|
210
175
|
}
|
|
211
176
|
async function readImageEditVersionText(container, version) {
|
|
212
|
-
|
|
213
|
-
const data = await container.readFile(path);
|
|
214
|
-
if (!data) return null;
|
|
215
|
-
return decoder2.decode(data);
|
|
177
|
+
return readTextSnapshot(container, version);
|
|
216
178
|
}
|
|
217
179
|
async function readImageEditVersion(container, version) {
|
|
218
180
|
const text = await readImageEditVersionText(container, version);
|
|
@@ -227,31 +189,13 @@ async function saveImageEditVersion(container, options = {}) {
|
|
|
227
189
|
}
|
|
228
190
|
const basename = options.basename ?? IMAGE_EDIT_DEFAULT_BASENAME;
|
|
229
191
|
const serialized = JSON.stringify(doc, null, 2);
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
const latest = versions[0];
|
|
233
|
-
const existing = await readImageEditVersionText(container, latest);
|
|
234
|
-
if (existing === serialized) {
|
|
235
|
-
return { saved: false, version: null, reason: "unchanged" };
|
|
236
|
-
}
|
|
237
|
-
}
|
|
238
|
-
const now = options.now ?? /* @__PURE__ */ new Date();
|
|
239
|
-
let collision = 0;
|
|
240
|
-
let path = buildImageEditVersionPath(basename, now, collision);
|
|
241
|
-
while (await container.exists(path)) {
|
|
242
|
-
collision += 1;
|
|
243
|
-
path = buildImageEditVersionPath(basename, now, collision);
|
|
244
|
-
}
|
|
245
|
-
const data = encoder2.encode(serialized);
|
|
246
|
-
await container.writeFile(path, data, "application/json");
|
|
247
|
-
const version = {
|
|
248
|
-
path,
|
|
192
|
+
return saveTextSnapshot(container, IMAGE_EDIT_VERSION_PATHS, {
|
|
193
|
+
content: serialized,
|
|
249
194
|
basename,
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
};
|
|
254
|
-
return { saved: true, version, reason: "saved" };
|
|
195
|
+
mimeType: "application/json",
|
|
196
|
+
now: options.now,
|
|
197
|
+
force: options.force
|
|
198
|
+
});
|
|
255
199
|
}
|
|
256
200
|
async function revertToImageEditVersion(container, version, options = {}) {
|
|
257
201
|
const doc = await readImageEditVersion(container, version);
|
|
@@ -267,45 +211,10 @@ async function revertToImageEditVersion(container, version, options = {}) {
|
|
|
267
211
|
return { reverted: true, snapshotted };
|
|
268
212
|
}
|
|
269
213
|
async function pruneImageEditVersions(container, policy, basename) {
|
|
270
|
-
|
|
271
|
-
const toDelete = [];
|
|
272
|
-
if (policy.type === "keep-last-n") {
|
|
273
|
-
const n = Math.max(0, Math.floor(policy.n));
|
|
274
|
-
toDelete.push(...versions.slice(n));
|
|
275
|
-
} else if (policy.type === "older-than") {
|
|
276
|
-
const cutoff = policy.date.getTime();
|
|
277
|
-
for (const v of versions) {
|
|
278
|
-
if (v.timestamp.getTime() < cutoff) toDelete.push(v);
|
|
279
|
-
}
|
|
280
|
-
} else {
|
|
281
|
-
for (const v of versions) {
|
|
282
|
-
if (!policy.keep(v, versions)) toDelete.push(v);
|
|
283
|
-
}
|
|
284
|
-
}
|
|
285
|
-
for (const v of toDelete) {
|
|
286
|
-
await container.removeFile(v.path);
|
|
287
|
-
}
|
|
288
|
-
return toDelete;
|
|
214
|
+
return pruneSnapshots(container, IMAGE_EDIT_VERSION_PATHS, policy, basename);
|
|
289
215
|
}
|
|
290
216
|
async function coalesceImageEditVersions(container, options = {}, basename) {
|
|
291
|
-
|
|
292
|
-
const versions = await listImageEditVersions(container, basename);
|
|
293
|
-
const toDelete = [];
|
|
294
|
-
if (versions.length > 0) {
|
|
295
|
-
let anchor = versions[0];
|
|
296
|
-
for (let i = 1; i < versions.length; i++) {
|
|
297
|
-
const candidate = versions[i];
|
|
298
|
-
if (anchor.timestamp.getTime() - candidate.timestamp.getTime() <= windowMs) {
|
|
299
|
-
toDelete.push(candidate);
|
|
300
|
-
} else {
|
|
301
|
-
anchor = candidate;
|
|
302
|
-
}
|
|
303
|
-
}
|
|
304
|
-
}
|
|
305
|
-
for (const v of toDelete) {
|
|
306
|
-
await container.removeFile(v.path);
|
|
307
|
-
}
|
|
308
|
-
return toDelete;
|
|
217
|
+
return coalesceSnapshots(container, IMAGE_EDIT_VERSION_PATHS, options, basename);
|
|
309
218
|
}
|
|
310
219
|
|
|
311
220
|
// src/imageEdit/ImageEditVersionManager.ts
|
|
@@ -428,8 +337,9 @@ function renderPathToSvg(layer, x, y, w, h) {
|
|
|
428
337
|
const strokeWidth = c.strokeWidth ?? 2;
|
|
429
338
|
const fill = c.fill ?? "none";
|
|
430
339
|
const dash = c.dasharray ? ` stroke-dasharray="${escapeAttr(c.dasharray)}"` : "";
|
|
431
|
-
const
|
|
432
|
-
const
|
|
340
|
+
const legacyArrow = c.arrow;
|
|
341
|
+
const startStyle = effectiveExportMarker(c.startMarker, legacyArrow, "start");
|
|
342
|
+
const endStyle = effectiveExportMarker(c.endMarker, legacyArrow, "end");
|
|
433
343
|
const start = markerPath(startStyle, "start");
|
|
434
344
|
const end = markerPath(endStyle, "end");
|
|
435
345
|
const startId = `marker-start-${layer.id}`;
|
|
@@ -439,9 +349,9 @@ function renderPathToSvg(layer, x, y, w, h) {
|
|
|
439
349
|
const markerEnd = end ? ` marker-end="url(#${endId})"` : "";
|
|
440
350
|
return defs + `<path d="${escapeAttr(d)}" fill="${escapeAttr(fill)}" stroke="${escapeAttr(stroke)}" stroke-width="${strokeWidth}"${dash}${markerStart}${markerEnd}/>`;
|
|
441
351
|
}
|
|
442
|
-
function effectiveExportMarker(explicit,
|
|
352
|
+
function effectiveExportMarker(explicit, legacyArrow, end) {
|
|
443
353
|
if (explicit) return explicit;
|
|
444
|
-
return
|
|
354
|
+
return legacyArrow === "both" || legacyArrow === end ? "arrow" : "none";
|
|
445
355
|
}
|
|
446
356
|
function markerDefSvg(id, dir, marker, stroke) {
|
|
447
357
|
const refX = dir === "end" ? 9 : 1;
|
|
@@ -590,4 +500,4 @@ export {
|
|
|
590
500
|
exportImageEditDoc,
|
|
591
501
|
buildSvgString
|
|
592
502
|
};
|
|
593
|
-
//# sourceMappingURL=chunk-
|
|
503
|
+
//# sourceMappingURL=chunk-5DKBFXPY.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/imageEdit/state.ts","../src/imageEdit/persistence.ts","../src/imageEdit/versionPaths.ts","../src/imageEdit/versions.ts","../src/imageEdit/ImageEditVersionManager.ts","../src/imageEdit/export.ts"],"sourcesContent":["/**\n * Pure helpers for constructing and mutating {@link ImageEditDoc} values.\n *\n * All functions are side-effect free and return new doc objects rather\n * than mutating their inputs, so they're safe to use from a reducer.\n */\n\nimport type { ImageEditDoc, ImageEditLayer } from '../schemas/ImageEditDoc.js';\n\n/** Filename used for the persisted editor state inside the sidecar. */\nexport const IMAGE_EDIT_STATE_FILENAME = 'state.json';\n\n/** Subfolder (inside the sidecar) for embedded asset bytes. */\nexport const IMAGE_EDIT_ASSETS_PREFIX = 'assets/';\n\n/** Generate a short random layer id. */\nfunction nextLayerId(): string {\n return `layer-${Math.random().toString(36).slice(2, 10)}`;\n}\n\n/**\n * Create an empty doc with the given canvas size and no layers.\n *\n * `meta.createdAt` and `meta.updatedAt` are set to the current time so\n * the doc has a complete metadata footprint from the start.\n */\nexport function createEmptyImageEditDoc(\n width: number,\n height: number,\n options: { background?: string; sourcePath?: string; now?: Date } = {},\n): ImageEditDoc {\n const now = (options.now ?? new Date()).toISOString();\n return {\n version: 1,\n canvas: {\n width,\n height,\n background: options.background ?? 'transparent',\n },\n layers: [],\n meta: {\n sourcePath: options.sourcePath,\n createdAt: now,\n updatedAt: now,\n },\n };\n}\n\n/** Append a layer (top of stack). Assigns an id when missing. */\nexport function addLayer(doc: ImageEditDoc, layer: ImageEditLayer): ImageEditDoc {\n const withId: ImageEditLayer = layer.id ? layer : { ...layer, id: nextLayerId() };\n return touch({ ...doc, layers: [...doc.layers, withId] });\n}\n\n/** Remove the layer with the given id. No-op if not found. */\nexport function removeLayer(doc: ImageEditDoc, layerId: string): ImageEditDoc {\n const next = doc.layers.filter((l) => l.id !== layerId);\n if (next.length === doc.layers.length) return doc;\n return touch({ ...doc, layers: next });\n}\n\n/**\n * Reorder a layer to a new index in the stack. The index is clamped to\n * the valid range; out-of-range / missing layers are no-ops.\n */\nexport function reorderLayer(doc: ImageEditDoc, layerId: string, toIndex: number): ImageEditDoc {\n const fromIndex = doc.layers.findIndex((l) => l.id === layerId);\n if (fromIndex < 0) return doc;\n const clamped = Math.max(0, Math.min(doc.layers.length - 1, Math.floor(toIndex)));\n if (clamped === fromIndex) return doc;\n const next = doc.layers.slice();\n const [layer] = next.splice(fromIndex, 1);\n next.splice(clamped, 0, layer!);\n return touch({ ...doc, layers: next });\n}\n\n/**\n * Replace the layer with the given id by applying a partial update.\n * The patch is shallow-merged at the top level; callers updating\n * `content`, `position`, etc. must spread those sub-objects themselves.\n */\nexport function updateLayer(\n doc: ImageEditDoc,\n layerId: string,\n patch: Partial<ImageEditLayer>,\n): ImageEditDoc {\n let changed = false;\n const next = doc.layers.map((l) => {\n if (l.id !== layerId) return l;\n changed = true;\n return { ...l, ...patch } as ImageEditLayer;\n });\n if (!changed) return doc;\n return touch({ ...doc, layers: next });\n}\n\n/** Replace the canvas configuration. */\nexport function setCanvas(doc: ImageEditDoc, canvas: ImageEditDoc['canvas']): ImageEditDoc {\n return touch({ ...doc, canvas });\n}\n\n/** Bump `meta.updatedAt`. Pure: returns a new doc. */\nexport function touch(doc: ImageEditDoc, now: Date = new Date()): ImageEditDoc {\n return {\n ...doc,\n meta: {\n ...doc.meta,\n createdAt: doc.meta?.createdAt ?? now.toISOString(),\n updatedAt: now.toISOString(),\n },\n };\n}\n","/**\n * Load and save {@link ImageEditDoc} JSON to a sidecar\n * {@link ContentContainer} (typically a {@link ScopedContentContainer}\n * rooted at `<basename>_files/`).\n */\n\nimport type { ContentContainer } from '../storage/ContentContainer.js';\nimport type { ImageEditDoc } from '../schemas/ImageEditDoc.js';\nimport { IMAGE_EDIT_STATE_FILENAME } from './state.js';\n\nconst decoder = new TextDecoder();\nconst encoder = new TextEncoder();\n\n/**\n * Read `state.json` from the sidecar and parse it. Returns `null` when\n * the file does not exist; throws on parse / shape errors.\n */\nexport async function readImageEditDoc(\n container: ContentContainer,\n filename: string = IMAGE_EDIT_STATE_FILENAME,\n): Promise<ImageEditDoc | null> {\n const data = await container.readFile(filename);\n if (!data) return null;\n const text = decoder.decode(data);\n let parsed: unknown;\n try {\n parsed = JSON.parse(text);\n } catch (err: unknown) {\n const msg = err instanceof Error ? err.message : String(err);\n throw new Error(`readImageEditDoc: ${filename} is not valid JSON: ${msg}`);\n }\n assertImageEditDoc(parsed, filename);\n return parsed;\n}\n\n/** Serialize `doc` to JSON and write it to the sidecar. */\nexport async function writeImageEditDoc(\n container: ContentContainer,\n doc: ImageEditDoc,\n filename: string = IMAGE_EDIT_STATE_FILENAME,\n): Promise<void> {\n const text = JSON.stringify(doc, null, 2);\n await container.writeFile(filename, encoder.encode(text), 'application/json');\n}\n\nfunction assertImageEditDoc(value: unknown, filename: string): asserts value is ImageEditDoc {\n if (!value || typeof value !== 'object') {\n throw new Error(`readImageEditDoc: ${filename} root must be an object`);\n }\n const v = value as Partial<ImageEditDoc>;\n if (v.version !== 1) {\n throw new Error(\n `readImageEditDoc: ${filename} has unsupported schema version ${String(v.version)} (expected 1)`,\n );\n }\n if (!v.canvas || typeof v.canvas.width !== 'number' || typeof v.canvas.height !== 'number') {\n throw new Error(`readImageEditDoc: ${filename} canvas.width/height must be numbers`);\n }\n if (!Array.isArray(v.layers)) {\n throw new Error(`readImageEditDoc: ${filename} layers must be an array`);\n }\n v.layers.forEach((layer, i) => assertLayer(layer, i, filename));\n}\n\nconst VALID_LAYER_TYPES = new Set(['image', 'text', 'shape', 'path']);\n\nfunction assertLayer(value: unknown, index: number, filename: string): void {\n const ctx = `${filename} layers[${index}]`;\n if (!value || typeof value !== 'object') {\n throw new Error(`readImageEditDoc: ${ctx} must be an object`);\n }\n const layer = value as Record<string, unknown>;\n\n if (typeof layer['id'] !== 'string') {\n throw new Error(`readImageEditDoc: ${ctx} must have a string id`);\n }\n if (typeof layer['type'] !== 'string' || !VALID_LAYER_TYPES.has(layer['type'])) {\n throw new Error(\n `readImageEditDoc: ${ctx} type must be one of image|text|shape, got ${String(layer['type'])}`,\n );\n }\n\n const pos = layer['position'];\n if (\n !pos ||\n typeof pos !== 'object' ||\n (typeof (pos as Record<string, unknown>)['x'] !== 'number' &&\n typeof (pos as Record<string, unknown>)['x'] !== 'string') ||\n (typeof (pos as Record<string, unknown>)['y'] !== 'number' &&\n typeof (pos as Record<string, unknown>)['y'] !== 'string')\n ) {\n throw new Error(`readImageEditDoc: ${ctx} position must have numeric or string x and y`);\n }\n\n const content = layer['content'];\n if (!content || typeof content !== 'object') {\n throw new Error(`readImageEditDoc: ${ctx} content must be an object`);\n }\n const c = content as Record<string, unknown>;\n\n if (layer['type'] === 'image' && typeof c['src'] !== 'string') {\n throw new Error(`readImageEditDoc: ${ctx} (image) content.src must be a string`);\n }\n if (layer['type'] === 'text' && typeof c['text'] !== 'string') {\n throw new Error(`readImageEditDoc: ${ctx} (text) content.text must be a string`);\n }\n if (layer['type'] === 'shape' && typeof c['shape'] !== 'string') {\n throw new Error(`readImageEditDoc: ${ctx} (shape) content.shape must be a string`);\n }\n if (layer['type'] === 'path' && typeof c['d'] !== 'string') {\n throw new Error(`readImageEditDoc: ${ctx} (path) content.d must be a string`);\n }\n}\n","/**\n * Path conventions for image-editor version snapshots.\n *\n * Snapshots live inside the sidecar at\n * `.versions/<basename>.<timestamp>[<-suffix>].json`. The collision\n * suffix (`-2`, `-3`, ...) is appended only when two snapshots land on\n * the same UTC second.\n *\n * Mirrors `core/src/versions/paths.ts` but keyed on `.json` instead of\n * `.md` so the two histories live side-by-side without colliding.\n */\n\nimport {\n buildSnapshotPath,\n parseSnapshotPath,\n type SnapshotPathStrategy,\n} from '../versions/snapshotStore.js';\n\n/** Subfolder (inside the sidecar) that holds image-editor snapshots. */\nexport const IMAGE_EDIT_VERSIONS_PREFIX = '.versions/';\n\n/** Default basename when none is supplied. Matches `IMAGE_EDIT_STATE_FILENAME` stem. */\nexport const IMAGE_EDIT_DEFAULT_BASENAME = 'state';\n\n/** Internal strategy shared with the generic snapshot store. */\nexport const IMAGE_EDIT_VERSION_PATHS: SnapshotPathStrategy = Object.freeze({\n prefix: IMAGE_EDIT_VERSIONS_PREFIX,\n extension: 'json',\n});\n\n/** Build a snapshot path. Optionally append a collision suffix. */\nexport function buildImageEditVersionPath(basename: string, date: Date, collision = 0): string {\n return buildSnapshotPath(IMAGE_EDIT_VERSION_PATHS, basename, date, collision);\n}\n\n/**\n * Parse a snapshot path. Returns `null` when the path doesn't match the\n * convention (so unrelated `.versions/*.json` files are ignored).\n */\nexport function parseImageEditVersionPath(\n path: string,\n): { basename: string; timestamp: Date; collision: number } | null {\n return parseSnapshotPath(IMAGE_EDIT_VERSION_PATHS, path);\n}\n","/**\n * Image-editor version operations against a sidecar\n * {@link ContentContainer}.\n *\n * Mirrors `core/src/versions/operations.ts` but operates on\n * {@link ImageEditDoc} JSON state at `.versions/<basename>.<timestamp>.json`\n * instead of the markdown document. Reuses {@link PrunePolicy} so hosts\n * can share their pruning configuration between the two histories. The\n * sidecar root gets a `.gitignore` rule for `.versions/` when snapshots\n * are retained.\n */\n\nimport type { ContentContainer } from '../storage/ContentContainer.js';\nimport type { ImageEditDoc } from '../schemas/ImageEditDoc.js';\nimport type { CoalesceOptions, PrunePolicy, Version } from '../versions/types.js';\nimport {\n coalesceSnapshots,\n listSnapshots,\n pruneSnapshots,\n readTextSnapshot,\n saveTextSnapshot,\n} from '../versions/snapshotStore.js';\nimport { readImageEditDoc, writeImageEditDoc } from './persistence.js';\nimport { IMAGE_EDIT_STATE_FILENAME } from './state.js';\nimport { IMAGE_EDIT_DEFAULT_BASENAME, IMAGE_EDIT_VERSION_PATHS } from './versionPaths.js';\n\n/** Options for {@link saveImageEditVersion}. */\nexport interface SaveImageEditVersionOptions {\n /** Override the doc to snapshot; otherwise `state.json` is read from the sidecar. */\n doc?: ImageEditDoc;\n /** Override the snapshot timestamp. Defaults to `new Date()`. */\n now?: Date;\n /** Skip the diff-vs-latest check and force a write. */\n force?: boolean;\n /** Override the basename used in version filenames. Defaults to `'state'`. */\n basename?: string;\n /** Override the source filename inside the sidecar. Defaults to `state.json`. */\n stateFilename?: string;\n}\n\n/** Result of a {@link saveImageEditVersion} call. */\nexport interface SaveImageEditVersionResult {\n saved: boolean;\n version: Version | null;\n /** `'saved' | 'unchanged' | 'no-state'` — `'no-state'` means there was nothing to snapshot. */\n reason: 'saved' | 'unchanged' | 'no-state';\n}\n\n/** Options for {@link revertToImageEditVersion}. */\nexport interface RevertImageEditOptions {\n /**\n * Whether to snapshot the *current* state before replacing it. Defaults to `true`.\n */\n snapshotCurrent?: boolean;\n /** Override the source filename. Defaults to `state.json`. */\n stateFilename?: string;\n}\n\n/** List image-edit snapshots in the sidecar, newest-first. */\nexport async function listImageEditVersions(\n container: ContentContainer,\n basename?: string,\n): Promise<Version[]> {\n return listSnapshots(container, IMAGE_EDIT_VERSION_PATHS, basename);\n}\n\n/** Read a snapshot's JSON text. Returns `null` if the snapshot is missing. */\nexport async function readImageEditVersionText(\n container: ContentContainer,\n version: Version | string,\n): Promise<string | null> {\n return readTextSnapshot(container, version);\n}\n\n/** Read and parse a snapshot. Returns `null` if missing. */\nexport async function readImageEditVersion(\n container: ContentContainer,\n version: Version | string,\n): Promise<ImageEditDoc | null> {\n const text = await readImageEditVersionText(container, version);\n if (text === null) return null;\n return JSON.parse(text) as ImageEditDoc;\n}\n\n/**\n * Snapshot the current image-editor state if it differs from the latest\n * existing snapshot.\n *\n * Diff is computed on the *serialized* JSON (with stable 2-space\n * indentation) so semantically-equal states with different key order\n * still snapshot — accept the over-snapshot rather than running a deep\n * structural compare.\n */\nexport async function saveImageEditVersion(\n container: ContentContainer,\n options: SaveImageEditVersionOptions = {},\n): Promise<SaveImageEditVersionResult> {\n const stateFilename = options.stateFilename ?? IMAGE_EDIT_STATE_FILENAME;\n const doc = options.doc ?? (await readImageEditDoc(container, stateFilename));\n if (!doc) {\n return { saved: false, version: null, reason: 'no-state' };\n }\n const basename = options.basename ?? IMAGE_EDIT_DEFAULT_BASENAME;\n const serialized = JSON.stringify(doc, null, 2);\n\n return saveTextSnapshot(container, IMAGE_EDIT_VERSION_PATHS, {\n content: serialized,\n basename,\n mimeType: 'application/json',\n now: options.now,\n force: options.force,\n });\n}\n\n/** Revert `state.json` to a prior snapshot. Snapshots the current state first by default. */\nexport async function revertToImageEditVersion(\n container: ContentContainer,\n version: Version | string,\n options: RevertImageEditOptions = {},\n): Promise<{ reverted: boolean; snapshotted: Version | null }> {\n const doc = await readImageEditVersion(container, version);\n if (!doc) {\n return { reverted: false, snapshotted: null };\n }\n\n let snapshotted: Version | null = null;\n if (options.snapshotCurrent !== false) {\n const result = await saveImageEditVersion(container, { stateFilename: options.stateFilename });\n snapshotted = result.version;\n }\n\n await writeImageEditDoc(container, doc, options.stateFilename);\n return { reverted: true, snapshotted };\n}\n\n/** Delete snapshots that don't satisfy the policy. */\nexport async function pruneImageEditVersions(\n container: ContentContainer,\n policy: PrunePolicy,\n basename?: string,\n): Promise<Version[]> {\n return pruneSnapshots(container, IMAGE_EDIT_VERSION_PATHS, policy, basename);\n}\n\n/** Collapse adjacent snapshots within `windowMs`, keeping the newer of each pair. */\nexport async function coalesceImageEditVersions(\n container: ContentContainer,\n options: CoalesceOptions = {},\n basename?: string,\n): Promise<Version[]> {\n return coalesceSnapshots(container, IMAGE_EDIT_VERSION_PATHS, options, basename);\n}\n","/**\n * Convenience wrapper around the image-edit version operations that\n * captures a {@link ContentContainer} reference and an optional basename\n * override. Mirrors `DocumentVersionManager` in shape.\n */\n\nimport type { ContentContainer } from '../storage/ContentContainer.js';\nimport type { CoalesceOptions, PrunePolicy, Version } from '../versions/types.js';\nimport {\n coalesceImageEditVersions,\n listImageEditVersions,\n pruneImageEditVersions,\n readImageEditVersion,\n revertToImageEditVersion,\n saveImageEditVersion,\n type RevertImageEditOptions,\n type SaveImageEditVersionOptions,\n type SaveImageEditVersionResult,\n} from './versions.js';\nimport type { ImageEditDoc } from '../schemas/ImageEditDoc.js';\n\nexport interface ImageEditVersionManagerOptions {\n /** Override the basename used in version filenames. Defaults to `'state'`. */\n basename?: string;\n /** Override the source filename inside the sidecar. Defaults to `state.json`. */\n stateFilename?: string;\n}\n\nexport class ImageEditVersionManager {\n private readonly container: ContentContainer;\n private readonly basename: string | undefined;\n private readonly stateFilename: string | undefined;\n\n constructor(container: ContentContainer, options: ImageEditVersionManagerOptions = {}) {\n this.container = container;\n this.basename = options.basename;\n this.stateFilename = options.stateFilename;\n }\n\n saveVersion(options: SaveImageEditVersionOptions = {}): Promise<SaveImageEditVersionResult> {\n return saveImageEditVersion(this.container, {\n basename: this.basename,\n stateFilename: this.stateFilename,\n ...options,\n });\n }\n\n listVersions(): Promise<Version[]> {\n return listImageEditVersions(this.container, this.basename);\n }\n\n readVersion(version: Version | string): Promise<ImageEditDoc | null> {\n return readImageEditVersion(this.container, version);\n }\n\n revertToVersion(\n version: Version | string,\n options: RevertImageEditOptions = {},\n ): Promise<{ reverted: boolean; snapshotted: Version | null }> {\n return revertToImageEditVersion(this.container, version, {\n stateFilename: this.stateFilename,\n ...options,\n });\n }\n\n pruneVersions(policy: PrunePolicy): Promise<Version[]> {\n return pruneImageEditVersions(this.container, policy, this.basename);\n }\n\n coalesceVersions(options: CoalesceOptions = {}): Promise<Version[]> {\n return coalesceImageEditVersions(this.container, options, this.basename);\n }\n}\n","/**\n * Rasterize an {@link ImageEditDoc} to a PNG / JPEG / WebP {@link Blob}.\n *\n * The pipeline:\n * 1. Build an SVG string mirroring the canvas + layers, with image\n * hrefs inlined as `data:` URLs (so the canvas isn't tainted).\n * 2. Wrap the SVG in a `Blob` and load it through an `Image`.\n * 3. Draw it onto a canvas (`OffscreenCanvas` when available,\n * otherwise a detached `HTMLCanvasElement`).\n * 4. Convert the canvas to the requested format.\n *\n * Browser-only — calls `Image`, `URL.createObjectURL`, and canvas APIs.\n * Returns a Promise that rejects on SVG parse / load failures.\n */\n\nimport type { ContentContainer } from '../storage/ContentContainer.js';\nimport type { ImageEditCanvas, ImageEditDoc, ImageEditLayer } from '../schemas/ImageEditDoc.js';\nimport type { MarkerStyle, Position } from '../schemas/Doc.js';\nimport { markerPath, shapePath } from '../doc/utils/shapeGeometry.js';\n\n/** Supported export formats. */\nexport type ImageEditExportFormat = 'png' | 'jpeg' | 'webp';\n\n/** Options for {@link exportImageEditDoc}. */\nexport interface ImageEditExportOptions {\n /** Output format. Defaults to `'png'`. */\n format?: ImageEditExportFormat;\n /** JPEG/WebP quality 0..1. Defaults to `0.92`. Ignored for PNG. */\n quality?: number;\n /** Resolution scale factor. Defaults to `1` (canvas pixels = output pixels). */\n scale?: number;\n}\n\nconst FORMAT_MIME: Record<ImageEditExportFormat, string> = {\n png: 'image/png',\n jpeg: 'image/jpeg',\n webp: 'image/webp',\n};\n\n/**\n * Export the doc to a raster {@link Blob}. Asset bytes referenced by image\n * layers are read from `container` and inlined as data URLs.\n */\nexport async function exportImageEditDoc(\n doc: ImageEditDoc,\n container: ContentContainer,\n options: ImageEditExportOptions = {},\n): Promise<Blob> {\n const format: ImageEditExportFormat = options.format ?? 'png';\n const quality = options.quality ?? 0.92;\n const scale = options.scale ?? 1;\n const mime = FORMAT_MIME[format];\n\n const svg = await buildSvgString(doc, container);\n const outW = Math.max(1, Math.round(doc.canvas.width * scale));\n const outH = Math.max(1, Math.round(doc.canvas.height * scale));\n\n const img = await loadSvgImage(svg);\n const canvas = createRasterCanvas(outW, outH);\n const ctx = canvas.getContext('2d');\n if (!ctx) throw new Error('exportImageEditDoc: 2D canvas context unavailable');\n\n // For JPEG (no alpha), pre-fill with the canvas background or white so\n // transparent regions aren't rendered as black.\n if (format === 'jpeg') {\n const bg =\n doc.canvas.background && doc.canvas.background !== 'transparent'\n ? doc.canvas.background\n : '#ffffff';\n ctx.fillStyle = bg;\n ctx.fillRect(0, 0, outW, outH);\n }\n\n ctx.drawImage(img as CanvasImageSource, 0, 0, outW, outH);\n return canvasToBlob(canvas, mime, quality);\n}\n\n// ============================================\n// SVG construction\n// ============================================\n\n/**\n * Build the standalone SVG string that mirrors the canvas + layers. Exposed\n * for tests (the raster step needs a browser); shape/text/path layers don't\n * touch `container`, only image layers do (to inline asset bytes).\n */\nexport async function buildSvgString(\n doc: ImageEditDoc,\n container: ContentContainer,\n): Promise<string> {\n const { width, height, background } = doc.canvas;\n const bgRect =\n background && background !== 'transparent'\n ? `<rect width=\"${width}\" height=\"${height}\" fill=\"${escapeAttr(background)}\"/>`\n : '';\n\n const layerXml: string[] = [];\n for (const layer of doc.layers) {\n if (layer.visible === false) continue;\n layerXml.push(await renderLayerToSvg(layer, doc.canvas, container));\n }\n\n return (\n `<?xml version=\"1.0\" encoding=\"UTF-8\"?>` +\n `<svg xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\"` +\n ` width=\"${width}\" height=\"${height}\" viewBox=\"0 0 ${width} ${height}\">` +\n bgRect +\n layerXml.join('') +\n `</svg>`\n );\n}\n\nasync function renderLayerToSvg(\n layer: ImageEditLayer,\n canvas: ImageEditCanvas,\n container: ContentContainer,\n): Promise<string> {\n const opacity = layer.opacity ?? 1;\n const opacityAttr = opacity < 1 ? ` opacity=\"${opacity}\"` : '';\n const inner = await renderLayerInner(layer, canvas, container);\n return `<g data-layer-id=\"${escapeAttr(layer.id)}\"${opacityAttr}>${inner}</g>`;\n}\n\nasync function renderLayerInner(\n layer: ImageEditLayer,\n canvas: ImageEditCanvas,\n container: ContentContainer,\n): Promise<string> {\n const { x, y, w, h } = resolveBox(layer.position, canvas);\n\n if (layer.type === 'image') {\n const dataUrl = await loadAssetAsDataUrl(container, layer.content.src);\n return (\n `<image x=\"${x}\" y=\"${y}\" width=\"${w}\" height=\"${h}\"` +\n ` preserveAspectRatio=\"${preserveAspectRatioFor(layer.content.fit)}\"` +\n ` xlink:href=\"${escapeAttr(dataUrl)}\"/>`\n );\n }\n\n if (layer.type === 'shape') {\n const c = layer.content;\n const fill = c.fill ?? 'none';\n const stroke = c.stroke ? ` stroke=\"${escapeAttr(c.stroke)}\"` : '';\n const strokeWidth = c.strokeWidth ? ` stroke-width=\"${c.strokeWidth}\"` : '';\n if (c.shape === 'rect') {\n const rx = c.borderRadius ? ` rx=\"${c.borderRadius}\" ry=\"${c.borderRadius}\"` : '';\n return `<rect x=\"${x}\" y=\"${y}\" width=\"${w}\" height=\"${h}\" fill=\"${escapeAttr(fill)}\"${stroke}${strokeWidth}${rx}/>`;\n }\n if (c.shape === 'circle') {\n const r = Math.min(w, h) / 2;\n return `<circle cx=\"${x + w / 2}\" cy=\"${y + h / 2}\" r=\"${r}\" fill=\"${escapeAttr(fill)}\"${stroke}${strokeWidth}/>`;\n }\n // line\n return `<line x1=\"${x}\" y1=\"${y}\" x2=\"${x + w}\" y2=\"${y + h}\"${stroke}${strokeWidth}/>`;\n }\n\n if (layer.type === 'path') {\n return renderPathToSvg(layer, x, y, w, h);\n }\n\n // text\n const c = layer.content;\n const style = c.style;\n const lineHeight = style.lineHeight ?? 1.4;\n const lineHeightPx = style.fontSize * lineHeight;\n const lines = (c.text ?? '').split('\\n');\n const tspans = lines\n .map(\n (line, i) => `<tspan x=\"${x}\" dy=\"${i === 0 ? 0 : lineHeightPx}\">${escapeText(line)}</tspan>`,\n )\n .join('');\n const fontFamily = style.fontFamily ?? 'sans-serif';\n const fontWeight = style.fontWeight ?? 'normal';\n const textAnchor =\n style.textAlign === 'center' ? 'middle' : style.textAlign === 'right' ? 'end' : 'start';\n return (\n `<text x=\"${x}\" y=\"${y + style.fontSize}\" font-family=\"${escapeAttr(fontFamily)}\"` +\n ` font-size=\"${style.fontSize}\" font-weight=\"${fontWeight}\"` +\n ` fill=\"${escapeAttr(style.color)}\" text-anchor=\"${textAnchor}\">` +\n tspans +\n `</text>`\n );\n}\n\n/**\n * Serialize a `path` layer to SVG. Mirrors the runtime\n * `react/src/layers/PathLayer.tsx`: when `shapeKind` is set, `d` is\n * re-derived from the position box via `shapePath` (so named shapes scale);\n * otherwise the stored absolute `d` is used. Start/end markers are emitted\n * as `<marker>` defs scoped to the layer id, matching `markerPath` geometry.\n */\nfunction renderPathToSvg(\n layer: ImageEditLayer & { type: 'path' },\n x: number,\n y: number,\n w: number,\n h: number,\n): string {\n const c = layer.content;\n const d = c.shapeKind ? (shapePath(c.shapeKind, x, y, w, h) ?? c.d) : c.d;\n const stroke = c.stroke ?? '#1e293b';\n const strokeWidth = c.strokeWidth ?? 2;\n const fill = c.fill ?? 'none';\n const dash = c.dasharray ? ` stroke-dasharray=\"${escapeAttr(c.dasharray)}\"` : '';\n\n // Keep old serialized documents readable without retaining `arrow` in the public type.\n const legacyArrow = (c as typeof c & { arrow?: 'none' | 'end' | 'start' | 'both' }).arrow;\n const startStyle = effectiveExportMarker(c.startMarker, legacyArrow, 'start');\n const endStyle = effectiveExportMarker(c.endMarker, legacyArrow, 'end');\n const start = markerPath(startStyle, 'start');\n const end = markerPath(endStyle, 'end');\n const startId = `marker-start-${layer.id}`;\n const endId = `marker-end-${layer.id}`;\n const defs =\n start || end\n ? `<defs>${start ? markerDefSvg(startId, 'start', start, stroke) : ''}${\n end ? markerDefSvg(endId, 'end', end, stroke) : ''\n }</defs>`\n : '';\n const markerStart = start ? ` marker-start=\"url(#${startId})\"` : '';\n const markerEnd = end ? ` marker-end=\"url(#${endId})\"` : '';\n\n return (\n defs +\n `<path d=\"${escapeAttr(d)}\" fill=\"${escapeAttr(fill)}\"` +\n ` stroke=\"${escapeAttr(stroke)}\" stroke-width=\"${strokeWidth}\"${dash}${markerStart}${markerEnd}/>`\n );\n}\n\nfunction effectiveExportMarker(\n explicit: MarkerStyle | undefined,\n legacyArrow: 'none' | 'end' | 'start' | 'both' | undefined,\n end: 'start' | 'end',\n): MarkerStyle {\n if (explicit) return explicit;\n return legacyArrow === 'both' || legacyArrow === end ? 'arrow' : 'none';\n}\n\nfunction markerDefSvg(\n id: string,\n dir: 'start' | 'end',\n marker: { d: string; filled: boolean },\n stroke: string,\n): string {\n const refX = dir === 'end' ? 9 : 1;\n const paint = marker.filled\n ? `fill=\"${escapeAttr(stroke)}\"`\n : `fill=\"none\" stroke=\"${escapeAttr(stroke)}\" stroke-width=\"1.5\"`;\n return (\n `<marker id=\"${escapeAttr(id)}\" viewBox=\"0 0 10 10\" refX=\"${refX}\" refY=\"5\"` +\n ` markerWidth=\"4\" markerHeight=\"4\" orient=\"auto-start-reverse\" markerUnits=\"strokeWidth\">` +\n `<path d=\"${escapeAttr(marker.d)}\" ${paint}/></marker>`\n );\n}\n\nfunction preserveAspectRatioFor(fit?: 'cover' | 'contain' | 'fill'): string {\n if (fit === 'cover') return 'xMidYMid slice';\n if (fit === 'fill') return 'none';\n return 'xMidYMid meet'; // contain (default)\n}\n\nfunction resolveBox(\n position: Position,\n canvas: ImageEditCanvas,\n): { x: number; y: number; w: number; h: number } {\n const x = resolveValue(position.x, canvas.width);\n const y = resolveValue(position.y, canvas.height);\n const w =\n position.width !== undefined ? resolveValue(position.width, canvas.width) : canvas.width;\n const h =\n position.height !== undefined ? resolveValue(position.height, canvas.height) : canvas.height;\n const offset = anchorOffset(position.anchor, w, h);\n return { x: x + offset.x, y: y + offset.y, w, h };\n}\n\nfunction resolveValue(v: number | string, base: number): number {\n if (typeof v === 'number') return v;\n if (v.endsWith('%')) return (parseFloat(v) / 100) * base;\n return parseFloat(v) || 0;\n}\n\nfunction anchorOffset(\n anchor: Position['anchor'] | undefined,\n w: number,\n h: number,\n): { x: number; y: number } {\n switch (anchor) {\n case 'center':\n return { x: -w / 2, y: -h / 2 };\n case 'top-right':\n return { x: -w, y: 0 };\n case 'bottom-left':\n return { x: 0, y: -h };\n case 'bottom-right':\n return { x: -w, y: -h };\n case 'top-left':\n default:\n return { x: 0, y: 0 };\n }\n}\n\n// ============================================\n// Asset inlining\n// ============================================\n\nasync function loadAssetAsDataUrl(container: ContentContainer, src: string): Promise<string> {\n // Already a data: or absolute URL — pass through.\n if (/^(data:|https?:|blob:)/.test(src)) return src;\n const data = await container.readFile(src);\n if (!data) throw new Error(`exportImageEditDoc: missing asset \"${src}\"`);\n const list = await container.listFiles(src);\n const mime = list.find((e) => e.path === src)?.mimeType ?? guessMime(src);\n return arrayBufferToDataUrl(data, mime);\n}\n\nfunction arrayBufferToDataUrl(data: ArrayBuffer, mime: string): string {\n const bytes = new Uint8Array(data);\n let binary = '';\n // chunk to avoid call-stack limits with large images\n const CHUNK = 0x8000;\n for (let i = 0; i < bytes.length; i += CHUNK) {\n binary += String.fromCharCode.apply(null, Array.from(bytes.subarray(i, i + CHUNK)));\n }\n // btoa exists in browsers and modern Node (>= 16); fall back to Buffer for older runtimes.\n const b64 = typeof btoa === 'function' ? btoa(binary) : Buffer.from(bytes).toString('base64');\n return `data:${mime};base64,${b64}`;\n}\n\nfunction guessMime(path: string): string {\n const dot = path.lastIndexOf('.');\n const ext = dot >= 0 ? path.slice(dot).toLowerCase() : '';\n switch (ext) {\n case '.png':\n return 'image/png';\n case '.jpg':\n case '.jpeg':\n return 'image/jpeg';\n case '.webp':\n return 'image/webp';\n case '.gif':\n return 'image/gif';\n case '.svg':\n return 'image/svg+xml';\n default:\n return 'application/octet-stream';\n }\n}\n\n// ============================================\n// SVG → Image → Canvas → Blob\n// ============================================\n\nasync function loadSvgImage(svg: string): Promise<HTMLImageElement> {\n const blob = new Blob([svg], { type: 'image/svg+xml;charset=utf-8' });\n const url = URL.createObjectURL(blob);\n try {\n return await new Promise<HTMLImageElement>((resolve, reject) => {\n const img = new Image();\n img.onload = () => resolve(img);\n img.onerror = () => reject(new Error('exportImageEditDoc: SVG image failed to load'));\n img.src = url;\n });\n } finally {\n URL.revokeObjectURL(url);\n }\n}\n\ntype RasterCanvas = HTMLCanvasElement | OffscreenCanvas;\n\nfunction createRasterCanvas(width: number, height: number): RasterCanvas {\n if (typeof OffscreenCanvas !== 'undefined') {\n return new OffscreenCanvas(width, height);\n }\n const c = document.createElement('canvas');\n c.width = width;\n c.height = height;\n return c;\n}\n\nasync function canvasToBlob(canvas: RasterCanvas, mime: string, quality: number): Promise<Blob> {\n if ('convertToBlob' in canvas) {\n return canvas.convertToBlob({ type: mime, quality });\n }\n return new Promise<Blob>((resolve, reject) => {\n canvas.toBlob(\n (blob) => {\n if (blob) resolve(blob);\n else reject(new Error('exportImageEditDoc: canvas.toBlob returned null'));\n },\n mime,\n quality,\n );\n });\n}\n\n// ============================================\n// XML escaping\n// ============================================\n\nfunction escapeAttr(s: string): string {\n return s\n .replace(/&/g, '&')\n .replace(/\"/g, '"')\n .replace(/</g, '<')\n .replace(/>/g, '>');\n}\n\nfunction escapeText(s: string): string {\n return s.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');\n}\n"],"mappings":"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","names":["c"]}
|