radiant-docs 0.1.61 → 0.1.62
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/package.json +1 -1
- package/template/package-lock.json +10 -4
- package/template/package.json +11 -2
- package/template/scripts/generate-proxy-allowed-origins.mjs +14 -6
- package/template/scripts/publish-shiki-platform-assets.mjs +1151 -0
- package/template/src/components/Header.astro +6 -1
- package/template/src/components/NavigationTabList.astro +65 -0
- package/template/src/components/NavigationTabs.astro +109 -0
- package/template/src/components/OpenApiPage.astro +17 -1
- package/template/src/components/Sidebar.astro +2 -2
- package/template/src/components/SidebarDropdown.astro +105 -44
- package/template/src/components/SidebarMenu.astro +3 -0
- package/template/src/components/SidebarSegmented.astro +87 -52
- package/template/src/components/SidebarTabs.astro +86 -0
- package/template/src/components/chat/AssistantDocsWidget.tsx +127 -2
- package/template/src/components/chat/AssistantEmbedPanel.tsx +269 -283
- package/template/src/components/user/Accordion.astro +1 -1
- package/template/src/components/user/Callout.astro +2 -2
- package/template/src/components/user/CodeBlock.astro +58 -7
- package/template/src/components/user/CodeGroup.astro +52 -1
- package/template/src/components/user/Column.astro +1 -1
- package/template/src/components/user/Step.astro +1 -1
- package/template/src/components/user/Tabs.astro +1 -1
- package/template/src/generated/shiki-platform-assets.json +24 -0
- package/template/src/layouts/Layout.astro +111 -8
- package/template/src/lib/assistant-panel-config.ts +59 -0
- package/template/src/lib/assistant-shiki-client.ts +506 -0
- package/template/src/lib/mdx/remark-resolve-internal-links.ts +334 -17
- package/template/src/lib/routes.ts +66 -24
- package/template/src/styles/global.css +12 -0
|
@@ -9,10 +9,12 @@ import {
|
|
|
9
9
|
resolveDocsHref,
|
|
10
10
|
type DocsConfig,
|
|
11
11
|
type NavGroup,
|
|
12
|
+
type NavMenu,
|
|
12
13
|
type NavMenuItem,
|
|
13
14
|
type NavOpenApi,
|
|
14
15
|
type NavOpenApiPage,
|
|
15
16
|
type NavPage,
|
|
17
|
+
type NavTabItem,
|
|
16
18
|
} from "../validation";
|
|
17
19
|
import { prependBasePath, withBasePath } from "../base-path";
|
|
18
20
|
import {
|
|
@@ -26,6 +28,11 @@ import {
|
|
|
26
28
|
} from "../utils";
|
|
27
29
|
|
|
28
30
|
type MdxNavItem = string | NavPage | NavGroup | NavOpenApiPage;
|
|
31
|
+
type NavigationContentContainer = {
|
|
32
|
+
pages?: MdxNavItem[];
|
|
33
|
+
menu?: NavMenu;
|
|
34
|
+
openapi?: string | NavOpenApi;
|
|
35
|
+
};
|
|
29
36
|
|
|
30
37
|
type AuxiliaryPageRef = {
|
|
31
38
|
filePath: string;
|
|
@@ -36,9 +43,24 @@ type DocsConfigWithAuxiliaryRefs = DocsConfig & {
|
|
|
36
43
|
auxiliaryPageRefs?: AuxiliaryPageRef[];
|
|
37
44
|
};
|
|
38
45
|
|
|
46
|
+
type OpenApiEndpointLinkRef = {
|
|
47
|
+
source?: string;
|
|
48
|
+
method: string;
|
|
49
|
+
path: string;
|
|
50
|
+
endpoint: string;
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
type OpenApiEndpointRouteTarget = OpenApiEndpointLinkRef & {
|
|
54
|
+
source: string;
|
|
55
|
+
targetKey: string;
|
|
56
|
+
};
|
|
57
|
+
|
|
39
58
|
type ResolvedRouteIndex = {
|
|
40
59
|
canonicalHrefByFilePath: Map<string, string>;
|
|
41
60
|
allHrefsByFilePath: Map<string, string[]>;
|
|
61
|
+
canonicalHrefByOpenApiEndpointKey: Map<string, string>;
|
|
62
|
+
allHrefsByOpenApiEndpointKey: Map<string, string[]>;
|
|
63
|
+
openApiEndpointTargetsByEndpoint: Map<string, OpenApiEndpointRouteTarget[]>;
|
|
42
64
|
validRoutePaths: Set<string>;
|
|
43
65
|
allDocsFilePaths: Set<string>;
|
|
44
66
|
routeSlugs: ReturnType<typeof createRouteSlugRegistry>;
|
|
@@ -76,10 +98,24 @@ const HTTP_METHODS = [
|
|
|
76
98
|
"options",
|
|
77
99
|
"trace",
|
|
78
100
|
];
|
|
101
|
+
const HTTP_METHOD_SET = new Set<string>(HTTP_METHODS);
|
|
79
102
|
|
|
80
103
|
let cachedRouteIndex: ResolvedRouteIndex | null = null;
|
|
81
104
|
let cachedConfig: DocsConfig | null = null;
|
|
82
105
|
|
|
106
|
+
function isUrl(value: string): boolean {
|
|
107
|
+
try {
|
|
108
|
+
const url = new URL(value);
|
|
109
|
+
return url.protocol === "http:" || url.protocol === "https:";
|
|
110
|
+
} catch {
|
|
111
|
+
return false;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function containsPathTraversal(value: string): boolean {
|
|
116
|
+
return value.replace(/\\/g, "/").split("/").includes("..");
|
|
117
|
+
}
|
|
118
|
+
|
|
83
119
|
function normalizeDocsFilePath(value: string): string {
|
|
84
120
|
let normalized = value.replace(/\\/g, "/").trim();
|
|
85
121
|
if (!normalized) return "";
|
|
@@ -94,6 +130,102 @@ function normalizeDocsFilePath(value: string): string {
|
|
|
94
130
|
return posixNormalized === "." ? "" : posixNormalized;
|
|
95
131
|
}
|
|
96
132
|
|
|
133
|
+
function normalizeOpenApiLinkSource(source: string): string {
|
|
134
|
+
const trimmedSource = source.trim();
|
|
135
|
+
if (isUrl(trimmedSource)) {
|
|
136
|
+
return trimmedSource;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
let normalizedSource = trimmedSource.replace(/\\/g, "/").trim();
|
|
140
|
+
normalizedSource = normalizedSource.replace(/^\/+/, "").replace(/\/+$/, "");
|
|
141
|
+
normalizedSource = normalizedSource.replace(/^src\/content\/docs\//, "");
|
|
142
|
+
|
|
143
|
+
const posixNormalized = path.posix
|
|
144
|
+
.normalize(`/${normalizedSource}`)
|
|
145
|
+
.replace(/^\/+/, "");
|
|
146
|
+
|
|
147
|
+
return posixNormalized === "." ? "" : posixNormalized;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function formatOpenApiEndpoint(
|
|
151
|
+
method: string,
|
|
152
|
+
endpointPath: string,
|
|
153
|
+
): string {
|
|
154
|
+
return `${method.toUpperCase()} ${endpointPath.toLowerCase()}`;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
function formatOpenApiEndpointTargetKey(
|
|
158
|
+
source: string,
|
|
159
|
+
method: string,
|
|
160
|
+
endpointPath: string,
|
|
161
|
+
): string {
|
|
162
|
+
return `${normalizeOpenApiLinkSource(source)}\0${formatOpenApiEndpoint(
|
|
163
|
+
method,
|
|
164
|
+
endpointPath,
|
|
165
|
+
)}`;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
function parseOpenApiEndpointLinkEndpoint(
|
|
169
|
+
endpointStr: string,
|
|
170
|
+
): Pick<OpenApiEndpointLinkRef, "method" | "path" | "endpoint"> | null {
|
|
171
|
+
const trimmed = endpointStr.trim();
|
|
172
|
+
const parts = trimmed.split(/\s+/);
|
|
173
|
+
|
|
174
|
+
if (parts.length !== 2) {
|
|
175
|
+
return null;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
const method = parts[0].toUpperCase();
|
|
179
|
+
const endpointPath = parts[1];
|
|
180
|
+
if (
|
|
181
|
+
!HTTP_METHOD_SET.has(method.toLowerCase()) ||
|
|
182
|
+
!endpointPath.startsWith("/")
|
|
183
|
+
) {
|
|
184
|
+
return null;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
const normalizedPath = endpointPath.toLowerCase();
|
|
188
|
+
return {
|
|
189
|
+
method,
|
|
190
|
+
path: normalizedPath,
|
|
191
|
+
endpoint: formatOpenApiEndpoint(method, normalizedPath),
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
function parseOpenApiEndpointLinkHref(
|
|
196
|
+
href: string,
|
|
197
|
+
): OpenApiEndpointLinkRef | null {
|
|
198
|
+
const trimmedHref = href.trim();
|
|
199
|
+
if (!trimmedHref) return null;
|
|
200
|
+
|
|
201
|
+
const hashIndex = trimmedHref.indexOf("#");
|
|
202
|
+
if (hashIndex > 0) {
|
|
203
|
+
const rawSource = trimmedHref.slice(0, hashIndex).trim();
|
|
204
|
+
if (!isUrl(rawSource) && containsPathTraversal(rawSource)) {
|
|
205
|
+
return null;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
const source = normalizeOpenApiLinkSource(rawSource);
|
|
209
|
+
if (!source) {
|
|
210
|
+
return null;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
const parsedEndpoint = parseOpenApiEndpointLinkEndpoint(
|
|
214
|
+
trimmedHref.slice(hashIndex + 1),
|
|
215
|
+
);
|
|
216
|
+
if (!parsedEndpoint) {
|
|
217
|
+
return null;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
return {
|
|
221
|
+
source,
|
|
222
|
+
...parsedEndpoint,
|
|
223
|
+
};
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
return parseOpenApiEndpointLinkEndpoint(trimmedHref);
|
|
227
|
+
}
|
|
228
|
+
|
|
97
229
|
function normalizeRoutePath(value: string): string {
|
|
98
230
|
const trimmed = value.trim();
|
|
99
231
|
if (!trimmed || trimmed === "/") {
|
|
@@ -208,6 +340,38 @@ function addMdxRoute(args: {
|
|
|
208
340
|
args.index.allHrefsByFilePath.set(normalizedFilePath, aliases);
|
|
209
341
|
}
|
|
210
342
|
|
|
343
|
+
function createOpenApiEndpointRouteTarget(args: {
|
|
344
|
+
source: string;
|
|
345
|
+
method: string;
|
|
346
|
+
endpointPath: string;
|
|
347
|
+
}): OpenApiEndpointRouteTarget {
|
|
348
|
+
const source = normalizeOpenApiLinkSource(args.source);
|
|
349
|
+
const endpoint = formatOpenApiEndpoint(args.method, args.endpointPath);
|
|
350
|
+
|
|
351
|
+
return {
|
|
352
|
+
source,
|
|
353
|
+
method: args.method.toUpperCase(),
|
|
354
|
+
path: args.endpointPath.toLowerCase(),
|
|
355
|
+
endpoint,
|
|
356
|
+
targetKey: formatOpenApiEndpointTargetKey(
|
|
357
|
+
source,
|
|
358
|
+
args.method,
|
|
359
|
+
args.endpointPath,
|
|
360
|
+
),
|
|
361
|
+
};
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
function addOpenApiEndpointTargetToMap(
|
|
365
|
+
map: Map<string, OpenApiEndpointRouteTarget[]>,
|
|
366
|
+
target: OpenApiEndpointRouteTarget,
|
|
367
|
+
): void {
|
|
368
|
+
const targets = map.get(target.endpoint) ?? [];
|
|
369
|
+
if (!targets.some((candidate) => candidate.targetKey === target.targetKey)) {
|
|
370
|
+
targets.push(target);
|
|
371
|
+
}
|
|
372
|
+
map.set(target.endpoint, targets);
|
|
373
|
+
}
|
|
374
|
+
|
|
211
375
|
function addOpenApiEndpointRoute(args: {
|
|
212
376
|
index: ResolvedRouteIndex;
|
|
213
377
|
parentSlug: string;
|
|
@@ -232,10 +396,30 @@ function addOpenApiEndpointRoute(args: {
|
|
|
232
396
|
}),
|
|
233
397
|
identity: `openapi:${args.source}:${args.method.toUpperCase()} ${args.endpointPath.toLowerCase()}`,
|
|
234
398
|
}).slug;
|
|
235
|
-
addValidRoutePath(
|
|
399
|
+
const href = addValidRoutePath(
|
|
236
400
|
args.index,
|
|
237
401
|
prependBasePath(`/${registeredSlug}`),
|
|
238
402
|
);
|
|
403
|
+
const target = createOpenApiEndpointRouteTarget({
|
|
404
|
+
source: args.source,
|
|
405
|
+
method: args.method,
|
|
406
|
+
endpointPath: args.endpointPath,
|
|
407
|
+
});
|
|
408
|
+
|
|
409
|
+
if (!args.index.canonicalHrefByOpenApiEndpointKey.has(target.targetKey)) {
|
|
410
|
+
args.index.canonicalHrefByOpenApiEndpointKey.set(target.targetKey, href);
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
const aliases =
|
|
414
|
+
args.index.allHrefsByOpenApiEndpointKey.get(target.targetKey) ?? [];
|
|
415
|
+
if (!aliases.includes(href)) {
|
|
416
|
+
aliases.push(href);
|
|
417
|
+
}
|
|
418
|
+
args.index.allHrefsByOpenApiEndpointKey.set(target.targetKey, aliases);
|
|
419
|
+
addOpenApiEndpointTargetToMap(
|
|
420
|
+
args.index.openApiEndpointTargetsByEndpoint,
|
|
421
|
+
target,
|
|
422
|
+
);
|
|
239
423
|
}
|
|
240
424
|
|
|
241
425
|
function addAuxiliaryPageRef(
|
|
@@ -414,27 +598,69 @@ async function processMenuItems(args: {
|
|
|
414
598
|
index: ResolvedRouteIndex;
|
|
415
599
|
homePath: string | null;
|
|
416
600
|
items: NavMenuItem[];
|
|
601
|
+
parentSlug?: string;
|
|
417
602
|
}): Promise<void> {
|
|
418
603
|
for (const menuItem of args.items) {
|
|
419
604
|
const menuSlug = slugify(menuItem.label);
|
|
420
|
-
const parentSlug =
|
|
605
|
+
const parentSlug = args.parentSlug
|
|
606
|
+
? `${args.parentSlug}/${menuSlug}`
|
|
607
|
+
: menuSlug;
|
|
608
|
+
|
|
609
|
+
await processNavigationContent({
|
|
610
|
+
index: args.index,
|
|
611
|
+
parentSlug,
|
|
612
|
+
homePath: args.homePath,
|
|
613
|
+
item: menuItem,
|
|
614
|
+
});
|
|
615
|
+
}
|
|
616
|
+
}
|
|
421
617
|
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
618
|
+
async function processTabItems(args: {
|
|
619
|
+
index: ResolvedRouteIndex;
|
|
620
|
+
homePath: string | null;
|
|
621
|
+
items: NavTabItem[];
|
|
622
|
+
}): Promise<void> {
|
|
623
|
+
for (const tabItem of args.items) {
|
|
624
|
+
const tabSlug = slugify(tabItem.slug || tabItem.label);
|
|
625
|
+
await processNavigationContent({
|
|
626
|
+
index: args.index,
|
|
627
|
+
parentSlug: tabSlug,
|
|
628
|
+
homePath: args.homePath,
|
|
629
|
+
item: tabItem,
|
|
630
|
+
});
|
|
631
|
+
}
|
|
632
|
+
}
|
|
430
633
|
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
634
|
+
async function processNavigationContent(args: {
|
|
635
|
+
index: ResolvedRouteIndex;
|
|
636
|
+
parentSlug: string;
|
|
637
|
+
homePath: string | null;
|
|
638
|
+
item: NavigationContentContainer;
|
|
639
|
+
}): Promise<void> {
|
|
640
|
+
if (Array.isArray(args.item.pages)) {
|
|
641
|
+
await processPages({
|
|
642
|
+
index: args.index,
|
|
643
|
+
parentSlug: args.parentSlug,
|
|
644
|
+
homePath: args.homePath,
|
|
645
|
+
items: args.item.pages as MdxNavItem[],
|
|
646
|
+
});
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
if (args.item.menu?.items) {
|
|
650
|
+
await processMenuItems({
|
|
651
|
+
index: args.index,
|
|
652
|
+
parentSlug: args.parentSlug,
|
|
653
|
+
homePath: args.homePath,
|
|
654
|
+
items: args.item.menu.items,
|
|
655
|
+
});
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
if (args.item.openapi) {
|
|
659
|
+
await processOpenApiFile({
|
|
660
|
+
index: args.index,
|
|
661
|
+
parentSlug: args.parentSlug,
|
|
662
|
+
openApiPathOrConfig: args.item.openapi,
|
|
663
|
+
});
|
|
438
664
|
}
|
|
439
665
|
}
|
|
440
666
|
|
|
@@ -444,6 +670,12 @@ async function buildRouteIndex(
|
|
|
444
670
|
const index: ResolvedRouteIndex = {
|
|
445
671
|
canonicalHrefByFilePath: new Map<string, string>(),
|
|
446
672
|
allHrefsByFilePath: new Map<string, string[]>(),
|
|
673
|
+
canonicalHrefByOpenApiEndpointKey: new Map<string, string>(),
|
|
674
|
+
allHrefsByOpenApiEndpointKey: new Map<string, string[]>(),
|
|
675
|
+
openApiEndpointTargetsByEndpoint: new Map<
|
|
676
|
+
string,
|
|
677
|
+
OpenApiEndpointRouteTarget[]
|
|
678
|
+
>(),
|
|
447
679
|
validRoutePaths: new Set<string>(),
|
|
448
680
|
allDocsFilePaths: new Set<string>(),
|
|
449
681
|
routeSlugs: createRouteSlugRegistry(),
|
|
@@ -473,6 +705,14 @@ async function buildRouteIndex(
|
|
|
473
705
|
});
|
|
474
706
|
}
|
|
475
707
|
|
|
708
|
+
if (config.navigation.tabs?.items) {
|
|
709
|
+
await processTabItems({
|
|
710
|
+
index,
|
|
711
|
+
homePath,
|
|
712
|
+
items: config.navigation.tabs.items,
|
|
713
|
+
});
|
|
714
|
+
}
|
|
715
|
+
|
|
476
716
|
const rootOpenApi = (config.navigation as any).openapi;
|
|
477
717
|
if (
|
|
478
718
|
typeof rootOpenApi === "string" ||
|
|
@@ -527,12 +767,89 @@ function buildInternalLinkRewriteError(args: {
|
|
|
527
767
|
);
|
|
528
768
|
}
|
|
529
769
|
|
|
770
|
+
function formatOpenApiEndpointSourceList(
|
|
771
|
+
targets: OpenApiEndpointRouteTarget[],
|
|
772
|
+
): string {
|
|
773
|
+
const sources = [...new Set(targets.map((target) => target.source))];
|
|
774
|
+
return sources.map((source) => `"${source}"`).join(", ");
|
|
775
|
+
}
|
|
776
|
+
|
|
777
|
+
function resolveOpenApiEndpointLinkToCanonicalHref(args: {
|
|
778
|
+
rawHref: string;
|
|
779
|
+
endpointLink: OpenApiEndpointLinkRef;
|
|
780
|
+
currentDocFilePath: string | null;
|
|
781
|
+
routeIndex: ResolvedRouteIndex;
|
|
782
|
+
warn: (message: string) => void;
|
|
783
|
+
}): string {
|
|
784
|
+
const target =
|
|
785
|
+
args.endpointLink.source !== undefined
|
|
786
|
+
? {
|
|
787
|
+
source: args.endpointLink.source,
|
|
788
|
+
targetKey: formatOpenApiEndpointTargetKey(
|
|
789
|
+
args.endpointLink.source,
|
|
790
|
+
args.endpointLink.method,
|
|
791
|
+
args.endpointLink.path,
|
|
792
|
+
),
|
|
793
|
+
}
|
|
794
|
+
: (() => {
|
|
795
|
+
const targets =
|
|
796
|
+
args.routeIndex.openApiEndpointTargetsByEndpoint.get(
|
|
797
|
+
args.endpointLink.endpoint,
|
|
798
|
+
) ?? [];
|
|
799
|
+
if (targets.length === 1) {
|
|
800
|
+
return targets[0];
|
|
801
|
+
}
|
|
802
|
+
|
|
803
|
+
throw buildInternalLinkRewriteError({
|
|
804
|
+
rawHref: args.rawHref,
|
|
805
|
+
currentDocFilePath: args.currentDocFilePath,
|
|
806
|
+
detail:
|
|
807
|
+
targets.length > 1
|
|
808
|
+
? `The endpoint "${args.endpointLink.endpoint}" is ambiguous across OpenAPI sources (${formatOpenApiEndpointSourceList(
|
|
809
|
+
targets,
|
|
810
|
+
)}).`
|
|
811
|
+
: `No generated OpenAPI endpoint route matched "${args.endpointLink.endpoint}".`,
|
|
812
|
+
});
|
|
813
|
+
})();
|
|
814
|
+
|
|
815
|
+
const canonicalHref =
|
|
816
|
+
args.routeIndex.canonicalHrefByOpenApiEndpointKey.get(target.targetKey);
|
|
817
|
+
if (!canonicalHref) {
|
|
818
|
+
throw buildInternalLinkRewriteError({
|
|
819
|
+
rawHref: args.rawHref,
|
|
820
|
+
currentDocFilePath: args.currentDocFilePath,
|
|
821
|
+
detail: `The OpenAPI endpoint "${args.endpointLink.endpoint}" from "${target.source}" did not resolve to a canonical route.`,
|
|
822
|
+
});
|
|
823
|
+
}
|
|
824
|
+
|
|
825
|
+
const aliases =
|
|
826
|
+
args.routeIndex.allHrefsByOpenApiEndpointKey.get(target.targetKey) ?? [];
|
|
827
|
+
if (aliases.length > 1) {
|
|
828
|
+
args.warn(
|
|
829
|
+
`[INTERNAL_LINK_WARNING] "${args.rawHref}" matched OpenAPI endpoint "${args.endpointLink.endpoint}" in "${target.source}", which has multiple URLs (${aliases.join(", ")}). Rewriting to canonical URL "${canonicalHref}".`,
|
|
830
|
+
);
|
|
831
|
+
}
|
|
832
|
+
|
|
833
|
+
return withBasePath(canonicalHref);
|
|
834
|
+
}
|
|
835
|
+
|
|
530
836
|
function resolveLinkToCanonicalHref(args: {
|
|
531
837
|
rawHref: string;
|
|
532
838
|
currentDocFilePath: string | null;
|
|
533
839
|
routeIndex: ResolvedRouteIndex;
|
|
534
840
|
warn: (message: string) => void;
|
|
535
841
|
}): string | null {
|
|
842
|
+
const endpointLink = parseOpenApiEndpointLinkHref(args.rawHref);
|
|
843
|
+
if (endpointLink) {
|
|
844
|
+
return resolveOpenApiEndpointLinkToCanonicalHref({
|
|
845
|
+
rawHref: args.rawHref,
|
|
846
|
+
endpointLink,
|
|
847
|
+
currentDocFilePath: args.currentDocFilePath,
|
|
848
|
+
routeIndex: args.routeIndex,
|
|
849
|
+
warn: args.warn,
|
|
850
|
+
});
|
|
851
|
+
}
|
|
852
|
+
|
|
536
853
|
const resolvedHref = resolveDocsHref(args.rawHref);
|
|
537
854
|
if (resolvedHref.kind === "ignored" || resolvedHref.kind === "local-asset") {
|
|
538
855
|
return null;
|
|
@@ -3,7 +3,9 @@ import {
|
|
|
3
3
|
type NavGroup,
|
|
4
4
|
type NavPage,
|
|
5
5
|
type NavOpenApiPage,
|
|
6
|
+
type NavMenu,
|
|
6
7
|
type NavMenuItem,
|
|
8
|
+
type NavTabItem,
|
|
7
9
|
type NavOpenApi,
|
|
8
10
|
type DocsConfig,
|
|
9
11
|
loadOpenApiSpec,
|
|
@@ -22,6 +24,11 @@ import { prependBasePath, withBasePath } from "./base-path";
|
|
|
22
24
|
import { getCollection } from "astro:content";
|
|
23
25
|
|
|
24
26
|
type MdxNavPageItem = string | NavPage;
|
|
27
|
+
type NavigationContentContainer = {
|
|
28
|
+
pages?: (string | NavPage | NavGroup | NavOpenApiPage)[];
|
|
29
|
+
menu?: NavMenu;
|
|
30
|
+
openapi?: string | NavOpenApi;
|
|
31
|
+
};
|
|
25
32
|
|
|
26
33
|
// Base route interface
|
|
27
34
|
export interface BaseRoute {
|
|
@@ -420,30 +427,66 @@ async function processMenuItem(
|
|
|
420
427
|
? `${parentSlug}/${menuItemSlug}`
|
|
421
428
|
: menuItemSlug;
|
|
422
429
|
|
|
423
|
-
|
|
430
|
+
routes = routes.concat(
|
|
431
|
+
await processNavigationContent(menuItem, currentPrefix, docs),
|
|
432
|
+
);
|
|
424
433
|
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
434
|
+
return routes;
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
async function processMenu(
|
|
438
|
+
menu: NavMenu,
|
|
439
|
+
parentSlug: string = "",
|
|
440
|
+
docs: any[],
|
|
441
|
+
): Promise<Route[]> {
|
|
442
|
+
let routes: Route[] = [];
|
|
443
|
+
|
|
444
|
+
for (const menuItem of menu.items) {
|
|
445
|
+
const menuItemRoutes = await processMenuItem(menuItem, parentSlug, docs);
|
|
446
|
+
routes = routes.concat(menuItemRoutes);
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
return routes;
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
async function processTabItem(
|
|
453
|
+
tabItem: NavTabItem,
|
|
454
|
+
parentSlug: string = "",
|
|
455
|
+
docs: any[],
|
|
456
|
+
): Promise<Route[]> {
|
|
457
|
+
const tabSlug = slugify(tabItem.slug || tabItem.label);
|
|
458
|
+
const currentPrefix = parentSlug ? `${parentSlug}/${tabSlug}` : tabSlug;
|
|
459
|
+
|
|
460
|
+
return processNavigationContent(tabItem, currentPrefix, docs);
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
async function processNavigationContent(
|
|
464
|
+
item: NavigationContentContainer,
|
|
465
|
+
parentSlug: string,
|
|
466
|
+
docs: any[],
|
|
467
|
+
): Promise<Route[]> {
|
|
468
|
+
let routes: Route[] = [];
|
|
469
|
+
|
|
470
|
+
if (item.pages) {
|
|
471
|
+
for (const pageItem of item.pages) {
|
|
472
|
+
if (typeof pageItem === "string" || "page" in pageItem) {
|
|
473
|
+
routes.push(processPageItem(pageItem, parentSlug, docs));
|
|
474
|
+
} else if ("group" in pageItem) {
|
|
431
475
|
routes = routes.concat(
|
|
432
|
-
await processGroup(
|
|
476
|
+
await processGroup(pageItem as NavGroup, parentSlug, docs),
|
|
433
477
|
);
|
|
434
|
-
} else if ("openapi" in
|
|
435
|
-
routes.push(await processOpenApiPageItem(
|
|
478
|
+
} else if ("openapi" in pageItem) {
|
|
479
|
+
routes.push(await processOpenApiPageItem(pageItem, parentSlug));
|
|
436
480
|
}
|
|
437
481
|
}
|
|
438
482
|
}
|
|
439
483
|
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
);
|
|
446
|
-
routes = routes.concat(openApiRoutes);
|
|
484
|
+
if (item.menu) {
|
|
485
|
+
routes = routes.concat(await processMenu(item.menu, parentSlug, docs));
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
if (item.openapi) {
|
|
489
|
+
routes = routes.concat(await processOpenApiFile(item.openapi, parentSlug));
|
|
447
490
|
}
|
|
448
491
|
|
|
449
492
|
return routes;
|
|
@@ -483,7 +526,7 @@ export async function getAllRoutes(): Promise<Route[]> {
|
|
|
483
526
|
|
|
484
527
|
let allRoutes: Route[] = [];
|
|
485
528
|
|
|
486
|
-
// 3. Identify the active navigation container
|
|
529
|
+
// 3. Identify the active navigation container
|
|
487
530
|
// We use the XOR guarantee from validateNavigation (only one key exists)
|
|
488
531
|
if (navigation.pages) {
|
|
489
532
|
// Case 1: Pages array at the top level (can contain pages and groups)
|
|
@@ -499,15 +542,14 @@ export async function getAllRoutes(): Promise<Route[]> {
|
|
|
499
542
|
}
|
|
500
543
|
}
|
|
501
544
|
} else if (navigation.menu) {
|
|
502
|
-
|
|
503
|
-
// Need to await all menu items since processMenuItem is now async
|
|
504
|
-
for (const menuItem of navigation.menu.items) {
|
|
505
|
-
const menuItemRoutes = await processMenuItem(menuItem, "", docs);
|
|
506
|
-
allRoutes = allRoutes.concat(menuItemRoutes);
|
|
507
|
-
}
|
|
545
|
+
allRoutes = allRoutes.concat(await processMenu(navigation.menu, "", docs));
|
|
508
546
|
} else if (navigation.openapi) {
|
|
509
547
|
const openApiRoutes = await processOpenApiFile(navigation.openapi, "");
|
|
510
548
|
allRoutes = allRoutes.concat(openApiRoutes);
|
|
549
|
+
} else if (navigation.tabs) {
|
|
550
|
+
for (const tabItem of navigation.tabs.items) {
|
|
551
|
+
allRoutes = allRoutes.concat(await processTabItem(tabItem, "", docs));
|
|
552
|
+
}
|
|
511
553
|
}
|
|
512
554
|
|
|
513
555
|
for (const auxiliaryPageRef of getAuxiliaryPageRefs(config)) {
|
|
@@ -139,6 +139,18 @@
|
|
|
139
139
|
display: none !important;
|
|
140
140
|
}
|
|
141
141
|
|
|
142
|
+
/* Disable route transition animation while preserving Astro client navigation. */
|
|
143
|
+
::view-transition,
|
|
144
|
+
::view-transition-group(*),
|
|
145
|
+
::view-transition-image-pair(*),
|
|
146
|
+
::view-transition-old(*),
|
|
147
|
+
::view-transition-new(*) {
|
|
148
|
+
animation: none !important;
|
|
149
|
+
animation-delay: 0s !important;
|
|
150
|
+
animation-duration: 0s !important;
|
|
151
|
+
mix-blend-mode: normal !important;
|
|
152
|
+
}
|
|
153
|
+
|
|
142
154
|
/* Prose styling */
|
|
143
155
|
.prose-rules {
|
|
144
156
|
@apply prose max-w-none *:my-6 *:first:mt-0 *:last:mb-0 prose-h2:mt-12 prose-h2:mb-2 prose-h2:scroll-mt-24 prose-h3:mt-8 prose-h3:mb-2 prose-h3:scroll-mt-20 prose-headings:font-semibold prose-p:mt-0 prose-p:mb-4 prose-ol:mt-0 prose-ol:mb-5 prose-ul:mt-0 prose-ul:mb-5 prose-a:decoration-(--color-theme) prose-a:decoration-from-font prose-blockquote:border-(--color-theme)/30 dark:prose-blockquote:border-(--color-theme)/30;
|