mirror of
https://github.com/john-okeefe/foliate-js.git
synced 2026-09-09 11:29:14 -04:00
Let reader handle storing annotations
The view will only: 1. Send a signal to the reader when overlays are created 2. Allow the reader to add or remove annotations for active overlays only; if the annotations won't be on an overlay, it does nothing 3. It returns the index and label, which helps the reader stores the annotations.
This commit is contained in:
@@ -1,65 +0,0 @@
|
|||||||
export class Annotations {
|
|
||||||
#annotationsByIndex = new Map()
|
|
||||||
#byValue = new Map()
|
|
||||||
#anchorsByValue = new Map()
|
|
||||||
#indicesByValue = new Map()
|
|
||||||
constructor({ resolve, compare, onAdd, onDelete, onUpdate }) {
|
|
||||||
this.resolve = resolve
|
|
||||||
this.compare = compare
|
|
||||||
this.onAdd = onAdd
|
|
||||||
this.onDelete = onDelete
|
|
||||||
this.onUpdate = onUpdate
|
|
||||||
}
|
|
||||||
async add(annotation, sorted) {
|
|
||||||
const { value } = annotation
|
|
||||||
if (this.#byValue.has(value)) return
|
|
||||||
const { index, anchor } = await this.resolve(value)
|
|
||||||
this.#byValue.set(value, annotation)
|
|
||||||
this.#indicesByValue.set(value, index)
|
|
||||||
this.#anchorsByValue.set(value, anchor)
|
|
||||||
if (this.#annotationsByIndex.has(index)) {
|
|
||||||
const arr = this.#annotationsByIndex.get(index)
|
|
||||||
if (sorted) {
|
|
||||||
arr.push(annotation)
|
|
||||||
this.onAdd?.(annotation, index, arr.length - 1)
|
|
||||||
} else {
|
|
||||||
let position = 0
|
|
||||||
for (let i = 0; i < arr.length; i++) {
|
|
||||||
const itemValue = arr[i].value
|
|
||||||
if (this.compare(value, itemValue) <= 0) break
|
|
||||||
position = i + 1
|
|
||||||
}
|
|
||||||
arr.splice(position, 0, annotation)
|
|
||||||
this.onAdd?.(annotation, index, position)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
this.#annotationsByIndex.set(index, [annotation])
|
|
||||||
this.onAdd?.(annotation, index, 0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
update(annotation) {
|
|
||||||
const index = this.#indicesByValue.get(annotation.value)
|
|
||||||
const old = this.#byValue.get(annotation.value)
|
|
||||||
Object.assign(old, annotation)
|
|
||||||
this.onUpdate?.(annotation, index)
|
|
||||||
}
|
|
||||||
delete(value) {
|
|
||||||
const index = this.#indicesByValue.get(value)
|
|
||||||
const arr = this.#annotationsByIndex.get(index)
|
|
||||||
const position = arr.findIndex(a => a.value === value)
|
|
||||||
arr.splice(position, 1)
|
|
||||||
this.#byValue.delete(value)
|
|
||||||
this.#indicesByValue.delete(value)
|
|
||||||
this.#anchorsByValue.delete(value)
|
|
||||||
this.onDelete?.(value, index, position)
|
|
||||||
}
|
|
||||||
getByIndex(index) {
|
|
||||||
return this.#annotationsByIndex.get(index) ?? []
|
|
||||||
}
|
|
||||||
getAnchor(value) {
|
|
||||||
return this.#anchorsByValue.get(value)
|
|
||||||
}
|
|
||||||
export() {
|
|
||||||
return Array.from(this.#annotationsByIndex.values()).flat()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,7 +1,6 @@
|
|||||||
import * as CFI from './epubcfi.js'
|
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'
|
||||||
import { Annotations } from './annotations.js'
|
|
||||||
|
|
||||||
const textWalker = function* (doc, func) {
|
const textWalker = function* (doc, func) {
|
||||||
const filter = NodeFilter.SHOW_ELEMENT | NodeFilter.SHOW_TEXT
|
const filter = NodeFilter.SHOW_ELEMENT | NodeFilter.SHOW_TEXT
|
||||||
@@ -85,27 +84,6 @@ export class View {
|
|||||||
textDirection = ''
|
textDirection = ''
|
||||||
isCJK = false
|
isCJK = false
|
||||||
isFixedLayout = false
|
isFixedLayout = false
|
||||||
annotations = new Annotations({
|
|
||||||
resolve: value => this.resolveCFI(value),
|
|
||||||
compare: CFI.compare,
|
|
||||||
onAdd: (annotation, index, position) => {
|
|
||||||
const o = this.#getOverlayer(index)
|
|
||||||
if (o) this.#drawAnnotation(o.doc, o.overlayer, annotation)
|
|
||||||
const label = this.#tocProgress.getProgress(index)?.label ?? ''
|
|
||||||
this.emit?.({ type: 'add-annotation', annotation, label, index, position })
|
|
||||||
},
|
|
||||||
onDelete: (key, index, position) => {
|
|
||||||
this.#getOverlayer(index)?.overlayer?.remove(key)
|
|
||||||
this.emit?.({ type: 'delete-annotation', index, position })
|
|
||||||
},
|
|
||||||
onUpdate: (annotation, index) => {
|
|
||||||
const o = this.#getOverlayer(index)
|
|
||||||
if (o) {
|
|
||||||
o.overlayer.remove(annotation.value)
|
|
||||||
this.#drawAnnotation(o.doc, o.overlayer, annotation)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
})
|
|
||||||
constructor(book, emit) {
|
constructor(book, emit) {
|
||||||
this.book = book
|
this.book = book
|
||||||
this.emit = emit
|
this.emit = emit
|
||||||
@@ -149,18 +127,12 @@ export class View {
|
|||||||
}
|
}
|
||||||
return this.renderer.element
|
return this.renderer.element
|
||||||
}
|
}
|
||||||
async init({ lastLocation, annotations }) {
|
async init({ lastLocation }) {
|
||||||
if (lastLocation) {
|
if (lastLocation) {
|
||||||
const resolved = this.resolveNavigation(lastLocation)
|
const resolved = this.resolveNavigation(lastLocation)
|
||||||
if (resolved) await this.renderer.goTo(resolved)
|
if (resolved) await this.renderer.goTo(resolved)
|
||||||
else await this.renderer.next()
|
else await this.renderer.next()
|
||||||
} else await this.renderer.next()
|
} else await this.renderer.next()
|
||||||
|
|
||||||
if (annotations) {
|
|
||||||
annotations.sort((a, b) => CFI.compare(a.value, b.value))
|
|
||||||
for (const annotation of annotations)
|
|
||||||
await this.annotations.add(annotation, true)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
#onRelocated(range, index, fraction, size) {
|
#onRelocated(range, index, fraction, size) {
|
||||||
if (!this.#sectionProgress) return
|
if (!this.#sectionProgress) return
|
||||||
@@ -207,12 +179,24 @@ export class View {
|
|||||||
|
|
||||||
this.emit?.({ type: 'loaded', doc, index })
|
this.emit?.({ type: 'loaded', doc, index })
|
||||||
}
|
}
|
||||||
#drawAnnotation(doc, overlayer, annotation) {
|
async addAnnotation(annotation, remove) {
|
||||||
const { value } = annotation
|
const { value } = annotation
|
||||||
const anchor = this.annotations.getAnchor(value)
|
const { index, anchor } = await this.resolveNavigation(value)
|
||||||
const range = doc ? anchor(doc) : anchor
|
const obj = this.#getOverlayer(index)
|
||||||
const [func, opts] = this.emit({ type: 'draw-annotation', annotation })
|
if (obj) {
|
||||||
overlayer.add(value, range, func, opts)
|
const { overlayer, doc } = obj
|
||||||
|
overlayer.remove(value)
|
||||||
|
if (!remove) {
|
||||||
|
const range = doc ? anchor(doc) : anchor
|
||||||
|
const [func, opts] = this.emit({ type: 'draw-annotation', annotation })
|
||||||
|
overlayer.add(value, range, func, opts)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const label = this.#tocProgress.getProgress(index)?.label ?? ''
|
||||||
|
return { index, label }
|
||||||
|
}
|
||||||
|
deleteAnnotation(annotation) {
|
||||||
|
return this.addAnnotation(annotation, true)
|
||||||
}
|
}
|
||||||
#getOverlayer(index) {
|
#getOverlayer(index) {
|
||||||
const obj = this.renderer.getOverlayer()
|
const obj = this.renderer.getOverlayer()
|
||||||
@@ -220,14 +204,13 @@ export class View {
|
|||||||
}
|
}
|
||||||
#createOverlayer(doc, index) {
|
#createOverlayer(doc, index) {
|
||||||
const overlayer = new Overlayer()
|
const overlayer = new Overlayer()
|
||||||
for (const annotation of this.annotations.getByIndex(index))
|
|
||||||
this.#drawAnnotation(doc, overlayer, annotation)
|
|
||||||
doc.addEventListener('click', e => {
|
doc.addEventListener('click', e => {
|
||||||
const [value, range] = overlayer.hitTest(e)
|
const [value, range] = overlayer.hitTest(e)
|
||||||
if (value) {
|
if (value) {
|
||||||
this.emit?.({ type: 'show-annotation', value, range })
|
this.emit?.({ type: 'show-annotation', value, range })
|
||||||
}
|
}
|
||||||
}, false)
|
}, false)
|
||||||
|
this.emit?.({ type: 'create-overlay', index })
|
||||||
return overlayer
|
return overlayer
|
||||||
}
|
}
|
||||||
async showAnnotation(annotation) {
|
async showAnnotation(annotation) {
|
||||||
|
|||||||
Reference in New Issue
Block a user