@miadi/chronicle-tui 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +85 -0
- package/dist/App.d.ts +22 -0
- package/dist/App.d.ts.map +1 -0
- package/dist/App.js +344 -0
- package/dist/App.js.map +1 -0
- package/dist/actions.d.ts +70 -0
- package/dist/actions.d.ts.map +1 -0
- package/dist/actions.js +94 -0
- package/dist/actions.js.map +1 -0
- package/dist/bin.d.ts +14 -0
- package/dist/bin.d.ts.map +1 -0
- package/dist/bin.js +63 -0
- package/dist/bin.js.map +1 -0
- package/dist/data.d.ts +56 -0
- package/dist/data.d.ts.map +1 -0
- package/dist/data.js +127 -0
- package/dist/data.js.map +1 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +5 -0
- package/dist/index.js.map +1 -0
- package/dist/model.d.ts +58 -0
- package/dist/model.d.ts.map +1 -0
- package/dist/model.js +170 -0
- package/dist/model.js.map +1 -0
- package/package.json +67 -0
package/dist/actions.js
ADDED
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The action registry (D6 of the proposal on jgwill/Miadi#706).
|
|
3
|
+
*
|
|
4
|
+
* Every operation the browser offers is one entry: a key, a label, when it
|
|
5
|
+
* applies, and a function that calls the door that already owns the
|
|
6
|
+
* operation. The key bar is drawn from this list, so an action added here
|
|
7
|
+
* appears on screen with no other change.
|
|
8
|
+
*
|
|
9
|
+
* Version 1 registers reading actions. Writes (answering an attention item,
|
|
10
|
+
* adding a review, relating an inquiry, lineage, taking a turn in a talking
|
|
11
|
+
* circle, steering a tide pane) join the same list, each through its own
|
|
12
|
+
* existing door, and declare `effect: "write"` so a host can gate them.
|
|
13
|
+
*/
|
|
14
|
+
import { episodeDoors, episodeOfReference, phoneDoor, proof, referenceDoors } from "./data.js";
|
|
15
|
+
/** What `o` and `r` act on: the selected reference, else the file, else the episode. */
|
|
16
|
+
async function doorsFor(context) {
|
|
17
|
+
if (context.reference)
|
|
18
|
+
return referenceDoors(context.chronicle, context.reference);
|
|
19
|
+
if (!context.episode)
|
|
20
|
+
return null;
|
|
21
|
+
return episodeDoors(context.chronicle, context.episode.name, context.file);
|
|
22
|
+
}
|
|
23
|
+
export const doorsAction = {
|
|
24
|
+
id: "doors",
|
|
25
|
+
key: "o",
|
|
26
|
+
label: "doors",
|
|
27
|
+
effect: "read",
|
|
28
|
+
matches: (input) => input === "o",
|
|
29
|
+
available: (context) => Boolean(context.episode || context.reference),
|
|
30
|
+
run: async (context) => {
|
|
31
|
+
const doors = await doorsFor(context);
|
|
32
|
+
if (!doors || doors.doors.length === 0)
|
|
33
|
+
return context.status("No door resolves for this.");
|
|
34
|
+
context.show({ type: "doors", doors, selected: 0 });
|
|
35
|
+
},
|
|
36
|
+
};
|
|
37
|
+
export const qrAction = {
|
|
38
|
+
id: "qr",
|
|
39
|
+
key: "r",
|
|
40
|
+
label: "QR",
|
|
41
|
+
effect: "read",
|
|
42
|
+
matches: (input) => input === "r",
|
|
43
|
+
available: (context) => Boolean(context.episode || context.reference),
|
|
44
|
+
run: async (context) => {
|
|
45
|
+
const doors = await doorsFor(context);
|
|
46
|
+
const door = doors ? phoneDoor(doors.doors) : undefined;
|
|
47
|
+
if (!doors || !door)
|
|
48
|
+
return context.status("No door resolves for this.");
|
|
49
|
+
context.show({ type: "qr", title: doors.title, door, invert: false });
|
|
50
|
+
},
|
|
51
|
+
};
|
|
52
|
+
export const proofAction = {
|
|
53
|
+
id: "proof",
|
|
54
|
+
key: "p",
|
|
55
|
+
label: "five-stage proof",
|
|
56
|
+
effect: "read",
|
|
57
|
+
matches: (input) => input === "p",
|
|
58
|
+
available: (context) => Boolean(context.episode) && context.view !== "file",
|
|
59
|
+
run: async (context) => {
|
|
60
|
+
const episode = context.episode.name;
|
|
61
|
+
context.show({ type: "proof", episode });
|
|
62
|
+
try {
|
|
63
|
+
context.show({ type: "proof", episode, status: await proof(context.chronicle.root, episode) });
|
|
64
|
+
}
|
|
65
|
+
catch (error) {
|
|
66
|
+
context.show({ type: "proof", episode, error: error.message });
|
|
67
|
+
}
|
|
68
|
+
},
|
|
69
|
+
};
|
|
70
|
+
export const followAction = {
|
|
71
|
+
id: "follow",
|
|
72
|
+
key: "Enter",
|
|
73
|
+
label: "follow reference",
|
|
74
|
+
effect: "read",
|
|
75
|
+
matches: (_input, key) => key.return,
|
|
76
|
+
available: (context) => context.view === "file" && Boolean(context.reference),
|
|
77
|
+
run: async (context) => {
|
|
78
|
+
const ref = context.reference;
|
|
79
|
+
if (ref.kind === "chronicle") {
|
|
80
|
+
const name = episodeOfReference(context.chronicle.rows, ref.uri);
|
|
81
|
+
if (name)
|
|
82
|
+
return context.jumpTo(name);
|
|
83
|
+
return context.status(`${ref.uri} names no single episode here.`);
|
|
84
|
+
}
|
|
85
|
+
const doors = await referenceDoors(context.chronicle, ref);
|
|
86
|
+
context.show({ type: "doors", doors, selected: 0 });
|
|
87
|
+
},
|
|
88
|
+
};
|
|
89
|
+
export const DEFAULT_ACTIONS = [followAction, doorsAction, qrAction, proofAction];
|
|
90
|
+
/** The first action this key press triggers here, if any. */
|
|
91
|
+
export function actionFor(actions, input, key, context) {
|
|
92
|
+
return actions.find((action) => action.matches(input, key) && action.available(context));
|
|
93
|
+
}
|
|
94
|
+
//# sourceMappingURL=actions.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"actions.js","sourceRoot":"","sources":["../src/actions.ts"],"names":[],"mappings":"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"}
|
package/dist/bin.d.ts
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* miadi-chronicle — browse the Miadi Chronicle from a terminal.
|
|
4
|
+
*
|
|
5
|
+
* miadi-chronicle the chronicle at $MIADI_CHRONICLE_ROOT
|
|
6
|
+
* miadi-chronicle --root <dir> another checkout
|
|
7
|
+
* miadi-chronicle 352 open on one episode (number, ep352 or folder name)
|
|
8
|
+
* miadi-chronicle --search "talking circle" open in full-text mode
|
|
9
|
+
* miadi-chronicle --filter review open with a title filter
|
|
10
|
+
*
|
|
11
|
+
* Exit codes: 0 ok, 2 usage (no root, no terminal).
|
|
12
|
+
*/
|
|
13
|
+
export {};
|
|
14
|
+
//# sourceMappingURL=bin.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bin.d.ts","sourceRoot":"","sources":["../src/bin.tsx"],"names":[],"mappings":";AACA;;;;;;;;;;GAUG"}
|
package/dist/bin.js
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
|
+
import { readFileSync } from "node:fs";
|
|
4
|
+
import { render } from "ink";
|
|
5
|
+
import { App } from "./App.js";
|
|
6
|
+
const USAGE = `miadi-chronicle [episode] [--root <dir>] [--search <text>] [--filter <text>]
|
|
7
|
+
|
|
8
|
+
Browse the Miadi Chronicle: list and filter episodes, search their text, read a
|
|
9
|
+
vessel's files, and open the room, the wheel or the vessel.
|
|
10
|
+
|
|
11
|
+
episode open on it: a number, epNNN or a folder name
|
|
12
|
+
--root <dir> the chronicle (default: $MIADI_CHRONICLE_ROOT)
|
|
13
|
+
--search <text> start in full-text mode
|
|
14
|
+
--filter <text> start with a title filter
|
|
15
|
+
--version, --help
|
|
16
|
+
|
|
17
|
+
Keys: / filter · Tab title|full text · ↑↓ j k · Enter open · Esc back
|
|
18
|
+
o doors · r QR · p five-stage proof · ? all keys · q quit
|
|
19
|
+
`;
|
|
20
|
+
function main(argv) {
|
|
21
|
+
let root = process.env.MIADI_CHRONICLE_ROOT?.trim() || "";
|
|
22
|
+
let search;
|
|
23
|
+
let filter;
|
|
24
|
+
let episode;
|
|
25
|
+
for (let index = 0; index < argv.length; index++) {
|
|
26
|
+
const arg = argv[index];
|
|
27
|
+
if (arg === "--help" || arg === "-h") {
|
|
28
|
+
process.stdout.write(USAGE);
|
|
29
|
+
return 0;
|
|
30
|
+
}
|
|
31
|
+
if (arg === "--version" || arg === "-v") {
|
|
32
|
+
const pkg = JSON.parse(readFileSync(new URL("../package.json", import.meta.url), "utf8"));
|
|
33
|
+
process.stdout.write(`${pkg.version}\n`);
|
|
34
|
+
return 0;
|
|
35
|
+
}
|
|
36
|
+
if (arg === "--root")
|
|
37
|
+
root = argv[++index] ?? "";
|
|
38
|
+
else if (arg === "--search")
|
|
39
|
+
search = argv[++index];
|
|
40
|
+
else if (arg === "--filter")
|
|
41
|
+
filter = argv[++index];
|
|
42
|
+
else if (arg.startsWith("--")) {
|
|
43
|
+
process.stderr.write(`Unknown option ${arg}\n\n${USAGE}`);
|
|
44
|
+
return 2;
|
|
45
|
+
}
|
|
46
|
+
else
|
|
47
|
+
episode = arg;
|
|
48
|
+
}
|
|
49
|
+
if (!root) {
|
|
50
|
+
process.stderr.write("No chronicle: set MIADI_CHRONICLE_ROOT or pass --root <dir>.\n");
|
|
51
|
+
return 2;
|
|
52
|
+
}
|
|
53
|
+
if (!process.stdin.isTTY || !process.stdout.isTTY) {
|
|
54
|
+
process.stderr.write("miadi-chronicle needs a terminal. For a plain list, run: inquiry-weave catalog\n");
|
|
55
|
+
return 2;
|
|
56
|
+
}
|
|
57
|
+
render(_jsx(App, { root: root, search: search, query: filter, episode: episode }), { alternateScreen: true });
|
|
58
|
+
return 0;
|
|
59
|
+
}
|
|
60
|
+
const code = main(process.argv.slice(2));
|
|
61
|
+
if (code !== 0)
|
|
62
|
+
process.exitCode = code;
|
|
63
|
+
//# sourceMappingURL=bin.js.map
|
package/dist/bin.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bin.js","sourceRoot":"","sources":["../src/bin.tsx"],"names":[],"mappings":";;AAcA,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAA;AACtC,OAAO,EAAE,MAAM,EAAE,MAAM,KAAK,CAAA;AAC5B,OAAO,EAAE,GAAG,EAAE,MAAM,UAAU,CAAA;AAE9B,MAAM,KAAK,GAAG;;;;;;;;;;;;;CAab,CAAA;AAED,SAAS,IAAI,CAAC,IAAc;IAC1B,IAAI,IAAI,GAAG,OAAO,CAAC,GAAG,CAAC,oBAAoB,EAAE,IAAI,EAAE,IAAI,EAAE,CAAA;IACzD,IAAI,MAA0B,CAAA;IAC9B,IAAI,MAA0B,CAAA;IAC9B,IAAI,OAA2B,CAAA;IAC/B,KAAK,IAAI,KAAK,GAAG,CAAC,EAAE,KAAK,GAAG,IAAI,CAAC,MAAM,EAAE,KAAK,EAAE,EAAE,CAAC;QACjD,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAE,CAAA;QACxB,IAAI,GAAG,KAAK,QAAQ,IAAI,GAAG,KAAK,IAAI,EAAE,CAAC;YACrC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,CAAA;YAC3B,OAAO,CAAC,CAAA;QACV,CAAC;QACD,IAAI,GAAG,KAAK,WAAW,IAAI,GAAG,KAAK,IAAI,EAAE,CAAC;YACxC,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,IAAI,GAAG,CAAC,iBAAiB,EAAE,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,MAAM,CAAC,CAAwB,CAAA;YAChH,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,GAAG,CAAC,OAAO,IAAI,CAAC,CAAA;YACxC,OAAO,CAAC,CAAA;QACV,CAAC;QACD,IAAI,GAAG,KAAK,QAAQ;YAAE,IAAI,GAAG,IAAI,CAAC,EAAE,KAAK,CAAC,IAAI,EAAE,CAAA;aAC3C,IAAI,GAAG,KAAK,UAAU;YAAE,MAAM,GAAG,IAAI,CAAC,EAAE,KAAK,CAAC,CAAA;aAC9C,IAAI,GAAG,KAAK,UAAU;YAAE,MAAM,GAAG,IAAI,CAAC,EAAE,KAAK,CAAC,CAAA;aAC9C,IAAI,GAAG,CAAC,UAAU,CAAC,IAAI,CAAC,EAAE,CAAC;YAC9B,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,kBAAkB,GAAG,OAAO,KAAK,EAAE,CAAC,CAAA;YACzD,OAAO,CAAC,CAAA;QACV,CAAC;;YAAM,OAAO,GAAG,GAAG,CAAA;IACtB,CAAC;IACD,IAAI,CAAC,IAAI,EAAE,CAAC;QACV,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,gEAAgE,CAAC,CAAA;QACtF,OAAO,CAAC,CAAA;IACV,CAAC;IACD,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,KAAK,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,KAAK,EAAE,CAAC;QAClD,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,kFAAkF,CAAC,CAAA;QACxG,OAAO,CAAC,CAAA;IACV,CAAC;IACD,MAAM,CAAC,KAAC,GAAG,IAAC,IAAI,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,GAAI,EAAE,EAAE,eAAe,EAAE,IAAI,EAAE,CAAC,CAAA;IACvG,OAAO,CAAC,CAAA;AACV,CAAC;AAED,MAAM,IAAI,GAAG,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAA;AACxC,IAAI,IAAI,KAAK,CAAC;IAAE,OAAO,CAAC,QAAQ,GAAG,IAAI,CAAA"}
|
package/dist/data.d.ts
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Everything the browser reads, each through the library that already owns
|
|
3
|
+
* it. The browser holds no chronicle rules of its own:
|
|
4
|
+
*
|
|
5
|
+
* - the catalog and the readings: `@miadi/inquiry-weave`
|
|
6
|
+
* - a vessel's summary, files, file text and full-text search: `@miadi/episode-vessel`
|
|
7
|
+
* - the doors a reference opens, and OSC 8 links: `@miadi/inquiry-weave`
|
|
8
|
+
* - the five-stage proof: `@miadi/inquiry-weave` (git and the wheel, so only on demand)
|
|
9
|
+
*/
|
|
10
|
+
import { type Destination, type EpisodeStatus, type TextReference } from "@miadi/inquiry-weave";
|
|
11
|
+
import { type EpisodeFile, type EpisodeSummary, type SearchResult } from "@miadi/episode-vessel";
|
|
12
|
+
import { type EpisodeRow } from "./model.js";
|
|
13
|
+
export interface Chronicle {
|
|
14
|
+
root: string;
|
|
15
|
+
rows: EpisodeRow[];
|
|
16
|
+
}
|
|
17
|
+
export declare function loadChronicle(root: string): Promise<Chronicle>;
|
|
18
|
+
export interface EpisodeDetail {
|
|
19
|
+
summary: EpisodeSummary | null;
|
|
20
|
+
files: EpisodeFile[];
|
|
21
|
+
error?: string;
|
|
22
|
+
}
|
|
23
|
+
export declare function loadDetail(root: string, name: string): EpisodeDetail;
|
|
24
|
+
export declare function loadText(root: string, name: string, file: string): {
|
|
25
|
+
content: string;
|
|
26
|
+
} | {
|
|
27
|
+
error: string;
|
|
28
|
+
};
|
|
29
|
+
/** Up to 1000 hits, newest episode first, file and line order kept inside each episode. */
|
|
30
|
+
export declare function search(root: string, query: string, rows?: readonly EpisodeRow[]): Promise<SearchResult>;
|
|
31
|
+
/** The episode a chronicle reference names, or null when it names none or several. */
|
|
32
|
+
export declare function episodeOfReference(rows: readonly EpisodeRow[], uri: string): string | null;
|
|
33
|
+
export interface Doors {
|
|
34
|
+
title: string;
|
|
35
|
+
doors: Destination[];
|
|
36
|
+
note?: string;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* The doors of an episode, or of a file inside it. The room addresses an open
|
|
40
|
+
* file as `?file=<path>` (app/chronicle/components/EpisodeFiles.tsx), and the
|
|
41
|
+
* vessel door becomes the file itself. The wheel doors stay the episode's.
|
|
42
|
+
*/
|
|
43
|
+
export declare function episodeDoors(chronicle: Chronicle, name: string, file?: string): Promise<Doors>;
|
|
44
|
+
/**
|
|
45
|
+
* The doors of a reference found in a file. A ceremony or circle shows its
|
|
46
|
+
* URLs only (D9): the page it opens asks the reader for their seat, and
|
|
47
|
+
* nothing the wheel holds about it is read here.
|
|
48
|
+
*/
|
|
49
|
+
export declare function referenceDoors(chronicle: Chronicle, ref: TextReference): Promise<Doors>;
|
|
50
|
+
/** The door a phone can open: the tailnet room, then the public one, then the first. */
|
|
51
|
+
export declare function phoneDoor(doors: readonly Destination[]): Destination | undefined;
|
|
52
|
+
export declare function qrLines(url: string, invert?: boolean): string[];
|
|
53
|
+
export declare function proof(root: string, name: string): Promise<EpisodeStatus>;
|
|
54
|
+
/** Hand a URL to the system opener. Returns the command used, or null when none exists. */
|
|
55
|
+
export declare function openUrl(url: string): string | null;
|
|
56
|
+
//# sourceMappingURL=data.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"data.d.ts","sourceRoot":"","sources":["../src/data.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAIH,OAAO,EASL,KAAK,WAAW,EAChB,KAAK,aAAa,EAElB,KAAK,aAAa,EACnB,MAAM,sBAAsB,CAAA;AAC7B,OAAO,EAML,KAAK,WAAW,EAChB,KAAK,cAAc,EACnB,KAAK,YAAY,EAClB,MAAM,uBAAuB,CAAA;AAE9B,OAAO,EAAmB,KAAK,UAAU,EAAE,MAAM,YAAY,CAAA;AAE7D,MAAM,WAAW,SAAS;IACxB,IAAI,EAAE,MAAM,CAAA;IACZ,IAAI,EAAE,UAAU,EAAE,CAAA;CACnB;AAED,wBAAsB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,SAAS,CAAC,CAIpE;AAED,MAAM,WAAW,aAAa;IAC5B,OAAO,EAAE,cAAc,GAAG,IAAI,CAAA;IAC9B,KAAK,EAAE,WAAW,EAAE,CAAA;IACpB,KAAK,CAAC,EAAE,MAAM,CAAA;CACf;AAED,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,aAAa,CAMpE;AAED,wBAAgB,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG;IAAE,OAAO,EAAE,MAAM,CAAA;CAAE,GAAG;IAAE,KAAK,EAAE,MAAM,CAAA;CAAE,CAM1G;AAED,2FAA2F;AAC3F,wBAAsB,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,IAAI,GAAE,SAAS,UAAU,EAAO,GAAG,OAAO,CAAC,YAAY,CAAC,CASjH;AAKD,sFAAsF;AACtF,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,SAAS,UAAU,EAAE,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAK1F;AAED,MAAM,WAAW,KAAK;IACpB,KAAK,EAAE,MAAM,CAAA;IACb,KAAK,EAAE,WAAW,EAAE,CAAA;IACpB,IAAI,CAAC,EAAE,MAAM,CAAA;CACd;AAED;;;;GAIG;AACH,wBAAsB,YAAY,CAAC,SAAS,EAAE,SAAS,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,KAAK,CAAC,CAWpG;AAED;;;;GAIG;AACH,wBAAsB,cAAc,CAAC,SAAS,EAAE,SAAS,EAAE,GAAG,EAAE,aAAa,GAAG,OAAO,CAAC,KAAK,CAAC,CAW7F;AAED,wFAAwF;AACxF,wBAAgB,SAAS,CAAC,KAAK,EAAE,SAAS,WAAW,EAAE,GAAG,WAAW,GAAG,SAAS,CAOhF;AAED,wBAAgB,OAAO,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,UAAQ,GAAG,MAAM,EAAE,CAE7D;AAED,wBAAsB,KAAK,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,CAAC,CAG9E;AAED,2FAA2F;AAC3F,wBAAgB,OAAO,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAiBlD"}
|
package/dist/data.js
ADDED
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Everything the browser reads, each through the library that already owns
|
|
3
|
+
* it. The browser holds no chronicle rules of its own:
|
|
4
|
+
*
|
|
5
|
+
* - the catalog and the readings: `@miadi/inquiry-weave`
|
|
6
|
+
* - a vessel's summary, files, file text and full-text search: `@miadi/episode-vessel`
|
|
7
|
+
* - the doors a reference opens, and OSC 8 links: `@miadi/inquiry-weave`
|
|
8
|
+
* - the five-stage proof: `@miadi/inquiry-weave` (git and the wheel, so only on demand)
|
|
9
|
+
*/
|
|
10
|
+
import { spawn } from "node:child_process";
|
|
11
|
+
import fs from "node:fs";
|
|
12
|
+
import { buildChronicleCatalog, episodeDoorStatus, readWebBases, resolveChronicleRef, resolveDoorContext, resolveForTerminal, resolveWheelRefForTerminal, parseMiadiChronicleUri, } from "@miadi/inquiry-weave";
|
|
13
|
+
import { listEpisodeFiles, readEpisodeSummary, readEpisodeText, resolveChronicleRoot, searchVessels, } from "@miadi/episode-vessel";
|
|
14
|
+
import { renderUnicodeCompact } from "uqr";
|
|
15
|
+
import { sortRows, toRow } from "./model.js";
|
|
16
|
+
export async function loadChronicle(root) {
|
|
17
|
+
const real = resolveChronicleRoot(root);
|
|
18
|
+
const catalog = await buildChronicleCatalog(real);
|
|
19
|
+
return { root: real, rows: sortRows(catalog.episodes.map(toRow)) };
|
|
20
|
+
}
|
|
21
|
+
export function loadDetail(root, name) {
|
|
22
|
+
try {
|
|
23
|
+
return { summary: readEpisodeSummary(name, { root }), files: listEpisodeFiles(name, { root }) };
|
|
24
|
+
}
|
|
25
|
+
catch (error) {
|
|
26
|
+
return { summary: null, files: [], error: error.message };
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
export function loadText(root, name, file) {
|
|
30
|
+
try {
|
|
31
|
+
return { content: readEpisodeText(name, file, { root }).content };
|
|
32
|
+
}
|
|
33
|
+
catch (error) {
|
|
34
|
+
return { error: error.message };
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
/** Up to 1000 hits, newest episode first, file and line order kept inside each episode. */
|
|
38
|
+
export async function search(root, query, rows = []) {
|
|
39
|
+
const result = await searchVessels(query, { root, limit: 1000 });
|
|
40
|
+
const rank = new Map(rows.map((row, index) => [row.name, index]));
|
|
41
|
+
const order = (name) => rank.get(name) ?? Number.MAX_SAFE_INTEGER;
|
|
42
|
+
const hits = result.hits
|
|
43
|
+
.map((hit, index) => ({ hit, index }))
|
|
44
|
+
.sort((a, b) => order(a.hit.episodePath) - order(b.hit.episodePath) || a.index - b.index)
|
|
45
|
+
.map(({ hit }) => hit);
|
|
46
|
+
return { ...result, hits };
|
|
47
|
+
}
|
|
48
|
+
const resolvable = (rows) => rows.map((row) => ({ name: row.name, number: row.number, slug: row.slug, date: row.date, title: row.title }));
|
|
49
|
+
/** The episode a chronicle reference names, or null when it names none or several. */
|
|
50
|
+
export function episodeOfReference(rows, uri) {
|
|
51
|
+
const parsed = parseMiadiChronicleUri(uri);
|
|
52
|
+
if (!parsed)
|
|
53
|
+
return null;
|
|
54
|
+
const resolution = resolveChronicleRef(parsed, resolvable(rows));
|
|
55
|
+
return resolution.target.name ?? null;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* The doors of an episode, or of a file inside it. The room addresses an open
|
|
59
|
+
* file as `?file=<path>` (app/chronicle/components/EpisodeFiles.tsx), and the
|
|
60
|
+
* vessel door becomes the file itself. The wheel doors stay the episode's.
|
|
61
|
+
*/
|
|
62
|
+
export async function episodeDoors(chronicle, name, file) {
|
|
63
|
+
const result = await resolveForTerminal(`miadi-chronicle:${name}`, resolvable(chronicle.rows), chronicle.root, {
|
|
64
|
+
bases: readWebBases(),
|
|
65
|
+
});
|
|
66
|
+
const doors = (result?.destinations ?? []).map((door) => {
|
|
67
|
+
if (!file)
|
|
68
|
+
return door;
|
|
69
|
+
if (door.kind === "room")
|
|
70
|
+
return { ...door, url: `${door.url}${door.url.includes("?") ? "&" : "?"}file=${encodeURIComponent(file)}` };
|
|
71
|
+
if (door.kind === "vessel")
|
|
72
|
+
return { ...door, url: `${door.url}/${file.split("/").map(encodeURIComponent).join("/")}` };
|
|
73
|
+
return door;
|
|
74
|
+
});
|
|
75
|
+
return { title: file ? `${name}/${file}` : name, doors };
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* The doors of a reference found in a file. A ceremony or circle shows its
|
|
79
|
+
* URLs only (D9): the page it opens asks the reader for their seat, and
|
|
80
|
+
* nothing the wheel holds about it is read here.
|
|
81
|
+
*/
|
|
82
|
+
export async function referenceDoors(chronicle, ref) {
|
|
83
|
+
if (ref.kind === "chronicle") {
|
|
84
|
+
const result = await resolveForTerminal(ref.uri, resolvable(chronicle.rows), chronicle.root, { bases: readWebBases() });
|
|
85
|
+
return { title: ref.uri, doors: result?.destinations ?? [] };
|
|
86
|
+
}
|
|
87
|
+
const result = await resolveWheelRefForTerminal(ref.uri, { bases: readWebBases() });
|
|
88
|
+
return {
|
|
89
|
+
title: ref.uri,
|
|
90
|
+
doors: result?.destinations ?? [],
|
|
91
|
+
note: `A ${ref.kind} is private until its page sees your seat. Only its addresses are shown here.`,
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
/** The door a phone can open: the tailnet room, then the public one, then the first. */
|
|
95
|
+
export function phoneDoor(doors) {
|
|
96
|
+
return (doors.find((door) => door.kind === "room" && door.base === "internal") ??
|
|
97
|
+
doors.find((door) => door.kind === "room" && door.base === "public") ??
|
|
98
|
+
doors.find((door) => /^https?:/.test(door.url) && !/127\.0\.0\.1|localhost/.test(door.url)) ??
|
|
99
|
+
doors[0]);
|
|
100
|
+
}
|
|
101
|
+
export function qrLines(url, invert = false) {
|
|
102
|
+
return renderUnicodeCompact(url, { border: 1, invert }).split("\n");
|
|
103
|
+
}
|
|
104
|
+
export async function proof(root, name) {
|
|
105
|
+
const context = resolveDoorContext({ chronicleRoot: root });
|
|
106
|
+
return episodeDoorStatus(context, name);
|
|
107
|
+
}
|
|
108
|
+
/** Hand a URL to the system opener. Returns the command used, or null when none exists. */
|
|
109
|
+
export function openUrl(url) {
|
|
110
|
+
const candidates = process.platform === "darwin"
|
|
111
|
+
? [["open", [url]]]
|
|
112
|
+
: [
|
|
113
|
+
["termux-open-url", [url]],
|
|
114
|
+
["xdg-open", [url]],
|
|
115
|
+
];
|
|
116
|
+
for (const [command, args] of candidates) {
|
|
117
|
+
const onPath = (process.env.PATH ?? "").split(":").some((dir) => dir && fs.existsSync(`${dir}/${command}`));
|
|
118
|
+
if (!onPath)
|
|
119
|
+
continue;
|
|
120
|
+
const child = spawn(command, args, { detached: true, stdio: "ignore" });
|
|
121
|
+
child.on("error", () => { });
|
|
122
|
+
child.unref();
|
|
123
|
+
return command;
|
|
124
|
+
}
|
|
125
|
+
return null;
|
|
126
|
+
}
|
|
127
|
+
//# sourceMappingURL=data.js.map
|
package/dist/data.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"data.js","sourceRoot":"","sources":["../src/data.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export { App, type AppProps } from "./App.js";
|
|
2
|
+
export { DEFAULT_ACTIONS, actionFor, doorsAction, followAction, proofAction, qrAction, type ActionContext, type ChronicleAction, type Overlay, type View, } from "./actions.js";
|
|
3
|
+
export { episodeDoors, episodeOfReference, loadChronicle, loadDetail, loadText, openUrl, phoneDoor, proof, qrLines, referenceDoors, search, type Chronicle, type Doors, type EpisodeDetail, } from "./data.js";
|
|
4
|
+
export { chipText, clamp, fileView, filterRows, screenLineOf, sortRows, toRow, windowOf, wrapSegments, type EpisodeRow, type FileView, type ScreenLine, type Segment, type SegmentStyle, } from "./model.js";
|
|
5
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,GAAG,EAAE,KAAK,QAAQ,EAAE,MAAM,UAAU,CAAA;AAC7C,OAAO,EACL,eAAe,EACf,SAAS,EACT,WAAW,EACX,YAAY,EACZ,WAAW,EACX,QAAQ,EACR,KAAK,aAAa,EAClB,KAAK,eAAe,EACpB,KAAK,OAAO,EACZ,KAAK,IAAI,GACV,MAAM,cAAc,CAAA;AACrB,OAAO,EACL,YAAY,EACZ,kBAAkB,EAClB,aAAa,EACb,UAAU,EACV,QAAQ,EACR,OAAO,EACP,SAAS,EACT,KAAK,EACL,OAAO,EACP,cAAc,EACd,MAAM,EACN,KAAK,SAAS,EACd,KAAK,KAAK,EACV,KAAK,aAAa,GACnB,MAAM,WAAW,CAAA;AAClB,OAAO,EACL,QAAQ,EACR,KAAK,EACL,QAAQ,EACR,UAAU,EACV,YAAY,EACZ,QAAQ,EACR,KAAK,EACL,QAAQ,EACR,YAAY,EACZ,KAAK,UAAU,EACf,KAAK,QAAQ,EACb,KAAK,UAAU,EACf,KAAK,OAAO,EACZ,KAAK,YAAY,GAClB,MAAM,YAAY,CAAA"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export { App } from "./App.js";
|
|
2
|
+
export { DEFAULT_ACTIONS, actionFor, doorsAction, followAction, proofAction, qrAction, } from "./actions.js";
|
|
3
|
+
export { episodeDoors, episodeOfReference, loadChronicle, loadDetail, loadText, openUrl, phoneDoor, proof, qrLines, referenceDoors, search, } from "./data.js";
|
|
4
|
+
export { chipText, clamp, fileView, filterRows, screenLineOf, sortRows, toRow, windowOf, wrapSegments, } from "./model.js";
|
|
5
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,GAAG,EAAiB,MAAM,UAAU,CAAA;AAC7C,OAAO,EACL,eAAe,EACf,SAAS,EACT,WAAW,EACX,YAAY,EACZ,WAAW,EACX,QAAQ,GAKT,MAAM,cAAc,CAAA;AACrB,OAAO,EACL,YAAY,EACZ,kBAAkB,EAClB,aAAa,EACb,UAAU,EACV,QAAQ,EACR,OAAO,EACP,SAAS,EACT,KAAK,EACL,OAAO,EACP,cAAc,EACd,MAAM,GAIP,MAAM,WAAW,CAAA;AAClB,OAAO,EACL,QAAQ,EACR,KAAK,EACL,QAAQ,EACR,UAAU,EACV,YAAY,EACZ,QAAQ,EACR,KAAK,EACL,QAAQ,EACR,YAAY,GAMb,MAAM,YAAY,CAAA"}
|
package/dist/model.d.ts
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The browser's pure half: what a row is, how a query filters rows, how a file
|
|
3
|
+
* becomes screen lines with its references marked. No I/O and no Ink, so it
|
|
4
|
+
* is tested on its own and a host other than the terminal can reuse it.
|
|
5
|
+
*/
|
|
6
|
+
import type { EpisodeManifestEntry } from "@miadi/episodic-memory-schema";
|
|
7
|
+
import { type EpisodeReadings, type TextReference } from "@miadi/inquiry-weave";
|
|
8
|
+
export interface EpisodeRow {
|
|
9
|
+
name: string;
|
|
10
|
+
number?: number;
|
|
11
|
+
date?: string;
|
|
12
|
+
title: string;
|
|
13
|
+
slug?: string;
|
|
14
|
+
type?: string;
|
|
15
|
+
status?: string;
|
|
16
|
+
/** An `episode.yaml` was read. Folders without one are listed, not hidden. */
|
|
17
|
+
structured: boolean;
|
|
18
|
+
readings: EpisodeReadings;
|
|
19
|
+
}
|
|
20
|
+
export declare function toRow(entry: EpisodeManifestEntry): EpisodeRow;
|
|
21
|
+
/** Newest first; folders without a number last, by name. */
|
|
22
|
+
export declare function sortRows(rows: readonly EpisodeRow[]): EpisodeRow[];
|
|
23
|
+
/**
|
|
24
|
+
* Every word of the query must appear in the row: its number, date, title,
|
|
25
|
+
* folder name, type, status or one of its readings. Case does not matter.
|
|
26
|
+
*/
|
|
27
|
+
export declare function filterRows(rows: readonly EpisodeRow[], query: string): EpisodeRow[];
|
|
28
|
+
/** The rows a list of `height` lines shows so that `selected` stays in view. */
|
|
29
|
+
export declare function windowOf<T>(items: readonly T[], selected: number, height: number): {
|
|
30
|
+
start: number;
|
|
31
|
+
items: T[];
|
|
32
|
+
};
|
|
33
|
+
export declare function clamp(value: number, count: number): number;
|
|
34
|
+
export type SegmentStyle = "text" | "heading" | "code" | "quote" | "ref";
|
|
35
|
+
export interface Segment {
|
|
36
|
+
text: string;
|
|
37
|
+
style: SegmentStyle;
|
|
38
|
+
/** For `ref` segments: which reference of the file this chip is. */
|
|
39
|
+
ref?: number;
|
|
40
|
+
}
|
|
41
|
+
export interface ScreenLine {
|
|
42
|
+
segments: Segment[];
|
|
43
|
+
/** 1-based line of the source file this screen line comes from. */
|
|
44
|
+
source: number;
|
|
45
|
+
}
|
|
46
|
+
export interface FileView {
|
|
47
|
+
lines: ScreenLine[];
|
|
48
|
+
refs: TextReference[];
|
|
49
|
+
}
|
|
50
|
+
/** The words a chip shows: the author's label, or what the reference names. */
|
|
51
|
+
export declare function chipText(ref: TextReference): string;
|
|
52
|
+
/** Cut segments into screen lines no wider than `width` columns, breaking at spaces where it can. */
|
|
53
|
+
export declare function wrapSegments(segments: Segment[], width: number): Segment[][];
|
|
54
|
+
/** A text file as wrapped screen lines, with every reference found and marked. */
|
|
55
|
+
export declare function fileView(content: string, width: number): FileView;
|
|
56
|
+
/** The first screen line that shows source line `line`. */
|
|
57
|
+
export declare function screenLineOf(view: FileView, line: number): number;
|
|
58
|
+
//# sourceMappingURL=model.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"model.d.ts","sourceRoot":"","sources":["../src/model.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAGH,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,+BAA+B,CAAA;AACzE,OAAO,EAAmC,KAAK,eAAe,EAAE,KAAK,aAAa,EAAE,MAAM,sBAAsB,CAAA;AAEhH,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,MAAM,CAAA;IACZ,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,KAAK,EAAE,MAAM,CAAA;IACb,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,8EAA8E;IAC9E,UAAU,EAAE,OAAO,CAAA;IACnB,QAAQ,EAAE,eAAe,CAAA;CAC1B;AAED,wBAAgB,KAAK,CAAC,KAAK,EAAE,oBAAoB,GAAG,UAAU,CAY7D;AAED,4DAA4D;AAC5D,wBAAgB,QAAQ,CAAC,IAAI,EAAE,SAAS,UAAU,EAAE,GAAG,UAAU,EAAE,CAOlE;AAED;;;GAGG;AACH,wBAAgB,UAAU,CAAC,IAAI,EAAE,SAAS,UAAU,EAAE,EAAE,KAAK,EAAE,MAAM,GAAG,UAAU,EAAE,CAoBnF;AAED,gFAAgF;AAChF,wBAAgB,QAAQ,CAAC,CAAC,EAAE,KAAK,EAAE,SAAS,CAAC,EAAE,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,CAAC,EAAE,CAAA;CAAE,CAIhH;AAED,wBAAgB,KAAK,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAE1D;AAMD,MAAM,MAAM,YAAY,GAAG,MAAM,GAAG,SAAS,GAAG,MAAM,GAAG,OAAO,GAAG,KAAK,CAAA;AAExE,MAAM,WAAW,OAAO;IACtB,IAAI,EAAE,MAAM,CAAA;IACZ,KAAK,EAAE,YAAY,CAAA;IACnB,oEAAoE;IACpE,GAAG,CAAC,EAAE,MAAM,CAAA;CACb;AAED,MAAM,WAAW,UAAU;IACzB,QAAQ,EAAE,OAAO,EAAE,CAAA;IACnB,mEAAmE;IACnE,MAAM,EAAE,MAAM,CAAA;CACf;AAED,MAAM,WAAW,QAAQ;IACvB,KAAK,EAAE,UAAU,EAAE,CAAA;IACnB,IAAI,EAAE,aAAa,EAAE,CAAA;CACtB;AAID,+EAA+E;AAC/E,wBAAgB,QAAQ,CAAC,GAAG,EAAE,aAAa,GAAG,MAAM,CAKnD;AAgBD,qGAAqG;AACrG,wBAAgB,YAAY,CAAC,QAAQ,EAAE,OAAO,EAAE,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,EAAE,EAAE,CA4C5E;AAED,kFAAkF;AAClF,wBAAgB,QAAQ,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,QAAQ,CAiBjE;AAED,2DAA2D;AAC3D,wBAAgB,YAAY,CAAC,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAGjE"}
|
package/dist/model.js
ADDED
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The browser's pure half: what a row is, how a query filters rows, how a file
|
|
3
|
+
* becomes screen lines with its references marked. No I/O and no Ink, so it
|
|
4
|
+
* is tested on its own and a host other than the terminal can reuse it.
|
|
5
|
+
*/
|
|
6
|
+
import stringWidth from "string-width";
|
|
7
|
+
import { findTextReferences, readEpisode } from "@miadi/inquiry-weave";
|
|
8
|
+
export function toRow(entry) {
|
|
9
|
+
return {
|
|
10
|
+
name: entry.name,
|
|
11
|
+
number: entry.number,
|
|
12
|
+
date: entry.date,
|
|
13
|
+
title: entry.title ?? entry.slug ?? entry.name,
|
|
14
|
+
slug: entry.slug,
|
|
15
|
+
type: entry.type,
|
|
16
|
+
status: entry.status,
|
|
17
|
+
structured: Boolean(entry.structured),
|
|
18
|
+
readings: readEpisode(entry),
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
/** Newest first; folders without a number last, by name. */
|
|
22
|
+
export function sortRows(rows) {
|
|
23
|
+
return [...rows].sort((a, b) => {
|
|
24
|
+
if (a.number !== undefined && b.number !== undefined && a.number !== b.number)
|
|
25
|
+
return b.number - a.number;
|
|
26
|
+
if (a.number === undefined && b.number !== undefined)
|
|
27
|
+
return 1;
|
|
28
|
+
if (b.number === undefined && a.number !== undefined)
|
|
29
|
+
return -1;
|
|
30
|
+
return a.name < b.name ? 1 : a.name > b.name ? -1 : 0;
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Every word of the query must appear in the row: its number, date, title,
|
|
35
|
+
* folder name, type, status or one of its readings. Case does not matter.
|
|
36
|
+
*/
|
|
37
|
+
export function filterRows(rows, query) {
|
|
38
|
+
const words = query.toLowerCase().split(/\s+/).filter(Boolean);
|
|
39
|
+
if (words.length === 0)
|
|
40
|
+
return [...rows];
|
|
41
|
+
return rows.filter((row) => {
|
|
42
|
+
const haystack = [
|
|
43
|
+
row.number === undefined ? "" : String(row.number),
|
|
44
|
+
row.date,
|
|
45
|
+
row.title,
|
|
46
|
+
row.name,
|
|
47
|
+
row.type,
|
|
48
|
+
row.status,
|
|
49
|
+
row.readings.theme,
|
|
50
|
+
row.readings.direction,
|
|
51
|
+
row.readings.arc,
|
|
52
|
+
]
|
|
53
|
+
.filter(Boolean)
|
|
54
|
+
.join(" ")
|
|
55
|
+
.toLowerCase();
|
|
56
|
+
return words.every((word) => haystack.includes(word));
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
/** The rows a list of `height` lines shows so that `selected` stays in view. */
|
|
60
|
+
export function windowOf(items, selected, height) {
|
|
61
|
+
const size = Math.max(1, height);
|
|
62
|
+
const start = Math.min(Math.max(0, selected - Math.floor(size / 2)), Math.max(0, items.length - size));
|
|
63
|
+
return { start, items: items.slice(start, start + size) };
|
|
64
|
+
}
|
|
65
|
+
export function clamp(value, count) {
|
|
66
|
+
return count <= 0 ? 0 : Math.min(Math.max(0, value), count - 1);
|
|
67
|
+
}
|
|
68
|
+
const shortId = (id) => (id.length > 22 ? `${id.slice(0, 20)}…` : id);
|
|
69
|
+
/** The words a chip shows: the author's label, or what the reference names. */
|
|
70
|
+
export function chipText(ref) {
|
|
71
|
+
if (ref.kind === "chronicle")
|
|
72
|
+
return `↗ ${ref.label ?? ref.uri.replace(/^miadi-chronicle:(\/\/)?/i, "ep ")}`;
|
|
73
|
+
const id = ref.uri.replace(/^miadi-(ceremony|circle):/i, "");
|
|
74
|
+
const mark = ref.kind === "ceremony" ? "◆" : "○";
|
|
75
|
+
return `${mark} ${ref.label ?? `${ref.kind} ${shortId(id)}`}`;
|
|
76
|
+
}
|
|
77
|
+
function lineSegments(line, refs, lineStart, style) {
|
|
78
|
+
const segments = [];
|
|
79
|
+
let at = 0;
|
|
80
|
+
refs.forEach((ref, index) => {
|
|
81
|
+
if (ref.start < lineStart || ref.end > lineStart + line.length)
|
|
82
|
+
return;
|
|
83
|
+
const from = ref.start - lineStart;
|
|
84
|
+
if (from > at)
|
|
85
|
+
segments.push({ text: line.slice(at, from), style });
|
|
86
|
+
segments.push({ text: chipText(ref), style: "ref", ref: index });
|
|
87
|
+
at = ref.end - lineStart;
|
|
88
|
+
});
|
|
89
|
+
if (at < line.length || segments.length === 0)
|
|
90
|
+
segments.push({ text: line.slice(at), style });
|
|
91
|
+
return segments;
|
|
92
|
+
}
|
|
93
|
+
/** Cut segments into screen lines no wider than `width` columns, breaking at spaces where it can. */
|
|
94
|
+
export function wrapSegments(segments, width) {
|
|
95
|
+
const limit = Math.max(8, width);
|
|
96
|
+
const lines = [[]];
|
|
97
|
+
let used = 0;
|
|
98
|
+
const push = (segment) => {
|
|
99
|
+
const last = lines[lines.length - 1];
|
|
100
|
+
const prev = last[last.length - 1];
|
|
101
|
+
if (prev && prev.style === segment.style && prev.ref === segment.ref)
|
|
102
|
+
prev.text += segment.text;
|
|
103
|
+
else
|
|
104
|
+
last.push({ ...segment });
|
|
105
|
+
used += stringWidth(segment.text);
|
|
106
|
+
};
|
|
107
|
+
const newline = () => {
|
|
108
|
+
lines.push([]);
|
|
109
|
+
used = 0;
|
|
110
|
+
};
|
|
111
|
+
for (const segment of segments) {
|
|
112
|
+
if (segment.style === "ref") {
|
|
113
|
+
// A chip is never split.
|
|
114
|
+
if (used > 0 && used + stringWidth(segment.text) > limit)
|
|
115
|
+
newline();
|
|
116
|
+
push(segment);
|
|
117
|
+
continue;
|
|
118
|
+
}
|
|
119
|
+
for (const word of segment.text.split(/(?<= )/)) {
|
|
120
|
+
let rest = word;
|
|
121
|
+
while (rest) {
|
|
122
|
+
const room = limit - used;
|
|
123
|
+
const w = stringWidth(rest);
|
|
124
|
+
if (w <= room) {
|
|
125
|
+
push({ ...segment, text: rest });
|
|
126
|
+
rest = "";
|
|
127
|
+
}
|
|
128
|
+
else if (used > 0 && w <= limit) {
|
|
129
|
+
newline();
|
|
130
|
+
}
|
|
131
|
+
else {
|
|
132
|
+
// A word longer than a whole line is cut where the line ends.
|
|
133
|
+
let cut = 0;
|
|
134
|
+
while (cut < rest.length && stringWidth(rest.slice(0, cut + 1)) <= room)
|
|
135
|
+
cut++;
|
|
136
|
+
push({ ...segment, text: rest.slice(0, Math.max(1, cut)) });
|
|
137
|
+
rest = rest.slice(Math.max(1, cut));
|
|
138
|
+
if (rest)
|
|
139
|
+
newline();
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
return lines;
|
|
145
|
+
}
|
|
146
|
+
/** A text file as wrapped screen lines, with every reference found and marked. */
|
|
147
|
+
export function fileView(content, width) {
|
|
148
|
+
const refs = findTextReferences(content);
|
|
149
|
+
const lines = [];
|
|
150
|
+
let offset = 0;
|
|
151
|
+
let fence = false;
|
|
152
|
+
content.split("\n").forEach((raw, index) => {
|
|
153
|
+
const line = raw.replace(/\r$/, "");
|
|
154
|
+
const fenceLine = /^\s*(```|~~~)/.test(line);
|
|
155
|
+
const style = fence || fenceLine ? "code" : /^#{1,6}\s/.test(line) ? "heading" : /^\s*>/.test(line) ? "quote" : "text";
|
|
156
|
+
if (fenceLine)
|
|
157
|
+
fence = !fence;
|
|
158
|
+
for (const segments of wrapSegments(lineSegments(line, refs, offset, style), width)) {
|
|
159
|
+
lines.push({ segments, source: index + 1 });
|
|
160
|
+
}
|
|
161
|
+
offset += raw.length + 1;
|
|
162
|
+
});
|
|
163
|
+
return { lines, refs };
|
|
164
|
+
}
|
|
165
|
+
/** The first screen line that shows source line `line`. */
|
|
166
|
+
export function screenLineOf(view, line) {
|
|
167
|
+
const found = view.lines.findIndex((screen) => screen.source >= line);
|
|
168
|
+
return found === -1 ? 0 : found;
|
|
169
|
+
}
|
|
170
|
+
//# sourceMappingURL=model.js.map
|