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.
- package/dist/config/validators/common.d.ts +37 -37
- package/dist/config/validators/common.js +4 -1
- package/dist/config/validators/common.js.map +1 -1
- package/dist/config/validators/validate.d.ts +14 -14
- package/dist/lib/plugins/openapi/OperationList.js +7 -33
- package/dist/lib/plugins/openapi/OperationList.js.map +1 -1
- package/dist/lib/plugins/openapi/Route.d.ts +4 -2
- package/dist/lib/plugins/openapi/Route.js +25 -2
- package/dist/lib/plugins/openapi/Route.js.map +1 -1
- package/dist/lib/plugins/openapi/context.d.ts +3 -3
- package/dist/lib/plugins/openapi/index.js +12 -12
- package/dist/lib/plugins/openapi/index.js.map +1 -1
- package/dist/lib/plugins/openapi/interfaces.d.ts +19 -0
- package/dist/lib/plugins/openapi/util/sanitizeMarkdownForMetatag.d.ts +1 -0
- package/dist/lib/plugins/openapi/util/sanitizeMarkdownForMetatag.js +27 -0
- package/dist/lib/plugins/openapi/util/sanitizeMarkdownForMetatag.js.map +1 -0
- package/dist/vite/config.js +2 -1
- package/dist/vite/config.js.map +1 -1
- package/dist/vite/plugin-api.js +35 -14
- package/dist/vite/plugin-api.js.map +1 -1
- package/lib/{OperationList-C7ac3kR5.js → OperationList-wvY-BrxS.js} +1173 -1157
- package/lib/OperationList-wvY-BrxS.js.map +1 -0
- package/lib/Route-C8nwd9A2.js +37 -0
- package/lib/Route-C8nwd9A2.js.map +1 -0
- package/lib/context-h_UkBLvr.js.map +1 -1
- package/lib/{index-C8ubT49C.js → index-C_ul-2fb.js} +442 -437
- package/lib/{index-C8ubT49C.js.map → index-C_ul-2fb.js.map} +1 -1
- package/lib/zudoku.plugin-openapi.js +1 -1
- package/package.json +1 -1
- package/src/lib/plugins/openapi/OperationList.tsx +42 -50
- package/src/lib/plugins/openapi/Route.tsx +45 -9
- package/src/lib/plugins/openapi/context.tsx +2 -2
- package/src/lib/plugins/openapi/index.tsx +35 -28
- package/src/lib/plugins/openapi/interfaces.ts +22 -1
- package/src/lib/plugins/openapi/util/sanitizeMarkdownForMetatag.tsx +32 -0
- package/lib/OperationList-C7ac3kR5.js.map +0 -1
- package/lib/Route-C9cYcP-j.js +0 -11
- package/lib/Route-C9cYcP-j.js.map +0 -1
package/package.json
CHANGED
|
@@ -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: 
|
|
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, "<")
|
|
138
|
-
.replace(/>/g, ">")
|
|
139
|
-
.replace(/"/g, """)
|
|
140
|
-
.replace(/'/g, "'");
|
|
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
|
-
?
|
|
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
|
-
<
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
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
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
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 {
|
|
2
|
+
import { OasPluginContext } from "./interfaces.js";
|
|
3
3
|
|
|
4
|
-
const OasContext = createContext<{ config:
|
|
4
|
+
const OasContext = createContext<{ config: OasPluginContext } | undefined>(
|
|
5
5
|
undefined,
|
|
6
6
|
);
|
|
7
7
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { matchPath
|
|
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
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
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
|
-
|
|
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
|
-
| {
|
|
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: 
|
|
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, "&")
|
|
27
|
+
.replace(/</g, "<")
|
|
28
|
+
.replace(/>/g, ">")
|
|
29
|
+
.replace(/"/g, """)
|
|
30
|
+
.replace(/'/g, "'")
|
|
31
|
+
);
|
|
32
|
+
}
|