Skip to content

Commit 7e2f3bf

Browse files
feat: init flexsearch plugin
1 parent 5993529 commit 7e2f3bf

30 files changed

+1030
-0
lines changed
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
# Change Log
2+
3+
All notable changes to this project will be documented in this file.
4+
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
5+
6+
# [2.0.0-rc.18](https://github.com/vuepress/ecosystem/compare/v2.0.0-rc.17...v2.0.0-rc.18) (2024-02-29)
7+
8+
**Note:** Version bump only for package @vuepress/plugin-search
9+
10+
# [2.0.0-rc.15](https://github.com/vuepress/ecosystem/compare/v2.0.0-rc.14...v2.0.0-rc.15) (2024-02-19)
11+
12+
**Note:** Version bump only for package @vuepress/plugin-search
13+
14+
# [2.0.0-rc.14](https://github.com/vuepress/ecosystem/compare/v2.0.0-rc.13...v2.0.0-rc.14) (2024-02-08)
15+
16+
**Note:** Version bump only for package @vuepress/plugin-search
17+
18+
# [2.0.0-rc.12](https://github.com/vuepress/ecosystem/compare/v2.0.0-rc.11...v2.0.0-rc.12) (2024-02-06)
19+
20+
**Note:** Version bump only for package @vuepress/plugin-search
21+
22+
# [2.0.0-rc.11](https://github.com/vuepress/ecosystem/compare/v2.0.0-rc.10...v2.0.0-rc.11) (2024-02-05)
23+
24+
**Note:** Version bump only for package @vuepress/plugin-search
25+
26+
# [2.0.0-rc.10](https://github.com/vuepress/ecosystem/compare/v2.0.0-rc.9...v2.0.0-rc.10) (2024-02-05)
27+
28+
### Bug Fixes
29+
30+
- **plugin-search:** fix hot reload ([637db2c](https://github.com/vuepress/ecosystem/commit/637db2ccefd00bea7aee0ed74b829e079d37e2a2))
31+
32+
### Features
33+
34+
- compatible with visual routes ([#57](https://github.com/vuepress/ecosystem/issues/57)) ([f1281be](https://github.com/vuepress/ecosystem/commit/f1281be141b9a5cb71d80048a2042b669cd4823e))
35+
36+
# [2.0.0-rc.9](https://github.com/vuepress/ecosystem/compare/v2.0.0-rc.8...v2.0.0-rc.9) (2024-02-03)
37+
38+
**Note:** Version bump only for package @vuepress/plugin-search
39+
40+
# [2.0.0-rc.8](https://github.com/vuepress/ecosystem/compare/v2.0.0-rc.7...v2.0.0-rc.8) (2024-02-03)
41+
42+
**Note:** Version bump only for package @vuepress/plugin-search
43+
44+
# [2.0.0-rc.7](https://github.com/vuepress/ecosystem/compare/v2.0.0-rc.6...v2.0.0-rc.7) (2024-02-02)
45+
46+
**Note:** Version bump only for package @vuepress/plugin-search
47+
48+
# [2.0.0-rc.3](https://github.com/vuepress/ecosystem/compare/v2.0.0-rc.2...v2.0.0-rc.3) (2024-01-31)
49+
50+
**Note:** Version bump only for package @vuepress/plugin-search
51+
52+
# 2.0.0-rc.1 (2024-01-26)
53+
54+
### Features
55+
56+
- bump to vp2rc1 and declare vuepress as peer ([af4f00b](https://github.com/vuepress/ecosystem/commit/af4f00b24dc64dfd3ec5f45053e78fdcf147da61))
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
{
2+
"name": "@vuepress/plugin-search",
3+
"version": "2.0.0-rc.18",
4+
"description": "VuePress plugin - built-in search",
5+
"keywords": [
6+
"vuepress-plugin",
7+
"vuepress",
8+
"plugin",
9+
"search"
10+
],
11+
"homepage": "https://ecosystem.vuejs.press/plugins/search.html",
12+
"bugs": {
13+
"url": "https://github.com/vuepress/ecosystem/issues"
14+
},
15+
"repository": {
16+
"type": "git",
17+
"url": "git+https://github.com/vuepress/ecosystem.git",
18+
"directory": "plugins/plugin-search"
19+
},
20+
"license": "MIT",
21+
"author": "meteorlxy",
22+
"type": "module",
23+
"exports": {
24+
".": "./lib/node/index.js",
25+
"./client": "./lib/client/index.js",
26+
"./package.json": "./package.json"
27+
},
28+
"main": "./lib/node/index.js",
29+
"types": "./lib/node/index.d.ts",
30+
"files": [
31+
"lib"
32+
],
33+
"scripts": {
34+
"build": "tsc -b tsconfig.build.json",
35+
"clean": "rimraf --glob ./lib ./*.tsbuildinfo",
36+
"copy": "cpx \"src/**/*.{d.ts,svg}\" lib",
37+
"style": "sass src:lib --no-source-map"
38+
},
39+
"dependencies": {
40+
"chokidar": "^3.6.0",
41+
"flexsearch": "^0.6",
42+
"he": "^1.2.0",
43+
"vue": "^3.4.21"
44+
},
45+
"peerDependencies": {
46+
"vuepress": "2.0.0-rc.8"
47+
},
48+
"publishConfig": {
49+
"access": "public"
50+
}
51+
}
Lines changed: 176 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,176 @@
1+
import { computed, defineComponent, h, ref, toRefs } from 'vue'
2+
import type { PropType } from 'vue'
3+
import { useRouteLocale, useRouter } from 'vuepress/client'
4+
import type { LocaleConfig } from 'vuepress/shared'
5+
import type { HotKeyOptions } from '../../shared/index.js'
6+
import {
7+
useHotKeys,
8+
useSearchIndex,
9+
useSearchSuggestions,
10+
useSuggestionsFocus,
11+
} from '../composables/index.js'
12+
13+
export type SearchBoxLocales = LocaleConfig<{
14+
placeholder: string
15+
}>
16+
17+
export const SearchBox = defineComponent({
18+
name: 'SearchBox',
19+
20+
props: {
21+
locales: {
22+
type: Object as PropType<SearchBoxLocales>,
23+
required: false,
24+
default: () => ({}),
25+
},
26+
27+
hotKeys: {
28+
type: Array as PropType<(string | HotKeyOptions)[]>,
29+
required: false,
30+
default: () => [],
31+
},
32+
33+
maxSuggestions: {
34+
type: Number,
35+
required: false,
36+
default: 5,
37+
},
38+
},
39+
40+
setup(props) {
41+
const { locales, hotKeys, maxSuggestions } = toRefs(props)
42+
43+
const router = useRouter()
44+
const routeLocale = useRouteLocale()
45+
const searchIndex = useSearchIndex
46+
47+
const input = ref<HTMLInputElement | null>(null)
48+
const isActive = ref(false)
49+
const query = ref('')
50+
const locale = computed(() => locales.value[routeLocale.value] ?? {})
51+
52+
const suggestions = useSearchSuggestions({
53+
searchIndex,
54+
routeLocale,
55+
query,
56+
maxSuggestions,
57+
})
58+
const { focusIndex, focusNext, focusPrev } =
59+
useSuggestionsFocus(suggestions)
60+
useHotKeys({ input, hotKeys })
61+
62+
const showSuggestions = computed(
63+
() => isActive.value && !!suggestions.value.length,
64+
)
65+
const onArrowUp = (): void => {
66+
if (!showSuggestions.value) {
67+
return
68+
}
69+
focusPrev()
70+
}
71+
const onArrowDown = (): void => {
72+
if (!showSuggestions.value) {
73+
return
74+
}
75+
focusNext()
76+
}
77+
const goTo = (index: number): void => {
78+
if (!showSuggestions.value) {
79+
return
80+
}
81+
82+
const suggestion = suggestions.value[index]
83+
if (!suggestion) {
84+
return
85+
}
86+
87+
router.push(suggestion.link).then(() => {
88+
query.value = ''
89+
focusIndex.value = 0
90+
})
91+
}
92+
93+
return () =>
94+
h(
95+
'form',
96+
{
97+
class: 'search-box',
98+
role: 'search',
99+
},
100+
[
101+
h('input', {
102+
ref: input,
103+
type: 'search',
104+
placeholder: locale.value.placeholder,
105+
autocomplete: 'off',
106+
spellcheck: false,
107+
value: query.value,
108+
onFocus: () => (isActive.value = true),
109+
onBlur: () => (isActive.value = false),
110+
onInput: (event) =>
111+
(query.value = (event.target as HTMLInputElement).value),
112+
onKeydown: (event) => {
113+
switch (event.key) {
114+
case 'ArrowUp': {
115+
onArrowUp()
116+
break
117+
}
118+
case 'ArrowDown': {
119+
onArrowDown()
120+
break
121+
}
122+
case 'Enter': {
123+
event.preventDefault()
124+
goTo(focusIndex.value)
125+
break
126+
}
127+
}
128+
},
129+
}),
130+
showSuggestions.value &&
131+
h(
132+
'ul',
133+
{
134+
class: 'suggestions',
135+
onMouseleave: () => (focusIndex.value = -1),
136+
},
137+
suggestions.value.map(({ link, title, text }, index) =>
138+
h(
139+
'li',
140+
{
141+
class: [
142+
'suggestion',
143+
{
144+
focus: focusIndex.value === index,
145+
},
146+
],
147+
onMouseenter: () => (focusIndex.value = index),
148+
onMousedown: () => goTo(index),
149+
},
150+
h(
151+
'a',
152+
{
153+
href: link,
154+
onClick: (event) => event.preventDefault(),
155+
},
156+
[
157+
h(
158+
'span',
159+
{
160+
class: 'page-title',
161+
},
162+
title,
163+
),
164+
h('span', {
165+
class: 'suggestion-result',
166+
innerHTML: text,
167+
}),
168+
],
169+
),
170+
),
171+
),
172+
),
173+
],
174+
)
175+
},
176+
})
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export * from './SearchBox.js'
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
export * from './useHotKeys.js'
2+
export * from './useSearchIndex.js'
3+
export * from './useSearchSuggestions.js'
4+
export * from './useSuggestionsFocus.js'
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import { onBeforeUnmount, onMounted } from 'vue'
2+
import type { Ref } from 'vue'
3+
import type { HotKeyOptions } from '../../shared/index.js'
4+
import { isFocusingTextControl, isKeyMatched } from '../utils/index.js'
5+
6+
export const useHotKeys = ({
7+
input,
8+
hotKeys,
9+
}: {
10+
input: Ref<HTMLInputElement | null>
11+
hotKeys: Ref<(string | HotKeyOptions)[]>
12+
}): void => {
13+
if (hotKeys.value.length === 0) return
14+
15+
const onKeydown = (event: KeyboardEvent): void => {
16+
if (!input.value) return
17+
if (
18+
// key matches
19+
isKeyMatched(event, hotKeys.value) &&
20+
// event does not come from the search box itself or
21+
// user isn't focusing (and thus perhaps typing in) a text control
22+
!isFocusingTextControl(event.target as EventTarget)
23+
) {
24+
event.preventDefault()
25+
input.value.focus()
26+
}
27+
}
28+
29+
onMounted(() => {
30+
document.addEventListener('keydown', onKeydown)
31+
})
32+
33+
onBeforeUnmount(() => {
34+
document.removeEventListener('keydown', onKeydown)
35+
})
36+
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import { searchIndex as searchIndexRaw } from '@internal/searchIndex'
2+
import FS from 'flexsearch'
3+
import { ref } from 'vue'
4+
5+
export interface SearchIndexRet {
6+
path: [string, string]
7+
title: string
8+
}
9+
10+
export type CSearchIndex = (string, number) => SearchIndexRet[]
11+
12+
const index = FS.create({
13+
async: false,
14+
doc: {
15+
id: 'id',
16+
field: ['title', 'content'],
17+
},
18+
})
19+
index.import(searchIndexRaw.idx)
20+
21+
export const useSearchIndex = ref((q: string, c: number) => {
22+
const rr: any = index.search(q, c)
23+
return rr.map((r) => {
24+
return {
25+
path: searchIndexRaw.paths[r.id],
26+
title: r.title,
27+
content: r.content,
28+
}
29+
})
30+
})

0 commit comments

Comments
 (0)