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.
Files changed (192) hide show
  1. package/cli.js +5 -1
  2. package/dist/app/demo.js +5 -4
  3. package/dist/app/demo.js.map +1 -1
  4. package/dist/app/main.js +3 -1
  5. package/dist/app/main.js.map +1 -1
  6. package/dist/app/standalone.js +5 -4
  7. package/dist/app/standalone.js.map +1 -1
  8. package/dist/config/validators/ResolvedSidebarSchema.d.ts +18 -0
  9. package/dist/config/validators/ResolvedSidebarSchema.js +76 -0
  10. package/dist/config/validators/ResolvedSidebarSchema.js.map +1 -0
  11. package/dist/config/validators/SidebarSchema.d.ts +177 -0
  12. package/dist/config/validators/SidebarSchema.js +71 -0
  13. package/dist/config/validators/SidebarSchema.js.map +1 -0
  14. package/dist/config/validators/validate.d.ts +411 -59
  15. package/dist/config/validators/validate.js +22 -4
  16. package/dist/config/validators/validate.js.map +1 -1
  17. package/dist/index.d.ts +1 -1
  18. package/dist/lib/components/DevPortal.js +1 -1
  19. package/dist/lib/components/DevPortal.js.map +1 -1
  20. package/dist/lib/components/Header.js.map +1 -1
  21. package/dist/lib/components/Heading.d.ts +1 -1
  22. package/dist/lib/components/Layout.js +2 -2
  23. package/dist/lib/components/Layout.js.map +1 -1
  24. package/dist/lib/components/TopNavigation.js +5 -5
  25. package/dist/lib/components/TopNavigation.js.map +1 -1
  26. package/dist/lib/components/context/DevPortalProvider.d.ts +9 -3
  27. package/dist/lib/components/context/DevPortalProvider.js +11 -23
  28. package/dist/lib/components/context/DevPortalProvider.js.map +1 -1
  29. package/dist/lib/components/context/ThemeContext.d.ts +1 -4
  30. package/dist/lib/components/context/ThemeContext.js +3 -29
  31. package/dist/lib/components/context/ThemeContext.js.map +1 -1
  32. package/dist/lib/components/context/ThemeProvider.d.ts +4 -0
  33. package/dist/lib/components/context/ThemeProvider.js +23 -0
  34. package/dist/lib/components/context/ThemeProvider.js.map +1 -0
  35. package/dist/lib/components/navigation/Sidebar.d.ts +1 -0
  36. package/dist/lib/components/navigation/Sidebar.js +12 -0
  37. package/dist/lib/components/navigation/Sidebar.js.map +1 -0
  38. package/dist/lib/components/navigation/SidebarBadge.d.ts +22 -0
  39. package/dist/lib/components/navigation/SidebarBadge.js +24 -0
  40. package/dist/lib/components/navigation/SidebarBadge.js.map +1 -0
  41. package/dist/lib/components/navigation/SidebarCategory.d.ts +5 -0
  42. package/dist/lib/components/navigation/SidebarCategory.js +33 -0
  43. package/dist/lib/components/navigation/SidebarCategory.js.map +1 -0
  44. package/dist/lib/components/navigation/SidebarItem.d.ts +12 -0
  45. package/dist/lib/components/navigation/SidebarItem.js +42 -0
  46. package/dist/lib/components/navigation/SidebarItem.js.map +1 -0
  47. package/dist/lib/components/navigation/{SideNavigationWrapper.d.ts → SidebarWrapper.d.ts} +1 -1
  48. package/dist/lib/components/navigation/{SideNavigationWrapper.js → SidebarWrapper.js} +2 -2
  49. package/dist/lib/components/navigation/SidebarWrapper.js.map +1 -0
  50. package/dist/lib/components/navigation/utils.d.ts +16 -0
  51. package/dist/lib/components/navigation/utils.js +85 -0
  52. package/dist/lib/components/navigation/utils.js.map +1 -0
  53. package/dist/lib/core/DevPortalContext.d.ts +9 -32
  54. package/dist/lib/core/DevPortalContext.js +8 -5
  55. package/dist/lib/core/DevPortalContext.js.map +1 -1
  56. package/dist/lib/core/plugins.d.ts +6 -8
  57. package/dist/lib/core/plugins.js.map +1 -1
  58. package/dist/lib/plugins/api-keys/SettingsApiKeys.js +16 -2
  59. package/dist/lib/plugins/api-keys/SettingsApiKeys.js.map +1 -1
  60. package/dist/lib/plugins/api-keys/index.js +6 -0
  61. package/dist/lib/plugins/api-keys/index.js.map +1 -1
  62. package/dist/lib/plugins/markdown/MdxPage.js +5 -36
  63. package/dist/lib/plugins/markdown/MdxPage.js.map +1 -1
  64. package/dist/lib/plugins/markdown/generateRoutes.js +20 -43
  65. package/dist/lib/plugins/markdown/generateRoutes.js.map +1 -1
  66. package/dist/lib/plugins/openapi/Sidecar.js +12 -2
  67. package/dist/lib/plugins/openapi/Sidecar.js.map +1 -1
  68. package/dist/lib/plugins/openapi/index.js +14 -11
  69. package/dist/lib/plugins/openapi/index.js.map +1 -1
  70. package/dist/lib/plugins/openapi/interfaces.d.ts +1 -1
  71. package/dist/lib/util/useScrollToAnchor.js +31 -17
  72. package/dist/lib/util/useScrollToAnchor.js.map +1 -1
  73. package/dist/vite/plugin-sidebar.d.ts +3 -0
  74. package/dist/vite/plugin-sidebar.js +23 -0
  75. package/dist/vite/plugin-sidebar.js.map +1 -0
  76. package/dist/vite/plugin.js +2 -0
  77. package/dist/vite/plugin.js.map +1 -1
  78. package/lib/{AuthenticationPlugin-XS0DoAhE.js → AuthenticationPlugin-DgwV0hVu.js} +7 -7
  79. package/lib/{AuthenticationPlugin-XS0DoAhE.js.map → AuthenticationPlugin-DgwV0hVu.js.map} +1 -1
  80. package/lib/{CategoryHeading-DCmchnA1.js → CategoryHeading-BWq12Bfa.js} +3 -3
  81. package/lib/{CategoryHeading-DCmchnA1.js.map → CategoryHeading-BWq12Bfa.js.map} +1 -1
  82. package/lib/{Combination-C442XfGG.js → Combination-DkycFHkm.js} +4 -4
  83. package/lib/{Combination-C442XfGG.js.map → Combination-DkycFHkm.js.map} +1 -1
  84. package/lib/{DevPortalProvider-BWeAysxF.js → DevPortalProvider-CTxoCHIT.js} +375 -417
  85. package/lib/DevPortalProvider-CTxoCHIT.js.map +1 -0
  86. package/lib/DeveloperHint-BQSFXH01.js +10 -0
  87. package/lib/{DeveloperHint-DQVwIery.js.map → DeveloperHint-BQSFXH01.js.map} +1 -1
  88. package/lib/{Input-3IEt27jb.js → Input-BclXSY0g.js} +5 -5
  89. package/lib/{Input-3IEt27jb.js.map → Input-BclXSY0g.js.map} +1 -1
  90. package/lib/{Markdown-QsZ-PHET.js → Markdown-B_Gax7at.js} +1136 -1152
  91. package/lib/{Markdown-QsZ-PHET.js.map → Markdown-B_Gax7at.js.map} +1 -1
  92. package/lib/{MdxPage-CA1WmW14.js → MdxPage-Crlr0GmN.js} +67 -81
  93. package/lib/MdxPage-Crlr0GmN.js.map +1 -0
  94. package/lib/{OperationList-CHK_erYP.js → OperationList-nQ0bd8TU.js} +8 -8
  95. package/lib/{OperationList-CHK_erYP.js.map → OperationList-nQ0bd8TU.js.map} +1 -1
  96. package/lib/Route-CNvxEBnR.js +14 -0
  97. package/lib/{Route-D70pGn9n.js.map → Route-CNvxEBnR.js.map} +1 -1
  98. package/lib/{SlotletProvider-B71hNEUL.js → SlotletProvider-CzMAO73_.js} +12 -12
  99. package/lib/{SlotletProvider-B71hNEUL.js.map → SlotletProvider-CzMAO73_.js.map} +1 -1
  100. package/lib/Spinner-fF-Xv-gw.js +274 -0
  101. package/lib/Spinner-fF-Xv-gw.js.map +1 -0
  102. package/lib/index-7kcHaXD6.js +1771 -0
  103. package/lib/index-7kcHaXD6.js.map +1 -0
  104. package/lib/{index-Bl6YeerK.js → index-CgCPw6Jn.js} +1026 -1043
  105. package/lib/index-CgCPw6Jn.js.map +1 -0
  106. package/lib/{index-BH-Ub36F.js → index-DkuZvRNP.js} +4 -4
  107. package/lib/{index-BH-Ub36F.js.map → index-DkuZvRNP.js.map} +1 -1
  108. package/lib/joinPath-VeNuJa7y.js +8 -0
  109. package/lib/joinPath-VeNuJa7y.js.map +1 -0
  110. package/lib/jsx-runtime-B6kdoens.js +635 -0
  111. package/lib/jsx-runtime-B6kdoens.js.map +1 -0
  112. package/lib/{AnchorLink-BZcpTwOs.js → utils-CzT_9Tsn.js} +258 -214
  113. package/lib/utils-CzT_9Tsn.js.map +1 -0
  114. package/lib/zudoku.auth-clerk.js +1 -1
  115. package/lib/zudoku.auth-openid.js +2 -2
  116. package/lib/zudoku.components.js +1229 -1227
  117. package/lib/zudoku.components.js.map +1 -1
  118. package/lib/zudoku.plugin-api-keys.js +129 -107
  119. package/lib/zudoku.plugin-api-keys.js.map +1 -1
  120. package/lib/zudoku.plugin-custom-page.js +2 -2
  121. package/lib/zudoku.plugin-markdown.js +21 -38
  122. package/lib/zudoku.plugin-markdown.js.map +1 -1
  123. package/lib/zudoku.plugin-openapi.js +8 -6
  124. package/lib/zudoku.plugin-openapi.js.map +1 -1
  125. package/package.json +4 -1
  126. package/src/app/demo.tsx +5 -4
  127. package/src/app/main.css +2 -2
  128. package/src/app/main.tsx +3 -1
  129. package/src/app/standalone.tsx +5 -4
  130. package/src/lib/components/DevPortal.tsx +1 -1
  131. package/src/lib/components/Header.tsx +2 -2
  132. package/src/lib/components/Layout.tsx +2 -2
  133. package/src/lib/components/TopNavigation.tsx +5 -5
  134. package/src/lib/components/context/DevPortalProvider.ts +11 -28
  135. package/src/lib/components/context/ThemeContext.tsx +3 -41
  136. package/src/lib/components/context/ThemeProvider.tsx +27 -0
  137. package/src/lib/components/navigation/{SideNavigation.tsx → Sidebar.tsx} +7 -7
  138. package/src/lib/components/navigation/SidebarBadge.tsx +40 -0
  139. package/src/lib/components/navigation/SidebarCategory.tsx +105 -0
  140. package/src/lib/components/navigation/SidebarItem.tsx +96 -0
  141. package/src/lib/components/navigation/{SideNavigationWrapper.tsx → SidebarWrapper.tsx} +1 -1
  142. package/src/lib/components/navigation/utils.ts +120 -0
  143. package/src/lib/core/DevPortalContext.ts +12 -44
  144. package/src/lib/core/plugins.ts +6 -13
  145. package/src/lib/plugins/api-keys/SettingsApiKeys.tsx +31 -10
  146. package/src/lib/plugins/api-keys/index.tsx +10 -0
  147. package/src/lib/plugins/markdown/MdxPage.tsx +14 -50
  148. package/src/lib/plugins/markdown/generateRoutes.tsx +29 -57
  149. package/src/lib/plugins/openapi/Sidecar.tsx +15 -2
  150. package/src/lib/plugins/openapi/index.tsx +17 -23
  151. package/src/lib/plugins/openapi/interfaces.ts +1 -1
  152. package/src/lib/util/useScrollToAnchor.ts +39 -18
  153. package/dist/lib/components/navigation/SideNavigation.d.ts +0 -1
  154. package/dist/lib/components/navigation/SideNavigation.js +0 -12
  155. package/dist/lib/components/navigation/SideNavigation.js.map +0 -1
  156. package/dist/lib/components/navigation/SideNavigationCategory.d.ts +0 -4
  157. package/dist/lib/components/navigation/SideNavigationCategory.js +0 -26
  158. package/dist/lib/components/navigation/SideNavigationCategory.js.map +0 -1
  159. package/dist/lib/components/navigation/SideNavigationItem.d.ts +0 -9
  160. package/dist/lib/components/navigation/SideNavigationItem.js +0 -44
  161. package/dist/lib/components/navigation/SideNavigationItem.js.map +0 -1
  162. package/dist/lib/components/navigation/SideNavigationWrapper.js.map +0 -1
  163. package/dist/lib/components/navigation/useNavigationCollapsibleState.d.ts +0 -9
  164. package/dist/lib/components/navigation/useNavigationCollapsibleState.js +0 -28
  165. package/dist/lib/components/navigation/useNavigationCollapsibleState.js.map +0 -1
  166. package/dist/lib/components/navigation/util.d.ts +0 -8
  167. package/dist/lib/components/navigation/util.js +0 -15
  168. package/dist/lib/components/navigation/util.js.map +0 -1
  169. package/dist/lib/plugins/openapi/MethodBadge.d.ts +0 -13
  170. package/dist/lib/plugins/openapi/MethodBadge.js +0 -26
  171. package/dist/lib/plugins/openapi/MethodBadge.js.map +0 -1
  172. package/dist/lib/util/traverseNavigation.d.ts +0 -6
  173. package/dist/lib/util/traverseNavigation.js +0 -30
  174. package/dist/lib/util/traverseNavigation.js.map +0 -1
  175. package/lib/AnchorLink-BZcpTwOs.js.map +0 -1
  176. package/lib/DevPortalProvider-BWeAysxF.js.map +0 -1
  177. package/lib/DeveloperHint-DQVwIery.js +0 -10
  178. package/lib/MdxPage-CA1WmW14.js.map +0 -1
  179. package/lib/Route-D70pGn9n.js +0 -13
  180. package/lib/Spinner-Coi7ORUV.js +0 -244
  181. package/lib/Spinner-Coi7ORUV.js.map +0 -1
  182. package/lib/index-Bl6YeerK.js.map +0 -1
  183. package/lib/index-Dt-pU7Vu.js +0 -916
  184. package/lib/index-Dt-pU7Vu.js.map +0 -1
  185. package/lib/jsx-runtime-CJBdjYYx.js +0 -1526
  186. package/lib/jsx-runtime-CJBdjYYx.js.map +0 -1
  187. package/src/lib/components/navigation/SideNavigationCategory.tsx +0 -72
  188. package/src/lib/components/navigation/SideNavigationItem.tsx +0 -148
  189. package/src/lib/components/navigation/useNavigationCollapsibleState.ts +0 -42
  190. package/src/lib/components/navigation/util.ts +0 -38
  191. package/src/lib/plugins/openapi/MethodBadge.tsx +0 -36
  192. 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
