zudoku 0.33.0 → 0.33.2-local.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (177) hide show
  1. package/dist/config/validators/common.d.ts +226 -8
  2. package/dist/config/validators/common.js +26 -8
  3. package/dist/config/validators/common.js.map +1 -1
  4. package/dist/config/validators/validate.d.ts +89 -2
  5. package/dist/lib/authentication/hook.d.ts +1 -0
  6. package/dist/lib/authentication/hook.js +11 -1
  7. package/dist/lib/authentication/hook.js.map +1 -1
  8. package/dist/lib/authentication/providers/clerk.js +6 -6
  9. package/dist/lib/authentication/providers/clerk.js.map +1 -1
  10. package/dist/lib/authentication/state.js +8 -2
  11. package/dist/lib/authentication/state.js.map +1 -1
  12. package/dist/lib/components/Banner.js +1 -1
  13. package/dist/lib/components/Banner.js.map +1 -1
  14. package/dist/lib/components/Heading.d.ts +1 -1
  15. package/dist/lib/components/Layout.js +1 -1
  16. package/dist/lib/components/Layout.js.map +1 -1
  17. package/dist/lib/components/index.d.ts +2 -1
  18. package/dist/lib/core/RouteGuard.d.ts +1 -1
  19. package/dist/lib/core/RouteGuard.js +24 -18
  20. package/dist/lib/core/RouteGuard.js.map +1 -1
  21. package/dist/lib/plugins/api-catalog/Catalog.d.ts +3 -1
  22. package/dist/lib/plugins/api-catalog/Catalog.js +7 -4
  23. package/dist/lib/plugins/api-catalog/Catalog.js.map +1 -1
  24. package/dist/lib/plugins/api-catalog/index.js +1 -1
  25. package/dist/lib/plugins/api-catalog/index.js.map +1 -1
  26. package/dist/lib/plugins/markdown/MdxPage.js +1 -1
  27. package/dist/lib/plugins/markdown/MdxPage.js.map +1 -1
  28. package/dist/lib/plugins/openapi/OperationList.d.ts +1 -1
  29. package/dist/lib/plugins/openapi/OperationList.js +2 -1
  30. package/dist/lib/plugins/openapi/OperationList.js.map +1 -1
  31. package/dist/lib/plugins/openapi/OperationListItem.js +2 -1
  32. package/dist/lib/plugins/openapi/OperationListItem.js.map +1 -1
  33. package/dist/lib/plugins/openapi/ParameterList.d.ts +2 -1
  34. package/dist/lib/plugins/openapi/ParameterList.js +3 -2
  35. package/dist/lib/plugins/openapi/ParameterList.js.map +1 -1
  36. package/dist/lib/plugins/openapi/PlaygroundDialogWrapper.js +3 -1
  37. package/dist/lib/plugins/openapi/PlaygroundDialogWrapper.js.map +1 -1
  38. package/dist/lib/plugins/openapi/Sidecar.js +1 -1
  39. package/dist/lib/plugins/openapi/Sidecar.js.map +1 -1
  40. package/dist/lib/plugins/openapi/playground/PathParams.d.ts +3 -2
  41. package/dist/lib/plugins/openapi/playground/PathParams.js +3 -2
  42. package/dist/lib/plugins/openapi/playground/PathParams.js.map +1 -1
  43. package/dist/lib/plugins/openapi/playground/Playground.d.ts +4 -1
  44. package/dist/lib/plugins/openapi/playground/Playground.js +10 -7
  45. package/dist/lib/plugins/openapi/playground/Playground.js.map +1 -1
  46. package/dist/lib/plugins/search-pagefind/PagefindSearch.d.ts +6 -0
  47. package/dist/lib/plugins/search-pagefind/PagefindSearch.js +66 -0
  48. package/dist/lib/plugins/search-pagefind/PagefindSearch.js.map +1 -0
  49. package/dist/lib/plugins/search-pagefind/ResultList.d.ts +8 -0
  50. package/dist/lib/plugins/search-pagefind/ResultList.js +31 -0
  51. package/dist/lib/plugins/search-pagefind/ResultList.js.map +1 -0
  52. package/dist/lib/plugins/search-pagefind/get-results.d.ts +3 -0
  53. package/dist/lib/plugins/search-pagefind/get-results.js +37 -0
  54. package/dist/lib/plugins/search-pagefind/get-results.js.map +1 -0
  55. package/dist/lib/plugins/search-pagefind/index.d.ts +8 -0
  56. package/dist/lib/plugins/search-pagefind/index.js +9 -0
  57. package/dist/lib/plugins/search-pagefind/index.js.map +1 -0
  58. package/dist/lib/plugins/search-pagefind/types.d.ts +85 -0
  59. package/dist/lib/plugins/search-pagefind/types.js +2 -0
  60. package/dist/lib/plugins/search-pagefind/types.js.map +1 -0
  61. package/dist/lib/ui/Command.d.ts +7 -1
  62. package/dist/lib/ui/Command.js +2 -2
  63. package/dist/lib/ui/Command.js.map +1 -1
  64. package/dist/lib/util/useScrollToAnchor.js +6 -8
  65. package/dist/lib/util/useScrollToAnchor.js.map +1 -1
  66. package/dist/vite/build.js +4 -0
  67. package/dist/vite/build.js.map +1 -1
  68. package/dist/vite/config.js +7 -2
  69. package/dist/vite/config.js.map +1 -1
  70. package/dist/vite/dev-server.js +8 -0
  71. package/dist/vite/dev-server.js.map +1 -1
  72. package/dist/vite/pagefind.d.ts +4 -0
  73. package/dist/vite/pagefind.js +15 -0
  74. package/dist/vite/pagefind.js.map +1 -0
  75. package/dist/vite/plugin-component.js +4 -0
  76. package/dist/vite/plugin-component.js.map +1 -1
  77. package/dist/vite/plugin-search.js +4 -0
  78. package/dist/vite/plugin-search.js.map +1 -1
  79. package/dist/vite/prerender/prerender.js +1 -1
  80. package/dist/vite/prerender/prerender.js.map +1 -1
  81. package/dist/vite/sitemap.js +2 -1
  82. package/dist/vite/sitemap.js.map +1 -1
  83. package/lib/{AuthenticationPlugin-_gUMnGxb.js → AuthenticationPlugin-BCYuduZ9.js} +3 -3
  84. package/lib/{AuthenticationPlugin-_gUMnGxb.js.map → AuthenticationPlugin-BCYuduZ9.js.map} +1 -1
  85. package/lib/Command-CrTA1FX0.js +140 -0
  86. package/lib/Command-CrTA1FX0.js.map +1 -0
  87. package/lib/Dialog-mi6BrnrM.js +83 -0
  88. package/lib/Dialog-mi6BrnrM.js.map +1 -0
  89. package/lib/{Markdown-DePfm7oQ.js → Markdown-DofXBcqg.js} +2 -2
  90. package/lib/{Markdown-DePfm7oQ.js.map → Markdown-DofXBcqg.js.map} +1 -1
  91. package/lib/MdxPage-KJcNWIgt.js +200 -0
  92. package/lib/MdxPage-KJcNWIgt.js.map +1 -0
  93. package/lib/{OasProvider-Bvu4dDpX.js → OasProvider-HcqBeC4H.js} +4 -4
  94. package/lib/{OasProvider-Bvu4dDpX.js.map → OasProvider-HcqBeC4H.js.map} +1 -1
  95. package/lib/{OperationList-DWnNbwVg.js → OperationList-C3wnbFxp.js} +1857 -1816
  96. package/lib/OperationList-C3wnbFxp.js.map +1 -0
  97. package/lib/{Select-BmoX1iTH.js → Select-Co6MuS4j.js} +36 -36
  98. package/lib/{Select-BmoX1iTH.js.map → Select-Co6MuS4j.js.map} +1 -1
  99. package/lib/{SlotletProvider-DdtIOUi6.js → SlotletProvider-CYFNHuok.js} +4 -4
  100. package/lib/{SlotletProvider-DdtIOUi6.js.map → SlotletProvider-CYFNHuok.js.map} +1 -1
  101. package/lib/{chunk-IR6S3I6Y-D_3UmFIn.js → chunk-IR6S3I6Y-CRDBmIgK.js} +3 -3
  102. package/lib/{chunk-IR6S3I6Y-D_3UmFIn.js.map → chunk-IR6S3I6Y-CRDBmIgK.js.map} +1 -1
  103. package/lib/hook-LTe5qHSc.js +347 -0
  104. package/lib/hook-LTe5qHSc.js.map +1 -0
  105. package/lib/{index-DVBlM15k.js → index-CtkRMvMw.js} +696 -743
  106. package/lib/index-CtkRMvMw.js.map +1 -0
  107. package/lib/index-gQD2h1wX.js +447 -0
  108. package/lib/index-gQD2h1wX.js.map +1 -0
  109. package/lib/index-vn5bsvmU.js +1399 -0
  110. package/lib/index-vn5bsvmU.js.map +1 -0
  111. package/lib/{mutation-DTunCQKB.js → mutation-B81DztCT.js} +2 -2
  112. package/lib/{mutation-DTunCQKB.js.map → mutation-B81DztCT.js.map} +1 -1
  113. package/lib/ui/Command.js +105 -78
  114. package/lib/ui/Command.js.map +1 -1
  115. package/lib/{useExposedProps-RIvey2Oy.js → useExposedProps-D76yras4.js} +2 -2
  116. package/lib/{useExposedProps-RIvey2Oy.js.map → useExposedProps-D76yras4.js.map} +1 -1
  117. package/lib/useQuery-CQUwWR9i.js +1137 -0
  118. package/lib/useQuery-CQUwWR9i.js.map +1 -0
  119. package/lib/useScrollToAnchor-DKyrbZoy.js +977 -0
  120. package/lib/useScrollToAnchor-DKyrbZoy.js.map +1 -0
  121. package/lib/zudoku.auth-auth0.js +1 -1
  122. package/lib/zudoku.auth-clerk.js +29 -29
  123. package/lib/zudoku.auth-clerk.js.map +1 -1
  124. package/lib/zudoku.auth-openid.js +3 -3
  125. package/lib/zudoku.components.js +32 -1382
  126. package/lib/zudoku.components.js.map +1 -1
  127. package/lib/zudoku.hooks.js +1 -1
  128. package/lib/zudoku.plugin-api-catalog.js +87 -71
  129. package/lib/zudoku.plugin-api-catalog.js.map +1 -1
  130. package/lib/zudoku.plugin-api-keys.js +16 -15
  131. package/lib/zudoku.plugin-api-keys.js.map +1 -1
  132. package/lib/zudoku.plugin-custom-pages.js +2 -2
  133. package/lib/zudoku.plugin-markdown.js +1 -1
  134. package/lib/zudoku.plugin-openapi.js +3 -3
  135. package/lib/zudoku.plugin-redirect.js +1 -1
  136. package/lib/zudoku.plugin-search-pagefind.js +274 -0
  137. package/lib/zudoku.plugin-search-pagefind.js.map +1 -0
  138. package/package.json +8 -3
  139. package/src/lib/authentication/hook.ts +12 -1
  140. package/src/lib/authentication/providers/clerk.tsx +10 -6
  141. package/src/lib/authentication/state.ts +8 -2
  142. package/src/lib/components/Banner.tsx +1 -0
  143. package/src/lib/components/Heading.tsx +1 -1
  144. package/src/lib/components/Layout.tsx +1 -0
  145. package/src/lib/core/RouteGuard.tsx +44 -18
  146. package/src/lib/plugins/api-catalog/Catalog.tsx +23 -7
  147. package/src/lib/plugins/api-catalog/index.tsx +1 -0
  148. package/src/lib/plugins/markdown/MdxPage.tsx +5 -1
  149. package/src/lib/plugins/openapi/OperationList.tsx +15 -3
  150. package/src/lib/plugins/openapi/OperationListItem.tsx +8 -0
  151. package/src/lib/plugins/openapi/ParameterList.tsx +4 -0
  152. package/src/lib/plugins/openapi/PlaygroundDialogWrapper.tsx +7 -0
  153. package/src/lib/plugins/openapi/Sidecar.tsx +2 -1
  154. package/src/lib/plugins/openapi/playground/PathParams.tsx +8 -2
  155. package/src/lib/plugins/openapi/playground/Playground.tsx +61 -5
  156. package/src/lib/plugins/search-pagefind/PagefindSearch.tsx +135 -0
  157. package/src/lib/plugins/search-pagefind/ResultList.tsx +104 -0
  158. package/src/lib/plugins/search-pagefind/get-results.tsx +54 -0
  159. package/src/lib/plugins/search-pagefind/index.tsx +21 -0
  160. package/src/lib/plugins/search-pagefind/types.ts +118 -0
  161. package/src/lib/ui/Command.tsx +25 -3
  162. package/src/lib/util/useScrollToAnchor.ts +8 -8
  163. package/README.md +0 -121
  164. package/lib/MdxPage-DM9mE-G-.js +0 -193
  165. package/lib/MdxPage-DM9mE-G-.js.map +0 -1
  166. package/lib/OperationList-DWnNbwVg.js.map +0 -1
  167. package/lib/hook-4_6pQSo4.js +0 -1460
  168. package/lib/hook-4_6pQSo4.js.map +0 -1
  169. package/lib/index-DVBlM15k.js.map +0 -1
  170. package/lib/index-Du5aNddU.js +0 -509
  171. package/lib/index-Du5aNddU.js.map +0 -1
  172. package/lib/index.esm-CQHE3GEU.js +0 -691
  173. package/lib/index.esm-CQHE3GEU.js.map +0 -1
  174. package/lib/objectEntries-yMIkr2mI.js +0 -5
  175. package/lib/objectEntries-yMIkr2mI.js.map +0 -1
  176. package/lib/useScrollToAnchor-BW8y_cwU.js +0 -290
  177. package/lib/useScrollToAnchor-BW8y_cwU.js.map +0 -1
