mirror of
https://github.com/john-okeefe/foliate-js.git
synced 2026-09-09 11:29:14 -04:00
Highlight search results
The code looks very inefficient but I guess you can always optimize it later...
This commit is contained in:
@@ -138,6 +138,23 @@ export class Overlayer {
|
|||||||
}
|
}
|
||||||
return g
|
return g
|
||||||
}
|
}
|
||||||
|
static outline(rects, options = {}) {
|
||||||
|
const { color = 'red', width: strokeWidth = 3, radius = 3 } = options
|
||||||
|
const g = createSVGElement('g')
|
||||||
|
g.setAttribute('fill', 'none')
|
||||||
|
g.setAttribute('stroke', color)
|
||||||
|
g.setAttribute('stroke-width', strokeWidth)
|
||||||
|
for (const { left, top, height, width } of rects) {
|
||||||
|
const el = createSVGElement('rect')
|
||||||
|
el.setAttribute('x', left)
|
||||||
|
el.setAttribute('y', top)
|
||||||
|
el.setAttribute('height', height)
|
||||||
|
el.setAttribute('width', width)
|
||||||
|
el.setAttribute('rx', radius)
|
||||||
|
g.append(el)
|
||||||
|
}
|
||||||
|
return g
|
||||||
|
}
|
||||||
// make an exact copy of an image in the overlay
|
// make an exact copy of an image in the overlay
|
||||||
// one can then apply filters to the entire element, without affecting them;
|
// one can then apply filters to the entire element, without affecting them;
|
||||||
// it's a bit silly and probably better to just invert images twice
|
// it's a bit silly and probably better to just invert images twice
|
||||||
|
|||||||
@@ -2,6 +2,8 @@ import * as CFI from './epubcfi.js'
|
|||||||
import { TOCProgress, SectionProgress } from './progress.js'
|
import { TOCProgress, SectionProgress } from './progress.js'
|
||||||
import { Overlayer } from './overlayer.js'
|
import { Overlayer } from './overlayer.js'
|
||||||
|
|
||||||
|
const SEARCH_PREFIX = 'foliate-search:'
|
||||||
|
|
||||||
class History extends EventTarget {
|
class History extends EventTarget {
|
||||||
#arr = []
|
#arr = []
|
||||||
#index = -1
|
#index = -1
|
||||||
@@ -83,6 +85,7 @@ export class View extends HTMLElement {
|
|||||||
#sectionProgress
|
#sectionProgress
|
||||||
#tocProgress
|
#tocProgress
|
||||||
#pageProgress
|
#pageProgress
|
||||||
|
#searchResults = new Map()
|
||||||
isFixedLayout = false
|
isFixedLayout = false
|
||||||
lastLocation
|
lastLocation
|
||||||
history = new History()
|
history = new History()
|
||||||
@@ -184,6 +187,21 @@ export class View extends HTMLElement {
|
|||||||
}
|
}
|
||||||
async addAnnotation(annotation, remove) {
|
async addAnnotation(annotation, remove) {
|
||||||
const { value } = annotation
|
const { value } = annotation
|
||||||
|
if (value.startsWith(SEARCH_PREFIX)) {
|
||||||
|
const cfi = value.replace(SEARCH_PREFIX, '')
|
||||||
|
const { index, anchor } = await this.resolveNavigation(cfi)
|
||||||
|
const obj = this.#getOverlayer(index)
|
||||||
|
if (obj) {
|
||||||
|
const { overlayer, doc } = obj
|
||||||
|
if (remove) {
|
||||||
|
overlayer.remove(value)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const range = doc ? anchor(doc) : anchor
|
||||||
|
overlayer.add(value, range, Overlayer.outline)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
const { index, anchor } = await this.resolveNavigation(value)
|
const { index, anchor } = await this.resolveNavigation(value)
|
||||||
const obj = this.#getOverlayer(index)
|
const obj = this.#getOverlayer(index)
|
||||||
if (obj) {
|
if (obj) {
|
||||||
@@ -209,10 +227,14 @@ export class View extends HTMLElement {
|
|||||||
const overlayer = new Overlayer()
|
const overlayer = new Overlayer()
|
||||||
doc.addEventListener('click', e => {
|
doc.addEventListener('click', e => {
|
||||||
const [value, range] = overlayer.hitTest(e)
|
const [value, range] = overlayer.hitTest(e)
|
||||||
if (value) {
|
if (value && !value.startsWith(SEARCH_PREFIX)) {
|
||||||
this.#emit('show-annotation', { value, range })
|
this.#emit('show-annotation', { value, range })
|
||||||
}
|
}
|
||||||
}, false)
|
}, false)
|
||||||
|
|
||||||
|
const list = this.#searchResults.get(index)
|
||||||
|
if (list) for (const item of list) this.addAnnotation(item)
|
||||||
|
|
||||||
this.#emit('create-overlay', { index })
|
this.#emit('create-overlay', { index })
|
||||||
return overlayer
|
return overlayer
|
||||||
}
|
}
|
||||||
@@ -331,6 +353,7 @@ export class View extends HTMLElement {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
async * search(opts) {
|
async * search(opts) {
|
||||||
|
this.clearSearch()
|
||||||
const { searchMatcher } = await import('./search.js')
|
const { searchMatcher } = await import('./search.js')
|
||||||
const { query, index } = opts
|
const { query, index } = opts
|
||||||
const matcher = searchMatcher(textWalker,
|
const matcher = searchMatcher(textWalker,
|
||||||
@@ -338,12 +361,37 @@ export class View extends HTMLElement {
|
|||||||
const iter = index != null
|
const iter = index != null
|
||||||
? this.#searchSection(matcher, query, index)
|
? this.#searchSection(matcher, query, index)
|
||||||
: this.#searchBook(matcher, query)
|
: this.#searchBook(matcher, query)
|
||||||
for await (const result of iter) yield 'subitems' in result ? {
|
|
||||||
label: this.#tocProgress.getProgress(result.index)?.label ?? '',
|
const list = []
|
||||||
subitems: result.subitems,
|
this.#searchResults.set(index, list)
|
||||||
} : result
|
|
||||||
|
for await (const result of iter) {
|
||||||
|
if (result.subitems){
|
||||||
|
const list = result.subitems
|
||||||
|
.map(({ cfi }) => ({ value: SEARCH_PREFIX + cfi }))
|
||||||
|
this.#searchResults.set(result.index, list)
|
||||||
|
for (const item of list) this.addAnnotation(item)
|
||||||
|
yield {
|
||||||
|
label: this.#tocProgress.getProgress(result.index)?.label ?? '',
|
||||||
|
subitems: result.subitems,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if (result.cfi) {
|
||||||
|
const item = { value: SEARCH_PREFIX + result.cfi }
|
||||||
|
list.push(item)
|
||||||
|
this.addAnnotation(item)
|
||||||
|
}
|
||||||
|
yield result
|
||||||
|
}
|
||||||
|
}
|
||||||
yield 'done'
|
yield 'done'
|
||||||
}
|
}
|
||||||
|
clearSearch() {
|
||||||
|
for (const list of this.#searchResults.values())
|
||||||
|
for (const item of list) this.deleteAnnotation(item)
|
||||||
|
this.#searchResults.clear()
|
||||||
|
}
|
||||||
destroy() {
|
destroy() {
|
||||||
this.book.destroy?.()
|
this.book.destroy?.()
|
||||||
this.renderer?.destroy?.()
|
this.renderer?.destroy?.()
|
||||||
|
|||||||
Reference in New Issue
Block a user