zudoku 0.0.0-fb7d300 → 0.0.0-fc5c03b

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 (267) hide show
  1. package/client.d.ts +7 -0
  2. package/dist/app/entry.client.d.ts +1 -0
  3. package/dist/app/entry.client.js +13 -0
  4. package/dist/app/entry.client.js.map +1 -1
  5. package/dist/app/entry.server.d.ts +1 -0
  6. package/dist/app/entry.server.js +1 -0
  7. package/dist/app/entry.server.js.map +1 -1
  8. package/dist/app/main.js +2 -1
  9. package/dist/app/main.js.map +1 -1
  10. package/dist/app/sentry.d.ts +3 -0
  11. package/dist/app/sentry.js +19 -0
  12. package/dist/app/sentry.js.map +1 -0
  13. package/dist/app/tailwind.d.ts +2 -1
  14. package/dist/app/tailwind.js +64 -52
  15. package/dist/app/tailwind.js.map +1 -1
  16. package/dist/cli/cli.js +0 -2
  17. package/dist/cli/cli.js.map +1 -1
  18. package/dist/cli/common/logger.js +9 -0
  19. package/dist/cli/common/logger.js.map +1 -1
  20. package/dist/cli/common/outdated.js +2 -1
  21. package/dist/cli/common/outdated.js.map +1 -1
  22. package/dist/config/common.d.ts +8 -0
  23. package/dist/config/common.js +2 -0
  24. package/dist/config/common.js.map +1 -0
  25. package/dist/config/config.d.ts +3 -2
  26. package/dist/config/loader.d.ts +20 -0
  27. package/dist/config/loader.js +154 -0
  28. package/dist/config/loader.js.map +1 -0
  29. package/dist/config/validators/InputSidebarSchema.d.ts +14 -13
  30. package/dist/config/validators/InputSidebarSchema.js.map +1 -1
  31. package/dist/config/validators/common.d.ts +4911 -0
  32. package/dist/config/validators/common.js +276 -0
  33. package/dist/config/validators/common.js.map +1 -0
  34. package/dist/config/validators/icon-types.d.ts +1 -0
  35. package/dist/config/validators/icon-types.js +2 -0
  36. package/dist/config/validators/icon-types.js.map +1 -0
  37. package/dist/config/validators/validate.d.ts +777 -521
  38. package/dist/config/validators/validate.js +9 -228
  39. package/dist/config/validators/validate.js.map +1 -1
  40. package/dist/lib/authentication/providers/openid.js +7 -2
  41. package/dist/lib/authentication/providers/openid.js.map +1 -1
  42. package/dist/lib/components/Heading.d.ts +4 -4
  43. package/dist/lib/components/Heading.js +1 -1
  44. package/dist/lib/components/Heading.js.map +1 -1
  45. package/dist/lib/components/Layout.js +3 -2
  46. package/dist/lib/components/Layout.js.map +1 -1
  47. package/dist/lib/components/Markdown.js +1 -1
  48. package/dist/lib/components/Markdown.js.map +1 -1
  49. package/dist/lib/components/MobileTopNavigation.js +1 -1
  50. package/dist/lib/components/MobileTopNavigation.js.map +1 -1
  51. package/dist/lib/components/ReactMarkdown.d.ts +29 -0
  52. package/dist/lib/components/ReactMarkdown.js +182 -0
  53. package/dist/lib/components/ReactMarkdown.js.map +1 -0
  54. package/dist/lib/components/Search.d.ts +3 -1
  55. package/dist/lib/components/Search.js +3 -3
  56. package/dist/lib/components/Search.js.map +1 -1
  57. package/dist/lib/components/SyntaxHighlight.js +0 -4
  58. package/dist/lib/components/SyntaxHighlight.js.map +1 -1
  59. package/dist/lib/components/TopNavigation.d.ts +1 -1
  60. package/dist/lib/components/navigation/Sidebar.d.ts +3 -1
  61. package/dist/lib/components/navigation/Sidebar.js +2 -2
  62. package/dist/lib/components/navigation/Sidebar.js.map +1 -1
  63. package/dist/lib/components/navigation/SidebarCategory.d.ts +2 -2
  64. package/dist/lib/components/navigation/SidebarCategory.js +3 -4
  65. package/dist/lib/components/navigation/SidebarCategory.js.map +1 -1
  66. package/dist/lib/components/navigation/SidebarItem.d.ts +2 -4
  67. package/dist/lib/components/navigation/SidebarItem.js +5 -10
  68. package/dist/lib/components/navigation/SidebarItem.js.map +1 -1
  69. package/dist/lib/components/navigation/SidebarWrapper.js +1 -1
  70. package/dist/lib/components/navigation/SidebarWrapper.js.map +1 -1
  71. package/dist/lib/core/ZudokuContext.d.ts +1 -1
  72. package/dist/lib/oas/graphql/index.js +4 -1
  73. package/dist/lib/oas/graphql/index.js.map +1 -1
  74. package/dist/lib/oas/parser/upgrade/index.d.ts +2 -2
  75. package/dist/lib/oas/parser/upgrade/index.js +3 -20
  76. package/dist/lib/oas/parser/upgrade/index.js.map +1 -1
  77. package/dist/lib/plugins/api-catalog/Catalog.d.ts +6 -0
  78. package/dist/lib/plugins/api-catalog/Catalog.js +29 -0
  79. package/dist/lib/plugins/api-catalog/Catalog.js.map +1 -0
  80. package/dist/lib/plugins/api-catalog/index.d.ts +23 -0
  81. package/dist/lib/plugins/api-catalog/index.js +15 -0
  82. package/dist/lib/plugins/api-catalog/index.js.map +1 -0
  83. package/dist/lib/plugins/markdown/MdxPage.d.ts +9 -1
  84. package/dist/lib/plugins/markdown/MdxPage.js +15 -2
  85. package/dist/lib/plugins/markdown/MdxPage.js.map +1 -1
  86. package/dist/lib/plugins/markdown/index.d.ts +2 -1
  87. package/dist/lib/plugins/markdown/index.js +1 -1
  88. package/dist/lib/plugins/markdown/index.js.map +1 -1
  89. package/dist/lib/plugins/markdown/resolver.js.map +1 -1
  90. package/dist/lib/plugins/openapi/OperationList.js +47 -2
  91. package/dist/lib/plugins/openapi/OperationList.js.map +1 -1
  92. package/dist/lib/plugins/openapi/client/GraphQLClient.js +12 -0
  93. package/dist/lib/plugins/openapi/client/GraphQLClient.js.map +1 -1
  94. package/dist/lib/plugins/openapi/client/useCreateQuery.d.ts +1 -1
  95. package/dist/lib/plugins/openapi/client/useCreateQuery.js +4 -2
  96. package/dist/lib/plugins/openapi/client/useCreateQuery.js.map +1 -1
  97. package/dist/lib/plugins/openapi/graphql/gql.d.ts +1 -1
  98. package/dist/lib/plugins/openapi/graphql/gql.js +1 -1
  99. package/dist/lib/plugins/openapi/graphql/gql.js.map +1 -1
  100. package/dist/lib/plugins/openapi/graphql/graphql.d.ts +2 -0
  101. package/dist/lib/plugins/openapi/graphql/graphql.js +1 -0
  102. package/dist/lib/plugins/openapi/graphql/graphql.js.map +1 -1
  103. package/dist/lib/plugins/openapi/interfaces.d.ts +1 -1
  104. package/dist/lib/plugins/openapi/playground/PathParams.js +1 -1
  105. package/dist/lib/plugins/openapi/playground/PathParams.js.map +1 -1
  106. package/dist/lib/plugins/openapi/post-processors/removeExtensions.d.ts +6 -0
  107. package/dist/lib/plugins/openapi/post-processors/removeExtensions.js +14 -0
  108. package/dist/lib/plugins/openapi/post-processors/removeExtensions.js.map +1 -0
  109. package/dist/lib/plugins/openapi/post-processors/removeExtensions.test.d.ts +1 -0
  110. package/dist/lib/plugins/openapi/post-processors/removeExtensions.test.js +125 -0
  111. package/dist/lib/plugins/openapi/post-processors/removeExtensions.test.js.map +1 -0
  112. package/dist/lib/plugins/openapi/post-processors/removePaths.d.ts +11 -0
  113. package/dist/lib/plugins/openapi/post-processors/removePaths.js +33 -0
  114. package/dist/lib/plugins/openapi/post-processors/removePaths.js.map +1 -0
  115. package/dist/lib/plugins/openapi/post-processors/removePaths.test.d.ts +1 -0
  116. package/dist/lib/plugins/openapi/post-processors/removePaths.test.js +104 -0
  117. package/dist/lib/plugins/openapi/post-processors/removePaths.test.js.map +1 -0
  118. package/dist/lib/plugins/openapi/post-processors/traverse.d.ts +1 -0
  119. package/dist/lib/plugins/openapi/post-processors/traverse.js +2 -0
  120. package/dist/lib/plugins/openapi/post-processors/traverse.js.map +1 -0
  121. package/dist/lib/plugins/redirect/index.d.ts +1 -1
  122. package/dist/lib/ui/Button.d.ts +1 -1
  123. package/dist/lib/ui/Command.d.ts +1 -1
  124. package/dist/lib/util/MdxComponents.d.ts +2 -3
  125. package/dist/lib/util/MdxComponents.js.map +1 -1
  126. package/dist/lib/util/traverse.d.ts +2 -0
  127. package/dist/lib/util/traverse.js +18 -0
  128. package/dist/lib/util/traverse.js.map +1 -0
  129. package/dist/vite/build.js +15 -3
  130. package/dist/vite/build.js.map +1 -1
  131. package/dist/vite/config.d.ts +6 -4
  132. package/dist/vite/config.js +54 -15
  133. package/dist/vite/config.js.map +1 -1
  134. package/dist/vite/config.test.js +7 -5
  135. package/dist/vite/config.test.js.map +1 -1
  136. package/dist/vite/dev-server.js +12 -3
  137. package/dist/vite/dev-server.js.map +1 -1
  138. package/dist/vite/output.d.ts +1 -1
  139. package/dist/vite/output.js +39 -10
  140. package/dist/vite/output.js.map +1 -1
  141. package/dist/vite/plugin-api.js +67 -20
  142. package/dist/vite/plugin-api.js.map +1 -1
  143. package/dist/vite/plugin-component.js +15 -19
  144. package/dist/vite/plugin-component.js.map +1 -1
  145. package/dist/vite/plugin-config-reload.d.ts +1 -2
  146. package/dist/vite/plugin-config-reload.js.map +1 -1
  147. package/dist/vite/plugin-config.js +20 -0
  148. package/dist/vite/plugin-config.js.map +1 -1
  149. package/dist/vite/plugin-docs.test.js +15 -23
  150. package/dist/vite/plugin-docs.test.js.map +1 -1
  151. package/dist/vite/plugin-mdx.js +58 -4
  152. package/dist/vite/plugin-mdx.js.map +1 -1
  153. package/dist/vite/{plugin-custom-css.d.ts → plugin-theme-css.d.ts} +2 -2
  154. package/dist/vite/{plugin-custom-css.js → plugin-theme-css.js} +10 -3
  155. package/dist/vite/plugin-theme-css.js.map +1 -0
  156. package/dist/vite/plugin.d.ts +1 -2
  157. package/dist/vite/plugin.js +2 -2
  158. package/dist/vite/plugin.js.map +1 -1
  159. package/dist/vite/prerender.d.ts +2 -1
  160. package/dist/vite/prerender.js +2 -2
  161. package/dist/vite/prerender.js.map +1 -1
  162. package/dist/vite/sitemap.d.ts +1 -1
  163. package/dist/zuplo/env.d.ts +6 -0
  164. package/dist/zuplo/env.js +9 -0
  165. package/dist/zuplo/env.js.map +1 -0
  166. package/dist/zuplo/with-zuplo.d.ts +3 -0
  167. package/dist/zuplo/with-zuplo.js +28 -0
  168. package/dist/zuplo/with-zuplo.js.map +1 -0
  169. package/lib/AnchorLink-BR0MvI7n.js +35 -0
  170. package/lib/AnchorLink-BR0MvI7n.js.map +1 -0
  171. package/lib/{Markdown-ievDDhFT.js → Markdown-CRsmPPfY.js} +13084 -13096
  172. package/lib/Markdown-CRsmPPfY.js.map +1 -0
  173. package/lib/MdxPage-Wp8QU0-q.js +188 -0
  174. package/lib/MdxPage-Wp8QU0-q.js.map +1 -0
  175. package/lib/{OperationList-BwBl1xrD.js → OperationList-u8xHU9-b.js} +1031 -1016
  176. package/lib/OperationList-u8xHU9-b.js.map +1 -0
  177. package/lib/{SlotletProvider-DyomlzGx.js → SlotletProvider-BgvYIPQe.js} +2 -2
  178. package/lib/{SlotletProvider-DyomlzGx.js.map → SlotletProvider-BgvYIPQe.js.map} +1 -1
  179. package/lib/{SyntaxHighlight-DkLOsjHS.js → SyntaxHighlight-C31iouiO.js} +1 -3
  180. package/lib/{SyntaxHighlight-DkLOsjHS.js.map → SyntaxHighlight-C31iouiO.js.map} +1 -1
  181. package/lib/assets/{worker-CPsGZsve.js → worker-C_2va8B8.js} +8 -9
  182. package/lib/assets/worker-C_2va8B8.js.map +1 -0
  183. package/lib/{createServer-DK-g7kbB.js → createServer-BCAHdrpE.js} +4461 -5248
  184. package/lib/createServer-BCAHdrpE.js.map +1 -0
  185. package/lib/{index-DNxQ_rCt.js → index-CmdLGbbk.js} +53 -47
  186. package/lib/index-CmdLGbbk.js.map +1 -0
  187. package/lib/{AnchorLink-CDlhr8gL.js → index.esm-Bm8pj-bc.js} +223 -254
  188. package/lib/index.esm-Bm8pj-bc.js.map +1 -0
  189. package/lib/object_hash-CvlLgU-M.js +785 -0
  190. package/lib/object_hash-CvlLgU-M.js.map +1 -0
  191. package/lib/post-processors/removeExtensions.js +11 -0
  192. package/lib/post-processors/removeExtensions.js.map +1 -0
  193. package/lib/post-processors/removePaths.js +28 -0
  194. package/lib/post-processors/removePaths.js.map +1 -0
  195. package/lib/post-processors/traverse.js +12 -0
  196. package/lib/post-processors/traverse.js.map +1 -0
  197. package/lib/ui/Drawer.js +79 -79
  198. package/lib/ui/Drawer.js.map +1 -1
  199. package/lib/zudoku.auth-openid.js +42 -37
  200. package/lib/zudoku.auth-openid.js.map +1 -1
  201. package/lib/zudoku.components.js +386 -367
  202. package/lib/zudoku.components.js.map +1 -1
  203. package/lib/zudoku.openapi-worker.js +1 -1
  204. package/lib/zudoku.plugin-api-catalog.js +121 -0
  205. package/lib/zudoku.plugin-api-catalog.js.map +1 -0
  206. package/lib/zudoku.plugin-api-keys.js +1 -1
  207. package/lib/zudoku.plugin-custom-pages.js +1 -1
  208. package/lib/zudoku.plugin-markdown.js +15 -14
  209. package/lib/zudoku.plugin-markdown.js.map +1 -1
  210. package/lib/zudoku.plugin-openapi.js +1 -1
  211. package/lib/zudoku.plugin-redirect.js.map +1 -1
  212. package/package.json +32 -9
  213. package/src/app/entry.client.tsx +15 -0
  214. package/src/app/entry.server.tsx +1 -0
  215. package/src/app/main.tsx +5 -1
  216. package/src/app/sentry.ts +24 -0
  217. package/src/app/tailwind.ts +67 -52
  218. package/src/lib/authentication/providers/openid.tsx +7 -2
  219. package/src/lib/components/Heading.tsx +13 -13
  220. package/src/lib/components/Layout.tsx +8 -3
  221. package/src/lib/components/Markdown.tsx +1 -1
  222. package/src/lib/components/MobileTopNavigation.tsx +18 -18
  223. package/src/lib/components/ReactMarkdown.license.txt +21 -0
  224. package/src/lib/components/ReactMarkdown.tsx +264 -0
  225. package/src/lib/components/Search.tsx +3 -3
  226. package/src/lib/components/SyntaxHighlight.tsx +0 -4
  227. package/src/lib/components/TopNavigation.tsx +1 -1
  228. package/src/lib/components/navigation/Sidebar.tsx +18 -8
  229. package/src/lib/components/navigation/SidebarCategory.tsx +10 -9
  230. package/src/lib/components/navigation/SidebarItem.tsx +10 -13
  231. package/src/lib/components/navigation/SidebarWrapper.tsx +1 -1
  232. package/src/lib/core/ZudokuContext.ts +1 -1
  233. package/src/lib/oas/graphql/index.ts +4 -1
  234. package/src/lib/oas/parser/upgrade/index.ts +4 -27
  235. package/src/lib/plugins/api-catalog/Catalog.tsx +124 -0
  236. package/src/lib/plugins/api-catalog/index.tsx +50 -0
  237. package/src/lib/plugins/markdown/MdxPage.tsx +27 -1
  238. package/src/lib/plugins/markdown/index.tsx +3 -1
  239. package/src/lib/plugins/markdown/resolver.ts +2 -4
  240. package/src/lib/plugins/openapi/OperationList.tsx +64 -3
  241. package/src/lib/plugins/openapi/client/GraphQLClient.tsx +17 -0
  242. package/src/lib/plugins/openapi/client/useCreateQuery.ts +5 -2
  243. package/src/lib/plugins/openapi/graphql/gql.ts +2 -2
  244. package/src/lib/plugins/openapi/graphql/graphql.ts +3 -0
  245. package/src/lib/plugins/openapi/interfaces.ts +1 -1
  246. package/src/lib/plugins/openapi/playground/PathParams.tsx +1 -0
  247. package/src/lib/plugins/openapi/post-processors/removeExtensions.test.ts +144 -0
  248. package/src/lib/plugins/openapi/post-processors/removeExtensions.ts +24 -0
  249. package/src/lib/plugins/openapi/post-processors/removePaths.test.ts +126 -0
  250. package/src/lib/plugins/openapi/post-processors/removePaths.ts +55 -0
  251. package/src/lib/plugins/openapi/post-processors/traverse.ts +1 -0
  252. package/src/lib/plugins/redirect/index.tsx +1 -1
  253. package/src/lib/util/MdxComponents.tsx +2 -5
  254. package/src/lib/util/traverse.ts +25 -0
  255. package/dist/vite/plugin-custom-css.js.map +0 -1
  256. package/lib/AnchorLink-CDlhr8gL.js.map +0 -1
  257. package/lib/Markdown-ievDDhFT.js.map +0 -1
  258. package/lib/MdxPage-Bwn-VSsH.js +0 -174
  259. package/lib/MdxPage-Bwn-VSsH.js.map +0 -1
  260. package/lib/OperationList-BwBl1xrD.js.map +0 -1
  261. package/lib/assets/worker-CPsGZsve.js.map +0 -1
  262. package/lib/createServer-DK-g7kbB.js.map +0 -1
  263. package/lib/index-DNxQ_rCt.js.map +0 -1
  264. package/lib/prism-markup-templating-DZrrEs0A.js +0 -62
  265. package/lib/prism-markup-templating-DZrrEs0A.js.map +0 -1
  266. package/lib/prism-php.min-o7FpoMP_.js +0 -11
  267. package/lib/prism-php.min-o7FpoMP_.js.map +0 -1
