Highlight the searched terms in the thread page
This commit is contained in:
parent
d61ec05963
commit
c9227ee8fb
7 changed files with 92 additions and 4 deletions
|
|
@ -15,7 +15,7 @@
|
|||
|
||||
function select(r: SearchResult) {
|
||||
open = false;
|
||||
goto(`/thread/${r.threadId}`);
|
||||
goto(search.threadUrl(r.threadId));
|
||||
}
|
||||
|
||||
function onKeydown(e: KeyboardEvent) {
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@
|
|||
|
||||
function select(r: SearchResult) {
|
||||
closeSearch();
|
||||
goto(`/thread/${r.threadId}`);
|
||||
goto(search.threadUrl(r.threadId));
|
||||
}
|
||||
|
||||
// Global "/" opens the search from any page, unless typing somewhere else
|
||||
|
|
|
|||
|
|
@ -41,7 +41,7 @@
|
|||
{#each search.results as r, i (r.threadId)}
|
||||
<a
|
||||
id="{idBase}-{i}"
|
||||
href="/thread/{r.threadId}"
|
||||
href={search.threadUrl(r.threadId)}
|
||||
role="option"
|
||||
aria-selected={i === search.activeIndex}
|
||||
class="block rounded px-4 py-2 {i === search.activeIndex
|
||||
|
|
|
|||
49
src/lib/pageHighlight.ts
Normal file
49
src/lib/pageHighlight.ts
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
const HIGHLIGHT_NAME = "search-terms";
|
||||
|
||||
function supported(): boolean {
|
||||
return typeof CSS !== "undefined" && "highlights" in CSS;
|
||||
}
|
||||
|
||||
// Highlight every occurrence of the terms inside the given roots using the
|
||||
// CSS Custom Highlight API, which paints ranges without touching the DOM, so
|
||||
// it never conflicts with Svelte re-renders. Returns the first match in
|
||||
// document order (for scrolling), or null. No-op on unsupported browsers.
|
||||
export function applyHighlights(
|
||||
roots: Iterable<Element>,
|
||||
terms: string[],
|
||||
): Range | null {
|
||||
if (!supported()) return null;
|
||||
CSS.highlights.delete(HIGHLIGHT_NAME);
|
||||
const lowers = [...new Set(terms.map((t) => t.toLowerCase()))].filter(
|
||||
(t) => t.length >= 2,
|
||||
);
|
||||
if (lowers.length === 0) return null;
|
||||
|
||||
const ranges: Range[] = [];
|
||||
for (const root of roots) {
|
||||
const walker = document.createTreeWalker(root, NodeFilter.SHOW_TEXT);
|
||||
let node: Node | null;
|
||||
while ((node = walker.nextNode())) {
|
||||
const lower = (node.textContent ?? "").toLowerCase();
|
||||
for (const term of lowers) {
|
||||
let i = 0;
|
||||
while ((i = lower.indexOf(term, i)) !== -1) {
|
||||
const range = new Range();
|
||||
range.setStart(node, i);
|
||||
range.setEnd(node, i + term.length);
|
||||
ranges.push(range);
|
||||
i += term.length;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (ranges.length === 0) return null;
|
||||
|
||||
CSS.highlights.set(HIGHLIGHT_NAME, new Highlight(...ranges));
|
||||
ranges.sort((a, b) => a.compareBoundaryPoints(Range.START_TO_START, b));
|
||||
return ranges[0];
|
||||
}
|
||||
|
||||
export function clearHighlights() {
|
||||
if (supported()) CSS.highlights.delete(HIGHLIGHT_NAME);
|
||||
}
|
||||
|
|
@ -98,6 +98,13 @@ export function createSearchState() {
|
|||
get active() {
|
||||
return resultsQuery.trim().length >= 2;
|
||||
},
|
||||
get resultsQuery() {
|
||||
return resultsQuery;
|
||||
},
|
||||
// Thread link carrying the query, so the page can highlight the terms
|
||||
threadUrl(threadId: string) {
|
||||
return `/thread/${threadId}?q=${encodeURIComponent(resultsQuery.trim())}`;
|
||||
},
|
||||
schedule,
|
||||
highlight,
|
||||
navigate,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue