mirror of
https://github.com/GitbookIO/gitbook.git
synced 2026-09-22 10:33:22 +00:00
Add breadcrumbs to search results (#2772)
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'gitbook': patch
|
||||
---
|
||||
|
||||
Add breadcrumbs to search results
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Icon } from '@gitbook/icons';
|
||||
import { tcls } from '@/lib/tailwind';
|
||||
import { Icon, type IconName } from '@gitbook/icons';
|
||||
import React from 'react';
|
||||
|
||||
import { tcls } from '@/lib/tailwind';
|
||||
import { Link } from '../primitives';
|
||||
import { HighlightQuery } from './HighlightQuery';
|
||||
import type { ComputedPageResult } from './server-actions';
|
||||
@@ -16,6 +16,14 @@ export const SearchPageResultItem = React.forwardRef(function SearchPageResultIt
|
||||
) {
|
||||
const { query, item, active } = props;
|
||||
|
||||
const breadcrumbs =
|
||||
item.breadcrumbs?.map((crumb) => (
|
||||
<span key={crumb.label} className="flex items-center gap-1">
|
||||
{crumb.icon ? <Icon className="size-3" icon={crumb.icon as IconName} /> : null}
|
||||
{crumb.label}
|
||||
</span>
|
||||
)) ?? [];
|
||||
|
||||
return (
|
||||
<Link
|
||||
ref={ref}
|
||||
@@ -53,18 +61,44 @@ export const SearchPageResultItem = React.forwardRef(function SearchPageResultIt
|
||||
/>
|
||||
</div>
|
||||
<div className={tcls('flex', 'flex-col', 'w-full')}>
|
||||
{item.spaceTitle ? (
|
||||
{breadcrumbs.length > 0 ? (
|
||||
<div
|
||||
className={tcls(
|
||||
'text-xs',
|
||||
'opacity-6',
|
||||
'contrast-more:opacity-11',
|
||||
'font-normal',
|
||||
'uppercase',
|
||||
'tracking-wider',
|
||||
'mb-1'
|
||||
'mb-1',
|
||||
'flex',
|
||||
'flex-wrap',
|
||||
'gap-x-2',
|
||||
'gap-y-1',
|
||||
'items-center'
|
||||
)}
|
||||
>
|
||||
{item.spaceTitle}
|
||||
{(breadcrumbs.length > 3
|
||||
? [
|
||||
...breadcrumbs.slice(0, 2),
|
||||
<Icon key="ellipsis" icon="ellipsis-h" className="size-3" />,
|
||||
...breadcrumbs.slice(-1),
|
||||
]
|
||||
: breadcrumbs
|
||||
).map((crumb, index) => (
|
||||
<>
|
||||
{index !== 0 ? (
|
||||
<Icon
|
||||
key={`${crumb.key}-icon`}
|
||||
icon="chevron-right"
|
||||
className="size-3"
|
||||
/>
|
||||
) : null}
|
||||
<span key={crumb.key} className="line-clamp-1">
|
||||
{crumb}
|
||||
</span>
|
||||
</>
|
||||
))}
|
||||
</div>
|
||||
) : null}
|
||||
<HighlightQuery query={query} text={item.title} />
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
'use server';
|
||||
|
||||
import { resolvePageId } from '@/lib/pages';
|
||||
import { findSiteSpaceById } from '@/lib/sites';
|
||||
import { findSiteSpaceById, getSiteStructureSections } from '@/lib/sites';
|
||||
import { filterOutNullable } from '@/lib/typescript';
|
||||
import { getV1BaseContext } from '@/lib/v1';
|
||||
import type {
|
||||
@@ -10,6 +10,8 @@ import type {
|
||||
SearchAIRecommendedQuestionStream,
|
||||
SearchPageResult,
|
||||
SearchSpaceResult,
|
||||
SiteSection,
|
||||
SiteSectionGroup,
|
||||
Space,
|
||||
} from '@gitbook/api';
|
||||
import type { GitBookBaseContext, GitBookSiteContext } from '@v2/lib/context';
|
||||
@@ -19,6 +21,7 @@ import type * as React from 'react';
|
||||
|
||||
import { joinPathWithBaseURL } from '@/lib/paths';
|
||||
import { isV2 } from '@/lib/v2';
|
||||
import type { IconName } from '@gitbook/icons';
|
||||
import { throwIfDataError } from '@v2/lib/data';
|
||||
import { getSiteURLDataFromMiddleware } from '@v2/lib/middleware';
|
||||
import { DocumentView } from '../DocumentView';
|
||||
@@ -46,8 +49,7 @@ export interface ComputedPageResult {
|
||||
pageId: string;
|
||||
spaceId: string;
|
||||
|
||||
/** When part of a multi-spaces search, the title of the space */
|
||||
spaceTitle?: string;
|
||||
breadcrumbs?: Array<{ icon?: IconName; label: string }>;
|
||||
}
|
||||
|
||||
export interface AskAnswerSource {
|
||||
@@ -258,7 +260,16 @@ async function searchSiteContent(
|
||||
return (
|
||||
await Promise.all(
|
||||
searchResults.map(async (spaceItem) => {
|
||||
const sections = getSiteStructureSections(structure).flatMap((item) =>
|
||||
item.object === 'site-section-group' ? [item, ...item.sections] : item
|
||||
);
|
||||
const siteSpace = findSiteSpaceById(structure, spaceItem.id);
|
||||
const siteSection = sections.find(
|
||||
(section) => section.id === siteSpace?.section
|
||||
) as SiteSection;
|
||||
const siteSectionGroup = siteSection?.sectionGroup
|
||||
? sections.find((sectionGroup) => sectionGroup.id === siteSection.sectionGroup)
|
||||
: null;
|
||||
|
||||
return Promise.all(
|
||||
spaceItem.pages.map((pageItem) =>
|
||||
@@ -267,6 +278,8 @@ async function searchSiteContent(
|
||||
spaceItem,
|
||||
space: siteSpace?.space,
|
||||
spaceURL: siteSpace?.urls.published,
|
||||
siteSection: siteSection ?? undefined,
|
||||
siteSectionGroup: (siteSectionGroup as SiteSectionGroup) ?? undefined,
|
||||
})
|
||||
)
|
||||
);
|
||||
@@ -348,9 +361,11 @@ async function transformSitePageResult(
|
||||
spaceItem: SearchSpaceResult;
|
||||
space?: Space;
|
||||
spaceURL?: string;
|
||||
siteSection?: SiteSection;
|
||||
siteSectionGroup?: SiteSectionGroup;
|
||||
}
|
||||
): Promise<OrderedComputedResult[]> {
|
||||
const { pageItem, spaceItem, space, spaceURL } = args;
|
||||
const { pageItem, spaceItem, space, spaceURL, siteSection, siteSectionGroup } = args;
|
||||
const { linker } = context;
|
||||
|
||||
const page: ComputedPageResult = {
|
||||
@@ -360,12 +375,26 @@ async function transformSitePageResult(
|
||||
href: spaceURL
|
||||
? linker.toLinkForContent(joinPathWithBaseURL(spaceURL, pageItem.path))
|
||||
: linker.toPathInSpace(pageItem.path),
|
||||
spaceTitle: space?.title,
|
||||
pageId: pageItem.id,
|
||||
spaceId: spaceItem.id,
|
||||
breadcrumbs: [
|
||||
siteSectionGroup && {
|
||||
icon: siteSectionGroup?.icon as IconName,
|
||||
label: siteSectionGroup.title,
|
||||
},
|
||||
siteSection && {
|
||||
icon: siteSection?.icon as IconName,
|
||||
label: siteSection.title,
|
||||
},
|
||||
(!siteSection || siteSection?.siteSpaces.length > 1) && space
|
||||
? {
|
||||
label: space?.title,
|
||||
}
|
||||
: undefined,
|
||||
].filter((item) => item !== undefined),
|
||||
};
|
||||
|
||||
const sections = await Promise.all(
|
||||
const pageSections = await Promise.all(
|
||||
pageItem.sections?.map<Promise<ComputedSectionResult>>(async (section) => ({
|
||||
type: 'section',
|
||||
id: `${page.id}/${section.id}`,
|
||||
@@ -379,5 +408,5 @@ async function transformSitePageResult(
|
||||
})) ?? []
|
||||
);
|
||||
|
||||
return [page, ...sections];
|
||||
return [page, ...pageSections];
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user