strom-research 1.18.0-beta.3 → 1.18.0-beta.5
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 +3 -2
- package/CHANGELOG.de.md +4 -2
- package/CHANGELOG.md +4 -2
- package/assets/lang/cs.json +12 -0
- package/assets/lang/de.json +12 -0
- package/dist/brief/brief.js +6 -1
- package/dist/cli/ui.js +3 -0
- package/dist/commands/document.js +37 -15
- package/dist/commands/intake.js +6 -4
- package/dist/commands/output.js +4 -2
- package/dist/commands/readings.js +18 -6
- package/dist/commands/sync.js +12 -7
- package/dist/core/document.js +4 -0
- package/dist/core/live-read.js +24 -2
- package/dist/core/live.js +15 -4
- package/dist/core/phrases.js +9 -0
- package/dist/core/readsync.js +269 -0
- package/dist/core/review.js +11 -1
- package/dist/core/stromapp.js +21 -0
- package/dist/core/sync.js +181 -13
- package/dist/core/tree.js +1 -1
- package/dist/gedcom/export.js +6 -0
- package/package.json +1 -1
package/CHANGELOG.cs.md
CHANGED
|
@@ -7,7 +7,7 @@ Změny každé verze ve zkratce. Úplné poznámky a instalační řádky jsou n
|
|
|
7
7
|
|
|
8
8
|
## 1.18.0
|
|
9
9
|
|
|
10
|
-
*Připravuje se: bety do 1.18.0-beta.
|
|
10
|
+
*Připravuje se: bety do 1.18.0-beta.5. Oddíl bude úplný s vydáním.*
|
|
11
11
|
|
|
12
12
|
- **Fotky z telefonu nastojato.** Fotka otočená podle údaje EXIF se čte tak, jak se má dívat, nastojato; místa na ní
|
|
13
13
|
vyznačená (výřezy, poloviny dvoustrany, uložená část) jsou místa obrázku, jak se ukazuje. Fotka sama zůstává, jak
|
|
@@ -20,7 +20,8 @@ Změny každé verze ve zkratce. Úplné poznámky a instalační řádky jsou n
|
|
|
20
20
|
z matriky, dopis, listinu, náhrobek, rub fotky: přepis slovo od slova, překlad, význam starých slov a zkratek
|
|
21
21
|
a osoby, které jmenuje, i s tím, kdo ze stromu to může být. Nic se nedomýšlí: jméno, které v dokumentu není,
|
|
22
22
|
vypadne — i příjmení, nikdy se nebere od otce ani z jiné rodiny —, co nejde přečíst, je označené; druhý čtenář
|
|
23
|
-
může číst naslepo. Do stromu se z toho nic nezapíše samo.
|
|
23
|
+
může číst naslepo. Do stromu se z toho nic nezapíše samo. Dokument už přečtený to o sobě řekne, takže se omylem
|
|
24
|
+
nečte podruhé. Dokument čtený znovu se ukazuje pod názvem svého souboru, nikdy pod kódem.
|
|
24
25
|
- **Fotky z telefonu.** `strom phone on`, které zapíná jen člověk, pustí k počítači telefon v domácí síti (Wi-Fi
|
|
25
26
|
nebo kabel — nikdy mobilní síť ani VPN). Telefon se jednou spáruje přes QR kód; jeho stránka pak fotí nebo vybírá
|
|
26
27
|
z galerie a posílá fotky celé do stromu výzkumu, s poznámkou a osobou, kterou ukazují — stejně jako „Přidat
|
package/CHANGELOG.de.md
CHANGED
|
@@ -7,7 +7,7 @@ Die Änderungen jeder Version in Kürze. Die vollständigen Hinweise und die Ins
|
|
|
7
7
|
|
|
8
8
|
## 1.18.0
|
|
9
9
|
|
|
10
|
-
*In Vorbereitung: die Betas bis 1.18.0-beta.
|
|
10
|
+
*In Vorbereitung: die Betas bis 1.18.0-beta.5. Der Abschnitt ist mit dem Release vollständig.*
|
|
11
11
|
|
|
12
12
|
- **Handyfotos aufrecht.** Ein Foto, das nach seiner EXIF-Ausrichtung gedreht ist, wird gelesen, wie es gesehen
|
|
13
13
|
werden soll, aufrecht; die darauf markierten Stellen (Ausschnitte, die Hälften einer Doppelseite, ein gespeicherter
|
|
@@ -21,7 +21,9 @@ Die Änderungen jeder Version in Kürze. Die vollständigen Hinweise und die Ins
|
|
|
21
21
|
Abschrift Wort für Wort, ihre Übersetzung, was alte Wörter und Abkürzungen bedeuten, und die genannten Personen,
|
|
22
22
|
samt wem im Stammbaum sie entsprechen können. Nichts wird ergänzt: ein Name, der nicht im Dokument steht, fällt
|
|
23
23
|
weg — auch ein Nachname, nie vom Vater oder aus einer anderen Familie genommen —, Unleserliches wird markiert; ein
|
|
24
|
-
zweiter Leser kann blind lesen. In den Stammbaum gelangt nichts von selbst.
|
|
24
|
+
zweiter Leser kann blind lesen. In den Stammbaum gelangt nichts von selbst. Ein schon gelesenes Dokument sagt es,
|
|
25
|
+
so wird es nicht versehentlich ein zweites Mal gelesen. Ein erneut gelesenes Dokument wird unter dem Namen seiner
|
|
26
|
+
Datei gezeigt, nie unter einem Code.
|
|
25
27
|
- **Fotos vom Handy.** `strom phone on`, das nur ein Mensch einschaltet, lässt ein Handy im Heimnetz (WLAN oder
|
|
26
28
|
Kabel — nie ein Mobilfunknetz oder ein VPN) zu diesem Computer. Das Handy wird einmal per QR-Code gekoppelt; seine
|
|
27
29
|
Seite fotografiert dann oder wählt aus der Galerie und schickt die Fotos ganz in einen Stammbaum der Forschung, mit
|
package/CHANGELOG.md
CHANGED
|
@@ -7,7 +7,7 @@ The changes of each version in short. The full notes and the install lines are o
|
|
|
7
7
|
|
|
8
8
|
## 1.18.0
|
|
9
9
|
|
|
10
|
-
*In preparation: the betas up to 1.18.0-beta.
|
|
10
|
+
*In preparation: the betas up to 1.18.0-beta.5. The section is complete with the release.*
|
|
11
11
|
|
|
12
12
|
- **Phone photos upright.** A photo turned by its EXIF orientation is read as it is meant to be seen, upright; the
|
|
13
13
|
places marked on it (excerpts, the halves of a spread, a cut-out part) are of the picture as it is shown. The
|
|
@@ -20,7 +20,9 @@ The changes of each version in short. The full notes and the install lines are o
|
|
|
20
20
|
entry, a letter, a deed, a gravestone, the back of a photo: the transcript word for word, its translation, what its
|
|
21
21
|
old words and abbreviations mean, and the people it names, with who of the tree they may be. Nothing is filled in:
|
|
22
22
|
a name the document does not have is left out — a surname too, never taken from a father or another family —,
|
|
23
|
-
what cannot be read is marked; a second reader can read it blind. Nothing of it enters the tree by itself.
|
|
23
|
+
what cannot be read is marked; a second reader can read it blind. Nothing of it enters the tree by itself. A
|
|
24
|
+
document read already says so, so it is not read a second time by mistake. A document read again is shown under
|
|
25
|
+
the name of its file, never a code.
|
|
24
26
|
- **Photos from a phone.** `strom phone on`, turned on by the person alone, lets a phone on the home network (Wi-Fi
|
|
25
27
|
or a cable — never a mobile network or a VPN) reach this computer. The phone is paired once by its QR code; its
|
|
26
28
|
page then takes photos or picks them from the gallery and sends them whole into a tree of the research, with a
|
package/assets/lang/cs.json
CHANGED
|
@@ -674,6 +674,15 @@
|
|
|
674
674
|
"sync.reading.note": "Uživatel přečetl tento záznam v aplikaci Strom jinak ({source}); čtení výzkumu platí, dokud se záznam nepřečte znovu:",
|
|
675
675
|
"sync.reading.page": "strana",
|
|
676
676
|
"sync.reading.text": "přepis",
|
|
677
|
+
"read.note.source.edited": "Přepis byl upraven v aplikaci Strom; čtení {read} má: {text}",
|
|
678
|
+
"read.note.edited": "Přepis byl upraven v aplikaci Strom: vodítko, dokud se záznam nepřečte ({read})",
|
|
679
|
+
"read.note.one": "Přečetl jeden čtenář ({read}): možné do druhého, slepého čtení a vlastního pohledu výzkumu na záznam",
|
|
680
|
+
"read.note.differ": "Čtenáři {read} přečetli jinak: {list} — vodítko, dokud se záznam nepřečte",
|
|
681
|
+
"read.note.absent": "Čtení {read} to tak neuvádí: vodítko, dokud se záznam nepřečte",
|
|
682
|
+
"read.note.uncertain": "Čtenář {read} si tím nebyl jistý: vodítko, dokud se záznam nepřečte",
|
|
683
|
+
"read.verify.what": "Ověřit čtení dokumentu u osoby {name}: {list}",
|
|
684
|
+
"read.verify.why": "údaje stojí na čtení čtenářů stromu ({read}, potvrzeném v aplikaci Strom, {source}), které ještě není jisté: jeden čtenář, rozdílná čtení, nebo ručně upravený přepis; druhé, slepé čtení a vlastní pohled výzkumu na záznam je doloží, nebo ukáže chybu",
|
|
685
|
+
"read.verify.done": "po jednom čtenáři druhý, slepý (strom document again {read} --readers 2) a zápis prohlédnutý výzkumem samotným (strom source show {source}, jeho snímek): čtení se shodují → pravděpodobné, prokázané až po tomto pohledu a jen u primární informace; liší se → rozpor s oběma čteními",
|
|
677
686
|
"archive.held": "odloženo: výzkum je Archiv Stromu (data přicházejí z aplikace Strom); do fronty se vrátí, až se zapne práce s agentem",
|
|
678
687
|
"archive.removed": "odebráno v aplikaci Strom",
|
|
679
688
|
"archive.wait": "Rozhodnutí o stahování z archivu {title}",
|
|
@@ -1172,6 +1181,8 @@
|
|
|
1172
1181
|
"ui.sync.do.family.cite.read": "bude ho citovat rodina (její sňatek), na čtení záznamu z aplikace",
|
|
1173
1182
|
"ui.sync.do.source.new": "pramen výzkumu; přepis z aplikace je vodítko, dokud záznam nepřečte výzkum",
|
|
1174
1183
|
"ui.sync.do.source.new.read": "pramen výzkumu; přepis z aplikace platí jako čtení záznamu",
|
|
1184
|
+
"ui.sync.do.source.new.reading": "pramen výzkumu z jeho vlastního čtení dokumentu (snímek a výřez zápisu): údaje tak jisté, jak je čtenáři přečetli",
|
|
1185
|
+
"ui.sync.do.source.new.reading.edited": "pramen výzkumu z jeho čtení dokumentu, přepis upravený v aplikaci: údaje jsou vodítko, dokud záznam nepřečte výzkum",
|
|
1175
1186
|
"ui.sync.do.source.changed": "převezme se: je to čtení z aplikace",
|
|
1176
1187
|
"ui.sync.do.source.changed.read": "převezme se: je to čtení z aplikace a platí",
|
|
1177
1188
|
"ui.sync.do.source.reading": "uloží se vedle čtení výzkumu; výzkum záznam přečte znovu",
|
|
@@ -1180,6 +1191,7 @@
|
|
|
1180
1191
|
"ui.sync.do.fact.cite": "údaj výzkumu ho bude citovat (vodítko, dokud záznam nepřečte výzkum)",
|
|
1181
1192
|
"ui.sync.do.fact.cite.read": "údaj výzkumu ho bude citovat a stojí na čtení záznamu z aplikace",
|
|
1182
1193
|
"ui.sync.do.reading": "přidá se na čtení záznamu z aplikace",
|
|
1194
|
+
"ui.sync.do.reading.document": "přidá se tak jistý, jak ho čtenáři dokumentu přečetli (jeden čtenář: možné, dva ve shodě: pravděpodobné, rozdílně: vodítko)",
|
|
1183
1195
|
"ui.sync.family.new": "{n}. nová rodina: {who}{kids}",
|
|
1184
1196
|
"ui.sync.child.new": "{n}. {who}: dítě — {child}",
|
|
1185
1197
|
"ui.sync.child.gone": "{n}. {who}: už to není jejich dítě — {child}",
|
package/assets/lang/de.json
CHANGED
|
@@ -674,6 +674,15 @@
|
|
|
674
674
|
"sync.reading.note": "Der Benutzer hat diesen Eintrag in der Strom-App anders gelesen ({source}); die Lesung der Forschung gilt, bis er erneut gelesen ist:",
|
|
675
675
|
"sync.reading.page": "Seite",
|
|
676
676
|
"sync.reading.text": "Abschrift",
|
|
677
|
+
"read.note.source.edited": "Die Abschrift wurde in der Strom-App geändert; die Lesung {read} hat: {text}",
|
|
678
|
+
"read.note.edited": "Die Abschrift wurde in der Strom-App geändert: ein Hinweis, bis der Eintrag gelesen ist ({read})",
|
|
679
|
+
"read.note.one": "Von einem Leser gelesen ({read}): möglich bis zu einer zweiten, blinden Lesung und dem eigenen Blick der Forschung auf den Eintrag",
|
|
680
|
+
"read.note.differ": "Die Leser von {read} lasen es verschieden: {list} — ein Hinweis, bis der Eintrag gelesen ist",
|
|
681
|
+
"read.note.absent": "Die Lesung {read} sagt es nicht so: ein Hinweis, bis der Eintrag gelesen ist",
|
|
682
|
+
"read.note.uncertain": "Der Leser von {read} war sich dessen nicht sicher: ein Hinweis, bis der Eintrag gelesen ist",
|
|
683
|
+
"read.verify.what": "Die Lesung eines Dokuments für {name} prüfen: {list}",
|
|
684
|
+
"read.verify.why": "die Angaben stehen auf einer Lesung der Leser von strom ({read}, in der Strom-App bestätigt, {source}), die noch nicht sicher ist: ein Leser, verschiedene Lesungen oder die Abschrift von Hand geändert; eine zweite, blinde Lesung und der eigene Blick der Forschung auf den Eintrag belegen sie oder zeigen einen Fehler",
|
|
685
|
+
"read.verify.done": "nach einem Leser ein zweiter, blinder (strom document again {read} --readers 2), und der Eintrag von der Forschung selbst angesehen (strom source show {source}, sein Bild): die Lesungen stimmen überein → wahrscheinlich, belegt erst nach diesem Blick und nur bei primärer Information; sie weichen ab → ein Widerspruch mit beiden Lesungen",
|
|
677
686
|
"archive.held": "zurückgestellt: die Forschung ist ein Strom-Archiv (die Daten kommen aus der Strom-App); zurück in der Warteschlange, wenn die Arbeit mit einem Agenten eingeschaltet wird",
|
|
678
687
|
"archive.removed": "in der Strom-App entfernt",
|
|
679
688
|
"archive.wait": "Entscheidung über das Herunterladen aus dem Archiv {title}",
|
|
@@ -1172,6 +1181,8 @@
|
|
|
1172
1181
|
"ui.sync.do.family.cite.read": "die Familie (ihre Heirat) zitiert sie, auf die eigene Lesung des Eintrags",
|
|
1173
1182
|
"ui.sync.do.source.new": "eine Quelle der Forschung; die eigene Abschrift ist ein Hinweis, bis die Forschung den Eintrag liest",
|
|
1174
1183
|
"ui.sync.do.source.new.read": "eine Quelle der Forschung; die eigene Abschrift gilt als Lesung des Eintrags",
|
|
1184
|
+
"ui.sync.do.source.new.reading": "eine Quelle der Forschung aus ihrer eigenen Lesung des Dokuments (Bild und Ausschnitt des Eintrags): die Angaben so sicher, wie die Leser sie lasen",
|
|
1185
|
+
"ui.sync.do.source.new.reading.edited": "eine Quelle der Forschung aus ihrer Lesung des Dokuments, die Abschrift in der App geändert: die Angaben ein Hinweis, bis die Forschung den Eintrag liest",
|
|
1175
1186
|
"ui.sync.do.source.changed": "übernommen: es ist die eigene Lesung",
|
|
1176
1187
|
"ui.sync.do.source.changed.read": "übernommen: es ist die eigene Lesung, und sie gilt",
|
|
1177
1188
|
"ui.sync.do.source.reading": "neben der Lesung der Forschung festgehalten; die Forschung liest den Eintrag erneut",
|
|
@@ -1180,6 +1191,7 @@
|
|
|
1180
1191
|
"ui.sync.do.fact.cite": "die Angabe der Forschung zitiert sie (ein Hinweis, bis die Forschung den Eintrag liest)",
|
|
1181
1192
|
"ui.sync.do.fact.cite.read": "die Angabe der Forschung zitiert sie und steht auf der eigenen Lesung des Eintrags",
|
|
1182
1193
|
"ui.sync.do.reading": "hinzugefügt auf der eigenen Lesung des Eintrags",
|
|
1194
|
+
"ui.sync.do.reading.document": "wird so sicher hinzugefügt, wie die Leser des Dokuments es lasen (ein Leser: möglich, zwei übereinstimmend: wahrscheinlich, verschieden: ein Hinweis)",
|
|
1183
1195
|
"ui.sync.family.new": "{n}. eine neue Familie: {who}{kids}",
|
|
1184
1196
|
"ui.sync.child.new": "{n}. {who}: ein Kind — {child}",
|
|
1185
1197
|
"ui.sync.child.gone": "{n}. {who}: nicht mehr ihr Kind — {child}",
|
package/dist/brief/brief.js
CHANGED
|
@@ -22,6 +22,7 @@ import { clock, hostAllowed, hostState, REFUSED_FOREVER } from "../core/net.js";
|
|
|
22
22
|
import { archiveLevel } from "../core/terms.js";
|
|
23
23
|
import { onHost, permissionQuestions, setHosts } from "../core/archivewait.js";
|
|
24
24
|
import { runs, runsArg, shellArg } from "../cli/format.js";
|
|
25
|
+
import { readingsLine, readingsOf } from "../core/readsync.js";
|
|
25
26
|
import { commandSheet } from "./sheet.js";
|
|
26
27
|
import { foldText } from "../core/text.js";
|
|
27
28
|
import { subjectPeople } from "../core/records.js";
|
|
@@ -933,9 +934,13 @@ export function buildBrief(tree, opts) {
|
|
|
933
934
|
const file = inputPath(tree, i);
|
|
934
935
|
// all that strom input show says of it, so it needs no extra command (K6)
|
|
935
936
|
const small = !i.text && i.kind === "text" && file && (i.size ?? 0) < 20_000 && /\.(txt|md|csv)$/i.test(file) ? readSmall(file) : undefined;
|
|
937
|
+
// read already (strom document read): its result, the file not read again
|
|
938
|
+
const read = readingsLine(tree, i);
|
|
939
|
+
const readDone = readingsOf(tree, i).some((r) => r.state === "done");
|
|
936
940
|
return [
|
|
937
941
|
`${i.id} ${i.name} [${i.kind} · ${i.state}]${i.from ? ` from ${i.from}` : ""}${i.mime ? ` · ${i.mime}${i.size !== undefined ? ` ${Math.round(i.size / 1024)} kB` : ""}` : ""}`,
|
|
938
|
-
file ? `file: ${file}${i.kind === "document" || i.kind === "photo" ? " — open it and read it yourself" : ""}` : "",
|
|
942
|
+
file ? `file: ${file}${(i.kind === "document" || i.kind === "photo") && !readDone ? " — open it and read it yourself" : ""}` : "",
|
|
943
|
+
read ? `read: ${read}` : "",
|
|
939
944
|
i.batch ? `batch: ${i.batch}${i.path ? ` · ${i.path}` : ""}` : "",
|
|
940
945
|
i.persons?.length ? `of: ${i.persons.join(", ")}` : "",
|
|
941
946
|
i.sorted ? `sorted: ${i.sorted.as}${i.sorted.source ? ` → ${i.sorted.source}` : ""}${i.sorted.reason ? ` (${i.sorted.reason})` : ""}` : "",
|
package/dist/cli/ui.js
CHANGED
|
@@ -1073,6 +1073,8 @@ export const UI = {
|
|
|
1073
1073
|
"ui.sync.do.family.cite.read": "the family (its marriage) cites it, on the file's reading of the record",
|
|
1074
1074
|
"ui.sync.do.source.new": "a source of the research; the file's transcript is a lead until the research reads the record",
|
|
1075
1075
|
"ui.sync.do.source.new.read": "a source of the research; the file's transcript counts as a reading of the record",
|
|
1076
|
+
"ui.sync.do.source.new.reading": "a source of the research from its own reading of the document (its picture and the entry's excerpt): the facts as sure as its readers read them",
|
|
1077
|
+
"ui.sync.do.source.new.reading.edited": "a source of the research from its reading of the document, the transcript changed in the app: the facts a lead until the research reads the record",
|
|
1076
1078
|
"ui.sync.do.source.changed": "taken: it is the file's reading",
|
|
1077
1079
|
"ui.sync.do.source.changed.read": "taken: it is the file's reading, and it counts",
|
|
1078
1080
|
"ui.sync.do.source.reading": "kept beside the research's reading; the research reads the record again",
|
|
@@ -1081,6 +1083,7 @@ export const UI = {
|
|
|
1081
1083
|
"ui.sync.do.fact.cite": "the research's fact cites it (a lead on it until the research reads the record)",
|
|
1082
1084
|
"ui.sync.do.fact.cite.read": "the research's fact cites it and stands on the file's reading of the record",
|
|
1083
1085
|
"ui.sync.do.reading": "added on the file's reading of the record",
|
|
1086
|
+
"ui.sync.do.reading.document": "added as sure as the readers of the document read it (one reader: possible; two alike: probable; apart: a lead)",
|
|
1084
1087
|
"ui.sync.family.new": "{n}. a new family: {who}{kids}",
|
|
1085
1088
|
"ui.sync.child.new": "{n}. {who}: a child — {child}",
|
|
1086
1089
|
"ui.sync.child.gone": "{n}. {who}: no longer their child — {child}",
|
|
@@ -20,7 +20,7 @@ import { addReaders } from "../core/session.js";
|
|
|
20
20
|
import { READER_MINUTES_HELP } from "../core/reader.js";
|
|
21
21
|
import { stamp } from "../core/net.js";
|
|
22
22
|
import { readers } from "./readers.js";
|
|
23
|
-
import { alive, APP_READ_FROM, backBefore, beforeOf, buildResult, cancelPath, DOC_NEXT, DOC_PAGES, DOC_VIEWS, docPrompt, endState, FINAL_STATES, listReadings, loadState, newReadId, nextVersion, parseRegion, READ_ID, reportPath, resultPath, saveState, SMALL_PX, withoutDocNext, } from "../core/document.js";
|
|
23
|
+
import { alive, APP_READ_FROM, backBefore, beforeOf, buildResult, cancelPath, DOC_NEXT, DOC_PAGES, DOC_VIEWS, docPrompt, endState, FINAL_STATES, listReadings, loadState, newReadId, nextVersion, parseRegion, READ_ID, reportPath, resultPath, saveState, shownName, SMALL_PX, withoutDocNext, } from "../core/document.js";
|
|
24
24
|
/** How often a reading at work looks whether it was cancelled (strom document cancel, from another process). */
|
|
25
25
|
const CANCEL_LOOK_MS = 500;
|
|
26
26
|
/** The image types a document is read from: a photo, a scan, a PDF (HEIC and PDF converted for the reader). */
|
|
@@ -73,6 +73,24 @@ function regionsOf(said) {
|
|
|
73
73
|
}
|
|
74
74
|
return out;
|
|
75
75
|
}
|
|
76
|
+
/** --name [<file>:]<name>: the name each file is shown under in the reading (the first file's without a number). */
|
|
77
|
+
function namesOf(said, files) {
|
|
78
|
+
const out = new Map();
|
|
79
|
+
for (const v of Array.isArray(said) ? said : said === undefined ? [] : [said]) {
|
|
80
|
+
const m = /^(\d{1,2}):(.*)$/su.exec(String(v));
|
|
81
|
+
const at = m && Number(m[1]) >= 1 && Number(m[1]) <= files ? Number(m[1]) : 1;
|
|
82
|
+
const name = shownName(m && at === Number(m[1]) ? m[2] : String(v));
|
|
83
|
+
if (!name)
|
|
84
|
+
throw new UsageError(`invalid --name "${String(v).slice(0, 60)}"`, { hint: 'the file\'s name as the person knows it: --name "back of the photo.jpg" (the second file\'s: --name 2:<name>)' });
|
|
85
|
+
out.set(at, name);
|
|
86
|
+
}
|
|
87
|
+
return out;
|
|
88
|
+
}
|
|
89
|
+
/** An image's name for a person: the file it came as (its `from`: "the Strom app: x.jpg", "portal · x.jpg"), else its ID — never its hash. */
|
|
90
|
+
function mediaName(m) {
|
|
91
|
+
const last = m.from?.split(/: | · /u).at(-1)?.trim();
|
|
92
|
+
return (last && !last.includes(m.sha.slice(0, 12)) && shownName(last)) || m.id;
|
|
93
|
+
}
|
|
76
94
|
function readersOf(said, dflt) {
|
|
77
95
|
if (said === undefined)
|
|
78
96
|
return dflt;
|
|
@@ -97,7 +115,7 @@ function filesOf(ctx, tree, args, persons, note) {
|
|
|
97
115
|
const id = normId(a.trim(), a.trim().toUpperCase().startsWith("M") ? "media" : "input");
|
|
98
116
|
const file = fileOf(tree, shared, id);
|
|
99
117
|
const rec = id.startsWith("M") ? requireRecord(tree, id, "media") : requireRecord(tree, id, "input");
|
|
100
|
-
records.push({ id, file, name:
|
|
118
|
+
records.push({ id, file, name: rec.type === "input" ? rec.name : mediaName(rec), ...(rec.sha ? { sha: rec.sha } : {}) });
|
|
101
119
|
continue;
|
|
102
120
|
}
|
|
103
121
|
const p = ctx.resolvePath(a);
|
|
@@ -442,6 +460,7 @@ register({
|
|
|
442
460
|
{ name: "region", type: "string", multiple: true, value: "[<page>:]x,y,w,h", description: "read only this part of a page: fractions of it from its top left (repeatable, one per page)" },
|
|
443
461
|
{ name: "note", type: "string", value: "<text>", description: "what the person says the document is (\"the back of a photo\") — a description only, never names to find" },
|
|
444
462
|
{ name: "person", type: "string", multiple: true, value: "<P…>", description: "a person it is of, as the person says (repeatable)" },
|
|
463
|
+
{ name: "name", type: "string", multiple: true, value: "[<file>:]<name>", description: "the name a file is shown under in the reading (default: its input's name, an image's original file name; repeatable, 2:<name> for the second file)" },
|
|
445
464
|
{ name: "model", type: "string", value: "<model>", description: "model of the readers (default: model.vision — never weaker for handwriting)" },
|
|
446
465
|
{ name: "minutes", type: "string", value: "<n>", description: READER_MINUTES_HELP, mistaken: ["timeout", "time", "time-limit", "max-minutes"] },
|
|
447
466
|
{ name: "queue", type: "boolean", description: "keep it to be read later, read nothing now (strom document read D… reads it)" },
|
|
@@ -465,6 +484,8 @@ register({
|
|
|
465
484
|
tree.withTreeLock(() => {
|
|
466
485
|
try {
|
|
467
486
|
const { records, added } = filesOf(ctx, tree, args, persons, note);
|
|
487
|
+
for (const [at, name] of namesOf(opts.name, records.length))
|
|
488
|
+
records[at - 1].name = name;
|
|
468
489
|
pages = pagesOf(tree, records, regions);
|
|
469
490
|
for (const r of new Set(records.map((x) => x.id)))
|
|
470
491
|
if (r.startsWith("I"))
|
|
@@ -562,20 +583,21 @@ register({
|
|
|
562
583
|
tree: true,
|
|
563
584
|
args: [{ name: "reading", description: "the reading: D…", required: true }],
|
|
564
585
|
examples: ["strom document show D20260101120000-ab12 --json"],
|
|
565
|
-
run: (ctx, { args }) =>
|
|
566
|
-
const tree = ctx.tree();
|
|
567
|
-
const id = readId(args[0]);
|
|
568
|
-
const s = loadState(tree.root, id);
|
|
569
|
-
let result;
|
|
570
|
-
if (s)
|
|
571
|
-
result = buildResult(tree, settled(tree, s));
|
|
572
|
-
else if (fs.existsSync(resultPath(tree.root, id)))
|
|
573
|
-
result = JSON.parse(fs.readFileSync(resultPath(tree.root, id), "utf8"));
|
|
574
|
-
else
|
|
575
|
-
throw new UsageError(`no reading ${id}`, { hint: "strom document list" });
|
|
576
|
-
return { text: said(ctx, result), data: result };
|
|
577
|
-
},
|
|
586
|
+
run: (ctx, { args }) => showReading(ctx, readId(args[0])),
|
|
578
587
|
});
|
|
588
|
+
/** A reading of a document as strom document show (and strom readings D…) says it: its result, or what its readers wrote so far. */
|
|
589
|
+
export function showReading(ctx, id) {
|
|
590
|
+
const tree = ctx.tree();
|
|
591
|
+
const s = loadState(tree.root, id);
|
|
592
|
+
let result;
|
|
593
|
+
if (s)
|
|
594
|
+
result = buildResult(tree, settled(tree, s));
|
|
595
|
+
else if (fs.existsSync(resultPath(tree.root, id)))
|
|
596
|
+
result = JSON.parse(fs.readFileSync(resultPath(tree.root, id), "utf8"));
|
|
597
|
+
else
|
|
598
|
+
throw new UsageError(`no reading ${id}`, { hint: "strom document list" });
|
|
599
|
+
return { text: said(ctx, result), data: result };
|
|
600
|
+
}
|
|
579
601
|
register({
|
|
580
602
|
path: ["document", "list"],
|
|
581
603
|
summary: "The readings of documents of this research, newest first",
|
package/dist/commands/intake.js
CHANGED
|
@@ -8,6 +8,7 @@ import fs from "node:fs";
|
|
|
8
8
|
import path from "node:path";
|
|
9
9
|
import { register } from "../cli/registry.js";
|
|
10
10
|
import { counted, lines, moreLine, paginate, table, truncate } from "../cli/format.js";
|
|
11
|
+
import { readingsLine, readingsOf } from "../core/readsync.js";
|
|
11
12
|
import { notFound, UsageError } from "../core/errors.js";
|
|
12
13
|
import { collectFiles, fileSha256, inputPath, MAX_IN_TREE, mimeOf, storeShared } from "../core/media.js";
|
|
13
14
|
import { create, normId, requireRecord, update } from "../core/records.js";
|
|
@@ -248,8 +249,9 @@ register({
|
|
|
248
249
|
const all = ctx.tree().list("input").filter((i) => !opts.state || i.state === opts.state);
|
|
249
250
|
const page = paginate(all, ctx.limit, ctx.page);
|
|
250
251
|
return {
|
|
251
|
-
|
|
252
|
-
|
|
252
|
+
// a file read already (strom document read) says so: its reading, not read again
|
|
253
|
+
text: all.length ? lines(table(page.items.map((i) => [i.id, i.kind, i.state, truncate(i.name, 60), i.read?.length ? `read ${i.read[i.read.length - 1]} (strom readings ${i.read[i.read.length - 1]})` : ""])), moreLine(page, "strom input list")) : 'no inputs → strom intake <files or folder>',
|
|
254
|
+
data: { total: all.length, inputs: opts.full ? page.items : page.items.map((i) => ({ id: i.id, kind: i.kind, state: i.state, name: i.name, ...(i.read?.length ? { read: i.read } : {}) })) },
|
|
253
255
|
};
|
|
254
256
|
},
|
|
255
257
|
}, {
|
|
@@ -263,10 +265,10 @@ register({
|
|
|
263
265
|
const i = requireRecord(tree, args[0], "input");
|
|
264
266
|
const abs = inputPath(tree, i);
|
|
265
267
|
const tasks = tree.list("task").filter((t) => t.where.includes(i.id));
|
|
266
|
-
const text = lines(`${i.id} ${i.name} [${i.kind} · ${i.state}]`, abs ? `file ${abs}` : undefined, i.from ? `from ${i.from}` : undefined, i.batch ? `batch ${i.batch}${i.path ? ` · ${i.path}` : ""}` : undefined, i.persons?.length ? `of ${i.persons.join(", ")}` : undefined, i.sorted ? `sorted ${i.sorted.as}${i.sorted.source ? ` → ${i.sorted.source}` : ""}${i.sorted.reason ? ` (${i.sorted.reason})` : ""}` : undefined, i.mime ? `type ${i.mime}${i.size !== undefined ? ` · ${Math.round(i.size / 1024)} kB` : ""}` : undefined, i.imported ? `import ${i.imported.persons} persons, ${i.imported.families} families as leads, cited as ${i.source}` : undefined, i.text ? `\n${i.text}` : undefined, !i.text && i.kind === "text" && abs && fs.existsSync(abs) && (i.size ?? 0) < 20_000 && /\.(txt|md|csv)$/i.test(abs) ? `\n${fs.readFileSync(abs, "utf8").trim()}` : undefined, tasks.length ? `\ntasks\n${table(tasks.map((t) => [` ${t.id}`, t.state, truncate(t.what, 60)]))}` : undefined, ...i.notes.map((n) => `note ${n.text}`), i.kind === "document" && abs
|
|
268
|
+
const text = lines(`${i.id} ${i.name} [${i.kind} · ${i.state}]`, abs ? `file ${abs}` : undefined, i.from ? `from ${i.from}` : undefined, i.batch ? `batch ${i.batch}${i.path ? ` · ${i.path}` : ""}` : undefined, i.persons?.length ? `of ${i.persons.join(", ")}` : undefined, i.sorted ? `sorted ${i.sorted.as}${i.sorted.source ? ` → ${i.sorted.source}` : ""}${i.sorted.reason ? ` (${i.sorted.reason})` : ""}` : undefined, i.mime ? `type ${i.mime}${i.size !== undefined ? ` · ${Math.round(i.size / 1024)} kB` : ""}` : undefined, i.imported ? `import ${i.imported.persons} persons, ${i.imported.families} families as leads, cited as ${i.source}` : undefined, i.read?.length ? `read ${readingsLine(tree, i)}` : undefined, i.text ? `\n${i.text}` : undefined, !i.text && i.kind === "text" && abs && fs.existsSync(abs) && (i.size ?? 0) < 20_000 && /\.(txt|md|csv)$/i.test(abs) ? `\n${fs.readFileSync(abs, "utf8").trim()}` : undefined, tasks.length ? `\ntasks\n${table(tasks.map((t) => [` ${t.id}`, t.state, truncate(t.what, 60)]))}` : undefined, ...i.notes.map((n) => `note ${n.text}`), i.kind === "document" && abs && !readingsOf(tree, i).some((r) => r.state === "done")
|
|
267
269
|
? `\nread the file (a big image: strom media view ${i.id} --grid, then --crop), then record what it says: strom source add … --input ${i.id}`
|
|
268
270
|
: undefined);
|
|
269
|
-
return { text, data: { input: i, path: abs, tasks: tasks.map((t) => t.id) } };
|
|
271
|
+
return { text, data: { input: i, path: abs, tasks: tasks.map((t) => t.id), ...(i.read?.length ? { readings: readingsOf(tree, i) } : {}) } };
|
|
270
272
|
},
|
|
271
273
|
}, {
|
|
272
274
|
path: ["input", "done"],
|
package/dist/commands/output.js
CHANGED
|
@@ -13,7 +13,7 @@ import { UsageError } from "../core/errors.js";
|
|
|
13
13
|
import { excerptSettings, planExcerpts } from "../core/excerpt.js";
|
|
14
14
|
import { runGit, tracked } from "../core/git.js";
|
|
15
15
|
import { Settings } from "../core/config.js";
|
|
16
|
-
import { appKnowsArchive, appKnowsNoCouple, appShowsHypothesisLinks, appDecidesConflicts, appOpensLinks, appReadsTitles, appShowsCoupleEvents, appShowsEdges, appShowsSourceReads, appShowsFactStatus, appShowsStoryDrafts, appTurnsExcerpts } from "../core/stromapp.js";
|
|
16
|
+
import { appKnowsArchive, appKnowsNoCouple, appShowsHypothesisLinks, appShowsReadings, appDecidesConflicts, appOpensLinks, appReadsTitles, appShowsCoupleEvents, appShowsEdges, appShowsSourceReads, appShowsFactStatus, appShowsStoryDrafts, appTurnsExcerpts } from "../core/stromapp.js";
|
|
17
17
|
import { EXCERPT_QUALITIES, EXCERPT_SCOPES } from "../core/model.js";
|
|
18
18
|
import { ui } from "../cli/ui.js";
|
|
19
19
|
import { isAgent } from "../core/which.js";
|
|
@@ -91,7 +91,9 @@ export function writeTreeGed(tree, opts) {
|
|
|
91
91
|
const hypothesisLinks = opts.for === "strom" && appShowsHypothesisLinks(new Settings(tree.env, {}), opts.stromVersion);
|
|
92
92
|
// …and the sides of a conflict of the user's edit, for an app that decides it
|
|
93
93
|
const conflictSides = opts.for === "strom" && appDecidesConflicts(new Settings(tree.env, {}), opts.stromVersion);
|
|
94
|
-
|
|
94
|
+
// …and the reading of a document each source is an entry of, for an app that opens it
|
|
95
|
+
const readings = opts.for === "strom" && appShowsReadings(new Settings(tree.env, {}), opts.stromVersion);
|
|
96
|
+
const result = exportGedcom(tree, { for: opts.for, stromVersion: opts.stromVersion, ...(persons ? { persons } : {}), ...(images ? { excerpts: images.of } : {}), ...(clips ? { clips } : {}), ...(research ? { research } : {}), ...(edges ? { edges } : {}), ...(storyDrafts ? { storyDrafts } : {}), ...(coupleResi ? { coupleResi } : {}), ...(sourceReads ? { sourceReads } : {}), ...(factStatus ? { factStatus } : {}), ...(archive ? { archive } : {}), ...(turnsExcerpts ? { turnsExcerpts } : {}), ...(noCouple ? { noCouple } : {}), ...(titles ? { titles } : {}), ...(hypothesisLinks ? { hypothesisLinks } : {}), ...(conflictSides ? { conflictSides } : {}), ...(readings ? { readings } : {}) });
|
|
95
97
|
const findings = validateGedcom(result.text, opts.for === "standard" ? { strict: true } : {});
|
|
96
98
|
const file = opts.out ?? gedFile(tree, opts.for, opts.research);
|
|
97
99
|
if (!hasGedErrors(findings) && !tree.dryRun) {
|
|
@@ -9,6 +9,8 @@ import { counted, lines, runs, shellArg, truncate } from "../cli/format.js";
|
|
|
9
9
|
import { UsageError } from "../core/errors.js";
|
|
10
10
|
import { requireRecord } from "../core/records.js";
|
|
11
11
|
import { imageRange } from "../core/media.js";
|
|
12
|
+
import { listReadings, READ_ID } from "../core/document.js";
|
|
13
|
+
import { showReading } from "./document.js";
|
|
12
14
|
import { parseReport } from "../core/reader.js";
|
|
13
15
|
import { foldText } from "../core/text.js";
|
|
14
16
|
import { REDUCED_BELOW, viewRegion } from "../core/views.js";
|
|
@@ -396,20 +398,29 @@ register({
|
|
|
396
398
|
"blocks with nothing found only what the reader could not read (a possible match is often there), the gaps in\n" +
|
|
397
399
|
"the page numbering and the parts of the book they saw. One image's block word for word: --image N.\n" +
|
|
398
400
|
"Default: the newest reading; with --images, --image or --match all of them. Nothing is written.",
|
|
399
|
-
args: [{ name: "which", description: "a record set (B…),
|
|
401
|
+
args: [{ name: "which", description: "a record set (B…), a reading: its report's name or the start of it (a day), or a reading of a document (D…)" }],
|
|
400
402
|
options: [
|
|
401
403
|
{ name: "images", type: "string", value: "<from-to>", description: "only these image numbers, e.g. 40-69 or 5-113,130-228" },
|
|
402
404
|
{ name: "image", type: "string", value: "<n>", description: "that image's whole block, word for word (a few: 35-38)" },
|
|
403
405
|
{ name: "match", type: "string", multiple: true, value: "<words>", description: "only blocks that mention it — any spelling of accents (repeatable: variants)" },
|
|
404
406
|
{ name: "list", type: "boolean", description: "the readings there are, one line each" },
|
|
405
407
|
],
|
|
406
|
-
examples: ["strom readings", "strom readings B0001 --images 40-69 --match Novák --match Nowak", "strom readings B0001 --image 57", "strom readings --list"],
|
|
408
|
+
examples: ["strom readings", "strom readings B0001 --images 40-69 --match Novák --match Nowak", "strom readings B0001 --image 57", "strom readings D20260101120000-ab12", "strom readings --list"],
|
|
407
409
|
run(ctx, { args, opts }) {
|
|
408
410
|
const tree = ctx.tree();
|
|
411
|
+
const which = args[0]?.trim();
|
|
412
|
+
// a reading of a document (strom document read): its result, as strom document show says it
|
|
413
|
+
if (which && READ_ID.test(which))
|
|
414
|
+
return showReading(ctx, which);
|
|
409
415
|
let all = loadReadings(tree);
|
|
410
|
-
|
|
416
|
+
// the readings of documents, one line each, after those of the books (--list)
|
|
417
|
+
const documents = () => listReadings(tree.root).map((r) => ({ line: `${r.id} · document · ${r.state} · ${r.files.join(", ")} · ${counted(r.pages, "page")} — strom readings ${r.id}`, data: { id: r.id, state: r.state, files: r.files, pages: r.pages } }));
|
|
418
|
+
if (all.length === 0) {
|
|
419
|
+
const docs = opts.list ? documents() : [];
|
|
420
|
+
if (docs.length)
|
|
421
|
+
return { text: lines(...docs.map((d) => d.line)), data: { readings: [], documents: docs.map((d) => d.data) } };
|
|
411
422
|
return { text: "no readings yet (strom read writes them into notes/readings)", data: { readings: [] } };
|
|
412
|
-
|
|
423
|
+
}
|
|
413
424
|
let book;
|
|
414
425
|
if (which && /^[Bb]\d+$/.test(which))
|
|
415
426
|
book = requireRecord(tree, which, "recordset").id;
|
|
@@ -439,8 +450,9 @@ register({
|
|
|
439
450
|
if (shown.length === 0)
|
|
440
451
|
return { text: `no reader reported ${image !== undefined ? `image ${runs([...image])}` : "these images"}${book ? ` of ${book}` : ""} · strom readings --list`, data: { readings: [] } };
|
|
441
452
|
if (opts.list) {
|
|
442
|
-
const
|
|
443
|
-
|
|
453
|
+
const docs = which || ranges || image !== undefined || match.length ? [] : documents();
|
|
454
|
+
const text = lines(...shown.flatMap(({ r, blocks }) => readingHead(r, blocks)[0].concat(r.question ? ` · "${truncate(r.question, 60)}"` : "")), ...docs.map((d) => d.line));
|
|
455
|
+
return { text, data: { readings: shown.map(({ r, blocks }) => ({ stem: r.stem, question: r.question, reports: r.reports, images: blocks.map((b) => b.num) })), ...(docs.length ? { documents: docs.map((d) => d.data) } : {}) } };
|
|
444
456
|
}
|
|
445
457
|
if (image !== undefined) {
|
|
446
458
|
const text = lines(...shown.flatMap(({ r, blocks }) => blocks.flatMap((b) => [`${b.report}${b.recordset ? ` · ${b.recordset}` : ""}${r.question ? ` · question: ${truncate(r.question, 120)}` : ""}`, `## ${b.text}`, ""])));
|
package/dist/commands/sync.js
CHANGED
|
@@ -77,7 +77,7 @@ function newReads(c, incoming) {
|
|
|
77
77
|
return !!s && s.quay !== 0 && (incoming.transcripts === "evidence" || !!s.verified);
|
|
78
78
|
}
|
|
79
79
|
/** One change as the user reads it, with what strom does with it. */
|
|
80
|
-
function changeLine(tree, c, incoming, lang) {
|
|
80
|
+
function changeLine(tree, c, incoming, lang, readings) {
|
|
81
81
|
const name = (key) => {
|
|
82
82
|
if (!key)
|
|
83
83
|
return "?";
|
|
@@ -150,11 +150,16 @@ function changeLine(tree, c, incoming, lang) {
|
|
|
150
150
|
? "ui.sync.do.detail"
|
|
151
151
|
: c.kind === "fact.part" && (c.action === "add" || c.action === "user")
|
|
152
152
|
? `ui.sync.do.part${c.action === "user" ? ".user" : ""}`
|
|
153
|
-
: c.kind
|
|
154
|
-
? `ui.sync.do
|
|
155
|
-
: c.kind === "fact.
|
|
156
|
-
?
|
|
157
|
-
:
|
|
153
|
+
: c.kind === "source.new" && c.reading?.ok
|
|
154
|
+
? `ui.sync.do.source.new.reading${c.reading.edited ? ".edited" : ""}`
|
|
155
|
+
: c.kind.startsWith("source.") || c.kind === "fact.cite" || c.kind === "person.cite" || c.kind === "family.cite"
|
|
156
|
+
? `ui.sync.do.${c.kind}${c.reads || (c.kind === "source.new" && newReads(c, incoming)) ? ".read" : ""}`
|
|
157
|
+
: c.kind === "fact.new" && c.reads
|
|
158
|
+
? "ui.sync.do.reading"
|
|
159
|
+
// a fact on an entry of the research's reading of a document: as sure as its readers read it
|
|
160
|
+
: c.kind === "fact.new" && c.fact?.cites?.some((x) => readings?.get(x.source)?.ok)
|
|
161
|
+
? "ui.sync.do.reading.document"
|
|
162
|
+
: `ui.sync.do.${c.action}`;
|
|
158
163
|
// an archive: nobody reads a record there, nor is an agent told — said without it
|
|
159
164
|
const said = actsAsArchive(tree) && `${does}.archive` in UI ? `${does}.archive` : does;
|
|
160
165
|
return `${ui(lang, key, v)} → ${ui(lang, said)}`;
|
|
@@ -163,7 +168,7 @@ function planText(tree, plan, incoming, file, lang, edits) {
|
|
|
163
168
|
const shown = file.includes(" ") ? `"${file}"` : file;
|
|
164
169
|
if (!plan.changes.length)
|
|
165
170
|
return ui(lang, "ui.sync.nothing", { file: path.basename(file) });
|
|
166
|
-
return lines(ui(lang, "ui.sync.title", { file: path.basename(file), n: plan.changes.length }), plan.base ? ui(lang, "ui.sync.base", { head: plan.head?.slice(0, 8) ?? incoming.since ?? "" }) : ui(lang, plan.head ? "ui.sync.headmissing" : "ui.sync.nobase", { head: plan.head?.slice(0, 8) ?? "" }), plan.partial ? ui(lang, "ui.sync.partial") : undefined, plan.identity.strangers.length ? ui(lang, "ui.sync.strangers", { ids: plan.identity.strangers.join(", ") }) : undefined, "", ...plan.changes.map((c) => ` ${changeLine(tree, c, incoming, lang)}`), "",
|
|
171
|
+
return lines(ui(lang, "ui.sync.title", { file: path.basename(file), n: plan.changes.length }), plan.base ? ui(lang, "ui.sync.base", { head: plan.head?.slice(0, 8) ?? incoming.since ?? "" }) : ui(lang, plan.head ? "ui.sync.headmissing" : "ui.sync.nobase", { head: plan.head?.slice(0, 8) ?? "" }), plan.partial ? ui(lang, "ui.sync.partial") : undefined, plan.identity.strangers.length ? ui(lang, "ui.sync.strangers", { ids: plan.identity.strangers.join(", ") }) : undefined, "", ...plan.changes.map((c) => ` ${changeLine(tree, c, incoming, lang, plan.readings)}`), "",
|
|
167
172
|
// an archive mirrors the app whatever the setting says (planSync)
|
|
168
173
|
actsAsArchive(tree) ? ui(lang, "ui.sync.edits.archive") : ui(lang, "ui.sync.edits", { mode: ui(lang, edits === "user" ? "ui.sync.mode.user" : "ui.sync.mode.conflict") }), ui(lang, "ui.sync.next", { file: shown }));
|
|
169
174
|
}
|
package/dist/core/document.js
CHANGED
|
@@ -831,6 +831,10 @@ export function backBefore(s) {
|
|
|
831
831
|
export function nextVersion(s) {
|
|
832
832
|
return Math.max(s.version, ...s.reports.map((r) => r.version)) + 1;
|
|
833
833
|
}
|
|
834
|
+
/** A file's name for a person: its last part, no control characters, at most 200 characters. */
|
|
835
|
+
export function shownName(name) {
|
|
836
|
+
return path.basename(name.normalize("NFC").replace(/[\\/]+/gu, "/")).replace(/[\p{Cc}]/gu, "").trim().slice(0, 200);
|
|
837
|
+
}
|
|
834
838
|
/** Whoever asked a reading from the Strom app (its bridge). */
|
|
835
839
|
export const APP_READ_FROM = "app:read";
|
|
836
840
|
export function readsDir(root) {
|
package/dist/core/live-read.js
CHANGED
|
@@ -15,7 +15,7 @@ import path from "node:path";
|
|
|
15
15
|
import { spawn } from "node:child_process";
|
|
16
16
|
import { newerTree, Tree } from "./tree.js";
|
|
17
17
|
import { actsAsArchive } from "./state.js";
|
|
18
|
-
import { APP_READ_FROM, alive, backBefore, buildResult, DOC_PAGES, FINAL_STATES, loadState, parseRegion, READ_ID, readsDir, reportPath, resultPath, saveState, statePath } from "./document.js";
|
|
18
|
+
import { APP_READ_FROM, alive, backBefore, buildResult, DOC_PAGES, FINAL_STATES, loadState, parseRegion, READ_ID, readsDir, reportPath, resultPath, saveState, shownName, statePath } from "./document.js";
|
|
19
19
|
import { knownOriginal } from "./originals.js";
|
|
20
20
|
import { readableTypes } from "./readable.js";
|
|
21
21
|
import { makeView } from "./views.js";
|
|
@@ -297,6 +297,8 @@ function notReady(tree, settings, env, seen, now) {
|
|
|
297
297
|
return undefined;
|
|
298
298
|
}
|
|
299
299
|
const BODY_MAX = 16 * 1024;
|
|
300
|
+
/** The names of files sent to be read kept until they are asked (the last ones). */
|
|
301
|
+
const NAMES_KEPT = 200;
|
|
300
302
|
/**
|
|
301
303
|
* The readings of the app: POST /read, /read/<id>/again, /read/<id>/cancel; GET /read/<id>, /read/<id>/page/<n>.jpg,
|
|
302
304
|
* /reads; the queue read one at a time (tick, each tick of the bridge); what changed, for the event read.
|
|
@@ -309,11 +311,26 @@ export class AppReadings {
|
|
|
309
311
|
starting = new Map();
|
|
310
312
|
told = new Map();
|
|
311
313
|
primed = false;
|
|
314
|
+
/** The name each file to read came under (X-Strom-Name of its last PUT): a file the research had already keeps its own. */
|
|
315
|
+
names = new Map();
|
|
312
316
|
b;
|
|
313
317
|
constructor(b) {
|
|
314
318
|
this.b = b;
|
|
315
319
|
this.seen = new ReadingsSeen(b.root, b.env);
|
|
316
320
|
}
|
|
321
|
+
/** A file sent to be read (PUT /media with X-Strom-Read) under its name in the app: the reading of it shown so (sent with no name: its record's own). */
|
|
322
|
+
named(sha, name) {
|
|
323
|
+
this.names.delete(sha);
|
|
324
|
+
// cleaned as strom document read --name cleans it: nothing left (only slashes, control characters) = no name
|
|
325
|
+
const shown = name && shownName(name);
|
|
326
|
+
if (shown)
|
|
327
|
+
this.names.set(sha, shown);
|
|
328
|
+
for (const old of this.names.keys()) {
|
|
329
|
+
if (this.names.size <= NAMES_KEPT)
|
|
330
|
+
break;
|
|
331
|
+
this.names.delete(old);
|
|
332
|
+
}
|
|
333
|
+
}
|
|
317
334
|
/** A reading of the app at work or queued (the bridge does not end idle meanwhile). */
|
|
318
335
|
active() {
|
|
319
336
|
return this.pending > 0 || this.appStates().some((s) => !FINAL_STATES.includes(s.state));
|
|
@@ -508,6 +525,8 @@ export class AppReadings {
|
|
|
508
525
|
}
|
|
509
526
|
const shared = this.b.settings().shared()?.value;
|
|
510
527
|
const records = [];
|
|
528
|
+
// each file under the name the app sent it with last, else its record's own (never its hash)
|
|
529
|
+
const names = [];
|
|
511
530
|
const types = readableTypes(this.b.env);
|
|
512
531
|
for (const f of files) {
|
|
513
532
|
const sha = f.toLowerCase();
|
|
@@ -521,13 +540,16 @@ export class AppReadings {
|
|
|
521
540
|
if (known.bytes > READ_MAX_BYTES)
|
|
522
541
|
return { code: 413, body: refusal(`larger than a reading takes (${READ_MAX_BYTES / 1024 / 1024} MB)`, "read.large", { sha, mb: String(READ_MAX_BYTES / 1024 / 1024) }) };
|
|
523
542
|
records.push(known.id);
|
|
543
|
+
const name = this.names.get(sha);
|
|
544
|
+
if (name)
|
|
545
|
+
names.push(`--name=${records.length}:${name}`);
|
|
524
546
|
}
|
|
525
547
|
const limited = this.limited();
|
|
526
548
|
if (limited)
|
|
527
549
|
return limited;
|
|
528
550
|
return {
|
|
529
551
|
what: "a reading",
|
|
530
|
-
args: ["document", "read", ...records, "--queue", "--readers", String(readers), "--into", lang, ...(person ? ["--person", person] : []), ...(note ? [`--note=${note}`] : []), ...(region ? [`--region=${region}`] : [])],
|
|
552
|
+
args: ["document", "read", ...records, "--queue", "--readers", String(readers), "--into", lang, ...(person ? ["--person", person] : []), ...(note ? [`--note=${note}`] : []), ...(region ? [`--region=${region}`] : []), ...names],
|
|
531
553
|
asked: "read",
|
|
532
554
|
};
|
|
533
555
|
}
|
package/dist/core/live.js
CHANGED
|
@@ -112,7 +112,7 @@ import { knownNewerVersion, updateChannel } from "./update.js";
|
|
|
112
112
|
import { whatsNew } from "./whatsnew.js";
|
|
113
113
|
import { gitProgram, runGit } from "./git.js";
|
|
114
114
|
import { RECENT_HOURS, recentChanges, recentNow } from "./recent.js";
|
|
115
|
-
import { appKnowsArchive, appKnowsNoCouple, appShowsHypothesisLinks, appDecidesConflicts, appOpensLinks, appReadsTitles, appShowsCoupleEvents, appShowsEdges, appShowsFactStatus, appShowsSourceReads, appShowsStoryDrafts, appTurnsExcerpts, appUrlSetting, isAppVersion, isStromAppOrigin } from "./stromapp.js";
|
|
115
|
+
import { appKnowsArchive, appKnowsNoCouple, appShowsHypothesisLinks, appShowsReadings, appDecidesConflicts, appOpensLinks, appReadsTitles, appShowsCoupleEvents, appShowsEdges, appShowsFactStatus, appShowsSourceReads, appShowsStoryDrafts, appTurnsExcerpts, appUrlSetting, isAppVersion, isStromAppOrigin } from "./stromapp.js";
|
|
116
116
|
import { Settings } from "./config.js";
|
|
117
117
|
import { LINK_SCHEME, linkActions, linkHandlerState, linkHandlerStateLater, linkScheme } from "./links.js";
|
|
118
118
|
import { autoTidy } from "./tidy.js";
|
|
@@ -702,7 +702,7 @@ function idsOf(applied, known) {
|
|
|
702
702
|
return Object.keys(ids.persons).length || Object.keys(ids.sources).length ? { ids } : {};
|
|
703
703
|
}
|
|
704
704
|
/** What the bridge does that an app may ask about (each added once, never taken away). */
|
|
705
|
-
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"];
|
|
705
|
+
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"];
|
|
706
706
|
/** /status recent, when it is ready. */
|
|
707
707
|
function recentOf(root, env, head) {
|
|
708
708
|
const recent = head ? recentNow(root, env, head) : undefined;
|
|
@@ -1364,12 +1364,13 @@ export function serveLive(root, env) {
|
|
|
1364
1364
|
const titles = appReadsTitles(new Settings(env, {}), version);
|
|
1365
1365
|
const hypothesisLinks = appShowsHypothesisLinks(new Settings(env, {}), version);
|
|
1366
1366
|
const conflictSides = appDecidesConflicts(new Settings(env, {}), version);
|
|
1367
|
+
const readings = appShowsReadings(new Settings(env, {}), version);
|
|
1367
1368
|
const offered = opens ? links(env) : [];
|
|
1368
1369
|
ged = {
|
|
1369
1370
|
head: h,
|
|
1370
1371
|
links: offered.join(" "),
|
|
1371
1372
|
version: version ?? "",
|
|
1372
|
-
text: exportGedcom(tree, { for: "strom", ...(h ? { head: h } : {}), ...(images ? { excerpts: images.of } : {}), ...(opens ? { clips: true, research: true } : {}), ...(edges ? { edges } : {}), ...(storyDrafts ? { storyDrafts } : {}), ...(coupleResi ? { coupleResi } : {}), ...(sourceReads ? { sourceReads } : {}), ...(factStatus ? { factStatus } : {}), ...(archive ? { archive } : {}), ...(turnsExcerpts ? { turnsExcerpts } : {}), ...(noCouple ? { noCouple } : {}), ...(titles ? { titles } : {}), ...(hypothesisLinks ? { hypothesisLinks } : {}), ...(conflictSides ? { conflictSides } : {}), ...(offered.length ? { links: offered, ...(linkScheme(env) !== LINK_SCHEME ? { linkScheme: linkScheme(env) } : {}) } : {}) }).text,
|
|
1373
|
+
text: exportGedcom(tree, { for: "strom", ...(h ? { head: h } : {}), ...(images ? { excerpts: images.of } : {}), ...(opens ? { clips: true, research: true } : {}), ...(edges ? { edges } : {}), ...(storyDrafts ? { storyDrafts } : {}), ...(coupleResi ? { coupleResi } : {}), ...(sourceReads ? { sourceReads } : {}), ...(factStatus ? { factStatus } : {}), ...(archive ? { archive } : {}), ...(turnsExcerpts ? { turnsExcerpts } : {}), ...(noCouple ? { noCouple } : {}), ...(titles ? { titles } : {}), ...(hypothesisLinks ? { hypothesisLinks } : {}), ...(conflictSides ? { conflictSides } : {}), ...(readings ? { readings } : {}), ...(offered.length ? { links: offered, ...(linkScheme(env) !== LINK_SCHEME ? { linkScheme: linkScheme(env) } : {}) } : {}) }).text,
|
|
1373
1374
|
};
|
|
1374
1375
|
}
|
|
1375
1376
|
res.writeHead(200, { "Content-Type": "text/plain; charset=utf-8", "Cache-Control": "no-store", "X-Strom-Head": h }).end(ged.text);
|
|
@@ -1701,6 +1702,11 @@ export function serveLive(root, env) {
|
|
|
1701
1702
|
: {}),
|
|
1702
1703
|
// the changes the research could not write (the rest is written): what and why
|
|
1703
1704
|
...(data.skipped?.length ? { skipped: data.skipped } : {}),
|
|
1705
|
+
// the sources that said they are an entry of a reading of a document (_STROM_READING): taken as the
|
|
1706
|
+
// research's reading, or not (why: a stable code) and so the app's own source as any
|
|
1707
|
+
...(changes.some((c) => c.reading)
|
|
1708
|
+
? { readings: changes.filter((c) => c.reading).map((c) => ({ source: c.source.startsWith("x:") ? `@${c.source.slice(2)}@` : c.source, read: c.reading.id, ok: c.reading.ok, ...(c.reading.entry ? { entry: c.reading.entry } : {}), ...(c.reading.code ? { code: c.reading.code } : {}), ...(c.reading.edited ? { edited: true } : {}) })) }
|
|
1709
|
+
: {}),
|
|
1704
1710
|
head: head(root),
|
|
1705
1711
|
...(data.input ? { input: data.input } : {}),
|
|
1706
1712
|
conflicts,
|
|
@@ -1964,6 +1970,7 @@ export function serveLive(root, env) {
|
|
|
1964
1970
|
return typeof v === "string" ? v : undefined;
|
|
1965
1971
|
};
|
|
1966
1972
|
let name;
|
|
1973
|
+
let named;
|
|
1967
1974
|
let note;
|
|
1968
1975
|
let meta;
|
|
1969
1976
|
let region;
|
|
@@ -1985,7 +1992,8 @@ export function serveLive(root, env) {
|
|
|
1985
1992
|
at = "X-Strom-Path";
|
|
1986
1993
|
where = header("x-strom-path") ? batchPath(decodeURIComponent(header("x-strom-path"))) : undefined;
|
|
1987
1994
|
at = "X-Strom-Name";
|
|
1988
|
-
|
|
1995
|
+
named = decodeURIComponent(header("x-strom-name") ?? "").trim() || undefined;
|
|
1996
|
+
name = named ?? sha.slice(0, 12);
|
|
1989
1997
|
at = "X-Strom-Note";
|
|
1990
1998
|
note = header("x-strom-note") ? decodeURIComponent(header("x-strom-note")).slice(0, 500) : undefined;
|
|
1991
1999
|
at = "X-Strom-Person";
|
|
@@ -2028,6 +2036,9 @@ export function serveLive(root, env) {
|
|
|
2028
2036
|
if (no)
|
|
2029
2037
|
return refuse(409, refusal(no.message, no.code === "task.no-file" ? "waiting.no-file" : "waiting.none", { id: forTask }));
|
|
2030
2038
|
}
|
|
2039
|
+
// a file to read: its reading shown under the name it came with now — also when the research had it already
|
|
2040
|
+
if (read)
|
|
2041
|
+
reads.named(sha, named);
|
|
2031
2042
|
receiveOriginal(receiver, req, res, { sha, name, note, persons: meta.persons, source: meta.source, region, batch, where, zip, task: forTask, ...(read ? { read } : {}) });
|
|
2032
2043
|
};
|
|
2033
2044
|
const closeBatchNow = (id, extra, reply) => closeBatchBy(receiver, id, extra, reply);
|