zudoku 0.39.2 → 0.39.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 (70) hide show
  1. package/dist/cli/build/handler.js +2 -1
  2. package/dist/cli/build/handler.js.map +1 -1
  3. package/dist/cli/dev/handler.js +2 -1
  4. package/dist/cli/dev/handler.js.map +1 -1
  5. package/dist/config/validators/common.d.ts +871 -23
  6. package/dist/config/validators/common.js +44 -1
  7. package/dist/config/validators/common.js.map +1 -1
  8. package/dist/config/validators/validate.d.ts +280 -9
  9. package/dist/lib/components/Footer.d.ts +1 -0
  10. package/dist/lib/components/Footer.js +32 -0
  11. package/dist/lib/components/Footer.js.map +1 -0
  12. package/dist/lib/components/Layout.js +2 -1
  13. package/dist/lib/components/Layout.js.map +1 -1
  14. package/dist/lib/components/Pagination.d.ts +2 -1
  15. package/dist/lib/components/Pagination.js +2 -2
  16. package/dist/lib/components/Pagination.js.map +1 -1
  17. package/dist/lib/components/navigation/Sidebar.js +1 -10
  18. package/dist/lib/components/navigation/Sidebar.js.map +1 -1
  19. package/dist/lib/components/navigation/SidebarWrapper.d.ts +2 -3
  20. package/dist/lib/components/navigation/SidebarWrapper.js +12 -3
  21. package/dist/lib/components/navigation/SidebarWrapper.js.map +1 -1
  22. package/dist/lib/core/ZudokuContext.d.ts +4 -2
  23. package/dist/lib/core/ZudokuContext.js.map +1 -1
  24. package/dist/lib/plugins/api-catalog/Catalog.js +1 -1
  25. package/dist/lib/plugins/api-catalog/Catalog.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.js +1 -1
  29. package/dist/lib/plugins/openapi/OperationList.js.map +1 -1
  30. package/dist/lib/plugins/openapi/OperationListItem.js +1 -1
  31. package/dist/lib/plugins/openapi/OperationListItem.js.map +1 -1
  32. package/dist/vite/api/schema-codegen.js +4 -1
  33. package/dist/vite/api/schema-codegen.js.map +1 -1
  34. package/dist/vite/plugin-api.js +3 -0
  35. package/dist/vite/plugin-api.js.map +1 -1
  36. package/dist/vite/plugin-config-reload.js +3 -2
  37. package/dist/vite/plugin-config-reload.js.map +1 -1
  38. package/lib/{MdxPage-JscVnWM8.js → MdxPage-Vw_dc9Yz.js} +24 -23
  39. package/lib/MdxPage-Vw_dc9Yz.js.map +1 -0
  40. package/lib/{OasProvider-C4T5TU8Z.js → OasProvider-CjXTP-nz.js} +2 -2
  41. package/lib/{OasProvider-C4T5TU8Z.js.map → OasProvider-CjXTP-nz.js.map} +1 -1
  42. package/lib/{OperationList-C-gBHUou.js → OperationList-BYemrDYk.js} +17 -15
  43. package/lib/{OperationList-C-gBHUou.js.map → OperationList-BYemrDYk.js.map} +1 -1
  44. package/lib/{Pagination-DCCvGq0m.js → Pagination-SdlocK96.js} +14 -12
  45. package/lib/Pagination-SdlocK96.js.map +1 -0
  46. package/lib/{SchemaList-DuN6ThXR.js → SchemaList-CAeKy9pV.js} +2 -2
  47. package/lib/{SchemaList-DuN6ThXR.js.map → SchemaList-CAeKy9pV.js.map} +1 -1
  48. package/lib/{index-DsdAaiwx.js → index-C1OzjGUK.js} +4 -4
  49. package/lib/{index-DsdAaiwx.js.map → index-C1OzjGUK.js.map} +1 -1
  50. package/lib/{index-D6ktNq4i.js → index-D48iOQvJ.js} +433 -323
  51. package/lib/index-D48iOQvJ.js.map +1 -0
  52. package/lib/zudoku.components.js +1 -1
  53. package/lib/zudoku.plugin-api-catalog.js +48 -55
  54. package/lib/zudoku.plugin-api-catalog.js.map +1 -1
  55. package/lib/zudoku.plugin-markdown.js +1 -1
  56. package/lib/zudoku.plugin-openapi.js +1 -1
  57. package/package.json +3 -3
  58. package/src/lib/components/Footer.tsx +136 -0
  59. package/src/lib/components/Layout.tsx +2 -0
  60. package/src/lib/components/Pagination.tsx +4 -1
  61. package/src/lib/components/navigation/Sidebar.tsx +26 -38
  62. package/src/lib/components/navigation/SidebarWrapper.tsx +12 -5
  63. package/src/lib/core/ZudokuContext.ts +7 -2
  64. package/src/lib/plugins/api-catalog/Catalog.tsx +1 -4
  65. package/src/lib/plugins/markdown/MdxPage.tsx +3 -2
  66. package/src/lib/plugins/openapi/OperationList.tsx +8 -6
  67. package/src/lib/plugins/openapi/OperationListItem.tsx +2 -2
  68. package/lib/MdxPage-JscVnWM8.js.map +0 -1
  69. package/lib/Pagination-DCCvGq0m.js.map +0 -1
  70. package/lib/index-D6ktNq4i.js.map +0 -1
