1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
|
// Copyright (c) 2021 Johannes Stoelp
// file: search.js
const formatResultPreview = (body, terms) => {
let body_idx = 0;
// Collect indexes into `body` that match any term.
let matches = body.toLowerCase().split(" ").map(word => {
let idx = body_idx;
// Update global index into body.
body_idx += word.length + 1 /* for the ' ' split char */;
if (terms.some(term => word.startsWith(term))) {
return { idx : idx, end : idx + word.length };
} else {
return null;
}
}).filter(match => match !== null);
// Format preview, by making each term bold and adding a short slice
// following after the term.
let preview = [];
matches.forEach(match => {
let end = Math.min(match.end + 40, body.length - 1);
preview.push(`<b>${body.substring(match.idx, match.end)}</b>`
+ body.substring(match.end, end)
+ "...");
});
return preview.join(" ");
}
const formatResult = (result, terms) => {
return "<div>"
+ `<a href="${result.ref}">${result.doc.title}</a>`
+ `<div>${formatResultPreview(result.doc.body, terms)}</div>`
+ "</div>";
}
const evaluteSearchTerm = (term, ctx) => {
// Nothing todo if current search term is the same as last one.
if (term === ctx.last_search) {
return;
}
// Empty search term, show list of blog pages again.
if (term === "") {
ctx.pages.style.display = "block";
ctx.results.style.display = "none";
return;
}
// Search term entered, show result list and hide list with blog posts.
ctx.pages.style.display = "none";
ctx.results.style.display = "block";
// Perform actual search.
let results = ctx.search_index.search(term, {
bool: "AND",
expand: true, // Also match if words starts with search query.
fields: {
title: {boost: 2},
body : {boost: 1},
}
});
if (results.length !== 0) {
// Update last search term.
ctx.last_search = term;
// Reset HTML of results items (previous search results).
ctx.results_items.innerHTML = "";
// Generate HTML for search results.
results.forEach(result => {
let item = document.createElement("li");
item.innerHTML = formatResult(result, term.split(" "));
ctx.results_items.appendChild(item);
});
}
};
const debounce = (func, wait) => {
let timeout = null;
return () => {
clearTimeout(timeout);
timeout = setTimeout(func , wait);
};
}
window.onload = () => {
let ctx = {
// Get HTML DOM elements.
results : document.getElementById("search-results"),
results_items : document.getElementById("search-results-items"),
pages : document.getElementById("pages"),
// Load search index.
search_index : elasticlunr.Index.load(window.searchIndex),
// Last search term.
last_search : null,
};
if (ctx.search_index) {
const input = document.getElementById("search");
input.onkeyup = debounce(() => evaluteSearchTerm(input.value.trim(), ctx), 200);
}
}
|