mirror of
https://github.com/futurepress/epub.js.git
synced 2025-10-05 15:32:55 +02:00
6758 lines
167 KiB
JavaScript
6758 lines
167 KiB
JavaScript
/*!
|
||
* @overview RSVP - a tiny implementation of Promises/A+.
|
||
* @copyright Copyright (c) 2014 Yehuda Katz, Tom Dale, Stefan Penner and contributors
|
||
* @license Licensed under MIT license
|
||
* See https://raw.githubusercontent.com/tildeio/rsvp.js/master/LICENSE
|
||
* @version 3.0.14
|
||
*/
|
||
|
||
(function() {
|
||
"use strict";
|
||
|
||
function $$rsvp$events$$indexOf(callbacks, callback) {
|
||
for (var i=0, l=callbacks.length; i<l; i++) {
|
||
if (callbacks[i] === callback) { return i; }
|
||
}
|
||
|
||
return -1;
|
||
}
|
||
|
||
function $$rsvp$events$$callbacksFor(object) {
|
||
var callbacks = object._promiseCallbacks;
|
||
|
||
if (!callbacks) {
|
||
callbacks = object._promiseCallbacks = {};
|
||
}
|
||
|
||
return callbacks;
|
||
}
|
||
|
||
var $$rsvp$events$$default = {
|
||
|
||
/**
|
||
`RSVP.EventTarget.mixin` extends an object with EventTarget methods. For
|
||
Example:
|
||
|
||
```javascript
|
||
var object = {};
|
||
|
||
RSVP.EventTarget.mixin(object);
|
||
|
||
object.on('finished', function(event) {
|
||
// handle event
|
||
});
|
||
|
||
object.trigger('finished', { detail: value });
|
||
```
|
||
|
||
`EventTarget.mixin` also works with prototypes:
|
||
|
||
```javascript
|
||
var Person = function() {};
|
||
RSVP.EventTarget.mixin(Person.prototype);
|
||
|
||
var yehuda = new Person();
|
||
var tom = new Person();
|
||
|
||
yehuda.on('poke', function(event) {
|
||
console.log('Yehuda says OW');
|
||
});
|
||
|
||
tom.on('poke', function(event) {
|
||
console.log('Tom says OW');
|
||
});
|
||
|
||
yehuda.trigger('poke');
|
||
tom.trigger('poke');
|
||
```
|
||
|
||
@method mixin
|
||
@for RSVP.EventTarget
|
||
@private
|
||
@param {Object} object object to extend with EventTarget methods
|
||
*/
|
||
mixin: function(object) {
|
||
object.on = this.on;
|
||
object.off = this.off;
|
||
object.trigger = this.trigger;
|
||
object._promiseCallbacks = undefined;
|
||
return object;
|
||
},
|
||
|
||
/**
|
||
Registers a callback to be executed when `eventName` is triggered
|
||
|
||
```javascript
|
||
object.on('event', function(eventInfo){
|
||
// handle the event
|
||
});
|
||
|
||
object.trigger('event');
|
||
```
|
||
|
||
@method on
|
||
@for RSVP.EventTarget
|
||
@private
|
||
@param {String} eventName name of the event to listen for
|
||
@param {Function} callback function to be called when the event is triggered.
|
||
*/
|
||
on: function(eventName, callback) {
|
||
var allCallbacks = $$rsvp$events$$callbacksFor(this), callbacks;
|
||
|
||
callbacks = allCallbacks[eventName];
|
||
|
||
if (!callbacks) {
|
||
callbacks = allCallbacks[eventName] = [];
|
||
}
|
||
|
||
if ($$rsvp$events$$indexOf(callbacks, callback) === -1) {
|
||
callbacks.push(callback);
|
||
}
|
||
},
|
||
|
||
/**
|
||
You can use `off` to stop firing a particular callback for an event:
|
||
|
||
```javascript
|
||
function doStuff() { // do stuff! }
|
||
object.on('stuff', doStuff);
|
||
|
||
object.trigger('stuff'); // doStuff will be called
|
||
|
||
// Unregister ONLY the doStuff callback
|
||
object.off('stuff', doStuff);
|
||
object.trigger('stuff'); // doStuff will NOT be called
|
||
```
|
||
|
||
If you don't pass a `callback` argument to `off`, ALL callbacks for the
|
||
event will not be executed when the event fires. For example:
|
||
|
||
```javascript
|
||
var callback1 = function(){};
|
||
var callback2 = function(){};
|
||
|
||
object.on('stuff', callback1);
|
||
object.on('stuff', callback2);
|
||
|
||
object.trigger('stuff'); // callback1 and callback2 will be executed.
|
||
|
||
object.off('stuff');
|
||
object.trigger('stuff'); // callback1 and callback2 will not be executed!
|
||
```
|
||
|
||
@method off
|
||
@for RSVP.EventTarget
|
||
@private
|
||
@param {String} eventName event to stop listening to
|
||
@param {Function} callback optional argument. If given, only the function
|
||
given will be removed from the event's callback queue. If no `callback`
|
||
argument is given, all callbacks will be removed from the event's callback
|
||
queue.
|
||
*/
|
||
off: function(eventName, callback) {
|
||
var allCallbacks = $$rsvp$events$$callbacksFor(this), callbacks, index;
|
||
|
||
if (!callback) {
|
||
allCallbacks[eventName] = [];
|
||
return;
|
||
}
|
||
|
||
callbacks = allCallbacks[eventName];
|
||
|
||
index = $$rsvp$events$$indexOf(callbacks, callback);
|
||
|
||
if (index !== -1) { callbacks.splice(index, 1); }
|
||
},
|
||
|
||
/**
|
||
Use `trigger` to fire custom events. For example:
|
||
|
||
```javascript
|
||
object.on('foo', function(){
|
||
console.log('foo event happened!');
|
||
});
|
||
object.trigger('foo');
|
||
// 'foo event happened!' logged to the console
|
||
```
|
||
|
||
You can also pass a value as a second argument to `trigger` that will be
|
||
passed as an argument to all event listeners for the event:
|
||
|
||
```javascript
|
||
object.on('foo', function(value){
|
||
console.log(value.name);
|
||
});
|
||
|
||
object.trigger('foo', { name: 'bar' });
|
||
// 'bar' logged to the console
|
||
```
|
||
|
||
@method trigger
|
||
@for RSVP.EventTarget
|
||
@private
|
||
@param {String} eventName name of the event to be triggered
|
||
@param {Any} options optional value to be passed to any event handlers for
|
||
the given `eventName`
|
||
*/
|
||
trigger: function(eventName, options) {
|
||
var allCallbacks = $$rsvp$events$$callbacksFor(this), callbacks, callback;
|
||
|
||
if (callbacks = allCallbacks[eventName]) {
|
||
// Don't cache the callbacks.length since it may grow
|
||
for (var i=0; i<callbacks.length; i++) {
|
||
callback = callbacks[i];
|
||
|
||
callback(options);
|
||
}
|
||
}
|
||
}
|
||
};
|
||
|
||
var $$rsvp$config$$config = {
|
||
instrument: false
|
||
};
|
||
|
||
$$rsvp$events$$default.mixin($$rsvp$config$$config);
|
||
|
||
function $$rsvp$config$$configure(name, value) {
|
||
if (name === 'onerror') {
|
||
// handle for legacy users that expect the actual
|
||
// error to be passed to their function added via
|
||
// `RSVP.configure('onerror', someFunctionHere);`
|
||
$$rsvp$config$$config.on('error', value);
|
||
return;
|
||
}
|
||
|
||
if (arguments.length === 2) {
|
||
$$rsvp$config$$config[name] = value;
|
||
} else {
|
||
return $$rsvp$config$$config[name];
|
||
}
|
||
}
|
||
|
||
function $$utils$$objectOrFunction(x) {
|
||
return typeof x === 'function' || (typeof x === 'object' && x !== null);
|
||
}
|
||
|
||
function $$utils$$isFunction(x) {
|
||
return typeof x === 'function';
|
||
}
|
||
|
||
function $$utils$$isMaybeThenable(x) {
|
||
return typeof x === 'object' && x !== null;
|
||
}
|
||
|
||
var $$utils$$_isArray;
|
||
|
||
if (!Array.isArray) {
|
||
$$utils$$_isArray = function (x) {
|
||
return Object.prototype.toString.call(x) === '[object Array]';
|
||
};
|
||
} else {
|
||
$$utils$$_isArray = Array.isArray;
|
||
}
|
||
|
||
var $$utils$$isArray = $$utils$$_isArray;
|
||
var $$utils$$now = Date.now || function() { return new Date().getTime(); };
|
||
function $$utils$$F() { }
|
||
|
||
var $$utils$$o_create = (Object.create || function (o) {
|
||
if (arguments.length > 1) {
|
||
throw new Error('Second argument not supported');
|
||
}
|
||
if (typeof o !== 'object') {
|
||
throw new TypeError('Argument must be an object');
|
||
}
|
||
$$utils$$F.prototype = o;
|
||
return new $$utils$$F();
|
||
});
|
||
|
||
var $$instrument$$queue = [];
|
||
|
||
var $$instrument$$default = function instrument(eventName, promise, child) {
|
||
if (1 === $$instrument$$queue.push({
|
||
name: eventName,
|
||
payload: {
|
||
guid: promise._guidKey + promise._id,
|
||
eventName: eventName,
|
||
detail: promise._result,
|
||
childGuid: child && promise._guidKey + child._id,
|
||
label: promise._label,
|
||
timeStamp: $$utils$$now(),
|
||
stack: new Error(promise._label).stack
|
||
}})) {
|
||
|
||
setTimeout(function() {
|
||
var entry;
|
||
for (var i = 0; i < $$instrument$$queue.length; i++) {
|
||
entry = $$instrument$$queue[i];
|
||
$$rsvp$config$$config.trigger(entry.name, entry.payload);
|
||
}
|
||
$$instrument$$queue.length = 0;
|
||
}, 50);
|
||
}
|
||
};
|
||
|
||
function $$$internal$$noop() {}
|
||
var $$$internal$$PENDING = void 0;
|
||
var $$$internal$$FULFILLED = 1;
|
||
var $$$internal$$REJECTED = 2;
|
||
var $$$internal$$GET_THEN_ERROR = new $$$internal$$ErrorObject();
|
||
|
||
function $$$internal$$getThen(promise) {
|
||
try {
|
||
return promise.then;
|
||
} catch(error) {
|
||
$$$internal$$GET_THEN_ERROR.error = error;
|
||
return $$$internal$$GET_THEN_ERROR;
|
||
}
|
||
}
|
||
|
||
function $$$internal$$tryThen(then, value, fulfillmentHandler, rejectionHandler) {
|
||
try {
|
||
then.call(value, fulfillmentHandler, rejectionHandler);
|
||
} catch(e) {
|
||
return e;
|
||
}
|
||
}
|
||
|
||
function $$$internal$$handleForeignThenable(promise, thenable, then) {
|
||
$$rsvp$config$$config.async(function(promise) {
|
||
var sealed = false;
|
||
var error = $$$internal$$tryThen(then, thenable, function(value) {
|
||
if (sealed) { return; }
|
||
sealed = true;
|
||
if (thenable !== value) {
|
||
$$$internal$$resolve(promise, value);
|
||
} else {
|
||
$$$internal$$fulfill(promise, value);
|
||
}
|
||
}, function(reason) {
|
||
if (sealed) { return; }
|
||
sealed = true;
|
||
|
||
$$$internal$$reject(promise, reason);
|
||
}, 'Settle: ' + (promise._label || ' unknown promise'));
|
||
|
||
if (!sealed && error) {
|
||
sealed = true;
|
||
$$$internal$$reject(promise, error);
|
||
}
|
||
}, promise);
|
||
}
|
||
|
||
function $$$internal$$handleOwnThenable(promise, thenable) {
|
||
if (thenable._state === $$$internal$$FULFILLED) {
|
||
$$$internal$$fulfill(promise, thenable._result);
|
||
} else if (promise._state === $$$internal$$REJECTED) {
|
||
$$$internal$$reject(promise, thenable._result);
|
||
} else {
|
||
$$$internal$$subscribe(thenable, undefined, function(value) {
|
||
if (thenable !== value) {
|
||
$$$internal$$resolve(promise, value);
|
||
} else {
|
||
$$$internal$$fulfill(promise, value);
|
||
}
|
||
}, function(reason) {
|
||
$$$internal$$reject(promise, reason);
|
||
});
|
||
}
|
||
}
|
||
|
||
function $$$internal$$handleMaybeThenable(promise, maybeThenable) {
|
||
if (maybeThenable.constructor === promise.constructor) {
|
||
$$$internal$$handleOwnThenable(promise, maybeThenable);
|
||
} else {
|
||
var then = $$$internal$$getThen(maybeThenable);
|
||
|
||
if (then === $$$internal$$GET_THEN_ERROR) {
|
||
$$$internal$$reject(promise, $$$internal$$GET_THEN_ERROR.error);
|
||
} else if (then === undefined) {
|
||
$$$internal$$fulfill(promise, maybeThenable);
|
||
} else if ($$utils$$isFunction(then)) {
|
||
$$$internal$$handleForeignThenable(promise, maybeThenable, then);
|
||
} else {
|
||
$$$internal$$fulfill(promise, maybeThenable);
|
||
}
|
||
}
|
||
}
|
||
|
||
function $$$internal$$resolve(promise, value) {
|
||
if (promise === value) {
|
||
$$$internal$$fulfill(promise, value);
|
||
} else if ($$utils$$objectOrFunction(value)) {
|
||
$$$internal$$handleMaybeThenable(promise, value);
|
||
} else {
|
||
$$$internal$$fulfill(promise, value);
|
||
}
|
||
}
|
||
|
||
function $$$internal$$publishRejection(promise) {
|
||
if (promise._onerror) {
|
||
promise._onerror(promise._result);
|
||
}
|
||
|
||
$$$internal$$publish(promise);
|
||
}
|
||
|
||
function $$$internal$$fulfill(promise, value) {
|
||
if (promise._state !== $$$internal$$PENDING) { return; }
|
||
|
||
promise._result = value;
|
||
promise._state = $$$internal$$FULFILLED;
|
||
|
||
if (promise._subscribers.length === 0) {
|
||
if ($$rsvp$config$$config.instrument) {
|
||
$$instrument$$default('fulfilled', promise);
|
||
}
|
||
} else {
|
||
$$rsvp$config$$config.async($$$internal$$publish, promise);
|
||
}
|
||
}
|
||
|
||
function $$$internal$$reject(promise, reason) {
|
||
if (promise._state !== $$$internal$$PENDING) { return; }
|
||
promise._state = $$$internal$$REJECTED;
|
||
promise._result = reason;
|
||
|
||
$$rsvp$config$$config.async($$$internal$$publishRejection, promise);
|
||
}
|
||
|
||
function $$$internal$$subscribe(parent, child, onFulfillment, onRejection) {
|
||
var subscribers = parent._subscribers;
|
||
var length = subscribers.length;
|
||
|
||
parent._onerror = null;
|
||
|
||
subscribers[length] = child;
|
||
subscribers[length + $$$internal$$FULFILLED] = onFulfillment;
|
||
subscribers[length + $$$internal$$REJECTED] = onRejection;
|
||
|
||
if (length === 0 && parent._state) {
|
||
$$rsvp$config$$config.async($$$internal$$publish, parent);
|
||
}
|
||
}
|
||
|
||
function $$$internal$$publish(promise) {
|
||
var subscribers = promise._subscribers;
|
||
var settled = promise._state;
|
||
|
||
if ($$rsvp$config$$config.instrument) {
|
||
$$instrument$$default(settled === $$$internal$$FULFILLED ? 'fulfilled' : 'rejected', promise);
|
||
}
|
||
|
||
if (subscribers.length === 0) { return; }
|
||
|
||
var child, callback, detail = promise._result;
|
||
|
||
for (var i = 0; i < subscribers.length; i += 3) {
|
||
child = subscribers[i];
|
||
callback = subscribers[i + settled];
|
||
|
||
if (child) {
|
||
$$$internal$$invokeCallback(settled, child, callback, detail);
|
||
} else {
|
||
callback(detail);
|
||
}
|
||
}
|
||
|
||
promise._subscribers.length = 0;
|
||
}
|
||
|
||
function $$$internal$$ErrorObject() {
|
||
this.error = null;
|
||
}
|
||
|
||
var $$$internal$$TRY_CATCH_ERROR = new $$$internal$$ErrorObject();
|
||
|
||
function $$$internal$$tryCatch(callback, detail) {
|
||
try {
|
||
return callback(detail);
|
||
} catch(e) {
|
||
$$$internal$$TRY_CATCH_ERROR.error = e;
|
||
return $$$internal$$TRY_CATCH_ERROR;
|
||
}
|
||
}
|
||
|
||
function $$$internal$$invokeCallback(settled, promise, callback, detail) {
|
||
var hasCallback = $$utils$$isFunction(callback),
|
||
value, error, succeeded, failed;
|
||
|
||
if (hasCallback) {
|
||
value = $$$internal$$tryCatch(callback, detail);
|
||
|
||
if (value === $$$internal$$TRY_CATCH_ERROR) {
|
||
failed = true;
|
||
error = value.error;
|
||
value = null;
|
||
} else {
|
||
succeeded = true;
|
||
}
|
||
|
||
if (promise === value) {
|
||
$$$internal$$reject(promise, new TypeError('A promises callback cannot return that same promise.'));
|
||
return;
|
||
}
|
||
|
||
} else {
|
||
value = detail;
|
||
succeeded = true;
|
||
}
|
||
|
||
if (promise._state !== $$$internal$$PENDING) {
|
||
// noop
|
||
} else if (hasCallback && succeeded) {
|
||
$$$internal$$resolve(promise, value);
|
||
} else if (failed) {
|
||
$$$internal$$reject(promise, error);
|
||
} else if (settled === $$$internal$$FULFILLED) {
|
||
$$$internal$$fulfill(promise, value);
|
||
} else if (settled === $$$internal$$REJECTED) {
|
||
$$$internal$$reject(promise, value);
|
||
}
|
||
}
|
||
|
||
function $$$internal$$initializePromise(promise, resolver) {
|
||
try {
|
||
resolver(function resolvePromise(value){
|
||
$$$internal$$resolve(promise, value);
|
||
}, function rejectPromise(reason) {
|
||
$$$internal$$reject(promise, reason);
|
||
});
|
||
} catch(e) {
|
||
$$$internal$$reject(promise, e);
|
||
}
|
||
}
|
||
|
||
function $$enumerator$$makeSettledResult(state, position, value) {
|
||
if (state === $$$internal$$FULFILLED) {
|
||
return {
|
||
state: 'fulfilled',
|
||
value: value
|
||
};
|
||
} else {
|
||
return {
|
||
state: 'rejected',
|
||
reason: value
|
||
};
|
||
}
|
||
}
|
||
|
||
function $$enumerator$$Enumerator(Constructor, input, abortOnReject, label) {
|
||
this._instanceConstructor = Constructor;
|
||
this.promise = new Constructor($$$internal$$noop, label);
|
||
this._abortOnReject = abortOnReject;
|
||
|
||
if (this._validateInput(input)) {
|
||
this._input = input;
|
||
this.length = input.length;
|
||
this._remaining = input.length;
|
||
|
||
this._init();
|
||
|
||
if (this.length === 0) {
|
||
$$$internal$$fulfill(this.promise, this._result);
|
||
} else {
|
||
this.length = this.length || 0;
|
||
this._enumerate();
|
||
if (this._remaining === 0) {
|
||
$$$internal$$fulfill(this.promise, this._result);
|
||
}
|
||
}
|
||
} else {
|
||
$$$internal$$reject(this.promise, this._validationError());
|
||
}
|
||
}
|
||
|
||
$$enumerator$$Enumerator.prototype._validateInput = function(input) {
|
||
return $$utils$$isArray(input);
|
||
};
|
||
|
||
$$enumerator$$Enumerator.prototype._validationError = function() {
|
||
return new Error('Array Methods must be provided an Array');
|
||
};
|
||
|
||
$$enumerator$$Enumerator.prototype._init = function() {
|
||
this._result = new Array(this.length);
|
||
};
|
||
|
||
var $$enumerator$$default = $$enumerator$$Enumerator;
|
||
|
||
$$enumerator$$Enumerator.prototype._enumerate = function() {
|
||
var length = this.length;
|
||
var promise = this.promise;
|
||
var input = this._input;
|
||
|
||
for (var i = 0; promise._state === $$$internal$$PENDING && i < length; i++) {
|
||
this._eachEntry(input[i], i);
|
||
}
|
||
};
|
||
|
||
$$enumerator$$Enumerator.prototype._eachEntry = function(entry, i) {
|
||
var c = this._instanceConstructor;
|
||
if ($$utils$$isMaybeThenable(entry)) {
|
||
if (entry.constructor === c && entry._state !== $$$internal$$PENDING) {
|
||
entry._onerror = null;
|
||
this._settledAt(entry._state, i, entry._result);
|
||
} else {
|
||
this._willSettleAt(c.resolve(entry), i);
|
||
}
|
||
} else {
|
||
this._remaining--;
|
||
this._result[i] = this._makeResult($$$internal$$FULFILLED, i, entry);
|
||
}
|
||
};
|
||
|
||
$$enumerator$$Enumerator.prototype._settledAt = function(state, i, value) {
|
||
var promise = this.promise;
|
||
|
||
if (promise._state === $$$internal$$PENDING) {
|
||
this._remaining--;
|
||
|
||
if (this._abortOnReject && state === $$$internal$$REJECTED) {
|
||
$$$internal$$reject(promise, value);
|
||
} else {
|
||
this._result[i] = this._makeResult(state, i, value);
|
||
}
|
||
}
|
||
|
||
if (this._remaining === 0) {
|
||
$$$internal$$fulfill(promise, this._result);
|
||
}
|
||
};
|
||
|
||
$$enumerator$$Enumerator.prototype._makeResult = function(state, i, value) {
|
||
return value;
|
||
};
|
||
|
||
$$enumerator$$Enumerator.prototype._willSettleAt = function(promise, i) {
|
||
var enumerator = this;
|
||
|
||
$$$internal$$subscribe(promise, undefined, function(value) {
|
||
enumerator._settledAt($$$internal$$FULFILLED, i, value);
|
||
}, function(reason) {
|
||
enumerator._settledAt($$$internal$$REJECTED, i, reason);
|
||
});
|
||
};
|
||
|
||
var $$promise$all$$default = function all(entries, label) {
|
||
return new $$enumerator$$default(this, entries, true /* abort on reject */, label).promise;
|
||
};
|
||
|
||
var $$promise$race$$default = function race(entries, label) {
|
||
/*jshint validthis:true */
|
||
var Constructor = this;
|
||
|
||
var promise = new Constructor($$$internal$$noop, label);
|
||
|
||
if (!$$utils$$isArray(entries)) {
|
||
$$$internal$$reject(promise, new TypeError('You must pass an array to race.'));
|
||
return promise;
|
||
}
|
||
|
||
var length = entries.length;
|
||
|
||
function onFulfillment(value) {
|
||
$$$internal$$resolve(promise, value);
|
||
}
|
||
|
||
function onRejection(reason) {
|
||
$$$internal$$reject(promise, reason);
|
||
}
|
||
|
||
for (var i = 0; promise._state === $$$internal$$PENDING && i < length; i++) {
|
||
$$$internal$$subscribe(Constructor.resolve(entries[i]), undefined, onFulfillment, onRejection);
|
||
}
|
||
|
||
return promise;
|
||
};
|
||
|
||
var $$promise$resolve$$default = function resolve(object, label) {
|
||
/*jshint validthis:true */
|
||
var Constructor = this;
|
||
|
||
if (object && typeof object === 'object' && object.constructor === Constructor) {
|
||
return object;
|
||
}
|
||
|
||
var promise = new Constructor($$$internal$$noop, label);
|
||
$$$internal$$resolve(promise, object);
|
||
return promise;
|
||
};
|
||
|
||
var $$promise$reject$$default = function reject(reason, label) {
|
||
/*jshint validthis:true */
|
||
var Constructor = this;
|
||
var promise = new Constructor($$$internal$$noop, label);
|
||
$$$internal$$reject(promise, reason);
|
||
return promise;
|
||
};
|
||
|
||
var $$rsvp$promise$$guidKey = 'rsvp_' + $$utils$$now() + '-';
|
||
var $$rsvp$promise$$counter = 0;
|
||
|
||
function $$rsvp$promise$$needsResolver() {
|
||
throw new TypeError('You must pass a resolver function as the first argument to the promise constructor');
|
||
}
|
||
|
||
function $$rsvp$promise$$needsNew() {
|
||
throw new TypeError("Failed to construct 'Promise': Please use the 'new' operator, this object constructor cannot be called as a function.");
|
||
}
|
||
|
||
var $$rsvp$promise$$default = $$rsvp$promise$$Promise;
|
||
|
||
/**
|
||
Promise objects represent the eventual result of an asynchronous operation. The
|
||
primary way of interacting with a promise is through its `then` method, which
|
||
registers callbacks to receive either a promise’s eventual value or the reason
|
||
why the promise cannot be fulfilled.
|
||
|
||
Terminology
|
||
-----------
|
||
|
||
- `promise` is an object or function with a `then` method whose behavior conforms to this specification.
|
||
- `thenable` is an object or function that defines a `then` method.
|
||
- `value` is any legal JavaScript value (including undefined, a thenable, or a promise).
|
||
- `exception` is a value that is thrown using the throw statement.
|
||
- `reason` is a value that indicates why a promise was rejected.
|
||
- `settled` the final resting state of a promise, fulfilled or rejected.
|
||
|
||
A promise can be in one of three states: pending, fulfilled, or rejected.
|
||
|
||
Promises that are fulfilled have a fulfillment value and are in the fulfilled
|
||
state. Promises that are rejected have a rejection reason and are in the
|
||
rejected state. A fulfillment value is never a thenable.
|
||
|
||
Promises can also be said to *resolve* a value. If this value is also a
|
||
promise, then the original promise's settled state will match the value's
|
||
settled state. So a promise that *resolves* a promise that rejects will
|
||
itself reject, and a promise that *resolves* a promise that fulfills will
|
||
itself fulfill.
|
||
|
||
|
||
Basic Usage:
|
||
------------
|
||
|
||
```js
|
||
var promise = new Promise(function(resolve, reject) {
|
||
// on success
|
||
resolve(value);
|
||
|
||
// on failure
|
||
reject(reason);
|
||
});
|
||
|
||
promise.then(function(value) {
|
||
// on fulfillment
|
||
}, function(reason) {
|
||
// on rejection
|
||
});
|
||
```
|
||
|
||
Advanced Usage:
|
||
---------------
|
||
|
||
Promises shine when abstracting away asynchronous interactions such as
|
||
`XMLHttpRequest`s.
|
||
|
||
```js
|
||
function getJSON(url) {
|
||
return new Promise(function(resolve, reject){
|
||
var xhr = new XMLHttpRequest();
|
||
|
||
xhr.open('GET', url);
|
||
xhr.onreadystatechange = handler;
|
||
xhr.responseType = 'json';
|
||
xhr.setRequestHeader('Accept', 'application/json');
|
||
xhr.send();
|
||
|
||
function handler() {
|
||
if (this.readyState === this.DONE) {
|
||
if (this.status === 200) {
|
||
resolve(this.response);
|
||
} else {
|
||
reject(new Error('getJSON: `' + url + '` failed with status: [' + this.status + ']'));
|
||
}
|
||
}
|
||
};
|
||
});
|
||
}
|
||
|
||
getJSON('/posts.json').then(function(json) {
|
||
// on fulfillment
|
||
}, function(reason) {
|
||
// on rejection
|
||
});
|
||
```
|
||
|
||
Unlike callbacks, promises are great composable primitives.
|
||
|
||
```js
|
||
Promise.all([
|
||
getJSON('/posts'),
|
||
getJSON('/comments')
|
||
]).then(function(values){
|
||
values[0] // => postsJSON
|
||
values[1] // => commentsJSON
|
||
|
||
return values;
|
||
});
|
||
```
|
||
|
||
@class RSVP.Promise
|
||
@param {function} resolver
|
||
@param {String} label optional string for labeling the promise.
|
||
Useful for tooling.
|
||
@constructor
|
||
*/
|
||
function $$rsvp$promise$$Promise(resolver, label) {
|
||
this._id = $$rsvp$promise$$counter++;
|
||
this._label = label;
|
||
this._state = undefined;
|
||
this._result = undefined;
|
||
this._subscribers = [];
|
||
|
||
if ($$rsvp$config$$config.instrument) {
|
||
$$instrument$$default('created', this);
|
||
}
|
||
|
||
if ($$$internal$$noop !== resolver) {
|
||
if (!$$utils$$isFunction(resolver)) {
|
||
$$rsvp$promise$$needsResolver();
|
||
}
|
||
|
||
if (!(this instanceof $$rsvp$promise$$Promise)) {
|
||
$$rsvp$promise$$needsNew();
|
||
}
|
||
|
||
$$$internal$$initializePromise(this, resolver);
|
||
}
|
||
}
|
||
|
||
// deprecated
|
||
$$rsvp$promise$$Promise.cast = $$promise$resolve$$default;
|
||
|
||
$$rsvp$promise$$Promise.all = $$promise$all$$default;
|
||
$$rsvp$promise$$Promise.race = $$promise$race$$default;
|
||
$$rsvp$promise$$Promise.resolve = $$promise$resolve$$default;
|
||
$$rsvp$promise$$Promise.reject = $$promise$reject$$default;
|
||
|
||
$$rsvp$promise$$Promise.prototype = {
|
||
constructor: $$rsvp$promise$$Promise,
|
||
|
||
_guidKey: $$rsvp$promise$$guidKey,
|
||
|
||
_onerror: function (reason) {
|
||
$$rsvp$config$$config.trigger('error', reason);
|
||
},
|
||
|
||
/**
|
||
The primary way of interacting with a promise is through its `then` method,
|
||
which registers callbacks to receive either a promise's eventual value or the
|
||
reason why the promise cannot be fulfilled.
|
||
|
||
```js
|
||
findUser().then(function(user){
|
||
// user is available
|
||
}, function(reason){
|
||
// user is unavailable, and you are given the reason why
|
||
});
|
||
```
|
||
|
||
Chaining
|
||
--------
|
||
|
||
The return value of `then` is itself a promise. This second, 'downstream'
|
||
promise is resolved with the return value of the first promise's fulfillment
|
||
or rejection handler, or rejected if the handler throws an exception.
|
||
|
||
```js
|
||
findUser().then(function (user) {
|
||
return user.name;
|
||
}, function (reason) {
|
||
return 'default name';
|
||
}).then(function (userName) {
|
||
// If `findUser` fulfilled, `userName` will be the user's name, otherwise it
|
||
// will be `'default name'`
|
||
});
|
||
|
||
findUser().then(function (user) {
|
||
throw new Error('Found user, but still unhappy');
|
||
}, function (reason) {
|
||
throw new Error('`findUser` rejected and we're unhappy');
|
||
}).then(function (value) {
|
||
// never reached
|
||
}, function (reason) {
|
||
// if `findUser` fulfilled, `reason` will be 'Found user, but still unhappy'.
|
||
// If `findUser` rejected, `reason` will be '`findUser` rejected and we're unhappy'.
|
||
});
|
||
```
|
||
If the downstream promise does not specify a rejection handler, rejection reasons will be propagated further downstream.
|
||
|
||
```js
|
||
findUser().then(function (user) {
|
||
throw new PedagogicalException('Upstream error');
|
||
}).then(function (value) {
|
||
// never reached
|
||
}).then(function (value) {
|
||
// never reached
|
||
}, function (reason) {
|
||
// The `PedgagocialException` is propagated all the way down to here
|
||
});
|
||
```
|
||
|
||
Assimilation
|
||
------------
|
||
|
||
Sometimes the value you want to propagate to a downstream promise can only be
|
||
retrieved asynchronously. This can be achieved by returning a promise in the
|
||
fulfillment or rejection handler. The downstream promise will then be pending
|
||
until the returned promise is settled. This is called *assimilation*.
|
||
|
||
```js
|
||
findUser().then(function (user) {
|
||
return findCommentsByAuthor(user);
|
||
}).then(function (comments) {
|
||
// The user's comments are now available
|
||
});
|
||
```
|
||
|
||
If the assimliated promise rejects, then the downstream promise will also reject.
|
||
|
||
```js
|
||
findUser().then(function (user) {
|
||
return findCommentsByAuthor(user);
|
||
}).then(function (comments) {
|
||
// If `findCommentsByAuthor` fulfills, we'll have the value here
|
||
}, function (reason) {
|
||
// If `findCommentsByAuthor` rejects, we'll have the reason here
|
||
});
|
||
```
|
||
|
||
Simple Example
|
||
--------------
|
||
|
||
Synchronous Example
|
||
|
||
```javascript
|
||
var result;
|
||
|
||
try {
|
||
result = findResult();
|
||
// success
|
||
} catch(reason) {
|
||
// failure
|
||
}
|
||
```
|
||
|
||
Errback Example
|
||
|
||
```js
|
||
findResult(function(result, err){
|
||
if (err) {
|
||
// failure
|
||
} else {
|
||
// success
|
||
}
|
||
});
|
||
```
|
||
|
||
Promise Example;
|
||
|
||
```javascript
|
||
findResult().then(function(result){
|
||
// success
|
||
}, function(reason){
|
||
// failure
|
||
});
|
||
```
|
||
|
||
Advanced Example
|
||
--------------
|
||
|
||
Synchronous Example
|
||
|
||
```javascript
|
||
var author, books;
|
||
|
||
try {
|
||
author = findAuthor();
|
||
books = findBooksByAuthor(author);
|
||
// success
|
||
} catch(reason) {
|
||
// failure
|
||
}
|
||
```
|
||
|
||
Errback Example
|
||
|
||
```js
|
||
|
||
function foundBooks(books) {
|
||
|
||
}
|
||
|
||
function failure(reason) {
|
||
|
||
}
|
||
|
||
findAuthor(function(author, err){
|
||
if (err) {
|
||
failure(err);
|
||
// failure
|
||
} else {
|
||
try {
|
||
findBoooksByAuthor(author, function(books, err) {
|
||
if (err) {
|
||
failure(err);
|
||
} else {
|
||
try {
|
||
foundBooks(books);
|
||
} catch(reason) {
|
||
failure(reason);
|
||
}
|
||
}
|
||
});
|
||
} catch(error) {
|
||
failure(err);
|
||
}
|
||
// success
|
||
}
|
||
});
|
||
```
|
||
|
||
Promise Example;
|
||
|
||
```javascript
|
||
findAuthor().
|
||
then(findBooksByAuthor).
|
||
then(function(books){
|
||
// found books
|
||
}).catch(function(reason){
|
||
// something went wrong
|
||
});
|
||
```
|
||
|
||
@method then
|
||
@param {Function} onFulfilled
|
||
@param {Function} onRejected
|
||
@param {String} label optional string for labeling the promise.
|
||
Useful for tooling.
|
||
@return {Promise}
|
||
*/
|
||
then: function(onFulfillment, onRejection, label) {
|
||
var parent = this;
|
||
var state = parent._state;
|
||
|
||
if (state === $$$internal$$FULFILLED && !onFulfillment || state === $$$internal$$REJECTED && !onRejection) {
|
||
if ($$rsvp$config$$config.instrument) {
|
||
$$instrument$$default('chained', this, this);
|
||
}
|
||
return this;
|
||
}
|
||
|
||
parent._onerror = null;
|
||
|
||
var child = new this.constructor($$$internal$$noop, label);
|
||
var result = parent._result;
|
||
|
||
if ($$rsvp$config$$config.instrument) {
|
||
$$instrument$$default('chained', parent, child);
|
||
}
|
||
|
||
if (state) {
|
||
var callback = arguments[state - 1];
|
||
$$rsvp$config$$config.async(function(){
|
||
$$$internal$$invokeCallback(state, child, callback, result);
|
||
});
|
||
} else {
|
||
$$$internal$$subscribe(parent, child, onFulfillment, onRejection);
|
||
}
|
||
|
||
return child;
|
||
},
|
||
|
||
/**
|
||
`catch` is simply sugar for `then(undefined, onRejection)` which makes it the same
|
||
as the catch block of a try/catch statement.
|
||
|
||
```js
|
||
function findAuthor(){
|
||
throw new Error('couldn't find that author');
|
||
}
|
||
|
||
// synchronous
|
||
try {
|
||
findAuthor();
|
||
} catch(reason) {
|
||
// something went wrong
|
||
}
|
||
|
||
// async with promises
|
||
findAuthor().catch(function(reason){
|
||
// something went wrong
|
||
});
|
||
```
|
||
|
||
@method catch
|
||
@param {Function} onRejection
|
||
@param {String} label optional string for labeling the promise.
|
||
Useful for tooling.
|
||
@return {Promise}
|
||
*/
|
||
'catch': function(onRejection, label) {
|
||
return this.then(null, onRejection, label);
|
||
},
|
||
|
||
/**
|
||
`finally` will be invoked regardless of the promise's fate just as native
|
||
try/catch/finally behaves
|
||
|
||
Synchronous example:
|
||
|
||
```js
|
||
findAuthor() {
|
||
if (Math.random() > 0.5) {
|
||
throw new Error();
|
||
}
|
||
return new Author();
|
||
}
|
||
|
||
try {
|
||
return findAuthor(); // succeed or fail
|
||
} catch(error) {
|
||
return findOtherAuther();
|
||
} finally {
|
||
// always runs
|
||
// doesn't affect the return value
|
||
}
|
||
```
|
||
|
||
Asynchronous example:
|
||
|
||
```js
|
||
findAuthor().catch(function(reason){
|
||
return findOtherAuther();
|
||
}).finally(function(){
|
||
// author was either found, or not
|
||
});
|
||
```
|
||
|
||
@method finally
|
||
@param {Function} callback
|
||
@param {String} label optional string for labeling the promise.
|
||
Useful for tooling.
|
||
@return {Promise}
|
||
*/
|
||
'finally': function(callback, label) {
|
||
var constructor = this.constructor;
|
||
|
||
return this.then(function(value) {
|
||
return constructor.resolve(callback()).then(function(){
|
||
return value;
|
||
});
|
||
}, function(reason) {
|
||
return constructor.resolve(callback()).then(function(){
|
||
throw reason;
|
||
});
|
||
}, label);
|
||
}
|
||
};
|
||
|
||
function $$rsvp$node$$Result() {
|
||
this.value = undefined;
|
||
}
|
||
|
||
var $$rsvp$node$$ERROR = new $$rsvp$node$$Result();
|
||
var $$rsvp$node$$GET_THEN_ERROR = new $$rsvp$node$$Result();
|
||
|
||
function $$rsvp$node$$getThen(obj) {
|
||
try {
|
||
return obj.then;
|
||
} catch(error) {
|
||
$$rsvp$node$$ERROR.value= error;
|
||
return $$rsvp$node$$ERROR;
|
||
}
|
||
}
|
||
|
||
function $$rsvp$node$$tryApply(f, s, a) {
|
||
try {
|
||
f.apply(s, a);
|
||
} catch(error) {
|
||
$$rsvp$node$$ERROR.value = error;
|
||
return $$rsvp$node$$ERROR;
|
||
}
|
||
}
|
||
|
||
function $$rsvp$node$$makeObject(_, argumentNames) {
|
||
var obj = {};
|
||
var name;
|
||
var i;
|
||
var length = _.length;
|
||
var args = new Array(length);
|
||
|
||
for (var x = 0; x < length; x++) {
|
||
args[x] = _[x];
|
||
}
|
||
|
||
for (i = 0; i < argumentNames.length; i++) {
|
||
name = argumentNames[i];
|
||
obj[name] = args[i + 1];
|
||
}
|
||
|
||
return obj;
|
||
}
|
||
|
||
function $$rsvp$node$$arrayResult(_) {
|
||
var length = _.length;
|
||
var args = new Array(length - 1);
|
||
|
||
for (var i = 1; i < length; i++) {
|
||
args[i - 1] = _[i];
|
||
}
|
||
|
||
return args;
|
||
}
|
||
|
||
function $$rsvp$node$$wrapThenable(then, promise) {
|
||
return {
|
||
then: function(onFulFillment, onRejection) {
|
||
return then.call(promise, onFulFillment, onRejection);
|
||
}
|
||
};
|
||
}
|
||
|
||
var $$rsvp$node$$default = function denodeify(nodeFunc, options) {
|
||
var fn = function() {
|
||
var self = this;
|
||
var l = arguments.length;
|
||
var args = new Array(l + 1);
|
||
var arg;
|
||
var promiseInput = false;
|
||
|
||
for (var i = 0; i < l; ++i) {
|
||
arg = arguments[i];
|
||
|
||
if (!promiseInput) {
|
||
// TODO: clean this up
|
||
promiseInput = $$rsvp$node$$needsPromiseInput(arg);
|
||
if (promiseInput === $$rsvp$node$$GET_THEN_ERROR) {
|
||
var p = new $$rsvp$promise$$default($$$internal$$noop);
|
||
$$$internal$$reject(p, $$rsvp$node$$GET_THEN_ERROR.value);
|
||
return p;
|
||
} else if (promiseInput && promiseInput !== true) {
|
||
arg = $$rsvp$node$$wrapThenable(promiseInput, arg);
|
||
}
|
||
}
|
||
args[i] = arg;
|
||
}
|
||
|
||
var promise = new $$rsvp$promise$$default($$$internal$$noop);
|
||
|
||
args[l] = function(err, val) {
|
||
if (err)
|
||
$$$internal$$reject(promise, err);
|
||
else if (options === undefined)
|
||
$$$internal$$resolve(promise, val);
|
||
else if (options === true)
|
||
$$$internal$$resolve(promise, $$rsvp$node$$arrayResult(arguments));
|
||
else if ($$utils$$isArray(options))
|
||
$$$internal$$resolve(promise, $$rsvp$node$$makeObject(arguments, options));
|
||
else
|
||
$$$internal$$resolve(promise, val);
|
||
};
|
||
|
||
if (promiseInput) {
|
||
return $$rsvp$node$$handlePromiseInput(promise, args, nodeFunc, self);
|
||
} else {
|
||
return $$rsvp$node$$handleValueInput(promise, args, nodeFunc, self);
|
||
}
|
||
};
|
||
|
||
fn.__proto__ = nodeFunc;
|
||
|
||
return fn;
|
||
};
|
||
|
||
function $$rsvp$node$$handleValueInput(promise, args, nodeFunc, self) {
|
||
var result = $$rsvp$node$$tryApply(nodeFunc, self, args);
|
||
if (result === $$rsvp$node$$ERROR) {
|
||
$$$internal$$reject(promise, result.value);
|
||
}
|
||
return promise;
|
||
}
|
||
|
||
function $$rsvp$node$$handlePromiseInput(promise, args, nodeFunc, self){
|
||
return $$rsvp$promise$$default.all(args).then(function(args){
|
||
var result = $$rsvp$node$$tryApply(nodeFunc, self, args);
|
||
if (result === $$rsvp$node$$ERROR) {
|
||
$$$internal$$reject(promise, result.value);
|
||
}
|
||
return promise;
|
||
});
|
||
}
|
||
|
||
function $$rsvp$node$$needsPromiseInput(arg) {
|
||
if (arg && typeof arg === 'object') {
|
||
if (arg.constructor === $$rsvp$promise$$default) {
|
||
return true;
|
||
} else {
|
||
return $$rsvp$node$$getThen(arg);
|
||
}
|
||
} else {
|
||
return false;
|
||
}
|
||
}
|
||
|
||
var $$rsvp$all$$default = function all(array, label) {
|
||
return $$rsvp$promise$$default.all(array, label);
|
||
};
|
||
|
||
function $$rsvp$all$settled$$AllSettled(Constructor, entries, label) {
|
||
this._superConstructor(Constructor, entries, false /* don't abort on reject */, label);
|
||
}
|
||
|
||
$$rsvp$all$settled$$AllSettled.prototype = $$utils$$o_create($$enumerator$$default.prototype);
|
||
$$rsvp$all$settled$$AllSettled.prototype._superConstructor = $$enumerator$$default;
|
||
$$rsvp$all$settled$$AllSettled.prototype._makeResult = $$enumerator$$makeSettledResult;
|
||
|
||
$$rsvp$all$settled$$AllSettled.prototype._validationError = function() {
|
||
return new Error('allSettled must be called with an array');
|
||
};
|
||
|
||
var $$rsvp$all$settled$$default = function allSettled(entries, label) {
|
||
return new $$rsvp$all$settled$$AllSettled($$rsvp$promise$$default, entries, label).promise;
|
||
};
|
||
|
||
var $$rsvp$race$$default = function race(array, label) {
|
||
return $$rsvp$promise$$default.race(array, label);
|
||
};
|
||
|
||
function $$promise$hash$$PromiseHash(Constructor, object, label) {
|
||
this._superConstructor(Constructor, object, true, label);
|
||
}
|
||
|
||
var $$promise$hash$$default = $$promise$hash$$PromiseHash;
|
||
$$promise$hash$$PromiseHash.prototype = $$utils$$o_create($$enumerator$$default.prototype);
|
||
$$promise$hash$$PromiseHash.prototype._superConstructor = $$enumerator$$default;
|
||
|
||
$$promise$hash$$PromiseHash.prototype._init = function() {
|
||
this._result = {};
|
||
};
|
||
|
||
$$promise$hash$$PromiseHash.prototype._validateInput = function(input) {
|
||
return input && typeof input === 'object';
|
||
};
|
||
|
||
$$promise$hash$$PromiseHash.prototype._validationError = function() {
|
||
return new Error('Promise.hash must be called with an object');
|
||
};
|
||
|
||
$$promise$hash$$PromiseHash.prototype._enumerate = function() {
|
||
var promise = this.promise;
|
||
var input = this._input;
|
||
var results = [];
|
||
|
||
for (var key in input) {
|
||
if (promise._state === $$$internal$$PENDING && input.hasOwnProperty(key)) {
|
||
results.push({
|
||
position: key,
|
||
entry: input[key]
|
||
});
|
||
}
|
||
}
|
||
|
||
var length = results.length;
|
||
this._remaining = length;
|
||
var result;
|
||
|
||
for (var i = 0; promise._state === $$$internal$$PENDING && i < length; i++) {
|
||
result = results[i];
|
||
this._eachEntry(result.entry, result.position);
|
||
}
|
||
};
|
||
|
||
var $$rsvp$hash$$default = function hash(object, label) {
|
||
return new $$promise$hash$$default($$rsvp$promise$$default, object, label).promise;
|
||
};
|
||
|
||
function $$rsvp$hash$settled$$HashSettled(Constructor, object, label) {
|
||
this._superConstructor(Constructor, object, false, label);
|
||
}
|
||
|
||
$$rsvp$hash$settled$$HashSettled.prototype = $$utils$$o_create($$promise$hash$$default.prototype);
|
||
$$rsvp$hash$settled$$HashSettled.prototype._superConstructor = $$enumerator$$default;
|
||
$$rsvp$hash$settled$$HashSettled.prototype._makeResult = $$enumerator$$makeSettledResult;
|
||
|
||
$$rsvp$hash$settled$$HashSettled.prototype._validationError = function() {
|
||
return new Error('hashSettled must be called with an object');
|
||
};
|
||
|
||
var $$rsvp$hash$settled$$default = function hashSettled(object, label) {
|
||
return new $$rsvp$hash$settled$$HashSettled($$rsvp$promise$$default, object, label).promise;
|
||
};
|
||
|
||
var $$rsvp$rethrow$$default = function rethrow(reason) {
|
||
setTimeout(function() {
|
||
throw reason;
|
||
});
|
||
throw reason;
|
||
};
|
||
|
||
var $$rsvp$defer$$default = function defer(label) {
|
||
var deferred = { };
|
||
|
||
deferred.promise = new $$rsvp$promise$$default(function(resolve, reject) {
|
||
deferred.resolve = resolve;
|
||
deferred.reject = reject;
|
||
}, label);
|
||
|
||
return deferred;
|
||
};
|
||
|
||
var $$rsvp$map$$default = function map(promises, mapFn, label) {
|
||
return $$rsvp$promise$$default.all(promises, label).then(function(values) {
|
||
if (!$$utils$$isFunction(mapFn)) {
|
||
throw new TypeError("You must pass a function as map's second argument.");
|
||
}
|
||
|
||
var length = values.length;
|
||
var results = new Array(length);
|
||
|
||
for (var i = 0; i < length; i++) {
|
||
results[i] = mapFn(values[i]);
|
||
}
|
||
|
||
return $$rsvp$promise$$default.all(results, label);
|
||
});
|
||
};
|
||
|
||
var $$rsvp$resolve$$default = function resolve(value, label) {
|
||
return $$rsvp$promise$$default.resolve(value, label);
|
||
};
|
||
|
||
var $$rsvp$reject$$default = function reject(reason, label) {
|
||
return $$rsvp$promise$$default.reject(reason, label);
|
||
};
|
||
|
||
var $$rsvp$filter$$default = function filter(promises, filterFn, label) {
|
||
return $$rsvp$promise$$default.all(promises, label).then(function(values) {
|
||
if (!$$utils$$isFunction(filterFn)) {
|
||
throw new TypeError("You must pass a function as filter's second argument.");
|
||
}
|
||
|
||
var length = values.length;
|
||
var filtered = new Array(length);
|
||
|
||
for (var i = 0; i < length; i++) {
|
||
filtered[i] = filterFn(values[i]);
|
||
}
|
||
|
||
return $$rsvp$promise$$default.all(filtered, label).then(function(filtered) {
|
||
var results = new Array(length);
|
||
var newLength = 0;
|
||
|
||
for (var i = 0; i < length; i++) {
|
||
if (filtered[i]) {
|
||
results[newLength] = values[i];
|
||
newLength++;
|
||
}
|
||
}
|
||
|
||
results.length = newLength;
|
||
|
||
return results;
|
||
});
|
||
});
|
||
};
|
||
|
||
var $$rsvp$asap$$len = 0;
|
||
|
||
var $$rsvp$asap$$default = function asap(callback, arg) {
|
||
$$rsvp$asap$$queue[$$rsvp$asap$$len] = callback;
|
||
$$rsvp$asap$$queue[$$rsvp$asap$$len + 1] = arg;
|
||
$$rsvp$asap$$len += 2;
|
||
if ($$rsvp$asap$$len === 2) {
|
||
// If len is 1, that means that we need to schedule an async flush.
|
||
// If additional callbacks are queued before the queue is flushed, they
|
||
// will be processed by this flush that we are scheduling.
|
||
$$rsvp$asap$$scheduleFlush();
|
||
}
|
||
};
|
||
|
||
var $$rsvp$asap$$browserGlobal = (typeof window !== 'undefined') ? window : {};
|
||
var $$rsvp$asap$$BrowserMutationObserver = $$rsvp$asap$$browserGlobal.MutationObserver || $$rsvp$asap$$browserGlobal.WebKitMutationObserver;
|
||
|
||
// test for web worker but not in IE10
|
||
var $$rsvp$asap$$isWorker = typeof Uint8ClampedArray !== 'undefined' &&
|
||
typeof importScripts !== 'undefined' &&
|
||
typeof MessageChannel !== 'undefined';
|
||
|
||
// node
|
||
function $$rsvp$asap$$useNextTick() {
|
||
return function() {
|
||
process.nextTick($$rsvp$asap$$flush);
|
||
};
|
||
}
|
||
|
||
function $$rsvp$asap$$useMutationObserver() {
|
||
var iterations = 0;
|
||
var observer = new $$rsvp$asap$$BrowserMutationObserver($$rsvp$asap$$flush);
|
||
var node = document.createTextNode('');
|
||
observer.observe(node, { characterData: true });
|
||
|
||
return function() {
|
||
node.data = (iterations = ++iterations % 2);
|
||
};
|
||
}
|
||
|
||
// web worker
|
||
function $$rsvp$asap$$useMessageChannel() {
|
||
var channel = new MessageChannel();
|
||
channel.port1.onmessage = $$rsvp$asap$$flush;
|
||
return function () {
|
||
channel.port2.postMessage(0);
|
||
};
|
||
}
|
||
|
||
function $$rsvp$asap$$useSetTimeout() {
|
||
return function() {
|
||
setTimeout($$rsvp$asap$$flush, 1);
|
||
};
|
||
}
|
||
|
||
var $$rsvp$asap$$queue = new Array(1000);
|
||
|
||
function $$rsvp$asap$$flush() {
|
||
for (var i = 0; i < $$rsvp$asap$$len; i+=2) {
|
||
var callback = $$rsvp$asap$$queue[i];
|
||
var arg = $$rsvp$asap$$queue[i+1];
|
||
|
||
callback(arg);
|
||
|
||
$$rsvp$asap$$queue[i] = undefined;
|
||
$$rsvp$asap$$queue[i+1] = undefined;
|
||
}
|
||
|
||
$$rsvp$asap$$len = 0;
|
||
}
|
||
|
||
var $$rsvp$asap$$scheduleFlush;
|
||
|
||
// Decide what async method to use to triggering processing of queued callbacks:
|
||
if (typeof process !== 'undefined' && {}.toString.call(process) === '[object process]') {
|
||
$$rsvp$asap$$scheduleFlush = $$rsvp$asap$$useNextTick();
|
||
} else if ($$rsvp$asap$$BrowserMutationObserver) {
|
||
$$rsvp$asap$$scheduleFlush = $$rsvp$asap$$useMutationObserver();
|
||
} else if ($$rsvp$asap$$isWorker) {
|
||
$$rsvp$asap$$scheduleFlush = $$rsvp$asap$$useMessageChannel();
|
||
} else {
|
||
$$rsvp$asap$$scheduleFlush = $$rsvp$asap$$useSetTimeout();
|
||
}
|
||
|
||
// default async is asap;
|
||
$$rsvp$config$$config.async = $$rsvp$asap$$default;
|
||
|
||
var $$rsvp$$cast = $$rsvp$resolve$$default;
|
||
|
||
function $$rsvp$$async(callback, arg) {
|
||
$$rsvp$config$$config.async(callback, arg);
|
||
}
|
||
|
||
function $$rsvp$$on() {
|
||
$$rsvp$config$$config.on.apply($$rsvp$config$$config, arguments);
|
||
}
|
||
|
||
function $$rsvp$$off() {
|
||
$$rsvp$config$$config.off.apply($$rsvp$config$$config, arguments);
|
||
}
|
||
|
||
// Set up instrumentation through `window.__PROMISE_INTRUMENTATION__`
|
||
if (typeof window !== 'undefined' && typeof window['__PROMISE_INSTRUMENTATION__'] === 'object') {
|
||
var $$rsvp$$callbacks = window['__PROMISE_INSTRUMENTATION__'];
|
||
$$rsvp$config$$configure('instrument', true);
|
||
for (var $$rsvp$$eventName in $$rsvp$$callbacks) {
|
||
if ($$rsvp$$callbacks.hasOwnProperty($$rsvp$$eventName)) {
|
||
$$rsvp$$on($$rsvp$$eventName, $$rsvp$$callbacks[$$rsvp$$eventName]);
|
||
}
|
||
}
|
||
}
|
||
|
||
var rsvp$umd$$RSVP = {
|
||
'race': $$rsvp$race$$default,
|
||
'Promise': $$rsvp$promise$$default,
|
||
'allSettled': $$rsvp$all$settled$$default,
|
||
'hash': $$rsvp$hash$$default,
|
||
'hashSettled': $$rsvp$hash$settled$$default,
|
||
'denodeify': $$rsvp$node$$default,
|
||
'on': $$rsvp$$on,
|
||
'off': $$rsvp$$off,
|
||
'map': $$rsvp$map$$default,
|
||
'filter': $$rsvp$filter$$default,
|
||
'resolve': $$rsvp$resolve$$default,
|
||
'reject': $$rsvp$reject$$default,
|
||
'all': $$rsvp$all$$default,
|
||
'rethrow': $$rsvp$rethrow$$default,
|
||
'defer': $$rsvp$defer$$default,
|
||
'EventTarget': $$rsvp$events$$default,
|
||
'configure': $$rsvp$config$$configure,
|
||
'async': $$rsvp$$async
|
||
};
|
||
|
||
/* global define:true module:true window: true */
|
||
if (typeof define === 'function' && define.amd) {
|
||
define(function() { return rsvp$umd$$RSVP; });
|
||
} else if (typeof module !== 'undefined' && module.exports) {
|
||
module.exports = rsvp$umd$$RSVP;
|
||
} else if (typeof this !== 'undefined') {
|
||
this['RSVP'] = rsvp$umd$$RSVP;
|
||
}
|
||
}).call(this);
|
||
if (typeof EPUBJS === 'undefined') {
|
||
(typeof window !== 'undefined' ? window : this).EPUBJS = {};
|
||
}
|
||
|
||
EPUBJS.VERSION = "0.3.0";
|
||
|
||
(function(root) {
|
||
"use strict";
|
||
var ePub = function(_url) {
|
||
return new EPUBJS.Book(_url);
|
||
};
|
||
|
||
// CommonJS
|
||
if (typeof exports === "object") {
|
||
root.RSVP = require("rsvp");
|
||
module.exports = ePub;
|
||
// RequireJS
|
||
} else if (typeof define === "function" && define.amd) {
|
||
define(ePub);
|
||
// Global
|
||
} else {
|
||
root.ePub = ePub;
|
||
}
|
||
|
||
})(this);
|
||
|
||
|
||
EPUBJS.core = {};
|
||
|
||
EPUBJS.core.request = function(url, type, withCredentials, headers) {
|
||
var supportsURL = window.URL;
|
||
var BLOB_RESPONSE = supportsURL ? "blob" : "arraybuffer";
|
||
|
||
var deferred = new RSVP.defer();
|
||
|
||
var xhr = new XMLHttpRequest();
|
||
|
||
//-- Check from PDF.js:
|
||
// https://github.com/mozilla/pdf.js/blob/master/web/compatibility.js
|
||
var xhrPrototype = XMLHttpRequest.prototype;
|
||
|
||
var header;
|
||
|
||
if (!('overrideMimeType' in xhrPrototype)) {
|
||
// IE10 might have response, but not overrideMimeType
|
||
Object.defineProperty(xhrPrototype, 'overrideMimeType', {
|
||
value: function xmlHttpRequestOverrideMimeType(mimeType) {}
|
||
});
|
||
}
|
||
if(withCredentials) {
|
||
xhr.withCredentials = true;
|
||
}
|
||
|
||
xhr.open("GET", url, true);
|
||
|
||
for(header in headers) {
|
||
xhr.setRequestHeader(header, headers[header]);
|
||
}
|
||
|
||
xhr.onreadystatechange = handler;
|
||
|
||
if(type == 'blob'){
|
||
xhr.responseType = BLOB_RESPONSE;
|
||
}
|
||
|
||
if(type == "json") {
|
||
xhr.setRequestHeader("Accept", "application/json");
|
||
}
|
||
|
||
if(type == 'xml') {
|
||
xhr.overrideMimeType('text/xml');
|
||
}
|
||
|
||
xhr.send();
|
||
|
||
function handler() {
|
||
if (this.readyState === this.DONE) {
|
||
if (this.status === 200 || this.responseXML ) { //-- Firefox is reporting 0 for blob urls
|
||
var r;
|
||
|
||
if(type == 'xml'){
|
||
|
||
// If this.responseXML wasn't set, try to parse using a DOMParser from text
|
||
if(!this.responseXML){
|
||
r = new DOMParser().parseFromString(this.response, "text/xml");
|
||
} else {
|
||
r = this.responseXML;
|
||
}
|
||
|
||
}else
|
||
if(type == 'json'){
|
||
r = JSON.parse(this.response);
|
||
}else
|
||
if(type == 'blob'){
|
||
|
||
if(supportsURL) {
|
||
r = this.response;
|
||
} else {
|
||
//-- Safari doesn't support responseType blob, so create a blob from arraybuffer
|
||
r = new Blob([this.response]);
|
||
}
|
||
|
||
}else{
|
||
r = this.response;
|
||
}
|
||
|
||
deferred.resolve(r);
|
||
} else {
|
||
deferred.reject({
|
||
status: this.status,
|
||
message : this.response,
|
||
stack : new Error().stack
|
||
});
|
||
}
|
||
}
|
||
}
|
||
|
||
return deferred.promise;
|
||
};
|
||
|
||
//-- Parse the different parts of a url, returning a object
|
||
EPUBJS.core.uri = function(url){
|
||
var uri = {
|
||
protocol : '',
|
||
host : '',
|
||
path : '',
|
||
origin : '',
|
||
directory : '',
|
||
base : '',
|
||
filename : '',
|
||
extension : '',
|
||
fragment : '',
|
||
href : url
|
||
},
|
||
doubleSlash = url.indexOf('://'),
|
||
search = url.indexOf('?'),
|
||
fragment = url.indexOf("#"),
|
||
withoutProtocol,
|
||
dot,
|
||
firstSlash;
|
||
|
||
if(fragment != -1) {
|
||
uri.fragment = url.slice(fragment + 1);
|
||
url = url.slice(0, fragment);
|
||
}
|
||
|
||
if(search != -1) {
|
||
uri.search = url.slice(search + 1);
|
||
url = url.slice(0, search);
|
||
href = url;
|
||
}
|
||
|
||
if(doubleSlash != -1) {
|
||
uri.protocol = url.slice(0, doubleSlash);
|
||
withoutProtocol = url.slice(doubleSlash+3);
|
||
firstSlash = withoutProtocol.indexOf('/');
|
||
|
||
if(firstSlash === -1) {
|
||
uri.host = uri.path;
|
||
uri.path = "";
|
||
} else {
|
||
uri.host = withoutProtocol.slice(0, firstSlash);
|
||
uri.path = withoutProtocol.slice(firstSlash);
|
||
}
|
||
|
||
|
||
uri.origin = uri.protocol + "://" + uri.host;
|
||
|
||
uri.directory = EPUBJS.core.folder(uri.path);
|
||
|
||
uri.base = uri.origin + uri.directory;
|
||
// return origin;
|
||
} else {
|
||
uri.path = url;
|
||
uri.directory = EPUBJS.core.folder(url);
|
||
uri.base = uri.directory;
|
||
}
|
||
|
||
//-- Filename
|
||
uri.filename = url.replace(uri.base, '');
|
||
dot = uri.filename.lastIndexOf('.');
|
||
if(dot != -1) {
|
||
uri.extension = uri.filename.slice(dot+1);
|
||
}
|
||
return uri;
|
||
};
|
||
|
||
//-- Parse out the folder, will return everything before the last slash
|
||
EPUBJS.core.folder = function(url){
|
||
|
||
var lastSlash = url.lastIndexOf('/');
|
||
|
||
if(lastSlash == -1) var folder = '';
|
||
|
||
folder = url.slice(0, lastSlash + 1);
|
||
|
||
return folder;
|
||
|
||
};
|
||
|
||
|
||
EPUBJS.core.queue = function(_scope){
|
||
var _q = [];
|
||
var scope = _scope;
|
||
// Add an item to the queue
|
||
var enqueue = function(funcName, args, context) {
|
||
_q.push({
|
||
"funcName" : funcName,
|
||
"args" : args,
|
||
"context" : context
|
||
});
|
||
return _q;
|
||
};
|
||
// Run one item
|
||
var dequeue = function(){
|
||
var inwait;
|
||
if(_q.length) {
|
||
inwait = _q.shift();
|
||
// Defer to any current tasks
|
||
// setTimeout(function(){
|
||
scope[inwait.funcName].apply(inwait.context || scope, inwait.args);
|
||
// }, 0);
|
||
}
|
||
};
|
||
|
||
// Run All
|
||
var flush = function(){
|
||
while(_q.length) {
|
||
dequeue();
|
||
}
|
||
};
|
||
// Clear all items in wait
|
||
var clear = function(){
|
||
_q = [];
|
||
};
|
||
|
||
var length = function(){
|
||
return _q.length;
|
||
};
|
||
|
||
return {
|
||
"enqueue" : enqueue,
|
||
"dequeue" : dequeue,
|
||
"flush" : flush,
|
||
"clear" : clear,
|
||
"length" : length
|
||
};
|
||
};
|
||
|
||
EPUBJS.core.isElement = function(obj) {
|
||
return !!(obj && obj.nodeType == 1);
|
||
};
|
||
|
||
// http://stackoverflow.com/questions/105034/how-to-create-a-guid-uuid-in-javascript
|
||
EPUBJS.core.uuid = function() {
|
||
var d = new Date().getTime();
|
||
var uuid = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
|
||
var r = (d + Math.random()*16)%16 | 0;
|
||
d = Math.floor(d/16);
|
||
return (c=='x' ? r : (r&0x7|0x8)).toString(16);
|
||
});
|
||
return uuid;
|
||
};
|
||
|
||
// From Lodash
|
||
EPUBJS.core.values = function(object) {
|
||
var index = -1,
|
||
props = Object.keys(object),
|
||
length = props.length,
|
||
result = Array(length);
|
||
|
||
while (++index < length) {
|
||
result[index] = object[props[index]];
|
||
}
|
||
return result;
|
||
};
|
||
|
||
EPUBJS.core.resolveUrl = function(base, path) {
|
||
var url = [],
|
||
segments = [],
|
||
baseUri = EPUBJS.core.uri(base),
|
||
pathUri = EPUBJS.core.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("/");
|
||
};
|
||
|
||
EPUBJS.core.documentHeight = function() {
|
||
return Math.max(
|
||
document.documentElement.clientHeight,
|
||
document.body.scrollHeight,
|
||
document.documentElement.scrollHeight,
|
||
document.body.offsetHeight,
|
||
document.documentElement.offsetHeight
|
||
);
|
||
};
|
||
|
||
EPUBJS.core.isNumber = function(n) {
|
||
return !isNaN(parseFloat(n)) && isFinite(n);
|
||
};
|
||
|
||
EPUBJS.core.prefixed = function(unprefixed) {
|
||
var vendors = ["Webkit", "Moz", "O", "ms" ],
|
||
prefixes = ['-Webkit-', '-moz-', '-o-', '-ms-'],
|
||
upper = unprefixed[0].toUpperCase() + unprefixed.slice(1),
|
||
length = vendors.length;
|
||
|
||
if (typeof(document.body.style[unprefixed]) != 'undefined') {
|
||
return unprefixed;
|
||
}
|
||
|
||
for ( var i=0; i < length; i++ ) {
|
||
if (typeof(document.body.style[vendors[i] + upper]) != 'undefined') {
|
||
return vendors[i] + upper;
|
||
}
|
||
}
|
||
|
||
return unprefixed;
|
||
};
|
||
|
||
EPUBJS.core.defaults = function(obj) {
|
||
for (var i = 1, length = arguments.length; i < length; i++) {
|
||
var source = arguments[i];
|
||
for (var prop in source) {
|
||
if (obj[prop] === void 0) obj[prop] = source[prop];
|
||
}
|
||
}
|
||
return obj;
|
||
};
|
||
|
||
EPUBJS.core.extend = function(target) {
|
||
var sources = [].slice.call(arguments, 1);
|
||
sources.forEach(function (source) {
|
||
if(!source) return;
|
||
Object.getOwnPropertyNames(source).forEach(function(propName) {
|
||
Object.defineProperty(target, propName, Object.getOwnPropertyDescriptor(source, propName));
|
||
});
|
||
});
|
||
return target;
|
||
};
|
||
|
||
// Fast quicksort insert for sorted array -- based on:
|
||
// http://stackoverflow.com/questions/1344500/efficient-way-to-insert-a-number-into-a-sorted-array-of-numbers
|
||
EPUBJS.core.insert = function(item, array, compareFunction) {
|
||
var location = EPUBJS.core.locationOf(item, array, compareFunction);
|
||
array.splice(location, 0, item);
|
||
|
||
return location;
|
||
};
|
||
// Returns where something would fit in
|
||
EPUBJS.core.locationOf = function(item, array, compareFunction, _start, _end) {
|
||
var start = _start || 0;
|
||
var end = _end || array.length;
|
||
var pivot = parseInt(start + (end - start) / 2);
|
||
var compared;
|
||
if(!compareFunction){
|
||
compareFunction = function(a, b) {
|
||
if(a > b) return 1;
|
||
if(a < b) return -1;
|
||
if(a = b) return 0;
|
||
};
|
||
}
|
||
if(end-start <= 0) {
|
||
return pivot;
|
||
}
|
||
|
||
compared = compareFunction(array[pivot], item);
|
||
if(end-start === 1) {
|
||
return compared > 0 ? pivot : pivot + 1;
|
||
}
|
||
|
||
if(compared === 0) {
|
||
return pivot;
|
||
}
|
||
if(compared === -1) {
|
||
return EPUBJS.core.locationOf(item, array, compareFunction, pivot, end);
|
||
} else{
|
||
return EPUBJS.core.locationOf(item, array, compareFunction, start, pivot);
|
||
}
|
||
};
|
||
// Returns -1 of mpt found
|
||
EPUBJS.core.indexOfSorted = function(item, array, compareFunction, _start, _end) {
|
||
var start = _start || 0;
|
||
var end = _end || array.length;
|
||
var pivot = parseInt(start + (end - start) / 2);
|
||
var compared;
|
||
if(!compareFunction){
|
||
compareFunction = function(a, b) {
|
||
if(a > b) return 1;
|
||
if(a < b) return -1;
|
||
if(a = b) return 0;
|
||
};
|
||
}
|
||
if(end-start <= 0) {
|
||
return -1; // Not found
|
||
}
|
||
|
||
compared = compareFunction(array[pivot], item);
|
||
if(end-start === 1) {
|
||
return compared === 0 ? pivot : -1;
|
||
}
|
||
if(compared === 0) {
|
||
return pivot; // Found
|
||
}
|
||
if(compared === -1) {
|
||
return EPUBJS.core.indexOfSorted(item, array, compareFunction, pivot, end);
|
||
} else{
|
||
return EPUBJS.core.indexOfSorted(item, array, compareFunction, start, pivot);
|
||
}
|
||
};
|
||
|
||
EPUBJS.core.requestAnimationFrame = window.requestAnimationFrame || window.mozRequestAnimationFrame || window.webkitRequestAnimationFrame || window.msRequestAnimationFrame;
|
||
|
||
EPUBJS.core.bounds = function(el) {
|
||
|
||
var style = window.getComputedStyle(el);
|
||
var widthProps = ["width", "paddingRight", "paddingLeft", "marginRight", "marginLeft", "borderRightWidth", "borderLeftWidth"];
|
||
var heightProps = ["height", "paddingTop", "paddingBottom", "marginTop", "marginBottom", "borderTopWidth", "borderBottomWidth"];
|
||
|
||
var width = 0;
|
||
var height = 0;
|
||
|
||
widthProps.forEach(function(prop){
|
||
width += parseFloat(style[prop]) || 0;
|
||
});
|
||
|
||
heightProps.forEach(function(prop){
|
||
height += parseFloat(style[prop]) || 0;
|
||
});
|
||
|
||
return {
|
||
height: height,
|
||
width: width
|
||
};
|
||
|
||
};
|
||
|
||
EPUBJS.core.borders = function(el) {
|
||
|
||
var style = window.getComputedStyle(el);
|
||
var widthProps = ["paddingRight", "paddingLeft", "marginRight", "marginLeft", "borderRightWidth", "borderLeftWidth"];
|
||
var heightProps = ["paddingTop", "paddingBottom", "marginTop", "marginBottom", "borderTopWidth", "borderBottomWidth"];
|
||
|
||
var width = 0;
|
||
var height = 0;
|
||
|
||
widthProps.forEach(function(prop){
|
||
width += parseFloat(style[prop]) || 0;
|
||
});
|
||
|
||
heightProps.forEach(function(prop){
|
||
height += parseFloat(style[prop]) || 0;
|
||
});
|
||
|
||
return {
|
||
height: height,
|
||
width: width
|
||
};
|
||
|
||
};
|
||
|
||
EPUBJS.core.windowBounds = function() {
|
||
|
||
var width = window.innerWidth;
|
||
var height = window.innerHeight;
|
||
|
||
return {
|
||
top: 0,
|
||
left: 0,
|
||
right: width,
|
||
bottom: height,
|
||
width: width,
|
||
height: height
|
||
};
|
||
|
||
};
|
||
|
||
//https://stackoverflow.com/questions/13482352/xquery-looking-for-text-with-single-quote/13483496#13483496
|
||
EPUBJS.core.cleanStringForXpath = function(str) {
|
||
var parts = str.match(/[^'"]+|['"]/g);
|
||
parts = parts.map(function(part){
|
||
if (part === "'") {
|
||
return '\"\'\"'; // output "'"
|
||
}
|
||
|
||
if (part === '"') {
|
||
return "\'\"\'"; // output '"'
|
||
}
|
||
return "\'" + part + "\'";
|
||
});
|
||
return "concat(\'\'," + parts.join(",") + ")";
|
||
};
|
||
|
||
EPUBJS.core.indexOfTextNode = function(textNode){
|
||
var parent = textNode.parentNode;
|
||
var children = parent.childNodes;
|
||
var sib;
|
||
var index = -1;
|
||
for (var i = 0; i < children.length; i++) {
|
||
sib = children[i];
|
||
if(sib.nodeType === Node.TEXT_NODE){
|
||
index++;
|
||
}
|
||
if(sib == textNode) break;
|
||
}
|
||
|
||
return index;
|
||
};
|
||
EPUBJS.Queue = function(_context){
|
||
this._q = [];
|
||
this.context = _context;
|
||
this.tick = EPUBJS.core.requestAnimationFrame;
|
||
this.running = false;
|
||
this.paused = false;
|
||
};
|
||
|
||
// Add an item to the queue
|
||
EPUBJS.Queue.prototype.enqueue = function() {
|
||
var deferred, promise;
|
||
var queued;
|
||
var task = [].shift.call(arguments);
|
||
var args = arguments;
|
||
|
||
// Handle single args without context
|
||
// if(args && !Array.isArray(args)) {
|
||
// args = [args];
|
||
// }
|
||
if(!task) {
|
||
return console.error("No Task Provided");
|
||
}
|
||
|
||
if(typeof task === "function"){
|
||
|
||
deferred = new RSVP.defer();
|
||
promise = deferred.promise;
|
||
|
||
queued = {
|
||
"task" : task,
|
||
"args" : args,
|
||
//"context" : context,
|
||
"deferred" : deferred,
|
||
"promise" : promise
|
||
};
|
||
|
||
} else {
|
||
// Task is a promise
|
||
queued = {
|
||
"promise" : task
|
||
};
|
||
|
||
}
|
||
|
||
this._q.push(queued);
|
||
|
||
// Wait to start queue flush
|
||
if (this.paused == false && !this.running) {
|
||
// setTimeout(this.flush.bind(this), 0);
|
||
// this.tick.call(window, this.run.bind(this));
|
||
this.run();
|
||
}
|
||
|
||
return queued.promise;
|
||
};
|
||
|
||
// Run one item
|
||
EPUBJS.Queue.prototype.dequeue = function(){
|
||
var inwait, task, result;
|
||
|
||
if(this._q.length) {
|
||
inwait = this._q.shift();
|
||
task = inwait.task;
|
||
if(task){
|
||
// console.log(task)
|
||
|
||
result = task.apply(this.context, inwait.args);
|
||
|
||
if(result && typeof result["then"] === "function") {
|
||
// Task is a function that returns a promise
|
||
return result.then(function(){
|
||
inwait.deferred.resolve.apply(this.context, arguments);
|
||
}.bind(this));
|
||
} else {
|
||
// Task resolves immediately
|
||
inwait.deferred.resolve.apply(this.context, result);
|
||
return inwait.promise;
|
||
}
|
||
|
||
|
||
|
||
} else if(inwait.promise) {
|
||
// Task is a promise
|
||
return inwait.promise;
|
||
}
|
||
|
||
} else {
|
||
inwait = new RSVP.defer();
|
||
inwait.deferred.resolve();
|
||
return inwait.promise;
|
||
}
|
||
|
||
};
|
||
|
||
// Run All Immediately
|
||
EPUBJS.Queue.prototype.dump = function(){
|
||
while(this._q.length) {
|
||
this.dequeue();
|
||
}
|
||
};
|
||
|
||
// Run all sequentially, at convince
|
||
|
||
EPUBJS.Queue.prototype.run = function(){
|
||
|
||
if(!this.running){
|
||
this.running = true;
|
||
this.defered = new RSVP.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));
|
||
|
||
// Unpause
|
||
if(this.paused == true) {
|
||
this.paused = false;
|
||
}
|
||
|
||
return this.defered.promise;
|
||
};
|
||
|
||
// Flush all, as quickly as possible
|
||
EPUBJS.Queue.prototype.flush = function(){
|
||
|
||
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
|
||
EPUBJS.Queue.prototype.clear = function(){
|
||
this._q = [];
|
||
this.running = false;
|
||
};
|
||
|
||
EPUBJS.Queue.prototype.length = function(){
|
||
return this._q.length;
|
||
};
|
||
|
||
EPUBJS.Queue.prototype.pause = function(){
|
||
this.paused = true;
|
||
};
|
||
|
||
// Create a new task from a callback
|
||
EPUBJS.Task = function(task, args, context){
|
||
|
||
return function(){
|
||
var toApply = arguments || [];
|
||
|
||
return new RSVP.Promise(function(resolve, reject) {
|
||
var callback = function(value){
|
||
resolve(value);
|
||
};
|
||
// Add the callback to the arguments list
|
||
toApply.push(callback);
|
||
|
||
// Apply all arguments to the functions
|
||
task.apply(this, toApply);
|
||
|
||
}.bind(this));
|
||
|
||
};
|
||
|
||
};
|
||
|
||
EPUBJS.Hook = function(context){
|
||
this.context = context || this;
|
||
this.hooks = [];
|
||
};
|
||
|
||
//-- Hooks allow for injecting functions that must all complete in order before finishing
|
||
// They will execute in parallel but all must finish before continuing
|
||
// Functions may return a promise if they are asycn.
|
||
|
||
// this.content = new EPUBJS.Hook();
|
||
// this.content.register(function(){});
|
||
// this.content.trigger(args).then(function(){});
|
||
|
||
// Adds a function to be run before a hook completes
|
||
EPUBJS.Hook.prototype.register = function(func){
|
||
this.hooks.push(func);
|
||
};
|
||
|
||
// Triggers a hook to run all functions
|
||
EPUBJS.Hook.prototype.trigger = function(){
|
||
var args = arguments;
|
||
var context = this.context;
|
||
var promises = [];
|
||
|
||
this.hooks.forEach(function(task, i) {
|
||
var executing = task.apply(context, args);
|
||
|
||
if(executing && typeof executing["then"] === "function") {
|
||
// Task is a function that returns a promise
|
||
promises.push(executing);
|
||
}
|
||
// Otherwise Task resolves immediately, continue
|
||
});
|
||
|
||
|
||
return RSVP.all(promises);
|
||
};
|
||
|
||
EPUBJS.Parser = function(){};
|
||
|
||
EPUBJS.Parser.prototype.container = function(containerXml){
|
||
//-- <rootfile full-path="OPS/package.opf" media-type="application/oebps-package+xml"/>
|
||
var rootfile, fullpath, folder, encoding;
|
||
|
||
if(!containerXml) {
|
||
console.error("Container File Not Found");
|
||
return;
|
||
}
|
||
|
||
rootfile = containerXml.querySelector("rootfile");
|
||
|
||
if(!rootfile) {
|
||
console.error("No RootFile Found");
|
||
return;
|
||
}
|
||
|
||
fullpath = rootfile.getAttribute('full-path');
|
||
folder = EPUBJS.core.uri(fullpath).directory;
|
||
encoding = containerXml.xmlEncoding;
|
||
|
||
//-- Now that we have the path we can parse the contents
|
||
return {
|
||
'packagePath' : fullpath,
|
||
'basePath' : folder,
|
||
'encoding' : encoding
|
||
};
|
||
};
|
||
|
||
EPUBJS.Parser.prototype.identifier = function(packageXml){
|
||
var metadataNode;
|
||
|
||
if(!packageXml) {
|
||
console.error("Package File Not Found");
|
||
return;
|
||
}
|
||
|
||
metadataNode = packageXml.querySelector("metadata");
|
||
|
||
if(!metadataNode) {
|
||
console.error("No Metadata Found");
|
||
return;
|
||
}
|
||
|
||
return this.getElementText(metadataNode, "identifier");
|
||
};
|
||
|
||
EPUBJS.Parser.prototype.packageContents = function(packageXml){
|
||
var parse = this;
|
||
var metadataNode, manifestNode, spineNode;
|
||
var manifest, navPath, ncxPath, coverPath;
|
||
var spineNodeIndex;
|
||
var spine;
|
||
var spineIndexByURL;
|
||
|
||
if(!packageXml) {
|
||
console.error("Package File Not Found");
|
||
return;
|
||
}
|
||
|
||
metadataNode = packageXml.querySelector("metadata");
|
||
if(!metadataNode) {
|
||
console.error("No Metadata Found");
|
||
return;
|
||
}
|
||
|
||
manifestNode = packageXml.querySelector("manifest");
|
||
if(!manifestNode) {
|
||
console.error("No Manifest Found");
|
||
return;
|
||
}
|
||
|
||
spineNode = packageXml.querySelector("spine");
|
||
if(!spineNode) {
|
||
console.error("No Spine Found");
|
||
return;
|
||
}
|
||
|
||
manifest = parse.manifest(manifestNode);
|
||
navPath = parse.findNavPath(manifestNode);
|
||
ncxPath = parse.findNcxPath(manifestNode);
|
||
coverPath = parse.findCoverPath(manifestNode);
|
||
|
||
spineNodeIndex = Array.prototype.indexOf.call(spineNode.parentNode.childNodes, spineNode);
|
||
|
||
spine = parse.spine(spineNode, manifest);
|
||
|
||
return {
|
||
'metadata' : parse.metadata(metadataNode),
|
||
'spine' : spine,
|
||
'manifest' : manifest,
|
||
'navPath' : navPath,
|
||
'ncxPath' : ncxPath,
|
||
'coverPath': coverPath,
|
||
'spineNodeIndex' : spineNodeIndex
|
||
};
|
||
};
|
||
|
||
//-- Find TOC NAV: media-type="application/xhtml+xml" href="toc.ncx"
|
||
EPUBJS.Parser.prototype.findNavPath = function(manifestNode){
|
||
var node = manifestNode.querySelector("item[properties^='nav']");
|
||
return node ? node.getAttribute('href') : false;
|
||
};
|
||
|
||
//-- Find TOC NCX: media-type="application/x-dtbncx+xml" href="toc.ncx"
|
||
EPUBJS.Parser.prototype.findNcxPath = function(manifestNode){
|
||
var node = manifestNode.querySelector("item[media-type='application/x-dtbncx+xml']");
|
||
return node ? node.getAttribute('href') : false;
|
||
};
|
||
|
||
//-- Find Cover: <item properties="cover-image" id="ci" href="cover.svg" media-type="image/svg+xml" />
|
||
EPUBJS.Parser.prototype.findCoverPath = function(manifestNode){
|
||
var node = manifestNode.querySelector("item[properties='cover-image']");
|
||
return node ? node.getAttribute('href') : false;
|
||
};
|
||
|
||
//-- Expanded to match Readium web components
|
||
EPUBJS.Parser.prototype.metadata = function(xml){
|
||
var metadata = {},
|
||
p = this;
|
||
|
||
metadata.title = p.getElementText(xml, 'title');
|
||
metadata.creator = p.getElementText(xml, 'creator');
|
||
metadata.description = p.getElementText(xml, 'description');
|
||
|
||
metadata.pubdate = p.getElementText(xml, 'date');
|
||
|
||
metadata.publisher = p.getElementText(xml, 'publisher');
|
||
|
||
metadata.identifier = p.getElementText(xml, "identifier");
|
||
metadata.language = p.getElementText(xml, "language");
|
||
metadata.rights = p.getElementText(xml, "rights");
|
||
|
||
metadata.modified_date = p.querySelectorText(xml, "meta[property='dcterms:modified']");
|
||
metadata.layout = p.querySelectorText(xml, "meta[property='rendition:layout']");
|
||
metadata.orientation = p.querySelectorText(xml, "meta[property='rendition:orientation']");
|
||
metadata.spread = p.querySelectorText(xml, "meta[property='rendition:spread']");
|
||
// metadata.page_prog_dir = packageXml.querySelector("spine").getAttribute("page-progression-direction");
|
||
|
||
return metadata;
|
||
};
|
||
|
||
EPUBJS.Parser.prototype.getElementText = function(xml, tag){
|
||
var found = xml.getElementsByTagNameNS("http://purl.org/dc/elements/1.1/", tag),
|
||
el;
|
||
|
||
if(!found || found.length === 0) return '';
|
||
|
||
el = found[0];
|
||
|
||
if(el.childNodes.length){
|
||
return el.childNodes[0].nodeValue;
|
||
}
|
||
|
||
return '';
|
||
|
||
};
|
||
|
||
EPUBJS.Parser.prototype.querySelectorText = function(xml, q){
|
||
var el = xml.querySelector(q);
|
||
|
||
if(el && el.childNodes.length){
|
||
return el.childNodes[0].nodeValue;
|
||
}
|
||
|
||
return '';
|
||
};
|
||
|
||
EPUBJS.Parser.prototype.manifest = function(manifestXml){
|
||
var manifest = {};
|
||
|
||
//-- Turn items into an array
|
||
var selected = manifestXml.querySelectorAll("item"),
|
||
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;
|
||
|
||
};
|
||
|
||
EPUBJS.Parser.prototype.spine = function(spineXml, manifest){
|
||
var spine = [];
|
||
|
||
var selected = spineXml.getElementsByTagName("itemref"),
|
||
items = Array.prototype.slice.call(selected);
|
||
|
||
var epubcfi = new EPUBJS.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;
|
||
};
|
||
|
||
EPUBJS.Parser.prototype.nav = function(navHtml){
|
||
var navEl = navHtml.querySelector('nav[*|type="toc"]'), //-- [*|type="toc"] * Doesn't seem to work
|
||
idCounter = 0;
|
||
|
||
if(!navEl) return [];
|
||
|
||
// Implements `> ol > li`
|
||
function findListItems(parent){
|
||
var items = [];
|
||
|
||
Array.prototype.slice.call(parent.childNodes).forEach(function(node){
|
||
if('ol' == node.tagName){
|
||
Array.prototype.slice.call(node.childNodes).forEach(function(item){
|
||
if('li' == item.tagName){
|
||
items.push(item);
|
||
}
|
||
});
|
||
}
|
||
});
|
||
|
||
return items;
|
||
|
||
}
|
||
|
||
// Implements `> a, > span`
|
||
function findAnchorOrSpan(parent){
|
||
var item = null;
|
||
|
||
Array.prototype.slice.call(parent.childNodes).forEach(function(node){
|
||
if('a' == node.tagName || 'span' == node.tagName){
|
||
item = node;
|
||
}
|
||
});
|
||
|
||
return item;
|
||
}
|
||
|
||
function getTOC(parent){
|
||
var list = [],
|
||
nodes = findListItems(parent),
|
||
items = Array.prototype.slice.call(nodes),
|
||
length = items.length,
|
||
node;
|
||
|
||
if(length === 0) return false;
|
||
|
||
items.forEach(function(item){
|
||
var id = item.getAttribute('id') || false,
|
||
content = findAnchorOrSpan(item),
|
||
href = content.getAttribute('href') || '',
|
||
text = content.textContent || "",
|
||
split = href.split("#"),
|
||
baseUrl = split[0],
|
||
subitems = getTOC(item);
|
||
// spinePos = spineIndexByURL[baseUrl],
|
||
// spineItem = bookSpine[spinePos],
|
||
// cfi = spineItem ? spineItem.cfi : '';
|
||
|
||
// if(!id) {
|
||
// if(spinePos) {
|
||
// spineItem = bookSpine[spinePos];
|
||
// id = spineItem.id;
|
||
// cfi = spineItem.cfi;
|
||
// } else {
|
||
// id = 'epubjs-autogen-toc-id-' + (idCounter++);
|
||
// }
|
||
// }
|
||
|
||
// item.setAttribute('id', id); // Ensure all elements have an id
|
||
list.push({
|
||
"id": id,
|
||
"href": href,
|
||
"label": text,
|
||
"subitems" : subitems,
|
||
"parent" : parent ? parent.getAttribute('id') : null
|
||
// "cfi" : cfi
|
||
});
|
||
|
||
});
|
||
|
||
return list;
|
||
}
|
||
|
||
return getTOC(navEl);
|
||
};
|
||
|
||
EPUBJS.Parser.prototype.ncx = function(tocXml){
|
||
var navMap = tocXml.querySelector("navMap");
|
||
if(!navMap) return [];
|
||
|
||
function getTOC(parent){
|
||
var list = [],
|
||
snapshot = tocXml.evaluate("*[local-name()='navPoint']", parent, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null),
|
||
length = snapshot.snapshotLength;
|
||
|
||
if(length === 0) return [];
|
||
|
||
for ( var i=length-1 ; i >= 0; i-- ) {
|
||
var item = snapshot.snapshotItem(i);
|
||
|
||
var id = item.getAttribute('id') || false,
|
||
content = item.querySelector("content"),
|
||
src = content.getAttribute('src'),
|
||
navLabel = item.querySelector("navLabel"),
|
||
text = navLabel.textContent ? navLabel.textContent : "",
|
||
split = src.split("#"),
|
||
baseUrl = split[0],
|
||
// spinePos = spineIndexByURL[baseUrl],
|
||
// spineItem = bookSpine[spinePos],
|
||
subitems = getTOC(item);
|
||
// cfi = spineItem ? spineItem.cfi : '';
|
||
|
||
// if(!id) {
|
||
// if(spinePos) {
|
||
// spineItem = bookSpine[spinePos];
|
||
// id = spineItem.id;
|
||
// cfi = spineItem.cfi;
|
||
// } else {
|
||
// id = 'epubjs-autogen-toc-id-' + (idCounter++);
|
||
// }
|
||
// }
|
||
|
||
list.unshift({
|
||
"id": id,
|
||
"href": src,
|
||
"label": text,
|
||
// "spinePos": spinePos,
|
||
"subitems" : subitems,
|
||
"parent" : parent ? parent.getAttribute('id') : null,
|
||
// "cfi" : cfi
|
||
});
|
||
|
||
}
|
||
|
||
return list;
|
||
}
|
||
|
||
return getTOC(navMap);
|
||
};
|
||
EPUBJS.EpubCFI = function(cfiStr){
|
||
if(cfiStr) return this.parse(cfiStr);
|
||
};
|
||
|
||
EPUBJS.EpubCFI.prototype.generateChapterComponent = function(_spineNodeIndex, _pos, id) {
|
||
var pos = parseInt(_pos),
|
||
spineNodeIndex = _spineNodeIndex + 1,
|
||
cfi = '/'+spineNodeIndex+'/';
|
||
|
||
cfi += (pos + 1) * 2;
|
||
|
||
if(id) cfi += "[" + id + "]";
|
||
|
||
//cfi += "!";
|
||
|
||
return cfi;
|
||
};
|
||
|
||
EPUBJS.EpubCFI.prototype.generatePathComponent = function(steps) {
|
||
var parts = [];
|
||
|
||
steps.forEach(function(part){
|
||
var segment = '';
|
||
segment += (part.index + 1) * 2;
|
||
|
||
if(part.id) {
|
||
segment += "[" + part.id + "]";
|
||
}
|
||
|
||
parts.push(segment);
|
||
});
|
||
|
||
return parts.join('/');
|
||
};
|
||
|
||
EPUBJS.EpubCFI.prototype.generateCfiFromElement = function(element, chapter) {
|
||
var steps = this.pathTo(element);
|
||
var path = this.generatePathComponent(steps);
|
||
if(!path.length) {
|
||
// Start of Chapter
|
||
return "epubcfi(" + chapter + "!/4/)";
|
||
} else {
|
||
// First Text Node
|
||
return "epubcfi(" + chapter + "!" + path + "/1:0)";
|
||
}
|
||
};
|
||
|
||
EPUBJS.EpubCFI.prototype.pathTo = function(node) {
|
||
var stack = [],
|
||
children;
|
||
|
||
while(node && node.parentNode !== null && node.parentNode.nodeType != 9) {
|
||
children = node.parentNode.children;
|
||
|
||
stack.unshift({
|
||
'id' : node.id,
|
||
// 'classList' : node.classList,
|
||
'tagName' : node.tagName,
|
||
'index' : children ? Array.prototype.indexOf.call(children, node) : 0
|
||
});
|
||
|
||
node = node.parentNode;
|
||
}
|
||
|
||
return stack;
|
||
};
|
||
|
||
EPUBJS.EpubCFI.prototype.getChapterComponent = function(cfiStr) {
|
||
|
||
var splitStr = cfiStr.split("!");
|
||
|
||
return splitStr[0];
|
||
};
|
||
|
||
EPUBJS.EpubCFI.prototype.getPathComponent = function(cfiStr) {
|
||
|
||
var splitStr = cfiStr.split("!");
|
||
var pathComponent = splitStr[1] ? splitStr[1].split(":") : '';
|
||
|
||
return pathComponent[0];
|
||
};
|
||
|
||
EPUBJS.EpubCFI.prototype.getCharecterOffsetComponent = function(cfiStr) {
|
||
var splitStr = cfiStr.split(":");
|
||
return splitStr[1] || '';
|
||
};
|
||
|
||
|
||
EPUBJS.EpubCFI.prototype.parse = function(cfiStr) {
|
||
var cfi = {},
|
||
chapSegment,
|
||
chapterComponent,
|
||
pathComponent,
|
||
charecterOffsetComponent,
|
||
assertion,
|
||
chapId,
|
||
path,
|
||
end,
|
||
endInt,
|
||
text,
|
||
parseStep = function(part){
|
||
var type, index, has_brackets, id;
|
||
|
||
type = "element";
|
||
index = parseInt(part) / 2 - 1;
|
||
has_brackets = part.match(/\[(.*)\]/);
|
||
if(has_brackets && has_brackets[1]){
|
||
id = has_brackets[1];
|
||
}
|
||
|
||
return {
|
||
"type" : type,
|
||
'index' : index,
|
||
'id' : id || false
|
||
};
|
||
};
|
||
|
||
if(typeof cfiStr !== "string") {
|
||
return {spinePos: -1};
|
||
}
|
||
|
||
cfi.str = cfiStr;
|
||
|
||
if(cfiStr.indexOf("epubcfi(") === 0 && cfiStr[cfiStr.length-1] === ")") {
|
||
// Remove intial epubcfi( and ending )
|
||
cfiStr = cfiStr.slice(8, cfiStr.length-1);
|
||
}
|
||
|
||
chapterComponent = this.getChapterComponent(cfiStr);
|
||
pathComponent = this.getPathComponent(cfiStr) || '';
|
||
charecterOffsetComponent = this.getCharecterOffsetComponent(cfiStr);
|
||
// Make sure this is a valid cfi or return
|
||
if(!chapterComponent) {
|
||
return {spinePos: -1};
|
||
}
|
||
|
||
// Chapter segment is always the second one
|
||
chapSegment = chapterComponent.split("/")[2] || '';
|
||
if(!chapSegment) return {spinePos:-1};
|
||
|
||
cfi.spinePos = (parseInt(chapSegment) / 2 - 1 ) || 0;
|
||
|
||
chapId = chapSegment.match(/\[(.*)\]/);
|
||
|
||
cfi.spineId = chapId ? chapId[1] : false;
|
||
|
||
if(pathComponent.indexOf(',') != -1) {
|
||
// Handle ranges -- not supported yet
|
||
console.warn("CFI Ranges are not supported");
|
||
}
|
||
|
||
path = pathComponent.split('/');
|
||
end = path.pop();
|
||
|
||
cfi.steps = [];
|
||
|
||
path.forEach(function(part){
|
||
var step;
|
||
|
||
if(part) {
|
||
step = parseStep(part);
|
||
cfi.steps.push(step);
|
||
}
|
||
});
|
||
|
||
//-- Check if END is a text node or element
|
||
endInt = parseInt(end);
|
||
if(!isNaN(endInt)) {
|
||
|
||
if(endInt % 2 === 0) { // Even = is an element
|
||
cfi.steps.push(parseStep(end));
|
||
} else {
|
||
cfi.steps.push({
|
||
"type" : "text",
|
||
'index' : (endInt - 1 ) / 2
|
||
});
|
||
}
|
||
|
||
}
|
||
|
||
assertion = charecterOffsetComponent.match(/\[(.*)\]/);
|
||
if(assertion && assertion[1]){
|
||
cfi.characterOffset = parseInt(charecterOffsetComponent.split('[')[0]);
|
||
// We arent handling these assertions yet
|
||
cfi.textLocationAssertion = assertion[1];
|
||
} else {
|
||
cfi.characterOffset = parseInt(charecterOffsetComponent);
|
||
}
|
||
|
||
return cfi;
|
||
};
|
||
|
||
EPUBJS.EpubCFI.prototype.addMarker = function(cfi, _doc, _marker) {
|
||
var doc = _doc || document;
|
||
var marker = _marker || this.createMarker(doc);
|
||
var parent;
|
||
var lastStep;
|
||
var text;
|
||
var split;
|
||
|
||
if(typeof cfi === 'string') {
|
||
cfi = this.parse(cfi);
|
||
}
|
||
// Get the terminal step
|
||
lastStep = cfi.steps[cfi.steps.length-1];
|
||
|
||
// check spinePos
|
||
if(cfi.spinePos === -1) {
|
||
// Not a valid CFI
|
||
return false;
|
||
}
|
||
|
||
// Find the CFI elements parent
|
||
parent = this.findParent(cfi, doc);
|
||
|
||
if(!parent) {
|
||
// CFI didn't return an element
|
||
// Maybe it isnt in the current chapter?
|
||
return false;
|
||
}
|
||
|
||
if(lastStep && lastStep.type === "text") {
|
||
text = parent.childNodes[lastStep.index];
|
||
if(cfi.characterOffset){
|
||
split = text.splitText(cfi.characterOffset);
|
||
marker.classList.add("EPUBJS-CFI-SPLIT");
|
||
parent.insertBefore(marker, split);
|
||
} else {
|
||
parent.insertBefore(marker, text);
|
||
}
|
||
} else {
|
||
parent.insertBefore(marker, parent.firstChild);
|
||
}
|
||
|
||
return marker;
|
||
};
|
||
|
||
EPUBJS.EpubCFI.prototype.createMarker = function(_doc) {
|
||
var doc = _doc || document;
|
||
var element = doc.createElement('span');
|
||
element.id = "EPUBJS-CFI-MARKER:"+ EPUBJS.core.uuid();
|
||
element.classList.add("EPUBJS-CFI-MARKER");
|
||
|
||
return element;
|
||
};
|
||
|
||
EPUBJS.EpubCFI.prototype.removeMarker = function(marker, _doc) {
|
||
var doc = _doc || document;
|
||
// var id = marker.id;
|
||
|
||
// Cleanup textnodes if they were split
|
||
if(marker.classList.contains("EPUBJS-CFI-SPLIT")){
|
||
nextSib = marker.nextSibling;
|
||
prevSib = marker.previousSibling;
|
||
if(nextSib &&
|
||
prevSib &&
|
||
nextSib.nodeType === 3 &&
|
||
prevSib.nodeType === 3){
|
||
|
||
prevSib.textContent += nextSib.textContent;
|
||
marker.parentNode.removeChild(nextSib);
|
||
}
|
||
marker.parentNode.removeChild(marker);
|
||
} else if(marker.classList.contains("EPUBJS-CFI-MARKER")) {
|
||
// Remove only elements added as markers
|
||
marker.parentNode.removeChild(marker);
|
||
}
|
||
|
||
};
|
||
|
||
EPUBJS.EpubCFI.prototype.findParent = function(cfi, _doc) {
|
||
var doc = _doc || document,
|
||
element = doc.getElementsByTagName('html')[0],
|
||
children = Array.prototype.slice.call(element.children),
|
||
num, index, part, sections,
|
||
text, textBegin, textEnd;
|
||
|
||
if(typeof cfi === 'string') {
|
||
cfi = this.parse(cfi);
|
||
}
|
||
|
||
sections = cfi.steps.slice(0); // Clone steps array
|
||
if(!sections.length) {
|
||
return doc.getElementsByTagName('body')[0];
|
||
}
|
||
|
||
while(sections && sections.length > 0) {
|
||
part = sections.shift();
|
||
// Find textNodes Parent
|
||
if(part.type === "text") {
|
||
text = element.childNodes[part.index];
|
||
element = text.parentNode || element;
|
||
// Find element by id if present
|
||
} else if(part.id){
|
||
element = doc.getElementById(part.id);
|
||
// Find element in parent
|
||
}else{
|
||
element = children[part.index];
|
||
}
|
||
// Element can't be found
|
||
if(typeof element === "undefined") {
|
||
console.error("No Element For", part, cfi.str);
|
||
return false;
|
||
}
|
||
// Get current element children and continue through steps
|
||
children = Array.prototype.slice.call(element.children);
|
||
}
|
||
|
||
return element;
|
||
};
|
||
|
||
EPUBJS.EpubCFI.prototype.compare = function(cfiOne, cfiTwo) {
|
||
if(typeof cfiOne === 'string') {
|
||
cfiOne = new EPUBJS.EpubCFI(cfiOne);
|
||
}
|
||
if(typeof cfiTwo === 'string') {
|
||
cfiTwo = new EPUBJS.EpubCFI(cfiTwo);
|
||
}
|
||
// Compare Spine Positions
|
||
if(cfiOne.spinePos > cfiTwo.spinePos) {
|
||
return 1;
|
||
}
|
||
if(cfiOne.spinePos < cfiTwo.spinePos) {
|
||
return -1;
|
||
}
|
||
|
||
|
||
// Compare Each Step in the First item
|
||
for (var i = 0; i < cfiOne.steps.length; i++) {
|
||
if(!cfiTwo.steps[i]) {
|
||
return 1;
|
||
}
|
||
if(cfiOne.steps[i].index > cfiTwo.steps[i].index) {
|
||
return 1;
|
||
}
|
||
if(cfiOne.steps[i].index < cfiTwo.steps[i].index) {
|
||
return -1;
|
||
}
|
||
// Otherwise continue checking
|
||
}
|
||
|
||
// All steps in First present in Second
|
||
if(cfiOne.steps.length < cfiTwo.steps.length) {
|
||
return -1;
|
||
}
|
||
|
||
// Compare the charecter offset of the text node
|
||
if(cfiOne.characterOffset > cfiTwo.characterOffset) {
|
||
return 1;
|
||
}
|
||
if(cfiOne.characterOffset < cfiTwo.characterOffset) {
|
||
return -1;
|
||
}
|
||
|
||
// CFI's are equal
|
||
return 0;
|
||
};
|
||
|
||
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;
|
||
};
|
||
|
||
EPUBJS.EpubCFI.prototype.generateCfiFromTextNode = function(anchor, offset, base) {
|
||
var parent = anchor.parentNode;
|
||
var steps = this.pathTo(parent);
|
||
var path = this.generatePathComponent(steps);
|
||
var index = 1 + (2 * Array.prototype.indexOf.call(parent.childNodes, anchor));
|
||
return "epubcfi(" + base + "!" + path + "/"+index+":"+(offset || 0)+")";
|
||
};
|
||
|
||
EPUBJS.EpubCFI.prototype.generateCfiFromRangeAnchor = function(range, base) {
|
||
var anchor = range.anchorNode;
|
||
var offset = range.anchorOffset;
|
||
return this.generateCfiFromTextNode(anchor, offset, base);
|
||
};
|
||
|
||
EPUBJS.EpubCFI.prototype.generateCfiFromRange = function(range, base) {
|
||
var start, startElement, startSteps, startPath, startOffset, startIndex;
|
||
var end, endElement, endSteps, endPath, endOffset, endIndex;
|
||
|
||
start = range.startContainer;
|
||
|
||
if(start.nodeType === 3) { // text node
|
||
startElement = start.parentNode;
|
||
//startIndex = 1 + (2 * Array.prototype.indexOf.call(startElement.childNodes, start));
|
||
startIndex = 1 + (2 * EPUBJS.core.indexOfTextNode(start));
|
||
startSteps = this.pathTo(startElement);
|
||
} else if(range.collapsed) {
|
||
return this.generateCfiFromElement(start, base); // single element
|
||
} else {
|
||
startSteps = this.pathTo(start);
|
||
}
|
||
|
||
startPath = this.generatePathComponent(startSteps);
|
||
startOffset = range.startOffset;
|
||
|
||
if(!range.collapsed) {
|
||
end = range.endContainer;
|
||
|
||
if(end.nodeType === 3) { // text node
|
||
endElement = end.parentNode;
|
||
// endIndex = 1 + (2 * Array.prototype.indexOf.call(endElement.childNodes, end));
|
||
endIndex = 1 + (2 * EPUBJS.core.indexOfTextNode(end));
|
||
|
||
endSteps = this.pathTo(endElement);
|
||
} else {
|
||
endSteps = this.pathTo(end);
|
||
}
|
||
|
||
endPath = this.generatePathComponent(endSteps);
|
||
endOffset = range.endOffset;
|
||
|
||
// Remove steps present in startPath
|
||
endPath = endPath.replace(startPath, '');
|
||
|
||
if (endPath.length) {
|
||
endPath = endPath + "/";
|
||
}
|
||
|
||
return "epubcfi(" + base + "!" + startPath + "/" + startIndex + ":" + startOffset + "," + endPath + endIndex + ":" + endOffset + ")";
|
||
|
||
} else {
|
||
return "epubcfi(" + base + "!" + startPath + "/"+ startIndex +":"+ startOffset +")";
|
||
}
|
||
};
|
||
|
||
EPUBJS.EpubCFI.prototype.generateXpathFromSteps = function(steps) {
|
||
var xpath = [".", "*"];
|
||
|
||
steps.forEach(function(step){
|
||
var position = step.index + 1;
|
||
|
||
if(step.id){
|
||
xpath.push("*[position()=" + position + " and @id='" + step.id + "']");
|
||
} else if(step.type === "text") {
|
||
xpath.push("text()[" + position + "]");
|
||
} else {
|
||
xpath.push("*[" + position + "]");
|
||
}
|
||
});
|
||
|
||
return xpath.join("/");
|
||
};
|
||
|
||
|
||
EPUBJS.EpubCFI.prototype.generateRangeFromCfi = function(cfi, _doc) {
|
||
var doc = _doc || document;
|
||
var range = doc.createRange();
|
||
var lastStep;
|
||
var xpath;
|
||
var startContainer;
|
||
var textLength;
|
||
|
||
if(typeof cfi === 'string') {
|
||
cfi = this.parse(cfi);
|
||
}
|
||
|
||
// check spinePos
|
||
if(cfi.spinePos === -1) {
|
||
// Not a valid CFI
|
||
return false;
|
||
}
|
||
|
||
xpath = this.generateXpathFromSteps(cfi.steps);
|
||
|
||
// Get the terminal step
|
||
lastStep = cfi.steps[cfi.steps.length-1];
|
||
startContainer = doc.evaluate(xpath, doc, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue;
|
||
|
||
if(!startContainer) {
|
||
return null;
|
||
}
|
||
|
||
if(startContainer && cfi.characterOffset >= 0) {
|
||
textLength = startContainer.length;
|
||
|
||
if(cfi.characterOffset < textLength) {
|
||
range.setStart(startContainer, cfi.characterOffset);
|
||
range.setEnd(startContainer, textLength );
|
||
} else {
|
||
console.debug("offset greater than length:", cfi.characterOffset, textLength);
|
||
range.setStart(startContainer, textLength - 1 );
|
||
range.setEnd(startContainer, textLength );
|
||
}
|
||
} else if(startContainer) {
|
||
range.selectNode(startContainer);
|
||
}
|
||
// doc.defaultView.getSelection().addRange(range);
|
||
return range;
|
||
};
|
||
|
||
EPUBJS.EpubCFI.prototype.isCfiString = function(target) {
|
||
if(typeof target === "string" &&
|
||
target.indexOf("epubcfi(") === 0) {
|
||
return true;
|
||
}
|
||
|
||
return false;
|
||
};
|
||
|
||
EPUBJS.Navigation = function(_package, _request){
|
||
var navigation = this;
|
||
var parse = new EPUBJS.Parser();
|
||
var request = _request || EPUBJS.core.request;
|
||
|
||
this.package = _package;
|
||
this.toc = [];
|
||
this.tocByHref = {};
|
||
this.tocById = {};
|
||
|
||
if(_package.navPath) {
|
||
this.navUrl = _package.baseUrl + _package.navPath;
|
||
this.nav = {};
|
||
|
||
this.nav.load = function(_request){
|
||
var loading = new RSVP.defer();
|
||
var loaded = loading.promise;
|
||
|
||
request(navigation.navUrl, 'xml').then(function(xml){
|
||
navigation.toc = parse.nav(xml);
|
||
navigation.loaded(navigation.toc);
|
||
loading.resolve(navigation.toc);
|
||
});
|
||
|
||
return loaded;
|
||
};
|
||
|
||
}
|
||
|
||
if(_package.ncxPath) {
|
||
this.ncxUrl = _package.baseUrl + _package.ncxPath;
|
||
this.ncx = {};
|
||
|
||
this.ncx.load = function(_request){
|
||
var loading = new RSVP.defer();
|
||
var loaded = loading.promise;
|
||
|
||
request(navigation.ncxUrl, 'xml').then(function(xml){
|
||
navigation.toc = parse.ncx(xml);
|
||
navigation.loaded(navigation.toc);
|
||
loading.resolve(navigation.toc);
|
||
});
|
||
|
||
return loaded;
|
||
};
|
||
|
||
}
|
||
};
|
||
|
||
// Load the navigation
|
||
EPUBJS.Navigation.prototype.load = function(_request) {
|
||
var request = _request || EPUBJS.core.request;
|
||
var loading, loaded;
|
||
|
||
if(this.nav) {
|
||
loading = this.nav.load();
|
||
} else if(this.ncx) {
|
||
loading = this.ncx.load();
|
||
} else {
|
||
loaded = new RSVP.defer();
|
||
loaded.resolve([]);
|
||
loading = loaded.promise;
|
||
}
|
||
|
||
return loading;
|
||
|
||
};
|
||
|
||
EPUBJS.Navigation.prototype.loaded = 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
|
||
EPUBJS.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];
|
||
};
|
||
EPUBJS.Section = function(item){
|
||
this.idref = item.idref;
|
||
this.linear = item.linear;
|
||
this.properties = item.properties;
|
||
this.index = item.index;
|
||
this.href = item.href;
|
||
this.url = item.url;
|
||
this.next = item.next;
|
||
this.prev = item.prev;
|
||
|
||
this.epubcfi = new EPUBJS.EpubCFI();
|
||
this.cfiBase = item.cfiBase;
|
||
|
||
this.hooks = {};
|
||
this.hooks.replacements = new EPUBJS.Hook(this);
|
||
|
||
// Register replacements
|
||
this.hooks.replacements.register(this.replacements);
|
||
};
|
||
|
||
|
||
EPUBJS.Section.prototype.load = function(_request){
|
||
var request = _request || this.request || EPUBJS.core.request;
|
||
var loading = new RSVP.defer();
|
||
var loaded = loading.promise;
|
||
|
||
if(this.contents) {
|
||
loading.resolve(this.contents);
|
||
} else {
|
||
request(this.url, 'xml')
|
||
.then(function(xml){
|
||
var base;
|
||
var directory = EPUBJS.core.folder(this.url);
|
||
|
||
this.document = xml;
|
||
this.contents = xml.documentElement;
|
||
|
||
return this.hooks.replacements.trigger(this.document);
|
||
}.bind(this))
|
||
.then(function(){
|
||
loading.resolve(this.contents);
|
||
}.bind(this))
|
||
.catch(function(error){
|
||
loading.reject(error);
|
||
});
|
||
}
|
||
|
||
return loaded;
|
||
};
|
||
|
||
EPUBJS.Section.prototype.replacements = function(_document){
|
||
var task = new RSVP.defer();
|
||
var base = _document.createElement("base"); // TODO: check if exists
|
||
var head;
|
||
|
||
base.setAttribute("href", 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;
|
||
};
|
||
|
||
EPUBJS.Section.prototype.beforeSectionLoad = function(){
|
||
// Stub for a hook - replace me for now
|
||
};
|
||
|
||
EPUBJS.Section.prototype.render = function(_request){
|
||
var rendering = new RSVP.defer();
|
||
var rendered = rendering.promise;
|
||
|
||
this.load(_request).then(function(contents){
|
||
var serializer = new XMLSerializer();
|
||
var output = serializer.serializeToString(contents);
|
||
rendering.resolve(output);
|
||
})
|
||
.catch(function(error){
|
||
rendering.reject(error);
|
||
});
|
||
|
||
return rendered;
|
||
};
|
||
|
||
EPUBJS.Section.prototype.find = function(_query){
|
||
|
||
};
|
||
|
||
/**
|
||
* Reconciles the current chapters layout properies with
|
||
* the global layout properities.
|
||
* Takes: global layout settings object, chapter properties string
|
||
* Returns: Object with layout properties
|
||
*/
|
||
EPUBJS.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){
|
||
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;
|
||
};
|
||
|
||
EPUBJS.Section.prototype.cfiFromRange = function(_range) {
|
||
return this.epubcfi.generateCfiFromRange(_range, this.cfiBase);
|
||
};
|
||
|
||
EPUBJS.Section.prototype.cfiFromElement = function(el) {
|
||
return this.epubcfi.generateCfiFromElement(el, this.cfiBase);
|
||
};
|
||
|
||
EPUBJS.Spine = function(_request){
|
||
this.request = _request;
|
||
this.spineItems = [];
|
||
this.spineByHref = {};
|
||
this.spineById = {};
|
||
|
||
};
|
||
|
||
EPUBJS.Spine.prototype.load = function(_package) {
|
||
|
||
this.items = _package.spine;
|
||
this.manifest = _package.manifest;
|
||
this.spineNodeIndex = _package.spineNodeIndex;
|
||
this.baseUrl = _package.baseUrl || '';
|
||
this.length = this.items.length;
|
||
this.epubcfi = new EPUBJS.EpubCFI();
|
||
|
||
this.items.forEach(function(item, index){
|
||
var href, url;
|
||
var manifestItem = this.manifest[item.idref];
|
||
var spineItem;
|
||
item.cfiBase = this.epubcfi.generateChapterComponent(this.spineNodeIndex, item.index, item.idref);
|
||
|
||
if(manifestItem) {
|
||
item.href = manifestItem.href;
|
||
item.url = this.baseUrl + item.href;
|
||
|
||
|
||
if(manifestItem.properties.length){
|
||
item.properties.push.apply(item.properties, manifestItem.properties);
|
||
}
|
||
}
|
||
|
||
// if(index > 0) {
|
||
item.prev = function(){ return this.get(index-1); }.bind(this);
|
||
// }
|
||
|
||
// if(index+1 < this.items.length) {
|
||
item.next = function(){ return this.get(index+1); }.bind(this);
|
||
// }
|
||
|
||
spineItem = new EPUBJS.Section(item);
|
||
this.append(spineItem);
|
||
|
||
|
||
}.bind(this));
|
||
|
||
};
|
||
|
||
// book.spine.get();
|
||
// book.spine.get(1);
|
||
// book.spine.get("chap1.html");
|
||
// book.spine.get("#id1234");
|
||
EPUBJS.Spine.prototype.get = function(target) {
|
||
var index = 0;
|
||
|
||
if(this.epubcfi.isCfiString(target)) {
|
||
cfi = this.epubcfi.parse(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;
|
||
};
|
||
|
||
EPUBJS.Spine.prototype.append = function(section) {
|
||
var index = this.spineItems.length;
|
||
section.index = index;
|
||
|
||
this.spineItems.push(section);
|
||
|
||
this.spineByHref[section.href] = index;
|
||
this.spineById[section.idref] = index;
|
||
|
||
return index;
|
||
};
|
||
|
||
EPUBJS.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;
|
||
};
|
||
|
||
EPUBJS.Spine.prototype.insert = function(section, index) {
|
||
|
||
};
|
||
|
||
EPUBJS.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);
|
||
}
|
||
};
|
||
|
||
EPUBJS.Spine.prototype.each = function() {
|
||
return this.spineItems.forEach.apply(this.spineItems, arguments);
|
||
};
|
||
|
||
EPUBJS.replace = {};
|
||
EPUBJS.replace.links = function(view, renderer) {
|
||
|
||
var links = view.document.querySelectorAll("a[href]");
|
||
var replaceLinks = function(link){
|
||
var href = link.getAttribute("href");
|
||
var uri = new EPUBJS.core.uri(href);
|
||
|
||
|
||
if(uri.protocol){
|
||
|
||
link.setAttribute("target", "_blank");
|
||
|
||
}else{
|
||
|
||
// relative = EPUBJS.core.resolveUrl(directory, href);
|
||
// if(uri.fragment && !base) {
|
||
// link.onclick = function(){
|
||
// renderer.fragment(href);
|
||
// return false;
|
||
// };
|
||
// } else {
|
||
|
||
//}
|
||
|
||
if(href.indexOf("#") === 0) {
|
||
// do nothing with fragment yet
|
||
} else {
|
||
link.onclick = function(){
|
||
renderer.display(href);
|
||
return false;
|
||
};
|
||
}
|
||
|
||
}
|
||
};
|
||
|
||
for (var i = 0; i < links.length; i++) {
|
||
replaceLinks(links[i]);
|
||
}
|
||
|
||
|
||
};
|
||
EPUBJS.Book = function(_url, options){
|
||
// Promises
|
||
this.opening = new RSVP.defer();
|
||
this.opened = this.opening.promise;
|
||
this.isOpen = false;
|
||
|
||
this.url = undefined;
|
||
|
||
this.loading = {
|
||
manifest: new RSVP.defer(),
|
||
spine: new RSVP.defer(),
|
||
metadata: new RSVP.defer(),
|
||
cover: new RSVP.defer(),
|
||
navigation: new RSVP.defer(),
|
||
pageList: new RSVP.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
|
||
};
|
||
|
||
this.ready = RSVP.hash(this.loaded);
|
||
|
||
// Queue for methods used before opening
|
||
this.isRendered = false;
|
||
this._q = EPUBJS.core.queue(this);
|
||
|
||
this.request = this.requestMethod.bind(this);
|
||
|
||
this.spine = new EPUBJS.Spine(this.request);
|
||
this.locations = new EPUBJS.Locations(this.spine, this.request);
|
||
|
||
if(_url) {
|
||
this.open(_url);
|
||
}
|
||
};
|
||
|
||
EPUBJS.Book.prototype.open = function(_url){
|
||
var uri;
|
||
var parse = new EPUBJS.Parser();
|
||
var epubPackage;
|
||
var book = this;
|
||
var containerPath = "META-INF/container.xml";
|
||
var location;
|
||
|
||
if(!_url) {
|
||
this.opening.resolve(this);
|
||
return this.opened;
|
||
}
|
||
|
||
// Reuse parsed url or create a new uri object
|
||
if(typeof(_url) === "object") {
|
||
uri = _url;
|
||
} else {
|
||
uri = EPUBJS.core.uri(_url);
|
||
}
|
||
|
||
// Find path to the Container
|
||
if(uri.extension === "opf") {
|
||
// Direct link to package, no container
|
||
this.packageUrl = uri.href;
|
||
this.containerUrl = '';
|
||
|
||
if(uri.origin) {
|
||
this.url = uri.base;
|
||
} else if(window){
|
||
location = EPUBJS.core.uri(window.location.href);
|
||
this.url = EPUBJS.core.resolveUrl(location.base, uri.directory);
|
||
} else {
|
||
this.url = uri.directory;
|
||
}
|
||
|
||
epubPackage = this.request(this.packageUrl);
|
||
|
||
} else if(uri.extension === "epub" || uri.extension === "zip" ) {
|
||
// Book is archived
|
||
this.archived = true;
|
||
this.url = '';
|
||
}
|
||
|
||
// Find the path to the Package from the container
|
||
else if (!uri.extension) {
|
||
|
||
this.containerUrl = _url + containerPath;
|
||
|
||
epubPackage = this.request(this.containerUrl).
|
||
then(function(containerXml){
|
||
return parse.container(containerXml); // Container has path to content
|
||
}).
|
||
then(function(paths){
|
||
var packageUri = EPUBJS.core.uri(paths.packagePath);
|
||
book.packageUrl = _url + paths.packagePath;
|
||
book.encoding = paths.encoding;
|
||
|
||
// Set Url relative to the content
|
||
if(packageUri.origin) {
|
||
book.url = packageUri.base;
|
||
} else if(window){
|
||
location = EPUBJS.core.uri(window.location.href);
|
||
book.url = EPUBJS.core.resolveUrl(location.base, _url + packageUri.directory);
|
||
} else {
|
||
book.url = packageUri.directory;
|
||
}
|
||
|
||
return book.request(book.packageUrl);
|
||
}).catch(function(error) {
|
||
// handle errors in either of the two requests
|
||
console.error("Could not load book at: " + (this.packageUrl || this.containerPath));
|
||
book.trigger("book:loadFailed", (this.packageUrl || this.containerPath));
|
||
book.opening.reject(error);
|
||
});
|
||
}
|
||
|
||
|
||
epubPackage.then(function(packageXml) {
|
||
// Get package information from epub opf
|
||
book.unpack(packageXml);
|
||
|
||
// Resolve promises
|
||
book.loading.manifest.resolve(book.package.manifest);
|
||
book.loading.metadata.resolve(book.package.metadata);
|
||
book.loading.spine.resolve(book.spine);
|
||
book.loading.cover.resolve(book.cover);
|
||
|
||
this.isOpen = true;
|
||
|
||
// Clear queue of any waiting book request
|
||
|
||
// Resolve book opened promise
|
||
book.opening.resolve(book);
|
||
|
||
}).catch(function(error) {
|
||
// handle errors in parsing the book
|
||
console.error(error.message, error.stack);
|
||
book.opening.reject(error);
|
||
});
|
||
|
||
return this.opened;
|
||
};
|
||
|
||
EPUBJS.Book.prototype.unpack = function(packageXml){
|
||
var book = this,
|
||
parse = new EPUBJS.Parser();
|
||
|
||
book.package = parse.packageContents(packageXml); // Extract info from contents
|
||
book.package.baseUrl = book.url; // Provides a url base for resolving paths
|
||
|
||
this.spine.load(book.package);
|
||
|
||
book.navigation = new EPUBJS.Navigation(book.package, this.request);
|
||
book.navigation.load().then(function(toc){
|
||
book.toc = toc;
|
||
book.loading.navigation.resolve(book.toc);
|
||
});
|
||
|
||
// //-- Set Global Layout setting based on metadata
|
||
// MOVE TO RENDER
|
||
// book.globalLayoutProperties = book.parseLayoutProperties(book.package.metadata);
|
||
|
||
book.cover = book.url + book.package.coverPath;
|
||
};
|
||
|
||
// Alias for book.spine.get
|
||
EPUBJS.Book.prototype.section = function(target) {
|
||
return this.spine.get(target);
|
||
};
|
||
|
||
// Sugar to render a book
|
||
EPUBJS.Book.prototype.renderTo = function(element, options) {
|
||
var renderer = (options && options.method) ?
|
||
options.method.charAt(0).toUpperCase() + options.method.substr(1) :
|
||
"Rendition";
|
||
|
||
this.rendition = new EPUBJS[renderer](this, options);
|
||
this.rendition.attachTo(element);
|
||
return this.rendition;
|
||
};
|
||
|
||
EPUBJS.Book.prototype.requestMethod = function(_url) {
|
||
// Switch request methods
|
||
if(this.archived) {
|
||
// TODO: handle archived
|
||
} else {
|
||
return EPUBJS.core.request(_url, 'xml', this.requestCredentials, this.requestHeaders);
|
||
}
|
||
|
||
};
|
||
|
||
EPUBJS.Book.prototype.setRequestCredentials = function(_credentials) {
|
||
this.requestCredentials = _credentials;
|
||
};
|
||
|
||
EPUBJS.Book.prototype.setRequestHeaders = function(_headers) {
|
||
this.requestHeaders = _headers;
|
||
};
|
||
|
||
//-- Enable binding events to book
|
||
RSVP.EventTarget.mixin(EPUBJS.Book.prototype);
|
||
|
||
//-- Handle RSVP Errors
|
||
RSVP.on('error', function(event) {
|
||
//console.error(event, event.detail);
|
||
});
|
||
|
||
RSVP.configure('instrument', true); //-- true | will logging out all RSVP rejections
|
||
// RSVP.on('created', listener);
|
||
// RSVP.on('chained', listener);
|
||
// RSVP.on('fulfilled', listener);
|
||
RSVP.on('rejected', function(event){
|
||
console.error(event.detail.message, event.detail.stack);
|
||
});
|
||
|
||
EPUBJS.View = function(section, options) {
|
||
this.settings = options || {};
|
||
|
||
this.id = "epubjs-view:" + EPUBJS.core.uuid();
|
||
this.section = section;
|
||
this.index = section.index;
|
||
|
||
this.element = document.createElement('div');
|
||
this.element.classList.add("epub-view");
|
||
|
||
|
||
// this.element.style.minHeight = "100px";
|
||
this.element.style.height = "0px";
|
||
this.element.style.width = "0px";
|
||
this.element.style.overflow = "hidden";
|
||
|
||
this.added = false;
|
||
this.displayed = false;
|
||
this.rendered = false;
|
||
|
||
//this.width = 0;
|
||
//this.height = 0;
|
||
|
||
// Blank Cfi for Parsing
|
||
this.epubcfi = new EPUBJS.EpubCFI();
|
||
|
||
if(this.settings.axis && this.settings.axis == "horizontal"){
|
||
this.element.style.display = "inline-block";
|
||
} else {
|
||
this.element.style.display = "block";
|
||
}
|
||
|
||
// Dom events to listen for
|
||
this.listenedEvents = ["keydown", "keyup", "keypressed", "mouseup", "mousedown", "click", "touchend", "touchstart"];
|
||
|
||
};
|
||
|
||
EPUBJS.View.prototype.create = function() {
|
||
|
||
if(this.iframe) {
|
||
return this.iframe;
|
||
}
|
||
|
||
this.iframe = document.createElement('iframe');
|
||
this.iframe.id = this.id;
|
||
this.iframe.scrolling = "no"; // Might need to be removed: breaks ios width calculations
|
||
this.iframe.style.overflow = "hidden";
|
||
this.iframe.seamless = "seamless";
|
||
// Back up if seamless isn't supported
|
||
this.iframe.style.border = "none";
|
||
|
||
this.resizing = true;
|
||
|
||
// this.iframe.style.display = "none";
|
||
this.element.style.visibility = "hidden";
|
||
this.iframe.style.visibility = "hidden";
|
||
|
||
this.iframe.style.width = "0";
|
||
this.iframe.style.height = "0";
|
||
this._width = 0;
|
||
this._height = 0;
|
||
|
||
this.element.appendChild(this.iframe);
|
||
this.added = true;
|
||
|
||
this.elementBounds = EPUBJS.core.bounds(this.element);
|
||
|
||
// if(width || height){
|
||
// this.resize(width, height);
|
||
// } else if(this.width && this.height){
|
||
// this.resize(this.width, this.height);
|
||
// } else {
|
||
// this.iframeBounds = EPUBJS.core.bounds(this.iframe);
|
||
// }
|
||
|
||
// Firefox has trouble with baseURI and srcdoc
|
||
// Disabled for now
|
||
/*
|
||
if(!!("srcdoc" in this.iframe)) {
|
||
this.supportsSrcdoc = true;
|
||
} else {
|
||
this.supportsSrcdoc = false;
|
||
}
|
||
*/
|
||
this.supportsSrcdoc = false;
|
||
|
||
return this.iframe;
|
||
};
|
||
|
||
|
||
EPUBJS.View.prototype.lock = function(what, width, height) {
|
||
|
||
var elBorders = EPUBJS.core.borders(this.element);
|
||
var iframeBorders;
|
||
|
||
if(this.iframe) {
|
||
iframeBorders = EPUBJS.core.borders(this.iframe);
|
||
} else {
|
||
iframeBorders = {width: 0, height: 0};
|
||
}
|
||
|
||
if(what == "width" && EPUBJS.core.isNumber(width)){
|
||
this.lockedWidth = width - elBorders.width - iframeBorders.width;
|
||
this.resize(this.lockedWidth, width); // width keeps ratio correct
|
||
}
|
||
|
||
if(what == "height" && EPUBJS.core.isNumber(height)){
|
||
this.lockedHeight = height - elBorders.height - iframeBorders.height;
|
||
this.resize(width, this.lockedHeight);
|
||
}
|
||
|
||
if(what === "both" &&
|
||
EPUBJS.core.isNumber(width) &&
|
||
EPUBJS.core.isNumber(height)){
|
||
|
||
this.lockedWidth = width - elBorders.width - iframeBorders.width;
|
||
this.lockedHeight = height - elBorders.height - iframeBorders.height;
|
||
|
||
this.resize(this.lockedWidth, this.lockedHeight);
|
||
}
|
||
|
||
if(this.displayed && this.iframe) {
|
||
|
||
this.layout();
|
||
this.expand();
|
||
|
||
}
|
||
|
||
|
||
|
||
};
|
||
|
||
EPUBJS.View.prototype.expand = function(force) {
|
||
var width = this.lockedWidth;
|
||
var height = this.lockedHeight;
|
||
|
||
var textWidth, textHeight;
|
||
// console.log("expanding a")
|
||
if(!this.iframe || this._expanding) return;
|
||
|
||
this._expanding = true;
|
||
|
||
// Expand Horizontally
|
||
if(height && !width) {
|
||
// Get the width of the text
|
||
textWidth = this.textWidth();
|
||
// Check if the textWidth has changed
|
||
if(textWidth != this._textWidth){
|
||
// Get the contentWidth by resizing the iframe
|
||
// Check with a min reset of the textWidth
|
||
width = this.contentWidth(textWidth);
|
||
// Save the textWdith
|
||
this._textWidth = textWidth;
|
||
// Save the contentWidth
|
||
this._contentWidth = width;
|
||
} else {
|
||
// Otherwise assume content height hasn't changed
|
||
width = this._contentWidth;
|
||
}
|
||
}
|
||
|
||
// Expand Vertically
|
||
if(width && !height) {
|
||
textHeight = this.textHeight();
|
||
if(textHeight != this._textHeight){
|
||
height = this.contentHeight(textHeight);
|
||
this._textHeight = textHeight;
|
||
this._contentHeight = height;
|
||
} else {
|
||
height = this._contentHeight;
|
||
}
|
||
}
|
||
|
||
// 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;
|
||
};
|
||
|
||
EPUBJS.View.prototype.contentWidth = function(min) {
|
||
var prev;
|
||
var width;
|
||
|
||
// Save previous width
|
||
prev = this.iframe.style.width;
|
||
// Set the iframe size to min, width will only ever be greater
|
||
// Will preserve the aspect ratio
|
||
this.iframe.style.width = (min || 0) + "px";
|
||
// Get the scroll overflow width
|
||
width = this.document.body.scrollWidth;
|
||
// Reset iframe size back
|
||
this.iframe.style.width = prev;
|
||
return width;
|
||
};
|
||
|
||
EPUBJS.View.prototype.contentHeight = function(min) {
|
||
var prev;
|
||
var height;
|
||
|
||
prev = this.iframe.style.height;
|
||
this.iframe.style.height = (min || 0) + "px";
|
||
height = this.document.body.scrollHeight;
|
||
this.iframe.style.height = prev;
|
||
return height;
|
||
};
|
||
|
||
EPUBJS.View.prototype.textWidth = function() {
|
||
var width;
|
||
var range = this.document.createRange();
|
||
|
||
// Select the contents of frame
|
||
range.selectNodeContents(this.document.body);
|
||
|
||
// get the width of the text content
|
||
width = range.getBoundingClientRect().width;
|
||
return width;
|
||
|
||
};
|
||
|
||
EPUBJS.View.prototype.textHeight = function() {
|
||
var height;
|
||
var range = this.document.createRange();
|
||
|
||
range.selectNodeContents(this.document.body);
|
||
|
||
height = range.getBoundingClientRect().height;
|
||
return height;
|
||
};
|
||
|
||
EPUBJS.View.prototype.resize = function(width, height) {
|
||
|
||
if(!this.iframe) return;
|
||
|
||
if(EPUBJS.core.isNumber(width)){
|
||
this.iframe.style.width = width + "px";
|
||
this._width = width;
|
||
}
|
||
|
||
if(EPUBJS.core.isNumber(height)){
|
||
this.iframe.style.height = height + "px";
|
||
this._height = height;
|
||
}
|
||
|
||
this.iframeBounds = EPUBJS.core.bounds(this.iframe);
|
||
|
||
this.reframe(this.iframeBounds.width, this.iframeBounds.height);
|
||
|
||
};
|
||
|
||
EPUBJS.View.prototype.reframe = function(width, height) {
|
||
//var prevBounds;
|
||
|
||
if(!this.displayed) {
|
||
this._needsReframe = true;
|
||
return;
|
||
}
|
||
|
||
if(EPUBJS.core.isNumber(width)){
|
||
this.element.style.width = width + "px";
|
||
}
|
||
|
||
if(EPUBJS.core.isNumber(height)){
|
||
this.element.style.height = height + "px";
|
||
}
|
||
|
||
this.prevBounds = this.elementBounds;
|
||
|
||
this.elementBounds = EPUBJS.core.bounds(this.element);
|
||
|
||
this.trigger("resized", {
|
||
width: this.elementBounds.width,
|
||
height: this.elementBounds.height,
|
||
widthDelta: this.elementBounds.width - this.prevBounds.width,
|
||
heightDelta: this.elementBounds.height - this.prevBounds.height,
|
||
});
|
||
|
||
};
|
||
|
||
EPUBJS.View.prototype.resized = function(e) {
|
||
/*
|
||
if (!this.resizing) {
|
||
if(this.iframe) {
|
||
// this.expand();
|
||
}
|
||
} else {
|
||
this.resizing = false;
|
||
}*/
|
||
|
||
};
|
||
|
||
EPUBJS.View.prototype.render = function(_request) {
|
||
|
||
// if(this.rendering){
|
||
// return this.displayed;
|
||
// }
|
||
|
||
this.rendering = true;
|
||
// this.displayingDefer = new RSVP.defer();
|
||
// this.displayedPromise = this.displaying.promise;
|
||
|
||
return this.section.render(_request)
|
||
.then(function(contents){
|
||
return this.load(contents);
|
||
}.bind(this));
|
||
};
|
||
|
||
EPUBJS.View.prototype.load = function(contents) {
|
||
var loading = new RSVP.defer();
|
||
var loaded = loading.promise;
|
||
|
||
if(!this.iframe) {
|
||
loading.reject(new Error("No Iframe Available"));
|
||
return loaded;
|
||
}
|
||
|
||
this.iframe.onload = function(event) {
|
||
|
||
this.window = this.iframe.contentWindow;
|
||
this.document = this.iframe.contentDocument;
|
||
this.rendering = false;
|
||
loading.resolve(this);
|
||
|
||
}.bind(this);
|
||
|
||
if(this.supportsSrcdoc){
|
||
this.iframe.srcdoc = contents;
|
||
} else {
|
||
|
||
this.document = this.iframe.contentDocument;
|
||
|
||
if(!this.document) {
|
||
loading.reject(new Error("No Document Available"));
|
||
return loaded;
|
||
}
|
||
|
||
this.document.open();
|
||
this.document.write(contents);
|
||
this.document.close();
|
||
|
||
}
|
||
|
||
return loaded;
|
||
};
|
||
|
||
|
||
EPUBJS.View.prototype.layout = function(layoutFunc) {
|
||
|
||
this.iframe.style.display = "inline-block";
|
||
|
||
// Reset Body Styles
|
||
this.document.body.style.margin = "0";
|
||
//this.document.body.style.display = "inline-block";
|
||
//this.document.documentElement.style.width = "auto";
|
||
|
||
if(layoutFunc){
|
||
layoutFunc(this);
|
||
}
|
||
|
||
this.onLayout(this);
|
||
|
||
};
|
||
|
||
EPUBJS.View.prototype.onLayout = function(view) {
|
||
// stub
|
||
};
|
||
|
||
EPUBJS.View.prototype.listeners = function() {
|
||
/*
|
||
setTimeout(function(){
|
||
this.window.addEventListener("resize", this.resized.bind(this), false);
|
||
}.bind(this), 10); // Wait to listen for resize events
|
||
*/
|
||
|
||
// Wait for fonts to load to finish
|
||
// http://dev.w3.org/csswg/css-font-loading/
|
||
// Not implemented fully except in chrome
|
||
|
||
if(this.document.fonts && this.document.fonts.status === "loading") {
|
||
// console.log("fonts unloaded");
|
||
this.document.fonts.onloadingdone = function(){
|
||
// console.log("loaded fonts");
|
||
this.expand();
|
||
}.bind(this);
|
||
}
|
||
|
||
if(this.section.properties.indexOf("scripted") > -1){
|
||
this.observer = this.observe(this.document.body);
|
||
}
|
||
|
||
this.imageLoadListeners();
|
||
|
||
this.mediaQueryListeners();
|
||
|
||
// this.resizeListenters();
|
||
|
||
this.addEventListeners();
|
||
|
||
this.addSelectionListeners();
|
||
};
|
||
|
||
EPUBJS.View.prototype.removeListeners = function() {
|
||
|
||
this.removeEventListeners();
|
||
|
||
this.removeSelectionListeners();
|
||
};
|
||
|
||
EPUBJS.View.prototype.resizeListenters = function() {
|
||
// Test size again
|
||
clearTimeout(this.expanding);
|
||
this.expanding = setTimeout(this.expand.bind(this), 350);
|
||
};
|
||
|
||
//https://github.com/tylergaw/media-query-events/blob/master/js/mq-events.js
|
||
EPUBJS.View.prototype.mediaQueryListeners = function() {
|
||
var sheets = this.document.styleSheets;
|
||
var mediaChangeHandler = function(m){
|
||
if(m.matches && !this._expanding) {
|
||
setTimeout(this.expand.bind(this), 1);
|
||
// this.expand();
|
||
}
|
||
}.bind(this);
|
||
|
||
for (var i = 0; i < sheets.length; i += 1) {
|
||
var rules = sheets[i].cssRules;
|
||
if(!rules) return; // Stylesheets changed
|
||
for (var j = 0; j < rules.length; j += 1) {
|
||
//if (rules[j].constructor === CSSMediaRule) {
|
||
if(rules[j].media){
|
||
var mql = this.window.matchMedia(rules[j].media.mediaText);
|
||
mql.addListener(mediaChangeHandler);
|
||
//mql.onchange = mediaChangeHandler;
|
||
}
|
||
}
|
||
}
|
||
};
|
||
|
||
EPUBJS.View.prototype.observe = function(target) {
|
||
var renderer = this;
|
||
|
||
// create an observer instance
|
||
var observer = new MutationObserver(function(mutations) {
|
||
if(renderer._expanding) {
|
||
renderer.expand();
|
||
}
|
||
// mutations.forEach(function(mutation) {
|
||
// console.log(mutation);
|
||
// });
|
||
});
|
||
|
||
// configuration of the observer:
|
||
var config = { attributes: true, childList: true, characterData: true, subtree: true };
|
||
|
||
// pass in the target node, as well as the observer options
|
||
observer.observe(target, config);
|
||
|
||
return observer;
|
||
};
|
||
|
||
// EPUBJS.View.prototype.appendTo = function(element) {
|
||
// this.element = element;
|
||
// this.element.appendChild(this.iframe);
|
||
// };
|
||
//
|
||
// EPUBJS.View.prototype.prependTo = function(element) {
|
||
// this.element = element;
|
||
// element.insertBefore(this.iframe, element.firstChild);
|
||
// };
|
||
|
||
EPUBJS.View.prototype.imageLoadListeners = function(target) {
|
||
var images = this.document.body.querySelectorAll("img");
|
||
var img;
|
||
for (var i = 0; i < images.length; i++) {
|
||
img = images[i];
|
||
|
||
if (typeof img.naturalWidth !== "undefined" &&
|
||
img.naturalWidth === 0) {
|
||
img.onload = this.expand.bind(this);
|
||
}
|
||
}
|
||
};
|
||
|
||
EPUBJS.View.prototype.display = function() {
|
||
var displayed = new RSVP.defer();
|
||
|
||
this.displayed = true;
|
||
|
||
this.layout();
|
||
|
||
this.listeners();
|
||
|
||
this.expand();
|
||
|
||
this.trigger("displayed", this);
|
||
this.onDisplayed(this);
|
||
|
||
displayed.resolve(this);
|
||
|
||
return displayed.promise;
|
||
};
|
||
|
||
EPUBJS.View.prototype.show = function() {
|
||
|
||
this.element.style.visibility = "visible";
|
||
|
||
if(this.iframe){
|
||
this.iframe.style.visibility = "visible";
|
||
}
|
||
|
||
this.trigger("shown", this);
|
||
};
|
||
|
||
EPUBJS.View.prototype.hide = function() {
|
||
// this.iframe.style.display = "none";
|
||
this.element.style.visibility = "hidden";
|
||
this.iframe.style.visibility = "hidden";
|
||
|
||
this.stopExpanding = true;
|
||
this.trigger("hidden", this);
|
||
};
|
||
|
||
EPUBJS.View.prototype.position = function() {
|
||
return this.element.getBoundingClientRect();
|
||
};
|
||
|
||
EPUBJS.View.prototype.onDisplayed = function(view) {
|
||
// Stub, override with a custom functions
|
||
};
|
||
|
||
EPUBJS.View.prototype.bounds = function() {
|
||
if(!this.elementBounds) {
|
||
this.elementBounds = EPUBJS.core.bounds(this.element);
|
||
}
|
||
return this.elementBounds;
|
||
};
|
||
|
||
EPUBJS.View.prototype.destroy = function() {
|
||
// Stop observing
|
||
if(this.observer) {
|
||
this.observer.disconnect();
|
||
}
|
||
|
||
if(this.displayed){
|
||
this.removeListeners();
|
||
|
||
this.stopExpanding = true;
|
||
this.element.removeChild(this.iframe);
|
||
this.displayed = false;
|
||
this.iframe = null;
|
||
|
||
this._textWidth = null;
|
||
this._textHeight = null;
|
||
this._width = null;
|
||
this._height = null;
|
||
}
|
||
// this.element.style.height = "0px";
|
||
// this.element.style.width = "0px";
|
||
};
|
||
|
||
EPUBJS.View.prototype.root = function() {
|
||
if(!this.document) return null;
|
||
return this.document.documentElement;
|
||
};
|
||
|
||
EPUBJS.View.prototype.locationOf = function(target) {
|
||
var parentPos = this.iframe.getBoundingClientRect();
|
||
var targetPos = {"left": 0, "top": 0};
|
||
|
||
if(!this.document) return;
|
||
|
||
if(this.epubcfi.isCfiString(target)) {
|
||
cfi = this.epubcfi.parse(target);
|
||
|
||
if(typeof document.evaluate === 'undefined') {
|
||
marker = this.epubcfi.addMarker(cfi, this.document);
|
||
if(marker) {
|
||
// Must Clean up Marker before going to page
|
||
this.epubcfi.removeMarker(marker, this.document);
|
||
|
||
targetPos = marker.getBoundingClientRect();
|
||
}
|
||
} else {
|
||
range = this.epubcfi.generateRangeFromCfi(cfi, this.document);
|
||
if(range) {
|
||
targetPos = range.getBoundingClientRect();
|
||
}
|
||
}
|
||
} else if(typeof target === "string" &&
|
||
target.indexOf("#") > -1) {
|
||
|
||
id = target.substring(target.indexOf("#")+1);
|
||
el = this.document.getElementById(id);
|
||
|
||
if(el) {
|
||
targetPos = el.getBoundingClientRect();
|
||
}
|
||
}
|
||
|
||
return {
|
||
"left": window.scrollX + parentPos.left + targetPos.left,
|
||
"top": window.scrollY + parentPos.top + targetPos.top
|
||
};
|
||
};
|
||
|
||
EPUBJS.View.prototype.addCss = function(src) {
|
||
return new RSVP.Promise(function(resolve, reject){
|
||
var $stylesheet;
|
||
var ready = false;
|
||
|
||
if(!this.document) {
|
||
resolve(false);
|
||
return;
|
||
}
|
||
|
||
$stylesheet = this.document.createElement('link');
|
||
$stylesheet.type = 'text/css';
|
||
$stylesheet.rel = "stylesheet";
|
||
$stylesheet.href = src;
|
||
$stylesheet.onload = $stylesheet.onreadystatechange = function() {
|
||
if ( !ready && (!this.readyState || this.readyState == 'complete') ) {
|
||
ready = true;
|
||
// Let apply
|
||
setTimeout(function(){
|
||
resolve(true);
|
||
}, 1);
|
||
}
|
||
};
|
||
|
||
this.document.head.appendChild($stylesheet);
|
||
|
||
}.bind(this));
|
||
};
|
||
|
||
// https://developer.mozilla.org/en-US/docs/Web/API/CSSStyleSheet/insertRule
|
||
EPUBJS.View.prototype.addStylesheetRules = function(rules) {
|
||
var styleEl;
|
||
var styleSheet;
|
||
|
||
if(!this.document) return;
|
||
|
||
styleEl = this.document.createElement('style');
|
||
|
||
// Append style element to head
|
||
this.document.head.appendChild(styleEl);
|
||
|
||
// Grab style sheet
|
||
styleSheet = styleEl.sheet;
|
||
|
||
for (var i = 0, rl = rules.length; i < rl; i++) {
|
||
var j = 1, rule = rules[i], selector = rules[i][0], propStr = '';
|
||
// If the second argument of a rule is an array of arrays, correct our variables.
|
||
if (Object.prototype.toString.call(rule[1][0]) === '[object Array]') {
|
||
rule = rule[1];
|
||
j = 0;
|
||
}
|
||
|
||
for (var pl = rule.length; j < pl; j++) {
|
||
var prop = rule[j];
|
||
propStr += prop[0] + ':' + prop[1] + (prop[2] ? ' !important' : '') + ';\n';
|
||
}
|
||
|
||
// Insert CSS Rule
|
||
styleSheet.insertRule(selector + '{' + propStr + '}', styleSheet.cssRules.length);
|
||
}
|
||
};
|
||
|
||
EPUBJS.View.prototype.addScript = function(src) {
|
||
|
||
return new RSVP.Promise(function(resolve, reject){
|
||
var $script;
|
||
var ready = false;
|
||
|
||
if(!this.document) {
|
||
resolve(false);
|
||
return;
|
||
}
|
||
|
||
$script = this.document.createElement('script');
|
||
$script.type = 'text/javascript';
|
||
$script.async = true;
|
||
$script.src = src;
|
||
$script.onload = $script.onreadystatechange = function() {
|
||
if ( !ready && (!this.readyState || this.readyState == 'complete') ) {
|
||
ready = true;
|
||
setTimeout(function(){
|
||
resolve(true);
|
||
}, 1);
|
||
}
|
||
};
|
||
|
||
this.document.head.appendChild($script);
|
||
|
||
}.bind(this));
|
||
};
|
||
|
||
EPUBJS.View.prototype.addEventListeners = function(){
|
||
if(!this.document) {
|
||
return;
|
||
}
|
||
this.listenedEvents.forEach(function(eventName){
|
||
this.document.addEventListener(eventName, this.triggerEvent.bind(this), false);
|
||
}, this);
|
||
|
||
};
|
||
|
||
EPUBJS.View.prototype.removeEventListeners = function(){
|
||
if(!this.document) {
|
||
return;
|
||
}
|
||
this.listenedEvents.forEach(function(eventName){
|
||
this.document.removeEventListener(eventName, this.triggerEvent, false);
|
||
}, this);
|
||
|
||
};
|
||
|
||
// Pass browser events
|
||
EPUBJS.View.prototype.triggerEvent = function(e){
|
||
this.trigger(e.type, e);
|
||
};
|
||
|
||
EPUBJS.View.prototype.addSelectionListeners = function(){
|
||
if(!this.document) {
|
||
return;
|
||
}
|
||
this.document.addEventListener("selectionchange", this.onSelectionChange.bind(this), false);
|
||
};
|
||
|
||
EPUBJS.View.prototype.removeSelectionListeners = function(){
|
||
if(!this.document) {
|
||
return;
|
||
}
|
||
this.document.removeEventListener("selectionchange", this.onSelectionChange, false);
|
||
};
|
||
|
||
EPUBJS.View.prototype.onSelectionChange = function(e){
|
||
if (this.selectionEndTimeout) {
|
||
clearTimeout(this.selectionEndTimeout);
|
||
}
|
||
this.selectionEndTimeout = setTimeout(function() {
|
||
this.selectedRange = this.window.getSelection();
|
||
this.trigger("selected", this.selectedRange);
|
||
}.bind(this), 500);
|
||
};
|
||
|
||
RSVP.EventTarget.mixin(EPUBJS.View.prototype);
|
||
|
||
EPUBJS.Views = function(container) {
|
||
this.container = container;
|
||
this._views = [];
|
||
this.length = 0;
|
||
this.hidden = false;
|
||
};
|
||
|
||
EPUBJS.Views.prototype.first = function() {
|
||
return this._views[0];
|
||
};
|
||
|
||
EPUBJS.Views.prototype.last = function() {
|
||
return this._views[this._views.length-1];
|
||
};
|
||
|
||
EPUBJS.Views.prototype.each = function() {
|
||
return this._views.forEach.apply(this._views, arguments);
|
||
};
|
||
|
||
EPUBJS.Views.prototype.indexOf = function(view) {
|
||
return this._views.indexOf(view);
|
||
};
|
||
|
||
EPUBJS.Views.prototype.slice = function() {
|
||
return this._views.slice.apply(this._views, arguments);
|
||
};
|
||
|
||
EPUBJS.Views.prototype.get = function(i) {
|
||
return this._views[i];
|
||
};
|
||
|
||
EPUBJS.Views.prototype.append = function(view){
|
||
this._views.push(view);
|
||
this.container.appendChild(view.element);
|
||
this.length++;
|
||
return view;
|
||
};
|
||
|
||
EPUBJS.Views.prototype.prepend = function(view){
|
||
this._views.unshift(view);
|
||
this.container.insertBefore(view.element, this.container.firstChild);
|
||
this.length++;
|
||
return view;
|
||
};
|
||
|
||
EPUBJS.Views.prototype.insert = function(view, index) {
|
||
this._views.splice(index, 0, view);
|
||
|
||
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;
|
||
};
|
||
|
||
EPUBJS.Views.prototype.remove = function(view) {
|
||
var index = this._views.indexOf(view);
|
||
|
||
if(index > -1) {
|
||
this._views.splice(index, 1);
|
||
}
|
||
|
||
|
||
this.destroy(view);
|
||
|
||
this.length--;
|
||
};
|
||
|
||
EPUBJS.Views.prototype.destroy = function(view) {
|
||
view.off("resized");
|
||
|
||
if(view.displayed){
|
||
view.destroy();
|
||
}
|
||
|
||
this.container.removeChild(view.element);
|
||
view = null;
|
||
};
|
||
|
||
// Iterators
|
||
|
||
EPUBJS.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._views = [];
|
||
this.length = 0;
|
||
};
|
||
|
||
EPUBJS.Views.prototype.find = function(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;
|
||
}
|
||
}
|
||
|
||
};
|
||
|
||
EPUBJS.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){
|
||
displayed.push(view);
|
||
}
|
||
}
|
||
return displayed;
|
||
};
|
||
|
||
EPUBJS.Views.prototype.show = function(){
|
||
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;
|
||
};
|
||
|
||
EPUBJS.Views.prototype.hide = function(){
|
||
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;
|
||
};
|
||
|
||
EPUBJS.Layout = EPUBJS.Layout || {};
|
||
|
||
EPUBJS.Layout.Reflowable = function(){
|
||
|
||
};
|
||
|
||
EPUBJS.Layout.Reflowable.prototype.calculate = function(_width, _height, _gap, _devisor){
|
||
|
||
var divisor = _devisor || 1;
|
||
|
||
//-- Check the width and create even width columns
|
||
var fullWidth = Math.floor(_width);
|
||
var width = (fullWidth % 2 === 0) ? fullWidth : fullWidth - 1;
|
||
|
||
var section = Math.floor(width / 8);
|
||
var gap = (_gap >= 0) ? _gap : ((section % 2 === 0) ? section : section - 1);
|
||
|
||
var colWidth;
|
||
var spreadWidth;
|
||
var delta;
|
||
|
||
//-- Double Page
|
||
if(divisor > 1) {
|
||
colWidth = Math.floor((width - gap) / divisor);
|
||
} else {
|
||
colWidth = width;
|
||
}
|
||
|
||
spreadWidth = colWidth * divisor;
|
||
|
||
delta = (colWidth + gap) * divisor;
|
||
|
||
|
||
|
||
this.columnAxis = EPUBJS.core.prefixed('columnAxis');
|
||
this.columnGap = EPUBJS.core.prefixed('columnGap');
|
||
this.columnWidth = EPUBJS.core.prefixed('columnWidth');
|
||
this.columnFill = EPUBJS.core.prefixed('columnFill');
|
||
|
||
this.width = width;
|
||
this.height = _height;
|
||
this.spread = spreadWidth;
|
||
this.delta = delta;
|
||
|
||
this.column = colWidth;
|
||
this.gap = gap;
|
||
this.divisor = divisor;
|
||
|
||
};
|
||
|
||
EPUBJS.Layout.Reflowable.prototype.format = function(view){
|
||
|
||
var $doc = view.document.documentElement;
|
||
var $body = view.document.body;//view.document.querySelector("body");
|
||
|
||
$doc.style.overflow = "hidden";
|
||
|
||
// Must be set to the new calculated width or the columns will be off
|
||
// $body.style.width = this.width + "px";
|
||
$doc.style.width = this.width + "px";
|
||
|
||
//-- Adjust height
|
||
$body.style.height = this.height + "px";
|
||
|
||
//-- Add columns
|
||
$body.style[this.columnAxis] = "horizontal";
|
||
$body.style[this.columnFill] = "auto";
|
||
$body.style[this.columnGap] = this.gap+"px";
|
||
$body.style[this.columnWidth] = this.column+"px";
|
||
|
||
// Add extra padding for the gap between this and the next view
|
||
view.iframe.style.marginRight = this.gap+"px";
|
||
};
|
||
|
||
EPUBJS.Layout.Reflowable.prototype.count = function(view) {
|
||
var totalWidth = view.root().scrollWidth;
|
||
var spreads = Math.ceil(totalWidth / this.spread);
|
||
|
||
return {
|
||
spreads : spreads,
|
||
pages : spreads * this.divisor
|
||
};
|
||
};
|
||
|
||
EPUBJS.Layout.Fixed = function(_width, _height){
|
||
|
||
};
|
||
|
||
EPUBJS.Layout.Fixed.prototype.calculate = function(_width, _height){
|
||
|
||
};
|
||
|
||
EPUBJS.Layout.Fixed.prototype.format = function(view){
|
||
var width, height;
|
||
|
||
var $doc = view.document.documentElement;
|
||
var $viewport = documentElement.querySelector("[name=viewport");
|
||
|
||
/**
|
||
* check for the viewport size
|
||
* <meta name="viewport" content="width=1024,height=697" />
|
||
*/
|
||
if($viewport && $viewport.hasAttribute("content")) {
|
||
content = $viewport.getAttribute("content");
|
||
contents = content.split(',');
|
||
if(contents[0]){
|
||
width = contents[0].replace("width=", '');
|
||
}
|
||
if(contents[1]){
|
||
height = contents[1].replace("height=", '');
|
||
}
|
||
}
|
||
|
||
//-- Adjust width and height
|
||
// $doc.style.width = width + "px" || "auto";
|
||
// $doc.style.height = height + "px" || "auto";
|
||
view.resize(width, height);
|
||
|
||
//-- Scroll
|
||
$doc.style.overflow = "auto";
|
||
|
||
};
|
||
|
||
EPUBJS.Layout.Fixed.prototype.count = function(){
|
||
return {
|
||
spreads : 1,
|
||
pages : 1
|
||
};
|
||
};
|
||
|
||
EPUBJS.Layout.Scroll = function(){
|
||
|
||
};
|
||
|
||
EPUBJS.Layout.Scroll.prototype.calculate = function(_width, _height){
|
||
this.spread = _width;
|
||
this.column = _width;
|
||
this.gap = 0;
|
||
};
|
||
|
||
EPUBJS.Layout.Scroll.prototype.format = function(view){
|
||
|
||
var $doc = view.document.documentElement;
|
||
|
||
$doc.style.width = "auto";
|
||
$doc.style.height = "auto";
|
||
|
||
};
|
||
|
||
EPUBJS.Layout.Scroll.prototype.count = function(){
|
||
return {
|
||
spreads : 1,
|
||
pages : 1
|
||
};
|
||
};
|
||
|
||
EPUBJS.Rendition = function(book, options) {
|
||
|
||
this.settings = EPUBJS.core.extend(this.settings || {}, {
|
||
infinite: true,
|
||
hidden: false,
|
||
width: false,
|
||
height: null,
|
||
layoutOveride : null, // Default: { spread: 'reflowable', layout: 'auto', orientation: 'auto'},
|
||
axis: "vertical"
|
||
});
|
||
|
||
EPUBJS.core.extend(this.settings, options);
|
||
|
||
this.viewSettings = {};
|
||
|
||
this.book = book;
|
||
|
||
this.views = null;
|
||
|
||
//-- Adds Hook methods to the Rendition prototype
|
||
this.hooks = {};
|
||
this.hooks.display = new EPUBJS.Hook(this);
|
||
this.hooks.content = new EPUBJS.Hook(this);
|
||
this.hooks.layout = new EPUBJS.Hook(this);
|
||
this.hooks.render = new EPUBJS.Hook(this);
|
||
this.hooks.show = new EPUBJS.Hook(this);
|
||
|
||
this.hooks.content.register(EPUBJS.replace.links.bind(this));
|
||
this.hooks.content.register(this.passViewEvents.bind(this));
|
||
|
||
// this.hooks.display.register(this.afterDisplay.bind(this));
|
||
|
||
this.epubcfi = new EPUBJS.EpubCFI();
|
||
|
||
this.q = new EPUBJS.Queue(this);
|
||
|
||
this.q.enqueue(this.book.opened);
|
||
|
||
this.q.enqueue(this.parseLayoutProperties);
|
||
|
||
};
|
||
|
||
/**
|
||
* Creates an element to render to.
|
||
* Resizes to passed width and height or to the elements size
|
||
*/
|
||
EPUBJS.Rendition.prototype.initialize = function(_options){
|
||
var options = _options || {};
|
||
var height = options.height;// !== false ? options.height : "100%";
|
||
var width = options.width;// !== false ? options.width : "100%";
|
||
var hidden = options.hidden || false;
|
||
var container;
|
||
var wrapper;
|
||
|
||
if(options.height && EPUBJS.core.isNumber(options.height)) {
|
||
height = options.height + "px";
|
||
}
|
||
|
||
if(options.width && EPUBJS.core.isNumber(options.width)) {
|
||
width = options.width + "px";
|
||
}
|
||
|
||
// Create new container element
|
||
container = document.createElement("div");
|
||
|
||
container.id = "epubjs-container:" + EPUBJS.core.uuid();
|
||
container.classList.add("epub-container");
|
||
|
||
// Style Element
|
||
container.style.fontSize = "0";
|
||
container.style.wordSpacing = "0";
|
||
container.style.lineHeight = "0";
|
||
container.style.verticalAlign = "top";
|
||
|
||
if(this.settings.axis === "horizontal") {
|
||
container.style.whiteSpace = "nowrap";
|
||
}
|
||
|
||
if(width){
|
||
container.style.width = width;
|
||
}
|
||
|
||
if(height){
|
||
container.style.height = height;
|
||
}
|
||
|
||
container.style.overflow = this.settings.overflow;
|
||
|
||
return container;
|
||
};
|
||
|
||
EPUBJS.Rendition.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;
|
||
};
|
||
|
||
// Call to attach the container to an element in the dom
|
||
// Container must be attached before rendering can begin
|
||
EPUBJS.Rendition.prototype.attachTo = function(_element){
|
||
var bounds;
|
||
|
||
this.container = this.initialize({
|
||
"width" : this.settings.width,
|
||
"height" : this.settings.height
|
||
});
|
||
|
||
if(EPUBJS.core.isElement(_element)) {
|
||
this.element = _element;
|
||
} else if (typeof _element === "string") {
|
||
this.element = document.getElementById(_element);
|
||
}
|
||
|
||
if(!this.element){
|
||
console.error("Not an Element");
|
||
return;
|
||
}
|
||
|
||
if(this.settings.hidden) {
|
||
this.wrapper = this.wrap(this.container);
|
||
this.element.appendChild(this.wrapper);
|
||
} else {
|
||
this.element.appendChild(this.container);
|
||
}
|
||
|
||
this.views = new EPUBJS.Views(this.container);
|
||
|
||
// Attach Listeners
|
||
this.attachListeners();
|
||
|
||
// Calculate Stage Size
|
||
this.stageSize();
|
||
|
||
// Add Layout method
|
||
this.applyLayoutMethod();
|
||
|
||
// Trigger Attached
|
||
this.trigger("attached");
|
||
|
||
// Start processing queue
|
||
// this.q.run();
|
||
|
||
};
|
||
|
||
EPUBJS.Rendition.prototype.attachListeners = function(){
|
||
|
||
// Listen to window for resize event if width or height is set to 100%
|
||
if(!EPUBJS.core.isNumber(this.settings.width) ||
|
||
!EPUBJS.core.isNumber(this.settings.height) ) {
|
||
window.addEventListener("resize", this.onResized.bind(this), false);
|
||
}
|
||
|
||
};
|
||
|
||
EPUBJS.Rendition.prototype.bounds = function() {
|
||
return this.container.getBoundingClientRect();
|
||
};
|
||
|
||
EPUBJS.Rendition.prototype.display = function(target){
|
||
|
||
return this.q.enqueue(this._display, target);
|
||
|
||
};
|
||
|
||
EPUBJS.Rendition.prototype._display = function(target){
|
||
|
||
var displaying = new RSVP.defer();
|
||
var displayed = displaying.promise;
|
||
|
||
var section;
|
||
var view;
|
||
var offset;
|
||
var fragment;
|
||
var cfi = this.epubcfi.isCfiString(target);
|
||
|
||
var visible;
|
||
|
||
section = this.book.spine.get(target);
|
||
|
||
if(!section){
|
||
displaying.reject(new Error("No Section Found"));
|
||
return displayed;
|
||
}
|
||
|
||
// Check to make sure the section we want isn't already shown
|
||
visible = this.views.find(section);
|
||
|
||
if(visible) {
|
||
offset = view.locationOf(target);
|
||
displayed = this.moveTo(offset)
|
||
.then(function(){
|
||
return this.check();
|
||
});
|
||
} else {
|
||
|
||
// Hide all current views
|
||
this.views.hide();
|
||
|
||
// Create a new view
|
||
view = new EPUBJS.View(section, this.viewSettings);
|
||
|
||
// This will clear all previous views
|
||
displayed = this.fill(view)
|
||
.then(function(){
|
||
|
||
// Parse the target fragment
|
||
if(typeof target === "string" &&
|
||
target.indexOf("#") > -1) {
|
||
fragment = target.substring(target.indexOf("#")+1);
|
||
}
|
||
|
||
// Move to correct place within the section, if needed
|
||
if(cfi || fragment) {
|
||
offset = view.locationOf(target);
|
||
return this.moveTo(offset);
|
||
}
|
||
|
||
if(typeof this.check === 'function') {
|
||
return this.check();
|
||
}
|
||
}.bind(this))
|
||
.then(function(){
|
||
return this.hooks.display.trigger(view);
|
||
}.bind(this))
|
||
.then(function(){
|
||
this.views.show();
|
||
}.bind(this));
|
||
}
|
||
|
||
displayed.then(function(){
|
||
|
||
this.trigger("displayed", section);
|
||
|
||
}.bind(this));
|
||
|
||
|
||
return displayed;
|
||
};
|
||
|
||
// Takes a cfi, fragment or page?
|
||
EPUBJS.Rendition.prototype.moveTo = function(offset){
|
||
this.scrollBy(offset.left, offset.top);
|
||
};
|
||
|
||
EPUBJS.Rendition.prototype.render = function(view, show) {
|
||
|
||
view.create();
|
||
|
||
view.onLayout = this.layout.format.bind(this.layout);
|
||
|
||
// Fit to size of the container, apply padding
|
||
this.resizeView(view);
|
||
|
||
// Render Chain
|
||
return view.render(this.book.request)
|
||
.then(function(){
|
||
return this.hooks.content.trigger(view, this);
|
||
}.bind(this))
|
||
.then(function(){
|
||
return this.hooks.layout.trigger(view, this);
|
||
}.bind(this))
|
||
.then(function(){
|
||
return view.display();
|
||
}.bind(this))
|
||
.then(function(){
|
||
return this.hooks.render.trigger(view, this);
|
||
}.bind(this))
|
||
.then(function(){
|
||
if(show !== false && this.views.hidden === false) {
|
||
this.q.enqueue(function(view){
|
||
view.show();
|
||
}, view);
|
||
}
|
||
|
||
|
||
// this.map = new EPUBJS.Map(view, this.layout);
|
||
this.trigger("rendered", view.section);
|
||
|
||
}.bind(this))
|
||
.catch(function(e){
|
||
this.trigger("loaderror", e);
|
||
}.bind(this));
|
||
|
||
};
|
||
|
||
|
||
EPUBJS.Rendition.prototype.afterDisplayed = function(view){
|
||
this.trigger("added", view.section);
|
||
};
|
||
|
||
EPUBJS.Rendition.prototype.fill = function(view){
|
||
|
||
this.views.clear();
|
||
|
||
this.views.append(view);
|
||
|
||
// view.on("shown", this.afterDisplayed.bind(this));
|
||
view.onDisplayed = this.afterDisplayed.bind(this);
|
||
|
||
return this.render(view);
|
||
};
|
||
|
||
EPUBJS.Rendition.prototype.resizeView = function(view) {
|
||
|
||
if(this.globalLayoutProperties.layout === "pre-paginated") {
|
||
view.lock("both", this.stage.width, this.stage.height);
|
||
} else {
|
||
view.lock("width", this.stage.width, this.stage.height);
|
||
}
|
||
|
||
};
|
||
|
||
EPUBJS.Rendition.prototype.stageSize = 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 === false) {
|
||
bounds = this.element.getBoundingClientRect();
|
||
|
||
if(bounds.width) {
|
||
width = bounds.width;
|
||
this.container.style.width = bounds.width + "px";
|
||
}
|
||
}
|
||
|
||
if(height === false) {
|
||
bounds = bounds || this.element.getBoundingClientRect();
|
||
|
||
if(bounds.height) {
|
||
height = bounds.height;
|
||
this.container.style.height = bounds.height + "px";
|
||
}
|
||
|
||
}
|
||
|
||
if(width && !EPUBJS.core.isNumber(width)) {
|
||
bounds = this.container.getBoundingClientRect();
|
||
width = bounds.width;
|
||
//height = bounds.height;
|
||
}
|
||
|
||
if(height && !EPUBJS.core.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
|
||
};
|
||
|
||
this.stage = {
|
||
width: width -
|
||
this.containerPadding.left -
|
||
this.containerPadding.right,
|
||
height: height -
|
||
this.containerPadding.top -
|
||
this.containerPadding.bottom
|
||
};
|
||
|
||
return this.stage;
|
||
|
||
};
|
||
|
||
EPUBJS.Rendition.prototype.applyLayoutMethod = function() {
|
||
|
||
this.layout = new EPUBJS.Layout.Scroll();
|
||
this.updateLayout();
|
||
|
||
this.map = new EPUBJS.Map(this.layout);
|
||
};
|
||
|
||
EPUBJS.Rendition.prototype.updateLayout = function() {
|
||
|
||
this.layout.calculate(this.stage.width, this.stage.height);
|
||
|
||
};
|
||
|
||
EPUBJS.Rendition.prototype.resize = function(width, height){
|
||
|
||
this.stageSize(width, height);
|
||
|
||
this.updateLayout();
|
||
|
||
this.views.each(this.resizeView.bind(this));
|
||
|
||
this.trigger("resized", {
|
||
width: this.stage.width,
|
||
height: this.stage.height
|
||
});
|
||
|
||
};
|
||
|
||
EPUBJS.Rendition.prototype.onResized = function(e) {
|
||
this.resize();
|
||
};
|
||
|
||
EPUBJS.Rendition.prototype.createView = function(section) {
|
||
return new EPUBJS.View(section, this.viewSettings);
|
||
};
|
||
|
||
EPUBJS.Rendition.prototype.next = function(){
|
||
|
||
return this.q.enqueue(function(){
|
||
|
||
var next;
|
||
var view;
|
||
|
||
if(!this.views.length) return;
|
||
|
||
next = this.views.last().section.next();
|
||
|
||
if(next) {
|
||
view = this.createView(next);
|
||
return this.fill(view);
|
||
}
|
||
|
||
});
|
||
|
||
};
|
||
|
||
EPUBJS.Rendition.prototype.prev = function(){
|
||
|
||
return this.q.enqueue(function(){
|
||
|
||
var prev;
|
||
var view;
|
||
|
||
if(!this.views.length) return;
|
||
|
||
prev = this.views.first().section.prev();
|
||
if(prev) {
|
||
view = this.createView(prev);
|
||
return this.fill(view);
|
||
}
|
||
|
||
});
|
||
|
||
};
|
||
|
||
//-- http://www.idpf.org/epub/fxl/
|
||
EPUBJS.Rendition.prototype.parseLayoutProperties = function(_metadata){
|
||
var metadata = _metadata || this.book.package.metadata;
|
||
var layout = (this.layoutOveride && this.layoutOveride.layout) || metadata.layout || "reflowable";
|
||
var spread = (this.layoutOveride && this.layoutOveride.spread) || metadata.spread || "auto";
|
||
var orientation = (this.layoutOveride && this.layoutOveride.orientation) || metadata.orientation || "auto";
|
||
this.globalLayoutProperties = {
|
||
layout : layout,
|
||
spread : spread,
|
||
orientation : orientation
|
||
};
|
||
return this.globalLayoutProperties;
|
||
};
|
||
|
||
|
||
EPUBJS.Rendition.prototype.current = function(){
|
||
var visible = this.visible();
|
||
if(visible.length){
|
||
// Current is the last visible view
|
||
return visible[visible.length-1];
|
||
}
|
||
return null;
|
||
};
|
||
|
||
EPUBJS.Rendition.prototype.isVisible = function(view, offsetPrev, offsetNext, _container){
|
||
var position = view.position();
|
||
var container = _container || this.container.getBoundingClientRect();
|
||
|
||
if(this.settings.axis === "horizontal" &&
|
||
position.right > container.left - offsetPrev &&
|
||
position.left < container.right + offsetNext) {
|
||
|
||
return true;
|
||
|
||
} else if(this.settings.axis === "vertical" &&
|
||
position.bottom > container.top - offsetPrev &&
|
||
position.top < container.bottom + offsetNext) {
|
||
|
||
return true;
|
||
}
|
||
|
||
return false;
|
||
|
||
};
|
||
|
||
EPUBJS.Rendition.prototype.visible = function(){
|
||
var container = this.bounds();
|
||
var displayedViews = this.views.displayed();
|
||
var visible = [];
|
||
var isVisible;
|
||
var view;
|
||
|
||
for (var i = 0; i < displayedViews.length; i++) {
|
||
view = displayedViews[i];
|
||
isVisible = this.isVisible(view, 0, 0, container);
|
||
|
||
if(isVisible === true) {
|
||
visible.push(view);
|
||
}
|
||
|
||
}
|
||
return visible;
|
||
|
||
};
|
||
|
||
EPUBJS.Rendition.prototype.bounds = function(func) {
|
||
var bounds;
|
||
|
||
if(!this.settings.height) {
|
||
bounds = EPUBJS.core.windowBounds();
|
||
} else {
|
||
bounds = this.container.getBoundingClientRect();
|
||
}
|
||
|
||
return bounds;
|
||
};
|
||
|
||
EPUBJS.Rendition.prototype.destroy = function(){
|
||
// Clear the queue
|
||
this.q.clear();
|
||
|
||
this.views.clear();
|
||
|
||
clearTimeout(this.trimTimeout);
|
||
if(this.settings.hidden) {
|
||
this.element.removeChild(this.wrapper);
|
||
} else {
|
||
this.element.removeChild(this.container);
|
||
}
|
||
|
||
};
|
||
|
||
EPUBJS.Rendition.prototype.reportLocation = function(){
|
||
return this.q.enqueue(function(){
|
||
this.location = this.currentLocation();
|
||
this.trigger("locationChanged", this.location);
|
||
}.bind(this));
|
||
};
|
||
|
||
EPUBJS.Rendition.prototype.currentLocation = function(){
|
||
var view;
|
||
var start, end;
|
||
|
||
if(this.views.length) {
|
||
view = this.views.first();
|
||
// start = container.left - view.position().left;
|
||
// end = start + this.layout.spread;
|
||
|
||
return this.map.page(view);
|
||
}
|
||
|
||
};
|
||
|
||
EPUBJS.Rendition.prototype.scrollBy = function(x, y, silent){
|
||
if(silent) {
|
||
this.ignore = true;
|
||
}
|
||
|
||
if(this.settings.height) {
|
||
|
||
if(x) this.container.scrollLeft += x;
|
||
if(y) this.container.scrollTop += y;
|
||
|
||
} else {
|
||
window.scrollBy(x,y);
|
||
}
|
||
// console.log("scrollBy", x, y);
|
||
this.scrolled = true;
|
||
};
|
||
|
||
EPUBJS.Rendition.prototype.scrollTo = function(x, y, silent){
|
||
if(silent) {
|
||
this.ignore = true;
|
||
}
|
||
|
||
if(this.settings.height) {
|
||
this.container.scrollLeft = x;
|
||
this.container.scrollTop = y;
|
||
} else {
|
||
window.scrollTo(x,y);
|
||
}
|
||
// console.log("scrollTo", x, y);
|
||
this.scrolled = true;
|
||
// if(this.container.scrollLeft != x){
|
||
// setTimeout(function() {
|
||
// this.scrollTo(x, y, silent);
|
||
// }.bind(this), 10);
|
||
// return;
|
||
// };
|
||
};
|
||
|
||
EPUBJS.Rendition.prototype.passViewEvents = function(view){
|
||
view.listenedEvents.forEach(function(e){
|
||
view.on(e, this.triggerViewEvent.bind(this));
|
||
}.bind(this));
|
||
};
|
||
|
||
EPUBJS.Rendition.prototype.triggerViewEvent = function(e){
|
||
this.trigger(e.type, e);
|
||
};
|
||
|
||
//-- Enable binding events to Renderer
|
||
RSVP.EventTarget.mixin(EPUBJS.Rendition.prototype);
|
||
|
||
EPUBJS.Continuous = function(book, options) {
|
||
|
||
EPUBJS.Rendition.apply(this, arguments); // call super constructor.
|
||
|
||
this.settings = EPUBJS.core.extend(this.settings || {}, {
|
||
infinite: true,
|
||
overflow: "auto",
|
||
axis: "vertical",
|
||
offset: 500,
|
||
offsetDelta: 250
|
||
});
|
||
|
||
EPUBJS.core.extend(this.settings, options);
|
||
|
||
if(this.settings.hidden) {
|
||
this.wrapper = this.wrap(this.container);
|
||
}
|
||
|
||
|
||
};
|
||
|
||
// subclass extends superclass
|
||
EPUBJS.Continuous.prototype = Object.create(EPUBJS.Rendition.prototype);
|
||
EPUBJS.Continuous.prototype.constructor = EPUBJS.Continuous;
|
||
|
||
EPUBJS.Continuous.prototype.attachListeners = function(){
|
||
|
||
// Listen to window for resize event if width or height is set to a percent
|
||
if(!EPUBJS.core.isNumber(this.settings.width) ||
|
||
!EPUBJS.core.isNumber(this.settings.height) ) {
|
||
window.addEventListener("resize", this.onResized.bind(this), false);
|
||
}
|
||
|
||
|
||
if(this.settings.infinite) {
|
||
this.start();
|
||
}
|
||
|
||
|
||
};
|
||
|
||
EPUBJS.Continuous.prototype.parseTarget = function(target){
|
||
if(this.epubcfi.isCfiString(target)) {
|
||
cfi = this.epubcfi.parse(target);
|
||
spinePos = cfi.spinePos;
|
||
section = this.book.spine.get(spinePos);
|
||
} else {
|
||
section = this.book.spine.get(target);
|
||
}
|
||
};
|
||
|
||
EPUBJS.Continuous.prototype.moveTo = function(offset){
|
||
// var bounds = this.bounds();
|
||
// var dist = Math.floor(offset.top / bounds.height) * bounds.height;
|
||
return this.check(
|
||
offset.left+this.settings.offset,
|
||
offset.top+this.settings.offset)
|
||
.then(function(){
|
||
|
||
if(this.settings.axis === "vertical") {
|
||
this.scrollBy(0, offset.top);
|
||
} else {
|
||
this.scrollBy(offset.left, 0);
|
||
}
|
||
|
||
}.bind(this));
|
||
};
|
||
|
||
EPUBJS.Continuous.prototype.afterDisplayed = function(currView){
|
||
var next = currView.section.next();
|
||
var prev = currView.section.prev();
|
||
var index = this.views.indexOf(currView);
|
||
var prevView, nextView;
|
||
|
||
if(index + 1 === this.views.length && next) {
|
||
nextView = new EPUBJS.View(next, this.viewSettings);
|
||
this.q.enqueue(this.append, nextView);
|
||
}
|
||
|
||
if(index === 0 && prev) {
|
||
prevView = new EPUBJS.View(prev, this.viewSettings);
|
||
this.q.enqueue(this.prepend, prevView);
|
||
}
|
||
|
||
// this.removeShownListeners(currView);
|
||
// currView.onShown = this.afterDisplayed.bind(this);
|
||
|
||
this.trigger("added", currView.section);
|
||
|
||
};
|
||
|
||
|
||
// Remove Previous Listeners if present
|
||
EPUBJS.Continuous.prototype.removeShownListeners = function(view){
|
||
|
||
// view.off("shown", this.afterDisplayed);
|
||
// view.off("shown", this.afterDisplayedAbove);
|
||
view.onDisplayed = function(){};
|
||
|
||
};
|
||
|
||
EPUBJS.Continuous.prototype.append = function(view){
|
||
|
||
// view.on("shown", this.afterDisplayed.bind(this));
|
||
view.onDisplayed = this.afterDisplayed.bind(this);
|
||
|
||
this.views.append(view);
|
||
|
||
//this.q.enqueue(this.check);
|
||
return this.check();
|
||
};
|
||
|
||
EPUBJS.Continuous.prototype.prepend = function(view){
|
||
// view.on("shown", this.afterDisplayedAbove.bind(this));
|
||
view.onDisplayed = this.afterDisplayed.bind(this);
|
||
|
||
view.on("resized", this.counter.bind(this));
|
||
|
||
this.views.prepend(view);
|
||
|
||
// this.q.enqueue(this.check);
|
||
return this.check();
|
||
};
|
||
|
||
EPUBJS.Continuous.prototype.counter = function(bounds){
|
||
|
||
if(this.settings.axis === "vertical") {
|
||
this.scrollBy(0, bounds.heightDelta, true);
|
||
} else {
|
||
this.scrollBy(bounds.widthDelta, 0, true);
|
||
}
|
||
|
||
};
|
||
|
||
EPUBJS.Continuous.prototype.check = function(_offset){
|
||
var checking = new RSVP.defer();
|
||
var container = this.bounds();
|
||
var promises = [];
|
||
var offset = _offset || this.settings.offset;
|
||
|
||
this.views.each(function(view){
|
||
var visible = this.isVisible(view, offset, offset, container);
|
||
|
||
if(visible) {
|
||
|
||
if(!view.displayed && !view.rendering) {
|
||
// console.log("render",view.section.index)
|
||
promises.push(this.render(view));
|
||
}
|
||
|
||
} else {
|
||
|
||
if(view.displayed) {
|
||
// console.log("destroy", view.section.index)
|
||
this.q.enqueue(view.destroy.bind(view));
|
||
// view.destroy();
|
||
// this.q.enqueue(this.trim);
|
||
clearTimeout(this.trimTimeout);
|
||
this.trimTimeout = setTimeout(function(){
|
||
this.q.enqueue(this.trim);
|
||
}.bind(this), 250);
|
||
}
|
||
|
||
}
|
||
|
||
}.bind(this));
|
||
|
||
|
||
if(promises.length){
|
||
|
||
return RSVP.all(promises)
|
||
.then(function(posts) {
|
||
// Check to see if anything new is on screen after rendering
|
||
this.q.enqueue(this.check);
|
||
|
||
}.bind(this));
|
||
|
||
} else {
|
||
checking.resolve();
|
||
|
||
return checking.promise;
|
||
}
|
||
|
||
};
|
||
|
||
EPUBJS.Continuous.prototype.trim = function(){
|
||
var task = new RSVP.defer();
|
||
var displayed = this.views.displayed();
|
||
var first = displayed[0];
|
||
var last = displayed[displayed.length-1];
|
||
var firstIndex = this.views.indexOf(first);
|
||
var lastIndex = this.views.indexOf(last);
|
||
var above = this.views.slice(0, firstIndex);
|
||
var below = this.views.slice(lastIndex+1);
|
||
|
||
// Erase all but last above
|
||
for (var i = 0; i < above.length-1; i++) {
|
||
this.erase(above[i], above);
|
||
}
|
||
|
||
// Erase all except first below
|
||
for (var j = 1; j < below.length; j++) {
|
||
this.erase(below[j]);
|
||
}
|
||
|
||
task.resolve();
|
||
return task.promise;
|
||
};
|
||
|
||
EPUBJS.Continuous.prototype.erase = function(view, above){ //Trim
|
||
|
||
var prevTop;
|
||
var prevLeft;
|
||
|
||
if(this.settings.height) {
|
||
prevTop = this.container.scrollTop;
|
||
prevLeft = this.container.scrollLeft;
|
||
} else {
|
||
prevTop = window.scrollY;
|
||
prevLeft = window.scrollX;
|
||
}
|
||
|
||
var bounds = view.bounds();
|
||
|
||
this.views.remove(view);
|
||
|
||
if(above) {
|
||
|
||
if(this.settings.axis === "vertical") {
|
||
this.scrollTo(0, prevTop - bounds.height, true);
|
||
} else {
|
||
this.scrollTo(prevLeft - bounds.width, 0, true);
|
||
}
|
||
}
|
||
|
||
};
|
||
|
||
EPUBJS.Continuous.prototype.start = function() {
|
||
var scroller;
|
||
|
||
this.tick = EPUBJS.core.requestAnimationFrame;
|
||
|
||
if(this.settings.height) {
|
||
this.prevScrollTop = this.container.scrollTop;
|
||
this.prevScrollLeft = this.container.scrollLeft;
|
||
} else {
|
||
this.prevScrollTop = window.scrollY;
|
||
this.prevScrollLeft = window.scrollX;
|
||
}
|
||
|
||
this.scrollDeltaVert = 0;
|
||
this.scrollDeltaHorz = 0;
|
||
|
||
if(this.settings.height) {
|
||
scroller = this.container;
|
||
} else {
|
||
scroller = window;
|
||
}
|
||
|
||
window.addEventListener("scroll", function(e){
|
||
if(!this.ignore) {
|
||
this.scrolled = true;
|
||
} else {
|
||
this.ignore = false;
|
||
}
|
||
}.bind(this));
|
||
|
||
window.addEventListener('unload', function(e){
|
||
this.ignore = true;
|
||
this.destroy();
|
||
}.bind(this));
|
||
|
||
this.tick.call(window, this.onScroll.bind(this));
|
||
|
||
this.scrolled = false;
|
||
|
||
};
|
||
|
||
EPUBJS.Continuous.prototype.onScroll = function(){
|
||
|
||
if(this.scrolled) {
|
||
|
||
if(this.settings.height) {
|
||
scrollTop = this.container.scrollTop;
|
||
scrollLeft = this.container.scrollLeft;
|
||
} else {
|
||
scrollTop = window.scrollY;
|
||
scrollLeft = window.scrollX;
|
||
}
|
||
|
||
if(!this.ignore) {
|
||
|
||
// this.trigger("scroll", {
|
||
// top: scrollTop,
|
||
// left: scrollLeft
|
||
// });
|
||
|
||
if((this.scrollDeltaVert === 0 &&
|
||
this.scrollDeltaHorz === 0) ||
|
||
this.scrollDeltaVert > this.settings.offsetDelta ||
|
||
this.scrollDeltaHorz > this.settings.offsetDelta) {
|
||
|
||
this.q.enqueue(this.check);
|
||
|
||
this.scrollDeltaVert = 0;
|
||
this.scrollDeltaHorz = 0;
|
||
|
||
}
|
||
|
||
} else {
|
||
this.ignore = false;
|
||
}
|
||
|
||
this.scrollDeltaVert += Math.abs(scrollTop-this.prevScrollTop);
|
||
this.scrollDeltaHorz += Math.abs(scrollLeft-this.prevScrollLeft);
|
||
|
||
if(this.settings.height) {
|
||
this.prevScrollTop = this.container.scrollTop;
|
||
this.prevScrollLeft = this.container.scrollLeft;
|
||
} else {
|
||
this.prevScrollTop = window.scrollY;
|
||
this.prevScrollLeft = window.scrollX;
|
||
}
|
||
|
||
|
||
clearTimeout(this.scrollTimeout);
|
||
this.scrollTimeout = setTimeout(function(){
|
||
this.scrollDeltaVert = 0;
|
||
this.scrollDeltaHorz = 0;
|
||
}.bind(this), 150);
|
||
|
||
|
||
this.scrolled = false;
|
||
}
|
||
|
||
this.tick.call(window, this.onScroll.bind(this));
|
||
|
||
};
|
||
|
||
|
||
EPUBJS.Continuous.prototype.resizeView = function(view) {
|
||
|
||
if(this.settings.axis === "horizontal") {
|
||
view.lock("height", this.stage.width, this.stage.height);
|
||
} else {
|
||
view.lock("width", this.stage.width, this.stage.height);
|
||
}
|
||
|
||
};
|
||
|
||
EPUBJS.Continuous.prototype.currentLocation = function(){
|
||
var visible = this.visible();
|
||
var startPage, endPage;
|
||
|
||
var container = this.container.getBoundingClientRect();
|
||
|
||
if(visible.length === 1) {
|
||
return this.map.page(visible[0]);
|
||
}
|
||
|
||
if(visible.length > 1) {
|
||
|
||
startPage = this.map.page(visible[0]);
|
||
endPage = this.map.page(visible[visible.length-1]);
|
||
|
||
return {
|
||
start: startPage.start,
|
||
end: endPage.end
|
||
};
|
||
}
|
||
|
||
};
|
||
|
||
/*
|
||
EPUBJS.Continuous.prototype.current = function(what){
|
||
var view, top;
|
||
var container = this.container.getBoundingClientRect();
|
||
var length = this.views.length - 1;
|
||
|
||
if(this.settings.axis === "horizontal") {
|
||
|
||
for (var i = length; i >= 0; i--) {
|
||
view = this.views[i];
|
||
left = view.position().left;
|
||
|
||
if(left < container.right) {
|
||
|
||
if(this._current == view) {
|
||
break;
|
||
}
|
||
|
||
this._current = view;
|
||
break;
|
||
}
|
||
}
|
||
|
||
} else {
|
||
|
||
for (var i = length; i >= 0; i--) {
|
||
view = this.views[i];
|
||
top = view.bounds().top;
|
||
if(top < container.bottom) {
|
||
|
||
if(this._current == view) {
|
||
break;
|
||
}
|
||
|
||
this._current = view;
|
||
|
||
break;
|
||
}
|
||
}
|
||
|
||
}
|
||
|
||
return this._current;
|
||
};
|
||
*/
|
||
|
||
EPUBJS.Paginate = function(book, options) {
|
||
|
||
EPUBJS.Continuous.apply(this, arguments);
|
||
|
||
this.settings = EPUBJS.core.extend(this.settings || {}, {
|
||
width: 600,
|
||
height: 400,
|
||
axis: "horizontal",
|
||
forceSingle: false,
|
||
minSpreadWidth: 800, //-- overridden by spread: none (never) / both (always)
|
||
gap: "auto", //-- "auto" or int
|
||
overflow: "hidden",
|
||
infinite: false
|
||
});
|
||
|
||
EPUBJS.core.extend(this.settings, options);
|
||
|
||
this.isForcedSingle = this.settings.forceSingle;
|
||
|
||
this.viewSettings = {
|
||
axis: this.settings.axis
|
||
};
|
||
|
||
this.start();
|
||
};
|
||
|
||
EPUBJS.Paginate.prototype = Object.create(EPUBJS.Continuous.prototype);
|
||
EPUBJS.Paginate.prototype.constructor = EPUBJS.Paginate;
|
||
|
||
|
||
EPUBJS.Paginate.prototype.determineSpreads = function(cutoff){
|
||
if(this.isForcedSingle || !cutoff || this.bounds().width < cutoff) {
|
||
return 1; //-- Single Page
|
||
}else{
|
||
return 2; //-- Double Page
|
||
}
|
||
};
|
||
|
||
EPUBJS.Paginate.prototype.forceSingle = function(bool){
|
||
if(bool === false) {
|
||
this.isForcedSingle = false;
|
||
// this.spreads = false;
|
||
} else {
|
||
this.isForcedSingle = true;
|
||
// this.spreads = this.determineSpreads(this.minSpreadWidth);
|
||
}
|
||
this.applyLayoutMethod();
|
||
};
|
||
|
||
/**
|
||
* Uses the settings to determine which Layout Method is needed
|
||
* Triggers events based on the method choosen
|
||
* Takes: Layout settings object
|
||
* Returns: String of appropriate for EPUBJS.Layout function
|
||
*/
|
||
// EPUBJS.Paginate.prototype.determineLayout = function(settings){
|
||
// // Default is layout: reflowable & spread: auto
|
||
// var spreads = this.determineSpreads(this.settings.minSpreadWidth);
|
||
// console.log("spreads", spreads, this.settings.minSpreadWidth)
|
||
// var layoutMethod = spreads ? "ReflowableSpreads" : "Reflowable";
|
||
// var scroll = false;
|
||
//
|
||
// if(settings.layout === "pre-paginated") {
|
||
// layoutMethod = "Fixed";
|
||
// scroll = true;
|
||
// spreads = false;
|
||
// }
|
||
//
|
||
// if(settings.layout === "reflowable" && settings.spread === "none") {
|
||
// layoutMethod = "Reflowable";
|
||
// scroll = false;
|
||
// spreads = false;
|
||
// }
|
||
//
|
||
// if(settings.layout === "reflowable" && settings.spread === "both") {
|
||
// layoutMethod = "ReflowableSpreads";
|
||
// scroll = false;
|
||
// spreads = true;
|
||
// }
|
||
//
|
||
// this.spreads = spreads;
|
||
//
|
||
// return layoutMethod;
|
||
// };
|
||
|
||
EPUBJS.Paginate.prototype.start = function(){
|
||
// On display
|
||
// this.layoutSettings = this.reconcileLayoutSettings(globalLayout, chapter.properties);
|
||
// this.layoutMethod = this.determineLayout(this.layoutSettings);
|
||
// this.layout = new EPUBJS.Layout[this.layoutMethod]();
|
||
//this.hooks.display.register(this.registerLayoutMethod.bind(this));
|
||
// this.hooks.display.register(this.reportLocation);
|
||
this.on('displayed', this.reportLocation.bind(this));
|
||
|
||
// this.hooks.content.register(this.adjustImages.bind(this));
|
||
|
||
this.currentPage = 0;
|
||
|
||
window.addEventListener('unload', function(e){
|
||
this.ignore = true;
|
||
this.destroy();
|
||
}.bind(this));
|
||
|
||
};
|
||
|
||
// EPUBJS.Rendition.prototype.createView = function(section) {
|
||
// var view = new EPUBJS.View(section, this.viewSettings);
|
||
|
||
|
||
// return view;
|
||
// };
|
||
|
||
EPUBJS.Paginate.prototype.applyLayoutMethod = function() {
|
||
//var task = new RSVP.defer();
|
||
|
||
// this.spreads = this.determineSpreads(this.settings.minSpreadWidth);
|
||
|
||
this.layout = new EPUBJS.Layout.Reflowable();
|
||
|
||
this.updateLayout();
|
||
|
||
// Set the look ahead offset for what is visible
|
||
|
||
this.map = new EPUBJS.Map(this.layout);
|
||
|
||
// this.hooks.layout.register(this.layout.format.bind(this));
|
||
|
||
//task.resolve();
|
||
//return task.promise;
|
||
// return layout;
|
||
};
|
||
|
||
EPUBJS.Paginate.prototype.updateLayout = function() {
|
||
|
||
this.spreads = this.determineSpreads(this.settings.minSpreadWidth);
|
||
|
||
this.layout.calculate(
|
||
this.stage.width,
|
||
this.stage.height,
|
||
this.settings.gap,
|
||
this.spreads
|
||
);
|
||
|
||
this.settings.offset = this.layout.delta;
|
||
|
||
};
|
||
|
||
EPUBJS.Paginate.prototype.moveTo = function(offset){
|
||
var dist = Math.floor(offset.left / this.layout.delta) * this.layout.delta;
|
||
return this.check(0, dist+this.settings.offset).then(function(){
|
||
this.scrollBy(dist, 0);
|
||
}.bind(this));
|
||
};
|
||
|
||
EPUBJS.Paginate.prototype.page = function(pg){
|
||
|
||
// this.currentPage = pg;
|
||
// this.renderer.infinite.scrollTo(this.currentPage * this.formated.pageWidth, 0);
|
||
//-- Return false if page is greater than the total
|
||
// return false;
|
||
};
|
||
|
||
EPUBJS.Paginate.prototype.next = function(){
|
||
|
||
return this.q.enqueue(function(){
|
||
// console.log(this.container.scrollWidth, this.container.scrollLeft + this.container.offsetWidth + this.layout.delta)
|
||
if(this.container.scrollLeft +
|
||
this.container.offsetWidth +
|
||
this.layout.delta < this.container.scrollWidth) {
|
||
this.scrollBy(this.layout.delta, 0);
|
||
} else {
|
||
this.scrollTo(this.container.scrollWidth - this.layout.delta, 0);
|
||
}
|
||
this.reportLocation();
|
||
return this.check();
|
||
});
|
||
|
||
// return this.page(this.currentPage + 1);
|
||
};
|
||
|
||
EPUBJS.Paginate.prototype.prev = function(){
|
||
|
||
return this.q.enqueue(function(){
|
||
this.scrollBy(-this.layout.delta, 0);
|
||
this.reportLocation();
|
||
return this.check();
|
||
});
|
||
// return this.page(this.currentPage - 1);
|
||
};
|
||
|
||
// EPUBJS.Paginate.prototype.reportLocation = function(){
|
||
// return this.q.enqueue(function(){
|
||
// this.location = this.currentLocation();
|
||
// this.trigger("locationChanged", this.location);
|
||
// }.bind(this));
|
||
// };
|
||
|
||
EPUBJS.Paginate.prototype.currentLocation = function(){
|
||
var visible = this.visible();
|
||
var startA, startB, endA, endB;
|
||
var pageLeft, pageRight;
|
||
var container = this.container.getBoundingClientRect();
|
||
|
||
if(visible.length === 1) {
|
||
startA = container.left - visible[0].position().left;
|
||
endA = startA + this.layout.spread;
|
||
|
||
return this.map.page(visible[0], startA, endA);
|
||
}
|
||
|
||
if(visible.length > 1) {
|
||
|
||
// Left Col
|
||
startA = container.left - visible[0].position().left;
|
||
endA = startA + this.layout.column;
|
||
|
||
// Right Col
|
||
startB = container.left + this.layout.spread - visible[visible.length-1].position().left;
|
||
endB = startB + this.layout.column;
|
||
|
||
pageLeft = this.map.page(visible[0], startA, endA);
|
||
pageRight = this.map.page(visible[visible.length-1], startB, endB);
|
||
|
||
return {
|
||
start: pageLeft.start,
|
||
end: pageRight.end
|
||
};
|
||
}
|
||
};
|
||
|
||
EPUBJS.Paginate.prototype.resize = function(width, height){
|
||
// Clear the queue
|
||
this.q.clear();
|
||
|
||
this.stageSize(width, height);
|
||
|
||
this.updateLayout();
|
||
|
||
if(this.location) {
|
||
this.display(this.location.start);
|
||
}
|
||
|
||
this.trigger("resized", {
|
||
width: this.stage.width,
|
||
height: this.stage.height
|
||
});
|
||
|
||
};
|
||
|
||
EPUBJS.Paginate.prototype.onResized = function(e) {
|
||
|
||
this.views.clear();
|
||
|
||
clearTimeout(this.resizeTimeout);
|
||
this.resizeTimeout = setTimeout(function(){
|
||
this.resize();
|
||
}.bind(this), 150);
|
||
};
|
||
|
||
EPUBJS.Paginate.prototype.adjustImages = function(view) {
|
||
|
||
view.addStylesheetRules([
|
||
["img",
|
||
["max-width", (this.layout.spread) + "px"],
|
||
["max-height", (this.layout.height) + "px"]
|
||
]
|
||
]);
|
||
return new RSVP.Promise(function(resolve, reject){
|
||
// Wait to apply
|
||
setTimeout(function() {
|
||
resolve();
|
||
}, 1);
|
||
});
|
||
};
|
||
|
||
// EPUBJS.Paginate.prototype.display = function(what){
|
||
// return this.display(what);
|
||
// };
|
||
|
||
EPUBJS.Map = function(layout){
|
||
this.layout = layout;
|
||
};
|
||
|
||
EPUBJS.Map.prototype.section = function(view) {
|
||
var ranges = this.findRanges(view);
|
||
var map = this.rangeListToCfiList(view, ranges);
|
||
|
||
return map;
|
||
};
|
||
|
||
EPUBJS.Map.prototype.page = function(view, start, end) {
|
||
var root = view.document.body;
|
||
return this.rangePairToCfiPair(view.section, {
|
||
start: this.findStart(root, start, end),
|
||
end: this.findEnd(root, start, end)
|
||
});
|
||
};
|
||
|
||
EPUBJS.Map.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;
|
||
};
|
||
|
||
EPUBJS.Map.prototype.findRanges = function(view){
|
||
var columns = [];
|
||
var count = this.layout.count(view);
|
||
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;
|
||
};
|
||
|
||
EPUBJS.Map.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);
|
||
};
|
||
|
||
EPUBJS.Map.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);
|
||
};
|
||
|
||
|
||
EPUBJS.Map.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];
|
||
};
|
||
|
||
EPUBJS.Map.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];
|
||
|
||
};
|
||
|
||
EPUBJS.Map.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;
|
||
};
|
||
|
||
|
||
|
||
EPUBJS.Map.prototype.rangePairToCfiPair = function(section, rangePair){
|
||
|
||
var startRange = rangePair.start;
|
||
var endRange = rangePair.end;
|
||
|
||
startRange.collapse(true);
|
||
endRange.collapse(true);
|
||
|
||
startCfi = section.cfiFromRange(startRange);
|
||
endCfi = section.cfiFromRange(endRange);
|
||
|
||
return {
|
||
start: startCfi,
|
||
end: endCfi
|
||
};
|
||
|
||
};
|
||
|
||
EPUBJS.Map.prototype.rangeListToCfiList = function(view, columns){
|
||
var map = [];
|
||
var rangePair, cifPair;
|
||
|
||
for (var i = 0; i < columns.length; i++) {
|
||
cifPair = this.rangePairToCfiPair(view.section, columns[i]);
|
||
|
||
map.push(cifPair);
|
||
|
||
}
|
||
|
||
return map;
|
||
};
|
||
EPUBJS.Locations = function(spine, request) {
|
||
this.spine = spine;
|
||
this.request = request;
|
||
|
||
this.q = new EPUBJS.Queue(this);
|
||
this.epubcfi = new EPUBJS.EpubCFI();
|
||
|
||
this._locations = [];
|
||
this.total = 0;
|
||
|
||
this.break = 150;
|
||
|
||
this._current = 0;
|
||
|
||
};
|
||
|
||
// Load all of sections in the book
|
||
EPUBJS.Locations.prototype.generate = function(chars) {
|
||
|
||
if (chars) {
|
||
this.break = chars;
|
||
}
|
||
|
||
this.q.pause();
|
||
|
||
this.spine.each(function(section) {
|
||
|
||
this.q.enqueue(this.process, 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.precentage(this.book.rendition.location.start), this.precentage(this.book.rendition.location.end));
|
||
}.bind(this));
|
||
|
||
};
|
||
|
||
EPUBJS.Locations.prototype.process = function(section) {
|
||
|
||
return section.load(this.request)
|
||
.then(function(contents) {
|
||
|
||
var range;
|
||
var doc = contents.ownerDocument;
|
||
var counter = 0;
|
||
|
||
this.sprint(contents, function(node) {
|
||
var len = node.length;
|
||
var dist;
|
||
var pos = 0;
|
||
|
||
// Start range
|
||
if (counter == 0) {
|
||
range = doc.createRange();
|
||
range.setStart(node, 0);
|
||
}
|
||
|
||
dist = this.break - counter;
|
||
|
||
// Node is smaller than a break
|
||
if(dist > len){
|
||
counter += len;
|
||
pos = len;
|
||
}
|
||
|
||
while (pos < len) {
|
||
counter = this.break;
|
||
pos += this.break;
|
||
|
||
// Gone over
|
||
if(pos >= len){
|
||
// Continue counter for next node
|
||
counter = len - (pos - this.break);
|
||
|
||
// At End
|
||
} else {
|
||
// End the previous range
|
||
range.setEnd(node, pos);
|
||
cfi = section.cfiFromRange(range);
|
||
this._locations.push(cfi);
|
||
counter = 0;
|
||
|
||
// Start new range
|
||
pos += 1;
|
||
range = doc.createRange();
|
||
range.setStart(node, pos);
|
||
}
|
||
}
|
||
|
||
|
||
|
||
}.bind(this));
|
||
|
||
// Close remaining
|
||
if (range) {
|
||
range.setEnd(prev, prev.length);
|
||
cfi = section.cfiFromRange(range);
|
||
this._locations.push(cfi)
|
||
counter = 0;
|
||
}
|
||
|
||
}.bind(this));
|
||
|
||
};
|
||
|
||
EPUBJS.Locations.prototype.sprint = function(root, func) {
|
||
var treeWalker = document.createTreeWalker(root, NodeFilter.SHOW_TEXT, null, false);
|
||
|
||
while ((node = treeWalker.nextNode())) {
|
||
func(node);
|
||
}
|
||
|
||
};
|
||
|
||
EPUBJS.Locations.prototype.locationFromCfi = function(cfi){
|
||
// Check if the location has not been set yet
|
||
if(this._locations.length === 0) {
|
||
return -1;
|
||
}
|
||
|
||
return EPUBJS.core.locationOf(cfi, this._locations, this.epubcfi.compare);
|
||
};
|
||
|
||
EPUBJS.Locations.prototype.precentageFromCfi = function(cfi) {
|
||
// Find closest cfi
|
||
var loc = this.locationFromCfi(cfi);
|
||
// Get percentage in total
|
||
return this.precentageFromLocation(loc);
|
||
};
|
||
|
||
EPUBJS.Locations.prototype.precentageFromLocation = function(loc) {
|
||
return Math.ceil((loc / this.total ) * 1000) / 1000;
|
||
};
|
||
|
||
EPUBJS.Locations.prototype.cfiFromLocation = function(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;
|
||
};
|
||
|
||
EPUBJS.Locations.prototype.cfiFromPercentage = function(percent){
|
||
var loc = Math.round(this.total * percent);
|
||
return this.cfiFromLocation(loc);
|
||
};
|
||
|
||
EPUBJS.Locations.prototype.load = function(locations){
|
||
this._locations = JSON.parse(locations);
|
||
this.total = this._locations.length-1;
|
||
return this._locations;
|
||
};
|
||
|
||
EPUBJS.Locations.prototype.save = function(json){
|
||
return JSON.stringify(this._locations);
|
||
};
|
||
|
||
EPUBJS.Locations.prototype.getCurrent = function(json){
|
||
return this._current;
|
||
};
|
||
|
||
EPUBJS.Locations.prototype.setCurrent = function(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;
|
||
}
|
||
console.log( this.precentageFromLocation(loc))
|
||
this.trigger("changed", {
|
||
percentage: this.precentageFromLocation(loc)
|
||
});
|
||
};
|
||
|
||
Object.defineProperty(EPUBJS.Locations.prototype, 'currentLocation', {
|
||
get: function () {
|
||
return this._current;
|
||
},
|
||
set: function (curr) {
|
||
this.setCurrent(curr);
|
||
}
|
||
});
|
||
|
||
RSVP.EventTarget.mixin(EPUBJS.Locations.prototype);
|