文档搜索
This commit is contained in:
parent
148c189a0a
commit
10a35036f5
|
|
@ -569,26 +569,118 @@ export default {
|
|||
if (!textDivs || !textDivs.length) {
|
||||
return
|
||||
}
|
||||
|
||||
const segments = []
|
||||
let position = 0
|
||||
textDivs.forEach((div) => {
|
||||
const original = div.dataset.originalText || div.textContent || ''
|
||||
if (!original) return
|
||||
pattern.lastIndex = 0
|
||||
if (!pattern.test(original)) {
|
||||
pattern.lastIndex = 0
|
||||
div.textContent = original
|
||||
return
|
||||
}
|
||||
pattern.lastIndex = 0
|
||||
segments.push({
|
||||
div,
|
||||
text: original,
|
||||
start: position,
|
||||
end: position + original.length,
|
||||
})
|
||||
position += original.length
|
||||
})
|
||||
|
||||
const highlighted = original.replace(pattern, '<mark class="search-highlight">$1</mark>')
|
||||
div.innerHTML = highlighted
|
||||
if (!segments.length) {
|
||||
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')
|
||||
marks.forEach((mark) => {
|
||||
mark.dataset.matchIndex = results.length
|
||||
results.push({
|
||||
pageIndex,
|
||||
element: mark,
|
||||
})
|
||||
const matchIndex = Number(mark.dataset.matchIndex)
|
||||
if (!Number.isNaN(matchIndex) && matchRecords[matchIndex]) {
|
||||
matchRecords[matchIndex].elements.push(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.searchResults.forEach((item, idx) => {
|
||||
if (idx === this.currentResultIndex) {
|
||||
item.element.classList.add('is-active')
|
||||
} else {
|
||||
item.element.classList.remove('is-active')
|
||||
}
|
||||
if (!item) return
|
||||
const elements = item.elements && item.elements.length ? item.elements : [item.element]
|
||||
elements.forEach((el) => {
|
||||
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
|
||||
|
|
@ -702,6 +799,15 @@ export default {
|
|||
}
|
||||
},
|
||||
|
||||
escapeForHtml(text = '') {
|
||||
return text
|
||||
.replace(/&/g, '&')
|
||||
.replace(/</g, '<')
|
||||
.replace(/>/g, '>')
|
||||
.replace(/"/g, '"')
|
||||
.replace(/'/g, ''')
|
||||
},
|
||||
|
||||
reload() {
|
||||
if (!this.pdfUrl) return
|
||||
this.loadDocument()
|
||||
|
|
|
|||
Loading…
Reference in New Issue