commonmeta-ruby 3.7.1 → 3.7.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.github/workflows/publish.yml +23 -0
- data/Gemfile.lock +26 -26
- data/bin/commonmeta +1 -1
- data/docs/.gitignore +1 -0
- data/docs/_publish.yml +4 -0
- data/docs/_quarto.yml +46 -0
- data/docs/_site/favicon.ico +0 -0
- data/docs/_site/images/icon.png +0 -0
- data/docs/_site/index.html +947 -0
- data/docs/_site/readers/bibtex_reader.html +802 -0
- data/docs/_site/search.json +74 -0
- data/docs/_site/site_libs/bootstrap/bootstrap-dark.min.css +9 -0
- data/docs/_site/site_libs/bootstrap/bootstrap-icons.css +2078 -0
- data/docs/_site/site_libs/bootstrap/bootstrap-icons.woff +0 -0
- data/docs/_site/site_libs/bootstrap/bootstrap.min.css +9 -0
- data/docs/_site/site_libs/bootstrap/bootstrap.min.js +7 -0
- data/docs/_site/site_libs/clipboard/clipboard.min.js +7 -0
- data/docs/_site/site_libs/quarto-html/anchor.min.js +9 -0
- data/docs/_site/site_libs/quarto-html/popper.min.js +6 -0
- data/docs/_site/site_libs/quarto-html/quarto-syntax-highlighting-dark.css +179 -0
- data/docs/_site/site_libs/quarto-html/quarto-syntax-highlighting.css +179 -0
- data/docs/_site/site_libs/quarto-html/quarto.js +889 -0
- data/docs/_site/site_libs/quarto-html/tippy.css +1 -0
- data/docs/_site/site_libs/quarto-html/tippy.umd.min.js +2 -0
- data/docs/_site/site_libs/quarto-nav/headroom.min.js +7 -0
- data/docs/_site/site_libs/quarto-nav/quarto-nav.js +288 -0
- data/docs/_site/site_libs/quarto-search/autocomplete.umd.js +3 -0
- data/docs/_site/site_libs/quarto-search/fuse.min.js +9 -0
- data/docs/_site/site_libs/quarto-search/quarto-search.js +1241 -0
- data/docs/_site/utils/doi_utils.html +787 -0
- data/docs/_site/writers/bibtex_writer.html +803 -0
- data/docs/favicon.ico +0 -0
- data/docs/images/icon.png +0 -0
- data/docs/index.qmd +83 -0
- data/docs/readers/bibtex_reader.ipynb +37 -0
- data/docs/theme.scss +7 -0
- data/docs/utils/doi_utils.ipynb +28 -0
- data/docs/writers/bibtex_writer.ipynb +39 -0
- data/lib/commonmeta/cli.rb +8 -8
- data/lib/commonmeta/readers/json_feed_reader.rb +6 -6
- data/lib/commonmeta/utils.rb +2 -2
- data/lib/commonmeta/version.rb +1 -1
- data/spec/cli_spec.rb +5 -5
- data/spec/fixtures/vcr_cassettes/Commonmeta_CLI/json_feed/json_feed_blog_slug.yml +71 -0
- data/spec/fixtures/vcr_cassettes/Commonmeta_CLI/json_feed/json_feed_updated.yml +186 -0
- data/spec/fixtures/vcr_cassettes/Commonmeta_Metadata/get_json_feed/updated_posts.yml +186 -0
- data/spec/readers/json_feed_reader_spec.rb +5 -5
- metadata +42 -5
- data/spec/fixtures/vcr_cassettes/Commonmeta_CLI/json_feed/json_feed_not_indexed.yml +0 -37
- data/spec/fixtures/vcr_cassettes/Commonmeta_Metadata/get_json_feed/not_indexed_posts.yml +0 -37
@@ -0,0 +1,1241 @@
|
|
1
|
+
const kQueryArg = "q";
|
2
|
+
const kResultsArg = "show-results";
|
3
|
+
|
4
|
+
// If items don't provide a URL, then both the navigator and the onSelect
|
5
|
+
// function aren't called (and therefore, the default implementation is used)
|
6
|
+
//
|
7
|
+
// We're using this sentinel URL to signal to those handlers that this
|
8
|
+
// item is a more item (along with the type) and can be handled appropriately
|
9
|
+
const kItemTypeMoreHref = "0767FDFD-0422-4E5A-BC8A-3BE11E5BBA05";
|
10
|
+
|
11
|
+
window.document.addEventListener("DOMContentLoaded", function (_event) {
|
12
|
+
// Ensure that search is available on this page. If it isn't,
|
13
|
+
// should return early and not do anything
|
14
|
+
var searchEl = window.document.getElementById("quarto-search");
|
15
|
+
if (!searchEl) return;
|
16
|
+
|
17
|
+
const { autocomplete } = window["@algolia/autocomplete-js"];
|
18
|
+
|
19
|
+
let quartoSearchOptions = {};
|
20
|
+
let language = {};
|
21
|
+
const searchOptionEl = window.document.getElementById(
|
22
|
+
"quarto-search-options"
|
23
|
+
);
|
24
|
+
if (searchOptionEl) {
|
25
|
+
const jsonStr = searchOptionEl.textContent;
|
26
|
+
quartoSearchOptions = JSON.parse(jsonStr);
|
27
|
+
language = quartoSearchOptions.language;
|
28
|
+
}
|
29
|
+
|
30
|
+
// note the search mode
|
31
|
+
if (quartoSearchOptions.type === "overlay") {
|
32
|
+
searchEl.classList.add("type-overlay");
|
33
|
+
} else {
|
34
|
+
searchEl.classList.add("type-textbox");
|
35
|
+
}
|
36
|
+
|
37
|
+
// Used to determine highlighting behavior for this page
|
38
|
+
// A `q` query param is expected when the user follows a search
|
39
|
+
// to this page
|
40
|
+
const currentUrl = new URL(window.location);
|
41
|
+
const query = currentUrl.searchParams.get(kQueryArg);
|
42
|
+
const showSearchResults = currentUrl.searchParams.get(kResultsArg);
|
43
|
+
const mainEl = window.document.querySelector("main");
|
44
|
+
|
45
|
+
// highlight matches on the page
|
46
|
+
if (query && mainEl) {
|
47
|
+
// perform any highlighting
|
48
|
+
highlight(escapeRegExp(query), mainEl);
|
49
|
+
|
50
|
+
// fix up the URL to remove the q query param
|
51
|
+
const replacementUrl = new URL(window.location);
|
52
|
+
replacementUrl.searchParams.delete(kQueryArg);
|
53
|
+
window.history.replaceState({}, "", replacementUrl);
|
54
|
+
}
|
55
|
+
|
56
|
+
// function to clear highlighting on the page when the search query changes
|
57
|
+
// (e.g. if the user edits the query or clears it)
|
58
|
+
let highlighting = true;
|
59
|
+
const resetHighlighting = (searchTerm) => {
|
60
|
+
if (mainEl && highlighting && query && searchTerm !== query) {
|
61
|
+
clearHighlight(query, mainEl);
|
62
|
+
highlighting = false;
|
63
|
+
}
|
64
|
+
};
|
65
|
+
|
66
|
+
// Clear search highlighting when the user scrolls sufficiently
|
67
|
+
const resetFn = () => {
|
68
|
+
resetHighlighting("");
|
69
|
+
window.removeEventListener("quarto-hrChanged", resetFn);
|
70
|
+
window.removeEventListener("quarto-sectionChanged", resetFn);
|
71
|
+
};
|
72
|
+
|
73
|
+
// Register this event after the initial scrolling and settling of events
|
74
|
+
// on the page
|
75
|
+
window.addEventListener("quarto-hrChanged", resetFn);
|
76
|
+
window.addEventListener("quarto-sectionChanged", resetFn);
|
77
|
+
|
78
|
+
// Responsively switch to overlay mode if the search is present on the navbar
|
79
|
+
// Note that switching the sidebar to overlay mode requires more coordinate (not just
|
80
|
+
// the media query since we generate different HTML for sidebar overlays than we do
|
81
|
+
// for sidebar input UI)
|
82
|
+
const detachedMediaQuery =
|
83
|
+
quartoSearchOptions.type === "overlay" ? "all" : "(max-width: 991px)";
|
84
|
+
|
85
|
+
// If configured, include the analytics client to send insights
|
86
|
+
const plugins = configurePlugins(quartoSearchOptions);
|
87
|
+
|
88
|
+
let lastState = null;
|
89
|
+
const { setIsOpen, setQuery, setCollections } = autocomplete({
|
90
|
+
container: searchEl,
|
91
|
+
detachedMediaQuery: detachedMediaQuery,
|
92
|
+
defaultActiveItemId: 0,
|
93
|
+
panelContainer: "#quarto-search-results",
|
94
|
+
panelPlacement: quartoSearchOptions["panel-placement"],
|
95
|
+
debug: false,
|
96
|
+
openOnFocus: true,
|
97
|
+
plugins,
|
98
|
+
classNames: {
|
99
|
+
form: "d-flex",
|
100
|
+
},
|
101
|
+
translations: {
|
102
|
+
clearButtonTitle: language["search-clear-button-title"],
|
103
|
+
detachedCancelButtonText: language["search-detached-cancel-button-title"],
|
104
|
+
submitButtonTitle: language["search-submit-button-title"],
|
105
|
+
},
|
106
|
+
initialState: {
|
107
|
+
query,
|
108
|
+
},
|
109
|
+
getItemUrl({ item }) {
|
110
|
+
return item.href;
|
111
|
+
},
|
112
|
+
onStateChange({ state }) {
|
113
|
+
// If this is a file URL, note that
|
114
|
+
|
115
|
+
// Perhaps reset highlighting
|
116
|
+
resetHighlighting(state.query);
|
117
|
+
|
118
|
+
// If the panel just opened, ensure the panel is positioned properly
|
119
|
+
if (state.isOpen) {
|
120
|
+
if (lastState && !lastState.isOpen) {
|
121
|
+
setTimeout(() => {
|
122
|
+
positionPanel(quartoSearchOptions["panel-placement"]);
|
123
|
+
}, 150);
|
124
|
+
}
|
125
|
+
}
|
126
|
+
|
127
|
+
// Perhaps show the copy link
|
128
|
+
showCopyLink(state.query, quartoSearchOptions);
|
129
|
+
|
130
|
+
lastState = state;
|
131
|
+
},
|
132
|
+
reshape({ sources, state }) {
|
133
|
+
return sources.map((source) => {
|
134
|
+
try {
|
135
|
+
const items = source.getItems();
|
136
|
+
|
137
|
+
// Validate the items
|
138
|
+
validateItems(items);
|
139
|
+
|
140
|
+
// group the items by document
|
141
|
+
const groupedItems = new Map();
|
142
|
+
items.forEach((item) => {
|
143
|
+
const hrefParts = item.href.split("#");
|
144
|
+
const baseHref = hrefParts[0];
|
145
|
+
const isDocumentItem = hrefParts.length === 1;
|
146
|
+
|
147
|
+
const items = groupedItems.get(baseHref);
|
148
|
+
if (!items) {
|
149
|
+
groupedItems.set(baseHref, [item]);
|
150
|
+
} else {
|
151
|
+
// If the href for this item matches the document
|
152
|
+
// exactly, place this item first as it is the item that represents
|
153
|
+
// the document itself
|
154
|
+
if (isDocumentItem) {
|
155
|
+
items.unshift(item);
|
156
|
+
} else {
|
157
|
+
items.push(item);
|
158
|
+
}
|
159
|
+
groupedItems.set(baseHref, items);
|
160
|
+
}
|
161
|
+
});
|
162
|
+
|
163
|
+
const reshapedItems = [];
|
164
|
+
let count = 1;
|
165
|
+
for (const [_key, value] of groupedItems) {
|
166
|
+
const firstItem = value[0];
|
167
|
+
reshapedItems.push({
|
168
|
+
...firstItem,
|
169
|
+
type: kItemTypeDoc,
|
170
|
+
});
|
171
|
+
|
172
|
+
const collapseMatches = quartoSearchOptions["collapse-after"];
|
173
|
+
const collapseCount =
|
174
|
+
typeof collapseMatches === "number" ? collapseMatches : 1;
|
175
|
+
|
176
|
+
if (value.length > 1) {
|
177
|
+
const target = `search-more-${count}`;
|
178
|
+
const isExpanded =
|
179
|
+
state.context.expanded &&
|
180
|
+
state.context.expanded.includes(target);
|
181
|
+
|
182
|
+
const remainingCount = value.length - collapseCount;
|
183
|
+
|
184
|
+
for (let i = 1; i < value.length; i++) {
|
185
|
+
if (collapseMatches && i === collapseCount) {
|
186
|
+
reshapedItems.push({
|
187
|
+
target,
|
188
|
+
title: isExpanded
|
189
|
+
? language["search-hide-matches-text"]
|
190
|
+
: remainingCount === 1
|
191
|
+
? `${remainingCount} ${language["search-more-match-text"]}`
|
192
|
+
: `${remainingCount} ${language["search-more-matches-text"]}`,
|
193
|
+
type: kItemTypeMore,
|
194
|
+
href: kItemTypeMoreHref,
|
195
|
+
});
|
196
|
+
}
|
197
|
+
|
198
|
+
if (isExpanded || !collapseMatches || i < collapseCount) {
|
199
|
+
reshapedItems.push({
|
200
|
+
...value[i],
|
201
|
+
type: kItemTypeItem,
|
202
|
+
target,
|
203
|
+
});
|
204
|
+
}
|
205
|
+
}
|
206
|
+
}
|
207
|
+
count += 1;
|
208
|
+
}
|
209
|
+
|
210
|
+
return {
|
211
|
+
...source,
|
212
|
+
getItems() {
|
213
|
+
return reshapedItems;
|
214
|
+
},
|
215
|
+
};
|
216
|
+
} catch (error) {
|
217
|
+
// Some form of error occurred
|
218
|
+
return {
|
219
|
+
...source,
|
220
|
+
getItems() {
|
221
|
+
return [
|
222
|
+
{
|
223
|
+
title: error.name || "An Error Occurred While Searching",
|
224
|
+
text:
|
225
|
+
error.message ||
|
226
|
+
"An unknown error occurred while attempting to perform the requested search.",
|
227
|
+
type: kItemTypeError,
|
228
|
+
},
|
229
|
+
];
|
230
|
+
},
|
231
|
+
};
|
232
|
+
}
|
233
|
+
});
|
234
|
+
},
|
235
|
+
navigator: {
|
236
|
+
navigate({ itemUrl }) {
|
237
|
+
if (itemUrl !== offsetURL(kItemTypeMoreHref)) {
|
238
|
+
window.location.assign(itemUrl);
|
239
|
+
}
|
240
|
+
},
|
241
|
+
navigateNewTab({ itemUrl }) {
|
242
|
+
if (itemUrl !== offsetURL(kItemTypeMoreHref)) {
|
243
|
+
const windowReference = window.open(itemUrl, "_blank", "noopener");
|
244
|
+
if (windowReference) {
|
245
|
+
windowReference.focus();
|
246
|
+
}
|
247
|
+
}
|
248
|
+
},
|
249
|
+
navigateNewWindow({ itemUrl }) {
|
250
|
+
if (itemUrl !== offsetURL(kItemTypeMoreHref)) {
|
251
|
+
window.open(itemUrl, "_blank", "noopener");
|
252
|
+
}
|
253
|
+
},
|
254
|
+
},
|
255
|
+
getSources({ state, setContext, setActiveItemId, refresh }) {
|
256
|
+
return [
|
257
|
+
{
|
258
|
+
sourceId: "documents",
|
259
|
+
getItemUrl({ item }) {
|
260
|
+
if (item.href) {
|
261
|
+
return offsetURL(item.href);
|
262
|
+
} else {
|
263
|
+
return undefined;
|
264
|
+
}
|
265
|
+
},
|
266
|
+
onSelect({
|
267
|
+
item,
|
268
|
+
state,
|
269
|
+
setContext,
|
270
|
+
setIsOpen,
|
271
|
+
setActiveItemId,
|
272
|
+
refresh,
|
273
|
+
}) {
|
274
|
+
if (item.type === kItemTypeMore) {
|
275
|
+
toggleExpanded(item, state, setContext, setActiveItemId, refresh);
|
276
|
+
|
277
|
+
// Toggle more
|
278
|
+
setIsOpen(true);
|
279
|
+
}
|
280
|
+
},
|
281
|
+
getItems({ query }) {
|
282
|
+
if (query === null || query === "") {
|
283
|
+
return [];
|
284
|
+
}
|
285
|
+
|
286
|
+
const limit = quartoSearchOptions.limit;
|
287
|
+
if (quartoSearchOptions.algolia) {
|
288
|
+
return algoliaSearch(query, limit, quartoSearchOptions.algolia);
|
289
|
+
} else {
|
290
|
+
// Fuse search options
|
291
|
+
const fuseSearchOptions = {
|
292
|
+
isCaseSensitive: false,
|
293
|
+
shouldSort: true,
|
294
|
+
minMatchCharLength: 2,
|
295
|
+
limit: limit,
|
296
|
+
};
|
297
|
+
|
298
|
+
return readSearchData().then(function (fuse) {
|
299
|
+
return fuseSearch(query, fuse, fuseSearchOptions);
|
300
|
+
});
|
301
|
+
}
|
302
|
+
},
|
303
|
+
templates: {
|
304
|
+
noResults({ createElement }) {
|
305
|
+
const hasQuery = lastState.query;
|
306
|
+
|
307
|
+
return createElement(
|
308
|
+
"div",
|
309
|
+
{
|
310
|
+
class: `quarto-search-no-results${
|
311
|
+
hasQuery ? "" : " no-query"
|
312
|
+
}`,
|
313
|
+
},
|
314
|
+
language["search-no-results-text"]
|
315
|
+
);
|
316
|
+
},
|
317
|
+
header({ items, createElement }) {
|
318
|
+
// count the documents
|
319
|
+
const count = items.filter((item) => {
|
320
|
+
return item.type === kItemTypeDoc;
|
321
|
+
}).length;
|
322
|
+
|
323
|
+
if (count > 0) {
|
324
|
+
return createElement(
|
325
|
+
"div",
|
326
|
+
{ class: "search-result-header" },
|
327
|
+
`${count} ${language["search-matching-documents-text"]}`
|
328
|
+
);
|
329
|
+
} else {
|
330
|
+
return createElement(
|
331
|
+
"div",
|
332
|
+
{ class: "search-result-header-no-results" },
|
333
|
+
``
|
334
|
+
);
|
335
|
+
}
|
336
|
+
},
|
337
|
+
footer({ _items, createElement }) {
|
338
|
+
if (
|
339
|
+
quartoSearchOptions.algolia &&
|
340
|
+
quartoSearchOptions.algolia["show-logo"]
|
341
|
+
) {
|
342
|
+
const libDir = quartoSearchOptions.algolia["libDir"];
|
343
|
+
const logo = createElement("img", {
|
344
|
+
src: offsetURL(
|
345
|
+
`${libDir}/quarto-search/search-by-algolia.svg`
|
346
|
+
),
|
347
|
+
class: "algolia-search-logo",
|
348
|
+
});
|
349
|
+
return createElement(
|
350
|
+
"a",
|
351
|
+
{ href: "http://www.algolia.com/" },
|
352
|
+
logo
|
353
|
+
);
|
354
|
+
}
|
355
|
+
},
|
356
|
+
|
357
|
+
item({ item, createElement }) {
|
358
|
+
return renderItem(
|
359
|
+
item,
|
360
|
+
createElement,
|
361
|
+
state,
|
362
|
+
setActiveItemId,
|
363
|
+
setContext,
|
364
|
+
refresh,
|
365
|
+
quartoSearchOptions
|
366
|
+
);
|
367
|
+
},
|
368
|
+
},
|
369
|
+
},
|
370
|
+
];
|
371
|
+
},
|
372
|
+
});
|
373
|
+
|
374
|
+
window.quartoOpenSearch = () => {
|
375
|
+
setIsOpen(false);
|
376
|
+
setIsOpen(true);
|
377
|
+
focusSearchInput();
|
378
|
+
};
|
379
|
+
|
380
|
+
document.addEventListener("keyup", (event) => {
|
381
|
+
const { key } = event;
|
382
|
+
const kbds = quartoSearchOptions["keyboard-shortcut"];
|
383
|
+
const focusedEl = document.activeElement;
|
384
|
+
|
385
|
+
const isFormElFocused = [
|
386
|
+
"input",
|
387
|
+
"select",
|
388
|
+
"textarea",
|
389
|
+
"button",
|
390
|
+
"option",
|
391
|
+
].find((tag) => {
|
392
|
+
return focusedEl.tagName.toLowerCase() === tag;
|
393
|
+
});
|
394
|
+
|
395
|
+
if (kbds && kbds.includes(key) && !isFormElFocused) {
|
396
|
+
event.preventDefault();
|
397
|
+
window.quartoOpenSearch();
|
398
|
+
}
|
399
|
+
});
|
400
|
+
|
401
|
+
// Remove the labeleledby attribute since it is pointing
|
402
|
+
// to a non-existent label
|
403
|
+
if (quartoSearchOptions.type === "overlay") {
|
404
|
+
const inputEl = window.document.querySelector(
|
405
|
+
"#quarto-search .aa-Autocomplete"
|
406
|
+
);
|
407
|
+
if (inputEl) {
|
408
|
+
inputEl.removeAttribute("aria-labelledby");
|
409
|
+
}
|
410
|
+
}
|
411
|
+
|
412
|
+
function throttle(func, wait) {
|
413
|
+
let waiting = false;
|
414
|
+
return function () {
|
415
|
+
if (!waiting) {
|
416
|
+
func.apply(this, arguments);
|
417
|
+
waiting = true;
|
418
|
+
setTimeout(function () {
|
419
|
+
waiting = false;
|
420
|
+
}, wait);
|
421
|
+
}
|
422
|
+
};
|
423
|
+
}
|
424
|
+
|
425
|
+
// If the main document scrolls dismiss the search results
|
426
|
+
// (otherwise, since they're floating in the document they can scroll with the document)
|
427
|
+
window.document.body.onscroll = throttle(() => {
|
428
|
+
// Only do this if we're not detached
|
429
|
+
// Bug #7117
|
430
|
+
// This will happen when the keyboard is shown on ios (resulting in a scroll)
|
431
|
+
// which then closed the search UI
|
432
|
+
if (!window.matchMedia(detachedMediaQuery).matches) {
|
433
|
+
setIsOpen(false);
|
434
|
+
}
|
435
|
+
}, 50);
|
436
|
+
|
437
|
+
if (showSearchResults) {
|
438
|
+
setIsOpen(true);
|
439
|
+
focusSearchInput();
|
440
|
+
}
|
441
|
+
});
|
442
|
+
|
443
|
+
function configurePlugins(quartoSearchOptions) {
|
444
|
+
const autocompletePlugins = [];
|
445
|
+
const algoliaOptions = quartoSearchOptions.algolia;
|
446
|
+
if (
|
447
|
+
algoliaOptions &&
|
448
|
+
algoliaOptions["analytics-events"] &&
|
449
|
+
algoliaOptions["search-only-api-key"] &&
|
450
|
+
algoliaOptions["application-id"]
|
451
|
+
) {
|
452
|
+
const apiKey = algoliaOptions["search-only-api-key"];
|
453
|
+
const appId = algoliaOptions["application-id"];
|
454
|
+
|
455
|
+
// Aloglia insights may not be loaded because they require cookie consent
|
456
|
+
// Use deferred loading so events will start being recorded when/if consent
|
457
|
+
// is granted.
|
458
|
+
const algoliaInsightsDeferredPlugin = deferredLoadPlugin(() => {
|
459
|
+
if (
|
460
|
+
window.aa &&
|
461
|
+
window["@algolia/autocomplete-plugin-algolia-insights"]
|
462
|
+
) {
|
463
|
+
window.aa("init", {
|
464
|
+
appId,
|
465
|
+
apiKey,
|
466
|
+
useCookie: true,
|
467
|
+
});
|
468
|
+
|
469
|
+
const { createAlgoliaInsightsPlugin } =
|
470
|
+
window["@algolia/autocomplete-plugin-algolia-insights"];
|
471
|
+
// Register the insights client
|
472
|
+
const algoliaInsightsPlugin = createAlgoliaInsightsPlugin({
|
473
|
+
insightsClient: window.aa,
|
474
|
+
onItemsChange({ insights, insightsEvents }) {
|
475
|
+
const events = insightsEvents.flatMap((event) => {
|
476
|
+
// This API limits the number of items per event to 20
|
477
|
+
const chunkSize = 20;
|
478
|
+
const itemChunks = [];
|
479
|
+
const eventItems = event.items;
|
480
|
+
for (let i = 0; i < eventItems.length; i += chunkSize) {
|
481
|
+
itemChunks.push(eventItems.slice(i, i + chunkSize));
|
482
|
+
}
|
483
|
+
// Split the items into multiple events that can be sent
|
484
|
+
const events = itemChunks.map((items) => {
|
485
|
+
return {
|
486
|
+
...event,
|
487
|
+
items,
|
488
|
+
};
|
489
|
+
});
|
490
|
+
return events;
|
491
|
+
});
|
492
|
+
|
493
|
+
for (const event of events) {
|
494
|
+
insights.viewedObjectIDs(event);
|
495
|
+
}
|
496
|
+
},
|
497
|
+
});
|
498
|
+
return algoliaInsightsPlugin;
|
499
|
+
}
|
500
|
+
});
|
501
|
+
|
502
|
+
// Add the plugin
|
503
|
+
autocompletePlugins.push(algoliaInsightsDeferredPlugin);
|
504
|
+
return autocompletePlugins;
|
505
|
+
}
|
506
|
+
}
|
507
|
+
|
508
|
+
// For plugins that may not load immediately, create a wrapper
|
509
|
+
// plugin and forward events and plugin data once the plugin
|
510
|
+
// is initialized. This is useful for cases like cookie consent
|
511
|
+
// which may prevent the analytics insights event plugin from initializing
|
512
|
+
// immediately.
|
513
|
+
function deferredLoadPlugin(createPlugin) {
|
514
|
+
let plugin = undefined;
|
515
|
+
let subscribeObj = undefined;
|
516
|
+
const wrappedPlugin = () => {
|
517
|
+
if (!plugin && subscribeObj) {
|
518
|
+
plugin = createPlugin();
|
519
|
+
if (plugin && plugin.subscribe) {
|
520
|
+
plugin.subscribe(subscribeObj);
|
521
|
+
}
|
522
|
+
}
|
523
|
+
return plugin;
|
524
|
+
};
|
525
|
+
|
526
|
+
return {
|
527
|
+
subscribe: (obj) => {
|
528
|
+
subscribeObj = obj;
|
529
|
+
},
|
530
|
+
onStateChange: (obj) => {
|
531
|
+
const plugin = wrappedPlugin();
|
532
|
+
if (plugin && plugin.onStateChange) {
|
533
|
+
plugin.onStateChange(obj);
|
534
|
+
}
|
535
|
+
},
|
536
|
+
onSubmit: (obj) => {
|
537
|
+
const plugin = wrappedPlugin();
|
538
|
+
if (plugin && plugin.onSubmit) {
|
539
|
+
plugin.onSubmit(obj);
|
540
|
+
}
|
541
|
+
},
|
542
|
+
onReset: (obj) => {
|
543
|
+
const plugin = wrappedPlugin();
|
544
|
+
if (plugin && plugin.onReset) {
|
545
|
+
plugin.onReset(obj);
|
546
|
+
}
|
547
|
+
},
|
548
|
+
getSources: (obj) => {
|
549
|
+
const plugin = wrappedPlugin();
|
550
|
+
if (plugin && plugin.getSources) {
|
551
|
+
return plugin.getSources(obj);
|
552
|
+
} else {
|
553
|
+
return Promise.resolve([]);
|
554
|
+
}
|
555
|
+
},
|
556
|
+
data: (obj) => {
|
557
|
+
const plugin = wrappedPlugin();
|
558
|
+
if (plugin && plugin.data) {
|
559
|
+
plugin.data(obj);
|
560
|
+
}
|
561
|
+
},
|
562
|
+
};
|
563
|
+
}
|
564
|
+
|
565
|
+
function validateItems(items) {
|
566
|
+
// Validate the first item
|
567
|
+
if (items.length > 0) {
|
568
|
+
const item = items[0];
|
569
|
+
const missingFields = [];
|
570
|
+
if (item.href == undefined) {
|
571
|
+
missingFields.push("href");
|
572
|
+
}
|
573
|
+
if (!item.title == undefined) {
|
574
|
+
missingFields.push("title");
|
575
|
+
}
|
576
|
+
if (!item.text == undefined) {
|
577
|
+
missingFields.push("text");
|
578
|
+
}
|
579
|
+
|
580
|
+
if (missingFields.length === 1) {
|
581
|
+
throw {
|
582
|
+
name: `Error: Search index is missing the <code>${missingFields[0]}</code> field.`,
|
583
|
+
message: `The items being returned for this search do not include all the required fields. Please ensure that your index items include the <code>${missingFields[0]}</code> field or use <code>index-fields</code> in your <code>_quarto.yml</code> file to specify the field names.`,
|
584
|
+
};
|
585
|
+
} else if (missingFields.length > 1) {
|
586
|
+
const missingFieldList = missingFields
|
587
|
+
.map((field) => {
|
588
|
+
return `<code>${field}</code>`;
|
589
|
+
})
|
590
|
+
.join(", ");
|
591
|
+
|
592
|
+
throw {
|
593
|
+
name: `Error: Search index is missing the following fields: ${missingFieldList}.`,
|
594
|
+
message: `The items being returned for this search do not include all the required fields. Please ensure that your index items includes the following fields: ${missingFieldList}, or use <code>index-fields</code> in your <code>_quarto.yml</code> file to specify the field names.`,
|
595
|
+
};
|
596
|
+
}
|
597
|
+
}
|
598
|
+
}
|
599
|
+
|
600
|
+
let lastQuery = null;
|
601
|
+
function showCopyLink(query, options) {
|
602
|
+
const language = options.language;
|
603
|
+
lastQuery = query;
|
604
|
+
// Insert share icon
|
605
|
+
const inputSuffixEl = window.document.body.querySelector(
|
606
|
+
".aa-Form .aa-InputWrapperSuffix"
|
607
|
+
);
|
608
|
+
|
609
|
+
if (inputSuffixEl) {
|
610
|
+
let copyButtonEl = window.document.body.querySelector(
|
611
|
+
".aa-Form .aa-InputWrapperSuffix .aa-CopyButton"
|
612
|
+
);
|
613
|
+
|
614
|
+
if (copyButtonEl === null) {
|
615
|
+
copyButtonEl = window.document.createElement("button");
|
616
|
+
copyButtonEl.setAttribute("class", "aa-CopyButton");
|
617
|
+
copyButtonEl.setAttribute("type", "button");
|
618
|
+
copyButtonEl.setAttribute("title", language["search-copy-link-title"]);
|
619
|
+
copyButtonEl.onmousedown = (e) => {
|
620
|
+
e.preventDefault();
|
621
|
+
e.stopPropagation();
|
622
|
+
};
|
623
|
+
|
624
|
+
const linkIcon = "bi-clipboard";
|
625
|
+
const checkIcon = "bi-check2";
|
626
|
+
|
627
|
+
const shareIconEl = window.document.createElement("i");
|
628
|
+
shareIconEl.setAttribute("class", `bi ${linkIcon}`);
|
629
|
+
copyButtonEl.appendChild(shareIconEl);
|
630
|
+
inputSuffixEl.prepend(copyButtonEl);
|
631
|
+
|
632
|
+
const clipboard = new window.ClipboardJS(".aa-CopyButton", {
|
633
|
+
text: function (_trigger) {
|
634
|
+
const copyUrl = new URL(window.location);
|
635
|
+
copyUrl.searchParams.set(kQueryArg, lastQuery);
|
636
|
+
copyUrl.searchParams.set(kResultsArg, "1");
|
637
|
+
return copyUrl.toString();
|
638
|
+
},
|
639
|
+
});
|
640
|
+
clipboard.on("success", function (e) {
|
641
|
+
// Focus the input
|
642
|
+
|
643
|
+
// button target
|
644
|
+
const button = e.trigger;
|
645
|
+
const icon = button.querySelector("i.bi");
|
646
|
+
|
647
|
+
// flash "checked"
|
648
|
+
icon.classList.add(checkIcon);
|
649
|
+
icon.classList.remove(linkIcon);
|
650
|
+
setTimeout(function () {
|
651
|
+
icon.classList.remove(checkIcon);
|
652
|
+
icon.classList.add(linkIcon);
|
653
|
+
}, 1000);
|
654
|
+
});
|
655
|
+
}
|
656
|
+
|
657
|
+
// If there is a query, show the link icon
|
658
|
+
if (copyButtonEl) {
|
659
|
+
if (lastQuery && options["copy-button"]) {
|
660
|
+
copyButtonEl.style.display = "flex";
|
661
|
+
} else {
|
662
|
+
copyButtonEl.style.display = "none";
|
663
|
+
}
|
664
|
+
}
|
665
|
+
}
|
666
|
+
}
|
667
|
+
|
668
|
+
/* Search Index Handling */
|
669
|
+
// create the index
|
670
|
+
var fuseIndex = undefined;
|
671
|
+
var shownWarning = false;
|
672
|
+
async function readSearchData() {
|
673
|
+
// Initialize the search index on demand
|
674
|
+
if (fuseIndex === undefined) {
|
675
|
+
if (window.location.protocol === "file:" && !shownWarning) {
|
676
|
+
window.alert(
|
677
|
+
"Search requires JavaScript features disabled when running in file://... URLs. In order to use search, please run this document in a web server."
|
678
|
+
);
|
679
|
+
shownWarning = true;
|
680
|
+
return;
|
681
|
+
}
|
682
|
+
// create fuse index
|
683
|
+
const options = {
|
684
|
+
keys: [
|
685
|
+
{ name: "title", weight: 20 },
|
686
|
+
{ name: "section", weight: 20 },
|
687
|
+
{ name: "text", weight: 10 },
|
688
|
+
],
|
689
|
+
ignoreLocation: true,
|
690
|
+
threshold: 0.1,
|
691
|
+
};
|
692
|
+
const fuse = new window.Fuse([], options);
|
693
|
+
|
694
|
+
// fetch the main search.json
|
695
|
+
const response = await fetch(offsetURL("search.json"));
|
696
|
+
if (response.status == 200) {
|
697
|
+
return response.json().then(function (searchDocs) {
|
698
|
+
searchDocs.forEach(function (searchDoc) {
|
699
|
+
fuse.add(searchDoc);
|
700
|
+
});
|
701
|
+
fuseIndex = fuse;
|
702
|
+
return fuseIndex;
|
703
|
+
});
|
704
|
+
} else {
|
705
|
+
return Promise.reject(
|
706
|
+
new Error(
|
707
|
+
"Unexpected status from search index request: " + response.status
|
708
|
+
)
|
709
|
+
);
|
710
|
+
}
|
711
|
+
}
|
712
|
+
|
713
|
+
return fuseIndex;
|
714
|
+
}
|
715
|
+
|
716
|
+
function inputElement() {
|
717
|
+
return window.document.body.querySelector(".aa-Form .aa-Input");
|
718
|
+
}
|
719
|
+
|
720
|
+
function focusSearchInput() {
|
721
|
+
setTimeout(() => {
|
722
|
+
const inputEl = inputElement();
|
723
|
+
if (inputEl) {
|
724
|
+
inputEl.focus();
|
725
|
+
}
|
726
|
+
}, 50);
|
727
|
+
}
|
728
|
+
|
729
|
+
/* Panels */
|
730
|
+
const kItemTypeDoc = "document";
|
731
|
+
const kItemTypeMore = "document-more";
|
732
|
+
const kItemTypeItem = "document-item";
|
733
|
+
const kItemTypeError = "error";
|
734
|
+
|
735
|
+
function renderItem(
|
736
|
+
item,
|
737
|
+
createElement,
|
738
|
+
state,
|
739
|
+
setActiveItemId,
|
740
|
+
setContext,
|
741
|
+
refresh,
|
742
|
+
quartoSearchOptions
|
743
|
+
) {
|
744
|
+
switch (item.type) {
|
745
|
+
case kItemTypeDoc:
|
746
|
+
return createDocumentCard(
|
747
|
+
createElement,
|
748
|
+
"file-richtext",
|
749
|
+
item.title,
|
750
|
+
item.section,
|
751
|
+
item.text,
|
752
|
+
item.href,
|
753
|
+
item.crumbs,
|
754
|
+
quartoSearchOptions
|
755
|
+
);
|
756
|
+
case kItemTypeMore:
|
757
|
+
return createMoreCard(
|
758
|
+
createElement,
|
759
|
+
item,
|
760
|
+
state,
|
761
|
+
setActiveItemId,
|
762
|
+
setContext,
|
763
|
+
refresh
|
764
|
+
);
|
765
|
+
case kItemTypeItem:
|
766
|
+
return createSectionCard(
|
767
|
+
createElement,
|
768
|
+
item.section,
|
769
|
+
item.text,
|
770
|
+
item.href
|
771
|
+
);
|
772
|
+
case kItemTypeError:
|
773
|
+
return createErrorCard(createElement, item.title, item.text);
|
774
|
+
default:
|
775
|
+
return undefined;
|
776
|
+
}
|
777
|
+
}
|
778
|
+
|
779
|
+
function createDocumentCard(
|
780
|
+
createElement,
|
781
|
+
icon,
|
782
|
+
title,
|
783
|
+
section,
|
784
|
+
text,
|
785
|
+
href,
|
786
|
+
crumbs,
|
787
|
+
quartoSearchOptions
|
788
|
+
) {
|
789
|
+
const iconEl = createElement("i", {
|
790
|
+
class: `bi bi-${icon} search-result-icon`,
|
791
|
+
});
|
792
|
+
const titleEl = createElement("p", { class: "search-result-title" }, title);
|
793
|
+
const titleContents = [iconEl, titleEl];
|
794
|
+
const showParent = quartoSearchOptions["show-item-context"];
|
795
|
+
if (crumbs && showParent) {
|
796
|
+
let crumbsOut = undefined;
|
797
|
+
const crumbClz = ["search-result-crumbs"];
|
798
|
+
if (showParent === "root") {
|
799
|
+
crumbsOut = crumbs.length > 1 ? crumbs[0] : undefined;
|
800
|
+
} else if (showParent === "parent") {
|
801
|
+
crumbsOut = crumbs.length > 1 ? crumbs[crumbs.length - 2] : undefined;
|
802
|
+
} else {
|
803
|
+
crumbsOut = crumbs.length > 1 ? crumbs.join(" > ") : undefined;
|
804
|
+
crumbClz.push("search-result-crumbs-wrap");
|
805
|
+
}
|
806
|
+
|
807
|
+
const crumbEl = createElement(
|
808
|
+
"p",
|
809
|
+
{ class: crumbClz.join(" ") },
|
810
|
+
crumbsOut
|
811
|
+
);
|
812
|
+
titleContents.push(crumbEl);
|
813
|
+
}
|
814
|
+
|
815
|
+
const titleContainerEl = createElement(
|
816
|
+
"div",
|
817
|
+
{ class: "search-result-title-container" },
|
818
|
+
titleContents
|
819
|
+
);
|
820
|
+
|
821
|
+
const textEls = [];
|
822
|
+
if (section) {
|
823
|
+
const sectionEl = createElement(
|
824
|
+
"p",
|
825
|
+
{ class: "search-result-section" },
|
826
|
+
section
|
827
|
+
);
|
828
|
+
textEls.push(sectionEl);
|
829
|
+
}
|
830
|
+
const descEl = createElement("p", {
|
831
|
+
class: "search-result-text",
|
832
|
+
dangerouslySetInnerHTML: {
|
833
|
+
__html: text,
|
834
|
+
},
|
835
|
+
});
|
836
|
+
textEls.push(descEl);
|
837
|
+
|
838
|
+
const textContainerEl = createElement(
|
839
|
+
"div",
|
840
|
+
{ class: "search-result-text-container" },
|
841
|
+
textEls
|
842
|
+
);
|
843
|
+
|
844
|
+
const containerEl = createElement(
|
845
|
+
"div",
|
846
|
+
{
|
847
|
+
class: "search-result-container",
|
848
|
+
},
|
849
|
+
[titleContainerEl, textContainerEl]
|
850
|
+
);
|
851
|
+
|
852
|
+
const linkEl = createElement(
|
853
|
+
"a",
|
854
|
+
{
|
855
|
+
href: offsetURL(href),
|
856
|
+
class: "search-result-link",
|
857
|
+
},
|
858
|
+
containerEl
|
859
|
+
);
|
860
|
+
|
861
|
+
const classes = ["search-result-doc", "search-item"];
|
862
|
+
if (!section) {
|
863
|
+
classes.push("document-selectable");
|
864
|
+
}
|
865
|
+
|
866
|
+
return createElement(
|
867
|
+
"div",
|
868
|
+
{
|
869
|
+
class: classes.join(" "),
|
870
|
+
},
|
871
|
+
linkEl
|
872
|
+
);
|
873
|
+
}
|
874
|
+
|
875
|
+
function createMoreCard(
|
876
|
+
createElement,
|
877
|
+
item,
|
878
|
+
state,
|
879
|
+
setActiveItemId,
|
880
|
+
setContext,
|
881
|
+
refresh
|
882
|
+
) {
|
883
|
+
const moreCardEl = createElement(
|
884
|
+
"div",
|
885
|
+
{
|
886
|
+
class: "search-result-more search-item",
|
887
|
+
onClick: (e) => {
|
888
|
+
// Handle expanding the sections by adding the expanded
|
889
|
+
// section to the list of expanded sections
|
890
|
+
toggleExpanded(item, state, setContext, setActiveItemId, refresh);
|
891
|
+
e.stopPropagation();
|
892
|
+
},
|
893
|
+
},
|
894
|
+
item.title
|
895
|
+
);
|
896
|
+
|
897
|
+
return moreCardEl;
|
898
|
+
}
|
899
|
+
|
900
|
+
function toggleExpanded(item, state, setContext, setActiveItemId, refresh) {
|
901
|
+
const expanded = state.context.expanded || [];
|
902
|
+
if (expanded.includes(item.target)) {
|
903
|
+
setContext({
|
904
|
+
expanded: expanded.filter((target) => target !== item.target),
|
905
|
+
});
|
906
|
+
} else {
|
907
|
+
setContext({ expanded: [...expanded, item.target] });
|
908
|
+
}
|
909
|
+
|
910
|
+
refresh();
|
911
|
+
setActiveItemId(item.__autocomplete_id);
|
912
|
+
}
|
913
|
+
|
914
|
+
function createSectionCard(createElement, section, text, href) {
|
915
|
+
const sectionEl = createSection(createElement, section, text, href);
|
916
|
+
return createElement(
|
917
|
+
"div",
|
918
|
+
{
|
919
|
+
class: "search-result-doc-section search-item",
|
920
|
+
},
|
921
|
+
sectionEl
|
922
|
+
);
|
923
|
+
}
|
924
|
+
|
925
|
+
function createSection(createElement, title, text, href) {
|
926
|
+
const descEl = createElement("p", {
|
927
|
+
class: "search-result-text",
|
928
|
+
dangerouslySetInnerHTML: {
|
929
|
+
__html: text,
|
930
|
+
},
|
931
|
+
});
|
932
|
+
|
933
|
+
const titleEl = createElement("p", { class: "search-result-section" }, title);
|
934
|
+
const linkEl = createElement(
|
935
|
+
"a",
|
936
|
+
{
|
937
|
+
href: offsetURL(href),
|
938
|
+
class: "search-result-link",
|
939
|
+
},
|
940
|
+
[titleEl, descEl]
|
941
|
+
);
|
942
|
+
return linkEl;
|
943
|
+
}
|
944
|
+
|
945
|
+
function createErrorCard(createElement, title, text) {
|
946
|
+
const descEl = createElement("p", {
|
947
|
+
class: "search-error-text",
|
948
|
+
dangerouslySetInnerHTML: {
|
949
|
+
__html: text,
|
950
|
+
},
|
951
|
+
});
|
952
|
+
|
953
|
+
const titleEl = createElement("p", {
|
954
|
+
class: "search-error-title",
|
955
|
+
dangerouslySetInnerHTML: {
|
956
|
+
__html: `<i class="bi bi-exclamation-circle search-error-icon"></i> ${title}`,
|
957
|
+
},
|
958
|
+
});
|
959
|
+
const errorEl = createElement("div", { class: "search-error" }, [
|
960
|
+
titleEl,
|
961
|
+
descEl,
|
962
|
+
]);
|
963
|
+
return errorEl;
|
964
|
+
}
|
965
|
+
|
966
|
+
function positionPanel(pos) {
|
967
|
+
const panelEl = window.document.querySelector(
|
968
|
+
"#quarto-search-results .aa-Panel"
|
969
|
+
);
|
970
|
+
const inputEl = window.document.querySelector(
|
971
|
+
"#quarto-search .aa-Autocomplete"
|
972
|
+
);
|
973
|
+
|
974
|
+
if (panelEl && inputEl) {
|
975
|
+
panelEl.style.top = `${Math.round(panelEl.offsetTop)}px`;
|
976
|
+
if (pos === "start") {
|
977
|
+
panelEl.style.left = `${Math.round(inputEl.left)}px`;
|
978
|
+
} else {
|
979
|
+
panelEl.style.right = `${Math.round(inputEl.offsetRight)}px`;
|
980
|
+
}
|
981
|
+
}
|
982
|
+
}
|
983
|
+
|
984
|
+
/* Highlighting */
|
985
|
+
// highlighting functions
|
986
|
+
function highlightMatch(query, text) {
|
987
|
+
if (text) {
|
988
|
+
const start = text.toLowerCase().indexOf(query.toLowerCase());
|
989
|
+
if (start !== -1) {
|
990
|
+
const startMark = "<mark class='search-match'>";
|
991
|
+
const endMark = "</mark>";
|
992
|
+
|
993
|
+
const end = start + query.length;
|
994
|
+
text =
|
995
|
+
text.slice(0, start) +
|
996
|
+
startMark +
|
997
|
+
text.slice(start, end) +
|
998
|
+
endMark +
|
999
|
+
text.slice(end);
|
1000
|
+
const startInfo = clipStart(text, start);
|
1001
|
+
const endInfo = clipEnd(
|
1002
|
+
text,
|
1003
|
+
startInfo.position + startMark.length + endMark.length
|
1004
|
+
);
|
1005
|
+
text =
|
1006
|
+
startInfo.prefix +
|
1007
|
+
text.slice(startInfo.position, endInfo.position) +
|
1008
|
+
endInfo.suffix;
|
1009
|
+
|
1010
|
+
return text;
|
1011
|
+
} else {
|
1012
|
+
return text;
|
1013
|
+
}
|
1014
|
+
} else {
|
1015
|
+
return text;
|
1016
|
+
}
|
1017
|
+
}
|
1018
|
+
|
1019
|
+
function clipStart(text, pos) {
|
1020
|
+
const clipStart = pos - 50;
|
1021
|
+
if (clipStart < 0) {
|
1022
|
+
// This will just return the start of the string
|
1023
|
+
return {
|
1024
|
+
position: 0,
|
1025
|
+
prefix: "",
|
1026
|
+
};
|
1027
|
+
} else {
|
1028
|
+
// We're clipping before the start of the string, walk backwards to the first space.
|
1029
|
+
const spacePos = findSpace(text, pos, -1);
|
1030
|
+
return {
|
1031
|
+
position: spacePos.position,
|
1032
|
+
prefix: "",
|
1033
|
+
};
|
1034
|
+
}
|
1035
|
+
}
|
1036
|
+
|
1037
|
+
function clipEnd(text, pos) {
|
1038
|
+
const clipEnd = pos + 200;
|
1039
|
+
if (clipEnd > text.length) {
|
1040
|
+
return {
|
1041
|
+
position: text.length,
|
1042
|
+
suffix: "",
|
1043
|
+
};
|
1044
|
+
} else {
|
1045
|
+
const spacePos = findSpace(text, clipEnd, 1);
|
1046
|
+
return {
|
1047
|
+
position: spacePos.position,
|
1048
|
+
suffix: spacePos.clipped ? "…" : "",
|
1049
|
+
};
|
1050
|
+
}
|
1051
|
+
}
|
1052
|
+
|
1053
|
+
function findSpace(text, start, step) {
|
1054
|
+
let stepPos = start;
|
1055
|
+
while (stepPos > -1 && stepPos < text.length) {
|
1056
|
+
const char = text[stepPos];
|
1057
|
+
if (char === " " || char === "," || char === ":") {
|
1058
|
+
return {
|
1059
|
+
position: step === 1 ? stepPos : stepPos - step,
|
1060
|
+
clipped: stepPos > 1 && stepPos < text.length,
|
1061
|
+
};
|
1062
|
+
}
|
1063
|
+
stepPos = stepPos + step;
|
1064
|
+
}
|
1065
|
+
|
1066
|
+
return {
|
1067
|
+
position: stepPos - step,
|
1068
|
+
clipped: false,
|
1069
|
+
};
|
1070
|
+
}
|
1071
|
+
|
1072
|
+
// removes highlighting as implemented by the mark tag
|
1073
|
+
function clearHighlight(searchterm, el) {
|
1074
|
+
const childNodes = el.childNodes;
|
1075
|
+
for (let i = childNodes.length - 1; i >= 0; i--) {
|
1076
|
+
const node = childNodes[i];
|
1077
|
+
if (node.nodeType === Node.ELEMENT_NODE) {
|
1078
|
+
if (
|
1079
|
+
node.tagName === "MARK" &&
|
1080
|
+
node.innerText.toLowerCase() === searchterm.toLowerCase()
|
1081
|
+
) {
|
1082
|
+
el.replaceChild(document.createTextNode(node.innerText), node);
|
1083
|
+
} else {
|
1084
|
+
clearHighlight(searchterm, node);
|
1085
|
+
}
|
1086
|
+
}
|
1087
|
+
}
|
1088
|
+
}
|
1089
|
+
|
1090
|
+
function escapeRegExp(string) {
|
1091
|
+
return string.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); // $& means the whole matched string
|
1092
|
+
}
|
1093
|
+
|
1094
|
+
// highlight matches
|
1095
|
+
function highlight(term, el) {
|
1096
|
+
const termRegex = new RegExp(term, "ig");
|
1097
|
+
const childNodes = el.childNodes;
|
1098
|
+
|
1099
|
+
// walk back to front avoid mutating elements in front of us
|
1100
|
+
for (let i = childNodes.length - 1; i >= 0; i--) {
|
1101
|
+
const node = childNodes[i];
|
1102
|
+
|
1103
|
+
if (node.nodeType === Node.TEXT_NODE) {
|
1104
|
+
// Search text nodes for text to highlight
|
1105
|
+
const text = node.nodeValue;
|
1106
|
+
|
1107
|
+
let startIndex = 0;
|
1108
|
+
let matchIndex = text.search(termRegex);
|
1109
|
+
if (matchIndex > -1) {
|
1110
|
+
const markFragment = document.createDocumentFragment();
|
1111
|
+
while (matchIndex > -1) {
|
1112
|
+
const prefix = text.slice(startIndex, matchIndex);
|
1113
|
+
markFragment.appendChild(document.createTextNode(prefix));
|
1114
|
+
|
1115
|
+
const mark = document.createElement("mark");
|
1116
|
+
mark.appendChild(
|
1117
|
+
document.createTextNode(
|
1118
|
+
text.slice(matchIndex, matchIndex + term.length)
|
1119
|
+
)
|
1120
|
+
);
|
1121
|
+
markFragment.appendChild(mark);
|
1122
|
+
|
1123
|
+
startIndex = matchIndex + term.length;
|
1124
|
+
matchIndex = text.slice(startIndex).search(new RegExp(term, "ig"));
|
1125
|
+
if (matchIndex > -1) {
|
1126
|
+
matchIndex = startIndex + matchIndex;
|
1127
|
+
}
|
1128
|
+
}
|
1129
|
+
if (startIndex < text.length) {
|
1130
|
+
markFragment.appendChild(
|
1131
|
+
document.createTextNode(text.slice(startIndex, text.length))
|
1132
|
+
);
|
1133
|
+
}
|
1134
|
+
|
1135
|
+
el.replaceChild(markFragment, node);
|
1136
|
+
}
|
1137
|
+
} else if (node.nodeType === Node.ELEMENT_NODE) {
|
1138
|
+
// recurse through elements
|
1139
|
+
highlight(term, node);
|
1140
|
+
}
|
1141
|
+
}
|
1142
|
+
}
|
1143
|
+
|
1144
|
+
/* Link Handling */
|
1145
|
+
// get the offset from this page for a given site root relative url
|
1146
|
+
function offsetURL(url) {
|
1147
|
+
var offset = getMeta("quarto:offset");
|
1148
|
+
return offset ? offset + url : url;
|
1149
|
+
}
|
1150
|
+
|
1151
|
+
// read a meta tag value
|
1152
|
+
function getMeta(metaName) {
|
1153
|
+
var metas = window.document.getElementsByTagName("meta");
|
1154
|
+
for (let i = 0; i < metas.length; i++) {
|
1155
|
+
if (metas[i].getAttribute("name") === metaName) {
|
1156
|
+
return metas[i].getAttribute("content");
|
1157
|
+
}
|
1158
|
+
}
|
1159
|
+
return "";
|
1160
|
+
}
|
1161
|
+
|
1162
|
+
function algoliaSearch(query, limit, algoliaOptions) {
|
1163
|
+
const { getAlgoliaResults } = window["@algolia/autocomplete-preset-algolia"];
|
1164
|
+
|
1165
|
+
const applicationId = algoliaOptions["application-id"];
|
1166
|
+
const searchOnlyApiKey = algoliaOptions["search-only-api-key"];
|
1167
|
+
const indexName = algoliaOptions["index-name"];
|
1168
|
+
const indexFields = algoliaOptions["index-fields"];
|
1169
|
+
const searchClient = window.algoliasearch(applicationId, searchOnlyApiKey);
|
1170
|
+
const searchParams = algoliaOptions["params"];
|
1171
|
+
const searchAnalytics = !!algoliaOptions["analytics-events"];
|
1172
|
+
|
1173
|
+
return getAlgoliaResults({
|
1174
|
+
searchClient,
|
1175
|
+
queries: [
|
1176
|
+
{
|
1177
|
+
indexName: indexName,
|
1178
|
+
query,
|
1179
|
+
params: {
|
1180
|
+
hitsPerPage: limit,
|
1181
|
+
clickAnalytics: searchAnalytics,
|
1182
|
+
...searchParams,
|
1183
|
+
},
|
1184
|
+
},
|
1185
|
+
],
|
1186
|
+
transformResponse: (response) => {
|
1187
|
+
if (!indexFields) {
|
1188
|
+
return response.hits.map((hit) => {
|
1189
|
+
return hit.map((item) => {
|
1190
|
+
return {
|
1191
|
+
...item,
|
1192
|
+
text: highlightMatch(query, item.text),
|
1193
|
+
};
|
1194
|
+
});
|
1195
|
+
});
|
1196
|
+
} else {
|
1197
|
+
const remappedHits = response.hits.map((hit) => {
|
1198
|
+
return hit.map((item) => {
|
1199
|
+
const newItem = { ...item };
|
1200
|
+
["href", "section", "title", "text", "crumbs"].forEach(
|
1201
|
+
(keyName) => {
|
1202
|
+
const mappedName = indexFields[keyName];
|
1203
|
+
if (
|
1204
|
+
mappedName &&
|
1205
|
+
item[mappedName] !== undefined &&
|
1206
|
+
mappedName !== keyName
|
1207
|
+
) {
|
1208
|
+
newItem[keyName] = item[mappedName];
|
1209
|
+
delete newItem[mappedName];
|
1210
|
+
}
|
1211
|
+
}
|
1212
|
+
);
|
1213
|
+
newItem.text = highlightMatch(query, newItem.text);
|
1214
|
+
return newItem;
|
1215
|
+
});
|
1216
|
+
});
|
1217
|
+
return remappedHits;
|
1218
|
+
}
|
1219
|
+
},
|
1220
|
+
});
|
1221
|
+
}
|
1222
|
+
|
1223
|
+
function fuseSearch(query, fuse, fuseOptions) {
|
1224
|
+
return fuse.search(query, fuseOptions).map((result) => {
|
1225
|
+
const addParam = (url, name, value) => {
|
1226
|
+
const anchorParts = url.split("#");
|
1227
|
+
const baseUrl = anchorParts[0];
|
1228
|
+
const sep = baseUrl.search("\\?") > 0 ? "&" : "?";
|
1229
|
+
anchorParts[0] = baseUrl + sep + name + "=" + value;
|
1230
|
+
return anchorParts.join("#");
|
1231
|
+
};
|
1232
|
+
|
1233
|
+
return {
|
1234
|
+
title: result.item.title,
|
1235
|
+
section: result.item.section,
|
1236
|
+
href: addParam(result.item.href, kQueryArg, query),
|
1237
|
+
text: highlightMatch(query, result.item.text),
|
1238
|
+
crumbs: result.item.crumbs,
|
1239
|
+
};
|
1240
|
+
});
|
1241
|
+
}
|