- navigation: NavigationItem[];
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 navigation: ZudokuContextOptions["navigation"];
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.navigation = config.navigation;
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
- getNavigation = async (path: string) => {
113
+ getPluginNavigation = async (path: string) => {
146
114
  const navigations = await Promise.all(
147
- this.navigationPlugins.map(async (plugin) =>
148
- plugin.getNavigation?.(path),
115
+ this.navigationPlugins.map((plugin) =>
116
+ plugin.getNavigation?.(joinPath(path)),
149
117
  ),
150
118
  );
151
119
 
@@ -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<PluginNavigationCategory[]>;
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) => NavigationItem[];
23
+ getProfileMenuItems: (context: DevPortalContext) => ProfileNavigationItem[];
31
24
  }
32
25
 
33
- export type NavigationItem = {
26
+ export type ProfileNavigationItem = {
34
27
  label: string;
35
28
  path?: string;
36
- children?: NavigationItem[];
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 h-1/2 my-8">
54
- <div className="text-center">
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 the first one now
58
- </div>
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 size="icon">
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
- <input
132
- className="border rounded bg-gray-100 dark:bg-gray-950 p-1 font-mono max-w-min"
133
- value={revealed ? apiKey : "•".repeat(apiKey.length)}
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 { useMemo, type PropsWithChildren, type ReactNode } from "react";
4
- import { Link, useLocation } from "react-router-dom";
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 { useTopNavigationItem } from "../../components/context/DevPortalProvider.js";
9
- import { isPathItem } from "../../components/navigation/util.js";
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 navItem = useTopNavigationItem();
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 } = useMemo(() => {
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.path ? (
90
+ {prev ? (
123
91
  <Link
124
- to={prev.path}
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.path ? (
106
+ {next ? (
141
107
  <Link
142
- to={next.path}
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 { Navigate, type RouteObject } from "react-router-dom";
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
- const routes = Object.entries(markdownFiles).flatMap(
16
- ([file, importPromise]) => {
17
- // @todo we can pass in the folder name and then filter the markdown files based on that path
18
- const match = file.match(/pages\/(.*).mdx?$/);
19
- const path = match?.at(1);
20
-
21
- if (!path) return [];
22
-
23
- const pathSegments = path.split("/");
24
- const isIndexFile = pathSegments.at(-1) === "index";
25
- const routePath = isIndexFile
26
- ? pathSegments.slice(0, -1).join("/")
27
- : path;
28
-
29
- return {
30
- path: routePath,
31
- lazy: async () => {
32
- const { MdxPage } = await import("./MdxPage.js");
33
- const { default: Component, ...props } = await importPromise();
34
- return {
35
- element: (
36
- <MdxPage
37
- mdxComponent={Component}
38
- {...props}
39
- defaultOptions={defaultOptions}
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
- MethodTextColorMap[operation.method as keyof typeof MethodTextColorMap];
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.path ?? "/reference";
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<PluginNavigationCategory>((tag) => ({
156
- path,
153
+ .map<ResolvedSidebarItem>((tag) => ({
154
+ type: "category",
157
155
  label: tag.name ?? "",
158
156
  collapsible: false,
159
- children: tag.operations.map((operation) => ({
160
- path: `#${operation.slug}`,
161
- muted: !!operation.deprecated,
162
- title: operation.summary ?? operation.path,
163
- label: (
164
- <div className="flex flex-1 min-w-0 justify-between gap-2">
165
- <span className="truncate">
166
- {operation.summary ?? operation.path}
167
- </span>
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
- path,
170
+ type: "link",
176
171
  label: "Overview",
177
- collapsible: false,
178
- children: [{ path: "#description", label: "Description" }],
172
+ href: "#description",
179
173
  });
180
174
 
181
175
  return categories;