fix(search): fix keyword highlighting and scrolling in excerpts

pull/2239/head
Divyansh Singh 1 year ago
parent b2077c7025
commit ca8db8adca

@ -36,7 +36,8 @@ const emit = defineEmits<{
(e: 'close'): void (e: 'close'): void
}>() }>()
const el = ref<HTMLDivElement>() const el = shallowRef<HTMLDivElement>()
const resultsEl = shallowRef<HTMLDivElement>()
/* Search */ /* Search */
@ -98,8 +99,6 @@ watchEffect(() => {
const results: Ref<(SearchResult & Result)[]> = shallowRef([]) const results: Ref<(SearchResult & Result)[]> = shallowRef([])
const contents = shallowRef(new Map<string, Map<string, string>>())
const headingRegex = /<h(\d*).*?>.*?<a.*? href="#(.*?)".*?>.*?<\/a><\/h\1>/gi const headingRegex = /<h(\d*).*?>.*?<a.*? href="#(.*?)".*?>.*?<\/a><\/h\1>/gi
const enableNoResults = ref(false) const enableNoResults = ref(false)
@ -108,6 +107,11 @@ watch(filterText, () => {
enableNoResults.value = false enableNoResults.value = false
}) })
const mark = computed(() => {
if (!resultsEl.value) return
return new Mark(resultsEl.value)
})
debouncedWatch( debouncedWatch(
() => [searchIndex.value, filterText.value, showDetailedList.value] as const, () => [searchIndex.value, filterText.value, showDetailedList.value] as const,
async ([index, filterTextValue, showDetailedListValue], old, onCleanup) => { async ([index, filterTextValue, showDetailedListValue], old, onCleanup) => {
@ -155,43 +159,38 @@ debouncedWatch(
} }
if (canceled) return if (canceled) return
} }
results.value = results.value.map((r) => {
let title = r.title
let titles = r.titles
let text = ''
// Highlight in text const terms = new Set<string>()
results.value = results.value.map((r) => {
const [id, anchor] = r.id.split('#') const [id, anchor] = r.id.split('#')
const map = c.get(id) const map = c.get(id)
if (map) { const text = map?.get(anchor) ?? ''
text = map.get(anchor) ?? ''
}
for (const term in r.match) { for (const term in r.match) {
const match = r.match[term] terms.add(term)
const reg = new RegExp(term, 'gi')
if (match.includes('title')) {
title = title.replace(reg, `<mark>$&</mark>`)
}
if (match.includes('titles')) {
titles = titles
.map((t) => t?.replace(reg, `<mark>$&</mark>`))
.filter(Boolean)
}
} }
return { ...r, text }
return { ...r, title, titles, text }
}) })
contents.value = c
await nextTick() await nextTick()
if (canceled) return
await new Promise((r) => {
mark.value?.unmark({
done: () => {
mark.value?.markRegExp(formMarkRegex(terms), { done: r })
}
})
})
const excerpts = el.value?.querySelectorAll('.result .excerpt') ?? [] const excerpts = el.value?.querySelectorAll('.result .excerpt') ?? []
let i = 0
for (const excerpt of excerpts) { for (const excerpt of excerpts) {
new Mark(excerpt as HTMLElement).mark(Object.keys(results.value[i].match)) excerpt
excerpt.querySelector('mark')?.scrollIntoView({ block: 'center' }) .querySelector('mark[data-markjs="true"]')
i += 1 ?.scrollIntoView({ block: 'center' })
} }
// FIXME: without this whole page scrolls to the bottom
el.value?.querySelector('.result')?.scrollIntoView({ block: 'start' })
}, },
{ debounce: 200, immediate: true } { debounce: 200, immediate: true }
) )
@ -312,6 +311,20 @@ useEventListener('popstate', (event) => {
event.preventDefault() event.preventDefault()
emit('close') emit('close')
}) })
function formMarkRegex(terms: Set<string>) {
return new RegExp(
[...terms]
.sort((a, b) => b.length - a.length)
.map((term) => {
return `(${term
.replace(/[|\\{}()[\]^$+*?.]/g, '\\$&')
.replace(/-/g, '\\x2d')})`
})
.join('|'),
'gi'
)
}
</script> </script>
<template> <template>
@ -417,7 +430,11 @@ useEventListener('popstate', (event) => {
</div> </div>
</div> </div>
<div class="results" @mousemove="disableMouseOver = false"> <div
ref="resultsEl"
class="results"
@mousemove="disableMouseOver = false"
>
<a <a
v-for="(p, index) in results" v-for="(p, index) in results"
:key="p.id" :key="p.id"

Loading…
Cancel
Save