Skip to content

Commit e9fde5c

Browse files
authored
feat($plugin-search): improve the native search algorithm (#1557)
1 parent 4c6fbcc commit e9fde5c

File tree

5 files changed

+107
-10
lines changed

5 files changed

+107
-10
lines changed

packages/@vuepress/plugin-search/SearchBox.vue

+5-8
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,8 @@
4545
</template>
4646

4747
<script>
48+
import matchQuery from './match-query'
49+
4850
/* global SEARCH_MAX_SUGGESTIONS, SEARCH_PATHS, SEARCH_HOTKEYS */
4951
export default {
5052
name: 'SearchBox',
@@ -76,11 +78,6 @@ export default {
7678
const { pages } = this.$site
7779
const max = this.$site.themeConfig.searchMaxSuggestions || SEARCH_MAX_SUGGESTIONS
7880
const localePath = this.$localePath
79-
const matches = item => (
80-
item
81-
&& item.title
82-
&& item.title.toLowerCase().indexOf(query) > -1
83-
)
8481
const res = []
8582
for (let i = 0; i < pages.length; i++) {
8683
if (res.length >= max) break
@@ -95,13 +92,13 @@ export default {
9592
continue
9693
}
9794
98-
if (matches(p)) {
95+
if (matchQuery(query, p)) {
9996
res.push(p)
10097
} else if (p.headers) {
10198
for (let j = 0; j < p.headers.length; j++) {
10299
if (res.length >= max) break
103100
const h = p.headers[j]
104-
if (matches(h)) {
101+
if (h.title && matchQuery(query, p, h.title)) {
105102
res.push(Object.assign({}, p, {
106103
path: p.path + '#' + h.slug,
107104
header: h
@@ -227,7 +224,7 @@ export default {
227224
background #fff
228225
width 20rem
229226
position absolute
230-
top 1.5rem
227+
top 2 rem
231228
border 1px solid darken($borderColor, 10%)
232229
border-radius 6px
233230
padding 0.4rem
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
import matchQuery from '../match-query'
2+
3+
describe('matchQuery', () => {
4+
const page = {
5+
title: 'HoMe PaGe',
6+
frontmatter: {
7+
tags: ['vuepress', 'is', 'jUst AwEsOme']
8+
}
9+
}
10+
11+
test('should match when query includes part of the page title', () => {
12+
const query = 'hom'
13+
14+
const match = matchQuery(query, page)
15+
16+
expect(match).toBe(true)
17+
})
18+
19+
test('should match when query includes the full page title', () => {
20+
const query = 'home page'
21+
22+
const match = matchQuery(query, page)
23+
24+
expect(match).toBe(true)
25+
})
26+
27+
test('should match when query includes a tag', () => {
28+
const query = 'vuepress'
29+
30+
const match = matchQuery(query, page)
31+
32+
expect(match).toBe(true)
33+
})
34+
35+
test('should match when query includes part of tag', () => {
36+
const query = 'just aw'
37+
38+
const match = matchQuery(query, page)
39+
40+
expect(match).toBe(true)
41+
})
42+
})
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
2+
import get from 'lodash/get'
3+
4+
export default (query, page, additionalStr = null) => {
5+
let domain = get(page, 'title', '')
6+
7+
if (get(page, 'frontmatter.tags')) {
8+
domain += ` ${page.frontmatter.tags.join(' ')}`
9+
}
10+
11+
if (additionalStr) {
12+
domain += ` ${additionalStr}`
13+
}
14+
15+
return matchTest(query, domain)
16+
}
17+
18+
const matchTest = (query, domain) => {
19+
const escapeRegExp = str => str.replace(/[-/\\^$*+?.()|[\]{}]/g, '\\$&')
20+
21+
const words = query
22+
.split(/\s+/g)
23+
.map(str => str.trim())
24+
.filter(str => !!str)
25+
const hasTrailingSpace = query.endsWith(' ')
26+
const searchRegex = new RegExp(
27+
words
28+
.map((word, index) => {
29+
if (words.length === index + 1 && !hasTrailingSpace) {
30+
// The last word - ok with the word being "startswith"-like
31+
return `(?=.*\\b${escapeRegExp(word)})`
32+
} else {
33+
// Not the last word - expect the whole word exactly
34+
return `(?=.*\\b${escapeRegExp(word)}\\b)`
35+
}
36+
})
37+
.join('') + '.+',
38+
'gi'
39+
)
40+
return searchRegex.test(domain)
41+
}
42+

packages/docs/docs/guide/frontmatter.md

+7
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,13 @@ lang = "en-US"
4949

5050
Title of current page.
5151

52+
### tags
53+
54+
- Type: `array`
55+
- Default: `undefined`
56+
57+
You can use tags to improve [built-in search](/theme/default-theme-config.html#built-in-search).
58+
5259
### lang
5360

5461
- Type: `string`

packages/docs/docs/theme/default-theme-config.md

+11-2
Original file line numberDiff line numberDiff line change
@@ -357,10 +357,19 @@ search: false
357357
```
358358

359359
::: tip
360-
Built-in Search only builds index from the title, `h2` and `h3` headers, if you need full text search, you can use [Algolia DocSearch](#algolia-docsearch).
360+
Built-in Search only builds index from the title, `h2` and `h3` headers and any tags added with `YAML front matter` as shown below, if you need full text search, you can use [Algolia Search](#algolia-search).
361361
:::
362362

363-
### Algolia DocSearch
363+
```yaml
364+
---
365+
tags:
366+
- configuration
367+
- theme
368+
- indexing
369+
---
370+
```
371+
372+
### Algolia Search
364373

365374
The `themeConfig.algolia` option allows you to use [Algolia DocSearch](https://community.algolia.com/docsearch/) to replace the simple built-in search. To enable it, you need to provide at least `apiKey` and `indexName`:
366375

0 commit comments

Comments
 (0)