zudoku 0.3.0-dev.82 → 0.3.0-dev.84
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/cli.js +5 -1
- package/dist/app/demo.js +5 -4
- package/dist/app/demo.js.map +1 -1
- package/dist/app/main.js +3 -1
- package/dist/app/main.js.map +1 -1
- package/dist/app/standalone.js +5 -4
- package/dist/app/standalone.js.map +1 -1
- package/dist/config/validators/ResolvedSidebarSchema.d.ts +18 -0
- package/dist/config/validators/ResolvedSidebarSchema.js +76 -0
- package/dist/config/validators/ResolvedSidebarSchema.js.map +1 -0
- package/dist/config/validators/SidebarSchema.d.ts +177 -0
- package/dist/config/validators/SidebarSchema.js +71 -0
- package/dist/config/validators/SidebarSchema.js.map +1 -0
- package/dist/config/validators/validate.d.ts +411 -59
- package/dist/config/validators/validate.js +22 -4
- package/dist/config/validators/validate.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/lib/components/DevPortal.js +1 -1
- package/dist/lib/components/DevPortal.js.map +1 -1
- package/dist/lib/components/Header.js.map +1 -1
- package/dist/lib/components/Heading.d.ts +1 -1
- package/dist/lib/components/Layout.js +2 -2
- package/dist/lib/components/Layout.js.map +1 -1
- package/dist/lib/components/TopNavigation.js +5 -5
- package/dist/lib/components/TopNavigation.js.map +1 -1
- package/dist/lib/components/context/DevPortalProvider.d.ts +9 -3
- package/dist/lib/components/context/DevPortalProvider.js +11 -23
- package/dist/lib/components/context/DevPortalProvider.js.map +1 -1
- package/dist/lib/components/context/ThemeContext.d.ts +1 -4
- package/dist/lib/components/context/ThemeContext.js +3 -29
- package/dist/lib/components/context/ThemeContext.js.map +1 -1
- package/dist/lib/components/context/ThemeProvider.d.ts +4 -0
- package/dist/lib/components/context/ThemeProvider.js +23 -0
- package/dist/lib/components/context/ThemeProvider.js.map +1 -0
- package/dist/lib/components/navigation/Sidebar.d.ts +1 -0
- package/dist/lib/components/navigation/Sidebar.js +12 -0
- package/dist/lib/components/navigation/Sidebar.js.map +1 -0
- package/dist/lib/components/navigation/SidebarBadge.d.ts +22 -0
- package/dist/lib/components/navigation/SidebarBadge.js +24 -0
- package/dist/lib/components/navigation/SidebarBadge.js.map +1 -0
- package/dist/lib/components/navigation/SidebarCategory.d.ts +5 -0
- package/dist/lib/components/navigation/SidebarCategory.js +33 -0
- package/dist/lib/components/navigation/SidebarCategory.js.map +1 -0
- package/dist/lib/components/navigation/SidebarItem.d.ts +12 -0
- package/dist/lib/components/navigation/SidebarItem.js +42 -0
- package/dist/lib/components/navigation/SidebarItem.js.map +1 -0
- package/dist/lib/components/navigation/{SideNavigationWrapper.d.ts → SidebarWrapper.d.ts} +1 -1
- package/dist/lib/components/navigation/{SideNavigationWrapper.js → SidebarWrapper.js} +2 -2
- package/dist/lib/components/navigation/SidebarWrapper.js.map +1 -0
- package/dist/lib/components/navigation/utils.d.ts +16 -0
- package/dist/lib/components/navigation/utils.js +85 -0
- package/dist/lib/components/navigation/utils.js.map +1 -0
- package/dist/lib/core/DevPortalContext.d.ts +9 -32
- package/dist/lib/core/DevPortalContext.js +8 -5
- package/dist/lib/core/DevPortalContext.js.map +1 -1
- package/dist/lib/core/plugins.d.ts +6 -8
- package/dist/lib/core/plugins.js.map +1 -1
- package/dist/lib/plugins/api-keys/SettingsApiKeys.js +16 -2
- package/dist/lib/plugins/api-keys/SettingsApiKeys.js.map +1 -1
- package/dist/lib/plugins/api-keys/index.js +6 -0
- package/dist/lib/plugins/api-keys/index.js.map +1 -1
- package/dist/lib/plugins/markdown/MdxPage.js +5 -36
- package/dist/lib/plugins/markdown/MdxPage.js.map +1 -1
- package/dist/lib/plugins/markdown/generateRoutes.js +20 -43
- package/dist/lib/plugins/markdown/generateRoutes.js.map +1 -1
- package/dist/lib/plugins/openapi/Sidecar.js +12 -2
- package/dist/lib/plugins/openapi/Sidecar.js.map +1 -1
- package/dist/lib/plugins/openapi/index.js +14 -11
- package/dist/lib/plugins/openapi/index.js.map +1 -1
- package/dist/lib/plugins/openapi/interfaces.d.ts +1 -1
- package/dist/lib/util/useScrollToAnchor.js +31 -17
- package/dist/lib/util/useScrollToAnchor.js.map +1 -1
- package/dist/vite/plugin-sidebar.d.ts +3 -0
- package/dist/vite/plugin-sidebar.js +23 -0
- package/dist/vite/plugin-sidebar.js.map +1 -0
- package/dist/vite/plugin.js +2 -0
- package/dist/vite/plugin.js.map +1 -1
- package/lib/{AuthenticationPlugin-XS0DoAhE.js → AuthenticationPlugin-DgwV0hVu.js} +7 -7
- package/lib/{AuthenticationPlugin-XS0DoAhE.js.map → AuthenticationPlugin-DgwV0hVu.js.map} +1 -1
- package/lib/{CategoryHeading-DCmchnA1.js → CategoryHeading-BWq12Bfa.js} +3 -3
- package/lib/{CategoryHeading-DCmchnA1.js.map → CategoryHeading-BWq12Bfa.js.map} +1 -1
- package/lib/{Combination-C442XfGG.js → Combination-DkycFHkm.js} +4 -4
- package/lib/{Combination-C442XfGG.js.map → Combination-DkycFHkm.js.map} +1 -1
- package/lib/{DevPortalProvider-BWeAysxF.js → DevPortalProvider-CTxoCHIT.js} +375 -417
- package/lib/DevPortalProvider-CTxoCHIT.js.map +1 -0
- package/lib/DeveloperHint-BQSFXH01.js +10 -0
- package/lib/{DeveloperHint-DQVwIery.js.map → DeveloperHint-BQSFXH01.js.map} +1 -1
- package/lib/{Input-3IEt27jb.js → Input-BclXSY0g.js} +5 -5
- package/lib/{Input-3IEt27jb.js.map → Input-BclXSY0g.js.map} +1 -1
- package/lib/{Markdown-QsZ-PHET.js → Markdown-B_Gax7at.js} +1136 -1152
- package/lib/{Markdown-QsZ-PHET.js.map → Markdown-B_Gax7at.js.map} +1 -1
- package/lib/{MdxPage-CA1WmW14.js → MdxPage-Crlr0GmN.js} +67 -81
- package/lib/MdxPage-Crlr0GmN.js.map +1 -0
- package/lib/{OperationList-CHK_erYP.js → OperationList-nQ0bd8TU.js} +8 -8
- package/lib/{OperationList-CHK_erYP.js.map → OperationList-nQ0bd8TU.js.map} +1 -1
- package/lib/Route-CNvxEBnR.js +14 -0
- package/lib/{Route-D70pGn9n.js.map → Route-CNvxEBnR.js.map} +1 -1
- package/lib/{SlotletProvider-B71hNEUL.js → SlotletProvider-CzMAO73_.js} +12 -12
- package/lib/{SlotletProvider-B71hNEUL.js.map → SlotletProvider-CzMAO73_.js.map} +1 -1
- package/lib/Spinner-fF-Xv-gw.js +274 -0
- package/lib/Spinner-fF-Xv-gw.js.map +1 -0
- package/lib/index-7kcHaXD6.js +1771 -0
- package/lib/index-7kcHaXD6.js.map +1 -0
- package/lib/{index-Bl6YeerK.js → index-CgCPw6Jn.js} +1026 -1043
- package/lib/index-CgCPw6Jn.js.map +1 -0
- package/lib/{index-BH-Ub36F.js → index-DkuZvRNP.js} +4 -4
- package/lib/{index-BH-Ub36F.js.map → index-DkuZvRNP.js.map} +1 -1
- package/lib/joinPath-VeNuJa7y.js +8 -0
- package/lib/joinPath-VeNuJa7y.js.map +1 -0
- package/lib/jsx-runtime-B6kdoens.js +635 -0
- package/lib/jsx-runtime-B6kdoens.js.map +1 -0
- package/lib/{AnchorLink-BZcpTwOs.js → utils-CzT_9Tsn.js} +258 -214
- package/lib/utils-CzT_9Tsn.js.map +1 -0
- package/lib/zudoku.auth-clerk.js +1 -1
- package/lib/zudoku.auth-openid.js +2 -2
- package/lib/zudoku.components.js +1229 -1227
- package/lib/zudoku.components.js.map +1 -1
- package/lib/zudoku.plugin-api-keys.js +129 -107
- package/lib/zudoku.plugin-api-keys.js.map +1 -1
- package/lib/zudoku.plugin-custom-page.js +2 -2
- package/lib/zudoku.plugin-markdown.js +21 -38
- package/lib/zudoku.plugin-markdown.js.map +1 -1
- package/lib/zudoku.plugin-openapi.js +8 -6
- package/lib/zudoku.plugin-openapi.js.map +1 -1
- package/package.json +4 -1
- package/src/app/demo.tsx +5 -4
- package/src/app/main.css +2 -2
- package/src/app/main.tsx +3 -1
- package/src/app/standalone.tsx +5 -4
- package/src/lib/components/DevPortal.tsx +1 -1
- package/src/lib/components/Header.tsx +2 -2
- package/src/lib/components/Layout.tsx +2 -2
- package/src/lib/components/TopNavigation.tsx +5 -5
- package/src/lib/components/context/DevPortalProvider.ts +11 -28
- package/src/lib/components/context/ThemeContext.tsx +3 -41
- package/src/lib/components/context/ThemeProvider.tsx +27 -0
- package/src/lib/components/navigation/{SideNavigation.tsx → Sidebar.tsx} +7 -7
- package/src/lib/components/navigation/SidebarBadge.tsx +40 -0
- package/src/lib/components/navigation/SidebarCategory.tsx +105 -0
- package/src/lib/components/navigation/SidebarItem.tsx +96 -0
- package/src/lib/components/navigation/{SideNavigationWrapper.tsx → SidebarWrapper.tsx} +1 -1
- package/src/lib/components/navigation/utils.ts +120 -0
- package/src/lib/core/DevPortalContext.ts +12 -44
- package/src/lib/core/plugins.ts +6 -13
- package/src/lib/plugins/api-keys/SettingsApiKeys.tsx +31 -10
- package/src/lib/plugins/api-keys/index.tsx +10 -0
- package/src/lib/plugins/markdown/MdxPage.tsx +14 -50
- package/src/lib/plugins/markdown/generateRoutes.tsx +29 -57
- package/src/lib/plugins/openapi/Sidecar.tsx +15 -2
- package/src/lib/plugins/openapi/index.tsx +17 -23
- package/src/lib/plugins/openapi/interfaces.ts +1 -1
- package/src/lib/util/useScrollToAnchor.ts +39 -18
- package/dist/lib/components/navigation/SideNavigation.d.ts +0 -1
- package/dist/lib/components/navigation/SideNavigation.js +0 -12
- package/dist/lib/components/navigation/SideNavigation.js.map +0 -1
- package/dist/lib/components/navigation/SideNavigationCategory.d.ts +0 -4
- package/dist/lib/components/navigation/SideNavigationCategory.js +0 -26
- package/dist/lib/components/navigation/SideNavigationCategory.js.map +0 -1
- package/dist/lib/components/navigation/SideNavigationItem.d.ts +0 -9
- package/dist/lib/components/navigation/SideNavigationItem.js +0 -44
- package/dist/lib/components/navigation/SideNavigationItem.js.map +0 -1
- package/dist/lib/components/navigation/SideNavigationWrapper.js.map +0 -1
- package/dist/lib/components/navigation/useNavigationCollapsibleState.d.ts +0 -9
- package/dist/lib/components/navigation/useNavigationCollapsibleState.js +0 -28
- package/dist/lib/components/navigation/useNavigationCollapsibleState.js.map +0 -1
- package/dist/lib/components/navigation/util.d.ts +0 -8
- package/dist/lib/components/navigation/util.js +0 -15
- package/dist/lib/components/navigation/util.js.map +0 -1
- package/dist/lib/plugins/openapi/MethodBadge.d.ts +0 -13
- package/dist/lib/plugins/openapi/MethodBadge.js +0 -26
- package/dist/lib/plugins/openapi/MethodBadge.js.map +0 -1
- package/dist/lib/util/traverseNavigation.d.ts +0 -6
- package/dist/lib/util/traverseNavigation.js +0 -30
- package/dist/lib/util/traverseNavigation.js.map +0 -1
- package/lib/AnchorLink-BZcpTwOs.js.map +0 -1
- package/lib/DevPortalProvider-BWeAysxF.js.map +0 -1
- package/lib/DeveloperHint-DQVwIery.js +0 -10
- package/lib/MdxPage-CA1WmW14.js.map +0 -1
- package/lib/Route-D70pGn9n.js +0 -13
- package/lib/Spinner-Coi7ORUV.js +0 -244
- package/lib/Spinner-Coi7ORUV.js.map +0 -1
- package/lib/index-Bl6YeerK.js.map +0 -1
- package/lib/index-Dt-pU7Vu.js +0 -916
- package/lib/index-Dt-pU7Vu.js.map +0 -1
- package/lib/jsx-runtime-CJBdjYYx.js +0 -1526
- package/lib/jsx-runtime-CJBdjYYx.js.map +0 -1
- package/src/lib/components/navigation/SideNavigationCategory.tsx +0 -72
- package/src/lib/components/navigation/SideNavigationItem.tsx +0 -148
- package/src/lib/components/navigation/useNavigationCollapsibleState.ts +0 -42
- package/src/lib/components/navigation/util.ts +0 -38
- package/src/lib/plugins/openapi/MethodBadge.tsx +0 -36
- package/src/lib/util/traverseNavigation.ts +0 -55
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import { useLocation } from "react-router-dom";
|
|
2
|
+
import type {
|
|
3
|
+
ResolvedSidebarItem,
|
|
4
|
+
ResolvedSidebarItemCategory,
|
|
5
|
+
} from "../../../config/validators/ResolvedSidebarSchema.js";
|
|
6
|
+
import { joinPath } from "../../util/joinPath.js";
|
|
7
|
+
import {
|
|
8
|
+
useDevPortal,
|
|
9
|
+
useTopNavigationItem,
|
|
10
|
+
} from "../context/DevPortalProvider.js";
|
|
11
|
+
|
|
12
|
+
export type TraverseCallback<T> = (
|
|
13
|
+
item: ResolvedSidebarItem,
|
|
14
|
+
parentCategories: ResolvedSidebarItem[],
|
|
15
|
+
) => T | void;
|
|
16
|
+
|
|
17
|
+
export const traverseSidebar = <T>(
|
|
18
|
+
sidebar: ResolvedSidebarItem[],
|
|
19
|
+
callback: TraverseCallback<T>,
|
|
20
|
+
): T | undefined => {
|
|
21
|
+
for (const item of sidebar) {
|
|
22
|
+
const result = traverseSidebarItem(item, callback);
|
|
23
|
+
if (result !== undefined) return result;
|
|
24
|
+
}
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
export const traverseSidebarItem = <T>(
|
|
28
|
+
item: ResolvedSidebarItem,
|
|
29
|
+
callback: TraverseCallback<T>,
|
|
30
|
+
parentCategories: ResolvedSidebarItem[] = [],
|
|
31
|
+
): T | undefined => {
|
|
32
|
+
const result = callback(item, parentCategories);
|
|
33
|
+
if (result !== undefined) return result;
|
|
34
|
+
|
|
35
|
+
if (item.type === "category") {
|
|
36
|
+
for (const child of item.items) {
|
|
37
|
+
const childResult = traverseSidebarItem(child, callback, [
|
|
38
|
+
...parentCategories,
|
|
39
|
+
item,
|
|
40
|
+
]);
|
|
41
|
+
if (childResult !== undefined) return childResult;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
export const useCurrentItem = () => {
|
|
47
|
+
const location = useLocation();
|
|
48
|
+
const topNavItem = useTopNavigationItem();
|
|
49
|
+
const { sidebars } = useDevPortal();
|
|
50
|
+
const currentSidebar = topNavItem?.id ? sidebars[topNavItem.id] : [];
|
|
51
|
+
|
|
52
|
+
return traverseSidebar(currentSidebar, (item) => {
|
|
53
|
+
if (
|
|
54
|
+
item.type === "doc" &&
|
|
55
|
+
joinPath(topNavItem?.id, item.id) === location.pathname
|
|
56
|
+
) {
|
|
57
|
+
return item;
|
|
58
|
+
}
|
|
59
|
+
});
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
export const useIsCategoryOpen = (category: ResolvedSidebarItemCategory) => {
|
|
63
|
+
const location = useLocation();
|
|
64
|
+
const topNavItem = useTopNavigationItem();
|
|
65
|
+
|
|
66
|
+
return traverseSidebarItem(category, (item) => {
|
|
67
|
+
if (item.type === "category" && item.link) {
|
|
68
|
+
const categoryLinkPath = joinPath(topNavItem?.id, item.link.id);
|
|
69
|
+
if (categoryLinkPath === location.pathname) {
|
|
70
|
+
return true;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if (item.type === "doc") {
|
|
75
|
+
const docPath = joinPath(topNavItem?.id, item.id);
|
|
76
|
+
if (docPath === location.pathname) {
|
|
77
|
+
return true;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
});
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
export const usePrevNext = (): {
|
|
84
|
+
prev?: { label: string; id: string };
|
|
85
|
+
next?: { label: string; id: string };
|
|
86
|
+
} => {
|
|
87
|
+
const currentId = useLocation().pathname;
|
|
88
|
+
const { sidebars } = useDevPortal();
|
|
89
|
+
const topNavItem = useTopNavigationItem();
|
|
90
|
+
const currentSidebar = topNavItem?.id ? sidebars[topNavItem.id] : [];
|
|
91
|
+
|
|
92
|
+
let prev;
|
|
93
|
+
let next;
|
|
94
|
+
|
|
95
|
+
let foundCurrent = false;
|
|
96
|
+
|
|
97
|
+
traverseSidebar(currentSidebar, (item) => {
|
|
98
|
+
const itemId =
|
|
99
|
+
item.type === "doc"
|
|
100
|
+
? joinPath(topNavItem?.id, item.id)
|
|
101
|
+
: item.type === "category" && item.link
|
|
102
|
+
? joinPath(topNavItem?.id, item.link.id)
|
|
103
|
+
: undefined;
|
|
104
|
+
|
|
105
|
+
if (!itemId) return;
|
|
106
|
+
|
|
107
|
+
if (foundCurrent) {
|
|
108
|
+
next = { label: item.label, id: itemId };
|
|
109
|
+
return true;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
if (currentId === itemId) {
|
|
113
|
+
foundCurrent = true;
|
|
114
|
+
} else {
|
|
115
|
+
prev = { label: item.label, id: itemId };
|
|
116
|
+
}
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
return { prev, next };
|
|
120
|
+
};
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { QueryClient } from "@tanstack/react-query";
|
|
2
|
-
import { type ReactNode } from "react";
|
|
3
2
|
import { NavigateFunction } from "react-router-dom";
|
|
3
|
+
import type { ResolvedSidebarConfig } from "../../config/validators/ResolvedSidebarSchema.js";
|
|
4
4
|
import { type AuthenticationProvider } from "../authentication/authentication.js";
|
|
5
5
|
import type { ComponentsContextType } from "../components/context/ComponentsContext.js";
|
|
6
|
-
import { type DevPortalPath } from "../components/DevPortal.js";
|
|
7
6
|
import { Slotlets } from "../components/SlotletProvider.js";
|
|
7
|
+
import { joinPath } from "../util/joinPath.js";
|
|
8
8
|
import type { MdxComponentsType } from "../util/MdxComponents.js";
|
|
9
9
|
import {
|
|
10
10
|
type DevPortalPlugin,
|
|
@@ -20,41 +20,6 @@ export interface ApiIdentity {
|
|
|
20
20
|
id: string;
|
|
21
21
|
}
|
|
22
22
|
|
|
23
|
-
type BaseNavigationCategoryItem = {
|
|
24
|
-
label: ReactNode;
|
|
25
|
-
title?: string;
|
|
26
|
-
muted?: boolean;
|
|
27
|
-
icon?: ReactNode;
|
|
28
|
-
};
|
|
29
|
-
|
|
30
|
-
export type PathNavigationCategoryItem = BaseNavigationCategoryItem & {
|
|
31
|
-
path: string;
|
|
32
|
-
children?: NavigationCategoryItem[];
|
|
33
|
-
};
|
|
34
|
-
|
|
35
|
-
export type HrefNavigationCategoryItem = BaseNavigationCategoryItem & {
|
|
36
|
-
href: string;
|
|
37
|
-
};
|
|
38
|
-
|
|
39
|
-
export type NavigationCategoryItem =
|
|
40
|
-
| PathNavigationCategoryItem
|
|
41
|
-
| HrefNavigationCategoryItem;
|
|
42
|
-
|
|
43
|
-
export type NavigationCategory = {
|
|
44
|
-
label: string;
|
|
45
|
-
path?: string;
|
|
46
|
-
expanded?: boolean;
|
|
47
|
-
collapsible?: boolean;
|
|
48
|
-
icon?: ReactNode;
|
|
49
|
-
children: NavigationCategoryItem[];
|
|
50
|
-
};
|
|
51
|
-
|
|
52
|
-
export type NavigationItem = {
|
|
53
|
-
label: string;
|
|
54
|
-
path: DevPortalPath;
|
|
55
|
-
categories?: NavigationCategory[];
|
|
56
|
-
};
|
|
57
|
-
|
|
58
23
|
export const queryClient = new QueryClient();
|
|
59
24
|
|
|
60
25
|
export type ApiKeyCache = "api-keys";
|
|
@@ -90,7 +55,8 @@ export type ZudokuContextOptions = {
|
|
|
90
55
|
metadata?: Metadata;
|
|
91
56
|
page?: Page;
|
|
92
57
|
authentication?: AuthenticationProvider;
|
|
93
|
-
|
|
58
|
+
topNavigation?: Array<{ id: string; label: string }>;
|
|
59
|
+
sidebars?: ResolvedSidebarConfig;
|
|
94
60
|
plugins?: DevPortalPlugin[];
|
|
95
61
|
slotlets?: Slotlets;
|
|
96
62
|
mdx?: {
|
|
@@ -100,8 +66,9 @@ export type ZudokuContextOptions = {
|
|
|
100
66
|
};
|
|
101
67
|
|
|
102
68
|
export class DevPortalContext {
|
|
103
|
-
public plugins: NonNullable<ZudokuContextOptions["plugins"]
|
|
104
|
-
public
|
|
69
|
+
public plugins: NonNullable<ZudokuContextOptions["plugins"]>;
|
|
70
|
+
public sidebars: NonNullable<ZudokuContextOptions["sidebars"]>;
|
|
71
|
+
public topNavigation: NonNullable<ZudokuContextOptions["topNavigation"]>;
|
|
105
72
|
public meta: ZudokuContextOptions["metadata"];
|
|
106
73
|
public page: ZudokuContextOptions["page"];
|
|
107
74
|
public authentication?: ZudokuContextOptions["authentication"];
|
|
@@ -109,7 +76,8 @@ export class DevPortalContext {
|
|
|
109
76
|
|
|
110
77
|
constructor(config: ZudokuContextOptions) {
|
|
111
78
|
this.plugins = config.plugins ?? [];
|
|
112
|
-
this.
|
|
79
|
+
this.topNavigation = config.topNavigation ?? [];
|
|
80
|
+
this.sidebars = config.sidebars ?? {};
|
|
113
81
|
this.navigationPlugins = this.plugins.filter(isNavigationPlugin);
|
|
114
82
|
this.authentication = config.authentication;
|
|
115
83
|
this.meta = config.metadata;
|
|
@@ -142,10 +110,10 @@ export class DevPortalContext {
|
|
|
142
110
|
return keys.flat();
|
|
143
111
|
};
|
|
144
112
|
|
|
145
|
-
|
|
113
|
+
getPluginNavigation = async (path: string) => {
|
|
146
114
|
const navigations = await Promise.all(
|
|
147
|
-
this.navigationPlugins.map(
|
|
148
|
-
plugin.getNavigation?.(path),
|
|
115
|
+
this.navigationPlugins.map((plugin) =>
|
|
116
|
+
plugin.getNavigation?.(joinPath(path)),
|
|
149
117
|
),
|
|
150
118
|
);
|
|
151
119
|
|
package/src/lib/core/plugins.ts
CHANGED
|
@@ -1,15 +1,8 @@
|
|
|
1
1
|
import { type ReactElement } from "react";
|
|
2
2
|
import { NavigateFunction, type RouteObject } from "react-router-dom";
|
|
3
|
+
import type { ResolvedSidebar } from "../../config/validators/ResolvedSidebarSchema.js";
|
|
3
4
|
import { MdxComponentsType } from "../util/MdxComponents.js";
|
|
4
|
-
import {
|
|
5
|
-
DevPortalContext,
|
|
6
|
-
type ApiIdentity,
|
|
7
|
-
type NavigationCategory,
|
|
8
|
-
} from "./DevPortalContext.js";
|
|
9
|
-
|
|
10
|
-
export type PluginNavigationCategory = {
|
|
11
|
-
path: string;
|
|
12
|
-
} & NavigationCategory;
|
|
5
|
+
import { DevPortalContext, type ApiIdentity } from "./DevPortalContext.js";
|
|
13
6
|
|
|
14
7
|
export type DevPortalPlugin =
|
|
15
8
|
| CommonPlugin
|
|
@@ -19,7 +12,7 @@ export type DevPortalPlugin =
|
|
|
19
12
|
|
|
20
13
|
export interface NavigationPlugin {
|
|
21
14
|
getRoutes: () => RouteObject[];
|
|
22
|
-
getNavigation?: (path: string) => Promise<
|
|
15
|
+
getNavigation?: (path: string) => Promise<ResolvedSidebar>;
|
|
23
16
|
}
|
|
24
17
|
|
|
25
18
|
export interface ApiIdentityPlugin {
|
|
@@ -27,13 +20,13 @@ export interface ApiIdentityPlugin {
|
|
|
27
20
|
}
|
|
28
21
|
|
|
29
22
|
export interface ProfileMenuPlugin {
|
|
30
|
-
getProfileMenuItems: (context: DevPortalContext) =>
|
|
23
|
+
getProfileMenuItems: (context: DevPortalContext) => ProfileNavigationItem[];
|
|
31
24
|
}
|
|
32
25
|
|
|
33
|
-
export type
|
|
26
|
+
export type ProfileNavigationItem = {
|
|
34
27
|
label: string;
|
|
35
28
|
path?: string;
|
|
36
|
-
children?:
|
|
29
|
+
children?: ProfileNavigationItem[];
|
|
37
30
|
};
|
|
38
31
|
|
|
39
32
|
export interface CommonPlugin {
|
|
@@ -34,6 +34,17 @@ export const SettingsApiKeys = ({ service }: { service: ApiKeyService }) => {
|
|
|
34
34
|
},
|
|
35
35
|
});
|
|
36
36
|
|
|
37
|
+
const rollKeyMutation = useMutation({
|
|
38
|
+
mutationFn: (id: string) => {
|
|
39
|
+
if (!service.rollKey) {
|
|
40
|
+
throw new Error("rollKey not implemented");
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
return service.rollKey(id, context);
|
|
44
|
+
},
|
|
45
|
+
onSuccess: () => queryClient.invalidateQueries({ queryKey: ["api-keys"] }),
|
|
46
|
+
});
|
|
47
|
+
|
|
37
48
|
return (
|
|
38
49
|
<div className="max-w-screen-lg h-full pt-[--padding-content-top] pb-[--padding-content-bottom]">
|
|
39
50
|
<Slotlet name="api-keys-list-page" />
|
|
@@ -50,14 +61,14 @@ export const SettingsApiKeys = ({ service }: { service: ApiKeyService }) => {
|
|
|
50
61
|
<Slotlet name="api-keys-list-page-before-keys" />
|
|
51
62
|
|
|
52
63
|
{data.length === 0 ? (
|
|
53
|
-
<div className="flex flex-col justify-center gap-4 items-center
|
|
54
|
-
<
|
|
64
|
+
<div className="flex flex-col justify-center gap-4 items-center p-8 border rounded bg-muted/30 text-muted-foreground">
|
|
65
|
+
<p className="text-center">
|
|
55
66
|
No API keys created yet.
|
|
56
67
|
<br />
|
|
57
|
-
Get started and create
|
|
58
|
-
</
|
|
68
|
+
Get started and create your first key.
|
|
69
|
+
</p>
|
|
59
70
|
{service.createKey && (
|
|
60
|
-
<Button asChild>
|
|
71
|
+
<Button asChild variant="outline">
|
|
61
72
|
<Link to="/settings/api-keys/new">Create API Key</Link>
|
|
62
73
|
</Button>
|
|
63
74
|
)}
|
|
@@ -94,7 +105,18 @@ export const SettingsApiKeys = ({ service }: { service: ApiKeyService }) => {
|
|
|
94
105
|
</div>
|
|
95
106
|
<div className="flex gap-2">
|
|
96
107
|
{service.rollKey && (
|
|
97
|
-
<Button
|
|
108
|
+
<Button
|
|
109
|
+
size="icon"
|
|
110
|
+
title="Roll this key"
|
|
111
|
+
variant="ghost"
|
|
112
|
+
onClick={() => {
|
|
113
|
+
if (!confirm("Do you want to roll this key?")) {
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
rollKeyMutation.mutate(key.id);
|
|
118
|
+
}}
|
|
119
|
+
>
|
|
98
120
|
<RotateCwIcon size={16} />
|
|
99
121
|
</Button>
|
|
100
122
|
)}
|
|
@@ -128,10 +150,9 @@ const RevealApiKey = ({ apiKey }: { apiKey: string }) => {
|
|
|
128
150
|
|
|
129
151
|
return (
|
|
130
152
|
<div className="flex gap-2 items-center text-sm w-full">
|
|
131
|
-
<
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
/>
|
|
153
|
+
<div className="border rounded bg-gray-100 dark:bg-gray-950 p-1 font-mono w-fit overflow-x-scroll h-9 items-center flex px-2">
|
|
154
|
+
{revealed ? apiKey : "•".repeat(apiKey.length)}
|
|
155
|
+
</div>
|
|
135
156
|
<Button
|
|
136
157
|
variant="outline"
|
|
137
158
|
onClick={() => setRevealed((prev) => !prev)}
|
|
@@ -54,6 +54,16 @@ const createDefaultHandler = (endpoint: string): ApiKeyService => {
|
|
|
54
54
|
const response = await fetch(request);
|
|
55
55
|
invariant(response.ok, "Failed to delete API key");
|
|
56
56
|
},
|
|
57
|
+
rollKey: async (id, context) => {
|
|
58
|
+
const response = await fetch(
|
|
59
|
+
await context.signRequest(
|
|
60
|
+
new Request(endpoint + `/v1/developer/api-keys/${id}/key`, {
|
|
61
|
+
method: "DELETE",
|
|
62
|
+
}),
|
|
63
|
+
),
|
|
64
|
+
);
|
|
65
|
+
invariant(response.ok, "Failed to delete API key");
|
|
66
|
+
},
|
|
57
67
|
createKey: async (apiKey, context) => {
|
|
58
68
|
const request = new Request(endpoint + `/v1/developer/api-keys`, {
|
|
59
69
|
method: "POST",
|
|
@@ -1,16 +1,17 @@
|
|
|
1
1
|
import { useMDXComponents } from "@mdx-js/react";
|
|
2
2
|
import { Helmet } from "@zudoku/react-helmet-async";
|
|
3
|
-
import {
|
|
4
|
-
import { Link
|
|
3
|
+
import { type PropsWithChildren } from "react";
|
|
4
|
+
import { Link } from "react-router-dom";
|
|
5
5
|
import { CategoryHeading } from "../../components/CategoryHeading.js";
|
|
6
6
|
import { Heading } from "../../components/Heading.js";
|
|
7
7
|
import { ProseClasses } from "../../components/Markdown.js";
|
|
8
|
-
import {
|
|
9
|
-
|
|
8
|
+
import {
|
|
9
|
+
useCurrentItem,
|
|
10
|
+
usePrevNext,
|
|
11
|
+
} from "../../components/navigation/utils.js";
|
|
10
12
|
import type { MdxComponentsType } from "../../util/MdxComponents.js";
|
|
11
13
|
import { cn } from "../../util/cn.js";
|
|
12
14
|
import slugify from "../../util/slugify.js";
|
|
13
|
-
import { traverseNavigation } from "../../util/traverseNavigation.js";
|
|
14
15
|
import { Toc } from "./Toc.js";
|
|
15
16
|
import { MarkdownPluginDefaultOptions, MDXImport } from "./index.js";
|
|
16
17
|
|
|
@@ -39,17 +40,7 @@ export const MdxPage = ({
|
|
|
39
40
|
defaultOptions?: MarkdownPluginDefaultOptions;
|
|
40
41
|
}
|
|
41
42
|
>) => {
|
|
42
|
-
const
|
|
43
|
-
const location = useLocation();
|
|
44
|
-
|
|
45
|
-
const categoryTitle = navItem
|
|
46
|
-
? traverseNavigation(navItem, (_node, fullPath, parentNodes) => {
|
|
47
|
-
if (fullPath === location.pathname) {
|
|
48
|
-
return parentNodes.at(0)?.label;
|
|
49
|
-
}
|
|
50
|
-
})
|
|
51
|
-
: undefined;
|
|
52
|
-
|
|
43
|
+
const categoryTitle = useCurrentItem()?.categoryLabel;
|
|
53
44
|
const title = frontmatter.title;
|
|
54
45
|
const category = frontmatter.category ?? categoryTitle;
|
|
55
46
|
const hideToc = frontmatter.toc === false || defaultOptions?.toc === false;
|
|
@@ -65,30 +56,7 @@ export const MdxPage = ({
|
|
|
65
56
|
|
|
66
57
|
const showToc = !hideToc && tocEntries.length > 0;
|
|
67
58
|
|
|
68
|
-
const { prev, next } =
|
|
69
|
-
let prev = { path: "", label: "" as ReactNode };
|
|
70
|
-
let next = { path: "", label: "" as ReactNode };
|
|
71
|
-
let shouldStop = false;
|
|
72
|
-
|
|
73
|
-
if (!navItem) return { prev, next };
|
|
74
|
-
|
|
75
|
-
traverseNavigation(navItem, (node, fullPath) => {
|
|
76
|
-
const item = { path: fullPath, label: node.label };
|
|
77
|
-
|
|
78
|
-
if (shouldStop && isPathItem(node) && !node.children?.length) {
|
|
79
|
-
next = item;
|
|
80
|
-
return true;
|
|
81
|
-
}
|
|
82
|
-
if (fullPath === location.pathname) {
|
|
83
|
-
shouldStop = true;
|
|
84
|
-
}
|
|
85
|
-
if (!shouldStop && isPathItem(node) && !node.children?.length) {
|
|
86
|
-
prev = item;
|
|
87
|
-
}
|
|
88
|
-
});
|
|
89
|
-
|
|
90
|
-
return { prev, next } as const;
|
|
91
|
-
}, [navItem, location.pathname]);
|
|
59
|
+
const { prev, next } = usePrevNext();
|
|
92
60
|
|
|
93
61
|
return (
|
|
94
62
|
<div className="xl:grid grid-cols-[--sidecar-grid-cols] gap-8 justify-between">
|
|
@@ -119,13 +87,11 @@ export const MdxPage = ({
|
|
|
119
87
|
<>
|
|
120
88
|
<hr />
|
|
121
89
|
<div className="not-prose flex items-center justify-between gap-8">
|
|
122
|
-
{prev
|
|
90
|
+
{prev ? (
|
|
123
91
|
<Link
|
|
124
|
-
to={prev.
|
|
92
|
+
to={prev.id}
|
|
125
93
|
className="flex flex-col items-stretch gap-2 flex-1 truncate border rounded px-6 py-4 text-start hover:border-primary/85 transition shadow-sm hover:shadow-md"
|
|
126
|
-
title={
|
|
127
|
-
typeof prev.label === "string" ? prev.label : undefined
|
|
128
|
-
}
|
|
94
|
+
title={prev.label}
|
|
129
95
|
>
|
|
130
96
|
<div className="text-sm text-muted-foreground">
|
|
131
97
|
← Previous page
|
|
@@ -137,13 +103,11 @@ export const MdxPage = ({
|
|
|
137
103
|
) : (
|
|
138
104
|
<div className="flex-1" />
|
|
139
105
|
)}
|
|
140
|
-
{next
|
|
106
|
+
{next ? (
|
|
141
107
|
<Link
|
|
142
|
-
to={next.
|
|
108
|
+
to={next.id}
|
|
143
109
|
className="flex flex-col items-stretch gap-2 flex-1 truncate border rounded px-6 py-4 text-end hover:border-primary/85 transition shadow-sm hover:shadow-md"
|
|
144
|
-
title={
|
|
145
|
-
typeof next.label === "string" ? next.label : undefined
|
|
146
|
-
}
|
|
110
|
+
title={next.label}
|
|
147
111
|
>
|
|
148
112
|
<div className="text-sm text-muted-foreground">
|
|
149
113
|
Next page →
|
|
@@ -1,7 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { useTopNavigationItem } from "../../components/context/DevPortalProvider.js";
|
|
3
|
-
import { isPathItem } from "../../components/navigation/util.js";
|
|
4
|
-
import { traverseNavigation } from "../../util/traverseNavigation.js";
|
|
1
|
+
import { type RouteObject } from "react-router-dom";
|
|
5
2
|
|
|
6
3
|
import {
|
|
7
4
|
MarkdownPluginDefaultOptions,
|
|
@@ -11,57 +8,32 @@ import {
|
|
|
11
8
|
export const generateRoutes = (
|
|
12
9
|
markdownFiles: MarkdownPluginOptions["markdownFiles"],
|
|
13
10
|
defaultOptions?: MarkdownPluginDefaultOptions,
|
|
14
|
-
): RouteObject[] =>
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
};
|
|
43
|
-
},
|
|
44
|
-
} satisfies RouteObject;
|
|
45
|
-
},
|
|
46
|
-
);
|
|
47
|
-
|
|
48
|
-
const rootRoutes: RouteObject[] = Array.from(
|
|
49
|
-
new Set(routes.map((route) => route.path.split("/").at(0))),
|
|
50
|
-
).map((dir) => ({
|
|
51
|
-
path: `/${dir}`,
|
|
52
|
-
element: <Redirect />,
|
|
53
|
-
}));
|
|
54
|
-
|
|
55
|
-
return [...routes, ...rootRoutes];
|
|
56
|
-
};
|
|
57
|
-
|
|
58
|
-
const Redirect = () => {
|
|
59
|
-
const navItem = useTopNavigationItem();
|
|
60
|
-
|
|
61
|
-
if (!navItem) return null;
|
|
62
|
-
|
|
63
|
-
return traverseNavigation(navItem, (node, fullPath) => {
|
|
64
|
-
if ("children" in node || !isPathItem(node)) return;
|
|
65
|
-
return <Navigate to={fullPath} replace />;
|
|
11
|
+
): RouteObject[] =>
|
|
12
|
+
Object.entries(markdownFiles).flatMap(([file, importPromise]) => {
|
|
13
|
+
// @todo we can pass in the folder name and then filter the markdown files based on that path
|
|
14
|
+
const match = file.match(/pages\/(.*).mdx?$/);
|
|
15
|
+
const path = match?.at(1);
|
|
16
|
+
|
|
17
|
+
if (!path) return [];
|
|
18
|
+
|
|
19
|
+
const pathSegments = path.split("/");
|
|
20
|
+
const isIndexFile = pathSegments.at(-1) === "index";
|
|
21
|
+
const routePath = isIndexFile ? pathSegments.slice(0, -1).join("/") : path;
|
|
22
|
+
|
|
23
|
+
return {
|
|
24
|
+
path: routePath,
|
|
25
|
+
lazy: async () => {
|
|
26
|
+
const { MdxPage } = await import("./MdxPage.js");
|
|
27
|
+
const { default: Component, ...props } = await importPromise();
|
|
28
|
+
return {
|
|
29
|
+
element: (
|
|
30
|
+
<MdxPage
|
|
31
|
+
mdxComponent={Component}
|
|
32
|
+
{...props}
|
|
33
|
+
defaultOptions={defaultOptions}
|
|
34
|
+
/>
|
|
35
|
+
),
|
|
36
|
+
};
|
|
37
|
+
},
|
|
38
|
+
} satisfies RouteObject;
|
|
66
39
|
});
|
|
67
|
-
};
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import { HTTPSnippet } from "@zudoku/httpsnippet";
|
|
2
2
|
import { Fragment, useMemo, useTransition } from "react";
|
|
3
3
|
import { useSearchParams } from "react-router-dom";
|
|
4
|
+
import { TextColorMap } from "../../components/navigation/SidebarBadge.js";
|
|
4
5
|
import { SyntaxHighlight } from "../../components/SyntaxHighlight.js";
|
|
5
6
|
import type { SchemaObject } from "../../oas/parser/index.js";
|
|
6
7
|
import { cn } from "../../util/cn.js";
|
|
7
8
|
import { ColorizedParam } from "./ColorizedParam.js";
|
|
8
9
|
import { useOasConfig } from "./context.js";
|
|
9
10
|
import { graphql } from "./graphql/index.js";
|
|
10
|
-
import { MethodTextColorMap } from "./MethodBadge.js";
|
|
11
11
|
import type { OperationListItemResult } from "./OperationList.js";
|
|
12
12
|
import { PlaygroundDialogWrapper } from "./PlaygroundDialogWrapper.js";
|
|
13
13
|
import { RequestBodySidecarBox } from "./RequestBodySidecarBox.js";
|
|
@@ -71,6 +71,17 @@ export const GetServerQuery = graphql(/* GraphQL */ `
|
|
|
71
71
|
|
|
72
72
|
const context = { suspense: true };
|
|
73
73
|
|
|
74
|
+
const methodToColor = {
|
|
75
|
+
get: TextColorMap.green,
|
|
76
|
+
post: TextColorMap.blue,
|
|
77
|
+
put: TextColorMap.yellow,
|
|
78
|
+
delete: TextColorMap.red,
|
|
79
|
+
patch: TextColorMap.purple,
|
|
80
|
+
options: TextColorMap.indigo,
|
|
81
|
+
head: TextColorMap.gray,
|
|
82
|
+
trace: TextColorMap.gray,
|
|
83
|
+
};
|
|
84
|
+
|
|
74
85
|
export const Sidecar = ({
|
|
75
86
|
operation,
|
|
76
87
|
}: {
|
|
@@ -83,7 +94,9 @@ export const Sidecar = ({
|
|
|
83
94
|
context,
|
|
84
95
|
});
|
|
85
96
|
const methodTextColor =
|
|
86
|
-
|
|
97
|
+
methodToColor[
|
|
98
|
+
operation.method.toLocaleLowerCase() as keyof typeof methodToColor
|
|
99
|
+
] ?? TextColorMap.gray;
|
|
87
100
|
|
|
88
101
|
const [searchParams, setSearchParams] = useSearchParams();
|
|
89
102
|
const [, startTransition] = useTransition();
|
|
@@ -1,10 +1,6 @@
|
|
|
1
1
|
import { matchPath, useRouteError, type RouteObject } from "react-router-dom";
|
|
2
|
-
import {
|
|
3
|
-
type DevPortalPlugin,
|
|
4
|
-
type PluginNavigationCategory,
|
|
5
|
-
} from "../../core/plugins.js";
|
|
2
|
+
import { type DevPortalPlugin } from "../../core/plugins.js";
|
|
6
3
|
import { graphql } from "./graphql/index.js";
|
|
7
|
-
import { MethodBadge } from "./MethodBadge.js";
|
|
8
4
|
import {
|
|
9
5
|
Client as UrqlClient,
|
|
10
6
|
cacheExchange,
|
|
@@ -14,10 +10,12 @@ import {
|
|
|
14
10
|
import { useQuery } from "@tanstack/react-query";
|
|
15
11
|
import { CirclePlayIcon, LogInIcon } from "lucide-react";
|
|
16
12
|
import { createClient } from "zudoku/openapi-worker";
|
|
13
|
+
import type { ResolvedSidebarItem } from "../../../config/validators/ResolvedSidebarSchema.js";
|
|
17
14
|
import { useAuth } from "../../authentication/hook.js";
|
|
18
15
|
import { ErrorPage } from "../../components/ErrorPage.js";
|
|
19
16
|
import { SyntaxHighlight } from "../../components/SyntaxHighlight.js";
|
|
20
17
|
import { Button } from "../../ui/Button.js";
|
|
18
|
+
import { joinPath } from "../../util/joinPath.js";
|
|
21
19
|
import { OasPluginConfig } from "./interfaces.js";
|
|
22
20
|
import type { PlaygroundContentProps } from "./playground/Playground.js";
|
|
23
21
|
import { PlaygroundDialog } from "./playground/PlaygroundDialog.js";
|
|
@@ -62,7 +60,7 @@ type InternalOasPluginConfig = { inMemory?: boolean };
|
|
|
62
60
|
export const openApiPlugin = (
|
|
63
61
|
config: OasPluginConfig & InternalOasPluginConfig,
|
|
64
62
|
): DevPortalPlugin => {
|
|
65
|
-
const basePath = config.
|
|
63
|
+
const basePath = joinPath(config.navigationId ?? "/reference");
|
|
66
64
|
|
|
67
65
|
const client = config.server
|
|
68
66
|
? new UrqlClient({
|
|
@@ -152,30 +150,26 @@ export const openApiPlugin = (
|
|
|
152
150
|
|
|
153
151
|
const categories = data.schema.tags
|
|
154
152
|
.filter((tag) => tag.operations.length > 0)
|
|
155
|
-
.map<
|
|
156
|
-
|
|
153
|
+
.map<ResolvedSidebarItem>((tag) => ({
|
|
154
|
+
type: "category",
|
|
157
155
|
label: tag.name ?? "",
|
|
158
156
|
collapsible: false,
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
<MethodBadge method={operation.method} />
|
|
169
|
-
</div>
|
|
170
|
-
),
|
|
157
|
+
collapsed: false,
|
|
158
|
+
items: tag.operations.map((operation) => ({
|
|
159
|
+
type: "link",
|
|
160
|
+
label: operation.summary ?? operation.path,
|
|
161
|
+
href: `#${operation.slug}`,
|
|
162
|
+
badge: {
|
|
163
|
+
label: operation.method,
|
|
164
|
+
color: "green",
|
|
165
|
+
},
|
|
171
166
|
})),
|
|
172
167
|
}));
|
|
173
168
|
|
|
174
169
|
categories.unshift({
|
|
175
|
-
|
|
170
|
+
type: "link",
|
|
176
171
|
label: "Overview",
|
|
177
|
-
|
|
178
|
-
children: [{ path: "#description", label: "Description" }],
|
|
172
|
+
href: "#description",
|
|
179
173
|
});
|
|
180
174
|
|
|
181
175
|
return categories;
|