diff --git a/examples/highlights.html b/examples/highlights.html index 94d1c6b..c0c9517 100644 --- a/examples/highlights.html +++ b/examples/highlights.html @@ -43,6 +43,14 @@ margin-top: 50px; } + [ref="epubjs-mk"] { + background: url("data:image/svg+xml;base64,PHN2ZyB2ZXJzaW9uPScxLjEnIHhtbG5zPSdodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZycgeG1sbnM6eGxpbms9J2h0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsnIHg9JzBweCcgeT0nMHB4JyB2aWV3Qm94PScwIDAgNzUgNzUnPjxnIGZpbGw9JyNCREJEQkQnIGlkPSdidWJibGUnPjxwYXRoIGNsYXNzPSdzdDAnIGQ9J00zNy41LDkuNEMxOS42LDkuNCw1LDIwLjUsNSwzNC4zYzAsNS45LDIuNywxMS4zLDcuMSwxNS42TDkuNiw2NS42bDE5LTcuM2MyLjgsMC42LDUuOCwwLjksOC45LDAuOSBDNTUuNSw1OS4yLDcwLDQ4LjEsNzAsMzQuM0M3MCwyMC41LDU1LjQsOS40LDM3LjUsOS40eicvPjwvZz48L3N2Zz4=") no-repeat; + width: 20px; + height: 20px; + cursor: pointer; + margin-left: 0; + } +
@@ -62,7 +70,8 @@ var rendition = book.renderTo("viewer", { width: "100%", height: 600, - ignoreClass: 'annotator-hl' + ignoreClass: 'annotator-hl', + manager: "continuous" }); var displayed = rendition.display(6); diff --git a/package-lock.json b/package-lock.json index 5054ab3..f72ed2d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "epubjs", - "version": "0.3.47", + "version": "0.3.53", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -9808,9 +9808,9 @@ "dev": true }, "marks-pane": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/marks-pane/-/marks-pane-1.0.2.tgz", - "integrity": "sha512-S3OuXWgPlCR9nQEN52Ut/beuw7SwkmVrTd9Ce38uEoaTbaO3/5fzD5KPERlWILH1mfi/B91DYfwCeC0bgsliXQ==" + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/marks-pane/-/marks-pane-1.0.4.tgz", + "integrity": "sha512-k3rauzqKlwDhIZClZ7/JGo3LEjxf1ReXzNoyFolniwKHsbAA8Cs8Ir4hpsWFB3KEW9NvtTYcffRnJCUybu0FVA==" }, "math-expression-evaluator": { "version": "1.2.17", diff --git a/package.json b/package.json index f525bf8..aba138b 100644 --- a/package.json +++ b/package.json @@ -74,7 +74,7 @@ "event-emitter": "^0.3.5", "jszip": "^3.1.4", "lodash": "^4.17.4", - "marks-pane": "^1.0.2", + "marks-pane": "^1.0.4", "path-webpack": "0.0.3", "stream-browserify": "^2.0.1", "xmldom": "^0.1.27" diff --git a/src/annotations.js b/src/annotations.js index 43ce376..224e3e7 100644 --- a/src/annotations.js +++ b/src/annotations.js @@ -35,7 +35,7 @@ class Annotations { this._annotations = {}; this._annotationsBySectionIndex = {}; - this.rendition.hooks.content.register(this.inject.bind(this)); + this.rendition.hooks.render.register(this.inject.bind(this)); this.rendition.hooks.unloaded.register(this.clear.bind(this)); } @@ -59,10 +59,11 @@ class Annotations { this._annotationsBySectionIndex[sectionIndex] = [hash]; } - let contents = this.rendition.getContents(); - contents.forEach( (content) => { - if (annotation.sectionIndex === content.sectionIndex) { - annotation.attach(content); + let views = this.rendition.views(); + + views.each( (view) => { + if (annotation.sectionIndex === view.index) { + annotation.attach(view); } }); @@ -75,10 +76,10 @@ class Annotations { 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); + let views = this.rendition.views(); + views.each( (view) => { + if (annotation.sectionIndex === view.index) { + annotation.detach(view); } }); @@ -102,24 +103,24 @@ class Annotations { return this._annotations.forEach.apply(this._annotations, arguments); } - inject (contents) { - let sectionIndex = contents.index; + 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(contents); + annotation.attach(view); }); } } - clear (contents) { - let sectionIndex = contents.index; + 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(contents); + annotation.detach(view); }); } } @@ -140,20 +141,22 @@ class Annotation { type, cfiRange, data, - sectionIndex + 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 (contents) { + attach (view) { let {cfiRange, data, type, mark, cb} = this; let result; /* @@ -161,12 +164,13 @@ class Annotation { return; // already added } */ + if (type === "highlight") { - result = contents.highlight(cfiRange, data, cb); + result = view.highlight(cfiRange, data, cb); } else if (type === "underline") { - result = contents.underline(cfiRange, data, cb); + result = view.underline(cfiRange, data, cb); } else if (type === "mark") { - result = contents.mark(cfiRange, data, cb); + result = view.mark(cfiRange, data, cb); } this.mark = result; @@ -174,17 +178,17 @@ class Annotation { return result; } - detach (contents) { + detach (view) { let {cfiRange, type} = this; let result; - if (contents) { + if (view) { if (type === "highlight") { - result = contents.unhighlight(cfiRange); + result = view.unhighlight(cfiRange); } else if (type === "underline") { - result = contents.ununderline(cfiRange); + result = view.ununderline(cfiRange); } else if (type === "mark") { - result = contents.unmark(cfiRange); + result = view.unmark(cfiRange); } } diff --git a/src/contents.js b/src/contents.js index b1775aa..01d69c8 100644 --- a/src/contents.js +++ b/src/contents.js @@ -3,7 +3,6 @@ import {isNumber, prefixed, borders} from "./utils/core"; import EpubCFI from "./epubcfi"; import Mapping from "./mapping"; import {replaceLinks} from "./utils/replacements"; -import { Pane, Highlight, Underline } from "marks-pane"; // Dom events to listen for const EVENTS = ["keydown", "keyup", "keypressed", "mouseup", "mousedown", "click", "touchend", "touchstart"]; @@ -32,11 +31,6 @@ class Contents { this.sectionIndex = sectionIndex || 0; this.cfiBase = cfiBase || ""; - this.pane = undefined; - this.highlights = {}; - this.underlines = {}; - this.marks = {}; - this.listeners(); } @@ -345,7 +339,6 @@ class Contents { height: height }; - this.pane && this.pane.render(); this.onResize && this.onResize(this._size); this.emit("resize", this._size); } @@ -534,7 +527,6 @@ class Contents { ready = true; // Let apply setTimeout(() => { - this.pane && this.pane.render(); resolve(true); }, 1); } @@ -605,7 +597,6 @@ class Contents { } }); } - this.pane && this.pane.render(); } addScript(src) { @@ -840,167 +831,6 @@ class Contents { }); } - highlight(cfiRange, data={}, cb) { - let range = this.range(cfiRange); - let emitter = () => { - this.emit("markClicked", cfiRange, data); - }; - - data["epubcfi"] = cfiRange; - - if (!this.pane) { - this.pane = new Pane(this.content, this.document.body); - } - - let m = new Highlight(range, "epubjs-hl", data, {'fill': 'yellow', 'fill-opacity': '0.3', 'mix-blend-mode': 'multiply'}); - let h = this.pane.addMark(m); - - this.highlights[cfiRange] = { "mark": h, "element": h.element, "listeners": [emitter, cb] }; - - h.element.addEventListener("click", emitter); - - if (cb) { - h.element.addEventListener("click", cb); - } - return h; - } - - underline(cfiRange, data={}, cb) { - let range = this.range(cfiRange); - let emitter = () => { - this.emit("markClicked", cfiRange, data); - }; - - data["epubcfi"] = cfiRange; - - if (!this.pane) { - this.pane = new Pane(this.content, this.document.body); - } - - let m = new Underline(range, "epubjs-ul", data, {'stroke': 'black', 'stroke-opacity': '0.3', 'mix-blend-mode': 'multiply'}); - let h = this.pane.addMark(m); - - this.underlines[cfiRange] = { "mark": h, "element": h.element, "listeners": [emitter, cb] }; - - h.element.addEventListener("click", emitter); - - if (cb) { - h.element.addEventListener("click", cb); - } - return h; - } - - /* - mark(cfiRange, data={}, cb) { - let range = this.range(cfiRange); - - let container = range.commonAncestorContainer; - let parent = (container.nodeType === 1) ? container : container.parentNode; - let emitter = () => { - this.emit("markClicked", cfiRange, data); - }; - - parent.setAttribute("ref", "epubjs-mk"); - - parent.dataset["epubcfi"] = cfiRange; - - if (data) { - Object.keys(data).forEach((key) => { - parent.dataset[key] = data[key]; - }); - } - - parent.addEventListener("click", emitter); - - if (cb) { - parent.addEventListener("click", cb); - } - - this.marks[cfiRange] = { "element": parent, "listeners": [emitter, cb] }; - - return parent; - } - */ - - mark(cfiRange, data={}, cb) { - - if (cfiRange in this.marks) { - let item = this.marks[cfiRange]; - return item; - } - - let range = this.range(cfiRange); - - let container = range.commonAncestorContainer; - let parent = (container.nodeType === 1) ? container : container.parentNode; - let emitter = (e) => { - this.emit("markClicked", cfiRange, data); - }; - - let pos = parent.getBoundingClientRect(); - let mark = this.document.createElement('a'); - mark.setAttribute("ref", "epubjs-mk"); - mark.style.position = "absolute"; - mark.style.top = `${pos.top}px`; - mark.style.left = `${pos.right}px`; - - mark.dataset["epubcfi"] = cfiRange; - - if (data) { - Object.keys(data).forEach((key) => { - mark.dataset[key] = data[key]; - }); - } - - if (cb) { - mark.addEventListener("click", cb); - } - - mark.addEventListener("click", emitter); - - this.content.appendChild(mark); - - this.marks[cfiRange] = { "element": mark, "listeners": [emitter, cb] }; - - return parent; - } - - unhighlight(cfiRange) { - let item; - if (cfiRange in this.highlights) { - item = this.highlights[cfiRange]; - this.pane.removeMark(item.mark); - item.listeners.forEach((l) => { - if (l) { item.element.removeEventListener("click", l) }; - }); - delete this.highlights[cfiRange]; - } - } - - ununderline(cfiRange) { - let item; - if (cfiRange in this.underlines) { - item = this.underlines[cfiRange]; - this.pane.removeMark(item.mark); - item.listeners.forEach((l) => { - if (l) { item.element.removeEventListener("click", l) }; - }); - delete this.underlines[cfiRange]; - } - } - - unmark(cfiRange) { - let item; - if (cfiRange in this.marks) { - item = this.marks[cfiRange]; - this.content.removeChild(item.element); - item.listeners.forEach((l) => { - if (l) { item.element.removeEventListener("click", l) }; - }); - delete this.marks[cfiRange]; - } - } - destroy() { // Stop observing if(this.observer) { diff --git a/src/managers/views/iframe.js b/src/managers/views/iframe.js index 48aeea8..75793c0 100644 --- a/src/managers/views/iframe.js +++ b/src/managers/views/iframe.js @@ -2,6 +2,7 @@ import EventEmitter from "event-emitter"; import {extend, borders, uuid, isNumber, bounds, defer} from "../../utils/core"; import EpubCFI from "../../epubcfi"; import Contents from "../../contents"; +import { Pane, Highlight, Underline } from "marks-pane"; class IframeView { constructor(section, options) { @@ -36,6 +37,12 @@ class IframeView { this.layout = this.settings.layout; // Dom events to listen for // this.listenedEvents = ["keydown", "keyup", "keypressed", "mouseup", "mousedown", "click", "touchend", "touchstart"]; + + this.pane = undefined; + this.highlights = {}; + this.underlines = {}; + this.marks = {}; + } container(axis) { @@ -47,6 +54,7 @@ class IframeView { element.style.height = "0px"; element.style.width = "0px"; element.style.overflow = "hidden"; + element.style.position = "relative"; if(axis && axis == "horizontal"){ element.style.display = "block"; @@ -297,6 +305,8 @@ class IframeView { heightDelta: heightDelta, }; + this.pane && this.pane.render(); + this.onResize(this, size); this.emit("resized", size); @@ -484,8 +494,166 @@ class IframeView { return this.elementBounds; } + highlight(cfiRange, data={}, cb) { + if (!this.contents) { + return; + } + let range = this.contents.range(cfiRange); + + let emitter = () => { + this.emit("markClicked", cfiRange, data); + }; + + data["epubcfi"] = cfiRange; + + if (!this.pane) { + this.pane = new Pane(this.iframe, this.element); + } + + let m = new Highlight(range, "epubjs-hl", data, {'fill': 'yellow', 'fill-opacity': '0.3', 'mix-blend-mode': 'multiply'}); + let h = this.pane.addMark(m); + + this.highlights[cfiRange] = { "mark": h, "element": h.element, "listeners": [emitter, cb] }; + + h.element.setAttribute("ref", "epubjs-hl"); + h.element.addEventListener("click", emitter); + h.element.addEventListener("touchstart", emitter); + + if (cb) { + h.element.addEventListener("click", cb); + h.element.addEventListener("touchstart", cb); + } + return h; + } + + underline(cfiRange, data={}, cb) { + if (!this.contents) { + return; + } + let range = this.contents.range(cfiRange); + let emitter = () => { + this.emit("markClicked", cfiRange, data); + }; + + data["epubcfi"] = cfiRange; + + if (!this.pane) { + this.pane = new Pane(this.iframe, this.element); + } + + let m = new Underline(range, "epubjs-ul", data, {'stroke': 'black', 'stroke-opacity': '0.3', 'mix-blend-mode': 'multiply'}); + let h = this.pane.addMark(m); + + this.underlines[cfiRange] = { "mark": h, "element": h.element, "listeners": [emitter, cb] }; + + h.element.setAttribute("ref", "epubjs-ul"); + h.element.addEventListener("click", emitter); + h.element.addEventListener("touchstart", emitter); + + if (cb) { + h.element.addEventListener("click", cb); + h.element.addEventListener("touchstart", cb); + } + return h; + } + + mark(cfiRange, data={}, cb) { + + if (cfiRange in this.marks) { + let item = this.marks[cfiRange]; + return item; + } + + let range = this.contents.range(cfiRange); + + let container = range.commonAncestorContainer; + let parent = (container.nodeType === 1) ? container : container.parentNode; + + let emitter = (e) => { + this.emit("markClicked", cfiRange, data); + }; + + let pos = parent.getBoundingClientRect(); + let mark = this.document.createElement('a'); + mark.setAttribute("ref", "epubjs-mk"); + mark.style.position = "absolute"; + mark.style.top = `${pos.top}px`; + mark.style.left = `${pos.right}px`; + + mark.dataset["epubcfi"] = cfiRange; + + if (data) { + Object.keys(data).forEach((key) => { + mark.dataset[key] = data[key]; + }); + } + + if (cb) { + mark.addEventListener("click", cb); + mark.addEventListener("touchstart", cb); + } + + mark.addEventListener("click", emitter); + mark.addEventListener("touchstart", emitter); + + this.element.appendChild(mark); + + this.marks[cfiRange] = { "element": mark, "listeners": [emitter, cb] }; + + return parent; + } + + unhighlight(cfiRange) { + let item; + if (cfiRange in this.highlights) { + item = this.highlights[cfiRange]; + + this.pane.removeMark(item.mark); + item.listeners.forEach((l) => { + if (l) { item.element.removeEventListener("click", l) }; + }); + delete this.highlights[cfiRange]; + } + } + + ununderline(cfiRange) { + let item; + if (cfiRange in this.underlines) { + item = this.underlines[cfiRange]; + this.pane.removeMark(item.mark); + item.listeners.forEach((l) => { + if (l) { item.element.removeEventListener("click", l) }; + }); + delete this.underlines[cfiRange]; + } + } + + unmark(cfiRange) { + let item; + if (cfiRange in this.marks) { + item = this.marks[cfiRange]; + this.element.removeChild(item.element); + item.listeners.forEach((l) => { + if (l) { item.element.removeEventListener("click", l) }; + }); + delete this.marks[cfiRange]; + } + } + destroy() { + for (let cfiRange in this.highlights) { + this.unhighlight(cfiRange); + } + + for (let cfiRange in this.underlines) { + this.ununderline(cfiRange); + } + + for (let cfiRange in this.marks) { + this.unmark(cfiRange); + } + if(this.displayed){ this.displayed = false; diff --git a/src/rendition.js b/src/rendition.js index 219823a..7c204be 100644 --- a/src/rendition.js +++ b/src/rendition.js @@ -53,7 +53,7 @@ class Rendition { this.book = book; - this.views = null; + // this.views = null; /** * Adds Hook methods to the Rendition prototype @@ -329,13 +329,19 @@ class Rendition { * @param {*} view */ afterDisplayed(view){ - if (view.contents) { - this.hooks.content.trigger(view.contents, this).then(() => { - this.emit("rendered", view.section, view); + + view.on("markClicked", (cfiRange, data) => this.triggerMarkEvent(cfiRange, data, view)); + + this.hooks.render.trigger(view, this) + .then(() => { + if (view.contents) { + this.hooks.content.trigger(view.contents, this).then(() => { + this.emit("rendered", view.section, view); + }); + } else { + this.emit("rendered", view.section, view); + } }); - } else { - this.emit("rendered", view.section, view); - } // this.reportLocation(); } @@ -661,7 +667,7 @@ class Rendition { this.book = undefined; - this.views = null; + // this.views = null; // this.hooks.display.clear(); // this.hooks.serialize.clear(); @@ -683,7 +689,7 @@ class Rendition { } /** - * Pass the events from a view + * Pass the events from a view's Contents * @private * @param {View} view */ @@ -695,7 +701,6 @@ class Rendition { }); contents.on("selected", (e) => this.triggerSelectedEvent(e, contents)); - contents.on("markClicked", (cfiRange, data) => this.triggerMarkEvent(cfiRange, data, contents)); } /** @@ -776,6 +781,10 @@ class Rendition { return this.manager ? this.manager.getContents() : []; } + views () { + return this.manager ? this.manager.views : []; + } + handleLinks(contents) { if (contents) { contents.on("link", (href) => {