mirror of
https://github.com/advplyr/audiobookshelf.git
synced 2026-07-10 11:21:36 +00:00
got it to build, quick n dirty; epubjs patch
This commit is contained in:
parent
c67c586c98
commit
2baf50da12
43 changed files with 14616 additions and 203 deletions
|
|
@ -15,7 +15,7 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import ePub from 'epubjs'
|
import ePub from '../../static/libs/epubjs-patch/index.js'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @typedef {object} EpubReader
|
* @typedef {object} EpubReader
|
||||||
|
|
|
||||||
|
|
@ -124,7 +124,8 @@ export default defineNuxtConfig({
|
||||||
autoprefixer: {}
|
autoprefixer: {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
transpile: ['luxon', 'cookie-es']
|
||||||
},
|
},
|
||||||
watchers: {
|
watchers: {
|
||||||
webpack: {
|
webpack: {
|
||||||
|
|
@ -137,7 +138,10 @@ export default defineNuxtConfig({
|
||||||
host: '0.0.0.0'
|
host: '0.0.0.0'
|
||||||
},
|
},
|
||||||
|
|
||||||
bridge: false,
|
bridge: {
|
||||||
|
transpile: true,
|
||||||
|
nitro: true
|
||||||
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Temporary workaround for @nuxt-community/tailwindcss-module.
|
* Temporary workaround for @nuxt-community/tailwindcss-module.
|
||||||
|
|
|
||||||
232
client/package-lock.json
generated
232
client/package-lock.json
generated
|
|
@ -30,6 +30,8 @@
|
||||||
"@nuxt/bridge": "^3.3.1",
|
"@nuxt/bridge": "^3.3.1",
|
||||||
"@nuxtjs/pwa": "^3.3.5",
|
"@nuxtjs/pwa": "^3.3.5",
|
||||||
"autoprefixer": "^10.4.7",
|
"autoprefixer": "^10.4.7",
|
||||||
|
"defu": "^6.1.4",
|
||||||
|
"hookable": "^5.5.3",
|
||||||
"nuxi": "^3.13.2",
|
"nuxi": "^3.13.2",
|
||||||
"postcss": "^8.3.6",
|
"postcss": "^8.3.6",
|
||||||
"tailwindcss": "^3.4.1"
|
"tailwindcss": "^3.4.1"
|
||||||
|
|
@ -4123,13 +4125,6 @@
|
||||||
"node": "^14.16.0 || ^16.10.0 || >=17.0.0"
|
"node": "^14.16.0 || ^16.10.0 || >=17.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@nuxt/bridge-schema/node_modules/defu": {
|
|
||||||
"version": "6.1.4",
|
|
||||||
"resolved": "https://registry.npmjs.org/defu/-/defu-6.1.4.tgz",
|
|
||||||
"integrity": "sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==",
|
|
||||||
"dev": true,
|
|
||||||
"license": "MIT"
|
|
||||||
},
|
|
||||||
"node_modules/@nuxt/bridge-schema/node_modules/scule": {
|
"node_modules/@nuxt/bridge-schema/node_modules/scule": {
|
||||||
"version": "1.3.0",
|
"version": "1.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/scule/-/scule-1.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/scule/-/scule-1.3.0.tgz",
|
||||||
|
|
@ -4191,13 +4186,6 @@
|
||||||
"node": ">=0.4.0"
|
"node": ">=0.4.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@nuxt/bridge/node_modules/defu": {
|
|
||||||
"version": "6.1.4",
|
|
||||||
"resolved": "https://registry.npmjs.org/defu/-/defu-6.1.4.tgz",
|
|
||||||
"integrity": "sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==",
|
|
||||||
"dev": true,
|
|
||||||
"license": "MIT"
|
|
||||||
},
|
|
||||||
"node_modules/@nuxt/bridge/node_modules/enhanced-resolve": {
|
"node_modules/@nuxt/bridge/node_modules/enhanced-resolve": {
|
||||||
"version": "5.17.1",
|
"version": "5.17.1",
|
||||||
"resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.17.1.tgz",
|
"resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.17.1.tgz",
|
||||||
|
|
@ -4268,13 +4256,6 @@
|
||||||
"url": "https://github.com/sponsors/sindresorhus"
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@nuxt/bridge/node_modules/hookable": {
|
|
||||||
"version": "5.5.3",
|
|
||||||
"resolved": "https://registry.npmjs.org/hookable/-/hookable-5.5.3.tgz",
|
|
||||||
"integrity": "sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ==",
|
|
||||||
"dev": true,
|
|
||||||
"license": "MIT"
|
|
||||||
},
|
|
||||||
"node_modules/@nuxt/bridge/node_modules/node-fetch": {
|
"node_modules/@nuxt/bridge/node_modules/node-fetch": {
|
||||||
"version": "3.3.2",
|
"version": "3.3.2",
|
||||||
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.2.tgz",
|
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.2.tgz",
|
||||||
|
|
@ -4560,12 +4541,6 @@
|
||||||
"node": "^14.18.0 || >=16.10.0"
|
"node": "^14.18.0 || >=16.10.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@nuxt/cli/node_modules/defu": {
|
|
||||||
"version": "6.1.4",
|
|
||||||
"resolved": "https://registry.npmjs.org/defu/-/defu-6.1.4.tgz",
|
|
||||||
"integrity": "sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==",
|
|
||||||
"license": "MIT"
|
|
||||||
},
|
|
||||||
"node_modules/@nuxt/cli/node_modules/fs-extra": {
|
"node_modules/@nuxt/cli/node_modules/fs-extra": {
|
||||||
"version": "11.2.0",
|
"version": "11.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.2.0.tgz",
|
||||||
|
|
@ -4580,6 +4555,12 @@
|
||||||
"node": ">=14.14"
|
"node": ">=14.14"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@nuxt/cli/node_modules/hookable": {
|
||||||
|
"version": "4.4.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/hookable/-/hookable-4.4.1.tgz",
|
||||||
|
"integrity": "sha512-KWjZM8C7IVT2qne5HTXjM6R6VnRfjfRlf/oCnHd+yFxoHO1DzOl6B9LzV/VqGQK/IrFewq+EG+ePVrE9Tpc3fg==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/@nuxt/components": {
|
"node_modules/@nuxt/components": {
|
||||||
"version": "2.2.1",
|
"version": "2.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/@nuxt/components/-/components-2.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/@nuxt/components/-/components-2.2.1.tgz",
|
||||||
|
|
@ -4666,12 +4647,6 @@
|
||||||
"node": "^14.18.0 || >=16.10.0"
|
"node": "^14.18.0 || >=16.10.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@nuxt/config/node_modules/defu": {
|
|
||||||
"version": "6.1.4",
|
|
||||||
"resolved": "https://registry.npmjs.org/defu/-/defu-6.1.4.tgz",
|
|
||||||
"integrity": "sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==",
|
|
||||||
"license": "MIT"
|
|
||||||
},
|
|
||||||
"node_modules/@nuxt/core": {
|
"node_modules/@nuxt/core": {
|
||||||
"version": "2.18.1",
|
"version": "2.18.1",
|
||||||
"resolved": "https://registry.npmjs.org/@nuxt/core/-/core-2.18.1.tgz",
|
"resolved": "https://registry.npmjs.org/@nuxt/core/-/core-2.18.1.tgz",
|
||||||
|
|
@ -4714,6 +4689,12 @@
|
||||||
"node": ">=14.14"
|
"node": ">=14.14"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@nuxt/core/node_modules/hookable": {
|
||||||
|
"version": "4.4.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/hookable/-/hookable-4.4.1.tgz",
|
||||||
|
"integrity": "sha512-KWjZM8C7IVT2qne5HTXjM6R6VnRfjfRlf/oCnHd+yFxoHO1DzOl6B9LzV/VqGQK/IrFewq+EG+ePVrE9Tpc3fg==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/@nuxt/devalue": {
|
"node_modules/@nuxt/devalue": {
|
||||||
"version": "2.0.2",
|
"version": "2.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/@nuxt/devalue/-/devalue-2.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/@nuxt/devalue/-/devalue-2.0.2.tgz",
|
||||||
|
|
@ -4839,12 +4820,6 @@
|
||||||
"node": "^14.18.0 || >=16.10.0"
|
"node": "^14.18.0 || >=16.10.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@nuxt/generator/node_modules/defu": {
|
|
||||||
"version": "6.1.4",
|
|
||||||
"resolved": "https://registry.npmjs.org/defu/-/defu-6.1.4.tgz",
|
|
||||||
"integrity": "sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==",
|
|
||||||
"license": "MIT"
|
|
||||||
},
|
|
||||||
"node_modules/@nuxt/generator/node_modules/fs-extra": {
|
"node_modules/@nuxt/generator/node_modules/fs-extra": {
|
||||||
"version": "11.2.0",
|
"version": "11.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.2.0.tgz",
|
||||||
|
|
@ -4901,13 +4876,6 @@
|
||||||
"node": "^14.18.0 || >=16.10.0"
|
"node": "^14.18.0 || >=16.10.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@nuxt/kit/node_modules/defu": {
|
|
||||||
"version": "6.1.4",
|
|
||||||
"resolved": "https://registry.npmjs.org/defu/-/defu-6.1.4.tgz",
|
|
||||||
"integrity": "sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==",
|
|
||||||
"dev": true,
|
|
||||||
"license": "MIT"
|
|
||||||
},
|
|
||||||
"node_modules/@nuxt/kit/node_modules/globby": {
|
"node_modules/@nuxt/kit/node_modules/globby": {
|
||||||
"version": "14.0.2",
|
"version": "14.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/globby/-/globby-14.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/globby/-/globby-14.0.2.tgz",
|
||||||
|
|
@ -5021,6 +4989,13 @@
|
||||||
"semver": "^7.3.4"
|
"semver": "^7.3.4"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@nuxt/postcss8/node_modules/defu": {
|
||||||
|
"version": "3.2.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/defu/-/defu-3.2.2.tgz",
|
||||||
|
"integrity": "sha512-8UWj5lNv7HD+kB0e9w77Z7TdQlbUYDVWqITLHNqFIn6khrNHv5WQo38Dcm1f6HeNyZf0U7UbPf6WeZDSdCzGDQ==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/@nuxt/postcss8/node_modules/postcss-import": {
|
"node_modules/@nuxt/postcss8/node_modules/postcss-import": {
|
||||||
"version": "13.0.0",
|
"version": "13.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-13.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-13.0.0.tgz",
|
||||||
|
|
@ -5073,20 +5048,6 @@
|
||||||
"node": "^14.18.0 || >=16.10.0"
|
"node": "^14.18.0 || >=16.10.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@nuxt/schema/node_modules/defu": {
|
|
||||||
"version": "6.1.4",
|
|
||||||
"resolved": "https://registry.npmjs.org/defu/-/defu-6.1.4.tgz",
|
|
||||||
"integrity": "sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==",
|
|
||||||
"dev": true,
|
|
||||||
"license": "MIT"
|
|
||||||
},
|
|
||||||
"node_modules/@nuxt/schema/node_modules/hookable": {
|
|
||||||
"version": "5.5.3",
|
|
||||||
"resolved": "https://registry.npmjs.org/hookable/-/hookable-5.5.3.tgz",
|
|
||||||
"integrity": "sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ==",
|
|
||||||
"dev": true,
|
|
||||||
"license": "MIT"
|
|
||||||
},
|
|
||||||
"node_modules/@nuxt/schema/node_modules/scule": {
|
"node_modules/@nuxt/schema/node_modules/scule": {
|
||||||
"version": "1.3.0",
|
"version": "1.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/scule/-/scule-1.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/scule/-/scule-1.3.0.tgz",
|
||||||
|
|
@ -5180,11 +5141,6 @@
|
||||||
"node": "^14.18.0 || >=16.10.0"
|
"node": "^14.18.0 || >=16.10.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@nuxt/telemetry/node_modules/defu": {
|
|
||||||
"version": "6.1.4",
|
|
||||||
"resolved": "https://registry.npmjs.org/defu/-/defu-6.1.4.tgz",
|
|
||||||
"integrity": "sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg=="
|
|
||||||
},
|
|
||||||
"node_modules/@nuxt/telemetry/node_modules/dotenv": {
|
"node_modules/@nuxt/telemetry/node_modules/dotenv": {
|
||||||
"version": "9.0.2",
|
"version": "9.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-9.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-9.0.2.tgz",
|
||||||
|
|
@ -5369,12 +5325,6 @@
|
||||||
"node": "^14.18.0 || >=16.10.0"
|
"node": "^14.18.0 || >=16.10.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@nuxt/vue-renderer/node_modules/defu": {
|
|
||||||
"version": "6.1.4",
|
|
||||||
"resolved": "https://registry.npmjs.org/defu/-/defu-6.1.4.tgz",
|
|
||||||
"integrity": "sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==",
|
|
||||||
"license": "MIT"
|
|
||||||
},
|
|
||||||
"node_modules/@nuxt/vue-renderer/node_modules/fs-extra": {
|
"node_modules/@nuxt/vue-renderer/node_modules/fs-extra": {
|
||||||
"version": "11.2.0",
|
"version": "11.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.2.0.tgz",
|
||||||
|
|
@ -5499,6 +5449,13 @@
|
||||||
"workbox-cdn": "^5.1.4"
|
"workbox-cdn": "^5.1.4"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@nuxtjs/pwa/node_modules/defu": {
|
||||||
|
"version": "3.2.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/defu/-/defu-3.2.2.tgz",
|
||||||
|
"integrity": "sha512-8UWj5lNv7HD+kB0e9w77Z7TdQlbUYDVWqITLHNqFIn6khrNHv5WQo38Dcm1f6HeNyZf0U7UbPf6WeZDSdCzGDQ==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/@nuxtjs/youch": {
|
"node_modules/@nuxtjs/youch": {
|
||||||
"version": "4.2.3",
|
"version": "4.2.3",
|
||||||
"resolved": "https://registry.npmjs.org/@nuxtjs/youch/-/youch-4.2.3.tgz",
|
"resolved": "https://registry.npmjs.org/@nuxtjs/youch/-/youch-4.2.3.tgz",
|
||||||
|
|
@ -5757,7 +5714,7 @@
|
||||||
},
|
},
|
||||||
"node_modules/@parcel/watcher-wasm/node_modules/napi-wasm": {
|
"node_modules/@parcel/watcher-wasm/node_modules/napi-wasm": {
|
||||||
"version": "1.1.0",
|
"version": "1.1.0",
|
||||||
"extraneous": true,
|
"dev": true,
|
||||||
"inBundle": true,
|
"inBundle": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
|
@ -6769,13 +6726,6 @@
|
||||||
"url": "https://github.com/sponsors/harlan-zw"
|
"url": "https://github.com/sponsors/harlan-zw"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@unhead/schema/node_modules/hookable": {
|
|
||||||
"version": "5.5.3",
|
|
||||||
"resolved": "https://registry.npmjs.org/hookable/-/hookable-5.5.3.tgz",
|
|
||||||
"integrity": "sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ==",
|
|
||||||
"dev": true,
|
|
||||||
"license": "MIT"
|
|
||||||
},
|
|
||||||
"node_modules/@unhead/shared": {
|
"node_modules/@unhead/shared": {
|
||||||
"version": "1.11.5",
|
"version": "1.11.5",
|
||||||
"resolved": "https://registry.npmjs.org/@unhead/shared/-/shared-1.11.5.tgz",
|
"resolved": "https://registry.npmjs.org/@unhead/shared/-/shared-1.11.5.tgz",
|
||||||
|
|
@ -6823,20 +6773,6 @@
|
||||||
"vue": ">=2.7 || >=3"
|
"vue": ">=2.7 || >=3"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@unhead/vue/node_modules/defu": {
|
|
||||||
"version": "6.1.4",
|
|
||||||
"resolved": "https://registry.npmjs.org/defu/-/defu-6.1.4.tgz",
|
|
||||||
"integrity": "sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==",
|
|
||||||
"dev": true,
|
|
||||||
"license": "MIT"
|
|
||||||
},
|
|
||||||
"node_modules/@unhead/vue/node_modules/hookable": {
|
|
||||||
"version": "5.5.3",
|
|
||||||
"resolved": "https://registry.npmjs.org/hookable/-/hookable-5.5.3.tgz",
|
|
||||||
"integrity": "sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ==",
|
|
||||||
"dev": true,
|
|
||||||
"license": "MIT"
|
|
||||||
},
|
|
||||||
"node_modules/@vercel/nft": {
|
"node_modules/@vercel/nft": {
|
||||||
"version": "0.26.5",
|
"version": "0.26.5",
|
||||||
"resolved": "https://registry.npmjs.org/@vercel/nft/-/nft-0.26.5.tgz",
|
"resolved": "https://registry.npmjs.org/@vercel/nft/-/nft-0.26.5.tgz",
|
||||||
|
|
@ -8757,13 +8693,6 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/c12/node_modules/defu": {
|
|
||||||
"version": "6.1.4",
|
|
||||||
"resolved": "https://registry.npmjs.org/defu/-/defu-6.1.4.tgz",
|
|
||||||
"integrity": "sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==",
|
|
||||||
"dev": true,
|
|
||||||
"license": "MIT"
|
|
||||||
},
|
|
||||||
"node_modules/cac": {
|
"node_modules/cac": {
|
||||||
"version": "6.7.14",
|
"version": "6.7.14",
|
||||||
"resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz",
|
"resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz",
|
||||||
|
|
@ -10828,10 +10757,10 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/defu": {
|
"node_modules/defu": {
|
||||||
"version": "3.2.2",
|
"version": "6.1.4",
|
||||||
"resolved": "https://registry.npmjs.org/defu/-/defu-3.2.2.tgz",
|
"resolved": "https://registry.npmjs.org/defu/-/defu-6.1.4.tgz",
|
||||||
"integrity": "sha512-8UWj5lNv7HD+kB0e9w77Z7TdQlbUYDVWqITLHNqFIn6khrNHv5WQo38Dcm1f6HeNyZf0U7UbPf6WeZDSdCzGDQ==",
|
"integrity": "sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==",
|
||||||
"dev": true
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/delayed-stream": {
|
"node_modules/delayed-stream": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
|
|
@ -12697,13 +12626,6 @@
|
||||||
"node": "^14.18.0 || >=16.10.0"
|
"node": "^14.18.0 || >=16.10.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/giget/node_modules/defu": {
|
|
||||||
"version": "6.1.4",
|
|
||||||
"resolved": "https://registry.npmjs.org/defu/-/defu-6.1.4.tgz",
|
|
||||||
"integrity": "sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==",
|
|
||||||
"dev": true,
|
|
||||||
"license": "MIT"
|
|
||||||
},
|
|
||||||
"node_modules/git-config-path": {
|
"node_modules/git-config-path": {
|
||||||
"version": "2.0.0",
|
"version": "2.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/git-config-path/-/git-config-path-2.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/git-config-path/-/git-config-path-2.0.0.tgz",
|
||||||
|
|
@ -12884,13 +12806,6 @@
|
||||||
"unenv": "^1.9.0"
|
"unenv": "^1.9.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/h3/node_modules/defu": {
|
|
||||||
"version": "6.1.4",
|
|
||||||
"resolved": "https://registry.npmjs.org/defu/-/defu-6.1.4.tgz",
|
|
||||||
"integrity": "sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==",
|
|
||||||
"dev": true,
|
|
||||||
"license": "MIT"
|
|
||||||
},
|
|
||||||
"node_modules/hard-source-webpack-plugin": {
|
"node_modules/hard-source-webpack-plugin": {
|
||||||
"version": "0.13.1",
|
"version": "0.13.1",
|
||||||
"resolved": "https://registry.npmjs.org/hard-source-webpack-plugin/-/hard-source-webpack-plugin-0.13.1.tgz",
|
"resolved": "https://registry.npmjs.org/hard-source-webpack-plugin/-/hard-source-webpack-plugin-0.13.1.tgz",
|
||||||
|
|
@ -13327,9 +13242,10 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/hookable": {
|
"node_modules/hookable": {
|
||||||
"version": "4.4.1",
|
"version": "5.5.3",
|
||||||
"resolved": "https://registry.npmjs.org/hookable/-/hookable-4.4.1.tgz",
|
"resolved": "https://registry.npmjs.org/hookable/-/hookable-5.5.3.tgz",
|
||||||
"integrity": "sha512-KWjZM8C7IVT2qne5HTXjM6R6VnRfjfRlf/oCnHd+yFxoHO1DzOl6B9LzV/VqGQK/IrFewq+EG+ePVrE9Tpc3fg==",
|
"integrity": "sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ==",
|
||||||
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/html-entities": {
|
"node_modules/html-entities": {
|
||||||
|
|
@ -14738,13 +14654,6 @@
|
||||||
"node": "^14.18.0 || >=16.10.0"
|
"node": "^14.18.0 || >=16.10.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/listhen/node_modules/defu": {
|
|
||||||
"version": "6.1.4",
|
|
||||||
"resolved": "https://registry.npmjs.org/defu/-/defu-6.1.4.tgz",
|
|
||||||
"integrity": "sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==",
|
|
||||||
"dev": true,
|
|
||||||
"license": "MIT"
|
|
||||||
},
|
|
||||||
"node_modules/listhen/node_modules/get-port-please": {
|
"node_modules/listhen/node_modules/get-port-please": {
|
||||||
"version": "3.1.2",
|
"version": "3.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/get-port-please/-/get-port-please-3.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/get-port-please/-/get-port-please-3.1.2.tgz",
|
||||||
|
|
@ -15915,13 +15824,6 @@
|
||||||
"node": ">=18"
|
"node": ">=18"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/mkdist/node_modules/defu": {
|
|
||||||
"version": "6.1.4",
|
|
||||||
"resolved": "https://registry.npmjs.org/defu/-/defu-6.1.4.tgz",
|
|
||||||
"integrity": "sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==",
|
|
||||||
"dev": true,
|
|
||||||
"license": "MIT"
|
|
||||||
},
|
|
||||||
"node_modules/mkdist/node_modules/esbuild": {
|
"node_modules/mkdist/node_modules/esbuild": {
|
||||||
"version": "0.23.1",
|
"version": "0.23.1",
|
||||||
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.23.1.tgz",
|
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.23.1.tgz",
|
||||||
|
|
@ -16619,13 +16521,6 @@
|
||||||
"node": "^14.18.0 || >=16.10.0"
|
"node": "^14.18.0 || >=16.10.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/nitropack/node_modules/defu": {
|
|
||||||
"version": "6.1.4",
|
|
||||||
"resolved": "https://registry.npmjs.org/defu/-/defu-6.1.4.tgz",
|
|
||||||
"integrity": "sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==",
|
|
||||||
"dev": true,
|
|
||||||
"license": "MIT"
|
|
||||||
},
|
|
||||||
"node_modules/nitropack/node_modules/esbuild": {
|
"node_modules/nitropack/node_modules/esbuild": {
|
||||||
"version": "0.20.2",
|
"version": "0.20.2",
|
||||||
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.20.2.tgz",
|
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.20.2.tgz",
|
||||||
|
|
@ -16730,13 +16625,6 @@
|
||||||
"url": "https://github.com/sponsors/sindresorhus"
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/nitropack/node_modules/hookable": {
|
|
||||||
"version": "5.5.3",
|
|
||||||
"resolved": "https://registry.npmjs.org/hookable/-/hookable-5.5.3.tgz",
|
|
||||||
"integrity": "sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ==",
|
|
||||||
"dev": true,
|
|
||||||
"license": "MIT"
|
|
||||||
},
|
|
||||||
"node_modules/nitropack/node_modules/mime": {
|
"node_modules/nitropack/node_modules/mime": {
|
||||||
"version": "4.0.4",
|
"version": "4.0.4",
|
||||||
"resolved": "https://registry.npmjs.org/mime/-/mime-4.0.4.tgz",
|
"resolved": "https://registry.npmjs.org/mime/-/mime-4.0.4.tgz",
|
||||||
|
|
@ -20427,11 +20315,6 @@
|
||||||
"destr": "^2.0.3"
|
"destr": "^2.0.3"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/rc9/node_modules/defu": {
|
|
||||||
"version": "6.1.4",
|
|
||||||
"resolved": "https://registry.npmjs.org/defu/-/defu-6.1.4.tgz",
|
|
||||||
"integrity": "sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg=="
|
|
||||||
},
|
|
||||||
"node_modules/read-cache": {
|
"node_modules/read-cache": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz",
|
||||||
|
|
@ -21242,12 +21125,6 @@
|
||||||
"defu": "^6.1.4"
|
"defu": "^6.1.4"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/serve-placeholder/node_modules/defu": {
|
|
||||||
"version": "6.1.4",
|
|
||||||
"resolved": "https://registry.npmjs.org/defu/-/defu-6.1.4.tgz",
|
|
||||||
"integrity": "sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==",
|
|
||||||
"license": "MIT"
|
|
||||||
},
|
|
||||||
"node_modules/serve-static": {
|
"node_modules/serve-static": {
|
||||||
"version": "1.15.0",
|
"version": "1.15.0",
|
||||||
"resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz",
|
"resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz",
|
||||||
|
|
@ -23449,13 +23326,6 @@
|
||||||
"node": "^14.18.0 || >=16.10.0"
|
"node": "^14.18.0 || >=16.10.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/unbuild/node_modules/defu": {
|
|
||||||
"version": "6.1.4",
|
|
||||||
"resolved": "https://registry.npmjs.org/defu/-/defu-6.1.4.tgz",
|
|
||||||
"integrity": "sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==",
|
|
||||||
"dev": true,
|
|
||||||
"license": "MIT"
|
|
||||||
},
|
|
||||||
"node_modules/unbuild/node_modules/esbuild": {
|
"node_modules/unbuild/node_modules/esbuild": {
|
||||||
"version": "0.19.12",
|
"version": "0.19.12",
|
||||||
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.19.12.tgz",
|
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.19.12.tgz",
|
||||||
|
|
@ -23515,13 +23385,6 @@
|
||||||
"url": "https://github.com/sponsors/sindresorhus"
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/unbuild/node_modules/hookable": {
|
|
||||||
"version": "5.5.3",
|
|
||||||
"resolved": "https://registry.npmjs.org/hookable/-/hookable-5.5.3.tgz",
|
|
||||||
"integrity": "sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ==",
|
|
||||||
"dev": true,
|
|
||||||
"license": "MIT"
|
|
||||||
},
|
|
||||||
"node_modules/unbuild/node_modules/pretty-bytes": {
|
"node_modules/unbuild/node_modules/pretty-bytes": {
|
||||||
"version": "6.1.1",
|
"version": "6.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-6.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-6.1.1.tgz",
|
||||||
|
|
@ -23681,13 +23544,6 @@
|
||||||
"node": "^14.18.0 || >=16.10.0"
|
"node": "^14.18.0 || >=16.10.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/unenv/node_modules/defu": {
|
|
||||||
"version": "6.1.4",
|
|
||||||
"resolved": "https://registry.npmjs.org/defu/-/defu-6.1.4.tgz",
|
|
||||||
"integrity": "sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==",
|
|
||||||
"dev": true,
|
|
||||||
"license": "MIT"
|
|
||||||
},
|
|
||||||
"node_modules/unenv/node_modules/mime": {
|
"node_modules/unenv/node_modules/mime": {
|
||||||
"version": "3.0.0",
|
"version": "3.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/mime/-/mime-3.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/mime/-/mime-3.0.0.tgz",
|
||||||
|
|
@ -23726,13 +23582,6 @@
|
||||||
"url": "https://github.com/sponsors/harlan-zw"
|
"url": "https://github.com/sponsors/harlan-zw"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/unhead/node_modules/hookable": {
|
|
||||||
"version": "5.5.3",
|
|
||||||
"resolved": "https://registry.npmjs.org/hookable/-/hookable-5.5.3.tgz",
|
|
||||||
"integrity": "sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ==",
|
|
||||||
"dev": true,
|
|
||||||
"license": "MIT"
|
|
||||||
},
|
|
||||||
"node_modules/unicode-canonical-property-names-ecmascript": {
|
"node_modules/unicode-canonical-property-names-ecmascript": {
|
||||||
"version": "2.0.0",
|
"version": "2.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.0.tgz",
|
||||||
|
|
@ -24108,13 +23957,6 @@
|
||||||
"untyped": "dist/cli.mjs"
|
"untyped": "dist/cli.mjs"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/untyped/node_modules/defu": {
|
|
||||||
"version": "6.1.4",
|
|
||||||
"resolved": "https://registry.npmjs.org/defu/-/defu-6.1.4.tgz",
|
|
||||||
"integrity": "sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==",
|
|
||||||
"dev": true,
|
|
||||||
"license": "MIT"
|
|
||||||
},
|
|
||||||
"node_modules/untyped/node_modules/scule": {
|
"node_modules/untyped/node_modules/scule": {
|
||||||
"version": "1.3.0",
|
"version": "1.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/scule/-/scule-1.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/scule/-/scule-1.3.0.tgz",
|
||||||
|
|
|
||||||
|
|
@ -5,11 +5,11 @@
|
||||||
"description": "Self-hosted audiobook and podcast client",
|
"description": "Self-hosted audiobook and podcast client",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "nuxt2",
|
"dev": "nuxi dev",
|
||||||
"dev2": "nuxt2 --hostname localhost --port 1337",
|
"dev2": "nuxi dev --hostname localhost --port 1337",
|
||||||
"build": "nuxt2 build",
|
"build": "nuxi build",
|
||||||
"start": "nuxt2 start",
|
"start": "nuxi preview",
|
||||||
"generate": "nuxt2 generate",
|
"generate": "nuxi generate",
|
||||||
"test": "npm run compile-tailwind && cypress run --component --browser chrome",
|
"test": "npm run compile-tailwind && cypress run --component --browser chrome",
|
||||||
"test-visually": "npm run compile-tailwind && cypress open --component --browser chrome",
|
"test-visually": "npm run compile-tailwind && cypress open --component --browser chrome",
|
||||||
"compile-tailwind": "tailwindcss -i ./assets/tailwind.css -o ./cypress/support/tailwind.compiled.css"
|
"compile-tailwind": "tailwindcss -i ./assets/tailwind.css -o ./cypress/support/tailwind.compiled.css"
|
||||||
|
|
@ -38,6 +38,8 @@
|
||||||
"@nuxt/bridge": "^3.3.1",
|
"@nuxt/bridge": "^3.3.1",
|
||||||
"@nuxtjs/pwa": "^3.3.5",
|
"@nuxtjs/pwa": "^3.3.5",
|
||||||
"autoprefixer": "^10.4.7",
|
"autoprefixer": "^10.4.7",
|
||||||
|
"defu": "^6.1.4",
|
||||||
|
"hookable": "^5.5.3",
|
||||||
"nuxi": "^3.13.2",
|
"nuxi": "^3.13.2",
|
||||||
"postcss": "^8.3.6",
|
"postcss": "^8.3.6",
|
||||||
"tailwindcss": "^3.4.1"
|
"tailwindcss": "^3.4.1"
|
||||||
|
|
|
||||||
302
client/static/libs/epubjs-patch/annotations.js
Normal file
302
client/static/libs/epubjs-patch/annotations.js
Normal file
|
|
@ -0,0 +1,302 @@
|
||||||
|
import { EventEmitter } from "events";
|
||||||
|
import EpubCFI from "./epubcfi";
|
||||||
|
import { EVENTS } from "./utils/constants";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 = {};
|
||||||
|
|
||||||
|
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
|
||||||
|
});
|
||||||
|
|
||||||
|
this._annotations[hash] = annotation;
|
||||||
|
|
||||||
|
if (sectionIndex in this._annotationsBySectionIndex) {
|
||||||
|
this._annotationsBySectionIndex[sectionIndex].push(hash);
|
||||||
|
} else {
|
||||||
|
this._annotationsBySectionIndex[sectionIndex] = [hash];
|
||||||
|
}
|
||||||
|
|
||||||
|
let views = this.rendition.views();
|
||||||
|
|
||||||
|
views.forEach( (view) => {
|
||||||
|
if (annotation.sectionIndex === view.index) {
|
||||||
|
annotation.attach(view);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
|
if (hash in this._annotations) {
|
||||||
|
let annotation = this._annotations[hash];
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
delete this._annotations[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];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* iterate over annotations in the store
|
||||||
|
*/
|
||||||
|
each () {
|
||||||
|
return this._annotations.forEach.apply(this._annotations, arguments);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 () {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Annotation object
|
||||||
|
* @class
|
||||||
|
* @param {object} options
|
||||||
|
* @param {string} options.type Type of annotation to add: "highlight", "underline", "mark"
|
||||||
|
* @param {EpubCFI} options.cfiRange EpubCFI range to attach annotation to
|
||||||
|
* @param {object} options.data Data to assign to annotation
|
||||||
|
* @param {int} options.sectionIndex Index in the Spine of the Section annotation belongs to
|
||||||
|
* @param {function} [options.cb] Callback after annotation is clicked
|
||||||
|
* @param {string} className CSS class to assign to annotation
|
||||||
|
* @param {object} styles CSS styles to assign to annotation
|
||||||
|
* @returns {Annotation} annotation
|
||||||
|
*/
|
||||||
|
class Annotation extends EventEmitter {
|
||||||
|
|
||||||
|
constructor ({
|
||||||
|
type,
|
||||||
|
cfiRange,
|
||||||
|
data,
|
||||||
|
sectionIndex,
|
||||||
|
cb,
|
||||||
|
className,
|
||||||
|
styles
|
||||||
|
}) {
|
||||||
|
super();
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
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 () {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
Object.assign(Annotation.prototype, EventEmitter.prototype);
|
||||||
|
|
||||||
|
|
||||||
|
export default Annotations
|
||||||
255
client/static/libs/epubjs-patch/archive.js
Normal file
255
client/static/libs/epubjs-patch/archive.js
Normal file
|
|
@ -0,0 +1,255 @@
|
||||||
|
import {defer, isXml, parse} from "./utils/core";
|
||||||
|
import request from "./utils/request";
|
||||||
|
import mime from "./utils/mime";
|
||||||
|
import Path from "./utils/path";
|
||||||
|
import JSZip from "jszip/dist/jszip";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles Unzipping a requesting files from an Epub Archive
|
||||||
|
* @class
|
||||||
|
*/
|
||||||
|
class Archive {
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this.zip = undefined;
|
||||||
|
this.urlCache = {};
|
||||||
|
|
||||||
|
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});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load and Open an archive
|
||||||
|
* @param {string} zipUrl
|
||||||
|
* @param {boolean} [isBase64] tells JSZip if the input data is base64 encoded
|
||||||
|
* @return {Promise} zipfile
|
||||||
|
*/
|
||||||
|
openUrl(zipUrl, isBase64){
|
||||||
|
return request(zipUrl, "binary")
|
||||||
|
.then(function(data){
|
||||||
|
return this.zip.loadAsync(data, {"base64": isBase64});
|
||||||
|
}.bind(this));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Request 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 isn't set, determine it from the file extension
|
||||||
|
if(!type) {
|
||||||
|
type = path.extension;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(type == "blob"){
|
||||||
|
response = this.getBlob(url);
|
||||||
|
} else {
|
||||||
|
response = this.getText(url);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (response) {
|
||||||
|
response.then(function (r) {
|
||||||
|
let result = this.handleResponse(r, type);
|
||||||
|
deferred.resolve(result);
|
||||||
|
}.bind(this));
|
||||||
|
} else {
|
||||||
|
deferred.reject({
|
||||||
|
message : "File not found in the epub: " + url,
|
||||||
|
stack : new Error().stack
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return deferred.promise;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle the response from request
|
||||||
|
* @private
|
||||||
|
* @param {any} response
|
||||||
|
* @param {string} [type]
|
||||||
|
* @return {any} the parsed result
|
||||||
|
*/
|
||||||
|
handleResponse(response, type){
|
||||||
|
var r;
|
||||||
|
|
||||||
|
if(type == "json") {
|
||||||
|
r = JSON.parse(response);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
if(isXml(type)) {
|
||||||
|
r = parse(response, "text/xml");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
if(type == "xhtml") {
|
||||||
|
r = parse(response, "application/xhtml+xml");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
if(type == "html" || type == "htm") {
|
||||||
|
r = parse(response, "text/html");
|
||||||
|
} else {
|
||||||
|
r = response;
|
||||||
|
}
|
||||||
|
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a Blob from Archive by Url
|
||||||
|
* @param {string} url
|
||||||
|
* @param {string} [mimeType]
|
||||||
|
* @return {Blob}
|
||||||
|
*/
|
||||||
|
getBlob(url, mimeType){
|
||||||
|
var decodededUrl = window.decodeURIComponent(url.substr(1)); // Remove first slash
|
||||||
|
var entry = this.zip.file(decodededUrl);
|
||||||
|
|
||||||
|
if(entry) {
|
||||||
|
mimeType = mimeType || mime.lookup(entry.name);
|
||||||
|
return entry.async("uint8array").then(function(uint8array) {
|
||||||
|
return new Blob([uint8array], {type : mimeType});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get Text from Archive by Url
|
||||||
|
* @param {string} url
|
||||||
|
* @param {string} [encoding]
|
||||||
|
* @return {string}
|
||||||
|
*/
|
||||||
|
getText(url, encoding){
|
||||||
|
var decodededUrl = window.decodeURIComponent(url.substr(1)); // Remove first slash
|
||||||
|
var entry = this.zip.file(decodededUrl);
|
||||||
|
|
||||||
|
if(entry) {
|
||||||
|
return entry.async("string").then(function(text) {
|
||||||
|
return text;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a base64 encoded result from Archive by Url
|
||||||
|
* @param {string} url
|
||||||
|
* @param {string} [mimeType]
|
||||||
|
* @return {string} base64 encoded
|
||||||
|
*/
|
||||||
|
getBase64(url, mimeType){
|
||||||
|
var decodededUrl = window.decodeURIComponent(url.substr(1)); // Remove first slash
|
||||||
|
var entry = this.zip.file(decodededUrl);
|
||||||
|
|
||||||
|
if(entry) {
|
||||||
|
mimeType = mimeType || mime.lookup(entry.name);
|
||||||
|
return entry.async("base64").then(function(data) {
|
||||||
|
return "data:" + mimeType + ";base64," + data;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a Url from an unarchived item
|
||||||
|
* @param {string} url
|
||||||
|
* @param {object} [options.base64] use base64 encoding or blob url
|
||||||
|
* @return {Promise} url promise with Url string
|
||||||
|
*/
|
||||||
|
createUrl(url, options){
|
||||||
|
var deferred = new defer();
|
||||||
|
var _URL = window.URL || window.webkitURL || window.mozURL;
|
||||||
|
var tempUrl;
|
||||||
|
var response;
|
||||||
|
var useBase64 = options && options.base64;
|
||||||
|
|
||||||
|
if(url in this.urlCache) {
|
||||||
|
deferred.resolve(this.urlCache[url]);
|
||||||
|
return deferred.promise;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (useBase64) {
|
||||||
|
response = this.getBase64(url);
|
||||||
|
|
||||||
|
if (response) {
|
||||||
|
response.then(function(tempUrl) {
|
||||||
|
|
||||||
|
this.urlCache[url] = tempUrl;
|
||||||
|
deferred.resolve(tempUrl);
|
||||||
|
|
||||||
|
}.bind(this));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
|
||||||
|
response = this.getBlob(url);
|
||||||
|
|
||||||
|
if (response) {
|
||||||
|
response.then(function(blob) {
|
||||||
|
|
||||||
|
tempUrl = _URL.createObjectURL(blob);
|
||||||
|
this.urlCache[url] = tempUrl;
|
||||||
|
deferred.resolve(tempUrl);
|
||||||
|
|
||||||
|
}.bind(this));
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if (!response) {
|
||||||
|
deferred.reject({
|
||||||
|
message : "File not found in the epub: " + url,
|
||||||
|
stack : new Error().stack
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return deferred.promise;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Revoke Temp Url for a 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 = {};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Archive;
|
||||||
769
client/static/libs/epubjs-patch/book.js
Normal file
769
client/static/libs/epubjs-patch/book.js
Normal file
|
|
@ -0,0 +1,769 @@
|
||||||
|
import { EventEmitter } from "events";
|
||||||
|
import {extend, defer} from "./utils/core";
|
||||||
|
import Url from "./utils/url";
|
||||||
|
import Path from "./utils/path";
|
||||||
|
import Spine from "./spine";
|
||||||
|
import Locations from "./locations";
|
||||||
|
import Container from "./container";
|
||||||
|
import Packaging from "./packaging";
|
||||||
|
import Navigation from "./navigation";
|
||||||
|
import Resources from "./resources";
|
||||||
|
import PageList from "./pagelist";
|
||||||
|
import Rendition from "./rendition";
|
||||||
|
import Archive from "./archive";
|
||||||
|
import request from "./utils/request";
|
||||||
|
import EpubCFI from "./epubcfi";
|
||||||
|
import Store from "./store";
|
||||||
|
import DisplayOptions from "./displayoptions";
|
||||||
|
import { EPUBJS_VERSION, EVENTS } from "./utils/constants";
|
||||||
|
|
||||||
|
const CONTAINER_PATH = "META-INF/container.xml";
|
||||||
|
const IBOOKS_DISPLAY_OPTIONS_PATH = "META-INF/com.apple.ibooks.display-options.xml";
|
||||||
|
|
||||||
|
const INPUT_TYPE = {
|
||||||
|
BINARY: "binary",
|
||||||
|
BASE64: "base64",
|
||||||
|
EPUB: "epub",
|
||||||
|
OPF: "opf",
|
||||||
|
MANIFEST: "json",
|
||||||
|
DIRECTORY: "directory"
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An Epub representation with methods for the loading, parsing and manipulation
|
||||||
|
* of its contents.
|
||||||
|
* @class
|
||||||
|
* @param {string} [url]
|
||||||
|
* @param {object} [options]
|
||||||
|
* @param {method} [options.requestMethod] a request function to use instead of the default
|
||||||
|
* @param {boolean} [options.requestCredentials=undefined] send the xhr request withCredentials
|
||||||
|
* @param {object} [options.requestHeaders=undefined] send the xhr request headers
|
||||||
|
* @param {string} [options.encoding=binary] optional to pass 'binary' or base64' for archived Epubs
|
||||||
|
* @param {string} [options.replacements=none] use base64, blobUrl, or none for replacing assets in archived Epubs
|
||||||
|
* @param {method} [options.canonical] optional function to determine canonical urls for a path
|
||||||
|
* @param {string} [options.openAs] optional string to determine the input type
|
||||||
|
* @param {string} [options.store=false] cache the contents in local storage, value should be the name of the reader
|
||||||
|
* @returns {Book}
|
||||||
|
* @example new Book("/path/to/book.epub", {})
|
||||||
|
* @example new Book({ replacements: "blobUrl" })
|
||||||
|
*/
|
||||||
|
class Book extends EventEmitter {
|
||||||
|
constructor(url, options) {
|
||||||
|
super();
|
||||||
|
// Allow passing just options to the Book
|
||||||
|
if (typeof(options) === "undefined" &&
|
||||||
|
typeof(url) !== "string" &&
|
||||||
|
url instanceof Blob === false &&
|
||||||
|
url instanceof ArrayBuffer === false) {
|
||||||
|
options = url;
|
||||||
|
url = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.settings = extend(this.settings || {}, {
|
||||||
|
requestMethod: undefined,
|
||||||
|
requestCredentials: undefined,
|
||||||
|
requestHeaders: undefined,
|
||||||
|
encoding: undefined,
|
||||||
|
replacements: undefined,
|
||||||
|
canonical: undefined,
|
||||||
|
openAs: undefined,
|
||||||
|
store: undefined
|
||||||
|
});
|
||||||
|
|
||||||
|
extend(this.settings, options);
|
||||||
|
|
||||||
|
|
||||||
|
// Promises
|
||||||
|
this.opening = new defer();
|
||||||
|
/**
|
||||||
|
* @member {promise} opened returns after the book is loaded
|
||||||
|
* @memberof Book
|
||||||
|
*/
|
||||||
|
this.opened = this.opening.promise;
|
||||||
|
this.isOpen = false;
|
||||||
|
|
||||||
|
this.loading = {
|
||||||
|
manifest: new defer(),
|
||||||
|
spine: new defer(),
|
||||||
|
metadata: new defer(),
|
||||||
|
cover: new defer(),
|
||||||
|
navigation: new defer(),
|
||||||
|
pageList: new defer(),
|
||||||
|
resources: new defer(),
|
||||||
|
displayOptions: new defer()
|
||||||
|
};
|
||||||
|
|
||||||
|
this.loaded = {
|
||||||
|
manifest: this.loading.manifest.promise,
|
||||||
|
spine: this.loading.spine.promise,
|
||||||
|
metadata: this.loading.metadata.promise,
|
||||||
|
cover: this.loading.cover.promise,
|
||||||
|
navigation: this.loading.navigation.promise,
|
||||||
|
pageList: this.loading.pageList.promise,
|
||||||
|
resources: this.loading.resources.promise,
|
||||||
|
displayOptions: this.loading.displayOptions.promise
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @member {promise} ready returns after the book is loaded and parsed
|
||||||
|
* @memberof Book
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
this.ready = Promise.all([
|
||||||
|
this.loaded.manifest,
|
||||||
|
this.loaded.spine,
|
||||||
|
this.loaded.metadata,
|
||||||
|
this.loaded.cover,
|
||||||
|
this.loaded.navigation,
|
||||||
|
this.loaded.resources,
|
||||||
|
this.loaded.displayOptions
|
||||||
|
]);
|
||||||
|
|
||||||
|
|
||||||
|
// Queue for methods used before opening
|
||||||
|
this.isRendered = false;
|
||||||
|
// this._q = queue(this);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @member {method} request
|
||||||
|
* @memberof Book
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
this.request = this.settings.requestMethod || request;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @member {Spine} spine
|
||||||
|
* @memberof Book
|
||||||
|
*/
|
||||||
|
this.spine = new Spine();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @member {Locations} locations
|
||||||
|
* @memberof Book
|
||||||
|
*/
|
||||||
|
this.locations = new Locations(this.spine, this.load.bind(this));
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @member {Navigation} navigation
|
||||||
|
* @memberof Book
|
||||||
|
*/
|
||||||
|
this.navigation = undefined;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @member {PageList} pagelist
|
||||||
|
* @memberof Book
|
||||||
|
*/
|
||||||
|
this.pageList = undefined;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @member {Url} url
|
||||||
|
* @memberof Book
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
this.url = undefined;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @member {Path} path
|
||||||
|
* @memberof Book
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
this.path = undefined;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @member {boolean} archived
|
||||||
|
* @memberof Book
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
this.archived = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @member {Archive} archive
|
||||||
|
* @memberof Book
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
this.archive = undefined;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @member {Store} storage
|
||||||
|
* @memberof Book
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
this.storage = undefined;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @member {Resources} resources
|
||||||
|
* @memberof Book
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
this.resources = undefined;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @member {Rendition} rendition
|
||||||
|
* @memberof Book
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
this.rendition = undefined;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @member {Container} container
|
||||||
|
* @memberof Book
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
this.container = undefined;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @member {Packaging} packaging
|
||||||
|
* @memberof Book
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
this.packaging = undefined;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @member {DisplayOptions} displayOptions
|
||||||
|
* @memberof DisplayOptions
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
this.displayOptions = undefined;
|
||||||
|
|
||||||
|
// this.toc = undefined;
|
||||||
|
if (this.settings.store) {
|
||||||
|
this.store(this.settings.store);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(url) {
|
||||||
|
this.open(url, this.settings.openAs).catch((error) => {
|
||||||
|
var err = new Error("Cannot load book at "+ url );
|
||||||
|
this.emit(EVENTS.BOOK.OPEN_FAILED, err);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Open a epub or url
|
||||||
|
* @param {string | ArrayBuffer} input Url, Path or ArrayBuffer
|
||||||
|
* @param {string} [what="binary", "base64", "epub", "opf", "json", "directory"] force opening as a certain type
|
||||||
|
* @returns {Promise} of when the book has been loaded
|
||||||
|
* @example book.open("/path/to/book.epub")
|
||||||
|
*/
|
||||||
|
open(input, what) {
|
||||||
|
var opening;
|
||||||
|
var type = what || this.determineType(input);
|
||||||
|
|
||||||
|
if (type === INPUT_TYPE.BINARY) {
|
||||||
|
this.archived = true;
|
||||||
|
this.url = new Url("/", "");
|
||||||
|
opening = this.openEpub(input);
|
||||||
|
} else if (type === INPUT_TYPE.BASE64) {
|
||||||
|
this.archived = true;
|
||||||
|
this.url = new Url("/", "");
|
||||||
|
opening = this.openEpub(input, type);
|
||||||
|
} else if (type === INPUT_TYPE.EPUB) {
|
||||||
|
this.archived = true;
|
||||||
|
this.url = new Url("/", "");
|
||||||
|
opening = this.request(input, "binary", this.settings.requestCredentials, this.settings.requestHeaders)
|
||||||
|
.then(this.openEpub.bind(this));
|
||||||
|
} else if(type == INPUT_TYPE.OPF) {
|
||||||
|
this.url = new Url(input);
|
||||||
|
opening = this.openPackaging(this.url.Path.toString());
|
||||||
|
} else if(type == INPUT_TYPE.MANIFEST) {
|
||||||
|
this.url = new Url(input);
|
||||||
|
opening = this.openManifest(this.url.Path.toString());
|
||||||
|
} else {
|
||||||
|
this.url = new Url(input);
|
||||||
|
opening = this.openContainer(CONTAINER_PATH)
|
||||||
|
.then(this.openPackaging.bind(this));
|
||||||
|
}
|
||||||
|
|
||||||
|
return opening;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Open an archived epub
|
||||||
|
* @private
|
||||||
|
* @param {binary} data
|
||||||
|
* @param {string} [encoding]
|
||||||
|
* @return {Promise}
|
||||||
|
*/
|
||||||
|
openEpub(data, encoding) {
|
||||||
|
return this.unarchive(data, encoding || this.settings.encoding)
|
||||||
|
.then(() => {
|
||||||
|
return this.openContainer(CONTAINER_PATH);
|
||||||
|
})
|
||||||
|
.then((packagePath) => {
|
||||||
|
return this.openPackaging(packagePath);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Open the epub container
|
||||||
|
* @private
|
||||||
|
* @param {string} url
|
||||||
|
* @return {string} packagePath
|
||||||
|
*/
|
||||||
|
openContainer(url) {
|
||||||
|
return this.load(url)
|
||||||
|
.then((xml) => {
|
||||||
|
this.container = new Container(xml);
|
||||||
|
return this.resolve(this.container.packagePath);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Open the Open Packaging Format Xml
|
||||||
|
* @private
|
||||||
|
* @param {string} url
|
||||||
|
* @return {Promise}
|
||||||
|
*/
|
||||||
|
openPackaging(url) {
|
||||||
|
this.path = new Path(url);
|
||||||
|
return this.load(url)
|
||||||
|
.then((xml) => {
|
||||||
|
this.packaging = new Packaging(xml);
|
||||||
|
return this.unpack(this.packaging);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Open the manifest JSON
|
||||||
|
* @private
|
||||||
|
* @param {string} url
|
||||||
|
* @return {Promise}
|
||||||
|
*/
|
||||||
|
openManifest(url) {
|
||||||
|
this.path = new Path(url);
|
||||||
|
return this.load(url)
|
||||||
|
.then((json) => {
|
||||||
|
this.packaging = new Packaging();
|
||||||
|
this.packaging.load(json);
|
||||||
|
return this.unpack(this.packaging);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load a resource from the Book
|
||||||
|
* @param {string} path path to the resource to load
|
||||||
|
* @return {Promise} returns a promise with the requested resource
|
||||||
|
*/
|
||||||
|
load(path) {
|
||||||
|
var resolved = this.resolve(path);
|
||||||
|
if(this.archived) {
|
||||||
|
return this.archive.request(resolved);
|
||||||
|
} else {
|
||||||
|
return this.request(resolved, null, this.settings.requestCredentials, this.settings.requestHeaders);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resolve a path to it's absolute position in the Book
|
||||||
|
* @param {string} path
|
||||||
|
* @param {boolean} [absolute] force resolving the full URL
|
||||||
|
* @return {string} the resolved path string
|
||||||
|
*/
|
||||||
|
resolve(path, absolute) {
|
||||||
|
if (!path) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var resolved = path;
|
||||||
|
var isAbsolute = (path.indexOf("://") > -1);
|
||||||
|
|
||||||
|
if (isAbsolute) {
|
||||||
|
return path;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.path) {
|
||||||
|
resolved = this.path.resolve(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(absolute != false && this.url) {
|
||||||
|
resolved = this.url.resolve(resolved);
|
||||||
|
}
|
||||||
|
|
||||||
|
return resolved;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a canonical link to a path
|
||||||
|
* @param {string} path
|
||||||
|
* @return {string} the canonical path string
|
||||||
|
*/
|
||||||
|
canonical(path) {
|
||||||
|
var url = path;
|
||||||
|
|
||||||
|
if (!path) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.settings.canonical) {
|
||||||
|
url = this.settings.canonical(path);
|
||||||
|
} else {
|
||||||
|
url = this.resolve(path, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
return url;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determine the type of they input passed to open
|
||||||
|
* @private
|
||||||
|
* @param {string} input
|
||||||
|
* @return {string} binary | directory | epub | opf
|
||||||
|
*/
|
||||||
|
determineType(input) {
|
||||||
|
var url;
|
||||||
|
var path;
|
||||||
|
var extension;
|
||||||
|
|
||||||
|
if (this.settings.encoding === "base64") {
|
||||||
|
return INPUT_TYPE.BASE64;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(typeof(input) != "string") {
|
||||||
|
return INPUT_TYPE.BINARY;
|
||||||
|
}
|
||||||
|
|
||||||
|
url = new Url(input);
|
||||||
|
path = url.path();
|
||||||
|
extension = path.extension;
|
||||||
|
|
||||||
|
// If there's a search string, remove it before determining type
|
||||||
|
if (extension) {
|
||||||
|
extension = extension.replace(/\?.*$/, "");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!extension) {
|
||||||
|
return INPUT_TYPE.DIRECTORY;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(extension === "epub"){
|
||||||
|
return INPUT_TYPE.EPUB;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(extension === "opf"){
|
||||||
|
return INPUT_TYPE.OPF;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(extension === "json"){
|
||||||
|
return INPUT_TYPE.MANIFEST;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* unpack the contents of the Books packaging
|
||||||
|
* @private
|
||||||
|
* @param {Packaging} packaging object
|
||||||
|
*/
|
||||||
|
unpack(packaging) {
|
||||||
|
this.package = packaging; //TODO: deprecated this
|
||||||
|
|
||||||
|
if (this.packaging.metadata.layout === "") {
|
||||||
|
// rendition:layout not set - check display options if book is pre-paginated
|
||||||
|
this.load(this.url.resolve(IBOOKS_DISPLAY_OPTIONS_PATH)).then((xml) => {
|
||||||
|
this.displayOptions = new DisplayOptions(xml);
|
||||||
|
this.loading.displayOptions.resolve(this.displayOptions);
|
||||||
|
}).catch((err) => {
|
||||||
|
this.displayOptions = new DisplayOptions();
|
||||||
|
this.loading.displayOptions.resolve(this.displayOptions);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
this.displayOptions = new DisplayOptions();
|
||||||
|
this.loading.displayOptions.resolve(this.displayOptions);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.spine.unpack(this.packaging, this.resolve.bind(this), this.canonical.bind(this));
|
||||||
|
|
||||||
|
this.resources = new Resources(this.packaging.manifest, {
|
||||||
|
archive: this.archive,
|
||||||
|
resolver: this.resolve.bind(this),
|
||||||
|
request: this.request.bind(this),
|
||||||
|
replacements: this.settings.replacements || (this.archived ? "blobUrl" : "base64")
|
||||||
|
});
|
||||||
|
|
||||||
|
this.loadNavigation(this.packaging).then(() => {
|
||||||
|
// this.toc = this.navigation.toc;
|
||||||
|
this.loading.navigation.resolve(this.navigation);
|
||||||
|
});
|
||||||
|
|
||||||
|
if (this.packaging.coverPath) {
|
||||||
|
this.cover = this.resolve(this.packaging.coverPath);
|
||||||
|
}
|
||||||
|
// Resolve promises
|
||||||
|
this.loading.manifest.resolve(this.packaging.manifest);
|
||||||
|
this.loading.metadata.resolve(this.packaging.metadata);
|
||||||
|
this.loading.spine.resolve(this.spine);
|
||||||
|
this.loading.cover.resolve(this.cover);
|
||||||
|
this.loading.resources.resolve(this.resources);
|
||||||
|
this.loading.pageList.resolve(this.pageList);
|
||||||
|
|
||||||
|
this.isOpen = true;
|
||||||
|
|
||||||
|
if(this.archived || this.settings.replacements && this.settings.replacements != "none") {
|
||||||
|
this.replacements().then(() => {
|
||||||
|
this.loaded.displayOptions.then(() => {
|
||||||
|
this.opening.resolve(this);
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
console.error(err);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// Resolve book opened promise
|
||||||
|
this.loaded.displayOptions.then(() => {
|
||||||
|
this.opening.resolve(this);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load Navigation and PageList from package
|
||||||
|
* @private
|
||||||
|
* @param {Packaging} packaging
|
||||||
|
*/
|
||||||
|
loadNavigation(packaging) {
|
||||||
|
let navPath = packaging.navPath || packaging.ncxPath;
|
||||||
|
let toc = packaging.toc;
|
||||||
|
|
||||||
|
// From json manifest
|
||||||
|
if (toc) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
this.navigation = new Navigation(toc);
|
||||||
|
|
||||||
|
if (packaging.pageList) {
|
||||||
|
this.pageList = new PageList(packaging.pageList); // TODO: handle page lists from Manifest
|
||||||
|
}
|
||||||
|
|
||||||
|
resolve(this.navigation);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!navPath) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
this.navigation = new Navigation();
|
||||||
|
this.pageList = new PageList();
|
||||||
|
|
||||||
|
resolve(this.navigation);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.load(navPath, "xml")
|
||||||
|
.then((xml) => {
|
||||||
|
this.navigation = new Navigation(xml);
|
||||||
|
this.pageList = new PageList(xml);
|
||||||
|
return this.navigation;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets a Section of the Book from the Spine
|
||||||
|
* Alias for `book.spine.get`
|
||||||
|
* @param {string} target
|
||||||
|
* @return {Section}
|
||||||
|
*/
|
||||||
|
section(target) {
|
||||||
|
return this.spine.get(target);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sugar to render a book to an element
|
||||||
|
* @param {element | string} element element or string to add a rendition to
|
||||||
|
* @param {object} [options]
|
||||||
|
* @return {Rendition}
|
||||||
|
*/
|
||||||
|
renderTo(element, options) {
|
||||||
|
this.rendition = new Rendition(this, options);
|
||||||
|
this.rendition.attachTo(element);
|
||||||
|
|
||||||
|
return this.rendition;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set if request should use withCredentials
|
||||||
|
* @param {boolean} credentials
|
||||||
|
*/
|
||||||
|
setRequestCredentials(credentials) {
|
||||||
|
this.settings.requestCredentials = credentials;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set headers request should use
|
||||||
|
* @param {object} headers
|
||||||
|
*/
|
||||||
|
setRequestHeaders(headers) {
|
||||||
|
this.settings.requestHeaders = headers;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unarchive a zipped epub
|
||||||
|
* @private
|
||||||
|
* @param {binary} input epub data
|
||||||
|
* @param {string} [encoding]
|
||||||
|
* @return {Archive}
|
||||||
|
*/
|
||||||
|
unarchive(input, encoding) {
|
||||||
|
this.archive = new Archive();
|
||||||
|
return this.archive.open(input, encoding);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Store the epubs contents
|
||||||
|
* @private
|
||||||
|
* @param {binary} input epub data
|
||||||
|
* @param {string} [encoding]
|
||||||
|
* @return {Store}
|
||||||
|
*/
|
||||||
|
store(name) {
|
||||||
|
// Use "blobUrl" or "base64" for replacements
|
||||||
|
let replacementsSetting = this.settings.replacements && this.settings.replacements !== "none";
|
||||||
|
// Save original url
|
||||||
|
let originalUrl = this.url;
|
||||||
|
// Save original request method
|
||||||
|
let requester = this.settings.requestMethod || request.bind(this);
|
||||||
|
// Create new Store
|
||||||
|
this.storage = new Store(name, requester, this.resolve.bind(this));
|
||||||
|
// Replace request method to go through store
|
||||||
|
this.request = this.storage.request.bind(this.storage);
|
||||||
|
|
||||||
|
this.opened.then(() => {
|
||||||
|
if (this.archived) {
|
||||||
|
this.storage.requester = this.archive.request.bind(this.archive);
|
||||||
|
}
|
||||||
|
// Substitute hook
|
||||||
|
let substituteResources = (output, section) => {
|
||||||
|
section.output = this.resources.substitute(output, section.url);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Set to use replacements
|
||||||
|
this.resources.settings.replacements = replacementsSetting || "blobUrl";
|
||||||
|
// Create replacement urls
|
||||||
|
this.resources.replacements().
|
||||||
|
then(() => {
|
||||||
|
return this.resources.replaceCss();
|
||||||
|
});
|
||||||
|
|
||||||
|
this.storage.on("offline", () => {
|
||||||
|
// Remove url to use relative resolving for hrefs
|
||||||
|
this.url = new Url("/", "");
|
||||||
|
// Add hook to replace resources in contents
|
||||||
|
this.spine.hooks.serialize.register(substituteResources);
|
||||||
|
});
|
||||||
|
|
||||||
|
this.storage.on("online", () => {
|
||||||
|
// Restore original url
|
||||||
|
this.url = originalUrl;
|
||||||
|
// Remove hook
|
||||||
|
this.spine.hooks.serialize.deregister(substituteResources);
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
return this.storage;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the cover url
|
||||||
|
* @return {Promise<?string>} coverUrl
|
||||||
|
*/
|
||||||
|
coverUrl() {
|
||||||
|
return this.loaded.cover.then(() => {
|
||||||
|
if (!this.cover) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.archived) {
|
||||||
|
return this.archive.createUrl(this.cover);
|
||||||
|
} else {
|
||||||
|
return this.cover;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load replacement urls
|
||||||
|
* @private
|
||||||
|
* @return {Promise} completed loading urls
|
||||||
|
*/
|
||||||
|
replacements() {
|
||||||
|
this.spine.hooks.serialize.register((output, section) => {
|
||||||
|
section.output = this.resources.substitute(output, section.url);
|
||||||
|
});
|
||||||
|
|
||||||
|
return this.resources.replacements().
|
||||||
|
then(() => {
|
||||||
|
return this.resources.replaceCss();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find a DOM Range for a given CFI Range
|
||||||
|
* @param {EpubCFI} cfiRange a epub cfi range
|
||||||
|
* @return {Promise}
|
||||||
|
*/
|
||||||
|
getRange(cfiRange) {
|
||||||
|
var cfi = new EpubCFI(cfiRange);
|
||||||
|
var item = this.spine.get(cfi.spinePos);
|
||||||
|
var _request = this.load.bind(this);
|
||||||
|
if (!item) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
reject("CFI could not be found");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return item.load(_request).then(function (contents) {
|
||||||
|
var range = cfi.toRange(item.document);
|
||||||
|
return range;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates the Book Key using the identifier in the manifest or other string provided
|
||||||
|
* @param {string} [identifier] to use instead of metadata identifier
|
||||||
|
* @return {string} key
|
||||||
|
*/
|
||||||
|
key(identifier) {
|
||||||
|
var ident = identifier || this.packaging.metadata.identifier || this.url.filename;
|
||||||
|
return `epubjs:${EPUBJS_VERSION}:${ident}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Destroy the Book and all associated objects
|
||||||
|
*/
|
||||||
|
destroy() {
|
||||||
|
this.opened = undefined;
|
||||||
|
this.loading = undefined;
|
||||||
|
this.loaded = undefined;
|
||||||
|
this.ready = undefined;
|
||||||
|
|
||||||
|
this.isOpen = false;
|
||||||
|
this.isRendered = false;
|
||||||
|
|
||||||
|
this.spine && this.spine.destroy();
|
||||||
|
this.locations && this.locations.destroy();
|
||||||
|
this.pageList && this.pageList.destroy();
|
||||||
|
this.archive && this.archive.destroy();
|
||||||
|
this.resources && this.resources.destroy();
|
||||||
|
this.container && this.container.destroy();
|
||||||
|
this.packaging && this.packaging.destroy();
|
||||||
|
this.rendition && this.rendition.destroy();
|
||||||
|
this.displayOptions && this.displayOptions.destroy();
|
||||||
|
|
||||||
|
this.spine = undefined;
|
||||||
|
this.locations = undefined;
|
||||||
|
this.pageList = undefined;
|
||||||
|
this.archive = undefined;
|
||||||
|
this.resources = undefined;
|
||||||
|
this.container = undefined;
|
||||||
|
this.packaging = undefined;
|
||||||
|
this.rendition = undefined;
|
||||||
|
|
||||||
|
this.navigation = undefined;
|
||||||
|
this.url = undefined;
|
||||||
|
this.path = undefined;
|
||||||
|
this.archived = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
//-- Enable binding events to book
|
||||||
|
Object.assign(Book.prototype, EventEmitter.prototype);
|
||||||
|
|
||||||
|
export default Book;
|
||||||
50
client/static/libs/epubjs-patch/container.js
Normal file
50
client/static/libs/epubjs-patch/container.js
Normal file
|
|
@ -0,0 +1,50 @@
|
||||||
|
import path from "path-webpack";
|
||||||
|
import {qs} from "./utils/core";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles Parsing and Accessing an Epub Container
|
||||||
|
* @class
|
||||||
|
* @param {document} [containerDocument] xml document
|
||||||
|
*/
|
||||||
|
class Container {
|
||||||
|
constructor(containerDocument) {
|
||||||
|
this.packagePath = '';
|
||||||
|
this.directory = '';
|
||||||
|
this.encoding = '';
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
if(!containerDocument) {
|
||||||
|
throw new Error("Container File Not Found");
|
||||||
|
}
|
||||||
|
|
||||||
|
rootfile = qs(containerDocument, "rootfile");
|
||||||
|
|
||||||
|
if(!rootfile) {
|
||||||
|
throw new Error("No RootFile Found");
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Container;
|
||||||
1265
client/static/libs/epubjs-patch/contents.js
Normal file
1265
client/static/libs/epubjs-patch/contents.js
Normal file
File diff suppressed because it is too large
Load diff
70
client/static/libs/epubjs-patch/displayoptions.js
Normal file
70
client/static/libs/epubjs-patch/displayoptions.js
Normal file
|
|
@ -0,0 +1,70 @@
|
||||||
|
import {qs, qsa } from "./utils/core";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Open DisplayOptions Format Parser
|
||||||
|
* @class
|
||||||
|
* @param {document} displayOptionsDocument XML
|
||||||
|
*/
|
||||||
|
class DisplayOptions {
|
||||||
|
constructor(displayOptionsDocument) {
|
||||||
|
this.interactive = "";
|
||||||
|
this.fixedLayout = "";
|
||||||
|
this.openToSpread = "";
|
||||||
|
this.orientationLock = "";
|
||||||
|
|
||||||
|
if (displayOptionsDocument) {
|
||||||
|
this.parse(displayOptionsDocument);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 options = qsa(displayOptionsNode, "option");
|
||||||
|
options.forEach((el) => {
|
||||||
|
let value = "";
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
destroy() {
|
||||||
|
this.interactive = undefined;
|
||||||
|
this.fixedLayout = undefined;
|
||||||
|
this.openToSpread = undefined;
|
||||||
|
this.orientationLock = undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default DisplayOptions;
|
||||||
35
client/static/libs/epubjs-patch/epub.js
Normal file
35
client/static/libs/epubjs-patch/epub.js
Normal file
|
|
@ -0,0 +1,35 @@
|
||||||
|
import Book from "./book";
|
||||||
|
import Rendition from "./rendition";
|
||||||
|
import CFI from "./epubcfi";
|
||||||
|
import Contents from "./contents";
|
||||||
|
import * as utils from "./utils/core";
|
||||||
|
import { EPUBJS_VERSION } from "./utils/constants";
|
||||||
|
|
||||||
|
import IframeView from "./managers/views/iframe";
|
||||||
|
import DefaultViewManager from "./managers/default";
|
||||||
|
import ContinuousViewManager from "./managers/continuous";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new Book
|
||||||
|
* @param {string|ArrayBuffer} url URL, Path or ArrayBuffer
|
||||||
|
* @param {object} options to pass to the book
|
||||||
|
* @returns {Book} a new Book object
|
||||||
|
* @example ePub("/path/to/book.epub", {})
|
||||||
|
*/
|
||||||
|
function ePub(url, options) {
|
||||||
|
return new Book(url, options);
|
||||||
|
}
|
||||||
|
|
||||||
|
ePub.VERSION = EPUBJS_VERSION;
|
||||||
|
|
||||||
|
if (typeof(global) !== "undefined") {
|
||||||
|
global.EPUBJS_VERSION = EPUBJS_VERSION;
|
||||||
|
}
|
||||||
|
|
||||||
|
ePub.Book = Book;
|
||||||
|
ePub.Rendition = Rendition;
|
||||||
|
ePub.Contents = Contents;
|
||||||
|
ePub.CFI = CFI;
|
||||||
|
ePub.utils = utils;
|
||||||
|
|
||||||
|
export default ePub;
|
||||||
1048
client/static/libs/epubjs-patch/epubcfi.js
Normal file
1048
client/static/libs/epubjs-patch/epubcfi.js
Normal file
File diff suppressed because it is too large
Load diff
15
client/static/libs/epubjs-patch/index.js
Normal file
15
client/static/libs/epubjs-patch/index.js
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
import Book from "./book";
|
||||||
|
import EpubCFI from "./epubcfi";
|
||||||
|
import Rendition from "./rendition";
|
||||||
|
import Contents from "./contents";
|
||||||
|
import Layout from "./layout";
|
||||||
|
import ePub from "./epub";
|
||||||
|
|
||||||
|
export default ePub;
|
||||||
|
export {
|
||||||
|
Book,
|
||||||
|
EpubCFI,
|
||||||
|
Rendition,
|
||||||
|
Contents,
|
||||||
|
Layout
|
||||||
|
};
|
||||||
261
client/static/libs/epubjs-patch/layout.js
Normal file
261
client/static/libs/epubjs-patch/layout.js
Normal file
|
|
@ -0,0 +1,261 @@
|
||||||
|
import { extend } from "./utils/core";
|
||||||
|
import { EVENTS } from "./utils/constants";
|
||||||
|
import { EventEmitter } from "events";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Figures out the CSS values to apply for a layout
|
||||||
|
* @class
|
||||||
|
* @param {object} settings
|
||||||
|
* @param {string} [settings.layout='reflowable']
|
||||||
|
* @param {string} [settings.spread]
|
||||||
|
* @param {number} [settings.minSpreadWidth=800]
|
||||||
|
* @param {boolean} [settings.evenSpreads=false]
|
||||||
|
*/
|
||||||
|
class Layout extends EventEmitter {
|
||||||
|
constructor(settings) {
|
||||||
|
super();
|
||||||
|
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";
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
this.width = 0;
|
||||||
|
this.height = 0;
|
||||||
|
this.spreadWidth = 0;
|
||||||
|
this.delta = 0;
|
||||||
|
|
||||||
|
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
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (min >= 0) {
|
||||||
|
this._minSpreadWidth = min;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 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;
|
||||||
|
|
||||||
|
//-- Check the width and create even width columns
|
||||||
|
// var fullWidth = Math.floor(_width);
|
||||||
|
var width = _width;
|
||||||
|
var height = _height;
|
||||||
|
|
||||||
|
var section = Math.floor(width / 12);
|
||||||
|
|
||||||
|
var columnWidth;
|
||||||
|
var spreadWidth;
|
||||||
|
var pageWidth;
|
||||||
|
var delta;
|
||||||
|
|
||||||
|
if (this._spread && width >= this._minSpreadWidth) {
|
||||||
|
divisor = 2;
|
||||||
|
} else {
|
||||||
|
divisor = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.name === "reflowable" && this._flow === "paginated" && !(_gap >= 0)) {
|
||||||
|
gap = ((section % 2 === 0) ? section : section - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.name === "pre-paginated" ) {
|
||||||
|
gap = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
//-- Double Page
|
||||||
|
if(divisor > 1) {
|
||||||
|
// width = width - gap;
|
||||||
|
// columnWidth = (width - gap) / divisor;
|
||||||
|
// gap = gap / divisor;
|
||||||
|
columnWidth = (width / divisor) - gap;
|
||||||
|
pageWidth = columnWidth + gap;
|
||||||
|
} else {
|
||||||
|
columnWidth = width;
|
||||||
|
pageWidth = width;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.name === "pre-paginated" && divisor > 1) {
|
||||||
|
width = columnWidth;
|
||||||
|
}
|
||||||
|
|
||||||
|
spreadWidth = (columnWidth * divisor) + gap;
|
||||||
|
|
||||||
|
delta = width;
|
||||||
|
|
||||||
|
this.width = width;
|
||||||
|
this.height = height;
|
||||||
|
this.spreadWidth = spreadWidth;
|
||||||
|
this.pageWidth = pageWidth;
|
||||||
|
this.delta = delta;
|
||||||
|
|
||||||
|
this.columnWidth = columnWidth;
|
||||||
|
this.gap = gap;
|
||||||
|
this.divisor = divisor;
|
||||||
|
|
||||||
|
// 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;
|
||||||
|
|
||||||
|
this.update({
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
spreadWidth,
|
||||||
|
pageWidth,
|
||||||
|
delta,
|
||||||
|
columnWidth,
|
||||||
|
gap,
|
||||||
|
divisor
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Apply Css to a Document
|
||||||
|
* @param {Contents} contents
|
||||||
|
* @return {Promise}
|
||||||
|
*/
|
||||||
|
format(contents, section, axis){
|
||||||
|
var formating;
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Object.assign(Layout.prototype, EventEmitter.prototype);
|
||||||
|
|
||||||
|
export default Layout;
|
||||||
27
client/static/libs/epubjs-patch/license
Normal file
27
client/static/libs/epubjs-patch/license
Normal file
|
|
@ -0,0 +1,27 @@
|
||||||
|
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:
|
||||||
|
|
||||||
|
1. Redistributions of source code must retain the above copyright notice, this
|
||||||
|
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.
|
||||||
|
|
||||||
|
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
|
||||||
|
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||||
|
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
|
||||||
|
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||||
|
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||||
|
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||||
|
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||||
|
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,
|
||||||
|
either expressed or implied, of the FreeBSD Project.
|
||||||
502
client/static/libs/epubjs-patch/locations.js
Normal file
502
client/static/libs/epubjs-patch/locations.js
Normal file
|
|
@ -0,0 +1,502 @@
|
||||||
|
import {qs, sprint, locationOf, defer} from "./utils/core";
|
||||||
|
import Queue from "./utils/queue";
|
||||||
|
import EpubCFI from "./epubcfi";
|
||||||
|
import { EVENTS } from "./utils/constants";
|
||||||
|
import { EventEmitter } from "events";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find Locations for a Book
|
||||||
|
* @param {Spine} spine
|
||||||
|
* @param {request} request
|
||||||
|
* @param {number} [pause=100]
|
||||||
|
*/
|
||||||
|
class Locations extends EventEmitter {
|
||||||
|
constructor(spine, request, pause) {
|
||||||
|
super();
|
||||||
|
this.spine = spine;
|
||||||
|
this.request = request;
|
||||||
|
this.pause = pause || 100;
|
||||||
|
|
||||||
|
this.q = new Queue(this);
|
||||||
|
this.epubcfi = new EpubCFI();
|
||||||
|
|
||||||
|
this._locations = [];
|
||||||
|
this._locationsWords = [];
|
||||||
|
this.total = 0;
|
||||||
|
|
||||||
|
this.break = 150;
|
||||||
|
|
||||||
|
this._current = 0;
|
||||||
|
|
||||||
|
this._wordCounter = 0;
|
||||||
|
|
||||||
|
this.currentLocation = '';
|
||||||
|
this._currentCfi ='';
|
||||||
|
this.processingTimeout = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load all of sections in the book to generate locations
|
||||||
|
* @param {int} chars how many chars to split on
|
||||||
|
* @return {Promise<Array<string>>} locations
|
||||||
|
*/
|
||||||
|
generate(chars) {
|
||||||
|
|
||||||
|
if (chars) {
|
||||||
|
this.break = chars;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.q.pause();
|
||||||
|
|
||||||
|
this.spine.each(function(section) {
|
||||||
|
if (section.linear) {
|
||||||
|
this.q.enqueue(this.process.bind(this), section);
|
||||||
|
}
|
||||||
|
}.bind(this));
|
||||||
|
|
||||||
|
return this.q.run().then(function() {
|
||||||
|
this.total = this._locations.length - 1;
|
||||||
|
|
||||||
|
if (this._currentCfi) {
|
||||||
|
this.currentLocation = this._currentCfi;
|
||||||
|
}
|
||||||
|
|
||||||
|
return this._locations;
|
||||||
|
// console.log(this.percentage(this.book.rendition.location.start), this.percentage(this.book.rendition.location.end));
|
||||||
|
}.bind(this));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
createRange () {
|
||||||
|
return {
|
||||||
|
startContainer: undefined,
|
||||||
|
startOffset: undefined,
|
||||||
|
endContainer: undefined,
|
||||||
|
endOffset: undefined
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
process(section) {
|
||||||
|
|
||||||
|
return section.load(this.request)
|
||||||
|
.then(function(contents) {
|
||||||
|
var completed = new defer();
|
||||||
|
var locations = this.parse(contents, section.cfiBase);
|
||||||
|
this._locations = this._locations.concat(locations);
|
||||||
|
|
||||||
|
section.unload();
|
||||||
|
|
||||||
|
this.processingTimeout = setTimeout(() => completed.resolve(locations), this.pause);
|
||||||
|
return completed.promise;
|
||||||
|
}.bind(this));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
parse(contents, cfiBase, chars) {
|
||||||
|
var locations = [];
|
||||||
|
var range;
|
||||||
|
var doc = contents.ownerDocument;
|
||||||
|
var body = qs(doc, "body");
|
||||||
|
var counter = 0;
|
||||||
|
var prev;
|
||||||
|
var _break = chars || this.break;
|
||||||
|
var parser = function(node) {
|
||||||
|
var len = node.length;
|
||||||
|
var dist;
|
||||||
|
var pos = 0;
|
||||||
|
|
||||||
|
if (node.textContent.trim().length === 0) {
|
||||||
|
return false; // continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start range
|
||||||
|
if (counter == 0) {
|
||||||
|
range = this.createRange();
|
||||||
|
range.startContainer = node;
|
||||||
|
range.startOffset = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
dist = _break - counter;
|
||||||
|
|
||||||
|
// Node is smaller than a break,
|
||||||
|
// skip over it
|
||||||
|
if(dist > len){
|
||||||
|
counter += len;
|
||||||
|
pos = len;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
while (pos < len) {
|
||||||
|
dist = _break - counter;
|
||||||
|
|
||||||
|
if (counter === 0) {
|
||||||
|
// Start new range
|
||||||
|
pos += 1;
|
||||||
|
range = this.createRange();
|
||||||
|
range.startContainer = node;
|
||||||
|
range.startOffset = pos;
|
||||||
|
}
|
||||||
|
|
||||||
|
// pos += dist;
|
||||||
|
|
||||||
|
// Gone over
|
||||||
|
if(pos + dist >= len){
|
||||||
|
// Continue counter for next node
|
||||||
|
counter += len - pos;
|
||||||
|
// break
|
||||||
|
pos = len;
|
||||||
|
// At End
|
||||||
|
} else {
|
||||||
|
// Advance pos
|
||||||
|
pos += dist;
|
||||||
|
|
||||||
|
// End the previous range
|
||||||
|
range.endContainer = node;
|
||||||
|
range.endOffset = pos;
|
||||||
|
// cfi = section.cfiFromRange(range);
|
||||||
|
let cfi = new EpubCFI(range, cfiBase).toString();
|
||||||
|
locations.push(cfi);
|
||||||
|
counter = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
prev = node;
|
||||||
|
};
|
||||||
|
|
||||||
|
sprint(body, parser.bind(this));
|
||||||
|
|
||||||
|
// Close remaining
|
||||||
|
if (range && range.startContainer && prev) {
|
||||||
|
range.endContainer = prev;
|
||||||
|
range.endOffset = prev.length;
|
||||||
|
let cfi = new EpubCFI(range, cfiBase).toString();
|
||||||
|
locations.push(cfi);
|
||||||
|
counter = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return locations;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load all of sections in the book to generate locations
|
||||||
|
* @param {string} startCfi start position
|
||||||
|
* @param {int} wordCount how many words to split on
|
||||||
|
* @param {int} count result count
|
||||||
|
* @return {object} locations
|
||||||
|
*/
|
||||||
|
generateFromWords(startCfi, wordCount, count) {
|
||||||
|
var start = startCfi ? new EpubCFI(startCfi) : undefined;
|
||||||
|
this.q.pause();
|
||||||
|
this._locationsWords = [];
|
||||||
|
this._wordCounter = 0;
|
||||||
|
|
||||||
|
this.spine.each(function(section) {
|
||||||
|
if (section.linear) {
|
||||||
|
if (start) {
|
||||||
|
if (section.index >= start.spinePos) {
|
||||||
|
this.q.enqueue(this.processWords.bind(this), section, wordCount, start, count);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.q.enqueue(this.processWords.bind(this), section, wordCount, start, count);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}.bind(this));
|
||||||
|
|
||||||
|
return this.q.run().then(function() {
|
||||||
|
if (this._currentCfi) {
|
||||||
|
this.currentLocation = this._currentCfi;
|
||||||
|
}
|
||||||
|
|
||||||
|
return this._locationsWords;
|
||||||
|
}.bind(this));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
processWords(section, wordCount, startCfi, count) {
|
||||||
|
if (count && this._locationsWords.length >= count) {
|
||||||
|
return Promise.resolve();
|
||||||
|
}
|
||||||
|
|
||||||
|
return section.load(this.request)
|
||||||
|
.then(function(contents) {
|
||||||
|
var completed = new defer();
|
||||||
|
var locations = this.parseWords(contents, section, wordCount, startCfi);
|
||||||
|
var remainingCount = count - this._locationsWords.length;
|
||||||
|
this._locationsWords = this._locationsWords.concat(locations.length >= count ? locations.slice(0, remainingCount) : locations);
|
||||||
|
|
||||||
|
section.unload();
|
||||||
|
|
||||||
|
this.processingTimeout = setTimeout(() => completed.resolve(locations), this.pause);
|
||||||
|
return completed.promise;
|
||||||
|
}.bind(this));
|
||||||
|
}
|
||||||
|
|
||||||
|
//http://stackoverflow.com/questions/18679576/counting-words-in-string
|
||||||
|
countWords(s) {
|
||||||
|
s = s.replace(/(^\s*)|(\s*$)/gi, "");//exclude start and end white-space
|
||||||
|
s = s.replace(/[ ]{2,}/gi, " ");//2 or more space to 1
|
||||||
|
s = s.replace(/\n /, "\n"); // exclude newline with a start spacing
|
||||||
|
return s.split(" ").length;
|
||||||
|
}
|
||||||
|
|
||||||
|
parseWords(contents, section, wordCount, startCfi) {
|
||||||
|
var cfiBase = section.cfiBase;
|
||||||
|
var locations = [];
|
||||||
|
var doc = contents.ownerDocument;
|
||||||
|
var body = qs(doc, "body");
|
||||||
|
var prev;
|
||||||
|
var _break = wordCount;
|
||||||
|
var foundStartNode = startCfi ? startCfi.spinePos !== section.index : true;
|
||||||
|
var startNode;
|
||||||
|
if (startCfi && section.index === startCfi.spinePos) {
|
||||||
|
startNode = startCfi.findNode(startCfi.range ? startCfi.path.steps.concat(startCfi.start.steps) : startCfi.path.steps, contents.ownerDocument);
|
||||||
|
}
|
||||||
|
var parser = function(node) {
|
||||||
|
if (!foundStartNode) {
|
||||||
|
if (node === startNode) {
|
||||||
|
foundStartNode = true;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (node.textContent.length < 10) {
|
||||||
|
if (node.textContent.trim().length === 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var len = this.countWords(node.textContent);
|
||||||
|
var dist;
|
||||||
|
var pos = 0;
|
||||||
|
|
||||||
|
if (len === 0) {
|
||||||
|
return false; // continue
|
||||||
|
}
|
||||||
|
|
||||||
|
dist = _break - this._wordCounter;
|
||||||
|
|
||||||
|
// Node is smaller than a break,
|
||||||
|
// skip over it
|
||||||
|
if (dist > len) {
|
||||||
|
this._wordCounter += len;
|
||||||
|
pos = len;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
while (pos < len) {
|
||||||
|
dist = _break - this._wordCounter;
|
||||||
|
|
||||||
|
// Gone over
|
||||||
|
if (pos + dist >= len) {
|
||||||
|
// Continue counter for next node
|
||||||
|
this._wordCounter += len - pos;
|
||||||
|
// break
|
||||||
|
pos = len;
|
||||||
|
// At End
|
||||||
|
} else {
|
||||||
|
// Advance pos
|
||||||
|
pos += dist;
|
||||||
|
|
||||||
|
let cfi = new EpubCFI(node, cfiBase);
|
||||||
|
locations.push({ cfi: cfi.toString(), wordCount: this._wordCounter });
|
||||||
|
this._wordCounter = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
prev = node;
|
||||||
|
};
|
||||||
|
|
||||||
|
sprint(body, parser.bind(this));
|
||||||
|
|
||||||
|
return locations;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a location from an EpubCFI
|
||||||
|
* @param {EpubCFI} cfi
|
||||||
|
* @return {number}
|
||||||
|
*/
|
||||||
|
locationFromCfi(cfi){
|
||||||
|
let loc;
|
||||||
|
if (EpubCFI.prototype.isCfiString(cfi)) {
|
||||||
|
cfi = new EpubCFI(cfi);
|
||||||
|
}
|
||||||
|
// Check if the location has not been set yet
|
||||||
|
if(this._locations.length === 0) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
loc = locationOf(cfi, this._locations, this.epubcfi.compare);
|
||||||
|
|
||||||
|
if (loc > this.total) {
|
||||||
|
return this.total;
|
||||||
|
}
|
||||||
|
|
||||||
|
return loc;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a percentage position in locations from an EpubCFI
|
||||||
|
* @param {EpubCFI} cfi
|
||||||
|
* @return {number}
|
||||||
|
*/
|
||||||
|
percentageFromCfi(cfi) {
|
||||||
|
if(this._locations.length === 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
// Find closest cfi
|
||||||
|
var loc = this.locationFromCfi(cfi);
|
||||||
|
// Get percentage in total
|
||||||
|
return this.percentageFromLocation(loc);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a percentage position from a location index
|
||||||
|
* @param {number} location
|
||||||
|
* @return {number}
|
||||||
|
*/
|
||||||
|
percentageFromLocation(loc) {
|
||||||
|
if (!loc || !this.total) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (loc / this.total);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get an EpubCFI from location index
|
||||||
|
* @param {number} loc
|
||||||
|
* @return {EpubCFI} cfi
|
||||||
|
*/
|
||||||
|
cfiFromLocation(loc){
|
||||||
|
var cfi = -1;
|
||||||
|
// check that pg is an int
|
||||||
|
if(typeof loc != "number"){
|
||||||
|
loc = parseInt(loc);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(loc >= 0 && loc < this._locations.length) {
|
||||||
|
cfi = this._locations[loc];
|
||||||
|
}
|
||||||
|
|
||||||
|
return cfi;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get an EpubCFI from location percentage
|
||||||
|
* @param {number} percentage
|
||||||
|
* @return {EpubCFI} cfi
|
||||||
|
*/
|
||||||
|
cfiFromPercentage(percentage){
|
||||||
|
let loc;
|
||||||
|
if (percentage > 1) {
|
||||||
|
console.warn("Normalize cfiFromPercentage value to between 0 - 1");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make sure 1 goes to very end
|
||||||
|
if (percentage >= 1) {
|
||||||
|
let cfi = new EpubCFI(this._locations[this.total]);
|
||||||
|
cfi.collapse();
|
||||||
|
return cfi.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
loc = Math.ceil(this.total * percentage);
|
||||||
|
return this.cfiFromLocation(loc);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load locations from JSON
|
||||||
|
* @param {json} locations
|
||||||
|
*/
|
||||||
|
load(locations){
|
||||||
|
if (typeof locations === "string") {
|
||||||
|
this._locations = JSON.parse(locations);
|
||||||
|
} else {
|
||||||
|
this._locations = locations;
|
||||||
|
}
|
||||||
|
this.total = this._locations.length - 1;
|
||||||
|
return this._locations;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Save locations to JSON
|
||||||
|
* @return {json}
|
||||||
|
*/
|
||||||
|
save(){
|
||||||
|
return JSON.stringify(this._locations);
|
||||||
|
}
|
||||||
|
|
||||||
|
getCurrent(){
|
||||||
|
return this._current;
|
||||||
|
}
|
||||||
|
|
||||||
|
setCurrent(curr){
|
||||||
|
var loc;
|
||||||
|
|
||||||
|
if(typeof curr == "string"){
|
||||||
|
this._currentCfi = curr;
|
||||||
|
} else if (typeof curr == "number") {
|
||||||
|
this._current = curr;
|
||||||
|
} else {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(this._locations.length === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(typeof curr == "string"){
|
||||||
|
loc = this.locationFromCfi(curr);
|
||||||
|
this._current = loc;
|
||||||
|
} else {
|
||||||
|
loc = curr;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.emit(EVENTS.LOCATIONS.CHANGED, {
|
||||||
|
percentage: this.percentageFromLocation(loc)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the current location
|
||||||
|
*/
|
||||||
|
get currentLocation() {
|
||||||
|
return this._current;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the current location
|
||||||
|
*/
|
||||||
|
set currentLocation(curr) {
|
||||||
|
this.setCurrent(curr);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Locations length
|
||||||
|
*/
|
||||||
|
length () {
|
||||||
|
return this._locations.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
destroy () {
|
||||||
|
this.spine = undefined;
|
||||||
|
this.request = undefined;
|
||||||
|
this.pause = undefined;
|
||||||
|
|
||||||
|
this.q.stop();
|
||||||
|
this.q = undefined;
|
||||||
|
this.epubcfi = undefined;
|
||||||
|
|
||||||
|
this._locations = undefined
|
||||||
|
this.total = undefined;
|
||||||
|
|
||||||
|
this.break = undefined;
|
||||||
|
this._current = undefined;
|
||||||
|
|
||||||
|
this.currentLocation = undefined;
|
||||||
|
this._currentCfi = undefined;
|
||||||
|
clearTimeout(this.processingTimeout);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Object.assign(Locations.prototype, EventEmitter.prototype);
|
||||||
|
|
||||||
|
export default Locations;
|
||||||
592
client/static/libs/epubjs-patch/managers/continuous/index.js
Normal file
592
client/static/libs/epubjs-patch/managers/continuous/index.js
Normal file
|
|
@ -0,0 +1,592 @@
|
||||||
|
import {extend, defer, requestAnimationFrame} from "../../utils/core";
|
||||||
|
import DefaultViewManager from "../default";
|
||||||
|
import Snap from "../helpers/snap";
|
||||||
|
import { EVENTS } from "../../utils/constants";
|
||||||
|
import debounce from "lodash/debounce";
|
||||||
|
|
||||||
|
class ContinuousViewManager extends DefaultViewManager {
|
||||||
|
constructor(options) {
|
||||||
|
super(options);
|
||||||
|
|
||||||
|
this.name = "continuous";
|
||||||
|
|
||||||
|
this.settings = extend(this.settings || {}, {
|
||||||
|
infinite: true,
|
||||||
|
overflow: undefined,
|
||||||
|
axis: undefined,
|
||||||
|
writingMode: undefined,
|
||||||
|
flow: "scrolled",
|
||||||
|
offset: 500,
|
||||||
|
offsetDelta: 250,
|
||||||
|
width: undefined,
|
||||||
|
height: undefined,
|
||||||
|
snap: false,
|
||||||
|
afterScrolledTimeout: 10,
|
||||||
|
allowScriptedContent: false,
|
||||||
|
allowPopups: false
|
||||||
|
});
|
||||||
|
|
||||||
|
extend(this.settings, options.settings || {});
|
||||||
|
|
||||||
|
// Gap can be 0, but defaults doesn't handle that
|
||||||
|
if (options.settings.gap != "undefined" && options.settings.gap === 0) {
|
||||||
|
this.settings.gap = options.settings.gap;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.viewSettings = {
|
||||||
|
ignoreClass: this.settings.ignoreClass,
|
||||||
|
axis: this.settings.axis,
|
||||||
|
flow: this.settings.flow,
|
||||||
|
layout: this.layout,
|
||||||
|
width: 0,
|
||||||
|
height: 0,
|
||||||
|
forceEvenPages: false,
|
||||||
|
allowScriptedContent: this.settings.allowScriptedContent,
|
||||||
|
allowPopups: this.settings.allowPopups
|
||||||
|
};
|
||||||
|
|
||||||
|
this.scrollTop = 0;
|
||||||
|
this.scrollLeft = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
display(section, target){
|
||||||
|
return DefaultViewManager.prototype.display.call(this, section, target)
|
||||||
|
.then(function () {
|
||||||
|
return this.fill();
|
||||||
|
}.bind(this));
|
||||||
|
}
|
||||||
|
|
||||||
|
fill(_full){
|
||||||
|
var full = _full || new defer();
|
||||||
|
|
||||||
|
this.q.enqueue(() => {
|
||||||
|
return this.check();
|
||||||
|
}).then((result) => {
|
||||||
|
if (result) {
|
||||||
|
this.fill(full);
|
||||||
|
} else {
|
||||||
|
full.resolve();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return full.promise;
|
||||||
|
}
|
||||||
|
|
||||||
|
moveTo(offset){
|
||||||
|
// var bounds = this.stage.bounds();
|
||||||
|
// var dist = Math.floor(offset.top / bounds.height) * bounds.height;
|
||||||
|
var distX = 0,
|
||||||
|
distY = 0;
|
||||||
|
|
||||||
|
var offsetX = 0,
|
||||||
|
offsetY = 0;
|
||||||
|
|
||||||
|
if(!this.isPaginated) {
|
||||||
|
distY = offset.top;
|
||||||
|
offsetY = offset.top+this.settings.offsetDelta;
|
||||||
|
} else {
|
||||||
|
distX = Math.floor(offset.left / this.layout.delta) * this.layout.delta;
|
||||||
|
offsetX = distX+this.settings.offsetDelta;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (distX > 0 || distY > 0) {
|
||||||
|
this.scrollBy(distX, distY, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
afterResized(view){
|
||||||
|
this.emit(EVENTS.MANAGERS.RESIZE, view.section);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove Previous Listeners if present
|
||||||
|
removeShownListeners(view){
|
||||||
|
|
||||||
|
// view.off("shown", this.afterDisplayed);
|
||||||
|
// view.off("shown", this.afterDisplayedAbove);
|
||||||
|
view.onDisplayed = function(){};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
add(section){
|
||||||
|
var view = this.createView(section);
|
||||||
|
|
||||||
|
this.views.append(view);
|
||||||
|
|
||||||
|
view.on(EVENTS.VIEWS.RESIZED, (bounds) => {
|
||||||
|
view.expanded = true;
|
||||||
|
});
|
||||||
|
|
||||||
|
view.on(EVENTS.VIEWS.AXIS, (axis) => {
|
||||||
|
this.updateAxis(axis);
|
||||||
|
});
|
||||||
|
|
||||||
|
view.on(EVENTS.VIEWS.WRITING_MODE, (mode) => {
|
||||||
|
this.updateWritingMode(mode);
|
||||||
|
});
|
||||||
|
|
||||||
|
// view.on(EVENTS.VIEWS.SHOWN, this.afterDisplayed.bind(this));
|
||||||
|
view.onDisplayed = this.afterDisplayed.bind(this);
|
||||||
|
view.onResize = this.afterResized.bind(this);
|
||||||
|
|
||||||
|
return view.display(this.request);
|
||||||
|
}
|
||||||
|
|
||||||
|
append(section){
|
||||||
|
var view = this.createView(section);
|
||||||
|
|
||||||
|
view.on(EVENTS.VIEWS.RESIZED, (bounds) => {
|
||||||
|
view.expanded = true;
|
||||||
|
});
|
||||||
|
|
||||||
|
view.on(EVENTS.VIEWS.AXIS, (axis) => {
|
||||||
|
this.updateAxis(axis);
|
||||||
|
});
|
||||||
|
|
||||||
|
view.on(EVENTS.VIEWS.WRITING_MODE, (mode) => {
|
||||||
|
this.updateWritingMode(mode);
|
||||||
|
});
|
||||||
|
|
||||||
|
this.views.append(view);
|
||||||
|
|
||||||
|
view.onDisplayed = this.afterDisplayed.bind(this);
|
||||||
|
|
||||||
|
return view;
|
||||||
|
}
|
||||||
|
|
||||||
|
prepend(section){
|
||||||
|
var view = this.createView(section);
|
||||||
|
|
||||||
|
view.on(EVENTS.VIEWS.RESIZED, (bounds) => {
|
||||||
|
this.counter(bounds);
|
||||||
|
view.expanded = true;
|
||||||
|
});
|
||||||
|
|
||||||
|
view.on(EVENTS.VIEWS.AXIS, (axis) => {
|
||||||
|
this.updateAxis(axis);
|
||||||
|
});
|
||||||
|
|
||||||
|
view.on(EVENTS.VIEWS.WRITING_MODE, (mode) => {
|
||||||
|
this.updateWritingMode(mode);
|
||||||
|
});
|
||||||
|
|
||||||
|
this.views.prepend(view);
|
||||||
|
|
||||||
|
view.onDisplayed = this.afterDisplayed.bind(this);
|
||||||
|
|
||||||
|
return view;
|
||||||
|
}
|
||||||
|
|
||||||
|
counter(bounds){
|
||||||
|
if(this.settings.axis === "vertical") {
|
||||||
|
this.scrollBy(0, bounds.heightDelta, true);
|
||||||
|
} else {
|
||||||
|
this.scrollBy(bounds.widthDelta, 0, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
update(_offset){
|
||||||
|
var container = this.bounds();
|
||||||
|
var views = this.views.all();
|
||||||
|
var viewsLength = views.length;
|
||||||
|
var visible = [];
|
||||||
|
var offset = typeof _offset != "undefined" ? _offset : (this.settings.offset || 0);
|
||||||
|
var isVisible;
|
||||||
|
var view;
|
||||||
|
|
||||||
|
var updating = new defer();
|
||||||
|
var promises = [];
|
||||||
|
for (var i = 0; i < viewsLength; i++) {
|
||||||
|
view = views[i];
|
||||||
|
|
||||||
|
isVisible = this.isVisible(view, offset, offset, container);
|
||||||
|
|
||||||
|
if(isVisible === true) {
|
||||||
|
// console.log("visible " + view.index, view.displayed);
|
||||||
|
|
||||||
|
if (!view.displayed) {
|
||||||
|
let displayed = view.display(this.request)
|
||||||
|
.then(function (view) {
|
||||||
|
view.show();
|
||||||
|
}, (err) => {
|
||||||
|
view.hide();
|
||||||
|
});
|
||||||
|
promises.push(displayed);
|
||||||
|
} else {
|
||||||
|
view.show();
|
||||||
|
}
|
||||||
|
visible.push(view);
|
||||||
|
} else {
|
||||||
|
this.q.enqueue(view.destroy.bind(view));
|
||||||
|
// console.log("hidden " + view.index, view.displayed);
|
||||||
|
|
||||||
|
clearTimeout(this.trimTimeout);
|
||||||
|
this.trimTimeout = setTimeout(function(){
|
||||||
|
this.q.enqueue(this.trim.bind(this));
|
||||||
|
}.bind(this), 250);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
if(promises.length){
|
||||||
|
return Promise.all(promises)
|
||||||
|
.catch((err) => {
|
||||||
|
updating.reject(err);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
updating.resolve();
|
||||||
|
return updating.promise;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
check(_offsetLeft, _offsetTop){
|
||||||
|
var checking = new defer();
|
||||||
|
var newViews = [];
|
||||||
|
|
||||||
|
var horizontal = (this.settings.axis === "horizontal");
|
||||||
|
var delta = this.settings.offset || 0;
|
||||||
|
|
||||||
|
if (_offsetLeft && horizontal) {
|
||||||
|
delta = _offsetLeft;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_offsetTop && !horizontal) {
|
||||||
|
delta = _offsetTop;
|
||||||
|
}
|
||||||
|
|
||||||
|
var bounds = this._bounds; // bounds saved this until resize
|
||||||
|
|
||||||
|
let offset = horizontal ? this.scrollLeft : this.scrollTop;
|
||||||
|
let visibleLength = horizontal ? Math.floor(bounds.width) : bounds.height;
|
||||||
|
let contentLength = horizontal ? this.container.scrollWidth : this.container.scrollHeight;
|
||||||
|
let writingMode = (this.writingMode && this.writingMode.indexOf("vertical") === 0) ? "vertical" : "horizontal";
|
||||||
|
let rtlScrollType = this.settings.rtlScrollType;
|
||||||
|
let rtl = this.settings.direction === "rtl";
|
||||||
|
|
||||||
|
if (!this.settings.fullsize) {
|
||||||
|
// Scroll offset starts at width of element
|
||||||
|
if (rtl && rtlScrollType === "default" && writingMode === "horizontal") {
|
||||||
|
offset = contentLength - visibleLength - offset;
|
||||||
|
}
|
||||||
|
// Scroll offset starts at 0 and goes negative
|
||||||
|
if (rtl && rtlScrollType === "negative" && writingMode === "horizontal") {
|
||||||
|
offset = offset * -1;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Scroll offset starts at 0 and goes negative
|
||||||
|
if ((horizontal && rtl && rtlScrollType === "negative") ||
|
||||||
|
(!horizontal && rtl && rtlScrollType === "default")) {
|
||||||
|
offset = offset * -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let prepend = () => {
|
||||||
|
let first = this.views.first();
|
||||||
|
let prev = first && first.section.prev();
|
||||||
|
|
||||||
|
if(prev) {
|
||||||
|
newViews.push(this.prepend(prev));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let append = () => {
|
||||||
|
let last = this.views.last();
|
||||||
|
let next = last && last.section.next();
|
||||||
|
|
||||||
|
if(next) {
|
||||||
|
newViews.push(this.append(next));
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
let end = offset + visibleLength + delta;
|
||||||
|
let start = offset - delta;
|
||||||
|
|
||||||
|
if (end >= contentLength) {
|
||||||
|
append();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (start < 0) {
|
||||||
|
prepend();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
let promises = newViews.map((view) => {
|
||||||
|
return view.display(this.request);
|
||||||
|
});
|
||||||
|
|
||||||
|
if(newViews.length){
|
||||||
|
return Promise.all(promises)
|
||||||
|
.then(() => {
|
||||||
|
return this.check();
|
||||||
|
})
|
||||||
|
.then(() => {
|
||||||
|
// Check to see if anything new is on screen after rendering
|
||||||
|
return this.update(delta);
|
||||||
|
}, (err) => {
|
||||||
|
return err;
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
this.q.enqueue(function(){
|
||||||
|
this.update();
|
||||||
|
}.bind(this));
|
||||||
|
checking.resolve(false);
|
||||||
|
return checking.promise;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
trim(){
|
||||||
|
var task = new defer();
|
||||||
|
var displayed = this.views.displayed();
|
||||||
|
var first = displayed[0];
|
||||||
|
var last = displayed[displayed.length-1];
|
||||||
|
var firstIndex = this.views.indexOf(first);
|
||||||
|
var lastIndex = this.views.indexOf(last);
|
||||||
|
var above = this.views.slice(0, firstIndex);
|
||||||
|
var below = this.views.slice(lastIndex+1);
|
||||||
|
|
||||||
|
// Erase all but last above
|
||||||
|
for (var i = 0; i < above.length-1; i++) {
|
||||||
|
this.erase(above[i], above);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Erase all except first below
|
||||||
|
for (var j = 1; j < below.length; j++) {
|
||||||
|
this.erase(below[j]);
|
||||||
|
}
|
||||||
|
|
||||||
|
task.resolve();
|
||||||
|
return task.promise;
|
||||||
|
}
|
||||||
|
|
||||||
|
erase(view, above){ //Trim
|
||||||
|
|
||||||
|
var prevTop;
|
||||||
|
var prevLeft;
|
||||||
|
|
||||||
|
if(!this.settings.fullsize) {
|
||||||
|
prevTop = this.container.scrollTop;
|
||||||
|
prevLeft = this.container.scrollLeft;
|
||||||
|
} else {
|
||||||
|
prevTop = window.scrollY;
|
||||||
|
prevLeft = window.scrollX;
|
||||||
|
}
|
||||||
|
|
||||||
|
var bounds = view.bounds();
|
||||||
|
|
||||||
|
this.views.remove(view);
|
||||||
|
|
||||||
|
if(above) {
|
||||||
|
if (this.settings.axis === "vertical") {
|
||||||
|
this.scrollTo(0, prevTop - bounds.height, true);
|
||||||
|
} else {
|
||||||
|
if(this.settings.direction === 'rtl') {
|
||||||
|
if (!this.settings.fullsize) {
|
||||||
|
this.scrollTo(prevLeft, 0, true);
|
||||||
|
} else {
|
||||||
|
this.scrollTo(prevLeft + Math.floor(bounds.width), 0, true);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.scrollTo(prevLeft - Math.floor(bounds.width), 0, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
addEventListeners(stage){
|
||||||
|
|
||||||
|
window.addEventListener("unload", function(e){
|
||||||
|
this.ignore = true;
|
||||||
|
// this.scrollTo(0,0);
|
||||||
|
this.destroy();
|
||||||
|
}.bind(this));
|
||||||
|
|
||||||
|
this.addScrollListeners();
|
||||||
|
|
||||||
|
if (this.isPaginated && this.settings.snap) {
|
||||||
|
this.snapper = new Snap(this, this.settings.snap && (typeof this.settings.snap === "object") && this.settings.snap);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
addScrollListeners() {
|
||||||
|
var scroller;
|
||||||
|
|
||||||
|
this.tick = requestAnimationFrame;
|
||||||
|
|
||||||
|
let dir = this.settings.direction === "rtl" && this.settings.rtlScrollType === "default" ? -1 : 1;
|
||||||
|
|
||||||
|
this.scrollDeltaVert = 0;
|
||||||
|
this.scrollDeltaHorz = 0;
|
||||||
|
|
||||||
|
if(!this.settings.fullsize) {
|
||||||
|
scroller = this.container;
|
||||||
|
this.scrollTop = this.container.scrollTop;
|
||||||
|
this.scrollLeft = this.container.scrollLeft;
|
||||||
|
} else {
|
||||||
|
scroller = window;
|
||||||
|
this.scrollTop = window.scrollY * dir;
|
||||||
|
this.scrollLeft = window.scrollX * dir;
|
||||||
|
}
|
||||||
|
|
||||||
|
this._onScroll = this.onScroll.bind(this);
|
||||||
|
scroller.addEventListener("scroll", this._onScroll);
|
||||||
|
this._scrolled = debounce(this.scrolled.bind(this), 30);
|
||||||
|
// this.tick.call(window, this.onScroll.bind(this));
|
||||||
|
|
||||||
|
this.didScroll = false;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
removeEventListeners(){
|
||||||
|
var scroller;
|
||||||
|
|
||||||
|
if(!this.settings.fullsize) {
|
||||||
|
scroller = this.container;
|
||||||
|
} else {
|
||||||
|
scroller = window;
|
||||||
|
}
|
||||||
|
|
||||||
|
scroller.removeEventListener("scroll", this._onScroll);
|
||||||
|
this._onScroll = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
onScroll(){
|
||||||
|
let scrollTop;
|
||||||
|
let scrollLeft;
|
||||||
|
let dir = this.settings.direction === "rtl" && this.settings.rtlScrollType === "default" ? -1 : 1;
|
||||||
|
|
||||||
|
if(!this.settings.fullsize) {
|
||||||
|
scrollTop = this.container.scrollTop;
|
||||||
|
scrollLeft = this.container.scrollLeft;
|
||||||
|
} else {
|
||||||
|
scrollTop = window.scrollY * dir;
|
||||||
|
scrollLeft = window.scrollX * dir;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.scrollTop = scrollTop;
|
||||||
|
this.scrollLeft = scrollLeft;
|
||||||
|
|
||||||
|
if(!this.ignore) {
|
||||||
|
|
||||||
|
this._scrolled();
|
||||||
|
|
||||||
|
} else {
|
||||||
|
this.ignore = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.scrollDeltaVert += Math.abs(scrollTop-this.prevScrollTop);
|
||||||
|
this.scrollDeltaHorz += Math.abs(scrollLeft-this.prevScrollLeft);
|
||||||
|
|
||||||
|
this.prevScrollTop = scrollTop;
|
||||||
|
this.prevScrollLeft = scrollLeft;
|
||||||
|
|
||||||
|
clearTimeout(this.scrollTimeout);
|
||||||
|
this.scrollTimeout = setTimeout(function(){
|
||||||
|
this.scrollDeltaVert = 0;
|
||||||
|
this.scrollDeltaHorz = 0;
|
||||||
|
}.bind(this), 150);
|
||||||
|
|
||||||
|
clearTimeout(this.afterScrolled);
|
||||||
|
|
||||||
|
this.didScroll = false;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
scrolled() {
|
||||||
|
|
||||||
|
this.q.enqueue(function() {
|
||||||
|
return this.check();
|
||||||
|
}.bind(this));
|
||||||
|
|
||||||
|
this.emit(EVENTS.MANAGERS.SCROLL, {
|
||||||
|
top: this.scrollTop,
|
||||||
|
left: this.scrollLeft
|
||||||
|
});
|
||||||
|
|
||||||
|
clearTimeout(this.afterScrolled);
|
||||||
|
this.afterScrolled = setTimeout(function () {
|
||||||
|
|
||||||
|
// Don't report scroll if we are about the snap
|
||||||
|
if (this.snapper && this.snapper.supportsTouch && this.snapper.needsSnap()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.emit(EVENTS.MANAGERS.SCROLLED, {
|
||||||
|
top: this.scrollTop,
|
||||||
|
left: this.scrollLeft
|
||||||
|
});
|
||||||
|
|
||||||
|
}.bind(this), this.settings.afterScrolledTimeout);
|
||||||
|
}
|
||||||
|
|
||||||
|
next(){
|
||||||
|
|
||||||
|
let delta = this.layout.props.name === "pre-paginated" &&
|
||||||
|
this.layout.props.spread ? this.layout.props.delta * 2 : this.layout.props.delta;
|
||||||
|
|
||||||
|
if(!this.views.length) return;
|
||||||
|
|
||||||
|
if(this.isPaginated && this.settings.axis === "horizontal") {
|
||||||
|
|
||||||
|
this.scrollBy(delta, 0, true);
|
||||||
|
|
||||||
|
} else {
|
||||||
|
|
||||||
|
this.scrollBy(0, this.layout.height, true);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
this.q.enqueue(function() {
|
||||||
|
return this.check();
|
||||||
|
}.bind(this));
|
||||||
|
}
|
||||||
|
|
||||||
|
prev(){
|
||||||
|
|
||||||
|
let delta = this.layout.props.name === "pre-paginated" &&
|
||||||
|
this.layout.props.spread ? this.layout.props.delta * 2 : this.layout.props.delta;
|
||||||
|
|
||||||
|
if(!this.views.length) return;
|
||||||
|
|
||||||
|
if(this.isPaginated && this.settings.axis === "horizontal") {
|
||||||
|
|
||||||
|
this.scrollBy(-delta, 0, true);
|
||||||
|
|
||||||
|
} else {
|
||||||
|
|
||||||
|
this.scrollBy(0, -this.layout.height, true);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
this.q.enqueue(function() {
|
||||||
|
return this.check();
|
||||||
|
}.bind(this));
|
||||||
|
}
|
||||||
|
|
||||||
|
updateFlow(flow){
|
||||||
|
if (this.rendered && this.snapper) {
|
||||||
|
this.snapper.destroy();
|
||||||
|
this.snapper = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
super.updateFlow(flow, "scroll");
|
||||||
|
|
||||||
|
if (this.rendered && this.isPaginated && this.settings.snap) {
|
||||||
|
this.snapper = new Snap(this, this.settings.snap && (typeof this.settings.snap === "object") && this.settings.snap);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
destroy(){
|
||||||
|
super.destroy();
|
||||||
|
|
||||||
|
if (this.snapper) {
|
||||||
|
this.snapper.destroy();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ContinuousViewManager;
|
||||||
1078
client/static/libs/epubjs-patch/managers/default/index.js
Normal file
1078
client/static/libs/epubjs-patch/managers/default/index.js
Normal file
File diff suppressed because it is too large
Load diff
339
client/static/libs/epubjs-patch/managers/helpers/snap.js
Normal file
339
client/static/libs/epubjs-patch/managers/helpers/snap.js
Normal file
|
|
@ -0,0 +1,339 @@
|
||||||
|
import {extend, defer, requestAnimationFrame, prefixed} from "../../utils/core";
|
||||||
|
import { EVENTS, DOM_EVENTS } from "../../utils/constants";
|
||||||
|
import { EventEmitter } from "events";
|
||||||
|
|
||||||
|
// easing equations from https://github.com/danro/easing-js/blob/master/easing.js
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
class Snap extends EventEmitter {
|
||||||
|
constructor(manager, options) {
|
||||||
|
super();
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Object.assign(Snap.prototype, EventEmitter.prototype);
|
||||||
|
|
||||||
|
export default Snap;
|
||||||
363
client/static/libs/epubjs-patch/managers/helpers/stage.js
Normal file
363
client/static/libs/epubjs-patch/managers/helpers/stage.js
Normal file
|
|
@ -0,0 +1,363 @@
|
||||||
|
import {uuid, isNumber, isElement, windowBounds, extend} from "../../utils/core";
|
||||||
|
import throttle from 'lodash/throttle'
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Stage;
|
||||||
167
client/static/libs/epubjs-patch/managers/helpers/views.js
Normal file
167
client/static/libs/epubjs-patch/managers/helpers/views.js
Normal file
|
|
@ -0,0 +1,167 @@
|
||||||
|
class Views {
|
||||||
|
constructor(container) {
|
||||||
|
this.container = container;
|
||||||
|
this._views = [];
|
||||||
|
this.length = 0;
|
||||||
|
this.hidden = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
all() {
|
||||||
|
return this._views;
|
||||||
|
}
|
||||||
|
|
||||||
|
first() {
|
||||||
|
return this._views[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
last() {
|
||||||
|
return this._views[this._views.length-1];
|
||||||
|
}
|
||||||
|
|
||||||
|
indexOf(view) {
|
||||||
|
return this._views.indexOf(view);
|
||||||
|
}
|
||||||
|
|
||||||
|
slice() {
|
||||||
|
return this._views.slice.apply(this._views, arguments);
|
||||||
|
}
|
||||||
|
|
||||||
|
get(i) {
|
||||||
|
return this._views[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.length++;
|
||||||
|
return view;
|
||||||
|
}
|
||||||
|
|
||||||
|
remove(view) {
|
||||||
|
var index = this._views.indexOf(view);
|
||||||
|
|
||||||
|
if(index > -1) {
|
||||||
|
this._views.splice(index, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
this.destroy(view);
|
||||||
|
|
||||||
|
this.length--;
|
||||||
|
}
|
||||||
|
|
||||||
|
destroy(view) {
|
||||||
|
if(view.displayed){
|
||||||
|
view.destroy();
|
||||||
|
}
|
||||||
|
|
||||||
|
if(this.container){
|
||||||
|
this.container.removeChild(view.element);
|
||||||
|
}
|
||||||
|
view = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Iterators
|
||||||
|
|
||||||
|
forEach() {
|
||||||
|
return this._views.forEach.apply(this._views, arguments);
|
||||||
|
}
|
||||||
|
|
||||||
|
clear(){
|
||||||
|
// Remove all views
|
||||||
|
var view;
|
||||||
|
var len = this.length;
|
||||||
|
|
||||||
|
if(!this.length) return;
|
||||||
|
|
||||||
|
for (var i = 0; i < len; i++) {
|
||||||
|
view = this._views[i];
|
||||||
|
this.destroy(view);
|
||||||
|
}
|
||||||
|
|
||||||
|
this._views = [];
|
||||||
|
this.length = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
find(section){
|
||||||
|
|
||||||
|
var view;
|
||||||
|
var len = this.length;
|
||||||
|
|
||||||
|
for (var i = 0; i < len; i++) {
|
||||||
|
view = this._views[i];
|
||||||
|
if(view.displayed && view.section.index == section.index) {
|
||||||
|
return view;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
displayed(){
|
||||||
|
var displayed = [];
|
||||||
|
var view;
|
||||||
|
var len = this.length;
|
||||||
|
|
||||||
|
for (var i = 0; i < len; i++) {
|
||||||
|
view = this._views[i];
|
||||||
|
if(view.displayed){
|
||||||
|
displayed.push(view);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return displayed;
|
||||||
|
}
|
||||||
|
|
||||||
|
show(){
|
||||||
|
var view;
|
||||||
|
var len = this.length;
|
||||||
|
|
||||||
|
for (var i = 0; i < len; i++) {
|
||||||
|
view = this._views[i];
|
||||||
|
if(view.displayed){
|
||||||
|
view.show();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.hidden = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
hide(){
|
||||||
|
var view;
|
||||||
|
var len = this.length;
|
||||||
|
|
||||||
|
for (var i = 0; i < len; i++) {
|
||||||
|
view = this._views[i];
|
||||||
|
if(view.displayed){
|
||||||
|
view.hide();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.hidden = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Views;
|
||||||
852
client/static/libs/epubjs-patch/managers/views/iframe.js
Normal file
852
client/static/libs/epubjs-patch/managers/views/iframe.js
Normal file
|
|
@ -0,0 +1,852 @@
|
||||||
|
import { EventEmitter } from "events";
|
||||||
|
import {extend, borders, uuid, isNumber, bounds, defer, createBlobUrl, revokeBlobUrl} from "../../utils/core";
|
||||||
|
import EpubCFI from "../../epubcfi";
|
||||||
|
import Contents from "../../contents";
|
||||||
|
import { EVENTS } from "../../utils/constants";
|
||||||
|
import { Pane, Highlight, Underline } from "marks-pane";
|
||||||
|
|
||||||
|
class IframeView extends EventEmitter {
|
||||||
|
constructor(section, options) {
|
||||||
|
super();
|
||||||
|
this.settings = extend({
|
||||||
|
ignoreClass : "",
|
||||||
|
axis: undefined, //options.layout && options.layout.props.flow === "scrolled" ? "vertical" : "horizontal",
|
||||||
|
direction: undefined,
|
||||||
|
width: 0,
|
||||||
|
height: 0,
|
||||||
|
layout: undefined,
|
||||||
|
globalLayoutProperties: {},
|
||||||
|
method: undefined,
|
||||||
|
forceRight: false,
|
||||||
|
allowScriptedContent: false,
|
||||||
|
allowPopups: false
|
||||||
|
}, 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;
|
||||||
|
// Dom events to listen for
|
||||||
|
// this.listenedEvents = ["keydown", "keyup", "keypressed", "mouseup", "mousedown", "click", "touchend", "touchstart"];
|
||||||
|
|
||||||
|
this.pane = undefined;
|
||||||
|
this.highlights = {};
|
||||||
|
this.underlines = {};
|
||||||
|
this.marks = {};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
container(axis) {
|
||||||
|
var element = document.createElement("div");
|
||||||
|
|
||||||
|
element.classList.add("epub-view");
|
||||||
|
|
||||||
|
// this.element.style.minHeight = "100px";
|
||||||
|
element.style.height = "0px";
|
||||||
|
element.style.width = "0px";
|
||||||
|
element.style.overflow = "hidden";
|
||||||
|
element.style.position = "relative";
|
||||||
|
element.style.display = "block";
|
||||||
|
|
||||||
|
if(axis && axis == "horizontal"){
|
||||||
|
element.style.flex = "none";
|
||||||
|
} else {
|
||||||
|
element.style.flex = "initial";
|
||||||
|
}
|
||||||
|
|
||||||
|
return element;
|
||||||
|
}
|
||||||
|
|
||||||
|
create() {
|
||||||
|
|
||||||
|
if(this.iframe) {
|
||||||
|
return this.iframe;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!this.element) {
|
||||||
|
this.element = this.createContainer();
|
||||||
|
}
|
||||||
|
|
||||||
|
this.iframe = document.createElement("iframe");
|
||||||
|
this.iframe.id = this.id;
|
||||||
|
this.iframe.scrolling = "no"; // Might need to be removed: breaks ios width calculations
|
||||||
|
this.iframe.style.overflow = "hidden";
|
||||||
|
this.iframe.seamless = "seamless";
|
||||||
|
// Back up if seamless isn't supported
|
||||||
|
this.iframe.style.border = "none";
|
||||||
|
|
||||||
|
// sandbox
|
||||||
|
this.iframe.sandbox = "allow-same-origin";
|
||||||
|
if (this.settings.allowScriptedContent) {
|
||||||
|
this.iframe.sandbox += " allow-scripts";
|
||||||
|
}
|
||||||
|
if (this.settings.allowPopups) {
|
||||||
|
this.iframe.sandbox += " allow-popups";
|
||||||
|
}
|
||||||
|
|
||||||
|
this.iframe.setAttribute("enable-annotation", "true");
|
||||||
|
|
||||||
|
this.resizing = true;
|
||||||
|
|
||||||
|
// this.iframe.style.display = "none";
|
||||||
|
this.element.style.visibility = "hidden";
|
||||||
|
this.iframe.style.visibility = "hidden";
|
||||||
|
|
||||||
|
this.iframe.style.width = "0";
|
||||||
|
this.iframe.style.height = "0";
|
||||||
|
this._width = 0;
|
||||||
|
this._height = 0;
|
||||||
|
|
||||||
|
this.element.setAttribute("ref", this.index);
|
||||||
|
|
||||||
|
this.added = true;
|
||||||
|
|
||||||
|
this.elementBounds = bounds(this.element);
|
||||||
|
|
||||||
|
// if(width || height){
|
||||||
|
// this.resize(width, height);
|
||||||
|
// } else if(this.width && this.height){
|
||||||
|
// this.resize(this.width, this.height);
|
||||||
|
// } else {
|
||||||
|
// this.iframeBounds = bounds(this.iframe);
|
||||||
|
// }
|
||||||
|
|
||||||
|
|
||||||
|
if(("srcdoc" in this.iframe)) {
|
||||||
|
this.supportsSrcdoc = true;
|
||||||
|
} else {
|
||||||
|
this.supportsSrcdoc = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this.settings.method) {
|
||||||
|
this.settings.method = this.supportsSrcdoc ? "srcdoc" : "write";
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.iframe;
|
||||||
|
}
|
||||||
|
|
||||||
|
render(request, show) {
|
||||||
|
|
||||||
|
// view.onLayout = this.layout.format.bind(this.layout);
|
||||||
|
this.create();
|
||||||
|
|
||||||
|
// Fit to size of the container, apply padding
|
||||||
|
this.size();
|
||||||
|
|
||||||
|
if(!this.sectionRender) {
|
||||||
|
this.sectionRender = this.section.render(request);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Render Chain
|
||||||
|
return this.sectionRender
|
||||||
|
.then(function(contents){
|
||||||
|
return this.load(contents);
|
||||||
|
}.bind(this))
|
||||||
|
.then(function(){
|
||||||
|
|
||||||
|
// find and report the writingMode axis
|
||||||
|
let writingMode = this.contents.writingMode();
|
||||||
|
|
||||||
|
// Set the axis based on the flow and writing mode
|
||||||
|
let axis;
|
||||||
|
if (this.settings.flow === "scrolled") {
|
||||||
|
axis = (writingMode.indexOf("vertical") === 0) ? "horizontal" : "vertical";
|
||||||
|
} else {
|
||||||
|
axis = (writingMode.indexOf("vertical") === 0) ? "vertical" : "horizontal";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (writingMode.indexOf("vertical") === 0 && this.settings.flow === "paginated") {
|
||||||
|
this.layout.delta = this.layout.height;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.setAxis(axis);
|
||||||
|
this.emit(EVENTS.VIEWS.AXIS, axis);
|
||||||
|
|
||||||
|
this.setWritingMode(writingMode);
|
||||||
|
this.emit(EVENTS.VIEWS.WRITING_MODE, writingMode);
|
||||||
|
|
||||||
|
|
||||||
|
// apply the layout function to the contents
|
||||||
|
this.layout.format(this.contents, this.section, this.axis);
|
||||||
|
|
||||||
|
// Listen for events that require an expansion of the iframe
|
||||||
|
this.addListeners();
|
||||||
|
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
// Expand the iframe to the full size of the content
|
||||||
|
this.expand();
|
||||||
|
|
||||||
|
if (this.settings.forceRight) {
|
||||||
|
this.element.style.marginLeft = this.width() + "px";
|
||||||
|
}
|
||||||
|
resolve();
|
||||||
|
});
|
||||||
|
|
||||||
|
}.bind(this), function(e){
|
||||||
|
this.emit(EVENTS.VIEWS.LOAD_ERROR, e);
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
reject(e);
|
||||||
|
});
|
||||||
|
}.bind(this))
|
||||||
|
.then(function() {
|
||||||
|
this.emit(EVENTS.VIEWS.RENDERED, this.section);
|
||||||
|
}.bind(this));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
reset () {
|
||||||
|
if (this.iframe) {
|
||||||
|
this.iframe.style.width = "0";
|
||||||
|
this.iframe.style.height = "0";
|
||||||
|
this._width = 0;
|
||||||
|
this._height = 0;
|
||||||
|
this._textWidth = undefined;
|
||||||
|
this._contentWidth = undefined;
|
||||||
|
this._textHeight = undefined;
|
||||||
|
this._contentHeight = undefined;
|
||||||
|
}
|
||||||
|
this._needsReframe = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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") {
|
||||||
|
this.lock("both", width, height);
|
||||||
|
} else if(this.settings.axis === "horizontal") {
|
||||||
|
this.lock("height", width, height);
|
||||||
|
} else {
|
||||||
|
this.lock("width", width, height);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.settings.width = width;
|
||||||
|
this.settings.height = height;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Lock an axis to element dimensions, taking borders into account
|
||||||
|
lock(what, width, height) {
|
||||||
|
var elBorders = borders(this.element);
|
||||||
|
var iframeBorders;
|
||||||
|
|
||||||
|
if(this.iframe) {
|
||||||
|
iframeBorders = borders(this.iframe);
|
||||||
|
} else {
|
||||||
|
iframeBorders = {width: 0, height: 0};
|
||||||
|
}
|
||||||
|
|
||||||
|
if(what == "width" && isNumber(width)){
|
||||||
|
this.lockedWidth = width - elBorders.width - iframeBorders.width;
|
||||||
|
// this.resize(this.lockedWidth, width); // width keeps ratio correct
|
||||||
|
}
|
||||||
|
|
||||||
|
if(what == "height" && isNumber(height)){
|
||||||
|
this.lockedHeight = height - elBorders.height - iframeBorders.height;
|
||||||
|
// this.resize(width, 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(this.displayed && this.iframe) {
|
||||||
|
|
||||||
|
// this.contents.layout();
|
||||||
|
this.expand();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Resize a single axis based on content dimensions
|
||||||
|
expand(force) {
|
||||||
|
var width = this.lockedWidth;
|
||||||
|
var height = this.lockedHeight;
|
||||||
|
var columns;
|
||||||
|
|
||||||
|
var textWidth, textHeight;
|
||||||
|
|
||||||
|
if(!this.iframe || this._expanding) return;
|
||||||
|
|
||||||
|
this._expanding = true;
|
||||||
|
|
||||||
|
if(this.layout.name === "pre-paginated") {
|
||||||
|
width = this.layout.columnWidth;
|
||||||
|
height = this.layout.height;
|
||||||
|
}
|
||||||
|
// Expand Horizontally
|
||||||
|
else if(this.settings.axis === "horizontal") {
|
||||||
|
// Get the width of the text
|
||||||
|
width = this.contents.textWidth();
|
||||||
|
|
||||||
|
if (width % this.layout.pageWidth > 0) {
|
||||||
|
width = Math.ceil(width / this.layout.pageWidth) * this.layout.pageWidth;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.settings.forceEvenPages) {
|
||||||
|
columns = (width / this.layout.pageWidth);
|
||||||
|
if ( this.layout.divisor > 1 &&
|
||||||
|
this.layout.name === "reflowable" &&
|
||||||
|
(columns % 2 > 0)) {
|
||||||
|
// add a blank page
|
||||||
|
width += this.layout.pageWidth;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // Expand Vertically
|
||||||
|
else if(this.settings.axis === "vertical") {
|
||||||
|
height = this.contents.textHeight();
|
||||||
|
if (this.settings.flow === "paginated" &&
|
||||||
|
height % this.layout.height > 0) {
|
||||||
|
height = Math.ceil(height / this.layout.height) * this.layout.height;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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.reframe(width, height);
|
||||||
|
}
|
||||||
|
|
||||||
|
this._expanding = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
reframe(width, height) {
|
||||||
|
var size;
|
||||||
|
|
||||||
|
if(isNumber(width)){
|
||||||
|
this.element.style.width = width + "px";
|
||||||
|
this.iframe.style.width = width + "px";
|
||||||
|
this._width = width;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(isNumber(height)){
|
||||||
|
this.element.style.height = height + "px";
|
||||||
|
this.iframe.style.height = height + "px";
|
||||||
|
this._height = height;
|
||||||
|
}
|
||||||
|
|
||||||
|
let widthDelta = this.prevBounds ? width - this.prevBounds.width : width;
|
||||||
|
let heightDelta = this.prevBounds ? height - this.prevBounds.height : height;
|
||||||
|
|
||||||
|
size = {
|
||||||
|
width: width,
|
||||||
|
height: height,
|
||||||
|
widthDelta: widthDelta,
|
||||||
|
heightDelta: heightDelta,
|
||||||
|
};
|
||||||
|
|
||||||
|
this.pane && this.pane.render();
|
||||||
|
|
||||||
|
requestAnimationFrame(() => {
|
||||||
|
let mark;
|
||||||
|
for (let m in this.marks) {
|
||||||
|
if (this.marks.hasOwnProperty(m)) {
|
||||||
|
mark = this.marks[m];
|
||||||
|
this.placeMark(mark.element, mark.range);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
this.onResize(this, size);
|
||||||
|
|
||||||
|
this.emit(EVENTS.VIEWS.RESIZED, size);
|
||||||
|
|
||||||
|
this.prevBounds = size;
|
||||||
|
|
||||||
|
this.elementBounds = bounds(this.element);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
load(contents) {
|
||||||
|
var loading = new defer();
|
||||||
|
var loaded = loading.promise;
|
||||||
|
|
||||||
|
if(!this.iframe) {
|
||||||
|
loading.reject(new Error("No Iframe Available"));
|
||||||
|
return loaded;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.iframe.onload = function(event) {
|
||||||
|
|
||||||
|
this.onLoad(event, loading);
|
||||||
|
|
||||||
|
}.bind(this);
|
||||||
|
|
||||||
|
if (this.settings.method === "blobUrl") {
|
||||||
|
this.blobUrl = createBlobUrl(contents, "application/xhtml+xml");
|
||||||
|
this.iframe.src = this.blobUrl;
|
||||||
|
this.element.appendChild(this.iframe);
|
||||||
|
} else if(this.settings.method === "srcdoc"){
|
||||||
|
this.iframe.srcdoc = contents;
|
||||||
|
this.element.appendChild(this.iframe);
|
||||||
|
} else {
|
||||||
|
|
||||||
|
this.element.appendChild(this.iframe);
|
||||||
|
|
||||||
|
this.document = this.iframe.contentDocument;
|
||||||
|
|
||||||
|
if(!this.document) {
|
||||||
|
loading.reject(new Error("No Document Available"));
|
||||||
|
return loaded;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.iframe.contentDocument.open();
|
||||||
|
// For Cordova windows platform
|
||||||
|
if(window.MSApp && MSApp.execUnsafeLocalFunction) {
|
||||||
|
var outerThis = this;
|
||||||
|
MSApp.execUnsafeLocalFunction(function () {
|
||||||
|
outerThis.iframe.contentDocument.write(contents);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
this.iframe.contentDocument.write(contents);
|
||||||
|
}
|
||||||
|
this.iframe.contentDocument.close();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return loaded;
|
||||||
|
}
|
||||||
|
|
||||||
|
onLoad(event, promise) {
|
||||||
|
|
||||||
|
this.window = this.iframe.contentWindow;
|
||||||
|
this.document = this.iframe.contentDocument;
|
||||||
|
|
||||||
|
this.contents = new Contents(this.document, this.document.body, this.section.cfiBase, this.section.index);
|
||||||
|
|
||||||
|
this.rendering = false;
|
||||||
|
|
||||||
|
var link = this.document.querySelector("link[rel='canonical']");
|
||||||
|
if (link) {
|
||||||
|
link.setAttribute("href", this.section.canonical);
|
||||||
|
} else {
|
||||||
|
link = this.document.createElement("link");
|
||||||
|
link.setAttribute("rel", "canonical");
|
||||||
|
link.setAttribute("href", this.section.canonical);
|
||||||
|
this.document.querySelector("head").appendChild(link);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.contents.on(EVENTS.CONTENTS.EXPAND, () => {
|
||||||
|
if(this.displayed && this.iframe) {
|
||||||
|
this.expand();
|
||||||
|
if (this.contents) {
|
||||||
|
this.layout.format(this.contents);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
this.contents.on(EVENTS.CONTENTS.RESIZE, (e) => {
|
||||||
|
if(this.displayed && this.iframe) {
|
||||||
|
this.expand();
|
||||||
|
if (this.contents) {
|
||||||
|
this.layout.format(this.contents);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
promise.resolve(this.contents);
|
||||||
|
}
|
||||||
|
|
||||||
|
setLayout(layout) {
|
||||||
|
this.layout = layout;
|
||||||
|
|
||||||
|
if (this.contents) {
|
||||||
|
this.layout.format(this.contents);
|
||||||
|
this.expand();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setAxis(axis) {
|
||||||
|
|
||||||
|
this.settings.axis = axis;
|
||||||
|
|
||||||
|
if(axis == "horizontal"){
|
||||||
|
this.element.style.flex = "none";
|
||||||
|
} else {
|
||||||
|
this.element.style.flex = "initial";
|
||||||
|
}
|
||||||
|
|
||||||
|
this.size();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
setWritingMode(mode) {
|
||||||
|
// this.element.style.writingMode = writingMode;
|
||||||
|
this.writingMode = mode;
|
||||||
|
}
|
||||||
|
|
||||||
|
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), function (err) {
|
||||||
|
displayed.reject(err, this);
|
||||||
|
});
|
||||||
|
|
||||||
|
} else {
|
||||||
|
displayed.resolve(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
return displayed.promise;
|
||||||
|
}
|
||||||
|
|
||||||
|
show() {
|
||||||
|
|
||||||
|
this.element.style.visibility = "visible";
|
||||||
|
|
||||||
|
if(this.iframe){
|
||||||
|
this.iframe.style.visibility = "visible";
|
||||||
|
|
||||||
|
// Remind Safari to redraw the iframe
|
||||||
|
this.iframe.style.transform = "translateZ(0)";
|
||||||
|
this.iframe.offsetWidth;
|
||||||
|
this.iframe.style.transform = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.emit(EVENTS.VIEWS.SHOWN, this);
|
||||||
|
}
|
||||||
|
|
||||||
|
hide() {
|
||||||
|
// this.iframe.style.display = "none";
|
||||||
|
this.element.style.visibility = "hidden";
|
||||||
|
this.iframe.style.visibility = "hidden";
|
||||||
|
|
||||||
|
this.stopExpanding = true;
|
||||||
|
this.emit(EVENTS.VIEWS.HIDDEN, this);
|
||||||
|
}
|
||||||
|
|
||||||
|
offset() {
|
||||||
|
return {
|
||||||
|
top: this.element.offsetTop,
|
||||||
|
left: this.element.offsetLeft
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
width() {
|
||||||
|
return this._width;
|
||||||
|
}
|
||||||
|
|
||||||
|
height() {
|
||||||
|
return this._height;
|
||||||
|
}
|
||||||
|
|
||||||
|
position() {
|
||||||
|
return this.element.getBoundingClientRect();
|
||||||
|
}
|
||||||
|
|
||||||
|
locationOf(target) {
|
||||||
|
var parentPos = this.iframe.getBoundingClientRect();
|
||||||
|
var targetPos = this.contents.locationOf(target, this.settings.ignoreClass);
|
||||||
|
|
||||||
|
return {
|
||||||
|
"left": targetPos.left,
|
||||||
|
"top": targetPos.top
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
onDisplayed(view) {
|
||||||
|
// Stub, override with a custom functions
|
||||||
|
}
|
||||||
|
|
||||||
|
onResize(view, e) {
|
||||||
|
// Stub, override with a custom functions
|
||||||
|
}
|
||||||
|
|
||||||
|
bounds(force) {
|
||||||
|
if(force || !this.elementBounds) {
|
||||||
|
this.elementBounds = bounds(this.element);
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.elementBounds;
|
||||||
|
}
|
||||||
|
|
||||||
|
highlight(cfiRange, data={}, cb, className = "epubjs-hl", styles = {}) {
|
||||||
|
if (!this.contents) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const attributes = Object.assign({"fill": "yellow", "fill-opacity": "0.3", "mix-blend-mode": "multiply"}, styles);
|
||||||
|
let range = this.contents.range(cfiRange);
|
||||||
|
|
||||||
|
let emitter = () => {
|
||||||
|
this.emit(EVENTS.VIEWS.MARK_CLICKED, cfiRange, data);
|
||||||
|
};
|
||||||
|
|
||||||
|
data["epubcfi"] = cfiRange;
|
||||||
|
|
||||||
|
if (!this.pane) {
|
||||||
|
this.pane = new Pane(this.iframe, this.element);
|
||||||
|
}
|
||||||
|
|
||||||
|
let m = new Highlight(range, className, data, attributes);
|
||||||
|
let h = this.pane.addMark(m);
|
||||||
|
|
||||||
|
this.highlights[cfiRange] = { "mark": h, "element": h.element, "listeners": [emitter, cb] };
|
||||||
|
|
||||||
|
h.element.setAttribute("ref", className);
|
||||||
|
h.element.addEventListener("click", emitter);
|
||||||
|
h.element.addEventListener("touchstart", emitter);
|
||||||
|
|
||||||
|
if (cb) {
|
||||||
|
h.element.addEventListener("click", cb);
|
||||||
|
h.element.addEventListener("touchstart", cb);
|
||||||
|
}
|
||||||
|
return h;
|
||||||
|
}
|
||||||
|
|
||||||
|
underline(cfiRange, data={}, cb, className = "epubjs-ul", styles = {}) {
|
||||||
|
if (!this.contents) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const attributes = Object.assign({"stroke": "black", "stroke-opacity": "0.3", "mix-blend-mode": "multiply"}, styles);
|
||||||
|
let range = this.contents.range(cfiRange);
|
||||||
|
let emitter = () => {
|
||||||
|
this.emit(EVENTS.VIEWS.MARK_CLICKED, cfiRange, data);
|
||||||
|
};
|
||||||
|
|
||||||
|
data["epubcfi"] = cfiRange;
|
||||||
|
|
||||||
|
if (!this.pane) {
|
||||||
|
this.pane = new Pane(this.iframe, this.element);
|
||||||
|
}
|
||||||
|
|
||||||
|
let m = new Underline(range, className, data, attributes);
|
||||||
|
let h = this.pane.addMark(m);
|
||||||
|
|
||||||
|
this.underlines[cfiRange] = { "mark": h, "element": h.element, "listeners": [emitter, cb] };
|
||||||
|
|
||||||
|
h.element.setAttribute("ref", className);
|
||||||
|
h.element.addEventListener("click", emitter);
|
||||||
|
h.element.addEventListener("touchstart", emitter);
|
||||||
|
|
||||||
|
if (cb) {
|
||||||
|
h.element.addEventListener("click", cb);
|
||||||
|
h.element.addEventListener("touchstart", cb);
|
||||||
|
}
|
||||||
|
return h;
|
||||||
|
}
|
||||||
|
|
||||||
|
mark(cfiRange, data={}, cb) {
|
||||||
|
if (!this.contents) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cfiRange in this.marks) {
|
||||||
|
let item = this.marks[cfiRange];
|
||||||
|
return item;
|
||||||
|
}
|
||||||
|
|
||||||
|
let range = this.contents.range(cfiRange);
|
||||||
|
if (!range) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let container = range.commonAncestorContainer;
|
||||||
|
let parent = (container.nodeType === 1) ? container : container.parentNode;
|
||||||
|
|
||||||
|
let emitter = (e) => {
|
||||||
|
this.emit(EVENTS.VIEWS.MARK_CLICKED, cfiRange, data);
|
||||||
|
};
|
||||||
|
|
||||||
|
if (range.collapsed && container.nodeType === 1) {
|
||||||
|
range = new Range();
|
||||||
|
range.selectNodeContents(container);
|
||||||
|
} else if (range.collapsed) { // Webkit doesn't like collapsed ranges
|
||||||
|
range = new Range();
|
||||||
|
range.selectNodeContents(parent);
|
||||||
|
}
|
||||||
|
|
||||||
|
let mark = this.document.createElement("a");
|
||||||
|
mark.setAttribute("ref", "epubjs-mk");
|
||||||
|
mark.style.position = "absolute";
|
||||||
|
|
||||||
|
mark.dataset["epubcfi"] = cfiRange;
|
||||||
|
|
||||||
|
if (data) {
|
||||||
|
Object.keys(data).forEach((key) => {
|
||||||
|
mark.dataset[key] = data[key];
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cb) {
|
||||||
|
mark.addEventListener("click", cb);
|
||||||
|
mark.addEventListener("touchstart", cb);
|
||||||
|
}
|
||||||
|
|
||||||
|
mark.addEventListener("click", emitter);
|
||||||
|
mark.addEventListener("touchstart", emitter);
|
||||||
|
|
||||||
|
this.placeMark(mark, range);
|
||||||
|
|
||||||
|
this.element.appendChild(mark);
|
||||||
|
|
||||||
|
this.marks[cfiRange] = { "element": mark, "range": range, "listeners": [emitter, cb] };
|
||||||
|
|
||||||
|
return parent;
|
||||||
|
}
|
||||||
|
|
||||||
|
placeMark(element, range) {
|
||||||
|
let top, right, left;
|
||||||
|
|
||||||
|
if(this.layout.name === "pre-paginated" ||
|
||||||
|
this.settings.axis !== "horizontal") {
|
||||||
|
let pos = range.getBoundingClientRect();
|
||||||
|
top = pos.top;
|
||||||
|
right = pos.right;
|
||||||
|
} else {
|
||||||
|
// Element might break columns, so find the left most element
|
||||||
|
let rects = range.getClientRects();
|
||||||
|
|
||||||
|
let rect;
|
||||||
|
for (var i = 0; i != rects.length; i++) {
|
||||||
|
rect = rects[i];
|
||||||
|
if (!left || rect.left < left) {
|
||||||
|
left = rect.left;
|
||||||
|
// right = rect.right;
|
||||||
|
right = Math.ceil(left / this.layout.props.pageWidth) * this.layout.props.pageWidth - (this.layout.gap / 2);
|
||||||
|
top = rect.top;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
element.style.top = `${top}px`;
|
||||||
|
element.style.left = `${right}px`;
|
||||||
|
}
|
||||||
|
|
||||||
|
unhighlight(cfiRange) {
|
||||||
|
let item;
|
||||||
|
if (cfiRange in this.highlights) {
|
||||||
|
item = this.highlights[cfiRange];
|
||||||
|
|
||||||
|
this.pane.removeMark(item.mark);
|
||||||
|
item.listeners.forEach((l) => {
|
||||||
|
if (l) {
|
||||||
|
item.element.removeEventListener("click", l);
|
||||||
|
item.element.removeEventListener("touchstart", l);
|
||||||
|
};
|
||||||
|
});
|
||||||
|
delete this.highlights[cfiRange];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ununderline(cfiRange) {
|
||||||
|
let item;
|
||||||
|
if (cfiRange in this.underlines) {
|
||||||
|
item = this.underlines[cfiRange];
|
||||||
|
this.pane.removeMark(item.mark);
|
||||||
|
item.listeners.forEach((l) => {
|
||||||
|
if (l) {
|
||||||
|
item.element.removeEventListener("click", l);
|
||||||
|
item.element.removeEventListener("touchstart", l);
|
||||||
|
};
|
||||||
|
});
|
||||||
|
delete this.underlines[cfiRange];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
unmark(cfiRange) {
|
||||||
|
let item;
|
||||||
|
if (cfiRange in this.marks) {
|
||||||
|
item = this.marks[cfiRange];
|
||||||
|
this.element.removeChild(item.element);
|
||||||
|
item.listeners.forEach((l) => {
|
||||||
|
if (l) {
|
||||||
|
item.element.removeEventListener("click", l);
|
||||||
|
item.element.removeEventListener("touchstart", l);
|
||||||
|
};
|
||||||
|
});
|
||||||
|
delete this.marks[cfiRange];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
destroy() {
|
||||||
|
|
||||||
|
for (let cfiRange in this.highlights) {
|
||||||
|
this.unhighlight(cfiRange);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let cfiRange in this.underlines) {
|
||||||
|
this.ununderline(cfiRange);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let cfiRange in this.marks) {
|
||||||
|
this.unmark(cfiRange);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.blobUrl) {
|
||||||
|
revokeBlobUrl(this.blobUrl);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(this.displayed){
|
||||||
|
this.displayed = false;
|
||||||
|
|
||||||
|
this.removeListeners();
|
||||||
|
this.contents.destroy();
|
||||||
|
|
||||||
|
this.stopExpanding = true;
|
||||||
|
this.element.removeChild(this.iframe);
|
||||||
|
|
||||||
|
if (this.pane) {
|
||||||
|
this.pane.element.remove();
|
||||||
|
this.pane = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.iframe = undefined;
|
||||||
|
this.contents = undefined;
|
||||||
|
|
||||||
|
this._textWidth = null;
|
||||||
|
this._textHeight = null;
|
||||||
|
this._width = null;
|
||||||
|
this._height = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// this.element.style.height = "0px";
|
||||||
|
// this.element.style.width = "0px";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Object.assign(IframeView.prototype, EventEmitter.prototype);
|
||||||
|
|
||||||
|
export default IframeView;
|
||||||
432
client/static/libs/epubjs-patch/managers/views/inline.js
Normal file
432
client/static/libs/epubjs-patch/managers/views/inline.js
Normal file
|
|
@ -0,0 +1,432 @@
|
||||||
|
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 { EVENTS } from "../../utils/constants";
|
||||||
|
|
||||||
|
class InlineView {
|
||||||
|
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;
|
||||||
|
// 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;
|
||||||
|
|
||||||
|
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.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");
|
||||||
|
|
||||||
|
/*
|
||||||
|
var srcs = doc.querySelectorAll("[src]");
|
||||||
|
|
||||||
|
Array.prototype.slice.call(srcs)
|
||||||
|
.forEach(function(item) {
|
||||||
|
var src = item.getAttribute("src");
|
||||||
|
var assetUri = URI(src);
|
||||||
|
var origin = assetUri.origin();
|
||||||
|
var absoluteUri;
|
||||||
|
|
||||||
|
if (!origin) {
|
||||||
|
absoluteUri = assetUri.absoluteTo(this.section.url);
|
||||||
|
item.src = absoluteUri;
|
||||||
|
}
|
||||||
|
}.bind(this));
|
||||||
|
*/
|
||||||
|
this.frame.innerHTML = body.innerHTML;
|
||||||
|
|
||||||
|
this.document = this.frame.ownerDocument;
|
||||||
|
this.window = this.document.defaultView;
|
||||||
|
|
||||||
|
this.contents = new Contents(this.document, this.frame);
|
||||||
|
|
||||||
|
this.rendering = false;
|
||||||
|
|
||||||
|
loading.resolve(this.contents);
|
||||||
|
|
||||||
|
|
||||||
|
return loaded;
|
||||||
|
}
|
||||||
|
|
||||||
|
setLayout(layout) {
|
||||||
|
this.layout = layout;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
resizeListenters() {
|
||||||
|
// Test size again
|
||||||
|
// clearTimeout(this.expanding);
|
||||||
|
// this.expanding = setTimeout(this.expand.bind(this), 350);
|
||||||
|
}
|
||||||
|
|
||||||
|
addListeners() {
|
||||||
|
//TODO: Add content listeners for expanding
|
||||||
|
}
|
||||||
|
|
||||||
|
removeListeners(layoutFunc) {
|
||||||
|
//TODO: remove content listeners for expanding
|
||||||
|
}
|
||||||
|
|
||||||
|
display(request) {
|
||||||
|
var displayed = new defer();
|
||||||
|
|
||||||
|
if (!this.displayed) {
|
||||||
|
|
||||||
|
this.render(request).then(function () {
|
||||||
|
|
||||||
|
this.emit(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";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
EventEmitter(InlineView.prototype);
|
||||||
|
|
||||||
|
export default InlineView;
|
||||||
511
client/static/libs/epubjs-patch/mapping.js
Normal file
511
client/static/libs/epubjs-patch/mapping.js
Normal file
|
|
@ -0,0 +1,511 @@
|
||||||
|
import EpubCFI from "./epubcfi";
|
||||||
|
import { nodeBounds } from "./utils/core";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Map text locations to CFI ranges
|
||||||
|
* @class
|
||||||
|
* @param {Layout} layout Layout to apply
|
||||||
|
* @param {string} [direction="ltr"] Text direction
|
||||||
|
* @param {string} [axis="horizontal"] vertical or horizontal axis
|
||||||
|
* @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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Mapping;
|
||||||
357
client/static/libs/epubjs-patch/navigation.js
Normal file
357
client/static/libs/epubjs-patch/navigation.js
Normal file
|
|
@ -0,0 +1,357 @@
|
||||||
|
import {qs, qsa, querySelectorByType, filterChildren, getParentByTagName} from "./utils/core";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Navigation Parser
|
||||||
|
* @param {document} xml navigation html / xhtml / ncx
|
||||||
|
*/
|
||||||
|
class Navigation {
|
||||||
|
constructor(xml) {
|
||||||
|
this.toc = [];
|
||||||
|
this.tocByHref = {};
|
||||||
|
this.tocById = {};
|
||||||
|
|
||||||
|
this.landmarks = [];
|
||||||
|
this.landmarksByType = {};
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.length = 0;
|
||||||
|
|
||||||
|
this.unpack(this.toc);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unpack navigation items
|
||||||
|
* @private
|
||||||
|
* @param {array} toc
|
||||||
|
*/
|
||||||
|
unpack(toc) {
|
||||||
|
var item;
|
||||||
|
|
||||||
|
for (var i = 0; i < toc.length; i++) {
|
||||||
|
item = toc[i];
|
||||||
|
|
||||||
|
if (item.href) {
|
||||||
|
this.tocByHref[item.href] = i;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (item.id) {
|
||||||
|
this.tocById[item.id] = i;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.length++;
|
||||||
|
|
||||||
|
if (item.subitems.length) {
|
||||||
|
this.unpack(item.subitems);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get an item from the navigation
|
||||||
|
* @param {string} target
|
||||||
|
* @return {object} navItem
|
||||||
|
*/
|
||||||
|
get(target) {
|
||||||
|
var index;
|
||||||
|
|
||||||
|
if(!target) {
|
||||||
|
return this.toc;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(target.indexOf("#") === 0) {
|
||||||
|
index = this.tocById[target.substring(1)];
|
||||||
|
} else if(target in this.tocByHref){
|
||||||
|
index = this.tocByHref[target];
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.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;
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
if(!type) {
|
||||||
|
return this.landmarks;
|
||||||
|
}
|
||||||
|
|
||||||
|
index = this.landmarksByType[type];
|
||||||
|
|
||||||
|
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 = [];
|
||||||
|
|
||||||
|
if (!navElement) return list;
|
||||||
|
|
||||||
|
let navList = filterChildren(navElement, "ol", true);
|
||||||
|
if (!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 = [];
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
|
if (item) {
|
||||||
|
result.push(item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
|
if (!content) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let src = content.getAttribute("href") || "";
|
||||||
|
|
||||||
|
if (!id) {
|
||||||
|
id = src;
|
||||||
|
}
|
||||||
|
let text = content.textContent || "";
|
||||||
|
|
||||||
|
let subitems = [];
|
||||||
|
let nested = filterChildren(item, "ol", true);
|
||||||
|
if (nested) {
|
||||||
|
subitems = this.parseNavList(nested, id);
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
"id": id,
|
||||||
|
"href": src,
|
||||||
|
"label": text,
|
||||||
|
"subitems" : subitems,
|
||||||
|
"parent" : parent
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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;
|
||||||
|
|
||||||
|
if(!navItems || length === 0) return list;
|
||||||
|
|
||||||
|
for (i = 0; i < length; ++i) {
|
||||||
|
item = this.landmarkItem(navItems[i]);
|
||||||
|
if (item) {
|
||||||
|
list.push(item);
|
||||||
|
this.landmarksByType[item.type] = i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a landmarkItem
|
||||||
|
* @private
|
||||||
|
* @param {element} item
|
||||||
|
* @return {object} landmarkItem
|
||||||
|
*/
|
||||||
|
landmarkItem(item){
|
||||||
|
let content = filterChildren(item, "a", true);
|
||||||
|
|
||||||
|
if (!content) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let type = content.getAttributeNS("http://www.idpf.org/2007/ops", "type") || undefined;
|
||||||
|
let href = content.getAttribute("href") || "";
|
||||||
|
let text = content.textContent || "";
|
||||||
|
|
||||||
|
return {
|
||||||
|
"href": href,
|
||||||
|
"label": text,
|
||||||
|
"type" : type
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse from a Epub > 3.0 NC
|
||||||
|
* @private
|
||||||
|
* @param {document} navHtml
|
||||||
|
* @return {array} navigation list
|
||||||
|
*/
|
||||||
|
parseNcx(tocXml){
|
||||||
|
var navPoints = qsa(tocXml, "navPoint");
|
||||||
|
var length = navPoints.length;
|
||||||
|
var i;
|
||||||
|
var toc = {};
|
||||||
|
var list = [];
|
||||||
|
var item, parent;
|
||||||
|
|
||||||
|
if(!navPoints || length === 0) return list;
|
||||||
|
|
||||||
|
for (i = 0; i < length; ++i) {
|
||||||
|
item = this.ncxItem(navPoints[i]);
|
||||||
|
toc[item.id] = item;
|
||||||
|
if(!item.parent) {
|
||||||
|
list.push(item);
|
||||||
|
} else {
|
||||||
|
parent = toc[item.parent];
|
||||||
|
parent.subitems.push(item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a ncxItem
|
||||||
|
* @private
|
||||||
|
* @param {element} item
|
||||||
|
* @return {object} ncxItem
|
||||||
|
*/
|
||||||
|
ncxItem(item){
|
||||||
|
var id = item.getAttribute("id") || false,
|
||||||
|
content = qs(item, "content"),
|
||||||
|
src = content.getAttribute("src"),
|
||||||
|
navLabel = qs(item, "navLabel"),
|
||||||
|
text = navLabel.textContent ? navLabel.textContent : "",
|
||||||
|
subitems = [],
|
||||||
|
parentNode = item.parentNode,
|
||||||
|
parent;
|
||||||
|
|
||||||
|
if(parentNode && (parentNode.nodeName === "navPoint" || parentNode.nodeName.split(':').slice(-1)[0] === "navPoint")) {
|
||||||
|
parent = parentNode.getAttribute("id");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
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;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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;
|
||||||
373
client/static/libs/epubjs-patch/packaging.js
Normal file
373
client/static/libs/epubjs-patch/packaging.js
Normal file
|
|
@ -0,0 +1,373 @@
|
||||||
|
import {qs, qsa, qsp, indexOfElementNode} from "./utils/core";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Open Packaging Format Parser
|
||||||
|
* @class
|
||||||
|
* @param {document} packageDocument OPF XML
|
||||||
|
*/
|
||||||
|
class Packaging {
|
||||||
|
constructor(packageDocument) {
|
||||||
|
this.manifest = {};
|
||||||
|
this.navPath = '';
|
||||||
|
this.ncxPath = '';
|
||||||
|
this.coverPath = '';
|
||||||
|
this.spineNodeIndex = 0;
|
||||||
|
this.spine = [];
|
||||||
|
this.metadata = {};
|
||||||
|
|
||||||
|
if (packageDocument) {
|
||||||
|
this.parse(packageDocument);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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");
|
||||||
|
}
|
||||||
|
|
||||||
|
metadataNode = qs(packageDocument, "metadata");
|
||||||
|
if(!metadataNode) {
|
||||||
|
throw new Error("No Metadata 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");
|
||||||
|
}
|
||||||
|
|
||||||
|
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.spine = this.parseSpine(spineNode, this.manifest);
|
||||||
|
|
||||||
|
this.uniqueIdentifier = this.findUniqueIdentifier(packageDocument);
|
||||||
|
this.metadata = this.parseMetadata(metadataNode);
|
||||||
|
|
||||||
|
this.metadata.direction = spineNode.getAttribute("page-progression-direction");
|
||||||
|
|
||||||
|
return {
|
||||||
|
"metadata" : this.metadata,
|
||||||
|
"spine" : this.spine,
|
||||||
|
"manifest" : this.manifest,
|
||||||
|
"navPath" : this.navPath,
|
||||||
|
"ncxPath" : this.ncxPath,
|
||||||
|
"coverPath": this.coverPath,
|
||||||
|
"spineNodeIndex" : this.spineNodeIndex
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse Metadata
|
||||||
|
* @private
|
||||||
|
* @param {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.pubdate = this.getElementText(xml, "date");
|
||||||
|
|
||||||
|
metadata.publisher = this.getElementText(xml, "publisher");
|
||||||
|
|
||||||
|
metadata.identifier = this.getElementText(xml, "identifier");
|
||||||
|
metadata.language = this.getElementText(xml, "language");
|
||||||
|
metadata.rights = this.getElementText(xml, "rights");
|
||||||
|
|
||||||
|
metadata.modified_date = this.getPropertyText(xml, "dcterms:modified");
|
||||||
|
|
||||||
|
metadata.layout = this.getPropertyText(xml, "rendition:layout");
|
||||||
|
metadata.orientation = this.getPropertyText(xml, "rendition:orientation");
|
||||||
|
metadata.flow = this.getPropertyText(xml, "rendition:flow");
|
||||||
|
metadata.viewport = this.getPropertyText(xml, "rendition:viewport");
|
||||||
|
metadata.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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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);
|
||||||
|
|
||||||
|
//-- 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(" ") : []
|
||||||
|
};
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
|
// var epubcfi = new EpubCFI();
|
||||||
|
|
||||||
|
//-- 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 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);
|
||||||
|
});
|
||||||
|
|
||||||
|
return spine;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 "";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (identifier.localName === "identifier" && identifier.namespaceURI === "http://purl.org/dc/elements/1.1/") {
|
||||||
|
return identifier.childNodes.length > 0 ? identifier.childNodes[0].nodeValue.trim() : "";
|
||||||
|
}
|
||||||
|
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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;
|
||||||
|
|
||||||
|
// 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}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return node ? node.getAttribute("href") : false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find the Cover Path
|
||||||
|
* <item properties="cover-image" id="ci" href="cover.svg" media-type="image/svg+xml" />
|
||||||
|
* Fallback for Epub 2.0
|
||||||
|
* @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.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;
|
||||||
|
|
||||||
|
if(!found || found.length === 0) return "";
|
||||||
|
|
||||||
|
el = found[0];
|
||||||
|
|
||||||
|
if(el.childNodes.length){
|
||||||
|
return el.childNodes[0].nodeValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load JSON Manifest
|
||||||
|
* @param {document} packageDocument OPF XML
|
||||||
|
* @return {object} parsed package parts
|
||||||
|
*/
|
||||||
|
load(json) {
|
||||||
|
this.metadata = json.metadata;
|
||||||
|
|
||||||
|
let spine = json.readingOrder || json.spine;
|
||||||
|
this.spine = spine.map((item, index) =>{
|
||||||
|
item.index = index;
|
||||||
|
item.linear = item.linear || "yes";
|
||||||
|
return item;
|
||||||
|
});
|
||||||
|
|
||||||
|
json.resources.forEach((item, index) => {
|
||||||
|
this.manifest[index] = item;
|
||||||
|
|
||||||
|
if (item.rel && item.rel[0] === "cover") {
|
||||||
|
this.coverPath = item.href;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
this.spineNodeIndex = 0;
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Packaging;
|
||||||
274
client/static/libs/epubjs-patch/pagelist.js
Normal file
274
client/static/libs/epubjs-patch/pagelist.js
Normal file
|
|
@ -0,0 +1,274 @@
|
||||||
|
import EpubCFI from "./epubcfi";
|
||||||
|
import {
|
||||||
|
qs,
|
||||||
|
qsa,
|
||||||
|
querySelectorByType,
|
||||||
|
indexOfSorted,
|
||||||
|
locationOf
|
||||||
|
} from "./utils/core";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Page List Parser
|
||||||
|
* @param {document} [xml]
|
||||||
|
*/
|
||||||
|
class PageList {
|
||||||
|
constructor(xml) {
|
||||||
|
this.pages = [];
|
||||||
|
this.locations = [];
|
||||||
|
this.epubcfi = new EpubCFI();
|
||||||
|
|
||||||
|
this.firstPage = 0;
|
||||||
|
this.lastPage = 0;
|
||||||
|
this.totalPages = 0;
|
||||||
|
|
||||||
|
this.toc = undefined;
|
||||||
|
this.ncx = undefined;
|
||||||
|
|
||||||
|
if (xml) {
|
||||||
|
this.pageList = this.parse(xml);
|
||||||
|
}
|
||||||
|
|
||||||
|
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");
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
if(!navItems || length === 0) return list;
|
||||||
|
|
||||||
|
for (i = 0; i < length; ++i) {
|
||||||
|
item = this.item(navItems[i]);
|
||||||
|
list.push(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
|
||||||
|
parseNcx(navXml) {
|
||||||
|
var list = [];
|
||||||
|
var i = 0;
|
||||||
|
var item;
|
||||||
|
var pageList;
|
||||||
|
var pageTargets;
|
||||||
|
var length = 0;
|
||||||
|
|
||||||
|
pageList = qs(navXml, "pageList");
|
||||||
|
if (!pageList) return list;
|
||||||
|
|
||||||
|
pageTargets = qsa(pageList, "pageTarget");
|
||||||
|
length = pageTargets.length;
|
||||||
|
|
||||||
|
if (!pageTargets || pageTargets.length === 0) {
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (i = 0; i < length; ++i) {
|
||||||
|
item = this.ncxItem(pageTargets[i]);
|
||||||
|
list.push(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: check if CFI is valid?
|
||||||
|
|
||||||
|
// check if the cfi is in the location list
|
||||||
|
// var index = this.locations.indexOf(cfi);
|
||||||
|
var index = indexOfSorted(cfi, this.locations, this.epubcfi.compare);
|
||||||
|
if(index != -1) {
|
||||||
|
pg = this.pages[index];
|
||||||
|
} else {
|
||||||
|
// Otherwise add it to the list of locations
|
||||||
|
// Insert it in the correct position in the locations page
|
||||||
|
//index = EPUBJS.core.insert(cfi, this.locations, this.epubcfi.compare);
|
||||||
|
index = locationOf(cfi, this.locations, this.epubcfi.compare);
|
||||||
|
// Get the page at the location just before the new one, or return the first
|
||||||
|
pg = index-1 >= 0 ? this.pages[index-1] : this.pages[0];
|
||||||
|
if(pg !== undefined) {
|
||||||
|
// Add the new page in so that the locations and page array match up
|
||||||
|
//this.pages.splice(index, 0, pg);
|
||||||
|
} else {
|
||||||
|
pg = -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
return pg;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get an EpubCFI from a Page List Item
|
||||||
|
* @param {string | number} pg
|
||||||
|
* @return {string} cfi
|
||||||
|
*/
|
||||||
|
cfiFromPage(pg){
|
||||||
|
var cfi = -1;
|
||||||
|
// check that pg is an int
|
||||||
|
if(typeof pg != "number"){
|
||||||
|
pg = parseInt(pg);
|
||||||
|
}
|
||||||
|
|
||||||
|
// check if the cfi is in the page list
|
||||||
|
// Pages could be unsorted.
|
||||||
|
var index = this.pages.indexOf(pg);
|
||||||
|
if(index != -1) {
|
||||||
|
cfi = this.locations[index];
|
||||||
|
}
|
||||||
|
// TODO: handle pages not in the list
|
||||||
|
return cfi;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a Page from Book percentage
|
||||||
|
* @param {number} percent
|
||||||
|
* @return {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 page
|
||||||
|
* @param {number} pg the page
|
||||||
|
* @return {number} percentage
|
||||||
|
*/
|
||||||
|
percentageFromPage(pg){
|
||||||
|
var percentage = (pg - this.firstPage) / this.totalPages;
|
||||||
|
return Math.round(percentage * 1000) / 1000;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a value between 0 - 1 corresponding to the location of a cfi
|
||||||
|
* @param {string} cfi EpubCFI String
|
||||||
|
* @return {number} percentage
|
||||||
|
*/
|
||||||
|
percentageFromCfi(cfi){
|
||||||
|
var pg = this.pageFromCfi(cfi);
|
||||||
|
var percentage = this.percentageFromPage(pg);
|
||||||
|
return percentage;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Destroy
|
||||||
|
*/
|
||||||
|
destroy() {
|
||||||
|
this.pages = undefined;
|
||||||
|
this.locations = undefined;
|
||||||
|
this.epubcfi = undefined;
|
||||||
|
|
||||||
|
this.pageList = undefined;
|
||||||
|
|
||||||
|
this.toc = undefined;
|
||||||
|
this.ncx = undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default PageList;
|
||||||
1070
client/static/libs/epubjs-patch/rendition.js
Normal file
1070
client/static/libs/epubjs-patch/rendition.js
Normal file
File diff suppressed because it is too large
Load diff
320
client/static/libs/epubjs-patch/resources.js
Normal file
320
client/static/libs/epubjs-patch/resources.js
Normal file
|
|
@ -0,0 +1,320 @@
|
||||||
|
import {substitute} from "./utils/replacements";
|
||||||
|
import {createBase64Url, createBlobUrl, blob2base64} from "./utils/core";
|
||||||
|
import Url from "./utils/url";
|
||||||
|
import mime from "./utils/mime";
|
||||||
|
import Path from "./utils/path";
|
||||||
|
import path from "path-webpack";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle Package Resources
|
||||||
|
* @class
|
||||||
|
* @param {Manifest} manifest
|
||||||
|
* @param {object} [options]
|
||||||
|
* @param {string} [options.replacements="base64"]
|
||||||
|
* @param {Archive} [options.archive]
|
||||||
|
* @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)
|
||||||
|
};
|
||||||
|
|
||||||
|
this.process(manifest);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Process resources
|
||||||
|
* @param {Manifest} manifest
|
||||||
|
*/
|
||||||
|
process(manifest){
|
||||||
|
this.manifest = manifest;
|
||||||
|
this.resources = Object.keys(manifest).
|
||||||
|
map(function (key){
|
||||||
|
return manifest[key];
|
||||||
|
});
|
||||||
|
|
||||||
|
this.replacementUrls = [];
|
||||||
|
|
||||||
|
this.html = [];
|
||||||
|
this.assets = [];
|
||||||
|
this.css = [];
|
||||||
|
|
||||||
|
this.urls = [];
|
||||||
|
this.cssUrls = [];
|
||||||
|
|
||||||
|
this.split();
|
||||||
|
this.splitUrls();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Exclude HTML
|
||||||
|
this.assets = this.resources.
|
||||||
|
filter(function (item){
|
||||||
|
if (item.type !== "application/xhtml+xml" &&
|
||||||
|
item.type !== "text/html") {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Only CSS
|
||||||
|
this.css = this.resources.
|
||||||
|
filter(function (item){
|
||||||
|
if (item.type === "text/css") {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert split resources into Urls
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
splitUrls(){
|
||||||
|
|
||||||
|
// All Assets Urls
|
||||||
|
this.urls = this.assets.
|
||||||
|
map(function(item) {
|
||||||
|
return item.href;
|
||||||
|
}.bind(this));
|
||||||
|
|
||||||
|
// Css Urls
|
||||||
|
this.cssUrls = this.css.map(function(item) {
|
||||||
|
return item.href;
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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);
|
||||||
|
|
||||||
|
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));
|
||||||
|
}
|
||||||
|
|
||||||
|
var replacements = this.urls.map( (url) => {
|
||||||
|
var absolute = this.settings.resolver(url);
|
||||||
|
|
||||||
|
return this.createUrl(absolute).
|
||||||
|
catch((err) => {
|
||||||
|
console.error(err);
|
||||||
|
return null;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
return Promise.all(replacements)
|
||||||
|
.then( (replacementUrls) => {
|
||||||
|
this.replacementUrls = replacementUrls.filter((url) => {
|
||||||
|
return (typeof(url) === "string");
|
||||||
|
});
|
||||||
|
return replacementUrls;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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))
|
||||||
|
|
||||||
|
|
||||||
|
replaced.push(replacement);
|
||||||
|
}.bind(this));
|
||||||
|
return Promise.all(replaced);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 (path.isAbsolute(href)) {
|
||||||
|
return new Promise(function(resolve){
|
||||||
|
resolve();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
var absolute = this.settings.resolver(href);
|
||||||
|
|
||||||
|
// Get the text of the css file from the archive
|
||||||
|
var textResponse;
|
||||||
|
|
||||||
|
if (this.settings.archive) {
|
||||||
|
textResponse = this.settings.archive.getText(absolute);
|
||||||
|
} else {
|
||||||
|
textResponse = this.settings.request(absolute, "text");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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);
|
||||||
|
|
||||||
|
return relative;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!textResponse) {
|
||||||
|
// file not found, don't replace
|
||||||
|
return new Promise(function(resolve){
|
||||||
|
resolve();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return textResponse.then( (text) => {
|
||||||
|
// Replacements in the css text
|
||||||
|
text = substitute(text, relUrls, this.replacementUrls);
|
||||||
|
|
||||||
|
// Get the new url
|
||||||
|
if (this.settings.replacements === "base64") {
|
||||||
|
newUrl = createBase64Url(text, "text/css");
|
||||||
|
} else {
|
||||||
|
newUrl = createBlobUrl(text, "text/css");
|
||||||
|
}
|
||||||
|
|
||||||
|
return newUrl;
|
||||||
|
}, (err) => {
|
||||||
|
// handle response errors
|
||||||
|
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;
|
||||||
|
|
||||||
|
// 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Resources;
|
||||||
323
client/static/libs/epubjs-patch/section.js
Normal file
323
client/static/libs/epubjs-patch/section.js
Normal file
|
|
@ -0,0 +1,323 @@
|
||||||
|
import { defer } from "./utils/core";
|
||||||
|
import EpubCFI from "./epubcfi";
|
||||||
|
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
|
||||||
|
*
|
||||||
|
* In most books this is equivalent to a Chapter
|
||||||
|
* @param {object} item The spine item representing the section
|
||||||
|
* @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;
|
||||||
|
|
||||||
|
this.cfiBase = item.cfiBase;
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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;
|
||||||
|
|
||||||
|
this.document = xml;
|
||||||
|
this.contents = xml.documentElement;
|
||||||
|
|
||||||
|
return this.hooks.content.trigger(this.document, this);
|
||||||
|
}.bind(this))
|
||||||
|
.then(function(){
|
||||||
|
loading.resolve(this.contents);
|
||||||
|
}.bind(this))
|
||||||
|
.catch(function(error){
|
||||||
|
loading.reject(error);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return loaded;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a base tag for resolving urls in the section
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
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?
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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);
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
|
// 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
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
last = pos;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
sprint(section.document, function(node) {
|
||||||
|
find(node);
|
||||||
|
});
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
};
|
||||||
|
|
||||||
|
//-- Get the chapter's display type
|
||||||
|
this.properties.forEach(function(prop){
|
||||||
|
var rendition = prop.replace("rendition:", "");
|
||||||
|
var split = rendition.indexOf("-");
|
||||||
|
var property, value;
|
||||||
|
|
||||||
|
if(split != -1){
|
||||||
|
property = rendition.slice(0, split);
|
||||||
|
value = rendition.slice(split+1);
|
||||||
|
|
||||||
|
settings[property] = value;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return settings;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a CFI from a Range in the Section
|
||||||
|
* @param {range} _range
|
||||||
|
* @return {string} cfi an EpubCFI string
|
||||||
|
*/
|
||||||
|
cfiFromRange(_range) {
|
||||||
|
return new EpubCFI(_range, this.cfiBase).toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a CFI from an Element in the Section
|
||||||
|
* @param {element} el
|
||||||
|
* @return {string} cfi an EpubCFI string
|
||||||
|
*/
|
||||||
|
cfiFromElement(el) {
|
||||||
|
return new EpubCFI(el, this.cfiBase).toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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();
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Section;
|
||||||
274
client/static/libs/epubjs-patch/spine.js
Normal file
274
client/static/libs/epubjs-patch/spine.js
Normal file
|
|
@ -0,0 +1,274 @@
|
||||||
|
import EpubCFI from "./epubcfi";
|
||||||
|
import Hook from "./utils/hook";
|
||||||
|
import Section from "./section";
|
||||||
|
import {replaceBase, replaceCanonical, replaceMeta} from "./utils/replacements";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A collection of Spine Items
|
||||||
|
*/
|
||||||
|
class Spine {
|
||||||
|
constructor() {
|
||||||
|
this.spineItems = [];
|
||||||
|
this.spineByHref = {};
|
||||||
|
this.spineById = {};
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
|
this.epubcfi = new EpubCFI();
|
||||||
|
|
||||||
|
this.loaded = false;
|
||||||
|
|
||||||
|
this.items = undefined;
|
||||||
|
this.manifest = undefined;
|
||||||
|
this.spineNodeIndex = undefined;
|
||||||
|
this.baseUrl = undefined;
|
||||||
|
this.length = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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.forEach( (item, index) => {
|
||||||
|
var manifestItem = this.manifest[item.idref];
|
||||||
|
var spineItem;
|
||||||
|
|
||||||
|
item.index = index;
|
||||||
|
item.cfiBase = this.epubcfi.generateChapterComponent(this.spineNodeIndex, item.index, item.id);
|
||||||
|
|
||||||
|
if (item.href) {
|
||||||
|
item.url = resolver(item.href, true);
|
||||||
|
item.canonical = canonical(item.href);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(manifestItem) {
|
||||||
|
item.href = manifestItem.href;
|
||||||
|
item.url = resolver(item.href, true);
|
||||||
|
item.canonical = canonical(item.href);
|
||||||
|
|
||||||
|
if(manifestItem.properties.length){
|
||||||
|
item.properties.push.apply(item.properties, manifestItem.properties);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
spineItem = new Section(item, this.hooks);
|
||||||
|
|
||||||
|
this.append(spineItem);
|
||||||
|
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
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 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);
|
||||||
|
|
||||||
|
// 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;
|
||||||
|
|
||||||
|
this.spineById[section.idref] = index;
|
||||||
|
|
||||||
|
return index;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prepend a Section to the Spine
|
||||||
|
* @private
|
||||||
|
* @param {Section} section
|
||||||
|
*/
|
||||||
|
prepend(section) {
|
||||||
|
// var index = this.spineItems.unshift(section);
|
||||||
|
this.spineByHref[section.href] = 0;
|
||||||
|
this.spineById[section.idref] = 0;
|
||||||
|
|
||||||
|
// Re-index
|
||||||
|
this.spineItems.forEach(function(item, index){
|
||||||
|
item.index = index;
|
||||||
|
});
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// insert(section, index) {
|
||||||
|
//
|
||||||
|
// };
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove a Section from the Spine
|
||||||
|
* @private
|
||||||
|
* @param {Section} section
|
||||||
|
*/
|
||||||
|
remove(section) {
|
||||||
|
var index = this.spineItems.indexOf(section);
|
||||||
|
|
||||||
|
if(index > -1) {
|
||||||
|
delete this.spineByHref[section.href];
|
||||||
|
delete this.spineById[section.idref];
|
||||||
|
|
||||||
|
return this.spineItems.splice(index, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loop over the Sections in the Spine
|
||||||
|
* @return {method} forEach
|
||||||
|
*/
|
||||||
|
each() {
|
||||||
|
return this.spineItems.forEach.apply(this.spineItems, arguments);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find the first Section in the Spine
|
||||||
|
* @return {Section} first section
|
||||||
|
*/
|
||||||
|
first() {
|
||||||
|
let index = 0;
|
||||||
|
|
||||||
|
do {
|
||||||
|
let next = this.get(index);
|
||||||
|
|
||||||
|
if (next && next.linear) {
|
||||||
|
return next;
|
||||||
|
}
|
||||||
|
index += 1;
|
||||||
|
} while (index < this.spineItems.length) ;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find the last Section in the Spine
|
||||||
|
* @return {Section} last section
|
||||||
|
*/
|
||||||
|
last() {
|
||||||
|
let index = this.spineItems.length-1;
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Spine;
|
||||||
385
client/static/libs/epubjs-patch/store.js
Normal file
385
client/static/libs/epubjs-patch/store.js
Normal file
|
|
@ -0,0 +1,385 @@
|
||||||
|
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 "events";
|
||||||
|
import localforage from "localforage";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles saving and requesting files from local storage
|
||||||
|
* @class
|
||||||
|
* @param {string} name This should be the name of the application for modals
|
||||||
|
* @param {function} [requester]
|
||||||
|
* @param {function} [resolver]
|
||||||
|
*/
|
||||||
|
class Store extends EventEmitter {
|
||||||
|
|
||||||
|
constructor(name, requester, resolver) {
|
||||||
|
super();
|
||||||
|
this.urlCache = {};
|
||||||
|
|
||||||
|
this.storage = undefined;
|
||||||
|
|
||||||
|
this.name = name;
|
||||||
|
this.requester = requester || httpRequest;
|
||||||
|
this.resolver = resolver;
|
||||||
|
|
||||||
|
this.online = true;
|
||||||
|
|
||||||
|
this.checkRequirements();
|
||||||
|
|
||||||
|
this.addListeners();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add online and offline event listeners
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
addListeners() {
|
||||||
|
this._status = this.status.bind(this);
|
||||||
|
window.addEventListener('online', this._status);
|
||||||
|
window.addEventListener('offline', this._status);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove online and offline event listeners
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
removeListeners() {
|
||||||
|
window.removeEventListener('online', this._status);
|
||||||
|
window.removeEventListener('offline', this._status);
|
||||||
|
this._status = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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);
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 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;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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);
|
||||||
|
|
||||||
|
// If type isn't set, determine it from the file extension
|
||||||
|
if(!type) {
|
||||||
|
type = path.extension;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(type == "blob"){
|
||||||
|
response = this.getBlob(url);
|
||||||
|
} else {
|
||||||
|
response = this.getText(url);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
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;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle the response from request
|
||||||
|
* @private
|
||||||
|
* @param {any} response
|
||||||
|
* @param {string} [type]
|
||||||
|
* @return {any} the parsed result
|
||||||
|
*/
|
||||||
|
handleResponse(response, type){
|
||||||
|
var r;
|
||||||
|
|
||||||
|
if(type == "json") {
|
||||||
|
r = JSON.parse(response);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
if(isXml(type)) {
|
||||||
|
r = parse(response, "text/xml");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
if(type == "xhtml") {
|
||||||
|
r = parse(response, "application/xhtml+xml");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
if(type == "html" || type == "htm") {
|
||||||
|
r = parse(response, "text/html");
|
||||||
|
} else {
|
||||||
|
r = response;
|
||||||
|
}
|
||||||
|
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a Blob from Storage by Url
|
||||||
|
* @param {string} url
|
||||||
|
* @param {string} [mimeType]
|
||||||
|
* @return {Blob}
|
||||||
|
*/
|
||||||
|
getBlob(url, mimeType){
|
||||||
|
let encodedUrl = window.encodeURIComponent(url);
|
||||||
|
|
||||||
|
return this.storage.getItem(encodedUrl).then(function(uint8array) {
|
||||||
|
if(!uint8array) return;
|
||||||
|
|
||||||
|
mimeType = mimeType || mime.lookup(url);
|
||||||
|
|
||||||
|
return new Blob([uint8array], {type : mimeType});
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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);
|
||||||
|
|
||||||
|
return this.storage.getItem(encodedUrl).then(function(uint8array) {
|
||||||
|
var deferred = new defer();
|
||||||
|
var reader = new FileReader();
|
||||||
|
var blob;
|
||||||
|
|
||||||
|
if(!uint8array) return;
|
||||||
|
|
||||||
|
blob = new Blob([uint8array], {type : mimeType});
|
||||||
|
|
||||||
|
reader.addEventListener("loadend", () => {
|
||||||
|
deferred.resolve(reader.result);
|
||||||
|
});
|
||||||
|
|
||||||
|
reader.readAsText(blob, 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);
|
||||||
|
|
||||||
|
mimeType = mimeType || mime.lookup(url);
|
||||||
|
|
||||||
|
return this.storage.getItem(encodedUrl).then((uint8array) => {
|
||||||
|
var deferred = new defer();
|
||||||
|
var reader = new FileReader();
|
||||||
|
var blob;
|
||||||
|
|
||||||
|
if(!uint8array) return;
|
||||||
|
|
||||||
|
blob = new Blob([uint8array], {type : mimeType});
|
||||||
|
|
||||||
|
reader.addEventListener("loadend", () => {
|
||||||
|
deferred.resolve(reader.result);
|
||||||
|
});
|
||||||
|
reader.readAsDataURL(blob, mimeType);
|
||||||
|
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Object.assign(Store.prototype, EventEmitter.prototype);
|
||||||
|
|
||||||
|
export default Store;
|
||||||
268
client/static/libs/epubjs-patch/themes.js
Normal file
268
client/static/libs/epubjs-patch/themes.js
Normal file
|
|
@ -0,0 +1,268 @@
|
||||||
|
import Url from "./utils/url";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Themes to apply to displayed content
|
||||||
|
* @class
|
||||||
|
* @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));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Select a theme
|
||||||
|
* @param {string} name
|
||||||
|
*/
|
||||||
|
select (name) {
|
||||||
|
var prev = this._current;
|
||||||
|
var contents;
|
||||||
|
|
||||||
|
this._current = name;
|
||||||
|
this.update(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);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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];
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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
|
||||||
|
};
|
||||||
|
|
||||||
|
contents.forEach( (content) => {
|
||||||
|
content.css(name, this._overrides[name].value, this._overrides[name].priority);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
removeOverride (name) {
|
||||||
|
var contents = this.rendition.getContents();
|
||||||
|
|
||||||
|
delete this._overrides[name];
|
||||||
|
|
||||||
|
contents.forEach( (content) => {
|
||||||
|
content.css(name);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Themes;
|
||||||
62
client/static/libs/epubjs-patch/utils/constants.js
Normal file
62
client/static/libs/epubjs-patch/utils/constants.js
Normal file
|
|
@ -0,0 +1,62 @@
|
||||||
|
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 EVENTS = {
|
||||||
|
BOOK : {
|
||||||
|
OPEN_FAILED : "openFailed"
|
||||||
|
},
|
||||||
|
CONTENTS : {
|
||||||
|
EXPAND : "expand",
|
||||||
|
RESIZE : "resize",
|
||||||
|
SELECTED : "selected",
|
||||||
|
SELECTED_RANGE : "selectedRange",
|
||||||
|
LINK_CLICKED : "linkClicked"
|
||||||
|
},
|
||||||
|
LOCATIONS : {
|
||||||
|
CHANGED : "changed"
|
||||||
|
},
|
||||||
|
MANAGERS : {
|
||||||
|
RESIZE : "resize",
|
||||||
|
RESIZED : "resized",
|
||||||
|
ORIENTATION_CHANGE : "orientationchange",
|
||||||
|
ADDED : "added",
|
||||||
|
SCROLL : "scroll",
|
||||||
|
SCROLLED : "scrolled",
|
||||||
|
REMOVED : "removed",
|
||||||
|
},
|
||||||
|
VIEWS : {
|
||||||
|
AXIS: "axis",
|
||||||
|
WRITING_MODE: "writingMode",
|
||||||
|
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"
|
||||||
|
},
|
||||||
|
LAYOUT : {
|
||||||
|
UPDATED : "updated"
|
||||||
|
},
|
||||||
|
ANNOTATION : {
|
||||||
|
ATTACH : "attach",
|
||||||
|
DETACH : "detach"
|
||||||
|
}
|
||||||
|
}
|
||||||
876
client/static/libs/epubjs-patch/utils/core.js
Normal file
876
client/static/libs/epubjs-patch/utils/core.js
Normal file
|
|
@ -0,0 +1,876 @@
|
||||||
|
/**
|
||||||
|
* Core Utilities and Helpers
|
||||||
|
* @module Core
|
||||||
|
*/
|
||||||
|
import { DOMParser as XMLDOMParser } from "@xmldom/xmldom";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Vendor prefixed requestAnimationFrame
|
||||||
|
* @returns {function} requestAnimationFrame
|
||||||
|
* @memberof Core
|
||||||
|
*/
|
||||||
|
export const requestAnimationFrame = (typeof window != "undefined") ? (window.requestAnimationFrame || window.mozRequestAnimationFrame || window.webkitRequestAnimationFrame || window.msRequestAnimationFrame) : false;
|
||||||
|
const ELEMENT_NODE = 1;
|
||||||
|
const TEXT_NODE = 3;
|
||||||
|
const COMMENT_NODE = 8;
|
||||||
|
const DOCUMENT_NODE = 9;
|
||||||
|
const _URL = typeof URL != "undefined" ? URL : (typeof window != "undefined" ? (window.URL || window.webkitURL || window.mozURL) : undefined);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates a UUID
|
||||||
|
* based on: http://stackoverflow.com/questions/105034/how-to-create-a-guid-uuid-in-javascript
|
||||||
|
* @returns {string} uuid
|
||||||
|
* @memberof Core
|
||||||
|
*/
|
||||||
|
export function uuid() {
|
||||||
|
var d = new Date().getTime();
|
||||||
|
var uuid = "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, function(c) {
|
||||||
|
var r = (d + Math.random()*16)%16 | 0;
|
||||||
|
d = Math.floor(d/16);
|
||||||
|
return (c=="x" ? r : (r&0x7|0x8)).toString(16);
|
||||||
|
});
|
||||||
|
return uuid;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the height of a document
|
||||||
|
* @returns {number} height
|
||||||
|
* @memberof Core
|
||||||
|
*/
|
||||||
|
export function documentHeight() {
|
||||||
|
return Math.max(
|
||||||
|
document.documentElement.clientHeight,
|
||||||
|
document.body.scrollHeight,
|
||||||
|
document.documentElement.scrollHeight,
|
||||||
|
document.body.offsetHeight,
|
||||||
|
document.documentElement.offsetHeight
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if a node is an element
|
||||||
|
* @param {object} obj
|
||||||
|
* @returns {boolean}
|
||||||
|
* @memberof Core
|
||||||
|
*/
|
||||||
|
export function isElement(obj) {
|
||||||
|
return !!(obj && obj.nodeType == 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {any} n
|
||||||
|
* @returns {boolean}
|
||||||
|
* @memberof Core
|
||||||
|
*/
|
||||||
|
export function isNumber(n) {
|
||||||
|
return !isNaN(parseFloat(n)) && isFinite(n);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {any} n
|
||||||
|
* @returns {boolean}
|
||||||
|
* @memberof Core
|
||||||
|
*/
|
||||||
|
export function isFloat(n) {
|
||||||
|
let f = parseFloat(n);
|
||||||
|
|
||||||
|
if (isNumber(n) === false) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof n === "string" && n.indexOf(".") > -1) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Math.floor(f) !== f;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a prefixed css property
|
||||||
|
* @param {string} unprefixed
|
||||||
|
* @returns {string}
|
||||||
|
* @memberof Core
|
||||||
|
*/
|
||||||
|
export function prefixed(unprefixed) {
|
||||||
|
var vendors = ["Webkit", "webkit", "Moz", "O", "ms" ];
|
||||||
|
var prefixes = ["-webkit-", "-webkit-", "-moz-", "-o-", "-ms-"];
|
||||||
|
var lower = unprefixed.toLowerCase();
|
||||||
|
var length = vendors.length;
|
||||||
|
|
||||||
|
if (typeof(document) === "undefined" || typeof(document.body.style[lower]) != "undefined") {
|
||||||
|
return unprefixed;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var i = 0; i < length; i++) {
|
||||||
|
if (typeof(document.body.style[prefixes[i] + lower]) != "undefined") {
|
||||||
|
return prefixes[i] + lower;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return unprefixed;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Apply defaults to an object
|
||||||
|
* @param {object} obj
|
||||||
|
* @returns {object}
|
||||||
|
* @memberof Core
|
||||||
|
*/
|
||||||
|
export function defaults(obj) {
|
||||||
|
for (var i = 1, length = arguments.length; i < length; i++) {
|
||||||
|
var source = arguments[i];
|
||||||
|
for (var prop in source) {
|
||||||
|
if (obj[prop] === void 0) obj[prop] = source[prop];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return obj;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extend properties of an object
|
||||||
|
* @param {object} target
|
||||||
|
* @returns {object}
|
||||||
|
* @memberof Core
|
||||||
|
*/
|
||||||
|
export function extend(target) {
|
||||||
|
var sources = [].slice.call(arguments, 1);
|
||||||
|
sources.forEach(function (source) {
|
||||||
|
if(!source) return;
|
||||||
|
Object.getOwnPropertyNames(source).forEach(function(propName) {
|
||||||
|
Object.defineProperty(target, propName, Object.getOwnPropertyDescriptor(source, propName));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
return target;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fast quicksort insert for sorted array -- based on:
|
||||||
|
* http://stackoverflow.com/questions/1344500/efficient-way-to-insert-a-number-into-a-sorted-array-of-numbers
|
||||||
|
* @param {any} item
|
||||||
|
* @param {array} array
|
||||||
|
* @param {function} [compareFunction]
|
||||||
|
* @returns {number} location (in array)
|
||||||
|
* @memberof Core
|
||||||
|
*/
|
||||||
|
export function insert(item, array, compareFunction) {
|
||||||
|
var location = locationOf(item, array, compareFunction);
|
||||||
|
array.splice(location, 0, item);
|
||||||
|
|
||||||
|
return location;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Finds where something would fit into a sorted array
|
||||||
|
* @param {any} item
|
||||||
|
* @param {array} array
|
||||||
|
* @param {function} [compareFunction]
|
||||||
|
* @param {function} [_start]
|
||||||
|
* @param {function} [_end]
|
||||||
|
* @returns {number} location (in array)
|
||||||
|
* @memberof Core
|
||||||
|
*/
|
||||||
|
export function locationOf(item, array, compareFunction, _start, _end) {
|
||||||
|
var start = _start || 0;
|
||||||
|
var end = _end || array.length;
|
||||||
|
var pivot = parseInt(start + (end - start) / 2);
|
||||||
|
var compared;
|
||||||
|
if(!compareFunction){
|
||||||
|
compareFunction = function(a, b) {
|
||||||
|
if(a > b) return 1;
|
||||||
|
if(a < b) return -1;
|
||||||
|
if(a == b) return 0;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if(end-start <= 0) {
|
||||||
|
return pivot;
|
||||||
|
}
|
||||||
|
|
||||||
|
compared = compareFunction(array[pivot], item);
|
||||||
|
if(end-start === 1) {
|
||||||
|
return compared >= 0 ? pivot : pivot + 1;
|
||||||
|
}
|
||||||
|
if(compared === 0) {
|
||||||
|
return pivot;
|
||||||
|
}
|
||||||
|
if(compared === -1) {
|
||||||
|
return locationOf(item, array, compareFunction, pivot, end);
|
||||||
|
} else{
|
||||||
|
return locationOf(item, array, compareFunction, start, pivot);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Finds index of something in a sorted array
|
||||||
|
* Returns -1 if not found
|
||||||
|
* @param {any} item
|
||||||
|
* @param {array} array
|
||||||
|
* @param {function} [compareFunction]
|
||||||
|
* @param {function} [_start]
|
||||||
|
* @param {function} [_end]
|
||||||
|
* @returns {number} index (in array) or -1
|
||||||
|
* @memberof Core
|
||||||
|
*/
|
||||||
|
export function indexOfSorted(item, array, compareFunction, _start, _end) {
|
||||||
|
var start = _start || 0;
|
||||||
|
var end = _end || array.length;
|
||||||
|
var pivot = parseInt(start + (end - start) / 2);
|
||||||
|
var compared;
|
||||||
|
if(!compareFunction){
|
||||||
|
compareFunction = function(a, b) {
|
||||||
|
if(a > b) return 1;
|
||||||
|
if(a < b) return -1;
|
||||||
|
if(a == b) return 0;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if(end-start <= 0) {
|
||||||
|
return -1; // Not found
|
||||||
|
}
|
||||||
|
|
||||||
|
compared = compareFunction(array[pivot], item);
|
||||||
|
if(end-start === 1) {
|
||||||
|
return compared === 0 ? pivot : -1;
|
||||||
|
}
|
||||||
|
if(compared === 0) {
|
||||||
|
return pivot; // Found
|
||||||
|
}
|
||||||
|
if(compared === -1) {
|
||||||
|
return indexOfSorted(item, array, compareFunction, pivot, end);
|
||||||
|
} else{
|
||||||
|
return indexOfSorted(item, array, compareFunction, start, pivot);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Find the bounds of an element
|
||||||
|
* taking padding and margin into account
|
||||||
|
* @param {element} el
|
||||||
|
* @returns {{ width: Number, height: Number}}
|
||||||
|
* @memberof Core
|
||||||
|
*/
|
||||||
|
export function bounds(el) {
|
||||||
|
|
||||||
|
var style = window.getComputedStyle(el);
|
||||||
|
var widthProps = ["width", "paddingRight", "paddingLeft", "marginRight", "marginLeft", "borderRightWidth", "borderLeftWidth"];
|
||||||
|
var heightProps = ["height", "paddingTop", "paddingBottom", "marginTop", "marginBottom", "borderTopWidth", "borderBottomWidth"];
|
||||||
|
|
||||||
|
var width = 0;
|
||||||
|
var height = 0;
|
||||||
|
|
||||||
|
widthProps.forEach(function(prop){
|
||||||
|
width += parseFloat(style[prop]) || 0;
|
||||||
|
});
|
||||||
|
|
||||||
|
heightProps.forEach(function(prop){
|
||||||
|
height += parseFloat(style[prop]) || 0;
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
height: height,
|
||||||
|
width: width
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find the bounds of an element
|
||||||
|
* taking padding, margin and borders into account
|
||||||
|
* @param {element} el
|
||||||
|
* @returns {{ width: Number, height: Number}}
|
||||||
|
* @memberof Core
|
||||||
|
*/
|
||||||
|
export function borders(el) {
|
||||||
|
|
||||||
|
var style = window.getComputedStyle(el);
|
||||||
|
var widthProps = ["paddingRight", "paddingLeft", "marginRight", "marginLeft", "borderRightWidth", "borderLeftWidth"];
|
||||||
|
var heightProps = ["paddingTop", "paddingBottom", "marginTop", "marginBottom", "borderTopWidth", "borderBottomWidth"];
|
||||||
|
|
||||||
|
var width = 0;
|
||||||
|
var height = 0;
|
||||||
|
|
||||||
|
widthProps.forEach(function(prop){
|
||||||
|
width += parseFloat(style[prop]) || 0;
|
||||||
|
});
|
||||||
|
|
||||||
|
heightProps.forEach(function(prop){
|
||||||
|
height += parseFloat(style[prop]) || 0;
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
height: height,
|
||||||
|
width: width
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find the bounds of any node
|
||||||
|
* allows for getting bounds of text nodes by wrapping them in a range
|
||||||
|
* @param {node} node
|
||||||
|
* @returns {BoundingClientRect}
|
||||||
|
* @memberof Core
|
||||||
|
*/
|
||||||
|
export function nodeBounds(node) {
|
||||||
|
let elPos;
|
||||||
|
let doc = node.ownerDocument;
|
||||||
|
if(node.nodeType == Node.TEXT_NODE){
|
||||||
|
let elRange = doc.createRange();
|
||||||
|
elRange.selectNodeContents(node);
|
||||||
|
elPos = elRange.getBoundingClientRect();
|
||||||
|
} else {
|
||||||
|
elPos = node.getBoundingClientRect();
|
||||||
|
}
|
||||||
|
return elPos;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find the equivalent of getBoundingClientRect of a browser window
|
||||||
|
* @returns {{ width: Number, height: Number, top: Number, left: Number, right: Number, bottom: Number }}
|
||||||
|
* @memberof Core
|
||||||
|
*/
|
||||||
|
export function windowBounds() {
|
||||||
|
|
||||||
|
var width = window.innerWidth;
|
||||||
|
var height = window.innerHeight;
|
||||||
|
|
||||||
|
return {
|
||||||
|
top: 0,
|
||||||
|
left: 0,
|
||||||
|
right: width,
|
||||||
|
bottom: height,
|
||||||
|
width: width,
|
||||||
|
height: height
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the index of a node in its parent
|
||||||
|
* @param {Node} node
|
||||||
|
* @param {string} typeId
|
||||||
|
* @return {number} index
|
||||||
|
* @memberof Core
|
||||||
|
*/
|
||||||
|
export function indexOfNode(node, typeId) {
|
||||||
|
var parent = node.parentNode;
|
||||||
|
var children = parent.childNodes;
|
||||||
|
var sib;
|
||||||
|
var index = -1;
|
||||||
|
for (var i = 0; i < children.length; i++) {
|
||||||
|
sib = children[i];
|
||||||
|
if (sib.nodeType === typeId) {
|
||||||
|
index++;
|
||||||
|
}
|
||||||
|
if (sib == node) break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return index;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the index of a text node in its parent
|
||||||
|
* @param {node} textNode
|
||||||
|
* @returns {number} index
|
||||||
|
* @memberof Core
|
||||||
|
*/
|
||||||
|
export function indexOfTextNode(textNode) {
|
||||||
|
return indexOfNode(textNode, TEXT_NODE);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the index of an element node in its parent
|
||||||
|
* @param {element} elementNode
|
||||||
|
* @returns {number} index
|
||||||
|
* @memberof Core
|
||||||
|
*/
|
||||||
|
export function indexOfElementNode(elementNode) {
|
||||||
|
return indexOfNode(elementNode, ELEMENT_NODE);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if extension is xml
|
||||||
|
* @param {string} ext
|
||||||
|
* @returns {boolean}
|
||||||
|
* @memberof Core
|
||||||
|
*/
|
||||||
|
export function isXml(ext) {
|
||||||
|
return ["xml", "opf", "ncx"].indexOf(ext) > -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new blob
|
||||||
|
* @param {any} content
|
||||||
|
* @param {string} mime
|
||||||
|
* @returns {Blob}
|
||||||
|
* @memberof Core
|
||||||
|
*/
|
||||||
|
export function createBlob(content, mime){
|
||||||
|
return new Blob([content], {type : mime });
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new blob url
|
||||||
|
* @param {any} content
|
||||||
|
* @param {string} mime
|
||||||
|
* @returns {string} url
|
||||||
|
* @memberof Core
|
||||||
|
*/
|
||||||
|
export function createBlobUrl(content, mime){
|
||||||
|
var tempUrl;
|
||||||
|
var blob = createBlob(content, mime);
|
||||||
|
|
||||||
|
tempUrl = _URL.createObjectURL(blob);
|
||||||
|
|
||||||
|
return tempUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove a blob url
|
||||||
|
* @param {string} url
|
||||||
|
* @memberof Core
|
||||||
|
*/
|
||||||
|
export function revokeBlobUrl(url){
|
||||||
|
return _URL.revokeObjectURL(url);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new base64 encoded url
|
||||||
|
* @param {any} content
|
||||||
|
* @param {string} mime
|
||||||
|
* @returns {string} url
|
||||||
|
* @memberof Core
|
||||||
|
*/
|
||||||
|
export function createBase64Url(content, mime){
|
||||||
|
var data;
|
||||||
|
var datauri;
|
||||||
|
|
||||||
|
if (typeof(content) !== "string") {
|
||||||
|
// Only handles strings
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
data = btoa(content);
|
||||||
|
|
||||||
|
datauri = "data:" + mime + ";base64," + data;
|
||||||
|
|
||||||
|
return datauri;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get type of an object
|
||||||
|
* @param {object} obj
|
||||||
|
* @returns {string} type
|
||||||
|
* @memberof Core
|
||||||
|
*/
|
||||||
|
export function type(obj){
|
||||||
|
return Object.prototype.toString.call(obj).slice(8, -1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse xml (or html) markup
|
||||||
|
* @param {string} markup
|
||||||
|
* @param {string} mime
|
||||||
|
* @param {boolean} forceXMLDom force using xmlDom to parse instead of native parser
|
||||||
|
* @returns {document} document
|
||||||
|
* @memberof Core
|
||||||
|
*/
|
||||||
|
export function parse(markup, mime, forceXMLDom) {
|
||||||
|
var doc;
|
||||||
|
var Parser;
|
||||||
|
|
||||||
|
if (typeof DOMParser === "undefined" || forceXMLDom) {
|
||||||
|
Parser = XMLDOMParser;
|
||||||
|
} else {
|
||||||
|
Parser = DOMParser;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove byte order mark before parsing
|
||||||
|
// https://www.w3.org/International/questions/qa-byte-order-mark
|
||||||
|
if(markup.charCodeAt(0) === 0xFEFF) {
|
||||||
|
markup = markup.slice(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
doc = new Parser().parseFromString(markup, mime);
|
||||||
|
|
||||||
|
return doc;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* querySelector polyfill
|
||||||
|
* @param {element} el
|
||||||
|
* @param {string} sel selector string
|
||||||
|
* @returns {element} element
|
||||||
|
* @memberof Core
|
||||||
|
*/
|
||||||
|
export function qs(el, sel) {
|
||||||
|
var elements;
|
||||||
|
if (!el) {
|
||||||
|
throw new Error("No Element Provided");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof el.querySelector != "undefined") {
|
||||||
|
return el.querySelector(sel);
|
||||||
|
} else {
|
||||||
|
elements = el.getElementsByTagName(sel);
|
||||||
|
if (elements.length) {
|
||||||
|
return elements[0];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* querySelectorAll polyfill
|
||||||
|
* @param {element} el
|
||||||
|
* @param {string} sel selector string
|
||||||
|
* @returns {element[]} elements
|
||||||
|
* @memberof Core
|
||||||
|
*/
|
||||||
|
export function qsa(el, sel) {
|
||||||
|
|
||||||
|
if (typeof el.querySelector != "undefined") {
|
||||||
|
return el.querySelectorAll(sel);
|
||||||
|
} else {
|
||||||
|
return el.getElementsByTagName(sel);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* querySelector by property
|
||||||
|
* @param {element} el
|
||||||
|
* @param {string} sel selector string
|
||||||
|
* @param {object[]} props
|
||||||
|
* @returns {element[]} elements
|
||||||
|
* @memberof Core
|
||||||
|
*/
|
||||||
|
export function qsp(el, sel, props) {
|
||||||
|
var q, filtered;
|
||||||
|
if (typeof el.querySelector != "undefined") {
|
||||||
|
sel += "[";
|
||||||
|
for (var prop in props) {
|
||||||
|
sel += prop + "~='" + props[prop] + "'";
|
||||||
|
}
|
||||||
|
sel += "]";
|
||||||
|
return el.querySelector(sel);
|
||||||
|
} else {
|
||||||
|
q = el.getElementsByTagName(sel);
|
||||||
|
filtered = Array.prototype.slice.call(q, 0).filter(function(el) {
|
||||||
|
for (var prop in props) {
|
||||||
|
if(el.getAttribute(prop) === props[prop]){
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (filtered) {
|
||||||
|
return filtered[0];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sprint through all text nodes in a document
|
||||||
|
* @memberof Core
|
||||||
|
* @param {element} root element to start with
|
||||||
|
* @param {function} func function to run on each element
|
||||||
|
*/
|
||||||
|
export function sprint(root, func) {
|
||||||
|
var doc = root.ownerDocument || root;
|
||||||
|
if (typeof(doc.createTreeWalker) !== "undefined") {
|
||||||
|
treeWalker(root, func, NodeFilter.SHOW_TEXT);
|
||||||
|
} else {
|
||||||
|
walk(root, function(node) {
|
||||||
|
if (node && node.nodeType === 3) { // Node.TEXT_NODE
|
||||||
|
func(node);
|
||||||
|
}
|
||||||
|
}, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a treeWalker
|
||||||
|
* @memberof Core
|
||||||
|
* @param {element} root element to start with
|
||||||
|
* @param {function} func function to run on each element
|
||||||
|
* @param {function | object} filter function or object to filter with
|
||||||
|
*/
|
||||||
|
export function treeWalker(root, func, filter) {
|
||||||
|
var treeWalker = document.createTreeWalker(root, filter, null, false);
|
||||||
|
let node;
|
||||||
|
while ((node = treeWalker.nextNode())) {
|
||||||
|
func(node);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @memberof Core
|
||||||
|
* @param {node} node
|
||||||
|
* @param {callback} return false for continue,true for break inside callback
|
||||||
|
*/
|
||||||
|
export function walk(node,callback){
|
||||||
|
if(callback(node)){
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
node = node.firstChild;
|
||||||
|
if(node){
|
||||||
|
do{
|
||||||
|
let walked = walk(node,callback);
|
||||||
|
if(walked){
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
node = node.nextSibling;
|
||||||
|
} while(node);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert a blob to a base64 encoded string
|
||||||
|
* @param {Blog} blob
|
||||||
|
* @returns {string}
|
||||||
|
* @memberof Core
|
||||||
|
*/
|
||||||
|
export function blob2base64(blob) {
|
||||||
|
return new Promise(function(resolve, reject) {
|
||||||
|
var reader = new FileReader();
|
||||||
|
reader.readAsDataURL(blob);
|
||||||
|
reader.onloadend = function() {
|
||||||
|
resolve(reader.result);
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new pending promise and provides methods to resolve or reject it.
|
||||||
|
* From: https://developer.mozilla.org/en-US/docs/Mozilla/JavaScript_code_modules/Promise.jsm/Deferred#backwards_forwards_compatible
|
||||||
|
* @memberof Core
|
||||||
|
*/
|
||||||
|
export function defer() {
|
||||||
|
/* A method to resolve the associated Promise with the value passed.
|
||||||
|
* If the promise is already settled it does nothing.
|
||||||
|
*
|
||||||
|
* @param {anything} value : This value is used to resolve the promise
|
||||||
|
* If the value is a Promise then the associated promise assumes the state
|
||||||
|
* of Promise passed as value.
|
||||||
|
*/
|
||||||
|
this.resolve = null;
|
||||||
|
|
||||||
|
/* A method to reject the associated Promise with the value passed.
|
||||||
|
* If the promise is already settled it does nothing.
|
||||||
|
*
|
||||||
|
* @param {anything} reason: The reason for the rejection of the Promise.
|
||||||
|
* Generally its an Error object. If however a Promise is passed, then the Promise
|
||||||
|
* itself will be the reason for rejection no matter the state of the Promise.
|
||||||
|
*/
|
||||||
|
this.reject = null;
|
||||||
|
|
||||||
|
this.id = uuid();
|
||||||
|
|
||||||
|
/* A newly created Pomise object.
|
||||||
|
* Initially in pending state.
|
||||||
|
*/
|
||||||
|
this.promise = new Promise((resolve, reject) => {
|
||||||
|
this.resolve = resolve;
|
||||||
|
this.reject = reject;
|
||||||
|
});
|
||||||
|
Object.freeze(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* querySelector with filter by epub type
|
||||||
|
* @param {element} html
|
||||||
|
* @param {string} element element type to find
|
||||||
|
* @param {string} type epub type to find
|
||||||
|
* @returns {element[]} elements
|
||||||
|
* @memberof Core
|
||||||
|
*/
|
||||||
|
export function querySelectorByType(html, element, type){
|
||||||
|
var query;
|
||||||
|
if (typeof html.querySelector != "undefined") {
|
||||||
|
query = html.querySelector(`${element}[*|type="${type}"]`);
|
||||||
|
}
|
||||||
|
// Handle IE not supporting namespaced epub:type in querySelector
|
||||||
|
if(!query || query.length === 0) {
|
||||||
|
query = qsa(html, element);
|
||||||
|
for (var i = 0; i < query.length; i++) {
|
||||||
|
if(query[i].getAttributeNS("http://www.idpf.org/2007/ops", "type") === type ||
|
||||||
|
query[i].getAttribute("epub:type") === type) {
|
||||||
|
return query[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return query;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find direct descendents of an element
|
||||||
|
* @param {element} el
|
||||||
|
* @returns {element[]} children
|
||||||
|
* @memberof Core
|
||||||
|
*/
|
||||||
|
export function findChildren(el) {
|
||||||
|
var result = [];
|
||||||
|
var childNodes = el.childNodes;
|
||||||
|
for (var i = 0; i < childNodes.length; i++) {
|
||||||
|
let node = childNodes[i];
|
||||||
|
if (node.nodeType === 1) {
|
||||||
|
result.push(node);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find all parents (ancestors) of an element
|
||||||
|
* @param {element} node
|
||||||
|
* @returns {element[]} parents
|
||||||
|
* @memberof Core
|
||||||
|
*/
|
||||||
|
export function parents(node) {
|
||||||
|
var nodes = [node];
|
||||||
|
for (; node; node = node.parentNode) {
|
||||||
|
nodes.unshift(node);
|
||||||
|
}
|
||||||
|
return nodes
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find all direct descendents of a specific type
|
||||||
|
* @param {element} el
|
||||||
|
* @param {string} nodeName
|
||||||
|
* @param {boolean} [single]
|
||||||
|
* @returns {element[]} children
|
||||||
|
* @memberof Core
|
||||||
|
*/
|
||||||
|
export function filterChildren(el, nodeName, single) {
|
||||||
|
var result = [];
|
||||||
|
var childNodes = el.childNodes;
|
||||||
|
for (var i = 0; i < childNodes.length; i++) {
|
||||||
|
let node = childNodes[i];
|
||||||
|
if (node.nodeType === 1 && node.nodeName.toLowerCase() === nodeName) {
|
||||||
|
if (single) {
|
||||||
|
return node;
|
||||||
|
} else {
|
||||||
|
result.push(node);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!single) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Filter all parents (ancestors) with tag name
|
||||||
|
* @param {element} node
|
||||||
|
* @param {string} tagname
|
||||||
|
* @returns {element[]} parents
|
||||||
|
* @memberof Core
|
||||||
|
*/
|
||||||
|
export function getParentByTagName(node, tagname) {
|
||||||
|
let parent;
|
||||||
|
if (node === null || tagname === '') return;
|
||||||
|
parent = node.parentNode;
|
||||||
|
while (parent.nodeType === 1) {
|
||||||
|
if (parent.tagName.toLowerCase() === tagname) {
|
||||||
|
return parent;
|
||||||
|
}
|
||||||
|
parent = parent.parentNode;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Lightweight Polyfill for DOM Range
|
||||||
|
* @class
|
||||||
|
* @memberof Core
|
||||||
|
*/
|
||||||
|
export class RangeObject {
|
||||||
|
constructor() {
|
||||||
|
this.collapsed = false;
|
||||||
|
this.commonAncestorContainer = undefined;
|
||||||
|
this.endContainer = undefined;
|
||||||
|
this.endOffset = undefined;
|
||||||
|
this.startContainer = undefined;
|
||||||
|
this.startOffset = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
setStart(startNode, startOffset) {
|
||||||
|
this.startContainer = startNode;
|
||||||
|
this.startOffset = startOffset;
|
||||||
|
|
||||||
|
if (!this.endContainer) {
|
||||||
|
this.collapse(true);
|
||||||
|
} else {
|
||||||
|
this.commonAncestorContainer = this._commonAncestorContainer();
|
||||||
|
}
|
||||||
|
|
||||||
|
this._checkCollapsed();
|
||||||
|
}
|
||||||
|
|
||||||
|
setEnd(endNode, endOffset) {
|
||||||
|
this.endContainer = endNode;
|
||||||
|
this.endOffset = endOffset;
|
||||||
|
|
||||||
|
if (!this.startContainer) {
|
||||||
|
this.collapse(false);
|
||||||
|
} else {
|
||||||
|
this.collapsed = false;
|
||||||
|
this.commonAncestorContainer = this._commonAncestorContainer();
|
||||||
|
}
|
||||||
|
|
||||||
|
this._checkCollapsed();
|
||||||
|
}
|
||||||
|
|
||||||
|
collapse(toStart) {
|
||||||
|
this.collapsed = true;
|
||||||
|
if (toStart) {
|
||||||
|
this.endContainer = this.startContainer;
|
||||||
|
this.endOffset = this.startOffset;
|
||||||
|
this.commonAncestorContainer = this.startContainer.parentNode;
|
||||||
|
} else {
|
||||||
|
this.startContainer = this.endContainer;
|
||||||
|
this.startOffset = this.endOffset;
|
||||||
|
this.commonAncestorContainer = this.endOffset.parentNode;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
selectNode(referenceNode) {
|
||||||
|
let parent = referenceNode.parentNode;
|
||||||
|
let index = Array.prototype.indexOf.call(parent.childNodes, referenceNode);
|
||||||
|
this.setStart(parent, index);
|
||||||
|
this.setEnd(parent, index + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
selectNodeContents(referenceNode) {
|
||||||
|
let end = referenceNode.childNodes[referenceNode.childNodes - 1];
|
||||||
|
let endIndex = (referenceNode.nodeType === 3) ?
|
||||||
|
referenceNode.textContent.length : parent.childNodes.length;
|
||||||
|
this.setStart(referenceNode, 0);
|
||||||
|
this.setEnd(referenceNode, endIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
_commonAncestorContainer(startContainer, endContainer) {
|
||||||
|
var startParents = parents(startContainer || this.startContainer);
|
||||||
|
var endParents = parents(endContainer || this.endContainer);
|
||||||
|
|
||||||
|
if (startParents[0] != endParents[0]) return undefined;
|
||||||
|
|
||||||
|
for (var i = 0; i < startParents.length; i++) {
|
||||||
|
if (startParents[i] != endParents[i]) {
|
||||||
|
return startParents[i - 1];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_checkCollapsed() {
|
||||||
|
if (this.startContainer === this.endContainer &&
|
||||||
|
this.startOffset === this.endOffset) {
|
||||||
|
this.collapsed = true;
|
||||||
|
} else {
|
||||||
|
this.collapsed = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
toString() {
|
||||||
|
// TODO: implement walking between start and end to find text
|
||||||
|
}
|
||||||
|
}
|
||||||
82
client/static/libs/epubjs-patch/utils/hook.js
Normal file
82
client/static/libs/epubjs-patch/utils/hook.js
Normal file
|
|
@ -0,0 +1,82 @@
|
||||||
|
/**
|
||||||
|
* Hooks allow for injecting functions that must all complete in order before finishing
|
||||||
|
* They will execute in parallel but all must finish before continuing
|
||||||
|
* Functions may return a promise if they are async.
|
||||||
|
* @param {any} context scope of this
|
||||||
|
* @example this.content = new EPUBJS.Hook(this);
|
||||||
|
*/
|
||||||
|
class Hook {
|
||||||
|
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]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 = [];
|
||||||
|
|
||||||
|
this.hooks.forEach(function(task) {
|
||||||
|
try {
|
||||||
|
var executing = task.apply(context, args);
|
||||||
|
} catch (err) {
|
||||||
|
console.log(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Adds a function to be run before a hook completes
|
||||||
|
list(){
|
||||||
|
return this.hooks;
|
||||||
|
}
|
||||||
|
|
||||||
|
clear(){
|
||||||
|
return this.hooks = [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
export default Hook;
|
||||||
169
client/static/libs/epubjs-patch/utils/mime.js
Executable file
169
client/static/libs/epubjs-patch/utils/mime.js
Executable file
|
|
@ -0,0 +1,169 @@
|
||||||
|
/*
|
||||||
|
From Zip.js, by Gildas Lormeau
|
||||||
|
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"
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
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";
|
||||||
|
|
||||||
|
function lookup(filename) {
|
||||||
|
return filename && mimeTypes[filename.split(".").pop().toLowerCase()] || defaultValue;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default { lookup };
|
||||||
102
client/static/libs/epubjs-patch/utils/path.js
Normal file
102
client/static/libs/epubjs-patch/utils/path.js
Normal file
|
|
@ -0,0 +1,102 @@
|
||||||
|
import path from "path-webpack";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a Path object for parsing and manipulation of a path strings
|
||||||
|
*
|
||||||
|
* Uses a polyfill for Nodejs path: https://nodejs.org/api/path.html
|
||||||
|
* @param {string} pathString a url string (relative or absolute)
|
||||||
|
* @class
|
||||||
|
*/
|
||||||
|
class Path {
|
||||||
|
constructor(pathString) {
|
||||||
|
var protocol;
|
||||||
|
var parsed;
|
||||||
|
|
||||||
|
protocol = pathString.indexOf("://");
|
||||||
|
if (protocol > -1) {
|
||||||
|
pathString = new URL(pathString).pathname;
|
||||||
|
}
|
||||||
|
|
||||||
|
parsed = this.parse(pathString);
|
||||||
|
|
||||||
|
this.path = pathString;
|
||||||
|
|
||||||
|
if (this.isDirectory(pathString)) {
|
||||||
|
this.directory = pathString;
|
||||||
|
} else {
|
||||||
|
this.directory = parsed.dir + "/";
|
||||||
|
}
|
||||||
|
|
||||||
|
this.filename = parsed.base;
|
||||||
|
this.extension = parsed.ext.slice(1);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
return path.relative(this.directory, what);
|
||||||
|
}
|
||||||
|
|
||||||
|
splitPath(filename) {
|
||||||
|
return this.splitPathRe.exec(filename).slice(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the path string
|
||||||
|
* @returns {string} path
|
||||||
|
*/
|
||||||
|
toString () {
|
||||||
|
return this.path;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Path;
|
||||||
246
client/static/libs/epubjs-patch/utils/queue.js
Normal file
246
client/static/libs/epubjs-patch/utils/queue.js
Normal file
|
|
@ -0,0 +1,246 @@
|
||||||
|
import {defer, requestAnimationFrame} from "./core";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Queue for handling tasks one at a time
|
||||||
|
* @class
|
||||||
|
* @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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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");
|
||||||
|
}
|
||||||
|
|
||||||
|
if(typeof task === "function"){
|
||||||
|
|
||||||
|
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
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
this._q.push(queued);
|
||||||
|
|
||||||
|
// Wait to start queue flush
|
||||||
|
if (this.paused == false && !this.running) {
|
||||||
|
// setTimeout(this.flush.bind(this), 0);
|
||||||
|
// this.tick.call(window, this.run.bind(this));
|
||||||
|
this.run();
|
||||||
|
}
|
||||||
|
|
||||||
|
return queued.promise;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Run one item
|
||||||
|
* @return {Promise}
|
||||||
|
*/
|
||||||
|
dequeue(){
|
||||||
|
var inwait, task, result;
|
||||||
|
|
||||||
|
if(this._q.length && !this.paused) {
|
||||||
|
inwait = this._q.shift();
|
||||||
|
task = inwait.task;
|
||||||
|
if(task){
|
||||||
|
// console.log(task)
|
||||||
|
|
||||||
|
result = task.apply(this.context, inwait.args);
|
||||||
|
|
||||||
|
if(result && typeof result["then"] === "function") {
|
||||||
|
// Task is a function that returns a promise
|
||||||
|
return result.then(function(){
|
||||||
|
inwait.deferred.resolve.apply(this.context, arguments);
|
||||||
|
}.bind(this), 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run All Immediately
|
||||||
|
dump(){
|
||||||
|
while(this._q.length) {
|
||||||
|
this.dequeue();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Run all tasks sequentially, at convince
|
||||||
|
* @return {Promise}
|
||||||
|
*/
|
||||||
|
run(){
|
||||||
|
|
||||||
|
if(!this.running){
|
||||||
|
this.running = true;
|
||||||
|
this.defered = new defer();
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new task from a callback
|
||||||
|
* @class
|
||||||
|
* @private
|
||||||
|
* @param {function} task
|
||||||
|
* @param {array} args
|
||||||
|
* @param {scope} context
|
||||||
|
* @return {function} task
|
||||||
|
*/
|
||||||
|
class Task {
|
||||||
|
constructor(task, args, context){
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
|
// Apply all arguments to the functions
|
||||||
|
task.apply(context || this, toApply);
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export default Queue;
|
||||||
|
export { Task };
|
||||||
138
client/static/libs/epubjs-patch/utils/replacements.js
Normal file
138
client/static/libs/epubjs-patch/utils/replacements.js
Normal file
|
|
@ -0,0 +1,138 @@
|
||||||
|
import { qs, qsa } 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);
|
||||||
|
|
||||||
|
if(!doc){
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
head = qs(doc, "head");
|
||||||
|
base = qs(head, "base");
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
base.setAttribute("href", url);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function replaceCanonical(doc, section){
|
||||||
|
var head;
|
||||||
|
var link;
|
||||||
|
var url = section.canonical;
|
||||||
|
|
||||||
|
if(!doc){
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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']");
|
||||||
|
|
||||||
|
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]");
|
||||||
|
|
||||||
|
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");
|
||||||
|
|
||||||
|
if(href.indexOf("mailto:") === 0){
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var absolute = (href.indexOf("://") > -1);
|
||||||
|
|
||||||
|
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]);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
150
client/static/libs/epubjs-patch/utils/request.js
Normal file
150
client/static/libs/epubjs-patch/utils/request.js
Normal file
|
|
@ -0,0 +1,150 @@
|
||||||
|
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 deferred = new defer();
|
||||||
|
|
||||||
|
var xhr = new XMLHttpRequest();
|
||||||
|
|
||||||
|
//-- Check from PDF.js:
|
||||||
|
// https://github.com/mozilla/pdf.js/blob/master/web/compatibility.js
|
||||||
|
var xhrPrototype = XMLHttpRequest.prototype;
|
||||||
|
|
||||||
|
var header;
|
||||||
|
|
||||||
|
if (!("overrideMimeType" in xhrPrototype)) {
|
||||||
|
// IE10 might have response, but not overrideMimeType
|
||||||
|
Object.defineProperty(xhrPrototype, "overrideMimeType", {
|
||||||
|
value: function xmlHttpRequestOverrideMimeType() {}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if(withCredentials) {
|
||||||
|
xhr.withCredentials = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
xhr.onreadystatechange = handler;
|
||||||
|
xhr.onerror = err;
|
||||||
|
|
||||||
|
xhr.open("GET", url, true);
|
||||||
|
|
||||||
|
for(header in headers) {
|
||||||
|
xhr.setRequestHeader(header, headers[header]);
|
||||||
|
}
|
||||||
|
|
||||||
|
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 == "blob"){
|
||||||
|
xhr.responseType = BLOB_RESPONSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if(isXml(type)) {
|
||||||
|
// xhr.responseType = "document";
|
||||||
|
xhr.overrideMimeType("text/xml"); // for OPF parsing
|
||||||
|
}
|
||||||
|
|
||||||
|
if(type == "xhtml") {
|
||||||
|
// xhr.responseType = "document";
|
||||||
|
}
|
||||||
|
|
||||||
|
if(type == "html" || type == "htm") {
|
||||||
|
// xhr.responseType = "document";
|
||||||
|
}
|
||||||
|
|
||||||
|
if(type == "binary") {
|
||||||
|
xhr.responseType = "arraybuffer";
|
||||||
|
}
|
||||||
|
|
||||||
|
xhr.send();
|
||||||
|
|
||||||
|
function err(e) {
|
||||||
|
deferred.reject(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
function handler() {
|
||||||
|
if (this.readyState === XMLHttpRequest.DONE) {
|
||||||
|
var responseXML = false;
|
||||||
|
|
||||||
|
if(this.responseType === "" || this.responseType === "document") {
|
||||||
|
responseXML = this.responseXML;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.status === 200 || this.status === 0 || responseXML) { //-- Firefox is reporting 0 for blob urls
|
||||||
|
var r;
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default request;
|
||||||
55
client/static/libs/epubjs-patch/utils/scrolltype.js
Normal file
55
client/static/libs/epubjs-patch/utils/scrolltype.js
Normal file
|
|
@ -0,0 +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);
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createDefiner() {
|
||||||
|
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";
|
||||||
|
|
||||||
|
var innerDiv = document.createElement('div');
|
||||||
|
innerDiv.style.width = "2px";
|
||||||
|
|
||||||
|
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";
|
||||||
|
|
||||||
|
innerDiv.appendChild(spanA);
|
||||||
|
innerDiv.appendChild(spanB);
|
||||||
|
definer.appendChild(innerDiv);
|
||||||
|
|
||||||
|
return definer;
|
||||||
|
}
|
||||||
108
client/static/libs/epubjs-patch/utils/url.js
Normal file
108
client/static/libs/epubjs-patch/utils/url.js
Normal file
|
|
@ -0,0 +1,108 @@
|
||||||
|
import Path from "./path";
|
||||||
|
import path from "path-webpack";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* creates a Url object for parsing and manipulation of a url string
|
||||||
|
* @param {string} urlString a url string (relative or absolute)
|
||||||
|
* @param {string} [baseString] optional base for the url,
|
||||||
|
* default to window.location.href
|
||||||
|
*/
|
||||||
|
class Url {
|
||||||
|
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;
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.Path = new Path(pathname);
|
||||||
|
|
||||||
|
this.directory = this.Path.directory;
|
||||||
|
this.filename = this.Path.filename;
|
||||||
|
this.extension = this.Path.extension;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @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;
|
||||||
|
|
||||||
|
if (isAbsolute) {
|
||||||
|
return what;
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
toString () {
|
||||||
|
return this.href;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Url;
|
||||||
Loading…
Add table
Add a link
Reference in a new issue