mirror of
https://github.com/GitbookIO/gitbook.git
synced 2026-10-05 12:54:22 +00:00
Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 68a881808f | |||
| 378f519231 |
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"gitbook": patch
|
||||
---
|
||||
|
||||
Search: link page/title matches to the top of the page instead of a section anchor. Section-level matches keep their heading anchor.
|
||||
@@ -7,6 +7,7 @@ import { Tooltip } from '../primitives';
|
||||
import { Emoji } from '../primitives/Emoji/Emoji';
|
||||
import { HighlightQuery } from './HighlightQuery';
|
||||
import { SearchResultItem } from './SearchResultItem';
|
||||
import { isPageTitleMatch } from './isPageTitleMatch';
|
||||
import type { MergedPageResult } from './reciprocalRankFusion';
|
||||
import type { ComputedPageResult } from './search-types';
|
||||
import type { LocalPageResult } from './useLocalSearchResults';
|
||||
@@ -26,9 +27,12 @@ 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;
|
||||
const pageHref = 'href' in item ? item.href : item.pathname;
|
||||
// Link to the section anchor only for genuine section matches. On a page/title
|
||||
// match, go to the top of the page even when a section snippet is shown, so
|
||||
// clicking a title result lands where the user expects.
|
||||
const href =
|
||||
bestSection?.body && !isPageTitleMatch(query, item.title) ? bestSection.href : pageHref;
|
||||
|
||||
const emoji = 'emoji' in item ? item.emoji : undefined;
|
||||
const icon = 'icon' in item ? item.icon : undefined;
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
import { describe, expect, it } from 'bun:test';
|
||||
import { isPageTitleMatch } from './isPageTitleMatch';
|
||||
|
||||
describe('isPageTitleMatch', () => {
|
||||
it('matches when the query equals the title', () => {
|
||||
expect(isPageTitleMatch('EDR Integrations', 'EDR Integrations')).toBe(true);
|
||||
});
|
||||
|
||||
it('is case-insensitive and tolerant of extra whitespace', () => {
|
||||
expect(isPageTitleMatch(' edr integrations ', 'EDR Integrations')).toBe(true);
|
||||
});
|
||||
|
||||
it('matches when word order differs', () => {
|
||||
expect(isPageTitleMatch('integrations edr', 'EDR Integrations')).toBe(true);
|
||||
});
|
||||
|
||||
it('matches a single title word', () => {
|
||||
expect(isPageTitleMatch('integrations', 'EDR Integrations')).toBe(true);
|
||||
});
|
||||
|
||||
it('does not match a section-level query whose words are absent from the title', () => {
|
||||
expect(isPageTitleMatch('selecting crowdstrike URL', 'EDR Integrations')).toBe(false);
|
||||
});
|
||||
|
||||
it('does not match when only some query words are in the title', () => {
|
||||
expect(isPageTitleMatch('EDR onboarding guide', 'EDR Integrations')).toBe(false);
|
||||
});
|
||||
|
||||
it('returns false for an empty query', () => {
|
||||
expect(isPageTitleMatch(' ', 'EDR Integrations')).toBe(false);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,20 @@
|
||||
/**
|
||||
* Whether the query targets the page itself (its title) rather than a specific
|
||||
* section within it. True when every word of the query appears in the page
|
||||
* title. We treat that as a page/title match so the result links to the top of
|
||||
* the page instead of dropping to a section anchor. Mirrors the per-word title
|
||||
* matching used for scoring in `reciprocalRankFusion`.
|
||||
*/
|
||||
export function isPageTitleMatch(query: string, title: string): boolean {
|
||||
const queryWords = query
|
||||
.toLowerCase()
|
||||
.split(/\s+/)
|
||||
.filter((word) => word.length > 0);
|
||||
|
||||
if (queryWords.length === 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const normalizedTitle = title.toLowerCase();
|
||||
return queryWords.every((word) => normalizedTitle.includes(word));
|
||||
}
|
||||
Reference in New Issue
Block a user