yamlover 0.3.4 → 0.3.6

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.
Files changed (137) hide show
  1. package/bin/yamlover.js +199 -112
  2. package/dist/client/assets/KaTeX_AMS-Regular-BQhdFMY1.woff2 +0 -0
  3. package/dist/client/assets/KaTeX_AMS-Regular-DMm9YOAa.woff +0 -0
  4. package/dist/client/assets/KaTeX_AMS-Regular-DRggAlZN.ttf +0 -0
  5. package/dist/client/assets/KaTeX_Caligraphic-Bold-ATXxdsX0.ttf +0 -0
  6. package/dist/client/assets/KaTeX_Caligraphic-Bold-BEiXGLvX.woff +0 -0
  7. package/dist/client/assets/KaTeX_Caligraphic-Bold-Dq_IR9rO.woff2 +0 -0
  8. package/dist/client/assets/KaTeX_Caligraphic-Regular-CTRA-rTL.woff +0 -0
  9. package/dist/client/assets/KaTeX_Caligraphic-Regular-Di6jR-x-.woff2 +0 -0
  10. package/dist/client/assets/KaTeX_Caligraphic-Regular-wX97UBjC.ttf +0 -0
  11. package/dist/client/assets/KaTeX_Fraktur-Bold-BdnERNNW.ttf +0 -0
  12. package/dist/client/assets/KaTeX_Fraktur-Bold-BsDP51OF.woff +0 -0
  13. package/dist/client/assets/KaTeX_Fraktur-Bold-CL6g_b3V.woff2 +0 -0
  14. package/dist/client/assets/KaTeX_Fraktur-Regular-CB_wures.ttf +0 -0
  15. package/dist/client/assets/KaTeX_Fraktur-Regular-CTYiF6lA.woff2 +0 -0
  16. package/dist/client/assets/KaTeX_Fraktur-Regular-Dxdc4cR9.woff +0 -0
  17. package/dist/client/assets/KaTeX_Main-Bold-Cx986IdX.woff2 +0 -0
  18. package/dist/client/assets/KaTeX_Main-Bold-Jm3AIy58.woff +0 -0
  19. package/dist/client/assets/KaTeX_Main-Bold-waoOVXN0.ttf +0 -0
  20. package/dist/client/assets/KaTeX_Main-BoldItalic-DxDJ3AOS.woff2 +0 -0
  21. package/dist/client/assets/KaTeX_Main-BoldItalic-DzxPMmG6.ttf +0 -0
  22. package/dist/client/assets/KaTeX_Main-BoldItalic-SpSLRI95.woff +0 -0
  23. package/dist/client/assets/KaTeX_Main-Italic-3WenGoN9.ttf +0 -0
  24. package/dist/client/assets/KaTeX_Main-Italic-BMLOBm91.woff +0 -0
  25. package/dist/client/assets/KaTeX_Main-Italic-NWA7e6Wa.woff2 +0 -0
  26. package/dist/client/assets/KaTeX_Main-Regular-B22Nviop.woff2 +0 -0
  27. package/dist/client/assets/KaTeX_Main-Regular-Dr94JaBh.woff +0 -0
  28. package/dist/client/assets/KaTeX_Main-Regular-ypZvNtVU.ttf +0 -0
  29. package/dist/client/assets/KaTeX_Math-BoldItalic-B3XSjfu4.ttf +0 -0
  30. package/dist/client/assets/KaTeX_Math-BoldItalic-CZnvNsCZ.woff2 +0 -0
  31. package/dist/client/assets/KaTeX_Math-BoldItalic-iY-2wyZ7.woff +0 -0
  32. package/dist/client/assets/KaTeX_Math-Italic-DA0__PXp.woff +0 -0
  33. package/dist/client/assets/KaTeX_Math-Italic-flOr_0UB.ttf +0 -0
  34. package/dist/client/assets/KaTeX_Math-Italic-t53AETM-.woff2 +0 -0
  35. package/dist/client/assets/KaTeX_SansSerif-Bold-CFMepnvq.ttf +0 -0
  36. package/dist/client/assets/KaTeX_SansSerif-Bold-D1sUS0GD.woff2 +0 -0
  37. package/dist/client/assets/KaTeX_SansSerif-Bold-DbIhKOiC.woff +0 -0
  38. package/dist/client/assets/KaTeX_SansSerif-Italic-C3H0VqGB.woff2 +0 -0
  39. package/dist/client/assets/KaTeX_SansSerif-Italic-DN2j7dab.woff +0 -0
  40. package/dist/client/assets/KaTeX_SansSerif-Italic-YYjJ1zSn.ttf +0 -0
  41. package/dist/client/assets/KaTeX_SansSerif-Regular-BNo7hRIc.ttf +0 -0
  42. package/dist/client/assets/KaTeX_SansSerif-Regular-CS6fqUqJ.woff +0 -0
  43. package/dist/client/assets/KaTeX_SansSerif-Regular-DDBCnlJ7.woff2 +0 -0
  44. package/dist/client/assets/KaTeX_Script-Regular-C5JkGWo-.ttf +0 -0
  45. package/dist/client/assets/KaTeX_Script-Regular-D3wIWfF6.woff2 +0 -0
  46. package/dist/client/assets/KaTeX_Script-Regular-D5yQViql.woff +0 -0
  47. package/dist/client/assets/KaTeX_Size1-Regular-C195tn64.woff +0 -0
  48. package/dist/client/assets/KaTeX_Size1-Regular-Dbsnue_I.ttf +0 -0
  49. package/dist/client/assets/KaTeX_Size1-Regular-mCD8mA8B.woff2 +0 -0
  50. package/dist/client/assets/KaTeX_Size2-Regular-B7gKUWhC.ttf +0 -0
  51. package/dist/client/assets/KaTeX_Size2-Regular-Dy4dx90m.woff2 +0 -0
  52. package/dist/client/assets/KaTeX_Size2-Regular-oD1tc_U0.woff +0 -0
  53. package/dist/client/assets/KaTeX_Size3-Regular-CTq5MqoE.woff +0 -0
  54. package/dist/client/assets/KaTeX_Size3-Regular-DgpXs0kz.ttf +0 -0
  55. package/dist/client/assets/KaTeX_Size4-Regular-BF-4gkZK.woff +0 -0
  56. package/dist/client/assets/KaTeX_Size4-Regular-DWFBv043.ttf +0 -0
  57. package/dist/client/assets/KaTeX_Size4-Regular-Dl5lxZxV.woff2 +0 -0
  58. package/dist/client/assets/KaTeX_Typewriter-Regular-C0xS9mPB.woff +0 -0
  59. package/dist/client/assets/KaTeX_Typewriter-Regular-CO6r4hn1.woff2 +0 -0
  60. package/dist/client/assets/KaTeX_Typewriter-Regular-D3Ib7_Hf.ttf +0 -0
  61. package/dist/client/assets/_commonjs-dynamic-modules-TDtrdbi3.js +1 -0
  62. package/dist/client/assets/decoded-CFkcuQ1f.js +1 -0
  63. package/dist/client/assets/djvu-B3wuFWwI.js +1 -0
  64. package/dist/client/assets/docx-putHYXzO.js +230 -0
  65. package/dist/client/assets/heic-oIxhIbIW.js +66 -0
  66. package/dist/client/assets/imagemap-CUKUIcGe.js +1 -0
  67. package/dist/client/assets/index-CQ3TGVSx.js +617 -0
  68. package/dist/client/assets/index-wTjOzmSY.css +1 -0
  69. package/dist/client/assets/map-CZS083CS.js +1 -0
  70. package/dist/client/assets/paged-CjxSuuik.js +1 -0
  71. package/dist/client/assets/panzoom-C7OuIIda.js +4 -0
  72. package/dist/client/assets/panzoom-CIGW-MKW.css +1 -0
  73. package/dist/client/assets/pdf-Dz3gUx0B.js +12 -0
  74. package/dist/client/assets/pdf-m5e3jsV4.css +1 -0
  75. package/dist/client/assets/pdf.worker.min-qwK7q_zL.mjs +28 -0
  76. package/dist/client/assets/psd-C8yNwHFw.js +11 -0
  77. package/dist/client/assets/spreadsheet-DBv7aqoN.js +38 -0
  78. package/dist/client/assets/tiff-73RKtIux.js +1 -0
  79. package/{index.html → dist/client/index.html} +2 -1
  80. package/package.json +18 -21
  81. package/src/client/App.tsx +0 -424
  82. package/src/client/NodeView.tsx +0 -422
  83. package/src/client/TaskStrip.tsx +0 -34
  84. package/src/client/Tree.tsx +0 -97
  85. package/src/client/api.ts +0 -213
  86. package/src/client/icons.ts +0 -91
  87. package/src/client/links.tsx +0 -108
  88. package/src/client/live.ts +0 -42
  89. package/src/client/main.tsx +0 -10
  90. package/src/client/paste-html.ts +0 -228
  91. package/src/client/paste-links.ts +0 -42
  92. package/src/client/paths.ts +0 -139
  93. package/src/client/render.tsx +0 -339
  94. package/src/client/renderers/annotate.tsx +0 -641
  95. package/src/client/renderers/asciidoc.tsx +0 -36
  96. package/src/client/renderers/chapter.tsx +0 -142
  97. package/src/client/renderers/csv.tsx +0 -234
  98. package/src/client/renderers/decoded.tsx +0 -72
  99. package/src/client/renderers/djvu.tsx +0 -271
  100. package/src/client/renderers/djvuWorker.ts +0 -99
  101. package/src/client/renderers/doc.tsx +0 -40
  102. package/src/client/renderers/docx.tsx +0 -49
  103. package/src/client/renderers/epub.tsx +0 -147
  104. package/src/client/renderers/explorer.tsx +0 -294
  105. package/src/client/renderers/fb2.tsx +0 -149
  106. package/src/client/renderers/headings.ts +0 -69
  107. package/src/client/renderers/heic.tsx +0 -23
  108. package/src/client/renderers/imagemap.tsx +0 -170
  109. package/src/client/renderers/kml.ts +0 -46
  110. package/src/client/renderers/latex.tsx +0 -37
  111. package/src/client/renderers/map.tsx +0 -205
  112. package/src/client/renderers/marklower.tsx +0 -120
  113. package/src/client/renderers/markup.tsx +0 -64
  114. package/src/client/renderers/media.tsx +0 -19
  115. package/src/client/renderers/paged.ts +0 -137
  116. package/src/client/renderers/panzoom.ts +0 -101
  117. package/src/client/renderers/pdf.tsx +0 -316
  118. package/src/client/renderers/plaintext.tsx +0 -120
  119. package/src/client/renderers/plantuml.tsx +0 -83
  120. package/src/client/renderers/psd.tsx +0 -25
  121. package/src/client/renderers/registry.tsx +0 -411
  122. package/src/client/renderers/rtf.tsx +0 -210
  123. package/src/client/renderers/spreadsheet.tsx +0 -105
  124. package/src/client/renderers/tag.tsx +0 -113
  125. package/src/client/renderers/text.tsx +0 -42
  126. package/src/client/renderers/tiff.tsx +0 -33
  127. package/src/client/styles.css +0 -1212
  128. package/src/client/vendor/README.md +0 -30
  129. package/src/client/vite-env.d.ts +0 -31
  130. package/src/server/api.ts +0 -147
  131. package/src/server/embed.ts +0 -187
  132. package/src/server/engine-api.ts +0 -1563
  133. package/src/server/gitignore.ts +0 -81
  134. package/src/server/node-kind.ts +0 -61
  135. package/src/server/tasks.ts +0 -83
  136. package/src/server/yamlover.ts +0 -1133
  137. /package/{src/client/vendor/djvu.js → dist/client/assets/djvu-Ci0lFKxE.js} +0 -0
