@dooc-dev/dooc 0.0.0-stage → 0.0.2

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 (221) hide show
  1. package/README.md +26 -2
  2. package/dist/cli.d.ts +2 -0
  3. package/dist/cli.js +311 -0
  4. package/dist/code.global.js +285 -0
  5. package/dist/compiler/bundle-urls.d.ts +3 -0
  6. package/dist/compiler/bundle-urls.js +294 -0
  7. package/dist/compiler/bundle.d.ts +2 -0
  8. package/dist/compiler/bundle.js +100 -0
  9. package/dist/compiler/check-prose.d.ts +11 -0
  10. package/dist/compiler/check-prose.js +138 -0
  11. package/dist/compiler/check-types.d.ts +44 -0
  12. package/dist/compiler/check-types.js +1 -0
  13. package/dist/compiler/check.d.ts +5 -0
  14. package/dist/compiler/check.js +450 -0
  15. package/dist/compiler/code-blocks.d.ts +17 -0
  16. package/dist/compiler/code-blocks.js +198 -0
  17. package/dist/compiler/code-languages.d.ts +10 -0
  18. package/dist/compiler/code-languages.js +109 -0
  19. package/dist/compiler/compile.d.ts +95 -0
  20. package/dist/compiler/compile.js +410 -0
  21. package/dist/compiler/config.d.ts +7 -0
  22. package/dist/compiler/config.js +52 -0
  23. package/dist/compiler/dialect.d.ts +24 -0
  24. package/dist/compiler/dialect.js +198 -0
  25. package/dist/compiler/drawio.d.ts +26 -0
  26. package/dist/compiler/drawio.js +176 -0
  27. package/dist/compiler/figures.d.ts +14 -0
  28. package/dist/compiler/figures.js +221 -0
  29. package/dist/compiler/file-cache.d.ts +14 -0
  30. package/dist/compiler/file-cache.js +61 -0
  31. package/dist/compiler/files.d.ts +29 -0
  32. package/dist/compiler/files.js +431 -0
  33. package/dist/compiler/fonts.d.ts +10 -0
  34. package/dist/compiler/fonts.js +54 -0
  35. package/dist/compiler/format.d.ts +1 -0
  36. package/dist/compiler/format.js +42 -0
  37. package/dist/compiler/from-mdast.d.ts +16 -0
  38. package/dist/compiler/from-mdast.js +142 -0
  39. package/dist/compiler/icons.d.ts +14 -0
  40. package/dist/compiler/icons.js +85 -0
  41. package/dist/compiler/language-codes.d.ts +7 -0
  42. package/dist/compiler/language-codes.js +17 -0
  43. package/dist/compiler/locales.d.ts +7 -0
  44. package/dist/compiler/locales.js +67 -0
  45. package/dist/compiler/manifest.d.ts +6 -0
  46. package/dist/compiler/manifest.js +30 -0
  47. package/dist/compiler/ops.d.ts +44 -0
  48. package/dist/compiler/ops.js +362 -0
  49. package/dist/compiler/public-file.d.ts +2 -0
  50. package/dist/compiler/public-file.js +93 -0
  51. package/dist/compiler/serialize.d.ts +3 -0
  52. package/dist/compiler/serialize.js +16 -0
  53. package/dist/compiler/terms.d.ts +9 -0
  54. package/dist/compiler/terms.js +44 -0
  55. package/dist/compiler/textfile-remote.d.ts +12 -0
  56. package/dist/compiler/textfile-remote.js +124 -0
  57. package/dist/compiler/textfile.d.ts +9 -0
  58. package/dist/compiler/textfile.js +113 -0
  59. package/dist/compiler/theme-variants.d.ts +4 -0
  60. package/dist/compiler/theme-variants.js +22 -0
  61. package/dist/compiler/zip.d.ts +6 -0
  62. package/dist/compiler/zip.js +48 -0
  63. package/dist/core/blocks.d.ts +99 -0
  64. package/dist/core/blocks.js +29 -0
  65. package/dist/core/code-file.d.ts +12 -0
  66. package/dist/core/code-file.js +68 -0
  67. package/dist/core/content-metadata.d.ts +48 -0
  68. package/dist/core/content-metadata.js +264 -0
  69. package/dist/core/content-types.d.ts +45 -0
  70. package/dist/core/content-types.js +1 -0
  71. package/dist/core/extension-package.d.ts +34 -0
  72. package/dist/core/extension-package.js +147 -0
  73. package/dist/core/figure-frame.d.ts +12 -0
  74. package/dist/core/figure-frame.js +108 -0
  75. package/dist/core/file-embed.d.ts +13 -0
  76. package/dist/core/file-embed.js +43 -0
  77. package/dist/core/files.d.ts +15 -0
  78. package/dist/core/files.js +59 -0
  79. package/dist/core/flow-base-css.generated.d.ts +1 -0
  80. package/dist/core/flow-base-css.generated.js +4 -0
  81. package/dist/core/flow-css.d.ts +9 -0
  82. package/dist/core/flow-css.js +54 -0
  83. package/dist/core/graph-common.d.ts +32 -0
  84. package/dist/core/graph-common.js +113 -0
  85. package/dist/core/graph.d.ts +117 -0
  86. package/dist/core/graph.js +480 -0
  87. package/dist/core/html.d.ts +29 -0
  88. package/dist/core/html.js +444 -0
  89. package/dist/core/icons.d.ts +2 -0
  90. package/dist/core/icons.js +2 -0
  91. package/dist/core/inline.d.ts +27 -0
  92. package/dist/core/inline.js +42 -0
  93. package/dist/core/language.d.ts +41 -0
  94. package/dist/core/language.js +44 -0
  95. package/dist/core/languages/ja.d.ts +2 -0
  96. package/dist/core/languages/ja.js +5 -0
  97. package/dist/core/markdown.d.ts +27 -0
  98. package/dist/core/markdown.js +154 -0
  99. package/dist/core/previewers.d.ts +37 -0
  100. package/dist/core/previewers.js +103 -0
  101. package/dist/core/routing.d.ts +20 -0
  102. package/dist/core/routing.js +64 -0
  103. package/dist/core/schema-comments.d.ts +14 -0
  104. package/dist/core/schema-comments.js +52 -0
  105. package/dist/core/schema-graph-flow.d.ts +9 -0
  106. package/dist/core/schema-graph-flow.js +379 -0
  107. package/dist/core/schema-graph-mount.d.ts +9 -0
  108. package/dist/core/schema-graph-mount.js +105 -0
  109. package/dist/core/schema-graph.d.ts +254 -0
  110. package/dist/core/schema-graph.js +981 -0
  111. package/dist/core/table-ui.d.ts +2 -0
  112. package/dist/core/table-ui.js +180 -0
  113. package/dist/core/taxonomy.d.ts +12 -0
  114. package/dist/core/taxonomy.js +49 -0
  115. package/dist/core/term-reference.d.ts +5 -0
  116. package/dist/core/term-reference.js +19 -0
  117. package/dist/core/terms-ui.d.ts +6 -0
  118. package/dist/core/terms-ui.js +22 -0
  119. package/dist/core/textfile.d.ts +10 -0
  120. package/dist/core/textfile.js +39 -0
  121. package/dist/core/theme-variants.d.ts +3 -0
  122. package/dist/core/theme-variants.js +37 -0
  123. package/dist/core/toc-ui.d.ts +2 -0
  124. package/dist/core/toc-ui.js +161 -0
  125. package/dist/core/video-ui.d.ts +2 -0
  126. package/dist/core/video-ui.js +253 -0
  127. package/dist/core/workspace.d.ts +15 -0
  128. package/dist/core/workspace.js +28 -0
  129. package/dist/edit.global.js +306 -0
  130. package/dist/flow.global.js +4959 -0
  131. package/dist/index.d.ts +17 -0
  132. package/dist/index.js +31 -0
  133. package/dist/mcp.d.ts +24 -0
  134. package/dist/mcp.js +467 -0
  135. package/dist/plugins/actions.d.ts +45 -0
  136. package/dist/plugins/actions.js +125 -0
  137. package/dist/plugins/blocks.d.ts +93 -0
  138. package/dist/plugins/blocks.js +100 -0
  139. package/dist/plugins/index.d.ts +4 -0
  140. package/dist/plugins/index.js +1 -0
  141. package/dist/plugins/local-storage.d.ts +18 -0
  142. package/dist/plugins/local-storage.js +115 -0
  143. package/dist/plugins/oauth.d.ts +14 -0
  144. package/dist/plugins/oauth.js +170 -0
  145. package/dist/plugins/runtime.d.ts +26 -0
  146. package/dist/plugins/runtime.js +395 -0
  147. package/dist/plugins/types.d.ts +59 -0
  148. package/dist/plugins/types.js +1 -0
  149. package/dist/plugins/view.d.ts +7 -0
  150. package/dist/plugins/view.js +21 -0
  151. package/dist/ui.global.js +78 -0
  152. package/dist/viewer/assets.d.ts +2 -0
  153. package/dist/viewer/assets.js +3 -0
  154. package/dist/viewer/build.d.ts +12 -0
  155. package/dist/viewer/build.js +55 -0
  156. package/dist/viewer/bundle-network.d.ts +2 -0
  157. package/dist/viewer/bundle-network.js +88 -0
  158. package/dist/viewer/code-file.d.ts +20 -0
  159. package/dist/viewer/code-file.js +42 -0
  160. package/dist/viewer/editor-tools.d.ts +38 -0
  161. package/dist/viewer/editor-tools.js +211 -0
  162. package/dist/viewer/extension-pack.d.ts +2 -0
  163. package/dist/viewer/extension-pack.js +36 -0
  164. package/dist/viewer/extensions.d.ts +10 -0
  165. package/dist/viewer/extensions.js +302 -0
  166. package/dist/viewer/file-references.d.ts +14 -0
  167. package/dist/viewer/file-references.js +38 -0
  168. package/dist/viewer/font-picker.d.ts +4 -0
  169. package/dist/viewer/font-picker.js +145 -0
  170. package/dist/viewer/font-settings.d.ts +11 -0
  171. package/dist/viewer/font-settings.js +50 -0
  172. package/dist/viewer/fonts/LICENSE.txt +119 -0
  173. package/dist/viewer/fonts/udev-gothic-regular.woff2 +0 -0
  174. package/dist/viewer/fonts.d.ts +2 -0
  175. package/dist/viewer/fonts.js +13 -0
  176. package/dist/viewer/git-diff.d.ts +3 -0
  177. package/dist/viewer/git-diff.js +33 -0
  178. package/dist/viewer/git-types.d.ts +73 -0
  179. package/dist/viewer/git-types.js +1 -0
  180. package/dist/viewer/git.d.ts +5 -0
  181. package/dist/viewer/git.js +337 -0
  182. package/dist/viewer/hmr.d.ts +53 -0
  183. package/dist/viewer/hmr.js +337 -0
  184. package/dist/viewer/i18n.d.ts +6 -0
  185. package/dist/viewer/i18n.js +65 -0
  186. package/dist/viewer/ja-lint.d.ts +3 -0
  187. package/dist/viewer/ja-lint.js +44 -0
  188. package/dist/viewer/language-picker.d.ts +2 -0
  189. package/dist/viewer/language-picker.js +233 -0
  190. package/dist/viewer/languages.d.ts +11 -0
  191. package/dist/viewer/languages.js +20 -0
  192. package/dist/viewer/locales/eng.json +317 -0
  193. package/dist/viewer/locales/jpn.json +317 -0
  194. package/dist/viewer/locales/zho.json +317 -0
  195. package/dist/viewer/nav-focus.d.ts +2 -0
  196. package/dist/viewer/nav-focus.js +64 -0
  197. package/dist/viewer/plugin-blocks.d.ts +3 -0
  198. package/dist/viewer/plugin-blocks.js +115 -0
  199. package/dist/viewer/plugin-config.d.ts +35 -0
  200. package/dist/viewer/plugin-config.js +57 -0
  201. package/dist/viewer/plugin-view.d.ts +2 -0
  202. package/dist/viewer/plugin-view.js +6 -0
  203. package/dist/viewer/plugins.d.ts +12 -0
  204. package/dist/viewer/plugins.js +267 -0
  205. package/dist/viewer/resize.d.ts +4 -0
  206. package/dist/viewer/resize.js +238 -0
  207. package/dist/viewer/resource-library.d.ts +29 -0
  208. package/dist/viewer/resource-library.js +104 -0
  209. package/dist/viewer/search.d.ts +16 -0
  210. package/dist/viewer/search.js +358 -0
  211. package/dist/viewer/serve.d.ts +210 -0
  212. package/dist/viewer/serve.js +3312 -0
  213. package/dist/viewer/site-icon.d.ts +8 -0
  214. package/dist/viewer/site-icon.js +56 -0
  215. package/dist/viewer/textfile.d.ts +9 -0
  216. package/dist/viewer/textfile.js +50 -0
  217. package/dist/viewer/theme-picker.d.ts +2 -0
  218. package/dist/viewer/theme-picker.js +135 -0
  219. package/dist/viewer/workspace.d.ts +4 -0
  220. package/dist/viewer/workspace.js +47 -0
  221. package/package.json +128 -5
@@ -0,0 +1,9 @@
1
+ export { parseTextfileRange } from "../core/textfile.js";
2
+ /** Select whole lines, preserving their original separators and excluding the final separator. */
3
+ export declare const selectTextfileRange: (text: string, range?: string) => string;
4
+ /** Embedded files must stay inside the docs root, including symlink targets. */
5
+ export declare const textfilePath: (root: string, file: string) => string;
6
+ export declare const readTextfile: (root: string, file: string, refresh?: boolean, domains?: readonly string[]) => string;
7
+ export declare const readTextfileAsync: (root: string, file: string, refresh?: boolean, domains?: readonly string[]) => Promise<string>;
8
+ /** Compare before writing; null means create only, never overwrite an existing file. */
9
+ export declare const writeTextfile: (root: string, file: string, content: string, base: string | null) => boolean;
@@ -0,0 +1,113 @@
1
+ import { randomUUID } from "node:crypto";
2
+ import { linkSync, lstatSync, mkdirSync, readFileSync, realpathSync, renameSync, rmSync, statSync, writeFileSync, } from "node:fs";
3
+ import { open, stat } from "node:fs/promises";
4
+ import { dirname, isAbsolute, join, relative, resolve, sep } from "node:path";
5
+ import { isRemoteTextfile, parseTextfileRange } from "../core/textfile.js";
6
+ import { readRemoteTextfile, readRemoteTextfileAsync } from "./textfile-remote.js";
7
+ export { parseTextfileRange } from "../core/textfile.js";
8
+ /** Select whole lines, preserving their original separators and excluding the final separator. */
9
+ export const selectTextfileRange = (text, range) => {
10
+ if (range === undefined)
11
+ return text;
12
+ const [start, end] = parseTextfileRange(range);
13
+ const starts = [
14
+ 0,
15
+ ...[...text.matchAll(/\r\n|\n|\r/g)]
16
+ .map((match) => match.index + match[0].length)
17
+ .filter((offset) => offset < text.length),
18
+ ];
19
+ const from = starts[start - 1];
20
+ if (from === undefined)
21
+ throw new Error(`range line ${start} does not exist`);
22
+ if (starts[end - 1] === undefined)
23
+ throw new Error(`range line ${end} does not exist`);
24
+ return text.slice(from, starts[end] ?? text.length).replace(/(?:\r\n|\n|\r)$/, "");
25
+ };
26
+ /** Embedded files must stay inside the docs root, including symlink targets. */
27
+ export const textfilePath = (root, file) => {
28
+ if (!file.trim() ||
29
+ /^[a-z][a-z\d+.-]*:/i.test(file) ||
30
+ /[\0\r\n]/.test(file) ||
31
+ isAbsolute(file) ||
32
+ file.includes("\\") ||
33
+ file.split("/").includes(".."))
34
+ throw new Error("expected a relative file path inside the docs root");
35
+ const path = resolve(root, file);
36
+ let ancestor = path;
37
+ while (!lstatSync(ancestor, { throwIfNoEntry: false }))
38
+ ancestor = dirname(ancestor);
39
+ let resolvedAncestor;
40
+ try {
41
+ resolvedAncestor = realpathSync(ancestor);
42
+ }
43
+ catch {
44
+ throw new Error("cannot resolve the file path (check symlink targets)");
45
+ }
46
+ const resolved = resolve(resolvedAncestor, relative(ancestor, path));
47
+ const rel = relative(realpathSync(root), resolved);
48
+ if (rel === ".." || rel.startsWith(`..${sep}`) || isAbsolute(rel))
49
+ throw new Error("file is outside the docs root");
50
+ return resolved;
51
+ };
52
+ export const readTextfile = (root, file, refresh = false, domains = []) => {
53
+ let data;
54
+ if (isRemoteTextfile(file)) {
55
+ data = readRemoteTextfile(file, domains, refresh);
56
+ }
57
+ else {
58
+ const path = textfilePath(root, file);
59
+ if (!statSync(path).isFile())
60
+ throw new Error("expected a regular UTF-8 text file");
61
+ data = readFileSync(path);
62
+ }
63
+ return decodeTextfile(data);
64
+ };
65
+ const decodeTextfile = (data) => {
66
+ const text = new TextDecoder("utf-8", { fatal: true, ignoreBOM: true }).decode(data);
67
+ if (text.includes("\0"))
68
+ throw new Error("expected a UTF-8 text file, not binary data");
69
+ return text;
70
+ };
71
+ export const readTextfileAsync = async (root, file, refresh = false, domains = []) => {
72
+ if (isRemoteTextfile(file)) {
73
+ return decodeTextfile(await readRemoteTextfileAsync(file, domains, refresh));
74
+ }
75
+ const path = textfilePath(root, file);
76
+ if (!(await stat(path)).isFile())
77
+ throw new Error("expected a regular UTF-8 text file");
78
+ const handle = await open(path, "r");
79
+ try {
80
+ if (!(await handle.stat()).isFile())
81
+ throw new Error("expected a regular UTF-8 text file");
82
+ return decodeTextfile(await handle.readFile());
83
+ }
84
+ finally {
85
+ await handle.close();
86
+ }
87
+ };
88
+ /** Compare before writing; null means create only, never overwrite an existing file. */
89
+ export const writeTextfile = (root, file, content, base) => {
90
+ if (/[\0\uD800-\uDFFF]/u.test(content))
91
+ throw new Error("expected UTF-8 text");
92
+ const path = textfilePath(root, file);
93
+ const exists = lstatSync(path, { throwIfNoEntry: false });
94
+ const current = exists ? readTextfile(root, file) : null;
95
+ if (current !== base)
96
+ return false;
97
+ mkdirSync(dirname(path), { recursive: true });
98
+ const temp = join(dirname(path), `.textfile-${randomUUID()}.tmp`);
99
+ try {
100
+ writeFileSync(temp, content, {
101
+ flag: "wx",
102
+ mode: current === null ? 0o666 : statSync(path).mode,
103
+ });
104
+ if (current === null)
105
+ linkSync(temp, path);
106
+ else
107
+ renameSync(temp, path);
108
+ }
109
+ finally {
110
+ rmSync(temp, { force: true });
111
+ }
112
+ return true;
113
+ };
@@ -0,0 +1,4 @@
1
+ import type { ParsedManifest } from "../core/content-metadata.js";
2
+ export declare const themeNames: (root: string) => string[];
3
+ export declare const siteThemes: (root: string, manifest?: ParsedManifest) => string[];
4
+ export declare const themeVariants: (root: string, file: string, themes?: string[]) => Record<string, string>;
@@ -0,0 +1,22 @@
1
+ import { existsSync, readdirSync } from "node:fs";
2
+ import { localFile } from "../core/files.js";
3
+ import { themeVariantFile } from "../core/theme-variants.js";
4
+ import { readManifest } from "./manifest.js";
5
+ import { textfilePath } from "./textfile.js";
6
+ export const themeNames = (root) => {
7
+ const directory = textfilePath(root, "themes");
8
+ if (!existsSync(directory))
9
+ return [];
10
+ return readdirSync(directory, { withFileTypes: true })
11
+ .filter((entry) => entry.isDirectory() && /^[a-z][a-z0-9-]*$/.test(entry.name))
12
+ .map((entry) => entry.name)
13
+ .sort();
14
+ };
15
+ export const siteThemes = (root, manifest = readManifest(root)) => manifest.themes ?? themeNames(root);
16
+ export const themeVariants = (root, file, themes = siteThemes(root)) => {
17
+ if (!localFile(file) || file.startsWith("themes/"))
18
+ return {};
19
+ return Object.fromEntries(themes
20
+ .map((theme) => [theme, themeVariantFile(file, theme)])
21
+ .filter(([, path]) => existsSync(textfilePath(root, path))));
22
+ };
@@ -0,0 +1,6 @@
1
+ export interface ZipEntry {
2
+ /** Path inside the zip (`/`-separated). */
3
+ name: string;
4
+ data: Buffer;
5
+ }
6
+ export declare const zip: (entries: readonly ZipEntry[]) => Buffer;
@@ -0,0 +1,48 @@
1
+ // Minimal zip writer (deflate, UTF-8 file names). Hand-rolled to avoid adding an external
2
+ // dependency.
3
+ // shortcut: no ZIP64 support = can't handle more than 4GB / 65535 entries; switch to a library if
4
+ // the docs root outgrows that.
5
+ import { crc32, deflateRawSync } from "node:zlib";
6
+ // Fixed date (1980-01-01 00:00) = the same input yields the same zip (reproducible).
7
+ const DOS_DATE = (0 << 9) | (1 << 5) | 1;
8
+ export const zip = (entries) => {
9
+ const locals = [];
10
+ const centrals = [];
11
+ let offset = 0;
12
+ for (const { name, data } of entries) {
13
+ const nameBuf = Buffer.from(name, "utf8");
14
+ const body = deflateRawSync(data);
15
+ const crc = crc32(data);
16
+ // The "version … name length" part shared by the local and central headers.
17
+ const common = Buffer.alloc(26);
18
+ common.writeUInt16LE(20, 0); // version needed
19
+ common.writeUInt16LE(0x0800, 2); // flag: UTF-8 file names
20
+ common.writeUInt16LE(8, 4); // method: deflate
21
+ common.writeUInt16LE(0, 6); // time
22
+ common.writeUInt16LE(DOS_DATE, 8);
23
+ common.writeUInt32LE(crc, 10);
24
+ common.writeUInt32LE(body.length, 14);
25
+ common.writeUInt32LE(data.length, 18);
26
+ common.writeUInt16LE(nameBuf.length, 22);
27
+ common.writeUInt16LE(0, 24); // extra length
28
+ const local = Buffer.concat([Buffer.from([0x50, 0x4b, 0x03, 0x04]), common, nameBuf]);
29
+ const central = Buffer.alloc(46);
30
+ central.writeUInt32LE(0x02014b50, 0);
31
+ central.writeUInt16LE(0x031e, 4); // version made by = Unix / 3.0 (unzip keeps UTF-8 names)
32
+ common.copy(central, 6);
33
+ // 32: comment length / 34: disk / 36: internal attr = 0; 38: external attr = Unix mode 0644
34
+ central.writeUInt32LE((0o100644 << 16) >>> 0, 38);
35
+ central.writeUInt32LE(offset, 42);
36
+ locals.push(local, body);
37
+ centrals.push(central, nameBuf);
38
+ offset += local.length + body.length;
39
+ }
40
+ const dir = Buffer.concat(centrals);
41
+ const end = Buffer.alloc(22);
42
+ end.writeUInt32LE(0x06054b50, 0);
43
+ end.writeUInt16LE(entries.length, 8);
44
+ end.writeUInt16LE(entries.length, 10);
45
+ end.writeUInt32LE(dir.length, 12);
46
+ end.writeUInt32LE(offset, 16);
47
+ return Buffer.concat([...locals, dir, end]);
48
+ };
@@ -0,0 +1,99 @@
1
+ import type { TermEntry } from "./content-types.js";
2
+ import type { GraphModel } from "./graph.js";
3
+ import type { SchemaModel } from "./schema-graph.js";
4
+ import type { CodeLang } from "./taxonomy.js";
5
+ /** Nestable list item. string = leaf, object = has children. ordered = whether the children are
6
+ * numbered (irrelevant for childless leaves). */
7
+ export type ListItem = string | {
8
+ text: string;
9
+ children: ListItem[];
10
+ ordered?: boolean;
11
+ } | {
12
+ blocks: Block[];
13
+ };
14
+ /** One element of a callout body. Paragraphs / lists can be mixed in any order. */
15
+ export type CalloutItem = {
16
+ type: "p";
17
+ text: string;
18
+ } | {
19
+ type: "list";
20
+ items: ListItem[];
21
+ ordered?: boolean;
22
+ };
23
+ export type Block = {
24
+ type: "themeVariants";
25
+ default: Block;
26
+ variants: Record<string, Block>;
27
+ } | {
28
+ type: "external";
29
+ url: string;
30
+ alt?: string;
31
+ } | {
32
+ type: "p";
33
+ text: string;
34
+ } | {
35
+ type: "quote";
36
+ blocks: Block[];
37
+ } | {
38
+ type: "h";
39
+ level: 1 | 2 | 3;
40
+ text: string;
41
+ slug: string;
42
+ } | {
43
+ type: "list";
44
+ items: ListItem[];
45
+ ordered?: boolean;
46
+ } | {
47
+ type: "code";
48
+ lines: string[];
49
+ lang?: CodeLang;
50
+ filename?: string;
51
+ range?: string;
52
+ tableMode?: "header";
53
+ alt?: string;
54
+ unavailable?: "missing" | "unreachable";
55
+ } | {
56
+ type: "table";
57
+ dataSource?: string;
58
+ tableMode?: "header";
59
+ headers: string[];
60
+ rows: string[][];
61
+ alt?: string;
62
+ caption?: string;
63
+ collapsible?: boolean;
64
+ } | {
65
+ type: "callout";
66
+ title?: string;
67
+ alt?: string;
68
+ body: CalloutItem[];
69
+ } | {
70
+ type: "figureRef";
71
+ name: string;
72
+ alt?: string;
73
+ caption?: string;
74
+ } | {
75
+ type: "term";
76
+ entry: TermEntry;
77
+ } | {
78
+ type: "figures";
79
+ } | {
80
+ type: "schemaGraph";
81
+ alt?: string;
82
+ caption?: string;
83
+ graph: SchemaModel;
84
+ source?: string;
85
+ } | {
86
+ type: "graph";
87
+ alt?: string;
88
+ caption?: string;
89
+ graph: GraphModel;
90
+ source?: string;
91
+ };
92
+ export interface TocEntry {
93
+ /** heading depth (1 = `#`, 2 = `##`, 3 = `###`) */
94
+ depth: 1 | 2 | 3;
95
+ text: string;
96
+ slug: string;
97
+ }
98
+ export declare const walkBlocks: (blocks: readonly Block[]) => Block[];
99
+ export declare const slugify: (text: string) => string;
@@ -0,0 +1,29 @@
1
+ export const walkBlocks = (blocks) => {
2
+ const listBlocks = (items) => items.flatMap((item) => typeof item === "string"
3
+ ? []
4
+ : "blocks" in item
5
+ ? walkBlocks(item.blocks)
6
+ : listBlocks(item.children));
7
+ return blocks.flatMap((block) => [
8
+ block,
9
+ ...(block.type === "themeVariants"
10
+ ? walkBlocks([block.default, ...Object.values(block.variants)])
11
+ : block.type === "quote"
12
+ ? walkBlocks(block.blocks)
13
+ : block.type === "list"
14
+ ? listBlocks(block.items)
15
+ : []),
16
+ ]);
17
+ };
18
+ // Slug for heading / term anchors: always ASCII, whatever the doc language (slugs are not part
19
+ // of the language socket). Like github-slugger for ASCII text (lowercase / strip symbols /
20
+ // whitespace → hyphen); accents are folded (`café` → `cafe`) and any other non-ASCII text is
21
+ // dropped, so the result may be "" — callers supply a fallback (e.g. `section-N`). Used together
22
+ // with routing.headingAnchor, which namespaces heading anchors (`<docId>__<slug>`).
23
+ export const slugify = (text) => text
24
+ .normalize("NFKD")
25
+ .toLowerCase()
26
+ .replace(/[^a-z0-9_\- ]/g, "")
27
+ .trim()
28
+ .replace(/\s+/g, "-")
29
+ .replace(/^-+|-+$/g, "");
@@ -0,0 +1,12 @@
1
+ import { type PreviewSettings } from "./previewers.js";
2
+ export declare const codeFileName: (body: string, position: number, documentId: string) => string;
3
+ /** Preserve the body bytes and replace only a standalone, closed fence. */
4
+ export declare const codeFilePlan: (body: string, position: number, target: string, settings?: PreviewSettings) => {
5
+ file: string;
6
+ content: string;
7
+ change: {
8
+ from: number;
9
+ to: number;
10
+ insert: string;
11
+ };
12
+ };
@@ -0,0 +1,68 @@
1
+ import { codeBlockAt } from "./markdown.js";
2
+ import { resolvePreviewer } from "./previewers.js";
3
+ import { CODE_TEMPLATES } from "./taxonomy.js";
4
+ import { termFileUrl } from "./term-reference.js";
5
+ export const codeFileName = (body, position, documentId) => {
6
+ const block = codeBlockAt(body, position);
7
+ const language = block.language.toLowerCase();
8
+ const aliases = {
9
+ javascript: "js",
10
+ typescript: "ts",
11
+ python: "py",
12
+ rust: "rs",
13
+ ruby: "rb",
14
+ csharp: "cs",
15
+ bash: "sh",
16
+ powershell: "ps1",
17
+ markdown: "md",
18
+ "c++": "cpp",
19
+ };
20
+ const extension = Object.hasOwn(aliases, language)
21
+ ? aliases[language]
22
+ : /^[a-z0-9_-]+$/.test(language)
23
+ ? language
24
+ : "txt";
25
+ const directory = language === "termth"
26
+ ? "terms"
27
+ : language === "graphth" || language === "schemath"
28
+ ? "figures"
29
+ : "examples";
30
+ return `${directory}/${documentId}-${body.slice(0, block.start).split("\n").length}.${extension}`;
31
+ };
32
+ /** Preserve the body bytes and replace only a standalone, closed fence. */
33
+ export const codeFilePlan = (body, position, target, settings) => {
34
+ const block = codeBlockAt(body, position);
35
+ if (!block.topLevel)
36
+ throw new Error("Only standalone code blocks can become files");
37
+ const semantic = Object.hasOwn(CODE_TEMPLATES, block.language);
38
+ if (block.meta && (!semantic || block.language === "termth"))
39
+ throw new Error("Remove code fence metadata before creating a file");
40
+ const file = target.trim().replace(/^(\.\/)+/, "");
41
+ if (!file ||
42
+ /^[/\\]|^[a-z][a-z\d+.-]*:/i.test(file) ||
43
+ /[\\\0\r\n]/.test(file) ||
44
+ file.split("/").includes(".."))
45
+ throw new Error("Use a relative file path inside the docs root");
46
+ if (semantic && !file.endsWith(`.${block.language}`))
47
+ throw new Error(`Use the .${block.language} extension`);
48
+ const url = termFileUrl(file);
49
+ const previewer = resolvePreviewer(url, settings);
50
+ if (semantic && previewer && previewer !== "code" && previewer !== "table")
51
+ throw new Error("Register this extension with the Code previewer");
52
+ const sourceOnly = !semantic &&
53
+ ((previewer !== undefined && previewer !== "code") ||
54
+ /\.(callouth|schemath|graphth|termth)$/i.test(file));
55
+ const lastLine = block.code.split("\n").length;
56
+ const range = sourceOnly ? `#L1${lastLine > 1 ? `-L${lastLine}` : ""}` : "";
57
+ const caption = semantic ? block.meta.replace(/[\\[\]]/g, "\\$&") : "";
58
+ const from = body.lastIndexOf("\n", block.start - 1) + 1;
59
+ const before = body.slice(0, from);
60
+ const after = body.slice(block.end);
61
+ const prefix = before.trim() && !/\r?\n[ \t]*\r?\n$/.test(before) ? block.eol : "";
62
+ const suffix = after.trim() && !/^\r?\n[ \t]*\r?\n/.test(after) ? block.eol : "";
63
+ return {
64
+ file,
65
+ content: block.code.replaceAll("\n", block.eol) + (block.from === block.to ? "" : block.eol),
66
+ change: { from, to: block.end, insert: `${prefix}![${caption}](${url}${range})${suffix}` },
67
+ };
68
+ };
@@ -0,0 +1,48 @@
1
+ import { type FileLayout } from "./files.js";
2
+ export declare const MANIFEST_FILE = "dooc.yaml";
3
+ export interface DocNode {
4
+ id: string;
5
+ children: DocNode[];
6
+ }
7
+ export interface ParsedManifest {
8
+ tree: DocNode[];
9
+ title?: string;
10
+ icon?: string;
11
+ defaultLang?: string;
12
+ files?: FileLayout;
13
+ /** Content languages offered by the site, including defaultLang; normalized to ISO 639-3. */
14
+ languages?: string[];
15
+ /** Color themes offered by the site, with authored variants for embedded files. */
16
+ themes?: ("light" | "dark")[];
17
+ errors: string[];
18
+ }
19
+ export declare const DOC_ID_RE: RegExp;
20
+ export declare const isDocId: (s: string) => boolean;
21
+ /** Doc file name directly under content/ (`<id>.md`) → id. undefined if the shape differs. */
22
+ export declare const docIdOfFile: (name: string) => string | undefined;
23
+ /**
24
+ * Turn the parsed dooc.yaml (YAML → JS value) into a tree. Each element is `id` (no
25
+ * children) or `{ id: [children...] }` (a single-key map; a null value also means no children).
26
+ * Validates id shape and duplicates.
27
+ */
28
+ export declare const parseManifest: (value: unknown) => ParsedManifest;
29
+ /** Tree → dooc.yaml text (serialized in the hand-written shape, keeping git diffs clean). */
30
+ export declare const serializeManifest: (tree: readonly DocNode[], { title, icon, defaultLang, files, languages, themes, }?: Omit<ParsedManifest, "tree" | "errors">) => string;
31
+ /** Flatten the tree into display order (pre-order). parent omitted = top level. */
32
+ export declare const flattenTree: (tree: readonly DocNode[]) => {
33
+ id: string;
34
+ parent?: string;
35
+ depth: number;
36
+ }[];
37
+ /** Append a new doc to the end of parent's children. Duplicate id / missing parent → undefined. */
38
+ export declare const insertDoc: (tree: readonly DocNode[], id: string, parent?: string) => DocNode[] | undefined;
39
+ /**
40
+ * Move a doc (with its children) to position index under parent. It can't be moved under
41
+ * itself or its own descendants (that would create a cycle). An invalid move returns undefined
42
+ * (no partial update).
43
+ */
44
+ export declare const moveDoc: (tree: readonly DocNode[], id: string, parent: string | undefined, index: number) => DocNode[] | undefined;
45
+ /** Remove a doc. Docs with children are not removed (we don't decide where the children go). */
46
+ export declare const removeDoc: (tree: readonly DocNode[], id: string) => DocNode[] | undefined;
47
+ /** Change a doc's id (position and children stay as they are). */
48
+ export declare const renameDoc: (tree: readonly DocNode[], oldId: string, newId: string) => DocNode[] | undefined;