zudoku 0.0.0-e5b3c9b → 0.0.0-e614aaa

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 (293) hide show
  1. package/cli.js +3 -0
  2. package/dist/app/entry.client.d.ts +5 -0
  3. package/dist/app/entry.client.js +1 -0
  4. package/dist/app/entry.client.js.map +1 -1
  5. package/dist/app/main.js +1 -1
  6. package/dist/app/main.js.map +1 -1
  7. package/dist/cli/build/handler.d.ts +1 -3
  8. package/dist/cli/build/handler.js +7 -0
  9. package/dist/cli/build/handler.js.map +1 -1
  10. package/dist/cli/cli.js +5 -0
  11. package/dist/cli/cli.js.map +1 -1
  12. package/dist/cli/cmds/build.d.ts +11 -3
  13. package/dist/cli/cmds/build.js +20 -13
  14. package/dist/cli/cmds/build.js.map +1 -1
  15. package/dist/cli/cmds/preview.d.ts +16 -0
  16. package/dist/cli/cmds/preview.js +25 -0
  17. package/dist/cli/cmds/preview.js.map +1 -0
  18. package/dist/cli/preview/handler.d.ts +3 -0
  19. package/dist/cli/preview/handler.js +37 -0
  20. package/dist/cli/preview/handler.js.map +1 -0
  21. package/dist/config/common.d.ts +5 -2
  22. package/dist/config/config.d.ts +2 -7
  23. package/dist/config/loader.d.ts +4 -4
  24. package/dist/config/loader.js +7 -4
  25. package/dist/config/loader.js.map +1 -1
  26. package/dist/config/validators/common.d.ts +226 -8
  27. package/dist/config/validators/common.js +26 -8
  28. package/dist/config/validators/common.js.map +1 -1
  29. package/dist/config/validators/validate.d.ts +90 -3
  30. package/dist/index.d.ts +1 -0
  31. package/dist/index.js +1 -0
  32. package/dist/index.js.map +1 -1
  33. package/dist/lib/authentication/providers/auth0.d.ts +2 -2
  34. package/dist/lib/authentication/providers/auth0.js +1 -0
  35. package/dist/lib/authentication/providers/auth0.js.map +1 -1
  36. package/dist/lib/authentication/providers/clerk.js +6 -6
  37. package/dist/lib/authentication/providers/clerk.js.map +1 -1
  38. package/dist/lib/authentication/providers/openid.d.ts +4 -3
  39. package/dist/lib/authentication/providers/openid.js +4 -2
  40. package/dist/lib/authentication/providers/openid.js.map +1 -1
  41. package/dist/lib/authentication/state.js +8 -2
  42. package/dist/lib/authentication/state.js.map +1 -1
  43. package/dist/lib/components/Banner.js +1 -1
  44. package/dist/lib/components/Banner.js.map +1 -1
  45. package/dist/lib/components/Heading.d.ts +1 -1
  46. package/dist/lib/components/Layout.js +1 -1
  47. package/dist/lib/components/Layout.js.map +1 -1
  48. package/dist/lib/components/ThemeSwitch.js +1 -1
  49. package/dist/lib/components/ThemeSwitch.js.map +1 -1
  50. package/dist/lib/components/Zudoku.d.ts +1 -1
  51. package/dist/lib/components/Zudoku.js +7 -6
  52. package/dist/lib/components/Zudoku.js.map +1 -1
  53. package/dist/lib/components/context/RouterEventsEmitter.d.ts +1 -0
  54. package/dist/lib/components/context/RouterEventsEmitter.js +17 -0
  55. package/dist/lib/components/context/RouterEventsEmitter.js.map +1 -0
  56. package/dist/lib/components/index.d.ts +1 -1
  57. package/dist/lib/core/RouteGuard.d.ts +1 -1
  58. package/dist/lib/core/RouteGuard.js +22 -9
  59. package/dist/lib/core/RouteGuard.js.map +1 -1
  60. package/dist/lib/core/ZudokuContext.d.ts +10 -0
  61. package/dist/lib/core/ZudokuContext.js +17 -1
  62. package/dist/lib/core/ZudokuContext.js.map +1 -1
  63. package/dist/lib/core/plugins.d.ts +8 -2
  64. package/dist/lib/core/plugins.js +1 -0
  65. package/dist/lib/core/plugins.js.map +1 -1
  66. package/dist/lib/hooks/index.d.ts +3 -0
  67. package/dist/lib/hooks/index.js +5 -0
  68. package/dist/lib/hooks/index.js.map +1 -0
  69. package/dist/lib/hooks/useEvent.d.ts +11 -0
  70. package/dist/lib/hooks/useEvent.js +19 -0
  71. package/dist/lib/hooks/useEvent.js.map +1 -0
  72. package/dist/lib/hooks/useEvent.test.d.ts +1 -0
  73. package/dist/lib/hooks/useEvent.test.js +100 -0
  74. package/dist/lib/hooks/useEvent.test.js.map +1 -0
  75. package/dist/lib/plugins/api-catalog/Catalog.d.ts +3 -1
  76. package/dist/lib/plugins/api-catalog/Catalog.js +7 -4
  77. package/dist/lib/plugins/api-catalog/Catalog.js.map +1 -1
  78. package/dist/lib/plugins/api-catalog/index.js +1 -1
  79. package/dist/lib/plugins/api-catalog/index.js.map +1 -1
  80. package/dist/lib/plugins/markdown/MdxPage.js +1 -1
  81. package/dist/lib/plugins/markdown/MdxPage.js.map +1 -1
  82. package/dist/lib/plugins/openapi/OperationList.d.ts +1 -1
  83. package/dist/lib/plugins/openapi/OperationList.js +2 -1
  84. package/dist/lib/plugins/openapi/OperationList.js.map +1 -1
  85. package/dist/lib/plugins/openapi/OperationListItem.js +2 -1
  86. package/dist/lib/plugins/openapi/OperationListItem.js.map +1 -1
  87. package/dist/lib/plugins/openapi/ParameterList.d.ts +2 -1
  88. package/dist/lib/plugins/openapi/ParameterList.js +3 -2
  89. package/dist/lib/plugins/openapi/ParameterList.js.map +1 -1
  90. package/dist/lib/plugins/openapi/Sidecar.js +1 -1
  91. package/dist/lib/plugins/openapi/Sidecar.js.map +1 -1
  92. package/dist/lib/plugins/openapi/playground/PathParams.d.ts +3 -2
  93. package/dist/lib/plugins/openapi/playground/PathParams.js +3 -2
  94. package/dist/lib/plugins/openapi/playground/PathParams.js.map +1 -1
  95. package/dist/lib/plugins/openapi/playground/Playground.js +3 -3
  96. package/dist/lib/plugins/openapi/playground/Playground.js.map +1 -1
  97. package/dist/lib/plugins/search-pagefind/PagefindSearch.d.ts +6 -0
  98. package/dist/lib/plugins/search-pagefind/PagefindSearch.js +67 -0
  99. package/dist/lib/plugins/search-pagefind/PagefindSearch.js.map +1 -0
  100. package/dist/lib/plugins/search-pagefind/ResultList.d.ts +7 -0
  101. package/dist/lib/plugins/search-pagefind/ResultList.js +30 -0
  102. package/dist/lib/plugins/search-pagefind/ResultList.js.map +1 -0
  103. package/dist/lib/plugins/search-pagefind/get-results.d.ts +3 -0
  104. package/dist/lib/plugins/search-pagefind/get-results.js +37 -0
  105. package/dist/lib/plugins/search-pagefind/get-results.js.map +1 -0
  106. package/dist/lib/plugins/search-pagefind/index.d.ts +6 -0
  107. package/dist/lib/plugins/search-pagefind/index.js +9 -0
  108. package/dist/lib/plugins/search-pagefind/index.js.map +1 -0
  109. package/dist/lib/plugins/search-pagefind/types.d.ts +85 -0
  110. package/dist/lib/plugins/search-pagefind/types.js +2 -0
  111. package/dist/lib/plugins/search-pagefind/types.js.map +1 -0
  112. package/dist/lib/ui/Command.d.ts +7 -1
  113. package/dist/lib/ui/Command.js +2 -2
  114. package/dist/lib/ui/Command.js.map +1 -1
  115. package/dist/lib/ui/Stepper.d.ts +4 -0
  116. package/dist/lib/ui/Stepper.js +7 -0
  117. package/dist/lib/ui/Stepper.js.map +1 -0
  118. package/dist/lib/util/MdxComponents.d.ts +3 -1
  119. package/dist/lib/util/MdxComponents.js +3 -0
  120. package/dist/lib/util/MdxComponents.js.map +1 -1
  121. package/dist/lib/util/useScrollToAnchor.js +6 -8
  122. package/dist/lib/util/useScrollToAnchor.js.map +1 -1
  123. package/dist/vite/build.js +5 -0
  124. package/dist/vite/build.js.map +1 -1
  125. package/dist/vite/config.d.ts +2 -5
  126. package/dist/vite/config.js +30 -35
  127. package/dist/vite/config.js.map +1 -1
  128. package/dist/vite/config.test.js +1 -1
  129. package/dist/vite/config.test.js.map +1 -1
  130. package/dist/vite/css/plugin.d.ts +2 -2
  131. package/dist/vite/css/plugin.js.map +1 -1
  132. package/dist/vite/dev-server.js +8 -0
  133. package/dist/vite/dev-server.js.map +1 -1
  134. package/dist/vite/pagefind.d.ts +4 -0
  135. package/dist/vite/pagefind.js +15 -0
  136. package/dist/vite/pagefind.js.map +1 -0
  137. package/dist/vite/plugin-api-keys.d.ts +2 -2
  138. package/dist/vite/plugin-api-keys.js +3 -3
  139. package/dist/vite/plugin-api-keys.js.map +1 -1
  140. package/dist/vite/plugin-api.d.ts +2 -2
  141. package/dist/vite/plugin-api.js +5 -5
  142. package/dist/vite/plugin-api.js.map +1 -1
  143. package/dist/vite/plugin-auth.d.ts +2 -2
  144. package/dist/vite/plugin-auth.js +3 -3
  145. package/dist/vite/plugin-auth.js.map +1 -1
  146. package/dist/vite/plugin-component.d.ts +2 -2
  147. package/dist/vite/plugin-component.js +7 -2
  148. package/dist/vite/plugin-component.js.map +1 -1
  149. package/dist/vite/plugin-config-reload.d.ts +4 -3
  150. package/dist/vite/plugin-config-reload.js +11 -6
  151. package/dist/vite/plugin-config-reload.js.map +1 -1
  152. package/dist/vite/plugin-config.d.ts +2 -2
  153. package/dist/vite/plugin-config.js +2 -2
  154. package/dist/vite/plugin-config.js.map +1 -1
  155. package/dist/vite/plugin-custom-pages.d.ts +2 -2
  156. package/dist/vite/plugin-custom-pages.js +3 -3
  157. package/dist/vite/plugin-custom-pages.js.map +1 -1
  158. package/dist/vite/plugin-docs.d.ts +2 -2
  159. package/dist/vite/plugin-docs.js +5 -5
  160. package/dist/vite/plugin-docs.js.map +1 -1
  161. package/dist/vite/plugin-frontmatter.d.ts +2 -2
  162. package/dist/vite/plugin-frontmatter.js +5 -4
  163. package/dist/vite/plugin-frontmatter.js.map +1 -1
  164. package/dist/vite/plugin-mdx.d.ts +2 -2
  165. package/dist/vite/plugin-mdx.js +1 -1
  166. package/dist/vite/plugin-mdx.js.map +1 -1
  167. package/dist/vite/plugin-redirect.d.ts +2 -2
  168. package/dist/vite/plugin-redirect.js +3 -3
  169. package/dist/vite/plugin-redirect.js.map +1 -1
  170. package/dist/vite/plugin-search.d.ts +2 -2
  171. package/dist/vite/plugin-search.js +5 -1
  172. package/dist/vite/plugin-search.js.map +1 -1
  173. package/dist/vite/plugin-sidebar.d.ts +2 -2
  174. package/dist/vite/plugin-sidebar.js +2 -2
  175. package/dist/vite/plugin-sidebar.js.map +1 -1
  176. package/dist/vite/plugin-theme-css.d.ts +2 -2
  177. package/dist/vite/plugin-theme-css.js.map +1 -1
  178. package/dist/vite/plugin.d.ts +2 -2
  179. package/dist/vite/plugin.js.map +1 -1
  180. package/dist/vite/prerender/FileWritingResponse.d.ts +2 -1
  181. package/dist/vite/prerender/FileWritingResponse.js +4 -1
  182. package/dist/vite/prerender/FileWritingResponse.js.map +1 -1
  183. package/dist/vite/prerender/prerender.js +3 -21
  184. package/dist/vite/prerender/prerender.js.map +1 -1
  185. package/dist/vite/prerender/worker.d.ts +1 -1
  186. package/dist/vite/prerender/worker.js +3 -0
  187. package/dist/vite/prerender/worker.js.map +1 -1
  188. package/dist/vite/sitemap.js +2 -1
  189. package/dist/vite/sitemap.js.map +1 -1
  190. package/lib/{AuthenticationPlugin-BlxA4Mbn.js → AuthenticationPlugin-CiO1FM6Q.js} +2 -2
  191. package/lib/{AuthenticationPlugin-BlxA4Mbn.js.map → AuthenticationPlugin-CiO1FM6Q.js.map} +1 -1
  192. package/lib/Dialog-DIKGQxQc.js +83 -0
  193. package/lib/Dialog-DIKGQxQc.js.map +1 -0
  194. package/lib/{Markdown-Cr9sYpR_.js → Markdown-DePfm7oQ.js} +1384 -1381
  195. package/lib/{Markdown-Cr9sYpR_.js.map → Markdown-DePfm7oQ.js.map} +1 -1
  196. package/lib/MdxPage-BN6l6zXL.js +201 -0
  197. package/lib/MdxPage-BN6l6zXL.js.map +1 -0
  198. package/lib/{OasProvider-WVtvHP5H.js → OasProvider-BSuptFK-.js} +3 -3
  199. package/lib/{OasProvider-WVtvHP5H.js.map → OasProvider-BSuptFK-.js.map} +1 -1
  200. package/lib/{OperationList-DhOwupvv.js → OperationList-DOcfDmJP.js} +1569 -1530
  201. package/lib/OperationList-DOcfDmJP.js.map +1 -0
  202. package/lib/{Select-D9hI1G-y.js → Select-Dqtcn53H.js} +36 -36
  203. package/lib/{Select-D9hI1G-y.js.map → Select-Dqtcn53H.js.map} +1 -1
  204. package/lib/{SlotletProvider-CEfNOA8i.js → SlotletProvider-DdtIOUi6.js} +2 -2
  205. package/lib/{SlotletProvider-CEfNOA8i.js.map → SlotletProvider-DdtIOUi6.js.map} +1 -1
  206. package/lib/{createServer-DMf6O2Rz.js → createServer-DmusVVsi.js} +987 -967
  207. package/lib/createServer-DmusVVsi.js.map +1 -0
  208. package/lib/{hook-CWwSAAlH.js → hook-CN__aZIt.js} +67 -63
  209. package/lib/{hook-CWwSAAlH.js.map → hook-CN__aZIt.js.map} +1 -1
  210. package/lib/{index-Do_30Hpk.js → index-BTbP6Lgf.js} +194 -189
  211. package/lib/index-BTbP6Lgf.js.map +1 -0
  212. package/lib/index-DwT-v3zK.js +86 -0
  213. package/lib/index-DwT-v3zK.js.map +1 -0
  214. package/lib/index-gQD2h1wX.js +447 -0
  215. package/lib/index-gQD2h1wX.js.map +1 -0
  216. package/lib/{mutation-B0wxqzSN.js → mutation-EclmI0is.js} +2 -2
  217. package/lib/{mutation-B0wxqzSN.js.map → mutation-EclmI0is.js.map} +1 -1
  218. package/lib/objectEntries-yMIkr2mI.js +5 -0
  219. package/lib/objectEntries-yMIkr2mI.js.map +1 -0
  220. package/lib/ui/Command.js +105 -78
  221. package/lib/ui/Command.js.map +1 -1
  222. package/lib/ui/Stepper.js +6 -0
  223. package/lib/ui/Stepper.js.map +1 -0
  224. package/lib/ui/SyntaxHighlight.js +2902 -7
  225. package/lib/ui/SyntaxHighlight.js.map +1 -1
  226. package/lib/{useScrollToAnchor-C-sRxs9o.js → useScrollToAnchor-RyqO3lj4.js} +20 -20
  227. package/lib/{useScrollToAnchor-C-sRxs9o.js.map → useScrollToAnchor-RyqO3lj4.js.map} +1 -1
  228. package/lib/zudoku.auth-auth0.js +15 -14
  229. package/lib/zudoku.auth-auth0.js.map +1 -1
  230. package/lib/zudoku.auth-clerk.js +29 -29
  231. package/lib/zudoku.auth-clerk.js.map +1 -1
  232. package/lib/zudoku.auth-openid.js +126 -124
  233. package/lib/zudoku.auth-openid.js.map +1 -1
  234. package/lib/zudoku.components.js +443 -389
  235. package/lib/zudoku.components.js.map +1 -1
  236. package/lib/zudoku.css +1 -0
  237. package/lib/zudoku.hooks.js +19 -0
  238. package/lib/zudoku.hooks.js.map +1 -0
  239. package/lib/zudoku.plugin-api-catalog.js +86 -70
  240. package/lib/zudoku.plugin-api-catalog.js.map +1 -1
  241. package/lib/zudoku.plugin-api-keys.js +5 -5
  242. package/lib/zudoku.plugin-custom-pages.js +1 -1
  243. package/lib/zudoku.plugin-markdown.js +1 -1
  244. package/lib/zudoku.plugin-openapi.js +2 -2
  245. package/lib/zudoku.plugins.js +9 -8
  246. package/lib/zudoku.plugins.js.map +1 -1
  247. package/package.json +17 -4
  248. package/src/app/entry.client.tsx +8 -0
  249. package/src/app/main.tsx +1 -1
  250. package/src/lib/authentication/providers/auth0.tsx +3 -2
  251. package/src/lib/authentication/providers/clerk.tsx +10 -6
  252. package/src/lib/authentication/providers/openid.tsx +8 -5
  253. package/src/lib/authentication/state.ts +8 -2
  254. package/src/lib/components/Banner.tsx +1 -0
  255. package/src/lib/components/Heading.tsx +1 -1
  256. package/src/lib/components/Layout.tsx +1 -0
  257. package/src/lib/components/ThemeSwitch.tsx +1 -1
  258. package/src/lib/components/Zudoku.tsx +9 -5
  259. package/src/lib/components/context/RouterEventsEmitter.tsx +19 -0
  260. package/src/lib/core/RouteGuard.tsx +42 -8
  261. package/src/lib/core/ZudokuContext.ts +31 -0
  262. package/src/lib/core/plugins.ts +16 -2
  263. package/src/lib/hooks/index.ts +5 -0
  264. package/src/lib/hooks/useEvent.test.tsx +149 -0
  265. package/src/lib/hooks/useEvent.ts +41 -0
  266. package/src/lib/plugins/api-catalog/Catalog.tsx +23 -7
  267. package/src/lib/plugins/api-catalog/index.tsx +1 -0
  268. package/src/lib/plugins/markdown/MdxPage.tsx +5 -1
  269. package/src/lib/plugins/openapi/OperationList.tsx +15 -3
  270. package/src/lib/plugins/openapi/OperationListItem.tsx +8 -0
  271. package/src/lib/plugins/openapi/ParameterList.tsx +4 -0
  272. package/src/lib/plugins/openapi/Sidecar.tsx +2 -1
  273. package/src/lib/plugins/openapi/playground/PathParams.tsx +8 -2
  274. package/src/lib/plugins/openapi/playground/Playground.tsx +3 -3
  275. package/src/lib/plugins/search-pagefind/PagefindSearch.tsx +139 -0
  276. package/src/lib/plugins/search-pagefind/ResultList.tsx +98 -0
  277. package/src/lib/plugins/search-pagefind/get-results.tsx +54 -0
  278. package/src/lib/plugins/search-pagefind/index.tsx +21 -0
  279. package/src/lib/plugins/search-pagefind/types.ts +118 -0
  280. package/src/lib/ui/Command.tsx +25 -3
  281. package/src/lib/ui/Stepper.css +43 -0
  282. package/src/lib/ui/Stepper.tsx +8 -0
  283. package/src/lib/util/MdxComponents.tsx +4 -1
  284. package/src/lib/util/useScrollToAnchor.ts +8 -8
  285. package/lib/MdxPage-Dt-UEQl8.js +0 -193
  286. package/lib/MdxPage-Dt-UEQl8.js.map +0 -1
  287. package/lib/OperationList-DhOwupvv.js.map +0 -1
  288. package/lib/SyntaxHighlight-CcnUjERD.js +0 -2986
  289. package/lib/SyntaxHighlight-CcnUjERD.js.map +0 -1
  290. package/lib/createServer-DMf6O2Rz.js.map +0 -1
  291. package/lib/index-Do_30Hpk.js.map +0 -1
  292. package/lib/index-Du5aNddU.js +0 -509
  293. package/lib/index-Du5aNddU.js.map +0 -1
