@dooc-dev/dooc 0.0.0-stage → 0.0.1
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/README.md +26 -2
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +244 -0
- package/dist/code.global.js +285 -0
- package/dist/compiler/bundle-urls.d.ts +3 -0
- package/dist/compiler/bundle-urls.js +294 -0
- package/dist/compiler/bundle.d.ts +2 -0
- package/dist/compiler/bundle.js +100 -0
- package/dist/compiler/check-prose.d.ts +11 -0
- package/dist/compiler/check-prose.js +138 -0
- package/dist/compiler/check-types.d.ts +44 -0
- package/dist/compiler/check-types.js +1 -0
- package/dist/compiler/check.d.ts +5 -0
- package/dist/compiler/check.js +450 -0
- package/dist/compiler/code-blocks.d.ts +17 -0
- package/dist/compiler/code-blocks.js +198 -0
- package/dist/compiler/code-languages.d.ts +10 -0
- package/dist/compiler/code-languages.js +109 -0
- package/dist/compiler/compile.d.ts +95 -0
- package/dist/compiler/compile.js +410 -0
- package/dist/compiler/config.d.ts +7 -0
- package/dist/compiler/config.js +52 -0
- package/dist/compiler/dialect.d.ts +24 -0
- package/dist/compiler/dialect.js +198 -0
- package/dist/compiler/drawio.d.ts +26 -0
- package/dist/compiler/drawio.js +176 -0
- package/dist/compiler/figures.d.ts +14 -0
- package/dist/compiler/figures.js +221 -0
- package/dist/compiler/file-cache.d.ts +14 -0
- package/dist/compiler/file-cache.js +61 -0
- package/dist/compiler/files.d.ts +29 -0
- package/dist/compiler/files.js +431 -0
- package/dist/compiler/fonts.d.ts +10 -0
- package/dist/compiler/fonts.js +54 -0
- package/dist/compiler/format.d.ts +1 -0
- package/dist/compiler/format.js +42 -0
- package/dist/compiler/from-mdast.d.ts +16 -0
- package/dist/compiler/from-mdast.js +142 -0
- package/dist/compiler/icons.d.ts +14 -0
- package/dist/compiler/icons.js +85 -0
- package/dist/compiler/language-codes.d.ts +7 -0
- package/dist/compiler/language-codes.js +17 -0
- package/dist/compiler/locales.d.ts +7 -0
- package/dist/compiler/locales.js +67 -0
- package/dist/compiler/manifest.d.ts +6 -0
- package/dist/compiler/manifest.js +30 -0
- package/dist/compiler/ops.d.ts +44 -0
- package/dist/compiler/ops.js +362 -0
- package/dist/compiler/public-file.d.ts +2 -0
- package/dist/compiler/public-file.js +93 -0
- package/dist/compiler/serialize.d.ts +3 -0
- package/dist/compiler/serialize.js +16 -0
- package/dist/compiler/terms.d.ts +9 -0
- package/dist/compiler/terms.js +44 -0
- package/dist/compiler/textfile-remote.d.ts +12 -0
- package/dist/compiler/textfile-remote.js +124 -0
- package/dist/compiler/textfile.d.ts +9 -0
- package/dist/compiler/textfile.js +113 -0
- package/dist/compiler/theme-variants.d.ts +4 -0
- package/dist/compiler/theme-variants.js +22 -0
- package/dist/compiler/zip.d.ts +6 -0
- package/dist/compiler/zip.js +48 -0
- package/dist/core/blocks.d.ts +99 -0
- package/dist/core/blocks.js +29 -0
- package/dist/core/code-file.d.ts +12 -0
- package/dist/core/code-file.js +68 -0
- package/dist/core/content-metadata.d.ts +48 -0
- package/dist/core/content-metadata.js +264 -0
- package/dist/core/content-types.d.ts +45 -0
- package/dist/core/content-types.js +1 -0
- package/dist/core/figure-frame.d.ts +12 -0
- package/dist/core/figure-frame.js +108 -0
- package/dist/core/file-embed.d.ts +13 -0
- package/dist/core/file-embed.js +43 -0
- package/dist/core/files.d.ts +15 -0
- package/dist/core/files.js +59 -0
- package/dist/core/flow-base-css.generated.d.ts +1 -0
- package/dist/core/flow-base-css.generated.js +4 -0
- package/dist/core/flow-css.d.ts +9 -0
- package/dist/core/flow-css.js +54 -0
- package/dist/core/graph-common.d.ts +32 -0
- package/dist/core/graph-common.js +113 -0
- package/dist/core/graph.d.ts +117 -0
- package/dist/core/graph.js +480 -0
- package/dist/core/html.d.ts +29 -0
- package/dist/core/html.js +444 -0
- package/dist/core/icons.d.ts +1 -0
- package/dist/core/icons.js +1 -0
- package/dist/core/inline.d.ts +27 -0
- package/dist/core/inline.js +42 -0
- package/dist/core/language.d.ts +41 -0
- package/dist/core/language.js +44 -0
- package/dist/core/languages/ja.d.ts +2 -0
- package/dist/core/languages/ja.js +5 -0
- package/dist/core/markdown.d.ts +27 -0
- package/dist/core/markdown.js +154 -0
- package/dist/core/previewers.d.ts +37 -0
- package/dist/core/previewers.js +103 -0
- package/dist/core/routing.d.ts +20 -0
- package/dist/core/routing.js +64 -0
- package/dist/core/schema-comments.d.ts +14 -0
- package/dist/core/schema-comments.js +52 -0
- package/dist/core/schema-graph-flow.d.ts +9 -0
- package/dist/core/schema-graph-flow.js +379 -0
- package/dist/core/schema-graph-mount.d.ts +9 -0
- package/dist/core/schema-graph-mount.js +105 -0
- package/dist/core/schema-graph.d.ts +254 -0
- package/dist/core/schema-graph.js +981 -0
- package/dist/core/table-ui.d.ts +2 -0
- package/dist/core/table-ui.js +180 -0
- package/dist/core/taxonomy.d.ts +12 -0
- package/dist/core/taxonomy.js +49 -0
- package/dist/core/term-reference.d.ts +5 -0
- package/dist/core/term-reference.js +19 -0
- package/dist/core/terms-ui.d.ts +6 -0
- package/dist/core/terms-ui.js +22 -0
- package/dist/core/textfile.d.ts +10 -0
- package/dist/core/textfile.js +39 -0
- package/dist/core/theme-variants.d.ts +3 -0
- package/dist/core/theme-variants.js +37 -0
- package/dist/core/toc-ui.d.ts +2 -0
- package/dist/core/toc-ui.js +161 -0
- package/dist/core/video-ui.d.ts +2 -0
- package/dist/core/video-ui.js +253 -0
- package/dist/core/workspace.d.ts +15 -0
- package/dist/core/workspace.js +28 -0
- package/dist/edit.global.js +305 -0
- package/dist/flow.global.js +4959 -0
- package/dist/index.d.ts +17 -0
- package/dist/index.js +31 -0
- package/dist/mcp.d.ts +24 -0
- package/dist/mcp.js +467 -0
- package/dist/plugins/actions.d.ts +45 -0
- package/dist/plugins/actions.js +125 -0
- package/dist/plugins/blocks.d.ts +92 -0
- package/dist/plugins/blocks.js +99 -0
- package/dist/plugins/index.d.ts +4 -0
- package/dist/plugins/index.js +1 -0
- package/dist/plugins/oauth.d.ts +14 -0
- package/dist/plugins/oauth.js +164 -0
- package/dist/plugins/runtime.d.ts +24 -0
- package/dist/plugins/runtime.js +339 -0
- package/dist/plugins/types.d.ts +56 -0
- package/dist/plugins/types.js +1 -0
- package/dist/plugins/view.d.ts +29 -0
- package/dist/plugins/view.js +60 -0
- package/dist/ui.global.js +77 -0
- package/dist/viewer/assets.d.ts +2 -0
- package/dist/viewer/assets.js +3 -0
- package/dist/viewer/build.d.ts +12 -0
- package/dist/viewer/build.js +55 -0
- package/dist/viewer/bundle-network.d.ts +2 -0
- package/dist/viewer/bundle-network.js +88 -0
- package/dist/viewer/code-file.d.ts +20 -0
- package/dist/viewer/code-file.js +42 -0
- package/dist/viewer/editor-tools.d.ts +38 -0
- package/dist/viewer/editor-tools.js +211 -0
- package/dist/viewer/file-references.d.ts +14 -0
- package/dist/viewer/file-references.js +38 -0
- package/dist/viewer/font-picker.d.ts +4 -0
- package/dist/viewer/font-picker.js +145 -0
- package/dist/viewer/font-settings.d.ts +11 -0
- package/dist/viewer/font-settings.js +50 -0
- package/dist/viewer/fonts/LICENSE.txt +119 -0
- package/dist/viewer/fonts/udev-gothic-regular.woff2 +0 -0
- package/dist/viewer/fonts.d.ts +2 -0
- package/dist/viewer/fonts.js +13 -0
- package/dist/viewer/git-diff.d.ts +3 -0
- package/dist/viewer/git-diff.js +33 -0
- package/dist/viewer/git-types.d.ts +73 -0
- package/dist/viewer/git-types.js +1 -0
- package/dist/viewer/git.d.ts +5 -0
- package/dist/viewer/git.js +337 -0
- package/dist/viewer/hmr.d.ts +53 -0
- package/dist/viewer/hmr.js +335 -0
- package/dist/viewer/i18n.d.ts +6 -0
- package/dist/viewer/i18n.js +65 -0
- package/dist/viewer/ja-lint.d.ts +3 -0
- package/dist/viewer/ja-lint.js +44 -0
- package/dist/viewer/language-picker.d.ts +2 -0
- package/dist/viewer/language-picker.js +233 -0
- package/dist/viewer/languages.d.ts +11 -0
- package/dist/viewer/languages.js +20 -0
- package/dist/viewer/locales/eng.json +294 -0
- package/dist/viewer/locales/jpn.json +294 -0
- package/dist/viewer/locales/zho.json +294 -0
- package/dist/viewer/nav-focus.d.ts +2 -0
- package/dist/viewer/nav-focus.js +64 -0
- package/dist/viewer/plugin-blocks.d.ts +3 -0
- package/dist/viewer/plugin-blocks.js +115 -0
- package/dist/viewer/plugin-config.d.ts +35 -0
- package/dist/viewer/plugin-config.js +57 -0
- package/dist/viewer/plugin-view.d.ts +3 -0
- package/dist/viewer/plugin-view.js +28 -0
- package/dist/viewer/plugins.d.ts +8 -0
- package/dist/viewer/plugins.js +214 -0
- package/dist/viewer/resize.d.ts +4 -0
- package/dist/viewer/resize.js +238 -0
- package/dist/viewer/resource-library.d.ts +29 -0
- package/dist/viewer/resource-library.js +104 -0
- package/dist/viewer/search.d.ts +16 -0
- package/dist/viewer/search.js +358 -0
- package/dist/viewer/serve.d.ts +206 -0
- package/dist/viewer/serve.js +3233 -0
- package/dist/viewer/site-icon.d.ts +8 -0
- package/dist/viewer/site-icon.js +56 -0
- package/dist/viewer/textfile.d.ts +9 -0
- package/dist/viewer/textfile.js +50 -0
- package/dist/viewer/theme-picker.d.ts +2 -0
- package/dist/viewer/theme-picker.js +135 -0
- package/dist/viewer/workspace.d.ts +4 -0
- package/dist/viewer/workspace.js +47 -0
- package/package.json +127 -5
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
/** Self-contained so the same controls work in the viewer and exported HTML. */
|
|
2
|
+
export function mountTables(root) {
|
|
3
|
+
const controller = new AbortController();
|
|
4
|
+
const mounted = new WeakMap();
|
|
5
|
+
const collator = new Intl.Collator(undefined, { numeric: true, sensitivity: "base" });
|
|
6
|
+
const initialize = () => {
|
|
7
|
+
for (const box of root.querySelectorAll(".data-table")) {
|
|
8
|
+
if (mounted.has(box))
|
|
9
|
+
continue;
|
|
10
|
+
const template = box.querySelector("[data-table-rows]");
|
|
11
|
+
const table = box.querySelector("table");
|
|
12
|
+
const header = box.dataset.tableMode === "header";
|
|
13
|
+
if (!template || !table)
|
|
14
|
+
continue;
|
|
15
|
+
const viewport = box.querySelector(".data-table-scroll");
|
|
16
|
+
const handle = box.querySelector(".data-table-resize");
|
|
17
|
+
if (viewport && handle) {
|
|
18
|
+
let drag;
|
|
19
|
+
const resize = (height) => {
|
|
20
|
+
const max = Math.max(64, table.getBoundingClientRect().height + 2);
|
|
21
|
+
const value = Math.round(Math.max(64, Math.min(height, max)));
|
|
22
|
+
viewport.style.maxHeight = "none";
|
|
23
|
+
viewport.style.height = `${value}px`;
|
|
24
|
+
handle.setAttribute("aria-valuenow", String(value));
|
|
25
|
+
handle.setAttribute("aria-valuemax", String(Math.round(max)));
|
|
26
|
+
};
|
|
27
|
+
handle.setAttribute("aria-valuemin", "64");
|
|
28
|
+
handle.addEventListener("focus", () => {
|
|
29
|
+
handle.setAttribute("aria-valuenow", String(Math.round(viewport.getBoundingClientRect().height)));
|
|
30
|
+
handle.setAttribute("aria-valuemax", String(Math.max(64, Math.round(table.getBoundingClientRect().height + 2))));
|
|
31
|
+
}, { signal: controller.signal });
|
|
32
|
+
handle.addEventListener("pointerdown", (event) => {
|
|
33
|
+
if (event.button !== 0)
|
|
34
|
+
return;
|
|
35
|
+
drag = {
|
|
36
|
+
y: event.clientY,
|
|
37
|
+
height: viewport.getBoundingClientRect().height,
|
|
38
|
+
};
|
|
39
|
+
handle.setPointerCapture(event.pointerId);
|
|
40
|
+
event.preventDefault();
|
|
41
|
+
}, { signal: controller.signal });
|
|
42
|
+
handle.addEventListener("pointermove", (event) => {
|
|
43
|
+
if (drag)
|
|
44
|
+
resize(drag.height + event.clientY - drag.y);
|
|
45
|
+
}, { signal: controller.signal });
|
|
46
|
+
handle.addEventListener("lostpointercapture", () => {
|
|
47
|
+
drag = undefined;
|
|
48
|
+
}, { signal: controller.signal });
|
|
49
|
+
handle.addEventListener("keydown", (event) => {
|
|
50
|
+
const height = viewport.getBoundingClientRect().height;
|
|
51
|
+
if (event.key === "ArrowDown")
|
|
52
|
+
resize(height + 32);
|
|
53
|
+
else if (event.key === "ArrowUp")
|
|
54
|
+
resize(height - 32);
|
|
55
|
+
else if (event.key === "Home")
|
|
56
|
+
resize(64);
|
|
57
|
+
else if (event.key === "End")
|
|
58
|
+
resize(table.getBoundingClientRect().height + 2);
|
|
59
|
+
else
|
|
60
|
+
return;
|
|
61
|
+
event.preventDefault();
|
|
62
|
+
event.stopPropagation();
|
|
63
|
+
}, { signal: controller.signal });
|
|
64
|
+
}
|
|
65
|
+
const rows = JSON.parse(template.content.textContent ?? "[]");
|
|
66
|
+
const key = `docs-table:${box.dataset.tableSource}:${box.dataset.tableMode}`;
|
|
67
|
+
let column = -1;
|
|
68
|
+
let descending = false;
|
|
69
|
+
try {
|
|
70
|
+
const saved = JSON.parse(localStorage.getItem(key) ?? "null");
|
|
71
|
+
if (Number.isInteger(saved?.column) && saved.column < (rows[0]?.length ?? 0))
|
|
72
|
+
column = Math.max(-1, saved.column);
|
|
73
|
+
descending = saved?.descending === true;
|
|
74
|
+
}
|
|
75
|
+
catch {
|
|
76
|
+
// Storage is optional; source order and sorting remain usable.
|
|
77
|
+
}
|
|
78
|
+
const render = () => {
|
|
79
|
+
const label = box.querySelector("[data-table-column-label]")?.textContent ?? "Column";
|
|
80
|
+
const head = document.createElement("tr");
|
|
81
|
+
const number = document.createElement("th");
|
|
82
|
+
number.scope = "col";
|
|
83
|
+
number.textContent = "#";
|
|
84
|
+
head.append(number);
|
|
85
|
+
for (let index = 0; index < (rows[0]?.length ?? 0); index++) {
|
|
86
|
+
const cell = document.createElement("th");
|
|
87
|
+
cell.scope = "col";
|
|
88
|
+
if (index === column)
|
|
89
|
+
cell.setAttribute("aria-sort", descending ? "descending" : "ascending");
|
|
90
|
+
const button = document.createElement("button");
|
|
91
|
+
button.type = "button";
|
|
92
|
+
button.dataset.tableSort = String(index);
|
|
93
|
+
button.textContent = header
|
|
94
|
+
? rows[0]?.[index] || `${label} ${index + 1}`
|
|
95
|
+
: `${label} ${index + 1}`;
|
|
96
|
+
const mark = document.createElement("span");
|
|
97
|
+
mark.className = "data-table-sort-mark";
|
|
98
|
+
mark.setAttribute("aria-hidden", "true");
|
|
99
|
+
mark.textContent = index === column && descending ? "↓" : "↑";
|
|
100
|
+
button.append(mark);
|
|
101
|
+
cell.append(button);
|
|
102
|
+
head.append(cell);
|
|
103
|
+
}
|
|
104
|
+
table.tHead?.replaceChildren(head);
|
|
105
|
+
const data = rows.map((cells, index) => ({ cells, index })).slice(header ? 1 : 0);
|
|
106
|
+
if (column >= 0) {
|
|
107
|
+
const numeric = data.every(({ cells }) => !cells[column]?.trim() ||
|
|
108
|
+
/^[+-]?(?:\d+\.?\d*|\.\d+)(?:e[+-]?\d+)?$/i.test(cells[column]?.trim() ?? ""));
|
|
109
|
+
data.sort((a, b) => {
|
|
110
|
+
const left = a.cells[column]?.trim() ?? "";
|
|
111
|
+
const right = b.cells[column]?.trim() ?? "";
|
|
112
|
+
if (!left || !right)
|
|
113
|
+
return Number(!left) - Number(!right) || a.index - b.index;
|
|
114
|
+
const order = numeric
|
|
115
|
+
? Number(left) - Number(right)
|
|
116
|
+
: collator.compare(left, right);
|
|
117
|
+
return (descending ? -order : order) || a.index - b.index;
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
const body = document.createDocumentFragment();
|
|
121
|
+
for (const { cells, index } of data) {
|
|
122
|
+
const row = document.createElement("tr");
|
|
123
|
+
const number = document.createElement("th");
|
|
124
|
+
number.scope = "row";
|
|
125
|
+
number.textContent = String(index + 1);
|
|
126
|
+
row.append(number);
|
|
127
|
+
for (const text of cells) {
|
|
128
|
+
const cell = document.createElement("td");
|
|
129
|
+
cell.textContent = text;
|
|
130
|
+
row.append(cell);
|
|
131
|
+
}
|
|
132
|
+
body.append(row);
|
|
133
|
+
}
|
|
134
|
+
table.tBodies[0]?.replaceChildren(body);
|
|
135
|
+
};
|
|
136
|
+
const persist = () => {
|
|
137
|
+
try {
|
|
138
|
+
localStorage.setItem(key, JSON.stringify({ column, descending }));
|
|
139
|
+
}
|
|
140
|
+
catch {
|
|
141
|
+
// Sorting also works without storage.
|
|
142
|
+
}
|
|
143
|
+
render();
|
|
144
|
+
};
|
|
145
|
+
box.addEventListener("click", (event) => {
|
|
146
|
+
const button = event.target.closest("button");
|
|
147
|
+
if (!button)
|
|
148
|
+
return;
|
|
149
|
+
if (button.hasAttribute("data-table-sort")) {
|
|
150
|
+
const next = Number(button.dataset.tableSort);
|
|
151
|
+
if (column !== next) {
|
|
152
|
+
column = next;
|
|
153
|
+
descending = false;
|
|
154
|
+
}
|
|
155
|
+
else if (!descending)
|
|
156
|
+
descending = true;
|
|
157
|
+
else
|
|
158
|
+
column = -1;
|
|
159
|
+
persist();
|
|
160
|
+
box.querySelector(`[data-table-sort="${next}"]`)?.focus({
|
|
161
|
+
preventScroll: true,
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
}, { signal: controller.signal });
|
|
165
|
+
mounted.set(box, render);
|
|
166
|
+
render();
|
|
167
|
+
}
|
|
168
|
+
};
|
|
169
|
+
initialize();
|
|
170
|
+
const observer = new MutationObserver(initialize);
|
|
171
|
+
observer.observe(root, { childList: true, subtree: true });
|
|
172
|
+
root.addEventListener("docs:ui-language", () => {
|
|
173
|
+
for (const box of root.querySelectorAll(".data-table"))
|
|
174
|
+
mounted.get(box)?.();
|
|
175
|
+
}, { signal: controller.signal });
|
|
176
|
+
return () => {
|
|
177
|
+
observer.disconnect();
|
|
178
|
+
controller.abort();
|
|
179
|
+
};
|
|
180
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export declare const CODE_TEMPLATES: Readonly<Record<string, string>>;
|
|
2
|
+
export declare const CODE_LANGS: readonly ["javascript", "typescript", "jsx", "tsx", "python", "java", "c", "cpp", "csharp", "go", "rust", "ruby", "php", "swift", "kotlin", "bash", "powershell", "html", "css", "json", "yaml", "toml", "sql", "markdown", "dockerfile", "diff", "graphql", "terraform", ...string[]];
|
|
3
|
+
export type CodeLang = string;
|
|
4
|
+
export declare const FM_REQUIRED: readonly ["title"];
|
|
5
|
+
export declare const FM_OPTIONAL: readonly ["lang"];
|
|
6
|
+
export interface Frontmatter {
|
|
7
|
+
title: string;
|
|
8
|
+
/** BCP 47 tag of the doc's language (language.ts). Omitted = the site default. */
|
|
9
|
+
lang?: string;
|
|
10
|
+
}
|
|
11
|
+
/** Display name of a doc (nav / headings / listings). Falls back if front-matter is broken. */
|
|
12
|
+
export declare const docDisplayName: (fm: Frontmatter | undefined, fallback: string) => string;
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
// Vocabulary for semantic code fences, front-matter fields, and code languages.
|
|
2
|
+
// Both compiler validation and viewer insert-completion read this.
|
|
3
|
+
export const CODE_TEMPLATES = {
|
|
4
|
+
csv: "```csv\n<value>,<value>\n```",
|
|
5
|
+
tsv: "```tsv\n<value>\t<value>\n```",
|
|
6
|
+
mermaid: "```mermaid\nflowchart LR\n A[<start>] --> B[<end>]\n```",
|
|
7
|
+
callouth: "```callouth <caption>\n- <item>\n```",
|
|
8
|
+
schemath: "```schemath <caption>\n### <group>\n<table>:\n id: pk\n <field>: fk(<ref_table>)\n\n<ref_table>:\n id: pk\n```",
|
|
9
|
+
graphth: "```graphth <caption>\n[<id>]: <label>\n[<ref_id>]: <ref_label>\n\n[<id>] -> [<ref_id>]\n```",
|
|
10
|
+
termth: '```termth\nname: "<name>"\ndefinition: "<definition>"\n```',
|
|
11
|
+
};
|
|
12
|
+
// Default syntax-highlighting languages; additional grammars live in .dooc/code-languages/.
|
|
13
|
+
export const CODE_LANGS = [
|
|
14
|
+
"javascript",
|
|
15
|
+
"typescript",
|
|
16
|
+
"jsx",
|
|
17
|
+
"tsx",
|
|
18
|
+
"python",
|
|
19
|
+
"java",
|
|
20
|
+
"c",
|
|
21
|
+
"cpp",
|
|
22
|
+
"csharp",
|
|
23
|
+
"go",
|
|
24
|
+
"rust",
|
|
25
|
+
"ruby",
|
|
26
|
+
"php",
|
|
27
|
+
"swift",
|
|
28
|
+
"kotlin",
|
|
29
|
+
"bash",
|
|
30
|
+
"powershell",
|
|
31
|
+
"html",
|
|
32
|
+
"css",
|
|
33
|
+
"json",
|
|
34
|
+
"yaml",
|
|
35
|
+
"toml",
|
|
36
|
+
"sql",
|
|
37
|
+
"markdown",
|
|
38
|
+
"dockerfile",
|
|
39
|
+
"diff",
|
|
40
|
+
"graphql",
|
|
41
|
+
"terraform",
|
|
42
|
+
...Object.keys(CODE_TEMPLATES),
|
|
43
|
+
];
|
|
44
|
+
// Front-matter schema (the hierarchy's SoT is dooc.yaml, so it is not written in
|
|
45
|
+
// front-matter; writing it there is an unknown-key error).
|
|
46
|
+
export const FM_REQUIRED = ["title"];
|
|
47
|
+
export const FM_OPTIONAL = ["lang"];
|
|
48
|
+
/** Display name of a doc (nav / headings / listings). Falls back if front-matter is broken. */
|
|
49
|
+
export const docDisplayName = (fm, fallback) => fm?.title ?? fallback;
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export declare const isTermFile: (file: string) => boolean;
|
|
2
|
+
/** Term links are local files relative to the docs root, just like file embeds. */
|
|
3
|
+
export declare const parseTermReference: (url: string) => string | undefined;
|
|
4
|
+
export declare const termFileUrl: (file: string) => string;
|
|
5
|
+
export declare const termAnchor: (id: string) => string;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { parseFileReference } from "./textfile.js";
|
|
2
|
+
export const isTermFile = (file) => /\.termth$/i.test(file);
|
|
3
|
+
/** Term links are local files relative to the docs root, just like file embeds. */
|
|
4
|
+
export const parseTermReference = (url) => {
|
|
5
|
+
if (!/\.termth(?:[?#]|$)/i.test(url))
|
|
6
|
+
return undefined;
|
|
7
|
+
const { file, range } = parseFileReference(url);
|
|
8
|
+
if (range !== undefined ||
|
|
9
|
+
/^(?:\/|[a-z][a-z\d+.-]*:)/i.test(file) ||
|
|
10
|
+
/[\\\0\r\n]/.test(file) ||
|
|
11
|
+
file.split("/").includes(".."))
|
|
12
|
+
throw new Error("Term links require a relative .termth file inside the docs root, without a query or fragment");
|
|
13
|
+
return file
|
|
14
|
+
.split("/")
|
|
15
|
+
.filter((part) => part && part !== ".")
|
|
16
|
+
.join("/");
|
|
17
|
+
};
|
|
18
|
+
export const termFileUrl = (file) => file.split("/").map(encodeURIComponent).join("/").replaceAll("(", "%28").replaceAll(")", "%29");
|
|
19
|
+
export const termAnchor = (id) => `term-${encodeURIComponent(id)}`;
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sorts terms listings in place on `[data-dict-sort]` change. The A → Z rank is computed at
|
|
3
|
+
* render time by the doc language's rule (html.ts → data-sort-order), so no language logic runs
|
|
4
|
+
* here. Delegation keeps sorting working after preview/editor content replacement.
|
|
5
|
+
*/
|
|
6
|
+
export declare function mountTerms(root: ParentNode): () => void;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sorts terms listings in place on `[data-dict-sort]` change. The A → Z rank is computed at
|
|
3
|
+
* render time by the doc language's rule (html.ts → data-sort-order), so no language logic runs
|
|
4
|
+
* here. Delegation keeps sorting working after preview/editor content replacement.
|
|
5
|
+
*/
|
|
6
|
+
export function mountTerms(root) {
|
|
7
|
+
const controller = new AbortController();
|
|
8
|
+
root.addEventListener("change", (event) => {
|
|
9
|
+
const select = event.target;
|
|
10
|
+
if (!(select instanceof HTMLSelectElement) || !select.matches("[data-dict-sort]"))
|
|
11
|
+
return;
|
|
12
|
+
const list = select.closest(".terms")?.querySelector(".dict");
|
|
13
|
+
if (!list)
|
|
14
|
+
return;
|
|
15
|
+
const key = select.value === "source" ? "sourceOrder" : "sortOrder";
|
|
16
|
+
const sign = select.value === "desc" ? -1 : 1;
|
|
17
|
+
const cards = Array.from(list.querySelectorAll(".dict-card"));
|
|
18
|
+
cards.sort((a, b) => sign * (Number(a.dataset[key]) - Number(b.dataset[key])));
|
|
19
|
+
list.append(...cards);
|
|
20
|
+
}, { signal: controller.signal });
|
|
21
|
+
return () => controller.abort();
|
|
22
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export declare const isRemoteTextfile: (file: string) => boolean;
|
|
2
|
+
/** GitHub-style, one-based inclusive line numbers. */
|
|
3
|
+
export declare const parseTextfileRange: (range: string) => [number, number];
|
|
4
|
+
/** Split before decoding so an encoded # or ? remains part of a local filename. */
|
|
5
|
+
export declare const parseFileReference: (source: string) => {
|
|
6
|
+
file: string;
|
|
7
|
+
range?: string;
|
|
8
|
+
};
|
|
9
|
+
/** The source stays intact for tooltips; only the visible label and language use this name. */
|
|
10
|
+
export declare const textfileName: (file: string) => string;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
export const isRemoteTextfile = (file) => /^https?:\/\//i.test(file);
|
|
2
|
+
/** GitHub-style, one-based inclusive line numbers. */
|
|
3
|
+
export const parseTextfileRange = (range) => {
|
|
4
|
+
const match = /^#L([1-9]\d*)(?:-L([1-9]\d*))?$/.exec(range);
|
|
5
|
+
const start = Number(match?.[1]);
|
|
6
|
+
const end = Number(match?.[2] ?? match?.[1]);
|
|
7
|
+
if (!Number.isSafeInteger(start) || !Number.isSafeInteger(end) || end < start)
|
|
8
|
+
throw new Error("Use #L10 or #L10-L20 with positive, ascending line numbers");
|
|
9
|
+
return [start, end];
|
|
10
|
+
};
|
|
11
|
+
/** Split before decoding so an encoded # or ? remains part of a local filename. */
|
|
12
|
+
export const parseFileReference = (source) => {
|
|
13
|
+
const hash = source.indexOf("#");
|
|
14
|
+
const path = hash < 0 ? source : source.slice(0, hash);
|
|
15
|
+
if (!path.trim())
|
|
16
|
+
throw new Error("File path is required");
|
|
17
|
+
const remote = isRemoteTextfile(path);
|
|
18
|
+
if (!remote && path.includes("?"))
|
|
19
|
+
throw new Error("Local file references do not support query parameters");
|
|
20
|
+
return {
|
|
21
|
+
file: remote ? path : decodeURIComponent(path),
|
|
22
|
+
...(hash < 0 ? {} : { range: decodeURIComponent(source.slice(hash)) }),
|
|
23
|
+
};
|
|
24
|
+
};
|
|
25
|
+
/** The source stays intact for tooltips; only the visible label and language use this name. */
|
|
26
|
+
export const textfileName = (file) => {
|
|
27
|
+
if (!isRemoteTextfile(file))
|
|
28
|
+
return file.split("/").at(-1) ?? file;
|
|
29
|
+
try {
|
|
30
|
+
const url = new URL(file);
|
|
31
|
+
const path = url.pathname.endsWith(".git") && url.hash.includes(":")
|
|
32
|
+
? url.hash.slice(url.hash.indexOf(":") + 1)
|
|
33
|
+
: url.pathname;
|
|
34
|
+
return decodeURIComponent(path.split("/").at(-1) || url.hostname);
|
|
35
|
+
}
|
|
36
|
+
catch {
|
|
37
|
+
return file;
|
|
38
|
+
}
|
|
39
|
+
};
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { localFile } from "./files.js";
|
|
2
|
+
export const themeVariantFile = (file, theme) => {
|
|
3
|
+
if (!localFile(file) || !/^[a-z][a-z0-9-]*$/.test(theme))
|
|
4
|
+
throw new Error("Expected a local file and a theme name");
|
|
5
|
+
return `themes/${theme}/${file}`;
|
|
6
|
+
};
|
|
7
|
+
/** A pane selects its own variant; an absent variant falls back to the original content. */
|
|
8
|
+
export const mountThemeVariants = (doc) => {
|
|
9
|
+
const media = matchMedia("(prefers-color-scheme: dark)");
|
|
10
|
+
const apply = () => {
|
|
11
|
+
for (const group of doc.querySelectorAll("[data-theme-variants]")) {
|
|
12
|
+
const choice = group.closest("[data-theme]")?.dataset.theme;
|
|
13
|
+
const theme = !choice || choice === "auto" ? (media.matches ? "dark" : "light") : choice;
|
|
14
|
+
const children = [...group.children];
|
|
15
|
+
const selected = children.find((child) => child.dataset.themeVariant === theme) ??
|
|
16
|
+
children.find((child) => child.dataset.themeVariant === "");
|
|
17
|
+
for (const child of children) {
|
|
18
|
+
const hidden = child !== selected;
|
|
19
|
+
if (child.hidden === hidden)
|
|
20
|
+
continue;
|
|
21
|
+
child.hidden = hidden;
|
|
22
|
+
// Recreate hidden frame contexts so revealed SVGs receive a live viewport.
|
|
23
|
+
if (!hidden)
|
|
24
|
+
for (const frame of child.querySelectorAll("iframe"))
|
|
25
|
+
frame.replaceWith(frame.cloneNode(true));
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
};
|
|
29
|
+
new MutationObserver(apply).observe(doc.documentElement, {
|
|
30
|
+
childList: true,
|
|
31
|
+
subtree: true,
|
|
32
|
+
attributes: true,
|
|
33
|
+
attributeFilter: ["data-theme"],
|
|
34
|
+
});
|
|
35
|
+
media.addEventListener("change", apply);
|
|
36
|
+
apply();
|
|
37
|
+
};
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
/** Self-contained so live previews and standalone HTML share the same navigation. */
|
|
2
|
+
export function mountToc(doc) {
|
|
3
|
+
const controller = new AbortController();
|
|
4
|
+
const mounted = new Map();
|
|
5
|
+
let sequence = 0;
|
|
6
|
+
let frame = 0;
|
|
7
|
+
const schedule = () => {
|
|
8
|
+
if (frame)
|
|
9
|
+
return;
|
|
10
|
+
frame = requestAnimationFrame(() => {
|
|
11
|
+
frame = 0;
|
|
12
|
+
for (const { update } of mounted.values())
|
|
13
|
+
update();
|
|
14
|
+
});
|
|
15
|
+
};
|
|
16
|
+
const resize = new ResizeObserver(schedule);
|
|
17
|
+
const initialize = () => {
|
|
18
|
+
for (const [nav, { article, dispose }] of mounted)
|
|
19
|
+
if (!nav.isConnected || !article.isConnected) {
|
|
20
|
+
dispose();
|
|
21
|
+
resize.unobserve(article);
|
|
22
|
+
mounted.delete(nav);
|
|
23
|
+
nav.remove();
|
|
24
|
+
}
|
|
25
|
+
for (const nav of doc.querySelectorAll(".in-toc")) {
|
|
26
|
+
if (mounted.has(nav))
|
|
27
|
+
continue;
|
|
28
|
+
const article = nav.closest("article");
|
|
29
|
+
const seek = nav.querySelector(".toc-seek");
|
|
30
|
+
const button = nav.querySelector(".toc-current");
|
|
31
|
+
const list = nav.querySelector("ul");
|
|
32
|
+
if (!article || !seek || !button || !list)
|
|
33
|
+
continue;
|
|
34
|
+
const pane = nav.closest(".preview-pane");
|
|
35
|
+
const scroller = pane ?? doc.scrollingElement;
|
|
36
|
+
if (!scroller)
|
|
37
|
+
continue;
|
|
38
|
+
const header = (pane ?? article.parentElement)?.querySelector(":scope > .pane-actions, :scope > .page-actions");
|
|
39
|
+
header?.prepend(nav);
|
|
40
|
+
const listeners = new AbortController();
|
|
41
|
+
const options = { signal: listeners.signal };
|
|
42
|
+
list.id = `toc-options-${++sequence}`;
|
|
43
|
+
button.setAttribute("aria-controls", list.id);
|
|
44
|
+
const sections = [...nav.querySelectorAll("[data-toc-target]")]
|
|
45
|
+
.map((link) => ({
|
|
46
|
+
link,
|
|
47
|
+
heading: article.querySelector(`#${CSS.escape(link.dataset.tocTarget ?? "")}`),
|
|
48
|
+
}))
|
|
49
|
+
.filter((section) => section.heading !== null);
|
|
50
|
+
let active;
|
|
51
|
+
const update = () => {
|
|
52
|
+
const maximum = Math.max(0, scroller.scrollHeight - scroller.clientHeight);
|
|
53
|
+
const progress = maximum
|
|
54
|
+
? Math.max(0, Math.min(1, scroller.scrollTop / maximum))
|
|
55
|
+
: 0;
|
|
56
|
+
seek.disabled = maximum === 0;
|
|
57
|
+
seek.value = String(Math.round(progress * 1000));
|
|
58
|
+
seek.style.setProperty("--toc-progress", `${progress * 100}%`);
|
|
59
|
+
const top = (header ?? nav).getBoundingClientRect().bottom + 20;
|
|
60
|
+
let current = sections[0];
|
|
61
|
+
for (const section of sections)
|
|
62
|
+
if ((section.heading?.getBoundingClientRect().top ?? Infinity) <= top)
|
|
63
|
+
current = section;
|
|
64
|
+
if (progress === 1)
|
|
65
|
+
current = sections.at(-1);
|
|
66
|
+
seek.setAttribute("aria-valuetext", `${current?.link.textContent ?? ""} · ${Math.round(progress * 100)}%`);
|
|
67
|
+
if (current?.link === active)
|
|
68
|
+
return;
|
|
69
|
+
active?.removeAttribute("aria-current");
|
|
70
|
+
active = current?.link;
|
|
71
|
+
if (active) {
|
|
72
|
+
active.setAttribute("aria-current", "location");
|
|
73
|
+
button.textContent = button.title = active.textContent ?? "";
|
|
74
|
+
}
|
|
75
|
+
};
|
|
76
|
+
button.addEventListener("click", () => {
|
|
77
|
+
if (list.matches(":popover-open")) {
|
|
78
|
+
list.hidePopover();
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
const rect = button.getBoundingClientRect();
|
|
82
|
+
const width = Math.min(240, innerWidth - 16);
|
|
83
|
+
list.style.width = `${width}px`;
|
|
84
|
+
list.style.left = `${Math.max(8, Math.min(rect.left, innerWidth - width - 8))}px`;
|
|
85
|
+
list.style.top = `${rect.bottom + 6}px`;
|
|
86
|
+
list.showPopover();
|
|
87
|
+
list.scrollTop = Math.max(0, (active?.offsetTop ?? 0) - list.clientHeight / 2);
|
|
88
|
+
active?.focus({ preventScroll: true });
|
|
89
|
+
}, options);
|
|
90
|
+
list.addEventListener("toggle", () => {
|
|
91
|
+
button.setAttribute("aria-expanded", String(list.matches(":popover-open")));
|
|
92
|
+
}, options);
|
|
93
|
+
list.addEventListener("keydown", (event) => {
|
|
94
|
+
const index = sections.findIndex(({ link }) => link === doc.activeElement);
|
|
95
|
+
if (["ArrowDown", "ArrowUp", "Home", "End"].includes(event.key)) {
|
|
96
|
+
event.preventDefault();
|
|
97
|
+
const next = event.key === "Home"
|
|
98
|
+
? 0
|
|
99
|
+
: event.key === "End"
|
|
100
|
+
? sections.length - 1
|
|
101
|
+
: (index +
|
|
102
|
+
(event.key === "ArrowDown" ? 1 : sections.length - 1)) %
|
|
103
|
+
sections.length;
|
|
104
|
+
sections[next]?.link.focus();
|
|
105
|
+
}
|
|
106
|
+
if (event.key === "Escape")
|
|
107
|
+
button.focus({ preventScroll: true });
|
|
108
|
+
}, options);
|
|
109
|
+
list.addEventListener("click", (event) => {
|
|
110
|
+
if (event.button !== 0 ||
|
|
111
|
+
event.metaKey ||
|
|
112
|
+
event.ctrlKey ||
|
|
113
|
+
event.shiftKey ||
|
|
114
|
+
event.altKey)
|
|
115
|
+
return;
|
|
116
|
+
const link = event.target.closest("[data-toc-target]");
|
|
117
|
+
const heading = sections.find((section) => section.link === link)?.heading;
|
|
118
|
+
if (!heading)
|
|
119
|
+
return;
|
|
120
|
+
list.hidePopover();
|
|
121
|
+
button.focus({ preventScroll: true });
|
|
122
|
+
if (pane) {
|
|
123
|
+
// Pane activation is asynchronous, so resolve the clicked pane directly.
|
|
124
|
+
event.preventDefault();
|
|
125
|
+
heading.scrollIntoView({ block: "start" });
|
|
126
|
+
}
|
|
127
|
+
}, options);
|
|
128
|
+
seek.addEventListener("input", () => {
|
|
129
|
+
const maximum = Math.max(0, scroller.scrollHeight - scroller.clientHeight);
|
|
130
|
+
scroller.scrollTo({
|
|
131
|
+
top: (Number(seek.value) / 1000) * maximum,
|
|
132
|
+
behavior: "instant",
|
|
133
|
+
});
|
|
134
|
+
schedule();
|
|
135
|
+
}, options);
|
|
136
|
+
mounted.set(nav, { article, update, dispose: () => listeners.abort() });
|
|
137
|
+
resize.observe(article);
|
|
138
|
+
}
|
|
139
|
+
schedule();
|
|
140
|
+
};
|
|
141
|
+
const observer = new MutationObserver(initialize);
|
|
142
|
+
observer.observe(doc.documentElement, { childList: true, subtree: true });
|
|
143
|
+
doc.addEventListener("scroll", schedule, {
|
|
144
|
+
capture: true,
|
|
145
|
+
passive: true,
|
|
146
|
+
signal: controller.signal,
|
|
147
|
+
});
|
|
148
|
+
doc.addEventListener("load", schedule, { capture: true, signal: controller.signal });
|
|
149
|
+
doc.addEventListener("docs:font-change", schedule, { signal: controller.signal });
|
|
150
|
+
window.addEventListener("resize", schedule, { signal: controller.signal });
|
|
151
|
+
initialize();
|
|
152
|
+
return () => {
|
|
153
|
+
controller.abort();
|
|
154
|
+
observer.disconnect();
|
|
155
|
+
resize.disconnect();
|
|
156
|
+
cancelAnimationFrame(frame);
|
|
157
|
+
for (const { dispose } of mounted.values())
|
|
158
|
+
dispose();
|
|
159
|
+
mounted.clear();
|
|
160
|
+
};
|
|
161
|
+
}
|