mirror of
https://github.com/futurepress/epub.js.git
synced 2025-10-04 15:09:16 +02:00
Add annotation manager in rendition
This commit is contained in:
parent
697c101a39
commit
987a216c55
5 changed files with 228 additions and 5 deletions
|
@ -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();
|
||||
|
|
205
src/annotations.js
Normal file
205
src/annotations.js
Normal file
|
@ -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
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue