strom-research 1.5.0 → 1.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/assets/lang/cs.json +82 -0
- package/assets/lang/de.json +82 -0
- package/assets/method/narrate.md +2 -1
- package/assets/method/recording.md +3 -1
- package/dist/agents/files.js +1 -1
- package/dist/cli/human.js +18 -0
- package/dist/cli/main.js +9 -0
- package/dist/cli/menu-links.js +249 -0
- package/dist/cli/menu-parts.js +2 -2
- package/dist/cli/menu-research.js +35 -16
- package/dist/cli/menu-waiting.js +61 -47
- package/dist/cli/ui.js +82 -0
- package/dist/cli/wizard.js +34 -1
- package/dist/commands/analysis.js +6 -2
- package/dist/commands/browse.js +2 -17
- package/dist/commands/guide.js +6 -1
- package/dist/commands/index.js +1 -0
- package/dist/commands/links.js +367 -0
- package/dist/commands/output.js +7 -1
- package/dist/commands/setup.js +13 -2
- package/dist/commands/start.js +6 -2
- package/dist/commands/story.js +17 -1
- package/dist/commands/sync.js +20 -6
- package/dist/core/actions.js +15 -0
- package/dist/core/dialog.js +25 -0
- package/dist/core/excerpt.js +53 -5
- package/dist/core/links.js +357 -0
- package/dist/core/live.js +81 -9
- package/dist/core/schema.js +1 -0
- package/dist/core/session.js +6 -0
- package/dist/core/stromapp.js +25 -0
- package/dist/core/sync.js +92 -2
- package/dist/core/tree.js +1 -1
- package/dist/core/uninstall.js +3 -0
- package/dist/gedcom/export.js +54 -0
- package/dist/gedcom/validate.js +13 -4
- package/package.json +1 -1
package/dist/core/stromapp.js
CHANGED
|
@@ -39,10 +39,35 @@ export const APP_SENDS_CHANGES = "3.3.0";
|
|
|
39
39
|
export function appSendsChanges(settings) {
|
|
40
40
|
return APP_SENDS_CHANGES !== undefined || stromAppUrl(settings) !== STROM_APP_URL;
|
|
41
41
|
}
|
|
42
|
+
/**
|
|
43
|
+
* The Strom app opens strom-research:// links (the app's spec docs/ZADANI_VYZKUM_ODKAZY_Z_APLIKACE.md): it reads
|
|
44
|
+
* each excerpt's mark (_STROM_CLIP) and the links a research offers (_STROM_LINKS in the GEDCOM the bridge serves).
|
|
45
|
+
* The first version of stromapp.info that does (3.4.0, 2026-09-27); another copy of the app (its beta, its
|
|
46
|
+
* development: strom.app.url) is taken as current. An app of a known version (strom.version) goes by it.
|
|
47
|
+
*/
|
|
48
|
+
export const APP_OPENS_LINKS = "3.4.0";
|
|
49
|
+
export function appOpensLinks(settings, version) {
|
|
50
|
+
if (stromAppUrl(settings) !== STROM_APP_URL)
|
|
51
|
+
return true;
|
|
52
|
+
if (!APP_OPENS_LINKS)
|
|
53
|
+
return false;
|
|
54
|
+
if (!version)
|
|
55
|
+
return true;
|
|
56
|
+
const n = (v) => v.split(".").map((x) => Number.parseInt(x, 10) || 0);
|
|
57
|
+
const [a, b] = [n(version), n(APP_OPENS_LINKS)];
|
|
58
|
+
for (let i = 0; i < 3; i++)
|
|
59
|
+
if ((a[i] ?? 0) !== (b[i] ?? 0))
|
|
60
|
+
return (a[i] ?? 0) > (b[i] ?? 0);
|
|
61
|
+
return true;
|
|
62
|
+
}
|
|
42
63
|
/** The address that opens the Strom app to send the user's edited tree to the bridge. */
|
|
43
64
|
export function sendAppUrl(bridge, settings) {
|
|
44
65
|
return `${stromAppUrl(settings)}?send=${encodeURIComponent(bridge)}`;
|
|
45
66
|
}
|
|
67
|
+
/** The address that opens the Strom app to hand one of its trees to a new research (strom-research://new). */
|
|
68
|
+
export function adoptAppUrl(bridge, settings) {
|
|
69
|
+
return `${stromAppUrl(settings)}?adopt=${encodeURIComponent(bridge)}`;
|
|
70
|
+
}
|
|
46
71
|
/** The address that opens the Strom app following a research through its bridge. */
|
|
47
72
|
export function liveAppUrl(bridge, settings) {
|
|
48
73
|
return `${stromAppUrl(settings)}?live=${encodeURIComponent(bridge)}`;
|
package/dist/core/sync.js
CHANGED
|
@@ -358,7 +358,86 @@ export function receivedSince(root, since) {
|
|
|
358
358
|
return undefined;
|
|
359
359
|
return fs
|
|
360
360
|
.readdirSync(dir)
|
|
361
|
-
.filter((f) => f.endsWith(".ged"))
|
|
361
|
+
.filter((f) => f.endsWith(".ged") && !f.startsWith("adopt-"))
|
|
362
|
+
.map((f) => path.join(dir, f))
|
|
363
|
+
.filter((f) => fs.statSync(f).mtimeMs >= since)
|
|
364
|
+
.sort()
|
|
365
|
+
.pop();
|
|
366
|
+
}
|
|
367
|
+
// ── a tree of the Strom app becomes a research (strom-research://new, ?adopt=) ──
|
|
368
|
+
/** Where a new research waits for the tree of the app it was started for: the app's mark, since when. */
|
|
369
|
+
const ADOPT_FILE = path.join(".strom", "adopt.json");
|
|
370
|
+
/** How long the app's mark is good (the app keeps it an hour too). */
|
|
371
|
+
export const ADOPT_FOR_MS = 60 * 60_000;
|
|
372
|
+
/** This research waits for the app's tree with this mark (the link's app=…). */
|
|
373
|
+
export function awaitAdoption(root, token) {
|
|
374
|
+
fs.mkdirSync(path.join(root, ".strom"), { recursive: true });
|
|
375
|
+
fs.writeFileSync(path.join(root, ADOPT_FILE), JSON.stringify({ token, at: new Date().toISOString() }));
|
|
376
|
+
}
|
|
377
|
+
/** The mark of the tree this research waits for — none when it waits for none, got it already, or waited too long. */
|
|
378
|
+
export function pendingAdoption(root) {
|
|
379
|
+
try {
|
|
380
|
+
const a = JSON.parse(fs.readFileSync(path.join(root, ADOPT_FILE), "utf8"));
|
|
381
|
+
return a.token && !a.done && Date.now() - Date.parse(a.at ?? "") < ADOPT_FOR_MS ? a.token : undefined;
|
|
382
|
+
}
|
|
383
|
+
catch {
|
|
384
|
+
return undefined;
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
/**
|
|
388
|
+
* The app's tree for a new research (POST <bridge>/adopt): a GEDCOM, kept in the inbox for the research in the
|
|
389
|
+
* terminal to take in — nothing of the research changes here. Taken once; the file it was kept as.
|
|
390
|
+
*/
|
|
391
|
+
export function receiveAdopted(root, text) {
|
|
392
|
+
if (!pendingAdoption(root))
|
|
393
|
+
throw new UsageError("this research waits for no tree");
|
|
394
|
+
if (!/^\uFEFF?\s*0\s+HEAD/.test(text))
|
|
395
|
+
throw new UsageError("not a GEDCOM file");
|
|
396
|
+
if (!/^1 INDI\b|^0 @[^@]+@ INDI\b/m.test(text))
|
|
397
|
+
throw new UsageError("empty: no people in it");
|
|
398
|
+
const dir = path.join(root, SYNC_INBOX);
|
|
399
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
400
|
+
const file = path.join(dir, `adopt-${new Date().toISOString().replace(/[:.]/g, "-")}.ged`);
|
|
401
|
+
fs.writeFileSync(file, text);
|
|
402
|
+
const a = JSON.parse(fs.readFileSync(path.join(root, ADOPT_FILE), "utf8"));
|
|
403
|
+
fs.writeFileSync(path.join(root, ADOPT_FILE), JSON.stringify({ ...a, done: new Date().toISOString() }));
|
|
404
|
+
return file;
|
|
405
|
+
}
|
|
406
|
+
/** Why the app's tree was not taken (POST <bridge>/adopt refused): noted for the research in the terminal, which stops waiting. */
|
|
407
|
+
export function noteAdoptFailed(root, why) {
|
|
408
|
+
const dir = path.join(root, SYNC_INBOX);
|
|
409
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
410
|
+
fs.writeFileSync(path.join(dir, `adopt-failed-${Date.now()}.json`), JSON.stringify({ why, at: new Date().toISOString() }));
|
|
411
|
+
}
|
|
412
|
+
/** The app's tree was refused since `since` (ms): why. */
|
|
413
|
+
export function adoptFailedSince(root, since) {
|
|
414
|
+
const dir = path.join(root, SYNC_INBOX);
|
|
415
|
+
if (!fs.existsSync(dir))
|
|
416
|
+
return undefined;
|
|
417
|
+
const last = fs
|
|
418
|
+
.readdirSync(dir)
|
|
419
|
+
.filter((f) => f.startsWith("adopt-failed-"))
|
|
420
|
+
.map((f) => path.join(dir, f))
|
|
421
|
+
.filter((f) => fs.statSync(f).mtimeMs >= since)
|
|
422
|
+
.sort()
|
|
423
|
+
.pop();
|
|
424
|
+
if (!last)
|
|
425
|
+
return undefined;
|
|
426
|
+
try {
|
|
427
|
+
return JSON.parse(fs.readFileSync(last, "utf8")).why;
|
|
428
|
+
}
|
|
429
|
+
catch {
|
|
430
|
+
return "other";
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
/** The tree the app handed over since `since` (ms), if any. */
|
|
434
|
+
export function adoptedSince(root, since) {
|
|
435
|
+
const dir = path.join(root, SYNC_INBOX);
|
|
436
|
+
if (!fs.existsSync(dir))
|
|
437
|
+
return undefined;
|
|
438
|
+
return fs
|
|
439
|
+
.readdirSync(dir)
|
|
440
|
+
.filter((f) => f.startsWith("adopt-") && f.endsWith(".ged"))
|
|
362
441
|
.map((f) => path.join(dir, f))
|
|
363
442
|
.filter((f) => fs.statSync(f).mtimeMs >= since)
|
|
364
443
|
.sort()
|
|
@@ -488,7 +567,7 @@ export function planSync(tree, incoming, edits, opts = {}) {
|
|
|
488
567
|
}
|
|
489
568
|
if (was && !partial)
|
|
490
569
|
for (const b of was)
|
|
491
|
-
if (!incKeys.has(factKey(b)) && !used.has(factKey(b)) && carried.has(kindOf(b.kind)) && ourKeys.has(factKey(b)))
|
|
570
|
+
if (!incKeys.has(factKey(b)) && !used.has(factKey(b)) && carried.has(kindOf(b.kind)) && ourKeys.has(factKey(b)) && !keptOnce(owner, b, inc))
|
|
492
571
|
push({ kind: "fact.gone", action: "report", ...owner, was: b });
|
|
493
572
|
};
|
|
494
573
|
// people
|
|
@@ -559,6 +638,16 @@ export function planSync(tree, incoming, edits, opts = {}) {
|
|
|
559
638
|
return { changes, base: !!base, ...(incoming.head ? { head: incoming.head } : {}), partial, identity };
|
|
560
639
|
}
|
|
561
640
|
/** Does the research's fact say all the file's fact says, and more: the same kind, and each of its date, place, value the same or missing in the file. */
|
|
641
|
+
/**
|
|
642
|
+
* The Strom app keeps a couple's marriages and divorces as the course of one union: two marriages with no divorce
|
|
643
|
+
* between are one marriage to it, the first (found live, 2026-09-27: a marriage from the register and a later
|
|
644
|
+
* record's other date). A further one of these missing while the file has one of that kind is its shape, not an edit.
|
|
645
|
+
*/
|
|
646
|
+
const UNION_KINDS = new Set(["MARR", "DIV"]);
|
|
647
|
+
function keptOnce(owner, gone, file) {
|
|
648
|
+
const kind = kindOf(gone.kind);
|
|
649
|
+
return !!owner.family && UNION_KINDS.has(kind) && file.some((f) => kindOf(f.kind) === kind);
|
|
650
|
+
}
|
|
562
651
|
function knowsMore(ours, file) {
|
|
563
652
|
if (kindOf(ours.kind) !== kindOf(file.kind))
|
|
564
653
|
return false;
|
|
@@ -658,6 +747,7 @@ export function applySync(tree, plan, incoming, source, only) {
|
|
|
658
747
|
const title = `${(c.name ?? owner).replace(/\//g, "").replace(/\s+/g, " ").trim()}: ${c.fact.kind} — ${describe(mine)} × ${describe(c.fact)}`;
|
|
659
748
|
const x = create(tree, "conflict", {
|
|
660
749
|
title: title.slice(0, 200),
|
|
750
|
+
fact: c.fact.kind,
|
|
661
751
|
subject: [owner],
|
|
662
752
|
claims: [
|
|
663
753
|
{ ...(mine.citations[0] ? { source: mine.citations[0].source } : {}), value: describe(mine), note: `the research: ${mine.id}` },
|
package/dist/core/tree.js
CHANGED
|
@@ -23,7 +23,7 @@ import * as git from "./git.js";
|
|
|
23
23
|
import { commitSeal, createKey, fingerprint, loadKey, sha256, signOp } from "./seal.js";
|
|
24
24
|
import { stringifyCanonical } from "./json.js";
|
|
25
25
|
export const TREE_FILE = "strom.json";
|
|
26
|
-
export const VERSION = "1.
|
|
26
|
+
export const VERSION = "1.6.0";
|
|
27
27
|
const GITIGNORE = `# Strom Research — local state and large files are not versioned
|
|
28
28
|
.strom/
|
|
29
29
|
/media/
|
package/dist/core/uninstall.js
CHANGED
|
@@ -12,6 +12,7 @@ import { shortcutCmdDir } from "./shortcut.js";
|
|
|
12
12
|
import { globalTargets, isInstalled, uninstallGlobal } from "../agents/global.js";
|
|
13
13
|
import { PROFILES } from "../agents/profiles.js";
|
|
14
14
|
import { installation } from "./self.js";
|
|
15
|
+
import { linkFiles, unregisterLinks } from "./links.js";
|
|
15
16
|
/** The mark the installer (install/install.sh) puts on the PATH line it adds. */
|
|
16
17
|
const MARK = "# strom research";
|
|
17
18
|
function exists(p) {
|
|
@@ -100,6 +101,8 @@ export function uninstallPlan(env, names, opts = {}) {
|
|
|
100
101
|
remove.push({ kind: "agent", agent: PROFILES[t.agent]?.name ?? t.agent, path: t.file, remove: () => uninstallGlobal(t) });
|
|
101
102
|
for (const f of shortcuts(env, platform, names))
|
|
102
103
|
remove.push({ kind: "shortcut", path: f, remove: () => (fs.rmSync(f, { force: true }), true) });
|
|
104
|
+
for (const f of linkFiles(env, platform))
|
|
105
|
+
remove.push({ kind: "links", path: f, remove: () => unregisterLinks(env, platform) });
|
|
103
106
|
const inst = opts.install ?? installation();
|
|
104
107
|
const git = ownGitDir(env);
|
|
105
108
|
// strom's own git lives in the installation's folder on Windows; alone, it goes by itself.
|
package/dist/gedcom/export.js
CHANGED
|
@@ -123,6 +123,18 @@ export function exportGedcom(tree, opts = {}) {
|
|
|
123
123
|
if (pl.coords)
|
|
124
124
|
for (const n of pl.names)
|
|
125
125
|
coords.set(foldText(n.name), pl.coords);
|
|
126
|
+
// What the research knows beyond the facts (the Strom profile, opts.research): read once.
|
|
127
|
+
const research = {
|
|
128
|
+
conflicts: opts.research ? tree.list("conflict") : [],
|
|
129
|
+
hypotheses: opts.research ? tree.list("hypothesis").filter((h) => h.state === "open") : [],
|
|
130
|
+
// a search is of the people of the task it served
|
|
131
|
+
searched: opts.research
|
|
132
|
+
? tree
|
|
133
|
+
.list("search")
|
|
134
|
+
.map((q) => ({ search: q, people: (q.task ? tree.get(q.task)?.subject : undefined) ?? [] }))
|
|
135
|
+
.filter((x) => x.people.length)
|
|
136
|
+
: [],
|
|
137
|
+
};
|
|
126
138
|
// ── header ──
|
|
127
139
|
w.line(0, "HEAD");
|
|
128
140
|
w.line(1, "SOUR", "STROM_RESEARCH");
|
|
@@ -136,6 +148,10 @@ export function exportGedcom(tree, opts = {}) {
|
|
|
136
148
|
const head = opts.head ?? git.head(tree.root);
|
|
137
149
|
if (head)
|
|
138
150
|
w.line(1, "_STROM_HEAD", head);
|
|
151
|
+
if (opts.links?.length)
|
|
152
|
+
w.line(1, "_STROM_LINKS", opts.links.join(" "));
|
|
153
|
+
if (opts.research)
|
|
154
|
+
w.line(1, "_STROM_ASOF", new Date().toISOString().slice(0, 10));
|
|
139
155
|
}
|
|
140
156
|
w.line(1, "SUBM", "@U1@");
|
|
141
157
|
w.line(1, "GEDC");
|
|
@@ -185,6 +201,8 @@ export function exportGedcom(tree, opts = {}) {
|
|
|
185
201
|
for (const d of [...nameQuotes, ...deferred])
|
|
186
202
|
w.text(1, "NOTE", d);
|
|
187
203
|
story(p.story);
|
|
204
|
+
if (opts.research && opts.for === "strom")
|
|
205
|
+
known(p);
|
|
188
206
|
for (const link of famc.get(p.id) ?? []) {
|
|
189
207
|
w.line(1, "FAMC", x(link.fam));
|
|
190
208
|
if (link.relation === "adopted" || link.relation === "foster")
|
|
@@ -297,6 +315,8 @@ export function exportGedcom(tree, opts = {}) {
|
|
|
297
315
|
w.line(1, "OBJE");
|
|
298
316
|
w.line(2, "FORM", "jpg");
|
|
299
317
|
w.line(2, "_STROM_KIND", "excerpt");
|
|
318
|
+
if (opts.clips && e.clip)
|
|
319
|
+
w.line(2, "_STROM_CLIP", e.clip);
|
|
300
320
|
if (e.url)
|
|
301
321
|
w.text(2, "_URL", e.url);
|
|
302
322
|
w.wrapped(2, "FILE", dataUrl(e));
|
|
@@ -417,6 +437,40 @@ export function exportGedcom(tree, opts = {}) {
|
|
|
417
437
|
* The story of a person or a couple: _STORY as Strom reads it (the facts it
|
|
418
438
|
* rests on as DATA lines, the caveat as NOTE); a note in strict GEDCOM.
|
|
419
439
|
*/
|
|
440
|
+
/** What the research knows of a person beyond the facts: its conflicts, open hypotheses, what was searched for them. */
|
|
441
|
+
function known(p) {
|
|
442
|
+
const about = (subject) => subject.includes(p.id) || subject.some((id) => families.some((f) => f.id === id && f.partners.includes(p.id)));
|
|
443
|
+
for (const c of research.conflicts.filter((x) => about(x.subject))) {
|
|
444
|
+
w.line(1, "_STROM_CONFLICT", c.id);
|
|
445
|
+
// the fact it is about: its tag, else a conflict of the research named by its title
|
|
446
|
+
w.line(2, "TYPE", c.fact ?? "EVEN");
|
|
447
|
+
w.text(2, "TITL", c.title);
|
|
448
|
+
w.line(2, "STAT", c.state === "resolved" ? "decided" : "open");
|
|
449
|
+
for (const claim of c.claims) {
|
|
450
|
+
w.text(2, "VAL", claim.value);
|
|
451
|
+
if (claim.source && sourceById.has(claim.source)) {
|
|
452
|
+
citedSources.add(claim.source);
|
|
453
|
+
w.line(3, "SOUR", x(claim.source));
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
if (c.state === "resolved" && c.resolution)
|
|
457
|
+
w.text(2, "DECI", c.resolution);
|
|
458
|
+
}
|
|
459
|
+
for (const h of research.hypotheses.filter((x) => about(x.subject))) {
|
|
460
|
+
w.line(1, "_STROM_HYPO");
|
|
461
|
+
w.text(2, "TITL", h.question);
|
|
462
|
+
w.text(2, "NOTE", h.variants.map((v) => `${v.label}: ${v.claim}`).join("\n"));
|
|
463
|
+
}
|
|
464
|
+
for (const q of research.searched.filter((x) => x.people.includes(p.id)).map((x) => x.search)) {
|
|
465
|
+
w.line(1, "_STROM_SEARCHED");
|
|
466
|
+
w.text(2, "TITL", q.question);
|
|
467
|
+
const years = /^(\d{3,4})(?:\s*[-–]\s*(\d{3,4}))?$/.exec(q.scope.years ?? "");
|
|
468
|
+
if (years)
|
|
469
|
+
w.line(2, "DATE", years[2] && years[2] !== years[1] ? `FROM ${years[1]} TO ${years[2]}` : years[1]);
|
|
470
|
+
w.line(2, "RESN", q.result === "found" ? "found" : "none");
|
|
471
|
+
w.line(2, "_AT", q.created.slice(0, 10));
|
|
472
|
+
}
|
|
473
|
+
}
|
|
420
474
|
function story(st) {
|
|
421
475
|
if (!st)
|
|
422
476
|
return;
|
package/dist/gedcom/validate.js
CHANGED
|
@@ -14,11 +14,15 @@ const INDI_EVENTS = [
|
|
|
14
14
|
const FAM_EVENTS = ["MARR", "DIV", "MARB", "MARC", "MARL", "MARS", "ANUL", "DIVF", "ENGA", "CENS", "EVEN", "NCHI"];
|
|
15
15
|
/** Allowed child tags by parent context ("INDI", "INDI.BIRT", …). CONC/CONT are allowed under anything with text. */
|
|
16
16
|
const CHILDREN = {
|
|
17
|
-
HEAD: ["SOUR", "DEST", "DATE", "SUBM", "SUBN", "FILE", "COPR", "GEDC", "CHAR", "LANG", "PLAC", "NOTE", "_STROM_TREE", "_STROM_HEAD"],
|
|
17
|
+
HEAD: ["SOUR", "DEST", "DATE", "SUBM", "SUBN", "FILE", "COPR", "GEDC", "CHAR", "LANG", "PLAC", "NOTE", "_STROM_TREE", "_STROM_HEAD", "_STROM_LINKS", "_STROM_ASOF"],
|
|
18
18
|
"HEAD.SOUR": ["VERS", "NAME", "CORP", "DATA"],
|
|
19
19
|
"HEAD.GEDC": ["VERS", "FORM"],
|
|
20
20
|
"HEAD.DATE": ["TIME"],
|
|
21
|
-
INDI: ["NAME", "SEX", "FAMC", "FAMS", "NOTE", "SOUR", "REFN", "ASSO", "ALIA", "OBJE", "RESN", "RIN", "CHAN", "_STORY", ...INDI_EVENTS],
|
|
21
|
+
INDI: ["NAME", "SEX", "FAMC", "FAMS", "NOTE", "SOUR", "REFN", "ASSO", "ALIA", "OBJE", "RESN", "RIN", "CHAN", "_STORY", "_STROM_CONFLICT", "_STROM_HYPO", "_STROM_SEARCHED", ...INDI_EVENTS],
|
|
22
|
+
"INDI._STROM_CONFLICT": ["TYPE", "TITL", "STAT", "VAL", "DECI"],
|
|
23
|
+
"INDI._STROM_HYPO": ["TITL", "NOTE"],
|
|
24
|
+
"INDI._STROM_SEARCHED": ["TITL", "DATE", "RESN", "_AT"],
|
|
25
|
+
_STROM_VALUE: ["SOUR"],
|
|
22
26
|
"INDI.NAME": ["TYPE", "GIVN", "SURN", "NPFX", "NSFX", "NICK", "SPFX", "SOUR", "NOTE"],
|
|
23
27
|
"INDI.FAMC": ["PEDI", "NOTE"],
|
|
24
28
|
"INDI.FAMS": ["NOTE"],
|
|
@@ -42,8 +46,11 @@ const CHILDREN = {
|
|
|
42
46
|
_WITN: ["RELA", "NOTE"],
|
|
43
47
|
ADDR: ["CONT", "ADR1", "ADR2", "ADR3", "CITY", "STAE", "POST", "CTRY"],
|
|
44
48
|
};
|
|
45
|
-
const TEXT_TAGS = new Set(["NOTE", "TEXT", "TITL", "PAGE", "AUTH", "PUBL", "_STORY", "DATA", "COPR", "ADDR", "CAUS", "FILE", "_URL"]);
|
|
46
|
-
const EXTENSIONS = new Set([
|
|
49
|
+
const TEXT_TAGS = new Set(["NOTE", "TEXT", "TITL", "PAGE", "AUTH", "PUBL", "_STORY", "DATA", "COPR", "ADDR", "CAUS", "FILE", "_URL", "VAL", "DECI"]);
|
|
50
|
+
const EXTENSIONS = new Set([
|
|
51
|
+
"_STORY", "_WITN", "_FREL", "_MREL", "_STROM_KIND", "_URL", "_STROM_TREE", "_STROM_HEAD", "_STROM_LINKS", "_STROM_CLIP",
|
|
52
|
+
"_STROM_ASOF", "_STROM_CONFLICT", "_STROM_HYPO", "_STROM_SEARCHED", "_AT",
|
|
53
|
+
]);
|
|
47
54
|
/** Context of the children of the last tag in `anc` (the ancestors of a line). */
|
|
48
55
|
function contextOf(anc) {
|
|
49
56
|
const record = anc[0];
|
|
@@ -74,6 +81,8 @@ function contextOf(anc) {
|
|
|
74
81
|
}
|
|
75
82
|
if (depth === 3 && parent === "ASSO")
|
|
76
83
|
return "EVENT.ASSO";
|
|
84
|
+
if (depth === 3 && (parent === "VAL" || parent === "DECI") && anc[1] === "_STROM_CONFLICT")
|
|
85
|
+
return "_STROM_VALUE";
|
|
77
86
|
if (depth === 3 && (parent === "HUSB" || parent === "WIFE") && record === "FAM")
|
|
78
87
|
return `EVENT.${parent}`;
|
|
79
88
|
return undefined;
|