Skip to content

Commit e186625

Browse files
authored
Merge pull request leo-buneev#10 from bobheadlabs/master
add support for hooks functions, setting query from URL, search exclusion
2 parents 0929f7a + 587e5e1 commit e186625

File tree

4 files changed

+134
-6
lines changed

4 files changed

+134
-6
lines changed

README.md

Lines changed: 78 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# vuepress-plugin-fulltext-search
22

3-
Adds full-text search capabilities to your vuepress site with a help of flexsearch library.
3+
Add full-text search capabilities to your [VuePress](https://vuepress.vuejs.org/) website using the [Flexsearch](https://github.com/nextapps-de/flexsearch) library.
44

55
Many thanks to [Ahmad Mostafa](https://ahmadmostafa.com/2019/12/09/build-better-search-in-vuepress-site/) for the idea.
66

@@ -28,3 +28,80 @@ And that is it! Just compile your app and see for yourself.
2828

2929
Webpack alias `@SearchBox` will be replaced with plugin's implementation, so it should work automatically with any
3030
VuePress theme.
31+
32+
### Functions
33+
34+
You can define functions to hook into various seach features. The functions that can be implemented are as follows:
35+
36+
```ts
37+
/**
38+
* Augment, adjust, or manipulate the suggestions shown to users.
39+
*/
40+
async function processSuggestions(suggestions: Suggestion[], queryString: string, queryTerms: string[]): Suggestion[]
41+
42+
/**
43+
* Callback function to call when a user clicks on a suggestion.
44+
*/
45+
async function onGoToSuggestion(index: number, suggestion: Suggestion, queryString: string, queryTerms: string[])
46+
```
47+
48+
Functions are provided to the plugin like so:
49+
50+
```js
51+
// docs/.vuepress/config.js
52+
const fs = require('fs');
53+
const { path } = require('@vuepress/shared-utils');
54+
55+
module.exports = {
56+
plugins: [
57+
['fulltext-search', {
58+
// provide the contents of a JavaScript file
59+
functions: fs.readFileSync(path.resolve(__dirname, 'fulltextSearchFunctions.js')),
60+
}],
61+
]
62+
}
63+
```
64+
65+
For example, in `fulltextSearchFunctions.js`, you might have:
66+
67+
```js
68+
// docs/.vuepress/fulltextSearchFunctions.js
69+
export async function processSuggestions(suggestions, queryString, queryTerms) {
70+
if (queryString) {
71+
// add a suggestion to start a search in an external service
72+
suggestions.push({
73+
path: 'https://sourcegraph.com/search?patternType=literal&q=',
74+
slug: queryString,
75+
parentPageTitle: 'Sourcegraph',
76+
title: 'Search code',
77+
contentStr: 'Search for "' + queryString + '" on Sourcegraph',
78+
external: true,
79+
});
80+
}
81+
return suggestions;
82+
}
83+
84+
export async function onGoToSuggestion() {
85+
// create an analytics event
86+
}
87+
```
88+
89+
### Search parameters
90+
91+
The `query` URL search parameter can be provided to automatically populate and focus the search box. This is useful for adding your VuePress website as a custom search engine in browsers. For example:
92+
93+
```none
94+
https://your-website.com?query=hello+world
95+
```
96+
97+
### Excluding pages from search
98+
99+
You can exclude pages from search suggestions by adding `search: false` to a page's fontmatter:
100+
101+
```none
102+
---
103+
search: false
104+
---
105+
106+
<!-- page content -->
107+
```

components/SearchBox.vue

Lines changed: 48 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,10 @@
4141

4242
<script>
4343
import flexsearchSvc from '../services/flexsearchSvc'
44+
45+
// see https://vuepress.vuejs.org/plugin/option-api.html#clientdynamicmodules
46+
import * as functions from '@dynamic/functions'
47+
4448
/* global SEARCH_MAX_SUGGESTIONS, SEARCH_PATHS, SEARCH_HOTKEYS */
4549
export default {
4650
name: 'SearchBox',
@@ -83,6 +87,16 @@ export default {
8387
flexsearchSvc.buildIndex(this.$site.pages)
8488
this.placeholder = this.$site.themeConfig.searchPlaceholder || ''
8589
document.addEventListener('keydown', this.onHotkey)
90+
91+
// set query from URL
92+
const params = this.urlParams()
93+
if (params) {
94+
const query = params.get('query')
95+
if (query) {
96+
this.query = decodeURI(query)
97+
this.focused = true
98+
}
99+
}
86100
},
87101
beforeDestroy() {
88102
document.removeEventListener('keydown', this.onHotkey)
@@ -98,11 +112,18 @@ export default {
98112
this.suggestions = []
99113
return
100114
}
101-
this.suggestions = await flexsearchSvc.match(
115+
const suggestions = await flexsearchSvc.match(
102116
this.query,
103117
this.queryTerms,
104118
this.$site.themeConfig.searchMaxSuggestions || SEARCH_MAX_SUGGESTIONS,
105119
)
120+
121+
// augment suggestions with user-provided function
122+
if (functions && functions.processSuggestions) {
123+
this.suggestions = await functions.processSuggestions(suggestions, this.query, this.queryTerms)
124+
} else {
125+
this.suggestions = suggestions
126+
}
106127
},
107128
getPageLocalePath(page) {
108129
for (const localePath in this.$site.locales || {}) {
@@ -153,16 +174,39 @@ export default {
153174
if (!this.showSuggestions) {
154175
return
155176
}
156-
this.$router.push(this.suggestions[i].path + this.suggestions[i].slug)
157-
this.query = ''
158-
this.focusIndex = 0
177+
if (functions && functions.onGoToSuggestion) {
178+
functions.onGoToSuggestion(i, this.suggestions[i], this.query, this.queryTerms)
179+
}
180+
if (this.suggestions[i].external) {
181+
window.open(this.suggestions[i].path + this.suggestions[i].slug, '_blank')
182+
} else {
183+
this.$router.push(this.suggestions[i].path + this.suggestions[i].slug)
184+
this.query = ''
185+
this.focusIndex = 0
186+
this.focused = false
187+
188+
// reset query param
189+
const params = this.urlParams()
190+
if (params) {
191+
params.delete('query')
192+
const paramsString = params.toString()
193+
const newState = window.location.pathname + (paramsString ? `?${paramsString}` : '')
194+
history.pushState(null, '', newState)
195+
}
196+
}
159197
},
160198
focus(i) {
161199
this.focusIndex = i
162200
},
163201
unfocus() {
164202
this.focusIndex = -1
165203
},
204+
urlParams() {
205+
if (!window.location.search) {
206+
return null
207+
}
208+
return new URLSearchParams(window.location.search)
209+
}
166210
},
167211
}
168212
</script>

index.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,12 @@ module.exports = options => ({
3232
alias: {
3333
'@SearchBox': path.resolve(__dirname, 'components/SearchBox.vue'),
3434
},
35+
clientDynamicModules() {
36+
return {
37+
name: 'functions.js',
38+
content: options.functions || 'export default null',
39+
}
40+
}
3541
})
3642

3743
function getCharsets(text) {

services/flexsearchSvc.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,8 @@ let pagesByPath = null
1212
const cjkRegex = /[\u3131-\u314e|\u314f-\u3163|\uac00-\ud7a3]|[\u4E00-\u9FCC\u3400-\u4DB5\uFA0E\uFA0F\uFA11\uFA13\uFA14\uFA1F\uFA21\uFA23\uFA24\uFA27-\uFA29]|[\ud840-\ud868][\udc00-\udfff]|\ud869[\udc00-\uded6\udf00-\udfff]|[\ud86a-\ud86c][\udc00-\udfff]|\ud86d[\udc00-\udf34\udf40-\udfff]|\ud86e[\udc00-\udc1d]|[\u3041-\u3096]|[\u30A1-\u30FA]/giu
1313

1414
export default {
15-
buildIndex(pages) {
15+
buildIndex(allPages) {
16+
const pages = allPages.filter((p) => !p.frontmatter || (p.frontmatter.search !== false))
1617
const indexSettings = {
1718
tokenize: 'forward',
1819
async: true,

0 commit comments

Comments
 (0)