| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288 |
- <template>
- <div class="search-box">
- <input
- ref="input"
- aria-label="Search"
- :value="query"
- :class="{ 'focused': focused }"
- :placeholder="placeholder"
- autocomplete="off"
- spellcheck="false"
- @input="query = $event.target.value"
- @focus="focused = true"
- @blur="focused = false"
- @keyup.enter="go(focusIndex)"
- @keyup.up="onUp"
- @keyup.down="onDown"
- >
- <ul
- v-if="showSuggestions"
- class="suggestions"
- :class="{ 'align-right': alignRight }"
- @mouseleave="unfocus"
- >
- <li
- v-for="(s, i) in suggestions"
- :key="i"
- class="suggestion"
- :class="{ focused: i === focusIndex }"
- @mousedown="go(i)"
- @mouseenter="focus(i)"
- >
- <a
- :href="s.path"
- @click.prevent
- >
- <span class="page-title">{{ s.title || s.path }}</span>
- <span
- v-if="s.header"
- class="header"
- >> {{ s.header.title }}</span>
- </a>
- </li>
- </ul>
- </div>
- </template>
- <script>
- import matchQuery from './match-query'
- /* global SEARCH_MAX_SUGGESTIONS, SEARCH_PATHS, SEARCH_HOTKEYS */
- export default {
- name: 'SearchBox',
- data () {
- return {
- query: '',
- focused: false,
- focusIndex: 0,
- placeholder: undefined
- }
- },
- computed: {
- showSuggestions () {
- return (
- this.focused
- && this.suggestions
- && this.suggestions.length
- )
- },
- suggestions () {
- const query = this.query.trim().toLowerCase()
- if (!query) {
- return
- }
- const { pages } = this.$site
- const max = this.$site.themeConfig.searchMaxSuggestions || SEARCH_MAX_SUGGESTIONS
- const localePath = this.$localePath
- const res = []
- for (let i = 0; i < pages.length; i++) {
- if (res.length >= max) break
- const p = pages[i]
- // filter out results that do not match current locale
- if (this.getPageLocalePath(p) !== localePath) {
- continue
- }
- // filter out results that do not match searchable paths
- if (!this.isSearchable(p)) {
- continue
- }
- if (matchQuery(query, p)) {
- res.push(p)
- } else if (p.headers) {
- for (let j = 0; j < p.headers.length; j++) {
- if (res.length >= max) break
- const h = p.headers[j]
- if (h.title && matchQuery(query, p, h.title)) {
- res.push(Object.assign({}, p, {
- path: p.path + '#' + h.slug,
- header: h
- }))
- }
- }
- }
- }
- return res
- },
- // make suggestions align right when there are not enough items
- alignRight () {
- const navCount = (this.$site.themeConfig.nav || []).length
- const repo = this.$site.repo ? 1 : 0
- return navCount + repo <= 2
- }
- },
- mounted () {
- this.placeholder = this.$site.themeConfig.searchPlaceholder || ''
- document.addEventListener('keydown', this.onHotkey)
- },
- beforeDestroy () {
- document.removeEventListener('keydown', this.onHotkey)
- },
- methods: {
- getPageLocalePath (page) {
- for (const localePath in this.$site.locales || {}) {
- if (localePath !== '/' && page.path.indexOf(localePath) === 0) {
- return localePath
- }
- }
- return '/'
- },
- isSearchable (page) {
- let searchPaths = SEARCH_PATHS
- // all paths searchables
- if (searchPaths === null) { return true }
- searchPaths = Array.isArray(searchPaths) ? searchPaths : new Array(searchPaths)
- return searchPaths.filter(path => {
- return page.path.match(path)
- }).length > 0
- },
- onHotkey (event) {
- if (event.srcElement === document.body && SEARCH_HOTKEYS.includes(event.key)) {
- this.$refs.input.focus()
- event.preventDefault()
- }
- },
- onUp () {
- if (this.showSuggestions) {
- if (this.focusIndex > 0) {
- this.focusIndex--
- } else {
- this.focusIndex = this.suggestions.length - 1
- }
- }
- },
- onDown () {
- if (this.showSuggestions) {
- if (this.focusIndex < this.suggestions.length - 1) {
- this.focusIndex++
- } else {
- this.focusIndex = 0
- }
- }
- },
- go (i) {
- if (!this.showSuggestions) {
- return
- }
- this.$router.push(this.suggestions[i].path)
- this.query = ''
- this.focusIndex = 0
- },
- focus (i) {
- this.focusIndex = i
- },
- unfocus () {
- this.focusIndex = -1
- }
- }
- }
- </script>
- <style lang="stylus">
- .search-box
- display inline-block
- position relative
- margin-right 1rem
- input
- cursor text
- width 10rem
- height: 2rem
- color lighten($textColor, 25%)
- display inline-block
- border 1px solid darken($borderColor, 10%)
- border-radius 2rem
- font-size 0.9rem
- line-height 2rem
- padding 0 0.5rem 0 2rem
- outline none
- transition all .2s ease
- background #fff url(search.svg) 0.6rem 0.5rem no-repeat
- background-size 1rem
- &:focus
- cursor auto
- border-color $accentColor
- .suggestions
- background #fff
- width 20rem
- position absolute
- top 2 rem
- border 1px solid darken($borderColor, 10%)
- border-radius 6px
- padding 0.4rem
- list-style-type none
- &.align-right
- right 0
- .suggestion
- line-height 1.4
- padding 0.4rem 0.6rem
- border-radius 4px
- cursor pointer
- a
- white-space normal
- color lighten($textColor, 35%)
- .page-title
- font-weight 600
- .header
- font-size 0.9em
- margin-left 0.25em
- &.focused
- background-color #f3f4f5
- a
- color $accentColor
- @media (max-width: $MQNarrow)
- .search-box
- input
- cursor pointer
- width 0
- border-color transparent
- position relative
- &:focus
- cursor text
- left 0
- width 10rem
- // Match IE11
- @media all and (-ms-high-contrast: none)
- .search-box input
- height 2rem
- @media (max-width: $MQNarrow) and (min-width: $MQMobile)
- .search-box
- .suggestions
- left 0
- @media (max-width: $MQMobile)
- .search-box
- margin-right 0
- input
- left 1rem
- .suggestions
- right 0
- @media (max-width: $MQMobileNarrow)
- .search-box
- .suggestions
- width calc(100vw - 4rem)
- input:focus
- width 8rem
- </style>
|