Skip to content

Commit

Permalink
feat(docs): algolia powered search (#2952)
Browse files Browse the repository at this point in the history
* feat(docs): add algolia search

* Update search.vue

* Update search.vue

* add algolia API key + search improvemnts

* Update styles.scss

* Update styles.scss

* Update search.vue

* Update _slug.vue

* Update _slug.vue

* Update index.js

* Update play.vue

* Update index.js

* Update styles.scss

* Update componentdoc.vue

* Update importdoc.vue

* Update nuxt.config.js

* Update importdoc.vue

* Update importdoc.vue

* Update componentdoc.vue

* Update nuxt.config.js

* Update styles.scss

* Update importdoc.vue

* Update styles.scss

* Update styles.scss

* Update nuxt.config.js

* Update styles.scss

* Update styles.scss

* Update componentdoc.vue

* Update importdoc.vue

* Update styles.scss

* Update styles.scss

* Update styles.scss

* Update styles.scss

* Update styles.scss

* Update search.vue
  • Loading branch information
jacobmllr95 authored Apr 1, 2019
1 parent 6964f7b commit 0417f7b
Show file tree
Hide file tree
Showing 10 changed files with 195 additions and 117 deletions.
37 changes: 37 additions & 0 deletions docs/assets/scss/styles.scss
Original file line number Diff line number Diff line change
Expand Up @@ -145,9 +145,46 @@ pre.editable.error:after {
background-color: rgba(255, 0, 0, 0.1);
}

/* Additional styling for (responsive) markdown tables */
.bv-docs-table {
font-size: 90%;

thead > tr > th {
min-width: 80px;
}

code {
white-space: nowrap !important;
}
}

/* CSS for table transition example */
table#table-transition-example {
.flip-list-move {
transition: transform 1s;
}
}

/*
* Docsearch overrides
* See: https://github.com/twbs/bootstrap/blob/master/site/static/docs/4.3/assets/scss/_algolia.scss
*/
.algolia-autocomplete {
.ds-dropdown-menu {
background: #fff !important;
border: 1px solid rgba(0, 0, 0, 0.1) !important;
box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.175) !important;

.ds-suggestions {
@media (min-width: 768px) {
max-height: calc(100vh - 12rem);
overflow-y: auto;
}

.algolia-docsearch-suggestion--subcategory-inline,
.algolia-docsearch-suggestion--title {
display: inline-block !important;
}
}
}
}
14 changes: 12 additions & 2 deletions docs/components/componentdoc.vue
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@
<b-table
:items="propsItems"
:fields="propsFields"
class="bv-docs-table"
responsive="sm"
small
head-variant="default"
striped
Expand Down Expand Up @@ -60,6 +62,8 @@
<b-table
:items="[componentVModel]"
:fields="['prop', 'event']"
class="bv-docs-table"
responsive="sm"
small
head-variant="default"
striped
Expand All @@ -81,6 +85,8 @@
<b-table
:items="slots"
:fields="slotsFields"
class="bv-docs-table"
responsive="sm"
small
head-variant="default"
striped
Expand All @@ -98,6 +104,8 @@
<b-table
:items="events"
:fields="eventsFields"
class="bv-docs-table"
responsive="sm"
small
head-variant="default"
striped
Expand All @@ -110,7 +118,7 @@
v-for="arg in value"
:key="`event-${item.event}-${arg.arg ? arg.arg : 'none'}`"
>
<template v-if="arg.arg"><code>{{ arg.arg }}</code> - </template>
<template v-if="arg.arg"><code class="text-nowrap">{{ arg.arg }}</code> - </template>
<span>{{ arg.description }}</span>
</div>
</template>
Expand All @@ -128,6 +136,8 @@
<b-table
:items="rootEventListeners"
:fields="rootEventListenersFields"
class="bv-docs-table"
responsive="sm"
small
head-variant="default"
striped
Expand All @@ -141,7 +151,7 @@
:key="`event-${item.event}-${arg.arg ? arg.arg : 'none'}`"
>
<template v-if="arg.arg">
<code>{{ arg.arg }}</code>
<code class="text-nowrap">{{ arg.arg }}</code>
<span v-if="arg.description"> - {{ arg.description }}</span>
</template>
</div>
Expand Down
21 changes: 17 additions & 4 deletions docs/components/importdoc.vue
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,18 @@
Importing individual {{ pluginTitle }} Components
</anchored-heading>