@@ -0,0 +1,135 @@
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 = (basePath?: string): 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(/* @vite-ignore */ joinUrl(basePath, "/pagefind/pagefind.js"));
33
+
34
+ const usePagefind = (options: PagefindOptions) => {
35
+ const { data: pagefind, ...result } = useQuery<Pagefind>({
36
+ queryKey: ["pagefind", options.ranking],
37
+ retry: false,
38
+ queryFn: async () => {
39
+ const pagefind = await importPagefind(options.basePath);
40
+ await pagefind.init();
41
+ await pagefind.options({
42
+ ranking: {
43
+ termFrequency:
44
+ options.ranking?.termFrequency ?? DEFAULT_RANKING.termFrequency,
45
+ pageLength: options.ranking?.pageLength ?? DEFAULT_RANKING.pageLength,
46
+ termSimilarity:
47
+ options.ranking?.termSimilarity ?? DEFAULT_RANKING.termSimilarity,
48
+ termSaturation:
49
+ options.ranking?.termSaturation ?? DEFAULT_RANKING.termSaturation,
50
+ },
51
+ });
52
+
53
+ return pagefind;
54
+ },
55
+ enabled: typeof window !== "undefined",
56
+ });
57
+
58
+ if (result.isError) {
59
+ // eslint-disable-next-line no-console
60
+ console.error(result.error);
61
+ }
62
+
63
+ return { ...result, pagefind };
64
+ };
65
+
66
+ export const PagefindSearch = ({
67
+ isOpen,
68
+ onClose,
69
+ options,
70
+ }: {
71
+ isOpen: boolean;
72
+ onClose: () => void;
73
+ options: PagefindOptions;
74
+ }) => {
75
+ const { pagefind, error, isError } = usePagefind(options);
76
+ const [searchTerm, setSearchTerm] = useState("");
77
+
78
+ const { data: searchResults } = useQuery({
79
+ queryKey: ["pagefind-search", searchTerm],
80
+ queryFn: async () => {
81
+ const search = await pagefind?.search(searchTerm);
82
+ if (!search) return [];
83
+ return getResults(search, options);
84
+ },
85
+ placeholderData: keepPreviousData,
86
+ enabled: !!pagefind && !!searchTerm,
87
+ });
88
+
89
+ return (
90
+ <CommandDialog
91
+ command={{ shouldFilter: false }}
92
+ content={{ className: "max-w-[750px]" }}
93
+ open={isOpen}
94
+ onOpenChange={onClose}
95
+ >
96
+ <VisuallyHidden>
97
+ <DialogTitle>Search</DialogTitle>
98
+ </VisuallyHidden>
99
+ <CommandInput
100
+ placeholder="Search..."
101
+ value={searchTerm}
102
+ onValueChange={setSearchTerm}
103
+ disabled={isError}
104
+ />
105
+ <CommandEmpty>
106
+ {searchTerm ? "No results found." : "Start typing to search"}
107
+ </CommandEmpty>
108
+ {isError ? (
109
+ <div className="p-4 text-sm">
110
+ {error.message === "NOT_BUILT_YET" ? (
111
+ <Callout type="info">
112
+ Search is currently not available in development mode by default.
113
+ <br />
114
+ To still use search in development, run <code>
115
+ zudoku build
116
+ </code>{" "}
117
+ and copy the <code>dist/pagefind</code> directory to your{" "}
118
+ <code>public</code> directory.
119
+ </Callout>
120
+ ) : (
121
+ "An error occurred while loading search."
122
+ )}
123
+ </div>
124
+ ) : (
125
+ <ResultList
126
+ basePath={options.basePath}
127
+ searchResults={searchResults ?? []}
128
+ searchTerm={searchTerm}
129
+ onClose={onClose}
130
+ maxSubResults={options.maxSubResults}
131
+ />
132
+ )}
133
+ </CommandDialog>
134
+ );
135
+ };
@@ -0,0 +1,104 @@
1
+ import { FileTextIcon } from "lucide-react";
2
+ import { useCallback } from "react";
3
+ import { Link, useNavigate } from "react-router";
4
+ import { CommandGroup, CommandItem, CommandList } from "zudoku/ui/Command.js";
5
+ import {
6
+ type PagefindSearchFragment,
7
+ type PagefindSubResult,
8
+ } from "./types.js";
9
+
10
+ const sortSubResults = (a: PagefindSubResult, b: PagefindSubResult) => {
11
+ const aScore = a.weighted_locations.reduce(
12
+ (sum, loc) => sum + loc.balanced_score,
13
+ 0,
14
+ );
15
+ const bScore = b.weighted_locations.reduce(
16
+ (sum, loc) => sum + loc.balanced_score,
17
+ 0,
18
+ );
19
+ return bScore - aScore;
20
+ };
21
+
22
+ const hoverClassname = `cursor-pointer border border-transparent data-[selected=true]:border-border`;
23
+
24
+ export const ResultList = ({
25
+ basePath,
26
+ searchResults,
27
+ searchTerm,
28
+ onClose,
29
+ maxSubResults = 4,
30
+ }: {
31
+ basePath?: string;
32
+ searchResults: PagefindSearchFragment[];
33
+ searchTerm: string;
34
+ onClose: () => void;
35
+ maxSubResults?: number;
36
+ }) => {
37
+ const navigate = useNavigate();
38
+
39
+ const cleanResultUrl = useCallback(
40
+ (url: string) => {
41
+ const clean = url.replace(".html", "");
42
+ return basePath && clean.startsWith(basePath)
43
+ ? clean.slice(basePath.length)
44
+ : clean;
45
+ },
46
+ [basePath],
47
+ );
48
+
49
+ return (
50
+ <CommandList className="max-h-[450px]">
51
+ {searchTerm && searchResults.length > 0 && (
52
+ <CommandGroup
53
+ className="text-sm text-muted-foreground"
54
+ heading={`${searchResults.length} results for "${searchTerm}"`}
55
+ />
56
+ )}
57
+ {searchResults.map((result) => (
58
+ <CommandGroup
59
+ key={[result.meta.title ?? result.excerpt, result.url].join("-")}
60
+ >
61
+ <CommandItem
62
+ asChild
63
+ value={`${result.meta.title}-${result.url}`}
64
+ className={hoverClassname}
65
+ onSelect={() => {
66
+ void navigate(cleanResultUrl(result.url));
67
+ onClose();
68
+ }}
69
+ >
70
+ <Link to={cleanResultUrl(result.url)}>
71
+ <FileTextIcon size={20} className="text-muted-foreground" />
72
+ {result.meta.title}
73
+ </Link>
74
+ </CommandItem>
75
+ {result.sub_results
76
+ .sort(sortSubResults)
77
+ .slice(0, maxSubResults)
78
+ .map((subResult) => (
79
+ <CommandItem
80
+ asChild
81
+ key={`${result.meta.title}-${subResult.url}`}
82
+ value={`${result.meta.title}-${subResult.url}`}
83
+ className={hoverClassname}
84
+ onSelect={() => {
85
+ void navigate(cleanResultUrl(subResult.url));
86
+ onClose();
87
+ }}
88
+ >
89
+ <Link to={cleanResultUrl(subResult.url)} onClick={onClose}>
90
+ <div className="flex flex-col items-start gap-2 ms-2.5 ps-5 border-l border-muted-foreground/50">
91
+ <span className="font-bold">{subResult.title}</span>
92
+ <span
93
+ className="text-[13px] [&_mark]:bg-primary [&_mark]:text-primary-foreground"
94
+ dangerouslySetInnerHTML={{ __html: subResult.excerpt }}
95
+ />
96
+ </div>
97
+ </Link>
98
+ </CommandItem>
99
+ ))}
100
+ </CommandGroup>
101
+ ))}
102
+ </CommandList>
103
+ );
104
+ };
@@ -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
+ > & { basePath?: string };
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>
@@ -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
 
