zudoku 0.65.3 → 0.66.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 (256) hide show
  1. package/dist/app/sentry.js +1 -1
  2. package/dist/config/validators/BuildSchema.js +18 -3
  3. package/dist/config/validators/BuildSchema.js.map +1 -1
  4. package/dist/config/validators/validate.d.ts +45 -3
  5. package/dist/config/validators/validate.js +7 -1
  6. package/dist/config/validators/validate.js.map +1 -1
  7. package/dist/flat-config.d.ts +9 -2
  8. package/dist/lib/components/Mermaid.d.ts +7 -0
  9. package/dist/lib/components/Mermaid.js +42 -0
  10. package/dist/lib/components/Mermaid.js.map +1 -0
  11. package/dist/lib/components/PagefindSearchMeta.d.ts +8 -0
  12. package/dist/lib/components/PagefindSearchMeta.js +7 -0
  13. package/dist/lib/components/PagefindSearchMeta.js.map +1 -0
  14. package/dist/lib/components/Zudoku.js +2 -5
  15. package/dist/lib/components/Zudoku.js.map +1 -1
  16. package/dist/lib/core/RouteGuard.js +1 -1
  17. package/dist/lib/core/RouteGuard.js.map +1 -1
  18. package/dist/lib/core/plugins.d.ts +3 -3
  19. package/dist/lib/oas/parser/index.d.ts +1 -0
  20. package/dist/lib/oas/parser/index.js.map +1 -1
  21. package/dist/lib/plugins/openapi/DownloadSchemaButton.d.ts +3 -0
  22. package/dist/lib/plugins/openapi/DownloadSchemaButton.js +47 -0
  23. package/dist/lib/plugins/openapi/DownloadSchemaButton.js.map +1 -0
  24. package/dist/lib/plugins/openapi/Endpoint.js +3 -6
  25. package/dist/lib/plugins/openapi/Endpoint.js.map +1 -1
  26. package/dist/lib/plugins/openapi/OasProvider.js +22 -13
  27. package/dist/lib/plugins/openapi/OasProvider.js.map +1 -1
  28. package/dist/lib/plugins/openapi/OperationList.js +12 -6
  29. package/dist/lib/plugins/openapi/OperationList.js.map +1 -1
  30. package/dist/lib/plugins/openapi/OperationListItem.js +2 -2
  31. package/dist/lib/plugins/openapi/OperationListItem.js.map +1 -1
  32. package/dist/lib/plugins/openapi/SchemaList.js +2 -1
  33. package/dist/lib/plugins/openapi/SchemaList.js.map +1 -1
  34. package/dist/lib/plugins/openapi/index.js +11 -5
  35. package/dist/lib/plugins/openapi/index.js.map +1 -1
  36. package/dist/lib/plugins/openapi/interfaces.d.ts +16 -13
  37. package/dist/lib/plugins/openapi/playground/Playground.js +1 -1
  38. package/dist/lib/plugins/openapi/playground/Playground.js.map +1 -1
  39. package/dist/lib/plugins/openapi/schema/SchemaView.d.ts +1 -1
  40. package/dist/lib/plugins/openapi/schema/SchemaView.js +14 -6
  41. package/dist/lib/plugins/openapi/schema/SchemaView.js.map +1 -1
  42. package/dist/lib/plugins/openapi/schema/utils.d.ts +2 -2
  43. package/dist/lib/plugins/openapi/schema/utils.js.map +1 -1
  44. package/dist/lib/plugins/openapi/util/getRoutes.d.ts +6 -1
  45. package/dist/lib/plugins/openapi/util/getRoutes.js +29 -2
  46. package/dist/lib/plugins/openapi/util/getRoutes.js.map +1 -1
  47. package/dist/lib/plugins/search-pagefind/IndexingDialog.d.ts +3 -0
  48. package/dist/lib/plugins/search-pagefind/IndexingDialog.js +64 -0
  49. package/dist/lib/plugins/search-pagefind/IndexingDialog.js.map +1 -0
  50. package/dist/lib/plugins/search-pagefind/PagefindSearch.js +22 -5
  51. package/dist/lib/plugins/search-pagefind/PagefindSearch.js.map +1 -1
  52. package/dist/lib/plugins/search-pagefind/ResultList.js +5 -4
  53. package/dist/lib/plugins/search-pagefind/ResultList.js.map +1 -1
  54. package/dist/lib/shiki.d.ts +1 -1
  55. package/dist/lib/shiki.js +18 -12
  56. package/dist/lib/shiki.js.map +1 -1
  57. package/dist/lib/ui/Button.d.ts +2 -2
  58. package/dist/lib/ui/Button.js +9 -8
  59. package/dist/lib/ui/Button.js.map +1 -1
  60. package/dist/lib/ui/ButtonGroup.d.ts +11 -0
  61. package/dist/lib/ui/ButtonGroup.js +28 -0
  62. package/dist/lib/ui/ButtonGroup.js.map +1 -0
  63. package/dist/lib/ui/Command.js +2 -2
  64. package/dist/lib/ui/Command.js.map +1 -1
  65. package/dist/lib/ui/DropdownMenu.d.ts +21 -23
  66. package/dist/lib/ui/DropdownMenu.js +47 -32
  67. package/dist/lib/ui/DropdownMenu.js.map +1 -1
  68. package/dist/lib/ui/Kbd.d.ts +3 -0
  69. package/dist/lib/ui/Kbd.js +10 -0
  70. package/dist/lib/ui/Kbd.js.map +1 -0
  71. package/dist/lib/util/MdxComponents.d.ts +1 -0
  72. package/dist/lib/util/MdxComponents.js +2 -0
  73. package/dist/lib/util/MdxComponents.js.map +1 -1
  74. package/dist/lib/util/flattenAllOf.js +27 -4
  75. package/dist/lib/util/flattenAllOf.js.map +1 -1
  76. package/dist/lib/util/flattenAllOf.test.js +67 -12
  77. package/dist/lib/util/flattenAllOf.test.js.map +1 -1
  78. package/dist/vite/api/SchemaManager.d.ts +5 -0
  79. package/dist/vite/api/SchemaManager.js +24 -0
  80. package/dist/vite/api/SchemaManager.js.map +1 -1
  81. package/dist/vite/api/SchemaManager.test.js +67 -0
  82. package/dist/vite/api/SchemaManager.test.js.map +1 -1
  83. package/dist/vite/config.js +8 -2
  84. package/dist/vite/config.js.map +1 -1
  85. package/dist/vite/dev-server.js +25 -0
  86. package/dist/vite/dev-server.js.map +1 -1
  87. package/dist/vite/pagefind-dev-index.d.ts +16 -0
  88. package/dist/vite/pagefind-dev-index.js +68 -0
  89. package/dist/vite/pagefind-dev-index.js.map +1 -0
  90. package/dist/vite/plugin-api.js +41 -3
  91. package/dist/vite/plugin-api.js.map +1 -1
  92. package/dist/vite/prerender/prerender.js +1 -19
  93. package/dist/vite/prerender/prerender.js.map +1 -1
  94. package/dist/vite/prerender/utils.d.ts +2 -0
  95. package/dist/vite/prerender/utils.js +24 -0
  96. package/dist/vite/prerender/utils.js.map +1 -0
  97. package/lib/Button-CynVW1JV.js +53 -0
  98. package/lib/Button-CynVW1JV.js.map +1 -0
  99. package/lib/ClaudeLogo-PxFjou9w.js +69 -0
  100. package/lib/ClaudeLogo-PxFjou9w.js.map +1 -0
  101. package/lib/{Command-CUcrW3qs.js → Command-BpT1iBE6.js} +25 -25
  102. package/lib/Command-BpT1iBE6.js.map +1 -0
  103. package/lib/Drawer-Ci7XwhqT.js.map +1 -1
  104. package/lib/DropdownMenu-C8SX_-S_.js +104 -0
  105. package/lib/DropdownMenu-C8SX_-S_.js.map +1 -0
  106. package/lib/{ErrorAlert-D5LKLFOd.js → ErrorAlert-BqjbNHIn.js} +1017 -1015
  107. package/lib/{ErrorAlert-D5LKLFOd.js.map → ErrorAlert-BqjbNHIn.js.map} +1 -1
  108. package/lib/IndexingDialog-B5zCiUKr.js +100 -0
  109. package/lib/IndexingDialog-B5zCiUKr.js.map +1 -0
  110. package/lib/MdxPage-CVFatbHw.js +210 -0
  111. package/lib/MdxPage-CVFatbHw.js.map +1 -0
  112. package/lib/Mermaid-CIFixY6C.js +102 -0
  113. package/lib/Mermaid-CIFixY6C.js.map +1 -0
  114. package/lib/{OAuthErrorPage-oXnxcJg4.js → OAuthErrorPage-Dup79DJk.js} +7 -7
  115. package/lib/{OAuthErrorPage-oXnxcJg4.js.map → OAuthErrorPage-Dup79DJk.js.map} +1 -1
  116. package/lib/OasProvider-BJeMq29o.js +40 -0
  117. package/lib/OasProvider-BJeMq29o.js.map +1 -0
  118. package/lib/{OperationList-CmMoKpGu.js → OperationList-ff3ZvQsO.js} +1701 -1585
  119. package/lib/OperationList-ff3ZvQsO.js.map +1 -0
  120. package/lib/{RouteGuard-Brz95MSt.js → RouteGuard-BXy13JSz.js} +19 -19
  121. package/lib/{RouteGuard-Brz95MSt.js.map → RouteGuard-BXy13JSz.js.map} +1 -1
  122. package/lib/{RouterError-VGZB_wg4.js → RouterError-CKOZTsDD.js} +3 -3
  123. package/lib/{RouterError-VGZB_wg4.js.map → RouterError-CKOZTsDD.js.map} +1 -1
  124. package/lib/{SchemaList-BykD27ga.js → SchemaList-BSC1KM3v.js} +28 -27
  125. package/lib/SchemaList-BSC1KM3v.js.map +1 -0
  126. package/lib/{SchemaView-Dt6hbCAt.js → SchemaView-CgwJ9gtb.js} +198 -187
  127. package/lib/SchemaView-CgwJ9gtb.js.map +1 -0
  128. package/lib/Select-VmDZ-nKe.js +337 -0
  129. package/lib/Select-VmDZ-nKe.js.map +1 -0
  130. package/lib/{SignUp-D2mmQOkg.js → SignUp-Pm_LGm6T.js} +13 -13
  131. package/lib/{SignUp-D2mmQOkg.js.map → SignUp-Pm_LGm6T.js.map} +1 -1
  132. package/lib/{SyntaxHighlight-C19vH0V_.js → SyntaxHighlight-bkmst3oV.js} +654 -622
  133. package/lib/SyntaxHighlight-bkmst3oV.js.map +1 -0
  134. package/lib/{Toc-CBWfFCVf.js → Toc-TUXNFbKl.js} +2 -2
  135. package/lib/{Toc-CBWfFCVf.js.map → Toc-TUXNFbKl.js.map} +1 -1
  136. package/lib/{ZudokuContext-BUZ5hkWB.js → ZudokuContext-np1wheDl.js} +8 -8
  137. package/lib/{ZudokuContext-BUZ5hkWB.js.map → ZudokuContext-np1wheDl.js.map} +1 -1
  138. package/lib/___vite-browser-external_commonjs-proxy-Cga3HsWk.js +9 -0
  139. package/lib/___vite-browser-external_commonjs-proxy-Cga3HsWk.js.map +1 -0
  140. package/lib/{chunk-PVWAREVJ-BMhpCH5D.js → chunk-PVWAREVJ-dLIqswPy.js} +5 -5
  141. package/lib/{chunk-PVWAREVJ-BMhpCH5D.js.map → chunk-PVWAREVJ-dLIqswPy.js.map} +1 -1
  142. package/lib/{circular-CNHs4gAz.js → circular-XPj_dwqA.js} +2 -2
  143. package/lib/{circular-CNHs4gAz.js.map → circular-XPj_dwqA.js.map} +1 -1
  144. package/lib/createServer-D01nCTNp.js +16693 -0
  145. package/lib/createServer-D01nCTNp.js.map +1 -0
  146. package/lib/{errors-D7xzOd8X.js → errors-B0hNTPFO.js} +3 -3
  147. package/lib/{errors-D7xzOd8X.js.map → errors-B0hNTPFO.js.map} +1 -1
  148. package/lib/{hook-CMeoxziF.js → hook-CvSwcbk6.js} +3 -3
  149. package/lib/{hook-CMeoxziF.js.map → hook-CvSwcbk6.js.map} +1 -1
  150. package/lib/{index-unv8c40u.js → index-Bjc_QsUR.js} +754 -738
  151. package/lib/{index-unv8c40u.js.map → index-Bjc_QsUR.js.map} +1 -1
  152. package/lib/index-CrcNWbel.js.map +1 -1
  153. package/lib/index-DnMgJWrI.js +133 -0
  154. package/lib/index-DnMgJWrI.js.map +1 -0
  155. package/lib/{index-CF7_erXq.js → index-DscsS121.js} +2 -2
  156. package/lib/{index-CF7_erXq.js.map → index-DscsS121.js.map} +1 -1
  157. package/lib/{index-CPws05Tb.js → index-mfkNWYG-.js} +10 -10
  158. package/lib/{index-CPws05Tb.js.map → index-mfkNWYG-.js.map} +1 -1
  159. package/lib/{index.esm-BnYHxCYC.js → index.esm-DtzT_KoE.js} +20 -20
  160. package/lib/{index.esm-BnYHxCYC.js.map → index.esm-DtzT_KoE.js.map} +1 -1
  161. package/lib/{invariant-Bm-FVUQE.js → invariant-CGOLuIIz.js} +3 -3
  162. package/lib/{invariant-Bm-FVUQE.js.map → invariant-CGOLuIIz.js.map} +1 -1
  163. package/lib/{mutation-BSU0xu4m.js → mutation-BlmnL5qL.js} +2 -2
  164. package/lib/{mutation-BSU0xu4m.js.map → mutation-BlmnL5qL.js.map} +1 -1
  165. package/lib/ui/ActionButton.js +1 -1
  166. package/lib/ui/Button.js +25 -24
  167. package/lib/ui/Button.js.map +1 -1
  168. package/lib/ui/ButtonGroup.js +77 -0
  169. package/lib/ui/ButtonGroup.js.map +1 -0
  170. package/lib/ui/Command.js +3 -3
  171. package/lib/ui/Command.js.map +1 -1
  172. package/lib/ui/DropdownMenu.js +227 -140
  173. package/lib/ui/DropdownMenu.js.map +1 -1
  174. package/lib/ui/Kbd.js +32 -0
  175. package/lib/ui/Kbd.js.map +1 -0
  176. package/lib/ui/SyntaxHighlight.js +3 -3
  177. package/lib/zudoku.__internal.js +8 -8
  178. package/lib/zudoku.auth-auth0.js +1 -1
  179. package/lib/zudoku.auth-azureb2c.js +4 -4
  180. package/lib/zudoku.auth-clerk.js +2 -2
  181. package/lib/zudoku.auth-openid.js +5 -5
  182. package/lib/zudoku.auth-supabase.js +5 -5
  183. package/lib/zudoku.components.js +7 -7
  184. package/lib/zudoku.hooks.js +11 -24
  185. package/lib/zudoku.hooks.js.map +1 -1
  186. package/lib/zudoku.mermaid.js +10 -0
  187. package/lib/zudoku.mermaid.js.map +1 -0
  188. package/lib/zudoku.plugin-api-catalog.js +6 -6
  189. package/lib/zudoku.plugin-api-keys.js +223 -198
  190. package/lib/zudoku.plugin-api-keys.js.map +1 -1
  191. package/lib/zudoku.plugin-custom-pages.js +1 -1
  192. package/lib/zudoku.plugin-markdown.js +1 -1
  193. package/lib/zudoku.plugin-openapi.js +3 -3
  194. package/lib/zudoku.plugin-redirect.js +1 -1
  195. package/lib/zudoku.plugin-search-pagefind.js +184 -226
  196. package/lib/zudoku.plugin-search-pagefind.js.map +1 -1
  197. package/lib/zudoku.plugins.js.map +1 -1
  198. package/lib/zudoku.router.js +2 -2
  199. package/package.json +29 -21
  200. package/src/app/sentry.ts +1 -1
  201. package/src/lib/components/Mermaid.tsx +68 -0
  202. package/src/lib/components/PagefindSearchMeta.tsx +14 -0
  203. package/src/lib/components/Zudoku.tsx +4 -7
  204. package/src/lib/core/RouteGuard.tsx +1 -1
  205. package/src/lib/core/plugins.ts +2 -2
  206. package/src/lib/oas/parser/index.ts +2 -0
  207. package/src/lib/plugins/openapi/DownloadSchemaButton.tsx +115 -0
  208. package/src/lib/plugins/openapi/Endpoint.tsx +20 -27
  209. package/src/lib/plugins/openapi/OasProvider.tsx +30 -17
  210. package/src/lib/plugins/openapi/OperationList.tsx +39 -21
  211. package/src/lib/plugins/openapi/OperationListItem.tsx +5 -5
  212. package/src/lib/plugins/openapi/SchemaList.tsx +4 -0
  213. package/src/lib/plugins/openapi/index.tsx +16 -7
  214. package/src/lib/plugins/openapi/interfaces.ts +16 -7
  215. package/src/lib/plugins/openapi/playground/Playground.tsx +1 -1
  216. package/src/lib/plugins/openapi/schema/SchemaView.tsx +36 -27
  217. package/src/lib/plugins/openapi/schema/utils.ts +5 -2
  218. package/src/lib/plugins/openapi/util/getRoutes.tsx +35 -3
  219. package/src/lib/plugins/search-pagefind/IndexingDialog.tsx +163 -0
  220. package/src/lib/plugins/search-pagefind/PagefindSearch.tsx +61 -22
  221. package/src/lib/plugins/search-pagefind/ResultList.tsx +8 -3
  222. package/src/lib/shiki.ts +21 -12
  223. package/src/lib/ui/Button.tsx +10 -10
  224. package/src/lib/ui/ButtonGroup.tsx +82 -0
  225. package/src/lib/ui/Command.tsx +3 -3
  226. package/src/lib/ui/DropdownMenu.tsx +226 -170
  227. package/src/lib/ui/Kbd.tsx +28 -0
  228. package/src/lib/util/MdxComponents.tsx +2 -0
  229. package/src/lib/util/flattenAllOf.test.ts +71 -19
  230. package/src/lib/util/flattenAllOf.ts +29 -8
  231. package/src/shiki/langs/markdown-nix.js +1 -0
  232. package/src/shiki/langs/openscad.js +1 -0
  233. package/dist/vite/create-pagefind-index.d.ts +0 -4
  234. package/dist/vite/create-pagefind-index.js +0 -12
  235. package/dist/vite/create-pagefind-index.js.map +0 -1
  236. package/lib/Button-B3ucvvQw.js +0 -52
  237. package/lib/Button-B3ucvvQw.js.map +0 -1
  238. package/lib/Command-CUcrW3qs.js.map +0 -1
  239. package/lib/DropdownMenu-BZ2NKQ3K.js +0 -126
  240. package/lib/DropdownMenu-BZ2NKQ3K.js.map +0 -1
  241. package/lib/MdxPage-hOCN-u-L.js +0 -240
  242. package/lib/MdxPage-hOCN-u-L.js.map +0 -1
  243. package/lib/OasProvider-CpniNNrW.js +0 -36
  244. package/lib/OasProvider-CpniNNrW.js.map +0 -1
  245. package/lib/OperationList-CmMoKpGu.js.map +0 -1
  246. package/lib/Pagination-lLSoHnxa.js +0 -37
  247. package/lib/Pagination-lLSoHnxa.js.map +0 -1
  248. package/lib/SchemaList-BykD27ga.js.map +0 -1
  249. package/lib/SchemaView-Dt6hbCAt.js.map +0 -1
  250. package/lib/Select-DFRCS31-.js +0 -399
  251. package/lib/Select-DFRCS31-.js.map +0 -1
  252. package/lib/SyntaxHighlight-C19vH0V_.js.map +0 -1
  253. package/lib/createServer-BmcVQAOQ.js +0 -13018
  254. package/lib/createServer-BmcVQAOQ.js.map +0 -1
  255. package/lib/useExposedProps-U3pmsHaG.js +0 -113
  256. package/lib/useExposedProps-U3pmsHaG.js.map +0 -1
