@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,117 @@
|
|
|
1
|
+
---
|
|
2
|
+
export interface TocHeading {
|
|
3
|
+
depth: number;
|
|
4
|
+
slug: string;
|
|
5
|
+
text: string;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
interface Props {
|
|
9
|
+
headings: TocHeading[];
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const { headings } = Astro.props as Props;
|
|
13
|
+
---
|
|
14
|
+
{headings.length > 0 && (
|
|
15
|
+
<nav class="wd-toc" aria-label="On this page">
|
|
16
|
+
<div class="wd-toc-title">On this page</div>
|
|
17
|
+
<ul>
|
|
18
|
+
{headings.map((h) => (
|
|
19
|
+
<li class={`wd-toc-item wd-toc-level-${h.depth}`}>
|
|
20
|
+
<a href={`#${h.slug}`} data-toc-slug={h.slug}>{h.text}</a>
|
|
21
|
+
</li>
|
|
22
|
+
))}
|
|
23
|
+
</ul>
|
|
24
|
+
</nav>
|
|
25
|
+
)}
|
|
26
|
+
|
|
27
|
+
<script>
|
|
28
|
+
function initToc(root: ParentNode) {
|
|
29
|
+
const tocLinks = Array.from(root.querySelectorAll<HTMLAnchorElement>('.wd-toc a[data-toc-slug]'));
|
|
30
|
+
if (tocLinks.length === 0) return;
|
|
31
|
+
|
|
32
|
+
const headingEls = tocLinks
|
|
33
|
+
.map((link) => document.getElementById(link.dataset.tocSlug ?? ''))
|
|
34
|
+
.filter((el): el is HTMLElement => el !== null);
|
|
35
|
+
|
|
36
|
+
if (headingEls.length === 0) return;
|
|
37
|
+
|
|
38
|
+
const linkBySlug = new Map(tocLinks.map((link) => [link.dataset.tocSlug, link]));
|
|
39
|
+
|
|
40
|
+
const setActive = (slug: string | null) => {
|
|
41
|
+
tocLinks.forEach((link) => link.classList.remove('active'));
|
|
42
|
+
if (slug) linkBySlug.get(slug)?.classList.add('active');
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
const observer = new IntersectionObserver(
|
|
46
|
+
(entries) => {
|
|
47
|
+
// Pick the topmost heading currently intersecting the "active band"
|
|
48
|
+
// near the top of the viewport, rather than whichever fires last -
|
|
49
|
+
// IntersectionObserver doesn't guarantee entries arrive in DOM order.
|
|
50
|
+
const visible = entries
|
|
51
|
+
.filter((e) => e.isIntersecting)
|
|
52
|
+
.map((e) => e.target as HTMLElement);
|
|
53
|
+
if (visible.length > 0) {
|
|
54
|
+
const topMost = visible.reduce((a, b) =>
|
|
55
|
+
a.getBoundingClientRect().top < b.getBoundingClientRect().top ? a : b
|
|
56
|
+
);
|
|
57
|
+
setActive(topMost.id);
|
|
58
|
+
}
|
|
59
|
+
},
|
|
60
|
+
{ rootMargin: '0px 0px -70% 0px', threshold: 1.0 }
|
|
61
|
+
);
|
|
62
|
+
|
|
63
|
+
headingEls.forEach((el) => observer.observe(el));
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
initToc(document);
|
|
67
|
+
document.addEventListener('astro:page-load', () => initToc(document));
|
|
68
|
+
</script>
|
|
69
|
+
|
|
70
|
+
<style>
|
|
71
|
+
/* top/max-height deliberately match Sidebar.astro's own .wd-sidebar
|
|
72
|
+
values exactly (see its comment) - both driven by --wd-topbar-offset
|
|
73
|
+
(src/scripts/topbar-offset.ts), the topbar's real measured height. */
|
|
74
|
+
.wd-toc {
|
|
75
|
+
position: sticky;
|
|
76
|
+
top: var(--wd-topbar-offset, 5rem);
|
|
77
|
+
padding: 1.5rem 1rem 1.5rem 1.5rem;
|
|
78
|
+
font-size: 0.85rem;
|
|
79
|
+
max-height: calc(100vh - var(--wd-topbar-offset, 5rem));
|
|
80
|
+
overflow-y: auto;
|
|
81
|
+
}
|
|
82
|
+
.wd-toc-title {
|
|
83
|
+
font-size: 0.75rem;
|
|
84
|
+
font-weight: 600;
|
|
85
|
+
text-transform: uppercase;
|
|
86
|
+
letter-spacing: 0.04em;
|
|
87
|
+
color: var(--wd-text-muted);
|
|
88
|
+
margin-bottom: 0.6rem;
|
|
89
|
+
}
|
|
90
|
+
.wd-toc ul {
|
|
91
|
+
list-style: none;
|
|
92
|
+
margin: 0;
|
|
93
|
+
padding: 0;
|
|
94
|
+
border-left: 1px solid var(--wd-border);
|
|
95
|
+
}
|
|
96
|
+
.wd-toc-item a {
|
|
97
|
+
display: block;
|
|
98
|
+
padding: 0.3rem 0 0.3rem 0.85rem;
|
|
99
|
+
margin-left: -1px;
|
|
100
|
+
border-left: 2px solid transparent;
|
|
101
|
+
text-decoration: none;
|
|
102
|
+
color: var(--wd-text-muted);
|
|
103
|
+
line-height: 1.4;
|
|
104
|
+
}
|
|
105
|
+
.wd-toc-level-3 a {
|
|
106
|
+
padding-left: 1.6rem;
|
|
107
|
+
font-size: 0.82rem;
|
|
108
|
+
}
|
|
109
|
+
.wd-toc-item a:hover {
|
|
110
|
+
color: var(--wd-text);
|
|
111
|
+
}
|
|
112
|
+
.wd-toc-item a.active {
|
|
113
|
+
color: var(--wd-primary);
|
|
114
|
+
border-left-color: var(--wd-primary);
|
|
115
|
+
font-weight: 500;
|
|
116
|
+
}
|
|
117
|
+
</style>
|
|
@@ -0,0 +1,311 @@
|
|
|
1
|
+
---
|
|
2
|
+
// The site's persistent header: brand, version/language/product switchers,
|
|
3
|
+
// tabs + global dropdowns (second row), writedocs.json's `topbar.links` (as a
|
|
4
|
+
// plain row above 860px, collapsed into a single ellipsis dropdown below
|
|
5
|
+
// it), the search trigger, and the theme toggle. Also renders the
|
|
6
|
+
// hamburger button that opens MobileMenu.astro's own drawer - the button
|
|
7
|
+
// itself lives here (it's positioned as the first child of
|
|
8
|
+
// .wd-topbar-inner, part of this row), the panel it opens is a separate
|
|
9
|
+
// component/file.
|
|
10
|
+
//
|
|
11
|
+
// Split out of BaseLayout.astro, which had grown to ~2200 lines covering
|
|
12
|
+
// several unrelated concerns in one file - see that component's own
|
|
13
|
+
// comment for the fuller rationale.
|
|
14
|
+
import { isExternalHref, type DocsConfig, type Selector, type GlobalDropdownView } from '../../lib/config';
|
|
15
|
+
import AppIcon from '../../components/AppIcon.astro';
|
|
16
|
+
import '../styles/dropdown.css';
|
|
17
|
+
import '../styles/topbar.css';
|
|
18
|
+
|
|
19
|
+
interface Props {
|
|
20
|
+
config: DocsConfig;
|
|
21
|
+
// One Selector per level of the active page's navigation path (tabs,
|
|
22
|
+
// versions, languages, products, or a dropdown used as a nested level) -
|
|
23
|
+
// see resolveSections()/buildSelectors() in lib/config.ts.
|
|
24
|
+
selectors: Selector[];
|
|
25
|
+
// The always-visible topbar dropdown list from navigation.global.dropdowns.
|
|
26
|
+
globalDropdowns: GlobalDropdownView[];
|
|
27
|
+
// Whether this page would show a sidebar at all (mode === 'default' or
|
|
28
|
+
// 'wide') - gates whether the hamburger toggle renders, same condition
|
|
29
|
+
// MobileMenu.astro itself is gated on for the panel it opens.
|
|
30
|
+
showSidebarCol: boolean;
|
|
31
|
+
logoLight?: string;
|
|
32
|
+
logoDark?: string;
|
|
33
|
+
brandLabel?: string;
|
|
34
|
+
}
|
|
35
|
+
const { config, selectors, globalDropdowns, showSidebarCol, logoLight, logoDark, brandLabel } = Astro.props as Props;
|
|
36
|
+
|
|
37
|
+
// The topbar is two stacked rows, matching Mintlify's own layout: a top
|
|
38
|
+
// row with the brand and the "pick one" switchers (versions/languages/
|
|
39
|
+
// products, or a dropdown used as a nested path segment) on the right,
|
|
40
|
+
// and a second row underneath for tabs plus the always-visible global
|
|
41
|
+
// dropdowns - both of which behave like a row of labeled links rather
|
|
42
|
+
// than a single current-value switcher.
|
|
43
|
+
//
|
|
44
|
+
// A dropdown container nested directly inside a tab (writedocs.json's "API
|
|
45
|
+
// Reference" tab having a `dropdowns` array instead of `pages`) reads as
|
|
46
|
+
// part of that tab's own local navigation, not as an independent
|
|
47
|
+
// top-level "pick one" choice - it's rendered by turning the *tab's own
|
|
48
|
+
// pill* into a dropdown-trigger (see SelectorOption.dropdown /
|
|
49
|
+
// buildSelectors() in lib/config.ts, and the tabSelectors.map below),
|
|
50
|
+
// rather than as a separate control. So unlike a root-level dropdown/
|
|
51
|
+
// version/language/product selector (which does surface as a brand-row
|
|
52
|
+
// switcher), this one is dropped from switcherSelectors entirely -
|
|
53
|
+
// `selectors` is in root-to-leaf path order, so "nested directly inside a
|
|
54
|
+
// tab" just means the previous entry in the array is the 'tab' selector.
|
|
55
|
+
const switcherSelectors = selectors.filter(
|
|
56
|
+
(sel, i) => sel.kind !== 'tab' && !(sel.kind === 'dropdown' && selectors[i - 1]?.kind === 'tab')
|
|
57
|
+
);
|
|
58
|
+
const tabSelectors = selectors.filter((sel) => sel.kind === 'tab');
|
|
59
|
+
const showSecondRow = tabSelectors.length > 0 || globalDropdowns.length > 0;
|
|
60
|
+
---
|
|
61
|
+
<header class="wd-topbar">
|
|
62
|
+
<div class="wd-topbar-row wd-topbar-row-brand">
|
|
63
|
+
<div class="wd-topbar-inner">
|
|
64
|
+
{/* Only rendered on pages that would show a sidebar at all
|
|
65
|
+
(showSidebarCol - same condition MobileMenu.astro's own
|
|
66
|
+
rendering is gated on). Hidden entirely above 860px via CSS
|
|
67
|
+
(.wd-mobile-menu-toggle's own media query in topbar.css), not
|
|
68
|
+
JS - so there's no flash-of-hamburger on a desktop-width load. */}
|
|
69
|
+
{showSidebarCol && (
|
|
70
|
+
<button
|
|
71
|
+
type="button"
|
|
72
|
+
class="wd-mobile-menu-toggle"
|
|
73
|
+
aria-label="Open menu"
|
|
74
|
+
aria-expanded="false"
|
|
75
|
+
aria-controls="wd-mobile-menu"
|
|
76
|
+
data-mobile-menu-trigger
|
|
77
|
+
>
|
|
78
|
+
<svg class="wd-mobile-menu-icon wd-mobile-menu-icon-bars" width="18" height="18" viewBox="0 0 18 18" aria-hidden="true">
|
|
79
|
+
<path d="M2.5 5H15.5M2.5 9H15.5M2.5 13H15.5" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" />
|
|
80
|
+
</svg>
|
|
81
|
+
<svg class="wd-mobile-menu-icon wd-mobile-menu-icon-close" width="18" height="18" viewBox="0 0 18 18" aria-hidden="true">
|
|
82
|
+
<path d="M4 4L14 14M14 4L4 14" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" />
|
|
83
|
+
</svg>
|
|
84
|
+
</button>
|
|
85
|
+
)}
|
|
86
|
+
<a class="wd-topbar-brand" href="/">
|
|
87
|
+
{logoLight && <img src={logoLight} alt={config.name} height="24" class="wd-logo-light" />}
|
|
88
|
+
{logoDark && <img src={logoDark} alt={config.name} height="24" class="wd-logo-dark" />}
|
|
89
|
+
{brandLabel && <span>{brandLabel}</span>}
|
|
90
|
+
</a>
|
|
91
|
+
|
|
92
|
+
{switcherSelectors.length > 0 && (
|
|
93
|
+
<nav class="wd-topbar-switchers" aria-label="Version, language and product selection">
|
|
94
|
+
{switcherSelectors.map((sel) => {
|
|
95
|
+
// versions / languages / products, and a dropdown used as a
|
|
96
|
+
// nested path segment, all render as a single switcher: the
|
|
97
|
+
// trigger shows the *current* option, the menu lists the
|
|
98
|
+
// alternatives - unlike tabs, where every option is its own
|
|
99
|
+
// always-visible pill in the row below.
|
|
100
|
+
const current = sel.options.find((o) => o.active) ?? sel.options[0];
|
|
101
|
+
return (
|
|
102
|
+
<div class="wd-dropdown" data-selector-kind={sel.kind}>
|
|
103
|
+
<button type="button" class="wd-dropdown-trigger wd-switcher-trigger" aria-haspopup="true" aria-expanded="false">
|
|
104
|
+
<AppIcon icon={current.icon} class="wd-tab-icon" />
|
|
105
|
+
{current.label}
|
|
106
|
+
{current.tag && <span class="wd-selector-tag">{current.tag}</span>}
|
|
107
|
+
<svg class="wd-dropdown-caret" width="10" height="10" 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
|
+
</button>
|
|
111
|
+
<div class="wd-dropdown-menu" role="menu">
|
|
112
|
+
<div class="wd-dropdown-menu-panel">
|
|
113
|
+
{sel.options.map((opt) => (
|
|
114
|
+
<a
|
|
115
|
+
href={opt.href}
|
|
116
|
+
role="menuitem"
|
|
117
|
+
class={opt.active ? 'active' : ''}
|
|
118
|
+
target={isExternalHref(opt.href) ? '_blank' : undefined}
|
|
119
|
+
rel={isExternalHref(opt.href) ? 'noopener noreferrer' : undefined}
|
|
120
|
+
>
|
|
121
|
+
<AppIcon icon={opt.icon} class="wd-tab-icon" />
|
|
122
|
+
{opt.label}
|
|
123
|
+
{opt.tag && <span class="wd-selector-tag">{opt.tag}</span>}
|
|
124
|
+
</a>
|
|
125
|
+
))}
|
|
126
|
+
</div>
|
|
127
|
+
</div>
|
|
128
|
+
</div>
|
|
129
|
+
);
|
|
130
|
+
})}
|
|
131
|
+
</nav>
|
|
132
|
+
)}
|
|
133
|
+
|
|
134
|
+
<nav class="wd-topbar-links">
|
|
135
|
+
{config.topbar.links.map((link) => (
|
|
136
|
+
<a
|
|
137
|
+
href={link.href}
|
|
138
|
+
target={isExternalHref(link.href) ? '_blank' : undefined}
|
|
139
|
+
rel={isExternalHref(link.href) ? 'noopener noreferrer' : undefined}
|
|
140
|
+
aria-label={!link.label ? link.icon : undefined}
|
|
141
|
+
>
|
|
142
|
+
<AppIcon icon={link.icon} class="wd-tab-icon" />
|
|
143
|
+
{link.label}
|
|
144
|
+
</a>
|
|
145
|
+
))}
|
|
146
|
+
</nav>
|
|
147
|
+
|
|
148
|
+
{/* Below 860px, .wd-topbar-links above is hidden (see topbar.css)
|
|
149
|
+
and this takes its place instead - same link set, same
|
|
150
|
+
writedocs.json config.topbar.links, just collapsed behind a single
|
|
151
|
+
icon button rather than an inline row of text links that would
|
|
152
|
+
otherwise wrap and eat vertical space next to the brand. Reuses
|
|
153
|
+
the existing .wd-dropdown/.wd-dropdown-trigger/.wd-dropdown-menu/
|
|
154
|
+
.wd-dropdown-menu-panel pattern (see the switcher/tab dropdowns
|
|
155
|
+
above) rather than inventing a new toggle mechanism -
|
|
156
|
+
initDropdowns() (src/scripts/dropdowns.ts, wired below) already
|
|
157
|
+
wires up every .wd-dropdown on the page generically, so this
|
|
158
|
+
needs no JS of its own. */}
|
|
159
|
+
{config.topbar.links.length > 0 && (
|
|
160
|
+
<div class="wd-dropdown wd-topbar-links-mobile" data-selector-kind="topbar-links">
|
|
161
|
+
<button
|
|
162
|
+
type="button"
|
|
163
|
+
class="wd-dropdown-trigger wd-topbar-links-toggle"
|
|
164
|
+
aria-haspopup="true"
|
|
165
|
+
aria-expanded="false"
|
|
166
|
+
aria-label="More links"
|
|
167
|
+
>
|
|
168
|
+
<svg width="4" height="16" viewBox="0 0 4 16" aria-hidden="true">
|
|
169
|
+
<circle cx="2" cy="2" r="1.7" fill="currentColor" />
|
|
170
|
+
<circle cx="2" cy="8" r="1.7" fill="currentColor" />
|
|
171
|
+
<circle cx="2" cy="14" r="1.7" fill="currentColor" />
|
|
172
|
+
</svg>
|
|
173
|
+
</button>
|
|
174
|
+
<div class="wd-dropdown-menu" role="menu">
|
|
175
|
+
<div class="wd-dropdown-menu-panel">
|
|
176
|
+
{config.topbar.links.map((link) => (
|
|
177
|
+
<a
|
|
178
|
+
href={link.href}
|
|
179
|
+
role="menuitem"
|
|
180
|
+
target={isExternalHref(link.href) ? '_blank' : undefined}
|
|
181
|
+
rel={isExternalHref(link.href) ? 'noopener noreferrer' : undefined}
|
|
182
|
+
aria-label={!link.label ? link.icon : undefined}
|
|
183
|
+
>
|
|
184
|
+
<AppIcon icon={link.icon} class="wd-tab-icon" />
|
|
185
|
+
{link.label}
|
|
186
|
+
</a>
|
|
187
|
+
))}
|
|
188
|
+
</div>
|
|
189
|
+
</div>
|
|
190
|
+
</div>
|
|
191
|
+
)}
|
|
192
|
+
|
|
193
|
+
<button type="button" class="wd-search-trigger" aria-label="Search" data-search-trigger>
|
|
194
|
+
<svg class="wd-search-icon" width="14" height="14" viewBox="0 0 14 14" aria-hidden="true">
|
|
195
|
+
<circle cx="6" cy="6" r="4.5" stroke="currentColor" stroke-width="1.4" fill="none" />
|
|
196
|
+
<path d="M9.5 9.5L13 13" stroke="currentColor" stroke-width="1.4" stroke-linecap="round" />
|
|
197
|
+
</svg>
|
|
198
|
+
<span class="wd-search-placeholder">Search...</span>
|
|
199
|
+
<kbd class="wd-search-kbd">⌘K</kbd>
|
|
200
|
+
</button>
|
|
201
|
+
|
|
202
|
+
<button type="button" class="wd-theme-toggle" aria-label="Toggle color theme">
|
|
203
|
+
<AppIcon icon="sun" class="wd-theme-icon wd-theme-icon-sun" />
|
|
204
|
+
<AppIcon icon="moon" class="wd-theme-icon wd-theme-icon-moon" />
|
|
205
|
+
</button>
|
|
206
|
+
</div>
|
|
207
|
+
</div>
|
|
208
|
+
|
|
209
|
+
{showSecondRow && (
|
|
210
|
+
<div class="wd-topbar-row wd-topbar-row-tabs">
|
|
211
|
+
<div class="wd-topbar-inner">
|
|
212
|
+
<nav class="wd-topbar-tabs" aria-label="Sections">
|
|
213
|
+
{tabSelectors.map((sel) =>
|
|
214
|
+
sel.options.map((opt) =>
|
|
215
|
+
opt.dropdown ? (
|
|
216
|
+
// A tab whose own content is `dropdowns` (not `pages`)
|
|
217
|
+
// renders as a dropdown-trigger in place of the plain
|
|
218
|
+
// pill - the tab's own label/icon stay on the trigger
|
|
219
|
+
// (not "whichever dropdown is current", so it reads as
|
|
220
|
+
// "this tab, which happens to expand" rather than
|
|
221
|
+
// jumping around as you navigate its sub-pages), and its
|
|
222
|
+
// dropdowns become the menu.
|
|
223
|
+
<div class="wd-dropdown" data-selector-kind="tab">
|
|
224
|
+
<button
|
|
225
|
+
type="button"
|
|
226
|
+
class={`wd-dropdown-trigger wd-tab-link ${opt.active ? 'active' : ''}`}
|
|
227
|
+
aria-haspopup="true"
|
|
228
|
+
aria-expanded="false"
|
|
229
|
+
>
|
|
230
|
+
<AppIcon icon={opt.icon} class="wd-tab-icon" />
|
|
231
|
+
{opt.label}
|
|
232
|
+
<svg class="wd-dropdown-caret" width="10" height="10" viewBox="0 0 10 10" aria-hidden="true">
|
|
233
|
+
<path d="M2 3.5L5 6.5L8 3.5" stroke="currentColor" stroke-width="1.4" fill="none" stroke-linecap="round" stroke-linejoin="round" />
|
|
234
|
+
</svg>
|
|
235
|
+
</button>
|
|
236
|
+
<div class="wd-dropdown-menu" role="menu">
|
|
237
|
+
<div class="wd-dropdown-menu-panel">
|
|
238
|
+
{opt.dropdown.map((d) => (
|
|
239
|
+
<a
|
|
240
|
+
href={d.href}
|
|
241
|
+
role="menuitem"
|
|
242
|
+
class={d.active ? 'active' : ''}
|
|
243
|
+
target={isExternalHref(d.href) ? '_blank' : undefined}
|
|
244
|
+
rel={isExternalHref(d.href) ? 'noopener noreferrer' : undefined}
|
|
245
|
+
>
|
|
246
|
+
<AppIcon icon={d.icon} class="wd-tab-icon" />
|
|
247
|
+
{d.label}
|
|
248
|
+
</a>
|
|
249
|
+
))}
|
|
250
|
+
</div>
|
|
251
|
+
</div>
|
|
252
|
+
</div>
|
|
253
|
+
) : (
|
|
254
|
+
<a
|
|
255
|
+
href={opt.href}
|
|
256
|
+
class={`wd-tab-link ${opt.active ? 'active' : ''}`}
|
|
257
|
+
target={isExternalHref(opt.href) ? '_blank' : undefined}
|
|
258
|
+
rel={isExternalHref(opt.href) ? 'noopener noreferrer' : undefined}
|
|
259
|
+
>
|
|
260
|
+
<AppIcon icon={opt.icon} class="wd-tab-icon" />
|
|
261
|
+
{opt.label}
|
|
262
|
+
</a>
|
|
263
|
+
)
|
|
264
|
+
)
|
|
265
|
+
)}
|
|
266
|
+
{globalDropdowns.map((d) =>
|
|
267
|
+
d.href !== null ? (
|
|
268
|
+
<a
|
|
269
|
+
href={d.href}
|
|
270
|
+
class="wd-tab-link"
|
|
271
|
+
target={isExternalHref(d.href) ? '_blank' : undefined}
|
|
272
|
+
rel={isExternalHref(d.href) ? 'noopener noreferrer' : undefined}
|
|
273
|
+
>
|
|
274
|
+
<AppIcon icon={d.icon} class="wd-tab-icon" />
|
|
275
|
+
{d.label}
|
|
276
|
+
</a>
|
|
277
|
+
) : (
|
|
278
|
+
<div class="wd-dropdown">
|
|
279
|
+
<button type="button" class="wd-dropdown-trigger" aria-haspopup="true" aria-expanded="false">
|
|
280
|
+
<AppIcon icon={d.icon} class="wd-tab-icon" />
|
|
281
|
+
{d.label}
|
|
282
|
+
<svg class="wd-dropdown-caret" width="10" height="10" viewBox="0 0 10 10" aria-hidden="true">
|
|
283
|
+
<path d="M2 3.5L5 6.5L8 3.5" stroke="currentColor" stroke-width="1.4" fill="none" stroke-linecap="round" stroke-linejoin="round" />
|
|
284
|
+
</svg>
|
|
285
|
+
</button>
|
|
286
|
+
<div class="wd-dropdown-menu" role="menu">
|
|
287
|
+
<div class="wd-dropdown-menu-panel">
|
|
288
|
+
{d.items.map((item) => (
|
|
289
|
+
<a href={item.href} role="menuitem" class={item.active ? 'active' : ''}>{item.label}</a>
|
|
290
|
+
))}
|
|
291
|
+
</div>
|
|
292
|
+
</div>
|
|
293
|
+
</div>
|
|
294
|
+
)
|
|
295
|
+
)}
|
|
296
|
+
</nav>
|
|
297
|
+
</div>
|
|
298
|
+
</div>
|
|
299
|
+
)}
|
|
300
|
+
</header>
|
|
301
|
+
<script>
|
|
302
|
+
import { initDropdowns } from '../../scripts/dropdowns';
|
|
303
|
+
import { initThemeToggle } from '../../scripts/theme-toggle';
|
|
304
|
+
|
|
305
|
+
initDropdowns(document);
|
|
306
|
+
initThemeToggle(document);
|
|
307
|
+
document.addEventListener('astro:page-load', () => {
|
|
308
|
+
initDropdowns(document);
|
|
309
|
+
initThemeToggle(document);
|
|
310
|
+
});
|
|
311
|
+
</script>
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/* writedocs.json's `banner` - see bannerSchema in lib/config.ts. Markup stays
|
|
2
|
+
inline in BaseLayout.astro (it's tiny, and its own [data-banner-dismissed]
|
|
3
|
+
flash-prevention script already lives there) - this is just its styling,
|
|
4
|
+
split out for the same organizational reasons as every other file in
|
|
5
|
+
this directory. Sits above .wd-topbar, full-width, no max-width wrapper -
|
|
6
|
+
a banner reads as a site-wide strip, not page content, so it shouldn't
|
|
7
|
+
line up with .wd-shell's centered column. Hidden via the
|
|
8
|
+
[data-banner-dismissed] attribute set by BaseLayout's own inline <head>
|
|
9
|
+
script rather than a client-side mount check, so a previously-dismissed
|
|
10
|
+
banner never flashes visible before JS runs - same technique as the
|
|
11
|
+
[data-theme] flash-prevention script. */
|
|
12
|
+
:root[data-banner-dismissed='true'] .wd-banner {
|
|
13
|
+
display: none;
|
|
14
|
+
}
|
|
15
|
+
.wd-banner {
|
|
16
|
+
width: 100%;
|
|
17
|
+
}
|
|
18
|
+
.wd-banner-info { background: var(--wd-primary); color: #fff; }
|
|
19
|
+
.wd-banner-warning { background: #b45309; color: #fff; }
|
|
20
|
+
.wd-banner-critical { background: #b91c1c; color: #fff; }
|
|
21
|
+
.wd-banner-inner {
|
|
22
|
+
max-width: 1440px;
|
|
23
|
+
margin: 0 auto;
|
|
24
|
+
padding: 0.6rem 1.5rem;
|
|
25
|
+
display: flex;
|
|
26
|
+
align-items: center;
|
|
27
|
+
justify-content: center;
|
|
28
|
+
gap: 1rem;
|
|
29
|
+
font-size: 0.88rem;
|
|
30
|
+
text-align: center;
|
|
31
|
+
}
|
|
32
|
+
.wd-banner-content { flex: 1; }
|
|
33
|
+
.wd-banner-dismiss {
|
|
34
|
+
flex-shrink: 0;
|
|
35
|
+
border: none;
|
|
36
|
+
background: transparent;
|
|
37
|
+
color: inherit;
|
|
38
|
+
cursor: pointer;
|
|
39
|
+
font-size: 1.1rem;
|
|
40
|
+
line-height: 1;
|
|
41
|
+
padding: 0 0.25rem;
|
|
42
|
+
opacity: 0.85;
|
|
43
|
+
}
|
|
44
|
+
.wd-banner-dismiss:hover { opacity: 1; }
|