@writedocs/generator 0.4.5 → 0.4.7
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 +30 -3
- package/package.json +1 -1
- package/src/components/Accordion.astro +22 -5
- package/src/components/AppIcon.astro +5 -0
- package/src/components/Callout.astro +15 -5
- package/src/components/Card.astro +64 -13
- package/src/components/Check.astro +13 -0
- package/src/components/Columns.astro +11 -0
- package/src/components/Frame.astro +10 -1
- package/src/components/Hint.astro +50 -4
- package/src/components/ParamField.astro +25 -0
- package/src/components/Parameter.astro +41 -4
- package/src/components/ResponseField.astro +18 -0
- package/src/components/Step.astro +22 -3
- package/src/components/Steps.astro +22 -0
- package/src/components/Tab.astro +7 -1
- package/src/components/Tabs.astro +27 -4
- package/src/components/Tooltip.astro +13 -0
- package/src/components/index.ts +5 -0
- package/src/content.config.ts +68 -2
- package/src/layout/components/AskAiWidget.astro +13 -0
- package/src/layout/components/NavTree.astro +34 -0
- package/src/layout/styles/base.css +12 -0
- package/src/lib/config.ts +1412 -1306
- package/src/lib/mdx-inject-builtins.js +5 -0
- package/src/lib/mdx-title-anchor-ids.js +1 -1
- package/src/lib/shiki-code-block.js +140 -26
- package/src/pages/[...slug].astro +87 -6
- package/src/pages/[...slug].md.ts +3 -0
- package/src/pages/llms-full.txt.ts +2 -0
- package/src/pages/llms.txt.ts +3 -0
- package/src/styles/global.css +10 -0
|
@@ -8,7 +8,7 @@ import { visit } from 'unist-util-visit';
|
|
|
8
8
|
// (Card, Tabs, AccordionGroup, etc. don't). AccordionGroup is deliberately
|
|
9
9
|
// excluded - it has no `title` of its own, just wraps Accordion children
|
|
10
10
|
// that each get their own entry here independently.
|
|
11
|
-
const TITLED_COMPONENT_NAMES = ['Callout', 'Note', 'Info', 'Tip', 'Warning', 'Danger', 'Accordion'];
|
|
11
|
+
const TITLED_COMPONENT_NAMES = ['Callout', 'Note', 'Info', 'Tip', 'Warning', 'Danger', 'Check', 'Accordion'];
|
|
12
12
|
|
|
13
13
|
// Deliberately the same slugify Callout.astro/Accordion.astro themselves
|
|
14
14
|
// fall back to (see their own comments) - kept as a literal duplicate
|
|
@@ -21,33 +21,124 @@
|
|
|
21
21
|
// at all, so this only ever touches genuine ```-fenced MDX content and
|
|
22
22
|
// never collides with the API playground's own bespoke copy buttons.
|
|
23
23
|
|
|
24
|
-
|
|
24
|
+
import { iconSvg } from './config.ts';
|
|
25
25
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
26
|
+
// The fence's info string after the language, split into tokens: a
|
|
27
|
+
// `key=value` option (value quoted, in braces, or bare), a bare `{1,3-5}`
|
|
28
|
+
// range, a `/word/` pattern, or any other bare word.
|
|
29
|
+
const META_TOKEN_RE = /([A-Za-z][\w-]*)=(?:"([^"]*)"|'([^']*)'|\{([^}]*)\}|(\S+))|\{([^}]*)\}|(\/(?:\\.|[^/\s])+\/)(?=\s|$)|(\S+)/g;
|
|
30
|
+
|
|
31
|
+
// Bare words that are options, not title text. Case-sensitive on purpose:
|
|
32
|
+
// Mintlify's own docs write ```js Wrap example wrap, where the capitalized
|
|
33
|
+
// "Wrap" is part of the title and only the trailing "wrap" is the flag.
|
|
34
|
+
const META_FLAGS = new Set(['wrap', 'lines', 'showLineNumbers', 'expandable', 'nocopy', 'twoslash']);
|
|
35
|
+
|
|
36
|
+
function parseRanges(value) {
|
|
37
|
+
return value
|
|
38
|
+
.split(',')
|
|
39
|
+
.map((part) => part.trim())
|
|
40
|
+
.filter(Boolean)
|
|
41
|
+
.flatMap((part) => {
|
|
42
|
+
const [start, end] = part.split('-').map((n) => Number.parseInt(n, 10));
|
|
43
|
+
if (Number.isNaN(start)) return [];
|
|
44
|
+
if (end === undefined || Number.isNaN(end)) return [start];
|
|
45
|
+
return Array.from({ length: end - start + 1 }, (_, i) => start + i);
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/** Parses a fence's info string in both syntaxes writedocs accepts:
|
|
50
|
+
* its own (```js title="a.js" {1,3} wrap) and Mintlify's
|
|
51
|
+
* (```js a.js highlight={1,3} focus={2} icon="js" nocopy wrap) - the
|
|
52
|
+
* Mintlify title is the run of plain words right after the language,
|
|
53
|
+
* before the first option. `title="..."` wins over an inline title.
|
|
54
|
+
*
|
|
55
|
+
* `shikiMeta` is what the stock @shikijs/transformers meta transformers
|
|
56
|
+
* get to see instead of the raw string (see preprocess below): only the
|
|
57
|
+
* highlight range and the /word/ patterns. Read raw, those transformers
|
|
58
|
+
* would take focus={2} as a highlight range (they grab the first {...}
|
|
59
|
+
* anywhere) and a / inside a title or path as a word to highlight. */
|
|
60
|
+
export function parseMeta(raw) {
|
|
61
|
+
const meta = {
|
|
62
|
+
title: null,
|
|
63
|
+
icon: null,
|
|
64
|
+
wrap: false,
|
|
65
|
+
lines: false,
|
|
66
|
+
expandable: false,
|
|
67
|
+
nocopy: false,
|
|
68
|
+
twoslash: false,
|
|
69
|
+
highlight: [],
|
|
70
|
+
focus: [],
|
|
71
|
+
words: [],
|
|
32
72
|
};
|
|
73
|
+
const titleWords = [];
|
|
74
|
+
let sawOption = false;
|
|
75
|
+
for (const m of raw.matchAll(META_TOKEN_RE)) {
|
|
76
|
+
const [, key, dq, sq, braced, bare, bareRange, word, other] = m;
|
|
77
|
+
if (key !== undefined) {
|
|
78
|
+
sawOption = true;
|
|
79
|
+
const value = dq ?? sq ?? braced ?? bare ?? '';
|
|
80
|
+
if (key === 'title') meta.title = value;
|
|
81
|
+
else if (key === 'icon') meta.icon = value;
|
|
82
|
+
else if (key === 'highlight') meta.highlight.push(...parseRanges(value));
|
|
83
|
+
else if (key === 'focus') meta.focus.push(...parseRanges(value));
|
|
84
|
+
// Mintlify's `nocopy="false"` (explicitly keep the copy button).
|
|
85
|
+
else if (META_FLAGS.has(key)) meta[key === 'showLineNumbers' ? 'lines' : key] = value !== 'false';
|
|
86
|
+
continue;
|
|
87
|
+
}
|
|
88
|
+
if (bareRange !== undefined) {
|
|
89
|
+
sawOption = true;
|
|
90
|
+
meta.highlight.push(...parseRanges(bareRange));
|
|
91
|
+
continue;
|
|
92
|
+
}
|
|
93
|
+
if (word !== undefined) {
|
|
94
|
+
sawOption = true;
|
|
95
|
+
meta.words.push(word);
|
|
96
|
+
continue;
|
|
97
|
+
}
|
|
98
|
+
if (META_FLAGS.has(other)) {
|
|
99
|
+
sawOption = true;
|
|
100
|
+
meta[other === 'showLineNumbers' ? 'lines' : other] = true;
|
|
101
|
+
continue;
|
|
102
|
+
}
|
|
103
|
+
if (!sawOption) titleWords.push(other);
|
|
104
|
+
}
|
|
105
|
+
if (meta.title === null && titleWords.length > 0) meta.title = titleWords.join(' ');
|
|
106
|
+
const shikiParts = [];
|
|
107
|
+
if (meta.highlight.length > 0) shikiParts.push(`{${meta.highlight.join(',')}}`);
|
|
108
|
+
shikiParts.push(...meta.words);
|
|
109
|
+
meta.shikiMeta = shikiParts.join(' ');
|
|
110
|
+
return meta;
|
|
33
111
|
}
|
|
34
112
|
|
|
35
113
|
export function codeBlockTransformer() {
|
|
36
114
|
return {
|
|
37
115
|
name: 'writedocs:code-block',
|
|
38
|
-
//
|
|
39
|
-
//
|
|
40
|
-
//
|
|
41
|
-
//
|
|
42
|
-
//
|
|
116
|
+
// preprocess runs before every other hook of every transformer (see
|
|
117
|
+
// @shikijs/core's codeToTokens), so this is where the raw info string
|
|
118
|
+
// is parsed once and swapped for the cleaned-up `shikiMeta` the stock
|
|
119
|
+
// meta-highlight transformers read - see parseMeta() above. The full
|
|
120
|
+
// parse is kept on the same options object for the hooks below.
|
|
121
|
+
preprocess(code, options) {
|
|
122
|
+
if (options.meta) {
|
|
123
|
+
options.meta.__wdMeta = parseMeta(options.meta.__raw ?? '');
|
|
124
|
+
options.meta.__raw = options.meta.__wdMeta.shikiMeta;
|
|
125
|
+
}
|
|
126
|
+
return code;
|
|
127
|
+
},
|
|
128
|
+
line(node, lineNumber) {
|
|
129
|
+
if (this.options.meta?.__wdMeta?.focus.includes(lineNumber)) this.addClassToHast(node, 'focused');
|
|
130
|
+
return node;
|
|
131
|
+
},
|
|
132
|
+
// Same classes transformerNotationFocus (`// [!code focus]`) adds, so
|
|
133
|
+
// `focus={...}` reuses [...slug].astro's existing dimming CSS.
|
|
43
134
|
pre(node) {
|
|
44
|
-
|
|
135
|
+
if (this.options.meta?.__wdMeta?.focus.length) this.addClassToHast(node, 'has-focused');
|
|
45
136
|
return node;
|
|
46
137
|
},
|
|
47
138
|
root(hast) {
|
|
48
139
|
const pre = hast.children.find((child) => child.type === 'element' && child.tagName === 'pre');
|
|
49
140
|
if (!pre) return hast;
|
|
50
|
-
const meta = this.
|
|
141
|
+
const meta = this.options.meta?.__wdMeta ?? parseMeta(this.options.meta?.__raw ?? '');
|
|
51
142
|
|
|
52
143
|
const wrapperClass = ['wd-code-block'];
|
|
53
144
|
if (meta.wrap) wrapperClass.push('wd-code-wrap');
|
|
@@ -56,25 +147,48 @@ export function codeBlockTransformer() {
|
|
|
56
147
|
|
|
57
148
|
const children = [];
|
|
58
149
|
if (meta.title) {
|
|
150
|
+
const titleChildren = [];
|
|
151
|
+
// Drawn as a CSS mask over currentColor, so the icon takes the
|
|
152
|
+
// title bar's own text color in both themes. An icon string that
|
|
153
|
+
// isn't an installed icon (emoji, unknown name) is skipped rather
|
|
154
|
+
// than failing the build.
|
|
155
|
+
const svg = meta.icon ? iconSvg(meta.icon) : null;
|
|
156
|
+
if (svg) {
|
|
157
|
+
titleChildren.push({
|
|
158
|
+
type: 'element',
|
|
159
|
+
tagName: 'span',
|
|
160
|
+
properties: {
|
|
161
|
+
class: 'wd-code-title-icon',
|
|
162
|
+
'aria-hidden': 'true',
|
|
163
|
+
style: `--wd-code-icon: url("data:image/svg+xml,${encodeURIComponent(svg)}")`,
|
|
164
|
+
},
|
|
165
|
+
children: [],
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
titleChildren.push({ type: 'text', value: meta.title });
|
|
59
169
|
children.push({
|
|
60
170
|
type: 'element',
|
|
61
171
|
tagName: 'div',
|
|
62
172
|
properties: { class: 'wd-code-title' },
|
|
63
|
-
children:
|
|
173
|
+
children: titleChildren,
|
|
64
174
|
});
|
|
65
175
|
}
|
|
66
176
|
children.push(pre);
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
type: '
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
177
|
+
// Mintlify's `nocopy` - for content where copying makes no sense
|
|
178
|
+
// (ASCII diagrams, sample output).
|
|
179
|
+
if (!meta.nocopy) {
|
|
180
|
+
children.push({
|
|
181
|
+
type: 'element',
|
|
182
|
+
tagName: 'button',
|
|
183
|
+
properties: {
|
|
184
|
+
type: 'button',
|
|
185
|
+
class: 'wd-code-copy-btn',
|
|
186
|
+
'data-role': 'copy-code',
|
|
187
|
+
'aria-label': 'Copy code',
|
|
188
|
+
},
|
|
189
|
+
children: [{ type: 'text', value: '⧉' }],
|
|
190
|
+
});
|
|
191
|
+
}
|
|
78
192
|
if (meta.expandable) {
|
|
79
193
|
children.push({
|
|
80
194
|
type: 'element',
|
|
@@ -51,6 +51,11 @@ import Badge from "../components/Badge.astro";
|
|
|
51
51
|
import Icon from "../components/Icon.astro";
|
|
52
52
|
import RequestExample from "../components/RequestExample.astro";
|
|
53
53
|
import ResponseExample from "../components/ResponseExample.astro";
|
|
54
|
+
import Check from "../components/Check.astro";
|
|
55
|
+
import ParamField from "../components/ParamField.astro";
|
|
56
|
+
import ResponseField from "../components/ResponseField.astro";
|
|
57
|
+
import Columns from "../components/Columns.astro";
|
|
58
|
+
import Tooltip from "../components/Tooltip.astro";
|
|
54
59
|
import ApiPlayground from "../components/ApiPlayground.astro";
|
|
55
60
|
import ApiReferencePanel from "../components/ApiReferencePanel.astro";
|
|
56
61
|
|
|
@@ -156,6 +161,15 @@ export async function getStaticPaths() {
|
|
|
156
161
|
|
|
157
162
|
const pageRoutes = sections.flatMap((section, sectionIndex) => {
|
|
158
163
|
const flatNav = flattenNav(section.pages);
|
|
164
|
+
// Prev/next skip pages that aren't in the sidebar (frontmatter
|
|
165
|
+
// `hidden`) or aren't pages at all (frontmatter `url`, an external
|
|
166
|
+
// link) - the reader should land on the next page they could also
|
|
167
|
+
// have clicked in the sidebar. Inlined for the same reason as the
|
|
168
|
+
// error branch below.
|
|
169
|
+
const isPaginationStop = (slug: string) => {
|
|
170
|
+
const data = entryByFileId.get(slug)?.data;
|
|
171
|
+
return Boolean(data) && !data!.hidden && !data!.url;
|
|
172
|
+
};
|
|
159
173
|
return flatNav.map((navEntry, i) => {
|
|
160
174
|
const entry = entryByFileId.get(navEntry.slug);
|
|
161
175
|
if (!entry) {
|
|
@@ -184,9 +198,17 @@ export async function getStaticPaths() {
|
|
|
184
198
|
);
|
|
185
199
|
}
|
|
186
200
|
claimedFileIds.add(navEntry.slug);
|
|
187
|
-
const prev =
|
|
188
|
-
const next =
|
|
201
|
+
const prev = flatNav.slice(0, i).findLast((e) => isPaginationStop(e.slug)) ?? null;
|
|
202
|
+
const next = flatNav.slice(i + 1).find((e) => isPaginationStop(e.slug)) ?? null;
|
|
189
203
|
const urlSlug = normalizeEntryId(entry.id);
|
|
204
|
+
// A frontmatter `url` page (Mintlify's external link) exists only
|
|
205
|
+
// to put that link in the sidebar - its own URL redirects there.
|
|
206
|
+
if (entry.data.url) {
|
|
207
|
+
return {
|
|
208
|
+
params: { slug: urlSlug === "index" ? undefined : urlSlug },
|
|
209
|
+
props: { redirectTo: entry.data.url },
|
|
210
|
+
};
|
|
211
|
+
}
|
|
190
212
|
return {
|
|
191
213
|
params: { slug: urlSlug === "index" ? undefined : urlSlug },
|
|
192
214
|
props: { entry, prev, next, sectionIndex },
|
|
@@ -276,6 +298,9 @@ const entries: DocsEntry[] = [...pagesEntries, ...generatedDocsEntries];
|
|
|
276
298
|
// themselves so hrefForSlug can resolve a writedocs.json file-id reference to
|
|
277
299
|
// wherever that page actually got routed.
|
|
278
300
|
const titleByFileId = new Map<string, string>();
|
|
301
|
+
// Navigation labels (sidebar, topbar dropdowns): a page's `sidebarTitle`
|
|
302
|
+
// when it has one - Mintlify's short nav label - else its `title`.
|
|
303
|
+
const navTitleByFileId = new Map<string, string>();
|
|
279
304
|
const entryByFileId = new Map<string, DocsEntry>();
|
|
280
305
|
// The HTTP method badge NavTree.astro shows next to an API operation
|
|
281
306
|
// page in the sidebar - read straight off the page's own `openapi:
|
|
@@ -286,13 +311,19 @@ const methodByFileId = new Map<string, string>();
|
|
|
286
311
|
for (const e of entries) {
|
|
287
312
|
const fileId = fileIdForEntry(contentDir, packageRoot, e);
|
|
288
313
|
titleByFileId.set(fileId, e.data.title);
|
|
314
|
+
navTitleByFileId.set(fileId, e.data.sidebarTitle ?? e.data.title);
|
|
289
315
|
entryByFileId.set(fileId, e);
|
|
290
|
-
|
|
316
|
+
// `hideApiMarker` (Mintlify's) drops the badge for this one page.
|
|
317
|
+
if (e.data.openapi && !e.data.hideApiMarker) {
|
|
291
318
|
methodByFileId.set(fileId, e.data.openapi.trim().split(/\s+/, 1)[0].toUpperCase());
|
|
292
319
|
}
|
|
293
320
|
}
|
|
294
321
|
const titleForSlug = (slug: string) => titleByFileId.get(slug) ?? slug;
|
|
322
|
+
const navTitleForSlug = (slug: string) => navTitleByFileId.get(slug) ?? slug;
|
|
295
323
|
const methodForSlug = (slug: string) => methodByFileId.get(slug) ?? null;
|
|
324
|
+
// Frontmatter that changes how a page appears in navigation (hidden, url,
|
|
325
|
+
// icon, tag, deprecated) - see buildNavTree() in lib/config.ts.
|
|
326
|
+
const navMetaForSlug = (slug: string) => entryByFileId.get(slug)?.data;
|
|
296
327
|
const hrefForSlug = (slug: string) => {
|
|
297
328
|
const raw = entryByFileId.get(slug)?.id ?? slug;
|
|
298
329
|
const effective = normalizeEntryId(raw);
|
|
@@ -303,7 +334,7 @@ const hrefForSlug = (slug: string) => {
|
|
|
303
334
|
// and what every "is this the active page" comparison below needs, since
|
|
304
335
|
// navTree/globalDropdowns are built from writedocs.json's file-id space.
|
|
305
336
|
const currentFileId = fileIdForEntry(contentDir, packageRoot, entry);
|
|
306
|
-
const navTree = buildNavTree(activeSection.pages,
|
|
337
|
+
const navTree = buildNavTree(activeSection.pages, navTitleForSlug, methodForSlug, navMetaForSlug);
|
|
307
338
|
// The ancestor sidebar-group trail above the auto <h1> (Breadcrumbs.astro)
|
|
308
339
|
// - empty for a page sitting at the top level of its Section with no
|
|
309
340
|
// enclosing group, in which case [...slug].astro below skips rendering
|
|
@@ -326,8 +357,9 @@ const selectors = buildSelectors(activeSection.path, hrefForSlug, activePagePosi
|
|
|
326
357
|
const globalDropdowns = buildGlobalDropdowns(
|
|
327
358
|
resolveGlobalDropdowns(config.navigation),
|
|
328
359
|
currentFileId,
|
|
329
|
-
|
|
360
|
+
navTitleForSlug,
|
|
330
361
|
hrefForSlug,
|
|
362
|
+
navMetaForSlug,
|
|
331
363
|
);
|
|
332
364
|
|
|
333
365
|
// Meta tags: this page's own frontmatter `seo` overrides writedocs.json's
|
|
@@ -401,6 +433,11 @@ const components = {
|
|
|
401
433
|
Icon,
|
|
402
434
|
RequestExample,
|
|
403
435
|
ResponseExample,
|
|
436
|
+
Check,
|
|
437
|
+
ParamField,
|
|
438
|
+
ResponseField,
|
|
439
|
+
Columns,
|
|
440
|
+
Tooltip,
|
|
404
441
|
};
|
|
405
442
|
---
|
|
406
443
|
|
|
@@ -430,13 +467,25 @@ const components = {
|
|
|
430
467
|
<article class={`wd-article ${pageMode === "wide" ? "wd-article-wide" : ""}`} data-pagefind-body>
|
|
431
468
|
{breadcrumbs.length > 0 && <Breadcrumbs items={breadcrumbs} />}
|
|
432
469
|
<div class="wd-article-header">
|
|
433
|
-
|
|
470
|
+
{
|
|
471
|
+
// Frontmatter `deprecated` puts a label beside the <h1>, not in
|
|
472
|
+
// it - the <h1>'s text is the search index's page title.
|
|
473
|
+
entry.data.deprecated ? (
|
|
474
|
+
<div class="wd-article-title">
|
|
475
|
+
<h1 data-pagefind-meta="title">{entry.data.title}</h1>
|
|
476
|
+
<span class="wd-deprecated-badge">Deprecated</span>
|
|
477
|
+
</div>
|
|
478
|
+
) : (
|
|
479
|
+
<h1 data-pagefind-meta="title">{entry.data.title}</h1>
|
|
480
|
+
)
|
|
481
|
+
}
|
|
434
482
|
{showCopyPageMenu && (
|
|
435
483
|
<CopyPageMenu currentPath={currentPath} siteUrl={siteUrl} contextMenu={config.contextMenu!} />
|
|
436
484
|
)}
|
|
437
485
|
</div>
|
|
438
486
|
<Content components={components} />
|
|
439
487
|
{entry.data.openapi && <ApiPlayground operation={entry.data.openapi} contentDir={contentDir} />}
|
|
488
|
+
{!entry.data.hideFooterPagination && (
|
|
440
489
|
<nav class="wd-prevnext" data-pagefind-ignore>
|
|
441
490
|
{prev && (
|
|
442
491
|
<a class="wd-prevnext-card wd-prevnext-prev" href={hrefForSlug(prev.slug)}>
|
|
@@ -485,6 +534,7 @@ const components = {
|
|
|
485
534
|
</a>
|
|
486
535
|
)}
|
|
487
536
|
</nav>
|
|
537
|
+
)}
|
|
488
538
|
</article>
|
|
489
539
|
)
|
|
490
540
|
}
|
|
@@ -777,6 +827,24 @@ const components = {
|
|
|
777
827
|
.wd-article-header h1 {
|
|
778
828
|
margin: 0;
|
|
779
829
|
}
|
|
830
|
+
/* Frontmatter `deprecated: true` (Mintlify's) - same amber as the
|
|
831
|
+
sidebar's own deprecated tag (NavTree.astro) and Parameter's
|
|
832
|
+
deprecated badge. */
|
|
833
|
+
.wd-article-title {
|
|
834
|
+
display: flex;
|
|
835
|
+
align-items: center;
|
|
836
|
+
flex-wrap: wrap;
|
|
837
|
+
gap: 0.6rem;
|
|
838
|
+
}
|
|
839
|
+
.wd-deprecated-badge {
|
|
840
|
+
display: inline-block;
|
|
841
|
+
padding: 0.15rem 0.55rem;
|
|
842
|
+
border-radius: 999px;
|
|
843
|
+
font-size: 0.8rem;
|
|
844
|
+
font-weight: 700;
|
|
845
|
+
color: #d97706;
|
|
846
|
+
background: color-mix(in srgb, #d97706 15%, transparent);
|
|
847
|
+
}
|
|
780
848
|
.wd-prevnext {
|
|
781
849
|
display: flex;
|
|
782
850
|
justify-content: space-between;
|
|
@@ -991,6 +1059,19 @@ const components = {
|
|
|
991
1059
|
background: var(--wd-surface);
|
|
992
1060
|
border-bottom: 1px solid var(--wd-border);
|
|
993
1061
|
}
|
|
1062
|
+
/* A fence's `icon="..."` (shiki-code-block.js) - the icon's own SVG
|
|
1063
|
+
arrives as a data URL in --wd-code-icon and is used as a mask, so it
|
|
1064
|
+
paints in the title's text color instead of the SVG's own fill. */
|
|
1065
|
+
.wd-code-title-icon {
|
|
1066
|
+
display: inline-block;
|
|
1067
|
+
width: 1em;
|
|
1068
|
+
height: 1em;
|
|
1069
|
+
margin-right: 0.45rem;
|
|
1070
|
+
vertical-align: -0.125em;
|
|
1071
|
+
background: currentColor;
|
|
1072
|
+
-webkit-mask: var(--wd-code-icon) center / contain no-repeat;
|
|
1073
|
+
mask: var(--wd-code-icon) center / contain no-repeat;
|
|
1074
|
+
}
|
|
994
1075
|
.wd-code-copy-btn {
|
|
995
1076
|
position: absolute;
|
|
996
1077
|
top: 0.6rem;
|
|
@@ -52,6 +52,9 @@ export async function getStaticPaths() {
|
|
|
52
52
|
// as an LLM-facing "view this page as text" export, so they're left
|
|
53
53
|
// out of this route entirely rather than serving something misleading.
|
|
54
54
|
.filter((entry) => !entry.data.openapi)
|
|
55
|
+
// A frontmatter `url` page's HTML route is a redirect to that link -
|
|
56
|
+
// there's no page content to serve as Markdown.
|
|
57
|
+
.filter((entry) => !entry.data.url)
|
|
55
58
|
.map((entry) => ({
|
|
56
59
|
params: { slug: normalizeEntryId(entry.id) },
|
|
57
60
|
props: { entry },
|
|
@@ -49,6 +49,8 @@ export const GET: APIRoute = async () => {
|
|
|
49
49
|
// Same noindex exclusion llms.txt.ts applies to its own listing -
|
|
50
50
|
// see that file's comment.
|
|
51
51
|
.filter((entry) => !entry.data.seo?.noindex)
|
|
52
|
+
// Same `url` exclusion as llms.txt.ts - a redirect, not a page.
|
|
53
|
+
.filter((entry) => !entry.data.url)
|
|
52
54
|
.map((entry: DocsEntry) => {
|
|
53
55
|
const slug = normalizeEntryId(entry.id);
|
|
54
56
|
const body = entry.body ?? '';
|
package/src/pages/llms.txt.ts
CHANGED
|
@@ -85,6 +85,9 @@ export const GET: APIRoute = async () => {
|
|
|
85
85
|
// same reason: llms.txt exists to help external tools discover
|
|
86
86
|
// pages, exactly what `noindex` asked not to happen.
|
|
87
87
|
.filter((entry) => !entry.data.seo?.noindex)
|
|
88
|
+
// A frontmatter `url` page (Mintlify's external link) has no content -
|
|
89
|
+
// its own URL only redirects to the link.
|
|
90
|
+
.filter((entry) => !entry.data.url)
|
|
88
91
|
.map((entry: DocsEntry) => {
|
|
89
92
|
const slug = normalizeEntryId(entry.id);
|
|
90
93
|
// Same URL Astro's own router resolves this entry to - see
|
package/src/styles/global.css
CHANGED
|
@@ -16,3 +16,13 @@
|
|
|
16
16
|
classes available with zero effect on existing unstyled elements. */
|
|
17
17
|
@import "tailwindcss/theme.css";
|
|
18
18
|
@import "tailwindcss/utilities.css";
|
|
19
|
+
|
|
20
|
+
/* Tailwind's `dark:` variant follows the site's own theme toggle
|
|
21
|
+
([data-theme="dark"] on <html>, set by BaseLayout.astro), not the OS
|
|
22
|
+
setting it defaults to - otherwise Mintlify-style light/dark image
|
|
23
|
+
pairs (className="block dark:hidden" / "hidden dark:block") show the
|
|
24
|
+
wrong one whenever the reader's toggle and their OS disagree. The
|
|
25
|
+
site's content directory is added as a Tailwind source at build time
|
|
26
|
+
by astro.config.mjs's contentTailwindSource() plugin, since it lives
|
|
27
|
+
outside this package where Tailwind looks by default. */
|
|
28
|
+
@custom-variant dark (&:where([data-theme="dark"], [data-theme="dark"] *));
|