zudoku 0.37.1 → 0.39.0

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 (260) hide show
  1. package/dist/app/main.js +2 -0
  2. package/dist/app/main.js.map +1 -1
  3. package/dist/app/tailwind.js +14 -0
  4. package/dist/app/tailwind.js.map +1 -1
  5. package/dist/config/validators/common.d.ts +287 -18
  6. package/dist/config/validators/common.js +2 -0
  7. package/dist/config/validators/common.js.map +1 -1
  8. package/dist/config/validators/validate.d.ts +107 -7
  9. package/dist/lib/authentication/authentication.d.ts +1 -0
  10. package/dist/lib/authentication/providers/clerk.js +19 -0
  11. package/dist/lib/authentication/providers/clerk.js.map +1 -1
  12. package/dist/lib/authentication/providers/openid.d.ts +1 -0
  13. package/dist/lib/authentication/providers/openid.js +5 -0
  14. package/dist/lib/authentication/providers/openid.js.map +1 -1
  15. package/dist/lib/authentication/providers/supabase.js +5 -0
  16. package/dist/lib/authentication/providers/supabase.js.map +1 -1
  17. package/dist/lib/authentication/state.d.ts +0 -26
  18. package/dist/lib/authentication/state.js +1 -16
  19. package/dist/lib/authentication/state.js.map +1 -1
  20. package/dist/lib/components/Heading.d.ts +1 -1
  21. package/dist/lib/components/Layout.js +5 -10
  22. package/dist/lib/components/Layout.js.map +1 -1
  23. package/dist/lib/components/Main.js +3 -1
  24. package/dist/lib/components/Main.js.map +1 -1
  25. package/dist/lib/components/Pagination.d.ts +10 -0
  26. package/dist/lib/components/Pagination.js +10 -0
  27. package/dist/lib/components/Pagination.js.map +1 -0
  28. package/dist/lib/components/TopNavigation.js +18 -2
  29. package/dist/lib/components/TopNavigation.js.map +1 -1
  30. package/dist/lib/components/navigation/SidebarItem.d.ts +1 -0
  31. package/dist/lib/components/navigation/SidebarItem.js +7 -3
  32. package/dist/lib/components/navigation/SidebarItem.js.map +1 -1
  33. package/dist/lib/{plugins/markdown → components/navigation}/Toc.js +5 -7
  34. package/dist/lib/components/navigation/Toc.js.map +1 -0
  35. package/dist/lib/core/ZudokuContext.d.ts +7 -0
  36. package/dist/lib/core/ZudokuContext.js +8 -3
  37. package/dist/lib/core/ZudokuContext.js.map +1 -1
  38. package/dist/lib/core/plugins.d.ts +1 -1
  39. package/dist/lib/oas/graphql/index.d.ts +2 -1
  40. package/dist/lib/oas/graphql/index.js +74 -14
  41. package/dist/lib/oas/graphql/index.js.map +1 -1
  42. package/dist/lib/oas/parser/dereference/index.js +2 -0
  43. package/dist/lib/oas/parser/dereference/index.js.map +1 -1
  44. package/dist/lib/oas/parser/index.d.ts +5 -3
  45. package/dist/lib/oas/parser/index.js +0 -22
  46. package/dist/lib/oas/parser/index.js.map +1 -1
  47. package/dist/lib/plugins/api-catalog/index.js +19 -17
  48. package/dist/lib/plugins/api-catalog/index.js.map +1 -1
  49. package/dist/lib/plugins/markdown/MdxPage.js +3 -9
  50. package/dist/lib/plugins/markdown/MdxPage.js.map +1 -1
  51. package/dist/lib/plugins/openapi/ColorizedParam.js +1 -1
  52. package/dist/lib/plugins/openapi/ColorizedParam.js.map +1 -1
  53. package/dist/lib/plugins/openapi/Endpoint.js +1 -1
  54. package/dist/lib/plugins/openapi/Endpoint.js.map +1 -1
  55. package/dist/lib/plugins/openapi/OperationList.d.ts +1 -1
  56. package/dist/lib/plugins/openapi/OperationList.js +29 -9
  57. package/dist/lib/plugins/openapi/OperationList.js.map +1 -1
  58. package/dist/lib/plugins/openapi/OperationListItem.js +1 -1
  59. package/dist/lib/plugins/openapi/OperationListItem.js.map +1 -1
  60. package/dist/lib/plugins/openapi/ParameterListItem.js +2 -1
  61. package/dist/lib/plugins/openapi/ParameterListItem.js.map +1 -1
  62. package/dist/lib/plugins/openapi/SchemaList.d.ts +1 -0
  63. package/dist/lib/plugins/openapi/SchemaList.js +52 -0
  64. package/dist/lib/plugins/openapi/SchemaList.js.map +1 -0
  65. package/dist/lib/plugins/openapi/Sidecar.js +29 -5
  66. package/dist/lib/plugins/openapi/Sidecar.js.map +1 -1
  67. package/dist/lib/plugins/openapi/client/GraphQLClient.d.ts +1 -1
  68. package/dist/lib/plugins/openapi/client/GraphQLClient.js +1 -1
  69. package/dist/lib/plugins/openapi/client/GraphQLClient.js.map +1 -1
  70. package/dist/lib/plugins/openapi/client/useCreateQuery.d.ts +6 -2
  71. package/dist/lib/plugins/openapi/client/useCreateQuery.js +5 -5
  72. package/dist/lib/plugins/openapi/client/useCreateQuery.js.map +1 -1
  73. package/dist/lib/plugins/openapi/components/EnumValues.js +1 -1
  74. package/dist/lib/plugins/openapi/components/EnumValues.js.map +1 -1
  75. package/dist/lib/plugins/openapi/graphql/gql.d.ts +6 -2
  76. package/dist/lib/plugins/openapi/graphql/gql.js +3 -2
  77. package/dist/lib/plugins/openapi/graphql/gql.js.map +1 -1
  78. package/dist/lib/plugins/openapi/graphql/graphql.d.ts +67 -11
  79. package/dist/lib/plugins/openapi/graphql/graphql.js +34 -5
  80. package/dist/lib/plugins/openapi/graphql/graphql.js.map +1 -1
  81. package/dist/lib/plugins/openapi/index.js +12 -0
  82. package/dist/lib/plugins/openapi/index.js.map +1 -1
  83. package/dist/lib/plugins/openapi/interfaces.d.ts +26 -0
  84. package/dist/lib/plugins/openapi/playground/Playground.js +1 -1
  85. package/dist/lib/plugins/openapi/playground/Playground.js.map +1 -1
  86. package/dist/lib/plugins/openapi/playground/result-panel/ResultPanel.js +2 -2
  87. package/dist/lib/plugins/openapi/playground/result-panel/ResultPanel.js.map +1 -1
  88. package/dist/lib/plugins/openapi/schema/LogicalGroup/LogicalGroup.d.ts +1 -2
  89. package/dist/lib/plugins/openapi/schema/LogicalGroup/LogicalGroup.js +2 -2
  90. package/dist/lib/plugins/openapi/schema/LogicalGroup/LogicalGroup.js.map +1 -1
  91. package/dist/lib/plugins/openapi/schema/LogicalGroup/LogicalGroupItem.d.ts +0 -1
  92. package/dist/lib/plugins/openapi/schema/LogicalGroup/LogicalGroupItem.js +1 -1
  93. package/dist/lib/plugins/openapi/schema/LogicalGroup/LogicalGroupItem.js.map +1 -1
  94. package/dist/lib/plugins/openapi/schema/SchemaExampleAndDefault.d.ts +4 -0
  95. package/dist/lib/plugins/openapi/schema/SchemaExampleAndDefault.js +12 -0
  96. package/dist/lib/plugins/openapi/schema/SchemaExampleAndDefault.js.map +1 -0
  97. package/dist/lib/plugins/openapi/schema/SchemaPropertyItem.d.ts +2 -4
  98. package/dist/lib/plugins/openapi/schema/SchemaPropertyItem.js +12 -9
  99. package/dist/lib/plugins/openapi/schema/SchemaPropertyItem.js.map +1 -1
  100. package/dist/lib/plugins/openapi/schema/SchemaView.d.ts +1 -2
  101. package/dist/lib/plugins/openapi/schema/SchemaView.js +30 -52
  102. package/dist/lib/plugins/openapi/schema/SchemaView.js.map +1 -1
  103. package/dist/lib/plugins/openapi/schema/utils.d.ts +1 -0
  104. package/dist/lib/plugins/openapi/schema/utils.js +3 -1
  105. package/dist/lib/plugins/openapi/schema/utils.js.map +1 -1
  106. package/dist/lib/plugins/openapi/state.d.ts +25 -0
  107. package/dist/lib/plugins/openapi/state.js +18 -0
  108. package/dist/lib/plugins/openapi/state.js.map +1 -0
  109. package/dist/lib/plugins/openapi/util/getRoutes.js +9 -3
  110. package/dist/lib/plugins/openapi/util/getRoutes.js.map +1 -1
  111. package/dist/lib/plugins/search-pagefind/PagefindSearch.js +13 -4
  112. package/dist/lib/plugins/search-pagefind/PagefindSearch.js.map +1 -1
  113. package/dist/lib/plugins/search-pagefind/ResultList.js +19 -12
  114. package/dist/lib/plugins/search-pagefind/ResultList.js.map +1 -1
  115. package/dist/lib/plugins/search-pagefind/get-results.d.ts +8 -1
  116. package/dist/lib/plugins/search-pagefind/get-results.js +9 -4
  117. package/dist/lib/plugins/search-pagefind/get-results.js.map +1 -1
  118. package/dist/lib/util/traverse.d.ts +2 -8
  119. package/dist/lib/util/traverse.js.map +1 -1
  120. package/dist/lib/util/types.d.ts +7 -0
  121. package/dist/lib/util/types.js +2 -0
  122. package/dist/lib/util/types.js.map +1 -0
  123. package/dist/lib/util/useOnScreen.d.ts +3 -2
  124. package/dist/lib/util/useOnScreen.js +3 -3
  125. package/dist/lib/util/useOnScreen.js.map +1 -1
  126. package/dist/lib/util/useScrollToAnchor.js +18 -12
  127. package/dist/lib/util/useScrollToAnchor.js.map +1 -1
  128. package/dist/vite/api/schema-codegen.js +2 -2
  129. package/dist/vite/api/schema-codegen.js.map +1 -1
  130. package/dist/vite/api/schema-codegen.test.js +5 -0
  131. package/dist/vite/api/schema-codegen.test.js.map +1 -1
  132. package/dist/vite/plugin-api.js +12 -8
  133. package/dist/vite/plugin-api.js.map +1 -1
  134. package/lib/{AuthenticationPlugin-Cij2tPWa.js → AuthenticationPlugin-foqdvvkf.js} +3 -3
  135. package/lib/{AuthenticationPlugin-Cij2tPWa.js.map → AuthenticationPlugin-foqdvvkf.js.map} +1 -1
  136. package/lib/{Callout-B2vsR09t.js → Callout-D5frCCJ0.js} +2 -2
  137. package/lib/{Callout-B2vsR09t.js.map → Callout-D5frCCJ0.js.map} +1 -1
  138. package/lib/{Dialog-sbgekbjb.js → Dialog-Dv6WG8RN.js} +5 -5
  139. package/lib/{Dialog-sbgekbjb.js.map → Dialog-Dv6WG8RN.js.map} +1 -1
  140. package/lib/{Markdown-DT5Rrq8_.js → Markdown-aF5FdsNi.js} +1945 -1937
  141. package/lib/{Markdown-DT5Rrq8_.js.map → Markdown-aF5FdsNi.js.map} +1 -1
  142. package/lib/MdxPage-ZW1StNhp.js +83 -0
  143. package/lib/MdxPage-ZW1StNhp.js.map +1 -0
  144. package/lib/{OasProvider-DdEBf2qS.js → OasProvider-Cld9RAMQ.js} +4 -4
  145. package/lib/{OasProvider-DdEBf2qS.js.map → OasProvider-Cld9RAMQ.js.map} +1 -1
  146. package/lib/OperationList-D-OfzJm6.js +5065 -0
  147. package/lib/OperationList-D-OfzJm6.js.map +1 -0
  148. package/lib/Pagination-CYB3nVYx.js +46 -0
  149. package/lib/Pagination-CYB3nVYx.js.map +1 -0
  150. package/lib/SchemaList-Ci1WxRh0.js +148 -0
  151. package/lib/SchemaList-Ci1WxRh0.js.map +1 -0
  152. package/lib/SchemaView-Brn-YxHY.js +345 -0
  153. package/lib/SchemaView-Brn-YxHY.js.map +1 -0
  154. package/lib/{Select-z1Lwl0-J.js → Select-DVFRKf1R.js} +8 -8
  155. package/lib/{Select-z1Lwl0-J.js.map → Select-DVFRKf1R.js.map} +1 -1
  156. package/lib/{SlotletProvider-D8OBnr77.js → SlotletProvider-DXvc0aY6.js} +4 -4
  157. package/lib/{SlotletProvider-D8OBnr77.js.map → SlotletProvider-DXvc0aY6.js.map} +1 -1
  158. package/lib/Toc-YBsgI72s.js +92 -0
  159. package/lib/Toc-YBsgI72s.js.map +1 -0
  160. package/lib/{chunk-HA7DTUK3-ZGg2W6yV.js → chunk-HA7DTUK3-C4gP41vD.js} +5 -5
  161. package/lib/{chunk-HA7DTUK3-ZGg2W6yV.js.map → chunk-HA7DTUK3-C4gP41vD.js.map} +1 -1
  162. package/lib/{createServer-DjgKDpGV.js → createServer-mMau3eV_.js} +1732 -1664
  163. package/lib/{createServer-DjgKDpGV.js.map → createServer-mMau3eV_.js.map} +1 -1
  164. package/lib/hook-CqpVYDqN.js +1483 -0
  165. package/lib/hook-CqpVYDqN.js.map +1 -0
  166. package/lib/index-Bt7MKhZq.js +2514 -0
  167. package/lib/index-Bt7MKhZq.js.map +1 -0
  168. package/lib/{index-DdQSV2RF.js → index-CjPMxpOV.js} +809 -750
  169. package/lib/index-CjPMxpOV.js.map +1 -0
  170. package/lib/{mutation-_Z5C2wFZ.js → mutation-8LjrN7uz.js} +2 -2
  171. package/lib/{mutation-_Z5C2wFZ.js.map → mutation-8LjrN7uz.js.map} +1 -1
  172. package/lib/post-processors/traverse.js.map +1 -1
  173. package/lib/ui/Command.js +1 -1
  174. package/lib/{useExposedProps-BslIn-FE.js → useExposedProps-B9qXJedG.js} +2 -2
  175. package/lib/{useExposedProps-BslIn-FE.js.map → useExposedProps-B9qXJedG.js.map} +1 -1
  176. package/lib/zudoku.auth-auth0.js +1 -1
  177. package/lib/zudoku.auth-clerk.js +59 -41
  178. package/lib/zudoku.auth-clerk.js.map +1 -1
  179. package/lib/zudoku.auth-openid.js +76 -73
  180. package/lib/zudoku.auth-openid.js.map +1 -1
  181. package/lib/zudoku.components.js +31 -1440
  182. package/lib/zudoku.components.js.map +1 -1
  183. package/lib/zudoku.hooks.js +1 -1
  184. package/lib/zudoku.plugin-api-catalog.js +81 -79
  185. package/lib/zudoku.plugin-api-catalog.js.map +1 -1
  186. package/lib/zudoku.plugin-api-keys.js +15 -16
  187. package/lib/zudoku.plugin-api-keys.js.map +1 -1
  188. package/lib/zudoku.plugin-custom-pages.js +2 -2
  189. package/lib/zudoku.plugin-markdown.js +1 -1
  190. package/lib/zudoku.plugin-openapi.js +5 -6
  191. package/lib/zudoku.plugin-openapi.js.map +1 -1
  192. package/lib/zudoku.plugin-redirect.js +1 -1
  193. package/lib/zudoku.plugin-search-pagefind.js +133 -98
  194. package/lib/zudoku.plugin-search-pagefind.js.map +1 -1
  195. package/lib/zudoku.plugins.js.map +1 -1
  196. package/package.json +4 -3
  197. package/src/app/main.tsx +2 -0
  198. package/src/app/tailwind.ts +14 -0
  199. package/src/lib/authentication/authentication.ts +2 -0
  200. package/src/lib/authentication/providers/clerk.tsx +20 -0
  201. package/src/lib/authentication/providers/openid.tsx +6 -0
  202. package/src/lib/authentication/providers/supabase.tsx +6 -0
  203. package/src/lib/authentication/state.ts +1 -35
  204. package/src/lib/components/Layout.tsx +17 -17
  205. package/src/lib/components/Main.tsx +3 -1
  206. package/src/lib/components/Pagination.tsx +47 -0
  207. package/src/lib/components/TopNavigation.tsx +29 -2
  208. package/src/lib/components/navigation/SidebarItem.tsx +10 -4
  209. package/src/lib/{plugins/markdown → components/navigation}/Toc.tsx +5 -14
  210. package/src/lib/core/ZudokuContext.ts +13 -6
  211. package/src/lib/core/plugins.ts +1 -1
  212. package/src/lib/oas/graphql/index.ts +118 -45
  213. package/src/lib/oas/parser/dereference/index.ts +2 -0
  214. package/src/lib/oas/parser/index.ts +7 -29
  215. package/src/lib/plugins/api-catalog/index.tsx +40 -35
  216. package/src/lib/plugins/markdown/MdxPage.tsx +6 -43
  217. package/src/lib/plugins/openapi/ColorizedParam.tsx +1 -1
  218. package/src/lib/plugins/openapi/Endpoint.tsx +1 -1
  219. package/src/lib/plugins/openapi/OperationList.tsx +37 -16
  220. package/src/lib/plugins/openapi/OperationListItem.tsx +7 -2
  221. package/src/lib/plugins/openapi/ParameterListItem.tsx +2 -0
  222. package/src/lib/plugins/openapi/SchemaList.tsx +151 -0
  223. package/src/lib/plugins/openapi/Sidecar.tsx +36 -7
  224. package/src/lib/plugins/openapi/client/GraphQLClient.tsx +1 -1
  225. package/src/lib/plugins/openapi/client/useCreateQuery.ts +12 -5
  226. package/src/lib/plugins/openapi/components/EnumValues.tsx +1 -1
  227. package/src/lib/plugins/openapi/graphql/gql.ts +15 -6
  228. package/src/lib/plugins/openapi/graphql/graphql.ts +104 -15
  229. package/src/lib/plugins/openapi/index.tsx +13 -0
  230. package/src/lib/plugins/openapi/interfaces.ts +29 -0
  231. package/src/lib/plugins/openapi/playground/Playground.tsx +1 -1
  232. package/src/lib/plugins/openapi/playground/result-panel/ResultPanel.tsx +2 -1
  233. package/src/lib/plugins/openapi/schema/LogicalGroup/LogicalGroup.tsx +1 -8
  234. package/src/lib/plugins/openapi/schema/LogicalGroup/LogicalGroupItem.tsx +1 -2
  235. package/src/lib/plugins/openapi/schema/SchemaExampleAndDefault.tsx +36 -0
  236. package/src/lib/plugins/openapi/schema/SchemaPropertyItem.tsx +20 -21
  237. package/src/lib/plugins/openapi/schema/SchemaView.tsx +69 -141
  238. package/src/lib/plugins/openapi/schema/utils.ts +7 -1
  239. package/src/lib/plugins/openapi/state.ts +36 -0
  240. package/src/lib/plugins/openapi/util/getRoutes.tsx +9 -6
  241. package/src/lib/plugins/search-pagefind/PagefindSearch.tsx +26 -4
  242. package/src/lib/plugins/search-pagefind/ResultList.tsx +59 -47
  243. package/src/lib/plugins/search-pagefind/get-results.tsx +31 -10
  244. package/src/lib/util/traverse.ts +2 -6
  245. package/src/lib/util/types.ts +7 -0
  246. package/src/lib/util/useOnScreen.ts +6 -4
  247. package/src/lib/util/useScrollToAnchor.ts +20 -12
  248. package/dist/lib/plugins/markdown/Toc.js.map +0 -1
  249. package/lib/MdxPage-D2rD1vC4.js +0 -200
  250. package/lib/MdxPage-D2rD1vC4.js.map +0 -1
  251. package/lib/OperationList-DT4-gm_S.js +0 -5363
  252. package/lib/OperationList-DT4-gm_S.js.map +0 -1
  253. package/lib/hook-DzQC8PzJ.js +0 -355
  254. package/lib/hook-DzQC8PzJ.js.map +0 -1
  255. package/lib/index-DdQSV2RF.js.map +0 -1
  256. package/lib/index.esm-CltAN0Tf.js +0 -711
  257. package/lib/index.esm-CltAN0Tf.js.map +0 -1
  258. package/lib/joinUrl-BjDooT-T.js +0 -1154
  259. package/lib/joinUrl-BjDooT-T.js.map +0 -1
  260. /package/dist/lib/{plugins/markdown → components/navigation}/Toc.d.ts +0 -0
