文档搜索
This commit is contained in:
parent
148c189a0a
commit
10a35036f5
|
|
@ -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, '&')
|
||||||
|
.replace(/</g, '<')
|
||||||
|
.replace(/>/g, '>')
|
||||||
|
.replace(/"/g, '"')
|
||||||
|
.replace(/'/g, ''')
|
||||||
|
},
|
||||||
|
|
||||||
reload() {
|
reload() {
|
||||||
if (!this.pdfUrl) return
|
if (!this.pdfUrl) return
|
||||||
this.loadDocument()
|
this.loadDocument()
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue