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,142 +0,0 @@
1
- import { NodeJson } from "../api";
2
- import { asLink, Link } from "../render";
3
- import { segsToStr, strToSegs } from "../paths";
4
- import { Chunk, rendererFor } from "./registry";
5
- import { useHashScroll } from "./headings";
6
-
7
- /**
8
- * The renderer for an `object`/`x-yamlover-chapter`: a chapter shown as a readable
9
- * page. This is yamlover's first instance of **partial flattening** (see the global
10
- * README): a deeper subtree is presented shallowly, pulling some descendants up as
11
- * constituent parts of *this* page rather than as nodes you navigate away to.
12
- *
13
- * A chapter is a heading (`title`/`description`) plus two arrays:
14
- *
15
- * - `chunks` — the body, **flattened** into this page as numbered blocks. Each
16
- * chunk is delegated to the renderer for its own (type, format), so
17
- * a chapter is not prose-only: a `text/markdown` chunk routes to the
18
- * text renderer, a `text/x-plantuml` chunk to the diagram renderer,
19
- * and any file-backed binary (image, html, pdf, fb2, epub, psd,
20
- * tiff, …) to its own renderer via `renderChunk`. A flattened chunk
21
- * exposes its location as a **fragment anchor** whose syntax is the
22
- * chunk's path continuation: chunk `[1]`, still reachable in full at
23
- * `<chapter>:chunks[1]`, is anchored here at `#:chunks[1]` (so
24
- * `<chapter>#:chunks[1]` scrolls to it). The `§N` marker is that
25
- * in-page anchor link. See 16-all-formats-chunks.
26
- * - `children` — the subchapters, *not* flattened: rendered as heading links you
27
- * navigate to (and surfaced in the TOC; see `chapterTocView`).
28
- *
29
- * The value arrives two levels deep (see the chapter renderer's `depth`): the
30
- * arrays are present, and each element is a link marker carrying its (type,
31
- * format), value/title, and path.
32
- */
33
- export function ChapterView({
34
- node,
35
- onNavigate,
36
- }: {
37
- node: NodeJson;
38
- onNavigate: (path: string) => void;
39
- }) {
40
- const v = (node.value ?? {}) as { chunks?: unknown; children?: unknown };
41
- const chunks = Array.isArray(v.chunks) ? v.chunks : [];
42
- const children = Array.isArray(v.children) ? v.children : [];
43
-
44
- // A deep link to a flattened chunk (`<chapter>#/chunks[1]`) lands on the chapter
45
- // page, so scroll to the anchored chunk once it has rendered (the browser's own
46
- // scroll fires before the async value arrives). Re-runs when the chapter changes.
47
- useHashScroll(node);
48
-
49
- return (
50
- <div className="chapter">
51
- {node.title && <h1 className="chapter-title">{node.title}</h1>}
52
- {node.description && <p className="chapter-subtitle">{node.description}</p>}
53
-
54
- {chunks.map((item, i) => (
55
- <ChunkBlock key={i} index={i} item={item} basePath={node.path} documentPath={node.documentPath} onNavigate={onNavigate} />
56
- ))}
57
-
58
- {children.map((item, i) => {
59
- const link = asLink(item);
60
- return (
61
- <h2 className="chapter-link" key={i}>
62
- <a
63
- className="descend"
64
- href={link?.path ?? "#"}
65
- onClick={(e) => {
66
- e.preventDefault();
67
- if (link) onNavigate(link.path);
68
- }}
69
- >
70
- {chapterTitle(link)}
71
- </a>
72
- </h2>
73
- );
74
- })}
75
- </div>
76
- );
77
- }
78
-
79
- /** One numbered chunk, flattened into the chapter page: its zero-based index `§N`
80
- * as an in-page anchor link to the chunk's own location (the fragment mirrors the
81
- * chunk's path continuation), and the chunk rendered by the renderer for its
82
- * (type, format) — falling back to a plain paragraph when none claims it (or the
83
- * chunk is a bare inline value with no path). */
84
- function ChunkBlock({
85
- index,
86
- item,
87
- basePath,
88
- documentPath,
89
- onNavigate,
90
- }: {
91
- index: number;
92
- item: unknown;
93
- basePath: string;
94
- documentPath?: string;
95
- onNavigate: (path: string) => void;
96
- }) {
97
- const link = asLink(item);
98
- const chunk: Chunk = {
99
- value: link ? link.value : item,
100
- path: link?.path ?? "",
101
- type: link?.type ?? "string",
102
- format: link?.format ?? null,
103
- // the renderer-dispatch facets (TYPES.md §9): a link carries them; a bare inline chunk is a string
104
- valueType: link?.valueType ?? "string",
105
- hasKeyed: link?.hasKeyed ?? false,
106
- hasOrdinal: link?.hasOrdinal ?? false,
107
- documentPath, // carried so a marklower chunk's `/…` link resolves to its document
108
- };
109
- const renderer = rendererFor(chunk);
110
- const body = renderer?.renderChunk
111
- ? renderer.renderChunk(chunk, onNavigate)
112
- : <p className="chapter-prose">{String(chunk.value ?? "")}</p>;
113
- // The chunk's location *within this page*: its path continuation past the chapter
114
- // (e.g. `/chunks[1]`), used as both the element id and the `§N` anchor link. The
115
- // full path stays navigable; this is the flattened, in-page locator.
116
- const anchor = chunk.path ? pathContinuation(basePath, chunk.path) : null;
117
- return (
118
- <div className="chunk" id={anchor ?? undefined}>
119
- {anchor ? (
120
- <a className="chunk-index" href={`#${anchor}`}>
121
- §{index}
122
- </a>
123
- ) : (
124
- <span className="chunk-index">§{index}</span>
125
- )}
126
- <div className="chunk-body">{body}</div>
127
- </div>
128
- );
129
- }
130
-
131
- /** The path continuation from `base` to `full` — the segments of `full` past
132
- * `base`, in JSON-path syntax. E.g. ("/book", "/book/chunks[1]") → "/chunks[1]".
133
- * A flattened child's fragment anchor is exactly this continuation, so the anchor
134
- * spelling matches the still-navigable full path. */
135
- function pathContinuation(base: string, full: string): string {
136
- return segsToStr(strToSegs(full).slice(strToSegs(base).length));
137
- }
138
-
139
- /** A subchapter link's label: its schema title, else a generic fallback. */
140
- function chapterTitle(link: Link | null): string {
141
- return link?.title ?? "(untitled chapter)";
142
- }
@@ -1,234 +0,0 @@
1
- import { NodeJson } from "../api";
2
- import { scalarValue } from "../render";
3
- import { Chunk } from "./registry";
4
-
5
- /**
6
- * The renderer for a `string`/`text/csv` (or `text/tab-separated-values`) node:
7
- * delimited text shown as a table. Like the markdown/asciidoc renderers it works
8
- * from the node's string value, so it serves both a whole `.csv`/`.tsv` file
9
- * (`render`) and a single inline chunk (`renderChunk`).
10
- *
11
- * The **main parsing parameters live in the URL**, alongside `?format=csv`, so a
12
- * particular reading of a file is a shareable link:
13
- *
14
- * - `sep` — the field separator. `,` `;` `|`, the word `tab` (or `space`), or
15
- * empty/absent for **auto-detect** (the most frequent candidate on
16
- * the first line; `\t` for a `.tsv`).
17
- * - `header` — whether the first row is a header (default true; `false`/`0` off).
18
- *
19
- * The full-page view exposes these in the node bar beside the renderer's tab (the
20
- * {@link CsvControls} `config` control, like the markdown/asciidoc width input),
21
- * writing the same query params (via `history.replaceState`, preserving the path +
22
- * `format`), so the URL stays the single source of truth — editing it by hand and
23
- * reloading is equivalent to using the controls.
24
- */
25
-
26
- /** Parse delimited text into rows of fields, RFC-4180-ish: fields may be wrapped in
27
- * `quote`, a doubled quote is a literal one, and separators/newlines inside quotes
28
- * are data. Handles `\n` and `\r\n`; a trailing newline does not yield an empty
29
- * row. */
30
- export function parseDelimited(text: string, sep: string, quote = '"'): string[][] {
31
- const rows: string[][] = [];
32
- let row: string[] = [];
33
- let field = "";
34
- let inQuotes = false;
35
- let i = 0;
36
- const pushField = () => {
37
- row.push(field);
38
- field = "";
39
- };
40
- const pushRow = () => {
41
- pushField();
42
- rows.push(row);
43
- row = [];
44
- };
45
- while (i < text.length) {
46
- const c = text[i];
47
- if (inQuotes) {
48
- if (c === quote) {
49
- if (text[i + 1] === quote) {
50
- field += quote;
51
- i += 2;
52
- } else {
53
- inQuotes = false;
54
- i++;
55
- }
56
- } else {
57
- field += c;
58
- i++;
59
- }
60
- continue;
61
- }
62
- if (c === quote) {
63
- inQuotes = true;
64
- i++;
65
- } else if (c === sep) {
66
- pushField();
67
- i++;
68
- } else if (c === "\n" || c === "\r") {
69
- if (c === "\r" && text[i + 1] === "\n") i++;
70
- pushRow();
71
- i++;
72
- } else {
73
- field += c;
74
- i++;
75
- }
76
- }
77
- // flush the final field/row unless the text ended exactly on a row break
78
- if (field !== "" || row.length) pushRow();
79
- return rows;
80
- }
81
-
82
- /** Decode a `sep` URL value to the actual separator character, or null for
83
- * auto-detect (empty/absent). `tab`/`space` are spelled out for URL-friendliness. */
84
- function decodeSep(v: string | null): string | null {
85
- if (!v) return null;
86
- if (v === "tab" || v === "\\t") return "\t";
87
- if (v === "space") return " ";
88
- return v[0];
89
- }
90
-
91
- /** Auto-detect a separator from the first line: a `.tsv` is tab; otherwise the most
92
- * frequent of `, ; \t |` (comma when none appears). */
93
- function autoSep(text: string, format: string | null): string {
94
- if (format === "text/tab-separated-values") return "\t";
95
- const firstLine = text.split(/\r?\n/, 1)[0] ?? "";
96
- let best = ",";
97
- let bestN = 0;
98
- for (const c of [",", ";", "\t", "|"]) {
99
- const n = firstLine.split(c).length - 1;
100
- if (n > bestN) {
101
- bestN = n;
102
- best = c;
103
- }
104
- }
105
- return best;
106
- }
107
-
108
- const SEP_OPTIONS: { label: string; value: string }[] = [
109
- { label: "auto", value: "" },
110
- { label: "comma ,", value: "," },
111
- { label: "semicolon ;", value: ";" },
112
- { label: "tab", value: "tab" },
113
- { label: "pipe |", value: "|" },
114
- ];
115
-
116
- const params = () => new URLSearchParams(window.location.search);
117
-
118
- /** Read the (header) flag from the URL — default true, off for `false`/`0`. */
119
- function headerOn(p: URLSearchParams): boolean {
120
- const h = p.get("header");
121
- return !(h === "false" || h === "0");
122
- }
123
-
124
- /** Pad a row to `cols` cells so every `<tr>` is rectangular. */
125
- function pad(row: string[], cols: number): string[] {
126
- return row.length >= cols ? row : [...row, ...Array(cols - row.length).fill("")];
127
- }
128
-
129
- /** The table itself — shared by the full page and the inline chunk. */
130
- function Table({ rows, header }: { rows: string[][]; header: boolean }) {
131
- if (!rows.length) return <p className="csv-empty">(empty)</p>;
132
- const cols = rows.reduce((m, r) => Math.max(m, r.length), 0);
133
- const head = header ? rows[0] : null;
134
- const body = header ? rows.slice(1) : rows;
135
- return (
136
- <div className="csv-scroll">
137
- <table className="csv-table">
138
- {head && (
139
- <thead>
140
- <tr>
141
- {pad(head, cols).map((c, i) => (
142
- <th key={i}>{c}</th>
143
- ))}
144
- </tr>
145
- </thead>
146
- )}
147
- <tbody>
148
- {body.map((r, ri) => (
149
- <tr key={ri}>
150
- {pad(r, cols).map((c, ci) => (
151
- <td key={ci}>{c}</td>
152
- ))}
153
- </tr>
154
- ))}
155
- </tbody>
156
- </table>
157
- </div>
158
- );
159
- }
160
-
161
- export function CsvView({ node }: { node: NodeJson }) {
162
- // Options are read straight from the URL each render; the node bar's CsvControls
163
- // (see registry `config`) write them and re-render the node view, so the URL stays
164
- // the single source of truth — this view holds no parsing state of its own.
165
- const p = params();
166
- const header = headerOn(p);
167
- const text = String(scalarValue(node.value) ?? "");
168
- const sep = decodeSep(p.get("sep")) ?? autoSep(text, node.format ?? null);
169
- const rows = parseDelimited(text, sep);
170
-
171
- return (
172
- <div className="csv">
173
- {node.title && <h1 className="chapter-title">{node.title}</h1>}
174
- {node.description && <p className="chapter-subtitle">{node.description}</p>}
175
- <Table rows={rows} header={header} />
176
- </div>
177
- );
178
- }
179
-
180
- /**
181
- * The CSV parsing controls (separator + header row) shown in the node bar beside
182
- * the renderer's tab — the `config` hook, mirroring the markdown/asciidoc width
183
- * input. Each writes a query param (preserving the path + other params) and calls
184
- * `rerender` so {@link CsvView} re-reads the URL and re-parses.
185
- */
186
- export function CsvControls({ rerender }: { rerender: () => void }) {
187
- const p = params();
188
- const sepParam = p.get("sep") ?? "";
189
- const header = headerOn(p);
190
-
191
- const setParam = (key: string, value: string) => {
192
- const q = params();
193
- if (value) q.set(key, value);
194
- else q.delete(key);
195
- const qs = q.toString();
196
- window.history.replaceState({}, "", window.location.pathname + (qs ? "?" + qs : ""));
197
- rerender();
198
- };
199
-
200
- return (
201
- <div className="csv-toolbar">
202
- <label>
203
- separator{" "}
204
- <select value={sepParam} onChange={(e) => setParam("sep", e.target.value)}>
205
- {SEP_OPTIONS.map((o) => (
206
- <option key={o.value} value={o.value}>
207
- {o.label}
208
- </option>
209
- ))}
210
- </select>
211
- </label>
212
- <label>
213
- <input
214
- type="checkbox"
215
- checked={header}
216
- onChange={(e) => setParam("header", e.target.checked ? "" : "false")}
217
- />{" "}
218
- header row
219
- </label>
220
- </div>
221
- );
222
- }
223
-
224
- /** A CSV chunk embedded inline in a chapter: just the table, auto-detecting the
225
- * separator and treating the first row as a header (no per-chunk URL controls). */
226
- export function CsvChunk({ chunk }: { chunk: Chunk }) {
227
- const text = String(chunk.value ?? "");
228
- const rows = parseDelimited(text, autoSep(text, chunk.format ?? null));
229
- return (
230
- <div className="csv">
231
- <Table rows={rows} header />
232
- </div>
233
- );
234
- }
@@ -1,72 +0,0 @@
1
- import { useEffect, useState } from "react";
2
- import { NodeJson, blobUrl } from "../api";
3
- import { PanZoomImage } from "./imagemap";
4
-
5
- /**
6
- * Shared scaffold for the file formats the browser cannot display natively but
7
- * that we can decode client-side to ordinary raster images (PSD, TIFF, HEIC).
8
- * It fetches the node's bytes from `/api/blob`, hands them to a format-specific
9
- * `decode` that returns one PNG `Blob` per page, and shows each in the same
10
- * pan/zoom viewer as a native image (each decoded page is its own object-URL).
11
- * Object-URLs are revoked on unmount / path change so decoded pages don't leak.
12
- */
13
- export function DecodedImageView({
14
- node,
15
- label,
16
- decode,
17
- }: {
18
- node: NodeJson;
19
- label: string;
20
- decode: (buf: ArrayBuffer) => Promise<Blob[]>;
21
- }) {
22
- const [urls, setUrls] = useState<string[]>([]);
23
- const [error, setError] = useState<string | null>(null);
24
-
25
- useEffect(() => {
26
- let cancelled = false;
27
- const created: string[] = [];
28
- setUrls([]);
29
- setError(null);
30
- (async () => {
31
- const buf = await fetch(blobUrl(node.path)).then((r) => r.arrayBuffer());
32
- const blobs = await decode(buf);
33
- if (cancelled) return;
34
- for (const b of blobs) created.push(URL.createObjectURL(b));
35
- if (!cancelled) setUrls(created);
36
- })().catch((e) => !cancelled && setError(String((e as Error).message || e)));
37
- return () => {
38
- cancelled = true;
39
- created.forEach(URL.revokeObjectURL);
40
- };
41
- }, [node.path]);
42
-
43
- if (error) return <div className="error">{label}: {error}</div>;
44
- if (!urls.length) return <div className="loading">decoding {label}…</div>;
45
- return (
46
- <>
47
- {urls.map((url, i) => (
48
- <PanZoomImage key={i} src={url} className="filemap fileimagemap" />
49
- ))}
50
- </>
51
- );
52
- }
53
-
54
- /** Paint RGBA pixels onto a canvas and export it as a PNG blob. */
55
- export async function rgbaToPng(rgba: Uint8ClampedArray | Uint8Array, width: number, height: number): Promise<Blob> {
56
- const canvas = document.createElement("canvas");
57
- canvas.width = width;
58
- canvas.height = height;
59
- const ctx = canvas.getContext("2d");
60
- if (!ctx) throw new Error("no 2d context");
61
- const img = ctx.createImageData(width, height);
62
- img.data.set(rgba);
63
- ctx.putImageData(img, 0, 0);
64
- return canvasToPng(canvas);
65
- }
66
-
67
- /** Export a canvas as a PNG blob (Promise wrapper over the callback API). */
68
- export function canvasToPng(canvas: HTMLCanvasElement): Promise<Blob> {
69
- return new Promise((resolve, reject) =>
70
- canvas.toBlob((b) => (b ? resolve(b) : reject(new Error("canvas export failed"))), "image/png"),
71
- );
72
- }