Skip to content

Commit

Permalink
fix: render markdown pages normally when isApiScrollingDisabled is tr…
Browse files Browse the repository at this point in the history
…ue (#1204)
  • Loading branch information
abvthecity authored Jul 26, 2024
1 parent c9b055a commit 50b56a5
Show file tree
Hide file tree
Showing 21 changed files with 232 additions and 103 deletions.
28 changes: 16 additions & 12 deletions packages/fdr-sdk/src/navigation/NodeCollector.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,8 @@ const NodeCollectorInstances = new WeakMap<NavigationNode, NodeCollector>();
export class NodeCollector {
private static readonly EMPTY = new NodeCollector(undefined);
private idToNode = new Map<FernNavigation.NodeId, NavigationNode>();
private slugToNode: Record<FernNavigation.Slug, NavigationNodeWithMetadataAndParents> = {};
private idToNodeParents = new Map<FernNavigation.NodeId, NavigationNode[]>();
private slugToNode = new Map<FernNavigation.Slug, NavigationNodeWithMetadataAndParents>();
private orphanedNodes: NavigationNodeWithMetadata[] = [];

public static collect(rootNode: NavigationNode | undefined): NodeCollector {
Expand All @@ -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;
Expand All @@ -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];
Expand Down Expand Up @@ -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))) {
Expand All @@ -119,7 +121,7 @@ export class NodeCollector {
});

private getSlugMap = once((): Map<string, NavigationNodeWithMetadata> => {
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<string, NavigationNodeWithMetadata> {
Expand All @@ -134,21 +136,23 @@ export class NodeCollector {
return this.idToNode.get(id);
}

public getSlugMapWithParents = once((): Map<string, NavigationNodeWithMetadataAndParents> => {
return new Map(Object.entries(this.slugToNode));
});
public getParents(id: FernNavigation.NodeId): NavigationNode[] {
return this.idToNodeParents.get(id) ?? [];
}

public getSlugMapWithParents = (): ReadonlyMap<FernNavigation.Slug, NavigationNodeWithMetadataAndParents> => {
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));
});

