duckfn-docs-kit 0.2.1 → 0.4.0
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/AGENTS.md +294 -224
- package/README.md +10 -6
- package/bin/sql-verify.mjs +12 -12
- package/dist/IconButton.d.ts +22 -0
- package/dist/codemirror.d.ts +29 -0
- package/dist/index.d.ts +28 -7
- package/dist/index.js +2 -2
- package/dist/mermaid/DfkMermaid.d.ts +7 -0
- package/dist/mermaid/config.d.ts +48 -0
- package/dist/mermaid/remark.d.ts +40 -0
- package/dist/mermaid/remark.js +32 -0
- package/dist/mermaid/render.d.ts +94 -0
- package/dist/mermaid/styles.d.ts +6 -0
- package/dist/mermaid/title.d.ts +23 -0
- package/dist/{register-DKLiYs-F.js → register-Dev_kc3Z.js} +921 -579
- package/dist/remark.d.ts +1 -1
- package/dist/sql/browserRunner.d.ts +41 -0
- package/dist/sql/browserRunner.js +186 -0
- package/dist/sql/client.js +1 -1
- package/dist/sql/harness.d.ts +59 -0
- package/dist/sql/harness.js +8328 -0
- package/dist/sql/remark.d.ts +6 -1
- package/dist/sql/renderers.d.ts +2 -0
- package/dist/sql/runtime.d.ts +20 -0
- package/dist/sql/verify.d.ts +4 -5
- package/dist/sql/verify.js +36 -49
- package/package.json +6 -2
- package/src/IconButton.ts +50 -0
- package/src/codemirror.ts +88 -0
- package/src/index.ts +31 -7
- package/src/mermaid/DfkMermaid.css +289 -0
- package/src/mermaid/DfkMermaid.ts +557 -0
- package/src/mermaid/config.ts +74 -0
- package/src/mermaid/remark.ts +98 -0
- package/src/mermaid/render.ts +178 -0
- package/src/mermaid/styles.ts +24 -0
- package/src/mermaid/title.ts +127 -0
- package/src/register.ts +3 -0
- package/src/remark.ts +1 -1
- package/src/sql/DfkSql.css +13 -10
- package/src/sql/DfkSql.ts +11 -46
- package/src/sql/browserRunner.ts +402 -0
- package/src/sql/harness.ts +134 -0
- package/src/sql/remark.ts +6 -1
- package/src/sql/renderers.ts +24 -3
- package/src/sql/runtime.ts +44 -0
- package/src/sql/sql.css +13 -11
- package/src/sql/verify.ts +23 -41
- package/dist/sql/editor.d.ts +0 -16
- package/dist/sql/nodeRunner.d.ts +0 -51
- package/dist/sql/nodeRunner.js +0 -115
- package/src/sql/editor.ts +0 -75
- package/src/sql/nodeRunner.ts +0 -298
package/dist/sql/remark.d.ts
CHANGED
|
@@ -34,8 +34,13 @@ export interface RunnableSqlConfig {
|
|
|
34
34
|
*
|
|
35
35
|
* `html` and `iframe` are the same renderer: both sandbox the markup in an
|
|
36
36
|
* iframe, so scripts run with an opaque origin.
|
|
37
|
+
*
|
|
38
|
+
* `mermaid` renders the column's mermaid source as a diagram through
|
|
39
|
+
* `<dfk-mermaid>` (the same element a ```mermaid fence produces), so the result
|
|
40
|
+
* gets the element's zoom, fullscreen, source editing and SVG download for
|
|
41
|
+
* free.
|
|
37
42
|
*/
|
|
38
|
-
show?: 'table' | 'html' | 'iframe' | 'svg' | 'text';
|
|
43
|
+
show?: 'table' | 'html' | 'iframe' | 'svg' | 'text' | 'mermaid';
|
|
39
44
|
/**
|
|
40
45
|
* What this block is expected to do when the docs' own SQL test suite runs it
|
|
41
46
|
* (`duckfn-docs-kit/sql/verify`). Defaults to `'ok'`; `'error'` marks a block
|
package/dist/sql/renderers.d.ts
CHANGED
|
@@ -5,6 +5,8 @@ import type { RunnableSqlConfig } from './remark';
|
|
|
5
5
|
*
|
|
6
6
|
* The registry is the seam later phases plug into. It ships `table` (VisActor
|
|
7
7
|
* VTable), a `text` fallback, the markup previews `iframe` / `html` / `svg`,
|
|
8
|
+
* `mermaid` (which hands the cell to the kit's own `<dfk-mermaid>` element, so a
|
|
9
|
+
* query can produce a diagram the reader can zoom, expand, edit and download),
|
|
8
10
|
* plus the `error` view every renderer shares.
|
|
9
11
|
*
|
|
10
12
|
* Heavy dependencies (`@visactor/vtable`) load through dynamic `import()`
|
package/dist/sql/runtime.d.ts
CHANGED
|
@@ -49,6 +49,26 @@ export type RuntimeState = 'idle' | 'loading' | 'ready' | 'error';
|
|
|
49
49
|
export interface RuntimeOptions {
|
|
50
50
|
/** Let `LOAD` accept an extension whose signature does not verify. */
|
|
51
51
|
allowUnsignedExtensions?: boolean;
|
|
52
|
+
/**
|
|
53
|
+
* Serve the DuckDB-Wasm engine from explicit same-origin URLs instead of the
|
|
54
|
+
* jsDelivr CDN. Used by the offline SQL verifier (`sql/browserRunner`): the
|
|
55
|
+
* harness passes the locally-served `duckdb-*.wasm` / worker script so a CI
|
|
56
|
+
* run never reaches the network. When set, `#create()` skips
|
|
57
|
+
* `getJsDelivrBundles()`/`selectBundle()` and the cross-origin blob-worker
|
|
58
|
+
* wrapper (the worker is same-origin here, so it is constructed directly).
|
|
59
|
+
*/
|
|
60
|
+
bundle?: LocalBundle;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* A locally-served DuckDB-Wasm bundle: absolute same-origin URLs for the
|
|
64
|
+
* engine wasm and its worker script. `pthreadWorker` is only needed for the
|
|
65
|
+
* cross-origin-isolated (COI) bundle; the default non-COI `eh`/`mvp` bundles run
|
|
66
|
+
* single-threaded and leave it unset.
|
|
67
|
+
*/
|
|
68
|
+
export interface LocalBundle {
|
|
69
|
+
mainModule: string;
|
|
70
|
+
mainWorker: string;
|
|
71
|
+
pthreadWorker?: string;
|
|
52
72
|
}
|
|
53
73
|
/** Options for {@link DuckDBRuntime.loadExtension}. */
|
|
54
74
|
export interface LoadExtensionOptions {
|
package/dist/sql/verify.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type WasmPlatform } from './
|
|
1
|
+
import { type WasmPlatform } from './browserRunner';
|
|
2
2
|
export interface VerifyOptions {
|
|
3
3
|
/** Docs site root; defaults to the working directory. */
|
|
4
4
|
siteDir?: string;
|
|
@@ -16,11 +16,10 @@ export interface VerifyOptions {
|
|
|
16
16
|
/** Per-block timeout in milliseconds; a hang is reported instead of blocking CI. */
|
|
17
17
|
timeoutMs?: number;
|
|
18
18
|
/**
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
* DuckDB's file system is the real one, relative to the working directory.
|
|
19
|
+
* The browser executable that runs the blocks. Defaults to a detected
|
|
20
|
+
* Chrome/Edge; the `DFK_BROWSER` environment variable is the same override.
|
|
22
21
|
*/
|
|
23
|
-
|
|
22
|
+
browser?: string;
|
|
24
23
|
/** Write the full result list here as JSON. */
|
|
25
24
|
reportFile?: string;
|
|
26
25
|
}
|
package/dist/sql/verify.js
CHANGED
|
@@ -1,52 +1,39 @@
|
|
|
1
1
|
import { collectRunnableSql as e, expectsError as t } from "./collect.js";
|
|
2
|
-
import {
|
|
2
|
+
import { BrowserSqlRunner as n } from "./browserRunner.js";
|
|
3
3
|
import { join as r, resolve as i } from "node:path";
|
|
4
|
-
import {
|
|
5
|
-
import { tmpdir as d } from "node:os";
|
|
4
|
+
import { readdirSync as a, statSync as o, writeFileSync as s } from "node:fs";
|
|
6
5
|
//#region src/sql/verify.ts
|
|
7
|
-
var
|
|
8
|
-
async function
|
|
9
|
-
let n = i(t.siteDir ?? process.cwd()),
|
|
6
|
+
var c = ["docs", "i18n"], l = 3e4;
|
|
7
|
+
async function u(t = {}) {
|
|
8
|
+
let n = i(t.siteDir ?? process.cwd()), r = e({
|
|
10
9
|
siteDir: n,
|
|
11
|
-
contentDirs: t.contentDirs ??
|
|
12
|
-
}),
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
let b;
|
|
17
|
-
try {
|
|
18
|
-
b = await h(s, f, t, m);
|
|
19
|
-
} finally {
|
|
20
|
-
process.chdir(_), t.workingDir || c(g, {
|
|
21
|
-
recursive: !0,
|
|
22
|
-
force: !0
|
|
23
|
-
});
|
|
24
|
-
}
|
|
25
|
-
let x = {
|
|
26
|
-
blocks: b,
|
|
27
|
-
asDeclared: b.filter((e) => e.outcome === "ok" || e.outcome === "error-as-expected"),
|
|
28
|
-
unexpected: b.filter((e) => e.outcome === "unexpected-error" || e.outcome === "unexpected-success")
|
|
10
|
+
contentDirs: t.contentDirs ?? m(n)
|
|
11
|
+
}), a = t.extension, o = await d(r, a ? /^https?:\/\//i.test(a) ? a : i(a) : h(n), t, t.timeoutMs ?? l), c = {
|
|
12
|
+
blocks: o,
|
|
13
|
+
asDeclared: o.filter((e) => e.outcome === "ok" || e.outcome === "error-as-expected"),
|
|
14
|
+
unexpected: o.filter((e) => e.outcome === "unexpected-error" || e.outcome === "unexpected-success")
|
|
29
15
|
};
|
|
30
|
-
return t.reportFile &&
|
|
16
|
+
return t.reportFile && s(t.reportFile, `${JSON.stringify(c, null, 2)}\n`), c;
|
|
31
17
|
}
|
|
32
|
-
async function
|
|
18
|
+
async function d(e, t, r, i) {
|
|
33
19
|
let a = await n.create({
|
|
34
20
|
extension: t,
|
|
35
21
|
platform: r.platform,
|
|
36
|
-
engine: r.engine
|
|
22
|
+
engine: r.engine,
|
|
23
|
+
browser: r.browser
|
|
37
24
|
}), o = [];
|
|
38
25
|
try {
|
|
39
26
|
let t = null;
|
|
40
|
-
for (let n of e) n.file !== t && (t = n.file, await a.newPage()), o.push(await
|
|
27
|
+
for (let n of e) n.file !== t && (t = n.file, await a.newPage()), o.push(await f(a, n, i));
|
|
41
28
|
} finally {
|
|
42
29
|
await a.close();
|
|
43
30
|
}
|
|
44
31
|
return o;
|
|
45
32
|
}
|
|
46
|
-
async function
|
|
33
|
+
async function f(e, n, r) {
|
|
47
34
|
let i = t(n.config);
|
|
48
35
|
try {
|
|
49
|
-
let t = await
|
|
36
|
+
let t = await p(e.run(n.sql), r);
|
|
50
37
|
return {
|
|
51
38
|
file: n.file,
|
|
52
39
|
line: n.line,
|
|
@@ -63,43 +50,43 @@ async function g(e, n, r) {
|
|
|
63
50
|
};
|
|
64
51
|
}
|
|
65
52
|
}
|
|
66
|
-
function
|
|
53
|
+
function p(e, t) {
|
|
67
54
|
let n, r = new Promise((e, r) => {
|
|
68
55
|
n = setTimeout(() => r(/* @__PURE__ */ Error(`timed out after ${t}ms`)), t);
|
|
69
56
|
});
|
|
70
57
|
return Promise.race([e, r]).finally(() => clearTimeout(n));
|
|
71
58
|
}
|
|
72
|
-
function
|
|
59
|
+
function m(e) {
|
|
73
60
|
let t = [];
|
|
74
|
-
|
|
75
|
-
let n = r(e,
|
|
76
|
-
if (
|
|
61
|
+
g(r(e, c[0])) && t.push(c[0]);
|
|
62
|
+
let n = r(e, c[1]);
|
|
63
|
+
if (g(n)) for (let e of a(n)) g(r(n, e, "docusaurus-plugin-content-docs", "current")) && t.push(r("i18n", e, "docusaurus-plugin-content-docs", "current"));
|
|
77
64
|
return t.length > 0 ? t : ["."];
|
|
78
65
|
}
|
|
79
|
-
function
|
|
80
|
-
let t = r(e, "static", "duckdb-extensions"), n =
|
|
66
|
+
function h(e) {
|
|
67
|
+
let t = r(e, "static", "duckdb-extensions"), n = g(t) ? a(t).filter((e) => e.endsWith(".duckdb_extension.wasm")) : [];
|
|
81
68
|
if (n.length === 0) throw Error(`sql/verify: no extension found in ${t} — pass --extension <file|url>, or let the site's extension preload plugin fetch it first`);
|
|
82
69
|
if (n.length > 1) throw Error(`sql/verify: several extensions found in ${t} (${n.join(", ")}) — pass --extension to pick one`);
|
|
83
70
|
return r(t, n[0]);
|
|
84
71
|
}
|
|
85
|
-
function
|
|
86
|
-
return
|
|
72
|
+
function g(e) {
|
|
73
|
+
return o(e, { throwIfNoEntry: !1 })?.isDirectory() ?? !1;
|
|
87
74
|
}
|
|
88
|
-
async function
|
|
89
|
-
let t =
|
|
75
|
+
async function _(e) {
|
|
76
|
+
let t = y(e);
|
|
90
77
|
if (t.help) {
|
|
91
|
-
process.stdout.write(
|
|
78
|
+
process.stdout.write(v);
|
|
92
79
|
return;
|
|
93
80
|
}
|
|
94
|
-
let n = Date.now(), r = await
|
|
81
|
+
let n = Date.now(), r = await u(t), i = ((Date.now() - n) / 1e3).toFixed(1), a = r.blocks.filter((e) => e.outcome === "error-as-expected").length;
|
|
95
82
|
if (t.quiet || (process.stdout.write(`\n${r.blocks.length} block(s) in ${i}s\n`), process.stdout.write(` ${r.asDeclared.length} as declared (${a} erroring on purpose), ${r.unexpected.length} unexpected\n`)), r.unexpected.length > 0) {
|
|
96
83
|
process.stdout.write("unexpected behaviour:\n");
|
|
97
84
|
for (let e of r.unexpected) process.stdout.write(`- ${e.file}:${e.line} [${e.outcome}] :: ${e.detail}\n`);
|
|
98
85
|
process.exitCode = 1;
|
|
99
86
|
}
|
|
100
87
|
}
|
|
101
|
-
var
|
|
102
|
-
function
|
|
88
|
+
var v = "Usage: duckfn-sql-verify [options]\n\nRuns every runnable SQL block of a duckfn docs site in a headless browser\n(DuckDB-Wasm), and checks that each one behaves as its own metadata declares\n(\"expect\": \"error\" for a block that demonstrates a failure).\n\n --site <dir> Docs site root (default: the working directory)\n --content <dir> Content directory, relative to the site root (repeatable;\n default: docs/ plus every i18n/<locale>/… translation)\n --extension <path> The extension to preload: a .duckdb_extension.wasm path,\n or an absolute http(s) URL (default: the single file under\n static/duckdb-extensions/)\n --platform <eh|mvp> DuckDB-Wasm bundle, which must match the extension build\n (default: eh)\n --engine <path> Engine wasm override\n --browser <path> Browser executable (default: a detected Chrome/Edge,\n or the DFK_BROWSER environment variable)\n --timeout <ms> Per-block timeout (default: 30000)\n --report <file> Write the full result list as JSON\n --quiet Only report unexpected behaviour\n --help Show this help\n";
|
|
89
|
+
function y(e) {
|
|
103
90
|
let t = {}, n = [];
|
|
104
91
|
for (let r = 0; r < e.length; r++) {
|
|
105
92
|
let i = e[r], a = () => {
|
|
@@ -123,12 +110,12 @@ function C(e) {
|
|
|
123
110
|
case "--engine":
|
|
124
111
|
t.engine = a();
|
|
125
112
|
break;
|
|
113
|
+
case "--browser":
|
|
114
|
+
t.browser = a();
|
|
115
|
+
break;
|
|
126
116
|
case "--timeout":
|
|
127
117
|
t.timeoutMs = Number(a());
|
|
128
118
|
break;
|
|
129
|
-
case "--working-dir":
|
|
130
|
-
t.workingDir = a();
|
|
131
|
-
break;
|
|
132
119
|
case "--report":
|
|
133
120
|
t.reportFile = a();
|
|
134
121
|
break;
|
|
@@ -145,4 +132,4 @@ function C(e) {
|
|
|
145
132
|
return n.length > 0 && (t.contentDirs = n), t;
|
|
146
133
|
}
|
|
147
134
|
//#endregion
|
|
148
|
-
export {
|
|
135
|
+
export { _ as cliMain, u as verifySqlDocs };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "duckfn-docs-kit",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "Shared Docusaurus building blocks (TOC toggle, home-page web components, brand tokens, remark version placeholder) for duckfn-family extension docs sites.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"keywords": [
|
|
@@ -45,7 +45,7 @@
|
|
|
45
45
|
"AGENTS.md"
|
|
46
46
|
],
|
|
47
47
|
"scripts": {
|
|
48
|
-
"build": "vite build && tsc -p tsconfig.build.json",
|
|
48
|
+
"build": "vite build && vite build --config vite.harness.config.ts && tsc -p tsconfig.build.json",
|
|
49
49
|
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
50
50
|
"clean": "node -e \"require('fs').rmSync('dist',{recursive:true,force:true})\"",
|
|
51
51
|
"prepack": "npm run build"
|
|
@@ -59,9 +59,13 @@
|
|
|
59
59
|
"@codemirror/state": "^6.7.6",
|
|
60
60
|
"@codemirror/view": "^6.43.13",
|
|
61
61
|
"@duckdb/duckdb-wasm": "1.33.1-dev64.0",
|
|
62
|
+
"@panzoom/panzoom": "^4.6.2",
|
|
62
63
|
"@visactor/vtable": "^1.26.8",
|
|
63
64
|
"codemirror": "^6.0.2",
|
|
65
|
+
"filenamify": "^7.0.3",
|
|
64
66
|
"iconify-icon": "^3.0.3",
|
|
67
|
+
"mermaid": "^12.0.0",
|
|
68
|
+
"playwright-core": "^1.63.0",
|
|
65
69
|
"sql-formatter": "^15.9.0"
|
|
66
70
|
},
|
|
67
71
|
"devDependencies": {
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import type {IconifyIconHTMLElement} from 'iconify-icon';
|
|
2
|
+
import {el} from './dom';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* A compact icon-only button with a hover/focus tooltip, built once and then
|
|
6
|
+
* mutated. Shared by `<dfk-sql>`'s code-block cluster and `<dfk-mermaid>`'s
|
|
7
|
+
* diagram cluster: both want the same "floating icon row over content" idiom,
|
|
8
|
+
* and neither wants a second implementation of it.
|
|
9
|
+
*
|
|
10
|
+
* The tooltip is also the accessible name — an icon-only control has no text to
|
|
11
|
+
* fall back on. The two class names are written by this module and styled in
|
|
12
|
+
* *each* consumer's shadow-root CSS; that duplication is unavoidable (a shadow
|
|
13
|
+
* boundary stops one sheet from reaching the other tree), so the rules carry the
|
|
14
|
+
* same names and are kept in sync by hand.
|
|
15
|
+
*/
|
|
16
|
+
export class IconButton {
|
|
17
|
+
readonly root = el('button', {class: 'dfk-icon-button', type: 'button'});
|
|
18
|
+
readonly #icon: IconifyIconHTMLElement = el('iconify-icon', {
|
|
19
|
+
class: 'dfk-icon',
|
|
20
|
+
attrs: {'aria-hidden': 'true'},
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
constructor(icon: string, onClick: () => void) {
|
|
24
|
+
this.root.appendChild(this.#icon);
|
|
25
|
+
this.root.addEventListener('click', onClick);
|
|
26
|
+
this.setIcon(icon);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
setIcon(icon: string): void {
|
|
30
|
+
this.#icon.setAttribute('icon', icon);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
setLabel(text: string): void {
|
|
34
|
+
this.root.setAttribute('data-tip', text);
|
|
35
|
+
this.root.setAttribute('aria-label', text);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/** Marks a toggle as currently on (e.g. the SQL block's wrap toggle). */
|
|
39
|
+
setOn(on: boolean): void {
|
|
40
|
+
this.root.classList.toggle('dfk-icon-on', on);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
setDisabled(disabled: boolean): void {
|
|
44
|
+
if (disabled) {
|
|
45
|
+
this.root.setAttribute('disabled', '');
|
|
46
|
+
} else {
|
|
47
|
+
this.root.removeAttribute('disabled');
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The CodeMirror 6 editor behind the kit's editable content: the code view of a
|
|
3
|
+
* runnable SQL block (`<dfk-sql>`) and the diagram-source dialog of a
|
|
4
|
+
* `<dfk-mermaid>`. One implementation, two languages — the SQL block asks for
|
|
5
|
+
* highlighting, the diagram editor takes the plain-text default.
|
|
6
|
+
*
|
|
7
|
+
* Every CodeMirror module arrives through a dynamic `import()` inside
|
|
8
|
+
* {@link mountCodeEditor}: a page full of blocks pays nothing for the editor on
|
|
9
|
+
* its critical path, and Docusaurus' Node prerender never evaluates any of it.
|
|
10
|
+
*
|
|
11
|
+
* This is browser-only code: `document` is touched only through the container
|
|
12
|
+
* the caller hands over, and everything else is behind the `import()`s.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
export interface CodeEditor {
|
|
16
|
+
getValue(): string;
|
|
17
|
+
setValue(value: string): void;
|
|
18
|
+
/** Soft-wraps long lines, or stops wrapping them (the block's wrap toggle). */
|
|
19
|
+
setWrap(wrapped: boolean): void;
|
|
20
|
+
destroy(): void;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface CodeEditorOptions {
|
|
24
|
+
/**
|
|
25
|
+
* Highlight the document as SQL. Omitted, the editor is plain text — which is
|
|
26
|
+
* what a mermaid diagram gets: there is no first-party CodeMirror language for
|
|
27
|
+
* it, and a docs reader is editing prose-shaped source, not writing SQL.
|
|
28
|
+
*/
|
|
29
|
+
language?: 'sql';
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export async function mountCodeEditor(
|
|
33
|
+
container: HTMLElement,
|
|
34
|
+
value: string,
|
|
35
|
+
onChange: (value: string) => void,
|
|
36
|
+
options: CodeEditorOptions = {},
|
|
37
|
+
): Promise<CodeEditor> {
|
|
38
|
+
const [{basicSetup}, {EditorView, keymap}, {defaultKeymap, historyKeymap}, {Compartment}] =
|
|
39
|
+
await Promise.all([
|
|
40
|
+
import('codemirror'),
|
|
41
|
+
import('@codemirror/view'),
|
|
42
|
+
import('@codemirror/commands'),
|
|
43
|
+
import('@codemirror/state'),
|
|
44
|
+
]);
|
|
45
|
+
// Loaded only for the blocks that ask for it, so a diagram editor does not pull
|
|
46
|
+
// the SQL grammar (and its lezer parsers) into the page.
|
|
47
|
+
const language =
|
|
48
|
+
options.language === 'sql' ? [(await import('@codemirror/lang-sql')).sql()] : [];
|
|
49
|
+
|
|
50
|
+
// Wrapping is toggled from the outside, and reconfiguring it must not disturb
|
|
51
|
+
// the document or the undo history — that is exactly what a compartment is
|
|
52
|
+
// for, so the extension is swapped in place rather than rebuilt.
|
|
53
|
+
const wrap = new Compartment();
|
|
54
|
+
|
|
55
|
+
const view = new EditorView({
|
|
56
|
+
doc: value,
|
|
57
|
+
extensions: [
|
|
58
|
+
basicSetup,
|
|
59
|
+
...language,
|
|
60
|
+
keymap.of([...defaultKeymap, ...historyKeymap]),
|
|
61
|
+
wrap.of([]),
|
|
62
|
+
EditorView.updateListener.of((update) => {
|
|
63
|
+
if (update.docChanged) {
|
|
64
|
+
onChange(update.state.doc.toString());
|
|
65
|
+
}
|
|
66
|
+
}),
|
|
67
|
+
],
|
|
68
|
+
parent: container,
|
|
69
|
+
// `root` is left to CodeMirror's own `getRoot(container)`. The container sits
|
|
70
|
+
// in the component's shadow root, so style-mod mounts the base theme into
|
|
71
|
+
// that same shadow root — exactly where the `.cm-*` rules are needed.
|
|
72
|
+
// Pinning it to `document` would put them outside the editor's tree instead,
|
|
73
|
+
// where a shadow boundary stops them.
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
return {
|
|
77
|
+
getValue: () => view.state.doc.toString(),
|
|
78
|
+
setValue: (next: string) =>
|
|
79
|
+
view.dispatch({
|
|
80
|
+
changes: {from: 0, to: view.state.doc.length, insert: next},
|
|
81
|
+
}),
|
|
82
|
+
setWrap: (wrapped: boolean) =>
|
|
83
|
+
view.dispatch({
|
|
84
|
+
effects: wrap.reconfigure(wrapped ? EditorView.lineWrapping : []),
|
|
85
|
+
}),
|
|
86
|
+
destroy: () => view.destroy(),
|
|
87
|
+
};
|
|
88
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -1,7 +1,14 @@
|
|
|
1
1
|
import type {HTMLAttributes} from 'react';
|
|
2
|
+
// Imported as well as re-exported: the `HTMLElementTagNameMap` augmentation below
|
|
3
|
+
// names the classes, and a module augmentation can only refer to local bindings.
|
|
4
|
+
import {DfkFeatures} from './home/DfkFeatures';
|
|
5
|
+
import {DfkHero} from './home/DfkHero';
|
|
6
|
+
import {DfkMermaid} from './mermaid/DfkMermaid';
|
|
7
|
+
import {DfkNextSteps} from './home/DfkNextSteps';
|
|
8
|
+
import {DfkSql} from './sql/DfkSql';
|
|
2
9
|
|
|
3
10
|
/**
|
|
4
|
-
* Browser entry for duckfn-docs-kit: the
|
|
11
|
+
* Browser entry for duckfn-docs-kit: the custom elements it registers and the
|
|
5
12
|
* value types their `set*` methods accept.
|
|
6
13
|
*
|
|
7
14
|
* The components are retained-mode (build once, then mutate held nodes) and
|
|
@@ -13,17 +20,16 @@ import type {HTMLAttributes} from 'react';
|
|
|
13
20
|
* web component, which `registerDfkElements()` registers as a side effect, so
|
|
14
21
|
* this package ships no icon data.
|
|
15
22
|
*
|
|
16
|
-
* `TocToggle` and the remark
|
|
17
|
-
* (`duckfn-docs-kit/toc-toggle/TocToggle`, `duckfn-docs-kit/remark
|
|
23
|
+
* `TocToggle` and the remark plugins keep their own subpaths
|
|
24
|
+
* (`duckfn-docs-kit/toc-toggle/TocToggle`, `duckfn-docs-kit/remark`,
|
|
25
|
+
* `duckfn-docs-kit/sql/remark`, `duckfn-docs-kit/mermaid/remark`) instead of
|
|
18
26
|
* being merged here: a Docusaurus config file must never pull browser code into
|
|
19
27
|
* Node, and a site that only wants the TOC collapse button should not pay for
|
|
20
28
|
* the bundled `iconify-icon`.
|
|
21
29
|
*/
|
|
22
|
-
export {DfkFeatures
|
|
23
|
-
export {DfkHero} from './home/DfkHero';
|
|
24
|
-
export {DfkNextSteps} from './home/DfkNextSteps';
|
|
25
|
-
export {DfkSql} from './sql/DfkSql';
|
|
30
|
+
export {DfkFeatures, DfkHero, DfkMermaid, DfkNextSteps, DfkSql};
|
|
26
31
|
export {registerDfkElements} from './register';
|
|
32
|
+
export type {DfkMermaidConfig, DfkMermaidConfigInput} from './mermaid/config';
|
|
27
33
|
export type {RunnableSqlConfig} from './sql/remark';
|
|
28
34
|
export type {
|
|
29
35
|
FeatureItem,
|
|
@@ -47,6 +53,23 @@ export type {
|
|
|
47
53
|
*/
|
|
48
54
|
export type DfkElementProps = HTMLAttributes<HTMLElement>;
|
|
49
55
|
|
|
56
|
+
/**
|
|
57
|
+
* The kit's tags in the DOM's own tag map, so `document.createElement('dfk-sql')`
|
|
58
|
+
* (and the kit's `el()` helper) is typed as the class it upgrades to. Without
|
|
59
|
+
* this, every place that builds a `dfk-*` element from scratch — `sql/renderers.ts`
|
|
60
|
+
* building a `<dfk-mermaid>` for a `mermaid` result, `docs/src/pages/index.tsx`
|
|
61
|
+
* mounting the home elements — would have to cast the result.
|
|
62
|
+
*/
|
|
63
|
+
declare global {
|
|
64
|
+
interface HTMLElementTagNameMap {
|
|
65
|
+
'dfk-hero': DfkHero;
|
|
66
|
+
'dfk-features': DfkFeatures;
|
|
67
|
+
'dfk-next-steps': DfkNextSteps;
|
|
68
|
+
'dfk-sql': DfkSql;
|
|
69
|
+
'dfk-mermaid': DfkMermaid;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
50
73
|
declare module 'react' {
|
|
51
74
|
namespace JSX {
|
|
52
75
|
interface IntrinsicElements {
|
|
@@ -54,6 +77,7 @@ declare module 'react' {
|
|
|
54
77
|
'dfk-features': DfkElementProps;
|
|
55
78
|
'dfk-next-steps': DfkElementProps;
|
|
56
79
|
'dfk-sql': DfkElementProps;
|
|
80
|
+
'dfk-mermaid': DfkElementProps;
|
|
57
81
|
}
|
|
58
82
|
}
|
|
59
83
|
}
|