forked from programminghistorian/jekyll
-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathlessonfilter.js
389 lines (321 loc) · 13.8 KB
/
lessonfilter.js
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
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
function resetSort() {
/* Function to reset sort buttons */
$('#current-sort').text('date');
$('#current-sort').removeClass().addClass("sort-desc");
$("#sort-by-date").removeClass().addClass("sort desc my-asc");
$("#sort-by-difficulty").removeClass().addClass("sort my-desc");
}
function applySortFromURI(uri, featureList) {
/* Function to update lesson-list using featureList and sort direction
- uses URI to generate sort directions
*/
const params = uri.search(true);
let sortOrder = params.sortOrder;
let sortType = params.sortType;
let nonSortOrder = (sortOrder == "desc" ? "asc" : "desc");
if (sortType) {
// Update arrow of selected sort button
$("#sort-by-" + sortType).removeClass().addClass("sort " + sortOrder + " my-" + nonSortOrder);
// Update arrow of filter header
$('#current-sort').removeClass().addClass("sort-" + sortOrder);
// Update filter header and restore defaults to other button
if (sortType == "date") {
$('#current-sort').text('date');
$('#sort-by-difficulty').removeClass().addClass("sort my-asc");
}
else {
$('#current-sort').text('difficulty');
$('#sort-by-date').removeClass().addClass("sort my-desc");
}
}
else {
// If no sort params, perform default sort
sortType = "date";
sortOrder = "desc";
// Set button classes to defaults
resetSort();
}
// Perform new sort
featureList.sort(sortType, { order: sortOrder });
// Reset filter results header
$('#results-value').text($(this).text().split(' ')[0] + '(' + featureList.update().matchingItems.length + ')' + " ");
$('#results-value').css('textTransform', 'uppercase');
}
function lunrSearch(searchString, idx, corpus, featureList, uri) {
/* Function to generate search using lunr
- find search results using index
- load in original corpus and find relevant lessons
- generate new html to show search snippet
- use featureList to show lessons with search results (also relevant filters)
- display html
*/
// Get lessons that contain search string using lunr index
const results = idx.search(searchString);
// Get lessons from corpus that contain the search string
let docs = results.filter(result => corpus.some(doc => result.ref === doc.url)).map(result => {
let doc = corpus.find(lesson => lesson.url === result.ref);
return {
...result,
...doc
};
});
const BUFFER = 30 // Number of characters to show for kwic-results
const MAX_KWIC = 3
// Create html to show search results using html mark
let elements = []
docs.map((doc) => {
// Gets element name from absolute url that is generated by search.json. If URL syntax is changed, will need to double check that this code still works. Currently it gets /language/lessons/lesson-url.
let elementName = '/' + doc.url.split('/').slice(3).join('/');
let search_keys = Object.keys(doc.matchData.metadata);
// Gets exact position of search results and highlights them using mark tags
let inner_results = search_keys.map((token) => {
let all_positions = doc.matchData.metadata[token].body.position;
let grouped_kwic = all_positions.slice(0, MAX_KWIC).map(function (pos) {
let loc1 = pos[0]
let loc2 = loc1 + pos[1]
let rendered_text = `... ${doc.body.substring(loc1 - BUFFER, loc1)} <mark>${doc.body.substring(loc1, loc2)}</mark> ${doc.body.substring(loc2, loc2 + BUFFER)} ...`
return rendered_text
}).join("")
return grouped_kwic
}).join("").replace(/(\r\n|\n|\r)/gm, "");
// Saves element name and results to array to be displayed once featureList is updated.
elements.push({ 'elementName': elementName, 'innerResults': inner_results });
// Updates score element for search results
$(`span[id="${elementName}-score"]`).html(doc.score);
});
// Filter featureList to only show items from search results and active filters
const params = uri.search(true);
let type = params.activity ? params.activity : params.topic;
featureList.filter((item) => {
let topicsArray = item.values().topics.split(/\s/);
let condition = params.topic ? topicsArray.includes(type) : item.values().activity == type;
// return items in list that are in search results and filter if clicked
return docs.find((doc) => {
if (doc.title === item.values().title) {
// update score values for item
item.values().score = doc.score;
// Could simply to just do Object.keys(params) > 1 here but in case we add more URI values this will explicitly check for filters along with search
return ['topic', 'activity'].some(key => Object.keys(params).includes(key)) ? ((doc.title === item.values().title) && condition) : (doc.title === item.values().title);
}
});
});
// remove existing sorts and indicate sorted by search results
$('#sort-by-difficulty').removeClass().addClass("sort my-asc");
$('#sort-by-date').removeClass().addClass("sort my-desc");
$('#current-sort').text('search');
$('#current-sort').removeClass().addClass("sort-desc");
// Sort featureList by score
featureList.sort('score', { order: "desc" });
// Hide original abstracts and update Filtering to show number of results
$('.abstract').css('display', 'none');
$('#results-value').text($(this).text().split(' ')[0] + '(' + featureList.update().matchingItems.length + ')' + " ");
$('#results-value').css('textTransform', 'uppercase');
// Display updated search results
elements.map((elm) => {
$(`p[id="${elm.elementName}-search_results"]`).css('display', '');
$(`p[id="${elm.elementName}-search_results"]`).html(elm.innerResults);
});
}
function resetSearch() {
/* Function to reset search values and display to original settings */
// Empty search input
$('#search').val('');
// Hide and empty search results
$('.search_results').css('display', 'none');
$('.search_results').html('');
// Show original abstract results
$('.abstract').css('display', 'block');
// Reset score
$('.score').html(0);
};
function wireButtons() {
/* Main function for page load */
// set URI object to current Window location
let uri = new URI(location);
// add 'content' if you want to use content from lesson-describe though it's currently commented out
let options = {
valueNames: ['date', 'title', 'difficulty', 'activity', 'topics', 'abstract', 'score']
};
let featureList = new List('lesson-list', options);
// We need a stateObj for adjusting the URIs on button clicks, but the value is moot for now; could be useful for future functionality.
let stateObj = { foo: "bar" };
// Get search indices and corpuses using URI to make a call to search-index for each language
let idx;
let corpus;
async function loadSearchData() {
/*Function to load search data asynchronously*/
// Hide enable button and show search input
$("#enable-search-div").css("display", "none");
$("#search-div").css("display", "");
$('#loading-search').css('display', 'none');
$('#search').css('display', '');
$('#search-button').prop('disabled', false);
// Get language and load idx and corpus
const language = uri.toString().split('/').slice(-3)[0];
const indexResponse = await fetch(`https://programminghistorian.github.io/search-index/indices/index${language.toUpperCase()}.json`);
const indexJSON = await indexResponse.json();
idx = lunr.Index.load(indexJSON);
const corpusResponse = await fetch(`https://programminghistorian.org/${language}/search.json`);
const corpusJSON = await corpusResponse.json();
corpus = corpusJSON;
}
// Enable search on button click
$("#enable-search-div").on("click", function () {
// Start loading search data
loadSearchData();
});
$("#search-button").click(function () {
/* Function that does ALL filtering and searching of list (whether search exists or not)
- checks if search string exists or not and updates URI
- if search string exists calls lunrSearch
- else performs filtering (if filter exists) or resets list
*/
// Get search string
const searchString = $("#search").val();
// Check that's it's not empty
if (searchString.length > 0) {
// Update URI
uri.setSearch("search", searchString)
uri.removeSearch('sortOrder');
uri.removeSearch('sortType');
history.pushState(stateObj, "", uri.toString());
// Call lunr search
lunrSearch(searchString, idx, corpus, featureList, uri);
} else {
// If empty check if topic or activity filter selected
// Call reset search to empty out search values and update URI
uri.removeSearch('search');
history.pushState(stateObj, "", uri.toString());
resetSearch();
const params = uri.search(true);
let type = params.activity ? params.activity : params.topic;
if (type) {
// If filter selected, return filter lessons based on URI
featureList.filter((item) => {
item.values().score = 0;
let topicsArray = item.values().topics.split(/\s/);
let condition = params.topic ? topicsArray.includes(type) : item.values().activity == type;
return condition
});
applySortFromURI(uri, featureList);
} else {
$('#filter-none').click();
}
}
});
// Search lessons on enter press
$('#search').on('keyup', function (event) {
if (event.which == 13) {
$("#search-button").click();
}
});
// Search info button clicked show additional information
$('#search-info-button').click(function() {
if ($("#search-info").hasClass("visible")) {
$("#search-info").removeClass("visible");
} else {
$("#search-info").addClass("visible");
}
});
// When a filter button is clicked
$('.filter').children().click(function () {
// Set clicked button as current
$('.filter').children().removeClass("current");
$(this).addClass("current");
let type = $(this).attr("id").substr(7);
let filterType = $(this).parent().attr('class').split(' ')[1];
// Reset url parameters
if (filterType === 'activities') {
uri.removeSearch("topic")
uri.setSearch("activity", type)
} else {
uri.removeSearch("activity")
uri.setSearch("topic", type)
}
// returns the URI instance for chaining
history.pushState(stateObj, "", uri.toString());
// Use search to perform filtering
$("#search-button").click();
return false;
});
// When the reset button is clicked
$('#filter-none').click(function () {
// Remove highlighting from filter buttons
$('.filter').children().removeClass("current");
// Reset filter results header
$('#results-value').text(featureList.update().items.length);
// Reset search results
resetSearch();
// Reset uri to remove query params
uri.search("");
history.pushState(stateObj, "", uri.toString());
// Reset filtering and perform default sort
featureList.filter(item => {
item.values().score = 0;
return true
});
featureList.sort('date', { order: "desc" });
// Reset sort buttons to defaults
resetSort();
// Call reset search a second time seems to refresh html (very hacky solution)
resetSearch();
});
// When a sort button is clicked, update the results header to show current sorting status
$('.sort').click(function () {
// Get sort type from button (date or difficulty)
let sortType = $(this).attr("data-sort");
// Get sort order info from button
let curSortOrder = $(this).hasClass("my-asc") ? "desc" : "asc";
let newSortOrder = (curSortOrder == "asc" ? "desc" : "asc");
// update class for clicked button
$(this).removeClass("my-" + newSortOrder).addClass("my-" + curSortOrder);
// Update filter results header to show current sorting (date or difficulty)
// Reset the other (non-pressed) button to its default sort arrow.
if (sortType == "date") {
$('#current-sort').text(dateSort);
$('#sort-by-difficulty').removeClass().addClass("sort my-asc");
}
else {
$('#current-sort').text(difficultySort);
$('#sort-by-date').removeClass().addClass("sort my-desc");
}
// Set CSS class for results header (the arrow)
$('#current-sort').removeClass().addClass("sort-" + newSortOrder);
// Manually sort to override default behavior of list.js, which does not support multiple sort buttons very well.
// The problem is that when changing sort type, list.js automatically sorts asc on the first sort. This is not
// necessarily what we want to do.
featureList.sort(sortType, { order: newSortOrder });
// update url parameters
uri.setSearch("sortType", sortType);
uri.setSearch("sortOrder", newSortOrder);
history.pushState(stateObj, "", uri.toString());
});
/***************************************
All below code runs on each page load
****************************************/
// set labels based on the current language
const dateSort = $('#date-sort-text').attr('label');
const difficultySort = $('#difficulty-sort-text').attr("label");
// // Look for URI query params
const params = uri.search(true);
let filter = params.activity ? params.activity : params.topic;
let search = params.search;
// If a filter or search is present in the URI, simulate a click to run filter
// Clicking a filter button checks for sort params in URI, so don't do it twice OR load search data and simulate search click.
const preloader = $('#pre-loader');
if (search) {
// If search is true, load pre-loading graph to allow search results time to load
preloader.css('visibility', 'visible');
$('#search').val(search);
loadSearchData().then(() => {
preloader.fadeOut(1500);
$('#search-button').click();
});
} else if (filter) {
$("#filter-" + filter).click();
}
else {
// Apply sorting criteria from the URI if no filter
applySortFromURI(uri, featureList);
}
};