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.
- package/bin/yamlover.js +199 -112
- package/dist/client/assets/KaTeX_AMS-Regular-BQhdFMY1.woff2 +0 -0
- package/dist/client/assets/KaTeX_AMS-Regular-DMm9YOAa.woff +0 -0
- package/dist/client/assets/KaTeX_AMS-Regular-DRggAlZN.ttf +0 -0
- package/dist/client/assets/KaTeX_Caligraphic-Bold-ATXxdsX0.ttf +0 -0
- package/dist/client/assets/KaTeX_Caligraphic-Bold-BEiXGLvX.woff +0 -0
- package/dist/client/assets/KaTeX_Caligraphic-Bold-Dq_IR9rO.woff2 +0 -0
- package/dist/client/assets/KaTeX_Caligraphic-Regular-CTRA-rTL.woff +0 -0
- package/dist/client/assets/KaTeX_Caligraphic-Regular-Di6jR-x-.woff2 +0 -0
- package/dist/client/assets/KaTeX_Caligraphic-Regular-wX97UBjC.ttf +0 -0
- package/dist/client/assets/KaTeX_Fraktur-Bold-BdnERNNW.ttf +0 -0
- package/dist/client/assets/KaTeX_Fraktur-Bold-BsDP51OF.woff +0 -0
- package/dist/client/assets/KaTeX_Fraktur-Bold-CL6g_b3V.woff2 +0 -0
- package/dist/client/assets/KaTeX_Fraktur-Regular-CB_wures.ttf +0 -0
- package/dist/client/assets/KaTeX_Fraktur-Regular-CTYiF6lA.woff2 +0 -0
- package/dist/client/assets/KaTeX_Fraktur-Regular-Dxdc4cR9.woff +0 -0
- package/dist/client/assets/KaTeX_Main-Bold-Cx986IdX.woff2 +0 -0
- package/dist/client/assets/KaTeX_Main-Bold-Jm3AIy58.woff +0 -0
- package/dist/client/assets/KaTeX_Main-Bold-waoOVXN0.ttf +0 -0
- package/dist/client/assets/KaTeX_Main-BoldItalic-DxDJ3AOS.woff2 +0 -0
- package/dist/client/assets/KaTeX_Main-BoldItalic-DzxPMmG6.ttf +0 -0
- package/dist/client/assets/KaTeX_Main-BoldItalic-SpSLRI95.woff +0 -0
- package/dist/client/assets/KaTeX_Main-Italic-3WenGoN9.ttf +0 -0
- package/dist/client/assets/KaTeX_Main-Italic-BMLOBm91.woff +0 -0
- package/dist/client/assets/KaTeX_Main-Italic-NWA7e6Wa.woff2 +0 -0
- package/dist/client/assets/KaTeX_Main-Regular-B22Nviop.woff2 +0 -0
- package/dist/client/assets/KaTeX_Main-Regular-Dr94JaBh.woff +0 -0
- package/dist/client/assets/KaTeX_Main-Regular-ypZvNtVU.ttf +0 -0
- package/dist/client/assets/KaTeX_Math-BoldItalic-B3XSjfu4.ttf +0 -0
- package/dist/client/assets/KaTeX_Math-BoldItalic-CZnvNsCZ.woff2 +0 -0
- package/dist/client/assets/KaTeX_Math-BoldItalic-iY-2wyZ7.woff +0 -0
- package/dist/client/assets/KaTeX_Math-Italic-DA0__PXp.woff +0 -0
- package/dist/client/assets/KaTeX_Math-Italic-flOr_0UB.ttf +0 -0
- package/dist/client/assets/KaTeX_Math-Italic-t53AETM-.woff2 +0 -0
- package/dist/client/assets/KaTeX_SansSerif-Bold-CFMepnvq.ttf +0 -0
- package/dist/client/assets/KaTeX_SansSerif-Bold-D1sUS0GD.woff2 +0 -0
- package/dist/client/assets/KaTeX_SansSerif-Bold-DbIhKOiC.woff +0 -0
- package/dist/client/assets/KaTeX_SansSerif-Italic-C3H0VqGB.woff2 +0 -0
- package/dist/client/assets/KaTeX_SansSerif-Italic-DN2j7dab.woff +0 -0
- package/dist/client/assets/KaTeX_SansSerif-Italic-YYjJ1zSn.ttf +0 -0
- package/dist/client/assets/KaTeX_SansSerif-Regular-BNo7hRIc.ttf +0 -0
- package/dist/client/assets/KaTeX_SansSerif-Regular-CS6fqUqJ.woff +0 -0
- package/dist/client/assets/KaTeX_SansSerif-Regular-DDBCnlJ7.woff2 +0 -0
- package/dist/client/assets/KaTeX_Script-Regular-C5JkGWo-.ttf +0 -0
- package/dist/client/assets/KaTeX_Script-Regular-D3wIWfF6.woff2 +0 -0
- package/dist/client/assets/KaTeX_Script-Regular-D5yQViql.woff +0 -0
- package/dist/client/assets/KaTeX_Size1-Regular-C195tn64.woff +0 -0
- package/dist/client/assets/KaTeX_Size1-Regular-Dbsnue_I.ttf +0 -0
- package/dist/client/assets/KaTeX_Size1-Regular-mCD8mA8B.woff2 +0 -0
- package/dist/client/assets/KaTeX_Size2-Regular-B7gKUWhC.ttf +0 -0
- package/dist/client/assets/KaTeX_Size2-Regular-Dy4dx90m.woff2 +0 -0
- package/dist/client/assets/KaTeX_Size2-Regular-oD1tc_U0.woff +0 -0
- package/dist/client/assets/KaTeX_Size3-Regular-CTq5MqoE.woff +0 -0
- package/dist/client/assets/KaTeX_Size3-Regular-DgpXs0kz.ttf +0 -0
- package/dist/client/assets/KaTeX_Size4-Regular-BF-4gkZK.woff +0 -0
- package/dist/client/assets/KaTeX_Size4-Regular-DWFBv043.ttf +0 -0
- package/dist/client/assets/KaTeX_Size4-Regular-Dl5lxZxV.woff2 +0 -0
- package/dist/client/assets/KaTeX_Typewriter-Regular-C0xS9mPB.woff +0 -0
- package/dist/client/assets/KaTeX_Typewriter-Regular-CO6r4hn1.woff2 +0 -0
- package/dist/client/assets/KaTeX_Typewriter-Regular-D3Ib7_Hf.ttf +0 -0
- package/dist/client/assets/_commonjs-dynamic-modules-TDtrdbi3.js +1 -0
- package/dist/client/assets/decoded-CFkcuQ1f.js +1 -0
- package/dist/client/assets/djvu-B3wuFWwI.js +1 -0
- package/dist/client/assets/docx-putHYXzO.js +230 -0
- package/dist/client/assets/heic-oIxhIbIW.js +66 -0
- package/dist/client/assets/imagemap-CUKUIcGe.js +1 -0
- package/dist/client/assets/index-CQ3TGVSx.js +617 -0
- package/dist/client/assets/index-wTjOzmSY.css +1 -0
- package/dist/client/assets/map-CZS083CS.js +1 -0
- package/dist/client/assets/paged-CjxSuuik.js +1 -0
- package/dist/client/assets/panzoom-C7OuIIda.js +4 -0
- package/dist/client/assets/panzoom-CIGW-MKW.css +1 -0
- package/dist/client/assets/pdf-Dz3gUx0B.js +12 -0
- package/dist/client/assets/pdf-m5e3jsV4.css +1 -0
- package/dist/client/assets/pdf.worker.min-qwK7q_zL.mjs +28 -0
- package/dist/client/assets/psd-C8yNwHFw.js +11 -0
- package/dist/client/assets/spreadsheet-DBv7aqoN.js +38 -0
- package/dist/client/assets/tiff-73RKtIux.js +1 -0
- package/{index.html → dist/client/index.html} +2 -1
- package/package.json +18 -21
- package/src/client/App.tsx +0 -424
- package/src/client/NodeView.tsx +0 -422
- package/src/client/TaskStrip.tsx +0 -34
- package/src/client/Tree.tsx +0 -97
- package/src/client/api.ts +0 -213
- package/src/client/icons.ts +0 -91
- package/src/client/links.tsx +0 -108
- package/src/client/live.ts +0 -42
- package/src/client/main.tsx +0 -10
- package/src/client/paste-html.ts +0 -228
- package/src/client/paste-links.ts +0 -42
- package/src/client/paths.ts +0 -139
- package/src/client/render.tsx +0 -339
- package/src/client/renderers/annotate.tsx +0 -641
- package/src/client/renderers/asciidoc.tsx +0 -36
- package/src/client/renderers/chapter.tsx +0 -142
- package/src/client/renderers/csv.tsx +0 -234
- package/src/client/renderers/decoded.tsx +0 -72
- package/src/client/renderers/djvu.tsx +0 -271
- package/src/client/renderers/djvuWorker.ts +0 -99
- package/src/client/renderers/doc.tsx +0 -40
- package/src/client/renderers/docx.tsx +0 -49
- package/src/client/renderers/epub.tsx +0 -147
- package/src/client/renderers/explorer.tsx +0 -294
- package/src/client/renderers/fb2.tsx +0 -149
- package/src/client/renderers/headings.ts +0 -69
- package/src/client/renderers/heic.tsx +0 -23
- package/src/client/renderers/imagemap.tsx +0 -170
- package/src/client/renderers/kml.ts +0 -46
- package/src/client/renderers/latex.tsx +0 -37
- package/src/client/renderers/map.tsx +0 -205
- package/src/client/renderers/marklower.tsx +0 -120
- package/src/client/renderers/markup.tsx +0 -64
- package/src/client/renderers/media.tsx +0 -19
- package/src/client/renderers/paged.ts +0 -137
- package/src/client/renderers/panzoom.ts +0 -101
- package/src/client/renderers/pdf.tsx +0 -316
- package/src/client/renderers/plaintext.tsx +0 -120
- package/src/client/renderers/plantuml.tsx +0 -83
- package/src/client/renderers/psd.tsx +0 -25
- package/src/client/renderers/registry.tsx +0 -411
- package/src/client/renderers/rtf.tsx +0 -210
- package/src/client/renderers/spreadsheet.tsx +0 -105
- package/src/client/renderers/tag.tsx +0 -113
- package/src/client/renderers/text.tsx +0 -42
- package/src/client/renderers/tiff.tsx +0 -33
- package/src/client/styles.css +0 -1212
- package/src/client/vendor/README.md +0 -30
- package/src/client/vite-env.d.ts +0 -31
- package/src/server/api.ts +0 -147
- package/src/server/embed.ts +0 -187
- package/src/server/engine-api.ts +0 -1563
- package/src/server/gitignore.ts +0 -81
- package/src/server/node-kind.ts +0 -61
- package/src/server/tasks.ts +0 -83
- package/src/server/yamlover.ts +0 -1133
- /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
|
-
}
|