diff --git a/.changeset/search-resulttype-page-linking.md b/.changeset/search-resulttype-page-linking.md new file mode 100644 index 000000000..6844b3078 --- /dev/null +++ b/.changeset/search-resulttype-page-linking.md @@ -0,0 +1,5 @@ +--- +"gitbook": patch +--- + +Search results from a page/title-level match now link to the top of the page instead of a section anchor; only section-level matches deep-link to their section. diff --git a/packages/gitbook/src/app/sites/dynamic/[mode]/[siteURL]/[siteData]/~gitbook/search/route.ts b/packages/gitbook/src/app/sites/dynamic/[mode]/[siteURL]/[siteData]/~gitbook/search/route.ts index 92f36e83b..172c83a72 100644 --- a/packages/gitbook/src/app/sites/dynamic/[mode]/[siteURL]/[siteData]/~gitbook/search/route.ts +++ b/packages/gitbook/src/app/sites/dynamic/[mode]/[siteURL]/[siteData]/~gitbook/search/route.ts @@ -143,6 +143,12 @@ function transformSitePageResult(args: { ? toEmbeddableLinkForPublishedContent(linker, spaceURL, pageItem.path) : linker.toLinkForContent(joinPathWithBaseURL(spaceURL, pageItem.path)); + // `resultType` marks whether the API matched the page at the title/page level or on a + // specific section. It is read defensively because it is not yet in the published + // `@gitbook/api` types; once present, page-level matches link to the top of the page. + const resultType = (pageItem as SearchPageResult & { resultType?: 'page' | 'section' }) + .resultType; + const page: ComputedPageResult = { type: 'page', id: `${spaceItem.id}/${pageItem.id}`, @@ -151,6 +157,7 @@ function transformSitePageResult(args: { pageId: pageItem.id, spaceId: spaceItem.id, score: pageItem.score, + resultType, breadcrumbs, }; diff --git a/packages/gitbook/src/components/Search/SearchPageResultItem.tsx b/packages/gitbook/src/components/Search/SearchPageResultItem.tsx index 9b70246bc..735c5a7e4 100644 --- a/packages/gitbook/src/components/Search/SearchPageResultItem.tsx +++ b/packages/gitbook/src/components/Search/SearchPageResultItem.tsx @@ -26,9 +26,15 @@ export const SearchPageResultItem = React.forwardRef(function SearchPageResultIt const language = useLanguage(); const bestSection = item.type === 'page' ? item.bestSection : undefined; - // When the section snippet is displayed, link to the section anchor so the - // link matches what the user sees. - const href = bestSection?.body ? bestSection.href : 'href' in item ? item.href : item.pathname; + // Section-level matches deep-link to their section anchor; page/title-level matches + // link to the top of the page even when a section snippet is shown as a preview. + const isPageLevelMatch = item.type === 'page' && item.resultType === 'page'; + const href = + bestSection?.body && !isPageLevelMatch + ? bestSection.href + : 'href' in item + ? item.href + : item.pathname; const emoji = 'emoji' in item ? item.emoji : undefined; const icon = 'icon' in item ? item.icon : undefined; diff --git a/packages/gitbook/src/components/Search/search-types.ts b/packages/gitbook/src/components/Search/search-types.ts index b1751edcf..3ed87deaa 100644 --- a/packages/gitbook/src/components/Search/search-types.ts +++ b/packages/gitbook/src/components/Search/search-types.ts @@ -20,6 +20,13 @@ export type ComputedPageResult = BaseComputedResult & { type: 'page'; pageId: string; spaceId: string; + /** + * Whether the API matched this result at the page/title level or on a specific + * section. Page-level matches link to the top of the page; section-level matches + * keep the section anchor. Optional to stay backward-compatible with API responses + * that do not yet return it. + */ + resultType?: 'page' | 'section'; breadcrumbs?: Array<{ icon?: IconName; label: string }>; /** The highest-scoring section for this page, used as a body snippet preview. */ bestSection?: {