@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,66 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import fs from 'node:fs';
|
|
3
|
+
import { spawn } from 'node:child_process';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Locates the installed pagefind package by walking up node_modules
|
|
7
|
+
* directories from packageRoot - the same lookup algorithm Node itself
|
|
8
|
+
* uses for bare-specifier resolution, just done by hand with fs instead
|
|
9
|
+
* of require.resolve()/import.meta.resolve(). This is necessary (rather
|
|
10
|
+
* than mirroring resolveAstroBin()'s simpler require.resolve()-based
|
|
11
|
+
* approach in run-astro.js) because pagefind's own package.json declares
|
|
12
|
+
* an "exports" map with only a "." entry scoped to the "import"
|
|
13
|
+
* condition: require.resolve('pagefind/package.json') is blocked
|
|
14
|
+
* outright (exports doesn't list a package.json subpath at all), and
|
|
15
|
+
* require.resolve('pagefind') also fails under CJS require() semantics
|
|
16
|
+
* (no "require"/"default" condition for it to satisfy).
|
|
17
|
+
* import.meta.resolve() would sidestep both, but needs Node 20.6+ -
|
|
18
|
+
* newer than this package's documented minimum (20.3.0, see engines in
|
|
19
|
+
* package.json). Walking node_modules ourselves avoids all of this - we
|
|
20
|
+
* only need pagefind's package.json (for its "bin" field) and the CLI
|
|
21
|
+
* script path, both plain file reads that "exports" doesn't restrict at
|
|
22
|
+
* all (only module *resolution* is subject to it).
|
|
23
|
+
*/
|
|
24
|
+
function findPagefindDir(startDir) {
|
|
25
|
+
let dir = startDir;
|
|
26
|
+
while (true) {
|
|
27
|
+
const candidate = path.join(dir, 'node_modules', 'pagefind');
|
|
28
|
+
if (fs.existsSync(path.join(candidate, 'package.json'))) return candidate;
|
|
29
|
+
const parent = path.dirname(dir);
|
|
30
|
+
if (parent === dir) {
|
|
31
|
+
throw new Error(`Could not find "pagefind" in any node_modules directory above ${startDir}`);
|
|
32
|
+
}
|
|
33
|
+
dir = parent;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function resolvePagefindBin(packageRoot) {
|
|
38
|
+
const dir = findPagefindDir(packageRoot);
|
|
39
|
+
const pkg = JSON.parse(fs.readFileSync(path.join(dir, 'package.json'), 'utf8'));
|
|
40
|
+
return path.join(dir, pkg.bin);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Indexes a built static site with Pagefind, writing the search bundle
|
|
45
|
+
* into <siteDir>/pagefind. Run as a postbuild step, after astro build has
|
|
46
|
+
* already produced the final HTML - Pagefind crawls that HTML directly
|
|
47
|
+
* rather than hooking into the Astro/Vite build pipeline, so search stays
|
|
48
|
+
* decoupled from whatever templating produced the pages. Only indexes
|
|
49
|
+
* elements carrying `data-pagefind-body` (see [...slug].astro's
|
|
50
|
+
* `.wd-article`) - without that scoping, Pagefind falls back to indexing
|
|
51
|
+
* every page's entire <body>, polluting every result with sidebar/topbar
|
|
52
|
+
* chrome text repeated on every page.
|
|
53
|
+
*/
|
|
54
|
+
export function runPagefind(siteDir, { packageRoot }) {
|
|
55
|
+
const bin = resolvePagefindBin(packageRoot);
|
|
56
|
+
return new Promise((resolve, reject) => {
|
|
57
|
+
const child = spawn(process.execPath, [bin, '--site', siteDir, '--quiet'], {
|
|
58
|
+
stdio: 'inherit',
|
|
59
|
+
});
|
|
60
|
+
child.on('exit', (code) => {
|
|
61
|
+
if (code === 0) resolve();
|
|
62
|
+
else reject(new Error(`pagefind exited with code ${code}`));
|
|
63
|
+
});
|
|
64
|
+
child.on('error', reject);
|
|
65
|
+
});
|
|
66
|
+
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
|
|
4
|
+
// Astro's own `output: 'static'` redirects (both writedocs.json's `redirects`
|
|
5
|
+
// feature and the automatic "/" -> first-nav-page redirect in
|
|
6
|
+
// [...slug].astro) are never real server/edge redirects - with no adapter
|
|
7
|
+
// installed, Astro can only ever emit a client-side
|
|
8
|
+
// `<meta http-equiv="refresh">` page (see redirectSchema's own comment in
|
|
9
|
+
// lib/config.ts for why `permanent`/status-code isn't even offered as a
|
|
10
|
+
// config option). That page is a real, working redirect, but it's a flash
|
|
11
|
+
// of unstyled content before the browser honors the meta-refresh - visibly
|
|
12
|
+
// bad on a deployed site, and exactly what a reader lands on for a second
|
|
13
|
+
// or more depending on network/host latency.
|
|
14
|
+
//
|
|
15
|
+
// Cloudflare Pages and Netlify both read a `_redirects` file at the root
|
|
16
|
+
// of the deployed output and turn each line into a real edge-level
|
|
17
|
+
// redirect - no page ever renders, no flash. This step generates that file
|
|
18
|
+
// as a purely additive improvement: hosts that honor `_redirects` get an
|
|
19
|
+
// instant real redirect, and every other static host (S3, GitHub Pages,
|
|
20
|
+
// etc.) falls back to the meta-refresh page Astro already generates, which
|
|
21
|
+
// keeps working exactly as before. Neither this file nor its absence ever
|
|
22
|
+
// changes what Astro itself builds.
|
|
23
|
+
// `contentDir` rather than a pre-loaded config object: this runs from
|
|
24
|
+
// build.js, invoked by plain `node`, not through Astro/Vite's TS
|
|
25
|
+
// transform - loadDocsConfig() (lib/config.ts) can't be imported here the
|
|
26
|
+
// way astro.config.mjs and every .astro file import it. writedocs.json has
|
|
27
|
+
// already been through preflightCheck() (valid JSON) and Astro's own
|
|
28
|
+
// loadDocsConfig() call inside astro.config.mjs (schema-valid, or the
|
|
29
|
+
// build above would already have failed) by the time this runs, so a
|
|
30
|
+
// second, un-validated JSON.parse() here is safe - this only ever reads
|
|
31
|
+
// the one `redirects` array back out, nothing that needs zod's defaults
|
|
32
|
+
// or transforms.
|
|
33
|
+
export function writeRedirectsFile(distDir, contentDir) {
|
|
34
|
+
const writedocsJson = JSON.parse(fs.readFileSync(path.join(contentDir, 'writedocs.json'), 'utf8'));
|
|
35
|
+
const redirects = writedocsJson.redirects ?? [];
|
|
36
|
+
const lines = [];
|
|
37
|
+
|
|
38
|
+
// writedocs.json's own `redirects` array - one line per entry, always 301
|
|
39
|
+
// (there's nowhere else in this project to source a status code from;
|
|
40
|
+
// see redirectSchema's comment in lib/config.ts on why the schema itself
|
|
41
|
+
// has no field for one - a redirect a site author deliberately
|
|
42
|
+
// configured is safe to treat as permanent).
|
|
43
|
+
for (const { source, destination } of redirects) {
|
|
44
|
+
lines.push(`${source} ${destination} 301`);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// The automatic root "/" redirect (see the `rootRedirect` block in
|
|
48
|
+
// [...slug].astro's getStaticPaths()) isn't reachable from here without
|
|
49
|
+
// re-deriving the same first-nav-page resolution Astro already did -
|
|
50
|
+
// instead, read it straight back out of the meta-refresh tag in the
|
|
51
|
+
// already-built dist/index.html. If writedocs.json already defines its own
|
|
52
|
+
// `/` redirect above, that one wins (first match wins in `_redirects`,
|
|
53
|
+
// and it's already listed first) - skip the automatic one entirely
|
|
54
|
+
// rather than emit a second, unreachable rule for the same path.
|
|
55
|
+
const hasExplicitRootRedirect = redirects.some((r) => r.source === '/');
|
|
56
|
+
const indexHtmlPath = path.join(distDir, 'index.html');
|
|
57
|
+
if (!hasExplicitRootRedirect && fs.existsSync(indexHtmlPath)) {
|
|
58
|
+
const html = fs.readFileSync(indexHtmlPath, 'utf8');
|
|
59
|
+
const match = html.match(/<meta http-equiv="refresh" content="\d+;\s*url=([^"]+)"/i);
|
|
60
|
+
if (match) lines.push(`/ ${match[1]} 301`);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
if (lines.length === 0) return;
|
|
64
|
+
|
|
65
|
+
const generated = [
|
|
66
|
+
'# Generated by writedocs (see src/cli/write-redirects-file.js).',
|
|
67
|
+
'# Gives Cloudflare Pages / Netlify a real edge redirect for the same',
|
|
68
|
+
"# paths Astro's own static-mode <meta http-equiv=\"refresh\"> pages",
|
|
69
|
+
'# already cover - other static hosts keep using those pages as-is.',
|
|
70
|
+
...lines,
|
|
71
|
+
'',
|
|
72
|
+
].join('\n');
|
|
73
|
+
|
|
74
|
+
const redirectsPath = path.join(distDir, '_redirects');
|
|
75
|
+
// A hand-written public/_redirects is already copied into dist by Astro
|
|
76
|
+
// by this point - preserve it (and its priority: first match wins) by
|
|
77
|
+
// appending after it rather than overwriting.
|
|
78
|
+
const existing = fs.existsSync(redirectsPath) ? fs.readFileSync(redirectsPath, 'utf8').replace(/\s*$/, '\n\n') : '';
|
|
79
|
+
fs.writeFileSync(redirectsPath, existing + generated);
|
|
80
|
+
}
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
---
|
|
2
|
+
import AppIcon from "./AppIcon.astro";
|
|
3
|
+
|
|
4
|
+
interface Props {
|
|
5
|
+
title: string;
|
|
6
|
+
icon?: string;
|
|
7
|
+
// Set automatically by remarkTitleAnchorIds (src/lib/mdx-title-anchor-ids.js)
|
|
8
|
+
// for every <Accordion title="..."> it finds in an .mdx file's own JSX,
|
|
9
|
+
// already deduped (-2, -3, ...) against every other Accordion/Callout
|
|
10
|
+
// title on that same page - see that plugin's own comment for why the
|
|
11
|
+
// dedup has to happen there, one level up, rather than here. Not meant
|
|
12
|
+
// to be set by hand in content; the slugify(title) fallback below covers
|
|
13
|
+
// every case this doesn't reach (plain .md content, a dynamic
|
|
14
|
+
// title={expr}, or this component used directly from a .astro file).
|
|
15
|
+
_titleId?: string;
|
|
16
|
+
}
|
|
17
|
+
const { title, icon, _titleId } = Astro.props as Props;
|
|
18
|
+
// Astro's own heading-id slugging (github-slugger, via the markdown
|
|
19
|
+
// pipeline) never sees this - `title` is a component prop, not a markdown
|
|
20
|
+
// heading, so it gets no id unless we make one ourselves. Same slugify
|
|
21
|
+
// Callout.astro's own fallback uses.
|
|
22
|
+
function slugify(value: string): string {
|
|
23
|
+
return value
|
|
24
|
+
.toLowerCase()
|
|
25
|
+
.replace(/[^a-z0-9]+/g, "-")
|
|
26
|
+
.replace(/^-+|-+$/g, "");
|
|
27
|
+
}
|
|
28
|
+
const titleId = _titleId ?? slugify(title);
|
|
29
|
+
---
|
|
30
|
+
|
|
31
|
+
<details class="wd-accordion">
|
|
32
|
+
<summary>
|
|
33
|
+
<span class="wd-accordion-heading">
|
|
34
|
+
{icon && <AppIcon icon={icon} class="wd-accordion-icon" />}
|
|
35
|
+
<span class="wd-accordion-title" id={titleId}>
|
|
36
|
+
{title}
|
|
37
|
+
<a class="wd-heading-anchor" href={`#${titleId}`} aria-label="Link to this section"> # </a>
|
|
38
|
+
</span>
|
|
39
|
+
</span>
|
|
40
|
+
{
|
|
41
|
+
/* Same "hide the native marker, draw our own instead" approach as
|
|
42
|
+
the sidebar's own collapsible groups (NavTree.astro's
|
|
43
|
+
.wd-nav-chevron) - see the ::-webkit-details-marker / ::marker
|
|
44
|
+
rules below. Drawn pointing down already (a native summary
|
|
45
|
+
marker instead defaults to pointing right, then rotating open),
|
|
46
|
+
since that's the resting/idle direction the reference design
|
|
47
|
+
this was built from actually uses - it flips 180deg on [open]
|
|
48
|
+
instead, in the CSS below. */
|
|
49
|
+
}
|
|
50
|
+
<svg class="wd-accordion-chevron" width="11" height="11" viewBox="0 0 10 10" aria-hidden="true">
|
|
51
|
+
<path
|
|
52
|
+
d="M2 3.5L5 6.5L8 3.5"
|
|
53
|
+
stroke="currentColor"
|
|
54
|
+
stroke-width="1.4"
|
|
55
|
+
fill="none"
|
|
56
|
+
stroke-linecap="round"
|
|
57
|
+
stroke-linejoin="round"></path>
|
|
58
|
+
</svg>
|
|
59
|
+
</summary>
|
|
60
|
+
<div class="wd-accordion-body"><slot /></div>
|
|
61
|
+
</details>
|
|
62
|
+
<script>
|
|
63
|
+
// The anchor link lives inside <summary> - a real <a>, but nested
|
|
64
|
+
// inside the element <details> itself listens on for its native
|
|
65
|
+
// open/close toggle, so without this a click on "#" would both
|
|
66
|
+
// navigate/copy the link *and* toggle the accordion as an unrelated
|
|
67
|
+
// side effect of where it happens to sit in the DOM. stopPropagation()
|
|
68
|
+
// only, not preventDefault() - the link itself should still navigate
|
|
69
|
+
// normally, just not also bubble up into summary's own click handler.
|
|
70
|
+
function initAccordionAnchors(root: ParentNode) {
|
|
71
|
+
root.querySelectorAll<HTMLAnchorElement>(".wd-accordion > summary .wd-heading-anchor").forEach((a) => {
|
|
72
|
+
if (a.dataset.wdInit) return;
|
|
73
|
+
a.dataset.wdInit = "true";
|
|
74
|
+
a.addEventListener("click", (e) => e.stopPropagation());
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
initAccordionAnchors(document);
|
|
78
|
+
document.addEventListener("astro:page-load", () => initAccordionAnchors(document));
|
|
79
|
+
</script>
|
|
80
|
+
<style>
|
|
81
|
+
.wd-accordion {
|
|
82
|
+
border: 1px solid var(--wd-border);
|
|
83
|
+
border-radius: 0.6rem;
|
|
84
|
+
margin: 0.6rem 0;
|
|
85
|
+
overflow: hidden;
|
|
86
|
+
}
|
|
87
|
+
.wd-accordion summary {
|
|
88
|
+
display: flex;
|
|
89
|
+
align-items: center;
|
|
90
|
+
justify-content: space-between;
|
|
91
|
+
gap: 0.75rem;
|
|
92
|
+
padding: 0.85rem 1.1rem;
|
|
93
|
+
cursor: pointer;
|
|
94
|
+
list-style: none;
|
|
95
|
+
}
|
|
96
|
+
/* Firefox/Chrome use ::marker for the default disclosure triangle,
|
|
97
|
+
Safari still needs the older, WebKit-specific pseudo-element -
|
|
98
|
+
both zeroed out so only the svg above ever shows. */
|
|
99
|
+
.wd-accordion summary::-webkit-details-marker {
|
|
100
|
+
display: none;
|
|
101
|
+
}
|
|
102
|
+
.wd-accordion summary::marker {
|
|
103
|
+
content: "";
|
|
104
|
+
}
|
|
105
|
+
.wd-accordion summary:hover {
|
|
106
|
+
background: var(--wd-surface);
|
|
107
|
+
}
|
|
108
|
+
.wd-accordion-heading {
|
|
109
|
+
display: flex;
|
|
110
|
+
align-items: center;
|
|
111
|
+
gap: 0.6rem;
|
|
112
|
+
min-width: 0;
|
|
113
|
+
}
|
|
114
|
+
/* :global() is load-bearing: AppIcon.astro renders this span/svg, not
|
|
115
|
+
Accordion.astro, so it never carries Accordion's own scope
|
|
116
|
+
attribute - without :global() these selectors compile to something
|
|
117
|
+
that can never match. Same fix Card.astro/Callout.astro's own icon
|
|
118
|
+
rules already needed for the identical reason. */
|
|
119
|
+
:global(span.wd-accordion-icon) {
|
|
120
|
+
display: block;
|
|
121
|
+
font-size: 1rem;
|
|
122
|
+
flex-shrink: 0;
|
|
123
|
+
}
|
|
124
|
+
:global(svg.wd-accordion-icon) {
|
|
125
|
+
display: block;
|
|
126
|
+
width: 1rem;
|
|
127
|
+
height: 1rem;
|
|
128
|
+
flex-shrink: 0;
|
|
129
|
+
color: var(--wd-text-muted);
|
|
130
|
+
}
|
|
131
|
+
.wd-accordion-title {
|
|
132
|
+
font-weight: 700;
|
|
133
|
+
color: var(--wd-text);
|
|
134
|
+
scroll-margin-top: calc(var(--wd-topbar-offset, 5rem) + 1rem);
|
|
135
|
+
}
|
|
136
|
+
/* .wd-heading-anchor's base look (color, opacity: 0, transition) comes
|
|
137
|
+
from [...slug].astro's own <style is:global> block - see
|
|
138
|
+
Callout.astro's identical comment on its own title anchor. Only the
|
|
139
|
+
hover trigger needs to live here, and - since both
|
|
140
|
+
.wd-accordion-title and the anchor inside it are rendered directly
|
|
141
|
+
by this component's own template - no :global() is needed for it
|
|
142
|
+
either. */
|
|
143
|
+
.wd-accordion-title:hover .wd-heading-anchor {
|
|
144
|
+
opacity: 1;
|
|
145
|
+
}
|
|
146
|
+
.wd-accordion-chevron {
|
|
147
|
+
flex-shrink: 0;
|
|
148
|
+
color: var(--wd-text-muted);
|
|
149
|
+
transition: transform 0.15s ease;
|
|
150
|
+
}
|
|
151
|
+
.wd-accordion[open] > summary .wd-accordion-chevron {
|
|
152
|
+
transform: rotate(180deg);
|
|
153
|
+
}
|
|
154
|
+
.wd-accordion-body {
|
|
155
|
+
padding: 1.1rem 1rem;
|
|
156
|
+
color: var(--wd-text-muted);
|
|
157
|
+
}
|
|
158
|
+
.wd-accordion-body :global(p:first-child) {
|
|
159
|
+
margin-top: 0;
|
|
160
|
+
}
|
|
161
|
+
.wd-accordion-body :global(p:last-child) {
|
|
162
|
+
margin-bottom: 0;
|
|
163
|
+
}
|
|
164
|
+
</style>
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
<div class="wd-accordion-group">
|
|
2
|
+
<slot />
|
|
3
|
+
</div>
|
|
4
|
+
<style is:global>
|
|
5
|
+
/* Same "one shared border, not N separate ones" pattern CodeGroup.astro
|
|
6
|
+
already uses for its own children (see that file's comment) -
|
|
7
|
+
overflow: hidden here is what actually clips the first/last
|
|
8
|
+
accordion's corners into this container's own radius, since every
|
|
9
|
+
.wd-accordion inside loses its own border-radius below. */
|
|
10
|
+
.wd-accordion-group {
|
|
11
|
+
margin: 1.25rem 0;
|
|
12
|
+
border: 1px solid var(--wd-border);
|
|
13
|
+
border-radius: 0.6rem;
|
|
14
|
+
overflow: hidden;
|
|
15
|
+
}
|
|
16
|
+
/* Overrides Accordion.astro's own scoped border/radius/margin - needs
|
|
17
|
+
is:global (this whole <style> block already is) since .wd-accordion
|
|
18
|
+
carries Accordion.astro's own scope attribute, not this file's. */
|
|
19
|
+
.wd-accordion-group > .wd-accordion {
|
|
20
|
+
margin: 0;
|
|
21
|
+
border: none;
|
|
22
|
+
border-radius: 0;
|
|
23
|
+
}
|
|
24
|
+
/* Divides one accordion from the next - not before the first one,
|
|
25
|
+
the group's own outer border above already closes that edge off.
|
|
26
|
+
~ (general sibling), not + (strictly adjacent): Accordion.astro's
|
|
27
|
+
own per-instance <script> tag is small enough that Astro/Vite
|
|
28
|
+
inlines it directly in the page body rather than extracting it to
|
|
29
|
+
an external bundle - it lands as a literal sibling element between
|
|
30
|
+
whichever pair of accordions happens to trigger the (deduped,
|
|
31
|
+
so it only ever appears once) inline, breaking + for exactly that
|
|
32
|
+
one pair while every other pair still matched. ~ tolerates
|
|
33
|
+
whatever non-.wd-accordion siblings end up between two accordions
|
|
34
|
+
for reasons like this, matching any accordion preceded by another
|
|
35
|
+
one anywhere earlier among its siblings instead of requiring zero
|
|
36
|
+
gap. */
|
|
37
|
+
.wd-accordion-group > .wd-accordion ~ .wd-accordion {
|
|
38
|
+
border-top: 1px solid var(--wd-border);
|
|
39
|
+
}
|
|
40
|
+
</style>
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
---
|
|
2
|
+
// Icon + label + checkmark language dropdown used by both of
|
|
3
|
+
// ApiReferencePanel's language pickers - the sidebar Request card's, and
|
|
4
|
+
// the Try-it modal's own copy (see rolePrefix below). Reuses the site's
|
|
5
|
+
// shared .wd-dropdown/.wd-dropdown-trigger/.wd-dropdown-menu/
|
|
6
|
+
// .wd-dropdown-menu-panel pattern (dropdown.css + initDropdowns() in
|
|
7
|
+
// src/scripts/dropdowns.ts, already wired globally against `document` by
|
|
8
|
+
// TopBar.astro/MobileMenu.astro on load and on every astro:page-load) so
|
|
9
|
+
// open/close/outside-click/Escape all come free here too, the same way
|
|
10
|
+
// CopyPageMenu.astro's own dropdown does - no separate wiring needed for
|
|
11
|
+
// any of that in this file or in ApiReferencePanel's own <script>.
|
|
12
|
+
//
|
|
13
|
+
// A hidden native <select> (data-role={rolePrefix}-select) stays the
|
|
14
|
+
// actual source of truth for "which language is selected" - every
|
|
15
|
+
// existing 'change' listener in ApiReferencePanel.astro's <script>
|
|
16
|
+
// (panel-toggle logic, live-preview refresh) was already written against
|
|
17
|
+
// that select, so ApiReferencePanel's own initLangDropdown() only ever
|
|
18
|
+
// *drives* it (sets .value, dispatches 'change') from a click on one of
|
|
19
|
+
// the buttons below, rather than replacing that wiring.
|
|
20
|
+
import { Icon } from 'astro-icon/components';
|
|
21
|
+
|
|
22
|
+
interface LangOption {
|
|
23
|
+
id: string;
|
|
24
|
+
label: string;
|
|
25
|
+
icon: string; // an astro-icon/Iconify id, e.g. "mdi:language-python"
|
|
26
|
+
}
|
|
27
|
+
interface Props {
|
|
28
|
+
options: LangOption[];
|
|
29
|
+
rolePrefix: string; // "lang" (sidebar) or "modal-lang" (Try-it modal)
|
|
30
|
+
ariaLabel: string;
|
|
31
|
+
}
|
|
32
|
+
const { options, rolePrefix, ariaLabel } = Astro.props as Props;
|
|
33
|
+
const first = options[0];
|
|
34
|
+
---
|
|
35
|
+
|
|
36
|
+
{first && (
|
|
37
|
+
<div class="wd-dropdown wd-api-lang-dropdown">
|
|
38
|
+
<select class="wd-api-lang-native-select" data-role={`${rolePrefix}-select`} tabindex="-1" aria-hidden="true">
|
|
39
|
+
{options.map((o) => <option value={o.id}>{o.label}</option>)}
|
|
40
|
+
</select>
|
|
41
|
+
<button
|
|
42
|
+
type="button"
|
|
43
|
+
class="wd-dropdown-trigger wd-api-lang-trigger"
|
|
44
|
+
data-role={`${rolePrefix}-trigger`}
|
|
45
|
+
aria-haspopup="true"
|
|
46
|
+
aria-expanded="false"
|
|
47
|
+
aria-label={ariaLabel}
|
|
48
|
+
>
|
|
49
|
+
<Icon name={first.icon} class="wd-api-lang-icon" />
|
|
50
|
+
<span class="wd-api-lang-label">{first.label}</span>
|
|
51
|
+
<svg class="wd-dropdown-caret" width="10" height="10" viewBox="0 0 10 10" aria-hidden="true">
|
|
52
|
+
<path d="M2 3.5L5 6.5L8 3.5" stroke="currentColor" stroke-width="1.4" fill="none" stroke-linecap="round" stroke-linejoin="round" />
|
|
53
|
+
</svg>
|
|
54
|
+
</button>
|
|
55
|
+
<div class="wd-dropdown-menu wd-api-lang-menu" role="menu">
|
|
56
|
+
<div class="wd-dropdown-menu-panel wd-api-lang-menu-panel">
|
|
57
|
+
{options.map((o, i) => (
|
|
58
|
+
<button
|
|
59
|
+
type="button"
|
|
60
|
+
class={`wd-api-lang-item${i === 0 ? ' active' : ''}`}
|
|
61
|
+
data-role={`${rolePrefix}-item`}
|
|
62
|
+
data-value={o.id}
|
|
63
|
+
>
|
|
64
|
+
<Icon name={o.icon} class="wd-api-lang-icon" />
|
|
65
|
+
<span class="wd-api-lang-label">{o.label}</span>
|
|
66
|
+
<svg class="wd-api-lang-check" width="12" height="12" viewBox="0 0 12 12" aria-hidden="true">
|
|
67
|
+
<path d="M2.5 6.2L5 8.7L9.5 3.5" stroke="currentColor" stroke-width="1.6" fill="none" stroke-linecap="round" stroke-linejoin="round" />
|
|
68
|
+
</svg>
|
|
69
|
+
</button>
|
|
70
|
+
))}
|
|
71
|
+
</div>
|
|
72
|
+
</div>
|
|
73
|
+
</div>
|
|
74
|
+
)}
|
|
75
|
+
|
|
76
|
+
<style>
|
|
77
|
+
.wd-api-lang-native-select {
|
|
78
|
+
position: absolute;
|
|
79
|
+
width: 1px;
|
|
80
|
+
height: 1px;
|
|
81
|
+
padding: 0;
|
|
82
|
+
margin: -1px;
|
|
83
|
+
overflow: hidden;
|
|
84
|
+
clip: rect(0, 0, 0, 0);
|
|
85
|
+
white-space: nowrap;
|
|
86
|
+
border: 0;
|
|
87
|
+
}
|
|
88
|
+
/* Overrides the shared .wd-dropdown-trigger base (dropdown.css, sized
|
|
89
|
+
for the topbar's larger switchers) down to the same small pill this
|
|
90
|
+
control replaced (.wd-api-select-wrap in ApiReferencePanel.astro's
|
|
91
|
+
own CSS) - scoped-style specificity wins this over the plain global
|
|
92
|
+
class rule regardless of import order. */
|
|
93
|
+
.wd-api-lang-trigger {
|
|
94
|
+
padding: 0.2rem 0.55rem;
|
|
95
|
+
border-radius: 999px;
|
|
96
|
+
border: 1px solid var(--wd-border);
|
|
97
|
+
background: var(--wd-background);
|
|
98
|
+
font-size: 0.75rem;
|
|
99
|
+
color: var(--wd-text);
|
|
100
|
+
}
|
|
101
|
+
.wd-api-lang-trigger:hover {
|
|
102
|
+
color: var(--wd-text);
|
|
103
|
+
border-color: var(--wd-text-muted);
|
|
104
|
+
}
|
|
105
|
+
.wd-api-lang-icon {
|
|
106
|
+
width: 0.95rem;
|
|
107
|
+
height: 0.95rem;
|
|
108
|
+
flex-shrink: 0;
|
|
109
|
+
color: var(--wd-text-muted);
|
|
110
|
+
}
|
|
111
|
+
/* Right-aligned, same as CopyPageMenu's own .wd-copy-page-menu override
|
|
112
|
+
- the default .wd-dropdown-menu (dropdown.css) is left: 0, correct
|
|
113
|
+
for the topbar's own left-anchored triggers but not for a trigger
|
|
114
|
+
that sits at the right edge of its own card. */
|
|
115
|
+
.wd-api-lang-menu {
|
|
116
|
+
left: auto;
|
|
117
|
+
right: 0;
|
|
118
|
+
}
|
|
119
|
+
.wd-api-lang-menu-panel {
|
|
120
|
+
min-width: 11rem;
|
|
121
|
+
display: flex;
|
|
122
|
+
flex-direction: column;
|
|
123
|
+
gap: 0.05rem;
|
|
124
|
+
}
|
|
125
|
+
.wd-api-lang-item {
|
|
126
|
+
display: flex;
|
|
127
|
+
align-items: center;
|
|
128
|
+
gap: 0.55rem;
|
|
129
|
+
width: 100%;
|
|
130
|
+
padding: 0.4rem 0.6rem;
|
|
131
|
+
border: none;
|
|
132
|
+
border-radius: 0.35rem;
|
|
133
|
+
background: none;
|
|
134
|
+
font-family: inherit;
|
|
135
|
+
font-size: 0.82rem;
|
|
136
|
+
color: var(--wd-text);
|
|
137
|
+
text-align: left;
|
|
138
|
+
cursor: pointer;
|
|
139
|
+
}
|
|
140
|
+
.wd-api-lang-item:hover {
|
|
141
|
+
background: var(--wd-surface);
|
|
142
|
+
}
|
|
143
|
+
.wd-api-lang-item .wd-api-lang-icon {
|
|
144
|
+
color: var(--wd-text-muted);
|
|
145
|
+
}
|
|
146
|
+
.wd-api-lang-item .wd-api-lang-label {
|
|
147
|
+
flex: 1;
|
|
148
|
+
}
|
|
149
|
+
.wd-api-lang-check {
|
|
150
|
+
flex-shrink: 0;
|
|
151
|
+
color: var(--wd-primary);
|
|
152
|
+
opacity: 0;
|
|
153
|
+
}
|
|
154
|
+
/* Same active-item convention as .wd-dropdown-menu a.active
|
|
155
|
+
(dropdown.css) - tinted text/background in the primary color, plus
|
|
156
|
+
the checkmark fading in, kept in sync with the hidden <select> by
|
|
157
|
+
ApiReferencePanel.astro's initLangDropdown(). */
|
|
158
|
+
.wd-api-lang-item.active {
|
|
159
|
+
color: var(--wd-primary);
|
|
160
|
+
background: color-mix(in srgb, var(--wd-primary) 10%, transparent);
|
|
161
|
+
}
|
|
162
|
+
.wd-api-lang-item.active .wd-api-lang-icon {
|
|
163
|
+
color: var(--wd-primary);
|
|
164
|
+
}
|
|
165
|
+
.wd-api-lang-item.active .wd-api-lang-check {
|
|
166
|
+
opacity: 1;
|
|
167
|
+
}
|
|
168
|
+
</style>
|