jamdesk 1.1.89 → 1.1.91
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/dist/__tests__/integration/validate.integration.test.js +2 -1
- package/dist/__tests__/integration/validate.integration.test.js.map +1 -1
- package/dist/__tests__/unit/dev-workspace-symlinks.test.d.ts +2 -0
- package/dist/__tests__/unit/dev-workspace-symlinks.test.d.ts.map +1 -0
- package/dist/__tests__/unit/dev-workspace-symlinks.test.js +112 -0
- package/dist/__tests__/unit/dev-workspace-symlinks.test.js.map +1 -0
- package/dist/__tests__/unit/docs-config-discovery.test.d.ts +2 -0
- package/dist/__tests__/unit/docs-config-discovery.test.d.ts.map +1 -0
- package/dist/__tests__/unit/docs-config-discovery.test.js +190 -0
- package/dist/__tests__/unit/docs-config-discovery.test.js.map +1 -0
- package/dist/__tests__/unit/docs-config.test.js +2 -1
- package/dist/__tests__/unit/docs-config.test.js.map +1 -1
- package/dist/__tests__/unit/language-filter.test.d.ts +2 -0
- package/dist/__tests__/unit/language-filter.test.d.ts.map +1 -0
- package/dist/__tests__/unit/language-filter.test.js +166 -0
- package/dist/__tests__/unit/language-filter.test.js.map +1 -0
- package/dist/__tests__/unit/output.test.d.ts +2 -0
- package/dist/__tests__/unit/output.test.d.ts.map +1 -0
- package/dist/__tests__/unit/output.test.js +61 -0
- package/dist/__tests__/unit/output.test.js.map +1 -0
- package/dist/commands/dev.d.ts.map +1 -1
- package/dist/commands/dev.js +4 -1
- package/dist/commands/dev.js.map +1 -1
- package/dist/commands/doctor.d.ts.map +1 -1
- package/dist/commands/doctor.js +14 -12
- package/dist/commands/doctor.js.map +1 -1
- package/dist/commands/validate.d.ts.map +1 -1
- package/dist/commands/validate.js +14 -2
- package/dist/commands/validate.js.map +1 -1
- package/dist/lib/docs-config.d.ts +54 -3
- package/dist/lib/docs-config.d.ts.map +1 -1
- package/dist/lib/docs-config.js +126 -8
- package/dist/lib/docs-config.js.map +1 -1
- package/dist/lib/language-filter.d.ts +31 -0
- package/dist/lib/language-filter.d.ts.map +1 -0
- package/dist/lib/language-filter.js +14 -0
- package/dist/lib/language-filter.js.map +1 -0
- package/package.json +1 -1
- package/vendored/app/api/r2/[project]/[...path]/route.ts +14 -9
- package/vendored/app/layout.tsx +2 -2
- package/vendored/components/HtmlLangSync.tsx +3 -2
- package/vendored/components/mdx/Accordion.tsx +1 -1
- package/vendored/components/mdx/Card.tsx +1 -1
- package/vendored/components/mdx/CodeGroup.tsx +18 -23
- package/vendored/components/mdx/Color.tsx +0 -1
- package/vendored/components/mdx/Icon.tsx +1 -1
- package/vendored/components/mdx/MDXComponents.tsx +92 -66
- package/vendored/components/mdx/OpenApiEndpoint.tsx +0 -1
- package/vendored/components/mdx/ParamField.tsx +0 -1
- package/vendored/components/mdx/RequestExample.tsx +0 -1
- package/vendored/components/mdx/ResponseExample.tsx +0 -1
- package/vendored/components/mdx/Steps.tsx +12 -3
- package/vendored/components/mdx/Table.tsx +8 -2
- package/vendored/components/mdx/Tabs.tsx +1 -1
- package/vendored/components/mdx/Tree.tsx +6 -4
- package/vendored/components/navigation/Header.tsx +7 -5
- package/vendored/components/navigation/LanguageSelector.tsx +32 -7
- package/vendored/components/navigation/TableOfContents.tsx +1 -1
- package/vendored/components/navigation/TabsNav.tsx +17 -5
- package/vendored/components/search/SearchModal.tsx +41 -36
- package/vendored/components/ui/CodePanel.tsx +2 -2
- package/vendored/hooks/useChat.ts +1 -1
- package/vendored/hooks/useShikiHighlight.ts +7 -1
- package/vendored/lib/build/error-parser.ts +38 -12
- package/vendored/lib/code-utils.ts +6 -2
- package/vendored/lib/health-checks.ts +2 -2
- package/vendored/lib/language-utils.ts +53 -2
- package/vendored/lib/layout-helpers.tsx +2 -1
- package/vendored/lib/mdx-inline-components.ts +1 -1
- package/vendored/lib/navigation-resolver.ts +0 -69
- package/vendored/lib/normalize-config.ts +1 -1
- package/vendored/lib/openapi/generator.ts +3 -3
- package/vendored/lib/openapi/parser.ts +14 -6
- package/vendored/lib/openapi/validator.ts +2 -2
- package/vendored/lib/openapi-isr.ts +4 -1
- package/vendored/lib/public-paths-resolver.ts +7 -6
- package/vendored/lib/redis.ts +2 -2
- package/vendored/lib/rehype-code-meta.ts +2 -2
- package/vendored/lib/render-doc-page.tsx +2 -2
- package/vendored/lib/seo.ts +21 -6
- package/vendored/lib/shiki-highlighter.ts +1 -1
- package/vendored/lib/snippet-loader-isr.ts +1 -1
- package/vendored/lib/validate-config.ts +136 -8
- package/vendored/shared/status-reporter.ts +12 -0
- package/vendored/workspace-package-lock.json +16 -16
|
@@ -1,4 +1,15 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
isValidElement,
|
|
3
|
+
type AnchorHTMLAttributes,
|
|
4
|
+
type BlockquoteHTMLAttributes,
|
|
5
|
+
type HTMLAttributes,
|
|
6
|
+
type ImgHTMLAttributes,
|
|
7
|
+
type OlHTMLAttributes,
|
|
8
|
+
type ReactNode,
|
|
9
|
+
type TableHTMLAttributes,
|
|
10
|
+
type TdHTMLAttributes,
|
|
11
|
+
type ThHTMLAttributes,
|
|
12
|
+
} from 'react';
|
|
2
13
|
import { Card } from './Card';
|
|
3
14
|
import { Note, Info, Warning, Tip, Check, Danger, Callout } from './Callouts';
|
|
4
15
|
import { Accordion, AccordionGroup } from './Accordion';
|
|
@@ -41,14 +52,20 @@ import { Visibility } from './Visibility';
|
|
|
41
52
|
* 2. language-* class on code element (standard markdown)
|
|
42
53
|
* 3. language-* class on pre element (fallback for edge cases)
|
|
43
54
|
*/
|
|
44
|
-
|
|
55
|
+
type PreLikeProps = {
|
|
56
|
+
'data-language'?: string;
|
|
57
|
+
className?: string;
|
|
58
|
+
};
|
|
59
|
+
type CodeLikeProps = { className?: string };
|
|
60
|
+
|
|
61
|
+
function getCodeLanguage(preProps: PreLikeProps, children: ReactNode): string {
|
|
45
62
|
// Check data-language on pre element (added by Shiki transformer)
|
|
46
63
|
if (preProps['data-language']) {
|
|
47
64
|
return preProps['data-language'];
|
|
48
65
|
}
|
|
49
66
|
// Check code element's className for language-* class
|
|
50
67
|
if (isValidElement(children)) {
|
|
51
|
-
const codeProps = children.props as
|
|
68
|
+
const codeProps = children.props as CodeLikeProps;
|
|
52
69
|
const className = codeProps?.className || '';
|
|
53
70
|
const match = className.match(/language-(\w+)/);
|
|
54
71
|
if (match) return match[1];
|
|
@@ -70,7 +87,7 @@ function extractTextFromChildren(children: ReactNode): string {
|
|
|
70
87
|
if (!children) return '';
|
|
71
88
|
|
|
72
89
|
if (isValidElement(children)) {
|
|
73
|
-
const props = children.props as
|
|
90
|
+
const props = children.props as { children?: ReactNode };
|
|
74
91
|
return extractTextFromChildren(props?.children);
|
|
75
92
|
}
|
|
76
93
|
|
|
@@ -239,9 +256,9 @@ export const MDXComponents = {
|
|
|
239
256
|
// Sized images from preprocess-mdx ( syntax).
|
|
240
257
|
// These are output as <SizedImage> JSX so they go through component mapping
|
|
241
258
|
// (raw <img> JSX in MDX bypasses the components provider).
|
|
242
|
-
SizedImage: ({ src, alt, width, height, className, ...
|
|
259
|
+
SizedImage: ({ src, alt, width, height, className, ..._rest }: Omit<ImgHTMLAttributes<HTMLImageElement>, 'src'> & { src?: string }) => (
|
|
243
260
|
<ZoomableImage
|
|
244
|
-
src={src}
|
|
261
|
+
src={src ?? ''}
|
|
245
262
|
alt={alt || ''}
|
|
246
263
|
width={width}
|
|
247
264
|
height={height}
|
|
@@ -269,11 +286,20 @@ export const MDXComponents = {
|
|
|
269
286
|
noStyle, // Explicitly destructure to prevent React warning about unknown DOM prop
|
|
270
287
|
nostyle, // Also handle lowercase version from HTML parsing
|
|
271
288
|
// Destructure and ignore any other props to prevent them from being passed to DOM
|
|
272
|
-
...
|
|
273
|
-
}:
|
|
289
|
+
..._rest
|
|
290
|
+
}: Omit<ImgHTMLAttributes<HTMLImageElement>, 'src'> & {
|
|
291
|
+
src?: string;
|
|
292
|
+
class?: string;
|
|
293
|
+
'data-no-zoom'?: string | boolean;
|
|
294
|
+
'data-no-style'?: string | boolean;
|
|
295
|
+
noZoom?: string | boolean;
|
|
296
|
+
nozoom?: string | boolean;
|
|
297
|
+
noStyle?: string | boolean;
|
|
298
|
+
nostyle?: string | boolean;
|
|
299
|
+
}) => {
|
|
274
300
|
// Check if this is a video file — render <Video> instead of <ZoomableImage>
|
|
275
301
|
if (isVideoUrl(src)) {
|
|
276
|
-
return <Video src={src} title={alt || undefined} />;
|
|
302
|
+
return <Video src={src ?? ''} title={alt || undefined} />;
|
|
277
303
|
}
|
|
278
304
|
|
|
279
305
|
// Check for data-no-zoom attribute or noZoom prop (handle both camelCase and lowercase)
|
|
@@ -295,7 +321,7 @@ export const MDXComponents = {
|
|
|
295
321
|
|
|
296
322
|
return (
|
|
297
323
|
<ZoomableImage
|
|
298
|
-
src={src}
|
|
324
|
+
src={src ?? ''}
|
|
299
325
|
alt={alt || ''}
|
|
300
326
|
width={width}
|
|
301
327
|
height={height}
|
|
@@ -308,64 +334,64 @@ export const MDXComponents = {
|
|
|
308
334
|
/>
|
|
309
335
|
);
|
|
310
336
|
},
|
|
311
|
-
h1: (props:
|
|
312
|
-
<h1
|
|
313
|
-
className="text-3xl font-bold text-theme-text-primary mt-8 mb-4 tracking-tight"
|
|
314
|
-
{...props}
|
|
337
|
+
h1: (props: HTMLAttributes<HTMLHeadingElement>) => (
|
|
338
|
+
<h1
|
|
339
|
+
className="text-3xl font-bold text-theme-text-primary mt-8 mb-4 tracking-tight"
|
|
340
|
+
{...props}
|
|
315
341
|
/>
|
|
316
342
|
),
|
|
317
|
-
h2: (props:
|
|
318
|
-
<h2
|
|
319
|
-
className="text-2xl font-semibold text-theme-text-primary mt-10 mb-4 tracking-tight scroll-mt-20"
|
|
320
|
-
{...props}
|
|
343
|
+
h2: (props: HTMLAttributes<HTMLHeadingElement>) => (
|
|
344
|
+
<h2
|
|
345
|
+
className="text-2xl font-semibold text-theme-text-primary mt-10 mb-4 tracking-tight scroll-mt-20"
|
|
346
|
+
{...props}
|
|
321
347
|
/>
|
|
322
348
|
),
|
|
323
|
-
h3: (props:
|
|
324
|
-
<h3
|
|
325
|
-
className="text-xl font-semibold text-theme-text-primary mt-8 mb-3 scroll-mt-20"
|
|
326
|
-
{...props}
|
|
349
|
+
h3: (props: HTMLAttributes<HTMLHeadingElement>) => (
|
|
350
|
+
<h3
|
|
351
|
+
className="text-xl font-semibold text-theme-text-primary mt-8 mb-3 scroll-mt-20"
|
|
352
|
+
{...props}
|
|
327
353
|
/>
|
|
328
354
|
),
|
|
329
|
-
h4: (props:
|
|
330
|
-
<h4
|
|
331
|
-
className="text-lg font-semibold text-theme-text-primary mt-6 mb-2 scroll-mt-20"
|
|
332
|
-
{...props}
|
|
355
|
+
h4: (props: HTMLAttributes<HTMLHeadingElement>) => (
|
|
356
|
+
<h4
|
|
357
|
+
className="text-lg font-semibold text-theme-text-primary mt-6 mb-2 scroll-mt-20"
|
|
358
|
+
{...props}
|
|
333
359
|
/>
|
|
334
360
|
),
|
|
335
|
-
p: (props:
|
|
336
|
-
<p
|
|
337
|
-
className="text-theme-text-secondary leading-7 mb-4"
|
|
338
|
-
{...props}
|
|
361
|
+
p: (props: HTMLAttributes<HTMLParagraphElement>) => (
|
|
362
|
+
<p
|
|
363
|
+
className="text-theme-text-secondary leading-7 mb-4"
|
|
364
|
+
{...props}
|
|
339
365
|
/>
|
|
340
366
|
),
|
|
341
|
-
ul: ({ class: htmlClass, className, ...props }:
|
|
342
|
-
<ul
|
|
343
|
-
className={`list-disc list-inside text-theme-text-secondary space-y-2 mb-4 ml-4 ${htmlClass || ''} ${className || ''}`.trim()}
|
|
344
|
-
{...props}
|
|
367
|
+
ul: ({ class: htmlClass, className, ...props }: HTMLAttributes<HTMLUListElement> & { class?: string }) => (
|
|
368
|
+
<ul
|
|
369
|
+
className={`list-disc list-inside text-theme-text-secondary space-y-2 mb-4 ml-4 ${htmlClass || ''} ${className || ''}`.trim()}
|
|
370
|
+
{...props}
|
|
345
371
|
/>
|
|
346
372
|
),
|
|
347
|
-
ol: ({ class: htmlClass, className, ...props }:
|
|
348
|
-
<ol
|
|
349
|
-
className={`list-decimal list-inside text-theme-text-secondary space-y-2 mb-4 ml-4 ${htmlClass || ''} ${className || ''}`.trim()}
|
|
350
|
-
{...props}
|
|
373
|
+
ol: ({ class: htmlClass, className, ...props }: OlHTMLAttributes<HTMLOListElement> & { class?: string }) => (
|
|
374
|
+
<ol
|
|
375
|
+
className={`list-decimal list-inside text-theme-text-secondary space-y-2 mb-4 ml-4 ${htmlClass || ''} ${className || ''}`.trim()}
|
|
376
|
+
{...props}
|
|
351
377
|
/>
|
|
352
378
|
),
|
|
353
|
-
li: (props:
|
|
354
|
-
<li
|
|
355
|
-
className="text-theme-text-secondary marker:text-theme-marker"
|
|
356
|
-
{...props}
|
|
379
|
+
li: (props: HTMLAttributes<HTMLLIElement>) => (
|
|
380
|
+
<li
|
|
381
|
+
className="text-theme-text-secondary marker:text-theme-marker"
|
|
382
|
+
{...props}
|
|
357
383
|
/>
|
|
358
384
|
),
|
|
359
385
|
// NOTE: page.tsx overrides this for hostAtDocs sites to auto-prefix links.
|
|
360
386
|
// If you change styling here, update the override in app/[[...slug]]/page.tsx too.
|
|
361
|
-
a: ({ ariaLabel, ...props }:
|
|
387
|
+
a: ({ ariaLabel, ...props }: AnchorHTMLAttributes<HTMLAnchorElement> & { ariaLabel?: string }) => (
|
|
362
388
|
<a
|
|
363
389
|
className="text-theme-accent hover:text-theme-accent-hover transition-colors"
|
|
364
390
|
aria-label={ariaLabel}
|
|
365
391
|
{...props}
|
|
366
392
|
/>
|
|
367
393
|
),
|
|
368
|
-
blockquote: (props:
|
|
394
|
+
blockquote: (props: BlockquoteHTMLAttributes<HTMLQuoteElement>) => (
|
|
369
395
|
<blockquote
|
|
370
396
|
className="border-l-4 pl-4 text-theme-text-tertiary my-6 not-italic"
|
|
371
397
|
style={{ borderColor: 'var(--color-border)' }}
|
|
@@ -373,7 +399,7 @@ export const MDXComponents = {
|
|
|
373
399
|
/>
|
|
374
400
|
),
|
|
375
401
|
// Custom pre component to wrap code blocks with titles in CodePanel
|
|
376
|
-
pre: ({ children, 'data-title': dataTitle, ...props }:
|
|
402
|
+
pre: ({ children, 'data-title': dataTitle, ...props }: HTMLAttributes<HTMLPreElement> & { 'data-title'?: string }) => {
|
|
377
403
|
const language = getCodeLanguage(props, children);
|
|
378
404
|
|
|
379
405
|
// Check for mermaid diagrams - render with Mermaid component
|
|
@@ -411,41 +437,41 @@ export const MDXComponents = {
|
|
|
411
437
|
);
|
|
412
438
|
},
|
|
413
439
|
// Inline code styling is done via CSS in base.css (.prose :not(pre) > code)
|
|
414
|
-
table: (props:
|
|
440
|
+
table: (props: TableHTMLAttributes<HTMLTableElement>) => (
|
|
415
441
|
<div className="overflow-x-auto my-6">
|
|
416
|
-
<table
|
|
417
|
-
className="w-full text-sm border-collapse"
|
|
418
|
-
{...props}
|
|
442
|
+
<table
|
|
443
|
+
className="w-full text-sm border-collapse"
|
|
444
|
+
{...props}
|
|
419
445
|
/>
|
|
420
446
|
</div>
|
|
421
447
|
),
|
|
422
|
-
th: (props:
|
|
423
|
-
<th
|
|
424
|
-
className="text-left font-semibold text-theme-text-primary p-3 border-b-2 border-theme-border bg-theme-bg-secondary"
|
|
425
|
-
{...props}
|
|
448
|
+
th: (props: ThHTMLAttributes<HTMLTableHeaderCellElement>) => (
|
|
449
|
+
<th
|
|
450
|
+
className="text-left font-semibold text-theme-text-primary p-3 border-b-2 border-theme-border bg-theme-bg-secondary"
|
|
451
|
+
{...props}
|
|
426
452
|
/>
|
|
427
453
|
),
|
|
428
|
-
td: (props:
|
|
429
|
-
<td
|
|
430
|
-
className="p-3 border-b border-theme-border text-theme-text-secondary"
|
|
431
|
-
{...props}
|
|
454
|
+
td: (props: TdHTMLAttributes<HTMLTableDataCellElement>) => (
|
|
455
|
+
<td
|
|
456
|
+
className="p-3 border-b border-theme-border text-theme-text-secondary"
|
|
457
|
+
{...props}
|
|
432
458
|
/>
|
|
433
459
|
),
|
|
434
|
-
hr: (props:
|
|
435
|
-
<hr
|
|
436
|
-
className="border-theme-border my-8"
|
|
437
|
-
{...props}
|
|
460
|
+
hr: (props: HTMLAttributes<HTMLHRElement>) => (
|
|
461
|
+
<hr
|
|
462
|
+
className="border-theme-border my-8"
|
|
463
|
+
{...props}
|
|
438
464
|
/>
|
|
439
465
|
),
|
|
440
466
|
// Generic div handler to convert class to className
|
|
441
|
-
div: ({ class: htmlClass, className, ...props }:
|
|
442
|
-
<div
|
|
443
|
-
className={`${htmlClass || ''} ${className || ''}`.trim() || undefined}
|
|
444
|
-
{...props}
|
|
467
|
+
div: ({ class: htmlClass, className, ...props }: HTMLAttributes<HTMLDivElement> & { class?: string }) => (
|
|
468
|
+
<div
|
|
469
|
+
className={`${htmlClass || ''} ${className || ''}`.trim() || undefined}
|
|
470
|
+
{...props}
|
|
445
471
|
/>
|
|
446
472
|
),
|
|
447
473
|
// Generic span handler to convert class to className
|
|
448
|
-
span: ({ class: htmlClass, className, ...props }:
|
|
474
|
+
span: ({ class: htmlClass, className, ...props }: HTMLAttributes<HTMLSpanElement> & { class?: string }) => (
|
|
449
475
|
<span
|
|
450
476
|
className={`${htmlClass || ''} ${className || ''}`.trim() || undefined}
|
|
451
477
|
{...props}
|
|
@@ -77,7 +77,6 @@ function renderSchemaType(schema: JsonSchema): string {
|
|
|
77
77
|
// Shiki HTML comes from server-side highlighting of trusted code strings;
|
|
78
78
|
// sanitization is unnecessary and would break the syntax-color spans.
|
|
79
79
|
function renderHighlightedHtml(html: string) {
|
|
80
|
-
// eslint-disable-next-line react/no-danger
|
|
81
80
|
return <div dangerouslySetInnerHTML={{ __html: html }} />;
|
|
82
81
|
}
|
|
83
82
|
|
|
@@ -30,7 +30,6 @@ export function ParamField({
|
|
|
30
30
|
}: ParamFieldProps) {
|
|
31
31
|
// Determine the parameter name from the prop
|
|
32
32
|
const paramName = body || header || query || path || '';
|
|
33
|
-
const location = body ? 'body' : header ? 'header' : query ? 'query' : path ? 'path' : '';
|
|
34
33
|
|
|
35
34
|
return (
|
|
36
35
|
<div className="group param-divider py-2.5 first:pt-0 not-prose">
|
|
@@ -3,7 +3,6 @@
|
|
|
3
3
|
import { ReactNode, Children, isValidElement } from 'react';
|
|
4
4
|
import { CodePanel, CodePanelTab } from '../ui/CodePanel';
|
|
5
5
|
import { formatLanguage, getElementProps } from '@/lib/code-utils';
|
|
6
|
-
import { getLanguageIcon } from '@/lib/language-icons';
|
|
7
6
|
|
|
8
7
|
interface RequestExampleProps {
|
|
9
8
|
children: ReactNode;
|
|
@@ -3,7 +3,6 @@
|
|
|
3
3
|
import { ReactNode, Children, isValidElement } from 'react';
|
|
4
4
|
import { CodePanel, CodePanelTab } from '../ui/CodePanel';
|
|
5
5
|
import { getElementProps } from '@/lib/code-utils';
|
|
6
|
-
import { getLanguageIcon } from '@/lib/language-icons';
|
|
7
6
|
|
|
8
7
|
interface ResponseExampleProps {
|
|
9
8
|
children: ReactNode;
|
|
@@ -1,6 +1,14 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
ReactNode,
|
|
5
|
+
Children,
|
|
6
|
+
Fragment,
|
|
7
|
+
cloneElement,
|
|
8
|
+
isValidElement,
|
|
9
|
+
memo,
|
|
10
|
+
type ReactElement,
|
|
11
|
+
} from 'react';
|
|
4
12
|
import { getIconClass } from '@/lib/icon-utils';
|
|
5
13
|
import { useStepSlug } from './StepSlugContext';
|
|
6
14
|
|
|
@@ -56,10 +64,11 @@ export const Steps = memo(function Steps({ children, titleSize = 'p' }: StepsPro
|
|
|
56
64
|
<div className="relative my-8 space-y-0 not-prose">
|
|
57
65
|
{childrenArray.map((child) => {
|
|
58
66
|
if (isStepComponent(child)) {
|
|
59
|
-
const
|
|
67
|
+
const stepEl = child as ReactElement<StepProps>;
|
|
68
|
+
const childProps = stepEl.props;
|
|
60
69
|
const currentStepIndex = stepIndex;
|
|
61
70
|
stepIndex++;
|
|
62
|
-
return cloneElement(
|
|
71
|
+
return cloneElement(stepEl, {
|
|
63
72
|
...childProps,
|
|
64
73
|
stepNumber: childProps.stepNumber ?? currentStepIndex + 1,
|
|
65
74
|
isLast: currentStepIndex === totalSteps - 1,
|
|
@@ -170,7 +170,10 @@ export function Table({
|
|
|
170
170
|
<tbody>
|
|
171
171
|
{sortedBodyRows.map((row, index) => {
|
|
172
172
|
if (isValidElement(row)) {
|
|
173
|
-
return cloneElement(
|
|
173
|
+
return cloneElement(
|
|
174
|
+
row as React.ReactElement<InternalRowProps>,
|
|
175
|
+
{ key: row.key ?? index, _rowIndex: index }
|
|
176
|
+
);
|
|
174
177
|
}
|
|
175
178
|
return row;
|
|
176
179
|
})}
|
|
@@ -223,7 +226,10 @@ export function Row({
|
|
|
223
226
|
<tr className={classes.join(' ')}>
|
|
224
227
|
{Children.map(children, (child, index) => {
|
|
225
228
|
if (isValidElement(child)) {
|
|
226
|
-
return cloneElement(
|
|
229
|
+
return cloneElement(
|
|
230
|
+
child as React.ReactElement<InternalCellProps>,
|
|
231
|
+
{ _isHeaderRow: header, _columnIndex: index }
|
|
232
|
+
);
|
|
227
233
|
}
|
|
228
234
|
return child;
|
|
229
235
|
})}
|
|
@@ -34,7 +34,7 @@ interface TabProps {
|
|
|
34
34
|
* Tab component - represents a single tab panel
|
|
35
35
|
* Must be used inside a Tabs component
|
|
36
36
|
*/
|
|
37
|
-
export const Tab = memo(function Tab({ title, icon, iconType, children }: TabProps) {
|
|
37
|
+
export const Tab = memo(function Tab({ title: _title, icon: _icon, iconType: _iconType, children }: TabProps) {
|
|
38
38
|
// This component is rendered by Tabs, not directly
|
|
39
39
|
// The props are extracted by Tabs to build the tab bar
|
|
40
40
|
return <>{children}</>;
|
|
@@ -10,8 +10,6 @@ import {
|
|
|
10
10
|
useMemo,
|
|
11
11
|
useId,
|
|
12
12
|
ReactNode,
|
|
13
|
-
Children,
|
|
14
|
-
isValidElement,
|
|
15
13
|
} from 'react';
|
|
16
14
|
|
|
17
15
|
// =============================================================================
|
|
@@ -158,7 +156,11 @@ function TreeRoot({ children }: TreeProps) {
|
|
|
158
156
|
|
|
159
157
|
traverse(null);
|
|
160
158
|
return items;
|
|
161
|
-
|
|
159
|
+
// itemsRef.current.size is intentional — refs don't trigger re-renders, but
|
|
160
|
+
// size changes correlate with register/unregister so this is the best
|
|
161
|
+
// available recompute signal without lifting items into state.
|
|
162
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
163
|
+
}, [expandedIds, itemsRef.current.size]);
|
|
162
164
|
|
|
163
165
|
// Initialize expanded state from defaultOpen props
|
|
164
166
|
useEffect(() => {
|
|
@@ -174,7 +176,7 @@ function TreeRoot({ children }: TreeProps) {
|
|
|
174
176
|
}
|
|
175
177
|
setInitialized(true);
|
|
176
178
|
}
|
|
177
|
-
}, [initialized, itemsRef.current.size]);
|
|
179
|
+
}, [initialized, itemsRef.current.size]);
|
|
178
180
|
|
|
179
181
|
// Type-ahead search handler
|
|
180
182
|
const handleTypeAhead = useCallback(
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
|
-
import { useState, useEffect, useMemo, useRef } from 'react';
|
|
3
|
+
import { useState, useEffect, useMemo, useRef, useCallback } from 'react';
|
|
4
4
|
import { useOnClickOutside } from '@/hooks/useOnClickOutside';
|
|
5
5
|
import Link from 'next/link';
|
|
6
6
|
import { usePathname } from 'next/navigation';
|
|
@@ -95,8 +95,10 @@ export function Header({ config, layout = 'header-logo', tabsPosition: tabsPosit
|
|
|
95
95
|
return resolveNavigation(config, pathname);
|
|
96
96
|
}, [config, pathname, hasTabs, layout, hasMultipleLanguages]);
|
|
97
97
|
|
|
98
|
-
// Simple check if current path belongs to a tab (without full navigation resolution)
|
|
99
|
-
|
|
98
|
+
// Simple check if current path belongs to a tab (without full navigation resolution).
|
|
99
|
+
// Pure over its arguments — wrapped in useCallback so it's stable for the
|
|
100
|
+
// tab list useMemo below.
|
|
101
|
+
const pathBelongsToTab = useCallback((tab: typeof navigationTabs[0], currentPath: string): boolean => {
|
|
100
102
|
const path = currentPath.replace(/^\/docs\/?/, '');
|
|
101
103
|
|
|
102
104
|
// Check tab's groups
|
|
@@ -124,7 +126,7 @@ export function Header({ config, layout = 'header-logo', tabsPosition: tabsPosit
|
|
|
124
126
|
}
|
|
125
127
|
|
|
126
128
|
return false;
|
|
127
|
-
};
|
|
129
|
+
}, []);
|
|
128
130
|
|
|
129
131
|
// Build tabs data for header
|
|
130
132
|
const headerTabs = useMemo(() => {
|
|
@@ -175,7 +177,7 @@ export function Header({ config, layout = 'header-logo', tabsPosition: tabsPosit
|
|
|
175
177
|
isExternal: false,
|
|
176
178
|
};
|
|
177
179
|
});
|
|
178
|
-
}, [navigationTabs, resolved, hasTabs, pathname, linkPrefix]);
|
|
180
|
+
}, [navigationTabs, resolved, hasTabs, pathname, linkPrefix, pathBelongsToTab]);
|
|
179
181
|
|
|
180
182
|
// Detect dark mode for logo switching
|
|
181
183
|
useEffect(() => {
|
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
saveLanguagePreference,
|
|
12
12
|
getLanguagePreference,
|
|
13
13
|
extractLanguageFromPath,
|
|
14
|
+
toHreflang,
|
|
14
15
|
} from '@/lib/language-utils';
|
|
15
16
|
import { useLinkPrefix } from '@/lib/link-prefix-context';
|
|
16
17
|
import { getUiStrings } from '@/lib/ui-strings';
|
|
@@ -51,13 +52,36 @@ export function LanguageSelector({
|
|
|
51
52
|
const isNavigating = pendingPathname !== null;
|
|
52
53
|
const showSpinner = spinnerPathname !== null;
|
|
53
54
|
|
|
55
|
+
// Dedupe entries that collapse onto the same BCP 47 tag (e.g. customer
|
|
56
|
+
// declared both `cn` and `zh-Hans`, which would render two identical
|
|
57
|
+
// "简体中文" rows). Prefer the entry whose code already IS the BCP 47 form;
|
|
58
|
+
// fall back to the first occurrence. The server-side warning in
|
|
59
|
+
// buildHreflangAlternates is the authoritative signal — this is the UX
|
|
60
|
+
// safety net so users don't see duplicate rows in the meantime.
|
|
61
|
+
const displayedLanguages = (() => {
|
|
62
|
+
const byTag = new Map<string, ResolvedLanguage>();
|
|
63
|
+
for (const lang of languages) {
|
|
64
|
+
const tag = toHreflang(lang.code);
|
|
65
|
+
const existing = byTag.get(tag);
|
|
66
|
+
if (!existing) {
|
|
67
|
+
byTag.set(tag, lang);
|
|
68
|
+
continue;
|
|
69
|
+
}
|
|
70
|
+
if (existing.code !== tag && lang.code === tag) byTag.set(tag, lang);
|
|
71
|
+
}
|
|
72
|
+
return [...byTag.values()];
|
|
73
|
+
})();
|
|
74
|
+
|
|
54
75
|
// Find current language
|
|
55
|
-
const currentLanguage =
|
|
76
|
+
const currentLanguage = displayedLanguages.find((l) => l.isActive) || displayedLanguages[0];
|
|
56
77
|
|
|
57
78
|
// Find actual default language from config
|
|
58
|
-
const actualDefault =
|
|
79
|
+
const actualDefault = displayedLanguages.find((l) => l.isDefault)?.code || defaultLanguage;
|
|
59
80
|
|
|
60
|
-
// Check localStorage preference on mount and redirect if needed
|
|
81
|
+
// Check localStorage preference on mount and redirect if needed.
|
|
82
|
+
// Intentionally mount-only: re-running on pathname/language changes would
|
|
83
|
+
// fight the user's in-session navigation choices (they may have explicitly
|
|
84
|
+
// switched languages this session, which we don't want to revert).
|
|
61
85
|
useEffect(() => {
|
|
62
86
|
const savedPref = getLanguagePreference();
|
|
63
87
|
if (savedPref && currentLanguage && savedPref !== currentLanguage.code) {
|
|
@@ -68,18 +92,20 @@ export function LanguageSelector({
|
|
|
68
92
|
if (!hasLangInPath) {
|
|
69
93
|
const basePath = transformLanguagePath(
|
|
70
94
|
pathname || '/docs',
|
|
71
|
-
actualDefault,
|
|
72
95
|
savedPref,
|
|
73
96
|
actualDefault
|
|
74
97
|
);
|
|
75
98
|
router.replace(`${linkPrefix}${basePath}`);
|
|
76
99
|
}
|
|
77
100
|
}
|
|
78
|
-
|
|
101
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps -- mount-only by design; see comment above the effect
|
|
102
|
+
}, []);
|
|
79
103
|
|
|
80
104
|
// Handle click outside to close
|
|
81
105
|
useOnClickOutside(containerRef, () => setIsOpen(false), isOpen);
|
|
82
106
|
|
|
107
|
+
// Defined before handleKeyDown so handleKeyDown can include it in its
|
|
108
|
+
// dependency array without TDZ issues.
|
|
83
109
|
const handleSelectLanguage = useCallback(
|
|
84
110
|
(lang: ResolvedLanguage) => {
|
|
85
111
|
// Block double-fire while a previous switch is still navigating —
|
|
@@ -95,7 +121,6 @@ export function LanguageSelector({
|
|
|
95
121
|
|
|
96
122
|
const basePath = transformLanguagePath(
|
|
97
123
|
pathname || '/docs',
|
|
98
|
-
currentLanguage?.code || actualDefault,
|
|
99
124
|
lang.code,
|
|
100
125
|
actualDefault
|
|
101
126
|
);
|
|
@@ -228,7 +253,7 @@ export function LanguageSelector({
|
|
|
228
253
|
`}
|
|
229
254
|
style={{ boxShadow: 'var(--shadow-lg)' }}
|
|
230
255
|
>
|
|
231
|
-
{
|
|
256
|
+
{displayedLanguages.map((lang, index) => (
|
|
232
257
|
<li key={lang.code}>
|
|
233
258
|
<button
|
|
234
259
|
role="option"
|
|
@@ -494,7 +494,7 @@ export function TableOfContents({ content, className = '' }: TableOfContentsProp
|
|
|
494
494
|
return () => {
|
|
495
495
|
scrollTarget.removeEventListener('scroll', computeActive);
|
|
496
496
|
};
|
|
497
|
-
|
|
497
|
+
|
|
498
498
|
}, [headings, isHidden]);
|
|
499
499
|
|
|
500
500
|
if (headings.length === 0 || isHidden) {
|
|
@@ -4,7 +4,12 @@ import { useMemo } from 'react';
|
|
|
4
4
|
import Link from 'next/link';
|
|
5
5
|
import { usePathname } from 'next/navigation';
|
|
6
6
|
// Icons use Font Awesome CSS classes for lightweight rendering
|
|
7
|
-
import type {
|
|
7
|
+
import type {
|
|
8
|
+
DocsConfig,
|
|
9
|
+
GroupConfig,
|
|
10
|
+
NavigationPage,
|
|
11
|
+
TabsPosition,
|
|
12
|
+
} from '@/lib/docs-types';
|
|
8
13
|
import { resolveNavigation } from '@/lib/navigation-resolver';
|
|
9
14
|
import { getIconClass } from '@/lib/icon-utils';
|
|
10
15
|
import { getTheme } from '@/themes';
|
|
@@ -87,14 +92,21 @@ export function TabsNav({ config, className = '' }: TabsNavProps) {
|
|
|
87
92
|
const currentPath = pathname.replace(/^\/docs\/?/, '').replace(/^\//, '');
|
|
88
93
|
let isActive = false;
|
|
89
94
|
|
|
90
|
-
const checkPages = (pages:
|
|
95
|
+
const checkPages = (pages: (NavigationPage | GroupConfig)[]): boolean => {
|
|
91
96
|
for (const page of pages) {
|
|
92
|
-
const pagePath =
|
|
93
|
-
|
|
97
|
+
const pagePath =
|
|
98
|
+
typeof page === 'string'
|
|
99
|
+
? page
|
|
100
|
+
: 'page' in page
|
|
101
|
+
? page.page
|
|
102
|
+
: 'group' in page
|
|
103
|
+
? page.group
|
|
104
|
+
: undefined;
|
|
105
|
+
if (pagePath && (pagePath === currentPath || currentPath.startsWith(pagePath + '/'))) {
|
|
94
106
|
return true;
|
|
95
107
|
}
|
|
96
108
|
// Check nested groups
|
|
97
|
-
if (page.pages) {
|
|
109
|
+
if (typeof page !== 'string' && 'pages' in page && page.pages) {
|
|
98
110
|
if (checkPages(page.pages)) return true;
|
|
99
111
|
}
|
|
100
112
|
}
|