Skip to content

Commit ae5e45a

Browse files
committed
feat(plugin-docsearch): add docsearch plugin
1 parent e5a643a commit ae5e45a

File tree

13 files changed

+483
-1
lines changed

13 files changed

+483
-1
lines changed

.vscode/settings.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
"vetur.validation.template": false,
1919
"cSpell.words": [
2020
"devtool",
21+
"docsearch",
2122
"frontmatter",
2223
"globby",
2324
"nprogress",

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,13 +61,13 @@ Features in the following list are not fully determined, and might be changed or
6161
- [x] @vuepress/plugin-active-header-links
6262
- [x] @vuepress/plugin-back-to-top
6363
- [x] @vuepress/plugin-container
64+
- [x] @vuepress/plugin-docsearch
6465
- [x] @vuepress/plugin-git
6566
- [x] @vuepress/plugin-google-analytics
6667
- [x] @vuepress/plugin-medium-zoom
6768
- [x] @vuepress/plugin-nprogress
6869
- [x] @vuepress/plugin-palette-stylus
6970
- [ ] @vuepress/plugin-pwa
70-
- [ ] @vuepress/plugin-search-algolia
7171

7272
- [ ] Documentation
7373
- [ ] Guide
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
{
2+
"name": "@vuepress/plugin-docsearch",
3+
"version": "2.0.0-alpha.0",
4+
"description": "VuePress plugin - docsearch",
5+
"keywords": [
6+
"vuepress",
7+
"plugin",
8+
"docsearch",
9+
"search",
10+
"algolia"
11+
],
12+
"homepage": "https://github.com/vuepress",
13+
"bugs": {
14+
"url": "https://github.com/vuepress/vuepress-next/issues"
15+
},
16+
"repository": {
17+
"type": "git",
18+
"url": "git+https://github.com/vuepress/vuepress-next.git"
19+
},
20+
"license": "MIT",
21+
"author": "meteorlxy",
22+
"main": "lib/index.js",
23+
"types": "lib/index.d.ts",
24+
"files": [
25+
"lib"
26+
],
27+
"scripts": {
28+
"build": "tsc -b tsconfig.build.json",
29+
"clean": "rimraf lib *.tsbuildinfo"
30+
},
31+
"dependencies": {
32+
"@docsearch/css": "3.0.0-alpha.31",
33+
"@docsearch/js": "3.0.0-alpha.31",
34+
"@types/react": "^17.0.0",
35+
"@vuepress/client": "2.0.0-alpha.0",
36+
"@vuepress/core": "2.0.0-alpha.0",
37+
"@vuepress/shared": "2.0.0-alpha.0",
38+
"@vuepress/utils": "2.0.0-alpha.0",
39+
"preact": "^10.0.0",
40+
"vue": "^3.0.0",
41+
"vue-router": "4.0.0-rc.5"
42+
},
43+
"publishConfig": {
44+
"access": "public"
45+
}
46+
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import { computed, h } from 'vue'
2+
import { useRouteLocale } from '@vuepress/client'
3+
import type { ClientAppEnhance } from '@vuepress/client'
4+
import type { LocaleConfig } from '@vuepress/shared'
5+
import { Docsearch } from './components/Docsearch'
6+
import type { DocsearchProps } from './components/Docsearch'
7+
8+
declare const DOCSEARCH_PROPS: DocsearchProps
9+
declare const DOCSEARCH_LOCALES: LocaleConfig<Pick<
10+
DocsearchProps,
11+
'placeholder'
12+
>>
13+
14+
const props = DOCSEARCH_PROPS
15+
const locales = DOCSEARCH_LOCALES
16+
17+
const clientAppEnhance: ClientAppEnhance = ({ app }) => {
18+
// wrap the `<Docsearch />` component with plugin options
19+
// eslint-disable-next-line vue/match-component-file-name
20+
app.component('Docsearch', {
21+
setup() {
22+
const routeLocale = useRouteLocale()
23+
24+
const locale = computed(() => locales[routeLocale.value])
25+
26+
return () =>
27+
h(Docsearch, {
28+
options: {
29+
...props,
30+
...locale.value,
31+
// TODO: add language filter to support multiple locales
32+
},
33+
})
34+
},
35+
})
36+
}
37+
38+
export default clientAppEnhance
Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
import { createElement } from 'preact'
2+
import { defineComponent, getCurrentInstance, h, onMounted, watch } from 'vue'
3+
import type { PropType } from 'vue'
4+
import { useRouter } from 'vue-router'
5+
import type { DocSearchProps } from '@docsearch/react'
6+
import { useSiteData } from '@vuepress/client'
7+
import { resolveRoutePathFromUrl } from '@vuepress/shared'
8+
9+
import '@docsearch/css'
10+
11+
export type DocsearchProps = DocSearchProps
12+
13+
type DocsearchFuncProps = DocSearchProps & {
14+
container: string
15+
}
16+
17+
type DocsearchFunc = (props: DocsearchFuncProps) => void
18+
19+
const loadDocsearch = async (): Promise<DocsearchFunc> => {
20+
// @ts-ignore: docsearch types issue
21+
const docsearch = await import('@docsearch/js')
22+
return docsearch.default
23+
}
24+
25+
const isSpecialClick = (event: MouseEvent): boolean => {
26+
return (
27+
event.button === 1 ||
28+
event.altKey ||
29+
event.ctrlKey ||
30+
event.metaKey ||
31+
event.shiftKey
32+
)
33+
}
34+
35+
export const Docsearch = defineComponent({
36+
name: 'Docsearch',
37+
38+
props: {
39+
options: {
40+
type: Object as PropType<DocsearchProps>,
41+
required: true,
42+
},
43+
},
44+
45+
setup(props) {
46+
const router = useRouter()
47+
const site = useSiteData()
48+
const vm = getCurrentInstance()
49+
50+
const initialize = (options: DocsearchProps): void => {
51+
loadDocsearch().then((docsearch) => {
52+
docsearch({
53+
...options,
54+
55+
// the container selector
56+
container: '#docsearch',
57+
58+
// navigation behavior triggered by `onKeyDown` internally
59+
// @ts-ignore: docsearch types issue
60+
navigator: {
61+
// when pressing Enter without metaKey
62+
navigate: ({ itemUrl }) => {
63+
router.push(itemUrl)
64+
},
65+
},
66+
67+
// transform full url to route path
68+
transformItems: (items) =>
69+
items.map((item) => {
70+
// the `item.url` is full url with protocol and hostname
71+
// so we have to transform it to vue-router path
72+
return {
73+
...item,
74+
url: resolveRoutePathFromUrl(item.url, site.value.base),
75+
}
76+
}),
77+
78+
// handle `onClick` by `router.push`
79+
hitComponent: ({ hit, children }) =>
80+
createElement(
81+
'a',
82+
{
83+
href: hit.url,
84+
onClick: (event: MouseEvent) => {
85+
if (isSpecialClick(event)) {
86+
return
87+
}
88+
event.preventDefault()
89+
router.push(hit.url)
90+
},
91+
},
92+
children
93+
),
94+
})
95+
})
96+
}
97+
98+
const update = (options: DocsearchProps): void => {
99+
if (vm && vm.vnode.el) {
100+
vm.vnode.el.innerHTML = '<div id="docsearch"></div>'
101+
initialize(options)
102+
}
103+
}
104+
105+
watch(
106+
() => props.options,
107+
(val, prevVal) => {
108+
// check if the options are modified
109+
const keys = Object.keys(val)
110+
const prevKeys = Object.keys(prevVal)
111+
if (
112+
keys.length !== prevKeys.length ||
113+
keys.some((key) => val[key] !== prevVal[key])
114+
) {
115+
update(val)
116+
}
117+
}
118+
)
119+
120+
onMounted(() => initialize(props.options))
121+
122+
return () => h('div', { id: 'docsearch' })
123+
},
124+
})
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
import { resolve } from 'path'
2+
import type { DocSearchProps } from '@docsearch/react'
3+
import type { Plugin, PluginObject } from '@vuepress/core'
4+
import type { LocaleConfig } from '@vuepress/shared'
5+
import { logger } from '@vuepress/utils'
6+
7+
export interface DocsearchPluginOptions
8+
extends Pick<
9+
DocSearchProps,
10+
| 'appId'
11+
| 'apiKey'
12+
| 'indexName'
13+
| 'searchParameters'
14+
| 'disableUserPersonalization'
15+
| 'initialQuery'
16+
> {
17+
locales?: LocaleConfig<Pick<DocSearchProps, 'placeholder'>>
18+
}
19+
20+
export const docsearchPlugin: Plugin<DocsearchPluginOptions> = ({
21+
locales,
22+
...props
23+
}) => {
24+
const plugin: PluginObject = {
25+
name: '@vuepress/plugin-docsearch',
26+
}
27+
28+
// if the required options are not set
29+
// print warning and return a noop plugin
30+
if (!props.apiKey || !props.indexName) {
31+
logger.warn(`[${plugin.name}] 'apiKey' and 'indexName' are required`)
32+
return plugin
33+
}
34+
35+
return {
36+
...plugin,
37+
38+
clientAppEnhanceFiles: resolve(__dirname, './clientAppEnhance.js'),
39+
40+
define: {
41+
DOCSEARCH_PROPS: props,
42+
DOCSEARCH_LOCALES: locales,
43+
},
44+
}
45+
}
46+
47+
export default docsearchPlugin
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
{
2+
"extends": "../../../tsconfig.base.json",
3+
"references": [
4+
{ "path": "../client/tsconfig.build.json" },
5+
{ "path": "../core/tsconfig.build.json" },
6+
{ "path": "../shared/tsconfig.build.json" },
7+
{ "path": "../utils/tsconfig.build.json" },
8+
{ "path": "./tsconfig.esm.json" },
9+
{ "path": "./tsconfig.cjs.json" }
10+
],
11+
"files": []
12+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
{
2+
"extends": "../../../tsconfig.base.json",
3+
"compilerOptions": {
4+
"module": "commonjs",
5+
"rootDir": "./src",
6+
"outDir": "./lib"
7+
},
8+
"include": ["./src/index.ts"]
9+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
{
2+
"extends": "../../../tsconfig.base.json",
3+
"compilerOptions": {
4+
"module": "ES2020",
5+
"rootDir": "./src",
6+
"outDir": "./lib"
7+
},
8+
"include": ["./src"],
9+
"exclude": ["./src/index.ts"]
10+
}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
{
2+
"extends": "../../../tsconfig.base.json",
3+
"include": ["./src", "./__tests__"]
4+
}

0 commit comments

Comments
 (0)