diff --git a/packages/fdr-sdk/src/navigation/NodeCollector.ts b/packages/fdr-sdk/src/navigation/NodeCollector.ts index e3c02a77b0..5105f498ba 100644 --- a/packages/fdr-sdk/src/navigation/NodeCollector.ts +++ b/packages/fdr-sdk/src/navigation/NodeCollector.ts @@ -25,7 +25,8 @@ const NodeCollectorInstances = new WeakMap(); export class NodeCollector { private static readonly EMPTY = new NodeCollector(undefined); private idToNode = new Map(); - private slugToNode: Record = {}; + private idToNodeParents = new Map(); + private slugToNode = new Map(); private orphanedNodes: NavigationNodeWithMetadata[] = []; public static collect(rootNode: NavigationNode | undefined): NodeCollector { @@ -45,7 +46,7 @@ export class NodeCollector { #lastNeighboringNode: NavigationNodeNeighbor | undefined; #setNode(slug: FernNavigation.Slug, node: NavigationNodeWithMetadata, parents: NavigationNode[]) { const toSet = { node, parents, prev: this.#lastNeighboringNode, next: undefined }; - this.slugToNode[slug] = toSet; + this.slugToNode.set(slug, toSet); if (isNeighbor(node) && !node.hidden) { this.#lastNeighboringNode = node; @@ -63,6 +64,7 @@ export class NodeCollector { } traverseNavigation(rootNode, (node, _index, parents) => { this.idToNode.set(node.id, node); + this.idToNodeParents.set(node.id, parents); // if the node is the default version, make a copy of it and "prune" the version slug from all children nodes const parent = parents[parents.length - 1]; @@ -95,7 +97,7 @@ export class NodeCollector { return; } - const existing = this.slugToNode[node.slug]; + const existing = this.slugToNode.get(node.slug); if (existing == null) { this.#setNode(node.slug, node, parents); } else if (!node.hidden && isPage(node) && (existing.node.hidden || !isPage(existing.node))) { @@ -119,7 +121,7 @@ export class NodeCollector { }); private getSlugMap = once((): Map => { - return new Map(Object.entries(this.slugToNode).map(([slug, { node }]) => [slug, node])); + return new Map([...this.slugToNode.entries()].map(([slug, { node }]) => [slug, node])); }); get slugMap(): Map { @@ -134,21 +136,23 @@ export class NodeCollector { return this.idToNode.get(id); } - public getSlugMapWithParents = once((): Map => { - return new Map(Object.entries(this.slugToNode)); - }); + public getParents(id: FernNavigation.NodeId): NavigationNode[] { + return this.idToNodeParents.get(id) ?? []; + } + + public getSlugMapWithParents = (): ReadonlyMap => { + return this.slugToNode; + }; public getSlugs = once((): string[] => { - return Object.keys(this.slugToNode); + return [...this.slugToNode.keys()]; }); /** * Returns a list of slugs for all pages in the navigation tree. This includes hidden pages. */ public getPageSlugs = once((): string[] => { - return Object.values(this.slugToNode) - .filter(({ node }) => isPage(node)) - .map(({ node }) => urljoin(node.slug)); + return [...this.slugToNode.values()].filter(({ node }) => isPage(node)).map(({ node }) => urljoin(node.slug)); }); /** @@ -157,7 +161,7 @@ export class NodeCollector { * This excludes hidden pages and noindex pages. */ public getIndexablePageSlugs = once((): string[] => { - return Object.values(this.slugToNode) + return [...this.slugToNode.values()] .filter(({ node }) => isPage(node) && node.hidden !== true) .filter(({ node }) => (hasMarkdown(node) ? node.noindex !== true : true)) .map(({ node }) => urljoin(node.slug)); diff --git a/packages/fdr-sdk/src/navigation/converters/ApiReferenceNavigationConverter.ts b/packages/fdr-sdk/src/navigation/converters/ApiReferenceNavigationConverter.ts index d2f5fe3489..8cda021a1a 100644 --- a/packages/fdr-sdk/src/navigation/converters/ApiReferenceNavigationConverter.ts +++ b/packages/fdr-sdk/src/navigation/converters/ApiReferenceNavigationConverter.ts @@ -21,6 +21,7 @@ export class ApiReferenceNavigationConverter { idgen?: NodeIdGenerator, lexicographic?: boolean, disableEndpointPairs?: boolean, + disableLongScrolling?: boolean, ) { return new ApiReferenceNavigationConverter( apiSection, @@ -30,6 +31,7 @@ export class ApiReferenceNavigationConverter { idgen ?? new NodeIdGenerator(), lexicographic, disableEndpointPairs, + disableLongScrolling, ).convert(); } @@ -48,6 +50,7 @@ export class ApiReferenceNavigationConverter { idgen: NodeIdGenerator, private lexicographic: boolean = false, private disableEndpointPairs: boolean = false, + private disableLongScrolling: boolean | undefined, ) { this.apiDefinitionId = FernNavigation.ApiDefinitionId(api.id); this.#holder = ApiDefinitionHolder.create(api); @@ -81,7 +84,8 @@ export class ApiReferenceNavigationConverter { apiDefinitionId: FernNavigation.ApiDefinitionId(this.apiSection.api), overviewPageId, noindex, - disableLongScrolling: this.apiSection.longScrolling === false ? true : undefined, + disableLongScrolling: + this.disableLongScrolling ?? (this.apiSection.longScrolling === false ? true : undefined), slug: slug.get(), icon: this.apiSection.icon, hidden: this.apiSection.hidden, diff --git a/packages/fdr-sdk/src/navigation/converters/NavigationConfigConverter.ts b/packages/fdr-sdk/src/navigation/converters/NavigationConfigConverter.ts index 06f82d99e5..38ff348082 100644 --- a/packages/fdr-sdk/src/navigation/converters/NavigationConfigConverter.ts +++ b/packages/fdr-sdk/src/navigation/converters/NavigationConfigConverter.ts @@ -19,6 +19,7 @@ export class NavigationConfigConverter { private basePath: string | undefined, private lexicographic?: boolean, private disableEndpointPairs?: boolean, + private disableLongScrolling?: boolean, ) {} public static convert( @@ -29,6 +30,7 @@ export class NavigationConfigConverter { basePath: string | undefined, lexicographic?: boolean, disableEndpointPairs?: boolean, + disableLongScrolling?: boolean, ): FernNavigation.RootNode { return new NavigationConfigConverter( title, @@ -38,6 +40,7 @@ export class NavigationConfigConverter { basePath, lexicographic, disableEndpointPairs, + disableLongScrolling, ).convert(); } @@ -289,12 +292,19 @@ export class NavigationConfigConverter { this.#idgen, this.lexicographic, this.disableEndpointPairs, + this.disableLongScrolling, ); }, changelog: (changelog) => ChangelogNavigationConverter.convert(changelog, this.noindexMap, parentSlug, this.#idgen), // Note: apiSection.node is imported from `navigation`, and is guaranteed to be a FernNavigation.ApiReferenceNode - apiV2: (apiSection) => apiSection.node as unknown as FernNavigation.ApiReferenceNode, + apiV2: (apiSection) => { + const node = apiSection.node as unknown as FernNavigation.ApiReferenceNode; + if (this.disableLongScrolling) { + node.disableLongScrolling = true; + } + return node; + }, changelogV3: (changelog) => changelog.node as unknown as FernNavigation.ChangelogNode, _other: (value) => assertNever(value as never), }); diff --git a/packages/fdr-sdk/src/navigation/utils/convertLoadDocsForUrlResponse.ts b/packages/fdr-sdk/src/navigation/utils/convertLoadDocsForUrlResponse.ts index 5dd86a8014..85a3c3ec39 100644 --- a/packages/fdr-sdk/src/navigation/utils/convertLoadDocsForUrlResponse.ts +++ b/packages/fdr-sdk/src/navigation/utils/convertLoadDocsForUrlResponse.ts @@ -7,6 +7,7 @@ import { getNoIndexFromFrontmatter } from "./getNoIndexFromFrontmatter"; export function convertLoadDocsForUrlResponse( response: DocsV2Read.LoadDocsForUrlResponse, disableEndpointPairs: boolean = false, + disableLongScrolling?: boolean, ): FernNavigation.RootNode { const noindexMap: Record = {}; Object.entries(response.definition.pages).forEach(([pageId, page]) => { @@ -23,6 +24,7 @@ export function convertLoadDocsForUrlResponse( response.baseUrl.basePath, isLexicographicSortEnabled(response.baseUrl.domain), disableEndpointPairs, + disableLongScrolling, ); } diff --git a/packages/fdr-sdk/src/navigation/utils/findNode.ts b/packages/fdr-sdk/src/navigation/utils/findNode.ts index ce192b45e3..65e94855e5 100644 --- a/packages/fdr-sdk/src/navigation/utils/findNode.ts +++ b/packages/fdr-sdk/src/navigation/utils/findNode.ts @@ -126,7 +126,7 @@ export function findNode(root: FernNavigation.RootNode, slug: FernNavigation.Slu return { type: "redirect", redirect }; } -function createBreadcrumb(nodes: NavigationNode[]): string[] { +export function createBreadcrumb(nodes: NavigationNode[]): string[] { const breadcrumb: string[] = []; nodes.forEach((node) => { if (!hasMetadata(node)) { diff --git a/packages/ui/app/src/api-page/ApiPage.tsx b/packages/ui/app/src/api-page/ApiPage.tsx index 11be9b1e5c..06b0b83268 100644 --- a/packages/ui/app/src/api-page/ApiPage.tsx +++ b/packages/ui/app/src/api-page/ApiPage.tsx @@ -6,32 +6,37 @@ import { ApiPageContext } from "../contexts/api-page"; import { ResolvedRootPackage } from "../resolver/types"; import { BuiltWithFern } from "../sidebar/BuiltWithFern"; import { ApiPackageContents } from "./ApiPackageContents"; +import { SingleApiPageContent } from "./SingleApiPageContent"; export declare namespace ApiPage { export interface Props { initialApi: ResolvedRootPackage; showErrors: boolean; + disableLongScrolling: boolean; } } -export const ApiPage: React.FC = ({ initialApi, showErrors }) => { +export const ApiPage: React.FC = ({ initialApi, showErrors, disableLongScrolling }) => { const hydrated = useIsReady(); const setDefinitions = useSetAtom(APIS_ATOM); - useEffect(() => { setDefinitions((prev) => ({ ...prev, [initialApi.api]: initialApi })); }, [initialApi, setDefinitions]); return ( - + {disableLongScrolling ? ( + + ) : ( + + )} {/* {isApiScrollingDisabled && (
diff --git a/packages/ui/app/src/api-page/SingleApiPageContent.tsx b/packages/ui/app/src/api-page/SingleApiPageContent.tsx new file mode 100644 index 0000000000..30548fdb5d --- /dev/null +++ b/packages/ui/app/src/api-page/SingleApiPageContent.tsx @@ -0,0 +1,54 @@ +import { FernNavigation } from "@fern-api/fdr-sdk"; +import { useAtomValue } from "jotai"; +import { ReactElement, useMemo } from "react"; +import { SLUG_ATOM, useNavigationNodes } from "../atoms"; +import { FernErrorBoundary } from "../components/FernErrorBoundary"; +import { ResolvedApiDefinition, ResolvedRootPackage, flattenRootPackage } from "../resolver/types"; +import { Endpoint } from "./endpoints/Endpoint"; +import { WebSocket } from "./web-socket/WebSocket"; +import { Webhook } from "./webhooks/Webhook"; + +interface SingleApiPageContentProps { + root: ResolvedRootPackage; + showErrors: boolean; +} + +export function SingleApiPageContent({ root, showErrors }: SingleApiPageContentProps): ReactElement | null { + const flattened = useMemo(() => { + return flattenRootPackage(root); + }, [root]); + const selectedSlug = useAtomValue(SLUG_ATOM); + const navigationNodes = useNavigationNodes(); + + const apiDefinition = flattened.apiDefinitions.find((api) => api.slug === selectedSlug); + + if (apiDefinition == null) { + return null; + } + + const parents = navigationNodes.getParents(apiDefinition.nodeId); + const breadcrumbs = FernNavigation.utils.createBreadcrumb(parents); + + return ( + + {ResolvedApiDefinition.visit(apiDefinition, { + endpoint: (endpoint) => ( + + ), + webhook: (webhook) => ( + + ), + websocket: (websocket) => ( + + ), + })} + + ); +} diff --git a/packages/ui/app/src/api-page/endpoints/Endpoint.tsx b/packages/ui/app/src/api-page/endpoints/Endpoint.tsx index cfe2bf86a9..c9628b21e2 100644 --- a/packages/ui/app/src/api-page/endpoints/Endpoint.tsx +++ b/packages/ui/app/src/api-page/endpoints/Endpoint.tsx @@ -1,6 +1,6 @@ import { useAtom } from "jotai"; import { memo, useEffect } from "react"; -import { FERN_STREAM_ATOM, useFeatureFlags, useResolvedPath } from "../../atoms"; +import { FERN_STREAM_ATOM, useResolvedPath } from "../../atoms"; import { useShouldLazyRender } from "../../hooks/useShouldLazyRender"; import { ResolvedEndpointDefinition, ResolvedTypeDefinition } from "../../resolver/types"; import { useApiPageCenterElement } from "../useApiPageCenterElement"; @@ -27,7 +27,6 @@ const UnmemoizedEndpoint: React.FC = ({ }) => { const [isStream, setStream] = useAtom(FERN_STREAM_ATOM); const resolvedPath = useResolvedPath(); - const { isApiScrollingDisabled } = useFeatureFlags(); const endpointSlug = endpoint.stream != null && isStream ? endpoint.stream.slug : endpoint.slug; @@ -57,7 +56,7 @@ const UnmemoizedEndpoint: React.FC = ({ endpoint={endpoint} breadcrumbs={breadcrumbs} containerRef={setTargetRef} - hideBottomSeparator={isLastInApi || isApiScrollingDisabled} + hideBottomSeparator={isLastInApi} types={types} /> ); diff --git a/packages/ui/app/src/api-page/endpoints/EndpointContent.tsx b/packages/ui/app/src/api-page/endpoints/EndpointContent.tsx index 9f45adf151..7b2926d04e 100644 --- a/packages/ui/app/src/api-page/endpoints/EndpointContent.tsx +++ b/packages/ui/app/src/api-page/endpoints/EndpointContent.tsx @@ -28,7 +28,6 @@ import { import { ApiPageDescription } from "../ApiPageDescription"; import { JsonPropertyPath } from "../examples/JsonPropertyPath"; import { CodeExample, generateCodeExamples } from "../examples/code-example"; -import { AnimatedTitle } from "./AnimatedTitle"; import { EndpointAvailabilityTag } from "./EndpointAvailabilityTag"; import { EndpointContentLeft, convertNameToAnchorPart } from "./EndpointContentLeft"; import { EndpointStreamingEnabledToggle } from "./EndpointStreamingEnabledToggle"; @@ -279,7 +278,8 @@ const UnmemoizedEndpointContent: React.FC = ({

- {endpoint.title} + {/* {endpoint.title} */} + {endpoint.title}

{endpoint.availability != null && ( diff --git a/packages/ui/app/src/api-page/web-socket/WebSocket.tsx b/packages/ui/app/src/api-page/web-socket/WebSocket.tsx index 291bfe53db..285a8cb958 100644 --- a/packages/ui/app/src/api-page/web-socket/WebSocket.tsx +++ b/packages/ui/app/src/api-page/web-socket/WebSocket.tsx @@ -5,7 +5,7 @@ import cn from "clsx"; import { Children, FC, HTMLAttributes, ReactNode, useMemo } from "react"; import { Wifi } from "react-feather"; import { PlaygroundButton } from "../../api-playground/PlaygroundButton"; -import { useFeatureFlags, useNavigationNodes } from "../../atoms"; +import { useNavigationNodes } from "../../atoms"; import { useSelectedEnvironmentId } from "../../atoms/environment"; import { AbsolutelyPositionedAnchor } from "../../commons/AbsolutelyPositionedAnchor"; import { useShouldLazyRender } from "../../hooks/useShouldLazyRender"; @@ -53,7 +53,6 @@ const WebhookContent: FC = ({ websocket, isLastInApi, types }) const maybeNode = nodes.get(websocket.nodeId); const node = maybeNode != null && FernNavigation.isApiLeaf(maybeNode) ? maybeNode : undefined; - const { isApiScrollingDisabled } = useFeatureFlags(); const route = `/${websocket.slug}`; const { setTargetRef } = useApiPageCenterElement({ slug: websocket.slug }); @@ -109,7 +108,7 @@ const WebhookContent: FC = ({ websocket, isLastInApi, types })
diff --git a/packages/ui/app/src/api-page/webhooks/Webhook.tsx b/packages/ui/app/src/api-page/webhooks/Webhook.tsx index 84aa183941..fd69f95390 100644 --- a/packages/ui/app/src/api-page/webhooks/Webhook.tsx +++ b/packages/ui/app/src/api-page/webhooks/Webhook.tsx @@ -1,4 +1,3 @@ -import { useFeatureFlags } from "../../atoms"; import { useShouldLazyRender } from "../../hooks/useShouldLazyRender"; import { ResolvedTypeDefinition, ResolvedWebhookDefinition } from "../../resolver/types"; import { useApiPageCenterElement } from "../useApiPageCenterElement"; @@ -16,7 +15,6 @@ export declare namespace Webhook { export const Webhook: React.FC = ({ webhook, breadcrumbs, isLastInApi, types }) => { const { setTargetRef } = useApiPageCenterElement({ slug: webhook.slug }); - const { isApiScrollingDisabled } = useFeatureFlags(); const route = `/${webhook.slug}`; // TODO: merge this with the Endpoint component @@ -30,7 +28,7 @@ export const Webhook: React.FC = ({ webhook, breadcrumbs, isLastI webhook={webhook} breadcrumbs={breadcrumbs} setContainerRef={setTargetRef} - hideBottomSeparator={isLastInApi || isApiScrollingDisabled} + hideBottomSeparator={isLastInApi} route={route} types={types} /> diff --git a/packages/ui/app/src/contexts/navigation-context/NavigationContextProvider.tsx b/packages/ui/app/src/contexts/navigation-context/NavigationContextProvider.tsx index 83c748a24f..ca9074a0fb 100644 --- a/packages/ui/app/src/contexts/navigation-context/NavigationContextProvider.tsx +++ b/packages/ui/app/src/contexts/navigation-context/NavigationContextProvider.tsx @@ -5,7 +5,7 @@ import { debounce } from "lodash-es"; import { Router, useRouter } from "next/router"; import { PropsWithChildren, useEffect, useMemo } from "react"; import { noop } from "ts-essentials"; -import { CURRENT_NODE_ATOM, useFeatureFlags } from "../../atoms"; +import { CURRENT_NODE_ATOM, useResolvedPath } from "../../atoms"; import { getRouteNodeWithAnchor } from "../../util/anchor"; import { NavigationContext } from "./NavigationContext"; @@ -85,7 +85,8 @@ function startScrollTracking(route: string, scrolledHere: boolean = false) { } export const NavigationContextProvider: React.FC = ({ children }) => { - const { isApiScrollingDisabled } = useFeatureFlags(); + const resolvedPath = useResolvedPath(); + const isApiScrollingDisabled = resolvedPath.type === "api-page" ? resolvedPath.disableLongScrolling : false; const router = useRouter(); const activeNavigatable = useAtomValue(CURRENT_NODE_ATOM); diff --git a/packages/ui/app/src/docs/DocsMainContent.tsx b/packages/ui/app/src/docs/DocsMainContent.tsx index 40444279c5..76e2ff503b 100644 --- a/packages/ui/app/src/docs/DocsMainContent.tsx +++ b/packages/ui/app/src/docs/DocsMainContent.tsx @@ -36,7 +36,11 @@ function DocsMainContentInternal(): ReactElement | null { return visitDiscriminatedUnion(resolvedPath)._visit({ "custom-markdown-page": (resolvedPath) => , "api-page": (resolvedPath) => ( - + ), changelog: (resolvedPath) => , "changelog-entry": (resolvedPath) => , diff --git a/packages/ui/app/src/hooks/useShouldLazyRender.ts b/packages/ui/app/src/hooks/useShouldLazyRender.ts index d9cb1157c6..0f656919f0 100644 --- a/packages/ui/app/src/hooks/useShouldLazyRender.ts +++ b/packages/ui/app/src/hooks/useShouldLazyRender.ts @@ -1,9 +1,8 @@ import { FernNavigation } from "@fern-api/fdr-sdk"; -import { useFeatureFlags, useIsReady, useIsSelectedSlug } from "../atoms"; +import { useIsReady, useIsSelectedSlug } from "../atoms"; export function useShouldLazyRender(slug: FernNavigation.Slug): boolean { - const { isApiScrollingDisabled } = useFeatureFlags(); const isSelectedSlug = useIsSelectedSlug(slug); const hydrated = useIsReady(); - return !isSelectedSlug && (!hydrated || isApiScrollingDisabled); + return !isSelectedSlug && !hydrated; } diff --git a/packages/ui/app/src/resolver/ResolvedPath.ts b/packages/ui/app/src/resolver/ResolvedPath.ts index a0701458ab..b7ae78ddf1 100644 --- a/packages/ui/app/src/resolver/ResolvedPath.ts +++ b/packages/ui/app/src/resolver/ResolvedPath.ts @@ -50,6 +50,7 @@ export declare namespace ResolvedPath { title: string; slug: FernNavigation.Slug; api: string; + disableLongScrolling: boolean; apiDefinition: ResolvedRootPackage; showErrors: boolean; neighbors: Neighbors; diff --git a/packages/ui/app/src/resolver/types.ts b/packages/ui/app/src/resolver/types.ts index 70ea15d457..b7caf67bb2 100644 --- a/packages/ui/app/src/resolver/types.ts +++ b/packages/ui/app/src/resolver/types.ts @@ -1,6 +1,7 @@ import type { APIV1Read, DocsV1Read, FdrAPI, FernNavigation } from "@fern-api/fdr-sdk"; import { assertNever } from "@fern-ui/core-utils"; import { sortBy } from "lodash-es"; +import { UnreachableCaseError } from "ts-essentials"; import { store } from "../atoms"; import { SELECTED_ENVIRONMENT_ATOM } from "../atoms/environment"; import type { BundledMDX } from "../mdx/types"; @@ -196,6 +197,27 @@ export function isWebSocket(definition: ResolvedApiDefinition): definition is Re return definition.type === "websocket"; } +interface ResolvedApiDefinitionVisitor { + endpoint(definition: ResolvedApiDefinition.Endpoint): T; + webhook(definition: ResolvedApiDefinition.Webhook): T; + websocket(definition: ResolvedApiDefinition.WebSocket): T; +} + +export const ResolvedApiDefinition = { + visit: (definition: ResolvedApiDefinition, visitor: ResolvedApiDefinitionVisitor): T => { + switch (definition.type) { + case "endpoint": + return visitor.endpoint(definition); + case "webhook": + return visitor.webhook(definition); + case "websocket": + return visitor.websocket(definition); + default: + throw new UnreachableCaseError(definition); + } + }, +}; + export interface ResolvedSubpackage extends WithMetadata, ResolvedWithApiDefinition { type: "subpackage"; // apiSectionId: FdrAPI.ApiDefinitionId; diff --git a/packages/ui/app/src/sidebar/nodes/SidebarApiPackageNode.tsx b/packages/ui/app/src/sidebar/nodes/SidebarApiPackageNode.tsx index da2d2f47fd..2f8c19523c 100644 --- a/packages/ui/app/src/sidebar/nodes/SidebarApiPackageNode.tsx +++ b/packages/ui/app/src/sidebar/nodes/SidebarApiPackageNode.tsx @@ -65,7 +65,8 @@ export function SidebarApiPackageNode({ ); } - const expanded = checkExpanded(node.id); + const expanded = + selectedNodeId === node.id || checkExpanded(node.id) || (childSelected && node.overviewPageId != null); const showIndicator = childSelected && !expanded; return ( @@ -81,6 +82,7 @@ export function SidebarApiPackageNode({ showIndicator={showIndicator} hidden={node.hidden} slug={node.overviewPageId != null ? node.slug : undefined} + selected={node.id === selectedNodeId} >
    => { - const holder = FernNavigation.ApiDefinitionHolder.create(apis[apiNode.apiDefinitionId]); - const typeResolver = new ApiTypeResolver(apis[apiNode.apiDefinitionId].types, mdxOptions); - return [ - apiNode.title, - await ApiDefinitionResolver.resolve( - apiNode, - holder, - typeResolver, - pages, - mdxOptions, - featureFlags, - domain, - ), - ]; - }), - ), - ); - return { - type: "custom-markdown-page", - slug: node.slug, - title: frontmatter.title ?? node.title, - mdx, - neighbors, - apis: resolvedApis, - }; + return resolveMarkdownPage(node, found, apis, pages, mdxOptions, featureFlags, domain, neighbors); } } +async function resolveMarkdownPage( + node: FernNavigation.NavigationNodePage, + found: FernNavigation.utils.Node.Found, + apis: Record, + pages: Record, + mdxOptions: FernSerializeMdxOptions | undefined, + featureFlags: FeatureFlags, + domain: string, + neighbors: ResolvedPath.Neighbors, +): Promise { + const pageId = FernNavigation.utils.getPageId(node); + if (pageId == null) { + return; + } + const pageContent = pages[pageId]; + if (pageContent == null) { + // eslint-disable-next-line no-console + console.error("Markdown content not found", pageId); + return; + } + const mdx = await serializeMdx(pageContent.markdown, { + ...mdxOptions, + filename: pageId, + frontmatterDefaults: { + title: node.title, + breadcrumbs: found.breadcrumb, + "edit-this-page-url": pageContent.editThisPageUrl, + "force-toc": featureFlags.isTocDefaultEnabled, + }, + }); + const frontmatter = typeof mdx === "string" ? {} : mdx.frontmatter; + + let apiNodes: FernNavigation.ApiReferenceNode[] = []; + if ( + pageContent.markdown.includes("EndpointRequestSnippet") || + pageContent.markdown.includes("EndpointResponseSnippet") + ) { + apiNodes = FernNavigation.utils.collectApiReferences(found.currentVersion ?? found.root); + } + const resolvedApis = Object.fromEntries( + await Promise.all( + apiNodes.map(async (apiNode): Promise<[string, ResolvedRootPackage]> => { + const holder = FernNavigation.ApiDefinitionHolder.create(apis[apiNode.apiDefinitionId]); + const typeResolver = new ApiTypeResolver(apis[apiNode.apiDefinitionId].types, mdxOptions); + return [ + apiNode.title, + await ApiDefinitionResolver.resolve( + apiNode, + holder, + typeResolver, + pages, + mdxOptions, + featureFlags, + domain, + ), + ]; + }), + ), + ); + return { + type: "custom-markdown-page", + slug: node.slug, + title: frontmatter.title ?? node.title, + mdx, + neighbors, + apis: resolvedApis, + }; +} + async function getNeighbor( node: FernNavigation.NavigationNodeNeighbor | undefined, pages: Record, diff --git a/packages/ui/docs-bundle/src/pages/api/fern-docs/resolve-api.ts b/packages/ui/docs-bundle/src/pages/api/fern-docs/resolve-api.ts index 39a178cc91..d05843086a 100644 --- a/packages/ui/docs-bundle/src/pages/api/fern-docs/resolve-api.ts +++ b/packages/ui/docs-bundle/src/pages/api/fern-docs/resolve-api.ts @@ -56,6 +56,7 @@ const resolveApiHandler: NextApiHandler = async ( const root = FernNavigation.utils.convertLoadDocsForUrlResponse( docsResponse.body, featureFlags.isBatchStreamToggleDisabled, + featureFlags.isApiScrollingDisabled, ); setMdxBundler(await getMdxBundler(featureFlags.useMdxBundler ? "mdx-bundler" : "next-mdx-remote")); diff --git a/packages/ui/docs-bundle/src/utils/getDocsPageProps.ts b/packages/ui/docs-bundle/src/utils/getDocsPageProps.ts index 58738e37ef..b752cd6d7f 100644 --- a/packages/ui/docs-bundle/src/utils/getDocsPageProps.ts +++ b/packages/ui/docs-bundle/src/utils/getDocsPageProps.ts @@ -7,9 +7,9 @@ import { getSearchConfig } from "@fern-ui/search-utils"; import { DocsPage, convertNavigatableToResolvedPath, - getSeoProps, getGitHubInfo, getGitHubRepo, + getSeoProps, setMdxBundler, } from "@fern-ui/ui"; import { FernUser, getAPIKeyInjectionConfigNode, getAuthEdgeConfig, verifyFernJWT } from "@fern-ui/ui/auth"; @@ -237,7 +237,11 @@ async function convertDocsToDocsPageProps({ } const featureFlags = await getFeatureFlags(xFernHost); - const root = FernNavigation.utils.convertLoadDocsForUrlResponse(docs, featureFlags.isBatchStreamToggleDisabled); + const root = FernNavigation.utils.convertLoadDocsForUrlResponse( + docs, + featureFlags.isBatchStreamToggleDisabled, + featureFlags.isApiScrollingDisabled, + ); const node = FernNavigation.utils.findNode(root, slug); if (node.type === "notFound") {