Skip to content

Commit

Permalink
fix: basepath for search record (#1228)
Browse files Browse the repository at this point in the history
  • Loading branch information
abvthecity authored Jul 31, 2024
1 parent ab3e949 commit 5c57645
Show file tree
Hide file tree
Showing 3 changed files with 61 additions and 44 deletions.
8 changes: 3 additions & 5 deletions packages/ui/app/src/search/SearchHit.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import { PageRecord } from "./content/PageRecord";
import { PageRecordV2 } from "./content/PageRecordV2";
import { PageRecordV3 } from "./content/PageRecordV3";
import type { SearchRecord } from "./types";
import { getFullPathForSearchRecord } from "./util";
import { getSlugForSearchRecord } from "./util";

export declare namespace SearchHit {
export interface Props {
Expand All @@ -33,9 +33,7 @@ export const SearchHit: React.FC<SearchHit.Props> = ({
const closeMobileSidebar = useCloseMobileSidebar();
const closeSearchDialog = useCloseSearchDialog();

const fullPath = useMemo(() => {
return getFullPathForSearchRecord(hit, basePath);
}, [hit, basePath]);
const slug = getSlugForSearchRecord(hit, basePath);

const content = useMemo(() => {
return visitDiscriminatedUnion(hit)._visit<ReactElement | null>({
Expand All @@ -58,7 +56,7 @@ export const SearchHit: React.FC<SearchHit.Props> = ({
className={cn("flex w-full items-center space-x-4 space-y-1 rounded-md px-3 py-2 !no-underline", {
"bg-accent t-accent-contrast": isHovered,
})}
href={`/${fullPath}`}
href={`/${slug}`}
onMouseEnter={onMouseEnter}
onMouseLeave={onMouseLeave}
onClick={() => {
Expand Down
17 changes: 5 additions & 12 deletions packages/ui/app/src/search/SearchHits.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
import { FernScrollArea } from "@fern-ui/components";
import { useKeyboardPress } from "@fern-ui/react-commons";
import { Hit } from "instantsearch.js";
import { useRouter } from "next/router";
import React, { PropsWithChildren, useCallback, useEffect, useMemo, useRef, useState } from "react";
import React, { PropsWithChildren, useEffect, useMemo, useRef, useState } from "react";
import { useInfiniteHits, useInstantSearch } from "react-instantsearch";
import { useBasePath, useCloseSearchDialog } from "../atoms";
import { SearchHit } from "./SearchHit";
import type { SearchRecord } from "./types";
import { getFullPathForSearchRecord } from "./util";
import { getSlugForSearchRecord } from "./util";

export const EmptyStateView: React.FC<PropsWithChildren> = ({ children }) => {
return <div className="justify t-muted flex h-24 w-full flex-col items-center py-3">{children}</div>;
Expand All @@ -21,13 +20,6 @@ export const SearchHits: React.FC = () => {
const router = useRouter();
const closeSearchDialog = useCloseSearchDialog();

const getFullPathForHit = useCallback(
(hit: Hit<SearchRecord>) => {
return getFullPathForSearchRecord(hit, basePath);
},
[basePath],
);

const refs = useRef(new Map<string, HTMLAnchorElement>());

useEffect(() => {
Expand Down Expand Up @@ -96,8 +88,9 @@ export const SearchHits: React.FC = () => {
if (hoveredSearchHit == null) {
return;
}
const fullPath = getFullPathForHit(hoveredSearchHit.record);
void router.replace(`/${fullPath}`, undefined, {
const slug = getSlugForSearchRecord(hoveredSearchHit.record, basePath);
void router.push(`/${slug}`, undefined, {
// TODO: shallow=true if currently in long scrolling api reference and the hit is on the same page
shallow: false,
});
closeSearchDialog();
Expand Down
80 changes: 53 additions & 27 deletions packages/ui/app/src/search/util.ts
Original file line number Diff line number Diff line change
@@ -1,35 +1,27 @@
import { FernNavigation } from "@fern-api/fdr-sdk";
import { Algolia, FernNavigation } from "@fern-api/fdr-sdk";
import { SidebarVersionInfo } from "@fern-ui/fdr-utils";
import urljoin from "url-join";
import { UnreachableCaseError } from "ts-essentials";
import type { SearchRecord } from "./types";

export function getFullPathForSearchRecord(record: SearchRecord, basePath: string | undefined): string {
const parts =
basePath
?.split("/")
.map((part) => part.trim())
.filter((part) => part.length > 0) ?? [];
const leadingPath = getLeadingPathForSearchRecord(record);
if (record.type === "endpoint" || record.type === "page") {
if (record.versionSlug == null) {
parts.push(...leadingPath);
} else {
parts.push(record.versionSlug, ...leadingPath);
}
} else if (record.type === "endpoint-v2" || record.type === "page-v2") {
if (record.version == null) {
parts.push(...leadingPath);
} else {
// return `${record.version.urlSlug}/${leadingPath}`;
parts.push(record.version.urlSlug, ...leadingPath);
}
} else {
parts.push(...record.slug.split("/"));
}
return urljoin(parts);
export function getSlugForSearchRecord(record: SearchRecord, basePath: string | undefined): string {
return visitSearchRecord<string>(record)._visit({
v3: (record) => record.slug,
v2: (record) =>
FernNavigation.utils.slugjoin(
basePath || "/",
record.version?.urlSlug ?? "",
...getLeadingPathForSearchRecord(record),
),
v1: (record) =>
FernNavigation.utils.slugjoin(
basePath || "/",
record.versionSlug ?? "",
...getLeadingPathForSearchRecord(record),
),
});
}

function getLeadingPathForSearchRecord(record: SearchRecord): string[] {
function getLeadingPathForSearchRecord(record: Algolia.AlgoliaRecord): string[] {
switch (record.type) {
case "page":
case "endpoint":
Expand Down Expand Up @@ -99,3 +91,37 @@ function checkHasEndpoints(sidebar: FernNavigation.SidebarRootNode): boolean {
});
return hasEndpoints;
}

interface SearchRecordVisitor<T> {
v3: (
record:
| Algolia.AlgoliaRecord.PageV3
| Algolia.AlgoliaRecord.EndpointV3
| Algolia.AlgoliaRecord.WebsocketV3
| Algolia.AlgoliaRecord.WebhookV3,
) => T;
v2: (record: Algolia.AlgoliaRecord.PageV2 | Algolia.AlgoliaRecord.EndpointV2) => T;
v1: (record: Algolia.AlgoliaRecord.Page | Algolia.AlgoliaRecord.Endpoint) => T;
}

function visitSearchRecord<T>(record: Algolia.AlgoliaRecord): { _visit: (visitor: SearchRecordVisitor<T>) => T } {
return {
_visit: (visitor: SearchRecordVisitor<T>) => {
switch (record.type) {
case "page":
case "endpoint":
return visitor.v1(record);
case "page-v2":
case "endpoint-v2":
return visitor.v2(record);
case "page-v3":
case "endpoint-v3":
case "websocket-v3":
case "webhook-v3":
return visitor.v3(record);
default:
throw new UnreachableCaseError(record);
}
},
};
}

0 comments on commit 5c57645

Please sign in to comment.