docs: fixes and refactoring for API reference (#9708)
* docs: fixes and refactoring for API reference * add route caching * remove caching * use next cache
This commit is contained in:
1 parent
6b989353ac
commit
b2122c4073
20 files changed
+377
-277
No files matched your search
@@ -3,22 +3,35 @@ import AdminContent from "../_mdx/admin.mdx"
|
|||||||
import Tags from "@/components/Tags"
|
import Tags from "@/components/Tags"
|
||||||
import PageTitleProvider from "@/providers/page-title"
|
import PageTitleProvider from "@/providers/page-title"
|
||||||
import { H1 } from "docs-ui"
|
import { H1 } from "docs-ui"
|
||||||
|
import { getBaseSpecs } from "../../lib"
|
||||||
|
import BaseSpecsProvider from "../../providers/base-specs"
|
||||||
|
import clsx from "clsx"
|
||||||
|
|
||||||
|
const AdminPage = async () => {
|
||||||
|
const data = await getBaseSpecs("admin")
|
||||||
|
|
||||||
const ReferencePage = async () => {
|
|
||||||
return (
|
return (
|
||||||
<AreaProvider area={"admin"}>
|
<BaseSpecsProvider baseSpecs={data}>
|
||||||
<PageTitleProvider>
|
<AreaProvider area={"admin"}>
|
||||||
<H1 className="!h2-docs scroll-m-56 lg:pl-4" id="introduction">
|
<PageTitleProvider>
|
||||||
Medusa V2 Admin API Reference
|
<H1
|
||||||
</H1>
|
className={clsx(
|
||||||
<AdminContent />
|
"!h2-docs lg:pl-4",
|
||||||
<Tags />
|
"scroll-m-[184px] lg:scroll-m-[264px]"
|
||||||
</PageTitleProvider>
|
)}
|
||||||
</AreaProvider>
|
id="introduction"
|
||||||
|
>
|
||||||
|
Medusa V2 Admin API Reference
|
||||||
|
</H1>
|
||||||
|
<AdminContent />
|
||||||
|
<Tags tags={data?.tags} />
|
||||||
|
</PageTitleProvider>
|
||||||
|
</AreaProvider>
|
||||||
|
</BaseSpecsProvider>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export default ReferencePage
|
export default AdminPage
|
||||||
|
|
||||||
export function generateMetadata() {
|
export function generateMetadata() {
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -1,9 +1,7 @@
|
|||||||
import { NextResponse } from "next/server"
|
import { NextResponse } from "next/server"
|
||||||
import { SchemaObject } from "../../../types/openapi"
|
|
||||||
import path from "path"
|
import path from "path"
|
||||||
import { existsSync, promises as fs } from "fs"
|
import { existsSync } from "fs"
|
||||||
import { parseDocument } from "yaml"
|
import getSchemaContent from "../../../utils/get-schema-content"
|
||||||
import dereference from "../../../utils/dereference"
|
|
||||||
|
|
||||||
export async function GET(request: Request) {
|
export async function GET(request: Request) {
|
||||||
const { searchParams } = new URL(request.url)
|
const { searchParams } = new URL(request.url)
|
||||||
@@ -59,14 +57,8 @@ export async function GET(request: Request) {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const schemaContent = await fs.readFile(schemaPath, "utf-8")
|
const { dereferencedDocument, originalSchema: schema } =
|
||||||
const schema = parseDocument(schemaContent).toJS() as SchemaObject
|
await getSchemaContent(schemaPath, baseSchemasPath)
|
||||||
|
|
||||||
// resolve references in schema
|
|
||||||
const dereferencedDocument = await dereference({
|
|
||||||
basePath: baseSchemasPath,
|
|
||||||
schemas: [schema],
|
|
||||||
})
|
|
||||||
|
|
||||||
return NextResponse.json(
|
return NextResponse.json(
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -3,22 +3,35 @@ import StoreContent from "../_mdx/store.mdx"
|
|||||||
import Tags from "@/components/Tags"
|
import Tags from "@/components/Tags"
|
||||||
import PageTitleProvider from "@/providers/page-title"
|
import PageTitleProvider from "@/providers/page-title"
|
||||||
import { H1 } from "docs-ui"
|
import { H1 } from "docs-ui"
|
||||||
|
import { getBaseSpecs } from "../../lib"
|
||||||
|
import BaseSpecsProvider from "../../providers/base-specs"
|
||||||
|
import clsx from "clsx"
|
||||||
|
|
||||||
|
const StorePage = async () => {
|
||||||
|
const data = await getBaseSpecs("store")
|
||||||
|
|
||||||
const ReferencePage = async () => {
|
|
||||||
return (
|
return (
|
||||||
<AreaProvider area={"store"}>
|
<BaseSpecsProvider baseSpecs={data}>
|
||||||
<PageTitleProvider>
|
<AreaProvider area={"store"}>
|
||||||
<H1 className="!h2-docs scroll-m-56 lg:pl-4" id="introduction">
|
<PageTitleProvider>
|
||||||
Medusa V2 Store API Reference
|
<H1
|
||||||
</H1>
|
className={clsx(
|
||||||
<StoreContent />
|
"!h2-docs lg:pl-4",
|
||||||
<Tags />
|
"scroll-m-[184px] lg:scroll-m-[264px]"
|
||||||
</PageTitleProvider>
|
)}
|
||||||
</AreaProvider>
|
id="introduction"
|
||||||
|
>
|
||||||
|
Medusa V2 Store API Reference
|
||||||
|
</H1>
|
||||||
|
<StoreContent />
|
||||||
|
<Tags tags={data?.tags} />
|
||||||
|
</PageTitleProvider>
|
||||||
|
</AreaProvider>
|
||||||
|
</BaseSpecsProvider>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export default ReferencePage
|
export default StorePage
|
||||||
|
|
||||||
export function generateMetadata() {
|
export function generateMetadata() {
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -3,12 +3,13 @@
|
|||||||
import { useScrollController, useSidebar, H2 as UiH2 } from "docs-ui"
|
import { useScrollController, useSidebar, H2 as UiH2 } from "docs-ui"
|
||||||
import { useEffect, useMemo, useRef, useState } from "react"
|
import { useEffect, useMemo, useRef, useState } from "react"
|
||||||
import getSectionId from "../../../utils/get-section-id"
|
import getSectionId from "../../../utils/get-section-id"
|
||||||
|
import { SidebarItem } from "types"
|
||||||
|
|
||||||
type H2Props = React.HTMLAttributes<HTMLHeadingElement>
|
type H2Props = React.HTMLAttributes<HTMLHeadingElement>
|
||||||
|
|
||||||
const H2 = ({ children, ...props }: H2Props) => {
|
const H2 = ({ children, ...props }: H2Props) => {
|
||||||
const headingRef = useRef<HTMLHeadingElement>(null)
|
const headingRef = useRef<HTMLHeadingElement>(null)
|
||||||
const { activePath, addItems } = useSidebar()
|
const { activePath, addItems, removeItems } = useSidebar()
|
||||||
const { scrollableElement, scrollToElement } = useScrollController()
|
const { scrollableElement, scrollToElement } = useScrollController()
|
||||||
const [scrolledFirstTime, setScrolledFirstTime] = useState(false)
|
const [scrolledFirstTime, setScrolledFirstTime] = useState(false)
|
||||||
|
|
||||||
@@ -28,14 +29,19 @@ const H2 = ({ children, ...props }: H2Props) => {
|
|||||||
}, [scrollableElement, headingRef, id])
|
}, [scrollableElement, headingRef, id])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
addItems([
|
const item: SidebarItem[] = [
|
||||||
{
|
{
|
||||||
type: "link",
|
type: "link",
|
||||||
path: `${id}`,
|
path: `${id}`,
|
||||||
title: children as string,
|
title: children as string,
|
||||||
loaded: true,
|
loaded: true,
|
||||||
},
|
},
|
||||||
])
|
]
|
||||||
|
addItems(item)
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
removeItems(item)
|
||||||
|
}
|
||||||
}, [id])
|
}, [id])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import type { TagOperationParametersProps } from "../.."
|
|||||||
import type { TagsOperationParametersNestedProps } from "../../Nested"
|
import type { TagsOperationParametersNestedProps } from "../../Nested"
|
||||||
import checkRequired from "@/utils/check-required"
|
import checkRequired from "@/utils/check-required"
|
||||||
import { Loading, type DetailsProps } from "docs-ui"
|
import { Loading, type DetailsProps } from "docs-ui"
|
||||||
import { useMemo } from "react"
|
import { Fragment, useMemo } from "react"
|
||||||
|
|
||||||
const TagOperationParameters = dynamic<TagOperationParametersProps>(
|
const TagOperationParameters = dynamic<TagOperationParametersProps>(
|
||||||
async () => import("../.."),
|
async () => import("../.."),
|
||||||
@@ -101,20 +101,19 @@ const TagOperationParametersObject = ({
|
|||||||
const content = (
|
const content = (
|
||||||
<>
|
<>
|
||||||
{sortedProperties.map((property, index) => (
|
{sortedProperties.map((property, index) => (
|
||||||
<>
|
<Fragment key={index}>
|
||||||
{index !== 0 && <hr className="bg-medusa-border-base my-0" />}
|
{index !== 0 && <hr className="bg-medusa-border-base my-0" />}
|
||||||
<TagOperationParameters
|
<TagOperationParameters
|
||||||
schemaObject={{
|
schemaObject={{
|
||||||
...properties[property],
|
...properties[property],
|
||||||
parameterName: property,
|
parameterName: property,
|
||||||
}}
|
}}
|
||||||
key={index}
|
|
||||||
isRequired={
|
isRequired={
|
||||||
properties[property].isRequired ||
|
properties[property].isRequired ||
|
||||||
checkRequired(schema, property)
|
checkRequired(schema, property)
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
</>
|
</Fragment>
|
||||||
))}
|
))}
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -7,7 +7,12 @@ import getSectionId from "@/utils/get-section-id"
|
|||||||
import { useCallback, useEffect, useMemo, useRef, useState } from "react"
|
import { useCallback, useEffect, useMemo, useRef, useState } from "react"
|
||||||
import dynamic from "next/dynamic"
|
import dynamic from "next/dynamic"
|
||||||
import { useInView } from "react-intersection-observer"
|
import { useInView } from "react-intersection-observer"
|
||||||
import { isElmWindow, useScrollController, useSidebar } from "docs-ui"
|
import {
|
||||||
|
isElmWindow,
|
||||||
|
useIsBrowser,
|
||||||
|
useScrollController,
|
||||||
|
useSidebar,
|
||||||
|
} from "docs-ui"
|
||||||
import type { TagOperationCodeSectionProps } from "./CodeSection"
|
import type { TagOperationCodeSectionProps } from "./CodeSection"
|
||||||
import TagsOperationDescriptionSection from "./DescriptionSection"
|
import TagsOperationDescriptionSection from "./DescriptionSection"
|
||||||
import DividedLayout from "@/layouts/Divided"
|
import DividedLayout from "@/layouts/Divided"
|
||||||
@@ -44,9 +49,14 @@ const TagOperation = ({
|
|||||||
const nodeRef = useRef<Element | null>(null)
|
const nodeRef = useRef<Element | null>(null)
|
||||||
const { loading, removeLoading } = useLoading()
|
const { loading, removeLoading } = useLoading()
|
||||||
const { scrollableElement, scrollToTop } = useScrollController()
|
const { scrollableElement, scrollToTop } = useScrollController()
|
||||||
|
const { isBrowser } = useIsBrowser()
|
||||||
const root = useMemo(() => {
|
const root = useMemo(() => {
|
||||||
|
if (!isBrowser) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
return isElmWindow(scrollableElement) ? document.body : scrollableElement
|
return isElmWindow(scrollableElement) ? document.body : scrollableElement
|
||||||
}, [scrollableElement])
|
}, [isBrowser, scrollableElement])
|
||||||
const { ref } = useInView({
|
const { ref } = useInView({
|
||||||
threshold: 0.3,
|
threshold: 0.3,
|
||||||
rootMargin: `112px 0px 112px 0px`,
|
rootMargin: `112px 0px 112px 0px`,
|
||||||
@@ -83,6 +93,10 @@ const TagOperation = ({
|
|||||||
)
|
)
|
||||||
|
|
||||||
const scrollIntoView = useCallback(() => {
|
const scrollIntoView = useCallback(() => {
|
||||||
|
if (!isBrowser) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if (nodeRef.current && !checkElementInViewport(nodeRef.current, 0)) {
|
if (nodeRef.current && !checkElementInViewport(nodeRef.current, 0)) {
|
||||||
const elm = nodeRef.current as HTMLElement
|
const elm = nodeRef.current as HTMLElement
|
||||||
scrollToTop(
|
scrollToTop(
|
||||||
@@ -91,7 +105,7 @@ const TagOperation = ({
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
setShow(true)
|
setShow(true)
|
||||||
}, [scrollToTop, nodeRef])
|
}, [scrollToTop, nodeRef, isBrowser])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (nodeRef && nodeRef.current) {
|
if (nodeRef && nodeRef.current) {
|
||||||
|
|||||||
@@ -1,21 +1,16 @@
|
|||||||
"use client"
|
"use client"
|
||||||
|
|
||||||
import getSectionId from "@/utils/get-section-id"
|
|
||||||
import type { OpenAPIV3 } from "openapi-types"
|
import type { OpenAPIV3 } from "openapi-types"
|
||||||
import useSWR from "swr"
|
|
||||||
import type { Operation, PathsObject } from "@/types/openapi"
|
import type { Operation, PathsObject } from "@/types/openapi"
|
||||||
import { useSidebar, swrFetcher } from "docs-ui"
|
import { useSidebar } from "docs-ui"
|
||||||
import { Fragment, useEffect, useMemo } from "react"
|
import { Fragment, useEffect } from "react"
|
||||||
import dynamic from "next/dynamic"
|
import dynamic from "next/dynamic"
|
||||||
import type { TagOperationProps } from "../Operation"
|
import type { TagOperationProps } from "../Operation"
|
||||||
import { useArea } from "@/providers/area"
|
|
||||||
import clsx from "clsx"
|
import clsx from "clsx"
|
||||||
import { useBaseSpecs } from "@/providers/base-specs"
|
|
||||||
import getTagChildSidebarItems from "@/utils/get-tag-child-sidebar-items"
|
import getTagChildSidebarItems from "@/utils/get-tag-child-sidebar-items"
|
||||||
import { useLoading } from "@/providers/loading"
|
import { useLoading } from "@/providers/loading"
|
||||||
import DividedLoading from "@/components/DividedLoading"
|
import DividedLoading from "@/components/DividedLoading"
|
||||||
import { SidebarItemSections, SidebarItem, SidebarItemCategory } from "types"
|
import { SidebarItemSections, SidebarItem, SidebarItemCategory } from "types"
|
||||||
import basePathUrl from "../../../utils/base-path-url"
|
|
||||||
|
|
||||||
const TagOperation = dynamic<TagOperationProps>(
|
const TagOperation = dynamic<TagOperationProps>(
|
||||||
async () => import("../Operation")
|
async () => import("../Operation")
|
||||||
@@ -23,35 +18,12 @@ const TagOperation = dynamic<TagOperationProps>(
|
|||||||
|
|
||||||
export type TagPathsProps = {
|
export type TagPathsProps = {
|
||||||
tag: OpenAPIV3.TagObject
|
tag: OpenAPIV3.TagObject
|
||||||
|
paths: PathsObject
|
||||||
} & React.HTMLAttributes<HTMLDivElement>
|
} & React.HTMLAttributes<HTMLDivElement>
|
||||||
|
|
||||||
const TagPaths = ({ tag, className }: TagPathsProps) => {
|
const TagPaths = ({ tag, className, paths }: TagPathsProps) => {
|
||||||
const tagSlugName = useMemo(() => getSectionId([tag.name]), [tag])
|
|
||||||
const { area } = useArea()
|
|
||||||
const { items, addItems, findItemInSection } = useSidebar()
|
const { items, addItems, findItemInSection } = useSidebar()
|
||||||
const { baseSpecs } = useBaseSpecs()
|
|
||||||
const { loading } = useLoading()
|
const { loading } = useLoading()
|
||||||
// if paths are already loaded since through
|
|
||||||
// the expanded field, they're loaded directly
|
|
||||||
// otherwise, they're loaded using the API route
|
|
||||||
let paths: PathsObject =
|
|
||||||
baseSpecs?.expandedTags &&
|
|
||||||
Object.hasOwn(baseSpecs.expandedTags, tagSlugName)
|
|
||||||
? baseSpecs.expandedTags[tagSlugName]
|
|
||||||
: {}
|
|
||||||
const { data } = useSWR<{
|
|
||||||
paths: PathsObject
|
|
||||||
}>(
|
|
||||||
!Object.keys(paths).length
|
|
||||||
? basePathUrl(`/api/tag?tagName=${tagSlugName}&area=${area}`)
|
|
||||||
: null,
|
|
||||||
swrFetcher,
|
|
||||||
{
|
|
||||||
errorRetryInterval: 2000,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
paths = data?.paths || paths
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (paths) {
|
if (paths) {
|
||||||
|
|||||||
@@ -1,10 +1,13 @@
|
|||||||
import { useEffect, useMemo, useRef, useState } from "react"
|
"use client"
|
||||||
|
|
||||||
|
import { useEffect, useMemo, useRef } from "react"
|
||||||
import { SchemaObject } from "../../../../types/openapi"
|
import { SchemaObject } from "../../../../types/openapi"
|
||||||
import TagOperationParameters from "../../Operation/Parameters"
|
import TagOperationParameters from "../../Operation/Parameters"
|
||||||
import {
|
import {
|
||||||
Badge,
|
Badge,
|
||||||
CodeBlock,
|
CodeBlock,
|
||||||
isElmWindow,
|
isElmWindow,
|
||||||
|
useIsBrowser,
|
||||||
useScrollController,
|
useScrollController,
|
||||||
useSidebar,
|
useSidebar,
|
||||||
} from "docs-ui"
|
} from "docs-ui"
|
||||||
@@ -16,7 +19,6 @@ import useSchemaExample from "../../../../hooks/use-schema-example"
|
|||||||
import { InView } from "react-intersection-observer"
|
import { InView } from "react-intersection-observer"
|
||||||
import checkElementInViewport from "../../../../utils/check-element-in-viewport"
|
import checkElementInViewport from "../../../../utils/check-element-in-viewport"
|
||||||
import { singular } from "pluralize"
|
import { singular } from "pluralize"
|
||||||
import useResizeObserver from "@react-hook/resize-observer"
|
|
||||||
import clsx from "clsx"
|
import clsx from "clsx"
|
||||||
|
|
||||||
export type TagSectionSchemaProps = {
|
export type TagSectionSchemaProps = {
|
||||||
@@ -26,7 +28,6 @@ export type TagSectionSchemaProps = {
|
|||||||
|
|
||||||
const TagSectionSchema = ({ schema, tagName }: TagSectionSchemaProps) => {
|
const TagSectionSchema = ({ schema, tagName }: TagSectionSchemaProps) => {
|
||||||
const paramsRef = useRef<HTMLDivElement>(null)
|
const paramsRef = useRef<HTMLDivElement>(null)
|
||||||
const [maxCodeHeight, setMaxCodeHeight] = useState(0)
|
|
||||||
const { addItems, setActivePath, activePath } = useSidebar()
|
const { addItems, setActivePath, activePath } = useSidebar()
|
||||||
const tagSlugName = useMemo(() => getSectionId([tagName]), [tagName])
|
const tagSlugName = useMemo(() => getSectionId([tagName]), [tagName])
|
||||||
const formattedName = useMemo(
|
const formattedName = useMemo(
|
||||||
@@ -43,14 +44,16 @@ const TagSectionSchema = ({ schema, tagName }: TagSectionSchemaProps) => {
|
|||||||
skipNonRequired: false,
|
skipNonRequired: false,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
useResizeObserver(paramsRef, () => {
|
const { isBrowser } = useIsBrowser()
|
||||||
setMaxCodeHeight(paramsRef.current?.clientHeight || 0)
|
|
||||||
})
|
|
||||||
|
|
||||||
const { scrollableElement, scrollToElement } = useScrollController()
|
const { scrollableElement, scrollToElement } = useScrollController()
|
||||||
const root = useMemo(() => {
|
const root = useMemo(() => {
|
||||||
|
if (!isBrowser) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
return isElmWindow(scrollableElement) ? document.body : scrollableElement
|
return isElmWindow(scrollableElement) ? document.body : scrollableElement
|
||||||
}, [scrollableElement])
|
}, [isBrowser, scrollableElement])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
addItems(
|
addItems(
|
||||||
@@ -77,18 +80,26 @@ const TagSectionSchema = ({ schema, tagName }: TagSectionSchemaProps) => {
|
|||||||
}, [formattedName])
|
}, [formattedName])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
if (!isBrowser) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if (schemaSlug === (activePath || location.hash.replace("#", ""))) {
|
if (schemaSlug === (activePath || location.hash.replace("#", ""))) {
|
||||||
const elm = document.getElementById(schemaSlug) as HTMLElement
|
const elm = document.getElementById(schemaSlug) as HTMLElement
|
||||||
if (!checkElementInViewport(elm, 0)) {
|
if (!checkElementInViewport(elm, 0)) {
|
||||||
scrollToElement(elm)
|
scrollToElement(elm)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, [activePath, schemaSlug])
|
}, [activePath, schemaSlug, isBrowser])
|
||||||
|
|
||||||
const handleViewChange = (
|
const handleViewChange = (
|
||||||
inView: boolean,
|
inView: boolean,
|
||||||
entry: IntersectionObserverEntry
|
entry: IntersectionObserverEntry
|
||||||
) => {
|
) => {
|
||||||
|
if (!isBrowser) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
const section = entry.target
|
const section = entry.target
|
||||||
|
|
||||||
if (
|
if (
|
||||||
@@ -106,7 +117,7 @@ const TagSectionSchema = ({ schema, tagName }: TagSectionSchemaProps) => {
|
|||||||
<InView
|
<InView
|
||||||
as="div"
|
as="div"
|
||||||
id={schemaSlug}
|
id={schemaSlug}
|
||||||
initialInView={false}
|
initialInView={true}
|
||||||
onChange={handleViewChange}
|
onChange={handleViewChange}
|
||||||
root={root}
|
root={root}
|
||||||
threshold={0.1}
|
threshold={0.1}
|
||||||
@@ -128,10 +139,9 @@ const TagSectionSchema = ({ schema, tagName }: TagSectionSchemaProps) => {
|
|||||||
source={examples[0].content}
|
source={examples[0].content}
|
||||||
lang="json"
|
lang="json"
|
||||||
title={`The ${formattedName} Object`}
|
title={`The ${formattedName} Object`}
|
||||||
className={clsx(maxCodeHeight && "overflow-auto")}
|
className={clsx("overflow-auto")}
|
||||||
style={{
|
style={{
|
||||||
// remove padding + extra space
|
maxHeight: "100vh",
|
||||||
maxHeight: maxCodeHeight ? maxCodeHeight - 212 : "unset",
|
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -6,13 +6,13 @@ import { useEffect, useMemo, useState } from "react"
|
|||||||
import {
|
import {
|
||||||
isElmWindow,
|
isElmWindow,
|
||||||
swrFetcher,
|
swrFetcher,
|
||||||
|
useIsBrowser,
|
||||||
useScrollController,
|
useScrollController,
|
||||||
useSidebar,
|
useSidebar,
|
||||||
} from "docs-ui"
|
} from "docs-ui"
|
||||||
import dynamic from "next/dynamic"
|
import dynamic from "next/dynamic"
|
||||||
import type { SectionProps } from "../../Section"
|
import type { SectionProps } from "../../Section"
|
||||||
import type { MDXContentClientProps } from "../../MDXContent/Client"
|
import type { MDXContentClientProps } from "../../MDXContent/Client"
|
||||||
import TagPaths from "../Paths"
|
|
||||||
import DividedLayout from "@/layouts/Divided"
|
import DividedLayout from "@/layouts/Divided"
|
||||||
import LoadingProvider from "@/providers/loading"
|
import LoadingProvider from "@/providers/loading"
|
||||||
import SectionContainer from "../../Section/Container"
|
import SectionContainer from "../../Section/Container"
|
||||||
@@ -22,11 +22,12 @@ import clsx from "clsx"
|
|||||||
import { Feedback, Loading, Link } from "docs-ui"
|
import { Feedback, Loading, Link } from "docs-ui"
|
||||||
import { usePathname, useRouter } from "next/navigation"
|
import { usePathname, useRouter } from "next/navigation"
|
||||||
import formatReportLink from "@/utils/format-report-link"
|
import formatReportLink from "@/utils/format-report-link"
|
||||||
import { SchemaObject, TagObject } from "@/types/openapi"
|
import { PathsObject, SchemaObject, TagObject } from "@/types/openapi"
|
||||||
import useSWR from "swr"
|
|
||||||
import { TagSectionSchemaProps } from "./Schema"
|
import { TagSectionSchemaProps } from "./Schema"
|
||||||
import basePathUrl from "../../../utils/base-path-url"
|
|
||||||
import checkElementInViewport from "../../../utils/check-element-in-viewport"
|
import checkElementInViewport from "../../../utils/check-element-in-viewport"
|
||||||
|
import TagPaths from "../Paths"
|
||||||
|
import useSWR from "swr"
|
||||||
|
import basePathUrl from "../../../utils/base-path-url"
|
||||||
|
|
||||||
export type TagSectionProps = {
|
export type TagSectionProps = {
|
||||||
tag: TagObject
|
tag: TagObject
|
||||||
@@ -47,39 +48,30 @@ const MDXContentClient = dynamic<MDXContentClientProps>(
|
|||||||
}
|
}
|
||||||
) as React.FC<MDXContentClientProps>
|
) as React.FC<MDXContentClientProps>
|
||||||
|
|
||||||
const TagSection = ({ tag }: TagSectionProps) => {
|
const TagSectionComponent = ({ tag }: TagSectionProps) => {
|
||||||
const { activePath, setActivePath } = useSidebar()
|
const { activePath, setActivePath } = useSidebar()
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const [loadPaths, setLoadPaths] = useState(false)
|
const [loadData, setLoadData] = useState(false)
|
||||||
const slugTagName = useMemo(() => getSectionId([tag.name]), [tag])
|
const slugTagName = useMemo(() => getSectionId([tag.name]), [tag])
|
||||||
const { area } = useArea()
|
const { area } = useArea()
|
||||||
const pathname = usePathname()
|
const pathname = usePathname()
|
||||||
const { scrollableElement, scrollToTop } = useScrollController()
|
const { scrollableElement, scrollToTop } = useScrollController()
|
||||||
const { data } = useSWR<{
|
const { isBrowser } = useIsBrowser()
|
||||||
schema: SchemaObject
|
|
||||||
}>(
|
|
||||||
tag["x-associatedSchema"]
|
|
||||||
? basePathUrl(
|
|
||||||
`/api/schema?name=${tag["x-associatedSchema"].$ref}&area=${area}`
|
|
||||||
)
|
|
||||||
: null,
|
|
||||||
swrFetcher,
|
|
||||||
{
|
|
||||||
errorRetryInterval: 2000,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
const associatedSchema = data?.schema
|
|
||||||
|
|
||||||
const root = useMemo(() => {
|
const root = useMemo(() => {
|
||||||
|
if (!isBrowser) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
return isElmWindow(scrollableElement) ? document.body : scrollableElement
|
return isElmWindow(scrollableElement) ? document.body : scrollableElement
|
||||||
}, [scrollableElement])
|
}, [scrollableElement, isBrowser])
|
||||||
const { ref } = useInView({
|
const { ref, inView } = useInView({
|
||||||
threshold: 0.8,
|
threshold: 0.8,
|
||||||
rootMargin: `112px 0px 112px 0px`,
|
rootMargin: `112px 0px 112px 0px`,
|
||||||
root,
|
root,
|
||||||
onChange: (inView) => {
|
onChange: (inView) => {
|
||||||
if (inView && !loadPaths) {
|
if (inView && !loadData) {
|
||||||
setLoadPaths(true)
|
setLoadData(true)
|
||||||
}
|
}
|
||||||
if (inView) {
|
if (inView) {
|
||||||
// ensure that the hash link doesn't change if it links to an inner path
|
// ensure that the hash link doesn't change if it links to an inner path
|
||||||
@@ -97,8 +89,36 @@ const TagSection = ({ tag }: TagSectionProps) => {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
const { data: schemaData } = useSWR<{
|
||||||
|
schema: SchemaObject
|
||||||
|
}>(
|
||||||
|
loadData && tag["x-associatedSchema"]
|
||||||
|
? basePathUrl(
|
||||||
|
`/api/schema?name=${tag["x-associatedSchema"].$ref}&area=${area}`
|
||||||
|
)
|
||||||
|
: null,
|
||||||
|
swrFetcher,
|
||||||
|
{
|
||||||
|
errorRetryInterval: 2000,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
const { data: pathsData } = useSWR<{
|
||||||
|
paths: PathsObject
|
||||||
|
}>(
|
||||||
|
loadData
|
||||||
|
? basePathUrl(`/api/tag?tagName=${slugTagName}&area=${area}`)
|
||||||
|
: null,
|
||||||
|
swrFetcher,
|
||||||
|
{
|
||||||
|
errorRetryInterval: 2000,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
if (!isBrowser) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if (activePath && activePath.includes(slugTagName)) {
|
if (activePath && activePath.includes(slugTagName)) {
|
||||||
const tagName = activePath.split("_")
|
const tagName = activePath.split("_")
|
||||||
if (tagName.length === 1 && tagName[0] === slugTagName) {
|
if (tagName.length === 1 && tagName[0] === slugTagName) {
|
||||||
@@ -110,18 +130,18 @@ const TagSection = ({ tag }: TagSectionProps) => {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
} else if (tagName.length > 1 && tagName[0] === slugTagName) {
|
} else if (tagName.length > 1 && tagName[0] === slugTagName) {
|
||||||
setLoadPaths(true)
|
setLoadData(true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, [slugTagName, activePath])
|
}, [slugTagName, activePath, isBrowser])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={clsx("min-h-screen", !loadPaths && "relative")}
|
className={clsx("min-h-screen", !loadData && "relative")}
|
||||||
id={slugTagName}
|
id={slugTagName}
|
||||||
|
ref={ref}
|
||||||
>
|
>
|
||||||
<DividedLayout
|
<DividedLayout
|
||||||
ref={ref}
|
|
||||||
mainContent={
|
mainContent={
|
||||||
<SectionContainer noDivider={true}>
|
<SectionContainer noDivider={true}>
|
||||||
<h2>{tag.name}</h2>
|
<h2>{tag.name}</h2>
|
||||||
@@ -159,17 +179,17 @@ const TagSection = ({ tag }: TagSectionProps) => {
|
|||||||
}
|
}
|
||||||
codeContent={<></>}
|
codeContent={<></>}
|
||||||
/>
|
/>
|
||||||
{associatedSchema && (
|
{schemaData && (
|
||||||
<TagSectionSchema schema={associatedSchema} tagName={tag.name} />
|
<TagSectionSchema schema={schemaData.schema} tagName={tag.name} />
|
||||||
)}
|
)}
|
||||||
{loadPaths && (
|
{loadData && pathsData && (
|
||||||
<LoadingProvider initialLoading={true}>
|
<LoadingProvider initialLoading={true}>
|
||||||
<TagPaths tag={tag} />
|
<TagPaths tag={tag} paths={pathsData.paths} />
|
||||||
</LoadingProvider>
|
</LoadingProvider>
|
||||||
)}
|
)}
|
||||||
{!loadPaths && <SectionDivider className="lg:!-left-1" />}
|
{!loadData && <SectionDivider className="lg:!-left-1" />}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export default TagSection
|
export default TagSectionComponent
|
||||||
@@ -1,120 +1,20 @@
|
|||||||
"use client"
|
import { OpenAPIV3 } from "openapi-types"
|
||||||
|
import { TagSectionProps } from "./Section"
|
||||||
import type { OpenAPIV3 } from "openapi-types"
|
|
||||||
import { useEffect, useState } from "react"
|
|
||||||
import useSWR from "swr"
|
|
||||||
import { useBaseSpecs } from "@/providers/base-specs"
|
|
||||||
import dynamic from "next/dynamic"
|
import dynamic from "next/dynamic"
|
||||||
import type { TagSectionProps } from "./Section"
|
|
||||||
import { useArea } from "@/providers/area"
|
|
||||||
import { swrFetcher, useSidebar } from "docs-ui"
|
|
||||||
import getSectionId from "@/utils/get-section-id"
|
|
||||||
import { ExpandedDocument } from "@/types/openapi"
|
|
||||||
import getTagChildSidebarItems from "@/utils/get-tag-child-sidebar-items"
|
|
||||||
import { SidebarItem, SidebarItemSections } from "types"
|
|
||||||
import basePathUrl from "../../utils/base-path-url"
|
|
||||||
import { useRouter } from "next/navigation"
|
|
||||||
|
|
||||||
const TagSection = dynamic<TagSectionProps>(
|
const TagSection = dynamic<TagSectionProps>(
|
||||||
async () => import("./Section")
|
async () => import("./Section")
|
||||||
) as React.FC<TagSectionProps>
|
) as React.FC<TagSectionProps>
|
||||||
|
|
||||||
export type TagsProps = React.HTMLAttributes<HTMLDivElement>
|
type TagsProps = {
|
||||||
|
tags?: OpenAPIV3.TagObject[]
|
||||||
function getCurrentTag() {
|
|
||||||
return typeof location !== "undefined"
|
|
||||||
? location.hash.replace("#", "").split("_")[0]
|
|
||||||
: ""
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const Tags = () => {
|
const Tags = ({ tags }: TagsProps) => {
|
||||||
const [tags, setTags] = useState<OpenAPIV3.TagObject[]>([])
|
|
||||||
const [loadData, setLoadData] = useState<boolean>(false)
|
|
||||||
const [expand, setExpand] = useState<string>("")
|
|
||||||
const { baseSpecs, setBaseSpecs } = useBaseSpecs()
|
|
||||||
const { activePath, addItems, setActivePath } = useSidebar()
|
|
||||||
const { area, prevArea } = useArea()
|
|
||||||
const router = useRouter()
|
|
||||||
|
|
||||||
const { data } = useSWR<ExpandedDocument>(
|
|
||||||
loadData && !baseSpecs
|
|
||||||
? basePathUrl(`/api/base-specs?area=${area}&expand=${expand}`)
|
|
||||||
: null,
|
|
||||||
swrFetcher,
|
|
||||||
{
|
|
||||||
errorRetryInterval: 2000,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
setExpand(getCurrentTag())
|
|
||||||
}, [])
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
setLoadData(true)
|
|
||||||
}, [expand])
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (data) {
|
|
||||||
setBaseSpecs(data)
|
|
||||||
}
|
|
||||||
if (data?.tags) {
|
|
||||||
setTags(data.tags)
|
|
||||||
}
|
|
||||||
}, [data, setBaseSpecs])
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (baseSpecs) {
|
|
||||||
if (prevArea !== area) {
|
|
||||||
setBaseSpecs(null)
|
|
||||||
setLoadData(true)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
const itemsToAdd: SidebarItem[] = [
|
|
||||||
{
|
|
||||||
type: "separator",
|
|
||||||
},
|
|
||||||
]
|
|
||||||
|
|
||||||
if (baseSpecs.tags) {
|
|
||||||
baseSpecs.tags.forEach((tag) => {
|
|
||||||
const tagPathName = getSectionId([tag.name.toLowerCase()])
|
|
||||||
const childItems =
|
|
||||||
baseSpecs.expandedTags &&
|
|
||||||
Object.hasOwn(baseSpecs.expandedTags, tagPathName)
|
|
||||||
? getTagChildSidebarItems(baseSpecs.expandedTags[tagPathName])
|
|
||||||
: []
|
|
||||||
itemsToAdd.push({
|
|
||||||
type: "category",
|
|
||||||
title: tag.name,
|
|
||||||
children: childItems,
|
|
||||||
loaded: childItems.length > 0,
|
|
||||||
showLoadingIfEmpty: true,
|
|
||||||
onOpen: () => {
|
|
||||||
if (location.hash !== tagPathName) {
|
|
||||||
router.push(`#${tagPathName}`, {
|
|
||||||
scroll: false,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
if (activePath !== tagPathName) {
|
|
||||||
setActivePath(tagPathName)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
addItems(itemsToAdd, {
|
|
||||||
section: SidebarItemSections.DEFAULT,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}, [baseSpecs, prevArea, area])
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{tags.map((tag, index) => (
|
{tags?.map((tag) => (
|
||||||
<TagSection tag={tag} key={index} />
|
<TagSection tag={tag} key={tag.name} />
|
||||||
))}
|
))}
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -0,0 +1,20 @@
|
|||||||
|
"use server"
|
||||||
|
|
||||||
|
import { Area, ExpandedDocument } from "../types/openapi"
|
||||||
|
|
||||||
|
const URL = `${process.env.NEXT_PUBLIC_BASE_URL}${process.env.NEXT_PUBLIC_BASE_PATH}`
|
||||||
|
|
||||||
|
export async function getBaseSpecs(area: Area) {
|
||||||
|
try {
|
||||||
|
const res = await fetch(`${URL}/api/base-specs?area=${area}`, {
|
||||||
|
next: {
|
||||||
|
revalidate: 3000,
|
||||||
|
tags: [area],
|
||||||
|
},
|
||||||
|
}).then(async (res) => res.json())
|
||||||
|
|
||||||
|
return res as ExpandedDocument
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,28 +1,34 @@
|
|||||||
"use client"
|
"use client"
|
||||||
|
|
||||||
import { ExpandedDocument, SecuritySchemeObject } from "@/types/openapi"
|
import { ExpandedDocument, SecuritySchemeObject } from "@/types/openapi"
|
||||||
import { ReactNode, createContext, useContext, useState } from "react"
|
import {
|
||||||
|
ReactNode,
|
||||||
|
createContext,
|
||||||
|
useCallback,
|
||||||
|
useContext,
|
||||||
|
useEffect,
|
||||||
|
} from "react"
|
||||||
|
import { SidebarItem, SidebarItemSections } from "types"
|
||||||
|
import getSectionId from "../utils/get-section-id"
|
||||||
|
import getTagChildSidebarItems from "../utils/get-tag-child-sidebar-items"
|
||||||
|
import { useRouter } from "next/navigation"
|
||||||
|
import { useSidebar } from "docs-ui"
|
||||||
|
|
||||||
type BaseSpecsContextType = {
|
type BaseSpecsContextType = {
|
||||||
baseSpecs: ExpandedDocument | null
|
baseSpecs: ExpandedDocument | undefined
|
||||||
setBaseSpecs: React.Dispatch<React.SetStateAction<ExpandedDocument | null>>
|
|
||||||
getSecuritySchema: (securityName: string) => SecuritySchemeObject | null
|
getSecuritySchema: (securityName: string) => SecuritySchemeObject | null
|
||||||
}
|
}
|
||||||
|
|
||||||
const BaseSpecsContext = createContext<BaseSpecsContextType | null>(null)
|
const BaseSpecsContext = createContext<BaseSpecsContextType | null>(null)
|
||||||
|
|
||||||
type BaseSpecsProviderProps = {
|
type BaseSpecsProviderProps = {
|
||||||
initialSpecs?: ExpandedDocument | null
|
baseSpecs: ExpandedDocument | undefined
|
||||||
children?: ReactNode
|
children?: ReactNode
|
||||||
}
|
}
|
||||||
|
|
||||||
const BaseSpecsProvider = ({
|
const BaseSpecsProvider = ({ children, baseSpecs }: BaseSpecsProviderProps) => {
|
||||||
children,
|
const router = useRouter()
|
||||||
initialSpecs = null,
|
const { activePath, addItems, setActivePath, resetItems } = useSidebar()
|
||||||
}: BaseSpecsProviderProps) => {
|
|
||||||
const [baseSpecs, setBaseSpecs] = useState<ExpandedDocument | null>(
|
|
||||||
initialSpecs
|
|
||||||
)
|
|
||||||
|
|
||||||
const getSecuritySchema = (
|
const getSecuritySchema = (
|
||||||
securityName: string
|
securityName: string
|
||||||
@@ -43,11 +49,63 @@ const BaseSpecsProvider = ({
|
|||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const handleOpen = useCallback(
|
||||||
|
(tagPathName: string) => {
|
||||||
|
if (location.hash !== tagPathName) {
|
||||||
|
router.push(`#${tagPathName}`, {
|
||||||
|
scroll: false,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if (activePath !== tagPathName) {
|
||||||
|
setActivePath(tagPathName)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[activePath, router, setActivePath]
|
||||||
|
)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!baseSpecs) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const itemsToAdd: SidebarItem[] = [
|
||||||
|
{
|
||||||
|
type: "separator",
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
baseSpecs.tags?.forEach((tag) => {
|
||||||
|
const tagPathName = getSectionId([tag.name.toLowerCase()])
|
||||||
|
const childItems =
|
||||||
|
baseSpecs.expandedTags &&
|
||||||
|
Object.hasOwn(baseSpecs.expandedTags, tagPathName)
|
||||||
|
? getTagChildSidebarItems(baseSpecs.expandedTags[tagPathName])
|
||||||
|
: []
|
||||||
|
itemsToAdd.push({
|
||||||
|
type: "category",
|
||||||
|
title: tag.name,
|
||||||
|
children: childItems,
|
||||||
|
loaded: childItems.length > 0,
|
||||||
|
showLoadingIfEmpty: true,
|
||||||
|
onOpen: () => {
|
||||||
|
handleOpen(tagPathName)
|
||||||
|
},
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
addItems(itemsToAdd, {
|
||||||
|
section: SidebarItemSections.DEFAULT,
|
||||||
|
})
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
resetItems()
|
||||||
|
}
|
||||||
|
}, [baseSpecs])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<BaseSpecsContext.Provider
|
<BaseSpecsContext.Provider
|
||||||
value={{
|
value={{
|
||||||
baseSpecs,
|
baseSpecs,
|
||||||
setBaseSpecs,
|
|
||||||
getSecuritySchema,
|
getSecuritySchema,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -1,12 +1,9 @@
|
|||||||
"use client"
|
|
||||||
|
|
||||||
import {
|
import {
|
||||||
AnalyticsProvider,
|
AnalyticsProvider,
|
||||||
PageLoadingProvider,
|
PageLoadingProvider,
|
||||||
ScrollControllerProvider,
|
ScrollControllerProvider,
|
||||||
SiteConfigProvider,
|
SiteConfigProvider,
|
||||||
} from "docs-ui"
|
} from "docs-ui"
|
||||||
import BaseSpecsProvider from "./base-specs"
|
|
||||||
import SidebarProvider from "./sidebar"
|
import SidebarProvider from "./sidebar"
|
||||||
import SearchProvider from "./search"
|
import SearchProvider from "./search"
|
||||||
import { config } from "../config"
|
import { config } from "../config"
|
||||||
@@ -21,15 +18,13 @@ const Providers = ({ children }: ProvidersProps) => {
|
|||||||
<AnalyticsProvider writeKey={process.env.NEXT_PUBLIC_SEGMENT_API_KEY}>
|
<AnalyticsProvider writeKey={process.env.NEXT_PUBLIC_SEGMENT_API_KEY}>
|
||||||
<SiteConfigProvider config={config}>
|
<SiteConfigProvider config={config}>
|
||||||
<PageLoadingProvider>
|
<PageLoadingProvider>
|
||||||
<BaseSpecsProvider>
|
<ScrollControllerProvider scrollableSelector="#main">
|
||||||
<ScrollControllerProvider scrollableSelector="#main">
|
<SidebarProvider>
|
||||||
<SidebarProvider>
|
<MainNavProvider>
|
||||||
<MainNavProvider>
|
<SearchProvider>{children}</SearchProvider>
|
||||||
<SearchProvider>{children}</SearchProvider>
|
</MainNavProvider>
|
||||||
</MainNavProvider>
|
</SidebarProvider>
|
||||||
</SidebarProvider>
|
</ScrollControllerProvider>
|
||||||
</ScrollControllerProvider>
|
|
||||||
</BaseSpecsProvider>
|
|
||||||
</PageLoadingProvider>
|
</PageLoadingProvider>
|
||||||
</SiteConfigProvider>
|
</SiteConfigProvider>
|
||||||
</AnalyticsProvider>
|
</AnalyticsProvider>
|
||||||
|
|||||||
@@ -3,12 +3,9 @@
|
|||||||
import {
|
import {
|
||||||
SidebarProvider as UiSidebarProvider,
|
SidebarProvider as UiSidebarProvider,
|
||||||
usePageLoading,
|
usePageLoading,
|
||||||
usePrevious,
|
|
||||||
useScrollController,
|
useScrollController,
|
||||||
} from "docs-ui"
|
} from "docs-ui"
|
||||||
import { config } from "../config"
|
import { config } from "../config"
|
||||||
import { useCallback } from "react"
|
|
||||||
import { usePathname } from "next/navigation"
|
|
||||||
|
|
||||||
type SidebarProviderProps = {
|
type SidebarProviderProps = {
|
||||||
children?: React.ReactNode
|
children?: React.ReactNode
|
||||||
@@ -17,13 +14,6 @@ type SidebarProviderProps = {
|
|||||||
const SidebarProvider = ({ children }: SidebarProviderProps) => {
|
const SidebarProvider = ({ children }: SidebarProviderProps) => {
|
||||||
const { isLoading, setIsLoading } = usePageLoading()
|
const { isLoading, setIsLoading } = usePageLoading()
|
||||||
const { scrollableElement } = useScrollController()
|
const { scrollableElement } = useScrollController()
|
||||||
const pathname = usePathname()
|
|
||||||
const prevPathName = usePrevious(pathname)
|
|
||||||
|
|
||||||
const resetOnCondition = useCallback(
|
|
||||||
() => prevPathName !== undefined && pathname !== prevPathName,
|
|
||||||
[pathname, prevPathName]
|
|
||||||
)
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<UiSidebarProvider
|
<UiSidebarProvider
|
||||||
@@ -32,7 +22,6 @@ const SidebarProvider = ({ children }: SidebarProviderProps) => {
|
|||||||
shouldHandleHashChange={true}
|
shouldHandleHashChange={true}
|
||||||
scrollableElement={scrollableElement}
|
scrollableElement={scrollableElement}
|
||||||
initialItems={config.sidebar}
|
initialItems={config.sidebar}
|
||||||
resetOnCondition={resetOnCondition}
|
|
||||||
persistState={false}
|
persistState={false}
|
||||||
projectName="api"
|
projectName="api"
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -53,6 +53,9 @@ export default async function dereference({
|
|||||||
canParse: /.*/,
|
canParse: /.*/,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
dereference: {
|
||||||
|
circular: "ignore",
|
||||||
|
},
|
||||||
})) as unknown as Document
|
})) as unknown as Document
|
||||||
|
|
||||||
return document
|
return document
|
||||||
|
|||||||
@@ -5,8 +5,9 @@ import type { Operation, Document, ParsedPathItemObject } from "@/types/openapi"
|
|||||||
import readSpecDocument from "./read-spec-document"
|
import readSpecDocument from "./read-spec-document"
|
||||||
import getSectionId from "./get-section-id"
|
import getSectionId from "./get-section-id"
|
||||||
import dereference from "./dereference"
|
import dereference from "./dereference"
|
||||||
|
import { unstable_cache } from "next/cache"
|
||||||
|
|
||||||
export default async function getPathsOfTag(
|
async function getPathsOfTag_(
|
||||||
tagName: string,
|
tagName: string,
|
||||||
area: string
|
area: string
|
||||||
): Promise<Document> {
|
): Promise<Document> {
|
||||||
@@ -47,3 +48,13 @@ export default async function getPathsOfTag(
|
|||||||
paths: documents,
|
paths: documents,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const getPathsOfTag = unstable_cache(
|
||||||
|
async (tagName: string, area: string) => getPathsOfTag_(tagName, area),
|
||||||
|
["tag-paths"],
|
||||||
|
{
|
||||||
|
revalidate: 3600,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
export default getPathsOfTag
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
import { promises as fs } from "fs"
|
||||||
|
import { parseDocument } from "yaml"
|
||||||
|
import { SchemaObject } from "../types/openapi"
|
||||||
|
import dereference from "./dereference"
|
||||||
|
import { unstable_cache } from "next/cache"
|
||||||
|
|
||||||
|
async function getSchemaContent_(schemaPath: string, baseSchemasPath: string) {
|
||||||
|
const schemaContent = await fs.readFile(schemaPath, "utf-8")
|
||||||
|
const schema = parseDocument(schemaContent).toJS() as SchemaObject
|
||||||
|
|
||||||
|
// resolve references in schema
|
||||||
|
const dereferencedDocument = await dereference({
|
||||||
|
basePath: baseSchemasPath,
|
||||||
|
schemas: [schema],
|
||||||
|
})
|
||||||
|
|
||||||
|
return {
|
||||||
|
dereferencedDocument,
|
||||||
|
originalSchema: schema,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const getSchemaContent = unstable_cache(
|
||||||
|
async (schemaPath: string, baseSchemasPath: string) =>
|
||||||
|
getSchemaContent_(schemaPath, baseSchemasPath),
|
||||||
|
["tag-schema"]
|
||||||
|
)
|
||||||
|
|
||||||
|
export default getSchemaContent
|
||||||
@@ -1,5 +1,3 @@
|
|||||||
"use client"
|
|
||||||
|
|
||||||
import React from "react"
|
import React from "react"
|
||||||
import {
|
import {
|
||||||
BrowserProvider,
|
BrowserProvider,
|
||||||
|
|||||||
@@ -134,14 +134,22 @@ export const Sidebar = ({
|
|||||||
{!sidebarItems.default.length && !staticSidebarItems && (
|
{!sidebarItems.default.length && !staticSidebarItems && (
|
||||||
<Loading className="px-0" />
|
<Loading className="px-0" />
|
||||||
)}
|
)}
|
||||||
{sidebarItems.default.map((item, index) => (
|
{sidebarItems.default.map((item, index) => {
|
||||||
<SidebarItem
|
const itemKey =
|
||||||
item={item}
|
item.type === "separator"
|
||||||
key={index}
|
? index
|
||||||
expandItems={expandItems}
|
: item.type === "link"
|
||||||
hasNextItems={index !== sidebarItems.default.length - 1}
|
? `${item.path}-${index}`
|
||||||
/>
|
: `${item.title}-${index}`
|
||||||
))}
|
return (
|
||||||
|
<SidebarItem
|
||||||
|
item={item}
|
||||||
|
key={itemKey}
|
||||||
|
expandItems={expandItems}
|
||||||
|
hasNextItems={index !== sidebarItems.default.length - 1}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
})}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</CSSTransition>
|
</CSSTransition>
|
||||||
|
|||||||
@@ -40,7 +40,8 @@ export type SidebarContextType = {
|
|||||||
setActivePath: (path: string | null) => void
|
setActivePath: (path: string | null) => void
|
||||||
isLinkActive: (item: SidebarItem, checkChildren?: boolean) => boolean
|
isLinkActive: (item: SidebarItem, checkChildren?: boolean) => boolean
|
||||||
isChildrenActive: (item: SidebarItemCategory) => boolean
|
isChildrenActive: (item: SidebarItemCategory) => boolean
|
||||||
addItems: (item: SidebarItem[], options?: ActionOptionsType) => void
|
addItems: (items: SidebarItem[], options?: ActionOptionsType) => void
|
||||||
|
removeItems: (items: SidebarItem[]) => void
|
||||||
findItemInSection: (
|
findItemInSection: (
|
||||||
section: SidebarItem[],
|
section: SidebarItem[],
|
||||||
item: Partial<SidebarItem>,
|
item: Partial<SidebarItem>,
|
||||||
@@ -87,6 +88,10 @@ export type ActionType =
|
|||||||
type: "replace"
|
type: "replace"
|
||||||
replacementItems: SidebarSectionItems
|
replacementItems: SidebarSectionItems
|
||||||
}
|
}
|
||||||
|
| {
|
||||||
|
type: "remove"
|
||||||
|
items: SidebarItem[]
|
||||||
|
}
|
||||||
|
|
||||||
type LinksMap = Map<string, SidebarItemLinkWithParent>
|
type LinksMap = Map<string, SidebarItemLinkWithParent>
|
||||||
|
|
||||||
@@ -152,10 +157,46 @@ const getLinksMap = (
|
|||||||
return map
|
return map
|
||||||
}
|
}
|
||||||
|
|
||||||
export const reducer = (state: SidebarSectionItems, actionData: ActionType) => {
|
export const reducer = (
|
||||||
|
state: SidebarSectionItems,
|
||||||
|
actionData: ActionType
|
||||||
|
): SidebarSectionItems => {
|
||||||
if (actionData.type === "replace") {
|
if (actionData.type === "replace") {
|
||||||
return actionData.replacementItems
|
return actionData.replacementItems
|
||||||
}
|
}
|
||||||
|
if (actionData.type === "remove") {
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
default: state.default.filter((item) => {
|
||||||
|
if (item.type === "separator") {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
const found = actionData.items.some((itemToRemove) => {
|
||||||
|
if (
|
||||||
|
itemToRemove.type === "separator" ||
|
||||||
|
itemToRemove.type !== item.type
|
||||||
|
) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
itemToRemove.type === "category" ||
|
||||||
|
itemToRemove.type === "sub-category"
|
||||||
|
) {
|
||||||
|
return itemToRemove.title === item.title
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
itemToRemove.path === (item as SidebarItemLink).path &&
|
||||||
|
itemToRemove.title === (item as SidebarItemLink).title
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
return !found
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
}
|
||||||
const { type, options } = actionData
|
const { type, options } = actionData
|
||||||
let { items } = actionData
|
let { items } = actionData
|
||||||
|
|
||||||
@@ -306,6 +347,13 @@ export const SidebarProvider = ({
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const removeItems = (itemsToRemove: SidebarItem[]) => {
|
||||||
|
dispatch({
|
||||||
|
type: "remove",
|
||||||
|
items: itemsToRemove,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
const isLinkActive = useCallback(
|
const isLinkActive = useCallback(
|
||||||
(item: SidebarItem, checkChildren = false): boolean => {
|
(item: SidebarItem, checkChildren = false): boolean => {
|
||||||
if (item.type !== "link") {
|
if (item.type !== "link") {
|
||||||
@@ -516,6 +564,7 @@ export const SidebarProvider = ({
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (resetOnCondition?.()) {
|
if (resetOnCondition?.()) {
|
||||||
resetItems()
|
resetItems()
|
||||||
|
setActivePath(null)
|
||||||
}
|
}
|
||||||
}, [resetOnCondition, resetItems])
|
}, [resetOnCondition, resetItems])
|
||||||
|
|
||||||
@@ -575,6 +624,7 @@ export const SidebarProvider = ({
|
|||||||
items,
|
items,
|
||||||
currentItems,
|
currentItems,
|
||||||
addItems,
|
addItems,
|
||||||
|
removeItems,
|
||||||
activePath,
|
activePath,
|
||||||
setActivePath,
|
setActivePath,
|
||||||
isLinkActive: isLinkActive,
|
isLinkActive: isLinkActive,
|
||||||
|
|||||||
Reference in new issue
Block a user