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,172 @@
|
|
|
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
|
+
|
|
17
|
+
const DEFAULT_STORAGE_KEY = 'duckfn:toc-collapsed';
|
|
18
|
+
const STATE_CLASS = 'toc-collapsed';
|
|
19
|
+
const BUTTON_CLASS = 'toc-toggle';
|
|
20
|
+
const COLUMN_CLASS = 'toc-column';
|
|
21
|
+
const TOC_ID = 'doc-toc';
|
|
22
|
+
const TOC_SELECTOR = '.theme-doc-toc-desktop';
|
|
23
|
+
const DESKTOP_QUERY = '(min-width: 997px)';
|
|
24
|
+
|
|
25
|
+
export interface TocToggleLabels {
|
|
26
|
+
hide: string;
|
|
27
|
+
show: string;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export interface TocToggleOptions {
|
|
31
|
+
/** Keyed by a lower-cased html-lang prefix; falls back to `en`. */
|
|
32
|
+
labels?: Record<string, TocToggleLabels>;
|
|
33
|
+
storageKey?: string;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const DEFAULT_LABELS: Record<string, TocToggleLabels> = {
|
|
37
|
+
en: {hide: 'Collapse table of contents', show: 'Expand table of contents'},
|
|
38
|
+
'zh-hans': {hide: '收起目录', show: '展开目录'},
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
export class TocToggle {
|
|
42
|
+
readonly #labels: Record<string, TocToggleLabels>;
|
|
43
|
+
readonly #storageKey: string;
|
|
44
|
+
#collapsed = false;
|
|
45
|
+
#button: HTMLButtonElement | null = null;
|
|
46
|
+
|
|
47
|
+
constructor(options: TocToggleOptions = {}) {
|
|
48
|
+
this.#labels = options.labels ?? DEFAULT_LABELS;
|
|
49
|
+
this.#storageKey = options.storageKey ?? DEFAULT_STORAGE_KEY;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
#currentLabels(): TocToggleLabels {
|
|
53
|
+
const lang = (document.documentElement.getAttribute('lang') ?? 'en').toLowerCase();
|
|
54
|
+
return this.#labels[lang] ?? this.#labels.en ?? DEFAULT_LABELS.en;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
#readPreference(): boolean {
|
|
58
|
+
try {
|
|
59
|
+
return window.localStorage.getItem(this.#storageKey) === 'true';
|
|
60
|
+
} catch {
|
|
61
|
+
return false;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
#storePreference(value: boolean): void {
|
|
66
|
+
try {
|
|
67
|
+
window.localStorage.setItem(this.#storageKey, String(value));
|
|
68
|
+
} catch {
|
|
69
|
+
// Storage may be unavailable (private mode, blocked cookies). The toggle
|
|
70
|
+
// still works for the current page; the choice is just not remembered.
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
#updateLabel(): void {
|
|
75
|
+
if (!this.#button) {
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
const {hide, show} = this.#currentLabels();
|
|
79
|
+
const label = this.#collapsed ? show : hide;
|
|
80
|
+
this.#button.setAttribute('aria-label', label);
|
|
81
|
+
this.#button.setAttribute('title', label);
|
|
82
|
+
this.#button.setAttribute('aria-expanded', String(!this.#collapsed));
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
#createButton(toc: Element): HTMLButtonElement {
|
|
86
|
+
if (!toc.id) {
|
|
87
|
+
toc.id = TOC_ID;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const button = document.createElement('button');
|
|
91
|
+
button.type = 'button';
|
|
92
|
+
button.className = `clean-btn ${BUTTON_CLASS}`;
|
|
93
|
+
button.setAttribute('aria-controls', toc.id);
|
|
94
|
+
button.addEventListener('click', () => {
|
|
95
|
+
this.#collapsed = !this.#collapsed;
|
|
96
|
+
this.#storePreference(this.#collapsed);
|
|
97
|
+
this.#apply();
|
|
98
|
+
});
|
|
99
|
+
return button;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* The state class goes on `<body>`, not on `<html>`: Docusaurus rewrites the
|
|
104
|
+
* whole `class` attribute of `<html>` on every route, which would drop the
|
|
105
|
+
* class immediately after it is set. `document.body` is left alone.
|
|
106
|
+
*/
|
|
107
|
+
#setStateClass(value: boolean): void {
|
|
108
|
+
document.body?.classList.toggle(STATE_CLASS, value);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
#apply(): void {
|
|
112
|
+
this.#setStateClass(this.#collapsed);
|
|
113
|
+
this.#updateLabel();
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Reconciles the button with the current page. The TOC is rendered by React,
|
|
118
|
+
* so it only exists on pages with headings and only on wide viewports; the
|
|
119
|
+
* button may also have been discarded by a re-render, so it is rebuilt when
|
|
120
|
+
* missing.
|
|
121
|
+
*/
|
|
122
|
+
refresh(): void {
|
|
123
|
+
const toc = document.querySelector(TOC_SELECTOR);
|
|
124
|
+
// The field may point at a button React has since removed from the DOM.
|
|
125
|
+
if (this.#button && !this.#button.isConnected) {
|
|
126
|
+
this.#button = null;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
if (!toc) {
|
|
130
|
+
// No desktop TOC on this page: drop the button and the layout class, so
|
|
131
|
+
// the article always uses the full width where there is nothing to
|
|
132
|
+
// collapse.
|
|
133
|
+
this.#button?.remove();
|
|
134
|
+
this.#button = null;
|
|
135
|
+
this.#setStateClass(false);
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
toc.parentElement?.classList.add(COLUMN_CLASS);
|
|
140
|
+
|
|
141
|
+
if (!this.#button) {
|
|
142
|
+
this.#button = this.#createButton(toc);
|
|
143
|
+
toc.parentElement?.insertBefore(this.#button, toc);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
this.#apply();
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Reads the stored preference, applies it before React renders (so a collapsed
|
|
151
|
+
* TOC never flashes open) and wires up the viewport listener.
|
|
152
|
+
*/
|
|
153
|
+
init(): void {
|
|
154
|
+
this.#collapsed = this.#readPreference();
|
|
155
|
+
this.#setStateClass(this.#collapsed);
|
|
156
|
+
|
|
157
|
+
// React drops the desktop TOC when the viewport shrinks; re-check once it
|
|
158
|
+
// has re-rendered.
|
|
159
|
+
window
|
|
160
|
+
.matchMedia(DESKTOP_QUERY)
|
|
161
|
+
.addEventListener('change', () => window.setTimeout(() => this.refresh(), 0));
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Builds a {@link TocToggle}. `client.ts` — the glue the `toc-toggle/plugin`
|
|
167
|
+
* entry injects — calls `init()` once (behind a `typeof window` guard) and
|
|
168
|
+
* exports `onRouteDidUpdate` bound to `refresh()`.
|
|
169
|
+
*/
|
|
170
|
+
export function createTocToggle(options?: TocToggleOptions): TocToggle {
|
|
171
|
+
return new TocToggle(options);
|
|
172
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The TOC toggle's browser glue, injected by `toc-toggle/plugin` so a
|
|
3
|
+
* consuming site does not keep a client module of its own. The behaviour lives
|
|
4
|
+
* in `toc-toggle/TocToggle`; this module is only the Docusaurus lifecycle
|
|
5
|
+
* glue: initialise once in the browser and refresh after every route change.
|
|
6
|
+
*
|
|
7
|
+
* Docusaurus evaluates this module in its Node prerender pass too, where there
|
|
8
|
+
* is no DOM to touch — hence the guard.
|
|
9
|
+
*/
|
|
10
|
+
import {createTocToggle} from './TocToggle';
|
|
11
|
+
|
|
12
|
+
const toggle = createTocToggle();
|
|
13
|
+
|
|
14
|
+
if (typeof window !== 'undefined') {
|
|
15
|
+
toggle.init();
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function onRouteDidUpdate(): void {
|
|
19
|
+
toggle.refresh();
|
|
20
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import {createRequire} from 'node:module';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* `duckfn-docs-kit/toc-toggle/plugin` — wires the kit's TOC collapse control
|
|
6
|
+
* into a Docusaurus site.
|
|
7
|
+
*
|
|
8
|
+
* The button and its behaviour live in `toc-toggle/TocToggle` +
|
|
9
|
+
* `TocToggle.css`; what a site used to hand-write as a client module is now
|
|
10
|
+
* injected by this plugin, so a site's own `clientModules` only carries client
|
|
11
|
+
* code the site itself owns.
|
|
12
|
+
*
|
|
13
|
+
* This is Node-side build code: it must not import any browser module, and the
|
|
14
|
+
* browser side must not import this file (the glue it injects is
|
|
15
|
+
* `./client.ts`, resolved through the package exports map).
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* The slice of Docusaurus' plugin API this module touches, typed structurally
|
|
20
|
+
* (like `sql/extensions.ts`) so the kit keeps zero Docusaurus dependencies.
|
|
21
|
+
*/
|
|
22
|
+
export interface DfkTocToggleContext {
|
|
23
|
+
siteDir: string;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export interface DfkTocTogglePlugin {
|
|
27
|
+
name: string;
|
|
28
|
+
getClientModules(): string[];
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** The plugin module Docusaurus calls with its `LoadContext` and options. */
|
|
32
|
+
export type DfkTocTogglePluginModule = (context: DfkTocToggleContext) => DfkTocTogglePlugin;
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Builds the plugin. Usage in `docusaurus.config.ts`:
|
|
36
|
+
*
|
|
37
|
+
* ```ts
|
|
38
|
+
* import {dfkTocToggle} from 'duckfn-docs-kit/toc-toggle/plugin';
|
|
39
|
+
*
|
|
40
|
+
* plugins: [dfkTocToggle()],
|
|
41
|
+
* ```
|
|
42
|
+
*/
|
|
43
|
+
export function dfkTocToggle(): DfkTocTogglePluginModule {
|
|
44
|
+
return (context) => ({
|
|
45
|
+
name: 'dfk-toc-toggle',
|
|
46
|
+
getClientModules() {
|
|
47
|
+
// Resolved from the consuming site, so the config bundler cannot break
|
|
48
|
+
// the lookup; the exports map (`./*` -> dist) keeps the subpath valid
|
|
49
|
+
// even if the entry moves.
|
|
50
|
+
const requireFromSite = createRequire(path.join(context.siteDir, 'package.json'));
|
|
51
|
+
return [requireFromSite.resolve('duckfn-docs-kit/toc-toggle/client')];
|
|
52
|
+
},
|
|
53
|
+
});
|
|
54
|
+
}
|
package/src/types.ts
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
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
|
+
|
|
16
|
+
/** A hero call-to-action. */
|
|
17
|
+
export interface HeroLink {
|
|
18
|
+
label: string;
|
|
19
|
+
href: string;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/** The secondary hero action (the GitHub button), which also carries a glyph. */
|
|
23
|
+
export interface HeroAction extends HeroLink {
|
|
24
|
+
/** Iconify icon name, e.g. `simple-icons:github`. */
|
|
25
|
+
icon: string;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/** A shields.io-style badge in the hero row. Always an external link. */
|
|
29
|
+
export interface HeroBadge {
|
|
30
|
+
href: string;
|
|
31
|
+
src: string;
|
|
32
|
+
alt: string;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export interface FeatureItem {
|
|
36
|
+
/** Iconify icon name, e.g. `lucide:sparkles`. */
|
|
37
|
+
icon: string;
|
|
38
|
+
title: string;
|
|
39
|
+
details: string;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export interface NextStepItem {
|
|
43
|
+
/** Already baseUrl-resolved internal path. */
|
|
44
|
+
href: string;
|
|
45
|
+
title: string;
|
|
46
|
+
details: string;
|
|
47
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
// Ambient module type for the Vite `?inline` CSS import in `styles.ts`. Vite
|
|
2
|
+
// injects this file into the build automatically; `tsc` picks it up through the
|
|
3
|
+
// `include: ["src"]` glob, which is what keeps `npm run typecheck` and the
|
|
4
|
+
// emitted `.d.ts` files working without a `vite/client` lib reference.
|
|
5
|
+
declare module '*.css?inline' {
|
|
6
|
+
const css: string;
|
|
7
|
+
export default css;
|
|
8
|
+
}
|