duckfn-docs-kit 0.1.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 +689 -0
- package/LICENSE +21 -0
- package/README.md +107 -0
- package/dist/dom.d.ts +69 -0
- package/dist/home/DfkFeatures.d.ts +20 -0
- package/dist/home/DfkHero.d.ts +25 -0
- package/dist/home/DfkNextSteps.d.ts +16 -0
- package/dist/home/styles.d.ts +8 -0
- package/dist/index.d.ts +50 -0
- package/dist/index.js +2 -0
- package/dist/register-DKLiYs-F.js +2324 -0
- package/dist/register.d.ts +10 -0
- package/dist/remark.d.ts +21 -0
- package/dist/remark.js +15 -0
- package/dist/runtimeConfig-Bokbb8VH.js +106 -0
- package/dist/sql/DfkSql.d.ts +7 -0
- package/dist/sql/PreviewTabs.d.ts +37 -0
- package/dist/sql/client.d.ts +1 -0
- package/dist/sql/client.js +4 -0
- package/dist/sql/editor.d.ts +16 -0
- package/dist/sql/extensions.d.ts +108 -0
- package/dist/sql/extensions.js +198 -0
- package/dist/sql/remark.d.ts +88 -0
- package/dist/sql/remark.js +69 -0
- package/dist/sql/renderers.d.ts +44 -0
- package/dist/sql/runtime.d.ts +105 -0
- package/dist/sql/runtimeConfig.d.ts +80 -0
- package/dist/sql/styles.d.ts +6 -0
- package/dist/toc-toggle/TocToggle.d.ts +46 -0
- package/dist/toc-toggle/TocToggle.js +69 -0
- package/dist/toc-toggle/client.d.ts +1 -0
- package/dist/toc-toggle/client.js +9 -0
- package/dist/toc-toggle/plugin.d.ts +36 -0
- package/dist/toc-toggle/plugin.js +13 -0
- package/dist/types.d.ts +42 -0
- package/package.json +73 -0
- package/src/dom.ts +109 -0
- package/src/home/DfkFeatures.ts +78 -0
- package/src/home/DfkHero.ts +128 -0
- package/src/home/DfkNextSteps.ts +73 -0
- package/src/home/home.css +520 -0
- package/src/home/styles.ts +28 -0
- package/src/index.ts +59 -0
- package/src/kit.css +19 -0
- package/src/register.ts +39 -0
- package/src/remark.ts +60 -0
- package/src/sql/DfkSql.css +226 -0
- package/src/sql/DfkSql.ts +620 -0
- package/src/sql/PreviewTabs.ts +169 -0
- package/src/sql/client.ts +16 -0
- package/src/sql/editor.ts +75 -0
- package/src/sql/extensions.ts +470 -0
- package/src/sql/remark.ts +213 -0
- package/src/sql/renderers.ts +916 -0
- package/src/sql/runtime.ts +348 -0
- package/src/sql/runtimeConfig.ts +249 -0
- package/src/sql/sql.css +397 -0
- package/src/sql/styles.ts +24 -0
- package/src/theme/tokens.css +75 -0
- package/src/toc-toggle/TocToggle.css +69 -0
- package/src/toc-toggle/TocToggle.ts +172 -0
- package/src/toc-toggle/client.ts +20 -0
- package/src/toc-toggle/plugin.ts +54 -0
- package/src/types.ts +47 -0
- package/src/vite-env.d.ts +8 -0
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
//#region src/sql/remark.ts
|
|
2
|
+
var e = "dfk-sql";
|
|
3
|
+
function t(e) {
|
|
4
|
+
if (!e) return null;
|
|
5
|
+
try {
|
|
6
|
+
let t = JSON.parse(e);
|
|
7
|
+
if (typeof t == "object" && t && t.type === "duckfn") return t;
|
|
8
|
+
} catch {}
|
|
9
|
+
return null;
|
|
10
|
+
}
|
|
11
|
+
function n(e) {
|
|
12
|
+
return Math.max(1, e.split("\n").length);
|
|
13
|
+
}
|
|
14
|
+
function r(e) {
|
|
15
|
+
return {
|
|
16
|
+
type: "mdxJsxFlowElement",
|
|
17
|
+
name: "div",
|
|
18
|
+
attributes: [{
|
|
19
|
+
type: "mdxJsxAttribute",
|
|
20
|
+
name: "className",
|
|
21
|
+
value: "dfk-sql-editor-skeleton"
|
|
22
|
+
}],
|
|
23
|
+
children: Array.from({ length: n(e) }, () => ({
|
|
24
|
+
type: "mdxJsxFlowElement",
|
|
25
|
+
name: "span",
|
|
26
|
+
attributes: [{
|
|
27
|
+
type: "mdxJsxAttribute",
|
|
28
|
+
name: "className",
|
|
29
|
+
value: "dfk-sql-editor-skeleton-line"
|
|
30
|
+
}],
|
|
31
|
+
children: []
|
|
32
|
+
}))
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
function i(t, n) {
|
|
36
|
+
let i = String(t.value ?? "");
|
|
37
|
+
return {
|
|
38
|
+
type: "mdxJsxFlowElement",
|
|
39
|
+
name: e,
|
|
40
|
+
attributes: [{
|
|
41
|
+
type: "mdxJsxAttribute",
|
|
42
|
+
name: "config",
|
|
43
|
+
value: JSON.stringify(n)
|
|
44
|
+
}, {
|
|
45
|
+
type: "mdxJsxAttribute",
|
|
46
|
+
name: "sql",
|
|
47
|
+
value: i
|
|
48
|
+
}],
|
|
49
|
+
children: [r(i), t]
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
var a = (e = {}) => (e) => {
|
|
53
|
+
let n = (e) => {
|
|
54
|
+
if (typeof e != "object" || !e) return;
|
|
55
|
+
let r = e;
|
|
56
|
+
Array.isArray(r.children) && (r.children = r.children.map((e) => {
|
|
57
|
+
if (typeof e != "object" || !e) return e;
|
|
58
|
+
let r = e;
|
|
59
|
+
if (r.type === "code" && r.lang === "sql") {
|
|
60
|
+
let e = t(r.meta);
|
|
61
|
+
if (e) return i(r, e);
|
|
62
|
+
}
|
|
63
|
+
return n(r), r;
|
|
64
|
+
}));
|
|
65
|
+
};
|
|
66
|
+
n(e);
|
|
67
|
+
};
|
|
68
|
+
//#endregion
|
|
69
|
+
export { e as DFK_SQL_TAG, a as remarkRunnableSql };
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import type { QueryResult } from './runtime';
|
|
2
|
+
import type { RunnableSqlConfig } from './remark';
|
|
3
|
+
/**
|
|
4
|
+
* Result renderers, keyed by the config's `show` field.
|
|
5
|
+
*
|
|
6
|
+
* The registry is the seam later phases plug into. It ships `table` (VisActor
|
|
7
|
+
* VTable), a `text` fallback, the markup previews `iframe` / `html` / `svg`,
|
|
8
|
+
* plus the `error` view every renderer shares.
|
|
9
|
+
*
|
|
10
|
+
* Heavy dependencies (`@visactor/vtable`) load through dynamic `import()`
|
|
11
|
+
* inside the renderer, so a page that never runs a query never pays for them,
|
|
12
|
+
* and Docusaurus' Node prerender never touches them.
|
|
13
|
+
*/
|
|
14
|
+
export interface RenderContext {
|
|
15
|
+
/** Container element in the light DOM, sized by `sql.css`. */
|
|
16
|
+
host: HTMLElement;
|
|
17
|
+
config: RunnableSqlConfig;
|
|
18
|
+
/** Localised strings resolved by the component (labels-by-html-lang). */
|
|
19
|
+
labels: Record<string, string>;
|
|
20
|
+
/**
|
|
21
|
+
* The component's fullscreen toggle, parked at the right end of the tab
|
|
22
|
+
* strip. `<dfk-sql>` owns the button's state and therefore the node; the
|
|
23
|
+
* renderer only borrows it so every result has the same chrome.
|
|
24
|
+
*/
|
|
25
|
+
fullscreenButton: HTMLElement;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Renders `result` into `context.host`. Resolves with a disposer releasing any
|
|
29
|
+
* resources (table instances) the renderer created; it runs on the next render
|
|
30
|
+
* and when the element disconnects.
|
|
31
|
+
*
|
|
32
|
+
* Every renderer is async — the heavy ones await their dynamic `import()`, and
|
|
33
|
+
* the trivial ones just resolve immediately — so the caller has exactly one
|
|
34
|
+
* shape to handle.
|
|
35
|
+
*/
|
|
36
|
+
export type Renderer = (context: RenderContext, result: QueryResult) => Promise<void | (() => void)>;
|
|
37
|
+
/** Shared error view: a styled block, never a thrown exception. */
|
|
38
|
+
export declare const errorRenderer: Renderer;
|
|
39
|
+
/**
|
|
40
|
+
* Picks the renderer for a result: the config's `show` wins, otherwise a
|
|
41
|
+
* single-column single-row result degrades to `text` (a bare scalar like
|
|
42
|
+
* `SELECT 1;` reads better as a line than as a 1×1 table), otherwise `table`.
|
|
43
|
+
*/
|
|
44
|
+
export declare function rendererFor(config: RunnableSqlConfig, result: QueryResult): Renderer;
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The browser-side DuckDB-Wasm runtime: one instance per docs-site frontend
|
|
3
|
+
* runtime (module-level singleton), *not* persisted across page loads.
|
|
4
|
+
*
|
|
5
|
+
* Design points:
|
|
6
|
+
*
|
|
7
|
+
* - `@duckdb/duckdb-wasm` is only ever reached through a dynamic `import()`,
|
|
8
|
+
* so Docusaurus' Node prerender pass and the initial page load never touch
|
|
9
|
+
* it. The wasm binary and the worker script come from the official jsDelivr
|
|
10
|
+
* CDN (`getJsDelivrBundles` + `selectBundle`), which sidesteps any webpack
|
|
11
|
+
* `asyncWebAssembly` / worker configuration on the consuming site.
|
|
12
|
+
* `selectBundle` falls back to a non-`SharedArrayBuffer` bundle when the
|
|
13
|
+
* page is not crossOrigin-isolated (GitHub Pages), so it works everywhere.
|
|
14
|
+
* - `init()` is idempotent and retryable: a failed init leaves `state` at
|
|
15
|
+
* `'error'` and clears the memoised promise, so a later Run click can try
|
|
16
|
+
* again.
|
|
17
|
+
* - `execute()` hands the whole string to DuckDB. Multi-statement queries
|
|
18
|
+
* return the result of the **last** statement, which is exactly the
|
|
19
|
+
* documented behaviour for runnable blocks.
|
|
20
|
+
* - Extensions get into the shared instance two ways, both through the same
|
|
21
|
+
* memoised loader: the **site preload list** (the ordered `preload` array of
|
|
22
|
+
* the JSON `<script>` tag the build-time plugin injects, loaded right after
|
|
23
|
+
* `connect()` so a block can rely on the extension without naming it), and
|
|
24
|
+
* the **per-block `extensions` config** loaded on demand by
|
|
25
|
+
* {@link DuckDBRuntime.loadExtension}. Note that on WebAssembly `INSTALL` is
|
|
26
|
+
* a no-op (there is no persistent storage to install *into*): it only
|
|
27
|
+
* records where a later `LOAD` fetches a name from. A load by name fetches
|
|
28
|
+
* `<repository>/duckdb-wasm/<revision>/<platform>/<name>.duckdb_extension.wasm`
|
|
29
|
+
* and verifies the signature; a `{url}` preload fetches exactly that URL
|
|
30
|
+
* (which is why the URL must be absolute — the worker runs from a blob URL
|
|
31
|
+
* and cannot resolve relative paths). Either way, the text before the first
|
|
32
|
+
* dot of the file name is the entry symbol DuckDB looks up, so a release
|
|
33
|
+
* asset like `duckfn-wasm_eh.duckdb_extension.wasm` has to be renamed to
|
|
34
|
+
* `duckfn.duckdb_extension.wasm` on the way in (enforced by validation).
|
|
35
|
+
* - Extension names, repositories and URLs are **validated, not escaped** (see
|
|
36
|
+
* `sql/runtimeConfig`): `LOAD` cannot take them as parameters.
|
|
37
|
+
* - `allowUnsignedExtensions` is opt-in and per-instance: it is a database
|
|
38
|
+
* setting fixed by `open()`, so it has to be known before the first
|
|
39
|
+
* `connect()`. The site-wide value (from the injected config) and the first
|
|
40
|
+
* caller's are merged by whichever `init()` actually creates the instance.
|
|
41
|
+
*/
|
|
42
|
+
export type RuntimeState = 'idle' | 'loading' | 'ready' | 'error';
|
|
43
|
+
/**
|
|
44
|
+
* Options for {@link DuckDBRuntime.init}. They are merged with the site-wide
|
|
45
|
+
* injected config, and only read by the caller that actually creates the
|
|
46
|
+
* instance: `allowUnsignedExtensions` is fixed at `open()` time and later
|
|
47
|
+
* callers cannot retune a database that already exists.
|
|
48
|
+
*/
|
|
49
|
+
export interface RuntimeOptions {
|
|
50
|
+
/** Let `LOAD` accept an extension whose signature does not verify. */
|
|
51
|
+
allowUnsignedExtensions?: boolean;
|
|
52
|
+
}
|
|
53
|
+
/** Options for {@link DuckDBRuntime.loadExtension}. */
|
|
54
|
+
export interface LoadExtensionOptions {
|
|
55
|
+
/** `community`, `core` or a repository URL, instead of the official default. */
|
|
56
|
+
repository?: string;
|
|
57
|
+
}
|
|
58
|
+
/** A normalised query result: column names plus row objects keyed by them. */
|
|
59
|
+
export interface QueryResult {
|
|
60
|
+
columns: string[];
|
|
61
|
+
rows: Record<string, unknown>[];
|
|
62
|
+
/** Set when the statement failed; `columns`/`rows` are then empty. */
|
|
63
|
+
error?: string;
|
|
64
|
+
}
|
|
65
|
+
export declare class DuckDBRuntime {
|
|
66
|
+
#private;
|
|
67
|
+
/** The per-frontend-runtime singleton; shared by every `<dfk-sql>` block. */
|
|
68
|
+
static getInstance(): DuckDBRuntime;
|
|
69
|
+
get state(): RuntimeState;
|
|
70
|
+
/** The last init failure's message, for the UI to display. */
|
|
71
|
+
get message(): string;
|
|
72
|
+
/**
|
|
73
|
+
* Creates the database in the background (first Run click triggers it; a
|
|
74
|
+
* consuming site may also call it early to warm the instance). Concurrent
|
|
75
|
+
* callers share one promise, and it only resolves once the site's preloads
|
|
76
|
+
* are loaded too.
|
|
77
|
+
*
|
|
78
|
+
* Options and the injected site config are only read by the caller that
|
|
79
|
+
* actually creates the instance: `allowUnsignedExtensions` is fixed at
|
|
80
|
+
* `open()` time, and later callers cannot retune a database that already
|
|
81
|
+
* exists. A malformed injected config fails here, before any download.
|
|
82
|
+
*/
|
|
83
|
+
init(options?: RuntimeOptions): Promise<void>;
|
|
84
|
+
/** Runs `sql` and resolves with the (last statement's) result. */
|
|
85
|
+
execute(sql: string): Promise<QueryResult>;
|
|
86
|
+
/**
|
|
87
|
+
* Loads one extension on demand — what a runnable block's `extensions`
|
|
88
|
+
* config ends up doing.
|
|
89
|
+
*
|
|
90
|
+
* `LOAD` is the whole mechanism on WebAssembly: it fetches the extension's
|
|
91
|
+
* `.duckdb_extension.wasm` and verifies the signature before loading it.
|
|
92
|
+
* `INSTALL … FROM` only records *where* a later `LOAD` should fetch from
|
|
93
|
+
* (there is no persistent storage to install into), which is also why it is
|
|
94
|
+
* used for a non-default `repository` instead of the global
|
|
95
|
+
* `SET custom_extension_repository` — the recorded source stays attached to
|
|
96
|
+
* this one extension.
|
|
97
|
+
*
|
|
98
|
+
* The name and repository are validated rather than escaped — `LOAD` takes
|
|
99
|
+
* an identifier, not a parameter, so anything that could terminate the
|
|
100
|
+
* statement is rejected outright. Successful loads (and in-flight ones) are
|
|
101
|
+
* memoised per repository + name; a **failure** is not, so a Run click can
|
|
102
|
+
* retry.
|
|
103
|
+
*/
|
|
104
|
+
loadExtension(name: string, options?: LoadExtensionOptions): Promise<void>;
|
|
105
|
+
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The contract between the build-time plugin (`sql/extensions`) and the
|
|
3
|
+
* browser runtime (`sql/runtime`).
|
|
4
|
+
*
|
|
5
|
+
* The plugin writes one JSON `<script>` tag into every page carrying the
|
|
6
|
+
* site's runtime configuration; the runtime reads it once, when the shared
|
|
7
|
+
* DuckDB instance is first created. Tag id, entry shapes and the validators
|
|
8
|
+
* live together in this dependency-free module so the two sides cannot drift —
|
|
9
|
+
* and so the browser bundle never drags in Node code (or the Node plugin the
|
|
10
|
+
* DOM).
|
|
11
|
+
*
|
|
12
|
+
* This module must not import anything: it is bundled into both the browser
|
|
13
|
+
* and the Node entry points.
|
|
14
|
+
*/
|
|
15
|
+
/** Id of the JSON config `<script>` the build-time plugin injects per page. */
|
|
16
|
+
export declare const DFK_SQL_RUNTIME_TAG_ID = "dfk-sql-runtime";
|
|
17
|
+
/** A bare SQL identifier: `LOAD` / `INSTALL` cannot be parameterised, so this is the guard. */
|
|
18
|
+
export declare const EXTENSION_NAME_PATTERN: RegExp;
|
|
19
|
+
/** A repository URL with no character that could escape the SQL string literal. */
|
|
20
|
+
export declare const REPOSITORY_URL_PATTERN: RegExp;
|
|
21
|
+
/** Bare `FROM` keywords `INSTALL` accepts instead of a repository URL. */
|
|
22
|
+
export declare const REPOSITORY_KEYWORDS: Set<string>;
|
|
23
|
+
/** An extension preloaded by name, from the official repository or another one. */
|
|
24
|
+
export interface NamedPreloadEntry {
|
|
25
|
+
/** The extension name, e.g. `duckfn`. */
|
|
26
|
+
name: string;
|
|
27
|
+
/** `community`, `core` or a repository URL; omitted = the official repository. */
|
|
28
|
+
repository?: string;
|
|
29
|
+
}
|
|
30
|
+
/** A GitHub release asset the build-time plugin copies to `url` at build time. */
|
|
31
|
+
export interface PreloadReleaseSource {
|
|
32
|
+
/** The GitHub repository, `owner/name`. */
|
|
33
|
+
repository: string;
|
|
34
|
+
/** The asset name on that repository's latest release, e.g. `duckfn-wasm_eh.duckdb_extension.wasm`. */
|
|
35
|
+
asset: string;
|
|
36
|
+
}
|
|
37
|
+
/** An extension preloaded from a file, served by the site itself or remotely. */
|
|
38
|
+
export interface UrlPreloadEntry {
|
|
39
|
+
/**
|
|
40
|
+
* A site-relative path (`duckdb-extensions/duckfn.duckdb_extension.wasm`),
|
|
41
|
+
* resolved against the site's baseUrl and served from `static/`; or an
|
|
42
|
+
* absolute `http(s)://` URL.
|
|
43
|
+
*
|
|
44
|
+
* The base name of the last path segment — the text before its first dot —
|
|
45
|
+
* must be the extension name: on WebAssembly that text is what DuckDB turns
|
|
46
|
+
* into the `<name>_init_c_api` entry symbol, which is exactly why a release
|
|
47
|
+
* asset like `duckfn-wasm_eh.duckdb_extension.wasm` has to be renamed to
|
|
48
|
+
* `duckfn.duckdb_extension.wasm` on the way in.
|
|
49
|
+
*/
|
|
50
|
+
url: string;
|
|
51
|
+
/** Fetch the file from the repository's latest GitHub release at build time. */
|
|
52
|
+
release?: PreloadReleaseSource;
|
|
53
|
+
}
|
|
54
|
+
/** One entry of the ordered site-level preload list. */
|
|
55
|
+
export type PreloadEntry = string | NamedPreloadEntry | UrlPreloadEntry;
|
|
56
|
+
/** The config the build-time plugin injects and the browser runtime reads. */
|
|
57
|
+
export interface SiteRuntimeConfig {
|
|
58
|
+
/** Let `LOAD` accept extensions without a valid signature; a site-wide opt-in. */
|
|
59
|
+
allowUnsignedExtensions?: boolean;
|
|
60
|
+
/** Ordered: every entry loads, one after another, before the instance is `ready`. */
|
|
61
|
+
preload: PreloadEntry[];
|
|
62
|
+
}
|
|
63
|
+
/** True for the only absolute URL form a preload may carry. */
|
|
64
|
+
export declare function isAbsoluteHttpUrl(value: string): boolean;
|
|
65
|
+
/**
|
|
66
|
+
* The entry-symbol base name of a served extension file: the text before the
|
|
67
|
+
* first dot in its last path segment. DuckDB loads a direct file by that name
|
|
68
|
+
* (`duckfn.duckdb_extension.wasm` -> `duckfn` -> `duckfn_init_c_api`), so a
|
|
69
|
+
* file still carrying a platform suffix cannot be loaded as-is.
|
|
70
|
+
*/
|
|
71
|
+
export declare function extensionBaseName(url: string): string;
|
|
72
|
+
/**
|
|
73
|
+
* Validates and normalises one preload entry, dropping unknown keys and
|
|
74
|
+
* lower-casing repository keywords; throws with a readable message on
|
|
75
|
+
* anything that could not be turned into a safe `LOAD` / `INSTALL` statement
|
|
76
|
+
* or a static file path.
|
|
77
|
+
*/
|
|
78
|
+
export declare function normalizePreloadEntry(entry: unknown): PreloadEntry;
|
|
79
|
+
/** Validates the injected config envelope and normalises every preload entry. */
|
|
80
|
+
export declare function parseSiteRuntimeConfig(value: unknown): SiteRuntimeConfig;
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Adds a collapse/expand control to the desktop table of contents.
|
|
3
|
+
*
|
|
4
|
+
* Docusaurus only has `themeConfig.docs.sidebar.hideable` for the left sidebar;
|
|
5
|
+
* the right-hand TOC has no such option (`themeConfig.tableOfContents` only
|
|
6
|
+
* accepts `minHeadingLevel` / `maxHeadingLevel`). So the button is injected here
|
|
7
|
+
* and the layout is switched by the `toc-collapsed` class on `<body>`. The
|
|
8
|
+
* matching CSS lives in `TocToggle.css`, next to this file.
|
|
9
|
+
*
|
|
10
|
+
* This is the class-ified form of the original TOC glue: the button and TOC
|
|
11
|
+
* references are held in fields instead of being looked up with
|
|
12
|
+
* `document.querySelector` on every update, and all module-level mutable state
|
|
13
|
+
* now lives inside {@link TocToggle}. `client.ts` next to this file is the thin
|
|
14
|
+
* Docusaurus glue that the `toc-toggle/plugin` entry injects into a site.
|
|
15
|
+
*/
|
|
16
|
+
export interface TocToggleLabels {
|
|
17
|
+
hide: string;
|
|
18
|
+
show: string;
|
|
19
|
+
}
|
|
20
|
+
export interface TocToggleOptions {
|
|
21
|
+
/** Keyed by a lower-cased html-lang prefix; falls back to `en`. */
|
|
22
|
+
labels?: Record<string, TocToggleLabels>;
|
|
23
|
+
storageKey?: string;
|
|
24
|
+
}
|
|
25
|
+
export declare class TocToggle {
|
|
26
|
+
#private;
|
|
27
|
+
constructor(options?: TocToggleOptions);
|
|
28
|
+
/**
|
|
29
|
+
* Reconciles the button with the current page. The TOC is rendered by React,
|
|
30
|
+
* so it only exists on pages with headings and only on wide viewports; the
|
|
31
|
+
* button may also have been discarded by a re-render, so it is rebuilt when
|
|
32
|
+
* missing.
|
|
33
|
+
*/
|
|
34
|
+
refresh(): void;
|
|
35
|
+
/**
|
|
36
|
+
* Reads the stored preference, applies it before React renders (so a collapsed
|
|
37
|
+
* TOC never flashes open) and wires up the viewport listener.
|
|
38
|
+
*/
|
|
39
|
+
init(): void;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Builds a {@link TocToggle}. `client.ts` — the glue the `toc-toggle/plugin`
|
|
43
|
+
* entry injects — calls `init()` once (behind a `typeof window` guard) and
|
|
44
|
+
* exports `onRouteDidUpdate` bound to `refresh()`.
|
|
45
|
+
*/
|
|
46
|
+
export declare function createTocToggle(options?: TocToggleOptions): TocToggle;
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
//#region src/toc-toggle/TocToggle.ts
|
|
2
|
+
var e = {
|
|
3
|
+
en: {
|
|
4
|
+
hide: "Collapse table of contents",
|
|
5
|
+
show: "Expand table of contents"
|
|
6
|
+
},
|
|
7
|
+
"zh-hans": {
|
|
8
|
+
hide: "收起目录",
|
|
9
|
+
show: "展开目录"
|
|
10
|
+
}
|
|
11
|
+
}, t = class {
|
|
12
|
+
#e;
|
|
13
|
+
#t;
|
|
14
|
+
#n = !1;
|
|
15
|
+
#r = null;
|
|
16
|
+
constructor(t = {}) {
|
|
17
|
+
this.#e = t.labels ?? e, this.#t = t.storageKey ?? "duckfn:toc-collapsed";
|
|
18
|
+
}
|
|
19
|
+
#i() {
|
|
20
|
+
let t = (document.documentElement.getAttribute("lang") ?? "en").toLowerCase();
|
|
21
|
+
return this.#e[t] ?? this.#e.en ?? e.en;
|
|
22
|
+
}
|
|
23
|
+
#a() {
|
|
24
|
+
try {
|
|
25
|
+
return window.localStorage.getItem(this.#t) === "true";
|
|
26
|
+
} catch {
|
|
27
|
+
return !1;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
#o(e) {
|
|
31
|
+
try {
|
|
32
|
+
window.localStorage.setItem(this.#t, String(e));
|
|
33
|
+
} catch {}
|
|
34
|
+
}
|
|
35
|
+
#s() {
|
|
36
|
+
if (!this.#r) return;
|
|
37
|
+
let { hide: e, show: t } = this.#i(), n = this.#n ? t : e;
|
|
38
|
+
this.#r.setAttribute("aria-label", n), this.#r.setAttribute("title", n), this.#r.setAttribute("aria-expanded", String(!this.#n));
|
|
39
|
+
}
|
|
40
|
+
#c(e) {
|
|
41
|
+
e.id ||= "doc-toc";
|
|
42
|
+
let t = document.createElement("button");
|
|
43
|
+
return t.type = "button", t.className = "clean-btn toc-toggle", t.setAttribute("aria-controls", e.id), t.addEventListener("click", () => {
|
|
44
|
+
this.#n = !this.#n, this.#o(this.#n), this.#u();
|
|
45
|
+
}), t;
|
|
46
|
+
}
|
|
47
|
+
#l(e) {
|
|
48
|
+
document.body?.classList.toggle("toc-collapsed", e);
|
|
49
|
+
}
|
|
50
|
+
#u() {
|
|
51
|
+
this.#l(this.#n), this.#s();
|
|
52
|
+
}
|
|
53
|
+
refresh() {
|
|
54
|
+
let e = document.querySelector(".theme-doc-toc-desktop");
|
|
55
|
+
if (this.#r && !this.#r.isConnected && (this.#r = null), !e) {
|
|
56
|
+
this.#r?.remove(), this.#r = null, this.#l(!1);
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
e.parentElement?.classList.add("toc-column"), this.#r || (this.#r = this.#c(e), e.parentElement?.insertBefore(this.#r, e)), this.#u();
|
|
60
|
+
}
|
|
61
|
+
init() {
|
|
62
|
+
this.#n = this.#a(), this.#l(this.#n), window.matchMedia("(min-width: 997px)").addEventListener("change", () => window.setTimeout(() => this.refresh(), 0));
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
function n(e) {
|
|
66
|
+
return new t(e);
|
|
67
|
+
}
|
|
68
|
+
//#endregion
|
|
69
|
+
export { t as TocToggle, n as createTocToggle };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function onRouteDidUpdate(): void;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `duckfn-docs-kit/toc-toggle/plugin` — wires the kit's TOC collapse control
|
|
3
|
+
* into a Docusaurus site.
|
|
4
|
+
*
|
|
5
|
+
* The button and its behaviour live in `toc-toggle/TocToggle` +
|
|
6
|
+
* `TocToggle.css`; what a site used to hand-write as a client module is now
|
|
7
|
+
* injected by this plugin, so a site's own `clientModules` only carries client
|
|
8
|
+
* code the site itself owns.
|
|
9
|
+
*
|
|
10
|
+
* This is Node-side build code: it must not import any browser module, and the
|
|
11
|
+
* browser side must not import this file (the glue it injects is
|
|
12
|
+
* `./client.ts`, resolved through the package exports map).
|
|
13
|
+
*/
|
|
14
|
+
/**
|
|
15
|
+
* The slice of Docusaurus' plugin API this module touches, typed structurally
|
|
16
|
+
* (like `sql/extensions.ts`) so the kit keeps zero Docusaurus dependencies.
|
|
17
|
+
*/
|
|
18
|
+
export interface DfkTocToggleContext {
|
|
19
|
+
siteDir: string;
|
|
20
|
+
}
|
|
21
|
+
export interface DfkTocTogglePlugin {
|
|
22
|
+
name: string;
|
|
23
|
+
getClientModules(): string[];
|
|
24
|
+
}
|
|
25
|
+
/** The plugin module Docusaurus calls with its `LoadContext` and options. */
|
|
26
|
+
export type DfkTocTogglePluginModule = (context: DfkTocToggleContext) => DfkTocTogglePlugin;
|
|
27
|
+
/**
|
|
28
|
+
* Builds the plugin. Usage in `docusaurus.config.ts`:
|
|
29
|
+
*
|
|
30
|
+
* ```ts
|
|
31
|
+
* import {dfkTocToggle} from 'duckfn-docs-kit/toc-toggle/plugin';
|
|
32
|
+
*
|
|
33
|
+
* plugins: [dfkTocToggle()],
|
|
34
|
+
* ```
|
|
35
|
+
*/
|
|
36
|
+
export declare function dfkTocToggle(): DfkTocTogglePluginModule;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { createRequire as e } from "node:module";
|
|
2
|
+
import t from "node:path";
|
|
3
|
+
//#region src/toc-toggle/plugin.ts
|
|
4
|
+
function n() {
|
|
5
|
+
return (n) => ({
|
|
6
|
+
name: "dfk-toc-toggle",
|
|
7
|
+
getClientModules() {
|
|
8
|
+
return [e(t.join(n.siteDir, "package.json")).resolve("duckfn-docs-kit/toc-toggle/client")];
|
|
9
|
+
}
|
|
10
|
+
});
|
|
11
|
+
}
|
|
12
|
+
//#endregion
|
|
13
|
+
export { n as dfkTocToggle };
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Value types the home-page web components accept.
|
|
3
|
+
*
|
|
4
|
+
* Every string is already resolved for the *current* locale: a custom element
|
|
5
|
+
* cannot render Docusaurus' React `<Translate>`, so the docs site resolves the
|
|
6
|
+
* copy with the imperative `translate()` API and hands plain strings to the
|
|
7
|
+
* components' `set*` methods. Internal `href`s are baseUrl-resolved by the
|
|
8
|
+
* caller (`useBaseUrl`), because a raw `<a href>` inside a custom element gets
|
|
9
|
+
* no Docusaurus prefixing.
|
|
10
|
+
*
|
|
11
|
+
* Icon fields are Iconify icon *names* (e.g. `lucide:arrow-right`), rendered by
|
|
12
|
+
* the official `<iconify-icon>` web component, which fetches the glyph from the
|
|
13
|
+
* public Iconify API. This package ships no icon data of its own.
|
|
14
|
+
*/
|
|
15
|
+
/** A hero call-to-action. */
|
|
16
|
+
export interface HeroLink {
|
|
17
|
+
label: string;
|
|
18
|
+
href: string;
|
|
19
|
+
}
|
|
20
|
+
/** The secondary hero action (the GitHub button), which also carries a glyph. */
|
|
21
|
+
export interface HeroAction extends HeroLink {
|
|
22
|
+
/** Iconify icon name, e.g. `simple-icons:github`. */
|
|
23
|
+
icon: string;
|
|
24
|
+
}
|
|
25
|
+
/** A shields.io-style badge in the hero row. Always an external link. */
|
|
26
|
+
export interface HeroBadge {
|
|
27
|
+
href: string;
|
|
28
|
+
src: string;
|
|
29
|
+
alt: string;
|
|
30
|
+
}
|
|
31
|
+
export interface FeatureItem {
|
|
32
|
+
/** Iconify icon name, e.g. `lucide:sparkles`. */
|
|
33
|
+
icon: string;
|
|
34
|
+
title: string;
|
|
35
|
+
details: string;
|
|
36
|
+
}
|
|
37
|
+
export interface NextStepItem {
|
|
38
|
+
/** Already baseUrl-resolved internal path. */
|
|
39
|
+
href: string;
|
|
40
|
+
title: string;
|
|
41
|
+
details: string;
|
|
42
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "duckfn-docs-kit",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Shared Docusaurus building blocks (TOC toggle, home-page web components, brand tokens, remark version placeholder) for duckfn-family extension docs sites.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"keywords": [
|
|
7
|
+
"duckdb",
|
|
8
|
+
"duckdb-wasm",
|
|
9
|
+
"docusaurus",
|
|
10
|
+
"documentation",
|
|
11
|
+
"web-components",
|
|
12
|
+
"remark",
|
|
13
|
+
"sql"
|
|
14
|
+
],
|
|
15
|
+
"homepage": "https://shijianjs.github.io/duckfn/docs/docs-kit",
|
|
16
|
+
"repository": {
|
|
17
|
+
"type": "git",
|
|
18
|
+
"url": "git+https://github.com/shijianjs/duckfn.git",
|
|
19
|
+
"directory": "duckfn-docs-kit"
|
|
20
|
+
},
|
|
21
|
+
"bugs": {
|
|
22
|
+
"url": "https://github.com/shijianjs/duckfn/issues"
|
|
23
|
+
},
|
|
24
|
+
"type": "module",
|
|
25
|
+
"main": "./dist/index.js",
|
|
26
|
+
"types": "./dist/index.d.ts",
|
|
27
|
+
"exports": {
|
|
28
|
+
".": {
|
|
29
|
+
"types": "./dist/index.d.ts",
|
|
30
|
+
"default": "./dist/index.js"
|
|
31
|
+
},
|
|
32
|
+
"./src/*": "./src/*",
|
|
33
|
+
"./*": {
|
|
34
|
+
"types": "./dist/*.d.ts",
|
|
35
|
+
"default": "./dist/*.js"
|
|
36
|
+
}
|
|
37
|
+
},
|
|
38
|
+
"files": [
|
|
39
|
+
"dist",
|
|
40
|
+
"src",
|
|
41
|
+
"AGENTS.md"
|
|
42
|
+
],
|
|
43
|
+
"scripts": {
|
|
44
|
+
"build": "vite build && tsc -p tsconfig.build.json",
|
|
45
|
+
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
46
|
+
"clean": "node -e \"require('fs').rmSync('dist',{recursive:true,force:true})\"",
|
|
47
|
+
"prepack": "npm run build"
|
|
48
|
+
},
|
|
49
|
+
"publishConfig": {
|
|
50
|
+
"access": "public"
|
|
51
|
+
},
|
|
52
|
+
"dependencies": {
|
|
53
|
+
"@codemirror/commands": "^6.11.1",
|
|
54
|
+
"@codemirror/lang-sql": "^6.10.0",
|
|
55
|
+
"@codemirror/state": "^6.7.6",
|
|
56
|
+
"@codemirror/view": "^6.43.13",
|
|
57
|
+
"@duckdb/duckdb-wasm": "1.33.1-dev64.0",
|
|
58
|
+
"@visactor/vtable": "^1.26.8",
|
|
59
|
+
"codemirror": "^6.0.2",
|
|
60
|
+
"iconify-icon": "^3.0.3",
|
|
61
|
+
"sql-formatter": "^15.9.0"
|
|
62
|
+
},
|
|
63
|
+
"devDependencies": {
|
|
64
|
+
"@types/node": "^26.6.3",
|
|
65
|
+
"@types/react": "^19.3.0",
|
|
66
|
+
"typescript": "~7.0.2",
|
|
67
|
+
"unified": "^11.0.5",
|
|
68
|
+
"vite": "^8.3.1"
|
|
69
|
+
},
|
|
70
|
+
"engines": {
|
|
71
|
+
"node": ">=20.0"
|
|
72
|
+
}
|
|
73
|
+
}
|