@@ -1,210 +0,0 @@
1
- import { useEffect, useState } from "react";
2
- import { NodeJson, blobUrl } from "../api";
3
- import { Chunk } from "./registry";
4
-
5
- /**
6
- * Renderer for an `application/rtf` (`.rtf`) document. RTF is a plain-text control
7
- * language; rather than pull in a heavy dependency we walk it with a compact,
8
- * dependency-free converter ({@link rtfToHtml}) that covers the common content:
9
- * paragraphs, bold/italic/underline runs, tabs/line breaks, hex (`\'xx`, CP-1252)
10
- * and `\uN` Unicode escapes — skipping the non-content destination groups
11
- * (`fonttbl`, `colortbl`, `stylesheet`, `info`, pictures, …). The result is shown
12
- * in the shared `.markup` body, like the Markdown/AsciiDoc renderers.
13
- */
14
-
15
- // The handful of Windows-1252 code points that differ from Latin-1 (0x80–0x9F),
16
- // used to decode `\'xx` bytes the way most RTF producers mean them.
17
- const CP1252: Record<number, string> = {
18
- 0x80: "€", 0x82: "‚", 0x83: "ƒ", 0x84: "„", 0x85: "…", 0x86: "†", 0x87: "‡",
19
- 0x88: "ˆ", 0x89: "‰", 0x8a: "Š", 0x8b: "‹", 0x8c: "Œ", 0x8e: "Ž", 0x91: "‘",
20
- 0x92: "’", 0x93: "“", 0x94: "”", 0x95: "•", 0x96: "–", 0x97: "—", 0x98: "˜",
21
- 0x99: "™", 0x9a: "š", 0x9b: "›", 0x9c: "œ", 0x9e: "ž", 0x9f: "Ÿ",
22
- };
23
- const byteToChar = (b: number) => (b < 0x80 ? String.fromCharCode(b) : CP1252[b] ?? String.fromCharCode(b));
24
-
25
- // Group destinations whose contents are not document text — skipped wholesale.
26
- const SKIP_DESTS = new Set([
27
- "fonttbl", "colortbl", "stylesheet", "info", "pict", "header", "footer",
28
- "footnote", "xmlnstbl", "themedata", "colorschememapping", "datastore",
29
- "latentstyles", "listtable", "listoverridetable", "rsidtbl", "generator",
30
- "operator", "creatim", "revtim", "printim", "buptim", "author", "title",
31
- "subject", "keywords", "comment", "company", "manager", "category", "doccomm",
32
- "hlinkbase", "filetbl", "mmathPr", "fldinst", "object", "nonshppict",
33
- ]);
34
-
35
- interface Style {
36
- b: boolean;
37
- i: boolean;
38
- u: boolean;
39
- uc: number; // Unicode fallback skip count (\ucN)
40
- }
41
- interface Run {
42
- text: string;
43
- b: boolean;
44
- i: boolean;
45
- u: boolean;
46
- }
47
-
48
- function escapeHtml(s: string): string {
49
- return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
50
- }
51
-
52
- /** Convert RTF source to an HTML string (a sequence of `<p>` paragraphs with
53
- * `<strong>`/`<em>`/`<u>` runs). Best-effort: unknown control words are ignored. */
54
- export function rtfToHtml(rtf: string): string {
55
- const paragraphs: Run[][] = [];
56
- let runs: Run[] = [];
57
- let st: Style = { b: false, i: false, u: false, uc: 1 };
58
- const stack: Style[] = [];
59
- let depth = 0;
60
- let skipDepth = Infinity; // skip content while depth >= skipDepth
61
- const n = rtf.length;
62
- let i = 0;
63
-
64
- const active = () => depth < skipDepth;
65
- const emit = (t: string) => {
66
- if (!t || !active()) return;
67
- const last = runs[runs.length - 1];
68
- if (last && last.b === st.b && last.i === st.i && last.u === st.u) last.text += t;
69
- else runs.push({ text: t, b: st.b, i: st.i, u: st.u });
70
- };
71
- const endPara = () => {
72
- if (!active()) return;
73
- paragraphs.push(runs);
74
- runs = [];
75
- };
76
-
77
- const control = (word: string, param: number | null) => {
78
- switch (word) {
79
- case "b": st.b = param !== 0; break;
80
- case "i": st.i = param !== 0; break;
81
- case "ul": st.u = param !== 0; break;
82
- case "ulnone": st.u = false; break;
83
- case "uc": st.uc = param ?? 1; break;
84
- case "par": case "row": endPara(); break;
85
- case "line": case "sect": emit("\n"); break;
86
- case "tab": case "cell": emit("\t"); break;
87
- case "pard": case "plain": st.b = st.i = st.u = false; break;
88
- default:
89
- if (SKIP_DESTS.has(word)) skipDepth = Math.min(skipDepth, depth);
90
- break;
91
- }
92
- };
93
-
94
- while (i < n) {
95
- const c = rtf[i];
96
- if (c === "{") {
97
- stack.push({ ...st });
98
- depth++;
99
- i++;
100
- } else if (c === "}") {
101
- depth--;
102
- if (depth < skipDepth) skipDepth = Infinity;
103
- st = stack.pop() ?? st;
104
- i++;
105
- } else if (c === "\\") {
106
- const next = rtf[i + 1];
107
- if (next === "\\" || next === "{" || next === "}") {
108
- emit(next);
109
- i += 2;
110
- } else if (next === "*") {
111
- skipDepth = Math.min(skipDepth, depth); // ignorable destination
112
- i += 2;
113
- } else if (next === "'") {
114
- const b = parseInt(rtf.substr(i + 2, 2), 16);
115
- if (!isNaN(b)) emit(byteToChar(b));
116
- i += 4;
117
- } else if (next === "~") {
118
- emit(" "); i += 2; // non-breaking space
119
- } else if (next === "-") {
120
- i += 2; // optional hyphen — drop
121
- } else if (/[a-zA-Z]/.test(next ?? "")) {
122
- let j = i + 1;
123
- while (j < n && /[a-zA-Z]/.test(rtf[j])) j++;
124
- const word = rtf.slice(i + 1, j);
125
- let num = "";
126
- if (rtf[j] === "-") { num += "-"; j++; }
127
- while (j < n && /[0-9]/.test(rtf[j])) num += rtf[j++];
128
- const param = num === "" ? null : parseInt(num, 10);
129
- if (rtf[j] === " ") j++; // a single trailing space delimits, and is consumed
130
- i = j;
131
- if (word === "u" && param !== null) {
132
- if (active()) emit(String.fromCodePoint(param < 0 ? param + 0x10000 : param));
133
- // skip the \ucN fallback characters that follow the \u
134
- for (let skip = st.uc; skip > 0 && i < n; skip--) {
135
- if (rtf[i] === "\\" && rtf[i + 1] === "'") i += 4;
136
- else if (rtf[i] === "\\") i += 2;
137
- else if (rtf[i] === "{" || rtf[i] === "}") break;
138
- else i++;
139
- }
140
- } else {
141
- control(word, param);
142
- }
143
- } else {
144
- i++; // a lone backslash before something unexpected
145
- }
146
- } else if (c === "\r" || c === "\n") {
147
- i++; // raw newlines in the source are not content
148
- } else {
149
- emit(c);
150
- i++;
151
- }
152
- }
153
- endPara();
154
-
155
- const renderRun = (r: Run) => {
156
- let t = escapeHtml(r.text).replace(/\t/g, "&nbsp;&nbsp;&nbsp;&nbsp;").replace(/\n/g, "<br>");
157
- if (r.u) t = `<u>${t}</u>`;
158
- if (r.i) t = `<em>${t}</em>`;
159
- if (r.b) t = `<strong>${t}</strong>`;
160
- return t;
161
- };
162
- return paragraphs
163
- .filter((p) => p.some((r) => r.text.trim() || r.text.includes("\n")))
164
- .map((p) => `<p>${p.map(renderRun).join("")}</p>`)
165
- .join("\n");
166
- }
167
-
168
- /** Fetch the `.rtf` bytes, decode as Windows-1252 (RTF source is 7-bit with
169
- * `\'xx` for the rest), and convert to HTML. Shared by the page and chunk forms. */
170
- function useRtfHtml(path: string): { html: string | null; error: string | null } {
171
- const [html, setHtml] = useState<string | null>(null);
172
- const [error, setError] = useState<string | null>(null);
173
- useEffect(() => {
174
- let cancelled = false;
175
- setHtml(null);
176
- setError(null);
177
- fetch(blobUrl(path))
178
- .then((r) => r.arrayBuffer())
179
- .then((buf) => {
180
- if (cancelled) return;
181
- const src = new TextDecoder("windows-1252").decode(new Uint8Array(buf));
182
- setHtml(rtfToHtml(src));
183
- })
184
- .catch((e) => !cancelled && setError(String((e as Error).message || e)));
185
- return () => {
186
- cancelled = true;
187
- };
188
- }, [path]);
189
- return { html, error };
190
- }
191
-
192
- export function RtfView({ node }: { node: NodeJson }) {
193
- const { html, error } = useRtfHtml(node.path);
194
- if (error) return <div className="error">rtf: {error}</div>;
195
- if (html == null) return <div className="loading">reading RTF…</div>;
196
- return (
197
- <div className="text">
198
- {node.title && <h1 className="chapter-title">{node.title}</h1>}
199
- {node.description && <p className="chapter-subtitle">{node.description}</p>}
200
- <div className="markup" dangerouslySetInnerHTML={{ __html: html }} />
201
- </div>
202
- );
203
- }
204
-
205
- export function RtfChunk({ chunk }: { chunk: Chunk }) {
206
- const { html, error } = useRtfHtml(chunk.path);
207
- if (error) return <div className="error">rtf: {error}</div>;
208
- if (html == null) return <div className="loading">reading RTF…</div>;
209
- return <div className="markup" dangerouslySetInnerHTML={{ __html: html }} />;
210
- }
@@ -1,105 +0,0 @@
1
- import { useEffect, useState } from "react";
2
- import * as XLSX from "xlsx";
3
- import { NodeJson, blobUrl } from "../api";
4
- import { Chunk } from "./registry";
5
-
6
- /**
7
- * Renderer for Excel workbooks — `.xlsx` (Office Open XML) and legacy `.xls`
8
- * (BIFF). SheetJS reads both from the served bytes; each sheet is shown as a table
9
- * (reusing the `.csv-table` styling), with a tab row to switch sheets in a
10
- * multi-sheet workbook. SheetJS is heavy, so the registry loads this module lazily.
11
- */
12
- function useWorkbook(path: string): { wb: XLSX.WorkBook | null; error: string | null } {
13
- const [wb, setWb] = useState<XLSX.WorkBook | null>(null);
14
- const [error, setError] = useState<string | null>(null);
15
- useEffect(() => {
16
- let cancelled = false;
17
- setWb(null);
18
- setError(null);
19
- fetch(blobUrl(path))
20
- .then((r) => r.arrayBuffer())
21
- .then((buf) => {
22
- if (cancelled) return;
23
- setWb(XLSX.read(new Uint8Array(buf), { type: "array" }));
24
- })
25
- .catch((e) => !cancelled && setError(String((e as Error).message || e)));
26
- return () => {
27
- cancelled = true;
28
- };
29
- }, [path]);
30
- return { wb, error };
31
- }
32
-
33
- /** One sheet → a 2-D array of cell strings, then a bordered table (first row as a
34
- * header, matching the CSV renderer's look). */
35
- function SheetTable({ sheet }: { sheet: XLSX.WorkSheet }) {
36
- const rows = XLSX.utils.sheet_to_json<string[]>(sheet, { header: 1, blankrows: false, defval: "" });
37
- if (!rows.length) return <p className="csv-empty">(empty sheet)</p>;
38
- const cols = rows.reduce((m, r) => Math.max(m, r.length), 0);
39
- const pad = (r: string[]) => (r.length >= cols ? r : [...r, ...Array(cols - r.length).fill("")]);
40
- const [head, ...body] = rows;
41
- return (
42
- <div className="csv-scroll">
43
- <table className="csv-table">
44
- <thead>
45
- <tr>
46
- {pad(head).map((c, k) => (
47
- <th key={k}>{String(c)}</th>
48
- ))}
49
- </tr>
50
- </thead>
51
- <tbody>
52
- {body.map((r, ri) => (
53
- <tr key={ri}>
54
- {pad(r).map((c, ci) => (
55
- <td key={ci}>{String(c)}</td>
56
- ))}
57
- </tr>
58
- ))}
59
- </tbody>
60
- </table>
61
- </div>
62
- );
63
- }
64
-
65
- /** The workbook body: a sheet-tab row (when there is more than one) and the active
66
- * sheet's table. Shared by the full page and the inline chunk. */
67
- function Workbook({ wb }: { wb: XLSX.WorkBook }) {
68
- const [active, setActive] = useState(0);
69
- const names = wb.SheetNames;
70
- const name = names[Math.min(active, names.length - 1)];
71
- return (
72
- <div className="spreadsheet">
73
- {names.length > 1 && (
74
- <div className="sheet-tabs">
75
- {names.map((nm, k) => (
76
- <button key={nm} className={"sheet-tab" + (k === active ? " active" : "")} onClick={() => setActive(k)}>
77
- {nm}
78
- </button>
79
- ))}
80
- </div>
81
- )}
82
- <SheetTable sheet={wb.Sheets[name]} />
83
- </div>
84
- );
85
- }
86
-
87
- export function SpreadsheetView({ node }: { node: NodeJson }) {
88
- const { wb, error } = useWorkbook(node.path);
89
- if (error) return <div className="error">spreadsheet: {error}</div>;
90
- if (!wb) return <div className="loading">reading workbook…</div>;
91
- return (
92
- <div className="text">
93
- {node.title && <h1 className="chapter-title">{node.title}</h1>}
94
- {node.description && <p className="chapter-subtitle">{node.description}</p>}
95
- <Workbook wb={wb} />
96
- </div>
97
- );
98
- }
99
-
100
- export function SpreadsheetChunk({ chunk }: { chunk: Chunk }) {
101
- const { wb, error } = useWorkbook(chunk.path);
102
- if (error) return <div className="error">spreadsheet: {error}</div>;
103
- if (!wb) return <div className="loading">reading workbook…</div>;
104
- return <Workbook wb={wb} />;
105
- }
@@ -1,113 +0,0 @@
1
- import { asLink } from "../render";
2
- import { strToSegs } from "../paths";
3
-
4
- export const TAG_FORMAT = "x-yamlover-tag";
5
-
6
- export interface TagLink {
7
- path: string;
8
- label: string;
9
- color?: string | null; // a pure color tag's explicit color (else the hue derives from label)
10
- }
11
-
12
- /**
13
- * Split a node's `relations` into **tag references** (any up-edge that resolves to
14
- * an `x-yamlover-tag` node) and everything else. This includes the structural
15
- * `..` when the containment parent is itself a tag — so a tag shows its *parent
16
- * tag* in the bar, exactly as a paper shows the tags it is filed under. Tag
17
- * references render as badges (see {@link TagBadges}); the rest stay in the
18
- * ordinary relations panel.
19
- */
20
- export function splitTagRefs(relations?: Record<string, unknown>): {
21
- tags: TagLink[];
22
- rest: Record<string, unknown>;
23
- } {
24
- const tags: TagLink[] = [];
25
- const rest: Record<string, unknown> = {};
26
- for (const [name, v] of Object.entries(relations || {})) {
27
- const link = asLink(v);
28
- if (link && link.format === TAG_FORMAT) {
29
- tags.push({ path: link.path, label: tagLabel(link.path, link.title), color: link.color ?? null });
30
- } else {
31
- rest[name] = v;
32
- }
33
- }
34
- return { tags, rest };
35
- }
36
-
37
- /** A stable color for a tag, derived from its name — so a tag is the same hue
38
- * everywhere it appears. Mid lightness keeps white label text legible. */
39
- export function tagColor(name: string): string {
40
- let h = 0;
41
- for (let i = 0; i < name.length; i++) h = (Math.imul(h, 31) + name.charCodeAt(i)) >>> 0;
42
- return `hsl(${h % 360} 52% 42%)`;
43
- }
44
-
45
- /** A tag's display color: its explicit `color` (a "pure color tag"), else the
46
- * stable hue derived from its name. */
47
- export function resolveTagColor(t: { name: string; color?: string | null }): string {
48
- return t.color ?? tagColor(t.name);
49
- }
50
-
51
- // A tag node's projected value arrives in one of two shapes: a plain object (a tag with only
52
- // fields), or a `$yamloverMixed` marker (a tag whose description is its BODY — the variant/omni
53
- // shape: `{kind:"omni", value: <body>, entries: [{key, value}]}`). The helpers below read both.
54
- const MIXED_KEY = "$yamloverMixed";
55
- type MixedMarker = { kind?: string; value?: unknown; entries?: { key: string | null; value: unknown }[] };
56
-
57
- /** A tag value's keyed fields as [key, value] pairs — from either projection shape. */
58
- export function tagFields(value: unknown): [string, unknown][] {
59
- if (!value || typeof value !== "object" || Array.isArray(value)) return [];
60
- const marker = (value as Record<string, unknown>)[MIXED_KEY] as MixedMarker | undefined;
61
- if (marker?.entries) return marker.entries.filter((e) => e.key != null).map((e) => [e.key!, e.value]);
62
- return Object.entries(value as Record<string, unknown>);
63
- }
64
-
65
- /** A tag's BODY (its description — the node's own scalar value), or null. */
66
- export function tagBody(value: unknown): string | null {
67
- if (typeof value === "string") return value;
68
- const marker = (value as Record<string, unknown> | null | undefined)?.[MIXED_KEY] as MixedMarker | undefined;
69
- return typeof marker?.value === "string" ? marker.value : null;
70
- }
71
-
72
- /** A tag node value's explicit `color`, if any. Depth-limited projection may hand the color
73
- * scalar as a `$yamloverLink` marker instead of a plain string — both shapes are read. */
74
- export function explicitColor(value: unknown): string | null {
75
- const raw = tagFields(value).find(([k]) => k === "color")?.[1];
76
- if (typeof raw === "string") return raw;
77
- const linked = (raw as { $yamloverLink?: { value?: unknown } } | null | undefined)?.$yamloverLink?.value;
78
- return typeof linked === "string" ? linked : null;
79
- }
80
-
81
- /** The tags a node is classified under, each a colored luggage-tag shape (one end
82
- * rectangular, the other a pierced triangular point), shown inline in the node's
83
- * header bar on every representation. Returns the tags directly (a fragment) so
84
- * they flow among the header's type/format chips. */
85
- export function TagBadges({ tags, onNavigate }: { tags: TagLink[]; onNavigate: (path: string) => void }) {
86
- if (tags.length === 0) return null;
87
- return (
88
- <>
89
- {tags.map((t) => (
90
- <a
91
- key={t.path}
92
- className="tagtag"
93
- style={{ background: resolveTagColor({ name: t.label, color: t.color }) }}
94
- href={t.path}
95
- title={t.label}
96
- onClick={(e) => {
97
- e.preventDefault();
98
- onNavigate(t.path);
99
- }}
100
- >
101
- {t.label}
102
- </a>
103
- ))}
104
- </>
105
- );
106
- }
107
-
108
- /** A node's display name: its schema title, else its last path segment. */
109
- export function tagLabel(path: string, title?: string | null): string {
110
- if (title) return title;
111
- const segs = strToSegs(path);
112
- return segs.length ? String(segs[segs.length - 1]) : path;
113
- }
@@ -1,42 +0,0 @@
1
- import { marked } from "marked";
2
- import { NodeJson } from "../api";
3
- import { scalarValue } from "../render";
4
- import { Chunk } from "./registry";
5
- import { anchorizeHeadings, useHashScroll } from "./headings";
6
- import { Markup } from "./markup";
7
-
8
- /**
9
- * The renderer for a `string`/`text/markdown` node: prose shown as rendered
10
- * Markdown rather than a quoted scalar. It serves in two contexts via the one
11
- * (type, format) routing key:
12
- *
13
- * - as a full RHS page (`render`) — a standalone prose node (e.g. a `.md`
14
- * file, read as a string by the server), and
15
- * - inline (`renderChunk`) — a single chunk embedded in another renderer's
16
- * page, e.g. one paragraph of a chapter.
17
- *
18
- * Markdown is parsed with `marked`; the value is whatever string the node holds,
19
- * so the same renderer covers an inline `const` chunk and a whole `.md` file. Each
20
- * heading is then given an id and a `§` anchor link (see {@link anchorizeHeadings})
21
- * so a section is addressable as `<page>#<slug>`.
22
- */
23
- function md(value: unknown): string {
24
- return anchorizeHeadings(marked.parse(String(value ?? ""), { async: false }) as string);
25
- }
26
-
27
- export function TextView({ node }: { node: NodeJson }) {
28
- useHashScroll(node);
29
- return (
30
- <div className="text">
31
- {node.title && <h1 className="chapter-title">{node.title}</h1>}
32
- {node.description && <p className="chapter-subtitle">{node.description}</p>}
33
- <Markup html={md(scalarValue(node.value))} />
34
- </div>
35
- );
36
- }
37
-
38
- /** A prose chunk embedded inline: just the rendered Markdown (the chapter
39
- * supplies the surrounding number + anchor). */
40
- export function TextChunk({ chunk }: { chunk: Chunk }) {
41
- return <div className="markup" dangerouslySetInnerHTML={{ __html: md(chunk.value) }} />;
42
- }
@@ -1,33 +0,0 @@
1
- import UTIF from "utif";
2
- import { NodeJson } from "../api";
3
- import { DecodedImageView, rgbaToPng } from "./decoded";
4
-
5
- /**
6
- * Renders a TIFF image (`image/tiff`, `.tif`/`.tiff`). Browsers don't display
7
- * TIFF, so we decode it with UTIF.js: each top-level IFD is one page (multi-page
8
- * TIFFs — common for scans — render every page), decoded to RGBA and painted to a
9
- * canvas/PNG. IFDs without dimensions (e.g. stray metadata directories) are skipped.
10
- */
11
- export function TiffView({ node }: { node: NodeJson }) {
12
- return (
13
- <DecodedImageView
14
- node={node}
15
- label="tiff"
16
- decode={async (buf) => {
17
- const view = new Uint8Array(buf);
18
- const ifds = UTIF.decode(view);
19
- const pages: Blob[] = [];
20
- for (const ifd of ifds) {
21
- UTIF.decodeImage(view, ifd);
22
- const w = ifd.width as number;
23
- const h = ifd.height as number;
24
- if (!w || !h) continue;
25
- const rgba = UTIF.toRGBA8(ifd);
26
- pages.push(await rgbaToPng(rgba, w, h));
27
- }
28
- if (!pages.length) throw new Error("no decodable image in this TIFF");
29
- return pages;
30
- }}
31
- />
32
- );
33
- }