docs: sort API reference sidebar items and sections (#12032)

This commit is contained in:
Shahed Nasser
2025-03-28 17:19:28 +02:00
committed by GitHub
parent acdcb11947
commit 9c90a39f1c
5 changed files with 99 additions and 23 deletions

View File

@@ -2,7 +2,7 @@
import type { OpenAPI } from "types" import type { OpenAPI } from "types"
import { findSidebarItem, useSidebar } from "docs-ui" import { findSidebarItem, useSidebar } from "docs-ui"
import { Fragment, Suspense, useEffect } from "react" import { Fragment, Suspense, useEffect, useMemo } from "react"
import dynamic from "next/dynamic" import dynamic from "next/dynamic"
import type { TagOperationProps } from "../Operation" import type { TagOperationProps } from "../Operation"
import clsx from "clsx" import clsx from "clsx"
@@ -10,6 +10,7 @@ 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 { Sidebar } from "types" import { Sidebar } from "types"
import { compareOperations } from "../../../utils/sort-operations-utils"
const TagOperation = dynamic<TagOperationProps>( const TagOperation = dynamic<TagOperationProps>(
async () => import("../Operation") async () => import("../Operation")
@@ -57,26 +58,53 @@ const TagPaths = ({ tag, className, paths }: TagPathsProps) => {
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
}, [paths, shownSidebar?.sidebar_id]) }, [paths, shownSidebar?.sidebar_id])
const sortedOperations = useMemo(() => {
const sortedOperations: {
endpointPath: string
method: string
operation: OpenAPI.Operation
}[] = []
Object.entries(paths).forEach(([endpointPath, operations]) => {
Object.entries(operations).forEach(([method, operation]) => {
sortedOperations.push({
endpointPath,
method,
operation: operation as OpenAPI.Operation,
})
})
})
sortedOperations.sort((a, b) => {
return compareOperations({
httpMethodA: a.method,
httpMethodB: b.method,
summaryA: a.operation.summary,
summaryB: b.operation.summary,
})
})
return sortedOperations
}, [paths])
return ( return (
<Suspense> <Suspense>
<div className={clsx("relative", className)}> <div className={clsx("relative", className)}>
{loading && <DividedLoading className="mt-7" />} {loading && <DividedLoading className="mt-7" />}
{Object.entries(paths).map(([endpointPath, operations], pathIndex) => ( {sortedOperations.map(
<Fragment key={pathIndex}> ({ endpointPath, method, operation }, operationIndex) => (
{Object.entries(operations).map( <Fragment key={operationIndex}>
([method, operation], operationIndex) => ( <TagOperation
<TagOperation method={method}
method={method} operation={operation}
operation={operation as OpenAPI.Operation} tag={tag}
tag={tag} key={`${operationIndex}`}
key={`${pathIndex}-${operationIndex}`} endpointPath={endpointPath}
endpointPath={endpointPath} className={clsx("pt-7")}
className={clsx("pt-7")} />
/> </Fragment>
) )
)} )}
</Fragment>
))}
</div> </div>
</Suspense> </Suspense>
) )

View File

@@ -2,10 +2,9 @@
import { OpenAPI } from "types" import { OpenAPI } from "types"
import { ReactNode, createContext, useContext, useEffect, useMemo } from "react" import { ReactNode, createContext, useContext, useEffect, useMemo } from "react"
import { Sidebar } from "types"
import getTagChildSidebarItems from "../utils/get-tag-child-sidebar-items" import getTagChildSidebarItems from "../utils/get-tag-child-sidebar-items"
import { useRouter } from "next/navigation" import { useRouter } from "next/navigation"
import { UpdateActionType, UpdateSidebarItemTypes, useSidebar } from "docs-ui" import { UpdateActionType, useSidebar } from "docs-ui"
import { getSectionId } from "docs-utils" import { getSectionId } from "docs-utils"
type BaseSpecsContextType = { type BaseSpecsContextType = {

View File

@@ -1,6 +1,12 @@
import { Sidebar } from "types"
declare global { declare global {
interface Window { interface Window {
// eslint-disable-next-line @typescript-eslint/no-explicit-any // eslint-disable-next-line @typescript-eslint/no-explicit-any
analytics?: any analytics?: any
} }
} }
export type SidebarItem = Sidebar.SidebarItem & {
http_method?: string
}

View File

@@ -1,9 +1,9 @@
import type { OpenAPI } from "types" import type { OpenAPI, Sidebar } from "types"
import dynamic from "next/dynamic" import dynamic from "next/dynamic"
import type { MethodLabelProps } from "@/components/MethodLabel" import type { MethodLabelProps } from "@/components/MethodLabel"
import { Sidebar } from "types"
import { getSectionId } from "docs-utils" import { getSectionId } from "docs-utils"
import { SidebarItem } from "../types/global"
import { compareOperations } from "./sort-operations-utils"
const MethodLabel = dynamic<MethodLabelProps>( const MethodLabel = dynamic<MethodLabelProps>(
async () => import("../components/MethodLabel") async () => import("../components/MethodLabel")
) as React.FC<MethodLabelProps> ) as React.FC<MethodLabelProps>
@@ -11,7 +11,7 @@ const MethodLabel = dynamic<MethodLabelProps>(
export default function getTagChildSidebarItems( export default function getTagChildSidebarItems(
paths: OpenAPI.PathsObject paths: OpenAPI.PathsObject
): Sidebar.SidebarItem[] { ): Sidebar.SidebarItem[] {
const items: Sidebar.SidebarItem[] = [] const items: SidebarItem[] = []
Object.entries(paths).forEach(([, operations]) => { Object.entries(paths).forEach(([, operations]) => {
Object.entries(operations).map(([method, operation]) => { Object.entries(operations).map(([method, operation]) => {
const definedOperation = operation as OpenAPI.Operation const definedOperation = operation as OpenAPI.Operation
@@ -30,9 +30,19 @@ export default function getTagChildSidebarItems(
<MethodLabel method={definedMethod} className="h-fit" /> <MethodLabel method={definedMethod} className="h-fit" />
), ),
loaded: true, loaded: true,
http_method: definedMethod,
}) })
}) })
}) })
return items return items
.sort((a, b) => {
return compareOperations({
httpMethodA: a.http_method || "",
httpMethodB: b.http_method || "",
summaryA: (a as Sidebar.SidebarItemLink).title,
summaryB: (b as Sidebar.SidebarItemLink).title,
})
})
.map(({ http_method, ...rest }) => rest)
} }

View File

@@ -0,0 +1,33 @@
export const getMethodOrder = (method: string) => {
switch (method) {
case "get":
return 1
case "post":
return 2
case "delete":
return 3
default:
return 4
}
}
export const compareOperations = ({
httpMethodA,
httpMethodB,
summaryA,
summaryB,
}: {
httpMethodA: string
httpMethodB: string
summaryA: string
summaryB: string
}) => {
const aOrder = getMethodOrder(httpMethodA)
const bOrder = getMethodOrder(httpMethodB)
if (aOrder !== bOrder) {
return aOrder - bOrder
}
return summaryA.localeCompare(summaryB)
}