zudoku 0.3.0-dev.98 → 0.3.0
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/app/demo.js +5 -9
- package/dist/app/demo.js.map +1 -1
- package/dist/app/main.js +9 -7
- package/dist/app/main.js.map +1 -1
- package/dist/app/standalone.js +12 -8
- package/dist/app/standalone.js.map +1 -1
- package/dist/cli/dev/handler.js +1 -1
- package/dist/config/validators/SidebarSchema.js +9 -4
- package/dist/config/validators/SidebarSchema.js.map +1 -1
- package/dist/lib/components/ClientOnly.d.ts +3 -0
- package/dist/lib/components/ClientOnly.js +7 -0
- package/dist/lib/components/ClientOnly.js.map +1 -0
- package/dist/lib/components/Header.js +3 -2
- package/dist/lib/components/Header.js.map +1 -1
- package/dist/lib/components/Markdown.js +3 -2
- package/dist/lib/components/Markdown.js.map +1 -1
- package/dist/lib/components/Search.js +5 -8
- package/dist/lib/components/Search.js.map +1 -1
- package/dist/lib/components/SlotletProvider.d.ts +2 -2
- package/dist/lib/components/SlotletProvider.js +7 -2
- package/dist/lib/components/SlotletProvider.js.map +1 -1
- package/dist/lib/components/navigation/SidebarCategory.js +6 -2
- package/dist/lib/components/navigation/SidebarCategory.js.map +1 -1
- package/dist/lib/components/navigation/SidebarItem.js +3 -2
- package/dist/lib/components/navigation/SidebarItem.js.map +1 -1
- package/dist/lib/components/navigation/SidebarWrapper.js +2 -3
- package/dist/lib/components/navigation/SidebarWrapper.js.map +1 -1
- package/dist/lib/core/plugins.d.ts +1 -1
- package/dist/lib/core/plugins.js +1 -1
- package/dist/lib/core/plugins.js.map +1 -1
- package/dist/lib/oas/graphql/index.js +18 -3
- package/dist/lib/oas/graphql/index.js.map +1 -1
- package/dist/lib/plugins/markdown/MdxPage.js +1 -1
- package/dist/lib/plugins/markdown/MdxPage.js.map +1 -1
- package/dist/lib/plugins/openapi/OperationListItem.js +5 -5
- package/dist/lib/plugins/openapi/OperationListItem.js.map +1 -1
- package/dist/lib/plugins/openapi/ParameterList.js +3 -1
- package/dist/lib/plugins/openapi/ParameterList.js.map +1 -1
- package/dist/lib/plugins/openapi/ResponsesSidecarBox.d.ts +3 -1
- package/dist/lib/plugins/openapi/ResponsesSidecarBox.js +5 -9
- package/dist/lib/plugins/openapi/ResponsesSidecarBox.js.map +1 -1
- package/dist/lib/plugins/openapi/Sidecar.d.ts +3 -1
- package/dist/lib/plugins/openapi/Sidecar.js +11 -5
- package/dist/lib/plugins/openapi/Sidecar.js.map +1 -1
- package/dist/lib/plugins/openapi/index.js +1 -1
- package/dist/lib/plugins/openapi/index.js.map +1 -1
- package/dist/lib/plugins/openapi/playground/Playground.js +2 -3
- package/dist/lib/plugins/openapi/playground/Playground.js.map +1 -1
- package/dist/lib/plugins/openapi/playground/createUrl.js +5 -1
- package/dist/lib/plugins/openapi/playground/createUrl.js.map +1 -1
- package/dist/lib/plugins/openapi/schema/SchemaComponents.d.ts +13 -0
- package/dist/lib/plugins/openapi/schema/SchemaComponents.js +28 -0
- package/dist/lib/plugins/openapi/schema/SchemaComponents.js.map +1 -0
- package/dist/lib/plugins/openapi/schema/SchemaView.d.ts +6 -0
- package/dist/lib/plugins/openapi/schema/SchemaView.js +58 -0
- package/dist/lib/plugins/openapi/schema/SchemaView.js.map +1 -0
- package/dist/lib/plugins/openapi/schema/utils.d.ts +3 -0
- package/dist/lib/plugins/openapi/schema/utils.js +6 -0
- package/dist/lib/plugins/openapi/schema/utils.js.map +1 -0
- package/dist/lib/plugins/search-inkeep/index.js +5 -8
- package/dist/lib/plugins/search-inkeep/index.js.map +1 -1
- package/dist/lib/plugins/search-inkeep/inkeep.d.ts +19 -2
- package/dist/lib/plugins/search-inkeep/inkeep.js +1 -15
- package/dist/lib/plugins/search-inkeep/inkeep.js.map +1 -1
- package/dist/lib/themeToggle.d.ts +1 -0
- package/dist/lib/themeToggle.js +7 -0
- package/dist/lib/themeToggle.js.map +1 -0
- package/dist/lib/util/MdxComponents.js +4 -4
- package/dist/lib/util/MdxComponents.js.map +1 -1
- package/dist/vite/config.d.ts +4 -5
- package/dist/vite/config.js +7 -4
- package/dist/vite/config.js.map +1 -1
- package/dist/vite/config.test.js +2 -2
- package/dist/vite/config.test.js.map +1 -1
- package/dist/vite/dev-server.d.ts +0 -1
- package/dist/vite/dev-server.js +2 -21
- package/dist/vite/dev-server.js.map +1 -1
- package/dist/vite/html.js +2 -11
- package/dist/vite/html.js.map +1 -1
- package/dist/vite/plugin-api-keys.d.ts +3 -3
- package/dist/vite/plugin-api-keys.js +2 -1
- package/dist/vite/plugin-api-keys.js.map +1 -1
- package/dist/vite/plugin-api.d.ts +3 -3
- package/dist/vite/plugin-api.js +2 -1
- package/dist/vite/plugin-api.js.map +1 -1
- package/dist/vite/plugin-auth.d.ts +3 -3
- package/dist/vite/plugin-auth.js +2 -1
- package/dist/vite/plugin-auth.js.map +1 -1
- package/dist/vite/plugin-component.d.ts +3 -3
- package/dist/vite/plugin-component.js +17 -14
- package/dist/vite/plugin-component.js.map +1 -1
- package/dist/vite/plugin-config-reload.d.ts +4 -0
- package/dist/vite/plugin-config-reload.js +24 -0
- package/dist/vite/plugin-config-reload.js.map +1 -0
- package/dist/vite/plugin-config.d.ts +2 -2
- package/dist/vite/plugin-config.js.map +1 -1
- package/dist/vite/plugin-custom-css.d.ts +3 -3
- package/dist/vite/plugin-custom-css.js +2 -1
- package/dist/vite/plugin-custom-css.js.map +1 -1
- package/dist/vite/plugin-docs.d.ts +3 -3
- package/dist/vite/plugin-docs.js +3 -2
- package/dist/vite/plugin-docs.js.map +1 -1
- package/dist/vite/plugin-html-transform.d.ts +2 -0
- package/dist/vite/plugin-html-transform.js +15 -0
- package/dist/vite/plugin-html-transform.js.map +1 -0
- package/dist/vite/plugin-mdx.d.ts +3 -3
- package/dist/vite/plugin-mdx.js +2 -1
- package/dist/vite/plugin-mdx.js.map +1 -1
- package/dist/vite/plugin-metadata.d.ts +1 -1
- package/dist/vite/plugin-redirect.d.ts +3 -3
- package/dist/vite/plugin-redirect.js +2 -1
- package/dist/vite/plugin-redirect.js.map +1 -1
- package/dist/vite/plugin-sidebar.d.ts +3 -3
- package/dist/vite/plugin-sidebar.js +5 -4
- package/dist/vite/plugin-sidebar.js.map +1 -1
- package/dist/vite/plugin.d.ts +3 -2
- package/dist/vite/plugin.js +16 -11
- package/dist/vite/plugin.js.map +1 -1
- package/lib/{CategoryHeading-BWq12Bfa.js → CategoryHeading-z15xh7Jb.js} +2 -2
- package/lib/{CategoryHeading-BWq12Bfa.js.map → CategoryHeading-z15xh7Jb.js.map} +1 -1
- package/lib/{Combination-D-9IH0zy.js → Combination-DTfV-c98.js} +2 -2
- package/lib/{Combination-D-9IH0zy.js.map → Combination-DTfV-c98.js.map} +1 -1
- package/lib/{Input-HmAaR6kw.js → Input-DB9VROFR.js} +3 -3
- package/lib/{Input-HmAaR6kw.js.map → Input-DB9VROFR.js.map} +1 -1
- package/lib/Markdown-CEccPMI_.js +20508 -0
- package/lib/Markdown-CEccPMI_.js.map +1 -0
- package/lib/{MdxPage-oN3huD58.js → MdxPage-CnqOoqvp.js} +12 -15
- package/lib/MdxPage-CnqOoqvp.js.map +1 -0
- package/lib/OperationList-Cxiw2Z-v.js +457 -0
- package/lib/OperationList-Cxiw2Z-v.js.map +1 -0
- package/lib/{Route-DAF15JAU.js → Route-DfAFiR7v.js} +2 -2
- package/lib/{Route-DAF15JAU.js.map → Route-DfAFiR7v.js.map} +1 -1
- package/lib/SlotletProvider-ByLSCZQa.js +262 -0
- package/lib/SlotletProvider-ByLSCZQa.js.map +1 -0
- package/lib/{Spinner-BCz1kNGw.js → Spinner-BT_AYFrA.js} +3 -3
- package/lib/{Spinner-BCz1kNGw.js.map → Spinner-BT_AYFrA.js.map} +1 -1
- package/lib/assets/{worker-CR7aeKop.js → worker-CzHUifWA.js} +710 -703
- package/lib/assets/{worker-CR7aeKop.js.map → worker-CzHUifWA.js.map} +1 -1
- package/lib/{index-CtKkHGcd.js → index-D-9zqIOh.js} +1159 -1147
- package/lib/index-D-9zqIOh.js.map +1 -0
- package/lib/{index-D-9Z7HSn.js → index-Dz4LyXZI.js} +3 -3
- package/lib/{index-D-9Z7HSn.js.map → index-Dz4LyXZI.js.map} +1 -1
- package/lib/zudoku.components.js +775 -759
- package/lib/zudoku.components.js.map +1 -1
- package/lib/zudoku.openapi-worker.js +734 -727
- package/lib/zudoku.openapi-worker.js.map +1 -1
- package/lib/zudoku.plugin-api-keys.js +4 -4
- package/lib/zudoku.plugin-custom-page.js +1 -1
- package/lib/zudoku.plugin-markdown.js +1 -1
- package/lib/zudoku.plugin-openapi.js +3 -3
- package/lib/zudoku.plugin-search-inkeep.js +24 -29
- package/lib/zudoku.plugin-search-inkeep.js.map +1 -1
- package/package.json +99 -78
- package/src/app/demo-cdn.html +1 -1
- package/src/app/demo.html +1 -1
- package/src/app/demo.tsx +7 -9
- package/src/app/main.css +22 -0
- package/src/app/main.tsx +11 -8
- package/src/app/standalone.html +1 -1
- package/src/app/standalone.tsx +13 -8
- package/src/lib/components/ClientOnly.tsx +13 -0
- package/src/lib/components/Header.tsx +4 -1
- package/src/lib/components/Markdown.tsx +3 -2
- package/src/lib/components/Search.tsx +7 -7
- package/src/lib/components/SlotletProvider.tsx +10 -5
- package/src/lib/components/navigation/SidebarCategory.tsx +13 -2
- package/src/lib/components/navigation/SidebarItem.tsx +3 -2
- package/src/lib/components/navigation/SidebarWrapper.tsx +20 -18
- package/src/lib/core/plugins.ts +2 -2
- package/src/lib/oas/graphql/index.ts +26 -7
- package/src/lib/plugins/markdown/MdxPage.tsx +0 -1
- package/src/lib/plugins/openapi/OperationListItem.tsx +22 -17
- package/src/lib/plugins/openapi/ParameterList.tsx +10 -8
- package/src/lib/plugins/openapi/ResponsesSidecarBox.tsx +51 -39
- package/src/lib/plugins/openapi/Sidecar.tsx +34 -19
- package/src/lib/plugins/openapi/index.tsx +1 -1
- package/src/lib/plugins/openapi/playground/Playground.tsx +3 -6
- package/src/lib/plugins/openapi/playground/createUrl.ts +6 -5
- package/src/lib/plugins/openapi/schema/SchemaComponents.tsx +126 -0
- package/src/lib/plugins/openapi/schema/SchemaView.tsx +172 -0
- package/src/lib/plugins/openapi/schema/utils.ts +10 -0
- package/src/lib/plugins/search-inkeep/index.tsx +16 -8
- package/src/lib/plugins/search-inkeep/inkeep.ts +3 -18
- package/src/lib/themeToggle.ts +7 -0
- package/src/lib/util/MdxComponents.tsx +12 -12
- package/LICENSE.md +0 -21
- package/dist/lib/plugins/openapi/SchemaListView.d.ts +0 -7
- package/dist/lib/plugins/openapi/SchemaListView.js +0 -27
- package/dist/lib/plugins/openapi/SchemaListView.js.map +0 -1
- package/dist/lib/plugins/openapi/SchemaListViewItem.d.ts +0 -8
- package/dist/lib/plugins/openapi/SchemaListViewItem.js +0 -25
- package/dist/lib/plugins/openapi/SchemaListViewItem.js.map +0 -1
- package/dist/lib/plugins/openapi/SchemaListViewItemGroup.d.ts +0 -8
- package/dist/lib/plugins/openapi/SchemaListViewItemGroup.js +0 -17
- package/dist/lib/plugins/openapi/SchemaListViewItemGroup.js.map +0 -1
- package/lib/Markdown-B_Gax7at.js +0 -14108
- package/lib/Markdown-B_Gax7at.js.map +0 -1
- package/lib/MdxPage-oN3huD58.js.map +0 -1
- package/lib/OperationList-Ctj0ihBN.js +0 -448
- package/lib/OperationList-Ctj0ihBN.js.map +0 -1
- package/lib/SlotletProvider-CzMAO73_.js +0 -82
- package/lib/SlotletProvider-CzMAO73_.js.map +0 -1
- package/lib/index-CtKkHGcd.js.map +0 -1
- package/src/lib/plugins/openapi/SchemaListView.tsx +0 -75
- package/src/lib/plugins/openapi/SchemaListViewItem.tsx +0 -125
- package/src/lib/plugins/openapi/SchemaListViewItemGroup.tsx +0 -63
|
@@ -1,15 +1,13 @@
|
|
|
1
1
|
import { SearchIcon } from "lucide-react";
|
|
2
|
-
import { useCallback, useEffect, useState } from "react";
|
|
2
|
+
import { Suspense, useCallback, useEffect, useState } from "react";
|
|
3
3
|
import { isSearchPlugin } from "../core/plugins.js";
|
|
4
4
|
import { useZudoku } from "./context/ZudokuContext.js";
|
|
5
5
|
|
|
6
6
|
export const Search = () => {
|
|
7
7
|
const ctx = useZudoku();
|
|
8
|
-
const [isOpen, setIsOpen] = useState(
|
|
8
|
+
const [isOpen, setIsOpen] = useState(false);
|
|
9
9
|
|
|
10
|
-
const onClose = useCallback(() =>
|
|
11
|
-
setIsOpen(false);
|
|
12
|
-
}, []);
|
|
10
|
+
const onClose = useCallback(() => setIsOpen(false), []);
|
|
13
11
|
|
|
14
12
|
useEffect(() => {
|
|
15
13
|
if (isOpen) {
|
|
@@ -40,6 +38,7 @@ export const Search = () => {
|
|
|
40
38
|
<>
|
|
41
39
|
<button
|
|
42
40
|
type="button"
|
|
41
|
+
onClick={() => setIsOpen(true)}
|
|
43
42
|
className="flex items-center border border-input hover:bg-accent hover:text-accent-foreground p-4 relative h-8 justify-start rounded-lg bg-background text-sm text-muted-foreground shadow-none w-40 sm:w-72"
|
|
44
43
|
>
|
|
45
44
|
<div className="flex items-center gap-2 flex-grow">
|
|
@@ -50,11 +49,12 @@ export const Search = () => {
|
|
|
50
49
|
⌘K
|
|
51
50
|
</kbd>
|
|
52
51
|
</button>
|
|
53
|
-
{
|
|
54
|
-
searchPlugin.
|
|
52
|
+
<Suspense fallback={null}>
|
|
53
|
+
{searchPlugin.renderSearch({
|
|
55
54
|
isOpen,
|
|
56
55
|
onClose,
|
|
57
56
|
})}
|
|
57
|
+
</Suspense>
|
|
58
58
|
</>
|
|
59
59
|
);
|
|
60
60
|
};
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import React, { ReactNode, useContext } from "react";
|
|
2
|
-
|
|
3
|
-
export type Slotlets = Record<string, ReactNode>;
|
|
1
|
+
import React, { type ReactElement, ReactNode, useContext } from "react";
|
|
2
|
+
import { isValidElementType } from "react-is";
|
|
3
|
+
export type Slotlets = Record<string, ReactNode | ReactElement>;
|
|
4
4
|
|
|
5
5
|
const SlotletContext = React.createContext<Slotlets | undefined>({});
|
|
6
6
|
|
|
@@ -19,7 +19,12 @@ export const SlotletProvider = ({
|
|
|
19
19
|
};
|
|
20
20
|
|
|
21
21
|
export const Slotlet = ({ name }: { name: string }) => {
|
|
22
|
-
const
|
|
22
|
+
const context = useContext(SlotletContext);
|
|
23
|
+
const componentOrElement = context?.[name];
|
|
24
|
+
|
|
25
|
+
if (isValidElementType(componentOrElement)) {
|
|
26
|
+
return React.createElement(componentOrElement);
|
|
27
|
+
}
|
|
23
28
|
|
|
24
|
-
return
|
|
29
|
+
return componentOrElement;
|
|
25
30
|
};
|
|
@@ -18,6 +18,7 @@ export const SidebarCategory = ({
|
|
|
18
18
|
}) => {
|
|
19
19
|
const topNavItem = useTopNavigationItem();
|
|
20
20
|
const isCategoryOpen = useIsCategoryOpen(category);
|
|
21
|
+
const [hasInteracted, setHasInteracted] = useState(false);
|
|
21
22
|
|
|
22
23
|
const isCollapsible = category.collapsible ?? true;
|
|
23
24
|
const isCollapsed = category.collapsed ?? true;
|
|
@@ -40,11 +41,15 @@ export const SidebarCategory = ({
|
|
|
40
41
|
onClick={(e) => {
|
|
41
42
|
e.preventDefault();
|
|
42
43
|
setOpen((prev) => !prev);
|
|
44
|
+
setHasInteracted(true);
|
|
43
45
|
}}
|
|
44
46
|
>
|
|
45
47
|
<ChevronRightIcon
|
|
46
48
|
size={16}
|
|
47
|
-
className=
|
|
49
|
+
className={cn(
|
|
50
|
+
hasInteracted && "transition",
|
|
51
|
+
"shrink-0 group-data-[state=open]:rotate-90",
|
|
52
|
+
)}
|
|
48
53
|
/>
|
|
49
54
|
</button>
|
|
50
55
|
);
|
|
@@ -88,7 +93,13 @@ export const SidebarCategory = ({
|
|
|
88
93
|
</div>
|
|
89
94
|
)}
|
|
90
95
|
</Collapsible.Trigger>
|
|
91
|
-
<Collapsible.Content
|
|
96
|
+
<Collapsible.Content
|
|
97
|
+
className={cn(
|
|
98
|
+
// CollapsibleContent class is used to animate and it should only be applied when the user has triggered the toggle
|
|
99
|
+
hasInteracted && "CollapsibleContent",
|
|
100
|
+
"ms-[calc(var(--padding-nav-item)*1.125)]",
|
|
101
|
+
)}
|
|
102
|
+
>
|
|
92
103
|
<ul className="mt-1 border-l ps-2">
|
|
93
104
|
{category.items.map((item) => (
|
|
94
105
|
<SidebarItem
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { cva } from "class-variance-authority";
|
|
2
2
|
import { ExternalLinkIcon } from "lucide-react";
|
|
3
|
-
import { NavLink } from "react-router-dom";
|
|
3
|
+
import { NavLink, useSearchParams } from "react-router-dom";
|
|
4
4
|
|
|
5
5
|
import type { SidebarItem as SidebarItemType } from "../../../config/validators/SidebarSchema.js";
|
|
6
6
|
import { cn } from "../../util/cn.js";
|
|
@@ -42,6 +42,7 @@ export const SidebarItem = ({
|
|
|
42
42
|
}) => {
|
|
43
43
|
const topNavItem = useTopNavigationItem();
|
|
44
44
|
const { activeAnchor } = useViewportAnchor();
|
|
45
|
+
const [searchParams] = useSearchParams();
|
|
45
46
|
|
|
46
47
|
switch (item.type) {
|
|
47
48
|
case "category":
|
|
@@ -69,7 +70,7 @@ export const SidebarItem = ({
|
|
|
69
70
|
case "link":
|
|
70
71
|
return item.href.startsWith("#") ? (
|
|
71
72
|
<AnchorLink
|
|
72
|
-
to={item.href}
|
|
73
|
+
to={{ hash: item.href, search: searchParams.toString() }}
|
|
73
74
|
{...{ [DATA_ANCHOR_ATTR]: item.href.slice(1) }}
|
|
74
75
|
className={cn(
|
|
75
76
|
"flex gap-2.5 justify-between",
|
|
@@ -4,21 +4,23 @@ import { cn } from "../../util/cn.js";
|
|
|
4
4
|
export const SidebarWrapper = forwardRef<
|
|
5
5
|
HTMLDivElement,
|
|
6
6
|
PropsWithChildren<{ pushMainContent?: boolean; className?: string }>
|
|
7
|
-
>(
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
7
|
+
>(({ children, className, pushMainContent }, ref) => (
|
|
8
|
+
<nav
|
|
9
|
+
// this data attribute is used in `Layout.tsx` to determine if side navigation
|
|
10
|
+
// is present for the current page so the main content is pushed to the right
|
|
11
|
+
// it's also important to set `peer` class here.
|
|
12
|
+
// maybe this could be simplified by adjusting the layout
|
|
13
|
+
data-navigation={String(pushMainContent)}
|
|
14
|
+
className={cn(
|
|
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]",
|
|
17
|
+
"w-[--side-nav-width] h-[calc(100%-var(--header-height))] scroll-pt-2 gap-2",
|
|
18
|
+
className,
|
|
19
|
+
)}
|
|
20
|
+
ref={ref}
|
|
21
|
+
>
|
|
22
|
+
{children}
|
|
23
|
+
</nav>
|
|
24
|
+
));
|
|
25
|
+
|
|
26
|
+
SidebarWrapper.displayName = "SidebarWrapper";
|
package/src/lib/core/plugins.ts
CHANGED
|
@@ -21,7 +21,7 @@ export interface ApiIdentityPlugin {
|
|
|
21
21
|
}
|
|
22
22
|
|
|
23
23
|
export interface SearchProviderPlugin {
|
|
24
|
-
|
|
24
|
+
renderSearch: (o: {
|
|
25
25
|
isOpen: boolean;
|
|
26
26
|
onClose: () => void;
|
|
27
27
|
}) => React.JSX.Element | null;
|
|
@@ -59,7 +59,7 @@ export const isNavigationPlugin = (
|
|
|
59
59
|
export const isSearchPlugin = (
|
|
60
60
|
obj: DevPortalPlugin,
|
|
61
61
|
): obj is SearchProviderPlugin =>
|
|
62
|
-
"
|
|
62
|
+
"renderSearch" in obj && typeof obj.renderSearch === "function";
|
|
63
63
|
|
|
64
64
|
export const needsInitialization = (
|
|
65
65
|
obj: DevPortalPlugin,
|
|
@@ -68,6 +68,25 @@ const builder = new SchemaBuilder<{
|
|
|
68
68
|
const JSONScalar = builder.addScalarType("JSON", GraphQLJSON);
|
|
69
69
|
const JSONObjectScalar = builder.addScalarType("JSONObject", GraphQLJSONObject);
|
|
70
70
|
|
|
71
|
+
const getAllTags = (schema: OpenAPIDocument): TagObject[] => {
|
|
72
|
+
const tags = schema.tags ?? [];
|
|
73
|
+
|
|
74
|
+
// Extract tags from operations
|
|
75
|
+
const operationTags = Object.values(schema.paths ?? {})
|
|
76
|
+
.flatMap((path) => Object.values(path ?? {}))
|
|
77
|
+
.flatMap((operation) =>
|
|
78
|
+
typeof operation === "object" && "tags" in operation
|
|
79
|
+
? operation.tags ?? []
|
|
80
|
+
: [],
|
|
81
|
+
);
|
|
82
|
+
|
|
83
|
+
// Remove duplicates and tags that appear in the schema
|
|
84
|
+
const uniqueOperationTags = [...new Set(operationTags)].filter(
|
|
85
|
+
(tag) => !tags.some((rootTag) => rootTag.name === tag),
|
|
86
|
+
);
|
|
87
|
+
return [...tags, ...uniqueOperationTags.map((tag) => ({ name: tag }))];
|
|
88
|
+
};
|
|
89
|
+
|
|
71
90
|
const getAllOperations = (paths?: PathsObject, tag?: string) => {
|
|
72
91
|
return Object.entries(paths ?? {}).flatMap(([path, value]) =>
|
|
73
92
|
HttpMethods.flatMap((method) => {
|
|
@@ -117,7 +136,7 @@ const SchemaTag = builder.objectRef<TagObject>("SchemaTag").implement({
|
|
|
117
136
|
operations: t.field({
|
|
118
137
|
type: [OperationItem],
|
|
119
138
|
resolve: (parent, _args, ctx) => {
|
|
120
|
-
const rootTags = ctx.schema.
|
|
139
|
+
const rootTags = getAllTags(ctx.schema).map((tag) => tag.name);
|
|
121
140
|
|
|
122
141
|
return getAllOperations(ctx.schema.paths, parent.name).filter((item) =>
|
|
123
142
|
parent.name
|
|
@@ -246,7 +265,7 @@ const RequestBodyObject = builder
|
|
|
246
265
|
const ResponseItem = builder
|
|
247
266
|
.objectRef<{
|
|
248
267
|
statusCode: string;
|
|
249
|
-
description
|
|
268
|
+
description?: string;
|
|
250
269
|
content: Array<{
|
|
251
270
|
mediaType: string;
|
|
252
271
|
schema: any;
|
|
@@ -258,7 +277,7 @@ const ResponseItem = builder
|
|
|
258
277
|
.implement({
|
|
259
278
|
fields: (t) => ({
|
|
260
279
|
statusCode: t.exposeString("statusCode"),
|
|
261
|
-
description: t.exposeString("description"),
|
|
280
|
+
description: t.exposeString("description", { nullable: true }),
|
|
262
281
|
content: t.expose("content", { type: [MediaTypeItem], nullable: true }),
|
|
263
282
|
headers: t.expose("headers", { type: JSONScalar, nullable: true }),
|
|
264
283
|
links: t.expose("links", { type: JSONScalar, nullable: true }),
|
|
@@ -363,10 +382,10 @@ const Schema = builder.objectRef<OpenAPIDocument>("Schema").implement({
|
|
|
363
382
|
name: t.arg.string(),
|
|
364
383
|
},
|
|
365
384
|
type: [SchemaTag],
|
|
366
|
-
resolve: (root, args) =>
|
|
367
|
-
[...(root
|
|
368
|
-
|
|
369
|
-
|
|
385
|
+
resolve: (root, args) => {
|
|
386
|
+
const tags = [...getAllTags(root), { name: "" }];
|
|
387
|
+
return args.name ? tags.filter((tag) => tag.name === args.name) : tags;
|
|
388
|
+
},
|
|
370
389
|
}),
|
|
371
390
|
operations: t.field({
|
|
372
391
|
type: [OperationItem],
|
|
@@ -1,14 +1,14 @@
|
|
|
1
|
+
import { useState } from "react";
|
|
1
2
|
import { Heading } from "../../components/Heading.js";
|
|
2
3
|
import { Markdown } from "../../components/Markdown.js";
|
|
3
|
-
import { Card } from "../../ui/Card.js";
|
|
4
4
|
import { Tabs, TabsContent, TabsList, TabsTrigger } from "../../ui/Tabs.js";
|
|
5
5
|
import { groupBy } from "../../util/groupBy.js";
|
|
6
6
|
import { renderIf } from "../../util/renderIf.js";
|
|
7
7
|
import { OperationsFragment } from "./OperationList.js";
|
|
8
8
|
import { ParameterList } from "./ParameterList.js";
|
|
9
|
-
import { SchemaListView } from "./SchemaListView.js";
|
|
10
9
|
import { Sidecar } from "./Sidecar.js";
|
|
11
10
|
import { FragmentType, useFragment } from "./graphql/index.js";
|
|
11
|
+
import { SchemaView } from "./schema/SchemaView.js";
|
|
12
12
|
import { SchemaProseClasses } from "./util/prose.js";
|
|
13
13
|
|
|
14
14
|
export const PARAM_GROUPS = ["path", "query", "header", "cookie"] as const;
|
|
@@ -26,6 +26,8 @@ export const OperationListItem = ({
|
|
|
26
26
|
);
|
|
27
27
|
|
|
28
28
|
const first = operation.responses.at(0);
|
|
29
|
+
const [selectedResponse, setSelectedResponse] = useState(first?.statusCode);
|
|
30
|
+
|
|
29
31
|
return (
|
|
30
32
|
<div
|
|
31
33
|
key={operation.operationId}
|
|
@@ -62,7 +64,7 @@ export const OperationListItem = ({
|
|
|
62
64
|
<Heading level={3} className="capitalize">
|
|
63
65
|
Request Body
|
|
64
66
|
</Heading>
|
|
65
|
-
<
|
|
67
|
+
<SchemaView schema={schema} />
|
|
66
68
|
</div>
|
|
67
69
|
))}
|
|
68
70
|
{operation.responses.length > 0 && (
|
|
@@ -70,12 +72,15 @@ export const OperationListItem = ({
|
|
|
70
72
|
<Heading level={3} className="capitalize mt-8 pt-8 border-t">
|
|
71
73
|
Responses
|
|
72
74
|
</Heading>
|
|
73
|
-
<Tabs
|
|
75
|
+
<Tabs
|
|
76
|
+
onValueChange={(value) => setSelectedResponse(value)}
|
|
77
|
+
value={selectedResponse}
|
|
78
|
+
>
|
|
74
79
|
{operation.responses.length > 1 && (
|
|
75
80
|
<TabsList>
|
|
76
81
|
{operation.responses.map((response) => (
|
|
77
82
|
<TabsTrigger
|
|
78
|
-
value={response.statusCode
|
|
83
|
+
value={response.statusCode}
|
|
79
84
|
key={response.statusCode}
|
|
80
85
|
title={response.description}
|
|
81
86
|
>
|
|
@@ -87,19 +92,15 @@ export const OperationListItem = ({
|
|
|
87
92
|
<ul className="list-none m-0 px-0 overflow-hidden">
|
|
88
93
|
{operation.responses.map((response) => (
|
|
89
94
|
<TabsContent
|
|
90
|
-
value={response.statusCode
|
|
95
|
+
value={response.statusCode}
|
|
91
96
|
key={response.statusCode}
|
|
92
97
|
>
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
<Card className="font-mono text-sm p-4">
|
|
100
|
-
No response body
|
|
101
|
-
</Card>
|
|
102
|
-
)}
|
|
98
|
+
<SchemaView
|
|
99
|
+
schema={
|
|
100
|
+
response.content?.find((content) => content.schema)
|
|
101
|
+
?.schema
|
|
102
|
+
}
|
|
103
|
+
/>
|
|
103
104
|
</TabsContent>
|
|
104
105
|
))}
|
|
105
106
|
</ul>
|
|
@@ -108,7 +109,11 @@ export const OperationListItem = ({
|
|
|
108
109
|
)}
|
|
109
110
|
</div>
|
|
110
111
|
|
|
111
|
-
<Sidecar
|
|
112
|
+
<Sidecar
|
|
113
|
+
selectedResponse={selectedResponse}
|
|
114
|
+
onSelectResponse={setSelectedResponse}
|
|
115
|
+
operation={operation}
|
|
116
|
+
/>
|
|
112
117
|
</div>
|
|
113
118
|
);
|
|
114
119
|
};
|
|
@@ -21,14 +21,16 @@ export const ParameterList = ({
|
|
|
21
21
|
</Heading>
|
|
22
22
|
<Card>
|
|
23
23
|
<ul className="list-none m-0 px-0 divide-y ">
|
|
24
|
-
{parameters
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
24
|
+
{parameters
|
|
25
|
+
.sort((a, b) => (a.required === b.required ? 0 : a.required ? -1 : 1))
|
|
26
|
+
.map((parameter) => (
|
|
27
|
+
<ParameterListItem
|
|
28
|
+
key={`${parameter.name}-${parameter.in}`}
|
|
29
|
+
parameter={parameter}
|
|
30
|
+
id={id}
|
|
31
|
+
group={group}
|
|
32
|
+
/>
|
|
33
|
+
))}
|
|
32
34
|
</ul>
|
|
33
35
|
</Card>
|
|
34
36
|
</>
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import
|
|
1
|
+
import * as Tabs from "@radix-ui/react-tabs";
|
|
2
2
|
import { SyntaxHighlight } from "../../components/SyntaxHighlight.js";
|
|
3
3
|
import { type SchemaObject } from "../../oas/graphql/index.js";
|
|
4
4
|
import { cn } from "../../util/cn.js";
|
|
@@ -9,52 +9,64 @@ import { generateSchemaExample } from "./util/generateSchemaExample.js";
|
|
|
9
9
|
type Responses = OperationListItemResult["responses"];
|
|
10
10
|
export const ResponsesSidecarBox = ({
|
|
11
11
|
responses,
|
|
12
|
+
selectedResponse,
|
|
13
|
+
onSelectResponse,
|
|
12
14
|
}: {
|
|
13
15
|
responses: Responses;
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
16
|
+
selectedResponse?: string;
|
|
17
|
+
onSelectResponse: (response: string) => void;
|
|
18
|
+
}) => (
|
|
19
|
+
<SidecarBox.Root>
|
|
20
|
+
<Tabs.Root
|
|
21
|
+
defaultValue={responses[0]?.statusCode}
|
|
22
|
+
value={selectedResponse}
|
|
23
|
+
onValueChange={(value) => onSelectResponse(value)}
|
|
24
|
+
>
|
|
25
|
+
<SidecarBox.Head className="text-xs flex flex-col gap-2 pb-0">
|
|
23
26
|
<span className="font-mono">Example Responses</span>
|
|
24
|
-
<
|
|
25
|
-
{responses.map((response
|
|
26
|
-
<
|
|
27
|
+
<Tabs.List className="flex gap-2">
|
|
28
|
+
{responses.map((response) => (
|
|
29
|
+
<Tabs.Trigger
|
|
27
30
|
key={response.statusCode}
|
|
28
|
-
|
|
31
|
+
value={response.statusCode}
|
|
29
32
|
className={cn(
|
|
30
33
|
"text-xs font-mono px-1.5 py-1 pb-px translate-y-px border-b-2 border-transparent rounded-t cursor-pointer",
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
: "hover:border-accent-foreground/25",
|
|
34
|
+
"data-[state=active]:text-primary data-[state=active]:dark:text-inherit data-[state=active]:border-primary",
|
|
35
|
+
"hover:border-accent-foreground/25",
|
|
34
36
|
)}
|
|
35
37
|
>
|
|
36
38
|
{response.statusCode}
|
|
37
|
-
</
|
|
39
|
+
</Tabs.Trigger>
|
|
38
40
|
))}
|
|
39
|
-
</
|
|
41
|
+
</Tabs.List>
|
|
40
42
|
</SidecarBox.Head>
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
}
|
|
43
|
+
{responses.map((response) => {
|
|
44
|
+
const schema = response.content?.[0]?.schema as
|
|
45
|
+
| SchemaObject
|
|
46
|
+
| undefined;
|
|
47
|
+
|
|
48
|
+
return (
|
|
49
|
+
<Tabs.Content key={response.statusCode} value={response.statusCode}>
|
|
50
|
+
<SidecarBox.Body>
|
|
51
|
+
{schema ? (
|
|
52
|
+
<SyntaxHighlight
|
|
53
|
+
language="json"
|
|
54
|
+
noBackground
|
|
55
|
+
className="text-xs"
|
|
56
|
+
code={JSON.stringify(generateSchemaExample(schema), null, 2)}
|
|
57
|
+
/>
|
|
58
|
+
) : (
|
|
59
|
+
<span className="text-muted-foreground font-mono italic text-xs">
|
|
60
|
+
Empty Response
|
|
61
|
+
</span>
|
|
62
|
+
)}
|
|
63
|
+
</SidecarBox.Body>
|
|
64
|
+
<SidecarBox.Footer className="flex justify-end text-xs">
|
|
65
|
+
{response.description}
|
|
66
|
+
</SidecarBox.Footer>
|
|
67
|
+
</Tabs.Content>
|
|
68
|
+
);
|
|
69
|
+
})}
|
|
70
|
+
</Tabs.Root>
|
|
71
|
+
</SidecarBox.Root>
|
|
72
|
+
);
|
|
@@ -84,8 +84,12 @@ const methodToColor = {
|
|
|
84
84
|
|
|
85
85
|
export const Sidecar = ({
|
|
86
86
|
operation,
|
|
87
|
+
selectedResponse,
|
|
88
|
+
onSelectResponse,
|
|
87
89
|
}: {
|
|
88
90
|
operation: OperationListItemResult;
|
|
91
|
+
selectedResponse?: string;
|
|
92
|
+
onSelectResponse: (response: string) => void;
|
|
89
93
|
}) => {
|
|
90
94
|
const oasConfig = useOasConfig();
|
|
91
95
|
const [result] = useQuery({
|
|
@@ -105,24 +109,31 @@ export const Sidecar = ({
|
|
|
105
109
|
|
|
106
110
|
const requestBodyContent = operation.requestBody?.content;
|
|
107
111
|
|
|
108
|
-
const path = operation.path.split("/").map((part) =>
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
112
|
+
const path = operation.path.split("/").map((part, i, arr) => {
|
|
113
|
+
const isParam =
|
|
114
|
+
(part.startsWith("{") && part.endsWith("}")) || part.startsWith(":");
|
|
115
|
+
const paramName = isParam ? part.replace(/[:{}]/g, "") : undefined;
|
|
116
|
+
|
|
117
|
+
return (
|
|
118
|
+
// eslint-disable-next-line react/no-array-index-key
|
|
119
|
+
<Fragment key={part + i}>
|
|
120
|
+
{paramName ? (
|
|
121
|
+
<ColorizedParam
|
|
122
|
+
name={paramName}
|
|
123
|
+
backgroundOpacity="0"
|
|
124
|
+
// same as in `ParameterListItem`
|
|
125
|
+
slug={`${operation.slug}-${paramName.toLocaleLowerCase()}`}
|
|
126
|
+
>
|
|
127
|
+
{part}
|
|
128
|
+
</ColorizedParam>
|
|
129
|
+
) : (
|
|
130
|
+
part
|
|
131
|
+
)}
|
|
132
|
+
{i < arr.length - 1 ? "/" : null}
|
|
133
|
+
<wbr />
|
|
134
|
+
</Fragment>
|
|
135
|
+
);
|
|
136
|
+
});
|
|
126
137
|
|
|
127
138
|
const code = useMemo(() => {
|
|
128
139
|
const example = requestBodyContent?.[0]?.schema
|
|
@@ -200,7 +211,11 @@ export const Sidecar = ({
|
|
|
200
211
|
<RequestBodySidecarBox content={requestBodyContent} />
|
|
201
212
|
)}
|
|
202
213
|
{operation.responses.length > 0 && (
|
|
203
|
-
<ResponsesSidecarBox
|
|
214
|
+
<ResponsesSidecarBox
|
|
215
|
+
selectedResponse={selectedResponse}
|
|
216
|
+
onSelectResponse={onSelectResponse}
|
|
217
|
+
responses={operation.responses}
|
|
218
|
+
/>
|
|
204
219
|
)}
|
|
205
220
|
</aside>
|
|
206
221
|
);
|
|
@@ -164,8 +164,9 @@ export const Playground = ({
|
|
|
164
164
|
});
|
|
165
165
|
|
|
166
166
|
const path = url.split("/").map((part, i, arr) => {
|
|
167
|
-
const isPathParam =
|
|
168
|
-
|
|
167
|
+
const isPathParam =
|
|
168
|
+
(part.startsWith("{") && part.endsWith("}")) || part.startsWith(":");
|
|
169
|
+
const replaced = part.replace(/[:{}]/g, "");
|
|
169
170
|
const value = formState.pathParams.find((p) => p.name === replaced)?.value;
|
|
170
171
|
|
|
171
172
|
const pathParamValue = value ? (
|
|
@@ -191,10 +192,6 @@ export const Playground = ({
|
|
|
191
192
|
);
|
|
192
193
|
});
|
|
193
194
|
|
|
194
|
-
const lang = mimeTypeToLanguage(
|
|
195
|
-
queryMutation.data?.headers.get("Content-Type") ?? "",
|
|
196
|
-
);
|
|
197
|
-
|
|
198
195
|
const headerEntries = Array.from(queryMutation.data?.headers.entries() ?? []);
|
|
199
196
|
|
|
200
197
|
const urlQueryParams = formState.queryParams
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import type { PlaygroundForm } from "./Playground.js";
|
|
2
2
|
|
|
3
3
|
export const createUrl = (host: string, path: string, data: PlaygroundForm) => {
|
|
4
|
-
const filledPath = path.replace(
|
|
5
|
-
|
|
6
|
-
(
|
|
7
|
-
|
|
8
|
-
|
|
4
|
+
const filledPath = path.replace(/(:\w+|\{\w+})/g, (match) => {
|
|
5
|
+
const key = match.replace(/[:{}]/g, "");
|
|
6
|
+
const value = data.pathParams.find((part) => part.name === key)?.value;
|
|
7
|
+
|
|
8
|
+
return value ?? match;
|
|
9
|
+
});
|
|
9
10
|
|
|
10
11
|
const url = new URL(filledPath, host);
|
|
11
12
|
|