website-theme/assets/js/search.js

176 lines
4.7 KiB
JavaScript
Raw Normal View History

2019-03-26 12:45:29 -04:00
/* global lunr $ */
let lunrIndex
let lunrResult
let pagesIndex
2018-10-08 06:37:58 -04:00
2019-03-26 13:03:27 -04:00
/**
* A function for splitting a string into bigram.
*
* @static
* @param {?(string|object|object[])} obj - The object to convert into tokens
* @param {?object} metadata - Optional metadata to associate with every token
* @returns {lunr.Token[]}
*/
2019-03-26 12:45:29 -04:00
const bigramTokeniser = (obj, metadata) => {
if (obj == null || obj === undefined) {
2019-03-24 13:02:50 -04:00
return []
}
2019-03-26 13:03:27 -04:00
let str = obj.toString().trim().toLowerCase()
let tokens = []
2019-03-24 13:02:50 -04:00
2019-03-26 13:03:27 -04:00
for (let i = 0; i <= str.length - 2; i++) {
const tokenMetadata = lunr.utils.clone(metadata) || {}
2019-03-26 12:45:29 -04:00
tokenMetadata['position'] = [i, i + 2]
tokenMetadata['index'] = tokens.length
2019-03-24 13:02:50 -04:00
tokens.push(
2019-03-26 12:45:29 -04:00
new lunr.Token(
2019-03-24 13:02:50 -04:00
str.slice(i, i + 2),
tokenMetadata
)
)
}
return tokens
}
2019-03-26 13:03:27 -04:00
/**
* A function for separating a string into bigram and join it with space.
*
* @static
* @param {?string} query - The string to convert into tokens
* @returns {string}
*/
const queryNgramSeparator = (query) => {
const str = query.toString().trim().toLowerCase()
const tokens = []
2019-03-24 13:02:50 -04:00
2019-03-26 13:03:27 -04:00
for (let i = 0; i <= str.length - 2; i++) {
2019-03-24 13:02:50 -04:00
tokens.push(str.slice(i, i + 2))
}
return tokens.join(' ')
}
2018-10-08 06:37:58 -04:00
/**
2019-03-24 11:41:16 -04:00
* Preparation for using lunr.js
2018-10-08 06:37:58 -04:00
*/
2019-03-26 12:45:29 -04:00
const initLunr = () => {
2019-03-26 13:03:27 -04:00
$.getJSON('index.json').done((index) => {
2019-03-24 11:41:16 -04:00
pagesIndex = index
2019-03-26 13:24:43 -04:00
lunrIndex = lunr(builder => {
builder.tokenizer = bigramTokeniser
builder.pipeline.reset()
builder.ref('ref')
builder.field('title', { boost: 10 })
2020-01-18 16:01:03 -05:00
builder.field('tags', { boost: 10 })
2019-03-26 13:24:43 -04:00
builder.field('body')
builder.metadataWhitelist = ['position']
2019-03-26 13:03:27 -04:00
for (let page of pagesIndex) {
2019-03-26 13:24:43 -04:00
builder.add(page)
2019-03-24 11:41:16 -04:00
}
})
2019-03-26 13:03:27 -04:00
}).fail((jqxhr, textStatus, error) => {
const err = textStatus + ', ' + error
2018-10-08 06:37:58 -04:00
console.error('Error getting Hugo index flie:', err)
})
}
2019-03-24 11:41:16 -04:00
/**
* Searching pages using lunr
2019-03-26 13:03:27 -04:00
* @param {String} query - Query string for searching
* @return {Object[]} - Array of search results
2019-03-24 11:41:16 -04:00
*/
2019-03-26 13:03:27 -04:00
const search = (query) => {
2019-03-24 13:02:50 -04:00
lunrResult = lunrIndex.search(queryNgramSeparator(query))
2019-03-26 13:03:27 -04:00
return lunrResult.map((result) => {
return pagesIndex.filter((page) => {
2019-03-24 11:41:16 -04:00
return page.ref === result.ref
})[0]
})
}
2018-10-08 06:37:58 -04:00
/**
* Setup UI for Search
*/
2019-03-26 13:03:27 -04:00
const initUI = () => {
2018-10-08 06:37:58 -04:00
// Clear query when clear icon is clicked
2019-03-26 13:03:27 -04:00
$('#searchBoxIcon').click(() => {
2019-03-23 03:18:03 -04:00
$('#searchBoxInput').val('')
$('#searchBoxInput').trigger('keyup')
2020-02-23 11:11:02 -05:00
$('#searchBoxInput').focus()
2018-10-08 06:37:58 -04:00
})
// Event when chenging query
2019-03-26 13:24:43 -04:00
$('#searchBoxInput').keyup(event => {
2019-03-26 13:03:27 -04:00
const $searchResults = $('#searchResults')
2019-03-26 13:24:43 -04:00
const query = $(event.currentTarget).val()
2018-10-08 06:37:58 -04:00
// Icon switching
const iconUrl = $('#searchBoxIcon').attr('src')
2018-10-08 06:37:58 -04:00
if (query.length) {
$('#searchBoxIcon').attr('src', iconUrl.replace('search.png', 'clear.png'))
2018-10-08 06:37:58 -04:00
$('#searchBoxIcon').css('cursor', 'pointer')
} else {
$('#searchBoxIcon').attr('src', iconUrl.replace('clear.png', 'search.png'))
2018-10-08 06:37:58 -04:00
$('#searchBoxIcon').css('cursor', 'default')
}
// Only trigger a search when 2 chars. at least have been provided
if (query.length < 2) {
$searchResults.hide()
return
}
// Display search results
2019-03-24 11:41:16 -04:00
renderResults(search(query))
2018-10-08 06:37:58 -04:00
$searchResults.show()
})
// Emit keyup event for when the query is already setted with browser back etc.
2019-03-23 03:18:03 -04:00
$('#searchBoxInput').trigger('keyup')
2020-02-23 11:11:02 -05:00
// Focus at searchBox
$('#searchBoxInput').focus()
2018-10-08 06:37:58 -04:00
}
/**
* Rendering search results
* @param {Object[]} results Array of search results
*/
2019-03-26 13:03:27 -04:00
const renderResults = (results) => {
const $searchResults = $('#searchResults')
const query = $('#searchBoxInput').val()
const BODY_LENGTH = 100
const MAX_PAGES = 10
2018-10-08 06:37:58 -04:00
// Clear search result
$searchResults.empty()
// Show message when results is empty
if (!results.length) {
$searchResults.append('<div class="searchResultPage">No results found for query "' + query + '"</div>')
return
}
// Only show the ten first results
2019-03-26 13:03:27 -04:00
results.slice(0, MAX_PAGES).forEach((result, idx) => {
const $searchResultPage = $('<div class="searchResultPage">')
const metadata = lunrResult[idx].matchData.metadata
const matchPosition = metadata[Object.keys(metadata)[0]].body ? metadata[Object.keys(metadata)[0]].body.position[0][0] : 0
const bodyStartPosition = (matchPosition - (BODY_LENGTH / 2) > 0) ? matchPosition - (BODY_LENGTH / 2) : 0
2019-03-24 11:41:16 -04:00
2018-10-08 06:37:58 -04:00
$searchResultPage.append('<a class="searchResultTitle" href="' + result.ref + '">' + result.title + '</a>')
2019-03-24 11:41:16 -04:00
$searchResultPage.append('<div class="searchResultBody">' + result.body.substr(bodyStartPosition, BODY_LENGTH) + '</div>')
2018-10-08 06:37:58 -04:00
$searchResults.append($searchResultPage)
// Highlight keyword
$('#searchResults').mark(query)
})
}
2019-03-24 11:41:16 -04:00
initLunr()
2018-10-08 06:37:58 -04:00
2019-03-26 13:03:27 -04:00
$(() => {
2018-10-08 06:37:58 -04:00
initUI()
})