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