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

Intial move to ES2015

This commit is contained in:
Fred Chasen 2016-12-06 15:04:16 +01:00
parent b0944bdff8
commit 353dfa62fd
46 changed files with 16839 additions and 18742 deletions

15
.babelrc Normal file
View file

@ -0,0 +1,15 @@
{
"presets": [
["env", {
"targets": {
"chrome": 54,
"safari" : 10,
"firefox" : 50,
"edge" : 14
}
}]
],
"plugins": [
"add-module-exports"
]
}

3
.gitignore vendored
View file

@ -5,3 +5,6 @@ components
node_modules node_modules
bower_components bower_components
books books
lib
dist
documentation/html

View file

@ -26,6 +26,10 @@
"examples" "examples"
], ],
"dependencies": { "dependencies": {
"es6-promise": "~4.0.5" "es6-promise": "~4.0.5",
"jszip": "^3.1.1",
"path-webpack": "^0.0.2",
"stream-browserify": "^2.0.1",
"xmldom": "^0.1.22"
} }
} }

17946
dist/epub.js vendored

File diff suppressed because it is too large Load diff

2
dist/epub.js.map vendored

File diff suppressed because one or more lines are too long

5
dist/epub.min.js vendored

File diff suppressed because one or more lines are too long

2110
dist/polyfills.js vendored

File diff suppressed because it is too large Load diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -24,7 +24,7 @@ module.exports = function(config) {
{pattern: 'node_modules/jszip/dist/jszip.js', watched: false, included: true, served: true}, {pattern: 'node_modules/jszip/dist/jszip.js', watched: false, included: true, served: true},
{pattern: 'node_modules/es6-promise/dist/es6-promise.auto.js', watched: false, included: true, served: true}, // {pattern: 'node_modules/es6-promise/dist/es6-promise.auto.js', watched: false, included: true, served: true},
{pattern: 'libs/url/url.js', watched: false, included: true, served: true} {pattern: 'libs/url/url.js', watched: false, included: true, served: true}
@ -53,6 +53,22 @@ module.exports = function(config) {
alias: { alias: {
path: "path-webpack" path: "path-webpack"
} }
},
module: {
loaders: [
{
test: /\.js$/,
exclude: /node_modules/,
loader: "babel-loader",
query: {
presets: ['es2015'],
plugins: [
"add-module-exports",
"transform-runtime"
]
}
}
]
} }
}, },

View file

@ -1,20 +1,34 @@
{ {
"name": "epubjs", "name": "epubjs",
"version": "0.3.0", "version": "0.3.1",
"description": "Render Epubs", "description": "Parse and Render Epubs",
"main": "src/epub.js", "main": "lib/index.js",
"jsnext:main" : "src/index.js",
"repository": "https://github.com/futurepress/epub.js", "repository": "https://github.com/futurepress/epub.js",
"directories": { "directories": {
"test": "test" "test": "test"
}, },
"scripts": { "scripts": {
"test": "./node_modules/.bin/karma start --single-run --browsers PhantomJS", "test": "./node_modules/.bin/karma start --single-run --browsers PhantomJS",
"start": "webpack-dev-server --inline", "documentation": "./node_modules/.bin/gulp docs",
"build": "./node_modules/.bin/gulp minify" "start": "webpack-dev-server --inline --d",
"build": "webpack --progress",
"minify": "NODE_ENV=production webpack --progress",
"legacy": "NODE_ENV=production LEGACY=true webpack --progress",
"compile": "babel --optional runtime -d lib/ src/",
"prepublish": "npm run compile && npm run build && npm run minify && npm run legacy"
}, },
"author": "fchasen@gmail.com", "author": "fchasen@gmail.com",
"license": "BSD-2-Clause", "license": "BSD-2-Clause",
"devDependencies": { "devDependencies": {
"babel-cli": "^6.18.0",
"babel-core": "^6.18.2",
"babel-loader": "^6.2.8",
"babel-plugin-add-module-exports": "^0.2.1",
"babel-plugin-transform-runtime": "^6.15.0",
"babel-polyfill": "^6.16.0",
"babel-preset-env": "0.0.9",
"babili-webpack-plugin": "0.0.7",
"colors": "^1.1.2", "colors": "^1.1.2",
"connect": "^3.0.1", "connect": "^3.0.1",
"express": "^4.5.1", "express": "^4.5.1",
@ -52,11 +66,10 @@
"webpack-dev-server": "^v2.1.0-beta.10" "webpack-dev-server": "^v2.1.0-beta.10"
}, },
"dependencies": { "dependencies": {
"es6-promise": "^4.0.5",
"event-emitter": "^0.3.4", "event-emitter": "^0.3.4",
"jszip": "^3.1.1", "jszip": "^3.1.1",
"xmldom": "^0.1.22",
"path-webpack": "^0.0.2", "path-webpack": "^0.0.2",
"stream-browserify": "^2.0.1" "stream-browserify": "^2.0.1",
"xmldom": "^0.1.22"
} }
} }

View file

@ -1,245 +1,247 @@
var core = require('./core'); import {defer, isXml, parse} from './utils/core';
var request = require('./request'); import request from './request';
var mime = require('../libs/mime/mime'); import mime from '../libs/mime/mime';
var Path = require('./core').Path; import Path from './utils/path';
/** /**
* Handles Unzipping a requesting files from an Epub Archive * Handles Unzipping a requesting files from an Epub Archive
* @class * @class
*/ */
function Archive() { class Archive {
this.zip = undefined; constructor() {
this.checkRequirements(); this.zip = undefined;
this.urlCache = {}; this.checkRequirements();
this.urlCache = {};
}
/**
* Checks to see if JSZip exists in global namspace,
* Requires JSZip if it isn't there
* @private
*/
checkRequirements(){
try {
if (typeof JSZip === 'undefined') {
JSZip = require('jszip');
}
this.zip = new JSZip();
} catch (e) {
console.error("JSZip lib not loaded");
}
};
/**
* Open an archive
* @param {binary} input
* @param {boolean} isBase64 tells JSZip if the input data is base64 encoded
* @return {Promise} zipfile
*/
open(input, isBase64){
return this.zip.loadAsync(input, {"base64": isBase64});
};
/**
* Load and Open an archive
* @param {string} zipUrl
* @param {boolean} isBase64 tells JSZip if the input data is base64 encoded
* @return {Promise} zipfile
*/
openUrl(zipUrl, isBase64){
return request(zipUrl, "binary")
.then(function(data){
return this.zip.loadAsync(data, {"base64": isBase64});
}.bind(this));
};
/**
* Request
* @param {string} url a url to request from the archive
* @param {[string]} type specify the type of the returned result
* @return {Promise}
*/
request(url, type){
var deferred = new defer();
var response;
var r;
var path = new Path(url);
// If type isn't set, determine it from the file extension
if(!type) {
type = path.extension;
}
if(type == 'blob'){
response = this.getBlob(url);
} else {
response = this.getText(url);
}
if (response) {
response.then(function (r) {
let result = this.handleResponse(r, type);
deferred.resolve(result);
}.bind(this));
} else {
deferred.reject({
message : "File not found in the epub: " + url,
stack : new Error().stack
});
}
return deferred.promise;
};
/**
* Handle the response from request
* @private
* @param {any} response
* @param {[string]} type
* @return {any} the parsed result
*/
handleResponse(response, type){
var r;
if(type == "json") {
r = JSON.parse(response);
}
else
if(isXml(type)) {
r = parse(response, "text/xml");
}
else
if(type == 'xhtml') {
r = parse(response, "application/xhtml+xml");
}
else
if(type == 'html' || type == 'htm') {
r = parse(response, "text/html");
} else {
r = response;
}
return r;
};
/**
* Get a Blob from Archive by Url
* @param {string} url
* @param {[string]} mimeType
* @return {Blob}
*/
getBlob(url, mimeType){
var decodededUrl = window.decodeURIComponent(url.substr(1)); // Remove first slash
var entry = this.zip.file(decodededUrl);
if(entry) {
mimeType = mimeType || mime.lookup(entry.name);
return entry.async("uint8array").then(function(uint8array) {
return new Blob([uint8array], {type : mimeType});
});
}
};
/**
* Get Text from Archive by Url
* @param {string} url
* @param {[string]} encoding
* @return {string}
*/
getText(url, encoding){
var decodededUrl = window.decodeURIComponent(url.substr(1)); // Remove first slash
var entry = this.zip.file(decodededUrl);
if(entry) {
return entry.async("string").then(function(text) {
return text;
});
}
};
/**
* Get a base64 encoded result from Archive by Url
* @param {string} url
* @param {[string]} mimeType
* @return {string} base64 encoded
*/
getBase64(url, mimeType){
var decodededUrl = window.decodeURIComponent(url.substr(1)); // Remove first slash
var entry = this.zip.file(decodededUrl);
if(entry) {
mimeType = mimeType || mime.lookup(entry.name);
return entry.async("base64").then(function(data) {
return "data:" + mimeType + ";base64," + data;
});
}
};
/**
* Create a Url from an unarchived item
* @param {string} url
* @param {[object]} options.base64 use base64 encoding or blob url
* @return {Promise} url promise with Url string
*/
createUrl(url, options){
var deferred = new defer();
var _URL = window.URL || window.webkitURL || window.mozURL;
var tempUrl;
var blob;
var response;
var useBase64 = options && options.base64;
if(url in this.urlCache) {
deferred.resolve(this.urlCache[url]);
return deferred.promise;
}
if (useBase64) {
response = this.getBase64(url);
if (response) {
response.then(function(tempUrl) {
this.urlCache[url] = tempUrl;
deferred.resolve(tempUrl);
}.bind(this));
}
} else {
response = this.getBlob(url);
if (response) {
response.then(function(blob) {
tempUrl = _URL.createObjectURL(blob);
this.urlCache[url] = tempUrl;
deferred.resolve(tempUrl);
}.bind(this));
}
}
if (!response) {
deferred.reject({
message : "File not found in the epub: " + url,
stack : new Error().stack
});
}
return deferred.promise;
};
/**
* Revoke Temp Url for a achive item
* @param {string} url url of the item in the archive
*/
revokeUrl(url){
var _URL = window.URL || window.webkitURL || window.mozURL;
var fromCache = this.urlCache[url];
if(fromCache) _URL.revokeObjectURL(fromCache);
};
} }
/** export default Archive;
* Checks to see if JSZip exists in global namspace,
* Requires JSZip if it isn't there
* @private
*/
Archive.prototype.checkRequirements = function(){
try {
if (typeof JSZip === 'undefined') {
JSZip = require('jszip');
}
this.zip = new JSZip();
} catch (e) {
console.error("JSZip lib not loaded");
}
};
/**
* Open an archive
* @param {binary} input
* @param {boolean} isBase64 tells JSZip if the input data is base64 encoded
* @return {Promise} zipfile
*/
Archive.prototype.open = function(input, isBase64){
return this.zip.loadAsync(input, {"base64": isBase64});
};
/**
* Load and Open an archive
* @param {string} zipUrl
* @param {boolean} isBase64 tells JSZip if the input data is base64 encoded
* @return {Promise} zipfile
*/
Archive.prototype.openUrl = function(zipUrl, isBase64){
return request(zipUrl, "binary")
.then(function(data){
return this.zip.loadAsync(data, {"base64": isBase64});
}.bind(this));
};
/**
* Request
* @param {string} url a url to request from the archive
* @param {[string]} type specify the type of the returned result
* @return {Promise}
*/
Archive.prototype.request = function(url, type){
var deferred = new core.defer();
var response;
var r;
var path = new Path(url);
// If type isn't set, determine it from the file extension
if(!type) {
type = path.extension;
}
if(type == 'blob'){
response = this.getBlob(url);
} else {
response = this.getText(url);
}
if (response) {
response.then(function (r) {
result = this.handleResponse(r, type);
deferred.resolve(result);
}.bind(this));
} else {
deferred.reject({
message : "File not found in the epub: " + url,
stack : new Error().stack
});
}
return deferred.promise;
};
/**
* Handle the response from request
* @private
* @param {any} response
* @param {[string]} type
* @return {any} the parsed result
*/
Archive.prototype.handleResponse = function(response, type){
var r;
if(type == "json") {
r = JSON.parse(response);
}
else
if(core.isXml(type)) {
r = core.parse(response, "text/xml");
}
else
if(type == 'xhtml') {
r = core.parse(response, "application/xhtml+xml");
}
else
if(type == 'html' || type == 'htm') {
r = core.parse(response, "text/html");
} else {
r = response;
}
return r;
};
/**
* Get a Blob from Archive by Url
* @param {string} url
* @param {[string]} mimeType
* @return {Blob}
*/
Archive.prototype.getBlob = function(url, mimeType){
var decodededUrl = window.decodeURIComponent(url.substr(1)); // Remove first slash
var entry = this.zip.file(decodededUrl);
if(entry) {
mimeType = mimeType || mime.lookup(entry.name);
return entry.async("uint8array").then(function(uint8array) {
return new Blob([uint8array], {type : mimeType});
});
}
};
/**
* Get Text from Archive by Url
* @param {string} url
* @param {[string]} encoding
* @return {string}
*/
Archive.prototype.getText = function(url, encoding){
var decodededUrl = window.decodeURIComponent(url.substr(1)); // Remove first slash
var entry = this.zip.file(decodededUrl);
if(entry) {
return entry.async("string").then(function(text) {
return text;
});
}
};
/**
* Get a base64 encoded result from Archive by Url
* @param {string} url
* @param {[string]} mimeType
* @return {string} base64 encoded
*/
Archive.prototype.getBase64 = function(url, mimeType){
var decodededUrl = window.decodeURIComponent(url.substr(1)); // Remove first slash
var entry = this.zip.file(decodededUrl);
if(entry) {
mimeType = mimeType || mime.lookup(entry.name);
return entry.async("base64").then(function(data) {
return "data:" + mimeType + ";base64," + data;
});
}
};
/**
* Create a Url from an unarchived item
* @param {string} url
* @param {[object]} options.base64 use base64 encoding or blob url
* @return {Promise} url promise with Url string
*/
Archive.prototype.createUrl = function(url, options){
var deferred = new core.defer();
var _URL = window.URL || window.webkitURL || window.mozURL;
var tempUrl;
var blob;
var response;
var useBase64 = options && options.base64;
if(url in this.urlCache) {
deferred.resolve(this.urlCache[url]);
return deferred.promise;
}
if (useBase64) {
response = this.getBase64(url);
if (response) {
response.then(function(tempUrl) {
this.urlCache[url] = tempUrl;
deferred.resolve(tempUrl);
}.bind(this));
}
} else {
response = this.getBlob(url);
if (response) {
response.then(function(blob) {
tempUrl = _URL.createObjectURL(blob);
this.urlCache[url] = tempUrl;
deferred.resolve(tempUrl);
}.bind(this));
}
}
if (!response) {
deferred.reject({
message : "File not found in the epub: " + url,
stack : new Error().stack
});
}
return deferred.promise;
};
/**
* Revoke Temp Url for a achive item
* @param {string} url url of the item in the archive
*/
Archive.prototype.revokeUrl = function(url){
var _URL = window.URL || window.webkitURL || window.mozURL;
var fromCache = this.urlCache[url];
if(fromCache) _URL.revokeObjectURL(fromCache);
};
module.exports = Archive;

View file

