diff --git a/Gruntfile.js b/Gruntfile.js deleted file mode 100644 index e9f1acf..0000000 --- a/Gruntfile.js +++ /dev/null @@ -1,32 +0,0 @@ -module.exports = function(grunt) { - - // Project configuration. - grunt.initConfig({ - pkg: grunt.file.readJSON('package.json'), - uglify: { - options: { - banner: '/*! <%= pkg.name %> <%= grunt.template.today("yyyy-mm-dd") %> */\n' - }, - build: { - src: 'src/<%= pkg.name %>.js', - dest: 'build/<%= pkg.name %>.min.js' - } - }, - connect: { - server: { - options: { - port: 9001, - base: '.' - } - } - } - }); - - grunt.loadNpmTasks('grunt-contrib-uglify'); - grunt.loadNpmTasks('grunt-contrib-connect'); - - // Default task(s). - grunt.registerTask('default', ['uglify']); - grunt.registerTask('serve', ['connect']); - -}; \ No newline at end of file diff --git a/dist/epub.js b/dist/epub.js new file mode 100644 index 0000000..7812393 --- /dev/null +++ b/dist/epub.js @@ -0,0 +1,4542 @@ +if (typeof EPUBJS === 'undefined') { + (typeof window !== 'undefined' ? window : this).EPUBJS = {}; +} + +EPUBJS.VERSION = "0.3.0"; +EPUBJS.Render = {}; + +(function(root) { + "use strict"; + var ePub = function(_url) { + return new EPUBJS.Book(_url); + }; + + ePub.Render = { + register: function(name, renderer) { + ePub.Render[name] = renderer; + } + } + + // 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); +(function(global) { +/** + @class RSVP + @module RSVP + */ +var define, require; + +(function() { + var registry = {}, seen = {}; + + define = function(name, deps, callback) { + registry[name] = { deps: deps, callback: callback }; + }; + + require = function(name) { + + if (seen[name]) { return seen[name]; } + seen[name] = {}; + + if (!registry[name]) { + throw new Error("Could not find module " + name); + } + + var mod = registry[name], + deps = mod.deps, + callback = mod.callback, + reified = [], + exports; + + for (var i=0, l=deps.length; i 1; + }; + + RSVP.filter(promises, filterFn).then(function(result){ + // result is [ 2, 3 ] + }); + ``` + + If any of the `promises` given to `RSVP.filter` are rejected, the first promise + that is rejected will be given as an argument to the returned promise's + rejection handler. For example: + + ```javascript + var promise1 = RSVP.resolve(1); + var promise2 = RSVP.reject(new Error("2")); + var promise3 = RSVP.reject(new Error("3")); + var promises = [ promise1, promise2, promise3 ]; + + var filterFn = function(item){ + return item > 1; + }; + + RSVP.filter(promises, filterFn).then(function(array){ + // Code here never runs because there are rejected promises! + }, function(reason) { + // reason.message === "2" + }); + ``` + + `RSVP.filter` will also wait for any promises returned from `filterFn`. + For instance, you may want to fetch a list of users then return a subset + of those users based on some asynchronous operation: + + ```javascript + + var alice = { name: 'alice' }; + var bob = { name: 'bob' }; + var users = [ alice, bob ]; + + var promises = users.map(function(user){ + return RSVP.resolve(user); + }); + + var filterFn = function(user){ + // Here, Alice has permissions to create a blog post, but Bob does not. + return getPrivilegesForUser(user).then(function(privs){ + return privs.can_create_blog_post === true; + }); + }; + RSVP.filter(promises, filterFn).then(function(users){ + // true, because the server told us only Alice can create a blog post. + users.length === 1; + // false, because Alice is the only user present in `users` + users[0] === bob; + }); + ``` + + @method filter + @static + @for RSVP + @param {Array} promises + @param {Function} filterFn - function to be called on each resolved value to + filter the final results. + @param {String} label optional string describing the promise. Useful for + tooling. + @return {Promise} + */ + __exports__['default'] = function filter(promises, filterFn, label) { + return Promise.all(promises, label).then(function (values) { + if (!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 Promise.all(filtered, label).then(function (filtered$2) { + var results = new Array(length); + var newLength = 0; + for (var i$2 = 0; i$2 < length; i$2++) { + if (filtered$2[i$2]) { + results[newLength] = values[i$2]; + newLength++; + } + } + results.length = newLength; + return results; + }); + }); + }; +}); +define('rsvp/hash-settled', [ + './promise', + './enumerator', + './promise-hash', + './utils', + 'exports' +], function (__dependency1__, __dependency2__, __dependency3__, __dependency4__, __exports__) { + 'use strict'; + var Promise = __dependency1__['default']; + var makeSettledResult = __dependency2__.makeSettledResult; + var PromiseHash = __dependency3__['default']; + var Enumerator = __dependency2__['default']; + var o_create = __dependency4__.o_create; + function HashSettled(Constructor, object, label) { + this._superConstructor(Constructor, object, false, label); + } + HashSettled.prototype = o_create(PromiseHash.prototype); + HashSettled.prototype._superConstructor = Enumerator; + HashSettled.prototype._makeResult = makeSettledResult; + HashSettled.prototype._validationError = function () { + return new Error('hashSettled must be called with an object'); + }; + /** + `RSVP.hashSettled` is similar to `RSVP.allSettled`, but takes an object + instead of an array for its `promises` argument. + + Unlike `RSVP.all` or `RSVP.hash`, which implement a fail-fast method, + but like `RSVP.allSettled`, `hashSettled` waits until all the + constituent promises have returned and then shows you all the results + with their states and values/reasons. This is useful if you want to + handle multiple promises' failure states together as a set. + + Returns a promise that is fulfilled when all the given promises have been + settled, or rejected if the passed parameters are invalid. + + The returned promise is fulfilled with a hash that has the same key names as + the `promises` object argument. If any of the values in the object are not + promises, they will be copied over to the fulfilled object and marked with state + 'fulfilled'. + + Example: + + ```javascript + var promises = { + myPromise: RSVP.Promise.resolve(1), + yourPromise: RSVP.Promise.resolve(2), + theirPromise: RSVP.Promise.resolve(3), + notAPromise: 4 + }; + + RSVP.hashSettled(promises).then(function(hash){ + // hash here is an object that looks like: + // { + // myPromise: { state: 'fulfilled', value: 1 }, + // yourPromise: { state: 'fulfilled', value: 2 }, + // theirPromise: { state: 'fulfilled', value: 3 }, + // notAPromise: { state: 'fulfilled', value: 4 } + // } + }); + ``` + + If any of the `promises` given to `RSVP.hash` are rejected, the state will + be set to 'rejected' and the reason for rejection provided. + + Example: + + ```javascript + var promises = { + myPromise: RSVP.Promise.resolve(1), + rejectedPromise: RSVP.Promise.reject(new Error('rejection')), + anotherRejectedPromise: RSVP.Promise.reject(new Error('more rejection')), + }; + + RSVP.hashSettled(promises).then(function(hash){ + // hash here is an object that looks like: + // { + // myPromise: { state: 'fulfilled', value: 1 }, + // rejectedPromise: { state: 'rejected', reason: Error }, + // anotherRejectedPromise: { state: 'rejected', reason: Error }, + // } + // Note that for rejectedPromise, reason.message == 'rejection', + // and for anotherRejectedPromise, reason.message == 'more rejection'. + }); + ``` + + An important note: `RSVP.hashSettled` is intended for plain JavaScript objects that + are just a set of keys and values. `RSVP.hashSettled` will NOT preserve prototype + chains. + + Example: + + ```javascript + function MyConstructor(){ + this.example = RSVP.Promise.resolve('Example'); + } + + MyConstructor.prototype = { + protoProperty: RSVP.Promise.resolve('Proto Property') + }; + + var myObject = new MyConstructor(); + + RSVP.hashSettled(myObject).then(function(hash){ + // protoProperty will not be present, instead you will just have an + // object that looks like: + // { + // example: { state: 'fulfilled', value: 'Example' } + // } + // + // hash.hasOwnProperty('protoProperty'); // false + // 'undefined' === typeof hash.protoProperty + }); + ``` + + @method hashSettled + @for RSVP + @param {Object} promises + @param {String} label optional string that describes the promise. + Useful for tooling. + @return {Promise} promise that is fulfilled when when all properties of `promises` + have been settled. + @static + */ + __exports__['default'] = function hashSettled(object, label) { + return new HashSettled(Promise, object, label).promise; + }; +}); +define('rsvp/hash', [ + './promise', + './promise-hash', + './enumerator', + 'exports' +], function (__dependency1__, __dependency2__, __dependency3__, __exports__) { + 'use strict'; + var Promise = __dependency1__['default']; + var PromiseHash = __dependency2__['default']; + var ABORT_ON_REJECTION = __dependency3__.ABORT_ON_REJECTION; + /** + `RSVP.hash` is similar to `RSVP.all`, but takes an object instead of an array + for its `promises` argument. + + Returns a promise that is fulfilled when all the given promises have been + fulfilled, or rejected if any of them become rejected. The returned promise + is fulfilled with a hash that has the same key names as the `promises` object + argument. If any of the values in the object are not promises, they will + simply be copied over to the fulfilled object. + + Example: + + ```javascript + var promises = { + myPromise: RSVP.resolve(1), + yourPromise: RSVP.resolve(2), + theirPromise: RSVP.resolve(3), + notAPromise: 4 + }; + + RSVP.hash(promises).then(function(hash){ + // hash here is an object that looks like: + // { + // myPromise: 1, + // yourPromise: 2, + // theirPromise: 3, + // notAPromise: 4 + // } + }); + ```` + + If any of the `promises` given to `RSVP.hash` are rejected, the first promise + that is rejected will be given as the reason to the rejection handler. + + Example: + + ```javascript + var promises = { + myPromise: RSVP.resolve(1), + rejectedPromise: RSVP.reject(new Error("rejectedPromise")), + anotherRejectedPromise: RSVP.reject(new Error("anotherRejectedPromise")), + }; + + RSVP.hash(promises).then(function(hash){ + // Code here never runs because there are rejected promises! + }, function(reason) { + // reason.message === "rejectedPromise" + }); + ``` + + An important note: `RSVP.hash` is intended for plain JavaScript objects that + are just a set of keys and values. `RSVP.hash` will NOT preserve prototype + chains. + + Example: + + ```javascript + function MyConstructor(){ + this.example = RSVP.resolve("Example"); + } + + MyConstructor.prototype = { + protoProperty: RSVP.resolve("Proto Property") + }; + + var myObject = new MyConstructor(); + + RSVP.hash(myObject).then(function(hash){ + // protoProperty will not be present, instead you will just have an + // object that looks like: + // { + // example: "Example" + // } + // + // hash.hasOwnProperty('protoProperty'); // false + // 'undefined' === typeof hash.protoProperty + }); + ``` + + @method hash + @static + @for RSVP + @param {Object} promises + @param {String} label optional string that describes the promise. + Useful for tooling. + @return {Promise} promise that is fulfilled when all properties of `promises` + have been fulfilled, or rejected if any of them become rejected. + */ + __exports__['default'] = function hash(object, label) { + return new PromiseHash(Promise, object, label).promise; + }; +}); +define('rsvp/instrument', [ + './config', + './utils', + 'exports' +], function (__dependency1__, __dependency2__, __exports__) { + 'use strict'; + var config = __dependency1__.config; + var now = __dependency2__.now; + var queue = []; + __exports__['default'] = function instrument(eventName, promise, child) { + if (1 === 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: now(), + stack: new Error(promise._label).stack + } + })) { + setTimeout(function () { + var entry; + for (var i = 0; i < queue.length; i++) { + entry = queue[i]; + config.trigger(entry.name, entry.payload); + } + queue.length = 0; + }, 50); + } + }; +}); +define('rsvp/map', [ + './promise', + './utils', + 'exports' +], function (__dependency1__, __dependency2__, __exports__) { + 'use strict'; + var Promise = __dependency1__['default']; + var isArray = __dependency2__.isArray; + var isFunction = __dependency2__.isFunction; + /** + `RSVP.map` is similar to JavaScript's native `map` method, except that it + waits for all promises to become fulfilled before running the `mapFn` on + each item in given to `promises`. `RSVP.map` returns a promise that will + become fulfilled with the result of running `mapFn` on the values the promises + become fulfilled with. + + For example: + + ```javascript + + var promise1 = RSVP.resolve(1); + var promise2 = RSVP.resolve(2); + var promise3 = RSVP.resolve(3); + var promises = [ promise1, promise2, promise3 ]; + + var mapFn = function(item){ + return item + 1; + }; + + RSVP.map(promises, mapFn).then(function(result){ + // result is [ 2, 3, 4 ] + }); + ``` + + If any of the `promises` given to `RSVP.map` are rejected, the first promise + that is rejected will be given as an argument to the returned promise's + rejection handler. For example: + + ```javascript + var promise1 = RSVP.resolve(1); + var promise2 = RSVP.reject(new Error("2")); + var promise3 = RSVP.reject(new Error("3")); + var promises = [ promise1, promise2, promise3 ]; + + var mapFn = function(item){ + return item + 1; + }; + + RSVP.map(promises, mapFn).then(function(array){ + // Code here never runs because there are rejected promises! + }, function(reason) { + // reason.message === "2" + }); + ``` + + `RSVP.map` will also wait if a promise is returned from `mapFn`. For example, + say you want to get all comments from a set of blog posts, but you need + the blog posts first because they contain a url to those comments. + + ```javscript + + var mapFn = function(blogPost){ + // getComments does some ajax and returns an RSVP.Promise that is fulfilled + // with some comments data + return getComments(blogPost.comments_url); + }; + + // getBlogPosts does some ajax and returns an RSVP.Promise that is fulfilled + // with some blog post data + RSVP.map(getBlogPosts(), mapFn).then(function(comments){ + // comments is the result of asking the server for the comments + // of all blog posts returned from getBlogPosts() + }); + ``` + + @method map + @static + @for RSVP + @param {Array} promises + @param {Function} mapFn function to be called on each fulfilled promise. + @param {String} label optional string for labeling the promise. + Useful for tooling. + @return {Promise} promise that is fulfilled with the result of calling + `mapFn` on each fulfilled promise or value when they become fulfilled. + The promise will be rejected if any of the given `promises` become rejected. + @static + */ + __exports__['default'] = function map(promises, mapFn, label) { + return Promise.all(promises, label).then(function (values) { + if (!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 Promise.all(results, label); + }); + }; +}); +define('rsvp/node', [ + './promise', + './utils', + 'exports' +], function (__dependency1__, __dependency2__, __exports__) { + 'use strict'; + /* global arraySlice */ + var Promise = __dependency1__['default']; + var isArray = __dependency2__.isArray; + /** + `RSVP.denodeify` takes a "node-style" function and returns a function that + will return an `RSVP.Promise`. You can use `denodeify` in Node.js or the + browser when you'd prefer to use promises over using callbacks. For example, + `denodeify` transforms the following: + + ```javascript + var fs = require('fs'); + + fs.readFile('myfile.txt', function(err, data){ + if (err) return handleError(err); + handleData(data); + }); + ``` + + into: + + ```javascript + var fs = require('fs'); + var readFile = RSVP.denodeify(fs.readFile); + + readFile('myfile.txt').then(handleData, handleError); + ``` + + If the node function has multiple success parameters, then `denodeify` + just returns the first one: + + ```javascript + var request = RSVP.denodeify(require('request')); + + request('http://example.com').then(function(res) { + // ... + }); + ``` + + However, if you need all success parameters, setting `denodeify`'s + second parameter to `true` causes it to return all success parameters + as an array: + + ```javascript + var request = RSVP.denodeify(require('request'), true); + + request('http://example.com').then(function(result) { + // result[0] -> res + // result[1] -> body + }); + ``` + + Or if you pass it an array with names it returns the parameters as a hash: + + ```javascript + var request = RSVP.denodeify(require('request'), ['res', 'body']); + + request('http://example.com').then(function(result) { + // result.res + // result.body + }); + ``` + + Sometimes you need to retain the `this`: + + ```javascript + var app = require('express')(); + var render = RSVP.denodeify(app.render.bind(app)); + ``` + + The denodified function inherits from the original function. It works in all + environments, except IE 10 and below. Consequently all properties of the original + function are available to you. However, any properties you change on the + denodeified function won't be changed on the original function. Example: + + ```javascript + var request = RSVP.denodeify(require('request')), + cookieJar = request.jar(); // <- Inheritance is used here + + request('http://example.com', {jar: cookieJar}).then(function(res) { + // cookieJar.cookies holds now the cookies returned by example.com + }); + ``` + + Using `denodeify` makes it easier to compose asynchronous operations instead + of using callbacks. For example, instead of: + + ```javascript + var fs = require('fs'); + + fs.readFile('myfile.txt', function(err, data){ + if (err) { ... } // Handle error + fs.writeFile('myfile2.txt', data, function(err){ + if (err) { ... } // Handle error + console.log('done') + }); + }); + ``` + + you can chain the operations together using `then` from the returned promise: + + ```javascript + var fs = require('fs'); + var readFile = RSVP.denodeify(fs.readFile); + var writeFile = RSVP.denodeify(fs.writeFile); + + readFile('myfile.txt').then(function(data){ + return writeFile('myfile2.txt', data); + }).then(function(){ + console.log('done') + }).catch(function(error){ + // Handle error + }); + ``` + + @method denodeify + @static + @for RSVP + @param {Function} nodeFunc a "node-style" function that takes a callback as + its last argument. The callback expects an error to be passed as its first + argument (if an error occurred, otherwise null), and the value from the + operation as its second argument ("function(err, value){ }"). + @param {Boolean|Array} argumentNames An optional paramter that if set + to `true` causes the promise to fulfill with the callback's success arguments + as an array. This is useful if the node function has multiple success + paramters. If you set this paramter to an array with names, the promise will + fulfill with a hash with these names as keys and the success parameters as + values. + @return {Function} a function that wraps `nodeFunc` to return an + `RSVP.Promise` + @static + */ + __exports__['default'] = function denodeify(nodeFunc, argumentNames) { + var asArray = argumentNames === true; + var asHash = isArray(argumentNames); + function denodeifiedFunction() { + var length = arguments.length; + var nodeArgs = new Array(length); + for (var i = 0; i < length; i++) { + nodeArgs[i] = arguments[i]; + } + var thisArg; + if (!asArray && !asHash && argumentNames) { + if (typeof console === 'object') { + console.warn('Deprecation: RSVP.denodeify() doesn\'t allow setting the ' + '"this" binding anymore. Use yourFunction.bind(yourThis) instead.'); + } + thisArg = argumentNames; + } else { + thisArg = this; + } + return Promise.all(nodeArgs).then(function (nodeArgs$2) { + return new Promise(resolver); + // sweet.js has a bug, this resolver can't be defined in the constructor + // or the arraySlice macro doesn't work + function resolver(resolve, reject) { + function callback() { + var length$2 = arguments.length; + var args = new Array(length$2); + for (var i$2 = 0; i$2 < length$2; i$2++) { + args[i$2] = arguments[i$2]; + } + var error = args[0]; + var value = args[1]; + if (error) { + reject(error); + } else if (asArray) { + resolve(args.slice(1)); + } else if (asHash) { + var obj = {}; + var successArguments = args.slice(1); + var name; + var i$3; + for (i$3 = 0; i$3 < argumentNames.length; i$3++) { + name = argumentNames[i$3]; + obj[name] = successArguments[i$3]; + } + resolve(obj); + } else { + resolve(value); + } + } + nodeArgs$2.push(callback); + nodeFunc.apply(thisArg, nodeArgs$2); + } + }); + } + denodeifiedFunction.__proto__ = nodeFunc; + return denodeifiedFunction; + }; +}); +define('rsvp/promise-hash', [ + './enumerator', + './-internal', + './utils', + 'exports' +], function (__dependency1__, __dependency2__, __dependency3__, __exports__) { + 'use strict'; + var Enumerator = __dependency1__['default']; + var PENDING = __dependency2__.PENDING; + var FULFILLED = __dependency2__.FULFILLED; + var o_create = __dependency3__.o_create; + function PromiseHash(Constructor, object, label) { + this._superConstructor(Constructor, object, true, label); + } + __exports__['default'] = PromiseHash; + PromiseHash.prototype = o_create(Enumerator.prototype); + PromiseHash.prototype._superConstructor = Enumerator; + PromiseHash.prototype._init = function () { + this._result = {}; + }; + PromiseHash.prototype._validateInput = function (input) { + return input && typeof input === 'object'; + }; + PromiseHash.prototype._validationError = function () { + return new Error('Promise.hash must be called with an object'); + }; + PromiseHash.prototype._enumerate = function () { + var promise = this.promise; + var input = this._input; + var results = []; + for (var key in input) { + if (promise._state === 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 === PENDING && i < length; i++) { + result = results[i]; + this._eachEntry(result.entry, result.position); + } + }; +}); +define('rsvp/promise', [ + './config', + './events', + './instrument', + './utils', + './-internal', + './promise/cast', + './promise/all', + './promise/race', + './promise/resolve', + './promise/reject', + 'exports' +], function (__dependency1__, __dependency2__, __dependency3__, __dependency4__, __dependency5__, __dependency6__, __dependency7__, __dependency8__, __dependency9__, __dependency10__, __exports__) { + 'use strict'; + var config = __dependency1__.config; + var EventTarget = __dependency2__['default']; + var instrument = __dependency3__['default']; + var objectOrFunction = __dependency4__.objectOrFunction; + var isFunction = __dependency4__.isFunction; + var now = __dependency4__.now; + var noop = __dependency5__.noop; + var resolve = __dependency5__.resolve; + var reject = __dependency5__.reject; + var fulfill = __dependency5__.fulfill; + var subscribe = __dependency5__.subscribe; + var initializePromise = __dependency5__.initializePromise; + var invokeCallback = __dependency5__.invokeCallback; + var FULFILLED = __dependency5__.FULFILLED; + var cast = __dependency6__['default']; + var all = __dependency7__['default']; + var race = __dependency8__['default']; + var Resolve = __dependency9__['default']; + var Reject = __dependency10__['default']; + var guidKey = 'rsvp_' + now() + '-'; + var counter = 0; + function needsResolver() { + throw new TypeError('You must pass a resolver function as the first argument to the promise constructor'); + } + function needsNew() { + throw new TypeError('Failed to construct \'Promise\': Please use the \'new\' operator, this object constructor cannot be called as a function.'); + } + __exports__['default'] = 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 Promise(resolver, label) { + this._id = counter++; + this._label = label; + this._subscribers = []; + if (config.instrument) { + instrument('created', this); + } + if (noop !== resolver) { + if (!isFunction(resolver)) { + needsResolver(); + } + if (!(this instanceof Promise)) { + needsNew(); + } + initializePromise(this, resolver); + } + } + Promise.cast = cast; + Promise.all = all; + Promise.race = race; + Promise.resolve = Resolve; + Promise.reject = Reject; + Promise.prototype = { + constructor: Promise, + _id: undefined, + _guidKey: guidKey, + _label: undefined, + _state: undefined, + _result: undefined, + _subscribers: undefined, + _onerror: function (reason) { + config.trigger('error', reason); + }, + then: function (onFulfillment, onRejection, label) { + var parent = this; + parent._onerror = null; + var child = new this.constructor(noop, label); + var state = parent._state; + var result = parent._result; + if (config.instrument) { + instrument('chained', parent, child); + } + if (state === FULFILLED && onFulfillment) { + config.async(function () { + invokeCallback(state, child, onFulfillment, result); + }); + } else { + subscribe(parent, child, onFulfillment, onRejection); + } + return child; + }, + 'catch': function (onRejection, label) { + return this.then(null, onRejection, label); + }, + '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); + } + }; +}); +define('rsvp/promise/all', [ + '../enumerator', + 'exports' +], function (__dependency1__, __exports__) { + 'use strict'; + var Enumerator = __dependency1__['default']; + /** + `RSVP.Promise.all` accepts an array of promises, and returns a new promise which + is fulfilled with an array of fulfillment values for the passed promises, or + rejected with the reason of the first passed promise to be rejected. It casts all + elements of the passed iterable to promises as it runs this algorithm. + + Example: + + ```javascript + var promise1 = RSVP.resolve(1); + var promise2 = RSVP.resolve(2); + var promise3 = RSVP.resolve(3); + var promises = [ promise1, promise2, promise3 ]; + + RSVP.Promise.all(promises).then(function(array){ + // The array here would be [ 1, 2, 3 ]; + }); + ``` + + If any of the `promises` given to `RSVP.all` are rejected, the first promise + that is rejected will be given as an argument to the returned promises's + rejection handler. For example: + + Example: + + ```javascript + var promise1 = RSVP.resolve(1); + var promise2 = RSVP.reject(new Error("2")); + var promise3 = RSVP.reject(new Error("3")); + var promises = [ promise1, promise2, promise3 ]; + + RSVP.Promise.all(promises).then(function(array){ + // Code here never runs because there are rejected promises! + }, function(error) { + // error.message === "2" + }); + ``` + + @method all + @static + @param {Array} entries array of promises + @param {String} label optional string for labeling the promise. + Useful for tooling. + @return {Promise} promise that is fulfilled when all `promises` have been + fulfilled, or rejected if any of them become rejected. + @static + */ + __exports__['default'] = function all(entries, label) { + return new Enumerator(this, entries, true, label).promise; + }; +}); +define('rsvp/promise/cast', [ + './resolve', + 'exports' +], function (__dependency1__, __exports__) { + 'use strict'; + var resolve = __dependency1__['default']; + /** + @deprecated + + `RSVP.Promise.cast` coerces its argument to a promise, or returns the + argument if it is already a promise which shares a constructor with the caster. + + Example: + + ```javascript + var promise = RSVP.Promise.resolve(1); + var casted = RSVP.Promise.cast(promise); + + console.log(promise === casted); // true + ``` + + In the case of a promise whose constructor does not match, it is assimilated. + The resulting promise will fulfill or reject based on the outcome of the + promise being casted. + + Example: + + ```javascript + var thennable = $.getJSON('/api/foo'); + var casted = RSVP.Promise.cast(thennable); + + console.log(thennable === casted); // false + console.log(casted instanceof RSVP.Promise) // true + + casted.then(function(data) { + // data is the value getJSON fulfills with + }); + ``` + + In the case of a non-promise, a promise which will fulfill with that value is + returned. + + Example: + + ```javascript + var value = 1; // could be a number, boolean, string, undefined... + var casted = RSVP.Promise.cast(value); + + console.log(value === casted); // false + console.log(casted instanceof RSVP.Promise) // true + + casted.then(function(val) { + val === value // => true + }); + ``` + + `RSVP.Promise.cast` is similar to `RSVP.Promise.resolve`, but `RSVP.Promise.cast` differs in the + following ways: + + * `RSVP.Promise.cast` serves as a memory-efficient way of getting a promise, when you + have something that could either be a promise or a value. RSVP.resolve + will have the same effect but will create a new promise wrapper if the + argument is a promise. + * `RSVP.Promise.cast` is a way of casting incoming thenables or promise subclasses to + promises of the exact class specified, so that the resulting object's `then` is + ensured to have the behavior of the constructor you are calling cast on (i.e., RSVP.Promise). + + @method cast + @static + @param {Object} object to be casted + @param {String} label optional string for labeling the promise. + Useful for tooling. + @return {Promise} promise + */ + __exports__['default'] = resolve; +}); +define('rsvp/promise/race', [ + '../utils', + '../-internal', + 'exports' +], function (__dependency1__, __dependency2__, __exports__) { + 'use strict'; + var isArray = __dependency1__.isArray; + var isFunction = __dependency1__.isFunction; + var isMaybeThenable = __dependency1__.isMaybeThenable; + var noop = __dependency2__.noop; + var resolve = __dependency2__.resolve; + var reject = __dependency2__.reject; + var subscribe = __dependency2__.subscribe; + var PENDING = __dependency2__.PENDING; + /** + `RSVP.Promise.race` returns a new promise which is settled in the same way as the + first passed promise to settle. + + Example: + + ```javascript + var promise1 = new RSVP.Promise(function(resolve, reject){ + setTimeout(function(){ + resolve("promise 1"); + }, 200); + }); + + var promise2 = new RSVP.Promise(function(resolve, reject){ + setTimeout(function(){ + resolve("promise 2"); + }, 100); + }); + + RSVP.Promise.race([promise1, promise2]).then(function(result){ + // result === "promise 2" because it was resolved before promise1 + // was resolved. + }); + ``` + + `RSVP.Promise.race` is deterministic in that only the state of the first + settled promise matters. For example, even if other promises given to the + `promises` array argument are resolved, but the first settled promise has + become rejected before the other promises became fulfilled, the returned + promise will become rejected: + + ```javascript + var promise1 = new RSVP.Promise(function(resolve, reject){ + setTimeout(function(){ + resolve("promise 1"); + }, 200); + }); + + var promise2 = new RSVP.Promise(function(resolve, reject){ + setTimeout(function(){ + reject(new Error("promise 2")); + }, 100); + }); + + RSVP.Promise.race([promise1, promise2]).then(function(result){ + // Code here never runs + }, function(reason){ + // reason.message === "promise 2" because promise 2 became rejected before + // promise 1 became fulfilled + }); + ``` + + An example real-world use case is implementing timeouts: + + ```javascript + RSVP.Promise.race([ajax('foo.json'), timeout(5000)]) + ``` + + @method race + @static + @param {Array} promises array of promises to observe + @param {String} label optional string for describing the promise returned. + Useful for tooling. + @return {Promise} a promise which settles in the same way as the first passed + promise to settle. + */ + __exports__['default'] = function race(entries, label) { + /*jshint validthis:true */ + var Constructor = this, entry; + var promise = new Constructor(noop, label); + if (!isArray(entries)) { + reject(promise, new TypeError('You must pass an array to race.')); + return promise; + } + var length = entries.length; + function onFulfillment(value) { + resolve(promise, value); + } + function onRejection(reason) { + reject(promise, reason); + } + for (var i = 0; promise._state === PENDING && i < length; i++) { + subscribe(Constructor.resolve(entries[i]), undefined, onFulfillment, onRejection); + } + return promise; + }; +}); +define('rsvp/promise/reject', [ + '../-internal', + 'exports' +], function (__dependency1__, __exports__) { + 'use strict'; + var noop = __dependency1__.noop; + var _reject = __dependency1__.reject; + /** + `RSVP.Promise.reject` returns a promise rejected with the passed `reason`. + It is shorthand for the following: + + ```javascript + var promise = new RSVP.Promise(function(resolve, reject){ + reject(new Error('WHOOPS')); + }); + + promise.then(function(value){ + // Code here doesn't run because the promise is rejected! + }, function(reason){ + // reason.message === 'WHOOPS' + }); + ``` + + Instead of writing the above, your code now simply becomes the following: + + ```javascript + var promise = RSVP.Promise.reject(new Error('WHOOPS')); + + promise.then(function(value){ + // Code here doesn't run because the promise is rejected! + }, function(reason){ + // reason.message === 'WHOOPS' + }); + ``` + + @method reject + @static + @param {Any} reason value that the returned promise will be rejected with. + @param {String} label optional string for identifying the returned promise. + Useful for tooling. + @return {Promise} a promise rejected with the given `reason`. + */ + __exports__['default'] = function reject(reason, label) { + /*jshint validthis:true */ + var Constructor = this; + var promise = new Constructor(noop, label); + _reject(promise, reason); + return promise; + }; +}); +define('rsvp/promise/resolve', [ + '../-internal', + 'exports' +], function (__dependency1__, __exports__) { + 'use strict'; + var noop = __dependency1__.noop; + var _resolve = __dependency1__.resolve; + /** + `RSVP.Promise.resolve` returns a promise that will become resolved with the + passed `value`. It is shorthand for the following: + + ```javascript + var promise = new RSVP.Promise(function(resolve, reject){ + resolve(1); + }); + + promise.then(function(value){ + // value === 1 + }); + ``` + + Instead of writing the above, your code now simply becomes the following: + + ```javascript + var promise = RSVP.Promise.resolve(1); + + promise.then(function(value){ + // value === 1 + }); + ``` + + @method resolve + @static + @param {Any} value value that the returned promise will be resolved with + @param {String} label optional string for identifying the returned promise. + Useful for tooling. + @return {Promise} a promise that will become fulfilled with the given + `value` + */ + __exports__['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(noop, label); + _resolve(promise, object); + return promise; + }; +}); +define('rsvp/race', [ + './promise', + 'exports' +], function (__dependency1__, __exports__) { + 'use strict'; + var Promise = __dependency1__['default']; + /** + This is a convenient alias for `RSVP.Promise.race`. + + @method race + @static + @for RSVP + @param {Array} array Array of promises. + @param {String} label An optional label. This is useful + for tooling. + */ + __exports__['default'] = function race(array, label) { + return Promise.race(array, label); + }; +}); +define('rsvp/reject', [ + './promise', + 'exports' +], function (__dependency1__, __exports__) { + 'use strict'; + var Promise = __dependency1__['default']; + /** + This is a convenient alias for `RSVP.Promise.reject`. + + @method reject + @static + @for RSVP + @param {Any} reason value that the returned promise will be rejected with. + @param {String} label optional string for identifying the returned promise. + Useful for tooling. + @return {Promise} a promise rejected with the given `reason`. + */ + __exports__['default'] = function reject(reason, label) { + return Promise.reject(reason, label); + }; +}); +define('rsvp/resolve', [ + './promise', + 'exports' +], function (__dependency1__, __exports__) { + 'use strict'; + var Promise = __dependency1__['default']; + /** + This is a convenient alias for `RSVP.Promise.resolve`. + + @method resolve + @static + @for RSVP + @param {Any} value value that the returned promise will be resolved with + @param {String} label optional string for identifying the returned promise. + Useful for tooling. + @return {Promise} a promise that will become fulfilled with the given + `value` + */ + __exports__['default'] = function resolve(value, label) { + return Promise.resolve(value, label); + }; +}); +define('rsvp/rethrow', ['exports'], function (__exports__) { + 'use strict'; + /** + `RSVP.rethrow` will rethrow an error on the next turn of the JavaScript event + loop in order to aid debugging. + + Promises A+ specifies that any exceptions that occur with a promise must be + caught by the promises implementation and bubbled to the last handler. For + this reason, it is recommended that you always specify a second rejection + handler function to `then`. However, `RSVP.rethrow` will throw the exception + outside of the promise, so it bubbles up to your console if in the browser, + or domain/cause uncaught exception in Node. `rethrow` will also throw the + error again so the error can be handled by the promise per the spec. + + ```javascript + function throws(){ + throw new Error('Whoops!'); + } + + var promise = new RSVP.Promise(function(resolve, reject){ + throws(); + }); + + promise.catch(RSVP.rethrow).then(function(){ + // Code here doesn't run because the promise became rejected due to an + // error! + }, function (err){ + // handle the error here + }); + ``` + + The 'Whoops' error will be thrown on the next turn of the event loop + and you can watch for it in your console. You can also handle it using a + rejection handler given to `.then` or `.catch` on the returned promise. + + @method rethrow + @static + @for RSVP + @param {Error} reason reason the promise became rejected. + @throws Error + @static + */ + __exports__['default'] = function rethrow(reason) { + setTimeout(function () { + throw reason; + }); + throw reason; + }; +}); +define('rsvp/utils', ['exports'], function (__exports__) { + 'use strict'; + function objectOrFunction(x) { + return typeof x === 'function' || typeof x === 'object' && x !== null; + } + __exports__.objectOrFunction = objectOrFunction; + function isFunction(x) { + return typeof x === 'function'; + } + __exports__.isFunction = isFunction; + function isMaybeThenable(x) { + return typeof x === 'object' && x !== null; + } + __exports__.isMaybeThenable = isMaybeThenable; + var _isArray; + if (!Array.isArray) { + _isArray = function (x) { + return Object.prototype.toString.call(x) === '[object Array]'; + }; + } else { + _isArray = Array.isArray; + } + var isArray = _isArray; + __exports__.isArray = isArray; + // Date.now is not available in browsers < IE9 + // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/now#Compatibility + var now = Date.now || function () { + return new Date().getTime(); + }; + __exports__.now = now; + var o_create = Object.create || function (object) { + var o = function () { + }; + o.prototype = object; + return o; + }; + __exports__.o_create = o_create; +}); +define('rsvp', [ + './rsvp/promise', + './rsvp/events', + './rsvp/node', + './rsvp/all', + './rsvp/all-settled', + './rsvp/race', + './rsvp/hash', + './rsvp/hash-settled', + './rsvp/rethrow', + './rsvp/defer', + './rsvp/config', + './rsvp/map', + './rsvp/resolve', + './rsvp/reject', + './rsvp/filter', + './rsvp/asap', + 'exports' +], function (__dependency1__, __dependency2__, __dependency3__, __dependency4__, __dependency5__, __dependency6__, __dependency7__, __dependency8__, __dependency9__, __dependency10__, __dependency11__, __dependency12__, __dependency13__, __dependency14__, __dependency15__, __dependency16__, __exports__) { + 'use strict'; + var Promise = __dependency1__['default']; + var EventTarget = __dependency2__['default']; + var denodeify = __dependency3__['default']; + var all = __dependency4__['default']; + var allSettled = __dependency5__['default']; + var race = __dependency6__['default']; + var hash = __dependency7__['default']; + var hashSettled = __dependency8__['default']; + var rethrow = __dependency9__['default']; + var defer = __dependency10__['default']; + var config = __dependency11__.config; + var configure = __dependency11__.configure; + var map = __dependency12__['default']; + var resolve = __dependency13__['default']; + var reject = __dependency14__['default']; + var filter = __dependency15__['default']; + var asap = __dependency16__['default']; + config.async = asap; + // default async is asap; + function async(callback, arg) { + config.async(callback, arg); + } + function on() { + config.on.apply(config, arguments); + } + function off() { + config.off.apply(config, arguments); + } + // Set up instrumentation through `window.__PROMISE_INTRUMENTATION__` + if (typeof window !== 'undefined' && typeof window.__PROMISE_INSTRUMENTATION__ === 'object') { + var callbacks = window.__PROMISE_INSTRUMENTATION__; + configure('instrument', true); + for (var eventName in callbacks) { + if (callbacks.hasOwnProperty(eventName)) { + on(eventName, callbacks[eventName]); + } + } + } + __exports__.Promise = Promise; + __exports__.EventTarget = EventTarget; + __exports__.all = all; + __exports__.allSettled = allSettled; + __exports__.race = race; + __exports__.hash = hash; + __exports__.hashSettled = hashSettled; + __exports__.rethrow = rethrow; + __exports__.defer = defer; + __exports__.denodeify = denodeify; + __exports__.configure = configure; + __exports__.on = on; + __exports__.off = off; + __exports__.resolve = resolve; + __exports__.reject = reject; + __exports__.async = async; + __exports__.map = map; + __exports__.filter = filter; +}); +global.RSVP = require('rsvp'); +}(self)); +EPUBJS.Book = function(_url){ + // Promises + this.opening = new RSVP.defer(); + this.opened = this.opening.promise; + this.isOpen = false; + + this.url = undefined; + + this.spine = 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); + + 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; + + // 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.origin + "/" + uri.directory; + } 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.url = _url + packageUri.directory; // Set Url relative to the content + book.encoding = paths.encoding; + + 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 + + book.spine = new EPUBJS.Spine(book.package, this.request); + + 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 rendition = new EPUBJS.Renderer(this, options); + rendition.attachTo(element); + return rendition; +}; + +EPUBJS.Book.prototype.request = function(_url) { + // Switch request methods + if(this.archived) { + // TODO: handle archived + } else { + return EPUBJS.core.request(_url, 'xml', this.credentials); + } + +}; + +EPUBJS.Book.prototype.setCredentials = function(_credentials) { + this.credentials = _credentials; +}; + +//-- 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.core = {}; + +EPUBJS.core.request = function(url, type, withCredentials) { + 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; + + 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); + 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'){ + 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({ + 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 = [], + // uri = EPUBJS.core.uri(path), + folders = base.split("/"), + paths; + + // if(uri.host) { + // return path; + // } + + folders.pop(); + + paths = path.split("/"); + paths.forEach(function(p){ + if(p === ".."){ + folders.pop(); + }else{ + segments.push(p); + } + }); + + url = folders.concat(segments); + + 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.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; + + 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.hooks = {}; +EPUBJS.Hooks = (function(){ + function hooks(){} + + //-- Get pre-registered hooks + hooks.prototype.getHooks = function(){ + var plugs, hooks; + this.hooks = {}; + Array.prototype.slice.call(arguments).forEach(function(arg){ + this.hooks[arg] = []; + }, this); + + for (var plugType in this.hooks) { + hooks = EPUBJS.hooks[plugType]; + if(hooks){ + plugs = EPUBJS.core.values(); + + plugs.forEach(function(hook){ + this.registerHook(plugType, hook); + }, this); + } + } + }; + + //-- Hooks allow for injecting async functions that must all complete before continuing + // Functions must have a callback as their first argument. + hooks.prototype.registerHook = function(type, toAdd, toFront){ + + if(typeof(this.hooks[type]) != "undefined"){ + + if(typeof(toAdd) === "function"){ + if(toFront) { + this.hooks[type].unshift(toAdd); + }else{ + this.hooks[type].push(toAdd); + } + }else if(Array.isArray(toAdd)){ + toAdd.forEach(function(hook){ + if(toFront) { + this.hooks[type].unshift(hook); + }else{ + this.hooks[type].push(hook); + } + }, this); + } + }else{ + //-- Allows for undefined hooks, but maybe this should error? + this.hooks[type] = [func]; + } + }; + + hooks.prototype.triggerHooks = function(type, callback, passed){ + var hooks, count; + + if(typeof(this.hooks[type]) == "undefined") return false; + + hooks = this.hooks[type]; + + count = hooks.length; + if(count === 0 && callback) { + callback(); + } + + function countdown(){ + count--; + if(count <= 0 && callback) callback(); + } + + hooks.forEach(function(hook){ + hook(countdown, passed); + }); + }; + + return { + register: function(name) { + if(EPUBJS.hooks[name] === undefined) { EPUBJS.hooks[name] = {}; } + if(typeof EPUBJS.hooks[name] !== 'object') { throw "Already registered: "+name; } + return EPUBJS.hooks[name]; + }, + mixin: function(object) { + for (var prop in hooks.prototype) { + object[prop] = hooks.prototype[prop]; + } + } + }; +})(); + + +EPUBJS.Infinite = function(container, renderer){ + this.container = container; + this.windowHeight = window.innerHeight; + this.tick = window.requestAnimationFrame || window.mozRequestAnimationFrame || window.webkitRequestAnimationFrame || window.msRequestAnimationFrame; + this.scrolled = false; + this.ignore = false; + this.displaying = false; + this.offset = 250; + this.views = []; + this.renderer = renderer; + this.prevScrollTop = 0; +}; + +EPUBJS.Infinite.prototype.start = function() { + + var firstScroll = true; + + this.container.addEventListener("scroll", function(e){ + if(!this.ignore) { + this.scrolled = true; + } else { + this.ignore = false; + } + // console.log("scroll", this.container.scrollTop) + }.bind(this)); + + // Reset to prevent jump + window.addEventListener('unload', function(e){ + this.ignore = true; + // window.scroll(0,0); + }); + + this.tick.call(window, this.check.bind(this)); + + this.scrolled = false; + +}; + +EPUBJS.Infinite.prototype.forwards = function() { + this.trigger("forwards"); + +}; + +EPUBJS.Infinite.prototype.backwards = function() { + this.trigger("backwards"); +}; + +/* + +// Manage Views +EPUBJS.Infinite.prototype.jump = function(view){ + this.views.push(view); +}; + +EPUBJS.Infinite.prototype.append = function(view){ + this.views.push(view); + view.appendTo(this.container); +}; + +EPUBJS.Infinite.prototype.prepend = function(view){ + this.views.unshift(view); + view.prependTo(this.container); +}; + +// Simple Insert +EPUBJS.Infinite.prototype.insert = function(view, index){ + + var position; + var distanceFront = index - this.positions[0]; + var distanceRear = index - this.positions[this.positions.length-1]; + + if(distanceFront >= 0 || !this.positions.length) { + position = this.append(view); + this.positions.push(index); + } else if(distanceRear <= 0) { + position = this.prepend(view); + this.positions.unshift(index); + } + + + + return position; +}; + +*/ + +EPUBJS.Infinite.prototype.check = function(){ + + if(this.scrolled && !this.displaying) { + + // var scrollTop = window.pageYOffset || document.documentElement.scrollTop + // var scrollLeft = window.pageXOffset || document.documentElement.scrollLeft + var scrollTop = this.container.scrollTop; + + // var scrollTop = document.body.scrollTop;//TODO: make document.body a variable + // var scrollHeight = document.documentElement.scrollHeight; + var scrollHeight = this.container.scrollHeight; + var direction = scrollTop - this.prevScrollTop; + var height = this.container.getBoundingClientRect().height; + + var up = height - (scrollHeight - scrollTop) > -this.offset; + var down = scrollTop < this.offset; + + if(up && direction > 0) { + this.forwards(); + } + else if(down && direction < 0) { + this.backwards(); + } + + // console.log(document.body.scrollTop) + this.prevScrollTop = scrollTop; + + this.scrolled = false; + } + + this.tick.call(window, this.check.bind(this)); +} + +RSVP.EventTarget.mixin(EPUBJS.Infinite.prototype); +EPUBJS.Layout = function(){}; + +/** +* 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.Layout.prototype.reconcileLayoutSettings = function(global, chapter){ + var settings = {}; + + //-- Get the global defaults + for (var attr in global) { + if (global.hasOwnProperty(attr)){ + settings[attr] = global[attr]; + } + } + //-- Get the chapter's display type + chapter.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; +}; + +/** +* 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.Layout.prototype.determineLayout = function(settings){ + // Default is layout: reflowable & spread: auto + var spreads = this.determineSpreads(this.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; + this.render.scroll(scroll); + this.trigger("renderer:spreads", spreads); + return layoutMethod; +}; + +//-- STYLES + +EPUBJS.Layout.prototype.applyStyles = function(styles) { + for (var style in styles) { + for (var view in this.views) { + view.setStyle(style, styles[style]); + } + } +}; + +EPUBJS.Layout.prototype.setStyle = function(style, val, prefixed){ + for (var view in this.views) { + view.setStyle(style, val, prefixed); + } +}; + +EPUBJS.Layout.prototype.removeStyle = function(style){ + for (var view in this.views) { + view.removeStyle(style); + } +}; + +//-- HEAD TAGS +EPUBJS.Layout.prototype.applyHeadTags = function(headTags) { + for ( var headTag in headTags ) { + this.render.addHeadTag(headTag, headTags[headTag]); + } +}; +/* +EPUBJS.Renderer.prototype.listeners = function(){ + // Dom events to listen for + this.listenedEvents = ["keydown", "keyup", "keypressed", "mouseup", "mousedown", "click"]; + this.upEvent = "mouseup"; + this.downEvent = "mousedown"; + if('ontouchstart' in document.documentElement) { + this.listenedEvents.push("touchstart", "touchend"); + this.upEvent = "touchend"; + this.downEvent = "touchstart"; + } + + // Resize events + // this.resized = _.debounce(this.onResized.bind(this), 100); + +}; + +//-- Listeners for events in the frame + +EPUBJS.Renderer.prototype.onResized = function(e) { + var width = this.container.clientWidth; + var height = this.container.clientHeight; + + this.resize(width, height, false); +}; + +EPUBJS.Renderer.prototype.addEventListeners = function(){ + if(!this.render.document) { + return; + } + this.listenedEvents.forEach(function(eventName){ + this.render.document.addEventListener(eventName, this.triggerEvent.bind(this), false); + }, this); + +}; + +EPUBJS.Renderer.prototype.removeEventListeners = function(){ + if(!this.render.document) { + return; + } + this.listenedEvents.forEach(function(eventName){ + this.render.document.removeEventListener(eventName, this.triggerEvent, false); + }, this); + +}; + +// Pass browser events +EPUBJS.Renderer.prototype.triggerEvent = function(e){ + this.trigger("renderer:"+e.type, e); +}; + +EPUBJS.Renderer.prototype.addSelectionListeners = function(){ + this.render.document.addEventListener("selectionchange", this.onSelectionChange.bind(this), false); +}; + +EPUBJS.Renderer.prototype.removeSelectionListeners = function(){ + if(!this.render.document) { + return; + } + this.doc.removeEventListener("selectionchange", this.onSelectionChange, false); +}; + +EPUBJS.Renderer.prototype.onSelectionChange = function(e){ + if (this.selectionEndTimeout) { + clearTimeout(this.selectionEndTimeout); + } + this.selectionEndTimeout = setTimeout(function() { + this.selectedRange = this.render.window.getSelection(); + this.trigger("renderer:selected", this.selectedRange); + }.bind(this), 500); +}; +*/ +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; + + if(this.nav) { + loading = this.nav.load(); + } else if(this.ncx) { + loading = this.ncx.load(); + } + + return loading; + +}; + +EPUBJS.Navigation.prototype.loaded = function(toc) { + var item; + + for (var i = 0; i < toc.length; i++) { + var 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.Parser = function(){}; + +EPUBJS.Parser.prototype.container = function(containerXml){ + //-- + 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: +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 + }; + + }); + + 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, + }; + 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.Renderer = function(book, _options) { + var options = _options || {}; + this.settings = { + hidden: options.hidden || false, + viewLimit: 3, + width: options.width || false, + height: options.height || false, + }; + + this.book = book; + + // Listen for load events + // this.on("render:loaded", this.loaded.bind(this)); + + // Blank Cfi for Parsing + this.epubcfi = new EPUBJS.EpubCFI(); + + this.layoutSettings = {}; + + //-- Adds Hook methods to the Book prototype + // Hooks will all return before triggering the callback. + EPUBJS.Hooks.mixin(this); + + //-- Get pre-registered hooks for events + this.getHooks("beforeChapterDisplay"); + + //-- Queue up page changes if page map isn't ready + this._q = EPUBJS.core.queue(this); + + this.position = 1; + + this.initialize({ + "width" : this.settings.width, + "height" : this.settings.height, + "hidden" : true + }); + + this.rendering = false; + this.views = []; + this.positions = []; + +}; + +/** +* Creates an element to render to. +* Resizes to passed width and height or to the elements size +*/ +EPUBJS.Renderer.prototype.initialize = function(_options){ + var options = _options || {}; + var height = options.height ? options.height + "px" : "100%"; + var width = options.width ? options.width + "px" : "100%"; + var hidden = options.hidden || false; + + + this.container = document.createElement("div"); + this.infinite = new EPUBJS.Infinite(this.container, this); + + this.container.style.width = height; + this.container.style.height = width; + this.container.style.overflow = "scroll"; + + if(options.hidden) { + this.wrapper = document.createElement("div"); + this.wrapper.style.visibility = "hidden"; + this.wrapper.style.overflow = "hidden"; + this.wrapper.style.width = "0"; + this.wrapper.style.height = "0"; + + this.wrapper.appendChild(this.container); + return this.wrapper; + } + + return this.container; +}; + +EPUBJS.Renderer.prototype.resize = function(_width, _height){ + var width = _width; + var height = _height; + + if(!_width) { + width = window.innerWidth; + } + if(!_height) { + height = window.innerHeight; + } + + this.container.style.width = width + "px"; + this.container.style.height = height + "px"; + + this.trigger("resized", { + width: this.width, + height: this.height + }); + +}; + +EPUBJS.Renderer.prototype.onResized = function(e) { + var bounds = this.element.getBoundingClientRect(); + + + this.resize(bounds.width, bounds.height); +}; + +EPUBJS.Renderer.prototype.attachTo = function(_element){ + var bounds; + + 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; + } + + this.element.appendChild(this.container); + + if(!this.settings.height && !this.settings.width) { + bounds = this.element.getBoundingClientRect(); + + this.resize(bounds.width, bounds.height); + } + + this.infinite.start(); + + this.infinite.on("forwards", this.forwards.bind(this)); + this.infinite.on("backwards", this.backwards.bind(this)); + + window.addEventListener("resize", this.onResized.bind(this), false); + +}; + + +EPUBJS.Renderer.prototype.display = function(what){ + var displaying = new RSVP.defer(); + var displayed = displaying.promise; + var view = new EPUBJS.View(); + + this.book.opened.then(function(){ + var section = this.book.spine.get(what); + var rendered = this.render(section); + + rendered.then(function(){ + this.fill(); + displaying.resolve(this); + }.bind(this)); + + }.bind(this)); + + return displayed; +}; + +EPUBJS.Renderer.prototype.render = function(section){ + var rendered; + var view = new EPUBJS.View(); + + if(!section) { + rendered.reject(); + return; + }; + + rendered = section.render(); + view.index = section.index; + + // Place view in correct position + this.insert(view, section.index); + + return rendered.then(function(contents){ + return view.load(contents); + }); + +}; + + +EPUBJS.Renderer.prototype.forwards = function(){ + var next; + var rendered; + var section; + + if(this.rendering) return; + console.log("going forwards") + + next = this.last().index + 1; + + if(next === this.book.spine.length){ + return; + } + + section = this.book.spine.get(next); + rendered = this.render(section); + this.rendering = true; + + rendered.then(function(){ + console.log("last:", this.last().height) + + this.rendering = false; + }.bind(this)); + + return rendered; +}; + +EPUBJS.Renderer.prototype.backwards = function(view){ + var prev; + var rendered; + var section; + + if(this.rendering) return; + console.log("going backwards") + + prev = this.first().index - 1; + if(prev < 0){ + return; //TODO: should reject + } + + section = this.book.spine.get(prev); + rendered = this.render(section); + this.rendering = true; + + rendered.then(function(){ + this.rendering = false; + // -- this might want to be in infinite + this.container.scrollTop += this.first().height; + + }.bind(this)); + + return rendered; +}; + + +// Manage Views + +// -- this might want to be in infinite +EPUBJS.Renderer.prototype.fill = function() { + console.log("filling") + var filling = this.backwards(); + var height = this.container.getBoundingClientRect().height; + filling.then(function(){ + var bottom = this.last().bounds().bottom; + while (height && bottom && bottom < height) { + this.forwards(); + }; + }.bind(this)); +}; + +EPUBJS.Renderer.prototype.jump = function(view){ + this.views.push(view); +}; + +EPUBJS.Renderer.prototype.append = function(view){ + this.views.push(view); + view.appendTo(this.container); +}; + +EPUBJS.Renderer.prototype.prepend = function(view){ + this.views.unshift(view); + view.prependTo(this.container); +}; + +// Simple Insert +EPUBJS.Renderer.prototype.insert = function(view, index){ + + if(!this.first()) { + this.append(view); + } else if(index - this.first().index >= 0) { + this.append(view); + } else if(index - this.last().index <= 0) { + this.prepend(view); + } + + // return position; +}; + +// Remove the render element and clean up listeners +EPUBJS.Renderer.prototype.destroy = function() { + +}; + +EPUBJS.Renderer.prototype.first = function() { + return this.views[0]; +}; + +EPUBJS.Renderer.prototype.last = function() { + return this.views[this.views.length-1]; +}; + +//-- Enable binding events to Renderer +RSVP.EventTarget.mixin(EPUBJS.Renderer.prototype); +EPUBJS.Spine = function(_package, _request){ + this.items = _package.spine; + this.manifest = _package.manifest; + this.spineNodeIndex = _package.spineNodeIndex; + this.baseUrl = _package.baseUrl || ''; + this.request = _request; + this.length = this.items.length; + this.epubcfi = new EPUBJS.EpubCFI(); + this.spineItems = []; + this.spineByHref = {}; + this.spineById = {}; + + this.items.forEach(function(item, index){ + var cfiBase = this.epubcfi.generateChapterComponent(this.spineNodeIndex, item.index, item.idref); + var href, url; + var manifestItem = this.manifest[item.idref]; + var spineItem; + + if(manifestItem) { + href = manifestItem.href; + url = this.baseUrl + href; + } + + spineItem = new EPUBJS.SpineItem(item, href, url, cfiBase); + this.spineItems.push(spineItem); + + this.spineByHref[spineItem.href] = index; + this.spineById[spineItem.idref] = index; + + + }.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(target && (typeof target === "number" || isNaN(target) === false)){ + index = target; + } else if(target && target.indexOf("#") === 0) { + index = this.spineById[target.substring(1)]; + } else if(target) { + index = this.spineByHref[target]; + } + + return this.spineItems[index]; +}; + + +EPUBJS.SpineItem = function(item, href, url, cfiBase){ + this.idref = item.idref; + this.linear = item.linear; + this.properties = item.properties; + this.index = item.index; + this.href = href; + this.url = url; + this.cfiBase = cfiBase; +}; + + +EPUBJS.SpineItem.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; + + this.replacements(this.document); + + loading.resolve(this.contents); + }.bind(this)); + } + + return loaded; +}; + +EPUBJS.SpineItem.prototype.replacements = function(_document){ + var base = _document.createElement("base"); + base.setAttribute("href", this.url); + _document.head.insertBefore(base, _document.head.firstChild); +}; + +EPUBJS.SpineItem.prototype.render = function(){ + var rendering = new RSVP.defer(); + var rendered = rendering.promise; + + this.load().then(function(contents){ + var serializer = new XMLSerializer(); + var output = serializer.serializeToString(contents); + rendering.resolve(output); + }); + + return rendered; +}; + +EPUBJS.SpineItem.prototype.find = function(_query){ + +}; +EPUBJS.View = function(options) { + this.id = "epubjs-view:" + EPUBJS.core.uuid(); + this.loading = new RSVP.defer(); + this.loaded = this.loading.promise; + this.iframe = this.create(); + this.height; + this.width; +}; + +EPUBJS.View.prototype.load = function(contents) { + var loading = new RSVP.defer(); + var loaded = loading.promise; + + this.document = this.iframe.contentDocument; + + // this.iframe.srcdoc = contents; + this.document.open(); + this.document.write(contents); + this.document.close(); + + this.iframe.onload = function(e) { + this.window = this.iframe.contentWindow; + this.window.addEventListener("resize", this.resized.bind(this), false); + this.document = this.iframe.contentDocument; + + this.iframe.style.display = "block"; + + // Reset Body Styles + this.document.body.style.margin = "0"; + this.document.body.style.display = "inline-block"; + + this.layout(); + + this.iframe.style.visibility = "visible"; + + loading.resolve(this); + this.loading.resolve(this); + }.bind(this); + + return loaded; +}; + + +EPUBJS.View.prototype.unload = function() { + +}; + +EPUBJS.View.prototype.create = function() { + this.iframe = document.createElement('iframe'); + this.iframe.id = this.id; + this.iframe.scrolling = "no"; + this.iframe.seamless = "seamless"; + // Back up if seamless isn't supported + this.iframe.style.border = "none"; + this.iframe.width = "100%"; + this.iframe.style.height = "0"; + + this.iframe.style.display = "none"; + this.iframe.style.visibility = "hidden"; + + return this.iframe; +}; + +EPUBJS.View.prototype.resized = function() { + + if (!this.resizing) { + this.layout(); + } else { + this.resizing = false; + } + +}; + +EPUBJS.View.prototype.layout = function() { + var bounds = {}, content, width = 0, height = 0; + + // This needs to run twice to get to the correct size + while(bounds.height != height || bounds.width != width) { + this.resizing = true; + + // Check bounds + bounds = this.document.body.getBoundingClientRect(); + + // Apply Changes + this.iframe.style.height = bounds.height + "px"; + this.iframe.style.width = bounds.width + "px"; + + // Check again + content = this.document.body.getBoundingClientRect(); + + height = content.height; + width = content.width; + + this.width = width; + this.height = height; + } + + +}; + +EPUBJS.View.prototype.appendTo = function(element) { + element.appendChild(this.iframe); +}; + +EPUBJS.View.prototype.prependTo = function(element) { + element.insertBefore(this.iframe, element.firstChild); +}; + +EPUBJS.View.prototype.bounds = function() { + return this.iframe.getBoundingClientRect(); +}; + +EPUBJS.View.prototype.destroy = function() { + +}; + diff --git a/dist/epub.min.js b/dist/epub.min.js new file mode 100644 index 0000000..cafb23a --- /dev/null +++ b/dist/epub.min.js @@ -0,0 +1,2 @@ +"undefined"==typeof EPUBJS&&(("undefined"!=typeof window?window:this).EPUBJS={}),EPUBJS.VERSION="0.3.0",EPUBJS.Render={},function(e){"use strict";var t=function(e){return new EPUBJS.Book(e)};t.Render={register:function(e,r){t.Render[e]=r}},"object"==typeof exports?(e.RSVP=require("rsvp"),module.exports=t):"function"==typeof define&&define.amd?define(t):e.ePub=t}(this),function(e){var t,r;!function(){var e={},n={};t=function(t,r,n){e[t]={deps:r,callback:n}},r=function(t){function i(e){if("."!==e.charAt(0))return e;for(var r=e.split("/"),n=t.split("/").slice(0,-1),i=0,o=r.length;o>i;i++){var s=r[i];if(".."===s)n.pop();else{if("."===s)continue;n.push(s)}}return n.join("/")}if(n[t])return n[t];if(n[t]={},!e[t])throw new Error("Could not find module "+t);for(var o,s=e[t],a=s.deps,u=s.callback,c=[],p=0,h=a.length;h>p;p++)c.push("exports"===a[p]?o={}:r(i(a[p])));var l=u.apply(this,c);return n[t]=o||l},r.entries=e}(),t("rsvp/-internal",["./utils","./instrument","./config","exports"],function(e,t,r,n){"use strict";function i(){}function o(e){try{return e.then}catch(t){return J.error=t,J}}function s(e,t,r,n){try{e.call(t,r,n)}catch(i){return i}}function a(e,t,r){w.async(function(e){var n=!1,i=s(r,t,function(r){n||(n=!0,t!==r?p(e,r):l(e,r))},function(t){n||(n=!0,f(e,t))},"Settle: "+(e._label||" unknown promise"));!n&&i&&(n=!0,f(e,i))},e)}function u(e,t){e._onerror=null,t._state===x?l(e,t._result):e._state===U?f(e,t._result):d(t,void 0,function(r){t!==r?p(e,r):l(e,r)},function(t){f(e,t)})}function c(e,t){if(t instanceof e.constructor)u(e,t);else{var r=o(t);r===J?f(e,J.error):void 0===r?l(e,t):P(r)?a(e,t,r):l(e,t)}}function p(e,t){e===t?l(e,t):S(t)?c(e,t):l(e,t)}function h(e){e._onerror&&e._onerror(e._result),v(e)}function l(e,t){e._state===B&&(e._result=t,e._state=x,0===e._subscribers.length?w.instrument&&b("fulfilled",e):w.async(v,e))}function f(e,t){e._state===B&&(e._state=U,e._result=t,w.async(h,e))}function d(e,t,r,n){var i=e._subscribers,o=i.length;e._onerror=null,i[o]=t,i[o+x]=r,i[o+U]=n,0===o&&e._state&&w.async(v,e)}function v(e){var t=e._subscribers,r=e._state;if(w.instrument&&b(r===x?"fulfilled":"rejected",e),0!==t.length){for(var n,i,o=e._result,s=0;se;e+=2){var t=h[e],r=h[e+1];t(r),h[e]=void 0,h[e+1]=void 0}s=0}var s=0;e["default"]=function(e,t){h[s]=e,h[s+1]=t,s+=2,2===s&&a()};var a,u="undefined"!=typeof window?window:{},c=u.MutationObserver||u.WebKitMutationObserver,p="undefined"!=typeof Uint8ClampedArray&&"undefined"!=typeof importScripts&&"undefined"!=typeof MessageChannel,h=new Array(1e3);a="undefined"!=typeof process&&"[object process]"==={}.toString.call(process)?t():c?r():p?n():i()}),t("rsvp/config",["./events","exports"],function(e,t){"use strict";function r(e,t){return"onerror"===e?void i.on("error",t):2!==arguments.length?i[e]:void(i[e]=t)}var n=e["default"],i={instrument:!1};n.mixin(i),t.config=i,t.configure=r}),t("rsvp/defer",["./promise","exports"],function(e,t){"use strict";var r=e["default"];t["default"]=function(e){var t={};return t.promise=new r(function(e,r){t.resolve=e,t.reject=r},e),t}}),t("rsvp/enumerator",["./utils","./-internal","exports"],function(e,t,r){"use strict";function n(e,t,r){return e===h?{state:"fulfilled",value:r}:{state:"rejected",reason:r}}function i(e,t,r,n){this._instanceConstructor=e,this.promise=new e(a,n),this._abortOnReject=r,this._validateInput(t)?(this._input=t,this.length=t.length,this._remaining=t.length,this._init(),0===this.length?c(this.promise,this._result):(this.length=this.length||0,this._enumerate(),0===this._remaining&&c(this.promise,this._result))):u(this.promise,this._validationError())}var o=e.isArray,s=e.isMaybeThenable,a=t.noop,u=t.reject,c=t.fulfill,p=t.subscribe,h=t.FULFILLED,l=t.REJECTED,f=t.PENDING,d=!0;r.ABORT_ON_REJECTION=d,r.makeSettledResult=n,i.prototype._validateInput=function(e){return o(e)},i.prototype._validationError=function(){return new Error("Array Methods must be provided an Array")},i.prototype._init=function(){this._result=new Array(this.length)},r["default"]=i,i.prototype._enumerate=function(){for(var e=this.length,t=this.promise,r=this._input,n=0;t._state===f&&e>n;n++)this._eachEntry(r[n],n)},i.prototype._eachEntry=function(e,t){var r=this._instanceConstructor;s(e)?e.constructor===r&&e._state!==f?(e._onerror=null,this._settledAt(e._state,t,e._result)):this._willSettleAt(r.resolve(e),t):(this._remaining--,this._result[t]=this._makeResult(h,t,e))},i.prototype._settledAt=function(e,t,r){var n=this.promise;n._state===f&&(this._remaining--,this._abortOnReject&&e===l?u(n,r):this._result[t]=this._makeResult(e,t,r)),0===this._remaining&&c(n,this._result)},i.prototype._makeResult=function(e,t,r){return r},i.prototype._willSettleAt=function(e,t){var r=this;p(e,void 0,function(e){r._settledAt(h,t,e)},function(e){r._settledAt(l,t,e)})}}),t("rsvp/events",["exports"],function(e){"use strict";function t(e,t){for(var r=0,n=e.length;n>r;r++)if(e[r]===t)return r;return-1}function r(e){var t=e._promiseCallbacks;return t||(t=e._promiseCallbacks={}),t}e["default"]={mixin:function(e){return e.on=this.on,e.off=this.off,e.trigger=this.trigger,e._promiseCallbacks=void 0,e},on:function(e,n){var i,o=r(this);i=o[e],i||(i=o[e]=[]),-1===t(i,n)&&i.push(n)},off:function(e,n){var i,o,s=r(this);return n?(i=s[e],o=t(i,n),void(-1!==o&&i.splice(o,1))):void(s[e]=[])},trigger:function(e,t){var n,i,o=r(this);if(n=o[e])for(var s=0;sa;a++)s[a]=t(e[a]);return n.all(s,r).then(function(t){for(var r=new Array(o),n=0,i=0;o>i;i++)t[i]&&(r[n]=e[i],n++);return r.length=n,r})})}}),t("rsvp/hash-settled",["./promise","./enumerator","./promise-hash","./utils","exports"],function(e,t,r,n,i){"use strict";function o(e,t,r){this._superConstructor(e,t,!1,r)}var s=e["default"],a=t.makeSettledResult,u=r["default"],c=t["default"],p=n.o_create;o.prototype=p(u.prototype),o.prototype._superConstructor=c,o.prototype._makeResult=a,o.prototype._validationError=function(){return new Error("hashSettled must be called with an object")},i["default"]=function(e,t){return new o(s,e,t).promise}}),t("rsvp/hash",["./promise","./promise-hash","./enumerator","exports"],function(e,t,r,n){"use strict";{var i=e["default"],o=t["default"];r.ABORT_ON_REJECTION}n["default"]=function(e,t){return new o(i,e,t).promise}}),t("rsvp/instrument",["./config","./utils","exports"],function(e,t,r){"use strict";var n=e.config,i=t.now,o=[];r["default"]=function(e,t,r){1===o.push({name:e,payload:{guid:t._guidKey+t._id,eventName:e,detail:t._result,childGuid:r&&t._guidKey+r._id,label:t._label,timeStamp:i(),stack:new Error(t._label).stack}})&&setTimeout(function(){for(var e,t=0;ta;a++)s[a]=t(e[a]);return n.all(s,r)})}}),t("rsvp/node",["./promise","./utils","exports"],function(e,t,r){"use strict";var n=e["default"],i=t.isArray;r["default"]=function(e,t){function r(){for(var r=arguments.length,i=new Array(r),a=0;r>a;a++)i[a]=arguments[a];var u;return o||s||!t?u=this:("object"==typeof console&&console.warn('Deprecation: RSVP.denodeify() doesn\'t allow setting the "this" binding anymore. Use yourFunction.bind(yourThis) instead.'),u=t),n.all(i).then(function(r){function i(n,i){function a(){for(var e=arguments.length,r=new Array(e),a=0;e>a;a++)r[a]=arguments[a];var u=r[0],c=r[1];if(u)i(u);else if(o)n(r.slice(1));else if(s){var p,h,l={},f=r.slice(1);for(h=0;ha;a++)o=r[a],this._eachEntry(o.entry,o.position)}}),t("rsvp/promise",["./config","./events","./instrument","./utils","./-internal","./promise/cast","./promise/all","./promise/race","./promise/resolve","./promise/reject","exports"],function(e,t,r,n,i,o,s,a,u,c,p){"use strict";function h(){throw new TypeError("You must pass a resolver function as the first argument to the promise constructor")}function l(){throw new TypeError("Failed to construct 'Promise': Please use the 'new' operator, this object constructor cannot be called as a function.")}function f(e,t){this._id=k++,this._label=t,this._subscribers=[],d.instrument&&v("created",this),m!==e&&(g(e)||h(),this instanceof f||l(),S(this,e))}var d=e.config,v=(t["default"],r["default"]),g=(n.objectOrFunction,n.isFunction),y=n.now,m=i.noop,E=(i.resolve,i.reject,i.fulfill,i.subscribe),S=i.initializePromise,P=i.invokeCallback,b=i.FULFILLED,w=o["default"],B=s["default"],x=a["default"],U=u["default"],J=c["default"],_="rsvp_"+y()+"-",k=0;p["default"]=f,f.cast=w,f.all=B,f.race=x,f.resolve=U,f.reject=J,f.prototype={constructor:f,_id:void 0,_guidKey:_,_label:void 0,_state:void 0,_result:void 0,_subscribers:void 0,_onerror:function(e){d.trigger("error",e)},then:function(e,t,r){var n=this;n._onerror=null;var i=new this.constructor(m,r),o=n._state,s=n._result;return d.instrument&&v("chained",n,i),o===b&&e?d.async(function(){P(o,i,e,s)}):E(n,i,e,t),i},"catch":function(e,t){return this.then(null,e,t)},"finally":function(e,t){var r=this.constructor;return this.then(function(t){return r.resolve(e()).then(function(){return t})},function(t){return r.resolve(e()).then(function(){throw t})},t)}}}),t("rsvp/promise/all",["../enumerator","exports"],function(e,t){"use strict";var r=e["default"];t["default"]=function(e,t){return new r(this,e,!0,t).promise}}),t("rsvp/promise/cast",["./resolve","exports"],function(e,t){"use strict";var r=e["default"];t["default"]=r}),t("rsvp/promise/race",["../utils","../-internal","exports"],function(e,t,r){"use strict";var n=e.isArray,i=(e.isFunction,e.isMaybeThenable,t.noop),o=t.resolve,s=t.reject,a=t.subscribe,u=t.PENDING;r["default"]=function(e,t){function r(e){o(h,e)}function c(e){s(h,e)}var p=this,h=new p(i,t);if(!n(e))return s(h,new TypeError("You must pass an array to race.")),h;for(var l=e.length,f=0;h._state===u&&l>f;f++)a(p.resolve(e[f]),void 0,r,c);return h}}),t("rsvp/promise/reject",["../-internal","exports"],function(e,t){"use strict";var r=e.noop,n=e.reject;t["default"]=function(e,t){var i=this,o=new i(r,t);return n(o,e),o}}),t("rsvp/promise/resolve",["../-internal","exports"],function(e,t){"use strict";var r=e.noop,n=e.resolve;t["default"]=function(e,t){var i=this;if(e&&"object"==typeof e&&e.constructor===i)return e;var o=new i(r,t);return n(o,e),o}}),t("rsvp/race",["./promise","exports"],function(e,t){"use strict";var r=e["default"];t["default"]=function(e,t){return r.race(e,t)}}),t("rsvp/reject",["./promise","exports"],function(e,t){"use strict";var r=e["default"];t["default"]=function(e,t){return r.reject(e,t)}}),t("rsvp/resolve",["./promise","exports"],function(e,t){"use strict";var r=e["default"];t["default"]=function(e,t){return r.resolve(e,t)}}),t("rsvp/rethrow",["exports"],function(e){"use strict";e["default"]=function(e){throw setTimeout(function(){throw e}),e}}),t("rsvp/utils",["exports"],function(e){"use strict";function t(e){return"function"==typeof e||"object"==typeof e&&null!==e}function r(e){return"function"==typeof e}function n(e){return"object"==typeof e&&null!==e}e.objectOrFunction=t,e.isFunction=r,e.isMaybeThenable=n;var i;i=Array.isArray?Array.isArray:function(e){return"[object Array]"===Object.prototype.toString.call(e)};var o=i;e.isArray=o;var s=Date.now||function(){return(new Date).getTime()};e.now=s;var a=Object.create||function(e){var t=function(){};return t.prototype=e,t};e.o_create=a}),t("rsvp",["./rsvp/promise","./rsvp/events","./rsvp/node","./rsvp/all","./rsvp/all-settled","./rsvp/race","./rsvp/hash","./rsvp/hash-settled","./rsvp/rethrow","./rsvp/defer","./rsvp/config","./rsvp/map","./rsvp/resolve","./rsvp/reject","./rsvp/filter","./rsvp/asap","exports"],function(e,t,r,n,i,o,s,a,u,c,p,h,l,f,d,v,g){"use strict";function y(e,t){C.async(e,t)}function m(){C.on.apply(C,arguments)}function E(){C.off.apply(C,arguments)}var S=e["default"],P=t["default"],b=r["default"],w=n["default"],B=i["default"],x=o["default"],U=s["default"],J=a["default"],_=u["default"],k=c["default"],C=p.config,R=p.configure,T=h["default"],N=l["default"],I=f["default"],F=d["default"],A=v["default"];if(C.async=A,"undefined"!=typeof window&&"object"==typeof window.__PROMISE_INSTRUMENTATION__){var O=window.__PROMISE_INSTRUMENTATION__;R("instrument",!0);for(var j in O)O.hasOwnProperty(j)&&m(j,O[j])}g.Promise=S,g.EventTarget=P,g.all=w,g.allSettled=B,g.race=x,g.hash=U,g.hashSettled=J,g.rethrow=_,g.defer=k,g.denodeify=b,g.configure=R,g.on=m,g.off=E,g.resolve=N,g.reject=I,g.async=y,g.map=T,g.filter=F}),e.RSVP=r("rsvp")}(self),EPUBJS.Book=function(e){this.opening=new RSVP.defer,this.opened=this.opening.promise,this.isOpen=!1,this.url=void 0,this.spine=void 0,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),this.isRendered=!1,this._q=EPUBJS.core.queue(this),e&&this.open(e)},EPUBJS.Book.prototype.open=function(e){var t,r,n,i=new EPUBJS.Parser,o=this,s="META-INF/container.xml";return t="object"==typeof e?e:EPUBJS.core.uri(e),"opf"===t.extension?(this.packageUrl=t.href,this.containerUrl="",t.origin?this.url=t.origin+"/"+t.directory:window?(n=EPUBJS.core.uri(window.location.href),this.url=EPUBJS.core.resolveUrl(n.base,t.directory)):this.url=t.directory,r=this.request(this.packageUrl)):"epub"===t.extension||"zip"===t.extension?(this.archived=!0,this.url=""):t.extension||(this.containerUrl=e+s,r=this.request(this.containerUrl).then(function(e){return i.container(e)}).then(function(t){var r=EPUBJS.core.uri(t.packagePath);return o.packageUrl=e+t.packagePath,o.url=e+r.directory,o.encoding=t.encoding,o.request(o.packageUrl)}).catch(function(e){console.error("Could not load book at: "+(this.packageUrl||this.containerPath)),o.trigger("book:loadFailed",this.packageUrl||this.containerPath),o.opening.reject(e)})),r.then(function(e){o.unpack(e),o.loading.manifest.resolve(o.package.manifest),o.loading.metadata.resolve(o.package.metadata),o.loading.spine.resolve(o.spine),o.loading.cover.resolve(o.cover),this.isOpen=!0,o.opening.resolve(o)}).catch(function(e){console.error(e.message,e.stack),o.opening.reject(e)}),this.opened},EPUBJS.Book.prototype.unpack=function(e){var t=this,r=new EPUBJS.Parser;t.package=r.packageContents(e),t.package.baseUrl=t.url,t.spine=new EPUBJS.Spine(t.package,this.request),t.navigation=new EPUBJS.Navigation(t.package,this.request),t.navigation.load().then(function(e){t.toc=e,t.loading.navigation.resolve(t.toc)}),t.cover=t.url+t.package.coverPath},EPUBJS.Book.prototype.section=function(e){return this.spine.get(e)},EPUBJS.Book.prototype.renderTo=function(e,t){var r=new EPUBJS.Renderer(this,t);return r.attachTo(e),r},EPUBJS.Book.prototype.request=function(e){return this.archived?void 0:EPUBJS.core.request(e,"xml",this.credentials)},EPUBJS.Book.prototype.setCredentials=function(e){this.credentials=e},RSVP.EventTarget.mixin(EPUBJS.Book.prototype),RSVP.on("error",function(){}),RSVP.configure("instrument",!0),RSVP.on("rejected",function(e){console.error(e.detail.message,e.detail.stack)}),EPUBJS.core={},EPUBJS.core.request=function(e,t,r){function n(){if(this.readyState===this.DONE)if(200===this.status||this.responseXML){var e;e="xml"==t?this.responseXML:"json"==t?JSON.parse(this.response):"blob"==t?i?this.response:new Blob([this.response]):this.response,s.resolve(e)}else s.reject({message:this.response,stack:(new Error).stack})}var i=window.URL,o=i?"blob":"arraybuffer",s=new RSVP.defer,a=new XMLHttpRequest,u=XMLHttpRequest.prototype;return"overrideMimeType"in u||Object.defineProperty(u,"overrideMimeType",{value:function(){}}),r&&(a.withCredentials=!0),a.open("GET",e,!0),a.onreadystatechange=n,"blob"==t&&(a.responseType=o),"json"==t&&a.setRequestHeader("Accept","application/json"),"xml"==t&&a.overrideMimeType("text/xml"),a.send(),s.promise},EPUBJS.core.uri=function(e){var t,r,n,i={protocol:"",host:"",path:"",origin:"",directory:"",base:"",filename:"",extension:"",fragment:"",href:e},o=e.indexOf("://"),s=e.indexOf("?"),a=e.indexOf("#");return-1!=a&&(i.fragment=e.slice(a+1),e=e.slice(0,a)),-1!=s&&(i.search=e.slice(s+1),e=e.slice(0,s),href=e),-1!=o?(i.protocol=e.slice(0,o),t=e.slice(o+3),n=t.indexOf("/"),-1===n?(i.host=i.path,i.path=""):(i.host=t.slice(0,n),i.path=t.slice(n)),i.origin=i.protocol+"://"+i.host,i.directory=EPUBJS.core.folder(i.path),i.base=i.origin+i.directory):(i.path=e,i.directory=EPUBJS.core.folder(e),i.base=i.directory),i.filename=e.replace(i.base,""),r=i.filename.lastIndexOf("."),-1!=r&&(i.extension=i.filename.slice(r+1)),i},EPUBJS.core.folder=function(e){var t=e.lastIndexOf("/");if(-1==t)var r="";return r=e.slice(0,t+1)},EPUBJS.core.queue=function(e){var t=[],r=e,n=function(e,r,n){return t.push({funcName:e,args:r,context:n}),t},i=function(){var e;t.length&&(e=t.shift(),r[e.funcName].apply(e.context||r,e.args))},o=function(){for(;t.length;)i()},s=function(){t=[]},a=function(){return t.length};return{enqueue:n,dequeue:i,flush:o,clear:s,length:a}},EPUBJS.core.isElement=function(e){return!(!e||1!=e.nodeType)},EPUBJS.core.uuid=function(){var e=(new Date).getTime(),t="xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g,function(t){var r=(e+16*Math.random())%16|0;return e=Math.floor(e/16),("x"==t?r:7&r|8).toString(16)});return t},EPUBJS.core.values=function(e){for(var t=-1,r=Object.keys(e),n=r.length,i=Array(n);++t0;){if(r=n.shift(),"text"===r.type?(i=s.childNodes[r.index],s=i.parentNode||s):s=r.id?o.getElementById(r.id):a[r.index],"undefined"==typeof s)return console.error("No Element For",r,e.str),!1;a=Array.prototype.slice.call(s.children)}return s},EPUBJS.EpubCFI.prototype.compare=function(e,t){if("string"==typeof e&&(e=new EPUBJS.EpubCFI(e)),"string"==typeof t&&(t=new EPUBJS.EpubCFI(t)),e.spinePos>t.spinePos)return 1;if(e.spinePost.steps[r].index)return 1;if(e.steps[r].indext.characterOffset?1:e.characterOffset=0?(o=i.length,e.characterOffset=o&&t&&t()}var i,o;return"undefined"==typeof this.hooks[e]?!1:(i=this.hooks[e],o=i.length,0===o&&t&&t(),void i.forEach(function(e){e(n,r)}))},{register:function(e){if(void 0===EPUBJS.hooks[e]&&(EPUBJS.hooks[e]={}),"object"!=typeof EPUBJS.hooks[e])throw"Already registered: "+e;return EPUBJS.hooks[e]},mixin:function(t){for(var r in e.prototype)t[r]=e.prototype[r]}}}(),EPUBJS.Infinite=function(e,t){this.container=e,this.windowHeight=window.innerHeight,this.tick=window.requestAnimationFrame||window.mozRequestAnimationFrame||window.webkitRequestAnimationFrame||window.msRequestAnimationFrame,this.scrolled=!1,this.ignore=!1,this.displaying=!1,this.offset=250,this.views=[],this.renderer=t,this.prevScrollTop=0},EPUBJS.Infinite.prototype.start=function(){this.container.addEventListener("scroll",function(){this.ignore?this.ignore=!1:this.scrolled=!0}.bind(this)),window.addEventListener("unload",function(){this.ignore=!0}),this.tick.call(window,this.check.bind(this)),this.scrolled=!1},EPUBJS.Infinite.prototype.forwards=function(){this.trigger("forwards")},EPUBJS.Infinite.prototype.backwards=function(){this.trigger("backwards")},EPUBJS.Infinite.prototype.check=function(){if(this.scrolled&&!this.displaying){var e=this.container.scrollTop,t=this.container.scrollHeight,r=e-this.prevScrollTop,n=this.container.getBoundingClientRect().height,i=n-(t-e)>-this.offset,o=e0?this.forwards():o&&0>r&&this.backwards(),this.prevScrollTop=e,this.scrolled=!1}this.tick.call(window,this.check.bind(this))},RSVP.EventTarget.mixin(EPUBJS.Infinite.prototype),EPUBJS.Layout=function(){},EPUBJS.Layout.prototype.reconcileLayoutSettings=function(e,t){var r={};for(var n in e)e.hasOwnProperty(n)&&(r[n]=e[n]);return t.forEach(function(e){var t,n,i=e.replace("rendition:",""),o=i.indexOf("-");-1!=o&&(t=i.slice(0,o),n=i.slice(o+1),r[t]=n)}),r},EPUBJS.Layout.prototype.determineLayout=function(e){var t=this.determineSpreads(this.minSpreadWidth),r=t?"ReflowableSpreads":"Reflowable",n=!1;return"pre-paginated"===e.layout&&(r="Fixed",n=!0,t=!1),"reflowable"===e.layout&&"none"===e.spread&&(r="Reflowable",n=!1,t=!1),"reflowable"===e.layout&&"both"===e.spread&&(r="ReflowableSpreads",n=!1,t=!0),this.spreads=t,this.render.scroll(n),this.trigger("renderer:spreads",t),r},EPUBJS.Layout.prototype.applyStyles=function(e){for(var t in e)for(var r in this.views)r.setStyle(t,e[t])},EPUBJS.Layout.prototype.setStyle=function(e,t,r){for(var n in this.views)n.setStyle(e,t,r)},EPUBJS.Layout.prototype.removeStyle=function(e){for(var t in this.views)t.removeStyle(e)},EPUBJS.Layout.prototype.applyHeadTags=function(e){for(var t in e)this.render.addHeadTag(t,e[t])},EPUBJS.Navigation=function(e,t){var r=this,n=new EPUBJS.Parser,i=t||EPUBJS.core.request;this.package=e,this.toc=[],this.tocByHref={},this.tocById={},e.navPath&&(this.navUrl=e.baseUrl+e.navPath,this.nav={},this.nav.load=function(){var e=new RSVP.defer,t=e.promise;return i(r.navUrl,"xml").then(function(t){r.toc=n.nav(t),r.loaded(r.toc),e.resolve(r.toc)}),t}),e.ncxPath&&(this.ncxUrl=e.baseUrl+e.ncxPath,this.ncx={},this.ncx.load=function(){var e=new RSVP.defer,t=e.promise;return i(r.ncxUrl,"xml").then(function(t){r.toc=n.ncx(t),r.loaded(r.toc),e.resolve(r.toc)}),t})},EPUBJS.Navigation.prototype.load=function(e){{var t;e||EPUBJS.core.request}return this.nav?t=this.nav.load():this.ncx&&(t=this.ncx.load()),t},EPUBJS.Navigation.prototype.loaded=function(e){for(var t,r=0;r=0;s--){var a=i.snapshotItem(s),u=a.getAttribute("id")||!1,c=a.querySelector("content"),p=c.getAttribute("src"),h=a.querySelector("navLabel"),l=h.textContent?h.textContent:"",f=p.split("#"),d=(f[0],t(a));n.unshift({id:u,href:p,label:l,subitems:d,parent:r?r.getAttribute("id"):null})}return n}var r=e.querySelector("navMap");return r?t(r):[]},EPUBJS.Renderer=function(e,t){var r=t||{};this.settings={hidden:r.hidden||!1,viewLimit:3,width:r.width||!1,height:r.height||!1},this.book=e,this.epubcfi=new EPUBJS.EpubCFI,this.layoutSettings={},EPUBJS.Hooks.mixin(this),this.getHooks("beforeChapterDisplay"),this._q=EPUBJS.core.queue(this),this.position=1,this.initialize({width:this.settings.width,height:this.settings.height,hidden:!0}),this.rendering=!1,this.views=[],this.positions=[]},EPUBJS.Renderer.prototype.initialize=function(e){{var t=e||{},r=t.height?t.height+"px":"100%",n=t.width?t.width+"px":"100%";t.hidden||!1}return this.container=document.createElement("div"),this.infinite=new EPUBJS.Infinite(this.container,this),this.container.style.width=r,this.container.style.height=n,this.container.style.overflow="scroll",t.hidden?(this.wrapper=document.createElement("div"),this.wrapper.style.visibility="hidden",this.wrapper.style.overflow="hidden",this.wrapper.style.width="0",this.wrapper.style.height="0",this.wrapper.appendChild(this.container),this.wrapper):this.container},EPUBJS.Renderer.prototype.resize=function(e,t){var r=e,n=t;e||(r=window.innerWidth),t||(n=window.innerHeight),this.container.style.width=r+"px",this.container.style.height=n+"px",this.trigger("resized",{width:this.width,height:this.height})},EPUBJS.Renderer.prototype.onResized=function(){var e=this.element.getBoundingClientRect();this.resize(e.width,e.height)},EPUBJS.Renderer.prototype.attachTo=function(e){var t;return EPUBJS.core.isElement(e)?this.element=e:"string"==typeof e&&(this.element=document.getElementById(e)),this.element?(this.element.appendChild(this.container),this.settings.height||this.settings.width||(t=this.element.getBoundingClientRect(),this.resize(t.width,t.height)),this.infinite.start(),this.infinite.on("forwards",this.forwards.bind(this)),this.infinite.on("backwards",this.backwards.bind(this)),void window.addEventListener("resize",this.onResized.bind(this),!1)):void console.error("Not an Element")},EPUBJS.Renderer.prototype.display=function(e){{var t=new RSVP.defer,r=t.promise;new EPUBJS.View}return this.book.opened.then(function(){var r=this.book.spine.get(e),n=this.render(r);n.then(function(){this.fill(),t.resolve(this)}.bind(this))}.bind(this)),r},EPUBJS.Renderer.prototype.render=function(e){var t,r=new EPUBJS.View;return e?(t=e.render(),r.index=e.index,this.insert(r,e.index),t.then(function(e){return r.load(e)})):void t.reject()},EPUBJS.Renderer.prototype.forwards=function(){var e,t,r;return this.rendering||(console.log("going forwards"),e=this.last().index+1,e===this.book.spine.length)?void 0:(r=this.book.spine.get(e),t=this.render(r),this.rendering=!0,t.then(function(){console.log("last:",this.last().height),this.rendering=!1}.bind(this)),t)},EPUBJS.Renderer.prototype.backwards=function(){var e,t,r;return this.rendering||(console.log("going backwards"),e=this.first().index-1,0>e)?void 0:(r=this.book.spine.get(e),t=this.render(r),this.rendering=!0,t.then(function(){this.rendering=!1,this.container.scrollTop+=this.first().height}.bind(this)),t)},EPUBJS.Renderer.prototype.fill=function(){console.log("filling");var e=this.backwards(),t=this.container.getBoundingClientRect().height;e.then(function(){for(var e=this.last().bounds().bottom;t&&e&&t>e;)this.forwards()}.bind(this))},EPUBJS.Renderer.prototype.jump=function(e){this.views.push(e)},EPUBJS.Renderer.prototype.append=function(e){this.views.push(e),e.appendTo(this.container)},EPUBJS.Renderer.prototype.prepend=function(e){this.views.unshift(e),e.prependTo(this.container)},EPUBJS.Renderer.prototype.insert=function(e,t){this.first()?t-this.first().index>=0?this.append(e):t-this.last().index<=0&&this.prepend(e):this.append(e)},EPUBJS.Renderer.prototype.destroy=function(){},EPUBJS.Renderer.prototype.first=function(){return this.views[0]},EPUBJS.Renderer.prototype.last=function(){return this.views[this.views.length-1]},RSVP.EventTarget.mixin(EPUBJS.Renderer.prototype),EPUBJS.Spine=function(e,t){this.items=e.spine,this.manifest=e.manifest,this.spineNodeIndex=e.spineNodeIndex,this.baseUrl=e.baseUrl||"",this.request=t,this.length=this.items.length,this.epubcfi=new EPUBJS.EpubCFI,this.spineItems=[],this.spineByHref={},this.spineById={},this.items.forEach(function(e,t){var r,n,i,o=this.epubcfi.generateChapterComponent(this.spineNodeIndex,e.index,e.idref),s=this.manifest[e.idref];s&&(r=s.href,n=this.baseUrl+r),i=new EPUBJS.SpineItem(e,r,n,o),this.spineItems.push(i),this.spineByHref[i.href]=t,this.spineById[i.idref]=t}.bind(this))},EPUBJS.Spine.prototype.get=function(e){var t=0;return!e||"number"!=typeof e&&isNaN(e)!==!1?e&&0===e.indexOf("#")?t=this.spineById[e.substring(1)]:e&&(t=this.spineByHref[e]):t=e,this.spineItems[t]},EPUBJS.SpineItem=function(e,t,r,n){this.idref=e.idref,this.linear=e.linear,this.properties=e.properties,this.index=e.index,this.href=t,this.url=r,this.cfiBase=n},EPUBJS.SpineItem.prototype.load=function(e){var t=e||this.request||EPUBJS.core.request,r=new RSVP.defer,n=r.promise;return this.contents?r.resolve(this.contents):t(this.url,"xml").then(function(e){EPUBJS.core.folder(this.url);this.document=e,this.contents=e.documentElement,this.replacements(this.document),r.resolve(this.contents)}.bind(this)),n},EPUBJS.SpineItem.prototype.replacements=function(e){var t=e.createElement("base");t.setAttribute("href",this.url),e.head.insertBefore(t,e.head.firstChild)},EPUBJS.SpineItem.prototype.render=function(){var e=new RSVP.defer,t=e.promise;return this.load().then(function(t){var r=new XMLSerializer,n=r.serializeToString(t);e.resolve(n)}),t},EPUBJS.SpineItem.prototype.find=function(){},EPUBJS.View=function(){this.id="epubjs-view:"+EPUBJS.core.uuid(),this.loading=new RSVP.defer,this.loaded=this.loading.promise,this.iframe=this.create(),this.height,this.width},EPUBJS.View.prototype.load=function(e){var t=new RSVP.defer,r=t.promise;return this.document=this.iframe.contentDocument,this.document.open(),this.document.write(e),this.document.close(),this.iframe.onload=function(){this.window=this.iframe.contentWindow,this.window.addEventListener("resize",this.resized.bind(this),!1),this.document=this.iframe.contentDocument,this.iframe.style.display="block",this.document.body.style.margin="0",this.document.body.style.display="inline-block",this.layout(),this.iframe.style.visibility="visible",t.resolve(this),this.loading.resolve(this)}.bind(this),r},EPUBJS.View.prototype.unload=function(){},EPUBJS.View.prototype.create=function(){return this.iframe=document.createElement("iframe"),this.iframe.id=this.id,this.iframe.scrolling="no",this.iframe.seamless="seamless",this.iframe.style.border="none",this.iframe.width="100%",this.iframe.style.height="0",this.iframe.style.display="none",this.iframe.style.visibility="hidden",this.iframe},EPUBJS.View.prototype.resized=function(){this.resizing?this.resizing=!1:this.layout()},EPUBJS.View.prototype.layout=function(){for(var e,t={},r=0,n=0;t.height!=n||t.width!=r;)this.resizing=!0,t=this.document.body.getBoundingClientRect(),this.iframe.style.height=t.height+"px",this.iframe.style.width=t.width+"px",e=this.document.body.getBoundingClientRect(),n=e.height,r=e.width,this.width=r,this.height=n},EPUBJS.View.prototype.appendTo=function(e){e.appendChild(this.iframe)},EPUBJS.View.prototype.prependTo=function(e){e.insertBefore(this.iframe,e.firstChild)},EPUBJS.View.prototype.bounds=function(){return this.iframe.getBoundingClientRect()},EPUBJS.View.prototype.destroy=function(){}; \ No newline at end of file diff --git a/examples/basic-render.html b/examples/basic-render.html index 1429fb1..6a13a9a 100644 --- a/examples/basic-render.html +++ b/examples/basic-render.html @@ -4,32 +4,19 @@ EPUB.js Basic Example - - - - - - - - - - - - - + - -
- - diff --git a/gulpfile.js b/gulpfile.js new file mode 100644 index 0000000..37d1cde --- /dev/null +++ b/gulpfile.js @@ -0,0 +1,35 @@ +var gulp = require('gulp'); +var jshint = require('gulp-jshint'); +var concat = require('gulp-concat'); +var rename = require('gulp-rename'); +var uglify = require('gulp-uglify'); + + +// Lint JS +gulp.task('lint', function() { + return gulp.src('lib/*/*.js') + .pipe(jshint()) + .pipe(jshint.reporter('default')); +}); + +// Concat & Minify JS +gulp.task('minify', function(){ + return gulp.src(['lib/*.js', 'bower_components/rsvp/rsvp.js', 'lib/epubjs/*.js']) + .pipe(concat('epub.js')) + .pipe(gulp.dest('dist')) + .pipe(rename('epub.min.js')) + .pipe(uglify()) + .pipe(gulp.dest('dist')); +}); + +// Watch Our Files +gulp.task('watch', function() { + gulp.watch('lib/*/*.js', ['lint', 'minify']); +}); + +// Default +gulp.task('default', ['lint', 'minify']); + +// gulp.task('default', function() { +// // place code for your default task here +// }); \ No newline at end of file diff --git a/lib/epubjs/core.js b/lib/epubjs/core.js index 2d65ce0..bd4eda1 100644 --- a/lib/epubjs/core.js +++ b/lib/epubjs/core.js @@ -257,4 +257,14 @@ EPUBJS.core.resolveUrl = function(base, path) { url = folders.concat(segments); 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 + ); }; \ No newline at end of file diff --git a/lib/epubjs/infinite.js b/lib/epubjs/infinite.js index 256b76e..4d3c39f 100644 --- a/lib/epubjs/infinite.js +++ b/lib/epubjs/infinite.js @@ -3,23 +3,37 @@ EPUBJS.Infinite = function(container, renderer){ this.windowHeight = window.innerHeight; this.tick = window.requestAnimationFrame || window.mozRequestAnimationFrame || window.webkitRequestAnimationFrame || window.msRequestAnimationFrame; this.scrolled = false; + this.ignore = false; this.displaying = false; this.offset = 250; this.views = []; this.renderer = renderer; + this.prevScrollTop = 0; }; -EPUBJS.Infinite.prototype.start = function(first_argument) { - - window.addEventListener("scroll", function(){ - this.scrolled = true; +EPUBJS.Infinite.prototype.start = function() { + + var firstScroll = true; + + this.container.addEventListener("scroll", function(e){ + if(!this.ignore) { + this.scrolled = true; + } else { + this.ignore = false; + } + // console.log("scroll", this.container.scrollTop) }.bind(this)); + + // Reset to prevent jump + window.addEventListener('unload', function(e){ + this.ignore = true; + // window.scroll(0,0); + }); this.tick.call(window, this.check.bind(this)); - // Fill Screen this.scrolled = false; - + }; EPUBJS.Infinite.prototype.forwards = function() { @@ -29,7 +43,6 @@ EPUBJS.Infinite.prototype.forwards = function() { EPUBJS.Infinite.prototype.backwards = function() { this.trigger("backwards"); - }; /* @@ -75,18 +88,28 @@ EPUBJS.Infinite.prototype.check = function(){ if(this.scrolled && !this.displaying) { - var scrollTop = document.body.scrollTop;//TODO: make document.body a variable - var scrollHeight = document.body.scrollHeight; - // console.log(scrollTop, this.windowHeight - (scrollHeight - scrollTop) ) - if(this.windowHeight - (scrollHeight - scrollTop) > -this.offset) { + // var scrollTop = window.pageYOffset || document.documentElement.scrollTop + // var scrollLeft = window.pageXOffset || document.documentElement.scrollLeft + var scrollTop = this.container.scrollTop; + + // var scrollTop = document.body.scrollTop;//TODO: make document.body a variable + // var scrollHeight = document.documentElement.scrollHeight; + var scrollHeight = this.container.scrollHeight; + var direction = scrollTop - this.prevScrollTop; + var height = this.container.getBoundingClientRect().height; + + var up = height - (scrollHeight - scrollTop) > -this.offset; + var down = scrollTop < this.offset; + + if(up && direction > 0) { this.forwards(); } - else if(scrollTop < this.offset) { + else if(down && direction < 0) { this.backwards(); } // console.log(document.body.scrollTop) - + this.prevScrollTop = scrollTop; this.scrolled = false; } diff --git a/lib/epubjs/layout.js b/lib/epubjs/layout.js index 7f200e3..8f8ab41 100644 --- a/lib/epubjs/layout.js +++ b/lib/epubjs/layout.js @@ -1,10 +1,12 @@ +EPUBJS.Layout = function(){}; + /** * 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.Renderer.prototype.reconcileLayoutSettings = function(global, chapter){ +EPUBJS.Layout.prototype.reconcileLayoutSettings = function(global, chapter){ var settings = {}; //-- Get the global defaults @@ -35,7 +37,7 @@ EPUBJS.Renderer.prototype.reconcileLayoutSettings = function(global, chapter){ * Takes: Layout settings object * Returns: String of appropriate for EPUBJS.Layout function */ -EPUBJS.Renderer.prototype.determineLayout = function(settings){ +EPUBJS.Layout.prototype.determineLayout = function(settings){ // Default is layout: reflowable & spread: auto var spreads = this.determineSpreads(this.minSpreadWidth); var layoutMethod = spreads ? "ReflowableSpreads" : "Reflowable"; @@ -67,7 +69,7 @@ EPUBJS.Renderer.prototype.determineLayout = function(settings){ //-- STYLES -EPUBJS.Renderer.prototype.applyStyles = function(styles) { +EPUBJS.Layout.prototype.applyStyles = function(styles) { for (var style in styles) { for (var view in this.views) { view.setStyle(style, styles[style]); @@ -75,20 +77,20 @@ EPUBJS.Renderer.prototype.applyStyles = function(styles) { } }; -EPUBJS.Renderer.prototype.setStyle = function(style, val, prefixed){ +EPUBJS.Layout.prototype.setStyle = function(style, val, prefixed){ for (var view in this.views) { view.setStyle(style, val, prefixed); } }; -EPUBJS.Renderer.prototype.removeStyle = function(style){ +EPUBJS.Layout.prototype.removeStyle = function(style){ for (var view in this.views) { view.removeStyle(style); } }; //-- HEAD TAGS -EPUBJS.Renderer.prototype.applyHeadTags = function(headTags) { +EPUBJS.Layout.prototype.applyHeadTags = function(headTags) { for ( var headTag in headTags ) { this.render.addHeadTag(headTag, headTags[headTag]); } diff --git a/lib/epubjs/listeners.js b/lib/epubjs/listeners.js index cc23dad..56ac3a2 100644 --- a/lib/epubjs/listeners.js +++ b/lib/epubjs/listeners.js @@ -1,3 +1,4 @@ +/* EPUBJS.Renderer.prototype.listeners = function(){ // Dom events to listen for this.listenedEvents = ["keydown", "keyup", "keypressed", "mouseup", "mousedown", "click"]; @@ -67,4 +68,5 @@ EPUBJS.Renderer.prototype.onSelectionChange = function(e){ this.selectedRange = this.render.window.getSelection(); this.trigger("renderer:selected", this.selectedRange); }.bind(this), 500); -}; \ No newline at end of file +}; +*/ \ No newline at end of file diff --git a/lib/epubjs/renderer.js b/lib/epubjs/renderer.js index 37c78ca..0dd6260 100644 --- a/lib/epubjs/renderer.js +++ b/lib/epubjs/renderer.js @@ -1,6 +1,6 @@ EPUBJS.Renderer = function(book, _options) { var options = _options || {}; - var settings = { + this.settings = { hidden: options.hidden || false, viewLimit: 3, width: options.width || false, @@ -14,7 +14,6 @@ EPUBJS.Renderer = function(book, _options) { // Blank Cfi for Parsing this.epubcfi = new EPUBJS.EpubCFI(); - // this.resized = _.debounce(this.onResized.bind(this), 100); this.layoutSettings = {}; @@ -31,12 +30,12 @@ EPUBJS.Renderer = function(book, _options) { this.position = 1; this.initialize({ - "width" : settings.width, - "height" : settings.height, + "width" : this.settings.width, + "height" : this.settings.height, "hidden" : true }); - this.displaying = false; + this.rendering = false; this.views = []; this.positions = []; @@ -48,8 +47,8 @@ EPUBJS.Renderer = function(book, _options) { */ EPUBJS.Renderer.prototype.initialize = function(_options){ var options = _options || {}; - var height = options.height || "100%"; - var width = options.width || "100%"; + var height = options.height ? options.height + "px" : "100%"; + var width = options.width ? options.width + "px" : "100%"; var hidden = options.hidden || false; @@ -74,28 +73,63 @@ EPUBJS.Renderer.prototype.initialize = function(_options){ return this.container; }; +EPUBJS.Renderer.prototype.resize = function(_width, _height){ + var width = _width; + var height = _height; + + if(!_width) { + width = window.innerWidth; + } + if(!_height) { + height = window.innerHeight; + } + + this.container.style.width = width + "px"; + this.container.style.height = height + "px"; + + this.trigger("resized", { + width: this.width, + height: this.height + }); + +}; + +EPUBJS.Renderer.prototype.onResized = function(e) { + var bounds = this.element.getBoundingClientRect(); + + + this.resize(bounds.width, bounds.height); +}; EPUBJS.Renderer.prototype.attachTo = function(_element){ - var element; + var bounds; if(EPUBJS.core.isElement(_element)) { - element = _element; + this.element = _element; } else if (typeof _element === "string") { - element = document.getElementById(_element); + this.element = document.getElementById(_element); } - if(!element){ + if(!this.element){ console.error("Not an Element"); return; } - element.appendChild(this.container); + this.element.appendChild(this.container); + + if(!this.settings.height && !this.settings.width) { + bounds = this.element.getBoundingClientRect(); + + this.resize(bounds.width, bounds.height); + } this.infinite.start(); this.infinite.on("forwards", this.forwards.bind(this)); this.infinite.on("backwards", this.backwards.bind(this)); + window.addEventListener("resize", this.onResized.bind(this), false); + }; @@ -106,37 +140,47 @@ EPUBJS.Renderer.prototype.display = function(what){ this.book.opened.then(function(){ var section = this.book.spine.get(what); - var rendered; + var rendered = this.render(section); - if(!section) { - displaying.reject(); - return; - }; - - rendered = section.render(); - - view.index = section.index; - - rendered.then(function(contents){ - return view.load(contents); - }).then(function(){ + rendered.then(function(){ + this.fill(); displaying.resolve(this); }.bind(this)); - - // Place view in correct position - this.insert(view, section.index); - + }.bind(this)); return displayed; }; +EPUBJS.Renderer.prototype.render = function(section){ + var rendered; + var view = new EPUBJS.View(); + + if(!section) { + rendered.reject(); + return; + }; + + rendered = section.render(); + view.index = section.index; + + // Place view in correct position + this.insert(view, section.index); + + return rendered.then(function(contents){ + return view.load(contents); + }); + +}; + EPUBJS.Renderer.prototype.forwards = function(){ var next; - var displayed; + var rendered; + var section; - if(this.displaying) return; + if(this.rendering) return; + console.log("going forwards") next = this.last().index + 1; @@ -144,40 +188,62 @@ EPUBJS.Renderer.prototype.forwards = function(){ return; } - displayed = this.display(next); - this.displaying = true; + section = this.book.spine.get(next); + rendered = this.render(section); + this.rendering = true; - displayed.then(function(){ - this.displaying = false; + rendered.then(function(){ + console.log("last:", this.last().height) + + this.rendering = false; }.bind(this)); - return displayed; + return rendered; }; EPUBJS.Renderer.prototype.backwards = function(view){ var prev; - var displayed; + var rendered; + var section; - if(this.displaying) return; + if(this.rendering) return; + console.log("going backwards") prev = this.first().index - 1; if(prev < 0){ - return; + return; //TODO: should reject } - displayed = this.display(prev); - this.displaying = true; + section = this.book.spine.get(prev); + rendered = this.render(section); + this.rendering = true; + + rendered.then(function(){ + this.rendering = false; + // -- this might want to be in infinite + this.container.scrollTop += this.first().height; - displayed.then(function(){ - this.displaying = false; - window.scrollBy(0, this.first().height + 20); }.bind(this)); - return displayed; + return rendered; }; // Manage Views + +// -- this might want to be in infinite +EPUBJS.Renderer.prototype.fill = function() { + console.log("filling") + var filling = this.backwards(); + var height = this.container.getBoundingClientRect().height; + filling.then(function(){ + var bottom = this.last().bounds().bottom; + while (height && bottom && bottom < height) { + this.forwards(); + }; + }.bind(this)); +}; + EPUBJS.Renderer.prototype.jump = function(view){ this.views.push(view); }; @@ -217,4 +283,7 @@ EPUBJS.Renderer.prototype.first = function() { EPUBJS.Renderer.prototype.last = function() { return this.views[this.views.length-1]; -}; \ No newline at end of file +}; + +//-- Enable binding events to Renderer +RSVP.EventTarget.mixin(EPUBJS.Renderer.prototype); \ No newline at end of file diff --git a/lib/epubjs/view.js b/lib/epubjs/view.js index 9e22058..54e96b0 100644 --- a/lib/epubjs/view.js +++ b/lib/epubjs/view.js @@ -7,28 +7,10 @@ EPUBJS.View = function(options) { this.width; }; -// EPUBJS.View.prototype.load = function(section) { -// var contents = section.render(); -// var loading = new RSVP.defer(); -// var loaded = loading.promise; - -// this.section = section; - -// contents.then(function(contents) { -// //TODO: Add a more generic set contents -// this.iframe.srcdoc = contents; -// this.layout(); - -// loading.resolve(this); -// }.bind(this)); - -// return loaded; -// }; - EPUBJS.View.prototype.load = function(contents) { var loading = new RSVP.defer(); var loaded = loading.promise; - + this.document = this.iframe.contentDocument; // this.iframe.srcdoc = contents; @@ -37,9 +19,20 @@ EPUBJS.View.prototype.load = function(contents) { this.document.close(); this.iframe.onload = function(e) { + this.window = this.iframe.contentWindow; + this.window.addEventListener("resize", this.resized.bind(this), false); + this.document = this.iframe.contentDocument; + this.iframe.style.display = "block"; + + // Reset Body Styles + this.document.body.style.margin = "0"; + this.document.body.style.display = "inline-block"; + this.layout(); + this.iframe.style.visibility = "visible"; + loading.resolve(this); this.loading.resolve(this); }.bind(this); @@ -60,18 +53,49 @@ EPUBJS.View.prototype.create = function() { // Back up if seamless isn't supported this.iframe.style.border = "none"; this.iframe.width = "100%"; - this.iframe.height = "100%"; - // this.iframe.addEventListener("load", this.loaded.bind(this), false); + this.iframe.style.height = "0"; + this.iframe.style.display = "none"; this.iframe.style.visibility = "hidden"; return this.iframe; }; +EPUBJS.View.prototype.resized = function() { + + if (!this.resizing) { + this.layout(); + } else { + this.resizing = false; + } + +}; + EPUBJS.View.prototype.layout = function() { - this.height = this.document.documentElement.getBoundingClientRect().height; - this.width = this.document.documentElement.getBoundingClientRect().width; - this.iframe.style.height = this.height + "px"; + var bounds = {}, content, width = 0, height = 0; + + // This needs to run twice to get to the correct size + while(bounds.height != height || bounds.width != width) { + this.resizing = true; + + // Check bounds + bounds = this.document.body.getBoundingClientRect(); + + // Apply Changes + this.iframe.style.height = bounds.height + "px"; + this.iframe.style.width = bounds.width + "px"; + + // Check again + content = this.document.body.getBoundingClientRect(); + + height = content.height; + width = content.width; + + this.width = width; + this.height = height; + } + + }; EPUBJS.View.prototype.appendTo = function(element) { @@ -82,6 +106,11 @@ EPUBJS.View.prototype.prependTo = function(element) { element.insertBefore(this.iframe, element.firstChild); }; +EPUBJS.View.prototype.bounds = function() { + return this.iframe.getBoundingClientRect(); +}; + EPUBJS.View.prototype.destroy = function() { -}; \ No newline at end of file +}; + diff --git a/package.json b/package.json index 68d7590..e25a232 100644 --- a/package.json +++ b/package.json @@ -15,9 +15,11 @@ "colors": "^0.6.2", "connect": "^3.0.1", "express": "^4.5.1", - "grunt": "^0.4.5", - "grunt-contrib-connect": "^0.8.0", - "grunt-contrib-uglify": "^0.5.0", + "gulp": "^3.8.7", + "gulp-concat": "^2.3.4", + "gulp-jshint": "^1.8.4", + "gulp-rename": "^1.2.0", + "gulp-uglify": "^0.3.1", "morgan": "^1.1.1", "optimist": "^0.6.1", "portfinder": "^0.2.1",