@@ -1,4 +1,3 @@
1
- import { useMemo } from "react";
2
1
  import { create, type Mutate, type StoreApi } from "zustand";
3
2
  import { createJSONStorage, persist } from "zustand/middleware";
4
3
 
@@ -20,7 +19,7 @@ export type StoreWithPersist<T> = Mutate<
20
19
  [["zustand/persist", unknown]]
21
20
  >;
22
21
 
23
- export const withStorageDOMEvents = <T>(store: StoreWithPersist<T>) => {
22
+ const withStorageDOMEvents = <T>(store: StoreWithPersist<T>) => {
24
23
  const storageEventCallback = (e: StorageEvent) => {
25
24
  if (e.key === store.persist.getOptions().name && e.newValue) {
26
25
  void store.persist.rehydrate();
@@ -68,36 +67,3 @@ export interface UserProfile {
68
67
  pictureUrl: string | undefined;
69
68
  [key: string]: string | boolean | undefined;
70
69
  }
71
-
72
- interface SelectedServerState {
73
- selectedServer?: string;
74
- setSelectedServer: (newServer: string) => void;
75
- }
76
-
77
- export const useSelectedServerStore = create<SelectedServerState>()(
78
- persist(
79
- (set) => ({
80
- selectedServer: undefined,
81
- setSelectedServer: (newServer: string) =>
82
- set({ selectedServer: newServer }),
83
- }),
84
- { name: "zudoku-selected-server" },
85
- ),
86
- );
87
-
88
- /**
89
- * Simple wrapper for `useSelectedServerStore` to fall back to first of the provided servers
90
- */
91
- export const useSelectedServer = (servers: Array<{ url: string }>) => {
92
- const { selectedServer, setSelectedServer } = useSelectedServerStore();
93
-
94
- const finalSelectedServer = useMemo(
95
- () =>
96
- selectedServer && servers.some((s) => s.url === selectedServer)
97
- ? selectedServer
98
- : (servers.at(0)?.url ?? ""),
99
- [selectedServer, servers],
100
- );
101
-
102
- return { selectedServer: finalSelectedServer, setSelectedServer };
103
- };
@@ -1,7 +1,7 @@
1
1
  import { Helmet } from "@zudoku/react-helmet-async";
2
2
  import { Suspense, useEffect, type ReactNode } from "react";
3
- import { Outlet, useNavigation } from "react-router";
4
- import { useSpinDelay } from "spin-delay";
3
+ import { Outlet, useLocation } from "react-router";
4
+ import { joinUrl } from "../util/joinUrl.js";
5
5
  import { useScrollToAnchor } from "../util/useScrollToAnchor.js";
6
6
  import { useScrollToTop } from "../util/useScrollToTop.js";
7
7
  import { useZudoku } from "./context/ZudokuContext.js";
@@ -17,7 +17,8 @@ const LoadingFallback = () => (
17
17
  );
18
18
 
19
19
  export const Layout = ({ children }: { children?: ReactNode }) => {
20
- const { meta, authentication } = useZudoku();
20
+ const { meta, authentication, options } = useZudoku();
21
+ const location = useLocation();
21
22
 
22
23
  useScrollToAnchor();
23
24
  useScrollToTop();
@@ -27,19 +28,22 @@ export const Layout = ({ children }: { children?: ReactNode }) => {
27
28
  authentication?.onPageLoad?.();
28
29
  }, [authentication]);
29
30
 
30
- // Page transition is happening: https://reactrouter.com/start/framework/pending-navigation
31
- const isNavigating = Boolean(useNavigation().location);
32
- const showSpinner = useSpinDelay(isNavigating, {
33
- delay: 300,
34
- minDuration: 500,
35
- });
36
-
37
31
  return (
38
32
  <>
39
33
  {import.meta.env.MODE === "standalone" && (
40
34
  <style>{`:root { --top-nav-height: 0px; }`}</style>
41
35
  )}
42
36
  <Helmet titleTemplate={meta?.title}>
37
+ {options.canonicalUrlOrigin && (
38
+ <link
39
+ rel="canonical"
40
+ href={joinUrl(
41
+ options.canonicalUrlOrigin,
42
+ options.basePath,
43
+ location.pathname,
44
+ )}
45
+ />
46
+ )}
43
47
  {meta?.description && (
44
48
  <meta name="description" content={meta.description} />
45
49
  )}
@@ -50,13 +54,9 @@ export const Layout = ({ children }: { children?: ReactNode }) => {
50
54
  <Slotlet name="layout-after-head" />
51
55
 
52
56
  <div className="grid grid-cols-1 grid-rows-[min-content_1fr] lg:grid-cols-[var(--side-nav-width)_1fr] max-w-screen-2xl w-full lg:mx-auto px-4 lg:px-8 2xl:border-x">
53
- {showSpinner ? (
54
- <LoadingFallback />
55
- ) : (
56
- <Suspense fallback={<LoadingFallback />}>
57
- <Main>{children ?? <Outlet />}</Main>
58
- </Suspense>
59
- )}
57
+ <Suspense fallback={<LoadingFallback />}>
58
+ <Main>{children ?? <Outlet />}</Main>
59
+ </Suspense>
60
60
  </div>
61
61
  </>
62
62
  );
@@ -1,5 +1,6 @@
1
1
  import { PanelLeftIcon } from "lucide-react";
2
2
  import { type PropsWithChildren, useState } from "react";
3
+ import { useNavigation } from "react-router";
3
4
  import { Drawer, DrawerTrigger } from "zudoku/ui/Drawer.js";
4
5
  import { cn } from "../util/cn.js";
5
6
  import { useCurrentNavigation } from "./context/ZudokuContext.js";
@@ -10,6 +11,7 @@ export const Main = ({ children }: PropsWithChildren) => {
10
11
  const [isDrawerOpen, setDrawerOpen] = useState(false);
11
12
  const { sidebar } = useCurrentNavigation();
12
13
  const hasSidebar = sidebar.length > 0;
14
+ const isNavigating = useNavigation().state === "loading";
13
15
 
14
16
  return (
15
17
  <Drawer
@@ -34,8 +36,8 @@ export const Main = ({ children }: PropsWithChildren) => {
34
36
  <main
35
37
  data-pagefind-body
36
38
  className={cn(
37
- "h-auto dark:border-white/10 translate-x-0",
38
39
  hasSidebar ? "lg:pl-12" : "col-span-full",
40
+ isNavigating && "animate-pulse",
39
41
  )}
40
42
  >
41
43
  <Slotlet name="zudoku-before-content" />
@@ -0,0 +1,47 @@
1
+ import { ArrowLeftIcon, ArrowRightIcon } from "lucide-react";
2
+ import { Link } from "react-router";
3
+ import { cn } from "../util/cn.js";
4
+ import { Button } from "./index.js";
5
+
6
+ export const Pagination = ({
7
+ prev,
8
+ next,
9
+ }: {
10
+ prev: { to: string; label: string } | undefined;
11
+ next: { to: string; label: string } | undefined;
12
+ }) => {
13
+ const linkClass =
14
+ "group transition-all p-5 space-x-1 transition-all hover:text-foreground";
15
+
16
+ return (
17
+ <div
18
+ className={cn(
19
+ "flex my-8 -mx-4 text-muted-foreground font-semibold",
20
+ prev ? "justify-between" : "justify-end",
21
+ )}
22
+ >
23
+ {prev && (
24
+ <Button variant="ghost" asChild>
25
+ <Link to={prev.to} relative="path" className={linkClass}>
26
+ <ArrowLeftIcon
27
+ className="group-hover:motion-safe:animate-bounce-x-start"
28
+ size={12}
29
+ />
30
+ <span className="truncate">{prev.label}</span>
31
+ </Link>
32
+ </Button>
33
+ )}
34
+ {next && (
35
+ <Button variant="ghost" asChild>
36
+ <Link to={next.to} relative="path" className={linkClass}>
37
+ <span className="truncate ">{next.label}</span>
38
+ <ArrowRightIcon
39
+ className="group-hover:motion-safe:animate-bounce-x-end"
40
+ size={12}
41
+ />
42
+ </Link>
43
+ </Button>
44
+ )}
45
+ </div>
46
+ );
47
+ };
@@ -1,5 +1,6 @@
1
+ import { useNProgress } from "@tanem/react-nprogress";
1
2
  import { cx } from "class-variance-authority";
2
- import { Suspense } from "react";
3
+ import { Suspense, useEffect, useState } from "react";
3
4
  import { NavLink, useNavigation } from "react-router";
4
5
  import type { TopNavigationItem } from "../../config/validators/common.js";
5
6
  import { useAuth } from "../authentication/hook.js";
@@ -20,6 +21,31 @@ export const isHiddenItem =
20
21
  );
21
22
  };
22
23
 
24
+ const Progress = () => {
25
+ const navigation = useNavigation();
26
+ const isNavigating = navigation.state === "loading";
27
+ // delay the animation to avoid flickering
28
+ const [isAnimating, setIsAnimating] = useState(false);
29
+
30
+ useEffect(() => {
31
+ const timer = setTimeout(() => setIsAnimating(isNavigating), 100);
32
+
33
+ return () => clearTimeout(timer);
34
+ }, [isNavigating]);
35
+
36
+ const { isFinished, progress } = useNProgress({ isAnimating });
37
+
38
+ return (
39
+ <div
40
+ className="absolute w-0 left-0 right-0 bottom-[-1px] h-[2px] bg-primary transition-all duration-300 ease-in-out"
41
+ style={{
42
+ opacity: isFinished ? 0 : 1,
43
+ width: isFinished ? 0 : `${progress * 100}%`,
44
+ }}
45
+ />
46
+ );
47
+ };
48
+
23
49
  export const TopNavigation = () => {
24
50
  const { topNavigation } = useZudoku();
25
51
  const { isAuthenticated } = useAuth();
@@ -32,7 +58,7 @@ export const TopNavigation = () => {
32
58
 
33
59
  return (
34
60
  <Suspense>
35
- <div className="items-center justify-between px-8 h-[--top-nav-height] hidden lg:flex text-sm">
61
+ <div className="items-center justify-between px-8 h-[--top-nav-height] hidden lg:flex text-sm relative">
36
62
  <nav className="text-sm">
37
63
  <ul className="flex flex-row items-center gap-8">
38
64
  {filteredItems.map((item) => (
@@ -43,6 +69,7 @@ export const TopNavigation = () => {
43
69
  </ul>
44
70
  </nav>
45
71
  <Slotlet name="top-navigation-side" />
72
+ <Progress />
46
73
  </div>
47
74
  </Suspense>
48
75
  );
@@ -3,14 +3,14 @@ import { ExternalLinkIcon } from "lucide-react";
3
3
  import { NavLink, useLocation, useSearchParams } from "react-router";
4
4
 
5
5
  import type { SidebarItem as SidebarItemType } from "../../../config/validators/SidebarSchema.js";
6
- import { joinPath } from "../../util/joinPath.js";
6
+ import { joinUrl } from "../../util/joinUrl.js";
7
7
  import { AnchorLink } from "../AnchorLink.js";
8
8
  import { useViewportAnchor } from "../context/ViewportAnchorContext.js";
9
9
  import { SidebarBadge } from "./SidebarBadge.js";
10
10
  import { SidebarCategory } from "./SidebarCategory.js";
11
11
 
12
12
  export const navigationListItem = cva(
13
- "flex items-center gap-2 px-[--padding-nav-item] my-0.5 py-1.5 rounded-lg hover:bg-accent",
13
+ "flex items-center gap-2 px-[--padding-nav-item] my-0.5 py-1.5 rounded-lg hover:bg-accent tabular-nums",
14
14
  {
15
15
  variants: {
16
16
  isActive: {
@@ -21,6 +21,10 @@ export const navigationListItem = cva(
21
21
  true: "text-foreground/30",
22
22
  false: "",
23
23
  },
24
+ isPending: {
25
+ true: "bg-accent animate-pulse",
26
+ false: "",
27
+ },
24
28
  },
25
29
  defaultVariants: {
26
30
  isActive: false,
@@ -49,8 +53,10 @@ export const SidebarItem = ({
49
53
  case "doc":
50
54
  return (
51
55
  <NavLink
52
- className={({ isActive }) => navigationListItem({ isActive })}
53
- to={joinPath(item.id)}
56
+ className={({ isActive, isPending }) =>
57
+ navigationListItem({ isActive, isPending })
58
+ }
59
+ to={joinUrl(item.id)}
54
60
  onClick={onRequestClose}
55
61
  end
56
62
  >
@@ -7,9 +7,9 @@ import {
7
7
  type CSSProperties,
8
8
  type PropsWithChildren,
9
9
  } from "react";
10
- import { AnchorLink } from "../../components/AnchorLink.js";
11
- import { useViewportAnchor } from "../../components/context/ViewportAnchorContext.js";
12
10
  import { cn } from "../../util/cn.js";
11
+ import { AnchorLink } from "../AnchorLink.js";
12
+ import { useViewportAnchor } from "../context/ViewportAnchorContext.js";
13
13
 
14
14
  const DATA_ANCHOR_ATTR = "data-active";
15
15
 
@@ -24,23 +24,14 @@ const TocItem = ({
24
24
  className?: string;
25
25
  }>) => {
26
26
  return (
27
- <li
28
- className={cn(
29
- "truncate",
30
- isActive
31
- ? "text-primary"
32
- : "text-foreground/65 dark:text-foreground/75",
33
- className,
34
- )}
35
- title={item.value}
36
- >
27
+ <li className={cn("truncate", className)} title={item.value}>
37
28
  <AnchorLink
38
29
  to={`#${item.id}`}
39
30
  {...{ [DATA_ANCHOR_ATTR]: item.id }}
40
31
  className={cn(
41
32
  isActive
42
33
  ? "text-primary"
43
- : "text-foreground/65 dark:text-foreground/75 hover:text-foreground",
34
+ : "hover:text-accent-foreground text-muted-foreground",
44
35
  )}
45
36
  >
46
37
  {item.value}
@@ -89,7 +80,7 @@ export const Toc = ({ entries }: { entries: TocEntry[] }) => {
89
80
  }, [activeAnchor]);
90
81
 
91
82
  return (
92
- <aside className="sticky scrollbar top-[--header-height] h-[calc(100vh-var(--header-height))] pt-[--padding-content-top] pb-[--padding-content-bottom] overflow-y-auto ps-1 text-sm">
83
+ <aside className="sticky scrollbar top-8 lg:top-[--header-height] h-[calc(100vh-var(--header-height))] pt-[--padding-content-top] pb-[--padding-content-bottom] overflow-y-auto ps-1 text-sm">
93
84
  <div className="flex items-center gap-2 font-medium mb-2">
94
85
  <ListTreeIcon size={16} />
95
86
  On this page
@@ -5,6 +5,7 @@ import type { Location } from "react-router";
5
5
  import type { TopNavigationItem } from "../../config/validators/common.js";
6
6
  import type { SidebarConfig } from "../../config/validators/SidebarSchema.js";
7
7
  import type { AuthenticationProvider } from "../authentication/authentication.js";
8
+ import { type AuthState, useAuthState } from "../authentication/state.js";
8
9
  import type { ComponentsContextType } from "../components/context/ComponentsContext.js";
9
10
  import type { Slotlets } from "../components/SlotletProvider.js";
10
11
  import { joinPath } from "../util/joinPath.js";
@@ -21,6 +22,7 @@ import {
21
22
 
22
23
  export interface ZudokuEvents {
23
24
  location: (event: { from?: Location; to: Location }) => void;
25
+ auth: (auth: { prev: AuthState; next: AuthState }) => void;
24
26
  }
25
27
 
26
28
  export interface ApiIdentity {
@@ -67,6 +69,8 @@ type Page = Partial<{
67
69
  }>;
68
70
 
69
71
  export type ZudokuContextOptions = {
72
+ basePath?: string;
73
+ canonicalUrlOrigin?: string;
70
74
  metadata?: Metadata;
71
75
  page?: Page;
72
76
  authentication?: AuthenticationProvider;
@@ -106,7 +110,14 @@ export class ZudokuContext {
106
110
  if (!isEventConsumerPlugin(plugin)) return;
107
111
 
108
112
  objectEntries(plugin.events).forEach(([event, handler]) => {
109
- this.emitter.on(event, handler);
113
+ this.emitter.on(event, handler!);
114
+ });
115
+ });
116
+
117
+ useAuthState.subscribe((state, prevState) => {
118
+ this.emitEvent("auth", {
119
+ prev: prevState,
120
+ next: state,
110
121
  });
111
122
  });
112
123
  }
@@ -158,10 +169,6 @@ export class ZudokuContext {
158
169
  throw new Error("No authentication provider configured");
159
170
  }
160
171
 
161
- const accessToken = await this.authentication.getAccessToken();
162
-
163
- request.headers.set("Authorization", `Bearer ${accessToken}`);
164
-
165
- return request;
172
+ return await this.authentication.signRequest(request);
166
173
  };
167
174
  }
@@ -65,7 +65,7 @@ export interface CommonPlugin {
65
65
  }
66
66
 
67
67
  export type EventConsumerPlugin<Event extends ZudokuEvents = ZudokuEvents> = {
68
- events: { [K in keyof Event]: Event[K] };
68
+ events: { [K in keyof Event]?: Event[K] };
69
69
  };
70
70
 
71
71
  export const isEventConsumerPlugin = (
@@ -97,7 +97,7 @@ const resolveExtensions = (obj: Record<string, any>) =>
97
97
  export const getAllTags = (
98
98
  schema: OpenAPIDocument,
99
99
  slugs: ReturnType<typeof getAllSlugs>["tags"],
100
- ): Array<TagObject & { slug?: string }> => {
100
+ ): Array<Omit<TagObject, "name"> & { name?: string; slug?: string }> => {
101
101
  const rootTags = schema.tags ?? [];
102
102
  const operationTags = new Set(
103
103
  Object.values(schema.paths ?? {})
@@ -105,6 +105,12 @@ export const getAllTags = (
105
105
  .flatMap((op) => (op as OperationObject).tags ?? []),
106
106
  );
107
107
 
108
+ const hasUntaggedOperations = Object.values(schema.paths ?? {}).some((path) =>
109
+ Object.values(path ?? {}).some(
110
+ (op) => !(op as OperationObject).tags?.length,
111
+ ),
112
+ );
113
+
108
114
  return [
109
115
  // Keep root tags that are actually used in operations
110
116
  ...rootTags
@@ -114,6 +120,8 @@ export const getAllTags = (
114
120
  ...[...operationTags]
115
121
  .filter((tag) => !rootTags.some((rt) => rt.name === tag))
116
122
  .map((tag) => ({ name: tag, slug: slugs[tag] })),
123
+ // Add untagged operations if there are any
124
+ ...(hasUntaggedOperations ? [{ name: undefined, slug: undefined }] : []),
117
125
  ];
118
126
  };
119
127
 
@@ -181,38 +189,55 @@ export const getAllOperations = (
181
189
  return operations;
182
190
  };
183
191
 
184
- const SchemaTag = builder
185
- .objectRef<
186
- Omit<TagObject, "name"> & { name?: string; slug?: string }
187
- >("SchemaTag")
188
- .implement({
189
- fields: (t) => ({
190
- name: t.exposeString("name", { nullable: true }),
191
- slug: t.exposeString("slug", { nullable: true }),
192
- description: t.exposeString("description", { nullable: true }),
193
- operations: t.field({
194
- type: [OperationItem],
195
- resolve: (parent, _args, ctx) => {
196
- const rootTags = ctx.tags.map((tag) => tag.name);
197
-
198
- return ctx.operations
199
- .filter((item) =>
200
- parent.name
201
- ? item.tags?.includes(parent.name)
202
- : item.tags?.length === 0 ||
203
- // If none of the tags are present in the root tags, then show them here
204
- item.tags?.every((tag) => !rootTags.includes(tag)),
205
- )
206
- .map((item) => ({ ...item, parentTag: parent.name }));
207
- },
208
- }),
209
- extensions: t.field({
210
- type: JSONObjectScalar,
211
- resolve: (parent) => resolveExtensions(parent),
212
- nullable: true,
213
- }),
192
+ const SchemaTag = builder.objectRef<
193
+ Omit<TagObject, "name"> & { name?: string; slug?: string }
194
+ >("SchemaTag");
195
+
196
+ SchemaTag.implement({
197
+ fields: (t) => ({
198
+ name: t.exposeString("name", { nullable: true }),
199
+ slug: t.exposeString("slug", { nullable: true }),
200
+ isUntagged: t.field({ type: "Boolean", resolve: (parent) => !parent.name }),
201
+ description: t.exposeString("description", { nullable: true }),
202
+ operations: t.field({
203
+ type: [OperationItem],
204
+ resolve: (parent, _args, ctx) => {
205
+ const rootTags = ctx.tags.map((tag) => tag.name);
206
+
207
+ return ctx.operations
208
+ .filter((item) =>
209
+ parent.name
210
+ ? item.tags?.includes(parent.name)
211
+ : item.tags?.length === 0 ||
212
+ // If none of the tags are present in the root tags, then show them here
213
+ item.tags?.every((tag) => !rootTags.includes(tag)),
214
+ )
215
+ .map((item) => ({ ...item, parentTag: parent.name }));
216
+ },
214
217
  }),
215
- });
218
+ prev: t.field({
219
+ type: SchemaTag,
220
+ nullable: true,
221
+ resolve: (parent, _args, ctx) => {
222
+ const index = ctx.tags.findIndex((tag) => tag.slug === parent.slug);
223
+ return ctx.tags[index - 1];
224
+ },
225
+ }),
226
+ next: t.field({
227
+ type: SchemaTag,
228
+ nullable: true,
229
+ resolve: (parent, _args, ctx) => {
230
+ const index = ctx.tags.findIndex((tag) => tag.slug === parent.slug);
231
+ return ctx.tags[index + 1];
232
+ },
233
+ }),
234
+ extensions: t.field({
235
+ type: JSONObjectScalar,
236
+ resolve: (parent) => resolveExtensions(parent),
237
+ nullable: true,
238
+ }),
239
+ }),
240
+ });
216
241
 
217
242
  const ServerItem = builder.objectRef<ServerObject>("Server").implement({
218
243
  fields: (t) => ({
@@ -466,6 +491,43 @@ const OperationItem = builder
466
491
  }),
467
492
  });
468
493
 
494
+ const SchemaItem = builder
495
+ .objectRef<{
496
+ name: string;
497
+ schema: SchemaObject;
498
+ extensions?: Record<string, any>;
499
+ }>("SchemaItem")
500
+ .implement({
501
+ fields: (t) => ({
502
+ name: t.exposeString("name"),
503
+ schema: t.expose("schema", { type: JSONSchemaScalar }),
504
+ extensions: t.expose("extensions", {
505
+ type: JSONObjectScalar,
506
+ nullable: true,
507
+ }),
508
+ }),
509
+ });
510
+
511
+ const Components = builder.objectRef<{
512
+ schemas?: Record<string, SchemaObject>;
513
+ }>("Components");
514
+
515
+ Components.implement({
516
+ fields: (t) => ({
517
+ schemas: t.field({
518
+ type: [SchemaItem],
519
+ resolve: (parent) => {
520
+ return Object.entries(parent.schemas ?? {}).map(([name, schema]) => ({
521
+ name,
522
+ schema,
523
+ extensions: resolveExtensions(schema),
524
+ }));
525
+ },
526
+ nullable: true,
527
+ }),
528
+ }),
529
+ });
530
+
469
531
  const Schema = builder.objectRef<OpenAPIDocument>("Schema").implement({
470
532
  fields: (t) => ({
471
533
  openapi: t.string({ resolve: (root) => root.openapi }),
@@ -495,24 +557,30 @@ const Schema = builder.objectRef<OpenAPIDocument>("Schema").implement({
495
557
  methods: Object.keys(value!) as typeof HttpMethods,
496
558
  })),
497
559
  }),
498
- tags: t.field({
560
+ tag: t.field({
561
+ type: SchemaTag,
499
562
  args: {
563
+ slug: t.arg.string(),
500
564
  name: t.arg.string(),
565
+ untagged: t.arg.boolean(),
501
566
  },
502
- type: [SchemaTag],
503
- resolve: (_root, args, ctx) => {
504
- if (args.name) {
505
- return ctx.tags.filter((tag) => tag.name === args.name);
567
+ nullable: true,
568
+ resolve: (_, args, ctx) => {
569
+ if (args.untagged) {
570
+ return ctx.tags.find((tag) => tag.name === undefined);
506
571
  }
507
-
508
- // Append empty tag which will be used to display untagged operations
509
- if (ctx.operations.some((op) => !op.tags?.length)) {
510
- return [...ctx.tags, { name: undefined, slug: undefined }];
572
+ if (args.slug) {
573
+ return ctx.tags.find((tag) => tag.slug === args.slug);
574
+ }
575
+ if (args.name) {
576
+ return ctx.tags.find((tag) => tag.name === args.name);
511
577
  }
512
-
513
- return ctx.tags;
514
578
  },
515
579
  }),
580
+ tags: t.field({
581
+ type: [SchemaTag],
582
+ resolve: (_root, _args, ctx) => ctx.tags,
583
+ }),
516
584
  operations: t.field({
517
585
  type: [OperationItem],
518
586
  args: {
@@ -522,7 +590,7 @@ const Schema = builder.objectRef<OpenAPIDocument>("Schema").implement({
522
590
  tag: t.arg.string(),
523
591
  untagged: t.arg.boolean(),
524
592
  },
525
- resolve: (parent, args, ctx) =>
593
+ resolve: (_parent, args, ctx) =>
526
594
  ctx.operations.filter((op) => {
527
595
  return (
528
596
  (!args.operationId || op.operationId === args.operationId) &&
@@ -533,6 +601,11 @@ const Schema = builder.objectRef<OpenAPIDocument>("Schema").implement({
533
601
  );
534
602
  }),
535
603
  }),
604
+ components: t.field({
605
+ type: Components,
606
+ resolve: (root) => root.components,
607
+ nullable: true,
608
+ }),
536
609
  extensions: t.field({
537
610
  type: JSONObjectScalar,
538
611
  resolve: (root) => resolveExtensions(root),
@@ -553,7 +626,7 @@ builder.queryType({
553
626
  type: t.arg({ type: SchemaSource, required: true }),
554
627
  input: t.arg({ type: JSONScalar, required: true }),
555
628
  },
556
- resolve: async (_, args, ctx) => {
629
+ resolve: async (_parent, args, ctx) => {
557
630
  if (args.type === "file" && typeof args.input === "string") {
558
631
  const loadSchema = ctx.schemaImports?.[args.input];
559
632
 
@@ -38,6 +38,8 @@ export const dereference = async (
38
38
  }
39
39
  } else {
40
40
  if ("$ref" in current && typeof current.$ref === "string") {
41
+ // Store the ref path before resolving
42
+ current.__$ref = current.$ref;
41
43
  for (const resolver of resolvers) {
42
44
  const resolved = await resolver(current.$ref);
43
45
  if (resolved) return await resolve(resolved, path);