@writedocs/generator 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/LICENSE +15 -0
- package/README.md +17 -0
- package/astro.config.mjs +419 -0
- package/bin/writedocs.js +73 -0
- package/package.json +79 -0
- package/src/assets/wd_watermark.png +0 -0
- package/src/assets/wd_watermark_dark.png +0 -0
- package/src/cli/build-auth.js +53 -0
- package/src/cli/build.js +40 -0
- package/src/cli/dev.js +12 -0
- package/src/cli/generate-api-pages.js +359 -0
- package/src/cli/init.js +81 -0
- package/src/cli/preflight.js +40 -0
- package/src/cli/run-astro.js +57 -0
- package/src/cli/run-pagefind.js +66 -0
- package/src/cli/write-redirects-file.js +80 -0
- package/src/components/Accordion.astro +164 -0
- package/src/components/AccordionGroup.astro +40 -0
- package/src/components/ApiLangSelect.astro +168 -0
- package/src/components/ApiPlayground.astro +281 -0
- package/src/components/ApiReferencePanel.astro +1754 -0
- package/src/components/ApiSchemaField.astro +54 -0
- package/src/components/AppIcon.astro +32 -0
- package/src/components/Badge.astro +128 -0
- package/src/components/Callout.astro +168 -0
- package/src/components/Card.astro +136 -0
- package/src/components/CardGroup.astro +20 -0
- package/src/components/CodeGroup.astro +184 -0
- package/src/components/CopyPageMenu.astro +246 -0
- package/src/components/Danger.astro +12 -0
- package/src/components/Expandable.astro +126 -0
- package/src/components/Frame.astro +102 -0
- package/src/components/Hint.astro +99 -0
- package/src/components/Icon.astro +70 -0
- package/src/components/Image.astro +147 -0
- package/src/components/Info.astro +12 -0
- package/src/components/Note.astro +12 -0
- package/src/components/Parameter.astro +119 -0
- package/src/components/RequestExample.astro +33 -0
- package/src/components/ResponseExample.astro +19 -0
- package/src/components/Searchbar.astro +117 -0
- package/src/components/Step.astro +10 -0
- package/src/components/Steps.astro +32 -0
- package/src/components/Tab.astro +9 -0
- package/src/components/Tabs.astro +52 -0
- package/src/components/Tip.astro +12 -0
- package/src/components/Video.astro +135 -0
- package/src/components/Warning.astro +12 -0
- package/src/components/index.ts +48 -0
- package/src/content.config.ts +223 -0
- package/src/layout/BaseLayout.astro +750 -0
- package/src/layout/components/AnalyticsScripts.astro +77 -0
- package/src/layout/components/AskAiWidget.astro +37 -0
- package/src/layout/components/Breadcrumbs.astro +97 -0
- package/src/layout/components/ImageZoom.astro +19 -0
- package/src/layout/components/MobileMenu.astro +200 -0
- package/src/layout/components/NavTree.astro +351 -0
- package/src/layout/components/SearchModal.astro +42 -0
- package/src/layout/components/Sidebar.astro +122 -0
- package/src/layout/components/SiteFooter.astro +85 -0
- package/src/layout/components/TableOfContents.astro +117 -0
- package/src/layout/components/TopBar.astro +311 -0
- package/src/layout/styles/banner.css +44 -0
- package/src/layout/styles/base.css +234 -0
- package/src/layout/styles/dropdown.css +133 -0
- package/src/layout/styles/footer.css +108 -0
- package/src/layout/styles/image-zoom.css +50 -0
- package/src/layout/styles/mobile-menu.css +258 -0
- package/src/layout/styles/search-modal.css +122 -0
- package/src/layout/styles/topbar.css +437 -0
- package/src/lib/config.ts +2131 -0
- package/src/lib/mdx-auto-hydrate.js +70 -0
- package/src/lib/mdx-inject-builtins.js +87 -0
- package/src/lib/mdx-substitute-variables.js +66 -0
- package/src/lib/mdx-title-anchor-ids.js +84 -0
- package/src/lib/mermaid-rehype.js +72 -0
- package/src/lib/openapi-render.ts +479 -0
- package/src/lib/shiki-code-block.js +102 -0
- package/src/lib/shiki-copy-button.js +45 -0
- package/src/lib/styles-asset-integration.js +210 -0
- package/src/lib/writedocs-temp-dir.js +93 -0
- package/src/pages/404.astro +62 -0
- package/src/pages/[...slug].astro +1270 -0
- package/src/pages/[...slug].md.ts +78 -0
- package/src/pages/llms-full.txt.ts +71 -0
- package/src/pages/llms.txt.ts +141 -0
- package/src/scripts/banner.ts +20 -0
- package/src/scripts/dropdowns.ts +61 -0
- package/src/scripts/image-zoom.ts +66 -0
- package/src/scripts/mobile-menu.ts +55 -0
- package/src/scripts/search.ts +155 -0
- package/src/scripts/sidebar-scroll.ts +65 -0
- package/src/scripts/theme-toggle.ts +35 -0
- package/src/scripts/topbar-offset.ts +141 -0
- package/src/styles/global.css +18 -0
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
// ClientRouter swaps <body> (and everything in it) on every navigation -
|
|
2
|
+
// [...slug].astro re-renders Sidebar.astro/NavTree.astro fresh per page,
|
|
3
|
+
// since the active-link highlight and each collapsible group's open/closed
|
|
4
|
+
// state are computed server-side from the *new* URL (see NavTree.astro's
|
|
5
|
+
// own comment on `open = isActive || navTreeContainsSlug(...)`). That's
|
|
6
|
+
// exactly the behavior a reader wants for highlighting/auto-expanding the
|
|
7
|
+
// right branch - but it also means .wd-sidebar is a brand-new DOM node on
|
|
8
|
+
// every navigation, with its own independent scroll position (it's
|
|
9
|
+
// `overflow-y: auto`, not the window) starting back at 0, same as a full
|
|
10
|
+
// page reload would. For a page nested deep in a long nav tree, that reads
|
|
11
|
+
// as the sidebar "jumping back to the top" on every click - disorienting,
|
|
12
|
+
// since the reader loses their place in the tree they were navigating.
|
|
13
|
+
// This module carries the old sidebar's scroll offset across the swap and
|
|
14
|
+
// re-applies it to the new one, without touching anything about how the
|
|
15
|
+
// highlight/expand state itself is computed (that server-rendered-per-page
|
|
16
|
+
// behavior stays exactly as it is - only the scroll position is preserved).
|
|
17
|
+
//
|
|
18
|
+
// A module-scoped variable (not sessionStorage) is enough: this script's
|
|
19
|
+
// own module graph is loaded once and never re-evaluated by a ClientRouter
|
|
20
|
+
// swap (only <body>'s content is replaced), so the value naturally
|
|
21
|
+
// survives from one navigation to the next without needing to serialize it
|
|
22
|
+
// anywhere. It deliberately does NOT survive a real full-page reload
|
|
23
|
+
// (Cmd+R/F5) - resetting to the top on an actual reload is ordinary,
|
|
24
|
+
// expected browser behavior, not something this needs to fight.
|
|
25
|
+
let savedScrollTop: number | null = null;
|
|
26
|
+
|
|
27
|
+
/** The sidebar rendered inline in the page (inside `.wd-sidebar-col`, for
|
|
28
|
+
* desktop widths) and the identical copy reused inside MobileMenu.astro's
|
|
29
|
+
* own drawer both carry the same `.wd-sidebar` class and both always
|
|
30
|
+
* exist in the DOM at once (see MobileMenu.astro's own comment) - a media
|
|
31
|
+
* query just hides whichever one isn't the current layout's. Picking
|
|
32
|
+
* "whichever one actually has a real layout box" is enough to find the
|
|
33
|
+
* one the reader could actually see and scroll, without needing to ask
|
|
34
|
+
* which layout mode is active. Returns null on a page with no sidebar at
|
|
35
|
+
* all (`mode: custom`/`blank`, see BaseLayout.astro) - both copies are
|
|
36
|
+
* simply absent there. */
|
|
37
|
+
function visibleSidebar(): HTMLElement | null {
|
|
38
|
+
const candidates = document.querySelectorAll<HTMLElement>('.wd-sidebar');
|
|
39
|
+
for (const el of candidates) {
|
|
40
|
+
if (el.offsetWidth > 0 || el.offsetHeight > 0) return el;
|
|
41
|
+
}
|
|
42
|
+
return null;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** Reads the current scroll offset right before ClientRouter tears down
|
|
46
|
+
* the old page's DOM - `astro:before-swap` is the last moment the old
|
|
47
|
+
* `.wd-sidebar` is still attached and its `scrollTop` still means
|
|
48
|
+
* anything. */
|
|
49
|
+
export function saveSidebarScroll() {
|
|
50
|
+
const sidebar = visibleSidebar();
|
|
51
|
+
savedScrollTop = sidebar ? sidebar.scrollTop : null;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/** Re-applies the saved offset onto the new page's own `.wd-sidebar`.
|
|
55
|
+
* Bound to `astro:after-swap` (fires synchronously right after the swap,
|
|
56
|
+
* before the browser's next paint - see BaseLayout.astro's own
|
|
57
|
+
* `wdApplyStoredPreferences` for the identical timing rationale) rather
|
|
58
|
+
* than the later `astro:page-load`, so the sidebar never visibly flashes
|
|
59
|
+
* at the top before jumping to the preserved position. */
|
|
60
|
+
export function restoreSidebarScroll() {
|
|
61
|
+
if (savedScrollTop === null) return;
|
|
62
|
+
const sidebar = visibleSidebar();
|
|
63
|
+
if (sidebar) sidebar.scrollTop = savedScrollTop;
|
|
64
|
+
savedScrollTop = null;
|
|
65
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
// Wires up every `.wd-theme-toggle` button on the page - not just the
|
|
2
|
+
// first. The real topbar (TopBar.astro) and the mobile drawer's own header
|
|
3
|
+
// (MobileMenu.astro) each render their own copy of this button, so both
|
|
4
|
+
// stay in sync for free: [data-theme] on <html>, not per-button JS state,
|
|
5
|
+
// is what actually drives which icon each button shows - they just need
|
|
6
|
+
// the same click handler, independently guarded (per button, via
|
|
7
|
+
// dataset.wdInit) so a re-run (e.g. astro:page-load on a client-side nav,
|
|
8
|
+
// or simply being imported and called by more than one component) never
|
|
9
|
+
// double-binds any single button.
|
|
10
|
+
export function initThemeToggle(root: ParentNode) {
|
|
11
|
+
const btns = root.querySelectorAll<HTMLButtonElement>('.wd-theme-toggle');
|
|
12
|
+
btns.forEach((btn) => {
|
|
13
|
+
if (btn.dataset.wdInit) return;
|
|
14
|
+
btn.dataset.wdInit = 'true';
|
|
15
|
+
btn.addEventListener('click', () => {
|
|
16
|
+
const next = document.documentElement.dataset.theme === 'dark' ? 'light' : 'dark';
|
|
17
|
+
document.documentElement.dataset.theme = next;
|
|
18
|
+
try {
|
|
19
|
+
localStorage.setItem('wd-theme', next);
|
|
20
|
+
} catch (e) {
|
|
21
|
+
// localStorage unavailable (private browsing, etc.) - theme still
|
|
22
|
+
// applies for the current page, just won't persist.
|
|
23
|
+
}
|
|
24
|
+
// Mermaid diagrams are rendered to static SVG once (baked with
|
|
25
|
+
// whichever theme was active at render time), unlike Shiki code
|
|
26
|
+
// blocks which swap colors live via CSS custom properties - so a
|
|
27
|
+
// theme change needs to trigger an actual re-render, not just a CSS
|
|
28
|
+
// update. [...slug].astro's initMermaid() (article content, not this
|
|
29
|
+
// file) listens for this rather than living here itself, since the
|
|
30
|
+
// diagrams themselves are article content and this file has no
|
|
31
|
+
// reason to know mermaid exists.
|
|
32
|
+
document.dispatchEvent(new CustomEvent('wd:theme-change', { detail: { theme: next } }));
|
|
33
|
+
});
|
|
34
|
+
});
|
|
35
|
+
}
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
// The sticky topbar's actual rendered height varies - one row vs two (the
|
|
2
|
+
// tabs/global-dropdowns row only exists when there's something to put in
|
|
3
|
+
// it - see TopBar.astro's own `showSecondRow`), and `topbar.links`
|
|
4
|
+
// wrapping onto its own row below ~860px (topbar.css). Every place that
|
|
5
|
+
// needs to offset content below this sticky chrome - heading
|
|
6
|
+
// `scroll-margin-top` ([...slug].astro), and the sidebar/TOC/API-
|
|
7
|
+
// reference-panel's own sticky `top`/`max-height` (Sidebar.astro,
|
|
8
|
+
// TableOfContents.astro, ApiReferencePanel.astro) - used to hardcode a
|
|
9
|
+
// single `5rem` guess that only ever matched the simplest case (one
|
|
10
|
+
// topbar row): silently wrong - a heading landing partly under the
|
|
11
|
+
// topbar on an anchor jump, or a sliver of dead space/slight overlap on
|
|
12
|
+
// the sticky columns - the moment a site's topbar grew a second row.
|
|
13
|
+
// This measures the real thing instead and exposes it as
|
|
14
|
+
// `--wd-topbar-offset` on <html>, which every one of those hardcoded
|
|
15
|
+
// values now reads from via `var(--wd-topbar-offset, 5rem)` - `5rem`
|
|
16
|
+
// stays as that var()'s own fallback, for the brief instant before this
|
|
17
|
+
// script's first measurement runs.
|
|
18
|
+
//
|
|
19
|
+
// writedocs.json's dismissible `banner` (BaseLayout.astro, above the topbar)
|
|
20
|
+
// isn't `position: sticky` itself - it scrolls away normally, unlike
|
|
21
|
+
// `.wd-topbar` (`position: sticky; top: 0`) right below it. Measuring
|
|
22
|
+
// just the topbar's own height used to be enough on the (wrong)
|
|
23
|
+
// assumption that nothing reads this offset until scroll has already
|
|
24
|
+
// carried the banner out of view - true for the heading
|
|
25
|
+
// `scroll-margin-top` case (a scroll-into-view only settles once
|
|
26
|
+
// something has actually been scrolled), but not for the sidebar/TOC/
|
|
27
|
+
// API-reference-panel's sticky `max-height` (Sidebar.astro,
|
|
28
|
+
// TableOfContents.astro, ApiReferencePanel.astro), which reads this
|
|
29
|
+
// offset immediately on page load, banner and all - a tall enough column
|
|
30
|
+
// there would overflow the viewport by exactly the banner's own height
|
|
31
|
+
// until the reader scrolled past it. Reading the topbar's own *bottom*
|
|
32
|
+
// edge (`getBoundingClientRect().bottom`) instead of its height fixes
|
|
33
|
+
// this for free, with no separate banner-specific case to maintain: while
|
|
34
|
+
// the banner is still in normal flow above the not-yet-stuck topbar, that
|
|
35
|
+
// bottom edge already equals banner height + topbar height; once scroll
|
|
36
|
+
// has carried the banner away and the topbar is genuinely stuck at
|
|
37
|
+
// `top: 0`, it settles back to exactly the topbar's own height - the
|
|
38
|
+
// same value this used to hardcode from `.height` alone. The only added
|
|
39
|
+
// cost is that this now needs to be re-measured on scroll too, not just
|
|
40
|
+
// resize (below), since it changes continuously while the banner is
|
|
41
|
+
// still scrolling past.
|
|
42
|
+
|
|
43
|
+
function applyOffset(topbar: HTMLElement) {
|
|
44
|
+
let bottom = topbar.getBoundingClientRect().bottom;
|
|
45
|
+
// .wd-shell (BaseLayout.astro) carries its own padding-top - breathing
|
|
46
|
+
// room between the topbar and the three columns below it (base.css).
|
|
47
|
+
// The sidebar/TOC/API-reference-panel's sticky top/max-height (the
|
|
48
|
+
// main reason this function exists - see the file-level comment) needs
|
|
49
|
+
// this folded in too: those columns' *natural*, not-yet-stuck flow
|
|
50
|
+
// position starts at the bottom of that padding, not at the topbar's
|
|
51
|
+
// own bottom edge alone, so a max-height computed from the smaller
|
|
52
|
+
// value left them tall enough to overshoot the viewport by exactly
|
|
53
|
+
// this padding - same shape of bug as the banner one above, just a
|
|
54
|
+
// fixed ~12px instead of a scrolling one. Folding it in here (instead
|
|
55
|
+
// of, say, only in Sidebar/TableOfContents/ApiReferencePanel's own
|
|
56
|
+
// CSS) keeps every reader of this one variable consistent, at the cost
|
|
57
|
+
// of very slightly over-padding the scroll-margin-top use case above -
|
|
58
|
+
// an extra dozen pixels of clearance above a jumped-to heading is
|
|
59
|
+
// unnoticeable; overflowing the viewport is not.
|
|
60
|
+
const shell = document.querySelector<HTMLElement>('.wd-shell');
|
|
61
|
+
if (shell) {
|
|
62
|
+
const shellPaddingTop = parseFloat(getComputedStyle(shell).paddingTop);
|
|
63
|
+
if (!Number.isNaN(shellPaddingTop)) bottom += shellPaddingTop;
|
|
64
|
+
}
|
|
65
|
+
// A zero (or negative, impossible but defensive) reading means the
|
|
66
|
+
// topbar hasn't actually been laid out yet - leave the CSS var()
|
|
67
|
+
// fallback in place rather than pin a bogus 0px offset that would
|
|
68
|
+
// itself need correcting a moment later.
|
|
69
|
+
if (bottom > 0) {
|
|
70
|
+
document.documentElement.style.setProperty('--wd-topbar-offset', `${bottom}px`);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/** Corrects the browser's own initial scroll-to-fragment jump. The
|
|
75
|
+
* browser scrolls a same-page #hash target into view as part of initial
|
|
76
|
+
* navigation - which can happen before this deferred module script ever
|
|
77
|
+
* runs, using whatever `scroll-margin-top` is in the CSS at that moment
|
|
78
|
+
* (the `5rem` fallback, not yet the real measured value once a site's
|
|
79
|
+
* topbar is taller than that). Re-running `scrollIntoView()` here -
|
|
80
|
+
* after `--wd-topbar-offset` is already set correctly - repeats that
|
|
81
|
+
* same browser-native, scroll-margin-aware scroll with the right value,
|
|
82
|
+
* once. `behavior: 'auto'` (this codebase never sets a global
|
|
83
|
+
* `scroll-behavior: smooth` that would turn this into a visible
|
|
84
|
+
* animation) - this corrects a wrong landing spot, it isn't itself a
|
|
85
|
+
* user-facing scroll action. */
|
|
86
|
+
function correctInitialHashScroll() {
|
|
87
|
+
if (!location.hash) return;
|
|
88
|
+
let target: HTMLElement | null;
|
|
89
|
+
try {
|
|
90
|
+
target = document.getElementById(decodeURIComponent(location.hash.slice(1)));
|
|
91
|
+
} catch {
|
|
92
|
+
return; // malformed %-escape in the hash - nothing sane to scroll to
|
|
93
|
+
}
|
|
94
|
+
target?.scrollIntoView({ block: 'start', behavior: 'auto' });
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// Module-scoped rather than per-call: only one topbar is ever live at a
|
|
98
|
+
// time, so each astro:page-load re-run should replace the previous
|
|
99
|
+
// ResizeObserver (and scroll listener, below) entirely, not accumulate a
|
|
100
|
+
// new one on top of the last - the dataset.wdInit-guard pattern this
|
|
101
|
+
// codebase uses elsewhere doesn't apply here, since that pattern is for
|
|
102
|
+
// re-binding listeners onto elements that might persist across a re-run,
|
|
103
|
+
// and the topbar itself never does (ClientRouter swaps <body> - see
|
|
104
|
+
// BaseLayout's own comment on swapRootAttributes()). The scroll listener
|
|
105
|
+
// is bound to `window`, which *does* persist across that swap unlike the
|
|
106
|
+
// topbar element the ResizeObserver watches - left unremoved, every
|
|
107
|
+
// navigation would add one more, all still firing on every future scroll.
|
|
108
|
+
let observer: ResizeObserver | null = null;
|
|
109
|
+
let scrollHandler: (() => void) | null = null;
|
|
110
|
+
|
|
111
|
+
export function initTopbarOffset() {
|
|
112
|
+
observer?.disconnect();
|
|
113
|
+
if (scrollHandler) window.removeEventListener('scroll', scrollHandler);
|
|
114
|
+
|
|
115
|
+
const topbar = document.querySelector<HTMLElement>('.wd-topbar');
|
|
116
|
+
if (!topbar) return; // mode: blank pages render no topbar at all
|
|
117
|
+
|
|
118
|
+
applyOffset(topbar);
|
|
119
|
+
correctInitialHashScroll();
|
|
120
|
+
|
|
121
|
+
// Font loading or topbar.links wrapping on window resize can both
|
|
122
|
+
// change the topbar's real height after this first measurement.
|
|
123
|
+
observer = new ResizeObserver(() => applyOffset(topbar));
|
|
124
|
+
observer.observe(topbar);
|
|
125
|
+
|
|
126
|
+
// Keeps the offset correct while a banner (if any) scrolls past above
|
|
127
|
+
// the not-yet-stuck topbar (see applyOffset's own comment on reading
|
|
128
|
+
// .bottom instead of .height) - rAF-throttled to one measurement per
|
|
129
|
+
// frame rather than one per scroll event, same as any other scroll-
|
|
130
|
+
// driven layout read.
|
|
131
|
+
let ticking = false;
|
|
132
|
+
scrollHandler = () => {
|
|
133
|
+
if (ticking) return;
|
|
134
|
+
ticking = true;
|
|
135
|
+
requestAnimationFrame(() => {
|
|
136
|
+
applyOffset(topbar);
|
|
137
|
+
ticking = false;
|
|
138
|
+
});
|
|
139
|
+
};
|
|
140
|
+
window.addEventListener('scroll', scrollHandler, { passive: true });
|
|
141
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/* Wires Tailwind's utility classes into every writedocs build. Nothing in
|
|
2
|
+
writedocs' own components (BaseLayout, NavTree, Sidebar, Card, etc.)
|
|
3
|
+
uses Tailwind - they keep their existing scoped <style> blocks built
|
|
4
|
+
on the --wd-* CSS variables, since those already support per-site
|
|
5
|
+
writedocs.json theming (colors, dark mode) at render time. This import
|
|
6
|
+
just makes Tailwind's utilities available for custom/future content,
|
|
7
|
+
e.g. hand-authored MDX.
|
|
8
|
+
|
|
9
|
+
Deliberately importing theme + utilities only, NOT the full
|
|
10
|
+
"tailwindcss" entrypoint - that also pulls in Tailwind's preflight
|
|
11
|
+
(a browser-default CSS reset), which sets h1-h6's font-size/
|
|
12
|
+
font-weight to `inherit`. MDX content headings here rely on the
|
|
13
|
+
browser's own default heading styles (nothing in this codebase sets
|
|
14
|
+
them explicitly), so preflight was silently collapsing every
|
|
15
|
+
heading down to plain paragraph text. Skipping it keeps utility
|
|
16
|
+
classes available with zero effect on existing unstyled elements. */
|
|
17
|
+
@import "tailwindcss/theme.css";
|
|
18
|
+
@import "tailwindcss/utilities.css";
|