Skip to content

Commit

Permalink
feat: init flexsearch plugin
Browse files Browse the repository at this point in the history
  • Loading branch information
svalaskevicius committed Mar 21, 2024
1 parent 5993529 commit 7e2f3bf
Show file tree
Hide file tree
Showing 30 changed files with 1,030 additions and 0 deletions.
56 changes: 56 additions & 0 deletions plugins/plugin-flexsearch/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
# Change Log

All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.

# [2.0.0-rc.18](https://github.com/vuepress/ecosystem/compare/v2.0.0-rc.17...v2.0.0-rc.18) (2024-02-29)

**Note:** Version bump only for package @vuepress/plugin-search

# [2.0.0-rc.15](https://github.com/vuepress/ecosystem/compare/v2.0.0-rc.14...v2.0.0-rc.15) (2024-02-19)

**Note:** Version bump only for package @vuepress/plugin-search

# [2.0.0-rc.14](https://github.com/vuepress/ecosystem/compare/v2.0.0-rc.13...v2.0.0-rc.14) (2024-02-08)

**Note:** Version bump only for package @vuepress/plugin-search

# [2.0.0-rc.12](https://github.com/vuepress/ecosystem/compare/v2.0.0-rc.11...v2.0.0-rc.12) (2024-02-06)

**Note:** Version bump only for package @vuepress/plugin-search

# [2.0.0-rc.11](https://github.com/vuepress/ecosystem/compare/v2.0.0-rc.10...v2.0.0-rc.11) (2024-02-05)

**Note:** Version bump only for package @vuepress/plugin-search

# [2.0.0-rc.10](https://github.com/vuepress/ecosystem/compare/v2.0.0-rc.9...v2.0.0-rc.10) (2024-02-05)

### Bug Fixes

