diff --git a/examples/highlights.html b/examples/highlights.html
index 0596cbd..26e0b31 100644
--- a/examples/highlights.html
+++ b/examples/highlights.html
@@ -106,7 +106,7 @@
// Apply a class to selected text
rendition.on("selected", function(cfiRange, contents) {
- contents.highlight(cfiRange, {}, (e) => {
+ rendition.annotations.highlight(cfiRange, {}, (e) => {
console.log("highlight clicked", e.target);
});
contents.window.getSelection().removeAllRanges();
diff --git a/src/annotations.js b/src/annotations.js
new file mode 100644
index 0000000..a718210
--- /dev/null
+++ b/src/annotations.js
@@ -0,0 +1,205 @@
+// 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.content.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 contents = this.rendition.getContents();
+ contents.forEach( (content) => {
+ if (annotation.sectionIndex === content.sectionIndex) {
+ annotation.attach(content);
+ }
+ });
+
+ return annotation;
+ }
+
+ remove (cfiRange) {
+ let hash = decodeURI(cfiRange);
+ let result;
+ if (hash in this._annotations) {
+ let annotation = this._annotations[hash];
+
+ let contents = this.rendition.getContents();
+ contents.forEach( (content) => {
+ if (annotation.sectionIndex === content.sectionIndex) {
+ annotation.detach(content);
+ }
+ });
+
+ delete this._annotations[hash];
+ }
+ return result;
+ }
+
+ 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 (contents) {
+ let sectionIndex = contents.index;
+ if (sectionIndex in this._annotationsBySectionIndex) {
+ let annotations = this._annotationsBySectionIndex[sectionIndex];
+ annotations.forEach((hash) => {
+ let annotation = this._annotations[hash];
+ annotation.attach(contents);
+ });
+ }
+ }
+
+ clear (contents) {
+ let sectionIndex = contents.index;
+ if (sectionIndex in this._annotationsBySectionIndex) {
+ let annotations = this._annotationsBySectionIndex[sectionIndex];
+ annotations.forEach((hash) => {
+ let annotation = this._annotations[hash];
+ annotation.detach(contents);
+ });
+ }
+ }
+
+ show () {
+
+ }
+
+ hide () {
+
+ }
+
+}
+
+class Annotation {
+
+ constructor ({
+ type,
+ cfiRange,
+ data,
+ sectionIndex
+ }) {
+ this.type = type;
+ this.cfiRange = cfiRange;
+ this.data = data;
+ this.sectionIndex = sectionIndex;
+ this.mark = undefined;
+ }
+
+ update (data) {
+ this.data = data;
+ }
+
+ attach (contents) {
+ let {cfiRange, data, type, mark, cb} = this;
+ let result;
+ /*
+ if (mark) {
+ return; // already added
+ }
+ */
+ if (type === "highlight") {
+ result = contents.highlight(cfiRange, data, cb);
+ } else if (type === "underline") {
+ result = contents.underline(cfiRange, data, cb);
+ } else if (type === "mark") {
+ result = contents.mark(cfiRange, data, cb);
+ }
+
+ this.mark = result;
+
+ return result;
+ }
+
+ detach (contents) {
+ let {cfiRange, type} = this;
+
+ if (contents) {
+ if (type === "highlight") {
+ result = contents.unhighlight(cfiRange);
+ } else if (type === "underline") {
+ result = contents.ununderline(cfiRange);
+ } else if (type === "mark") {
+ result = contents.unmark(cfiRange);
+ }
+ }
+
+ this.mark = undefined;
+
+ return result;
+ }
+
+ text () {
+ // TODO: needs implementation in contents
+ }
+
+}
+
+EventEmitter(Annotation.prototype);
+
+
+export default Annotations
diff --git a/src/contents.js b/src/contents.js
index 1ef6f9c..98fcd51 100644
--- a/src/contents.js
+++ b/src/contents.js
@@ -9,7 +9,7 @@ import { Pane, Highlight, Underline } from "marks-pane";
const EVENTS = ["keydown", "keyup", "keypressed", "mouseup", "mousedown", "click", "touchend", "touchstart"];
class Contents {
- constructor(doc, content, cfiBase) {
+ constructor(doc, content, cfiBase, sectionIndex) {
// Blank Cfi for Parsing
this.epubcfi = new EpubCFI();
@@ -23,6 +23,7 @@ class Contents {
height: 0
};
+ this.sectionIndex = sectionIndex || 0;
this.cfiBase = cfiBase || "";
this.pane = undefined;
diff --git a/src/managers/views/iframe.js b/src/managers/views/iframe.js
index 4ca3a9b..3280a78 100644
--- a/src/managers/views/iframe.js
+++ b/src/managers/views/iframe.js
@@ -418,7 +418,7 @@ class IframeView {
this.window = this.iframe.contentWindow;
this.document = this.iframe.contentDocument;
- this.contents = new Contents(this.document, this.document.body, this.section.cfiBase);
+ this.contents = new Contents(this.document, this.document.body, this.section.cfiBase, this.section.index);
this.rendering = false;
diff --git a/src/rendition.js b/src/rendition.js
index 4f198dc..8c74e0f 100644
--- a/src/rendition.js
+++ b/src/rendition.js
@@ -7,6 +7,7 @@ import Layout from "./layout";
import Mapping from "./mapping";
import Themes from "./themes";
import Contents from "./contents";
+import Annotations from "./annotations";
/**
* [Rendition description]
@@ -66,6 +67,7 @@ class Rendition {
* @type {Hook}
*/
this.hooks.content = new Hook(this);
+ this.hooks.unloaded = new Hook(this);
this.hooks.layout = new Hook(this);
this.hooks.render = new Hook(this);
this.hooks.show = new Hook(this);
@@ -85,6 +87,8 @@ class Rendition {
// this.hooks.display.register(this.afterDisplay.bind(this));
this.themes = new Themes(this);
+ this.annotations = new Annotations(this);
+
this.epubcfi = new EpubCFI();
this.q = new Queue(this);
@@ -171,6 +175,7 @@ class Rendition {
// Listen for displayed views
this.manager.on("added", this.afterDisplayed.bind(this));
+ this.manager.on("removed", this.afterRemoved.bind(this));
// Listen for resizing
this.manager.on("resized", this.onResized.bind(this));
@@ -309,11 +314,23 @@ class Rendition {
* @param {*} view
*/
afterDisplayed(view){
- this.hooks.content.trigger(view.contents, this);
- this.emit("rendered", view.section, view);
+ this.hooks.content.trigger(view.contents, this).then(() => {
+ this.emit("rendered", view.section, view);
+ });
// this.reportLocation();
}
+ /**
+ * Report what has been removed
+ * @private
+ * @param {*} view
+ */
+ afterRemoved(view){
+ this.hooks.unloaded.trigger(view, this).then(() => {
+ this.emit("removed", view.section, view);
+ });
+ }
+
/**
* Report resize events and display the last seen location
* @private