zudoku 0.23.4 → 0.23.5

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 (38) hide show
  1. package/dist/config/validators/common.d.ts +37 -37
  2. package/dist/config/validators/common.js +4 -1
  3. package/dist/config/validators/common.js.map +1 -1
  4. package/dist/config/validators/validate.d.ts +14 -14
  5. package/dist/lib/plugins/openapi/OperationList.js +7 -33
  6. package/dist/lib/plugins/openapi/OperationList.js.map +1 -1
  7. package/dist/lib/plugins/openapi/Route.d.ts +4 -2
  8. package/dist/lib/plugins/openapi/Route.js +25 -2
  9. package/dist/lib/plugins/openapi/Route.js.map +1 -1
  10. package/dist/lib/plugins/openapi/context.d.ts +3 -3
  11. package/dist/lib/plugins/openapi/index.js +12 -12
  12. package/dist/lib/plugins/openapi/index.js.map +1 -1
  13. package/dist/lib/plugins/openapi/interfaces.d.ts +19 -0
  14. package/dist/lib/plugins/openapi/util/sanitizeMarkdownForMetatag.d.ts +1 -0
  15. package/dist/lib/plugins/openapi/util/sanitizeMarkdownForMetatag.js +27 -0
  16. package/dist/lib/plugins/openapi/util/sanitizeMarkdownForMetatag.js.map +1 -0
  17. package/dist/vite/config.js +2 -1
  18. package/dist/vite/config.js.map +1 -1
  19. package/dist/vite/plugin-api.js +35 -14
  20. package/dist/vite/plugin-api.js.map +1 -1
  21. package/lib/{OperationList-C7ac3kR5.js → OperationList-wvY-BrxS.js} +1173 -1157
  22. package/lib/OperationList-wvY-BrxS.js.map +1 -0
  23. package/lib/Route-C8nwd9A2.js +37 -0
  24. package/lib/Route-C8nwd9A2.js.map +1 -0
  25. package/lib/context-h_UkBLvr.js.map +1 -1
  26. package/lib/{index-C8ubT49C.js → index-C_ul-2fb.js} +442 -437
  27. package/lib/{index-C8ubT49C.js.map → index-C_ul-2fb.js.map} +1 -1
  28. package/lib/zudoku.plugin-openapi.js +1 -1
  29. package/package.json +1 -1
  30. package/src/lib/plugins/openapi/OperationList.tsx +42 -50
  31. package/src/lib/plugins/openapi/Route.tsx +45 -9
  32. package/src/lib/plugins/openapi/context.tsx +2 -2
  33. package/src/lib/plugins/openapi/index.tsx +35 -28
  34. package/src/lib/plugins/openapi/interfaces.ts +22 -1
  35. package/src/lib/plugins/openapi/util/sanitizeMarkdownForMetatag.tsx +32 -0
  36. package/lib/OperationList-C7ac3kR5.js.map +0 -1
  37. package/lib/Route-C9cYcP-j.js +0 -11
  38. package/lib/Route-C9cYcP-j.js.map +0 -1
@@ -1,6 +1,6 @@
1
1
  import "./jsx-runtime-Dx-03ztt.js";
2
2
  import "./chunk-D52XG6IA-Dl7HLe6j.js";
3
- import { o as a } from "./index-C8ubT49C.js";
3
+ import { o as a } from "./index-C_ul-2fb.js";
4
4
  import "./utils-B4O1uet5.js";
5
5
  import "lucide-react";
6
6
  import "./hook-DgGeo5iL.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "zudoku",
3
- "version": "0.23.4",
3
+ "version": "0.23.5",
4
4
  "type": "module",
5
5
  "homepage": "https://zudoku.dev",
