1
0
Fork 0
mirror of https://github.com/futurepress/epub.js.git synced 2025-10-02 14:49:16 +02:00
This commit is contained in:
Valdrin Trena 2024-06-08 20:14:31 +02:00
parent f09089cf77
commit 7c7c554ee7
84 changed files with 13879 additions and 14510 deletions

View file

@ -1,11 +1,14 @@
{
"presets": [
["@babel/preset-env", {
"targets": "last 2 Chrome versions, last 2 Safari versions, last 2 ChromeAndroid versions, last 2 iOS versions, last 2 Firefox versions, last 2 Edge versions",
"corejs": 3,
"useBuiltIns": "usage",
"bugfixes": true,
"modules": "auto"
}]
],
"presets": [
[
"@babel/preset-env",
{
"targets": "last 2 Chrome versions, last 2 Safari versions, last 2 ChromeAndroid versions, last 2 iOS versions, last 2 Firefox versions, last 2 Edge versions",
"corejs": 3,
"useBuiltIns": "usage",
"bugfixes": true,
"modules": "auto"
}
]
]
}

View file

@ -1,43 +1,25 @@
module.exports = {
"env": {
"browser": true,
"commonjs": true,
"es6": true,
"node": true
},
"globals": {
"ePub": true,
"JSZip": true
},
"extends": "eslint:recommended",
"parserOptions": {
"sourceType": "module"
},
"rules": {
"indent": [
"error",
"tab",
{ "VariableDeclarator": { "var": 2, "let": 2, "const": 3 } }
],
"linebreak-style": [
"error",
"unix"
],
"quotes": [
"warn",
"double"
],
"semi": [
"error",
"always"
],
"no-unused-vars" : ["warn"],
"no-console" : ["warn"],
"no-unused-vars": [
"error",
{ "vars": "all", "args": "none" }
],
"no-mixed-spaces-and-tabs": ["error", "smart-tabs"],
"valid-jsdoc": ["warn"]
}
env: {
browser: true,
commonjs: true,
es6: true,
node: true,
},
globals: {
ePub: true,
JSZip: true,
},
extends: "eslint:recommended",
parserOptions: {
sourceType: "module",
},
rules: {
"linebreak-style": ["error", "unix"],
quotes: ["warn", "double"],
semi: ["error", "always"],
"no-console": ["warn"],
"no-unused-vars": ["error", { vars: "all", args: "none" }],
"no-mixed-spaces-and-tabs": ["error", "smart-tabs"],
"no-prototype-builtins": "off",
},
};

2
.gitignore vendored
View file

@ -8,4 +8,4 @@ books
lib
dist
documentation/html
types/*.js
types/*.js

View file

@ -2,10 +2,8 @@
"browser": true,
"devel": true,
"worker": true,
"trailing": true,
"strict": false,
"boss": true,
"funcscope": true,
"globalstrict": true,
@ -14,10 +12,9 @@
"nonstandard": true,
"sub": true,
"validthis": true,
"globals": {
"_": false,
"define" : false,
"module" : false
"define": false,
"module": false
}
}

View file

@ -1,6 +1,3 @@
{
"ignore_dirs": [
".git",
"node_modules"
]
"ignore_dirs": [".git", "node_modules"]
}

View file

@ -43,7 +43,7 @@ Create the new ePub, and then render it to that element:
```html
<script>
var book = ePub("url/to/book/package.opf");
var rendition = book.renderTo("area", {width: 600, height: 400});
var rendition = book.renderTo("area", { width: 600, height: 400 });
var displayed = rendition.display();
</script>
```
@ -65,6 +65,7 @@ The default manager only displays a single section at a time.
```js
book.renderTo("area", { method: "continuous", width: "100%", height: "100%" });
```
[View example](http://futurepress.github.io/epub.js/examples/continuous-scrolled.html)
The continuous manager will display as many sections as need to fill the screen, and preload the next section offscreen. This enables seamless swiping / scrolling between pages on mobile and desktop, but is less performant than the default method.
@ -72,6 +73,7 @@ The continuous manager will display as many sections as need to fill the screen,
## Flow Overrides
### Auto (Default)
`book.renderTo("area", { flow: "auto", width: "900", height: "600" });`
Flow will be based on the settings in the OPF, defaults to `paginated`.
@ -90,7 +92,7 @@ Scrolled: `book.renderTo("area", { flow: "scrolled-doc" });`
## Scripted Content
[Scripted content](https://www.w3.org/TR/epub-33/#sec-scripted-content), JavasScript the ePub HTML content, is disabled by default due to the potential for executing malicious content.
[Scripted content](https://www.w3.org/TR/epub-33/#sec-scripted-content), JavasScript the ePub HTML content, is disabled by default due to the potential for executing malicious content.
This is done by sandboxing the iframe the content is rendered into, though it is still recommended to sanitize the ePub content server-side as well.
@ -101,7 +103,7 @@ If a trusted ePub contains interactivity, it can be enabled by passing `allowScr
var rendition = book.renderTo("area", {
width: 600,
height: 400,
allowScriptedContent: true
allowScriptedContent: true,
});
</script>
```
@ -132,11 +134,11 @@ npm start
## Examples
+ [Spreads](http://futurepress.github.io/epub.js/examples/spreads.html)
+ [Scrolled](http://futurepress.github.io/epub.js/examples/scrolled.html)
+ [Swipe](http://futurepress.github.io/epub.js/examples/swipe.html)
+ [Input](http://futurepress.github.io/epub.js/examples/input.html)
+ [Highlights](http://futurepress.github.io/epub.js/examples/highlights.html)
- [Spreads](http://futurepress.github.io/epub.js/examples/spreads.html)
- [Scrolled](http://futurepress.github.io/epub.js/examples/scrolled.html)
- [Swipe](http://futurepress.github.io/epub.js/examples/swipe.html)
- [Input](http://futurepress.github.io/epub.js/examples/input.html)
- [Highlights](http://futurepress.github.io/epub.js/examples/highlights.html)
[View All Examples](http://futurepress.github.io/epub.js/examples/)
@ -175,29 +177,28 @@ Hooks require an event to register to and a can return a promise to block until
Example hook:
```javascript
rendition.hooks.content.register(function(contents, view) {
rendition.hooks.content.register(function (contents, view) {
var elements = contents.document.querySelectorAll("[video]");
var items = Array.prototype.slice.call(elements);
var elements = contents.document.querySelectorAll('[video]');
var items = Array.prototype.slice.call(elements);
items.forEach(function(item){
// do something with the video item
});
})
items.forEach(function (item) {
// do something with the video item
});
});
```
The parts of the rendering process that can be hooked into are below.
```js
book.spine.hooks.serialize // Section is being converted to text
book.spine.hooks.content // Section has been loaded and parsed
rendition.hooks.render // Section is rendered to the screen
rendition.hooks.content // Section contents have been loaded
rendition.hooks.unloaded // Section contents are being unloaded
book.spine.hooks.serialize; // Section is being converted to text
book.spine.hooks.content; // Section has been loaded and parsed
rendition.hooks.render; // Section is rendered to the screen
rendition.hooks.content; // Section contents have been loaded
rendition.hooks.unloaded; // Section contents are being unloaded
```
## Reader
The reader has moved to its own repo at: https://github.com/futurepress/epubjs-reader/
## Additional Resources
@ -210,7 +211,7 @@ IRC Server: freenode.net Channel: #epub.js
Follow us on twitter: @Epubjs
+ http://twitter.com/#!/Epubjs
- http://twitter.com/#!/Epubjs
## Other

View file

@ -1,19 +1,11 @@
{
"name": "epubjs",
"version": "0.3.0",
"authors": [
"Fred Chasen <fchasen@gmail.com>"
],
"authors": ["Fred Chasen <fchasen@gmail.com>"],
"description": "Enhanced eBooks in the browser.",
"main": "dist/epub.js",
"moduleType": [
"amd",
"globals",
"node"
],
"keywords": [
"epub"
],
"moduleType": ["amd", "globals", "node"],
"keywords": ["epub"],
"license": "MIT",
"homepage": "http://futurepress.org",
"ignore": [

View file

@ -3,115 +3,75 @@ webpackConfig.mode = "development";
webpackConfig.externals = {};
webpackConfig.module.rules.push({
test: /\.xhtml$/i,
use: 'raw-loader',
use: "raw-loader",
});
module.exports = function(config) {
module.exports = function (config) {
config.set({
// base path that will be used to resolve all patterns (eg. files, exclude)
basePath: '',
basePath: "",
// frameworks to use
// available frameworks: https://npmjs.org/browse/keyword/karma-adapter
frameworks: ['mocha'],
frameworks: ["mocha"],
// list of files / patterns to load in the browser
files: [
{ pattern: "src/*.js", watched: true, included: false, served: false },
{pattern: 'src/*.js', watched: true, included: false, served: false},
{ pattern: "test/*.js", watched: false },
{pattern: 'test/*.js', watched: false},
{pattern: 'test/fixtures/**/*', watched: false, included: false, served: true},
{
pattern: "test/fixtures/**/*",
watched: false,
included: false,
served: true,
},
// {pattern: 'node_modules/jszip/dist/jszip.js', watched: false, included: true, served: true},
// {pattern: 'node_modules/es6-promise/dist/es6-promise.auto.js', watched: false, included: true, served: true},
// {pattern: 'libs/url/url-polyfill.js', watched: false, included: true, served: true}
],
// list of files to exclude
exclude: [
],
exclude: [],
// preprocess matching files before serving them to the browser
// available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor
preprocessors: {
// add webpack as preprocessor
'test/*.js': ['webpack', 'sourcemap'],
// 'test/**/*.js': ['webpack', 'sourcemap']
"test/*.js": ["webpack", "sourcemap"],
},
webpack: webpackConfig,
// {
// mode: "development",
// externals: {
// "jszip": "JSZip"
// // "xmldom": "xmldom"
// },
// devtool: 'inline-source-map',
// resolve: {
// alias: {
// path: "path-webpack"
// }
// },
// module: {
// rules: [
// {
// test: /\.js$/,
// exclude: /node_modules/,
// loader: "babel-loader",
// query: {
// presets: [["@babel/preset-env", {
// targets: "defaults",
// }]],
// }
// },
// {
// test: /\.xhtml$/i,
// use: 'raw-loader',
// }
// ]
// }
// },
webpack: webpackConfig,
webpackMiddleware: {
stats: 'errors-only'
stats: "errors-only",
},
// test results reporter to use
// possible values: 'dots', 'progress'
// available reporters: https://npmjs.org/browse/keyword/karma-reporter
reporters: ['mocha'],
reporters: ["mocha"],
// web server port
port: 9876,
// enable / disable colors in the output (reporters and logs)
colors: true,
// level of logging
// possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG
logLevel: config.LOG_INFO,
// enable / disable watching file and executing tests whenever any file changes
autoWatch: true,
// start these browsers
// available browser launchers: https://npmjs.org/browse/keyword/karma-launcher
browsers: ['ChromeHeadless', 'ChromeHeadlessNoSandbox'],
browsers: ["ChromeHeadless", "ChromeHeadlessNoSandbox"],
// Continuous Integration mode
// if true, Karma captures browsers, runs the tests and exits
@ -121,27 +81,19 @@ module.exports = function(config) {
// how many browser should be started simultaneous
concurrency: Infinity,
proxies: {
"/fixtures/": "/base/test/fixtures/"
},
proxies: { "/fixtures/": "/base/test/fixtures/" },
client: {
config: {
browserConsoleLogOptions: true
},
config: { browserConsoleLogOptions: true },
captureConsole: true,
mocha: {
reporter: 'html'
// bail: true
}
mocha: { reporter: "html" },
},
customLaunchers: {
ChromeHeadlessNoSandbox: {
base: 'ChromeHeadless',
flags: ['--no-sandbox']
}
}
})
}
base: "ChromeHeadless",
flags: ["--no-sandbox"],
},
},
});
};

View file

@ -3,13 +3,13 @@ Copyright (c) 2013, FuturePress
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
@ -23,5 +23,5 @@ ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
The views and conclusions contained in the software and documentation are those
of the authors and should not be interpreted as representing official policies,
of the authors and should not be interpreted as representing official policies,
either expressed or implied, of the FreeBSD Project.

View file

@ -3,200 +3,195 @@ import EpubCFI from "./epubcfi";
import { EVENTS } from "./utils/constants";
/**
* Handles managing adding & removing Annotations
* @param {Rendition} rendition
* @class
*/
* Handles managing adding & removing Annotations
* @param {Rendition} rendition
* @class
*/
class Annotations {
constructor(rendition) {
this.rendition = rendition;
this.highlights = [];
this.underlines = [];
this.marks = [];
this._annotations = {};
this._annotationsBySectionIndex = {};
constructor (rendition) {
this.rendition = rendition;
this.highlights = [];
this.underlines = [];
this.marks = [];
this._annotations = {};
this._annotationsBySectionIndex = {};
this.rendition.hooks.render.register(this.inject.bind(this));
this.rendition.hooks.unloaded.register(this.clear.bind(this));
}
this.rendition.hooks.render.register(this.inject.bind(this));
this.rendition.hooks.unloaded.register(this.clear.bind(this));
}
/**
* Add an annotation to store
* @param {string} type Type of annotation to add: "highlight", "underline", "mark"
* @param {EpubCFI} cfiRange EpubCFI range to attach annotation to
* @param {object} data Data to assign to annotation
* @param {function} [cb] Callback after annotation is added
* @param {string} className CSS class to assign to annotation
* @param {object} styles CSS styles to assign to annotation
* @returns {Annotation} annotation
*/
add(type, cfiRange, data, cb, className, styles) {
let hash = encodeURI(cfiRange + type);
let cfi = new EpubCFI(cfiRange);
let sectionIndex = cfi.spinePos;
let annotation = new Annotation({
type,
cfiRange,
data,
sectionIndex,
cb,
className,
styles,
});
/**
* Add an annotation to store
* @param {string} type Type of annotation to add: "highlight", "underline", "mark"
* @param {EpubCFI} cfiRange EpubCFI range to attach annotation to
* @param {object} data Data to assign to annotation
* @param {function} [cb] Callback after annotation is added
* @param {string} className CSS class to assign to annotation
* @param {object} styles CSS styles to assign to annotation
* @returns {Annotation} annotation
*/
add (type, cfiRange, data, cb, className, styles) {
let hash = encodeURI(cfiRange + type);
let cfi = new EpubCFI(cfiRange);
let sectionIndex = cfi.spinePos;
let annotation = new Annotation({
type,
cfiRange,
data,
sectionIndex,
cb,
className,
styles
});
this._annotations[hash] = annotation;
this._annotations[hash] = annotation;
if (sectionIndex in this._annotationsBySectionIndex) {
this._annotationsBySectionIndex[sectionIndex].push(hash);
} else {
this._annotationsBySectionIndex[sectionIndex] = [hash];
}
if (sectionIndex in this._annotationsBySectionIndex) {
this._annotationsBySectionIndex[sectionIndex].push(hash);
} else {
this._annotationsBySectionIndex[sectionIndex] = [hash];
}
let views = this.rendition.views();
let views = this.rendition.views();
views.forEach((view) => {
if (annotation.sectionIndex === view.index) {
annotation.attach(view);
}
});
views.forEach( (view) => {
if (annotation.sectionIndex === view.index) {
annotation.attach(view);
}
});
return annotation;
}
return annotation;
}
/**
* Remove an annotation from store
* @param {EpubCFI} cfiRange EpubCFI range the annotation is attached to
* @param {string} type Type of annotation to add: "highlight", "underline", "mark"
*/
remove(cfiRange, type) {
let hash = encodeURI(cfiRange + type);
/**
* Remove an annotation from store
* @param {EpubCFI} cfiRange EpubCFI range the annotation is attached to
* @param {string} type Type of annotation to add: "highlight", "underline", "mark"
*/
remove (cfiRange, type) {
let hash = encodeURI(cfiRange + type);
if (hash in this._annotations) {
let annotation = this._annotations[hash];
if (hash in this._annotations) {
let annotation = this._annotations[hash];
if (type && annotation.type !== type) {
return;
}
if (type && annotation.type !== type) {
return;
}
let views = this.rendition.views();
views.forEach((view) => {
this._removeFromAnnotationBySectionIndex(annotation.sectionIndex, hash);
if (annotation.sectionIndex === view.index) {
annotation.detach(view);
}
});
let views = this.rendition.views();
views.forEach( (view) => {
this._removeFromAnnotationBySectionIndex(annotation.sectionIndex, hash);
if (annotation.sectionIndex === view.index) {
annotation.detach(view);
}
});
delete this._annotations[hash];
}
}
delete this._annotations[hash];
}
}
/**
* Remove an annotations by Section Index
* @private
*/
_removeFromAnnotationBySectionIndex(sectionIndex, hash) {
this._annotationsBySectionIndex[sectionIndex] = this._annotationsAt(
sectionIndex
).filter((h) => h !== hash);
}
/**
* Remove an annotations by Section Index
* @private
*/
_removeFromAnnotationBySectionIndex (sectionIndex, hash) {
this._annotationsBySectionIndex[sectionIndex] = this._annotationsAt(sectionIndex).filter(h => h !== hash);
}
/**
* Get annotations by Section Index
* @private
*/
_annotationsAt(index) {
return this._annotationsBySectionIndex[index];
}
/**
* Get annotations by Section Index
* @private
*/
_annotationsAt (index) {
return this._annotationsBySectionIndex[index];
}
/**
* Add a highlight to the store
* @param {EpubCFI} cfiRange EpubCFI range to attach annotation to
* @param {object} data Data to assign to annotation
* @param {function} cb Callback after annotation is clicked
* @param {string} className CSS class to assign to annotation
* @param {object} styles CSS styles to assign to annotation
*/
highlight(cfiRange, data, cb, className, styles) {
return this.add("highlight", cfiRange, data, cb, className, styles);
}
/**
* Add a underline to the store
* @param {EpubCFI} cfiRange EpubCFI range to attach annotation to
* @param {object} data Data to assign to annotation
* @param {function} cb Callback after annotation is clicked
* @param {string} className CSS class to assign to annotation
* @param {object} styles CSS styles to assign to annotation
*/
underline(cfiRange, data, cb, className, styles) {
return this.add("underline", cfiRange, data, cb, className, styles);
}
/**
* Add a highlight to the store
* @param {EpubCFI} cfiRange EpubCFI range to attach annotation to
* @param {object} data Data to assign to annotation
* @param {function} cb Callback after annotation is clicked
* @param {string} className CSS class to assign to annotation
* @param {object} styles CSS styles to assign to annotation
*/
highlight (cfiRange, data, cb, className, styles) {
return this.add("highlight", cfiRange, data, cb, className, styles);
}
/**
* Add a mark to the store
* @param {EpubCFI} cfiRange EpubCFI range to attach annotation to
* @param {object} data Data to assign to annotation
* @param {function} cb Callback after annotation is clicked
*/
mark(cfiRange, data, cb) {
return this.add("mark", cfiRange, data, cb);
}
/**
* Add a underline to the store
* @param {EpubCFI} cfiRange EpubCFI range to attach annotation to
* @param {object} data Data to assign to annotation
* @param {function} cb Callback after annotation is clicked
* @param {string} className CSS class to assign to annotation
* @param {object} styles CSS styles to assign to annotation
*/
underline (cfiRange, data, cb, className, styles) {
return this.add("underline", cfiRange, data, cb, className, styles);
}
/**
* iterate over annotations in the store
*/
each() {
return this._annotations.forEach.apply(this._annotations, arguments);
}
/**
* Add a mark to the store
* @param {EpubCFI} cfiRange EpubCFI range to attach annotation to
* @param {object} data Data to assign to annotation
* @param {function} cb Callback after annotation is clicked
*/
mark (cfiRange, data, cb) {
return this.add("mark", cfiRange, data, cb);
}
/**
* Hook for injecting annotation into a view
* @param {View} view
* @private
*/
inject(view) {
let sectionIndex = view.index;
if (sectionIndex in this._annotationsBySectionIndex) {
let annotations = this._annotationsBySectionIndex[sectionIndex];
annotations.forEach((hash) => {
let annotation = this._annotations[hash];
annotation.attach(view);
});
}
}
/**
* iterate over annotations in the store
*/
each () {
return this._annotations.forEach.apply(this._annotations, arguments);
}
/**
* Hook for removing annotation from a view
* @param {View} view
* @private
*/
clear(view) {
let sectionIndex = view.index;
if (sectionIndex in this._annotationsBySectionIndex) {
let annotations = this._annotationsBySectionIndex[sectionIndex];
annotations.forEach((hash) => {
let annotation = this._annotations[hash];
annotation.detach(view);
});
}
}
/**
* Hook for injecting annotation into a view
* @param {View} view
* @private
*/
inject (view) {
let sectionIndex = view.index;
if (sectionIndex in this._annotationsBySectionIndex) {
let annotations = this._annotationsBySectionIndex[sectionIndex];
annotations.forEach((hash) => {
let annotation = this._annotations[hash];
annotation.attach(view);
});
}
}
/**
* Hook for removing annotation from a view
* @param {View} view
* @private
*/
clear (view) {
let sectionIndex = view.index;
if (sectionIndex in this._annotationsBySectionIndex) {
let annotations = this._annotationsBySectionIndex[sectionIndex];
annotations.forEach((hash) => {
let annotation = this._annotations[hash];
annotation.detach(view);
});
}
}
/**
* [Not Implemented] Show annotations
* @TODO: needs implementation in View
*/
show () {
}
/**
* [Not Implemented] Hide annotations
* @TODO: needs implementation in View
*/
hide () {
}
/**
* [Not Implemented] Show annotations
* @TODO: needs implementation in View
*/
show() {}
/**
* [Not Implemented] Hide annotations
* @TODO: needs implementation in View
*/
hide() {}
}
/**
@ -213,89 +208,76 @@ class Annotations {
* @returns {Annotation} annotation
*/
class Annotation {
constructor({ type, cfiRange, data, sectionIndex, cb, className, styles }) {
this.type = type;
this.cfiRange = cfiRange;
this.data = data;
this.sectionIndex = sectionIndex;
this.mark = undefined;
this.cb = cb;
this.className = className;
this.styles = styles;
}
constructor ({
type,
cfiRange,
data,
sectionIndex,
cb,
className,
styles
}) {
this.type = type;
this.cfiRange = cfiRange;
this.data = data;
this.sectionIndex = sectionIndex;
this.mark = undefined;
this.cb = cb;
this.className = className;
this.styles = styles;
}
/**
* Update stored data
* @param {object} data
*/
update(data) {
this.data = data;
}
/**
* Update stored data
* @param {object} data
*/
update (data) {
this.data = data;
}
/**
* Add to a view
* @param {View} view
*/
attach(view) {
let { cfiRange, data, type, cb, className, styles } = this;
let result;
/**
* Add to a view
* @param {View} view
*/
attach (view) {
let {cfiRange, data, type, mark, cb, className, styles} = this;
let result;
if (type === "highlight") {
result = view.highlight(cfiRange, data, cb, className, styles);
} else if (type === "underline") {
result = view.underline(cfiRange, data, cb, className, styles);
} else if (type === "mark") {
result = view.mark(cfiRange, data, cb);
}
if (type === "highlight") {
result = view.highlight(cfiRange, data, cb, className, styles);
} else if (type === "underline") {
result = view.underline(cfiRange, data, cb, className, styles);
} else if (type === "mark") {
result = view.mark(cfiRange, data, cb);
}
this.mark = result;
this.emit(EVENTS.ANNOTATION.ATTACH, result);
return result;
}
this.mark = result;
this.emit(EVENTS.ANNOTATION.ATTACH, result);
return result;
}
/**
* Remove from a view
* @param {View} view
*/
detach(view) {
let { cfiRange, type } = this;
let result;
/**
* Remove from a view
* @param {View} view
*/
detach (view) {
let {cfiRange, type} = this;
let result;
if (view) {
if (type === "highlight") {
result = view.unhighlight(cfiRange);
} else if (type === "underline") {
result = view.ununderline(cfiRange);
} else if (type === "mark") {
result = view.unmark(cfiRange);
}
}
if (view) {
if (type === "highlight") {
result = view.unhighlight(cfiRange);
} else if (type === "underline") {
result = view.ununderline(cfiRange);
} else if (type === "mark") {
result = view.unmark(cfiRange);
}
}
this.mark = undefined;
this.emit(EVENTS.ANNOTATION.DETACH, result);
return result;
}
/**
* [Not Implemented] Get text of an annotation
* @TODO: needs implementation in contents
*/
text () {
}
this.mark = undefined;
this.emit(EVENTS.ANNOTATION.DETACH, result);
return result;
}
/**
* [Not Implemented] Get text of an annotation
* @TODO: needs implementation in contents
*/
text() {}
}
EventEmitter(Annotation.prototype);
export default Annotations
export default Annotations;

View file

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

File diff suppressed because it is too large Load diff

View file

@ -1,5 +1,5 @@
import path from "path-webpack";
import {qs} from "./utils/core";
import { qs } from "./utils/core";
/**
* Handles Parsing and Accessing an Epub Container
@ -7,44 +7,44 @@ import {qs} from "./utils/core";
* @param {document} [containerDocument] xml document
*/
class Container {
constructor(containerDocument) {
this.packagePath = '';
this.directory = '';
this.encoding = '';
constructor(containerDocument) {
this.packagePath = "";
this.directory = "";
this.encoding = "";
if (containerDocument) {
this.parse(containerDocument);
}
}
if (containerDocument) {
this.parse(containerDocument);
}
}
/**
* Parse the Container XML
* @param {document} containerDocument
*/
parse(containerDocument){
//-- <rootfile full-path="OPS/package.opf" media-type="application/oebps-package+xml"/>
var rootfile;
/**
* Parse the Container XML
* @param {document} containerDocument
*/
parse(containerDocument) {
//-- <rootfile full-path="OPS/package.opf" media-type="application/oebps-package+xml"/>
var rootfile;
if(!containerDocument) {
throw new Error("Container File Not Found");
}
if (!containerDocument) {
throw new Error("Container File Not Found");
}
rootfile = qs(containerDocument, "rootfile");
rootfile = qs(containerDocument, "rootfile");
if(!rootfile) {
throw new Error("No RootFile Found");
}
if (!rootfile) {
throw new Error("No RootFile Found");
}
this.packagePath = rootfile.getAttribute("full-path");
this.directory = path.dirname(this.packagePath);
this.encoding = containerDocument.xmlEncoding;
}
this.packagePath = rootfile.getAttribute("full-path");
this.directory = path.dirname(this.packagePath);
this.encoding = containerDocument.xmlEncoding;
}
destroy() {
this.packagePath = undefined;
this.directory = undefined;
this.encoding = undefined;
}
destroy() {
this.packagePath = undefined;
this.directory = undefined;
this.encoding = undefined;
}
}
export default Container;

File diff suppressed because it is too large Load diff

View file

@ -1,4 +1,4 @@
import {qs, qsa } from "./utils/core";
import { qs, qsa } from "./utils/core";
/**
* Open DisplayOptions Format Parser
@ -6,65 +6,65 @@ import {qs, qsa } from "./utils/core";
* @param {document} displayOptionsDocument XML
*/
class DisplayOptions {
constructor(displayOptionsDocument) {
this.interactive = "";
this.fixedLayout = "";
this.openToSpread = "";
this.orientationLock = "";
constructor(displayOptionsDocument) {
this.interactive = "";
this.fixedLayout = "";
this.openToSpread = "";
this.orientationLock = "";
if (displayOptionsDocument) {
this.parse(displayOptionsDocument);
}
}
if (displayOptionsDocument) {
this.parse(displayOptionsDocument);
}
}
/**
* Parse XML
* @param {document} displayOptionsDocument XML
* @return {DisplayOptions} self
*/
parse(displayOptionsDocument) {
if(!displayOptionsDocument) {
return this;
}
/**
* Parse XML
* @param {document} displayOptionsDocument XML
* @return {DisplayOptions} self
*/
parse(displayOptionsDocument) {
if (!displayOptionsDocument) {
return this;
}
const displayOptionsNode = qs(displayOptionsDocument, "display_options");
if(!displayOptionsNode) {
return this;
}
const displayOptionsNode = qs(displayOptionsDocument, "display_options");
if (!displayOptionsNode) {
return this;
}
const options = qsa(displayOptionsNode, "option");
options.forEach((el) => {
let value = "";
const options = qsa(displayOptionsNode, "option");
options.forEach((el) => {
let value = "";
if (el.childNodes.length) {
value = el.childNodes[0].nodeValue;
}
if (el.childNodes.length) {
value = el.childNodes[0].nodeValue;
}
switch (el.attributes.name.value) {
case "interactive":
this.interactive = value;
break;
case "fixed-layout":
this.fixedLayout = value;
break;
case "open-to-spread":
this.openToSpread = value;
break;
case "orientation-lock":
this.orientationLock = value;
break;
}
});
switch (el.attributes.name.value) {
case "interactive":
this.interactive = value;
break;
case "fixed-layout":
this.fixedLayout = value;
break;
case "open-to-spread":
this.openToSpread = value;
break;
case "orientation-lock":
this.orientationLock = value;
break;
}
});
return this;
}
return this;
}
destroy() {
this.interactive = undefined;
this.fixedLayout = undefined;
this.openToSpread = undefined;
this.orientationLock = undefined;
}
destroy() {
this.interactive = undefined;
this.fixedLayout = undefined;
this.openToSpread = undefined;
this.orientationLock = undefined;
}
}
export default DisplayOptions;

View file

