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,83 +0,0 @@
1
- import { deflateSync } from "fflate";
2
- import { NodeJson } from "../api";
3
- import { scalarValue } from "../render";
4
- import { Chunk } from "./registry";
5
-
6
- /**
7
- * The renderer for a `string`/`text/x-plantuml` node: PlantUML source shown as the
8
- * diagram it describes. Like the markdown/asciidoc renderers it works from the
9
- * node's string value, so it serves both a whole `.puml` file (`render`) and a
10
- * single inline chunk embedded in a chapter (`renderChunk`).
11
- *
12
- * PlantUML is a *language that compiles to a picture* — it can only be rendered by
13
- * a PlantUML server. So, exactly as every PlantUML integration does, the source is
14
- * deflate+encoded into a URL and handed to one as an `<img>`. The default is the
15
- * public server; point `VITE_PLANTUML_SERVER` at a self-hosted instance (e.g.
16
- * `docker run -d -p 8080:8080 plantuml/plantuml-server`) to keep diagrams off it.
17
- */
18
- const PLANTUML_SERVER =
19
- ((import.meta as any).env?.VITE_PLANTUML_SERVER as string | undefined)?.replace(/\/+$/, "") ??
20
- "https://www.plantuml.com/plantuml";
21
-
22
- // PlantUML's text transport: UTF-8 → raw DEFLATE → its own base64 variant (the
23
- // alphabet `0-9 A-Z a-z - _`, three bytes packed into four 6-bit chars). The
24
- // server inflates it back, so any valid DEFLATE stream is accepted — we let
25
- // fflate compress and only reproduce PlantUML's character mapping.
26
- function encode6bit(b: number): string {
27
- if (b < 10) return String.fromCharCode(48 + b);
28
- b -= 10;
29
- if (b < 26) return String.fromCharCode(65 + b);
30
- b -= 26;
31
- if (b < 26) return String.fromCharCode(97 + b);
32
- b -= 26;
33
- if (b === 0) return "-";
34
- if (b === 1) return "_";
35
- return "?";
36
- }
37
-
38
- function append3bytes(b1: number, b2: number, b3: number): string {
39
- const c1 = b1 >> 2;
40
- const c2 = ((b1 & 0x3) << 4) | (b2 >> 4);
41
- const c3 = ((b2 & 0xf) << 2) | (b3 >> 6);
42
- const c4 = b3 & 0x3f;
43
- return encode6bit(c1 & 0x3f) + encode6bit(c2 & 0x3f) + encode6bit(c3 & 0x3f) + encode6bit(c4 & 0x3f);
44
- }
45
-
46
- function encode64(data: Uint8Array): string {
47
- let r = "";
48
- for (let i = 0; i < data.length; i += 3) {
49
- if (i + 2 === data.length) r += append3bytes(data[i], data[i + 1], 0);
50
- else if (i + 1 === data.length) r += append3bytes(data[i], 0, 0);
51
- else r += append3bytes(data[i], data[i + 1], data[i + 2]);
52
- }
53
- return r;
54
- }
55
-
56
- /** The PlantUML server URL that renders `source` as an SVG diagram. */
57
- export function plantumlUrl(source: string): string {
58
- const deflated = deflateSync(new TextEncoder().encode(source), { level: 9 });
59
- return `${PLANTUML_SERVER}/svg/${encode64(deflated)}`;
60
- }
61
-
62
- function Diagram({ source }: { source: string }) {
63
- return (
64
- <div className="filemedia">
65
- <img className="fileimage plantuml" src={plantumlUrl(source)} alt="PlantUML diagram" />
66
- </div>
67
- );
68
- }
69
-
70
- export function PlantumlView({ node }: { node: NodeJson }) {
71
- return (
72
- <div className="text">
73
- {node.title && <h1 className="chapter-title">{node.title}</h1>}
74
- {node.description && <p className="chapter-subtitle">{node.description}</p>}
75
- <Diagram source={String(scalarValue(node.value) ?? "")} />
76
- </div>
77
- );
78
- }
79
-
80
- /** A diagram chunk embedded inline (the chapter supplies the number + anchor). */
81
- export function PlantumlChunk({ chunk }: { chunk: Chunk }) {
82
- return <Diagram source={String(chunk.value ?? "")} />;
83
- }
@@ -1,25 +0,0 @@
1
- import { readPsd } from "ag-psd";
2
- import { NodeJson } from "../api";
3
- import { DecodedImageView, canvasToPng } from "./decoded";
4
-
5
- /**
6
- * Renders an Adobe Photoshop document (`image/vnd.adobe.photoshop`, `.psd`/`.psb`).
7
- * The browser has no native PSD support, so we decode it with `ag-psd`: read the
8
- * file's *flattened composite* — the merged RGB preview Photoshop embeds on save —
9
- * which `ag-psd` paints onto a `<canvas>` for us, and export that as a PNG. We skip
10
- * the per-layer/thumbnail image data: we only show the one composite, and decoding
11
- * every layer of a big PSD would allocate far more than we display.
12
- */
13
- export function PsdView({ node }: { node: NodeJson }) {
14
- return (
15
- <DecodedImageView
16
- node={node}
17
- label="psd"
18
- decode={async (buf) => {
19
- const psd = readPsd(buf, { skipLayerImageData: true, skipThumbnail: true });
20
- if (!psd.canvas) throw new Error("no composite image in this PSD");
21
- return [await canvasToPng(psd.canvas)];
22
- }}
23
- />
24
- );
25
- }
@@ -1,411 +0,0 @@
1
- import { lazy, Suspense } from "react";
2
- import { NodeJson, TreeNode } from "../api";
3
- import { ChapterView } from "./chapter";
4
- import { TextView, TextChunk } from "./text";
5
- import { MarklowerView, MarklowerChunk } from "./marklower";
6
- import { LatexView, LatexChunk } from "./latex";
7
- import { AsciidocView, AsciidocChunk } from "./asciidoc";
8
- import { CsvView, CsvChunk, CsvControls } from "./csv";
9
- import { PlaintextView, PlaintextChunk, EncodingControl } from "./plaintext";
10
- import { RtfView, RtfChunk } from "./rtf";
11
- import { DocView, DocChunk } from "./doc";
12
- import { PlantumlView, PlantumlChunk } from "./plantuml";
13
- import { ExplorerView, ExplorerViewControl } from "./explorer";
14
- import { Fb2View } from "./fb2";
15
- import { EpubView } from "./epub";
16
- import { HtmlView } from "./media";
17
- import { MarkupWidthControl } from "./markup";
18
-
19
- // pdf.js and DjVu.js are heavy and browser-only (they reach for canvas globals at
20
- // import time). Load them lazily so the registry — imported by the TOC and by
21
- // tests — never pulls them in until a PDF/DjVu node is actually rendered.
22
- const PdfView = lazy(() => import("./pdf").then((m) => ({ default: m.PdfView })));
23
- const DjvuView = lazy(() => import("./djvu").then((m) => ({ default: m.DjvuView })));
24
- const PsdView = lazy(() => import("./psd").then((m) => ({ default: m.PsdView })));
25
- const TiffView = lazy(() => import("./tiff").then((m) => ({ default: m.TiffView })));
26
- const HeicView = lazy(() => import("./heic").then((m) => ({ default: m.HeicView })));
27
- // mammoth (.docx) and SheetJS (.xls/.xlsx) are heavy; load each on first use.
28
- const DocxView = lazy(() => import("./docx").then((m) => ({ default: m.DocxView })));
29
- const DocxChunk = lazy(() => import("./docx").then((m) => ({ default: m.DocxChunk })));
30
- const SpreadsheetView = lazy(() => import("./spreadsheet").then((m) => ({ default: m.SpreadsheetView })));
31
- const SpreadsheetChunk = lazy(() => import("./spreadsheet").then((m) => ({ default: m.SpreadsheetChunk })));
32
- // Leaflet (KML/KMZ maps; and the pan/zoom image viewer) is heavy and browser-only; lazy-load.
33
- const MapView = lazy(() => import("./map").then((m) => ({ default: m.MapView })));
34
- const MapChunk = lazy(() => import("./map").then((m) => ({ default: m.MapChunk })));
35
- const ImageView = lazy(() => import("./imagemap").then((m) => ({ default: m.ImageView })));
36
- const ImageChunk = lazy(() => import("./imagemap").then((m) => ({ default: m.ImageChunk })));
37
- const lazily = (el: JSX.Element) => <Suspense fallback={<div className="loading">…</div>}>{el}</Suspense>;
38
-
39
- /** Synthesize a minimal `NodeJson` from a chunk so a file-backed renderer (which
40
- * only needs the node's `path`/`value`) can be reused *inline* as a chapter
41
- * chunk — the same view, addressed by the chunk's own node path. This is how a
42
- * PDF / DjVu / PSD / TIFF / HEIC / FB2 / EPUB / HTML chunk renders in a chapter
43
- * body, not just a full page. */
44
- const chunkNode = (chunk: Chunk): NodeJson => ({
45
- path: chunk.path,
46
- type: chunk.type,
47
- format: chunk.format,
48
- concrete: null,
49
- title: null,
50
- description: null,
51
- value: chunk.value,
52
- });
53
-
54
- /**
55
- * A renderer turns a node into a React element for the RHS pane. It is selected
56
- * by the node's **(type, format)** tuple — the same key the TOC icons and the
57
- * link markers carry — so a renderer can claim, say, every `string`/`text/markdown`,
58
- * every `object`/`x-yamlover-chapter`, or a bare `(type, None)`. Our own custom
59
- * formats are prefixed `x-yamlover-`.
60
- *
61
- * The registry is the single extension point: add an entry here to teach the UI a
62
- * new renderable shape. A renderer's `name` is also its representation key — the
63
- * label of its tab and the `?format=` value (e.g. `chapter`).
64
- *
65
- * A renderer participates in the UI three ways, all keyed by the same tuple:
66
- * - `render` — the full RHS page.
67
- * - `renderChunk` — its *inline* form, when embedded in another renderer's page
68
- * (a chapter renders each chunk by routing to the chunk's own renderer here).
69
- * - `tocView` — how the node appears in the TOC: which children are navigable,
70
- * whether it expands, and whether they are loaded. The chapter unwraps its
71
- * `children` array (subchapters become its direct TOC entries) and keeps its
72
- * `chunks` off the tree (prose is read on the page, not browsed).
73
- */
74
-
75
- /** A single chunk handed to a renderer's `renderChunk` — its value plus the
76
- * (type, format) it was routed on and its JSON path (the anchor target). */
77
- export interface Chunk {
78
- value: unknown;
79
- path: string;
80
- type: string;
81
- format: string | null;
82
- valueType?: string | null; // renderer dispatch facets (TYPES.md §9) — so a tagged chunk still routes
83
- hasKeyed?: boolean;
84
- hasOrdinal?: boolean;
85
- /** The JSON-space path of the document this chunk belongs to (the enclosing
86
- * chapter's `documentPath`) — the anchor a document-relative (`/…`) marklower
87
- * link in the chunk resolves against. */
88
- documentPath?: string;
89
- }
90
-
91
- /** How a node appears in the TOC. `children` are the rows shown beneath it;
92
- * `expandable` shows a chevron; `loaded` false means the children must be
93
- * fetched (by `node.path`) on first expand. `loadDepth` is how many levels that
94
- * expand fetch must pull (default 1) — more when a renderer's TOC rows live
95
- * deeper than the node's direct children (a chapter surfaces its subchapters
96
- * from *under* its `children` wrapper, and fetches one further level so each
97
- * revealed subchapter's own chevron is accurate — so it needs 3). */
98
- export interface TocView {
99
- children: TreeNode[];
100
- expandable: boolean;
101
- loaded: boolean;
102
- loadDepth?: number;
103
- }
104
-
105
- /** The three TYPE FACETS a renderer dispatches on (TYPES.md §9): the scalar self-VALUE's type,
106
- * the node's `format`, and whether it owns keyed/ordinal elements. */
107
- export interface TypeFacets {
108
- valueType: string | null;
109
- format: string | null;
110
- hasKeyed: boolean;
111
- hasOrdinal: boolean;
112
- }
113
- /** A renderer's acceptance predicate — a hand-coded type formula. What it does NOT test, it
114
- * TOLERATES: a `byFormat("text/markdown")` matcher ignores the keyed/ordinal facets, so a
115
- * markdown chunk that gained `yamlover-annotations` keys (an omni node) still matches. */
116
- export type Accepts = (f: TypeFacets) => boolean;
117
-
118
- /** Any projected node/chunk/link shape carrying the facet fields. */
119
- type FacetSource = { type?: string; format?: string | null; valueType?: string | null; hasKeyed?: boolean; hasOrdinal?: boolean };
120
- const facetsFrom = (n: FacetSource): TypeFacets => ({ valueType: n.valueType ?? null, format: n.format ?? null, hasKeyed: !!n.hasKeyed, hasOrdinal: !!n.hasOrdinal });
121
- /** The common matcher: claims a node whose `format` is one of `fmts` — tolerant of all structure. */
122
- const byFormat = (...fmts: string[]): Accepts => (f) => f.format !== null && fmts.includes(f.format);
123
-
124
- export interface Renderer {
125
- name: string;
126
- /** Whether this renderer claims a node, from its {@link TypeFacets}. */
127
- accepts: Accepts;
128
- /** Tie-break among matches — the highest wins. Format matchers are 2; the bare-string
129
- * default (marklower) is 1. */
130
- specificity: number;
131
- /** Value depth `NodeView` must fetch for this renderer (default 1). A chapter
132
- * needs 2: its `chunks`/`children` arrays one level, their elements the next. */
133
- depth?: number;
134
- /** This node's TOC presentation (default: its own children, lazily loaded). */
135
- tocView?: (node: TreeNode) => TocView;
136
- render: (node: NodeJson, onNavigate: (path: string) => void) => JSX.Element;
137
- /** This renderer's inline form, for embedding a single value in another page. */
138
- renderChunk?: (chunk: Chunk, onNavigate: (path: string) => void) => JSX.Element;
139
- /** An optional control shown in the tab bar beside this renderer's button (only while its
140
- * view is active) — e.g. the markdown/asciidoc reading-width input. `rerender` refreshes
141
- * the node view after the control changes a URL parameter. */
142
- config?: (rerender: () => void) => JSX.Element;
143
- }
144
-
145
- /**
146
- * The EXPLORER — a file-manager "small icons" grid of a node's members, uplinks first. It is
147
- * claimed two ways: by (type, format) for tags (a tag projects as `object` — fields only,
148
- * `variant` — description BODY + fields, `string` — a leaf tag that is just its description,
149
- * or `null` — a bare tag with neither, the shape the picker's create-on-miss writes;
150
- * the grid shows the tagged MATERIALS), and as the CONCRETE fallback for any node stored as a
151
- * filesystem directory (`dir`/`yamlover`) that no (type, format) renderer claims — see
152
- * {@link getRenderer}. Hoisted so the fallback can reference the same instance.
153
- */
154
- const EXPLORER: Renderer = {
155
- name: "explorer",
156
- // a tag, whatever shape it projects (object / variant / leaf string / bare null) — the format
157
- // alone identifies it; the grid shows the tagged MATERIALS. Also the dir-concrete fallback below.
158
- accepts: byFormat("x-yamlover-tag"),
159
- specificity: 2,
160
- render: (node, onNavigate) => <ExplorerView node={node} onNavigate={onNavigate} />,
161
- config: (rerender) => <ExplorerViewControl rerender={rerender} />, // large/small icons (`?view=`)
162
- };
163
-
164
- /** A directory-stored node (`dir` = a plain folder, `yamlover` = a folder with `.yamlover/`). */
165
- const isDirConcrete = (concrete: string | null | undefined): boolean => concrete === "dir" || concrete === "yamlover";
166
-
167
- const REGISTRY: Renderer[] = [
168
- {
169
- name: "chapter",
170
- accepts: byFormat("x-yamlover-chapter"),
171
- specificity: 2,
172
- depth: 2, // reach the chunk/subchapter elements (arrays one level, items the next)
173
- tocView: chapterTocView,
174
- render: (node, onNavigate) => <ChapterView node={node} onNavigate={onNavigate} />,
175
- },
176
- {
177
- // Our default for a bare, format-less string: marklower, a markup language a
178
- // notch below Markdown. A chapter's prose chunks route here — both the bare
179
- // (string, null) form and the explicit `text/marklower` the chunk schema applies.
180
- name: "marklower",
181
- accepts: (f) => f.format === "text/marklower" || (f.format === null && f.valueType === "string"),
182
- specificity: 1, // the bare-string default — a tagged bare string (format-less) still routes here
183
- render: (node, onNavigate) => <MarklowerView node={node} onNavigate={onNavigate} />,
184
- renderChunk: (chunk, onNavigate) => <MarklowerChunk chunk={chunk} onNavigate={onNavigate} />,
185
- },
186
- {
187
- // Markdown (the component file is text.tsx for historical reasons; the renderer —
188
- // its tab label and `?format=` key — is named for what it renders).
189
- name: "markdown",
190
- accepts: byFormat("text/markdown"),
191
- specificity: 2,
192
- render: (node) => <TextView node={node} />,
193
- renderChunk: (chunk) => <TextChunk chunk={chunk} />,
194
- config: (rerender) => <MarkupWidthControl rerender={rerender} />,
195
- },
196
- {
197
- name: "asciidoc",
198
- accepts: byFormat("text/asciidoc"),
199
- specificity: 2,
200
- render: (node) => <AsciidocView node={node} />,
201
- renderChunk: (chunk) => <AsciidocChunk chunk={chunk} />,
202
- config: (rerender) => <MarkupWidthControl rerender={rerender} />,
203
- },
204
- {
205
- // Delimited text (CSV/TSV, a string) shown as a table; its parsing options
206
- // (separator, header) ride in the URL query — see csv.tsx.
207
- name: "csv",
208
- accepts: byFormat("text/csv", "text/tab-separated-values"),
209
- specificity: 2,
210
- render: (node) => <CsvView node={node} />,
211
- renderChunk: (chunk) => <CsvChunk chunk={chunk} />,
212
- config: (rerender) => <CsvControls rerender={rerender} />,
213
- },
214
- {
215
- // Plain text shown verbatim (no markup), with a node-bar encoding selector —
216
- // CP866 / Windows-1251 / KOI8-R / UTF-8 (see plaintext.tsx). Served as raw
217
- // bytes so the encoding is the client's to choose.
218
- name: "plaintext",
219
- accepts: byFormat("text/plain"),
220
- specificity: 2,
221
- render: (node) => <PlaintextView node={node} />,
222
- renderChunk: (chunk) => <PlaintextChunk chunk={chunk} />,
223
- config: (rerender) => <EncodingControl rerender={rerender} />,
224
- },
225
- {
226
- // RTF — a dependency-free converter to HTML (see rtf.tsx).
227
- name: "rtf",
228
- accepts: byFormat("application/rtf"),
229
- specificity: 2,
230
- render: (node) => <RtfView node={node} />,
231
- renderChunk: (chunk) => <RtfChunk chunk={chunk} />,
232
- },
233
- {
234
- // .docx (Office Open XML) via mammoth, lazily loaded.
235
- name: "docx",
236
- accepts: byFormat("application/vnd.openxmlformats-officedocument.wordprocessingml.document"),
237
- specificity: 2,
238
- render: (node) => lazily(<DocxView node={node} />),
239
- renderChunk: (chunk) => lazily(<DocxChunk chunk={chunk} />),
240
- },
241
- {
242
- // Excel workbooks — .xlsx and legacy .xls — via SheetJS, lazily loaded.
243
- name: "spreadsheet",
244
- accepts: byFormat("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", "application/vnd.ms-excel"),
245
- specificity: 2,
246
- render: (node) => lazily(<SpreadsheetView node={node} />),
247
- renderChunk: (chunk) => lazily(<SpreadsheetChunk chunk={chunk} />),
248
- },
249
- {
250
- // Legacy .doc (Word 97–2003 binary) — no in-browser parser; download fallback.
251
- name: "doc",
252
- accepts: byFormat("application/msword"),
253
- specificity: 2,
254
- render: (node) => <DocView node={node} />,
255
- renderChunk: (chunk) => <DocChunk chunk={chunk} />,
256
- },
257
- {
258
- // KML / KMZ geographic overlays drawn on a Leaflet map (lazily loaded).
259
- name: "map",
260
- accepts: byFormat("application/vnd.google-earth.kml+xml", "application/vnd.google-earth.kmz"),
261
- specificity: 2,
262
- render: (node) => lazily(<MapView node={node} />),
263
- renderChunk: (chunk) => lazily(<MapChunk chunk={chunk} />),
264
- },
265
- {
266
- // LaTeX math (a string) typeset with KaTeX, both whole and inline. marklower
267
- // reuses the same engine for its `$$…$$` spans.
268
- name: "latex",
269
- accepts: byFormat("text/x-latex"),
270
- specificity: 2,
271
- render: (node) => <LatexView node={node} />,
272
- renderChunk: (chunk) => <LatexChunk chunk={chunk} />,
273
- },
274
- {
275
- // PlantUML source (a string) shown as the diagram it compiles to, both as a
276
- // whole node and inline as a chapter chunk.
277
- name: "plantuml",
278
- accepts: byFormat("text/x-plantuml"),
279
- specificity: 2,
280
- render: (node) => <PlantumlView node={node} />,
281
- renderChunk: (chunk) => <PlantumlChunk chunk={chunk} />,
282
- },
283
- EXPLORER,
284
- {
285
- // File-backed binaries the server tags with an inferred image format.
286
- name: "image",
287
- accepts: byFormat("image/png", "image/jpeg", "image/gif", "image/webp", "image/avif", "image/bmp", "image/x-icon", "image/svg+xml"),
288
- specificity: 2,
289
- render: (node) => lazily(<ImageView node={node} />),
290
- renderChunk: (chunk) => lazily(<ImageChunk chunk={chunk} />),
291
- },
292
- {
293
- name: "html",
294
- accepts: byFormat("text/html"),
295
- specificity: 2,
296
- render: (node) => <HtmlView node={node} />,
297
- renderChunk: (chunk) => <HtmlView node={chunkNode(chunk)} />,
298
- },
299
- {
300
- name: "fb2",
301
- accepts: byFormat("application/x-fictionbook+xml"),
302
- specificity: 2,
303
- render: (node) => <Fb2View node={node} />,
304
- renderChunk: (chunk) => <Fb2View node={chunkNode(chunk)} />,
305
- },
306
- {
307
- name: "epub",
308
- accepts: byFormat("application/epub+zip"),
309
- specificity: 2,
310
- render: (node) => <EpubView node={node} />,
311
- renderChunk: (chunk) => <EpubView node={chunkNode(chunk)} />,
312
- },
313
- {
314
- name: "pdf",
315
- accepts: byFormat("application/pdf"),
316
- specificity: 2,
317
- render: (node) => lazily(<PdfView node={node} />),
318
- renderChunk: (chunk) => lazily(<PdfView node={chunkNode(chunk)} />),
319
- },
320
- {
321
- name: "djvu",
322
- accepts: byFormat("image/vnd.djvu"),
323
- specificity: 2,
324
- render: (node) => lazily(<DjvuView node={node} />),
325
- renderChunk: (chunk) => lazily(<DjvuView node={chunkNode(chunk)} />),
326
- },
327
- {
328
- name: "psd",
329
- accepts: byFormat("image/vnd.adobe.photoshop"),
330
- specificity: 2,
331
- render: (node) => lazily(<PsdView node={node} />),
332
- renderChunk: (chunk) => lazily(<PsdView node={chunkNode(chunk)} />),
333
- },
334
- {
335
- name: "tiff",
336
- accepts: byFormat("image/tiff"),
337
- specificity: 2,
338
- render: (node) => lazily(<TiffView node={node} />),
339
- renderChunk: (chunk) => lazily(<TiffView node={chunkNode(chunk)} />),
340
- },
341
- {
342
- name: "heic",
343
- accepts: byFormat("image/heic"),
344
- specificity: 2,
345
- render: (node) => lazily(<HeicView node={node} />),
346
- renderChunk: (chunk) => lazily(<HeicView node={chunkNode(chunk)} />),
347
- },
348
- ];
349
-
350
- /** The last path segment (a property key or `[index]`) of a colon-form client path. */
351
- function basename(path: string): string {
352
- const i = path.lastIndexOf(":");
353
- return i < 0 ? path : path.slice(i + 1);
354
- }
355
-
356
- /** A chapter's TOC view: its subchapters (the items of its `children` array)
357
- * surfaced directly, with its `chunks` kept off the tree. The `children` wrapper
358
- * sits one level below the chapter, so its items are loaded one level deeper —
359
- * expandability follows the wrapper's own `hasChildren`. */
360
- function chapterTocView(node: TreeNode): TocView {
361
- // Subchapters live under the `children` wrapper, one level below the chapter, so
362
- // revealing them costs a level (chapter → children → subchapters). We fetch one
363
- // more (→ each subchapter's own `children` wrapper) so a revealed subchapter's
364
- // chevron is decided from its real subchapter list, not the generic `hasChildren`
365
- // hint (which is always true for a chapter — it has `chunks`/`children` arrays).
366
- // Hence 3, so a childless chapter (only chunks) shows no chevron from the start.
367
- const wrap = node.children.find((c) => basename(c.path) === "children");
368
- if (!wrap) {
369
- // the chapter itself is not loaded yet — defer to the server's hint
370
- return { children: [], expandable: node.hasChildren, loaded: node.children.length > 0, loadDepth: 3 };
371
- }
372
- // expandable iff the `children` wrapper actually holds subchapters (chunks-only
373
- // chapters have an empty wrapper → no chevron)
374
- return {
375
- children: wrap.children,
376
- expandable: wrap.hasChildren,
377
- loaded: wrap.children.length > 0 || !wrap.hasChildren,
378
- loadDepth: 3,
379
- };
380
- }
381
-
382
- /** The renderer that claims `src`'s facets, or null when none does: of the matchers that accept
383
- * it, the most SPECIFIC (highest `specificity`) wins (TYPES.md §9). */
384
- export function rendererFor(src: FacetSource): Renderer | null {
385
- const f = facetsFrom(src);
386
- let best: Renderer | null = null;
387
- for (const r of REGISTRY) if (r.accepts(f) && (best === null || r.specificity > best.specificity)) best = r;
388
- return best;
389
- }
390
-
391
- /** The renderer for a node: its facet claim, else — for a node stored as a filesystem directory
392
- * that no format renderer claims (a dir-backed chapter stays a chapter) — the explorer, else
393
- * null → the default tabbed view. */
394
- export function getRenderer(node: NodeJson): Renderer | null {
395
- return rendererFor(node) ?? (isDirConcrete(node.concrete) ? EXPLORER : null);
396
- }
397
-
398
- /** The name (= representation key / `?format=` value) of the renderer that claims `src` — with the
399
- * same directory-concrete explorer fallback as {@link getRenderer} — or null when none claims it. */
400
- export function rendererName(src: FacetSource, concrete?: string | null): string | null {
401
- return rendererFor(src)?.name ?? (isDirConcrete(concrete) ? EXPLORER.name : null);
402
- }
403
-
404
- /** How `node` appears in the TOC: its renderer's `tocView`, or — when no renderer
405
- * claims it — its own children, lazily loaded (the passive default). */
406
- export function tocView(node: TreeNode): TocView {
407
- const r = rendererFor(node);
408
- if (r?.tocView) return r.tocView(node);
409
- const loaded = node.children.length > 0;
410
- return { children: node.children, expandable: loaded ? node.children.length > 0 : node.hasChildren, loaded };
411
- }