Skip to content

Commit 2a37d1e

Browse files
committed
Merge branch 'main' of github.com:quarto-dev/quarto-cli into main
2 parents 4a34086 + 16b652d commit 2a37d1e

File tree

2 files changed

+108
-1
lines changed

2 files changed

+108
-1
lines changed

src/resources/formats/html/bootstrap/_bootstrap-rules.scss

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,10 @@ h4 {
4242
@include body-secondary;
4343
}
4444

45+
mark {
46+
padding: 0em;
47+
}
48+
4549
caption,
4650
.figure-caption {
4751
@include body-secondary;

src/resources/projects/website/search/quarto-search.js

Lines changed: 104 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -99,10 +99,18 @@ window.document.addEventListener("DOMContentLoaded", function (_event) {
9999

100100
callback(fuse.search(query, searchOptions)
101101
.map(result => {
102+
const addParam = (url, name, value) => {
103+
const anchorParts = url.split('#');
104+
const baseUrl = anchorParts[0];
105+
const sep = baseUrl.search("\\?") > 0 ? "&" : "?";
106+
anchorParts[0] = baseUrl + sep + name + "=" + value;
107+
return anchorParts.join("#");
108+
}
109+
102110
return {
103111
title: result.item.title,
104112
section: result.item.section,
105-
href: result.item.href,
113+
href: addParam(result.item.href, "q", query),
106114
text: highlightMatch(query, result.item.text)
107115
}
108116
})
@@ -157,4 +165,99 @@ window.document.addEventListener("DOMContentLoaded", function (_event) {
157165
console.log(error);
158166
});
159167

168+
// If there is a query param indicating that a search was performed, highlight any matching terms
169+
const params = new URLSearchParams(window.location.search);
170+
if (params.has("q")) {
171+
172+
// show the search term in the search box
173+
const searchterm = params.get("q");
174+
searchEl.value = searchterm;
175+
searchEl.focus();
176+
177+
// Search the main section of the document for matching terms to highlight
178+
const mainEl = window.document.querySelector("main");
179+
if (mainEl) {
180+
// highlight matches
181+
highlight(searchterm, mainEl);
182+
183+
// if the input changes, clear the highlighting
184+
let highlighting = true;
185+
searchEl.addEventListener('input', () => {
186+
if (highlighting && searchEl.value !== searchterm) {
187+
clearHighlight(searchterm, mainEl)
188+
highlighting = false;
189+
}
190+
});
191+
192+
// clear the search input if the user presses 'esc'
193+
searchEl.onkeydown = (event) => {
194+
if (event.key === "Escape") {
195+
searchEl.value = "";
196+
}
197+
}
198+
}
199+
}
160200
});
201+
202+
// removes highlighting as implemented by the mark tag
203+
function clearHighlight(searchterm, el) {
204+
const childNodes = el.childNodes;
205+
for (let i = childNodes.length - 1; i >= 0; i--) {
206+
const node = childNodes[i];
207+
if (node.nodeType === Node.ELEMENT_NODE) {
208+
if (node.tagName === 'MARK'&& node.innerText.toLowerCase() === searchterm.toLowerCase()) {
209+
el.replaceChild(document.createTextNode(node.innerText), node);
210+
} else {
211+
clearHighlight(searchterm, node);
212+
}
213+
}
214+
}
215+
}
216+
217+
// highlight matches
218+
function highlight(term, el) {
219+
const termRegex = new RegExp(term, 'ig')
220+
const childNodes = el.childNodes;
221+
222+
// walk back to front avoid mutating elements in front of us
223+
for (let i = childNodes.length - 1; i >= 0; i--) {
224+
const node = childNodes[i];
225+
226+
227+
if (node.nodeType === Node.TEXT_NODE) {
228+
// Search text nodes for text to highlight
229+
const text = node.nodeValue;
230+
231+
let startIndex = 0;
232+
let matchIndex = text.search(termRegex);
233+
if (matchIndex > -1) {
234+
const markFragment = document.createDocumentFragment();
235+
while (matchIndex > -1) {
236+
const prefix = text.slice(startIndex, matchIndex);
237+
markFragment.appendChild(document.createTextNode(prefix));
238+
239+
const mark = document.createElement("mark");
240+
mark.appendChild(document.createTextNode(text.slice(matchIndex, matchIndex + term.length)));
241+
markFragment.appendChild(mark);
242+
243+
244+
startIndex = matchIndex + term.length;
245+
matchIndex = text.slice(startIndex).search(new RegExp(term, 'ig'));
246+
if (matchIndex > -1) {
247+
matchIndex = startIndex + matchIndex;
248+
}
249+
}
250+
if (startIndex < text.length) {
251+
markFragment.appendChild(document.createTextNode(text.slice(startIndex, text.length)));
252+
}
253+
254+
el.replaceChild(markFragment, node);
255+
}
256+
} else if (node.nodeType === Node.ELEMENT_NODE) {
257+
// recurse through elements
258+
highlight(term, node);
259+
}
260+
}
261+
}
262+
263+

0 commit comments

Comments
 (0)