Skip to content

Commit 9d536a2

Browse files
committed
feat: plugin-search
1 parent 7b42984 commit 9d536a2

File tree

6 files changed

+320
-0
lines changed

6 files changed

+320
-0
lines changed
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
__tests__
2+
__mocks__
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
# @vuepress/plugin-search
2+
3+
> header-based search plugin for vuepress
4+
5+
## Usage
6+
7+
1. Enable this plugin:
8+
9+
```js
10+
// .vuepress/config.js or themedir/index.js
11+
12+
module.exports = {
13+
plugins: [
14+
['@vuepress/search', {
15+
searchMaxSuggestions: 10
16+
}]
17+
],
18+
// Tweak the default color via palette.
19+
palette: {
20+
$accentColor: '#b58900',
21+
$textColor: '#586e75',
22+
$borderColor: '#eaecef',
23+
$codeBgColor: '#282c34',
24+
$arrowBgColor: '#ccc'
25+
}
26+
}
27+
```
28+
29+
2. Using search component:
30+
31+
```vue
32+
import SearchBox from '@SearchBox'
33+
```
34+
35+
## Options
36+
37+
### searchMaxSuggestions
38+
39+
- Type: `number`
40+
- Default: `true`
41+
42+
Set the maximum number of results for search
Lines changed: 239 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,239 @@
1+
<template>
2+
<div class="search-box">
3+
<input
4+
@input="query = $event.target.value"
5+
aria-label="Search"
6+
:value="query"
7+
:class="{ 'focused': focused }"
8+
autocomplete="off"
9+
spellcheck="false"
10+
@focus="focused = true"
11+
@blur="focused = false"
12+
@keyup.enter="go(focusIndex)"
13+
@keyup.up="onUp"
14+
@keyup.down="onDown"
15+
>
16+
<ul
17+
class="suggestions"
18+
v-if="showSuggestions"
19+
:class="{ 'align-right': alignRight }"
20+
@mouseleave="unfocus"
21+
>
22+
<li
23+
class="suggestion"
24+
v-for="(s, i) in suggestions"
25+
:class="{ focused: i === focusIndex }"
26+
@mousedown="go(i)"
27+
@mouseenter="focus(i)"
28+
>
29+
<a :href="s.path" @click.prevent>
30+
<span class="page-title">{{ s.title || s.path }}</span>
31+
<span v-if="s.header" class="header">&gt; {{ s.header.title }}</span>
32+
</a>
33+
</li>
34+
</ul>
35+
</div>
36+
</template>
37+
38+
<script>
39+
/* global SEARCH_MAX_SUGGESTIONS */
40+
export default {
41+
data () {
42+
return {
43+
query: '',
44+
focused: false,
45+
focusIndex: 0
46+
}
47+
},
48+
49+
computed: {
50+
showSuggestions () {
51+
return (
52+
this.focused &&
53+
this.suggestions &&
54+
this.suggestions.length
55+
)
56+
},
57+
58+
suggestions () {
59+
const query = this.query.trim().toLowerCase()
60+
if (!query) {
61+
return
62+
}
63+
64+
const { pages } = this.$site
65+
const max = SEARCH_MAX_SUGGESTIONS
66+
const localePath = this.$localePath
67+
const matches = item => (
68+
item.title &&
69+
item.title.toLowerCase().indexOf(query) > -1
70+
)
71+
const res = []
72+
for (let i = 0; i < pages.length; i++) {
73+
if (res.length >= max) break
74+
const p = pages[i]
75+
// filter out results that do not match current locale
76+
if (this.getPageLocalePath(p) !== localePath) {
77+
continue
78+
}
79+
if (matches(p)) {
80+
res.push(p)
81+
} else if (p.headers) {
82+
for (let j = 0; j < p.headers.length; j++) {
83+
if (res.length >= max) break
84+
const h = p.headers[j]
85+
if (matches(h)) {
86+
res.push(Object.assign({}, p, {
87+
path: p.path + '#' + h.slug,
88+
header: h
89+
}))
90+
}
91+
}
92+
}
93+
}
94+
return res
95+
},
96+
97+
// make suggestions align right when there are not enough items
98+
alignRight () {
99+
const navCount = (this.$site.themeConfig.nav || []).length
100+
const repo = this.$site.repo ? 1 : 0
101+
return navCount + repo <= 2
102+
}
103+
},
104+
105+
methods: {
106+
getPageLocalePath (page) {
107+
for (const localePath in this.$site.locales || {}) {
108+
if (localePath !== '/' && page.path.indexOf(localePath) === 0) {
109+
return localePath
110+
}
111+
}
112+
return '/'
113+
},
114+
115+
onUp () {
116+
if (this.showSuggestions) {
117+
if (this.focusIndex > 0) {
118+
this.focusIndex--
119+
} else {
120+
this.focusIndex = this.suggestions.length - 1
121+
}
122+
}
123+
},
124+
125+
onDown () {
126+
if (this.showSuggestions) {
127+
if (this.focusIndex < this.suggestions.length - 1) {
128+
this.focusIndex++
129+
} else {
130+
this.focusIndex = 0
131+
}
132+
}
133+
},
134+
135+
go (i) {
136+
if (!this.showSuggestions) {
137+
return
138+
}
139+
this.$router.push(this.suggestions[i].path)
140+
this.query = ''
141+
this.focusIndex = 0
142+
},
143+
144+
focus (i) {
145+
this.focusIndex = i
146+
},
147+
148+
unfocus () {
149+
this.focusIndex = -1
150+
}
151+
}
152+
}
153+
</script>
154+
155+
<style lang="stylus">
156+
@import '~@app/style/config'
157+
158+
.search-box
159+
display inline-block
160+
position relative
161+
margin-right 1rem
162+
input
163+
cursor text
164+
width 10rem
165+
color lighten($textColor, 25%)
166+
display inline-block
167+
border 1px solid darken($borderColor, 10%)
168+
border-radius 2rem
169+
font-size 0.9rem
170+
line-height 2rem
171+
padding 0 0.5rem 0 2rem
172+
outline none
173+
transition all .2s ease
174+
background #fff url(search.svg) 0.6rem 0.5rem no-repeat
175+
background-size 1rem
176+
&:focus
177+
cursor auto
178+
border-color $accentColor
179+
.suggestions
180+
background #fff
181+
width 20rem
182+
position absolute
183+
top 1.5rem
184+
border 1px solid darken($borderColor, 10%)
185+
border-radius 6px
186+
padding 0.4rem
187+
list-style-type none
188+
&.align-right
189+
right 0
190+
.suggestion
191+
line-height 1.4
192+
padding 0.4rem 0.6rem
193+
border-radius 4px
194+
cursor pointer
195+
a
196+
white-space normal
197+
color lighten($textColor, 35%)
198+
.page-title
199+
font-weight 600
200+
.header
201+
font-size 0.9em
202+
margin-left 0.25em
203+
&.focused
204+
background-color #f3f4f5
205+
a
206+
color $accentColor
207+
208+
@media (max-width: $MQNarrow)
209+
.search-box
210+
input
211+
cursor pointer
212+
width 0
213+
border-color transparent
214+
position relative
215+
&:focus
216+
cursor text
217+
left 0
218+
width 10rem
219+
220+
@media (max-width: $MQNarrow) and (min-width: $MQMobile)
221+
.search-box
222+
.suggestions
223+
left 0
224+
225+
@media (max-width: $MQMobile)
226+
.search-box
227+
margin-right 0
228+
input
229+
left 1rem
230+
.suggestions
231+
right 0
232+
233+
@media (max-width: $MQMobileNarrow)
234+
.search-box
235+
.suggestions
236+
width calc(100vw - 4rem)
237+
input:focus
238+
width 8rem
239+
</style>
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
const path = require('path')
2+
3+
module.exports = (options) => ({
4+
alias: {
5+
'@SearchBox':
6+
path.resolve(__dirname, 'SearchBox.vue')
7+
},
8+
define: {
9+
SEARCH_MAX_SUGGESTIONS: options.searchMaxSuggestions || 5
10+
}
11+
})
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
{
2+
"name": "@vuepress/plugin-search",
3+
"version": "1.0.0",
4+
"description": "search plugin for vuepress",
5+
"main": "index.js",
6+
"publishConfig": {
7+
"access": "public"
8+
},
9+
"repository": {
10+
"type": "git",
11+
"url": "git+https://github.com/vuejs/vuepress.git"
12+
},
13+
"keywords": [
14+
"documentation",
15+
"vue",
16+
"vuepress",
17+
"generator"
18+
],
19+
"author": "Evan You",
20+
"license": "MIT",
21+
"bugs": {
22+
"url": "https://github.com/vuejs/vuepress/issues"
23+
},
24+
"homepage": "https://github.com/vuejs/vuepress/packages/@vuepress/plugin-search#readme"
25+
}
Lines changed: 1 addition & 0 deletions
Loading

0 commit comments

Comments
 (0)