Skip to content

Commit

Permalink
improvement: API reference anchor links now use dot notation (#365)
Browse files Browse the repository at this point in the history
  • Loading branch information
abvthecity authored Jan 17, 2024
1 parent f5edbbd commit 93de77b
Show file tree
Hide file tree
Showing 17 changed files with 87 additions and 98 deletions.
12 changes: 6 additions & 6 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ orbs:
jobs:
check:
machine:
image: ubuntu-2004:current
image: ubuntu-2004:2023.07.1
resource_class: large
steps:
- checkout
Expand All @@ -32,7 +32,7 @@ jobs:

organize-imports:
machine:
image: ubuntu-2004:current
image: ubuntu-2004:2023.07.1
resource_class: large
steps:
- checkout
Expand All @@ -52,7 +52,7 @@ jobs:

eslint:
machine:
image: ubuntu-2004:current
image: ubuntu-2004:2023.07.1
resource_class: large
steps:
- checkout
Expand All @@ -68,7 +68,7 @@ jobs:

compile:
machine:
image: ubuntu-2004:current
image: ubuntu-2004:2023.07.1
resource_class: large
steps:
- checkout
Expand All @@ -81,7 +81,7 @@ jobs:
test:
# this is machine because of the docker-utils tests
machine:
image: ubuntu-2004:current
image: ubuntu-2004:2023.07.1
steps:
- checkout
- node/install-packages:
Expand All @@ -95,7 +95,7 @@ jobs:

check-docs-release-is-allowed:
machine:
image: ubuntu-2004:current
image: ubuntu-2004:2023.07.1
steps:
- checkout
- node/install-packages:
Expand Down
2 changes: 1 addition & 1 deletion packages/ui/app/src/api-page/artifacts/ApiArtifacts.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ export const ApiArtifacts: React.FC<ApiArtifacts.Props> = ({ apiArtifacts }) =>

return (
<ApiPageMargins>
<div ref={setTargetRef} data-route={`/${slug}`} className="scroll-mt-20">
<div ref={setTargetRef} data-route={`/${slug}`.toLowerCase()} className="scroll-mt-20">
<H2 className="pt-20">{API_ARTIFACTS_TITLE}</H2>
<div className="t-muted mt-5 text-lg">
Official open-source client libraries for your favorite platforms.
Expand Down
9 changes: 1 addition & 8 deletions packages/ui/app/src/api-page/endpoints/Endpoint.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,7 @@ export declare namespace Endpoint {
}
}

export const Endpoint: React.FC<Endpoint.Props> = ({
endpoint,
fullSlug,
package: package_,
isLastInApi,
anchorIdParts,
}) => {
export const Endpoint: React.FC<Endpoint.Props> = ({ endpoint, fullSlug, package: package_, isLastInApi }) => {
const { setTargetRef } = useApiPageCenterElement({ slug: fullSlug });
const route = `/${fullSlug}`;

Expand All @@ -36,7 +30,6 @@ export const Endpoint: React.FC<Endpoint.Props> = ({
setContainerRef={setTargetRef}
package={package_}
hideBottomSeparator={isLastInApi}
anchorIdParts={[...anchorIdParts, endpoint.id]}
route={route}
/>
);
Expand Down
41 changes: 26 additions & 15 deletions packages/ui/app/src/api-page/endpoints/EndpointContent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import React, { useCallback, useEffect, useMemo, useState } from "react";
import { useInView } from "react-intersection-observer";
import { useApiDefinitionContext } from "../../api-context/useApiDefinitionContext";
import { useNavigationContext } from "../../navigation-context";
import { getAnchorId } from "../../util/anchor";
import { useViewportContext } from "../../viewport-context/useViewportContext";
import { type CodeExampleClient } from "../examples/code-example";
import { getCurlLines } from "../examples/curl-example/curlUtils";
Expand All @@ -20,7 +19,6 @@ export declare namespace EndpointContent {
export interface Props {
endpoint: APIV1Read.EndpointDefinition;
package: APIV1Read.ApiDefinitionPackage;
anchorIdParts: string[];
hideBottomSeparator?: boolean;
setContainerRef: (ref: HTMLElement | null) => void;
route: string;
Expand Down Expand Up @@ -74,12 +72,28 @@ function getAvailableExampleClients(example: APIV1Read.ExampleEndpointCall): Cod

const fernClientIdAtom = atomWithStorage<CodeExampleClient["id"]>("fern-client-id", DEFAULT_CLIENT.id);

const ERROR_ANCHOR_PREFIX = "response.error.";

function maybeGetStatusCodeFromAnchor(anchor: string | undefined): number | undefined {
if (anchor != null && anchor.startsWith(ERROR_ANCHOR_PREFIX)) {
// error anchor format is response.error.{statusCode}.property.a.b.c
// get {statusCode} from the anchor
const statusCodeString = anchor.split(".")[2];
if (statusCodeString != null) {
const statusCode = parseInt(statusCodeString, 10);
if (!isNaN(statusCode)) {
return statusCode;
}
}
}
return undefined;
}

export const EndpointContent: React.FC<EndpointContent.Props> = ({
endpoint,
package: package_,
hideBottomSeparator = false,
setContainerRef,
anchorIdParts,
route,
}) => {
const router = useRouter();
Expand Down Expand Up @@ -107,14 +121,12 @@ export const EndpointContent: React.FC<EndpointContent.Props> = ({
);

const [storedSelectedExampleClientId, setSelectedExampleClientId] = useAtom(fernClientIdAtom);
const [selectedErrorIndex, setSelectedErrorIndex] = useState<number | null>(null);
const [selectedErrorStatusCode, setSelectedErrorStatusCode] = useState<number | undefined>();

useEffect(() => {
const currentAnchor = router.asPath.split("#")[1];
const errorAnchor = getAnchorId([...anchorIdParts, "errors"]);
if (currentAnchor != null && currentAnchor.startsWith(`${errorAnchor}-`)) {
const idx = Number(currentAnchor.substring(errorAnchor.length + 1).split("-")[0]);
setSelectedErrorIndex(idx);
const statusCode = maybeGetStatusCodeFromAnchor(router.asPath.split("#")[1]);
if (statusCode != null) {
setSelectedErrorStatusCode(statusCode);
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
Expand All @@ -125,7 +137,7 @@ export const EndpointContent: React.FC<EndpointContent.Props> = ({
.sort((e1, e2) => e1.statusCode - e2.statusCode);
}, [endpoint.errorsV2]);

const selectedError = selectedErrorIndex == null ? null : errors[selectedErrorIndex] ?? null;
const selectedError = errors.find((error) => error.statusCode === selectedErrorStatusCode);
const example = useMemo(() => {
if (selectedError == null) {
// Look for success example
Expand Down Expand Up @@ -208,13 +220,13 @@ export const EndpointContent: React.FC<EndpointContent.Props> = ({
className={classNames("pb-20 pl-6 md:pl-12 pr-4 scroll-mt-20", {
"border-border-default-light dark:border-border-default-dark border-b": !hideBottomSeparator,
})}
onClick={() => setSelectedErrorIndex(null)}
onClick={() => setSelectedErrorStatusCode(undefined)}
ref={containerRef}
>
<div
className="flex min-w-0 flex-1 scroll-mt-16 flex-col justify-between lg:flex-row lg:space-x-[4vw]"
ref={setContainerRef}
data-route={route}
data-route={route.toLowerCase()}
>
<div
className="flex min-w-0 max-w-2xl flex-1 flex-col"
Expand All @@ -226,13 +238,12 @@ export const EndpointContent: React.FC<EndpointContent.Props> = ({
<EndpointContentLeft
endpoint={endpoint}
package={package_}
anchorIdParts={anchorIdParts}
apiSection={apiSection}
onHoverRequestProperty={onHoverRequestProperty}
onHoverResponseProperty={onHoverResponseProperty}
errors={errors}
selectedErrorIndex={selectedErrorIndex}
setSelectedErrorIndex={setSelectedErrorIndex}
selectedErrorStatusCode={selectedErrorStatusCode}
setSelectedErrorStatusCode={setSelectedErrorStatusCode}
route={route}
/>
)}
Expand Down
36 changes: 17 additions & 19 deletions packages/ui/app/src/api-page/endpoints/EndpointContentLeft.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,27 +24,25 @@ export declare namespace EndpointContentLeft {
export interface Props {
endpoint: APIV1Read.EndpointDefinition;
package: APIV1Read.ApiDefinitionPackage;
anchorIdParts: string[];
apiSection: DocsV1Read.ApiSection;
onHoverRequestProperty: (jsonPropertyPath: JsonPropertyPath, hovering: HoveringProps) => void;
onHoverResponseProperty: (jsonPropertyPath: JsonPropertyPath, hovering: HoveringProps) => void;
errors: APIV1Read.ErrorDeclarationV2[];
selectedErrorIndex: number | null;
setSelectedErrorIndex: (idx: number | null) => void;
selectedErrorStatusCode: number | undefined;
setSelectedErrorStatusCode: (idx: number | undefined) => void;
route: string;
}
}

const UnmemoizedEndpointContentLeft: React.FC<EndpointContentLeft.Props> = ({
endpoint,
package: package_,
anchorIdParts,
apiSection,
onHoverRequestProperty,
onHoverResponseProperty,
errors,
selectedErrorIndex,
setSelectedErrorIndex,
selectedErrorStatusCode,
setSelectedErrorStatusCode,
route,
}) => {
const requestExpandAll = useBooleanState(false);
Expand Down Expand Up @@ -77,14 +75,14 @@ const UnmemoizedEndpointContentLeft: React.FC<EndpointContentLeft.Props> = ({
{endpoint.path.pathParameters.length > 0 && (
<PathParametersSection
pathParameters={endpoint.path.pathParameters}
anchorIdParts={[...anchorIdParts, "path"]}
anchorIdParts={["request", "path"]}
route={route}
/>
)}
{endpoint.headers.length > 0 && (
<EndpointSection
title="Headers"
anchorIdParts={[...anchorIdParts, "headers"]}
anchorIdParts={["request", "header"]}
route={route}
showExpandCollapse={false}
expandAll={noop}
Expand All @@ -97,7 +95,7 @@ const UnmemoizedEndpointContentLeft: React.FC<EndpointContentLeft.Props> = ({
<EndpointParameter
name={header.key}
type={header.type}
anchorIdParts={[...anchorIdParts, "headers", header.key]}
anchorIdParts={["request", "header", header.key]}
route={route}
description={header.description}
descriptionContainsMarkdown={header.descriptionContainsMarkdown ?? false}
Expand All @@ -111,14 +109,14 @@ const UnmemoizedEndpointContentLeft: React.FC<EndpointContentLeft.Props> = ({
{endpoint.queryParameters.length > 0 && (
<QueryParametersSection
queryParameters={endpoint.queryParameters}
anchorIdParts={[...anchorIdParts, "query"]}
anchorIdParts={["request", "query"]}
route={route}
/>
)}
{endpoint.request != null && (
<EndpointSection
title="Request"
anchorIdParts={[...anchorIdParts, "request"]}
anchorIdParts={["request", "body"]}
route={route}
expandAll={requestExpandAll.setTrue}
collapseAll={requestExpandAll.setFalse}
Expand All @@ -127,7 +125,7 @@ const UnmemoizedEndpointContentLeft: React.FC<EndpointContentLeft.Props> = ({
<EndpointRequestSection
httpRequest={endpoint.request}
onHoverProperty={onHoverRequestProperty}
anchorIdParts={[...anchorIdParts, "request"]}
anchorIdParts={["request", "body"]}
route={route}
defaultExpandAll={requestExpandAll.value}
/>
Expand All @@ -136,7 +134,7 @@ const UnmemoizedEndpointContentLeft: React.FC<EndpointContentLeft.Props> = ({
{endpoint.response != null && (
<EndpointSection
title="Response"
anchorIdParts={[...anchorIdParts, "response"]}
anchorIdParts={["response", "body"]}
route={route}
expandAll={responseExpandAll.setTrue}
collapseAll={responseExpandAll.setFalse}
Expand All @@ -145,7 +143,7 @@ const UnmemoizedEndpointContentLeft: React.FC<EndpointContentLeft.Props> = ({
<EndpointResponseSection
httpResponse={endpoint.response}
onHoverProperty={onHoverResponseProperty}
anchorIdParts={[...anchorIdParts, "response"]}
anchorIdParts={["response", "body"]}
route={route}
defaultExpandAll={responseExpandAll.value}
/>
Expand All @@ -154,21 +152,21 @@ const UnmemoizedEndpointContentLeft: React.FC<EndpointContentLeft.Props> = ({
{apiSection.showErrors && errors.length > 0 && (
<EndpointSection
title="Errors"
anchorIdParts={[...anchorIdParts, "errors"]}
anchorIdParts={["response", "error"]}
route={route}
expandAll={errorExpandAll.setTrue}
collapseAll={errorExpandAll.setFalse}
showExpandCollapse={false}
>
<EndpointErrorsSection
errors={errors}
onClickError={(_, idx, event) => {
onClickError={(error, _, event) => {
event.stopPropagation();
setSelectedErrorIndex(idx);
setSelectedErrorStatusCode(error.statusCode);
}}
onHoverProperty={onHoverResponseProperty}
selectedErrorIndex={selectedErrorIndex}
anchorIdParts={[...anchorIdParts, "errors"]}
selectedErrorStatusCode={selectedErrorStatusCode}
anchorIdParts={["response", "error"]}
route={route}
defaultExpandAll={errorExpandAll.value}
/>
Expand Down
10 changes: 5 additions & 5 deletions packages/ui/app/src/api-page/endpoints/EndpointErrorsSection.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ export declare namespace EndpointErrorsSection {
index: number,
event: React.MouseEvent<HTMLButtonElement>
) => void;
selectedErrorIndex: number | null;
selectedErrorStatusCode: number | undefined;
anchorIdParts: string[];
route: string;
defaultExpandAll?: boolean;
Expand All @@ -20,7 +20,7 @@ export declare namespace EndpointErrorsSection {

export const EndpointErrorsSection: React.FC<EndpointErrorsSection.Props> = ({
errors,
selectedErrorIndex,
selectedErrorStatusCode,
onHoverProperty,
onClickError,
anchorIdParts,
Expand All @@ -32,14 +32,14 @@ export const EndpointErrorsSection: React.FC<EndpointErrorsSection.Props> = ({
{errors.map((error, idx) => {
return (
<EndpointError
key={idx}
key={error.statusCode}
error={error}
isFirst={idx === 0}
isLast={idx === errors.length - 1}
isSelected={idx === selectedErrorIndex}
isSelected={error.statusCode === selectedErrorStatusCode}
onClick={(event) => onClickError(error, idx, event)}
onHoverProperty={onHoverProperty}
anchorIdParts={[...anchorIdParts, `${idx}`]}
anchorIdParts={[...anchorIdParts, `${error.statusCode}`]}
route={route}
availability={error.availability}
defaultExpandAll={defaultExpandAll}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ export const EndpointParameter: React.FC<EndpointParameter.Props> = ({
const anchorRoute = `${route}#${anchorId}`;
return (
<div
data-route={anchorRoute}
data-route={anchorRoute.toLowerCase()}
id={anchorId}
className="group/anchor-container relative flex scroll-mt-20 flex-col gap-2 py-3"
>
Expand Down
2 changes: 1 addition & 1 deletion packages/ui/app/src/api-page/endpoints/EndpointSection.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ export const EndpointSection: React.FC<EndpointSection.Props> = ({
const anchorId = getAnchorId(anchorIdParts);
const anchorRoute = `${route}#${anchorId}`;
return (
<div ref={ref} data-route={anchorRoute} id={anchorId} className="flex scroll-mt-20 flex-col">
<div ref={ref} data-route={anchorRoute.toLowerCase()} id={anchorId} className="flex scroll-mt-20 flex-col">
<div className="group/anchor-container relative flex items-baseline gap-4 pb-3">
<h3 className="relative mt-0 flex items-center">
<AbsolutelyPositionedAnchor href={anchorRoute} />
Expand Down
2 changes: 1 addition & 1 deletion packages/ui/app/src/api-page/subpackages/ApiSubpackage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ export const ApiSubpackage: React.FC<ApiSubpackage.Props> = ({
return (
<>
<ApiPageMargins>
<div ref={setTargetRef} data-route={`/${slug}`} className="scroll-mt-16" />
<div ref={setTargetRef} data-route={`/${slug}`.toLowerCase()} className="scroll-mt-16" />
</ApiPageMargins>
{subpackage != null && (
<ApiPackageContents
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ export const ObjectProperty: React.FC<ObjectProperty.Props> = ({

return (
<div
data-route={anchorRoute}
data-route={anchorRoute.toLowerCase()}
id={anchorId}
className={classNames("flex relative flex-col py-3 scroll-mt-20", {
"px-3": !contextValue.isRootTypeDefinition,
Expand Down
Loading

1 comment on commit 93de77b

@vercel
Copy link

@vercel vercel bot commented on 93de77b Jan 17, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Successfully deployed to the following URLs:

fern-dev – ./packages/ui/fe-bundle

fern-dev-buildwithfern.vercel.app
fern-dev-git-main-buildwithfern.vercel.app
app-dev.buildwithfern.com

Please sign in to comment.