<b-table :items="componentImports" small head-variant="default" striped>
<b-table
:items="componentImports"
class="bv-docs-table"
small
striped
head-variant="default"
>
<template slot="component" slot-scope="{ value }">
<code class="text-nowrap">{{ value }}</code>
</template>
<template slot="importPath" slot-scope="{ value }">
<code>{{ value }}</code>
<code class="text-nowrap">{{ value }}</code>
</template>
</b-table>

Expand All @@ -30,12 +36,19 @@
Importing individual {{ pluginTitle }} Directives
</anchored-heading>

<b-table :items="directiveImports" small head-variant="default" striped>
<b-table
:items="directiveImports"
class="bv-docs-table"
small
striped
responsive="sm"
head-variant="default"
>
<template slot="directive" slot-scope="{ value }">
<code class="text-nowrap">{{ value }}</code>
</template>
<template slot="importPath" slot-scope="{ value }">
<code>{{ value }}</code>
<code class="text-nowrap">{{ value }}</code>
</template>
</b-table>

Expand Down
163 changes: 62 additions & 101 deletions docs/components/search.vue
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
<template>
<div class="bd-search d-flex align-items-center">
<form
class="bd-search d-flex align-items-center"
@submit.stop.prevent
>
<b-form-input
id="bd-search-input"
v-model="search"
autocomplete="off"
type="search"
placeholder="Search keywords..."
aria-label="Search site"
placeholder="Search..."
aria-label="Search docs"
></b-form-input>
<button
v-b-toggle.bd-docs-nav
Expand All @@ -32,119 +34,78 @@
/>
</svg>
</button>
<b-popover target="bd-search-input" placement="bottom" triggers="focus">
<span v-if="search.length && Object.keys(results).length === 0">No results found</span>
<span v-else-if="search.length"></span>
<span v-else>Type something to start search</span>

<div
v-for="(results, section, idx) in results"
:key="section"
:class="idx > 0 ? 'mt-2' : ''"
role="group"
>
<h6 class="bd-text-purple my-1" v-html="section"></h6>
<div v-for="t in results" :key="t.href" class="my-1">
<b-link :to="t.href" @click="search = ''" v-html="t.title"></b-link>
</div>
</div>
</b-popover>
</div>
</form>
</template>

