mirror of
https://github.com/advplyr/audiobookshelf.git
synced 2026-07-10 19:31:39 +00:00
got it to build, quick n dirty; epubjs patch
This commit is contained in:
parent
c67c586c98
commit
2baf50da12
43 changed files with 14616 additions and 203 deletions
852
client/static/libs/epubjs-patch/managers/views/iframe.js
Normal file
852
client/static/libs/epubjs-patch/managers/views/iframe.js
Normal file
|
|
@ -0,0 +1,852 @@
|
|||
import { EventEmitter } from "events";
|
||||
import {extend, borders, uuid, isNumber, bounds, defer, createBlobUrl, revokeBlobUrl} from "../../utils/core";
|
||||
import EpubCFI from "../../epubcfi";
|
||||
import Contents from "../../contents";
|
||||
import { EVENTS } from "../../utils/constants";
|
||||
import { Pane, Highlight, Underline } from "marks-pane";
|
||||
|
||||
class IframeView extends EventEmitter {
|
||||
constructor(section, options) {
|
||||
super();
|
||||
this.settings = extend({
|
||||
ignoreClass : "",
|
||||
axis: undefined, //options.layout && options.layout.props.flow === "scrolled" ? "vertical" : "horizontal",
|
||||
direction: undefined,
|
||||
width: 0,
|
||||
height: 0,
|
||||
layout: undefined,
|
||||
globalLayoutProperties: {},
|
||||
method: undefined,
|
||||
forceRight: false,
|
||||
allowScriptedContent: false,
|
||||
allowPopups: false
|
||||
}, options || {});
|
||||
|
||||
this.id = "epubjs-view-" + uuid();
|
||||
this.section = section;
|
||||
this.index = section.index;
|
||||
|
||||
this.element = this.container(this.settings.axis);
|
||||
|
||||
this.added = false;
|
||||
this.displayed = false;
|
||||
this.rendered = false;
|
||||
|
||||
// this.width = this.settings.width;
|
||||
// this.height = this.settings.height;
|
||||
|
||||
this.fixedWidth = 0;
|
||||
this.fixedHeight = 0;
|
||||
|
||||
// Blank Cfi for Parsing
|
||||
this.epubcfi = new EpubCFI();
|
||||
|
||||
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) {
|
||||
var element = document.createElement("div");
|
||||
|
||||
element.classList.add("epub-view");
|
||||
|
||||
// this.element.style.minHeight = "100px";
|
||||
element.style.height = "0px";
|
||||
element.style.width = "0px";
|
||||
element.style.overflow = "hidden";
|
||||
element.style.position = "relative";
|
||||
element.style.display = "block";
|
||||
|
||||
if(axis && axis == "horizontal"){
|
||||
element.style.flex = "none";
|
||||
} else {
|
||||
element.style.flex = "initial";
|
||||
}
|
||||
|
||||
return element;
|
||||
}
|
||||
|
||||
create() {
|
||||
|
||||
if(this.iframe) {
|
||||
return this.iframe;
|
||||
}
|
||||
|
||||
if(!this.element) {
|
||||
this.element = this.createContainer();
|
||||
}
|
||||
|
||||
this.iframe = document.createElement("iframe");
|
||||
this.iframe.id = this.id;
|
||||
this.iframe.scrolling = "no"; // Might need to be removed: breaks ios width calculations
|
||||
this.iframe.style.overflow = "hidden";
|
||||
this.iframe.seamless = "seamless";
|
||||
// Back up if seamless isn't supported
|
||||
this.iframe.style.border = "none";
|
||||
|
||||
// sandbox
|
||||
this.iframe.sandbox = "allow-same-origin";
|
||||
if (this.settings.allowScriptedContent) {
|
||||
this.iframe.sandbox += " allow-scripts";
|
||||
}
|
||||
if (this.settings.allowPopups) {
|
||||
this.iframe.sandbox += " allow-popups";
|
||||
}
|
||||
|
||||
this.iframe.setAttribute("enable-annotation", "true");
|
||||
|
||||
this.resizing = true;
|
||||
|
||||
// this.iframe.style.display = "none";
|
||||
this.element.style.visibility = "hidden";
|
||||
this.iframe.style.visibility = "hidden";
|
||||
|
||||
this.iframe.style.width = "0";
|
||||
this.iframe.style.height = "0";
|
||||
this._width = 0;
|
||||
this._height = 0;
|
||||
|
||||
this.element.setAttribute("ref", this.index);
|
||||
|
||||
this.added = true;
|
||||
|
||||
this.elementBounds = bounds(this.element);
|
||||
|
||||
// if(width || height){
|
||||
// this.resize(width, height);
|
||||
// } else if(this.width && this.height){
|
||||
// this.resize(this.width, this.height);
|
||||
// } else {
|
||||
// this.iframeBounds = bounds(this.iframe);
|
||||
// }
|
||||
|
||||
|
||||
if(("srcdoc" in this.iframe)) {
|
||||
this.supportsSrcdoc = true;
|
||||
} else {
|
||||
this.supportsSrcdoc = false;
|
||||
}
|
||||
|
||||
if (!this.settings.method) {
|
||||
this.settings.method = this.supportsSrcdoc ? "srcdoc" : "write";
|
||||
}
|
||||
|
||||
return this.iframe;
|
||||
}
|
||||
|
||||
render(request, show) {
|
||||
|
||||
// view.onLayout = this.layout.format.bind(this.layout);
|
||||
this.create();
|
||||
|
||||
// Fit to size of the container, apply padding
|
||||
this.size();
|
||||
|
||||
if(!this.sectionRender) {
|
||||
this.sectionRender = this.section.render(request);
|
||||
}
|
||||
|
||||
// Render Chain
|
||||
return this.sectionRender
|
||||
.then(function(contents){
|
||||
return this.load(contents);
|
||||
}.bind(this))
|
||||
.then(function(){
|
||||
|
||||
// find and report the writingMode axis
|
||||
let writingMode = this.contents.writingMode();
|
||||
|
||||
// Set the axis based on the flow and writing mode
|
||||
let axis;
|
||||
if (this.settings.flow === "scrolled") {
|
||||
axis = (writingMode.indexOf("vertical") === 0) ? "horizontal" : "vertical";
|
||||
} else {
|
||||
axis = (writingMode.indexOf("vertical") === 0) ? "vertical" : "horizontal";
|
||||
}
|
||||
|
||||
if (writingMode.indexOf("vertical") === 0 && this.settings.flow === "paginated") {
|
||||
this.layout.delta = this.layout.height;
|
||||
}
|
||||
|
||||
this.setAxis(axis);
|
||||
this.emit(EVENTS.VIEWS.AXIS, axis);
|
||||
|
||||
this.setWritingMode(writingMode);
|
||||
this.emit(EVENTS.VIEWS.WRITING_MODE, writingMode);
|
||||
|
||||
|
||||
// apply the layout function to the contents
|
||||
this.layout.format(this.contents, this.section, this.axis);
|
||||
|
||||
// Listen for events that require an expansion of the iframe
|
||||
this.addListeners();
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
// Expand the iframe to the full size of the content
|
||||
this.expand();
|
||||
|
||||
if (this.settings.forceRight) {
|
||||
this.element.style.marginLeft = this.width() + "px";
|
||||
}
|
||||
resolve();
|
||||
});
|
||||
|
||||
}.bind(this), function(e){
|
||||
this.emit(EVENTS.VIEWS.LOAD_ERROR, e);
|
||||
return new Promise((resolve, reject) => {
|
||||
reject(e);
|
||||
});
|
||||
}.bind(this))
|
||||
.then(function() {
|
||||
this.emit(EVENTS.VIEWS.RENDERED, this.section);
|
||||
}.bind(this));
|
||||
|
||||
}
|
||||
|
||||
reset () {
|
||||
if (this.iframe) {
|
||||
this.iframe.style.width = "0";
|
||||
this.iframe.style.height = "0";
|
||||
this._width = 0;
|
||||
this._height = 0;
|
||||
this._textWidth = undefined;
|
||||
this._contentWidth = undefined;
|
||||
this._textHeight = undefined;
|
||||
this._contentHeight = undefined;
|
||||
}
|
||||
this._needsReframe = true;
|
||||
}
|
||||
|
||||
// Determine locks base on settings
|
||||
size(_width, _height) {
|
||||
var width = _width || this.settings.width;
|
||||
var height = _height || this.settings.height;
|
||||
|
||||
if(this.layout.name === "pre-paginated") {
|
||||
this.lock("both", width, height);
|
||||
} else if(this.settings.axis === "horizontal") {
|
||||
this.lock("height", width, height);
|
||||
} else {
|
||||
this.lock("width", width, height);
|
||||
}
|
||||
|
||||
this.settings.width = width;
|
||||
this.settings.height = height;
|
||||
}
|
||||
|
||||
// Lock an axis to element dimensions, taking borders into account
|
||||
lock(what, width, height) {
|
||||
var elBorders = borders(this.element);
|
||||
var iframeBorders;
|
||||
|
||||
if(this.iframe) {
|
||||
iframeBorders = borders(this.iframe);
|
||||
} else {
|
||||
iframeBorders = {width: 0, height: 0};
|
||||
}
|
||||
|
||||
if(what == "width" && isNumber(width)){
|
||||
this.lockedWidth = width - elBorders.width - iframeBorders.width;
|
||||
// this.resize(this.lockedWidth, width); // width keeps ratio correct
|
||||
}
|
||||
|
||||
if(what == "height" && isNumber(height)){
|
||||
this.lockedHeight = height - elBorders.height - iframeBorders.height;
|
||||
// this.resize(width, this.lockedHeight);
|
||||
}
|
||||
|
||||
if(what === "both" &&
|
||||
isNumber(width) &&
|
||||
isNumber(height)){
|
||||
|
||||
this.lockedWidth = width - elBorders.width - iframeBorders.width;
|
||||
this.lockedHeight = height - elBorders.height - iframeBorders.height;
|
||||
// this.resize(this.lockedWidth, this.lockedHeight);
|
||||
}
|
||||
|
||||
if(this.displayed && this.iframe) {
|
||||
|
||||
// this.contents.layout();
|
||||
this.expand();
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
// Resize a single axis based on content dimensions
|
||||
expand(force) {
|
||||
var width = this.lockedWidth;
|
||||
var height = this.lockedHeight;
|
||||
var columns;
|
||||
|
||||
var textWidth, textHeight;
|
||||
|
||||
if(!this.iframe || this._expanding) return;
|
||||
|
||||
this._expanding = true;
|
||||
|
||||
if(this.layout.name === "pre-paginated") {
|
||||
width = this.layout.columnWidth;
|
||||
height = this.layout.height;
|
||||
}
|
||||
// Expand Horizontally
|
||||
else if(this.settings.axis === "horizontal") {
|
||||
// Get the width of the text
|
||||
width = this.contents.textWidth();
|
||||
|
||||
if (width % this.layout.pageWidth > 0) {
|
||||
width = Math.ceil(width / this.layout.pageWidth) * this.layout.pageWidth;
|
||||
}
|
||||
|
||||
if (this.settings.forceEvenPages) {
|
||||
columns = (width / this.layout.pageWidth);
|
||||
if ( this.layout.divisor > 1 &&
|
||||
this.layout.name === "reflowable" &&
|
||||
(columns % 2 > 0)) {
|
||||
// add a blank page
|
||||
width += this.layout.pageWidth;
|
||||
}
|
||||
}
|
||||
|
||||
} // Expand Vertically
|
||||
else if(this.settings.axis === "vertical") {
|
||||
height = this.contents.textHeight();
|
||||
if (this.settings.flow === "paginated" &&
|
||||
height % this.layout.height > 0) {
|
||||
height = Math.ceil(height / this.layout.height) * this.layout.height;
|
||||
}
|
||||
}
|
||||
|
||||
// Only Resize if dimensions have changed or
|
||||
// if Frame is still hidden, so needs reframing
|
||||
if(this._needsReframe || width != this._width || height != this._height){
|
||||
this.reframe(width, height);
|
||||
}
|
||||
|
||||
this._expanding = false;
|
||||
}
|
||||
|
||||
reframe(width, height) {
|
||||
var size;
|
||||
|
||||
if(isNumber(width)){
|
||||
this.element.style.width = width + "px";
|
||||
this.iframe.style.width = width + "px";
|
||||
this._width = width;
|
||||
}
|
||||
|
||||
if(isNumber(height)){
|
||||
this.element.style.height = height + "px";
|
||||
this.iframe.style.height = height + "px";
|
||||
this._height = height;
|
||||
}
|
||||
|
||||
let widthDelta = this.prevBounds ? width - this.prevBounds.width : width;
|
||||
let heightDelta = this.prevBounds ? height - this.prevBounds.height : height;
|
||||
|
||||
size = {
|
||||
width: width,
|
||||
height: height,
|
||||
widthDelta: widthDelta,
|
||||
heightDelta: heightDelta,
|
||||
};
|
||||
|
||||
this.pane && this.pane.render();
|
||||
|
||||
requestAnimationFrame(() => {
|
||||
let mark;
|
||||
for (let m in this.marks) {
|
||||
if (this.marks.hasOwnProperty(m)) {
|
||||
mark = this.marks[m];
|
||||
this.placeMark(mark.element, mark.range);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
this.onResize(this, size);
|
||||
|
||||
this.emit(EVENTS.VIEWS.RESIZED, size);
|
||||
|
||||
this.prevBounds = size;
|
||||
|
||||
this.elementBounds = bounds(this.element);
|
||||
|
||||
}
|
||||
|
||||
|
||||
load(contents) {
|
||||
var loading = new defer();
|
||||
var loaded = loading.promise;
|
||||
|
||||
if(!this.iframe) {
|
||||
loading.reject(new Error("No Iframe Available"));
|
||||
return loaded;
|
||||
}
|
||||
|
||||
this.iframe.onload = function(event) {
|
||||
|
||||
this.onLoad(event, loading);
|
||||
|
||||
}.bind(this);
|
||||
|
||||
if (this.settings.method === "blobUrl") {
|
||||
this.blobUrl = createBlobUrl(contents, "application/xhtml+xml");
|
||||
this.iframe.src = this.blobUrl;
|
||||
this.element.appendChild(this.iframe);
|
||||
} else if(this.settings.method === "srcdoc"){
|
||||
this.iframe.srcdoc = contents;
|
||||
this.element.appendChild(this.iframe);
|
||||
} else {
|
||||
|
||||
this.element.appendChild(this.iframe);
|
||||
|
||||
this.document = this.iframe.contentDocument;
|
||||
|
||||
if(!this.document) {
|
||||
loading.reject(new Error("No Document Available"));
|
||||
return loaded;
|
||||
}
|
||||
|
||||
this.iframe.contentDocument.open();
|
||||
// For Cordova windows platform
|
||||
if(window.MSApp && MSApp.execUnsafeLocalFunction) {
|
||||
var outerThis = this;
|
||||
MSApp.execUnsafeLocalFunction(function () {
|
||||
outerThis.iframe.contentDocument.write(contents);
|
||||
});
|
||||
} else {
|
||||
this.iframe.contentDocument.write(contents);
|
||||
}
|
||||
this.iframe.contentDocument.close();
|
||||
|
||||
}
|
||||
|
||||
return loaded;
|
||||
}
|
||||
|
||||
onLoad(event, promise) {
|
||||
|
||||
this.window = this.iframe.contentWindow;
|
||||
this.document = this.iframe.contentDocument;
|
||||
|
||||
this.contents = new Contents(this.document, this.document.body, this.section.cfiBase, this.section.index);
|
||||
|
||||
this.rendering = false;
|
||||
|
||||
var link = this.document.querySelector("link[rel='canonical']");
|
||||
if (link) {
|
||||
link.setAttribute("href", this.section.canonical);
|
||||
} else {
|
||||
link = this.document.createElement("link");
|
||||
link.setAttribute("rel", "canonical");
|
||||
link.setAttribute("href", this.section.canonical);
|
||||
this.document.querySelector("head").appendChild(link);
|
||||
}
|
||||
|
||||
this.contents.on(EVENTS.CONTENTS.EXPAND, () => {
|
||||
if(this.displayed && this.iframe) {
|
||||
this.expand();
|
||||
if (this.contents) {
|
||||
this.layout.format(this.contents);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
this.contents.on(EVENTS.CONTENTS.RESIZE, (e) => {
|
||||
if(this.displayed && this.iframe) {
|
||||
this.expand();
|
||||
if (this.contents) {
|
||||
this.layout.format(this.contents);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
promise.resolve(this.contents);
|
||||
}
|
||||
|
||||
setLayout(layout) {
|
||||
this.layout = layout;
|
||||
|
||||
if (this.contents) {
|
||||
this.layout.format(this.contents);
|
||||
this.expand();
|
||||
}
|
||||
}
|
||||
|
||||
setAxis(axis) {
|
||||
|
||||
this.settings.axis = axis;
|
||||
|
||||
if(axis == "horizontal"){
|
||||
this.element.style.flex = "none";
|
||||
} else {
|
||||
this.element.style.flex = "initial";
|
||||
}
|
||||
|
||||
this.size();
|
||||
|
||||
}
|
||||
|
||||
setWritingMode(mode) {
|
||||
// this.element.style.writingMode = writingMode;
|
||||
this.writingMode = mode;
|
||||
}
|
||||
|
||||
addListeners() {
|
||||
//TODO: Add content listeners for expanding
|
||||
}
|
||||
|
||||
removeListeners(layoutFunc) {
|
||||
//TODO: remove content listeners for expanding
|
||||
}
|
||||
|
||||
display(request) {
|
||||
var displayed = new defer();
|
||||
|
||||
if (!this.displayed) {
|
||||
|
||||
this.render(request)
|
||||
.then(function () {
|
||||
|
||||
this.emit(EVENTS.VIEWS.DISPLAYED, this);
|
||||
this.onDisplayed(this);
|
||||
|
||||
this.displayed = true;
|
||||
displayed.resolve(this);
|
||||
|
||||
}.bind(this), function (err) {
|
||||
displayed.reject(err, this);
|
||||
});
|
||||
|
||||
} else {
|
||||
displayed.resolve(this);
|
||||
}
|
||||
|
||||
|
||||
return displayed.promise;
|
||||
}
|
||||
|
||||
show() {
|
||||
|
||||
this.element.style.visibility = "visible";
|
||||
|
||||
if(this.iframe){
|
||||
this.iframe.style.visibility = "visible";
|
||||
|
||||
// Remind Safari to redraw the iframe
|
||||
this.iframe.style.transform = "translateZ(0)";
|
||||
this.iframe.offsetWidth;
|
||||
this.iframe.style.transform = null;
|
||||
}
|
||||
|
||||
this.emit(EVENTS.VIEWS.SHOWN, this);
|
||||
}
|
||||
|
||||
hide() {
|
||||
// this.iframe.style.display = "none";
|
||||
this.element.style.visibility = "hidden";
|
||||
this.iframe.style.visibility = "hidden";
|
||||
|
||||
this.stopExpanding = true;
|
||||
this.emit(EVENTS.VIEWS.HIDDEN, this);
|
||||
}
|
||||
|
||||
offset() {
|
||||
return {
|
||||
top: this.element.offsetTop,
|
||||
left: this.element.offsetLeft
|
||||
}
|
||||
}
|
||||
|
||||
width() {
|
||||
return this._width;
|
||||
}
|
||||
|
||||
height() {
|
||||
return this._height;
|
||||
}
|
||||
|
||||
position() {
|
||||
return this.element.getBoundingClientRect();
|
||||
}
|
||||
|
||||
locationOf(target) {
|
||||
var parentPos = this.iframe.getBoundingClientRect();
|
||||
var targetPos = this.contents.locationOf(target, this.settings.ignoreClass);
|
||||
|
||||
return {
|
||||
"left": targetPos.left,
|
||||
"top": targetPos.top
|
||||
};
|
||||
}
|
||||
|
||||
onDisplayed(view) {
|
||||
// Stub, override with a custom functions
|
||||
}
|
||||
|
||||
onResize(view, e) {
|
||||
// Stub, override with a custom functions
|
||||
}
|
||||
|
||||
bounds(force) {
|
||||
if(force || !this.elementBounds) {
|
||||
this.elementBounds = bounds(this.element);
|
||||
}
|
||||
|
||||
return this.elementBounds;
|
||||
}
|
||||
|
||||
highlight(cfiRange, data={}, cb, className = "epubjs-hl", styles = {}) {
|
||||
if (!this.contents) {
|
||||
return;
|
||||
}
|
||||
const attributes = Object.assign({"fill": "yellow", "fill-opacity": "0.3", "mix-blend-mode": "multiply"}, styles);
|
||||
let range = this.contents.range(cfiRange);
|
||||
|
||||
let emitter = () => {
|
||||
this.emit(EVENTS.VIEWS.MARK_CLICKED, cfiRange, data);
|
||||
};
|
||||
|
||||
data["epubcfi"] = cfiRange;
|
||||
|
||||
if (!this.pane) {
|
||||
this.pane = new Pane(this.iframe, this.element);
|
||||
}
|
||||
|
||||
let m = new Highlight(range, className, data, attributes);
|
||||
let h = this.pane.addMark(m);
|
||||
|
||||
this.highlights[cfiRange] = { "mark": h, "element": h.element, "listeners": [emitter, cb] };
|
||||
|
||||
h.element.setAttribute("ref", className);
|
||||
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, className = "epubjs-ul", styles = {}) {
|
||||
if (!this.contents) {
|
||||
return;
|
||||
}
|
||||
const attributes = Object.assign({"stroke": "black", "stroke-opacity": "0.3", "mix-blend-mode": "multiply"}, styles);
|
||||
let range = this.contents.range(cfiRange);
|
||||
let emitter = () => {
|
||||
this.emit(EVENTS.VIEWS.MARK_CLICKED, cfiRange, data);
|
||||
};
|
||||
|
||||
data["epubcfi"] = cfiRange;
|
||||
|
||||
if (!this.pane) {
|
||||
this.pane = new Pane(this.iframe, this.element);
|
||||
}
|
||||
|
||||
let m = new Underline(range, className, data, attributes);
|
||||
let h = this.pane.addMark(m);
|
||||
|
||||
this.underlines[cfiRange] = { "mark": h, "element": h.element, "listeners": [emitter, cb] };
|
||||
|
||||
h.element.setAttribute("ref", className);
|
||||
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 (!this.contents) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (cfiRange in this.marks) {
|
||||
let item = this.marks[cfiRange];
|
||||
return item;
|
||||
}
|
||||
|
||||
let range = this.contents.range(cfiRange);
|
||||
if (!range) {
|
||||
return;
|
||||
}
|
||||
let container = range.commonAncestorContainer;
|
||||
let parent = (container.nodeType === 1) ? container : container.parentNode;
|
||||
|
||||
let emitter = (e) => {
|
||||
this.emit(EVENTS.VIEWS.MARK_CLICKED, cfiRange, data);
|
||||
};
|
||||
|
||||
if (range.collapsed && container.nodeType === 1) {
|
||||
range = new Range();
|
||||
range.selectNodeContents(container);
|
||||
} else if (range.collapsed) { // Webkit doesn't like collapsed ranges
|
||||
range = new Range();
|
||||
range.selectNodeContents(parent);
|
||||
}
|
||||
|
||||
let mark = this.document.createElement("a");
|
||||
mark.setAttribute("ref", "epubjs-mk");
|
||||
mark.style.position = "absolute";
|
||||
|
||||
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.placeMark(mark, range);
|
||||
|
||||
this.element.appendChild(mark);
|
||||
|
||||
this.marks[cfiRange] = { "element": mark, "range": range, "listeners": [emitter, cb] };
|
||||
|
||||
return parent;
|
||||
}
|
||||
|
||||
placeMark(element, range) {
|
||||
let top, right, left;
|
||||
|
||||
if(this.layout.name === "pre-paginated" ||
|
||||
this.settings.axis !== "horizontal") {
|
||||
let pos = range.getBoundingClientRect();
|
||||
top = pos.top;
|
||||
right = pos.right;
|
||||
} else {
|
||||
// Element might break columns, so find the left most element
|
||||
let rects = range.getClientRects();
|
||||
|
||||
let rect;
|
||||
for (var i = 0; i != rects.length; i++) {
|
||||
rect = rects[i];
|
||||
if (!left || rect.left < left) {
|
||||
left = rect.left;
|
||||
// right = rect.right;
|
||||
right = Math.ceil(left / this.layout.props.pageWidth) * this.layout.props.pageWidth - (this.layout.gap / 2);
|
||||
top = rect.top;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
element.style.top = `${top}px`;
|
||||
element.style.left = `${right}px`;
|
||||
}
|
||||
|
||||
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);
|
||||
item.element.removeEventListener("touchstart", 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);
|
||||
item.element.removeEventListener("touchstart", 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);
|
||||
item.element.removeEventListener("touchstart", 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.blobUrl) {
|
||||
revokeBlobUrl(this.blobUrl);
|
||||
}
|
||||
|
||||
if(this.displayed){
|
||||
this.displayed = false;
|
||||
|
||||
this.removeListeners();
|
||||
this.contents.destroy();
|
||||
|
||||
this.stopExpanding = true;
|
||||
this.element.removeChild(this.iframe);
|
||||
|
||||
if (this.pane) {
|
||||
this.pane.element.remove();
|
||||
this.pane = undefined;
|
||||
}
|
||||
|
||||
this.iframe = undefined;
|
||||
this.contents = undefined;
|
||||
|
||||
this._textWidth = null;
|
||||
this._textHeight = null;
|
||||
this._width = null;
|
||||
this._height = null;
|
||||
}
|
||||
|
||||
// this.element.style.height = "0px";
|
||||
// this.element.style.width = "0px";
|
||||
}
|
||||
}
|
||||
|
||||
Object.assign(IframeView.prototype, EventEmitter.prototype);
|
||||
|
||||
export default IframeView;
|
||||
432
client/static/libs/epubjs-patch/managers/views/inline.js
Normal file
432
client/static/libs/epubjs-patch/managers/views/inline.js
Normal file
|
|
@ -0,0 +1,432 @@
|
|||
import EventEmitter from "event-emitter";
|
||||
import {extend, borders, uuid, isNumber, bounds, defer, qs, parse} from "../../utils/core";
|
||||
import EpubCFI from "../../epubcfi";
|
||||
import Contents from "../../contents";
|
||||
import { EVENTS } from "../../utils/constants";
|
||||
|
||||
class InlineView {
|
||||
constructor(section, options) {
|
||||
this.settings = extend({
|
||||
ignoreClass : "",
|
||||
axis: "vertical",
|
||||
width: 0,
|
||||
height: 0,
|
||||
layout: undefined,
|
||||
globalLayoutProperties: {},
|
||||
}, options || {});
|
||||
|
||||
this.id = "epubjs-view:" + uuid();
|
||||
this.section = section;
|
||||
this.index = section.index;
|
||||
|
||||
this.element = this.container(this.settings.axis);
|
||||
|
||||
this.added = false;
|
||||
this.displayed = false;
|
||||
this.rendered = false;
|
||||
|
||||
this.width = this.settings.width;
|
||||
this.height = this.settings.height;
|
||||
|
||||
this.fixedWidth = 0;
|
||||
this.fixedHeight = 0;
|
||||
|
||||
// Blank Cfi for Parsing
|
||||
this.epubcfi = new EpubCFI();
|
||||
|
||||
this.layout = this.settings.layout;
|
||||
// Dom events to listen for
|
||||
// this.listenedEvents = ["keydown", "keyup", "keypressed", "mouseup", "mousedown", "click", "touchend", "touchstart"];
|
||||
|
||||
}
|
||||
|
||||
container(axis) {
|
||||
var element = document.createElement("div");
|
||||
|
||||
element.classList.add("epub-view");
|
||||
|
||||
// if(this.settings.axis === "horizontal") {
|
||||
// element.style.width = "auto";
|
||||
// element.style.height = "0";
|
||||
// } else {
|
||||
// element.style.width = "0";
|
||||
// element.style.height = "auto";
|
||||
// }
|
||||
|
||||
element.style.overflow = "hidden";
|
||||
|
||||
if(axis && axis == "horizontal"){
|
||||
element.style.display = "inline-block";
|
||||
} else {
|
||||
element.style.display = "block";
|
||||
}
|
||||
|
||||
return element;
|
||||
}
|
||||
|
||||
create() {
|
||||
|
||||
if(this.frame) {
|
||||
return this.frame;
|
||||
}
|
||||
|
||||
if(!this.element) {
|
||||
this.element = this.createContainer();
|
||||
}
|
||||
|
||||
this.frame = document.createElement("div");
|
||||
this.frame.id = this.id;
|
||||
this.frame.style.overflow = "hidden";
|
||||
this.frame.style.wordSpacing = "initial";
|
||||
this.frame.style.lineHeight = "initial";
|
||||
|
||||
this.resizing = true;
|
||||
|
||||
// this.frame.style.display = "none";
|
||||
this.element.style.visibility = "hidden";
|
||||
this.frame.style.visibility = "hidden";
|
||||
|
||||
if(this.settings.axis === "horizontal") {
|
||||
this.frame.style.width = "auto";
|
||||
this.frame.style.height = "0";
|
||||
} else {
|
||||
this.frame.style.width = "0";
|
||||
this.frame.style.height = "auto";
|
||||
}
|
||||
|
||||
this._width = 0;
|
||||
this._height = 0;
|
||||
|
||||
this.element.appendChild(this.frame);
|
||||
this.added = true;
|
||||
|
||||
this.elementBounds = bounds(this.element);
|
||||
|
||||
return this.frame;
|
||||
}
|
||||
|
||||
render(request, show) {
|
||||
|
||||
// view.onLayout = this.layout.format.bind(this.layout);
|
||||
this.create();
|
||||
|
||||
// Fit to size of the container, apply padding
|
||||
this.size();
|
||||
|
||||
// Render Chain
|
||||
return this.section.render(request)
|
||||
.then(function(contents){
|
||||
return this.load(contents);
|
||||
}.bind(this))
|
||||
// .then(function(doc){
|
||||
// return this.hooks.content.trigger(view, this);
|
||||
// }.bind(this))
|
||||
.then(function(){
|
||||
// this.settings.layout.format(view.contents);
|
||||
// return this.hooks.layout.trigger(view, this);
|
||||
}.bind(this))
|
||||
// .then(function(){
|
||||
// return this.display();
|
||||
// }.bind(this))
|
||||
// .then(function(){
|
||||
// return this.hooks.render.trigger(view, this);
|
||||
// }.bind(this))
|
||||
.then(function(){
|
||||
|
||||
// apply the layout function to the contents
|
||||
this.settings.layout.format(this.contents);
|
||||
|
||||
// Expand the iframe to the full size of the content
|
||||
// this.expand();
|
||||
|
||||
// Listen for events that require an expansion of the iframe
|
||||
this.addListeners();
|
||||
|
||||
if(show !== false) {
|
||||
//this.q.enqueue(function(view){
|
||||
this.show();
|
||||
//}, view);
|
||||
}
|
||||
// this.map = new Map(view, this.layout);
|
||||
//this.hooks.show.trigger(view, this);
|
||||
this.emit(EVENTS.VIEWS.RENDERED, this.section);
|
||||
|
||||
}.bind(this))
|
||||
.catch(function(e){
|
||||
this.emit(EVENTS.VIEWS.LOAD_ERROR, e);
|
||||
}.bind(this));
|
||||
|
||||
}
|
||||
|
||||
// Determine locks base on settings
|
||||
size(_width, _height) {
|
||||
var width = _width || this.settings.width;
|
||||
var height = _height || this.settings.height;
|
||||
|
||||
if(this.layout.name === "pre-paginated") {
|
||||
// TODO: check if these are different than the size set in chapter
|
||||
this.lock("both", width, height);
|
||||
} else if(this.settings.axis === "horizontal") {
|
||||
this.lock("height", width, height);
|
||||
} else {
|
||||
this.lock("width", width, height);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Lock an axis to element dimensions, taking borders into account
|
||||
lock(what, width, height) {
|
||||
var elBorders = borders(this.element);
|
||||
var iframeBorders;
|
||||
|
||||
if(this.frame) {
|
||||
iframeBorders = borders(this.frame);
|
||||
} else {
|
||||
iframeBorders = {width: 0, height: 0};
|
||||
}
|
||||
|
||||
if(what == "width" && isNumber(width)){
|
||||
this.lockedWidth = width - elBorders.width - iframeBorders.width;
|
||||
this.resize(this.lockedWidth, false); // width keeps ratio correct
|
||||
}
|
||||
|
||||
if(what == "height" && isNumber(height)){
|
||||
this.lockedHeight = height - elBorders.height - iframeBorders.height;
|
||||
this.resize(false, this.lockedHeight);
|
||||
}
|
||||
|
||||
if(what === "both" &&
|
||||
isNumber(width) &&
|
||||
isNumber(height)){
|
||||
|
||||
this.lockedWidth = width - elBorders.width - iframeBorders.width;
|
||||
this.lockedHeight = height - elBorders.height - iframeBorders.height;
|
||||
|
||||
this.resize(this.lockedWidth, this.lockedHeight);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Resize a single axis based on content dimensions
|
||||
expand(force) {
|
||||
var width = this.lockedWidth;
|
||||
var height = this.lockedHeight;
|
||||
|
||||
var textWidth, textHeight;
|
||||
|
||||
if(!this.frame || this._expanding) return;
|
||||
|
||||
this._expanding = true;
|
||||
|
||||
// Expand Horizontally
|
||||
if(this.settings.axis === "horizontal") {
|
||||
width = this.contentWidth(textWidth);
|
||||
} // Expand Vertically
|
||||
else if(this.settings.axis === "vertical") {
|
||||
height = this.contentHeight(textHeight);
|
||||
}
|
||||
|
||||
// Only Resize if dimensions have changed or
|
||||
// if Frame is still hidden, so needs reframing
|
||||
if(this._needsReframe || width != this._width || height != this._height){
|
||||
this.resize(width, height);
|
||||
}
|
||||
|
||||
this._expanding = false;
|
||||
}
|
||||
|
||||
contentWidth(min) {
|
||||
return this.frame.scrollWidth;
|
||||
}
|
||||
|
||||
contentHeight(min) {
|
||||
return this.frame.scrollHeight;
|
||||
}
|
||||
|
||||
|
||||
resize(width, height) {
|
||||
|
||||
if(!this.frame) return;
|
||||
|
||||
if(isNumber(width)){
|
||||
this.frame.style.width = width + "px";
|
||||
this._width = width;
|
||||
}
|
||||
|
||||
if(isNumber(height)){
|
||||
this.frame.style.height = height + "px";
|
||||
this._height = height;
|
||||
}
|
||||
|
||||
this.prevBounds = this.elementBounds;
|
||||
|
||||
this.elementBounds = bounds(this.element);
|
||||
|
||||
let size = {
|
||||
width: this.elementBounds.width,
|
||||
height: this.elementBounds.height,
|
||||
widthDelta: this.elementBounds.width - this.prevBounds.width,
|
||||
heightDelta: this.elementBounds.height - this.prevBounds.height,
|
||||
};
|
||||
|
||||
this.onResize(this, size);
|
||||
|
||||
this.emit(EVENTS.VIEWS.RESIZED, size);
|
||||
|
||||
}
|
||||
|
||||
|
||||
load(contents) {
|
||||
var loading = new defer();
|
||||
var loaded = loading.promise;
|
||||
var doc = parse(contents, "text/html");
|
||||
var body = qs(doc, "body");
|
||||
|
||||
/*
|
||||
var srcs = doc.querySelectorAll("[src]");
|
||||
|
||||
Array.prototype.slice.call(srcs)
|
||||
.forEach(function(item) {
|
||||
var src = item.getAttribute("src");
|
||||
var assetUri = URI(src);
|
||||
var origin = assetUri.origin();
|
||||
var absoluteUri;
|
||||
|
||||
if (!origin) {
|
||||
absoluteUri = assetUri.absoluteTo(this.section.url);
|
||||
item.src = absoluteUri;
|
||||
}
|
||||
}.bind(this));
|
||||
*/
|
||||
this.frame.innerHTML = body.innerHTML;
|
||||
|
||||
this.document = this.frame.ownerDocument;
|
||||
this.window = this.document.defaultView;
|
||||
|
||||
this.contents = new Contents(this.document, this.frame);
|
||||
|
||||
this.rendering = false;
|
||||
|
||||
loading.resolve(this.contents);
|
||||
|
||||
|
||||
return loaded;
|
||||
}
|
||||
|
||||
setLayout(layout) {
|
||||
this.layout = layout;
|
||||
}
|
||||
|
||||
|
||||
resizeListenters() {
|
||||
// Test size again
|
||||
// clearTimeout(this.expanding);
|
||||
// this.expanding = setTimeout(this.expand.bind(this), 350);
|
||||
}
|
||||
|
||||
addListeners() {
|
||||
//TODO: Add content listeners for expanding
|
||||
}
|
||||
|
||||
removeListeners(layoutFunc) {
|
||||
//TODO: remove content listeners for expanding
|
||||
}
|
||||
|
||||
display(request) {
|
||||
var displayed = new defer();
|
||||
|
||||
if (!this.displayed) {
|
||||
|
||||
this.render(request).then(function () {
|
||||
|
||||
this.emit(EVENTS.VIEWS.DISPLAYED, this);
|
||||
this.onDisplayed(this);
|
||||
|
||||
this.displayed = true;
|
||||
|
||||
displayed.resolve(this);
|
||||
|
||||
}.bind(this));
|
||||
|
||||
} else {
|
||||
displayed.resolve(this);
|
||||
}
|
||||
|
||||
|
||||
return displayed.promise;
|
||||
}
|
||||
|
||||
show() {
|
||||
|
||||
this.element.style.visibility = "visible";
|
||||
|
||||
if(this.frame){
|
||||
this.frame.style.visibility = "visible";
|
||||
}
|
||||
|
||||
this.emit(EVENTS.VIEWS.SHOWN, this);
|
||||
}
|
||||
|
||||
hide() {
|
||||
// this.frame.style.display = "none";
|
||||
this.element.style.visibility = "hidden";
|
||||
this.frame.style.visibility = "hidden";
|
||||
|
||||
this.stopExpanding = true;
|
||||
this.emit(EVENTS.VIEWS.HIDDEN, this);
|
||||
}
|
||||
|
||||
position() {
|
||||
return this.element.getBoundingClientRect();
|
||||
}
|
||||
|
||||
locationOf(target) {
|
||||
var parentPos = this.frame.getBoundingClientRect();
|
||||
var targetPos = this.contents.locationOf(target, this.settings.ignoreClass);
|
||||
|
||||
return {
|
||||
"left": window.scrollX + parentPos.left + targetPos.left,
|
||||
"top": window.scrollY + parentPos.top + targetPos.top
|
||||
};
|
||||
}
|
||||
|
||||
onDisplayed(view) {
|
||||
// Stub, override with a custom functions
|
||||
}
|
||||
|
||||
onResize(view, e) {
|
||||
// Stub, override with a custom functions
|
||||
}
|
||||
|
||||
bounds() {
|
||||
if(!this.elementBounds) {
|
||||
this.elementBounds = bounds(this.element);
|
||||
}
|
||||
return this.elementBounds;
|
||||
}
|
||||
|
||||
destroy() {
|
||||
|
||||
if(this.displayed){
|
||||
this.displayed = false;
|
||||
|
||||
this.removeListeners();
|
||||
|
||||
this.stopExpanding = true;
|
||||
this.element.removeChild(this.frame);
|
||||
this.displayed = false;
|
||||
this.frame = null;
|
||||
|
||||
this._textWidth = null;
|
||||
this._textHeight = null;
|
||||
this._width = null;
|
||||
this._height = null;
|
||||
}
|
||||
// this.element.style.height = "0px";
|
||||
// this.element.style.width = "0px";
|
||||
}
|
||||
}
|
||||
|
||||
EventEmitter(InlineView.prototype);
|
||||
|
||||
export default InlineView;
|
||||
Loading…
Add table
Add a link
Reference in a new issue