/build/epub.js

https://github.com/kenyasullivan/epub.js · JavaScript · 7241 lines · 4430 code · 1156 blank · 1655 comment · 832 complexity · 0c4e8ce899fc0b6d28b004de298c7bc4 MD5 · raw file

  1. (function(){var n=this,t=n._,r={},e=Array.prototype,u=Object.prototype,i=Function.prototype,a=e.push,o=e.slice,c=e.concat,l=u.toString,f=u.hasOwnProperty,s=e.forEach,p=e.map,h=e.reduce,v=e.reduceRight,d=e.filter,g=e.every,m=e.some,y=e.indexOf,b=e.lastIndexOf,x=Array.isArray,_=Object.keys,j=i.bind,w=function(n){return n instanceof w?n:this instanceof w?(this._wrapped=n,void 0):new w(n)};"undefined"!=typeof exports?("undefined"!=typeof module&&module.exports&&(exports=module.exports=w),exports._=w):n._=w,w.VERSION="1.4.4";var A=w.each=w.forEach=function(n,t,e){if(null!=n)if(s&&n.forEach===s)n.forEach(t,e);else if(n.length===+n.length){for(var u=0,i=n.length;i>u;u++)if(t.call(e,n[u],u,n)===r)return}else for(var a in n)if(w.has(n,a)&&t.call(e,n[a],a,n)===r)return};w.map=w.collect=function(n,t,r){var e=[];return null==n?e:p&&n.map===p?n.map(t,r):(A(n,function(n,u,i){e[e.length]=t.call(r,n,u,i)}),e)};var O="Reduce of empty array with no initial value";w.reduce=w.foldl=w.inject=function(n,t,r,e){var u=arguments.length>2;if(null==n&&(n=[]),h&&n.reduce===h)return e&&(t=w.bind(t,e)),u?n.reduce(t,r):n.reduce(t);if(A(n,function(n,i,a){u?r=t.call(e,r,n,i,a):(r=n,u=!0)}),!u)throw new TypeError(O);return r},w.reduceRight=w.foldr=function(n,t,r,e){var u=arguments.length>2;if(null==n&&(n=[]),v&&n.reduceRight===v)return e&&(t=w.bind(t,e)),u?n.reduceRight(t,r):n.reduceRight(t);var i=n.length;if(i!==+i){var a=w.keys(n);i=a.length}if(A(n,function(o,c,l){c=a?a[--i]:--i,u?r=t.call(e,r,n[c],c,l):(r=n[c],u=!0)}),!u)throw new TypeError(O);return r},w.find=w.detect=function(n,t,r){var e;return E(n,function(n,u,i){return t.call(r,n,u,i)?(e=n,!0):void 0}),e},w.filter=w.select=function(n,t,r){var e=[];return null==n?e:d&&n.filter===d?n.filter(t,r):(A(n,function(n,u,i){t.call(r,n,u,i)&&(e[e.length]=n)}),e)},w.reject=function(n,t,r){return w.filter(n,function(n,e,u){return!t.call(r,n,e,u)},r)},w.every=w.all=function(n,t,e){t||(t=w.identity);var u=!0;return null==n?u:g&&n.every===g?n.every(t,e):(A(n,function(n,i,a){return(u=u&&t.call(e,n,i,a))?void 0:r}),!!u)};var E=w.some=w.any=function(n,t,e){t||(t=w.identity);var u=!1;return null==n?u:m&&n.some===m?n.some(t,e):(A(n,function(n,i,a){return u||(u=t.call(e,n,i,a))?r:void 0}),!!u)};w.contains=w.include=function(n,t){return null==n?!1:y&&n.indexOf===y?n.indexOf(t)!=-1:E(n,function(n){return n===t})},w.invoke=function(n,t){var r=o.call(arguments,2),e=w.isFunction(t);return w.map(n,function(n){return(e?t:n[t]).apply(n,r)})},w.pluck=function(n,t){return w.map(n,function(n){return n[t]})},w.where=function(n,t,r){return w.isEmpty(t)?r?null:[]:w[r?"find":"filter"](n,function(n){for(var r in t)if(t[r]!==n[r])return!1;return!0})},w.findWhere=function(n,t){return w.where(n,t,!0)},w.max=function(n,t,r){if(!t&&w.isArray(n)&&n[0]===+n[0]&&65535>n.length)return Math.max.apply(Math,n);if(!t&&w.isEmpty(n))return-1/0;var e={computed:-1/0,value:-1/0};return A(n,function(n,u,i){var a=t?t.call(r,n,u,i):n;a>=e.computed&&(e={value:n,computed:a})}),e.value},w.min=function(n,t,r){if(!t&&w.isArray(n)&&n[0]===+n[0]&&65535>n.length)return Math.min.apply(Math,n);if(!t&&w.isEmpty(n))return 1/0;var e={computed:1/0,value:1/0};return A(n,function(n,u,i){var a=t?t.call(r,n,u,i):n;e.computed>a&&(e={value:n,computed:a})}),e.value},w.shuffle=function(n){var t,r=0,e=[];return A(n,function(n){t=w.random(r++),e[r-1]=e[t],e[t]=n}),e};var k=function(n){return w.isFunction(n)?n:function(t){return t[n]}};w.sortBy=function(n,t,r){var e=k(t);return w.pluck(w.map(n,function(n,t,u){return{value:n,index:t,criteria:e.call(r,n,t,u)}}).sort(function(n,t){var r=n.criteria,e=t.criteria;if(r!==e){if(r>e||r===void 0)return 1;if(e>r||e===void 0)return-1}return n.index<t.index?-1:1}),"value")};var F=function(n,t,r,e){var u={},i=k(t||w.identity);return A(n,function(t,a){var o=i.call(r,t,a,n);e(u,o,t)}),u};w.groupBy=function(n,t,r){return F(n,t,r,function(n,t,r){(w.has(n,t)?n[t]:n[t]=[]).push(r)})},w.countBy=function(n,t,r){return F(n,t,r,function(n,t){w.has(n,t)||(n[t]=0),n[t]++})},w.sortedIndex=function(n,t,r,e){r=null==r?w.identity:k(r);for(var u=r.call(e,t),i=0,a=n.length;a>i;){var o=i+a>>>1;u>r.call(e,n[o])?i=o+1:a=o}return i},w.toArray=function(n){return n?w.isArray(n)?o.call(n):n.length===+n.length?w.map(n,w.identity):w.values(n):[]},w.size=function(n){return null==n?0:n.length===+n.length?n.length:w.keys(n).length},w.first=w.head=w.take=function(n,t,r){return null==n?void 0:null==t||r?n[0]:o.call(n,0,t)},w.initial=function(n,t,r){return o.call(n,0,n.length-(null==t||r?1:t))},w.last=function(n,t,r){return null==n?void 0:null==t||r?n[n.length-1]:o.call(n,Math.max(n.length-t,0))},w.rest=w.tail=w.drop=function(n,t,r){return o.call(n,null==t||r?1:t)},w.compact=function(n){return w.filter(n,w.identity)};var R=function(n,t,r){return A(n,function(n){w.isArray(n)?t?a.apply(r,n):R(n,t,r):r.push(n)}),r};w.flatten=function(n,t){return R(n,t,[])},w.without=function(n){return w.difference(n,o.call(arguments,1))},w.uniq=w.unique=function(n,t,r,e){w.isFunction(t)&&(e=r,r=t,t=!1);var u=r?w.map(n,r,e):n,i=[],a=[];return A(u,function(r,e){(t?e&&a[a.length-1]===r:w.contains(a,r))||(a.push(r),i.push(n[e]))}),i},w.union=function(){return w.uniq(c.apply(e,arguments))},w.intersection=function(n){var t=o.call(arguments,1);return w.filter(w.uniq(n),function(n){return w.every(t,function(t){return w.indexOf(t,n)>=0})})},w.difference=function(n){var t=c.apply(e,o.call(arguments,1));return w.filter(n,function(n){return!w.contains(t,n)})},w.zip=function(){for(var n=o.call(arguments),t=w.max(w.pluck(n,"length")),r=Array(t),e=0;t>e;e++)r[e]=w.pluck(n,""+e);return r},w.object=function(n,t){if(null==n)return{};for(var r={},e=0,u=n.length;u>e;e++)t?r[n[e]]=t[e]:r[n[e][0]]=n[e][1];return r},w.indexOf=function(n,t,r){if(null==n)return-1;var e=0,u=n.length;if(r){if("number"!=typeof r)return e=w.sortedIndex(n,t),n[e]===t?e:-1;e=0>r?Math.max(0,u+r):r}if(y&&n.indexOf===y)return n.indexOf(t,r);for(;u>e;e++)if(n[e]===t)return e;return-1},w.lastIndexOf=function(n,t,r){if(null==n)return-1;var e=null!=r;if(b&&n.lastIndexOf===b)return e?n.lastIndexOf(t,r):n.lastIndexOf(t);for(var u=e?r:n.length;u--;)if(n[u]===t)return u;return-1},w.range=function(n,t,r){1>=arguments.length&&(t=n||0,n=0),r=arguments[2]||1;for(var e=Math.max(Math.ceil((t-n)/r),0),u=0,i=Array(e);e>u;)i[u++]=n,n+=r;return i},w.bind=function(n,t){if(n.bind===j&&j)return j.apply(n,o.call(arguments,1));var r=o.call(arguments,2);return function(){return n.apply(t,r.concat(o.call(arguments)))}},w.partial=function(n){var t=o.call(arguments,1);return function(){return n.apply(this,t.concat(o.call(arguments)))}},w.bindAll=function(n){var t=o.call(arguments,1);return 0===t.length&&(t=w.functions(n)),A(t,function(t){n[t]=w.bind(n[t],n)}),n},w.memoize=function(n,t){var r={};return t||(t=w.identity),function(){var e=t.apply(this,arguments);return w.has(r,e)?r[e]:r[e]=n.apply(this,arguments)}},w.delay=function(n,t){var r=o.call(arguments,2);return setTimeout(function(){return n.apply(null,r)},t)},w.defer=function(n){return w.delay.apply(w,[n,1].concat(o.call(arguments,1)))},w.throttle=function(n,t){var r,e,u,i,a=0,o=function(){a=new Date,u=null,i=n.apply(r,e)};return function(){var c=new Date,l=t-(c-a);return r=this,e=arguments,0>=l?(clearTimeout(u),u=null,a=c,i=n.apply(r,e)):u||(u=setTimeout(o,l)),i}},w.debounce=function(n,t,r){var e,u;return function(){var i=this,a=arguments,o=function(){e=null,r||(u=n.apply(i,a))},c=r&&!e;return clearTimeout(e),e=setTimeout(o,t),c&&(u=n.apply(i,a)),u}},w.once=function(n){var t,r=!1;return function(){return r?t:(r=!0,t=n.apply(this,arguments),n=null,t)}},w.wrap=function(n,t){return function(){var r=[n];return a.apply(r,arguments),t.apply(this,r)}},w.compose=function(){var n=arguments;return function(){for(var t=arguments,r=n.length-1;r>=0;r--)t=[n[r].apply(this,t)];return t[0]}},w.after=function(n,t){return 0>=n?t():function(){return 1>--n?t.apply(this,arguments):void 0}},w.keys=_||function(n){if(n!==Object(n))throw new TypeError("Invalid object");var t=[];for(var r in n)w.has(n,r)&&(t[t.length]=r);return t},w.values=function(n){var t=[];for(var r in n)w.has(n,r)&&t.push(n[r]);return t},w.pairs=function(n){var t=[];for(var r in n)w.has(n,r)&&t.push([r,n[r]]);return t},w.invert=function(n){var t={};for(var r in n)w.has(n,r)&&(t[n[r]]=r);return t},w.functions=w.methods=function(n){var t=[];for(var r in n)w.isFunction(n[r])&&t.push(r);return t.sort()},w.extend=function(n){return A(o.call(arguments,1),function(t){if(t)for(var r in t)n[r]=t[r]}),n},w.pick=function(n){var t={},r=c.apply(e,o.call(arguments,1));return A(r,function(r){r in n&&(t[r]=n[r])}),t},w.omit=function(n){var t={},r=c.apply(e,o.call(arguments,1));for(var u in n)w.contains(r,u)||(t[u]=n[u]);return t},w.defaults=function(n){return A(o.call(arguments,1),function(t){if(t)for(var r in t)null==n[r]&&(n[r]=t[r])}),n},w.clone=function(n){return w.isObject(n)?w.isArray(n)?n.slice():w.extend({},n):n},w.tap=function(n,t){return t(n),n};var I=function(n,t,r,e){if(n===t)return 0!==n||1/n==1/t;if(null==n||null==t)return n===t;n instanceof w&&(n=n._wrapped),t instanceof w&&(t=t._wrapped);var u=l.call(n);if(u!=l.call(t))return!1;switch(u){case"[object String]":return n==t+"";case"[object Number]":return n!=+n?t!=+t:0==n?1/n==1/t:n==+t;case"[object Date]":case"[object Boolean]":return+n==+t;case"[object RegExp]":return n.source==t.source&&n.global==t.global&&n.multiline==t.multiline&&n.ignoreCase==t.ignoreCase}if("object"!=typeof n||"object"!=typeof t)return!1;for(var i=r.length;i--;)if(r[i]==n)return e[i]==t;r.push(n),e.push(t);var a=0,o=!0;if("[object Array]"==u){if(a=n.length,o=a==t.length)for(;a--&&(o=I(n[a],t[a],r,e)););}else{var c=n.constructor,f=t.constructor;if(c!==f&&!(w.isFunction(c)&&c instanceof c&&w.isFunction(f)&&f instanceof f))return!1;for(var s in n)if(w.has(n,s)&&(a++,!(o=w.has(t,s)&&I(n[s],t[s],r,e))))break;if(o){for(s in t)if(w.has(t,s)&&!a--)break;o=!a}}return r.pop(),e.pop(),o};w.isEqual=function(n,t){return I(n,t,[],[])},w.isEmpty=function(n){if(null==n)return!0;if(w.isArray(n)||w.isString(n))return 0===n.length;for(var t in n)if(w.has(n,t))return!1;return!0},w.isElement=function(n){return!(!n||1!==n.nodeType)},w.isArray=x||function(n){return"[object Array]"==l.call(n)},w.isObject=function(n){return n===Object(n)},A(["Arguments","Function","String","Number","Date","RegExp"],function(n){w["is"+n]=function(t){return l.call(t)=="[object "+n+"]"}}),w.isArguments(arguments)||(w.isArguments=function(n){return!(!n||!w.has(n,"callee"))}),"function"!=typeof/./&&(w.isFunction=function(n){return"function"==typeof n}),w.isFinite=function(n){return isFinite(n)&&!isNaN(parseFloat(n))},w.isNaN=function(n){return w.isNumber(n)&&n!=+n},w.isBoolean=function(n){return n===!0||n===!1||"[object Boolean]"==l.call(n)},w.isNull=function(n){return null===n},w.isUndefined=function(n){return n===void 0},w.has=function(n,t){return f.call(n,t)},w.noConflict=function(){return n._=t,this},w.identity=function(n){return n},w.times=function(n,t,r){for(var e=Array(n),u=0;n>u;u++)e[u]=t.call(r,u);return e},w.random=function(n,t){return null==t&&(t=n,n=0),n+Math.floor(Math.random()*(t-n+1))};var M={escape:{"&":"&amp;","<":"&lt;",">":"&gt;",'"':"&quot;","'":"&#x27;","/":"&#x2F;"}};M.unescape=w.invert(M.escape);var S={escape:RegExp("["+w.keys(M.escape).join("")+"]","g"),unescape:RegExp("("+w.keys(M.unescape).join("|")+")","g")};w.each(["escape","unescape"],function(n){w[n]=function(t){return null==t?"":(""+t).replace(S[n],function(t){return M[n][t]})}}),w.result=function(n,t){if(null==n)return null;var r=n[t];return w.isFunction(r)?r.call(n):r},w.mixin=function(n){A(w.functions(n),function(t){var r=w[t]=n[t];w.prototype[t]=function(){var n=[this._wrapped];return a.apply(n,arguments),D.call(this,r.apply(w,n))}})};var N=0;w.uniqueId=function(n){var t=++N+"";return n?n+t:t},w.templateSettings={evaluate:/<%([\s\S]+?)%>/g,interpolate:/<%=([\s\S]+?)%>/g,escape:/<%-([\s\S]+?)%>/g};var T=/(.)^/,q={"'":"'","\\":"\\","\r":"r","\n":"n"," ":"t","\u2028":"u2028","\u2029":"u2029"},B=/\\|'|\r|\n|\t|\u2028|\u2029/g;w.template=function(n,t,r){var e;r=w.defaults({},r,w.templateSettings);var u=RegExp([(r.escape||T).source,(r.interpolate||T).source,(r.evaluate||T).source].join("|")+"|$","g"),i=0,a="__p+='";n.replace(u,function(t,r,e,u,o){return a+=n.slice(i,o).replace(B,function(n){return"\\"+q[n]}),r&&(a+="'+\n((__t=("+r+"))==null?'':_.escape(__t))+\n'"),e&&(a+="'+\n((__t=("+e+"))==null?'':__t)+\n'"),u&&(a+="';\n"+u+"\n__p+='"),i=o+t.length,t}),a+="';\n",r.variable||(a="with(obj||{}){\n"+a+"}\n"),a="var __t,__p='',__j=Array.prototype.join,"+"print=function(){__p+=__j.call(arguments,'');};\n"+a+"return __p;\n";try{e=Function(r.variable||"obj","_",a)}catch(o){throw o.source=a,o}if(t)return e(t,w);var c=function(n){return e.call(this,n,w)};return c.source="function("+(r.variable||"obj")+"){\n"+a+"}",c},w.chain=function(n){return w(n).chain()};var D=function(n){return this._chain?w(n).chain():n};w.mixin(w),A(["pop","push","reverse","shift","sort","splice","unshift"],function(n){var t=e[n];w.prototype[n]=function(){var r=this._wrapped;return t.apply(r,arguments),"shift"!=n&&"splice"!=n||0!==r.length||delete r[0],D.call(this,r)}}),A(["concat","join","slice"],function(n){var t=e[n];w.prototype[n]=function(){return D.call(this,t.apply(this._wrapped,arguments))}}),w.extend(w.prototype,{chain:function(){return this._chain=!0,this},value:function(){return this._wrapped}})}).call(this);
  2. (function(global) {
  3. var define, requireModule, require, requirejs;
  4. (function() {
  5. var registry = {}, seen = {};
  6. define = function(name, deps, callback) {
  7. registry[name] = { deps: deps, callback: callback };
  8. };
  9. requirejs = require = requireModule = function(name) {
  10. requirejs._eak_seen = registry;
  11. if (seen[name]) { return seen[name]; }
  12. seen[name] = {};
  13. if (!registry[name]) {
  14. throw new Error("Could not find module " + name);
  15. }
  16. var mod = registry[name],
  17. deps = mod.deps,
  18. callback = mod.callback,
  19. reified = [],
  20. exports;
  21. for (var i=0, l=deps.length; i<l; i++) {
  22. if (deps[i] === 'exports') {
  23. reified.push(exports = {});
  24. } else {
  25. reified.push(requireModule(resolve(deps[i])));
  26. }
  27. }
  28. var value = callback.apply(this, reified);
  29. return seen[name] = exports || value;
  30. function resolve(child) {
  31. if (child.charAt(0) !== '.') { return child; }
  32. var parts = child.split("/");
  33. var parentBase = name.split("/").slice(0, -1);
  34. for (var i=0, l=parts.length; i<l; i++) {
  35. var part = parts[i];
  36. if (part === '..') { parentBase.pop(); }
  37. else if (part === '.') { continue; }
  38. else { parentBase.push(part); }
  39. }
  40. return parentBase.join("/");
  41. }
  42. };
  43. })();
  44. define("rsvp/all",
  45. ["./promise","exports"],
  46. function(__dependency1__, __exports__) {
  47. "use strict";
  48. var Promise = __dependency1__["default"];
  49. __exports__["default"] = function all(array, label) {
  50. return Promise.all(array, label);
  51. };
  52. });
  53. define("rsvp/all_settled",
  54. ["./promise","./utils","exports"],
  55. function(__dependency1__, __dependency2__, __exports__) {
  56. "use strict";
  57. var Promise = __dependency1__["default"];
  58. var isArray = __dependency2__.isArray;
  59. var isNonThenable = __dependency2__.isNonThenable;
  60. /**
  61. `RSVP.allSettled` is similar to `RSVP.all`, but instead of implementing
  62. a fail-fast method, it waits until all the promises have returned and
  63. shows you all the results. This is useful if you want to handle multiple
  64. promises' failure states together as a set.
  65. Returns a promise that is fulfilled when all the given promises have been
  66. settled. The return promise is fulfilled with an array of the states of
  67. the promises passed into the `promises` array argument.
  68. Each state object will either indicate fulfillment or rejection, and
  69. provide the corresponding value or reason. The states will take one of
  70. the following formats:
  71. ```javascript
  72. { state: 'fulfilled', value: value }
  73. or
  74. { state: 'rejected', reason: reason }
  75. ```
  76. Example:
  77. ```javascript
  78. var promise1 = RSVP.Promise.resolve(1);
  79. var promise2 = RSVP.Promise.reject(new Error('2'));
  80. var promise3 = RSVP.Promise.reject(new Error('3'));
  81. var promises = [ promise1, promise2, promise3 ];
  82. RSVP.allSettled(promises).then(function(array){
  83. // array == [
  84. // { state: 'fulfilled', value: 1 },
  85. // { state: 'rejected', reason: Error },
  86. // { state: 'rejected', reason: Error }
  87. // ]
  88. // Note that for the second item, reason.message will be "2", and for the
  89. // third item, reason.message will be "3".
  90. }, function(error) {
  91. // Not run. (This block would only be called if allSettled had failed,
  92. // for instance if passed an incorrect argument type.)
  93. });
  94. ```
  95. @method @allSettled
  96. @for RSVP
  97. @param {Array} promises;
  98. @param {String} label - optional string that describes the promise.
  99. Useful for tooling.
  100. @return {Promise} promise that is fulfilled with an array of the settled
  101. states of the constituent promises.
  102. */
  103. __exports__["default"] = function allSettled(entries, label) {
  104. return new Promise(function(resolve, reject) {
  105. if (!isArray(entries)) {
  106. throw new TypeError('You must pass an array to allSettled.');
  107. }
  108. var remaining = entries.length;
  109. var entry;
  110. if (remaining === 0) {
  111. resolve([]);
  112. return;
  113. }
  114. var results = new Array(remaining);
  115. function fulfilledResolver(index) {
  116. return function(value) {
  117. resolveAll(index, fulfilled(value));
  118. };
  119. }
  120. function rejectedResolver(index) {
  121. return function(reason) {
  122. resolveAll(index, rejected(reason));
  123. };
  124. }
  125. function resolveAll(index, value) {
  126. results[index] = value;
  127. if (--remaining === 0) {
  128. resolve(results);
  129. }
  130. }
  131. for (var index = 0; index < entries.length; index++) {
  132. entry = entries[index];
  133. if (isNonThenable(entry)) {
  134. resolveAll(index, fulfilled(entry));
  135. } else {
  136. Promise.cast(entry).then(fulfilledResolver(index), rejectedResolver(index));
  137. }
  138. }
  139. }, label);
  140. };
  141. function fulfilled(value) {
  142. return { state: 'fulfilled', value: value };
  143. }
  144. function rejected(reason) {
  145. return { state: 'rejected', reason: reason };
  146. }
  147. });
  148. define("rsvp/asap",
  149. ["exports"],
  150. function(__exports__) {
  151. "use strict";
  152. __exports__["default"] = function asap(callback, arg) {
  153. var length = queue.push([callback, arg]);
  154. if (length === 1) {
  155. // If length is 1, that means that we need to schedule an async flush.
  156. // If additional callbacks are queued before the queue is flushed, they
  157. // will be processed by this flush that we are scheduling.
  158. scheduleFlush();
  159. }
  160. };
  161. var browserGlobal = (typeof window !== 'undefined') ? window : {};
  162. var BrowserMutationObserver = browserGlobal.MutationObserver || browserGlobal.WebKitMutationObserver;
  163. // node
  164. function useNextTick() {
  165. return function() {
  166. process.nextTick(flush);
  167. };
  168. }
  169. function useMutationObserver() {
  170. var iterations = 0;
  171. var observer = new BrowserMutationObserver(flush);
  172. var node = document.createTextNode('');
  173. observer.observe(node, { characterData: true });
  174. return function() {
  175. node.data = (iterations = ++iterations % 2);
  176. };
  177. }
  178. function useSetTimeout() {
  179. return function() {
  180. setTimeout(flush, 1);
  181. };
  182. }
  183. var queue = [];
  184. function flush() {
  185. for (var i = 0; i < queue.length; i++) {
  186. var tuple = queue[i];
  187. var callback = tuple[0], arg = tuple[1];
  188. callback(arg);
  189. }
  190. queue = [];
  191. }
  192. var scheduleFlush;
  193. // Decide what async method to use to triggering processing of queued callbacks:
  194. if (typeof process !== 'undefined' && {}.toString.call(process) === '[object process]') {
  195. scheduleFlush = useNextTick();
  196. } else if (BrowserMutationObserver) {
  197. scheduleFlush = useMutationObserver();
  198. } else {
  199. scheduleFlush = useSetTimeout();
  200. }
  201. });
  202. define("rsvp/config",
  203. ["./events","exports"],
  204. function(__dependency1__, __exports__) {
  205. "use strict";
  206. var EventTarget = __dependency1__["default"];
  207. var config = {
  208. instrument: false
  209. };
  210. EventTarget.mixin(config);
  211. function configure(name, value) {
  212. if (name === 'onerror') {
  213. // handle for legacy users that expect the actual
  214. // error to be passed to their function added via
  215. // `RSVP.configure('onerror', someFunctionHere);`
  216. config.on('error', value);
  217. return;
  218. }
  219. if (arguments.length === 2) {
  220. config[name] = value;
  221. } else {
  222. return config[name];
  223. }
  224. }
  225. __exports__.config = config;
  226. __exports__.configure = configure;
  227. });
  228. define("rsvp/defer",
  229. ["./promise","exports"],
  230. function(__dependency1__, __exports__) {
  231. "use strict";
  232. var Promise = __dependency1__["default"];
  233. /**
  234. `RSVP.defer` returns an object similar to jQuery's `$.Deferred` objects.
  235. `RSVP.defer` should be used when porting over code reliant on `$.Deferred`'s
  236. interface. New code should use the `RSVP.Promise` constructor instead.
  237. The object returned from `RSVP.defer` is a plain object with three properties:
  238. * promise - an `RSVP.Promise`.
  239. * reject - a function that causes the `promise` property on this object to
  240. become rejected
  241. * resolve - a function that causes the `promise` property on this object to
  242. become fulfilled.
  243. Example:
  244. ```javascript
  245. var deferred = RSVP.defer();
  246. deferred.resolve("Success!");
  247. defered.promise.then(function(value){
  248. // value here is "Success!"
  249. });
  250. ```
  251. @method defer
  252. @for RSVP
  253. @param {String} label optional string for labeling the promise.
  254. Useful for tooling.
  255. @return {Object}
  256. */
  257. __exports__["default"] = function defer(label) {
  258. var deferred = { };
  259. deferred.promise = new Promise(function(resolve, reject) {
  260. deferred.resolve = resolve;
  261. deferred.reject = reject;
  262. }, label);
  263. return deferred;
  264. };
  265. });
  266. define("rsvp/events",
  267. ["exports"],
  268. function(__exports__) {
  269. "use strict";
  270. var indexOf = function(callbacks, callback) {
  271. for (var i=0, l=callbacks.length; i<l; i++) {
  272. if (callbacks[i] === callback) { return i; }
  273. }
  274. return -1;
  275. };
  276. var callbacksFor = function(object) {
  277. var callbacks = object._promiseCallbacks;
  278. if (!callbacks) {
  279. callbacks = object._promiseCallbacks = {};
  280. }
  281. return callbacks;
  282. };
  283. /**
  284. //@module RSVP
  285. //@class EventTarget
  286. */
  287. __exports__["default"] = {
  288. /**
  289. `RSVP.EventTarget.mixin` extends an object with EventTarget methods. For
  290. Example:
  291. ```javascript
  292. var object = {};
  293. RSVP.EventTarget.mixin(object);
  294. object.on("finished", function(event) {
  295. // handle event
  296. });
  297. object.trigger("finished", { detail: value });
  298. ```
  299. `EventTarget.mixin` also works with prototypes:
  300. ```javascript
  301. var Person = function() {};
  302. RSVP.EventTarget.mixin(Person.prototype);
  303. var yehuda = new Person();
  304. var tom = new Person();
  305. yehuda.on("poke", function(event) {
  306. console.log("Yehuda says OW");
  307. });
  308. tom.on("poke", function(event) {
  309. console.log("Tom says OW");
  310. });
  311. yehuda.trigger("poke");
  312. tom.trigger("poke");
  313. ```
  314. @method mixin
  315. @param {Object} object object to extend with EventTarget methods
  316. @private
  317. */
  318. mixin: function(object) {
  319. object.on = this.on;
  320. object.off = this.off;
  321. object.trigger = this.trigger;
  322. object._promiseCallbacks = undefined;
  323. return object;
  324. },
  325. /**
  326. Registers a callback to be executed when `eventName` is triggered
  327. ```javascript
  328. object.on('event', function(eventInfo){
  329. // handle the event
  330. });
  331. object.trigger('event');
  332. ```
  333. @method on
  334. @param {String} eventName name of the event to listen for
  335. @param {Function} callback function to be called when the event is triggered.
  336. @private
  337. */
  338. on: function(eventName, callback) {
  339. var allCallbacks = callbacksFor(this), callbacks;
  340. callbacks = allCallbacks[eventName];
  341. if (!callbacks) {
  342. callbacks = allCallbacks[eventName] = [];
  343. }
  344. if (indexOf(callbacks, callback) === -1) {
  345. callbacks.push(callback);
  346. }
  347. },
  348. /**
  349. You can use `off` to stop firing a particular callback for an event:
  350. ```javascript
  351. function doStuff() { // do stuff! }
  352. object.on('stuff', doStuff);
  353. object.trigger('stuff'); // doStuff will be called
  354. // Unregister ONLY the doStuff callback
  355. object.off('stuff', doStuff);
  356. object.trigger('stuff'); // doStuff will NOT be called
  357. ```
  358. If you don't pass a `callback` argument to `off`, ALL callbacks for the
  359. event will not be executed when the event fires. For example:
  360. ```javascript
  361. var callback1 = function(){};
  362. var callback2 = function(){};
  363. object.on('stuff', callback1);
  364. object.on('stuff', callback2);
  365. object.trigger('stuff'); // callback1 and callback2 will be executed.
  366. object.off('stuff');
  367. object.trigger('stuff'); // callback1 and callback2 will not be executed!
  368. ```
  369. @method off
  370. @param {String} eventName event to stop listening to
  371. @param {Function} callback optional argument. If given, only the function
  372. given will be removed from the event's callback queue. If no `callback`
  373. argument is given, all callbacks will be removed from the event's callback
  374. queue.
  375. @private
  376. */
  377. off: function(eventName, callback) {
  378. var allCallbacks = callbacksFor(this), callbacks, index;
  379. if (!callback) {
  380. allCallbacks[eventName] = [];
  381. return;
  382. }
  383. callbacks = allCallbacks[eventName];
  384. index = indexOf(callbacks, callback);
  385. if (index !== -1) { callbacks.splice(index, 1); }
  386. },
  387. /**
  388. Use `trigger` to fire custom events. For example:
  389. ```javascript
  390. object.on('foo', function(){
  391. console.log('foo event happened!');
  392. });
  393. object.trigger('foo');
  394. // 'foo event happened!' logged to the console
  395. ```
  396. You can also pass a value as a second argument to `trigger` that will be
  397. passed as an argument to all event listeners for the event:
  398. ```javascript
  399. object.on('foo', function(value){
  400. console.log(value.name);
  401. });
  402. object.trigger('foo', { name: 'bar' });
  403. // 'bar' logged to the console
  404. ```
  405. @method trigger
  406. @param {String} eventName name of the event to be triggered
  407. @param {Any} options optional value to be passed to any event handlers for
  408. the given `eventName`
  409. @private
  410. */
  411. trigger: function(eventName, options) {
  412. var allCallbacks = callbacksFor(this),
  413. callbacks, callbackTuple, callback, binding;
  414. if (callbacks = allCallbacks[eventName]) {
  415. // Don't cache the callbacks.length since it may grow
  416. for (var i=0; i<callbacks.length; i++) {
  417. callback = callbacks[i];
  418. callback(options);
  419. }
  420. }
  421. }
  422. };
  423. });
  424. define("rsvp/filter",
  425. ["./all","./map","./utils","exports"],
  426. function(__dependency1__, __dependency2__, __dependency3__, __exports__) {
  427. "use strict";
  428. var all = __dependency1__["default"];
  429. var map = __dependency2__["default"];
  430. var isFunction = __dependency3__.isFunction;
  431. var isArray = __dependency3__.isArray;
  432. /**
  433. `RSVP.filter` is similar to JavaScript's native `filter` method, except that it
  434. waits for all promises to become fulfilled before running the `filterFn` on
  435. each item in given to `promises`. `RSVP.filterFn` returns a promise that will
  436. become fulfilled with the result of running `filterFn` on the values the
  437. promises become fulfilled with.
  438. For example:
  439. ```javascript
  440. var promise1 = RSVP.resolve(1);
  441. var promise2 = RSVP.resolve(2);
  442. var promise3 = RSVP.resolve(3);
  443. var filterFn = function(item){
  444. return item > 1;
  445. };
  446. RSVP.filter(promises, filterFn).then(function(result){
  447. // result is [ 2, 3 ]
  448. });
  449. ```
  450. If any of the `promises` given to `RSVP.filter` are rejected, the first promise
  451. that is rejected will be given as an argument to the returned promises's
  452. rejection handler. For example:
  453. ```javascript
  454. var promise1 = RSVP.resolve(1);
  455. var promise2 = RSVP.reject(new Error("2"));
  456. var promise3 = RSVP.reject(new Error("3"));
  457. var promises = [ promise1, promise2, promise3 ];
  458. var filterFn = function(item){
  459. return item > 1;
  460. };
  461. RSVP.filter(promises, filterFn).then(function(array){
  462. // Code here never runs because there are rejected promises!
  463. }, function(reason) {
  464. // reason.message === "2"
  465. });
  466. ```
  467. `RSVP.filter` will also wait for any promises returned from `filterFn`.
  468. For instance, you may want to fetch a list of users then return a subset
  469. of those users based on some asynchronous operation:
  470. ```javascript
  471. var alice = { name: 'alice' };
  472. var bob = { name: 'bob' };
  473. var users = [ alice, bob ];
  474. var promises = users.map(function(user){
  475. return RSVP.resolve(user);
  476. });
  477. var filterFn = function(user){
  478. // Here, Alice has permissions to create a blog post, but Bob does not.
  479. return getPrivilegesForUser(user).then(function(privs){
  480. return privs.can_create_blog_post === true;
  481. });
  482. };
  483. RSVP.filter(promises, filterFn).then(function(users){
  484. // true, because the server told us only Alice can create a blog post.
  485. users.length === 1;
  486. // false, because Alice is the only user present in `users`
  487. users[0] === bob;
  488. });
  489. ```
  490. @method filter
  491. @for RSVP
  492. @param {Array} promises
  493. @param {Function} filterFn - function to be called on each resolved value to
  494. filter the final results.
  495. @param {String} label optional string describing the promise. Useful for
  496. tooling.
  497. @return {Promise}
  498. */
  499. function filter(promises, filterFn, label) {
  500. if (!isArray(promises)) {
  501. throw new TypeError('You must pass an array to filter.');
  502. }
  503. if (!isFunction(filterFn)){
  504. throw new TypeError("You must pass a function to filter's second argument.");
  505. }
  506. return all(promises, label).then(function(values){
  507. return map(promises, filterFn, label).then(function(filterResults){
  508. var i,
  509. valuesLen = values.length,
  510. filtered = [];
  511. for (i = 0; i < valuesLen; i++){
  512. if(filterResults[i]) filtered.push(values[i]);
  513. }
  514. return filtered;
  515. });
  516. });
  517. }
  518. __exports__["default"] = filter;
  519. });
  520. define("rsvp/hash",
  521. ["./promise","./utils","exports"],
  522. function(__dependency1__, __dependency2__, __exports__) {
  523. "use strict";
  524. var Promise = __dependency1__["default"];
  525. var isNonThenable = __dependency2__.isNonThenable;
  526. var keysOf = __dependency2__.keysOf;
  527. /**
  528. `RSVP.hash` is similar to `RSVP.all`, but takes an object instead of an array
  529. for its `promises` argument.
  530. Returns a promise that is fulfilled when all the given promises have been
  531. fulfilled, or rejected if any of them become rejected. The returned promise
  532. is fulfilled with a hash that has the same key names as the `promises` object
  533. argument. If any of the values in the object are not promises, they will
  534. simply be copied over to the fulfilled object.
  535. Example:
  536. ```javascript
  537. var promises = {
  538. myPromise: RSVP.resolve(1),
  539. yourPromise: RSVP.resolve(2),
  540. theirPromise: RSVP.resolve(3),
  541. notAPromise: 4
  542. };
  543. RSVP.hash(promises).then(function(hash){
  544. // hash here is an object that looks like:
  545. // {
  546. // myPromise: 1,
  547. // yourPromise: 2,
  548. // theirPromise: 3,
  549. // notAPromise: 4
  550. // }
  551. });
  552. ````
  553. If any of the `promises` given to `RSVP.hash` are rejected, the first promise
  554. that is rejected will be given as as the first argument, or as the reason to
  555. the rejection handler. For example:
  556. ```javascript
  557. var promises = {
  558. myPromise: RSVP.resolve(1),
  559. rejectedPromise: RSVP.reject(new Error("rejectedPromise")),
  560. anotherRejectedPromise: RSVP.reject(new Error("anotherRejectedPromise")),
  561. };
  562. RSVP.hash(promises).then(function(hash){
  563. // Code here never runs because there are rejected promises!
  564. }, function(reason) {
  565. // reason.message === "rejectedPromise"
  566. });
  567. ```
  568. An important note: `RSVP.hash` is intended for plain JavaScript objects that
  569. are just a set of keys and values. `RSVP.hash` will NOT preserve prototype
  570. chains.
  571. Example:
  572. ```javascript
  573. function MyConstructor(){
  574. this.example = RSVP.resolve("Example");
  575. }
  576. MyConstructor.prototype = {
  577. protoProperty: RSVP.resolve("Proto Property")
  578. };
  579. var myObject = new MyConstructor();
  580. RSVP.hash(myObject).then(function(hash){
  581. // protoProperty will not be present, instead you will just have an
  582. // object that looks like:
  583. // {
  584. // example: "Example"
  585. // }
  586. //
  587. // hash.hasOwnProperty('protoProperty'); // false
  588. // 'undefined' === typeof hash.protoProperty
  589. });
  590. ```
  591. @method hash
  592. @for RSVP
  593. @param {Object} promises
  594. @param {String} label - optional string that describes the promise.
  595. Useful for tooling.
  596. @return {Promise} promise that is fulfilled when all properties of `promises`
  597. have been fulfilled, or rejected if any of them become rejected.
  598. */
  599. __exports__["default"] = function hash(object, label) {
  600. return new Promise(function(resolve, reject){
  601. var results = {};
  602. var keys = keysOf(object);
  603. var remaining = keys.length;
  604. var entry, property;
  605. if (remaining === 0) {
  606. resolve(results);
  607. return;
  608. }
  609. function fulfilledTo(property) {
  610. return function(value) {
  611. results[property] = value;
  612. if (--remaining === 0) {
  613. resolve(results);
  614. }
  615. };
  616. }
  617. function onRejection(reason) {
  618. remaining = 0;
  619. reject(reason);
  620. }
  621. for (var i = 0; i < keys.length; i++) {
  622. property = keys[i];
  623. entry = object[property];
  624. if (isNonThenable(entry)) {
  625. results[property] = entry;
  626. if (--remaining === 0) {
  627. resolve(results);
  628. }
  629. } else {
  630. Promise.cast(entry).then(fulfilledTo(property), onRejection);
  631. }
  632. }
  633. });
  634. };
  635. });
  636. define("rsvp/instrument",
  637. ["./config","./utils","exports"],
  638. function(__dependency1__, __dependency2__, __exports__) {
  639. "use strict";
  640. var config = __dependency1__.config;
  641. var now = __dependency2__.now;
  642. __exports__["default"] = function instrument(eventName, promise, child) {
  643. // instrumentation should not disrupt normal usage.
  644. try {
  645. config.trigger(eventName, {
  646. guid: promise._guidKey + promise._id,
  647. eventName: eventName,
  648. detail: promise._detail,
  649. childGuid: child && promise._guidKey + child._id,
  650. label: promise._label,
  651. timeStamp: now(),
  652. stack: new Error(promise._label).stack
  653. });
  654. } catch(error) {
  655. setTimeout(function(){
  656. throw error;
  657. }, 0);
  658. }
  659. };
  660. });
  661. define("rsvp/map",
  662. ["./promise","./all","./utils","exports"],
  663. function(__dependency1__, __dependency2__, __dependency3__, __exports__) {
  664. "use strict";
  665. var Promise = __dependency1__["default"];
  666. var all = __dependency2__["default"];
  667. var isArray = __dependency3__.isArray;
  668. var isFunction = __dependency3__.isFunction;
  669. /**
  670. `RSVP.map` is similar to JavaScript's native `map` method, except that it
  671. waits for all promises to become fulfilled before running the `mapFn` on
  672. each item in given to `promises`. `RSVP.map` returns a promise that will
  673. become fulfilled with the result of running `mapFn` on the values the promises
  674. become fulfilled with.
  675. For example:
  676. ```javascript
  677. var promise1 = RSVP.resolve(1);
  678. var promise2 = RSVP.resolve(2);
  679. var promise3 = RSVP.resolve(3);
  680. var promises = [ promise1, promise2, promise3 ];
  681. var mapFn = function(item){
  682. return item + 1;
  683. };
  684. RSVP.map(promises, mapFn).then(function(result){
  685. // result is [ 2, 3, 4 ]
  686. });
  687. ```
  688. If any of the `promises` given to `RSVP.map` are rejected, the first promise
  689. that is rejected will be given as an argument to the returned promises's
  690. rejection handler. For example:
  691. ```javascript
  692. var promise1 = RSVP.resolve(1);
  693. var promise2 = RSVP.reject(new Error("2"));
  694. var promise3 = RSVP.reject(new Error("3"));
  695. var promises = [ promise1, promise2, promise3 ];
  696. var mapFn = function(item){
  697. return item + 1;
  698. };
  699. RSVP.map(promises, mapFn).then(function(array){
  700. // Code here never runs because there are rejected promises!
  701. }, function(reason) {
  702. // reason.message === "2"
  703. });
  704. ```
  705. `RSVP.map` will also wait if a promise is returned from `mapFn`. For example,
  706. say you want to get all comments from a set of blog posts, but you need
  707. the blog posts first becuase they contain a url to those comments.
  708. ```javscript
  709. var mapFn = function(blogPost){
  710. // getComments does some ajax and returns an RSVP.Promise that is fulfilled
  711. // with some comments data
  712. return getComments(blogPost.comments_url);
  713. };
  714. // getBlogPosts does some ajax and returns an RSVP.Promise that is fulfilled
  715. // with some blog post data
  716. RSVP.map(getBlogPosts(), mapFn).then(function(comments){
  717. // comments is the result of asking the server for the comments
  718. // of all blog posts returned from getBlogPosts()
  719. });
  720. ```
  721. @method map
  722. @for RSVP
  723. @param {Array} promises
  724. @param {Function} mapFn function to be called on each fulfilled promise.
  725. @param {String} label optional string for labeling the promise.
  726. Useful for tooling.
  727. @return {Promise} promise that is fulfilled with the result of calling
  728. `mapFn` on each fulfilled promise or value when they become fulfilled.
  729. The promise will be rejected if any of the given `promises` become rejected.
  730. */
  731. __exports__["default"] = function map(promises, mapFn, label) {
  732. if (!isArray(promises)) {
  733. throw new TypeError('You must pass an array to map.');
  734. }
  735. if (!isFunction(mapFn)){
  736. throw new TypeError("You must pass a function to map's second argument.");
  737. }
  738. return all(promises, label).then(function(results){
  739. var resultLen = results.length,
  740. mappedResults = [],
  741. i;
  742. for (i = 0; i < resultLen; i++){
  743. mappedResults.push(mapFn(results[i]));
  744. }
  745. return all(mappedResults, label);
  746. });
  747. };
  748. });
  749. define("rsvp/node",
  750. ["./promise","exports"],
  751. function(__dependency1__, __exports__) {
  752. "use strict";
  753. var Promise = __dependency1__["default"];
  754. var slice = Array.prototype.slice;
  755. function makeNodeCallbackFor(resolve, reject) {
  756. return function (error, value) {
  757. if (error) {
  758. reject(error);
  759. } else if (arguments.length > 2) {
  760. resolve(slice.call(arguments, 1));
  761. } else {
  762. resolve(value);
  763. }
  764. };
  765. }
  766. /**
  767. `RSVP.denodeify` takes a "node-style" function and returns a function that
  768. will return an `RSVP.Promise`. You can use `denodeify` in Node.js or the
  769. browser when you'd prefer to use promises over using callbacks. For example,
  770. `denodeify` transforms the following:
  771. ```javascript
  772. var fs = require('fs');
  773. fs.readFile('myfile.txt', function(err, data){
  774. if (err) return handleError(err);
  775. handleData(data);
  776. });
  777. ```
  778. into:
  779. ```javascript
  780. var fs = require('fs');
  781. var readFile = RSVP.denodeify(fs.readFile);
  782. readFile('myfile.txt').then(handleData, handleError);
  783. ```
  784. Using `denodeify` makes it easier to compose asynchronous operations instead
  785. of using callbacks. For example, instead of:
  786. ```javascript
  787. var fs = require('fs');
  788. var log = require('some-async-logger');
  789. fs.readFile('myfile.txt', function(err, data){
  790. if (err) return handleError(err);
  791. fs.writeFile('myfile2.txt', data, function(err){
  792. if (err) throw err;
  793. log('success', function(err) {
  794. if (err) throw err;
  795. });
  796. });
  797. });
  798. ```
  799. You can chain the operations together using `then` from the returned promise:
  800. ```javascript
  801. var fs = require('fs');
  802. var denodeify = RSVP.denodeify;
  803. var readFile = denodeify(fs.readFile);
  804. var writeFile = denodeify(fs.writeFile);
  805. var log = denodeify(require('some-async-logger'));
  806. readFile('myfile.txt').then(function(data){
  807. return writeFile('myfile2.txt', data);
  808. }).then(function(){
  809. return log('SUCCESS');
  810. }).then(function(){
  811. // success handler
  812. }, function(reason){
  813. // rejection handler
  814. });
  815. ```
  816. @method denodeify
  817. @for RSVP
  818. @param {Function} nodeFunc a "node-style" function that takes a callback as
  819. its last argument. The callback expects an error to be passed as its first
  820. argument (if an error occurred, otherwise null), and the value from the
  821. operation as its second argument ("function(err, value){ }").
  822. @param {Any} binding optional argument for binding the "this" value when
  823. calling the `nodeFunc` function.
  824. @return {Function} a function that wraps `nodeFunc` to return an
  825. `RSVP.Promise`
  826. */
  827. __exports__["default"] = function denodeify(nodeFunc, binding) {
  828. return function() {
  829. var nodeArgs = slice.call(arguments), resolve, reject;
  830. var thisArg = this || binding;
  831. return new Promise(function(resolve, reject) {
  832. Promise.all(nodeArgs).then(function(nodeArgs) {
  833. try {
  834. nodeArgs.push(makeNodeCallbackFor(resolve, reject));
  835. nodeFunc.apply(thisArg, nodeArgs);
  836. } catch(e) {
  837. reject(e);
  838. }
  839. });
  840. });
  841. };
  842. };
  843. });
  844. define("rsvp/promise",
  845. ["./config","./events","./instrument","./utils","./promise/cast","./promise/all","./promise/race","./promise/resolve","./promise/reject","exports"],
  846. function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __dependency5__, __dependency6__, __dependency7__, __dependency8__, __dependency9__, __exports__) {
  847. "use strict";
  848. var config = __dependency1__.config;
  849. var EventTarget = __dependency2__["default"];
  850. var instrument = __dependency3__["default"];
  851. var objectOrFunction = __dependency4__.objectOrFunction;
  852. var isFunction = __dependency4__.isFunction;
  853. var now = __dependency4__.now;
  854. var cast = __dependency5__["default"];
  855. var all = __dependency6__["default"];
  856. var race = __dependency7__["default"];
  857. var Resolve = __dependency8__["default"];
  858. var Reject = __dependency9__["default"];
  859. var guidKey = 'rsvp_' + now() + '-';
  860. var counter = 0;
  861. function noop() {}
  862. __exports__["default"] = Promise;
  863. /**
  864. Promise objects represent the eventual result of an asynchronous operation. The
  865. primary way of interacting with a promise is through its `then` method, which
  866. registers callbacks to receive either a promise’s eventual value or the reason
  867. why the promise cannot be fulfilled.
  868. Terminology
  869. -----------
  870. - `promise` is an object or function with a `then` method whose behavior conforms to this specification.
  871. - `thenable` is an object or function that defines a `then` method.
  872. - `value` is any legal JavaScript value (including undefined, a thenable, or a promise).
  873. - `exception` is a value that is thrown using the throw statement.
  874. - `reason` is a value that indicates why a promise was rejected.
  875. - `settled` the final resting state of a promise, fulfilled or rejected.
  876. A promise can be in one of three states: pending, fulfilled, or rejected.
  877. Basic Usage:
  878. ------------
  879. ```js
  880. var promise = new Promise(function(resolve, reject) {
  881. // on success
  882. resolve(value);
  883. // on failure
  884. reject(reason);
  885. });
  886. promise.then(function(value) {
  887. // on fulfillment
  888. }, function(reason) {
  889. // on rejection
  890. });
  891. ```
  892. Advanced Usage:
  893. ---------------
  894. Promises shine when abstracting away asynchronous interactions such as
  895. `XMLHttpRequest`s.
  896. ```js
  897. function getJSON(url) {
  898. return new Promise(function(resolve, reject){
  899. var xhr = new XMLHttpRequest();
  900. xhr.open('GET', url);
  901. xhr.onreadystatechange = handler;
  902. xhr.responseType = 'json';
  903. xhr.setRequestHeader('Accept', 'application/json');
  904. xhr.send();
  905. function handler() {
  906. if (this.readyState === this.DONE) {
  907. if (this.status === 200) {
  908. resolve(this.response);
  909. } else {
  910. reject(new Error("getJSON: `" + url + "` failed with status: [" + this.status + "]");
  911. }
  912. }
  913. };
  914. });
  915. }
  916. getJSON('/posts.json').then(function(json) {
  917. // on fulfillment
  918. }, function(reason) {
  919. // on rejection
  920. });
  921. ```
  922. Unlike callbacks, promises are great composable primitives.
  923. ```js
  924. Promise.all([
  925. getJSON('/posts'),
  926. getJSON('/comments')
  927. ]).then(function(values){
  928. values[0] // => postsJSON
  929. values[1] // => commentsJSON
  930. return values;
  931. });
  932. ```
  933. @class Promise
  934. @param {function}
  935. @param {String} label optional string for labeling the promise.
  936. Useful for tooling.
  937. @constructor
  938. */
  939. function Promise(resolver, label) {
  940. if (!isFunction(resolver)) {
  941. throw new TypeError('You must pass a resolver function as the first argument to the promise constructor');
  942. }
  943. if (!(this instanceof Promise)) {
  944. throw new TypeError("Failed to construct 'Promise': Please use the 'new' operator, this object constructor cannot be called as a function.");
  945. }
  946. this._id = counter++;
  947. this._label = label;
  948. this._subscribers = [];
  949. if (config.instrument) {
  950. instrument('created', this);
  951. }
  952. if (noop !== resolver) {
  953. invokeResolver(resolver, this);
  954. }
  955. }
  956. function invokeResolver(resolver, promise) {
  957. function resolvePromise(value) {
  958. resolve(promise, value);
  959. }
  960. function rejectPromise(reason) {
  961. reject(promise, reason);
  962. }
  963. try {
  964. resolver(resolvePromise, rejectPromise);
  965. } catch(e) {
  966. rejectPromise(e);
  967. }
  968. }
  969. Promise.cast = cast;
  970. Promise.all = all;
  971. Promise.race = race;
  972. Promise.resolve = Resolve;
  973. Promise.reject = Reject;
  974. var PENDING = void 0;
  975. var SEALED = 0;
  976. var FULFILLED = 1;
  977. var REJECTED = 2;
  978. function subscribe(parent, child, onFulfillment, onRejection) {
  979. var subscribers = parent._subscribers;
  980. var length = subscribers.length;
  981. subscribers[length] = child;
  982. subscribers[length + FULFILLED] = onFulfillment;
  983. subscribers[length + REJECTED] = onRejection;
  984. }
  985. function publish(promise, settled) {
  986. var child, callback, subscribers = promise._subscribers, detail = promise._detail;
  987. if (config.instrument) {
  988. instrument(settled === FULFILLED ? 'fulfilled' : 'rejected', promise);
  989. }
  990. for (var i = 0; i < subscribers.length; i += 3) {
  991. child = subscribers[i];
  992. callback = subscribers[i + settled];
  993. invokeCallback(settled, child, callback, detail);
  994. }
  995. promise._subscribers = null;
  996. }
  997. Promise.prototype = {
  998. /**
  999. @property constructor
  1000. */
  1001. constructor: Promise,
  1002. _id: undefined,
  1003. _guidKey: guidKey,
  1004. _label: undefined,
  1005. _state: undefined,
  1006. _detail: undefined,
  1007. _subscribers: undefined,
  1008. _onerror: function (reason) {
  1009. config.trigger('error', reason);
  1010. },
  1011. /**
  1012. A promise represents the eventual result of an asynchronous operation. The
  1013. primary way of interacting with a promise is through its `then` method, which
  1014. registers callbacks to receive either a promise's eventual value or the reason
  1015. why the promise cannot be fulfilled.
  1016. ```js
  1017. findUser().then(function(user){
  1018. // user is available
  1019. }, function(reason){
  1020. // user is unavailable, and you are given the reason why
  1021. });
  1022. ```
  1023. Chaining
  1024. --------
  1025. The return value of `then` is itself a promise. This second, "downstream"
  1026. promise is resolved with the return value of the first promise's fulfillment
  1027. or rejection handler, or rejected if the handler throws an exception.
  1028. ```js
  1029. findUser().then(function (user) {
  1030. return user.name;
  1031. }, function (reason) {
  1032. return "default name";
  1033. }).then(function (userName) {
  1034. // If `findUser` fulfilled, `userName` will be the user's name, otherwise it
  1035. // will be `"default name"`
  1036. });
  1037. findUser().then(function (user) {
  1038. throw "Found user, but still unhappy";
  1039. }, function (reason) {
  1040. throw "`findUser` rejected and we're unhappy";
  1041. }).then(function (value) {
  1042. // never reached
  1043. }, function (reason) {
  1044. // if `findUser` fulfilled, `reason` will be "Found user, but still unhappy".
  1045. // If `findUser` rejected, `reason` will be "`findUser` rejected and we're unhappy".
  1046. });
  1047. ```
  1048. If the downstream promise does not specify a rejection handler, rejection reasons will be propagated further downstream.
  1049. ```js
  1050. findUser().then(function (user) {
  1051. throw new PedagogicalException("Upstream error");
  1052. }).then(function (value) {
  1053. // never reached
  1054. }).then(function (value) {
  1055. // never reached
  1056. }, function (reason) {
  1057. // The `PedgagocialException` is propagated all the way down to here
  1058. });
  1059. ```
  1060. Assimilation
  1061. ------------
  1062. Sometimes the value you want to propagate to a downstream promise can only be
  1063. retrieved asynchronously. This can be achieved by returning a promise in the
  1064. fulfillment or rejection handler. The downstream promise will then be pending
  1065. until the returned promise is settled. This is called *assimilation*.
  1066. ```js
  1067. findUser().then(function (user) {
  1068. return findCommentsByAuthor(user);
  1069. }).then(function (comments) {
  1070. // The user's comments are now available
  1071. });
  1072. ```
  1073. If the assimliated promise rejects, then the downstream promise will also reject.
  1074. ```js
  1075. findUser().then(function (user) {
  1076. return findCommentsByAuthor(user);
  1077. }).then(function (comments) {
  1078. // If `findCommentsByAuthor` fulfills, we'll have the value here
  1079. }, function (reason) {
  1080. // If `findCommentsByAuthor` rejects, we'll have the reason here
  1081. });
  1082. ```
  1083. Simple Example
  1084. --------------
  1085. Synchronous Example
  1086. ```javascript
  1087. var result;
  1088. try {
  1089. result = findResult();
  1090. // success
  1091. } catch(reason) {
  1092. // failure
  1093. }
  1094. ```
  1095. Errback Example
  1096. ```js
  1097. findResult(function(result, err){
  1098. if (err) {
  1099. // failure
  1100. } else {
  1101. // success
  1102. }
  1103. });
  1104. ```
  1105. Promise Example;
  1106. ```javacsript
  1107. findResult().then(function(result){
  1108. }, function(reason){
  1109. });
  1110. ```
  1111. Advanced Example
  1112. --------------
  1113. Synchronous Example
  1114. ```javascript
  1115. var author, books;
  1116. try {
  1117. author = findAuthor();
  1118. books = findBooksByAuthor(author);
  1119. // success
  1120. } catch(reason) {
  1121. // failure
  1122. }
  1123. ```
  1124. Errback Example
  1125. ```js
  1126. function foundBooks(books) {
  1127. }
  1128. function failure(reason) {
  1129. }
  1130. findAuthor(function(author, err){
  1131. if (err) {
  1132. failure(err);
  1133. // failure
  1134. } else {
  1135. try {
  1136. findBoooksByAuthor(author, function(books, err) {
  1137. if (err) {
  1138. failure(err);
  1139. } else {
  1140. try {
  1141. foundBooks(books);
  1142. } catch(reason) {
  1143. failure(reason);
  1144. }
  1145. }
  1146. });
  1147. } catch(error) {
  1148. failure(err);
  1149. }
  1150. // success
  1151. }
  1152. });
  1153. ```
  1154. Promise Example;
  1155. ```javacsript
  1156. findAuthor().
  1157. then(findBooksByAuthor).
  1158. then(function(books){
  1159. // found books
  1160. }).catch(function(reason){
  1161. // something went wrong;
  1162. });
  1163. ```
  1164. @method then
  1165. @param {Function} onFulfillment
  1166. @param {Function} onRejection
  1167. @param {String} label optional string for labeling the promise.
  1168. Useful for tooling.
  1169. @return {Promise}
  1170. */
  1171. then: function(onFulfillment, onRejection, label) {
  1172. var promise = this;
  1173. this._onerror = null;
  1174. var thenPromise = new this.constructor(noop, label);
  1175. if (this._state) {
  1176. var callbacks = arguments;
  1177. config.async(function invokePromiseCallback() {
  1178. invokeCallback(promise._state, thenPromise, callbacks[promise._state - 1], promise._detail);
  1179. });
  1180. } else {
  1181. subscribe(this, thenPromise, onFulfillment, onRejection);
  1182. }
  1183. if (config.instrument) {
  1184. instrument('chained', promise, thenPromise);
  1185. }
  1186. return thenPromise;
  1187. },
  1188. /**
  1189. `catch` is simply sugar for `then(null, onRejection)` which makes it the same
  1190. as the catch block, of a try/catch statement.
  1191. ```js
  1192. function findAuthor(){
  1193. throw new Error("couldn't find that author");
  1194. }
  1195. // synchronous
  1196. try {
  1197. findAuthor();
  1198. } catch(reason) {
  1199. }
  1200. // async with promises
  1201. findAuthor().catch(function(reason){
  1202. // something went wrong;
  1203. });
  1204. ```
  1205. @method catch
  1206. @param {Function} onRejection
  1207. @param {String} label optional string for labeling the promise.
  1208. Useful for tooling.
  1209. @return {Promise}
  1210. */
  1211. 'catch': function(onRejection, label) {
  1212. return this.then(null, onRejection, label);
  1213. },
  1214. /**
  1215. `finally` will be invoked regardless of the promise's fate just as native
  1216. try/catch/finally behaves
  1217. ```js
  1218. findAuthor() {
  1219. if (Math.random() > 0.5) {
  1220. throw new Error();
  1221. }
  1222. return new Author();
  1223. }
  1224. try {
  1225. return findAuthor(); // succeed or fail
  1226. } catch(error) {
  1227. return findOtherAuther();
  1228. } finally {
  1229. // always runs
  1230. // doesn't effect the return value
  1231. }
  1232. findAuthor().finally(function(){
  1233. // author was either found, or not
  1234. });
  1235. ```
  1236. @method finally
  1237. @param {Function} callback
  1238. @param {String} label optional string for labeling the promise.
  1239. Useful for tooling.
  1240. @return {Promise}
  1241. */
  1242. 'finally': function(callback, label) {
  1243. var constructor = this.constructor;
  1244. return this.then(function(value) {
  1245. return constructor.cast(callback()).then(function(){
  1246. return value;
  1247. });
  1248. }, function(reason) {
  1249. return constructor.cast(callback()).then(function(){
  1250. throw reason;
  1251. });
  1252. }, label);
  1253. }
  1254. };
  1255. function invokeCallback(settled, promise, callback, detail) {
  1256. var hasCallback = isFunction(callback),
  1257. value, error, succeeded, failed;
  1258. if (hasCallback) {
  1259. try {
  1260. value = callback(detail);
  1261. succeeded = true;
  1262. } catch(e) {
  1263. failed = true;
  1264. error = e;
  1265. }
  1266. } else {
  1267. value = detail;
  1268. succeeded = true;
  1269. }
  1270. if (handleThenable(promise, value)) {
  1271. return;
  1272. } else if (hasCallback && succeeded) {
  1273. resolve(promise, value);
  1274. } else if (failed) {
  1275. reject(promise, error);
  1276. } else if (settled === FULFILLED) {
  1277. resolve(promise, value);
  1278. } else if (settled === REJECTED) {
  1279. reject(promise, value);
  1280. }
  1281. }
  1282. function handleThenable(promise, value) {
  1283. var then = null,
  1284. resolved;
  1285. try {
  1286. if (promise === value) {
  1287. throw new TypeError("A promises callback cannot return that same promise.");
  1288. }
  1289. if (objectOrFunction(value)) {
  1290. then = value.then;
  1291. if (isFunction(then)) {
  1292. then.call(value, function(val) {
  1293. if (resolved) { return true; }
  1294. resolved = true;
  1295. if (value !== val) {
  1296. resolve(promise, val);
  1297. } else {
  1298. fulfill(promise, val);
  1299. }
  1300. }, function(val) {
  1301. if (resolved) { return true; }
  1302. resolved = true;
  1303. reject(promise, val);
  1304. }, 'derived from: ' + (promise._label || ' unknown promise'));
  1305. return true;
  1306. }
  1307. }
  1308. } catch (error) {
  1309. if (resolved) { return true; }
  1310. reject(promise, error);
  1311. return true;
  1312. }
  1313. return false;
  1314. }
  1315. function resolve(promise, value) {
  1316. if (promise === value) {
  1317. fulfill(promise, value);
  1318. } else if (!handleThenable(promise, value)) {
  1319. fulfill(promise, value);
  1320. }
  1321. }
  1322. function fulfill(promise, value) {
  1323. if (promise._state !== PENDING) { return; }
  1324. promise._state = SEALED;
  1325. promise._detail = value;
  1326. config.async(publishFulfillment, promise);
  1327. }
  1328. function reject(promise, reason) {
  1329. if (promise._state !== PENDING) { return; }
  1330. promise._state = SEALED;
  1331. promise._detail = reason;
  1332. config.async(publishRejection, promise);
  1333. }
  1334. function publishFulfillment(promise) {
  1335. publish(promise, promise._state = FULFILLED);
  1336. }
  1337. function publishRejection(promise) {
  1338. if (promise._onerror) {
  1339. promise._onerror(promise._detail);
  1340. }
  1341. publish(promise, promise._state = REJECTED);
  1342. }
  1343. });
  1344. define("rsvp/promise/all",
  1345. ["../utils","exports"],
  1346. function(__dependency1__, __exports__) {
  1347. "use strict";
  1348. var isArray = __dependency1__.isArray;
  1349. var isNonThenable = __dependency1__.isNonThenable;
  1350. /**
  1351. `RSVP.Promise.all` returns a new promise which is fulfilled with an array of
  1352. fulfillment values for the passed promises, or rejects with the reason of the
  1353. first passed promise that rejects. It casts all elements of the passed iterable
  1354. to promises as it runs this algorithm.
  1355. Example:
  1356. ```javascript
  1357. var promise1 = RSVP.resolve(1);
  1358. var promise2 = RSVP.resolve(2);
  1359. var promise3 = RSVP.resolve(3);
  1360. var promises = [ promise1, promise2, promise3 ];
  1361. RSVP.Promise.all(promises).then(function(array){
  1362. // The array here would be [ 1, 2, 3 ];
  1363. });
  1364. ```
  1365. If any of the `promises` given to `RSVP.all` are rejected, the first promise
  1366. that is rejected will be given as an argument to the returned promises's
  1367. rejection handler. For example:
  1368. Example:
  1369. ```javascript
  1370. var promise1 = RSVP.resolve(1);
  1371. var promise2 = RSVP.reject(new Error("2"));
  1372. var promise3 = RSVP.reject(new Error("3"));
  1373. var promises = [ promise1, promise2, promise3 ];
  1374. RSVP.Promise.all(promises).then(function(array){
  1375. // Code here never runs because there are rejected promises!
  1376. }, function(error) {
  1377. // error.message === "2"
  1378. });
  1379. ```
  1380. @method all
  1381. @for RSVP.Promise
  1382. @param {Array} promises
  1383. @param {String} label optional string for labeling the promise.
  1384. Useful for tooling.
  1385. @return {Promise} promise that is fulfilled when all `promises` have been
  1386. fulfilled, or rejected if any of them become rejected.
  1387. */
  1388. __exports__["default"] = function all(entries, label) {
  1389. /*jshint validthis:true */
  1390. var Constructor = this;
  1391. return new Constructor(function(resolve, reject) {
  1392. if (!isArray(entries)) {
  1393. throw new TypeError('You must pass an array to all.');
  1394. }
  1395. var remaining = entries.length;
  1396. var results = new Array(remaining);
  1397. var entry, pending = true;
  1398. if (remaining === 0) {
  1399. resolve(results);
  1400. return;
  1401. }
  1402. function fulfillmentAt(index) {
  1403. return function(value) {
  1404. results[index] = value;
  1405. if (--remaining === 0) {
  1406. resolve(results);
  1407. }
  1408. };
  1409. }
  1410. function onRejection(reason) {
  1411. remaining = 0;
  1412. reject(reason);
  1413. }
  1414. for (var index = 0; index < entries.length; index++) {
  1415. entry = entries[index];
  1416. if (isNonThenable(entry)) {
  1417. results[index] = entry;
  1418. if (--remaining === 0) {
  1419. resolve(results);
  1420. }
  1421. } else {
  1422. Constructor.cast(entry).then(fulfillmentAt(index), onRejection);
  1423. }
  1424. }
  1425. }, label);
  1426. };
  1427. });
  1428. define("rsvp/promise/cast",
  1429. ["exports"],
  1430. function(__exports__) {
  1431. "use strict";
  1432. /**
  1433. `RSVP.Promise.cast` cast coerces its argument to a promise, or returns the
  1434. argument if it is already a promise which shares a constructor with the caster;
  1435. Example:
  1436. ```javascript
  1437. var promise = RSVP.Promise.resolve(1);
  1438. var casted = RSVP.Promise.cast(promise);
  1439. console.log(promise === casted); // true
  1440. ```
  1441. In the case of a promise whose constructor does not match, it is assimilated.
  1442. The resulting promise will fulfill or reject based on the outcome of the
  1443. promise being casted.
  1444. In the case of a non-promise, a promise which will fulfill with that value is
  1445. returned.
  1446. Example:
  1447. ```javascript
  1448. var value = 1; // could be a number, boolean, string, undefined...
  1449. var casted = RSVP.Promise.cast(value);
  1450. console.log(value === casted); // false
  1451. console.log(casted instanceof RSVP.Promise) // true
  1452. casted.then(function(val) {
  1453. val === value // => true
  1454. });
  1455. ```
  1456. `RSVP.Promise.cast` is similar to `RSVP.Promise.resolve`, but `RSVP.Promise.cast` differs in the
  1457. following ways:
  1458. * `RSVP.Promise.cast` serves as a memory-efficient way of getting a promise, when you
  1459. have something that could either be a promise or a value. RSVP.resolve
  1460. will have the same effect but will create a new promise wrapper if the
  1461. argument is a promise.
  1462. * `RSVP.Promise.cast` is a way of casting incoming thenables or promise subclasses to
  1463. promises of the exact class specified, so that the resulting object's `then` is
  1464. ensured to have the behavior of the constructor you are calling cast on (i.e., RSVP.Promise).
  1465. @method cast
  1466. @for RSVP.Promise
  1467. @param {Object} object to be casted
  1468. @param {String} label optional string for labeling the promise.
  1469. Useful for tooling.
  1470. @return {Promise} promise
  1471. */
  1472. __exports__["default"] = function cast(object, label) {
  1473. /*jshint validthis:true */
  1474. var Constructor = this;
  1475. if (object && typeof object === 'object' && object.constructor === Constructor) {
  1476. return object;
  1477. }
  1478. return new Constructor(function(resolve) {
  1479. resolve(object);
  1480. }, label);
  1481. };
  1482. });
  1483. define("rsvp/promise/race",
  1484. ["../utils","exports"],
  1485. function(__dependency1__, __exports__) {
  1486. "use strict";
  1487. /* global toString */
  1488. var isArray = __dependency1__.isArray;
  1489. var isFunction = __dependency1__.isFunction;
  1490. var isNonThenable = __dependency1__.isNonThenable;
  1491. /**
  1492. `RSVP.Promise.race` returns a new promise which is settled in the same way as the
  1493. first passed promise to settle.
  1494. Example:
  1495. ```javascript
  1496. var promise1 = new RSVP.Promise(function(resolve, reject){
  1497. setTimeout(function(){
  1498. resolve("promise 1");
  1499. }, 200);
  1500. });
  1501. var promise2 = new RSVP.Promise(function(resolve, reject){
  1502. setTimeout(function(){
  1503. resolve("promise 2");
  1504. }, 100);
  1505. });
  1506. RSVP.Promise.race([promise1, promise2]).then(function(result){
  1507. // result === "promise 2" because it was resolved before promise1
  1508. // was resolved.
  1509. });
  1510. ```
  1511. `RSVP.Promise.race` is deterministic in that only the state of the first
  1512. completed promise matters. For example, even if other promises given to the
  1513. `promises` array argument are resolved, but the first completed promise has
  1514. become rejected before the other promises became fulfilled, the returned
  1515. promise will become rejected:
  1516. ```javascript
  1517. var promise1 = new RSVP.Promise(function(resolve, reject){
  1518. setTimeout(function(){
  1519. resolve("promise 1");
  1520. }, 200);
  1521. });
  1522. var promise2 = new RSVP.Promise(function(resolve, reject){
  1523. setTimeout(function(){
  1524. reject(new Error("promise 2"));
  1525. }, 100);
  1526. });
  1527. RSVP.Promise.race([promise1, promise2]).then(function(result){
  1528. // Code here never runs because there are rejected promises!
  1529. }, function(reason){
  1530. // reason.message === "promise2" because promise 2 became rejected before
  1531. // promise 1 became fulfilled
  1532. });
  1533. ```
  1534. @method race
  1535. @for RSVP.Promise
  1536. @param {Array} promises array of promises to observe
  1537. @param {String} label optional string for describing the promise returned.
  1538. Useful for tooling.
  1539. @return {Promise} a promise which settles in the same way as the first passed
  1540. promise to settle.
  1541. */
  1542. __exports__["default"] = function race(entries, label) {
  1543. /*jshint validthis:true */
  1544. var Constructor = this, entry;
  1545. return new Constructor(function(resolve, reject) {
  1546. if (!isArray(entries)) {
  1547. throw new TypeError('You must pass an array to race.');
  1548. }
  1549. var pending = true;
  1550. function onFulfillment(value) { if (pending) { pending = false; resolve(value); } }
  1551. function onRejection(reason) { if (pending) { pending = false; reject(reason); } }
  1552. for (var i = 0; i < entries.length; i++) {
  1553. entry = entries[i];
  1554. if (isNonThenable(entry)) {
  1555. pending = false;
  1556. resolve(entry);
  1557. return;
  1558. } else {
  1559. Constructor.cast(entry).then(onFulfillment, onRejection);
  1560. }
  1561. }
  1562. }, label);
  1563. };
  1564. });
  1565. define("rsvp/promise/reject",
  1566. ["exports"],
  1567. function(__exports__) {
  1568. "use strict";
  1569. /**
  1570. `RSVP.Promise.reject` returns a promise rejected with the passed `reason`.
  1571. It is essentially shorthand for the following:
  1572. ```javascript
  1573. var promise = new RSVP.Promise(function(resolve, reject){
  1574. reject(new Error('WHOOPS'));
  1575. });
  1576. promise.then(function(value){
  1577. // Code here doesn't run because the promise is rejected!
  1578. }, function(reason){
  1579. // reason.message === 'WHOOPS'
  1580. });
  1581. ```
  1582. Instead of writing the above, your code now simply becomes the following:
  1583. ```javascript
  1584. var promise = RSVP.Promise.reject(new Error('WHOOPS'));
  1585. promise.then(function(value){
  1586. // Code here doesn't run because the promise is rejected!
  1587. }, function(reason){
  1588. // reason.message === 'WHOOPS'
  1589. });
  1590. ```
  1591. @method reject
  1592. @for RSVP.Promise
  1593. @param {Any} reason value that the returned promise will be rejected with.
  1594. @param {String} label optional string for identifying the returned promise.
  1595. Useful for tooling.
  1596. @return {Promise} a promise rejected with the given `reason`.
  1597. */
  1598. __exports__["default"] = function reject(reason, label) {
  1599. /*jshint validthis:true */
  1600. var Constructor = this;
  1601. return new Constructor(function (resolve, reject) {
  1602. reject(reason);
  1603. }, label);
  1604. };
  1605. });
  1606. define("rsvp/promise/resolve",
  1607. ["exports"],
  1608. function(__exports__) {
  1609. "use strict";
  1610. /**
  1611. `RSVP.Promise.resolve` returns a promise that will become fulfilled with the passed
  1612. `value`. It is essentially shorthand for the following:
  1613. ```javascript
  1614. var promise = new RSVP.Promise(function(resolve, reject){
  1615. resolve(1);
  1616. });
  1617. promise.then(function(value){
  1618. // value === 1
  1619. });
  1620. ```
  1621. Instead of writing the above, your code now simply becomes the following:
  1622. ```javascript
  1623. var promise = RSVP.Promise.resolve(1);
  1624. promise.then(function(value){
  1625. // value === 1
  1626. });
  1627. ```
  1628. @method resolve
  1629. @for RSVP.Promise
  1630. @param {Any} value value that the returned promise will be resolved with
  1631. @param {String} label optional string for identifying the returned promise.
  1632. Useful for tooling.
  1633. @return {Promise} a promise that will become fulfilled with the given
  1634. `value`
  1635. */
  1636. __exports__["default"] = function resolve(value, label) {
  1637. /*jshint validthis:true */
  1638. var Constructor = this;
  1639. return new Constructor(function(resolve, reject) {
  1640. resolve(value);
  1641. }, label);
  1642. };
  1643. });
  1644. define("rsvp/race",
  1645. ["./promise","exports"],
  1646. function(__dependency1__, __exports__) {
  1647. "use strict";
  1648. var Promise = __dependency1__["default"];
  1649. __exports__["default"] = function race(array, label) {
  1650. return Promise.race(array, label);
  1651. };
  1652. });
  1653. define("rsvp/reject",
  1654. ["./promise","exports"],
  1655. function(__dependency1__, __exports__) {
  1656. "use strict";
  1657. var Promise = __dependency1__["default"];
  1658. __exports__["default"] = function reject(reason, label) {
  1659. return Promise.reject(reason, label);
  1660. };
  1661. });
  1662. define("rsvp/resolve",
  1663. ["./promise","exports"],
  1664. function(__dependency1__, __exports__) {
  1665. "use strict";
  1666. var Promise = __dependency1__["default"];
  1667. __exports__["default"] = function resolve(value, label) {
  1668. return Promise.resolve(value, label);
  1669. };
  1670. });
  1671. define("rsvp/rethrow",
  1672. ["exports"],
  1673. function(__exports__) {
  1674. "use strict";
  1675. /**
  1676. `RSVP.rethrow` will rethrow an error on the next turn of the JavaScript event
  1677. loop in order to aid debugging.
  1678. Promises A+ specifies that any exceptions that occur with a promise must be
  1679. caught by the promises implementation and bubbled to the last handler. For
  1680. this reason, it is recommended that you always specify a second rejection
  1681. handler function to `then`. However, `RSVP.rethrow` will throw the exception
  1682. outside of the promise, so it bubbles up to your console if in the browser,
  1683. or domain/cause uncaught exception in Node. `rethrow` will throw the error
  1684. again so the error can be handled by the promise.
  1685. ```javascript
  1686. function throws(){
  1687. throw new Error('Whoops!');
  1688. }
  1689. var promise = new RSVP.Promise(function(resolve, reject){
  1690. throws();
  1691. });
  1692. promise.catch(RSVP.rethrow).then(function(){
  1693. // Code here doesn't run because the promise became rejected due to an
  1694. // error!
  1695. }, function (err){
  1696. // handle the error here
  1697. });
  1698. ```
  1699. The 'Whoops' error will be thrown on the next turn of the event loop
  1700. and you can watch for it in your console. You can also handle it using a
  1701. rejection handler given to `.then` or `.catch` on the returned promise.
  1702. @method rethrow
  1703. @for RSVP
  1704. @param {Error} reason reason the promise became rejected.
  1705. @throws Error
  1706. */
  1707. __exports__["default"] = function rethrow(reason) {
  1708. setTimeout(function() {
  1709. throw reason;
  1710. });
  1711. throw reason;
  1712. };
  1713. });
  1714. define("rsvp/utils",
  1715. ["exports"],
  1716. function(__exports__) {
  1717. "use strict";
  1718. function objectOrFunction(x) {
  1719. return typeof x === "function" || (typeof x === "object" && x !== null);
  1720. }
  1721. __exports__.objectOrFunction = objectOrFunction;function isFunction(x) {
  1722. return typeof x === "function";
  1723. }
  1724. __exports__.isFunction = isFunction;function isNonThenable(x) {
  1725. return !objectOrFunction(x);
  1726. }
  1727. __exports__.isNonThenable = isNonThenable;function isArray(x) {
  1728. return Object.prototype.toString.call(x) === "[object Array]";
  1729. }
  1730. __exports__.isArray = isArray;// Date.now is not available in browsers < IE9
  1731. // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/now#Compatibility
  1732. var now = Date.now || function() { return new Date().getTime(); };
  1733. __exports__.now = now;
  1734. var keysOf = Object.keys || function(object) {
  1735. var result = [];
  1736. for (var prop in object) {
  1737. result.push(prop);
  1738. }
  1739. return result;
  1740. };
  1741. __exports__.keysOf = keysOf;
  1742. });
  1743. define("rsvp",
  1744. ["./rsvp/promise","./rsvp/events","./rsvp/node","./rsvp/all","./rsvp/all_settled","./rsvp/race","./rsvp/hash","./rsvp/rethrow","./rsvp/defer","./rsvp/config","./rsvp/map","./rsvp/resolve","./rsvp/reject","./rsvp/asap","./rsvp/filter","exports"],
  1745. function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __dependency5__, __dependency6__, __dependency7__, __dependency8__, __dependency9__, __dependency10__, __dependency11__, __dependency12__, __dependency13__, __dependency14__, __dependency15__, __exports__) {
  1746. "use strict";
  1747. var Promise = __dependency1__["default"];
  1748. var EventTarget = __dependency2__["default"];
  1749. var denodeify = __dependency3__["default"];
  1750. var all = __dependency4__["default"];
  1751. var allSettled = __dependency5__["default"];
  1752. var race = __dependency6__["default"];
  1753. var hash = __dependency7__["default"];
  1754. var rethrow = __dependency8__["default"];
  1755. var defer = __dependency9__["default"];
  1756. var config = __dependency10__.config;
  1757. var configure = __dependency10__.configure;
  1758. var map = __dependency11__["default"];
  1759. var resolve = __dependency12__["default"];
  1760. var reject = __dependency13__["default"];
  1761. var asap = __dependency14__["default"];
  1762. var filter = __dependency15__["default"];
  1763. config.async = asap; // default async is asap;
  1764. function async(callback, arg) {
  1765. config.async(callback, arg);
  1766. }
  1767. function on() {
  1768. config.on.apply(config, arguments);
  1769. }
  1770. function off() {
  1771. config.off.apply(config, arguments);
  1772. }
  1773. // Set up instrumentation through `window.__PROMISE_INTRUMENTATION__`
  1774. if (typeof window !== 'undefined' && typeof window.__PROMISE_INSTRUMENTATION__ === 'object') {
  1775. var callbacks = window.__PROMISE_INSTRUMENTATION__;
  1776. configure('instrument', true);
  1777. for (var eventName in callbacks) {
  1778. if (callbacks.hasOwnProperty(eventName)) {
  1779. on(eventName, callbacks[eventName]);
  1780. }
  1781. }
  1782. }
  1783. __exports__.Promise = Promise;
  1784. __exports__.EventTarget = EventTarget;
  1785. __exports__.all = all;
  1786. __exports__.allSettled = allSettled;
  1787. __exports__.race = race;
  1788. __exports__.hash = hash;
  1789. __exports__.rethrow = rethrow;
  1790. __exports__.defer = defer;
  1791. __exports__.denodeify = denodeify;
  1792. __exports__.configure = configure;
  1793. __exports__.on = on;
  1794. __exports__.off = off;
  1795. __exports__.resolve = resolve;
  1796. __exports__.reject = reject;
  1797. __exports__.async = async;
  1798. __exports__.map = map;
  1799. __exports__.filter = filter;
  1800. });
  1801. global.RSVP = requireModule('rsvp');
  1802. }(window));
  1803. 'use strict';
  1804. var EPUBJS = EPUBJS || {};
  1805. EPUBJS.VERSION = "0.2.1";
  1806. EPUBJS.plugins = EPUBJS.plugins || {};
  1807. EPUBJS.filePath = EPUBJS.filePath || "/epubjs/";
  1808. EPUBJS.Render = {};
  1809. (function(root) {
  1810. var previousEpub = root.ePub || {};
  1811. var ePub = root.ePub = function() {
  1812. var bookPath, options;
  1813. //-- var book = ePub("path/to/book.epub", { restore: true })
  1814. if(typeof(arguments[0]) != 'undefined' &&
  1815. typeof arguments[0] === 'string') {
  1816. bookPath = arguments[0];
  1817. if( arguments[1] && typeof arguments[1] === 'object' ) {
  1818. options = arguments[1];
  1819. options.bookPath = bookPath;
  1820. } else {
  1821. options = { 'bookPath' : bookPath };
  1822. }
  1823. }
  1824. /*
  1825. * var book = ePub({ bookPath: "path/to/book.epub", restore: true });
  1826. *
  1827. * - OR -
  1828. *
  1829. * var book = ePub({ restore: true });
  1830. * book.open("path/to/book.epub");
  1831. */
  1832. if( arguments[0] && typeof arguments[0] === 'object' ) {
  1833. options = arguments[0];
  1834. }
  1835. return new EPUBJS.Book(options);
  1836. };
  1837. _.extend(ePub, {
  1838. noConflict : function() {
  1839. root.ePub = previousEpub;
  1840. return this;
  1841. }
  1842. });
  1843. //exports to multiple environments
  1844. if (typeof define === 'function' && define.amd)
  1845. //AMD
  1846. define(function(){ return ePub; });
  1847. else if (typeof module != "undefined" && module.exports)
  1848. //Node
  1849. module.exports = ePub;
  1850. })(window);
  1851. EPUBJS.Book = function(options){
  1852. var book = this;
  1853. this.settings = _.defaults(options || {}, {
  1854. bookPath : null,
  1855. bookKey : null,
  1856. packageUrl : null,
  1857. storage: false, //-- true (auto) or false (none) | override: 'ram', 'websqldatabase', 'indexeddb', 'filesystem'
  1858. fromStorage : false,
  1859. saved : false,
  1860. online : true,
  1861. contained : false,
  1862. width : null,
  1863. height: null,
  1864. layoutOveride : null, // Default: { spread: 'reflowable', layout: 'auto', orientation: 'auto'}
  1865. orientation : null,
  1866. minSpreadWidth: 800, //-- overridden by spread: none (never) / both (always)
  1867. gap: "auto", //-- "auto" or int
  1868. version: 1,
  1869. restore: false,
  1870. reload : false,
  1871. goto : false,
  1872. styles : {},
  1873. headTags : {},
  1874. withCredentials: false,
  1875. render_method: "Iframe"
  1876. });
  1877. this.settings.EPUBJSVERSION = EPUBJS.VERSION;
  1878. this.spinePos = 0;
  1879. this.stored = false;
  1880. //-- All Book events for listening
  1881. /*
  1882. book:ready
  1883. book:stored
  1884. book:online
  1885. book:offline
  1886. book:pageChanged
  1887. book:loadFailed
  1888. book:loadChapterFailed
  1889. */
  1890. //-- Adds Hook methods to the Book prototype
  1891. // Hooks will all return before triggering the callback.
  1892. // EPUBJS.Hooks.mixin(this);
  1893. //-- Get pre-registered hooks for events
  1894. // this.getHooks("beforeChapterDisplay");
  1895. this.online = this.settings.online || navigator.onLine;
  1896. this.networkListeners();
  1897. this.store = false; //-- False if not using storage;
  1898. //-- Determine storage method
  1899. //-- Override options: none | ram | websqldatabase | indexeddb | filesystem
  1900. if(this.settings.storage !== false){
  1901. this.storage = new fileStorage.storage(this.settings.storage);
  1902. }
  1903. this.ready = {
  1904. manifest: new RSVP.defer(),
  1905. spine: new RSVP.defer(),
  1906. metadata: new RSVP.defer(),
  1907. cover: new RSVP.defer(),
  1908. toc: new RSVP.defer(),
  1909. pageList: new RSVP.defer()
  1910. };
  1911. this.readyPromises = [
  1912. this.ready.manifest.promise,
  1913. this.ready.spine.promise,
  1914. this.ready.metadata.promise,
  1915. this.ready.cover.promise,
  1916. this.ready.toc.promise
  1917. ];
  1918. this.pageList = [];
  1919. this.pagination = new EPUBJS.Pagination();
  1920. this.pageListReady = this.ready.pageList.promise;
  1921. this.ready.all = RSVP.all(this.readyPromises);
  1922. this.ready.all.then(this._ready.bind(this));
  1923. // Queue for methods used before rendering
  1924. this.isRendered = false;
  1925. this._q = EPUBJS.core.queue(this);
  1926. // Queue for rendering
  1927. this._rendering = false;
  1928. this._displayQ = EPUBJS.core.queue(this);
  1929. // Queue for going to another location
  1930. this._moving = false;
  1931. this._gotoQ = EPUBJS.core.queue(this);
  1932. /**
  1933. * Creates a new renderer.
  1934. * The renderer will handle displaying the content using the method provided in the settings
  1935. */
  1936. this.renderer = new EPUBJS.Renderer(this.settings.render_method);
  1937. //-- Set the width at which to switch from spreads to single pages
  1938. this.renderer.setMinSpreadWidth(this.settings.minSpreadWidth);
  1939. this.renderer.setGap(this.settings.gap);
  1940. //-- Pass through the renderer events
  1941. this.listenToRenderer(this.renderer);
  1942. this.defer_opened = new RSVP.defer();
  1943. this.opened = this.defer_opened.promise;
  1944. // BookUrl is optional, but if present start loading process
  1945. if(typeof this.settings.bookPath === 'string') {
  1946. this.open(this.settings.bookPath, this.settings.reload);
  1947. }
  1948. window.addEventListener("beforeunload", this.unload.bind(this), false);
  1949. //-- Listen for these promises:
  1950. //-- book.opened.then()
  1951. //-- book.rendered.then()
  1952. };
  1953. //-- Check bookUrl and start parsing book Assets or load them from storage
  1954. EPUBJS.Book.prototype.open = function(bookPath, forceReload){
  1955. var book = this,
  1956. epubpackage,
  1957. opened = new RSVP.defer();
  1958. this.settings.bookPath = bookPath;
  1959. //-- Get a absolute URL from the book path
  1960. this.bookUrl = this.urlFrom(bookPath);
  1961. if(this.settings.contained || this.isContained(bookPath)){
  1962. this.settings.contained = this.contained = true;
  1963. this.bookUrl = '';
  1964. epubpackage = this.unarchive(bookPath).
  1965. then(function(){
  1966. return book.loadPackage();
  1967. });
  1968. } else {
  1969. epubpackage = this.loadPackage();
  1970. }
  1971. if(this.settings.restore && !forceReload && localStorage){
  1972. //-- Will load previous package json, or re-unpack if error
  1973. epubpackage.then(function(packageXml) {
  1974. var identifier = book.packageIdentifier(packageXml);
  1975. var restored = book.restore(identifier);
  1976. if(!restored) {
  1977. book.unpack(packageXml);
  1978. }
  1979. opened.resolve();
  1980. book.defer_opened.resolve();
  1981. });
  1982. }else{
  1983. //-- Get package information from epub opf
  1984. epubpackage.then(function(packageXml) {
  1985. book.unpack(packageXml);
  1986. opened.resolve();
  1987. book.defer_opened.resolve();
  1988. });
  1989. }
  1990. //-- If there is network connection, store the books contents
  1991. if(this.online && this.settings.storage && !this.settings.contained){
  1992. if(!this.settings.stored) opened.then(book.storeOffline());
  1993. }
  1994. this._registerReplacements(this.renderer);
  1995. return opened.promise;
  1996. };
  1997. EPUBJS.Book.prototype.loadPackage = function(_containerPath){
  1998. var book = this,
  1999. parse = new EPUBJS.Parser(),
  2000. containerPath = _containerPath || "META-INF/container.xml",
  2001. containerXml,
  2002. packageXml;
  2003. if(!this.settings.packageUrl) { //-- provide the packageUrl to skip this step
  2004. packageXml = book.loadXml(book.bookUrl + containerPath).
  2005. then(function(containerXml){
  2006. return parse.container(containerXml); // Container has path to content
  2007. }).
  2008. then(function(paths){
  2009. book.settings.contentsPath = book.bookUrl + paths.basePath;
  2010. book.settings.packageUrl = book.bookUrl + paths.packagePath;
  2011. book.settings.encoding = paths.encoding;
  2012. return book.loadXml(book.settings.packageUrl); // Containes manifest, spine and metadata
  2013. });
  2014. } else {
  2015. packageXml = book.loadXml(book.settings.packageUrl);
  2016. }
  2017. packageXml.catch(function(error) {
  2018. // handle errors in either of the two requests
  2019. console.error("Could not load book at: "+ containerPath);
  2020. book.trigger("book:loadFailed", containerPath);
  2021. });
  2022. return packageXml;
  2023. };
  2024. EPUBJS.Book.prototype.packageIdentifier = function(packageXml){
  2025. var book = this,
  2026. parse = new EPUBJS.Parser();
  2027. return parse.identifier(packageXml);
  2028. };
  2029. EPUBJS.Book.prototype.unpack = function(packageXml){
  2030. var book = this,
  2031. parse = new EPUBJS.Parser();
  2032. book.contents = parse.packageContents(packageXml, book.settings.contentsPath); // Extract info from contents
  2033. book.manifest = book.contents.manifest;
  2034. book.spine = book.contents.spine;
  2035. book.spineIndexByURL = book.contents.spineIndexByURL;
  2036. book.metadata = book.contents.metadata;
  2037. if(!book.settings.bookKey) {
  2038. book.settings.bookKey = book.generateBookKey(book.metadata.identifier);
  2039. }
  2040. //-- Set Globbal Layout setting based on metadata
  2041. book.globalLayoutProperties = book.parseLayoutProperties(book.metadata);
  2042. book.cover = book.contents.cover = book.settings.contentsPath + book.contents.coverPath;
  2043. book.spineNodeIndex = book.contents.spineNodeIndex;
  2044. book.ready.manifest.resolve(book.contents.manifest);
  2045. book.ready.spine.resolve(book.contents.spine);
  2046. book.ready.metadata.resolve(book.contents.metadata);
  2047. book.ready.cover.resolve(book.contents.cover);
  2048. //-- Load the TOC, optional; either the EPUB3 XHTML Navigation file or the EPUB2 NCX file
  2049. if(book.contents.navPath) {
  2050. book.settings.navUrl = book.settings.contentsPath + book.contents.navPath;
  2051. book.loadXml(book.settings.navUrl).
  2052. then(function(navHtml){
  2053. return parse.nav(navHtml, book.spineIndexByURL, book.spine); // Grab Table of Contents
  2054. }).then(function(toc){
  2055. book.toc = book.contents.toc = toc;
  2056. book.ready.toc.resolve(book.contents.toc);
  2057. }, function(error) {
  2058. book.ready.toc.resolve(false);
  2059. });
  2060. // Load the optional pageList
  2061. book.loadXml(book.settings.navUrl).
  2062. then(function(navHtml){
  2063. return parse.pageList(navHtml, book.spineIndexByURL, book.spine);
  2064. }).then(function(pageList){
  2065. var epubcfi = new EPUBJS.EpubCFI();
  2066. var wait = 0; // need to generate a cfi
  2067. // No pageList found
  2068. if(pageList.length === 0) {
  2069. return;
  2070. }
  2071. book.pageList = book.contents.pageList = pageList;
  2072. // Replace HREFs with CFI
  2073. book.pageList.forEach(function(pg){
  2074. if(!pg.cfi) {
  2075. wait += 1;
  2076. epubcfi.generateCfiFromHref(pg.href, book).then(function(cfi){
  2077. pg.cfi = cfi;
  2078. pg.packageUrl = book.settings.packageUrl;
  2079. wait -= 1;
  2080. if(wait === 0) {
  2081. book.pagination.process(book.pageList);
  2082. book.ready.pageList.resolve(book.pageList);
  2083. }
  2084. });
  2085. }
  2086. });
  2087. if(!wait) {
  2088. book.pagination.process(book.pageList);
  2089. book.ready.pageList.resolve(book.pageList);
  2090. }
  2091. }, function(error) {
  2092. book.ready.pageList.resolve([]);
  2093. });
  2094. } else if(book.contents.tocPath) {
  2095. book.settings.tocUrl = book.settings.contentsPath + book.contents.tocPath;
  2096. book.loadXml(book.settings.tocUrl).
  2097. then(function(tocXml){
  2098. return parse.toc(tocXml, book.spineIndexByURL, book.spine); // Grab Table of Contents
  2099. }).then(function(toc){
  2100. book.toc = book.contents.toc = toc;
  2101. book.ready.toc.resolve(book.contents.toc);
  2102. }, function(error) {
  2103. book.ready.toc.resolve(false);
  2104. });
  2105. } else {
  2106. book.ready.toc.resolve(false);
  2107. }
  2108. };
  2109. EPUBJS.Book.prototype.createHiddenRender = function(renderer, _width, _height) {
  2110. var box = this.element.getBoundingClientRect();
  2111. var width = _width || this.settings.width || box.width;
  2112. var height = _height || this.settings.height || box.height;
  2113. var hiddenContainer;
  2114. var hiddenEl;
  2115. renderer.setMinSpreadWidth(this.settings.minSpreadWidth);
  2116. renderer.setGap(this.settings.gap);
  2117. this._registerReplacements(renderer);
  2118. if(this.settings.forceSingle) {
  2119. renderer.forceSingle(true);
  2120. }
  2121. hiddenContainer = document.createElement("div");
  2122. hiddenContainer.style.visibility = "hidden";
  2123. hiddenContainer.style.overflow = "hidden";
  2124. hiddenContainer.style.width = "0";
  2125. hiddenContainer.style.height = "0";
  2126. this.element.appendChild(hiddenContainer);
  2127. hiddenEl = document.createElement("div");
  2128. hiddenEl.style.visibility = "hidden";
  2129. hiddenEl.style.overflow = "hidden";
  2130. hiddenEl.style.width = width + "px";//"0";
  2131. hiddenEl.style.height = height +"px"; //"0";
  2132. hiddenContainer.appendChild(hiddenEl);
  2133. renderer.initialize(hiddenEl);
  2134. return hiddenContainer;
  2135. };
  2136. // Generates the pageList array by loading every chapter and paging through them
  2137. EPUBJS.Book.prototype.generatePageList = function(width, height){
  2138. var pageList = [];
  2139. var pager = new EPUBJS.Renderer(this.settings.render_method, false); //hidden
  2140. var hiddenContainer = this.createHiddenRender(pager, width, height);
  2141. var deferred = new RSVP.defer();
  2142. var spinePos = -1;
  2143. var spineLength = this.spine.length;
  2144. var totalPages = 0;
  2145. var currentPage = 0;
  2146. var nextChapter = function(deferred){
  2147. var chapter;
  2148. var next = spinePos + 1;
  2149. var done = deferred || new RSVP.defer();
  2150. var loaded;
  2151. if(next >= spineLength) {
  2152. done.resolve();
  2153. } else {
  2154. spinePos = next;
  2155. chapter = new EPUBJS.Chapter(this.spine[spinePos], this.store);
  2156. pager.displayChapter(chapter, this.globalLayoutProperties).then(function(chap){
  2157. pager.pageMap.forEach(function(item){
  2158. currentPage += 1;
  2159. pageList.push({
  2160. "cfi" : item.start,
  2161. "page" : currentPage
  2162. });
  2163. });
  2164. if(pager.pageMap.length % 2 > 0 &&
  2165. pager.spreads) {
  2166. currentPage += 1; // Handle Spreads
  2167. pageList.push({
  2168. "cfi" : pager.pageMap[pager.pageMap.length - 1].end,
  2169. "page" : currentPage
  2170. });
  2171. }
  2172. // Load up the next chapter
  2173. setTimeout(function(){
  2174. nextChapter(done);
  2175. }, 1);
  2176. });
  2177. }
  2178. return done.promise;
  2179. }.bind(this);
  2180. var finished = nextChapter().then(function(){
  2181. pager.remove();
  2182. this.element.removeChild(hiddenContainer);
  2183. deferred.resolve(pageList);
  2184. }.bind(this));
  2185. return deferred.promise;
  2186. };
  2187. // Render out entire book and generate the pagination
  2188. // Width and Height are optional and will default to the current dimensions
  2189. EPUBJS.Book.prototype.generatePagination = function(width, height) {
  2190. var book = this;
  2191. var defered = new RSVP.defer();
  2192. this.ready.spine.promise.then(function(){
  2193. book.generatePageList(width, height).then(function(pageList){
  2194. book.pageList = book.contents.pageList = pageList;
  2195. book.pagination.process(pageList);
  2196. book.ready.pageList.resolve(book.pageList);
  2197. defered.resolve(book.pageList);
  2198. });
  2199. });
  2200. return defered.promise;
  2201. };
  2202. // Process the pagination from a JSON array containing the pagelist
  2203. EPUBJS.Book.prototype.loadPagination = function(pagelistJSON) {
  2204. var pageList = JSON.parse(pagelistJSON);
  2205. if(pageList && pageList.length) {
  2206. this.pageList = pageList;
  2207. this.pagination.process(this.pageList);
  2208. this.ready.pageList.resolve(this.pageList);
  2209. }
  2210. return this.pageList;
  2211. };
  2212. EPUBJS.Book.prototype.getPageList = function() {
  2213. return this.ready.pageList.promise;
  2214. };
  2215. EPUBJS.Book.prototype.getMetadata = function() {
  2216. return this.ready.metadata.promise;
  2217. };
  2218. EPUBJS.Book.prototype.getToc = function() {
  2219. return this.ready.toc.promise;
  2220. };
  2221. /* Private Helpers */
  2222. //-- Listeners for browser events
  2223. EPUBJS.Book.prototype.networkListeners = function(){
  2224. var book = this;
  2225. window.addEventListener("offline", function(e) {
  2226. book.online = false;
  2227. book.trigger("book:offline");
  2228. }, false);
  2229. window.addEventListener("online", function(e) {
  2230. book.online = true;
  2231. book.trigger("book:online");
  2232. }, false);
  2233. };
  2234. // Listen to all events the renderer triggers and pass them as book events
  2235. EPUBJS.Book.prototype.listenToRenderer = function(renderer){
  2236. var book = this;
  2237. renderer.Events.forEach(function(eventName){
  2238. renderer.on(eventName, function(e){
  2239. book.trigger(eventName, e);
  2240. });
  2241. });
  2242. renderer.on("renderer:visibleRangeChanged", function(range) {
  2243. var startPage, endPage, percent;
  2244. var pageRange = [];
  2245. if(this.pageList.length > 0) {
  2246. startPage = this.pagination.pageFromCfi(range.start);
  2247. percent = this.pagination.percentageFromPage(startPage);
  2248. pageRange.push(startPage);
  2249. if(range.end) {
  2250. endPage = this.pagination.pageFromCfi(range.end);
  2251. //if(startPage != endPage) {
  2252. pageRange.push(endPage);
  2253. //}
  2254. }
  2255. this.trigger("book:pageChanged", {
  2256. "anchorPage": startPage,
  2257. "percentage": percent,
  2258. "pageRange" : pageRange
  2259. });
  2260. // TODO: Add event for first and last page.
  2261. // (though last is going to be hard, since it could be several reflowed pages long)
  2262. }
  2263. }.bind(this));
  2264. renderer.on("render:loaded", this.loadChange.bind(this));
  2265. };
  2266. // Listens for load events from the Renderer and checks against the current chapter
  2267. // Prevents the Render from loading a different chapter when back button is pressed
  2268. EPUBJS.Book.prototype.loadChange = function(url){
  2269. var uri = EPUBJS.core.uri(url);
  2270. var chapter;
  2271. if(this.currentChapter) {
  2272. chapter = EPUBJS.core.uri(this.currentChapter.absolute);
  2273. }
  2274. if(!this._rendering && this.currentChapter && uri.path != chapter.path){
  2275. console.warn("Miss Match", uri.path, this.currentChapter.absolute);
  2276. this.goto(uri.filename);
  2277. }
  2278. };
  2279. EPUBJS.Book.prototype.unlistenToRenderer = function(renderer){
  2280. renderer.Events.forEach(function(eventName){
  2281. renderer.off(eventName);
  2282. } );
  2283. };
  2284. //-- Choose between a request from store or a request from network
  2285. EPUBJS.Book.prototype.loadXml = function(url){
  2286. if(this.settings.fromStorage) {
  2287. return this.storage.getXml(url, this.settings.encoding);
  2288. } else if(this.settings.contained) {
  2289. return this.zip.getXml(url, this.settings.encoding);
  2290. }else{
  2291. return EPUBJS.core.request(url, 'xml', this.settings.withCredentials);
  2292. }
  2293. };
  2294. //-- Turns a url into a absolute url
  2295. EPUBJS.Book.prototype.urlFrom = function(bookPath){
  2296. var uri = EPUBJS.core.uri(bookPath),
  2297. absolute = uri.protocol,
  2298. fromRoot = uri.path[0] == "/",
  2299. location = window.location,
  2300. //-- Get URL orgin, try for native or combine
  2301. origin = location.origin || location.protocol + "//" + location.host,
  2302. baseTag = document.getElementsByTagName('base'),
  2303. base;
  2304. //-- Check is Base tag is set
  2305. if(baseTag.length) {
  2306. base = baseTag[0].href;
  2307. }
  2308. //-- 1. Check if url is absolute
  2309. if(uri.protocol){
  2310. return uri.origin + uri.path;
  2311. }
  2312. //-- 2. Check if url starts with /, add base url
  2313. if(!absolute && fromRoot){
  2314. return (base || origin) + uri.path;
  2315. }
  2316. //-- 3. Or find full path to url and add that
  2317. if(!absolute && !fromRoot){
  2318. return EPUBJS.core.resolveUrl(base || location.pathname, uri.path);
  2319. }
  2320. };
  2321. EPUBJS.Book.prototype.unarchive = function(bookPath){
  2322. var book = this,
  2323. unarchived;
  2324. //-- Must use storage
  2325. // if(this.settings.storage == false ){
  2326. // this.settings.storage = true;
  2327. // this.storage = new fileStorage.storage();
  2328. // }
  2329. this.zip = new EPUBJS.Unarchiver();
  2330. this.store = this.zip; // Use zip storaged in ram
  2331. return this.zip.openZip(bookPath);
  2332. };
  2333. //-- Checks if url has a .epub or .zip extension
  2334. EPUBJS.Book.prototype.isContained = function(bookUrl){
  2335. var uri = EPUBJS.core.uri(bookUrl);
  2336. if(uri.extension && (uri.extension == "epub" || uri.extension == "zip")){
  2337. return true;
  2338. }
  2339. return false;
  2340. };
  2341. //-- Checks if the book can be retrieved from localStorage
  2342. EPUBJS.Book.prototype.isSaved = function(bookKey) {
  2343. var storedSettings;
  2344. if(!localStorage) {
  2345. return false;
  2346. }
  2347. storedSettings = localStorage.getItem(bookKey);
  2348. if( !localStorage ||
  2349. storedSettings === null) {
  2350. return false;
  2351. } else {
  2352. return true;
  2353. }
  2354. };
  2355. // Generates the Book Key using the identifer in the manifest or other string provided
  2356. EPUBJS.Book.prototype.generateBookKey = function(identifier){
  2357. return "epubjs:" + EPUBJS.VERSION + ":" + window.location.host + ":" + identifier;
  2358. };
  2359. EPUBJS.Book.prototype.saveContents = function(){
  2360. if(!localStorage) {
  2361. return false;
  2362. }
  2363. localStorage.setItem(this.settings.bookKey, JSON.stringify(this.contents));
  2364. };
  2365. EPUBJS.Book.prototype.removeSavedContents = function() {
  2366. if(!localStorage) {
  2367. return false;
  2368. }
  2369. localStorage.removeItem(this.settings.bookKey);
  2370. };
  2371. //-- Takes a string or a element
  2372. EPUBJS.Book.prototype.renderTo = function(elem){
  2373. var book = this,
  2374. rendered;
  2375. if(_.isElement(elem)) {
  2376. this.element = elem;
  2377. } else if (typeof elem == "string") {
  2378. this.element = EPUBJS.core.getEl(elem);
  2379. } else {
  2380. console.error("Not an Element");
  2381. return;
  2382. }
  2383. rendered = this.opened.
  2384. then(function(){
  2385. // book.render = new EPUBJS.Renderer[this.settings.renderer](book);
  2386. book.renderer.initialize(book.element, book.settings.width, book.settings.height);
  2387. book._rendered();
  2388. return book.startDisplay();
  2389. });
  2390. // rendered.then(null, function(error) { console.error(error); });
  2391. return rendered;
  2392. };
  2393. EPUBJS.Book.prototype.startDisplay = function(){
  2394. var display;
  2395. if(this.settings.goto) {
  2396. display = this.goto(this.settings.goto);
  2397. }else if(this.settings.previousLocationCfi) {
  2398. display = this.gotoCfi(this.settings.previousLocationCfi);
  2399. }else{
  2400. display = this.displayChapter(this.spinePos);
  2401. }
  2402. return display;
  2403. };
  2404. EPUBJS.Book.prototype.restore = function(identifier){
  2405. var book = this,
  2406. fetch = ['manifest', 'spine', 'metadata', 'cover', 'toc', 'spineNodeIndex', 'spineIndexByURL', 'globalLayoutProperties'],
  2407. reject = false,
  2408. bookKey = this.generateBookKey(identifier),
  2409. fromStore = localStorage.getItem(bookKey),
  2410. len = fetch.length,
  2411. i;
  2412. if(this.settings.clearSaved) reject = true;
  2413. if(!reject && fromStore != 'undefined' && fromStore !== null){
  2414. book.contents = JSON.parse(fromStore);
  2415. for(i = 0; i < len; i++) {
  2416. var item = fetch[i];
  2417. if(!book.contents[item]) {
  2418. reject = true;
  2419. break;
  2420. }
  2421. book[item] = book.contents[item];
  2422. }
  2423. }
  2424. if(reject || !fromStore || !this.contents || !this.settings.contentsPath){
  2425. return false;
  2426. }else{
  2427. this.settings.bookKey = bookKey;
  2428. this.ready.manifest.resolve(this.manifest);
  2429. this.ready.spine.resolve(this.spine);
  2430. this.ready.metadata.resolve(this.metadata);
  2431. this.ready.cover.resolve(this.cover);
  2432. this.ready.toc.resolve(this.toc);
  2433. return true;
  2434. }
  2435. };
  2436. EPUBJS.Book.prototype.displayChapter = function(chap, end, deferred){
  2437. var book = this,
  2438. render,
  2439. cfi,
  2440. pos,
  2441. store,
  2442. defer = deferred || new RSVP.defer();
  2443. var chapter;
  2444. if(!this.isRendered) {
  2445. this._q.enqueue("displayChapter", arguments);
  2446. // Reject for now. TODO: pass promise to queue
  2447. defer.reject({
  2448. message : "Rendering",
  2449. stack : new Error().stack
  2450. });
  2451. return defer.promise;
  2452. }
  2453. if(this._rendering || this._rendering) {
  2454. // Pass along the current defer
  2455. this._displayQ.enqueue("displayChapter", [chap, end, defer]);
  2456. return defer.promise;
  2457. }
  2458. if(_.isNumber(chap)){
  2459. pos = chap;
  2460. }else{
  2461. cfi = new EPUBJS.EpubCFI(chap);
  2462. pos = cfi.spinePos;
  2463. }
  2464. if(pos < 0 || pos >= this.spine.length){
  2465. console.warn("Not A Valid Location");
  2466. pos = 0;
  2467. end = false;
  2468. cfi = false;
  2469. }
  2470. //-- Create a new chapter
  2471. chapter = new EPUBJS.Chapter(this.spine[pos], this.store);
  2472. this._rendering = true;
  2473. render = book.renderer.displayChapter(chapter, this.globalLayoutProperties);
  2474. if(cfi) {
  2475. book.renderer.gotoCfi(cfi);
  2476. } else if(end) {
  2477. book.renderer.lastPage();
  2478. }
  2479. //-- Success, Clear render queue
  2480. render.then(function(rendered){
  2481. // var inwait;
  2482. //-- Set the book's spine position
  2483. book.spinePos = pos;
  2484. defer.resolve(book.renderer);
  2485. if(!book.settings.fromStorage &&
  2486. !book.settings.contained) {
  2487. book.preloadNextChapter();
  2488. }
  2489. book.currentChapter = chapter;
  2490. book._rendering = false;
  2491. book._displayQ.dequeue();
  2492. if(book._displayQ.length() === 0) {
  2493. book._gotoQ.dequeue();
  2494. }
  2495. }, function(error) {
  2496. // handle errors in either of the two requests
  2497. console.error("Could not load Chapter: "+ chapter.absolute);
  2498. book.trigger("book:chapterLoadFailed", chapter.absolute);
  2499. book._rendering = false;
  2500. defer.reject(error);
  2501. });
  2502. return defer.promise;
  2503. };
  2504. EPUBJS.Book.prototype.nextPage = function(){
  2505. var next;
  2506. if(!this.isRendered) return this._q.enqueue("nextPage", arguments);
  2507. next = this.renderer.nextPage();
  2508. if(!next){
  2509. return this.nextChapter();
  2510. }
  2511. };
  2512. EPUBJS.Book.prototype.prevPage = function() {
  2513. var prev;
  2514. if(!this.isRendered) return this._q.enqueue("prevPage", arguments);
  2515. prev = this.renderer.prevPage();
  2516. if(!prev){
  2517. return this.prevChapter();
  2518. }
  2519. };
  2520. EPUBJS.Book.prototype.nextChapter = function() {
  2521. var next;
  2522. if (this.spinePos < this.spine.length - 1) {
  2523. next = this.spinePos + 1;
  2524. while (this.spine[next] && this.spine[next].linear && this.spine[next].linear == 'no') {
  2525. next++;
  2526. }
  2527. if (next < this.spine.length - 1) {
  2528. return this.displayChapter(next);
  2529. } else {
  2530. this.trigger("book:atEnd");
  2531. }
  2532. } else {
  2533. this.trigger("book:atEnd");
  2534. }
  2535. };
  2536. EPUBJS.Book.prototype.prevChapter = function() {
  2537. var prev;
  2538. if (this.spinePos > 0) {
  2539. prev = this.spinePos - 1;
  2540. while (this.spine[prev] && this.spine[prev].linear && this.spine[prev].linear == 'no') {
  2541. prev--;
  2542. }
  2543. if (prev >= 0) {
  2544. return this.displayChapter(prev, true);
  2545. } else {
  2546. this.trigger("book:atStart");
  2547. }
  2548. } else {
  2549. this.trigger("book:atStart");
  2550. }
  2551. };
  2552. EPUBJS.Book.prototype.getCurrentLocationCfi = function() {
  2553. if(!this.isRendered) return false;
  2554. return this.renderer.currentLocationCfi;
  2555. };
  2556. EPUBJS.Book.prototype.goto = function(target){
  2557. if(target.indexOf("epubcfi(") === 0) {
  2558. return this.gotoCfi(target);
  2559. } else if(target.indexOf("%") === target.length-1) {
  2560. return this.gotoPercentage(parseInt(target.substring(0, target.length-1))/100);
  2561. } else if(typeof target === "number" || isNaN(target) === false){
  2562. return this.gotoPage(target);
  2563. } else {
  2564. return this.gotoHref(target);
  2565. }
  2566. };
  2567. EPUBJS.Book.prototype.gotoCfi = function(cfiString, defer){
  2568. var cfi,
  2569. spinePos,
  2570. spineItem,
  2571. rendered,
  2572. deferred = defer || new RSVP.defer();
  2573. if(!this.isRendered) {
  2574. console.warn("Not yet Rendered");
  2575. this.settings.previousLocationCfi = cfiString;
  2576. return false;
  2577. }
  2578. // Currently going to a chapter
  2579. if(this._moving || this._rendering) {
  2580. console.warn("Renderer is moving");
  2581. this._gotoQ.enqueue("gotoCfi", [cfiString, deferred]);
  2582. return false;
  2583. }
  2584. cfi = new EPUBJS.EpubCFI(cfiString);
  2585. spinePos = cfi.spinePos;
  2586. if(spinePos == -1) {
  2587. return false;
  2588. }
  2589. spineItem = this.spine[spinePos];
  2590. promise = deferred.promise;
  2591. this._moving = true;
  2592. //-- If same chapter only stay on current chapter
  2593. if(this.currentChapter && this.spinePos === spinePos){
  2594. this.renderer.gotoCfi(cfi);
  2595. this._moving = false;
  2596. deferred.resolve(this.renderer.currentLocationCfi);
  2597. } else {
  2598. if(!spineItem || spinePos == -1) {
  2599. spinePos = 0;
  2600. spineItem = this.spine[spinePos];
  2601. }
  2602. this.currentChapter = new EPUBJS.Chapter(spineItem, this.store);
  2603. if(this.currentChapter) {
  2604. this.spinePos = spinePos;
  2605. render = this.renderer.displayChapter(this.currentChapter, this.globalLayoutProperties);
  2606. this.renderer.gotoCfi(cfi);
  2607. render.then(function(rendered){
  2608. this._moving = false;
  2609. deferred.resolve(rendered.currentLocationCfi);
  2610. }.bind(this));
  2611. }
  2612. }
  2613. promise.then(function(){
  2614. this._gotoQ.dequeue();
  2615. }.bind(this));
  2616. return promise;
  2617. };
  2618. EPUBJS.Book.prototype.gotoHref = function(url, defer){
  2619. var split, chapter, section, relativeURL, spinePos;
  2620. var deferred = defer || new RSVP.defer();
  2621. if(!this.isRendered) {
  2622. this.settings.goto = url;
  2623. return false;
  2624. }
  2625. // Currently going to a chapter
  2626. if(this._moving || this._rendering) {
  2627. this._gotoQ.enqueue("gotoHref", [url, deferred]);
  2628. return false;
  2629. }
  2630. split = url.split("#");
  2631. chapter = split[0];
  2632. section = split[1] || false;
  2633. // absoluteURL = (chapter.search("://") === -1) ? (this.settings.contentsPath + chapter) : chapter;
  2634. relativeURL = chapter.replace(this.settings.contentsPath, '');
  2635. spinePos = this.spineIndexByURL[relativeURL];
  2636. //-- If link fragment only stay on current chapter
  2637. if(!chapter){
  2638. spinePos = this.currentChapter ? this.currentChapter.spinePos : 0;
  2639. }
  2640. //-- Check that URL is present in the index, or stop
  2641. if(typeof(spinePos) != "number") return false;
  2642. if(!this.currentChapter || spinePos != this.currentChapter.spinePos){
  2643. //-- Load new chapter if different than current
  2644. return this.displayChapter(spinePos).then(function(){
  2645. if(section){
  2646. this.renderer.section(section);
  2647. }
  2648. deferred.resolve(this.renderer.currentLocationCfi);
  2649. }.bind(this));
  2650. }else{
  2651. //-- Goto section
  2652. if(section) {
  2653. this.renderer.section(section);
  2654. } else {
  2655. // Or jump to the start
  2656. this.renderer.firstPage();
  2657. }
  2658. deferred.resolve(this.renderer.currentLocationCfi);
  2659. }
  2660. deferred.promise.then(function(){
  2661. this._gotoQ.dequeue();
  2662. }.bind(this));
  2663. return deferred.promise;
  2664. };
  2665. EPUBJS.Book.prototype.gotoPage = function(pg){
  2666. var cfi = this.pagination.cfiFromPage(pg);
  2667. return this.gotoCfi(cfi);
  2668. };
  2669. EPUBJS.Book.prototype.gotoPercentage = function(percent){
  2670. var pg = this.pagination.pageFromPercentage(percent);
  2671. return this.gotoPage(pg);
  2672. };
  2673. EPUBJS.Book.prototype.preloadNextChapter = function() {
  2674. var next;
  2675. var chap = this.spinePos + 1;
  2676. if(chap >= this.spine.length){
  2677. return false;
  2678. }
  2679. next = new EPUBJS.Chapter(this.spine[chap]);
  2680. if(next) {
  2681. EPUBJS.core.request(next.absolute);
  2682. }
  2683. };
  2684. EPUBJS.Book.prototype.storeOffline = function() {
  2685. var book = this,
  2686. assets = _.values(this.manifest);
  2687. //-- Creates a queue of all items to load
  2688. return EPUBJS.storage.batch(assets).
  2689. then(function(){
  2690. book.settings.stored = true;
  2691. book.trigger("book:stored");
  2692. });
  2693. };
  2694. EPUBJS.Book.prototype.availableOffline = function() {
  2695. return this.settings.stored > 0 ? true : false;
  2696. };
  2697. /*
  2698. EPUBJS.Book.prototype.fromStorage = function(stored) {
  2699. if(this.contained) return;
  2700. if(!stored){
  2701. this.online = true;
  2702. this.tell("book:online");
  2703. }else{
  2704. if(!this.availableOffline){
  2705. //-- If book hasn't been cached yet, store offline
  2706. this.storeOffline(function(){
  2707. this.online = false;
  2708. this.tell("book:offline");
  2709. }.bind(this));
  2710. }else{
  2711. this.online = false;
  2712. this.tell("book:offline");
  2713. }
  2714. }
  2715. }
  2716. */
  2717. EPUBJS.Book.prototype.setStyle = function(style, val, prefixed) {
  2718. var noreflow = ["color", "background", "background-color"];
  2719. if(!this.isRendered) return this._q.enqueue("setStyle", arguments);
  2720. this.settings.styles[style] = val;
  2721. this.renderer.setStyle(style, val, prefixed);
  2722. if(noreflow.indexOf(style) === -1) {
  2723. clearTimeout(this.reformatTimeout);
  2724. this.reformatTimeout = setTimeout(function(){
  2725. this.renderer.reformat();
  2726. }.bind(this), 10);
  2727. }
  2728. };
  2729. EPUBJS.Book.prototype.removeStyle = function(style) {
  2730. if(!this.isRendered) return this._q.enqueue("removeStyle", arguments);
  2731. this.renderer.removeStyle(style);
  2732. this.renderer.reformat();
  2733. delete this.settings.styles[style];
  2734. };
  2735. EPUBJS.Book.prototype.addHeadTag = function(tag, attrs) {
  2736. if(!this.isRendered) return this._q.enqueue("addHeadTag", arguments);
  2737. this.settings.headTags[tag] = attrs;
  2738. };
  2739. EPUBJS.Book.prototype.useSpreads = function(use) {
  2740. console.warn("useSpreads is deprecated, use forceSingle or set a layoutOveride instead");
  2741. if(use === false) {
  2742. this.forceSingle(true);
  2743. } else {
  2744. this.forceSingle(false);
  2745. }
  2746. };
  2747. EPUBJS.Book.prototype.forceSingle = function(use) {
  2748. this.renderer.forceSingle(use);
  2749. this.settings.forceSingle = use;
  2750. if(this.isRendered) {
  2751. this.renderer.reformat();
  2752. }
  2753. };
  2754. EPUBJS.Book.prototype.setMinSpreadWidth = function(width) {
  2755. this.settings.minSpreadWidth = width;
  2756. if(this.isRendered) {
  2757. this.renderer.setMinSpreadWidth(this.settings.minSpreadWidth);
  2758. this.renderer.reformat();
  2759. }
  2760. };
  2761. EPUBJS.Book.prototype.setGap = function(gap) {
  2762. this.settings.gap = gap;
  2763. if(this.isRendered) {
  2764. this.renderer.setGap(this.settings.gap);
  2765. this.renderer.reformat();
  2766. }
  2767. };
  2768. EPUBJS.Book.prototype.unload = function(){
  2769. if(this.settings.restore && localStorage) {
  2770. this.saveContents();
  2771. }
  2772. this.unlistenToRenderer(this.renderer);
  2773. this.trigger("book:unload");
  2774. };
  2775. EPUBJS.Book.prototype.destroy = function() {
  2776. window.removeEventListener("beforeunload", this.unload);
  2777. if(this.currentChapter) this.currentChapter.unload();
  2778. this.unload();
  2779. if(this.render) this.render.remove();
  2780. };
  2781. EPUBJS.Book.prototype._ready = function() {
  2782. this.trigger("book:ready");
  2783. };
  2784. EPUBJS.Book.prototype._rendered = function(err) {
  2785. var book = this;
  2786. this.isRendered = true;
  2787. this.trigger("book:rendered");
  2788. this._q.flush();
  2789. };
  2790. EPUBJS.Book.prototype.applyStyles = function(renderer, callback){
  2791. // if(!this.isRendered) return this._q.enqueue("applyStyles", arguments);
  2792. renderer.applyStyles(this.settings.styles);
  2793. callback();
  2794. };
  2795. EPUBJS.Book.prototype.applyHeadTags = function(renderer, callback){
  2796. // if(!this.isRendered) return this._q.enqueue("applyHeadTags", arguments);
  2797. renderer.applyHeadTags(this.settings.headTags);
  2798. callback();
  2799. };
  2800. EPUBJS.Book.prototype._registerReplacements = function(renderer){
  2801. renderer.registerHook("beforeChapterDisplay", this.applyStyles.bind(this, renderer), true);
  2802. renderer.registerHook("beforeChapterDisplay", this.applyHeadTags.bind(this, renderer), true);
  2803. renderer.registerHook("beforeChapterDisplay", EPUBJS.replace.hrefs.bind(this), true);
  2804. if(this._needsAssetReplacement()) {
  2805. renderer.registerHook("beforeChapterDisplay", [
  2806. EPUBJS.replace.head,
  2807. EPUBJS.replace.resources,
  2808. EPUBJS.replace.svg
  2809. ], true);
  2810. }
  2811. };
  2812. EPUBJS.Book.prototype._needsAssetReplacement = function(){
  2813. if(this.settings.fromStorage) {
  2814. //-- Filesystem api links are relative, so no need to replace them
  2815. if(this.storage.getStorageType() == "filesystem") {
  2816. return false;
  2817. }
  2818. return true;
  2819. } else if(this.settings.contained) {
  2820. return true;
  2821. } else {
  2822. return false;
  2823. }
  2824. };
  2825. //-- http://www.idpf.org/epub/fxl/
  2826. EPUBJS.Book.prototype.parseLayoutProperties = function(metadata){
  2827. var layout = (this.layoutOveride && this.layoutOveride.layout) || metadata.layout || "reflowable";
  2828. var spread = (this.layoutOveride && this.layoutOveride.spread) || metadata.spread || "auto";
  2829. var orientation = (this.layoutOveride && this.layoutOveride.orientation) || metadata.orientation || "auto";
  2830. return {
  2831. layout : layout,
  2832. spread : spread,
  2833. orientation : orientation
  2834. };
  2835. };
  2836. //-- Enable binding events to book
  2837. RSVP.EventTarget.mixin(EPUBJS.Book.prototype);
  2838. //-- Handle RSVP Errors
  2839. RSVP.on('error', function(event) {
  2840. //console.error(event, event.detail);
  2841. });
  2842. RSVP.configure('instrument', true); //-- true | will logging out all RSVP rejections
  2843. // RSVP.on('created', listener);
  2844. // RSVP.on('chained', listener);
  2845. // RSVP.on('fulfilled', listener);
  2846. RSVP.on('rejected', function(event){
  2847. console.error(event.detail.message, event.detail.stack);
  2848. });
  2849. EPUBJS.Chapter = function(spineObject, store){
  2850. this.href = spineObject.href;
  2851. this.absolute = spineObject.url;
  2852. this.id = spineObject.id;
  2853. this.spinePos = spineObject.index;
  2854. this.cfiBase = spineObject.cfiBase;
  2855. this.properties = spineObject.properties;
  2856. this.manifestProperties = spineObject.manifestProperties;
  2857. this.linear = spineObject.linear;
  2858. this.pages = 1;
  2859. this.store = store;
  2860. this.epubcfi = new EPUBJS.EpubCFI();
  2861. };
  2862. EPUBJS.Chapter.prototype.contents = function(_store){
  2863. var store = _store || this.store;
  2864. // if(this.store && (!this.book.online || this.book.contained))
  2865. if(store){
  2866. return store.get(href);
  2867. }else{
  2868. return EPUBJS.core.request(href, 'xml');
  2869. }
  2870. };
  2871. EPUBJS.Chapter.prototype.url = function(_store){
  2872. var deferred = new RSVP.defer();
  2873. var store = _store || this.store;
  2874. var loaded;
  2875. var chapter = this;
  2876. var url;
  2877. if(store){
  2878. if(!this.tempUrl) {
  2879. store.getUrl(this.absolute).then(function(url){
  2880. chapter.tempUrl = url;
  2881. deferred.resolve(url);
  2882. });
  2883. } else {
  2884. url = this.tempUrl;
  2885. deferred.resolve(url);
  2886. }
  2887. }else{
  2888. url = this.absolute;
  2889. deferred.resolve(url);
  2890. }
  2891. /*
  2892. loaded = EPUBJS.core.request(url, 'xml', false);
  2893. loaded.then(function(contents){
  2894. chapter.contents = contents;
  2895. deferred.resolve(chapter.absolute);
  2896. }, function(error){
  2897. deferred.reject(error);
  2898. });
  2899. */
  2900. return deferred.promise;
  2901. };
  2902. EPUBJS.Chapter.prototype.setPages = function(num){
  2903. this.pages = num;
  2904. };
  2905. EPUBJS.Chapter.prototype.getPages = function(num){
  2906. return this.pages;
  2907. };
  2908. EPUBJS.Chapter.prototype.getID = function(){
  2909. return this.ID;
  2910. };
  2911. EPUBJS.Chapter.prototype.unload = function(store){
  2912. this.contents = null;
  2913. if(this.tempUrl && store) {
  2914. store.revokeUrl(this.tempUrl);
  2915. this.tempUrl = false;
  2916. }
  2917. };
  2918. EPUBJS.Chapter.prototype.cfiFromRange = function(_range) {
  2919. var range;
  2920. var startXpath, endXpath;
  2921. var startContainer, endContainer;
  2922. var cleanTextContent, cleanEndTextContent;
  2923. // Check for Contents
  2924. if(!this.contents) return;
  2925. startXpath = EPUBJS.core.getElementXPath(_range.startContainer);
  2926. // console.log(startContainer)
  2927. endXpath = EPUBJS.core.getElementXPath(_range.endContainer);
  2928. startContainer = this.contents.evaluate(startXpath, this.contents, EPUBJS.core.nsResolver, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue;
  2929. if(!_range.collapsed) {
  2930. endContainer = this.contents.evaluate(endXpath, this.contents, EPUBJS.core.nsResolver, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue;
  2931. }
  2932. range = this.contents.createRange();
  2933. // Find Exact Range in original document
  2934. if(startContainer) {
  2935. try {
  2936. range.setStart(startContainer, _range.startOffset);
  2937. if(!_range.collapsed && endContainer) {
  2938. range.setEnd(endContainer, _range.endOffset);
  2939. }
  2940. } catch (e) {
  2941. console.log("missed");
  2942. startContainer = false;
  2943. }
  2944. }
  2945. // Fuzzy Match
  2946. if(!startContainer) {
  2947. console.log("not found, try fuzzy match");
  2948. cleanStartTextContent = EPUBJS.core.cleanStringForXpath(_range.startContainer.textContent);
  2949. startXpath = "//text()[contains(.," + cleanStartTextContent + ")]";
  2950. startContainer = this.contents.evaluate(startXpath, this.contents, EPUBJS.core.nsResolver, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue;
  2951. if(startContainer){
  2952. // console.log("Found with Fuzzy");
  2953. range.setStart(startContainer, _range.startOffset);
  2954. if(!_range.collapsed) {
  2955. cleanEndTextContent = EPUBJS.core.cleanStringForXpath(_range.endContainer.textContent);
  2956. endXpath = "//text()[contains(.," + cleanEndTextContent + ")]";
  2957. endContainer = this.contents.evaluate(endXpath, this.contents, EPUBJS.core.nsResolver, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue;
  2958. if(endContainer) {
  2959. range.setEnd(endContainer, _range.endOffset);
  2960. }
  2961. }
  2962. }
  2963. }
  2964. // Generate the Cfi
  2965. return this.epubcfi.generateCfiFromRange(range, this.cfiBase);
  2966. };
  2967. var EPUBJS = EPUBJS || {};
  2968. EPUBJS.core = {};
  2969. //-- Get a element for an id
  2970. EPUBJS.core.getEl = function(elem) {
  2971. return document.getElementById(elem);
  2972. };
  2973. //-- Get all elements for a class
  2974. EPUBJS.core.getEls = function(classes) {
  2975. return document.getElementsByClassName(classes);
  2976. };
  2977. EPUBJS.core.request = function(url, type, withCredentials) {
  2978. var supportsURL = window.URL;
  2979. var BLOB_RESPONSE = supportsURL ? "blob" : "arraybuffer";
  2980. var deferred = new RSVP.defer();
  2981. var xhr = new XMLHttpRequest();
  2982. //-- Check from PDF.js:
  2983. // https://github.com/mozilla/pdf.js/blob/master/web/compatibility.js
  2984. var xhrPrototype = XMLHttpRequest.prototype;
  2985. if (!('overrideMimeType' in xhrPrototype)) {
  2986. // IE10 might have response, but not overrideMimeType
  2987. Object.defineProperty(xhrPrototype, 'overrideMimeType', {
  2988. value: function xmlHttpRequestOverrideMimeType(mimeType) {}
  2989. });
  2990. }
  2991. if(withCredentials) {
  2992. xhr.withCredentials = true;
  2993. }
  2994. xhr.open("GET", url, true);
  2995. xhr.onreadystatechange = handler;
  2996. if(type == 'blob'){
  2997. xhr.responseType = BLOB_RESPONSE;
  2998. }
  2999. if(type == "json") {
  3000. xhr.setRequestHeader("Accept", "application/json");
  3001. }
  3002. if(type == 'xml') {
  3003. xhr.overrideMimeType('text/xml');
  3004. }
  3005. xhr.send();
  3006. function handler() {
  3007. if (this.readyState === this.DONE) {
  3008. if (this.status === 200 || this.responseXML ) { //-- Firefox is reporting 0 for blob urls
  3009. var r;
  3010. if(type == 'xml'){
  3011. r = this.responseXML;
  3012. }else
  3013. if(type == 'json'){
  3014. r = JSON.parse(this.response);
  3015. }else
  3016. if(type == 'blob'){
  3017. if(supportsURL) {
  3018. r = this.response;
  3019. } else {
  3020. //-- Safari doesn't support responseType blob, so create a blob from arraybuffer
  3021. r = new Blob([this.response]);
  3022. }
  3023. }else{
  3024. r = this.response;
  3025. }
  3026. deferred.resolve(r);
  3027. } else {
  3028. deferred.reject({
  3029. message : this.response,
  3030. stack : new Error().stack
  3031. });
  3032. }
  3033. }
  3034. }
  3035. return deferred.promise;
  3036. };
  3037. EPUBJS.core.toArray = function(obj) {
  3038. var arr = [];
  3039. for (var member in obj) {
  3040. var newitm;
  3041. if ( obj.hasOwnProperty(member) ) {
  3042. newitm = obj[member];
  3043. newitm.ident = member;
  3044. arr.push(newitm);
  3045. }
  3046. }
  3047. return arr;
  3048. };
  3049. //-- Parse the different parts of a url, returning a object
  3050. EPUBJS.core.uri = function(url){
  3051. var uri = {
  3052. protocol : '',
  3053. host : '',
  3054. path : '',
  3055. origin : '',
  3056. directory : '',
  3057. base : '',
  3058. filename : '',
  3059. extension : '',
  3060. fragment : '',
  3061. href : url
  3062. },
  3063. doubleSlash = url.indexOf('://'),
  3064. search = url.indexOf('?'),
  3065. fragment = url.indexOf("#"),
  3066. withoutProtocol,
  3067. dot,
  3068. firstSlash;
  3069. if(fragment != -1) {
  3070. uri.fragment = url.slice(fragment + 1);
  3071. url = url.slice(0, fragment);
  3072. }
  3073. if(search != -1) {
  3074. uri.search = url.slice(search + 1);
  3075. url = url.slice(0, search);
  3076. href = url;
  3077. }
  3078. if(doubleSlash != -1) {
  3079. uri.protocol = url.slice(0, doubleSlash);
  3080. withoutProtocol = url.slice(doubleSlash+3);
  3081. firstSlash = withoutProtocol.indexOf('/');
  3082. if(firstSlash === -1) {
  3083. uri.host = uri.path;
  3084. uri.path = "";
  3085. } else {
  3086. uri.host = withoutProtocol.slice(0, firstSlash);
  3087. uri.path = withoutProtocol.slice(firstSlash);
  3088. }
  3089. uri.origin = uri.protocol + "://" + uri.host;
  3090. uri.directory = EPUBJS.core.folder(uri.path);
  3091. uri.base = uri.origin + uri.directory;
  3092. // return origin;
  3093. } else {
  3094. uri.path = url;
  3095. uri.directory = EPUBJS.core.folder(url);
  3096. uri.base = uri.directory;
  3097. }
  3098. //-- Filename
  3099. uri.filename = url.replace(uri.base, '');
  3100. dot = uri.filename.lastIndexOf('.');
  3101. if(dot != -1) {
  3102. uri.extension = uri.filename.slice(dot+1);
  3103. }
  3104. return uri;
  3105. };
  3106. //-- Parse out the folder, will return everything before the last slash
  3107. EPUBJS.core.folder = function(url){
  3108. var lastSlash = url.lastIndexOf('/');
  3109. if(lastSlash == -1) var folder = '';
  3110. folder = url.slice(0, lastSlash + 1);
  3111. return folder;
  3112. };
  3113. //-- https://github.com/ebidel/filer.js/blob/master/src/filer.js#L128
  3114. EPUBJS.core.dataURLToBlob = function(dataURL) {
  3115. var BASE64_MARKER = ';base64,',
  3116. parts, contentType, raw, rawLength, uInt8Array;
  3117. if (dataURL.indexOf(BASE64_MARKER) == -1) {
  3118. parts = dataURL.split(',');
  3119. contentType = parts[0].split(':')[1];
  3120. raw = parts[1];
  3121. return new Blob([raw], {type: contentType});
  3122. }
  3123. parts = dataURL.split(BASE64_MARKER);
  3124. contentType = parts[0].split(':')[1];
  3125. raw = window.atob(parts[1]);
  3126. rawLength = raw.length;
  3127. uInt8Array = new Uint8Array(rawLength);
  3128. for (var i = 0; i < rawLength; ++i) {
  3129. uInt8Array[i] = raw.charCodeAt(i);
  3130. }
  3131. return new Blob([uInt8Array], {type: contentType});
  3132. };
  3133. //-- Load scripts async: http://stackoverflow.com/questions/7718935/load-scripts-asynchronously
  3134. EPUBJS.core.addScript = function(src, callback, target) {
  3135. var s, r;
  3136. r = false;
  3137. s = document.createElement('script');
  3138. s.type = 'text/javascript';
  3139. s.async = false;
  3140. s.src = src;
  3141. s.onload = s.onreadystatechange = function() {
  3142. if ( !r && (!this.readyState || this.readyState == 'complete') ) {
  3143. r = true;
  3144. if(callback) callback();
  3145. }
  3146. };
  3147. target = target || document.body;
  3148. target.appendChild(s);
  3149. };
  3150. EPUBJS.core.addScripts = function(srcArr, callback, target) {
  3151. var total = srcArr.length,
  3152. curr = 0,
  3153. cb = function(){
  3154. curr++;
  3155. if(total == curr){
  3156. if(callback) callback();
  3157. }else{
  3158. EPUBJS.core.addScript(srcArr[curr], cb, target);
  3159. }
  3160. };
  3161. EPUBJS.core.addScript(srcArr[curr], cb, target);
  3162. };
  3163. EPUBJS.core.addCss = function(src, callback, target) {
  3164. var s, r;
  3165. r = false;
  3166. s = document.createElement('link');
  3167. s.type = 'text/css';
  3168. s.rel = "stylesheet";
  3169. s.href = src;
  3170. s.onload = s.onreadystatechange = function() {
  3171. if ( !r && (!this.readyState || this.readyState == 'complete') ) {
  3172. r = true;
  3173. if(callback) callback();
  3174. }
  3175. };
  3176. target = target || document.body;
  3177. target.appendChild(s);
  3178. };
  3179. EPUBJS.core.prefixed = function(unprefixed) {
  3180. var vendors = ["Webkit", "Moz", "O", "ms" ],
  3181. prefixes = ['-Webkit-', '-moz-', '-o-', '-ms-'],
  3182. upper = unprefixed[0].toUpperCase() + unprefixed.slice(1),
  3183. length = vendors.length;
  3184. if (typeof(document.body.style[unprefixed]) != 'undefined') {
  3185. return unprefixed;
  3186. }
  3187. for ( var i=0; i < length; i++ ) {
  3188. if (typeof(document.body.style[vendors[i] + upper]) != 'undefined') {
  3189. return vendors[i] + upper;
  3190. }
  3191. }
  3192. return unprefixed;
  3193. };
  3194. EPUBJS.core.resolveUrl = function(base, path) {
  3195. var url,
  3196. segments = [],
  3197. uri = EPUBJS.core.uri(path),
  3198. folders = base.split("/"),
  3199. paths;
  3200. if(uri.host) {
  3201. return path;
  3202. }
  3203. folders.pop();
  3204. paths = path.split("/");
  3205. paths.forEach(function(p){
  3206. if(p === ".."){
  3207. folders.pop();
  3208. }else{
  3209. segments.push(p);
  3210. }
  3211. });
  3212. url = folders.concat(segments);
  3213. return url.join("/");
  3214. };
  3215. // http://stackoverflow.com/questions/105034/how-to-create-a-guid-uuid-in-javascript
  3216. EPUBJS.core.uuid = function() {
  3217. var d = new Date().getTime();
  3218. var uuid = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
  3219. var r = (d + Math.random()*16)%16 | 0;
  3220. d = Math.floor(d/16);
  3221. return (c=='x' ? r : (r&0x7|0x8)).toString(16);
  3222. });
  3223. return uuid;
  3224. };
  3225. // Fast quicksort insert for sorted array -- based on:
  3226. // http://stackoverflow.com/questions/1344500/efficient-way-to-insert-a-number-into-a-sorted-array-of-numbers
  3227. EPUBJS.core.insert = function(item, array, compareFunction) {
  3228. var location = EPUBJS.core.locationOf(item, array, compareFunction);
  3229. array.splice(location, 0, item);
  3230. return location;
  3231. };
  3232. EPUBJS.core.locationOf = function(item, array, compareFunction, _start, _end) {
  3233. var start = _start || 0;
  3234. var end = _end || array.length;
  3235. var pivot = parseInt(start + (end - start) / 2);
  3236. var compared;
  3237. if(!compareFunction){
  3238. compareFunction = function(a, b) {
  3239. if(a > b) return 1;
  3240. if(a < b) return -1;
  3241. if(a = b) return 0;
  3242. };
  3243. }
  3244. if(end-start <= 0) {
  3245. return pivot;
  3246. }
  3247. compared = compareFunction(array[pivot], item);
  3248. if(end-start === 1) {
  3249. return compared > 0 ? pivot : pivot + 1;
  3250. }
  3251. if(compared === 0) {
  3252. return pivot;
  3253. }
  3254. if(compared === -1) {
  3255. return EPUBJS.core.locationOf(item, array, compareFunction, pivot, end);
  3256. } else{
  3257. return EPUBJS.core.locationOf(item, array, compareFunction, start, pivot);
  3258. }
  3259. };
  3260. EPUBJS.core.indexOfSorted = function(item, array, compareFunction, _start, _end) {
  3261. var start = _start || 0;
  3262. var end = _end || array.length;
  3263. var pivot = parseInt(start + (end - start) / 2);
  3264. var compared;
  3265. if(!compareFunction){
  3266. compareFunction = function(a, b) {
  3267. if(a > b) return 1;
  3268. if(a < b) return -1;
  3269. if(a = b) return 0;
  3270. };
  3271. }
  3272. if(end-start <= 0) {
  3273. return -1; // Not found
  3274. }
  3275. compared = compareFunction(array[pivot], item);
  3276. if(end-start === 1) {
  3277. return compared === 0 ? pivot : -1;
  3278. }
  3279. if(compared === 0) {
  3280. return pivot; // Found
  3281. }
  3282. if(compared === -1) {
  3283. return EPUBJS.core.indexOfSorted(item, array, compareFunction, pivot, end);
  3284. } else{
  3285. return EPUBJS.core.indexOfSorted(item, array, compareFunction, start, pivot);
  3286. }
  3287. };
  3288. EPUBJS.core.queue = function(_scope){
  3289. var _q = [];
  3290. var scope = _scope;
  3291. // Add an item to the queue
  3292. var enqueue = function(funcName, args, context) {
  3293. _q.push({
  3294. "funcName" : funcName,
  3295. "args" : args,
  3296. "context" : context
  3297. });
  3298. return _q;
  3299. };
  3300. // Run one item
  3301. var dequeue = function(){
  3302. var inwait;
  3303. if(_q.length) {
  3304. inwait = _q.shift();
  3305. // Defer to any current tasks
  3306. // setTimeout(function(){
  3307. scope[inwait.funcName].apply(inwait.context || scope, inwait.args);
  3308. // }, 0);
  3309. }
  3310. };
  3311. // Run All
  3312. var flush = function(){
  3313. while(_q.length) {
  3314. dequeue();
  3315. }
  3316. };
  3317. // Clear all items in wait
  3318. var clear = function(){
  3319. _q = [];
  3320. };
  3321. var length = function(){
  3322. return _q.length;
  3323. };
  3324. return {
  3325. "enqueue" : enqueue,
  3326. "dequeue" : dequeue,
  3327. "flush" : flush,
  3328. "clear" : clear,
  3329. "length" : length
  3330. };
  3331. };
  3332. // From: https://code.google.com/p/fbug/source/browse/branches/firebug1.10/content/firebug/lib/xpath.js
  3333. /**
  3334. * Gets an XPath for an element which describes its hierarchical location.
  3335. */
  3336. EPUBJS.core.getElementXPath = function(element) {
  3337. if (element && element.id) {
  3338. return '//*[@id="' + element.id + '"]';
  3339. } else {
  3340. return EPUBJS.core.getElementTreeXPath(element);
  3341. }
  3342. };
  3343. EPUBJS.core.getElementTreeXPath = function(element) {
  3344. var paths = [];
  3345. var isXhtml = (element.ownerDocument.documentElement.getAttribute('xmlns') === "http://www.w3.org/1999/xhtml");
  3346. var index, nodeName, tagName, pathIndex;
  3347. if(element.nodeType === Node.TEXT_NODE){
  3348. // index = Array.prototype.indexOf.call(element.parentNode.childNodes, element) + 1;
  3349. index = EPUBJS.core.indexOfTextNode(element) + 1;
  3350. paths.push("text()["+index+"]");
  3351. element = element.parentNode;
  3352. }
  3353. // Use nodeName (instead of localName) so namespace prefix is included (if any).
  3354. for (; element && element.nodeType == 1; element = element.parentNode)
  3355. {
  3356. index = 0;
  3357. for (var sibling = element.previousSibling; sibling; sibling = sibling.previousSibling)
  3358. {
  3359. // Ignore document type declaration.
  3360. if (sibling.nodeType == Node.DOCUMENT_TYPE_NODE) {
  3361. continue;
  3362. }
  3363. if (sibling.nodeName == element.nodeName) {
  3364. ++index;
  3365. }
  3366. }
  3367. nodeName = element.nodeName.toLowerCase();
  3368. tagName = (isXhtml ? "xhtml:" + nodeName : nodeName);
  3369. pathIndex = (index ? "[" + (index+1) + "]" : "");
  3370. paths.splice(0, 0, tagName + pathIndex);
  3371. }
  3372. return paths.length ? "./" + paths.join("/") : null;
  3373. };
  3374. EPUBJS.core.nsResolver = function(prefix) {
  3375. var ns = {
  3376. 'xhtml' : 'http://www.w3.org/1999/xhtml',
  3377. 'epub': 'http://www.idpf.org/2007/ops'
  3378. };
  3379. return ns[prefix] || null;
  3380. };
  3381. //https://stackoverflow.com/questions/13482352/xquery-looking-for-text-with-single-quote/13483496#13483496
  3382. EPUBJS.core.cleanStringForXpath = function(str) {
  3383. var parts = str.match(/[^'"]+|['"]/g);
  3384. parts = parts.map(function(part){
  3385. if (part === "'") {
  3386. return '\"\'\"'; // output "'"
  3387. }
  3388. if (part === '"') {
  3389. return "\'\"\'"; // output '"'
  3390. }
  3391. return "\'" + part + "\'";
  3392. });
  3393. return "concat(\'\'," + parts.join(",") + ")";
  3394. };
  3395. EPUBJS.core.indexOfTextNode = function(textNode){
  3396. var parent = textNode.parentNode;
  3397. var children = parent.childNodes;
  3398. var sib;
  3399. var index = -1;
  3400. for (var i = 0; i < children.length; i++) {
  3401. sib = children[i];
  3402. if(sib.nodeType === Node.TEXT_NODE){
  3403. index++;
  3404. }
  3405. if(sib == textNode) break;
  3406. }
  3407. return index;
  3408. };
  3409. EPUBJS.EpubCFI = function(cfiStr){
  3410. if(cfiStr) return this.parse(cfiStr);
  3411. };
  3412. EPUBJS.EpubCFI.prototype.generateChapterComponent = function(_spineNodeIndex, _pos, id) {
  3413. var pos = parseInt(_pos),
  3414. spineNodeIndex = _spineNodeIndex + 1,
  3415. cfi = '/'+spineNodeIndex+'/';
  3416. cfi += (pos + 1) * 2;
  3417. if(id) cfi += "[" + id + "]";
  3418. //cfi += "!";
  3419. return cfi;
  3420. };
  3421. EPUBJS.EpubCFI.prototype.generatePathComponent = function(steps) {
  3422. var parts = [];
  3423. steps.forEach(function(part){
  3424. var segment = '';
  3425. segment += (part.index + 1) * 2;
  3426. if(part.id) {
  3427. segment += "[" + part.id + "]";
  3428. }
  3429. parts.push(segment);
  3430. });
  3431. return parts.join('/');
  3432. };
  3433. EPUBJS.EpubCFI.prototype.generateCfiFromElement = function(element, chapter) {
  3434. var steps = this.pathTo(element);
  3435. var path = this.generatePathComponent(steps);
  3436. if(!path.length) {
  3437. // Start of Chapter
  3438. return "epubcfi(" + chapter + "!/4/)";
  3439. } else {
  3440. // First Text Node
  3441. return "epubcfi(" + chapter + "!" + path + "/1:0)";
  3442. }
  3443. };
  3444. EPUBJS.EpubCFI.prototype.pathTo = function(node) {
  3445. var stack = [],
  3446. children;
  3447. while(node && node.parentNode !== null && node.parentNode.nodeType != 9) {
  3448. children = node.parentNode.children;
  3449. stack.unshift({
  3450. 'id' : node.id,
  3451. // 'classList' : node.classList,
  3452. 'tagName' : node.tagName,
  3453. 'index' : children ? Array.prototype.indexOf.call(children, node) : 0
  3454. });
  3455. node = node.parentNode;
  3456. }
  3457. return stack;
  3458. };
  3459. EPUBJS.EpubCFI.prototype.getChapterComponent = function(cfiStr) {
  3460. var splitStr = cfiStr.split("!");
  3461. return splitStr[0];
  3462. };
  3463. EPUBJS.EpubCFI.prototype.getPathComponent = function(cfiStr) {
  3464. var splitStr = cfiStr.split("!");
  3465. var pathComponent = splitStr[1] ? splitStr[1].split(":") : '';
  3466. return pathComponent[0];
  3467. };
  3468. EPUBJS.EpubCFI.prototype.getCharecterOffsetComponent = function(cfiStr) {
  3469. var splitStr = cfiStr.split(":");
  3470. return splitStr[1] || '';
  3471. };
  3472. EPUBJS.EpubCFI.prototype.parse = function(cfiStr) {
  3473. var cfi = {},
  3474. chapSegment,
  3475. chapterComponent,
  3476. pathComponent,
  3477. charecterOffsetComponent,
  3478. assertion,
  3479. chapId,
  3480. path,
  3481. end,
  3482. endInt,
  3483. text,
  3484. parseStep = function(part){
  3485. var type, index, has_brackets, id;
  3486. type = "element";
  3487. index = parseInt(part) / 2 - 1;
  3488. has_brackets = part.match(/\[(.*)\]/);
  3489. if(has_brackets && has_brackets[1]){
  3490. id = has_brackets[1];
  3491. }
  3492. return {
  3493. "type" : type,
  3494. 'index' : index,
  3495. 'id' : id || false
  3496. };
  3497. };
  3498. if(typeof cfiStr !== "string") {
  3499. return {spinePos: -1};
  3500. }
  3501. cfi.str = cfiStr;
  3502. if(cfiStr.indexOf("epubcfi(") === 0 && cfiStr[cfiStr.length-1] === ")") {
  3503. // Remove intial epubcfi( and ending )
  3504. cfiStr = cfiStr.slice(8, cfiStr.length-1);
  3505. }
  3506. chapterComponent = this.getChapterComponent(cfiStr);
  3507. pathComponent = this.getPathComponent(cfiStr) || '';
  3508. charecterOffsetComponent = this.getCharecterOffsetComponent(cfiStr);
  3509. // Make sure this is a valid cfi or return
  3510. if(!chapterComponent) {
  3511. return {spinePos: -1};
  3512. }
  3513. // Chapter segment is always the second one
  3514. chapSegment = chapterComponent.split("/")[2] || '';
  3515. if(!chapSegment) return {spinePos:-1};
  3516. cfi.spinePos = (parseInt(chapSegment) / 2 - 1 ) || 0;
  3517. chapId = chapSegment.match(/\[(.*)\]/);
  3518. cfi.spineId = chapId ? chapId[1] : false;
  3519. if(pathComponent.indexOf(',') != -1) {
  3520. // Handle ranges -- not supported yet
  3521. console.warn("CFI Ranges are not supported");
  3522. }
  3523. path = pathComponent.split('/');
  3524. end = path.pop();
  3525. cfi.steps = [];
  3526. path.forEach(function(part){
  3527. var step;
  3528. if(part) {
  3529. step = parseStep(part);
  3530. cfi.steps.push(step);
  3531. }
  3532. });
  3533. //-- Check if END is a text node or element
  3534. endInt = parseInt(end);
  3535. if(!isNaN(endInt)) {
  3536. if(endInt % 2 === 0) { // Even = is an element
  3537. cfi.steps.push(parseStep(end));
  3538. } else {
  3539. cfi.steps.push({
  3540. "type" : "text",
  3541. 'index' : (endInt - 1 ) / 2
  3542. });
  3543. }
  3544. }
  3545. assertion = charecterOffsetComponent.match(/\[(.*)\]/);
  3546. if(assertion && assertion[1]){
  3547. cfi.characterOffset = parseInt(charecterOffsetComponent.split('[')[0]);
  3548. // We arent handling these assertions yet
  3549. cfi.textLocationAssertion = assertion[1];
  3550. } else {
  3551. cfi.characterOffset = parseInt(charecterOffsetComponent);
  3552. }
  3553. return cfi;
  3554. };
  3555. EPUBJS.EpubCFI.prototype.addMarker = function(cfi, _doc, _marker) {
  3556. var doc = _doc || document;
  3557. var marker = _marker || this.createMarker(doc);
  3558. var parent;
  3559. var lastStep;
  3560. var text;
  3561. var split;
  3562. if(typeof cfi === 'string') {
  3563. cfi = this.parse(cfi);
  3564. }
  3565. // Get the terminal step
  3566. lastStep = cfi.steps[cfi.steps.length-1];
  3567. // check spinePos
  3568. if(cfi.spinePos === -1) {
  3569. // Not a valid CFI
  3570. return false;
  3571. }
  3572. // Find the CFI elements parent
  3573. parent = this.findParent(cfi, doc);
  3574. if(!parent) {
  3575. // CFI didn't return an element
  3576. // Maybe it isnt in the current chapter?
  3577. return false;
  3578. }
  3579. if(lastStep && lastStep.type === "text") {
  3580. text = parent.childNodes[lastStep.index];
  3581. if(cfi.characterOffset){
  3582. split = text.splitText(cfi.characterOffset);
  3583. marker.classList.add("EPUBJS-CFI-SPLIT");
  3584. parent.insertBefore(marker, split);
  3585. } else {
  3586. parent.insertBefore(marker, text);
  3587. }
  3588. } else {
  3589. parent.insertBefore(marker, parent.firstChild);
  3590. }
  3591. return marker;
  3592. };
  3593. EPUBJS.EpubCFI.prototype.createMarker = function(_doc) {
  3594. var doc = _doc || document;
  3595. var element = doc.createElement('span');
  3596. element.id = "EPUBJS-CFI-MARKER:"+ EPUBJS.core.uuid();
  3597. element.classList.add("EPUBJS-CFI-MARKER");
  3598. return element;
  3599. };
  3600. EPUBJS.EpubCFI.prototype.removeMarker = function(marker, _doc) {
  3601. var doc = _doc || document;
  3602. // var id = marker.id;
  3603. // Cleanup textnodes if they were split
  3604. if(marker.classList.contains("EPUBJS-CFI-SPLIT")){
  3605. nextSib = marker.nextSibling;
  3606. prevSib = marker.previousSibling;
  3607. if(nextSib &&
  3608. prevSib &&
  3609. nextSib.nodeType === 3 &&
  3610. prevSib.nodeType === 3){
  3611. prevSib.textContent += nextSib.textContent;
  3612. marker.parentNode.removeChild(nextSib);
  3613. }
  3614. marker.parentNode.removeChild(marker);
  3615. } else if(marker.classList.contains("EPUBJS-CFI-MARKER")) {
  3616. // Remove only elements added as markers
  3617. marker.parentNode.removeChild(marker);
  3618. }
  3619. };
  3620. EPUBJS.EpubCFI.prototype.findParent = function(cfi, _doc) {
  3621. var doc = _doc || document,
  3622. element = doc.getElementsByTagName('html')[0],
  3623. children = Array.prototype.slice.call(element.children),
  3624. num, index, part, sections,
  3625. text, textBegin, textEnd;
  3626. if(typeof cfi === 'string') {
  3627. cfi = this.parse(cfi);
  3628. }
  3629. sections = cfi.steps.slice(0); // Clone steps array
  3630. if(!sections.length) {
  3631. return doc.getElementsByTagName('body')[0];
  3632. }
  3633. while(sections && sections.length > 0) {
  3634. part = sections.shift();
  3635. // Find textNodes Parent
  3636. if(part.type === "text") {
  3637. text = element.childNodes[part.index];
  3638. element = text.parentNode || element;
  3639. // Find element by id if present
  3640. } else if(part.id){
  3641. element = doc.getElementById(part.id);
  3642. // Find element in parent
  3643. }else{
  3644. element = children[part.index];
  3645. }
  3646. // Element can't be found
  3647. if(typeof element === "undefined") {
  3648. console.error("No Element For", part, cfi.str);
  3649. return false;
  3650. }
  3651. // Get current element children and continue through steps
  3652. children = Array.prototype.slice.call(element.children);
  3653. }
  3654. return element;
  3655. };
  3656. EPUBJS.EpubCFI.prototype.compare = function(cfiOne, cfiTwo) {
  3657. if(typeof cfiOne === 'string') {
  3658. cfiOne = new EPUBJS.EpubCFI(cfiOne);
  3659. }
  3660. if(typeof cfiTwo === 'string') {
  3661. cfiTwo = new EPUBJS.EpubCFI(cfiTwo);
  3662. }
  3663. // Compare Spine Positions
  3664. if(cfiOne.spinePos > cfiTwo.spinePos) {
  3665. return 1;
  3666. }
  3667. if(cfiOne.spinePos < cfiTwo.spinePos) {
  3668. return -1;
  3669. }
  3670. // Compare Each Step in the First item
  3671. for (var i = 0; i < cfiOne.steps.length; i++) {
  3672. if(!cfiTwo.steps[i]) {
  3673. return 1;
  3674. }
  3675. if(cfiOne.steps[i].index > cfiTwo.steps[i].index) {
  3676. return 1;
  3677. }
  3678. if(cfiOne.steps[i].index < cfiTwo.steps[i].index) {
  3679. return -1;
  3680. }
  3681. // Otherwise continue checking
  3682. }
  3683. // All steps in First present in Second
  3684. if(cfiOne.steps.length < cfiTwo.steps.length) {
  3685. return -1;
  3686. }
  3687. // Compare the charecter offset of the text node
  3688. if(cfiOne.characterOffset > cfiTwo.characterOffset) {
  3689. return 1;
  3690. }
  3691. if(cfiOne.characterOffset < cfiTwo.characterOffset) {
  3692. return -1;
  3693. }
  3694. // CFI's are equal
  3695. return 0;
  3696. };
  3697. EPUBJS.EpubCFI.prototype.generateCfiFromHref = function(href, book) {
  3698. var uri = EPUBJS.core.uri(href);
  3699. var path = uri.path;
  3700. var fragment = uri.fragment;
  3701. var spinePos = book.spineIndexByURL[path];
  3702. var loaded;
  3703. var deferred = new RSVP.defer();
  3704. var epubcfi = new EPUBJS.EpubCFI();
  3705. var spineItem;
  3706. if(typeof spinePos !== "undefined"){
  3707. spineItem = book.spine[spinePos];
  3708. loaded = book.loadXml(spineItem.url);
  3709. loaded.then(function(doc){
  3710. var element = doc.getElementById(fragment);
  3711. var cfi;
  3712. cfi = epubcfi.generateCfiFromElement(element, spineItem.cfiBase);
  3713. deferred.resolve(cfi);
  3714. });
  3715. }
  3716. return deferred.promise;
  3717. };
  3718. EPUBJS.EpubCFI.prototype.generateCfiFromTextNode = function(anchor, offset, base) {
  3719. var parent = anchor.parentNode;
  3720. var steps = this.pathTo(parent);
  3721. var path = this.generatePathComponent(steps);
  3722. var index = 1 + (2 * Array.prototype.indexOf.call(parent.childNodes, anchor));
  3723. return "epubcfi(" + base + "!" + path + "/"+index+":"+(offset || 0)+")";
  3724. };
  3725. EPUBJS.EpubCFI.prototype.generateCfiFromRangeAnchor = function(range, base) {
  3726. var anchor = range.anchorNode;
  3727. var offset = range.anchorOffset;
  3728. return this.generateCfiFromTextNode(anchor, offset, base);
  3729. };
  3730. EPUBJS.EpubCFI.prototype.generateCfiFromRange = function(range, base) {
  3731. var start, startElement, startSteps, startPath, startOffset, startIndex;
  3732. var end, endElement, endSteps, endPath, endOffset, endIndex;
  3733. start = range.startContainer;
  3734. if(start.nodeType === 3) { // text node
  3735. startElement = start.parentNode;
  3736. //startIndex = 1 + (2 * Array.prototype.indexOf.call(startElement.childNodes, start));
  3737. startIndex = 1 + (2 * EPUBJS.core.indexOfTextNode(start));
  3738. startSteps = this.pathTo(startElement);
  3739. } else if(range.collapsed) {
  3740. return this.generateCfiFromElement(start, base); // single element
  3741. } else {
  3742. startSteps = this.pathTo(start);
  3743. }
  3744. startPath = this.generatePathComponent(startSteps);
  3745. startOffset = range.startOffset;
  3746. if(!range.collapsed) {
  3747. end = range.endContainer;
  3748. if(end.nodeType === 3) { // text node
  3749. endElement = end.parentNode;
  3750. // endIndex = 1 + (2 * Array.prototype.indexOf.call(endElement.childNodes, end));
  3751. endIndex = 1 + (2 * EPUBJS.core.indexOfTextNode(end));
  3752. endSteps = this.pathTo(endElement);
  3753. } else {
  3754. endSteps = this.pathTo(end);
  3755. }
  3756. endPath = this.generatePathComponent(endSteps);
  3757. endOffset = range.endOffset;
  3758. return "epubcfi(" + base + "!" + startPath + "/" + startIndex + ":" + startOffset + "," + endPath + "/" + endIndex + ":" + endOffset + ")";
  3759. } else {
  3760. return "epubcfi(" + base + "!" + startPath + "/"+ startIndex +":"+ startOffset +")";
  3761. }
  3762. };
  3763. EPUBJS.EpubCFI.prototype.generateXpathFromSteps = function(steps) {
  3764. var xpath = [".", "*"];
  3765. steps.forEach(function(step){
  3766. var position = step.index + 1;
  3767. if(step.id){
  3768. xpath.push("*[position()=" + position + " and @id='" + step.id + "']");
  3769. } else if(step.type === "text") {
  3770. xpath.push("text()[" + position + "]");
  3771. } else {
  3772. xpath.push("*[" + position + "]");
  3773. }
  3774. });
  3775. return xpath.join("/");
  3776. };
  3777. EPUBJS.EpubCFI.prototype.generateRangeFromCfi = function(cfi, _doc) {
  3778. var doc = _doc || document;
  3779. var range = doc.createRange();
  3780. var lastStep;
  3781. var xpath;
  3782. var startContainer;
  3783. var textLength;
  3784. if(typeof cfi === 'string') {
  3785. cfi = this.parse(cfi);
  3786. }
  3787. // check spinePos
  3788. if(cfi.spinePos === -1) {
  3789. // Not a valid CFI
  3790. return false;
  3791. }
  3792. xpath = this.generateXpathFromSteps(cfi.steps);
  3793. // Get the terminal step
  3794. lastStep = cfi.steps[cfi.steps.length-1];
  3795. startContainer = doc.evaluate(xpath, doc, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue;
  3796. if(!startContainer) {
  3797. return null;
  3798. }
  3799. if(startContainer && cfi.characterOffset >= 0) {
  3800. textLength = startContainer.length;
  3801. if(cfi.characterOffset < textLength) {
  3802. range.setStart(startContainer, cfi.characterOffset);
  3803. range.setEnd(startContainer, textLength );
  3804. } else {
  3805. console.debug("offset greater than length:", cfi.characterOffset, textLength);
  3806. range.setStart(startContainer, textLength - 1 );
  3807. range.setEnd(startContainer, textLength );
  3808. }
  3809. } else if(startContainer) {
  3810. range.selectNode(startContainer);
  3811. }
  3812. // doc.defaultView.getSelection().addRange(range);
  3813. return range;
  3814. };
  3815. EPUBJS.Events = function(obj, el){
  3816. this.events = {};
  3817. if(!el){
  3818. this.el = document.createElement('div');
  3819. }else{
  3820. this.el = el;
  3821. }
  3822. obj.createEvent = this.createEvent;
  3823. obj.tell = this.tell;
  3824. obj.listen = this.listen;
  3825. obj.deafen = this.deafen;
  3826. obj.listenUntil = this.listenUntil;
  3827. return this;
  3828. };
  3829. EPUBJS.Events.prototype.createEvent = function(evt){
  3830. var e = new CustomEvent(evt);
  3831. this.events[evt] = e;
  3832. return e;
  3833. };
  3834. EPUBJS.Events.prototype.tell = function(evt, msg){
  3835. var e;
  3836. if(!this.events[evt]){
  3837. console.warn("No event:", evt, "defined yet, creating.");
  3838. e = this.createEvent(evt);
  3839. }else{
  3840. e = this.events[evt];
  3841. }
  3842. if(msg) e.msg = msg;
  3843. this.el.dispatchEvent(e);
  3844. };
  3845. EPUBJS.Events.prototype.listen = function(evt, func, bindto){
  3846. if(!this.events[evt]){
  3847. console.warn("No event:", evt, "defined yet, creating.");
  3848. this.createEvent(evt);
  3849. return;
  3850. }
  3851. if(bindto){
  3852. this.el.addEventListener(evt, func.bind(bindto), false);
  3853. }else{
  3854. this.el.addEventListener(evt, func, false);
  3855. }
  3856. };
  3857. EPUBJS.Events.prototype.deafen = function(evt, func){
  3858. this.el.removeEventListener(evt, func, false);
  3859. };
  3860. EPUBJS.Events.prototype.listenUntil = function(OnEvt, OffEvt, func, bindto){
  3861. this.listen(OnEvt, func, bindto);
  3862. function unlisten(){
  3863. this.deafen(OnEvt, func);
  3864. this.deafen(OffEvt, unlisten);
  3865. }
  3866. this.listen(OffEvt, unlisten, this);
  3867. };
  3868. EPUBJS.hooks = {};
  3869. EPUBJS.Hooks = (function(){
  3870. function hooks(){}
  3871. //-- Get pre-registered hooks
  3872. hooks.prototype.getHooks = function(){
  3873. var plugs;
  3874. this.hooks = {};
  3875. Array.prototype.slice.call(arguments).forEach(function(arg){
  3876. this.hooks[arg] = [];
  3877. }, this);
  3878. for (var plugType in this.hooks) {
  3879. plugs = _.values(EPUBJS.hooks[plugType]);
  3880. plugs.forEach(function(hook){
  3881. this.registerHook(plugType, hook);
  3882. }, this);
  3883. }
  3884. };
  3885. //-- Hooks allow for injecting async functions that must all complete before continuing
  3886. // Functions must have a callback as their first argument.
  3887. hooks.prototype.registerHook = function(type, toAdd, toFront){
  3888. if(typeof(this.hooks[type]) != "undefined"){
  3889. if(typeof(toAdd) === "function"){
  3890. if(toFront) {
  3891. this.hooks[type].unshift(toAdd);
  3892. }else{
  3893. this.hooks[type].push(toAdd);
  3894. }
  3895. }else if(Array.isArray(toAdd)){
  3896. toAdd.forEach(function(hook){
  3897. if(toFront) {
  3898. this.hooks[type].unshift(hook);
  3899. }else{
  3900. this.hooks[type].push(hook);
  3901. }
  3902. }, this);
  3903. }
  3904. }else{
  3905. //-- Allows for undefined hooks, but maybe this should error?
  3906. this.hooks[type] = [func];
  3907. }
  3908. };
  3909. hooks.prototype.triggerHooks = function(type, callback, passed){
  3910. var hooks, count;
  3911. if(typeof(this.hooks[type]) == "undefined") return false;
  3912. hooks = this.hooks[type];
  3913. count = hooks.length;
  3914. if(count === 0 && callback) {
  3915. callback();
  3916. }
  3917. function countdown(){
  3918. count--;
  3919. if(count <= 0 && callback) callback();
  3920. }
  3921. hooks.forEach(function(hook){
  3922. hook(countdown, passed);
  3923. });
  3924. };
  3925. return {
  3926. register: function(name) {
  3927. if(EPUBJS.hooks[name] === undefined) { EPUBJS.hooks[name] = {}; }
  3928. if(typeof EPUBJS.hooks[name] !== 'object') { throw "Already registered: "+name; }
  3929. return EPUBJS.hooks[name];
  3930. },
  3931. mixin: function(object) {
  3932. for (var prop in hooks.prototype) {
  3933. object[prop] = hooks.prototype[prop];
  3934. }
  3935. }
  3936. };
  3937. })();
  3938. EPUBJS.Layout = EPUBJS.Layout || {};
  3939. EPUBJS.Layout.Reflowable = function(){
  3940. this.documentElement = null;
  3941. this.spreadWidth = null;
  3942. };
  3943. EPUBJS.Layout.Reflowable.prototype.format = function(documentElement, _width, _height, _gap){
  3944. // Get the prefixed CSS commands
  3945. var columnAxis = EPUBJS.core.prefixed('columnAxis');
  3946. var columnGap = EPUBJS.core.prefixed('columnGap');
  3947. var columnWidth = EPUBJS.core.prefixed('columnWidth');
  3948. var columnFill = EPUBJS.core.prefixed('columnFill');
  3949. //-- Check the width and create even width columns
  3950. var width = Math.floor(_width);
  3951. // var width = (fullWidth % 2 === 0) ? fullWidth : fullWidth - 0; // Not needed for single
  3952. var section = Math.floor(width / 8);
  3953. var gap = (_gap >= 0) ? _gap : ((section % 2 === 0) ? section : section - 1);
  3954. this.documentElement = documentElement;
  3955. //-- Single Page
  3956. this.spreadWidth = (width + gap);
  3957. documentElement.style.overflow = "hidden";
  3958. // Must be set to the new calculated width or the columns will be off
  3959. documentElement.style.width = width + "px";
  3960. //-- Adjust height
  3961. documentElement.style.height = _height + "px";
  3962. //-- Add columns
  3963. documentElement.style[columnAxis] = "horizontal";
  3964. documentElement.style[columnFill] = "auto";
  3965. documentElement.style[columnWidth] = width+"px";
  3966. documentElement.style[columnGap] = gap+"px";
  3967. this.colWidth = width;
  3968. this.gap = gap;
  3969. return {
  3970. pageWidth : this.spreadWidth,
  3971. pageHeight : _height
  3972. };
  3973. };
  3974. EPUBJS.Layout.Reflowable.prototype.calculatePages = function() {
  3975. var totalWidth, displayedPages;
  3976. this.documentElement.style.width = "auto"; //-- reset width for calculations
  3977. totalWidth = this.documentElement.scrollWidth;
  3978. displayedPages = Math.ceil(totalWidth / this.spreadWidth);
  3979. return {
  3980. displayedPages : displayedPages,
  3981. pageCount : displayedPages
  3982. };
  3983. };
  3984. EPUBJS.Layout.ReflowableSpreads = function(){
  3985. this.documentElement = null;
  3986. this.spreadWidth = null;
  3987. };
  3988. EPUBJS.Layout.ReflowableSpreads.prototype.format = function(documentElement, _width, _height, _gap){
  3989. var columnAxis = EPUBJS.core.prefixed('columnAxis');
  3990. var columnGap = EPUBJS.core.prefixed('columnGap');
  3991. var columnWidth = EPUBJS.core.prefixed('columnWidth');
  3992. var columnFill = EPUBJS.core.prefixed('columnFill');
  3993. var divisor = 2,
  3994. cutoff = 800;
  3995. //-- Check the width and create even width columns
  3996. var fullWidth = Math.floor(_width);
  3997. var width = (fullWidth % 2 === 0) ? fullWidth : fullWidth - 1;
  3998. var section = Math.floor(width / 8);
  3999. var gap = (_gap >= 0) ? _gap : ((section % 2 === 0) ? section : section - 1);
  4000. //-- Double Page
  4001. var colWidth = Math.floor((width - gap) / divisor);
  4002. this.documentElement = documentElement;
  4003. this.spreadWidth = (colWidth + gap) * divisor;
  4004. documentElement.style.overflow = "hidden";
  4005. // Must be set to the new calculated width or the columns will be off
  4006. documentElement.style.width = width + "px";
  4007. //-- Adjust height
  4008. documentElement.style.height = _height + "px";
  4009. //-- Add columns
  4010. documentElement.style[columnAxis] = "horizontal";
  4011. documentElement.style[columnFill] = "auto";
  4012. documentElement.style[columnGap] = gap+"px";
  4013. documentElement.style[columnWidth] = colWidth+"px";
  4014. this.colWidth = colWidth;
  4015. this.gap = gap;
  4016. return {
  4017. pageWidth : this.spreadWidth,
  4018. pageHeight : _height
  4019. };
  4020. };
  4021. EPUBJS.Layout.ReflowableSpreads.prototype.calculatePages = function() {
  4022. var totalWidth = this.documentElement.scrollWidth;
  4023. var displayedPages = Math.ceil(totalWidth / this.spreadWidth);
  4024. //-- Add a page to the width of the document to account an for odd number of pages
  4025. this.documentElement.style.width = totalWidth + this.spreadWidth + "px";
  4026. return {
  4027. displayedPages : displayedPages,
  4028. pageCount : displayedPages * 2
  4029. };
  4030. };
  4031. EPUBJS.Layout.Fixed = function(){
  4032. this.documentElement = null;
  4033. };
  4034. EPUBJS.Layout.Fixed = function(documentElement, _width, _height, _gap){
  4035. var columnWidth = EPUBJS.core.prefixed('columnWidth');
  4036. var viewport = documentElement.querySelector("[name=viewport");
  4037. var content;
  4038. var contents;
  4039. var width, height;
  4040. this.documentElement = documentElement;
  4041. /**
  4042. * check for the viewport size
  4043. * <meta name="viewport" content="width=1024,height=697" />
  4044. */
  4045. if(viewport && viewport.hasAttribute("content")) {
  4046. content = viewport.getAttribute("content");
  4047. contents = content.split(',');
  4048. if(contents[0]){
  4049. width = contents[0].replace("width=", '');
  4050. }
  4051. if(contents[1]){
  4052. height = contents[1].replace("height=", '');
  4053. }
  4054. }
  4055. //-- Adjust width and height
  4056. documentElement.style.width = width + "px" || "auto";
  4057. documentElement.style.height = height + "px" || "auto";
  4058. //-- Remove columns
  4059. documentElement.style[columnWidth] = "auto";
  4060. //-- Scroll
  4061. documentElement.style.overflow = "auto";
  4062. this.colWidth = width;
  4063. this.gap = 0;
  4064. return {
  4065. pageWidth : width,
  4066. pageHeight : height
  4067. };
  4068. };
  4069. EPUBJS.Layout.Fixed.prototype.calculatePages = function(){
  4070. return {
  4071. displayedPages : 1,
  4072. pageCount : 1
  4073. };
  4074. };
  4075. EPUBJS.Pagination = function(pageList) {
  4076. this.pages = [];
  4077. this.locations = [];
  4078. this.epubcfi = new EPUBJS.EpubCFI();
  4079. if(pageList && pageList.length) {
  4080. this.process(pageList);
  4081. }
  4082. };
  4083. EPUBJS.Pagination.prototype.process = function(pageList){
  4084. pageList.forEach(function(item){
  4085. this.pages.push(item.page);
  4086. this.locations.push(item.cfi);
  4087. }, this);
  4088. this.pageList = pageList;
  4089. this.firstPage = parseInt(this.pages[0]);
  4090. this.lastPage = parseInt(this.pages[this.pages.length-1]);
  4091. this.totalPages = this.lastPage - this.firstPage;
  4092. };
  4093. EPUBJS.Pagination.prototype.pageFromCfi = function(cfi){
  4094. var pg = -1;
  4095. // Check if the pageList has not been set yet
  4096. if(this.locations.length === 0) {
  4097. return -1;
  4098. }
  4099. // TODO: check if CFI is valid?
  4100. // check if the cfi is in the location list
  4101. // var index = this.locations.indexOf(cfi);
  4102. var index = EPUBJS.core.indexOfSorted(cfi, this.locations, this.epubcfi.compare);
  4103. if(index != -1 && index < (this.pages.length-1) ) {
  4104. pg = this.pages[index];
  4105. } else {
  4106. // Otherwise add it to the list of locations
  4107. // Insert it in the correct position in the locations page
  4108. //index = EPUBJS.core.insert(cfi, this.locations, this.epubcfi.compare);
  4109. index = EPUBJS.core.locationOf(cfi, this.locations, this.epubcfi.compare);
  4110. // Get the page at the location just before the new one, or return the first
  4111. pg = index-1 >= 0 ? this.pages[index-1] : this.pages[0];
  4112. pg = this.pages[index];
  4113. if(pg !== undefined) {
  4114. // Add the new page in so that the locations and page array match up
  4115. //this.pages.splice(index, 0, pg);
  4116. } else {
  4117. pg = -1;
  4118. }
  4119. }
  4120. return pg;
  4121. };
  4122. EPUBJS.Pagination.prototype.cfiFromPage = function(pg){
  4123. var cfi = -1;
  4124. // check that pg is an int
  4125. if(typeof pg != "number"){
  4126. pg = parseInt(pg);
  4127. }
  4128. // check if the cfi is in the page list
  4129. // Pages could be unsorted.
  4130. var index = this.pages.indexOf(pg);
  4131. if(index != -1) {
  4132. cfi = this.locations[index];
  4133. }
  4134. // TODO: handle pages not in the list
  4135. return cfi;
  4136. };
  4137. EPUBJS.Pagination.prototype.pageFromPercentage = function(percent){
  4138. var pg = Math.round(this.totalPages * percent);
  4139. return pg;
  4140. };
  4141. // Returns a value between 0 - 1 corresponding to the location of a page
  4142. EPUBJS.Pagination.prototype.percentageFromPage = function(pg){
  4143. var percentage = (pg - this.firstPage) / this.totalPages;
  4144. return Math.round(percentage * 1000) / 1000;
  4145. };
  4146. // Returns a value between 0 - 1 corresponding to the location of a cfi
  4147. EPUBJS.Pagination.prototype.percentageFromCfi = function(cfi){
  4148. var pg = this.pageFromCfi(cfi);
  4149. var percentage = this.percentageFromPage(pg);
  4150. return percentage;
  4151. };
  4152. EPUBJS.Parser = function(baseUrl){
  4153. this.baseUrl = baseUrl || '';
  4154. };
  4155. EPUBJS.Parser.prototype.container = function(containerXml){
  4156. //-- <rootfile full-path="OPS/package.opf" media-type="application/oebps-package+xml"/>
  4157. var rootfile, fullpath, folder, encoding;
  4158. if(!containerXml) {
  4159. console.error("Container File Not Found");
  4160. return;
  4161. }
  4162. rootfile = containerXml.querySelector("rootfile");
  4163. if(!rootfile) {
  4164. console.error("No RootFile Found");
  4165. return;
  4166. }
  4167. fullpath = rootfile.getAttribute('full-path');
  4168. folder = EPUBJS.core.uri(fullpath).directory;
  4169. encoding = containerXml.xmlEncoding;
  4170. //-- Now that we have the path we can parse the contents
  4171. return {
  4172. 'packagePath' : fullpath,
  4173. 'basePath' : folder,
  4174. 'encoding' : encoding
  4175. };
  4176. };
  4177. EPUBJS.Parser.prototype.identifier = function(packageXml){
  4178. var metadataNode;
  4179. if(!packageXml) {
  4180. console.error("Package File Not Found");
  4181. return;
  4182. }
  4183. metadataNode = packageXml.querySelector("metadata");
  4184. if(!metadataNode) {
  4185. console.error("No Metadata Found");
  4186. return;
  4187. }
  4188. return this.getElementText(metadataNode, "identifier");
  4189. };
  4190. EPUBJS.Parser.prototype.packageContents = function(packageXml, baseUrl){
  4191. var parse = this;
  4192. var metadataNode, manifestNode, spineNode;
  4193. var manifest, navPath, tocPath, coverPath;
  4194. var spineNodeIndex;
  4195. var spine;
  4196. var spineIndexByURL;
  4197. if(baseUrl) this.baseUrl = baseUrl;
  4198. if(!packageXml) {
  4199. console.error("Package File Not Found");
  4200. return;
  4201. }
  4202. metadataNode = packageXml.querySelector("metadata");
  4203. if(!metadataNode) {
  4204. console.error("No Metadata Found");
  4205. return;
  4206. }
  4207. manifestNode = packageXml.querySelector("manifest");
  4208. if(!manifestNode) {
  4209. console.error("No Manifest Found");
  4210. return;
  4211. }
  4212. spineNode = packageXml.querySelector("spine");
  4213. if(!spineNode) {
  4214. console.error("No Spine Found");
  4215. return;
  4216. }
  4217. manifest = parse.manifest(manifestNode);
  4218. navPath = parse.findNavPath(manifestNode);
  4219. tocPath = parse.findTocPath(manifestNode);
  4220. coverPath = parse.findCoverPath(manifestNode);
  4221. spineNodeIndex = Array.prototype.indexOf.call(spineNode.parentNode.childNodes, spineNode);
  4222. spine = parse.spine(spineNode, manifest);
  4223. spineIndexByURL = {};
  4224. spine.forEach(function(item){
  4225. spineIndexByURL[item.href] = item.index;
  4226. });
  4227. return {
  4228. 'metadata' : parse.metadata(metadataNode),
  4229. 'spine' : spine,
  4230. 'manifest' : manifest,
  4231. 'navPath' : navPath,
  4232. 'tocPath' : tocPath,
  4233. 'coverPath': coverPath,
  4234. 'spineNodeIndex' : spineNodeIndex,
  4235. 'spineIndexByURL' : spineIndexByURL
  4236. };
  4237. };
  4238. //-- Find TOC NAV: media-type="application/xhtml+xml" href="toc.ncx"
  4239. EPUBJS.Parser.prototype.findNavPath = function(manifestNode){
  4240. var node = manifestNode.querySelector("item[properties^='nav']");
  4241. return node ? node.getAttribute('href') : false;
  4242. };
  4243. //-- Find TOC NCX: media-type="application/x-dtbncx+xml" href="toc.ncx"
  4244. EPUBJS.Parser.prototype.findTocPath = function(manifestNode){
  4245. var node = manifestNode.querySelector("item[media-type='application/x-dtbncx+xml']");
  4246. return node ? node.getAttribute('href') : false;
  4247. };
  4248. //-- Find Cover: <item properties="cover-image" id="ci" href="cover.svg" media-type="image/svg+xml" />
  4249. EPUBJS.Parser.prototype.findCoverPath = function(manifestNode){
  4250. var node = manifestNode.querySelector("item[properties='cover-image']");
  4251. return node ? node.getAttribute('href') : false;
  4252. };
  4253. //-- Expanded to match Readium web components
  4254. EPUBJS.Parser.prototype.metadata = function(xml){
  4255. var metadata = {},
  4256. p = this;
  4257. metadata.bookTitle = p.getElementText(xml, 'title');
  4258. metadata.creator = p.getElementText(xml, 'creator');
  4259. metadata.description = p.getElementText(xml, 'description');
  4260. metadata.pubdate = p.getElementText(xml, 'date');
  4261. metadata.publisher = p.getElementText(xml, 'publisher');
  4262. metadata.identifier = p.getElementText(xml, "identifier");
  4263. metadata.language = p.getElementText(xml, "language");
  4264. metadata.rights = p.getElementText(xml, "rights");
  4265. metadata.modified_date = p.querySelectorText(xml, "meta[property='dcterms:modified']");
  4266. metadata.layout = p.querySelectorText(xml, "meta[property='rendition:layout']");
  4267. metadata.orientation = p.querySelectorText(xml, "meta[property='rendition:orientation']");
  4268. metadata.spread = p.querySelectorText(xml, "meta[property='rendition:spread']");
  4269. // metadata.page_prog_dir = packageXml.querySelector("spine").getAttribute("page-progression-direction");
  4270. return metadata;
  4271. };
  4272. EPUBJS.Parser.prototype.getElementText = function(xml, tag){
  4273. var found = xml.getElementsByTagNameNS("http://purl.org/dc/elements/1.1/", tag),
  4274. el;
  4275. if(!found || found.length === 0) return '';
  4276. el = found[0];
  4277. if(el.childNodes.length){
  4278. return el.childNodes[0].nodeValue;
  4279. }
  4280. return '';
  4281. };
  4282. EPUBJS.Parser.prototype.querySelectorText = function(xml, q){
  4283. var el = xml.querySelector(q);
  4284. if(el && el.childNodes.length){
  4285. return el.childNodes[0].nodeValue;
  4286. }
  4287. return '';
  4288. };
  4289. EPUBJS.Parser.prototype.manifest = function(manifestXml){
  4290. var baseUrl = this.baseUrl,
  4291. manifest = {};
  4292. //-- Turn items into an array
  4293. var selected = manifestXml.querySelectorAll("item"),
  4294. items = Array.prototype.slice.call(selected);
  4295. //-- Create an object with the id as key
  4296. items.forEach(function(item){
  4297. var id = item.getAttribute('id'),
  4298. href = item.getAttribute('href') || '',
  4299. type = item.getAttribute('media-type') || '',
  4300. properties = item.getAttribute('properties') || '';
  4301. manifest[id] = {
  4302. 'href' : href,
  4303. 'url' : baseUrl + href, //-- Absolute URL for loading with a web worker
  4304. 'type' : type,
  4305. 'properties' : properties
  4306. };
  4307. });
  4308. return manifest;
  4309. };
  4310. EPUBJS.Parser.prototype.spine = function(spineXml, manifest){
  4311. var spine = [];
  4312. var selected = spineXml.getElementsByTagName("itemref"),
  4313. items = Array.prototype.slice.call(selected);
  4314. var spineNodeIndex = Array.prototype.indexOf.call(spineXml.parentNode.childNodes, spineXml);
  4315. var epubcfi = new EPUBJS.EpubCFI();
  4316. //-- Add to array to mantain ordering and cross reference with manifest
  4317. items.forEach(function(item, index){
  4318. var Id = item.getAttribute('idref');
  4319. var cfiBase = epubcfi.generateChapterComponent(spineNodeIndex, index, Id);
  4320. var props = item.getAttribute('properties') || '';
  4321. var propArray = props.length ? props.split(' ') : [];
  4322. var manifestProps = manifest[Id].properties;
  4323. var manifestPropArray = manifestProps.length ? manifestProps.split(' ') : [];
  4324. var vert = {
  4325. 'id' : Id,
  4326. 'linear' : item.getAttribute('linear') || '',
  4327. 'properties' : propArray,
  4328. 'manifestProperties' : manifestPropArray,
  4329. 'href' : manifest[Id].href,
  4330. 'url' : manifest[Id].url,
  4331. 'index' : index,
  4332. 'cfiBase' : cfiBase,
  4333. 'cfi' : "epub(" + cfiBase + ")"
  4334. };
  4335. spine.push(vert);
  4336. });
  4337. return spine;
  4338. };
  4339. EPUBJS.Parser.prototype.nav = function(navHtml, spineIndexByURL, bookSpine){
  4340. var navEl = navHtml.querySelector('nav[*|type="toc"]'), //-- [*|type="toc"] * Doesn't seem to work
  4341. idCounter = 0;
  4342. if(!navEl) return [];
  4343. // Implements `> ol > li`
  4344. function findListItems(parent){
  4345. var items = [];
  4346. Array.prototype.slice.call(parent.childNodes).forEach(function(node){
  4347. if('ol' == node.tagName){
  4348. Array.prototype.slice.call(node.childNodes).forEach(function(item){
  4349. if('li' == item.tagName){
  4350. items.push(item);
  4351. }
  4352. });
  4353. }
  4354. });
  4355. return items;
  4356. }
  4357. // Implements `> a, > span`
  4358. function findAnchorOrSpan(parent){
  4359. var item = null;
  4360. Array.prototype.slice.call(parent.childNodes).forEach(function(node){
  4361. if('a' == node.tagName || 'span' == node.tagName){
  4362. item = node;
  4363. }
  4364. });
  4365. return item;
  4366. }
  4367. function getTOC(parent){
  4368. var list = [],
  4369. nodes = findListItems(parent),
  4370. items = Array.prototype.slice.call(nodes),
  4371. length = items.length,
  4372. node;
  4373. if(length === 0) return false;
  4374. items.forEach(function(item){
  4375. var id = item.getAttribute('id') || false,
  4376. content = findAnchorOrSpan(item),
  4377. href = content.getAttribute('href') || '',
  4378. text = content.textContent || "",
  4379. split = href.split("#"),
  4380. baseUrl = split[0],
  4381. subitems = getTOC(item),
  4382. spinePos = spineIndexByURL[baseUrl],
  4383. spineItem = bookSpine[spinePos],
  4384. cfi = spineItem ? spineItem.cfi : '';
  4385. if(!id) {
  4386. if(spinePos) {
  4387. spineItem = bookSpine[spinePos];
  4388. id = spineItem.id;
  4389. cfi = spineItem.cfi;
  4390. } else {
  4391. id = 'epubjs-autogen-toc-id-' + (idCounter++);
  4392. }
  4393. }
  4394. item.setAttribute('id', id); // Ensure all elements have an id
  4395. list.push({
  4396. "id": id,
  4397. "href": href,
  4398. "label": text,
  4399. "subitems" : subitems,
  4400. "parent" : parent ? parent.getAttribute('id') : null,
  4401. "cfi" : cfi
  4402. });
  4403. });
  4404. return list;
  4405. }
  4406. return getTOC(navEl);
  4407. };
  4408. EPUBJS.Parser.prototype.toc = function(tocXml, spineIndexByURL, bookSpine){
  4409. var navMap = tocXml.querySelector("navMap");
  4410. if(!navMap) return [];
  4411. function getTOC(parent){
  4412. var list = [],
  4413. nodes = parent.querySelectorAll("navPoint"),
  4414. items = Array.prototype.slice.call(nodes).reverse(),
  4415. length = items.length,
  4416. iter = length,
  4417. node;
  4418. if(length === 0) return [];
  4419. items.forEach(function(item){
  4420. var id = item.getAttribute('id') || false,
  4421. content = item.querySelector("content"),
  4422. src = content.getAttribute('src'),
  4423. navLabel = item.querySelector("navLabel"),
  4424. text = navLabel.textContent ? navLabel.textContent : "",
  4425. split = src.split("#"),
  4426. baseUrl = split[0],
  4427. spinePos = spineIndexByURL[baseUrl],
  4428. spineItem = bookSpine[spinePos],
  4429. subitems = getTOC(item),
  4430. cfi = spineItem ? spineItem.cfi : '';
  4431. if(!id) {
  4432. if(spinePos) {
  4433. spineItem = bookSpine[spinePos];
  4434. id = spineItem.id;
  4435. cfi = spineItem.cfi;
  4436. } else {
  4437. id = 'epubjs-autogen-toc-id-' + (idCounter++);
  4438. }
  4439. }
  4440. list.unshift({
  4441. "id": id,
  4442. "href": src,
  4443. "label": text,
  4444. "spinePos": spinePos,
  4445. "subitems" : subitems,
  4446. "parent" : parent ? parent.getAttribute('id') : null,
  4447. "cfi" : cfi
  4448. });
  4449. });
  4450. return list;
  4451. }
  4452. return getTOC(navMap);
  4453. };
  4454. EPUBJS.Parser.prototype.pageList = function(navHtml, spineIndexByURL, bookSpine){
  4455. var navEl = navHtml.querySelector('nav[*|type="page-list"]'),
  4456. idCounter = 0;
  4457. if(!navEl) return [];
  4458. // Implements `> ol > li`
  4459. function findListItems(parent){
  4460. var items = [];
  4461. Array.prototype.slice.call(parent.childNodes).forEach(function(node){
  4462. if('ol' == node.tagName){
  4463. Array.prototype.slice.call(node.childNodes).forEach(function(item){
  4464. if('li' == item.tagName){
  4465. items.push(item);
  4466. }
  4467. });
  4468. }
  4469. });
  4470. return items;
  4471. }
  4472. // Implements `> a, > span`
  4473. function findAnchorOrSpan(parent){
  4474. var item = null;
  4475. Array.prototype.slice.call(parent.childNodes).forEach(function(node){
  4476. if('a' == node.tagName || 'span' == node.tagName){
  4477. item = node;
  4478. }
  4479. });
  4480. return item;
  4481. }
  4482. function getPages(parent){
  4483. var list = [],
  4484. nodes = findListItems(parent),
  4485. items = Array.prototype.slice.call(nodes),
  4486. length = items.length,
  4487. node;
  4488. if(length === 0) return false;
  4489. items.forEach(function(item){
  4490. var id = item.getAttribute('id') || false,
  4491. content = findAnchorOrSpan(item),
  4492. href = content.getAttribute('href') || '',
  4493. text = content.textContent || "",
  4494. page = parseInt(text),
  4495. isCfi = href.indexOf("epubcfi"),
  4496. split,
  4497. packageUrl,
  4498. cfi;
  4499. if(isCfi != -1) {
  4500. split = href.split("#");
  4501. packageUrl = split[0];
  4502. cfi = split.length > 1 ? split[1] : false;
  4503. list.push({
  4504. "cfi" : cfi,
  4505. "href" : href,
  4506. "packageUrl" : packageUrl,
  4507. "page" : page
  4508. });
  4509. } else {
  4510. list.push({
  4511. "href" : href,
  4512. "page" : page
  4513. });
  4514. }
  4515. });
  4516. return list;
  4517. }
  4518. return getPages(navEl);
  4519. };
  4520. EPUBJS.Render.Iframe = function() {
  4521. this.iframe = null;
  4522. this.document = null;
  4523. this.window = null;
  4524. this.docEl = null;
  4525. this.bodyEl = null;
  4526. this.leftPos = 0;
  4527. this.pageWidth = 0;
  4528. };
  4529. //-- Build up any html needed
  4530. EPUBJS.Render.Iframe.prototype.create = function(){
  4531. this.iframe = document.createElement('iframe');
  4532. this.iframe.id = "epubjs-iframe:" + EPUBJS.core.uuid();
  4533. this.iframe.scrolling = "no";
  4534. this.iframe.seamless = "seamless";
  4535. // Back up if seamless isn't supported
  4536. this.iframe.style.border = "none";
  4537. this.iframe.addEventListener("load", this.loaded.bind(this), false);
  4538. return this.iframe;
  4539. };
  4540. /**
  4541. * Sets the source of the iframe with the given URL string
  4542. * Takes: URL string
  4543. * Returns: promise with document element
  4544. */
  4545. EPUBJS.Render.Iframe.prototype.load = function(url){
  4546. var render = this,
  4547. deferred = new RSVP.defer();
  4548. this.iframe.contentWindow.location.replace(url);
  4549. // Reset the scroll position
  4550. render.leftPos = 0;
  4551. if(this.window) {
  4552. this.unload();
  4553. }
  4554. this.iframe.onload = function(e) {
  4555. render.document = render.iframe.contentDocument;
  4556. render.docEl = render.document.documentElement;
  4557. render.headEl = render.document.head;
  4558. render.bodyEl = render.document.body;
  4559. render.window = render.iframe.contentWindow;
  4560. render.window.addEventListener("resize", render.resized.bind(render), false);
  4561. //-- Clear Margins
  4562. if(render.bodyEl) {
  4563. render.bodyEl.style.margin = "0";
  4564. }
  4565. deferred.resolve(render.docEl);
  4566. };
  4567. this.iframe.onerror = function(e) {
  4568. //console.error("Error Loading Contents", e);
  4569. deferred.reject({
  4570. message : "Error Loading Contents: " + e,
  4571. stack : new Error().stack
  4572. });
  4573. };
  4574. return deferred.promise;
  4575. };
  4576. EPUBJS.Render.Iframe.prototype.loaded = function(v){
  4577. var url = this.iframe.contentWindow.location.href;
  4578. if(url != "about:blank"){
  4579. this.trigger("render:loaded", url);
  4580. }
  4581. };
  4582. // Resize the iframe to the given width and height
  4583. EPUBJS.Render.Iframe.prototype.resize = function(width, height){
  4584. var iframeBox;
  4585. if(!this.iframe) return;
  4586. this.iframe.height = height;
  4587. if(!isNaN(width) && width % 2 !== 0){
  4588. width += 1; //-- Prevent cutting off edges of text in columns
  4589. }
  4590. this.iframe.width = width;
  4591. // Get the fractional height and width of the iframe
  4592. // Default to orginal if bounding rect is 0
  4593. this.width = this.iframe.getBoundingClientRect().width || width;
  4594. this.height = this.iframe.getBoundingClientRect().height || height;
  4595. };
  4596. EPUBJS.Render.Iframe.prototype.resized = function(e){
  4597. // Get the fractional height and width of the iframe
  4598. this.width = this.iframe.getBoundingClientRect().width;
  4599. this.height = this.iframe.getBoundingClientRect().height;
  4600. };
  4601. EPUBJS.Render.Iframe.prototype.totalWidth = function(){
  4602. return this.docEl.scrollWidth;
  4603. };
  4604. EPUBJS.Render.Iframe.prototype.totalHeight = function(){
  4605. return this.docEl.scrollHeight;
  4606. };
  4607. EPUBJS.Render.Iframe.prototype.setPageDimensions = function(pageWidth, pageHeight){
  4608. this.pageWidth = pageWidth;
  4609. this.pageHeight = pageHeight;
  4610. //-- Add a page to the width of the document to account an for odd number of pages
  4611. // this.docEl.style.width = this.docEl.scrollWidth + pageWidth + "px";
  4612. };
  4613. EPUBJS.Render.Iframe.prototype.setLeft = function(leftPos){
  4614. // this.bodyEl.style.marginLeft = -leftPos + "px";
  4615. // this.docEl.style.marginLeft = -leftPos + "px";
  4616. // this.docEl.style[EPUBJS.Render.Iframe.transform] = 'translate('+ (-leftPos) + 'px, 0)';
  4617. this.document.defaultView.scrollTo(leftPos, 0);
  4618. };
  4619. EPUBJS.Render.Iframe.prototype.setStyle = function(style, val, prefixed){
  4620. if(prefixed) {
  4621. style = EPUBJS.core.prefixed(style);
  4622. }
  4623. if(this.bodyEl) this.bodyEl.style[style] = val;
  4624. };
  4625. EPUBJS.Render.Iframe.prototype.removeStyle = function(style){
  4626. if(this.bodyEl) this.bodyEl.style[style] = '';
  4627. };
  4628. EPUBJS.Render.Iframe.prototype.addHeadTag = function(tag, attrs) {
  4629. var tagEl = document.createElement(tag);
  4630. for(var attr in attrs) {
  4631. tagEl[attr] = attrs[attr];
  4632. }
  4633. if(this.headEl) this.headEl.appendChild(tagEl);
  4634. };
  4635. EPUBJS.Render.Iframe.prototype.page = function(pg){
  4636. this.leftPos = this.pageWidth * (pg-1); //-- pages start at 1
  4637. this.setLeft(this.leftPos);
  4638. };
  4639. //-- Show the page containing an Element
  4640. EPUBJS.Render.Iframe.prototype.getPageNumberByElement = function(el){
  4641. var left, pg;
  4642. if(!el) return;
  4643. left = this.leftPos + el.getBoundingClientRect().left; //-- Calculate left offset compaired to scrolled position
  4644. pg = Math.floor(left / this.pageWidth) + 1; //-- pages start at 1
  4645. return pg;
  4646. };
  4647. //-- Show the page containing an Element
  4648. EPUBJS.Render.Iframe.prototype.getPageNumberByRect = function(boundingClientRect){
  4649. var left, pg;
  4650. left = this.leftPos + boundingClientRect.left; //-- Calculate left offset compaired to scrolled position
  4651. pg = Math.floor(left / this.pageWidth) + 1; //-- pages start at 1
  4652. return pg;
  4653. };
  4654. // Return the root element of the content
  4655. EPUBJS.Render.Iframe.prototype.getBaseElement = function(){
  4656. return this.bodyEl;
  4657. };
  4658. // Checks if an element is on the screen
  4659. EPUBJS.Render.Iframe.prototype.isElementVisible = function(el){
  4660. var rect;
  4661. var left;
  4662. if(el && typeof el.getBoundingClientRect === 'function'){
  4663. rect = el.getBoundingClientRect();
  4664. left = rect.left; //+ rect.width;
  4665. if( rect.width !== 0 &&
  4666. rect.height !== 0 && // Element not visible
  4667. left >= 0 &&
  4668. left < this.pageWidth ) {
  4669. return true;
  4670. }
  4671. }
  4672. return false;
  4673. };
  4674. EPUBJS.Render.Iframe.prototype.scroll = function(bool){
  4675. if(bool) {
  4676. this.iframe.scrolling = "yes";
  4677. } else {
  4678. this.iframe.scrolling = "no";
  4679. }
  4680. };
  4681. // Cleanup event listeners
  4682. EPUBJS.Render.Iframe.prototype.unload = function(){
  4683. this.window.removeEventListener("resize", this.resized);
  4684. };
  4685. //-- Enable binding events to Render
  4686. RSVP.EventTarget.mixin(EPUBJS.Render.Iframe.prototype);
  4687. EPUBJS.Renderer = function(renderMethod, hidden) {
  4688. // Dom events to listen for
  4689. this.listenedEvents = ["keydown", "keyup", "keypressed", "mouseup", "mousedown", "click"];
  4690. this.upEvent = "mouseup";
  4691. this.downEvent = "mousedown";
  4692. if('ontouchstart' in document.documentElement) {
  4693. this.listenedEvents.push("touchstart", "touchend");
  4694. this.upEvent = "touchend";
  4695. this.downEvent = "touchstart";
  4696. }
  4697. /**
  4698. * Setup a render method.
  4699. * Options are: Iframe
  4700. */
  4701. if(renderMethod && typeof(EPUBJS.Render[renderMethod]) != "undefined"){
  4702. this.render = new EPUBJS.Render[renderMethod]();
  4703. } else {
  4704. console.error("Not a Valid Rendering Method");
  4705. }
  4706. // Listen for load events
  4707. this.render.on("render:loaded", this.loaded.bind(this));
  4708. // Cached for replacement urls from storage
  4709. this.caches = {};
  4710. // Blank Cfi for Parsing
  4711. this.epubcfi = new EPUBJS.EpubCFI();
  4712. this.spreads = true;
  4713. this.isForcedSingle = false;
  4714. this.resized = _.debounce(this.onResized.bind(this), 100);
  4715. this.layoutSettings = {};
  4716. this.hidden = hidden || false;
  4717. //-- Adds Hook methods to the Book prototype
  4718. // Hooks will all return before triggering the callback.
  4719. EPUBJS.Hooks.mixin(this);
  4720. //-- Get pre-registered hooks for events
  4721. this.getHooks("beforeChapterDisplay");
  4722. //-- Queue up page changes if page map isn't ready
  4723. this._q = EPUBJS.core.queue(this);
  4724. this._moving = false;
  4725. };
  4726. //-- Renderer events for listening
  4727. EPUBJS.Renderer.prototype.Events = [
  4728. "renderer:keydown",
  4729. "renderer:keyup",
  4730. "renderer:keypressed",
  4731. "renderer:mouseup",
  4732. "renderer:mousedown",
  4733. "renderer:click",
  4734. "renderer:touchstart",
  4735. "renderer:touchend",
  4736. "renderer:selected",
  4737. "renderer:chapterUnloaded",
  4738. "renderer:chapterDisplayed",
  4739. "renderer:locationChanged",
  4740. "renderer:visibleLocationChanged",
  4741. "renderer:resized",
  4742. "renderer:spreads"
  4743. ];
  4744. /**
  4745. * Creates an element to render to.
  4746. * Resizes to passed width and height or to the elements size
  4747. */
  4748. EPUBJS.Renderer.prototype.initialize = function(element, width, height){
  4749. this.container = element;
  4750. this.element = this.render.create();
  4751. this.initWidth = width;
  4752. this.initHeight = height;
  4753. this.width = width || this.container.clientWidth;
  4754. this.height = height || this.container.clientHeight;
  4755. this.container.appendChild(this.element);
  4756. if(width && height){
  4757. this.render.resize(this.width, this.height);
  4758. } else {
  4759. this.render.resize('100%', '100%');
  4760. }
  4761. };
  4762. /**
  4763. * Display a chapter
  4764. * Takes: chapter object, global layout settings
  4765. * Returns: Promise with passed Renderer after pages has loaded
  4766. */
  4767. EPUBJS.Renderer.prototype.displayChapter = function(chapter, globalLayout){
  4768. var store = false;
  4769. if(this._moving) {
  4770. console.error("Rendering In Progress");
  4771. return;
  4772. }
  4773. this._moving = true;
  4774. // Get the url string from the chapter (may be from storage)
  4775. return chapter.url().
  4776. then(function(url) {
  4777. // Unload the previous chapter listener
  4778. if(this.currentChapter) {
  4779. this.currentChapter.unload(); // Remove stored blobs
  4780. if(this.render.window){
  4781. this.render.window.removeEventListener("resize", this.resized);
  4782. }
  4783. this.removeEventListeners();
  4784. this.removeSelectionListeners();
  4785. this.trigger("renderer:chapterUnloaded");
  4786. this.contents = null;
  4787. this.doc = null;
  4788. this.pageMap = null;
  4789. }
  4790. this.currentChapter = chapter;
  4791. this.chapterPos = 1;
  4792. this.currentChapterCfiBase = chapter.cfiBase;
  4793. this.layoutSettings = this.reconcileLayoutSettings(globalLayout, chapter.properties);
  4794. return this.load(url);
  4795. }.bind(this));
  4796. };
  4797. /**
  4798. * Loads a url (string) and renders it,
  4799. * attaching event listeners and triggering hooks.
  4800. * Returns: Promise with the rendered contents.
  4801. */
  4802. EPUBJS.Renderer.prototype.load = function(url){
  4803. var deferred = new RSVP.defer();
  4804. var loaded;
  4805. // Switch to the required layout method for the settings
  4806. this.layoutMethod = this.determineLayout(this.layoutSettings);
  4807. this.layout = new EPUBJS.Layout[this.layoutMethod]();
  4808. this.visible(false);
  4809. render = this.render.load(url);
  4810. render.then(function(contents) {
  4811. var formated;
  4812. this.currentChapter.contents = this.render.document;
  4813. this.contents = contents;
  4814. this.doc = this.render.document;
  4815. // Format the contents using the current layout method
  4816. this.formated = this.layout.format(contents, this.render.width, this.render.height, this.gap);
  4817. this.render.setPageDimensions(this.formated.pageWidth, this.formated.pageHeight);
  4818. // window.addEventListener("orientationchange", this.onResized.bind(this), false);
  4819. if(!this.initWidth && !this.initHeight){
  4820. this.render.window.addEventListener("resize", this.resized, false);
  4821. }
  4822. this.addEventListeners();
  4823. this.addSelectionListeners();
  4824. //-- Trigger registered hooks before displaying
  4825. this.beforeDisplay(function(){
  4826. var pages = this.layout.calculatePages();
  4827. var msg = this.currentChapter;
  4828. var queued = this._q.length();
  4829. this._moving = false;
  4830. this.updatePages(pages);
  4831. this.visibleRangeCfi = this.getVisibleRangeCfi();
  4832. this.currentLocationCfi = this.visibleRangeCfi.start;
  4833. if(queued === 0) {
  4834. this.trigger("renderer:locationChanged", this.currentLocationCfi);
  4835. this.trigger("renderer:visibleRangeChanged", this.visibleRangeCfi);
  4836. }
  4837. msg.cfi = this.currentLocationCfi; //TODO: why is this cfi passed to chapterDisplayed
  4838. this.trigger("renderer:chapterDisplayed", msg);
  4839. this.visible(true);
  4840. deferred.resolve(this); //-- why does this return the renderer?
  4841. }.bind(this));
  4842. }.bind(this));
  4843. return deferred.promise;
  4844. };
  4845. EPUBJS.Renderer.prototype.loaded = function(url){
  4846. this.trigger("render:loaded", url);
  4847. // var uri = EPUBJS.core.uri(url);
  4848. // var relative = uri.path.replace(book.bookUrl, '');
  4849. // console.log(url, uri, relative);
  4850. };
  4851. /**
  4852. * Reconciles the current chapters layout properies with
  4853. * the global layout properities.
  4854. * Takes: global layout settings object, chapter properties string
  4855. * Returns: Object with layout properties
  4856. */
  4857. EPUBJS.Renderer.prototype.reconcileLayoutSettings = function(global, chapter){
  4858. var settings = {};
  4859. //-- Get the global defaults
  4860. for (var attr in global) {
  4861. if (global.hasOwnProperty(attr)){
  4862. settings[attr] = global[attr];
  4863. }
  4864. }
  4865. //-- Get the chapter's display type
  4866. chapter.forEach(function(prop){
  4867. var rendition = prop.replace("rendition:", '');
  4868. var split = rendition.indexOf("-");
  4869. var property, value;
  4870. if(split != -1){
  4871. property = rendition.slice(0, split);
  4872. value = rendition.slice(split+1);
  4873. settings[property] = value;
  4874. }
  4875. });
  4876. return settings;
  4877. };
  4878. /**
  4879. * Uses the settings to determine which Layout Method is needed
  4880. * Triggers events based on the method choosen
  4881. * Takes: Layout settings object
  4882. * Returns: String of appropriate for EPUBJS.Layout function
  4883. */
  4884. EPUBJS.Renderer.prototype.determineLayout = function(settings){
  4885. // Default is layout: reflowable & spread: auto
  4886. var spreads = this.determineSpreads(this.minSpreadWidth);
  4887. var layoutMethod = spreads ? "ReflowableSpreads" : "Reflowable";
  4888. var scroll = false;
  4889. if(settings.layout === "pre-paginated") {
  4890. layoutMethod = "Fixed";
  4891. scroll = true;
  4892. spreads = false;
  4893. }
  4894. if(settings.layout === "reflowable" && settings.spread === "none") {
  4895. layoutMethod = "Reflowable";
  4896. scroll = false;
  4897. spreads = false;
  4898. }
  4899. if(settings.layout === "reflowable" && settings.spread === "both") {
  4900. layoutMethod = "ReflowableSpreads";
  4901. scroll = false;
  4902. spreads = true;
  4903. }
  4904. this.spreads = spreads;
  4905. this.render.scroll(scroll);
  4906. this.trigger("renderer:spreads", spreads);
  4907. return layoutMethod;
  4908. };
  4909. // Shortcut to trigger the hook before displaying the chapter
  4910. EPUBJS.Renderer.prototype.beforeDisplay = function(callback, renderer){
  4911. this.triggerHooks("beforeChapterDisplay", callback, this);
  4912. };
  4913. // Update the renderer with the information passed by the layout
  4914. EPUBJS.Renderer.prototype.updatePages = function(layout){
  4915. this.pageMap = this.mapPage();
  4916. // this.displayedPages = layout.displayedPages;
  4917. if (this.spreads) {
  4918. this.displayedPages = Math.ceil(this.pageMap.length / 2);
  4919. } else {
  4920. this.displayedPages = this.pageMap.length;
  4921. }
  4922. // this.currentChapter.pages = layout.pageCount;
  4923. this.currentChapter.pages = this.pageMap.length;
  4924. this._q.flush();
  4925. };
  4926. // Apply the layout again and jump back to the previous cfi position
  4927. EPUBJS.Renderer.prototype.reformat = function(){
  4928. var renderer = this;
  4929. var formated, pages;
  4930. if(!this.contents) return;
  4931. spreads = this.determineSpreads(this.minSpreadWidth);
  4932. // Only re-layout if the spreads have switched
  4933. if(spreads != this.spreads){
  4934. this.spreads = spreads;
  4935. this.layoutMethod = this.determineLayout(this.layoutSettings);
  4936. this.layout = new EPUBJS.Layout[this.layoutMethod]();
  4937. }
  4938. this.formated = this.layout.format(this.contents, this.render.width, this.render.height, this.gap);
  4939. this.render.setPageDimensions(this.formated.pageWidth, this.formated.pageHeight);
  4940. pages = renderer.layout.calculatePages();
  4941. renderer.updatePages(pages);
  4942. // Give the css styles time to update
  4943. clearTimeout(this.timeoutTillCfi);
  4944. this.timeoutTillCfi = setTimeout(function(){
  4945. //-- Go to current page after formating
  4946. if(renderer.currentLocationCfi){
  4947. renderer.gotoCfi(renderer.currentLocationCfi);
  4948. }
  4949. this.timeoutTillCfi = null;
  4950. }, 10);
  4951. };
  4952. // Hide and show the render's container .
  4953. EPUBJS.Renderer.prototype.visible = function(bool){
  4954. if(typeof(bool) === "undefined") {
  4955. return this.element.style.visibility;
  4956. }
  4957. if(bool === true && !this.hidden){
  4958. this.element.style.visibility = "visible";
  4959. }else if(bool === false){
  4960. this.element.style.visibility = "hidden";
  4961. }
  4962. };
  4963. // Remove the render element and clean up listeners
  4964. EPUBJS.Renderer.prototype.remove = function() {
  4965. if(this.render.window) {
  4966. this.render.unload();
  4967. this.render.window.removeEventListener("resize", this.resized);
  4968. this.removeEventListeners();
  4969. this.removeSelectionListeners();
  4970. }
  4971. this.container.removeChild(this.element);
  4972. };
  4973. //-- STYLES
  4974. EPUBJS.Renderer.prototype.applyStyles = function(styles) {
  4975. for (var style in styles) {
  4976. this.render.setStyle(style, styles[style]);
  4977. }
  4978. };
  4979. EPUBJS.Renderer.prototype.setStyle = function(style, val, prefixed){
  4980. this.render.setStyle(style, val, prefixed);
  4981. };
  4982. EPUBJS.Renderer.prototype.removeStyle = function(style){
  4983. this.render.removeStyle(style);
  4984. };
  4985. //-- HEAD TAGS
  4986. EPUBJS.Renderer.prototype.applyHeadTags = function(headTags) {
  4987. for ( var headTag in headTags ) {
  4988. this.render.addHeadTag(headTag, headTags[headTag]);
  4989. }
  4990. };
  4991. //-- NAVIGATION
  4992. EPUBJS.Renderer.prototype.page = function(pg){
  4993. if(!this.pageMap) {
  4994. console.warn("pageMap not set, queuing");
  4995. this._q.enqueue("page", arguments);
  4996. return true;
  4997. }
  4998. if(pg >= 1 && pg <= this.displayedPages){
  4999. this.chapterPos = pg;
  5000. this.render.page(pg);
  5001. this.visibleRangeCfi = this.getVisibleRangeCfi();
  5002. this.currentLocationCfi = this.visibleRangeCfi.start;
  5003. this.trigger("renderer:locationChanged", this.currentLocationCfi);
  5004. this.trigger("renderer:visibleRangeChanged", this.visibleRangeCfi);
  5005. return true;
  5006. }
  5007. //-- Return false if page is greater than the total
  5008. return false;
  5009. };
  5010. // Short cut to find next page's cfi starting at the last visible element
  5011. /*
  5012. EPUBJS.Renderer.prototype.nextPage = function(){
  5013. var pg = this.chapterPos + 1;
  5014. if(pg <= this.displayedPages){
  5015. this.chapterPos = pg;
  5016. this.render.page(pg);
  5017. this.currentLocationCfi = this.getPageCfi(this.visibileEl);
  5018. this.trigger("renderer:locationChanged", this.currentLocationCfi);
  5019. return true;
  5020. }
  5021. //-- Return false if page is greater than the total
  5022. return false;
  5023. };
  5024. */
  5025. EPUBJS.Renderer.prototype.nextPage = function(){
  5026. return this.page(this.chapterPos + 1);
  5027. };
  5028. EPUBJS.Renderer.prototype.prevPage = function(){
  5029. return this.page(this.chapterPos - 1);
  5030. };
  5031. //-- Show the page containing an Element
  5032. EPUBJS.Renderer.prototype.pageByElement = function(el){
  5033. var pg;
  5034. if(!el) return;
  5035. pg = this.render.getPageNumberByElement(el);
  5036. this.page(pg);
  5037. };
  5038. // Jump to the last page of the chapter
  5039. EPUBJS.Renderer.prototype.lastPage = function(){
  5040. if(this._moving) {
  5041. return this._q.enqueue("lastPage", arguments);
  5042. }
  5043. this.page(this.displayedPages);
  5044. };
  5045. // Jump to the first page of the chapter
  5046. EPUBJS.Renderer.prototype.firstPage = function(){
  5047. this.page(1);
  5048. };
  5049. //-- Find a section by fragement id
  5050. EPUBJS.Renderer.prototype.section = function(fragment){
  5051. var el = this.doc.getElementById(fragment),
  5052. left, pg;
  5053. if(el){
  5054. this.pageByElement(el);
  5055. }
  5056. };
  5057. EPUBJS.Renderer.prototype.firstElementisTextNode = function(node) {
  5058. var children = node.childNodes;
  5059. var leng = children.length;
  5060. if(leng &&
  5061. children[0] && // First Child
  5062. children[0].nodeType === 3 && // This is a textNodes
  5063. children[0].textContent.trim().length) { // With non whitespace or return charecters
  5064. return true;
  5065. }
  5066. return false;
  5067. };
  5068. // Walk the node tree from a start element to next visible element
  5069. EPUBJS.Renderer.prototype.walk = function(node, x, y) {
  5070. var r, children, leng,
  5071. startNode = node,
  5072. prevNode,
  5073. stack = [startNode];
  5074. var STOP = 10000, ITER=0;
  5075. while(!r && stack.length) {
  5076. node = stack.shift();
  5077. if( this.containsPoint(node, x, y) && this.firstElementisTextNode(node)) {
  5078. r = node;
  5079. }
  5080. if(!r && node && node.childElementCount > 0){
  5081. children = node.children;
  5082. if (children && children.length) {
  5083. leng = children.length ? children.length : 0;
  5084. } else {
  5085. return r;
  5086. }
  5087. for (var i = leng-1; i >= 0; i--) {
  5088. if(children[i] != prevNode) stack.unshift(children[i]);
  5089. }
  5090. }
  5091. if(!r && stack.length === 0 && startNode && startNode.parentNode !== null){
  5092. stack.push(startNode.parentNode);
  5093. prevNode = startNode;
  5094. startNode = startNode.parentNode;
  5095. }
  5096. ITER++;
  5097. if(ITER > STOP) {
  5098. console.error("ENDLESS LOOP");
  5099. break;
  5100. }
  5101. }
  5102. return r;
  5103. };
  5104. // Checks if an element is on the screen
  5105. EPUBJS.Renderer.prototype.containsPoint = function(el, x, y){
  5106. var rect;
  5107. var left;
  5108. if(el && typeof el.getBoundingClientRect === 'function'){
  5109. rect = el.getBoundingClientRect();
  5110. // console.log(el, rect, x, y);
  5111. if( rect.width !== 0 &&
  5112. rect.height !== 0 && // Element not visible
  5113. rect.left >= x &&
  5114. x <= rect.left + rect.width) {
  5115. return true;
  5116. }
  5117. }
  5118. return false;
  5119. };
  5120. EPUBJS.Renderer.prototype.textSprint = function(root, func) {
  5121. var treeWalker = document.createTreeWalker(root, NodeFilter.SHOW_TEXT, {
  5122. acceptNode: function (node) {
  5123. if ( ! /^\s*$/.test(node.data) ) {
  5124. return NodeFilter.FILTER_ACCEPT;
  5125. } else {
  5126. return NodeFilter.FILTER_REJECT;
  5127. }
  5128. }
  5129. }, false);
  5130. var node;
  5131. while ((node = treeWalker.nextNode())) {
  5132. func(node);
  5133. }
  5134. };
  5135. EPUBJS.Renderer.prototype.sprint = function(root, func) {
  5136. var treeWalker = document.createTreeWalker(root, NodeFilter.SHOW_ELEMENT, null, false);
  5137. var node;
  5138. while ((node = treeWalker.nextNode())) {
  5139. func(node);
  5140. }
  5141. };
  5142. EPUBJS.Renderer.prototype.mapPage = function(){
  5143. var renderer = this;
  5144. var map = [{ start: null, end: null }];
  5145. var root = this.render.getBaseElement();
  5146. var page = 1;
  5147. var width = this.layout.colWidth + this.layout.gap;
  5148. var offset = this.formated.pageWidth * (this.chapterPos-1);
  5149. var limit = (width * page) - offset;// (width * page) - offset;
  5150. var elLimit = 0;
  5151. var prevRange;
  5152. var cfi;
  5153. var check = function(node) {
  5154. var elPos;
  5155. var elRange;
  5156. var children = Array.prototype.slice.call(node.childNodes);
  5157. if (node.nodeType == Node.ELEMENT_NODE) {
  5158. // elPos = node.getBoundingClientRect();
  5159. elRange = document.createRange();
  5160. elRange.selectNodeContents(node);
  5161. elPos = elRange.getBoundingClientRect();
  5162. if(!elPos || (elPos.width === 0 && elPos.height === 0)) {
  5163. return;
  5164. }
  5165. //-- Element starts new Col
  5166. if(elPos.left > elLimit) {
  5167. children.forEach(function(node){
  5168. if(node.nodeType == Node.TEXT_NODE &&
  5169. node.textContent.trim().length) {
  5170. checkText(node);
  5171. }
  5172. });
  5173. }
  5174. //-- Element Spans new Col
  5175. if(elPos.right > elLimit) {
  5176. children.forEach(function(node){
  5177. if(node.nodeType == Node.TEXT_NODE &&
  5178. node.textContent.trim().length) {
  5179. checkText(node);
  5180. }
  5181. });
  5182. }
  5183. }
  5184. };
  5185. var checkText = function(node){
  5186. var ranges = renderer.splitTextNodeIntoWordsRanges(node);
  5187. ranges.forEach(function(range){
  5188. var pos = range.getBoundingClientRect();
  5189. if(!pos || (pos.width === 0 && pos.height === 0)) {
  5190. return;
  5191. }
  5192. if(pos.left + pos.width < limit) {
  5193. if(!map[page-1].start){
  5194. range.collapse(true);
  5195. cfi = renderer.currentChapter.cfiFromRange(range);
  5196. map[page-1].start = cfi;
  5197. }
  5198. } else {
  5199. if(prevRange){
  5200. prevRange.collapse(true);
  5201. cfi = renderer.currentChapter.cfiFromRange(prevRange);
  5202. map[page-1].end = cfi;
  5203. }
  5204. range.collapse(true);
  5205. cfi = renderer.currentChapter.cfiFromRange(range);
  5206. map.push({
  5207. start: cfi,
  5208. end: null
  5209. });
  5210. page += 1;
  5211. limit = (width * page) - offset;
  5212. elLimit = limit;
  5213. }
  5214. prevRange = range;
  5215. });
  5216. };
  5217. this.sprint(root, check);
  5218. // this.textSprint(root, checkText);
  5219. if(prevRange){
  5220. prevRange.collapse(true);
  5221. cfi = renderer.currentChapter.cfiFromRange(prevRange);
  5222. map[page-1].end = cfi;
  5223. }
  5224. // Handle empty map
  5225. if(map.length === 1 && !map[0].start) {
  5226. range = this.doc.createRange();
  5227. range.selectNodeContents(root);
  5228. range.collapse(true);
  5229. cfi = renderer.currentChapter.cfiFromRange(range);
  5230. map[0].start = cfi;
  5231. map[0].end = cfi;
  5232. }
  5233. // clean up
  5234. prevRange = null;
  5235. ranges = null;
  5236. range = null;
  5237. root = null;
  5238. return map;
  5239. };
  5240. EPUBJS.Renderer.prototype.splitTextNodeIntoWordsRanges = function(node){
  5241. var ranges = [];
  5242. var text = node.textContent.trim();
  5243. var range;
  5244. var rect;
  5245. var list;
  5246. pos = text.indexOf(" ");
  5247. if(pos === -1) {
  5248. range = this.doc.createRange();
  5249. range.selectNodeContents(node);
  5250. return [range];
  5251. }
  5252. range = this.doc.createRange();
  5253. range.setStart(node, 0);
  5254. range.setEnd(node, pos);
  5255. ranges.push(range);
  5256. range = false;
  5257. while ( pos != -1 ) {
  5258. pos = text.indexOf(" ", pos + 1);
  5259. if(pos > 0) {
  5260. if(range) {
  5261. range.setEnd(node, pos);
  5262. ranges.push(range);
  5263. }
  5264. range = this.doc.createRange();
  5265. range.setStart(node, pos+1);
  5266. }
  5267. }
  5268. if(range) {
  5269. range.setEnd(node, text.length);
  5270. ranges.push(range);
  5271. }
  5272. return ranges;
  5273. };
  5274. EPUBJS.Renderer.prototype.rangePosition = function(range){
  5275. var rect;
  5276. var list;
  5277. list = range.getClientRects();
  5278. if(list.length) {
  5279. rect = list[0];
  5280. return rect;
  5281. }
  5282. return null;
  5283. };
  5284. /*
  5285. // Get the cfi of the current page
  5286. EPUBJS.Renderer.prototype.getPageCfi = function(prevEl){
  5287. var range = this.doc.createRange();
  5288. var position;
  5289. // TODO : this might need to take margin / padding into account?
  5290. var x = 1;//this.formated.pageWidth/2;
  5291. var y = 1;//;this.formated.pageHeight/2;
  5292. range = this.getRange(x, y);
  5293. // var test = this.doc.defaultView.getSelection();
  5294. // var r = this.doc.createRange();
  5295. // test.removeAllRanges();
  5296. // r.setStart(range.startContainer, range.startOffset);
  5297. // r.setEnd(range.startContainer, range.startOffset + 1);
  5298. // test.addRange(r);
  5299. return this.currentChapter.cfiFromRange(range);
  5300. };
  5301. */
  5302. // Get the cfi of the current page
  5303. EPUBJS.Renderer.prototype.getPageCfi = function(){
  5304. var pg;
  5305. if (this.spreads) {
  5306. pg = this.chapterPos*2;
  5307. startRange = this.pageMap[pg-2];
  5308. } else {
  5309. pg = this.chapterPos;
  5310. startRange = this.pageMap[pg-1];
  5311. }
  5312. return this.pageMap[(this.chapterPos * 2) -1].start;
  5313. };
  5314. EPUBJS.Renderer.prototype.getRange = function(x, y, forceElement){
  5315. var range = this.doc.createRange();
  5316. var position;
  5317. forceElement = true; // temp override
  5318. if(typeof document.caretPositionFromPoint !== "undefined" && !forceElement){
  5319. position = this.doc.caretPositionFromPoint(x, y);
  5320. range.setStart(position.offsetNode, position.offset);
  5321. } else if(typeof document.caretRangeFromPoint !== "undefined" && !forceElement){
  5322. range = this.doc.caretRangeFromPoint(x, y);
  5323. } else {
  5324. this.visibileEl = this.findElementAfter(x, y);
  5325. range.setStart(this.visibileEl, 1);
  5326. }
  5327. // var test = this.doc.defaultView.getSelection();
  5328. // var r = this.doc.createRange();
  5329. // test.removeAllRanges();
  5330. // r.setStart(range.startContainer, range.startOffset);
  5331. // r.setEnd(range.startContainer, range.startOffset + 1);
  5332. // test.addRange(r);
  5333. return range;
  5334. };
  5335. /*
  5336. EPUBJS.Renderer.prototype.getVisibleRangeCfi = function(prevEl){
  5337. var startX = 0;
  5338. var startY = 0;
  5339. var endX = this.width-1;
  5340. var endY = this.height-1;
  5341. var startRange = this.getRange(startX, startY);
  5342. var endRange = this.getRange(endX, endY); //fix if carret not avail
  5343. var startCfi = this.currentChapter.cfiFromRange(startRange);
  5344. var endCfi;
  5345. if(endRange) {
  5346. endCfi = this.currentChapter.cfiFromRange(endRange);
  5347. }
  5348. return {
  5349. start: startCfi,
  5350. end: endCfi || false
  5351. };
  5352. };
  5353. */
  5354. EPUBJS.Renderer.prototype.pagesInCurrentChapter = function() {
  5355. var pgs;
  5356. var length;
  5357. if(!this.pageMap) {
  5358. console.warn("page map not loaded");
  5359. return false;
  5360. }
  5361. length = this.pageMap.length;
  5362. if(this.spreads){
  5363. pgs = Math.ceil(length / 2);
  5364. } else {
  5365. pgs = length;
  5366. }
  5367. return pgs;
  5368. };
  5369. EPUBJS.Renderer.prototype.currentRenderedPage = function(){
  5370. var pg;
  5371. if(!this.pageMap) {
  5372. console.warn("page map not loaded");
  5373. return false;
  5374. }
  5375. if (this.spreads && this.layout.pageCount > 1) {
  5376. pg = this.chapterPos*2;
  5377. } else {
  5378. pg = this.chapterPos;
  5379. }
  5380. return pg;
  5381. };
  5382. EPUBJS.Renderer.prototype.getRenderedPagesLeft = function(){
  5383. var pg;
  5384. var lastPage;
  5385. var pagesLeft;
  5386. if(!this.pageMap) {
  5387. console.warn("page map not loaded");
  5388. return false;
  5389. }
  5390. lastPage = this.pageMap.length;
  5391. if (this.spreads) {
  5392. pg = this.chapterPos*2;
  5393. } else {
  5394. pg = this.chapterPos;
  5395. }
  5396. pagesLeft = lastPage - pg;
  5397. return pagesLeft;
  5398. };
  5399. EPUBJS.Renderer.prototype.getVisibleRangeCfi = function(){
  5400. var pg;
  5401. var startRange, endRange;
  5402. if(!this.pageMap) {
  5403. console.warn("page map not loaded");
  5404. return false;
  5405. }
  5406. if (this.spreads) {
  5407. pg = this.chapterPos*2;
  5408. startRange = this.pageMap[pg-2];
  5409. endRange = startRange;
  5410. if(this.layout.pageCount > 1) {
  5411. endRange = this.pageMap[pg-1];
  5412. }
  5413. } else {
  5414. pg = this.chapterPos;
  5415. startRange = this.pageMap[pg-1];
  5416. endRange = startRange;
  5417. }
  5418. if(!startRange) {
  5419. console.warn("page range miss:", pg, this.pageMap);
  5420. startRange = this.pageMap[this.pageMap.length-1];
  5421. endRange = startRange;
  5422. }
  5423. return {
  5424. start: startRange.start,
  5425. end: endRange.end
  5426. };
  5427. };
  5428. // Goto a cfi position in the current chapter
  5429. EPUBJS.Renderer.prototype.gotoCfi = function(cfi){
  5430. var pg;
  5431. var marker;
  5432. var range;
  5433. if(this._moving){
  5434. return this._q.enqueue("gotoCfi", arguments);
  5435. }
  5436. if(_.isString(cfi)){
  5437. cfi = this.epubcfi.parse(cfi);
  5438. }
  5439. if(typeof document.evaluate === 'undefined') {
  5440. marker = this.epubcfi.addMarker(cfi, this.doc);
  5441. if(marker) {
  5442. pg = this.render.getPageNumberByElement(marker);
  5443. // Must Clean up Marker before going to page
  5444. this.epubcfi.removeMarker(marker, this.doc);
  5445. this.page(pg);
  5446. }
  5447. } else {
  5448. range = this.epubcfi.generateRangeFromCfi(cfi, this.doc);
  5449. if(range) {
  5450. pg = this.render.getPageNumberByRect(range.getBoundingClientRect());
  5451. this.page(pg);
  5452. }
  5453. }
  5454. };
  5455. // Walk nodes until a visible element is found
  5456. EPUBJS.Renderer.prototype.findFirstVisible = function(startEl){
  5457. var el = startEl || this.render.getBaseElement();
  5458. var found;
  5459. found = this.walk(el);
  5460. if(found) {
  5461. return found;
  5462. }else{
  5463. return startEl;
  5464. }
  5465. };
  5466. // TODO: remove me - unsused
  5467. EPUBJS.Renderer.prototype.findElementAfter = function(x, y, startEl){
  5468. var el = startEl || this.render.getBaseElement();
  5469. var found;
  5470. found = this.walk(el, x, y);
  5471. if(found) {
  5472. return found;
  5473. }else{
  5474. return el;
  5475. }
  5476. };
  5477. /*
  5478. EPUBJS.Renderer.prototype.route = function(hash, callback){
  5479. var location = window.location.hash.replace('#/', '');
  5480. if(this.useHash && location.length && location != this.prevLocation){
  5481. this.show(location, callback);
  5482. this.prevLocation = location;
  5483. return true;
  5484. }
  5485. return false;
  5486. }
  5487. EPUBJS.Renderer.prototype.hideHashChanges = function(){
  5488. this.useHash = false;
  5489. }
  5490. */
  5491. EPUBJS.Renderer.prototype.resize = function(width, height, setSize){
  5492. var spreads;
  5493. this.width = width;
  5494. this.height = height;
  5495. if(setSize !== false) {
  5496. this.render.resize(this.width, this.height);
  5497. }
  5498. if(this.contents){
  5499. this.reformat();
  5500. }
  5501. this.trigger("renderer:resized", {
  5502. width: this.width,
  5503. height: this.height
  5504. });
  5505. };
  5506. //-- Listeners for events in the frame
  5507. EPUBJS.Renderer.prototype.onResized = function(e) {
  5508. var width = this.container.clientWidth;
  5509. var height = this.container.clientHeight;
  5510. this.resize(width, height, false);
  5511. };
  5512. EPUBJS.Renderer.prototype.addEventListeners = function(){
  5513. if(!this.render.document) {
  5514. return;
  5515. }
  5516. this.listenedEvents.forEach(function(eventName){
  5517. this.render.document.addEventListener(eventName, this.triggerEvent.bind(this), false);
  5518. }, this);
  5519. };
  5520. EPUBJS.Renderer.prototype.removeEventListeners = function(){
  5521. if(!this.render.document) {
  5522. return;
  5523. }
  5524. this.listenedEvents.forEach(function(eventName){
  5525. this.render.document.removeEventListener(eventName, this.triggerEvent, false);
  5526. }, this);
  5527. };
  5528. // Pass browser events
  5529. EPUBJS.Renderer.prototype.triggerEvent = function(e){
  5530. this.trigger("renderer:"+e.type, e);
  5531. };
  5532. EPUBJS.Renderer.prototype.addSelectionListeners = function(){
  5533. this.render.document.addEventListener("selectionchange", this.onSelectionChange.bind(this), false);
  5534. };
  5535. EPUBJS.Renderer.prototype.removeSelectionListeners = function(){
  5536. if(!this.render.document) {
  5537. return;
  5538. }
  5539. this.doc.removeEventListener("selectionchange", this.onSelectionChange, false);
  5540. };
  5541. EPUBJS.Renderer.prototype.onSelectionChange = function(e){
  5542. if (this.selectionEndTimeout) {
  5543. clearTimeout(this.selectionEndTimeout);
  5544. }
  5545. this.selectionEndTimeout = setTimeout(function() {
  5546. this.selectedRange = this.render.window.getSelection();
  5547. this.trigger("renderer:selected", this.selectedRange);
  5548. }.bind(this), 500);
  5549. };
  5550. //-- Spreads
  5551. EPUBJS.Renderer.prototype.setMinSpreadWidth = function(width){
  5552. this.minSpreadWidth = width;
  5553. this.spreads = this.determineSpreads(width);
  5554. };
  5555. EPUBJS.Renderer.prototype.determineSpreads = function(cutoff){
  5556. if(this.isForcedSingle || !cutoff || this.width < cutoff) {
  5557. return false; //-- Single Page
  5558. }else{
  5559. return true; //-- Double Page
  5560. }
  5561. };
  5562. EPUBJS.Renderer.prototype.forceSingle = function(bool){
  5563. if(bool) {
  5564. this.isForcedSingle = true;
  5565. // this.spreads = false;
  5566. } else {
  5567. this.isForcedSingle = false;
  5568. // this.spreads = this.determineSpreads(this.minSpreadWidth);
  5569. }
  5570. };
  5571. EPUBJS.Renderer.prototype.setGap = function(gap){
  5572. this.gap = gap; //-- False == auto gap
  5573. };
  5574. //-- Content Replacements
  5575. EPUBJS.Renderer.prototype.replace = function(query, func, finished, progress){
  5576. var items = this.contents.querySelectorAll(query),
  5577. resources = Array.prototype.slice.call(items),
  5578. count = resources.length;
  5579. if(count === 0) {
  5580. finished(false);
  5581. return;
  5582. }
  5583. resources.forEach(function(item){
  5584. var called = false;
  5585. var after = function(result, full){
  5586. if(called === false) {
  5587. count--;
  5588. if(progress) progress(result, full, count);
  5589. if(count <= 0 && finished) finished(true);
  5590. called = true;
  5591. }
  5592. };
  5593. func(item, after);
  5594. }.bind(this));
  5595. };
  5596. EPUBJS.Renderer.prototype.replaceWithStored = function(query, attr, func, callback) {
  5597. var _oldUrls,
  5598. _newUrls = {},
  5599. _store = this.currentChapter.store,
  5600. _cache = this.caches[query],
  5601. _uri = EPUBJS.core.uri(this.currentChapter.absolute),
  5602. _chapterBase = _uri.base,
  5603. _attr = attr,
  5604. _wait = 2000,
  5605. progress = function(url, full, count) {
  5606. _newUrls[full] = url;
  5607. },
  5608. finished = function(notempty) {
  5609. if(callback) callback();
  5610. _.each(_oldUrls, function(url){
  5611. _store.revokeUrl(url);
  5612. });
  5613. _cache = _newUrls;
  5614. };
  5615. if(!_store) return;
  5616. if(!_cache) _cache = {};
  5617. _oldUrls = _.clone(_cache);
  5618. this.replace(query, function(link, done){
  5619. var src = link.getAttribute(_attr),
  5620. full = EPUBJS.core.resolveUrl(_chapterBase, src);
  5621. var replaceUrl = function(url) {
  5622. var timeout;
  5623. link.onload = function(){
  5624. clearTimeout(timeout);
  5625. done(url, full);
  5626. };
  5627. link.onerror = function(e){
  5628. clearTimeout(timeout);
  5629. done(url, full);
  5630. console.error(e);
  5631. };
  5632. if(query == "image") {
  5633. //-- SVG needs this to trigger a load event
  5634. link.setAttribute("externalResourcesRequired", "true");
  5635. }
  5636. if(query == "link[href]" && link.getAttribute("rel") !== "stylesheet") {
  5637. //-- Only Stylesheet links seem to have a load events, just continue others
  5638. done(url, full);
  5639. }
  5640. link.setAttribute(_attr, url);
  5641. //-- If elements never fire Load Event, should continue anyways
  5642. timeout = setTimeout(function(){
  5643. done(url, full);
  5644. }, _wait);
  5645. };
  5646. if(full in _oldUrls){
  5647. replaceUrl(_oldUrls[full]);
  5648. _newUrls[full] = _oldUrls[full];
  5649. delete _oldUrls[full];
  5650. }else{
  5651. func(_store, full, replaceUrl, link);
  5652. }
  5653. }, finished, progress);
  5654. };
  5655. //-- Enable binding events to Renderer
  5656. RSVP.EventTarget.mixin(EPUBJS.Renderer.prototype);
  5657. var EPUBJS = EPUBJS || {};
  5658. EPUBJS.replace = {};
  5659. //-- Replaces the relative links within the book to use our internal page changer
  5660. EPUBJS.replace.hrefs = function(callback, renderer){
  5661. var book = this;
  5662. var replacments = function(link, done){
  5663. var href = link.getAttribute("href"),
  5664. isRelative = href.search("://"),
  5665. directory,
  5666. relative;
  5667. if(isRelative != -1){
  5668. link.setAttribute("target", "_blank");
  5669. }else{
  5670. directory = EPUBJS.core.uri(renderer.render.window.location.href).directory;
  5671. relative = EPUBJS.core.resolveUrl(directory, href);
  5672. link.onclick = function(){
  5673. book.goto(relative);
  5674. return false;
  5675. };
  5676. }
  5677. done();
  5678. };
  5679. renderer.replace("a[href]", replacments, callback);
  5680. };
  5681. EPUBJS.replace.head = function(callback, renderer) {
  5682. renderer.replaceWithStored("link[href]", "href", EPUBJS.replace.links, callback);
  5683. };
  5684. //-- Replaces assets src's to point to stored version if browser is offline
  5685. EPUBJS.replace.resources = function(callback, renderer){
  5686. //srcs = this.doc.querySelectorAll('[src]');
  5687. renderer.replaceWithStored("[src]", "src", EPUBJS.replace.srcs, callback);
  5688. };
  5689. EPUBJS.replace.svg = function(callback, renderer) {
  5690. renderer.replaceWithStored("image", "xlink:href", function(_store, full, done){
  5691. _store.getUrl(full).then(done);
  5692. }, callback);
  5693. };
  5694. EPUBJS.replace.srcs = function(_store, full, done){
  5695. _store.getUrl(full).then(done);
  5696. };
  5697. //-- Replaces links in head, such as stylesheets - link[href]
  5698. EPUBJS.replace.links = function(_store, full, done, link){
  5699. //-- Handle replacing urls in CSS
  5700. if(link.getAttribute("rel") === "stylesheet") {
  5701. EPUBJS.replace.stylesheets(_store, full).then(function(url, full){
  5702. // done
  5703. setTimeout(function(){
  5704. done(url, full);
  5705. }, 5); //-- Allow for css to apply before displaying chapter
  5706. });
  5707. }else{
  5708. _store.getUrl(full).then(done);
  5709. }
  5710. };
  5711. EPUBJS.replace.stylesheets = function(_store, full) {
  5712. var deferred = new RSVP.defer();
  5713. if(!_store) return;
  5714. _store.getText(full).then(function(text){
  5715. var url;
  5716. EPUBJS.replace.cssUrls(_store, full, text).then(function(newText){
  5717. var _URL = window.URL || window.webkitURL || window.mozURL;
  5718. var blob = new Blob([newText], { "type" : "text\/css" }),
  5719. url = _URL.createObjectURL(blob);
  5720. deferred.resolve(url);
  5721. }, function(e) {
  5722. console.error(e);
  5723. });
  5724. });
  5725. return deferred.promise;
  5726. };
  5727. EPUBJS.replace.cssUrls = function(_store, base, text){
  5728. var deferred = new RSVP.defer(),
  5729. promises = [],
  5730. matches = text.match(/url\(\'?\"?([^\'|^\"^\)]*)\'?\"?\)/g);
  5731. if(!_store) return;
  5732. if(!matches){
  5733. deferred.resolve(text);
  5734. return deferred.promise;
  5735. }
  5736. matches.forEach(function(str){
  5737. var full = EPUBJS.core.resolveUrl(base, str.replace(/url\(|[|\)|\'|\"]/g, ''));
  5738. var replaced = _store.getUrl(full).then(function(url){
  5739. text = text.replace(str, 'url("'+url+'")');
  5740. });
  5741. promises.push(replaced);
  5742. });
  5743. RSVP.all(promises).then(function(){
  5744. deferred.resolve(text);
  5745. });
  5746. return deferred.promise;
  5747. };
  5748. EPUBJS.Unarchiver = function(url){
  5749. this.libPath = EPUBJS.filePath;
  5750. this.zipUrl = url;
  5751. this.loadLib();
  5752. this.urlCache = {};
  5753. this.zipFs = new zip.fs.FS();
  5754. return this.promise;
  5755. };
  5756. //-- Load the zip lib and set the workerScriptsPath
  5757. EPUBJS.Unarchiver.prototype.loadLib = function(callback){
  5758. if(typeof(zip) == "undefined") console.error("Zip lib not loaded");
  5759. /*
  5760. //-- load script
  5761. EPUBJS.core.loadScript(this.libPath+"zip.js", function(){
  5762. //-- Tell zip where it is located
  5763. zip.workerScriptsPath = this.libPath;
  5764. callback();
  5765. }.bind(this));
  5766. */
  5767. // console.log(this.libPath)
  5768. zip.workerScriptsPath = this.libPath;
  5769. };
  5770. EPUBJS.Unarchiver.prototype.openZip = function(zipUrl, callback){
  5771. var deferred = new RSVP.defer();
  5772. var zipFs = this.zipFs;
  5773. zipFs.importHttpContent(zipUrl, false, function() {
  5774. deferred.resolve(zipFs);
  5775. }, this.failed);
  5776. return deferred.promise;
  5777. };
  5778. EPUBJS.Unarchiver.prototype.getXml = function(url, encoding){
  5779. return this.getText(url, encoding).
  5780. then(function(text){
  5781. var parser = new DOMParser();
  5782. return parser.parseFromString(text, "application/xml");
  5783. });
  5784. };
  5785. EPUBJS.Unarchiver.prototype.getUrl = function(url, mime){
  5786. var unarchiver = this;
  5787. var deferred = new RSVP.defer();
  5788. var decodededUrl = window.decodeURIComponent(url);
  5789. var entry = this.zipFs.find(decodededUrl);
  5790. var _URL = window.URL || window.webkitURL || window.mozURL;
  5791. if(!entry) {
  5792. deferred.reject({
  5793. message : "File not found in the epub: " + url,
  5794. stack : new Error().stack
  5795. });
  5796. return deferred.promise;
  5797. }
  5798. if(url in this.urlCache) {
  5799. deferred.resolve(this.urlCache[url]);
  5800. return deferred.promise;
  5801. }
  5802. entry.getBlob(mime || zip.getMimeType(entry.name), function(blob){
  5803. var tempUrl = _URL.createObjectURL(blob);
  5804. deferred.resolve(tempUrl);
  5805. unarchiver.urlCache[url] = tempUrl;
  5806. });
  5807. return deferred.promise;
  5808. };
  5809. EPUBJS.Unarchiver.prototype.getText = function(url, encoding){
  5810. var unarchiver = this;
  5811. var deferred = new RSVP.defer();
  5812. var decodededUrl = window.decodeURIComponent(url);
  5813. var entry = this.zipFs.find(decodededUrl);
  5814. var _URL = window.URL || window.webkitURL || window.mozURL;
  5815. if(!entry) {
  5816. console.warn("File not found in the contained epub:", url);
  5817. return deferred.promise;
  5818. }
  5819. entry.getText(function(text){
  5820. deferred.resolve(text);
  5821. }, null, null, encoding || 'UTF-8');
  5822. return deferred.promise;
  5823. };
  5824. EPUBJS.Unarchiver.prototype.revokeUrl = function(url){
  5825. var _URL = window.URL || window.webkitURL || window.mozURL;
  5826. var fromCache = unarchiver.urlCache[url];
  5827. if(fromCache) _URL.revokeObjectURL(fromCache);
  5828. };
  5829. EPUBJS.Unarchiver.prototype.failed = function(error){
  5830. console.error(error);
  5831. };
  5832. EPUBJS.Unarchiver.prototype.afterSaved = function(error){
  5833. this.callback();
  5834. };
  5835. EPUBJS.Unarchiver.prototype.toStorage = function(entries){
  5836. var timeout = 0,
  5837. delay = 20,
  5838. that = this,
  5839. count = entries.length;
  5840. function callback(){
  5841. count--;
  5842. if(count === 0) that.afterSaved();
  5843. }
  5844. entries.forEach(function(entry){
  5845. setTimeout(function(entry){
  5846. that.saveEntryFileToStorage(entry, callback);
  5847. }, timeout, entry);
  5848. timeout += delay;
  5849. });
  5850. console.log("time", timeout);
  5851. //entries.forEach(this.saveEntryFileToStorage.bind(this));
  5852. };
  5853. EPUBJS.Unarchiver.prototype.saveEntryFileToStorage = function(entry, callback){
  5854. var that = this;
  5855. entry.getData(new zip.BlobWriter(), function(blob) {
  5856. EPUBJS.storage.save(entry.filename, blob, callback);
  5857. });
  5858. };
  5859. //# sourceMappingURL=epub.js.map