@ -1,22 +1,21 @@
var EventEmitter = require('event-emitter'); import EventEmitter from 'event-emitter';
var path = require('path'); // import path from 'path';
var core = require('./core'); import {extend, defer} from './utils/core';
var Url = require('./core').Url; import Url from './utils/url';
var Path = require('./core').Path; import Path from './utils/path';
var Spine = require('./spine'); import Spine from './spine';
var Locations = require('./locations'); import Locations from './locations';
var Container = require('./container'); import Container from './container';
var Packaging = require('./packaging'); import Packaging from './packaging';
var Navigation = require('./navigation'); import Navigation from './navigation';
var Resources = require('./resources'); import Resources from './resources';
var PageList = require('./pagelist'); import PageList from './pagelist';
var Rendition = require('./rendition'); import Rendition from './rendition';
var Archive = require('./archive'); import Archive from './archive';
var request = require('./request'); import request from './request';
var EpubCFI = require('./epubcfi'); import EpubCFI from './epubcfi';
// Const const CONTAINER_PATH = "META-INF/container.xml";
var CONTAINER_PATH = "META-INF/container.xml";
/** /**
* Creates a new Book * Creates a new Book
@ -32,459 +31,461 @@ var CONTAINER_PATH = "META-INF/container.xml";
* @example new Book("/path/to/book.epub", {}) * @example new Book("/path/to/book.epub", {})
* @example new Book({ replacements: "blobUrl" }) * @example new Book({ replacements: "blobUrl" })
*/ */
function Book(url, options){ class Book {
constructor(url, options) {
// Allow passing just options to the Book
if (typeof(options) === "undefined"
&& typeof(url) === "object") {
options = url;
url = undefined;
}
// Allow passing just options to the Book this.settings = extend(this.settings || {}, {
if (typeof(options) === "undefined" requestMethod: undefined,
&& typeof(url) === "object") { requestCredentials: undefined,
options = url; requestHeaders: undefined,
url = undefined; encoding: undefined,
} replacements: 'base64'
});
this.settings = core.extend(this.settings || {}, { extend(this.settings, options);
requestMethod: undefined,
requestCredentials: undefined,
requestHeaders: undefined,
encoding: undefined,
replacements: 'base64'
});
core.extend(this.settings, options);
// Promises // Promises
this.opening = new core.defer(); this.opening = new defer();
/** /**
* @property {promise} opened returns after the book is loaded * @property {promise} opened returns after the book is loaded
*/ */
this.opened = this.opening.promise; this.opened = this.opening.promise;
this.isOpen = false; this.isOpen = false;
this.loading = { this.loading = {
manifest: new core.defer(), manifest: new defer(),
spine: new core.defer(), spine: new defer(),
metadata: new core.defer(), metadata: new defer(),
cover: new core.defer(), cover: new defer(),
navigation: new core.defer(), navigation: new defer(),
pageList: new core.defer(), pageList: new defer(),
resources: new core.defer() resources: new defer()
};
this.loaded = {
manifest: this.loading.manifest.promise,
spine: this.loading.spine.promise,
metadata: this.loading.metadata.promise,
cover: this.loading.cover.promise,
navigation: this.loading.navigation.promise,
pageList: this.loading.pageList.promise,
resources: this.loading.resources.promise
};
// this.ready = RSVP.hash(this.loaded);
/**
* @property {promise} ready returns after the book is loaded and parsed
* @private
*/
this.ready = Promise.all([this.loaded.manifest,
this.loaded.spine,
this.loaded.metadata,
this.loaded.cover,
this.loaded.navigation,
this.loaded.resources ]);
// Queue for methods used before opening
this.isRendered = false;
// this._q = queue(this);
/**
* @property {method} request
* @private
*/
this.request = this.settings.requestMethod || request;
/**
* @property {Spine} spine
*/
this.spine = new Spine();
/**
* @property {Locations} locations
*/
this.locations = new Locations(this.spine, this.load.bind(this));
/**
* @property {Navigation} navigation
*/
this.navigation = undefined;
/**
* @property {PageList} pagelist
*/
this.pageList = new PageList();
/**
* @property {Url} url
* @private
*/
this.url = undefined;
/**
* @property {Path} path
* @private
*/
this.path = undefined;
/**
* @property {boolean} archived
* @private
*/
this.archived = false;
if(url) {
this.open(url).catch((error) => {
var err = new Error("Cannot load book at "+ url );
console.error(err);
this.emit("openFailed", err);
console.log(error);
});
}
}; };
this.loaded = { /**
manifest: this.loading.manifest.promise, * Open a epub or url
spine: this.loading.spine.promise, * @param {string} input URL, Path or ArrayBuffer
metadata: this.loading.metadata.promise, * @param {string} [what] to force opening
cover: this.loading.cover.promise, * @returns {Promise} of when the book has been loaded
navigation: this.loading.navigation.promise, * @example book.open("/path/to/book.epub")
pageList: this.loading.pageList.promise, */
resources: this.loading.resources.promise open(input, what) {
var opening;
var type = what || this.determineType(input);
if (type === "binary") {
this.archived = true;
this.url = new Url("/", "");
opening = this.openEpub(input);
} else if (type === "epub") {
this.archived = true;
this.url = new Url("/", "");
opening = this.request(input, 'binary')
.then(this.openEpub.bind(this));
} else if(type == "opf") {
this.url = new Url(input);
opening = this.openPackaging(this.url.Path.toString());
} else {
this.url = new Url(input);
opening = this.openContainer(CONTAINER_PATH)
.then(this.openPackaging.bind(this));
}
return opening;
}
/**
* Open an archived epub
* @private
* @param {binary} data
* @param {[string]} encoding
* @return {Promise}
*/
openEpub(data, encoding) {
return this.unarchive(data, encoding || this.settings.encoding)
.then(() => {
return this.openContainer(CONTAINER_PATH);
})
.then((packagePath) => {
return this.openPackaging(packagePath);
});
}
/**
* Open the epub container
* @private
* @param {string} url
* @return {string} packagePath
*/
openContainer(url) {
return this.load(url)
.then((xml) => {
this.container = new Container(xml);
return this.resolve(this.container.packagePath);
});
}
/**
* Open the Open Packaging Format Xml
* @private
* @param {string} url
* @return {Promise}
*/
openPackaging(url) {
var packageUrl;
this.path = new Path(url);
return this.load(url)
.then((xml) => {
this.packaging = new Packaging(xml);
return this.unpack(this.packaging);
});
}
/**
* Load a resource from the Book
* @param {string} path path to the resource to load
* @return {Promise} returns a promise with the requested resource
*/
load(path) {
var resolved;
if(this.archived) {
resolved = this.resolve(path);
return this.archive.request(resolved);
} else {
resolved = this.resolve(path);
return this.request(resolved, null, this.settings.requestCredentials, this.settings.requestHeaders);
}
}
/**
* Resolve a path to it's absolute position in the Book
* @param {string} path
* @param {[boolean]} absolute force resolving the full URL
* @return {string} the resolved path string
*/
resolve(path, absolute) {
var resolved = path;
var isAbsolute = (path.indexOf('://') > -1);
if (isAbsolute) {
return path;
}
if (this.path) {
resolved = this.path.resolve(path);
}
if(absolute != false && this.url) {
resolved = this.url.resolve(resolved);
}
return resolved;
}
/**
* Determine the type of they input passed to open
* @private
* @param {string} input
* @return {string} binary | directory | epub | opf
*/
determineType(input) {
var url;
var path;
var extension;
if (typeof(input) != "string") {
return "binary";
}
url = new Url(input);
path = url.path();
extension = path.extension;
if (!extension) {
return "directory";
}
if(extension === "epub"){
return "epub";
}
if(extension === "opf"){
return "opf";
}
}; };
// this.ready = RSVP.hash(this.loaded);
/** /**
* @property {promise} ready returns after the book is loaded and parsed * unpack the contents of the Books packageXml
* @private * @private
* @param {document} packageXml XML Document
*/ */
this.ready = Promise.all([this.loaded.manifest, unpack(opf) {
this.loaded.spine, this.package = opf;
this.loaded.metadata,
this.loaded.cover, this.spine.unpack(this.package, this.resolve.bind(this));
this.loaded.navigation,
this.loaded.resources ]); this.resources = new Resources(this.package.manifest, {
archive: this.archive,
resolver: this.resolve.bind(this),
replacements: this.settings.replacements
});
this.loadNavigation(this.package).then(() => {
this.toc = this.navigation.toc;
this.loading.navigation.resolve(this.navigation);
});
this.cover = this.resolve(this.package.coverPath);
// Resolve promises
this.loading.manifest.resolve(this.package.manifest);
this.loading.metadata.resolve(this.package.metadata);
this.loading.spine.resolve(this.spine);
this.loading.cover.resolve(this.cover);
this.loading.resources.resolve(this.resources);
this.loading.pageList.resolve(this.pageList);
// Queue for methods used before opening this.isOpen = true;
this.isRendered = false;
// this._q = core.queue(this);
/** if(this.archived) {
* @property {method} request this.replacements().then(() => {
* @private this.opening.resolve(this);
*/ });
this.request = this.settings.requestMethod || request; } else {
// Resolve book opened promise
/**
* @property {Spine} spine
*/
this.spine = new Spine();
/**
* @property {Locations} locations
*/
this.locations = new Locations(this.spine, this.load.bind(this));
/**
* @property {Navigation} navigation
*/
this.navigation = undefined;
/**
* @property {PageList} pagelist
*/
this.pageList = new PageList();
/**
* @property {Url} url
* @private
*/
this.url = undefined;
/**
* @property {Path} path
* @private
*/
this.path = undefined;
/**
* @property {boolean} archived
* @private
*/
this.archived = false;
if(url) {
this.open(url).catch(function (error) {
var err = new Error("Cannot load book at "+ url );
console.error(err);
this.emit("openFailed", err);
console.log(error);
}.bind(this));
}
};
/**
* Open a epub or url
* @param {string} input URL, Path or ArrayBuffer
* @param {string} [what] to force opening
* @returns {Promise} of when the book has been loaded
* @example book.open("/path/to/book.epub")
*/
Book.prototype.open = function(input, what){
var opening;
var type = what || this.determineType(input);
if (type === "binary") {
this.archived = true;
this.url = new Url("/", "");
opening = this.openEpub(input);
} else if (type === "epub") {
this.archived = true;
this.url = new Url("/", "");
opening = this.request(input, 'binary')
.then(this.openEpub.bind(this));
} else if(type == "opf") {
this.url = new Url(input);
opening = this.openPackaging(this.url.Path.toString());
} else {
this.url = new Url(input);
opening = this.openContainer(CONTAINER_PATH)
.then(this.openPackaging.bind(this));
}
return opening;
};
/**
* Open an archived epub
* @private
* @param {binary} data
* @param {[string]} encoding
* @return {Promise}
*/
Book.prototype.openEpub = function(data, encoding){
return this.unarchive(data, encoding || this.settings.encoding)
.then(function() {
return this.openContainer(CONTAINER_PATH);
}.bind(this))
.then(function(packagePath) {
return this.openPackaging(packagePath);
}.bind(this));
};
/**
* Open the epub container
* @private
* @param {string} url
* @return {string} packagePath
*/
Book.prototype.openContainer = function(url){
return this.load(url)
.then(function(xml) {
this.container = new Container(xml);
return this.resolve(this.container.packagePath);
}.bind(this));
};
/**
* Open the Open Packaging Format Xml
* @private
* @param {string} url
* @return {Promise}
*/
Book.prototype.openPackaging = function(url){
var packageUrl;
this.path = new Path(url);
return this.load(url)
.then(function(xml) {
this.packaging = new Packaging(xml);
return this.unpack(this.packaging);
}.bind(this));
};
/**
* Load a resource from the Book
* @param {string} path path to the resource to load
* @return {Promise} returns a promise with the requested resource
*/
Book.prototype.load = function (path) {
var resolved;
if(this.archived) {
resolved = this.resolve(path);
return this.archive.request(resolved);
} else {
resolved = this.resolve(path);
return this.request(resolved, null, this.settings.requestCredentials, this.settings.requestHeaders);
}
};
/**
* Resolve a path to it's absolute position in the Book
* @param {string} path
* @param {[boolean]} absolute force resolving the full URL
* @return {string} the resolved path string
*/
Book.prototype.resolve = function (path, absolute) {
var resolved = path;
var isAbsolute = (path.indexOf('://') > -1);
if (isAbsolute) {
return path;
}
if (this.path) {
resolved = this.path.resolve(path);
}
if(absolute != false && this.url) {
resolved = this.url.resolve(resolved);
}
return resolved;
}
/**
* Determine the type of they input passed to open
* @private
* @param {string} input
* @return {string} binary | directory | epub | opf
*/
Book.prototype.determineType = function(input) {
var url;
var path;
var extension;
if (typeof(input) != "string") {
return "binary";
}
url = new Url(input);
path = url.path();
extension = path.extension;
if (!extension) {
return "directory";
}
if(extension === "epub"){
return "epub";
}
if(extension === "opf"){
return "opf";
}
};
/**
* unpack the contents of the Books packageXml
* @private
* @param {document} packageXml XML Document
*/
Book.prototype.unpack = function(opf){
this.package = opf;
this.spine.unpack(this.package, this.resolve.bind(this));
this.resources = new Resources(this.package.manifest, {
archive: this.archive,
resolver: this.resolve.bind(this),
replacements: this.settings.replacements
});
this.loadNavigation(this.package).then(function(){
this.toc = this.navigation.toc;
this.loading.navigation.resolve(this.navigation);
}.bind(this));
this.cover = this.resolve(this.package.coverPath);
// Resolve promises
this.loading.manifest.resolve(this.package.manifest);
this.loading.metadata.resolve(this.package.metadata);
this.loading.spine.resolve(this.spine);
this.loading.cover.resolve(this.cover);
this.loading.resources.resolve(this.resources);
this.loading.pageList.resolve(this.pageList);
this.isOpen = true;
if(this.archived) {
this.replacements().then(function() {
this.opening.resolve(this); this.opening.resolve(this);
}.bind(this)); }
} else {
// Resolve book opened promise
this.opening.resolve(this);
} }
}; /**
* Load Navigation and PageList from package
* @private
* @param {document} opf XML Document
*/
loadNavigation(opf) {
var navPath = opf.navPath || opf.ncxPath;
/** if (!navPath) {
* Load Navigation and PageList from package return;
* @private }
* @param {document} opf XML Document
*/
Book.prototype.loadNavigation = function(opf){
var navPath = opf.navPath || opf.ncxPath;
if (!navPath) { return this.load(navPath, 'xml')
return; .then((xml) => {
this.navigation = new Navigation(xml);
this.pageList = new PageList(xml);
return this.navigation;
});
} }
return this.load(navPath, 'xml') /**
.then(function(xml) { * Alias for book.spine.get
this.navigation = new Navigation(xml); * @param {string} target
this.pageList = new PageList(xml); */
return this.navigation; section(target) {
}.bind(this)); return this.spine.get(target);
}; }
/** /**
* Alias for book.spine.get * Sugar to render a book
* @param {string} target * @param {element} element element to add the views to
*/ * @param {[object]} options
Book.prototype.section = function(target) { * @return {Rendition}
return this.spine.get(target); */
}; renderTo(element, options) {
// var renderMethod = (options && options.method) ?
// options.method :
// "single";
/** this.rendition = new Rendition(this, options);
* Sugar to render a book this.rendition.attachTo(element);
* @param {element} element element to add the views to
* @param {[object]} options
* @return {Rendition}
*/
Book.prototype.renderTo = function(element, options) {
// var renderMethod = (options && options.method) ?
// options.method :
// "single";
this.rendition = new Rendition(this, options); return this.rendition;
this.rendition.attachTo(element); };
return this.rendition; /**
}; * Set if request should use withCredentials
* @param {boolean} credentials
*/
setRequestCredentials(credentials) {
this.settings.requestCredentials = credentials;
};
/** /**
* Set if request should use withCredentials * Set headers request should use
* @param {boolean} credentials * @param {object} headers
*/ */
Book.prototype.setRequestCredentials = function(credentials) { setRequestHeaders(headers) {
this.settings.requestCredentials = credentials; this.settings.requestHeaders = headers;
}; };
/** /**
* Set headers request should use * Unarchive a zipped epub
* @param {object} headers * @private
*/ * @param {binary} input epub data
Book.prototype.setRequestHeaders = function(headers) { * @param {[string]} encoding
this.settings.requestHeaders = headers; * @return {Archive}
}; */
unarchive(input, encoding) {
this.archive = new Archive();
return this.archive.open(input, encoding);
}
/** /**
* Unarchive a zipped epub * Get the cover url
* @private * @return {string} coverUrl
* @param {binary} input epub data */
* @param {[string]} encoding coverUrl() {
* @return {Archive} var retrieved = this.loaded.cover.
*/ then((url) => {
Book.prototype.unarchive = function(input, encoding){ if(this.archived) {
this.archive = new Archive(); // return this.archive.createUrl(this.cover);
return this.archive.open(input, encoding); return this.resources.get(this.cover);
}; }else{
return this.cover;
/** }
* Get the cover url });
* @return {string} coverUrl
*/
Book.prototype.coverUrl = function(){
var retrieved = this.loaded.cover.
then(function(url) {
if(this.archived) {
// return this.archive.createUrl(this.cover);
return this.resources.get(this.cover);
}else{
return this.cover;
}
}.bind(this));
return retrieved; return retrieved;
}; }
/** /**
* load replacement urls * load replacement urls
* @private * @private
* @return {Promise} completed loading urls * @return {Promise} completed loading urls
*/ */
Book.prototype.replacements = function(){ replacements() {
this.spine.hooks.serialize.register(function(output, section) { this.spine.hooks.serialize.register((output, section) => {
section.output = this.resources.substitute(output, section.url); section.output = this.resources.substitute(output, section.url);
}.bind(this)); });
return this.resources.replacements(). return this.resources.replacements().
then(function() { then(() => {
return this.resources.replaceCss(); return this.resources.replaceCss();
}.bind(this)); });
}; }
/** /**
* Find a DOM Range for a given CFI Range * Find a DOM Range for a given CFI Range
* @param {EpubCFI} cfiRange a epub cfi range * @param {EpubCFI} cfiRange a epub cfi range
* @return {Range} * @return {Range}
*/ */
Book.prototype.range = function(cfiRange) { range(cfiRange) {
var cfi = new EpubCFI(cfiRange); var cfi = new EpubCFI(cfiRange);
var item = this.spine.get(cfi.spinePos); var item = this.spine.get(cfi.spinePos);
return item.load().then(function (contents) { return item.load().then(function (contents) {
var range = cfi.toRange(item.document); var range = cfi.toRange(item.document);
return range; return range;
}) })
}; }
/** /**
* Generates the Book Key using the identifer in the manifest or other string provided * 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 * @return {string} key
*/ */
Book.prototype.key = function(identifier){ key(identifier) {
var ident = identifier || this.package.metadata.identifier || this.url.filename; var ident = identifier || this.package.metadata.identifier || this.url.filename;
return "epubjs:" + (EPUBJS_VERSION || ePub.VERSION) + ":" + ident; return "epubjs:" + (EPUBJS_VERSION || ePub.VERSION) + ":" + ident;
}; }
}
//-- Enable binding events to book //-- Enable binding events to book
EventEmitter(Book.prototype); EventEmitter(Book.prototype);
module.exports = Book; export default Book;

View file

@ -1,41 +1,43 @@
var path = require('path'); import path from 'path-webpack';
var core = require('./core'); import {qs} from './utils/core';
var EpubCFI = require('./epubcfi'); import EpubCFI from './epubcfi';
/** /**
* Handles Parsing and Accessing an Epub Container * Handles Parsing and Accessing an Epub Container
* @class * @class
* @param {[document]} containerDocument xml document * @param {[document]} containerDocument xml document
*/ */
function Container(containerDocument) { class Container {
if (containerDocument) { constructor(containerDocument) {
this.parse(containerDocument); if (containerDocument) {
} this.parse(containerDocument);
};
/**
* Parse the Container XML
* @param {document} containerDocument
*/
Container.prototype.parse = function(containerDocument){
//-- <rootfile full-path="OPS/package.opf" media-type="application/oebps-package+xml"/>
var rootfile, fullpath, folder, encoding;
if(!containerDocument) {
console.error("Container File Not Found");
return;
} }
};
rootfile = core.qs(containerDocument, "rootfile"); /**
* Parse the Container XML
* @param {document} containerDocument
*/
parse(containerDocument){
//-- <rootfile full-path="OPS/package.opf" media-type="application/oebps-package+xml"/>
var rootfile, fullpath, folder, encoding;
if(!rootfile) { if(!containerDocument) {
console.error("No RootFile Found"); console.error("Container File Not Found");
return; return;
} }
this.packagePath = rootfile.getAttribute('full-path'); rootfile = qs(containerDocument, "rootfile");
this.directory = path.dirname(this.packagePath);
this.encoding = containerDocument.xmlEncoding;
};
module.exports = Container; if(!rootfile) {
console.error("No RootFile Found");
return;
}
this.packagePath = rootfile.getAttribute('full-path');
this.directory = path.dirname(this.packagePath);
this.encoding = containerDocument.xmlEncoding;
};
}
export default Container;

File diff suppressed because it is too large Load diff

View file

@ -1,7 +1,7 @@
var Book = require('./book'); import Book from './book';
var EpubCFI = require('./epubcfi'); import EpubCFI from './epubcfi';
var Rendition = require('./rendition'); import Rendition from './rendition';
var Contents = require('./contents'); import Contents from './contents';
/** /**
* Creates a new Book * Creates a new Book
@ -51,4 +51,4 @@ ePub.register.view("iframe", require('./managers/views/iframe'));
ePub.register.manager("default", require('./managers/default')); ePub.register.manager("default", require('./managers/default'));
ePub.register.manager("continuous", require('./managers/continuous')); ePub.register.manager("continuous", require('./managers/continuous'));
module.exports = ePub; export default ePub;

File diff suppressed because it is too large Load diff

View file

@ -5,58 +5,59 @@
* @param {any} context scope of this * @param {any} context scope of this
* @example this.content = new EPUBJS.Hook(this); * @example this.content = new EPUBJS.Hook(this);
*/ */
function Hook(context){ class Hook {
this.context = context || this; constructor(context){
this.hooks = []; this.context = context || this;
}; this.hooks = [];
};
/** /**
* Adds a function to be run before a hook completes * Adds a function to be run before a hook completes
* @example this.content.register(function(){...}); * @example this.content.register(function(){...});
*/ */
Hook.prototype.register = function(){ register(){
for(var i = 0; i < arguments.length; ++i) { for(var i = 0; i < arguments.length; ++i) {
if (typeof arguments[i] === "function") { if (typeof arguments[i] === "function") {
this.hooks.push(arguments[i]); this.hooks.push(arguments[i]);
} else { } else {
// unpack array // unpack array
for(var j = 0; j < arguments[i].length; ++j) { for(var j = 0; j < arguments[i].length; ++j) {
this.hooks.push(arguments[i][j]); this.hooks.push(arguments[i][j]);
}
} }
} }
} };
};
/** /**
* Triggers a hook to run all functions * Triggers a hook to run all functions
* @example this.content.trigger(args).then(function(){...}); * @example this.content.trigger(args).then(function(){...});
*/ */
Hook.prototype.trigger = function(){ trigger(){
var args = arguments; var args = arguments;
var context = this.context; var context = this.context;
var promises = []; var promises = [];
this.hooks.forEach(function(task, i) { this.hooks.forEach(function(task, i) {
var executing = task.apply(context, args); var executing = task.apply(context, args);
if(executing && typeof executing["then"] === "function") { if(executing && typeof executing["then"] === "function") {
// Task is a function that returns a promise // Task is a function that returns a promise
promises.push(executing); promises.push(executing);
} }
// Otherwise Task resolves immediately, continue // Otherwise Task resolves immediately, continue
}); });
return Promise.all(promises); return Promise.all(promises);
}; };
// Adds a function to be run before a hook completes // Adds a function to be run before a hook completes
Hook.prototype.list = function(){ list(){
return this.hooks; return this.hooks;
}; };
Hook.prototype.clear = function(){ clear(){
return this.hooks = []; return this.hooks = [];
}; };
}
module.exports = Hook; export default Hook;

15
src/index.js Normal file
View file

@ -0,0 +1,15 @@
import Book from './book';
import EpubCFI from './epubcfi';
import Rendition from './rendition';
import Contents from './contents';
import Layout from './layout';
import ePub from './epub';
export default ePub;
export {
Book,
EpubCFI,
Rendition,
Contents,
Layout
};

View file

@ -1,4 +1,4 @@
var core = require('./core'); import core from './utils/core';
/** /**
* Figures out the CSS to apply for a layout * Figures out the CSS to apply for a layout
@ -9,146 +9,148 @@ var core = require('./core');
* @param {[int=800]} settings.minSpreadWidth * @param {[int=800]} settings.minSpreadWidth
* @param {[boolean=false]} settings.evenSpreads * @param {[boolean=false]} settings.evenSpreads
*/ */
function Layout(settings){ class Layout {
this.name = settings.layout || "reflowable"; constructor(settings) {
this._spread = (settings.spread === "none") ? false : true; this.name = settings.layout || "reflowable";
this._minSpreadWidth = settings.minSpreadWidth || 800; this._spread = (settings.spread === "none") ? false : true;
this._evenSpreads = settings.evenSpreads || false; this._minSpreadWidth = settings.minSpreadWidth || 800;
this._evenSpreads = settings.evenSpreads || false;
if (settings.flow === "scrolled-continuous" || if (settings.flow === "scrolled-continuous" ||
settings.flow === "scrolled-doc") { settings.flow === "scrolled-doc") {
this._flow = "scrolled"; this._flow = "scrolled";
} else { } else {
this._flow = "paginated"; this._flow = "paginated";
} }
this.width = 0; this.width = 0;
this.height = 0; this.height = 0;
this.spreadWidth = 0; this.spreadWidth = 0;
this.delta = 0; this.delta = 0;
this.columnWidth = 0; this.columnWidth = 0;
this.gap = 0; this.gap = 0;
this.divisor = 1; this.divisor = 1;
};
/**
* Switch the flow between paginated and scrolled
* @param {string} flow paginated | scrolled
*/
Layout.prototype.flow = function(flow) {
this._flow = (flow === "paginated") ? "paginated" : "scrolled";
}
/**
* Switch between using spreads or not, and set the
* width at which they switch to single.
* @param {string} spread true | false
* @param {boolean} min integer in pixels
*/
Layout.prototype.spread = function(spread, min) {
this._spread = (spread === "none") ? false : true;
if (min >= 0) {
this._minSpreadWidth = min;
}
}
/**
* Calculate the dimensions of the pagination
* @param {number} _width [description]
* @param {number} _height [description]
* @param {number} _gap [description]
*/
Layout.prototype.calculate = function(_width, _height, _gap){
var divisor = 1;
var gap = _gap || 0;
//-- Check the width and create even width columns
var fullWidth = Math.floor(_width);
var width = _width;
var section = Math.floor(width / 8);
var colWidth;
var spreadWidth;
var delta;
if (this._spread && width >= this._minSpreadWidth) {
divisor = 2;
} else {
divisor = 1;
}
if (this.name === "reflowable" && this._flow === "paginated" && !(_gap >= 0)) {
gap = ((section % 2 === 0) ? section : section - 1);
}
if (this.name === "pre-paginated" ) {
gap = 0;
}
//-- Double Page
if(divisor > 1) {
colWidth = (width - gap) / divisor;
} else {
colWidth = width;
}
if (this.name === "pre-paginated" && divisor > 1) {
width = colWidth;
}
spreadWidth = colWidth * divisor;
delta = (colWidth + gap) * divisor;
this.width = width;
this.height = _height;
this.spreadWidth = spreadWidth;
this.delta = delta;
this.columnWidth = colWidth;
this.gap = gap;
this.divisor = divisor;
};
/**
* Apply Css to a Document
* @param {Contents} contents
* @return {[Promise]}
*/
Layout.prototype.format = function(contents){
var formating;
if (this.name === "pre-paginated") {
formating = contents.fit(this.columnWidth, this.height);
} else if (this._flow === "paginated") {
formating = contents.columns(this.width, this.height, this.columnWidth, this.gap);
} else { // scrolled
formating = contents.size(this.width, null);
}
return formating; // might be a promise in some View Managers
};
/**
* Count number of pages
* @param {number} totalWidth
* @return {number} spreads
* @return {number} pages
*/
Layout.prototype.count = function(totalWidth) {
// var totalWidth = contents.scrollWidth();
var spreads = Math.ceil( totalWidth / this.spreadWidth);
return {
spreads : spreads,
pages : spreads * this.divisor
}; };
};
module.exports = Layout; /**
* Switch the flow between paginated and scrolled
* @param {string} flow paginated | scrolled
*/
flow(flow) {
this._flow = (flow === "paginated") ? "paginated" : "scrolled";
}
/**
* Switch between using spreads or not, and set the
* width at which they switch to single.
* @param {string} spread true | false
* @param {boolean} min integer in pixels
*/
spread(spread, min) {
this._spread = (spread === "none") ? false : true;
if (min >= 0) {
this._minSpreadWidth = min;
}
}
/**
* Calculate the dimensions of the pagination
* @param {number} _width [description]
* @param {number} _height [description]
* @param {number} _gap [description]
*/
calculate(_width, _height, _gap){
var divisor = 1;
var gap = _gap || 0;
//-- Check the width and create even width columns
var fullWidth = Math.floor(_width);
var width = _width;
var section = Math.floor(width / 8);
var colWidth;
var spreadWidth;
var delta;
if (this._spread && width >= this._minSpreadWidth) {
divisor = 2;
} else {
divisor = 1;
}
if (this.name === "reflowable" && this._flow === "paginated" && !(_gap >= 0)) {
gap = ((section % 2 === 0) ? section : section - 1);
}
if (this.name === "pre-paginated" ) {
gap = 0;
}
//-- Double Page
if(divisor > 1) {
colWidth = (width - gap) / divisor;
} else {
colWidth = width;
}
if (this.name === "pre-paginated" && divisor > 1) {
width = colWidth;
}
spreadWidth = colWidth * divisor;
delta = (colWidth + gap) * divisor;
this.width = width;
this.height = _height;
this.spreadWidth = spreadWidth;
this.delta = delta;
this.columnWidth = colWidth;
this.gap = gap;
this.divisor = divisor;
};
/**
* Apply Css to a Document
* @param {Contents} contents
* @return {[Promise]}
*/
format(contents){
var formating;
if (this.name === "pre-paginated") {
formating = contents.fit(this.columnWidth, this.height);
} else if (this._flow === "paginated") {
formating = contents.columns(this.width, this.height, this.columnWidth, this.gap);
} else { // scrolled
formating = contents.size(this.width, null);
}
return formating; // might be a promise in some View Managers
};
/**
* Count number of pages
* @param {number} totalWidth
* @return {number} spreads
* @return {number} pages
*/
count(totalWidth) {
// var totalWidth = contents.scrollWidth();
var spreads = Math.ceil( totalWidth / this.spreadWidth);
return {
spreads : spreads,
pages : spreads * this.divisor
};
};
}
export default Layout;

View file

@ -1,257 +1,258 @@
var core = require('./core'); import {qs, sprint, locationOf} from './utils/core';
var Queue = require('./queue'); import Queue from './queue';
var EpubCFI = require('./epubcfi'); import EpubCFI from './epubcfi';
var EventEmitter = require('event-emitter'); import EventEmitter from 'event-emitter';
/** /**
* Find Locations for a Book * Find Locations for a Book
* @param {Spine} spine * @param {Spine} spine
* @param {request} request * @param {request} request
*/ */
function Locations(spine, request) { class Locations {
this.spine = spine; constructor(spine, request) {
this.request = request; this.spine = spine;
this.request = request;
this.q = new Queue(this); this.q = new Queue(this);
this.epubcfi = new EpubCFI(); this.epubcfi = new EpubCFI();
this._locations = []; this._locations = [];
this.total = 0; this.total = 0;
this.break = 150; this.break = 150;
this._current = 0; this._current = 0;
};
/**
* Load all of sections in the book to generate locations
* @param {int} chars how many chars to split on
* @return {object} locations
*/
Locations.prototype.generate = function(chars) {
if (chars) {
this.break = chars;
}
this.q.pause();
this.spine.each(function(section) {
this.q.enqueue(this.process.bind(this), section);
}.bind(this));
return this.q.run().then(function() {
this.total = this._locations.length-1;
if (this._currentCfi) {
this.currentLocation = this._currentCfi;
}
return this._locations;
// console.log(this.percentage(this.book.rendition.location.start), this.percentage(this.book.rendition.location.end));
}.bind(this));
};
Locations.prototype.createRange = function () {
return {
startContainer: undefined,
startOffset: undefined,
endContainer: undefined,
endOffset: undefined
}
};
Locations.prototype.process = function(section) {
return section.load(this.request)
.then(function(contents) {
var locations = this.parse(contents, section.cfiBase);
this._locations = this._locations.concat(locations);
}.bind(this));
};
Locations.prototype.parse = function(contents, cfiBase, chars) {
var locations = [];
var range;
var doc = contents.ownerDocument;
var body = core.qs(doc, 'body');
var counter = 0;
var prev;
var _break = chars || this.break;
var parser = function(node) {
var len = node.length;
var dist;
var pos = 0;
if (node.textContent.trim().length === 0) {
return false; // continue
}
// Start range
if (counter == 0) {
range = this.createRange();
range.startContainer = node;
range.startOffset = 0;
}
dist = _break - counter;
// Node is smaller than a break,
// skip over it
if(dist > len){
counter += len;
pos = len;
}
while (pos < len) {
// counter = this.break;
pos += dist;
// Gone over
if(pos >= len){
// Continue counter for next node
counter = len - (pos - _break);
// At End
} else {
// End the previous range
range.endContainer = node;
range.endOffset = pos;
// cfi = section.cfiFromRange(range);
cfi = new EpubCFI(range, cfiBase).toString();
locations.push(cfi);
counter = 0;
// Start new range
pos += 1;
range = this.createRange();
range.startContainer = node;
range.startOffset = pos;
dist = _break;
}
}
prev = node;
}; };
core.sprint(body, parser.bind(this)); /**
* Load all of sections in the book to generate locations
* @param {int} chars how many chars to split on
* @return {object} locations
*/
generate(chars) {
// Close remaining if (chars) {
if (range && range.startContainer && prev) { this.break = chars;
range.endContainer = prev; }
range.endOffset = prev.length;
// cfi = section.cfiFromRange(range);
cfi = new EpubCFI(range, cfiBase).toString();
locations.push(cfi);
counter = 0;
}
return locations; this.q.pause();
};
Locations.prototype.locationFromCfi = function(cfi){ this.spine.each(function(section) {
// Check if the location has not been set yet
if(this._locations.length === 0) {
return -1;
}
return core.locationOf(cfi.start, this._locations, this.epubcfi.compare);
};
Locations.prototype.percentageFromCfi = function(cfi) { this.q.enqueue(this.process.bind(this), section);
if(this._locations.length === 0) {
return null;
}
// Find closest cfi
var loc = this.locationFromCfi(cfi);
// Get percentage in total
return this.percentageFromLocation(loc);
};
Locations.prototype.percentageFromLocation = function(loc) { }.bind(this));
if (!loc || !this.total) {
return 0;
}
return (loc / this.total);
};
Locations.prototype.cfiFromLocation = function(loc){ return this.q.run().then(function() {
var cfi = -1; this.total = this._locations.length-1;
// check that pg is an int
if(typeof loc != "number"){
loc = parseInt(pg);
}
if(loc >= 0 && loc < this._locations.length) { if (this._currentCfi) {
cfi = this._locations[loc]; this.currentLocation = this._currentCfi;
} }
return cfi; return this._locations;
}; // console.log(this.percentage(this.book.rendition.location.start), this.percentage(this.book.rendition.location.end));
}.bind(this));
Locations.prototype.cfiFromPercentage = function(value){ };
var percentage = (value > 1) ? value / 100 : value; // Normalize value to 0-1
var loc = Math.ceil(this.total * percentage);
return this.cfiFromLocation(loc); createRange () {
}; return {
startContainer: undefined,
startOffset: undefined,
endContainer: undefined,
endOffset: undefined
}
};
Locations.prototype.load = function(locations){ process(section) {
this._locations = JSON.parse(locations);
this.total = this._locations.length-1;
return this._locations;
};
Locations.prototype.save = function(json){ return section.load(this.request)
return JSON.stringify(this._locations); .then(function(contents) {
}; var locations = this.parse(contents, section.cfiBase);
this._locations = this._locations.concat(locations);
}.bind(this));
Locations.prototype.getCurrent = function(json){ };
return this._current;
};
Locations.prototype.setCurrent = function(curr){ parse(contents, cfiBase, chars) {
var loc; var locations = [];
var range;
var doc = contents.ownerDocument;
var body = qs(doc, 'body');
var counter = 0;
var prev;
var _break = chars || this.break;
var parser = function(node) {
var len = node.length;
var dist;
var pos = 0;
if(typeof curr == "string"){ if (node.textContent.trim().length === 0) {
this._currentCfi = curr; return false; // continue
} else if (typeof curr == "number") { }
this._current = curr;
} else {
return;
}
if(this._locations.length === 0) { // Start range
return; if (counter == 0) {
} range = this.createRange();
range.startContainer = node;
range.startOffset = 0;
}
if(typeof curr == "string"){ dist = _break - counter;
loc = this.locationFromCfi(curr);
this._current = loc;
} else {
loc = curr;
}
this.emit("changed", { // Node is smaller than a break,
percentage: this.percentageFromLocation(loc) // skip over it
}); if(dist > len){
}; counter += len;
pos = len;
}
Object.defineProperty(Locations.prototype, 'currentLocation', { while (pos < len) {
get: function () { // counter = this.break;
pos += dist;
// Gone over
if(pos >= len){
// Continue counter for next node
counter = len - (pos - _break);
// At End
} else {
// End the previous range
range.endContainer = node;
range.endOffset = pos;
// cfi = section.cfiFromRange(range);
let cfi = new EpubCFI(range, cfiBase).toString();
locations.push(cfi);
counter = 0;
// Start new range
pos += 1;
range = this.createRange();
range.startContainer = node;
range.startOffset = pos;
dist = _break;
}
}
prev = node;
};
sprint(body, parser.bind(this));
// Close remaining
if (range && range.startContainer && prev) {
range.endContainer = prev;
range.endOffset = prev.length;
// cfi = section.cfiFromRange(range);
let cfi = new EpubCFI(range, cfiBase).toString();
locations.push(cfi);
counter = 0;
}
return locations;
};
locationFromCfi(cfi){
// Check if the location has not been set yet
if(this._locations.length === 0) {
return -1;
}
return locationOf(cfi.start, this._locations, this.epubcfi.compare);
};
percentageFromCfi(cfi) {
if(this._locations.length === 0) {
return null;
}
// Find closest cfi
var loc = this.locationFromCfi(cfi);
// Get percentage in total
return this.percentageFromLocation(loc);
};
percentageFromLocation(loc) {
if (!loc || !this.total) {
return 0;
}
return (loc / this.total);
};
cfiFromLocation(loc){
var cfi = -1;
// check that pg is an int
if(typeof loc != "number"){
loc = parseInt(pg);
}
if(loc >= 0 && loc < this._locations.length) {
cfi = this._locations[loc];
}
return cfi;
};
cfiFromPercentage(value){
var percentage = (value > 1) ? value / 100 : value; // Normalize value to 0-1
var loc = Math.ceil(this.total * percentage);
return this.cfiFromLocation(loc);
};
load(locations){
this._locations = JSON.parse(locations);
this.total = this._locations.length-1;
return this._locations;
};
save(json){
return JSON.stringify(this._locations);
};
getCurrent(json){
return this._current; return this._current;
}, };
set: function (curr) {
setCurrent(curr){
var loc;
if(typeof curr == "string"){
this._currentCfi = curr;
} else if (typeof curr == "number") {
this._current = curr;
} else {
return;
}
if(this._locations.length === 0) {
return;
}
if(typeof curr == "string"){
loc = this.locationFromCfi(curr);
this._current = loc;
} else {
loc = curr;
}
this.emit("changed", {
percentage: this.percentageFromLocation(loc)
});
};
get currentLocation() {
return this._current;
}
set currentLocation(curr) {
this.setCurrent(curr); this.setCurrent(curr);
} }
});
Locations.prototype.length = function () { length () {
return this._locations.length; return this._locations.length;
}; };
}
EventEmitter(Locations.prototype); EventEmitter(Locations.prototype);
module.exports = Locations; export default Locations;

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -1,231 +1,231 @@
var core = require('../../core'); import {uuid, isNumber, isElement, windowBounds} from '../../utils/core';
function Stage(_options) { class Stage {
this.settings = _options || {}; constructor(_options) {
this.id = "epubjs-container-" + core.uuid(); this.settings = _options || {};
this.id = "epubjs-container-" + uuid();
this.container = this.create(this.settings); this.container = this.create(this.settings);
if(this.settings.hidden) { if(this.settings.hidden) {
this.wrapper = this.wrap(this.container); this.wrapper = this.wrap(this.container);
}
}
/*
* Creates an element to render to.
* Resizes to passed width and height or to the elements size
*/
Stage.prototype.create = function(options){
var height = options.height;// !== false ? options.height : "100%";
var width = options.width;// !== false ? options.width : "100%";
var overflow = options.overflow || false;
var axis = options.axis || "vertical";
if(options.height && core.isNumber(options.height)) {
height = options.height + "px";
}
if(options.width && core.isNumber(options.width)) {
width = options.width + "px";
}
// Create new container element
container = document.createElement("div");
container.id = this.id;
container.classList.add("epub-container");
// Style Element
// container.style.fontSize = "0";
container.style.wordSpacing = "0";
container.style.lineHeight = "0";
container.style.verticalAlign = "top";
if(axis === "horizontal") {
container.style.whiteSpace = "nowrap";
}
if(width){
container.style.width = width;
}
if(height){
container.style.height = height;
}
if (overflow) {
container.style.overflow = overflow;
}
return container;
};
Stage.wrap = function(container) {
var wrapper = document.createElement("div");
wrapper.style.visibility = "hidden";
wrapper.style.overflow = "hidden";
wrapper.style.width = "0";
wrapper.style.height = "0";
wrapper.appendChild(container);
return wrapper;
};
Stage.prototype.getElement = function(_element){
var element;
if(core.isElement(_element)) {
element = _element;
} else if (typeof _element === "string") {
element = document.getElementById(_element);
}
if(!element){
console.error("Not an Element");
return;
}
return element;
};
Stage.prototype.attachTo = function(what){
var element = this.getElement(what);
var base;
if(!element){
return;
}
if(this.settings.hidden) {
base = this.wrapper;
} else {
base = this.container;
}
element.appendChild(base);
this.element = element;
return element;
};
Stage.prototype.getContainer = function() {
return this.container;
};
Stage.prototype.onResize = function(func){
// Only listen to window for resize event if width and height are not fixed.
// This applies if it is set to a percent or auto.
if(!core.isNumber(this.settings.width) ||
!core.isNumber(this.settings.height) ) {
window.addEventListener("resize", func, false);
}
};
Stage.prototype.size = function(width, height){
var bounds;
// var width = _width || this.settings.width;
// var height = _height || this.settings.height;
// If width or height are set to false, inherit them from containing element
if(width === null) {
bounds = this.element.getBoundingClientRect();
if(bounds.width) {
width = bounds.width;
this.container.style.width = bounds.width + "px";
}
}
if(height === null) {
bounds = bounds || this.element.getBoundingClientRect();
if(bounds.height) {
height = bounds.height;
this.container.style.height = bounds.height + "px";
} }
} }
if(!core.isNumber(width)) { /*
bounds = this.container.getBoundingClientRect(); * Creates an element to render to.
width = bounds.width; * Resizes to passed width and height or to the elements size
//height = bounds.height; */
} create(options){
var height = options.height;// !== false ? options.height : "100%";
var width = options.width;// !== false ? options.width : "100%";
var overflow = options.overflow || false;
var axis = options.axis || "vertical";
if(!core.isNumber(height)) { if(options.height && isNumber(options.height)) {
bounds = bounds || this.container.getBoundingClientRect(); height = options.height + "px";
//width = bounds.width; }
height = bounds.height;
}
if(options.width && isNumber(options.width)) {
width = options.width + "px";
}
this.containerStyles = window.getComputedStyle(this.container); // Create new container element
let container = document.createElement("div");
this.containerPadding = { container.id = this.id;
left: parseFloat(this.containerStyles["padding-left"]) || 0, container.classList.add("epub-container");
right: parseFloat(this.containerStyles["padding-right"]) || 0,
top: parseFloat(this.containerStyles["padding-top"]) || 0, // Style Element
bottom: parseFloat(this.containerStyles["padding-bottom"]) || 0 // container.style.fontSize = "0";
container.style.wordSpacing = "0";
container.style.lineHeight = "0";
container.style.verticalAlign = "top";
if(axis === "horizontal") {
container.style.whiteSpace = "nowrap";
}
if(width){
container.style.width = width;
}
if(height){
container.style.height = height;
}
if (overflow) {
container.style.overflow = overflow;
}
return container;
}; };
return { wrap(container) {
width: width - var wrapper = document.createElement("div");
this.containerPadding.left -
this.containerPadding.right, wrapper.style.visibility = "hidden";
height: height - wrapper.style.overflow = "hidden";
this.containerPadding.top - wrapper.style.width = "0";
this.containerPadding.bottom wrapper.style.height = "0";
wrapper.appendChild(container);
return wrapper;
}; };
};
Stage.prototype.bounds = function(){ getElement(_element){
var element;
if(!this.container) { if(isElement(_element)) {
return core.windowBounds(); element = _element;
} else { } else if (typeof _element === "string") {
return this.container.getBoundingClientRect(); element = document.getElementById(_element);
} }
} if(!element){
console.error("Not an Element");
return;
}
Stage.prototype.getSheet = function(){ return element;
var style = document.createElement("style"); };
// WebKit hack --> https://davidwalsh.name/add-rules-stylesheets attachTo(what){
style.appendChild(document.createTextNode(""));
document.head.appendChild(style); var element = this.getElement(what);
var base;
return style.sheet; if(!element){
} return;
}
Stage.prototype.addStyleRules = function(selector, rulesArray){ if(this.settings.hidden) {
var scope = "#" + this.id + " "; base = this.wrapper;
var rules = ""; } else {
base = this.container;
}
if(!this.sheet){ element.appendChild(base);
this.sheet = this.getSheet();
}
rulesArray.forEach(function(set) { this.element = element;
for (var prop in set) {
if(set.hasOwnProperty(prop)) { return element;
rules += prop + ":" + set[prop] + ";";
};
getContainer() {
return this.container;
};
onResize(func){
// Only listen to window for resize event if width and height are not fixed.
// This applies if it is set to a percent or auto.
if(!isNumber(this.settings.width) ||
!isNumber(this.settings.height) ) {
window.addEventListener("resize", func, false);
}
};
size(width, height){
var bounds;
// var width = _width || this.settings.width;
// var height = _height || this.settings.height;
// If width or height are set to false, inherit them from containing element
if(width === null) {
bounds = this.element.getBoundingClientRect();
if(bounds.width) {
width = bounds.width;
this.container.style.width = bounds.width + "px";
} }
} }
})
this.sheet.insertRule(scope + selector + " {" + rules + "}", 0); if(height === null) {
bounds = bounds || this.element.getBoundingClientRect();
if(bounds.height) {
height = bounds.height;
this.container.style.height = bounds.height + "px";
}
}
if(!isNumber(width)) {
bounds = this.container.getBoundingClientRect();
width = bounds.width;
//height = bounds.height;
}
if(!isNumber(height)) {
bounds = bounds || this.container.getBoundingClientRect();
//width = bounds.width;
height = bounds.height;
}
this.containerStyles = window.getComputedStyle(this.container);
this.containerPadding = {
left: parseFloat(this.containerStyles["padding-left"]) || 0,
right: parseFloat(this.containerStyles["padding-right"]) || 0,
top: parseFloat(this.containerStyles["padding-top"]) || 0,
bottom: parseFloat(this.containerStyles["padding-bottom"]) || 0
};
return {
width: width -
this.containerPadding.left -
this.containerPadding.right,
height: height -
this.containerPadding.top -
this.containerPadding.bottom
};
};
bounds(){
if(!this.container) {
return windowBounds();
} else {
return this.container.getBoundingClientRect();
}
}
getSheet(){
var style = document.createElement("style");
// WebKit hack --> https://davidwalsh.name/add-rules-stylesheets
style.appendChild(document.createTextNode(""));
document.head.appendChild(style);
return style.sheet;
}
addStyleRules(selector, rulesArray){
var scope = "#" + this.id + " ";
var rules = "";
if(!this.sheet){
this.sheet = this.getSheet();
}
rulesArray.forEach(function(set) {
for (var prop in set) {
if(set.hasOwnProperty(prop)) {
rules += prop + ":" + set[prop] + ";";
}
}
})
this.sheet.insertRule(scope + selector + " {" + rules + "}", 0);
}
} }
export default Stage;
module.exports = Stage;

View file

@ -1,165 +1,167 @@
function Views(container) { class Views {
this.container = container; constructor(container) {
this._views = []; this.container = container;
this.length = 0; this._views = [];
this.hidden = false; this.length = 0;
}; this.hidden = false;
};
Views.prototype.all = function() { all() {
return this._views; return this._views;
}; };
Views.prototype.first = function() { first() {
return this._views[0]; return this._views[0];
}; };
Views.prototype.last = function() { last() {
return this._views[this._views.length-1]; return this._views[this._views.length-1];
}; };
Views.prototype.indexOf = function(view) { indexOf(view) {
return this._views.indexOf(view); return this._views.indexOf(view);
}; };
Views.prototype.slice = function() { slice() {
return this._views.slice.apply(this._views, arguments); return this._views.slice.apply(this._views, arguments);
}; };
Views.prototype.get = function(i) { get(i) {
return this._views[i]; return this._views[i];
}; };
Views.prototype.append = function(view){ append(view){
this._views.push(view); this._views.push(view);
if(this.container){ if(this.container){
this.container.appendChild(view.element);
}
this.length++;
return view;
};
Views.prototype.prepend = function(view){
this._views.unshift(view);
if(this.container){
this.container.insertBefore(view.element, this.container.firstChild);
}
this.length++;
return view;
};
Views.prototype.insert = function(view, index) {
this._views.splice(index, 0, view);
if(this.container){
if(index < this.container.children.length){
this.container.insertBefore(view.element, this.container.children[index]);
} else {
this.container.appendChild(view.element); this.container.appendChild(view.element);
} }
} this.length++;
return view;
};
this.length++; prepend(view){
return view; this._views.unshift(view);
}; if(this.container){
this.container.insertBefore(view.element, this.container.firstChild);
}
this.length++;
return view;
};
Views.prototype.remove = function(view) { insert(view, index) {
var index = this._views.indexOf(view); this._views.splice(index, 0, view);
if(index > -1) { if(this.container){
this._views.splice(index, 1); if(index < this.container.children.length){
} this.container.insertBefore(view.element, this.container.children[index]);
} else {
this.container.appendChild(view.element);
}
}
this.length++;
return view;
};
remove(view) {
var index = this._views.indexOf(view);
if(index > -1) {
this._views.splice(index, 1);
}
this.destroy(view);
this.length--;
};
Views.prototype.destroy = function(view) {
if(view.displayed){
view.destroy();
}
if(this.container){
this.container.removeChild(view.element);
}
view = null;
};
// Iterators
Views.prototype.each = function() {
return this._views.forEach.apply(this._views, arguments);
};
Views.prototype.clear = function(){
// Remove all views
var view;
var len = this.length;
if(!this.length) return;
for (var i = 0; i < len; i++) {
view = this._views[i];
this.destroy(view); this.destroy(view);
}
this._views = []; this.length--;
this.length = 0; };
};
Views.prototype.find = function(section){ destroy(view) {
var view;
var len = this.length;
for (var i = 0; i < len; i++) {
view = this._views[i];
if(view.displayed && view.section.index == section.index) {
return view;
}
}
};
Views.prototype.displayed = function(){
var displayed = [];
var view;
var len = this.length;
for (var i = 0; i < len; i++) {
view = this._views[i];
if(view.displayed){ if(view.displayed){
displayed.push(view); view.destroy();
} }
}
return displayed;
};
Views.prototype.show = function(){ if(this.container){
var view; this.container.removeChild(view.element);
var len = this.length;
for (var i = 0; i < len; i++) {
view = this._views[i];
if(view.displayed){
view.show();
} }
} view = null;
this.hidden = false; };
};
Views.prototype.hide = function(){ // Iterators
var view;
var len = this.length;
for (var i = 0; i < len; i++) { each() {
view = this._views[i]; return this._views.forEach.apply(this._views, arguments);
if(view.displayed){ };
view.hide();
clear(){
// Remove all views
var view;
var len = this.length;
if(!this.length) return;
for (var i = 0; i < len; i++) {
view = this._views[i];
this.destroy(view);
} }
}
this.hidden = true;
};
module.exports = Views; this._views = [];
this.length = 0;
};
find(section){
var view;
var len = this.length;
for (var i = 0; i < len; i++) {
view = this._views[i];
if(view.displayed && view.section.index == section.index) {
return view;
}
}
};
displayed(){
var displayed = [];
var view;
var len = this.length;
for (var i = 0; i < len; i++) {
view = this._views[i];
if(view.displayed){
displayed.push(view);
}
}
return displayed;
};
show(){
var view;
var len = this.length;
for (var i = 0; i < len; i++) {
view = this._views[i];
if(view.displayed){
view.show();
}
}
this.hidden = false;
};
hide(){
var view;
var len = this.length;
for (var i = 0; i < len; i++) {
view = this._views[i];
if(view.displayed){
view.hide();
}
}
this.hidden = true;
};
}
export default Views;

File diff suppressed because it is too large Load diff

View file

@ -1,429 +1,431 @@
var EventEmitter = require('event-emitter'); import EventEmitter from 'event-emitter';
var core = require('../../core'); import {extend, borders, uuid, isNumber, bounds, defer} from '../../utils/core';
var EpubCFI = require('../../epubcfi'); import EpubCFI from '../../epubcfi';
var Contents = require('../../contents'); import Contents from '../../contents';
// var URI = require('urijs'); // import URI from 'urijs';
function InlineView(section, options) { class InlineView {
this.settings = core.extend({ constructor(section, options) {
ignoreClass : '', this.settings = extend({
axis: 'vertical', ignoreClass : '',
width: 0, axis: 'vertical',
height: 0, width: 0,
layout: undefined, height: 0,
globalLayoutProperties: {}, layout: undefined,
}, options || {}); globalLayoutProperties: {},
}, options || {});
this.id = "epubjs-view:" + core.uuid();
this.section = section; this.id = "epubjs-view:" + uuid();
this.index = section.index; this.section = section;
this.index = section.index;
this.element = this.container(this.settings.axis);
this.element = this.container(this.settings.axis);
this.added = false;
this.displayed = false; this.added = 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"];
};
InlineView.prototype.container = function(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;
};
InlineView.prototype.create = function() {
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 = core.bounds(this.element);
return this.frame;
};
InlineView.prototype.render = function(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("rendered", this.section);
}.bind(this))
.catch(function(e){
this.emit("loaderror", e);
}.bind(this));
};
// Determine locks base on settings
InlineView.prototype.size = function(_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
InlineView.prototype.lock = function(what, width, height) {
var elBorders = core.borders(this.element);
var iframeBorders;
if(this.frame) {
iframeBorders = core.borders(this.frame);
} else {
iframeBorders = {width: 0, height: 0};
}
if(what == "width" && core.isNumber(width)){
this.lockedWidth = width - elBorders.width - iframeBorders.width;
this.resize(this.lockedWidth, false); // width keeps ratio correct
}
if(what == "height" && core.isNumber(height)){
this.lockedHeight = height - elBorders.height - iframeBorders.height;
this.resize(false, this.lockedHeight);
}
if(what === "both" &&
core.isNumber(width) &&
core.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
InlineView.prototype.expand = function(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;
};
InlineView.prototype.contentWidth = function(min) {
return this.frame.scrollWidth;
};
InlineView.prototype.contentHeight = function(min) {
console.log(this.frame.scrollHeight);
return this.frame.scrollHeight;
};
InlineView.prototype.resize = function(width, height) {
if(!this.frame) return;
if(core.isNumber(width)){
this.frame.style.width = width + "px";
this._width = width;
}
if(core.isNumber(height)){
this.frame.style.height = height + "px";
this._height = height;
}
this.prevBounds = this.elementBounds;
this.elementBounds = core.bounds(this.element);
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("resized", size);
};
InlineView.prototype.load = function(contents) {
var loading = new core.defer();
var loaded = loading.promise;
var doc = core.parse(contents, "text/html");
var body = core.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;
};
InlineView.prototype.setLayout = function(layout) {
this.layout = layout;
};
InlineView.prototype.resizeListenters = function() {
// Test size again
// clearTimeout(this.expanding);
// this.expanding = setTimeout(this.expand.bind(this), 350);
};
InlineView.prototype.addListeners = function() {
//TODO: Add content listeners for expanding
};
InlineView.prototype.removeListeners = function(layoutFunc) {
//TODO: remove content listeners for expanding
};
InlineView.prototype.display = function(request) {
var displayed = new core.defer();
if (!this.displayed) {
this.render(request).then(function () {
this.emit("displayed", this);
this.onDisplayed(this);
this.displayed = true;
displayed.resolve(this);
}.bind(this));
} else {
displayed.resolve(this);
}
return displayed.promise;
};
InlineView.prototype.show = function() {
this.element.style.visibility = "visible";
if(this.frame){
this.frame.style.visibility = "visible";
}
this.emit("shown", this);
};
InlineView.prototype.hide = function() {
// this.frame.style.display = "none";
this.element.style.visibility = "hidden";
this.frame.style.visibility = "hidden";
this.stopExpanding = true;
this.emit("hidden", this);
};
InlineView.prototype.position = function() {
return this.element.getBoundingClientRect();
};
InlineView.prototype.locationOf = function(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
};
};
InlineView.prototype.onDisplayed = function(view) {
// Stub, override with a custom functions
};
InlineView.prototype.onResize = function(view, e) {
// Stub, override with a custom functions
};
InlineView.prototype.bounds = function() {
if(!this.elementBounds) {
this.elementBounds = core.bounds(this.element);
}
return this.elementBounds;
};
InlineView.prototype.destroy = function() {
if(this.displayed){
this.displayed = false; this.displayed = false;
this.rendered = false;
this.removeListeners(); 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("rendered", this.section);
}.bind(this))
.catch(function(e){
this.emit("loaderror", 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) {
console.log(this.frame.scrollHeight);
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);
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("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("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("shown", this);
};
hide() {
// this.frame.style.display = "none";
this.element.style.visibility = "hidden";
this.frame.style.visibility = "hidden";
this.stopExpanding = true; this.stopExpanding = true;
this.element.removeChild(this.frame); this.emit("hidden", this);
this.displayed = false; };
this.frame = null;
this._textWidth = null; position() {
this._textHeight = null; return this.element.getBoundingClientRect();
this._width = null; };
this._height = null;
} locationOf(target) {
// this.element.style.height = "0px"; var parentPos = this.frame.getBoundingClientRect();
// this.element.style.width = "0px"; 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); EventEmitter(InlineView.prototype);
module.exports = InlineView; export default InlineView;

View file

@ -1,297 +1,299 @@
var EpubCFI = require('./epubcfi'); import EpubCFI from './epubcfi';
function Mapping(layout){ class Mapping {
this.layout = layout; constructor(layout) {
}; this.layout = layout;
Mapping.prototype.section = function(view) {
var ranges = this.findRanges(view);
var map = this.rangeListToCfiList(view.section.cfiBase, ranges);
return map;
};
Mapping.prototype.page = function(contents, cfiBase, start, end) {
var root = contents && contents.document ? contents.document.body : false;
if (!root) {
return;
}
return this.rangePairToCfiPair(cfiBase, {
start: this.findStart(root, start, end),
end: this.findEnd(root, start, end)
});
};
Mapping.prototype.walk = function(root, func) {
//var treeWalker = document.createTreeWalker(root, NodeFilter.SHOW_ELEMENT + NodeFilter.SHOW_TEXT, null, false);
var treeWalker = document.createTreeWalker(root, NodeFilter.SHOW_TEXT, {
acceptNode: function (node) {
if ( node.data.trim().length > 0 ) {
return NodeFilter.FILTER_ACCEPT;
} else {
return NodeFilter.FILTER_REJECT;
}
}
}, false);
var node;
var result;
while ((node = treeWalker.nextNode())) {
result = func(node);
if(result) break;
}
return result;
};
Mapping.prototype.findRanges = function(view){
var columns = [];
var scrollWidth = view.contents.scrollWidth();
var count = this.layout.count(scrollWidth);
var column = this.layout.column;
var gap = this.layout.gap;
var start, end;
for (var i = 0; i < count.pages; i++) {
start = (column + gap) * i;
end = (column * (i+1)) + (gap * i);
columns.push({
start: this.findStart(view.document.body, start, end),
end: this.findEnd(view.document.body, start, end)
});
}
return columns;
};
Mapping.prototype.findStart = function(root, start, end){
var stack = [root];
var $el;
var found;
var $prev = root;
while (stack.length) {
$el = stack.shift();
found = this.walk($el, function(node){
var left, right;
var elPos;
var elRange;
if(node.nodeType == Node.TEXT_NODE){
elRange = document.createRange();
elRange.selectNodeContents(node);
elPos = elRange.getBoundingClientRect();
} else {
elPos = node.getBoundingClientRect();
}
left = elPos.left;
right = elPos.right;
if( left >= start && left <= end ) {
return node;
} else if (right > start) {
return node;
} else {
$prev = node;
stack.push(node);
}
});
if(found) {
return this.findTextStartRange(found, start, end);
}
}
// Return last element
return this.findTextStartRange($prev, start, end);
};
Mapping.prototype.findEnd = function(root, start, end){
var stack = [root];
var $el;
var $prev = root;
var found;
while (stack.length) {
$el = stack.shift();
found = this.walk($el, function(node){
var left, right;
var elPos;
var elRange;
if(node.nodeType == Node.TEXT_NODE){
elRange = document.createRange();
elRange.selectNodeContents(node);
elPos = elRange.getBoundingClientRect();
} else {
elPos = node.getBoundingClientRect();
}
left = elPos.left;
right = elPos.right;
if(left > end && $prev) {
return $prev;
} else if(right > end) {
return node;
} else {
$prev = node;
stack.push(node);
}
});
if(found){
return this.findTextEndRange(found, start, end);
}
}
// end of chapter
return this.findTextEndRange($prev, start, end);
};
Mapping.prototype.findTextStartRange = function(node, start, end){
var ranges = this.splitTextNodeIntoRanges(node);
var prev;
var range;
var pos;
for (var i = 0; i < ranges.length; i++) {
range = ranges[i];
pos = range.getBoundingClientRect();
if( pos.left >= start ) {
return range;
}
prev = range;
}
return ranges[0];
};
Mapping.prototype.findTextEndRange = function(node, start, end){
var ranges = this.splitTextNodeIntoRanges(node);
var prev;
var range;
var pos;
for (var i = 0; i < ranges.length; i++) {
range = ranges[i];
pos = range.getBoundingClientRect();
if(pos.left > end && prev) {
return prev;
} else if(pos.right > end) {
return range;
}
prev = range;
}
// Ends before limit
return ranges[ranges.length-1];
};
Mapping.prototype.splitTextNodeIntoRanges = function(node, _splitter){
var ranges = [];
var textContent = node.textContent || "";
var text = textContent.trim();
var range;
var rect;
var list;
var doc = node.ownerDocument;
var splitter = _splitter || " ";
pos = text.indexOf(splitter);
if(pos === -1 || node.nodeType != Node.TEXT_NODE) {
range = doc.createRange();
range.selectNodeContents(node);
return [range];
}
range = doc.createRange();
range.setStart(node, 0);
range.setEnd(node, pos);
ranges.push(range);
range = false;
while ( pos != -1 ) {
pos = text.indexOf(splitter, pos + 1);
if(pos > 0) {
if(range) {
range.setEnd(node, pos);
ranges.push(range);
}
range = doc.createRange();
range.setStart(node, pos+1);
}
}
if(range) {
range.setEnd(node, text.length);
ranges.push(range);
}
return ranges;
};
Mapping.prototype.rangePairToCfiPair = function(cfiBase, rangePair){
var startRange = rangePair.start;
var endRange = rangePair.end;
startRange.collapse(true);
endRange.collapse(true);
// startCfi = section.cfiFromRange(startRange);
// endCfi = section.cfiFromRange(endRange);
startCfi = new EpubCFI(startRange, cfiBase).toString();
endCfi = new EpubCFI(endRange, cfiBase).toString();
return {
start: startCfi,
end: endCfi
}; };
}; section(view) {
var ranges = this.findRanges(view);
var map = this.rangeListToCfiList(view.section.cfiBase, ranges);
Mapping.prototype.rangeListToCfiList = function(cfiBase, columns){ return map;
var map = []; };
var rangePair, cifPair;
for (var i = 0; i < columns.length; i++) { page(contents, cfiBase, start, end) {
cifPair = this.rangePairToCfiPair(cfiBase, columns[i]); var root = contents && contents.document ? contents.document.body : false;
map.push(cifPair); if (!root) {
return;
}
} return this.rangePairToCfiPair(cfiBase, {
start: this.findStart(root, start, end),
end: this.findEnd(root, start, end)
});
};
return map; walk(root, func) {
}; //var treeWalker = document.createTreeWalker(root, NodeFilter.SHOW_ELEMENT + NodeFilter.SHOW_TEXT, null, false);
var treeWalker = document.createTreeWalker(root, NodeFilter.SHOW_TEXT, {
acceptNode: function (node) {
if ( node.data.trim().length > 0 ) {
return NodeFilter.FILTER_ACCEPT;
} else {
return NodeFilter.FILTER_REJECT;
}
}
}, false);
var node;
var result;
while ((node = treeWalker.nextNode())) {
result = func(node);
if(result) break;
}
module.exports = Mapping; return result;
};
findRanges(view){
var columns = [];
var scrollWidth = view.contents.scrollWidth();
var count = this.layout.count(scrollWidth);
var column = this.layout.column;
var gap = this.layout.gap;
var start, end;
for (var i = 0; i < count.pages; i++) {
start = (column + gap) * i;
end = (column * (i+1)) + (gap * i);
columns.push({
start: this.findStart(view.document.body, start, end),
end: this.findEnd(view.document.body, start, end)
});
}
return columns;
};
findStart(root, start, end){
var stack = [root];
var $el;
var found;
var $prev = root;
while (stack.length) {
$el = stack.shift();
found = this.walk($el, function(node){
var left, right;
var elPos;
var elRange;
if(node.nodeType == Node.TEXT_NODE){
elRange = document.createRange();
elRange.selectNodeContents(node);
elPos = elRange.getBoundingClientRect();
} else {
elPos = node.getBoundingClientRect();
}
left = elPos.left;
right = elPos.right;
if( left >= start && left <= end ) {
return node;
} else if (right > start) {
return node;
} else {
$prev = node;
stack.push(node);
}
});
if(found) {
return this.findTextStartRange(found, start, end);
}
}
// Return last element
return this.findTextStartRange($prev, start, end);
};
findEnd(root, start, end){
var stack = [root];
var $el;
var $prev = root;
var found;
while (stack.length) {
$el = stack.shift();
found = this.walk($el, function(node){
var left, right;
var elPos;
var elRange;
if(node.nodeType == Node.TEXT_NODE){
elRange = document.createRange();
elRange.selectNodeContents(node);
elPos = elRange.getBoundingClientRect();
} else {
elPos = node.getBoundingClientRect();
}
left = elPos.left;
right = elPos.right;
if(left > end && $prev) {
return $prev;
} else if(right > end) {
return node;
} else {
$prev = node;
stack.push(node);
}
});
if(found){
return this.findTextEndRange(found, start, end);
}
}
// end of chapter
return this.findTextEndRange($prev, start, end);
};
findTextStartRange(node, start, end){
var ranges = this.splitTextNodeIntoRanges(node);
var prev;
var range;
var pos;
for (var i = 0; i < ranges.length; i++) {
range = ranges[i];
pos = range.getBoundingClientRect();
if( pos.left >= start ) {
return range;
}
prev = range;
}
return ranges[0];
};
findTextEndRange(node, start, end){
var ranges = this.splitTextNodeIntoRanges(node);
var prev;
var range;
var pos;
for (var i = 0; i < ranges.length; i++) {
range = ranges[i];
pos = range.getBoundingClientRect();
if(pos.left > end && prev) {
return prev;
} else if(pos.right > end) {
return range;
}
prev = range;
}
// Ends before limit
return ranges[ranges.length-1];
};
splitTextNodeIntoRanges(node, _splitter){
var ranges = [];
var textContent = node.textContent || "";
var text = textContent.trim();
var range;
var rect;
var list;
var doc = node.ownerDocument;
var splitter = _splitter || " ";
var pos = text.indexOf(splitter);
if(pos === -1 || node.nodeType != Node.TEXT_NODE) {
range = doc.createRange();
range.selectNodeContents(node);
return [range];
}
range = doc.createRange();
range.setStart(node, 0);
range.setEnd(node, pos);
ranges.push(range);
range = false;
while ( pos != -1 ) {
pos = text.indexOf(splitter, pos + 1);
if(pos > 0) {
if(range) {
range.setEnd(node, pos);
ranges.push(range);
}
range = doc.createRange();
range.setStart(node, pos+1);
}
}
if(range) {
range.setEnd(node, text.length);
ranges.push(range);
}
return ranges;
};
rangePairToCfiPair(cfiBase, rangePair){
var startRange = rangePair.start;
var endRange = rangePair.end;
startRange.collapse(true);
endRange.collapse(true);
// startCfi = section.cfiFromRange(startRange);
// endCfi = section.cfiFromRange(endRange);
let startCfi = new EpubCFI(startRange, cfiBase).toString();
let endCfi = new EpubCFI(endRange, cfiBase).toString();
return {
start: startCfi,
end: endCfi
};
};
rangeListToCfiList(cfiBase, columns){
var map = [];
var rangePair, cifPair;
for (var i = 0; i < columns.length; i++) {
cifPair = this.rangePairToCfiPair(cfiBase, columns[i]);
map.push(cifPair);
}
return map;
};
}
export default Mapping;

View file

@ -1,200 +1,201 @@
var core = require('./core'); import {qs, qsa, querySelectorByType} from './utils/core';
var path = require('path');
/** /**
* Navigation Parser * Navigation Parser
* @param {document} xml navigation html / xhtml / ncx * @param {document} xml navigation html / xhtml / ncx
*/ */
function Navigation(xml){ class Navigation {
this.toc = []; constructor(xml) {
this.tocByHref = {}; this.toc = [];
this.tocById = {}; this.tocByHref = {};
this.tocById = {};
if (xml) { if (xml) {
this.parse(xml); this.parse(xml);
}
};
/**
* Parse out the navigation items
* @param {document} xml navigation html / xhtml / ncx
*/
Navigation.prototype.parse = function(xml) {
var html = core.qs(xml, "html");
var ncx = core.qs(xml, "ncx");
if(html) {
this.toc = this.parseNav(xml);
} else if(ncx){
this.toc = this.parseNcx(xml);
}
this.unpack(this.toc);
};
/**
* Unpack navigation items
* @private
* @param {array} toc
*/
Navigation.prototype.unpack = function(toc) {
var item;
for (var i = 0; i < toc.length; i++) {
item = toc[i];
this.tocByHref[item.href] = i;
this.tocById[item.id] = i;
}
};
/**
* Get an item from the navigation
* @param {string} target
* @return {object} navItems
*/
Navigation.prototype.get = function(target) {
var index;
if(!target) {
return this.toc;
}
if(target.indexOf("#") === 0) {
index = this.tocById[target.substring(1)];
} else if(target in this.tocByHref){
index = this.tocByHref[target];
}
return this.toc[index];
};
/**
* Parse from a Epub > 3.0 Nav
* @private
* @param {document} navHtml
* @return {array} navigation list
*/
Navigation.prototype.parseNav = function(navHtml){
var navElement = core.querySelectorByType(navHtml, "nav", "toc");
var navItems = navElement ? core.qsa(navElement, "li") : [];
var length = navItems.length;
var i;
var toc = {};
var list = [];
var item, parent;
if(!navItems || length === 0) return list;
for (i = 0; i < length; ++i) {
item = this.navItem(navItems[i]);
toc[item.id] = item;
if(!item.parent) {
list.push(item);
} else {
parent = toc[item.parent];
parent.subitems.push(item);
} }
}
return list;
};
/**
* Create a navItem
* @private
* @param {element} item
* @return {object} navItem
*/
Navigation.prototype.navItem = function(item){
var id = item.getAttribute('id') || false,
content = core.qs(item, "a"),
src = content.getAttribute('href') || '',
text = content.textContent || "",
subitems = [],
parentNode = item.parentNode,
parent;
if(parentNode && parentNode.nodeName === "navPoint") {
parent = parentNode.getAttribute('id');
}
return {
"id": id,
"href": src,
"label": text,
"subitems" : subitems,
"parent" : parent
}; };
};
/** /**
* Parse from a Epub > 3.0 NC * Parse out the navigation items
* @private * @param {document} xml navigation html / xhtml / ncx
* @param {document} navHtml */
* @return {array} navigation list parse(xml) {
*/ var html = qs(xml, "html");
Navigation.prototype.parseNcx = function(tocXml){ var ncx = qs(xml, "ncx");
var navPoints = core.qsa(tocXml, "navPoint");
var length = navPoints.length;
var i;
var toc = {};
var list = [];
var item, parent;
if(!navPoints || length === 0) return list; if(html) {
this.toc = this.parseNav(xml);
for (i = 0; i < length; ++i) { } else if(ncx){
item = this.ncxItem(navPoints[i]); this.toc = this.parseNcx(xml);
toc[item.id] = item;
if(!item.parent) {
list.push(item);
} else {
parent = toc[item.parent];
parent.subitems.push(item);
} }
}
return list; this.unpack(this.toc);
};
/**
* Create a ncxItem
* @private
* @param {element} item
* @return {object} ncxItem
*/
Navigation.prototype.ncxItem = function(item){
var id = item.getAttribute('id') || false,
content = core.qs(item, "content"),
src = content.getAttribute('src'),
navLabel = core.qs(item, "navLabel"),
text = navLabel.textContent ? navLabel.textContent : "",
subitems = [],
parentNode = item.parentNode,
parent;
if(parentNode && parentNode.nodeName === "navPoint") {
parent = parentNode.getAttribute('id');
}
return {
"id": id,
"href": src,
"label": text,
"subitems" : subitems,
"parent" : parent
}; };
};
/** /**
* forEach pass through * Unpack navigation items
* @param {Function} fn function to run on each item * @private
* @return {method} forEach loop * @param {array} toc
*/ */
Navigation.prototype.forEach = function(fn) { unpack(toc) {
return this.toc.forEach(fn); var item;
};
module.exports = Navigation; for (var i = 0; i < toc.length; i++) {
item = toc[i];
this.tocByHref[item.href] = i;
this.tocById[item.id] = i;
}
};
/**
* Get an item from the navigation
* @param {string} target
* @return {object} navItems
*/
get(target) {
var index;
if(!target) {
return this.toc;
}
if(target.indexOf("#") === 0) {
index = this.tocById[target.substring(1)];
} else if(target in this.tocByHref){
index = this.tocByHref[target];
}
return this.toc[index];
};
/**
* Parse from a Epub > 3.0 Nav
* @private
* @param {document} navHtml
* @return {array} navigation list
*/
parseNav(navHtml){
var navElement = querySelectorByType(navHtml, "nav", "toc");
var navItems = navElement ? qsa(navElement, "li") : [];
var length = navItems.length;
var i;
var toc = {};
var list = [];
var item, parent;
if(!navItems || length === 0) return list;
for (i = 0; i < length; ++i) {
item = this.navItem(navItems[i]);
toc[item.id] = item;
if(!item.parent) {
list.push(item);
} else {
parent = toc[item.parent];
parent.subitems.push(item);
}
}
return list;
};
/**
* Create a navItem
* @private
* @param {element} item
* @return {object} navItem
*/
navItem(item){
var id = item.getAttribute('id') || false,
content = qs(item, "a"),
src = content.getAttribute('href') || '',
text = content.textContent || "",
subitems = [],
parentNode = item.parentNode,
parent;
if(parentNode && parentNode.nodeName === "navPoint") {
parent = parentNode.getAttribute('id');
}
return {
"id": id,
"href": src,
"label": text,
"subitems" : subitems,
"parent" : parent
};
};
/**
* Parse from a Epub > 3.0 NC
* @private
* @param {document} navHtml
* @return {array} navigation list
*/
parseNcx(tocXml){
var navPoints = qsa(tocXml, "navPoint");
var length = navPoints.length;
var i;
var toc = {};
var list = [];
var item, parent;
if(!navPoints || length === 0) return list;
for (i = 0; i < length; ++i) {
item = this.ncxItem(navPoints[i]);
toc[item.id] = item;
if(!item.parent) {
list.push(item);
} else {
parent = toc[item.parent];
parent.subitems.push(item);
}
}
return list;
};
/**
* Create a ncxItem
* @private
* @param {element} item
* @return {object} ncxItem
*/
ncxItem(item){
var id = item.getAttribute('id') || false,
content = qs(item, "content"),
src = content.getAttribute('src'),
navLabel = qs(item, "navLabel"),
text = navLabel.textContent ? navLabel.textContent : "",
subitems = [],
parentNode = item.parentNode,
parent;
if(parentNode && parentNode.nodeName === "navPoint") {
parent = parentNode.getAttribute('id');
}
return {
"id": id,
"href": src,
"label": text,
"subitems" : subitems,
"parent" : parent
};
};
/**
* forEach pass through
* @param {Function} fn function to run on each item
* @return {method} forEach loop
*/
forEach(fn) {
return this.toc.forEach(fn);
};
}
export default Navigation;

View file

@ -1,282 +1,283 @@
var path = require('path'); import {qs, qsa, qsp} from './utils/core';
var core = require('./core'); import EpubCFI from './epubcfi';
var EpubCFI = require('./epubcfi');
/** /**
* Open Packaging Format Parser * Open Packaging Format Parser
* @class * @class
* @param {document} packageDocument OPF XML * @param {document} packageDocument OPF XML
*/ */
function Packaging(packageDocument) { class Packaging {
if (packageDocument) { constructor(packageDocument) {
this.parse(packageDocument); if (packageDocument) {
} this.parse(packageDocument);
};
/**
* Parse OPF XML
* @param {document} packageDocument OPF XML
* @return {object} parsed package parts
*/
Packaging.prototype.parse = function(packageDocument){
var metadataNode, manifestNode, spineNode;
if(!packageDocument) {
console.error("Package File Not Found");
return;
}
metadataNode = core.qs(packageDocument, "metadata");
if(!metadataNode) {
console.error("No Metadata Found");
return;
}
manifestNode = core.qs(packageDocument, "manifest");
if(!manifestNode) {
console.error("No Manifest Found");
return;
}
spineNode = core.qs(packageDocument, "spine");
if(!spineNode) {
console.error("No Spine Found");
return;
}
this.manifest = this.parseManifest(manifestNode);
this.navPath = this.findNavPath(manifestNode);
this.ncxPath = this.findNcxPath(manifestNode, spineNode);
this.coverPath = this.findCoverPath(packageDocument);
this.spineNodeIndex = Array.prototype.indexOf.call(spineNode.parentNode.childNodes, spineNode);
this.spine = this.parseSpine(spineNode, this.manifest);
this.metadata = this.parseMetadata(metadataNode);
this.metadata.direction = spineNode.getAttribute("page-progression-direction");
return {
'metadata' : this.metadata,
'spine' : this.spine,
'manifest' : this.manifest,
'navPath' : this.navPath,
'ncxPath' : this.ncxPath,
'coverPath': this.coverPath,
'spineNodeIndex' : this.spineNodeIndex
};
};
/**
* Parse Metadata
* @private
* @param {document} xml
* @return {object} metadata
*/
Packaging.prototype.parseMetadata = function(xml){
var metadata = {};
metadata.title = this.getElementText(xml, 'title');
metadata.creator = this.getElementText(xml, 'creator');
metadata.description = this.getElementText(xml, 'description');
metadata.pubdate = this.getElementText(xml, 'date');
metadata.publisher = this.getElementText(xml, 'publisher');
metadata.identifier = this.getElementText(xml, "identifier");
metadata.language = this.getElementText(xml, "language");
metadata.rights = this.getElementText(xml, "rights");
metadata.modified_date = this.getPropertyText(xml, 'dcterms:modified');
metadata.layout = this.getPropertyText(xml, "rendition:layout");
metadata.orientation = this.getPropertyText(xml, 'rendition:orientation');
metadata.flow = this.getPropertyText(xml, 'rendition:flow');
metadata.viewport = this.getPropertyText(xml, 'rendition:viewport');
// metadata.page_prog_dir = packageXml.querySelector("spine").getAttribute("page-progression-direction");
return metadata;
};
/**
* Parse Manifest
* @private
* @param {document} manifestXml
* @return {object} manifest
*/
Packaging.prototype.parseManifest = function(manifestXml){
var manifest = {};
//-- Turn items into an array
// var selected = manifestXml.querySelectorAll("item");
var selected = core.qsa(manifestXml, "item");
var items = Array.prototype.slice.call(selected);
//-- Create an object with the id as key
items.forEach(function(item){
var id = item.getAttribute('id'),
href = item.getAttribute('href') || '',
type = item.getAttribute('media-type') || '',
properties = item.getAttribute('properties') || '';
manifest[id] = {
'href' : href,
// 'url' : href,
'type' : type,
'properties' : properties.length ? properties.split(' ') : []
};
});
return manifest;
};
/**
* Parse Spine
* @param {document} spineXml
* @param {Packaging.manifest} manifest
* @return {object} spine
*/
Packaging.prototype.parseSpine = function(spineXml, manifest){
var spine = [];
var selected = spineXml.getElementsByTagName("itemref"),
items = Array.prototype.slice.call(selected);
var epubcfi = new EpubCFI();
//-- Add to array to mantain ordering and cross reference with manifest
items.forEach(function(item, index){
var idref = item.getAttribute('idref');
// var cfiBase = epubcfi.generateChapterComponent(spineNodeIndex, index, Id);
var props = item.getAttribute('properties') || '';
var propArray = props.length ? props.split(' ') : [];
// var manifestProps = manifest[Id].properties;
// var manifestPropArray = manifestProps.length ? manifestProps.split(' ') : [];
var itemref = {
'idref' : idref,
'linear' : item.getAttribute('linear') || '',
'properties' : propArray,
// 'href' : manifest[Id].href,
// 'url' : manifest[Id].url,
'index' : index
// 'cfiBase' : cfiBase
};
spine.push(itemref);
});
return spine;
};
/**
* Find TOC NAV
* @private
*/
Packaging.prototype.findNavPath = function(manifestNode){
// Find item with property 'nav'
// Should catch nav irregardless of order
// var node = manifestNode.querySelector("item[properties$='nav'], item[properties^='nav '], item[properties*=' nav ']");
var node = core.qsp(manifestNode, "item", {"properties":"nav"});
return node ? node.getAttribute('href') : false;
};
/**
* Find TOC NCX
* media-type="application/x-dtbncx+xml" href="toc.ncx"
* @private
*/
Packaging.prototype.findNcxPath = function(manifestNode, spineNode){
// var node = manifestNode.querySelector("item[media-type='application/x-dtbncx+xml']");
var node = core.qsp(manifestNode, "item", {"media-type":"application/x-dtbncx+xml"});
var tocId;
// If we can't find the toc by media-type then try to look for id of the item in the spine attributes as
// according to http://www.idpf.org/epub/20/spec/OPF_2.0.1_draft.htm#Section2.4.1.2,
// "The item that describes the NCX must be referenced by the spine toc attribute."
if (!node) {
tocId = spineNode.getAttribute("toc");
if(tocId) {
// node = manifestNode.querySelector("item[id='" + tocId + "']");
node = manifestNode.getElementById(tocId);
} }
} };
return node ? node.getAttribute('href') : false; /**
}; * Parse OPF XML
* @param {document} packageDocument OPF XML
* @return {object} parsed package parts
*/
parse(packageDocument){
var metadataNode, manifestNode, spineNode;
/** if(!packageDocument) {
* Find the Cover Path console.error("Package File Not Found");
* <item properties="cover-image" id="ci" href="cover.svg" media-type="image/svg+xml" /> return;
* Fallback for Epub 2.0 }
* @param {document} packageXml
* @return {string} href
*/
Packaging.prototype.findCoverPath = function(packageXml){
var pkg = core.qs(packageXml, "package");
var epubVersion = pkg.getAttribute('version');
if (epubVersion === '2.0') { metadataNode = qs(packageDocument, "metadata");
var metaCover = core.qsp(packageXml, 'meta', {'name':'cover'}); if(!metadataNode) {
if (metaCover) { console.error("No Metadata Found");
var coverId = metaCover.getAttribute('content'); return;
// var cover = packageXml.querySelector("item[id='" + coverId + "']"); }
var cover = packageXml.getElementById(coverId);
return cover ? cover.getAttribute('href') : ''; manifestNode = qs(packageDocument, "manifest");
if(!manifestNode) {
console.error("No Manifest Found");
return;
}
spineNode = qs(packageDocument, "spine");
if(!spineNode) {
console.error("No Spine Found");
return;
}
this.manifest = this.parseManifest(manifestNode);
this.navPath = this.findNavPath(manifestNode);
this.ncxPath = this.findNcxPath(manifestNode, spineNode);
this.coverPath = this.findCoverPath(packageDocument);
this.spineNodeIndex = Array.prototype.indexOf.call(spineNode.parentNode.childNodes, spineNode);
this.spine = this.parseSpine(spineNode, this.manifest);
this.metadata = this.parseMetadata(metadataNode);
this.metadata.direction = spineNode.getAttribute("page-progression-direction");
return {
'metadata' : this.metadata,
'spine' : this.spine,
'manifest' : this.manifest,
'navPath' : this.navPath,
'ncxPath' : this.ncxPath,
'coverPath': this.coverPath,
'spineNodeIndex' : this.spineNodeIndex
};
};
/**
* Parse Metadata
* @private
* @param {document} xml
* @return {object} metadata
*/
parseMetadata(xml){
var metadata = {};
metadata.title = this.getElementText(xml, 'title');
metadata.creator = this.getElementText(xml, 'creator');
metadata.description = this.getElementText(xml, 'description');
metadata.pubdate = this.getElementText(xml, 'date');
metadata.publisher = this.getElementText(xml, 'publisher');
metadata.identifier = this.getElementText(xml, "identifier");
metadata.language = this.getElementText(xml, "language");
metadata.rights = this.getElementText(xml, "rights");
metadata.modified_date = this.getPropertyText(xml, 'dcterms:modified');
metadata.layout = this.getPropertyText(xml, "rendition:layout");
metadata.orientation = this.getPropertyText(xml, 'rendition:orientation');
metadata.flow = this.getPropertyText(xml, 'rendition:flow');
metadata.viewport = this.getPropertyText(xml, 'rendition:viewport');
// metadata.page_prog_dir = packageXml.querySelector("spine").getAttribute("page-progression-direction");
return metadata;
};
/**
* Parse Manifest
* @private
* @param {document} manifestXml
* @return {object} manifest
*/
parseManifest(manifestXml){
var manifest = {};
//-- Turn items into an array
// var selected = manifestXml.querySelectorAll("item");
var selected = qsa(manifestXml, "item");
var items = Array.prototype.slice.call(selected);
//-- Create an object with the id as key
items.forEach(function(item){
var id = item.getAttribute('id'),
href = item.getAttribute('href') || '',
type = item.getAttribute('media-type') || '',
properties = item.getAttribute('properties') || '';
manifest[id] = {
'href' : href,
// 'url' : href,
'type' : type,
'properties' : properties.length ? properties.split(' ') : []
};
});
return manifest;
};
/**
* Parse Spine
* @param {document} spineXml
* @param {Packaging.manifest} manifest
* @return {object} spine
*/
parseSpine(spineXml, manifest){
var spine = [];
var selected = spineXml.getElementsByTagName("itemref"),
items = Array.prototype.slice.call(selected);
var epubcfi = new EpubCFI();
//-- Add to array to mantain ordering and cross reference with manifest
items.forEach(function(item, index){
var idref = item.getAttribute('idref');
// var cfiBase = epubcfi.generateChapterComponent(spineNodeIndex, index, Id);
var props = item.getAttribute('properties') || '';
var propArray = props.length ? props.split(' ') : [];
// var manifestProps = manifest[Id].properties;
// var manifestPropArray = manifestProps.length ? manifestProps.split(' ') : [];
var itemref = {
'idref' : idref,
'linear' : item.getAttribute('linear') || '',
'properties' : propArray,
// 'href' : manifest[Id].href,
// 'url' : manifest[Id].url,
'index' : index
// 'cfiBase' : cfiBase
};
spine.push(itemref);
});
return spine;
};
/**
* Find TOC NAV
* @private
*/
findNavPath(manifestNode){
// Find item with property 'nav'
// Should catch nav irregardless of order
// var node = manifestNode.querySelector("item[properties$='nav'], item[properties^='nav '], item[properties*=' nav ']");
var node = qsp(manifestNode, "item", {"properties":"nav"});
return node ? node.getAttribute('href') : false;
};
/**
* Find TOC NCX
* media-type="application/x-dtbncx+xml" href="toc.ncx"
* @private
*/
findNcxPath(manifestNode, spineNode){
// var node = manifestNode.querySelector("item[media-type='application/x-dtbncx+xml']");
var node = qsp(manifestNode, "item", {"media-type":"application/x-dtbncx+xml"});
var tocId;
// If we can't find the toc by media-type then try to look for id of the item in the spine attributes as
// according to http://www.idpf.org/epub/20/spec/OPF_2.0.1_draft.htm#Section2.4.1.2,
// "The item that describes the NCX must be referenced by the spine toc attribute."
if (!node) {
tocId = spineNode.getAttribute("toc");
if(tocId) {
// node = manifestNode.querySelector("item[id='" + tocId + "']");
node = manifestNode.getElementById(tocId);
}
}
return node ? node.getAttribute('href') : false;
};
/**
* Find the Cover Path
* <item properties="cover-image" id="ci" href="cover.svg" media-type="image/svg+xml" />
* Fallback for Epub 2.0
* @param {document} packageXml
* @return {string} href
*/
findCoverPath(packageXml){
var pkg = qs(packageXml, "package");
var epubVersion = pkg.getAttribute('version');
if (epubVersion === '2.0') {
var metaCover = qsp(packageXml, 'meta', {'name':'cover'});
if (metaCover) {
var coverId = metaCover.getAttribute('content');
// var cover = packageXml.querySelector("item[id='" + coverId + "']");
var cover = packageXml.getElementById(coverId);
return cover ? cover.getAttribute('href') : '';
}
else {
return false;
}
} }
else { else {
return false; // var node = packageXml.querySelector("item[properties='cover-image']");
var node = qsp(packageXml, 'item', {'properties':'cover-image'});
return node ? node.getAttribute('href') : '';
} }
} };
else {
// var node = packageXml.querySelector("item[properties='cover-image']");
var node = core.qsp(packageXml, 'item', {'properties':'cover-image'});
return node ? node.getAttribute('href') : '';
}
};
/** /**
* Get text of a namespaced element * Get text of a namespaced element
* @private * @private
* @param {document} xml * @param {document} xml
* @param {string} tag * @param {string} tag
* @return {string} text * @return {string} text
*/ */
Packaging.prototype.getElementText = function(xml, tag){ getElementText(xml, tag){
var found = xml.getElementsByTagNameNS("http://purl.org/dc/elements/1.1/", tag), var found = xml.getElementsByTagNameNS("http://purl.org/dc/elements/1.1/", tag),
el; el;
if(!found || found.length === 0) return ''; if(!found || found.length === 0) return '';
el = found[0]; el = found[0];
if(el.childNodes.length){ if(el.childNodes.length){
return el.childNodes[0].nodeValue; return el.childNodes[0].nodeValue;
} }
return ''; return '';
}; };
/** /**
* Get text by property * Get text by property
* @private * @private
* @param {document} xml * @param {document} xml
* @param {string} property * @param {string} property
* @return {string} text * @return {string} text
*/ */
Packaging.prototype.getPropertyText = function(xml, property){ getPropertyText(xml, property){
var el = core.qsp(xml, "meta", {"property":property}); var el = qsp(xml, "meta", {"property":property});
if(el && el.childNodes.length){ if(el && el.childNodes.length){
return el.childNodes[0].nodeValue; return el.childNodes[0].nodeValue;
} }
return ''; return '';
}; };
}
module.exports = Packaging; export default Packaging;

View file

@ -1,249 +1,257 @@
var EpubCFI = require('./epubcfi'); import EpubCFI from './epubcfi';
var core = require('./core'); import {
qs,
qsa,
querySelectorByType,
indexOfSorted,
locationOf
} from './utils/core';
/** /**
* Page List Parser * Page List Parser
* @param {[document]} xml * @param {[document]} xml
*/ */
function PageList(xml) { class PageList {
this.pages = []; constructor(xml) {
this.locations = []; this.pages = [];
this.epubcfi = new EpubCFI(); this.locations = [];
this.epubcfi = new EpubCFI();
if (xml) { if (xml) {
this.pageList = this.parse(xml); this.pageList = this.parse(xml);
}
if(this.pageList && this.pageList.length) {
this.process(this.pageList);
}
};
/**
* Parse PageList Xml
* @param {document} xml
*/
PageList.prototype.parse = function(xml) {
var html = core.qs(xml, "html");
// var ncx = core.qs(xml, "ncx");
if(html) {
this.toc = this.parseNav(xml);
} else if(ncx){ // Not supported
// this.toc = this.parseNcx(xml);
return;
}
};
/**
* Parse a Nav PageList
* @private
* @param {document} navHtml
* @return {PageList.item[]} list
*/
PageList.prototype.parseNav = function(navHtml){
var navElement = core.querySelectorByType(navHtml, "nav", "page-list");
var navItems = navElement ? core.qsa(navElement, "li") : [];
var length = navItems.length;
var i;
var toc = {};
var list = [];
var item;
if(!navItems || length === 0) return list;
for (i = 0; i < length; ++i) {
item = this.item(navItems[i]);
list.push(item);
}
return list;
};
/**
* Page List Item
* @private
* @param {object} item
* @return {object} pageListItem
*/
PageList.prototype.item = function(item){
var id = item.getAttribute('id') || false,
content = core.qs(item, "a"),
href = content.getAttribute('href') || '',
text = content.textContent || "",
page = parseInt(text),
isCfi = href.indexOf("epubcfi"),
split,
packageUrl,
cfi;
if(isCfi != -1) {
split = href.split("#");
packageUrl = split[0];
cfi = split.length > 1 ? split[1] : false;
return {
"cfi" : cfi,
"href" : href,
"packageUrl" : packageUrl,
"page" : page
};
} else {
return {
"href" : href,
"page" : page
};
}
};
/**
* Process pageList items
* @private
* @param {array} pageList
*/
PageList.prototype.process = function(pageList){
pageList.forEach(function(item){
this.pages.push(item.page);
if (item.cfi) {
this.locations.push(item.cfi);
} }
}, this);
this.firstPage = parseInt(this.pages[0]);
this.lastPage = parseInt(this.pages[this.pages.length-1]);
this.totalPages = this.lastPage - this.firstPage;
};
if(this.pageList && this.pageList.length) {
/** this.process(this.pageList);
* Replace HREFs with CFI
* TODO: implement getting CFI from Href
*/
PageList.prototype.addCFIs = function() {
this.pageList.forEach(function(pg){
if(!pg.cfi) {
// epubcfi.generateCfiFromHref(pg.href, book).then(function(cfi){
// pg.cfi = cfi;
// pg.packageUrl = book.settings.packageUrl;
// });
} }
}); };
/**
* Parse PageList Xml
* @param {document} xml
*/
parse(xml) {
var html = qs(xml, "html");
// var ncx = qs(xml, "ncx");
if(html) {
this.toc = this.parseNav(xml);
} else if(ncx){ // Not supported
// this.toc = this.parseNcx(xml);
return;
}
};
/**
* Parse a Nav PageList
* @private
* @param {document} navHtml
* @return {PageList.item[]} list
*/
parseNav(navHtml){
var navElement = querySelectorByType(navHtml, "nav", "page-list");
var navItems = navElement ? qsa(navElement, "li") : [];
var length = navItems.length;
var i;
var toc = {};
var list = [];
var item;
if(!navItems || length === 0) return list;
for (i = 0; i < length; ++i) {
item = this.item(navItems[i]);
list.push(item);
}
return list;
};
/**
* Page List Item
* @private
* @param {object} item
* @return {object} pageListItem
*/
item(item){
var id = item.getAttribute('id') || false,
content = qs(item, "a"),
href = content.getAttribute('href') || '',
text = content.textContent || "",
page = parseInt(text),
isCfi = href.indexOf("epubcfi"),
split,
packageUrl,
cfi;
if(isCfi != -1) {
split = href.split("#");
packageUrl = split[0];
cfi = split.length > 1 ? split[1] : false;
return {
"cfi" : cfi,
"href" : href,
"packageUrl" : packageUrl,
"page" : page
};
} else {
return {
"href" : href,
"page" : page
};
}
};
/**
* Process pageList items
* @private
* @param {array} pageList
*/
process(pageList){
pageList.forEach(function(item){
this.pages.push(item.page);
if (item.cfi) {
this.locations.push(item.cfi);
}
}, this);
this.firstPage = parseInt(this.pages[0]);
this.lastPage = parseInt(this.pages[this.pages.length-1]);
this.totalPages = this.lastPage - this.firstPage;
};
/**
* Replace HREFs with CFI
* TODO: implement getting CFI from Href
*/
addCFIs() {
this.pageList.forEach(function(pg){
if(!pg.cfi) {
// epubcfi.generateCfiFromHref(pg.href, book).then(function(cfi){
// pg.cfi = cfi;
// pg.packageUrl = book.settings.packageUrl;
// });
}
});
}
/*
EPUBJS.generateCfiFromHref(href, book) {
var uri = EPUBJS.core.uri(href);
var path = uri.path;
var fragment = uri.fragment;
var spinePos = book.spineIndexByURL[path];
var loaded;
var deferred = new RSVP.defer();
var epubcfi = new EPUBJS.EpubCFI();
var spineItem;
if(typeof spinePos !== "undefined"){
spineItem = book.spine[spinePos];
loaded = book.loadXml(spineItem.url);
loaded.then(function(doc){
var element = doc.getElementById(fragment);
var cfi;
cfi = epubcfi.generateCfiFromElement(element, spineItem.cfiBase);
deferred.resolve(cfi);
});
}
return deferred.promise;
};
*/
/**
* Get a PageList result from a EpubCFI
* @param {string} cfi EpubCFI String
* @return {string} page
*/
pageFromCfi(cfi){
var pg = -1;
// Check if the pageList has not been set yet
if(this.locations.length === 0) {
return -1;
}
// TODO: check if CFI is valid?
// check if the cfi is in the location list
// var index = this.locations.indexOf(cfi);
var index = indexOfSorted(cfi, this.locations, this.epubcfi.compare);
if(index != -1) {
pg = this.pages[index];
} else {
// Otherwise add it to the list of locations
// Insert it in the correct position in the locations page
//index = EPUBJS.core.insert(cfi, this.locations, this.epubcfi.compare);
index = locationOf(cfi, this.locations, this.epubcfi.compare);
// Get the page at the location just before the new one, or return the first
pg = index-1 >= 0 ? this.pages[index-1] : this.pages[0];
if(pg !== undefined) {
// Add the new page in so that the locations and page array match up
//this.pages.splice(index, 0, pg);
} else {
pg = -1;
}
}
return pg;
};
/**
* Get an EpubCFI from a Page List Item
* @param {string} pg
* @return {string} cfi
*/
cfiFromPage(pg){
var cfi = -1;
// check that pg is an int
if(typeof pg != "number"){
pg = parseInt(pg);
}
// check if the cfi is in the page list
// Pages could be unsorted.
var index = this.pages.indexOf(pg);
if(index != -1) {
cfi = this.locations[index];
}
// TODO: handle pages not in the list
return cfi;
};
/**
* Get a Page from Book percentage
* @param {number} percent
* @return {string} page
*/
pageFromPercentage(percent){
var pg = Math.round(this.totalPages * percent);
return pg;
};
/**
* Returns a value between 0 - 1 corresponding to the location of a page
* @param {int} pg the page
* @return {number} percentage
*/
percentageFromPage(pg){
var percentage = (pg - this.firstPage) / this.totalPages;
return Math.round(percentage * 1000) / 1000;
};
/**
* Returns a value between 0 - 1 corresponding to the location of a cfi
* @param {string} cfi EpubCFI String
* @return {number} percentage
*/
percentageFromCfi(cfi){
var pg = this.pageFromCfi(cfi);
var percentage = this.percentageFromPage(pg);
return percentage;
};
} }
/* export default PageList;
EPUBJS.EpubCFI.prototype.generateCfiFromHref = function(href, book) {
var uri = EPUBJS.core.uri(href);
var path = uri.path;
var fragment = uri.fragment;
var spinePos = book.spineIndexByURL[path];
var loaded;
var deferred = new RSVP.defer();
var epubcfi = new EPUBJS.EpubCFI();
var spineItem;
if(typeof spinePos !== "undefined"){
spineItem = book.spine[spinePos];
loaded = book.loadXml(spineItem.url);
loaded.then(function(doc){
var element = doc.getElementById(fragment);
var cfi;
cfi = epubcfi.generateCfiFromElement(element, spineItem.cfiBase);
deferred.resolve(cfi);
});
}
return deferred.promise;
};
*/
/**
* Get a PageList result from a EpubCFI
* @param {string} cfi EpubCFI String
* @return {string} page
*/
PageList.prototype.pageFromCfi = function(cfi){
var pg = -1;
// Check if the pageList has not been set yet
if(this.locations.length === 0) {
return -1;
}
// TODO: check if CFI is valid?
// check if the cfi is in the location list
// var index = this.locations.indexOf(cfi);
var index = core.indexOfSorted(cfi, this.locations, this.epubcfi.compare);
if(index != -1) {
pg = this.pages[index];
} else {
// Otherwise add it to the list of locations
// Insert it in the correct position in the locations page
//index = EPUBJS.core.insert(cfi, this.locations, this.epubcfi.compare);
index = EPUBJS.core.locationOf(cfi, this.locations, this.epubcfi.compare);
// Get the page at the location just before the new one, or return the first
pg = index-1 >= 0 ? this.pages[index-1] : this.pages[0];
if(pg !== undefined) {
// Add the new page in so that the locations and page array match up
//this.pages.splice(index, 0, pg);
} else {
pg = -1;
}
}
return pg;
};
/**
* Get an EpubCFI from a Page List Item
* @param {string} pg
* @return {string} cfi
*/
PageList.prototype.cfiFromPage = function(pg){
var cfi = -1;
// check that pg is an int
if(typeof pg != "number"){
pg = parseInt(pg);
}
// check if the cfi is in the page list
// Pages could be unsorted.
var index = this.pages.indexOf(pg);
if(index != -1) {
cfi = this.locations[index];
}
// TODO: handle pages not in the list
return cfi;
};
/**
* Get a Page from Book percentage
* @param {number} percent
* @return {string} page
*/
PageList.prototype.pageFromPercentage = function(percent){
var pg = Math.round(this.totalPages * percent);
return pg;
};
/**
* Returns a value between 0 - 1 corresponding to the location of a page
* @param {int} pg the page
* @return {number} percentage
*/
PageList.prototype.percentageFromPage = function(pg){
var percentage = (pg - this.firstPage) / this.totalPages;
return Math.round(percentage * 1000) / 1000;
};
/**
* Returns a value between 0 - 1 corresponding to the location of a cfi
* @param {string} cfi EpubCFI String
* @return {number} percentage
*/
PageList.prototype.percentageFromCfi = function(cfi){
var pg = this.pageFromCfi(cfi);
var percentage = this.percentageFromPage(pg);
return percentage;
};
module.exports = PageList;

View file

@ -1,198 +1,201 @@
var core = require('./core'); import {defer, requestAnimationFrame} from './utils/core';
/** /**
* Queue for handling tasks one at a time * Queue for handling tasks one at a time
* @class * @class
* @param {scope} context what this will resolve to in the tasks * @param {scope} context what this will resolve to in the tasks
*/ */
function Queue(context){ class Queue {
this._q = []; constructor(context){
this.context = context; this._q = [];
this.tick = core.requestAnimationFrame; this.context = context;
this.running = false; this.tick = requestAnimationFrame;
this.paused = false; this.running = false;
}; this.paused = false;
};
/** /**
* Add an item to the queue * Add an item to the queue
* @return {Promise} * @return {Promise}
*/ */
Queue.prototype.enqueue = function() { enqueue() {
var deferred, promise; var deferred, promise;
var queued; var queued;
var task = [].shift.call(arguments); var task = [].shift.call(arguments);
var args = arguments; var args = arguments;
// Handle single args without context // Handle single args without context
// if(args && !Array.isArray(args)) { // if(args && !Array.isArray(args)) {
// args = [args]; // args = [args];
// } // }
if(!task) { if(!task) {
return console.error("No Task Provided"); return console.error("No Task Provided");
} }
if(typeof task === "function"){ if(typeof task === "function"){
deferred = new core.defer(); deferred = new defer();
promise = deferred.promise; promise = deferred.promise;
queued = { queued = {
"task" : task, "task" : task,
"args" : args, "args" : args,
//"context" : context, //"context" : context,
"deferred" : deferred, "deferred" : deferred,
"promise" : promise "promise" : promise
}; };
} else { } else {
// Task is a promise // Task is a promise
queued = { queued = {
"promise" : task "promise" : task
}; };
} }
this._q.push(queued); this._q.push(queued);
// Wait to start queue flush // Wait to start queue flush
if (this.paused == false && !this.running) { if (this.paused == false && !this.running) {
// setTimeout(this.flush.bind(this), 0); // setTimeout(this.flush.bind(this), 0);
// this.tick.call(window, this.run.bind(this)); // this.tick.call(window, this.run.bind(this));
this.run(); this.run();
} }
return queued.promise; return queued.promise;
}; };
/** /**
* Run one item * Run one item
* @return {Promise} * @return {Promise}
*/ */
Queue.prototype.dequeue = function(){ dequeue(){
var inwait, task, result; var inwait, task, result;
if(this._q.length) { if(this._q.length) {
inwait = this._q.shift(); inwait = this._q.shift();
task = inwait.task; task = inwait.task;
if(task){ if(task){
// console.log(task) // console.log(task)
result = task.apply(this.context, inwait.args); result = task.apply(this.context, inwait.args);
if(result && typeof result["then"] === "function") { if(result && typeof result["then"] === "function") {
// Task is a function that returns a promise // Task is a function that returns a promise
return result.then(function(){ return result.then(function(){
inwait.deferred.resolve.apply(this.context, arguments); inwait.deferred.resolve.apply(this.context, arguments);
}, function(reason) { }.bind(this), function(reason) {
inwait.deferred.reject.apply(this.context, arguments); inwait.deferred.reject.apply(this.context, arguments);
}.bind(this)); }.bind(this));
} else { } else {
// Task resolves immediately // Task resolves immediately
inwait.deferred.resolve.apply(this.context, result); inwait.deferred.resolve.apply(this.context, result);
return inwait.promise;
}
} else if(inwait.promise) {
// Task is a promise
return inwait.promise; return inwait.promise;
} }
} else {
inwait = new defer();
} else if(inwait.promise) { inwait.deferred.resolve();
// Task is a promise
return inwait.promise; return inwait.promise;
} }
} else { };
inwait = new core.defer();
inwait.deferred.resolve();
return inwait.promise;
}
}; // Run All Immediately
dump(){
while(this._q.length) {
this.dequeue();
}
};
// Run All Immediately /**
Queue.prototype.dump = function(){ * Run all tasks sequentially, at convince
while(this._q.length) { * @return {Promise}
this.dequeue(); */
} run(){
};
/** if(!this.running){
* Run all tasks sequentially, at convince this.running = true;
* @return {Promise} this.defered = new defer();
*/
Queue.prototype.run = function(){
if(!this.running){
this.running = true;
this.defered = new core.defer();
}
this.tick.call(window, function() {
if(this._q.length) {
this.dequeue()
.then(function(){
this.run();
}.bind(this));
} else {
this.defered.resolve();
this.running = undefined;
} }
}.bind(this)); this.tick.call(window, function() {
// Unpause if(this._q.length) {
if(this.paused == true) {
this.paused = false;
}
return this.defered.promise; this.dequeue()
}; .then(function(){
this.run();
}.bind(this));
/** } else {
* Flush all, as quickly as possible this.defered.resolve();
* @return {Promise}
*/
Queue.prototype.flush = function(){
if(this.running){
return this.running;
}
if(this._q.length) {
this.running = this.dequeue()
.then(function(){
this.running = undefined; this.running = undefined;
return this.flush(); }
}.bind(this));
return this.running; }.bind(this));
}
}; // Unpause
if(this.paused == true) {
this.paused = false;
}
/** return this.defered.promise;
* Clear all items in wait };
*/
Queue.prototype.clear = function(){
this._q = [];
this.running = false;
};
/** /**
* Get the number of tasks in the queue * Flush all, as quickly as possible
* @return {int} tasks * @return {Promise}
*/ */
Queue.prototype.length = function(){ flush(){
return this._q.length;
}; if(this.running){
return this.running;
}
if(this._q.length) {
this.running = this.dequeue()
.then(function(){
this.running = undefined;
return this.flush();
}.bind(this));
return this.running;
}
};
/**
* Clear all items in wait
*/
clear(){
this._q = [];
this.running = false;
};
/**
* Get the number of tasks in the queue
* @return {int} tasks
*/
length(){
return this._q.length;
};
/**
* Pause a running queue
*/
pause(){
this.paused = true;
};
}
/**
* Pause a running queue
*/
Queue.prototype.pause = function(){
this.paused = true;
};
/** /**
* Create a new task from a callback * Create a new task from a callback
@ -203,25 +206,28 @@ Queue.prototype.pause = function(){
* @param {scope} context * @param {scope} context
* @return {function} task * @return {function} task
*/ */
function Task(task, args, context){ class Task {
constructor(task, args, context){
return function(){ return function(){
var toApply = arguments || []; var toApply = arguments || [];
return new Promise(function(resolve, reject) { return new Promise(function(resolve, reject) {
var callback = function(value){ var callback = function(value){
resolve(value); resolve(value);
}; };
// Add the callback to the arguments list // Add the callback to the arguments list
toApply.push(callback); toApply.push(callback);
// Apply all arguments to the functions // Apply all arguments to the functions
task.apply(this, toApply); task.apply(this, toApply);
}.bind(this)); }.bind(this));
};
}; };
}
};
module.exports = Queue; export default Queue;

File diff suppressed because it is too large Load diff

View file

@ -1,8 +1,8 @@
// var URI = require('urijs'); // import URI from 'urijs';
var core = require('./core'); import { qs } from './utils/core';
var Url = require('./core').Url; import Url from './utils/url';
function base(doc, section){ export function replaceBase(doc, section){
var base; var base;
var head; var head;
@ -12,8 +12,8 @@ function base(doc, section){
// head = doc.querySelector("head"); // head = doc.querySelector("head");
// base = head.querySelector("base"); // base = head.querySelector("base");
head = core.qs(doc, "head"); head = qs(doc, "head");
base = core.qs(head, "base"); base = qs(head, "base");
if(!base) { if(!base) {
base = doc.createElement("base"); base = doc.createElement("base");
@ -23,7 +23,7 @@ function base(doc, section){
base.setAttribute("href", section.url); base.setAttribute("href", section.url);
} }
function canonical(doc, section){ export function replaceCanonical(doc, section){
var head; var head;
var link; var link;
var url = section.url; // window.location.origin + window.location.pathname + "?loc=" + encodeURIComponent(section.url); var url = section.url; // window.location.origin + window.location.pathname + "?loc=" + encodeURIComponent(section.url);
@ -32,8 +32,8 @@ function canonical(doc, section){
return; return;
} }
head = core.qs(doc, "head"); head = qs(doc, "head");
link = core.qs(head, "link[rel='canonical']"); link = qs(head, "link[rel='canonical']");
if (link) { if (link) {
link.setAttribute("href", url); link.setAttribute("href", url);
@ -45,10 +45,10 @@ function canonical(doc, section){
} }
} }
function links(view, renderer) { export function replaceLinks(view, renderer) {
var links = view.document.querySelectorAll("a[href]"); var links = view.document.querySelectorAll("a[href]");
var replaceLinks = function(link){ var replaceLink = function(link){
var href = link.getAttribute("href"); var href = link.getAttribute("href");
if(href.indexOf("mailto:") === 0){ if(href.indexOf("mailto:") === 0){
@ -92,13 +92,13 @@ function links(view, renderer) {
}.bind(this); }.bind(this);
for (var i = 0; i < links.length; i++) { for (var i = 0; i < links.length; i++) {
replaceLinks(links[i]); replaceLink(links[i]);
} }
}; };
function substitute(content, urls, replacements) { export function substitute(content, urls, replacements) {
urls.forEach(function(url, i){ urls.forEach(function(url, i){
if (url && replacements[i]) { if (url && replacements[i]) {
content = content.replace(new RegExp(url, 'g'), replacements[i]); content = content.replace(new RegExp(url, 'g'), replacements[i]);
@ -106,9 +106,3 @@ function substitute(content, urls, replacements) {
}); });
return content; return content;
} }
module.exports = {
'base': base,
'canonical' : canonical,
'links': links,
'substitute': substitute
};

View file

@ -1,12 +1,12 @@
var core = require('./core'); import {defer, isXml, parse} from './utils/core';
var Path = require('./core').Path; import Path from './utils/path';
function request(url, type, withCredentials, headers) { function request(url, type, withCredentials, headers) {
var supportsURL = (typeof window != "undefined") ? window.URL : false; // TODO: fallback for url if window isn't defined var supportsURL = (typeof window != "undefined") ? window.URL : false; // TODO: fallback for url if window isn't defined
var BLOB_RESPONSE = supportsURL ? "blob" : "arraybuffer"; var BLOB_RESPONSE = supportsURL ? "blob" : "arraybuffer";
var uri; var uri;
var deferred = new core.defer(); var deferred = new defer();
var xhr = new XMLHttpRequest(); var xhr = new XMLHttpRequest();
@ -49,7 +49,7 @@ function request(url, type, withCredentials, headers) {
} }
if(core.isXml(type)) { if(isXml(type)) {
// xhr.responseType = "document"; // xhr.responseType = "document";
xhr.overrideMimeType('text/xml'); // for OPF parsing xhr.overrideMimeType('text/xml'); // for OPF parsing
} }
@ -106,16 +106,16 @@ function request(url, type, withCredentials, headers) {
if(responseXML){ if(responseXML){
r = this.responseXML; r = this.responseXML;
} else } else
if(core.isXml(type)){ if(isXml(type)){
// xhr.overrideMimeType('text/xml'); // for OPF parsing // xhr.overrideMimeType('text/xml'); // for OPF parsing
// If this.responseXML wasn't set, try to parse using a DOMParser from text // If this.responseXML wasn't set, try to parse using a DOMParser from text
r = core.parse(this.response, "text/xml"); r = parse(this.response, "text/xml");
}else }else
if(type == 'xhtml'){ if(type == 'xhtml'){
r = core.parse(this.response, "application/xhtml+xml"); r = parse(this.response, "application/xhtml+xml");
}else }else
if(type == 'html' || type == 'htm'){ if(type == 'html' || type == 'htm'){
r = core.parse(this.response, "text/html"); r = parse(this.response, "text/html");
}else }else
if(type == 'json'){ if(type == 'json'){
r = JSON.parse(this.response); r = JSON.parse(this.response);
@ -149,4 +149,4 @@ function request(url, type, withCredentials, headers) {
return deferred.promise; return deferred.promise;
}; };
module.exports = request; export default request;

View file

@ -1,7 +1,7 @@
var replace = require('./replacements'); import {substitute} from './replacements';
var core = require('./core'); import {createBase64Url, createBlobUrl} from './utils/core';
var Path = require('./core').Path; import Path from './utils/path';
var path = require('path'); import path from 'path-webpack';
/** /**
* Handle Package Resources * Handle Package Resources
@ -12,234 +12,235 @@ var path = require('path');
* @param {[Archive]} options.archive * @param {[Archive]} options.archive
* @param {[method]} options.resolver * @param {[method]} options.resolver
*/ */
function Resources(manifest, options) { class Resources {
this.settings = { constructor(manifest, options) {
replacements: (options && options.replacements) || 'base64', this.settings = {
archive: (options && options.archive), replacements: (options && options.replacements) || 'base64',
resolver: (options && options.resolver) archive: (options && options.archive),
}; resolver: (options && options.resolver)
this.manifest = manifest; };
this.resources = Object.keys(manifest). this.manifest = manifest;
map(function (key){ this.resources = Object.keys(manifest).
return manifest[key]; map(function (key){
}); return manifest[key];
});
this.replacementUrls = []; this.replacementUrls = [];
this.split(); this.split();
this.splitUrls(); this.splitUrls();
}
/**
* Split resources by type
* @private
*/
Resources.prototype.split = function(){
// HTML
this.html = this.resources.
filter(function (item){
if (item.type === "application/xhtml+xml" ||
item.type === "text/html") {
return true;
}
});
// Exclude HTML
this.assets = this.resources.
filter(function (item){
if (item.type !== "application/xhtml+xml" &&
item.type !== "text/html") {
return true;
}
});
// Only CSS
this.css = this.resources.
filter(function (item){
if (item.type === "text/css") {
return true;
}
});
};
/**
* Convert split resources into Urls
* @private
*/
Resources.prototype.splitUrls = function(){
// All Assets Urls
this.urls = this.assets.
map(function(item) {
return item.href;
}.bind(this));
// Css Urls
this.cssUrls = this.css.map(function(item) {
return item.href;
});
};
/**
* Create blob urls for all the assets
* @param {Archive} archive
* @param {resolver} resolver Url resolver
* @return {Promise} returns replacement urls
*/
Resources.prototype.replacements = function(archive, resolver){
archive = archive || this.settings.archive;
resolver = resolver || this.settings.resolver;
if (this.settings.replacements === "none") {
return new Promise(function(resolve, reject) {
resolve(this.urls);
}.bind(this));
} }
var replacements = this.urls. /**
map(function(url) { * Split resources by type
var absolute = resolver(url); * @private
*/
split(){
return archive.createUrl(absolute, {"base64": (this.settings.replacements === "base64")}); // HTML
}.bind(this)) this.html = this.resources.
filter(function (item){
return Promise.all(replacements) if (item.type === "application/xhtml+xml" ||
.then(function(replacementUrls) { item.type === "text/html") {
this.replacementUrls = replacementUrls; return true;
return replacementUrls;
}.bind(this));
};
/**
* Replace URLs in CSS resources
* @private
* @param {[Archive]} archive
* @param {[method]} resolver
* @return {Promise}
*/
Resources.prototype.replaceCss = function(archive, resolver){
var replaced = [];
archive = archive || this.settings.archive;
resolver = resolver || this.settings.resolver;
this.cssUrls.forEach(function(href) {
var replacement = this.createCssFile(href, archive, resolver)
.then(function (replacementUrl) {
// switch the url in the replacementUrls
var indexInUrls = this.urls.indexOf(href);
if (indexInUrls > -1) {
this.replacementUrls[indexInUrls] = replacementUrl;
} }
});
// Exclude HTML
this.assets = this.resources.
filter(function (item){
if (item.type !== "application/xhtml+xml" &&
item.type !== "text/html") {
return true;
}
});
// Only CSS
this.css = this.resources.
filter(function (item){
if (item.type === "text/css") {
return true;
}
});
};
/**
* Convert split resources into Urls
* @private
*/
splitUrls(){
// All Assets Urls
this.urls = this.assets.
map(function(item) {
return item.href;
}.bind(this)); }.bind(this));
replaced.push(replacement); // Css Urls
}.bind(this)); this.cssUrls = this.css.map(function(item) {
return Promise.all(replaced); return item.href;
}; });
/** };
* Create a new CSS file with the replaced URLs
* @private /**
* @param {string} href the original css file * Create blob urls for all the assets
* @param {[Archive]} archive * @param {Archive} archive
* @param {[method]} resolver * @param {resolver} resolver Url resolver
* @return {Promise} returns a BlobUrl to the new CSS file or a data url * @return {Promise} returns replacement urls
*/ */
Resources.prototype.createCssFile = function(href, archive, resolver){ replacements(archive, resolver){
var newUrl;
var indexInUrls;
archive = archive || this.settings.archive; archive = archive || this.settings.archive;
resolver = resolver || this.settings.resolver; resolver = resolver || this.settings.resolver;
if (path.isAbsolute(href)) { if (this.settings.replacements === "none") {
return new Promise(function(resolve, reject){ return new Promise(function(resolve, reject) {
resolve(urls, replacementUrls); resolve(this.urls);
}); }.bind(this));
} }
var absolute = resolver(href); var replacements = this.urls.
map(function(url) {
var absolute = resolver(url);
// Get the text of the css file from the archive return archive.createUrl(absolute, {"base64": (this.settings.replacements === "base64")});
var textResponse = archive.getText(absolute); }.bind(this))
// Get asset links relative to css file
var relUrls = this.urls.map(function(assetHref) {
var resolved = resolver(assetHref);
var relative = new Path(absolute).relative(resolved);
return relative; return Promise.all(replacements)
.then(function(replacementUrls) {
this.replacementUrls = replacementUrls;
return replacementUrls;
}.bind(this)); }.bind(this));
};
return textResponse.then(function (text) { /**
// Replacements in the css text * Replace URLs in CSS resources
text = replace.substitute(text, relUrls, this.replacementUrls); * @private
* @param {[Archive]} archive
* @param {[method]} resolver
* @return {Promise}
*/
replaceCss(archive, resolver){
var replaced = [];
archive = archive || this.settings.archive;
resolver = resolver || this.settings.resolver;
this.cssUrls.forEach(function(href) {
var replacement = this.createCssFile(href, archive, resolver)
.then(function (replacementUrl) {
// switch the url in the replacementUrls
var indexInUrls = this.urls.indexOf(href);
if (indexInUrls > -1) {
this.replacementUrls[indexInUrls] = replacementUrl;
}
}.bind(this));
// Get the new url replaced.push(replacement);
if (this.settings.replacements === "base64") { }.bind(this));
newUrl = core.createBase64Url(text, 'text/css'); return Promise.all(replaced);
} else { };
newUrl = core.createBlobUrl(text, 'text/css');
/**
* Create a new CSS file with the replaced URLs
* @private
* @param {string} href the original css file
* @param {[Archive]} archive
* @param {[method]} resolver
* @return {Promise} returns a BlobUrl to the new CSS file or a data url
*/
createCssFile(href, archive, resolver){
var newUrl;
var indexInUrls;
archive = archive || this.settings.archive;
resolver = resolver || this.settings.resolver;
if (path.isAbsolute(href)) {
return new Promise(function(resolve, reject){
resolve(urls, replacementUrls);
});
} }
return newUrl; var absolute = resolver(href);
}.bind(this));
}; // Get the text of the css file from the archive
var textResponse = archive.getText(absolute);
// Get asset links relative to css file
var relUrls = this.urls.map(function(assetHref) {
var resolved = resolver(assetHref);
var relative = new Path(absolute).relative(resolved);
/** return relative;
* Resolve all resources URLs relative to an absolute URL }.bind(this));
* @param {string} absolute to be resolved to
* @param {[resolver]} resolver
* @return {string[]} array with relative Urls
*/
Resources.prototype.relativeTo = function(absolute, resolver){
resolver = resolver || this.settings.resolver;
// Get Urls relative to current sections return textResponse.then(function (text) {
return this.urls. // Replacements in the css text
map(function(href) { text = substitute(text, relUrls, this.replacementUrls);
var resolved = resolver(href);
var relative = new Path(absolute).relative(resolved);
return relative;
}.bind(this));
};
/** // Get the new url
* Get a URL for a resource if (this.settings.replacements === "base64") {
* @param {string} path newUrl = createBase64Url(text, 'text/css');
* @return {string} url } else {
*/ newUrl = createBlobUrl(text, 'text/css');
Resources.prototype.get = function(path) { }
var indexInUrls = this.urls.indexOf(path);
if (indexInUrls === -1) { return newUrl;
return; }.bind(this));
}
if (this.replacementUrls.length) { };
return new Promise(function(resolve, reject) {
resolve(this.replacementUrls[indexInUrls]); /**
}.bind(this)); * Resolve all resources URLs relative to an absolute URL
} else { * @param {string} absolute to be resolved to
return archive.createUrl(absolute, * @param {[resolver]} resolver
{"base64": (this.settings.replacements === "base64")}) * @return {string[]} array with relative Urls
*/
relativeTo(absolute, resolver){
resolver = resolver || this.settings.resolver;
// Get Urls relative to current sections
return this.urls.
map(function(href) {
var resolved = resolver(href);
var relative = new Path(absolute).relative(resolved);
return relative;
}.bind(this));
};
/**
* Get a URL for a resource
* @param {string} path
* @return {string} url
*/
get(path) {
var indexInUrls = this.urls.indexOf(path);
if (indexInUrls === -1) {
return;
}
if (this.replacementUrls.length) {
return new Promise(function(resolve, reject) {
resolve(this.replacementUrls[indexInUrls]);
}.bind(this));
} else {
return archive.createUrl(absolute,
{"base64": (this.settings.replacements === "base64")})
}
} }
/**
* Substitute urls in content, with replacements,
* relative to a url if provided
* @param {string} content
* @param {[string]} url url to resolve to
* @return {string} content with urls substituted
*/
substitute(content, url) {
var relUrls;
if (url) {
relUrls = this.relativeTo(url);
} else {
relUrls = this.urls;
}
return substitute(content, relUrls, this.replacementUrls);
};
} }
/** export default Resources;
* Substitute urls in content, with replacements,
* relative to a url if provided
* @param {string} content
* @param {[string]} url url to resolve to
* @return {string} content with urls substituted
*/
Resources.prototype.substitute = function(content, url) {
var relUrls;
if (url) {
relUrls = this.relativeTo(url);
} else {
relUrls = this.urls;
}
return replace.substitute(content, relUrls, this.replacementUrls);
};
module.exports = Resources;

View file

@ -1,7 +1,7 @@
var core = require('./core'); import { defer } from './utils/core';
var EpubCFI = require('./epubcfi'); import EpubCFI from './epubcfi';
var Hook = require('./hook'); import Hook from './hook';
var Url = require('./core').Url; import Url from './utils/url';
/** /**
* Represents a Section of the Book * Represents a Section of the Book
@ -9,178 +9,180 @@ var Url = require('./core').Url;
* @param {object} item The spine item representing the section * @param {object} item The spine item representing the section
* @param {object} hooks hooks for serialize and content * @param {object} hooks hooks for serialize and content
*/ */
function Section(item, hooks){ class Section {
this.idref = item.idref; constructor(item, hooks){
this.linear = item.linear; this.idref = item.idref;
this.properties = item.properties; this.linear = item.linear;
this.index = item.index; this.properties = item.properties;
this.href = item.href; this.index = item.index;
this.url = item.url; this.href = item.href;
this.next = item.next; this.url = item.url;
this.prev = item.prev; this.next = item.next;
this.prev = item.prev;
this.cfiBase = item.cfiBase; this.cfiBase = item.cfiBase;
if (hooks) { if (hooks) {
this.hooks = hooks; this.hooks = hooks;
} else { } else {
this.hooks = {}; this.hooks = {};
this.hooks.serialize = new Hook(this); this.hooks.serialize = new Hook(this);
this.hooks.content = new Hook(this); this.hooks.content = new Hook(this);
}
};
/**
* Load the section from its url
* @param {method} _request a request method to use for loading
* @return {document} a promise with the xml document
*/
Section.prototype.load = function(_request){
var request = _request || this.request || require('./request');
var loading = new core.defer();
var loaded = loading.promise;
if(this.contents) {
loading.resolve(this.contents);
} else {
request(this.url)
.then(function(xml){
var base;
var directory = new Url(this.url).directory;
this.document = xml;
this.contents = xml.documentElement;
return this.hooks.content.trigger(this.document, this);
}.bind(this))
.then(function(){
loading.resolve(this.contents);
}.bind(this))
.catch(function(error){
loading.reject(error);
});
}
return loaded;
};
/**
* Adds a base tag for resolving urls in the section
* @private
* @param {document} _document
*/
Section.prototype.base = function(_document){
var task = new core.defer();
var base = _document.createElement("base"); // TODO: check if exists
var head;
base.setAttribute("href", window.location.origin + "/" +this.url);
if(_document) {
head = _document.querySelector("head");
}
if(head) {
head.insertBefore(base, head.firstChild);
task.resolve();
} else {
task.reject(new Error("No head to insert into"));
}
return task.promise;
};
/**
* Render the contents of a section
* @param {method} _request a request method to use for loading
* @return {string} output a serialized XML Document
*/
Section.prototype.render = function(_request){
var rendering = new core.defer();
var rendered = rendering.promise;
this.output; // TODO: better way to return this from hooks?
this.load(_request).
then(function(contents){
var serializer;
if (typeof XMLSerializer === "undefined") {
XMLSerializer = require('xmldom').XMLSerializer;
} }
serializer = new XMLSerializer();
this.output = serializer.serializeToString(contents);
return this.output;
}.bind(this)).
then(function(){
return this.hooks.serialize.trigger(this.output, this);
}.bind(this)).
then(function(){
rendering.resolve(this.output);
}.bind(this))
.catch(function(error){
rendering.reject(error);
});
return rendered;
};
/**
* Find a string in a section
* TODO: need reimplementation from v0.2
* @param {string} query [description]
* @return {[type]} [description]
*/
Section.prototype.find = function(query){
};
/**
* Reconciles the current chapters layout properies with
* the global layout properities.
* @param {object} global The globa layout settings object, chapter properties string
* @return {object} layoutProperties Object with layout properties
*/
Section.prototype.reconcileLayoutSettings = function(global){
//-- Get the global defaults
var settings = {
layout : global.layout,
spread : global.spread,
orientation : global.orientation
}; };
//-- Get the chapter's display type /**
this.properties.forEach(function(prop){ * Load the section from its url
var rendition = prop.replace("rendition:", ''); * @param {method} _request a request method to use for loading
var split = rendition.indexOf("-"); * @return {document} a promise with the xml document
var property, value; */
load(_request){
var request = _request || this.request || require('./request');
var loading = new defer();
var loaded = loading.promise;
if(split != -1){ if(this.contents) {
property = rendition.slice(0, split); loading.resolve(this.contents);
value = rendition.slice(split+1); } else {
request(this.url)
.then(function(xml){
var base;
var directory = new Url(this.url).directory;
settings[property] = value; this.document = xml;
this.contents = xml.documentElement;
return this.hooks.content.trigger(this.document, this);
}.bind(this))
.then(function(){
loading.resolve(this.contents);
}.bind(this))
.catch(function(error){
loading.reject(error);
});
} }
});
return settings;
};
/** return loaded;
* Get a CFI from a Range in the Section };
* @param {range} _range
* @return {string} cfi an EpubCFI string
*/
Section.prototype.cfiFromRange = function(_range) {
return new EpubCFI(_range, this.cfiBase).toString();
};
/** /**
* Get a CFI from an Element in the Section * Adds a base tag for resolving urls in the section
* @param {element} el * @private
* @return {string} cfi an EpubCFI string * @param {document} _document
*/ */
Section.prototype.cfiFromElement = function(el) { base(_document){
return new EpubCFI(el, this.cfiBase).toString(); var task = new defer();
}; var base = _document.createElement("base"); // TODO: check if exists
var head;
module.exports = Section; base.setAttribute("href", window.location.origin + "/" +this.url);
if(_document) {
head = _document.querySelector("head");
}
if(head) {
head.insertBefore(base, head.firstChild);
task.resolve();
} else {
task.reject(new Error("No head to insert into"));
}
return task.promise;
};
/**
* Render the contents of a section
* @param {method} _request a request method to use for loading
* @return {string} output a serialized XML Document
*/
render(_request){
var rendering = new defer();
var rendered = rendering.promise;
this.output; // TODO: better way to return this from hooks?
this.load(_request).
then(function(contents){
var serializer;
if (typeof XMLSerializer === "undefined") {
XMLSerializer = require('xmldom').XMLSerializer;
}
serializer = new XMLSerializer();
this.output = serializer.serializeToString(contents);
return this.output;
}.bind(this)).
then(function(){
return this.hooks.serialize.trigger(this.output, this);
}.bind(this)).
then(function(){
rendering.resolve(this.output);
}.bind(this))
.catch(function(error){
rendering.reject(error);
});
return rendered;
};
/**
* Find a string in a section
* TODO: need reimplementation from v0.2
* @param {string} query [description]
* @return {[type]} [description]
*/
find(query){
};
/**
* Reconciles the current chapters layout properies with
* the global layout properities.
* @param {object} global The globa layout settings object, chapter properties string
* @return {object} layoutProperties Object with layout properties
*/
reconcileLayoutSettings(global){
//-- Get the global defaults
var settings = {
layout : global.layout,
spread : global.spread,
orientation : global.orientation
};
//-- Get the chapter's display type
this.properties.forEach(function(prop){
var rendition = prop.replace("rendition:", '');
var split = rendition.indexOf("-");
var property, value;
if(split != -1){
property = rendition.slice(0, split);
value = rendition.slice(split+1);
settings[property] = value;
}
});
return settings;
};
/**
* Get a CFI from a Range in the Section
* @param {range} _range
* @return {string} cfi an EpubCFI string
*/
cfiFromRange(_range) {
return new EpubCFI(_range, this.cfiBase).toString();
};
/**
* Get a CFI from an Element in the Section
* @param {element} el
* @return {string} cfi an EpubCFI string
*/
cfiFromElement(el) {
return new EpubCFI(el, this.cfiBase).toString();
};
}
export default Section;

View file

@ -1,161 +1,163 @@
var core = require('./core'); import core from './utils/core';
var EpubCFI = require('./epubcfi'); import EpubCFI from './epubcfi';
var Hook = require('./hook'); import Hook from './hook';
var Section = require('./section'); import Section from './section';
var replacements = require('./replacements'); import {replaceBase, replaceCanonical} from './replacements';
/** /**
* A collection of Spine Items * A collection of Spine Items
*/ */
function Spine(){ class Spine {
this.spineItems = []; constructor() {
this.spineByHref = {}; this.spineItems = [];
this.spineById = {}; this.spineByHref = {};
this.spineById = {};
this.hooks = {}; this.hooks = {};
this.hooks.serialize = new Hook(); this.hooks.serialize = new Hook();
this.hooks.content = new Hook(); this.hooks.content = new Hook();
// Register replacements // Register replacements
this.hooks.content.register(replacements.base); this.hooks.content.register(replaceBase);
this.hooks.content.register(replacements.canonical); this.hooks.content.register(replaceCanonical);
this.epubcfi = new EpubCFI(); this.epubcfi = new EpubCFI();
this.loaded = false; this.loaded = false;
}; };
/** /**
* Unpack items from a opf into spine items * Unpack items from a opf into spine items
* @param {Package} _package * @param {Package} _package
* @param {method} resolver URL resolver * @param {method} resolver URL resolver
*/ */
Spine.prototype.unpack = function(_package, resolver) { unpack(_package, resolver) {
this.items = _package.spine; this.items = _package.spine;
this.manifest = _package.manifest; this.manifest = _package.manifest;
this.spineNodeIndex = _package.spineNodeIndex; this.spineNodeIndex = _package.spineNodeIndex;
this.baseUrl = _package.baseUrl || _package.basePath || ''; this.baseUrl = _package.baseUrl || _package.basePath || '';
this.length = this.items.length; this.length = this.items.length;
this.items.forEach(function(item, index){ this.items.forEach(function(item, index){
var href, url; var href, url;
var manifestItem = this.manifest[item.idref]; var manifestItem = this.manifest[item.idref];
var spineItem; var spineItem;
item.cfiBase = this.epubcfi.generateChapterComponent(this.spineNodeIndex, item.index, item.idref); item.cfiBase = this.epubcfi.generateChapterComponent(this.spineNodeIndex, item.index, item.idref);
if(manifestItem) { if(manifestItem) {
item.href = manifestItem.href; item.href = manifestItem.href;
item.url = resolver(item.href, true); item.url = resolver(item.href, true);
if(manifestItem.properties.length){ if(manifestItem.properties.length){
item.properties.push.apply(item.properties, manifestItem.properties); item.properties.push.apply(item.properties, manifestItem.properties);
}
} }
item.prev = function(){ return this.get(index-1); }.bind(this);
item.next = function(){ return this.get(index+1); }.bind(this);
spineItem = new Section(item, this.hooks);
this.append(spineItem);
}.bind(this));
this.loaded = true;
};
/**
* Get an item from the spine
* @param {[string|int]} target
* @return {Section} section
* @example spine.get();
* @example spine.get(1);
* @example spine.get("chap1.html");
* @example spine.get("#id1234");
*/
get(target) {
var index = 0;
if(this.epubcfi.isCfiString(target)) {
cfi = new EpubCFI(target);
index = cfi.spinePos;
} else if(target && (typeof target === "number" || isNaN(target) === false)){
index = target;
} else if(target && target.indexOf("#") === 0) {
index = this.spineById[target.substring(1)];
} else if(target) {
// Remove fragments
target = target.split("#")[0];
index = this.spineByHref[target];
} }
item.prev = function(){ return this.get(index-1); }.bind(this); return this.spineItems[index] || null;
item.next = function(){ return this.get(index+1); }.bind(this); };
spineItem = new Section(item, this.hooks); /**
* Append a Section to the Spine
* @private
* @param {Section} section
*/
append(section) {
var index = this.spineItems.length;
section.index = index;
this.append(spineItem); this.spineItems.push(section);
this.spineByHref[section.href] = index;
this.spineById[section.idref] = index;
}.bind(this)); return index;
};
this.loaded = true; /**
}; * Prepend a Section to the Spine
* @private
* @param {Section} section
*/
prepend(section) {
var index = this.spineItems.unshift(section);
this.spineByHref[section.href] = 0;
this.spineById[section.idref] = 0;
/** // Re-index
* Get an item from the spine this.spineItems.forEach(function(item, index){
* @param {[string|int]} target item.index = index;
* @return {Section} section });
* @example spine.get();
* @example spine.get(1);
* @example spine.get("chap1.html");
* @example spine.get("#id1234");
*/
Spine.prototype.get = function(target) {
var index = 0;
if(this.epubcfi.isCfiString(target)) { return 0;
cfi = new EpubCFI(target); };
index = cfi.spinePos;
} else if(target && (typeof target === "number" || isNaN(target) === false)){
index = target;
} else if(target && target.indexOf("#") === 0) {
index = this.spineById[target.substring(1)];
} else if(target) {
// Remove fragments
target = target.split("#")[0];
index = this.spineByHref[target];
}
return this.spineItems[index] || null; // insert(section, index) {
}; //
// };
/** /**
* Append a Section to the Spine * Remove a Section from the Spine
* @private * @private
* @param {Section} section * @param {Section} section
*/ */
Spine.prototype.append = function(section) { remove(section) {
var index = this.spineItems.length; var index = this.spineItems.indexOf(section);
section.index = index;
this.spineItems.push(section); if(index > -1) {
delete this.spineByHref[section.href];
delete this.spineById[section.idref];
this.spineByHref[section.href] = index; return this.spineItems.splice(index, 1);
this.spineById[section.idref] = index; }
};
return index; /**
}; * Loop over the Sections in the Spine
* @return {method} forEach
*/
each() {
return this.spineItems.forEach.apply(this.spineItems, arguments);
};
}
/** export default Spine;
* Prepend a Section to the Spine
* @private
* @param {Section} section
*/
Spine.prototype.prepend = function(section) {
var index = this.spineItems.unshift(section);
this.spineByHref[section.href] = 0;
this.spineById[section.idref] = 0;
// Re-index
this.spineItems.forEach(function(item, index){
item.index = index;
});
return 0;
};
// Spine.prototype.insert = function(section, index) {
//
// };
/**
* Remove a Section from the Spine
* @private
* @param {Section} section
*/
Spine.prototype.remove = function(section) {
var index = this.spineItems.indexOf(section);
if(index > -1) {
delete this.spineByHref[section.href];
delete this.spineById[section.idref];
return this.spineItems.splice(index, 1);
}
};
/**
* Loop over the Sections in the Spine
* @return {method} forEach
*/
Spine.prototype.each = function() {
return this.spineItems.forEach.apply(this.spineItems, arguments);
};
module.exports = Spine;

View file

@ -1,155 +1,158 @@
var Url = require('./core').Url; import Url from './utils/url';
function Themes(rendition) { class Themes {
this.rendition = rendition; constructor(rendition) {
this._themes = { this.rendition = rendition;
"default" : { this._themes = {
"rules" : [], "default" : {
"url" : "", "rules" : [],
"serialized" : '' "url" : "",
"serialized" : ''
}
};
this._overrides = {};
this._current = "default";
this._injected = [];
this.rendition.hooks.content.register(this.inject.bind(this));
this.rendition.hooks.content.register(this.overrides.bind(this));
}
register () {
if (arguments.length === 0) {
return;
}
if (arguments.length === 1 && typeof(arguments[0]) === "object") {
return this.registerThemes(arguments[0]);
}
if (arguments.length === 1 && typeof(arguments[0]) === "string") {
return this.default(arguments[0]);
}
if (arguments.length === 2 && typeof(arguments[1]) === "string") {
return this.registerUrl(arguments[0], arguments[1]);
}
if (arguments.length === 2 && typeof(arguments[1]) === "object") {
return this.registerRules(arguments[0], arguments[1]);
} }
}; };
this._overrides = {};
this._current = "default"; default (theme) {
this._injected = []; if (!theme) {
this.rendition.hooks.content.register(this.inject.bind(this)); return;
this.rendition.hooks.content.register(this.overrides.bind(this)); }
if (typeof(theme) === "string") {
return this.registerUrl("default", theme);
}
if (typeof(theme) === "object") {
return this.registerRules("default", theme);
}
};
registerThemes (themes) {
for (var theme in themes) {
if (themes.hasOwnProperty(theme)) {
if (typeof(themes[theme]) === "string") {
this.registerUrl(theme, themes[theme]);
} else {
this.registerRules(theme, themes[theme]);
}
}
}
};
registerUrl (name, input) {
var url = new Url(input);
this._themes[name] = { "url": url.toString() };
};
registerRules (name, rules) {
this._themes[name] = { "rules": rules };
// TODO: serialize css rules
};
apply (name) {
var prev = this._current;
var contents;
this._current = name;
this.update(name);
contents = this.rendition.getContents();
contents.forEach(function (content) {
content.removeClass(prev);
content.addClass(name);
}.bind(this));
};
update (name) {
var contents = this.rendition.getContents();
contents.forEach(function (content) {
this.add(name, content);
}.bind(this));
};
inject (view) {
var links = [];
var themes = this._themes;
var theme;
for (var name in themes) {
if (themes.hasOwnProperty(name)) {
theme = themes[name];
if(theme.rules || (theme.url && links.indexOf(theme.url) === -1)) {
this.add(name, view.contents);
}
}
}
if(this._current) {
view.contents.addClass(this._current);
}
};
add (name, contents) {
var theme = this._themes[name];
if (!theme) {
return;
}
if (theme.url) {
contents.addStylesheet(theme.url);
} else if (theme.serialized) {
// TODO: handle serialized
} else if (theme.rules && theme.rules.length) {
contents.addStylesheetRules(theme.rules);
theme.injected = true;
}
};
override (name, value) {
var contents = this.rendition.getContents();
this._overrides[name] = value;
contents.forEach(function (content) {
content.css(name, this._overrides[name]);
}.bind(this));
};
overrides (view) {
var contents = view.contents;
var overrides = this._overrides;
var rules = [];
for (var rule in overrides) {
if (overrides.hasOwnProperty(rule)) {
contents.css(rule, overrides[rule]);
}
}
};
fontSize (size) {
this.override("font-size", size);
};
} }
Themes.prototype.register = function () { export default Themes;
if (arguments.length === 0) {
return;
}
if (arguments.length === 1 && typeof(arguments[0]) === "object") {
return this.registerThemes(arguments[0]);
}
if (arguments.length === 1 && typeof(arguments[0]) === "string") {
return this.default(arguments[0]);
}
if (arguments.length === 2 && typeof(arguments[1]) === "string") {
return this.registerUrl(arguments[0], arguments[1]);
}
if (arguments.length === 2 && typeof(arguments[1]) === "object") {
return this.registerRules(arguments[0], arguments[1]);
}
};
Themes.prototype.default = function (theme) {
if (!theme) {
return;
}
if (typeof(theme) === "string") {
return this.registerUrl("default", theme);
}
if (typeof(theme) === "object") {
return this.registerRules("default", theme);
}
};
Themes.prototype.registerThemes = function (themes) {
for (var theme in themes) {
if (themes.hasOwnProperty(theme)) {
if (typeof(themes[theme]) === "string") {
this.registerUrl(theme, themes[theme]);
} else {
this.registerRules(theme, themes[theme]);
}
}
}
};
Themes.prototype.registerUrl = function (name, input) {
var url = new Url(input);
this._themes[name] = { "url": url.toString() };
};
Themes.prototype.registerRules = function (name, rules) {
this._themes[name] = { "rules": rules };
// TODO: serialize css rules
};
Themes.prototype.apply = function (name) {
var prev = this._current;
var contents;
this._current = name;
this.update(name);
contents = this.rendition.getContents();
contents.forEach(function (content) {
content.removeClass(prev);
content.addClass(name);
}.bind(this));
};
Themes.prototype.update = function (name) {
var contents = this.rendition.getContents();
contents.forEach(function (content) {
this.add(name, content);
}.bind(this));
};
Themes.prototype.inject = function (view) {
var links = [];
var themes = this._themes;
var theme;
for (var name in themes) {
if (themes.hasOwnProperty(name)) {
theme = themes[name];
if(theme.rules || (theme.url && links.indexOf(theme.url) === -1)) {
this.add(name, view.contents);
}
}
}
if(this._current) {
view.contents.addClass(this._current);
}
};
Themes.prototype.add = function (name, contents) {
var theme = this._themes[name];
if (!theme) {
return;
}
if (theme.url) {
contents.addStylesheet(theme.url);
} else if (theme.serialized) {
// TODO: handle serialized
} else if (theme.rules && theme.rules.length) {
contents.addStylesheetRules(theme.rules);
theme.injected = true;
}
};
Themes.prototype.override = function (name, value) {
var contents = this.rendition.getContents();
this._overrides[name] = value;
contents.forEach(function (content) {
content.css(name, this._overrides[name]);
}.bind(this));
};
Themes.prototype.overrides = function (view) {
var contents = view.contents;
var overrides = this._overrides;
var rules = [];
for (var rule in overrides) {
if (overrides.hasOwnProperty(rule)) {
contents.css(rule, overrides[rule]);
}
}
};
Themes.prototype.fontSize = function (size) {
this.override("font-size", size);
};
module.exports = Themes;

View file

@ -1,144 +1,11 @@
var base64 = require('base64-js'); export const requestAnimationFrame = (typeof window != 'undefined') ? (window.requestAnimationFrame || window.mozRequestAnimationFrame || window.webkitRequestAnimationFrame || window.msRequestAnimationFrame) : false;
var path = require('path');
var requestAnimationFrame = (typeof window != 'undefined') ? (window.requestAnimationFrame || window.mozRequestAnimationFrame || window.webkitRequestAnimationFrame || window.msRequestAnimationFrame) : false; export function isElement(obj) {
/**
* creates a uri object
* @param {string} urlString a url string (relative or absolute)
* @param {[string]} baseString optional base for the url,
* default to window.location.href
* @return {object} url
*/
function Url(urlString, baseString) {
var absolute = (urlString.indexOf('://') > -1);
var pathname = urlString;
this.Url = undefined;
this.href = urlString;
this.protocol = "";
this.origin = "";
this.fragment = "";
this.search = "";
this.base = baseString;
if (!absolute && (typeof(baseString) !== "string")) {
this.base = window && window.location.href;
}
// URL Polyfill doesn't throw an error if base is empty
if (absolute || this.base) {
try {
this.Url = new URL(urlString, this.base);
this.href = this.Url.href;
this.protocol = this.Url.protocol;
this.origin = this.Url.origin;
this.fragment = this.Url.fragment;
this.search = this.Url.search;
pathname = this.Url.pathname;
} catch (e) {
// Skip URL parsing
this.Url = undefined;
}
}
this.Path = new Path(pathname);
this.directory = this.Path.directory;
this.filename = this.Path.filename;
this.extension = this.Path.extension;
}
Url.prototype.path = function () {
return this.Path;
};
Url.prototype.resolve = function (what) {
var isAbsolute = (what.indexOf('://') > -1);
var fullpath;
if (isAbsolute) {
return what;
}
fullpath = path.resolve(this.directory, what);
return this.origin + fullpath;
};
Url.prototype.relative = function (what) {
return path.relative(what, this.directory);
};
Url.prototype.toString = function () {
return this.href;
};
function Path(pathString) {
var protocol;
var parsed;
protocol = pathString.indexOf('://');
if (protocol > -1) {
pathString = new URL(pathString).pathname;
}
parsed = this.parse(pathString);
this.path = pathString;
if (this.isDirectory(pathString)) {
this.directory = pathString;
} else {
this.directory = parsed.dir + "/";
}
this.filename = parsed.base;
this.extension = parsed.ext.slice(1);
}
Path.prototype.parse = function (what) {
return path.parse(what);
};
Path.prototype.isAbsolute = function (what) {
return path.isAbsolute(what || this.path);
};
Path.prototype.isDirectory = function (what) {
return (what.charAt(what.length-1) === '/');
};
Path.prototype.resolve = function (what) {
return path.resolve(this.directory, what);
};
Path.prototype.relative = function (what) {
return path.relative(this.directory, what);
};
Path.prototype.splitPath = function(filename) {
return this.splitPathRe.exec(filename).slice(1);
};
Path.prototype.toString = function () {
return this.path;
};
function assertPath(path) {
if (typeof path !== 'string') {
throw new TypeError('Path must be a string. Received ', path);
}
};
function isElement(obj) {
return !!(obj && obj.nodeType == 1); return !!(obj && obj.nodeType == 1);
}; };
// http://stackoverflow.com/questions/105034/how-to-create-a-guid-uuid-in-javascript // http://stackoverflow.com/questions/105034/how-to-create-a-guid-uuid-in-javascript
function uuid() { export function uuid() {
var d = new Date().getTime(); var d = new Date().getTime();
var uuid = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) { var uuid = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
var r = (d + Math.random()*16)%16 | 0; var r = (d + Math.random()*16)%16 | 0;
@ -149,7 +16,7 @@ function uuid() {
}; };
// From Lodash // From Lodash
function values(object) { export function values(object) {
var index = -1, var index = -1,
props = Object.keys(object), props = Object.keys(object),
length = props.length, length = props.length,
@ -161,70 +28,7 @@ function values(object) {
return result; return result;
}; };
function resolveUrl(base, path) { export function documentHeight() {
var url = [],
segments = [],
baseUri = uri(base),
pathUri = uri(path),
baseDirectory = baseUri.directory,
pathDirectory = pathUri.directory,
directories = [],
// folders = base.split("/"),
paths;
// if(uri.host) {
// return path;
// }
if(baseDirectory[0] === "/") {
baseDirectory = baseDirectory.substring(1);
}
if(pathDirectory[pathDirectory.length-1] === "/") {
baseDirectory = baseDirectory.substring(0, baseDirectory.length-1);
}
if(pathDirectory[0] === "/") {
pathDirectory = pathDirectory.substring(1);
}
if(pathDirectory[pathDirectory.length-1] === "/") {
pathDirectory = pathDirectory.substring(0, pathDirectory.length-1);
}
if(baseDirectory) {
directories = baseDirectory.split("/");
}
paths = pathDirectory.split("/");
paths.reverse().forEach(function(part, index){
if(part === ".."){
directories.pop();
} else if(part === directories[directories.length-1]) {
directories.pop();
segments.unshift(part);
} else {
segments.unshift(part);
}
});
url = [baseUri.origin];
if(directories.length) {
url = url.concat(directories);
}
if(segments) {
url = url.concat(segments);
}
url = url.concat(pathUri.filename);
return url.join("/");
};
function documentHeight() {
return Math.max( return Math.max(
document.documentElement.clientHeight, document.documentElement.clientHeight,
document.body.scrollHeight, document.body.scrollHeight,
@ -234,15 +38,15 @@ function documentHeight() {
); );
}; };
function isNumber(n) { export function isNumber(n) {
return !isNaN(parseFloat(n)) && isFinite(n); return !isNaN(parseFloat(n)) && isFinite(n);
}; };
function isFloat(n) { export function isFloat(n) {
return isNumber(n) && (Math.floor(n) !== n); return isNumber(n) && (Math.floor(n) !== n);
} }
function prefixed(unprefixed) { export function prefixed(unprefixed) {
var vendors = ["Webkit", "Moz", "O", "ms" ], var vendors = ["Webkit", "Moz", "O", "ms" ],
prefixes = ['-Webkit-', '-moz-', '-o-', '-ms-'], prefixes = ['-Webkit-', '-moz-', '-o-', '-ms-'],
upper = unprefixed[0].toUpperCase() + unprefixed.slice(1), upper = unprefixed[0].toUpperCase() + unprefixed.slice(1),
@ -261,7 +65,7 @@ function prefixed(unprefixed) {
return unprefixed; return unprefixed;
}; };
function defaults(obj) { export function defaults(obj) {
for (var i = 1, length = arguments.length; i < length; i++) { for (var i = 1, length = arguments.length; i < length; i++) {
var source = arguments[i]; var source = arguments[i];
for (var prop in source) { for (var prop in source) {
@ -271,7 +75,7 @@ function defaults(obj) {
return obj; return obj;
}; };
function extend(target) { export function extend(target) {
var sources = [].slice.call(arguments, 1); var sources = [].slice.call(arguments, 1);
sources.forEach(function (source) { sources.forEach(function (source) {
if(!source) return; if(!source) return;
@ -284,14 +88,14 @@ function extend(target) {
// Fast quicksort insert for sorted array -- based on: // 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 // http://stackoverflow.com/questions/1344500/efficient-way-to-insert-a-number-into-a-sorted-array-of-numbers
function insert(item, array, compareFunction) { export function insert(item, array, compareFunction) {
var location = locationOf(item, array, compareFunction); var location = locationOf(item, array, compareFunction);
array.splice(location, 0, item); array.splice(location, 0, item);
return location; return location;
}; };
// Returns where something would fit in // Returns where something would fit in
function locationOf(item, array, compareFunction, _start, _end) { export function locationOf(item, array, compareFunction, _start, _end) {
var start = _start || 0; var start = _start || 0;
var end = _end || array.length; var end = _end || array.length;
var pivot = parseInt(start + (end - start) / 2); var pivot = parseInt(start + (end - start) / 2);
@ -322,7 +126,7 @@ function locationOf(item, array, compareFunction, _start, _end) {
} }
}; };
// Returns -1 of mpt found // Returns -1 of mpt found
function indexOfSorted(item, array, compareFunction, _start, _end) { export function indexOfSorted(item, array, compareFunction, _start, _end) {
var start = _start || 0; var start = _start || 0;
var end = _end || array.length; var end = _end || array.length;
var pivot = parseInt(start + (end - start) / 2); var pivot = parseInt(start + (end - start) / 2);
@ -352,7 +156,7 @@ function indexOfSorted(item, array, compareFunction, _start, _end) {
} }
}; };
function bounds(el) { export function bounds(el) {
var style = window.getComputedStyle(el); var style = window.getComputedStyle(el);
var widthProps = ["width", "paddingRight", "paddingLeft", "marginRight", "marginLeft", "borderRightWidth", "borderLeftWidth"]; var widthProps = ["width", "paddingRight", "paddingLeft", "marginRight", "marginLeft", "borderRightWidth", "borderLeftWidth"];
@ -376,7 +180,7 @@ function bounds(el) {
}; };
function borders(el) { export function borders(el) {
var style = window.getComputedStyle(el); var style = window.getComputedStyle(el);
var widthProps = ["paddingRight", "paddingLeft", "marginRight", "marginLeft", "borderRightWidth", "borderLeftWidth"]; var widthProps = ["paddingRight", "paddingLeft", "marginRight", "marginLeft", "borderRightWidth", "borderLeftWidth"];
@ -400,7 +204,7 @@ function borders(el) {
}; };
function windowBounds() { export function windowBounds() {
var width = window.innerWidth; var width = window.innerWidth;
var height = window.innerHeight; var height = window.innerHeight;
@ -417,7 +221,7 @@ function windowBounds() {
}; };
//https://stackoverflow.com/questions/13482352/xquery-looking-for-text-with-single-quote/13483496#13483496 //https://stackoverflow.com/questions/13482352/xquery-looking-for-text-with-single-quote/13483496#13483496
function cleanStringForXpath(str) { export function cleanStringForXpath(str) {
var parts = str.match(/[^'"]+|['"]/g); var parts = str.match(/[^'"]+|['"]/g);
parts = parts.map(function(part){ parts = parts.map(function(part){
if (part === "'") { if (part === "'") {
@ -432,7 +236,7 @@ function cleanStringForXpath(str) {
return "concat(\'\'," + parts.join(",") + ")"; return "concat(\'\'," + parts.join(",") + ")";
}; };
function indexOfTextNode(textNode){ export function indexOfTextNode(textNode){
var parent = textNode.parentNode; var parent = textNode.parentNode;
var children = parent.childNodes; var children = parent.childNodes;
var sib; var sib;
@ -448,17 +252,17 @@ function indexOfTextNode(textNode){
return index; return index;
}; };
function isXml(ext) { export function isXml(ext) {
return ['xml', 'opf', 'ncx'].indexOf(ext) > -1; return ['xml', 'opf', 'ncx'].indexOf(ext) > -1;
} }
function createBlob(content, mime){ export function createBlob(content, mime){
var blob = new Blob([content], {type : mime }); var blob = new Blob([content], {type : mime });
return blob; return blob;
}; };
function createBlobUrl(content, mime){ export function createBlobUrl(content, mime){
var _URL = window.URL || window.webkitURL || window.mozURL; var _URL = window.URL || window.webkitURL || window.mozURL;
var tempUrl; var tempUrl;
var blob = this.createBlob(content, mime); var blob = this.createBlob(content, mime);
@ -468,7 +272,7 @@ function createBlobUrl(content, mime){
return tempUrl; return tempUrl;
}; };
function createBase64Url(content, mime){ export function createBase64Url(content, mime){
var string; var string;
var data; var data;
var datauri; var datauri;
@ -485,11 +289,11 @@ function createBase64Url(content, mime){
return datauri; return datauri;
}; };
function type(obj){ export function type(obj){
return Object.prototype.toString.call(obj).slice(8, -1); return Object.prototype.toString.call(obj).slice(8, -1);
} }
function parse(markup, mime, forceXMLDom) { export function parse(markup, mime, forceXMLDom) {
var doc; var doc;
if (typeof DOMParser === "undefined" || forceXMLDom) { if (typeof DOMParser === "undefined" || forceXMLDom) {
@ -502,7 +306,7 @@ function parse(markup, mime, forceXMLDom) {
return doc; return doc;
} }
function qs(el, sel) { export function qs(el, sel) {
var elements; var elements;
if (!el) { if (!el) {
throw new Error('No Element Provided'); throw new Error('No Element Provided');
@ -518,7 +322,7 @@ function qs(el, sel) {
} }
} }
function qsa(el, sel) { export function qsa(el, sel) {
if (typeof el.querySelector != "undefined") { if (typeof el.querySelector != "undefined") {
return el.querySelectorAll(sel); return el.querySelectorAll(sel);
@ -527,7 +331,7 @@ function qsa(el, sel) {
} }
} }
function qsp(el, sel, props) { export function qsp(el, sel, props) {
var q, filtered; var q, filtered;
if (typeof el.querySelector != "undefined") { if (typeof el.querySelector != "undefined") {
sel += '['; sel += '[';
@ -558,7 +362,7 @@ function qsp(el, sel, props) {
* @param {element} root element to start with * @param {element} root element to start with
* @param {function} func function to run on each element * @param {function} func function to run on each element
*/ */
function sprint(root, func) { export function sprint(root, func) {
var doc = root.ownerDocument || root; var doc = root.ownerDocument || root;
if (typeof(doc.createTreeWalker) !== "undefined") { if (typeof(doc.createTreeWalker) !== "undefined") {
treeWalker(root, func, NodeFilter.SHOW_TEXT); treeWalker(root, func, NodeFilter.SHOW_TEXT);
@ -571,14 +375,15 @@ function sprint(root, func) {
} }
} }
function treeWalker(root, func, filter) { export function treeWalker(root, func, filter) {
var treeWalker = document.createTreeWalker(root, filter, null, false); var treeWalker = document.createTreeWalker(root, filter, null, false);
let node;
while ((node = treeWalker.nextNode())) { while ((node = treeWalker.nextNode())) {
func(node); func(node);
} }
} }
// function walk(root, func, onlyText) { // export function walk(root, func, onlyText) {
// var node = root; // var node = root;
// //
// if (node && !onlyText || node.nodeType === 3) { // Node.TEXT_NODE // if (node && !onlyText || node.nodeType === 3) { // Node.TEXT_NODE
@ -597,7 +402,7 @@ function treeWalker(root, func, filter) {
* @param callback return false for continue,true for break * @param callback return false for continue,true for break
* @return boolean true: break visit; * @return boolean true: break visit;
*/ */
function walk(node,callback){ export function walk(node,callback){
if(callback(node)){ if(callback(node)){
return true; return true;
} }
@ -608,7 +413,7 @@ function walk(node,callback){
} }
} }
function blob2base64(blob, cb) { export function blob2base64(blob, cb) {
var reader = new FileReader(); var reader = new FileReader();
reader.readAsDataURL(blob); reader.readAsDataURL(blob);
reader.onloadend = function() { reader.onloadend = function() {
@ -617,7 +422,7 @@ function blob2base64(blob, cb) {
} }
// From: https://developer.mozilla.org/en-US/docs/Mozilla/JavaScript_code_modules/Promise.jsm/Deferred#backwards_forwards_compatible // From: https://developer.mozilla.org/en-US/docs/Mozilla/JavaScript_code_modules/Promise.jsm/Deferred#backwards_forwards_compatible
function defer() { export function defer() {
/* A method to resolve the associated Promise with the value passed. /* A method to resolve the associated Promise with the value passed.
* If the promise is already settled it does nothing. * If the promise is already settled it does nothing.
* *
@ -648,14 +453,14 @@ function defer() {
Object.freeze(this); Object.freeze(this);
} }
function querySelectorByType(html, element, type){ export function querySelectorByType(html, element, type){
var query; var query;
if (typeof html.querySelector != "undefined") { if (typeof html.querySelector != "undefined") {
query = html.querySelector(element+'[*|type="'+type+'"]'); query = html.querySelector(element+'[*|type="'+type+'"]');
} }
// Handle IE not supporting namespaced epub:type in querySelector // Handle IE not supporting namespaced epub:type in querySelector
if(!query || query.length === 0) { if(!query || query.length === 0) {
query = this.qsa(html, element); query = qsa(html, element);
for (var i = 0; i < query.length; i++) { for (var i = 0; i < query.length; i++) {
if(query[i].getAttributeNS("http://www.idpf.org/2007/ops", "type") === type) { if(query[i].getAttributeNS("http://www.idpf.org/2007/ops", "type") === type) {
return query[i]; return query[i];
@ -666,53 +471,14 @@ function defer() {
} }
} }
function children(el) { export function findChildren(el) {
var children = []; var result = [];
var childNodes = el.parentNode.childNodes; var childNodes = el.parentNode.childNodes;
for (var i = 0; i < childNodes.length; i++) { for (var i = 0; i < childNodes.length; i++) {
node = childNodes[i]; let node = childNodes[i];
if (node.nodeType === 1) { if (node.nodeType === 1) {
children.push(node); result.push(node);
} }
}; };
return children; return result;
} }
module.exports = {
'isElement': isElement,
'uuid': uuid,
'values': values,
'resolveUrl': resolveUrl,
'indexOfSorted': indexOfSorted,
'documentHeight': documentHeight,
'isNumber': isNumber,
'isFloat': isFloat,
'prefixed': prefixed,
'defaults': defaults,
'extend': extend,
'insert': insert,
'locationOf': locationOf,
'indexOfSorted': indexOfSorted,
'requestAnimationFrame': requestAnimationFrame,
'bounds': bounds,
'borders': borders,
'windowBounds': windowBounds,
'cleanStringForXpath': cleanStringForXpath,
'indexOfTextNode': indexOfTextNode,
'isXml': isXml,
'createBlob': createBlob,
'createBlobUrl': createBlobUrl,
'type': type,
'parse' : parse,
'qs' : qs,
'qsa' : qsa,
'qsp' : qsp,
'blob2base64' : blob2base64,
'createBase64Url': createBase64Url,
'defer': defer,
'Url': Url,
'Path': Path,
'querySelectorByType': querySelectorByType,
'sprint' : sprint,
'children' : children
};

57
src/utils/path.js Normal file
View file

@ -0,0 +1,57 @@
import path from 'path-webpack';
class Path {
constructor(pathString) {
var protocol;
var parsed;
protocol = pathString.indexOf('://');
if (protocol > -1) {
pathString = new URL(pathString).pathname;
}
parsed = this.parse(pathString);
this.path = pathString;
if (this.isDirectory(pathString)) {
this.directory = pathString;
} else {
this.directory = parsed.dir + "/";
}
this.filename = parsed.base;
this.extension = parsed.ext.slice(1);
}
parse (what) {
return path.parse(what);
};
isAbsolute (what) {
return path.isAbsolute(what || this.path);
};
isDirectory (what) {
return (what.charAt(what.length-1) === '/');
};
resolve (what) {
return path.resolve(this.directory, what);
};
relative (what) {
return path.relative(this.directory, what);
};
splitPath(filename) {
return this.splitPathRe.exec(filename).slice(1);
};
toString () {
return this.path;
};
}
export default Path

83
src/utils/url.js Normal file
View file

@ -0,0 +1,83 @@
import Path from './path'
import path from 'path-webpack';
/**
* creates a uri object
* @param {string} urlString a url string (relative or absolute)
* @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);
var pathname = urlString;
this.Url = undefined;
this.href = urlString;
this.protocol = "";
this.origin = "";
this.fragment = "";
this.search = "";
this.base = baseString;
if (!absolute && (typeof(baseString) !== "string")) {
this.base = window && window.location.href;
}
// URL Polyfill doesn't throw an error if base is empty
if (absolute || this.base) {
try {
if (this.base) { // Safari doesn't like an undefined base
this.Url = new URL(urlString, this.base);
} else {
this.Url = new URL(urlString);
}
this.href = this.Url.href;
this.protocol = this.Url.protocol;
this.origin = this.Url.origin;
this.fragment = this.Url.fragment;
this.search = this.Url.search;
pathname = this.Url.pathname;
} catch (e) {
// Skip URL parsing
this.Url = undefined;
}
}
this.Path = new Path(pathname);
this.directory = this.Path.directory;
this.filename = this.Path.filename;
this.extension = this.Path.extension;
}
path () {
return this.Path;
};
resolve (what) {
var isAbsolute = (what.indexOf('://') > -1);
var fullpath;
if (isAbsolute) {
return what;
}
fullpath = path.resolve(this.directory, what);
return this.origin + fullpath;
};
relative (what) {
return path.relative(what, this.directory);
};
toString () {
return this.href;
};
}
export default Url

View file

@ -9,7 +9,7 @@ describe('Core', function() {
describe('Url', function () { describe('Url', function () {
var Url = require('../src/core').Url; var Url = require('../src/utils/url');
it("Url()", function() { it("Url()", function() {
var url = new Url("http://example.com/fred/chasen/derf.html"); var url = new Url("http://example.com/fred/chasen/derf.html");
@ -61,7 +61,7 @@ describe('Core', function() {
describe('Path', function () { describe('Path', function () {
var Path = require('../src/core').Path; var Path = require('../src/utils/path');
it("Path()", function() { it("Path()", function() {
var path = new Path("/fred/chasen/derf.html"); var path = new Path("/fred/chasen/derf.html");

View file

@ -8,13 +8,13 @@ describe('EpubCFI', function() {
var EpubCFI = require('../src/epubcfi.js'); var EpubCFI = require('../src/epubcfi.js');
it('parse a cfi on init', function() { it('parse a cfi on init', function() {
var cfi = EpubCFI("epubcfi(/6/2[cover]!/6)"); var cfi = new EpubCFI("epubcfi(/6/2[cover]!/6)");
assert.equal( cfi.spinePos, 0, "spinePos is parsed as the first item" ); assert.equal( cfi.spinePos, 0, "spinePos is parsed as the first item" );
}); });
it('parse a cfi and ignore the base if present', function() { it('parse a cfi and ignore the base if present', function() {
var cfi = EpubCFI("epubcfi(/6/2[cover]!/6)", "/6/6[end]"); var cfi = new EpubCFI("epubcfi(/6/2[cover]!/6)", "/6/6[end]");
assert.equal( cfi.spinePos, 0, "base is ignored and spinePos is parsed as the first item" ); assert.equal( cfi.spinePos, 0, "base is ignored and spinePos is parsed as the first item" );
}); });
@ -56,9 +56,9 @@ describe('EpubCFI', function() {
describe('#toString()', function() { describe('#toString()', function() {
it('parse a cfi and write it back', function() { it('parse a cfi and write it back', function() {
assert.equal(EpubCFI("epubcfi(/6/2[cover]!/6)").toString(), "epubcfi(/6/2[cover]!/6)", "output cfi string is same as input" ); assert.equal(new EpubCFI("epubcfi(/6/2[cover]!/6)").toString(), "epubcfi(/6/2[cover]!/6)", "output cfi string is same as input" );
assert.equal(EpubCFI("epubcfi(/6/4[chap01ref]!/4[body01]/10[para05]/2/1:3)").toString(), "epubcfi(/6/4[chap01ref]!/4[body01]/10[para05]/2/1:3)", "output cfi string is same as input" ); assert.equal(new EpubCFI("epubcfi(/6/4[chap01ref]!/4[body01]/10[para05]/2/1:3)").toString(), "epubcfi(/6/4[chap01ref]!/4[body01]/10[para05]/2/1:3)", "output cfi string is same as input" );
assert.equal(EpubCFI("epubcfi(/6/4[chap01ref]!/4[body01]/10[para05],/2/1:1,/3:4)").toString(), "epubcfi(/6/4[chap01ref]!/4[body01]/10[para05],/2/1:1,/3:4)", "output cfi string is same as input" ); assert.equal(new EpubCFI("epubcfi(/6/4[chap01ref]!/4[body01]/10[para05],/2/1:1,/3:4)").toString(), "epubcfi(/6/4[chap01ref]!/4[body01]/10[para05],/2/1:1,/3:4)", "output cfi string is same as input" );
}); });
}); });
@ -73,7 +73,7 @@ describe('EpubCFI', function() {
}); });
it('determine the type of a cfi', function() { it('determine the type of a cfi', function() {
var ogcfi = EpubCFI("epubcfi(/6/4[chap01ref]!/4[body01]/10[para05]/2/1:3)"); var ogcfi = new EpubCFI("epubcfi(/6/4[chap01ref]!/4[body01]/10[para05]/2/1:3)");
var cfi = new EpubCFI(); var cfi = new EpubCFI();
assert.equal( cfi.checkType(ogcfi), 'EpubCFI' ); assert.equal( cfi.checkType(ogcfi), 'EpubCFI' );

View file

@ -2,12 +2,10 @@ var assert = require('assert');
describe('Locations', function() { describe('Locations', function() {
var Locations = require('../src/locations'); var Locations = require('../src/locations');
var core = require('../src/core'); var core = require('../src/utils/core');
var chapter = require('raw-loader!./fixtures/locations.xhtml');
describe('#parse', function() { describe('#parse', function() {
var Locations = require('../src/locations'); var Locations = require('../src/locations');
var core = require('../src/core');
var chapter = require('raw-loader!./fixtures/locations.xhtml'); var chapter = require('raw-loader!./fixtures/locations.xhtml');
it('parse locations from a document', function() { it('parse locations from a document', function() {
@ -25,7 +23,6 @@ describe('Locations', function() {
var locations = new Locations(); var locations = new Locations();
var result = locations.parse(contents, "/6/4[chap01ref]", 100); var result = locations.parse(contents, "/6/4[chap01ref]", 100);
console.log(result);
assert.equal(result.length, 15); assert.equal(result.length, 15);
}); });

View file

@ -1,19 +1,23 @@
var webpack = require("webpack"); var webpack = require("webpack");
var path = require('path'); var path = require('path');
var BabiliPlugin = require("babili-webpack-plugin");
var PROD = (process.env.NODE_ENV === 'production') var PROD = (process.env.NODE_ENV === 'production')
var LEGACY = (process.env.LEGACY)
var hostname = "localhost"; var hostname = "localhost";
var port = "8080"; var port = "8080";
var enter = LEGACY ? {
"epub.legacy": ["babel-polyfill", "./libs/url/url.js", "./src/epub.js"]
} : {
"epub": "./src/epub.js",
};
module.exports = { module.exports = {
entry: { entry: enter,
epub: "./src/epub.js", devtool: PROD ? false : 'source-map',
polyfills: ["./node_modules/es6-promise/dist/es6-promise.auto.js", "./libs/url/url.js"]
},
devtool: 'source-map',
output: { output: {
path: path.resolve("./dist"), path: path.resolve("./dist"),
// path: "./dist", // path: "./dist",
filename: "[name].js", filename: PROD ? "[name].min.js" : "[name].js",
sourceMapFilename: "[name].js.map", sourceMapFilename: "[name].js.map",
library: "ePub", library: "ePub",
libraryTarget: "umd", libraryTarget: "umd",
@ -23,9 +27,9 @@ module.exports = {
"jszip": "JSZip", "jszip": "JSZip",
"xmldom": "xmldom" "xmldom": "xmldom"
}, },
plugins: [ plugins: PROD ? [
// new webpack.IgnorePlugin(/punycode|IPv6/), new BabiliPlugin()
], ] : [],
resolve: { resolve: {
alias: { alias: {
path: "path-webpack" path: "path-webpack"
@ -35,5 +39,24 @@ module.exports = {
host: hostname, host: hostname,
port: port, port: port,
inline: true inline: true
},
module: {
loaders: [
LEGACY ? {
test: /\.js$/,
exclude: /node_modules/,
loader: "babel-loader",
query: {
presets: ['es2015'],
plugins: [
"add-module-exports",
]
}
} : {
test: /\.js$/,
exclude: /node_modules/,
loader: "babel-loader"
}
]
} }
} }