Skip to content

Commit 1c1bf42

Browse files
authored
Merge pull request #6 from 10gen/DOCSP-486
DOCSP-486: Implement initial search
2 parents b4e65b6 + 819f6c8 commit 1c1bf42

File tree

13 files changed

+3806
-967
lines changed

13 files changed

+3806
-967
lines changed

Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ help: ## Show this help message
77

88
build: | tools/node_modules ## Build the documentation under build/<git branch>
99
hugo -d build/$(GIT_BRANCH)
10-
$(NODE) tools/genindex.js content/tutorials build/$(GIT_BRANCH)/ --config config.toml
10+
$(NODE) tools/genindex.js content/tutorials build/$(GIT_BRANCH)/search.json --config config.toml
1111

1212
serve: ## Host the documentation on port 1313
1313
hugo serve

themes/mongodb-tutorials/package.json

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,9 @@
44
"description": "",
55
"main": "index.js",
66
"scripts": {
7-
"build": "npm run build:js && npm run build:css",
8-
"build:js": "./node_modules/.bin/webpack",
7+
"build": "npm run build:js && npm run build:search && npm run build:css",
8+
"build:js": "./node_modules/.bin/webpack --config webpack/webpack.config.js",
9+
"build:search": "./node_modules/.bin/webpack --config webpack/webpack-search.config.js",
910
"build:css": "./node_modules/.bin/lessc src/styles/app.less static/style.css"
1011
},
1112
"keywords": [],
@@ -20,7 +21,8 @@
2021
"less": "^2.7.2",
2122
"react": "^15.4.2",
2223
"react-dom": "^15.4.2",
23-
"webpack": "^2.2.1"
24+
"webpack": "^2.2.1",
25+
"lunr": "i80and/lunr.js#19a85e62ae8103a48ce5a1fad507f758cf6016d9"
2426
},
2527
"devDependencies": {
2628
"webpack": "^2.2.1"
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
export default class SearchChannel {
2+
constructor(url) {
3+
this.worker = new Worker('worker-search.js')
4+
5+
this.pending = null
6+
this.busy = false
7+
8+
this.loaded = false
9+
this.loadingTime = null
10+
this.loadWaiter = new Promise((resolve) => {
11+
this.worker.onmessage = (ev) => {
12+
if (ev.data.loaded) {
13+
this.loaded = true
14+
this.loadingTime = ev.data.loaded
15+
return resolve(this.loadingTime)
16+
}
17+
18+
if (ev.data.results) {
19+
this.onresults(ev.data.results)
20+
}
21+
22+
this.busy = false
23+
if (this.pending !== null) {
24+
this.search(this.pending)
25+
}
26+
}
27+
})
28+
29+
this.worker.postMessage({ 'load': url })
30+
this.onresults = (results) => {}
31+
}
32+
33+
search(query) {
34+
if (!this.busy) {
35+
if (!query) {
36+
this.onresults([])
37+
} else {
38+
this.worker.postMessage({ 'search': query })
39+
}
40+
this.pending = null
41+
this.busy = true
42+
} else {
43+
this.pending = query
44+
}
45+
}
46+
47+
load() {
48+
if (this.loadingTime !== null) {
49+
return new Promise((resolve, reject) => resolve(this.loadingTime))
50+
}
51+
return this.loadWaiter
52+
}
53+
}

themes/mongodb-tutorials/src/index.js

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,14 @@ import React from 'react'
22
import ReactDOM from 'react-dom'
33

44
import Facet from './facet.js'
5+
import Search from './Search.js'
56
import TutorialList from './tutorialList.js'
67

7-
88
class App extends React.Component {
99
constructor (props) {
1010
super(props)
1111
this.state = {
12+
searchResults: null,
1213
options: [
1314
{ name: "MongoDB", facet: 'product', active: false },
1415
{ name: "Atlas", facet: 'product', active: false },
@@ -93,6 +94,10 @@ class App extends React.Component {
9394
this.setState({ options })
9495
}
9596

97+
onResults = (results) => {
98+
this.setState({searchResults: results})
99+
}
100+
96101
render () {
97102
// TODO: This should be possible with reduce
98103
let facetNames = []
@@ -115,7 +120,7 @@ class App extends React.Component {
115120
return { facet: option.facet, name: option.name } // remove active field for stringification
116121
})
117122

118-
const tutorials = this.state.tutorials.filter(tutorial => {
123+
const tutorialsMatchingFacets = this.state.tutorials.filter(tutorial => {
119124
let shouldInclude = true // by default show all the tutorials
120125

121126
activeOptions.map(option => {
@@ -129,8 +134,17 @@ class App extends React.Component {
129134
return shouldInclude
130135
})
131136

137+
let tutorials = tutorialsMatchingFacets
138+
if (this.state.searchResults !== null) {
139+
const tutorialsSet = new Set(tutorialsMatchingFacets.map(tutorial => tutorial.url))
140+
tutorials = this.state.searchResults.filter(slug => {
141+
return tutorialsSet.has(slug)
142+
})
143+
}
144+
132145
return (
133146
<div>
147+
<Search onResults={this.onResults} />
134148
<a onClick={this.clearFacets}>Clear Filters</a>
135149
{ facets }
136150
<TutorialList tutorials={ tutorials } />
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
import React from 'react'
2+
3+
import SearchChannel from './SearchChannel.js'
4+
5+
export default class Search extends React.Component {
6+
constructor(props) {
7+
super(props)
8+
9+
this.state = {
10+
searcher: new SearchChannel('/search.json'),
11+
timeout: -1,
12+
loaded: false,
13+
searchText: ''
14+
}
15+
16+
17+
this.state.searcher.load().then(() => {
18+
this.setState({loaded: true})
19+
}).catch((err) => console.error(err))
20+
21+
this.state.searcher.onresults = props.onResults
22+
}
23+
24+
get placeholder() {
25+
if (!this.state.searcher.loaded) {
26+
return 'Loading...'
27+
}
28+
29+
return 'search by keywords'
30+
}
31+
32+
onInput = (event) => {
33+
this.setState({
34+
searchText: event.target.value
35+
})
36+
37+
if (this.state.timeout !== -1) {
38+
clearTimeout(this.state.timeout)
39+
}
40+
41+
this.setState({timeout:
42+
this.state.timeout = setTimeout(() => {
43+
this.state.searcher.search(this.state.searchText)
44+
}, 250)})
45+
}
46+
47+
render() {
48+
return <input type="search" placeholder={this.placeholder} value={this.state.searchText} disabled={!this.state.searcher.loaded} onInput={this.onInput} />
49+
}
50+
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
'use strict'
2+
import lunr from 'lunr'
3+
4+
let searchIndex = null
5+
let slugMapping = []
6+
7+
function load(url) {
8+
let start
9+
return fetch(url).then((response) => {
10+
start = performance.now()
11+
return response.json()
12+
}).then((data) => {
13+
searchIndex = lunr.Index.load(data)
14+
slugMapping = data.slugs
15+
return (performance.now() - start) / 1000
16+
})
17+
}
18+
19+
function search(query) {
20+
const start = performance.now()
21+
const results = searchIndex.search(query).map((row) => {
22+
return slugMapping[row.ref]
23+
})
24+
// console.log((performance.now() - start) / 1000)
25+
return results
26+
}
27+
28+
self.onmessage = function(ev) {
29+
if (ev.data.load !== undefined) {
30+
load(ev.data.load).then((enlapsed) => {
31+
self.postMessage({ 'loaded': enlapsed })
32+
})
33+
} else if (ev.data.search !== undefined) {
34+
const results = search(ev.data.search)
35+
self.postMessage({ 'results': results })
36+
}
37+
}

0 commit comments

Comments
 (0)