zudoku 0.13.7 → 0.14.1

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 (177) hide show
  1. package/cli.js +2 -2
  2. package/dist/app/entry.client.js +2 -2
  3. package/dist/app/entry.client.js.map +1 -1
  4. package/dist/app/entry.server.js +3 -0
  5. package/dist/app/entry.server.js.map +1 -1
  6. package/dist/app/main.d.ts +1 -0
  7. package/dist/app/main.js +6 -21
  8. package/dist/app/main.js.map +1 -1
  9. package/dist/app/standalone.js.map +1 -1
  10. package/dist/cli/common/machine-id/lib.js.map +1 -1
  11. package/dist/cli/common/outdated.js.map +1 -1
  12. package/dist/cli/common/utils/box.js.map +1 -1
  13. package/dist/config/validators/InputSidebarSchema.d.ts +2 -2
  14. package/dist/config/validators/SidebarSchema.d.ts +24 -1
  15. package/dist/config/validators/SidebarSchema.js +76 -39
  16. package/dist/config/validators/SidebarSchema.js.map +1 -1
  17. package/dist/config/validators/validate.d.ts +329 -264
  18. package/dist/config/validators/validate.js +11 -10
  19. package/dist/config/validators/validate.js.map +1 -1
  20. package/dist/index.d.ts +4 -1
  21. package/dist/index.js +1 -1
  22. package/dist/index.js.map +1 -1
  23. package/dist/lib/components/Header.js +6 -1
  24. package/dist/lib/components/Header.js.map +1 -1
  25. package/dist/lib/components/Heading.d.ts +1 -1
  26. package/dist/lib/components/SlotletProvider.d.ts +2 -1
  27. package/dist/lib/components/SlotletProvider.js.map +1 -1
  28. package/dist/lib/components/SyntaxHighlight.js +4 -1
  29. package/dist/lib/components/SyntaxHighlight.js.map +1 -1
  30. package/dist/lib/components/TopNavigation.js +30 -5
  31. package/dist/lib/components/TopNavigation.js.map +1 -1
  32. package/dist/lib/components/context/ZudokuContext.d.ts +6 -12
  33. package/dist/lib/components/context/ZudokuContext.js +26 -20
  34. package/dist/lib/components/context/ZudokuContext.js.map +1 -1
  35. package/dist/lib/components/navigation/Sidebar.js +3 -3
  36. package/dist/lib/components/navigation/Sidebar.js.map +1 -1
  37. package/dist/lib/components/navigation/SidebarCategory.js +2 -4
  38. package/dist/lib/components/navigation/SidebarCategory.js.map +1 -1
  39. package/dist/lib/components/navigation/SidebarItem.js +1 -3
  40. package/dist/lib/components/navigation/SidebarItem.js.map +1 -1
  41. package/dist/lib/components/navigation/utils.js +10 -14
  42. package/dist/lib/components/navigation/utils.js.map +1 -1
  43. package/dist/lib/core/DevPortalContext.d.ts +3 -7
  44. package/dist/lib/core/DevPortalContext.js.map +1 -1
  45. package/dist/lib/core/plugins.d.ts +1 -0
  46. package/dist/lib/core/plugins.js.map +1 -1
  47. package/dist/lib/plugins/custom-pages/CustomPage.js +2 -2
  48. package/dist/lib/plugins/markdown/index.d.ts +5 -6
  49. package/dist/lib/plugins/markdown/index.js +31 -3
  50. package/dist/lib/plugins/markdown/index.js.map +1 -1
  51. package/dist/lib/plugins/markdown/resolver.d.ts +38 -0
  52. package/dist/lib/plugins/markdown/resolver.js +75 -0
  53. package/dist/lib/plugins/markdown/resolver.js.map +1 -0
  54. package/dist/lib/plugins/openapi/Endpoint.js.map +1 -1
  55. package/dist/lib/plugins/openapi/RequestBodySidecarBox.js.map +1 -1
  56. package/dist/lib/plugins/openapi/Sidecar.js.map +1 -1
  57. package/dist/lib/plugins/openapi/client/worker.js.map +1 -1
  58. package/dist/lib/plugins/openapi/index.js.map +1 -1
  59. package/dist/lib/ui/Badge.d.ts +1 -1
  60. package/dist/lib/ui/Button.d.ts +1 -1
  61. package/dist/lib/ui/Command.d.ts +80 -0
  62. package/dist/lib/ui/Command.js +31 -0
  63. package/dist/lib/ui/Command.js.map +1 -0
  64. package/dist/lib/util/MdxComponents.js.map +1 -1
  65. package/dist/lib/util/useExposedProps.js +3 -2
  66. package/dist/lib/util/useExposedProps.js.map +1 -1
  67. package/dist/lib/util/useScrollToAnchor.js.map +1 -1
  68. package/dist/vite/build.js +7 -2
  69. package/dist/vite/build.js.map +1 -1
  70. package/dist/vite/config.js +11 -6
  71. package/dist/vite/config.js.map +1 -1
  72. package/dist/vite/debug.d.ts +1 -0
  73. package/dist/vite/debug.js +10 -0
  74. package/dist/vite/debug.js.map +1 -0
  75. package/dist/vite/plugin-config-reload.js +0 -2
  76. package/dist/vite/plugin-config-reload.js.map +1 -1
  77. package/dist/vite/plugin-docs.js +37 -26
  78. package/dist/vite/plugin-docs.js.map +1 -1
  79. package/dist/vite/plugin-frontmatter.d.ts +2 -1
  80. package/dist/vite/plugin-frontmatter.js +27 -24
  81. package/dist/vite/plugin-frontmatter.js.map +1 -1
  82. package/dist/vite/plugin-sidebar.js +7 -6
  83. package/dist/vite/plugin-sidebar.js.map +1 -1
  84. package/dist/vite/plugin.js +1 -1
  85. package/dist/vite/plugin.js.map +1 -1
  86. package/dist/vite/prerender.d.ts +5 -1
  87. package/dist/vite/prerender.js +6 -5
  88. package/dist/vite/prerender.js.map +1 -1
  89. package/lib/{utils-B2yoT99j.js → AnchorLink-BbB2q-jx.js} +214 -258
  90. package/lib/AnchorLink-BbB2q-jx.js.map +1 -0
  91. package/lib/{AuthenticationPlugin-Bpdes0cJ.js → AuthenticationPlugin-C9BHGXlE.js} +2 -2
  92. package/lib/{AuthenticationPlugin-Bpdes0cJ.js.map → AuthenticationPlugin-C9BHGXlE.js.map} +1 -1
  93. package/lib/Dialog-k70Qfukb.js +67 -0
  94. package/lib/Dialog-k70Qfukb.js.map +1 -0
  95. package/lib/{Markdown-1BO9EA_X.js → Markdown-BDcCAWwm.js} +18 -16
  96. package/lib/{Markdown-1BO9EA_X.js.map → Markdown-BDcCAWwm.js.map} +1 -1
  97. package/lib/{MdxPage-BEOcOICU.js → MdxPage-DKMH_t0f.js} +14 -13
  98. package/lib/{MdxPage-BEOcOICU.js.map → MdxPage-DKMH_t0f.js.map} +1 -1
  99. package/lib/{OperationList-Cea2Yt8e.js → OperationList-Tj7ubW_t.js} +3 -3
  100. package/lib/OperationList-Tj7ubW_t.js.map +1 -0
  101. package/lib/{Route-BHT-onwf.js → Route-C3DGB6OS.js} +2 -2
  102. package/lib/{Route-BHT-onwf.js.map → Route-C3DGB6OS.js.map} +1 -1
  103. package/lib/{Select-m1aXZGAP.js → Select-Bagt3Bme.js} +3 -3
  104. package/lib/{Select-m1aXZGAP.js.map → Select-Bagt3Bme.js.map} +1 -1
  105. package/lib/{SlotletProvider-CPfsBw39.js → SlotletProvider-Da7eFgd2.js} +3 -3
  106. package/lib/{SlotletProvider-CPfsBw39.js.map → SlotletProvider-Da7eFgd2.js.map} +1 -1
  107. package/lib/{ZudokuContext-D1D8Anlj.js → ZudokuContext-BKXGJTmu.js} +459 -410
  108. package/lib/ZudokuContext-BKXGJTmu.js.map +1 -0
  109. package/lib/__vite-browser-external-BYRIRx8p.js +9 -0
  110. package/lib/__vite-browser-external-BYRIRx8p.js.map +1 -0
  111. package/lib/assets/worker-Bf8vjASY.js.map +1 -1
  112. package/lib/{hook-JSRuxV1P.js → hook-sn0zMTkE.js} +2 -2
  113. package/lib/{hook-JSRuxV1P.js.map → hook-sn0zMTkE.js.map} +1 -1
  114. package/lib/{index-Cj-F-4ME.js → index-AjWCJNGC.js} +1180 -1238
  115. package/lib/index-AjWCJNGC.js.map +1 -0
  116. package/lib/ui/Command.js +550 -0
  117. package/lib/ui/Command.js.map +1 -0
  118. package/lib/useExposedProps-ChOIUaS4.js +9 -0
  119. package/lib/useExposedProps-ChOIUaS4.js.map +1 -0
  120. package/lib/zudoku.auth-clerk.js +1 -1
  121. package/lib/zudoku.auth-openid.js +2 -2
  122. package/lib/zudoku.components.js +467 -451
  123. package/lib/zudoku.components.js.map +1 -1
  124. package/lib/zudoku.plugin-api-keys.js +6 -6
  125. package/lib/zudoku.plugin-custom-pages.js +14 -14
  126. package/lib/zudoku.plugin-custom-pages.js.map +1 -1
  127. package/lib/zudoku.plugin-markdown.js +93 -27
  128. package/lib/zudoku.plugin-markdown.js.map +1 -1
  129. package/lib/zudoku.plugin-openapi.js +5 -6
  130. package/lib/zudoku.plugin-openapi.js.map +1 -1
  131. package/package.json +4 -3
  132. package/src/app/entry.client.tsx +4 -2
  133. package/src/app/entry.server.tsx +4 -0
  134. package/src/app/main.css +4 -0
  135. package/src/app/main.tsx +9 -25
  136. package/src/app/standalone.tsx +1 -1
  137. package/src/lib/components/Header.tsx +17 -2
  138. package/src/lib/components/SlotletProvider.tsx +2 -0
  139. package/src/lib/components/SyntaxHighlight.tsx +5 -1
  140. package/src/lib/components/TopNavigation.tsx +58 -24
  141. package/src/lib/components/context/ZudokuContext.ts +28 -20
  142. package/src/lib/components/navigation/Sidebar.tsx +5 -5
  143. package/src/lib/components/navigation/SidebarCategory.tsx +2 -4
  144. package/src/lib/components/navigation/SidebarItem.tsx +1 -3
  145. package/src/lib/components/navigation/utils.ts +11 -16
  146. package/src/lib/core/DevPortalContext.ts +3 -7
  147. package/src/lib/core/plugins.ts +2 -0
  148. package/src/lib/plugins/custom-pages/CustomPage.tsx +2 -2
  149. package/src/lib/plugins/markdown/index.tsx +49 -12
  150. package/src/lib/plugins/markdown/resolver.ts +92 -0
  151. package/src/lib/plugins/openapi/Endpoint.tsx +2 -2
  152. package/src/lib/plugins/openapi/RequestBodySidecarBox.tsx +1 -1
  153. package/src/lib/plugins/openapi/Sidecar.tsx +1 -1
  154. package/src/lib/plugins/openapi/client/worker.ts +2 -2
  155. package/src/lib/plugins/openapi/index.tsx +1 -1
  156. package/src/lib/ui/Command.tsx +151 -0
  157. package/src/lib/util/MdxComponents.tsx +0 -1
  158. package/src/lib/util/useExposedProps.tsx +8 -2
  159. package/src/lib/util/useScrollToAnchor.ts +1 -1
  160. package/dist/lib/plugins/markdown/generateRoutes.d.ts +0 -3
  161. package/dist/lib/plugins/markdown/generateRoutes.js +0 -21
  162. package/dist/lib/plugins/markdown/generateRoutes.js.map +0 -1
  163. package/dist/lib/ui/Note.d.ts +0 -8
  164. package/dist/lib/ui/Note.js +0 -23
  165. package/dist/lib/ui/Note.js.map +0 -1
  166. package/lib/OperationList-Cea2Yt8e.js.map +0 -1
  167. package/lib/ZudokuContext-D1D8Anlj.js.map +0 -1
  168. package/lib/index-Cj-F-4ME.js.map +0 -1
  169. package/lib/joinPath-B7kNnUX4.js +0 -8
  170. package/lib/joinPath-B7kNnUX4.js.map +0 -1
  171. package/lib/ui/Note.js +0 -51
  172. package/lib/ui/Note.js.map +0 -1
  173. package/lib/useExposedProps-B9K-9GTc.js +0 -9
  174. package/lib/useExposedProps-B9K-9GTc.js.map +0 -1
  175. package/lib/utils-B2yoT99j.js.map +0 -1
  176. package/src/lib/plugins/markdown/generateRoutes.tsx +0 -38
  177. package/src/lib/ui/Note.tsx +0 -58
