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
package/src/client/api.ts DELETED
@@ -1,213 +0,0 @@
1
- // Typed wrappers over the server's JSON API.
2
-
3
- export interface TreeNode {
4
- path: string;
5
- label: string;
6
- type: string;
7
- format: string | null;
8
- valueType?: string | null; // renderer dispatch facets (TYPES.md §9)
9
- hasKeyed?: boolean;
10
- hasOrdinal?: boolean;
11
- concrete: string | null; // how it is stored; `dir` → a plain-folder icon
12
- hasChildren: boolean;
13
- children: TreeNode[];
14
- }
15
-
16
- export interface NodeJson {
17
- path: string;
18
- type: string;
19
- format?: string | null; // schema `format`; with the facets it keys the renderer (TYPES.md §9)
20
- valueType?: string | null; // the scalar self-VALUE's type (null|boolean|integer|number|string|binary), or null
21
- hasKeyed?: boolean; // owns ≥1 keyed element
22
- hasOrdinal?: boolean; // owns ≥1 ordinal (keyless) element
23
- concrete: string | null;
24
- documentPath?: string; // the document (nearest yamlover entity) this node is in —
25
- // the anchor a document-relative (`/…`) link resolves against
26
- title: string | null;
27
- description: string | null;
28
- value: unknown;
29
- relations?: Record<string, unknown>; // named up-edges (+ `..`) as ref markers
30
- }
31
-
32
- async function getJson<T>(url: string): Promise<T> {
33
- const res = await fetch(url);
34
- const body = await res.json();
35
- if (!res.ok) throw new Error((body && body.error) || `HTTP ${res.status}`);
36
- return body as T;
37
- }
38
-
39
- /** Server info: the ROOT path as given on the CLI (breadcrumb head; "" if omitted). */
40
- export function fetchInfo(): Promise<{ root: string }> {
41
- return getJson<{ root: string }>("/api/info");
42
- }
43
-
44
- /** A long-running server task (indexing, hashing, …) — mirrors server/tasks.ts. Updates ride
45
- * /api/events as `{type:"task", task}` frames; this shape is also what GET /api/tasks lists. */
46
- export interface TaskInfo {
47
- id: string;
48
- label: string;
49
- state: "running" | "done" | "error";
50
- progress: { done: number; total?: number; message?: string };
51
- startedAt: number;
52
- finishedAt?: number;
53
- error?: string;
54
- }
55
-
56
- /** Server tasks in flight (or just finished) — the snapshot a freshly loaded page needs. */
57
- export function fetchTasks(): Promise<TaskInfo[]> {
58
- return getJson<TaskInfo[]>("/api/tasks");
59
- }
60
-
61
- /** The TOC subtree rooted at `path`, `depth` levels deep (server default 3). */
62
- export function fetchTree(path = ":", depth?: number): Promise<TreeNode> {
63
- const q = new URLSearchParams({ path });
64
- if (depth != null) q.set("depth", String(depth));
65
- return getJson<TreeNode>(`/api/tree?${q}`);
66
- }
67
-
68
- export function fetchNode(
69
- path: string,
70
- depth?: number,
71
- opts?: { binary?: boolean },
72
- ): Promise<NodeJson> {
73
- const q = new URLSearchParams({ path });
74
- if (depth != null) q.set("depth", String(depth));
75
- if (opts?.binary) q.set("binary", "1"); // request a binary leaf's base64 bytes
76
- return getJson<NodeJson>(`/api/json?${q}`);
77
- }
78
-
79
- /** URL of a file-backed node's raw bytes (image / pdf / html / djvu source). */
80
- export function blobUrl(path: string): string {
81
- return `/api/blob?path=${encodeURIComponent(path)}`;
82
- }
83
-
84
- /** The node's instance schema, one level deep (nested containers as link markers). */
85
- export function fetchSchema(path: string, depth?: number): Promise<unknown> {
86
- const q = new URLSearchParams({ path });
87
- if (depth != null) q.set("depth", String(depth));
88
- return getJson<unknown>(`/api/schema?${q}`);
89
- }
90
-
91
- /** A tag as the annotation API hands it around: its node path, display name (the taxonomy key),
92
- * and explicit color — null for a named tag, whose hue the client derives from the name. */
93
- export interface TagRef {
94
- path: string;
95
- name: string;
96
- color: string | null;
97
- }
98
-
99
- /** An annotation of a material — ONE TAG APPLICATION (ANNOTATIONS.md): the whole node, or a
100
- * fragment within it (then `selector` carries that fragment's region and `fragmentSlug` its
101
- * key, and `imageUrl` its crop), tagged by `tag` with optional `description` / `params`. */
102
- export interface Annotation {
103
- tag?: TagRef | null;
104
- selector?: { type?: string; exact?: string; prefix?: string; suffix?: string; [k: string]: unknown };
105
- fragmentSlug?: string; // set when the tag is on a fragment (the region) rather than the whole node
106
- imageUrl?: string; // an image-like fragment's crop (a /api/blob URL)
107
- description?: string;
108
- params?: Record<string, unknown>;
109
- created?: string;
110
- path?: string; // a transient client marker only ("(preview)"/"(pending)"); annotations have no node path
111
- }
112
-
113
- /** The annotations whose `target` is the material at `path` (the engine's reverse link). */
114
- export function fetchAnnotations(path: string): Promise<Annotation[]> {
115
- return getJson<Annotation[]>(`/api/annotations?path=${encodeURIComponent(path)}`);
116
- }
117
-
118
- /** The materials filed under the tag at `path` — `$yamloverLink` markers, annotations already
119
- * resolved to their `target` and deduped (the explorer's member list for a tag page). */
120
- export function fetchTagged(path: string): Promise<unknown[]> {
121
- return getJson<unknown[]>(`/api/tagged?path=${encodeURIComponent(path)}`);
122
- }
123
-
124
- /** Evaluate a colon-grammar QUERY (QUERY.md / engine `query` op) at `at` (default: the root `:`),
125
- * returning the matched node paths in canonical colon form. A malformed query rejects (the
126
- * server answers 400). Reused by the tag-picker typeahead and, later, by find-usages. */
127
- export function query(q: string, at = ":"): Promise<string[]> {
128
- const params = new URLSearchParams({ q, path: at });
129
- return getJson<{ results: string[] }>(`/api/query?${params}`).then((r) => r.results);
130
- }
131
-
132
- async function postJson<T>(url: string, body: unknown): Promise<T> {
133
- const res = await fetch(url, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(body) });
134
- const json = await res.json();
135
- if (!res.ok) throw new Error((json && json.error) || `HTTP ${res.status}`);
136
- return json as T;
137
- }
138
-
139
- /** Create a FRAGMENT — a marked region in the node at `target` (ANNOTATIONS.md). Returns its slug
140
- * and full node path, which is then the `target` for {@link annotate}. `imageBase64` is an
141
- * optional PNG crop for image-like selections. */
142
- export function createFragment(target: string, selector: Record<string, unknown>, imageBase64?: string): Promise<{ slug: string; fragmentPath: string }> {
143
- return postJson("/api/fragment", { target, selector, ...(imageBase64 ? { imageBase64 } : {}) });
144
- }
145
-
146
- /** Apply the tag at `tag` to the node at `target` (a whole node OR a fragment path) — appends to
147
- * the target's `yamlover-annotations`. `description`/`params` make it a parametrized annotation. */
148
- export function annotate(a: { target: string; tag: string; description?: string; params?: Record<string, unknown> }): Promise<{ ok: true }> {
149
- return postJson("/api/annotate", a);
150
- }
151
-
152
- /** Create a named tag at the project's default tags location (settings.yamlover; `/tags` by
153
- * default) — the picker's create-on-miss. Idempotent: an existing tag at that path is returned
154
- * as-is; a non-tag node already occupying the path is an error. */
155
- export function createTag(name: string): Promise<TagRef> {
156
- return fetch("/api/tag", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ name }) }).then(
157
- async (res) => {
158
- const body = await res.json();
159
- if (!res.ok) throw new Error((body && body.error) || `HTTP ${res.status}`);
160
- return body as TagRef;
161
- },
162
- );
163
- }
164
-
165
- /** The result of pasting/uploading a file or text: the new file's node path (for a text chunk,
166
- * the chapter it joined), and (for a chapter) the chapter path plus any chunk pointer appended. */
167
- export interface PasteResult {
168
- path: string; // the new file's node path (a text chunk: the chapter's own path)
169
- chapter?: string; // the chapter the chunk was appended to (chapter paste only)
170
- pointer?: string; // the `*…` chunk pointer appended (chapter FILE paste only)
171
- dir?: string; // the enclosing directory the file landed in (directory/member paste)
172
- open?: boolean; // true when the page was a MEMBER of a directory → open the new file
173
- }
174
-
175
- function postPaste(body: Record<string, unknown>): Promise<PasteResult> {
176
- return fetch("/api/paste", {
177
- method: "POST",
178
- headers: { "Content-Type": "application/json" },
179
- body: JSON.stringify(body),
180
- }).then(async (res) => {
181
- const json = await res.json();
182
- if (!res.ok) throw new Error((json && json.error) || `HTTP ${res.status}`);
183
- return json as PasteResult;
184
- });
185
- }
186
-
187
- /** Upload a pasted file onto the page at `target` (a directory or a chapter). */
188
- export function pasteFile(target: string, filename: string, contentBase64: string): Promise<PasteResult> {
189
- return postPaste({ path: target, filename, contentBase64 });
190
- }
191
-
192
- /** Paste plain TEXT onto the page at `target`: a chapter gains it as a new chunk; anywhere else
193
- * it becomes a new chapter .yamlover file in the nearest enclosing directory. */
194
- export function pasteText(target: string, text: string): Promise<PasteResult> {
195
- return postPaste({ path: target, text });
196
- }
197
-
198
- /** Paste RICH content (an HTML selection: text + images + heading-nested subchapters) onto the
199
- * page at `target`. A chapter appends the chunks and subchapters; anywhere else a new chapter
200
- * is created — directory-backed when files are present, a standalone .yamlover file otherwise.
201
- * `rich` is the RichNode tree from paste-html.ts (images already inline as base64 files). */
202
- export function pasteRich(target: string, rich: unknown): Promise<PasteResult> {
203
- return postPaste({ path: target, rich });
204
- }
205
-
206
- /** Remove the application of `tag` from the node at `target` (a whole node OR a fragment path) —
207
- * splices the matching element out of its `yamlover-annotations`. */
208
- export function deleteAnnotation(target: string, tag: string): Promise<void> {
209
- const q = new URLSearchParams({ target, tag });
210
- return fetch(`/api/annotate?${q}`, { method: "DELETE" }).then(async (res) => {
211
- if (!res.ok) throw new Error(((await res.json().catch(() => null))?.error) || `HTTP ${res.status}`);
212
- });
213
- }
@@ -1,91 +0,0 @@
1
- // Type/format icon for a TOC node — chosen by the schema `format`, falling back
2
- // to `type`. One exception to being concrete-agnostic: a node stored as an
3
- // on-disk directory gets a folder icon, since it really is a filesystem folder —
4
- // plain (`dir`, no `.yamlover/` marker) or a yamlover entity (`yamlover`).
5
-
6
- export interface Glyph {
7
- glyph: string;
8
- cls: string; // CSS class (color)
9
- title: string; // tooltip
10
- }
11
-
12
- // Type → a monochrome glyph, colored by category (matches the value highlighting).
13
- const TYPE: Record<string, { glyph: string; cls: string }> = {
14
- object: { glyph: "{}", cls: "t-struct" },
15
- array: { glyph: "[]", cls: "t-struct" },
16
- string: { glyph: "“”", cls: "t-str" },
17
- integer: { glyph: "#", cls: "t-num" },
18
- number: { glyph: "½", cls: "t-num" },
19
- boolean: { glyph: "◧", cls: "t-bool" },
20
- null: { glyph: "∅", cls: "t-null" },
21
- binary: { glyph: "0110", cls: "t-bin binsq" }, // bits in a little square
22
- };
23
-
24
- // Exact-match formats → an icon.
25
- const FORMAT: Record<string, string> = {
26
- "date-time": "🕑",
27
- date: "📅",
28
- time: "🕑",
29
- duration: "⏳",
30
- email: "✉️",
31
- "idn-email": "✉️",
32
- hostname: "🖥️",
33
- "idn-hostname": "🖥️",
34
- ipv4: "🌐",
35
- ipv6: "🌐",
36
- uri: "🔗",
37
- iri: "🔗",
38
- "uri-reference": "🔗",
39
- "iri-reference": "🔗",
40
- "uri-template": "🔗",
41
- url: "🔗",
42
- uuid: "🆔",
43
- regex: "🔣",
44
- "json-pointer": "📍",
45
- "relative-json-pointer": "📍",
46
- password: "🔑",
47
- color: "🎨",
48
- };
49
-
50
- // Media-type / binary-encoding / custom formats → an icon, chosen by prefix.
51
- function mediaIcon(format: string): string | null {
52
- if (format === "x-yamlover-chapter") return "§"; // a chapter — the section sign
53
- if (format === "x-yamlover-tag") return "🏷️";
54
- if (format.startsWith("x-yamlover-")) return "🧩"; // a custom yamlover renderer
55
- if (format === "application/pdf") return "📕";
56
- if (format === "application/x-fictionbook+xml") return "📘";
57
- if (format === "application/epub+zip") return "📗";
58
- if (format === "image/vnd.djvu") return "📓";
59
- if (format.startsWith("image/")) return "🖼️";
60
- if (format === "text/markdown") return "📝";
61
- if (format === "text/asciidoc") return "📃";
62
- if (format === "text/csv" || format === "text/tab-separated-values") return "▦"; // a table
63
- if (format === "text/x-plantuml") return "📊"; // source that compiles to a diagram
64
- if (format === "application/vnd.ms-excel") return "▦"; // legacy .xls workbook
65
- if (format === "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet") return "▦"; // .xlsx
66
- if (format === "application/rtf") return "📄";
67
- if (format === "application/msword") return "📄"; // legacy .doc
68
- if (format === "application/vnd.openxmlformats-officedocument.wordprocessingml.document") return "📄"; // .docx
69
- if (format === "application/vnd.google-earth.kml+xml" || format === "application/vnd.google-earth.kmz") return "🗺️"; // map overlay
70
- if (format.startsWith("text/")) return "📄";
71
- if (format.startsWith("audio/")) return "🔊";
72
- if (format.startsWith("video/")) return "🎬";
73
- if (/^(u?int|float)\d/.test(format)) return "💾"; // int32/le, float64, …
74
- return null;
75
- }
76
-
77
- /** The type/format icon for a node — `format` wins, then a directory concrete
78
- * (`dir`/`yamlover`) shows a folder, else `type`. */
79
- export function typeIcon(type: string, format: string | null, concrete?: string | null): Glyph {
80
- if (format) {
81
- const g = FORMAT[format] ?? mediaIcon(format);
82
- if (g) return { glyph: g, cls: "t-fmt", title: format };
83
- }
84
- // a plain directory (no `.yamlover/`) — a real OS folder
85
- if (concrete === "dir") return { glyph: "📁", cls: "t-struct", title: "folder" };
86
- // a yamlover entity stored as a directory (a folder with a `.yamlover/` marker)
87
- if (concrete === "yamlover") return { glyph: "🗂️", cls: "t-struct", title: "yamlover folder" };
88
- const t = TYPE[type];
89
- if (t) return { glyph: t.glyph, cls: t.cls, title: type };
90
- return { glyph: "•", cls: "t-bin", title: type || "unknown" };
91
- }
@@ -1,108 +0,0 @@
1
- import { ReactNode } from "react";
2
- import { segsToStr, strToSegs } from "./paths";
3
-
4
- /**
5
- * The shared **link** concept: one place that decides what a link *target* means
6
- * and how a link is made clickable. Every renderer that emits links routes through
7
- * here, so link behaviour is defined once.
8
- *
9
- * A target is addressed in the app's JSON instance space — the same space the
10
- * tree, breadcrumbs, and the URL all navigate — with two anchors, mirroring how an
11
- * `x-yamlover` `rel` pointer is written:
12
- *
13
- * - **`/some/path`** — relative to the *document* the link appears in (the
14
- * nearest yamlover entity; "document" meaning the literal file/entity, overlays
15
- * and all). Resolved against the `documentPath` the server reports for the node.
16
- * - **`//some/path`** — relative to the *project root* (the location given at
17
- * yamlover startup), i.e. the served root → browser path `/some/path`.
18
- * - **`scheme://…` / `mailto:…`** — an ordinary external link.
19
- *
20
- * `resolveLink` is deliberately the single seam for interpretation: it is where
21
- * refs and rels are expected to plug in later (gaining the full pointer grammar —
22
- * `..`, `^name`, virtual children), rather than each renderer re-deciding what a
23
- * target points at. (Refs/rels keep their own server-side interpretation for now;
24
- * this powers marklower links.)
25
- */
26
-
27
- /** A link's resolved destination. Exactly one of `path` (an in-app JSON-space path
28
- * for SPA navigation) or `href` (an external URL) is set; both null means the
29
- * target did not resolve and the link renders as plain text. */
30
- export interface ResolvedLink {
31
- path: string | null;
32
- href: string | null;
33
- }
34
-
35
- const UNRESOLVED: ResolvedLink = { path: null, href: null };
36
-
37
- /** True for an external target carrying a URI scheme (`http:`, `https:`, `mailto:`,
38
- * …). A `//`-rooted project path is *not* a scheme (no leading `scheme:`). */
39
- const hasScheme = (s: string) => /^[a-z][a-z0-9+.-]*:/i.test(s);
40
-
41
- /** Join a document base path with a document-relative path (both JSON-space),
42
- * canonicalizing the result. */
43
- function joinDoc(documentPath: string, rel: string): string {
44
- return segsToStr([...strToSegs(documentPath), ...strToSegs(rel)]);
45
- }
46
-
47
- /** Tokenize a LEGACY slash-spelled link target (`/a/b[0]`) into segments. */
48
- function slashSegs(str: string): (string | number)[] {
49
- const out: (string | number)[] = [];
50
- for (const tok of str.match(/\[\d+\]|[^/\[\]]+/g) || []) {
51
- out.push(/^\[\d+\]$/.test(tok) ? Number(tok.slice(1, -1)) : tok);
52
- }
53
- return out;
54
- }
55
-
56
- /** Interpret a link `target` against the `documentPath` it appears in (the JSON-space
57
- * path of its document; defaults to root). Colon spellings (`:a:b`, `::a:b` —
58
- * SEPARATOR.md) are canonical; legacy slash spellings (`/a/b`, `//a/b`) still parse. */
59
- export function resolveLink(target: string, documentPath = ":"): ResolvedLink {
60
- const raw = target.trim();
61
- if (!raw) return UNRESOLVED;
62
- if (raw.startsWith("::")) return { path: segsToStr(strToSegs(raw.slice(2))), href: null }; // project root
63
- if (raw.startsWith(":")) return { path: joinDoc(documentPath, raw), href: null }; // document-relative
64
- if (raw.startsWith("//")) return { path: segsToStr(slashSegs(raw)), href: null }; // legacy project root
65
- if (hasScheme(raw)) return { path: null, href: raw }; // external (http(s)/mailto/…)
66
- if (raw.startsWith("/")) return { path: segsToStr([...strToSegs(documentPath), ...slashSegs(raw)]), href: null }; // legacy doc-relative
67
- return UNRESOLVED; // anything else is not (yet) a recognized link target
68
- }
69
-
70
- /** Render a link as the right kind of anchor: an in-app `.descend` link that calls
71
- * `onNavigate` for an internal target, an ordinary external `.extlink` for a URL,
72
- * or plain children when the target doesn't resolve. The single place a link
73
- * becomes clickable — shared by every renderer that emits links. */
74
- export function NavLink({
75
- target,
76
- documentPath,
77
- onNavigate,
78
- children,
79
- }: {
80
- target: string;
81
- documentPath?: string;
82
- onNavigate: (path: string) => void;
83
- children: ReactNode;
84
- }) {
85
- const { path, href } = resolveLink(target, documentPath);
86
- if (href) {
87
- return (
88
- <a className="extlink" href={href} target="_blank" rel="noopener noreferrer">
89
- {children}
90
- </a>
91
- );
92
- }
93
- if (path) {
94
- return (
95
- <a
96
- className="descend"
97
- href={path}
98
- onClick={(e) => {
99
- e.preventDefault();
100
- onNavigate(path);
101
- }}
102
- >
103
- {children}
104
- </a>
105
- );
106
- }
107
- return <>{children}</>;
108
- }
@@ -1,42 +0,0 @@
1
- // live.ts — the client side of the UNIFIED change flow. Every change to the served tree —
2
- // mediated writes (annotate, tag, paste, mv) and external edits (the FS watcher) — reaches the
3
- // client as ONE currency: a file-level IndexDiff over /api/events, which App re-broadcasts as a
4
- // `yamlover:diff` window event. Hooks that hold server-derived state subscribe HERE instead of
5
- // inventing per-feature push paths; a new surface gets live refresh by adding one useDiffBump.
6
-
7
- import { useEffect, useState } from "react";
8
-
9
- /** A reindex/write diff as App re-broadcasts it: client JSON paths of the touched FILES
10
- * (added + changed + removed + both ends of moves), with the removals also listed alone. */
11
- export interface DiffDetail {
12
- paths: string[];
13
- removed: string[];
14
- }
15
-
16
- export const DIFF_EVENT = "yamlover:diff";
17
-
18
- /** Re-broadcast a server diff to the window (App's SSE handler is the only caller). */
19
- export function broadcastDiff(detail: DiffDetail): void {
20
- window.dispatchEvent(new CustomEvent(DIFF_EVENT, { detail }));
21
- }
22
-
23
- /** Diffs that can affect graph-derived state (annotations, tags, document bodies) — any touched
24
- * `.yamlover` source. A binary-only diff (a photo import, say) passes nothing here. */
25
- export const touchesYamlover = (d: DiffDetail): boolean => d.paths.some((p) => p.endsWith(".yamlover"));
26
-
27
- /** A counter that bumps whenever a diff matches `match` (default: every diff) — put it in a
28
- * fetch effect's dependency list and the data refetches on relevant changes. `match` must be
29
- * a stable (module-level) predicate; it is deliberately not a re-subscribe dependency. */
30
- export function useDiffBump(match: (d: DiffDetail) => boolean = () => true): number {
31
- const [bump, setBump] = useState(0);
32
- useEffect(() => {
33
- const on = (e: Event) => {
34
- const det = (e as CustomEvent).detail as DiffDetail | undefined;
35
- if (det && match(det)) setBump((b) => b + 1);
36
- };
37
- window.addEventListener(DIFF_EVENT, on);
38
- return () => window.removeEventListener(DIFF_EVENT, on);
39
- // eslint-disable-next-line react-hooks/exhaustive-deps
40
- }, []);
41
- return bump;
42
- }
@@ -1,10 +0,0 @@
1
- import React from "react";
2
- import { createRoot } from "react-dom/client";
3
- import { App } from "./App";
4
- import "./styles.css";
5
-
6
- createRoot(document.getElementById("root")!).render(
7
- <React.StrictMode>
8
- <App />
9
- </React.StrictMode>,
10
- );