strom-research 1.22.0 → 1.23.0-beta.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/CHANGELOG.cs.md +21 -0
- package/CHANGELOG.de.md +24 -0
- package/CHANGELOG.md +21 -0
- package/assets/lang/cs.json +8 -0
- package/assets/lang/de.json +8 -0
- package/dist/brief/brief.js +1 -1
- package/dist/brief/history.js +6 -0
- package/dist/cli/ui.js +6 -0
- package/dist/commands/history.js +8 -2
- package/dist/commands/people.js +35 -6
- package/dist/commands/sync.js +28 -26
- package/dist/core/actions.js +61 -10
- package/dist/core/changelog.js +5 -0
- package/dist/core/import.js +10 -4
- package/dist/core/live.js +1 -1
- package/dist/core/overview.js +3 -2
- package/dist/core/people.js +14 -1
- package/dist/core/phrases.js +2 -0
- package/dist/core/placehistory.js +33 -0
- package/dist/core/retractions.js +6 -4
- package/dist/core/stromapp.js +21 -0
- package/dist/core/sync.js +110 -8
- package/dist/core/tree.js +1 -1
- package/dist/core/validate.js +4 -0
- package/dist/gedcom/export.js +14 -2
- package/dist/gedcom/validate.js +13 -5
- package/package.json +4 -1
package/CHANGELOG.cs.md
CHANGED
|
@@ -5,6 +5,27 @@
|
|
|
5
5
|
Změny každé verze ve zkratce. Úplné poznámky a instalační řádky jsou na
|
|
6
6
|
[GitHub Releases](https://github.com/ACiDekCZ/strom-research/releases) (anglicky).
|
|
7
7
|
|
|
8
|
+
## 1.23.0
|
|
9
|
+
|
|
10
|
+
*Připravuje se: bety do 1.23.0-beta.1. Oddíl bude úplný s vydáním.*
|
|
11
|
+
|
|
12
|
+
- **Osoba ukázaná zvoleným jménem.** Žena narozená pod jedním jménem, provdaná pod druhým a po rozvodu zase u prvního
|
|
13
|
+
se dá ukazovat tímto jménem znovu, aniž by se přidávalo podruhé: `strom name primary <osoba> "<jméno>"` (a
|
|
14
|
+
`--clear`), nebo `strom name add --primary`. Volba patří jen uživateli, nikdy se nečte ze záznamu; opravy, citace
|
|
15
|
+
i synchronizace s aplikací Strom se dál řídí rodným jménem. Soubor pro Strom zvolené jméno označí, standardní soubor
|
|
16
|
+
GEDCOM ho dá na první místo.
|
|
17
|
+
- **Soubor pro aplikaci Strom bez varování.** Export pro aplikaci Strom varoval před nestandardní značkou u každé
|
|
18
|
+
osoby se zvoleným jménem a u několika značek zdrojů; kontrola teď zná všechny vlastní značky Stromu.
|
|
19
|
+
|
|
20
|
+
## 1.22.1
|
|
21
|
+
|
|
22
|
+
*29. 10. 2026 · patří k aplikaci Strom 3.17.0.*
|
|
23
|
+
|
|
24
|
+
- **Událost místa se u regionu nezapíše znovu.** Když se dějiny místa hledaly dřív než dějiny regionů, do kterých
|
|
25
|
+
patří, mohly dějiny regionu přidat tutéž událost ještě jednou (stejný druh, stejné roky). Region teď vynechá, co už
|
|
26
|
+
má některé z jeho míst; událost, která se týkala celého regionu, se zapíše dál. To, co agent k místu čte, říká
|
|
27
|
+
i to, které z jeho regionů se ještě neprošly.
|
|
28
|
+
|
|
8
29
|
## 1.22.0
|
|
9
30
|
|
|
10
31
|
*29. 10. 2026 · patří k aplikaci Strom 3.17.0.*
|
package/CHANGELOG.de.md
CHANGED
|
@@ -5,6 +5,30 @@
|
|
|
5
5
|
Die Änderungen jeder Version in Kürze. Die vollständigen Hinweise und die Installationszeilen stehen bei
|
|
6
6
|
[GitHub Releases](https://github.com/ACiDekCZ/strom-research/releases) (auf Englisch).
|
|
7
7
|
|
|
8
|
+
## 1.23.0
|
|
9
|
+
|
|
10
|
+
*In Vorbereitung: die Betas bis 1.23.0-beta.1. Der Abschnitt ist mit dem Release vollständig.*
|
|
11
|
+
|
|
12
|
+
- **Eine Person unter dem gewählten Namen.** Eine Frau, unter einem Namen geboren, unter einem anderen verheiratet und
|
|
13
|
+
nach einer Scheidung wieder beim ersten, lässt sich wieder unter diesem Namen zeigen, ohne dass er ein zweites Mal
|
|
14
|
+
hinzukommt: `strom name primary <Person> "<Name>"` (und `--clear`) oder `strom name add --primary`. Die Wahl trifft
|
|
15
|
+
allein der Nutzer, nie wird sie aus einem Eintrag gelesen; Korrekturen, Zitate und der Abgleich mit der Strom-App
|
|
16
|
+
richten sich weiter nach dem Geburtsnamen. Die Strom-Datei markiert den gewählten Namen, die Standard-GEDCOM-Datei
|
|
17
|
+
setzt ihn an die erste Stelle.
|
|
18
|
+
- **Die Datei für die Strom-App ohne Warnungen.** Der Export für die Strom-App warnte bei jeder Person mit gewähltem
|
|
19
|
+
Namen und bei mehreren Kennzeichen von Quellen vor einem nicht standardisierten Tag; die Prüfung kennt jetzt alle
|
|
20
|
+
eigenen Tags von Strom.
|
|
21
|
+
|
|
22
|
+
## 1.22.1
|
|
23
|
+
|
|
24
|
+
*29.10.2026 · gehört zur Strom-App 3.17.0.*
|
|
25
|
+
|
|
26
|
+
- **Ein Ereignis eines Ortes wird bei seiner Region nicht noch einmal geschrieben.** Wurde die Geschichte eines Ortes
|
|
27
|
+
vor den Regionen gesucht, zu denen er gehört, konnte die Geschichte der Region dasselbe Ereignis noch einmal
|
|
28
|
+
hinzufügen (dieselbe Art, dieselben Jahre). Die Region lässt jetzt aus, was einer ihrer Orte schon hat; ein
|
|
29
|
+
Ereignis, das die ganze Region betraf, wird weiter geschrieben. Was der Agent zu einem Ort liest, sagt auch, welche
|
|
30
|
+
seiner Regionen noch nicht durchgesehen sind.
|
|
31
|
+
|
|
8
32
|
## 1.22.0
|
|
9
33
|
|
|
10
34
|
*29.10.2026 · gehört zur Strom-App 3.17.0.*
|
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,27 @@ English · [Čeština](CHANGELOG.cs.md) · [Deutsch](CHANGELOG.de.md)
|
|
|
5
5
|
The changes of each version in short. The full notes and the install lines are on
|
|
6
6
|
[GitHub Releases](https://github.com/ACiDekCZ/strom-research/releases).
|
|
7
7
|
|
|
8
|
+
## 1.23.0
|
|
9
|
+
|
|
10
|
+
*In preparation: the betas up to 1.23.0-beta.1. The section is complete with the release.*
|
|
11
|
+
|
|
12
|
+
- **A person shown by the name chosen.** A woman born under one name, married under another and back at the first
|
|
13
|
+
after a divorce can be shown by that name again without it being added twice: `strom name primary <person>
|
|
14
|
+
"<name>"` (and `--clear`), or `strom name add --primary`. The choice is the user's alone, never read from a record;
|
|
15
|
+
corrections, citations and the sync with the Strom app still go by the birth name. The Strom file marks the chosen
|
|
16
|
+
name, the standard GEDCOM file gives it first.
|
|
17
|
+
- **The file for the Strom app without warnings.** Exporting for the Strom app warned of a non-standard tag on each
|
|
18
|
+
person with a chosen name and on several marks of sources; the check now knows every tag of Strom's own.
|
|
19
|
+
|
|
20
|
+
## 1.22.1
|
|
21
|
+
|
|
22
|
+
*2026-10-29 · goes with the Strom app 3.17.0.*
|
|
23
|
+
|
|
24
|
+
- **An event of a place is not written again for its region.** When the history of a place was looked for before the
|
|
25
|
+
regions it lies in, the region's history could add the same event once more (the same kind, the same years). The
|
|
26
|
+
region now leaves out what one of its places has already; an event that touched the whole region is still written.
|
|
27
|
+
What the agent reads for a place also says which of its regions are not looked through yet.
|
|
28
|
+
|
|
8
29
|
## 1.22.0
|
|
9
30
|
|
|
10
31
|
*2026-10-29 · goes with the Strom app 3.17.0.*
|
package/assets/lang/cs.json
CHANGED
|
@@ -1060,6 +1060,7 @@
|
|
|
1060
1060
|
"ui.show.name.married": "po sňatku",
|
|
1061
1061
|
"ui.show.name.religious": "řeholní",
|
|
1062
1062
|
"ui.show.name.alias": "jinak též",
|
|
1063
|
+
"ui.show.name.current": "hlavní",
|
|
1063
1064
|
"ui.show.role.godparent": "kmotr",
|
|
1064
1065
|
"ui.show.role.witness": "svědek",
|
|
1065
1066
|
"ui.show.role.officiant": "duchovní",
|
|
@@ -1348,7 +1349,10 @@
|
|
|
1348
1349
|
"ui.sync.do.name.gone": "odebere se (měly ho jen úpravy v aplikaci nebo jiné rodokmeny)",
|
|
1349
1350
|
"ui.sync.do.name.kept": "zůstává: dokládá ho záznam",
|
|
1350
1351
|
"ui.sync.do.name.user": "platí úprava z aplikace: osoba se ukazuje pod novým jménem, jméno ze záznamu zůstane vedle něj",
|
|
1352
|
+
"ui.sync.do.name.shown": "osoba se ukazuje tímto jménem",
|
|
1353
|
+
"ui.sync.do.name.shown.conflict": "rozpor k rozhodnutí (výzkum ho změnil také)",
|
|
1351
1354
|
"ui.sync.name.title": "{n}. {who}: {title} — {old} → {new}",
|
|
1355
|
+
"ui.sync.name.shown": "{n}. {who}: hlavní jméno — {old} → {new}",
|
|
1352
1356
|
"ui.sync.do.title": "doplní se ke jménu s odkazem na strom z aplikace",
|
|
1353
1357
|
"ui.sync.do.title.user": "platí úprava z aplikace (verze výzkumu zůstane v historii)",
|
|
1354
1358
|
"ui.sync.sex.changed": "{n}. {who}: pohlaví {text}",
|
|
@@ -2242,6 +2246,8 @@
|
|
|
2242
2246
|
"log.note.edit": "Opravená poznámka: {name}",
|
|
2243
2247
|
"log.name.retract": "Odvolané jméno: {name} – {who}",
|
|
2244
2248
|
"log.name.restore": "Vrácené jméno: {name} – {who}",
|
|
2249
|
+
"log.name.primary": "Hlavní jméno: {name} – {who}",
|
|
2250
|
+
"log.name.primary.clear": "Hlavní jméno zase rodné: {name} – {who}",
|
|
2245
2251
|
"log.note.retract": "Odvolaná poznámka: {name}",
|
|
2246
2252
|
"log.note.restore": "Vrácená poznámka: {name}",
|
|
2247
2253
|
"log.event.status": "Změněná jistota údaje: {who} – {fact}",
|
|
@@ -2507,6 +2513,8 @@
|
|
|
2507
2513
|
"ui.conflict.sex": "pohlaví",
|
|
2508
2514
|
"ui.conflict.sex.U": "neznámé",
|
|
2509
2515
|
"ui.conflict.name": "jméno",
|
|
2516
|
+
"ui.conflict.primary": "hlavní jméno",
|
|
2517
|
+
"ui.conflict.primary.none": "nezvoleno",
|
|
2510
2518
|
"ui.conflict.titleBefore": "titul před jménem",
|
|
2511
2519
|
"ui.conflict.titleAfter": "titul za jménem",
|
|
2512
2520
|
"ui.conflict.parents": "rodiče",
|
package/assets/lang/de.json
CHANGED
|
@@ -1060,6 +1060,7 @@
|
|
|
1060
1060
|
"ui.show.name.married": "nach der Heirat",
|
|
1061
1061
|
"ui.show.name.religious": "Ordensname",
|
|
1062
1062
|
"ui.show.name.alias": "auch genannt",
|
|
1063
|
+
"ui.show.name.current": "Hauptname",
|
|
1063
1064
|
"ui.show.role.godparent": "Pate",
|
|
1064
1065
|
"ui.show.role.witness": "Zeuge",
|
|
1065
1066
|
"ui.show.role.officiant": "Geistlicher",
|
|
@@ -1348,7 +1349,10 @@
|
|
|
1348
1349
|
"ui.sync.do.name.gone": "wird entfernt (nur Änderungen in der App oder andere Stammbäume gaben ihn)",
|
|
1349
1350
|
"ui.sync.do.name.kept": "bleibt: ein Eintrag belegt ihn",
|
|
1350
1351
|
"ui.sync.do.name.user": "Die eigene Änderung gilt: die Person erscheint unter dem neuen Namen, der Name aus dem Eintrag bleibt daneben",
|
|
1352
|
+
"ui.sync.do.name.shown": "die Person wird so angezeigt",
|
|
1353
|
+
"ui.sync.do.name.shown.conflict": "ein Widerspruch zum Entscheiden (auch die Forschung hat es geändert)",
|
|
1351
1354
|
"ui.sync.name.title": "{n}. {who}: {title} — {old} → {new}",
|
|
1355
|
+
"ui.sync.name.shown": "{n}. {who}: Hauptname — {old} → {new}",
|
|
1352
1356
|
"ui.sync.do.title": "wird dem Namen hinzugefügt, mit Verweis auf den eigenen Stammbaum",
|
|
1353
1357
|
"ui.sync.do.title.user": "Die eigene Änderung gilt (die der Forschung bleibt in ihrer Geschichte)",
|
|
1354
1358
|
"ui.sync.sex.changed": "{n}. {who}: Geschlecht {text}",
|
|
@@ -2242,6 +2246,8 @@
|
|
|
2242
2246
|
"log.note.edit": "Notiz berichtigt: {name}",
|
|
2243
2247
|
"log.name.retract": "Name zurückgezogen: {name} – {who}",
|
|
2244
2248
|
"log.name.restore": "Name wiederhergestellt: {name} – {who}",
|
|
2249
|
+
"log.name.primary": "Hauptname: {name} – {who}",
|
|
2250
|
+
"log.name.primary.clear": "Hauptname wieder der Geburtsname: {name} – {who}",
|
|
2245
2251
|
"log.note.retract": "Notiz zurückgezogen: {name}",
|
|
2246
2252
|
"log.note.restore": "Notiz wiederhergestellt: {name}",
|
|
2247
2253
|
"log.event.status": "Sicherheit einer Angabe geändert: {who} – {fact}",
|
|
@@ -2507,6 +2513,8 @@
|
|
|
2507
2513
|
"ui.conflict.sex": "Geschlecht",
|
|
2508
2514
|
"ui.conflict.sex.U": "unbekannt",
|
|
2509
2515
|
"ui.conflict.name": "Name",
|
|
2516
|
+
"ui.conflict.primary": "Hauptname",
|
|
2517
|
+
"ui.conflict.primary.none": "nicht gewählt",
|
|
2510
2518
|
"ui.conflict.titleBefore": "Titel vor dem Namen",
|
|
2511
2519
|
"ui.conflict.titleAfter": "Titel nach dem Namen",
|
|
2512
2520
|
"ui.conflict.parents": "Eltern",
|
package/dist/brief/brief.js
CHANGED
|
@@ -116,7 +116,7 @@ function personBlock(tree, p, depth, short = false) {
|
|
|
116
116
|
const out = [` ${label(p)} ${p.sex}`];
|
|
117
117
|
// other names, and where any name comes from
|
|
118
118
|
if (p.names.length > 1 || p.names.some((n) => n.citations?.length))
|
|
119
|
-
out.push(` names: ${p.names.map((n) => `${formatName(n)}${n.kind ? ` (${n.kind})` : ""}${n.citations?.length ? ` ← ${citesOf(n.citations)}` : ""}`).join("; ")}`);
|
|
119
|
+
out.push(` names: ${p.names.map((n) => `${formatName(n)}${n.kind ? ` (${n.kind})` : ""}${n.primary ? " (shown by it)" : ""}${n.citations?.length ? ` ← ${citesOf(n.citations)}` : ""}`).join("; ")}`);
|
|
120
120
|
for (const e of p.events.filter((x) => !x.retracted))
|
|
121
121
|
out.push(eventLine(e));
|
|
122
122
|
const parents = parentsOf(tree, p.id);
|
package/dist/brief/history.js
CHANGED
|
@@ -200,6 +200,9 @@ function placeSections(tree, id, shared, alone) {
|
|
|
200
200
|
const events = c?.events ?? [];
|
|
201
201
|
const live = events.filter((e) => !e.retracted);
|
|
202
202
|
const theirs = regionEventsAt(tree, shared, p.id, at);
|
|
203
|
+
// its regions not looked through yet (K7.1a): what touched the whole region is theirs, written when they are
|
|
204
|
+
const maps = readPlaces(tree);
|
|
205
|
+
const waiting = [...new Set((at?.keys ?? []).flatMap((k) => maps[k]?.regions ?? []).map((g) => g.id))].filter((g) => !shared || !readCatalog(shared, g)?.done);
|
|
203
206
|
const out = [];
|
|
204
207
|
out.push({
|
|
205
208
|
name: "history",
|
|
@@ -237,6 +240,9 @@ function placeSections(tree, id, shared, alone) {
|
|
|
237
240
|
: [" none yet"]),
|
|
238
241
|
theirs.length ? `The regions' events at this place in its years (${theirs.length}) — the Strom app shows them here already, never written for the place (refused):` : "No region's event at this place yet.",
|
|
239
242
|
...theirs.slice(0, EVENTS_SHOWN).map(({ event: e, region }) => ` ${e.id} ${years(e.from, e.to)} ${e.kind} (${region.region.id}) "${short(e.text[lang] ?? e.text.en ?? Object.values(e.text)[0] ?? "")}"`),
|
|
243
|
+
waiting.length
|
|
244
|
+
? `Regions of this place not looked through yet (${waiting.join(", ")}): what touched the whole region (a war of the land, its epidemic, a reform of the state) is theirs, written when they are — here only what happened at the place or near enough to touch its people`
|
|
245
|
+
: "",
|
|
240
246
|
c?.tried.length ? `Pages strom could not read (never tried again): ${c.tried.slice(-20).map((t) => `${t.url} (${short(t.why, 40)})`).join(" · ")}` : "",
|
|
241
247
|
].filter(Boolean).join("\n"),
|
|
242
248
|
});
|
package/dist/cli/ui.js
CHANGED
|
@@ -884,6 +884,7 @@ export const UI = {
|
|
|
884
884
|
"ui.show.name.married": "married",
|
|
885
885
|
"ui.show.name.religious": "religious",
|
|
886
886
|
"ui.show.name.alias": "alias",
|
|
887
|
+
"ui.show.name.current": "current",
|
|
887
888
|
"ui.show.role.godparent": "godparent",
|
|
888
889
|
"ui.show.role.witness": "witness",
|
|
889
890
|
"ui.show.role.officiant": "officiant",
|
|
@@ -1313,7 +1314,10 @@ export const UI = {
|
|
|
1313
1314
|
"ui.sync.do.name.gone": "taken off (only edits in the app or other family trees gave it)",
|
|
1314
1315
|
"ui.sync.do.name.kept": "kept: a record gives it",
|
|
1315
1316
|
"ui.sync.do.name.user": "the edit wins: shown by the new name, the record's kept beside it",
|
|
1317
|
+
"ui.sync.do.name.shown": "the person shown by it",
|
|
1318
|
+
"ui.sync.do.name.shown.conflict": "a conflict to decide (the research changed it too)",
|
|
1316
1319
|
"ui.sync.name.title": "{n}. {who}: {title} — {old} → {new}",
|
|
1320
|
+
"ui.sync.name.shown": "{n}. {who}: name shown — {old} → {new}",
|
|
1317
1321
|
"ui.sync.do.title": "added to the name, citing the tree sent",
|
|
1318
1322
|
"ui.sync.do.title.user": "the edit wins (the research's in its history)",
|
|
1319
1323
|
"ui.sync.sex.changed": "{n}. {who}: sex {text}",
|
|
@@ -2432,6 +2436,8 @@ export const UI = {
|
|
|
2432
2436
|
"ui.conflict.sex": "sex",
|
|
2433
2437
|
"ui.conflict.sex.U": "not known",
|
|
2434
2438
|
"ui.conflict.name": "name",
|
|
2439
|
+
"ui.conflict.primary": "name shown",
|
|
2440
|
+
"ui.conflict.primary.none": "none chosen",
|
|
2435
2441
|
"ui.conflict.titleBefore": "title before the name",
|
|
2436
2442
|
"ui.conflict.titleAfter": "title after the name",
|
|
2437
2443
|
"ui.conflict.parents": "parents",
|
package/dist/commands/history.js
CHANGED
|
@@ -15,7 +15,7 @@ import { resolvePerson } from "../core/people.js";
|
|
|
15
15
|
import { checkQuotes, cleanText, findPassages, mainQuote, pageParts, PageError, quotesShape, readPage, PART_CHARS } from "../core/pagecheck.js";
|
|
16
16
|
import { currentSession } from "../core/session.js";
|
|
17
17
|
import { addLocalEvent, addRegionEvent, askFirst, assignPlace, checkKind, checkTexts, checkYears, deleteEvent, ensureRegion, factPlaceKey, hideEvent, historyAnswer, historyStatus, forGaps, forHistory, gapsOfTask, gapText, historyOrder, historyHeld, historyTurn, mapPlace, raiseHistoryTasks, markDone, donePeriod, noteTried, readCatalog, redoRegion, refuseBeforePage, REGION_LANGS, regionIdOk, regionOfTask, requireHistoryOn, requireRegionId, retractEvent, skipHistory, EVENT_KINDS, yearsToSearch, whoSees, readPlaces, inRegion, regionName, placeRecordOf, } from "../core/pastevents.js";
|
|
18
|
-
import { addPlaceEvent, askPlace, refusePlaceAsk, ensurePlaceCatalog, markPlaceDone, factsByRecord, notePlaceTried, placeEstimate, placeKeyOf, placeLabel, placeOfTask, readPlaceCatalog, refusePlaceBeforePage, requirePlace, setArea, yearsAt, } from "../core/placehistory.js";
|
|
18
|
+
import { addPlaceEvent, askPlace, refusePlaceAsk, ensurePlaceCatalog, markPlaceDone, factsByRecord, notePlaceTried, placeEstimate, placeKeyOf, placeLabel, placeOfTask, readPlaceCatalog, refusePlaceBeforePage, refusePlaceSame, requirePlace, setArea, yearsAt, } from "../core/placehistory.js";
|
|
19
19
|
// strom history list
|
|
20
20
|
import { moreLine, paginate } from "../cli/format.js";
|
|
21
21
|
import { displayName } from "../core/people.js";
|
|
@@ -336,8 +336,11 @@ register({
|
|
|
336
336
|
}
|
|
337
337
|
if (place)
|
|
338
338
|
refusePlaceBeforePage(tree, sh, place, { kind, from, to }, s, url);
|
|
339
|
-
else
|
|
339
|
+
else {
|
|
340
340
|
refuseBeforePage(sh, region, { kind, from, to }, s, url);
|
|
341
|
+
if (!s.sameAs && !s.distinct)
|
|
342
|
+
refusePlaceSame(tree, sh, region, { kind, from, to });
|
|
343
|
+
}
|
|
341
344
|
let checked;
|
|
342
345
|
try {
|
|
343
346
|
checked = await checkQuotes(url, quotes, { from, to }, sh, ctx.env);
|
|
@@ -363,6 +366,9 @@ register({
|
|
|
363
366
|
sha: checked.page.sha,
|
|
364
367
|
};
|
|
365
368
|
const event = { from, to, ...(date ? { date } : {}), kind, text, ...(why ? { why } : {}), ...(tip ? { tip } : {}), ...(opts.sensitive ? { sensitive: true } : {}), source };
|
|
369
|
+
// a place looked for before its region (K7.1a): its own events checked again once the page is read
|
|
370
|
+
if (!place && !s.sameAs && !s.distinct)
|
|
371
|
+
refusePlaceSame(tree, sh, region, { kind, from, to });
|
|
366
372
|
const r = place ? addPlaceEvent(tree, sh, place, event, s) : addRegionEvent(sh, tree.config.id, { region: region, ...event }, s);
|
|
367
373
|
const where = place ? `at ${placeLabel(place, "en")} (${place.id}) and its surroundings` : `in ${region}`;
|
|
368
374
|
// who it shows at (their person in the Strom app, their story): for the agent to say to the person who asked
|
package/dist/commands/people.js
CHANGED
|
@@ -3,13 +3,13 @@ import { register } from "../cli/registry.js";
|
|
|
3
3
|
import { lines, moreLine, paginate, table, truncate } from "../cli/format.js";
|
|
4
4
|
import { suggestionLines, suggestPeople } from "../core/participantlinks.js";
|
|
5
5
|
import { checkStreetPlace } from "../core/streets.js";
|
|
6
|
-
import { addChild, addEvent, addFamily, addName, addNote, addPerson, citeEvent, citeRecord, editEvent, editFamily, editPerson, findEventOwner, mergeFamilies, notRetracted, mergePersons, parseParticipant, retractEvent, retractPerson } from "../core/actions.js";
|
|
6
|
+
import { addChild, addEvent, addFamily, addName, addNote, addPerson, citeEvent, citeRecord, editEvent, editFamily, editPerson, findEventOwner, markPrimaryName, mergeFamilies, notRetracted, mergePersons, parseParticipant, retractEvent, retractPerson } from "../core/actions.js";
|
|
7
7
|
import { listOpt, normId, requireRecord } from "../core/records.js";
|
|
8
8
|
import { SINGLE_KINDS } from "../core/actions.js";
|
|
9
9
|
import { UsageError } from "../core/errors.js";
|
|
10
10
|
import { INFORMATION } from "../core/evidence.js";
|
|
11
11
|
import { EVENT_KINDS, NAME_KINDS } from "../core/model.js";
|
|
12
|
-
import { ancestorGenerations, displayName, familiesAsChild, familiesAsPartner, findPersons, formatName, gedcomName, label, lifespan, parseName, primaryName, relationTo, resolvePerson, sameName, titledName, } from "../core/people.js";
|
|
12
|
+
import { ancestorGenerations, currentName, displayName, familiesAsChild, familiesAsPartner, findPersons, formatName, gedcomName, label, lifespan, parseName, primaryName, relationTo, resolvePerson, sameName, titledName, } from "../core/people.js";
|
|
13
13
|
import { resolveResearch } from "./research.js";
|
|
14
14
|
import { editNote, notesOf, restoreName, restoreNote, retractName, retractNote } from "../core/retractions.js";
|
|
15
15
|
import { DEATH_AFTER_YEARS, unprovenPeople } from "../core/review.js";
|
|
@@ -126,7 +126,7 @@ function citesOf(citations, w = ENGLISH) {
|
|
|
126
126
|
}
|
|
127
127
|
function nameLine(n, w = ENGLISH) {
|
|
128
128
|
const cites = citesOf(n.citations, w);
|
|
129
|
-
return `${titledName(n)}${n.kind ? ` (${w.word("name", n.kind)})` : ""}${cites ? ` ← ${cites}` : ""}`;
|
|
129
|
+
return `${titledName(n)}${n.kind ? ` (${w.word("name", n.kind)})` : ""}${n.primary ? ` · ${w.word("name", "current")}` : ""}${cites ? ` ← ${cites}` : ""}`;
|
|
130
130
|
}
|
|
131
131
|
function historyOf(p) {
|
|
132
132
|
const out = [];
|
|
@@ -305,7 +305,7 @@ register({
|
|
|
305
305
|
const who = (x) => (shown.add(x), relative(x, w));
|
|
306
306
|
const out = [
|
|
307
307
|
...(removed ? [removed.line, ""] : []),
|
|
308
|
-
`${p.id} ${titledName(
|
|
308
|
+
`${p.id} ${titledName(currentName(p))}${lifespan(p) ? ` (${lifespan(p)})` : ""} ${w.lang ? w.word("sex", p.sex) : `sex ${p.sex}`}`,
|
|
309
309
|
...(p.names.length > 1 || p.names.some((n) => n.citations?.length) ? ["", w.say("ui.show.names", "names"), ...p.names.map((n) => ` ${nameLine(n, w)}`)] : []),
|
|
310
310
|
"",
|
|
311
311
|
w.say("ui.show.facts", "facts"),
|
|
@@ -426,14 +426,15 @@ register({
|
|
|
426
426
|
tree: true,
|
|
427
427
|
writes: true,
|
|
428
428
|
description: "A name without a surname (\"Markéta\") is completed by the full one and the person is shown by it; the same\n" +
|
|
429
|
-
"name again only adds its citation. A woman is shown by her birth name; --kind married keeps the married one aside
|
|
429
|
+
"name again only adds its citation. A woman is shown by her birth name; --kind married keeps the married one aside.\n" +
|
|
430
|
+
"--primary shows the person by this name from now on (strom name primary marks a name they have already).",
|
|
430
431
|
args: [
|
|
431
432
|
{ name: "person", description: "ID (P0001) or name", required: true },
|
|
432
433
|
{ name: "name", description: 'the name, surname in slashes: "Markéta /Růžičková/"', required: true },
|
|
433
434
|
],
|
|
434
435
|
options: [
|
|
435
436
|
{ name: "kind", type: "string", value: "<kind>", description: `${NAME_KINDS.join(", ")} (default: plain)` },
|
|
436
|
-
{ name: "primary", type: "boolean", description: "show the person by this name" },
|
|
437
|
+
{ name: "primary", type: "boolean", description: "show the person by this name now (their identity stays the birth name)" },
|
|
437
438
|
...TITLE_OPTIONS,
|
|
438
439
|
...CITE_OPTIONS.filter((o) => o.name !== "status"),
|
|
439
440
|
],
|
|
@@ -454,6 +455,34 @@ register({
|
|
|
454
455
|
const r = addName(tree, p.id, { name: args[1], kind: opts.kind, citation: citationOf(opts), primary: Boolean(opts.primary), prefix: opts.prefix, suffix: opts.suffix });
|
|
455
456
|
return { text: written(tree), data: { person: r.person, name: r.name } };
|
|
456
457
|
},
|
|
458
|
+
}, {
|
|
459
|
+
path: ["name", "primary"],
|
|
460
|
+
summary: "Show a person by one of their names now — a divorced woman back at her birth surname — without adding it again",
|
|
461
|
+
group: "people",
|
|
462
|
+
tree: true,
|
|
463
|
+
writes: true,
|
|
464
|
+
description: "The user's choice, never told from a record (a divorce does not say it): the name is marked, every other name\n" +
|
|
465
|
+
"stays as it is. person show, lists, the brief, the Strom app show the person by it; their identity — what a\n" +
|
|
466
|
+
"correction (person edit --name), a citation of the person and pairing go by — stays the birth name. --clear takes\n" +
|
|
467
|
+
"the mark off: shown by the birth name again (the Strom app: by the married surname, as before).",
|
|
468
|
+
args: [
|
|
469
|
+
{ name: "person", description: "ID (P0001) or name", required: true },
|
|
470
|
+
{ name: "name", description: 'one of their names, surname in slashes: "Jana /Lhotová/" (none with --clear)' },
|
|
471
|
+
],
|
|
472
|
+
options: [
|
|
473
|
+
{ name: "clear", type: "boolean", description: "take the mark off: no name chosen" },
|
|
474
|
+
],
|
|
475
|
+
examples: ['strom name primary P0002 "Josef /Nowak/"', "strom name primary P0002 --clear"],
|
|
476
|
+
run(ctx, { args, opts }) {
|
|
477
|
+
const tree = ctx.tree();
|
|
478
|
+
const p = resolvePerson(tree, args[0]);
|
|
479
|
+
if (opts.clear && args[1] !== undefined)
|
|
480
|
+
throw new UsageError("a name or --clear, not both", { hint: `strom name primary ${p.id} "<Given /Surname/>" · strom name primary ${p.id} --clear` });
|
|
481
|
+
if (!opts.clear && args[1] === undefined)
|
|
482
|
+
throw new UsageError("which name?", { hint: `its names: ${p.names.map((n) => `"${gedcomName(n)}"`).join(", ")} · strom name primary ${p.id} "<Given /Surname/>" · --clear` });
|
|
483
|
+
const r = markPrimaryName(tree, p.id, opts.clear ? undefined : args[1], opts.reason);
|
|
484
|
+
return { text: written(tree), data: { person: r.person, name: r.name, ...(r.was ? { was: r.was } : {}) } };
|
|
485
|
+
},
|
|
457
486
|
}, {
|
|
458
487
|
path: ["name", "retract"],
|
|
459
488
|
summary: "Take back a name of a person that turned out wrong (kept with the reason, shown nowhere but in the history)",
|
package/dist/commands/sync.js
CHANGED
|
@@ -110,8 +110,8 @@ function changeLine(tree, c, incoming, lang, readings) {
|
|
|
110
110
|
now: c.kind === "fact.detail" ? detailText(c.fact, lang, short) : factText(c.fact, lang, short),
|
|
111
111
|
when: [humanDate(c.fact?.date, lang), humanPlace(c.fact?.place, undefined, lang)].filter(Boolean).join(", "),
|
|
112
112
|
text: truncate(c.text ?? "", 120),
|
|
113
|
-
old: c.note ? truncate(c.note.was, 80) : c.title ? truncate(c.title.was, 80) || "—" : c.wasTitle !== undefined ? truncate(c.wasTitle, 80) : shownName(c.wasName),
|
|
114
|
-
new: c.note ? truncate(c.text ?? "", 80) : c.title || c.wasTitle !== undefined ? truncate(c.text ?? "", 80) || "—" : shownName(c.text),
|
|
113
|
+
old: c.note ? truncate(c.note.was, 80) : c.title ? truncate(c.title.was, 80) || "—" : c.wasTitle !== undefined ? truncate(c.wasTitle, 80) : c.shown ? shownName(c.wasName) || ui(lang, "ui.conflict.primary.none") : shownName(c.wasName),
|
|
114
|
+
new: c.note ? truncate(c.text ?? "", 80) : c.title || c.wasTitle !== undefined ? truncate(c.text ?? "", 80) || "—" : c.shown ? shownName(c.text) || ui(lang, "ui.conflict.primary.none") : shownName(c.text),
|
|
115
115
|
title: c.title ? ui(lang, c.title.part === "before" ? "ui.conflict.titleBefore" : "ui.conflict.titleAfter") : "",
|
|
116
116
|
child: name(c.child),
|
|
117
117
|
// a child's tie to each parent: one word for both, else each parent's
|
|
@@ -148,30 +148,32 @@ function changeLine(tree, c, incoming, lang, readings) {
|
|
|
148
148
|
? `ui.sync.do.removed${c.action === "conflict" ? ".conflict" : ""}`
|
|
149
149
|
: c.kind === "name.changed" && c.action === "user"
|
|
150
150
|
? "ui.sync.do.name.user"
|
|
151
|
-
: c.kind === "name.
|
|
152
|
-
? `ui.sync.do.name
|
|
153
|
-
: c.kind === "name.
|
|
154
|
-
? `ui.sync.do.
|
|
155
|
-
: c.kind === "
|
|
156
|
-
?
|
|
157
|
-
: c.kind === "
|
|
158
|
-
? "ui.sync.do.
|
|
159
|
-
: c.kind === "
|
|
160
|
-
? "ui.sync.do.
|
|
161
|
-
: c.kind === "fact.
|
|
162
|
-
?
|
|
163
|
-
: c.kind === "
|
|
164
|
-
? `ui.sync.do.
|
|
165
|
-
: c.kind === "source.
|
|
166
|
-
? `ui.sync.do.source.
|
|
167
|
-
: c.kind
|
|
168
|
-
? `ui.sync.do
|
|
169
|
-
: c.kind === "fact.
|
|
170
|
-
?
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
:
|
|
151
|
+
: c.kind === "name.shown"
|
|
152
|
+
? `ui.sync.do.name.shown${c.action === "conflict" ? ".conflict" : ""}`
|
|
153
|
+
: c.kind === "name.gone"
|
|
154
|
+
? `ui.sync.do.name.${c.action === "remove" ? "gone" : "kept"}`
|
|
155
|
+
: c.kind === "name.title" && (c.action === "add" || c.action === "user")
|
|
156
|
+
? `ui.sync.do.title${c.action === "user" ? ".user" : ""}`
|
|
157
|
+
: c.kind === "note.changed" && c.action !== "pick"
|
|
158
|
+
? "ui.sync.do.note.changed"
|
|
159
|
+
: c.kind === "family.union" && c.action !== "report"
|
|
160
|
+
? "ui.sync.do.family.union"
|
|
161
|
+
: c.kind === "fact.detail" && c.action === "add"
|
|
162
|
+
? "ui.sync.do.detail"
|
|
163
|
+
: c.kind === "fact.part" && (c.action === "add" || c.action === "user")
|
|
164
|
+
? `ui.sync.do.part${c.action === "user" ? ".user" : ""}`
|
|
165
|
+
: c.kind === "source.title"
|
|
166
|
+
? `ui.sync.do.source.title${c.action === "add" ? ".beside" : c.action === "user" ? ".user" : ""}`
|
|
167
|
+
: c.kind === "source.new" && c.reading?.ok
|
|
168
|
+
? `ui.sync.do.source.new.reading${c.reading.edited ? ".edited" : ""}`
|
|
169
|
+
: c.kind.startsWith("source.") || c.kind === "fact.cite" || c.kind === "person.cite" || c.kind === "family.cite"
|
|
170
|
+
? `ui.sync.do.${c.kind}${c.reads || (c.kind === "source.new" && newReads(c, incoming)) ? ".read" : ""}`
|
|
171
|
+
: c.kind === "fact.new" && c.reads
|
|
172
|
+
? "ui.sync.do.reading"
|
|
173
|
+
// a fact on an entry of the research's reading of a document: as sure as its readers read it
|
|
174
|
+
: c.kind === "fact.new" && c.fact?.cites?.some((x) => readings?.get(x.source)?.ok)
|
|
175
|
+
? "ui.sync.do.reading.document"
|
|
176
|
+
: `ui.sync.do.${c.action}`;
|
|
175
177
|
// an archive: nobody reads a record there, nor is an agent told — said without it
|
|
176
178
|
const said = actsAsArchive(tree) && `${does}.archive` in UI ? `${does}.archive` : does;
|
|
177
179
|
return `${ui(lang, key, v)} → ${ui(lang, said)}`;
|
package/dist/core/actions.js
CHANGED
|
@@ -5,7 +5,7 @@ import { normalizeDate, reversedRange } from "./gdate.js";
|
|
|
5
5
|
import { normalizeAge } from "./age.js";
|
|
6
6
|
import { checkStatus, defaultStatus } from "./evidence.js";
|
|
7
7
|
import { eventKind, FAMILY_EVENT_KINDS, NOTE_MAX, STATUSES, PARTICIPANT_ROLES, NAME_KINDS, RECORD_TYPES, } from "./model.js";
|
|
8
|
-
import { cleanTitle, displayName, familiesAsChild, familiesAsPartner, gedcomName, gedcomTitledName, closesCircle, isAncestorOf, isBirthFamily, keepSides, notAName, parseName, primaryName, sameName, slashInName } from "./people.js";
|
|
8
|
+
import { cleanTitle, displayName, familiesAsChild, familiesAsPartner, gedcomName, gedcomTitledName, closesCircle, isAncestorOf, isBirthFamily, keepSides, notAName, parseName, primaryName, sameName, slashInName, withoutMark } from "./people.js";
|
|
9
9
|
import { foldText } from "./text.js";
|
|
10
10
|
import { roleWord } from "./roles.js";
|
|
11
11
|
import { checkParticipant, trailingNote } from "./participants.js";
|
|
@@ -651,8 +651,8 @@ export function addName(tree, id, input) {
|
|
|
651
651
|
throw new UsageError(`invalid --kind "${input.kind}"`, { hint: NAME_KINDS.join(", ") });
|
|
652
652
|
name.kind = input.kind;
|
|
653
653
|
}
|
|
654
|
-
if (input.
|
|
655
|
-
throw new UsageError(`a person is
|
|
654
|
+
if (input.first && name.kind && name.kind !== "birth")
|
|
655
|
+
throw new UsageError(`a person's identity is a birth name, not a ${name.kind} one`);
|
|
656
656
|
if (input.citation)
|
|
657
657
|
checkCitation(tree, input.citation);
|
|
658
658
|
return tree.withTreeLock(() => {
|
|
@@ -667,11 +667,12 @@ export function addName(tree, id, input) {
|
|
|
667
667
|
const rekind = name.kind !== undefined && name.kind !== cur.kind;
|
|
668
668
|
const retitled = withTitles(cur, input.prefix, input.suffix);
|
|
669
669
|
const titles = retitled.prefix !== cur.prefix || retitled.suffix !== cur.suffix;
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
670
|
+
const marks = !!input.primary && !cur.primary;
|
|
671
|
+
if (!input.citation && !marks && !input.first && !rekind && !titles)
|
|
672
|
+
throw new UsageError(input.primary ? `${id} is shown by ${gedcomName(cur)} already` : `${id} already has the name ${gedcomName(name)}`, { hint: `cite it: strom name add ${id} "${gedcomName(name)}" --cite S…` });
|
|
673
|
+
added = { ...retitled, ...(rekind ? { kind: name.kind } : {}), ...(input.primary ? { primary: true } : {}), ...(input.citation ? { citations: withCitation(cur.citations, input.citation, `${id} ${gedcomName(name)}`) } : {}) };
|
|
673
674
|
names = p.names.map((n, j) => (j === i ? added : n));
|
|
674
|
-
if (input.
|
|
675
|
+
if (input.first)
|
|
675
676
|
names = [added, ...names.filter((n) => n !== added)];
|
|
676
677
|
}
|
|
677
678
|
else {
|
|
@@ -681,19 +682,62 @@ export function addName(tree, id, input) {
|
|
|
681
682
|
const plain = !name.kind || name.kind === "birth";
|
|
682
683
|
// "Markéta" of the family memory is "Markéta /Růžičková/" of the register: one name, now complete.
|
|
683
684
|
completes = Boolean(!input.other && plain && !primary.surname && name.surname && foldText(primary.given) === foldText(name.given) && !primary.citations?.length);
|
|
685
|
+
// the name completed was the one the person is shown by: the full one is
|
|
686
|
+
if (input.primary || (completes && primary.primary))
|
|
687
|
+
added = { ...added, primary: true };
|
|
684
688
|
const rest = completes ? p.names.filter((n) => n !== primary) : p.names;
|
|
685
|
-
names = !input.other && (input.
|
|
689
|
+
names = !input.other && (input.first || (plain && !primary.surname && name.surname)) ? [added, ...rest] : [...rest, added];
|
|
686
690
|
}
|
|
691
|
+
// one name the person is shown by: the one marked now
|
|
692
|
+
if (added.primary)
|
|
693
|
+
names = names.map((n) => (n === added || !n.primary ? n : withoutMark(n)));
|
|
687
694
|
const updated = { ...p, names, updated: now() };
|
|
688
695
|
tree.put(updated, {
|
|
689
696
|
op: "person.name",
|
|
690
697
|
targets: [id],
|
|
691
|
-
summary: `${id} name ${gedcomTitledName(added)}${added.kind ? ` (${added.kind})` : ""}${input.citation ? ` ← ${input.citation.source}` : ""}`,
|
|
698
|
+
summary: `${id} name ${gedcomTitledName(added)}${added.kind ? ` (${added.kind})` : ""}${input.citation ? ` ← ${input.citation.source}` : ""}${input.primary ? "; shown by it" : ""}`,
|
|
692
699
|
...(completes ? { reason: `the name without a surname is completed: ${gedcomName(added)}` } : {}),
|
|
693
700
|
});
|
|
694
701
|
return { person: updated, name: added };
|
|
695
702
|
});
|
|
696
703
|
}
|
|
704
|
+
/**
|
|
705
|
+
* Show a person by one of their names (the mark Name.primary: a divorced woman back at her birth surname, spec 024),
|
|
706
|
+
* without adding it again — or, with no name, by the name of their identity again (the mark taken off). The user's
|
|
707
|
+
* choice: never told from a record, never needs one.
|
|
708
|
+
*/
|
|
709
|
+
export function markPrimaryName(tree, id, text, reason) {
|
|
710
|
+
const wanted = text === undefined ? undefined : parseName(text);
|
|
711
|
+
if (wanted && !wanted.given && !wanted.surname)
|
|
712
|
+
throw new UsageError("the name is empty");
|
|
713
|
+
return tree.withTreeLock(() => {
|
|
714
|
+
const p = requirePerson(tree, id);
|
|
715
|
+
const was = p.names.find((n) => n.primary);
|
|
716
|
+
let name;
|
|
717
|
+
if (wanted) {
|
|
718
|
+
const found = p.names.find((n) => sameName(n, wanted));
|
|
719
|
+
if (!found) {
|
|
720
|
+
const gone = p.retractedNames?.find((n) => sameName(n, wanted));
|
|
721
|
+
if (gone)
|
|
722
|
+
throw new UsageError(`the name ${gedcomName(gone)} of ${id} was retracted: ${gone.retracted.reason}`, { hint: `right after all: strom name restore ${id} "${gedcomName(gone)}" --reason "…"`, code: "name.retracted", params: { name: gedcomName(gone), id, reason: gone.retracted.reason } });
|
|
723
|
+
throw new UsageError(`${id} has no name ${gedcomName(wanted)}`, { hint: `its names: ${p.names.map((n) => `"${gedcomName(n)}"`).join(", ")} · a new one: strom name add ${id} "${gedcomName(wanted)}" --primary`, code: "name.none" });
|
|
724
|
+
}
|
|
725
|
+
if (found.primary)
|
|
726
|
+
throw new UsageError(`${id} is shown by ${gedcomName(found)} already`, { hint: `back to the name of their identity: strom name primary ${id} --clear` });
|
|
727
|
+
name = { ...found, primary: true };
|
|
728
|
+
const names = p.names.map((n) => (n === found ? name : n.primary ? withoutMark(n) : n));
|
|
729
|
+
const updated = { ...p, names, updated: now() };
|
|
730
|
+
tree.put(updated, { op: "name.primary", targets: [id], summary: `${id} shown by ${gedcomName(name)}${was ? ` (was ${gedcomName(was)})` : ""}`, ...(reason?.trim() ? { reason: reason.trim() } : {}) });
|
|
731
|
+
return { person: updated, name, ...(was ? { was } : {}) };
|
|
732
|
+
}
|
|
733
|
+
if (!was)
|
|
734
|
+
throw new UsageError(`no name of ${id} is marked: shown by ${gedcomName(primaryName(p))}`, { hint: `strom name primary ${id} "<Given /Surname/>"` });
|
|
735
|
+
const updated = { ...p, names: p.names.map((n) => (n === was ? withoutMark(n) : n)), updated: now() };
|
|
736
|
+
name = primaryName(updated);
|
|
737
|
+
tree.put(updated, { op: "name.primary", targets: [id], summary: `${id} shown by ${gedcomName(name)} again (the mark of ${gedcomName(was)} taken off)`, ...(reason?.trim() ? { reason: reason.trim() } : {}) });
|
|
738
|
+
return { person: updated, name, was };
|
|
739
|
+
});
|
|
740
|
+
}
|
|
697
741
|
/** Change a person's sex or correct the spelling of their name — with a reason, unless an unknown sex is filled in. */
|
|
698
742
|
export function editPerson(tree, id, edit, reason) {
|
|
699
743
|
const sex = edit.sex === undefined ? undefined : parseSex(edit.sex);
|
|
@@ -925,13 +969,20 @@ export function mergePersons(tree, keepId, otherId, reason) {
|
|
|
925
969
|
const names = keep.names.map((n) => {
|
|
926
970
|
const same = other.names.find((o) => sameName(o, n));
|
|
927
971
|
const cites = [...(n.citations ?? []), ...(same?.citations ?? []).filter((c) => !(n.citations ?? []).some((x) => x.source === c.source && x.locator === c.locator))];
|
|
928
|
-
|
|
972
|
+
// the other shown by this same name, the kept one by none marked: shown by it
|
|
973
|
+
const mark = same?.primary && !keep.names.some((k) => k.primary) ? { primary: true } : {};
|
|
974
|
+
return cites.length ? { ...n, ...mark, citations: cites } : { ...n, ...mark };
|
|
929
975
|
});
|
|
930
976
|
// a name the person's research took back stays taken back (the other's retracted come along, for the history)
|
|
931
977
|
const retractedNames = [...(keep.retractedNames ?? []), ...(other.retractedNames ?? [])];
|
|
932
978
|
for (const o of other.names)
|
|
933
979
|
if (!names.some((n) => sameName(n, o)) && !retractedNames.some((n) => sameName(n, o)))
|
|
934
980
|
names.push(o);
|
|
981
|
+
// the name they are shown by: the kept person's, else the other's — one
|
|
982
|
+
const marked = names.find((n) => n.primary);
|
|
983
|
+
for (let i = 0; i < names.length; i++)
|
|
984
|
+
if (names[i].primary && names[i] !== marked)
|
|
985
|
+
names[i] = withoutMark(names[i]);
|
|
935
986
|
const retractedNotes = [...(keep.retractedNotes ?? []), ...(other.retractedNotes ?? [])];
|
|
936
987
|
const refs = [...(keep.refs ?? []), ...(other.refs ?? []).filter((r) => !(keep.refs ?? []).some((k) => k.system === r.system && k.id === r.id))];
|
|
937
988
|
const why = reason.trim();
|
package/dist/core/changelog.js
CHANGED
|
@@ -365,6 +365,11 @@ export function changeLines(tree, ops, subject, lang, was, tasks) {
|
|
|
365
365
|
say(m ? phrase(lang, o.op === "name.retract" ? "log.name.retract" : "log.name.restore", { name: m[1].replace(/\//g, " ").replace(/\s+/g, " ").trim(), who: who(first) }) : changed(o));
|
|
366
366
|
continue;
|
|
367
367
|
}
|
|
368
|
+
if (o.op === "name.primary") {
|
|
369
|
+
const m = /^\S+ shown by (.+?)( again \(|(?: \(was |$))/.exec(o.summary);
|
|
370
|
+
say(m ? phrase(lang, m[2] === " again (" ? "log.name.primary.clear" : "log.name.primary", { name: m[1].replace(/\//g, " ").replace(/\s+/g, " ").trim(), who: who(first) }) : changed(o));
|
|
371
|
+
continue;
|
|
372
|
+
}
|
|
368
373
|
if (o.op === "note.remove") {
|
|
369
374
|
const m = /^\S+ note "(.*)" removed:/.exec(o.summary);
|
|
370
375
|
say(m ? phrase(lang, "log.note.remove", { note: m[1], name: name(first) }) : changed(o));
|
package/dist/core/import.js
CHANGED
|
@@ -12,7 +12,7 @@ import { roleWord } from "./roles.js";
|
|
|
12
12
|
import { validateRecord } from "./validate.js";
|
|
13
13
|
import { normalizeDate } from "./gdate.js";
|
|
14
14
|
import { isRetractedName } from "./retractions.js";
|
|
15
|
-
import { appNoSurname, cleanTitle, gedcomName, noName, notAName, parseName, sameName, surnameAfterGiven, withoutTitles } from "./people.js";
|
|
15
|
+
import { appNoSurname, cleanTitle, gedcomName, noName, notAName, parseName, sameName, surnameAfterGiven, withoutMark, withoutTitles } from "./people.js";
|
|
16
16
|
import { update } from "./records.js";
|
|
17
17
|
import { now } from "./tree.js";
|
|
18
18
|
import { appUnknownIsNoSurname, appWritesSexUnknown } from "./stromapp.js";
|
|
@@ -373,9 +373,14 @@ export function importGedcom(tree, text, opts) {
|
|
|
373
373
|
if (notAName(found.given))
|
|
374
374
|
found.given = "?";
|
|
375
375
|
// a slash inside a name ("/⟨K/Č⟩emenská/") is kept as "|": the name stays one GEDCOM can write
|
|
376
|
-
|
|
376
|
+
const name = { ...found, given: found.given.replace(/\//g, "|"), surname: found.surname.replace(/\//g, "|") };
|
|
377
|
+
// the name the person is shown by now (the Strom file's mark, the user's choice): the first one marked
|
|
378
|
+
if (/^y(es)?$/i.test(val(n, "_STROM_PRIMARY")?.trim() ?? ""))
|
|
379
|
+
name.primary = true;
|
|
380
|
+
return name;
|
|
377
381
|
})
|
|
378
|
-
.map((n) => (n.given || n.surname ? n : { ...n, given: "?", surname: "" }))
|
|
382
|
+
.map((n) => (n.given || n.surname ? n : { ...n, given: "?", surname: "" }))
|
|
383
|
+
.map((n, i, all) => (n.primary && all.findIndex((m) => m.primary) !== i ? withoutMark(n) : n));
|
|
379
384
|
// a married name other programs give apart (_MARNM): a name of the married kind, unless the person has it already
|
|
380
385
|
return [...names, ...importedMarnm(r, fromApp).filter((m) => !names.some((n) => sameName(n, m)))];
|
|
381
386
|
};
|
|
@@ -407,7 +412,8 @@ export function importGedcom(tree, text, opts) {
|
|
|
407
412
|
const facts = incoming.filter((e) => !knownFact(cur.events, e));
|
|
408
413
|
result.events -= incoming.length - facts.length;
|
|
409
414
|
// a name the research took back is not taken again from a file that still has it
|
|
410
|
-
|
|
415
|
+
// the names another file adds: never the one the person is shown by (theirs stands)
|
|
416
|
+
const names = namesOf(r).filter((n) => n.given !== "?" && !cur.names.some((x) => sameName(x, n)) && !isRetractedName(cur, n)).map(withoutMark);
|
|
411
417
|
if (!facts.length && !names.length)
|
|
412
418
|
continue;
|
|
413
419
|
const next = { ...cur, names: [...cur.names, ...names], events: [...cur.events, ...facts], refs: [...(cur.refs ?? []), { system, id: r.xref }], updated: now() };
|
package/dist/core/live.js
CHANGED
|
@@ -917,7 +917,7 @@ function idsOf(applied, known) {
|
|
|
917
917
|
return Object.keys(ids.persons).length || Object.keys(ids.sources).length ? { ids } : {};
|
|
918
918
|
}
|
|
919
919
|
/** What the bridge does that an app may ask about (each added once, never taken away). */
|
|
920
|
-
export const BRIDGE_FEATURES = ["sync.again", "sync.undoneSince", "sync.takenBack", "sync.conflictEdit", "sync.since", "sync.ids", "family.noCouple", "family.alone", "adopt.transfer", "adopt.empty", "material.list", "person.titles", "media.codes", "hypothesis.links", "status.recent", "spend.readers", "hypothesis.intree", "archive.permission", "task.check", "waiting.groups", "waiting.sent", "waiting.note", "batch.kept", "live.end", "whats.new", "terminal.choice", "person.marriedSurname", "phone", "read.now", "read.sync", "excerpt.get", "log.sources", "material.assign", "material.thumb", "seal.foreign", "tree.state", "disk.full", "tree.notDownloaded", "sync.removed", "tree.kind", "experiments", "experiments.off", "experiments.link", "experiments.split", "history.events", "history.hide", "experiments.pace", "history.first", "history.regions", "history.hide.all", "history.skip", "history.map", "history.delete", "history.delete.restore", "stories.history", "history.place", "surnames.suggest", "surnames.read", "duplicates.judge", "quality.dismiss", "quality.read"];
|
|
920
|
+
export const BRIDGE_FEATURES = ["sync.again", "sync.undoneSince", "sync.takenBack", "sync.conflictEdit", "sync.since", "sync.ids", "family.noCouple", "family.alone", "adopt.transfer", "adopt.empty", "material.list", "person.titles", "media.codes", "hypothesis.links", "status.recent", "spend.readers", "hypothesis.intree", "archive.permission", "task.check", "waiting.groups", "waiting.sent", "waiting.note", "batch.kept", "live.end", "whats.new", "terminal.choice", "person.marriedSurname", "phone", "read.now", "read.sync", "excerpt.get", "log.sources", "material.assign", "material.thumb", "seal.foreign", "tree.state", "disk.full", "tree.notDownloaded", "sync.removed", "tree.kind", "experiments", "experiments.off", "experiments.link", "experiments.split", "history.events", "history.hide", "experiments.pace", "history.first", "history.regions", "history.hide.all", "history.skip", "history.map", "history.delete", "history.delete.restore", "stories.history", "history.place", "surnames.suggest", "surnames.read", "duplicates.judge", "quality.dismiss", "quality.read", "person.primary"];
|
|
921
921
|
/** /status recent, when it is ready. */
|
|
922
922
|
function recentOf(root, env, head) {
|
|
923
923
|
const recent = head ? recentNow(root, env, head) : undefined;
|
package/dist/core/overview.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
// ancestors of one person (pedigree), one person's life (card), and what came
|
|
3
3
|
// in lately (recent). Read only, from the records and the operation logs; the
|
|
4
4
|
// commands in commands/browse.ts show them in the research language.
|
|
5
|
-
import { ancestorGenerations, birthEvent, displayName, familiesAsPartner, formatName, lifespan, parentsOf,
|
|
5
|
+
import { ancestorGenerations, birthEvent, currentName, displayName, familiesAsPartner, formatName, lifespan, parentsOf, sameName, titledName } from "./people.js";
|
|
6
6
|
import { dateYears } from "./gdate.js";
|
|
7
7
|
import { informationOf } from "./evidence.js";
|
|
8
8
|
import { imagesIndex } from "./mediaindex.js";
|
|
@@ -159,7 +159,8 @@ export function cardFact(tree, e) {
|
|
|
159
159
|
};
|
|
160
160
|
}
|
|
161
161
|
export function personCard(tree, p) {
|
|
162
|
-
|
|
162
|
+
// the name they are shown by (the one the user chose, else their birth name): the others beside it
|
|
163
|
+
const main = currentName(p);
|
|
163
164
|
const families = familiesAsPartner(tree, p.id).map((f) => {
|
|
164
165
|
const other = f.partners.filter((x) => x !== p.id).map((x) => tree.get(x)).find((x) => x && !x.retracted);
|
|
165
166
|
return {
|
package/dist/core/people.js
CHANGED
|
@@ -179,8 +179,21 @@ export function slashInName(n) {
|
|
|
179
179
|
export function primaryName(p) {
|
|
180
180
|
return p.names.find((n) => !n.kind || n.kind === "birth") ?? p.names[0] ?? { given: "", surname: "" };
|
|
181
181
|
}
|
|
182
|
+
/**
|
|
183
|
+
* The name the person is shown by now: the one marked (Name.primary, the user's choice — a divorced woman back at her
|
|
184
|
+
* birth surname), else the name of their identity (primaryName). Only for showing them: the identity a correction, a
|
|
185
|
+
* citation, pairing and a sync go by stays primaryName.
|
|
186
|
+
*/
|
|
187
|
+
export function currentName(p) {
|
|
188
|
+
return p.names.find((n) => n.primary) ?? primaryName(p);
|
|
189
|
+
}
|
|
190
|
+
/** A name without the mark of the one the person is shown by (Name.primary). */
|
|
191
|
+
export function withoutMark(n) {
|
|
192
|
+
const { primary: _p, ...rest } = n;
|
|
193
|
+
return rest;
|
|
194
|
+
}
|
|
182
195
|
export function displayName(p) {
|
|
183
|
-
return formatName(
|
|
196
|
+
return formatName(currentName(p));
|
|
184
197
|
}
|
|
185
198
|
const STATUS_RANK = { proven: 0, probable: 1, possible: 2, lead: 3 };
|
|
186
199
|
/** Best-supported first: status, then the number of citations, then the order they were added. */
|
package/dist/core/phrases.js
CHANGED
|
@@ -271,6 +271,8 @@ export const PHRASES = {
|
|
|
271
271
|
"log.note.edit": "Note corrected: {name}",
|
|
272
272
|
"log.name.retract": "Name retracted: {name} – {who}",
|
|
273
273
|
"log.name.restore": "Name put back: {name} – {who}",
|
|
274
|
+
"log.name.primary": "Shown by the name: {name} – {who}",
|
|
275
|
+
"log.name.primary.clear": "Shown by the birth name again: {name} – {who}",
|
|
274
276
|
"log.note.retract": "Note retracted: {name}",
|
|
275
277
|
"log.note.restore": "Note put back: {name}",
|
|
276
278
|
"log.event.status": "How sure a fact is, changed: {who} – {fact}",
|
|
@@ -218,6 +218,39 @@ export function regionEventsAt(tree, shared, id, at = factsByRecord(tree).get(id
|
|
|
218
218
|
}
|
|
219
219
|
return [...out.values()].sort((a, b) => a.event.from - b.event.from || a.event.id.localeCompare(b.event.id));
|
|
220
220
|
}
|
|
221
|
+
/**
|
|
222
|
+
* The own events of the tree's places that belong to a region in the years of an event (their facts' places mapped to
|
|
223
|
+
* it): the mirror of regionEventsAt, for a place looked for before its region (spec 020 K7.1a). Not taken back.
|
|
224
|
+
*/
|
|
225
|
+
export function placeEventsIn(tree, shared, region, e) {
|
|
226
|
+
if (!shared)
|
|
227
|
+
return [];
|
|
228
|
+
const maps = readPlaces(tree);
|
|
229
|
+
const folded = new Map(Object.entries(maps).map(([k, v]) => [foldText(k), v]));
|
|
230
|
+
const out = [];
|
|
231
|
+
for (const [id, at] of factsByRecord(tree)) {
|
|
232
|
+
const inIt = at.keys.some((key) => ((maps[key] ?? folded.get(foldText(key)))?.regions ?? []).some((g) => g.id === region && inRegion(e, g)));
|
|
233
|
+
const p = inIt ? placeRecord(tree, id) : undefined;
|
|
234
|
+
if (!p)
|
|
235
|
+
continue;
|
|
236
|
+
for (const x of readPlaceCatalog(shared, placeKeyOf(tree, p))?.events ?? [])
|
|
237
|
+
if (!x.retracted)
|
|
238
|
+
out.push({ event: x, place: p });
|
|
239
|
+
}
|
|
240
|
+
return out.sort((a, b) => a.event.from - b.event.from || a.event.id.localeCompare(b.event.id));
|
|
241
|
+
}
|
|
242
|
+
/** An event of one of the region's places of the same kind and years: the region's history never writes it again. */
|
|
243
|
+
export function refusePlaceSame(tree, shared, region, e) {
|
|
244
|
+
const same = placeEventsIn(tree, shared, region, e).filter((x) => x.event.kind === e.kind && x.event.from <= e.to && e.from <= x.event.to);
|
|
245
|
+
if (!same.length)
|
|
246
|
+
return;
|
|
247
|
+
const ids = same.map((x) => `${x.event.id} (${x.place.id})`);
|
|
248
|
+
throw new UsageError(`an event of a place of ${region} of the same kind and years is there already: ${ids.join(", ")} — nothing written`, {
|
|
249
|
+
code: "history.same.place",
|
|
250
|
+
params: { ids: same.map((x) => x.event.id).join(", ") },
|
|
251
|
+
hint: `the place's event shows there already — leave it out; an event of the whole region (it touched its other places too): --distinct "<why it is another>"`,
|
|
252
|
+
});
|
|
253
|
+
}
|
|
221
254
|
// ── the person's asks ──────────────────────────────────────────────────────
|
|
222
255
|
export function readPlaceAsks(tree) {
|
|
223
256
|
try {
|
package/dist/core/retractions.js
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
import { UsageError } from "./errors.js";
|
|
6
6
|
import { findEventOwner, makeNote } from "./actions.js";
|
|
7
7
|
import { EMBEDDED_PREFIXES } from "./model.js";
|
|
8
|
-
import { gedcomName, parseName, primaryName, sameName } from "./people.js";
|
|
8
|
+
import { currentName, gedcomName, parseName, primaryName, sameName, withoutMark } from "./people.js";
|
|
9
9
|
import { now, typeOfId } from "./tree.js";
|
|
10
10
|
function needsReason(reason, what) {
|
|
11
11
|
const why = reason?.trim();
|
|
@@ -48,8 +48,8 @@ export function retractName(tree, id, text, reason) {
|
|
|
48
48
|
});
|
|
49
49
|
const gone = { ...name, retracted: retraction(tree, why) };
|
|
50
50
|
const updated = { ...p, names: p.names.filter((n) => n !== name), retractedNames: [...(p.retractedNames ?? []), gone], updated: now() };
|
|
51
|
-
const shown =
|
|
52
|
-
const moved = name ===
|
|
51
|
+
const shown = currentName(updated);
|
|
52
|
+
const moved = name === currentName(p);
|
|
53
53
|
tree.put(updated, {
|
|
54
54
|
op: "name.retract",
|
|
55
55
|
targets: [id],
|
|
@@ -69,7 +69,9 @@ export function restoreName(tree, id, text, reason) {
|
|
|
69
69
|
throw new UsageError(`${id} has no retracted name ${gedcomName(wanted)}`, { hint: `its retracted names: ${nameList(p.retractedNames ?? [])}`, code: "name.none" });
|
|
70
70
|
if (p.names.some((n) => sameName(n, gone)))
|
|
71
71
|
throw new UsageError(`${id} has the name ${gedcomName(gone)} again`, { hint: `nothing to restore: strom person show ${id}` });
|
|
72
|
-
|
|
72
|
+
// the name they were shown by, back as that one unless another is marked since
|
|
73
|
+
const { retracted: _was, ...back } = gone;
|
|
74
|
+
const name = back.primary && p.names.some((n) => n.primary) ? withoutMark(back) : back;
|
|
73
75
|
const rest = (p.retractedNames ?? []).filter((n) => n !== gone);
|
|
74
76
|
const { retractedNames: _all, ...base } = p;
|
|
75
77
|
const updated = { ...base, names: [...p.names, name], ...(rest.length ? { retractedNames: rest } : {}), updated: now() };
|
package/dist/core/stromapp.js
CHANGED
|
@@ -537,6 +537,27 @@ export function appReadsSurnameGroups(settings, version, gate = APP_READS_SURNAM
|
|
|
537
537
|
return compareVersions(v, APP_READS_SURNAME_MARKERS) >= 0;
|
|
538
538
|
return !!gate && compareVersions(v, gate) >= 0;
|
|
539
539
|
}
|
|
540
|
+
/**
|
|
541
|
+
* The first Strom app that knows which name a person is shown by (spec 024, the app's ZADANI_VYZKUM_soucasne-prijmeni.md):
|
|
542
|
+
* it writes 2 _STROM_PRIMARY Y under the NAME the user chose (its Person.primaryName birth | married, the JSON's field
|
|
543
|
+
* of that name) and none where the user chose none. The research writes the mark to every app (an older one skips it in
|
|
544
|
+
* silence and loses nothing); a mark an app sends is the user's choice whatever its version — but a mark missing is
|
|
545
|
+
* the user's "none" only from an app of this version on: from an older one, or one that says no version, nothing
|
|
546
|
+
* changed. Not released yet (undefined): set to the version the app's "je venku" names; another copy of the app (its
|
|
547
|
+
* beta, its development) gets it at once from APP_PRIMARY_NAME_BETA once that is known too.
|
|
548
|
+
*/
|
|
549
|
+
export const APP_KNOWS_PRIMARY_NAME = undefined;
|
|
550
|
+
/** The first beta of the app that writes the mark (another copy of the app gets it from it); undefined: none yet. */
|
|
551
|
+
export const APP_PRIMARY_NAME_BETA = undefined;
|
|
552
|
+
/** Does a file of the Strom app of this version say every choice of the name a person is shown by — a mark missing the user's "none"? */
|
|
553
|
+
export function appKnowsPrimaryName(settings, version) {
|
|
554
|
+
const v = version?.trim();
|
|
555
|
+
if (!v || !isAppVersion(v))
|
|
556
|
+
return false;
|
|
557
|
+
if (anotherCopy(settings) && APP_PRIMARY_NAME_BETA)
|
|
558
|
+
return compareVersions(v, APP_PRIMARY_NAME_BETA) >= 0;
|
|
559
|
+
return !!APP_KNOWS_PRIMARY_NAME && compareVersions(v, APP_KNOWS_PRIMARY_NAME) >= 0;
|
|
560
|
+
}
|
|
540
561
|
/** The address that opens the Strom app to send the user's edited tree to the bridge. */
|
|
541
562
|
export function sendAppUrl(bridge, settings) {
|
|
542
563
|
return `${stromAppUrl(settings)}?send=${encodeURIComponent(bridge)}`;
|
package/dist/core/sync.js
CHANGED
|
@@ -24,7 +24,7 @@ import path from "node:path";
|
|
|
24
24
|
import { CHILD_RELATIONS, UNIONS, eventKind } from "./model.js";
|
|
25
25
|
import { isWeak } from "./evidence.js";
|
|
26
26
|
import { phrase } from "./phrases.js";
|
|
27
|
-
import { addChild, addEvent, addFamily, addName, addNote, editEvent, editPerson, retractEvent, retractPerson } from "./actions.js";
|
|
27
|
+
import { addChild, addEvent, addFamily, addName, addNote, editEvent, editPerson, markPrimaryName, retractEvent, retractPerson } from "./actions.js";
|
|
28
28
|
import { exportGedcom, REFN_TYPE } from "../gedcom/export.js";
|
|
29
29
|
import { children, parseGedcomText, val } from "../gedcom/parse.js";
|
|
30
30
|
import { fromFlexDate, houseOf, importDate, marnmNames, readGedName, readGedSex, stromAppFile } from "./import.js";
|
|
@@ -42,14 +42,14 @@ import * as git from "./git.js";
|
|
|
42
42
|
import { now, Tree, typeOfId } from "./tree.js";
|
|
43
43
|
import { actsAsArchive } from "./state.js";
|
|
44
44
|
import { appSides } from "./conflicts.js";
|
|
45
|
-
import { APP_DATA_SEX_UNKNOWN, appKeepsPeriods, appReadsSurnameGroups, isAppVersion } from "./stromapp.js";
|
|
45
|
+
import { APP_DATA_SEX_UNKNOWN, appKeepsPeriods, appKnowsPrimaryName, appReadsSurnameGroups, isAppVersion } from "./stromapp.js";
|
|
46
46
|
import { Settings } from "./config.js";
|
|
47
47
|
import { applySurnameChange, formsSaid, planSurnames, readMarkLines, readSurnames, surnamesAt, undoSurnameChange } from "./surnames.js";
|
|
48
48
|
import { SURNAME_GROUPS_MARK, SURNAME_REJECTIONS_MARK } from "../gedcom/export.js";
|
|
49
49
|
import { bestGrade, checkReading, readingGrade, readingResult, readingSource, surer, takeReadingImages } from "./readsync.js";
|
|
50
50
|
import { earlierNoteTexts } from "./retractions.js";
|
|
51
51
|
import { mendAll } from "./damaged.js";
|
|
52
|
-
import { cleanTitle, familiesAsPartner, formatName, foundTitles, gedcomTitledName, parseName, primaryName, sameName as sameWords, titledName } from "./people.js";
|
|
52
|
+
import { cleanTitle, currentName, familiesAsPartner, formatName, foundTitles, gedcomTitledName, parseName, primaryName, sameName as sameWords, titledName } from "./people.js";
|
|
53
53
|
/** The words of a _STAT (the Strom app's own, also Legacy's and FTM's) as a union, or nothing it names. */
|
|
54
54
|
function unionOfStat(value) {
|
|
55
55
|
const v = (value ?? "").trim().toLowerCase();
|
|
@@ -480,6 +480,8 @@ export function readGedcom(text) {
|
|
|
480
480
|
const head = records.find((r) => r.tag === "HEAD");
|
|
481
481
|
/** A file of the Strom app (its HEAD: 1 SOUR STROM): its own ways of writing are read as it means them. */
|
|
482
482
|
const fromApp = stromAppFile(head);
|
|
483
|
+
// the research's own file (what it gave the app, the research now): it says every choice of a name shown by
|
|
484
|
+
const research = val(head, "SOUR")?.trim() === "STROM_RESEARCH";
|
|
483
485
|
const notes = new Map(records.filter((r) => r.tag === "NOTE" && r.xref).map((r) => [r.xref, r.value]));
|
|
484
486
|
const noteText = (n) => (/^@[^@]+@$/.test(n.value.trim()) ? (notes.get(n.value.trim()) ?? "") : n.value);
|
|
485
487
|
// the sources: ours by their REFN, the file's own by their xref
|
|
@@ -567,6 +569,12 @@ export function readGedcom(text) {
|
|
|
567
569
|
// a placeholder of the Strom app (an unknown parent drawn in the tree): nobody
|
|
568
570
|
if (!ours && !names.length)
|
|
569
571
|
continue;
|
|
572
|
+
// the name they are shown by (spec 024) as the app sees it: its birth name (the first), its one name after a marriage
|
|
573
|
+
// (the last TYPE married, else the last _MARNM), else none — a mark missing says it only in the research's own file
|
|
574
|
+
const mi = kept.findIndex(({ n }) => /^y(es)?$/i.test(val(n, "_STROM_PRIMARY")?.trim() ?? ""));
|
|
575
|
+
const typed = names.filter((_, i) => i > 0 && i < kept.length && kept[i].name.kind === "married");
|
|
576
|
+
const appMarried = typed.at(-1) ?? married.at(-1);
|
|
577
|
+
const primary = mi === 0 ? "birth" : mi > 0 && names[mi] === appMarried ? `married|${nameKey(names[mi])}` : mi > 0 || research ? "none" : undefined;
|
|
570
578
|
const facts = gedFacts(r, undefined, read);
|
|
571
579
|
// a godparent other programs give the person (1 ASSO): theirs at the baptism, else the birth
|
|
572
580
|
const host = facts.find((f) => kindOf(f.kind) === "BAPM") ?? facts.find((f) => f.kind === "BIRT");
|
|
@@ -587,6 +595,7 @@ export function readGedcom(text) {
|
|
|
587
595
|
...(cites.length ? { cites } : {}),
|
|
588
596
|
...(titles ? { titles } : {}),
|
|
589
597
|
...(married.length ? { married } : {}),
|
|
598
|
+
...(primary ? { primary } : {}),
|
|
590
599
|
});
|
|
591
600
|
}
|
|
592
601
|
// how a child belongs to a family (the child's FAMC + PEDI: adopted, foster, step): none said is birth
|
|
@@ -826,8 +835,11 @@ export function readStromJson(data) {
|
|
|
826
835
|
const marriedSurname = typeof p.marriedSurname === "string" ? p.marriedSurname.replace(/[/\s]+/g, " ").trim() : "";
|
|
827
836
|
const married = marriedSurname ? `${p.firstName ?? ""} /${marriedSurname}/`.trim() : undefined;
|
|
828
837
|
const names = [name, ...(married ? [married] : []), ...(p.nameVariants ?? [])].filter((n) => n.replace(/[/?\s]/g, ""));
|
|
838
|
+
// the name the user chose to show them by (spec 024): the birth one, the one after the marriage; none said: nothing
|
|
839
|
+
const primary = p.primaryName === "birth" ? "birth" : p.primaryName === "married" && married ? `married|${nameKey(married)}` : undefined;
|
|
829
840
|
persons.set(key, {
|
|
830
841
|
key,
|
|
842
|
+
...(primary ? { primary } : {}),
|
|
831
843
|
...(titles ? { titles } : {}),
|
|
832
844
|
names: names.filter((n, i) => n !== married || names.findIndex((m) => nameKey(m) === nameKey(n)) === i),
|
|
833
845
|
...(married && names.findIndex((n) => nameKey(n) === nameKey(married)) > 0 ? { married: [married] } : {}),
|
|
@@ -1917,6 +1929,8 @@ export function planSync(tree, incoming, edits, opts = {}) {
|
|
|
1917
1929
|
});
|
|
1918
1930
|
}
|
|
1919
1931
|
const partial = !!base && incoming.persons.size < base.persons.size / 2;
|
|
1932
|
+
// a file that says every choice of the name a person is shown by: its mark missing is the user's "none" (spec 024)
|
|
1933
|
+
const knowsPrimary = appKnowsPrimaryName(new Settings(tree.env, {}), incoming.appVersion);
|
|
1920
1934
|
const changes = [];
|
|
1921
1935
|
const push = (c) => changes.push({ ...c, n: changes.length + 1 });
|
|
1922
1936
|
/** What the file no longer has: an archive takes it away — unless the window that sends never had it (kept, said). */
|
|
@@ -2220,6 +2234,34 @@ export function planSync(tree, incoming, edits, opts = {}) {
|
|
|
2220
2234
|
push({ kind: "name.gone", action: nameBacked(tree, mine) ? "report" : "remove", person: id, name, text: gedcomName(mine) });
|
|
2221
2235
|
}
|
|
2222
2236
|
};
|
|
2237
|
+
/**
|
|
2238
|
+
* The name the person is shown by (spec 024): the user's choice, never a record's — compared as the app sees it
|
|
2239
|
+
* (SPerson.primary). Changed in the app only: taken; in the research only: the research's, given the app again; both,
|
|
2240
|
+
* apart: the user's to decide (or their word: sync.edits user, an archive). A mark missing from an app before
|
|
2241
|
+
* APP_KNOWS_PRIMARY_NAME (or one that says no version) changes nothing; without the state it was given only a choice
|
|
2242
|
+
* where the research has none is taken.
|
|
2243
|
+
*/
|
|
2244
|
+
const primaryDiff = (id, name, p, o, b) => {
|
|
2245
|
+
const theirs = p.primary ?? (knowsPrimary ? "none" : undefined);
|
|
2246
|
+
const mine = o.primary ?? "none";
|
|
2247
|
+
if (theirs === undefined || theirs === mine)
|
|
2248
|
+
return;
|
|
2249
|
+
let action;
|
|
2250
|
+
if (b) {
|
|
2251
|
+
const given = b.primary ?? "none";
|
|
2252
|
+
if (theirs === given)
|
|
2253
|
+
return;
|
|
2254
|
+
action = mine !== given ? (edits === "user" ? "user" : "conflict") : "correct";
|
|
2255
|
+
}
|
|
2256
|
+
else {
|
|
2257
|
+
if (mine !== "none" || theirs === "none")
|
|
2258
|
+
return;
|
|
2259
|
+
action = "add";
|
|
2260
|
+
}
|
|
2261
|
+
// as the line says it: the name each side shows them by ("" none chosen)
|
|
2262
|
+
const line = (x, view) => (view === "birth" ? (x.names[0] ?? "") : (x.names.find((n) => `married|${nameKey(n)}` === view) ?? ""));
|
|
2263
|
+
push({ kind: "name.shown", action, person: id, name, shown: { theirs, mine }, text: line(p, theirs), wasName: line(o, mine) });
|
|
2264
|
+
};
|
|
2223
2265
|
/** The user's edits of a person: the file's (`p`) against the research's (`o`) and what the app was given (`b`). */
|
|
2224
2266
|
const personDiff = (id, name, p, o, b) => {
|
|
2225
2267
|
factsDiff({ person: id, name }, p.facts, o.facts, b?.facts, true);
|
|
@@ -2247,6 +2289,7 @@ export function planSync(tree, incoming, edits, opts = {}) {
|
|
|
2247
2289
|
if (!theirMarried.has(n) && !((renamed || both) && n === now) && !ourNames.has(nameKey(n)) && !baseNames.has(nameKey(n)) && !goneNames.has(nameKey(n)))
|
|
2248
2290
|
push({ kind: "name.new", action: "add", person: id, name, text: n });
|
|
2249
2291
|
marriedDiff(id, name, p, o, b);
|
|
2292
|
+
primaryDiff(id, name, p, o, b);
|
|
2250
2293
|
// the titles of the name they are shown by (T07), each apart and never as a change of the name itself: the user's
|
|
2251
2294
|
// edit where the file's differs from what the app was given — one the name lacks added, a lead's corrected, a
|
|
2252
2295
|
// record's name the user's to decide (or their word: sync.edits user, an archive); the research's changed since
|
|
@@ -3927,7 +3970,7 @@ export function applySync(tree, plan, incoming, source, only) {
|
|
|
3927
3970
|
}
|
|
3928
3971
|
else if (c.action === "user") {
|
|
3929
3972
|
// the user's word wins: shown by the new name, the record's kept beside it
|
|
3930
|
-
const { name } = addName(tree, owner, { name: c.text, citation: cite("name"),
|
|
3973
|
+
const { name } = addName(tree, owner, { name: c.text, citation: cite("name"), first: true });
|
|
3931
3974
|
applied.push({ do: "name.primary", id: owner, before: JSON.stringify({ added: exact(`${name.given}|${name.surname}`), shown: exact(`${shown.given}|${shown.surname}`) }) });
|
|
3932
3975
|
}
|
|
3933
3976
|
else {
|
|
@@ -3946,6 +3989,28 @@ export function applySync(tree, plan, incoming, source, only) {
|
|
|
3946
3989
|
}
|
|
3947
3990
|
break;
|
|
3948
3991
|
}
|
|
3992
|
+
case "name.shown": {
|
|
3993
|
+
// the name the user chose to show the person by (spec 024): marked, or the mark taken off
|
|
3994
|
+
if (!owner || !c.shown)
|
|
3995
|
+
break;
|
|
3996
|
+
const p = tree.get(owner);
|
|
3997
|
+
const target = nameOfView(p, c.shown.theirs);
|
|
3998
|
+
const was = p.names.find((n) => n.primary);
|
|
3999
|
+
if (target === undefined || (target === null ? !was : target.primary))
|
|
4000
|
+
break;
|
|
4001
|
+
if (c.action === "conflict") {
|
|
4002
|
+
const who = gedcomName(primaryName(p)).replace(/\//g, "").replace(/\s+/g, " ").trim();
|
|
4003
|
+
const said = (n) => (n ? formatName(n) : ui(tree.lang, "ui.conflict.primary.none"));
|
|
4004
|
+
conflictOfEdit(tree, applied, owner, "NAME", editTitle(who, ui(tree.lang, "ui.conflict.primary"), said(was), said(target)), [
|
|
4005
|
+
{ value: was ? gedcomName(was) : "", note: "the research: the name it shows the person by", text: said(was) },
|
|
4006
|
+
{ source: source.id, value: target ? gedcomName(target) : "", note: "the user's edit", text: said(target) },
|
|
4007
|
+
], undefined, undefined, true);
|
|
4008
|
+
break;
|
|
4009
|
+
}
|
|
4010
|
+
markPrimaryName(tree, owner, target ? gedcomName(target) : undefined, `the user's edit: ${reason}`);
|
|
4011
|
+
applied.push({ do: "name.mark", id: owner, before: was ? gedcomName(was) : "" });
|
|
4012
|
+
break;
|
|
4013
|
+
}
|
|
3949
4014
|
case "name.title": {
|
|
3950
4015
|
if (!owner || !c.title)
|
|
3951
4016
|
break;
|
|
@@ -4274,6 +4339,20 @@ function claimValue(f) {
|
|
|
4274
4339
|
* The title of a conflict of the user's edit, in the research's language — the Strom app shows it in what the research
|
|
4275
4340
|
* knows (found on Mac: "Petr Svoboda: SEX — U × F" in a Czech research): "Petr Svoboda: Pohlaví — neznámé × žena".
|
|
4276
4341
|
*/
|
|
4342
|
+
/**
|
|
4343
|
+
* The research's name of a person the app's view of the name they are shown by names (SPerson.primary): "birth" the
|
|
4344
|
+
* name of their identity, "married|<key>" their name of that key (not the identity's), "none" null; undefined where the
|
|
4345
|
+
* research has no such name.
|
|
4346
|
+
*/
|
|
4347
|
+
function nameOfView(p, view) {
|
|
4348
|
+
if (view === "none")
|
|
4349
|
+
return null;
|
|
4350
|
+
const first = primaryName(p);
|
|
4351
|
+
if (view === "birth")
|
|
4352
|
+
return first;
|
|
4353
|
+
const key = view.startsWith("married|") ? view.slice("married|".length) : undefined;
|
|
4354
|
+
return key === undefined ? undefined : p.names.find((n) => n !== first && nameKey(gedcomName(n)) === key);
|
|
4355
|
+
}
|
|
4277
4356
|
function editTitle(who, what, ours, theirs) {
|
|
4278
4357
|
return `${who.replace(/\//g, "").replace(/\s+/g, " ").trim()}: ${what.charAt(0).toUpperCase()}${what.slice(1)} — ${ours} × ${theirs}`;
|
|
4279
4358
|
}
|
|
@@ -4363,6 +4442,19 @@ export function takeSide(tree, x, side, reason) {
|
|
|
4363
4442
|
setTitle(tree, p.id, part, user.value, from, `the user's edit (${x.id}): ${reason}`);
|
|
4364
4443
|
return `${p.id}: ${titledName(primaryName(tree.get(p.id)))}`;
|
|
4365
4444
|
}
|
|
4445
|
+
// the name the user chose in the Strom app to show the person by (spec 024): marked, or none marked
|
|
4446
|
+
if (x.primary) {
|
|
4447
|
+
const user = x.claims.find((c) => c.note === "the user's edit");
|
|
4448
|
+
const p = x.subject[0] ? tree.get(x.subject[0]) : undefined;
|
|
4449
|
+
if (!p || p.retracted || !user)
|
|
4450
|
+
throw new UsageError(`the person of ${x.id} is no longer in the research`, { hint: `strom conflict show ${x.id}` });
|
|
4451
|
+
const target = user.value ? p.names.find((n) => sameWords(n, parseName(user.value))) : null;
|
|
4452
|
+
if (target === undefined)
|
|
4453
|
+
throw new UsageError(`${p.id} has no name ${user.value} any more`, { hint: `strom person show ${p.id}` });
|
|
4454
|
+
if (target ? !target.primary : p.names.some((n) => n.primary))
|
|
4455
|
+
markPrimaryName(tree, p.id, target ? gedcomName(target) : undefined, `the user's edit (${x.id}): ${reason}`);
|
|
4456
|
+
return `${p.id} shown as ${gedcomName(currentName(tree.get(p.id)))}`;
|
|
4457
|
+
}
|
|
4366
4458
|
// a name or a sex the user gave in the Strom app (found on Mac: --take refused for them, only for facts and parents)
|
|
4367
4459
|
if (!x.edit && (x.fact === "NAME" || x.fact === "SEX")) {
|
|
4368
4460
|
const owner = x.subject[0];
|
|
@@ -4378,7 +4470,7 @@ export function takeSide(tree, x, side, reason) {
|
|
|
4378
4470
|
const words = (v) => foldText(v.replace(/\//g, " ")).replace(/\s+/g, " ").trim();
|
|
4379
4471
|
const kept = p.names.find((n) => words(`${n.given} ${n.surname}`) === words(user.value));
|
|
4380
4472
|
const from = user.source && tree.get(user.source)?.type === "source" && !kept?.citations?.some((c) => c.source === user.source) ? { source: user.source, locator: "name" } : undefined;
|
|
4381
|
-
const { name } = addName(tree, p.id, { name: user.value,
|
|
4473
|
+
const { name } = addName(tree, p.id, { name: user.value, first: true, ...(from ? { citation: from } : {}) });
|
|
4382
4474
|
return `${p.id} shown as ${gedcomName(name)}`;
|
|
4383
4475
|
}
|
|
4384
4476
|
if (!x.edit)
|
|
@@ -4415,11 +4507,11 @@ function parentsText(tree, incoming, keys) {
|
|
|
4415
4507
|
};
|
|
4416
4508
|
return keys.map(shown).join(" & ") || "?";
|
|
4417
4509
|
}
|
|
4418
|
-
function conflictOfEdit(tree, applied, owner, fact, title, claims, edit, parents) {
|
|
4510
|
+
function conflictOfEdit(tree, applied, owner, fact, title, claims, edit, parents, primary) {
|
|
4419
4511
|
const [ours, theirs] = claims;
|
|
4420
4512
|
const open = tree
|
|
4421
4513
|
.list("conflict")
|
|
4422
|
-
.find((x) => x.state === "open" && x.fact === fact && x.subject[0] === owner && x.claims.length === 2 && x.claims[0].note === ours.note && x.claims[0].value === ours.value && x.claims[1].note === theirs.note);
|
|
4514
|
+
.find((x) => x.state === "open" && x.fact === fact && !!x.primary === !!primary && x.subject[0] === owner && x.claims.length === 2 && x.claims[0].note === ours.note && x.claims[0].value === ours.value && x.claims[1].note === theirs.note);
|
|
4423
4515
|
if (open) {
|
|
4424
4516
|
if (open.claims[1].value === theirs.value)
|
|
4425
4517
|
return { open: open.id };
|
|
@@ -4430,7 +4522,7 @@ function conflictOfEdit(tree, applied, owner, fact, title, claims, edit, parents
|
|
|
4430
4522
|
applied.push({ do: "conflict.edit", id: open.id, before: JSON.stringify({ title: open.title, claims: open.claims }) });
|
|
4431
4523
|
return { was: open.claims[1].value };
|
|
4432
4524
|
}
|
|
4433
|
-
const x = create(tree, "conflict", { title: title.slice(0, 200), fact, subject: [owner], claims, ...(edit ? { edit } : {}), ...(parents ? { parents } : {}), state: "open" }, (id) => `+${id} conflict "${title.slice(0, 60)}"`, [owner]);
|
|
4525
|
+
const x = create(tree, "conflict", { title: title.slice(0, 200), fact, subject: [owner], claims, ...(edit ? { edit } : {}), ...(parents ? { parents } : {}), ...(primary ? { primary } : {}), state: "open" }, (id) => `+${id} conflict "${title.slice(0, 60)}"`, [owner]);
|
|
4434
4526
|
applied.push({ do: "conflict.add", id: x.id });
|
|
4435
4527
|
return {};
|
|
4436
4528
|
}
|
|
@@ -4643,6 +4735,16 @@ export function undoSync(tree, input) {
|
|
|
4643
4735
|
});
|
|
4644
4736
|
break;
|
|
4645
4737
|
}
|
|
4738
|
+
case "name.mark": {
|
|
4739
|
+
// the name the person was shown by before the user's choice (none: no mark)
|
|
4740
|
+
const p = tree.get(a.id);
|
|
4741
|
+
const was = String(a.before ?? "");
|
|
4742
|
+
const back = was ? p?.names.find((n) => sameWords(n, parseName(was))) : null;
|
|
4743
|
+
if (!p || back === undefined || (back === null ? !p.names.some((n) => n.primary) : back.primary))
|
|
4744
|
+
break;
|
|
4745
|
+
markPrimaryName(tree, a.id, back ? gedcomName(back) : undefined, reason);
|
|
4746
|
+
break;
|
|
4747
|
+
}
|
|
4646
4748
|
case "name.title": {
|
|
4647
4749
|
// the title as it was, and the citation of the sync it added taken off
|
|
4648
4750
|
const b = JSON.parse(String(a.before ?? "{}"));
|
package/dist/core/tree.js
CHANGED
|
@@ -29,7 +29,7 @@ export const TREE_FILE = "strom.json";
|
|
|
29
29
|
export const SEAL_FOREIGN = "seal.foreign";
|
|
30
30
|
// between releases the candidate being made (a bridge run from the sources says what it is: the Strom app goes by it);
|
|
31
31
|
// at a release the release's own (package.json)
|
|
32
|
-
export const VERSION = "1.
|
|
32
|
+
export const VERSION = "1.23.0-beta.1";
|
|
33
33
|
const GITIGNORE = `# Strom Research — local state and large files are not versioned
|
|
34
34
|
.strom/
|
|
35
35
|
/media/
|
package/dist/core/validate.js
CHANGED
|
@@ -200,8 +200,12 @@ function checkPerson(p, out) {
|
|
|
200
200
|
out.push({ path: `names[${i}].${t}`, message: "must be a title (a string, not empty)" });
|
|
201
201
|
if (n.kind !== undefined && !NAME_KINDS.includes(n.kind))
|
|
202
202
|
out.push({ path: `names[${i}].kind`, message: `must be one of ${NAME_KINDS.join(", ")}` });
|
|
203
|
+
if (n.primary !== undefined && n.primary !== true)
|
|
204
|
+
out.push({ path: `names[${i}].primary`, message: "must be true, or left out" });
|
|
203
205
|
checkCitations(n.citations, `names[${i}].citations`, out);
|
|
204
206
|
});
|
|
207
|
+
if (Array.isArray(p.names) && p.names.filter((n) => isObj(n) && n.primary).length > 1)
|
|
208
|
+
out.push({ path: "names", message: "at most one name is the one the person is shown by (primary)" });
|
|
205
209
|
if (p.retractedNames !== undefined) {
|
|
206
210
|
if (!Array.isArray(p.retractedNames))
|
|
207
211
|
out.push({ path: "retractedNames", message: "must be an array" });
|
package/dist/gedcom/export.js
CHANGED
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
import { GedWriter, clipChars, lineValue } from "./lines.js";
|
|
28
28
|
import { labels, RELA } from "./labels.js";
|
|
29
29
|
import { linkInTree, recordedLinks } from "../core/hypolinks.js";
|
|
30
|
-
import { birthEvent, claimText, conflictTitle, coupleSides, displayName, familySides, formatName, gedcomTitledName, noName, preferredOrder, primaryName, relationTo } from "../core/people.js";
|
|
30
|
+
import { birthEvent, claimText, conflictTitle, coupleSides, currentName, displayName, familySides, formatName, gedcomTitledName, noName, preferredOrder, primaryName, relationTo } from "../core/people.js";
|
|
31
31
|
import { foldText } from "../core/text.js";
|
|
32
32
|
import { dateYears } from "../core/gdate.js";
|
|
33
33
|
import { humanAge, isGedcomAge, normalizeAge } from "../core/age.js";
|
|
@@ -258,7 +258,17 @@ export function exportGedcom(tree, opts = {}) {
|
|
|
258
258
|
w.record(x(p.id), "INDI");
|
|
259
259
|
const primary = primaryName(p);
|
|
260
260
|
const nameQuotes = [];
|
|
261
|
-
|
|
261
|
+
// the name the person is shown by now (Name.primary): first for any program (GEDCOM 7: the preferred name first);
|
|
262
|
+
// the Strom file keeps the name of their identity first (the app's birth surname) and says the mark — a married
|
|
263
|
+
// name marked last of them (the app keeps one name after a marriage: the last), so even an app that reads no mark
|
|
264
|
+
// shows it
|
|
265
|
+
const current = currentName(p);
|
|
266
|
+
const rest = p.names.filter((n) => n !== primary && n !== current);
|
|
267
|
+
const order = current === primary ? [primary, ...rest]
|
|
268
|
+
: strict ? [current, primary, ...rest]
|
|
269
|
+
: current.kind === "married" ? [primary, ...rest, current]
|
|
270
|
+
: [primary, ...p.names.filter((n) => n !== primary)];
|
|
271
|
+
for (const n of order) {
|
|
262
272
|
// a name with a title says it in the line (any program shows it) and spells its parts out below: NPFX, GIVN, SURN,
|
|
263
273
|
// NSFX — for any program (GEDCOM 5.5.1's own tags), for the Strom app from its 3.10 (titles; an older one reads
|
|
264
274
|
// the line). A part holding a comma gets no GIVN / SURN (GEDCOM reads commas there as a list); the line has it.
|
|
@@ -280,6 +290,8 @@ export function exportGedcom(tree, opts = {}) {
|
|
|
280
290
|
// "birth" says something only next to another name
|
|
281
291
|
if (n.kind && (n.kind !== "birth" || p.names.length > 1))
|
|
282
292
|
w.line(2, "TYPE", NAME_TYPE[n.kind]);
|
|
293
|
+
if (!strict && n.primary)
|
|
294
|
+
w.line(2, "_STROM_PRIMARY", "Y");
|
|
283
295
|
for (const c of n.citations ?? []) {
|
|
284
296
|
const q = citation(2, c);
|
|
285
297
|
if (q)
|
package/dist/gedcom/validate.js
CHANGED
|
@@ -5,7 +5,7 @@ import { normalizeDate } from "../core/gdate.js";
|
|
|
5
5
|
import { isGedcomAge } from "../core/age.js";
|
|
6
6
|
import { MAX_LINE_BYTES } from "./lines.js";
|
|
7
7
|
const LINE = /^(\d{1,2}) (?:(@[^@ ]+@) )?([A-Za-z0-9_]+)(?: (.*))?$/;
|
|
8
|
-
const EVENT_DETAIL = ["TYPE", "DATE", "PLAC", "AGE", "SOUR", "NOTE", "ASSO", "_WITN", "CAUS", "ADDR", "AGNC", "RELI", "OBJE", "HUSB", "WIFE"];
|
|
8
|
+
const EVENT_DETAIL = ["TYPE", "DATE", "PLAC", "AGE", "SOUR", "NOTE", "ASSO", "_WITN", "CAUS", "ADDR", "AGNC", "RELI", "OBJE", "HUSB", "WIFE", "_STROM_STATUS"];
|
|
9
9
|
const INDI_EVENTS = [
|
|
10
10
|
"BIRT", "DEAT", "BAPM", "CHR", "CHRA", "CONF", "FCOM", "BARM", "BASM", "ORDN", "EDUC", "GRAD", "OCCU", "RESI", "EMIG", "IMMI",
|
|
11
11
|
"NATU", "RELI", "TITL", "NATI", "ADOP", "WILL", "PROB", "BURI", "CREM", "CENS", "EVEN", "RETI", "BLES", "CAST", "DSCR", "IDNO",
|
|
@@ -14,7 +14,7 @@ const INDI_EVENTS = [
|
|
|
14
14
|
const FAM_EVENTS = ["MARR", "DIV", "MARB", "MARC", "MARL", "MARS", "ANUL", "DIVF", "ENGA", "CENS", "RESI", "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", "_STROM_LINKS", "_STROM_ASOF"],
|
|
17
|
+
HEAD: ["SOUR", "DEST", "DATE", "SUBM", "SUBN", "FILE", "COPR", "GEDC", "CHAR", "LANG", "PLAC", "NOTE", "_STROM_TREE", "_STROM_HEAD", "_STROM_LINKS", "_STROM_ASOF", "_STROM_MODE"],
|
|
18
18
|
"HEAD.SOUR": ["VERS", "NAME", "CORP", "DATA"],
|
|
19
19
|
"HEAD._STROM_LINKS": ["_SCHEME"],
|
|
20
20
|
"HEAD.GEDC": ["VERS", "FORM"],
|
|
@@ -33,14 +33,16 @@ const CHILDREN = {
|
|
|
33
33
|
"_STROM_EDGE._JOIN": ["_VAR", "_ISLAND", "_HELD"],
|
|
34
34
|
// a value of a conflict the app decides by side: its side, a sex as M, F or U
|
|
35
35
|
_STROM_VALUE: ["SOUR", "_STROM_SIDE", "_STROM_RAW"],
|
|
36
|
-
"INDI.NAME": ["TYPE", "GIVN", "SURN", "NPFX", "NSFX", "NICK", "SPFX", "SOUR", "NOTE"],
|
|
36
|
+
"INDI.NAME": ["TYPE", "GIVN", "SURN", "NPFX", "NSFX", "NICK", "SPFX", "SOUR", "NOTE", "_STROM_PRIMARY"],
|
|
37
37
|
"INDI.FAMC": ["PEDI", "NOTE"],
|
|
38
38
|
"INDI.FAMS": ["NOTE"],
|
|
39
39
|
"FAM.CHIL": ["_FREL", "_MREL"],
|
|
40
40
|
"INDI.ASSO": ["RELA", "SOUR", "NOTE"],
|
|
41
|
-
FAM: ["HUSB", "WIFE", "CHIL", "NOTE", "SOUR", "REFN", "OBJE", "RIN", "CHAN", "_STORY", ...FAM_EVENTS],
|
|
41
|
+
FAM: ["HUSB", "WIFE", "CHIL", "NOTE", "SOUR", "REFN", "OBJE", "RIN", "CHAN", "_STORY", "_STAT", "_STROM_NO_COUPLE", ...FAM_EVENTS],
|
|
42
42
|
// the Strom profile also writes PAGE on the record: where in the book the source is (the Strom app's reference)
|
|
43
|
-
SOUR: ["TITL", "AUTH", "ABBR", "PUBL", "TEXT", "REPO", "NOTE", "REFN", "DATA", "OBJE", "RIN", "CHAN"],
|
|
43
|
+
SOUR: ["TITL", "AUTH", "ABBR", "PUBL", "TEXT", "REPO", "NOTE", "REFN", "DATA", "OBJE", "RIN", "CHAN", "_STROM_READ", "_STROM_VERIFIED", "_STROM_READING", "_STROM_IMG_OMITTED"],
|
|
44
|
+
"SOUR._STROM_READING": ["_STROM_ENTRY"],
|
|
45
|
+
"SOUR._STROM_IMG_OMITTED": ["_STROM_CLIP"],
|
|
44
46
|
"SOUR.REPO": ["CALN", "NOTE"],
|
|
45
47
|
REPO: ["NAME", "ADDR", "NOTE", "REFN", "RIN", "CHAN"],
|
|
46
48
|
SUBM: ["NAME", "ADDR", "LANG", "NOTE", "RIN", "CHAN"],
|
|
@@ -73,6 +75,12 @@ const EXTENSIONS = new Set([
|
|
|
73
75
|
"_VAR", "_LINK", "_PERS", "_FAM", "_PAR", "_CHOSEN", "_INTREE",
|
|
74
76
|
// a source's excerpts the file leaves out, and why
|
|
75
77
|
"_STROM_IMG_OMITTED",
|
|
78
|
+
// the name a person is shown by; a couple's state; parents who are no couple; an archive
|
|
79
|
+
"_STROM_PRIMARY", "_STAT", "_STROM_NO_COUPLE", "_STROM_MODE",
|
|
80
|
+
// who read a source, the user's verification, the reading it is an entry of; how sure a fact is
|
|
81
|
+
"_STROM_READ", "_STROM_VERIFIED", "_STROM_READING", "_STROM_ENTRY", "_STROM_STATUS",
|
|
82
|
+
// an excerpt's original by its content, and how its picture lies
|
|
83
|
+
"_STROM_SHA", "_STROM_ORIENT",
|
|
76
84
|
]);
|
|
77
85
|
/** Context of the children of the last tag in `anc` (the ancestors of a line). */
|
|
78
86
|
function contextOf(anc) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "strom-research",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.23.0-beta.1",
|
|
4
4
|
"description": "Genealogical research toolkit for AI coding agents",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -57,5 +57,8 @@
|
|
|
57
57
|
"devDependencies": {
|
|
58
58
|
"@types/node": "^22.20.4",
|
|
59
59
|
"typescript": "^7.0.2"
|
|
60
|
+
},
|
|
61
|
+
"publishConfig": {
|
|
62
|
+
"tag": "beta"
|
|
60
63
|
}
|
|
61
64
|
}
|