<script>
import groupBy from 'lodash/groupBy'
import intersectionBy from 'lodash/intersectionBy'
import { makeTOC } from '~/utils'
import { components, directives, reference, misc } from '~/content'
const SEARCH = []
const process = (readme, section, page) => {
const baseURL = '/' + ['docs', section, page].filter(v => !!v).join('/')
const { title, toc } = makeTOC(readme)
;[...toc].forEach(({ label, href }) => {
SEARCH.push({
section: title,
title: label,
href: `${baseURL}${href}`.replace('/#', '#')
})
})
}
import { relativeUrl } from '../utils'
// Async build the search database
import('~/markdown/intro/README.md' /* webpackChunkName: "docs/intro" */).then(readme =>
process(readme.default, '', '')
)
Object.keys(components).forEach(page => {
import(`~/../src/components/${page}/README.md` /* webpackChunkName: "docs/components" */).then(
readme => process(readme.default, 'components', page)
)
})
Object.keys(directives).forEach(page => {
import(`~/../src/directives/${page}/README.md` /* webpackChunkName: "docs/directives" */).then(
readme => process(readme.default, 'directives', page)
)
})
Object.keys(reference).forEach(page => {
import(`~/markdown/reference/${page}/README.md` /* webpackChunkName: "docs/reference" */).then(
readme => process(readme.default, 'reference', page)
)
})
Object.keys(misc).forEach(page => {
import(`~/markdown/misc/${page}/README.md` /* webpackChunkName: "docs/misc" */).then(readme =>
process(readme.default, 'misc', page)
)
})
let scriptsInjected = false
export default {
data() {
return {
search: ''
docsearch: null
}
},
computed: {
results() {
if (!this.search.length) {
return {}
mounted() {
this.loadDocsearch().then(this.initDocsearch)
},
methods: {
async loadDocsearch() {
if (scriptsInjected) {
return
}
// Break the searh into individual terms
const terms = this.search
.replace(/\s+/g, ' ')
.split(/\s+/)
.filter(t => t)
if (terms.length === 0) {
return {}
}
// Search indexing config stored at:
// https://github.com/algolia/docsearch-configs/blob/master/configs/bootstrap-vue.json
// Find results for each term
let results = terms.map(term => this.resultsFor(term))
const cdnBaseUrl = '//cdn.jsdelivr.net/docsearch.js/2/'
const $body = document.body
// If no results return emptiness
if (results.length === 0) {
return {}
}
// Load JS
const loadJs = new Promise(resolve => {
let $script = document.createElement('script')
$script.setAttribute('type', 'text/javascript')
$script.setAttribute('src', `${cdnBaseUrl}docsearch.min.js`)
$body.appendChild($script)
$script.onload = resolve
})
// Add our intersectionBy 'iteratee' key as the last array entry
results.push('href')
// Find the intersection (common) of all individual term results (all retults ANDed)
results = intersectionBy(...results)
// Load CSS
const loadCss = new Promise(resolve => {
let $link = document.createElement('link')
$link.setAttribute('rel', 'stylesheet')
$link.setAttribute('type', 'text/css')
$link.setAttribute('href', `${cdnBaseUrl}docsearch.min.css`)
$body.appendChild($link)
$link.onload = resolve
})
// Return the first 6 results or an empty array
return groupBy(results.slice(0, 6), 'section')
}
},
methods: {
resultsFor(term) {
// Return the search entries for a particular search term
const regex = new RegExp(`\\b${term}`, 'i')
return SEARCH.reduce((results, item) => {
if (regex.test(item.title) || regex.test(item.section)) {
results.push(item)
}
return results
}, [])
await Promise.all([loadJs, loadCss])
scriptsInjected = true
},
initDocsearch() {
if (this.docsearch) {
return
}
// Initialize docsearch
this.docsearch = window.docsearch({
apiKey: 'c816d3054b015320f0cfb40042f7e2bc',
indexName: 'bootstrap-vue',
inputSelector: '#bd-search-input',
transformData(hits) {
return hits.map(function(hit) {
// Transform URL to a relative URL
hit.url = relativeUrl(hit.url)
return hit
})
},
// Set debug to `true` if you want to inspect the dropdown
debug: false
})
}
}
}
Expand Down
7 changes: 4 additions & 3 deletions docs/nuxt.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -45,10 +45,11 @@ renderer.heading = function(text, level, raw, slugger) {
// BS4 table support for markdown renderer
const originalTable = renderer.table
renderer.table = function(header, body) {
let r = originalTable.apply(this, arguments)
return r
.replace('<table>', '<table class="table b-table table-striped">')
let table = originalTable.apply(this, arguments)
table = table
.replace('<table>', '<table class="table b-table table-striped table-sm bv-docs-table">')
.replace('<thead>', '<thead class="thead-default">')
return `<div class="table-responsive-sm">${table}</div>`
}

module.exports = {
Expand Down
3 changes: 2 additions & 1 deletion docs/pages/docs/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ export default {
defaultConfig
}
},
template: `<div class="container bd-content">${readme}</div>`,
// We use a string template here so that the docs README can do interpolation
template: `<main class="container"><div class="bd-content">${readme}</div></main>`,
layout: 'docs'
}
4 changes: 2 additions & 2 deletions docs/pages/docs/misc/_slug.vue
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<template>
<div class="container">
<main class="container">
<div class="bd-content" v-html="readme"></div>
</div>
</main>
</template>

<script>
Expand Down
4 changes: 2 additions & 2 deletions docs/pages/docs/reference/_slug.vue
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<template>
<div class="container">
<main class="container">
<div v-play class="bd-content" v-html="readme"></div>
</div>
</main>
</template>

<script>
Expand Down
Loading

0 comments on commit 0417f7b

Please sign in to comment.