yamlover 0.3.3 → 0.3.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/server.js +370 -109
- package/package.json +1 -1
- package/src/client/App.tsx +3 -3
- package/src/client/api.ts +45 -18
- package/src/client/render.tsx +16 -3
- package/src/client/renderers/annotate.tsx +173 -53
- package/src/client/renderers/asciidoc.tsx +2 -1
- package/src/client/renderers/chapter.tsx +5 -1
- package/src/client/renderers/csv.tsx +2 -1
- package/src/client/renderers/imagemap.tsx +19 -6
- package/src/client/renderers/latex.tsx +2 -1
- package/src/client/renderers/marklower.tsx +2 -1
- package/src/client/renderers/plantuml.tsx +2 -1
- package/src/client/renderers/registry.tsx +91 -69
- package/src/client/renderers/text.tsx +2 -1
- package/src/client/styles.css +25 -3
- package/src/server/embed.ts +187 -0
- package/src/server/engine-api.ts +243 -122
- package/src/server/node-kind.ts +15 -2
package/dist/server.js
CHANGED
|
@@ -912,8 +912,8 @@ function walkNodes(node, path8, visit, parent = null, label = null, pos = null)
|
|
|
912
912
|
if (!node.entries) return;
|
|
913
913
|
node.entries.forEach((e2, i) => {
|
|
914
914
|
if (isPointer(e2.value)) return;
|
|
915
|
-
const
|
|
916
|
-
walkNodes(e2.value,
|
|
915
|
+
const childPath2 = (path8 === ":" ? "" : path8) + (e2.key != null ? ":" + e2.key : "[" + i + "]");
|
|
916
|
+
walkNodes(e2.value, childPath2, visit, path8, e2.key, i);
|
|
917
917
|
});
|
|
918
918
|
}
|
|
919
919
|
function formatFromMeta(node) {
|
|
@@ -1465,7 +1465,7 @@ var Block = class {
|
|
|
1465
1465
|
node(minIndent) {
|
|
1466
1466
|
const l = this.peek();
|
|
1467
1467
|
if (!l || l.indent < minIndent) return null;
|
|
1468
|
-
if (/^!!(mix|omni|set)(?=\s|$)/.test(l.text)) {
|
|
1468
|
+
if (/^!!(mix|var|omni|set)(?=\s|$)/.test(l.text)) {
|
|
1469
1469
|
this.i++;
|
|
1470
1470
|
return this.valueAfter(l.text, l.indent - 1, l.n, l.indent);
|
|
1471
1471
|
}
|
|
@@ -1604,9 +1604,9 @@ var Block = class {
|
|
|
1604
1604
|
({ rest, col } = adv(rest, close + 1, col));
|
|
1605
1605
|
}
|
|
1606
1606
|
let typeTag;
|
|
1607
|
-
const tag = /^!!(mix|omni|set)(?=\s|$)/.exec(rest);
|
|
1607
|
+
const tag = /^!!(mix|var|omni|set)(?=\s|$)/.exec(rest);
|
|
1608
1608
|
if (tag) {
|
|
1609
|
-
typeTag = tag[1];
|
|
1609
|
+
typeTag = tag[1] === "var" ? "omni" : tag[1];
|
|
1610
1610
|
({ rest, col } = adv(rest, tag[0].length, col));
|
|
1611
1611
|
}
|
|
1612
1612
|
const anchors = [];
|
|
@@ -2346,6 +2346,8 @@ async function walkTreeAsync(absDir, opts = {}) {
|
|
|
2346
2346
|
return r.value;
|
|
2347
2347
|
}
|
|
2348
2348
|
var BUILTIN_TAG_SCHEMA = "type: object\nformat: x-yamlover-tag\nproperties:\n color:\n type: string\nadditionalProperties: *:: yamlover: $defs: tag\n";
|
|
2349
|
+
var BUILTIN_FRAGMENT_SCHEMA = "type: object\nformat: x-yamlover-fragment\n";
|
|
2350
|
+
var BUILTIN_ANNOTATION_SCHEMA = "type: variant\nformat: x-yamlover-annotation\n";
|
|
2349
2351
|
var BUILTIN_TAGS_BODY = '!!<*yamlover:$defs:tag>\ncolors: The palette\n yellow:\n color: "#f9e2af"\n green:\n color: "#a6e3a1"\n sky:\n color: "#89dceb"\n mauve:\n color: "#cba6f7"\n pink:\n color: "#f5c2e7"\n peach:\n color: "#fab387"\n';
|
|
2350
2352
|
var builtinTemplate = null;
|
|
2351
2353
|
function builtinYamloverGraft() {
|
|
@@ -2354,15 +2356,29 @@ function builtinYamloverGraft() {
|
|
|
2354
2356
|
tags: parseYamlover(BUILTIN_TAGS_BODY, "tags/.yamlover/body.yamlover").root
|
|
2355
2357
|
};
|
|
2356
2358
|
const tagCopy = structuredClone(builtinTemplate.tag);
|
|
2359
|
+
const fragCopy = parseYamlover(BUILTIN_FRAGMENT_SCHEMA, "$defs/fragment").root;
|
|
2360
|
+
const annCopy = parseYamlover(BUILTIN_ANNOTATION_SCHEMA, "$defs/annotation").root;
|
|
2357
2361
|
const node = {
|
|
2358
2362
|
kind: "mapping",
|
|
2359
2363
|
array: false,
|
|
2360
2364
|
entries: [
|
|
2361
|
-
{
|
|
2365
|
+
{
|
|
2366
|
+
key: "$defs",
|
|
2367
|
+
edge: "contain",
|
|
2368
|
+
value: {
|
|
2369
|
+
kind: "mapping",
|
|
2370
|
+
array: false,
|
|
2371
|
+
entries: [
|
|
2372
|
+
{ key: "tag", edge: "contain", value: tagCopy },
|
|
2373
|
+
{ key: "fragment", edge: "contain", value: fragCopy },
|
|
2374
|
+
{ key: "annotation", edge: "contain", value: annCopy }
|
|
2375
|
+
]
|
|
2376
|
+
}
|
|
2377
|
+
},
|
|
2362
2378
|
{ key: "tags", edge: "contain", value: structuredClone(builtinTemplate.tags) }
|
|
2363
2379
|
]
|
|
2364
2380
|
};
|
|
2365
|
-
return { node, defs: /* @__PURE__ */ new Map([["tag", tagCopy]]) };
|
|
2381
|
+
return { node, defs: /* @__PURE__ */ new Map([["tag", tagCopy], ["fragment", fragCopy], ["annotation", annCopy]]) };
|
|
2366
2382
|
}
|
|
2367
2383
|
function* walkTreeGen(absDir, opts = {}) {
|
|
2368
2384
|
const ctx = { root: path.resolve(absDir), opts, files: /* @__PURE__ */ new Map(), count: 0 };
|
|
@@ -3567,6 +3583,157 @@ function compact(xs) {
|
|
|
3567
3583
|
return xs.filter((x) => x !== null);
|
|
3568
3584
|
}
|
|
3569
3585
|
|
|
3586
|
+
// src/server/embed.ts
|
|
3587
|
+
var ANNOTATIONS_KEY = "yamlover-annotations";
|
|
3588
|
+
var FRAGMENTS_KEY = "yamlover-fragments";
|
|
3589
|
+
var indentOf = (line) => {
|
|
3590
|
+
let i = 0;
|
|
3591
|
+
while (line[i] === " ") i++;
|
|
3592
|
+
return i;
|
|
3593
|
+
};
|
|
3594
|
+
var isContentLine = (line) => {
|
|
3595
|
+
const t2 = line.trim();
|
|
3596
|
+
return t2.length > 0 && !t2.startsWith("#");
|
|
3597
|
+
};
|
|
3598
|
+
function firstContentIndent(lines) {
|
|
3599
|
+
for (const l of lines) if (isContentLine(l)) return indentOf(l);
|
|
3600
|
+
return 0;
|
|
3601
|
+
}
|
|
3602
|
+
function keyToken(key) {
|
|
3603
|
+
return /^[A-Za-z0-9_][A-Za-z0-9_-]*$/.test(key) ? key : JSON.stringify(key);
|
|
3604
|
+
}
|
|
3605
|
+
function findKeyLine(lines, lo, hi, indent, key) {
|
|
3606
|
+
const tok = keyToken(key);
|
|
3607
|
+
for (let i = lo; i < hi; i++) {
|
|
3608
|
+
if (!isContentLine(lines[i])) continue;
|
|
3609
|
+
const ind = indentOf(lines[i]);
|
|
3610
|
+
if (ind < indent) return -1;
|
|
3611
|
+
if (ind !== indent) continue;
|
|
3612
|
+
const t2 = lines[i].trim();
|
|
3613
|
+
if (t2 === `${key}:` || t2.startsWith(`${key}: `) || t2 === `${tok}:` || t2.startsWith(`${tok}: `)) return i;
|
|
3614
|
+
}
|
|
3615
|
+
return -1;
|
|
3616
|
+
}
|
|
3617
|
+
function trimBack(lines, floor, end) {
|
|
3618
|
+
let e2 = end;
|
|
3619
|
+
while (e2 > floor + 1 && !isContentLine(lines[e2 - 1])) e2--;
|
|
3620
|
+
return e2;
|
|
3621
|
+
}
|
|
3622
|
+
function blockEnd(lines, from, hi, indent) {
|
|
3623
|
+
let last = from;
|
|
3624
|
+
for (let i = from; i < hi; i++) {
|
|
3625
|
+
if (!isContentLine(lines[i])) continue;
|
|
3626
|
+
if (indentOf(lines[i]) < indent) return trimBack(lines, last, i);
|
|
3627
|
+
last = i;
|
|
3628
|
+
}
|
|
3629
|
+
return trimBack(lines, last, hi);
|
|
3630
|
+
}
|
|
3631
|
+
function reachBody(lines, within) {
|
|
3632
|
+
let lo = 0;
|
|
3633
|
+
let hi = lines.length;
|
|
3634
|
+
let indent = firstContentIndent(lines);
|
|
3635
|
+
if (lines.length === 1 && lines[0] === "") {
|
|
3636
|
+
lines.length = 0;
|
|
3637
|
+
hi = 0;
|
|
3638
|
+
indent = 0;
|
|
3639
|
+
}
|
|
3640
|
+
for (const key of within) {
|
|
3641
|
+
const L = findKeyLine(lines, lo, hi, indent, key);
|
|
3642
|
+
if (L < 0) {
|
|
3643
|
+
const at = trimBack(lines, lo - 1, hi);
|
|
3644
|
+
lines.splice(at, 0, `${" ".repeat(indent)}${keyToken(key)}:`);
|
|
3645
|
+
lo = at + 1;
|
|
3646
|
+
hi = at + 1;
|
|
3647
|
+
indent += 2;
|
|
3648
|
+
continue;
|
|
3649
|
+
}
|
|
3650
|
+
const inline = lines[L].slice(indentOf(lines[L])).slice(`${lines[L].trim().split(":")[0]}:`.length);
|
|
3651
|
+
const bodyLo = L + 1;
|
|
3652
|
+
const bodyHi = blockEnd(lines, bodyLo, hi, indent + 1);
|
|
3653
|
+
let childIndent = indent + 2;
|
|
3654
|
+
for (let i = bodyLo; i < bodyHi; i++) {
|
|
3655
|
+
if (isContentLine(lines[i]) && indentOf(lines[i]) > indent) {
|
|
3656
|
+
childIndent = indentOf(lines[i]);
|
|
3657
|
+
break;
|
|
3658
|
+
}
|
|
3659
|
+
}
|
|
3660
|
+
lo = bodyLo;
|
|
3661
|
+
hi = bodyHi;
|
|
3662
|
+
indent = childIndent;
|
|
3663
|
+
}
|
|
3664
|
+
return { lo, hi, indent };
|
|
3665
|
+
}
|
|
3666
|
+
function seqItemLines(lines, region, key) {
|
|
3667
|
+
const keyLine = findKeyLine(lines, region.lo, region.hi, region.indent, key);
|
|
3668
|
+
if (keyLine < 0) return null;
|
|
3669
|
+
const items = [];
|
|
3670
|
+
let end = keyLine + 1;
|
|
3671
|
+
for (let i = keyLine + 1; i < region.hi; i++) {
|
|
3672
|
+
if (!isContentLine(lines[i])) continue;
|
|
3673
|
+
const ind = indentOf(lines[i]);
|
|
3674
|
+
if (ind < region.indent) break;
|
|
3675
|
+
if (ind === region.indent) {
|
|
3676
|
+
const t2 = lines[i].trim();
|
|
3677
|
+
if (t2 === "-" || t2.startsWith("- ")) {
|
|
3678
|
+
items.push(i);
|
|
3679
|
+
end = i;
|
|
3680
|
+
continue;
|
|
3681
|
+
}
|
|
3682
|
+
break;
|
|
3683
|
+
}
|
|
3684
|
+
end = i;
|
|
3685
|
+
}
|
|
3686
|
+
return { keyLine, items, end: trimBack(lines, end, region.hi) };
|
|
3687
|
+
}
|
|
3688
|
+
function appendAnnotation(text, within, render) {
|
|
3689
|
+
const lines = text.replace(/\n$/, "").split("\n");
|
|
3690
|
+
const region = reachBody(lines, within);
|
|
3691
|
+
const seq = seqItemLines(lines, region, ANNOTATIONS_KEY);
|
|
3692
|
+
if (!seq) {
|
|
3693
|
+
const at = trimBack(lines, region.lo - 1, region.hi);
|
|
3694
|
+
lines.splice(at, 0, `${" ".repeat(region.indent)}${ANNOTATIONS_KEY}:`, ...render(region.indent));
|
|
3695
|
+
} else {
|
|
3696
|
+
lines.splice(seq.end, 0, ...render(region.indent));
|
|
3697
|
+
}
|
|
3698
|
+
return lines.join("\n") + "\n";
|
|
3699
|
+
}
|
|
3700
|
+
function upsertFragment(text, within, slug, render) {
|
|
3701
|
+
const lines = text.replace(/\n$/, "").split("\n");
|
|
3702
|
+
const region = reachBody(lines, within);
|
|
3703
|
+
let fragKey = findKeyLine(lines, region.lo, region.hi, region.indent, FRAGMENTS_KEY);
|
|
3704
|
+
if (fragKey < 0) {
|
|
3705
|
+
const at = trimBack(lines, region.lo - 1, region.hi);
|
|
3706
|
+
lines.splice(at, 0, `${" ".repeat(region.indent)}${FRAGMENTS_KEY}:`);
|
|
3707
|
+
fragKey = at;
|
|
3708
|
+
}
|
|
3709
|
+
const mapIndent = region.indent + 2;
|
|
3710
|
+
const mapBody = { lo: fragKey + 1, hi: blockEnd(lines, fragKey + 1, lines.length, region.indent + 1), indent: mapIndent };
|
|
3711
|
+
const existing = findKeyLine(lines, mapBody.lo, mapBody.hi, mapIndent, slug);
|
|
3712
|
+
if (existing >= 0) {
|
|
3713
|
+
const end = blockEnd(lines, existing + 1, mapBody.hi, mapIndent + 1);
|
|
3714
|
+
lines.splice(existing, end - existing, ...render(mapIndent));
|
|
3715
|
+
} else {
|
|
3716
|
+
const at = trimBack(lines, fragKey, mapBody.hi);
|
|
3717
|
+
lines.splice(at, 0, ...render(mapIndent));
|
|
3718
|
+
}
|
|
3719
|
+
return lines.join("\n") + "\n";
|
|
3720
|
+
}
|
|
3721
|
+
function removeAnnotation(text, within, predicate) {
|
|
3722
|
+
const lines = text.replace(/\n$/, "").split("\n");
|
|
3723
|
+
const region = reachBody(lines, within);
|
|
3724
|
+
const seq = seqItemLines(lines, region, ANNOTATIONS_KEY);
|
|
3725
|
+
if (!seq) return text;
|
|
3726
|
+
for (let k = 0; k < seq.items.length; k++) {
|
|
3727
|
+
const i = seq.items[k];
|
|
3728
|
+
const itemText = lines[i].trim().replace(/^-\s*/, "");
|
|
3729
|
+
if (!predicate(itemText)) continue;
|
|
3730
|
+
const next = k + 1 < seq.items.length ? seq.items[k + 1] : seq.end;
|
|
3731
|
+
lines.splice(i, next - i);
|
|
3732
|
+
return lines.join("\n") + "\n";
|
|
3733
|
+
}
|
|
3734
|
+
return text;
|
|
3735
|
+
}
|
|
3736
|
+
|
|
3570
3737
|
// src/server/gitignore.ts
|
|
3571
3738
|
var import_ignore = __toESM(require_ignore(), 1);
|
|
3572
3739
|
import fs5 from "node:fs";
|
|
@@ -3652,6 +3819,14 @@ function scalarType(v) {
|
|
|
3652
3819
|
if (typeof v === "number") return Number.isInteger(v) ? "integer" : "number";
|
|
3653
3820
|
return "string";
|
|
3654
3821
|
}
|
|
3822
|
+
function facetsOf(s, p, row) {
|
|
3823
|
+
const ents = ownedEntries(s, p);
|
|
3824
|
+
return {
|
|
3825
|
+
valueType: row.type === "scalar" ? scalarType(row.value) : row.type === "blob" ? "binary" : null,
|
|
3826
|
+
hasKeyed: ents.some((e2) => e2.label !== null),
|
|
3827
|
+
hasOrdinal: ents.some((e2) => e2.label === null)
|
|
3828
|
+
};
|
|
3829
|
+
}
|
|
3655
3830
|
|
|
3656
3831
|
// src/server/tasks.ts
|
|
3657
3832
|
var PROGRESS_EMIT_MS = 150;
|
|
@@ -3899,31 +4074,40 @@ function createHandlers(dataRoot, opts = {}) {
|
|
|
3899
4074
|
}
|
|
3900
4075
|
if (req.method === "POST" && url.pathname === "/api/annotate") {
|
|
3901
4076
|
readBody(req).then(
|
|
3902
|
-
(data) => (
|
|
3903
|
-
|
|
3904
|
-
|
|
3905
|
-
|
|
3906
|
-
|
|
3907
|
-
|
|
3908
|
-
|
|
3909
|
-
|
|
3910
|
-
|
|
3911
|
-
|
|
3912
|
-
|
|
3913
|
-
|
|
3914
|
-
|
|
3915
|
-
|
|
3916
|
-
|
|
3917
|
-
|
|
4077
|
+
(data) => enqueue(async () => {
|
|
4078
|
+
const a = data;
|
|
4079
|
+
const tagStore = storePath(strToSegs(a.tag ?? ""));
|
|
4080
|
+
if (!a?.tag || s.node(tagStore)?.format !== TAG_FORMAT) {
|
|
4081
|
+
throw new Error("annotation needs a `tag` that is an x-yamlover-tag node");
|
|
4082
|
+
}
|
|
4083
|
+
embedAnnotation(dataRoot, s, a);
|
|
4084
|
+
broadcast(await doReindex());
|
|
4085
|
+
scheduleHasher();
|
|
4086
|
+
return { ok: true };
|
|
4087
|
+
})
|
|
4088
|
+
).then((body) => sendJson(res, 201, body)).catch((e2) => sendJson(res, 400, { error: String(e2.message || e2) }));
|
|
4089
|
+
return;
|
|
4090
|
+
}
|
|
4091
|
+
if (req.method === "POST" && url.pathname === "/api/fragment") {
|
|
4092
|
+
readBody(req).then(
|
|
4093
|
+
(data) => enqueue(async () => {
|
|
4094
|
+
const f = data;
|
|
4095
|
+
if (!f?.selector || typeof f.selector !== "object") throw new Error("a fragment needs a selector");
|
|
4096
|
+
const made = embedFragment(dataRoot, s, f);
|
|
4097
|
+
broadcast(await doReindex());
|
|
4098
|
+
scheduleHasher();
|
|
4099
|
+
return made;
|
|
4100
|
+
})
|
|
3918
4101
|
).then((body) => sendJson(res, 201, body)).catch((e2) => sendJson(res, 400, { error: String(e2.message || e2) }));
|
|
3919
4102
|
return;
|
|
3920
4103
|
}
|
|
3921
4104
|
if (req.method === "DELETE" && url.pathname === "/api/annotate") {
|
|
3922
|
-
const
|
|
3923
|
-
|
|
3924
|
-
|
|
3925
|
-
|
|
3926
|
-
|
|
4105
|
+
const target = url.searchParams.get("target") ?? "";
|
|
4106
|
+
const tag = url.searchParams.get("tag") || "";
|
|
4107
|
+
enqueue(async () => {
|
|
4108
|
+
if (!tag) throw new Error("delete needs a `tag`");
|
|
4109
|
+
unembedAnnotation(dataRoot, s, target, tag);
|
|
4110
|
+
broadcast(await doReindex());
|
|
3927
4111
|
}).then(() => sendJson(res, 200, { ok: true })).catch((e2) => sendJson(res, 400, { error: String(e2.message || e2) }));
|
|
3928
4112
|
return;
|
|
3929
4113
|
}
|
|
@@ -4023,6 +4207,8 @@ function createHandlers(dataRoot, opts = {}) {
|
|
|
4023
4207
|
path: segsToStr(segs),
|
|
4024
4208
|
type: tocType(s, p, row),
|
|
4025
4209
|
format: row.format ?? null,
|
|
4210
|
+
...facetsOf(s, p, row),
|
|
4211
|
+
// valueType / hasKeyed / hasOrdinal — the renderer dispatch facets (TYPES.md §9)
|
|
4026
4212
|
concrete: concreteOf(dataRoot, segs, row),
|
|
4027
4213
|
// dir | yamlover | null (stat-derived; engine tracks no per-node concrete yet)
|
|
4028
4214
|
documentPath: documentPath(s, segs),
|
|
@@ -4148,7 +4334,7 @@ function linkMarker(dataRoot, s, segs) {
|
|
|
4148
4334
|
const p = storePath(segs);
|
|
4149
4335
|
const row = s.node(p);
|
|
4150
4336
|
const k = displayKind(s, p, row);
|
|
4151
|
-
const info = { kind: k, type: tocType(s, p, row), path: segsToStr(segs) };
|
|
4337
|
+
const info = { kind: k, type: tocType(s, p, row), ...facetsOf(s, p, row), path: segsToStr(segs) };
|
|
4152
4338
|
if (row.format) info.format = row.format;
|
|
4153
4339
|
const concrete = concreteOf(dataRoot, segs, row);
|
|
4154
4340
|
if (concrete) info.concrete = concrete;
|
|
@@ -4207,6 +4393,7 @@ function buildTree(dataRoot, s, segs, label, depth) {
|
|
|
4207
4393
|
label,
|
|
4208
4394
|
type: tocType(s, p, row),
|
|
4209
4395
|
format: row.format ?? null,
|
|
4396
|
+
...facetsOf(s, p, row),
|
|
4210
4397
|
concrete: concreteOf(dataRoot, segs, row),
|
|
4211
4398
|
hasChildren: s.hasChildren(p),
|
|
4212
4399
|
children: []
|
|
@@ -4225,45 +4412,77 @@ function labelFor(s, p, keyOrIdx) {
|
|
|
4225
4412
|
return typeof keyOrIdx === "number" ? `[${keyOrIdx}]` : keyOrIdx;
|
|
4226
4413
|
}
|
|
4227
4414
|
var TAG_FORMAT = "x-yamlover-tag";
|
|
4228
|
-
|
|
4229
|
-
|
|
4230
|
-
|
|
4231
|
-
|
|
4232
|
-
|
|
4233
|
-
|
|
4234
|
-
const
|
|
4415
|
+
var ANN_KEY = "yamlover-annotations";
|
|
4416
|
+
var FRAG_KEY = "yamlover-fragments";
|
|
4417
|
+
var CROP_DIR = "fragments";
|
|
4418
|
+
var childPath = (parent, key) => (parent === ":" ? "" : parent) + ":" + key;
|
|
4419
|
+
function projectTag(s, tagStore) {
|
|
4420
|
+
if (s.node(tagStore)?.format !== TAG_FORMAT) return null;
|
|
4421
|
+
const segs = storePathToSegs(tagStore);
|
|
4422
|
+
const color = s.node(tagStore + ":color")?.value;
|
|
4235
4423
|
return { path: segsToStr(segs), name: String(segs[segs.length - 1] ?? ""), color: typeof color === "string" ? color : null };
|
|
4236
4424
|
}
|
|
4425
|
+
function readAnnotations(s, hostStore) {
|
|
4426
|
+
const arr = childPath(hostStore, ANN_KEY);
|
|
4427
|
+
if (!s.node(arr)) return [];
|
|
4428
|
+
const out = [];
|
|
4429
|
+
for (const e2 of s.entries(arr)) {
|
|
4430
|
+
if (e2.kind === "ref") {
|
|
4431
|
+
const tag = projectTag(s, e2.to);
|
|
4432
|
+
if (tag) out.push({ tag });
|
|
4433
|
+
} else if (e2.kind === "contain") {
|
|
4434
|
+
const tagEdge = s.relationships(e2.to).out.find((o) => o.kind === "ref" && o.label === "tag");
|
|
4435
|
+
const tag = tagEdge ? projectTag(s, tagEdge.to) : null;
|
|
4436
|
+
if (!tag) continue;
|
|
4437
|
+
const params = {};
|
|
4438
|
+
let description;
|
|
4439
|
+
for (const c of s.children(e2.to)) {
|
|
4440
|
+
const v = s.node(c.to)?.value;
|
|
4441
|
+
if (c.label === "description") description = v == null ? void 0 : String(v);
|
|
4442
|
+
else if (c.label) params[c.label] = v;
|
|
4443
|
+
}
|
|
4444
|
+
out.push({ tag, description, params: Object.keys(params).length ? params : void 0 });
|
|
4445
|
+
}
|
|
4446
|
+
}
|
|
4447
|
+
return out;
|
|
4448
|
+
}
|
|
4449
|
+
function readFragments(s, hostStore) {
|
|
4450
|
+
const frags = childPath(hostStore, FRAG_KEY);
|
|
4451
|
+
if (!s.node(frags)) return [];
|
|
4452
|
+
const out = [];
|
|
4453
|
+
for (const fc of s.children(frags)) {
|
|
4454
|
+
if (!fc.label) continue;
|
|
4455
|
+
const selector = {};
|
|
4456
|
+
for (const c of s.children(fc.to)) {
|
|
4457
|
+
if (c.label && c.label !== ANN_KEY && c.label !== "created") selector[c.label] = s.node(c.to)?.value;
|
|
4458
|
+
}
|
|
4459
|
+
const imgEdge = s.relationships(fc.to).out.find((o) => o.kind === "ref" && o.label === "image");
|
|
4460
|
+
const imageUrl = imgEdge ? `/api/blob?path=${encodeURIComponent(segsToStr(storePathToSegs(imgEdge.to)))}` : void 0;
|
|
4461
|
+
out.push({ slug: fc.label, node: fc.to, selector, imageUrl });
|
|
4462
|
+
}
|
|
4463
|
+
return out;
|
|
4464
|
+
}
|
|
4237
4465
|
function annotationsFor(dataRoot, s, segs) {
|
|
4238
4466
|
const p = storePath(segs);
|
|
4239
4467
|
const out = [];
|
|
4240
|
-
for (const
|
|
4241
|
-
|
|
4242
|
-
const
|
|
4243
|
-
|
|
4244
|
-
|
|
4245
|
-
out.push({
|
|
4246
|
-
path: segsToStr(aSegs),
|
|
4247
|
-
tag: appliedTag(s, e2.from),
|
|
4248
|
-
...projectValue(dataRoot, s, aSegs, 6, true)
|
|
4249
|
-
});
|
|
4468
|
+
for (const a of readAnnotations(s, p)) out.push({ ...a });
|
|
4469
|
+
for (const f of readFragments(s, p)) {
|
|
4470
|
+
for (const a of readAnnotations(s, f.node)) {
|
|
4471
|
+
out.push({ ...a, selector: f.selector, fragmentSlug: f.slug, ...f.imageUrl ? { imageUrl: f.imageUrl } : {} });
|
|
4472
|
+
}
|
|
4250
4473
|
}
|
|
4251
4474
|
return out;
|
|
4252
4475
|
}
|
|
4253
4476
|
function taggedMaterials(dataRoot, s, tagStorePath) {
|
|
4254
4477
|
const seen = /* @__PURE__ */ new Set();
|
|
4255
4478
|
const out = [];
|
|
4256
|
-
const
|
|
4257
|
-
for (const e2 of
|
|
4258
|
-
|
|
4259
|
-
|
|
4260
|
-
|
|
4261
|
-
|
|
4262
|
-
|
|
4263
|
-
}
|
|
4264
|
-
if (seen.has(material) || !s.node(material)) continue;
|
|
4265
|
-
seen.add(material);
|
|
4266
|
-
out.push(linkMarker(dataRoot, s, storePathToSegs(material)));
|
|
4479
|
+
const ins = s.relationships(tagStorePath).in.filter((e2) => (e2.kind === "ref" || e2.kind === "back") && e2.from).sort((a, b) => a.from < b.from ? -1 : a.from > b.from ? 1 : 0);
|
|
4480
|
+
for (const e2 of ins) {
|
|
4481
|
+
const arrOwner = e2.from.replace(/\[\d+\]$/, "").match(/^(.*):yamlover-annotations$/);
|
|
4482
|
+
const owner = arrOwner ? arrOwner[1] || ":" : e2.from;
|
|
4483
|
+
if (owner === tagStorePath || seen.has(owner) || !s.node(owner)) continue;
|
|
4484
|
+
seen.add(owner);
|
|
4485
|
+
out.push(linkMarker(dataRoot, s, storePathToSegs(owner)));
|
|
4267
4486
|
}
|
|
4268
4487
|
return out;
|
|
4269
4488
|
}
|
|
@@ -4277,25 +4496,77 @@ function pointerRaw(clientPath) {
|
|
|
4277
4496
|
function yScalar(v) {
|
|
4278
4497
|
return typeof v === "number" || typeof v === "boolean" ? String(v) : JSON.stringify(String(v ?? ""));
|
|
4279
4498
|
}
|
|
4280
|
-
function
|
|
4281
|
-
|
|
4282
|
-
|
|
4283
|
-
|
|
4284
|
-
|
|
4285
|
-
|
|
4286
|
-
|
|
4287
|
-
|
|
4288
|
-
|
|
4289
|
-
|
|
4290
|
-
|
|
4291
|
-
|
|
4292
|
-
|
|
4293
|
-
|
|
4294
|
-
|
|
4295
|
-
|
|
4296
|
-
|
|
4297
|
-
|
|
4298
|
-
|
|
4499
|
+
function hostFor(dataRoot, s, segs) {
|
|
4500
|
+
for (let i = segs.length; i >= 0; i--) {
|
|
4501
|
+
const sub = segs.slice(0, i);
|
|
4502
|
+
const abs = path7.resolve(dataRoot, ...sub.map(String));
|
|
4503
|
+
let st;
|
|
4504
|
+
try {
|
|
4505
|
+
st = fs6.statSync(abs);
|
|
4506
|
+
} catch {
|
|
4507
|
+
continue;
|
|
4508
|
+
}
|
|
4509
|
+
if (st.isDirectory()) return { bodyFile: path7.join(abs, ".yamlover", "body.yamlover"), within: segs.slice(i).map(String) };
|
|
4510
|
+
if (st.isFile()) {
|
|
4511
|
+
const node = s.node(storePath(sub));
|
|
4512
|
+
if (node?.meta?.documentRoot && node.type === "mapping" && !node.is_array) {
|
|
4513
|
+
return { bodyFile: abs, within: segs.slice(i).map(String) };
|
|
4514
|
+
}
|
|
4515
|
+
const dir = path7.resolve(dataRoot, ...sub.slice(0, -1).map(String));
|
|
4516
|
+
return { bodyFile: path7.join(dir, ".yamlover", "body.yamlover"), within: segs.slice(i - 1).map(String) };
|
|
4517
|
+
}
|
|
4518
|
+
}
|
|
4519
|
+
return { bodyFile: path7.join(dataRoot, ".yamlover", "body.yamlover"), within: segs.map(String) };
|
|
4520
|
+
}
|
|
4521
|
+
function annotationItemLines(a, indent) {
|
|
4522
|
+
const pad = " ".repeat(indent);
|
|
4523
|
+
const ptr = pointerToken(pointerRaw(a.tag));
|
|
4524
|
+
const params = { ...a.params ?? {} };
|
|
4525
|
+
if (a.description != null && a.description !== "") params.description = a.description;
|
|
4526
|
+
const keys = Object.keys(params);
|
|
4527
|
+
if (keys.length === 0) return [`${pad}- ${ptr}`];
|
|
4528
|
+
return [`${pad}- tag: ${ptr}`, ...keys.map((k) => `${pad} ${keyToken(k)}: ${yScalar(params[k])}`)];
|
|
4529
|
+
}
|
|
4530
|
+
function fragmentBlockLines(slug, selector, imagePtr, indent) {
|
|
4531
|
+
const pad = " ".repeat(indent);
|
|
4532
|
+
const lines = [`${pad}${keyToken(slug)}: !!<*::yamlover:$defs:fragment>`];
|
|
4533
|
+
for (const [k, v] of Object.entries(selector)) lines.push(`${pad} ${keyToken(k)}: ${yScalar(v)}`);
|
|
4534
|
+
if (imagePtr) lines.push(`${pad} image: ${imagePtr}`);
|
|
4535
|
+
lines.push(`${pad} created: ${(/* @__PURE__ */ new Date()).toISOString()}`);
|
|
4536
|
+
return lines;
|
|
4537
|
+
}
|
|
4538
|
+
function embedAnnotation(dataRoot, s, a) {
|
|
4539
|
+
const { bodyFile, within } = hostFor(dataRoot, s, strToSegs(a.target || ":"));
|
|
4540
|
+
fs6.mkdirSync(path7.dirname(bodyFile), { recursive: true });
|
|
4541
|
+
const src = fs6.existsSync(bodyFile) ? fs6.readFileSync(bodyFile, "utf8") : "";
|
|
4542
|
+
fs6.writeFileSync(bodyFile, appendAnnotation(src, within, (indent) => annotationItemLines(a, indent)));
|
|
4543
|
+
}
|
|
4544
|
+
function embedFragment(dataRoot, s, f) {
|
|
4545
|
+
const segs = strToSegs(f.target || ":");
|
|
4546
|
+
const { bodyFile, within } = hostFor(dataRoot, s, segs);
|
|
4547
|
+
const slug = `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;
|
|
4548
|
+
let imagePtr = null;
|
|
4549
|
+
if (f.imageBase64) {
|
|
4550
|
+
const bytes = Buffer.from(String(f.imageBase64).replace(/^data:[^,]*,/, ""), "base64");
|
|
4551
|
+
if (bytes.length > 0) {
|
|
4552
|
+
const cropDir = path7.join(dataRoot, CROP_DIR);
|
|
4553
|
+
fs6.mkdirSync(cropDir, { recursive: true });
|
|
4554
|
+
const cropName = `${slug}.png`;
|
|
4555
|
+
writeInside(dataRoot, cropDir, cropName, bytes);
|
|
4556
|
+
imagePtr = pointerToken(pointerRaw(segsToStr([CROP_DIR, cropName])));
|
|
4557
|
+
}
|
|
4558
|
+
}
|
|
4559
|
+
fs6.mkdirSync(path7.dirname(bodyFile), { recursive: true });
|
|
4560
|
+
const src = fs6.existsSync(bodyFile) ? fs6.readFileSync(bodyFile, "utf8") : "";
|
|
4561
|
+
fs6.writeFileSync(bodyFile, upsertFragment(src, within, slug, (indent) => fragmentBlockLines(slug, f.selector, imagePtr, indent)));
|
|
4562
|
+
return { slug, fragmentPath: segsToStr([...segs, FRAG_KEY, slug]) };
|
|
4563
|
+
}
|
|
4564
|
+
function unembedAnnotation(dataRoot, s, target, tag) {
|
|
4565
|
+
const { bodyFile, within } = hostFor(dataRoot, s, strToSegs(target || ":"));
|
|
4566
|
+
if (!fs6.existsSync(bodyFile)) return;
|
|
4567
|
+
const needle = pointerRaw(tag);
|
|
4568
|
+
const src = fs6.readFileSync(bodyFile, "utf8");
|
|
4569
|
+
fs6.writeFileSync(bodyFile, removeAnnotation(src, within, (itemText) => itemText.includes(needle)));
|
|
4299
4570
|
}
|
|
4300
4571
|
function writeTag(dataRoot, location, name) {
|
|
4301
4572
|
if (/[/\\\r\n:]/.test(name)) throw new Error("a tag name cannot contain '/', '\\', ':' or line breaks");
|
|
@@ -4306,7 +4577,7 @@ function writeTag(dataRoot, location, name) {
|
|
|
4306
4577
|
const createdFile = !fs6.existsSync(file);
|
|
4307
4578
|
const head = "# Named tags created from the annotation picker (settings.yamlover: tags.location).\n";
|
|
4308
4579
|
const existing = createdFile ? head : fs6.readFileSync(file, "utf8");
|
|
4309
|
-
const body = (existing === "" || existing.endsWith("\n") ? existing : existing + "\n") + `${name}:
|
|
4580
|
+
const body = (existing === "" || existing.endsWith("\n") ? existing : existing + "\n") + `${name}: !!<*::yamlover:$defs:tag>
|
|
4310
4581
|
`;
|
|
4311
4582
|
const entries = parseYamlover(body, file).root.entries ?? [];
|
|
4312
4583
|
const pos = entries.findIndex((e2) => e2.key === name);
|
|
@@ -4318,16 +4589,6 @@ function writeTag(dataRoot, location, name) {
|
|
|
4318
4589
|
fs6.writeFileSync(file, body);
|
|
4319
4590
|
return { node: entry.value, pos, file: [...strToSegs(location).map(String), ".yamlover", "body.yamlover"].join("/"), createdFile };
|
|
4320
4591
|
}
|
|
4321
|
-
function deleteAnnotation(dataRoot, s, annPath) {
|
|
4322
|
-
const segs = strToSegs(annPath);
|
|
4323
|
-
if (!String(segs[segs.length - 1] ?? "").endsWith(".yamlover")) throw new Error("not an annotation file");
|
|
4324
|
-
if (s.node(storePath(segs))?.format !== "x-yamlover-annotation") throw new Error("not an annotation node");
|
|
4325
|
-
const root = path7.resolve(dataRoot);
|
|
4326
|
-
const file = path7.resolve(dataRoot, ...segs.map(String));
|
|
4327
|
-
if (!file.startsWith(root + path7.sep)) throw new Error("outside the served root");
|
|
4328
|
-
if (!fs6.existsSync(file) || fs6.statSync(file).isDirectory()) throw new Error("not an annotation file");
|
|
4329
|
-
fs6.rmSync(file, { force: true });
|
|
4330
|
-
}
|
|
4331
4592
|
function handlePaste(dataRoot, s, input) {
|
|
4332
4593
|
const segs = strToSegs(input.path || ":");
|
|
4333
4594
|
const row = s.node(storePath(segs));
|
|
@@ -4398,7 +4659,7 @@ function pasteTextAsChapterFile(dataRoot, segs, text) {
|
|
|
4398
4659
|
const dir = path7.resolve(dataRoot, ...dirSegs.map(String));
|
|
4399
4660
|
const title = titleFromText(text);
|
|
4400
4661
|
const final = uniqueName(dir, chapterFileName(title));
|
|
4401
|
-
const src = ["
|
|
4662
|
+
const src = ["!!<*::yamlover:$defs:chapter>", `title: ${JSON.stringify(title)}`, "chunks:", ...textChunkLines(text, 0), ""].join("\n");
|
|
4402
4663
|
writeInside(dataRoot, dir, final, Buffer.from(src, "utf8"));
|
|
4403
4664
|
return { path: segsToStr([...dirSegs, final]), dir: segsToStr(dirSegs), open: dirSegs.length !== segs.length };
|
|
4404
4665
|
}
|
|
@@ -4486,7 +4747,7 @@ function pasteRichAsChapter(dataRoot, segs, rich) {
|
|
|
4486
4747
|
return { path: segsToStr([...dirSegs, name]), dir: segsToStr(dirSegs), open: dirSegs.length !== segs.length };
|
|
4487
4748
|
}
|
|
4488
4749
|
function renderChapterSource(title, rich, pointerFor) {
|
|
4489
|
-
const lines = ["
|
|
4750
|
+
const lines = ["!!<*::yamlover:$defs:chapter>", `title: ${JSON.stringify(title)}`];
|
|
4490
4751
|
if (rich.chunks.length) lines.push("chunks:", ...rich.chunks.flatMap((c) => richItemLines(c, 0, pointerFor)));
|
|
4491
4752
|
if (rich.children.length) lines.push("children:", ...rich.children.flatMap((k) => richChildLines(k, 0, pointerFor)));
|
|
4492
4753
|
return lines.join("\n") + "\n";
|
|
@@ -4522,12 +4783,12 @@ function writeInside(dataRoot, dir, name, bytes) {
|
|
|
4522
4783
|
if (target !== root && !target.startsWith(root + path7.sep)) throw new Error("target escapes the data root");
|
|
4523
4784
|
fs6.writeFileSync(target, bytes);
|
|
4524
4785
|
}
|
|
4525
|
-
var
|
|
4786
|
+
var indentOf2 = (line) => {
|
|
4526
4787
|
let i = 0;
|
|
4527
4788
|
while (line[i] === " ") i++;
|
|
4528
4789
|
return i;
|
|
4529
4790
|
};
|
|
4530
|
-
var
|
|
4791
|
+
var isContentLine2 = (line) => {
|
|
4531
4792
|
const t2 = line.trim();
|
|
4532
4793
|
return t2.length > 0 && !t2.startsWith("#");
|
|
4533
4794
|
};
|
|
@@ -4543,10 +4804,10 @@ function appendToList(text, chapterPath, key, renderItems) {
|
|
|
4543
4804
|
const lines = text.split("\n");
|
|
4544
4805
|
let lo = 0;
|
|
4545
4806
|
let hi = lines.length;
|
|
4546
|
-
let indent =
|
|
4807
|
+
let indent = firstContentIndent2(lines);
|
|
4547
4808
|
for (let i = 0; i < chapterPath.length; i += 2) {
|
|
4548
4809
|
const idx = Number(chapterPath[i + 1]);
|
|
4549
|
-
const kids =
|
|
4810
|
+
const kids = findKeyLine2(lines, lo, hi, indent, "children");
|
|
4550
4811
|
if (kids < 0) throw new Error(`no 'children:' at indent ${indent}`);
|
|
4551
4812
|
const items = seqItems(lines, kids + 1, hi, indent);
|
|
4552
4813
|
if (!(idx >= 0 && idx < items.length)) throw new Error(`children[${idx}] out of range (${items.length})`);
|
|
@@ -4554,9 +4815,9 @@ function appendToList(text, chapterPath, key, renderItems) {
|
|
|
4554
4815
|
lo = items[idx] + 1;
|
|
4555
4816
|
indent += 2;
|
|
4556
4817
|
}
|
|
4557
|
-
const keyLine =
|
|
4818
|
+
const keyLine = findKeyLine2(lines, lo, hi, indent, key);
|
|
4558
4819
|
if (keyLine < 0) {
|
|
4559
|
-
const end =
|
|
4820
|
+
const end = trimBack2(lines, lo - 1, hi);
|
|
4560
4821
|
lines.splice(end, 0, `${" ".repeat(indent)}${key}:`, ...renderItems(indent));
|
|
4561
4822
|
} else {
|
|
4562
4823
|
const end = seqEnd(lines, keyLine + 1, hi, indent);
|
|
@@ -4564,14 +4825,14 @@ function appendToList(text, chapterPath, key, renderItems) {
|
|
|
4564
4825
|
}
|
|
4565
4826
|
return lines.join("\n");
|
|
4566
4827
|
}
|
|
4567
|
-
function
|
|
4568
|
-
for (const l of lines) if (
|
|
4828
|
+
function firstContentIndent2(lines) {
|
|
4829
|
+
for (const l of lines) if (isContentLine2(l)) return indentOf2(l);
|
|
4569
4830
|
return 0;
|
|
4570
4831
|
}
|
|
4571
|
-
function
|
|
4832
|
+
function findKeyLine2(lines, lo, hi, indent, key) {
|
|
4572
4833
|
for (let i = lo; i < hi; i++) {
|
|
4573
|
-
if (!
|
|
4574
|
-
const ind =
|
|
4834
|
+
if (!isContentLine2(lines[i])) continue;
|
|
4835
|
+
const ind = indentOf2(lines[i]);
|
|
4575
4836
|
if (ind < indent) return -1;
|
|
4576
4837
|
if (ind !== indent) continue;
|
|
4577
4838
|
const t2 = lines[i].trim();
|
|
@@ -4582,8 +4843,8 @@ function findKeyLine(lines, lo, hi, indent, key) {
|
|
|
4582
4843
|
function seqItems(lines, from, hi, indent) {
|
|
4583
4844
|
const out = [];
|
|
4584
4845
|
for (let i = from; i < hi; i++) {
|
|
4585
|
-
if (!
|
|
4586
|
-
const ind =
|
|
4846
|
+
if (!isContentLine2(lines[i])) continue;
|
|
4847
|
+
const ind = indentOf2(lines[i]);
|
|
4587
4848
|
if (ind < indent) break;
|
|
4588
4849
|
if (ind !== indent) continue;
|
|
4589
4850
|
const t2 = lines[i].trim();
|
|
@@ -4595,24 +4856,24 @@ function seqItems(lines, from, hi, indent) {
|
|
|
4595
4856
|
function seqEnd(lines, from, hi, indent) {
|
|
4596
4857
|
let last = from;
|
|
4597
4858
|
for (let i = from; i < hi; i++) {
|
|
4598
|
-
if (!
|
|
4599
|
-
const ind =
|
|
4600
|
-
if (ind < indent) return
|
|
4859
|
+
if (!isContentLine2(lines[i])) continue;
|
|
4860
|
+
const ind = indentOf2(lines[i]);
|
|
4861
|
+
if (ind < indent) return trimBack2(lines, last, i);
|
|
4601
4862
|
if (ind === indent) {
|
|
4602
4863
|
const t2 = lines[i].trim();
|
|
4603
4864
|
if (t2 === "-" || t2.startsWith("- ")) {
|
|
4604
4865
|
last = i;
|
|
4605
4866
|
continue;
|
|
4606
4867
|
}
|
|
4607
|
-
return
|
|
4868
|
+
return trimBack2(lines, last, i);
|
|
4608
4869
|
}
|
|
4609
4870
|
last = i;
|
|
4610
4871
|
}
|
|
4611
|
-
return
|
|
4872
|
+
return trimBack2(lines, last, hi);
|
|
4612
4873
|
}
|
|
4613
|
-
function
|
|
4874
|
+
function trimBack2(lines, lastItemLine, end) {
|
|
4614
4875
|
let e2 = end;
|
|
4615
|
-
while (e2 > lastItemLine + 1 && !
|
|
4876
|
+
while (e2 > lastItemLine + 1 && !isContentLine2(lines[e2 - 1])) e2--;
|
|
4616
4877
|
return e2;
|
|
4617
4878
|
}
|
|
4618
4879
|
function readBody(req) {
|