Skip to content

Commit 850df09

Browse files
committed
Search: Improve search result snippets. Cancel highlight with ESC.
1 parent a198e99 commit 850df09

File tree

2 files changed

+103
-21
lines changed

2 files changed

+103
-21
lines changed

src/theme/book.css

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -231,6 +231,11 @@ ul#searchresults span.teaser {
231231
display: block;
232232
clear: both;
233233
margin: 5px 0 0 20px;
234+
font-size: 0.8em;
235+
}
236+
ul#searchresults span.teaser em {
237+
font-weight: bold;
238+
font-style: normal;
234239
}
235240
.menu-title {
236241
position: absolute;

src/theme/book.js

Lines changed: 98 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ $( document ).ready(function() {
2525
},
2626
mark_exclude : [], // ['.hljs']
2727
current_searchterm : "",
28-
teaser_size_half : 80,
28+
teaser_words : 30,
2929
resultcount_limit : 30,
3030
SEARCH_PARAM : 'search',
3131
MARK_PARAM : 'highlight',
@@ -160,24 +160,7 @@ $( document ).ready(function() {
160160
,
161161
formatSearchResult : function (result, searchterms) {
162162
// Show text around first occurrence of first search term.
163-
var firstoccurence = result.doc.body.search(searchterms[0]);
164-
var teaser = "";
165-
if (firstoccurence != -1) {
166-
var teaserstartindex = firstoccurence - this.teaser_size_half;
167-
var nextwordindex = result.doc.body.indexOf(" ", teaserstartindex);
168-
if (nextwordindex != -1) {
169-
teaserstartindex = nextwordindex;
170-
}
171-
var teaserendindex = firstoccurence + this.teaser_size_half;
172-
nextwordindex = result.doc.body.indexOf(" ", teaserendindex);
173-
if (nextwordindex != -1) {
174-
teaserendindex = nextwordindex;
175-
}
176-
teaser = (teaserstartindex > 0) ? "... " : "";
177-
teaser += result.doc.body.substring(teaserstartindex, teaserendindex) + " ...";
178-
} else {
179-
teaser = result.doc.body.substr(0, this.teaser_size_half * 2) + " ...";
180-
}
163+
var teaser = this.makeTeaser(this.escapeHTML(result.doc.body), searchterms);
181164

182165
// The ?MARK_PARAM= parameter belongs inbetween the page and the #heading-anchor
183166
var url = result.ref.split("#");
@@ -189,10 +172,104 @@ $( document ).ready(function() {
189172
+ url[0] + '?' + this.MARK_PARAM + '=' + searchterms + '#' + url[1]
190173
+ '">' + result.doc.breadcrumbs + '</a>' // doc.title
191174
+ '<span class="breadcrumbs">' + '</span>'
192-
+ '<span class="teaser">' + this.escapeHTML(teaser) + '</span>'
175+
+ '<span class="teaser">' + teaser + '</span>'
193176
+ '</li>');
194177
}
195178
,
179+
makeTeaser : function (body, searchterms) {
180+
// The strategy is as follows:
181+
// First, assign a value to each word in the document:
182+
// Words that correspond to search terms (stemmer aware): 40
183+
// Normal words: 2
184+
// First word in a sentence: 8
185+
// Then use a sliding window with a constant number of words and count the
186+
// sum of the values of the words within the window. Then use the window that got the
187+
// maximum sum. If there are multiple maximas, then get the last one.
188+
// Enclose the terms in <em>.
189+
var stemmed_searchterms = searchterms.map(elasticlunr.stemmer);
190+
var searchterm_weight = 40;
191+
var weighted = []; // contains elements of ["word", weight, index_in_document]
192+
// split in sentences, then words
193+
var sentences = body.split('. ');
194+
var index = 0;
195+
var value = 0;
196+
var searchterm_found = false;
197+
for (var sentenceindex in sentences) {
198+
var words = sentences[sentenceindex].split(' ');
199+
value = 8;
200+
for (var wordindex in words) {
201+
var word = words[wordindex];
202+
if (word.length > 0) {
203+
for (var searchtermindex in stemmed_searchterms) {
204+
if (elasticlunr.stemmer(word).startsWith(stemmed_searchterms[searchtermindex])) {
205+
value = searchterm_weight;
206+
searchterm_found = true;
207+
}
208+
};
209+
weighted.push([word, value, index]);
210+
value = 2;
211+
}
212+
index += word.length;
213+
index += 1; // ' ' or '.' if last word in sentence
214+
};
215+
index += 1; // because we split at a two-char boundary '. '
216+
};
217+
218+
if (weighted.length == 0) {
219+
return body;
220+
}
221+
222+
var window_weight = [];
223+
var window_size = Math.min(weighted.length, this.teaser_words);
224+
225+
var cur_sum = 0;
226+
for (var wordindex = 0; wordindex < window_size; wordindex++) {
227+
cur_sum += weighted[wordindex][1];
228+
};
229+
window_weight.push(cur_sum);
230+
for (var wordindex = 0; wordindex < weighted.length - window_size; wordindex++) {
231+
cur_sum -= weighted[wordindex][1];
232+
cur_sum += weighted[wordindex + window_size][1];
233+
window_weight.push(cur_sum);
234+
};
235+
236+
if (searchterm_found) {
237+
var max_sum = 0;
238+
var max_sum_window_index = 0;
239+
// backwards
240+
for (var i = window_weight.length - 1; i >= 0; i--) {
241+
if (window_weight[i] > max_sum) {
242+
max_sum = window_weight[i];
243+
max_sum_window_index = i;
244+
}
245+
};
246+
} else {
247+
max_sum_window_index = 0;
248+
}
249+
250+
// add <em/> around searchterms
251+
var teaser_split = [];
252+
var index = weighted[max_sum_window_index][2];
253+
for (var i = max_sum_window_index; i < max_sum_window_index+window_size; i++) {
254+
var word = weighted[i];
255+
if (index < word[2]) {
256+
// missing text from index to start of `word`
257+
teaser_split.push(body.substring(index, word[2]));
258+
index = word[2];
259+
}
260+
if (word[1] == searchterm_weight) {
261+
teaser_split.push("<em>")
262+
}
263+
index = word[2] + word[0].length;
264+
teaser_split.push(body.substring(word[2], index));
265+
if (word[1] == searchterm_weight) {
266+
teaser_split.push("</em>")
267+
}
268+
};
269+
270+
return teaser_split.join('');
271+
}
272+
,
196273
doSearch : function (searchterm) {
197274

198275
// Don't search the same twice
@@ -245,7 +322,6 @@ $( document ).ready(function() {
245322
,
246323
init : function () {
247324
var this_ = this;
248-
window.md = this;
249325

250326
// For testing purposes: Index current page
251327
//this.create_test_searchindex();
@@ -296,6 +372,7 @@ $( document ).ready(function() {
296372
(this.searchbar[0].value.trim() != 0) ? "push" : "replace");
297373
this.unfocusSearchbar();
298374
this.searchbar_outer.slideUp();
375+
this.content.unmark();
299376
return;
300377
}
301378
if (!this.hasFocus() && e.keyCode == this.SEARCH_HOTKEY_KEYCODE) {

0 commit comments

Comments
 (0)