文档搜索

This commit is contained in:
cwchen 2025-11-10 18:12:34 +08:00
parent 148c189a0a
commit 10a35036f5
1 changed files with 126 additions and 20 deletions

View File

@ -569,26 +569,118 @@ export default {
if (!textDivs || !textDivs.length) { if (!textDivs || !textDivs.length) {
return return
} }
const segments = []
let position = 0
textDivs.forEach((div) => { textDivs.forEach((div) => {
const original = div.dataset.originalText || div.textContent || '' const original = div.dataset.originalText || div.textContent || ''
if (!original) return segments.push({
pattern.lastIndex = 0 div,
if (!pattern.test(original)) { text: original,
pattern.lastIndex = 0 start: position,
div.textContent = original end: position + original.length,
return })
} position += original.length
pattern.lastIndex = 0 })
const highlighted = original.replace(pattern, '<mark class="search-highlight">$1</mark>') if (!segments.length) {
div.innerHTML = highlighted return
}
const pageText = segments.map((seg) => seg.text).join('')
if (!pageText) {
return
}
pattern.lastIndex = 0
const perDivHighlights = new Map()
const matchRecords = []
let match
while ((match = pattern.exec(pageText)) !== null) {
if (!match[0]) continue
const start = match.index
const end = start + match[0].length
const matchIndex = matchRecords.length
matchRecords.push({
pageIndex,
start,
end,
elements: [],
})
let segIndex = segments.findIndex((seg) => start < seg.end && end > seg.start)
if (segIndex === -1) continue
let currentStart = start
while (segIndex < segments.length && currentStart < end) {
const seg = segments[segIndex]
const highlightStart = Math.max(0, currentStart - seg.start)
const highlightEnd = Math.min(seg.text.length, end - seg.start)
if (highlightEnd > highlightStart) {
const ranges = perDivHighlights.get(seg.div) || []
ranges.push({
start: highlightStart,
end: highlightEnd,
matchIndex,
})
perDivHighlights.set(seg.div, ranges)
}
if (end <= seg.end) break
currentStart = seg.end
segIndex += 1
}
}
if (!matchRecords.length) {
return
}
perDivHighlights.forEach((ranges, div) => {
const original = div.dataset.originalText || div.textContent || ''
if (!original) return
const sorted = ranges
.slice()
.sort((a, b) => (a.start === b.start ? a.end - b.end : a.start - b.start))
let cursor = 0
let html = ''
sorted.forEach(({ start, end, matchIndex }) => {
if (start > cursor) {
html += this.escapeForHtml(original.slice(cursor, start))
}
const text = original.slice(start, end)
html += `<mark class="search-highlight" data-match-index="${matchIndex}">${this.escapeForHtml(text)}</mark>`
cursor = end
})
if (cursor < original.length) {
html += this.escapeForHtml(original.slice(cursor))
}
div.innerHTML = html
})
perDivHighlights.forEach((_ranges, div) => {
const marks = div.querySelectorAll('mark.search-highlight') const marks = div.querySelectorAll('mark.search-highlight')
marks.forEach((mark) => { marks.forEach((mark) => {
mark.dataset.matchIndex = results.length const matchIndex = Number(mark.dataset.matchIndex)
results.push({ if (!Number.isNaN(matchIndex) && matchRecords[matchIndex]) {
pageIndex, matchRecords[matchIndex].elements.push(mark)
element: mark, mark.dataset.pageIndex = String(pageIndex)
}) }
})
})
matchRecords.forEach((record) => {
if (!record.elements.length) return
const newIndex = results.length
record.elements.forEach((mark) => {
mark.dataset.matchIndex = String(newIndex)
})
results.push({
pageIndex,
element: record.elements[0],
elements: record.elements,
}) })
}) })
}) })
@ -629,11 +721,16 @@ export default {
this.currentResultIndex = index this.currentResultIndex = index
this.searchResults.forEach((item, idx) => { this.searchResults.forEach((item, idx) => {
if (idx === this.currentResultIndex) { if (!item) return
item.element.classList.add('is-active') const elements = item.elements && item.elements.length ? item.elements : [item.element]
} else { elements.forEach((el) => {
item.element.classList.remove('is-active') if (!el) return
} if (idx === this.currentResultIndex) {
el.classList.add('is-active')
} else {
el.classList.remove('is-active')
}
})
}) })
const target = this.searchResults[this.currentResultIndex]?.element const target = this.searchResults[this.currentResultIndex]?.element
@ -702,6 +799,15 @@ export default {
} }
}, },
escapeForHtml(text = '') {
return text
.replace(/&/g, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/"/g, '&quot;')
.replace(/'/g, '&#39;')
},
reload() { reload() {
if (!this.pdfUrl) return if (!this.pdfUrl) return
this.loadDocument() this.loadDocument()