1
0
Fork 0
mirror of https://github.com/futurepress/epub.js.git synced 2025-10-03 14:59:18 +02:00

Documentation clean up

This commit is contained in:
Fred Chasen 2017-11-07 16:23:23 -08:00
parent 7ff4c8e37c
commit 77846842cd
20 changed files with 1090 additions and 8239 deletions

View file

@ -1,28 +1,9 @@
// 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
* @param {Rendition} rendition
* @class
*/
class Annotations {
@ -39,6 +20,14 @@ class Annotations {
this.rendition.hooks.unloaded.register(this.clear.bind(this));
}
/**
* Add an annotation to store
* @param {string} type Type of annotation to add: "highlight", "underline", "mark"
* @param {EpubCFI} cfiRange EpubCFI range to attach annotation to
* @param {object} data Data to assign to annotation
* @param {function} [cb] Callback after annotation is added
* @returns {Annotation} annotation
*/
add (type, cfiRange, data, cb) {
let hash = encodeURI(cfiRange);
let cfi = new EpubCFI(cfiRange);
@ -70,6 +59,11 @@ class Annotations {
return annotation;
}
/**
* Remove an annotation from store
* @param {EpubCFI} cfiRange EpubCFI range the annotation is attached to
* @param {string} type Type of annotation to add: "highlight", "underline", "mark"
*/
remove (cfiRange, type) {
let hash = encodeURI(cfiRange);
@ -92,30 +86,65 @@ class Annotations {
}
}
/**
* Remove an annotations by Section Index
* @private
*/
_removeFromAnnotationBySectionIndex (sectionIndex, hash) {
this._annotationsBySectionIndex[sectionIndex] = this._annotationsAt(sectionIndex).filter(h => h !== hash);
}
/**
* Get annotations by Section Index
* @private
*/
_annotationsAt (index) {
return this._annotationsBySectionIndex[index];
}
/**
* Add a highlight to the store
* @param {EpubCFI} cfiRange EpubCFI range to attach annotation to
* @param {object} data Data to assign to annotation
* @param {function} cb Callback after annotation is added
*/
highlight (cfiRange, data, cb) {
this.add("highlight", cfiRange, data, cb);
}
/**
* Add a underline to the store
* @param {EpubCFI} cfiRange EpubCFI range to attach annotation to
* @param {object} data Data to assign to annotation
* @param {function} cb Callback after annotation is added
*/
underline (cfiRange, data, cb) {
this.add("underline", cfiRange, data, cb);
}
/**
* Add a mark to the store
* @param {EpubCFI} cfiRange EpubCFI range to attach annotation to
* @param {object} data Data to assign to annotation
* @param {function} cb Callback after annotation is added
*/
mark (cfiRange, data, cb) {
this.add("mark", cfiRange, data, cb);
}
/**
* iterate over annotations in the store
*/
each () {
return this._annotations.forEach.apply(this._annotations, arguments);
}
/**
* Hook for injecting annotation into a view
* @param {View} view
* @private
*/
inject (view) {
let sectionIndex = view.index;
if (sectionIndex in this._annotationsBySectionIndex) {
@ -127,6 +156,11 @@ class Annotations {
}
}
/**
* Hook for removing annotation from a view
* @param {View} view
* @private
*/
clear (view) {
let sectionIndex = view.index;
if (sectionIndex in this._annotationsBySectionIndex) {
@ -138,16 +172,35 @@ class Annotations {
}
}
/**
* [Not Implemented] Show annotations
* @TODO: needs implementation in View
*/
show () {
}
/**
* [Not Implemented] Hide annotations
* @TODO: needs implementation in View
*/
hide () {
}
}
/**
* Annotation object
* @class
* @param {object} options
* @param {string} options.type Type of annotation to add: "highlight", "underline", "mark"
* @param {EpubCFI} options.cfiRange EpubCFI range to attach annotation to
* @param {object} options.data Data to assign to annotation
* @param {int} options.sectionIndex Index in the Spine of the Section annotation belongs to
* @param {function} [options.cb] Callback after annotation is added
* @returns {Annotation} annotation
*/
class Annotation {
constructor ({
@ -165,18 +218,21 @@ class Annotation {
this.cb = cb;
}
/**
* Update stored data
* @param {object} data
*/
update (data) {
this.data = data;
}
/**
* Add to a view
* @param {View} view
*/
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);
@ -191,6 +247,10 @@ class Annotation {
return result;
}
/**
* Remove from a view
* @param {View} view
*/
detach (view) {
let {cfiRange, type} = this;
let result;
@ -210,8 +270,12 @@ class Annotation {
return result;
}
/**
* [Not Implemented] Get text of an annotation
* @TODO: needs implementation in contents
*/
text () {
// TODO: needs implementation in contents
}
}

View file

@ -59,9 +59,9 @@ class Archive {
}
/**
* Request
* Request a url from the archive
* @param {string} url a url to request from the archive
* @param {[string]} type specify the type of the returned result
* @param {string} [type] specify the type of the returned result
* @return {Promise}
*/
request(url, type){
@ -98,7 +98,7 @@ class Archive {
* Handle the response from request
* @private
* @param {any} response
* @param {[string]} type
* @param {string} [type]
* @return {any} the parsed result
*/
handleResponse(response, type){
@ -128,7 +128,7 @@ class Archive {
/**
* Get a Blob from Archive by Url
* @param {string} url
* @param {[string]} mimeType
* @param {string} [mimeType]
* @return {Blob}
*/
getBlob(url, mimeType){
@ -146,7 +146,7 @@ class Archive {
/**
* Get Text from Archive by Url
* @param {string} url
* @param {[string]} encoding
* @param {string} [encoding]
* @return {string}
*/
getText(url, encoding){
@ -163,7 +163,7 @@ class Archive {
/**
* Get a base64 encoded result from Archive by Url
* @param {string} url
* @param {[string]} mimeType
* @param {string} [mimeType]
* @return {string} base64 encoded
*/
getBase64(url, mimeType){
@ -181,7 +181,7 @@ class Archive {
/**
* Create a Url from an unarchived item
* @param {string} url
* @param {[object]} options.base64 use base64 encoding or blob url
* @param {object} [options.base64] use base64 encoding or blob url
* @return {Promise} url promise with Url string
*/
createUrl(url, options){

View file

@ -1,5 +1,4 @@
import EventEmitter from "event-emitter";
// import path from "path";
import {extend, defer} from "./utils/core";
import Url from "./utils/url";
import Path from "./utils/path";
@ -19,16 +18,27 @@ import { EVENTS } from "./utils/constants";
const CONTAINER_PATH = "META-INF/container.xml";
const EPUBJS_VERSION = "0.3";
const INPUT_TYPE = {
BINARY: "binary",
BASE64: "base64",
EPUB: "epub",
OPF: "opf",
MANIFEST: "json",
DIRECTORY: "directory"
};
/**
* Creates a new Book
* An Epub representation with methods for the loading, parsing and manipulation
* of its contents.
* @class
* @param {string} url
* @param {object} options
* @param {method} options.requestMethod a request function to use instead of the default
* @param {string} [url]
* @param {object} [options]
* @param {method} [options.requestMethod] a request function to use instead of the default
* @param {boolean} [options.requestCredentials=undefined] send the xhr request withCredentials
* @param {object} [options.requestHeaders=undefined] send the xhr request headers
* @param {string} [options.encoding=binary] optional to pass 'binary' or base64' for archived Epubs
* @param {string} [options.replacements=none] use base64, blobUrl, or none for replacing assets in archived Epubs
* @param {method} [options.canonical] optional function to determine canonical urls for a path
* @returns {Book}
* @example new Book("/path/to/book.epub", {})
* @example new Book({ replacements: "blobUrl" })
@ -57,7 +67,8 @@ class Book {
// Promises
this.opening = new defer();
/**
* @property {promise} opened returns after the book is loaded
* @member {promise} opened returns after the book is loaded
* @memberof Book
*/
this.opened = this.opening.promise;
this.isOpen = false;
@ -82,9 +93,9 @@ class Book {
resources: this.loading.resources.promise
};
// this.ready = RSVP.hash(this.loaded);
/**
* @property {promise} ready returns after the book is loaded and parsed
* @member {promise} ready returns after the book is loaded and parsed
* @memberof Book
* @private
*/
this.ready = Promise.all([
@ -102,70 +113,93 @@ class Book {
// this._q = queue(this);
/**
* @property {method} request
* @member {method} request
* @memberof Book
* @private
*/
this.request = this.settings.requestMethod || request;
/**
* @property {Spine} spine
* @member {Spine} spine
* @memberof Book
*/
this.spine = new Spine();
/**
* @property {Locations} locations
* @member {Locations} locations
* @memberof Book
*/
this.locations = new Locations(this.spine, this.load.bind(this));
/**
* @property {Navigation} navigation
* @member {Navigation} navigation
* @memberof Book
*/
this.navigation = undefined;
/**
* @property {PageList} pagelist
* @member {PageList} pagelist
* @memberof Book
*/
this.pageList = undefined;
/**
* @property {Url} url
* @member {Url} url
* @memberof Book
* @private
*/
this.url = undefined;
/**
* @property {Path} path
* @member {Path} path
* @memberof Book
* @private
*/
this.path = undefined;
/**
* @property {boolean} archived
* @member {boolean} archived
* @memberof Book
* @private
*/
this.archived = false;
/**
* @property {Archive} archive
* @member {Archive} archive
* @memberof Book
* @private
*/
this.archive = undefined;
/**
* @property {Resources} resources
* @member {Resources} resources
* @memberof Book
* @private
*/
this.resources = undefined;
/**
* @property {Rendition} rendition
* @member {Rendition} rendition
* @memberof Book
* @private
*/
this.rendition = undefined;
/**
* @member {Container} container
* @memberof Book
* @private
*/
this.container = undefined;
/**
* @member {Packaging} packaging
* @memberof Book
* @private
*/
this.packaging = undefined;
this.toc = undefined;
// this.toc = undefined;
if(url) {
this.open(url).catch((error) => {
@ -177,8 +211,8 @@ class Book {
/**
* Open a epub or url
* @param {string} input URL, Path or ArrayBuffer
* @param {string} [what] to force opening
* @param {string | ArrayBuffer} input Url, Path or ArrayBuffer
* @param {string} [what="binary", "base64", "epub", "opf", "json", "directory"] force opening as a certain type
* @returns {Promise} of when the book has been loaded
* @example book.open("/path/to/book.epub")
*/
@ -186,23 +220,23 @@ class Book {
var opening;
var type = what || this.determineType(input);
if (type === "binary") {
if (type === INPUT_TYPE.BINARY) {
this.archived = true;
this.url = new Url("/", "");
opening = this.openEpub(input);
} else if (type === "base64") {
} else if (type === INPUT_TYPE.BASE64) {
this.archived = true;
this.url = new Url("/", "");
opening = this.openEpub(input, type);
} else if (type === "epub") {
} else if (type === INPUT_TYPE.EPUB) {
this.archived = true;
this.url = new Url("/", "");
opening = this.request(input, "binary")
.then(this.openEpub.bind(this));
} else if(type == "opf") {
} else if(type == INPUT_TYPE.OPF) {
this.url = new Url(input);
opening = this.openPackaging(this.url.Path.toString());
} else if(type == "json") {
} else if(type == INPUT_TYPE.MANIFEST) {
this.url = new Url(input);
opening = this.openManifest(this.url.Path.toString());
} else {
@ -218,7 +252,7 @@ class Book {
* Open an archived epub
* @private
* @param {binary} data
* @param {[string]} encoding
* @param {string} [encoding]
* @return {Promise}
*/
openEpub(data, encoding) {
@ -296,7 +330,7 @@ class Book {
/**
* Resolve a path to it's absolute position in the Book
* @param {string} path
* @param {[boolean]} absolute force resolving the full URL
* @param {boolean} [absolute] force resolving the full URL
* @return {string} the resolved path string
*/
resolve(path, absolute) {
@ -354,11 +388,11 @@ class Book {
var extension;
if (this.settings.encoding === "base64") {
return "base64";
return INPUT_TYPE.BASE64;
}
if(typeof(input) != "string") {
return "binary";
return INPUT_TYPE.BINARY;
}
url = new Url(input);
@ -366,19 +400,19 @@ class Book {
extension = path.extension;
if (!extension) {
return "directory";
return INPUT_TYPE.DIRECTORY;
}
if(extension === "epub"){
return "epub";
return INPUT_TYPE.EPUB;
}
if(extension === "opf"){
return "opf";
return INPUT_TYPE.OPF;
}
if(extension === "json"){
return "json";
return INPUT_TYPE.MANIFEST;
}
}
@ -401,7 +435,7 @@ class Book {
});
this.loadNavigation(this.package).then(() => {
this.toc = this.navigation.toc;
// this.toc = this.navigation.toc;
this.loading.navigation.resolve(this.navigation);
});
@ -472,24 +506,22 @@ class Book {
}
/**
* Alias for book.spine.get
* Gets a Section of the Book from the Spine
* Alias for `book.spine.get`
* @param {string} target
* @return {Section}
*/
section(target) {
return this.spine.get(target);
}
/**
* Sugar to render a book
* @param {element} element element to add the views to
* @param {[object]} options
* Sugar to render a book to an element
* @param {element | string} element element or string to add a rendition to
* @param {object} [options]
* @return {Rendition}
*/
renderTo(element, options) {
// var renderMethod = (options && options.method) ?
// options.method :
// "single";
this.rendition = new Rendition(this, options);
this.rendition.attachTo(element);
@ -516,7 +548,7 @@ class Book {
* Unarchive a zipped epub
* @private
* @param {binary} input epub data
* @param {[string]} encoding
* @param {string} [encoding]
* @return {Archive}
*/
unarchive(input, encoding) {
@ -539,13 +571,11 @@ class Book {
}
});
return retrieved;
}
/**
* load replacement urls
* Load replacement urls
* @private
* @return {Promise} completed loading urls
*/
@ -582,7 +612,7 @@ class Book {
/**
* Generates the Book Key using the identifer in the manifest or other string provided
* @param {[string]} identifier to use instead of metadata identifier
* @param {string} [identifier] to use instead of metadata identifier
* @return {string} key
*/
key(identifier) {
@ -590,6 +620,9 @@ class Book {
return `epubjs:${EPUBJS_VERSION}:${ident}`;
}
/**
* Destroy the Book and all associated objects
*/
destroy() {
this.opened = undefined;
this.loading = undefined;
@ -621,7 +654,6 @@ class Book {
this.url = undefined;
this.path = undefined;
this.archived = false;
this.toc = undefined;
}
}

View file

@ -4,7 +4,7 @@ import {qs} from "./utils/core";
/**
* Handles Parsing and Accessing an Epub Container
* @class
* @param {[document]} containerDocument xml document
* @param {document} [containerDocument] xml document
*/
class Container {
constructor(containerDocument) {

View file

@ -11,6 +11,14 @@ const isWebkit = !isChrome && /AppleWebKit/.test(navigator.userAgent);
const ELEMENT_NODE = 1;
const TEXT_NODE = 3;
/**
* Handles DOM manipulation, queries and events for View contents
* @class
* @param {document} doc Document
* @param {element} content Parent Element (typically Body)
* @param {string} cfiBase Section component of CFIs
* @param {number} sectionIndex Index in Spine of Conntent's Section
*/
class Contents {
constructor(doc, content, cfiBase, sectionIndex) {
// Blank Cfi for Parsing
@ -34,10 +42,18 @@ class Contents {
this.listeners();
}
/**
* Get DOM events that are listened for and passed along
*/
static get listenedEvents() {
return DOM_EVENTS;
}
/**
* Get or Set width
* @param {number} [w]
* @returns {number} width
*/
width(w) {
// var frame = this.documentElement;
var frame = this.content;
@ -56,6 +72,11 @@ class Contents {
}
/**
* Get or Set height
* @param {number} [h]
* @returns {number} height
*/
height(h) {
// var frame = this.documentElement;
var frame = this.content;
@ -73,6 +94,11 @@ class Contents {
}
/**
* Get or Set width of the contents
* @param {number} [w]
* @returns {number} width
*/
contentWidth(w) {
var content = this.content || this.document.body;
@ -90,6 +116,11 @@ class Contents {
}
/**
* Get or Set height of the contents
* @param {number} [h]
* @returns {number} height
*/
contentHeight(h) {
var content = this.content || this.document.body;
@ -106,6 +137,10 @@ class Contents {
}
/**
* Get the width of the text using Range
* @returns {number} width
*/
textWidth() {
let width;
let range = this.document.createRange();
@ -125,6 +160,10 @@ class Contents {
return Math.round(width);
}
/**
* Get the height of the text using Range
* @returns {number} height
*/
textHeight() {
let height;
let range = this.document.createRange();
@ -142,18 +181,30 @@ class Contents {
return Math.round(height);
}
/**
* Get documentElement scrollWidth
* @returns {number} width
*/
scrollWidth() {
var width = this.documentElement.scrollWidth;
return width;
}
/**
* Get documentElement scrollHeight
* @returns {number} height
*/
scrollHeight() {
var height = this.documentElement.scrollHeight;
return height;
}
/**
* Set overflow css style of the contents
* @param {string} [overflow]
*/
overflow(overflow) {
if (overflow) {
@ -163,6 +214,10 @@ class Contents {
return this.window.getComputedStyle(this.documentElement)["overflow"];
}
/**
* Set overflowX css style of the documentElement
* @param {string} [overflow]
*/
overflowX(overflow) {
if (overflow) {
@ -172,6 +227,10 @@ class Contents {
return this.window.getComputedStyle(this.documentElement)["overflowX"];
}
/**
* Set overflowY css style of the documentElement
* @param {string} [overflow]
*/
overflowY(overflow) {
if (overflow) {
@ -181,6 +240,12 @@ class Contents {
return this.window.getComputedStyle(this.documentElement)["overflowY"];
}
/**
* Set Css styles on the contents element (typically Body)
* @param {string} property
* @param {string} value
* @param {boolean} [priority] set as "important"
*/
css(property, value, priority) {
var content = this.content || this.document.body;
@ -191,6 +256,16 @@ class Contents {
return this.window.getComputedStyle(content)[property];
}
/**
* Get or Set the viewport element
* @param {object} [options]
* @param {string} [options.width]
* @param {string} [options.height]
* @param {string} [options.scale]
* @param {string} [options.minimum]
* @param {string} [options.maximum]
* @param {string} [options.scalable]
*/
viewport(options) {
var _width, _height, _scale, _minimum, _maximum, _scalable;
// var width, height, scale, minimum, maximum, scalable;
@ -288,32 +363,18 @@ class Contents {
return settings;
}
// layout(layoutFunc) {
//
// this.iframe.style.display = "inline-block";
//
// // Reset Body Styles
// this.content.style.margin = "0";
// //this.document.body.style.display = "inline-block";
// //this.document.documentElement.style.width = "auto";
//
// if(layoutFunc){
// layoutFunc(this);
// }
//
// this.onLayout(this);
//
// };
//
// onLayout(view) {
// // stub
// };
/**
* Event emitter for when the contents has expanded
* @private
*/
expand() {
this.emit(EVENTS.CONTENTS.EXPAND);
}
/**
* Add DOM listeners
* @private
*/
listeners() {
this.imageLoadListeners();
@ -335,6 +396,10 @@ class Contents {
this.linksHandler();
}
/**
* Remove DOM listeners
* @private
*/
removeListeners() {
this.removeEventListeners();
@ -344,6 +409,11 @@ class Contents {
clearTimeout(this.expanding);
}
/**
* Check if size of contents has changed and
* emit 'resize' event if it has.
* @private
*/
resizeCheck() {
let width = this.textWidth();
let height = this.textHeight();
@ -360,6 +430,10 @@ class Contents {
}
}
/**
* Poll for resize detection
* @private
*/
resizeListeners() {
var width, height;
// Test size again
@ -370,6 +444,10 @@ class Contents {
this.expanding = setTimeout(this.resizeListeners.bind(this), 350);
}
/**
* Use css transitions to detect resize
* @private
*/
transitionListeners() {
let body = this.content;
@ -381,13 +459,16 @@ class Contents {
this.document.addEventListener('transitionend', this.resizeCheck.bind(this));
}
//https://github.com/tylergaw/media-query-events/blob/master/js/mq-events.js
/**
* Listen for media query changes and emit 'expand' event
* Adapted from: https://github.com/tylergaw/media-query-events/blob/master/js/mq-events.js
* @private
*/
mediaQueryListeners() {
var sheets = this.document.styleSheets;
var mediaChangeHandler = function(m){
if(m.matches && !this._expanding) {
setTimeout(this.expand.bind(this), 1);
// this.expand();
}
}.bind(this);
@ -411,6 +492,10 @@ class Contents {
}
}
/**
* Use MutationObserver to listen for changes in the DOM and check for resize
* @private
*/
resizeObservers() {
// create an observer instance
this.observer = new MutationObserver((mutations) => {
@ -437,22 +522,36 @@ class Contents {
}
}
/**
* Listen for font load and check for resize when loaded
* @private
*/
fontLoadListeners(target) {
if (!this.document || !this.document.fonts) {
return;
}
this.document.fonts.ready.then(function () {
this.expand();
this.resizeCheck();
}.bind(this));
}
/**
* Get the documentElement
* @returns {element} documentElement
*/
root() {
if(!this.document) return null;
return this.document.documentElement;
}
/**
* Get the location offset of a EpubCFI or an #id
* @param {string | EpubCFI} target
* @param {string} [ignoreClass] for the cfi
* @returns { {left: Number, top: Number }
*/
locationOf(target, ignoreClass) {
var position;
var targetPos = {"left": 0, "top": 0};
@ -517,6 +616,10 @@ class Contents {
return targetPos;
}
/**
* Append a stylesheet link to the document head
* @param {string} src url
*/
addStylesheet(src) {
return new Promise(function(resolve, reject){
var $stylesheet;
@ -553,8 +656,12 @@ class Contents {
}.bind(this));
}
// Array: https://developer.mozilla.org/en-US/docs/Web/API/CSSStyleSheet/insertRule
// Object: https://github.com/desirable-objects/json-to-css
/**
* Append stylesheet rules to a generate stylesheet
* Array: https://developer.mozilla.org/en-US/docs/Web/API/CSSStyleSheet/insertRule
* Object: https://github.com/desirable-objects/json-to-css
* @param {array | object} rules
*/
addStylesheetRules(rules) {
var styleEl;
var styleSheet;
@ -615,6 +722,11 @@ class Contents {
}
}
/**
* Append a script tag to the document head
* @param {string} src url
* @returns {Promise} loaded
*/
addScript(src) {
return new Promise(function(resolve, reject){
@ -644,6 +756,10 @@ class Contents {
}.bind(this));
}
/**
* Add a class to the contents container
* @param {string} className
*/
addClass(className) {
var content;
@ -657,6 +773,10 @@ class Contents {
}
/**
* Remove a class from the contents container
* @param {string} removeClass
*/
removeClass(className) {
var content;
@ -670,6 +790,10 @@ class Contents {
}
/**
* Add DOM event listeners
* @private
*/
addEventListeners(){
if(!this.document) {
return;
@ -681,6 +805,10 @@ class Contents {
}
/**
* Remove DOM event listeners
* @private
*/
removeEventListeners(){
if(!this.document) {
return;
@ -691,11 +819,18 @@ class Contents {
}
// Pass browser events
/**
* Emit passed browser events
* @private
*/
triggerEvent(e){
this.emit(e.type, e);
}
/**
* Add listener for text selection
* @private
*/
addSelectionListeners(){
if(!this.document) {
return;
@ -703,6 +838,10 @@ class Contents {
this.document.addEventListener("selectionchange", this.onSelectionChange.bind(this), false);
}
/**
* Remove listener for text selection
* @private
*/
removeSelectionListeners(){
if(!this.document) {
return;
@ -710,6 +849,10 @@ class Contents {
this.document.removeEventListener("selectionchange", this.onSelectionChange, false);
}
/**
* Handle getting text on selection
* @private
*/
onSelectionChange(e){
if (this.selectionEndTimeout) {
clearTimeout(this.selectionEndTimeout);
@ -720,6 +863,10 @@ class Contents {
}.bind(this), 250);
}
/**
* Emit event on text selection
* @private
*/
triggerSelectedEvent(selection){
var range, cfirange;
@ -734,24 +881,48 @@ class Contents {
}
}
/**
* Get a Dom Range from EpubCFI
* @param {EpubCFI} _cfi
* @param {string} [ignoreClass]
* @returns {Range} range
*/
range(_cfi, ignoreClass){
var cfi = new EpubCFI(_cfi);
return cfi.toRange(this.document, ignoreClass);
}
/**
* Get an EpubCFI from a Dom Range
* @param {Range} range
* @param {string} [ignoreClass]
* @returns {EpubCFI} cfi
*/
cfiFromRange(range, ignoreClass){
return new EpubCFI(range, this.cfiBase, ignoreClass).toString();
}
/**
* Get an EpubCFI from a Dom node
* @param {node} node
* @param {string} [ignoreClass]
* @returns {EpubCFI} cfi
*/
cfiFromNode(node, ignoreClass){
return new EpubCFI(node, this.cfiBase, ignoreClass).toString();
}
// TODO: find where this is used - remove?
map(layout){
var map = new Mapping(layout);
return map.section();
}
/**
* Size the contents to a given width and height
* @param {number} [width]
* @param {number} [height]
*/
size(width, height){
var viewport = { scale: 1.0, scalable: "no" };
@ -775,6 +946,13 @@ class Contents {
this.viewport(viewport);
}
/**
* Apply columns to the contents for pagination
* @param {number} width
* @param {number} height
* @param {number} columnWidth
* @param {number} gap
*/
columns(width, height, columnWidth, gap){
var COLUMN_AXIS = prefixed("column-axis");
var COLUMN_GAP = prefixed("column-gap");
@ -810,6 +988,12 @@ class Contents {
this.css(COLUMN_WIDTH, columnWidth+"px");
}
/**
* Scale contents from center
* @param {number} scale
* @param {number} offsetX
* @param {number} offsetY
*/
scaler(scale, offsetX, offsetY){
var scaleStr = "scale(" + scale + ")";
var translateStr = "";
@ -823,6 +1007,11 @@ class Contents {
this.css("transform", scaleStr + translateStr);
}
/**
* Fit contents into a fixed width and height
* @param {number} width
* @param {number} height
*/
fit(width, height){
var viewport = this.viewport();
var widthScale = width / parseInt(viewport.width);
@ -843,6 +1032,10 @@ class Contents {
this.css("background-color", "transparent");
}
/**
* Set the direction of the text
* @param {string} [dir="ltr"] "rtl" | "ltr"
*/
direction(dir) {
if (this.documentElement) {
this.documentElement.style["direction"] = dir;
@ -855,12 +1048,20 @@ class Contents {
return mapping.page(this, cfiBase, start, end);
}
/**
* Emit event when link in content is clicked
* @private
*/
linksHandler() {
replaceLinks(this.content, (href) => {
this.emit(EVENTS.CONTENTS.LINK_CLICKED, href);
});
}
/**
* Set the writingMode of the text
* @param {string} [mode="horizontal-tb"] "horizontal-tb" | "vertical-rl" | "vertical-lr"
*/
writingMode(mode) {
let WRITING_MODE = prefixed("writing-mode");
@ -871,6 +1072,11 @@ class Contents {
return this.window.getComputedStyle(this.documentElement)[WRITING_MODE] || '';
}
/**
* Set the layoutStyle of the content
* @param {string} [style="paginated"] "scrolling" | "paginated"
* @private
*/
layoutStyle(style) {
if (style) {
@ -881,6 +1087,12 @@ class Contents {
return this._layoutStyle || "paginated";
}
/**
* Add the epubReadingSystem object to the navigator
* @param {string} name
* @param {string} version
* @private
*/
epubReadingSystem(name, version) {
navigator.epubReadingSystem = {
name: name,

View file

@ -1,10 +1,14 @@
import Book from "./book";
import EpubCFI from "./epubcfi";
import Rendition from "./rendition";
import EpubCFI from "./epubcfi";
import Contents from "./contents";
import * as core from "./utils/core";
import '../libs/url/url-polyfill'
import IframeView from "./managers/views/iframe";
import DefaultViewManager from "./managers/default";
import ContinuousViewManager from "./managers/continuous";
/**
* Creates a new Book
* @param {string|ArrayBuffer} url URL, Path or ArrayBuffer
@ -30,7 +34,7 @@ ePub.utils = core;
ePub.ViewManagers = {};
ePub.Views = {};
/**
* register plugins
* Register Managers and Views
*/
ePub.register = {
/**
@ -48,10 +52,10 @@ ePub.register = {
};
// Default Views
ePub.register.view("iframe", require("./managers/views/iframe"));
ePub.register.view("iframe", IframeView);
// Default View Managers
ePub.register.manager("default", require("./managers/default"));
ePub.register.manager("continuous", require("./managers/continuous"));
ePub.register.manager("default", DefaultViewManager);
ePub.register.manager("continuous", ContinuousViewManager);
export default ePub;

View file

@ -1,24 +1,27 @@
import {extend, type, findChildren, RangeObject, isNumber} from "./utils/core";
/**
EPUB CFI spec: http://www.idpf.org/epub/linking/cfi/epub-cfi.html
Implements:
- Character Offset: epubcfi(/6/4[chap01ref]!/4[body01]/10[para05]/2/1:3)
- Simple Ranges : epubcfi(/6/4[chap01ref]!/4[body01]/10[para05],/2/1:1,/3:4)
Does Not Implement:
- Temporal Offset (~)
- Spatial Offset (@)
- Temporal-Spatial Offset (~ + @)
- Text Location Assertion ([)
*/
const ELEMENT_NODE = 1;
const TEXT_NODE = 3;
// const COMMENT_NODE = 8;
const COMMENT_NODE = 8;
const DOCUMENT_NODE = 9;
/**
* Parsing and creation of EpubCFIs: http://www.idpf.org/epub/linking/cfi/epub-cfi.html
* Implements:
* - Character Offset: epubcfi(/6/4[chap01ref]!/4[body01]/10[para05]/2/1:3)
* - Simple Ranges : epubcfi(/6/4[chap01ref]!/4[body01]/10[para05],/2/1:1,/3:4)
* Does Not Implement:
* - Temporal Offset (~)
* - Spatial Offset (@)
* - Temporal-Spatial Offset (~ + @)
* - Text Location Assertion ([)
* @class
@param {string | Range | Node } [cfiFrom]
@param {string | object} [base]
@param {string} [ignoreClass] class to ignore when parsing DOM
*/
class EpubCFI {
constructor(cfiFrom, base, ignoreClass){
var type;
@ -65,6 +68,10 @@ class EpubCFI {
}
/**
* Check the type of constructor input
* @private
*/
checkType(cfi) {
if (this.isCfiString(cfi)) {
@ -81,6 +88,11 @@ class EpubCFI {
}
}
/**
* Parse a cfi string to a CFI object representation
* @param {string} cfiStr
* @returns {object} cfi
*/
parse(cfiStr) {
var cfi = {
spinePos: -1,
@ -289,6 +301,10 @@ class EpubCFI {
return segmentString;
}
/**
* Convert CFI to a epubcfi(...) string
* @returns {string} epubcfi
*/
toString() {
var cfiString = "epubcfi(";
@ -313,6 +329,11 @@ class EpubCFI {
return cfiString;
}
/**
* Compare which of two CFIs is earlier in the text
* @returns {number} First is earlier = 1, Second is earlier = -1, They are equal = 0
*/
compare(cfiOne, cfiTwo) {
var stepsA, stepsB;
var terminalA, terminalB;
@ -477,6 +498,13 @@ class EpubCFI {
return false;
}
/**
* Create a CFI object from a Range
* @param {Range} range
* @param {string | object} base
* @param {string} [ignoreClass]
* @returns {object} cfi
*/
fromRange(range, base, ignoreClass) {
var cfi = {
range: false,
@ -564,6 +592,13 @@ class EpubCFI {
return cfi;
}
/**
* Create a CFI object from a Node
* @param {Node} anchor
* @param {string | object} base
* @param {string} [ignoreClass]
* @returns {object} cfi
*/
fromNode(anchor, base, ignoreClass) {
var cfi = {
range: false,
@ -886,6 +921,12 @@ class EpubCFI {
}
/**
* Creates a DOM range representing a CFI
* @param {document} _doc document referenced in the base
* @param {string} [ignoreClass]
* @return {Range}
*/
toRange(_doc, ignoreClass) {
var doc = _doc || document;
var range;
@ -953,7 +994,11 @@ class EpubCFI {
return range;
}
// is a cfi string, should be wrapped with "epubcfi()"
/**
* Check if a string is wrapped with "epubcfi()"
* @param {string} str
* @returns {boolean}
*/
isCfiString(str) {
if(typeof str === "string" &&
str.indexOf("epubcfi(") === 0 &&
@ -978,6 +1023,10 @@ class EpubCFI {
return cfi;
}
/**
* Collapse a CFI Range to a single CFI Position
* @param {boolean} [toStart=false]
*/
collapse(toStart) {
if (!this.range) {
return;

View file

@ -1,11 +1,11 @@
/**
* Figures out the CSS to apply for a layout
* Figures out the CSS values to apply for a layout
* @class
* @param {object} settings
* @param {[string=reflowable]} settings.layout
* @param {[string]} settings.spread
* @param {[int=800]} settings.minSpreadWidth
* @param {[boolean=false]} settings.evenSpreads
* @param {string} [settings.layout='reflowable']
* @param {string} [settings.spread]
* @param {int} [settings.minSpreadWidth=800]
* @param {boolean} [settings.evenSpreads=false]
*/
class Layout {
constructor(settings) {
@ -166,7 +166,7 @@ class Layout {
/**
* Apply Css to a Document
* @param {Contents} contents
* @return {[Promise]}
* @return {Promise}
*/
format(contents){
var formating;
@ -184,12 +184,12 @@ class Layout {
/**
* Count number of pages
* @param {number} totalWidth
* @return {number} spreads
* @return {number} pages
* @param {number} totalLength
* @param {number} pageLength
* @return {{spreads: Number, pages: Number}}
*/
count(totalLength, pageLength) {
// var totalWidth = contents.scrollWidth();
let spreads, pages;
if (this.name === "pre-paginated") {

View file

@ -171,6 +171,11 @@ class Locations {
return locations;
}
/**
* Get a location from an EpubCFI
* @param {EpubCFI} cfi
* @return {number}
*/
locationFromCfi(cfi){
let loc;
if (EpubCFI.prototype.isCfiString(cfi)) {
@ -190,6 +195,11 @@ class Locations {
return loc;
}
/**
* Get a percentage position in locations from an EpubCFI
* @param {EpubCFI} cfi
* @return {number}
*/
percentageFromCfi(cfi) {
if(this._locations.length === 0) {
return null;
@ -200,6 +210,11 @@ class Locations {
return this.percentageFromLocation(loc);
}
/**
* Get a percentage position from a location index
* @param {number} location
* @return {number}
*/
percentageFromLocation(loc) {
if (!loc || !this.total) {
return 0;
@ -208,6 +223,11 @@ class Locations {
return (loc / this.total);
}
/**
* Get an EpubCFI from location index
* @param {number} loc
* @return {EpubCFI} cfi
*/
cfiFromLocation(loc){
var cfi = -1;
// check that pg is an int
@ -222,6 +242,11 @@ class Locations {
return cfi;
}
/**
* Get an EpubCFI from location percentage
* @param {number} percentage
* @return {EpubCFI} cfi
*/
cfiFromPercentage(percentage){
let loc;
if (percentage > 1) {
@ -239,6 +264,10 @@ class Locations {
return this.cfiFromLocation(loc);
}
/**
* Load locations from JSON
* @param {json} locations
*/
load(locations){
if (typeof locations === "string") {
this._locations = JSON.parse(locations);
@ -249,11 +278,15 @@ class Locations {
return this._locations;
}
save(json){
/**
* Save locations to JSON
* @return {json}
*/
save(){
return JSON.stringify(this._locations);
}
getCurrent(json){
getCurrent(){
return this._current;
}
@ -284,14 +317,23 @@ class Locations {
});
}
/**
* Get the current location
*/
get currentLocation() {
return this._current;
}
/**
* Set the current location
*/
set currentLocation(curr) {
this.setCurrent(curr);
}
/**
* Locations length
*/
length () {
return this._locations.length;
}

View file

@ -1,5 +1,9 @@
import EpubCFI from "./epubcfi";
/**
* Map text locations to CFI ranges
* @class
*/
class Mapping {
constructor(layout, direction, axis, dev) {
this.layout = layout;
@ -8,6 +12,9 @@ class Mapping {
this._dev = dev;
}
/**
* Find CFI pairs for entire section at once
*/
section(view) {
var ranges = this.findRanges(view);
var map = this.rangeListToCfiList(view.section.cfiBase, ranges);
@ -15,6 +22,9 @@ class Mapping {
return map;
}
/**
* Find CFI pairs for a page
*/
page(contents, cfiBase, start, end) {
var root = contents && contents.document ? contents.document.body : false;
var result;

View file

@ -9,7 +9,7 @@ import {
/**
* Page List Parser
* @param {[document]} xml
* @param {document} [xml]
*/
class PageList {
constructor(xml) {

View file

@ -11,19 +11,22 @@ import Annotations from "./annotations";
import { EVENTS } from "./utils/constants";
/**
* [Rendition description]
* Displays an Epub as a series of Views for each Section.
* Requires Manager and View class to handle specifics of rendering
* the section contetn.
* @class
* @param {Book} book
* @param {object} options
* @param {int} options.width
* @param {int} options.height
* @param {string} options.ignoreClass
* @param {string} options.manager
* @param {string} options.view
* @param {string} options.layout
* @param {string} options.spread
* @param {int} options.minSpreadWidth overridden by spread: none (never) / both (always)
* @param {string} options.stylesheet url of stylesheet to be injected
* @param {object} [options]
* @param {number} [options.width]
* @param {number} [options.height]
* @param {string} [options.ignoreClass] class for the cfi parser to ignore
* @param {string | function | object} [options.manager='default']
* @param {string | function} [options.view='iframe']
* @param {string} [options.layout] layout to force
* @param {string} [options.spread] force spread value
* @param {number} [options.minSpreadWidth] overridden by spread: none (never) / both (always)
* @param {string} [options.stylesheet] url of stylesheet to be injected
* @param {string} [options.script] url of script to be injected
*/
class Rendition {
constructor(book, options) {
@ -50,19 +53,15 @@ class Rendition {
this.book = book;
// this.views = null;
/**
* Adds Hook methods to the Rendition prototype
* @property {Hook} hooks
* @member {object} hooks
* @property {Hook} hooks.content
* @memberof Rendition
*/
this.hooks = {};
this.hooks.display = new Hook(this);
this.hooks.serialize = new Hook(this);
/**
* @property {method} hooks.content
* @type {Hook}
*/
this.hooks.content = new Hook(this);
this.hooks.unloaded = new Hook(this);
this.hooks.layout = new Hook(this);
@ -83,20 +82,60 @@ class Rendition {
this.book.spine.hooks.content.register(this.injectScript.bind(this));
}
// this.hooks.display.register(this.afterDisplay.bind(this));
/**
* @member {Themes} themes
* @memberof Rendition
*/
this.themes = new Themes(this);
/**
* @member {Annotations} annotations
* @memberof Rendition
*/
this.annotations = new Annotations(this);
this.epubcfi = new EpubCFI();
this.q = new Queue(this);
/**
* A Rendered Location Range
* @typedef location
* @type {Object}
* @property {object} start
* @property {string} start.index
* @property {string} start.href
* @property {object} start.displayed
* @property {EpubCFI} start.cfi
* @property {number} start.location
* @property {number} start.percentage
* @property {number} start.displayed.page
* @property {number} start.displayed.total
* @property {object} end
* @property {string} end.index
* @property {string} end.href
* @property {object} end.displayed
* @property {EpubCFI} end.cfi
* @property {number} end.location
* @property {number} end.percentage
* @property {number} end.displayed.page
* @property {number} end.displayed.total
* @property {boolean} atStart
* @property {boolean} atEnd
* @memberof Rendition
*/
this.location = undefined;
// Hold queue until book is opened
this.q.enqueue(this.book.opened);
// Block the queue until rendering is started
this.starting = new defer();
/**
* @member {promise} started returns after the rendition has started
* @memberof Rendition
*/
this.started = this.starting.promise;
// Block the queue until rendering is started
this.q.enqueue(this.start);
}
@ -109,20 +148,19 @@ class Rendition {
}
/**
* Require the manager from passed string, or as a function
* @param {string|function} manager [description]
* Require the manager from passed string, or as a class function
* @param {string|object} manager [description]
* @return {method}
*/
requireManager(manager) {
var viewManager;
// If manager is a string, try to load from register managers,
// or require included managers directly
if (typeof manager === "string") {
// Use global or require
viewManager = typeof ePub != "undefined" ? ePub.ViewManagers[manager] : undefined; //require("./managers/"+manager);
// If manager is a string, try to load from global registered managers
if (typeof manager === "string" && typeof ePub != "undefined") {
// Use global
viewManager = ePub.ViewManagers[manager];
} else {
// otherwise, assume we were passed a function
// otherwise, assume we were passed a class function
viewManager = manager;
}
@ -130,17 +168,19 @@ class Rendition {
}
/**
* Require the view from passed string, or as a function
* @param {string|function} view
* Require the view from passed string, or as a class function
* @param {string|object} view
* @return {view}
*/
requireView(view) {
var View;
if (typeof view == "string") {
View = typeof ePub != "undefined" ? ePub.Views[view] : undefined; //require("./views/"+view);
// If view is a string, try to load from global registered views,
if (typeof view == "string" && typeof ePub != "undefined") {
// Use global
View = ePub.Views[view];
} else {
// otherwise, assume we were passed a function
// otherwise, assume we were passed a class function
View = view;
}
@ -187,7 +227,11 @@ class Rendition {
// Listen for scroll changes
this.manager.on(EVENTS.MANAGERS.SCROLLED, this.reportLocation.bind(this));
// Trigger that rendering has started
/**
* Emit that rendering has started
* @event started
* @memberof Rendition
*/
this.emit(EVENTS.RENDITION.STARTED);
// Start processing queue
@ -210,7 +254,11 @@ class Rendition {
"height" : this.settings.height
});
// Trigger Attached
/**
* Emit that rendering has attached to an element
* @event attached
* @memberof Rendition
*/
this.emit(EVENTS.RENDITION.ATTACHED);
}.bind(this));
@ -270,9 +318,21 @@ class Rendition {
displaying.resolve(section);
this.displaying = undefined;
/**
* Emit that a section has been displayed
* @event displayed
* @param {Section} section
* @memberof Rendition
*/
this.emit(EVENTS.RENDITION.DISPLAYED, section);
this.reportLocation();
}, (err) => {
/**
* Emit that has been an error displaying
* @event displayError
* @param {Section} section
* @memberof Rendition
*/
this.emit(EVENTS.RENDITION.DISPLAY_ERROR, err);
});
@ -325,7 +385,7 @@ class Rendition {
*/
/**
* Report what has been displayed
* Report what section has been displayed
* @private
* @param {*} view
*/
@ -337,6 +397,13 @@ class Rendition {
.then(() => {
if (view.contents) {
this.hooks.content.trigger(view.contents, this).then(() => {
/**
* Emit that a section has been rendered
* @event rendered
* @param {Section} section
* @param {View} view
* @memberof Rendition
*/
this.emit(EVENTS.RENDITION.RENDERED, view.section, view);
});
} else {
@ -344,7 +411,6 @@ class Rendition {
}
});
// this.reportLocation();
}
/**
@ -354,6 +420,13 @@ class Rendition {
*/
afterRemoved(view){
this.hooks.unloaded.trigger(view, this).then(() => {
/**
* Emit that a section has been removed
* @event removed
* @param {Section} section
* @param {View} view
* @memberof Rendition
*/
this.emit(EVENTS.RENDITION.REMOVED, view.section, view);
});
}
@ -364,6 +437,13 @@ class Rendition {
*/
onResized(size){
/**
* Emit that the rendition has been resized
* @event resized
* @param {number} width
* @param {height} height
* @memberof Rendition
*/
this.emit(EVENTS.RENDITION.RESIZED, {
width: size.width,
height: size.height
@ -380,11 +460,12 @@ class Rendition {
* @private
*/
onOrientationChange(orientation){
// Handled in resize event
// if (this.location) {
// this.display(this.location.start.cfi);
// }
/**
* Emit that the rendition has been rotated
* @event orientationchange
* @param {string} orientation
* @memberof Rendition
*/
this.emit(EVENTS.RENDITION.ORIENTATION_CHANGE, orientation);
}
@ -450,14 +531,6 @@ class Rendition {
return properties;
}
// applyLayoutProperties(){
// var settings = this.determineLayoutProperties(this.book.package.metadata);
//
// this.flow(settings.flow);
//
// this.layout(settings);
// };
/**
* Adjust the flow of the rendition to paginated or scrolled
* (scrolled-continuous vs scrolled-doc are handled by different view managers)
@ -548,7 +621,8 @@ class Rendition {
/**
* Report the current location
* @private
* @fires relocated
* @fires locationChanged
*/
reportLocation(){
return this.q.enqueue(function reportedLocation(){
@ -583,6 +657,17 @@ class Rendition {
this.location = located;
/**
* @event locationChanged
* @deprecated
* @type {object}
* @property {number} index
* @property {string} href
* @property {EpubCFI} start
* @property {EpubCFI} end
* @property {number} percentage
* @memberof Rendition
*/
this.emit(EVENTS.RENDITION.LOCATION_CHANGED, {
index: this.location.start.index,
href: this.location.start.href,
@ -591,6 +676,11 @@ class Rendition {
percentage: this.location.start.percentage
});
/**
* @event relocated
* @type {displayedLocation}
* @memberof Rendition
*/
this.emit(EVENTS.RENDITION.RELOCATED, this.location);
}
}.bind(this));
@ -598,8 +688,8 @@ class Rendition {
}
/**
* Get the Current Location CFI
* @return {EpubCFI} location (may be a promise)
* Get the Current Location object
* @return {displayedLocation | promise} location (may be a promise)
*/
currentLocation(){
var location = this.manager.currentLocation();
@ -614,6 +704,12 @@ class Rendition {
}
}
/**
* Creates a Rendition#locationRange from location
* passed by the Manager
* @returns {displayedLocation}
* @private
*/
located(location){
if (!location.length) {
return {};
@ -740,6 +836,13 @@ class Rendition {
* @param {EpubCFI} cfirange
*/
triggerSelectedEvent(cfirange, contents){
/**
* Emit that a text selection has occured
* @event selected
* @param {EpubCFI} cfirange
* @param {Contents} contents
* @memberof Rendition
*/
this.emit(EVENTS.RENDITION.SELECTED, cfirange, contents);
}
@ -749,6 +852,14 @@ class Rendition {
* @param {EpubCFI} cfirange
*/
triggerMarkEvent(cfiRange, data, contents){
/**
* Emit that a mark was clicked
* @event markClicked
* @param {EpubCFI} cfirange
* @param {object} data
* @param {Contents} contents
* @memberof Rendition
*/
this.emit(EVENTS.RENDITION.MARK_CLICKED, cfiRange, data, contents);
}
@ -772,7 +883,8 @@ class Rendition {
/**
* Hook to adjust images to fit in columns
* @param {View} view
* @param {Contents} contents
* @private
*/
adjustImages(contents) {
@ -799,15 +911,28 @@ class Rendition {
});
}
/**
* Get the Contents object of each rendered view
* @returns {Contents[]}
*/
getContents () {
return this.manager ? this.manager.getContents() : [];
}
/**
* Get the views member from the manager
* @returns {Views}
*/
views () {
let views = this.manager ? this.manager.views : undefined;
return views || [];
}
/**
* Hook to handle link clicks in rendered content
* @param {Contents} contents
* @private
*/
handleLinks(contents) {
if (contents) {
contents.on(EVENTS.CONTENTS.LINK_CLICKED, (href) => {
@ -817,6 +942,13 @@ class Rendition {
}
}
/**
* Hook to handle injecting stylesheet before
* a Section is serialized
* @param {document} doc
* @param {Section} section
* @private
*/
injectStylesheet(doc, section) {
let style = doc.createElement("link");
style.setAttribute("type", "text/css");
@ -825,6 +957,13 @@ class Rendition {
doc.getElementsByTagName("head")[0].appendChild(style);
}
/**
* Hook to handle injecting scripts before
* a Section is serialized
* @param {document} doc
* @param {Section} section
* @private
*/
injectScript(doc, section) {
let script = doc.createElement("script");
script.setAttribute("type", "text/javascript");
@ -833,6 +972,13 @@ class Rendition {
doc.getElementsByTagName("head")[0].appendChild(script);
}
/**
* Hook to handle the document identifier before
* a Section is serialized
* @param {document} doc
* @param {Section} section
* @private
*/
injectIdentifier(doc, section) {
let ident = this.book.package.metadata.identifier;
let meta = doc.createElement("meta");

View file

@ -150,8 +150,8 @@ class Resources {
/**
* Replace URLs in CSS resources
* @private
* @param {[Archive]} archive
* @param {[method]} resolver
* @param {Archive} [archive]
* @param {method} [resolver]
* @return {Promise}
*/
replaceCss(archive, resolver){
@ -277,7 +277,7 @@ class Resources {
* Substitute urls in content, with replacements,
* relative to a url if provided
* @param {string} content
* @param {[string]} url url to resolve to
* @param {string} [url] url to resolve to
* @return {string} content with urls substituted
*/
substitute(content, url) {

View file

@ -6,6 +6,7 @@ import { replaceBase } from "./utils/replacements";
/**
* Represents a Section of the Book
*
* In most books this is equivelent to a Chapter
* @param {object} item The spine item representing the section
* @param {object} hooks hooks for serialize and content

View file

@ -112,7 +112,7 @@ class Spine {
/**
* Get an item from the spine
* @param {[string|int]} target
* @param {string|int} [target]
* @return {Section} section
* @example spine.get();
* @example spine.get(1);

View file

@ -1,5 +1,10 @@
import Url from "./utils/url";
/**
* Themes to apply to displayed content
* @class
* @param {Rendition} rendition
*/
class Themes {
constructor(rendition) {
this.rendition = rendition;
@ -18,6 +23,13 @@ class Themes {
}
/**
* Add themes to be used by a rendition
* @param {object | string}
* @example themes.register("light", "http://example.com/light.css")
* @example themes.register("light", { "body": { "color": "purple"}})
* @example themes.register({ "light" : {...}, "dark" : {...}})
*/
register () {
if (arguments.length === 0) {
return;
@ -36,6 +48,12 @@ class Themes {
}
}
/**
* Add a default theme to be used by a rendition
* @param {object | string} theme
* @example themes.register("http://example.com/default.css")
* @example themes.register({ "body": { "color": "purple"}})
*/
default (theme) {
if (!theme) {
return;
@ -154,10 +172,18 @@ class Themes {
}
}
/**
* Adjust the font size of a rendition
* @param {number} size
*/
fontSize (size) {
this.override("font-size", size);
}
/**
* Adjust the font-family of a rendition
* @param {string} f
*/
font (f) {
this.override("font-family", f);
}

View file

@ -1,3 +1,13 @@
/**
* Core Utilities and Helpers
* @module Core
*/
/**
* Vendor prefixed requestAnimationFrame
* @returns {function} requestAnimationFrame
* @memberof Core
*/
export const requestAnimationFrame = (typeof window != "undefined") ? (window.requestAnimationFrame || window.mozRequestAnimationFrame || window.webkitRequestAnimationFrame || window.msRequestAnimationFrame) : false;
const ELEMENT_NODE = 1;
const TEXT_NODE = 3;
@ -5,11 +15,12 @@ const COMMENT_NODE = 8;
const DOCUMENT_NODE = 9;
const _URL = typeof URL != "undefined" ? URL : (typeof window != "undefined" ? (window.URL || window.webkitURL || window.mozURL) : undefined);
export function isElement(obj) {
return !!(obj && obj.nodeType == 1);
}
// http://stackoverflow.com/questions/105034/how-to-create-a-guid-uuid-in-javascript
/**
* Generates a UUID
* based on: http://stackoverflow.com/questions/105034/how-to-create-a-guid-uuid-in-javascript
* @returns {string} uuid
* @memberof Core
*/
export function uuid() {
var d = new Date().getTime();
var uuid = "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, function(c) {
@ -20,6 +31,11 @@ export function uuid() {
return uuid;
}
/**
* Gets the height of a document
* @returns {number} height
* @memberof Core
*/
export function documentHeight() {
return Math.max(
document.documentElement.clientHeight,
@ -30,15 +46,37 @@ export function documentHeight() {
);
}
/**
* Checks if a node is an element
* @returns {boolean}
* @memberof Core
*/
export function isElement(obj) {
return !!(obj && obj.nodeType == 1);
}
/**
* @returns {boolean}
* @memberof Core
*/
export function isNumber(n) {
return !isNaN(parseFloat(n)) && isFinite(n);
}
/**
* @returns {boolean}
* @memberof Core
*/
export function isFloat(n) {
let f = parseFloat(n);
return f === n && isNumber(n) && (Math.floor(f) !== n);
}
/**
* Get a prefixed css property
* @returns {string}
* @memberof Core
*/
export function prefixed(unprefixed) {
var vendors = ["Webkit", "webkit", "Moz", "O", "ms" ];
var prefixes = ["-webkit-", "-webkit-", "-moz-", "-o-", "-ms-"];
@ -58,6 +96,12 @@ export function prefixed(unprefixed) {
return unprefixed;
}
/**
* Apply defaults to an object
* @param {object} obj
* @returns {object}
* @memberof Core
*/
export function defaults(obj) {
for (var i = 1, length = arguments.length; i < length; i++) {
var source = arguments[i];
@ -68,6 +112,12 @@ export function defaults(obj) {
return obj;
}
/**
* Extend undefined properties of an object
* @param {object} target
* @returns {object}
* @memberof Core
*/
export function extend(target) {
var sources = [].slice.call(arguments, 1);
sources.forEach(function (source) {
@ -79,8 +129,15 @@ export function extend(target) {
return target;
}
// Fast quicksort insert for sorted array -- based on:
// http://stackoverflow.com/questions/1344500/efficient-way-to-insert-a-number-into-a-sorted-array-of-numbers
/**
* Fast quicksort insert for sorted array -- based on:
* http://stackoverflow.com/questions/1344500/efficient-way-to-insert-a-number-into-a-sorted-array-of-numbers
* @param {any} item
* @param {array} array
* @param {function} [compareFunction]
* @returns {number} location (in array)
* @memberof Core
*/
export function insert(item, array, compareFunction) {
var location = locationOf(item, array, compareFunction);
array.splice(location, 0, item);
@ -88,7 +145,16 @@ export function insert(item, array, compareFunction) {
return location;
}
// Returns where something would fit in
/**
* Finds where something would fit into a sorted array
* @param {any} item
* @param {array} array
* @param {function} [compareFunction]
* @param {function} [_start]
* @param {function} [_end]
* @returns {number} location (in array)
* @memberof Core
*/
export function locationOf(item, array, compareFunction, _start, _end) {
var start = _start || 0;
var end = _end || array.length;
@ -119,7 +185,17 @@ export function locationOf(item, array, compareFunction, _start, _end) {
}
}
// Returns -1 of mpt found
/**
* Finds index of something in a sorted array
* Returns -1 if not found
* @param {any} item
* @param {array} array
* @param {function} [compareFunction]
* @param {function} [_start]
* @param {function} [_end]
* @returns {number} index (in array) or -1
* @memberof Core
*/
export function indexOfSorted(item, array, compareFunction, _start, _end) {
var start = _start || 0;
var end = _end || array.length;
@ -149,7 +225,13 @@ export function indexOfSorted(item, array, compareFunction, _start, _end) {
return indexOfSorted(item, array, compareFunction, start, pivot);
}
}
/**
* Find the bounds of an element
* taking padding and margin into account
* @param {element} el
* @returns {{ width: Number, height: Number}}
* @memberof Core
*/
export function bounds(el) {
var style = window.getComputedStyle(el);
@ -174,6 +256,13 @@ export function bounds(el) {
}
/**
* Find the bounds of an element
* taking padding, margin and borders into account
* @param {element} el
* @returns {{ width: Number, height: Number}}
* @memberof Core
*/
export function borders(el) {
var style = window.getComputedStyle(el);
@ -198,6 +287,11 @@ export function borders(el) {
}
/**
* Find the equivelent of getBoundingClientRect of a browser window
* @returns {{ width: Number, height: Number, top: Number, left: Number, right: Number, bottom: Number }}
* @memberof Core
*/
export function windowBounds() {
var width = window.innerWidth;
@ -214,22 +308,11 @@ export function windowBounds() {
}
//-- https://stackoverflow.com/questions/13482352/xquery-looking-for-text-with-single-quote/13483496#13483496
export function cleanStringForXpath(str) {
var parts = str.match(/[^'"]+|['"]/g);
parts = parts.map(function(part){
if (part === "'") {
return "\"\'\""; // output "'"
}
if (part === "\"") {
return "\'\"\'"; // output '"'
}
return `\'${part}\'`;
});
return `concat(\'\',${ parts.join(",") })`;
}
/**
* Gets the index of a node in its parent
* @private
* @memberof Core
*/
export function indexOfNode(node, typeId) {
var parent = node.parentNode;
var children = parent.childNodes;
@ -246,22 +329,54 @@ export function indexOfNode(node, typeId) {
return index;
}
/**
* Gets the index of a text node in its parent
* @param {node} textNode
* @returns {number} index
* @memberof Core
*/
export function indexOfTextNode(textNode) {
return indexOfNode(textNode, TEXT_NODE);
}
/**
* Gets the index of an element node in its parent
* @param {element} elementNode
* @returns {number} index
* @memberof Core
*/
export function indexOfElementNode(elementNode) {
return indexOfNode(elementNode, ELEMENT_NODE);
}
/**
* Check if extension is xml
* @param {string} ext
* @returns {boolean}
* @memberof Core
*/
export function isXml(ext) {
return ["xml", "opf", "ncx"].indexOf(ext) > -1;
}
/**
* Create a new blob
* @param {any} content
* @param {string} mime
* @returns {Blob}
* @memberof Core
*/
export function createBlob(content, mime){
return new Blob([content], {type : mime });
}
/**
* Create a new blob url
* @param {any} content
* @param {string} mime
* @returns {string} url
* @memberof Core
*/
export function createBlobUrl(content, mime){
var tempUrl;
var blob = createBlob(content, mime);
@ -271,11 +386,22 @@ export function createBlobUrl(content, mime){
return tempUrl;
}
/**
* Remove a blob url
* @param {string} url
* @memberof Core
*/
export function revokeBlobUrl(url){
return _URL.revokeObjectURL(url);
}
/**
* Create a new base64 encoded url
* @param {any} content
* @param {string} mime
* @returns {string} url
* @memberof Core
*/
export function createBase64Url(content, mime){
var data;
var datauri;
@ -292,10 +418,24 @@ export function createBase64Url(content, mime){
return datauri;
}
/**
* Get type of an object
* @param {object} obj
* @returns {string} type
* @memberof Core
*/
export function type(obj){
return Object.prototype.toString.call(obj).slice(8, -1);
}
/**
* Parse xml (or html) markup
* @param {string} markup
* @param {string} mime
* @param {boolean} forceXMLDom force using xmlDom to parse instead of native parser
* @returns {document} document
* @memberof Core
*/
export function parse(markup, mime, forceXMLDom) {
var doc;
var Parser;
@ -317,6 +457,13 @@ export function parse(markup, mime, forceXMLDom) {
return doc;
}
/**
* querySelector polyfill
* @param {element} el
* @param {string} sel selector string
* @returns {element} element
* @memberof Core
*/
export function qs(el, sel) {
var elements;
if (!el) {
@ -333,6 +480,13 @@ export function qs(el, sel) {
}
}
/**
* querySelectorAll polyfill
* @param {element} el
* @param {string} sel selector string
* @returns {element[]} elements
* @memberof Core
*/
export function qsa(el, sel) {
if (typeof el.querySelector != "undefined") {
@ -342,6 +496,14 @@ export function qsa(el, sel) {
}
}
/**
* querySelector by property
* @param {element} el
* @param {string} sel selector string
* @param {props[]} props
* @returns {element[]} elements
* @memberof Core
*/
export function qsp(el, sel, props) {
var q, filtered;
if (typeof el.querySelector != "undefined") {
@ -370,6 +532,7 @@ export function qsp(el, sel, props) {
/**
* Sprint through all text nodes in a document
* @memberof Core
* @param {element} root element to start with
* @param {function} func function to run on each element
*/
@ -394,24 +557,10 @@ export function treeWalker(root, func, filter) {
}
}
// export function walk(root, func, onlyText) {
// var node = root;
//
// if (node && !onlyText || node.nodeType === 3) { // Node.TEXT_NODE
// func(node);
// }
// console.log(root);
//
// node = node.firstChild;
// while(node) {
// walk(node, func, onlyText);
// node = node.nextSibling;
// }
// }
/**
* @param callback return false for continue,true for break
* @return boolean true: break visit;
* @memberof Core
* @param {node} node
* @param {callback} return false for continue,true for break inside callback
*/
export function walk(node,callback){
if(callback(node)){
@ -429,6 +578,12 @@ export function walk(node,callback){
}
}
/**
* Convert a blob to a base64 encoded string
* @param {Blog} blob
* @returns {string}
* @memberof Core
*/
export function blob2base64(blob) {
return new Promise(function(resolve, reject) {
var reader = new FileReader();
@ -439,7 +594,12 @@ export function blob2base64(blob) {
});
}
// From: https://developer.mozilla.org/en-US/docs/Mozilla/JavaScript_code_modules/Promise.jsm/Deferred#backwards_forwards_compatible
/**
* Creates a new pending promise and provides methods to resolve or reject it.
* From: https://developer.mozilla.org/en-US/docs/Mozilla/JavaScript_code_modules/Promise.jsm/Deferred#backwards_forwards_compatible
* @memberof Core
*/
export function defer() {
/* A method to resolve the associated Promise with the value passed.
* If the promise is already settled it does nothing.
@ -471,6 +631,14 @@ export function defer() {
Object.freeze(this);
}
/**
* querySelector with filter by epub type
* @param {element} html
* @param {string} element element type to find
* @param {string} type epub type to find
* @returns {element[]} elements
* @memberof Core
*/
export function querySelectorByType(html, element, type){
var query;
if (typeof html.querySelector != "undefined") {
@ -490,6 +658,12 @@ export function querySelectorByType(html, element, type){
}
}
/**
* Find direct decendents of an element
* @param {element} el
* @returns {element[]} children
* @memberof Core
*/
export function findChildren(el) {
var result = [];
var childNodes = el.childNodes;
@ -502,6 +676,12 @@ export function findChildren(el) {
return result;
}
/**
* Find all parents (ancestors) of an element
* @param {element} node
* @returns {element[]} parents
* @memberof Core
*/
export function parents(node) {
var nodes = [node];
for (; node; node = node.parentNode) {
@ -510,6 +690,14 @@ export function parents(node) {
return nodes
}
/**
* Find all direct decendents of a specific type
* @param {element} el
* @param {string} nodeName
* @param {boolean} [single]
* @returns {element[]} children
* @memberof Core
*/
export function filterChildren(el, nodeName, single) {
var result = [];
var childNodes = el.childNodes;
@ -528,6 +716,13 @@ export function filterChildren(el, nodeName, single) {
}
}
/**
* Filter all parents (ancestors) with tag name
* @param {element} node
* @param {string} tagname
* @returns {element[]} parents
* @memberof Core
*/
export function getParentByTagName(node, tagname) {
let parent;
if (node === null || tagname === '') return;
@ -540,6 +735,11 @@ export function getParentByTagName(node, tagname) {
}
}
/**
* Lightweight Polyfill for DOM Range
* @class
* @memberof Core
*/
export class RangeObject {
constructor() {
this.collapsed = false;

View file

@ -1,5 +1,12 @@
import path from "path-webpack";
/**
* Creates a Path object for parsing and manipulation of a path strings
*
* Uses a polyfill for Nodejs path: https://nodejs.org/api/path.html
* @param {string} pathString a url string (relative or absolute)
* @class
*/
class Path {
constructor(pathString) {
var protocol;
@ -25,22 +32,50 @@ class Path {
}
/**
* Parse the path: https://nodejs.org/api/path.html#path_path_parse_path
* @param {string} what
* @returns {object}
*/
parse (what) {
return path.parse(what);
}
/**
* @param {string} what
* @returns {boolean}
*/
isAbsolute (what) {
return path.isAbsolute(what || this.path);
}
/**
* Check if path ends with a directory
* @param {string} what
* @returns {boolean}
*/
isDirectory (what) {
return (what.charAt(what.length-1) === "/");
}
/**
* Resolve a path against the directory of the Path
*
* https://nodejs.org/api/path.html#path_path_resolve_paths
* @param {string} what
* @returns {string} resolved
*/
resolve (what) {
return path.resolve(this.directory, what);
}
/**
* Resolve a path relative to the directory of the Path
*
* https://nodejs.org/api/path.html#path_path_relative_from_to
* @param {string} what
* @returns {string} relative
*/
relative (what) {
return path.relative(this.directory, what);
}
@ -49,6 +84,10 @@ class Path {
return this.splitPathRe.exec(filename).slice(1);
}
/**
* Return the path string
* @returns {string} path
*/
toString () {
return this.path;
}

View file

@ -2,13 +2,11 @@ import Path from "./path";
import path from "path-webpack";
/**
* creates a uri object
* creates a Url object for parsing and manipulation of a url string
* @param {string} urlString a url string (relative or absolute)
* @param {[string]} baseString optional base for the url,
* @param {string} [baseString] optional base for the url,
* default to window.location.href
* @return {object} url
*/
class Url {
constructor(urlString, baseString) {
var absolute = (urlString.indexOf("://") > -1);
@ -66,10 +64,17 @@ class Url {
}
/**
* @returns {Path}
*/
path () {
return this.Path;
}
/**
* Resolves a relative path to a absolute url
* @returns {string} url
*/
resolve (what) {
var isAbsolute = (what.indexOf("://") > -1);
var fullpath;
@ -82,10 +87,17 @@ class Url {
return this.origin + fullpath;
}
/**
* Resolve a path relative to the url
* @returns {string} path
*/
relative (what) {
return path.relative(what, this.directory);
}
/**
* @returns {string}
*/
toString () {
return this.href;
}

7986
yarn.lock

File diff suppressed because it is too large Load diff