@writedocs/generator 0.4.9 → 0.4.11
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 +39 -2
- package/bin/writedocs.js +23 -0
- package/package.json +1 -1
- package/src/cli/convert.js +82 -0
- package/src/cli/generate-api-pages.js +56 -3
- package/src/components/Accordion.astro +2 -1
- package/src/components/AccordionGroup.astro +4 -1
- package/src/components/ApiPlayground.astro +6 -2
- package/src/components/ApiReferencePanel.astro +6 -2
- package/src/components/Badge.astro +2 -0
- package/src/components/Callout.astro +2 -1
- package/src/components/Card.astro +2 -1
- package/src/components/CardGroup.astro +2 -1
- package/src/components/Check.astro +1 -1
- package/src/components/CodeBlock.astro +94 -0
- package/src/components/CodeGroup.astro +2 -1
- package/src/components/Color.astro +2 -1
- package/src/components/ColorItem.astro +2 -1
- package/src/components/ColorRow.astro +2 -1
- package/src/components/Column.astro +19 -0
- package/src/components/Columns.astro +1 -1
- package/src/components/Danger.astro +1 -1
- package/src/components/Expandable.astro +2 -1
- package/src/components/Frame.astro +2 -1
- package/src/components/GitHubRepo.astro +2 -1
- package/src/components/Hint.astro +2 -1
- package/src/components/Icon.astro +3 -2
- package/src/components/Image.astro +2 -1
- package/src/components/Info.astro +1 -1
- package/src/components/Note.astro +1 -1
- package/src/components/Panel.astro +2 -1
- package/src/components/Parameter.astro +2 -1
- package/src/components/Prompt.astro +2 -1
- package/src/components/RequestExample.astro +2 -1
- package/src/components/ResponseExample.astro +2 -1
- package/src/components/Searchbar.astro +2 -1
- package/src/components/Step.astro +2 -1
- package/src/components/Steps.astro +4 -1
- package/src/components/Tab.astro +2 -1
- package/src/components/Tabs.astro +2 -1
- package/src/components/Tile.astro +2 -1
- package/src/components/Tip.astro +1 -1
- package/src/components/TreeFile.astro +2 -1
- package/src/components/TreeFolder.astro +2 -1
- package/src/components/Update.astro +2 -1
- package/src/components/Video.astro +2 -1
- package/src/components/View.astro +2 -1
- package/src/components/Warning.astro +1 -1
- package/src/components/class-names.ts +8 -0
- package/src/components/index.ts +2 -0
- package/src/content.config.ts +23 -2
- package/src/lib/content-check.js +36 -6
- package/src/lib/mdx-auto-hydrate.js +12 -0
- package/src/lib/mdx-inject-builtins.js +15 -0
- package/src/lib/mdx-inline-react.js +202 -0
- package/src/lib/mdx-mintlify.js +65 -0
- package/src/lib/mdx-substitute-variables.js +17 -0
- package/src/lib/mdx-unknown-components.js +56 -3
- package/src/lib/mintlify-convert.js +599 -0
- package/src/lib/openapi-ref.js +44 -0
- package/src/lib/openapi-render.ts +10 -1
- package/src/lib/pages.js +89 -17
- package/src/pages/[...slug].astro +8 -2
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
---
|
|
2
|
+
import { extraClasses } from './class-names';
|
|
2
3
|
// Mintlify's <GitHub.Repo repo="owner/name" variant="inset|flat"> - a card
|
|
3
4
|
// linking to a public GitHub repository. Attached as GitHub.Repo in
|
|
4
5
|
// components/compound.ts. `repo` is an "owner/name" slug or a full
|
|
@@ -27,7 +28,7 @@ const valid = /^[\w.-]+\/[\w.-]+$/.test(slug);
|
|
|
27
28
|
---
|
|
28
29
|
{valid && (
|
|
29
30
|
<a
|
|
30
|
-
class:list={['wd-github-repo', `wd-github-repo-${variant}
|
|
31
|
+
class:list={['wd-github-repo', `wd-github-repo-${variant}`, extraClasses(Astro.props)]}
|
|
31
32
|
href={`https://github.com/${slug}`}
|
|
32
33
|
target="_blank"
|
|
33
34
|
rel="noopener noreferrer"
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
---
|
|
2
|
+
import { extraClasses } from './class-names';
|
|
2
3
|
// Inline, text-level component - unlike every other built-in here
|
|
3
4
|
// (Callout, Card, Accordion, ...), which are all block-level and get
|
|
4
5
|
// their own line, a Hint sits mid-sentence: `word <Hint tip="...">this
|
|
@@ -28,7 +29,7 @@ const { tip, headline, cta, href } = Astro.props as Props;
|
|
|
28
29
|
const hasLink = Boolean(cta && href);
|
|
29
30
|
---
|
|
30
31
|
|
|
31
|
-
<span class:list={["wd-hint", { "wd-hint-rich": Boolean(headline) || hasLink }]} tabindex="0">
|
|
32
|
+
<span class:list={["wd-hint", { "wd-hint-rich": Boolean(headline) || hasLink }, extraClasses(Astro.props)]} tabindex="0">
|
|
32
33
|
<span class="wd-hint-trigger"><slot /></span>
|
|
33
34
|
<span class="wd-hint-tooltip" role="tooltip">
|
|
34
35
|
{headline && <span class="wd-hint-headline">{headline}</span>}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
---
|
|
2
|
+
import { extraClasses } from './class-names';
|
|
2
3
|
// Renders either a real icon (via `icon`) or a custom image (via
|
|
3
4
|
// `src`) inline with surrounding text - modeled on Mintlify's own Icon
|
|
4
5
|
// (https://www.mintlify.com/docs/components/icons), which documents
|
|
@@ -42,9 +43,9 @@ const iconStyle =
|
|
|
42
43
|
|
|
43
44
|
{
|
|
44
45
|
src ? (
|
|
45
|
-
<img src={src} alt={alt} class="wd-icon-image" style={`height:${height}`} />
|
|
46
|
+
<img src={src} alt={alt} class:list={["wd-icon-image", extraClasses(Astro.props)]} style={`height:${height}`} />
|
|
46
47
|
) : icon ? (
|
|
47
|
-
<AppIcon icon={icon} class="wd-icon-inline" style={iconStyle} />
|
|
48
|
+
<AppIcon icon={icon} class={["wd-icon-inline", ...extraClasses(Astro.props)].join(" ")} style={iconStyle} />
|
|
48
49
|
) : null
|
|
49
50
|
}
|
|
50
51
|
<style>
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
---
|
|
2
|
+
import { extraClasses } from './class-names';
|
|
2
3
|
// Block-level (own line, centered) - unlike Hint (inline/text-level),
|
|
3
4
|
// this is meant to be dropped in on its own the way Callout/Card are.
|
|
4
5
|
//
|
|
@@ -65,7 +66,7 @@ const sizeStyle = size ? `--wd-image-size:${size}` : undefined;
|
|
|
65
66
|
---
|
|
66
67
|
|
|
67
68
|
<figure
|
|
68
|
-
class:list={["wd-image-figure", caption && "wd-image-figure-framed", srcDark && "wd-image-has-dark"]}
|
|
69
|
+
class:list={["wd-image-figure", caption && "wd-image-figure-framed", srcDark && "wd-image-has-dark", extraClasses(Astro.props)]}
|
|
69
70
|
style={sizeStyle}
|
|
70
71
|
>
|
|
71
72
|
<img src={src} alt={alt} class="wd-image wd-image-light" nozoom={noZoom || undefined} />
|
|
@@ -9,4 +9,4 @@ interface Props {
|
|
|
9
9
|
}
|
|
10
10
|
const { title, _titleId } = Astro.props as Props;
|
|
11
11
|
---
|
|
12
|
-
<Callout type="info" title={title} _titleId={_titleId}><slot /></Callout>
|
|
12
|
+
<Callout type="info" title={title} _titleId={_titleId} class={Astro.props.class} className={Astro.props.className}><slot /></Callout>
|
|
@@ -9,4 +9,4 @@ interface Props {
|
|
|
9
9
|
}
|
|
10
10
|
const { title, _titleId } = Astro.props as Props;
|
|
11
11
|
---
|
|
12
|
-
<Callout type="note" title={title} _titleId={_titleId}><slot /></Callout>
|
|
12
|
+
<Callout type="note" title={title} _titleId={_titleId} class={Astro.props.class} className={Astro.props.className}><slot /></Callout>
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
---
|
|
2
|
+
import { extraClasses } from './class-names';
|
|
2
3
|
// Mintlify's <Panel> - content pinned to the right-hand column on desktop,
|
|
3
4
|
// in place of the table of contents. It's the same docking
|
|
4
5
|
// RequestExample/ResponseExample already use (initExamplePanels() in
|
|
@@ -8,4 +9,4 @@
|
|
|
8
9
|
// with no table-of-contents column (any mode but `default`), the Panel
|
|
9
10
|
// stays where it is in the page.
|
|
10
11
|
---
|
|
11
|
-
<div class="wd-example-panel wd-panel"><slot /></div>
|
|
12
|
+
<div class:list={["wd-example-panel wd-panel", extraClasses(Astro.props)]}><slot /></div>
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
---
|
|
2
|
+
import { extraClasses } from './class-names';
|
|
2
3
|
// The name+type+description row used throughout API reference content -
|
|
3
4
|
// modeled directly on Mintlify's own ParamField/ResponseField
|
|
4
5
|
// (https://www.mintlify.com/docs/components/expandables), which this
|
|
@@ -38,7 +39,7 @@ const { name, type, required = false, default: defaultValue, deprecated = false,
|
|
|
38
39
|
const hasDefault = defaultValue !== undefined && defaultValue !== null && defaultValue !== "";
|
|
39
40
|
---
|
|
40
41
|
|
|
41
|
-
<div class:list={["wd-parameter", { "wd-parameter-deprecated": deprecated }]}>
|
|
42
|
+
<div class:list={["wd-parameter", { "wd-parameter-deprecated": deprecated }, extraClasses(Astro.props)]}>
|
|
42
43
|
<div class="wd-parameter-head">
|
|
43
44
|
{pre.map((label) => <span class="wd-parameter-label">{label}</span>)}
|
|
44
45
|
<span class="wd-parameter-name">{name}</span>
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
---
|
|
2
|
+
import { extraClasses } from './class-names';
|
|
2
3
|
// Mintlify's <Prompt> - a ready-made AI prompt: a card showing
|
|
3
4
|
// `description` (inline Markdown) and an optional icon, with actions to
|
|
4
5
|
// copy the prompt or open it in Cursor. The prompt itself is the slot; it's
|
|
@@ -27,7 +28,7 @@ const { description, icon, actions = ['copy'], _text } = Astro.props as Props;
|
|
|
27
28
|
const cursorHref = _text ? `https://cursor.com/link/prompt?text=${encodeURIComponent(_text)}` : null;
|
|
28
29
|
const showCursor = actions.includes('cursor') && cursorHref !== null && cursorHref.length <= 10000;
|
|
29
30
|
---
|
|
30
|
-
<div class="wd-prompt">
|
|
31
|
+
<div class:list={["wd-prompt", extraClasses(Astro.props)]}>
|
|
31
32
|
<div class="wd-prompt-head">
|
|
32
33
|
{icon && <AppIcon icon={icon} class="wd-prompt-icon" />}
|
|
33
34
|
{description && <div class="wd-prompt-description" set:html={inlineMarkdown(description)} />}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
---
|
|
2
|
+
import { extraClasses } from './class-names';
|
|
2
3
|
// Pins its code example(s) in the right sidebar - in place of the page's
|
|
3
4
|
// normal table of contents - on desktop; falls back to a plain in-place
|
|
4
5
|
// CodeGroup on narrower viewports where that sidebar column doesn't
|
|
@@ -26,7 +27,7 @@ interface Props {
|
|
|
26
27
|
}
|
|
27
28
|
const { dropdown = false } = Astro.props as Props;
|
|
28
29
|
---
|
|
29
|
-
<div class="wd-example-panel wd-request-example">
|
|
30
|
+
<div class:list={["wd-example-panel wd-request-example", extraClasses(Astro.props)]}>
|
|
30
31
|
<CodeGroup dropdown={dropdown}>
|
|
31
32
|
<slot />
|
|
32
33
|
</CodeGroup>
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
---
|
|
2
|
+
import { extraClasses } from './class-names';
|
|
2
3
|
// See RequestExample.astro's own comment - identical in every respect
|
|
3
4
|
// except the marker class (wd-response-example vs wd-request-example),
|
|
4
5
|
// which src/scripts/example-panels.ts uses to always dock Response
|
|
@@ -12,7 +13,7 @@ interface Props {
|
|
|
12
13
|
}
|
|
13
14
|
const { dropdown = false } = Astro.props as Props;
|
|
14
15
|
---
|
|
15
|
-
<div class="wd-example-panel wd-response-example">
|
|
16
|
+
<div class:list={["wd-example-panel wd-response-example", extraClasses(Astro.props)]}>
|
|
16
17
|
<CodeGroup dropdown={dropdown}>
|
|
17
18
|
<slot />
|
|
18
19
|
</CodeGroup>
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
---
|
|
2
|
+
import { extraClasses } from './class-names';
|
|
2
3
|
// Wraps a GFM table (or any other block-level slot content, though a
|
|
3
4
|
// table is the intended/primary use case) with a text input above it
|
|
4
5
|
// that live-filters rows by their own text content - "let users filter
|
|
@@ -21,7 +22,7 @@ interface Props {
|
|
|
21
22
|
const { placeholder = "Search..." } = Astro.props as Props;
|
|
22
23
|
---
|
|
23
24
|
|
|
24
|
-
<div class="wd-searchbar">
|
|
25
|
+
<div class:list={["wd-searchbar", extraClasses(Astro.props)]}>
|
|
25
26
|
<input type="text" class="wd-searchbar-input" placeholder={placeholder} aria-label={placeholder} />
|
|
26
27
|
<div class="wd-searchbar-content">
|
|
27
28
|
<slot />
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
---
|
|
2
|
+
import { extraClasses } from './class-names';
|
|
2
3
|
import AppIcon from './AppIcon.astro';
|
|
3
4
|
// `icon`, `stepNumber` and `titleSize` are Mintlify's. `icon` replaces the
|
|
4
5
|
// number in the step's circle. `stepNumber` sets this step's number; the
|
|
@@ -15,7 +16,7 @@ const { title, icon, stepNumber, titleSize = 'p' } = Astro.props as Props;
|
|
|
15
16
|
const style = typeof stepNumber === 'number' ? `counter-set: wd-step ${stepNumber - 1}` : undefined;
|
|
16
17
|
const TitleTag = titleSize === 'h2' || titleSize === 'h3' ? titleSize : 'p';
|
|
17
18
|
---
|
|
18
|
-
<div class:list={['wd-step', { 'wd-step-has-icon': Boolean(icon) }]} style={style}>
|
|
19
|
+
<div class:list={['wd-step', { 'wd-step-has-icon': Boolean(icon) }, extraClasses(Astro.props)]} style={style}>
|
|
19
20
|
{icon && <AppIcon icon={icon} class="wd-step-icon" />}
|
|
20
21
|
{
|
|
21
22
|
title &&
|
package/src/components/Tab.astro
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
---
|
|
2
|
+
import { extraClasses } from './class-names';
|
|
2
3
|
import AppIcon from './AppIcon.astro';
|
|
3
4
|
// `icon` is Mintlify's: shown before the title in the tab's button. The
|
|
4
5
|
// button is built client-side by Tabs.astro, which moves this hidden,
|
|
@@ -9,7 +10,7 @@ interface Props {
|
|
|
9
10
|
}
|
|
10
11
|
const { title, icon } = Astro.props as Props;
|
|
11
12
|
---
|
|
12
|
-
<div class="wd-tab" data-title={title}>
|
|
13
|
+
<div class:list={["wd-tab", extraClasses(Astro.props)]} data-title={title}>
|
|
13
14
|
{icon && <span class="wd-tab-icon-src" hidden><AppIcon icon={icon} class="wd-tabs-btn-icon" /></span>}
|
|
14
15
|
<slot />
|
|
15
16
|
</div>
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
---
|
|
2
|
+
import { extraClasses } from './class-names';
|
|
2
3
|
// `defaultTabIndex` is Mintlify's: which tab (0-based) starts selected.
|
|
3
4
|
interface Props {
|
|
4
5
|
defaultTabIndex?: number;
|
|
5
6
|
}
|
|
6
7
|
const { defaultTabIndex = 0 } = Astro.props as Props;
|
|
7
8
|
---
|
|
8
|
-
<div class="wd-tabs" data-default-index={defaultTabIndex}>
|
|
9
|
+
<div class:list={["wd-tabs", extraClasses(Astro.props)]} data-default-index={defaultTabIndex}>
|
|
9
10
|
<slot />
|
|
10
11
|
</div>
|
|
11
12
|
<script>
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
---
|
|
2
|
+
import { extraClasses } from './class-names';
|
|
2
3
|
// Mintlify's <Tile> - a visual preview (the slot, usually an image or a
|
|
3
4
|
// light/dark image pair) on a patterned background, with a title and
|
|
4
5
|
// description under it. Usually laid out in a <Columns> grid. `title`
|
|
@@ -13,7 +14,7 @@ interface Props {
|
|
|
13
14
|
const { href, title, description } = Astro.props as Props;
|
|
14
15
|
const Tag = href ? 'a' : 'div';
|
|
15
16
|
---
|
|
16
|
-
<Tag class="wd-tile" href={href}>
|
|
17
|
+
<Tag class:list={["wd-tile", extraClasses(Astro.props)]} href={href}>
|
|
17
18
|
<div class="wd-tile-preview"><slot /></div>
|
|
18
19
|
{title && <div class="wd-tile-title" set:html={inlineMarkdown(title)} />}
|
|
19
20
|
{description && <div class="wd-tile-description">{description}</div>}
|
package/src/components/Tip.astro
CHANGED
|
@@ -9,4 +9,4 @@ interface Props {
|
|
|
9
9
|
}
|
|
10
10
|
const { title, _titleId } = Astro.props as Props;
|
|
11
11
|
---
|
|
12
|
-
<Callout type="tip" title={title} _titleId={_titleId}><slot /></Callout>
|
|
12
|
+
<Callout type="tip" title={title} _titleId={_titleId} class={Astro.props.class} className={Astro.props.className}><slot /></Callout>
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
---
|
|
2
|
+
import { extraClasses } from './class-names';
|
|
2
3
|
// Mintlify's <Tree.File name="..."> - see Tree.astro. `highlight` marks it
|
|
3
4
|
// as the active item.
|
|
4
5
|
import AppIcon from './AppIcon.astro';
|
|
@@ -8,7 +9,7 @@ interface Props {
|
|
|
8
9
|
}
|
|
9
10
|
const { name, highlight = false } = Astro.props as Props;
|
|
10
11
|
---
|
|
11
|
-
<li class:list={['wd-tree-item', 'wd-tree-file', { 'wd-tree-highlight': highlight }]} role="treeitem" tabindex="-1">
|
|
12
|
+
<li class:list={['wd-tree-item', 'wd-tree-file', { 'wd-tree-highlight': highlight }, extraClasses(Astro.props)]} role="treeitem" tabindex="-1">
|
|
12
13
|
<span class="wd-tree-row">
|
|
13
14
|
<span class="wd-tree-chevron wd-tree-chevron-spacer" aria-hidden="true"></span>
|
|
14
15
|
<AppIcon icon="lucide:file" class="wd-tree-icon" />
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
---
|
|
2
|
+
import { extraClasses } from './class-names';
|
|
2
3
|
// Mintlify's <Tree.Folder name="..."> - see Tree.astro. `defaultOpen`
|
|
3
4
|
// starts it expanded; `openable={false}` makes it a fixed, non-toggling
|
|
4
5
|
// folder (shown open or closed per `defaultOpen`); `highlight` marks it as
|
|
@@ -13,7 +14,7 @@ interface Props {
|
|
|
13
14
|
const { name, defaultOpen = false, openable = true, highlight = false } = Astro.props as Props;
|
|
14
15
|
---
|
|
15
16
|
<li
|
|
16
|
-
class:list={['wd-tree-item', 'wd-tree-folder', { 'wd-tree-highlight': highlight, 'wd-tree-static': !openable }]}
|
|
17
|
+
class:list={['wd-tree-item', 'wd-tree-folder', { 'wd-tree-highlight': highlight, 'wd-tree-static': !openable }, extraClasses(Astro.props)]}
|
|
17
18
|
role="treeitem"
|
|
18
19
|
aria-expanded={defaultOpen ? 'true' : 'false'}
|
|
19
20
|
tabindex="-1"
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
---
|
|
2
|
+
import { extraClasses } from './class-names';
|
|
2
3
|
// Mintlify's <Update> - one changelog entry: `label` (usually a date or
|
|
3
4
|
// version) in a left column that sticks while its content scrolls, with
|
|
4
5
|
// `description` and `tags` under it. The label is a linkable anchor, deduped
|
|
@@ -25,7 +26,7 @@ function slugify(value: string): string {
|
|
|
25
26
|
}
|
|
26
27
|
const id = _titleId ?? slugify(label);
|
|
27
28
|
---
|
|
28
|
-
<section class="wd-update" data-update-tags={JSON.stringify(tags)}>
|
|
29
|
+
<section class:list={["wd-update", extraClasses(Astro.props)]} data-update-tags={JSON.stringify(tags)}>
|
|
29
30
|
<div class="wd-update-meta">
|
|
30
31
|
<div class="wd-update-label" id={id}>
|
|
31
32
|
<a href={`#${id}`}>{label}</a>
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
---
|
|
2
|
+
import { extraClasses } from './class-names';
|
|
2
3
|
// Renders either a native <video> or an <iframe> embed from the same
|
|
3
4
|
// `src` prop, auto-detected rather than requiring the author to pick -
|
|
4
5
|
// `src="media/clip.mp4"` (a direct video file, local or remote) gets a
|
|
@@ -57,7 +58,7 @@ const sizeStyle = width ? `--wd-video-size:${width}` : undefined;
|
|
|
57
58
|
const effectiveMuted = muted || autoplay;
|
|
58
59
|
---
|
|
59
60
|
|
|
60
|
-
<figure class:list={["wd-video-figure", caption && "wd-video-figure-framed"]} style={sizeStyle}>
|
|
61
|
+
<figure class:list={["wd-video-figure", caption && "wd-video-figure-framed", extraClasses(Astro.props)]} style={sizeStyle}>
|
|
61
62
|
{
|
|
62
63
|
isFile ? (
|
|
63
64
|
<video
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
---
|
|
2
|
+
import { extraClasses } from './class-names';
|
|
2
3
|
// Mintlify's <View title="..." icon="..."> - content for one of several
|
|
3
4
|
// alternatives (a language, a framework) on the same page. Every distinct
|
|
4
5
|
// `title` on the page becomes an option in one switcher, placed under the
|
|
@@ -16,7 +17,7 @@ interface Props {
|
|
|
16
17
|
}
|
|
17
18
|
const { title, icon } = Astro.props as Props;
|
|
18
19
|
---
|
|
19
|
-
<div class="wd-view" data-view-title={title}>
|
|
20
|
+
<div class:list={["wd-view", extraClasses(Astro.props)]} data-view-title={title}>
|
|
20
21
|
{icon && <span class="wd-view-icon-src" hidden><AppIcon icon={icon} class="wd-view-icon" /></span>}
|
|
21
22
|
<slot />
|
|
22
23
|
</div>
|
|
@@ -9,4 +9,4 @@ interface Props {
|
|
|
9
9
|
}
|
|
10
10
|
const { title, _titleId } = Astro.props as Props;
|
|
11
11
|
---
|
|
12
|
-
<Callout type="warning" title={title} _titleId={_titleId}><slot /></Callout>
|
|
12
|
+
<Callout type="warning" title={title} _titleId={_titleId} class={Astro.props.class} className={Astro.props.className}><slot /></Callout>
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
// Every built-in component takes extra classes for its root element - as
|
|
2
|
+
// `className` (how MDX, and Mintlify content, passes it) or `class` (Astro's
|
|
3
|
+
// own spelling). Components add extraClasses(Astro.props) to their root
|
|
4
|
+
// element's class:list; with neither set it adds nothing, so the rendered
|
|
5
|
+
// markup is unchanged.
|
|
6
|
+
export function extraClasses(props: Record<string, unknown>): string[] {
|
|
7
|
+
return [props.class, props.className].filter((c): c is string => typeof c === 'string' && c.trim() !== '');
|
|
8
|
+
}
|
package/src/components/index.ts
CHANGED
|
@@ -50,6 +50,8 @@ export { default as Check } from './Check.astro';
|
|
|
50
50
|
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
|
+
export { default as Column } from './Column.astro';
|
|
54
|
+
export { default as CodeBlock } from './CodeBlock.astro';
|
|
53
55
|
export { default as Tooltip } from './Tooltip.astro';
|
|
54
56
|
export { default as Update } from './Update.astro';
|
|
55
57
|
export { default as Tile } from './Tile.astro';
|
package/src/content.config.ts
CHANGED
|
@@ -5,6 +5,7 @@ import fs from 'node:fs';
|
|
|
5
5
|
import path from 'node:path';
|
|
6
6
|
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
7
7
|
import { pageFrontmatterSchema, findAllPages } from './lib/config';
|
|
8
|
+
import { titleFromPath } from './lib/pages.js';
|
|
8
9
|
import { writedocsTempDir } from './lib/writedocs-temp-dir.js';
|
|
9
10
|
|
|
10
11
|
const contentDir = process.env.WRITEDOCS_CONTENT_DIR || process.cwd();
|
|
@@ -51,11 +52,31 @@ function skipIfMissing(inner: Loader): Loader {
|
|
|
51
52
|
name: inner.name,
|
|
52
53
|
load: async (context) => {
|
|
53
54
|
if (!fs.existsSync(generatedDocsBase)) return;
|
|
54
|
-
return inner.load(context);
|
|
55
|
+
return inner.load(withTitleFallback(context));
|
|
55
56
|
},
|
|
56
57
|
};
|
|
57
58
|
}
|
|
58
59
|
|
|
60
|
+
// A page with no `title` in its frontmatter gets one from its file name,
|
|
61
|
+
// before the schema (which requires `title`) sees it - Mintlify's rule, so
|
|
62
|
+
// a migrated page that relied on it builds with the same title. See
|
|
63
|
+
// titleFromPath() in lib/pages.js; `writedocs validate` applies the same
|
|
64
|
+
// default (lib/content-check.js).
|
|
65
|
+
type LoaderContext = Parameters<Loader['load']>[0];
|
|
66
|
+
function withTitleFallback(context: LoaderContext): LoaderContext {
|
|
67
|
+
return {
|
|
68
|
+
...context,
|
|
69
|
+
parseData: (props) =>
|
|
70
|
+
context.parseData({
|
|
71
|
+
...props,
|
|
72
|
+
data:
|
|
73
|
+
typeof props.data?.title === 'string'
|
|
74
|
+
? props.data
|
|
75
|
+
: { ...props.data, title: titleFromPath(props.filePath ?? props.id) },
|
|
76
|
+
}),
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
|
|
59
80
|
const generatedDocs = defineCollection({
|
|
60
81
|
loader: skipIfMissing(glob({ pattern: '**/*.{md,mdx}', base: generatedDocsBase })),
|
|
61
82
|
schema: docsSchema,
|
|
@@ -135,7 +156,7 @@ function livePages(dir: string, base: URL): Loader {
|
|
|
135
156
|
for (const id of store.keys()) store.delete(id);
|
|
136
157
|
return;
|
|
137
158
|
}
|
|
138
|
-
await glob({ pattern: files, base }).load({ ...context, watcher: undefined });
|
|
159
|
+
await glob({ pattern: files, base }).load({ ...withTitleFallback(context), watcher: undefined });
|
|
139
160
|
}
|
|
140
161
|
|
|
141
162
|
await resync();
|
package/src/lib/content-check.js
CHANGED
|
@@ -8,19 +8,23 @@
|
|
|
8
8
|
// pageFrontmatterSchema the build uses) or isn't valid YAML
|
|
9
9
|
// - an .mdx file doesn't parse as MDX
|
|
10
10
|
// - writedocs.json's navigation lists a page that doesn't exist
|
|
11
|
+
// - a redirect is a pattern (`/old/:slug`) rather than one exact path
|
|
11
12
|
// Warnings - the build succeeds, but not as written:
|
|
12
13
|
// - an unknown component (the build shows only its content - see
|
|
13
14
|
// lib/mdx-unknown-components.js)
|
|
14
15
|
// - an icon name no installed icon set has (the build leaves it out -
|
|
15
16
|
// see AppIcon.astro), in a component's `icon`, a page's `icon`, or
|
|
16
17
|
// writedocs.json
|
|
18
|
+
// - a built-in component inside a page component that runs as React,
|
|
19
|
+
// where it renders simplified (lib/mdx-inline-react.js)
|
|
17
20
|
import fs from 'node:fs';
|
|
18
21
|
import path from 'node:path';
|
|
19
22
|
import matter from 'gray-matter';
|
|
20
23
|
import { visit } from 'unist-util-visit';
|
|
21
|
-
import { findAllPages, fileIdForPath } from './pages.js';
|
|
24
|
+
import { findAllPages, fileIdForPath, titleFromPath } from './pages.js';
|
|
22
25
|
import { iconExists } from './icons.js';
|
|
23
26
|
import { findUnknownComponents } from './mdx-unknown-components.js';
|
|
27
|
+
import { findInteractiveComponents, simplifiedBuiltinMessage } from './mdx-inline-react.js';
|
|
24
28
|
import { pageFrontmatterSchema, createJsonLocator } from './config-schema.js';
|
|
25
29
|
|
|
26
30
|
/** An issue: { file, line?, message, suggestion? } - `file` relative to the
|
|
@@ -75,8 +79,10 @@ async function checkPage(contentDir, rel, errors, warnings) {
|
|
|
75
79
|
// returns from its cache (findAllPages() already parsed every page once).
|
|
76
80
|
const frontmatterText = raw.match(/^---[^\n]*\n([\s\S]*?)\n---/)?.[1] ?? '';
|
|
77
81
|
|
|
78
|
-
// Frontmatter against the same schema the build uses
|
|
79
|
-
|
|
82
|
+
// Frontmatter against the same schema the build uses - with the same
|
|
83
|
+
// file-name default for a missing title (content.config.ts).
|
|
84
|
+
const data = typeof parsed.data?.title === 'string' ? parsed.data : { ...parsed.data, title: titleFromPath(rel) };
|
|
85
|
+
const result = pageFrontmatterSchema.safeParse(data);
|
|
80
86
|
if (!result.success) {
|
|
81
87
|
for (const i of result.error.issues) {
|
|
82
88
|
const key = String(i.path[0] ?? '');
|
|
@@ -122,6 +128,14 @@ async function checkPage(contentDir, rel, errors, warnings) {
|
|
|
122
128
|
)
|
|
123
129
|
);
|
|
124
130
|
}
|
|
131
|
+
// Built-ins inside a page component that runs as React - they render
|
|
132
|
+
// simplified there (lib/mdx-inline-react.js, which the build warns from
|
|
133
|
+
// with the same words).
|
|
134
|
+
for (const use of findInteractiveComponents(tree, parsed.content).simplified) {
|
|
135
|
+
const line = parsed.content.slice(0, use.offset).split('\n').length + lineOffset;
|
|
136
|
+
const { message, suggestion } = simplifiedBuiltinMessage(use);
|
|
137
|
+
warnings.push(issue(rel, line, message, suggestion));
|
|
138
|
+
}
|
|
125
139
|
visit(tree, ['mdxJsxFlowElement', 'mdxJsxTextElement'], (node) => {
|
|
126
140
|
const attr = node.attributes?.find((a) => a.type === 'mdxJsxAttribute' && a.name === 'icon');
|
|
127
141
|
if (!attr || typeof attr.value !== 'string' || iconExists(attr.value)) return;
|
|
@@ -195,18 +209,34 @@ export async function checkContent(contentDir, configText) {
|
|
|
195
209
|
const ids = new Set(pages.map(fileIdForPath));
|
|
196
210
|
for (const ref of navigationReferences(config.navigation)) {
|
|
197
211
|
if (ids.has(ref.id)) continue;
|
|
198
|
-
const
|
|
212
|
+
const folderIndex = ref.id.endsWith('/index') && ids.has(ref.id.replace(/\/index$/, ''));
|
|
199
213
|
errors.push(
|
|
200
214
|
issue(
|
|
201
215
|
'writedocs.json',
|
|
202
216
|
locate(ref.path)?.line,
|
|
203
217
|
`The navigation lists page "${ref.id}", but there's no page with that path.`,
|
|
204
|
-
|
|
205
|
-
?
|
|
218
|
+
folderIndex
|
|
219
|
+
? `A folder's index page is listed by the folder's path - write "${ref.id.replace(/\/index$/, '')}".`
|
|
206
220
|
: `Create ${ref.id}.mdx, or fix the path (it's relative to the folder writedocs.json is in, without the extension).`
|
|
207
221
|
)
|
|
208
222
|
);
|
|
209
223
|
}
|
|
224
|
+
// A redirect is one exact path - a Mintlify-style pattern (`/old/:slug`,
|
|
225
|
+
// `/old/*`) becomes a literal page path, and the build fails on it.
|
|
226
|
+
(Array.isArray(config.redirects) ? config.redirects : []).forEach((r, i) => {
|
|
227
|
+
for (const key of ['source', 'destination']) {
|
|
228
|
+
const value = r?.[key];
|
|
229
|
+
if (typeof value !== 'string' || !/(^|\/):[A-Za-z_]|\*/.test(value)) continue;
|
|
230
|
+
errors.push(
|
|
231
|
+
issue(
|
|
232
|
+
'writedocs.json',
|
|
233
|
+
locate(['redirects', i, key])?.line,
|
|
234
|
+
`Redirect ${key} "${value}" is a pattern - writedocs redirects match one exact path.`,
|
|
235
|
+
'Replace it with one redirect per exact path.'
|
|
236
|
+
)
|
|
237
|
+
);
|
|
238
|
+
}
|
|
239
|
+
});
|
|
210
240
|
for (const { icon, path: jsonPath } of configIcons(config)) {
|
|
211
241
|
if (iconExists(icon)) continue;
|
|
212
242
|
const { message, suggestion } = unknownIconMessage(icon);
|
|
@@ -63,6 +63,18 @@ export function remarkAutoHydrateSnippets() {
|
|
|
63
63
|
(attr) => attr.type === 'mdxJsxAttribute' && typeof attr.name === 'string' && attr.name.startsWith(CLIENT_ATTR_PREFIX)
|
|
64
64
|
);
|
|
65
65
|
if (hasClientDirective) return;
|
|
66
|
+
// A component handed over as a prop (`Renderer={MyBlock}`, a pattern
|
|
67
|
+
// Mintlify content uses) can't be hydrated: Astro sends an island's
|
|
68
|
+
// props to the browser as data, and a function doesn't survive that -
|
|
69
|
+
// it arrives as null and React throws. Such a usage is rendered on the
|
|
70
|
+
// server only; everything else about it still works.
|
|
71
|
+
const passesComponent = attributes.some(
|
|
72
|
+
(attr) =>
|
|
73
|
+
attr.type === 'mdxJsxAttribute' &&
|
|
74
|
+
attr.value?.type === 'mdxJsxAttributeValueExpression' &&
|
|
75
|
+
/^\s*[A-Z][A-Za-z0-9]*\s*$/.test(attr.value.value ?? '')
|
|
76
|
+
);
|
|
77
|
+
if (passesComponent) return;
|
|
66
78
|
attributes.push({ type: 'mdxJsxAttribute', name: 'client:load', value: null });
|
|
67
79
|
node.attributes = attributes;
|
|
68
80
|
});
|
|
@@ -38,6 +38,8 @@ export const BUILTIN_COMPONENT_NAMES = [
|
|
|
38
38
|
'ParamField',
|
|
39
39
|
'ResponseField',
|
|
40
40
|
'Columns',
|
|
41
|
+
'Column',
|
|
42
|
+
'CodeBlock',
|
|
41
43
|
'Tooltip',
|
|
42
44
|
'Update',
|
|
43
45
|
'Tile',
|
|
@@ -95,6 +97,19 @@ export function remarkInjectBuiltinComponents() {
|
|
|
95
97
|
}
|
|
96
98
|
});
|
|
97
99
|
|
|
100
|
+
// Built-ins used inside the page's own code, too - JSX in an
|
|
101
|
+
// `export const Widget = () => <Icon icon="x" />` or a `{...}`
|
|
102
|
+
// expression isn't part of the content tree above, and the components
|
|
103
|
+
// map [...slug].astro passes only reaches content, so without an import
|
|
104
|
+
// `Icon` is simply undefined there. Mintlify makes its components
|
|
105
|
+
// available in both places.
|
|
106
|
+
visit(tree, ['mdxjsEsm', 'mdxFlowExpression', 'mdxTextExpression'], (node) => {
|
|
107
|
+
for (const m of String(node.value ?? '').matchAll(/<([A-Z][A-Za-z0-9]*)[\s./>]/g)) {
|
|
108
|
+
const name = m[1];
|
|
109
|
+
if (BUILTIN_COMPONENT_NAMES.includes(name) && !locallyBound.has(name)) needed.add(name);
|
|
110
|
+
}
|
|
111
|
+
});
|
|
112
|
+
|
|
98
113
|
if (needed.size === 0) return;
|
|
99
114
|
|
|
100
115
|
const names = [...needed].sort();
|