Skip to content

Commit 7564a9c

Browse files
NoelDeMartinchrisvfritz
authored andcommitted
Implement search page (#1488)
* #1087 Implement search page * Fix spacing and add https in search page * Add sponsors and push history state when search is updated in search page * Add EOL to search styles * Visit first result when submitting search form * Add 'search by algolia' to search page
1 parent b89dd05 commit 7564a9c

File tree

7 files changed

+236
-19
lines changed

7 files changed

+236
-19
lines changed

src/images/search-by-algolia.png

2.81 KB
Loading

src/v2/search/index.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
title: Search Vue.js
3+
type: search
4+
search: true
5+
---

themes/vue/layout/page.ejs

+2
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@
2323
<% } %>
2424
<% if (page.sponsors) { %>
2525
<%- partial('sponsors-page') %>
26+
<% } else if (page.search) { %>
27+
<%- partial('search-page') %>
2628
<% } else { %>
2729
<%- page.content %>
2830
<% } %>

themes/vue/layout/partials/sidebar.ejs

+21-19
Original file line numberDiff line numberDiff line change
@@ -5,25 +5,27 @@
55
</ul>
66
<div class="list">
77
<%- partial('partials/sponsors_sidebar') %>
8-
<h2>
9-
<% titles = {
10-
api: 'API',
11-
examples: 'Examples',
12-
guide: 'Guide',
13-
cookbook: 'Cookbook<sup class="beta">beta</sup>',
14-
'style-guide': 'Style Guide<sup class="beta">beta</sup>'
15-
} %>
16-
<%- titles[type] %>
17-
<% if (['cookbook', 'style-guide'].indexOf(type) === -1) { %>
18-
<select class="version-select">
19-
<option value="SELF" selected>2.x</option>
20-
<option value="v1">1.0</option>
21-
<option value="012">0.12</option>
22-
<option value="011">0.11</option>
23-
</select>
24-
<% } %>
25-
</h2>
26-
<%- partial('partials/toc', { type: type }) %>
8+
<% if (type !== 'search') { %>
9+
<h2>
10+
<% titles = {
11+
api: 'API',
12+
examples: 'Examples',
13+
guide: 'Guide',
14+
cookbook: 'Cookbook<sup class="beta">beta</sup>',
15+
'style-guide': 'Style Guide<sup class="beta">beta</sup>'
16+
} %>
17+
<%- titles[type] %>
18+
<% if (['cookbook', 'style-guide'].indexOf(type) === -1) { %>
19+
<select class="version-select">
20+
<option value="SELF" selected>2.x</option>
21+
<option value="v1">1.0</option>
22+
<option value="012">0.12</option>
23+
<option value="011">0.11</option>
24+
</select>
25+
<% } %>
26+
</h2>
27+
<%- partial('partials/toc', { type: type }) %>
28+
<% } %>
2729
</div>
2830
</div>
2931
</div>

themes/vue/layout/search-page.ejs

