@umami/shiso 1.11.0 → 1.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/shiso.mjs +20 -1
- package/dist/chunks/App.js +412 -51
- package/dist/chunks/abnfDiagram-VCTEODGH.js +109 -0
- package/dist/chunks/arc.js +130 -0
- package/dist/chunks/architecture-7GRP2DOG.js +4 -0
- package/dist/chunks/architectureDiagram-5GKGNRK7.js +7466 -0
- package/dist/chunks/array.js +8 -0
- package/dist/chunks/blockDiagram-I7D4REHJ.js +2546 -0
- package/dist/chunks/c4Diagram-7LVT6UL2.js +3654 -0
- package/dist/chunks/channel.js +9 -0
- package/dist/chunks/chunk-2Q5K7J3B.js +21 -0
- package/dist/chunks/chunk-4HAMMTFA.js +6106 -0
- package/dist/chunks/chunk-5VM5RSS4.js +21 -0
- package/dist/chunks/chunk-75Z2AOVW.js +2185 -0
- package/dist/chunks/chunk-DU6HZSFF.js +8729 -0
- package/dist/chunks/chunk-F27PBJKO.js +95 -0
- package/dist/chunks/chunk-FOHPRMQF.js +27162 -0
- package/dist/chunks/chunk-GMAD6QVW.js +2369 -0
- package/dist/chunks/chunk-GVQU2GXP.js +58 -0
- package/dist/chunks/chunk-IMKFNOWR.js +2593 -0
- package/dist/chunks/chunk-JWPE2WC7.js +12 -0
- package/dist/chunks/chunk-L3NEJ4N5.js +420 -0
- package/dist/chunks/chunk-OSK3NFVY.js +1029 -0
- package/dist/chunks/chunk-P2QGCYS3.js +95 -0
- package/dist/chunks/chunk-POPQ4Y6H.js +33 -0
- package/dist/chunks/chunk-PWAF6VOD.js +467 -0
- package/dist/chunks/chunk-SHT3W25Y.js +4813 -0
- package/dist/chunks/chunk-SVP7TREG.js +686 -0
- package/dist/chunks/chunk-TICWLB2K.js +3800 -0
- package/dist/chunks/chunk-XXDRQBXY.js +12 -0
- package/dist/chunks/chunk-Y2CYZVJY.js +15 -0
- package/dist/chunks/classDiagram-ZZMXUADV.js +33 -0
- package/dist/chunks/classDiagram-v2-VYDZK3BY.js +33 -0
- package/dist/chunks/cose-bilkent-JH36ORCC.js +4268 -0
- package/dist/chunks/cynefin-OW5HDTMX.js +4 -0
- package/dist/chunks/cynefinDiagram-5FMLGOSQ.js +482 -0
- package/dist/chunks/cytoscape.esm.js +28282 -0
- package/dist/chunks/dagre-GXQ25YYZ.js +583 -0
- package/dist/chunks/dagre.js +4235 -0
- package/dist/chunks/defaultLocale.js +246 -0
- package/dist/chunks/diagram-S7CK7UJ4.js +402 -0
- package/dist/chunks/diagram-UQ7AKVKN.js +291 -0
- package/dist/chunks/diagram-VSXAHHWV.js +553 -0
- package/dist/chunks/diagram-VX7I27RA.js +798 -0
- package/dist/chunks/diagram-Z3DM3KII.js +186 -0
- package/dist/chunks/dist.js +92 -0
- package/dist/chunks/docs.js +989 -93
- package/dist/chunks/ebnfDiagram-PWID7BFC.js +123 -0
- package/dist/chunks/erDiagram-RLTQ6QDP.js +2295 -0
- package/dist/chunks/eventmodeling-NTZA5JFV.js +4 -0
- package/dist/chunks/flowDiagram-HODETNUW.js +17 -0
- package/dist/chunks/ganttDiagram-EL5Y4UJY.js +3928 -0
- package/dist/chunks/gitGraph-4MIJSDKK.js +4 -0
- package/dist/chunks/gitGraphDiagram-WWUBYQGX.js +1153 -0
- package/dist/chunks/graphlib.js +4194 -0
- package/dist/chunks/info-A6RAGUB7.js +4 -0
- package/dist/chunks/infoDiagram-27XIBGKW.js +25 -0
- package/dist/chunks/init.js +14 -0
- package/dist/chunks/ishikawaDiagram-5VMMS53U.js +971 -0
- package/dist/chunks/journeyDiagram-3NMN7TZE.js +1226 -0
- package/dist/chunks/kanban-definition-UXKFOSKX.js +1241 -0
- package/dist/chunks/katex.js +26463 -0
- package/dist/chunks/line.js +48 -0
- package/dist/chunks/linear.js +391 -0
- package/dist/chunks/mermaid-parser.core.js +754 -0
- package/dist/chunks/mermaid.core.js +4898 -0
- package/dist/chunks/mindmap-definition-YA3MSWOX.js +1309 -0
- package/dist/chunks/ordinal.js +84 -0
- package/dist/chunks/packet-AYTQ26CC.js +4 -0
- package/dist/chunks/path.js +106 -0
- package/dist/chunks/pegDiagram-XKGWAZYB.js +115 -0
- package/dist/chunks/pie-WAS4IAKB.js +4 -0
- package/dist/chunks/pieDiagram-E7YTZNPT.js +297 -0
- package/dist/chunks/quadrantDiagram-AXDQQJYC.js +2234 -0
- package/dist/chunks/radar-RG4KPBEZ.js +4 -0
- package/dist/chunks/railroad-74A4TZTK.js +4 -0
- package/dist/chunks/railroad-abnf-HS5TGJTU.js +4 -0
- package/dist/chunks/railroad-ebnf-LZEXJU2U.js +4 -0
- package/dist/chunks/railroad-peg-WCYAUIDC.js +4 -0
- package/dist/chunks/railroadDiagram-O6MQD6OU.js +89 -0
- package/dist/chunks/requirementDiagram-BXWQKSXE.js +2464 -0
- package/dist/chunks/rough.esm.js +1396 -0
- package/dist/chunks/sankeyDiagram-P5KCCOFB.js +1308 -0
- package/dist/chunks/sequenceDiagram-WJ2MYXX4.js +5445 -0
- package/dist/chunks/sizeCapture-INFHLROL.js +56 -0
- package/dist/chunks/src.js +2653 -0
- package/dist/chunks/stateDiagram-D77RDMKH.js +368 -0
- package/dist/chunks/stateDiagram-v2-MP3YSRHH.js +33 -0
- package/dist/chunks/swimlanes-42K2YHIH.js +6925 -0
- package/dist/chunks/swimlanesDiagram-VR7AAH4N.js +32 -0
- package/dist/chunks/timeline-definition-24CTP7MA.js +1507 -0
- package/dist/chunks/treeView-Q6P3EWNA.js +4 -0
- package/dist/chunks/treemap-WGGIJYW6.js +4 -0
- package/dist/chunks/vennDiagram-4TSXK5OY.js +2803 -0
- package/dist/chunks/wardley-WFR3VGLG.js +4 -0
- package/dist/chunks/wardleyDiagram-VM6X3IG4.js +877 -0
- package/dist/chunks/xychartDiagram-S5SC5T6Z.js +2698 -0
- package/dist/components.js +2 -2
- package/dist/entry-client.js +2 -2
- package/dist/entry-server.js +31 -3
- package/docs.schema.json +90 -0
- package/mdx.config.ts +13 -97
- package/package.json +6 -4
- package/scripts/build-runtime.mjs +1 -0
- package/scripts/check-content.mjs +358 -0
- package/scripts/expand-navigation-globs.mjs +277 -0
- package/scripts/expand-openapi-navigation.mjs +94 -0
- package/scripts/generate-openapi.mjs +117 -0
- package/scripts/generate-search-index.mjs +31 -1
- package/scripts/lib/openapi.mjs +653 -0
- package/scripts/load-docs-config.mjs +52 -6
- package/scripts/load-shiso-config.mjs +45 -3
- package/scripts/prerender.mjs +83 -3
- package/scripts/vite-docs-config.mjs +27 -6
- package/src/App.tsx +0 -1
- package/src/components/CodeBlock.tsx +108 -8
- package/src/components/DocContent.tsx +18 -0
- package/src/components/Docs.tsx +45 -17
- package/src/components/OpenApiOperation.tsx +205 -0
- package/src/components/SideNav.tsx +13 -2
- package/src/components/docs/Changelog.tsx +163 -0
- package/src/components/docs/CodeGroup.tsx +6 -2
- package/src/components/docs/Mermaid.tsx +164 -0
- package/src/components/docs/Panel.tsx +23 -0
- package/src/components/docs/ParamField.tsx +17 -5
- package/src/components/docs/PropertiesTable.tsx +2 -2
- package/src/components/docs/ResponseField.tsx +17 -5
- package/src/components/docs/Tiles.tsx +59 -0
- package/src/components/docs/Tree.tsx +359 -0
- package/src/components/docs/Update.tsx +72 -0
- package/src/components/docs/index.ts +7 -0
- package/src/components/docs/panel-context.tsx +25 -0
- package/src/components/docs/styles.ts +49 -0
- package/src/components/icons/index.ts +4 -0
- package/src/entry-server.tsx +50 -0
- package/src/lib/code-blocks.ts +18 -0
- package/src/lib/code-meta.ts +87 -0
- package/src/lib/docs-config.ts +4 -1
- package/src/lib/openapi.generated.ts +4 -0
- package/src/lib/openapi.ts +63 -0
- package/src/lib/rehype-shiki.ts +213 -0
- package/src/lib/site-model.ts +2 -0
- package/src/lib/types.ts +116 -2
- package/src/styles/global.css +63 -73
- package/types/components.d.ts +79 -0
- package/types/config.d.ts +11 -4
- package/vite.config.ts +49 -3
- package/CHANGELOG.md +0 -171
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
import { CodeBlock } from '@/components/CodeBlock';
|
|
2
|
+
import { Badge } from '@/components/docs/Badge';
|
|
3
|
+
import { CodeGroup } from '@/components/docs/CodeGroup';
|
|
4
|
+
import { Expandable } from '@/components/docs/Expandable';
|
|
5
|
+
import { ParamField } from '@/components/docs/ParamField';
|
|
6
|
+
import { ResponseField } from '@/components/docs/ResponseField';
|
|
7
|
+
import { styles } from '@/components/docs/styles';
|
|
8
|
+
import { operationSections, statusColor } from '@/lib/openapi';
|
|
9
|
+
import type { NormalizedOperation, SchemaNode } from '@/lib/types';
|
|
10
|
+
|
|
11
|
+
type ParamLocation = 'path' | 'query' | 'header' | 'cookie';
|
|
12
|
+
|
|
13
|
+
const PARAM_LOCATIONS: ParamLocation[] = ['path', 'query', 'header', 'cookie'];
|
|
14
|
+
|
|
15
|
+
function FieldChildren({ node }: { node: SchemaNode }) {
|
|
16
|
+
return (
|
|
17
|
+
<>
|
|
18
|
+
{node.description}
|
|
19
|
+
{node.enum && node.enum.length > 0 && (
|
|
20
|
+
<div className="mt-1">
|
|
21
|
+
Options:{' '}
|
|
22
|
+
{node.enum.map((value, index) => (
|
|
23
|
+
<span key={value}>
|
|
24
|
+
{index > 0 && ', '}
|
|
25
|
+
<code>{value}</code>
|
|
26
|
+
</span>
|
|
27
|
+
))}
|
|
28
|
+
</div>
|
|
29
|
+
)}
|
|
30
|
+
{node.children && node.children.length > 0 && (
|
|
31
|
+
<Expandable title="properties">
|
|
32
|
+
{node.children.map(child => (
|
|
33
|
+
<SchemaField key={child.name || child.type} node={child} />
|
|
34
|
+
))}
|
|
35
|
+
</Expandable>
|
|
36
|
+
)}
|
|
37
|
+
</>
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function SchemaField({ node }: { node: SchemaNode }) {
|
|
42
|
+
const hasDetails = Boolean(node.description || node.enum?.length || node.children?.length);
|
|
43
|
+
|
|
44
|
+
return (
|
|
45
|
+
<ResponseField
|
|
46
|
+
name={node.name || node.type}
|
|
47
|
+
type={node.name ? node.type : undefined}
|
|
48
|
+
required={node.required}
|
|
49
|
+
deprecated={node.deprecated}
|
|
50
|
+
default={node.default}
|
|
51
|
+
>
|
|
52
|
+
{hasDetails ? <FieldChildren node={node} /> : null}
|
|
53
|
+
</ResponseField>
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/** Renders a schema tree: a root object's properties, or the node itself. */
|
|
58
|
+
function SchemaFields({ node }: { node: SchemaNode }) {
|
|
59
|
+
if (!node.name && node.children?.length) {
|
|
60
|
+
return (
|
|
61
|
+
<>
|
|
62
|
+
{node.children.map(child => (
|
|
63
|
+
<SchemaField key={child.name || child.type} node={child} />
|
|
64
|
+
))}
|
|
65
|
+
</>
|
|
66
|
+
);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
return <SchemaField node={node} />;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function HighlightedCode({
|
|
73
|
+
language,
|
|
74
|
+
title,
|
|
75
|
+
html,
|
|
76
|
+
source,
|
|
77
|
+
lineCount,
|
|
78
|
+
}: {
|
|
79
|
+
language: string;
|
|
80
|
+
title?: string;
|
|
81
|
+
html?: string;
|
|
82
|
+
source: string;
|
|
83
|
+
lineCount?: number;
|
|
84
|
+
}) {
|
|
85
|
+
return (
|
|
86
|
+
<CodeBlock
|
|
87
|
+
data-language={language}
|
|
88
|
+
data-title={title}
|
|
89
|
+
data-line-count={lineCount ? String(lineCount) : undefined}
|
|
90
|
+
>
|
|
91
|
+
{html ? (
|
|
92
|
+
// Highlighted at build time by the openapi generator; same markup as
|
|
93
|
+
// the rehype-shiki pipeline produces for fenced code blocks.
|
|
94
|
+
<code
|
|
95
|
+
className={`language-${language}`}
|
|
96
|
+
// biome-ignore lint/security/noDangerouslySetInnerHtml: build-time generated markup from the project's own OpenAPI spec.
|
|
97
|
+
dangerouslySetInnerHTML={{ __html: html }}
|
|
98
|
+
/>
|
|
99
|
+
) : (
|
|
100
|
+
<code className={`language-${language}`}>{source}</code>
|
|
101
|
+
)}
|
|
102
|
+
</CodeBlock>
|
|
103
|
+
);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export interface OpenApiOperationProps {
|
|
107
|
+
operation: NormalizedOperation;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/** The generated reference for one API operation, rendered under the page body. */
|
|
111
|
+
export function OpenApiOperation({ operation }: OpenApiOperationProps) {
|
|
112
|
+
const sections = new Map(operationSections(operation).map(entry => [entry.name, entry.id]));
|
|
113
|
+
const showParameters = sections.has('Parameters');
|
|
114
|
+
|
|
115
|
+
return (
|
|
116
|
+
<div className="docs-markdown">
|
|
117
|
+
{showParameters && (
|
|
118
|
+
<section>
|
|
119
|
+
<h2 id={sections.get('Parameters')}>Parameters</h2>
|
|
120
|
+
<div className={styles.fieldGroup}>
|
|
121
|
+
{operation.security.length > 0 && (
|
|
122
|
+
<ParamField header="Authorization" type="string" required>
|
|
123
|
+
Authentication credentials, e.g. <code>Bearer <token></code>.
|
|
124
|
+
</ParamField>
|
|
125
|
+
)}
|
|
126
|
+
{PARAM_LOCATIONS.map(location =>
|
|
127
|
+
operation.parameters[location].map(parameter => (
|
|
128
|
+
<ParamField
|
|
129
|
+
key={`${location}-${parameter.name}`}
|
|
130
|
+
{...{ [location]: parameter.name }}
|
|
131
|
+
type={parameter.type}
|
|
132
|
+
required={parameter.required}
|
|
133
|
+
>
|
|
134
|
+
<FieldChildren node={{ ...parameter, name: undefined, description: undefined }} />
|
|
135
|
+
{parameter.description}
|
|
136
|
+
</ParamField>
|
|
137
|
+
)),
|
|
138
|
+
)}
|
|
139
|
+
</div>
|
|
140
|
+
</section>
|
|
141
|
+
)}
|
|
142
|
+
{operation.requestBody && (
|
|
143
|
+
<section>
|
|
144
|
+
<h2 id={sections.get('Request body')}>Request body</h2>
|
|
145
|
+
<SchemaFields node={operation.requestBody.schema} />
|
|
146
|
+
{operation.requestBody.example && (
|
|
147
|
+
<HighlightedCode
|
|
148
|
+
language="json"
|
|
149
|
+
title="Example request"
|
|
150
|
+
html={operation.requestBody.exampleHtml}
|
|
151
|
+
source={operation.requestBody.example}
|
|
152
|
+
/>
|
|
153
|
+
)}
|
|
154
|
+
</section>
|
|
155
|
+
)}
|
|
156
|
+
{operation.responses.length > 0 && (
|
|
157
|
+
<section>
|
|
158
|
+
<h2 id={sections.get('Responses')}>Responses</h2>
|
|
159
|
+
{operation.responses.map(response => (
|
|
160
|
+
<div key={response.status} className="mt-6 first:mt-0">
|
|
161
|
+
<div className="flex items-center gap-2">
|
|
162
|
+
<Badge color={statusColor(response.status)} size="sm">
|
|
163
|
+
{response.status}
|
|
164
|
+
</Badge>
|
|
165
|
+
{response.description && (
|
|
166
|
+
<span className="text-muted-foreground text-sm">{response.description}</span>
|
|
167
|
+
)}
|
|
168
|
+
</div>
|
|
169
|
+
{response.schema && (
|
|
170
|
+
<div className={styles.fieldGroup}>
|
|
171
|
+
<SchemaFields node={response.schema} />
|
|
172
|
+
</div>
|
|
173
|
+
)}
|
|
174
|
+
{response.example && (
|
|
175
|
+
<HighlightedCode
|
|
176
|
+
language="json"
|
|
177
|
+
title={`${response.status} example`}
|
|
178
|
+
html={response.exampleHtml}
|
|
179
|
+
source={response.example}
|
|
180
|
+
/>
|
|
181
|
+
)}
|
|
182
|
+
</div>
|
|
183
|
+
))}
|
|
184
|
+
</section>
|
|
185
|
+
)}
|
|
186
|
+
{operation.samples.length > 0 && (
|
|
187
|
+
<section>
|
|
188
|
+
<h2 id={sections.get('Code samples')}>Code samples</h2>
|
|
189
|
+
<CodeGroup>
|
|
190
|
+
{operation.samples.map(sample => (
|
|
191
|
+
<HighlightedCode
|
|
192
|
+
key={sample.language}
|
|
193
|
+
language={sample.language}
|
|
194
|
+
title={sample.label}
|
|
195
|
+
html={sample.html}
|
|
196
|
+
source={sample.source}
|
|
197
|
+
lineCount={sample.lineCount}
|
|
198
|
+
/>
|
|
199
|
+
))}
|
|
200
|
+
</CodeGroup>
|
|
201
|
+
</section>
|
|
202
|
+
)}
|
|
203
|
+
</div>
|
|
204
|
+
);
|
|
205
|
+
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { type ReactNode, useEffect, useState } from 'react';
|
|
2
2
|
import { Link, useLocation, useNavigate } from 'react-router';
|
|
3
|
+
import { Badge as MethodBadge } from '@/components/docs/Badge';
|
|
3
4
|
import { resolveIcon } from '@/components/docs/utils';
|
|
4
5
|
import { ChevronRight, ExternalLink } from '@/components/icons';
|
|
5
6
|
import { Badge } from '@/components/ui/badge';
|
|
@@ -7,6 +8,7 @@ import { Button } from '@/components/ui/button';
|
|
|
7
8
|
import { Collapsible, CollapsibleContent, CollapsibleTrigger } from '@/components/ui/collapsible';
|
|
8
9
|
import { ScrollArea } from '@/components/ui/scroll-area';
|
|
9
10
|
import { flattenNav, isNodeHidden } from '@/lib/docs-config';
|
|
11
|
+
import { methodColor } from '@/lib/openapi';
|
|
10
12
|
import type { DocsTab, NavGroupNode, NavNode } from '@/lib/types';
|
|
11
13
|
import { cn } from '@/lib/utils';
|
|
12
14
|
|
|
@@ -256,7 +258,7 @@ function NavNodes({
|
|
|
256
258
|
}
|
|
257
259
|
|
|
258
260
|
if (node.kind === 'page') {
|
|
259
|
-
const { url, label, icon, tag } = node.page;
|
|
261
|
+
const { url, label, icon, tag, method } = node.page;
|
|
260
262
|
const isSelected = url === pathname;
|
|
261
263
|
|
|
262
264
|
rendered.push(
|
|
@@ -273,8 +275,17 @@ function NavNodes({
|
|
|
273
275
|
)}
|
|
274
276
|
>
|
|
275
277
|
{resolveIcon(icon)}
|
|
278
|
+
{method ? (
|
|
279
|
+
<MethodBadge
|
|
280
|
+
color={methodColor(method)}
|
|
281
|
+
size="xs"
|
|
282
|
+
className="w-10 shrink-0 justify-center whitespace-nowrap font-mono"
|
|
283
|
+
>
|
|
284
|
+
{method === 'DELETE' ? 'DEL' : method}
|
|
285
|
+
</MethodBadge>
|
|
286
|
+
) : null}
|
|
276
287
|
{label}
|
|
277
|
-
{tag ? (
|
|
288
|
+
{!method && tag ? (
|
|
278
289
|
<Badge
|
|
279
290
|
variant="secondary"
|
|
280
291
|
className="ml-auto h-auto rounded-sm px-[0.35rem] py-[0.05rem] text-[0.7rem] text-muted-foreground uppercase"
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
import { Children, Fragment, isValidElement, type ReactElement, useMemo } from 'react';
|
|
2
|
+
import { useSearchParams } from 'react-router';
|
|
3
|
+
import { Badge } from './Badge';
|
|
4
|
+
import { styles } from './styles';
|
|
5
|
+
import { Update } from './Update';
|
|
6
|
+
|
|
7
|
+
export interface ChangelogProps {
|
|
8
|
+
children?: React.ReactNode;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
interface UpdateChildInfo {
|
|
12
|
+
key: string;
|
|
13
|
+
tags: string[];
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/** Flattens fragments so <Update> entries are found however MDX nests them. */
|
|
17
|
+
function flatElements(children: React.ReactNode): ReactElement<{ tags?: unknown }>[] {
|
|
18
|
+
const flat: ReactElement<{ tags?: unknown }>[] = [];
|
|
19
|
+
|
|
20
|
+
Children.forEach(children, child => {
|
|
21
|
+
if (!isValidElement<{ tags?: unknown }>(child)) {
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
if (child.type === Fragment) {
|
|
25
|
+
flat.push(...flatElements(child.props.children));
|
|
26
|
+
} else {
|
|
27
|
+
flat.push(child);
|
|
28
|
+
}
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
return flat;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/** Reads `tags` off <Update> children without rendering them. */
|
|
35
|
+
function updateInfo(children: React.ReactNode): Map<string, UpdateChildInfo> {
|
|
36
|
+
const info = new Map<string, UpdateChildInfo>();
|
|
37
|
+
|
|
38
|
+
flatElements(children).forEach((child, index) => {
|
|
39
|
+
if (child.type !== Update) {
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const key = String(child.key ?? `update-${index}`);
|
|
44
|
+
const raw = child.props.tags;
|
|
45
|
+
const tags = Array.isArray(raw)
|
|
46
|
+
? [...new Set(raw.filter((tag): tag is string => typeof tag === 'string' && tag.trim()))].map(
|
|
47
|
+
tag => tag.trim(),
|
|
48
|
+
)
|
|
49
|
+
: [];
|
|
50
|
+
info.set(key, { key, tags });
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
return info;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Groups <Update> entries with Mintlify-style tag filtering.
|
|
58
|
+
* Selected tags sync to `?tags=a,b` so filtered views are shareable.
|
|
59
|
+
* Pages without any tagged updates render as a plain list.
|
|
60
|
+
*/
|
|
61
|
+
export function Changelog({ children }: ChangelogProps) {
|
|
62
|
+
const [searchParams, setSearchParams] = useSearchParams();
|
|
63
|
+
|
|
64
|
+
const allTags = useMemo(() => {
|
|
65
|
+
const seen = new Map<string, string>();
|
|
66
|
+
for (const { tags } of updateInfo(children).values()) {
|
|
67
|
+
for (const tag of tags) {
|
|
68
|
+
const key = tag.toLowerCase();
|
|
69
|
+
if (!seen.has(key)) {
|
|
70
|
+
seen.set(key, tag);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
return [...seen.values()];
|
|
75
|
+
}, [children]);
|
|
76
|
+
|
|
77
|
+
const selected = useMemo(() => {
|
|
78
|
+
const param = searchParams.get('tags');
|
|
79
|
+
if (!param) {
|
|
80
|
+
return [];
|
|
81
|
+
}
|
|
82
|
+
const wanted = new Set(
|
|
83
|
+
param
|
|
84
|
+
.split(',')
|
|
85
|
+
.map(tag => tag.trim().toLowerCase())
|
|
86
|
+
.filter(Boolean),
|
|
87
|
+
);
|
|
88
|
+
return allTags.filter(tag => wanted.has(tag.toLowerCase()));
|
|
89
|
+
}, [searchParams, allTags]);
|
|
90
|
+
|
|
91
|
+
const infoByKey = useMemo(() => updateInfo(children), [children]);
|
|
92
|
+
|
|
93
|
+
if (allTags.length === 0) {
|
|
94
|
+
return <div className={styles.changelog}>{children}</div>;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const toggle = (tag: string) => {
|
|
98
|
+
const lower = tag.toLowerCase();
|
|
99
|
+
const next = selected.some(item => item.toLowerCase() === lower)
|
|
100
|
+
? selected.filter(item => item.toLowerCase() !== lower)
|
|
101
|
+
: [...selected, tag];
|
|
102
|
+
|
|
103
|
+
setSearchParams(next.length ? { tags: next.join(',') } : {}, { preventScrollReset: true });
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
const clear = () => {
|
|
107
|
+
setSearchParams({}, { preventScrollReset: true });
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
const selectedSet = new Set(selected.map(tag => tag.toLowerCase()));
|
|
111
|
+
|
|
112
|
+
const filtered = flatElements(children).map((child, index) => {
|
|
113
|
+
if (child.type !== Update) {
|
|
114
|
+
return child;
|
|
115
|
+
}
|
|
116
|
+
const key = String(child.key ?? `update-${index}`);
|
|
117
|
+
const tags = infoByKey.get(key)?.tags ?? [];
|
|
118
|
+
if (selectedSet.size === 0) {
|
|
119
|
+
return child;
|
|
120
|
+
}
|
|
121
|
+
const lower = tags.map(tag => tag.toLowerCase());
|
|
122
|
+
const matches = [...selectedSet].every(tag => lower.includes(tag));
|
|
123
|
+
return matches ? child : null;
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
const visibleUpdates = filtered.filter(
|
|
127
|
+
child => child && (child as React.ReactElement).type === Update,
|
|
128
|
+
).length;
|
|
129
|
+
|
|
130
|
+
return (
|
|
131
|
+
<div className={styles.changelog}>
|
|
132
|
+
{/* biome-ignore lint/a11y/useSemanticElements: toggle-button group labelled for the filter */}
|
|
133
|
+
<div className={styles.changelogFilters} role="group" aria-label="Filter updates by tag">
|
|
134
|
+
{allTags.map(tag => {
|
|
135
|
+
const active = selectedSet.has(tag.toLowerCase());
|
|
136
|
+
return (
|
|
137
|
+
<button
|
|
138
|
+
key={tag}
|
|
139
|
+
type="button"
|
|
140
|
+
onClick={() => toggle(tag)}
|
|
141
|
+
aria-pressed={active}
|
|
142
|
+
className={styles.changelogFilterButton}
|
|
143
|
+
data-active={active ? '' : undefined}
|
|
144
|
+
>
|
|
145
|
+
<Badge size="sm" color={active ? 'primary' : undefined}>
|
|
146
|
+
{tag}
|
|
147
|
+
</Badge>
|
|
148
|
+
</button>
|
|
149
|
+
);
|
|
150
|
+
})}
|
|
151
|
+
{selected.length > 0 ? (
|
|
152
|
+
<button type="button" onClick={clear} className={styles.changelogClear}>
|
|
153
|
+
Clear
|
|
154
|
+
</button>
|
|
155
|
+
) : null}
|
|
156
|
+
</div>
|
|
157
|
+
{visibleUpdates === 0 ? (
|
|
158
|
+
<p className={styles.changelogEmpty}>No updates match the selected tags.</p>
|
|
159
|
+
) : null}
|
|
160
|
+
{filtered}
|
|
161
|
+
</div>
|
|
162
|
+
);
|
|
163
|
+
}
|
|
@@ -39,14 +39,18 @@ export function CodeGroup({ children }: CodeGroupProps) {
|
|
|
39
39
|
}
|
|
40
40
|
|
|
41
41
|
if (blocks.length === 1) {
|
|
42
|
-
return
|
|
42
|
+
return (
|
|
43
|
+
<div className={`${styles.tabs} [&_[data-slot=code-block-header]]:hidden`}>
|
|
44
|
+
{blocks[0].content}
|
|
45
|
+
</div>
|
|
46
|
+
);
|
|
43
47
|
}
|
|
44
48
|
|
|
45
49
|
return (
|
|
46
50
|
<TabsPrimitive
|
|
47
51
|
value={selected}
|
|
48
52
|
onValueChange={setSelectedKey}
|
|
49
|
-
className="my-4 gap-0 overflow-hidden rounded-lg bg-card [&_[data-slot=code-block]]:my-0 [&_[data-slot=code-block]]:rounded-none [&_[data-slot=code-block]]:border-0 [&_[data-slot=code-block]]:bg-transparent"
|
|
53
|
+
className="my-4 gap-0 overflow-hidden rounded-lg bg-card [&_[data-slot=code-block]]:my-0 [&_[data-slot=code-block]]:rounded-none [&_[data-slot=code-block]]:border-0 [&_[data-slot=code-block]]:bg-transparent [&_[data-slot=code-block-header]]:hidden"
|
|
50
54
|
>
|
|
51
55
|
<TabsList
|
|
52
56
|
variant="line"
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
import { type ReactNode, useEffect, useId, useRef, useState } from 'react';
|
|
2
|
+
import { styles } from './styles';
|
|
3
|
+
|
|
4
|
+
export type MermaidPlacement = 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right';
|
|
5
|
+
|
|
6
|
+
export interface MermaidProps {
|
|
7
|
+
/** Diagram definition. Falls back to string children when omitted. */
|
|
8
|
+
chart?: string;
|
|
9
|
+
title?: ReactNode;
|
|
10
|
+
/**
|
|
11
|
+
* Show zoom/pan/reset controls. Defaults to true when the diagram exceeds
|
|
12
|
+
* 120px in height (Mintlify behavior). Pass false to hide.
|
|
13
|
+
*/
|
|
14
|
+
actions?: boolean;
|
|
15
|
+
/** Corner for the controls. Defaults to "bottom-right". */
|
|
16
|
+
placement?: MermaidPlacement;
|
|
17
|
+
children?: ReactNode;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function childrenToText(node: ReactNode): string {
|
|
21
|
+
if (typeof node === 'string' || typeof node === 'number') {
|
|
22
|
+
return String(node);
|
|
23
|
+
}
|
|
24
|
+
if (Array.isArray(node)) {
|
|
25
|
+
return node.map(childrenToText).join('');
|
|
26
|
+
}
|
|
27
|
+
if (node && typeof node === 'object' && 'props' in (node as object)) {
|
|
28
|
+
const props = (node as { props?: { children?: ReactNode } }).props;
|
|
29
|
+
return childrenToText(props?.children);
|
|
30
|
+
}
|
|
31
|
+
return '';
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/** Extracts raw diagram source from MDX children (string or <code> element). */
|
|
35
|
+
export function mermaidSource(chart: string | undefined, children: ReactNode): string {
|
|
36
|
+
const raw = typeof chart === 'string' && chart.trim() ? chart : childrenToText(children);
|
|
37
|
+
return raw.replace(/^\n+|\n+$/g, '').replace(/\\n$/, '');
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function isDarkMode(): boolean {
|
|
41
|
+
if (typeof document === 'undefined') {
|
|
42
|
+
return false;
|
|
43
|
+
}
|
|
44
|
+
return document.documentElement.classList.contains('dark');
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const PLACEMENT_CLASS: Record<MermaidPlacement, string> = {
|
|
48
|
+
'top-left': 'top-2 left-2',
|
|
49
|
+
'top-right': 'top-2 right-2',
|
|
50
|
+
'bottom-left': 'bottom-2 left-2',
|
|
51
|
+
'bottom-right': 'bottom-2 right-2',
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Renders a Mermaid diagram. Server/prerender output is the raw definition in
|
|
56
|
+
* a <pre> so search indexing and no-JS still see the content; the client
|
|
57
|
+
* replaces it with SVG via a lazy `mermaid` import (kept out of the SSR bundle).
|
|
58
|
+
*/
|
|
59
|
+
export function Mermaid({
|
|
60
|
+
chart,
|
|
61
|
+
title,
|
|
62
|
+
actions,
|
|
63
|
+
placement = 'bottom-right',
|
|
64
|
+
children,
|
|
65
|
+
}: MermaidProps) {
|
|
66
|
+
const source = mermaidSource(chart, children);
|
|
67
|
+
const containerRef = useRef<HTMLDivElement>(null);
|
|
68
|
+
const svgHostRef = useRef<HTMLDivElement>(null);
|
|
69
|
+
const diagramId = useId().replace(/[^a-zA-Z0-9]/g, '');
|
|
70
|
+
const [svg, setSvg] = useState<string | null>(null);
|
|
71
|
+
const [error, setError] = useState(false);
|
|
72
|
+
const [zoom, setZoom] = useState(1);
|
|
73
|
+
const [dark, setDark] = useState(false);
|
|
74
|
+
|
|
75
|
+
useEffect(() => {
|
|
76
|
+
setDark(isDarkMode());
|
|
77
|
+
const observer = new MutationObserver(() => setDark(isDarkMode()));
|
|
78
|
+
observer.observe(document.documentElement, { attributes: true, attributeFilter: ['class'] });
|
|
79
|
+
return () => observer.disconnect();
|
|
80
|
+
}, []);
|
|
81
|
+
|
|
82
|
+
useEffect(() => {
|
|
83
|
+
if (!source.trim() || typeof window === 'undefined') {
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
let cancelled = false;
|
|
88
|
+
setError(false);
|
|
89
|
+
setSvg(null);
|
|
90
|
+
(async () => {
|
|
91
|
+
try {
|
|
92
|
+
const { default: mermaid } = await import('mermaid');
|
|
93
|
+
if (cancelled) {
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
mermaid.initialize({
|
|
97
|
+
startOnLoad: false,
|
|
98
|
+
theme: dark ? 'dark' : 'neutral',
|
|
99
|
+
securityLevel: 'strict',
|
|
100
|
+
});
|
|
101
|
+
const { svg: rendered } = await mermaid.render(`shiso-mermaid-${diagramId}`, source);
|
|
102
|
+
if (!cancelled) {
|
|
103
|
+
setSvg(rendered);
|
|
104
|
+
}
|
|
105
|
+
} catch {
|
|
106
|
+
if (!cancelled) {
|
|
107
|
+
setError(true);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
})();
|
|
111
|
+
|
|
112
|
+
return () => {
|
|
113
|
+
cancelled = true;
|
|
114
|
+
};
|
|
115
|
+
}, [source, diagramId, dark]);
|
|
116
|
+
|
|
117
|
+
// Pan is native scroll; zoom controls scale the SVG host.
|
|
118
|
+
const showControls = actions ?? true;
|
|
119
|
+
|
|
120
|
+
return (
|
|
121
|
+
<figure className={styles.mermaid} data-dark={dark ? '' : undefined}>
|
|
122
|
+
{title ? <figcaption className={styles.mermaidTitle}>{title}</figcaption> : null}
|
|
123
|
+
<div ref={containerRef} className={styles.mermaidViewport}>
|
|
124
|
+
{svg && !error ? (
|
|
125
|
+
<div
|
|
126
|
+
ref={svgHostRef}
|
|
127
|
+
className={styles.mermaidSvg}
|
|
128
|
+
style={{ transform: `scale(${zoom})` }}
|
|
129
|
+
// biome-ignore lint/security/noDangerouslySetInnerHtml: mermaid generates this SVG from the page's own diagram source.
|
|
130
|
+
dangerouslySetInnerHTML={{ __html: svg }}
|
|
131
|
+
role="img"
|
|
132
|
+
aria-label={title ? String(title) : 'Diagram'}
|
|
133
|
+
/>
|
|
134
|
+
) : (
|
|
135
|
+
<pre className={styles.mermaidFallback} title="Diagram source">
|
|
136
|
+
{source}
|
|
137
|
+
</pre>
|
|
138
|
+
)}
|
|
139
|
+
{error ? <p className={styles.mermaidError}>Could not render this diagram.</p> : null}
|
|
140
|
+
{showControls && svg && !error ? (
|
|
141
|
+
<div className={`${styles.mermaidControls} ${PLACEMENT_CLASS[placement]}`}>
|
|
142
|
+
<button
|
|
143
|
+
type="button"
|
|
144
|
+
aria-label="Zoom out"
|
|
145
|
+
onClick={() => setZoom(z => Math.max(0.5, +(z - 0.25).toFixed(2)))}
|
|
146
|
+
>
|
|
147
|
+
−
|
|
148
|
+
</button>
|
|
149
|
+
<button type="button" aria-label="Reset view" onClick={() => setZoom(1)}>
|
|
150
|
+
⟳
|
|
151
|
+
</button>
|
|
152
|
+
<button
|
|
153
|
+
type="button"
|
|
154
|
+
aria-label="Zoom in"
|
|
155
|
+
onClick={() => setZoom(z => Math.min(2.5, +(z + 0.25).toFixed(2)))}
|
|
156
|
+
>
|
|
157
|
+
+
|
|
158
|
+
</button>
|
|
159
|
+
</div>
|
|
160
|
+
) : null}
|
|
161
|
+
</div>
|
|
162
|
+
</figure>
|
|
163
|
+
);
|
|
164
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { type ReactNode, useEffect } from 'react';
|
|
2
|
+
import { useSetPanelContent } from './panel-context';
|
|
3
|
+
import { styles } from './styles';
|
|
4
|
+
|
|
5
|
+
export interface PanelProps {
|
|
6
|
+
children?: ReactNode;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Pins supplementary content to the right rail (replacing the table of
|
|
11
|
+
* contents on desktop). On small screens the content renders inline where it
|
|
12
|
+
* is written. Only one Panel per page is supported; the last one wins.
|
|
13
|
+
*/
|
|
14
|
+
export function Panel({ children }: PanelProps) {
|
|
15
|
+
const setContent = useSetPanelContent();
|
|
16
|
+
|
|
17
|
+
useEffect(() => {
|
|
18
|
+
setContent(children ? <div className={styles.panel}>{children}</div> : null);
|
|
19
|
+
return () => setContent(null);
|
|
20
|
+
}, [children, setContent]);
|
|
21
|
+
|
|
22
|
+
return <div className={styles.panelInline}>{children}</div>;
|
|
23
|
+
}
|
|
@@ -28,14 +28,26 @@ export function ParamField({
|
|
|
28
28
|
const location = query ? 'query' : path ? 'path' : header ? 'header' : body ? 'body' : undefined;
|
|
29
29
|
|
|
30
30
|
return (
|
|
31
|
-
<div className={styles.paramField}>
|
|
31
|
+
<div className={styles.paramField} data-slot="field-group-item">
|
|
32
32
|
<div className={styles.fieldHeader}>
|
|
33
33
|
<span className={styles.fieldName}>{label}</span>
|
|
34
|
-
{location ? <Badge>{location}</Badge> : null}
|
|
35
|
-
{type ?
|
|
36
|
-
|
|
34
|
+
{location ? <Badge size="sm">{location}</Badge> : null}
|
|
35
|
+
{type ? (
|
|
36
|
+
<span className={styles.fieldType} data-slot="field-group-type">
|
|
37
|
+
{decodeHtmlEntities(type)}
|
|
38
|
+
</span>
|
|
39
|
+
) : null}
|
|
40
|
+
{required ? (
|
|
41
|
+
<Badge size="sm" tone="primary">
|
|
42
|
+
required
|
|
43
|
+
</Badge>
|
|
44
|
+
) : null}
|
|
37
45
|
</div>
|
|
38
|
-
{children ?
|
|
46
|
+
{children ? (
|
|
47
|
+
<div className={styles.fieldBody} data-slot="field-group-body">
|
|
48
|
+
{children}
|
|
49
|
+
</div>
|
|
50
|
+
) : null}
|
|
39
51
|
</div>
|
|
40
52
|
);
|
|
41
53
|
}
|
|
@@ -45,11 +45,11 @@ export function PropertiesTable({ children }: PropertiesTableProps) {
|
|
|
45
45
|
<span className="inline-flex flex-wrap items-center gap-2">
|
|
46
46
|
{name}
|
|
47
47
|
{required ? (
|
|
48
|
-
<Badge size="
|
|
48
|
+
<Badge size="sm" tone="primary">
|
|
49
49
|
required
|
|
50
50
|
</Badge>
|
|
51
51
|
) : null}
|
|
52
|
-
{deprecated ? <Badge size="
|
|
52
|
+
{deprecated ? <Badge size="sm">deprecated</Badge> : null}
|
|
53
53
|
</span>
|
|
54
54
|
</td>
|
|
55
55
|
<td>{displayValue(type)}</td>
|