@@ -1,9 +1,9 @@
1
- import { j as t } from "./jsx-runtime-CYK1ROHF.js";
1
+ import { j as s } from "./jsx-runtime-CYK1ROHF.js";
2
2
  import { s as h } from "./index-LNp6rxyU.js";
3
3
  import { d as b, m as x } from "./chunk-HA7DTUK3-C4gP41vD.js";
4
4
  import { u as j, d as y, f as p } from "./hook-CqpVYDqN.js";
5
5
  import { H as v } from "./RouteGuard-CqZPoZYJ.js";
6
- import { L as N } from "./index-D6ktNq4i.js";
6
+ import { L as N } from "./index-D48iOQvJ.js";
7
7
  import { H as S, M as w } from "./Markdown-aF5FdsNi.js";
8
8
  const H = ({
9
9
  items: r,
@@ -11,52 +11,45 @@ const H = ({
11
11
  label: d = "API Library",
12
12
  categoryLabel: a
13
13
  }) => {
14
- const n = j(), i = b({ path: "/catalog/:category" }), s = i == null ? void 0 : i.params.category, l = y({
14
+ const n = j(), i = b({ path: "/catalog/:category" }), t = i == null ? void 0 : i.params.category, l = y({
15
15
  queryFn: () => o(r, { auth: n }),
16
16
  queryKey: ["catalogItems", n]
17
- }), m = s ? { "data-pagefind-ignore": "all" } : {};
18
- return /* @__PURE__ */ t.jsxs(
19
- "section",
20
- {
21
- className: "pt-[--padding-content-top] pb-[--padding-content-bottom]",
22
- ...m,
23
- children: [
24
- /* @__PURE__ */ t.jsx(v, { children: /* @__PURE__ */ t.jsxs("title", { children: [
25
- a ? `${a} - ` : "",
26
- d
27
- ] }) }),
28
- /* @__PURE__ */ t.jsxs("div", { className: "grid gap-4", children: [
29
- /* @__PURE__ */ t.jsxs(S, { level: 2, children: [
30
- d,
31
- a && ` - ${a}`
32
- ] }),
33
- /* @__PURE__ */ t.jsx("div", { className: "grid grid-cols-2 gap-4", children: l.data.filter(
34
- (e) => !s || e.categories.find(
35
- (c) => c.tags.find((g) => u(c.label, g) === s)
17
+ }), m = t ? { "data-pagefind-ignore": "all" } : {};
18
+ return /* @__PURE__ */ s.jsxs("section", { className: "pt-[--padding-content-top]", ...m, children: [
19
+ /* @__PURE__ */ s.jsx(v, { children: /* @__PURE__ */ s.jsxs("title", { children: [
20
+ a ? `${a} - ` : "",
21
+ d
22
+ ] }) }),
23
+ /* @__PURE__ */ s.jsxs("div", { className: "grid gap-4", children: [
24
+ /* @__PURE__ */ s.jsxs(S, { level: 2, children: [
25
+ d,
26
+ a && ` - ${a}`
27
+ ] }),
28
+ /* @__PURE__ */ s.jsx("div", { className: "grid grid-cols-2 gap-4", children: l.data.filter(
29
+ (e) => !t || e.categories.find(
30
+ (c) => c.tags.find((u) => f(c.label, u) === t)
31
+ )
32
+ ).map((e) => /* @__PURE__ */ s.jsx(
33
+ N,
34
+ {
35
+ to: p(e.path),
36
+ className: "no-underline hover:!text-foreground",
37
+ children: /* @__PURE__ */ s.jsxs("div", { className: "border h-full rounded-lg p-4 flex flex-col gap-2 cursor-pointer hover:bg-border/20 font-normal", children: [
38
+ /* @__PURE__ */ s.jsx("span", { className: "font-semibold", children: e.label }),
39
+ /* @__PURE__ */ s.jsx(
40
+ w,
41
+ {
42
+ className: "text-sm whitespace-pre-wrap mb-6 line-clamp-2",
43
+ content: e.description
44
+ }
36
45
  )
37
- ).map((e) => /* @__PURE__ */ t.jsx(
38
- N,
39
- {
40
- to: p(e.path),
41
- className: "no-underline hover:!text-foreground",
42
- children: /* @__PURE__ */ t.jsxs("div", { className: "border h-full rounded-lg p-4 flex flex-col gap-2 cursor-pointer hover:bg-border/20 font-normal", children: [
43
- /* @__PURE__ */ t.jsx("span", { className: "font-semibold", children: e.label }),
44
- /* @__PURE__ */ t.jsx(
45
- w,
46
- {
47
- className: "text-sm whitespace-pre-wrap mb-6 line-clamp-2",
48
- content: e.description
49
- }
50
- )
51
- ] })
52
- },
53
- e.path
54
- )) })
55
- ] })
56
- ]
57
- }
58
- );
59
- }, u = (r, o) => h(`${r}-${o}`), A = ({
46
+ ] })
47
+ },
48
+ e.path
49
+ )) })
50
+ ] })
51
+ ] });
52
+ }, f = (r, o) => h(`${r}-${o}`), A = ({
60
53
  navigationId: r,
61
54
  items: o,
62
55
  label: d,
@@ -65,16 +58,16 @@ const H = ({
65
58
  }) => {
66
59
  const i = Object.fromEntries(
67
60
  a.flatMap(
68
- (s) => [void 0, ...s.tags].map((l) => [
69
- p(r, l ? u(s.label, l) : void 0),
61
+ (t) => [void 0, ...t.tags].map((l) => [
62
+ p(r, l ? f(t.label, l) : void 0),
70
63
  l
71
64
  ])
72
65
  )
73
66
  );
74
67
  return {
75
- getSidebar: async (s) => {
68
+ getSidebar: async (t) => {
76
69
  if (!Object.keys(i).some(
77
- (e) => x(e, s)
70
+ (e) => x(e, t)
78
71
  ))
79
72
  return [];
80
73
  const m = a.map((e) => ({
@@ -83,12 +76,12 @@ const H = ({
83
76
  collapsible: !1,
84
77
  items: e.tags.map((c) => ({
85
78
  type: "doc",
86
- id: p(r, u(e.label, c)),
79
+ id: p(r, f(e.label, c)),
87
80
  label: c,
88
81
  badge: {
89
82
  label: String(
90
83
  o.filter(
91
- (g) => g.categories.find((f) => f.tags.includes(c))
84
+ (u) => u.categories.find((g) => g.tags.includes(c))
92
85
  ).length
93
86
  ),
94
87
  color: "outline"
@@ -102,9 +95,9 @@ const H = ({
102
95
  badge: { label: String(o.length), color: "outline" }
103
96
  }), m;
104
97
  },
105
- getRoutes: () => Object.entries(i).map(([s, l]) => ({
106
- path: s,
107
- element: /* @__PURE__ */ t.jsx(
98
+ getRoutes: () => Object.entries(i).map(([t, l]) => ({
99
+ path: t,
100
+ element: /* @__PURE__ */ s.jsx(
108
101
  H,
109
102
  {
110
103
  label: d,
@@ -119,6 +112,6 @@ const H = ({
119
112
  };
120
113
  export {
121
114
  A as apiCatalogPlugin,
122
- u as getKey
115
+ f as getKey
123
116
  };
124
117
  //# sourceMappingURL=zudoku.plugin-api-catalog.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"zudoku.plugin-api-catalog.js","sources":["../src/lib/plugins/api-catalog/Catalog.tsx","../src/lib/plugins/api-catalog/index.tsx"],"sourcesContent":["import { useSuspenseQuery } from \"@tanstack/react-query\";\nimport { Helmet } from \"@zudoku/react-helmet-async\";\nimport { useMatch } from \"react-router\";\nimport { Link } from \"zudoku/components\";\nimport { useAuthState } from \"../../authentication/state.js\";\nimport { Heading } from \"../../components/Heading.js\";\nimport { Markdown } from \"../../components/Markdown.js\";\nimport { joinUrl } from \"../../util/joinUrl.js\";\nimport { type ApiCatalogPluginOptions, getKey } from \"./index.js\";\n\nexport const Catalog = ({\n items,\n filterCatalogItems = (items) => items,\n label = \"API Library\",\n categoryLabel,\n}: Omit<ApiCatalogPluginOptions, \"navigationId\"> & {\n categoryLabel?: string;\n}) => {\n const auth = useAuthState();\n const match = useMatch({ path: \"/catalog/:category\" });\n const activeCategory = match?.params.category;\n\n const catalogItems = useSuspenseQuery({\n queryFn: () => filterCatalogItems(items, { auth }),\n queryKey: [\"catalogItems\", auth],\n });\n\n // Only index the overview page, ignore the rest\n const dataSet = activeCategory ? { \"data-pagefind-ignore\": \"all\" } : {};\n\n return (\n <section\n className=\"pt-[--padding-content-top] pb-[--padding-content-bottom]\"\n {...dataSet}\n >\n <Helmet>\n <title>\n {categoryLabel ? `${categoryLabel} - ` : \"\"}\n {label}\n </title>\n </Helmet>\n <div className=\"grid gap-4\">\n <Heading level={2}>\n {label}\n {categoryLabel && ` - ${categoryLabel}`}\n </Heading>\n\n <div className=\"grid grid-cols-2 gap-4\">\n {catalogItems.data\n .filter(\n (api) =>\n !activeCategory ||\n api.categories.find((c) =>\n c.tags.find((t) => getKey(c.label, t) === activeCategory),\n ),\n )\n .map((api) => (\n <Link\n to={joinUrl(api.path)}\n className=\"no-underline hover:!text-foreground\"\n key={api.path}\n >\n <div className=\"border h-full rounded-lg p-4 flex flex-col gap-2 cursor-pointer hover:bg-border/20 font-normal\">\n <span className=\"font-semibold\">{api.label}</span>\n <Markdown\n className=\"text-sm whitespace-pre-wrap mb-6 line-clamp-2\"\n content={api.description}\n />\n </div>\n </Link>\n ))}\n </div>\n </div>\n </section>\n );\n};\n","import slugify from \"@sindresorhus/slugify\";\nimport { matchPath } from \"react-router\";\nimport type { SidebarItem } from \"../../../config/validators/SidebarSchema.js\";\nimport type { AuthState } from \"../../authentication/state.js\";\nimport type { ZudokuPlugin } from \"../../core/plugins.js\";\nimport { joinUrl } from \"../../util/joinUrl.js\";\nimport { Catalog } from \"./Catalog.js\";\n\nexport const getKey = (category: string, tag: string) =>\n slugify(`${category}-${tag}`);\n\nexport type ApiCatalogItem = {\n path: string;\n label: string;\n description: string;\n categories: CatalogCategory[];\n};\n\nexport type CatalogCategory = {\n label: string;\n tags: string[];\n};\n\nexport type ApiCatalogPluginOptions = {\n navigationId: string;\n label: string;\n categories?: CatalogCategory[];\n items: ApiCatalogItem[];\n filterCatalogItems?: filterCatalogItems;\n};\n\nexport type CatalogContext<ProviderData = unknown> = {\n auth: AuthState<ProviderData>;\n};\n\nexport type filterCatalogItems<ProviderData = unknown> = (\n items: ApiCatalogItem[],\n { auth }: CatalogContext<ProviderData>,\n) => ApiCatalogItem[];\n\nexport const apiCatalogPlugin = ({\n navigationId,\n items,\n label,\n categories = [],\n filterCatalogItems,\n}: {\n navigationId: string;\n label: string;\n categories?: CatalogCategory[];\n items: ApiCatalogItem[];\n filterCatalogItems?: filterCatalogItems;\n}): ZudokuPlugin => {\n const paths = Object.fromEntries(\n categories.flatMap((category) =>\n [undefined, ...category.tags].map((tag) => [\n joinUrl(navigationId, tag ? getKey(category.label, tag) : undefined),\n tag,\n ]),\n ),\n );\n\n return {\n getSidebar: async (currentPath) => {\n const matches = Object.keys(paths).some((path) =>\n matchPath(path, currentPath),\n );\n\n if (!matches) {\n return [];\n }\n\n const sidebar: SidebarItem[] = categories.map((category) => ({\n type: \"category\" as const,\n label: category.label,\n collapsible: false,\n items: category.tags.map((tag) => ({\n type: \"doc\" as const,\n id: joinUrl(navigationId, getKey(category.label, tag)),\n label: tag,\n badge: {\n label: String(\n items.filter((api) =>\n api.categories.find((c) => c.tags.includes(tag)),\n ).length,\n ),\n color: \"outline\" as const,\n },\n })),\n }));\n\n sidebar.unshift({\n type: \"doc\" as const,\n id: joinUrl(navigationId),\n label: \"Overview\",\n badge: { label: String(items.length), color: \"outline\" as const },\n });\n\n return sidebar;\n },\n getRoutes: () =>\n Object.entries(paths).map(([path, tag]) => ({\n path,\n element: (\n <Catalog\n label={label}\n categoryLabel={tag}\n items={items}\n filterCatalogItems={filterCatalogItems}\n categories={categories}\n />\n ),\n })),\n };\n};\n"],"names":["Catalog","items","filterCatalogItems","label","categoryLabel","auth","useAuthState","match","useMatch","activeCategory","catalogItems","useSuspenseQuery","dataSet","jsxs","jsx","Helmet","Heading","api","t","getKey","Link","joinUrl","Markdown","category","tag","slugify","apiCatalogPlugin","navigationId","categories","paths","currentPath","path","matchPath","sidebar","c"],"mappings":"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"}
1
+ {"version":3,"file":"zudoku.plugin-api-catalog.js","sources":["../src/lib/plugins/api-catalog/Catalog.tsx","../src/lib/plugins/api-catalog/index.tsx"],"sourcesContent":["import { useSuspenseQuery } from \"@tanstack/react-query\";\nimport { Helmet } from \"@zudoku/react-helmet-async\";\nimport { useMatch } from \"react-router\";\nimport { Link } from \"zudoku/components\";\nimport { useAuthState } from \"../../authentication/state.js\";\nimport { Heading } from \"../../components/Heading.js\";\nimport { Markdown } from \"../../components/Markdown.js\";\nimport { joinUrl } from \"../../util/joinUrl.js\";\nimport { type ApiCatalogPluginOptions, getKey } from \"./index.js\";\n\nexport const Catalog = ({\n items,\n filterCatalogItems = (items) => items,\n label = \"API Library\",\n categoryLabel,\n}: Omit<ApiCatalogPluginOptions, \"navigationId\"> & {\n categoryLabel?: string;\n}) => {\n const auth = useAuthState();\n const match = useMatch({ path: \"/catalog/:category\" });\n const activeCategory = match?.params.category;\n\n const catalogItems = useSuspenseQuery({\n queryFn: () => filterCatalogItems(items, { auth }),\n queryKey: [\"catalogItems\", auth],\n });\n\n // Only index the overview page, ignore the rest\n const dataSet = activeCategory ? { \"data-pagefind-ignore\": \"all\" } : {};\n\n return (\n <section className=\"pt-[--padding-content-top]\" {...dataSet}>\n <Helmet>\n <title>\n {categoryLabel ? `${categoryLabel} - ` : \"\"}\n {label}\n </title>\n </Helmet>\n <div className=\"grid gap-4\">\n <Heading level={2}>\n {label}\n {categoryLabel && ` - ${categoryLabel}`}\n </Heading>\n\n <div className=\"grid grid-cols-2 gap-4\">\n {catalogItems.data\n .filter(\n (api) =>\n !activeCategory ||\n api.categories.find((c) =>\n c.tags.find((t) => getKey(c.label, t) === activeCategory),\n ),\n )\n .map((api) => (\n <Link\n to={joinUrl(api.path)}\n className=\"no-underline hover:!text-foreground\"\n key={api.path}\n >\n <div className=\"border h-full rounded-lg p-4 flex flex-col gap-2 cursor-pointer hover:bg-border/20 font-normal\">\n <span className=\"font-semibold\">{api.label}</span>\n <Markdown\n className=\"text-sm whitespace-pre-wrap mb-6 line-clamp-2\"\n content={api.description}\n />\n </div>\n </Link>\n ))}\n </div>\n </div>\n </section>\n );\n};\n","import slugify from \"@sindresorhus/slugify\";\nimport { matchPath } from \"react-router\";\nimport type { SidebarItem } from \"../../../config/validators/SidebarSchema.js\";\nimport type { AuthState } from \"../../authentication/state.js\";\nimport type { ZudokuPlugin } from \"../../core/plugins.js\";\nimport { joinUrl } from \"../../util/joinUrl.js\";\nimport { Catalog } from \"./Catalog.js\";\n\nexport const getKey = (category: string, tag: string) =>\n slugify(`${category}-${tag}`);\n\nexport type ApiCatalogItem = {\n path: string;\n label: string;\n description: string;\n categories: CatalogCategory[];\n};\n\nexport type CatalogCategory = {\n label: string;\n tags: string[];\n};\n\nexport type ApiCatalogPluginOptions = {\n navigationId: string;\n label: string;\n categories?: CatalogCategory[];\n items: ApiCatalogItem[];\n filterCatalogItems?: filterCatalogItems;\n};\n\nexport type CatalogContext<ProviderData = unknown> = {\n auth: AuthState<ProviderData>;\n};\n\nexport type filterCatalogItems<ProviderData = unknown> = (\n items: ApiCatalogItem[],\n { auth }: CatalogContext<ProviderData>,\n) => ApiCatalogItem[];\n\nexport const apiCatalogPlugin = ({\n navigationId,\n items,\n label,\n categories = [],\n filterCatalogItems,\n}: {\n navigationId: string;\n label: string;\n categories?: CatalogCategory[];\n items: ApiCatalogItem[];\n filterCatalogItems?: filterCatalogItems;\n}): ZudokuPlugin => {\n const paths = Object.fromEntries(\n categories.flatMap((category) =>\n [undefined, ...category.tags].map((tag) => [\n joinUrl(navigationId, tag ? getKey(category.label, tag) : undefined),\n tag,\n ]),\n ),\n );\n\n return {\n getSidebar: async (currentPath) => {\n const matches = Object.keys(paths).some((path) =>\n matchPath(path, currentPath),\n );\n\n if (!matches) {\n return [];\n }\n\n const sidebar: SidebarItem[] = categories.map((category) => ({\n type: \"category\" as const,\n label: category.label,\n collapsible: false,\n items: category.tags.map((tag) => ({\n type: \"doc\" as const,\n id: joinUrl(navigationId, getKey(category.label, tag)),\n label: tag,\n badge: {\n label: String(\n items.filter((api) =>\n api.categories.find((c) => c.tags.includes(tag)),\n ).length,\n ),\n color: \"outline\" as const,\n },\n })),\n }));\n\n sidebar.unshift({\n type: \"doc\" as const,\n id: joinUrl(navigationId),\n label: \"Overview\",\n badge: { label: String(items.length), color: \"outline\" as const },\n });\n\n return sidebar;\n },\n getRoutes: () =>\n Object.entries(paths).map(([path, tag]) => ({\n path,\n element: (\n <Catalog\n label={label}\n categoryLabel={tag}\n items={items}\n filterCatalogItems={filterCatalogItems}\n categories={categories}\n />\n ),\n })),\n };\n};\n"],"names":["Catalog","items","filterCatalogItems","label","categoryLabel","auth","useAuthState","match","useMatch","activeCategory","catalogItems","useSuspenseQuery","dataSet","jsxs","jsx","Helmet","Heading","api","t","getKey","Link","joinUrl","Markdown","category","tag","slugify","apiCatalogPlugin","navigationId","categories","paths","currentPath","path","matchPath","sidebar","c"],"mappings":"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"}
@@ -53,7 +53,7 @@ const P = (e) => ({
53
53
  const u = {
54
54
  path: r,
55
55
  lazy: async () => {
56
- const { MdxPage: p } = await import("./MdxPage-JscVnWM8.js"), { default: f, ...l } = await i();
56
+ const { MdxPage: p } = await import("./MdxPage-Vw_dc9Yz.js"), { default: f, ...l } = await i();
57
57
  return {
58
58
  element: /* @__PURE__ */ d.jsx(
59
59
  p,
@@ -3,7 +3,7 @@ import "lucide-react";
3
3
  import "./chunk-HA7DTUK3-C4gP41vD.js";
4
4
  import "./hook-CqpVYDqN.js";
5
5
  import "./ui/Button.js";
6
- import { U as a, o as e } from "./index-DsdAaiwx.js";
6
+ import { U as a, o as e } from "./index-C1OzjGUK.js";
7
7
  export {
8
8
  a as UNTAGGED_PATH,
9
9
  e as openApiPlugin
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "zudoku",
3
- "version": "0.39.2",
3
+ "version": "0.39.4",
4
4
  "type": "module",
5
5
  "homepage": "https://zudoku.dev",
6
6
  "repository": {
@@ -177,7 +177,7 @@
177
177
  "dotenv": "16.4.7",
178
178
  "embla-carousel-react": "8.5.2",
179
179
  "esm-loader-css": "^1.0.3",
180
- "estree-util-value-to-estree": "3.3.2",
180
+ "estree-util-value-to-estree": "3.3.3",
181
181
  "express": "4.21.2",
182
182
  "fast-equals": "5.2.2",
183
183
  "glob": "11.0.1",
@@ -259,7 +259,7 @@
259
259
  "@types/unist": "^3.0.3",
260
260
  "@types/yargs": "17.0.33",
261
261
  "@vitest/coverage-v8": "3.0.5",
262
- "happy-dom": "17.1.1",
262
+ "happy-dom": "17.4.4",
263
263
  "mdast-util-mdx": "3.0.0",
264
264
  "react": "19.0.0",
265
265
  "react-dom": "19.0.0",
@@ -0,0 +1,136 @@
1
+ import { ExternalLink as ExternalLinkIcon } from "lucide-react";
2
+ import type { CSSProperties, ReactNode } from "react";
3
+ import type { FooterSocialIcons } from "../../config/validators/common.js";
4
+ import { cn } from "../util/cn.js";
5
+ import { AnchorLink } from "./AnchorLink.js";
6
+ import { useZudoku } from "./index.js";
7
+ import { Slotlet } from "./SlotletProvider.js";
8
+
9
+ const SocialIcon = ({
10
+ icon,
11
+ }: {
12
+ icon: (typeof FooterSocialIcons)[number] | ReactNode;
13
+ }) => {
14
+ if (typeof icon === "string") {
15
+ return (
16
+ <img
17
+ src={`https://cdn.simpleicons.org/${icon}/000000/ffffff`}
18
+ className="size-5"
19
+ alt={icon}
20
+ />
21
+ );
22
+ }
23
+ return icon;
24
+ };
25
+
26
+ const isExternalUrl = (href: string) => /^https?:/.test(href);
27
+
28
+ export const Footer = () => {
29
+ const { page } = useZudoku();
30
+ const footer = page?.footer;
31
+
32
+ if (!footer) return null;
33
+
34
+ return (
35
+ <footer className="border-t bg-background">
36
+ <div className="mx-auto max-w-screen-2xl px-4 lg:px-8 py-8 pt-20">
37
+ <div
38
+ className={cn("flex flex-row gap-8", {
39
+ "justify-center": !footer.position || footer.position === "center",
40
+ "justify-start": footer.position === "start",
41
+ "justify-end": footer.position === "end",
42
+ })}
43
+ >
44
+ <Slotlet name="footer-before" />
45
+ {footer.columns && (
46
+ <div
47
+ className="w-full md:max-w-screen-md grid grid-cols-[1fr_1fr] gap-8 md:grid-cols-[repeat(var(--columns),minmax(0,1fr))]"
48
+ style={{ "--columns": footer.columns.length } as CSSProperties}
49
+ >
50
+ {footer.columns.map((column) => (
51
+ <div
52
+ className={cn({
53
+ "justify-self-center":
54
+ !column.position || column.position === "center",
55
+ "justify-self-start": column.position === "start",
56
+ "justify-self-end": column.position === "end",
57
+ })}
58
+ key={column.title}
59
+ >
60
+ <span className="text-sm font-semibold">{column.title}</span>
61
+ <ul className="mt-4 space-y-2">
62
+ {column.links.map((link) => {
63
+ const className =
64
+ "flex flex-row gap-1 items-center text-sm text-muted-foreground hover:text-accent-foreground";
65
+
66
+ return (
67
+ <li key={link.href}>
68
+ {isExternalUrl(link.href) ? (
69
+ <a
70
+ href={link.href}
71
+ target="_blank"
72
+ rel="noopener noreferrer"
73
+ className={className}
74
+ >
75
+ <span>{link.label}</span>
76
+ <ExternalLinkIcon size={12} />
77
+ </a>
78
+ ) : (
79
+ <AnchorLink to={link.href} className={className}>
80
+ <span>{link.label}</span>
81
+ </AnchorLink>
82
+ )}
83
+ </li>
84
+ );
85
+ })}
86
+ </ul>
87
+ </div>
88
+ ))}
89
+ </div>
90
+ )}
91
+ <Slotlet name="footer-after" />
92
+ </div>
93
+ <div
94
+ className={cn(
95
+ "flex items-center justify-between",
96
+ footer.columns && "border-t mt-8 pt-8",
97
+ )}
98
+ >
99
+ {footer.logo && (
100
+ <>
101
+ <img
102
+ src={footer.logo.src.light}
103
+ alt={footer.logo.alt}
104
+ className="w-8 dark:hidden"
105
+ style={{ width: footer.logo.width }}
106
+ />
107
+ <img
108
+ src={footer.logo.src.dark}
109
+ alt={footer.logo.alt}
110
+ className="w-8 hidden dark:block"
111
+ style={{ width: footer.logo.width }}
112
+ />
113
+ </>
114
+ )}
115
+ {footer.copyright && (
116
+ <p className="text-sm text-muted-foreground">{footer.copyright}</p>
117
+ )}
118
+ <div className="flex items-center gap-2">
119
+ {footer.social?.map((social) => (
120
+ <a
121
+ key={social.href}
122
+ href={social.href}
123
+ target="_blank"
124
+ rel="noopener noreferrer"
125
+ className="w-auto gap-2 flex text-muted-foreground hover:text-accent-foreground"
126
+ >
127
+ <SocialIcon icon={social.icon} />
128
+ {social.label}
129
+ </a>
130
+ ))}
131
+ </div>
132
+ </div>
133
+ </div>
134
+ </footer>
135
+ );
136
+ };
@@ -5,6 +5,7 @@ import { joinUrl } from "../util/joinUrl.js";
5
5
  import { useScrollToAnchor } from "../util/useScrollToAnchor.js";
6
6
  import { useScrollToTop } from "../util/useScrollToTop.js";
7
7
  import { useZudoku } from "./context/ZudokuContext.js";
8
+ import { Footer } from "./Footer.js";
8
9
  import { Header } from "./Header.js";
9
10
  import { Main } from "./Main.js";
10
11
  import { Slotlet } from "./SlotletProvider.js";
@@ -58,6 +59,7 @@ export const Layout = ({ children }: { children?: ReactNode }) => {
58
59
  <Main>{children ?? <Outlet />}</Main>
59
60
  </Suspense>
60
61
  </div>
62
+ <Footer />
61
63
  </>
62
64
  );
63
65
  };
@@ -6,9 +6,11 @@ import { Button } from "./index.js";
6
6
  export const Pagination = ({
7
7
  prev,
8
8
  next,
9
+ className,
9
10
  }: {
10
11
  prev: { to: string; label: string } | undefined;
11
12
  next: { to: string; label: string } | undefined;
13
+ className?: string;
12
14
  }) => {
13
15
  const linkClass =
14
16
  "group transition-all p-5 space-x-1 transition-all hover:text-foreground";
@@ -16,8 +18,9 @@ export const Pagination = ({
16
18
  return (
17
19
  <div
18
20
  className={cn(
19
- "flex my-8 -mx-4 text-muted-foreground font-semibold",
21
+ "flex -mx-4 text-muted-foreground font-semibold",
20
22
  prev ? "justify-between" : "justify-end",
23
+ className,
21
24
  )}
22
25
  >
23
26
  {prev && (
@@ -1,9 +1,6 @@
1
- import { useEffect, useRef } from "react";
2
-
3
1
  import { VisuallyHidden } from "@radix-ui/react-visually-hidden";
4
2
  import type { SidebarItem as SidebarItemType } from "../../../config/validators/SidebarSchema.js";
5
3
  import { DrawerContent, DrawerTitle } from "../../ui/Drawer.js";
6
- import { scrollIntoViewIfNeeded } from "../../util/scrollIntoViewIfNeeded.js";
7
4
  import { Slotlet } from "../SlotletProvider.js";
8
5
  import { SidebarItem } from "./SidebarItem.js";
9
6
  import { SidebarWrapper } from "./SidebarWrapper.js";
@@ -14,40 +11,31 @@ export const Sidebar = ({
14
11
  }: {
15
12
  onRequestClose?: () => void;
16
13
  sidebar: SidebarItemType[];
17
- }) => {
18
- const navRef = useRef<HTMLDivElement>(null);
19
-
20
- useEffect(() => {
21
- const active = navRef.current?.querySelector('[aria-current="page"]');
22
- scrollIntoViewIfNeeded(active ?? null);
23
- }, []);
24
-
25
- return (
26
- <>
27
- <SidebarWrapper ref={navRef}>
28
- <Slotlet name="zudoku-before-navigation" />
14
+ }) => (
15
+ <>
16
+ <SidebarWrapper>
17
+ <Slotlet name="zudoku-before-navigation" />
18
+ {sidebar.map((item) => (
19
+ <SidebarItem key={item.label} item={item} />
20
+ ))}
21
+ <Slotlet name="zudoku-after-navigation" />
22
+ </SidebarWrapper>
23
+ <DrawerContent
24
+ className="lg:hidden h-[100dvh] left-0 w-[320px] rounded-none"
25
+ aria-describedby={undefined}
26
+ >
27
+ <div className="p-4 overflow-y-auto overscroll-none">
28
+ <VisuallyHidden>
29
+ <DrawerTitle>Sidebar</DrawerTitle>
30
+ </VisuallyHidden>
29
31
  {sidebar.map((item) => (
30
- <SidebarItem key={item.label} item={item} />
32
+ <SidebarItem
33
+ key={item.label}
34
+ item={item}
35
+ onRequestClose={onRequestClose}
36
+ />
31
37
  ))}
32
- <Slotlet name="zudoku-after-navigation" />
33
- </SidebarWrapper>
34
- <DrawerContent
35
- className="lg:hidden h-[100dvh] left-0 w-[320px] rounded-none"
36
- aria-describedby={undefined}
37
- >
38
- <div className="p-4 overflow-y-auto overscroll-none">
39
- <VisuallyHidden>
40
- <DrawerTitle>Sidebar</DrawerTitle>
41
- </VisuallyHidden>
42
- {sidebar.map((item) => (
43
- <SidebarItem
44
- key={item.label}
45
- item={item}
46
- onRequestClose={onRequestClose}
47
- />
48
- ))}
49
- </div>
50
- </DrawerContent>
51
- </>
52
- );
53
- };
38
+ </div>
39
+ </DrawerContent>
40
+ </>
41
+ );
@@ -1,30 +1,37 @@
1
- import { type PropsWithChildren, type Ref } from "react";
1
+ import { useEffect, useRef, type PropsWithChildren } from "react";
2
2
  import { cn } from "../../util/cn.js";
3
+ import { scrollIntoViewIfNeeded } from "../../util/scrollIntoViewIfNeeded.js";
3
4
  import { useZudoku } from "../context/ZudokuContext.js";
4
5
  import { PoweredByZudoku } from "./PoweredByZudoku.js";
5
6
 
6
7
  export const SidebarWrapper = ({
7
8
  children,
8
9
  className,
9
- ref,
10
10
  }: PropsWithChildren<{
11
11
  className?: string;
12
- ref?: Ref<HTMLDivElement>;
13
12
  }>) => {
14
13
  const { options } = useZudoku();
14
+ const navRef = useRef<HTMLDivElement>(null);
15
+
16
+ useEffect(() => {
17
+ const active = navRef.current?.querySelector('[aria-current="page"]');
18
+ scrollIntoViewIfNeeded(active ?? null);
19
+ }, []);
15
20
 
16
21
  return (
17
22
  <div className="grid sticky top-[--header-height] lg:h-[calc(100vh-var(--header-height))] grid-rows-[1fr_min-content] border-r">
18
23
  <nav
24
+ ref={navRef}
19
25
  className={cn(
20
26
  "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",
27
+ "-mx-[--padding-nav-item] pb-3 pt-[--padding-content-top] scroll-pt-2 gap-1",
28
+ // Revert the padding/margin on the first child
29
+ "-mt-2.5",
22
30
  className,
23
31
  )}
24
32
  style={{
25
33
  maskImage: `linear-gradient(180deg, transparent 1%, rgba(0, 0, 0, 1) 20px, rgba(0, 0, 0, 1) 90%, transparent 99%)`,
26
34
  }}
27
- ref={ref}
28
35
  >
29
36
  {children}
30
37
  </nav>
@@ -2,7 +2,11 @@ import type { QueryClient } from "@tanstack/react-query";
2
2
  import { createNanoEvents } from "nanoevents";
3
3
  import type { ReactNode } from "react";
4
4
  import type { Location } from "react-router";
5
- import type { TopNavigationItem } from "../../config/validators/common.js";
5
+ import type { z } from "zod";
6
+ import type {
7
+ FooterSchema,
8
+ TopNavigationItem,
9
+ } from "../../config/validators/common.js";
6
10
  import type { SidebarConfig } from "../../config/validators/SidebarSchema.js";
7
11
  import type { AuthenticationProvider } from "../authentication/authentication.js";
8
12
  import { type AuthState, useAuthState } from "../authentication/state.js";
@@ -59,7 +63,7 @@ type Page = Partial<{
59
63
  light: string;
60
64
  dark: string;
61
65
  };
62
- width?: string;
66
+ width?: string | number;
63
67
  alt?: string;
64
68
  };
65
69
  banner?: {
@@ -67,6 +71,7 @@ type Page = Partial<{
67
71
  color?: "note" | "tip" | "info" | "caution" | "danger" | (string & {});
68
72
  dismissible?: boolean;
69
73
  };
74
+ footer?: z.infer<typeof FooterSchema>;
70
75
  }>;
71
76
 
72
77
  export type ZudokuContextOptions = {
@@ -29,10 +29,7 @@ export const Catalog = ({
29
29
  const dataSet = activeCategory ? { "data-pagefind-ignore": "all" } : {};
30
30
 
31
31
  return (
32
- <section
33
- className="pt-[--padding-content-top] pb-[--padding-content-bottom]"
34
- {...dataSet}
35
- >
32
+ <section className="pt-[--padding-content-top]" {...dataSet}>
36
33
  <Helmet>
37
34
  <title>
38
35
  {categoryLabel ? `${categoryLabel} - ` : ""}
@@ -96,7 +96,7 @@ export const MdxPage = ({
96
96
  <div
97
97
  className={cn(
98
98
  ProseClasses,
99
- "max-w-full xl:w-full xl:max-w-3xl flex-1 flex-shrink pt-[--padding-content-top] pb-[--padding-content-bottom]",
99
+ "max-w-full xl:w-full xl:max-w-3xl flex-1 flex-shrink pt-[--padding-content-top]",
100
100
  )}
101
101
  >
102
102
  {(category || title) && (
@@ -114,10 +114,11 @@ export const MdxPage = ({
114
114
  />
115
115
  {!hidePager && (
116
116
  <>
117
- <hr />
117
+ <hr className="my-10" />
118
118
  <Pagination
119
119
  prev={prev ? { to: prev.id, label: prev.label } : undefined}
120
120
  next={next ? { to: next.id, label: next.label } : undefined}
121
+ className="mb-4"
121
122
  />
122
123
  </>
123
124
  )}
@@ -308,13 +308,15 @@ export const OperationList = ({
308
308
  {/* px, -mx is so that `content-visibility` doesn't cut off overflown heading anchor links '#' */}
309
309
  <div className="px-6 mt-6 -mx-6 [content-visibility:auto]">
310
310
  {operations.map((fragment) => (
311
- <OperationListItem
312
- serverUrl={selectedServer}
313
- key={fragment.slug}
314
- operationFragment={fragment}
315
- />
311
+ <div key={fragment.slug}>
312
+ <OperationListItem
313
+ serverUrl={selectedServer}
314
+ operationFragment={fragment}
315
+ />
316
+ <hr className="my-10" />
317
+ </div>
316
318
  ))}
317
- <Pagination {...paginationProps} />
319
+ <Pagination className="mb-4" {...paginationProps} />
318
320
  </div>
319
321
  </div>
320
322
  );
@@ -35,7 +35,7 @@ export const OperationListItem = ({
35
35
  const [selectedResponse, setSelectedResponse] = useState(first?.statusCode);
36
36
 
37
37
  return (
38
- <div className="border-b-2 mb-16 pb-16">
38
+ <div>
39
39
  {operation.deprecated && (
40
40
  <Badge variant="muted" className="text-xs mb-4">
41
41
  deprecated
@@ -52,7 +52,7 @@ export const OperationListItem = ({
52
52
  level={2}
53
53
  id={operation.slug}
54
54
  registerSidebarAnchor
55
- className="break-all"
55
+ className="break-all col-span-full"
56
56
  >
57
57
  {operation.summary}
58
58
  </Heading>