package/README.md DELETED
@@ -1,121 +0,0 @@
1
- <div align=center>
2
-
3
- <a href="https://zudoku.dev" alt="Zudoku">
4
- <img src="./assets/github-hero.png" width=630 alt="Zudoku Docs & Developer Portal">
5
- </a>
6
-
7
- [![MIT License](https://img.shields.io/badge/license-mit-green?style=flat&labelColor=000000)](https://github.com/zuplo/zudoku/license.md) [![Zudoku Version](https://img.shields.io/npm/v/zudoku.svg?style=flat&labelColor=000000)](https://www.npmjs.com/package/zudoku) [![Create Zudoku App Version](https://img.shields.io/npm/v/create-zudoku-app?label=cli&style=flat&labelColor=000000)](https://www.npmjs.com/package/create-zudoku-app) [![Made with love by Zuplo](https://img.shields.io/badge/Made%20by%20Zuplo-FF00BD.svg?style=flat&logo=data:image/svg%2bxml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGZpbGw9Im5vbmUiIHZpZXdCb3g9IjAgMCAzNyAzMiIgYXJpYS1oaWRkZW49InRydWUiPgogIDxwYXRoIGZpbGw9IiNGRjAwQkQiIGQ9Ik0yNy4xNDIgMTkuOTc4SDE2LjYyTDI3LjgzIDguNzQ2YS43NTguNzU4IDAgMDAtLjUzNC0xLjI5M0g5LjQ4OFYwaDE5LjUzNGE3LjU3MyA3LjU3MyAwIDAxNC4wNjUgMS4xMjUgNy41OTEgNy41OTEgMCAwMTIuODM2IDMuMTI2IDcuNDAyIDcuNDAyIDAgMDEtMS40NjEgOC4zOThsLTcuMzIgNy4zMjh6Ii8+CiAgPHBhdGggZmlsbD0iI0ZGMDBCRCIgZD0iTTkuNDg5IDExLjA0MmgxMC41MjRsLTExLjE5IDExLjIxYS43NzIuNzcyIDAgMDAuNTQzIDEuMzE2aDE3Ljc1OXY3LjQ1Mkg3LjYxYTcuNTc0IDcuNTc0IDAgMDEtNC4wNjUtMS4xMjVBNy41OTMgNy41OTMgMCAwMS43MSAyNi43NjhhNy40MDMgNy40MDMgMCAwMTEuNDYyLTguMzk3bDcuMzE4LTcuMzI5eiIvPgo8L3N2Zz4K&labelColor=000)](https://zuplo.com)
8
-
9
- </div>
10
-
11
- # Zudoku
12
-
13
- API documentation should always be free.
14
-
15
- <a href="#-installation"><strong>Installation</strong></a> · <a href="https://zudoku.dev/docs/app-quickstart"><strong>Docs</strong></a> · <a href="#-examples"><strong>Examples</strong></a> · <a href="#-contributing--community"><strong>Contributing</strong></a> · <a href="#-motivation"><strong>Motivation</strong></a>
16
-
17
- ## Introduction
18
-
19
- **Zudoku** (pronounced "zoo-doh-koo") is an open-source, highly customizable API documentation framework for building quality developer experiences around OpenAPI and, soon, GraphQL documents.
20
-
21
- Because great API documentation frameworks should be:
22
-
23
- 🌍 Free & Open Source<br /> ✅ OpenAPI powered<br /> 🔩 Extensible with Plugins<br /> ⚡ Easy to setup & blazing fast to work with<br /> 🔧 Easy to maintain
24
-
25
- ## 🤩 Try it, right now!
26
-
27
- You can test Zudoku with your own OpenAPI document at [zudoku.dev](https://zudoku.dev?utm_source=github&utm_medium=web&utm_content=link&ref=github) and see how good your documentation can look!
28
-
29
- ## ✨ Features
30
-
31
- - 🚀 Generate documentation from a single or multiple [OpenAPI](https://swagger.io/specification/) schema
32
- - 📄 Create [MDX pages](https://mdxjs.com/) for anything you want to document
33
- - 🔐 Integrate your users with authentication via OpenID or OAuth2
34
- - 🧪 Let users test their API calls using the Integrated Playground (includes authentication!)
35
- - 🌑 Dark mode (of course!)
36
-
37
- Zudoku is quick to implement, easy to configure and is highly composable with sensible defaults.
38
-
39
- ## ⚙️ Installation
40
-
41
- You can use the CLI to generate a new project or use it standalone via CDN as a React component.
42
-
43
- ### ⚡️ Quick start
44
-
45
- Fire up your new API docs using the command line generator:
46
-
47
- ```
48
- npm create zudoku-app@latest
49
- ```
50
-
51
- ### 📦 Standalone via CDN
52
-
53
- Add the package and styles to your `<head>` and pass the URL for your API schema to the `data-api-url` property, as shown here:
54
-
55
- ```html
56
- <!doctype html>
57
- <html>
58
- <head>
59
- <title>Zudoku Demo</title>
60
- <meta charset="UTF-8" />
61
- <meta name="viewport" content="width=device-width, initial-scale=1.0" />
62
-
63
- <link rel="icon" type="image/svg+xml" href="https://cdn.zudoku.dev/logos/favicon.svg" />
64
- <script type="module" src="https://cdn.zudoku.dev/latest/main.js" crossorigin></script>
65
- <link rel="stylesheet" href="https://cdn.zudoku.dev/latest/style.css" crossorigin />
66
- </head>
67
- <body>
68
- <div data-api-url="https://api.example.com/openapi.json"></div>
69
- </body>
70
- </html>
71
- ```
72
-
73
- ### 🧱 Getting started templates
74
-
75
- To get started with some of the features Zudoku offers you can use one of these [example templates](https://github.com/zuplo/zudoku/tree/main/examples):
76
-
77
- | Template | What it does |
78
- | --------------------------------------------------------------------------------------- | ----------------------------------------------------- |
79
- | [many-apis](https://github.com/zuplo/zudoku/tree/main/examples/many-apis) | Using more than one OpenAPI document with Zudoku |
80
- | [with-auth0](https://github.com/zuplo/zudoku/tree/main/examples/with-auth0) | Authenticate users in docs with the Auth0 plugin |
81
- | [with-config](https://github.com/zuplo/zudoku/tree/main/examples/with-config) | Barebones config, ready for you to setup how you like |
82
- | [with-vite-config](https://github.com/zuplo/zudoku/tree/main/examples/with-vite-config) | Use Zudoku with your Vite config (Advanced) |
83
-
84
- ## 🎓 Examples
85
-
86
- - [Rick & Morty API](https://zudoku.dev/demo?api-url=https://rickandmorty.zuplo.io/openapi.json)
87
- - [Pet Store API](https://zudoku.dev/demo?api-url=https://zudoku.dev/petstore.oas.json)
88
- - [Zuplo API Documentation](https://docs-zudoku.pages.dev/)
89
-
90
- ### Zudoku use cases
91
-
92
- Zudoku is a flexible and highly customizable framework that can be used to create many things, including:
93
-
94
- - Standalone documentation websites
95
- - OpenAPI powered API references
96
- - A developer portal with documentation, fully functional API reference for testing and authentication support for your user accounts.
97
- - Internal documentation
98
-
99
- ## 🔧 Contributing & Community
100
-
101
- For details on how to contribute to Zudoku, see the [contributing guide](CONTRIBUTING.md).
102
-
103
- ## Changelog
104
-
105
- Details of the latest updates to Zudoku can be found in the [changelog](CHANGELOG.md).
106
-
107
- ## 🎯 Motivation
108
-
109
- At Zuplo, we couldn’t find an open-source solution that met our high standards for both trustworthiness and programmability, so we decided to create our own. And since no one chooses Zuplo solely because of our documentation, we felt great about open-sourcing this tool and making it easy for anyone to self-host.
110
-
111
- We hope that if you use it, you’ll think fondly of Zuplo, and one day, when you’re looking for a gateway or API management product, you’ll consider us as a vendor to evaluate.
112
-
113
- Zudoku will always be open-source. It will always be free.
114
-
115
- ## License
116
-
117
- Zudoku is licensed under MIT. See the full [license](LICENSE.md).
118
-
119
- <a href="https://twitter.com/zuplo">
120
- <img alt="X (formerly Twitter) Follow" src="https://img.shields.io/twitter/follow/zuplo">
121
- </a>