fix: improve search to find terms in page headings and body content
Karen Li committed
Apr 10, 2026 at 21:30 UTC
7b91f4b896f51852adacf2e58dfec3ec019cf7dd
3 files changed
+66
-1
src/hooks/__tests__/use-search.test.js
new
+47
@@ -0,0 +1,47 @@
1
+import {flattenHeadings} from '../use-search'
2
+
3
+describe('flattenHeadings', () => {
4
+ test('returns empty array for null/undefined input', () => {
5
+ expect(flattenHeadings(null)).toEqual([])
6
+ expect(flattenHeadings(undefined)).toEqual([])
7
+ })
8
+
9
+ test('returns empty array for empty items', () => {
10
+ expect(flattenHeadings([])).toEqual([])
11
+ })
12
+
13
+ test('flattens single level of headings', () => {
14
+ const items = [{title: 'Description'}, {title: 'Configuration'}]
15
+ expect(flattenHeadings(items)).toEqual(['Description', 'Configuration'])
16
+ })
17
+
18
+ test('flattens nested headings', () => {
19
+ const items = [
20
+ {
21
+ title: 'Description',
22
+ items: [{title: 'before'}, {title: 'min-release-age'}],
23
+ },
24
+ ]
25
+ expect(flattenHeadings(items)).toEqual(['Description', 'before', 'min-release-age'])
26
+ })
27
+
28
+ test('flattens deeply nested headings', () => {
29
+ const items = [
30
+ {
31
+ title: 'Top',
32
+ items: [
33
+ {
34
+ title: 'Mid',
35
+ items: [{title: 'Deep'}],
36
+ },
37
+ ],
38
+ },
39
+ ]
40
+ expect(flattenHeadings(items)).toEqual(['Top', 'Mid', 'Deep'])
41
+ })
42
+
43
+ test('skips items without a title', () => {
44
+ const items = [{items: [{title: 'child'}]}, {title: 'sibling'}]
45
+ expect(flattenHeadings(items)).toEqual(['child', 'sibling'])
46
+ })
47
+})
src/hooks/use-search.js
+11
@@ -6,6 +6,15 @@ import usePage from './use-page'
6
import * as getNav from '../util/get-nav'
7
import {CLI_PATH} from '../constants'
8
9
+export const flattenHeadings = items => {
10
+ if (!items) return []
11
+ return items.reduce((acc, item) => {
12
+ if (item.title) acc.push(item.title)
13
+ if (item.items) acc.push(...flattenHeadings(item.items))
14
+ return acc
15
+ }, [])
16
+}
17
+
18
const useSearchData = () => {
19
const data = useStaticQuery(graphql`
20
{
@@ -15,6 +24,7 @@ const useSearchData = () => {
24
frontmatter {
25
title
26
}
27
+ tableOfContents
28
body
29
}
30
}
@@ -40,6 +50,7 @@ const useSearchData = () => {
50
return {
51
path: node.path,
52
title: mdxNode.frontmatter.title,
53
+ headings: flattenHeadings(mdxNode.tableOfContents?.items).join(' '),
54
body: mdxNode.body,
55
}
56
})
src/util/search.worker.js
+8
-1
@@ -36,7 +36,14 @@ class Fuse {
36
const items = this.allItems.filter(item =>
37
item.path.startsWith(this.cliVersion.root) ? item.path.startsWith(this.cliVersion.current) : true,
38
)
39
- this.instances.set(this.cliVersion.current, new FuseJs(items, {threshold: 0.2, keys: ['title', 'body']}))
39
+ this.instances.set(
40
+ this.cliVersion.current,
41
+ new FuseJs(items, {
42
+ threshold: 0.2,
43
+ ignoreLocation: true,
44
+ keys: ['title', 'headings', 'body'],
45
+ }),
46
+ )
47
}
48
49
setItems(items) {