Skip to content

Commit 655d6af

Browse files
committed
improvement: Allow the search type to be selected in the UI.
1 parent 26f7232 commit 655d6af

9 files changed

+176
-49
lines changed

assets/css/search-bar.css

+28-2
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@
3030
color: var(--searchAccentMain);
3131
position: relative;
3232
height: 46px;
33-
padding: 8px 35px 8px 43px;
33+
padding: 8px 135px 8px 43px;
3434
width: 100%;
3535
transition: var(--transition-all);
3636
}
@@ -80,7 +80,8 @@
8080
background-color: transparent;
8181
border: none;
8282
cursor: pointer;
83-
right: 11px;
83+
/* FIXME: I know this is wrong. Not sure what to do though */
84+
right: 100px;
8485
margin: 0;
8586
opacity: 0.5;
8687
padding: 5px 1px 5px 0;
@@ -95,6 +96,31 @@
9596
opacity: 0.7;
9697
}
9798

99+
.top-search .search-bar .search-type {
100+
background-color: transparent;
101+
border-top: 1px solid transparent;
102+
border-bottom: 1px solid transparent;
103+
border-right: 1px solid transparent;
104+
border-left: 1px solid var(--searchBarBorder);
105+
border-top-right-radius: var(--borderRadius-base);
106+
border-bottom-right-radius: var(--borderRadius-base);
107+
color: var(--searchAccentMain);
108+
position: absolute;
109+
top: calc(50% - 23px);
110+
right: 0;
111+
height: 46px;
112+
padding: 8px 8px 8px 16px;
113+
transition: var(--transition-all);
114+
z-index: 99;
115+
}
116+
117+
.top-search .search-bar .search-type:focus {
118+
border: 1px solid var(--searchBarFocusColor);
119+
border-top-right-radius: calc(var(--borderRadius-base) - 1px);
120+
border-bottom-right-radius: calc(var(--borderRadius-base) - 1px);
121+
box-shadow: 0px 4px 20px 0px var(--searchBarBorderColor) inset;
122+
}
123+
98124
.top-search .search-settings button.icon-settings {
99125
display: flex;
100126
align-items: center;

assets/js/search-bar.js

+89-3
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import {
1010
AUTOCOMPLETE_CONTAINER_SELECTOR,
1111
AUTOCOMPLETE_SUGGESTION_LIST_SELECTOR
1212
} from './autocomplete/autocomplete-list'
13-
import { isEmbedded } from './globals'
13+
import { isEmbedded, getSearchNodes, getVersionNodes } from './globals'
1414
import { isAppleOS, qs } from './helpers'
1515

1616
const SEARCH_INPUT_SELECTOR = 'form.search-bar input'
@@ -37,6 +37,12 @@ function initialize () {
3737
focusSearchInput()
3838
if (open) { showPreview(event.target) } else { hidePreview() }
3939
}
40+
41+
if (window.navigator.onLine) {
42+
enableRemoteSearch(null)
43+
} else {
44+
disableRemoteSearch(null)
45+
}
4046
}
4147

