1
0
Fork 0
mirror of https://github.com/futurepress/epub.js.git synced 2025-10-03 14:59:18 +02:00
epub.js/src/annotations.js

222 lines
4.3 KiB
JavaScript

// Manage annotations for a book?
/*
let a = rendition.annotations.highlight(cfiRange, data)
a.on("added", () => console.log("added"))
a.on("removed", () => console.log("removed"))
a.on("clicked", () => console.log("clicked"))
a.update(data)
a.remove();
a.text();
rendition.annotations.show()
rendition.annotations.hide()
rendition.annotations.highlights.show()
rendition.annotations.highlights.hide()
*/
import EventEmitter from "event-emitter";
import EpubCFI from "./epubcfi";
/**
* Handles managing adding & removing Annotations
* @class
*/
class Annotations {
constructor (rendition) {
this.rendition = rendition;
this.highlights = [];
this.underlines = [];
this.marks = [];
this._annotations = {};
this._annotationsBySectionIndex = {};
this.rendition.hooks.render.register(this.inject.bind(this));
this.rendition.hooks.unloaded.register(this.clear.bind(this));
}
add (type, cfiRange, data, cb) {
let hash = encodeURI(cfiRange);
let cfi = new EpubCFI(cfiRange);
let sectionIndex = cfi.spinePos;
let annotation = new Annotation({
type,
cfiRange,
data,
sectionIndex,
cb
});
this._annotations[hash] = annotation;
if (sectionIndex in this._annotationsBySectionIndex) {
this._annotationsBySectionIndex[sectionIndex].push(hash);
} else {
this._annotationsBySectionIndex[sectionIndex] = [hash];
}
let views = this.rendition.views();
views.forEach( (view) => {
if (annotation.sectionIndex === view.index) {
annotation.attach(view);
}
});
return annotation;
}
remove (cfiRange, type) {
let hash = encodeURI(cfiRange);
if (hash in this._annotations) {
let annotation = this._annotations[hash];
if (type && annotation.type !== type) {
return;
}
let views = this.rendition.views();
views.forEach( (view) => {
this._removeFromAnnotationBySectionIndex(annotation.sectionIndex, hash);
if (annotation.sectionIndex === view.index) {
annotation.detach(view);
}
});
delete this._annotations[hash];
}
}
_removeFromAnnotationBySectionIndex (sectionIndex, hash) {
this._annotationsBySectionIndex[sectionIndex] = this._annotationsAt(sectionIndex).filter(h => h !== hash);
}
_annotationsAt (index) {
return this._annotationsBySectionIndex[index];
}
highlight (cfiRange, data, cb) {
this.add("highlight", cfiRange, data, cb);
}
underline (cfiRange, data, cb) {
this.add("underline", cfiRange, data, cb);
}
mark (cfiRange, data, cb) {
this.add("mark", cfiRange, data, cb);
}
each () {
return this._annotations.forEach.apply(this._annotations, arguments);
}
inject (view) {
let sectionIndex = view.index;
if (sectionIndex in this._annotationsBySectionIndex) {
let annotations = this._annotationsBySectionIndex[sectionIndex];
annotations.forEach((hash) => {
let annotation = this._annotations[hash];
annotation.attach(view);
});
}
}
clear (view) {
let sectionIndex = view.index;
if (sectionIndex in this._annotationsBySectionIndex) {
let annotations = this._annotationsBySectionIndex[sectionIndex];
annotations.forEach((hash) => {
let annotation = this._annotations[hash];
annotation.detach(view);
});
}
}
show () {
}
hide () {
}
}
class Annotation {
constructor ({
type,
cfiRange,
data,
sectionIndex,
cb
}) {
this.type = type;
this.cfiRange = cfiRange;
this.data = data;
this.sectionIndex = sectionIndex;
this.mark = undefined;
this.cb = cb;
}
update (data) {
this.data = data;
}
attach (view) {
let {cfiRange, data, type, mark, cb} = this;
let result;
/*
if (mark) {
return; // already added
}
*/
if (type === "highlight") {
result = view.highlight(cfiRange, data, cb);
} else if (type === "underline") {
result = view.underline(cfiRange, data, cb);
} else if (type === "mark") {
result = view.mark(cfiRange, data, cb);
}
this.mark = result;
return result;
}
detach (view) {
let {cfiRange, type} = this;
let result;
if (view) {
if (type === "highlight") {
result = view.unhighlight(cfiRange);
} else if (type === "underline") {
result = view.ununderline(cfiRange);
} else if (type === "mark") {
result = view.unmark(cfiRange);
}
}
this.mark = undefined;
return result;
}
text () {
// TODO: needs implementation in contents
}
}
EventEmitter(Annotation.prototype);
export default Annotations