/**
Expand All @@ -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));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ export class ApiReferenceNavigationConverter {
idgen?: NodeIdGenerator,
lexicographic?: boolean,
disableEndpointPairs?: boolean,
disableLongScrolling?: boolean,
) {
return new ApiReferenceNavigationConverter(
apiSection,
Expand All @@ -30,6 +31,7 @@ export class ApiReferenceNavigationConverter {
idgen ?? new NodeIdGenerator(),
lexicographic,
disableEndpointPairs,
disableLongScrolling,
).convert();
}

Expand All @@ -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);
Expand Down Expand Up @@ -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,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ export class NavigationConfigConverter {
private basePath: string | undefined,
private lexicographic?: boolean,
private disableEndpointPairs?: boolean,
private disableLongScrolling?: boolean,
) {}

public static convert(
Expand All @@ -29,6 +30,7 @@ export class NavigationConfigConverter {
basePath: string | undefined,
lexicographic?: boolean,
disableEndpointPairs?: boolean,
disableLongScrolling?: boolean,
): FernNavigation.RootNode {
return new NavigationConfigConverter(
title,
Expand All @@ -38,6 +40,7 @@ export class NavigationConfigConverter {
basePath,
lexicographic,
disableEndpointPairs,
disableLongScrolling,
).convert();
}

Expand Down Expand Up @@ -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),
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { getNoIndexFromFrontmatter } from "./getNoIndexFromFrontmatter";
export function convertLoadDocsForUrlResponse(
response: DocsV2Read.LoadDocsForUrlResponse,
disableEndpointPairs: boolean = false,
disableLongScrolling?: boolean,
): FernNavigation.RootNode {
const noindexMap: Record<FernNavigation.PageId, boolean> = {};
Object.entries(response.definition.pages).forEach(([pageId, page]) => {
Expand All @@ -23,6 +24,7 @@ export function convertLoadDocsForUrlResponse(
response.baseUrl.basePath,
isLexicographicSortEnabled(response.baseUrl.domain),
disableEndpointPairs,
disableLongScrolling,
);
}

Expand Down
2 changes: 1 addition & 1 deletion packages/fdr-sdk/src/navigation/utils/findNode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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)) {
Expand Down
25 changes: 15 additions & 10 deletions packages/ui/app/src/api-page/ApiPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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<ApiPage.Props> = ({ initialApi, showErrors }) => {
export const ApiPage: React.FC<ApiPage.Props> = ({ initialApi, showErrors, disableLongScrolling }) => {
const hydrated = useIsReady();
const setDefinitions = useSetAtom(APIS_ATOM);

useEffect(() => {
setDefinitions((prev) => ({ ...prev, [initialApi.api]: initialApi }));
}, [initialApi, setDefinitions]);

return (
<ApiPageContext.Provider value={true}>
<ApiPackageContents
api={initialApi.api}
types={initialApi.types}
showErrors={showErrors}
apiDefinition={initialApi}
isLastInParentPackage={true}
anchorIdParts={EMPTY_ARRAY}
/>
{disableLongScrolling ? (
<SingleApiPageContent root={initialApi} showErrors={showErrors} />
) : (
<ApiPackageContents
api={initialApi.api}
types={initialApi.types}
showErrors={showErrors}
apiDefinition={initialApi}
isLastInParentPackage={true}
anchorIdParts={EMPTY_ARRAY}
/>
)}

{/* {isApiScrollingDisabled && (
<div className="mx-4 max-w-content-width md:mx-6 md:max-w-endpoint-width lg:mx-8">
Expand Down
54 changes: 54 additions & 0 deletions packages/ui/app/src/api-page/SingleApiPageContent.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<FernErrorBoundary component="ApiPackageContents">
{ResolvedApiDefinition.visit(apiDefinition, {
endpoint: (endpoint) => (
<Endpoint
api={root.api}
showErrors={showErrors}
endpoint={endpoint}
breadcrumbs={breadcrumbs}
isLastInApi={true}
types={root.types}
/>
),
webhook: (webhook) => (
<Webhook webhook={webhook} breadcrumbs={breadcrumbs} isLastInApi={true} types={root.types} />
),
websocket: (websocket) => (
<WebSocket api={root.api} websocket={websocket} isLastInApi={true} types={root.types} />
),
})}
</FernErrorBoundary>
);
}
5 changes: 2 additions & 3 deletions packages/ui/app/src/api-page/endpoints/Endpoint.tsx
Original file line number Diff line number Diff line change
@@ -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";
Expand All @@ -27,7 +27,6 @@ const UnmemoizedEndpoint: React.FC<Endpoint.Props> = ({
}) => {
const [isStream, setStream] = useAtom(FERN_STREAM_ATOM);
const resolvedPath = useResolvedPath();
const { isApiScrollingDisabled } = useFeatureFlags();

const endpointSlug = endpoint.stream != null && isStream ? endpoint.stream.slug : endpoint.slug;

Expand Down Expand Up @@ -57,7 +56,7 @@ const UnmemoizedEndpoint: React.FC<Endpoint.Props> = ({
endpoint={endpoint}
breadcrumbs={breadcrumbs}
containerRef={setTargetRef}
hideBottomSeparator={isLastInApi || isApiScrollingDisabled}
hideBottomSeparator={isLastInApi}
types={types}
/>
);
Expand Down
4 changes: 2 additions & 2 deletions packages/ui/app/src/api-page/endpoints/EndpointContent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -279,7 +278,8 @@ const UnmemoizedEndpointContent: React.FC<EndpointContent.Props> = ({
<div className="flex items-center justify-between">
<span>
<h1 className="fern-page-heading">
<AnimatedTitle>{endpoint.title}</AnimatedTitle>
{/* <AnimatedTitle>{endpoint.title}</AnimatedTitle> */}
{endpoint.title}
</h1>
{endpoint.availability != null && (
<span className="inline-block ml-2 align-text-bottom">
Expand Down
5 changes: 2 additions & 3 deletions packages/ui/app/src/api-page/web-socket/WebSocket.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -53,7 +53,6 @@ const WebhookContent: FC<WebSocket.Props> = ({ 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 });
Expand Down Expand Up @@ -109,7 +108,7 @@ const WebhookContent: FC<WebSocket.Props> = ({ websocket, isLastInApi, types })
<div className={"fern-endpoint-content"} ref={setTargetRef} data-route={route.toLowerCase()}>
<article
className={cn("scroll-mt-content max-w-content-width md:max-w-endpoint-width mx-auto", {
"border-default border-b mb-px pb-20": !isLastInApi && !isApiScrollingDisabled,
"border-default border-b mb-px pb-20": !isLastInApi,
})}
>
<header className="space-y-2.5 pt-8">
Expand Down
4 changes: 1 addition & 3 deletions packages/ui/app/src/api-page/webhooks/Webhook.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { useFeatureFlags } from "../../atoms";
import { useShouldLazyRender } from "../../hooks/useShouldLazyRender";
import { ResolvedTypeDefinition, ResolvedWebhookDefinition } from "../../resolver/types";
import { useApiPageCenterElement } from "../useApiPageCenterElement";
Expand All @@ -16,7 +15,6 @@ export declare namespace Webhook {

export const Webhook: React.FC<Webhook.Props> = ({ webhook, breadcrumbs, isLastInApi, types }) => {
const { setTargetRef } = useApiPageCenterElement({ slug: webhook.slug });
const { isApiScrollingDisabled } = useFeatureFlags();
const route = `/${webhook.slug}`;

// TODO: merge this with the Endpoint component
Expand All @@ -30,7 +28,7 @@ export const Webhook: React.FC<Webhook.Props> = ({ webhook, breadcrumbs, isLastI
webhook={webhook}
breadcrumbs={breadcrumbs}
setContainerRef={setTargetRef}
hideBottomSeparator={isLastInApi || isApiScrollingDisabled}
hideBottomSeparator={isLastInApi}
route={route}
types={types}
/>
Expand Down
Loading

0 comments on commit 50b56a5

Please sign in to comment.