@@ -1,7 +1,9 @@
1
1
  import { useQuery, useSuspenseQuery } from "@tanstack/react-query";
2
2
  import { createContext, useContext } from "react";
3
- import { useLocation } from "react-router-dom";
3
+ import { matchPath, useLocation } from "react-router-dom";
4
4
  import { DevPortalContext } from "../../core/DevPortalContext.js";
5
+ import { joinPath } from "../../util/joinPath.js";
6
+ import { traverseSidebar } from "../navigation/utils.js";
5
7
 
6
8
  export const ZudokuReactContext = createContext<DevPortalContext | undefined>(
7
9
  undefined,
@@ -25,34 +27,40 @@ export const useApiIdentities = () => {
25
27
  });
26
28
  };
27
29
 
28
- export const useTopNavigationItem = () => {
29
- const { topNavigation } = useZudoku();
30
+ export const useCurrentNavigation = () => {
31
+ const { getPluginSidebar, sidebars, topNavigation } = useZudoku();
30
32
  const location = useLocation();
31
33
 
32
- const firstPart = location.pathname.split("/").at(1);
33
- if (!firstPart) return;
34
+ const currentSidebarItem = Object.entries(sidebars).find(([, sidebar]) => {
35
+ return traverseSidebar(sidebar, (item) => {
36
+ const itemId =
37
+ item.type === "doc"
38
+ ? joinPath(item.id)
39
+ : item.type === "category" && item.link
40
+ ? joinPath(item.link.id)
41
+ : undefined;
34
42
 
35
- return topNavigation.find((item) => item.id === firstPart);
36
- };
37
-
38
- export const useNavigation = () => {
39
- const { getPluginSidebar, sidebars } = useZudoku();
40
- const navItem = useTopNavigationItem();
41
- const path = navItem?.id;
42
- const currentSidebar = path ? (sidebars[path] ?? []) : [];
43
- const location = useLocation();
43
+ if (itemId === location.pathname) {
44
+ return item;
45
+ }
46
+ });
47
+ });
48
+ const currentTopNavItem =
49
+ topNavigation.find((t) => t.id === currentSidebarItem?.[0]) ??
50
+ topNavigation.find((item) => matchPath(item.id, location.pathname));
44
51
 
45
52
  return useSuspenseQuery({
46
53
  queryFn: async () => {
47
- const pluginSidebar = path
48
- ? await getPluginSidebar(path)
49
- : await getPluginSidebar(location.pathname);
54
+ const pluginSidebar = await getPluginSidebar(location.pathname);
50
55
 
51
56
  return {
52
- items: [...currentSidebar, ...pluginSidebar],
53
- currentTopNavItem: navItem,
57
+ sidebar: [
58
+ ...(currentSidebarItem ? currentSidebarItem[1] : []),
59
+ ...pluginSidebar,
60
+ ],
61
+ topNavItem: currentTopNavItem,
54
62
  };
55
63
  },
56
- queryKey: ["navigation", path],
64
+ queryKey: ["navigation", location.pathname],
57
65
  });
58
66
  };
@@ -2,23 +2,23 @@ import { useRef } from "react";
2
2
 
3
3
  import { VisuallyHidden } from "@radix-ui/react-visually-hidden";
4
4
  import { DrawerContent, DrawerTitle } from "../../ui/Drawer.js";
5
- import { useNavigation } from "../context/ZudokuContext.js";
5
+ import { useCurrentNavigation } from "../context/ZudokuContext.js";
6
6
  import { Slotlet } from "../SlotletProvider.js";
7
7
  import { SidebarItem } from "./SidebarItem.js";
8
8
  import { SidebarWrapper } from "./SidebarWrapper.js";
9
9
 
10
10
  export const Sidebar = () => {
11
11
  const navRef = useRef<HTMLDivElement | null>(null);
12
- const navigation = useNavigation();
12
+ const navigation = useCurrentNavigation();
13
13
 
14
14
  return (
15
15
  <>
16
16
  <SidebarWrapper
17
17
  ref={navRef}
18
- pushMainContent={navigation.data.items.length > 0}
18
+ pushMainContent={navigation.data.sidebar.length > 0}
19
19
  >
20
20
  <Slotlet name="zudoku-before-navigation" />
21
- {navigation.data.items.map((item) => (
21
+ {navigation.data.sidebar.map((item) => (
22
22
  <SidebarItem key={item.label} item={item} />
23
23
  ))}
24
24
  <Slotlet name="zudoku-after-navigation" />
@@ -30,7 +30,7 @@ export const Sidebar = () => {
30
30
  <VisuallyHidden>
31
31
  <DrawerTitle>Sidebar</DrawerTitle>
32
32
  </VisuallyHidden>
33
- {navigation.data.items.map((item) => (
33
+ {navigation.data.sidebar.map((item) => (
34
34
  <SidebarItem key={item.label} item={item} />
35
35
  ))}
36
36
  </DrawerContent>
@@ -5,7 +5,6 @@ import { NavLink, useMatch } from "react-router-dom";
5
5
  import type { SidebarItemCategory } from "../../../config/validators/SidebarSchema.js";
6
6
  import { cn } from "../../util/cn.js";
7
7
  import { joinPath } from "../../util/joinPath.js";
8
- import { useTopNavigationItem } from "../context/ZudokuContext.js";
9
8
  import { navigationListItem, SidebarItem } from "./SidebarItem.js";
10
9
  import { useIsCategoryOpen } from "./utils.js";
11
10
 
@@ -16,7 +15,6 @@ export const SidebarCategory = ({
16
15
  category: SidebarItemCategory;
17
16
  level: number;
18
17
  }) => {
19
- const topNavItem = useTopNavigationItem();
20
18
  const isCategoryOpen = useIsCategoryOpen(category);
21
19
  const [hasInteracted, setHasInteracted] = useState(false);
22
20
 
@@ -26,7 +24,7 @@ export const SidebarCategory = ({
26
24
  !isCollapsible || !isCollapsed || isCategoryOpen,
27
25
  );
28
26
  const [open, setOpen] = useState(isDefaultOpen);
29
- const isActive = useMatch(joinPath(topNavItem?.id, category.link?.id));
27
+ const isActive = useMatch(category.link?.id ?? "");
30
28
 
31
29
  useEffect(() => {
32
30
  // this is triggered when an item from the sidebar is clicked
@@ -87,7 +85,7 @@ export const SidebarCategory = ({
87
85
  )}
88
86
  {category.link?.type === "doc" ? (
89
87
  <NavLink
90
- to={joinPath(topNavItem?.id, category.link.id)}
88
+ to={joinPath(category.link.id)}
91
89
  className="flex-1"
92
90
  onClick={() => {
93
91
  // if it is the current path and closed then open it because there's no path change to trigger the open
@@ -6,7 +6,6 @@ import type { SidebarItem as SidebarItemType } from "../../../config/validators/
6
6
  import { joinPath } from "../../util/joinPath.js";
7
7
  import { AnchorLink } from "../AnchorLink.js";
8
8
  import { useViewportAnchor } from "../context/ViewportAnchorContext.js";
9
- import { useTopNavigationItem } from "../context/ZudokuContext.js";
10
9
  import { SidebarBadge } from "./SidebarBadge.js";
11
10
  import { SidebarCategory } from "./SidebarCategory.js";
12
11
 
@@ -43,7 +42,6 @@ export const SidebarItem = ({
43
42
  basePath?: string;
44
43
  level?: number;
45
44
  }) => {
46
- const topNavItem = useTopNavigationItem();
47
45
  const { activeAnchor } = useViewportAnchor();
48
46
  const [searchParams] = useSearchParams();
49
47
 
@@ -56,7 +54,7 @@ export const SidebarItem = ({
56
54
  className={({ isActive }) =>
57
55
  navigationListItem({ isActive, isTopLevel: level === 0 })
58
56
  }
59
- to={joinPath(topNavItem?.id, item.id)}
57
+ to={joinPath(item.id)}
60
58
  >
61
59
  {item.icon && <item.icon size={16} className="align-[-0.125em]" />}
62
60
  {item.badge ? (
@@ -4,7 +4,7 @@ import type {
4
4
  SidebarItemCategory,
5
5
  } from "../../../config/validators/SidebarSchema.js";
6
6
  import { joinPath } from "../../util/joinPath.js";
7
- import { useTopNavigationItem, useZudoku } from "../context/ZudokuContext.js";
7
+ import { useCurrentNavigation } from "../context/ZudokuContext.js";
8
8
 
9
9
  export type TraverseCallback<T> = (
10
10
  item: SidebarItem,
@@ -42,15 +42,12 @@ export const traverseSidebarItem = <T>(
42
42
 
43
43
  export const useCurrentItem = () => {
44
44
  const location = useLocation();
45
- const topNavItem = useTopNavigationItem();
46
- const { sidebars } = useZudoku();
47
- const currentSidebar = topNavItem?.id ? sidebars[topNavItem.id] : [];
45
+ const nav = useCurrentNavigation();
46
+
47
+ const currentSidebar = nav.data.sidebar;
48
48
 
49
49
  return traverseSidebar(currentSidebar, (item) => {
50
- if (
51
- item.type === "doc" &&
52
- joinPath(topNavItem?.id, item.id) === location.pathname
53
- ) {
50
+ if (item.type === "doc" && joinPath(item.id) === location.pathname) {
54
51
  return item;
55
52
  }
56
53
  });
@@ -58,18 +55,17 @@ export const useCurrentItem = () => {
58
55
 
59
56
  export const useIsCategoryOpen = (category: SidebarItemCategory) => {
60
57
  const location = useLocation();
61
- const topNavItem = useTopNavigationItem();
62
58
 
63
59
  return traverseSidebarItem(category, (item) => {
64
60
  if (item.type === "category" && item.link) {
65
- const categoryLinkPath = joinPath(topNavItem?.id, item.link.id);
61
+ const categoryLinkPath = joinPath(item.link.id);
66
62
  if (categoryLinkPath === location.pathname) {
67
63
  return true;
68
64
  }
69
65
  }
70
66
 
71
67
  if (item.type === "doc") {
72
- const docPath = joinPath(topNavItem?.id, item.id);
68
+ const docPath = joinPath(item.id);
73
69
  if (docPath === location.pathname) {
74
70
  return true;
75
71
  }
@@ -82,9 +78,8 @@ export const usePrevNext = (): {
82
78
  next?: { label: string; id: string };
83
79
  } => {
84
80
  const currentId = useLocation().pathname;
85
- const { sidebars } = useZudoku();
86
- const topNavItem = useTopNavigationItem();
87
- const currentSidebar = topNavItem?.id ? sidebars[topNavItem.id] : [];
81
+ const nav = useCurrentNavigation();
82
+ const currentSidebar = nav.data.sidebar;
88
83
 
89
84
  let prev;
90
85
  let next;
@@ -94,9 +89,9 @@ export const usePrevNext = (): {
94
89
  traverseSidebar(currentSidebar, (item) => {
95
90
  const itemId =
96
91
  item.type === "doc"
97
- ? joinPath(topNavItem?.id, item.id)
92
+ ? joinPath(item.id)
98
93
  : item.type === "category" && item.link
99
- ? joinPath(topNavItem?.id, item.link.id)
94
+ ? joinPath(item.link.id)
100
95
  : undefined;
101
96
 
102
97
  if (!itemId) return;
@@ -1,6 +1,7 @@
1
1
  import { QueryClient } from "@tanstack/react-query";
2
2
  import { ReactNode } from "react";
3
3
  import type { SidebarConfig } from "../../config/validators/SidebarSchema.js";
4
+ import { TopNavigationItem } from "../../config/validators/validate.js";
4
5
  import { type AuthenticationProvider } from "../authentication/authentication.js";
5
6
  import type { ComponentsContextType } from "../components/context/ComponentsContext.js";
6
7
  import { Slotlets } from "../components/SlotletProvider.js";
@@ -60,12 +61,7 @@ export type ZudokuContextOptions = {
60
61
  metadata?: Metadata;
61
62
  page?: Page;
62
63
  authentication?: AuthenticationProvider;
63
- topNavigation?: Array<{
64
- id: string;
65
- label: string;
66
- default?: string;
67
- display?: "auth" | "anon" | "always";
68
- }>;
64
+ topNavigation?: TopNavigationItem[];
69
65
  sidebars?: SidebarConfig;
70
66
  plugins?: DevPortalPlugin[];
71
67
  slotlets?: Slotlets;
@@ -77,7 +73,7 @@ export type ZudokuContextOptions = {
77
73
 
78
74
  export class DevPortalContext {
79
75
  public plugins: NonNullable<ZudokuContextOptions["plugins"]>;
80
- public sidebars: NonNullable<ZudokuContextOptions["sidebars"]>;
76
+ public sidebars: SidebarConfig;
81
77
  public topNavigation: NonNullable<ZudokuContextOptions["topNavigation"]>;
82
78
  public meta: ZudokuContextOptions["metadata"];
83
79
  public page: ZudokuContextOptions["page"];
@@ -11,6 +11,8 @@ export type DevPortalPlugin =
11
11
  | ApiIdentityPlugin
12
12
  | SearchProviderPlugin;
13
13
 
14
+ export type { RouteObject };
15
+
14
16
  export interface NavigationPlugin {
15
17
  getRoutes: () => RouteObject[];
16
18
  getSidebar?: (path: string) => Promise<Sidebar>;
@@ -9,8 +9,8 @@ export const CustomPage = ({
9
9
  render,
10
10
  prose = true,
11
11
  }: Omit<CustomPageConfig, "path">) => {
12
- const slotletProps = useExposedProps();
13
- const content = render ? React.createElement(render, slotletProps) : element;
12
+ const exposedProps = useExposedProps();
13
+ const content = render ? React.createElement(render, exposedProps) : element;
14
14
 
15
15
  return (
16
16
  <div className={cn(prose && ProseClasses, "max-w-full")}>{content}</div>
@@ -1,13 +1,13 @@
1
1
  import type { Toc } from "@stefanprobst/rehype-extract-toc";
2
2
  import type { MDXProps } from "mdx/types.js";
3
+ import { RouteObject } from "react-router-dom";
4
+ import { ZudokuDocsConfig } from "../../../config/validators/validate.js";
3
5
  import type { DevPortalPlugin } from "../../core/plugins.js";
4
- import { generateRoutes } from "./generateRoutes.js";
6
+ import { DocResolver } from "./resolver.js";
5
7
 
6
- export type MarkdownPluginOptions = {
7
- markdownFiles: Record<string, () => Promise<MDXImport>>;
8
- defaultOptions?: MarkdownPluginDefaultOptions;
9
- filesPath: string;
10
- };
8
+ export interface MarkdownPluginOptions extends ZudokuDocsConfig {
9
+ fileImports: Record<string, () => Promise<MDXImport>>;
10
+ }
11
11
  export type MarkdownPluginDefaultOptions = Pick<
12
12
  Frontmatter,
13
13
  "toc" | "disablePager"
@@ -27,10 +27,47 @@ export type MDXImport = {
27
27
  default: (props: MDXProps) => JSX.Element;
28
28
  };
29
29
 
30
- export const markdownPlugin = ({
31
- markdownFiles,
32
- defaultOptions,
33
- filesPath,
34
- }: MarkdownPluginOptions): DevPortalPlugin => ({
35
- getRoutes: () => generateRoutes(markdownFiles, filesPath, defaultOptions),
30
+ export const markdownPlugin = (
31
+ options: MarkdownPluginOptions[],
32
+ ): DevPortalPlugin => ({
33
+ getRoutes: () => {
34
+ const routeMap = new Map<string, RouteObject>();
35
+ options.forEach(({ fileImports, files, defaultOptions }) =>
36
+ Object.entries(fileImports).flatMap(([file, importPromise]) => {
37
+ const routePath = DocResolver.resolveRoutePath({
38
+ filesGlob: files,
39
+ fsPath: file,
40
+ });
41
+
42
+ if (!routePath) return [];
43
+
44
+ if (routeMap.has(routePath)) {
45
+ // eslint-disable-next-line no-console
46
+ console.warn(
47
+ `Duplicate route path found for ${routePath}. Skipping file at '${file}'.`,
48
+ );
49
+ return [];
50
+ }
51
+
52
+ const route: RouteObject = {
53
+ path: routePath,
54
+ lazy: async () => {
55
+ const { MdxPage } = await import("./MdxPage.js");
56
+ const { default: Component, ...props } = await importPromise();
57
+ return {
58
+ element: (
59
+ <MdxPage
60
+ mdxComponent={Component}
61
+ {...props}
62
+ defaultOptions={defaultOptions}
63
+ />
64
+ ),
65
+ };
66
+ },
67
+ };
68
+ routeMap.set(routePath, route);
69
+ }),
70
+ );
71
+ return [...routeMap.values()];
72
+ },
36
73
  });
@@ -0,0 +1,92 @@
1
+ import fs from "fs";
2
+ import path from "path";
3
+ import {
4
+ ZudokuConfig,
5
+ ZudokuDocsConfig,
6
+ } from "../../../config/validators/validate.js";
7
+
8
+ const DEFAULT_DOCS_FILES = "/pages/**/*.{md,mdx}";
9
+
10
+ // TODO: This should be dynamic based on the glob selector
11
+ const SUPPORTED_EXTENSIONS = [".md", ".mdx"];
12
+
13
+ /**
14
+ * Utilities for resolving markdown file paths and routes
15
+ */
16
+ export class DocResolver {
17
+ constructor(private config: ZudokuConfig) {}
18
+
19
+ fileMap = new Map<string, string>();
20
+
21
+ /**
22
+ * Gets the default docs config from the zudoku config
23
+ */
24
+ getDocsConfigs() {
25
+ const docsConfigs: ZudokuDocsConfig[] = this.config.docs
26
+ ? Array.isArray(this.config.docs)
27
+ ? this.config.docs
28
+ : [this.config.docs]
29
+ : [{ files: DEFAULT_DOCS_FILES }];
30
+
31
+ return docsConfigs;
32
+ }
33
+
34
+ /**
35
+ * Resolves the first matching file system path for a given docId
36
+ * @param docId - The docId to resolve
37
+ * @returns
38
+ */
39
+ resolveFilePath(docId: string) {
40
+ const docsConfigs = this.getDocsConfigs();
41
+ let fsPath: string | undefined;
42
+
43
+ docsConfigs.forEach(({ files: fileGlob }) => {
44
+ if (fsPath) {
45
+ return;
46
+ }
47
+ const rootDir = DocResolver.getRootDir(fileGlob);
48
+ for (const ext of SUPPORTED_EXTENSIONS) {
49
+ if (fsPath) {
50
+ return;
51
+ }
52
+ const checkPath = path.join(rootDir, `${docId}${ext}`);
53
+ if (fs.existsSync(checkPath)) {
54
+ fsPath = checkPath;
55
+ }
56
+ }
57
+ });
58
+
59
+ return fsPath;
60
+ }
61
+
62
+ /**
63
+ * Gets the root directory from a files glob
64
+ */
65
+ private static getRootDir(filesGlob: string) {
66
+ let rootDir = filesGlob.split("**")[0];
67
+ if (!rootDir) {
68
+ throw new Error("Invalid files glob. Must have '**' in the path.");
69
+ }
70
+ rootDir = rootDir.replace("/**", "/");
71
+ return rootDir;
72
+ }
73
+
74
+ /**
75
+ * Resolves the route path for a given file system path
76
+ * @param options - The options to resolve the route path
77
+ * @returns The string route path
78
+ */
79
+ static resolveRoutePath({
80
+ filesGlob,
81
+ fsPath,
82
+ }: {
83
+ filesGlob: string;
84
+ fsPath: string;
85
+ }) {
86
+ const rootDir = this.getRootDir(filesGlob);
87
+ const re = new RegExp(`^${rootDir}(.*).mdx?`);
88
+ const match = fsPath.match(re);
89
+ const routePath = match?.at(1);
90
+ return routePath;
91
+ }
92
+ }
@@ -62,9 +62,9 @@ export const Endpoint = () => {
62
62
  <div className="flex items-center gap-2">
63
63
  <span className="font-medium text-sm">Endpoint:</span>
64
64
  <InlineCode className="text-xs px-2 py-1.5" selectOnClick>
65
- {servers[0].url}
65
+ {servers[0]!.url}
66
66
  </InlineCode>
67
- <CopyButton url={servers[0].url} />
67
+ <CopyButton url={servers[0]!.url} />
68
68
  </div>
69
69
  );
70
70
  }
@@ -26,7 +26,7 @@ export const RequestBodySidecarBox = ({ content }: { content: Content }) => {
26
26
  className="text-xs max-h-[450px] p-2"
27
27
  code={JSON.stringify(
28
28
  content.at(0)?.schema
29
- ? generateSchemaExample(content[0].schema as SchemaObject)
29
+ ? generateSchemaExample(content[0]!.schema as SchemaObject)
30
30
  : "",
31
31
  null,
32
32
  2,
@@ -198,7 +198,7 @@ export const Sidecar = ({
198
198
  language={selectedLang}
199
199
  noBackground
200
200
  className="text-xs p-2"
201
- code={code}
201
+ code={code!}
202
202
  />
203
203
  </SidecarBox.Body>
204
204
  <SidecarBox.Footer className="flex items-center text-xs gap-2 justify-end py-1">
@@ -10,7 +10,7 @@ worker.addEventListener(
10
10
  function (event: MessageEvent<{ id: string; body: string }>) {
11
11
  const port = event.ports[0];
12
12
 
13
- port.onmessage = async function (e) {
13
+ port!.onmessage = async function (e) {
14
14
  const response = await localServer.fetch(
15
15
  new Request("/__z/graphql", {
16
16
  method: "POST",
@@ -21,7 +21,7 @@ worker.addEventListener(
21
21
  }),
22
22
  );
23
23
 
24
- port.postMessage({
24
+ port!.postMessage({
25
25
  id: e.data.id,
26
26
  body: await response.text(),
27
27
  } satisfies WorkerGraphQLMessage);
@@ -170,7 +170,7 @@ export const openApiPlugin = (
170
170
  href: `#${operation.slug}`,
171
171
  badge: {
172
172
  label: operation.method,
173
- color: MethodColorMap[operation.method.toLowerCase()],
173
+ color: MethodColorMap[operation.method.toLowerCase()]!,
174
174
  },
175
175
  })),
176
176
  }));
@@ -0,0 +1,151 @@
1
+ import { type DialogProps } from "@radix-ui/react-dialog";
2
+ import { Command as CommandPrimitive } from "cmdk";
3
+ import { Search } from "lucide-react";
4
+ import * as React from "react";
5
+ import { Dialog, DialogContent } from "zudoku/ui/Dialog.js";
6
+ import { cn } from "../util/cn.js";
7
+
8
+ const Command = React.forwardRef<
9
+ React.ElementRef<typeof CommandPrimitive>,
10
+ React.ComponentPropsWithoutRef<typeof CommandPrimitive>
11
+ >(({ className, ...props }, ref) => (
12
+ <CommandPrimitive
13
+ ref={ref}
14
+ className={cn(
15
+ "flex h-full w-full flex-col overflow-hidden rounded-md bg-popover text-popover-foreground",
16
+ className,
17
+ )}
18
+ {...props}
19
+ />
20
+ ));
21
+ Command.displayName = CommandPrimitive.displayName;
22
+
23
+ const CommandDialog = ({ children, ...props }: DialogProps) => {
24
+ return (
25
+ <Dialog {...props}>
26
+ <DialogContent className="overflow-hidden p-0 shadow-lg">
27
+ <Command className="[&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:font-medium [&_[cmdk-group-heading]]:text-muted-foreground [&_[cmdk-group]:not([hidden])_~[cmdk-group]]:pt-0 [&_[cmdk-group]]:px-2 [&_[cmdk-input-wrapper]_svg]:h-5 [&_[cmdk-input-wrapper]_svg]:w-5 [&_[cmdk-input]]:h-12 [&_[cmdk-item]]:px-2 [&_[cmdk-item]]:py-3 [&_[cmdk-item]_svg]:h-5 [&_[cmdk-item]_svg]:w-5">
28
+ {children}
29
+ </Command>
30
+ </DialogContent>
31
+ </Dialog>
32
+ );
33
+ };
34
+
35
+ const CommandInput = React.forwardRef<
36
+ React.ElementRef<typeof CommandPrimitive.Input>,
37
+ React.ComponentPropsWithoutRef<typeof CommandPrimitive.Input>
38
+ >(({ className, ...props }, ref) => (
39
+ // eslint-disable-next-line react/no-unknown-property
40
+ <div className="flex items-center border-b px-3" cmdk-input-wrapper="">
41
+ <Search className="mr-2 h-4 w-4 shrink-0 opacity-50" />
42
+ <CommandPrimitive.Input
43
+ ref={ref}
44
+ className={cn(
45
+ "flex h-11 w-full rounded-md bg-transparent py-3 text-sm outline-none placeholder:text-muted-foreground disabled:cursor-not-allowed disabled:opacity-50",
46
+ className,
47
+ )}
48
+ {...props}
49
+ />
50
+ </div>
51
+ ));
52
+
53
+ CommandInput.displayName = CommandPrimitive.Input.displayName;
54
+
55
+ const CommandList = React.forwardRef<
56
+ React.ElementRef<typeof CommandPrimitive.List>,
57
+ React.ComponentPropsWithoutRef<typeof CommandPrimitive.List>
58
+ >(({ className, ...props }, ref) => (
59
+ <CommandPrimitive.List
60
+ ref={ref}
61
+ className={cn("max-h-[300px] overflow-y-auto overflow-x-hidden", className)}
62
+ {...props}
63
+ />
64
+ ));
65
+
66
+ CommandList.displayName = CommandPrimitive.List.displayName;
67
+
68
+ const CommandEmpty = React.forwardRef<
69
+ React.ElementRef<typeof CommandPrimitive.Empty>,
70
+ React.ComponentPropsWithoutRef<typeof CommandPrimitive.Empty>
71
+ >((props, ref) => (
72
+ <CommandPrimitive.Empty
73
+ ref={ref}
74
+ className="py-6 text-center text-sm"
75
+ {...props}
76
+ />
77
+ ));
78
+
79
+ CommandEmpty.displayName = CommandPrimitive.Empty.displayName;
80
+
81
+ const CommandGroup = React.forwardRef<
82
+ React.ElementRef<typeof CommandPrimitive.Group>,
83
+ React.ComponentPropsWithoutRef<typeof CommandPrimitive.Group>
84
+ >(({ className, ...props }, ref) => (
85
+ <CommandPrimitive.Group
86
+ ref={ref}
87
+ className={cn(
88
+ "overflow-hidden p-1 text-foreground [&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:py-1.5 [&_[cmdk-group-heading]]:text-xs [&_[cmdk-group-heading]]:font-medium [&_[cmdk-group-heading]]:text-muted-foreground",
89
+ className,
90
+ )}
91
+ {...props}
92
+ />
93
+ ));
94
+
95
+ CommandGroup.displayName = CommandPrimitive.Group.displayName;
96
+
97
+ const CommandSeparator = React.forwardRef<
98
+ React.ElementRef<typeof CommandPrimitive.Separator>,
99
+ React.ComponentPropsWithoutRef<typeof CommandPrimitive.Separator>
100
+ >(({ className, ...props }, ref) => (
101
+ <CommandPrimitive.Separator
102
+ ref={ref}
103
+ className={cn("-mx-1 h-px bg-border", className)}
104
+ {...props}
105
+ />
106
+ ));
107
+ CommandSeparator.displayName = CommandPrimitive.Separator.displayName;
108
+
109
+ const CommandItem = React.forwardRef<
110
+ React.ElementRef<typeof CommandPrimitive.Item>,
111
+ React.ComponentPropsWithoutRef<typeof CommandPrimitive.Item>
112
+ >(({ className, ...props }, ref) => (
113
+ <CommandPrimitive.Item
114
+ ref={ref}
115
+ className={cn(
116
+ "relative flex cursor-default gap-2 select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none data-[disabled=true]:pointer-events-none data-[selected='true']:bg-accent data-[selected=true]:text-accent-foreground data-[disabled=true]:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
117
+ className,
118
+ )}
119
+ {...props}
120
+ />
121
+ ));
122
+
123
+ CommandItem.displayName = CommandPrimitive.Item.displayName;
124
+
125
+ const CommandShortcut = ({
126
+ className,
127
+ ...props
128
+ }: React.HTMLAttributes<HTMLSpanElement>) => {
129
+ return (
130
+ <span
131
+ className={cn(
132
+ "ml-auto text-xs tracking-widest text-muted-foreground",
133
+ className,
134
+ )}
135
+ {...props}
136
+ />
137
+ );
138
+ };
139
+ CommandShortcut.displayName = "CommandShortcut";
140
+
141
+ export {
142
+ Command,
143
+ CommandDialog,
144
+ CommandEmpty,
145
+ CommandGroup,
146
+ CommandInput,
147
+ CommandItem,
148
+ CommandList,
149
+ CommandSeparator,
150
+ CommandShortcut,
151
+ };