- **plugin-search:** fix hot reload ([637db2c](https://github.com/vuepress/ecosystem/commit/637db2ccefd00bea7aee0ed74b829e079d37e2a2))

### Features

- compatible with visual routes ([#57](https://github.com/vuepress/ecosystem/issues/57)) ([f1281be](https://github.com/vuepress/ecosystem/commit/f1281be141b9a5cb71d80048a2042b669cd4823e))

# [2.0.0-rc.9](https://github.com/vuepress/ecosystem/compare/v2.0.0-rc.8...v2.0.0-rc.9) (2024-02-03)

**Note:** Version bump only for package @vuepress/plugin-search

# [2.0.0-rc.8](https://github.com/vuepress/ecosystem/compare/v2.0.0-rc.7...v2.0.0-rc.8) (2024-02-03)

**Note:** Version bump only for package @vuepress/plugin-search

# [2.0.0-rc.7](https://github.com/vuepress/ecosystem/compare/v2.0.0-rc.6...v2.0.0-rc.7) (2024-02-02)

**Note:** Version bump only for package @vuepress/plugin-search

# [2.0.0-rc.3](https://github.com/vuepress/ecosystem/compare/v2.0.0-rc.2...v2.0.0-rc.3) (2024-01-31)

**Note:** Version bump only for package @vuepress/plugin-search

# 2.0.0-rc.1 (2024-01-26)

### Features

- bump to vp2rc1 and declare vuepress as peer ([af4f00b](https://github.com/vuepress/ecosystem/commit/af4f00b24dc64dfd3ec5f45053e78fdcf147da61))
51 changes: 51 additions & 0 deletions plugins/plugin-flexsearch/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
{
"name": "@vuepress/plugin-search",
"version": "2.0.0-rc.18",
"description": "VuePress plugin - built-in search",
"keywords": [
"vuepress-plugin",
"vuepress",
"plugin",
"search"
],
"homepage": "https://ecosystem.vuejs.press/plugins/search.html",
"bugs": {
"url": "https://github.com/vuepress/ecosystem/issues"
},
"repository": {
"type": "git",
"url": "git+https://github.com/vuepress/ecosystem.git",
"directory": "plugins/plugin-search"
},
"license": "MIT",
"author": "meteorlxy",
"type": "module",
"exports": {
".": "./lib/node/index.js",
"./client": "./lib/client/index.js",
"./package.json": "./package.json"
},
"main": "./lib/node/index.js",
"types": "./lib/node/index.d.ts",
"files": [
"lib"
],
"scripts": {
"build": "tsc -b tsconfig.build.json",
"clean": "rimraf --glob ./lib ./*.tsbuildinfo",
"copy": "cpx \"src/**/*.{d.ts,svg}\" lib",
"style": "sass src:lib --no-source-map"
},
"dependencies": {
"chokidar": "^3.6.0",
"flexsearch": "^0.6",
"he": "^1.2.0",
"vue": "^3.4.21"
},
"peerDependencies": {
"vuepress": "2.0.0-rc.8"
},
"publishConfig": {
"access": "public"
}
}
176 changes: 176 additions & 0 deletions plugins/plugin-flexsearch/src/client/components/SearchBox.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,176 @@
import { computed, defineComponent, h, ref, toRefs } from 'vue'
import type { PropType } from 'vue'
import { useRouteLocale, useRouter } from 'vuepress/client'
import type { LocaleConfig } from 'vuepress/shared'
import type { HotKeyOptions } from '../../shared/index.js'
import {
useHotKeys,
useSearchIndex,
useSearchSuggestions,
useSuggestionsFocus,
} from '../composables/index.js'

export type SearchBoxLocales = LocaleConfig<{
placeholder: string
}>

export const SearchBox = defineComponent({
name: 'SearchBox',

props: {
locales: {
type: Object as PropType<SearchBoxLocales>,
required: false,
default: () => ({}),
},

hotKeys: {
type: Array as PropType<(string | HotKeyOptions)[]>,
required: false,
default: () => [],
},

maxSuggestions: {
type: Number,
required: false,
default: 5,
},
},

setup(props) {
const { locales, hotKeys, maxSuggestions } = toRefs(props)

const router = useRouter()
const routeLocale = useRouteLocale()
const searchIndex = useSearchIndex

const input = ref<HTMLInputElement | null>(null)
const isActive = ref(false)
const query = ref('')
const locale = computed(() => locales.value[routeLocale.value] ?? {})

const suggestions = useSearchSuggestions({
searchIndex,
routeLocale,
query,
maxSuggestions,
})
const { focusIndex, focusNext, focusPrev } =
useSuggestionsFocus(suggestions)
useHotKeys({ input, hotKeys })

const showSuggestions = computed(
() => isActive.value && !!suggestions.value.length,
)
const onArrowUp = (): void => {
if (!showSuggestions.value) {
return
}
focusPrev()
}
const onArrowDown = (): void => {
if (!showSuggestions.value) {
return
}
focusNext()
}
const goTo = (index: number): void => {
if (!showSuggestions.value) {
return
}

const suggestion = suggestions.value[index]
if (!suggestion) {
return
}

router.push(suggestion.link).then(() => {
query.value = ''
focusIndex.value = 0
})
}

return () =>
h(
'form',
{
class: 'search-box',
role: 'search',
},
[
h('input', {
ref: input,
type: 'search',
placeholder: locale.value.placeholder,
autocomplete: 'off',
spellcheck: false,
value: query.value,
onFocus: () => (isActive.value = true),
onBlur: () => (isActive.value = false),
onInput: (event) =>
(query.value = (event.target as HTMLInputElement).value),
onKeydown: (event) => {
switch (event.key) {
case 'ArrowUp': {
onArrowUp()
break
}
case 'ArrowDown': {
onArrowDown()
break
}
case 'Enter': {
event.preventDefault()
goTo(focusIndex.value)
break
}
}
},
}),
showSuggestions.value &&
h(
'ul',
{
class: 'suggestions',
onMouseleave: () => (focusIndex.value = -1),
},
suggestions.value.map(({ link, title, text }, index) =>
h(
'li',
{
class: [
'suggestion',
{
focus: focusIndex.value === index,
},
],
onMouseenter: () => (focusIndex.value = index),
onMousedown: () => goTo(index),
},
h(
'a',
{
href: link,
onClick: (event) => event.preventDefault(),
},
[
h(
'span',
{
class: 'page-title',
},
title,
),
h('span', {
class: 'suggestion-result',
innerHTML: text,
}),
],
),
),
),
),
],
)
},
})
1 change: 1 addition & 0 deletions plugins/plugin-flexsearch/src/client/components/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './SearchBox.js'
4 changes: 4 additions & 0 deletions plugins/plugin-flexsearch/src/client/composables/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export * from './useHotKeys.js'
export * from './useSearchIndex.js'
export * from './useSearchSuggestions.js'
export * from './useSuggestionsFocus.js'
36 changes: 36 additions & 0 deletions plugins/plugin-flexsearch/src/client/composables/useHotKeys.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { onBeforeUnmount, onMounted } from 'vue'
import type { Ref } from 'vue'
import type { HotKeyOptions } from '../../shared/index.js'
import { isFocusingTextControl, isKeyMatched } from '../utils/index.js'

export const useHotKeys = ({
input,
hotKeys,
}: {
input: Ref<HTMLInputElement | null>
hotKeys: Ref<(string | HotKeyOptions)[]>
}): void => {
if (hotKeys.value.length === 0) return

const onKeydown = (event: KeyboardEvent): void => {
if (!input.value) return
if (
// key matches
isKeyMatched(event, hotKeys.value) &&
// event does not come from the search box itself or
// user isn't focusing (and thus perhaps typing in) a text control
!isFocusingTextControl(event.target as EventTarget)
) {
event.preventDefault()
input.value.focus()
}
}

onMounted(() => {
document.addEventListener('keydown', onKeydown)
})

onBeforeUnmount(() => {
document.removeEventListener('keydown', onKeydown)
})
}
30 changes: 30 additions & 0 deletions plugins/plugin-flexsearch/src/client/composables/useSearchIndex.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { searchIndex as searchIndexRaw } from '@internal/searchIndex'
import FS from 'flexsearch'
import { ref } from 'vue'

export interface SearchIndexRet {
path: [string, string]
title: string
}

export type CSearchIndex = (string, number) => SearchIndexRet[]

const index = FS.create({
async: false,
doc: {
id: 'id',
field: ['title', 'content'],
},
})
index.import(searchIndexRaw.idx)

export const useSearchIndex = ref((q: string, c: number) => {
const rr: any = index.search(q, c)
return rr.map((r) => {
return {
path: searchIndexRaw.paths[r.id],
title: r.title,
content: r.content,
}
})
})
Loading

0 comments on commit 7e2f3bf

Please sign in to comment.