@@ -1,4 +1,4 @@
1
- import { Control, Controller, useFieldArray } from "react-hook-form";
1
+ import { type Control, Controller, useFieldArray } from "react-hook-form";
2
2
  import { Card } from "zudoku/ui/Card.js";
3
3
  import { Input } from "../../../ui/Input.js";
4
4
  import { ColorizedParam } from "../ColorizedParam.js";
@@ -7,18 +7,24 @@ import type { PlaygroundForm } from "./Playground.js";
7
7
 
8
8
  export const PathParams = ({
9
9
  control,
10
+ url,
10
11
  }: {
11
12
  control: Control<PlaygroundForm>;
13
+ url: string;
12
14
  }) => {
13
15
  const { fields } = useFieldArray<PlaygroundForm, "pathParams">({
14
16
  control,
15
17
  name: "pathParams",
16
18
  });
17
19
 
20
+ const sortedFields = [...fields].sort(
21
+ (a, b) => url.indexOf(`{${a.name}}`) - url.indexOf(`{${b.name}}`),
22
+ );
23
+
18
24
  return (
19
25
  <Card className="rounded-lg">
20
26
  <ParamsGrid>
21
- {fields.map((field, i) => (
27
+ {sortedFields.map((field, i) => (
22
28
  <ParamsGridItem key={field.id}>
23
29
  <Controller
24
30
  control={control}
@@ -262,7 +262,7 @@ export const Playground = ({
262
262
  ));
263
263
 
264
264
  const serverSelect = (
265
- <div className="inline-block opacity-50 hover:opacity-100 transition">
265
+ <div className="inline-block opacity-50 hover:opacity-100 transition translate-y-[4px]">
266
266
  {server ? (
267
267
  <span>{server.replace(/^https?:\/\//, "")}</span>
268
268
  ) : (
@@ -305,7 +305,7 @@ export const Playground = ({
305
305
  <div className="border-r p-2 bg-muted rounded-l-md self-stretch font-semibold font-mono flex items-center">
306
306
  {method.toUpperCase()}
307
307
  </div>
308
- <div className="items-center p-2 font-mono text-xs break-words">
308
+ <div className="items-center px-2 py-0.5 font-mono text-xs break-all leading-6">
309
309
  {serverSelect}
310
310
  {path}
311
311
  {urlQueryParams.length > 0 ? "?" : ""}
@@ -351,7 +351,7 @@ export const Playground = ({
351
351
  {pathParams.length > 0 && (
352
352
  <div className="flex flex-col gap-4 my-4">
353
353
  <span className="font-semibold">Path Parameters</span>
354
- <PathParams control={control} />
354
+ <PathParams url={url} control={control} />
355
355
  </div>
356
356
  )}
357
357
  <div className="flex flex-col gap-4 my-4">
@@ -0,0 +1,139 @@
1
+ import { VisuallyHidden } from "@radix-ui/react-visually-hidden";
2
+ import { keepPreviousData, useQuery } from "@tanstack/react-query";
3
+ import { useState } from "react";
4
+ import { Callout } from "zudoku/ui/Callout.js";
5
+ import {
6
+ CommandDialog,
7
+ CommandEmpty,
8
+ CommandInput,
9
+ } from "zudoku/ui/Command.js";
10
+ import { DialogTitle } from "zudoku/ui/Dialog.js";
11
+ import { joinUrl } from "../../util/joinUrl.js";
12
+ import { getResults } from "./get-results.js";
13
+ import type { PagefindOptions } from "./index.js";
14
+ import { ResultList } from "./ResultList.js";
15
+ import type { Pagefind } from "./types.js";
16
+
17
+ const DEFAULT_RANKING = {
18
+ // Slightly lower than default because API docs tend to have repetitive terms (parameter names, HTTP methods, etc.)
19
+ termFrequency: 0.8,
20
+ // Lower than default because API documentation pages tend to be longer due to comprehensive endpoint documentation
21
+ pageLength: 0.6,
22
+ // Slightly higher than default because in technical documentation, exact matches should be prioritized
23
+ termSimilarity: 1.2,
24
+ // Slightly lower than default because API docs might have legitimate repetition of terms
25
+ termSaturation: 1.2,
26
+ };
27
+
28
+ const importPagefind = (): Promise<Pagefind> =>
29
+ import.meta.env.DEV
30
+ ? // @ts-expect-error TypeScript can't resolve the import
31
+ import(/* @vite-ignore */ "/pagefind/pagefind.js")
32
+ : import(
33
+ /* @vite-ignore */ joinUrl(
34
+ import.meta.env.BASE_URL,
35
+ "/pagefind/pagefind.js",
36
+ )
37
+ );
38
+
39
+ const usePagefind = (options: PagefindOptions) => {
40
+ const { data: pagefind, ...result } = useQuery<Pagefind>({
41
+ queryKey: ["pagefind", options.ranking],
42
+ retry: false,
43
+ queryFn: async () => {
44
+ const pagefind = await importPagefind();
45
+ await pagefind.init();
46
+ await pagefind.options({
47
+ ranking: {
48
+ termFrequency:
49
+ options.ranking?.termFrequency ?? DEFAULT_RANKING.termFrequency,
50
+ pageLength: options.ranking?.pageLength ?? DEFAULT_RANKING.pageLength,
51
+ termSimilarity:
52
+ options.ranking?.termSimilarity ?? DEFAULT_RANKING.termSimilarity,
53
+ termSaturation:
54
+ options.ranking?.termSaturation ?? DEFAULT_RANKING.termSaturation,
55
+ },
56
+ });
57
+
58
+ return pagefind;
59
+ },
60
+ enabled: typeof window !== "undefined",
61
+ });
62
+
63
+ if (result.isError) {
64
+ // eslint-disable-next-line no-console
65
+ console.error(result.error);
66
+ }
67
+
68
+ return { ...result, pagefind };
69
+ };
70
+
71
+ export const PagefindSearch = ({
72
+ isOpen,
73
+ onClose,
74
+ options,
75
+ }: {
76
+ isOpen: boolean;
77
+ onClose: () => void;
78
+ options: PagefindOptions;
79
+ }) => {
80
+ const { pagefind, error, isError } = usePagefind(options);
81
+ const [searchTerm, setSearchTerm] = useState("");
82
+
83
+ const { data: searchResults } = useQuery({
84
+ queryKey: ["pagefind-search", searchTerm],
85
+ queryFn: async () => {
86
+ const search = await pagefind?.search(searchTerm);
87
+ if (!search) return [];
88
+ return getResults(search, options);
89
+ },
90
+ placeholderData: keepPreviousData,
91
+ enabled: !!pagefind && !!searchTerm,
92
+ });
93
+
94
+ return (
95
+ <CommandDialog
96
+ command={{ shouldFilter: false }}
97
+ content={{ className: "max-w-[750px]" }}
98
+ open={isOpen}
99
+ onOpenChange={onClose}
100
+ >
101
+ <VisuallyHidden>
102
+ <DialogTitle>Search</DialogTitle>
103
+ </VisuallyHidden>
104
+ <CommandInput
105
+ placeholder="Search..."
106
+ value={searchTerm}
107
+ onValueChange={setSearchTerm}
108
+ disabled={isError}
109
+ />
110
+ <CommandEmpty>
111
+ {searchTerm ? "No results found." : "Start typing to search"}
112
+ </CommandEmpty>
113
+ {isError ? (
114
+ <div className="p-4 text-sm">
115
+ {error.message === "NOT_BUILT_YET" ? (
116
+ <Callout type="info">
117
+ Search is currently not available in development mode by default.
118
+ <br />
119
+ To still use search in development, run <code>
120
+ zudoku build
121
+ </code>{" "}
122
+ and copy the <code>dist/pagefind</code> directory to your{" "}
123
+ <code>public</code> directory.
124
+ </Callout>
125
+ ) : (
126
+ "An error occurred while loading search."
127
+ )}
128
+ </div>
129
+ ) : (
130
+ <ResultList
131
+ searchResults={searchResults ?? []}
132
+ searchTerm={searchTerm}
133
+ onClose={onClose}
134
+ maxSubResults={options.maxSubResults}
135
+ />
136
+ )}
137
+ </CommandDialog>
138
+ );
139
+ };
@@ -0,0 +1,98 @@
1
+ import { FileTextIcon } from "lucide-react";
2
+ import { Link, useNavigate } from "react-router";
3
+ import { CommandGroup, CommandItem, CommandList } from "zudoku/ui/Command.js";
4
+ import {
5
+ type PagefindSearchFragment,
6
+ type PagefindSubResult,
7
+ } from "./types.js";
8
+
9
+ const sortSubResults = (a: PagefindSubResult, b: PagefindSubResult) => {
10
+ const aScore = a.weighted_locations.reduce(
11
+ (sum, loc) => sum + loc.balanced_score,
12
+ 0,
13
+ );
14
+ const bScore = b.weighted_locations.reduce(
15
+ (sum, loc) => sum + loc.balanced_score,
16
+ 0,
17
+ );
18
+ return bScore - aScore;
19
+ };
20
+
21
+ const hoverClassname = `cursor-pointer border border-transparent data-[selected=true]:border-border`;
22
+
23
+ const cleanResultUrl = (url: string) => {
24
+ const clean = url.replace(".html", "");
25
+ return clean.startsWith(import.meta.env.BASE_URL)
26
+ ? clean.slice(import.meta.env.BASE_URL.length)
27
+ : clean;
28
+ };
29
+
30
+ export const ResultList = ({
31
+ searchResults,
32
+ searchTerm,
33
+ onClose,
34
+ maxSubResults = 4,
35
+ }: {
36
+ searchResults: PagefindSearchFragment[];
37
+ searchTerm: string;
38
+ onClose: () => void;
39
+ maxSubResults?: number;
40
+ }) => {
41
+ const navigate = useNavigate();
42
+
43
+ return (
44
+ <CommandList className="max-h-[450px]">
45
+ {searchTerm && searchResults.length > 0 && (
46
+ <CommandGroup
47
+ className="text-sm text-muted-foreground"
48
+ heading={`${searchResults.length} results for "${searchTerm}"`}
49
+ />
50
+ )}
51
+ {searchResults.map((result) => (
52
+ <CommandGroup
53
+ key={[result.meta.title ?? result.excerpt, result.url].join("-")}
54
+ >
55
+ <CommandItem
56
+ asChild
57
+ value={`${result.meta.title}-${result.url}`}
58
+ className={hoverClassname}
59
+ onSelect={() => {
60
+ void navigate(cleanResultUrl(result.url));
61
+ onClose();
62
+ }}
63
+ >
64
+ <Link to={cleanResultUrl(result.url)}>
65
+ <FileTextIcon size={20} className="text-muted-foreground" />
66
+ {result.meta.title}
67
+ </Link>
68
+ </CommandItem>
69
+ {result.sub_results
70
+ .sort(sortSubResults)
71
+ .slice(0, maxSubResults)
72
+ .map((subResult) => (
73
+ <CommandItem
74
+ asChild
75
+ key={`${result.meta.title}-${subResult.url}`}
76
+ value={`${result.meta.title}-${subResult.url}`}
77
+ className={hoverClassname}
78
+ onSelect={() => {
79
+ void navigate(cleanResultUrl(subResult.url));
80
+ onClose();
81
+ }}
82
+ >
83
+ <Link to={cleanResultUrl(subResult.url)} onClick={onClose}>
84
+ <div className="flex flex-col items-start gap-2 ms-2.5 ps-5 border-l border-muted-foreground/50">
85
+ <span className="font-bold">{subResult.title}</span>
86
+ <span
87
+ className="text-[13px] [&_mark]:bg-primary [&_mark]:text-primary-foreground"
88
+ dangerouslySetInnerHTML={{ __html: subResult.excerpt }}
89
+ />
90
+ </div>
91
+ </Link>
92
+ </CommandItem>
93
+ ))}
94
+ </CommandGroup>
95
+ ))}
96
+ </CommandList>
97
+ );
98
+ };
@@ -0,0 +1,54 @@
1
+ import type { PagefindOptions } from "./index.js";
2
+ import type { PagefindSearchFragment, PagefindSearchResults } from "./types.js";
3
+
4
+ export const getResults = async (
5
+ search: PagefindSearchResults,
6
+ options: PagefindOptions,
7
+ ) => {
8
+ const maxResults = options.maxResults ?? 10;
9
+ const transformFn = options.transformResults ?? (() => true);
10
+
11
+ const transformedResults: PagefindSearchFragment[] = [];
12
+
13
+ const generator = searchResultGenerator(search, transformFn);
14
+
15
+ for await (const result of generator) {
16
+ transformedResults.push(result);
17
+ if (transformedResults.length >= maxResults) break;
18
+ }
19
+
20
+ return transformedResults;
21
+ };
22
+
23
+ async function* searchResultGenerator(
24
+ search: PagefindSearchResults,
25
+ transformFn: NonNullable<PagefindOptions["transformResults"]>,
26
+ ) {
27
+ const batchSize = 5;
28
+ let processedCount = 0;
29
+
30
+ while (processedCount < search.results.length) {
31
+ const batch = search.results.slice(
32
+ processedCount,
33
+ processedCount + batchSize,
34
+ );
35
+ processedCount += batch.length;
36
+
37
+ const batchData = await Promise.all(batch.map((result) => result.data()));
38
+
39
+ for (const result of batchData) {
40
+ const transformed = transformFn(result);
41
+
42
+ if (transformed === false) {
43
+ // Skip this result
44
+ continue;
45
+ } else if (transformed === true || transformed == null) {
46
+ // Keep the original result
47
+ yield result;
48
+ } else {
49
+ // Return the transformed result
50
+ yield transformed;
51
+ }
52
+ }
53
+ }
54
+ }
@@ -0,0 +1,21 @@
1
+ import type { ZudokuConfig } from "../../../config/validators/validate.js";
2
+ import { ClientOnly } from "../../components/ClientOnly.js";
3
+ import type { ZudokuPlugin } from "../../core/plugins.js";
4
+ import { PagefindSearch } from "./PagefindSearch.js";
5
+
6
+ export type PagefindOptions = Extract<
7
+ ZudokuConfig["search"],
8
+ { type: "pagefind" }
9
+ >;
10
+
11
+ export const pagefindSearchPlugin = (
12
+ options: PagefindOptions,
13
+ ): ZudokuPlugin => {
14
+ return {
15
+ renderSearch: ({ isOpen, onClose }) => (
16
+ <ClientOnly>
17
+ <PagefindSearch isOpen={isOpen} onClose={onClose} options={options} />
18
+ </ClientOnly>
19
+ ),
20
+ };
21
+ };
@@ -0,0 +1,118 @@
1
+ interface PagefindIndexOptions {
2
+ basePath?: string;
3
+ baseUrl?: string;
4
+ excerptLength?: number;
5
+ indexWeight?: number;
6
+ mergeFilter?: Record<string, unknown>;
7
+ highlightParam?: string;
8
+ language?: string;
9
+ primary?: boolean;
10
+ ranking?: PagefindRankingWeights;
11
+ }
12
+
13
+ interface PagefindRankingWeights {
14
+ termSimilarity?: number;
15
+ pageLength?: number;
16
+ termSaturation?: number;
17
+ termFrequency?: number;
18
+ }
19
+
20
+ interface PagefindSearchOptions {
21
+ preload?: boolean;
22
+ verbose?: boolean;
23
+ filters?: Record<string, unknown>;
24
+ sort?: Record<string, unknown>;
25
+ }
26
+
27
+ type PagefindFilterCounts = Record<string, Record<string, number>> & {};
28
+
29
+ interface PagefindSearchResults {
30
+ results: PagefindSearchResult[];
31
+ unfilteredResultCount: number;
32
+ filters: PagefindFilterCounts;
33
+ totalFilters: PagefindFilterCounts;
34
+ timings: {
35
+ preload: number;
36
+ search: number;
37
+ total: number;
38
+ };
39
+ }
40
+
41
+ interface PagefindSearchResult {
42
+ id: string;
43
+ score: number;
44
+ words: number[];
45
+ data: () => Promise<PagefindSearchFragment>;
46
+ }
47
+
48
+ interface PagefindSearchFragment {
49
+ url: string;
50
+ raw_url?: string;
51
+ content: string;
52
+ raw_content?: string;
53
+ excerpt: string;
54
+ sub_results: PagefindSubResult[];
55
+ word_count: number;
56
+ locations: number[];
57
+ weighted_locations: PagefindWordLocation[];
58
+ filters: Record<string, string[]>;
59
+ meta: Record<string, string>;
60
+ anchors: PagefindSearchAnchor[];
61
+ }
62
+
63
+ interface PagefindSubResult {
64
+ title: string;
65
+ url: string;
66
+ locations: number[];
67
+ weighted_locations: PagefindWordLocation[];
68
+ excerpt: string;
69
+ anchor?: PagefindSearchAnchor;
70
+ }
71
+
72
+ interface PagefindWordLocation {
73
+ weight: number;
74
+ balanced_score: number;
75
+ location: number;
76
+ }
77
+
78
+ interface PagefindSearchAnchor {
79
+ element: string;
80
+ id: string;
81
+ text?: string;
82
+ location: number;
83
+ }
84
+
85
+ interface Pagefind {
86
+ debouncedSearch: (
87
+ query: string,
88
+ options?: PagefindSearchOptions,
89
+ duration?: number,
90
+ ) => Promise<PagefindSearchResults>;
91
+ destroy: () => Promise<void>;
92
+ filters: () => Promise<PagefindFilterCounts>;
93
+ init: () => Promise<void>;
94
+ mergeIndex: (
95
+ indexPath: string,
96
+ options?: Record<string, unknown>,
97
+ ) => Promise<void>;
98
+ options: (options: PagefindIndexOptions) => Promise<void>;
99
+ preload: (term: string, options?: PagefindIndexOptions) => Promise<void>;
100
+ search: (
101
+ term: string,
102
+ options?: PagefindSearchOptions,
103
+ ) => Promise<PagefindSearchResults>;
104
+ }
105
+
106
+ export type {
107
+ Pagefind,
108
+ PagefindFilterCounts,
109
+ PagefindIndexOptions,
110
+ PagefindRankingWeights,
111
+ PagefindSearchAnchor,
112
+ PagefindSearchFragment,
113
+ PagefindSearchOptions,
114
+ PagefindSearchResult,
115
+ PagefindSearchResults,
116
+ PagefindSubResult,
117
+ PagefindWordLocation,
118
+ };
@@ -2,6 +2,7 @@ import { type DialogProps } from "@radix-ui/react-dialog";
2
2
  import { Command as CommandPrimitive } from "cmdk";
3
3
  import { Search } from "lucide-react";
4
4
  import * as React from "react";
5
+ import { ComponentPropsWithoutRef } from "react";
5
6
  import { Dialog, DialogContent } from "zudoku/ui/Dialog.js";
6
7
  import { cn } from "../util/cn.js";
7
8
 
@@ -20,11 +21,32 @@ const Command = React.forwardRef<
20
21
  ));
21
22
  Command.displayName = CommandPrimitive.displayName;
22
23
 
23
- const CommandDialog = ({ children, ...props }: DialogProps) => {
24
+ const CommandDialog = ({
25
+ children,
26
+ command,
27
+ content,
28
+ ...props
29
+ }: DialogProps & {
30
+ command?: ComponentPropsWithoutRef<typeof CommandPrimitive>;
31
+ content?: ComponentPropsWithoutRef<typeof DialogContent>;
32
+ }) => {
24
33
  return (
25
34
  <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">
35
+ <DialogContent
36
+ {...content}
37
+ className={cn(
38
+ "overflow-hidden p-0 shadow-lg top-[15vh] translate-y-[0%]",
39
+ content?.className,
40
+ )}
41
+ aria-describedby={undefined}
42
+ >
43
+ <Command
44
+ {...command}
45
+ className={cn(
46
+ "[&_[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",
47
+ command?.className,
48
+ )}
49
+ >
28
50
  {children}
29
51
  </Command>
30
52
  </DialogContent>
@@ -0,0 +1,43 @@
1
+ @tailwind components;
2
+
3
+ @layer components {
4
+ .stepper {
5
+ /* @apply mb-6; */
6
+ }
7
+
8
+ .stepper > ol {
9
+ --bullet-size: 1.75rem;
10
+ --line-spacing: 0.25rem;
11
+ @apply flex flex-col list-none p-0 m-0;
12
+ counter-reset: step-counter;
13
+ }
14
+
15
+ .stepper > ol > li {
16
+ @apply relative ps-12 pb-6 my-0;
17
+ counter-increment: step-counter;
18
+ }
19
+
20
+ .stepper > ol > li:last-child {
21
+ @apply pb-0;
22
+ }
23
+
24
+ /* Bullet with number */
25
+ .stepper > ol > li::before {
26
+ @apply absolute left-0 flex items-center justify-center size-[--bullet-size];
27
+ @apply bg-muted/80 border border-border text-muted-foreground text-[calc(var(--bullet-size)/2)] font-semibold rounded-full;
28
+ content: counter(step-counter);
29
+ }
30
+
31
+ /* Vertical line */
32
+ .stepper > ol > li::after {
33
+ @apply absolute content-[''] w-[2px] bg-border -translate-x-1/2;
34
+ left: calc(var(--bullet-size) / 2);
35
+ top: calc(var(--bullet-size) + var(--line-spacing));
36
+ height: calc(100% - var(--bullet-size) - (2 * var(--line-spacing)));
37
+ }
38
+
39
+ .stepper > ol > li > :first-child {
40
+ @apply mt-0 pt-0;
41
+ transform: translateY(calc((var(--bullet-size) - 1lh) / 2));
42
+ }
43
+ }
@@ -0,0 +1,8 @@
1
+ import type { PropsWithChildren } from "react";
2
+ import "./Stepper.css";
3
+
4
+ const Stepper = ({ children }: PropsWithChildren) => {
5
+ return <div className="stepper">{children}</div>;
6
+ };
7
+
8
+ export { Stepper };
@@ -1,8 +1,9 @@
1
- import { MDXComponents } from "mdx/types.js";
1
+ import { type MDXComponents } from "mdx/types.js";
2
2
  import { Link } from "react-router";
3
3
  import { Heading } from "../components/Heading.js";
4
4
  import { InlineCode } from "../components/InlineCode.js";
5
5
  import { Callout } from "../ui/Callout.js";
6
+ import { Stepper } from "../ui/Stepper.js";
6
7
  import { SyntaxHighlight } from "../ui/SyntaxHighlight.js";
7
8
 
8
9
  export type MdxComponentsType = Readonly<MDXComponents> | null | undefined;
@@ -51,6 +52,8 @@ export const MdxComponents = {
51
52
  <a href={href} target="_blank" {...props} rel="noreferrer" />
52
53
  ),
53
54
  Callout,
55
+ Stepper,
56
+ SyntaxHighlight,
54
57
  tip: (props) => <Callout type="tip" {...props} />,
55
58
  info: (props) => <Callout type="info" {...props} />,
56
59
  note: (props) => <Callout type="note" {...props} />,
@@ -9,21 +9,21 @@ export const useScrollToHash = () => {
9
9
 
10
10
  const scrollToHash = useCallback(
11
11
  (hash: string) => {
12
- const cleanHash = hash
13
- .replace(/^#/, "")
14
- // Operation list items might have subdivisions that the sidebar doesn't show.
15
- // The subdivisions are separated by a slash so we need to remove everything before the slash to get the sidebar correct item.
16
- .split("/")
17
- .at(0)!;
12
+ const cleanHash = hash.replace(/^#/, "");
13
+
14
+ // Operation list items might have subdivisions that the sidebar doesn't show.
15
+ // The subdivisions are separated by a slash so we need to remove everything before the slash to get the sidebar correct item.
16
+ const linkHash = cleanHash.split("/").at(0)!;
18
17
  const element = document.getElementById(decodeURIComponent(cleanHash));
18
+
19
19
  const link = document.querySelector(
20
- `[${DATA_ANCHOR_ATTR}="${cleanHash}"]`,
20
+ `[${DATA_ANCHOR_ATTR}="${linkHash}"]`,
21
21
  );
22
22
 
23
23
  if (element) {
24
24
  element.scrollIntoView();
25
25
  scrollIntoViewIfNeeded(link);
26
- requestIdleCallback(() => setActiveAnchor(cleanHash));
26
+ requestIdleCallback(() => setActiveAnchor(linkHash));
27
27
  return true;
28
28
  }
29
29