@@ -10,10 +10,10 @@ import { useIsCategoryOpen } from "./utils.js";
10
10
 
11
11
  export const SidebarCategory = ({
12
12
  category,
13
- level,
13
+ onRequestClose,
14
14
  }: {
15
15
  category: SidebarItemCategory;
16
- level: number;
16
+ onRequestClose?: () => void;
17
17
  }) => {
18
18
  const isCategoryOpen = useIsCategoryOpen(category);
19
19
  const [hasInteracted, setHasInteracted] = useState(false);
@@ -65,7 +65,6 @@ export const SidebarCategory = ({
65
65
  onClick={() => setHasInteracted(true)}
66
66
  className={navigationListItem({
67
67
  isActive: false,
68
- isTopLevel: level === 0,
69
68
  className: [
70
69
  "text-start",
71
70
  isCollapsible
@@ -77,10 +76,7 @@ export const SidebarCategory = ({
77
76
  {category.icon && (
78
77
  <category.icon
79
78
  size={16}
80
- className={cn(
81
- "align-[-0.125em] -translate-x-1",
82
- isActive && "text-primary",
83
- )}
79
+ className={cn("align-[-0.125em] ", isActive && "text-primary")}
84
80
  />
85
81
  )}
86
82
  {category.link?.type === "doc" ? (
@@ -116,9 +112,14 @@ export const SidebarCategory = ({
116
112
  className={cn(
117
113
  // CollapsibleContent class is used to animate and it should only be applied when the user has triggered the toggle
118
114
  hasInteracted && "CollapsibleContent",
115
+ "ms-6 my-1",
119
116
  )}
120
117
  >
121
- <ul className="mt-1 border-l ms-0.5">
118
+ <ul
119
+ className={
120
+ "relative after:absolute after:-left-[--padding-nav-item] after:translate-x-[1.5px] after:top-0 after:bottom-0 after:w-px after:bg-border"
121
+ }
122
+ >
122
123
  {category.items.map((item) => (
123
124
  <SidebarItem
124
125
  key={
@@ -126,7 +127,7 @@ export const SidebarCategory = ({
126
127
  ("href" in item ? item.href : "") +
127
128
  item.label
128
129
  }
129
- level={level + 1}
130
+ onRequestClose={onRequestClose}
130
131
  item={item}
131
132
  />
132
133
  ))}
@@ -13,10 +13,6 @@ export const navigationListItem = cva(
13
13
  "flex items-center gap-2 px-[--padding-nav-item] py-1.5 rounded-lg hover:bg-accent transition-colors duration-300",
14
14
  {
15
15
  variants: {
16
- isTopLevel: {
17
- true: "font-medium -mx-[--padding-nav-item]",
18
- false: "-mr-[--padding-nav-item] ml-[--padding-nav-item]",
19
- },
20
16
  isActive: {
21
17
  true: "text-primary font-medium",
22
18
  false: "text-foreground/80",
@@ -36,25 +32,25 @@ export const DATA_ANCHOR_ATTR = "data-anchor";
36
32
 
37
33
  export const SidebarItem = ({
38
34
  item,
39
- level = 0,
35
+ onRequestClose,
40
36
  }: {
41
37
  item: SidebarItemType;
42
- basePath?: string;
43
- level?: number;
38
+ onRequestClose?: () => void;
44
39
  }) => {
45
40
  const { activeAnchor } = useViewportAnchor();
46
41
  const [searchParams] = useSearchParams();
47
42
 
48
43
  switch (item.type) {
49
44
  case "category":
50
- return <SidebarCategory category={item} level={level} />;
45
+ return (
46
+ <SidebarCategory category={item} onRequestClose={onRequestClose} />
47
+ );
51
48
  case "doc":
52
49
  return (
53
50
  <NavLink
54
- className={({ isActive }) =>
55
- navigationListItem({ isActive, isTopLevel: level === 0 })
56
- }
51
+ className={({ isActive }) => navigationListItem({ isActive })}
57
52
  to={joinPath(item.id)}
53
+ onClick={onRequestClose}
58
54
  >
59
55
  {item.icon && <item.icon size={16} className="align-[-0.125em]" />}
60
56
  {item.badge ? (
@@ -76,9 +72,9 @@ export const SidebarItem = ({
76
72
  {...{ [DATA_ANCHOR_ATTR]: item.href.slice(1) }}
77
73
  className={navigationListItem({
78
74
  isActive: item.href.slice(1) === activeAnchor,
79
- isTopLevel: level === 0,
80
75
  className: item.badge?.placement !== "start" && "justify-between",
81
76
  })}
77
+ onClick={onRequestClose}
82
78
  >
83
79
  {item.badge ? (
84
80
  <>
@@ -111,10 +107,11 @@ export const SidebarItem = ({
111
107
  </NavLink>
112
108
  ) : (
113
109
  <a
114
- className={navigationListItem({ isTopLevel: level === 0 })}
110
+ className={navigationListItem()}
115
111
  href={item.href}
116
112
  target="_blank"
117
113
  rel="noopener noreferrer"
114
+ onClick={onRequestClose}
118
115
  >
119
116
  <span className="whitespace-normal">{item.label}</span>
120
117
  {/* This prevents that the icon would be positioned in its own line if the text fills a line entirely */}
@@ -13,7 +13,7 @@ export const SidebarWrapper = forwardRef<
13
13
  data-navigation={String(pushMainContent)}
14
14
  className={cn(
15
15
  "scrollbar peer hidden lg:flex flex-col fixed text-sm overflow-y-auto shrink-0",
16
- "px-[--padding-nav-item] -mx-[--padding-nav-item] pb-20 mt-[--padding-content-top]",
16
+ "-mx-[--padding-nav-item] pb-20 mt-[--padding-content-top]",
17
17
  "w-[--side-nav-width] h-[calc(100%-var(--header-height))] scroll-pt-2 gap-2",
18
18
  className,
19
19
  )}
@@ -1,6 +1,6 @@
1
1
  import { ReactNode } from "react";
2
+ import { TopNavigationItem } from "../../config/validators/common.js";
2
3
  import type { SidebarConfig } from "../../config/validators/SidebarSchema.js";
3
- import { TopNavigationItem } from "../../config/validators/validate.js";
4
4
  import { type AuthenticationProvider } from "../authentication/authentication.js";
5
5
  import type { ComponentsContextType } from "../components/context/ComponentsContext.js";
6
6
  import { Slotlets } from "../components/SlotletProvider.js";
@@ -390,6 +390,10 @@ const Schema = builder.objectRef<OpenAPIDocument>("Schema").implement({
390
390
  resolve: (root) => root.info.description,
391
391
  nullable: true,
392
392
  }),
393
+ summary: t.string({
394
+ resolve: (root) => root.info.summary,
395
+ nullable: true,
396
+ }),
393
397
  paths: t.field({
394
398
  type: [PathItem],
395
399
  resolve: (root) =>
@@ -439,7 +443,6 @@ const SchemaSource = builder.enumType("SchemaType", {
439
443
 
440
444
  builder.queryType({
441
445
  fields: (t) => ({
442
- // https://tan-cow-main-bce8a06.d2.zuplo.dev/openapi
443
446
  schema: t.field({
444
447
  type: Schema,
445
448
  args: {
@@ -1,4 +1,5 @@
1
1
  /* eslint-disable @typescript-eslint/no-explicit-any */
2
+ import { type RecordAny, traverse } from "../../../util/traverse.js";
2
3
  import type { OpenAPIDocument } from "../index.js";
3
4
  /**
4
5
  * Upgrade from OpenAPI 3.0.x to 3.1.0
@@ -6,30 +7,8 @@ import type { OpenAPIDocument } from "../index.js";
6
7
  * Taken from https://github.com/scalar/openapi-parser/blob/main/packages/openapi-parser/src/utils/upgradeFromThreeToThreeOne.ts
7
8
  * https://www.openapis.org/blog/2021/02/16/migrating-from-openapi-3-0-to-3-1-0
8
9
  */
9
- export function traverse(
10
- specification: Record<string, any>,
11
- transform: (specification: Record<string, any>) => Record<string, any>,
12
- ) {
13
- const result: Record<string, any> = {};
14
10
 
15
- for (const [key, value] of Object.entries(specification)) {
16
- if (Array.isArray(value)) {
17
- result[key] = value.map((item) =>
18
- typeof item === "object" && item !== null
19
- ? traverse(item, transform)
20
- : item,
21
- );
22
- } else if (typeof value === "object" && value !== null) {
23
- result[key] = traverse(value, transform);
24
- } else {
25
- result[key] = value;
26
- }
27
- }
28
-
29
- return transform(result);
30
- }
31
-
32
- export const upgradeSchema = (schema: Record<string, any>): OpenAPIDocument => {
11
+ export const upgradeSchema = (schema: RecordAny): OpenAPIDocument => {
33
12
  if (schema.openapi?.startsWith("3.0")) {
34
13
  schema.openapi = "3.1.0";
35
14
  }
@@ -64,9 +43,7 @@ export const upgradeSchema = (schema: Record<string, any>): OpenAPIDocument => {
64
43
  schema = traverse(schema, (sub) => {
65
44
  if (sub.example !== undefined) {
66
45
  sub.examples = {
67
- default: {
68
- value: sub.example,
69
- },
46
+ default: sub.example,
70
47
  };
71
48
  delete sub.example;
72
49
  }
@@ -77,7 +54,7 @@ export const upgradeSchema = (schema: Record<string, any>): OpenAPIDocument => {
77
54
  schema = traverse(schema, (sub) => {
78
55
  if (sub.type === "object" && sub.properties !== undefined) {
79
56
  for (const [, value] of Object.entries(sub.properties)) {
80
- const v = (value ?? {}) as Record<string, any>;
57
+ const v = (value ?? {}) as RecordAny;
81
58
  if (v.type === "string" && v.format === "binary") {
82
59
  v.contentEncoding = "application/octet-stream";
83
60
  delete v.format;
@@ -0,0 +1,124 @@
1
+ import slugify from "@sindresorhus/slugify";
2
+ import { Fragment } from "react";
3
+ import { Head, Link } from "zudoku/components";
4
+ import { Markdown } from "../../components/Markdown.js";
5
+ import { useExposedProps } from "../../util/useExposedProps.js";
6
+ import type { ApiCatalogItem, CatalogCategory } from "./index.js";
7
+
8
+ export const Catalog = ({
9
+ items,
10
+ categories,
11
+ label = "API Library",
12
+ }: {
13
+ label: string;
14
+ items: ApiCatalogItem[];
15
+ categories: CatalogCategory[];
16
+ }) => {
17
+ const { searchParams, setSearchParams } = useExposedProps();
18
+ const activeCategory = searchParams.get("category");
19
+ return (
20
+ <section className="pt-[--padding-content-top] pb-[--padding-content-bottom]">
21
+ <Head>
22
+ <title>{label}</title>
23
+ </Head>
24
+ <div className="grid grid-cols-12 gap-12">
25
+ <div className="flex flex-col gap-4 col-span-3 px-4 not-prose sticky top-48">
26
+ <div className="justify-between">
27
+ {categories.map((category, idx) => (
28
+ <Fragment key={category.label}>
29
+ <div className="flex justify-between mb-2.5">
30
+ <span className="font-medium text-sm">{category.label}</span>
31
+ {idx === 0 && activeCategory && (
32
+ <button
33
+ type="button"
34
+ className="text-end text-sm mr-8 text-foreground/60 hover:text-foreground"
35
+ onClick={() => setSearchParams({})}
36
+ >
37
+ Clear
38
+ </button>
39
+ )}
40
+ </div>
41
+ <ul className="space-y-1 [&>li]:py-2">
42
+ {Array.from(
43
+ new Set(
44
+ category.tags
45
+ .map((tag) => ({
46
+ tag,
47
+ count: items.filter((api) =>
48
+ api.categories.find((c) => c.tags.includes(tag)),
49
+ ).length,
50
+ }))
51
+ .map(({ tag, count }) => (
52
+ <li
53
+ key={slugify(category.label + " " + tag)}
54
+ className={`flex px-4 rounded-lg -translate-x-4 justify-between text-sm cursor-pointer hover:text-primary transition ${
55
+ slugify(tag) === activeCategory
56
+ ? "font-medium bg-border/30 rounded"
57
+ : ""
58
+ }`}
59
+ onClick={() =>
60
+ setSearchParams({
61
+ category: slugify(category.label + " " + tag),
62
+ })
63
+ }
64
+ >
65
+ <span>{tag}</span>
66
+ <span
67
+ className={`flex items-center justify-center border rounded-md w-8 text-xs font-semibold ${
68
+ slugify(tag) === activeCategory
69
+ ? "bg-primary border-primary text-primary-foreground"
70
+ : ""
71
+ }`}
72
+ >
73
+ {count}
74
+ </span>
75
+ </li>
76
+ )),
77
+ ),
78
+ )}
79
+ </ul>
80
+ </Fragment>
81
+ ))}
82
+ </div>
83
+ </div>
84
+ <div className="col-span-9">
85
+ <h3 className="mt-0 text-2xl font-bold mb-4">{label}</h3>
86
+
87
+ <div className="grid grid-cols-2 gap-4">
88
+ {items
89
+ .filter(
90
+ (api) =>
91
+ !activeCategory ||
92
+ api.categories.find((c) =>
93
+ c.tags.find(
94
+ (t) => slugify(c.label + " " + t) === activeCategory,
95
+ ),
96
+ ),
97
+ )
98
+ .map((api, i) => (
99
+ <Link
100
+ to={{
101
+ pathname: `/${api.path}`,
102
+ search: activeCategory ? `category=${activeCategory}` : "",
103
+ }}
104
+ className="no-underline hover:!text-foreground"
105
+ key={api.path}
106
+ >
107
+ <div
108
+ className="border h-full rounded p-4 flex flex-col gap-2 cursor-pointer hover:bg-border/20 font-normal"
109
+ key={i}
110
+ >
111
+ <span className="font-semibold">{api.label}</span>
112
+ <Markdown
113
+ className="text-sm whitespace-pre-wrap mb-6 line-clamp-2"
114
+ content={api.description}
115
+ />
116
+ </div>
117
+ </Link>
118
+ ))}
119
+ </div>
120
+ </div>
121
+ </div>
122
+ </section>
123
+ );
124
+ };
@@ -0,0 +1,50 @@
1
+ import type { ZudokuPlugin } from "../../core/plugins.js";
2
+ import { Catalog } from "./Catalog.js";
3
+
4
+ export type ApiCatalogItem = {
5
+ path: string;
6
+ label: string;
7
+ description: string;
8
+ categories: CatalogCategory[];
9
+ };
10
+
11
+ export type CatalogCategory = {
12
+ label: string;
13
+ tags: string[];
14
+ };
15
+
16
+ export type ApiCatalogPluginOptions = {
17
+ navigationId: string;
18
+ label: string;
19
+ categories?: CatalogCategory[];
20
+ items: ApiCatalogItem[];
21
+ };
22
+
23
+ export const apiCatalogPlugin = ({
24
+ navigationId,
25
+ items,
26
+ label,
27
+ categories,
28
+ }: {
29
+ navigationId: string;
30
+ label: string;
31
+ categories?: CatalogCategory[];
32
+ items: ApiCatalogItem[];
33
+ }): ZudokuPlugin => {
34
+ return {
35
+ getRoutes: () => {
36
+ return [
37
+ {
38
+ path: navigationId,
39
+ element: (
40
+ <Catalog
41
+ label={label}
42
+ items={items}
43
+ categories={categories ?? []}
44
+ />
45
+ ),
46
+ },
47
+ ];
48
+ },
49
+ };
50
+ };
@@ -1,7 +1,7 @@
1
1
  import { useMDXComponents } from "@mdx-js/react";
2
2
  import slugify from "@sindresorhus/slugify";
3
3
  import { Helmet } from "@zudoku/react-helmet-async";
4
- import { type PropsWithChildren } from "react";
4
+ import { type PropsWithChildren, useEffect } from "react";
5
5
  import { Link } from "react-router-dom";
6
6
  import { CategoryHeading } from "../../components/CategoryHeading.js";
7
7
  import { Heading } from "../../components/Heading.js";
@@ -15,6 +15,14 @@ import { cn } from "../../util/cn.js";
15
15
  import { Toc } from "./Toc.js";
16
16
  import { MarkdownPluginDefaultOptions, MDXImport } from "./index.js";
17
17
 
18
+ declare global {
19
+ interface Window {
20
+ __getReactRefreshIgnoredExports?: (args: {
21
+ id: string;
22
+ }) => string[] | undefined;
23
+ }
24
+ }
25
+
18
26
  const MarkdownHeadings = {
19
27
  h2: ({ children, id }) => (
20
28
  <Heading level={2} id={id} registerSidebarAnchor>
@@ -30,11 +38,14 @@ const MarkdownHeadings = {
30
38
 
31
39
  export const MdxPage = ({
32
40
  mdxComponent: MdxComponent,
41
+ file,
33
42
  frontmatter = {},
34
43
  defaultOptions,
35
44
  tableOfContents,
45
+ excerpt,
36
46
  }: PropsWithChildren<
37
47
  Omit<MDXImport, "default"> & {
48
+ file: string;
38
49
  mdxComponent: MDXImport["default"];
39
50
  defaultOptions?: MarkdownPluginDefaultOptions;
40
51
  }
@@ -57,10 +68,25 @@ export const MdxPage = ({
57
68
 
58
69
  const { prev, next } = usePrevNext();
59
70
 
71
+ useEffect(() => {
72
+ if (process.env.NODE_ENV === "development") {
73
+ window.__getReactRefreshIgnoredExports = ({ id }) => {
74
+ if (!id.endsWith(file)) return;
75
+
76
+ return ["frontmatter", "tableOfContents"];
77
+ };
78
+
79
+ return () => {
80
+ window.__getReactRefreshIgnoredExports = undefined;
81
+ };
82
+ }
83
+ }, [file]);
84
+
60
85
  return (
61
86
  <div className="xl:grid grid-cols-[--sidecar-grid-cols] gap-8 justify-between">
62
87
  <Helmet>
63
88
  <title>{pageTitle}</title>
89
+ {excerpt && <meta name="description" content={excerpt} />}
64
90
  </Helmet>
65
91
  <div
66
92
  className={cn(
@@ -1,7 +1,7 @@
1
1
  import type { Toc } from "@stefanprobst/rehype-extract-toc";
2
2
  import type { MDXProps } from "mdx/types.js";
3
3
  import { RouteObject } from "react-router-dom";
4
- import { ZudokuDocsConfig } from "../../../config/validators/validate.js";
4
+ import { ZudokuDocsConfig } from "../../../config/validators/common.js";
5
5
  import type { ZudokuPlugin } from "../../core/plugins.js";
6
6
  import { DocResolver } from "./resolver.js";
7
7
 
@@ -24,6 +24,7 @@ export type Frontmatter = {
24
24
  export type MDXImport = {
25
25
  tableOfContents: Toc;
26
26
  frontmatter: Frontmatter;
27
+ excerpt?: string;
27
28
  default: (props: MDXProps) => JSX.Element;
28
29
  };
29
30
 
@@ -57,6 +58,7 @@ export const markdownPlugin = (
57
58
  return {
58
59
  element: (
59
60
  <MdxPage
61
+ file={file}
60
62
  mdxComponent={Component}
61
63
  {...props}
62
64
  defaultOptions={defaultOptions}
@@ -1,9 +1,7 @@
1
1
  import fs from "fs";
2
2
  import path from "path";
3
- import {
4
- ZudokuConfig,
5
- ZudokuDocsConfig,
6
- } from "../../../config/validators/validate.js";
3
+ import { ZudokuDocsConfig } from "../../../config/validators/common.js";
4
+ import { ZudokuConfig } from "../../../config/validators/validate.js";
7
5
 
8
6
  const DEFAULT_DOCS_FILES = "/pages/**/*.{md,mdx}";
9
7
 
@@ -1,5 +1,6 @@
1
1
  import { ResultOf } from "@graphql-typed-document-node/core";
2
2
  import { useSuspenseQuery } from "@tanstack/react-query";
3
+ import { Helmet } from "@zudoku/react-helmet-async";
3
4
  import { CategoryHeading } from "../../components/CategoryHeading.js";
4
5
  import { Heading } from "../../components/Heading.js";
5
6
  import { Markdown, ProseClasses } from "../../components/Markdown.js";
@@ -81,6 +82,7 @@ const AllOperationsQuery = graphql(/* GraphQL */ `
81
82
  query AllOperations($input: JSON!, $type: SchemaType!) {
82
83
  schema(input: $input, type: $type) {
83
84
  description
85
+ summary
84
86
  title
85
87
  url
86
88
  version
@@ -96,19 +98,77 @@ const AllOperationsQuery = graphql(/* GraphQL */ `
96
98
  }
97
99
  `);
98
100
 
101
+ /**
102
+ * @description Clean up a commonmark formatted description for use in the meta
103
+ * description.
104
+ */
105
+ function cleanDescription(
106
+ description: string,
107
+ maxLength: number = 160,
108
+ ): string {
109
+ if (!description) {
110
+ return "";
111
+ }
112
+
113
+ // Replace Markdown links [text](url) with just "text"
114
+ description = description.replace(/\[([^\]]+)\]\([^)]+\)/g, "$1");
115
+
116
+ // Remove Markdown image syntax: ![alt](url)
117
+ description = description.replace(/!\[.*?\]\(.*?\)/g, "");
118
+
119
+ // Remove other Markdown syntax (e.g., **bold**, _italic_, `code`)
120
+ description = description.replace(/[_*`~]/g, "");
121
+
122
+ // Remove headings (# Heading), blockquotes (> Quote), and horizontal rules (--- or ***)
123
+ description = description.replace(/^(?:>|\s*#+|-{3,}|\*{3,})/gm, "");
124
+
125
+ // Remove any remaining formatting characters
126
+ description = description.replace(/[|>{}[\]]/g, "");
127
+
128
+ // Collapse multiple spaces and trim the text
129
+ description = description.replace(/\s+/g, " ").trim();
130
+
131
+ // Limit to the specified maximum length
132
+ description = description.substring(0, maxLength);
133
+
134
+ // Escape for HTML safety
135
+ return description
136
+ .replace(/&/g, "&amp;")
137
+ .replace(/</g, "&lt;")
138
+ .replace(/>/g, "&gt;")
139
+ .replace(/"/g, "&quot;")
140
+ .replace(/'/g, "&#039;");
141
+ }
142
+
99
143
  export const OperationList = () => {
100
144
  const { input, type } = useOasConfig();
101
145
  const query = useCreateQuery(AllOperationsQuery, { input, type });
102
146
  const result = useSuspenseQuery(query);
103
-
147
+ const title = result.data.schema.title;
148
+ const summary = result.data.schema.summary;
149
+ const description = result.data.schema.description;
150
+ // The summary property is preferable here as it is a short description of
151
+ // the API, whereas the description property is typically longer and supports
152
+ // commonmark formatting, making it ill-suited for use in the meta description
153
+ const metaDescription = summary
154
+ ? summary
155
+ : description
156
+ ? cleanDescription(description)
157
+ : undefined;
104
158
  return (
105
159
  <div className="pt-[--padding-content-top]">
160
+ <Helmet>
161
+ <title>{title}</title>
162
+ {metaDescription && (
163
+ <meta name="description" content={metaDescription} />
164
+ )}
165
+ </Helmet>
106
166
  <div
107
167
  className={cn(ProseClasses, "mb-16 max-w-full prose-img:max-w-prose")}
108
168
  >
109
169
  <CategoryHeading>Overview</CategoryHeading>
110
170
  <Heading level={1} id="description" registerSidebarAnchor>
111
- {result.data.schema.title}
171
+ {title}
112
172
  </Heading>
113
173
  <Markdown content={result.data.schema.description ?? ""} />
114
174
  </div>
@@ -120,7 +180,8 @@ export const OperationList = () => {
120
180
  {result.data.schema.tags
121
181
  .filter((tag) => tag.operations.length > 0)
122
182
  .map((tag) => (
123
- <div key={tag.name} className="[content-visibility:auto]">
183
+ // px, -mx is so that `content-visibility` doesn't cut off overflown heading anchor links '#'
184
+ <div key={tag.name} className="px-6 -mx-6 [content-visibility:auto]">
124
185
  {tag.name && <CategoryHeading>{tag.name}</CategoryHeading>}
125
186
  {tag.description && (
126
187
  <Markdown
@@ -15,6 +15,20 @@ type GraphQLResponse<TResult> = {
15
15
  data: TResult;
16
16
  };
17
17
 
18
+ const resolveVariables = async (variables?: unknown) => {
19
+ if (!variables) return;
20
+
21
+ if (
22
+ typeof variables === "object" &&
23
+ "type" in variables &&
24
+ variables.type === "file" &&
25
+ "input" in variables &&
26
+ typeof variables.input === "function"
27
+ ) {
28
+ variables.input = await variables.input();
29
+ }
30
+ };
31
+
18
32
  const throwIfError = (response: GraphQLResponse<unknown>) => {
19
33
  if (!response.errors?.[0]) return;
20
34
 
@@ -47,6 +61,9 @@ export class GraphQLClient {
47
61
  ...[variables]: TVariables extends Record<string, never> ? [] : [TVariables]
48
62
  ) => {
49
63
  const operationName = query.match(/query (\w+)/)?.[1];
64
+
65
+ await resolveVariables(variables);
66
+
50
67
  const body = JSON.stringify({ query, variables, operationName });
51
68
 
52
69
  switch (this.#mode) {
@@ -1,4 +1,5 @@
1
- import { useContext } from "react";
1
+ import hashit from "object-hash";
2
+ import { useContext, useMemo } from "react";
2
3
  import type { TypedDocumentString } from "../graphql/graphql.js";
3
4
  import { GraphQLContext } from "./GraphQLContext.js";
4
5
 
@@ -11,8 +12,10 @@ export const useCreateQuery = <TResult, TVariables>(
11
12
  throw new Error("useGraphQL must be used within a GraphQLProvider");
12
13
  }
13
14
 
15
+ const hash = useMemo(() => hashit(variables[0] ?? {}), [variables]);
16
+
14
17
  return {
15
18
  queryFn: () => graphQLClient.fetch(query, ...variables),
16
- queryKey: [query, variables[0]],
19
+ queryKey: [query, hash],
17
20
  } as const;
18
21
  };
@@ -17,7 +17,7 @@ const documents = {
17
17
  types.ServersQueryDocument,
18
18
  "\n fragment OperationsFragment on OperationItem {\n slug\n summary\n method\n description\n operationId\n contentTypes\n path\n parameters {\n name\n in\n description\n required\n schema\n style\n examples {\n name\n description\n externalValue\n value\n summary\n }\n }\n requestBody {\n content {\n mediaType\n encoding {\n name\n }\n examples {\n name\n description\n externalValue\n value\n summary\n }\n schema\n }\n description\n required\n }\n responses {\n statusCode\n links\n description\n content {\n examples {\n name\n description\n externalValue\n value\n summary\n }\n mediaType\n encoding {\n name\n }\n schema\n }\n }\n }\n":
19
19
  types.OperationsFragmentFragmentDoc,
20
- "\n query AllOperations($input: JSON!, $type: SchemaType!) {\n schema(input: $input, type: $type) {\n description\n title\n url\n version\n tags {\n name\n description\n operations {\n slug\n ...OperationsFragment\n }\n }\n }\n }\n":
20
+ "\n query AllOperations($input: JSON!, $type: SchemaType!) {\n schema(input: $input, type: $type) {\n description\n summary\n title\n url\n version\n tags {\n name\n description\n operations {\n slug\n ...OperationsFragment\n }\n }\n }\n }\n":
21
21
  types.AllOperationsDocument,
22
22
  "\n query getServerQuery($input: JSON!, $type: SchemaType!) {\n schema(input: $input, type: $type) {\n url\n servers {\n url\n }\n }\n }\n":
23
23
  types.GetServerQueryDocument,
@@ -41,7 +41,7 @@ export function graphql(
41
41
  * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
42
42
  */
43
43
  export function graphql(
44
- source: "\n query AllOperations($input: JSON!, $type: SchemaType!) {\n schema(input: $input, type: $type) {\n description\n title\n url\n version\n tags {\n name\n description\n operations {\n slug\n ...OperationsFragment\n }\n }\n }\n }\n",
44
+ source: "\n query AllOperations($input: JSON!, $type: SchemaType!) {\n schema(input: $input, type: $type) {\n description\n summary\n title\n url\n version\n tags {\n name\n description\n operations {\n slug\n ...OperationsFragment\n }\n }\n }\n }\n",
45
45
  ): typeof import("./graphql.js").AllOperationsDocument;
46
46
  /**
47
47
  * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.