@writedocs/generator 0.4.6 → 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/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
|
@@ -1,4 +1,11 @@
|
|
|
1
|
-
|
|
1
|
+
---
|
|
2
|
+
// `defaultTabIndex` is Mintlify's: which tab (0-based) starts selected.
|
|
3
|
+
interface Props {
|
|
4
|
+
defaultTabIndex?: number;
|
|
5
|
+
}
|
|
6
|
+
const { defaultTabIndex = 0 } = Astro.props as Props;
|
|
7
|
+
---
|
|
8
|
+
<div class="wd-tabs" data-default-index={defaultTabIndex}>
|
|
2
9
|
<slot />
|
|
3
10
|
</div>
|
|
4
11
|
<script>
|
|
@@ -7,13 +14,20 @@
|
|
|
7
14
|
if (tabs.dataset.wdInit) return;
|
|
8
15
|
tabs.dataset.wdInit = 'true';
|
|
9
16
|
const panels = Array.from(tabs.querySelectorAll<HTMLElement>(':scope > .wd-tab'));
|
|
17
|
+
const requested = Number(tabs.dataset.defaultIndex ?? 0);
|
|
18
|
+
const initial = Number.isInteger(requested) && requested >= 0 && requested < panels.length ? requested : 0;
|
|
10
19
|
const nav = document.createElement('div');
|
|
11
20
|
nav.className = 'wd-tabs-nav';
|
|
12
21
|
panels.forEach((panel, i) => {
|
|
13
22
|
const btn = document.createElement('button');
|
|
14
23
|
btn.type = 'button';
|
|
15
|
-
|
|
16
|
-
|
|
24
|
+
// A Tab's `icon` is rendered server-side (astro-icon only runs at
|
|
25
|
+
// build time) into a hidden holder inside the panel - see
|
|
26
|
+
// Tab.astro - and moved into its button here.
|
|
27
|
+
const icon = panel.querySelector<HTMLElement>(':scope > .wd-tab-icon-src > *');
|
|
28
|
+
if (icon) btn.appendChild(icon);
|
|
29
|
+
btn.appendChild(document.createTextNode(panel.dataset.title ?? `Tab ${i + 1}`));
|
|
30
|
+
btn.className = 'wd-tabs-btn' + (i === initial ? ' active' : '');
|
|
17
31
|
btn.addEventListener('click', () => {
|
|
18
32
|
nav.querySelectorAll('.wd-tabs-btn').forEach((b) => b.classList.remove('active'));
|
|
19
33
|
panels.forEach((p) => (p.style.display = 'none'));
|
|
@@ -21,7 +35,7 @@
|
|
|
21
35
|
panel.style.display = 'block';
|
|
22
36
|
});
|
|
23
37
|
nav.appendChild(btn);
|
|
24
|
-
panel.style.display = i ===
|
|
38
|
+
panel.style.display = i === initial ? 'block' : 'none';
|
|
25
39
|
});
|
|
26
40
|
tabs.prepend(nav);
|
|
27
41
|
});
|
|
@@ -45,6 +59,15 @@
|
|
|
45
59
|
color: var(--wd-text-muted);
|
|
46
60
|
border-bottom: 2px solid transparent;
|
|
47
61
|
}
|
|
62
|
+
.wd-tabs-btn {
|
|
63
|
+
display: inline-flex;
|
|
64
|
+
align-items: center;
|
|
65
|
+
gap: 0.4rem;
|
|
66
|
+
}
|
|
67
|
+
svg.wd-tabs-btn-icon {
|
|
68
|
+
width: 1em;
|
|
69
|
+
height: 1em;
|
|
70
|
+
}
|
|
48
71
|
.wd-tabs-btn.active {
|
|
49
72
|
color: var(--wd-primary);
|
|
50
73
|
border-bottom-color: var(--wd-primary);
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
---
|
|
2
|
+
// Mintlify's <Tooltip tip headline cta href> - rendered by Hint.astro,
|
|
3
|
+
// which is the same inline hover bubble under writedocs' own name.
|
|
4
|
+
import Hint from './Hint.astro';
|
|
5
|
+
interface Props {
|
|
6
|
+
tip: string;
|
|
7
|
+
headline?: string;
|
|
8
|
+
cta?: string;
|
|
9
|
+
href?: string;
|
|
10
|
+
}
|
|
11
|
+
const props = Astro.props as Props;
|
|
12
|
+
---
|
|
13
|
+
<Hint {...props}><slot /></Hint>
|
package/src/components/index.ts
CHANGED
|
@@ -46,3 +46,8 @@ export { default as Badge } from './Badge.astro';
|
|
|
46
46
|
export { default as Icon } from './Icon.astro';
|
|
47
47
|
export { default as RequestExample } from './RequestExample.astro';
|
|
48
48
|
export { default as ResponseExample } from './ResponseExample.astro';
|
|
49
|
+
export { default as Check } from './Check.astro';
|
|
50
|
+
export { default as ParamField } from './ParamField.astro';
|
|
51
|
+
export { default as ResponseField } from './ResponseField.astro';
|
|
52
|
+
export { default as Columns } from './Columns.astro';
|
|
53
|
+
export { default as Tooltip } from './Tooltip.astro';
|
package/src/content.config.ts
CHANGED
|
@@ -32,6 +32,8 @@ const contentDir = process.env.WRITEDOCS_CONTENT_DIR || process.cwd();
|
|
|
32
32
|
const generatedDocsDir = path.join(writedocsTempDir(contentDir), 'generated-docs');
|
|
33
33
|
const generatedDocsBase = pathToFileURL(generatedDocsDir + path.sep);
|
|
34
34
|
|
|
35
|
+
const MINTLIFY_MODE_ALIASES: Record<string, string> = { center: 'frame', assistant: 'default' };
|
|
36
|
+
|
|
35
37
|
const docsSchema = z.object({
|
|
36
38
|
title: z.string(),
|
|
37
39
|
description: z.string().optional(),
|
|
@@ -82,7 +84,19 @@ const docsSchema = z.object({
|
|
|
82
84
|
// chrome at all, just <slot />. For a page that wants to
|
|
83
85
|
// look nothing like the rest of the site (an auth screen,
|
|
84
86
|
// a print-style page).
|
|
85
|
-
|
|
87
|
+
//
|
|
88
|
+
// Two Mintlify values are accepted so a migrated page doesn't fail the
|
|
89
|
+
// build (see MINTLIFY_MODE_ALIASES below): `center` is the same layout
|
|
90
|
+
// as our `frame`, and `assistant` (a full-page Mintlify AI chat, which
|
|
91
|
+
// writedocs doesn't have) falls back to `default`. Mintlify's own
|
|
92
|
+
// `frame` means something else (a canvas that keeps the sidebar) - it's
|
|
93
|
+
// left as our `frame`; see docs/dev/docs/mintlify-compat.mdx.
|
|
94
|
+
mode: z
|
|
95
|
+
.preprocess(
|
|
96
|
+
(value) => (typeof value === 'string' && value in MINTLIFY_MODE_ALIASES ? MINTLIFY_MODE_ALIASES[value] : value),
|
|
97
|
+
z.enum(['default', 'wide', 'frame', 'custom', 'blank']),
|
|
98
|
+
)
|
|
99
|
+
.default('default'),
|
|
86
100
|
// Per-page meta tag overrides - same shape as writedocs.json's top-level
|
|
87
101
|
// `seo` (see seoFieldsSchema in lib/config.ts, the single source of
|
|
88
102
|
// truth for this shape). A page only needs to set the specific fields
|
|
@@ -90,7 +104,59 @@ const docsSchema = z.object({
|
|
|
90
104
|
// for anything left unset. See BaseLayout.astro for where this and the
|
|
91
105
|
// site-wide seo actually get merged and rendered.
|
|
92
106
|
seo: seoFieldsSchema.optional(),
|
|
93
|
-
|
|
107
|
+
// Mintlify's spelling of `seo.noindex` - a top-level `noindex: true`.
|
|
108
|
+
// Folded into `seo` by the transform below, so everything downstream
|
|
109
|
+
// (the robots meta tag, sitemap.xml, llms.txt) keeps reading
|
|
110
|
+
// `seo.noindex` only.
|
|
111
|
+
noindex: z.boolean().optional(),
|
|
112
|
+
// Mintlify's short navigation label - used for the sidebar and the
|
|
113
|
+
// topbar dropdown menus in place of `title` ([...slug].astro's
|
|
114
|
+
// navTitleForSlug). The page's own <h1> and prev/next links keep `title`.
|
|
115
|
+
sidebarTitle: z.string().optional(),
|
|
116
|
+
// Mintlify's spellings of `seo.keywords`, `seo.ogImage`, `seo.ogType`
|
|
117
|
+
// and `seo.twitterCard`, folded into `seo` below like `noindex`.
|
|
118
|
+
keywords: z.array(z.string()).optional(),
|
|
119
|
+
'og:image': z.string().optional(),
|
|
120
|
+
'og:type': z.string().optional(),
|
|
121
|
+
'twitter:card': z.enum(['summary', 'summary_large_image']).optional(),
|
|
122
|
+
// Mintlify's sidebar/page-chrome fields - see NavTree.astro and
|
|
123
|
+
// [...slug].astro for where each is used:
|
|
124
|
+
// icon - shown before the page's label in the sidebar.
|
|
125
|
+
// tag - a short label after it (e.g. "NEW").
|
|
126
|
+
// deprecated - a "Deprecated" label in the sidebar and next
|
|
127
|
+
// to the page's <h1>.
|
|
128
|
+
// hidden - left out of the sidebar, dropdowns and
|
|
129
|
+
// prev/next, but still built and reachable by
|
|
130
|
+
// URL. Also noindexed, as on Mintlify.
|
|
131
|
+
// url - an external link: the page's sidebar entry
|
|
132
|
+
// links straight to it, and the page's own URL
|
|
133
|
+
// redirects there.
|
|
134
|
+
// hideFooterPagination - no prev/next links on this page.
|
|
135
|
+
// hideApiMarker - no HTTP method badge on this page's sidebar
|
|
136
|
+
// entry.
|
|
137
|
+
icon: z.string().optional(),
|
|
138
|
+
tag: z.string().optional(),
|
|
139
|
+
deprecated: z.boolean().optional(),
|
|
140
|
+
hidden: z.boolean().optional(),
|
|
141
|
+
url: z.string().optional(),
|
|
142
|
+
hideFooterPagination: z.boolean().optional(),
|
|
143
|
+
hideApiMarker: z.boolean().optional(),
|
|
144
|
+
}).transform(
|
|
145
|
+
({ noindex, keywords, 'og:image': ogImage, 'og:type': ogType, 'twitter:card': twitterCard, ...data }) => {
|
|
146
|
+
// Top-level Mintlify keys fill in `seo` only where the page's own `seo`
|
|
147
|
+
// doesn't already set that field - an explicit `seo` value always wins.
|
|
148
|
+
// A `hidden` page is noindexed unless it says otherwise.
|
|
149
|
+
const fromMintlify = { noindex: noindex ?? (data.hidden ? true : undefined), keywords, ogImage, ogType, twitterCard };
|
|
150
|
+
const seo = { ...data.seo };
|
|
151
|
+
let changed = false;
|
|
152
|
+
for (const [key, value] of Object.entries(fromMintlify)) {
|
|
153
|
+
if (value === undefined || seo[key as keyof typeof seo] !== undefined) continue;
|
|
154
|
+
(seo as Record<string, unknown>)[key] = value;
|
|
155
|
+
changed = true;
|
|
156
|
+
}
|
|
157
|
+
return changed ? { ...data, seo } : data;
|
|
158
|
+
},
|
|
159
|
+
);
|
|
94
160
|
|
|
95
161
|
/** Wraps another loader so it's skipped entirely - no filesystem scan, no
|
|
96
162
|
* "directory doesn't exist"/"no files found" warning - when its own base
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
import { navTreeContainsSlug, isExternalHref, type NavTreeNode } from "../../lib/config";
|
|
3
|
+
import AppIcon from "../../components/AppIcon.astro";
|
|
3
4
|
import { methodClass } from "../../lib/openapi-render";
|
|
4
5
|
|
|
5
6
|
interface Props {
|
|
@@ -22,7 +23,10 @@ const { nodes, hrefForSlug, currentSlug, depth = 0 } = Astro.props as Props;
|
|
|
22
23
|
{node.method && (
|
|
23
24
|
<span class={`wd-nav-method wd-nav-method-${methodClass(node.method)}`}>{node.method}</span>
|
|
24
25
|
)}
|
|
26
|
+
{node.icon && <AppIcon icon={node.icon} class="wd-nav-page-icon" />}
|
|
25
27
|
<span class="wd-nav-title">{node.title}</span>
|
|
28
|
+
{node.deprecated && <span class="wd-nav-tag wd-nav-tag-deprecated">Deprecated</span>}
|
|
29
|
+
{node.tag && <span class="wd-nav-tag">{node.tag}</span>}
|
|
26
30
|
</a>
|
|
27
31
|
</li>
|
|
28
32
|
);
|
|
@@ -227,6 +231,36 @@ const { nodes, hrefForSlug, currentSlug, depth = 0 } = Astro.props as Props;
|
|
|
227
231
|
/* text-overflow: ellipsis;
|
|
228
232
|
white-space: nowrap; */
|
|
229
233
|
}
|
|
234
|
+
/* A page's own frontmatter `icon` (Mintlify's) - :global() because
|
|
235
|
+
AppIcon.astro renders the svg/span, not this component. */
|
|
236
|
+
:global(svg.wd-nav-page-icon),
|
|
237
|
+
:global(span.wd-nav-page-icon) {
|
|
238
|
+
flex-shrink: 0;
|
|
239
|
+
width: 1em;
|
|
240
|
+
height: 1em;
|
|
241
|
+
}
|
|
242
|
+
:global(span.wd-nav-page-icon) {
|
|
243
|
+
display: flex;
|
|
244
|
+
align-items: center;
|
|
245
|
+
justify-content: center;
|
|
246
|
+
}
|
|
247
|
+
/* Frontmatter `tag` / `deprecated` - a small pill after the title, same
|
|
248
|
+
shape as the method badge. */
|
|
249
|
+
.wd-nav-tag {
|
|
250
|
+
flex-shrink: 0;
|
|
251
|
+
padding: 0.05rem 0.4rem;
|
|
252
|
+
border-radius: 999px;
|
|
253
|
+
font-size: 0.62rem;
|
|
254
|
+
font-weight: 700;
|
|
255
|
+
line-height: 1.5;
|
|
256
|
+
letter-spacing: 0.02em;
|
|
257
|
+
color: var(--wd-primary);
|
|
258
|
+
background: color-mix(in srgb, var(--wd-primary) 12%, transparent);
|
|
259
|
+
}
|
|
260
|
+
.wd-nav-tag-deprecated {
|
|
261
|
+
color: #d97706;
|
|
262
|
+
background: color-mix(in srgb, #d97706 15%, transparent);
|
|
263
|
+
}
|
|
230
264
|
.wd-nav-method {
|
|
231
265
|
flex-shrink: 0;
|
|
232
266
|
display: inline-block;
|
|
@@ -232,3 +232,15 @@ pre {
|
|
|
232
232
|
font-weight: var(--shiki-dark-font-weight) !important;
|
|
233
233
|
text-decoration: var(--shiki-dark-text-decoration) !important;
|
|
234
234
|
}
|
|
235
|
+
|
|
236
|
+
/* An icon given as an image URL or path (resolveIcon()'s 'image' kind,
|
|
237
|
+
rendered by AppIcon.astro) - 1em square inside its caller's own icon
|
|
238
|
+
span, so it sizes like a glyph icon would. Lives here rather than in
|
|
239
|
+
AppIcon.astro so it's part of the shared stylesheet, not an inline
|
|
240
|
+
<style> added to every page. */
|
|
241
|
+
img.wd-icon-img {
|
|
242
|
+
display: block;
|
|
243
|
+
width: 1em;
|
|
244
|
+
height: 1em;
|
|
245
|
+
object-fit: contain;
|
|
246
|
+
}
|