@writedocs/generator 0.4.7 → 0.4.8
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/astro.config.mjs +7 -0
- package/package.json +1 -1
- package/src/components/Color.astro +61 -0
- package/src/components/ColorItem.astro +93 -0
- package/src/components/ColorRow.astro +39 -0
- package/src/components/GitHubRepo.astro +156 -0
- package/src/components/Panel.astro +11 -0
- package/src/components/Prompt.astro +143 -0
- package/src/components/Tile.astro +65 -0
- package/src/components/Tree.astro +213 -0
- package/src/components/TreeFile.astro +17 -0
- package/src/components/TreeFolder.astro +28 -0
- package/src/components/Update.astro +171 -0
- package/src/components/View.astro +214 -0
- package/src/components/Visibility.astro +11 -0
- package/src/components/compound.ts +21 -0
- package/src/components/index.ts +7 -0
- package/src/lib/inline-markdown.js +29 -0
- package/src/lib/mdx-inject-builtins.js +15 -2
- package/src/lib/mdx-mintlify.js +99 -0
- package/src/lib/mdx-title-anchor-ids.js +7 -2
- package/src/lib/visibility.js +29 -0
- package/src/pages/[...slug].astro +23 -1
- package/src/pages/[...slug].md.ts +4 -1
- package/src/pages/llms-full.txt.ts +3 -1
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
---
|
|
2
|
+
// Mintlify's <Tree> (alias <FileTree>) - a file/folder tree built from
|
|
3
|
+
// <Tree.Folder> and <Tree.File> (TreeFolder.astro / TreeFile.astro,
|
|
4
|
+
// attached as properties in components/compound.ts), or from a Markdown
|
|
5
|
+
// list, which remarkMintlifyTreeLists (lib/mdx-mintlify.js) rewrites into
|
|
6
|
+
// those same elements at build time.
|
|
7
|
+
//
|
|
8
|
+
// Rendered as an ARIA tree. The script below adds Mintlify's documented
|
|
9
|
+
// keyboard navigation: Up/Down between visible items, Right to open a
|
|
10
|
+
// folder (or step into it), Left to close it (or step out to its parent),
|
|
11
|
+
// Home/End, Enter/Space to toggle, * to open every sibling folder, and
|
|
12
|
+
// type-ahead to jump to an item by its first letters.
|
|
13
|
+
//
|
|
14
|
+
// The highlight color is the `--tree-highlight` custom property, which a
|
|
15
|
+
// page can set on the tree itself: <Tree style={{ "--tree-highlight": "..." }}>.
|
|
16
|
+
interface Props {
|
|
17
|
+
style?: string | Record<string, string>;
|
|
18
|
+
class?: string;
|
|
19
|
+
className?: string;
|
|
20
|
+
}
|
|
21
|
+
const { style, class: className, className: mintlifyClassName } = Astro.props as Props;
|
|
22
|
+
---
|
|
23
|
+
<ul class:list={['wd-tree', className, mintlifyClassName]} role="tree" style={style}><slot /></ul>
|
|
24
|
+
<script>
|
|
25
|
+
function initTrees(root: ParentNode) {
|
|
26
|
+
root.querySelectorAll<HTMLElement>('.wd-tree').forEach((tree) => {
|
|
27
|
+
if (tree.dataset.wdInit) return;
|
|
28
|
+
tree.dataset.wdInit = 'true';
|
|
29
|
+
const items = () => Array.from(tree.querySelectorAll<HTMLElement>('.wd-tree-item'));
|
|
30
|
+
const isFolder = (el: HTMLElement) => el.classList.contains('wd-tree-folder');
|
|
31
|
+
const isOpen = (el: HTMLElement) => el.getAttribute('aria-expanded') === 'true';
|
|
32
|
+
// Visible = not inside a closed folder.
|
|
33
|
+
const visible = () =>
|
|
34
|
+
items().filter((el) => {
|
|
35
|
+
let parent = el.parentElement?.closest<HTMLElement>('.wd-tree-folder');
|
|
36
|
+
while (parent && tree.contains(parent)) {
|
|
37
|
+
if (!isOpen(parent)) return false;
|
|
38
|
+
parent = parent.parentElement?.closest<HTMLElement>('.wd-tree-folder');
|
|
39
|
+
}
|
|
40
|
+
return true;
|
|
41
|
+
});
|
|
42
|
+
const setOpen = (el: HTMLElement, open: boolean) => {
|
|
43
|
+
if (!isFolder(el) || el.classList.contains('wd-tree-static')) return;
|
|
44
|
+
el.setAttribute('aria-expanded', String(open));
|
|
45
|
+
};
|
|
46
|
+
const focus = (el: HTMLElement | undefined) => {
|
|
47
|
+
if (!el) return;
|
|
48
|
+
items().forEach((i) => (i.tabIndex = -1));
|
|
49
|
+
el.tabIndex = 0;
|
|
50
|
+
el.focus();
|
|
51
|
+
};
|
|
52
|
+
const first = items()[0];
|
|
53
|
+
if (first) first.tabIndex = 0;
|
|
54
|
+
|
|
55
|
+
tree.addEventListener('click', (e) => {
|
|
56
|
+
const row = (e.target as HTMLElement).closest('.wd-tree-row');
|
|
57
|
+
const item = row?.parentElement as HTMLElement | null;
|
|
58
|
+
if (!item || !tree.contains(item)) return;
|
|
59
|
+
if (isFolder(item)) setOpen(item, !isOpen(item));
|
|
60
|
+
focus(item);
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
let typed = '';
|
|
64
|
+
let typedAt = 0;
|
|
65
|
+
tree.addEventListener('keydown', (e) => {
|
|
66
|
+
const current = (e.target as HTMLElement).closest<HTMLElement>('.wd-tree-item');
|
|
67
|
+
if (!current) return;
|
|
68
|
+
const list = visible();
|
|
69
|
+
const i = list.indexOf(current);
|
|
70
|
+
const parentFolder = current.parentElement?.closest<HTMLElement>('.wd-tree-folder');
|
|
71
|
+
switch (e.key) {
|
|
72
|
+
case 'ArrowDown':
|
|
73
|
+
focus(list[i + 1]);
|
|
74
|
+
break;
|
|
75
|
+
case 'ArrowUp':
|
|
76
|
+
focus(list[i - 1]);
|
|
77
|
+
break;
|
|
78
|
+
case 'ArrowRight':
|
|
79
|
+
if (isFolder(current) && !isOpen(current) && !current.classList.contains('wd-tree-static')) setOpen(current, true);
|
|
80
|
+
else if (isFolder(current) && isOpen(current)) focus(current.querySelector<HTMLElement>('.wd-tree-item') ?? undefined);
|
|
81
|
+
break;
|
|
82
|
+
case 'ArrowLeft':
|
|
83
|
+
if (isFolder(current) && isOpen(current) && !current.classList.contains('wd-tree-static')) setOpen(current, false);
|
|
84
|
+
else if (parentFolder && tree.contains(parentFolder)) focus(parentFolder);
|
|
85
|
+
break;
|
|
86
|
+
case 'Home':
|
|
87
|
+
focus(list[0]);
|
|
88
|
+
break;
|
|
89
|
+
case 'End':
|
|
90
|
+
focus(list[list.length - 1]);
|
|
91
|
+
break;
|
|
92
|
+
case 'Enter':
|
|
93
|
+
case ' ':
|
|
94
|
+
if (isFolder(current)) setOpen(current, !isOpen(current));
|
|
95
|
+
break;
|
|
96
|
+
case '*':
|
|
97
|
+
Array.from(current.parentElement?.children ?? [])
|
|
98
|
+
.filter((el): el is HTMLElement => el instanceof HTMLElement && isFolder(el))
|
|
99
|
+
.forEach((el) => setOpen(el, true));
|
|
100
|
+
break;
|
|
101
|
+
default:
|
|
102
|
+
if (e.key.length === 1 && /\S/.test(e.key) && !e.ctrlKey && !e.metaKey && !e.altKey) {
|
|
103
|
+
const now = Date.now();
|
|
104
|
+
typed = now - typedAt > 700 ? e.key.toLowerCase() : typed + e.key.toLowerCase();
|
|
105
|
+
typedAt = now;
|
|
106
|
+
const nameOf = (el: HTMLElement) =>
|
|
107
|
+
el.querySelector(':scope > .wd-tree-row .wd-tree-name')?.textContent?.toLowerCase() ?? '';
|
|
108
|
+
// A single letter looks past the current item; a longer
|
|
109
|
+
// prefix (typed quickly) keeps matching from it, so "pa"
|
|
110
|
+
// after "p" landed on page.tsx stays there.
|
|
111
|
+
const startAt = typed.length > 1 ? i : i + 1;
|
|
112
|
+
const ordered = [...list.slice(startAt), ...list.slice(0, startAt)];
|
|
113
|
+
const match = ordered.find((el) => nameOf(el).startsWith(typed));
|
|
114
|
+
if (match) focus(match);
|
|
115
|
+
break;
|
|
116
|
+
}
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
e.preventDefault();
|
|
120
|
+
});
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
initTrees(document);
|
|
124
|
+
document.addEventListener('astro:page-load', () => initTrees(document));
|
|
125
|
+
</script>
|
|
126
|
+
<style is:global>
|
|
127
|
+
.wd-tree {
|
|
128
|
+
--wd-tree-accent: var(--tree-highlight, var(--wd-primary));
|
|
129
|
+
margin: 1.25rem 0;
|
|
130
|
+
padding: 0.6rem;
|
|
131
|
+
list-style: none;
|
|
132
|
+
border: 1px solid var(--wd-border);
|
|
133
|
+
border-radius: 0.6rem;
|
|
134
|
+
font-family: monospace;
|
|
135
|
+
font-size: 0.875rem;
|
|
136
|
+
}
|
|
137
|
+
.wd-tree .wd-tree-group {
|
|
138
|
+
margin: 0;
|
|
139
|
+
padding-left: 1.1rem;
|
|
140
|
+
list-style: none;
|
|
141
|
+
}
|
|
142
|
+
.wd-tree .wd-tree-item {
|
|
143
|
+
margin: 0;
|
|
144
|
+
padding: 0;
|
|
145
|
+
outline: none;
|
|
146
|
+
}
|
|
147
|
+
.wd-tree-folder[aria-expanded='false'] > .wd-tree-group {
|
|
148
|
+
display: none;
|
|
149
|
+
}
|
|
150
|
+
.wd-tree-row {
|
|
151
|
+
display: flex;
|
|
152
|
+
align-items: center;
|
|
153
|
+
gap: 0.4rem;
|
|
154
|
+
padding: 0.2rem 0.4rem;
|
|
155
|
+
border-radius: 0.35rem;
|
|
156
|
+
color: var(--wd-text);
|
|
157
|
+
cursor: default;
|
|
158
|
+
user-select: none;
|
|
159
|
+
}
|
|
160
|
+
.wd-tree-folder:not(.wd-tree-static) > .wd-tree-row {
|
|
161
|
+
cursor: pointer;
|
|
162
|
+
}
|
|
163
|
+
.wd-tree-row:hover,
|
|
164
|
+
.wd-tree-item:focus-visible > .wd-tree-row {
|
|
165
|
+
background: var(--wd-surface);
|
|
166
|
+
}
|
|
167
|
+
.wd-tree-item:focus-visible > .wd-tree-row {
|
|
168
|
+
box-shadow: inset 0 0 0 1px var(--wd-tree-accent);
|
|
169
|
+
}
|
|
170
|
+
.wd-tree-chevron {
|
|
171
|
+
width: 0.8rem;
|
|
172
|
+
flex-shrink: 0;
|
|
173
|
+
display: inline-flex;
|
|
174
|
+
justify-content: center;
|
|
175
|
+
}
|
|
176
|
+
.wd-tree-folder > .wd-tree-row > .wd-tree-chevron::before {
|
|
177
|
+
content: '';
|
|
178
|
+
width: 0.35rem;
|
|
179
|
+
height: 0.35rem;
|
|
180
|
+
border-right: 1.5px solid var(--wd-text-muted);
|
|
181
|
+
border-bottom: 1.5px solid var(--wd-text-muted);
|
|
182
|
+
transform: rotate(-45deg);
|
|
183
|
+
transition: transform 0.12s ease;
|
|
184
|
+
}
|
|
185
|
+
.wd-tree-folder[aria-expanded='true'] > .wd-tree-row > .wd-tree-chevron::before {
|
|
186
|
+
transform: rotate(45deg);
|
|
187
|
+
}
|
|
188
|
+
.wd-tree-static > .wd-tree-row > .wd-tree-chevron::before {
|
|
189
|
+
opacity: 0.35;
|
|
190
|
+
}
|
|
191
|
+
svg.wd-tree-icon {
|
|
192
|
+
width: 1em;
|
|
193
|
+
height: 1em;
|
|
194
|
+
flex-shrink: 0;
|
|
195
|
+
color: var(--wd-text-muted);
|
|
196
|
+
}
|
|
197
|
+
.wd-tree-folder[aria-expanded='true'] > .wd-tree-row > .wd-tree-icon-closed,
|
|
198
|
+
.wd-tree-folder[aria-expanded='false'] > .wd-tree-row > .wd-tree-icon-open {
|
|
199
|
+
display: none;
|
|
200
|
+
}
|
|
201
|
+
/* The highlighted item gets a tinted row; a highlighted folder also
|
|
202
|
+
tints the text of everything inside it, so the active path reads. */
|
|
203
|
+
.wd-tree-highlight > .wd-tree-row {
|
|
204
|
+
background: color-mix(in srgb, var(--wd-tree-accent) 14%, transparent);
|
|
205
|
+
color: var(--wd-tree-accent);
|
|
206
|
+
font-weight: 600;
|
|
207
|
+
}
|
|
208
|
+
.wd-tree-highlight > .wd-tree-row .wd-tree-icon,
|
|
209
|
+
.wd-tree-folder.wd-tree-highlight .wd-tree-row,
|
|
210
|
+
.wd-tree-folder.wd-tree-highlight .wd-tree-icon {
|
|
211
|
+
color: var(--wd-tree-accent);
|
|
212
|
+
}
|
|
213
|
+
</style>
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
---
|
|
2
|
+
// Mintlify's <Tree.File name="..."> - see Tree.astro. `highlight` marks it
|
|
3
|
+
// as the active item.
|
|
4
|
+
import AppIcon from './AppIcon.astro';
|
|
5
|
+
interface Props {
|
|
6
|
+
name: string;
|
|
7
|
+
highlight?: boolean;
|
|
8
|
+
}
|
|
9
|
+
const { name, highlight = false } = Astro.props as Props;
|
|
10
|
+
---
|
|
11
|
+
<li class:list={['wd-tree-item', 'wd-tree-file', { 'wd-tree-highlight': highlight }]} role="treeitem" tabindex="-1">
|
|
12
|
+
<span class="wd-tree-row">
|
|
13
|
+
<span class="wd-tree-chevron wd-tree-chevron-spacer" aria-hidden="true"></span>
|
|
14
|
+
<AppIcon icon="lucide:file" class="wd-tree-icon" />
|
|
15
|
+
<span class="wd-tree-name">{name}</span>
|
|
16
|
+
</span>
|
|
17
|
+
</li>
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
---
|
|
2
|
+
// Mintlify's <Tree.Folder name="..."> - see Tree.astro. `defaultOpen`
|
|
3
|
+
// starts it expanded; `openable={false}` makes it a fixed, non-toggling
|
|
4
|
+
// folder (shown open or closed per `defaultOpen`); `highlight` marks it as
|
|
5
|
+
// the active item and tints everything inside it.
|
|
6
|
+
import AppIcon from './AppIcon.astro';
|
|
7
|
+
interface Props {
|
|
8
|
+
name: string;
|
|
9
|
+
defaultOpen?: boolean;
|
|
10
|
+
openable?: boolean;
|
|
11
|
+
highlight?: boolean;
|
|
12
|
+
}
|
|
13
|
+
const { name, defaultOpen = false, openable = true, highlight = false } = Astro.props as Props;
|
|
14
|
+
---
|
|
15
|
+
<li
|
|
16
|
+
class:list={['wd-tree-item', 'wd-tree-folder', { 'wd-tree-highlight': highlight, 'wd-tree-static': !openable }]}
|
|
17
|
+
role="treeitem"
|
|
18
|
+
aria-expanded={defaultOpen ? 'true' : 'false'}
|
|
19
|
+
tabindex="-1"
|
|
20
|
+
>
|
|
21
|
+
<span class="wd-tree-row">
|
|
22
|
+
<span class="wd-tree-chevron" aria-hidden="true"></span>
|
|
23
|
+
<AppIcon icon="lucide:folder" class="wd-tree-icon wd-tree-icon-closed" />
|
|
24
|
+
<AppIcon icon="lucide:folder-open" class="wd-tree-icon wd-tree-icon-open" />
|
|
25
|
+
<span class="wd-tree-name">{name}</span>
|
|
26
|
+
</span>
|
|
27
|
+
<ul class="wd-tree-group" role="group"><slot /></ul>
|
|
28
|
+
</li>
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
---
|
|
2
|
+
// Mintlify's <Update> - one changelog entry: `label` (usually a date or
|
|
3
|
+
// version) in a left column that sticks while its content scrolls, with
|
|
4
|
+
// `description` and `tags` under it. The label is a linkable anchor, deduped
|
|
5
|
+
// against every other anchor on the page by remarkTitleAnchorIds, same as a
|
|
6
|
+
// Callout/Accordion title. Stacks into one column on narrow screens.
|
|
7
|
+
//
|
|
8
|
+
// When any Update on the page has `tags`, a row of tag filters appears
|
|
9
|
+
// above the first one (the script below) - Mintlify shows the same filters
|
|
10
|
+
// in its right-hand panel. `rss` is accepted but unused: writedocs doesn't
|
|
11
|
+
// generate an RSS feed from updates.
|
|
12
|
+
interface Props {
|
|
13
|
+
label: string;
|
|
14
|
+
description?: string;
|
|
15
|
+
tags?: string[];
|
|
16
|
+
rss?: { title?: string; description?: string };
|
|
17
|
+
_titleId?: string;
|
|
18
|
+
}
|
|
19
|
+
const { label, description, tags = [], _titleId } = Astro.props as Props;
|
|
20
|
+
function slugify(value: string): string {
|
|
21
|
+
return value
|
|
22
|
+
.toLowerCase()
|
|
23
|
+
.replace(/[^a-z0-9]+/g, '-')
|
|
24
|
+
.replace(/^-+|-+$/g, '');
|
|
25
|
+
}
|
|
26
|
+
const id = _titleId ?? slugify(label);
|
|
27
|
+
---
|
|
28
|
+
<section class="wd-update" data-update-tags={JSON.stringify(tags)}>
|
|
29
|
+
<div class="wd-update-meta">
|
|
30
|
+
<div class="wd-update-label" id={id}>
|
|
31
|
+
<a href={`#${id}`}>{label}</a>
|
|
32
|
+
</div>
|
|
33
|
+
{description && <div class="wd-update-description">{description}</div>}
|
|
34
|
+
{tags.length > 0 && (
|
|
35
|
+
<div class="wd-update-tags">
|
|
36
|
+
{tags.map((tag) => <span class="wd-update-tag">{tag}</span>)}
|
|
37
|
+
</div>
|
|
38
|
+
)}
|
|
39
|
+
</div>
|
|
40
|
+
<div class="wd-update-body"><slot /></div>
|
|
41
|
+
</section>
|
|
42
|
+
<script>
|
|
43
|
+
// Builds one tag-filter bar per page, before the first Update, from every
|
|
44
|
+
// Update's tags. Clicking a tag shows only the updates that have it;
|
|
45
|
+
// clicking it again (or "All") shows everything.
|
|
46
|
+
function initUpdateFilters(root: ParentNode) {
|
|
47
|
+
const updates = Array.from(root.querySelectorAll<HTMLElement>('.wd-update'));
|
|
48
|
+
if (updates.length === 0 || updates[0].dataset.wdInit) return;
|
|
49
|
+
updates.forEach((u) => (u.dataset.wdInit = 'true'));
|
|
50
|
+
const tagsOf = (u: HTMLElement): string[] => {
|
|
51
|
+
try {
|
|
52
|
+
return JSON.parse(u.dataset.updateTags ?? '[]');
|
|
53
|
+
} catch {
|
|
54
|
+
return [];
|
|
55
|
+
}
|
|
56
|
+
};
|
|
57
|
+
const allTags = [...new Set(updates.flatMap(tagsOf))];
|
|
58
|
+
if (allTags.length === 0) return;
|
|
59
|
+
|
|
60
|
+
const bar = document.createElement('div');
|
|
61
|
+
bar.className = 'wd-update-filters';
|
|
62
|
+
bar.setAttribute('role', 'toolbar');
|
|
63
|
+
bar.setAttribute('aria-label', 'Filter updates by tag');
|
|
64
|
+
let active: string | null = null;
|
|
65
|
+
const buttons: HTMLButtonElement[] = [];
|
|
66
|
+
const apply = () => {
|
|
67
|
+
updates.forEach((u) => (u.hidden = active !== null && !tagsOf(u).includes(active)));
|
|
68
|
+
buttons.forEach((b) => b.setAttribute('aria-pressed', String((b.dataset.tag ?? null) === active)));
|
|
69
|
+
};
|
|
70
|
+
for (const tag of [null, ...allTags]) {
|
|
71
|
+
const b = document.createElement('button');
|
|
72
|
+
b.type = 'button';
|
|
73
|
+
b.className = 'wd-update-filter';
|
|
74
|
+
b.textContent = tag ?? 'All';
|
|
75
|
+
if (tag !== null) b.dataset.tag = tag;
|
|
76
|
+
b.addEventListener('click', () => {
|
|
77
|
+
active = tag === null || active === tag ? null : tag;
|
|
78
|
+
apply();
|
|
79
|
+
});
|
|
80
|
+
buttons.push(b);
|
|
81
|
+
bar.appendChild(b);
|
|
82
|
+
}
|
|
83
|
+
updates[0].before(bar);
|
|
84
|
+
apply();
|
|
85
|
+
}
|
|
86
|
+
initUpdateFilters(document);
|
|
87
|
+
document.addEventListener('astro:page-load', () => initUpdateFilters(document));
|
|
88
|
+
</script>
|
|
89
|
+
<style>
|
|
90
|
+
.wd-update {
|
|
91
|
+
display: grid;
|
|
92
|
+
grid-template-columns: 11rem minmax(0, 1fr);
|
|
93
|
+
gap: 2rem;
|
|
94
|
+
padding: 2rem 0;
|
|
95
|
+
border-top: 1px solid var(--wd-border);
|
|
96
|
+
}
|
|
97
|
+
.wd-update:first-of-type {
|
|
98
|
+
border-top: 0;
|
|
99
|
+
}
|
|
100
|
+
.wd-update[hidden] {
|
|
101
|
+
display: none;
|
|
102
|
+
}
|
|
103
|
+
.wd-update-meta {
|
|
104
|
+
position: sticky;
|
|
105
|
+
top: calc(var(--wd-topbar-offset, 5rem) + 1rem);
|
|
106
|
+
align-self: start;
|
|
107
|
+
}
|
|
108
|
+
.wd-update-label {
|
|
109
|
+
scroll-margin-top: calc(var(--wd-topbar-offset, 5rem) + 1rem);
|
|
110
|
+
}
|
|
111
|
+
.wd-update-label a {
|
|
112
|
+
display: inline-block;
|
|
113
|
+
padding: 0.2rem 0.6rem;
|
|
114
|
+
border-radius: 999px;
|
|
115
|
+
font-size: 0.85rem;
|
|
116
|
+
font-weight: 600;
|
|
117
|
+
text-decoration: none;
|
|
118
|
+
color: var(--wd-primary);
|
|
119
|
+
background: color-mix(in srgb, var(--wd-primary) 12%, transparent);
|
|
120
|
+
}
|
|
121
|
+
.wd-update-description {
|
|
122
|
+
margin-top: 0.5rem;
|
|
123
|
+
font-size: 0.85rem;
|
|
124
|
+
color: var(--wd-text-muted);
|
|
125
|
+
}
|
|
126
|
+
.wd-update-tags {
|
|
127
|
+
display: flex;
|
|
128
|
+
flex-wrap: wrap;
|
|
129
|
+
gap: 0.3rem;
|
|
130
|
+
margin-top: 0.5rem;
|
|
131
|
+
}
|
|
132
|
+
.wd-update-tag {
|
|
133
|
+
padding: 0.05rem 0.45rem;
|
|
134
|
+
border: 1px solid var(--wd-border);
|
|
135
|
+
border-radius: 999px;
|
|
136
|
+
font-size: 0.72rem;
|
|
137
|
+
color: var(--wd-text-muted);
|
|
138
|
+
}
|
|
139
|
+
.wd-update-body > :global(:first-child) {
|
|
140
|
+
margin-top: 0;
|
|
141
|
+
}
|
|
142
|
+
@media (max-width: 720px) {
|
|
143
|
+
.wd-update {
|
|
144
|
+
grid-template-columns: minmax(0, 1fr);
|
|
145
|
+
gap: 0.75rem;
|
|
146
|
+
}
|
|
147
|
+
.wd-update-meta {
|
|
148
|
+
position: static;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
:global(.wd-update-filters) {
|
|
152
|
+
display: flex;
|
|
153
|
+
flex-wrap: wrap;
|
|
154
|
+
gap: 0.4rem;
|
|
155
|
+
margin: 1rem 0 0.5rem;
|
|
156
|
+
}
|
|
157
|
+
:global(.wd-update-filter) {
|
|
158
|
+
padding: 0.2rem 0.7rem;
|
|
159
|
+
border: 1px solid var(--wd-border);
|
|
160
|
+
border-radius: 999px;
|
|
161
|
+
background: none;
|
|
162
|
+
color: var(--wd-text-muted);
|
|
163
|
+
font-size: 0.8rem;
|
|
164
|
+
cursor: pointer;
|
|
165
|
+
}
|
|
166
|
+
:global(.wd-update-filter[aria-pressed='true']) {
|
|
167
|
+
border-color: var(--wd-primary);
|
|
168
|
+
color: var(--wd-primary);
|
|
169
|
+
background: color-mix(in srgb, var(--wd-primary) 10%, transparent);
|
|
170
|
+
}
|
|
171
|
+
</style>
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
---
|
|
2
|
+
// Mintlify's <View title="..." icon="..."> - content for one of several
|
|
3
|
+
// alternatives (a language, a framework) on the same page. Every distinct
|
|
4
|
+
// `title` on the page becomes an option in one switcher, placed under the
|
|
5
|
+
// page title (or before the first View on a page without one); only the
|
|
6
|
+
// selected title's Views show. As on Mintlify, the table of contents only
|
|
7
|
+
// lists the headings in the visible Views.
|
|
8
|
+
//
|
|
9
|
+
// The choice is remembered (localStorage) across pages, so a reader who
|
|
10
|
+
// picks "Python" keeps seeing Python wherever the same title exists. A
|
|
11
|
+
// link to an anchor inside a hidden View switches to that View first.
|
|
12
|
+
import AppIcon from './AppIcon.astro';
|
|
13
|
+
interface Props {
|
|
14
|
+
title: string;
|
|
15
|
+
icon?: string;
|
|
16
|
+
}
|
|
17
|
+
const { title, icon } = Astro.props as Props;
|
|
18
|
+
---
|
|
19
|
+
<div class="wd-view" data-view-title={title}>
|
|
20
|
+
{icon && <span class="wd-view-icon-src" hidden><AppIcon icon={icon} class="wd-view-icon" /></span>}
|
|
21
|
+
<slot />
|
|
22
|
+
</div>
|
|
23
|
+
<script>
|
|
24
|
+
const STORAGE_KEY = 'wd-view';
|
|
25
|
+
|
|
26
|
+
function initViews(root: Document) {
|
|
27
|
+
const views = Array.from(root.querySelectorAll<HTMLElement>('.wd-view[data-view-title]'));
|
|
28
|
+
if (views.length === 0 || root.querySelector('.wd-view-switcher')) return;
|
|
29
|
+
|
|
30
|
+
const titles = [...new Set(views.map((v) => v.dataset.viewTitle!))];
|
|
31
|
+
const iconFor = (title: string) =>
|
|
32
|
+
views.find((v) => v.dataset.viewTitle === title)?.querySelector<HTMLElement>('.wd-view-icon-src > *') ?? null;
|
|
33
|
+
|
|
34
|
+
let stored: string | null = null;
|
|
35
|
+
try {
|
|
36
|
+
stored = localStorage.getItem(STORAGE_KEY);
|
|
37
|
+
} catch {}
|
|
38
|
+
const hashTarget = location.hash ? document.getElementById(decodeURIComponent(location.hash.slice(1))) : null;
|
|
39
|
+
const hashView = hashTarget?.closest<HTMLElement>('.wd-view')?.dataset.viewTitle;
|
|
40
|
+
let current = hashView ?? (stored && titles.includes(stored) ? stored : titles[0]);
|
|
41
|
+
|
|
42
|
+
// The switcher: a button showing the current option, and a menu of all.
|
|
43
|
+
const switcher = document.createElement('div');
|
|
44
|
+
switcher.className = 'wd-view-switcher';
|
|
45
|
+
const button = document.createElement('button');
|
|
46
|
+
button.type = 'button';
|
|
47
|
+
button.className = 'wd-view-button';
|
|
48
|
+
button.setAttribute('aria-haspopup', 'listbox');
|
|
49
|
+
button.setAttribute('aria-expanded', 'false');
|
|
50
|
+
const menu = document.createElement('ul');
|
|
51
|
+
menu.className = 'wd-view-menu';
|
|
52
|
+
menu.setAttribute('role', 'listbox');
|
|
53
|
+
menu.hidden = true;
|
|
54
|
+
|
|
55
|
+
const label = (title: string) => {
|
|
56
|
+
const frag = document.createDocumentFragment();
|
|
57
|
+
const icon = iconFor(title);
|
|
58
|
+
if (icon) frag.appendChild(icon.cloneNode(true));
|
|
59
|
+
frag.appendChild(document.createTextNode(title));
|
|
60
|
+
return frag;
|
|
61
|
+
};
|
|
62
|
+
const options = titles.map((title) => {
|
|
63
|
+
const li = document.createElement('li');
|
|
64
|
+
li.setAttribute('role', 'option');
|
|
65
|
+
li.tabIndex = -1;
|
|
66
|
+
li.dataset.viewTitle = title;
|
|
67
|
+
li.appendChild(label(title));
|
|
68
|
+
li.addEventListener('click', () => {
|
|
69
|
+
select(title, true);
|
|
70
|
+
close();
|
|
71
|
+
button.focus();
|
|
72
|
+
});
|
|
73
|
+
li.addEventListener('keydown', (e) => {
|
|
74
|
+
const i = options.indexOf(li);
|
|
75
|
+
if (e.key === 'ArrowDown') options[Math.min(i + 1, options.length - 1)].focus();
|
|
76
|
+
else if (e.key === 'ArrowUp') options[Math.max(i - 1, 0)].focus();
|
|
77
|
+
else if (e.key === 'Enter' || e.key === ' ') li.click();
|
|
78
|
+
else if (e.key === 'Escape') {
|
|
79
|
+
close();
|
|
80
|
+
button.focus();
|
|
81
|
+
} else return;
|
|
82
|
+
e.preventDefault();
|
|
83
|
+
});
|
|
84
|
+
menu.appendChild(li);
|
|
85
|
+
return li;
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
const open = () => {
|
|
89
|
+
menu.hidden = false;
|
|
90
|
+
button.setAttribute('aria-expanded', 'true');
|
|
91
|
+
(options.find((o) => o.dataset.viewTitle === current) ?? options[0]).focus();
|
|
92
|
+
};
|
|
93
|
+
const close = () => {
|
|
94
|
+
menu.hidden = true;
|
|
95
|
+
button.setAttribute('aria-expanded', 'false');
|
|
96
|
+
};
|
|
97
|
+
button.addEventListener('click', () => (menu.hidden ? open() : close()));
|
|
98
|
+
document.addEventListener('click', (e) => {
|
|
99
|
+
if (!switcher.contains(e.target as Node)) close();
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
function select(title: string, remember: boolean) {
|
|
103
|
+
current = title;
|
|
104
|
+
views.forEach((v) => (v.hidden = v.dataset.viewTitle !== title));
|
|
105
|
+
button.replaceChildren(label(title));
|
|
106
|
+
const chevron = document.createElement('span');
|
|
107
|
+
chevron.className = 'wd-view-chevron';
|
|
108
|
+
chevron.setAttribute('aria-hidden', 'true');
|
|
109
|
+
button.appendChild(chevron);
|
|
110
|
+
options.forEach((o) => o.setAttribute('aria-selected', String(o.dataset.viewTitle === title)));
|
|
111
|
+
// Table of contents: only headings the reader can currently see.
|
|
112
|
+
document.querySelectorAll<HTMLAnchorElement>('.wd-toc a[data-toc-slug]').forEach((a) => {
|
|
113
|
+
const heading = document.getElementById(a.dataset.tocSlug ?? '');
|
|
114
|
+
const li = a.closest('li');
|
|
115
|
+
if (li) li.hidden = Boolean(heading?.closest('.wd-view[hidden]'));
|
|
116
|
+
});
|
|
117
|
+
if (remember) {
|
|
118
|
+
try {
|
|
119
|
+
localStorage.setItem(STORAGE_KEY, title);
|
|
120
|
+
} catch {}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
switcher.append(button, menu);
|
|
125
|
+
const header = document.querySelector('.wd-article-header');
|
|
126
|
+
if (header) header.after(switcher);
|
|
127
|
+
else views[0].before(switcher);
|
|
128
|
+
select(current, false);
|
|
129
|
+
if (hashTarget && hashView) hashTarget.scrollIntoView();
|
|
130
|
+
|
|
131
|
+
// Same for an in-page link to an anchor inside a hidden View.
|
|
132
|
+
window.addEventListener('hashchange', () => {
|
|
133
|
+
if (!switcher.isConnected) return;
|
|
134
|
+
const el = location.hash ? document.getElementById(decodeURIComponent(location.hash.slice(1))) : null;
|
|
135
|
+
const title = el?.closest<HTMLElement>('.wd-view[hidden]')?.dataset.viewTitle;
|
|
136
|
+
if (!title) return;
|
|
137
|
+
select(title, false);
|
|
138
|
+
el!.scrollIntoView();
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
initViews(document);
|
|
142
|
+
document.addEventListener('astro:page-load', () => initViews(document));
|
|
143
|
+
</script>
|
|
144
|
+
<style>
|
|
145
|
+
.wd-view[hidden] {
|
|
146
|
+
display: none;
|
|
147
|
+
}
|
|
148
|
+
:global(.wd-view-switcher) {
|
|
149
|
+
position: relative;
|
|
150
|
+
display: inline-block;
|
|
151
|
+
margin: 0 0 1rem;
|
|
152
|
+
}
|
|
153
|
+
:global(.wd-view-button) {
|
|
154
|
+
display: inline-flex;
|
|
155
|
+
align-items: center;
|
|
156
|
+
gap: 0.45rem;
|
|
157
|
+
padding: 0.35rem 0.75rem;
|
|
158
|
+
border: 1px solid var(--wd-border);
|
|
159
|
+
border-radius: 0.5rem;
|
|
160
|
+
background: var(--wd-background);
|
|
161
|
+
color: var(--wd-text);
|
|
162
|
+
font-size: 0.875rem;
|
|
163
|
+
cursor: pointer;
|
|
164
|
+
}
|
|
165
|
+
:global(.wd-view-button:hover) {
|
|
166
|
+
border-color: var(--wd-primary);
|
|
167
|
+
}
|
|
168
|
+
:global(.wd-view-chevron) {
|
|
169
|
+
width: 0.4rem;
|
|
170
|
+
height: 0.4rem;
|
|
171
|
+
margin-left: 0.2rem;
|
|
172
|
+
border-right: 1.5px solid currentColor;
|
|
173
|
+
border-bottom: 1.5px solid currentColor;
|
|
174
|
+
transform: translateY(-2px) rotate(45deg);
|
|
175
|
+
}
|
|
176
|
+
:global(.wd-view-menu) {
|
|
177
|
+
position: absolute;
|
|
178
|
+
top: calc(100% + 0.3rem);
|
|
179
|
+
left: 0;
|
|
180
|
+
z-index: 20;
|
|
181
|
+
min-width: 100%;
|
|
182
|
+
margin: 0;
|
|
183
|
+
padding: 0.3rem;
|
|
184
|
+
list-style: none;
|
|
185
|
+
border: 1px solid var(--wd-border);
|
|
186
|
+
border-radius: 0.5rem;
|
|
187
|
+
background: var(--wd-background);
|
|
188
|
+
box-shadow: 0 6px 20px rgba(0, 0, 0, 0.12);
|
|
189
|
+
}
|
|
190
|
+
:global(.wd-view-menu li) {
|
|
191
|
+
display: flex;
|
|
192
|
+
align-items: center;
|
|
193
|
+
gap: 0.45rem;
|
|
194
|
+
margin: 0;
|
|
195
|
+
padding: 0.35rem 0.6rem;
|
|
196
|
+
border-radius: 0.35rem;
|
|
197
|
+
font-size: 0.875rem;
|
|
198
|
+
white-space: nowrap;
|
|
199
|
+
cursor: pointer;
|
|
200
|
+
}
|
|
201
|
+
:global(.wd-view-menu li:hover),
|
|
202
|
+
:global(.wd-view-menu li:focus),
|
|
203
|
+
:global(.wd-view-menu li[aria-selected='true']) {
|
|
204
|
+
outline: none;
|
|
205
|
+
background: color-mix(in srgb, var(--wd-primary) 10%, transparent);
|
|
206
|
+
color: var(--wd-primary);
|
|
207
|
+
}
|
|
208
|
+
:global(svg.wd-view-icon),
|
|
209
|
+
:global(span.wd-view-icon) {
|
|
210
|
+
width: 1em;
|
|
211
|
+
height: 1em;
|
|
212
|
+
flex-shrink: 0;
|
|
213
|
+
}
|
|
214
|
+
</style>
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
---
|
|
2
|
+
// Mintlify's <Visibility for="humans|agents">. On the rendered site,
|
|
3
|
+
// `humans` content shows and `agents` content doesn't; the per-page `.md`
|
|
4
|
+
// route and llms-full.txt do the opposite (see lib/visibility.js). A
|
|
5
|
+
// <Visibility> with no `for` counts as `humans`.
|
|
6
|
+
interface Props {
|
|
7
|
+
for?: 'humans' | 'agents';
|
|
8
|
+
}
|
|
9
|
+
const { for: audience = 'humans' } = Astro.props as Props;
|
|
10
|
+
---
|
|
11
|
+
{audience !== 'agents' && <slot />}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
// Mintlify's dotted component names - <Tree.Folder>, <Color.Item>,
|
|
2
|
+
// <GitHub.Repo>, ... MDX compiles <Tree.Folder> to a property lookup on
|
|
3
|
+
// whatever `Tree` resolves to, so the parts are attached as properties of
|
|
4
|
+
// their parent here, once, and everything that hands components to MDX (the
|
|
5
|
+
// src/components/index.ts barrel and [...slug].astro's `components` map)
|
|
6
|
+
// takes these three from this module rather than importing the .astro files
|
|
7
|
+
// directly. `FileTree` is Mintlify's other name for `Tree`.
|
|
8
|
+
import TreeRoot from './Tree.astro';
|
|
9
|
+
import TreeFolder from './TreeFolder.astro';
|
|
10
|
+
import TreeFile from './TreeFile.astro';
|
|
11
|
+
import ColorRoot from './Color.astro';
|
|
12
|
+
import ColorRow from './ColorRow.astro';
|
|
13
|
+
import ColorItem from './ColorItem.astro';
|
|
14
|
+
import GitHubRepo from './GitHubRepo.astro';
|
|
15
|
+
|
|
16
|
+
export const Tree = Object.assign(TreeRoot, { Folder: TreeFolder, File: TreeFile });
|
|
17
|
+
export const FileTree = Tree;
|
|
18
|
+
export const Color = Object.assign(ColorRoot, { Row: ColorRow, Item: ColorItem });
|
|
19
|
+
// Mintlify has no bare <GitHub> - only <GitHub.Repo> - so this is just a
|
|
20
|
+
// namespace object, not a component.
|
|
21
|
+
export const GitHub = { Repo: GitHubRepo };
|
package/src/components/index.ts
CHANGED
|
@@ -51,3 +51,10 @@ export { default as ParamField } from './ParamField.astro';
|
|
|
51
51
|
export { default as ResponseField } from './ResponseField.astro';
|
|
52
52
|
export { default as Columns } from './Columns.astro';
|
|
53
53
|
export { default as Tooltip } from './Tooltip.astro';
|
|
54
|
+
export { default as Update } from './Update.astro';
|
|
55
|
+
export { default as Tile } from './Tile.astro';
|
|
56
|
+
export { default as Panel } from './Panel.astro';
|
|
57
|
+
export { default as Prompt } from './Prompt.astro';
|
|
58
|
+
export { default as View } from './View.astro';
|
|
59
|
+
export { default as Visibility } from './Visibility.astro';
|
|
60
|
+
export { Tree, FileTree, Color, GitHub } from './compound';
|