+178
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,178 @@
1+
<div id="search-page">
2+
<form class="search-form" @submit="submit">
3+
<input
4+
class="search-query"
5+
v-model="search"
6+
placeholder="Search Vue.js"
7+
>
8+
<div class="search-footer">
9+
<p>
10+
<template v-if="totalResults">
11+
<strong>{{ totalResults }} results</strong> found in {{ queryTime }}ms
12+
</template>
13+
</p>
14+
<a target="_blank" href="https://www.algolia.com/">
15+
<img src="/images/search-by-algolia.png" height="16">
16+
</a>
17+
</div>
18+
</form>
19+
20+
<template v-if="results.length">
21+
<search-result
22+
v-for="(result, i) in results"
23+
:key="i"
24+
:result="result"
25+
></search-result>
26+
</template>
27+
28+
<p v-else>No results were found.</p>
29+
30+
<div ref="infiniteScrollAnchor"></div>
31+
32+
</div>
33+
34+
<script src="https://cdn.jsdelivr.net/algoliasearch/3/algoliasearch.min.js"></script>
35+
<script src="https://cdn.jsdelivr.net/algoliasearch.helper/2/algoliasearch.helper.min.js"></script>
36+
<script src="https://polyfill.io/v2/polyfill.min.js?features=IntersectionObserver"></script>
37+
<script>
38+
var match = window.location.pathname.match(/^\/(v\d+)/)
39+
var version = match ? match[1] : 'v2'
40+
var algolia = algoliasearch('BH4D9OD16A', '85cc3221c9f23bfbaa4e3913dd7625ea')
41+
var algoliaHelper = algoliasearchHelper(algolia, 'vuejs', {
42+
hitsPerPage: 15,
43+
maxValuesPerFacet: 10,
44+
advancedSyntax: true,
45+
facets: ['version'],
46+
})
47+
48+
algoliaHelper.addFacetRefinement('version', version)
49+
algoliaHelper.on('result', parseSearchResults)
50+
51+
var searchPage = new Vue({
52+
el: '#search-page',
53+
components: {
54+
'search-result': {
55+
props: {
56+
result: {
57+
type: Object,
58+
required: true,
59+
},
60+
},
61+
render(h) {
62+
var content = []
63+
content.push(h('a', {
64+
attrs: {
65+
class: 'title',
66+
href: this.result.url,
67+
},
68+
domProps: { innerHTML: this.result.title },
69+
}))
70+
if (this.result.summary) {
71+
content.push(h('p', {
72+
attrs: { class: 'summary' },
73+
domProps: { innerHTML: this.result.summary },
74+
}))
75+
}
76+
content.push(h(
77+
'div',
78+
{ attrs: { class: 'breadcrumbs'} },
79+
this.result.breadcrumbs.map(function(breadcrumb) {
80+
return h('span', {
81+
attrs: { class: 'breadcrumb' },
82+
domProps: { innerHTML: breadcrumb },
83+
})
84+
})
85+
))
86+
return h('div', { attrs: { class: 'search-result' } }, content)
87+
}
88+
}
89+
},
90+
data: {
91+
search: (new URL(location)).searchParams.get('q') || '',
92+
totalResults: 0,
93+
queryTime: 0,
94+
results: [],
95+
totalPages: 0,
96+
lastPage: 0,
97+
},
98+
watch: {
99+
search() {
100+
this.updateSearch()
101+
window.history.pushState(
102+
{},
103+
'Vue.js Search',
104+
window.location.origin + window.location.pathname + '?q=' + encodeURIComponent(this.search)
105+
)
106+
}
107+
},
108+
created() {
109+
this.updateSearch()
110+
},
111+
mounted() {
112+
var observer = new IntersectionObserver(function(entries) {
113+
if (entries[0].isIntersecting && searchPage.totalPages > searchPage.lastPage + 1) {
114+
searchPage.addPage()
115+
}
116+
})
117+
observer.observe(this.$refs.infiniteScrollAnchor)
118+
},
119+
methods: {
120+
addPage() {
121+
algoliaHelper.setCurrentPage(this.lastPage + 1).search()
122+
},
123+
updateSearch() {
124+
algoliaHelper.setCurrentPage(0).setQuery(this.search).search()
125+
},
126+
submit(e) {
127+
e.preventDefault()
128+
if (this.results.length > 0) {
129+
window.location = this.results[0].url
130+
}
131+
}
132+
}
133+
})
134+
135+
function parseSearchResults(content) {
136+
if (content.query === '' || !(content.hits instanceof Array)) {
137+
searchPage.totalResults = 0
138+
searchPage.queryTime = 0
139+
searchPage.results = []
140+
searchPage.totalPages = 0
141+
searchPage.lastPage = 0
142+
return
143+
}
144+
145+
var results = []
146+
147+
for (var hit of content.hits) {
148+
var hierarchy = hit._highlightResult.hierarchy
149+
var titles = []
150+
var level = 0
151+
var levelName
152+
while ((levelName = 'lvl' + level++) in hierarchy) {
153+
titles.push(hierarchy[levelName].value)
154+
}
155+
var summary
156+
if (hit._snippetResult && hit._snippetResult.content) {
157+
summary = hit._snippetResult.content.value + '...'
158+
}
159+
results.push({
160+
title: titles.pop(),
161+
url: hit.url,
162+
summary: summary,
163+
breadcrumbs: titles,
164+
})
165+
}
166+
167+
searchPage.totalResults = content.nbHits
168+
searchPage.queryTime = content.processingTimeMS
169+
searchPage.totalPages = content.nbPages
170+
searchPage.lastPage = content.page
171+
172+
if (searchPage.lastPage === 0) {
173+
searchPage.results = results
174+
} else {
175+
searchPage.results = searchPage.results.concat(results)
176+
}
177+
}
178+
</script>
+29
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
#search-page
2+
.search-form
3+
.search-query
4+
width: 100%
5+
border-radius: 5px
6+
margin-right: 0
7+
.search-footer
8+
display: flex
9+
height: 35px
10+
align-items: center
11+
justify-content: space-between
12+
margin-bottom: 15px
13+
p
14+
margin: 0
15+
padding: 0
16+
.search-result
17+
margin-bottom: 15px;
18+
.title
19+
display: block
20+
font-size: 17.55px
21+
.summary
22+
padding: 0
23+
margin: 0
24+
.breadcrumb
25+
color: $light
26+
& + .breadcrumb::before
27+
content: "\203A\A0"
28+
margin-left: 5px
29+
color: $light

themes/vue/source/css/page.styl

+1
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
@import "_demo"
66
@import "_sponsors-page"
77
@import "_sponsors-sidebar"
8+
@import "_search-page"
89
@import "_migration"
910
@import "_sidebar"
1011
@import "_offline-menu"

0 commit comments

Comments
 (0)