zudoku 0.39.0 → 0.39.2

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 (146) hide show
  1. package/dist/app/entry.server.d.ts +5 -4
  2. package/dist/app/entry.server.js +2 -2
  3. package/dist/app/entry.server.js.map +1 -1
  4. package/dist/cli/cmds/dev.js +1 -7
  5. package/dist/cli/cmds/dev.js.map +1 -1
  6. package/dist/config/validators/common.d.ts +17 -0
  7. package/dist/config/validators/common.js +1 -0
  8. package/dist/config/validators/common.js.map +1 -1
  9. package/dist/config/validators/validate.d.ts +7 -0
  10. package/dist/lib/components/Bootstrap.d.ts +2 -1
  11. package/dist/lib/components/Bootstrap.js +3 -2
  12. package/dist/lib/components/Bootstrap.js.map +1 -1
  13. package/dist/lib/components/Header.js +2 -2
  14. package/dist/lib/components/Header.js.map +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/Main.js +1 -1
  18. package/dist/lib/components/Main.js.map +1 -1
  19. package/dist/lib/components/MobileTopNavigation.js +6 -3
  20. package/dist/lib/components/MobileTopNavigation.js.map +1 -1
  21. package/dist/lib/components/TopNavigation.d.ts +1 -0
  22. package/dist/lib/components/TopNavigation.js +2 -2
  23. package/dist/lib/components/TopNavigation.js.map +1 -1
  24. package/dist/lib/components/Zudoku.js +4 -1
  25. package/dist/lib/components/Zudoku.js.map +1 -1
  26. package/dist/lib/components/context/BypassProtectedRoutesContext.d.ts +1 -0
  27. package/dist/lib/components/context/BypassProtectedRoutesContext.js +3 -0
  28. package/dist/lib/components/context/BypassProtectedRoutesContext.js.map +1 -0
  29. package/dist/lib/components/index.d.ts +2 -1
  30. package/dist/lib/components/navigation/PoweredByZudoku.d.ts +3 -0
  31. package/dist/lib/components/navigation/PoweredByZudoku.js +6 -0
  32. package/dist/lib/components/navigation/PoweredByZudoku.js.map +1 -0
  33. package/dist/lib/components/navigation/SidebarWrapper.js +9 -2
  34. package/dist/lib/components/navigation/SidebarWrapper.js.map +1 -1
  35. package/dist/lib/components/navigation/ZudokuLogo.d.ts +6 -0
  36. package/dist/lib/components/navigation/ZudokuLogo.js +5 -0
  37. package/dist/lib/components/navigation/ZudokuLogo.js.map +1 -0
  38. package/dist/lib/core/RouteGuard.d.ts +1 -0
  39. package/dist/lib/core/RouteGuard.js +9 -3
  40. package/dist/lib/core/RouteGuard.js.map +1 -1
  41. package/dist/lib/core/ZudokuContext.d.ts +1 -0
  42. package/dist/lib/core/ZudokuContext.js.map +1 -1
  43. package/dist/lib/plugins/openapi/schema/LogicalGroup/LogicalGroupConnector.d.ts +2 -1
  44. package/dist/lib/plugins/openapi/schema/LogicalGroup/LogicalGroupConnector.js +2 -2
  45. package/dist/lib/plugins/openapi/schema/LogicalGroup/LogicalGroupConnector.js.map +1 -1
  46. package/dist/lib/plugins/openapi/schema/LogicalGroup/LogicalGroupItem.js +1 -1
  47. package/dist/lib/plugins/openapi/schema/LogicalGroup/LogicalGroupItem.js.map +1 -1
  48. package/dist/lib/plugins/openapi/schema/SchemaView.js +1 -1
  49. package/dist/lib/plugins/openapi/schema/SchemaView.js.map +1 -1
  50. package/dist/lib/plugins/search-pagefind/PagefindSearch.js +9 -4
  51. package/dist/lib/plugins/search-pagefind/PagefindSearch.js.map +1 -1
  52. package/dist/lib/plugins/search-pagefind/ResultList.d.ts +1 -1
  53. package/dist/lib/plugins/search-pagefind/ResultList.js +6 -12
  54. package/dist/lib/plugins/search-pagefind/ResultList.js.map +1 -1
  55. package/dist/lib/plugins/search-pagefind/index.d.ts +1 -3
  56. package/dist/vite/build.js +1 -9
  57. package/dist/vite/build.js.map +1 -1
  58. package/dist/vite/plugin-search.js +1 -1
  59. package/dist/vite/plugin-search.js.map +1 -1
  60. package/dist/vite/prerender/FileWritingResponse.d.ts +9 -5
  61. package/dist/vite/prerender/FileWritingResponse.js +5 -5
  62. package/dist/vite/prerender/FileWritingResponse.js.map +1 -1
  63. package/dist/vite/prerender/InMemoryResponse.d.ts +16 -0
  64. package/dist/vite/prerender/InMemoryResponse.js +32 -0
  65. package/dist/vite/prerender/InMemoryResponse.js.map +1 -0
  66. package/dist/vite/prerender/PrerenderResponse.d.ts +10 -0
  67. package/dist/vite/prerender/PrerenderResponse.js +2 -0
  68. package/dist/vite/prerender/PrerenderResponse.js.map +1 -0
  69. package/dist/vite/prerender/prerender.d.ts +1 -0
  70. package/dist/vite/prerender/prerender.js +18 -0
  71. package/dist/vite/prerender/prerender.js.map +1 -1
  72. package/dist/vite/prerender/worker.js +36 -8
  73. package/dist/vite/prerender/worker.js.map +1 -1
  74. package/dist/zuplo/with-zuplo.js +4 -0
  75. package/dist/zuplo/with-zuplo.js.map +1 -1
  76. package/lib/{Callout-D5frCCJ0.js → Callout-B2vsR09t.js} +2 -2
  77. package/lib/{Callout-D5frCCJ0.js.map → Callout-B2vsR09t.js.map} +1 -1
  78. package/lib/{Dialog-Dv6WG8RN.js → Dialog-sbgekbjb.js} +5 -5
  79. package/lib/{Dialog-Dv6WG8RN.js.map → Dialog-sbgekbjb.js.map} +1 -1
  80. package/lib/{MdxPage-ZW1StNhp.js → MdxPage-JscVnWM8.js} +22 -21
  81. package/lib/{MdxPage-ZW1StNhp.js.map → MdxPage-JscVnWM8.js.map} +1 -1
  82. package/lib/{OasProvider-Cld9RAMQ.js → OasProvider-C4T5TU8Z.js} +2 -2
  83. package/lib/{OasProvider-Cld9RAMQ.js.map → OasProvider-C4T5TU8Z.js.map} +1 -1
  84. package/lib/{OperationList-D-OfzJm6.js → OperationList-C-gBHUou.js} +49 -48
  85. package/lib/{OperationList-D-OfzJm6.js.map → OperationList-C-gBHUou.js.map} +1 -1
  86. package/lib/{Pagination-CYB3nVYx.js → Pagination-DCCvGq0m.js} +2 -2
  87. package/lib/{Pagination-CYB3nVYx.js.map → Pagination-DCCvGq0m.js.map} +1 -1
  88. package/lib/RouteGuard-CqZPoZYJ.js +744 -0
  89. package/lib/RouteGuard-CqZPoZYJ.js.map +1 -0
  90. package/lib/{SchemaList-Ci1WxRh0.js → SchemaList-DuN6ThXR.js} +3 -3
  91. package/lib/{SchemaList-Ci1WxRh0.js.map → SchemaList-DuN6ThXR.js.map} +1 -1
  92. package/lib/{SchemaView-Brn-YxHY.js → SchemaView-B3fOPR4V.js} +112 -100
  93. package/lib/SchemaView-B3fOPR4V.js.map +1 -0
  94. package/lib/{createServer-mMau3eV_.js → createServer-mYvGvmc0.js} +2469 -2443
  95. package/lib/createServer-mYvGvmc0.js.map +1 -0
  96. package/lib/index-D6ktNq4i.js +1863 -0
  97. package/lib/index-D6ktNq4i.js.map +1 -0
  98. package/lib/{index-CjPMxpOV.js → index-DsdAaiwx.js} +13 -12
  99. package/lib/{index-CjPMxpOV.js.map → index-DsdAaiwx.js.map} +1 -1
  100. package/lib/objectEntries-yMIkr2mI.js +5 -0
  101. package/lib/objectEntries-yMIkr2mI.js.map +1 -0
  102. package/lib/ui/Command.js +1 -1
  103. package/lib/useLatest-hmRS46UF.js +11 -0
  104. package/lib/useLatest-hmRS46UF.js.map +1 -0
  105. package/lib/zudoku.components.js +15 -14
  106. package/lib/zudoku.components.js.map +1 -1
  107. package/lib/zudoku.plugin-api-catalog.js +6 -5
  108. package/lib/zudoku.plugin-api-catalog.js.map +1 -1
  109. package/lib/zudoku.plugin-markdown.js +1 -1
  110. package/lib/zudoku.plugin-openapi.js +1 -1
  111. package/lib/zudoku.plugin-search-pagefind.js +132 -137
  112. package/lib/zudoku.plugin-search-pagefind.js.map +1 -1
  113. package/package.json +3 -3
  114. package/src/app/entry.server.tsx +6 -3
  115. package/src/lib/components/Bootstrap.tsx +13 -6
  116. package/src/lib/components/Header.tsx +2 -2
  117. package/src/lib/components/Layout.tsx +1 -1
  118. package/src/lib/components/Main.tsx +3 -2
  119. package/src/lib/components/MobileTopNavigation.tsx +27 -18
  120. package/src/lib/components/TopNavigation.tsx +2 -2
  121. package/src/lib/components/Zudoku.tsx +5 -3
  122. package/src/lib/components/context/BypassProtectedRoutesContext.ts +3 -0
  123. package/src/lib/components/navigation/PoweredByZudoku.tsx +23 -0
  124. package/src/lib/components/navigation/SidebarWrapper.tsx +27 -13
  125. package/src/lib/components/navigation/ZudokuLogo.tsx +25 -0
  126. package/src/lib/core/RouteGuard.tsx +26 -4
  127. package/src/lib/core/ZudokuContext.ts +1 -0
  128. package/src/lib/plugins/openapi/schema/LogicalGroup/LogicalGroupConnector.tsx +3 -0
  129. package/src/lib/plugins/openapi/schema/LogicalGroup/LogicalGroupItem.tsx +5 -1
  130. package/src/lib/plugins/openapi/schema/SchemaView.tsx +6 -1
  131. package/src/lib/plugins/search-pagefind/PagefindSearch.tsx +11 -4
  132. package/src/lib/plugins/search-pagefind/ResultList.tsx +5 -16
  133. package/src/lib/plugins/search-pagefind/index.tsx +1 -1
  134. package/dist/cli/dev/pagefind-command.d.ts +0 -3
  135. package/dist/cli/dev/pagefind-command.js +0 -59
  136. package/dist/cli/dev/pagefind-command.js.map +0 -1
  137. package/dist/lib/components/context/PluginSystem.d.ts +0 -1
  138. package/dist/lib/components/context/PluginSystem.js +0 -2
  139. package/dist/lib/components/context/PluginSystem.js.map +0 -1
  140. package/lib/SchemaView-Brn-YxHY.js.map +0 -1
  141. package/lib/createServer-mMau3eV_.js.map +0 -1
  142. package/lib/index-Bt7MKhZq.js +0 -2514
  143. package/lib/index-Bt7MKhZq.js.map +0 -1
  144. package/lib/objectEntries-BS7aAgOm.js +0 -12
  145. package/lib/objectEntries-BS7aAgOm.js.map +0 -1
  146. package/src/lib/components/context/PluginSystem.ts +0 -0
