@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,77 @@
|
|
|
1
|
+
---
|
|
2
|
+
// writedocs.json's `integrations` (integrationsSchema in lib/config.ts) - the
|
|
3
|
+
// six analytics providers. Each provider's inline-JS half (the ones that
|
|
4
|
+
// need one; Fathom/Plausible/Umami are pure <script src> tags with data-*
|
|
5
|
+
// attributes, rendered directly in the template below with no inline half
|
|
6
|
+
// to build here) is assembled as a plain string, interpolating the site's
|
|
7
|
+
// own config values via JSON.stringify (not raw string interpolation) so
|
|
8
|
+
// a value containing a quote or backslash can never break out of the
|
|
9
|
+
// generated script.
|
|
10
|
+
//
|
|
11
|
+
// Rendered by BaseLayout.astro in <head>, right after the meta tags and
|
|
12
|
+
// before even the theme-detection script - the usual placement every one
|
|
13
|
+
// of these providers' own install instructions ask for, so pageview
|
|
14
|
+
// timing is captured as close to actual page load as it can be. Every
|
|
15
|
+
// snippet below was sourced from that provider's own current official
|
|
16
|
+
// docs (fetched directly, not recalled from training data) while building
|
|
17
|
+
// this feature - see docs/dev/docs/integrations.mdx for links and dates.
|
|
18
|
+
// Every provider field is optional and independent - a site can turn on
|
|
19
|
+
// any subset (including all of them at once, for a migration period) or
|
|
20
|
+
// none.
|
|
21
|
+
//
|
|
22
|
+
// AskAiWidget.astro is the seventh integration (DocsBot) - kept as its
|
|
23
|
+
// own separate component rather than folded in here, since it renders at
|
|
24
|
+
// the *end* of <body>, not in <head> (it's a UI widget, not a pageview
|
|
25
|
+
// beacon - see that component's own comment).
|
|
26
|
+
//
|
|
27
|
+
// Split out of BaseLayout.astro, which had grown to ~2200 lines covering
|
|
28
|
+
// several unrelated concerns in one file - see that component's own
|
|
29
|
+
// comment for the fuller rationale.
|
|
30
|
+
import type { DocsConfig } from '../../lib/config';
|
|
31
|
+
|
|
32
|
+
interface Props {
|
|
33
|
+
config: DocsConfig;
|
|
34
|
+
}
|
|
35
|
+
const { config } = Astro.props as Props;
|
|
36
|
+
|
|
37
|
+
// Google Tag Manager's snippet is copied verbatim from Google's own
|
|
38
|
+
// long-stable install snippet (unchanged in substance for years) - the
|
|
39
|
+
// self-invoking function that pushes the initial 'gtm.js' event and
|
|
40
|
+
// injects the real gtm.js loader script.
|
|
41
|
+
const gtmScript = config.integrations.googleTagManager
|
|
42
|
+
? `(function(w,d,s,l,i){w[l]=w[l]||[];w[l].push({'gtm.start':new Date().getTime(),event:'gtm.js'});var f=d.getElementsByTagName(s)[0],j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src='https://www.googletagmanager.com/gtm.js?id='+i+dl;f.parentNode.insertBefore(j,f);})(window,document,'script','dataLayer',${JSON.stringify(config.integrations.googleTagManager.containerId)});`
|
|
43
|
+
: null;
|
|
44
|
+
// GA4's own gtag.js config call - paired with the separate `<script
|
|
45
|
+
// async src="...gtag/js?id=...">` loader tag rendered directly below,
|
|
46
|
+
// both gated on the same config.integrations.ga4 check.
|
|
47
|
+
const ga4ConfigScript = config.integrations.ga4
|
|
48
|
+
? `window.dataLayer=window.dataLayer||[];function gtag(){dataLayer.push(arguments);}gtag('js',new Date());gtag('config',${JSON.stringify(config.integrations.ga4.measurementId)});`
|
|
49
|
+
: null;
|
|
50
|
+
// PostHog's own "array.js" bootstrap loader - copied verbatim from
|
|
51
|
+
// posthog.com/docs/libraries/js (fetched directly while building this
|
|
52
|
+
// feature, not recalled from training data - PostHog revises this
|
|
53
|
+
// snippet's minified body occasionally, so re-fetch and diff before
|
|
54
|
+
// assuming it's still current if this ever needs touching again). The
|
|
55
|
+
// `defaults` date is PostHog's own versioned configuration-snapshot pin
|
|
56
|
+
// (their docs describe it as "a date... for a configuration snapshot used
|
|
57
|
+
// as defaults") - current as of the fetch above; bump it if PostHog's own
|
|
58
|
+
// docs have moved on to a newer one.
|
|
59
|
+
const posthogScript = config.integrations.posthog
|
|
60
|
+
? `!function(t,e){var o,n,p,r;e.__SV||(window.posthog=e,e._i=[],e.init=function(i,s,a){function g(t,e){var o=e.split(".");2==o.length&&(t=t[o[0]],e=o[1]),t[e]=function(){t.push([e].concat(Array.prototype.slice.call(arguments,0)))}}(p=t.createElement("script")).type="text/javascript",p.crossOrigin="anonymous",p.async=!0,p.src=s.api_host.replace(".i.posthog.com","-assets.i.posthog.com")+"/static/array.js",(r=t.getElementsByTagName("script")[0]).parentNode.insertBefore(p,r);var u=e;for(void 0!==a?u=e[a]=[]:a="posthog",u.people=u.people||[],u.toString=function(t){var e="posthog";return"posthog"!==a&&(e+="."+a),t||(e+=" (stub)"),e},u.people.toString=function(){return u.toString(1)+".people (stub)"},o="init capture register register_once register_for_session unregister unregister_for_session getFeatureFlag getFeatureFlagResult isFeatureEnabled reloadFeatureFlags updateEarlyAccessFeatureEnrollment getEarlyAccessFeatures on onFeatureFlags onSessionId getSurveys getActiveMatchingSurveys renderSurvey canRenderSurvey getNextSurveyStep identify setPersonProperties group resetGroups setPersonPropertiesForFlags resetPersonPropertiesForFlags setGroupPropertiesForFlags resetGroupPropertiesForFlags reset get_distinct_id getGroups get_session_id get_session_replay_url alias set_config startSessionRecording stopSessionRecording sessionRecordingStarted captureException loadToolbar get_property getSessionProperty createPersonProfile opt_in_capturing opt_out_capturing has_opted_in_capturing has_opted_out_capturing clear_opt_in_out_capturing debug".split(" "),n=0;n<o.length;n++)g(u,o[n]);e._i.push([i,s,a])},e.__SV=1)}(document,window.posthog||[]);posthog.init(${JSON.stringify(config.integrations.posthog.apiKey)},{api_host:${JSON.stringify(config.integrations.posthog.apiHost)},defaults:'2026-05-30'});`
|
|
61
|
+
: null;
|
|
62
|
+
---
|
|
63
|
+
{config.integrations.ga4 && (
|
|
64
|
+
<script is:inline async src={`https://www.googletagmanager.com/gtag/js?id=${config.integrations.ga4.measurementId}`}></script>
|
|
65
|
+
)}
|
|
66
|
+
{ga4ConfigScript && <script is:inline set:html={ga4ConfigScript} />}
|
|
67
|
+
{gtmScript && <script is:inline set:html={gtmScript} />}
|
|
68
|
+
{config.integrations.plausible && (
|
|
69
|
+
<script is:inline defer data-domain={config.integrations.plausible.domain} src={config.integrations.plausible.src}></script>
|
|
70
|
+
)}
|
|
71
|
+
{config.integrations.fathom && (
|
|
72
|
+
<script is:inline src="https://cdn.usefathom.com/script.js" data-site={config.integrations.fathom.siteId} defer></script>
|
|
73
|
+
)}
|
|
74
|
+
{posthogScript && <script is:inline set:html={posthogScript} />}
|
|
75
|
+
{config.integrations.umami && (
|
|
76
|
+
<script is:inline defer src={config.integrations.umami.src} data-website-id={config.integrations.umami.websiteId}></script>
|
|
77
|
+
)}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
---
|
|
2
|
+
// writedocs.json's `integrations.askAi` (askAiIntegrationSchema in
|
|
3
|
+
// lib/config.ts) - DocsBot's own embeddable-chat-widget loader, copied
|
|
4
|
+
// verbatim from docsbot.ai/documentation/developer/embeddable-chat-widget
|
|
5
|
+
// (fetched directly while building this feature, not recalled from
|
|
6
|
+
// training data). Unlike AnalyticsScripts.astro's six providers, this
|
|
7
|
+
// isn't rendered in <head> - it's a UI widget (a floating chat bubble),
|
|
8
|
+
// not a pageview beacon, so there's no reason to load it before everything
|
|
9
|
+
// else the way the analytics scripts need to for accurate timing;
|
|
10
|
+
// BaseLayout.astro renders this at the very end of <body> instead, after
|
|
11
|
+
// even the site's own `scripts.body`/root-JS. The loader itself never
|
|
12
|
+
// changes per-site (it's DocsBot's fixed bootstrap, not something a
|
|
13
|
+
// config value gets interpolated into), so it's a plain constant rather
|
|
14
|
+
// than a template string - only the second, actual init call needs the
|
|
15
|
+
// site's own bot id.
|
|
16
|
+
//
|
|
17
|
+
// Split out of BaseLayout.astro, which had grown to ~2200 lines covering
|
|
18
|
+
// several unrelated concerns in one file - see that component's own
|
|
19
|
+
// comment for the fuller rationale.
|
|
20
|
+
import type { DocsConfig } from '../../lib/config';
|
|
21
|
+
|
|
22
|
+
interface Props {
|
|
23
|
+
config: DocsConfig;
|
|
24
|
+
}
|
|
25
|
+
const { config } = Astro.props as Props;
|
|
26
|
+
|
|
27
|
+
const askAiLoaderScript = `window.DocsBotAI=window.DocsBotAI||{},DocsBotAI.init=function(e){return new Promise((t,r)=>{var n=document.createElement("script");n.type="text/javascript",n.async=!0,n.src="https://widget.docsbot.ai/chat.js";let o=document.getElementsByTagName("script")[0];o.parentNode.insertBefore(n,o),n.addEventListener("load",()=>{let n;Promise.all([new Promise((t,r)=>{window.DocsBotAI.mount(Object.assign({}, e)).then(t).catch(r)}),(n=function e(t){return new Promise(e=>{if(document.querySelector(t))return e(document.querySelector(t));let r=new MutationObserver(n=>{if(document.querySelector(t))return e(document.querySelector(t)),r.disconnect()});r.observe(document.body,{childList:!0,subtree:!0})})})("#docsbotai-root"),]).then(()=>t()).catch(r)}),n.addEventListener("error",e=>{r(e.message)})})};`;
|
|
28
|
+
const askAiInitScript = config.integrations.askAi
|
|
29
|
+
? `DocsBotAI.init(${JSON.stringify({ id: config.integrations.askAi.id })});`
|
|
30
|
+
: null;
|
|
31
|
+
---
|
|
32
|
+
{config.integrations.askAi && (
|
|
33
|
+
<>
|
|
34
|
+
<script is:inline set:html={askAiLoaderScript} />
|
|
35
|
+
{askAiInitScript && <script is:inline set:html={askAiInitScript} />}
|
|
36
|
+
</>
|
|
37
|
+
)}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
---
|
|
2
|
+
// The ancestor-group trail above the auto-rendered <h1> ("Learn the
|
|
3
|
+
// Basics > Authentication Flows") - `items` is ancestorGroupsForSlug()'s
|
|
4
|
+
// output (lib/config.ts): the chain of group labels (root first) the
|
|
5
|
+
// current page is nested under within its active Section's sidebar tree,
|
|
6
|
+
// each linking to that group's own attached page when it has one
|
|
7
|
+
// (`href`), or rendered as plain unlinked text when it doesn't.
|
|
8
|
+
// Deliberately does NOT include the current page itself as a trailing
|
|
9
|
+
// crumb - that's already the <h1> right below this, and repeating it
|
|
10
|
+
// here would just be noise. [...slug].astro only renders this component
|
|
11
|
+
// at all when `items` is non-empty (a page with no enclosing group has
|
|
12
|
+
// no ancestor trail to show, and a home-icon-only bar isn't worth
|
|
13
|
+
// rendering on its own).
|
|
14
|
+
//
|
|
15
|
+
// The home icon is always the first crumb, on every page this component
|
|
16
|
+
// renders on, regardless of `items` - a fixed entry point back to `/`,
|
|
17
|
+
// not itself part of the ancestor chain. Rendered via astro-icon (same
|
|
18
|
+
// system every other writedocs.json `icon` field/AppIcon.astro uses - a real
|
|
19
|
+
// build-time <svg>, no client JS/React needed) - fa6-solid:house is the
|
|
20
|
+
// Iconify id for the exact same Font Awesome 6 glyph as react-icons'
|
|
21
|
+
// `FaHouse`, without pulling React into an otherwise-static nav element.
|
|
22
|
+
import { Icon } from 'astro-icon/components';
|
|
23
|
+
|
|
24
|
+
export interface BreadcrumbItem {
|
|
25
|
+
label: string;
|
|
26
|
+
href: string | null;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
interface Props {
|
|
30
|
+
items: BreadcrumbItem[];
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const { items } = Astro.props as Props;
|
|
34
|
+
---
|
|
35
|
+
<nav class="wd-breadcrumbs" aria-label="Breadcrumb" data-pagefind-ignore>
|
|
36
|
+
<ol class="wd-breadcrumb-list">
|
|
37
|
+
<li class="wd-breadcrumb-item">
|
|
38
|
+
<a href="/" class="wd-breadcrumb-home" aria-label="Home">
|
|
39
|
+
<Icon name="fa6-solid:house" class="wd-breadcrumb-home-icon" />
|
|
40
|
+
</a>
|
|
41
|
+
<svg class="wd-breadcrumb-sep" width="10" height="10" viewBox="0 0 18 18" fill="none" aria-hidden="true">
|
|
42
|
+
<path d="M6.5 2.75L12.75 9L6.5 15.25" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" />
|
|
43
|
+
</svg>
|
|
44
|
+
</li>
|
|
45
|
+
{items.map((item, i) => (
|
|
46
|
+
<li class="wd-breadcrumb-item">
|
|
47
|
+
{item.href ? <a href={item.href}>{item.label}</a> : <span>{item.label}</span>}
|
|
48
|
+
{i < items.length - 1 && (
|
|
49
|
+
<svg class="wd-breadcrumb-sep" width="10" height="10" viewBox="0 0 18 18" fill="none" aria-hidden="true">
|
|
50
|
+
<path d="M6.5 2.75L12.75 9L6.5 15.25" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" />
|
|
51
|
+
</svg>
|
|
52
|
+
)}
|
|
53
|
+
</li>
|
|
54
|
+
))}
|
|
55
|
+
</ol>
|
|
56
|
+
</nav>
|
|
57
|
+
|
|
58
|
+
<style>
|
|
59
|
+
.wd-breadcrumbs {
|
|
60
|
+
margin-bottom: 0.6rem;
|
|
61
|
+
}
|
|
62
|
+
.wd-breadcrumb-list {
|
|
63
|
+
display: flex;
|
|
64
|
+
flex-wrap: wrap;
|
|
65
|
+
align-items: center;
|
|
66
|
+
gap: 0.35rem;
|
|
67
|
+
list-style: none;
|
|
68
|
+
margin: 0;
|
|
69
|
+
padding: 0;
|
|
70
|
+
font-size: 0.85rem;
|
|
71
|
+
color: var(--wd-text-muted);
|
|
72
|
+
}
|
|
73
|
+
.wd-breadcrumb-item {
|
|
74
|
+
display: flex;
|
|
75
|
+
align-items: center;
|
|
76
|
+
gap: 0.35rem;
|
|
77
|
+
}
|
|
78
|
+
.wd-breadcrumb-item a {
|
|
79
|
+
display: flex;
|
|
80
|
+
align-items: center;
|
|
81
|
+
color: inherit;
|
|
82
|
+
text-decoration: none;
|
|
83
|
+
}
|
|
84
|
+
.wd-breadcrumb-item a:hover {
|
|
85
|
+
color: var(--wd-text);
|
|
86
|
+
}
|
|
87
|
+
.wd-breadcrumb-home-icon {
|
|
88
|
+
width: 0.85em;
|
|
89
|
+
height: 0.85em;
|
|
90
|
+
flex-shrink: 0;
|
|
91
|
+
}
|
|
92
|
+
.wd-breadcrumb-sep {
|
|
93
|
+
flex-shrink: 0;
|
|
94
|
+
color: var(--wd-text-muted);
|
|
95
|
+
opacity: 0.6;
|
|
96
|
+
}
|
|
97
|
+
</style>
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
---
|
|
2
|
+
// The fullscreen lightbox overlay opened by clicking any zoomable content
|
|
3
|
+
// image (src/scripts/image-zoom.ts). Mounted once in BaseLayout.astro
|
|
4
|
+
// alongside SearchModal, same reasoning as that component's own comment:
|
|
5
|
+
// self-contained, no writedocs.json-derived props, and needs to exist in the
|
|
6
|
+
// DOM regardless of page `mode` since its own script queries the whole
|
|
7
|
+
// document for zoomable images rather than being scoped to a slot.
|
|
8
|
+
import '../styles/image-zoom.css';
|
|
9
|
+
---
|
|
10
|
+
<div class="wd-zoom-overlay" id="wd-zoom-overlay" hidden>
|
|
11
|
+
<button type="button" class="wd-zoom-close" aria-label="Close">×</button>
|
|
12
|
+
<img class="wd-zoom-image" id="wd-zoom-image" alt="" />
|
|
13
|
+
</div>
|
|
14
|
+
<script>
|
|
15
|
+
import { initImageZoom } from '../../scripts/image-zoom';
|
|
16
|
+
|
|
17
|
+
initImageZoom(document);
|
|
18
|
+
document.addEventListener('astro:page-load', () => initImageZoom(document));
|
|
19
|
+
</script>
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
---
|
|
2
|
+
// The <= 860px overlay drawer that TopBar.astro's own hamburger button
|
|
3
|
+
// opens - everything that's a side-by-side control on desktop (the
|
|
4
|
+
// brand-row switchers, the tabs bar, the always-visible global dropdowns)
|
|
5
|
+
// plus the sidebar itself collapses into this one panel below 860px.
|
|
6
|
+
// Reuses the exact same `selectors`/`globalDropdowns` props TopBar.astro
|
|
7
|
+
// renders its own desktop switchers/tabs from (see its own
|
|
8
|
+
// switcherSelectors/tabSelectors) rather than a filtered subset - unlike
|
|
9
|
+
// TopBar's switcherSelectors (which deliberately *drops* a dropdown
|
|
10
|
+
// selector nested directly under a tab, since desktop folds it into that
|
|
11
|
+
// tab's own dropdown-trigger button), this renders every entry of
|
|
12
|
+
// `selectors` in root-to-leaf order as its own accordion row. That's what
|
|
13
|
+
// makes "a tab with its own dropdowns gets a second selector right below
|
|
14
|
+
// it" fall out for free: when the active page is under such a tab,
|
|
15
|
+
// `selectors` already contains that nested dropdown as its own array
|
|
16
|
+
// entry immediately following the tab's entry (see buildSelectors() in
|
|
17
|
+
// lib/config.ts, which walks the active PathSegment[] in that exact
|
|
18
|
+
// order) - no tab-specific branching needed here at all.
|
|
19
|
+
//
|
|
20
|
+
// The sidebar itself is the `sidebar` slot, forwarded straight through
|
|
21
|
+
// from whatever BaseLayout.astro received - rendered a second time
|
|
22
|
+
// (Astro renders the same passed-in slot content at every place it's
|
|
23
|
+
// referenced, not just once); the first copy is in BaseLayout's own
|
|
24
|
+
// `.wd-sidebar-col`, for desktop. Both copies exist in the DOM on every
|
|
25
|
+
// load; only one is ever visible at a time (mobile-menu.css hides this
|
|
26
|
+
// whole panel above 860px, base.css hides .wd-sidebar-col at/below it),
|
|
27
|
+
// so there's no duplicate-content flash either way.
|
|
28
|
+
//
|
|
29
|
+
// Split out of BaseLayout.astro, which had grown to ~2200 lines covering
|
|
30
|
+
// several unrelated concerns in one file - see that component's own
|
|
31
|
+
// comment for the fuller rationale.
|
|
32
|
+
import { isExternalHref, type DocsConfig, type Selector, type GlobalDropdownView } from '../../lib/config';
|
|
33
|
+
import AppIcon from '../../components/AppIcon.astro';
|
|
34
|
+
import '../styles/dropdown.css';
|
|
35
|
+
import '../styles/topbar.css';
|
|
36
|
+
import '../styles/mobile-menu.css';
|
|
37
|
+
|
|
38
|
+
interface Props {
|
|
39
|
+
config: DocsConfig;
|
|
40
|
+
selectors: Selector[];
|
|
41
|
+
globalDropdowns: GlobalDropdownView[];
|
|
42
|
+
// Whether this page would show a sidebar at all (mode === 'default' or
|
|
43
|
+
// 'wide') - this whole component renders nothing otherwise, same
|
|
44
|
+
// condition TopBar.astro gates its own hamburger toggle on.
|
|
45
|
+
showSidebarCol: boolean;
|
|
46
|
+
logoLight?: string;
|
|
47
|
+
logoDark?: string;
|
|
48
|
+
brandLabel?: string;
|
|
49
|
+
}
|
|
50
|
+
const { config, selectors, globalDropdowns, showSidebarCol, logoLight, logoDark, brandLabel } = Astro.props as Props;
|
|
51
|
+
---
|
|
52
|
+
{showSidebarCol && (
|
|
53
|
+
<div class="wd-mobile-menu" id="wd-mobile-menu" inert>
|
|
54
|
+
<div class="wd-mobile-menu-backdrop" data-mobile-menu-backdrop></div>
|
|
55
|
+
<div class="wd-mobile-menu-panel" role="dialog" aria-modal="true" aria-label="Navigation">
|
|
56
|
+
{/* The panel reaches all the way to the top of the viewport (see
|
|
57
|
+
mobile-menu.css) rather than stopping below the real topbar -
|
|
58
|
+
the real topbar's own height varies (one row, or two once
|
|
59
|
+
tabs/global dropdowns/a wrapped search box are involved), which
|
|
60
|
+
made lining this panel up under it a fragile approximation.
|
|
61
|
+
Instead this header replicates the topbar's own brand/close/
|
|
62
|
+
theme row *inside* the panel, at a fixed height the panel fully
|
|
63
|
+
controls, so it reads as "the same navbar" occupying the same
|
|
64
|
+
spot rather than a separate floating piece underneath one of
|
|
65
|
+
variable size. The real topbar is still there underneath, just
|
|
66
|
+
no longer relied on for anything while the menu is open - it's
|
|
67
|
+
covered by this panel's own opaque background (or dimmed by the
|
|
68
|
+
backdrop, past the panel's own width). */}
|
|
69
|
+
<div class="wd-mobile-menu-header">
|
|
70
|
+
<button
|
|
71
|
+
type="button"
|
|
72
|
+
class="wd-mobile-menu-close"
|
|
73
|
+
aria-label="Close menu"
|
|
74
|
+
data-mobile-menu-close
|
|
75
|
+
>
|
|
76
|
+
<svg width="18" height="18" viewBox="0 0 18 18" aria-hidden="true">
|
|
77
|
+
<path d="M4 4L14 14M14 4L4 14" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" />
|
|
78
|
+
</svg>
|
|
79
|
+
</button>
|
|
80
|
+
<a class="wd-topbar-brand" href="/">
|
|
81
|
+
{logoLight && <img src={logoLight} alt={config.name} height="24" class="wd-logo-light" />}
|
|
82
|
+
{logoDark && <img src={logoDark} alt={config.name} height="24" class="wd-logo-dark" />}
|
|
83
|
+
{brandLabel && <span>{brandLabel}</span>}
|
|
84
|
+
</a>
|
|
85
|
+
{/* Same class as the real topbar's own theme button -
|
|
86
|
+
initThemeToggle() (src/scripts/theme-toggle.ts, wired below)
|
|
87
|
+
wires up every `.wd-theme-toggle` it finds (not just the
|
|
88
|
+
first), and both stay in sync for free since [data-theme] on
|
|
89
|
+
<html> - not per-button JS state - is what actually drives
|
|
90
|
+
which icon shows. */}
|
|
91
|
+
<button type="button" class="wd-theme-toggle" aria-label="Toggle color theme">
|
|
92
|
+
<AppIcon icon="sun" class="wd-theme-icon wd-theme-icon-sun" />
|
|
93
|
+
<AppIcon icon="moon" class="wd-theme-icon wd-theme-icon-moon" />
|
|
94
|
+
</button>
|
|
95
|
+
</div>
|
|
96
|
+
<div class="wd-mobile-menu-scroll">
|
|
97
|
+
{(selectors.length > 0 || globalDropdowns.length > 0) && (
|
|
98
|
+
<div class="wd-mobile-menu-selectors">
|
|
99
|
+
{selectors.map((sel) => {
|
|
100
|
+
const current = sel.options.find((o) => o.active) ?? sel.options[0];
|
|
101
|
+
return (
|
|
102
|
+
<details class="wd-mobile-accordion" name="wd-mobile-accordion">
|
|
103
|
+
<summary class="wd-mobile-accordion-trigger">
|
|
104
|
+
<AppIcon icon={current.icon} class="wd-tab-icon" />
|
|
105
|
+
<span class="wd-mobile-accordion-label">{current.label}</span>
|
|
106
|
+
{current.tag && <span class="wd-selector-tag">{current.tag}</span>}
|
|
107
|
+
<svg class="wd-mobile-accordion-caret" width="12" height="12" viewBox="0 0 10 10" aria-hidden="true">
|
|
108
|
+
<path d="M2 3.5L5 6.5L8 3.5" stroke="currentColor" stroke-width="1.4" fill="none" stroke-linecap="round" stroke-linejoin="round" />
|
|
109
|
+
</svg>
|
|
110
|
+
</summary>
|
|
111
|
+
<div class="wd-mobile-accordion-options">
|
|
112
|
+
<div class="wd-mobile-accordion-options-panel">
|
|
113
|
+
{sel.options.map((opt) => (
|
|
114
|
+
<a
|
|
115
|
+
href={opt.href}
|
|
116
|
+
class={opt.active ? 'active' : ''}
|
|
117
|
+
target={isExternalHref(opt.href) ? '_blank' : undefined}
|
|
118
|
+
rel={isExternalHref(opt.href) ? 'noopener noreferrer' : undefined}
|
|
119
|
+
>
|
|
120
|
+
<AppIcon icon={opt.icon} class="wd-tab-icon" />
|
|
121
|
+
<span class="wd-mobile-accordion-option-label">{opt.label}</span>
|
|
122
|
+
{opt.tag && <span class="wd-selector-tag">{opt.tag}</span>}
|
|
123
|
+
{opt.active && (
|
|
124
|
+
<svg class="wd-mobile-accordion-check" width="14" height="14" viewBox="0 0 14 14" aria-hidden="true">
|
|
125
|
+
<path d="M3 7.5L6 10.5L11 4.5" stroke="currentColor" stroke-width="1.6" fill="none" stroke-linecap="round" stroke-linejoin="round" />
|
|
126
|
+
</svg>
|
|
127
|
+
)}
|
|
128
|
+
</a>
|
|
129
|
+
))}
|
|
130
|
+
</div>
|
|
131
|
+
</div>
|
|
132
|
+
</details>
|
|
133
|
+
);
|
|
134
|
+
})}
|
|
135
|
+
{globalDropdowns.map((d) =>
|
|
136
|
+
d.href !== null ? (
|
|
137
|
+
<a
|
|
138
|
+
href={d.href}
|
|
139
|
+
class="wd-mobile-menu-link"
|
|
140
|
+
target={isExternalHref(d.href) ? '_blank' : undefined}
|
|
141
|
+
rel={isExternalHref(d.href) ? 'noopener noreferrer' : undefined}
|
|
142
|
+
>
|
|
143
|
+
<AppIcon icon={d.icon} class="wd-tab-icon" />
|
|
144
|
+
<span class="wd-mobile-accordion-option-label">{d.label}</span>
|
|
145
|
+
</a>
|
|
146
|
+
) : (
|
|
147
|
+
<details class="wd-mobile-accordion" name="wd-mobile-accordion">
|
|
148
|
+
<summary class="wd-mobile-accordion-trigger">
|
|
149
|
+
<AppIcon icon={d.icon} class="wd-tab-icon" />
|
|
150
|
+
<span class="wd-mobile-accordion-label">{d.label}</span>
|
|
151
|
+
<svg class="wd-mobile-accordion-caret" width="12" height="12" viewBox="0 0 10 10" aria-hidden="true">
|
|
152
|
+
<path d="M2 3.5L5 6.5L8 3.5" stroke="currentColor" stroke-width="1.4" fill="none" stroke-linecap="round" stroke-linejoin="round" />
|
|
153
|
+
</svg>
|
|
154
|
+
</summary>
|
|
155
|
+
<div class="wd-mobile-accordion-options">
|
|
156
|
+
<div class="wd-mobile-accordion-options-panel">
|
|
157
|
+
{d.items.map((item) => (
|
|
158
|
+
<a href={item.href} class={item.active ? 'active' : ''}>
|
|
159
|
+
<span class="wd-mobile-accordion-option-label">{item.label}</span>
|
|
160
|
+
{item.active && (
|
|
161
|
+
<svg class="wd-mobile-accordion-check" width="14" height="14" viewBox="0 0 14 14" aria-hidden="true">
|
|
162
|
+
<path d="M3 7.5L6 10.5L11 4.5" stroke="currentColor" stroke-width="1.6" fill="none" stroke-linecap="round" stroke-linejoin="round" />
|
|
163
|
+
</svg>
|
|
164
|
+
)}
|
|
165
|
+
</a>
|
|
166
|
+
))}
|
|
167
|
+
</div>
|
|
168
|
+
</div>
|
|
169
|
+
</details>
|
|
170
|
+
)
|
|
171
|
+
)}
|
|
172
|
+
</div>
|
|
173
|
+
)}
|
|
174
|
+
<div class="wd-mobile-menu-sidebar">
|
|
175
|
+
<slot name="sidebar" />
|
|
176
|
+
</div>
|
|
177
|
+
</div>
|
|
178
|
+
</div>
|
|
179
|
+
</div>
|
|
180
|
+
)}
|
|
181
|
+
<script>
|
|
182
|
+
import { initMobileMenu } from '../../scripts/mobile-menu';
|
|
183
|
+
import { initThemeToggle } from '../../scripts/theme-toggle';
|
|
184
|
+
// Not required by any markup this component currently renders (its own
|
|
185
|
+
// accordion rows are plain <details>, not .wd-dropdown), but imported
|
|
186
|
+
// and called anyway so both this file and TopBar.astro consistently own
|
|
187
|
+
// wiring up whatever interactive primitives their own markup might use
|
|
188
|
+
// - safe/idempotent regardless of how many components call it, see
|
|
189
|
+
// src/scripts/dropdowns.ts's own comment.
|
|
190
|
+
import { initDropdowns } from '../../scripts/dropdowns';
|
|
191
|
+
|
|
192
|
+
initMobileMenu(document);
|
|
193
|
+
initThemeToggle(document);
|
|
194
|
+
initDropdowns(document);
|
|
195
|
+
document.addEventListener('astro:page-load', () => {
|
|
196
|
+
initMobileMenu(document);
|
|
197
|
+
initThemeToggle(document);
|
|
198
|
+
initDropdowns(document);
|
|
199
|
+
});
|
|
200
|
+
</script>
|