mirror of
https://github.com/em-squared/5e-drs.git
synced 2025-12-16 23:20:14 +00:00
search autocomplete
This commit is contained in:
parent
d47ad5e88d
commit
eccc40a123
15 changed files with 1094 additions and 9924 deletions
6
docs/.vuepress/theme/components/search/README.md
Normal file
6
docs/.vuepress/theme/components/search/README.md
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
# @vuepress/plugin-search
|
||||
|
||||
> header-based search plugin for VuePress
|
||||
|
||||
See [documentation](https://v1.vuepress.vuejs.org/plugin/official/plugin-search.html).
|
||||
|
||||
159
docs/.vuepress/theme/components/search/SRDSearchBox.vue
Normal file
159
docs/.vuepress/theme/components/search/SRDSearchBox.vue
Normal file
|
|
@ -0,0 +1,159 @@
|
|||
<template>
|
||||
<v-autocomplete
|
||||
v-model="select"
|
||||
:loading="loading"
|
||||
:items="entries"
|
||||
:search-input.sync="search"
|
||||
cache-items
|
||||
class="mx-4"
|
||||
flat
|
||||
clearable
|
||||
hide-no-data
|
||||
hide-details
|
||||
item-text="title"
|
||||
item-value="path"
|
||||
:label="placeholder"
|
||||
append-icon="mdi-magnify"
|
||||
solo-inverted
|
||||
return-object
|
||||
>
|
||||
|
||||
<template v-slot:item="data">
|
||||
<template>
|
||||
<v-list-item-content>
|
||||
<v-list-item-title v-html="data.item.title"></v-list-item-title>
|
||||
<v-list-item-subtitle v-if="data.item.subtitle" v-html="data.item.subtitle"></v-list-item-subtitle>
|
||||
</v-list-item-content>
|
||||
</template>
|
||||
</template>
|
||||
|
||||
</v-autocomplete>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import matchQuery from './match-query'
|
||||
|
||||
/* global SEARCH_MAX_SUGGESTIONS, SEARCH_PATHS, SEARCH_HOTKEYS */
|
||||
export default {
|
||||
name: 'SRDSearchBox',
|
||||
|
||||
data () {
|
||||
return {
|
||||
loading: false,
|
||||
focused: false,
|
||||
focusIndex: 0,
|
||||
placeholder: undefined,
|
||||
search: null,
|
||||
select: null,
|
||||
items: []
|
||||
}
|
||||
},
|
||||
|
||||
watch: {
|
||||
search (query) {
|
||||
if (query && query.length > 1) {
|
||||
this.suggestions(query)
|
||||
}
|
||||
},
|
||||
|
||||
select (selected) {
|
||||
this.go(selected)
|
||||
}
|
||||
},
|
||||
|
||||
computed: {
|
||||
entries () {
|
||||
return this.items.map(item => {
|
||||
return {
|
||||
title: item.title,
|
||||
subtitle: item.subtitle,
|
||||
path: item.path
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
|
||||
mounted () {
|
||||
this.placeholder = this.$site.themeConfig.searchPlaceholder || ''
|
||||
},
|
||||
|
||||
methods: {
|
||||
suggestions (query) {
|
||||
this.loading = true
|
||||
query = query.trim().toLowerCase()
|
||||
if (!query) {
|
||||
this.items = []
|
||||
return
|
||||
}
|
||||
|
||||
const { pages } = this.$site
|
||||
const max = this.$site.themeConfig.searchMaxSuggestions || 10
|
||||
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, {
|
||||
subtitle: p.title,
|
||||
title: h.title,
|
||||
path: p.path + '#' + h.slug,
|
||||
header: h
|
||||
}))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
this.items = res
|
||||
this.loading = false
|
||||
},
|
||||
|
||||
getPageLocalePath (page) {
|
||||
for (const localePath in this.$site.locales || {}) {
|
||||
if (localePath !== '/' && page.path.indexOf(localePath) === 0) {
|
||||
return localePath
|
||||
}
|
||||
}
|
||||
return '/'
|
||||
},
|
||||
|
||||
isSearchable (page) {
|
||||
let searchPaths = null
|
||||
|
||||
// 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
|
||||
},
|
||||
|
||||
go (selected) {
|
||||
if (selected) {
|
||||
this.$router.push(selected.path)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
</style>
|
||||
14
docs/.vuepress/theme/components/search/index.js
Normal file
14
docs/.vuepress/theme/components/search/index.js
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
const { path } = require('@vuepress/shared-utils')
|
||||
|
||||
module.exports = (options) => ({
|
||||
alias: {
|
||||
'@SRDSearchBox':
|
||||
path.resolve(__dirname, 'SearchBox.vue')
|
||||
},
|
||||
|
||||
define: {
|
||||
SEARCH_MAX_SUGGESTIONS: options.searchMaxSuggestions || 5,
|
||||
SEARCH_PATHS: options.test || null,
|
||||
SEARCH_HOTKEYS: options.searchHotkeys || ['s', '/']
|
||||
}
|
||||
})
|
||||
42
docs/.vuepress/theme/components/search/match-query.js
Normal file
42
docs/.vuepress/theme/components/search/match-query.js
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
|
||||
import get from 'lodash/get'
|
||||
|
||||
export default (query, page, additionalStr = null) => {
|
||||
let domain = get(page, 'title', '')
|
||||
|
||||
if (get(page, 'frontmatter.tags')) {
|
||||
domain += ` ${page.frontmatter.tags.join(' ')}`
|
||||
}
|
||||
|
||||
if (additionalStr) {
|
||||
domain += ` ${additionalStr}`
|
||||
}
|
||||
|
||||
return matchTest(query, domain)
|
||||
}
|
||||
|
||||
const matchTest = (query, domain) => {
|
||||
const escapeRegExp = str => str.replace(/[-/\\^$*+?.()|[\]{}]/g, '\\$&')
|
||||
|
||||
const words = query
|
||||
.split(/\s+/g)
|
||||
.map(str => str.trim())
|
||||
.filter(str => !!str)
|
||||
const hasTrailingSpace = query.endsWith(' ')
|
||||
const searchRegex = new RegExp(
|
||||
words
|
||||
.map((word, index) => {
|
||||
if (words.length === index + 1 && !hasTrailingSpace) {
|
||||
// The last word - ok with the word being "startswith"-like
|
||||
return `(?=.*\\b${escapeRegExp(word)})`
|
||||
} else {
|
||||
// Not the last word - expect the whole word exactly
|
||||
return `(?=.*\\b${escapeRegExp(word)}\\b)`
|
||||
}
|
||||
})
|
||||
.join('') + '.+',
|
||||
'gi'
|
||||
)
|
||||
return searchRegex.test(domain)
|
||||
}
|
||||
|
||||
Loading…
Add table
Add a link
Reference in a new issue