// 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