6
6
  "repository": {
@@ -1,6 +1,14 @@
1
1
  import { ResultOf } from "@graphql-typed-document-node/core";
2
2
  import { useSuspenseQuery } from "@tanstack/react-query";
3
3
  import { Helmet } from "@zudoku/react-helmet-async";
4
+ import { useNavigate } from "react-router";
5
+ import {
6
+ Select,
7
+ SelectContent,
8
+ SelectItem,
9
+ SelectTrigger,
10
+ SelectValue,
11
+ } from "zudoku/ui/Select.js";
4
12
  import { CategoryHeading } from "../../components/CategoryHeading.js";
5
13
  import { Heading } from "../../components/Heading.js";
6
14
  import { Markdown, ProseClasses } from "../../components/Markdown.js";
@@ -11,6 +19,7 @@ import StaggeredRender from "./StaggeredRender.js";
11
19
  import { useCreateQuery } from "./client/useCreateQuery.js";
12
20
  import { useOasConfig } from "./context.js";
13
21
  import { graphql } from "./graphql/index.js";
22
+ import { sanitizeMarkdownForMetatag } from "./util/sanitizeMarkdownForMetatag.js";
14
23
 
15
24
  export const OperationsFragment = graphql(/* GraphQL */ `
16
25
  fragment OperationsFragment on OperationItem {
@@ -98,63 +107,24 @@ const AllOperationsQuery = graphql(/* GraphQL */ `
98
107
  }
99
108
  `);
100
109
 
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, "&")
137
- .replace(/</g, "&lt;")
138
- .replace(/>/g, "&gt;")
139
- .replace(/"/g, "&quot;")
140
- .replace(/'/g, "&#039;");
141
- }
142
-
143
110
  export const OperationList = () => {
144
- const { input, type } = useOasConfig();
111
+ const { input, type, versions, version } = useOasConfig();
145
112
  const query = useCreateQuery(AllOperationsQuery, { input, type });
146
113
  const result = useSuspenseQuery(query);
147
114
  const title = result.data.schema.title;
148
115
  const summary = result.data.schema.summary;
149
116
  const description = result.data.schema.description;
117
+ const navigate = useNavigate();
118
+
150
119
  // The summary property is preferable here as it is a short description of
151
120
  // the API, whereas the description property is typically longer and supports
152
121
  // commonmark formatting, making it ill-suited for use in the meta description
153
122
  const metaDescription = summary
154
123
  ? summary
155
124
  : description
156
- ? cleanDescription(description)
125
+ ? sanitizeMarkdownForMetatag(description)
157
126
  : undefined;
127
+
158
128
  return (
159
129
  <div className="pt-[--padding-content-top]">
160
130
  <Helmet>
@@ -166,17 +136,39 @@ export const OperationList = () => {
166
136
  <div
167
137
  className={cn(ProseClasses, "mb-16 max-w-full prose-img:max-w-prose")}
168
138
  >
169
- <CategoryHeading>Overview</CategoryHeading>
170
- <Heading level={1} id="description" registerSidebarAnchor>
171
- {title}
172
- </Heading>
139
+ <div className="flex">
140
+ <div className="flex-1">
141
+ <CategoryHeading>Overview</CategoryHeading>
142
+ <Heading level={1} id="description" registerSidebarAnchor>
143
+ {title}
144
+ </Heading>
145
+ </div>
146
+ <div>
147
+ {Object.entries(versions).length > 1 && (
148
+ <Select
149
+ onValueChange={(version) => navigate(versions[version]!)}
150
+ defaultValue={version}
151
+ >
152
+ <SelectTrigger className="w-[180px]">
153
+ <SelectValue placeholder="Select version" />
154
+ </SelectTrigger>
155
+ <SelectContent>
156
+ {Object.entries(versions).map(([version]) => (
157
+ <SelectItem key={version} value={version}>
158
+ {version}
159
+ </SelectItem>
160
+ ))}
161
+ </SelectContent>
162
+ </Select>
163
+ )}
164
+ </div>
165
+ </div>
173
166
  <Markdown content={result.data.schema.description ?? ""} />
174
167
  </div>
175
168
  <hr />
176
- <div className="my-4 flex justify-end">
169
+ <div className="my-4 flex items-center justify-end gap-4">
177
170
  <Endpoint />
178
171
  </div>
179
-
180
172
  {result.data.schema.tags
181
173
  .filter((tag) => tag.operations.length > 0)
182
174
  .map((tag) => (
@@ -1,19 +1,55 @@
1
- import { Outlet } from "react-router";
1
+ import { Outlet, useParams } from "react-router";
2
+ import { joinPath } from "../../util/joinPath.js";
2
3
  import type { GraphQLClient } from "./client/GraphQLClient.js";
3
4
  import { GraphQLProvider } from "./client/GraphQLContext.js";
4
5
  import { OasConfigProvider } from "./context.js";
5
- import { OasPluginConfig } from "./interfaces.js";
6
+ import { type OasPluginConfig } from "./interfaces.js";
6
7
 
7
8
  export const OpenApiRoute = ({
9
+ basePath,
10
+ versions,
8
11
  config,
9
12
  client,
10
13
  }: {
14
+ basePath: string;
15
+ versions: string[];
11
16
  config: OasPluginConfig;
12
17
  client: GraphQLClient;
13
- }) => (
14
- <OasConfigProvider value={{ config }}>
15
- <GraphQLProvider client={client}>
16
- <Outlet />
17
- </GraphQLProvider>
18
- </OasConfigProvider>
19
- );
18
+ }) => {
19
+ const { version } = useParams<"version">();
20
+
21
+ const input =
22
+ config.type === "file"
23
+ ? {
24
+ type: config.type,
25
+ input: version
26
+ ? config.input[version]!
27
+ : Object.values(config.input).at(0)!,
28
+ }
29
+ : { type: config.type, input: config.input };
30
+
31
+ const currentVersion = version ?? versions.at(0);
32
+
33
+ if (!currentVersion) {
34
+ throw new Error("No version found");
35
+ }
36
+
37
+ return (
38
+ <OasConfigProvider
39
+ value={{
40
+ config: {
41
+ ...config,
42
+ version: currentVersion,
43
+ versions: Object.fromEntries(
44
+ versions.map((version) => [version, joinPath(basePath, version)]),
45
+ ),
46
+ ...input,
47
+ },
48
+ }}
49
+ >
50
+ <GraphQLProvider client={client}>
51
+ <Outlet />
52
+ </GraphQLProvider>
53
+ </OasConfigProvider>
54
+ );
55
+ };
@@ -1,7 +1,7 @@
1
1
  import { createContext, useContext } from "react";
2
- import { OasPluginConfig } from "./interfaces.js";
2
+ import { OasPluginContext } from "./interfaces.js";
3
3
 
4
- const OasContext = createContext<{ config: OasPluginConfig } | undefined>(
4
+ const OasContext = createContext<{ config: OasPluginContext } | undefined>(
5
5
  undefined,
6
6
  );
7
7
 
@@ -1,4 +1,4 @@
1
- import { matchPath, type RouteObject } from "react-router";
1
+ import { matchPath } from "react-router";
2
2
  import { type ZudokuPlugin } from "../../core/plugins.js";
3
3
  import { graphql } from "./graphql/index.js";
4
4
 
@@ -52,6 +52,8 @@ export type OpenApiPluginOptions = OasPluginConfig & InternalOasPluginConfig;
52
52
  export const openApiPlugin = (config: OpenApiPluginOptions): ZudokuPlugin => {
53
53
  const basePath = joinPath(config.navigationId ?? "/reference");
54
54
 
55
+ const versions = config.type === "file" ? Object.keys(config.input) : [];
56
+
55
57
  const client = new GraphQLClient(config);
56
58
 
57
59
  return {
@@ -125,9 +127,14 @@ export const openApiPlugin = (config: OpenApiPluginOptions): ZudokuPlugin => {
125
127
  }
126
128
 
127
129
  try {
130
+ const version =
131
+ versions.find((v) => path === joinPath(basePath, v)) ??
132
+ Object.keys(config.input).at(0);
133
+
128
134
  const data = await client.fetch(GetCategoriesQuery, {
129
135
  type: config.type,
130
- input: config.input,
136
+ input: config.type === "file" ? config.input[version!] : config.input,
137
+ version,
131
138
  });
132
139
 
133
140
  const categories = data.schema.tags
@@ -159,32 +166,32 @@ export const openApiPlugin = (config: OpenApiPluginOptions): ZudokuPlugin => {
159
166
  return [];
160
167
  }
161
168
  },
162
- getRoutes: () =>
163
- [
164
- {
165
- async lazy() {
166
- const { OpenApiRoute } = await import("./Route.js");
167
- return {
168
- element: <OpenApiRoute client={client} config={config} />,
169
- };
170
- },
171
- children: [
172
- {
173
- path: basePath,
174
- children: [
175
- {
176
- index: true,
177
- async lazy() {
178
- const { OperationList } = await import(
179
- "./OperationList.js"
180
- );
181
- return { element: <OperationList /> };
182
- },
183
- },
184
- ],
185
- },
186
- ],
169
+ getRoutes: () => [
170
+ {
171
+ path: basePath + "/:version?",
172
+ async lazy() {
173
+ const { OpenApiRoute } = await import("./Route.js");
174
+ return {
175
+ element: (
176
+ <OpenApiRoute
177
+ basePath={basePath}
178
+ versions={versions}
179
+ client={client}
180
+ config={config}
181
+ />
182
+ ),
183
+ };
187
184
  },
188
- ] satisfies RouteObject[],
185
+ children: [
186
+ {
187
+ index: true,
188
+ async lazy() {
189
+ const { OperationList } = await import("./OperationList.js");
190
+ return { element: <OperationList /> };
191
+ },
192
+ },
193
+ ],
194
+ },
195
+ ],
189
196
  };
190
197
  };
@@ -1,6 +1,19 @@
1
1
  type OasSource =
2
2
  | { type: "url"; input: string }
3
- | { type: "file"; input: () => Promise<unknown> }
3
+ | {
4
+ type: "file";
5
+ input: {
6
+ [version: string]: () => Promise<unknown>;
7
+ };
8
+ }
9
+ | { type: "raw"; input: string };
10
+
11
+ export type ContextOasSource =
12
+ | { type: "url"; input: string }
13
+ | {
14
+ type: "file";
15
+ input: () => Promise<unknown>;
16
+ }
4
17
  | { type: "raw"; input: string };
5
18
 
6
19
  export type OasPluginConfig = {
@@ -8,3 +21,11 @@ export type OasPluginConfig = {
8
21
  navigationId?: string;
9
22
  skipPreload?: boolean;
10
23
  } & OasSource;
24
+
25
+ export type OasPluginContext = {
26
+ server?: string;
27
+ navigationId?: string;
28
+ skipPreload?: boolean;
29
+ version: string;
30
+ versions: Record<string, string>;
31
+ } & ContextOasSource;
@@ -0,0 +1,32 @@
1
+ export function sanitizeMarkdownForMetatag(
2
+ description: string,
3
+ maxLength: number = 160,
4
+ ): string {
5
+ if (!description) {
6
+ return "";
7
+ }
8
+
9
+ return (
10
+ description
11
+ // Replace Markdown links [text](url) with just "text"
12
+ .replace(/\[([^\]]+)\]\([^)]+\)/g, "$1")
13
+ // Remove Markdown image syntax: ![alt](url)
14
+ .replace(/!\[.*?\]\(.*?\)/g, "")
15
+ // Remove other Markdown syntax (e.g., **bold**, _italic_, `code`)
16
+ .replace(/[_*`~]/g, "")
17
+ // Remove headings (# Heading), blockquotes (> Quote), and horizontal rules (--- or ***)
18
+ .replace(/^(?:>|\s*#+|-{3,}|\*{3,})/gm, "")
19
+ // Remove any remaining formatting characters
20
+ .replace(/[|>{}[\]]/g, "")
21
+ // Collapse multiple spaces and trim the text
22
+ .replace(/\s+/g, " ")
23
+ .trim()
24
+ // Limit to the specified maximum length
25
+ .substring(0, maxLength)
26
+ .replace(/&/g, "&amp;")
27
+ .replace(/</g, "&lt;")
28
+ .replace(/>/g, "&gt;")
29
+ .replace(/"/g, "&quot;")
30
+ .replace(/'/g, "&#039;")
31
+ );
32
+ }