strom-research 1.17.0 → 1.18.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 +18 -0
- package/CHANGELOG.de.md +21 -0
- package/CHANGELOG.md +19 -0
- package/assets/lang/cs.json +19 -3
- package/assets/lang/de.json +19 -3
- package/dist/cli/context.js +3 -1
- package/dist/cli/ui.js +19 -3
- package/dist/commands/clips.js +5 -1
- package/dist/commands/media.js +37 -21
- package/dist/commands/pack.js +2 -1
- package/dist/commands/read.js +9 -4
- package/dist/commands/setup.js +9 -4
- package/dist/commands/sources.js +12 -6
- package/dist/commands/transcripts.js +20 -2
- package/dist/commands/trees.js +2 -1
- package/dist/core/excerpt.js +16 -3
- package/dist/core/readable.js +363 -0
- package/dist/core/tidy.js +18 -3
- package/dist/core/tree.js +1 -1
- package/dist/core/views.js +48 -13
- package/dist/image/image.js +39 -0
- package/dist/image/index.js +45 -5
- package/dist/image/pdf.js +331 -0
- package/package.json +4 -1
package/CHANGELOG.cs.md
CHANGED
|
@@ -5,6 +5,24 @@
|
|
|
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.18.0
|
|
9
|
+
|
|
10
|
+
*Připravuje se: bety do 1.18.0-beta.1. Oddíl bude úplný s vydáním.*
|
|
11
|
+
|
|
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
|
+
vyznačená (výřezy, poloviny dvoustrany, uložená část) jsou místa obrázku, jak se ukazuje. Fotka sama zůstává, jak
|
|
14
|
+
byla.
|
|
15
|
+
- **Fotky HEIC a dokumenty PDF jdou přečíst.** Fotky HEIC z telefonu a stránky PDF se pro čtení převádějí na JPEG —
|
|
16
|
+
na macOS, Windows i Linuxu — a originál zůstává, jak přišel. Fotka HEIC jde do aplikace Strom jako výřez JPEG.
|
|
17
|
+
- **Řečeno, když obrázek převést nejde.** Kde fotku nebo stránku PDF na počítači nic nepřevede, řekne to jedna věta
|
|
18
|
+
i s tím, co by pomohlo, v jazyce výzkumu.
|
|
19
|
+
- **Převedené obrázky v pořádku.** `strom tidy` převedené obrázky po 30 dnech nebo nad 500 MB uklidí; když jsou
|
|
20
|
+
potřeba, vzniknou znovu.
|
|
21
|
+
- **Souhlas v jednom jazyce.** Souhlas, který dává jen člověk (rozbalení výzkumu, odebrání stromu, odmítnuté okno),
|
|
22
|
+
zazní celý v jazyce výzkumu, nic z něj nezůstane anglicky.
|
|
23
|
+
- **Odmítnutá aktualizace.** Když se `strom update` odmítne, řekne, že se nic nenainstalovalo a která verze zůstává,
|
|
24
|
+
a skončí chybovým kódem; nepovedená instalace se řekne také v jazyce výzkumu.
|
|
25
|
+
|
|
8
26
|
## 1.17.0
|
|
9
27
|
|
|
10
28
|
*19. 10. 2026 · patří k aplikaci Strom 3.13.1.*
|
package/CHANGELOG.de.md
CHANGED
|
@@ -5,6 +5,27 @@
|
|
|
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.18.0
|
|
9
|
+
|
|
10
|
+
*In Vorbereitung: die Betas bis 1.18.0-beta.1. Der Abschnitt ist mit dem Release vollständig.*
|
|
11
|
+
|
|
12
|
+
- **Handyfotos aufrecht.** Ein Foto, das nach seiner EXIF-Ausrichtung gedreht ist, wird gelesen, wie es gesehen
|
|
13
|
+
werden soll, aufrecht; die darauf markierten Stellen (Ausschnitte, die Hälften einer Doppelseite, ein gespeicherter
|
|
14
|
+
Teil) sind die des Bildes, wie es gezeigt wird. Das Foto selbst bleibt, wie es war.
|
|
15
|
+
- **HEIC-Fotos und PDF-Dokumente lesbar.** HEIC-Fotos vom Handy und die Seiten einer PDF werden zum Lesen in JPEG
|
|
16
|
+
umgewandelt — unter macOS, Windows und Linux —, das Original bleibt, wie es kam. Ein HEIC-Foto geht als
|
|
17
|
+
JPEG-Ausschnitt in die Strom-App.
|
|
18
|
+
- **Gesagt, wenn ein Bild nicht umgewandelt werden kann.** Wo nichts auf dem Computer ein Foto oder eine PDF-Seite
|
|
19
|
+
umwandelt, sagt es ein Satz, auch was helfen würde, in der Sprache der Forschung.
|
|
20
|
+
- **Umgewandelte Bilder in Ordnung gehalten.** `strom tidy` räumt umgewandelte Bilder nach 30 Tagen oder über 500 MB
|
|
21
|
+
weg; bei Bedarf entstehen sie neu.
|
|
22
|
+
- **Eine Zustimmung in einer Sprache.** Eine Zustimmung, die nur ein Mensch gibt (eine Forschung entpacken, einen
|
|
23
|
+
Stammbaum entfernen, ein abgelehntes Fenster), steht ganz in der Sprache der Forschung, nichts davon bleibt
|
|
24
|
+
Englisch.
|
|
25
|
+
- **Ein abgelehntes Update.** Wird `strom update` abgelehnt, sagt es, dass nichts installiert wurde und welche
|
|
26
|
+
Version bleibt, und endet mit einem Fehlercode; eine misslungene Installation wird auch in der Sprache der
|
|
27
|
+
Forschung gesagt.
|
|
28
|
+
|
|
8
29
|
## 1.17.0
|
|
9
30
|
|
|
10
31
|
*19.10.2026 · gehört zur Strom-App 3.13.1.*
|
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,25 @@ 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.18.0
|
|
9
|
+
|
|
10
|
+
*In preparation: the betas up to 1.18.0-beta.1. The section is complete with the release.*
|
|
11
|
+
|
|
12
|
+
- **Phone photos upright.** A photo turned by its EXIF orientation is read as it is meant to be seen, upright; the
|
|
13
|
+
places marked on it (excerpts, the halves of a spread, a cut-out part) are of the picture as it is shown. The
|
|
14
|
+
photo itself stays as it was.
|
|
15
|
+
- **HEIC photos and PDF documents can be read.** HEIC photos from a phone and the pages of a PDF are converted to
|
|
16
|
+
JPEG for reading — on macOS, Windows and Linux — and the original is kept as it came. A HEIC photo goes to the
|
|
17
|
+
Strom app as a JPEG excerpt.
|
|
18
|
+
- **Said when a picture cannot be converted.** Where nothing on the computer converts a photo or a PDF page, one
|
|
19
|
+
sentence says so and what would help, in the research's language.
|
|
20
|
+
- **Converted pictures kept in order.** `strom tidy` clears the converted pictures away after 30 days or above
|
|
21
|
+
500 MB; they are made again when needed.
|
|
22
|
+
- **A consent in one language.** A consent only a person gives (unpacking a research, removing a tree, a window said
|
|
23
|
+
no to) is said whole in the research's language, nothing of it left in English.
|
|
24
|
+
- **An update said no to.** When `strom update` is declined, it says nothing was installed and which version stays,
|
|
25
|
+
and ends with an error code; a failed install is said in the research's language too.
|
|
26
|
+
|
|
8
27
|
## 1.17.0
|
|
9
28
|
|
|
10
29
|
*2026-10-19 · goes with the Strom app 3.13.1.*
|
package/assets/lang/cs.json
CHANGED
|
@@ -59,7 +59,6 @@
|
|
|
59
59
|
"ui.dialog.title": "Strom výzkum",
|
|
60
60
|
"ui.dialog.yes": "Povolit",
|
|
61
61
|
"ui.dialog.no": "Nepovolit",
|
|
62
|
-
"ui.dialog.refused": "Nepovoleno – nic se nezměnilo.",
|
|
63
62
|
"ui.link.isolated": "Tohle je izolovaná instalace (STROM_ISOLATED): odkazy strom-research:// nezapisuje – zůstávají běžné instalaci stromu.",
|
|
64
63
|
"ui.doc.isolated": "izolovaná instalace v {folder}",
|
|
65
64
|
"ui.doc.second": "druhá instalace: příkaz {command}, ve složce {folder}, její odkazy {scheme}://",
|
|
@@ -760,9 +759,10 @@
|
|
|
760
759
|
"ui.update.available": "Vyšla nová verze: {version} (tato je {current}) – nainstaluje ji strom update.",
|
|
761
760
|
"ui.update.npm": "Vyšla nová verze: {version}. strom je tu nainstalovaný přes npm: npm install -g strom-research@{tag}",
|
|
762
761
|
"ui.update.sure": "Aktualizovat strom z {current} na {version}? Výzkumy zůstanou, jak jsou.",
|
|
763
|
-
"ui.update.later": "
|
|
762
|
+
"ui.update.later": "Neaktualizováno – nic se nenainstalovalo, strom zůstává ve verzi {current}. Kdykoli znovu: {command}",
|
|
764
763
|
"ui.update.downloading": "Stahuji strom {version}…",
|
|
765
|
-
"ui.update.failed": "
|
|
764
|
+
"ui.error.update.failed": "aktualizace se nepovedla: {detail}. strom zůstává ve verzi {current}",
|
|
765
|
+
"ui.error.update.failed.hint": "znovu {command} – nebo spustit instalátor znovu",
|
|
766
766
|
"ui.update.done": "✓ strom je aktualizovaný: {previous} → {version}.",
|
|
767
767
|
"ui.update.whatsnew": "Co je nového: {url}",
|
|
768
768
|
"ui.update.restart": "strom je aktualizovaný — tahle nabídka ale ještě běží ve staré verzi. Po novém spuštění stromu (nebo zástupce „{shortcut}“) poběží nová verze.",
|
|
@@ -1022,6 +1022,7 @@
|
|
|
1022
1022
|
"ui.tidy.kind.views": "Náhledy skenů (udělají se znovu, až budou potřeba)",
|
|
1023
1023
|
"ui.tidy.kind.excerpts": "Výřezy ze skenů (udělají se znovu, až budou potřeba)",
|
|
1024
1024
|
"ui.tidy.kind.fetch": "Pracovní složky přerušených stahování",
|
|
1025
|
+
"ui.tidy.kind.converted": "Fotky a strany PDF převedené pro čtení (vytvoří se znovu, až budou potřeba)",
|
|
1025
1026
|
"ui.tidy.kind.kept": "Starší poslání z aplikace Strom, už zapsaná (změny jsou v historii)",
|
|
1026
1027
|
"ui.tidy.kind.inbox": "Dávná poslání z aplikace Strom, která ve schránce nechal starší strom",
|
|
1027
1028
|
"ui.tidy.kind.pointers": "Zbytky skončených pracovních sezení",
|
|
@@ -1040,6 +1041,8 @@
|
|
|
1040
1041
|
"ui.tidy.hint": "Vedle výzkumu si strom drží {strom}, z toho jde uvolnit {size}: strom tidy (v menu Nastavení → Údržba → Místo na disku)",
|
|
1041
1042
|
"ui.error.consent": "tady je potřeba souhlas – {question}",
|
|
1042
1043
|
"ui.error.consent.hint": "spustit ve vlastním terminálu: {set}",
|
|
1044
|
+
"ui.error.consent.refused": "nepovoleno – nic se nezměnilo",
|
|
1045
|
+
"ui.error.consent.refused.hint": "kdykoli znovu: {set}",
|
|
1043
1046
|
"ui.status.disk": "Na disku: výzkum {research} · vedle něj si strom drží {strom}",
|
|
1044
1047
|
"ui.status.disk.frees": " (strom tidy uvolní {size})",
|
|
1045
1048
|
"ui.status.refused": "{host} odmítl automatický přístup stromu — snímky ručně; zrušit: strom allow host {host} --unblock",
|
|
@@ -1215,6 +1218,19 @@
|
|
|
1215
1218
|
"ui.error.tree.empty": "soubor je prázdný, nejsou v něm žádné osoby",
|
|
1216
1219
|
"ui.error.tree.empty.hint": "soubor vyexportovaný z aplikace Strom nebo z jiného programu pro rodokmeny",
|
|
1217
1220
|
"ui.error.tree.unreadable": "tohle není soubor GEDCOM ani rodokmen z aplikace Strom",
|
|
1221
|
+
"ui.error.read.heic": "{file}: fotku HEIC nejde na tomto počítači převést pro čtení – originál zůstává beze změny",
|
|
1222
|
+
"ui.error.read.heic.windows.hint": "rozšíření HEIF a HEVC z Microsoft Storu, nebo fotku uložit jako JPEG",
|
|
1223
|
+
"ui.error.read.heic.mac.hint": "fotku uložit jako JPEG (Náhled: Soubor → Exportovat)",
|
|
1224
|
+
"ui.error.read.heic.linux.hint": "nainstalovat heif-convert (libheif) nebo ImageMagick, nebo fotku uložit jako JPEG",
|
|
1225
|
+
"ui.error.read.pdf-render": "{file}: toto PDF nejde na tomto počítači převést na obrázky pro čtení – originál zůstává beze změny",
|
|
1226
|
+
"ui.error.read.pdf-render.hint": "strany uložit jako JPEG",
|
|
1227
|
+
"ui.error.read.pdf-render.linux.hint": "nainstalovat pdftoppm (poppler-utils), nebo strany uložit jako JPEG",
|
|
1228
|
+
"ui.error.read.convert": "{file} se nepodařilo převést pro čtení – originál zůstává beze změny",
|
|
1229
|
+
"ui.error.read.convert.hint": "soubor uložit jako JPEG (umí to každý prohlížeč obrázků)",
|
|
1230
|
+
"ui.error.read.type": "{file} není obrázek, který jde přečíst (JPEG, PNG, HEIC nebo PDF) – originál zůstává beze změny",
|
|
1231
|
+
"ui.error.read.type.hint": "soubor uložit jako JPEG (umí to každý prohlížeč obrázků)",
|
|
1232
|
+
"ui.error.read.page": "{file} má {pages, plural, one {jednu stranu} few {# strany} other {# stran}}, strana {page} v něm není",
|
|
1233
|
+
"ui.error.read.page.hint": "strana od 1 do {pages}",
|
|
1218
1234
|
"ui.error.adopt.none": "tento výzkum na žádný strom nečeká",
|
|
1219
1235
|
"ui.error.tree.unknown": "žádný rodokmen „{name}“ tu není",
|
|
1220
1236
|
"ui.error.tree.unknown.hint": "rodokmeny, které tu jsou: strom trees",
|
package/assets/lang/de.json
CHANGED
|
@@ -59,7 +59,6 @@
|
|
|
59
59
|
"ui.dialog.title": "Strom Ahnenforschung",
|
|
60
60
|
"ui.dialog.yes": "Erlauben",
|
|
61
61
|
"ui.dialog.no": "Nicht erlauben",
|
|
62
|
-
"ui.dialog.refused": "Nicht erlaubt – nichts wurde geändert.",
|
|
63
62
|
"ui.link.isolated": "Dies ist eine isolierte Installation (STROM_ISOLATED): sie registriert keine strom-research://-Links – diese bleiben beim eigenen strom.",
|
|
64
63
|
"ui.doc.isolated": "isolierte Installation in {folder}",
|
|
65
64
|
"ui.doc.second": "zweite Installation: der Befehl {command}, in {folder}, ihre Links {scheme}://",
|
|
@@ -760,9 +759,10 @@
|
|
|
760
759
|
"ui.update.available": "Eine neue Version ist da: {version} (diese ist {current}) – strom update installiert sie.",
|
|
761
760
|
"ui.update.npm": "Eine neue Version ist da: {version}. strom ist hier mit npm installiert: npm install -g strom-research@{tag}",
|
|
762
761
|
"ui.update.sure": "strom von {current} auf {version} aktualisieren? Die Forschungen bleiben, wie sie sind.",
|
|
763
|
-
"ui.update.later": "Nicht
|
|
762
|
+
"ui.update.later": "Nicht aktualisiert – nichts wurde installiert, strom bleibt bei {current}. Jederzeit erneut: {command}",
|
|
764
763
|
"ui.update.downloading": "strom {version} wird heruntergeladen…",
|
|
765
|
-
"ui.update.failed": "
|
|
764
|
+
"ui.error.update.failed": "die Aktualisierung hat nicht geklappt: {detail}. strom bleibt bei {current}",
|
|
765
|
+
"ui.error.update.failed.hint": "erneut {command} – oder das Installationsprogramm erneut ausführen",
|
|
766
766
|
"ui.update.done": "✓ strom ist aktualisiert: {previous} → {version}.",
|
|
767
767
|
"ui.update.whatsnew": "Was ist neu: {url}",
|
|
768
768
|
"ui.update.restart": "strom ist aktualisiert — dieses Menü läuft aber noch in der alten Version. Nach einem Neustart von strom (oder der Verknüpfung „{shortcut}“) läuft die neue Version.",
|
|
@@ -1022,6 +1022,7 @@
|
|
|
1022
1022
|
"ui.tidy.kind.views": "Ansichten von Scans (werden bei Bedarf neu erstellt)",
|
|
1023
1023
|
"ui.tidy.kind.excerpts": "Ausschnitte aus Scans (werden bei Bedarf neu erstellt)",
|
|
1024
1024
|
"ui.tidy.kind.fetch": "Arbeitsordner abgebrochener Downloads",
|
|
1025
|
+
"ui.tidy.kind.converted": "Fotos und PDF-Seiten, zum Lesen umgewandelt (bei Bedarf neu erstellt)",
|
|
1025
1026
|
"ui.tidy.kind.kept": "Ältere Sendungen der Strom-App, schon geschrieben (die Änderungen sind in der Geschichte)",
|
|
1026
1027
|
"ui.tidy.kind.inbox": "Längst vergangene Sendungen der Strom-App, die ein älterer strom im Eingang ließ",
|
|
1027
1028
|
"ui.tidy.kind.pointers": "Reste beendeter Arbeitssitzungen",
|
|
@@ -1040,6 +1041,8 @@
|
|
|
1040
1041
|
"ui.tidy.hint": "Neben der Forschung hält strom {strom}, davon lassen sich {size} freigeben: strom tidy (im Menü Einstellungen → Wartung → Speicherplatz)",
|
|
1041
1042
|
"ui.error.consent": "dafür braucht es eine Zustimmung – {question}",
|
|
1042
1043
|
"ui.error.consent.hint": "im eigenen Terminal ausführen: {set}",
|
|
1044
|
+
"ui.error.consent.refused": "nicht erlaubt – nichts wurde geändert",
|
|
1045
|
+
"ui.error.consent.refused.hint": "jederzeit erneut: {set}",
|
|
1043
1046
|
"ui.status.disk": "Auf der Festplatte: die Forschung {research} · daneben hält strom {strom}",
|
|
1044
1047
|
"ui.status.disk.frees": " (strom tidy gibt {size} frei)",
|
|
1045
1048
|
"ui.status.refused": "{host} hat den automatischen Zugriff von strom abgelehnt — Bilder von Hand; aufheben: strom allow host {host} --unblock",
|
|
@@ -1215,6 +1218,19 @@
|
|
|
1215
1218
|
"ui.error.tree.empty": "die Datei ist leer, es sind keine Personen darin",
|
|
1216
1219
|
"ui.error.tree.empty.hint": "eine Datei, aus der Strom-App oder einem anderen Stammbaumprogramm exportiert",
|
|
1217
1220
|
"ui.error.tree.unreadable": "das ist weder eine GEDCOM-Datei noch ein Stammbaum der Strom-App",
|
|
1221
|
+
"ui.error.read.heic": "{file}: ein HEIC-Foto lässt sich auf diesem Computer nicht zum Lesen umwandeln – das Original bleibt unverändert",
|
|
1222
|
+
"ui.error.read.heic.windows.hint": "die Erweiterungen HEIF und HEVC aus dem Microsoft Store, oder das Foto als JPEG speichern",
|
|
1223
|
+
"ui.error.read.heic.mac.hint": "das Foto als JPEG speichern (Vorschau: Ablage → Exportieren)",
|
|
1224
|
+
"ui.error.read.heic.linux.hint": "heif-convert (libheif) oder ImageMagick installieren, oder das Foto als JPEG speichern",
|
|
1225
|
+
"ui.error.read.pdf-render": "{file}: dieses PDF lässt sich auf diesem Computer nicht in Bilder zum Lesen umwandeln – das Original bleibt unverändert",
|
|
1226
|
+
"ui.error.read.pdf-render.hint": "die Seiten als JPEG speichern",
|
|
1227
|
+
"ui.error.read.pdf-render.linux.hint": "pdftoppm (poppler-utils) installieren, oder die Seiten als JPEG speichern",
|
|
1228
|
+
"ui.error.read.convert": "{file} ließ sich nicht zum Lesen umwandeln – das Original bleibt unverändert",
|
|
1229
|
+
"ui.error.read.convert.hint": "die Datei als JPEG speichern (jeder Bildbetrachter kann das)",
|
|
1230
|
+
"ui.error.read.type": "{file} ist kein Bild, das sich lesen lässt (JPEG, PNG, HEIC oder PDF) – das Original bleibt unverändert",
|
|
1231
|
+
"ui.error.read.type.hint": "die Datei als JPEG speichern (jeder Bildbetrachter kann das)",
|
|
1232
|
+
"ui.error.read.page": "{file} hat {pages, plural, one {eine Seite} other {# Seiten}}, keine Seite {page}",
|
|
1233
|
+
"ui.error.read.page.hint": "eine Seite von 1 bis {pages}",
|
|
1218
1234
|
"ui.error.adopt.none": "diese Forschung wartet auf keinen Stammbaum",
|
|
1219
1235
|
"ui.error.tree.unknown": "keinen Stammbaum „{name}“ gibt es hier",
|
|
1220
1236
|
"ui.error.tree.unknown.hint": "die Stammbäume hier: strom trees",
|
package/dist/cli/context.js
CHANGED
|
@@ -209,8 +209,10 @@ export class Context {
|
|
|
209
209
|
const answer = this.io.dialog ? this.io.dialog(text) : systemDialog(text, this.env);
|
|
210
210
|
if (answer === true)
|
|
211
211
|
return "window";
|
|
212
|
+
// said no: an agent reads the English and what not to do; a person (the app, a script) their language — nothing
|
|
213
|
+
// changed and how to ask again (printError: ui.error.consent.refused), never the agent's English under it
|
|
212
214
|
if (answer === false)
|
|
213
|
-
throw new StromError(ui(
|
|
215
|
+
throw new StromError(ui("en", "ui.error.consent.refused"), { hint: `the user said no to: ${question} — do not ask again unless they want to`, code: "consent.refused", params: { set } });
|
|
214
216
|
}
|
|
215
217
|
// a person (a script, no terminal) reads it in their language; an agent the English it goes by
|
|
216
218
|
// a run nobody watches: nobody to say yes before it ends — a task for it, for the user to see, and on another way
|
package/dist/cli/ui.js
CHANGED
|
@@ -15,7 +15,6 @@ export const UI = {
|
|
|
15
15
|
"ui.dialog.title": "Strom research",
|
|
16
16
|
"ui.dialog.yes": "Allow",
|
|
17
17
|
"ui.dialog.no": "Don't allow",
|
|
18
|
-
"ui.dialog.refused": "Not allowed — nothing was changed.",
|
|
19
18
|
"ui.link.isolated": "This is an isolated installation (STROM_ISOLATED): it registers no strom-research:// links — they stay the regular strom's.",
|
|
20
19
|
"ui.doc.isolated": "isolated installation in {folder}",
|
|
21
20
|
"ui.doc.second": "a second installation: the command {command}, in {folder}, its links {scheme}://",
|
|
@@ -687,9 +686,10 @@ export const UI = {
|
|
|
687
686
|
"ui.update.available": "A new version is out: {version} (this is {current}) — strom update installs it.",
|
|
688
687
|
"ui.update.npm": "A new version is out: {version}. strom is installed with npm here: npm install -g strom-research@{tag}",
|
|
689
688
|
"ui.update.sure": "Update strom from {current} to {version}? The research stays as it is.",
|
|
690
|
-
"ui.update.later": "Not
|
|
689
|
+
"ui.update.later": "Not updated — nothing was installed, strom stays at {current}. Any time again: {command}",
|
|
691
690
|
"ui.update.downloading": "Downloading strom {version}…",
|
|
692
|
-
"ui.update.failed": "
|
|
691
|
+
"ui.error.update.failed": "the update did not work: {detail}. strom stays at {current}",
|
|
692
|
+
"ui.error.update.failed.hint": "{command} again — or run the installer again",
|
|
693
693
|
"ui.update.done": "✓ strom is updated: {previous} → {version}.",
|
|
694
694
|
"ui.update.whatsnew": "What's new: {url}",
|
|
695
695
|
"ui.update.restart": "strom is updated — this menu still runs the old version. Start strom again (or the shortcut \"{shortcut}\"), and the new version is at work.",
|
|
@@ -926,6 +926,7 @@ export const UI = {
|
|
|
926
926
|
"ui.tidy.kind.views": "Views of scans (made again when needed)",
|
|
927
927
|
"ui.tidy.kind.excerpts": "Excerpts of scans (made again when needed)",
|
|
928
928
|
"ui.tidy.kind.fetch": "Work folders of downloads cut short",
|
|
929
|
+
"ui.tidy.kind.converted": "Photos and PDF pages converted for reading (made again when needed)",
|
|
929
930
|
"ui.tidy.kind.kept": "Sends of the Strom app written long ago (their changes are in the history)",
|
|
930
931
|
"ui.tidy.kind.inbox": "Sends of the Strom app long ago, left in the inbox by an older strom",
|
|
931
932
|
"ui.tidy.kind.pointers": "Leftovers of work sessions that ended",
|
|
@@ -944,6 +945,8 @@ export const UI = {
|
|
|
944
945
|
"ui.tidy.hint": "Beside the research strom keeps {strom}, {size} of it can go: strom tidy (the menu: Settings → Maintenance → Disk space)",
|
|
945
946
|
"ui.error.consent": "this needs a yes — {question}",
|
|
946
947
|
"ui.error.consent.hint": "run in a terminal of its own: {set}",
|
|
948
|
+
"ui.error.consent.refused": "not allowed — nothing was changed",
|
|
949
|
+
"ui.error.consent.refused.hint": "any time again: {set}",
|
|
947
950
|
"ui.status.disk": "On disk: the research {research} · strom's own beside it {strom}",
|
|
948
951
|
"ui.status.disk.frees": " (strom tidy frees {size})",
|
|
949
952
|
"ui.status.refused": "{host} refused strom's automated access — images by hand; to lift it: strom allow host {host} --unblock",
|
|
@@ -1128,6 +1131,19 @@ export const UI = {
|
|
|
1128
1131
|
"ui.error.tree.empty": "the file is empty: no people in it",
|
|
1129
1132
|
"ui.error.tree.empty.hint": "a file exported from the Strom app or another family-tree program",
|
|
1130
1133
|
"ui.error.tree.unreadable": "this is neither a GEDCOM file nor a family tree of the Strom app",
|
|
1134
|
+
"ui.error.read.heic": "{file}: a HEIC photo cannot be converted for reading on this computer — the original stays as it is",
|
|
1135
|
+
"ui.error.read.heic.windows.hint": "the HEIF and HEVC extensions from the Microsoft Store, or the photo saved as JPEG",
|
|
1136
|
+
"ui.error.read.heic.mac.hint": "the photo saved as JPEG (Preview: File → Export)",
|
|
1137
|
+
"ui.error.read.heic.linux.hint": "heif-convert (libheif) or ImageMagick installed, or the photo saved as JPEG",
|
|
1138
|
+
"ui.error.read.pdf-render": "{file}: this PDF cannot be turned into pictures for reading on this computer — the original stays as it is",
|
|
1139
|
+
"ui.error.read.pdf-render.hint": "the pages saved as JPEG",
|
|
1140
|
+
"ui.error.read.pdf-render.linux.hint": "pdftoppm (poppler-utils) installed, or the pages saved as JPEG",
|
|
1141
|
+
"ui.error.read.convert": "{file} could not be converted for reading — the original stays as it is",
|
|
1142
|
+
"ui.error.read.convert.hint": "the file saved as JPEG (any image viewer can)",
|
|
1143
|
+
"ui.error.read.type": "{file} is no picture that can be read (JPEG, PNG, HEIC or PDF) — the original stays as it is",
|
|
1144
|
+
"ui.error.read.type.hint": "the file saved as JPEG (any image viewer can)",
|
|
1145
|
+
"ui.error.read.page": "{file} has {pages, plural, one {one page} other {# pages}}, no page {page}",
|
|
1146
|
+
"ui.error.read.page.hint": "a page from 1 to {pages}",
|
|
1131
1147
|
"ui.error.adopt.none": "this research waits for no tree",
|
|
1132
1148
|
"ui.error.tree.unknown": "no tree \"{name}\"",
|
|
1133
1149
|
"ui.error.tree.unknown.hint": "strom trees",
|
package/dist/commands/clips.js
CHANGED
|
@@ -10,6 +10,8 @@ import { counted, lines, truncate } from "../cli/format.js";
|
|
|
10
10
|
import { UsageError } from "../core/errors.js";
|
|
11
11
|
import { normId, requireRecord, update } from "../core/records.js";
|
|
12
12
|
import { makeView } from "../core/views.js";
|
|
13
|
+
import { shownToFile } from "../image/image.js";
|
|
14
|
+
import { exifOrientation } from "../image/index.js";
|
|
13
15
|
import { clipText, MAX_CLIPS, regionText } from "../core/media.js";
|
|
14
16
|
import { withMargin } from "../core/excerpt.js";
|
|
15
17
|
import { batches, READER_MINUTES_HELP } from "../core/reader.js";
|
|
@@ -161,10 +163,12 @@ register({
|
|
|
161
163
|
// what the readers cost goes to the session that started them (apart from the agent's own)
|
|
162
164
|
addReaders(tree, ctx.env, ctx.refs, { usd: r0.cost(), runs: r0.runs(), partial: r0.partial() });
|
|
163
165
|
// 3. What passed becomes the source's clips.
|
|
166
|
+
// the readers saw each image upright; a clip is kept as the file lies (the app turns it as it turns the image)
|
|
164
167
|
const clipsOf = new Map();
|
|
168
|
+
const asFile = (l) => shownToFile(l.region, exifOrientation(path.join(shared, all.get(l.media).file)));
|
|
165
169
|
for (const l of hits)
|
|
166
170
|
if (passed.has(`${l.source} ${l.media}`))
|
|
167
|
-
clipsOf.set(l.source, [...(clipsOf.get(l.source) ?? []), { media: l.media, region: l
|
|
171
|
+
clipsOf.set(l.source, [...(clipsOf.get(l.source) ?? []), { media: l.media, region: asFile(l) }].slice(0, MAX_CLIPS));
|
|
168
172
|
for (const [id, clips] of clipsOf)
|
|
169
173
|
update(tree, id, "source", (s) => ({ ...s, clips }), { op: "source.edit", summary: `${id} clips ${clips.map(clipText).join(" ")} (found by a reader, checked)` });
|
|
170
174
|
// The reports are working notes of the research: committed with it.
|
package/dist/commands/media.js
CHANGED
|
@@ -11,9 +11,10 @@ import { notFound, UsageError } from "../core/errors.js";
|
|
|
11
11
|
import { makeNote } from "../core/actions.js";
|
|
12
12
|
import { clipText, collectFiles, coveringPart, fileSha256, findImage, imageNumbers, imageOfRef, imageRange, IMAGE_FILE_EXT, inboxFolders, inputPath, isWhole, mimeOf, onlyParts, otherCopies, parseImageList, regionText, sharperPart, storeShared, wholeSizeOf } from "../core/media.js";
|
|
13
13
|
import { create, normId, requireRecord, update } from "../core/records.js";
|
|
14
|
-
import { imageSizeOfFile } from "../image/index.js";
|
|
14
|
+
import { exifOrientation, imageSizeOfFile } from "../image/index.js";
|
|
15
|
+
import { fileToShown, shownToFile, turnsAcross } from "../image/image.js";
|
|
15
16
|
import { imageOf, pageOf } from "../core/calibration.js";
|
|
16
|
-
import { cropOf, describeView, ENLARGED_HINT, halves, makeView, OVERVIEW_HINT, parseCrop, partRegion, readInHalves, REDUCED_HINT, SHARPER_SCAN, viewLine, viewRegion, viewSize } from "../core/views.js";
|
|
17
|
+
import { cropOf, describeView, ENLARGED_HINT, halves, makeView, mediaOrient, OVERVIEW_HINT, parseCrop, partRegion, readInHalves, REDUCED_HINT, SHARPER_SCAN, viewLine, viewRegion, viewSize } from "../core/views.js";
|
|
17
18
|
import { IMAGE_MAX, IMAGE_MAX_LARGE, OVERVIEW_MAX } from "../agents/images.js";
|
|
18
19
|
import { viewSizesFor } from "../core/viewsizes.js";
|
|
19
20
|
import { PROFILES } from "../agents/profiles.js";
|
|
@@ -141,13 +142,18 @@ function resolveTarget(tree, shared, ref, opts) {
|
|
|
141
142
|
throw new UsageError(`image ${image} of ${b.id} is not registered`, { hint: `strom media list --recordset ${b.id} · strom media add <files> --recordset ${b.id}` });
|
|
142
143
|
return { key: m.id, file: path.join(shared, m.file), media: m };
|
|
143
144
|
}
|
|
144
|
-
|
|
145
|
+
// a page of a document the user gave: I0002:3
|
|
146
|
+
const paged = /^([Ii]\d+):(\d+)$/.exec(ref.trim());
|
|
147
|
+
const id = normId(paged ? paged[1] : ref);
|
|
145
148
|
if (id.startsWith("I")) {
|
|
146
149
|
const i = requireRecord(tree, id, "input");
|
|
147
150
|
const file = inputPath(tree, i);
|
|
148
151
|
if (!file)
|
|
149
152
|
throw new UsageError(`${i.id} has no file`);
|
|
150
|
-
|
|
153
|
+
const page = paged ? Number(paged[2]) : undefined;
|
|
154
|
+
if (page !== undefined && page < 1)
|
|
155
|
+
throw new UsageError(`no page ${page}`, { hint: `${i.id}:1 is its first page` });
|
|
156
|
+
return { key: i.id, file, ...(page !== undefined ? { page } : {}) };
|
|
151
157
|
}
|
|
152
158
|
const m = requireRecord(tree, id, "media");
|
|
153
159
|
return { key: m.id, file: path.join(shared, m.file), media: m };
|
|
@@ -243,8 +249,10 @@ function partSpecs(t, p, max, inHalves = false) {
|
|
|
243
249
|
const size = ofParts ?? (t.media?.width && t.media.height ? { width: t.media.width, height: t.media.height } : imageSizeOfFile(t.file));
|
|
244
250
|
if (!size)
|
|
245
251
|
return [{ half: p.half, crop: p.crop, label: p.label }]; // no image there: the view says why
|
|
246
|
-
|
|
247
|
-
const
|
|
252
|
+
// the boxes are of the picture as the view shows it: a photo turned a quarter by its EXIF has its sides swapped
|
|
253
|
+
const across = !ofParts && turnsAcross(exifOrientation(t.file));
|
|
254
|
+
const W = across ? size.height : size.width;
|
|
255
|
+
const H = across ? size.width : size.height;
|
|
248
256
|
let boxes;
|
|
249
257
|
if (p.both) {
|
|
250
258
|
// the halves rule: the two pages of a spread only where they are sharper than one view of it — a book read worse
|
|
@@ -489,11 +497,11 @@ function resolveTargets(tree, shared, all, args, opts, parts, maxViews = VIEW_MA
|
|
|
489
497
|
}
|
|
490
498
|
else {
|
|
491
499
|
const r = resolveTarget(tree, shared, w.ref, {});
|
|
492
|
-
t = { ...r, ref: r.media && !r.media.part && r.media.recordset && r.media.image !== undefined ? `${r.media.recordset}:${r.media.image}` : r.key };
|
|
500
|
+
t = { ...r, ref: r.media && !r.media.part && r.media.recordset && r.media.image !== undefined ? `${r.media.recordset}:${r.media.image}` : r.page ? `${r.key}:${r.page}` : r.key };
|
|
493
501
|
}
|
|
494
|
-
if (seen.has(t.key))
|
|
502
|
+
if (seen.has(`${t.key}:${t.page ?? 1}`))
|
|
495
503
|
continue;
|
|
496
|
-
seen.add(t.key);
|
|
504
|
+
seen.add(`${t.key}:${t.page ?? 1}`);
|
|
497
505
|
targets.push(t);
|
|
498
506
|
}
|
|
499
507
|
if (!targets.length && !several) {
|
|
@@ -513,24 +521,29 @@ function oneView(ctx, tree, shared, all, target, asked, meta = {}, noSharper = f
|
|
|
513
521
|
let v;
|
|
514
522
|
let sharper;
|
|
515
523
|
if (t.media?.width && t.media.height && !t.media.part) {
|
|
516
|
-
|
|
517
|
-
const
|
|
524
|
+
// the place asked is of the picture shown (upright by its EXIF); parts are kept as the file lies
|
|
525
|
+
const o = mediaOrient(shared, t.media);
|
|
526
|
+
const [W, H] = turnsAcross(o) ? [t.media.height, t.media.width] : [t.media.width, t.media.height];
|
|
527
|
+
const px = viewRegion(spec, W, H);
|
|
528
|
+
const s = sharperPart(all, t.media, shownToFile({ x: px.x / W, y: px.y / H, w: px.w / W, h: px.h / H }, o));
|
|
518
529
|
if (s) {
|
|
519
530
|
sharper = s;
|
|
520
531
|
shown = s.part;
|
|
521
532
|
const whole = !s.part.part && !spec.crop && !spec.half;
|
|
522
|
-
v = makeView(tree, path.join(shared, s.part.file), s.part.id, { ...spec, half: undefined, crop: whole ? undefined : regionText(s.crop) }, meta);
|
|
533
|
+
v = makeView(tree, path.join(shared, s.part.file), s.part.id, { ...spec, half: undefined, crop: whole ? undefined : regionText(fileToShown(s.crop, mediaOrient(shared, s.part))) }, meta);
|
|
523
534
|
}
|
|
524
535
|
}
|
|
525
|
-
v ??= makeView(tree, t.file, t.key, spec, meta);
|
|
536
|
+
v ??= makeView(tree, t.file, t.key, t.page ? { ...spec, page: t.page } : spec, meta);
|
|
526
537
|
const page = t.media ? pageLabel(tree, t.media) : "";
|
|
527
538
|
// still too little detail: a connector that fetched the image may fetch this part of it sharper
|
|
528
539
|
const base = shown?.part ?? { x: 0, y: 0, w: 1, h: 1 };
|
|
540
|
+
// what the view shows, as the picture lies in its file (a photo shown turned by its EXIF): where clips are kept
|
|
541
|
+
const inFile = shownToFile({ x: v.region.x / v.original.width, y: v.region.y / v.original.height, w: v.region.w / v.original.width, h: v.region.h / v.original.height }, v.orient);
|
|
529
542
|
const inWhole = {
|
|
530
|
-
x: base.x +
|
|
531
|
-
y: base.y +
|
|
532
|
-
w:
|
|
533
|
-
h:
|
|
543
|
+
x: base.x + inFile.x * base.w,
|
|
544
|
+
y: base.y + inFile.y * base.h,
|
|
545
|
+
w: inFile.w * base.w,
|
|
546
|
+
h: inFile.h * base.h,
|
|
534
547
|
};
|
|
535
548
|
const whole = t.media?.recordset !== undefined && t.media.image !== undefined ? findImage(all, t.media.recordset, t.media.image) : undefined;
|
|
536
549
|
const from = whole?.fetched ? listConnectors(shared).find((c) => c.name === whole.fetched.connector) : undefined;
|
|
@@ -538,11 +551,12 @@ function oneView(ctx, tree, shared, all, target, asked, meta = {}, noSharper = f
|
|
|
538
551
|
const fetchPart = canPart && !noSharper && v.scale > 1.25 && inWhole.w * inWhole.h < 0.9
|
|
539
552
|
? `strom fetch ${from.name} --recordset ${whole.recordset} --images ${whole.image} --crop ${regionText(inWhole)}`
|
|
540
553
|
: undefined;
|
|
541
|
-
// The same part as a clip of the source read in it: of the whole image where it is registered
|
|
554
|
+
// The same part as a clip of the source read in it: of the whole image where it is registered — as --clip takes it,
|
|
555
|
+
// of the picture as its view shows it (source add keeps it as the file lies)
|
|
542
556
|
const clip = !t.media || (!spec.crop && !spec.half)
|
|
543
557
|
? undefined
|
|
544
558
|
: whole && !whole.part
|
|
545
|
-
? clipText({ media: whole.id, region: inWhole })
|
|
559
|
+
? clipText({ media: whole.id, region: fileToShown(inWhole, mediaOrient(shared, whole)) })
|
|
546
560
|
: clipText({ media: t.key, region: { x: v.region.x / v.original.width, y: v.region.y / v.original.height, w: v.region.w / v.original.width, h: v.region.h / v.original.height } });
|
|
547
561
|
return { v, page, ...(t !== target ? { part: t.media } : {}), ...(sharper ? { sharper } : {}), ...(fetchPart ? { fetchPart } : {}), ...(clip ? { clip } : {}) };
|
|
548
562
|
}
|
|
@@ -697,7 +711,8 @@ register({
|
|
|
697
711
|
if (it.image === undefined)
|
|
698
712
|
throw new UsageError("which image is it a part of? --image <n>", { hint: "strom media add <file> --recordset B… --image <n> --half left" });
|
|
699
713
|
const whole = findImage(tree.list("media").filter((m) => !m.part), recordset, it.image);
|
|
700
|
-
it
|
|
714
|
+
// of the whole image as its view shows it (upright by its EXIF)
|
|
715
|
+
it.part = partRegion(opts, whole, mediaOrient(shared, whole));
|
|
701
716
|
}
|
|
702
717
|
if (recordset) {
|
|
703
718
|
// an image of a book without its number cannot be cited — nor found again
|
|
@@ -845,7 +860,7 @@ register({
|
|
|
845
860
|
"--half right; B0001 --image 2 --crop … B0001 --image 7 --crop …; B0001 2 --crop … 7 --crop …); parts after the\n" +
|
|
846
861
|
"last image are of every image. At most\n" +
|
|
847
862
|
`${VIEW_MAX_IMAGES} images and ${VIEW_MAX_VIEWS} views in one call: every view stays in the context that opens it.`,
|
|
848
|
-
args: [{ name: "image", description: "M0012, B0001:57 (record set:image), a range B0001:57-60, B0001 with --page, or an input I0002; several = several images", required: true, variadic: true }],
|
|
863
|
+
args: [{ name: "image", description: "M0012, B0001:57 (record set:image), a range B0001:57-60, B0001 with --page, an input I0002, or a page of an input document I0002:3 (a PDF; HEIC and PDF are converted for reading, the original kept); several = several images", required: true, variadic: true }],
|
|
849
864
|
options: [
|
|
850
865
|
{ name: "crop", type: "string", multiple: true, value: "<x,y,w,h>", description: "part of the image: fractions (0.1,0.35,0.4,0.2) or pixels; repeated = several parts; after one image of several: of that image alone (--half, --split, --grid too)" },
|
|
851
866
|
{ name: "half", type: "string", multiple: true, value: "<side>", description: "left or right page of a double page (top, bottom); both = the two pages, overlapping at the gutter" },
|
|
@@ -870,6 +885,7 @@ register({
|
|
|
870
885
|
"strom media view B0001:2 --crop 0.05,0.10,0.45,0.30 --crop 0.05,0.40,0.45,0.30",
|
|
871
886
|
"strom media view B0001:2 --crop 0.05,0.10,0.45,0.30 B0001:3 --crop 0.50,0.60,0.45,0.20",
|
|
872
887
|
"strom media view B0001 --page 112 113 --split 2x2",
|
|
888
|
+
"strom media view I0005:2 --half top",
|
|
873
889
|
],
|
|
874
890
|
run(ctx, { args, opts, seq }) {
|
|
875
891
|
// the whole image is what a view is without a part of it (strom read --whole: each image in one view)
|
package/dist/commands/pack.js
CHANGED
|
@@ -81,8 +81,9 @@ register({
|
|
|
81
81
|
async run(ctx, { args }) {
|
|
82
82
|
const lang = ctx.uiLang();
|
|
83
83
|
const src = ctx.resolvePath(args[0]);
|
|
84
|
+
// the English an agent (and a program) goes by; a person (a script, a launcher) reads it in their language
|
|
84
85
|
if (!ctx.interactive)
|
|
85
|
-
throw new NeedsConsentError([{ key: "unpack", kind: "consent", question: ui(
|
|
86
|
+
throw new NeedsConsentError([{ key: "unpack", kind: "consent", question: ui("en", "ui.unpack.person"), set: `strom unpack "${src}"` }], ui(lang, "ui.unpack.person"));
|
|
86
87
|
// someone new to strom (a launcher of the package installed it just now): the setup wizard first
|
|
87
88
|
if (!ctx.settings.home() && !isAgent(ctx.env)) {
|
|
88
89
|
await setupWizard(ctx);
|
package/dist/commands/read.js
CHANGED
|
@@ -17,7 +17,8 @@ import { IMAGE_MAX, IMAGE_MAX_LARGE } from "../agents/images.js";
|
|
|
17
17
|
import { viewSizesFor } from "../core/viewsizes.js";
|
|
18
18
|
import { currentSession } from "../core/session.js";
|
|
19
19
|
import { BATCH, BATCH_MAX, batches, NEXT_BLOCK, parseReport, READER_MINUTES_HELP, readerPrompt, readerSettings, viewCount, withoutNextBlock } from "../core/reader.js";
|
|
20
|
-
import {
|
|
20
|
+
import { exifOrientation, shownSize } from "../image/index.js";
|
|
21
|
+
import { turnsAcross } from "../image/image.js";
|
|
21
22
|
import { RUNNERS } from "../runners/index.js";
|
|
22
23
|
import { which, withoutAgentMarks } from "../core/which.js";
|
|
23
24
|
import { permissionPath } from "../agents/files.js";
|
|
@@ -306,13 +307,17 @@ register({
|
|
|
306
307
|
const file = path.join(ctx.settings.shared().value, m.file);
|
|
307
308
|
const page = m.page ?? (b && m.image !== undefined ? pageOf(b, m.image) : undefined);
|
|
308
309
|
const contrast = Boolean(opts.contrast);
|
|
309
|
-
|
|
310
|
+
// the image as a reader sees it: upright (a photo turned by its EXIF has its sides swapped)
|
|
311
|
+
const across = fs.existsSync(file) && turnsAcross(exifOrientation(file));
|
|
312
|
+
const W = across ? m.height : m.width;
|
|
313
|
+
const H = across ? m.width : m.height;
|
|
314
|
+
if (typeof opts.crop === "string" && W && H) {
|
|
310
315
|
// a crop is read at full resolution — in overlapping parts when it is bigger than one look
|
|
311
|
-
const all = tiles(parseCrop(partCrops.get(m.id) ?? opts.crop,
|
|
316
|
+
const all = tiles(parseCrop(partCrops.get(m.id) ?? opts.crop, W, H), max);
|
|
312
317
|
const parts = all.map((t) => ({ label: t.label, view: view(file, m, { crop: t.crop, max, contrast }, all.length > 1 ? "split" : "crop").file }));
|
|
313
318
|
return { id: m.id, image: m.image, page, view: parts[0].view, ...(parts.length > 1 ? { parts } : {}) };
|
|
314
319
|
}
|
|
315
|
-
const size = !opts.whole && fs.existsSync(file) ? (
|
|
320
|
+
const size = !opts.whole && fs.existsSync(file) ? (W && H ? { width: W, height: H } : shownSize(new Uint8Array(fs.readFileSync(file)))) : undefined;
|
|
316
321
|
if (size && half) {
|
|
317
322
|
// one page of a spread, with the strip past the gutter (a scan is seldom centred on it)
|
|
318
323
|
const v = view(file, m, { crop: cropOf(halves(size.width, size.height)[half]), max, contrast }, "half");
|
package/dist/commands/setup.js
CHANGED
|
@@ -45,7 +45,7 @@ import { agentSettingsLine, chooseEffort } from "../cli/model-choice.js";
|
|
|
45
45
|
import { calibrationKey, calibrationLabel, calibrationOffer, forgetCalibration, researchKey, sizesText, viewModel, viewSizesFor } from "../core/viewsizes.js";
|
|
46
46
|
import { bookTuned, bookViewSizes, forgetTuning, keyTuningOf, readingOf, selfTune, treeTuning } from "../core/tune.js";
|
|
47
47
|
import { loadAliases } from "../core/modelkey.js";
|
|
48
|
-
import { NeedsInputError, StromError, UsageError } from "../core/errors.js";
|
|
48
|
+
import { EXIT, NeedsInputError, StromError, UsageError } from "../core/errors.js";
|
|
49
49
|
import { worsenings } from "../core/tunereset.js";
|
|
50
50
|
import { resetReading } from "../cli/tunereset.js";
|
|
51
51
|
import { loadRollup, manyWeb } from "../core/readstats.js";
|
|
@@ -646,13 +646,16 @@ register({
|
|
|
646
646
|
: newer
|
|
647
647
|
? t("ui.update.sure", { version: newest, current: VERSION })
|
|
648
648
|
: t("ui.update.node.sure", { from: inst.node ?? "?", to: node.version });
|
|
649
|
+
const again = to ? `strom update --channel ${to}` : "strom update";
|
|
649
650
|
if (isAgent(ctx.env) || !ctx.yes) {
|
|
651
|
+
// said no: said so, nothing installed and the version it stays at, and not ok (exit 1) — never a quiet 0 that
|
|
652
|
+
// reads as done (found: strom update --channel beta ended 0 at the old version, nobody knew why)
|
|
650
653
|
if (!isAgent(ctx.env) && ctx.interactive) {
|
|
651
654
|
if (!(await ctx.confirm(question, true)))
|
|
652
|
-
return { text: t("ui.update.later"), data: { current: VERSION, latest: newest, updated: false, ...extra } };
|
|
655
|
+
return { text: t("ui.update.later", { current: VERSION, command: again }), data: { current: VERSION, latest: newest, updated: false, refused: true, ...extra }, exitCode: EXIT.error };
|
|
653
656
|
}
|
|
654
657
|
else
|
|
655
|
-
ctx.requireHuman(`update strom to ${newest}${to ? ` (--channel ${to})` : ""}`,
|
|
658
|
+
ctx.requireHuman(`update strom to ${newest}${to ? ` (--channel ${to})` : ""}`, again, "update", question);
|
|
656
659
|
}
|
|
657
660
|
// Another channel, or an older version: every research and the settings backed up before anything is installed —
|
|
658
661
|
// none made, nothing installed (Milan, 2026-10-07: "přechod beta ↔ produkce nikdy nepřijde o data")
|
|
@@ -666,7 +669,9 @@ register({
|
|
|
666
669
|
done = await installUpdate(ctx.env, inst.root, process.platform, { release: latest, keep: ctx.researchFolders(), ...(switching ? { channel: to } : {}) });
|
|
667
670
|
}
|
|
668
671
|
catch (e) {
|
|
669
|
-
|
|
672
|
+
// the person's language for a person (ui.error.update.failed and its hint), the English for an agent and a program
|
|
673
|
+
const params = { detail: e.message, current: VERSION, command: again };
|
|
674
|
+
throw new StromError(ui("en", "ui.error.update.failed", params), { hint: ui("en", "ui.error.update.failed.hint", params), code: "update.failed", params });
|
|
670
675
|
}
|
|
671
676
|
// The agents learn what the new version tells them.
|
|
672
677
|
const { command, args } = stromLauncher();
|
package/dist/commands/sources.js
CHANGED
|
@@ -11,7 +11,8 @@ import { makeNote, makeNotes, parseDate } from "../core/actions.js";
|
|
|
11
11
|
import { calibrationLine, fitCalibration } from "../core/calibration.js";
|
|
12
12
|
import { foldText } from "../core/text.js";
|
|
13
13
|
import { clipNote, clipText, imageOfRef, MAX_CLIPS, transcriptNote } from "../core/media.js";
|
|
14
|
-
import { partRegion } from "../core/views.js";
|
|
14
|
+
import { mediaOrient, partRegion } from "../core/views.js";
|
|
15
|
+
import { fileToShown } from "../image/image.js";
|
|
15
16
|
import { formatName } from "../core/people.js";
|
|
16
17
|
import { yearsOption, yearsOverlap } from "../core/years.js";
|
|
17
18
|
import { levelWords } from "../core/archiveasks.js";
|
|
@@ -49,8 +50,9 @@ function mediaRefs(tree, refs) {
|
|
|
49
50
|
/**
|
|
50
51
|
* Where the entry is on an image, as its view was cut: "M0012@0.05,0.4,0.45,0.18", "B0001:57@left:0.1,0.4,0.8,0.1"
|
|
51
52
|
* (a half, then a crop within it), pixels of the registered image, or "M0012" alone (the entry is the whole image).
|
|
53
|
+
* Of the picture as the view showed it (a photo upright, turned by its EXIF); kept as the picture lies in its file.
|
|
52
54
|
*/
|
|
53
|
-
function clipRefs(tree, specs) {
|
|
55
|
+
function clipRefs(tree, specs, shared) {
|
|
54
56
|
return specs.map((spec) => {
|
|
55
57
|
const m = /^([^@]+?)(?:@(.+))?$/.exec(spec.trim()) ?? [];
|
|
56
58
|
let [ref, where] = [m[1] ?? "", m[2]];
|
|
@@ -62,7 +64,8 @@ function clipRefs(tree, specs) {
|
|
|
62
64
|
if (!where)
|
|
63
65
|
return { media, region: { x: 0, y: 0, w: 1, h: 1 } };
|
|
64
66
|
const w = /^(?:(left|right|top|bottom)(?::|$))?(.*)$/.exec(where.trim());
|
|
65
|
-
const
|
|
67
|
+
const img = tree.get(media);
|
|
68
|
+
const region = partRegion({ half: w[1], crop: w[2] || undefined }, img, mediaOrient(shared, img));
|
|
66
69
|
return { media, region };
|
|
67
70
|
});
|
|
68
71
|
}
|
|
@@ -180,7 +183,7 @@ register({
|
|
|
180
183
|
requireRecord(tree, opts.repo, "repository");
|
|
181
184
|
if (opts.input)
|
|
182
185
|
requireRecord(tree, opts.input, "input");
|
|
183
|
-
const clips = clipRefs(tree, listOpt(opts.clip));
|
|
186
|
+
const clips = clipRefs(tree, listOpt(opts.clip), ctx.settings.shared()?.value);
|
|
184
187
|
if (clips.length > MAX_CLIPS)
|
|
185
188
|
throw new UsageError(`at most ${MAX_CLIPS} clips: an entry, and its continuation over a page break`);
|
|
186
189
|
const media = [...new Set([...mediaRefs(tree, csvOpt(opts.media)), ...clips.map((c) => c.media)])];
|
|
@@ -243,6 +246,7 @@ register({
|
|
|
243
246
|
args: [{ name: "source", description: "source ID (S0001)", required: true }],
|
|
244
247
|
run(ctx, { args }) {
|
|
245
248
|
const tree = ctx.tree();
|
|
249
|
+
const shared = ctx.settings.shared()?.value;
|
|
246
250
|
const s = requireRecord(tree, args[0], "source");
|
|
247
251
|
const citing = [];
|
|
248
252
|
const at = (c) => (c.locator ? ` (${c.locator})` : "");
|
|
@@ -265,7 +269,9 @@ register({
|
|
|
265
269
|
if (c.source === s.id)
|
|
266
270
|
citing.push(`${f.id} ${e.id} ${e.kind}${at(c)}`);
|
|
267
271
|
}
|
|
268
|
-
const text = lines(`${s.id} ${s.title}`, `kind ${s.kind} · ${s.form} · information ${s.information}${s.language ? ` · language ${s.language}` : ""}${s.date ? ` · dated ${s.date}` : ""}`, s.recordset || s.repository ? `in ${[s.recordset, s.repository].filter(Boolean).join(" · ")}${s.locator ? ` · ${s.locator}` : ""}` : s.locator ? `at ${s.locator}` : undefined, s.url ? `url ${s.url}` : undefined, s.media?.length ? `images ${s.media.map((id) => { const m = tree.get(id); return m ? `${id}${m.recordset ? ` (${m.recordset}:${m.image ?? "?"})` : ""}` : id; }).join(" · ")} — strom media view <M…>` : undefined,
|
|
272
|
+
const text = lines(`${s.id} ${s.title}`, `kind ${s.kind} · ${s.form} · information ${s.information}${s.language ? ` · language ${s.language}` : ""}${s.date ? ` · dated ${s.date}` : ""}`, s.recordset || s.repository ? `in ${[s.recordset, s.repository].filter(Boolean).join(" · ")}${s.locator ? ` · ${s.locator}` : ""}` : s.locator ? `at ${s.locator}` : undefined, s.url ? `url ${s.url}` : undefined, s.media?.length ? `images ${s.media.map((id) => { const m = tree.get(id); return m ? `${id}${m.recordset ? ` (${m.recordset}:${m.image ?? "?"})` : ""}` : id; }).join(" · ")} — strom media view <M…>` : undefined,
|
|
273
|
+
// as --clip takes them: of the picture as its view shows it
|
|
274
|
+
s.clips?.length ? `clips ${s.clips.map((c, i) => `${i + 1}. ${clipText({ media: c.media, region: fileToShown(c.region, mediaOrient(shared, tree.get(c.media))) })}`).join(" · ")} — where the entry is on its image` : undefined, s.transcript ? `\ntranscript\n${s.transcript}` : undefined, s.translation ? `\ntranslation\n${s.translation}` : undefined, citing.length ? `\ncited by\n${citing.map((c) => ` ${c}`).join("\n")}` : "\ncited by nothing yet", ...s.notes.map((n) => `note ${n.text}`));
|
|
269
275
|
return { text, data: { source: s, citedBy: citing } };
|
|
270
276
|
},
|
|
271
277
|
});
|
|
@@ -725,7 +731,7 @@ register({
|
|
|
725
731
|
requireRecord(tree, opts.recordset, "recordset");
|
|
726
732
|
if (opts.repo)
|
|
727
733
|
requireRecord(tree, opts.repo, "repository");
|
|
728
|
-
const clips = clipRefs(tree, listOpt(opts.clip));
|
|
734
|
+
const clips = clipRefs(tree, listOpt(opts.clip), ctx.settings.shared()?.value);
|
|
729
735
|
const drop = str(opts["clip-remove"]);
|
|
730
736
|
const media = [...new Set([...mediaRefs(tree, csvOpt(opts.media)), ...clips.map((c) => c.media)])];
|
|
731
737
|
const s = editFields(tree, args[0], "source", {
|