4248
/**
@@ -137,6 +143,66 @@ function addEventListeners () {
137143
clearSearch()
138144
hideAutocomplete()
139145
})
146+
147+
window.addEventListener('online', enableRemoteSearch)
148+
window.addEventListener('offline', disableRemoteSearch)
149+
}
150+
151+
function enableRemoteSearch (_event) {
152+
Array.from(document.getElementsByClassName('online-only')).forEach(element => {
153+
element.removeAttribute('disabled')
154+
element.removeAttribute('title')
155+
updateSearchTypeOptions(element)
156+
})
157+
}
158+
159+
function disableRemoteSearch (_event) {
160+
Array.from(document.getElementsByClassName('online-only')).forEach(element => {
161+
element.setAttribute('disabled', '')
162+
element.setAttribute('title', 'Local searching only - browser is offline')
163+
updateSearchTypeOptions(element)
164+
})
165+
}
166+
167+
function updateSearchTypeOptions (element) {
168+
Array.from(element.getElementsByTagName('option')).forEach(option => {
169+
if (option.value === 'related') {
170+
if (shouldEnableRelatedSearch()) {
171+
option.removeAttribute('disabled')
172+
option.removeAttribute('title')
173+
} else {
174+
option.setAttribute('disabled', '')
175+
option.setAttribute('title', 'No known related packages to search')
176+
}
177+
}
178+
if (option.value === 'latest') {
179+
if (shouldEnableLatestSearch()) {
180+
option.removeAttribute('disabled')
181+
option.removeAttribute('title')
182+
} else {
183+
option.setAttribute('disabled', '')
184+
option.setAttribute('title', 'Already browsing latest version')
185+
}
186+
}
187+
})
188+
}
189+
190+
function shouldEnableRelatedSearch () {
191+
return getSearchNodes().length > 1
192+
}
193+
194+
function shouldEnableLatestSearch () {
195+
const versionNodes = getVersionNodes()
196+
197+
if (versionNodes.length > 0) {
198+
const latest = versionNodes[0]
199+
const searchNodes = getSearchNodes()
200+
const match = searchNodes.some(node => `v${node.version}` === latest.version)
201+
202+
return !match
203+
}
204+
205+
return false
140206
}
141207

142208
function handleAutocompleteFormSubmission (event) {
@@ -155,8 +221,18 @@ function handleAutocompleteFormSubmission (event) {
155221
anchor.setAttribute('href', autocompleteSuggestion.link)
156222
} else {
157223
const meta = document.querySelector('meta[name="exdoc:full-text-search-url"]')
158-
const url = meta ? meta.getAttribute('content') : 'search.html?q='
159-
anchor.setAttribute('href', `${url}${encodeURIComponent(searchInput.value)}`)
224+
const url = meta ? meta.getAttribute('content') : 'search.html'
225+
226+
const params = new URLSearchParams()
227+
params.set('q', searchInput.value)
228+
229+
const searchType = getSearchType()
230+
231+
if (searchType !== 'local') {
232+
params.set('type', searchType)
233+
}
234+
235+
anchor.setAttribute('href', `${url}?${params.toString()}`)
160236
}
161237

162238
anchor.click()
@@ -167,6 +243,16 @@ function handleAutocompleteFormSubmission (event) {
167243
}
168244
}
169245

246+
function getSearchType () {
247+
const searchTypes = Array.from(document.getElementsByClassName('search-type'))
248+
249+
if (searchTypes.length > 0) {
250+
return searchTypes[0].value
251+
}
252+
253+
return 'local'
254+
}
255+
170256
function clearSearch () {
171257
const input = qs(SEARCH_INPUT_SELECTOR)
172258
input.value = ''

assets/js/search-page.js

+32-22
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ lunr.Pipeline.registerFunction(docTrimmerFunction, 'docTrimmer')
2424

2525
window.addEventListener('exdoc:loaded', initialize)
2626

27-
function initialize() {
27+
function initialize () {
2828
const pathname = window.location.pathname
2929
if (pathname.endsWith('/search.html') || pathname.endsWith('/search')) {
3030
const query = getQueryParamByName('q')
@@ -33,7 +33,7 @@ function initialize() {
3333
}
3434
}
3535

36-
async function search(value, queryType) {
36+
async function search (value, queryType) {
3737
if (isBlank(value)) {
3838
renderResults({ value })
3939
} else {
@@ -44,6 +44,8 @@ async function search(value, queryType) {
4444
const searchNodes = getSearchNodes()
4545

4646
if (['related', 'latest'].includes(queryType) && searchNodes.length > 0) {
47+
setSearchType(queryType)
48+
4749
results = await remoteSearch(value, queryType, searchNodes)
4850
} else {
4951
results = await localSearch(value)
@@ -56,7 +58,7 @@ async function search(value, queryType) {
5658
}
5759
}
5860

59-
async function localSearch(value) {
61+
async function localSearch (value) {
6062
const index = await getIndex()
6163

6264
// We cannot match on atoms :foo because that would be considered
@@ -65,7 +67,7 @@ async function localSearch(value) {
6567
return searchResultsToDecoratedSearchItems(index.search(fixedValue))
6668
}
6769

68-
async function remoteSearch(value, queryType, searchNodes) {
70+
async function remoteSearch (value, queryType, searchNodes) {
6971
let filterNodes = searchNodes
7072

7173
if (queryType === 'latest') {
@@ -107,13 +109,21 @@ async function remoteSearch(value, queryType, searchNodes) {
107109
}
108110
}
109111

110-
function renderResults({ value, results, errorMessage }) {
112+
function setSearchType (value) {
113+
const searchTypes = Array.from(document.getElementsByClassName('search-type'))
114+
115+
searchTypes.forEach(element => {
116+
element.value = value
117+
})
118+
}
119+
120+
function renderResults ({ value, results, errorMessage }) {
111121
const searchContainer = qs(SEARCH_CONTAINER_SELECTOR)
112122
const resultsHtml = searchResultsTemplate({ value, results, errorMessage })
113123
searchContainer.innerHTML = resultsHtml
114124
}
115125

116-
async function getIndex() {
126+
async function getIndex () {
117127
const cachedIndex = await loadIndex()
118128
if (cachedIndex) { return cachedIndex }
119129

@@ -122,7 +132,7 @@ async function getIndex() {
122132
return index
123133
}
124134

125-
async function loadIndex() {
135+
async function loadIndex () {
126136
try {
127137
const serializedIndex = sessionStorage.getItem(indexStorageKey())
128138
if (serializedIndex) {
@@ -137,7 +147,7 @@ async function loadIndex() {
137147
}
138148
}
139149

140-
async function saveIndex(index) {
150+
async function saveIndex (index) {
141151
try {
142152
const serializedIndex = await compress(index)
143153
sessionStorage.setItem(indexStorageKey(), serializedIndex)
@@ -146,7 +156,7 @@ async function saveIndex(index) {
146156
}
147157
}
148158

149-
async function compress(index) {
159+
async function compress (index) {
150160
const stream = new Blob([JSON.stringify(index)], {
151161
type: 'application/json'
152162
}).stream().pipeThrough(new window.CompressionStream('gzip'))
@@ -156,7 +166,7 @@ async function compress(index) {
156166
return b64encode(buffer)
157167
}
158168

159-
async function decompress(index) {
169+
async function decompress (index) {
160170
const stream = new Blob([b64decode(index)], {
161171
type: 'application/json'
162172
}).stream().pipeThrough(new window.DecompressionStream('gzip'))
@@ -165,7 +175,7 @@ async function decompress(index) {
165175
return JSON.parse(blob)
166176
}
167177

168-
function b64encode(buffer) {
178+
function b64encode (buffer) {
169179
let binary = ''
170180
const bytes = new Uint8Array(buffer)
171181
const len = bytes.byteLength
@@ -175,7 +185,7 @@ function b64encode(buffer) {
175185
return window.btoa(binary)
176186
}
177187

178-
function b64decode(str) {
188+
function b64decode (str) {
179189
const binaryString = window.atob(str)
180190
const len = binaryString.length
181191
const bytes = new Uint8Array(new ArrayBuffer(len))
@@ -185,11 +195,11 @@ function b64decode(str) {
185195
return bytes
186196
}
187197

188-
function indexStorageKey() {
198+
function indexStorageKey () {
189199
return `idv5:${getProjectNameAndVersion()}`
190200
}
191201

192-
function createIndex() {
202+
function createIndex () {
193203
return lunr(function () {
194204
this.ref('ref')
195205
this.field('title', { boost: 3 })
@@ -207,11 +217,11 @@ function createIndex() {
207217
})
208218
}
209219

210-
function docTokenSplitter(builder) {
220+
function docTokenSplitter (builder) {
211221
builder.pipeline.before(lunr.stemmer, docTokenFunction)
212222
}
213223

214-
function docTokenFunction(token) {
224+
function docTokenFunction (token) {
215225
// If we have something with an arity, we split on : . to make partial
216226
// matches easier. We split only when tokenizing, not when searching.
217227
// Below we use ExDoc.Markdown.to_ast/2 as an example.
@@ -275,11 +285,11 @@ function docTokenFunction(token) {
275285
return tokens
276286
}
277287

278-
function docTrimmer(builder) {
288+
function docTrimmer (builder) {
279289
builder.pipeline.before(lunr.stemmer, docTrimmerFunction)
280290
}
281291

282-
function docTrimmerFunction(token) {
292+
function docTrimmerFunction (token) {
283293
// Preserve @ and : at the beginning of tokens,
284294
// and ? and ! at the end of tokens. It needs to
285295
// be done before stemming, otherwise search and
@@ -289,7 +299,7 @@ function docTrimmerFunction(token) {
289299
})
290300
}
291301

292-
function searchResultsToDecoratedSearchItems(results) {
302+
function searchResultsToDecoratedSearchItems (results) {
293303
return results
294304
// If the docs are regenerated without changing its version,
295305
// a reference may have been doc'ed false in the code but
@@ -306,11 +316,11 @@ function searchResultsToDecoratedSearchItems(results) {
306316
})
307317
}
308318

309-
function getSearchItemByRef(ref) {
319+
function getSearchItemByRef (ref) {
310320
return searchData.items.find(searchItem => searchItem.ref === ref) || null
311321
}
312322

313-
function getExcerpts(searchItem, metadata) {
323+
function getExcerpts (searchItem, metadata) {
314324
const { doc } = searchItem
315325
const searchTerms = Object.keys(metadata)
316326

@@ -331,7 +341,7 @@ function getExcerpts(searchItem, metadata) {
331341
return excerpts.slice(0, 1)
332342
}
333343

334-
function excerpt(doc, sliceStart, sliceLength) {
344+
function excerpt (doc, sliceStart, sliceLength) {
335345
const startPos = Math.max(sliceStart - EXCERPT_RADIUS, 0)
336346
const endPos = Math.min(sliceStart + sliceLength + EXCERPT_RADIUS, doc.length)
337347
return [

0 commit comments

Comments
 (0)