@ -1,13 +1,9 @@
import Book from "./book";
import Rendition from "./rendition";
import CFI from "./epubcfi";
import Contents from "./contents";
import * as utils from "./utils/core";
import CFI from "./epubcfi";
import Rendition from "./rendition";
import { EPUBJS_VERSION } from "./utils/constants";
import IframeView from "./managers/views/iframe";
import DefaultViewManager from "./managers/default";
import ContinuousViewManager from "./managers/continuous";
import * as utils from "./utils/core";
/**
* Creates a new Book
@ -17,13 +13,13 @@ import ContinuousViewManager from "./managers/continuous";
* @example ePub("/path/to/book.epub", {})
*/
function ePub(url, options) {
return new Book(url, options);
return new Book(url, options);
}
ePub.VERSION = EPUBJS_VERSION;
if (typeof(global) !== "undefined") {
global.EPUBJS_VERSION = EPUBJS_VERSION;
if (typeof global !== "undefined") {
global.EPUBJS_VERSION = EPUBJS_VERSION;
}
ePub.Book = Book;

File diff suppressed because it is too large Load diff

View file

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

View file

@ -1,6 +1,6 @@
import { extend } from "./utils/core";
import { EVENTS } from "./utils/constants";
import EventEmitter from "event-emitter";
import { EVENTS } from "./utils/constants";
import { extend } from "./utils/core";
/**
* Figures out the CSS values to apply for a layout
@ -12,247 +12,255 @@ import EventEmitter from "event-emitter";
* @param {boolean} [settings.evenSpreads=false]
*/
class Layout {
constructor(settings) {
this.settings = settings;
this.name = settings.layout || "reflowable";
this._spread = (settings.spread === "none") ? false : true;
this._minSpreadWidth = settings.minSpreadWidth || 800;
this._evenSpreads = settings.evenSpreads || false;
constructor(settings) {
this.settings = settings;
this.name = settings.layout || "reflowable";
this._spread = settings.spread === "none" ? false : true;
this._minSpreadWidth = settings.minSpreadWidth || 800;
this._evenSpreads = settings.evenSpreads || false;
if (settings.flow === "scrolled" ||
settings.flow === "scrolled-continuous" ||
settings.flow === "scrolled-doc") {
this._flow = "scrolled";
} else {
this._flow = "paginated";
}
if (
settings.flow === "scrolled" ||
settings.flow === "scrolled-continuous" ||
settings.flow === "scrolled-doc"
) {
this._flow = "scrolled";
} else {
this._flow = "paginated";
}
this.width = 0;
this.height = 0;
this.spreadWidth = 0;
this.delta = 0;
this.width = 0;
this.height = 0;
this.spreadWidth = 0;
this.delta = 0;
this.columnWidth = 0;
this.gap = 0;
this.divisor = 1;
this.columnWidth = 0;
this.gap = 0;
this.divisor = 1;
this.props = {
name: this.name,
spread: this._spread,
flow: this._flow,
width: 0,
height: 0,
spreadWidth: 0,
delta: 0,
columnWidth: 0,
gap: 0,
divisor: 1,
};
}
this.props = {
name: this.name,
spread: this._spread,
flow: this._flow,
width: 0,
height: 0,
spreadWidth: 0,
delta: 0,
columnWidth: 0,
gap: 0,
divisor: 1
};
/**
* Switch the flow between paginated and scrolled
* @param {string} flow paginated | scrolled
* @return {string} simplified flow
*/
flow(flow) {
if (typeof flow != "undefined") {
if (
flow === "scrolled" ||
flow === "scrolled-continuous" ||
flow === "scrolled-doc"
) {
this._flow = "scrolled";
} else {
this._flow = "paginated";
}
// this.props.flow = this._flow;
this.update({ flow: this._flow });
}
return this._flow;
}
}
/**
* Switch between using spreads or not, and set the
* width at which they switch to single.
* @param {string} spread "none" | "always" | "auto"
* @param {number} min integer in pixels
* @return {boolean} spread true | false
*/
spread(spread, min) {
if (spread) {
this._spread = spread === "none" ? false : true;
// this.props.spread = this._spread;
this.update({ spread: this._spread });
}
/**
* Switch the flow between paginated and scrolled
* @param {string} flow paginated | scrolled
* @return {string} simplified flow
*/
flow(flow) {
if (typeof(flow) != "undefined") {
if (flow === "scrolled" ||
flow === "scrolled-continuous" ||
flow === "scrolled-doc") {
this._flow = "scrolled";
} else {
this._flow = "paginated";
}
// this.props.flow = this._flow;
this.update({flow: this._flow});
}
return this._flow;
}
if (min >= 0) {
this._minSpreadWidth = min;
}
/**
* Switch between using spreads or not, and set the
* width at which they switch to single.
* @param {string} spread "none" | "always" | "auto"
* @param {number} min integer in pixels
* @return {boolean} spread true | false
*/
spread(spread, min) {
return this._spread;
}
if (spread) {
this._spread = (spread === "none") ? false : true;
// this.props.spread = this._spread;
this.update({spread: this._spread});
}
/**
* Calculate the dimensions of the pagination
* @param {number} _width width of the rendering
* @param {number} _height height of the rendering
* @param {number} _gap width of the gap between columns
*/
calculate(_width, _height, _gap) {
var divisor = 1;
var gap = _gap || 0;
if (min >= 0) {
this._minSpreadWidth = min;
}
//-- Check the width and create even width columns
// var fullWidth = Math.floor(_width);
var width = _width;
var height = _height;
return this._spread;
}
var section = Math.floor(width / 12);
/**
* Calculate the dimensions of the pagination
* @param {number} _width width of the rendering
* @param {number} _height height of the rendering
* @param {number} _gap width of the gap between columns
*/
calculate(_width, _height, _gap){
var columnWidth;
var spreadWidth;
var pageWidth;
var delta;
var divisor = 1;
var gap = _gap || 0;
if (this._spread && width >= this._minSpreadWidth) {
divisor = 2;
} else {
divisor = 1;
}
//-- Check the width and create even width columns
// var fullWidth = Math.floor(_width);
var width = _width;
var height = _height;
if (
this.name === "reflowable" &&
this._flow === "paginated" &&
!(_gap >= 0)
) {
gap = section % 2 === 0 ? section : section - 1;
}
var section = Math.floor(width / 12);
if (this.name === "pre-paginated") {
gap = 0;
}
var columnWidth;
var spreadWidth;
var pageWidth;
var delta;
//-- Double Page
if (divisor > 1) {
// width = width - gap;
// columnWidth = (width - gap) / divisor;
// gap = gap / divisor;
columnWidth = width / divisor - gap;
pageWidth = columnWidth + gap;
} else {
columnWidth = width;
pageWidth = width;
}
if (this._spread && width >= this._minSpreadWidth) {
divisor = 2;
} else {
divisor = 1;
}
if (this.name === "pre-paginated" && divisor > 1) {
width = columnWidth;
}
if (this.name === "reflowable" && this._flow === "paginated" && !(_gap >= 0)) {
gap = ((section % 2 === 0) ? section : section - 1);
}
spreadWidth = columnWidth * divisor + gap;
if (this.name === "pre-paginated" ) {
gap = 0;
}
delta = width;
//-- Double Page
if(divisor > 1) {
// width = width - gap;
// columnWidth = (width - gap) / divisor;
// gap = gap / divisor;
columnWidth = (width / divisor) - gap;
pageWidth = columnWidth + gap;
} else {
columnWidth = width;
pageWidth = width;
}
this.width = width;
this.height = height;
this.spreadWidth = spreadWidth;
this.pageWidth = pageWidth;
this.delta = delta;
if (this.name === "pre-paginated" && divisor > 1) {
width = columnWidth;
}
this.columnWidth = columnWidth;
this.gap = gap;
this.divisor = divisor;
spreadWidth = (columnWidth * divisor) + gap;
// this.props.width = width;
// this.props.height = _height;
// this.props.spreadWidth = spreadWidth;
// this.props.pageWidth = pageWidth;
// this.props.delta = delta;
//
// this.props.columnWidth = colWidth;
// this.props.gap = gap;
// this.props.divisor = divisor;
delta = width;
this.update({
width,
height,
spreadWidth,
pageWidth,
delta,
columnWidth,
gap,
divisor,
});
}
this.width = width;
this.height = height;
this.spreadWidth = spreadWidth;
this.pageWidth = pageWidth;
this.delta = delta;
/**
* Apply Css to a Document
* @param {Contents} contents
* @return {Promise}
*/
format(contents, section, axis) {
var formating;
this.columnWidth = columnWidth;
this.gap = gap;
this.divisor = divisor;
if (this.name === "pre-paginated") {
formating = contents.fit(this.columnWidth, this.height, section);
} else if (this._flow === "paginated") {
formating = contents.columns(
this.width,
this.height,
this.columnWidth,
this.gap,
this.settings.direction
);
} else if (axis && axis === "horizontal") {
formating = contents.size(null, this.height);
} else {
formating = contents.size(this.width, null);
}
// this.props.width = width;
// this.props.height = _height;
// this.props.spreadWidth = spreadWidth;
// this.props.pageWidth = pageWidth;
// this.props.delta = delta;
//
// this.props.columnWidth = colWidth;
// this.props.gap = gap;
// this.props.divisor = divisor;
return formating; // might be a promise in some View Managers
}
this.update({
width,
height,
spreadWidth,
pageWidth,
delta,
columnWidth,
gap,
divisor
});
/**
* Count number of pages
* @param {number} totalLength
* @param {number} pageLength
* @return {{spreads: Number, pages: Number}}
*/
count(totalLength, pageLength) {
let spreads, pages;
}
if (this.name === "pre-paginated") {
spreads = 1;
pages = 1;
} else if (this._flow === "paginated") {
pageLength = pageLength || this.delta;
spreads = Math.ceil(totalLength / pageLength);
pages = spreads * this.divisor;
} else {
// scrolled
pageLength = pageLength || this.height;
spreads = Math.ceil(totalLength / pageLength);
pages = spreads;
}
/**
* Apply Css to a Document
* @param {Contents} contents
* @return {Promise}
*/
format(contents, section, axis){
var formating;
return {
spreads,
pages,
};
}
if (this.name === "pre-paginated") {
formating = contents.fit(this.columnWidth, this.height, section);
} else if (this._flow === "paginated") {
formating = contents.columns(this.width, this.height, this.columnWidth, this.gap, this.settings.direction);
} else if (axis && axis === "horizontal") {
formating = contents.size(null, this.height);
} else {
formating = contents.size(this.width, null);
}
/**
* Update props that have changed
* @private
* @param {object} props
*/
update(props) {
// Remove props that haven't changed
Object.keys(props).forEach((propName) => {
if (this.props[propName] === props[propName]) {
delete props[propName];
}
});
return formating; // might be a promise in some View Managers
}
/**
* Count number of pages
* @param {number} totalLength
* @param {number} pageLength
* @return {{spreads: Number, pages: Number}}
*/
count(totalLength, pageLength) {
let spreads, pages;
if (this.name === "pre-paginated") {
spreads = 1;
pages = 1;
} else if (this._flow === "paginated") {
pageLength = pageLength || this.delta;
spreads = Math.ceil( totalLength / pageLength);
pages = spreads * this.divisor;
} else { // scrolled
pageLength = pageLength || this.height;
spreads = Math.ceil( totalLength / pageLength);
pages = spreads;
}
return {
spreads,
pages
};
}
/**
* Update props that have changed
* @private
* @param {object} props
*/
update(props) {
// Remove props that haven't changed
Object.keys(props).forEach((propName) => {
if (this.props[propName] === props[propName]) {
delete props[propName];
}
});
if(Object.keys(props).length > 0) {
let newProps = extend(this.props, props);
this.emit(EVENTS.LAYOUT.UPDATED, newProps, props);
}
}
if (Object.keys(props).length > 0) {
let newProps = extend(this.props, props);
this.emit(EVENTS.LAYOUT.UPDATED, newProps, props);
}
}
}
EventEmitter(Layout.prototype);

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -1,336 +1,348 @@
import {extend, defer, requestAnimationFrame, prefixed} from "../../utils/core";
import { EVENTS, DOM_EVENTS } from "../../utils/constants";
import EventEmitter from "event-emitter";
import { EVENTS } from "../../utils/constants";
import { defer, extend } from "../../utils/core";
// easing equations from https://github.com/danro/easing-js/blob/master/easing.js
const PI_D2 = (Math.PI / 2);
const PI_D2 = Math.PI / 2;
const EASING_EQUATIONS = {
easeOutSine: function (pos) {
return Math.sin(pos * PI_D2);
},
easeInOutSine: function (pos) {
return (-0.5 * (Math.cos(Math.PI * pos) - 1));
},
easeInOutQuint: function (pos) {
if ((pos /= 0.5) < 1) {
return 0.5 * Math.pow(pos, 5);
}
return 0.5 * (Math.pow((pos - 2), 5) + 2);
},
easeInCubic: function(pos) {
return Math.pow(pos, 3);
}
easeOutSine: function (pos) {
return Math.sin(pos * PI_D2);
},
easeInOutSine: function (pos) {
return -0.5 * (Math.cos(Math.PI * pos) - 1);
},
easeInOutQuint: function (pos) {
if ((pos /= 0.5) < 1) {
return 0.5 * Math.pow(pos, 5);
}
return 0.5 * (Math.pow(pos - 2, 5) + 2);
},
easeInCubic: function (pos) {
return Math.pow(pos, 3);
},
};
class Snap {
constructor(manager, options) {
this.settings = extend({
duration: 80,
minVelocity: 0.2,
minDistance: 10,
easing: EASING_EQUATIONS['easeInCubic']
}, options || {});
this.supportsTouch = this.supportsTouch();
if (this.supportsTouch) {
this.setup(manager);
}
}
setup(manager) {
this.manager = manager;
this.layout = this.manager.layout;
this.fullsize = this.manager.settings.fullsize;
if (this.fullsize) {
this.element = this.manager.stage.element;
this.scroller = window;
this.disableScroll();
} else {
this.element = this.manager.stage.container;
this.scroller = this.element;
this.element.style["WebkitOverflowScrolling"] = "touch";
}
// this.overflow = this.manager.overflow;
// set lookahead offset to page width
this.manager.settings.offset = this.layout.width;
this.manager.settings.afterScrolledTimeout = this.settings.duration * 2;
this.isVertical = this.manager.settings.axis === "vertical";
// disable snapping if not paginated or axis in not horizontal
if (!this.manager.isPaginated || this.isVertical) {
return;
}
this.touchCanceler = false;
this.resizeCanceler = false;
this.snapping = false;
this.scrollLeft;
this.scrollTop;
this.startTouchX = undefined;
this.startTouchY = undefined;
this.startTime = undefined;
this.endTouchX = undefined;
this.endTouchY = undefined;
this.endTime = undefined;
this.addListeners();
}
supportsTouch() {
if (('ontouchstart' in window) || window.DocumentTouch && document instanceof DocumentTouch) {
return true;
}
return false;
}
disableScroll() {
this.element.style.overflow = "hidden";
}
enableScroll() {
this.element.style.overflow = "";
}
addListeners() {
this._onResize = this.onResize.bind(this);
window.addEventListener('resize', this._onResize);
this._onScroll = this.onScroll.bind(this);
this.scroller.addEventListener('scroll', this._onScroll);
this._onTouchStart = this.onTouchStart.bind(this);
this.scroller.addEventListener('touchstart', this._onTouchStart, { passive: true });
this.on('touchstart', this._onTouchStart);
this._onTouchMove = this.onTouchMove.bind(this);
this.scroller.addEventListener('touchmove', this._onTouchMove, { passive: true });
this.on('touchmove', this._onTouchMove);
this._onTouchEnd = this.onTouchEnd.bind(this);
this.scroller.addEventListener('touchend', this._onTouchEnd, { passive: true });
this.on('touchend', this._onTouchEnd);
this._afterDisplayed = this.afterDisplayed.bind(this);
this.manager.on(EVENTS.MANAGERS.ADDED, this._afterDisplayed);
}
removeListeners() {
window.removeEventListener('resize', this._onResize);
this._onResize = undefined;
this.scroller.removeEventListener('scroll', this._onScroll);
this._onScroll = undefined;
this.scroller.removeEventListener('touchstart', this._onTouchStart, { passive: true });
this.off('touchstart', this._onTouchStart);
this._onTouchStart = undefined;
this.scroller.removeEventListener('touchmove', this._onTouchMove, { passive: true });
this.off('touchmove', this._onTouchMove);
this._onTouchMove = undefined;
this.scroller.removeEventListener('touchend', this._onTouchEnd, { passive: true });
this.off('touchend', this._onTouchEnd);
this._onTouchEnd = undefined;
this.manager.off(EVENTS.MANAGERS.ADDED, this._afterDisplayed);
this._afterDisplayed = undefined;
}
afterDisplayed(view) {
let contents = view.contents;
["touchstart", "touchmove", "touchend"].forEach((e) => {
contents.on(e, (ev) => this.triggerViewEvent(ev, contents));
});
}
triggerViewEvent(e, contents){
this.emit(e.type, e, contents);
}
onScroll(e) {
this.scrollLeft = this.fullsize ? window.scrollX : this.scroller.scrollLeft;
this.scrollTop = this.fullsize ? window.scrollY : this.scroller.scrollTop;
}
onResize(e) {
this.resizeCanceler = true;
}
onTouchStart(e) {
let { screenX, screenY } = e.touches[0];
if (this.fullsize) {
this.enableScroll();
}
this.touchCanceler = true;
if (!this.startTouchX) {
this.startTouchX = screenX;
this.startTouchY = screenY;
this.startTime = this.now();
}
this.endTouchX = screenX;
this.endTouchY = screenY;
this.endTime = this.now();
}
onTouchMove(e) {
let { screenX, screenY } = e.touches[0];
let deltaY = Math.abs(screenY - this.endTouchY);
this.touchCanceler = true;
if (!this.fullsize && deltaY < 10) {
this.element.scrollLeft -= screenX - this.endTouchX;
}
this.endTouchX = screenX;
this.endTouchY = screenY;
this.endTime = this.now();
}
onTouchEnd(e) {
if (this.fullsize) {
this.disableScroll();
}
this.touchCanceler = false;
let swipped = this.wasSwiped();
if (swipped !== 0) {
this.snap(swipped);
} else {
this.snap();
}
this.startTouchX = undefined;
this.startTouchY = undefined;
this.startTime = undefined;
this.endTouchX = undefined;
this.endTouchY = undefined;
this.endTime = undefined;
}
wasSwiped() {
let snapWidth = this.layout.pageWidth * this.layout.divisor;
let distance = (this.endTouchX - this.startTouchX);
let absolute = Math.abs(distance);
let time = this.endTime - this.startTime;
let velocity = (distance / time);
let minVelocity = this.settings.minVelocity;
if (absolute <= this.settings.minDistance || absolute >= snapWidth) {
return 0;
}
if (velocity > minVelocity) {
// previous
return -1;
} else if (velocity < -minVelocity) {
// next
return 1;
}
}
needsSnap() {
let left = this.scrollLeft;
let snapWidth = this.layout.pageWidth * this.layout.divisor;
return (left % snapWidth) !== 0;
}
snap(howMany=0) {
let left = this.scrollLeft;
let snapWidth = this.layout.pageWidth * this.layout.divisor;
let snapTo = Math.round(left / snapWidth) * snapWidth;
if (howMany) {
snapTo += (howMany * snapWidth);
}
return this.smoothScrollTo(snapTo);
}
smoothScrollTo(destination) {
const deferred = new defer();
const start = this.scrollLeft;
const startTime = this.now();
const duration = this.settings.duration;
const easing = this.settings.easing;
this.snapping = true;
// add animation loop
function tick() {
const now = this.now();
const time = Math.min(1, ((now - startTime) / duration));
const timeFunction = easing(time);
if (this.touchCanceler || this.resizeCanceler) {
this.resizeCanceler = false;
this.snapping = false;
deferred.resolve();
return;
}
if (time < 1) {
window.requestAnimationFrame(tick.bind(this));
this.scrollTo(start + ((destination - start) * time), 0);
} else {
this.scrollTo(destination, 0);
this.snapping = false;
deferred.resolve();
}
}
tick.call(this);
return deferred.promise;
}
scrollTo(left=0, top=0) {
if (this.fullsize) {
window.scroll(left, top);
} else {
this.scroller.scrollLeft = left;
this.scroller.scrollTop = top;
}
}
now() {
return ('now' in window.performance) ? performance.now() : new Date().getTime();
}
destroy() {
if (!this.scroller) {
return;
}
if (this.fullsize) {
this.enableScroll();
}
this.removeListeners();
this.scroller = undefined;
}
constructor(manager, options) {
this.settings = extend(
{
duration: 80,
minVelocity: 0.2,
minDistance: 10,
easing: EASING_EQUATIONS["easeInCubic"],
},
options || {}
);
this.supportsTouch = this.supportsTouch();
if (this.supportsTouch) {
this.setup(manager);
}
}
setup(manager) {
this.manager = manager;
this.layout = this.manager.layout;
this.fullsize = this.manager.settings.fullsize;
if (this.fullsize) {
this.element = this.manager.stage.element;
this.scroller = window;
this.disableScroll();
} else {
this.element = this.manager.stage.container;
this.scroller = this.element;
this.element.style["WebkitOverflowScrolling"] = "touch";
}
// set lookahead offset to page width
this.manager.settings.offset = this.layout.width;
this.manager.settings.afterScrolledTimeout = this.settings.duration * 2;
this.isVertical = this.manager.settings.axis === "vertical";
// disable snapping if not paginated or axis in not horizontal
if (!this.manager.isPaginated || this.isVertical) {
return;
}
this.touchCanceler = false;
this.resizeCanceler = false;
this.snapping = false;
this.scrollLeft;
this.scrollTop;
this.startTouchX = undefined;
this.startTouchY = undefined;
this.startTime = undefined;
this.endTouchX = undefined;
this.endTouchY = undefined;
this.endTime = undefined;
this.addListeners();
}
supportsTouch() {
if (
"ontouchstart" in window ||
(window.DocumentTouch && document instanceof DocumentTouch)
) {
return true;
}
return false;
}
disableScroll() {
this.element.style.overflow = "hidden";
}
enableScroll() {
this.element.style.overflow = "";
}
addListeners() {
this._onResize = this.onResize.bind(this);
window.addEventListener("resize", this._onResize);
this._onScroll = this.onScroll.bind(this);
this.scroller.addEventListener("scroll", this._onScroll);
this._onTouchStart = this.onTouchStart.bind(this);
this.scroller.addEventListener("touchstart", this._onTouchStart, {
passive: true,
});
this.on("touchstart", this._onTouchStart);
this._onTouchMove = this.onTouchMove.bind(this);
this.scroller.addEventListener("touchmove", this._onTouchMove, {
passive: true,
});
this.on("touchmove", this._onTouchMove);
this._onTouchEnd = this.onTouchEnd.bind(this);
this.scroller.addEventListener("touchend", this._onTouchEnd, {
passive: true,
});
this.on("touchend", this._onTouchEnd);
this._afterDisplayed = this.afterDisplayed.bind(this);
this.manager.on(EVENTS.MANAGERS.ADDED, this._afterDisplayed);
}
removeListeners() {
window.removeEventListener("resize", this._onResize);
this._onResize = undefined;
this.scroller.removeEventListener("scroll", this._onScroll);
this._onScroll = undefined;
this.scroller.removeEventListener("touchstart", this._onTouchStart, {
passive: true,
});
this.off("touchstart", this._onTouchStart);
this._onTouchStart = undefined;
this.scroller.removeEventListener("touchmove", this._onTouchMove, {
passive: true,
});
this.off("touchmove", this._onTouchMove);
this._onTouchMove = undefined;
this.scroller.removeEventListener("touchend", this._onTouchEnd, {
passive: true,
});
this.off("touchend", this._onTouchEnd);
this._onTouchEnd = undefined;
this.manager.off(EVENTS.MANAGERS.ADDED, this._afterDisplayed);
this._afterDisplayed = undefined;
}
afterDisplayed(view) {
let contents = view.contents;
["touchstart", "touchmove", "touchend"].forEach((e) => {
contents.on(e, (ev) => this.triggerViewEvent(ev, contents));
});
}
triggerViewEvent(e, contents) {
this.emit(e.type, e, contents);
}
onScroll(e) {
this.scrollLeft = this.fullsize ? window.scrollX : this.scroller.scrollLeft;
this.scrollTop = this.fullsize ? window.scrollY : this.scroller.scrollTop;
}
onResize(e) {
this.resizeCanceler = true;
}
onTouchStart(e) {
let { screenX, screenY } = e.touches[0];
if (this.fullsize) {
this.enableScroll();
}
this.touchCanceler = true;
if (!this.startTouchX) {
this.startTouchX = screenX;
this.startTouchY = screenY;
this.startTime = this.now();
}
this.endTouchX = screenX;
this.endTouchY = screenY;
this.endTime = this.now();
}
onTouchMove(e) {
let { screenX, screenY } = e.touches[0];
let deltaY = Math.abs(screenY - this.endTouchY);
this.touchCanceler = true;
if (!this.fullsize && deltaY < 10) {
this.element.scrollLeft -= screenX - this.endTouchX;
}
this.endTouchX = screenX;
this.endTouchY = screenY;
this.endTime = this.now();
}
onTouchEnd(e) {
if (this.fullsize) {
this.disableScroll();
}
this.touchCanceler = false;
let swipped = this.wasSwiped();
if (swipped !== 0) {
this.snap(swipped);
} else {
this.snap();
}
this.startTouchX = undefined;
this.startTouchY = undefined;
this.startTime = undefined;
this.endTouchX = undefined;
this.endTouchY = undefined;
this.endTime = undefined;
}
wasSwiped() {
let snapWidth = this.layout.pageWidth * this.layout.divisor;
let distance = this.endTouchX - this.startTouchX;
let absolute = Math.abs(distance);
let time = this.endTime - this.startTime;
let velocity = distance / time;
let minVelocity = this.settings.minVelocity;
if (absolute <= this.settings.minDistance || absolute >= snapWidth) {
return 0;
}
if (velocity > minVelocity) {
// previous
return -1;
} else if (velocity < -minVelocity) {
// next
return 1;
}
}
needsSnap() {
let left = this.scrollLeft;
let snapWidth = this.layout.pageWidth * this.layout.divisor;
return left % snapWidth !== 0;
}
snap(howMany = 0) {
let left = this.scrollLeft;
let snapWidth = this.layout.pageWidth * this.layout.divisor;
let snapTo = Math.round(left / snapWidth) * snapWidth;
if (howMany) {
snapTo += howMany * snapWidth;
}
return this.smoothScrollTo(snapTo);
}
smoothScrollTo(destination) {
const deferred = new defer();
const start = this.scrollLeft;
const startTime = this.now();
const duration = this.settings.duration;
this.snapping = true;
// add animation loop
function tick() {
const now = this.now();
const time = Math.min(1, (now - startTime) / duration);
if (this.touchCanceler || this.resizeCanceler) {
this.resizeCanceler = false;
this.snapping = false;
deferred.resolve();
return;
}
if (time < 1) {
window.requestAnimationFrame(tick.bind(this));
this.scrollTo(start + (destination - start) * time, 0);
} else {
this.scrollTo(destination, 0);
this.snapping = false;
deferred.resolve();
}
}
tick.call(this);
return deferred.promise;
}
scrollTo(left = 0, top = 0) {
if (this.fullsize) {
window.scroll(left, top);
} else {
this.scroller.scrollLeft = left;
this.scroller.scrollTop = top;
}
}
now() {
return "now" in window.performance
? performance.now()
: new Date().getTime();
}
destroy() {
if (!this.scroller) {
return;
}
if (this.fullsize) {
this.enableScroll();
}
this.removeListeners();
this.scroller = undefined;
}
}
EventEmitter(Snap.prototype);

View file

@ -1,363 +1,337 @@
import {uuid, isNumber, isElement, windowBounds, extend} from "../../utils/core";
import throttle from 'lodash/throttle'
import throttle from "lodash/throttle";
import {
extend,
isElement,
isNumber,
uuid,
windowBounds,
} from "../../utils/core";
class Stage {
constructor(_options) {
this.settings = _options || {};
this.id = "epubjs-container-" + uuid();
this.container = this.create(this.settings);
if(this.settings.hidden) {
this.wrapper = this.wrap(this.container);
}
}
/*
* Creates an element to render to.
* Resizes to passed width and height or to the elements size
*/
create(options){
let height = options.height;// !== false ? options.height : "100%";
let width = options.width;// !== false ? options.width : "100%";
let overflow = options.overflow || false;
let axis = options.axis || "vertical";
let direction = options.direction;
extend(this.settings, options);
if(options.height && isNumber(options.height)) {
height = options.height + "px";
}
if(options.width && isNumber(options.width)) {
width = options.width + "px";
}
// Create new container element
let container = document.createElement("div");
container.id = this.id;
container.classList.add("epub-container");
// Style Element
// container.style.fontSize = "0";
container.style.wordSpacing = "0";
container.style.lineHeight = "0";
container.style.verticalAlign = "top";
container.style.position = "relative";
if(axis === "horizontal") {
// container.style.whiteSpace = "nowrap";
container.style.display = "flex";
container.style.flexDirection = "row";
container.style.flexWrap = "nowrap";
}
if(width){
container.style.width = width;
}
if(height){
container.style.height = height;
}
if (overflow) {
if (overflow === "scroll" && axis === "vertical") {
container.style["overflow-y"] = overflow;
container.style["overflow-x"] = "hidden";
} else if (overflow === "scroll" && axis === "horizontal") {
container.style["overflow-y"] = "hidden";
container.style["overflow-x"] = overflow;
} else {
container.style["overflow"] = overflow;
}
}
if (direction) {
container.dir = direction;
container.style["direction"] = direction;
}
if (direction && this.settings.fullsize) {
document.body.style["direction"] = direction;
}
return container;
}
wrap(container) {
var wrapper = document.createElement("div");
wrapper.style.visibility = "hidden";
wrapper.style.overflow = "hidden";
wrapper.style.width = "0";
wrapper.style.height = "0";
wrapper.appendChild(container);
return wrapper;
}
getElement(_element){
var element;
if(isElement(_element)) {
element = _element;
} else if (typeof _element === "string") {
element = document.getElementById(_element);
}
if(!element){
throw new Error("Not an Element");
}
return element;
}
attachTo(what){
var element = this.getElement(what);
var base;
if(!element){
return;
}
if(this.settings.hidden) {
base = this.wrapper;
} else {
base = this.container;
}
element.appendChild(base);
this.element = element;
return element;
}
getContainer() {
return this.container;
}
onResize(func){
// Only listen to window for resize event if width and height are not fixed.
// This applies if it is set to a percent or auto.
if(!isNumber(this.settings.width) ||
!isNumber(this.settings.height) ) {
this.resizeFunc = throttle(func, 50);
window.addEventListener("resize", this.resizeFunc, false);
}
}
onOrientationChange(func){
this.orientationChangeFunc = func;
window.addEventListener("orientationchange", this.orientationChangeFunc, false);
}
size(width, height){
var bounds;
let _width = width || this.settings.width;
let _height = height || this.settings.height;
// If width or height are set to false, inherit them from containing element
if(width === null) {
bounds = this.element.getBoundingClientRect();
if(bounds.width) {
width = Math.floor(bounds.width);
this.container.style.width = width + "px";
}
} else {
if (isNumber(width)) {
this.container.style.width = width + "px";
} else {
this.container.style.width = width;
}
}
if(height === null) {
bounds = bounds || this.element.getBoundingClientRect();
if(bounds.height) {
height = bounds.height;
this.container.style.height = height + "px";
}
} else {
if (isNumber(height)) {
this.container.style.height = height + "px";
} else {
this.container.style.height = height;
}
}
if(!isNumber(width)) {
width = this.container.clientWidth;
}
if(!isNumber(height)) {
height = this.container.clientHeight;
}
this.containerStyles = window.getComputedStyle(this.container);
this.containerPadding = {
left: parseFloat(this.containerStyles["padding-left"]) || 0,
right: parseFloat(this.containerStyles["padding-right"]) || 0,
top: parseFloat(this.containerStyles["padding-top"]) || 0,
bottom: parseFloat(this.containerStyles["padding-bottom"]) || 0
};
// Bounds not set, get them from window
let _windowBounds = windowBounds();
let bodyStyles = window.getComputedStyle(document.body);
let bodyPadding = {
left: parseFloat(bodyStyles["padding-left"]) || 0,
right: parseFloat(bodyStyles["padding-right"]) || 0,
top: parseFloat(bodyStyles["padding-top"]) || 0,
bottom: parseFloat(bodyStyles["padding-bottom"]) || 0
};
if (!_width) {
width = _windowBounds.width -
bodyPadding.left -
bodyPadding.right;
}
if ((this.settings.fullsize && !_height) || !_height) {
height = _windowBounds.height -
bodyPadding.top -
bodyPadding.bottom;
}
return {
width: width -
this.containerPadding.left -
this.containerPadding.right,
height: height -
this.containerPadding.top -
this.containerPadding.bottom
};
}
bounds(){
let box;
if (this.container.style.overflow !== "visible") {
box = this.container && this.container.getBoundingClientRect();
}
if(!box || !box.width || !box.height) {
return windowBounds();
} else {
return box;
}
}
getSheet(){
var style = document.createElement("style");
// WebKit hack --> https://davidwalsh.name/add-rules-stylesheets
style.appendChild(document.createTextNode(""));
document.head.appendChild(style);
return style.sheet;
}
addStyleRules(selector, rulesArray){
var scope = "#" + this.id + " ";
var rules = "";
if(!this.sheet){
this.sheet = this.getSheet();
}
rulesArray.forEach(function(set) {
for (var prop in set) {
if(set.hasOwnProperty(prop)) {
rules += prop + ":" + set[prop] + ";";
}
}
});
this.sheet.insertRule(scope + selector + " {" + rules + "}", 0);
}
axis(axis) {
if(axis === "horizontal") {
this.container.style.display = "flex";
this.container.style.flexDirection = "row";
this.container.style.flexWrap = "nowrap";
} else {
this.container.style.display = "block";
}
this.settings.axis = axis;
}
// orientation(orientation) {
// if (orientation === "landscape") {
//
// } else {
//
// }
//
// this.orientation = orientation;
// }
direction(dir) {
if (this.container) {
this.container.dir = dir;
this.container.style["direction"] = dir;
}
if (this.settings.fullsize) {
document.body.style["direction"] = dir;
}
this.settings.dir = dir;
}
overflow(overflow) {
if (this.container) {
if (overflow === "scroll" && this.settings.axis === "vertical") {
this.container.style["overflow-y"] = overflow;
this.container.style["overflow-x"] = "hidden";
} else if (overflow === "scroll" && this.settings.axis === "horizontal") {
this.container.style["overflow-y"] = "hidden";
this.container.style["overflow-x"] = overflow;
} else {
this.container.style["overflow"] = overflow;
}
}
this.settings.overflow = overflow;
}
destroy() {
var base;
if (this.element) {
if(this.settings.hidden) {
base = this.wrapper;
} else {
base = this.container;
}
if(this.element.contains(this.container)) {
this.element.removeChild(this.container);
}
window.removeEventListener("resize", this.resizeFunc);
window.removeEventListener("orientationChange", this.orientationChangeFunc);
}
}
constructor(_options) {
this.settings = _options || {};
this.id = "epubjs-container-" + uuid();
this.container = this.create(this.settings);
if (this.settings.hidden) {
this.wrapper = this.wrap(this.container);
}
}
/*
* Creates an element to render to.
* Resizes to passed width and height or to the elements size
*/
create(options) {
let height = options.height;
let width = options.width;
let overflow = options.overflow || false;
let axis = options.axis || "vertical";
let direction = options.direction;
extend(this.settings, options);
if (options.height && isNumber(options.height)) {
height = options.height + "px";
}
if (options.width && isNumber(options.width)) {
width = options.width + "px";
}
// Create new container element
let container = document.createElement("div");
container.id = this.id;
container.classList.add("epub-container");
// Style Element
container.style.wordSpacing = "0";
container.style.lineHeight = "0";
container.style.verticalAlign = "top";
container.style.position = "relative";
if (axis === "horizontal") {
container.style.display = "flex";
container.style.flexDirection = "row";
container.style.flexWrap = "nowrap";
}
if (width) {
container.style.width = width;
}
if (height) {
container.style.height = height;
}
if (overflow) {
if (overflow === "scroll" && axis === "vertical") {
container.style["overflow-y"] = overflow;
container.style["overflow-x"] = "hidden";
} else if (overflow === "scroll" && axis === "horizontal") {
container.style["overflow-y"] = "hidden";
container.style["overflow-x"] = overflow;
} else {
container.style["overflow"] = overflow;
}
}
if (direction) {
container.dir = direction;
container.style["direction"] = direction;
}
if (direction && this.settings.fullsize) {
document.body.style["direction"] = direction;
}
return container;
}
wrap(container) {
var wrapper = document.createElement("div");
wrapper.style.visibility = "hidden";
wrapper.style.overflow = "hidden";
wrapper.style.width = "0";
wrapper.style.height = "0";
wrapper.appendChild(container);
return wrapper;
}
getElement(_element) {
var element;
if (isElement(_element)) {
element = _element;
} else if (typeof _element === "string") {
element = document.getElementById(_element);
}
if (!element) {
throw new Error("Not an Element");
}
return element;
}
attachTo(what) {
var element = this.getElement(what);
var base;
if (!element) {
return;
}
if (this.settings.hidden) {
base = this.wrapper;
} else {
base = this.container;
}
element.appendChild(base);
this.element = element;
return element;
}
getContainer() {
return this.container;
}
onResize(func) {
// Only listen to window for resize event if width and height are not fixed.
// This applies if it is set to a percent or auto.
if (!isNumber(this.settings.width) || !isNumber(this.settings.height)) {
this.resizeFunc = throttle(func, 50);
window.addEventListener("resize", this.resizeFunc, false);
}
}
onOrientationChange(func) {
this.orientationChangeFunc = func;
window.addEventListener(
"orientationchange",
this.orientationChangeFunc,
false
);
}
size(width, height) {
var bounds;
let _width = width || this.settings.width;
let _height = height || this.settings.height;
// If width or height are set to false, inherit them from containing element
if (width === null) {
bounds = this.element.getBoundingClientRect();
if (bounds.width) {
width = Math.floor(bounds.width);
this.container.style.width = width + "px";
}
} else {
if (isNumber(width)) {
this.container.style.width = width + "px";
} else {
this.container.style.width = width;
}
}
if (height === null) {
bounds = bounds || this.element.getBoundingClientRect();
if (bounds.height) {
height = bounds.height;
this.container.style.height = height + "px";
}
} else {
if (isNumber(height)) {
this.container.style.height = height + "px";
} else {
this.container.style.height = height;
}
}
if (!isNumber(width)) {
width = this.container.clientWidth;
}
if (!isNumber(height)) {
height = this.container.clientHeight;
}
this.containerStyles = window.getComputedStyle(this.container);
this.containerPadding = {
left: parseFloat(this.containerStyles["padding-left"]) || 0,
right: parseFloat(this.containerStyles["padding-right"]) || 0,
top: parseFloat(this.containerStyles["padding-top"]) || 0,
bottom: parseFloat(this.containerStyles["padding-bottom"]) || 0,
};
// Bounds not set, get them from window
let _windowBounds = windowBounds();
let bodyStyles = window.getComputedStyle(document.body);
let bodyPadding = {
left: parseFloat(bodyStyles["padding-left"]) || 0,
right: parseFloat(bodyStyles["padding-right"]) || 0,
top: parseFloat(bodyStyles["padding-top"]) || 0,
bottom: parseFloat(bodyStyles["padding-bottom"]) || 0,
};
if (!_width) {
width = _windowBounds.width - bodyPadding.left - bodyPadding.right;
}
if ((this.settings.fullsize && !_height) || !_height) {
height = _windowBounds.height - bodyPadding.top - bodyPadding.bottom;
}
return {
width: width - this.containerPadding.left - this.containerPadding.right,
height: height - this.containerPadding.top - this.containerPadding.bottom,
};
}
bounds() {
let box;
if (this.container.style.overflow !== "visible") {
box = this.container && this.container.getBoundingClientRect();
}
if (!box || !box.width || !box.height) {
return windowBounds();
} else {
return box;
}
}
getSheet() {
var style = document.createElement("style");
// WebKit hack --> https://davidwalsh.name/add-rules-stylesheets
style.appendChild(document.createTextNode(""));
document.head.appendChild(style);
return style.sheet;
}
addStyleRules(selector, rulesArray) {
var scope = "#" + this.id + " ";
var rules = "";
if (!this.sheet) {
this.sheet = this.getSheet();
}
rulesArray.forEach(function (set) {
for (var prop in set) {
if (set.hasOwnProperty(prop)) {
rules += prop + ":" + set[prop] + ";";
}
}
});
this.sheet.insertRule(scope + selector + " {" + rules + "}", 0);
}
axis(axis) {
if (axis === "horizontal") {
this.container.style.display = "flex";
this.container.style.flexDirection = "row";
this.container.style.flexWrap = "nowrap";
} else {
this.container.style.display = "block";
}
this.settings.axis = axis;
}
direction(dir) {
if (this.container) {
this.container.dir = dir;
this.container.style["direction"] = dir;
}
if (this.settings.fullsize) {
document.body.style["direction"] = dir;
}
this.settings.dir = dir;
}
overflow(overflow) {
if (this.container) {
if (overflow === "scroll" && this.settings.axis === "vertical") {
this.container.style["overflow-y"] = overflow;
this.container.style["overflow-x"] = "hidden";
} else if (overflow === "scroll" && this.settings.axis === "horizontal") {
this.container.style["overflow-y"] = "hidden";
this.container.style["overflow-x"] = overflow;
} else {
this.container.style["overflow"] = overflow;
}
}
this.settings.overflow = overflow;
}
destroy() {
if (this.element) {
if (this.element.contains(this.container)) {
this.element.removeChild(this.container);
}
window.removeEventListener("resize", this.resizeFunc);
window.removeEventListener(
"orientationChange",
this.orientationChangeFunc
);
}
}
}
export default Stage;

View file

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

File diff suppressed because it is too large Load diff

View file

@ -1,430 +1,368 @@
import EventEmitter from "event-emitter";
import {extend, borders, uuid, isNumber, bounds, defer, qs, parse} from "../../utils/core";
import EpubCFI from "../../epubcfi";
import Contents from "../../contents";
import EpubCFI from "../../epubcfi";
import { EVENTS } from "../../utils/constants";
import {
borders,
bounds,
defer,
extend,
isNumber,
parse,
qs,
uuid,
} from "../../utils/core";
class InlineView {
constructor(section, options) {
this.settings = extend({
ignoreClass : "",
axis: "vertical",
width: 0,
height: 0,
layout: undefined,
globalLayoutProperties: {},
}, options || {});
constructor(section, options) {
this.settings = extend(
{
ignoreClass: "",
axis: "vertical",
width: 0,
height: 0,
layout: undefined,
globalLayoutProperties: {},
},
options || {}
);
this.id = "epubjs-view:" + uuid();
this.section = section;
this.index = section.index;
this.element = this.container(this.settings.axis);
this.added = false;
this.displayed = false;
this.rendered = false;
this.width = this.settings.width;
this.height = this.settings.height;
this.fixedWidth = 0;
this.fixedHeight = 0;
// Blank Cfi for Parsing
this.epubcfi = new EpubCFI();
this.layout = this.settings.layout;
}
container(axis) {
var element = document.createElement("div");
element.classList.add("epub-view");
element.style.overflow = "hidden";
if (axis && axis == "horizontal") {
element.style.display = "inline-block";
} else {
element.style.display = "block";
}
return element;
}
create() {
if (this.frame) {
return this.frame;
}
if (!this.element) {
this.element = this.createContainer();
}
this.frame = document.createElement("div");
this.frame.id = this.id;
this.frame.style.overflow = "hidden";
this.frame.style.wordSpacing = "initial";
this.frame.style.lineHeight = "initial";
this.resizing = true;
this.element.style.visibility = "hidden";
this.frame.style.visibility = "hidden";
if (this.settings.axis === "horizontal") {
this.frame.style.width = "auto";
this.frame.style.height = "0";
} else {
this.frame.style.width = "0";
this.frame.style.height = "auto";
}
this._width = 0;
this._height = 0;
this.element.appendChild(this.frame);
this.added = true;
this.elementBounds = bounds(this.element);
return this.frame;
}
render(request, show) {
this.create();
// Fit to size of the container, apply padding
this.size();
// Render Chain
return this.section
.render(request)
.then(
function (contents) {
return this.load(contents);
}.bind(this)
)
.then(function () {}.bind(this))
.then(
function () {
// apply the layout function to the contents
this.settings.layout.format(this.contents);
// Listen for events that require an expansion of the iframe
this.addListeners();
if (show !== false) {
this.show();
}
this.emit(EVENTS.VIEWS.RENDERED, this.section);
}.bind(this)
)
.catch(
function (e) {
this.emit(EVENTS.VIEWS.LOAD_ERROR, e);
}.bind(this)
);
}
// Determine locks base on settings
size(_width, _height) {
var width = _width || this.settings.width;
var height = _height || this.settings.height;
if (this.layout.name === "pre-paginated") {
// TODO: check if these are different than the size set in chapter
this.lock("both", width, height);
} else if (this.settings.axis === "horizontal") {
this.lock("height", width, height);
} else {
this.lock("width", width, height);
}
}
// Lock an axis to element dimensions, taking borders into account
lock(what, width, height) {
var elBorders = borders(this.element);
var iframeBorders;
if (this.frame) {
iframeBorders = borders(this.frame);
} else {
iframeBorders = { width: 0, height: 0 };
}
if (what == "width" && isNumber(width)) {
this.lockedWidth = width - elBorders.width - iframeBorders.width;
this.resize(this.lockedWidth, false); // width keeps ratio correct
}
if (what == "height" && isNumber(height)) {
this.lockedHeight = height - elBorders.height - iframeBorders.height;
this.resize(false, this.lockedHeight);
}
if (what === "both" && isNumber(width) && isNumber(height)) {
this.lockedWidth = width - elBorders.width - iframeBorders.width;
this.lockedHeight = height - elBorders.height - iframeBorders.height;
this.resize(this.lockedWidth, this.lockedHeight);
}
}
// Resize a single axis based on content dimensions
expand(force) {
var width = this.lockedWidth;
var height = this.lockedHeight;
var textWidth, textHeight;
if (!this.frame || this._expanding) return;
this._expanding = true;
// Expand Horizontally
if (this.settings.axis === "horizontal") {
width = this.contentWidth(textWidth);
} // Expand Vertically
else if (this.settings.axis === "vertical") {
height = this.contentHeight(textHeight);
}
// Only Resize if dimensions have changed or
// if Frame is still hidden, so needs reframing
if (this._needsReframe || width != this._width || height != this._height) {
this.resize(width, height);
}
this._expanding = false;
}
contentWidth(min) {
return this.frame.scrollWidth;
}
contentHeight(min) {
return this.frame.scrollHeight;
}
resize(width, height) {
if (!this.frame) return;
if (isNumber(width)) {
this.frame.style.width = width + "px";
this._width = width;
}
if (isNumber(height)) {
this.frame.style.height = height + "px";
this._height = height;
}
this.prevBounds = this.elementBounds;
this.elementBounds = bounds(this.element);
let size = {
width: this.elementBounds.width,
height: this.elementBounds.height,
widthDelta: this.elementBounds.width - this.prevBounds.width,
heightDelta: this.elementBounds.height - this.prevBounds.height,
};
this.id = "epubjs-view:" + uuid();
this.section = section;
this.index = section.index;
this.onResize(this, size);
this.emit(EVENTS.VIEWS.RESIZED, size);
}
load(contents) {
var loading = new defer();
var loaded = loading.promise;
var doc = parse(contents, "text/html");
var body = qs(doc, "body");
this.frame.innerHTML = body.innerHTML;
this.element = this.container(this.settings.axis);
this.document = this.frame.ownerDocument;
this.window = this.document.defaultView;
this.added = false;
this.displayed = false;
this.rendered = false;
this.width = this.settings.width;
this.height = this.settings.height;
this.fixedWidth = 0;
this.fixedHeight = 0;
// Blank Cfi for Parsing
this.epubcfi = new EpubCFI();
this.layout = this.settings.layout;
// Dom events to listen for
// this.listenedEvents = ["keydown", "keyup", "keypressed", "mouseup", "mousedown", "click", "touchend", "touchstart"];
}
container(axis) {
var element = document.createElement("div");
element.classList.add("epub-view");
// if(this.settings.axis === "horizontal") {
// element.style.width = "auto";
// element.style.height = "0";
// } else {
// element.style.width = "0";
// element.style.height = "auto";
// }
element.style.overflow = "hidden";
if(axis && axis == "horizontal"){
element.style.display = "inline-block";
} else {
element.style.display = "block";
}
return element;
}
create() {
if(this.frame) {
return this.frame;
}
if(!this.element) {
this.element = this.createContainer();
}
this.frame = document.createElement("div");
this.frame.id = this.id;
this.frame.style.overflow = "hidden";
this.frame.style.wordSpacing = "initial";
this.frame.style.lineHeight = "initial";
this.resizing = true;
// this.frame.style.display = "none";
this.element.style.visibility = "hidden";
this.frame.style.visibility = "hidden";
if(this.settings.axis === "horizontal") {
this.frame.style.width = "auto";
this.frame.style.height = "0";
} else {
this.frame.style.width = "0";
this.frame.style.height = "auto";
}
this._width = 0;
this._height = 0;
this.element.appendChild(this.frame);
this.added = true;
this.elementBounds = bounds(this.element);
return this.frame;
}
render(request, show) {
// view.onLayout = this.layout.format.bind(this.layout);
this.create();
// Fit to size of the container, apply padding
this.size();
// Render Chain
return this.section.render(request)
.then(function(contents){
return this.load(contents);
}.bind(this))
// .then(function(doc){
// return this.hooks.content.trigger(view, this);
// }.bind(this))
.then(function(){
// this.settings.layout.format(view.contents);
// return this.hooks.layout.trigger(view, this);
}.bind(this))
// .then(function(){
// return this.display();
// }.bind(this))
// .then(function(){
// return this.hooks.render.trigger(view, this);
// }.bind(this))
.then(function(){
// apply the layout function to the contents
this.settings.layout.format(this.contents);
// Expand the iframe to the full size of the content
// this.expand();
// Listen for events that require an expansion of the iframe
this.addListeners();
if(show !== false) {
//this.q.enqueue(function(view){
this.show();
//}, view);
}
// this.map = new Map(view, this.layout);
//this.hooks.show.trigger(view, this);
this.emit(EVENTS.VIEWS.RENDERED, this.section);
}.bind(this))
.catch(function(e){
this.emit(EVENTS.VIEWS.LOAD_ERROR, e);
}.bind(this));
}
// Determine locks base on settings
size(_width, _height) {
var width = _width || this.settings.width;
var height = _height || this.settings.height;
if(this.layout.name === "pre-paginated") {
// TODO: check if these are different than the size set in chapter
this.lock("both", width, height);
} else if(this.settings.axis === "horizontal") {
this.lock("height", width, height);
} else {
this.lock("width", width, height);
}
}
// Lock an axis to element dimensions, taking borders into account
lock(what, width, height) {
var elBorders = borders(this.element);
var iframeBorders;
this.contents = new Contents(this.document, this.frame);
if(this.frame) {
iframeBorders = borders(this.frame);
} else {
iframeBorders = {width: 0, height: 0};
}
this.rendering = false;
if(what == "width" && isNumber(width)){
this.lockedWidth = width - elBorders.width - iframeBorders.width;
this.resize(this.lockedWidth, false); // width keeps ratio correct
}
if(what == "height" && isNumber(height)){
this.lockedHeight = height - elBorders.height - iframeBorders.height;
this.resize(false, this.lockedHeight);
}
if(what === "both" &&
isNumber(width) &&
isNumber(height)){
this.lockedWidth = width - elBorders.width - iframeBorders.width;
this.lockedHeight = height - elBorders.height - iframeBorders.height;
this.resize(this.lockedWidth, this.lockedHeight);
}
}
// Resize a single axis based on content dimensions
expand(force) {
var width = this.lockedWidth;
var height = this.lockedHeight;
var textWidth, textHeight;
if(!this.frame || this._expanding) return;
this._expanding = true;
loading.resolve(this.contents);
// Expand Horizontally
if(this.settings.axis === "horizontal") {
width = this.contentWidth(textWidth);
} // Expand Vertically
else if(this.settings.axis === "vertical") {
height = this.contentHeight(textHeight);
}
return loaded;
}
// Only Resize if dimensions have changed or
// if Frame is still hidden, so needs reframing
if(this._needsReframe || width != this._width || height != this._height){
this.resize(width, height);
}
setLayout(layout) {
this.layout = layout;
}
this._expanding = false;
}
resizeListenters() {}
contentWidth(min) {
return this.frame.scrollWidth;
}
addListeners() {
//TODO: Add content listeners for expanding
}
contentHeight(min) {
return this.frame.scrollHeight;
}
removeListeners(layoutFunc) {
//TODO: remove content listeners for expanding
}
display(request) {
var displayed = new defer();
resize(width, height) {
if (!this.displayed) {
this.render(request).then(
function () {
this.emit(EVENTS.VIEWS.DISPLAYED, this);
this.onDisplayed(this);
if(!this.frame) return;
this.displayed = true;
if(isNumber(width)){
this.frame.style.width = width + "px";
this._width = width;
}
displayed.resolve(this);
}.bind(this)
);
} else {
displayed.resolve(this);
}
if(isNumber(height)){
this.frame.style.height = height + "px";
this._height = height;
}
return displayed.promise;
}
this.prevBounds = this.elementBounds;
show() {
this.element.style.visibility = "visible";
this.elementBounds = bounds(this.element);
if (this.frame) {
this.frame.style.visibility = "visible";
}
let size = {
width: this.elementBounds.width,
height: this.elementBounds.height,
widthDelta: this.elementBounds.width - this.prevBounds.width,
heightDelta: this.elementBounds.height - this.prevBounds.height,
};
this.emit(EVENTS.VIEWS.SHOWN, this);
}
this.onResize(this, size);
hide() {
this.element.style.visibility = "hidden";
this.frame.style.visibility = "hidden";
this.emit(EVENTS.VIEWS.RESIZED, size);
this.stopExpanding = true;
this.emit(EVENTS.VIEWS.HIDDEN, this);
}
}
position() {
return this.element.getBoundingClientRect();
}
locationOf(target) {
var parentPos = this.frame.getBoundingClientRect();
var targetPos = this.contents.locationOf(target, this.settings.ignoreClass);
load(contents) {
var loading = new defer();
var loaded = loading.promise;
var doc = parse(contents, "text/html");
var body = qs(doc, "body");
return {
left: window.scrollX + parentPos.left + targetPos.left,
top: window.scrollY + parentPos.top + targetPos.top,
};
}
/*
var srcs = doc.querySelectorAll("[src]");
onDisplayed(view) {
// Stub, override with a custom functions
}
Array.prototype.slice.call(srcs)
.forEach(function(item) {
var src = item.getAttribute("src");
var assetUri = URI(src);
var origin = assetUri.origin();
var absoluteUri;
onResize(view, e) {
// Stub, override with a custom functions
}
if (!origin) {
absoluteUri = assetUri.absoluteTo(this.section.url);
item.src = absoluteUri;
}
}.bind(this));
*/
this.frame.innerHTML = body.innerHTML;
bounds() {
if (!this.elementBounds) {
this.elementBounds = bounds(this.element);
}
return this.elementBounds;
}
this.document = this.frame.ownerDocument;
this.window = this.document.defaultView;
destroy() {
if (this.displayed) {
this.displayed = false;
this.contents = new Contents(this.document, this.frame);
this.removeListeners();
this.rendering = false;
this.stopExpanding = true;
this.element.removeChild(this.frame);
this.displayed = false;
this.frame = null;
loading.resolve(this.contents);
return loaded;
}
setLayout(layout) {
this.layout = layout;
}
resizeListenters() {
// Test size again
// clearTimeout(this.expanding);
// this.expanding = setTimeout(this.expand.bind(this), 350);
}
addListeners() {
//TODO: Add content listeners for expanding
}
removeListeners(layoutFunc) {
//TODO: remove content listeners for expanding
}
display(request) {
var displayed = new defer();
if (!this.displayed) {
this.render(request).then(function () {
this.emit(EVENTS.VIEWS.DISPLAYED, this);
this.onDisplayed(this);
this.displayed = true;
displayed.resolve(this);
}.bind(this));
} else {
displayed.resolve(this);
}
return displayed.promise;
}
show() {
this.element.style.visibility = "visible";
if(this.frame){
this.frame.style.visibility = "visible";
}
this.emit(EVENTS.VIEWS.SHOWN, this);
}
hide() {
// this.frame.style.display = "none";
this.element.style.visibility = "hidden";
this.frame.style.visibility = "hidden";
this.stopExpanding = true;
this.emit(EVENTS.VIEWS.HIDDEN, this);
}
position() {
return this.element.getBoundingClientRect();
}
locationOf(target) {
var parentPos = this.frame.getBoundingClientRect();
var targetPos = this.contents.locationOf(target, this.settings.ignoreClass);
return {
"left": window.scrollX + parentPos.left + targetPos.left,
"top": window.scrollY + parentPos.top + targetPos.top
};
}
onDisplayed(view) {
// Stub, override with a custom functions
}
onResize(view, e) {
// Stub, override with a custom functions
}
bounds() {
if(!this.elementBounds) {
this.elementBounds = bounds(this.element);
}
return this.elementBounds;
}
destroy() {
if(this.displayed){
this.displayed = false;
this.removeListeners();
this.stopExpanding = true;
this.element.removeChild(this.frame);
this.displayed = false;
this.frame = null;
this._textWidth = null;
this._textHeight = null;
this._width = null;
this._height = null;
}
// this.element.style.height = "0px";
// this.element.style.width = "0px";
}
this._textWidth = null;
this._textHeight = null;
this._width = null;
this._height = null;
}
}
}
EventEmitter(InlineView.prototype);

View file

@ -10,502 +10,459 @@ import { nodeBounds } from "./utils/core";
* @param {boolean} [dev] toggle developer highlighting
*/
class Mapping {
constructor(layout, direction, axis, dev=false) {
this.layout = layout;
this.horizontal = (axis === "horizontal") ? true : false;
this.direction = direction || "ltr";
this._dev = dev;
}
/**
* Find CFI pairs for entire section at once
*/
section(view) {
var ranges = this.findRanges(view);
var map = this.rangeListToCfiList(view.section.cfiBase, ranges);
return map;
}
/**
* Find CFI pairs for a page
* @param {Contents} contents Contents from view
* @param {string} cfiBase string of the base for a cfi
* @param {number} start position to start at
* @param {number} end position to end at
*/
page(contents, cfiBase, start, end) {
var root = contents && contents.document ? contents.document.body : false;
var result;
if (!root) {
return;
}
result = this.rangePairToCfiPair(cfiBase, {
start: this.findStart(root, start, end),
end: this.findEnd(root, start, end)
});
if (this._dev === true) {
let doc = contents.document;
let startRange = new EpubCFI(result.start).toRange(doc);
let endRange = new EpubCFI(result.end).toRange(doc);
let selection = doc.defaultView.getSelection();
let r = doc.createRange();
selection.removeAllRanges();
r.setStart(startRange.startContainer, startRange.startOffset);
r.setEnd(endRange.endContainer, endRange.endOffset);
selection.addRange(r);
}
return result;
}
/**
* Walk a node, preforming a function on each node it finds
* @private
* @param {Node} root Node to walkToNode
* @param {function} func walk function
* @return {*} returns the result of the walk function
*/
walk(root, func) {
// IE11 has strange issue, if root is text node IE throws exception on
// calling treeWalker.nextNode(), saying
// Unexpected call to method or property access instead of returning null value
if(root && root.nodeType === Node.TEXT_NODE) {
return;
}
// safeFilter is required so that it can work in IE as filter is a function for IE
// and for other browser filter is an object.
var filter = {
acceptNode: function(node) {
if (node.data.trim().length > 0) {
return NodeFilter.FILTER_ACCEPT;
} else {
return NodeFilter.FILTER_REJECT;
}
}
};
var safeFilter = filter.acceptNode;
safeFilter.acceptNode = filter.acceptNode;
var treeWalker = document.createTreeWalker(root, NodeFilter.SHOW_TEXT, safeFilter, false);
var node;
var result;
while ((node = treeWalker.nextNode())) {
result = func(node);
if(result) break;
}
return result;
}
findRanges(view){
var columns = [];
var scrollWidth = view.contents.scrollWidth();
var spreads = Math.ceil( scrollWidth / this.layout.spreadWidth);
var count = spreads * this.layout.divisor;
var columnWidth = this.layout.columnWidth;
var gap = this.layout.gap;
var start, end;
for (var i = 0; i < count.pages; i++) {
start = (columnWidth + gap) * i;
end = (columnWidth * (i+1)) + (gap * i);
columns.push({
start: this.findStart(view.document.body, start, end),
end: this.findEnd(view.document.body, start, end)
});
}
return columns;
}
/**
* Find Start Range
* @private
* @param {Node} root root node
* @param {number} start position to start at
* @param {number} end position to end at
* @return {Range}
*/
findStart(root, start, end){
var stack = [root];
var $el;
var found;
var $prev = root;
while (stack.length) {
$el = stack.shift();
found = this.walk($el, (node) => {
var left, right, top, bottom;
var elPos;
var elRange;
elPos = nodeBounds(node);
if (this.horizontal && this.direction === "ltr") {
left = this.horizontal ? elPos.left : elPos.top;
right = this.horizontal ? elPos.right : elPos.bottom;
if( left >= start && left <= end ) {
return node;
} else if (right > start) {
return node;
} else {
$prev = node;
stack.push(node);
}
} else if (this.horizontal && this.direction === "rtl") {
left = elPos.left;
right = elPos.right;
if( right <= end && right >= start ) {
return node;
} else if (left < end) {
return node;
} else {
$prev = node;
stack.push(node);
}
} else {
top = elPos.top;
bottom = elPos.bottom;
if( top >= start && top <= end ) {
return node;
} else if (bottom > start) {
return node;
} else {
$prev = node;
stack.push(node);
}
}
});
if(found) {
return this.findTextStartRange(found, start, end);
}
}
// Return last element
return this.findTextStartRange($prev, start, end);
}
/**
* Find End Range
* @private
* @param {Node} root root node
* @param {number} start position to start at
* @param {number} end position to end at
* @return {Range}
*/
findEnd(root, start, end){
var stack = [root];
var $el;
var $prev = root;
var found;
while (stack.length) {
$el = stack.shift();
found = this.walk($el, (node) => {
var left, right, top, bottom;
var elPos;
var elRange;
elPos = nodeBounds(node);
if (this.horizontal && this.direction === "ltr") {
left = Math.round(elPos.left);
right = Math.round(elPos.right);
if(left > end && $prev) {
return $prev;
} else if(right > end) {
return node;
} else {
$prev = node;
stack.push(node);
}
} else if (this.horizontal && this.direction === "rtl") {
left = Math.round(this.horizontal ? elPos.left : elPos.top);
right = Math.round(this.horizontal ? elPos.right : elPos.bottom);
if(right < start && $prev) {
return $prev;
} else if(left < start) {
return node;
} else {
$prev = node;
stack.push(node);
}
} else {
top = Math.round(elPos.top);
bottom = Math.round(elPos.bottom);
if(top > end && $prev) {
return $prev;
} else if(bottom > end) {
return node;
} else {
$prev = node;
stack.push(node);
}
}
});
if(found){
return this.findTextEndRange(found, start, end);
}
}
// end of chapter
return this.findTextEndRange($prev, start, end);
}
/**
* Find Text Start Range
* @private
* @param {Node} root root node
* @param {number} start position to start at
* @param {number} end position to end at
* @return {Range}
*/
findTextStartRange(node, start, end){
var ranges = this.splitTextNodeIntoRanges(node);
var range;
var pos;
var left, top, right;
for (var i = 0; i < ranges.length; i++) {
range = ranges[i];
pos = range.getBoundingClientRect();
if (this.horizontal && this.direction === "ltr") {
left = pos.left;
if( left >= start ) {
return range;
}
} else if (this.horizontal && this.direction === "rtl") {
right = pos.right;
if( right <= end ) {
return range;
}
} else {
top = pos.top;
if( top >= start ) {
return range;
}
}
// prev = range;
}
return ranges[0];
}
/**
* Find Text End Range
* @private
* @param {Node} root root node
* @param {number} start position to start at
* @param {number} end position to end at
* @return {Range}
*/
findTextEndRange(node, start, end){
var ranges = this.splitTextNodeIntoRanges(node);
var prev;
var range;
var pos;
var left, right, top, bottom;
for (var i = 0; i < ranges.length; i++) {
range = ranges[i];
pos = range.getBoundingClientRect();
if (this.horizontal && this.direction === "ltr") {
left = pos.left;
right = pos.right;
if(left > end && prev) {
return prev;
} else if(right > end) {
return range;
}
} else if (this.horizontal && this.direction === "rtl") {
left = pos.left
right = pos.right;
if(right < start && prev) {
return prev;
} else if(left < start) {
return range;
}
} else {
top = pos.top;
bottom = pos.bottom;
if(top > end && prev) {
return prev;
} else if(bottom > end) {
return range;
}
}
prev = range;
}
// Ends before limit
return ranges[ranges.length-1];
}
/**
* Split up a text node into ranges for each word
* @private
* @param {Node} root root node
* @param {string} [_splitter] what to split on
* @return {Range[]}
*/
splitTextNodeIntoRanges(node, _splitter){
var ranges = [];
var textContent = node.textContent || "";
var text = textContent.trim();
var range;
var doc = node.ownerDocument;
var splitter = _splitter || " ";
var pos = text.indexOf(splitter);
if(pos === -1 || node.nodeType != Node.TEXT_NODE) {
range = doc.createRange();
range.selectNodeContents(node);
return [range];
}
range = doc.createRange();
range.setStart(node, 0);
range.setEnd(node, pos);
ranges.push(range);
range = false;
while ( pos != -1 ) {
pos = text.indexOf(splitter, pos + 1);
if(pos > 0) {
if(range) {
range.setEnd(node, pos);
ranges.push(range);
}
range = doc.createRange();
range.setStart(node, pos+1);
}
}
if(range) {
range.setEnd(node, text.length);
ranges.push(range);
}
return ranges;
}
/**
* Turn a pair of ranges into a pair of CFIs
* @private
* @param {string} cfiBase base string for an EpubCFI
* @param {object} rangePair { start: Range, end: Range }
* @return {object} { start: "epubcfi(...)", end: "epubcfi(...)" }
*/
rangePairToCfiPair(cfiBase, rangePair){
var startRange = rangePair.start;
var endRange = rangePair.end;
startRange.collapse(true);
endRange.collapse(false);
let startCfi = new EpubCFI(startRange, cfiBase).toString();
let endCfi = new EpubCFI(endRange, cfiBase).toString();
return {
start: startCfi,
end: endCfi
};
}
rangeListToCfiList(cfiBase, columns){
var map = [];
var cifPair;
for (var i = 0; i < columns.length; i++) {
cifPair = this.rangePairToCfiPair(cfiBase, columns[i]);
map.push(cifPair);
}
return map;
}
/**
* Set the axis for mapping
* @param {string} axis horizontal | vertical
* @return {boolean} is it horizontal?
*/
axis(axis) {
if (axis) {
this.horizontal = (axis === "horizontal") ? true : false;
}
return this.horizontal;
}
constructor(layout, direction, axis, dev = false) {
this.layout = layout;
this.horizontal = axis === "horizontal" ? true : false;
this.direction = direction || "ltr";
this._dev = dev;
}
/**
* Find CFI pairs for entire section at once
*/
section(view) {
var ranges = this.findRanges(view);
var map = this.rangeListToCfiList(view.section.cfiBase, ranges);
return map;
}
/**
* Find CFI pairs for a page
* @param {Contents} contents Contents from view
* @param {string} cfiBase string of the base for a cfi
* @param {number} start position to start at
* @param {number} end position to end at
*/
page(contents, cfiBase, start, end) {
var root = contents && contents.document ? contents.document.body : false;
var result;
if (!root) {
return;
}
result = this.rangePairToCfiPair(cfiBase, {
start: this.findStart(root, start, end),
end: this.findEnd(root, start, end),
});
if (this._dev === true) {
let doc = contents.document;
let startRange = new EpubCFI(result.start).toRange(doc);
let endRange = new EpubCFI(result.end).toRange(doc);
let selection = doc.defaultView.getSelection();
let r = doc.createRange();
selection.removeAllRanges();
r.setStart(startRange.startContainer, startRange.startOffset);
r.setEnd(endRange.endContainer, endRange.endOffset);
selection.addRange(r);
}
return result;
}
/**
* Walk a node, preforming a function on each node it finds
* @private
* @param {Node} root Node to walkToNode
* @param {function} func walk function
* @return {*} returns the result of the walk function
*/
walk(root, func) {
// IE11 has strange issue, if root is text node IE throws exception on
// calling treeWalker.nextNode(), saying
// Unexpected call to method or property access instead of returning null value
if (root && root.nodeType === Node.TEXT_NODE) {
return;
}
// safeFilter is required so that it can work in IE as filter is a function for IE
// and for other browser filter is an object.
var filter = {
acceptNode: function (node) {
if (node.data.trim().length > 0) {
return NodeFilter.FILTER_ACCEPT;
} else {
return NodeFilter.FILTER_REJECT;
}
},
};
var safeFilter = filter.acceptNode;
safeFilter.acceptNode = filter.acceptNode;
var treeWalker = document.createTreeWalker(
root,
NodeFilter.SHOW_TEXT,
safeFilter,
false
);
var node;
var result;
while ((node = treeWalker.nextNode())) {
result = func(node);
if (result) break;
}
return result;
}
findRanges(view) {
var columns = [];
var scrollWidth = view.contents.scrollWidth();
var spreads = Math.ceil(scrollWidth / this.layout.spreadWidth);
var count = spreads * this.layout.divisor;
var columnWidth = this.layout.columnWidth;
var gap = this.layout.gap;
var start, end;
for (var i = 0; i < count.pages; i++) {
start = (columnWidth + gap) * i;
end = columnWidth * (i + 1) + gap * i;
columns.push({
start: this.findStart(view.document.body, start, end),
end: this.findEnd(view.document.body, start, end),
});
}
return columns;
}
/**
* Find Start Range
* @private
* @param {Node} root root node
* @param {number} start position to start at
* @param {number} end position to end at
* @return {Range}
*/
findStart(root, start, end) {
var stack = [root];
var $el;
var found;
var $prev = root;
while (stack.length) {
$el = stack.shift();
found = this.walk($el, (node) => {
var left, right, top, bottom;
var elPos;
elPos = nodeBounds(node);
if (this.horizontal && this.direction === "ltr") {
left = this.horizontal ? elPos.left : elPos.top;
right = this.horizontal ? elPos.right : elPos.bottom;
if (left >= start && left <= end) {
return node;
} else if (right > start) {
return node;
} else {
$prev = node;
stack.push(node);
}
} else if (this.horizontal && this.direction === "rtl") {
left = elPos.left;
right = elPos.right;
if (right <= end && right >= start) {
return node;
} else if (left < end) {
return node;
} else {
$prev = node;
stack.push(node);
}
} else {
top = elPos.top;
bottom = elPos.bottom;
if (top >= start && top <= end) {
return node;
} else if (bottom > start) {
return node;
} else {
$prev = node;
stack.push(node);
}
}
});
if (found) {
return this.findTextStartRange(found, start, end);
}
}
// Return last element
return this.findTextStartRange($prev, start, end);
}
/**
* Find End Range
* @private
* @param {Node} root root node
* @param {number} start position to start at
* @param {number} end position to end at
* @return {Range}
*/
findEnd(root, start, end) {
var stack = [root];
var $el;
var $prev = root;
var found;
while (stack.length) {
$el = stack.shift();
found = this.walk($el, (node) => {
var left, right, top, bottom;
var elPos;
elPos = nodeBounds(node);
if (this.horizontal && this.direction === "ltr") {
left = Math.round(elPos.left);
right = Math.round(elPos.right);
if (left > end && $prev) {
return $prev;
} else if (right > end) {
return node;
} else {
$prev = node;
stack.push(node);
}
} else if (this.horizontal && this.direction === "rtl") {
left = Math.round(this.horizontal ? elPos.left : elPos.top);
right = Math.round(this.horizontal ? elPos.right : elPos.bottom);
if (right < start && $prev) {
return $prev;
} else if (left < start) {
return node;
} else {
$prev = node;
stack.push(node);
}
} else {
top = Math.round(elPos.top);
bottom = Math.round(elPos.bottom);
if (top > end && $prev) {
return $prev;
} else if (bottom > end) {
return node;
} else {
$prev = node;
stack.push(node);
}
}
});
if (found) {
return this.findTextEndRange(found, start, end);
}
}
// end of chapter
return this.findTextEndRange($prev, start, end);
}
/**
* Find Text Start Range
* @private
* @param {Node} root root node
* @param {number} start position to start at
* @param {number} end position to end at
* @return {Range}
*/
findTextStartRange(node, start, end) {
var ranges = this.splitTextNodeIntoRanges(node);
var range;
var pos;
var left, top, right;
for (var i = 0; i < ranges.length; i++) {
range = ranges[i];
pos = range.getBoundingClientRect();
if (this.horizontal && this.direction === "ltr") {
left = pos.left;
if (left >= start) {
return range;
}
} else if (this.horizontal && this.direction === "rtl") {
right = pos.right;
if (right <= end) {
return range;
}
} else {
top = pos.top;
if (top >= start) {
return range;
}
}
}
return ranges[0];
}
/**
* Find Text End Range
* @private
* @param {Node} root root node
* @param {number} start position to start at
* @param {number} end position to end at
* @return {Range}
*/
findTextEndRange(node, start, end) {
var ranges = this.splitTextNodeIntoRanges(node);
var prev;
var range;
var pos;
var left, right, top, bottom;
for (var i = 0; i < ranges.length; i++) {
range = ranges[i];
pos = range.getBoundingClientRect();
if (this.horizontal && this.direction === "ltr") {
left = pos.left;
right = pos.right;
if (left > end && prev) {
return prev;
} else if (right > end) {
return range;
}
} else if (this.horizontal && this.direction === "rtl") {
left = pos.left;
right = pos.right;
if (right < start && prev) {
return prev;
} else if (left < start) {
return range;
}
} else {
top = pos.top;
bottom = pos.bottom;
if (top > end && prev) {
return prev;
} else if (bottom > end) {
return range;
}
}
prev = range;
}
// Ends before limit
return ranges[ranges.length - 1];
}
/**
* Split up a text node into ranges for each word
* @private
* @param {Node} root root node
* @param {string} [_splitter] what to split on
* @return {Range[]}
*/
splitTextNodeIntoRanges(node, _splitter) {
var ranges = [];
var textContent = node.textContent || "";
var text = textContent.trim();
var range;
var doc = node.ownerDocument;
var splitter = _splitter || " ";
var pos = text.indexOf(splitter);
if (pos === -1 || node.nodeType != Node.TEXT_NODE) {
range = doc.createRange();
range.selectNodeContents(node);
return [range];
}
range = doc.createRange();
range.setStart(node, 0);
range.setEnd(node, pos);
ranges.push(range);
range = false;
while (pos != -1) {
pos = text.indexOf(splitter, pos + 1);
if (pos > 0) {
if (range) {
range.setEnd(node, pos);
ranges.push(range);
}
range = doc.createRange();
range.setStart(node, pos + 1);
}
}
if (range) {
range.setEnd(node, text.length);
ranges.push(range);
}
return ranges;
}
/**
* Turn a pair of ranges into a pair of CFIs
* @private
* @param {string} cfiBase base string for an EpubCFI
* @param {object} rangePair { start: Range, end: Range }
* @return {object} { start: "epubcfi(...)", end: "epubcfi(...)" }
*/
rangePairToCfiPair(cfiBase, rangePair) {
var startRange = rangePair.start;
var endRange = rangePair.end;
startRange.collapse(true);
endRange.collapse(false);
let startCfi = new EpubCFI(startRange, cfiBase).toString();
let endCfi = new EpubCFI(endRange, cfiBase).toString();
return {
start: startCfi,
end: endCfi,
};
}
rangeListToCfiList(cfiBase, columns) {
var map = [];
var cifPair;
for (var i = 0; i < columns.length; i++) {
cifPair = this.rangePairToCfiPair(cfiBase, columns[i]);
map.push(cifPair);
}
return map;
}
/**
* Set the axis for mapping
* @param {string} axis horizontal | vertical
* @return {boolean} is it horizontal?
*/
axis(axis) {
if (axis) {
this.horizontal = axis === "horizontal" ? true : false;
}
return this.horizontal;
}
}
export default Mapping;

View file

@ -1,357 +1,361 @@
import {qs, qsa, querySelectorByType, filterChildren, getParentByTagName} from "./utils/core";
import { filterChildren, qs, qsa, querySelectorByType } from "./utils/core";
/**
* Navigation Parser
* @param {document} xml navigation html / xhtml / ncx
*/
class Navigation {
constructor(xml) {
this.toc = [];
this.tocByHref = {};
this.tocById = {};
constructor(xml) {
this.toc = [];
this.tocByHref = {};
this.tocById = {};
this.landmarks = [];
this.landmarksByType = {};
this.landmarks = [];
this.landmarksByType = {};
this.length = 0;
if (xml) {
this.parse(xml);
}
}
this.length = 0;
if (xml) {
this.parse(xml);
}
}
/**
* Parse out the navigation items
* @param {document} xml navigation html / xhtml / ncx
*/
parse(xml) {
let isXml = xml.nodeType;
let html;
let ncx;
/**
* Parse out the navigation items
* @param {document} xml navigation html / xhtml / ncx
*/
parse(xml) {
let isXml = xml.nodeType;
let html;
let ncx;
if (isXml) {
html = qs(xml, "html");
ncx = qs(xml, "ncx");
}
if (isXml) {
html = qs(xml, "html");
ncx = qs(xml, "ncx");
}
if (!isXml) {
this.toc = this.load(xml);
} else if(html) {
this.toc = this.parseNav(xml);
this.landmarks = this.parseLandmarks(xml);
} else if(ncx){
this.toc = this.parseNcx(xml);
}
if (!isXml) {
this.toc = this.load(xml);
} else if (html) {
this.toc = this.parseNav(xml);
this.landmarks = this.parseLandmarks(xml);
} else if (ncx) {
this.toc = this.parseNcx(xml);
}
this.length = 0;
this.length = 0;
this.unpack(this.toc);
}
this.unpack(this.toc);
}
/**
* Unpack navigation items
* @private
* @param {array} toc
*/
unpack(toc) {
var item;
/**
* Unpack navigation items
* @private
* @param {array} toc
*/
unpack(toc) {
var item;
for (var i = 0; i < toc.length; i++) {
item = toc[i];
for (var i = 0; i < toc.length; i++) {
item = toc[i];
if (item.href) {
this.tocByHref[item.href] = i;
}
if (item.href) {
this.tocByHref[item.href] = i;
}
if (item.id) {
this.tocById[item.id] = i;
}
if (item.id) {
this.tocById[item.id] = i;
}
this.length++;
this.length++;
if (item.subitems.length) {
this.unpack(item.subitems);
}
}
if (item.subitems.length) {
this.unpack(item.subitems);
}
}
}
}
/**
* Get an item from the navigation
* @param {string} target
* @return {object} navItem
*/
get(target) {
var index;
/**
* Get an item from the navigation
* @param {string} target
* @return {object} navItem
*/
get(target) {
var index;
if (!target) {
return this.toc;
}
if(!target) {
return this.toc;
}
if (target.indexOf("#") === 0) {
index = this.tocById[target.substring(1)];
} else if (target in this.tocByHref) {
index = this.tocByHref[target];
}
if(target.indexOf("#") === 0) {
index = this.tocById[target.substring(1)];
} else if(target in this.tocByHref){
index = this.tocByHref[target];
}
return this.getByIndex(target, index, this.toc);
}
return this.getByIndex(target, index, this.toc);
}
/**
* Get an item from navigation subitems recursively by index
* @param {string} target
* @param {number} index
* @param {array} navItems
* @return {object} navItem
*/
getByIndex(target, index, navItems) {
if (navItems.length === 0) {
return;
}
/**
* Get an item from navigation subitems recursively by index
* @param {string} target
* @param {number} index
* @param {array} navItems
* @return {object} navItem
*/
getByIndex(target, index, navItems) {
if (navItems.length === 0) {
return;
}
const item = navItems[index];
if (item && (target === item.id || target === item.href)) {
return item;
} else {
let result;
for (let i = 0; i < navItems.length; ++i) {
result = this.getByIndex(target, index, navItems[i].subitems);
if (result) {
break;
}
}
return result;
}
}
const item = navItems[index];
if (item && (target === item.id || target === item.href)) {
return item;
} else {
let result;
for (let i = 0; i < navItems.length; ++i) {
result = this.getByIndex(target, index, navItems[i].subitems);
if (result) {
break;
}
}
return result;
}
}
/**
* Get a landmark by type
* List of types: https://idpf.github.io/epub-vocabs/structure/
* @param {string} type
* @return {object} landmarkItem
*/
landmark(type) {
var index;
/**
* Get a landmark by type
* List of types: https://idpf.github.io/epub-vocabs/structure/
* @param {string} type
* @return {object} landmarkItem
*/
landmark(type) {
var index;
if (!type) {
return this.landmarks;
}
if(!type) {
return this.landmarks;
}
index = this.landmarksByType[type];
index = this.landmarksByType[type];
return this.landmarks[index];
}
return this.landmarks[index];
}
/**
* Parse toc from a Epub > 3.0 Nav
* @private
* @param {document} navHtml
* @return {array} navigation list
*/
parseNav(navHtml) {
var navElement = querySelectorByType(navHtml, "nav", "toc");
var list = [];
/**
* Parse toc from a Epub > 3.0 Nav
* @private
* @param {document} navHtml
* @return {array} navigation list
*/
parseNav(navHtml){
var navElement = querySelectorByType(navHtml, "nav", "toc");
var list = [];
if (!navElement) return list;
if (!navElement) return list;
let navList = filterChildren(navElement, "ol", true);
if (!navList) return list;
let navList = filterChildren(navElement, "ol", true);
if (!navList) return list;
list = this.parseNavList(navList);
return list;
}
list = this.parseNavList(navList);
return list;
}
/**
* Parses lists in the toc
* @param {document} navListHtml
* @param {string} parent id
* @return {array} navigation list
*/
parseNavList(navListHtml, parent) {
const result = [];
/**
* Parses lists in the toc
* @param {document} navListHtml
* @param {string} parent id
* @return {array} navigation list
*/
parseNavList(navListHtml, parent) {
const result = [];
if (!navListHtml) return result;
if (!navListHtml.children) return result;
if (!navListHtml) return result;
if (!navListHtml.children) return result;
for (let i = 0; i < navListHtml.children.length; i++) {
const item = this.navItem(navListHtml.children[i], parent);
for (let i = 0; i < navListHtml.children.length; i++) {
const item = this.navItem(navListHtml.children[i], parent);
if (item) {
result.push(item);
}
}
if (item) {
result.push(item);
}
}
return result;
}
return result;
}
/**
* Create a navItem
* @private
* @param {element} item
* @return {object} navItem
*/
navItem(item, parent) {
let id = item.getAttribute("id") || undefined;
let content = filterChildren(item, "a", true)
|| filterChildren(item, "span", true);
/**
* Create a navItem
* @private
* @param {element} item
* @return {object} navItem
*/
navItem(item, parent) {
let id = item.getAttribute("id") || undefined;
let content =
filterChildren(item, "a", true) || filterChildren(item, "span", true);
if (!content) {
return;
}
if (!content) {
return;
}
let src = content.getAttribute("href") || "";
if (!id) {
id = src;
}
let text = content.textContent || "";
let src = content.getAttribute("href") || "";
let subitems = [];
let nested = filterChildren(item, "ol", true);
if (nested) {
subitems = this.parseNavList(nested, id);
}
if (!id) {
id = src;
}
let text = content.textContent || "";
return {
"id": id,
"href": src,
"label": text,
"subitems" : subitems,
"parent" : parent
};
}
let subitems = [];
let nested = filterChildren(item, "ol", true);
if (nested) {
subitems = this.parseNavList(nested, id);
}
/**
* Parse landmarks from a Epub > 3.0 Nav
* @private
* @param {document} navHtml
* @return {array} landmarks list
*/
parseLandmarks(navHtml){
var navElement = querySelectorByType(navHtml, "nav", "landmarks");
var navItems = navElement ? qsa(navElement, "li") : [];
var length = navItems.length;
var i;
var list = [];
var item;
return {
id: id,
href: src,
label: text,
subitems: subitems,
parent: parent,
};
}
if(!navItems || length === 0) return list;
/**
* Parse landmarks from a Epub > 3.0 Nav
* @private
* @param {document} navHtml
* @return {array} landmarks list
*/
parseLandmarks(navHtml) {
var navElement = querySelectorByType(navHtml, "nav", "landmarks");
var navItems = navElement ? qsa(navElement, "li") : [];
var length = navItems.length;
var i;
var list = [];
var item;
for (i = 0; i < length; ++i) {
item = this.landmarkItem(navItems[i]);
if (item) {
list.push(item);
this.landmarksByType[item.type] = i;
}
}
if (!navItems || length === 0) return list;
return list;
}
for (i = 0; i < length; ++i) {
item = this.landmarkItem(navItems[i]);
if (item) {
list.push(item);
this.landmarksByType[item.type] = i;
}
}
/**
* Create a landmarkItem
* @private
* @param {element} item
* @return {object} landmarkItem
*/
landmarkItem(item){
let content = filterChildren(item, "a", true);
return list;
}
if (!content) {
return;
}
/**
* Create a landmarkItem
* @private
* @param {element} item
* @return {object} landmarkItem
*/
landmarkItem(item) {
let content = filterChildren(item, "a", true);
let type = content.getAttributeNS("http://www.idpf.org/2007/ops", "type") || undefined;
let href = content.getAttribute("href") || "";
let text = content.textContent || "";
if (!content) {
return;
}
return {
"href": href,
"label": text,
"type" : type
};
}
let type =
content.getAttributeNS("http://www.idpf.org/2007/ops", "type") ||
undefined;
let href = content.getAttribute("href") || "";
let text = content.textContent || "";
/**
* Parse from a Epub > 3.0 NC
* @private
* @param {document} navHtml
* @return {array} navigation list
*/
parseNcx(tocXml){
var navPoints = qsa(tocXml, "navPoint");
var length = navPoints.length;
var i;
var toc = {};
var list = [];
var item, parent;
return {
href: href,
label: text,
type: type,
};
}
if(!navPoints || length === 0) return list;
/**
* Parse from a Epub > 3.0 NC
* @private
* @param {document} navHtml
* @return {array} navigation list
*/
parseNcx(tocXml) {
var navPoints = qsa(tocXml, "navPoint");
var length = navPoints.length;
var i;
var toc = {};
var list = [];
var item, parent;
for (i = 0; i < length; ++i) {
item = this.ncxItem(navPoints[i]);
toc[item.id] = item;
if(!item.parent) {
list.push(item);
} else {
parent = toc[item.parent];
parent.subitems.push(item);
}
}
if (!navPoints || length === 0) return list;
return list;
}
for (i = 0; i < length; ++i) {
item = this.ncxItem(navPoints[i]);
toc[item.id] = item;
if (!item.parent) {
list.push(item);
} else {
parent = toc[item.parent];
parent.subitems.push(item);
}
}
/**
* Create a ncxItem
* @private
* @param {element} item
* @return {object} ncxItem
*/
ncxItem(item){
var id = item.getAttribute("id") || false,
content = qs(item, "content"),
src = content.getAttribute("src"),
navLabel = qs(item, "navLabel"),
text = navLabel.textContent ? navLabel.textContent : "",
subitems = [],
parentNode = item.parentNode,
parent;
return list;
}
if(parentNode && (parentNode.nodeName === "navPoint" || parentNode.nodeName.split(':').slice(-1)[0] === "navPoint")) {
parent = parentNode.getAttribute("id");
}
/**
* Create a ncxItem
* @private
* @param {element} item
* @return {object} ncxItem
*/
ncxItem(item) {
var id = item.getAttribute("id") || false,
content = qs(item, "content"),
src = content.getAttribute("src"),
navLabel = qs(item, "navLabel"),
text = navLabel.textContent ? navLabel.textContent : "",
subitems = [],
parentNode = item.parentNode,
parent;
if (
parentNode &&
(parentNode.nodeName === "navPoint" ||
parentNode.nodeName.split(":").slice(-1)[0] === "navPoint")
) {
parent = parentNode.getAttribute("id");
}
return {
"id": id,
"href": src,
"label": text,
"subitems" : subitems,
"parent" : parent
};
}
return {
id: id,
href: src,
label: text,
subitems: subitems,
parent: parent,
};
}
/**
* Load Spine Items
* @param {object} json the items to be loaded
* @return {Array} navItems
*/
load(json) {
return json.map(item => {
item.label = item.title;
item.subitems = item.children ? this.load(item.children) : [];
return item;
});
}
/**
* Load Spine Items
* @param {object} json the items to be loaded
* @return {Array} navItems
*/
load(json) {
return json.map((item) => {
item.label = item.title;
item.subitems = item.children ? this.load(item.children) : [];
return item;
});
}
/**
* forEach pass through
* @param {Function} fn function to run on each item
* @return {method} forEach loop
*/
forEach(fn) {
return this.toc.forEach(fn);
}
/**
* forEach pass through
* @param {Function} fn function to run on each item
* @return {method} forEach loop
*/
forEach(fn) {
return this.toc.forEach(fn);
}
}
export default Navigation;

View file

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

View file

@ -1,10 +1,10 @@
import EpubCFI from "./epubcfi";
import {
qs,
qsa,
querySelectorByType,
indexOfSorted,
locationOf
indexOfSorted,
locationOf,
qs,
qsa,
querySelectorByType,
} from "./utils/core";
/**
@ -12,263 +12,261 @@ import {
* @param {document} [xml]
*/
class PageList {
constructor(xml) {
this.pages = [];
this.locations = [];
this.epubcfi = new EpubCFI();
constructor(xml) {
this.pages = [];
this.locations = [];
this.epubcfi = new EpubCFI();
this.firstPage = 0;
this.lastPage = 0;
this.totalPages = 0;
this.firstPage = 0;
this.lastPage = 0;
this.totalPages = 0;
this.toc = undefined;
this.ncx = undefined;
this.toc = undefined;
this.ncx = undefined;
if (xml) {
this.pageList = this.parse(xml);
}
if (xml) {
this.pageList = this.parse(xml);
}
if(this.pageList && this.pageList.length) {
this.process(this.pageList);
}
}
if (this.pageList && this.pageList.length) {
this.process(this.pageList);
}
}
/**
* Parse PageList Xml
* @param {document} xml
*/
parse(xml) {
var html = qs(xml, "html");
var ncx = qs(xml, "ncx");
/**
* Parse PageList Xml
* @param {document} xml
*/
parse(xml) {
var html = qs(xml, "html");
var ncx = qs(xml, "ncx");
if(html) {
return this.parseNav(xml);
} else if(ncx){
return this.parseNcx(xml);
}
if (html) {
return this.parseNav(xml);
} else if (ncx) {
return this.parseNcx(xml);
}
}
}
/**
* Parse a Nav PageList
* @private
* @param {node} navHtml
* @return {PageList.item[]} list
*/
parseNav(navHtml) {
var navElement = querySelectorByType(navHtml, "nav", "page-list");
var navItems = navElement ? qsa(navElement, "li") : [];
var length = navItems.length;
var i;
var list = [];
var item;
/**
* Parse a Nav PageList
* @private
* @param {node} navHtml
* @return {PageList.item[]} list
*/
parseNav(navHtml){
var navElement = querySelectorByType(navHtml, "nav", "page-list");
var navItems = navElement ? qsa(navElement, "li") : [];
var length = navItems.length;
var i;
var list = [];
var item;
if (!navItems || length === 0) return list;
if(!navItems || length === 0) return list;
for (i = 0; i < length; ++i) {
item = this.item(navItems[i]);
list.push(item);
}
for (i = 0; i < length; ++i) {
item = this.item(navItems[i]);
list.push(item);
}
return list;
}
return list;
}
parseNcx(navXml) {
var list = [];
var i = 0;
var item;
var pageList;
var pageTargets;
var length = 0;
parseNcx(navXml) {
var list = [];
var i = 0;
var item;
var pageList;
var pageTargets;
var length = 0;
pageList = qs(navXml, "pageList");
if (!pageList) return list;
pageList = qs(navXml, "pageList");
if (!pageList) return list;
pageTargets = qsa(pageList, "pageTarget");
length = pageTargets.length;
pageTargets = qsa(pageList, "pageTarget");
length = pageTargets.length;
if (!pageTargets || pageTargets.length === 0) {
return list;
}
if (!pageTargets || pageTargets.length === 0) {
return list;
}
for (i = 0; i < length; ++i) {
item = this.ncxItem(pageTargets[i]);
list.push(item);
}
for (i = 0; i < length; ++i) {
item = this.ncxItem(pageTargets[i]);
list.push(item);
}
return list;
}
return list;
}
ncxItem(item) {
var navLabel = qs(item, "navLabel");
var navLabelText = qs(navLabel, "text");
var pageText = navLabelText.textContent;
var content = qs(item, "content");
ncxItem(item) {
var navLabel = qs(item, "navLabel");
var navLabelText = qs(navLabel, "text");
var pageText = navLabelText.textContent;
var content = qs(item, "content");
var href = content.getAttribute("src");
var page = parseInt(pageText, 10);
var href = content.getAttribute("src");
var page = parseInt(pageText, 10);
return {
href: href,
page: page,
};
}
return {
"href": href,
"page": page,
};
}
/**
* Page List Item
* @private
* @param {node} item
* @return {object} pageListItem
*/
item(item) {
var content = qs(item, "a"),
href = content.getAttribute("href") || "",
text = content.textContent || "",
page = parseInt(text),
isCfi = href.indexOf("epubcfi"),
split,
packageUrl,
cfi;
/**
* Page List Item
* @private
* @param {node} item
* @return {object} pageListItem
*/
item(item){
var content = qs(item, "a"),
href = content.getAttribute("href") || "",
text = content.textContent || "",
page = parseInt(text),
isCfi = href.indexOf("epubcfi"),
split,
packageUrl,
cfi;
if (isCfi != -1) {
split = href.split("#");
packageUrl = split[0];
cfi = split.length > 1 ? split[1] : false;
return {
cfi: cfi,
href: href,
packageUrl: packageUrl,
page: page,
};
} else {
return {
href: href,
page: page,
};
}
}
if(isCfi != -1) {
split = href.split("#");
packageUrl = split[0];
cfi = split.length > 1 ? split[1] : false;
return {
"cfi" : cfi,
"href" : href,
"packageUrl" : packageUrl,
"page" : page
};
} else {
return {
"href" : href,
"page" : page
};
}
}
/**
* Process pageList items
* @private
* @param {array} pageList
*/
process(pageList) {
pageList.forEach(function (item) {
this.pages.push(item.page);
if (item.cfi) {
this.locations.push(item.cfi);
}
}, this);
this.firstPage = parseInt(this.pages[0]);
this.lastPage = parseInt(this.pages[this.pages.length - 1]);
this.totalPages = this.lastPage - this.firstPage;
}
/**
* Process pageList items
* @private
* @param {array} pageList
*/
process(pageList){
pageList.forEach(function(item){
this.pages.push(item.page);
if (item.cfi) {
this.locations.push(item.cfi);
}
}, this);
this.firstPage = parseInt(this.pages[0]);
this.lastPage = parseInt(this.pages[this.pages.length-1]);
this.totalPages = this.lastPage - this.firstPage;
}
/**
* Get a PageList result from a EpubCFI
* @param {string} cfi EpubCFI String
* @return {number} page
*/
pageFromCfi(cfi) {
var pg = -1;
/**
* Get a PageList result from a EpubCFI
* @param {string} cfi EpubCFI String
* @return {number} page
*/
pageFromCfi(cfi){
var pg = -1;
// Check if the pageList has not been set yet
if (this.locations.length === 0) {
return -1;
}
// Check if the pageList has not been set yet
if(this.locations.length === 0) {
return -1;
}
// TODO: check if CFI is valid?
// TODO: check if CFI is valid?
// check if the cfi is in the location list
// var index = this.locations.indexOf(cfi);
var index = indexOfSorted(cfi, this.locations, this.epubcfi.compare);
if (index != -1) {
pg = this.pages[index];
} else {
// Otherwise add it to the list of locations
// Insert it in the correct position in the locations page
//index = EPUBJS.core.insert(cfi, this.locations, this.epubcfi.compare);
index = locationOf(cfi, this.locations, this.epubcfi.compare);
// Get the page at the location just before the new one, or return the first
pg = index - 1 >= 0 ? this.pages[index - 1] : this.pages[0];
if (pg !== undefined) {
// Add the new page in so that the locations and page array match up
//this.pages.splice(index, 0, pg);
} else {
pg = -1;
}
}
return pg;
}
// check if the cfi is in the location list
// var index = this.locations.indexOf(cfi);
var index = indexOfSorted(cfi, this.locations, this.epubcfi.compare);
if(index != -1) {
pg = this.pages[index];
} else {
// Otherwise add it to the list of locations
// Insert it in the correct position in the locations page
//index = EPUBJS.core.insert(cfi, this.locations, this.epubcfi.compare);
index = locationOf(cfi, this.locations, this.epubcfi.compare);
// Get the page at the location just before the new one, or return the first
pg = index-1 >= 0 ? this.pages[index-1] : this.pages[0];
if(pg !== undefined) {
// Add the new page in so that the locations and page array match up
//this.pages.splice(index, 0, pg);
} else {
pg = -1;
}
/**
* Get an EpubCFI from a Page List Item
* @param {string | number} pg
* @return {string} cfi
*/
cfiFromPage(pg) {
var cfi = -1;
// check that pg is an int
if (typeof pg != "number") {
pg = parseInt(pg);
}
}
return pg;
}
// check if the cfi is in the page list
// Pages could be unsorted.
var index = this.pages.indexOf(pg);
if (index != -1) {
cfi = this.locations[index];
}
// TODO: handle pages not in the list
return cfi;
}
/**
* Get an EpubCFI from a Page List Item
* @param {string | number} pg
* @return {string} cfi
*/
cfiFromPage(pg){
var cfi = -1;
// check that pg is an int
if(typeof pg != "number"){
pg = parseInt(pg);
}
/**
* Get a Page from Book percentage
* @param {number} percent
* @return {number} page
*/
pageFromPercentage(percent) {
var pg = Math.round(this.totalPages * percent);
return pg;
}
// check if the cfi is in the page list
// Pages could be unsorted.
var index = this.pages.indexOf(pg);
if(index != -1) {
cfi = this.locations[index];
}
// TODO: handle pages not in the list
return cfi;
}
/**
* Returns a value between 0 - 1 corresponding to the location of a page
* @param {number} pg the page
* @return {number} percentage
*/
percentageFromPage(pg) {
var percentage = (pg - this.firstPage) / this.totalPages;
return Math.round(percentage * 1000) / 1000;
}
/**
* Get a Page from Book percentage
* @param {number} percent
* @return {number} page
*/
pageFromPercentage(percent){
var pg = Math.round(this.totalPages * percent);
return pg;
}
/**
* Returns a value between 0 - 1 corresponding to the location of a cfi
* @param {string} cfi EpubCFI String
* @return {number} percentage
*/
percentageFromCfi(cfi) {
var pg = this.pageFromCfi(cfi);
var percentage = this.percentageFromPage(pg);
return percentage;
}
/**
* Returns a value between 0 - 1 corresponding to the location of a page
* @param {number} pg the page
* @return {number} percentage
*/
percentageFromPage(pg){
var percentage = (pg - this.firstPage) / this.totalPages;
return Math.round(percentage * 1000) / 1000;
}
/**
* Destroy
*/
destroy() {
this.pages = undefined;
this.locations = undefined;
this.epubcfi = undefined;
/**
* Returns a value between 0 - 1 corresponding to the location of a cfi
* @param {string} cfi EpubCFI String
* @return {number} percentage
*/
percentageFromCfi(cfi){
var pg = this.pageFromCfi(cfi);
var percentage = this.percentageFromPage(pg);
return percentage;
}
this.pageList = undefined;
/**
* Destroy
*/
destroy() {
this.pages = undefined;
this.locations = undefined;
this.epubcfi = undefined;
this.pageList = undefined;
this.toc = undefined;
this.ncx = undefined;
}
this.toc = undefined;
this.ncx = undefined;
}
}
export default PageList;

File diff suppressed because it is too large Load diff

View file

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

View file

@ -1,10 +1,9 @@
import { defer } from "./utils/core";
import { DOMParser as XMLDOMSerializer } from "@xmldom/xmldom";
import EpubCFI from "./epubcfi";
import { defer, sprint } from "./utils/core";
import Hook from "./utils/hook";
import { sprint } from "./utils/core";
import { replaceBase } from "./utils/replacements";
import Request from "./utils/request";
import { DOMParser as XMLDOMSerializer } from "@xmldom/xmldom";
/**
* Represents a Section of the Book
@ -14,310 +13,347 @@ import { DOMParser as XMLDOMSerializer } from "@xmldom/xmldom";
* @param {object} hooks hooks for serialize and content
*/
class Section {
constructor(item, hooks){
this.idref = item.idref;
this.linear = item.linear === "yes";
this.properties = item.properties;
this.index = item.index;
this.href = item.href;
this.url = item.url;
this.canonical = item.canonical;
this.next = item.next;
this.prev = item.prev;
constructor(item, hooks) {
this.idref = item.idref;
this.linear = item.linear === "yes";
this.properties = item.properties;
this.index = item.index;
this.href = item.href;
this.url = item.url;
this.canonical = item.canonical;
this.next = item.next;
this.prev = item.prev;
this.cfiBase = item.cfiBase;
this.cfiBase = item.cfiBase;
if (hooks) {
this.hooks = hooks;
} else {
this.hooks = {};
this.hooks.serialize = new Hook(this);
this.hooks.content = new Hook(this);
}
if (hooks) {
this.hooks = hooks;
} else {
this.hooks = {};
this.hooks.serialize = new Hook(this);
this.hooks.content = new Hook(this);
}
this.document = undefined;
this.contents = undefined;
this.output = undefined;
}
this.document = undefined;
this.contents = undefined;
this.output = undefined;
}
/**
* Load the section from its url
* @param {method} [_request] a request method to use for loading
* @return {document} a promise with the xml document
*/
load(_request){
var request = _request || this.request || Request;
var loading = new defer();
var loaded = loading.promise;
/**
* Load the section from its url
* @param {method} [_request] a request method to use for loading
* @return {document} a promise with the xml document
*/
load(_request) {
var request = _request || this.request || Request;
var loading = new defer();
var loaded = loading.promise;
if(this.contents) {
loading.resolve(this.contents);
} else {
request(this.url)
.then(function(xml){
// var directory = new Url(this.url).directory;
if (this.contents) {
loading.resolve(this.contents);
} else {
request(this.url)
.then(
function (xml) {
// var directory = new Url(this.url).directory;
this.document = xml;
this.contents = xml.documentElement;
this.document = xml;
this.contents = xml.documentElement;
return this.hooks.content.trigger(this.document, this);
}.bind(this))
.then(function(){
loading.resolve(this.contents);
}.bind(this))
.catch(function(error){
loading.reject(error);
});
}
return this.hooks.content.trigger(this.document, this);
}.bind(this)
)
.then(
function () {
loading.resolve(this.contents);
}.bind(this)
)
.catch(function (error) {
loading.reject(error);
});
}
return loaded;
}
return loaded;
}
/**
* Adds a base tag for resolving urls in the section
* @private
*/
base(){
return replaceBase(this.document, this);
}
/**
* Adds a base tag for resolving urls in the section
* @private
*/
base() {
return replaceBase(this.document, this);
}
/**
* Render the contents of a section
* @param {method} [_request] a request method to use for loading
* @return {string} output a serialized XML Document
*/
render(_request){
var rendering = new defer();
var rendered = rendering.promise;
this.output; // TODO: better way to return this from hooks?
/**
* Render the contents of a section
* @param {method} [_request] a request method to use for loading
* @return {string} output a serialized XML Document
*/
render(_request) {
var rendering = new defer();
var rendered = rendering.promise;
this.output; // TODO: better way to return this from hooks?
this.load(_request).
then(function(contents){
var userAgent = (typeof navigator !== 'undefined' && navigator.userAgent) || '';
var isIE = userAgent.indexOf('Trident') >= 0;
var Serializer;
if (typeof XMLSerializer === "undefined" || isIE) {
Serializer = XMLDOMSerializer;
} else {
Serializer = XMLSerializer;
}
var serializer = new Serializer();
this.output = serializer.serializeToString(contents);
return this.output;
}.bind(this)).
then(function(){
return this.hooks.serialize.trigger(this.output, this);
}.bind(this)).
then(function(){
rendering.resolve(this.output);
}.bind(this))
.catch(function(error){
rendering.reject(error);
});
this.load(_request)
.then(
function (contents) {
var userAgent =
(typeof navigator !== "undefined" && navigator.userAgent) || "";
var isIE = userAgent.indexOf("Trident") >= 0;
var Serializer;
if (typeof XMLSerializer === "undefined" || isIE) {
Serializer = XMLDOMSerializer;
} else {
Serializer = XMLSerializer;
}
var serializer = new Serializer();
this.output = serializer.serializeToString(contents);
return this.output;
}.bind(this)
)
.then(
function () {
return this.hooks.serialize.trigger(this.output, this);
}.bind(this)
)
.then(
function () {
rendering.resolve(this.output);
}.bind(this)
)
.catch(function (error) {
rendering.reject(error);
});
return rendered;
}
return rendered;
}
/**
* Find a string in a section
* @param {string} _query The query string to find
* @return {object[]} A list of matches, with form {cfi, excerpt}
*/
find(_query){
var section = this;
var matches = [];
var query = _query.toLowerCase();
var find = function(node){
var text = node.textContent.toLowerCase();
var range = section.document.createRange();
var cfi;
var pos;
var last = -1;
var excerpt;
var limit = 150;
/**
* Find a string in a section
* @param {string} _query The query string to find
* @return {object[]} A list of matches, with form {cfi, excerpt}
*/
find(_query) {
var section = this;
var matches = [];
var query = _query.toLowerCase();
var find = function (node) {
var text = node.textContent.toLowerCase();
var range = section.document.createRange();
var cfi;
var pos;
var last = -1;
var excerpt;
var limit = 150;
while (pos != -1) {
// Search for the query
pos = text.indexOf(query, last + 1);
while (pos != -1) {
// Search for the query
pos = text.indexOf(query, last + 1);
if (pos != -1) {
// We found it! Generate a CFI
range = section.document.createRange();
range.setStart(node, pos);
range.setEnd(node, pos + query.length);
if (pos != -1) {
// We found it! Generate a CFI
range = section.document.createRange();
range.setStart(node, pos);
range.setEnd(node, pos + query.length);
cfi = section.cfiFromRange(range);
cfi = section.cfiFromRange(range);
// Generate the excerpt
if (node.textContent.length < limit) {
excerpt = node.textContent;
}
else {
excerpt = node.textContent.substring(pos - limit/2, pos + limit/2);
excerpt = "..." + excerpt + "...";
}
// Generate the excerpt
if (node.textContent.length < limit) {
excerpt = node.textContent;
} else {
excerpt = node.textContent.substring(
pos - limit / 2,
pos + limit / 2
);
excerpt = "..." + excerpt + "...";
}
// Add the CFI to the matches list
matches.push({
cfi: cfi,
excerpt: excerpt
});
}
// Add the CFI to the matches list
matches.push({
cfi: cfi,
excerpt: excerpt,
});
}
last = pos;
}
};
last = pos;
}
};
sprint(section.document, function(node) {
find(node);
});
sprint(section.document, function (node) {
find(node);
});
return matches;
};
return matches;
}
/**
* Search a string in multiple sequential Element of the section. If the document.createTreeWalker api is missed(eg: IE8), use `find` as a fallback.
* @param {string} _query The query string to search
* @param {int} maxSeqEle The maximum number of Element that are combined for search, default value is 5.
* @return {object[]} A list of matches, with form {cfi, excerpt}
*/
search(_query, maxSeqEle = 5) {
if (typeof document.createTreeWalker == "undefined") {
return this.find(_query);
}
let matches = [];
const excerptLimit = 150;
const section = this;
const query = _query.toLowerCase();
const search = function (nodeList) {
const textWithCase = nodeList.reduce((acc, current) => {
return acc + current.textContent;
}, "");
const text = textWithCase.toLowerCase();
const pos = text.indexOf(query);
if (pos != -1) {
const startNodeIndex = 0,
endPos = pos + query.length;
let endNodeIndex = 0,
l = 0;
if (pos < nodeList[startNodeIndex].length) {
let cfi;
while (endNodeIndex < nodeList.length - 1) {
l += nodeList[endNodeIndex].length;
if (endPos <= l) {
break;
}
endNodeIndex += 1;
}
/**
* Search a string in multiple sequential Element of the section. If the document.createTreeWalker api is missed(eg: IE8), use `find` as a fallback.
* @param {string} _query The query string to search
* @param {int} maxSeqEle The maximum number of Element that are combined for search, default value is 5.
* @return {object[]} A list of matches, with form {cfi, excerpt}
*/
search(_query , maxSeqEle = 5){
if (typeof(document.createTreeWalker) == "undefined") {
return this.find(_query);
}
let matches = [];
const excerptLimit = 150;
const section = this;
const query = _query.toLowerCase();
const search = function(nodeList){
const textWithCase = nodeList.reduce((acc ,current)=>{
return acc + current.textContent;
},"");
const text = textWithCase.toLowerCase();
const pos = text.indexOf(query);
if (pos != -1){
const startNodeIndex = 0 , endPos = pos + query.length;
let endNodeIndex = 0 , l = 0;
if (pos < nodeList[startNodeIndex].length){
let cfi;
while( endNodeIndex < nodeList.length - 1 ){
l += nodeList[endNodeIndex].length;
if ( endPos <= l){
break;
}
endNodeIndex += 1;
}
let startNode = nodeList[startNodeIndex],
endNode = nodeList[endNodeIndex];
let range = section.document.createRange();
range.setStart(startNode, pos);
let beforeEndLengthCount = nodeList
.slice(0, endNodeIndex)
.reduce((acc, current) => {
return acc + current.textContent.length;
}, 0);
range.setEnd(
endNode,
beforeEndLengthCount > endPos
? endPos
: endPos - beforeEndLengthCount
);
cfi = section.cfiFromRange(range);
let startNode = nodeList[startNodeIndex] , endNode = nodeList[endNodeIndex];
let range = section.document.createRange();
range.setStart(startNode,pos);
let beforeEndLengthCount = nodeList.slice(0, endNodeIndex).reduce((acc,current)=>{return acc+current.textContent.length;},0) ;
range.setEnd(endNode, beforeEndLengthCount > endPos ? endPos : endPos - beforeEndLengthCount );
cfi = section.cfiFromRange(range);
let excerpt = nodeList
.slice(0, endNodeIndex + 1)
.reduce((acc, current) => {
return acc + current.textContent;
}, "");
if (excerpt.length > excerptLimit) {
excerpt = excerpt.substring(
pos - excerptLimit / 2,
pos + excerptLimit / 2
);
excerpt = "..." + excerpt + "...";
}
matches.push({
cfi: cfi,
excerpt: excerpt,
});
}
}
};
let excerpt = nodeList.slice(0, endNodeIndex+1).reduce((acc,current)=>{return acc+current.textContent ;},"");
if (excerpt.length > excerptLimit){
excerpt = excerpt.substring(pos - excerptLimit/2, pos + excerptLimit/2);
excerpt = "..." + excerpt + "...";
}
matches.push({
cfi: cfi,
excerpt: excerpt
});
}
}
}
const treeWalker = document.createTreeWalker(
section.document,
NodeFilter.SHOW_TEXT,
null,
false
);
let node,
nodeList = [];
while ((node = treeWalker.nextNode())) {
nodeList.push(node);
if (nodeList.length == maxSeqEle) {
search(nodeList.slice(0, maxSeqEle));
nodeList = nodeList.slice(1, maxSeqEle);
}
}
if (nodeList.length > 0) {
search(nodeList);
}
return matches;
}
const treeWalker = document.createTreeWalker(section.document, NodeFilter.SHOW_TEXT, null, false);
let node , nodeList = [];
while (node = treeWalker.nextNode()) {
nodeList.push(node);
if (nodeList.length == maxSeqEle){
search(nodeList.slice(0 , maxSeqEle));
nodeList = nodeList.slice(1, maxSeqEle);
}
}
if (nodeList.length > 0){
search(nodeList);
}
return matches;
}
/**
* Reconciles the current chapters layout properties with
* the global layout properties.
* @param {object} globalLayout The global layout settings object, chapter properties string
* @return {object} layoutProperties Object with layout properties
*/
reconcileLayoutSettings(globalLayout) {
//-- Get the global defaults
var settings = {
layout: globalLayout.layout,
spread: globalLayout.spread,
orientation: globalLayout.orientation,
};
/**
* Reconciles the current chapters layout properties with
* the global layout properties.
* @param {object} globalLayout The global layout settings object, chapter properties string
* @return {object} layoutProperties Object with layout properties
*/
reconcileLayoutSettings(globalLayout){
//-- Get the global defaults
var settings = {
layout : globalLayout.layout,
spread : globalLayout.spread,
orientation : globalLayout.orientation
};
//-- Get the chapter's display type
this.properties.forEach(function (prop) {
var rendition = prop.replace("rendition:", "");
var split = rendition.indexOf("-");
var property, value;
//-- Get the chapter's display type
this.properties.forEach(function(prop){
var rendition = prop.replace("rendition:", "");
var split = rendition.indexOf("-");
var property, value;
if (split != -1) {
property = rendition.slice(0, split);
value = rendition.slice(split + 1);
if(split != -1){
property = rendition.slice(0, split);
value = rendition.slice(split+1);
settings[property] = value;
}
});
return settings;
}
settings[property] = value;
}
});
return settings;
}
/**
* Get a CFI from a Range in the Section
* @param {range} _range
* @return {string} cfi an EpubCFI string
*/
cfiFromRange(_range) {
return new EpubCFI(_range, this.cfiBase).toString();
}
/**
* Get a CFI from a Range in the Section
* @param {range} _range
* @return {string} cfi an EpubCFI string
*/
cfiFromRange(_range) {
return new EpubCFI(_range, this.cfiBase).toString();
}
/**
* Get a CFI from an Element in the Section
* @param {element} el
* @return {string} cfi an EpubCFI string
*/
cfiFromElement(el) {
return new EpubCFI(el, this.cfiBase).toString();
}
/**
* Get a CFI from an Element in the Section
* @param {element} el
* @return {string} cfi an EpubCFI string
*/
cfiFromElement(el) {
return new EpubCFI(el, this.cfiBase).toString();
}
/**
* Unload the section document
*/
unload() {
this.document = undefined;
this.contents = undefined;
this.output = undefined;
}
/**
* Unload the section document
*/
unload() {
this.document = undefined;
this.contents = undefined;
this.output = undefined;
}
destroy() {
this.unload();
this.hooks.serialize.clear();
this.hooks.content.clear();
destroy() {
this.unload();
this.hooks.serialize.clear();
this.hooks.content.clear();
this.hooks = undefined;
this.idref = undefined;
this.linear = undefined;
this.properties = undefined;
this.index = undefined;
this.href = undefined;
this.url = undefined;
this.next = undefined;
this.prev = undefined;
this.hooks = undefined;
this.idref = undefined;
this.linear = undefined;
this.properties = undefined;
this.index = undefined;
this.href = undefined;
this.url = undefined;
this.next = undefined;
this.prev = undefined;
this.cfiBase = undefined;
}
this.cfiBase = undefined;
}
}
export default Section;

View file

@ -1,274 +1,276 @@
import EpubCFI from "./epubcfi";
import Hook from "./utils/hook";
import Section from "./section";
import {replaceBase, replaceCanonical, replaceMeta} from "./utils/replacements";
import Hook from "./utils/hook";
import {
replaceBase,
replaceCanonical,
replaceMeta,
} from "./utils/replacements";
/**
* A collection of Spine Items
*/
class Spine {
constructor() {
this.spineItems = [];
this.spineByHref = {};
this.spineById = {};
constructor() {
this.spineItems = [];
this.spineByHref = {};
this.spineById = {};
this.hooks = {};
this.hooks.serialize = new Hook();
this.hooks.content = new Hook();
this.hooks = {};
this.hooks.serialize = new Hook();
this.hooks.content = new Hook();
// Register replacements
this.hooks.content.register(replaceBase);
this.hooks.content.register(replaceCanonical);
this.hooks.content.register(replaceMeta);
// Register replacements
this.hooks.content.register(replaceBase);
this.hooks.content.register(replaceCanonical);
this.hooks.content.register(replaceMeta);
this.epubcfi = new EpubCFI();
this.epubcfi = new EpubCFI();
this.loaded = false;
this.items = undefined;
this.manifest = undefined;
this.spineNodeIndex = undefined;
this.baseUrl = undefined;
this.length = undefined;
}
this.loaded = false;
/**
* Unpack items from a opf into spine items
* @param {Packaging} _package
* @param {method} resolver URL resolver
* @param {method} canonical Resolve canonical url
*/
unpack(_package, resolver, canonical) {
this.items = _package.spine;
this.manifest = _package.manifest;
this.spineNodeIndex = _package.spineNodeIndex;
this.baseUrl = _package.baseUrl || _package.basePath || "";
this.length = this.items.length;
this.items = undefined;
this.manifest = undefined;
this.spineNodeIndex = undefined;
this.baseUrl = undefined;
this.length = undefined;
}
this.items.forEach((item, index) => {
var manifestItem = this.manifest[item.idref];
var spineItem;
/**
* Unpack items from a opf into spine items
* @param {Packaging} _package
* @param {method} resolver URL resolver
* @param {method} canonical Resolve canonical url
*/
unpack(_package, resolver, canonical) {
item.index = index;
item.cfiBase = this.epubcfi.generateChapterComponent(
this.spineNodeIndex,
item.index,
item.id
);
this.items = _package.spine;
this.manifest = _package.manifest;
this.spineNodeIndex = _package.spineNodeIndex;
this.baseUrl = _package.baseUrl || _package.basePath || "";
this.length = this.items.length;
if (item.href) {
item.url = resolver(item.href, true);
item.canonical = canonical(item.href);
}
this.items.forEach( (item, index) => {
var manifestItem = this.manifest[item.idref];
var spineItem;
if (manifestItem) {
item.href = manifestItem.href;
item.url = resolver(item.href, true);
item.canonical = canonical(item.href);
item.index = index;
item.cfiBase = this.epubcfi.generateChapterComponent(this.spineNodeIndex, item.index, item.id);
if (manifestItem.properties.length) {
item.properties.push.apply(item.properties, manifestItem.properties);
}
}
if (item.href) {
item.url = resolver(item.href, true);
item.canonical = canonical(item.href);
}
if (item.linear === "yes") {
item.prev = function () {
let prevIndex = item.index;
while (prevIndex > 0) {
let prev = this.get(prevIndex - 1);
if (prev && prev.linear) {
return prev;
}
prevIndex -= 1;
}
return;
}.bind(this);
item.next = function () {
let nextIndex = item.index;
while (nextIndex < this.spineItems.length - 1) {
let next = this.get(nextIndex + 1);
if (next && next.linear) {
return next;
}
nextIndex += 1;
}
return;
}.bind(this);
} else {
item.prev = function () {
return;
};
item.next = function () {
return;
};
}
if(manifestItem) {
item.href = manifestItem.href;
item.url = resolver(item.href, true);
item.canonical = canonical(item.href);
spineItem = new Section(item, this.hooks);
if(manifestItem.properties.length){
item.properties.push.apply(item.properties, manifestItem.properties);
}
}
this.append(spineItem);
});
if (item.linear === "yes") {
item.prev = function() {
let prevIndex = item.index;
while (prevIndex > 0) {
let prev = this.get(prevIndex-1);
if (prev && prev.linear) {
return prev;
}
prevIndex -= 1;
}
return;
}.bind(this);
item.next = function() {
let nextIndex = item.index;
while (nextIndex < this.spineItems.length-1) {
let next = this.get(nextIndex+1);
if (next && next.linear) {
return next;
}
nextIndex += 1;
}
return;
}.bind(this);
} else {
item.prev = function() {
return;
}
item.next = function() {
return;
}
}
this.loaded = true;
}
/**
* Get an item from the spine
* @param {string|number} [target]
* @return {Section} section
* @example spine.get();
* @example spine.get(1);
* @example spine.get("chap1.html");
* @example spine.get("#id1234");
*/
get(target) {
var index = 0;
spineItem = new Section(item, this.hooks);
if (typeof target === "undefined") {
while (index < this.spineItems.length) {
let next = this.spineItems[index];
if (next && next.linear) {
break;
}
index += 1;
}
} else if (this.epubcfi.isCfiString(target)) {
let cfi = new EpubCFI(target);
index = cfi.spinePos;
} else if (typeof target === "number" || isNaN(target) === false) {
index = target;
} else if (typeof target === "string" && target.indexOf("#") === 0) {
index = this.spineById[target.substring(1)];
} else if (typeof target === "string") {
// Remove fragments
target = target.split("#")[0];
index = this.spineByHref[target] || this.spineByHref[encodeURI(target)];
}
this.append(spineItem);
return this.spineItems[index] || null;
}
/**
* Append a Section to the Spine
* @private
* @param {Section} section
*/
append(section) {
var index = this.spineItems.length;
section.index = index;
});
this.spineItems.push(section);
this.loaded = true;
}
// Encode and Decode href lookups
// see pr for details: https://github.com/futurepress/epub.js/pull/358
this.spineByHref[decodeURI(section.href)] = index;
this.spineByHref[encodeURI(section.href)] = index;
this.spineByHref[section.href] = index;
/**
* Get an item from the spine
* @param {string|number} [target]
* @return {Section} section
* @example spine.get();
* @example spine.get(1);
* @example spine.get("chap1.html");
* @example spine.get("#id1234");
*/
get(target) {
var index = 0;
this.spineById[section.idref] = index;
if (typeof target === "undefined") {
while (index < this.spineItems.length) {
let next = this.spineItems[index];
if (next && next.linear) {
break;
}
index += 1;
}
} else if(this.epubcfi.isCfiString(target)) {
let cfi = new EpubCFI(target);
index = cfi.spinePos;
} else if(typeof target === "number" || isNaN(target) === false){
index = target;
} else if(typeof target === "string" && target.indexOf("#") === 0) {
index = this.spineById[target.substring(1)];
} else if(typeof target === "string") {
// Remove fragments
target = target.split("#")[0];
index = this.spineByHref[target] || this.spineByHref[encodeURI(target)];
}
return index;
}
return this.spineItems[index] || null;
}
/**
* Prepend a Section to the Spine
* @private
* @param {Section} section
*/
prepend(section) {
// var index = this.spineItems.unshift(section);
this.spineByHref[section.href] = 0;
this.spineById[section.idref] = 0;
/**
* Append a Section to the Spine
* @private
* @param {Section} section
*/
append(section) {
var index = this.spineItems.length;
section.index = index;
// Re-index
this.spineItems.forEach(function (item, index) {
item.index = index;
});
this.spineItems.push(section);
return 0;
}
// Encode and Decode href lookups
// see pr for details: https://github.com/futurepress/epub.js/pull/358
this.spineByHref[decodeURI(section.href)] = index;
this.spineByHref[encodeURI(section.href)] = index;
this.spineByHref[section.href] = index;
// insert(section, index) {
//
// };
this.spineById[section.idref] = index;
/**
* Remove a Section from the Spine
* @private
* @param {Section} section
*/
remove(section) {
var index = this.spineItems.indexOf(section);
return index;
}
if (index > -1) {
delete this.spineByHref[section.href];
delete this.spineById[section.idref];
/**
* Prepend a Section to the Spine
* @private
* @param {Section} section
*/
prepend(section) {
// var index = this.spineItems.unshift(section);
this.spineByHref[section.href] = 0;
this.spineById[section.idref] = 0;
return this.spineItems.splice(index, 1);
}
}
// Re-index
this.spineItems.forEach(function(item, index){
item.index = index;
});
/**
* Loop over the Sections in the Spine
* @return {method} forEach
*/
each() {
return this.spineItems.forEach.apply(this.spineItems, arguments);
}
return 0;
}
/**
* Find the first Section in the Spine
* @return {Section} first section
*/
first() {
let index = 0;
// insert(section, index) {
//
// };
do {
let next = this.get(index);
/**
* Remove a Section from the Spine
* @private
* @param {Section} section
*/
remove(section) {
var index = this.spineItems.indexOf(section);
if (next && next.linear) {
return next;
}
index += 1;
} while (index < this.spineItems.length);
}
if(index > -1) {
delete this.spineByHref[section.href];
delete this.spineById[section.idref];
/**
* Find the last Section in the Spine
* @return {Section} last section
*/
last() {
let index = this.spineItems.length - 1;
return this.spineItems.splice(index, 1);
}
}
do {
let prev = this.get(index);
if (prev && prev.linear) {
return prev;
}
index -= 1;
} while (index >= 0);
}
/**
* Loop over the Sections in the Spine
* @return {method} forEach
*/
each() {
return this.spineItems.forEach.apply(this.spineItems, arguments);
}
destroy() {
this.each((section) => section.destroy());
/**
* Find the first Section in the Spine
* @return {Section} first section
*/
first() {
let index = 0;
this.spineItems = undefined;
this.spineByHref = undefined;
this.spineById = undefined;
do {
let next = this.get(index);
this.hooks.serialize.clear();
this.hooks.content.clear();
this.hooks = undefined;
if (next && next.linear) {
return next;
}
index += 1;
} while (index < this.spineItems.length) ;
}
this.epubcfi = undefined;
/**
* Find the last Section in the Spine
* @return {Section} last section
*/
last() {
let index = this.spineItems.length-1;
this.loaded = false;
do {
let prev = this.get(index);
if (prev && prev.linear) {
return prev;
}
index -= 1;
} while (index >= 0);
}
destroy() {
this.each((section) => section.destroy());
this.spineItems = undefined
this.spineByHref = undefined
this.spineById = undefined
this.hooks.serialize.clear();
this.hooks.content.clear();
this.hooks = undefined;
this.epubcfi = undefined;
this.loaded = false;
this.items = undefined;
this.manifest = undefined;
this.spineNodeIndex = undefined;
this.baseUrl = undefined;
this.length = undefined;
}
this.items = undefined;
this.manifest = undefined;
this.spineNodeIndex = undefined;
this.baseUrl = undefined;
this.length = undefined;
}
}
export default Spine;

View file

@ -1,9 +1,9 @@
import {defer, isXml, parse} from "./utils/core";
import httpRequest from "./utils/request";
import mime from "./utils/mime";
import Path from "./utils/path";
import EventEmitter from "event-emitter";
import localforage from "localforage";
import { defer, isXml, parse } from "./utils/core";
import mime from "./utils/mime";
import Path from "./utils/path";
import httpRequest from "./utils/request";
/**
* Handles saving and requesting files from local storage
@ -13,370 +13,352 @@ import localforage from "localforage";
* @param {function} [resolver]
*/
class Store {
constructor(name, requester, resolver) {
this.urlCache = {};
this.storage = undefined;
this.name = name;
this.requester = requester || httpRequest;
this.resolver = resolver;
this.online = true;
constructor(name, requester, resolver) {
this.urlCache = {};
this.checkRequirements();
this.addListeners();
}
this.storage = undefined;
/**
* Checks to see if localForage exists in global namspace,
* Requires localForage if it isn't there
* @private
*/
checkRequirements() {
try {
let store;
if (typeof localforage === "undefined") {
store = localforage;
}
this.storage = store.createInstance({
name: this.name,
});
} catch (e) {
throw new Error("localForage lib not loaded");
}
}
this.name = name;
this.requester = requester || httpRequest;
this.resolver = resolver;
/**
* Add online and offline event listeners
* @private
*/
addListeners() {
this._status = this.status.bind(this);
window.addEventListener("online", this._status);
window.addEventListener("offline", this._status);
}
this.online = true;
/**
* Remove online and offline event listeners
* @private
*/
removeListeners() {
window.removeEventListener("online", this._status);
window.removeEventListener("offline", this._status);
this._status = undefined;
}
this.checkRequirements();
/**
* Update the online / offline status
* @private
*/
status(event) {
let online = navigator.onLine;
this.online = online;
if (online) {
this.emit("online", this);
} else {
this.emit("offline", this);
}
}
this.addListeners();
}
/**
* Add all of a book resources to the store
* @param {Resources} resources book resources
* @param {boolean} [force] force resaving resources
* @return {Promise<object>} store objects
*/
add(resources, force) {
let mapped = resources.resources.map((item) => {
let { href } = item;
let url = this.resolver(href);
let encodedUrl = window.encodeURIComponent(url);
/**
* Checks to see if localForage exists in global namspace,
* Requires localForage if it isn't there
* @private
*/
checkRequirements(){
try {
let store;
if (typeof localforage === "undefined") {
store = localforage;
}
this.storage = store.createInstance({
name: this.name
});
} catch (e) {
throw new Error("localForage lib not loaded");
}
}
return this.storage.getItem(encodedUrl).then((item) => {
if (!item || force) {
return this.requester(url, "binary").then((data) => {
return this.storage.setItem(encodedUrl, data);
});
} else {
return item;
}
});
});
return Promise.all(mapped);
}
/**
* Add online and offline event listeners
* @private
*/
addListeners() {
this._status = this.status.bind(this);
window.addEventListener('online', this._status);
window.addEventListener('offline', this._status);
}
/**
* Put binary data from a url to storage
* @param {string} url a url to request from storage
* @param {boolean} [withCredentials]
* @param {object} [headers]
* @return {Promise<Blob>}
*/
put(url, withCredentials, headers) {
let encodedUrl = window.encodeURIComponent(url);
/**
* Remove online and offline event listeners
* @private
*/
removeListeners() {
window.removeEventListener('online', this._status);
window.removeEventListener('offline', this._status);
this._status = undefined;
}
return this.storage.getItem(encodedUrl).then((result) => {
if (!result) {
return this.requester(url, "binary", withCredentials, headers).then(
(data) => {
return this.storage.setItem(encodedUrl, data);
}
);
}
return result;
});
}
/**
* Update the online / offline status
* @private
*/
status(event) {
let online = navigator.onLine;
this.online = online;
if (online) {
this.emit("online", this);
} else {
this.emit("offline", this);
}
}
/**
* Request a url
* @param {string} url a url to request from storage
* @param {string} [type] specify the type of the returned result
* @param {boolean} [withCredentials]
* @param {object} [headers]
* @return {Promise<Blob | string | JSON | Document | XMLDocument>}
*/
request(url, type, withCredentials, headers) {
if (this.online) {
// From network
return this.requester(url, type, withCredentials, headers).then(
(data) => {
// save to store if not present
this.put(url);
return data;
}
);
} else {
// From store
return this.retrieve(url, type);
}
}
/**
* Add all of a book resources to the store
* @param {Resources} resources book resources
* @param {boolean} [force] force resaving resources
* @return {Promise<object>} store objects
*/
add(resources, force) {
let mapped = resources.resources.map((item) => {
let { href } = item;
let url = this.resolver(href);
let encodedUrl = window.encodeURIComponent(url);
/**
* Request a url from storage
* @param {string} url a url to request from storage
* @param {string} [type] specify the type of the returned result
* @return {Promise<Blob | string | JSON | Document | XMLDocument>}
*/
retrieve(url, type) {
var response;
var path = new Path(url);
return this.storage.getItem(encodedUrl).then((item) => {
if (!item || force) {
return this.requester(url, "binary")
.then((data) => {
return this.storage.setItem(encodedUrl, data);
});
} else {
return item;
}
});
// If type isn't set, determine it from the file extension
if (!type) {
type = path.extension;
}
});
return Promise.all(mapped);
}
if (type == "blob") {
response = this.getBlob(url);
} else {
response = this.getText(url);
}
/**
* Put binary data from a url to storage
* @param {string} url a url to request from storage
* @param {boolean} [withCredentials]
* @param {object} [headers]
* @return {Promise<Blob>}
*/
put(url, withCredentials, headers) {
let encodedUrl = window.encodeURIComponent(url);
return response.then((r) => {
var deferred = new defer();
var result;
if (r) {
result = this.handleResponse(r, type);
deferred.resolve(result);
} else {
deferred.reject({
message: "File not found in storage: " + url,
stack: new Error().stack,
});
}
return deferred.promise;
});
}
return this.storage.getItem(encodedUrl).then((result) => {
if (!result) {
return this.requester(url, "binary", withCredentials, headers).then((data) => {
return this.storage.setItem(encodedUrl, data);
});
}
return result;
});
}
/**
* Handle the response from request
* @private
* @param {any} response
* @param {string} [type]
* @return {any} the parsed result
*/
handleResponse(response, type) {
var r;
/**
* Request a url
* @param {string} url a url to request from storage
* @param {string} [type] specify the type of the returned result
* @param {boolean} [withCredentials]
* @param {object} [headers]
* @return {Promise<Blob | string | JSON | Document | XMLDocument>}
*/
request(url, type, withCredentials, headers){
if (this.online) {
// From network
return this.requester(url, type, withCredentials, headers).then((data) => {
// save to store if not present
this.put(url);
return data;
})
} else {
// From store
return this.retrieve(url, type);
}
if (type == "json") {
r = JSON.parse(response);
} else if (isXml(type)) {
r = parse(response, "text/xml");
} else if (type == "xhtml") {
r = parse(response, "application/xhtml+xml");
} else if (type == "html" || type == "htm") {
r = parse(response, "text/html");
} else {
r = response;
}
}
return r;
}
/**
* Request a url from storage
* @param {string} url a url to request from storage
* @param {string} [type] specify the type of the returned result
* @return {Promise<Blob | string | JSON | Document | XMLDocument>}
*/
retrieve(url, type) {
var deferred = new defer();
var response;
var path = new Path(url);
/**
* Get a Blob from Storage by Url
* @param {string} url
* @param {string} [mimeType]
* @return {Blob}
*/
getBlob(url, mimeType) {
let encodedUrl = window.encodeURIComponent(url);
// If type isn't set, determine it from the file extension
if(!type) {
type = path.extension;
}
return this.storage.getItem(encodedUrl).then(function (uint8array) {
if (!uint8array) return;
if(type == "blob"){
response = this.getBlob(url);
} else {
response = this.getText(url);
}
mimeType = mimeType || mime.lookup(url);
return new Blob([uint8array], { type: mimeType });
});
}
return response.then((r) => {
var deferred = new defer();
var result;
if (r) {
result = this.handleResponse(r, type);
deferred.resolve(result);
} else {
deferred.reject({
message : "File not found in storage: " + url,
stack : new Error().stack
});
}
return deferred.promise;
});
}
/**
* Get Text from Storage by Url
* @param {string} url
* @param {string} [mimeType]
* @return {string}
*/
getText(url, mimeType) {
let encodedUrl = window.encodeURIComponent(url);
/**
* Handle the response from request
* @private
* @param {any} response
* @param {string} [type]
* @return {any} the parsed result
*/
handleResponse(response, type){
var r;
mimeType = mimeType || mime.lookup(url);
if(type == "json") {
r = JSON.parse(response);
}
else
if(isXml(type)) {
r = parse(response, "text/xml");
}
else
if(type == "xhtml") {
r = parse(response, "application/xhtml+xml");
}
else
if(type == "html" || type == "htm") {
r = parse(response, "text/html");
} else {
r = response;
}
return this.storage.getItem(encodedUrl).then(function (uint8array) {
var deferred = new defer();
var reader = new FileReader();
var blob;
return r;
}
if (!uint8array) return;
/**
* Get a Blob from Storage by Url
* @param {string} url
* @param {string} [mimeType]
* @return {Blob}
*/
getBlob(url, mimeType){
let encodedUrl = window.encodeURIComponent(url);
blob = new Blob([uint8array], { type: mimeType });
return this.storage.getItem(encodedUrl).then(function(uint8array) {
if(!uint8array) return;
reader.addEventListener("loadend", () => {
deferred.resolve(reader.result);
});
mimeType = mimeType || mime.lookup(url);
reader.readAsText(blob, mimeType);
return new Blob([uint8array], {type : mimeType});
});
return deferred.promise;
});
}
}
/**
* Get a base64 encoded result from Storage by Url
* @param {string} url
* @param {string} [mimeType]
* @return {string} base64 encoded
*/
getBase64(url, mimeType) {
let encodedUrl = window.encodeURIComponent(url);
/**
* Get Text from Storage by Url
* @param {string} url
* @param {string} [mimeType]
* @return {string}
*/
getText(url, mimeType){
let encodedUrl = window.encodeURIComponent(url);
mimeType = mimeType || mime.lookup(url);
mimeType = mimeType || mime.lookup(url);
return this.storage.getItem(encodedUrl).then((uint8array) => {
var deferred = new defer();
var reader = new FileReader();
var blob;
return this.storage.getItem(encodedUrl).then(function(uint8array) {
var deferred = new defer();
var reader = new FileReader();
var blob;
if (!uint8array) return;
if(!uint8array) return;
blob = new Blob([uint8array], { type: mimeType });
blob = new Blob([uint8array], {type : mimeType});
reader.addEventListener("loadend", () => {
deferred.resolve(reader.result);
});
reader.readAsDataURL(blob, mimeType);
reader.addEventListener("loadend", () => {
deferred.resolve(reader.result);
});
return deferred.promise;
});
}
reader.readAsText(blob, mimeType);
/**
* Create a Url from a stored item
* @param {string} url
* @param {object} [options.base64] use base64 encoding or blob url
* @return {Promise} url promise with Url string
*/
createUrl(url, options) {
var deferred = new defer();
var _URL = window.URL || window.webkitURL || window.mozURL;
var tempUrl;
var response;
var useBase64 = options && options.base64;
return deferred.promise;
});
}
if (url in this.urlCache) {
deferred.resolve(this.urlCache[url]);
return deferred.promise;
}
/**
* Get a base64 encoded result from Storage by Url
* @param {string} url
* @param {string} [mimeType]
* @return {string} base64 encoded
*/
getBase64(url, mimeType){
let encodedUrl = window.encodeURIComponent(url);
if (useBase64) {
response = this.getBase64(url);
mimeType = mimeType || mime.lookup(url);
if (response) {
response.then(
function (tempUrl) {
this.urlCache[url] = tempUrl;
deferred.resolve(tempUrl);
}.bind(this)
);
}
} else {
response = this.getBlob(url);
return this.storage.getItem(encodedUrl).then((uint8array) => {
var deferred = new defer();
var reader = new FileReader();
var blob;
if (response) {
response.then(
function (blob) {
tempUrl = _URL.createObjectURL(blob);
this.urlCache[url] = tempUrl;
deferred.resolve(tempUrl);
}.bind(this)
);
}
}
if(!uint8array) return;
if (!response) {
deferred.reject({
message: "File not found in storage: " + url,
stack: new Error().stack,
});
}
blob = new Blob([uint8array], {type : mimeType});
return deferred.promise;
}
reader.addEventListener("loadend", () => {
deferred.resolve(reader.result);
});
reader.readAsDataURL(blob, mimeType);
/**
* Revoke Temp Url for a archive item
* @param {string} url url of the item in the store
*/
revokeUrl(url) {
var _URL = window.URL || window.webkitURL || window.mozURL;
var fromCache = this.urlCache[url];
if (fromCache) _URL.revokeObjectURL(fromCache);
}
return deferred.promise;
});
}
/**
* Create a Url from a stored item
* @param {string} url
* @param {object} [options.base64] use base64 encoding or blob url
* @return {Promise} url promise with Url string
*/
createUrl(url, options){
var deferred = new defer();
var _URL = window.URL || window.webkitURL || window.mozURL;
var tempUrl;
var response;
var useBase64 = options && options.base64;
if(url in this.urlCache) {
deferred.resolve(this.urlCache[url]);
return deferred.promise;
}
if (useBase64) {
response = this.getBase64(url);
if (response) {
response.then(function(tempUrl) {
this.urlCache[url] = tempUrl;
deferred.resolve(tempUrl);
}.bind(this));
}
} else {
response = this.getBlob(url);
if (response) {
response.then(function(blob) {
tempUrl = _URL.createObjectURL(blob);
this.urlCache[url] = tempUrl;
deferred.resolve(tempUrl);
}.bind(this));
}
}
if (!response) {
deferred.reject({
message : "File not found in storage: " + url,
stack : new Error().stack
});
}
return deferred.promise;
}
/**
* Revoke Temp Url for a archive item
* @param {string} url url of the item in the store
*/
revokeUrl(url){
var _URL = window.URL || window.webkitURL || window.mozURL;
var fromCache = this.urlCache[url];
if(fromCache) _URL.revokeObjectURL(fromCache);
}
destroy() {
var _URL = window.URL || window.webkitURL || window.mozURL;
for (let fromCache in this.urlCache) {
_URL.revokeObjectURL(fromCache);
}
this.urlCache = {};
this.removeListeners();
}
destroy() {
var _URL = window.URL || window.webkitURL || window.mozURL;
for (let fromCache in this.urlCache) {
_URL.revokeObjectURL(fromCache);
}
this.urlCache = {};
this.removeListeners();
}
}
EventEmitter(Store.prototype);

View file

@ -6,263 +6,271 @@ import Url from "./utils/url";
* @param {Rendition} rendition
*/
class Themes {
constructor(rendition) {
this.rendition = rendition;
this._themes = {
"default" : {
"rules" : {},
"url" : "",
"serialized" : ""
}
};
this._overrides = {};
this._current = "default";
this._injected = [];
this.rendition.hooks.content.register(this.inject.bind(this));
this.rendition.hooks.content.register(this.overrides.bind(this));
constructor(rendition) {
this.rendition = rendition;
this._themes = {
default: {
rules: {},
url: "",
serialized: "",
},
};
this._overrides = {};
this._current = "default";
this._injected = [];
this.rendition.hooks.content.register(this.inject.bind(this));
this.rendition.hooks.content.register(this.overrides.bind(this));
}
}
/**
* Add themes to be used by a rendition
* @param {object | Array<object> | string}
* @example themes.register("light", "http://example.com/light.css")
* @example themes.register("light", { "body": { "color": "purple"}})
* @example themes.register({ "light" : {...}, "dark" : {...}})
*/
register() {
if (arguments.length === 0) {
return;
}
if (arguments.length === 1 && typeof arguments[0] === "object") {
return this.registerThemes(arguments[0]);
}
if (arguments.length === 1 && typeof arguments[0] === "string") {
return this.default(arguments[0]);
}
if (arguments.length === 2 && typeof arguments[1] === "string") {
return this.registerUrl(arguments[0], arguments[1]);
}
if (arguments.length === 2 && typeof arguments[1] === "object") {
return this.registerRules(arguments[0], arguments[1]);
}
}
/**
* Add themes to be used by a rendition
* @param {object | Array<object> | string}
* @example themes.register("light", "http://example.com/light.css")
* @example themes.register("light", { "body": { "color": "purple"}})
* @example themes.register({ "light" : {...}, "dark" : {...}})
*/
register () {
if (arguments.length === 0) {
return;
}
if (arguments.length === 1 && typeof(arguments[0]) === "object") {
return this.registerThemes(arguments[0]);
}
if (arguments.length === 1 && typeof(arguments[0]) === "string") {
return this.default(arguments[0]);
}
if (arguments.length === 2 && typeof(arguments[1]) === "string") {
return this.registerUrl(arguments[0], arguments[1]);
}
if (arguments.length === 2 && typeof(arguments[1]) === "object") {
return this.registerRules(arguments[0], arguments[1]);
}
}
/**
* Add a default theme to be used by a rendition
* @param {object | string} theme
* @example themes.register("http://example.com/default.css")
* @example themes.register({ "body": { "color": "purple"}})
*/
default(theme) {
if (!theme) {
return;
}
if (typeof theme === "string") {
return this.registerUrl("default", theme);
}
if (typeof theme === "object") {
return this.registerRules("default", theme);
}
}
/**
* Add a default theme to be used by a rendition
* @param {object | string} theme
* @example themes.register("http://example.com/default.css")
* @example themes.register({ "body": { "color": "purple"}})
*/
default (theme) {
if (!theme) {
return;
}
if (typeof(theme) === "string") {
return this.registerUrl("default", theme);
}
if (typeof(theme) === "object") {
return this.registerRules("default", theme);
}
}
/**
* Register themes object
* @param {object} themes
*/
registerThemes(themes) {
for (var theme in themes) {
if (themes.hasOwnProperty(theme)) {
if (typeof themes[theme] === "string") {
this.registerUrl(theme, themes[theme]);
} else {
this.registerRules(theme, themes[theme]);
}
}
}
}
/**
* Register themes object
* @param {object} themes
*/
registerThemes (themes) {
for (var theme in themes) {
if (themes.hasOwnProperty(theme)) {
if (typeof(themes[theme]) === "string") {
this.registerUrl(theme, themes[theme]);
} else {
this.registerRules(theme, themes[theme]);
}
}
}
}
/**
* Register a theme by passing its css as string
* @param {string} name
* @param {string} css
*/
registerCss(name, css) {
this._themes[name] = { serialized: css };
if (this._injected[name] || name == "default") {
this.update(name);
}
}
/**
* Register a theme by passing its css as string
* @param {string} name
* @param {string} css
*/
registerCss (name, css) {
this._themes[name] = { "serialized" : css };
if (this._injected[name] || name == 'default') {
this.update(name);
}
}
/**
* Register a url
* @param {string} name
* @param {string} input
*/
registerUrl(name, input) {
var url = new Url(input);
this._themes[name] = { url: url.toString() };
if (this._injected[name] || name == "default") {
this.update(name);
}
}
/**
* Register a url
* @param {string} name
* @param {string} input
*/
registerUrl (name, input) {
var url = new Url(input);
this._themes[name] = { "url": url.toString() };
if (this._injected[name] || name == 'default') {
this.update(name);
}
}
/**
* Register rule
* @param {string} name
* @param {object} rules
*/
registerRules(name, rules) {
this._themes[name] = { rules: rules };
// TODO: serialize css rules
if (this._injected[name] || name == "default") {
this.update(name);
}
}
/**
* Register rule
* @param {string} name
* @param {object} rules
*/
registerRules (name, rules) {
this._themes[name] = { "rules": rules };
// TODO: serialize css rules
if (this._injected[name] || name == 'default') {
this.update(name);
}
}
/**
* Select a theme
* @param {string} name
*/
select(name) {
var prev = this._current;
var contents;
/**
* Select a theme
* @param {string} name
*/
select (name) {
var prev = this._current;
var contents;
this._current = name;
this.update(name);
this._current = name;
this.update(name);
contents = this.rendition.getContents();
contents.forEach((content) => {
content.removeClass(prev);
content.addClass(name);
});
}
contents = this.rendition.getContents();
contents.forEach( (content) => {
content.removeClass(prev);
content.addClass(name);
});
}
/**
* Update a theme
* @param {string} name
*/
update(name) {
var contents = this.rendition.getContents();
contents.forEach((content) => {
this.add(name, content);
});
}
/**
* Update a theme
* @param {string} name
*/
update (name) {
var contents = this.rendition.getContents();
contents.forEach( (content) => {
this.add(name, content);
});
}
/**
* Inject all themes into contents
* @param {Contents} contents
*/
inject(contents) {
var links = [];
var themes = this._themes;
var theme;
/**
* Inject all themes into contents
* @param {Contents} contents
*/
inject (contents) {
var links = [];
var themes = this._themes;
var theme;
for (var name in themes) {
if (
themes.hasOwnProperty(name) &&
(name === this._current || name === "default")
) {
theme = themes[name];
if (
(theme.rules && Object.keys(theme.rules).length > 0) ||
(theme.url && links.indexOf(theme.url) === -1)
) {
this.add(name, contents);
}
this._injected.push(name);
}
}
for (var name in themes) {
if (themes.hasOwnProperty(name) && (name === this._current || name === "default")) {
theme = themes[name];
if((theme.rules && Object.keys(theme.rules).length > 0) || (theme.url && links.indexOf(theme.url) === -1)) {
this.add(name, contents);
}
this._injected.push(name);
}
}
if (this._current != "default") {
contents.addClass(this._current);
}
}
if(this._current != "default") {
contents.addClass(this._current);
}
}
/**
* Add Theme to contents
* @param {string} name
* @param {Contents} contents
*/
add(name, contents) {
var theme = this._themes[name];
/**
* Add Theme to contents
* @param {string} name
* @param {Contents} contents
*/
add (name, contents) {
var theme = this._themes[name];
if (!theme || !contents) {
return;
}
if (!theme || !contents) {
return;
}
if (theme.url) {
contents.addStylesheet(theme.url);
} else if (theme.serialized) {
contents.addStylesheetCss(theme.serialized, name);
theme.injected = true;
} else if (theme.rules) {
contents.addStylesheetRules(theme.rules, name);
theme.injected = true;
}
}
if (theme.url) {
contents.addStylesheet(theme.url);
} else if (theme.serialized) {
contents.addStylesheetCss(theme.serialized, name);
theme.injected = true;
} else if (theme.rules) {
contents.addStylesheetRules(theme.rules, name);
theme.injected = true;
}
}
/**
* Add override
* @param {string} name
* @param {string} value
* @param {boolean} priority
*/
override(name, value, priority) {
var contents = this.rendition.getContents();
/**
* Add override
* @param {string} name
* @param {string} value
* @param {boolean} priority
*/
override (name, value, priority) {
var contents = this.rendition.getContents();
this._overrides[name] = {
value: value,
priority: priority === true,
};
this._overrides[name] = {
value: value,
priority: priority === true
};
contents.forEach((content) => {
content.css(
name,
this._overrides[name].value,
this._overrides[name].priority
);
});
}
contents.forEach( (content) => {
content.css(name, this._overrides[name].value, this._overrides[name].priority);
});
}
removeOverride(name) {
var contents = this.rendition.getContents();
removeOverride (name) {
var contents = this.rendition.getContents();
delete this._overrides[name];
delete this._overrides[name];
contents.forEach((content) => {
content.css(name);
});
}
contents.forEach( (content) => {
content.css(name);
});
}
/**
* Add all overrides
* @param {Content} content
*/
overrides(contents) {
var overrides = this._overrides;
/**
* Add all overrides
* @param {Content} content
*/
overrides (contents) {
var overrides = this._overrides;
for (var rule in overrides) {
if (overrides.hasOwnProperty(rule)) {
contents.css(rule, overrides[rule].value, overrides[rule].priority);
}
}
}
for (var rule in overrides) {
if (overrides.hasOwnProperty(rule)) {
contents.css(rule, overrides[rule].value, overrides[rule].priority);
}
}
}
/**
* Adjust the font size of a rendition
* @param {number} size
*/
fontSize(size) {
this.override("font-size", size);
}
/**
* Adjust the font size of a rendition
* @param {number} size
*/
fontSize (size) {
this.override("font-size", size);
}
/**
* Adjust the font-family of a rendition
* @param {string} f
*/
font (f) {
this.override("font-family", f, true);
}
destroy() {
this.rendition = undefined;
this._themes = undefined;
this._overrides = undefined;
this._current = undefined;
this._injected = undefined;
}
/**
* Adjust the font-family of a rendition
* @param {string} f
*/
font(f) {
this.override("font-family", f, true);
}
destroy() {
this.rendition = undefined;
this._themes = undefined;
this._overrides = undefined;
this._current = undefined;
this._injected = undefined;
}
}
export default Themes;

View file

@ -1,62 +1,73 @@
export const EPUBJS_VERSION = "0.3";
// Dom events to listen for
export const DOM_EVENTS = ["keydown", "keyup", "keypressed", "mouseup", "mousedown", "mousemove", "click", "touchend", "touchstart", "touchmove"];
export const DOM_EVENTS = [
"keydown",
"keyup",
"keypressed",
"mouseup",
"mousedown",
"mousemove",
"click",
"touchend",
"touchstart",
"touchmove",
];
export const EVENTS = {
BOOK : {
OPEN_FAILED : "openFailed"
BOOK: {
OPEN_FAILED: "openFailed",
},
CONTENTS : {
EXPAND : "expand",
RESIZE : "resize",
SELECTED : "selected",
SELECTED_RANGE : "selectedRange",
LINK_CLICKED : "linkClicked"
CONTENTS: {
EXPAND: "expand",
RESIZE: "resize",
SELECTED: "selected",
SELECTED_RANGE: "selectedRange",
LINK_CLICKED: "linkClicked",
},
LOCATIONS : {
CHANGED : "changed"
LOCATIONS: {
CHANGED: "changed",
},
MANAGERS : {
RESIZE : "resize",
RESIZED : "resized",
ORIENTATION_CHANGE : "orientationchange",
ADDED : "added",
SCROLL : "scroll",
SCROLLED : "scrolled",
REMOVED : "removed",
MANAGERS: {
RESIZE: "resize",
RESIZED: "resized",
ORIENTATION_CHANGE: "orientationchange",
ADDED: "added",
SCROLL: "scroll",
SCROLLED: "scrolled",
REMOVED: "removed",
},
VIEWS : {
VIEWS: {
AXIS: "axis",
WRITING_MODE: "writingMode",
LOAD_ERROR : "loaderror",
RENDERED : "rendered",
RESIZED : "resized",
DISPLAYED : "displayed",
SHOWN : "shown",
HIDDEN : "hidden",
MARK_CLICKED : "markClicked"
LOAD_ERROR: "loaderror",
RENDERED: "rendered",
RESIZED: "resized",
DISPLAYED: "displayed",
SHOWN: "shown",
HIDDEN: "hidden",
MARK_CLICKED: "markClicked",
},
RENDITION : {
STARTED : "started",
ATTACHED : "attached",
DISPLAYED : "displayed",
DISPLAY_ERROR : "displayerror",
RENDERED : "rendered",
REMOVED : "removed",
RESIZED : "resized",
ORIENTATION_CHANGE : "orientationchange",
LOCATION_CHANGED : "locationChanged",
RELOCATED : "relocated",
MARK_CLICKED : "markClicked",
SELECTED : "selected",
LAYOUT: "layout"
RENDITION: {
STARTED: "started",
ATTACHED: "attached",
DISPLAYED: "displayed",
DISPLAY_ERROR: "displayerror",
RENDERED: "rendered",
REMOVED: "removed",
RESIZED: "resized",
ORIENTATION_CHANGE: "orientationchange",
LOCATION_CHANGED: "locationChanged",
RELOCATED: "relocated",
MARK_CLICKED: "markClicked",
SELECTED: "selected",
LAYOUT: "layout",
},
LAYOUT : {
UPDATED : "updated"
LAYOUT: {
UPDATED: "updated",
},
ANNOTATION : {
ATTACH : "attach",
DETACH : "detach"
}
}
ANNOTATION: {
ATTACH: "attach",
DETACH: "detach",
},
};

File diff suppressed because it is too large Load diff

View file

@ -6,77 +6,76 @@
* @example this.content = new EPUBJS.Hook(this);
*/
class Hook {
constructor(context){
this.context = context || this;
this.hooks = [];
}
constructor(context) {
this.context = context || this;
this.hooks = [];
}
/**
* Adds a function to be run before a hook completes
* @example this.content.register(function(){...});
*/
register(){
for(var i = 0; i < arguments.length; ++i) {
if (typeof arguments[i] === "function") {
this.hooks.push(arguments[i]);
} else {
// unpack array
for(var j = 0; j < arguments[i].length; ++j) {
this.hooks.push(arguments[i][j]);
}
}
}
}
/**
* Adds a function to be run before a hook completes
* @example this.content.register(function(){...});
*/
register() {
for (var i = 0; i < arguments.length; ++i) {
if (typeof arguments[i] === "function") {
this.hooks.push(arguments[i]);
} else {
// unpack array
for (var j = 0; j < arguments[i].length; ++j) {
this.hooks.push(arguments[i][j]);
}
}
}
}
/**
* Removes a function
* @example this.content.deregister(function(){...});
*/
deregister(func){
let hook;
for (let i = 0; i < this.hooks.length; i++) {
hook = this.hooks[i];
if (hook === func) {
this.hooks.splice(i, 1);
break;
}
}
}
/**
* Removes a function
* @example this.content.deregister(function(){...});
*/
deregister(func) {
let hook;
for (let i = 0; i < this.hooks.length; i++) {
hook = this.hooks[i];
if (hook === func) {
this.hooks.splice(i, 1);
break;
}
}
}
/**
* Triggers a hook to run all functions
* @example this.content.trigger(args).then(function(){...});
*/
trigger(){
var args = arguments;
var context = this.context;
var promises = [];
/**
* Triggers a hook to run all functions
* @example this.content.trigger(args).then(function(){...});
*/
trigger() {
var args = arguments;
var context = this.context;
var promises = [];
this.hooks.forEach(function(task) {
try {
var executing = task.apply(context, args);
} catch (err) {
console.log(err);
}
this.hooks.forEach(function (task) {
try {
var executing = task.apply(context, args);
} catch (err) {
console.error(err);
}
if(executing && typeof executing["then"] === "function") {
// Task is a function that returns a promise
promises.push(executing);
}
// Otherwise Task resolves immediately, continue
});
if (executing && typeof executing["then"] === "function") {
// Task is a function that returns a promise
promises.push(executing);
}
// Otherwise Task resolves immediately, continue
});
return Promise.all(promises);
}
return Promise.all(promises);
}
// Adds a function to be run before a hook completes
list() {
return this.hooks;
}
// Adds a function to be run before a hook completes
list(){
return this.hooks;
}
clear(){
return this.hooks = [];
}
clear() {
return (this.hooks = []);
}
}
export default Hook;

View file

@ -4,166 +4,173 @@ edited down
*/
var table = {
"application" : {
"ecmascript" : [ "es", "ecma" ],
"javascript" : "js",
"ogg" : "ogx",
"pdf" : "pdf",
"postscript" : [ "ps", "ai", "eps", "epsi", "epsf", "eps2", "eps3" ],
"rdf+xml" : "rdf",
"smil" : [ "smi", "smil" ],
"xhtml+xml" : [ "xhtml", "xht" ],
"xml" : [ "xml", "xsl", "xsd", "opf", "ncx" ],
"zip" : "zip",
"x-httpd-eruby" : "rhtml",
"x-latex" : "latex",
"x-maker" : [ "frm", "maker", "frame", "fm", "fb", "book", "fbdoc" ],
"x-object" : "o",
"x-shockwave-flash" : [ "swf", "swfl" ],
"x-silverlight" : "scr",
"epub+zip" : "epub",
"font-tdpfr" : "pfr",
"inkml+xml" : [ "ink", "inkml" ],
"json" : "json",
"jsonml+json" : "jsonml",
"mathml+xml" : "mathml",
"metalink+xml" : "metalink",
"mp4" : "mp4s",
// "oebps-package+xml" : "opf",
"omdoc+xml" : "omdoc",
"oxps" : "oxps",
"vnd.amazon.ebook" : "azw",
"widget" : "wgt",
// "x-dtbncx+xml" : "ncx",
"x-dtbook+xml" : "dtb",
"x-dtbresource+xml" : "res",
"x-font-bdf" : "bdf",
"x-font-ghostscript" : "gsf",
"x-font-linux-psf" : "psf",
"x-font-otf" : "otf",
"x-font-pcf" : "pcf",
"x-font-snf" : "snf",
"x-font-ttf" : [ "ttf", "ttc" ],
"x-font-type1" : [ "pfa", "pfb", "pfm", "afm" ],
"x-font-woff" : "woff",
"x-mobipocket-ebook" : [ "prc", "mobi" ],
"x-mspublisher" : "pub",
"x-nzb" : "nzb",
"x-tgif" : "obj",
"xaml+xml" : "xaml",
"xml-dtd" : "dtd",
"xproc+xml" : "xpl",
"xslt+xml" : "xslt",
"internet-property-stream" : "acx",
"x-compress" : "z",
"x-compressed" : "tgz",
"x-gzip" : "gz",
},
"audio" : {
"flac" : "flac",
"midi" : [ "mid", "midi", "kar", "rmi" ],
"mpeg" : [ "mpga", "mpega", "mp2", "mp3", "m4a", "mp2a", "m2a", "m3a" ],
"mpegurl" : "m3u",
"ogg" : [ "oga", "ogg", "spx" ],
"x-aiff" : [ "aif", "aiff", "aifc" ],
"x-ms-wma" : "wma",
"x-wav" : "wav",
"adpcm" : "adp",
"mp4" : "mp4a",
"webm" : "weba",
"x-aac" : "aac",
"x-caf" : "caf",
"x-matroska" : "mka",
"x-pn-realaudio-plugin" : "rmp",
"xm" : "xm",
"mid" : [ "mid", "rmi" ]
},
"image" : {
"gif" : "gif",
"ief" : "ief",
"jpeg" : [ "jpeg", "jpg", "jpe" ],
"pcx" : "pcx",
"png" : "png",
"svg+xml" : [ "svg", "svgz" ],
"tiff" : [ "tiff", "tif" ],
"x-icon" : "ico",
"bmp" : "bmp",
"webp" : "webp",
"x-pict" : [ "pic", "pct" ],
"x-tga" : "tga",
"cis-cod" : "cod"
},
"text" : {
"cache-manifest" : [ "manifest", "appcache" ],
"css" : "css",
"csv" : "csv",
"html" : [ "html", "htm", "shtml", "stm" ],
"mathml" : "mml",
"plain" : [ "txt", "text", "brf", "conf", "def", "list", "log", "in", "bas" ],
"richtext" : "rtx",
"tab-separated-values" : "tsv",
"x-bibtex" : "bib"
},
"video" : {
"mpeg" : [ "mpeg", "mpg", "mpe", "m1v", "m2v", "mp2", "mpa", "mpv2" ],
"mp4" : [ "mp4", "mp4v", "mpg4" ],
"quicktime" : [ "qt", "mov" ],
"ogg" : "ogv",
"vnd.mpegurl" : [ "mxu", "m4u" ],
"x-flv" : "flv",
"x-la-asf" : [ "lsf", "lsx" ],
"x-mng" : "mng",
"x-ms-asf" : [ "asf", "asx", "asr" ],
"x-ms-wm" : "wm",
"x-ms-wmv" : "wmv",
"x-ms-wmx" : "wmx",
"x-ms-wvx" : "wvx",
"x-msvideo" : "avi",
"x-sgi-movie" : "movie",
"x-matroska" : [ "mpv", "mkv", "mk3d", "mks" ],
"3gpp2" : "3g2",
"h261" : "h261",
"h263" : "h263",
"h264" : "h264",
"jpeg" : "jpgv",
"jpm" : [ "jpm", "jpgm" ],
"mj2" : [ "mj2", "mjp2" ],
"vnd.ms-playready.media.pyv" : "pyv",
"vnd.uvvu.mp4" : [ "uvu", "uvvu" ],
"vnd.vivo" : "viv",
"webm" : "webm",
"x-f4v" : "f4v",
"x-m4v" : "m4v",
"x-ms-vob" : "vob",
"x-smv" : "smv"
}
application: {
ecmascript: ["es", "ecma"],
javascript: "js",
ogg: "ogx",
pdf: "pdf",
postscript: ["ps", "ai", "eps", "epsi", "epsf", "eps2", "eps3"],
"rdf+xml": "rdf",
smil: ["smi", "smil"],
"xhtml+xml": ["xhtml", "xht"],
xml: ["xml", "xsl", "xsd", "opf", "ncx"],
zip: "zip",
"x-httpd-eruby": "rhtml",
"x-latex": "latex",
"x-maker": ["frm", "maker", "frame", "fm", "fb", "book", "fbdoc"],
"x-object": "o",
"x-shockwave-flash": ["swf", "swfl"],
"x-silverlight": "scr",
"epub+zip": "epub",
"font-tdpfr": "pfr",
"inkml+xml": ["ink", "inkml"],
json: "json",
"jsonml+json": "jsonml",
"mathml+xml": "mathml",
"metalink+xml": "metalink",
mp4: "mp4s",
// "oebps-package+xml" : "opf",
"omdoc+xml": "omdoc",
oxps: "oxps",
"vnd.amazon.ebook": "azw",
widget: "wgt",
// "x-dtbncx+xml" : "ncx",
"x-dtbook+xml": "dtb",
"x-dtbresource+xml": "res",
"x-font-bdf": "bdf",
"x-font-ghostscript": "gsf",
"x-font-linux-psf": "psf",
"x-font-otf": "otf",
"x-font-pcf": "pcf",
"x-font-snf": "snf",
"x-font-ttf": ["ttf", "ttc"],
"x-font-type1": ["pfa", "pfb", "pfm", "afm"],
"x-font-woff": "woff",
"x-mobipocket-ebook": ["prc", "mobi"],
"x-mspublisher": "pub",
"x-nzb": "nzb",
"x-tgif": "obj",
"xaml+xml": "xaml",
"xml-dtd": "dtd",
"xproc+xml": "xpl",
"xslt+xml": "xslt",
"internet-property-stream": "acx",
"x-compress": "z",
"x-compressed": "tgz",
"x-gzip": "gz",
},
audio: {
flac: "flac",
midi: ["mid", "midi", "kar", "rmi"],
mpeg: ["mpga", "mpega", "mp2", "mp3", "m4a", "mp2a", "m2a", "m3a"],
mpegurl: "m3u",
ogg: ["oga", "ogg", "spx"],
"x-aiff": ["aif", "aiff", "aifc"],
"x-ms-wma": "wma",
"x-wav": "wav",
adpcm: "adp",
mp4: "mp4a",
webm: "weba",
"x-aac": "aac",
"x-caf": "caf",
"x-matroska": "mka",
"x-pn-realaudio-plugin": "rmp",
xm: "xm",
mid: ["mid", "rmi"],
},
image: {
gif: "gif",
ief: "ief",
jpeg: ["jpeg", "jpg", "jpe"],
pcx: "pcx",
png: "png",
"svg+xml": ["svg", "svgz"],
tiff: ["tiff", "tif"],
"x-icon": "ico",
bmp: "bmp",
webp: "webp",
"x-pict": ["pic", "pct"],
"x-tga": "tga",
"cis-cod": "cod",
},
text: {
"cache-manifest": ["manifest", "appcache"],
css: "css",
csv: "csv",
html: ["html", "htm", "shtml", "stm"],
mathml: "mml",
plain: ["txt", "text", "brf", "conf", "def", "list", "log", "in", "bas"],
richtext: "rtx",
"tab-separated-values": "tsv",
"x-bibtex": "bib",
},
video: {
mpeg: ["mpeg", "mpg", "mpe", "m1v", "m2v", "mp2", "mpa", "mpv2"],
mp4: ["mp4", "mp4v", "mpg4"],
quicktime: ["qt", "mov"],
ogg: "ogv",
"vnd.mpegurl": ["mxu", "m4u"],
"x-flv": "flv",
"x-la-asf": ["lsf", "lsx"],
"x-mng": "mng",
"x-ms-asf": ["asf", "asx", "asr"],
"x-ms-wm": "wm",
"x-ms-wmv": "wmv",
"x-ms-wmx": "wmx",
"x-ms-wvx": "wvx",
"x-msvideo": "avi",
"x-sgi-movie": "movie",
"x-matroska": ["mpv", "mkv", "mk3d", "mks"],
"3gpp2": "3g2",
h261: "h261",
h263: "h263",
h264: "h264",
jpeg: "jpgv",
jpm: ["jpm", "jpgm"],
mj2: ["mj2", "mjp2"],
"vnd.ms-playready.media.pyv": "pyv",
"vnd.uvvu.mp4": ["uvu", "uvvu"],
"vnd.vivo": "viv",
webm: "webm",
"x-f4v": "f4v",
"x-m4v": "m4v",
"x-ms-vob": "vob",
"x-smv": "smv",
},
};
var mimeTypes = (function() {
var type, subtype, val, index, mimeTypes = {};
for (type in table) {
if (table.hasOwnProperty(type)) {
for (subtype in table[type]) {
if (table[type].hasOwnProperty(subtype)) {
val = table[type][subtype];
if (typeof val == "string") {
mimeTypes[val] = type + "/" + subtype;
} else {
for (index = 0; index < val.length; index++) {
mimeTypes[val[index]] = type + "/" + subtype;
}
}
}
}
}
}
return mimeTypes;
var mimeTypes = (function () {
var type,
subtype,
val,
index,
mimeTypes = {};
for (type in table) {
if (table.hasOwnProperty(type)) {
for (subtype in table[type]) {
if (table[type].hasOwnProperty(subtype)) {
val = table[type][subtype];
if (typeof val == "string") {
mimeTypes[val] = type + "/" + subtype;
} else {
for (index = 0; index < val.length; index++) {
mimeTypes[val[index]] = type + "/" + subtype;
}
}
}
}
}
}
return mimeTypes;
})();
var defaultValue = "text/plain";//"application/octet-stream";
var defaultValue = "text/plain"; //"application/octet-stream";
function lookup(filename) {
return filename && mimeTypes[filename.split(".").pop().toLowerCase()] || defaultValue;
};
return (
(filename && mimeTypes[filename.split(".").pop().toLowerCase()]) ||
defaultValue
);
}
export default { lookup };

View file

@ -8,95 +8,94 @@ import path from "path-webpack";
* @class
*/
class Path {
constructor(pathString) {
var protocol;
var parsed;
constructor(pathString) {
var protocol;
var parsed;
protocol = pathString.indexOf("://");
if (protocol > -1) {
pathString = new URL(pathString).pathname;
}
protocol = pathString.indexOf("://");
if (protocol > -1) {
pathString = new URL(pathString).pathname;
}
parsed = this.parse(pathString);
parsed = this.parse(pathString);
this.path = pathString;
this.path = pathString;
if (this.isDirectory(pathString)) {
this.directory = pathString;
} else {
this.directory = parsed.dir + "/";
}
if (this.isDirectory(pathString)) {
this.directory = pathString;
} else {
this.directory = parsed.dir + "/";
}
this.filename = parsed.base;
this.extension = parsed.ext.slice(1);
this.filename = parsed.base;
this.extension = parsed.ext.slice(1);
}
}
/**
* Parse the path: https://nodejs.org/api/path.html#path_path_parse_path
* @param {string} what
* @returns {object}
*/
parse(what) {
return path.parse(what);
}
/**
* Parse the path: https://nodejs.org/api/path.html#path_path_parse_path
* @param {string} what
* @returns {object}
*/
parse (what) {
return path.parse(what);
}
/**
* @param {string} what
* @returns {boolean}
*/
isAbsolute(what) {
return path.isAbsolute(what || this.path);
}
/**
* @param {string} what
* @returns {boolean}
*/
isAbsolute (what) {
return path.isAbsolute(what || this.path);
}
/**
* Check if path ends with a directory
* @param {string} what
* @returns {boolean}
*/
isDirectory(what) {
return what.charAt(what.length - 1) === "/";
}
/**
* Check if path ends with a directory
* @param {string} what
* @returns {boolean}
*/
isDirectory (what) {
return (what.charAt(what.length-1) === "/");
}
/**
* Resolve a path against the directory of the Path
*
* https://nodejs.org/api/path.html#path_path_resolve_paths
* @param {string} what
* @returns {string} resolved
*/
resolve(what) {
return path.resolve(this.directory, what);
}
/**
* Resolve a path against the directory of the Path
*
* https://nodejs.org/api/path.html#path_path_resolve_paths
* @param {string} what
* @returns {string} resolved
*/
resolve (what) {
return path.resolve(this.directory, what);
}
/**
* Resolve a path relative to the directory of the Path
*
* https://nodejs.org/api/path.html#path_path_relative_from_to
* @param {string} what
* @returns {string} relative
*/
relative(what) {
var isAbsolute = what && what.indexOf("://") > -1;
/**
* Resolve a path relative to the directory of the Path
*
* https://nodejs.org/api/path.html#path_path_relative_from_to
* @param {string} what
* @returns {string} relative
*/
relative (what) {
var isAbsolute = what && (what.indexOf("://") > -1);
if (isAbsolute) {
return what;
}
if (isAbsolute) {
return what;
}
return path.relative(this.directory, what);
}
return path.relative(this.directory, what);
}
splitPath(filename) {
return this.splitPathRe.exec(filename).slice(1);
}
splitPath(filename) {
return this.splitPathRe.exec(filename).slice(1);
}
/**
* Return the path string
* @returns {string} path
*/
toString () {
return this.path;
}
/**
* Return the path string
* @returns {string} path
*/
toString() {
return this.path;
}
}
export default Path;

View file

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

View file

@ -1,138 +1,131 @@
import { qs, qsa } from "./core";
import { qs } from "./core";
import Url from "./url";
import Path from "./path";
export function replaceBase(doc, section){
var base;
var head;
var url = section.url;
var absolute = (url.indexOf("://") > -1);
export function replaceBase(doc, section) {
var base;
var head;
var url = section.url;
var absolute = url.indexOf("://") > -1;
if(!doc){
return;
}
if (!doc) {
return;
}
head = qs(doc, "head");
base = qs(head, "base");
head = qs(doc, "head");
base = qs(head, "base");
if(!base) {
base = doc.createElement("base");
head.insertBefore(base, head.firstChild);
}
if (!base) {
base = doc.createElement("base");
head.insertBefore(base, head.firstChild);
}
// Fix for Safari crashing if the url doesn't have an origin
if (!absolute && window && window.location) {
url = window.location.origin + url;
}
// Fix for Safari crashing if the url doesn't have an origin
if (!absolute && window && window.location) {
url = window.location.origin + url;
}
base.setAttribute("href", url);
base.setAttribute("href", url);
}
export function replaceCanonical(doc, section){
var head;
var link;
var url = section.canonical;
export function replaceCanonical(doc, section) {
var head;
var link;
var url = section.canonical;
if(!doc){
return;
}
if (!doc) {
return;
}
head = qs(doc, "head");
link = qs(head, "link[rel='canonical']");
head = qs(doc, "head");
link = qs(head, "link[rel='canonical']");
if (link) {
link.setAttribute("href", url);
} else {
link = doc.createElement("link");
link.setAttribute("rel", "canonical");
link.setAttribute("href", url);
head.appendChild(link);
}
if (link) {
link.setAttribute("href", url);
} else {
link = doc.createElement("link");
link.setAttribute("rel", "canonical");
link.setAttribute("href", url);
head.appendChild(link);
}
}
export function replaceMeta(doc, section){
var head;
var meta;
var id = section.idref;
if(!doc){
return;
}
export function replaceMeta(doc, section) {
var head;
var meta;
var id = section.idref;
if (!doc) {
return;
}
head = qs(doc, "head");
meta = qs(head, "link[property='dc.identifier']");
head = qs(doc, "head");
meta = qs(head, "link[property='dc.identifier']");
if (meta) {
meta.setAttribute("content", id);
} else {
meta = doc.createElement("meta");
meta.setAttribute("name", "dc.identifier");
meta.setAttribute("content", id);
head.appendChild(meta);
}
if (meta) {
meta.setAttribute("content", id);
} else {
meta = doc.createElement("meta");
meta.setAttribute("name", "dc.identifier");
meta.setAttribute("content", id);
head.appendChild(meta);
}
}
// TODO: move me to Contents
export function replaceLinks(contents, fn) {
var links = contents.querySelectorAll("a[href]");
var links = contents.querySelectorAll("a[href]");
if (!links.length) {
return;
}
if (!links.length) {
return;
}
var base = qs(contents.ownerDocument, "base");
var location = base ? base.getAttribute("href") : undefined;
var replaceLink = function (link) {
var href = link.getAttribute("href");
var base = qs(contents.ownerDocument, "base");
var location = base ? base.getAttribute("href") : undefined;
var replaceLink = function(link){
var href = link.getAttribute("href");
if (href.indexOf("mailto:") === 0) {
return;
}
if(href.indexOf("mailto:") === 0){
return;
}
var absolute = href.indexOf("://") > -1;
var absolute = (href.indexOf("://") > -1);
if (absolute) {
link.setAttribute("target", "_blank");
} else {
var linkUrl;
try {
linkUrl = new Url(href, location);
} catch (error) {
// NOOP
}
if(absolute){
link.setAttribute("target", "_blank");
}else{
var linkUrl;
try {
linkUrl = new Url(href, location);
} catch(error) {
// NOOP
}
link.onclick = function(){
if(linkUrl && linkUrl.hash) {
fn(linkUrl.Path.path + linkUrl.hash);
} else if(linkUrl){
fn(linkUrl.Path.path);
} else {
fn(href);
}
return false;
};
}
}.bind(this);
for (var i = 0; i < links.length; i++) {
replaceLink(links[i]);
}
link.onclick = function () {
if (linkUrl && linkUrl.hash) {
fn(linkUrl.Path.path + linkUrl.hash);
} else if (linkUrl) {
fn(linkUrl.Path.path);
} else {
fn(href);
}
return false;
};
}
}.bind(this);
for (var i = 0; i < links.length; i++) {
replaceLink(links[i]);
}
}
export function substitute(content, urls, replacements) {
urls.forEach(function(url, i){
if (url && replacements[i]) {
// Account for special characters in the file name.
// See https://stackoverflow.com/a/6318729.
url = url.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&");
content = content.replace(new RegExp(url, "g"), replacements[i]);
}
});
return content;
urls.forEach(function (url, i) {
if (url && replacements[i]) {
// Account for special characters in the file name.
// See https://stackoverflow.com/a/6318729.
url = url.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&");
content = content.replace(new RegExp(url, "g"), replacements[i]);
}
});
return content;
}

View file

@ -1,150 +1,132 @@
import {defer, isXml, parse} from "./core";
import { defer, isXml, parse } from "./core";
import Path from "./path";
function request(url, type, withCredentials, headers) {
var supportsURL = (typeof window != "undefined") ? window.URL : false; // TODO: fallback for url if window isn't defined
var BLOB_RESPONSE = supportsURL ? "blob" : "arraybuffer";
var supportsURL = typeof window != "undefined" ? window.URL : false; // TODO: fallback for url if window isn't defined
var BLOB_RESPONSE = supportsURL ? "blob" : "arraybuffer";
var deferred = new defer();
var deferred = new defer();
var xhr = new XMLHttpRequest();
var xhr = new XMLHttpRequest();
//-- Check from PDF.js:
// https://github.com/mozilla/pdf.js/blob/master/web/compatibility.js
var xhrPrototype = XMLHttpRequest.prototype;
//-- Check from PDF.js:
// https://github.com/mozilla/pdf.js/blob/master/web/compatibility.js
var xhrPrototype = XMLHttpRequest.prototype;
var header;
var header;
if (!("overrideMimeType" in xhrPrototype)) {
// IE10 might have response, but not overrideMimeType
Object.defineProperty(xhrPrototype, "overrideMimeType", {
value: function xmlHttpRequestOverrideMimeType() {}
});
}
if (!("overrideMimeType" in xhrPrototype)) {
// IE10 might have response, but not overrideMimeType
Object.defineProperty(xhrPrototype, "overrideMimeType", {
value: function xmlHttpRequestOverrideMimeType() {},
});
}
if(withCredentials) {
xhr.withCredentials = true;
}
if (withCredentials) {
xhr.withCredentials = true;
}
xhr.onreadystatechange = handler;
xhr.onerror = err;
xhr.onreadystatechange = handler;
xhr.onerror = err;
xhr.open("GET", url, true);
xhr.open("GET", url, true);
for(header in headers) {
xhr.setRequestHeader(header, headers[header]);
}
for (header in headers) {
xhr.setRequestHeader(header, headers[header]);
}
if(type == "json") {
xhr.setRequestHeader("Accept", "application/json");
}
if (type == "json") {
xhr.setRequestHeader("Accept", "application/json");
}
// If type isn"t set, determine it from the file extension
if(!type) {
type = new Path(url).extension;
}
// If type isn"t set, determine it from the file extension
if (!type) {
type = new Path(url).extension;
}
if(type == "blob"){
xhr.responseType = BLOB_RESPONSE;
}
if (type == "blob") {
xhr.responseType = BLOB_RESPONSE;
}
if (isXml(type)) {
xhr.overrideMimeType("text/xml"); // for OPF parsing
}
if(isXml(type)) {
// xhr.responseType = "document";
xhr.overrideMimeType("text/xml"); // for OPF parsing
}
if (type == "binary") {
xhr.responseType = "arraybuffer";
}
if(type == "xhtml") {
// xhr.responseType = "document";
}
xhr.send();
if(type == "html" || type == "htm") {
// xhr.responseType = "document";
}
function err(e) {
deferred.reject(e);
}
if(type == "binary") {
xhr.responseType = "arraybuffer";
}
function handler() {
if (this.readyState === XMLHttpRequest.DONE) {
var responseXML = false;
xhr.send();
if (this.responseType === "" || this.responseType === "document") {
responseXML = this.responseXML;
}
function err(e) {
deferred.reject(e);
}
if (this.status === 200 || this.status === 0 || responseXML) {
//-- Firefox is reporting 0 for blob urls
var r;
function handler() {
if (this.readyState === XMLHttpRequest.DONE) {
var responseXML = false;
if (!this.response && !responseXML) {
deferred.reject({
status: this.status,
message: "Empty Response",
stack: new Error().stack,
});
return deferred.promise;
}
if(this.responseType === "" || this.responseType === "document") {
responseXML = this.responseXML;
}
if (this.status === 403) {
deferred.reject({
status: this.status,
response: this.response,
message: "Forbidden",
stack: new Error().stack,
});
return deferred.promise;
}
if (responseXML) {
r = this.responseXML;
} else if (isXml(type)) {
// xhr.overrideMimeType("text/xml"); // for OPF parsing
// If this.responseXML wasn't set, try to parse using a DOMParser from text
r = parse(this.response, "text/xml");
} else if (type == "xhtml") {
r = parse(this.response, "application/xhtml+xml");
} else if (type == "html" || type == "htm") {
r = parse(this.response, "text/html");
} else if (type == "json") {
r = JSON.parse(this.response);
} else if (type == "blob") {
if (supportsURL) {
r = this.response;
} else {
//-- Safari doesn't support responseType blob, so create a blob from arraybuffer
r = new Blob([this.response]);
}
} else {
r = this.response;
}
if (this.status === 200 || this.status === 0 || responseXML) { //-- Firefox is reporting 0 for blob urls
var r;
deferred.resolve(r);
} else {
deferred.reject({
status: this.status,
message: this.response,
stack: new Error().stack,
});
}
}
}
if (!this.response && !responseXML) {
deferred.reject({
status: this.status,
message : "Empty Response",
stack : new Error().stack
});
return deferred.promise;
}
if (this.status === 403) {
deferred.reject({
status: this.status,
response: this.response,
message : "Forbidden",
stack : new Error().stack
});
return deferred.promise;
}
if(responseXML){
r = this.responseXML;
} else
if(isXml(type)){
// xhr.overrideMimeType("text/xml"); // for OPF parsing
// If this.responseXML wasn't set, try to parse using a DOMParser from text
r = parse(this.response, "text/xml");
}else
if(type == "xhtml"){
r = parse(this.response, "application/xhtml+xml");
}else
if(type == "html" || type == "htm"){
r = parse(this.response, "text/html");
}else
if(type == "json"){
r = JSON.parse(this.response);
}else
if(type == "blob"){
if(supportsURL) {
r = this.response;
} else {
//-- Safari doesn't support responseType blob, so create a blob from arraybuffer
r = new Blob([this.response]);
}
}else{
r = this.response;
}
deferred.resolve(r);
} else {
deferred.reject({
status: this.status,
message : this.response,
stack : new Error().stack
});
}
}
}
return deferred.promise;
return deferred.promise;
}
export default request;

View file

@ -1,55 +1,55 @@
// Detect RTL scroll type
// Based on https://github.com/othree/jquery.rtl-scroll-type/blob/master/src/jquery.rtl-scroll.js
export default function scrollType() {
var type = "reverse";
var definer = createDefiner();
document.body.appendChild(definer);
var type = "reverse";
var definer = createDefiner();
document.body.appendChild(definer);
if (definer.scrollLeft > 0) {
type = "default";
} else {
if (typeof Element !== 'undefined' && Element.prototype.scrollIntoView) {
definer.children[0].children[1].scrollIntoView();
if (definer.scrollLeft < 0) {
type = "negative";
}
} else {
definer.scrollLeft = 1;
if (definer.scrollLeft === 0) {
type = "negative";
}
}
}
if (definer.scrollLeft > 0) {
type = "default";
} else {
if (typeof Element !== "undefined" && Element.prototype.scrollIntoView) {
definer.children[0].children[1].scrollIntoView();
if (definer.scrollLeft < 0) {
type = "negative";
}
} else {
definer.scrollLeft = 1;
if (definer.scrollLeft === 0) {
type = "negative";
}
}
}
document.body.removeChild(definer);
return type;
document.body.removeChild(definer);
return type;
}
export function createDefiner() {
var definer = document.createElement('div');
definer.dir="rtl";
var definer = document.createElement("div");
definer.dir = "rtl";
definer.style.position = "fixed";
definer.style.width = "1px";
definer.style.height = "1px";
definer.style.top = "0px";
definer.style.left = "0px";
definer.style.overflow = "hidden";
definer.style.position = "fixed";
definer.style.width = "1px";
definer.style.height = "1px";
definer.style.top = "0px";
definer.style.left = "0px";
definer.style.overflow = "hidden";
var innerDiv = document.createElement('div');
innerDiv.style.width = "2px";
var innerDiv = document.createElement("div");
innerDiv.style.width = "2px";
var spanA = document.createElement('span');
spanA.style.width = "1px";
spanA.style.display = "inline-block";
var spanA = document.createElement("span");
spanA.style.width = "1px";
spanA.style.display = "inline-block";
var spanB = document.createElement('span');
spanB.style.width = "1px";
spanB.style.display = "inline-block";
var spanB = document.createElement("span");
spanB.style.width = "1px";
spanB.style.display = "inline-block";
innerDiv.appendChild(spanA);
innerDiv.appendChild(spanB);
definer.appendChild(innerDiv);
innerDiv.appendChild(spanA);
innerDiv.appendChild(spanB);
definer.appendChild(innerDiv);
return definer;
return definer;
}

View file

@ -1,5 +1,5 @@
import Path from "./path";
import path from "path-webpack";
import Path from "./path";
/**
* creates a Url object for parsing and manipulation of a url string
@ -8,101 +8,104 @@ import path from "path-webpack";
* default to window.location.href
*/
class Url {
constructor(urlString, baseString) {
var absolute = (urlString.indexOf("://") > -1);
var pathname = urlString;
var basePath;
constructor(urlString, baseString) {
var absolute = urlString.indexOf("://") > -1;
var pathname = urlString;
var basePath;
this.Url = undefined;
this.href = urlString;
this.protocol = "";
this.origin = "";
this.hash = "";
this.hash = "";
this.search = "";
this.base = baseString;
this.Url = undefined;
this.href = urlString;
this.protocol = "";
this.origin = "";
this.hash = "";
this.hash = "";
this.search = "";
this.base = baseString;
if (!absolute &&
baseString !== false &&
typeof(baseString) !== "string" &&
window && window.location) {
this.base = window.location.href;
}
if (
!absolute &&
baseString !== false &&
typeof baseString !== "string" &&
window &&
window.location
) {
this.base = window.location.href;
}
// URL Polyfill doesn't throw an error if base is empty
if (absolute || this.base) {
try {
if (this.base) { // Safari doesn't like an undefined base
this.Url = new URL(urlString, this.base);
} else {
this.Url = new URL(urlString);
}
this.href = this.Url.href;
// URL Polyfill doesn't throw an error if base is empty
if (absolute || this.base) {
try {
if (this.base) {
// Safari doesn't like an undefined base
this.Url = new URL(urlString, this.base);
} else {
this.Url = new URL(urlString);
}
this.href = this.Url.href;
this.protocol = this.Url.protocol;
this.origin = this.Url.origin;
this.hash = this.Url.hash;
this.search = this.Url.search;
this.protocol = this.Url.protocol;
this.origin = this.Url.origin;
this.hash = this.Url.hash;
this.search = this.Url.search;
pathname = this.Url.pathname + (this.Url.search ? this.Url.search : '');
} catch (e) {
// Skip URL parsing
this.Url = undefined;
// resolve the pathname from the base
if (this.base) {
basePath = new Path(this.base);
pathname = basePath.resolve(pathname);
}
}
}
pathname = this.Url.pathname + (this.Url.search ? this.Url.search : "");
} catch (e) {
// Skip URL parsing
this.Url = undefined;
// resolve the pathname from the base
if (this.base) {
basePath = new Path(this.base);
pathname = basePath.resolve(pathname);
}
}
}
this.Path = new Path(pathname);
this.Path = new Path(pathname);
this.directory = this.Path.directory;
this.filename = this.Path.filename;
this.extension = this.Path.extension;
this.directory = this.Path.directory;
this.filename = this.Path.filename;
this.extension = this.Path.extension;
}
}
/**
* @returns {Path}
*/
path() {
return this.Path;
}
/**
* @returns {Path}
*/
path () {
return this.Path;
}
/**
* Resolves a relative path to a absolute url
* @param {string} what
* @returns {string} url
*/
resolve(what) {
var isAbsolute = what.indexOf("://") > -1;
var fullpath;
/**
* Resolves a relative path to a absolute url
* @param {string} what
* @returns {string} url
*/
resolve (what) {
var isAbsolute = (what.indexOf("://") > -1);
var fullpath;
if (isAbsolute) {
return what;
}
if (isAbsolute) {
return what;
}
fullpath = path.resolve(this.directory, what);
return this.origin + fullpath;
}
fullpath = path.resolve(this.directory, what);
return this.origin + fullpath;
}
/**
* Resolve a path relative to the url
* @param {string} what
* @returns {string} path
*/
relative(what) {
return path.relative(what, this.directory);
}
/**
* Resolve a path relative to the url
* @param {string} what
* @returns {string} path
*/
relative (what) {
return path.relative(what, this.directory);
}
/**
* @returns {string}
*/
toString () {
return this.href;
}
/**
* @returns {string}
*/
toString() {
return this.href;
}
}
export default Url;

View file

@ -1,371 +0,0 @@
var domain = window.location.origin;
module('Core');
test("EPUBJS.core.resolveUrl", 1, function() {
var a = "http://example.com/fred/chasen/";
var b = "/chasen/derf.html";
var resolved = EPUBJS.core.resolveUrl(a, b);
equal( resolved, "http://example.com/fred/chasen/derf.html", "resolved" );
});
test("EPUBJS.core.resolveUrl ../", 1, function() {
var a = "http://example.com/fred/chasen/";
var b = "../derf.html";
var resolved = EPUBJS.core.resolveUrl(a, b);
equal( resolved, "http://example.com/fred/derf.html", "resolved" );
});
test("EPUBJS.core.resolveUrl folders", 1, function() {
var a = "/fred/chasen/";
var b = "/fred/chasen/derf.html";
var resolved = EPUBJS.core.resolveUrl(a, b);
equal( resolved, "/fred/chasen/derf.html", "resolved" );
});
test("EPUBJS.core.resolveUrl ../folders", 1, function() {
var a = "/fred/chasen/";
var b = "../../derf.html";
var resolved = EPUBJS.core.resolveUrl(a, b);
equal( resolved, "/derf.html", "resolved" );
});
module('Create');
asyncTest("Create new ePub(/path/to/epub/)", 1, function() {
var book = ePub("../books/moby-dick/");
book.opened.then(function(){
equal( book.url, "../books/moby-dick/OPS/", "book url is passed to new EPUBJS.Book" );
start();
});
});
asyncTest("Create new ePub(/path/to/epub/package.opf)", 1, function() {
var book = ePub("../books/moby-dick/OPS/package.opf");
book.opened.then(function(){
equal( book.url, domain + "/books/moby-dick/OPS/", "bookPath is passed to new EPUBJS.Book" );
start();
});
});
asyncTest("Open using ePub(/path/to/epub/package.opf)", 1, function() {
var book = ePub("../books/moby-dick/OPS/package.opf");
book.opened.then(function(){
equal( book.packageUrl, "../books/moby-dick/OPS/package.opf", "packageUrl is set" );
start();
});
});
asyncTest("Open Remote ePub", 1, function() {
var book = ePub("https://s3.amazonaws.com/moby-dick/");
book.opened.then(function(){
equal( book.packageUrl, "https://s3.amazonaws.com/moby-dick/OPS/package.opf", "packageUrl is set" );
start();
});
});
asyncTest("Open Remote ePub from Package", 1, function() {
var book = ePub("https://s3.amazonaws.com/moby-dick/OPS/package.opf");
book.opened.then(function(){
equal( book.packageUrl, "https://s3.amazonaws.com/moby-dick/OPS/package.opf", "packageUrl is set" );
start();
});
});
asyncTest("Find Epub Package", 1, function() {
var book = ePub("../books/moby-dick/OPS/package.opf");
book.opened.then(function(){
equal( book.packageUrl, "../books/moby-dick/OPS/package.opf", "packageUrl is set" );
start();
});
});
module('Parse');
//TODO: add mocked tests for parser
asyncTest("Manifest", 1, function() {
var book = ePub("../books/moby-dick/OPS/package.opf");
book.opened.then(function(){
equal( Object.keys(book.package.manifest).length, 152, "Manifest is parsed" );
start();
});
});
asyncTest("Metadata", 3, function() {
var book = ePub("../books/moby-dick/OPS/package.opf");
book.opened.then(function(){
equal( book.package.metadata.creator, "Herman Melville", "Creator metadata is parsed" );
equal( book.package.metadata.title, "Moby-Dick", "Title metadata is parsed" );
equal( book.package.metadata.identifier, "code.google.com.epub-samples.moby-dick-basic", "Identifier metadata is parsed" );
start();
});
});
asyncTest("Spine", 1, function() {
var book = ePub("../books/moby-dick/OPS/package.opf");
book.opened.then(function(){
equal( book.package.spine.length, 144, "Spine is parsed" );
start();
});
});
asyncTest("Cover", 1, function() {
var book = ePub("../books/moby-dick/");
book.opened.then(function(){
equal( book.cover, "../books/moby-dick/OPS/images/9780316000000.jpg", "Cover is set" );
start();
});
});
module('Spine');
asyncTest("Length", 1, function() {
var book = ePub("../books/moby-dick/OPS/package.opf");
book.opened.then(function(){
equal( book.spine.length, 144, "All spine items present" );
start();
});
});
asyncTest("Items", 1, function() {
var book = ePub("../books/moby-dick/OPS/package.opf");
book.opened.then(function(){
equal( book.spine.spineItems.length, 144, "All spine items added" );
start();
});
});
asyncTest("First Item", 2, function() {
var book = ePub("../books/moby-dick/OPS/package.opf");
book.opened.then(function(){
var section = book.spine.get(0);
equal( section.href, "cover.xhtml", "First spine item href found" );
equal( section.cfiBase, "/6/2[cover]", "First spine item cfi found" );
start();
});
});
asyncTest("Find Item by Href", 2, function() {
var book = ePub("../books/moby-dick/OPS/package.opf");
book.opened.then(function(){
var section = book.spine.get("chapter_001.xhtml");
equal( section.href, "chapter_001.xhtml", "chap 1 spine item href found" );
equal( section.cfiBase, "/6/14[xchapter_001]", "chap 1 spine item cfi found" );
start();
});
});
asyncTest("Find Item by ID", 2, function() {
var book = ePub("../books/moby-dick/OPS/package.opf");
book.opened.then(function(){
var section = book.spine.get("#xchapter_050");
equal( section.href, "chapter_050.xhtml", "chap 50 spine item href found" );
equal( section.cfiBase, "/6/112[xchapter_050]", "chap 50 spine item cfi found" );
start();
});
});
asyncTest("Render Spine Item", 1, function() {
var book = ePub("../books/moby-dick/OPS/package.opf");
book.opened.then(function(){
var section = book.spine.get("#xchapter_050");
section.render().then(function(content){
equal( content.substring(377, 429), "<h1>Chapter 50. Ahabs Boat and Crew. Fedallah.</h1>", "Chapter text rendered as string" );
});
start();
});
});
module('Navigation');
asyncTest("NCX & Nav", 2, function() {
var book = ePub("../books/moby-dick/");
book.opened.then(function(){
equal( book.navigation.navUrl, "../books/moby-dick/OPS/toc.xhtml", "Nav URL found" );
equal( book.navigation.ncxUrl, "../books/moby-dick/OPS/toc.ncx", "NCX URL found" );
start();
});
});
asyncTest("Load TOC Auto Pick", 1, function() {
var book = ePub("../books/moby-dick/");
book.opened.then(function(){
book.navigation.load().then(function(toc){
equal( toc.length, 141, "Full Nav toc parsed" );
start();
});
});
});
asyncTest("Load TOC from Nav", 1, function() {
var book = ePub("../books/moby-dick/OPS/package.opf");
book.opened.then(function(){
var nav = book.navigation.nav.load();
nav.then(function(toc){
equal( toc.length, 141, "Full Nav toc parsed" );
start();
});
});
});
asyncTest("Load TOC from NCX", 1, function() {
var book = ePub("../books/moby-dick/OPS/package.opf");
book.opened.then(function(){
var ncx = book.navigation.ncx.load();
ncx.then(function(toc){
equal( toc.length, 14, "Full NCX toc parsed" );
start();
});
});
});
asyncTest("Get all TOC", 1, function() {
var book = ePub("../books/moby-dick/OPS/package.opf");
book.loaded.navigation.then(function(){
equal( book.navigation.get().length, 141, "Full Nav toc parsed" );
start();
});
});
asyncTest("Get TOC item by href", 1, function() {
var book = ePub("../books/moby-dick/OPS/package.opf");
book.loaded.navigation.then(function(){
var item = book.navigation.get("chapter_001.xhtml");
equal( item.id, "toc-chapter_001", "Found TOC item" );
start();
});
});
asyncTest("Get TOC item by ID", 1, function() {
var book = ePub("../books/moby-dick/OPS/package.opf");
book.loaded.navigation.then(function(){
var item = book.navigation.get("#toc-chapter_001");
equal( item.href, "chapter_001.xhtml", "Found TOC item" );
start();
});
});
module('Hooks');
asyncTest("Register a new hook", 1, function() {
var beforeDisplay = new EPUBJS.Hook();
beforeDisplay.register(function(args){
var defer = new RSVP.defer();
console.log("ran", 1);
defer.resolve();
return defer.promise;
});
equal( beforeDisplay.hooks.length, 1, "Registered a hook" );
start();
// this.beforeDisplay.trigger(args).then(function(){});
});
asyncTest("Trigger all new hook", 4, function() {
var beforeDisplay = new EPUBJS.Hook(this);
this.testerObject = {tester: 1};
beforeDisplay.register(function(testerObject){
var defer = new RSVP.defer();
start();
equal( testerObject.tester, 1, "tester is 1" );
stop();
testerObject.tester += 1;
defer.resolve();
return defer.promise;
});
beforeDisplay.register(function(testerObject){
var defer = new RSVP.defer();
start();
equal(testerObject.tester, 2, "tester is 2" );
stop();
testerObject.tester += 1;
defer.resolve();
return defer.promise;
});
start();
equal( beforeDisplay.hooks.length, 2, "Added two hooks" );
stop();
beforeDisplay.trigger(this.testerObject).then(function(){
start();
equal( this.testerObject.tester, 3, "tester is 3" );
}.bind(this));
});

View file

@ -1,16 +0,0 @@
module('Rendering');
/*
asyncTest("Render To", 1, function() {
var book = ePub("../books/moby-dick/OPS/package.opf");
var rendition = book.renderTo("qunit-fixture", {width:400, height:600});
var displayed = rendition.display(0);
displayed.then(function(){
equal( $( "iframe", "#qunit-fixture" ).length, 1, "iframe added successfully" );
start();
});
});
*/

View file

@ -1,53 +1,60 @@
import Rendition from "./rendition";
import View from "./managers/view";
import Rendition from "./rendition";
export default class Annotations {
constructor(rendition: Rendition);
add(type: string, cfiRange: string, data?: object, cb?: Function, className?: string, styles?: object): Annotation;
add(
type: string,
cfiRange: string,
data?: object,
cb?: Function,
className?: string,
styles?: object
): Annotation;
remove(cfiRange: string, type: string): void;
highlight(
cfiRange: string,
data?: object,
cb?: Function,
className?: string,
styles?: object
): void;
underline(
cfiRange: string,
data?: object,
cb?: Function,
className?: string,
styles?: object
): void;
mark(cfiRange: string, data?: object, cb?: Function): void;
each(): Array<Annotation>;
highlight(cfiRange: string, data?: object, cb?: Function, className?: string, styles?: object): void;
underline(cfiRange: string, data?: object, cb?: Function, className?: string, styles?: object): void;
mark(cfiRange: string, data?: object, cb?: Function): void;
each(): Array<Annotation>
private _removeFromAnnotationBySectionIndex(sectionIndex: number, hash: string): void;
private _removeFromAnnotationBySectionIndex(
sectionIndex: number,
hash: string
): void;
private _annotationsAt(index: number): void;
private inject(view: View): void;
private clear(view: View): void;
}
declare class Annotation {
constructor(options: {
type: string,
cfiRange: string,
data?: object,
sectionIndex?: number,
cb?: Function,
className?: string,
styles?: object
});
type: string;
cfiRange: string;
data?: object;
sectionIndex?: number;
cb?: Function;
className?: string;
styles?: object;
});
update(data: object): void;
attach(view: View): any;
detach(view: View): any;
// Event emitters
emit(type: any, ...args: any[]): void;
off(type: any, listener: any): any;
on(type: any, listener: any): any;
once(type: any, listener: any, ...args: any[]): any;
}

20
types/archive.d.ts vendored
View file

@ -1,27 +1,25 @@
import JSZip = require('jszip');
import JSZip = require("jszip");
export default class Archive {
constructor();
open(input: BinaryType, isBase64?: boolean): Promise<JSZip>;
openUrl(zipUrl: string, isBase64?: boolean): Promise<JSZip>;
request(url: string, type?: string): Promise<Blob | string | JSON | Document | XMLDocument>;
request(
url: string,
type?: string
): Promise<Blob | string | JSON | Document | XMLDocument>;
getBlob(url: string, mimeType?: string): Promise<Blob>;
getText(url: string): Promise<string>;
getBase64(url: string, mimeType?: string): Promise<string>;
createUrl(url: string, options: { base64: boolean }): Promise<string>;
revokeUrl(url: string): void;
destroy(): void;
private checkRequirements(): void;
private handleResponse(response: any, type?: string): Blob | string | JSON | Document | XMLDocument;
private handleResponse(
response: any,
type?: string
): Blob | string | JSON | Document | XMLDocument;
}

190
types/book.d.ts vendored
View file

@ -1,122 +1,98 @@
import {
import Archive from "./archive";
import Container from "./container";
import Locations from "./locations";
import Navigation from "./navigation";
import Packaging, {
PackagingManifestObject,
PackagingMetadataObject,
PackagingSpineItem,
PackagingObject
} from "./packaging";
import PageList, { PageListItem } from "./pagelist";
import Rendition, { RenditionOptions } from "./rendition";
import Section, { SpineItem } from "./section";
import Archive from "./archive";
import Navigation from "./navigation";
import PageList, {PageListItem} from "./pagelist";
import Spine from "./spine";
import Locations from "./locations";
import Url from "./utils/url";
import Path from "./utils/path";
import Resources from "./resources";
import Container from "./container";
import Packaging from "./packaging";
import Section, { SpineItem } from "./section";
import Spine from "./spine";
import Store from "./store";
import Path from "./utils/path";
import Url from "./utils/url";
export interface BookOptions {
requestMethod?: (url: string, type: string, withCredentials: object, headers: object) => Promise<object>;
requestCredentials?: object,
requestHeaders?: object,
encoding?: string,
replacements?: string,
canonical?: (path: string) => string,
openAs?: string,
store?: string
requestMethod?: (
url: string,
type: string,
withCredentials: object,
headers: object
) => Promise<object>;
requestCredentials?: object;
requestHeaders?: object;
encoding?: string;
replacements?: string;
canonical?: (path: string) => string;
openAs?: string;
store?: string;
}
export default class Book {
constructor(url: string, options?: BookOptions);
constructor(options?: BookOptions);
constructor(url: string, options?: BookOptions);
constructor(options?: BookOptions);
settings: BookOptions;
opening: any; // should be core.defer
opened: Promise<Book>;
isOpen: boolean;
loaded: {
metadata: Promise<PackagingMetadataObject>,
spine: Promise<SpineItem[]>,
manifest: Promise<PackagingManifestObject>,
cover: Promise<string>,
navigation: Promise<Navigation>,
pageList: Promise<PageListItem[]>,
resources: Promise<string[]>,
}
ready: Promise<void>;
request: Function;
spine: Spine;
locations: Locations;
navigation: Navigation;
pageList: PageList;
url: Url;
path: Path;
archived: boolean;
archive: Archive;
resources: Resources;
rendition: Rendition
container: Container;
packaging: Packaging;
storage: Store;
settings: BookOptions;
opening: any; // should be core.defer
opened: Promise<Book>;
isOpen: boolean;
loaded: {
metadata: Promise<PackagingMetadataObject>;
spine: Promise<SpineItem[]>;
manifest: Promise<PackagingManifestObject>;
cover: Promise<string>;
navigation: Promise<Navigation>;
pageList: Promise<PageListItem[]>;
resources: Promise<string[]>;
};
ready: Promise<void>;
request: Function;
spine: Spine;
locations: Locations;
navigation: Navigation;
pageList: PageList;
url: Url;
path: Path;
archived: boolean;
archive: Archive;
resources: Resources;
rendition: Rendition;
container: Container;
packaging: Packaging;
storage: Store;
canonical(path: string): string;
coverUrl(): Promise<string | null>;
destroy(): void;
determineType(input: string): string;
getRange(cfiRange: string): Promise<Range>;
key(identifier?: string): string;
load(path: string): Promise<object>;
loadNavigation(opf: XMLDocument): Promise<Navigation>;
open(input: string, what?: string): Promise<object>;
open(input: ArrayBuffer, what?: string): Promise<object>;
openContainer(url: string): Promise<string>;
openEpub(data: BinaryType, encoding?: string): Promise<Book>;
openManifest(url: string): Promise<Book>;
openPackaging(url: string): Promise<Book>;
renderTo(element: Element, options?: RenditionOptions): Rendition;
renderTo(element: string, options?: RenditionOptions): Rendition;
resolve(path: string, absolute?: boolean): string;
section(target: string): Section;
section(target: number): Section;
setRequestCredentials(credentials: object): void;
setRequestHeaders(headers: object): void;
unarchive(input: BinaryType, encoding?: string): Promise<Archive>;
store(name: string): Store;
unpack(opf: XMLDocument): Promise<Book>;
canonical(path: string): string;
coverUrl(): Promise<string | null>;
destroy(): void;
determineType(input: string): string;
getRange(cfiRange: string): Promise<Range>;
key(identifier?: string): string;
load(path: string): Promise<object>;
loadNavigation(opf: XMLDocument): Promise<Navigation>;
open(input: string, what?: string): Promise<object>;
open(input: ArrayBuffer, what?: string): Promise<object>;
openContainer(url: string): Promise<string>;
openEpub(data: BinaryType, encoding?: string): Promise<Book>;
openManifest(url: string): Promise<Book>;
openPackaging(url: string): Promise<Book>;
renderTo(element: Element, options?: RenditionOptions): Rendition;
renderTo(element: string, options?: RenditionOptions): Rendition;
private replacements(): Promise<void>;
resolve(path: string, absolute?: boolean): string;
section(target: string): Section;
section(target: number): Section;
setRequestCredentials(credentials: object): void;
setRequestHeaders(headers: object): void;
unarchive(input: BinaryType, encoding?: string): Promise<Archive>;
store(name: string): Store;
unpack(opf: XMLDocument): Promise<Book>;
// Event emitters
emit(type: any, ...args: any[]): void;
off(type: any, listener: any): any;
on(type: any, listener: any): any;
once(type: any, listener: any, ...args: any[]): any;
private replacements(): Promise<void>;
// Event emitters
emit(type: any, ...args: any[]): void;
off(type: any, listener: any): any;
on(type: any, listener: any): any;
once(type: any, listener: any, ...args: any[]): any;
}

View file

@ -2,6 +2,5 @@ export default class Container {
constructor(containerDocument: Document);
parse(containerDocument: Document): void;
destroy(): void;
}

224
types/contents.d.ts vendored
View file

@ -1,139 +1,107 @@
import EpubCFI from "./epubcfi";
export interface ViewportSettings {
width: string,
height: string,
scale: string,
scalable: string,
minimum: string,
maximum: string
width: string;
height: string;
scale: string;
scalable: string;
minimum: string;
maximum: string;
}
export default class Contents {
constructor(doc: Document, content: Element, cfiBase: string, sectionIndex: number);
constructor(
doc: Document,
content: Element,
cfiBase: string,
sectionIndex: number
);
epubcfi: EpubCFI;
document: Document;
documentElement: Element;
content: Element;
window: Window;
sectionIndex: number;
cfiBase: string;
epubcfi: EpubCFI;
document: Document;
documentElement: Element;
content: Element;
window: Window;
sectionIndex: number;
cfiBase: string;
static listenedEvents: string[];
static listenedEvents: string[];
addClass(className: string): void;
addClass(className: string): void;
addScript(src: string): Promise<boolean>;
addStylesheet(src: string): Promise<boolean>;
addStylesheetRules(
rules: Array<object> | object,
key: string
): Promise<boolean>;
addStylesheetCss(serializedCss: string, key: string): Promise<boolean>;
cfiFromNode(node: Node, ignoreClass?: string): string;
cfiFromRange(range: Range, ignoreClass?: string): string;
columns(
width: number,
height: number,
columnWidth: number,
gap: number,
dir: string
): void;
contentHeight(h: number): number;
contentWidth(w: number): number;
css(property: string, value: string, priority?: boolean): string;
destroy(): void;
direction(dir: string): void;
fit(width: number, height: number): void;
height(h: number): number;
locationOf(
target: string | EpubCFI,
ignoreClass?: string
): Promise<{ top: number; left: number }>;
map(layout: any): any;
mapPage(
cfiBase: string,
layout: object,
start: number,
end: number,
dev: boolean
): any;
overflow(overflow: string): string;
overflowX(overflow: string): string;
overflowY(overflow: string): string;
range(cfi: string, ignoreClass?: string): Range;
removeClass(className: any): void;
root(): Element;
scaler(scale: number, offsetX: number, offsetY: number): void;
scrollHeight(): number;
scrollWidth(): number;
size(width: number, height: number): void;
textHeight(): number;
textWidth(): number;
viewport(options: ViewportSettings): ViewportSettings;
width(w: number): number;
writingMode(mode: string): string;
// Event emitters
emit(type: any, ...args: any[]): void;
off(type: any, listener: any): any;
on(type: any, listener: any): any;
once(type: any, listener: any, ...args: any[]): any;
addScript(src: string): Promise<boolean>;
addStylesheet(src: string): Promise<boolean>;
addStylesheetRules(rules: Array<object> | object, key: string): Promise<boolean>;
addStylesheetCss(serializedCss: string, key: string): Promise<boolean>;
cfiFromNode(node: Node, ignoreClass?: string): string;
cfiFromRange(range: Range, ignoreClass?: string): string;
columns(width: number, height: number, columnWidth: number, gap: number, dir: string): void;
contentHeight(h: number): number;
contentWidth(w: number): number;
css(property: string, value: string, priority?: boolean): string;
destroy(): void;
direction(dir: string): void;
fit(width: number, height: number): void;
height(h: number): number;
locationOf(target: string | EpubCFI, ignoreClass?: string): Promise<{ top: number, left: number }>;
map(layout: any): any;
mapPage(cfiBase: string, layout: object, start: number, end: number, dev: boolean): any;
overflow(overflow: string): string;
overflowX(overflow: string): string;
overflowY(overflow: string): string;
range(cfi: string, ignoreClass?: string): Range;
removeClass(className: any): void;
root(): Element;
scaler(scale: number, offsetX: number, offsetY: number): void;
scrollHeight(): number;
scrollWidth(): number;
size(width: number, height: number): void;
textHeight(): number;
textWidth(): number;
viewport(options: ViewportSettings): ViewportSettings;
width(w: number): number;
writingMode(mode: string): string;
// Event emitters
emit(type: any, ...args: any[]): void;
off(type: any, listener: any): any;
on(type: any, listener: any): any;
once(type: any, listener: any, ...args: any[]): any;
private addEventListeners(): void;
private addSelectionListeners(): void;
private epubReadingSystem(name: string, version: string): object;
private expand(): void;
private fontLoadListeners(): void;
private imageLoadListeners(): void;
private layoutStyle(style: string): string;
private linksHandler(): void;
private listeners(): void;
private mediaQueryListeners(): void;
private onSelectionChange(e: Event): void;
private removeEventListeners(): void;
private removeListeners(): void;
private removeSelectionListeners(): void;
private resizeCheck(): void;
private resizeListeners(): void;
private resizeObservers(): void;
private transitionListeners(): void;
private triggerEvent(e: Event): void;
private triggerSelectedEvent(selection: Selection): void;
private addEventListeners(): void;
private addSelectionListeners(): void;
private epubReadingSystem(name: string, version: string): object;
private expand(): void;
private fontLoadListeners(): void;
private imageLoadListeners(): void;
private layoutStyle(style: string): string;
private linksHandler(): void;
private listeners(): void;
private mediaQueryListeners(): void;
private onSelectionChange(e: Event): void;
private removeEventListeners(): void;
private removeListeners(): void;
private removeSelectionListeners(): void;
private resizeCheck(): void;
private resizeListeners(): void;
private resizeObservers(): void;
private transitionListeners(): void;
private triggerEvent(e: Event): void;
private triggerSelectedEvent(selection: Selection): void;
}

116
types/core.d.ts vendored
View file

@ -1,83 +1,87 @@
export module Core {
export function uuid(): string;
export function documentHeight(): number;
export function isElement(obj: object): boolean;
export function isNumber(n: any): boolean;
export function isFloat(n: any): boolean;
export function prefixed(unprefixed: string): string;
export function defaults(obj: object): object;
export function extend(target: object): object;
export function insert(item: any, array: Array<any>, compareFunction: Function): number;
export function locationOf(item: any, array: Array<any>, compareFunction: Function, _start: Function, _end: Function): number;
export function indexOfSorted(item: any, array: Array<any>, compareFunction: Function, _start: Function, _end: Function): number;
export function bounds(el: Element): { width: Number, height: Number};
export function borders(el: Element): { width: Number, height: Number};
export function insert(
item: any,
array: Array<any>,
compareFunction: Function
): number;
export function locationOf(
item: any,
array: Array<any>,
compareFunction: Function,
_start: Function,
_end: Function
): number;
export function indexOfSorted(
item: any,
array: Array<any>,
compareFunction: Function,
_start: Function,
_end: Function
): number;
export function bounds(el: Element): { width: Number; height: Number };
export function borders(el: Element): { width: Number; height: Number };
export function nodeBounds(node: Node): object;
export function windowBounds(): { width: Number, height: Number, top: Number, left: Number, right: Number, bottom: Number };
export function windowBounds(): {
width: Number;
height: Number;
top: Number;
left: Number;
right: Number;
bottom: Number;
};
export function indexOfNode(node: Node, typeId: string): number;
export function indexOfTextNode(textNode: Node): number;
export function indexOfElementNode(elementNode: Element): number;
export function isXml(ext: string): boolean;
export function createBlob(content: any, mime: string): Blob;
export function createBlobUrl(content: any, mime: string): string;
export function revokeBlobUrl(url: string): void;
export function createBase64Url(content: any, mime: string): string
export function createBase64Url(content: any, mime: string): string;
export function type(obj: object): string;
export function parse(markup: string, mime: string, forceXMLDom: boolean): Document;
export function parse(
markup: string,
mime: string,
forceXMLDom: boolean
): Document;
export function qs(el: Element, sel: string): Element;
export function qsa(el: Element, sel: string): ArrayLike<Element>;
export function qsp(el: Element, sel: string, props: Array<object>): ArrayLike<Element>;
export function qsp(
el: Element,
sel: string,
props: Array<object>
): ArrayLike<Element>;
export function sprint(root: Node, func: Function): void;
export function treeWalker(root: Node, func: Function, filter: object | Function): void;
export function treeWalker(
root: Node,
func: Function,
filter: object | Function
): void;
export function walk(node: Node, callback: Function): void;
export function blob2base64(blob: Blob): string;
export function defer(): Promise<any>;
export function querySelectorByType(html: Element, element: string, type: string): Array<Element>;
export function querySelectorByType(
html: Element,
element: string,
type: string
): Array<Element>;
export function findChildren(el: Element): Array<Element>;
export function parents(node: Element): Array<Element>;
export function filterChildren(el: Element, nodeName: string, single: boolean): Array<Element>;
export function getParentByTagName(node: Element, tagname: string): Array<Element>;
export class RangeObject extends Range {
}
export function filterChildren(
el: Element,
nodeName: string,
single: boolean
): Array<Element>;
export function getParentByTagName(
node: Element,
tagname: string
): Array<Element>;
export class RangeObject extends Range {}
}

7
types/epub.d.ts vendored
View file

@ -2,5 +2,8 @@ import Book, { BookOptions } from "./book";
export default Epub;
declare function Epub(urlOrData: string | ArrayBuffer, options?: BookOptions) : Book;
declare function Epub(options?: BookOptions) : Book;
declare function Epub(
urlOrData: string | ArrayBuffer,
options?: BookOptions
): Book;
declare function Epub(options?: BookOptions): Book;

163
types/epubcfi.d.ts vendored
View file

@ -1,97 +1,98 @@
interface EpubCFISegment {
steps: Array<object>,
steps: Array<object>;
terminal: {
offset: number,
assertion: string
}
offset: number;
assertion: string;
};
}
interface EpubCFIStep {
id: string,
tagName: string,
type: number,
index: number
id: string;
tagName: string;
type: number;
index: number;
}
interface EpubCFIComponent {
steps: Array<EpubCFIStep>,
steps: Array<EpubCFIStep>;
terminal: {
offset: number,
assertion: string
}
offset: number;
assertion: string;
};
}
export default class EpubCFI {
constructor(cfiFrom?: string | Range | Node, base?: string | object, ignoreClass?: string);
constructor(
cfiFrom?: string | Range | Node,
base?: string | object,
ignoreClass?: string
);
base: EpubCFIComponent;
spinePos: number;
range: boolean;
base: EpubCFIComponent;
spinePos: number;
range: boolean;
isCfiString(str: string): boolean;
fromNode(anchor: Node, base: string | object, ignoreClass?: string): EpubCFI;
fromRange(range: Range, base: string | object, ignoreClass?: string): EpubCFI;
parse(cfiStr: string): EpubCFI;
collapse(toStart?: boolean): void;
compare(cfiOne: string | EpubCFI, cfiTwo: string | EpubCFI): number;
equalStep(stepA: object, stepB: object): boolean;
filter(anchor: Element, ignoreClass?: string): Element | false;
toRange(_doc?: Document, ignoreClass?: string): Range;
toString(): string;
private filteredStep(node: Node, ignoreClass?: string): any;
private findNode(steps: Array<EpubCFIStep>, _doc?: Document, ignoreClass?: string): Node;
private fixMiss(steps: Array<EpubCFIStep>, offset: number, _doc?: Document, ignoreClass?: string): any;
private checkType(cfi: string | Range | Node): string | false;
private generateChapterComponent(_spineNodeIndex: number, _pos: number, id: string): string;
private getChapterComponent(cfiStr: string): string;
private getCharecterOffsetComponent(cfiStr: string): string;
private getPathComponent(cfiStr: string): string;
private getRange(cfiStr: string): string;
private joinSteps(steps: Array<EpubCFIStep>): Array<EpubCFIStep>;
private normalizedMap(children: Array<Node>, nodeType: number, ignoreClass?: string): object;
private parseComponent(componentStr: string): object;
private parseStep(stepStr: string): object;
private parseTerminal(termialStr: string): object;
private patchOffset(anchor: Node, offset: number, ignoreClass?: string): number;
private pathTo(node: Node, offset: number, ignoreClass?: string): EpubCFISegment;
private position(anchor: Node): number;
private segmentString(segment: EpubCFISegment): string;
private step(node: Node): EpubCFIStep;
private stepsToQuerySelector(steps: Array<EpubCFIStep>): string;
private stepsToXpath(steps: Array<EpubCFIStep>): string;
private textNodes(container: Node, ignoreClass?: string): Array<Node>;
private walkToNode(steps: Array<EpubCFIStep>, _doc?: Document, ignoreClass?: string): Node;
isCfiString(str: string): boolean;
fromNode(anchor: Node, base: string | object, ignoreClass?: string): EpubCFI;
fromRange(range: Range, base: string | object, ignoreClass?: string): EpubCFI;
parse(cfiStr: string): EpubCFI;
collapse(toStart?: boolean): void;
compare(cfiOne: string | EpubCFI, cfiTwo: string | EpubCFI): number;
equalStep(stepA: object, stepB: object): boolean;
filter(anchor: Element, ignoreClass?: string): Element | false;
toRange(_doc?: Document, ignoreClass?: string): Range;
toString(): string;
private filteredStep(node: Node, ignoreClass?: string): any;
private findNode(
steps: Array<EpubCFIStep>,
_doc?: Document,
ignoreClass?: string
): Node;
private fixMiss(
steps: Array<EpubCFIStep>,
offset: number,
_doc?: Document,
ignoreClass?: string
): any;
private checkType(cfi: string | Range | Node): string | false;
private generateChapterComponent(
_spineNodeIndex: number,
_pos: number,
id: string
): string;
private getChapterComponent(cfiStr: string): string;
private getCharecterOffsetComponent(cfiStr: string): string;
private getPathComponent(cfiStr: string): string;
private getRange(cfiStr: string): string;
private joinSteps(steps: Array<EpubCFIStep>): Array<EpubCFIStep>;
private normalizedMap(
children: Array<Node>,
nodeType: number,
ignoreClass?: string
): object;
private parseComponent(componentStr: string): object;
private parseStep(stepStr: string): object;
private parseTerminal(termialStr: string): object;
private patchOffset(
anchor: Node,
offset: number,
ignoreClass?: string
): number;
private pathTo(
node: Node,
offset: number,
ignoreClass?: string
): EpubCFISegment;
private position(anchor: Node): number;
private segmentString(segment: EpubCFISegment): string;
private step(node: Node): EpubCFIStep;
private stepsToQuerySelector(steps: Array<EpubCFIStep>): string;
private stepsToXpath(steps: Array<EpubCFIStep>): string;
private textNodes(container: Node, ignoreClass?: string): Array<Node>;
private walkToNode(
steps: Array<EpubCFIStep>,
_doc?: Document,
ignoreClass?: string
): Node;
}

View file

@ -1,9 +0,0 @@
import ePub, { Book } from '../';
function testEpub() {
const epub = ePub("https://s3.amazonaws.com/moby-dick/moby-dick.epub");
const book = new Book("https://s3.amazonaws.com/moby-dick/moby-dick.epub", {});
}
testEpub();

16
types/index.d.ts vendored
View file

@ -8,13 +8,9 @@ export as namespace ePub;
export default Epub;
export { default as Book } from './book';
export { default as EpubCFI } from './epubcfi';
export { default as Rendition, Location } from './rendition';
export { default as Contents } from './contents';
export { default as Layout } from './layout';
export { NavItem } from './navigation';
declare namespace ePub {
}
export { default as Book } from "./book";
export { default as Contents } from "./contents";
export { default as EpubCFI } from "./epubcfi";
export { default as Layout } from "./layout";
export { NavItem } from "./navigation";
export { Location, default as Rendition } from "./rendition";

43
types/layout.d.ts vendored
View file

@ -1,10 +1,10 @@
import Contents from "./contents";
interface LayoutSettings {
layout: string,
spread: string,
minSpreadWidth: number,
evenSpreads: boolean
layout: string;
spread: string;
minSpreadWidth: number;
evenSpreads: boolean;
}
export default class Layout {
@ -13,35 +13,30 @@ export default class Layout {
settings: LayoutSettings;
name: string;
props: {
name: string,
spread: string,
flow: string,
width: number,
height: number,
spreadWidth: number,
delta: number,
columnWidth: number,
gap: number,
divisor: number
name: string;
spread: string;
flow: string;
width: number;
height: number;
spreadWidth: number;
delta: number;
columnWidth: number;
gap: number;
divisor: number;
};
flow(flow: string): string;
spread(spread: string, min: number): boolean;
calculate(_width:number, _height:number, _gap?:number): void;
calculate(_width: number, _height: number, _gap?: number): void;
format(contents: Contents): void | Promise<void>;
count(totalLength: number, pageLength: number): {spreads: Number, pages: Number};
count(
totalLength: number,
pageLength: number
): { spreads: Number; pages: Number };
// Event emitters
emit(type: any, ...args: any[]): void;
off(type: any, listener: any): any;
on(type: any, listener: any): any;
once(type: any, listener: any, ...args: any[]): any;
private update(props: object): void;

26
types/locations.d.ts vendored
View file

@ -1,41 +1,29 @@
import Spine from "./spine";
import Section from "./section";
import EpubCFI from "./epubcfi";
import Section from "./section";
import Spine from "./spine";
export default class Locations {
constructor(spine: Spine, request?: Function, pause?: number);
generate(chars: number): Promise<Array<string>>;
process(section: Section): Promise<Array<string>>;
locationFromCfi(cfi: string | EpubCFI): Location;
percentageFromCfi(cfi: string | EpubCFI): number;
percentageFromLocation(loc: number): number;
cfiFromLocation(loc: number): string;
cfiFromPercentage(percentage: number): string;
load(locations: string): Array<string>;
save(): string;
currentLocation(): Location;
currentLocation(curr: string | number): void;
length(): number;
destroy(): void;
private createRange(): {
startContainer: Element,
startOffset: number,
endContainer: Element,
endOffset: number
startContainer: Element;
startOffset: number;
endContainer: Element;
endOffset: number;
};
private parse(contents: Node, cfiBase: string, chars: number) : Array<string>;
private parse(contents: Node, cfiBase: string, chars: number): Array<string>;
}

View file

@ -1,90 +1,59 @@
import Section from "../section";
import Layout from "../layout";
import Contents from "../contents";
import View, { ViewSettings } from "./view";
import Layout from "../layout";
import { EpubCFIPair } from "../mapping";
import Section from "../section";
import View, { ViewSettings } from "./view";
export interface ViewLocation {
index: number,
href: string,
pages: number[],
totalPages: number,
mapping: EpubCFIPair
index: number;
href: string;
pages: number[];
totalPages: number;
mapping: EpubCFIPair;
}
export interface ManagerOptions extends ViewSettings {
infinite?: boolean,
overflow?: string,
[key: string]: any
infinite?: boolean;
overflow?: string;
[key: string]: any;
}
export default class Manager {
constructor(options: object);
render(element: Element, size?: { width: Number, height: Number }): void;
resize(width: Number, height: Number): void;
onOrientationChange(e: Event): void;
private createView(section: Section): View;
display(section: Section, target: string | number): Promise<void>;
private afterDisplayed(view: View): void;
private afterResized(view: View): void;
private moveTo(offset: {top: Number, left: Number}): void;
private append(section: Section): Promise<void>;
private prepend(section: Section): Promise<void>;
next(): Promise<void>;
prev(): Promise<void>;
current(): View;
applyLayout(layout: Layout): void;
bounds(): object;
clear(): void;
current(): View;
currentLocation(): ViewLocation[];
destroy(): void;
direction(dir: string): void;
display(section: Section, target: string | number): Promise<void>;
getContents(): Contents[];
isRendered(): boolean;
next(): Promise<void>;
onOrientationChange(e: Event): void;
prev(): Promise<void>;
render(element: Element, size?: { width: Number; height: Number }): void;
resize(width: Number, height: Number): void;
setLayout(layout: Layout): void;
updateAxis(axis: string, forceUpdate: boolean): void;
updateFlow(flow: string): void;
updateLayout(): void;
visible(): View[];
private createView(section: Section): View;
private afterDisplayed(view: View): void;
private afterResized(view: View): void;
private moveTo(offset: { top: Number; left: Number }): void;
private append(section: Section): Promise<void>;
private prepend(section: Section): Promise<void>;
private scrollBy(x: number, y: number, silent: boolean): void;
private scrollTo(x: number, y: number, silent: boolean): void;
private onScroll(): void;
bounds(): object;
applyLayout(layout: Layout): void;
updateLayout(): void;
setLayout(layout: Layout): void;
updateAxis(axis: string, forceUpdate: boolean): void;
updateFlow(flow: string): void;
getContents(): Contents[];
direction(dir: string): void;
isRendered(): boolean;
destroy(): void;
// Event emitters
emit(type: any, ...args: any[]): void;
off(type: any, listener: any): any;
on(type: any, listener: any): any;
once(type: any, listener: any, ...args: any[]): any;
}

View file

@ -1,80 +1,65 @@
import Section from "../section";
import Contents from "../contents";
import Layout from "../layout";
import Section from "../section";
export interface ViewSettings {
ignoreClass?: string,
axis?: string,
flow?: string,
layout?: Layout,
method?: string,
width?: number,
height?: number,
forceEvenPages?: boolean,
allowScriptedContent?: boolean
ignoreClass?: string;
axis?: string;
flow?: string;
layout?: Layout;
method?: string;
width?: number;
height?: number;
forceEvenPages?: boolean;
allowScriptedContent?: boolean;
}
export default class View {
constructor(section: Section, options: ViewSettings);
create(): any;
render(request?: Function, show?: boolean): Promise<void>;
reset(): void;
size(_width: Number, _height: Number): void;
load(content: Contents): Promise<any>;
setLayout(layout: Layout): void;
setAxis(axis: string): void;
display(request?: Function): Promise<any>;
show(): void;
hide(): void;
offset(): { top: Number, left: Number };
offset(): { top: Number; left: Number };
width(): Number;
height(): Number;
position(): object;
locationOf(target: string): { top: Number, left: Number };
locationOf(target: string): { top: Number; left: Number };
onDisplayed(view: View): void;
onResize(view: View): void;
bounds(force?: boolean): object;
highlight(
cfiRange: string,
data?: object,
cb?: Function,
className?: string,
styles?: object
): void;
highlight(cfiRange: string, data?: object, cb?: Function, className?: string, styles?: object): void;
underline(cfiRange: string, data?: object, cb?: Function, className?: string, styles?: object): void;
mark(cfiRange: string, data?: object, cb?: Function): void;
underline(
cfiRange: string,
data?: object,
cb?: Function,
className?: string,
styles?: object
): void;
mark(cfiRange: string, data?: object, cb?: Function): void;
unhighlight(cfiRange: string): void;
ununderline(cfiRange: string): void;
unmark(cfiRange: string): void;
destroy(): void;
private onLoad(event: Event, promise: Promise<any>): void;
// Event emitters
emit(type: any, ...args: any[]): void;
off(type: any, listener: any): any;
on(type: any, listener: any): any;
once(type: any, listener: any, ...args: any[]): any;
}

29
types/mapping.d.ts vendored
View file

@ -1,34 +1,35 @@
import Layout from "./layout";
import Contents from "./contents";
import Layout from "./layout";
export interface EpubCFIPair {
start: string,
end: string
start: string;
end: string;
}
export interface RangePair {
start: Range,
end: Range
start: Range;
end: Range;
}
export default class Mapping {
constructor(layout: Layout, direction?: string, axis?: string, dev?: boolean);
page(contents: Contents, cfiBase: string, start: number, end: number): EpubCFIPair;
page(
contents: Contents,
cfiBase: string,
start: number,
end: number
): EpubCFIPair;
axis(axis: string): boolean;
private walk(root: Node, func: Function);
private findStart(root: Node, start: number, end: number): Range;
private findEnd(root: Node, start: number, end: number): Range;
private findTextStartRange(node: Node, start: number, end: number): Range;
private findTextEndRange(node: Node, start: number, end: number): Range;
private splitTextNodeIntoRanges(node: Node, _splitter?: string): Array<Range>;
private rangePairToCfiPair(cfiBase: string, rangePair: RangePair): EpubCFIPair;
private rangePairToCfiPair(
cfiBase: string,
rangePair: RangePair
): EpubCFIPair;
}

37
types/navigation.d.ts vendored
View file

@ -1,15 +1,15 @@
export interface NavItem {
id: string,
href: string,
label: string,
subitems?: Array<NavItem>,
parent?: string
id: string;
href: string;
label: string;
subitems?: Array<NavItem>;
parent?: string;
}
export interface LandmarkItem {
href?: string,
label?: string,
type?: string
href?: string;
label?: string;
type?: string;
}
export default class Navigation {
@ -19,28 +19,21 @@ export default class Navigation {
landmarks: Array<LandmarkItem>;
parse(xml: XMLDocument): void;
get(target: string) : NavItem;
landmark(type: string) : LandmarkItem;
get(target: string): NavItem;
landmark(type: string): LandmarkItem;
load(json: string): Array<NavItem>;
forEach(fn: (item: NavItem) => {}): any;
private unpack(toc: Array<NavItem>): void;
private parseNav(navHtml: XMLDocument): Array<NavItem>;
private navItem(item: Element): NavItem;
private parseLandmarks(navHtml: XMLDocument): Array<LandmarkItem>;
private landmarkItem(item: Element): LandmarkItem;
private parseNcx(navHtml: XMLDocument): Array<NavItem>;
private ncxItem(item: Element): NavItem;
private getByIndex(target: string, index: number, navItems: NavItem[]): NavItem;
private getByIndex(
target: string,
index: number,
navItems: NavItem[]
): NavItem;
}

76
types/packaging.d.ts vendored
View file

@ -1,47 +1,47 @@
import { SpineItem } from "./section";
export interface PackagingObject {
metadata: PackagingMetadataObject,
spine: Array<SpineItem>,
manifest: PackagingManifestObject,
navPath: string,
ncxPath: string,
coverPath: string,
spineNodeIndex: number
metadata: PackagingMetadataObject;
spine: Array<SpineItem>;
manifest: PackagingManifestObject;
navPath: string;
ncxPath: string;
coverPath: string;
spineNodeIndex: number;
}
export interface PackagingMetadataObject {
title: string,
creator: string,
description: string,
pubdate: string,
publisher: string,
identifier: string,
language: string,
rights: string,
modified_date: string,
layout: string,
orientation: string,
flow: string,
viewport: string,
spread: string,
direction: string,
title: string;
creator: string;
description: string;
pubdate: string;
publisher: string;
identifier: string;
language: string;
rights: string;
modified_date: string;
layout: string;
orientation: string;
flow: string;
viewport: string;
spread: string;
direction: string;
}
export interface PackagingSpineItem {
idref: string,
properties: Array<string>,
index: number
idref: string;
properties: Array<string>;
index: number;
}
export interface PackagingManifestItem {
href: string,
type: string,
properties: Array<string>
href: string;
type: string;
properties: Array<string>;
}
export interface PackagingManifestObject {
[key: string]: PackagingManifestItem
[key: string]: PackagingManifestItem;
}
export default class Packaging {
@ -56,24 +56,18 @@ export default class Packaging {
metadata: PackagingMetadataObject;
parse(packageDocument: XMLDocument): PackagingObject;
load(json: string): PackagingObject;
destroy(): void;
private parseMetadata(xml: Node): PackagingMetadataObject;
private parseManifest(xml: Node): PackagingManifestObject;
private parseSpine(xml: Node, manifest: PackagingManifestObject): Array<PackagingSpineItem>;
private parseSpine(
xml: Node,
manifest: PackagingManifestObject
): Array<PackagingSpineItem>;
private findNavPath(manifestNode: Node): string | false;
private findNcxPath(manifestNode: Node, spineNode: Node): string | false;
private findCoverPath(packageXml: Node): string;
private getElementText(xml: Node, tag: string): string
private getPropertyText(xml: Node, property: string): string
private getElementText(xml: Node, tag: string): string;
private getPropertyText(xml: Node, property: string): string;
}

16
types/pagelist.d.ts vendored
View file

@ -1,29 +1,21 @@
export interface PageListItem {
href: string,
page: string,
cfi?: string,
packageUrl?: string
href: string;
page: string;
cfi?: string;
packageUrl?: string;
}
export default class Pagelist {
constructor(xml: XMLDocument);
parse(xml: XMLDocument): Array<PageListItem>;
pageFromCfi(cfi: string): number;
cfiFromPage(pg: string | number): string;
pageFromPercentage(percent: number): number;
percentageFromPage(pg: number): number;
destroy(): void;
private parseNav(navHtml: Node): Array<PageListItem>;
private item(item: Node): PageListItem;
private process(pageList: Array<PageListItem>): void;
}

236
types/rendition.d.ts vendored
View file

@ -1,155 +1,119 @@
import Annotations from "./annotations";
import Book from "./book";
import Contents from "./contents";
import Section from "./section";
import View from "./managers/view";
import Hook from "./utils/hook";
import Themes from "./themes";
import EpubCFI from "./epubcfi";
import Annotations from "./annotations";
import View from "./managers/view";
import Section from "./section";
import Themes from "./themes";
import Hook from "./utils/hook";
import Queue from "./utils/queue";
export interface RenditionOptions {
width?: number | string,
height?: number | string,
ignoreClass?: string,
manager?: string | Function | object,
view?: string | Function | object,
flow?: string,
layout?: string,
spread?: string,
minSpreadWidth?: number,
stylesheet?: string,
resizeOnOrientationChange?: boolean,
script?: string,
infinite?: boolean,
overflow?: string,
snap?: boolean | object,
defaultDirection?: string,
allowScriptedContent?: boolean,
allowPopups?: boolean
width?: number | string;
height?: number | string;
ignoreClass?: string;
manager?: string | Function | object;
view?: string | Function | object;
flow?: string;
layout?: string;
spread?: string;
minSpreadWidth?: number;
stylesheet?: string;
resizeOnOrientationChange?: boolean;
script?: string;
infinite?: boolean;
overflow?: string;
snap?: boolean | object;
defaultDirection?: string;
allowScriptedContent?: boolean;
allowPopups?: boolean;
}
export interface DisplayedLocation {
index: number,
href: string,
cfi: string,
location: number,
percentage: number,
index: number;
href: string;
cfi: string;
location: number;
percentage: number;
displayed: {
page: number,
total: number
}
page: number;
total: number;
};
}
export interface Location {
start: DisplayedLocation,
end: DisplayedLocation,
atStart: boolean,
atEnd: boolean
start: DisplayedLocation;
end: DisplayedLocation;
atStart: boolean;
atEnd: boolean;
}
export default class Rendition {
constructor(book: Book, options: RenditionOptions);
constructor(book: Book, options: RenditionOptions);
settings: RenditionOptions;
book: Book;
hooks: {
display: Hook;
serialize: Hook;
content: Hook;
unloaded: Hook;
layout: Hook;
render: Hook;
show: Hook;
};
themes: Themes;
annotations: Annotations;
epubcfi: EpubCFI;
q: Queue;
location: Location;
started: Promise<void>;
settings: RenditionOptions;
book: Book;
hooks: {
display: Hook,
serialize: Hook,
content: Hook,
unloaded: Hook,
layout: Hook,
render: Hook,
show: Hook
}
themes: Themes;
annotations: Annotations;
epubcfi: EpubCFI;
q: Queue;
location: Location;
started: Promise<void>;
adjustImages(contents: Contents): Promise<void>;
attachTo(element: Element): Promise<void>;
clear(): void;
currentLocation(): DisplayedLocation;
currentLocation(): Promise<DisplayedLocation>;
destroy(): void;
determineLayoutProperties(metadata: object): object;
direction(dir: string): void;
display(target?: string): Promise<void>;
display(target?: number): Promise<void>;
flow(flow: string): void;
getContents(): Contents;
getRange(cfi: string, ignoreClass?: string): Range;
handleLinks(contents: Contents): void;
injectIdentifier(doc: Document, section: Section): void;
injectScript(doc: Document, section: Section): void;
injectStylesheet(doc: Document, section: Section): void;
layout(settings: any): any;
located(location: Location): DisplayedLocation;
moveTo(offset: number): void;
next(): Promise<void>;
onOrientationChange(orientation: string): void;
passEvents(contents: Contents): void;
prev(): Promise<void>;
reportLocation(): Promise<void>;
requireManager(manager: string | Function | object): any;
requireView(view: string | Function | object): any;
resize(width: number, height: number): void;
setManager(manager: Function): void;
spread(spread: string, min?: number): void;
start(): void;
views(): Array<View>;
// Event emitters
emit(type: any, ...args: any[]): void;
off(type: any, listener: any): any;
on(type: any, listener: any): any;
once(type: any, listener: any, ...args: any[]): any;
private triggerMarkEvent(cfiRange: string, data: object, contents: Contents): void;
private triggerSelectedEvent(cfirange: string, contents: Contents): void;
private triggerViewEvent(e: Event, contents: Contents): void;
private onResized(size: { width: number, height: number }): void;
private afterDisplayed(view: any): void;
private afterRemoved(view: any): void;
adjustImages(contents: Contents): Promise<void>;
attachTo(element: Element): Promise<void>;
clear(): void;
currentLocation(): DisplayedLocation;
currentLocation(): Promise<DisplayedLocation>;
destroy(): void;
determineLayoutProperties(metadata: object): object;
direction(dir: string): void;
display(target?: string): Promise<void>;
display(target?: number): Promise<void>;
flow(flow: string): void;
getContents(): Contents;
getRange(cfi: string, ignoreClass?: string): Range;
handleLinks(contents: Contents): void;
injectIdentifier(doc: Document, section: Section): void;
injectScript(doc: Document, section: Section): void;
injectStylesheet(doc: Document, section: Section): void;
layout(settings: any): any;
located(location: Location): DisplayedLocation;
moveTo(offset: number): void;
next(): Promise<void>;
onOrientationChange(orientation: string): void;
passEvents(contents: Contents): void;
prev(): Promise<void>;
reportLocation(): Promise<void>;
requireManager(manager: string | Function | object): any;
requireView(view: string | Function | object): any;
resize(width: number, height: number): void;
setManager(manager: Function): void;
spread(spread: string, min?: number): void;
start(): void;
views(): Array<View>;
// Event emitters
emit(type: any, ...args: any[]): void;
off(type: any, listener: any): any;
on(type: any, listener: any): any;
once(type: any, listener: any, ...args: any[]): any;
private triggerMarkEvent(
cfiRange: string,
data: object,
contents: Contents
): void;
private triggerSelectedEvent(cfirange: string, contents: Contents): void;
private triggerViewEvent(e: Event, contents: Contents): void;
private onResized(size: { width: number; height: number }): void;
private afterDisplayed(view: any): void;
private afterRemoved(view: any): void;
}

32
types/resources.d.ts vendored
View file

@ -1,33 +1,29 @@
import { PackagingManifestObject } from "./packaging";
import Archive from "./archive";
import { PackagingManifestObject } from "./packaging";
export default class Resources {
constructor(manifest: PackagingManifestObject, options: {
replacements?: string,
archive?: Archive,
resolver?: Function,
request?: Function
});
constructor(
manifest: PackagingManifestObject,
options: {
replacements?: string;
archive?: Archive;
resolver?: Function;
request?: Function;
}
);
process(manifest: PackagingManifestObject): void;
createUrl(url: string): Promise<string>;
replacements(): Promise<Array<string>>;
relativeTo(absolute: boolean, resolver?: Function): Array<string>;
get(path: string): string;
substitute(content: string, url?: string): string;
destroy(): void;
private split(): void;
private splitUrls(): void;
private replaceCss(archive: Archive, resolver?: Function): Promise<Array<string>>;
private replaceCss(
archive: Archive,
resolver?: Function
): Promise<Array<string>>;
private createCssFile(href: string): Promise<string>;
}

42
types/section.d.ts vendored
View file

@ -1,32 +1,31 @@
import { HooksObject } from "./utils/hook";
export interface GlobalLayout {
layout: string,
spread: string,
orientation: string
layout: string;
spread: string;
orientation: string;
}
export interface LayoutSettings {
layout: string,
spread: string,
orientation: string
layout: string;
spread: string;
orientation: string;
}
export interface SpineItem {
index: number,
cfiBase: string,
href?: string,
url?: string,
canonical?: string,
properties?: Array<string>,
linear?: string,
next: () => SpineItem,
prev: () => SpineItem,
index: number;
cfiBase: string;
href?: string;
url?: string;
canonical?: string;
properties?: Array<string>;
linear?: string;
next: () => SpineItem;
prev: () => SpineItem;
}
export default class Section {
constructor(item: SpineItem, hooks: HooksObject);
idref: string;
linear: boolean;
properties: Array<string>;
@ -37,28 +36,17 @@ export default class Section {
next: () => SpineItem;
prev: () => SpineItem;
cfiBase: string;
document: Document;
contents: Element;
output: string;
hooks: HooksObject;
load(_request?: Function): Document;
render(_request?: Function): string;
find(_query: string): Array<Element>;
reconcileLayoutSettings(globalLayout: GlobalLayout): LayoutSettings;
cfiFromRange(_range: Range): string;
cfiFromElement(el: Element): string;
unload(): void;
destroy(): void;
private base(): void;
}

14
types/spine.d.ts vendored
View file

@ -4,27 +4,17 @@ import Hook from "./utils/hook";
export default class Spine {
constructor();
hooks: {
serialize: Hook,
content: Hook
serialize: Hook;
content: Hook;
};
unpack(_package: Packaging, resolver: Function, canonical: Function): void;
get(target?: string | number): Section;
each(...args: any[]): any;
first(): Section;
last(): Section;
destroy(): void;
private append(section: Section): number;
private prepend(section: Section): number;
private remove(section: Section): number;
}

31
types/store.d.ts vendored
View file

@ -1,30 +1,29 @@
import localForage = require('localforage');
import localForage = require("localforage");
import Resources from "./resources";
export default class Store {
constructor(name: string, request?: Function, resolver?: Function);
add(resources: Resources, force?: boolean): Promise<Array<object>>;
put(url: string, withCredentials?: boolean, headers?: object): Promise<Blob>;
request(url: string, type?: string, withCredentials?: boolean, headers?: object): Promise<Blob | string | JSON | Document | XMLDocument>;
retrieve(url: string, type?: string): Promise<Blob | string | JSON | Document | XMLDocument>;
request(
url: string,
type?: string,
withCredentials?: boolean,
headers?: object
): Promise<Blob | string | JSON | Document | XMLDocument>;
retrieve(
url: string,
type?: string
): Promise<Blob | string | JSON | Document | XMLDocument>;
getBlob(url: string, mimeType?: string): Promise<Blob>;
getText(url: string): Promise<string>;
getBase64(url: string, mimeType?: string): Promise<string>;
createUrl(url: string, options: { base64: boolean }): Promise<string>;
revokeUrl(url: string): void;
destroy(): void;
private checkRequirements(): void;
private handleResponse(response: any, type?: string): Blob | string | JSON | Document | XMLDocument;
private handleResponse(
response: any,
type?: string
): Blob | string | JSON | Document | XMLDocument;
}

43
types/themes.d.ts vendored
View file

@ -1,40 +1,23 @@
import Rendition from "./rendition";
import Contents from "./contents";
import Rendition from "./rendition";
export default class Themes {
constructor(rendition: Rendition);
register( themeObject: object ): void;
register( theme: string, url: string ): void;
register( theme: string, themeObject: object ): void;
default( theme: object | string ): void;
registerThemes( themes: object ): void;
registerCss( name: string, css: string ): void;
registerUrl( name: string, input: string ): void;
registerRules( name: string, rules: object ): void;
select( name: string ): void;
update( name: string ): void;
inject( content: Contents ): void;
add( name: string, contents: Contents ): void;
register(themeObject: object): void;
register(theme: string, url: string): void;
register(theme: string, themeObject: object): void;
default(theme: object | string): void;
registerThemes(themes: object): void;
registerCss(name: string, css: string): void;
registerUrl(name: string, input: string): void;
registerRules(name: string, rules: object): void;
select(name: string): void;
update(name: string): void;
inject(content: Contents): void;
add(name: string, contents: Contents): void;
override(name: string, value: string, priority?: boolean): void;
overrides(contents: Contents): void;
fontSize(size: string): void;
font(f: string): void;
destroy(): void;
}

View file

@ -1,24 +1,16 @@
{
"compilerOptions": {
"module": "commonjs",
"lib": [
"es6",
"dom"
],
"noImplicitAny": true,
"noImplicitThis": true,
"strictNullChecks": true,
"strictFunctionTypes": true,
"baseUrl": "../",
"typeRoots": [
"../"
],
"types": [],
"noEmit": true,
"forceConsistentCasingInFileNames": true
},
"files": [
"index.d.ts",
"epubjs-tests.ts"
]
"compilerOptions": {
"module": "commonjs",
"lib": ["es6", "dom"],
"noImplicitAny": true,
"noImplicitThis": true,
"strictNullChecks": true,
"strictFunctionTypes": true,
"baseUrl": "../",
"typeRoots": ["../"],
"types": [],
"noEmit": true,
"forceConsistentCasingInFileNames": true
},
"files": ["index.d.ts"]
}

View file

@ -4,6 +4,6 @@ export const DOM_EVENTS: Array<string>;
export const EVENTS: {
[key: string]: {
[key: string]: string
}
}
[key: string]: string;
};
};

114
types/utils/core.d.ts vendored
View file

@ -1,79 +1,85 @@
export function uuid(): string;
export function documentHeight(): number;
export function isElement(obj: object): boolean;
export function isNumber(n: any): boolean;
export function isFloat(n: any): boolean;
export function prefixed(unprefixed: string): string;
export function defaults(obj: object): object;
export function extend(target: object): object;
export function insert(item: any, array: Array<any>, compareFunction: Function): number;
export function locationOf(item: any, array: Array<any>, compareFunction: Function, _start: Function, _end: Function): number;
export function indexOfSorted(item: any, array: Array<any>, compareFunction: Function, _start: Function, _end: Function): number;
export function bounds(el: Element): { width: Number, height: Number};
export function borders(el: Element): { width: Number, height: Number};
export function insert(
item: any,
array: Array<any>,
compareFunction: Function
): number;
export function locationOf(
item: any,
array: Array<any>,
compareFunction: Function,
_start: Function,
_end: Function
): number;
export function indexOfSorted(
item: any,
array: Array<any>,
compareFunction: Function,
_start: Function,
_end: Function
): number;
export function bounds(el: Element): { width: Number; height: Number };
export function borders(el: Element): { width: Number; height: Number };
export function nodeBounds(node: Node): object;
export function windowBounds(): { width: Number, height: Number, top: Number, left: Number, right: Number, bottom: Number };
export function windowBounds(): {
width: Number;
height: Number;
top: Number;
left: Number;
right: Number;
bottom: Number;
};
export function indexOfNode(node: Node, typeId: string): number;
export function indexOfTextNode(textNode: Node): number;
export function indexOfElementNode(elementNode: Element): number;
export function isXml(ext: string): boolean;
export function createBlob(content: any, mime: string): Blob;
export function createBlobUrl(content: any, mime: string): string;
export function revokeBlobUrl(url: string): void;
export function createBase64Url(content: any, mime: string): string
export function createBase64Url(content: any, mime: string): string;
export function type(obj: object): string;
export function parse(markup: string, mime: string, forceXMLDom: boolean): Document;
export function parse(
markup: string,
mime: string,
forceXMLDom: boolean
): Document;
export function qs(el: Element, sel: string): Element;
export function qsa(el: Element, sel: string): ArrayLike<Element>;
export function qsp(el: Element, sel: string, props: Array<object>): ArrayLike<Element>;
export function qsp(
el: Element,
sel: string,
props: Array<object>
): ArrayLike<Element>;
export function sprint(root: Node, func: Function): void;
export function treeWalker(root: Node, func: Function, filter: object | Function): void;
export function treeWalker(
root: Node,
func: Function,
filter: object | Function
): void;
export function walk(node: Node, callback: Function): void;
export function blob2base64(blob: Blob): string;
export function defer(): Promise<any>;
export function querySelectorByType(html: Element, element: string, type: string): Array<Element>;
export function querySelectorByType(
html: Element,
element: string,
type: string
): Array<Element>;
export function findChildren(el: Element): Array<Element>;
export function parents(node: Element): Array<Element>;
export function filterChildren(el: Element, nodeName: string, single: boolean): Array<Element>;
export function getParentByTagName(node: Element, tagname: string): Array<Element>;
export class RangeObject extends Range {
}
export function filterChildren(
el: Element,
nodeName: string,
single: boolean
): Array<Element>;
export function getParentByTagName(
node: Element,
tagname: string
): Array<Element>;
export class RangeObject extends Range {}

View file

@ -1,5 +1,5 @@
interface HooksObject {
[key: string]: Hook
[key: string]: Hook;
}
export default class Hook {
@ -7,12 +7,8 @@ export default class Hook {
register(func: Function): void;
register(arr: Array<Function>): void;
deregister(func: Function): void;
trigger(...args: any[]): Promise<any>;
list(): Array<any>;
clear(): void;
}

View file

@ -2,16 +2,10 @@ export default class Path {
constructor(pathString: string);
parse(what: string): object;
isAbsolute(what: string): boolean;
isDirectory(what: string): boolean;
resolve(what: string): string;
relative(what: string): string;
splitPath(filename: string): string;
toString(): string;
}

View file

@ -1,31 +1,21 @@
import { defer } from "./core";
export interface QueuedTask {
task: any | Task,
args: any[],
deferred: any, // should be defer, but not working
promise: Promise<any>
task: any | Task;
args: any[];
deferred: any; // should be defer, but not working
promise: Promise<any>;
}
export default class Queue {
constructor(context: any);
enqueue(func: Promise<Function> | Function, ...args: any[]): Promise<any>;
dequeue(): Promise<QueuedTask>;
dump(): void;
run(): Promise<void>;
flush(): Promise<void>;
clear(): void;
length(): number;
pause(): void;
stop(): void;
}

View file

@ -1,12 +1,12 @@
import Section from "../section";
import Contents from "../contents";
import Section from "../section";
export function replaceBase(doc: Document, section: Section): void;
export function replaceCanonical(doc: Document, section: Section): void;
export function replaceMeta(doc: Document, section: Section): void;
export function replaceLinks(contents: Contents, fn: Function): void;
export function substitute(contents: Contents, urls: string[], replacements: string[]): void;
export function substitute(
contents: Contents,
urls: string[],
replacements: string[]
): void;

View file

@ -1 +1,6 @@
export default function request(url: string, type?: string, withCredentials?: boolean, headers?: object): Promise<Blob | string | JSON | Document | XMLDocument>;
export default function request(
url: string,
type?: string,
withCredentials?: boolean,
headers?: object
): Promise<Blob | string | JSON | Document | XMLDocument>;

View file

@ -1,3 +1,2 @@
export default function scrollType(): string;
export function createDefiner(): Node;

View file

@ -4,10 +4,7 @@ export default class Url {
constructor(urlString: string, baseString: string);
path(): Path;
resolve(what: string): string;
relative(what: string): string;
toString(): string;
}

View file

@ -1,83 +1,88 @@
var webpack = require("webpack");
var path = require("path");
var PROD = (process.env.NODE_ENV === "production")
var LEGACY = (process.env.LEGACY)
var MINIMIZE = (process.env.MINIMIZE === "true")
var LEGACY = process.env.LEGACY;
var MINIMIZE = process.env.MINIMIZE === "true";
var hostname = "localhost";
var port = 8080;
var filename = "[name]";
var sourceMapFilename = "[name]";
if (LEGACY) {
filename += ".legacy";
filename += ".legacy";
}
if (MINIMIZE) {
filename += ".min.js";
sourceMapFilename += ".min.js.map";
filename += ".min.js";
sourceMapFilename += ".min.js.map";
} else {
filename += ".js";
sourceMapFilename += ".js.map";
filename += ".js";
sourceMapFilename += ".js.map";
}
module.exports = {
mode: process.env.NODE_ENV,
entry: {
"epub": "./src/epub.js",
},
devtool: MINIMIZE ? false : 'source-map',
output: {
path: path.resolve("./dist"),
filename: filename,
sourceMapFilename: sourceMapFilename,
library: "ePub",
libraryTarget: "umd",
libraryExport: "default",
publicPath: "/dist/"
},
optimization: {
minimize: MINIMIZE
},
externals: {
"jszip/dist/jszip": "JSZip",
"xmldom": "xmldom"
},
plugins: [],
resolve: {
alias: {
path: "path-webpack"
}
},
devServer: {
host: hostname,
port: port,
inline: true,
headers: {
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Methods": "GET,PUT,POST,DELETE",
"Access-Control-Allow-Headers": "Content-Type"
}
},
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
use: {
loader: "babel-loader",
options: {
presets: [["@babel/preset-env", {
targets: LEGACY ? "defaults" : "last 2 Chrome versions, last 2 Safari versions, last 2 ChromeAndroid versions, last 2 iOS versions, last 2 Firefox versions, last 2 Edge versions",
corejs: 3,
useBuiltIns: "usage",
bugfixes: true,
modules: false
}]]
}
}
}
]
},
performance: {
hints: false
}
}
mode: process.env.NODE_ENV,
entry: {
epub: "./src/epub.js",
},
devtool: MINIMIZE ? false : "source-map",
output: {
path: path.resolve("./dist"),
filename: filename,
sourceMapFilename: sourceMapFilename,
library: "ePub",
libraryTarget: "umd",
libraryExport: "default",
publicPath: "/dist/",
},
optimization: {
minimize: MINIMIZE,
},
externals: {
"jszip/dist/jszip": "JSZip",
xmldom: "xmldom",
},
plugins: [],
resolve: {
alias: {
path: "path-webpack",
},
},
devServer: {
host: hostname,
port: port,
inline: true,
headers: {
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Methods": "GET,PUT,POST,DELETE",
"Access-Control-Allow-Headers": "Content-Type",
},
},
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
use: {
loader: "babel-loader",
options: {
presets: [
[
"@babel/preset-env",
{
targets: LEGACY
? "defaults"
: "last 2 Chrome versions, last 2 Safari versions, last 2 ChromeAndroid versions, last 2 iOS versions, last 2 Firefox versions, last 2 Edge versions",
corejs: 3,
useBuiltIns: "usage",
bugfixes: true,
modules: false,
},
],
],
},
},
},
],
},
performance: {
hints: false,
},
};