strom-research 1.18.0-beta.4 → 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 +2 -2
- package/CHANGELOG.de.md +3 -2
- package/CHANGELOG.md +3 -2
- package/dist/commands/document.js +23 -2
- package/dist/core/document.js +4 -0
- package/dist/core/live-read.js +24 -2
- package/dist/core/live.js +6 -1
- package/dist/core/tree.js +1 -1
- 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
|
|
@@ -21,7 +21,7 @@ Změny každé verze ve zkratce. Úplné poznámky a instalační řádky jsou n
|
|
|
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
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é.
|
|
24
|
+
nečte podruhé. Dokument čtený znovu se ukazuje pod názvem svého souboru, nikdy pod kódem.
|
|
25
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
|
|
26
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á
|
|
27
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
|
|
@@ -22,7 +22,8 @@ Die Änderungen jeder Version in Kürze. Die vollständigen Hinweise und die Ins
|
|
|
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
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.
|
|
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.
|
|
26
27
|
- **Fotos vom Handy.** `strom phone on`, das nur ein Mensch einschaltet, lässt ein Handy im Heimnetz (WLAN oder
|
|
27
28
|
Kabel — nie ein Mobilfunknetz oder ein VPN) zu diesem Computer. Das Handy wird einmal per QR-Code gekoppelt; seine
|
|
28
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
|
|
@@ -21,7 +21,8 @@ The changes of each version in short. The full notes and the install lines are o
|
|
|
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
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.
|
|
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.
|
|
25
26
|
- **Photos from a phone.** `strom phone on`, turned on by the person alone, lets a phone on the home network (Wi-Fi
|
|
26
27
|
or a cable — never a mobile network or a VPN) reach this computer. The phone is paired once by its QR code; its
|
|
27
28
|
page then takes photos or picks them from the gallery and sends them whole into a tree of the research, with a
|
|
@@ -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"))
|
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
|
@@ -1970,6 +1970,7 @@ export function serveLive(root, env) {
|
|
|
1970
1970
|
return typeof v === "string" ? v : undefined;
|
|
1971
1971
|
};
|
|
1972
1972
|
let name;
|
|
1973
|
+
let named;
|
|
1973
1974
|
let note;
|
|
1974
1975
|
let meta;
|
|
1975
1976
|
let region;
|
|
@@ -1991,7 +1992,8 @@ export function serveLive(root, env) {
|
|
|
1991
1992
|
at = "X-Strom-Path";
|
|
1992
1993
|
where = header("x-strom-path") ? batchPath(decodeURIComponent(header("x-strom-path"))) : undefined;
|
|
1993
1994
|
at = "X-Strom-Name";
|
|
1994
|
-
|
|
1995
|
+
named = decodeURIComponent(header("x-strom-name") ?? "").trim() || undefined;
|
|
1996
|
+
name = named ?? sha.slice(0, 12);
|
|
1995
1997
|
at = "X-Strom-Note";
|
|
1996
1998
|
note = header("x-strom-note") ? decodeURIComponent(header("x-strom-note")).slice(0, 500) : undefined;
|
|
1997
1999
|
at = "X-Strom-Person";
|
|
@@ -2034,6 +2036,9 @@ export function serveLive(root, env) {
|
|
|
2034
2036
|
if (no)
|
|
2035
2037
|
return refuse(409, refusal(no.message, no.code === "task.no-file" ? "waiting.no-file" : "waiting.none", { id: forTask }));
|
|
2036
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);
|
|
2037
2042
|
receiveOriginal(receiver, req, res, { sha, name, note, persons: meta.persons, source: meta.source, region, batch, where, zip, task: forTask, ...(read ? { read } : {}) });
|
|
2038
2043
|
};
|
|
2039
2044
|
const closeBatchNow = (id, extra, reply) => closeBatchBy(receiver, id, extra, reply);
|
package/dist/core/tree.js
CHANGED
|
@@ -27,7 +27,7 @@ import { lastBackup } from "./backup.js";
|
|
|
27
27
|
export const TREE_FILE = "strom.json";
|
|
28
28
|
// between releases the candidate being made (a bridge run from the sources says what it is: the Strom app goes by it);
|
|
29
29
|
// at a release the release's own (package.json)
|
|
30
|
-
export const VERSION = "1.18.0-beta.
|
|
30
|
+
export const VERSION = "1.18.0-beta.5";
|
|
31
31
|
const GITIGNORE = `# Strom Research — local state and large files are not versioned
|
|
32
32
|
.strom/
|
|
33
33
|
/media/
|