@@ -1 +1 @@
1
- {"version":3,"file":"zudoku.plugin-search-pagefind.js","sources":["../src/lib/plugins/search-pagefind/get-results.tsx","../src/lib/plugins/search-pagefind/ResultList.tsx","../src/lib/plugins/search-pagefind/PagefindSearch.tsx","../src/lib/plugins/search-pagefind/index.tsx"],"sourcesContent":["import type { AuthState } from \"../../authentication/state.js\";\nimport type { ZudokuContext } from \"../../core/ZudokuContext.js\";\nimport type { PagefindOptions } from \"./index.js\";\nimport type { PagefindSearchFragment, PagefindSearchResults } from \"./types.js\";\n\nexport const getResults = async ({\n search,\n options,\n auth,\n context,\n}: {\n search: PagefindSearchResults;\n options: PagefindOptions;\n auth: AuthState;\n context: ZudokuContext;\n}) => {\n const maxResults = options.maxResults ?? 10;\n const transformFn = options.transformResults ?? (() => true);\n\n const transformedResults: PagefindSearchFragment[] = [];\n\n const generator = searchResultGenerator({\n search,\n transformFn,\n auth,\n context,\n });\n\n for await (const result of generator) {\n transformedResults.push(result);\n if (transformedResults.length >= maxResults) break;\n }\n\n return transformedResults;\n};\n\nasync function* searchResultGenerator({\n search,\n transformFn,\n auth,\n context,\n}: {\n search: PagefindSearchResults;\n transformFn: NonNullable<PagefindOptions[\"transformResults\"]>;\n auth: AuthState<unknown>;\n context: ZudokuContext;\n}) {\n const batchSize = 5;\n let processedCount = 0;\n\n while (processedCount < search.results.length) {\n const batch = search.results.slice(\n processedCount,\n processedCount + batchSize,\n );\n processedCount += batch.length;\n\n const batchData = await Promise.all(batch.map((result) => result.data()));\n\n for (const result of batchData) {\n const transformed = transformFn({ result, auth, context });\n\n if (transformed === false) {\n // Skip this result\n continue;\n } else if (transformed === true || transformed == null) {\n // Keep the original result\n yield result;\n } else {\n // Return the transformed result\n yield transformed;\n }\n }\n }\n}\n","import { BracketsIcon, FileTextIcon } from \"lucide-react\";\nimport { useCallback, useLayoutEffect, useRef } from \"react\";\nimport { Link, useNavigate } from \"react-router\";\nimport { CommandGroup, CommandItem, CommandList } from \"zudoku/ui/Command.js\";\nimport {\n type PagefindSearchFragment,\n type PagefindSubResult,\n} from \"./types.js\";\n\nconst sortSubResults = (a: PagefindSubResult, b: PagefindSubResult) => {\n const aScore = a.weighted_locations.reduce(\n (sum, loc) => sum + loc.balanced_score,\n 0,\n );\n const bScore = b.weighted_locations.reduce(\n (sum, loc) => sum + loc.balanced_score,\n 0,\n );\n return bScore - aScore;\n};\n\nconst hoverClassname = `cursor-pointer border border-transparent data-[selected=true]:border-border`;\n\nexport const ResultList = ({\n basePath,\n searchResults,\n searchTerm,\n onClose,\n maxSubResults = 4,\n}: {\n basePath?: string;\n searchResults: PagefindSearchFragment[];\n searchTerm: string;\n onClose: () => void;\n maxSubResults?: number;\n}) => {\n const navigate = useNavigate();\n const commandListRef = useRef<HTMLDivElement | null>(null);\n\n const cleanResultUrl = useCallback(\n (url: string) => {\n const clean = url.replace(\".html\", \"\");\n return basePath && clean.startsWith(basePath)\n ? clean.slice(basePath.length)\n : clean;\n },\n [basePath],\n );\n\n useLayoutEffect(() => {\n requestIdleCallback(() => {\n commandListRef.current?.scrollTo({ top: 0 });\n });\n }, [searchTerm]);\n\n return (\n <CommandList className=\"max-h-[450px]\" ref={commandListRef}>\n {searchTerm && searchResults.length > 0 && (\n <CommandGroup\n className=\"text-sm text-muted-foreground\"\n heading={`${searchResults.length} results for \"${searchTerm}\"`}\n />\n )}\n {searchTerm &&\n searchResults.map((result) => (\n <CommandGroup\n key={[result.meta.title ?? result.excerpt, result.url].join(\"-\")}\n >\n <CommandItem\n asChild\n value={`${result.meta.title}-${result.url}`}\n className={hoverClassname}\n onSelect={() => {\n void navigate(cleanResultUrl(result.url));\n onClose();\n }}\n >\n <Link to={cleanResultUrl(result.url)}>\n {result.meta.section === \"openapi\" ? (\n <BracketsIcon />\n ) : (\n <FileTextIcon />\n )}\n {result.meta.title}\n </Link>\n </CommandItem>\n {result.sub_results\n .sort(sortSubResults)\n .slice(0, maxSubResults)\n .map((subResult) => (\n <CommandItem\n asChild\n key={`sub-${result.meta.title}-${subResult.url}`}\n value={`sub-${result.meta.title}-${subResult.url}`}\n className={hoverClassname}\n onSelect={() => {\n void navigate(cleanResultUrl(subResult.url));\n onClose();\n }}\n >\n <Link to={cleanResultUrl(subResult.url)} onClick={onClose}>\n <div className=\"flex flex-col items-start gap-2 ms-2.5 ps-5 border-l border-muted-foreground/50\">\n <span className=\"font-bold\">{subResult.title}</span>\n <span\n className=\"text-[13px] [&_mark]:bg-primary [&_mark]:text-primary-foreground\"\n dangerouslySetInnerHTML={{ __html: subResult.excerpt }}\n />\n </div>\n </Link>\n </CommandItem>\n ))}\n </CommandGroup>\n ))}\n </CommandList>\n );\n};\n","import { VisuallyHidden } from \"@radix-ui/react-visually-hidden\";\nimport { keepPreviousData, useQuery } from \"@tanstack/react-query\";\nimport { useRef, useState } from \"react\";\nimport { Button } from \"zudoku/ui/Button.js\";\nimport { Callout } from \"zudoku/ui/Callout.js\";\nimport {\n CommandDialog,\n CommandEmpty,\n CommandInput,\n} from \"zudoku/ui/Command.js\";\nimport { DialogTitle } from \"zudoku/ui/Dialog.js\";\nimport { useAuthState } from \"../../authentication/state.js\";\nimport { useZudoku } from \"../../components/context/ZudokuContext.js\";\nimport { joinUrl } from \"../../util/joinUrl.js\";\nimport { getResults } from \"./get-results.js\";\nimport type { PagefindOptions } from \"./index.js\";\nimport { ResultList } from \"./ResultList.js\";\nimport type { Pagefind } from \"./types.js\";\n\nconst DEFAULT_RANKING = {\n // Slightly lower than default because API docs tend to have repetitive terms (parameter names, HTTP methods, etc.)\n termFrequency: 0.8,\n // Lower than default because API documentation pages tend to be longer due to comprehensive endpoint documentation\n pageLength: 0.6,\n // Slightly higher than default because in technical documentation, exact matches should be prioritized\n termSimilarity: 1.2,\n // Slightly lower than default because API docs might have legitimate repetition of terms\n termSaturation: 1.2,\n};\n\nconst importPagefind = (basePath?: string): Promise<Pagefind> =>\n import.meta.env.DEV\n ? // @ts-expect-error TypeScript can't resolve the import\n import(/* @vite-ignore */ \"/pagefind/pagefind.js\")\n : import(/* @vite-ignore */ joinUrl(basePath, \"/pagefind/pagefind.js\"));\n\nconst usePagefind = (options: PagefindOptions) => {\n const { data: pagefind, ...result } = useQuery<Pagefind>({\n queryKey: [\"pagefind\", options.ranking],\n retry: false,\n queryFn: async () => {\n const pagefind = await importPagefind(options.basePath);\n await pagefind.init();\n await pagefind.options({\n ranking: {\n termFrequency:\n options.ranking?.termFrequency ?? DEFAULT_RANKING.termFrequency,\n pageLength: options.ranking?.pageLength ?? DEFAULT_RANKING.pageLength,\n termSimilarity:\n options.ranking?.termSimilarity ?? DEFAULT_RANKING.termSimilarity,\n termSaturation:\n options.ranking?.termSaturation ?? DEFAULT_RANKING.termSaturation,\n },\n });\n\n return pagefind;\n },\n enabled: typeof window !== \"undefined\",\n });\n\n if (result.isError && result.error.message !== \"NOT_BUILT_YET\") {\n // eslint-disable-next-line no-console\n console.error(result.error);\n }\n\n return { ...result, pagefind };\n};\n\nexport const PagefindSearch = ({\n isOpen,\n onClose,\n options,\n}: {\n isOpen: boolean;\n onClose: () => void;\n options: PagefindOptions;\n}) => {\n const { pagefind, error, isError } = usePagefind(options);\n const [searchTerm, setSearchTerm] = useState(\"\");\n const auth = useAuthState();\n const context = useZudoku();\n const inputRef = useRef<HTMLInputElement>(null);\n\n const { data: searchResults } = useQuery({\n queryKey: [\"pagefind-search\", searchTerm],\n queryFn: async () => {\n const search = await pagefind?.search(searchTerm);\n if (!search) return [];\n return getResults({ search, options, auth, context });\n },\n placeholderData: keepPreviousData,\n enabled: !!pagefind && !!searchTerm,\n });\n\n return (\n <CommandDialog\n command={{ shouldFilter: false }}\n content={{ className: \"max-w-[750px]\" }}\n open={isOpen}\n onOpenChange={onClose}\n >\n <VisuallyHidden>\n <DialogTitle>Search</DialogTitle>\n </VisuallyHidden>\n <CommandInput\n ref={inputRef}\n placeholder=\"Search...\"\n value={searchTerm}\n onValueChange={setSearchTerm}\n disabled={isError}\n />\n <CommandEmpty>\n {searchTerm ? (\n <div className=\"flex flex-col items-center\">\n No results found.\n <Button\n variant=\"link\"\n onClick={() => {\n setSearchTerm(\"\");\n inputRef.current?.focus();\n }}\n >\n Clear search\n </Button>\n </div>\n ) : (\n \"Start typing to search\"\n )}\n </CommandEmpty>\n {isError ? (\n <div className=\"p-4 text-sm\">\n {error.message === \"NOT_BUILT_YET\" ? (\n <Callout type=\"info\">\n Search is currently not available in development mode by default.\n <br />\n To still use search in development, run <code>\n zudoku build\n </code>{\" \"}\n and copy the <code>dist/pagefind</code> directory to your{\" \"}\n <code>public</code> directory.\n </Callout>\n ) : (\n \"An error occurred while loading search.\"\n )}\n </div>\n ) : (\n <ResultList\n basePath={options.basePath}\n searchResults={searchResults ?? []}\n searchTerm={searchTerm}\n onClose={onClose}\n maxSubResults={options.maxSubResults}\n />\n )}\n </CommandDialog>\n );\n};\n","import type { ZudokuConfig } from \"../../../config/validators/validate.js\";\nimport { ClientOnly } from \"../../components/ClientOnly.js\";\nimport type { ZudokuPlugin } from \"../../core/plugins.js\";\nimport { PagefindSearch } from \"./PagefindSearch.js\";\n\nexport type PagefindOptions = Extract<\n ZudokuConfig[\"search\"],\n { type: \"pagefind\" }\n> & { basePath?: string };\n\nexport const pagefindSearchPlugin = (\n options: PagefindOptions,\n): ZudokuPlugin => {\n return {\n renderSearch: ({ isOpen, onClose }) => (\n <ClientOnly>\n <PagefindSearch isOpen={isOpen} onClose={onClose} options={options} />\n </ClientOnly>\n ),\n };\n};\n"],"names":["getResults","search","options","auth","context","maxResults","transformFn","transformedResults","generator","searchResultGenerator","result","processedCount","batch","batchData","transformed","sortSubResults","a","b","aScore","sum","loc","hoverClassname","ResultList","basePath","searchResults","searchTerm","onClose","maxSubResults","navigate","useNavigate","commandListRef","useRef","cleanResultUrl","useCallback","url","clean","useLayoutEffect","_a","jsxs","CommandList","jsx","CommandGroup","CommandItem","Link","BracketsIcon","FileTextIcon","subResult","DEFAULT_RANKING","importPagefind","joinUrl","usePagefind","pagefind","useQuery","_b","_c","_d","PagefindSearch","isOpen","error","isError","setSearchTerm","useState","useAuthState","useZudoku","inputRef","keepPreviousData","CommandDialog","VisuallyHidden","DialogTitle","CommandInput","CommandEmpty","Button","Callout","pagefindSearchPlugin","ClientOnly"],"mappings":"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"}
1
+ {"version":3,"file":"zudoku.plugin-search-pagefind.js","sources":["../src/lib/plugins/search-pagefind/get-results.tsx","../src/lib/plugins/search-pagefind/ResultList.tsx","../src/lib/plugins/search-pagefind/PagefindSearch.tsx","../src/lib/plugins/search-pagefind/index.tsx"],"sourcesContent":["import type { AuthState } from \"../../authentication/state.js\";\nimport type { ZudokuContext } from \"../../core/ZudokuContext.js\";\nimport type { PagefindOptions } from \"./index.js\";\nimport type { PagefindSearchFragment, PagefindSearchResults } from \"./types.js\";\n\nexport const getResults = async ({\n search,\n options,\n auth,\n context,\n}: {\n search: PagefindSearchResults;\n options: PagefindOptions;\n auth: AuthState;\n context: ZudokuContext;\n}) => {\n const maxResults = options.maxResults ?? 10;\n const transformFn = options.transformResults ?? (() => true);\n\n const transformedResults: PagefindSearchFragment[] = [];\n\n const generator = searchResultGenerator({\n search,\n transformFn,\n auth,\n context,\n });\n\n for await (const result of generator) {\n transformedResults.push(result);\n if (transformedResults.length >= maxResults) break;\n }\n\n return transformedResults;\n};\n\nasync function* searchResultGenerator({\n search,\n transformFn,\n auth,\n context,\n}: {\n search: PagefindSearchResults;\n transformFn: NonNullable<PagefindOptions[\"transformResults\"]>;\n auth: AuthState<unknown>;\n context: ZudokuContext;\n}) {\n const batchSize = 5;\n let processedCount = 0;\n\n while (processedCount < search.results.length) {\n const batch = search.results.slice(\n processedCount,\n processedCount + batchSize,\n );\n processedCount += batch.length;\n\n const batchData = await Promise.all(batch.map((result) => result.data()));\n\n for (const result of batchData) {\n const transformed = transformFn({ result, auth, context });\n\n if (transformed === false) {\n // Skip this result\n continue;\n } else if (transformed === true || transformed == null) {\n // Keep the original result\n yield result;\n } else {\n // Return the transformed result\n yield transformed;\n }\n }\n }\n}\n","import { BracketsIcon, FileTextIcon } from \"lucide-react\";\nimport { useLayoutEffect, useRef } from \"react\";\nimport { Link, useNavigate } from \"react-router\";\nimport { CommandGroup, CommandItem, CommandList } from \"zudoku/ui/Command.js\";\nimport {\n type PagefindSearchFragment,\n type PagefindSubResult,\n} from \"./types.js\";\n\nconst sortSubResults = (a: PagefindSubResult, b: PagefindSubResult) => {\n const aScore = a.weighted_locations.reduce(\n (sum, loc) => sum + loc.balanced_score,\n 0,\n );\n const bScore = b.weighted_locations.reduce(\n (sum, loc) => sum + loc.balanced_score,\n 0,\n );\n return bScore - aScore;\n};\n\nconst hoverClassname = `cursor-pointer border border-transparent data-[selected=true]:border-border`;\n\nexport const ResultList = ({\n searchResults,\n searchTerm,\n onClose,\n maxSubResults = 4,\n}: {\n basePath?: string;\n searchResults: PagefindSearchFragment[];\n searchTerm: string;\n onClose: () => void;\n maxSubResults?: number;\n}) => {\n const navigate = useNavigate();\n const commandListRef = useRef<HTMLDivElement | null>(null);\n\n useLayoutEffect(() => {\n requestIdleCallback(() => {\n commandListRef.current?.scrollTo({ top: 0 });\n });\n }, [searchTerm]);\n\n return (\n <CommandList className=\"max-h-[450px]\" ref={commandListRef}>\n {searchTerm && searchResults.length > 0 && (\n <CommandGroup\n className=\"text-sm text-muted-foreground\"\n heading={`${searchResults.length} results for \"${searchTerm}\"`}\n />\n )}\n {searchTerm &&\n searchResults.map((result) => (\n <CommandGroup\n key={[result.meta.title ?? result.excerpt, result.url].join(\"-\")}\n >\n <CommandItem\n asChild\n value={`${result.meta.title}-${result.url}`}\n className={hoverClassname}\n onSelect={() => {\n void navigate(result.url);\n onClose();\n }}\n >\n <Link to={result.url}>\n {result.meta.section === \"openapi\" ? (\n <BracketsIcon />\n ) : (\n <FileTextIcon />\n )}\n {result.meta.title}\n </Link>\n </CommandItem>\n {result.sub_results\n .sort(sortSubResults)\n .slice(0, maxSubResults)\n .map((subResult) => (\n <CommandItem\n asChild\n key={`sub-${result.meta.title}-${subResult.url}`}\n value={`sub-${result.meta.title}-${subResult.url}`}\n className={hoverClassname}\n onSelect={() => {\n void navigate(subResult.url);\n onClose();\n }}\n >\n <Link to={subResult.url} onClick={onClose}>\n <div className=\"flex flex-col items-start gap-2 ms-2.5 ps-5 border-l border-muted-foreground/50\">\n <span className=\"font-bold\">{subResult.title}</span>\n <span\n className=\"text-[13px] [&_mark]:bg-primary [&_mark]:text-primary-foreground\"\n dangerouslySetInnerHTML={{ __html: subResult.excerpt }}\n />\n </div>\n </Link>\n </CommandItem>\n ))}\n </CommandGroup>\n ))}\n </CommandList>\n );\n};\n","import { VisuallyHidden } from \"@radix-ui/react-visually-hidden\";\nimport { keepPreviousData, useQuery } from \"@tanstack/react-query\";\nimport { useRef, useState } from \"react\";\nimport { Button } from \"zudoku/ui/Button.js\";\nimport { Callout } from \"zudoku/ui/Callout.js\";\nimport {\n CommandDialog,\n CommandEmpty,\n CommandInput,\n} from \"zudoku/ui/Command.js\";\nimport { DialogTitle } from \"zudoku/ui/Dialog.js\";\nimport { useAuthState } from \"../../authentication/state.js\";\nimport { useZudoku } from \"../../components/context/ZudokuContext.js\";\nimport { SEARCH_PROTECTED_SECTION } from \"../../core/RouteGuard.js\";\nimport { joinUrl } from \"../../util/joinUrl.js\";\nimport { getResults } from \"./get-results.js\";\nimport type { PagefindOptions } from \"./index.js\";\nimport { ResultList } from \"./ResultList.js\";\nimport type { Pagefind } from \"./types.js\";\n\nconst DEFAULT_RANKING = {\n // Slightly lower than default because API docs tend to have repetitive terms (parameter names, HTTP methods, etc.)\n termFrequency: 0.8,\n // Lower than default because API documentation pages tend to be longer due to comprehensive endpoint documentation\n pageLength: 0.6,\n // Slightly higher than default because in technical documentation, exact matches should be prioritized\n termSimilarity: 1.2,\n // Slightly lower than default because API docs might have legitimate repetition of terms\n termSaturation: 1.2,\n};\n\nconst importPagefind = (basePath?: string): Promise<Pagefind> =>\n import.meta.env.DEV\n ? // @ts-expect-error TypeScript can't resolve the import\n import(/* @vite-ignore */ \"/pagefind/pagefind.js\")\n : import(/* @vite-ignore */ joinUrl(basePath, \"/pagefind/pagefind.js\"));\n\nconst usePagefind = (options: PagefindOptions) => {\n const {\n options: { basePath },\n } = useZudoku();\n const { data: pagefind, ...result } = useQuery<Pagefind>({\n queryKey: [\"pagefind\", options.ranking],\n retry: false,\n queryFn: async () => {\n const pagefind = await importPagefind(basePath);\n await pagefind.init();\n await pagefind.options({\n ranking: {\n termFrequency:\n options.ranking?.termFrequency ?? DEFAULT_RANKING.termFrequency,\n pageLength: options.ranking?.pageLength ?? DEFAULT_RANKING.pageLength,\n termSimilarity:\n options.ranking?.termSimilarity ?? DEFAULT_RANKING.termSimilarity,\n termSaturation:\n options.ranking?.termSaturation ?? DEFAULT_RANKING.termSaturation,\n },\n });\n\n return pagefind;\n },\n enabled: typeof window !== \"undefined\",\n });\n\n if (result.isError && result.error.message !== \"NOT_BUILT_YET\") {\n // eslint-disable-next-line no-console\n console.error(result.error);\n }\n\n return { ...result, pagefind };\n};\n\nexport const PagefindSearch = ({\n isOpen,\n onClose,\n options,\n}: {\n isOpen: boolean;\n onClose: () => void;\n options: PagefindOptions;\n}) => {\n const { pagefind, error, isError } = usePagefind(options);\n const [searchTerm, setSearchTerm] = useState(\"\");\n const auth = useAuthState();\n const context = useZudoku();\n const inputRef = useRef<HTMLInputElement>(null);\n\n const { data: searchResults } = useQuery({\n queryKey: [\"pagefind-search\", searchTerm, auth.isAuthenticated],\n queryFn: async () => {\n const filters = auth.isAuthenticated\n ? undefined\n : { not: { section: SEARCH_PROTECTED_SECTION } };\n\n const search = await pagefind?.search(searchTerm, { filters });\n if (!search) return [];\n return getResults({ search, options, auth, context });\n },\n placeholderData: keepPreviousData,\n enabled: !!pagefind && !!searchTerm,\n });\n\n return (\n <CommandDialog\n command={{ shouldFilter: false }}\n content={{ className: \"max-w-[750px]\" }}\n open={isOpen}\n onOpenChange={onClose}\n >\n <VisuallyHidden>\n <DialogTitle>Search</DialogTitle>\n </VisuallyHidden>\n <CommandInput\n ref={inputRef}\n placeholder=\"Search...\"\n value={searchTerm}\n onValueChange={setSearchTerm}\n disabled={isError}\n />\n <CommandEmpty>\n {searchTerm ? (\n <div className=\"flex flex-col items-center\">\n No results found.\n <Button\n variant=\"link\"\n onClick={() => {\n setSearchTerm(\"\");\n inputRef.current?.focus();\n }}\n >\n Clear search\n </Button>\n </div>\n ) : (\n \"Start typing to search\"\n )}\n </CommandEmpty>\n {isError ? (\n <div className=\"p-4 text-sm\">\n {error.message === \"NOT_BUILT_YET\" ? (\n <Callout type=\"info\">\n Search is currently not available in development mode by default.\n <br />\n To still use search in development, run <code>\n zudoku build\n </code>{\" \"}\n and copy the <code>dist/pagefind</code> directory to your{\" \"}\n <code>public</code> directory.\n </Callout>\n ) : (\n \"An error occurred while loading search.\"\n )}\n </div>\n ) : (\n <ResultList\n searchResults={searchResults ?? []}\n searchTerm={searchTerm}\n onClose={onClose}\n maxSubResults={options.maxSubResults}\n />\n )}\n </CommandDialog>\n );\n};\n","import type { ZudokuConfig } from \"../../../config/validators/validate.js\";\nimport { ClientOnly } from \"../../components/ClientOnly.js\";\nimport type { ZudokuPlugin } from \"../../core/plugins.js\";\nimport { PagefindSearch } from \"./PagefindSearch.js\";\n\nexport type PagefindOptions = Extract<\n ZudokuConfig[\"search\"],\n { type: \"pagefind\" }\n>;\n\nexport const pagefindSearchPlugin = (\n options: PagefindOptions,\n): ZudokuPlugin => {\n return {\n renderSearch: ({ isOpen, onClose }) => (\n <ClientOnly>\n <PagefindSearch isOpen={isOpen} onClose={onClose} options={options} />\n </ClientOnly>\n ),\n };\n};\n"],"names":["getResults","search","options","auth","context","maxResults","transformFn","transformedResults","generator","searchResultGenerator","result","processedCount","batch","batchData","transformed","sortSubResults","a","b","aScore","sum","loc","hoverClassname","ResultList","searchResults","searchTerm","onClose","maxSubResults","navigate","useNavigate","commandListRef","useRef","useLayoutEffect","_a","jsxs","CommandList","jsx","CommandGroup","CommandItem","Link","BracketsIcon","FileTextIcon","subResult","DEFAULT_RANKING","importPagefind","basePath","joinUrl","usePagefind","useZudoku","pagefind","useQuery","_b","_c","_d","PagefindSearch","isOpen","error","isError","setSearchTerm","useState","useAuthState","inputRef","filters","SEARCH_PROTECTED_SECTION","keepPreviousData","CommandDialog","VisuallyHidden","DialogTitle","CommandInput","CommandEmpty","Button","Callout","pagefindSearchPlugin","ClientOnly"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "zudoku",
3
- "version": "0.39.0",
3
+ "version": "0.39.2",
4
4
  "type": "module",
5
5
  "homepage": "https://zudoku.dev",
6
6
  "repository": {
@@ -200,7 +200,7 @@
200
200
  "openapi-types": "12.1.3",
201
201
  "pagefind": "1.4.0-alpha.1",
202
202
  "picocolors": "1.1.1",
203
- "piscina": "5.0.0-alpha.1",
203
+ "piscina": "5.0.0-alpha.2",
204
204
  "postcss": "8.5.3",
205
205
  "posthog-node": "4.8.1",
206
206
  "prism-react-renderer": "2.4.1",
@@ -243,7 +243,7 @@
243
243
  },
244
244
  "devDependencies": {
245
245
  "@graphql-codegen/cli": "5.0.5",
246
- "@graphql-codegen/client-preset": "4.7.0",
246
+ "@graphql-codegen/client-preset": "4.8.0",
247
247
  "@testing-library/react": "16.2.0",
248
248
  "@types/estree": "1.0.7",
249
249
  "@types/express": "5.0.0",
@@ -14,7 +14,7 @@ import "virtual:zudoku-theme.css";
14
14
  import "vite/modulepreload-polyfill";
15
15
  import { BootstrapStatic, ServerError } from "zudoku/components";
16
16
  import { NO_DEHYDRATE } from "../lib/components/cache.js";
17
- import type { FileWritingResponse } from "../vite/prerender/FileWritingResponse.js";
17
+ import type { PrerenderResponse } from "../vite/prerender/PrerenderResponse.js";
18
18
  import "./main.css";
19
19
  import { getRoutesByConfig } from "./main.js";
20
20
  export { getRoutesByConfig };
@@ -25,12 +25,14 @@ export const render = async ({
25
25
  response,
26
26
  routes,
27
27
  basePath,
28
+ bypassProtection,
28
29
  }: {
29
30
  template: string;
30
31
  request: express.Request | Request;
31
- response: express.Response | FileWritingResponse;
32
+ response: express.Response | PrerenderResponse;
32
33
  routes: RouteObject[];
33
34
  basePath?: string;
35
+ bypassProtection?: boolean;
34
36
  }) => {
35
37
  const { query, dataRoutes } = createStaticHandler(routes, {
36
38
  basename: basePath,
@@ -73,6 +75,7 @@ export const render = async ({
73
75
  context={context}
74
76
  queryClient={queryClient}
75
77
  helmetContext={helmetContext}
78
+ bypassProtection={bypassProtection}
76
79
  />
77
80
  );
78
81
 
@@ -143,7 +146,7 @@ export const render = async ({
143
146
 
144
147
  export function createFetchRequest(
145
148
  req: express.Request,
146
- res: express.Response | FileWritingResponse,
149
+ res: express.Response | PrerenderResponse,
147
150
  ): Request {
148
151
  const origin = `${req.protocol}://${req.get("host")}`;
149
152
  // Note: This had to take originalUrl into account for presumably vite's proxying
@@ -13,6 +13,7 @@ import {
13
13
  } from "react-router";
14
14
  import { RouterProvider } from "react-router/dom";
15
15
  import { StaggeredRenderContext } from "../plugins/openapi/StaggeredRender.js";
16
+ import { BypassProtectedRoutesContext } from "./context/BypassProtectedRoutesContext.js";
16
17
 
17
18
  const queryClient = new QueryClient({
18
19
  defaultOptions: {
@@ -32,11 +33,13 @@ const Bootstrap = ({
32
33
  <StrictMode>
33
34
  <QueryClientProvider client={queryClient}>
34
35
  <HydrationBoundary state={hydrate ? (window as any).DATA : undefined}>
35
- <HelmetProvider>
36
- <StaggeredRenderContext.Provider value={{ stagger: !hydrate }}>
37
- <RouterProvider router={router} />
38
- </StaggeredRenderContext.Provider>
39
- </HelmetProvider>
36
+ <BypassProtectedRoutesContext value={false}>
37
+ <HelmetProvider>
38
+ <StaggeredRenderContext.Provider value={{ stagger: !hydrate }}>
39
+ <RouterProvider router={router} />
40
+ </StaggeredRenderContext.Provider>
41
+ </HelmetProvider>
42
+ </BypassProtectedRoutesContext>
40
43
  </HydrationBoundary>
41
44
  </QueryClientProvider>
42
45
  </StrictMode>
@@ -47,16 +50,20 @@ const BootstrapStatic = ({
47
50
  context,
48
51
  queryClient,
49
52
  helmetContext,
53
+ bypassProtection = false,
50
54
  }: {
51
55
  helmetContext: HelmetData["context"];
52
56
  context: StaticHandlerContext;
53
57
  queryClient: QueryClient;
54
58
  router: ReturnType<typeof createStaticRouter>;
59
+ bypassProtection?: boolean;
55
60
  }) => (
56
61
  <StrictMode>
57
62
  <QueryClientProvider client={queryClient}>
58
63
  <HelmetProvider context={helmetContext}>
59
- <StaticRouterProvider router={router} context={context} />
64
+ <BypassProtectedRoutesContext value={bypassProtection}>
65
+ <StaticRouterProvider router={router} context={context} />
66
+ </BypassProtectedRoutesContext>
60
67
  </HelmetProvider>
61
68
  </QueryClientProvider>
62
69
  </StrictMode>
@@ -69,7 +69,7 @@ export const Header = memo(function HeaderInner() {
69
69
  <header className="sticky lg:top-0 z-10 bg-background/80 backdrop-blur w-full">
70
70
  <Banner />
71
71
  <div className="border-b">
72
- <div className="max-w-screen-2xl 2xl:border-x mx-auto flex relative items-center justify-between px-4 lg:px-8 h-[--top-header-height]">
72
+ <div className="max-w-screen-2xl mx-auto flex relative items-center justify-between px-4 lg:px-8 h-[--top-header-height] border-transparent">
73
73
  <div className="flex">
74
74
  <Link to="/">
75
75
  <div className="flex items-center gap-3.5">
@@ -182,7 +182,7 @@ export const Header = memo(function HeaderInner() {
182
182
  </div>
183
183
  </div>
184
184
  <div className="border-b hidden lg:block">
185
- <div className="max-w-screen-2xl mx-auto 2xl:border-x">
185
+ <div className="max-w-screen-2xl mx-auto border-transparent">
186
186
  <Slotlet name="top-navigation-before" />
187
187
  <TopNavigation />
188
188
  <Slotlet name="top-navigation-after" />
@@ -53,7 +53,7 @@ export const Layout = ({ children }: { children?: ReactNode }) => {
53
53
  <Header />
54
54
  <Slotlet name="layout-after-head" />
55
55
 
56
- <div className="grid grid-cols-1 grid-rows-[min-content_1fr] lg:grid-cols-[var(--side-nav-width)_1fr] max-w-screen-2xl w-full lg:mx-auto px-4 lg:px-8 2xl:border-x">
56
+ <div className="grid grid-cols-1 grid-rows-[0_min-content_1fr] lg:grid-rows-[min-content_1fr] lg:grid-cols-[var(--side-nav-width)_1fr] max-w-screen-2xl w-full lg:mx-auto">
57
57
  <Suspense fallback={<LoadingFallback />}>
58
58
  <Main>{children ?? <Outlet />}</Main>
59
59
  </Suspense>
@@ -27,7 +27,7 @@ export const Main = ({ children }: PropsWithChildren) => {
27
27
  )}
28
28
  {hasSidebar && (
29
29
  <div className="lg:hidden -mx-4 px-4 py-2 sticky bg-background/80 backdrop-blur z-10 top-0 left-0 right-0 border-b">
30
- <DrawerTrigger className="flex items-center gap-2">
30
+ <DrawerTrigger className="flex items-center gap-2 px-4">
31
31
  <PanelLeftIcon size={16} strokeWidth={1.5} />
32
32
  <span className="text-sm">Menu</span>
33
33
  </DrawerTrigger>
@@ -36,7 +36,8 @@ export const Main = ({ children }: PropsWithChildren) => {
36
36
  <main
37
37
  data-pagefind-body
38
38
  className={cn(
39
- hasSidebar ? "lg:pl-12" : "col-span-full",
39
+ "px-4 lg:pe-8 lg:px-8",
40
+ !hasSidebar && "col-span-full",
40
41
  isNavigating && "animate-pulse",
41
42
  )}
42
43
  >
@@ -9,12 +9,13 @@ import {
9
9
  DrawerTrigger,
10
10
  } from "../ui/Drawer.js";
11
11
  import { useZudoku } from "./context/ZudokuContext.js";
12
+ import { PoweredByZudoku } from "./navigation/PoweredByZudoku.js";
12
13
  import { Search } from "./Search.js";
13
14
  import { ThemeSwitch } from "./ThemeSwitch.js";
14
- import { isHiddenItem, TopNavItem } from "./TopNavigation.js";
15
+ import { isHiddenItem, PageProgress, TopNavItem } from "./TopNavigation.js";
15
16
 
16
17
  export const MobileTopNavigation = () => {
17
- const { topNavigation } = useZudoku();
18
+ const { topNavigation, options } = useZudoku();
18
19
  const { isAuthenticated } = useAuth();
19
20
  const [drawerOpen, setDrawerOpen] = useState(false);
20
21
 
@@ -28,28 +29,36 @@ export const MobileTopNavigation = () => {
28
29
  <DrawerTrigger className="lg:hidden">
29
30
  <MenuIcon size={22} />
30
31
  </DrawerTrigger>
32
+ <PageProgress />
31
33
  </div>
32
34
  <DrawerContent
33
35
  className="lg:hidden h-[100dvh] right-0 left-auto w-[320px] rounded-none"
34
36
  aria-describedby={undefined}
35
37
  >
36
- <div className="p-4 overflow-y-auto overscroll-none">
37
- <VisuallyHidden>
38
- <DrawerTitle>Navigation</DrawerTitle>
39
- </VisuallyHidden>
40
- <Search className="flex p-4" />
41
- <ul className="flex flex-col items-center gap-4 p-4">
42
- <li>
43
- <ThemeSwitch />
44
- </li>
45
- {topNavigation.filter(isHiddenItem(isAuthenticated)).map((item) => (
46
- <li key={item.label}>
47
- <button type="button" onClick={() => setDrawerOpen(false)}>
48
- <TopNavItem {...item} />
49
- </button>
38
+ <div className="p-4 overflow-y-auto overscroll-none h-full flex flex-col justify-between">
39
+ <div>
40
+ <VisuallyHidden>
41
+ <DrawerTitle>Navigation</DrawerTitle>
42
+ </VisuallyHidden>
43
+ <Search className="flex p-4" />
44
+ <ul className="flex flex-col items-center gap-4 p-4">
45
+ <li>
46
+ <ThemeSwitch />
50
47
  </li>
51
- ))}
52
- </ul>
48
+ {topNavigation
49
+ .filter(isHiddenItem(isAuthenticated))
50
+ .map((item) => (
51
+ <li key={item.label}>
52
+ <button type="button" onClick={() => setDrawerOpen(false)}>
53
+ <TopNavItem {...item} />
54
+ </button>
55
+ </li>
56
+ ))}
57
+ </ul>
58
+ </div>
59
+ {options.page?.showPoweredBy !== false && (
60
+ <PoweredByZudoku className="flex-grow-0 justify-center gap-1" />
61
+ )}
53
62
  </div>
54
63
  </DrawerContent>
55
64
  </Drawer>
@@ -21,7 +21,7 @@ export const isHiddenItem =
21
21
  );
22
22
  };
23
23
 
24
- const Progress = () => {
24
+ export const PageProgress = () => {
25
25
  const navigation = useNavigation();
26
26
  const isNavigating = navigation.state === "loading";
27
27
  // delay the animation to avoid flickering
@@ -69,8 +69,8 @@ export const TopNavigation = () => {
69
69
  </ul>
70
70
  </nav>
71
71
  <Slotlet name="top-navigation-side" />
72
- <Progress />
73
72
  </div>
73
+ <PageProgress />
74
74
  </Suspense>
75
75
  );
76
76
  };
@@ -30,6 +30,8 @@ import { ViewportAnchorProvider } from "./context/ViewportAnchorContext.js";
30
30
  import { ZudokuProvider } from "./context/ZudokuProvider.js";
31
31
  import { SlotletProvider } from "./SlotletProvider.js";
32
32
 
33
+ let zudokuContext: ZudokuContext | undefined;
34
+
33
35
  const ZudokoInner = memo(
34
36
  ({ children, ...props }: PropsWithChildren<ZudokuContextOptions>) => {
35
37
  const components = useMemo(
@@ -70,9 +72,9 @@ const ZudokoInner = memo(
70
72
  setDidNavigate(true);
71
73
  }, [didNavigate, navigation.location]);
72
74
 
73
- const [zudokuContext] = useState(
74
- () => new ZudokuContext(props, queryClient),
75
- );
75
+ if (!zudokuContext) {
76
+ zudokuContext = new ZudokuContext(props, queryClient);
77
+ }
76
78
 
77
79
  const heads = props.plugins
78
80
  ?.flatMap((plugin) =>
@@ -0,0 +1,3 @@
1
+ import { createContext } from "react";
2
+
3
+ export const BypassProtectedRoutesContext = createContext(false);
@@ -0,0 +1,23 @@
1
+ import { ChevronRightIcon } from "lucide-react";
2
+ import { cn } from "../../util/cn.js";
3
+ import ZudokuLogo from "./ZudokuLogo.js";
4
+
5
+ export const PoweredByZudoku = ({ className }: { className?: string }) => (
6
+ <a
7
+ href="https://zudoku.dev"
8
+ target="_blank"
9
+ rel="noopener noreferrer"
10
+ className={cn(
11
+ "flex justify-between items-center w-full border border-transparent hover:border-border rounded-full hover:shadow-sm h-7 px-3 text-nowrap hover:bg-muted/80 transition-all",
12
+ className,
13
+ )}
14
+ >
15
+ <div className="opacity-70 hover:opacity-100 transition-opacity gap-1.5 text-[11px] font-medium rounded-full h-7 flex items-center text-nowrap">
16
+ <ZudokuLogo className="w-3.5 h-3.5 dark:fill-white" />
17
+ powered by Zudoku
18
+ </div>
19
+ <div className="text-xs font-medium opacity-70 hover:text-foreground transition-colors cursor-pointer">
20
+ <ChevronRightIcon size={12} absoluteStrokeWidth strokeWidth={1.5} />
21
+ </div>
22
+ </a>
23
+ );
@@ -1,5 +1,7 @@
1
1
  import { type PropsWithChildren, type Ref } from "react";
2
2
  import { cn } from "../../util/cn.js";
3
+ import { useZudoku } from "../context/ZudokuContext.js";
4
+ import { PoweredByZudoku } from "./PoweredByZudoku.js";
3
5
 
4
6
  export const SidebarWrapper = ({
5
7
  children,
@@ -8,18 +10,30 @@ export const SidebarWrapper = ({
8
10
  }: PropsWithChildren<{
9
11
  className?: string;
10
12
  ref?: Ref<HTMLDivElement>;
11
- }>) => (
12
- <nav
13
- className={cn(
14
- "hidden lg:flex h-full scrollbar flex-col overflow-y-auto shrink-0 text-sm border-r pr-6",
15
- "sticky top-[--header-height] h-[calc(100vh-var(--header-height))]",
16
- "-mx-[--padding-nav-item] max-w-[--side-nav-width] pb-6 pt-[--padding-content-top] scroll-pt-2 gap-2",
17
- className,
18
- )}
19
- ref={ref}
20
- >
21
- {children}
22
- </nav>
23
- );
13
+ }>) => {
14
+ const { options } = useZudoku();
15
+
16
+ return (
17
+ <div className="grid sticky top-[--header-height] lg:h-[calc(100vh-var(--header-height))] grid-rows-[1fr_min-content] border-r">
18
+ <nav
19
+ className={cn(
20
+ "hidden max-w-[calc(var(--side-nav-width)+var(--padding-nav-item))] lg:flex scrollbar flex-col overflow-y-auto shrink-0 text-sm pe-3 ps-4 lg:ps-8",
21
+ "-mx-[--padding-nav-item] pb-6 pt-[--padding-content-top] scroll-pt-2 gap-1",
22
+ className,
23
+ )}
24
+ style={{
25
+ maskImage: `linear-gradient(180deg, transparent 1%, rgba(0, 0, 0, 1) 20px, rgba(0, 0, 0, 1) 90%, transparent 99%)`,
26
+ }}
27
+ ref={ref}
28
+ >
29
+ {children}
30
+ </nav>
31
+
32
+ <div className="bg-background border-t p-2 mx-5 gap-2 items-center mt-2 drop-shadow-[0_-3px_1px_rgba(0,0,0,0.015)] hidden lg:[&:has(>_:nth-child(1):last-child)]:flex">
33
+ {options.page?.showPoweredBy !== false && <PoweredByZudoku />}
34
+ </div>
35
+ </div>
36
+ );
37
+ };
24
38
 
25
39
  SidebarWrapper.displayName = "SidebarWrapper";
@@ -0,0 +1,25 @@
1
+ import * as React from "react";
2
+
3
+ const ZudokuLogo = (props: React.SVGProps<SVGSVGElement>) => (
4
+ <svg
5
+ xmlns="http://www.w3.org/2000/svg"
6
+ viewBox="0 0 132 100"
7
+ fill="none"
8
+ {...props}
9
+ >
10
+ <path
11
+ fill="currentColor"
12
+ fillRule="evenodd"
13
+ d="M80.092 2.963A4.66 4.66 0 0 1 84.449 0h34.049c6.325 0 10.835 6.135 8.948 12.172L116.653 46.71a4.688 4.688 0 0 1-4.474 3.29H75c-5.178 0-7.813 4.687-9.375 9.374-1.288 3.864-11.07 28.963-14.467 37.662A4.66 4.66 0 0 1 46.801 100H12.75c-6.324 0-10.834-6.134-8.947-12.171l10.793-34.54A4.688 4.688 0 0 1 19.071 50H56.25c5.178 0 7.813-4.687 9.375-9.375 1.288-3.864 11.07-28.962 14.467-37.662Z"
14
+ clipRule="evenodd"
15
+ />
16
+ <path
17
+ fill="currentColor"
18
+ d="M83.54 57.813a7.813 7.813 0 0 0-7.316 5.07L63.888 95.777c-.766 2.043.744 4.222 2.926 4.222h36.828c5.211 0 9.875-3.232 11.704-8.11l8.821-23.522c1.915-5.107-1.861-10.555-7.315-10.555H83.539ZM29.17 0a12.5 12.5 0 0 0-11.704 8.111l-8.82 23.521c-1.915 5.107 1.86 10.556 7.315 10.556h33.312a7.813 7.813 0 0 0 7.316-5.07L68.924 4.223C69.691 2.18 68.18 0 65.998 0H29.17Z"
19
+ />
20
+ </svg>
21
+ );
22
+
23
+ ZudokuLogo.displayName = "ZudokuLogo";
24
+
25
+ export default ZudokuLogo;
@@ -1,4 +1,6 @@
1
1
  import { useQuery } from "@tanstack/react-query";
2
+ import { Helmet } from "@zudoku/react-helmet-async";
3
+ import { use } from "react";
2
4
  import { matchPath, Outlet, useLocation, useNavigate } from "react-router";
3
5
  import {
4
6
  Dialog,
@@ -8,21 +10,28 @@ import {
8
10
  DialogTitle,
9
11
  } from "zudoku/ui/Dialog.js";
10
12
  import { useAuth } from "../authentication/hook.js";
13
+ import { BypassProtectedRoutesContext } from "../components/context/BypassProtectedRoutesContext.js";
11
14
  import { useZudoku } from "../components/context/ZudokuContext.js";
12
15
  import { ZudokuError } from "../util/invariant.js";
13
16
  import { useLatest } from "../util/useLatest.js";
14
17
 
18
+ export const SEARCH_PROTECTED_SECTION = "protected";
19
+
15
20
  export const RouteGuard = () => {
16
21
  const auth = useAuth();
17
22
  const zudoku = useZudoku();
18
23
  const navigate = useNavigate();
19
24
  const location = useLocation();
20
25
  const latestPath = useLatest(location.pathname);
26
+ const shouldBypass = use(BypassProtectedRoutesContext);
27
+
21
28
  const { protectedRoutes = [] } = zudoku.options;
22
29
 
23
- const isProtected = protectedRoutes.some((path) =>
24
- matchPath({ path, end: true }, location.pathname),
25
- );
30
+ const isProtected =
31
+ !shouldBypass &&
32
+ protectedRoutes.some((path) =>
33
+ matchPath({ path, end: true }, location.pathname),
34
+ );
26
35
 
27
36
  useQuery({
28
37
  queryKey: ["login-redirect"],
@@ -70,5 +79,18 @@ export const RouteGuard = () => {
70
79
  });
71
80
  }
72
81
 
73
- return <Outlet />;
82
+ return (
83
+ <>
84
+ {shouldBypass && (
85
+ <Helmet>
86
+ <meta
87
+ name="pagefind"
88
+ data-pagefind-filter={`section:${SEARCH_PROTECTED_SECTION}`}
89
+ content="true"
90
+ />
91
+ </Helmet>
92
+ )}
93
+ <Outlet />
94
+ </>
95
+ );
74
96
  };
@@ -52,6 +52,7 @@ type Metadata = Partial<{
52
52
  }>;
53
53
 
54
54
  type Page = Partial<{
55
+ showPoweredBy: boolean;
55
56
  pageTitle?: string;
56
57
  logo?: {
57
58
  src: {
@@ -24,10 +24,12 @@ export const LogicalGroupConnector = ({
24
24
  type,
25
25
  isOpen,
26
26
  className,
27
+ schemeName,
27
28
  }: {
28
29
  type: LogicalGroupType;
29
30
  isOpen: boolean;
30
31
  className?: string;
32
+ schemeName?: string;
31
33
  }) => {
32
34
  return (
33
35
  <div
@@ -48,6 +50,7 @@ export const LogicalGroupConnector = ({
48
50
  >
49
51
  <ChevronDownIcon size={16} />
50
52
  </div>
53
+ <span className="text-sm text-foreground">{schemeName}</span>
51
54
  </div>
52
55
  </div>
53
56
  );
@@ -18,7 +18,11 @@ export const LogicalGroupItem = (props: {
18
18
  className="group"
19
19
  >
20
20
  <Collapsible.Trigger>
21
- <LogicalGroupConnector type={props.type} isOpen={isOpen} />
21
+ <LogicalGroupConnector
22
+ type={props.type}
23
+ isOpen={isOpen}
24
+ schemeName={props.schema.title}
25
+ />
22
26
  </Collapsible.Trigger>
23
27
  {!isOpen && <div className="wavy-line bg-border translate-y-1" />}
24
28
  <Collapsible.Content>
@@ -57,7 +57,12 @@ export const SchemaView = ({
57
57
  }
58
58
 
59
59
  if (schema.type === "array" && typeof schema.items === "object") {
60
- return renderBasicSchema(schema.items as SchemaObject);
60
+ return (
61
+ <Card className="p-4 space-y-2 text-sm">
62
+ <ParamInfos schema={schema} />
63
+ <SchemaView schema={schema.items as SchemaObject} />
64
+ </Card>
65
+ );
61
66
  }
62
67
 
63
68
  if (schema.type === "object") {