feat: improve search preview styling and tokenization

This commit is contained in:
Jacky Zhao 2024-02-01 21:15:28 -08:00
parent c00089bd57
commit c0c0b24138
4 changed files with 43 additions and 34 deletions

View file

@ -24,14 +24,20 @@ const contextWindowWords = 30
const numSearchResults = 8 const numSearchResults = 8
const numTagResults = 5 const numTagResults = 5
const tokenizeTerm = (term: string) => const tokenizeTerm = (term: string) => {
term const tokens = term.split(/\s+/).filter((t) => t.trim() !== "")
.split(/\s+/)
.filter((t) => t !== "") const tokenLen = tokens.length
.sort((a, b) => b.length - a.length) if (tokenLen > 1) {
for (let i = 1; i < tokenLen; i++) {
tokens.push(tokens.slice(0, i + 1).join(" "))
}
}
return tokens.sort((a, b) => b.length - a.length) // always highlight longest terms first
}
function highlight(searchTerm: string, text: string, trim?: boolean) { function highlight(searchTerm: string, text: string, trim?: boolean) {
// try to highlight longest tokens first
const tokenizedTerms = tokenizeTerm(searchTerm) const tokenizedTerms = tokenizeTerm(searchTerm)
let tokenizedText = text.split(/\s+/).filter((t) => t !== "") let tokenizedText = text.split(/\s+/).filter((t) => t !== "")
@ -69,7 +75,6 @@ function highlight(searchTerm: string, text: string, trim?: boolean) {
} }
return tok return tok
}) })
.slice(startIndex, endIndex + 1)
.join(" ") .join(" ")
return `${startIndex === 0 ? "" : "..."}${slice}${ return `${startIndex === 0 ? "" : "..."}${slice}${
@ -89,29 +94,32 @@ function highlightHTML(searchTerm: string, el: HTMLElement) {
return span return span
} }
const highlightTextNodes = (node: Node) => { const highlightTextNodes = (node: Node, term: string) => {
if (node.nodeType === Node.TEXT_NODE) { if (node.nodeType === Node.TEXT_NODE) {
let nodeText = node.nodeValue || "" const nodeText = node.nodeValue ?? ""
tokenizedTerms.forEach((term) => {
const regex = new RegExp(term.toLowerCase(), "gi") const regex = new RegExp(term.toLowerCase(), "gi")
const matches = nodeText.match(regex) const matches = nodeText.match(regex)
if (!matches || matches.length === 0) return
const spanContainer = document.createElement("span") const spanContainer = document.createElement("span")
let lastIndex = 0 let lastIndex = 0
matches?.forEach((match) => { for (const match of matches) {
const matchIndex = nodeText.indexOf(match, lastIndex) const matchIndex = nodeText.indexOf(match, lastIndex)
spanContainer.appendChild(document.createTextNode(nodeText.slice(lastIndex, matchIndex))) spanContainer.appendChild(document.createTextNode(nodeText.slice(lastIndex, matchIndex)))
spanContainer.appendChild(createHighlightSpan(match)) spanContainer.appendChild(createHighlightSpan(match))
lastIndex = matchIndex + match.length lastIndex = matchIndex + match.length
}) }
spanContainer.appendChild(document.createTextNode(nodeText.slice(lastIndex))) spanContainer.appendChild(document.createTextNode(nodeText.slice(lastIndex)))
node.parentNode?.replaceChild(spanContainer, node) node.parentNode?.replaceChild(spanContainer, node)
})
} else if (node.nodeType === Node.ELEMENT_NODE) { } else if (node.nodeType === Node.ELEMENT_NODE) {
Array.from(node.childNodes).forEach(highlightTextNodes) if ((node as HTMLElement).classList.contains("highlight")) return
Array.from(node.childNodes).forEach((child) => highlightTextNodes(child, term))
} }
} }
highlightTextNodes(html.body) for (const term of tokenizedTerms) {
highlightTextNodes(html.body, term)
}
return html.body return html.body
} }
@ -137,13 +145,13 @@ document.addEventListener("nav", async (e: CustomEventMap["nav"]) => {
let previewInner: HTMLDivElement | undefined = undefined let previewInner: HTMLDivElement | undefined = undefined
const results = document.createElement("div") const results = document.createElement("div")
results.id = "results-container" results.id = "results-container"
results.style.flexBasis = enablePreview ? "30%" : "100%" results.style.flexBasis = enablePreview ? "min(30%, 450px)" : "100%"
appendLayout(results) appendLayout(results)
if (enablePreview) { if (enablePreview) {
preview = document.createElement("div") preview = document.createElement("div")
preview.id = "preview-container" preview.id = "preview-container"
preview.style.flexBasis = "70%" preview.style.flexBasis = "100%"
appendLayout(preview) appendLayout(preview)
} }

View file

@ -54,7 +54,7 @@
} }
& > #search-space { & > #search-space {
width: 75%; width: 65%;
margin-top: 12vh; margin-top: 12vh;
margin-left: auto; margin-left: auto;
margin-right: auto; margin-right: auto;
@ -85,7 +85,6 @@
& > #search-layout { & > #search-layout {
display: flex; display: flex;
flex-direction: row; flex-direction: row;
justify-content: space-between;
opacity: 0; opacity: 0;
border: 1px solid var(--lightgray); border: 1px solid var(--lightgray);
@ -106,7 +105,7 @@
} }
} }
@media all and (max-width: $mobileBreakpoint) { @media all and (max-width: $tabletBreakpoint) {
display: block; display: block;
& > *:not(#results-container) { & > *:not(#results-container) {
display: none !important; display: none !important;
@ -119,8 +118,8 @@
} }
& .highlight { & .highlight {
color: var(--secondary); background: color-mix(in srgb, var(--tertiary) 60%, transparent);
font-weight: 700; border-radius: 5px;
} }
& > #preview-container { & > #preview-container {
@ -129,8 +128,10 @@
overflow: hidden; overflow: hidden;
& .preview-inner { & .preview-inner {
margin: 0 auto;
padding: 1em; padding: 1em;
height: 100%; height: 100%;
width: 100%;
box-sizing: border-box; box-sizing: border-box;
overflow-y: auto; overflow-y: auto;
font-family: inherit; font-family: inherit;

View file

@ -26,7 +26,7 @@ section {
} }
::selection { ::selection {
background: color-mix(in srgb, var(--tertiary) 75%, transparent); background: color-mix(in srgb, var(--tertiary) 60%, transparent);
color: var(--darkgray); color: var(--darkgray);
} }

View file

@ -1,6 +1,6 @@
$pageWidth: 750px; $pageWidth: 750px;
$mobileBreakpoint: 600px; $mobileBreakpoint: 600px;
$tabletBreakpoint: 1200px; $tabletBreakpoint: 1000px;
$sidePanelWidth: 380px; $sidePanelWidth: 380px;
$topSpacing: 6rem; $topSpacing: 6rem;
$fullPageWidth: $pageWidth + 2 * $sidePanelWidth; $fullPageWidth: $pageWidth + 2 * $sidePanelWidth;