@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,184 @@
|
|
|
1
|
+
---
|
|
2
|
+
// `dropdown` is only ever passed down from RequestExample/
|
|
3
|
+
// ResponseExample.astro today (CodeGroup itself is never used with it
|
|
4
|
+
// directly in any fixture/doc) - kept as a real prop rather than a
|
|
5
|
+
// RequestExample-only concern so the switcher-style logic lives in one
|
|
6
|
+
// place instead of being duplicated for a second "tabbed code" component.
|
|
7
|
+
interface Props {
|
|
8
|
+
dropdown?: boolean;
|
|
9
|
+
}
|
|
10
|
+
const { dropdown = false } = Astro.props as Props;
|
|
11
|
+
---
|
|
12
|
+
<div class="wd-codegroup" data-dropdown={dropdown ? "true" : undefined}>
|
|
13
|
+
<slot />
|
|
14
|
+
</div>
|
|
15
|
+
<script>
|
|
16
|
+
// Mirrors Tabs.astro's own initTabs() almost exactly (see that file's
|
|
17
|
+
// comment for the dataset.wdInit re-run guard and astro:page-load
|
|
18
|
+
// rationale) - CodeGroup just reads each tab's label off the fenced
|
|
19
|
+
// block itself instead of a `title` prop, since a code fence's own
|
|
20
|
+
// ```lang title="..." meta (see shiki-code-block.js's parseMeta())
|
|
21
|
+
// already carries exactly that string, rendered as a .wd-code-title
|
|
22
|
+
// bar at build time - no reason to make an MDX author repeat it as a
|
|
23
|
+
// second prop on <CodeGroup>'s own children.
|
|
24
|
+
function labelFor(block: HTMLElement, index: number): string {
|
|
25
|
+
const title = block.querySelector<HTMLElement>('.wd-code-title')?.textContent?.trim();
|
|
26
|
+
if (title) return title;
|
|
27
|
+
// No `title=` meta on this fence - fall back to the language Shiki
|
|
28
|
+
// already tagged the <pre> with (data-language, always present),
|
|
29
|
+
// then a plain ordinal as the last resort for a truly bare ``` fence.
|
|
30
|
+
const lang = block.querySelector('pre[data-language]')?.getAttribute('data-language');
|
|
31
|
+
return lang ? lang.toUpperCase() : `Snippet ${index + 1}`;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function initCodeGroups(root: ParentNode) {
|
|
35
|
+
root.querySelectorAll<HTMLElement>('.wd-codegroup').forEach((group) => {
|
|
36
|
+
if (group.dataset.wdInit) return;
|
|
37
|
+
const blocks = Array.from(group.querySelectorAll<HTMLElement>(':scope > .wd-code-block'));
|
|
38
|
+
// A single-block <CodeGroup> has nothing to switch between - a
|
|
39
|
+
// one-tab tab strip (or a one-option dropdown) is pure noise, so
|
|
40
|
+
// it's left exactly as the CSS below already renders it: one
|
|
41
|
+
// plain bordered block, no nav.
|
|
42
|
+
if (blocks.length < 2) return;
|
|
43
|
+
group.dataset.wdInit = 'true';
|
|
44
|
+
// Only added once there's an actual tab strip/dropdown - see the
|
|
45
|
+
// CSS below, this is what hides each block's own now-redundant
|
|
46
|
+
// .wd-code-title bar in favor of the tab/option label showing the
|
|
47
|
+
// same text. Without JS (or a group too small to switch), that
|
|
48
|
+
// class never gets added and every block's title bar stays
|
|
49
|
+
// visible - the CodeGroup then just degrades to last commit's
|
|
50
|
+
// "stacked, shared border" look rather than losing the title text
|
|
51
|
+
// outright.
|
|
52
|
+
group.classList.add('wd-codegroup-tabbed');
|
|
53
|
+
blocks.forEach((block, i) => {
|
|
54
|
+
block.style.display = i === 0 ? '' : 'none';
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
if (group.dataset.dropdown === 'true') {
|
|
58
|
+
// RequestExample/ResponseExample's own `dropdown` prop - a
|
|
59
|
+
// <select> instead of a tab strip, per Mintlify's own prop of
|
|
60
|
+
// the same name/meaning on those two components. Same
|
|
61
|
+
// show/hide-blocks logic as the tab case below, just driven by
|
|
62
|
+
// a `change` event instead of individual button clicks.
|
|
63
|
+
const select = document.createElement('select');
|
|
64
|
+
select.className = 'wd-codegroup-select';
|
|
65
|
+
blocks.forEach((block, i) => {
|
|
66
|
+
const option = document.createElement('option');
|
|
67
|
+
option.value = String(i);
|
|
68
|
+
option.textContent = labelFor(block, i);
|
|
69
|
+
select.appendChild(option);
|
|
70
|
+
});
|
|
71
|
+
select.addEventListener('change', () => {
|
|
72
|
+
const index = Number(select.value);
|
|
73
|
+
blocks.forEach((b, i) => (b.style.display = i === index ? '' : 'none'));
|
|
74
|
+
});
|
|
75
|
+
group.prepend(select);
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const nav = document.createElement('div');
|
|
80
|
+
nav.className = 'wd-codegroup-nav';
|
|
81
|
+
blocks.forEach((block, i) => {
|
|
82
|
+
const btn = document.createElement('button');
|
|
83
|
+
btn.type = 'button';
|
|
84
|
+
btn.textContent = labelFor(block, i);
|
|
85
|
+
btn.className = 'wd-codegroup-tab' + (i === 0 ? ' active' : '');
|
|
86
|
+
btn.addEventListener('click', () => {
|
|
87
|
+
nav.querySelectorAll('.wd-codegroup-tab').forEach((b) => b.classList.remove('active'));
|
|
88
|
+
blocks.forEach((b) => (b.style.display = 'none'));
|
|
89
|
+
btn.classList.add('active');
|
|
90
|
+
block.style.display = '';
|
|
91
|
+
});
|
|
92
|
+
nav.appendChild(btn);
|
|
93
|
+
});
|
|
94
|
+
group.prepend(nav);
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
initCodeGroups(document);
|
|
98
|
+
document.addEventListener('astro:page-load', () => initCodeGroups(document));
|
|
99
|
+
</script>
|
|
100
|
+
<style is:global>
|
|
101
|
+
/* The single rounded border the docs promise ("a tabbed interface,
|
|
102
|
+
switching between snippets the same way Tabs does" - see
|
|
103
|
+
site/docs/content/components.mdx and kitchen-sink's own copy)
|
|
104
|
+
lives here now, on the group itself, not on each block -
|
|
105
|
+
overflow: hidden is load-bearing: every .wd-code-block inside loses
|
|
106
|
+
its own border-radius below, so it's this container's own radius
|
|
107
|
+
clipping the first/last block's corners (and, once tabbed, the
|
|
108
|
+
tab strip's own top corners) into that outer radius, not each
|
|
109
|
+
piece rounding itself. */
|
|
110
|
+
.wd-codegroup {
|
|
111
|
+
margin: 1.25rem 0;
|
|
112
|
+
border: 1px solid var(--wd-border);
|
|
113
|
+
border-radius: 0.55rem;
|
|
114
|
+
overflow: hidden;
|
|
115
|
+
}
|
|
116
|
+
/* Each fenced code block inside a <CodeGroup> is wrapped in a
|
|
117
|
+
.wd-code-block div by codeBlockTransformer (see astro.config.mjs /
|
|
118
|
+
src/lib/shiki-code-block.js) rather than being a bare <pre> - and
|
|
119
|
+
.wd-code-block itself now owns the card chrome (border/radius),
|
|
120
|
+
not the <pre> inside it (see [...slug].astro's base rule). Zeroes
|
|
121
|
+
out that per-block chrome entirely - margin (so blocks stack flush
|
|
122
|
+
with no visible gap between them, unlike a standalone block),
|
|
123
|
+
border and radius (both now the group's own job, above). */
|
|
124
|
+
.wd-codegroup > .wd-code-block {
|
|
125
|
+
margin: 0;
|
|
126
|
+
border: none;
|
|
127
|
+
border-radius: 0;
|
|
128
|
+
}
|
|
129
|
+
/* No-JS / too-small-to-tab fallback only - once initCodeGroups() above
|
|
130
|
+
hides every block but the active one, there's never two adjacent
|
|
131
|
+
*visible* blocks left for this to draw a line between, so it's
|
|
132
|
+
inert in the normal tabbed case. Without JS, every block stays
|
|
133
|
+
visible and stacked, and this is what still separates them. */
|
|
134
|
+
.wd-codegroup > .wd-code-block + .wd-code-block {
|
|
135
|
+
border-top: 1px solid var(--wd-border);
|
|
136
|
+
}
|
|
137
|
+
.wd-codegroup-nav {
|
|
138
|
+
display: flex;
|
|
139
|
+
gap: 0.25rem;
|
|
140
|
+
padding: 0 0.5rem;
|
|
141
|
+
overflow-x: auto;
|
|
142
|
+
background: var(--wd-surface);
|
|
143
|
+
border-bottom: 1px solid var(--wd-border);
|
|
144
|
+
}
|
|
145
|
+
.wd-codegroup-tab {
|
|
146
|
+
flex-shrink: 0;
|
|
147
|
+
padding: 0.55rem 0.7rem;
|
|
148
|
+
border: none;
|
|
149
|
+
border-bottom: 2px solid transparent;
|
|
150
|
+
background: none;
|
|
151
|
+
font-family: monospace;
|
|
152
|
+
font-size: 0.78rem;
|
|
153
|
+
font-weight: 500;
|
|
154
|
+
color: var(--wd-text-muted);
|
|
155
|
+
white-space: nowrap;
|
|
156
|
+
cursor: pointer;
|
|
157
|
+
}
|
|
158
|
+
.wd-codegroup-tab.active {
|
|
159
|
+
color: var(--wd-primary);
|
|
160
|
+
border-bottom-color: var(--wd-primary);
|
|
161
|
+
}
|
|
162
|
+
/* dropdown="true" case - same background/border-bottom as the tab
|
|
163
|
+
strip it replaces, so a RequestExample/ResponseExample's dropdown
|
|
164
|
+
mode still reads as the same "code block chrome" family. */
|
|
165
|
+
.wd-codegroup-select {
|
|
166
|
+
display: block;
|
|
167
|
+
width: 100%;
|
|
168
|
+
padding: 0.5rem 0.7rem;
|
|
169
|
+
border: none;
|
|
170
|
+
border-bottom: 1px solid var(--wd-border);
|
|
171
|
+
background: var(--wd-surface);
|
|
172
|
+
color: var(--wd-text);
|
|
173
|
+
font-family: monospace;
|
|
174
|
+
font-size: 0.78rem;
|
|
175
|
+
font-weight: 500;
|
|
176
|
+
cursor: pointer;
|
|
177
|
+
}
|
|
178
|
+
/* The tab/option label already shows this same text - see labelFor()
|
|
179
|
+
above - so the per-block title bar underneath would just repeat
|
|
180
|
+
it. */
|
|
181
|
+
.wd-codegroup-tabbed .wd-code-title {
|
|
182
|
+
display: none;
|
|
183
|
+
}
|
|
184
|
+
</style>
|
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
---
|
|
2
|
+
// The "Copy page" split button - opt-in via writedocs.json's `contextMenu`
|
|
3
|
+
// field (see contextMenuSchema in lib/config.ts). A primary button
|
|
4
|
+
// (always-visible "Copy page" action, copies this page's Markdown
|
|
5
|
+
// straight to the clipboard on click - see initCopyPageMenu() in
|
|
6
|
+
// [...slug].astro) plus a small caret-only button next to it that opens
|
|
7
|
+
// a dropdown for the rest (View as Markdown, Open in ChatGPT/Claude/
|
|
8
|
+
// Perplexity) - only the caret button is a .wd-dropdown-trigger, so only
|
|
9
|
+
// clicking (or hovering, per the shared CSS) *it* opens the menu, not
|
|
10
|
+
// the primary action next to it. The dropdown itself reuses the exact
|
|
11
|
+
// .wd-dropdown/.wd-dropdown-trigger/.wd-dropdown-menu/
|
|
12
|
+
// .wd-dropdown-menu-panel markup pattern BaseLayout.astro's topbar
|
|
13
|
+
// selectors already use - open/close/outside-click/Escape all come free
|
|
14
|
+
// from BaseLayout's initDropdowns(document), which queries `.wd-dropdown`
|
|
15
|
+
// anywhere in the page, not just its own template. See BaseLayout.astro's
|
|
16
|
+
// own comment on the :global() wrapping its copy of that CSS needed so
|
|
17
|
+
// the rules reach markup rendered by a different component (this one).
|
|
18
|
+
//
|
|
19
|
+
// Rendered from [...slug].astro (see its own comment on where/when),
|
|
20
|
+
// which passes in everything needed to build both same-origin links (the
|
|
21
|
+
// .md route from [...slug].md.ts) and the external "Open in X" links
|
|
22
|
+
// (which need an absolute URL - siteUrl is null on sites with no
|
|
23
|
+
// writedocs.json `domain` set, in which case those three are left out
|
|
24
|
+
// entirely rather than linking a service at a URL it could never fetch).
|
|
25
|
+
import { Icon } from 'astro-icon/components';
|
|
26
|
+
import type { ContextMenuConfig } from '../lib/config';
|
|
27
|
+
|
|
28
|
+
interface Props {
|
|
29
|
+
currentPath: string; // e.g. "/" or "/guides/foo/" - see hrefForSlug() in [...slug].astro
|
|
30
|
+
siteUrl: string | null;
|
|
31
|
+
contextMenu: ContextMenuConfig;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const { currentPath, siteUrl, contextMenu } = Astro.props as Props;
|
|
35
|
+
|
|
36
|
+
// The [...slug].md.ts route this page is also served at - same
|
|
37
|
+
// normalizeEntryId()-driven convention that file uses to build its own
|
|
38
|
+
// getStaticPaths params ("/" -> "index.md", everything else -> its own
|
|
39
|
+
// slug with a literal ".md" appended instead of the trailing slash).
|
|
40
|
+
const mdPath = currentPath === '/' ? '/index.md' : `${currentPath.replace(/\/$/, '')}.md`;
|
|
41
|
+
const mdAbsoluteUrl = siteUrl ? `${siteUrl}${mdPath}` : null;
|
|
42
|
+
|
|
43
|
+
// Well-known query-string conventions for starting a new conversation
|
|
44
|
+
// pre-seeded with a prompt (not an official API for any of the three -
|
|
45
|
+
// just the same `?q=`/`/new?q=`/`/search?q=` pattern each already
|
|
46
|
+
// supports for search-engine-style deep links). Pointing the prompt at
|
|
47
|
+
// this page's own .md URL rather than pasting the page content directly
|
|
48
|
+
// into the query string keeps the link itself short and lets the
|
|
49
|
+
// assistant fetch the current content instead of a snapshot baked into
|
|
50
|
+
// a bookmarked/shared link.
|
|
51
|
+
const promptFor = (url: string) => `Read ${url} so you can answer questions about it.`;
|
|
52
|
+
|
|
53
|
+
const assistants = [
|
|
54
|
+
{ id: 'chatgpt' as const, label: 'ChatGPT', icon: 'simple-icons:openai', buildHref: (q: string) => `https://chatgpt.com/?q=${q}` },
|
|
55
|
+
{ id: 'claude' as const, label: 'Claude', icon: 'simple-icons:claude', buildHref: (q: string) => `https://claude.ai/new?q=${q}` },
|
|
56
|
+
{ id: 'perplexity' as const, label: 'Perplexity', icon: 'simple-icons:perplexity', buildHref: (q: string) => `https://www.perplexity.ai/search?q=${q}` },
|
|
57
|
+
];
|
|
58
|
+
|
|
59
|
+
// No siteUrl at all -> mdAbsoluteUrl is null -> none of these render,
|
|
60
|
+
// regardless of what contextMenu.openIn lists: a link these services
|
|
61
|
+
// could never actually fetch (a bare relative path, meaningless once
|
|
62
|
+
// it's opened on a different origin) is worse than not offering it.
|
|
63
|
+
const enabledAssistants = mdAbsoluteUrl
|
|
64
|
+
? assistants.filter((a) => contextMenu.openIn.includes(a.id))
|
|
65
|
+
: [];
|
|
66
|
+
---
|
|
67
|
+
<div class="wd-copy-page-split">
|
|
68
|
+
<button type="button" class="wd-copy-page-primary" data-md-path={mdPath} aria-label="Copy page as Markdown">
|
|
69
|
+
<Icon name="lucide:copy" class="wd-copy-page-icon" />
|
|
70
|
+
<span class="wd-copy-page-primary-label">Copy page</span>
|
|
71
|
+
</button>
|
|
72
|
+
<div class="wd-dropdown wd-copy-page">
|
|
73
|
+
<button type="button" class="wd-dropdown-trigger wd-copy-page-caret-trigger" aria-haspopup="true" aria-expanded="false" aria-label="More copy options">
|
|
74
|
+
<svg class="wd-dropdown-caret" width="10" height="10" viewBox="0 0 10 10" aria-hidden="true">
|
|
75
|
+
<path d="M2 3.5L5 6.5L8 3.5" stroke="currentColor" stroke-width="1.4" fill="none" stroke-linecap="round" stroke-linejoin="round" />
|
|
76
|
+
</svg>
|
|
77
|
+
</button>
|
|
78
|
+
<div class="wd-dropdown-menu wd-copy-page-menu" role="menu">
|
|
79
|
+
<div class="wd-dropdown-menu-panel wd-copy-page-menu-panel">
|
|
80
|
+
<a class="wd-copy-page-item" href={mdPath} target="_blank" rel="noopener">
|
|
81
|
+
<Icon name="lucide:external-link" class="wd-copy-page-icon" />
|
|
82
|
+
<span class="wd-copy-page-item-text">
|
|
83
|
+
<span class="wd-copy-page-item-title">View as Markdown</span>
|
|
84
|
+
<span class="wd-copy-page-item-desc">View this page as plain text</span>
|
|
85
|
+
</span>
|
|
86
|
+
</a>
|
|
87
|
+
{enabledAssistants.map((a) => (
|
|
88
|
+
<a class="wd-copy-page-item" href={a.buildHref(encodeURIComponent(promptFor(mdAbsoluteUrl!)))} target="_blank" rel="noopener">
|
|
89
|
+
<Icon name={a.icon} class="wd-copy-page-icon" />
|
|
90
|
+
<span class="wd-copy-page-item-text">
|
|
91
|
+
<span class="wd-copy-page-item-title">Open in {a.label}</span>
|
|
92
|
+
<span class="wd-copy-page-item-desc">Ask questions about this page</span>
|
|
93
|
+
</span>
|
|
94
|
+
</a>
|
|
95
|
+
))}
|
|
96
|
+
</div>
|
|
97
|
+
</div>
|
|
98
|
+
</div>
|
|
99
|
+
</div>
|
|
100
|
+
|
|
101
|
+
<style>
|
|
102
|
+
/* The split button: a primary "Copy page" action (always visible,
|
|
103
|
+
copies on click - see initCopyPageMenu() in [...slug].astro) and a
|
|
104
|
+
small caret-only button next to it that opens the rest (View as
|
|
105
|
+
Markdown, Open in X). Each button gets its own half of the shared
|
|
106
|
+
border-radius (left corners on the primary button, right corners on
|
|
107
|
+
the caret button) rather than `overflow: hidden` on the wrapper -
|
|
108
|
+
overflow: hidden was tried first and clips more than intended: the
|
|
109
|
+
dropdown menu itself is an absolutely-positioned descendant of
|
|
110
|
+
.wd-dropdown, which lives inside this wrapper, so clipping the
|
|
111
|
+
wrapper's overflow silently clipped the open menu away too (the
|
|
112
|
+
caret still visibly rotated on hover/open, since that's a transform
|
|
113
|
+
on the caret itself, not the menu - only the menu's own visibility
|
|
114
|
+
was affected, which is what made this look like "nothing happens"
|
|
115
|
+
rather than an obvious rendering glitch). */
|
|
116
|
+
.wd-copy-page-split {
|
|
117
|
+
display: flex;
|
|
118
|
+
align-items: stretch;
|
|
119
|
+
border: 1px solid var(--wd-border);
|
|
120
|
+
border-radius: 0.4rem;
|
|
121
|
+
flex-shrink: 0;
|
|
122
|
+
}
|
|
123
|
+
/* .wd-copy-page is this instance's own class on the .wd-dropdown div
|
|
124
|
+
wrapping the caret button + menu (see BaseLayout.astro's shared
|
|
125
|
+
.wd-dropdown rule for `position: relative`, which still applies
|
|
126
|
+
alongside this). .wd-copy-page-split's `align-items: stretch` above
|
|
127
|
+
stretches this div's own height to match the primary button next to
|
|
128
|
+
it, but that alone doesn't stretch the *caret button inside it* -
|
|
129
|
+
.wd-dropdown is a plain block div, not a flex container, so its
|
|
130
|
+
child sizes itself independently and just sits at the top of the
|
|
131
|
+
now-taller div, which is what made the arrow look vertically
|
|
132
|
+
off-center against the primary button. Making this div a flex
|
|
133
|
+
container too (with its own stretch) propagates the height down to
|
|
134
|
+
the button, which centers the svg within it via the shared
|
|
135
|
+
.wd-dropdown-trigger rule's own align-items: center. */
|
|
136
|
+
.wd-copy-page {
|
|
137
|
+
display: flex;
|
|
138
|
+
align-items: stretch;
|
|
139
|
+
}
|
|
140
|
+
.wd-copy-page-primary {
|
|
141
|
+
display: flex;
|
|
142
|
+
align-items: center;
|
|
143
|
+
gap: 0.35rem;
|
|
144
|
+
padding: 0.35rem 0.65rem;
|
|
145
|
+
border: none;
|
|
146
|
+
border-radius: 0.35rem 0 0 0.35rem;
|
|
147
|
+
background: none;
|
|
148
|
+
cursor: pointer;
|
|
149
|
+
color: var(--wd-text-muted);
|
|
150
|
+
font-size: 0.85rem;
|
|
151
|
+
font-weight: 500;
|
|
152
|
+
font-family: inherit;
|
|
153
|
+
}
|
|
154
|
+
.wd-copy-page-primary:hover {
|
|
155
|
+
color: var(--wd-text);
|
|
156
|
+
background: var(--wd-surface);
|
|
157
|
+
}
|
|
158
|
+
/* Toggled by initCopyPageMenu() for ~1.5s after a successful copy -
|
|
159
|
+
the label itself swaps to "Copied!" via JS (not CSS content, so a
|
|
160
|
+
screen reader announces it), this just tints icon+text green to
|
|
161
|
+
match. */
|
|
162
|
+
.wd-copy-page-primary.wd-copy-page-copied {
|
|
163
|
+
color: #16a34a;
|
|
164
|
+
}
|
|
165
|
+
/* Overrides the shared .wd-dropdown-trigger base rule (BaseLayout.astro)
|
|
166
|
+
down to an icon-only square button - no padding/border of its own
|
|
167
|
+
beyond the divider line, and only the right corners rounded (to
|
|
168
|
+
match .wd-copy-page-split's own outer radius on that side - see the
|
|
169
|
+
comment above on why the wrapper itself no longer clips to its
|
|
170
|
+
radius via overflow: hidden). */
|
|
171
|
+
.wd-copy-page-caret-trigger {
|
|
172
|
+
padding: 0.35rem 0.5rem;
|
|
173
|
+
border: none;
|
|
174
|
+
border-left: 1px solid var(--wd-border);
|
|
175
|
+
border-radius: 0 0.35rem 0.35rem 0;
|
|
176
|
+
}
|
|
177
|
+
.wd-copy-page-menu {
|
|
178
|
+
left: auto;
|
|
179
|
+
right: 0;
|
|
180
|
+
}
|
|
181
|
+
/* overflow: hidden here (in addition to .wd-copy-page-item below no
|
|
182
|
+
longer overflowing its own box) is defensive belt-and-suspenders -
|
|
183
|
+
keeps any future item's hover background from ever visibly poking
|
|
184
|
+
past the panel's own rounded corners, the exact bug .wd-copy-page-item
|
|
185
|
+
used to have. */
|
|
186
|
+
.wd-copy-page-menu-panel {
|
|
187
|
+
min-width: 260px;
|
|
188
|
+
overflow: hidden;
|
|
189
|
+
}
|
|
190
|
+
.wd-copy-page-icon {
|
|
191
|
+
width: 1em;
|
|
192
|
+
height: 1em;
|
|
193
|
+
flex-shrink: 0;
|
|
194
|
+
}
|
|
195
|
+
/* .wd-dropdown-menu a (BaseLayout.astro) is single-line only
|
|
196
|
+
(align-items: center on one row of text) - these items need a
|
|
197
|
+
title + description subtitle, so .wd-copy-page-item re-implements
|
|
198
|
+
that layout instead (icon top-aligned next to a two-line text
|
|
199
|
+
stack) rather than trying to make the shared single-line rule cover
|
|
200
|
+
both shapes.
|
|
201
|
+
No explicit `width` here - deliberately: this codebase doesn't set
|
|
202
|
+
a global `box-sizing: border-box` reset (see the comment on
|
|
203
|
+
Tailwind's preflight in src/styles/global.css), so a block-level
|
|
204
|
+
element's default `width: auto` already correctly fills its
|
|
205
|
+
container *net of* its own padding, while an explicit `width: 100%`
|
|
206
|
+
would add that padding on top instead, overflowing past the
|
|
207
|
+
container's edge - exactly what an earlier version of this rule did
|
|
208
|
+
(visible as the hover background bleeding out past the panel's
|
|
209
|
+
rounded corner on hover). Leave width unset rather than reaching for
|
|
210
|
+
`box-sizing: border-box` here alone, which would just be one rule
|
|
211
|
+
quietly relying on a codebase-wide convention it doesn't establish. */
|
|
212
|
+
.wd-copy-page-item {
|
|
213
|
+
display: flex;
|
|
214
|
+
align-items: flex-start;
|
|
215
|
+
gap: 0.6rem;
|
|
216
|
+
padding: 0.5rem 0.6rem;
|
|
217
|
+
border: none;
|
|
218
|
+
border-radius: 0.35rem;
|
|
219
|
+
background: none;
|
|
220
|
+
text-decoration: none;
|
|
221
|
+
color: var(--wd-text);
|
|
222
|
+
font-family: inherit;
|
|
223
|
+
text-align: left;
|
|
224
|
+
cursor: pointer;
|
|
225
|
+
}
|
|
226
|
+
.wd-copy-page-item:hover {
|
|
227
|
+
background: var(--wd-surface);
|
|
228
|
+
}
|
|
229
|
+
.wd-copy-page-item .wd-copy-page-icon {
|
|
230
|
+
margin-top: 0.15rem;
|
|
231
|
+
color: var(--wd-text-muted);
|
|
232
|
+
}
|
|
233
|
+
.wd-copy-page-item-text {
|
|
234
|
+
display: flex;
|
|
235
|
+
flex-direction: column;
|
|
236
|
+
gap: 0.1rem;
|
|
237
|
+
}
|
|
238
|
+
.wd-copy-page-item-title {
|
|
239
|
+
font-size: 0.86rem;
|
|
240
|
+
font-weight: 500;
|
|
241
|
+
}
|
|
242
|
+
.wd-copy-page-item-desc {
|
|
243
|
+
font-size: 0.76rem;
|
|
244
|
+
color: var(--wd-text-muted);
|
|
245
|
+
}
|
|
246
|
+
</style>
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
---
|
|
2
|
+
// Shorthand for <Callout type="danger">. See Callout.astro's own comment.
|
|
3
|
+
import Callout from './Callout.astro';
|
|
4
|
+
interface Props {
|
|
5
|
+
title?: string;
|
|
6
|
+
// See Callout.astro's own comment - set automatically by
|
|
7
|
+
// remarkCalloutAnchorIds, not meant to be passed by hand.
|
|
8
|
+
_titleId?: string;
|
|
9
|
+
}
|
|
10
|
+
const { title, _titleId } = Astro.props as Props;
|
|
11
|
+
---
|
|
12
|
+
<Callout type="danger" title={title} _titleId={_titleId}><slot /></Callout>
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
---
|
|
2
|
+
// The collapsible "Show/Hide properties" wrapper that pairs with
|
|
3
|
+
// Parameter for nested object properties - modeled directly on
|
|
4
|
+
// Mintlify's own Expandable (https://www.mintlify.com/docs/components/
|
|
5
|
+
// expandables), the reference this was built against. Structurally
|
|
6
|
+
// near-identical to Accordion.astro (same <details>/<summary>, same
|
|
7
|
+
// custom chevron svg, same border/radius language) - deliberately reused
|
|
8
|
+
// rather than reinvented, since "apply the same styles as our api
|
|
9
|
+
// pages" means this should read as the same family of collapsible box
|
|
10
|
+
// as everything else in this site, not a one-off. Two real differences
|
|
11
|
+
// from Accordion, both because the two components solve different
|
|
12
|
+
// problems: this defaults to *open* (Accordion defaults closed - an
|
|
13
|
+
// Expandable's whole point in the reference design is showing a nested
|
|
14
|
+
// object's properties immediately, with room to collapse if they're
|
|
15
|
+
// not needed, the opposite default of an FAQ-style Accordion), and its
|
|
16
|
+
// toggle label is computed ("Show "/"Hide " + title) rather than a
|
|
17
|
+
// fixed heading, updated live via the <script> below rather than at
|
|
18
|
+
// build time - a bare CSS/HTML approach can't express "this text
|
|
19
|
+
// depends on live open/closed state" the way a details' own [open]
|
|
20
|
+
// attribute can drive a CSS transform.
|
|
21
|
+
interface Props {
|
|
22
|
+
title?: string;
|
|
23
|
+
defaultOpen?: boolean;
|
|
24
|
+
}
|
|
25
|
+
const { title = "properties", defaultOpen = true } = Astro.props as Props;
|
|
26
|
+
---
|
|
27
|
+
|
|
28
|
+
<details class="wd-expandable" open={defaultOpen}>
|
|
29
|
+
<summary>
|
|
30
|
+
<svg class="wd-expandable-chevron" width="11" height="11" viewBox="0 0 10 10" aria-hidden="true">
|
|
31
|
+
<path
|
|
32
|
+
d="M2 3.5L5 6.5L8 3.5"
|
|
33
|
+
stroke="currentColor"
|
|
34
|
+
stroke-width="1.4"
|
|
35
|
+
fill="none"
|
|
36
|
+
stroke-linecap="round"
|
|
37
|
+
stroke-linejoin="round"></path>
|
|
38
|
+
</svg>
|
|
39
|
+
<span class="wd-expandable-label" data-title={title}>{defaultOpen ? `Hide ${title}` : `Show ${title}`}</span>
|
|
40
|
+
</summary>
|
|
41
|
+
<div class="wd-expandable-body"><slot /></div>
|
|
42
|
+
</details>
|
|
43
|
+
<script>
|
|
44
|
+
// The label text isn't a static title (unlike Accordion's own
|
|
45
|
+
// <summary>) - it flips between "Show "/"Hide " + title as the
|
|
46
|
+
// <details> element's own open/closed state changes, so it has to be
|
|
47
|
+
// driven by the native `toggle` event rather than expressed purely
|
|
48
|
+
// in markup/CSS. data-title (set from the `title` prop above) is
|
|
49
|
+
// what the prefix gets rebuilt around each time.
|
|
50
|
+
function initExpandables(root: ParentNode) {
|
|
51
|
+
root.querySelectorAll<HTMLDetailsElement>(".wd-expandable").forEach((details) => {
|
|
52
|
+
if (details.dataset.wdInit) return;
|
|
53
|
+
details.dataset.wdInit = "true";
|
|
54
|
+
const label = details.querySelector<HTMLElement>(".wd-expandable-label");
|
|
55
|
+
details.addEventListener("toggle", () => {
|
|
56
|
+
const title = label?.dataset.title ?? "properties";
|
|
57
|
+
if (label) label.textContent = `${details.open ? "Hide" : "Show"} ${title}`;
|
|
58
|
+
});
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
initExpandables(document);
|
|
62
|
+
document.addEventListener("astro:page-load", () => initExpandables(document));
|
|
63
|
+
</script>
|
|
64
|
+
<style>
|
|
65
|
+
.wd-expandable {
|
|
66
|
+
border: 1px solid var(--wd-border);
|
|
67
|
+
border-radius: 0.6rem;
|
|
68
|
+
margin: 0.75rem 0;
|
|
69
|
+
overflow: hidden;
|
|
70
|
+
}
|
|
71
|
+
.wd-expandable summary {
|
|
72
|
+
display: flex;
|
|
73
|
+
align-items: center;
|
|
74
|
+
gap: 0.6rem;
|
|
75
|
+
padding: 0.5rem 1rem;
|
|
76
|
+
cursor: pointer;
|
|
77
|
+
list-style: none;
|
|
78
|
+
}
|
|
79
|
+
/* Firefox/Chrome use ::marker for the default disclosure triangle,
|
|
80
|
+
Safari still needs the older, WebKit-specific pseudo-element -
|
|
81
|
+
both zeroed out so only the svg above ever shows. Same as
|
|
82
|
+
Accordion.astro's own identical rules. */
|
|
83
|
+
.wd-expandable summary::-webkit-details-marker {
|
|
84
|
+
display: none;
|
|
85
|
+
}
|
|
86
|
+
.wd-expandable summary::marker {
|
|
87
|
+
content: "";
|
|
88
|
+
}
|
|
89
|
+
.wd-expandable summary:hover {
|
|
90
|
+
background: var(--wd-surface);
|
|
91
|
+
}
|
|
92
|
+
/* The divider between the "Show/Hide" row and the fields beneath it -
|
|
93
|
+
only drawn while open, matching the reference design (a closed
|
|
94
|
+
Expandable has nothing below the row to separate from, so drawing
|
|
95
|
+
a border under it while collapsed would just be an orphaned line
|
|
96
|
+
under an otherwise plain pill). */
|
|
97
|
+
.wd-expandable[open] > summary {
|
|
98
|
+
border-bottom: 1px solid var(--wd-border);
|
|
99
|
+
}
|
|
100
|
+
.wd-expandable-chevron {
|
|
101
|
+
flex-shrink: 0;
|
|
102
|
+
color: var(--wd-text-muted);
|
|
103
|
+
transition: transform 0.15s ease;
|
|
104
|
+
}
|
|
105
|
+
.wd-expandable[open] > summary .wd-expandable-chevron {
|
|
106
|
+
transform: rotate(180deg);
|
|
107
|
+
}
|
|
108
|
+
/* Plain weight + muted color, not bold - unlike Accordion's own
|
|
109
|
+
title (a real heading for the section beneath it), this label is a
|
|
110
|
+
UI affordance ("there's more, click to see it"/"click to hide it
|
|
111
|
+
again"), not content in its own right, matching the lighter-weight
|
|
112
|
+
treatment the reference design uses for it. */
|
|
113
|
+
.wd-expandable-label {
|
|
114
|
+
color: var(--wd-text-muted);
|
|
115
|
+
font-size: 0.75rem;
|
|
116
|
+
}
|
|
117
|
+
/* No top padding on the body itself - Parameter's own top-level
|
|
118
|
+
padding (0.9rem 0 - see its own comment) is exactly the gap wanted
|
|
119
|
+
between the divider above and the first field's name/type row, so
|
|
120
|
+
adding more here would just double it up. Bottom/side padding
|
|
121
|
+
still applies normally since Parameter has no matching
|
|
122
|
+
horizontal inset of its own. */
|
|
123
|
+
.wd-expandable-body {
|
|
124
|
+
padding: 0 1rem 0.9rem;
|
|
125
|
+
}
|
|
126
|
+
</style>
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
---
|
|
2
|
+
// A generic "put a rounded, bordered card around this" wrapper -
|
|
3
|
+
// deliberately different from Image (a specific <img> with its own
|
|
4
|
+
// src/srcDark/size handling): Frame wraps *arbitrary* slot content - a
|
|
5
|
+
// raw HTML <img>, a markdown ![]() image (which the markdown pipeline
|
|
6
|
+
// still turns into a real <img>, just possibly wrapped in a <p> by the
|
|
7
|
+
// surrounding paragraph parsing), a <video>, an <iframe> embed, or
|
|
8
|
+
// anything else a site author already has and just wants the card
|
|
9
|
+
// treatment on - matching Mintlify's own <Frame>, which exists for
|
|
10
|
+
// exactly this "I already have markup, give it the frame" case rather
|
|
11
|
+
// than duplicating Image's own prop-driven API. Nothing here about this
|
|
12
|
+
// component's name collides with frontmatter's own `mode: "frame"`
|
|
13
|
+
// page-rendering option (BaseLayout.astro) - that's an unrelated,
|
|
14
|
+
// page-level concept keyed off a plain string, not a JSX component.
|
|
15
|
+
interface Props {
|
|
16
|
+
caption?: string;
|
|
17
|
+
}
|
|
18
|
+
const { caption } = Astro.props as Props;
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
<figure class="wd-frame">
|
|
22
|
+
<div class="wd-frame-content">
|
|
23
|
+
<slot />
|
|
24
|
+
</div>
|
|
25
|
+
{caption && <figcaption class="wd-frame-caption">{caption}</figcaption>}
|
|
26
|
+
</figure>
|
|
27
|
+
<style>
|
|
28
|
+
/* Border/background/padding live on the outer <figure>, not the inner
|
|
29
|
+
content div, so a caption renders *inside* the same bordered card as
|
|
30
|
+
the media above it - one continuous box, not a border tight around
|
|
31
|
+
just the image with plain caption text floating separately below it
|
|
32
|
+
(an earlier version of this file did exactly that; a real reference
|
|
33
|
+
screenshot showing the border wrapping the caption too is what this
|
|
34
|
+
was rebuilt against). Image.astro's own caption case mirrors this
|
|
35
|
+
same card language for the same reason - see its own comment. */
|
|
36
|
+
.wd-frame {
|
|
37
|
+
margin: 1.25rem 0;
|
|
38
|
+
border: 1px solid var(--wd-border);
|
|
39
|
+
border-radius: 0.75rem;
|
|
40
|
+
background: var(--wd-surface);
|
|
41
|
+
padding: 0.5rem;
|
|
42
|
+
/* Belt-and-suspenders alongside the border - see Image.astro's own
|
|
43
|
+
identical box-shadow comment. var(--wd-border)/var(--wd-surface)
|
|
44
|
+
are low-contrast by default, and a real user report traced an
|
|
45
|
+
"invisible card" complaint to exactly that: a demo asset that
|
|
46
|
+
happened to be the same image as the page's own background,
|
|
47
|
+
leaving nothing but a barely-visible 1px line to signal a card
|
|
48
|
+
was even there. A shadow reads regardless of border/background
|
|
49
|
+
contrast against whatever the page looks like. */
|
|
50
|
+
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.08);
|
|
51
|
+
}
|
|
52
|
+
.wd-frame-content {
|
|
53
|
+
display: flex;
|
|
54
|
+
justify-content: center;
|
|
55
|
+
align-items: center;
|
|
56
|
+
}
|
|
57
|
+
/* Slot content isn't rendered by this component's own template - it's
|
|
58
|
+
whatever markup the author put between <Frame> and </Frame> (a raw
|
|
59
|
+
<img>, markdown's own ![]() output, possibly wrapped in a <p> by the
|
|
60
|
+
surrounding paragraph parsing) - same as Accordion-body's own
|
|
61
|
+
:global(p:first-child)/:global(p:last-child) rules, this needs
|
|
62
|
+
:global() to reach it at all; without it these selectors would
|
|
63
|
+
compile with this component's own scope attribute and never match
|
|
64
|
+
anything actually inside <slot />. iframe included alongside img/
|
|
65
|
+
video since an embed (a YouTube player, a CodeSandbox, ...) is just
|
|
66
|
+
as plausible a Frame child as an image - border: 0 clears the
|
|
67
|
+
default inset border some browsers still draw around a bare
|
|
68
|
+
<iframe>, which would otherwise double up with this card's own
|
|
69
|
+
border. border-radius on the media itself (rather than clipping via
|
|
70
|
+
the container's own overflow: hidden) matches Image's own approach -
|
|
71
|
+
modern browsers clip img/video/iframe content to their own
|
|
72
|
+
border-radius directly, no wrapping element needed purely for that. */
|
|
73
|
+
.wd-frame-content :global(img),
|
|
74
|
+
.wd-frame-content :global(video),
|
|
75
|
+
.wd-frame-content :global(iframe) {
|
|
76
|
+
display: block;
|
|
77
|
+
width: 100%;
|
|
78
|
+
max-width: 100%;
|
|
79
|
+
height: auto;
|
|
80
|
+
margin: 0;
|
|
81
|
+
border: 0;
|
|
82
|
+
border-radius: 0.5rem;
|
|
83
|
+
}
|
|
84
|
+
/* A markdown ![]() image lands wrapped in its own <p> (the surrounding
|
|
85
|
+
paragraph the image syntax was written inside) - display: contents
|
|
86
|
+
drops that wrapper's own box entirely, so the width: 100% rule above
|
|
87
|
+
still sizes the <img> itself against .wd-frame-content directly,
|
|
88
|
+
regardless of whether the author wrote a raw <img> (no wrapping <p>
|
|
89
|
+
at all) or markdown image syntax (always one). Harmless no-op for
|
|
90
|
+
any other block-level slot content (a code block, a Callout, ...) -
|
|
91
|
+
display: contents only changes how *this* one wrapper participates
|
|
92
|
+
in layout, never anything it contains. */
|
|
93
|
+
.wd-frame-content :global(p) {
|
|
94
|
+
display: contents;
|
|
95
|
+
}
|
|
96
|
+
.wd-frame-caption {
|
|
97
|
+
margin: 0.6rem 0 0;
|
|
98
|
+
text-align: center;
|
|
99
|
+
color: var(--wd-text-muted);
|
|
100
|
+
font-size: 0.75rem;
|
|
101
|
+
}
|
|
102
|
+
</style>
|