Skip to content

Commit

Permalink
feat: add api.enableSearch (#231)
Browse files Browse the repository at this point in the history
  • Loading branch information
egoist authored Jun 8, 2019
1 parent 7988391 commit 02e7111
Show file tree
Hide file tree
Showing 10 changed files with 279 additions and 6 deletions.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
"marked": "^0.6.1",
"medium-zoom": "^1.0.2",
"prismjs": "^1.15.0",
"throttle-debounce": "^2.1.0",
"vue": "^2.6.10",
"vue-content-loader": "^0.2.1",
"vue-router": "^3.0.1",
Expand Down
7 changes: 7 additions & 0 deletions src/PluginAPI.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ export default class PluginAPI {
this.router = router
this.components = {}
this.hooks = hooks
this.search = {}

Vue.component(InjectedComponents.name, InjectedComponents)
}
Expand Down Expand Up @@ -51,4 +52,10 @@ export default class PluginAPI {
this.hooks.add('extendMarkdownComponent', fn)
return this
}

enableSearch(search = {}) {
this.search = search
this.search.enabled = true
return this
}
}
4 changes: 0 additions & 4 deletions src/components/Header.vue
Original file line number Diff line number Diff line change
Expand Up @@ -125,10 +125,6 @@ export default {
height: calc(var(--header-height) - 1px);
background: var(--header-background);
@media (max-width: 768px) {
display: none;
}
@media print {
display: flex;
right: 0;
Expand Down
2 changes: 2 additions & 0 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import evaluateContentPlugin from './plugins/evaluateContent'
import versionsPlugin from './plugins/versions'
import bannerFooter from './plugins/banner-footer'
import darkThemeToggler from './plugins/dark-theme-toggler'
import searchPlugin from './plugins/search'

Vue.component(ImageZoom.name, ImageZoom)
Vue.component(Badge.name, Badge)
Expand Down Expand Up @@ -59,6 +60,7 @@ class Docute {
versionsPlugin,
bannerFooter,
darkThemeToggler,
searchPlugin,
...(store.state.originalConfig.plugins || [])
]
this.pluginApi = new PluginAPI({plugins, store, router})
Expand Down
203 changes: 203 additions & 0 deletions src/plugins/search/SearchBar.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,203 @@
<template>
<div class="search" :class="{'is-focused': focused}" v-if="enabled">
<div class="search-input-wrapper">
<span class="search-icon">
<svg
width="13"
height="13"
viewBox="0 0 13 13"
xmlns="http://www.w3.org/2000/svg"
fill="currentColor"
>
<path
d="M8.87 8.16l3.25 3.25-.7.71-3.26-3.25a5 5 0 1 1 .7-.7zM5 9a4 4 0 1 0 0-8 4 4 0 0 0 0 8z"
></path>
</svg>
</span>
<input
class="search-input"
type="text"
@input="handleSearch"
@focus="toggleFocus(true)"
/>
<div class="search-result" ref="result" v-show="result.length > 0">
<router-link
:to="item.link"
class="search-result-item"
v-for="(item, i) in result"
:key="i"
>
<div class="item-header">
<div class="item-title" v-html="item.title"></div>
<span class="item-label" v-if="item.label">{{ item.label }}</span>
</div>
<div class="item-desc" v-html="item.description"></div>
</router-link>
</div>
</div>
</div>
</template>

<script>
import {debounce} from 'throttle-debounce'
export default {
data() {
return {
result: [],
focused: false
}
},
watch: {
'$route.fullPath'() {
this.focused = false
}
},
mounted() {
document.addEventListener('click', this.handleClick)
},
beforeDestroy() {
document.removeEventListener('click', this.handleClick)
},
computed: {
enabled() {
return this.$pluginApi.search.enabled
}
},
methods: {
handleClick(e) {
if (
!this.$el.contains(e.target) ||
this.$refs.result.contains(e.target)
) {
this.focused = false
}
},
handleSearch: debounce(300, async function(e) {
const {handler} = this.$pluginApi.search
this.result = await handler(e.target.value)
}),
toggleFocus(focused) {
this.focused = focused
}
}
}
</script>

<style scoped>
.search {
display: flex;
height: 100%;
align-items: center;
position: relative;
&.is-focused {
& .search-icon {
color: var(--search-focus-icon-color);
}
& .search-input-wrapper {
border-color: var(--search-focus-border-color);
}
& .search-result {
display: block;
}
}
}
.search-input-wrapper {
border: 1px solid var(--border-color);
border-radius: 4px;
padding: 0;
height: 50%;
position: relative;
width: 240px;
@media (max-width: 768px) {
width: 28px;
overflow: hidden;
@nest .is-focused & {
width: 200px;
overflow: initial;
}
}
}
.search-icon {
color: var(--search-icon-color);
position: absolute;
top: 50%;
left: 7px;
transform: translateY(-50%);
margin-top: 2px;
}
.search-input {
border: none;
outline: none;
background: transparent;
color: var(--text-color);
position: absolute;
padding: 0 8px 0 28px;
width: 100%;
height: 100%;
}
.search-result {
display: none;
position: absolute;
top: calc(100% + 8px);
right: 0;
width: 20rem;
border-radius: 4px;
overflow: hidden;
box-shadow: 0px 10px 30px rgba(0, 0, 0, 0.12);
z-index: 9999;
background: var(--header-background);
}
.search-result-item {
padding: 10px;
display: block;
background: var(--search-result-background);
&:not(:last-child) {
border-bottom: 1px solid var(--border-color);
}
&:hover {
background: var(--search-result-hover-background);
}
}
.item-title {
font-size: 1.1rem;
font-weight: 500;
display: inline;
line-height: 1;
}
.item-desc {
font-size: 0.875rem;
margin-top: 10px;
}
.item-label {
border-radius: 4px;
padding: 0 5px;
height: 22px;
display: inline-block;
border: 1px solid var(--border-color);
font-size: 13px;
margin-left: 10px;
}
</style>
8 changes: 8 additions & 0 deletions src/plugins/search/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import SearchBar from './SearchBar.vue'

export default {
name: 'search',
extend(api) {
api.registerComponent('header-right:start', SearchBar)
}
}
13 changes: 11 additions & 2 deletions src/utils/cssVariables.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,11 @@ const defaultCssVariables = {
loaderSecondaryColor: '#ecebeb',
tableHeaderBackground: '#fafafa',
tableHeaderColor: '#666',
docuteSelectHeight: '38px'
docuteSelectHeight: '38px',
searchIconColor: '#999',
searchFocusBorderColor: '#ccc',
searchFocusIconColor: '#333',
searchResultHoverBackground: '#f9f9f9'
}

const darkCssVariables = {
Expand All @@ -52,7 +56,12 @@ const darkCssVariables = {
contentLinkBorder: '2px solid hsla(0, 0%, 100%, 0.28)',
contentLinkHoverBorderColor: 'currentColor',
tableHeaderBackground: 'var(--border-color)',
tableHeaderColor: '#868686'
tableHeaderColor: '#868686',
searchIconColor: '#999',
searchFocusBorderColor: '#999',
searchFocusIconColor: '#ccc',
searchResultBackground: '#27292f',
searchResultHoverBackground: '#1e2025'
}

export {defaultCssVariables, darkCssVariables}
21 changes: 21 additions & 0 deletions website/docs/plugin-api.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,27 @@ Register a component at specific position:
- `header-right:start`: The start of right nav in site header.
- `header-right:end`: The end of right nav in site header.

## api.enableSearch(options)

Enable search bar.

Properties in `options`:

|Property|Type|Description|
|---|---|---|
|`handler`|`Handler`|A handler function triggered by every user input.|

```ts
type Handler = (keyword: string) => SearchResult[] | Promise<SearchResult[]>

interface SearchResult {
title: string
link: string
label: string?
description: string?
}
```

## api.router

Basically the [Vue Router](https://router.vuejs.org/api/#router-instance-properties) instance.
Expand Down
21 changes: 21 additions & 0 deletions website/docs/zh/plugin-api.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,27 @@ Register a component at specific position:
- `header-right:start`: The start of right nav in site header.
- `header-right:end`: The end of right nav in site header.

## api.enableSearch(options)

Enable search bar.

Properties in `options`:

|Property|Type|Description|
|---|---|---|
|`handler`|`Handler`|A handler function triggered by every user input.|

```ts
type Handler = (keyword: string) => SearchResult[] | Promise<SearchResult[]>

interface SearchResult {
title: string
link: string
label: string?
description: string?
}
```

## api.router

基本上是 [Vue Router](https://router.vuejs.org/api/#router-instance-properties) 实例。
Expand Down
5 changes: 5 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -11787,6 +11787,11 @@ thread-loader@^1.2.0:
loader-runner "^2.3.0"
loader-utils "^1.1.0"

throttle-debounce@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/throttle-debounce/-/throttle-debounce-2.1.0.tgz#257e648f0a56bd9e54fe0f132c4ab8611df4e1d5"
integrity sha512-AOvyNahXQuU7NN+VVvOOX+uW6FPaWdAOdRP5HfwYxAfCzXTFKRMoIMk+n+po318+ktcChx+F1Dd91G3YHeMKyg==

through2@^2.0.0, through2@^2.0.2, through2@~2.0.0:
version "2.0.5"
resolved "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz#01c1e39eb31d07cb7d03a96a70823260b23132cd"
Expand Down

0 comments on commit 02e7111

Please sign in to comment.