mirror of
https://github.com/alrayyes/wiki.git
synced 2024-11-26 13:26:22 +00:00
feat: improve search preview styling and tokenization
This commit is contained in:
parent
c00089bd57
commit
c0c0b24138
4 changed files with 43 additions and 34 deletions
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
|
|
Loading…
Reference in a new issue