@@ -18,9 +18,12 @@ import { CategoryHeading } from "../../components/CategoryHeading.js";
18
18
  import { useApiIdentities } from "../../components/context/ZudokuContext.js";
19
19
  import { Heading } from "../../components/Heading.js";
20
20
  import { Markdown } from "../../components/Markdown.js";
21
+ import { PagefindSearchMeta } from "../../components/PagefindSearchMeta.js";
21
22
  import { Pagination } from "../../components/Pagination.js";
23
+ import { joinUrl } from "../../util/joinUrl.js";
22
24
  import { useCreateQuery } from "./client/useCreateQuery.js";
23
25
  import { useOasConfig } from "./context.js";
26
+ import { DownloadSchemaButton } from "./DownloadSchemaButton.js";
24
27
  import { Endpoint } from "./Endpoint.js";
25
28
  import { graphql } from "./graphql/index.js";
26
29
  import { UNTAGGED_PATH } from "./index.js";
@@ -155,7 +158,7 @@ export const OperationList = ({
155
158
  tag?: string;
156
159
  untagged?: boolean;
157
160
  }) => {
158
- const { input, type, versions, version, options } = useOasConfig();
161
+ const { path, input, type, versions, version, options } = useOasConfig();
159
162
  const { tag: tagFromParams } = useParams<"tag">();
160
163
  const query = useCreateQuery(OperationsForTagQuery, {
161
164
  input,
@@ -244,12 +247,20 @@ export const OperationList = ({
244
247
  const tagTitle = schema.tag.extensions?.["x-displayName"] ?? schema.tag.name;
245
248
  const helmetTitle = [tagTitle, title].filter(Boolean).join(" - ");
246
249
 
250
+ const downloadUrl =
251
+ typeof input === "string"
252
+ ? type === "url"
253
+ ? input
254
+ : joinUrl(path, version, input.split("/").pop())
255
+ : undefined;
256
+
247
257
  return (
248
258
  <div
249
259
  className="pt-(--padding-content-top)"
250
260
  data-pagefind-filter="section:openapi"
251
261
  data-pagefind-meta="section:openapi"
252
262
  >
263
+ <PagefindSearchMeta name="category">{title}</PagefindSearchMeta>
253
264
  <Helmet>
254
265
  {helmetTitle && <title>{helmetTitle}</title>}
255
266
  {metaDescription && (
@@ -262,7 +273,7 @@ export const OperationList = ({
262
273
  className="w-full"
263
274
  defaultOpen={options?.expandApiInformation}
264
275
  >
265
- <div className="flex flex-col gap-y-4 sm:flex-row justify-around items-start sm:items-end">
276
+ <div className="flex flex-col gap-4 sm:flex-row justify-around items-start sm:items-end">
266
277
  <div className="flex flex-col flex-1 gap-2">
267
278
  <CategoryHeading>{title}</CategoryHeading>
268
279
  <Heading
@@ -282,25 +293,32 @@ export const OperationList = ({
282
293
  <Endpoint />
283
294
  </div>
284
295
  <div className="flex flex-col gap-4 sm:items-end">
285
- {showVersions && (
286
- <Select
287
- // biome-ignore lint/style/noNonNullAssertion: is guaranteed to be defined
288
- onValueChange={(version) => navigate(versions[version]!)}
289
- defaultValue={version}
290
- disabled={!hasMultipleVersions}
291
- >
292
- <SelectTrigger className="w-[180px]">
293
- <SelectValue placeholder="Select version" />
294
- </SelectTrigger>
295
- <SelectContent>
296
- {Object.entries(versions).map(([version]) => (
297
- <SelectItem key={version} value={version}>
298
- {version}
299
- </SelectItem>
300
- ))}
301
- </SelectContent>
302
- </Select>
303
- )}
296
+ <div className="flex gap-2 items-center">
297
+ {showVersions && (
298
+ <Select
299
+ onValueChange={(version) =>
300
+ // biome-ignore lint/style/noNonNullAssertion: is guaranteed to be defined
301
+ navigate(versions[version]!.path)
302
+ }
303
+ defaultValue={version}
304
+ disabled={!hasMultipleVersions}
305
+ >
306
+ <SelectTrigger className="w-[180px]" size="sm">
307
+ <SelectValue placeholder="Select version" />
308
+ </SelectTrigger>
309
+ <SelectContent>
310
+ {Object.entries(versions).map(([version, { label }]) => (
311
+ <SelectItem key={version} value={version}>
312
+ {label}
313
+ </SelectItem>
314
+ ))}
315
+ </SelectContent>
316
+ </Select>
317
+ )}
318
+ {options?.schemaDownload?.enabled && downloadUrl && (
319
+ <DownloadSchemaButton downloadUrl={downloadUrl} />
320
+ )}
321
+ </div>
304
322
  {schema.description && (
305
323
  <CollapsibleTrigger className="flex items-center gap-1 text-sm font-medium text-muted-foreground group">
306
324
  <span>API information</span>
@@ -1,9 +1,9 @@
1
- import { VisuallyHidden } from "@radix-ui/react-visually-hidden";
2
1
  import { useState } from "react";
3
2
  import { Badge } from "zudoku/ui/Badge.js";
4
3
  import { Separator } from "zudoku/ui/Separator.js";
5
4
  import { Heading } from "../../components/Heading.js";
6
5
  import { Markdown } from "../../components/Markdown.js";
6
+ import { PagefindSearchMeta } from "../../components/PagefindSearchMeta.js";
7
7
  import { cn } from "../../util/cn.js";
8
8
  import { groupBy } from "../../util/groupBy.js";
9
9
  import { renderIf } from "../../util/renderIf.js";
@@ -134,9 +134,9 @@ export const OperationListItem = ({
134
134
  id={`${operation.slug}/request-body`}
135
135
  >
136
136
  {operation.summary && (
137
- <VisuallyHidden>
137
+ <PagefindSearchMeta>
138
138
  {operation.summary} &rsaquo;{" "}
139
- </VisuallyHidden>
139
+ </PagefindSearchMeta>
140
140
  )}
141
141
  Request Body{" "}
142
142
  {operation.requestBody?.required === false ? (
@@ -154,9 +154,9 @@ export const OperationListItem = ({
154
154
  <>
155
155
  <Heading level={3} id={`${operation.slug}/responses`}>
156
156
  {operation.summary && (
157
- <VisuallyHidden>
157
+ <PagefindSearchMeta>
158
158
  {operation.summary} &rsaquo;{" "}
159
- </VisuallyHidden>
159
+ </PagefindSearchMeta>
160
160
  )}
161
161
  Responses
162
162
  </Heading>
@@ -16,6 +16,7 @@ import { CategoryHeading } from "../../components/CategoryHeading.js";
16
16
  import { Heading } from "../../components/Heading.js";
17
17
  import { Markdown } from "../../components/Markdown.js";
18
18
  import { Toc } from "../../components/navigation/Toc.js";
19
+ import { PagefindSearchMeta } from "../../components/PagefindSearchMeta.js";
19
20
  import { useCreateQuery } from "./client/useCreateQuery.js";
20
21
  import { useOasConfig } from "./context.js";
21
22
  import { graphql } from "./graphql/gql.js";
@@ -70,6 +71,9 @@ export function SchemaList() {
70
71
  data-pagefind-filter="section:openapi"
71
72
  data-pagefind-meta="section:openapi"
72
73
  >
74
+ <PagefindSearchMeta name="category">
75
+ {data.schema.title}
76
+ </PagefindSearchMeta>
73
77
  <Helmet>
74
78
  <title>Schemas {showVersions ? version : ""}</title>
75
79
  <meta name="description" content="List of schemas used by the API." />
@@ -13,7 +13,7 @@ import type { OasPluginConfig } from "./interfaces.js";
13
13
  import type { PlaygroundContentProps } from "./playground/Playground.js";
14
14
  import { PlaygroundDialog } from "./playground/PlaygroundDialog.js";
15
15
  import { createNavigationCategory } from "./util/createNavigationCategory.js";
16
- import { getRoutes, getVersions } from "./util/getRoutes.js";
16
+ import { getRoutes, getVersionMetadata } from "./util/getRoutes.js";
17
17
 
18
18
  export const GetNavigationOperationsQuery = graphql(`
19
19
  query GetNavigationOperations($input: JSON!, $type: SchemaType!) {
@@ -54,14 +54,19 @@ export const openApiPlugin = (config: OasPluginConfig): ZudokuPlugin => {
54
54
  return {
55
55
  getHead: () => {
56
56
  if (config.type === "url" && !config.skipPreload) {
57
- return (
57
+ const urls = Array.isArray(config.input)
58
+ ? config.input.map((v) => v.input)
59
+ : [config.input];
60
+
61
+ return urls.map((url) => (
58
62
  <link
63
+ key={url}
64
+ href={url}
59
65
  rel="preload"
60
- href={config.input}
61
66
  as="fetch"
62
67
  crossOrigin="anonymous"
63
68
  />
64
- );
69
+ ));
65
70
  }
66
71
 
67
72
  if (config.server) {
@@ -111,10 +116,14 @@ export const openApiPlugin = (config: OasPluginConfig): ZudokuPlugin => {
111
116
 
112
117
  try {
113
118
  const versionParam = match?.params.version;
114
- const version = versionParam ?? getVersions(config).at(0);
119
+ const { versions } = getVersionMetadata(config);
120
+ const version = versionParam ?? versions.at(0);
115
121
  const { type } = config;
116
- // biome-ignore lint/style/noNonNullAssertion: version is guaranteed to be defined
117
- const input = type === "file" ? config.input[version!] : config.input;
122
+
123
+ const input = Array.isArray(config.input)
124
+ ? (config.input.find((v) => v.path === version)?.input ??
125
+ config.input[0]?.input)
126
+ : config.input;
118
127
 
119
128
  const query = createQuery(client, GetNavigationOperationsQuery, {
120
129
  type,
@@ -5,15 +5,21 @@ import type { OperationsFragmentFragment } from "./graphql/graphql.js";
5
5
 
6
6
  type DynamicInput = () => Promise<unknown>;
7
7
 
8
+ export type VersionedInput<T> = Array<{
9
+ path: string;
10
+ label?: string;
11
+ input: T;
12
+ }>;
13
+
8
14
  type OasSource =
9
- | { type: "url"; input: string }
10
- | { type: "file"; input: { [version: string]: DynamicInput } }
15
+ | { type: "url"; input: string | VersionedInput<string> }
16
+ | { type: "file"; input: VersionedInput<DynamicInput> }
11
17
  | { type: "raw"; input: string };
12
18
 
13
- export type ContextOasSource =
14
- | { type: "url"; input: string }
15
- | { type: "file"; input: DynamicInput }
16
- | { type: "raw"; input: string };
19
+ export type ContextOasSource = {
20
+ type: "url" | "file" | "raw";
21
+ input: string | DynamicInput;
22
+ };
17
23
 
18
24
  type Example = {
19
25
  name: string;
@@ -66,6 +72,9 @@ type BaseOasConfig = {
66
72
  showVersionSelect?: "always" | "if-available" | "hide";
67
73
  expandAllTags?: boolean;
68
74
  expandApiInformation?: boolean;
75
+ schemaDownload?: {
76
+ enabled: boolean;
77
+ };
69
78
  transformExamples?: TransformExamplesFn;
70
79
  generateCodeSnippet?: GenerateCodeSnippetFn;
71
80
  };
@@ -76,5 +85,5 @@ export type OasPluginConfig = BaseOasConfig & OasSource;
76
85
  export type OasPluginContext = BaseOasConfig &
77
86
  ContextOasSource & {
78
87
  version?: string;
79
- versions: Record<string, string>;
88
+ versions: Record<string, { path: string; label: string }>;
80
89
  };
@@ -387,7 +387,7 @@ export const Playground = ({
387
387
  value={selectedServer}
388
388
  defaultValue={selectedServer}
389
389
  >
390
- <SelectTrigger className="p-0 border-none flex-row-reverse bg-transparent text-xs gap-0.5 h-auto translate-y-[4px]">
390
+ <SelectTrigger className="p-0 h-fit shadow-none border-none flex-row-reverse bg-transparent text-xs gap-0.5 translate-y-[4px]">
391
391
  <SelectValue />
392
392
  </SelectTrigger>
393
393
  <SelectContent>
@@ -17,7 +17,7 @@ import { ParamInfos } from "../ParamInfos.js";
17
17
  import { SchemaExampleAndDefault } from "./SchemaExampleAndDefault.js";
18
18
  import { SchemaPropertyItem } from "./SchemaPropertyItem.js";
19
19
  import { UnionView } from "./UnionView.js";
20
- import { isBasicType } from "./utils.js";
20
+ import { isArrayType, isBasicType } from "./utils.js";
21
21
 
22
22
  const renderMarkdown = (content?: string) =>
23
23
  content && (
@@ -91,10 +91,20 @@ export const SchemaView = ({
91
91
  return renderBasicSchema(schema, cardHeader, embedded);
92
92
  }
93
93
 
94
- if (schema.type === "array" && typeof schema.items === "object") {
95
- return <SchemaView schema={schema.items} cardHeader={cardHeader} />;
94
+ if (isArrayType(schema) && typeof schema.items === "object") {
95
+ const wrappedSchema: SchemaObject = {
96
+ type: "object",
97
+ properties: { "": schema },
98
+ };
99
+
100
+ return (
101
+ <SchemaView schema={wrappedSchema} cardHeader={cardHeader} defaultOpen />
102
+ );
96
103
  }
97
104
 
105
+ const additionalObjectProperties = typeof schema.additionalProperties ===
106
+ "object" && <SchemaView schema={schema.additionalProperties} embedded />;
107
+
98
108
  if (schema.type === "object") {
99
109
  const groupedProperties = groupBy(
100
110
  Object.entries(schema.properties ?? {}),
@@ -106,32 +116,31 @@ export const SchemaView = ({
106
116
  : "optional";
107
117
  },
108
118
  );
119
+
109
120
  const groupNames = ["required", "optional", "deprecated"] as const;
121
+ const groups = groupNames.flatMap((group) => {
122
+ const properties = groupedProperties[group];
123
+ return properties ? { group, properties } : [];
124
+ });
110
125
 
111
- const additionalObjectProperties = typeof schema.additionalProperties ===
112
- "object" && <SchemaView schema={schema.additionalProperties} embedded />;
113
-
114
- const itemsList = groupNames.map(
115
- (group, index) =>
116
- groupedProperties[group] && (
117
- <Fragment key={group}>
118
- {index > 0 && <ItemSeparator />}
119
- <ItemGroup className="overflow-clip">
120
- {groupedProperties[group].map(([name, schema], index) => (
121
- <Fragment key={name}>
122
- {index > 0 && <ItemSeparator />}
123
- <SchemaPropertyItem
124
- name={name}
125
- schema={schema}
126
- group={group}
127
- defaultOpen={defaultOpen}
128
- />
129
- </Fragment>
130
- ))}
131
- </ItemGroup>
132
- </Fragment>
133
- ),
134
- );
126
+ const itemsList = groups.map(({ group, properties }, index) => (
127
+ <Fragment key={group}>
128
+ {index > 0 && <ItemSeparator />}
129
+ <ItemGroup className="overflow-clip">
130
+ {properties.map(([name, schema], index) => (
131
+ <Fragment key={name}>
132
+ {index > 0 && <ItemSeparator />}
133
+ <SchemaPropertyItem
134
+ name={name}
135
+ schema={schema}
136
+ group={group}
137
+ defaultOpen={defaultOpen}
138
+ />
139
+ </Fragment>
140
+ ))}
141
+ </ItemGroup>
142
+ </Fragment>
143
+ ));
135
144
 
136
145
  if (embedded) {
137
146
  return itemsList;
@@ -1,5 +1,8 @@
1
1
  import { CIRCULAR_REF } from "../../../oas/graphql/circular.js";
2
- import type { SchemaObject } from "../../../oas/parser/index.js";
2
+ import type {
3
+ ArraySchemaObject,
4
+ SchemaObject,
5
+ } from "../../../oas/parser/index.js";
3
6
 
4
7
  export const isBasicType = (
5
8
  type: unknown,
@@ -8,7 +11,7 @@ export const isBasicType = (
8
11
  ["string", "number", "boolean", "integer", "null"].includes(type)) ||
9
12
  (Array.isArray(type) && type.every(isBasicType));
10
13
 
11
- export const isArrayType = (value: SchemaObject) =>
14
+ export const isArrayType = (value: SchemaObject): value is ArraySchemaObject =>
12
15
  value.type === "array" ||
13
16
  // schema.type might be an array of types, so we need to check if "array" is one of them
14
17
  (Array.isArray(value.type) && value.type.includes("array"));
@@ -148,8 +148,18 @@ const createVersionRoutes = (
148
148
  ];
149
149
  };
150
150
 
151
- export const getVersions = (config: OasPluginConfig) =>
152
- config.type === "file" ? Object.keys(config.input) : [];
151
+ export const getVersionMetadata = (config: OasPluginConfig) => {
152
+ if (config.type === "raw" || !Array.isArray(config.input)) {
153
+ return { versions: [], labels: {} };
154
+ }
155
+
156
+ return {
157
+ versions: config.input.map((v) => v.path),
158
+ labels: Object.fromEntries(
159
+ config.input.map((v) => [v.path, v.label ?? v.path]),
160
+ ),
161
+ };
162
+ };
153
163
 
154
164
  export const getRoutes = ({
155
165
  basePath,
@@ -161,10 +171,33 @@ export const getRoutes = ({
161
171
  basePath: string;
162
172
  }): RouteObject[] => {
163
173
  const tagPages = config.tagPages;
174
+ const { versions } = getVersionMetadata(config);
164
175
 
165
176
  // If the config does not provide tag pages the catch-all
166
177
  // route handles all operations on a single page
167
178
  if (!tagPages) {
179
+ // If there are versions, create versioned routes even without tag pages
180
+ if (versions.length > 0) {
181
+ const versionsInPath =
182
+ versions.length > 1 ? [undefined, ...versions] : [undefined];
183
+
184
+ return versionsInPath.map((version) => {
185
+ const versionPath = joinUrl(basePath, version);
186
+ return createOasProvider({
187
+ basePath,
188
+ version,
189
+ routePath: versionPath,
190
+ routes: [
191
+ createNonTagPagesRoute({ path: `${versionPath}/:tag?` }),
192
+ ...createAdditionalRoutes(versionPath),
193
+ ],
194
+ client,
195
+ config,
196
+ });
197
+ });
198
+ }
199
+
200
+ // No versions, single route
168
201
  return [
169
202
  createOasProvider({
170
203
  basePath,
@@ -179,7 +212,6 @@ export const getRoutes = ({
179
212
  ];
180
213
  }
181
214
 
182
- const versions = getVersions(config);
183
215
  // The latest version always is added as index path
184
216
  const versionsInPath =
185
217
  versions.length > 1 ? [undefined, ...versions] : [undefined];
@@ -0,0 +1,163 @@
1
+ import { DialogTrigger } from "@radix-ui/react-dialog";
2
+ import {
3
+ type PropsWithChildren,
4
+ useCallback,
5
+ useEffect,
6
+ useState,
7
+ } from "react";
8
+ import { Button } from "zudoku/ui/Button.js";
9
+ import {
10
+ Dialog,
11
+ DialogContent,
12
+ DialogDescription,
13
+ DialogFooter,
14
+ DialogHeader,
15
+ DialogTitle,
16
+ } from "zudoku/ui/Dialog.js";
17
+
18
+ type IndexingState =
19
+ | { status: "idle" }
20
+ | { status: "indexing"; total: number; current: number; path: string }
21
+ | { status: "complete"; indexed: number }
22
+ | { status: "error"; message: string };
23
+
24
+ const ProgressBar = ({
25
+ total,
26
+ current,
27
+ barLength = 25,
28
+ emptyChar = "░",
29
+ filledChar = "█",
30
+ }: {
31
+ total: number;
32
+ current: number;
33
+ barLength?: number;
34
+ emptyChar?: string;
35
+ filledChar?: string;
36
+ }) => {
37
+ const percent = Math.round((current / total) * 100);
38
+ const filled = Math.round((percent / 100) * barLength);
39
+ const empty = barLength - filled;
40
+
41
+ return (
42
+ <>
43
+ {filledChar.repeat(filled)}
44
+ {emptyChar.repeat(empty)} {percent}% ({current}/{total})
45
+ </>
46
+ );
47
+ };
48
+
49
+ const IndexingDialog = ({ children }: PropsWithChildren) => {
50
+ const [indexingState, setIndexingState] = useState<IndexingState>({
51
+ status: "idle",
52
+ });
53
+
54
+ const startIndexing = useCallback(() => {
55
+ setIndexingState({ status: "indexing", total: 0, current: 0, path: "" });
56
+
57
+ const eventSource = new EventSource("/__z/pagefind-reindex");
58
+
59
+ eventSource.onmessage = (event) => {
60
+ const data = JSON.parse(event.data);
61
+
62
+ if (data.type === "progress") {
63
+ setIndexingState({
64
+ status: "indexing",
65
+ total: data.total,
66
+ current: data.current,
67
+ path: data.path,
68
+ });
69
+ } else if (data.type === "complete") {
70
+ eventSource.close();
71
+ if (data.success) {
72
+ setIndexingState({ status: "complete", indexed: data.indexed });
73
+ } else {
74
+ setIndexingState({
75
+ status: "error",
76
+ message: data.error ?? "Indexing failed",
77
+ });
78
+ }
79
+ }
80
+ };
81
+
82
+ eventSource.onerror = () => {
83
+ eventSource.close();
84
+ setIndexingState({
85
+ status: "error",
86
+ message: "Connection lost during indexing",
87
+ });
88
+ };
89
+
90
+ return () => eventSource.close();
91
+ }, []);
92
+
93
+ useEffect(() => {
94
+ if (indexingState.status !== "idle") return;
95
+ return startIndexing();
96
+ }, [indexingState.status, startIndexing]);
97
+
98
+ const handleDone = () => {
99
+ if (indexingState.status !== "complete") return;
100
+ window.location.reload();
101
+ };
102
+
103
+ return (
104
+ <Dialog>
105
+ <DialogTrigger asChild>{children}</DialogTrigger>
106
+ <DialogContent
107
+ className="max-w-sm! top-1/3"
108
+ showCloseButton={false}
109
+ onInteractOutside={(e) => e.preventDefault()}
110
+ >
111
+ <DialogHeader>
112
+ <DialogTitle>
113
+ {indexingState.status === "indexing" && "Building Search Index"}
114
+ {indexingState.status === "complete" && "Indexing Complete"}
115
+ {indexingState.status === "error" && "Indexing Failed"}
116
+ {indexingState.status === "idle" && "Build Search Index"}
117
+ </DialogTitle>
118
+ <DialogDescription>
119
+ {indexingState.status === "indexing" && (
120
+ <>
121
+ {indexingState.total > 0 && (
122
+ <div className="font-mono text-sm mb-2">
123
+ <ProgressBar {...indexingState} />
124
+ </div>
125
+ )}
126
+ {indexingState.path && (
127
+ <span className="block text-xs truncate">
128
+ {indexingState.path}
129
+ </span>
130
+ )}
131
+ </>
132
+ )}
133
+ {indexingState.status === "complete" && (
134
+ <>Successfully indexed {indexingState.indexed} pages.</>
135
+ )}
136
+ {indexingState.status === "error" && (
137
+ <span className="text-destructive">{indexingState.message}</span>
138
+ )}
139
+ </DialogDescription>
140
+ </DialogHeader>
141
+ <DialogFooter>
142
+ <div className="flex justify-end gap-2">
143
+ {indexingState.status === "complete" && (
144
+ <Button size="sm" onClick={handleDone}>
145
+ Close and reload
146
+ </Button>
147
+ )}
148
+ {indexingState.status === "error" && (
149
+ <>
150
+ <Button variant="outline" onClick={handleDone}>
151
+ Cancel
152
+ </Button>
153
+ <Button onClick={startIndexing}>Retry</Button>
154
+ </>
155
+ )}
156
+ </div>
157
+ </DialogFooter>
158
+ </DialogContent>
159
+ </Dialog>
160
+ );
161
+ };
162
+
163
+ export default IndexingDialog;