mirror of
https://github.com/futurepress/epub.js.git
synced 2025-10-02 14:49:16 +02:00
Merge d215eed12d
into f09089cf77
This commit is contained in:
commit
184520769d
14 changed files with 1024 additions and 21196 deletions
325
espark_reader/reader.css
Normal file
325
espark_reader/reader.css
Normal file
|
@ -0,0 +1,325 @@
|
|||
body {
|
||||
margin: 0;
|
||||
background: #fafafa;
|
||||
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
|
||||
color: #333;
|
||||
|
||||
/*position: absolute;*/
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
/*min-height: 800px;*/
|
||||
}
|
||||
|
||||
#title {
|
||||
width: 900px;
|
||||
min-height: 18px;
|
||||
margin: 10px auto;
|
||||
text-align: center;
|
||||
font-size: 16px;
|
||||
color: #E2E2E2;
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
#title:hover {
|
||||
color: #777;
|
||||
}
|
||||
|
||||
#viewer {
|
||||
width: 900px;
|
||||
height: 600px;
|
||||
box-shadow: 0 0 4px #ccc;
|
||||
padding: 10px 10px 0px 10px;
|
||||
margin: 5px auto;
|
||||
background: white;
|
||||
}
|
||||
|
||||
#viewer.spreads {
|
||||
width: 900px;
|
||||
height: 600px;
|
||||
box-shadow: 0 0 4px #ccc;
|
||||
border-radius: 5px;
|
||||
padding: 0;
|
||||
position: relative;
|
||||
margin: 10px auto;
|
||||
background: white;
|
||||
top: calc(50vh - 400px);
|
||||
}
|
||||
|
||||
/* Smartphone - Portrait */
|
||||
@media only screen and (min-width: 320px) and (max-width: 667px) and (orientation: portrait) {
|
||||
#viewer {
|
||||
width: 320px;
|
||||
height: 667px;
|
||||
}
|
||||
}
|
||||
|
||||
/* Smartphone - Landscape */
|
||||
@media only screen and (min-width: 320px) and (max-width: 667px) and (orientation: landscape) {
|
||||
#viewer {
|
||||
width: 667px;
|
||||
height: 320px;
|
||||
}
|
||||
}
|
||||
|
||||
/* Tablet - Portrait and Landscape */
|
||||
@media only screen and (min-width: 667px) and (max-width: 1024px) {
|
||||
#viewer {
|
||||
width: 600px;
|
||||
height: 600px;
|
||||
}
|
||||
}
|
||||
|
||||
#viewer.spreads .epub-view>iframe {
|
||||
background: white;
|
||||
}
|
||||
|
||||
#viewer.scrolled {
|
||||
overflow: hidden;
|
||||
width: 800px;
|
||||
margin: 0 auto;
|
||||
position: relative;
|
||||
background: url('ajax-loader.gif') center center no-repeat;
|
||||
box-shadow: 0 0 4px #ccc;
|
||||
padding: 20px;
|
||||
background: white;
|
||||
}
|
||||
|
||||
#viewer.scrolled .epub-view>iframe {
|
||||
background: white;
|
||||
}
|
||||
|
||||
#prev {
|
||||
left: 0;
|
||||
}
|
||||
|
||||
#next {
|
||||
right: 0;
|
||||
}
|
||||
|
||||
#toc {
|
||||
display: block;
|
||||
margin: 10px auto;
|
||||
}
|
||||
|
||||
@media (min-width: 1000px) {
|
||||
/*#viewer.spreads:after {
|
||||
position: absolute;
|
||||
width: 1px;
|
||||
border-right: 1px #000 solid;
|
||||
height: 90%;
|
||||
z-index: 1;
|
||||
left: 50%;
|
||||
margin-left: -1px;
|
||||
top: 5%;
|
||||
opacity: .15;
|
||||
box-shadow: -2px 0 15px rgba(0, 0, 0, 1);
|
||||
content: "";
|
||||
}
|
||||
|
||||
#viewer.spreads.single:after {
|
||||
display: none;
|
||||
}*/
|
||||
|
||||
#prev {
|
||||
left: 40px;
|
||||
}
|
||||
|
||||
#next {
|
||||
right: 40px;
|
||||
}
|
||||
}
|
||||
|
||||
.arrow {
|
||||
position: fixed;
|
||||
top: 50%;
|
||||
margin-top: -32px;
|
||||
font-size: 64px;
|
||||
color: #E2E2E2;
|
||||
font-family: arial, sans-serif;
|
||||
font-weight: bold;
|
||||
cursor: pointer;
|
||||
-webkit-user-select: none;
|
||||
-moz-user-select: none;
|
||||
user-select: none;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.navlink {
|
||||
margin: 14px;
|
||||
display: block;
|
||||
text-align: center;
|
||||
text-decoration: none;
|
||||
color: #ccc;
|
||||
}
|
||||
|
||||
.arrow:hover,
|
||||
.navlink:hover {
|
||||
color: #777;
|
||||
}
|
||||
|
||||
.arrow:active,
|
||||
.navlink:hover {
|
||||
color: #000;
|
||||
}
|
||||
|
||||
#book-wrapper {
|
||||
width: 480px;
|
||||
height: 640px;
|
||||
overflow: hidden;
|
||||
border: 1px solid #ccc;
|
||||
margin: 28px auto;
|
||||
background: #fff;
|
||||
border-radius: 0 5px 5px 0;
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
#book-viewer {
|
||||
width: 480px;
|
||||
height: 660px;
|
||||
margin: -30px auto;
|
||||
-moz-box-shadow: inset 10px 0 20px rgba(0, 0, 0, .1);
|
||||
-webkit-box-shadow: inset 10px 0 20px rgba(0, 0, 0, .1);
|
||||
box-shadow: inset 10px 0 20px rgba(0, 0, 0, .1);
|
||||
}
|
||||
|
||||
#book-viewer iframe {
|
||||
padding: 40px 40px;
|
||||
}
|
||||
|
||||
#controls {
|
||||
position: absolute;
|
||||
bottom: 16px;
|
||||
left: 50%;
|
||||
width: 400px;
|
||||
margin-left: -200px;
|
||||
text-align: center;
|
||||
display: none;
|
||||
}
|
||||
|
||||
#controls>input[type=range] {
|
||||
width: 400px;
|
||||
}
|
||||
|
||||
#navigation {
|
||||
width: 400px;
|
||||
height: 100vh;
|
||||
position: absolute;
|
||||
overflow: auto;
|
||||
top: 0;
|
||||
left: 0;
|
||||
background: #777;
|
||||
-webkit-transition: -webkit-transform .25s ease-out;
|
||||
-moz-transition: -moz-transform .25s ease-out;
|
||||
-ms-transition: -moz-transform .25s ease-out;
|
||||
transition: transform .25s ease-out;
|
||||
|
||||
}
|
||||
|
||||
#navigation.fixed {
|
||||
position: fixed;
|
||||
}
|
||||
|
||||
#navigation h1 {
|
||||
width: 200px;
|
||||
font-size: 16px;
|
||||
font-weight: normal;
|
||||
color: #fff;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
#navigation h2 {
|
||||
font-size: 14px;
|
||||
font-weight: normal;
|
||||
color: #B0B0B0;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
#navigation ul {
|
||||
padding-left: 36px;
|
||||
margin-left: 0;
|
||||
margin-top: 12px;
|
||||
margin-bottom: 12px;
|
||||
width: 340px;
|
||||
}
|
||||
|
||||
#navigation ul li {
|
||||
list-style: decimal;
|
||||
margin-bottom: 10px;
|
||||
color: #cccddd;
|
||||
font-size: 12px;
|
||||
padding-left: 0;
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
#navigation ul li a {
|
||||
color: #ccc;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
#navigation ul li a:hover {
|
||||
color: #fff;
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
#navigation ul li a.active {
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
#navigation #cover {
|
||||
display: block;
|
||||
margin: 24px auto;
|
||||
}
|
||||
|
||||
#navigation #closer {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
padding: 12px;
|
||||
color: #cccddd;
|
||||
width: 24px;
|
||||
}
|
||||
|
||||
#navigation.closed {
|
||||
-webkit-transform: translate(-400px, 0);
|
||||
-moz-transform: translate(-400px, 0);
|
||||
-ms-transform: translate(-400px, 0);
|
||||
transform: translate(-400px, 0);
|
||||
}
|
||||
|
||||
svg {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.close-x {
|
||||
stroke: #cccddd;
|
||||
fill: transparent;
|
||||
stroke-linecap: round;
|
||||
stroke-width: 5;
|
||||
}
|
||||
|
||||
.close-x:hover {
|
||||
stroke: #fff;
|
||||
}
|
||||
|
||||
#opener {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
padding: 10px;
|
||||
stroke: #E2E2E2;
|
||||
fill: #E2E2E2;
|
||||
|
||||
}
|
||||
|
||||
#opener:hover {
|
||||
stroke: #777;
|
||||
fill: #777;
|
||||
}
|
||||
|
||||
#audioPlayer {
|
||||
margin: 10px auto;
|
||||
text-align: center;
|
||||
font-size: 16px;
|
||||
color: #E2E2E2;
|
||||
font-weight: 400;
|
||||
}
|
38
espark_reader/reader.html
Normal file
38
espark_reader/reader.html
Normal file
|
@ -0,0 +1,38 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>EPUB.js Input Example</title>
|
||||
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/jszip/3.1.5/jszip.min.js"></script>
|
||||
<script src="../dist/epub.js"></script>
|
||||
<script src="../howler.js/src/howler.core.js"></script>
|
||||
<script src="reader.js"></script>
|
||||
<link rel="stylesheet" type="text/css" href="reader.css">
|
||||
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="title">
|
||||
<input type="file" id="input">
|
||||
<div id="audioplayer"><button id="playButton" onclick="playPause()">Play</button><button id="resetButton"
|
||||
onclick="resetAudioToStart()">Reset</button></div>
|
||||
</div>
|
||||
|
||||
<div id="viewer"></div>
|
||||
<a id="prev" href="#prev" class="arrow">‹</a>
|
||||
<a id="next" href="#next" class="arrow">›</a>
|
||||
|
||||
<script>
|
||||
|
||||
init();
|
||||
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
</body>
|
||||
|
||||
</html>
|
186
espark_reader/reader.js
Normal file
186
espark_reader/reader.js
Normal file
|
@ -0,0 +1,186 @@
|
|||
var book = ePub();
|
||||
var rendition;
|
||||
|
||||
var inputElement;
|
||||
var audioClips = [];
|
||||
var currentWord = 0;
|
||||
var isPlaying = false;
|
||||
function init() {
|
||||
inputElement = document.getElementById("input");
|
||||
|
||||
inputElement.addEventListener('change', function (e) {
|
||||
var file = e.target.files[0];
|
||||
if (window.FileReader) {
|
||||
var reader = new FileReader();
|
||||
reader.onload = openBook;
|
||||
reader.readAsArrayBuffer(file);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function openBook(e) {
|
||||
var bookData = e.target.result;
|
||||
var title = document.getElementById("title");
|
||||
var next = document.getElementById("next");
|
||||
var prev = document.getElementById("prev");
|
||||
|
||||
book.open(bookData, "binary");
|
||||
/* console.log("opened book")
|
||||
console.log(book) */
|
||||
|
||||
var rendition = book.renderTo("viewer", {
|
||||
//manager: "continuous",
|
||||
flow: "paginated",
|
||||
width: "100%",
|
||||
height: "100%",
|
||||
snap: true
|
||||
});
|
||||
|
||||
rendition.display();
|
||||
var keyListener = function (e) {
|
||||
|
||||
// Left Key
|
||||
if ((e.keyCode || e.which) == 37) {
|
||||
rendition.prev();
|
||||
}
|
||||
|
||||
// Right Key
|
||||
if ((e.keyCode || e.which) == 39) {
|
||||
rendition.next();
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
rendition.on("keyup", keyListener);
|
||||
rendition.on("relocated", function (location) {
|
||||
console.log(location);
|
||||
initAudio();
|
||||
});
|
||||
|
||||
next.addEventListener("click", function (e) {
|
||||
rendition.next();
|
||||
e.preventDefault();
|
||||
}, false);
|
||||
|
||||
prev.addEventListener("click", function (e) {
|
||||
rendition.prev();
|
||||
e.preventDefault();
|
||||
}, false);
|
||||
|
||||
document.addEventListener("keyup", keyListener, false);
|
||||
|
||||
|
||||
}
|
||||
function initAudio() {
|
||||
var iframeList = document.getElementsByTagName("iframe");
|
||||
audioClips = [];
|
||||
for (let iframe of iframeList) {
|
||||
var iframeDoc = iframe.contentDocument;
|
||||
|
||||
//get audio information
|
||||
iframeDoc.querySelectorAll('par').forEach(par => {
|
||||
var text = par.getElementsByTagName("text")[0]; //should only be one
|
||||
var audio = par.getElementsByTagName("audio")[0]; //should only be one
|
||||
var textId = text.getAttribute("src").split("#")[1];//text source format is page.xhtml#word
|
||||
var textRef = iframeDoc.getElementById(textId);
|
||||
var audioSrc = audio.getAttribute("src");
|
||||
const contentType = "audio/mp3";
|
||||
/*var sound = new Howl({
|
||||
src: [`data:${contentType};base64,${audioSrc}`]
|
||||
});*/
|
||||
audioClips.push({
|
||||
textId: textId,
|
||||
text: textRef,
|
||||
audio: audio,
|
||||
clipBegin: parseFloat(audio.getAttribute("clipbegin")),
|
||||
clipEnd: parseFloat(audio.getAttribute("clipend"))
|
||||
//duration: 1000 * (parseFloat(audio.getAttribute("clipend")) - parseFloat(audio.getAttribute("clipbegin")))
|
||||
})
|
||||
});
|
||||
//add next clip's start to the duration to account for pauses
|
||||
for (let i = 0; i < audioClips.length - 1; i++) {
|
||||
audioClips[i].duration = 1000 * (audioClips[i + 1].clipBegin - audioClips[i].clipBegin);
|
||||
}
|
||||
if (audioClips.length > 0) {
|
||||
document.getElementById("audioplayer").style.visibility = "visible";
|
||||
resetAudioToStart();
|
||||
}
|
||||
else {
|
||||
document.getElementById("audioplayer").style.visibility = "hidden";
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
/**
|
||||
* This is where the audio play/pause function should load and play everything on the page.
|
||||
* Needs to loop through all available iframes because there may be 1-2 pages loaded separately on the page.
|
||||
* I believe the audio tags will be in order per epub spec. Could look at play start and check if that's not the case.
|
||||
* To play audio and go through highlighting the text and stopping at the right point, need to look at the clipBegin/clipEnd properties
|
||||
* You could go through each audio tag and play/stop for each one but I think that'd cause some choppy behavior. Probably would be smoother to get the start/end for the page,
|
||||
* and then track the time so that you can track the word for highlighting and pausing.
|
||||
*
|
||||
*
|
||||
* Something to consider: will tracking the pages like this get messed up if the device rotates and only shows one page?
|
||||
*/
|
||||
function playPause() {
|
||||
if (audioClips.length > 0) {
|
||||
if (isPlaying) {
|
||||
pauseAudio();
|
||||
}
|
||||
else {
|
||||
playAudio();
|
||||
}
|
||||
}
|
||||
}
|
||||
/**
|
||||
* The audio is the same for the whole book, need to play/pause the same audio tag
|
||||
*/
|
||||
function playAudio() {
|
||||
isPlaying = true;
|
||||
highlightWord();
|
||||
audioClips[0].audio.play();
|
||||
document.getElementById("playButton").textContent = "Pause";
|
||||
}
|
||||
function pauseAudio() {
|
||||
isPlaying = false;
|
||||
audioClips[0].audio.pause();
|
||||
document.getElementById("playButton").textContent = "Play";
|
||||
}
|
||||
function resetAudioToStart() {
|
||||
if (currentWord > 0)
|
||||
audioClips[currentWord - 1].text.setAttribute("style", "");
|
||||
currentWord = 0;
|
||||
pauseAudio();
|
||||
audioClips[0].audio.currentTime = audioClips[currentWord].clipBegin;
|
||||
}
|
||||
function resetAudioToWord() {
|
||||
audioClips[0].audio.currentTime = audioClips[currentWord].clipBegin;
|
||||
}
|
||||
/**
|
||||
* To highlight text with audio, search for the span with the id that matches the #id in the text src wrapping the audio. Each par tag has a text with source and the audio.
|
||||
* Add a css highlight class to the span, then remove when moving to the next word.
|
||||
*/
|
||||
function highlightWord() {
|
||||
//don't increment words if the audio is ended
|
||||
if (currentWord >= audioClips.length) {
|
||||
resetAudioToStart();
|
||||
return;
|
||||
}
|
||||
if (!isPlaying) {
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* note: using class add/remove doesn't work with iframes because the css is separate
|
||||
*/
|
||||
|
||||
//highlight current word
|
||||
if (currentWord > 0)
|
||||
audioClips[currentWord - 1].text.setAttribute("style", "");
|
||||
|
||||
audioClips[currentWord].text.setAttribute("style", "background-color:yellow;");
|
||||
//setup timer for when word is done being read
|
||||
const myTimeout = setTimeout(highlightWord, audioClips[currentWord].duration);
|
||||
//increment word index
|
||||
currentWord++;
|
||||
}
|
3
espark_reader/view.css
Normal file
3
espark_reader/view.css
Normal file
|
@ -0,0 +1,3 @@
|
|||
.highlight {
|
||||
background: yellow;
|
||||
}
|
|
@ -1,5 +1,6 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
|
@ -11,6 +12,7 @@
|
|||
<link rel="stylesheet" type="text/css" href="examples.css">
|
||||
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="title"><input type="file" id="input"></div>
|
||||
<div id="viewer" class="spreads"></div>
|
||||
|
@ -19,73 +21,74 @@
|
|||
|
||||
<script>
|
||||
|
||||
var book = ePub();
|
||||
var rendition;
|
||||
var book = ePub();
|
||||
var rendition;
|
||||
|
||||
var inputElement = document.getElementById("input");
|
||||
var inputElement = document.getElementById("input");
|
||||
|
||||
inputElement.addEventListener('change', function (e) {
|
||||
var file = e.target.files[0];
|
||||
if (window.FileReader) {
|
||||
var reader = new FileReader();
|
||||
reader.onload = openBook;
|
||||
reader.readAsArrayBuffer(file);
|
||||
inputElement.addEventListener('change', function (e) {
|
||||
var file = e.target.files[0];
|
||||
if (window.FileReader) {
|
||||
var reader = new FileReader();
|
||||
reader.onload = openBook;
|
||||
reader.readAsArrayBuffer(file);
|
||||
}
|
||||
});
|
||||
|
||||
function openBook(e) {
|
||||
var bookData = e.target.result;
|
||||
var title = document.getElementById("title");
|
||||
var next = document.getElementById("next");
|
||||
var prev = document.getElementById("prev");
|
||||
|
||||
book.open(bookData, "binary");
|
||||
|
||||
rendition = book.renderTo("viewer", {
|
||||
width: "100%",
|
||||
height: "100%"
|
||||
});
|
||||
|
||||
rendition.display();
|
||||
|
||||
var keyListener = function (e) {
|
||||
|
||||
// Left Key
|
||||
if ((e.keyCode || e.which) == 37) {
|
||||
rendition.prev();
|
||||
}
|
||||
});
|
||||
|
||||
function openBook(e){
|
||||
var bookData = e.target.result;
|
||||
var title = document.getElementById("title");
|
||||
var next = document.getElementById("next");
|
||||
var prev = document.getElementById("prev");
|
||||
// Right Key
|
||||
if ((e.keyCode || e.which) == 39) {
|
||||
rendition.next();
|
||||
}
|
||||
|
||||
book.open(bookData, "binary");
|
||||
};
|
||||
|
||||
rendition = book.renderTo("viewer", {
|
||||
width: "100%",
|
||||
height: 600
|
||||
});
|
||||
rendition.on("keyup", keyListener);
|
||||
rendition.on("relocated", function (location) {
|
||||
console.log(location);
|
||||
});
|
||||
|
||||
rendition.display();
|
||||
|
||||
var keyListener = function(e){
|
||||
|
||||
// Left Key
|
||||
if ((e.keyCode || e.which) == 37) {
|
||||
rendition.prev();
|
||||
}
|
||||
|
||||
// Right Key
|
||||
if ((e.keyCode || e.which) == 39) {
|
||||
next.addEventListener("click", function (e) {
|
||||
rendition.next();
|
||||
}
|
||||
e.preventDefault();
|
||||
}, false);
|
||||
|
||||
};
|
||||
|
||||
rendition.on("keyup", keyListener);
|
||||
rendition.on("relocated", function(location){
|
||||
console.log(location);
|
||||
});
|
||||
|
||||
next.addEventListener("click", function(e){
|
||||
rendition.next();
|
||||
e.preventDefault();
|
||||
}, false);
|
||||
|
||||
prev.addEventListener("click", function(e){
|
||||
rendition.prev();
|
||||
e.preventDefault();
|
||||
}, false);
|
||||
prev.addEventListener("click", function (e) {
|
||||
rendition.prev();
|
||||
e.preventDefault();
|
||||
}, false);
|
||||
|
||||
|
||||
|
||||
|
||||
document.addEventListener("keyup", keyListener, false);
|
||||
}
|
||||
document.addEventListener("keyup", keyListener, false);
|
||||
}
|
||||
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
</body>
|
||||
|
||||
</html>
|
20921
package-lock.json
generated
20921
package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
@ -59,6 +59,7 @@
|
|||
"@xmldom/xmldom": "^0.7.5",
|
||||
"core-js": "^3.18.3",
|
||||
"event-emitter": "^0.3.5",
|
||||
"howler": "^2.2.3",
|
||||
"jszip": "^3.7.1",
|
||||
"localforage": "^1.10.0",
|
||||
"lodash": "^4.17.21",
|
||||
|
|
114
src/packaging.js
114
src/packaging.js
|
@ -1,4 +1,4 @@
|
|||
import {qs, qsa, qsp, indexOfElementNode} from "./utils/core";
|
||||
import { qs, qsa, qsp, indexOfElementNode } from "./utils/core";
|
||||
|
||||
/**
|
||||
* Open Packaging Format Parser
|
||||
|
@ -25,25 +25,25 @@ class Packaging {
|
|||
* @param {document} packageDocument OPF XML
|
||||
* @return {object} parsed package parts
|
||||
*/
|
||||
parse(packageDocument){
|
||||
parse(packageDocument) {
|
||||
var metadataNode, manifestNode, spineNode;
|
||||
|
||||
if(!packageDocument) {
|
||||
if (!packageDocument) {
|
||||
throw new Error("Package File Not Found");
|
||||
}
|
||||
|
||||
metadataNode = qs(packageDocument, "metadata");
|
||||
if(!metadataNode) {
|
||||
if (!metadataNode) {
|
||||
throw new Error("No Metadata Found");
|
||||
}
|
||||
|
||||
manifestNode = qs(packageDocument, "manifest");
|
||||
if(!manifestNode) {
|
||||
if (!manifestNode) {
|
||||
throw new Error("No Manifest Found");
|
||||
}
|
||||
|
||||
spineNode = qs(packageDocument, "spine");
|
||||
if(!spineNode) {
|
||||
if (!spineNode) {
|
||||
throw new Error("No Spine Found");
|
||||
}
|
||||
|
||||
|
@ -53,7 +53,7 @@ class Packaging {
|
|||
this.coverPath = this.findCoverPath(packageDocument);
|
||||
|
||||
this.spineNodeIndex = indexOfElementNode(spineNode);
|
||||
|
||||
//KEM: spine creation/reading
|
||||
this.spine = this.parseSpine(spineNode, this.manifest);
|
||||
|
||||
this.uniqueIdentifier = this.findUniqueIdentifier(packageDocument);
|
||||
|
@ -62,13 +62,13 @@ class Packaging {
|
|||
this.metadata.direction = spineNode.getAttribute("page-progression-direction");
|
||||
|
||||
return {
|
||||
"metadata" : this.metadata,
|
||||
"spine" : this.spine,
|
||||
"manifest" : this.manifest,
|
||||
"navPath" : this.navPath,
|
||||
"ncxPath" : this.ncxPath,
|
||||
"metadata": this.metadata,
|
||||
"spine": this.spine,
|
||||
"manifest": this.manifest,
|
||||
"navPath": this.navPath,
|
||||
"ncxPath": this.ncxPath,
|
||||
"coverPath": this.coverPath,
|
||||
"spineNodeIndex" : this.spineNodeIndex
|
||||
"spineNodeIndex": this.spineNodeIndex
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -78,7 +78,7 @@ class Packaging {
|
|||
* @param {node} xml
|
||||
* @return {object} metadata
|
||||
*/
|
||||
parseMetadata(xml){
|
||||
parseMetadata(xml) {
|
||||
var metadata = {};
|
||||
|
||||
metadata.title = this.getElementText(xml, "title");
|
||||
|
@ -112,7 +112,7 @@ class Packaging {
|
|||
* @param {node} manifestXml
|
||||
* @return {object} manifest
|
||||
*/
|
||||
parseManifest(manifestXml){
|
||||
parseManifest(manifestXml) {
|
||||
var manifest = {};
|
||||
|
||||
//-- Turn items into an array
|
||||
|
@ -121,19 +121,19 @@ class Packaging {
|
|||
var items = Array.prototype.slice.call(selected);
|
||||
|
||||
//-- Create an object with the id as key
|
||||
items.forEach(function(item){
|
||||
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") || "";
|
||||
href = item.getAttribute("href") || "",
|
||||
type = item.getAttribute("media-type") || "",
|
||||
overlay = item.getAttribute("media-overlay") || "",
|
||||
properties = item.getAttribute("properties") || "";
|
||||
|
||||
manifest[id] = {
|
||||
"href" : href,
|
||||
"href": href,
|
||||
// "url" : href,
|
||||
"type" : type,
|
||||
"overlay" : overlay,
|
||||
"properties" : properties.length ? properties.split(" ") : []
|
||||
"type": type,
|
||||
"overlay": overlay,
|
||||
"properties": properties.length ? properties.split(" ") : []
|
||||
};
|
||||
|
||||
});
|
||||
|
@ -149,7 +149,7 @@ class Packaging {
|
|||
* @param {Packaging.manifest} manifest
|
||||
* @return {object} spine
|
||||
*/
|
||||
parseSpine(spineXml, manifest){
|
||||
parseSpine(spineXml, manifest) {
|
||||
var spine = [];
|
||||
|
||||
var selected = qsa(spineXml, "itemref");
|
||||
|
@ -158,7 +158,7 @@ class Packaging {
|
|||
// var epubcfi = new EpubCFI();
|
||||
|
||||
//-- Add to array to maintain ordering and cross reference with manifest
|
||||
items.forEach(function(item, index){
|
||||
items.forEach(function (item, index) {
|
||||
var idref = item.getAttribute("idref");
|
||||
// var cfiBase = epubcfi.generateChapterComponent(spineNodeIndex, index, Id);
|
||||
var props = item.getAttribute("properties") || "";
|
||||
|
@ -167,13 +167,13 @@ class Packaging {
|
|||
// var manifestPropArray = manifestProps.length ? manifestProps.split(" ") : [];
|
||||
|
||||
var itemref = {
|
||||
"id" : item.getAttribute("id"),
|
||||
"idref" : idref,
|
||||
"linear" : item.getAttribute("linear") || "yes",
|
||||
"properties" : propArray,
|
||||
"id": item.getAttribute("id"),
|
||||
"idref": idref,
|
||||
"linear": item.getAttribute("linear") || "yes",
|
||||
"properties": propArray,
|
||||
// "href" : manifest[Id].href,
|
||||
// "url" : manifest[Id].url,
|
||||
"index" : index
|
||||
"index": index
|
||||
// "cfiBase" : cfiBase
|
||||
};
|
||||
spine.push(itemref);
|
||||
|
@ -188,13 +188,13 @@ class Packaging {
|
|||
* @param {node} packageXml
|
||||
* @return {string} Unique Identifier text
|
||||
*/
|
||||
findUniqueIdentifier(packageXml){
|
||||
findUniqueIdentifier(packageXml) {
|
||||
var uniqueIdentifierId = packageXml.documentElement.getAttribute("unique-identifier");
|
||||
if (! uniqueIdentifierId) {
|
||||
if (!uniqueIdentifierId) {
|
||||
return "";
|
||||
}
|
||||
var identifier = packageXml.getElementById(uniqueIdentifierId);
|
||||
if (! identifier) {
|
||||
if (!identifier) {
|
||||
return "";
|
||||
}
|
||||
|
||||
|
@ -211,11 +211,11 @@ class Packaging {
|
|||
* @param {element} manifestNode
|
||||
* @return {string}
|
||||
*/
|
||||
findNavPath(manifestNode){
|
||||
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"});
|
||||
var node = qsp(manifestNode, "item", { "properties": "nav" });
|
||||
return node ? node.getAttribute("href") : false;
|
||||
}
|
||||
|
||||
|
@ -227,9 +227,9 @@ class Packaging {
|
|||
* @param {element} spineNode
|
||||
* @return {string}
|
||||
*/
|
||||
findNcxPath(manifestNode, spineNode){
|
||||
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 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
|
||||
|
@ -237,7 +237,7 @@ class Packaging {
|
|||
// "The item that describes the NCX must be referenced by the spine toc attribute."
|
||||
if (!node) {
|
||||
tocId = spineNode.getAttribute("toc");
|
||||
if(tocId) {
|
||||
if (tocId) {
|
||||
// node = manifestNode.querySelector("item[id='" + tocId + "']");
|
||||
node = manifestNode.querySelector(`#${tocId}`);
|
||||
}
|
||||
|
@ -254,17 +254,17 @@ class Packaging {
|
|||
* @param {node} packageXml
|
||||
* @return {string} href
|
||||
*/
|
||||
findCoverPath(packageXml){
|
||||
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"});
|
||||
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"});
|
||||
var metaCover = qsp(packageXml, "meta", { "name": "cover" });
|
||||
|
||||
if (metaCover) {
|
||||
var coverId = metaCover.getAttribute("content");
|
||||
|
@ -284,15 +284,15 @@ class Packaging {
|
|||
* @param {string} tag
|
||||
* @return {string} text
|
||||
*/
|
||||
getElementText(xml, tag){
|
||||
getElementText(xml, tag) {
|
||||
var found = xml.getElementsByTagNameNS("http://purl.org/dc/elements/1.1/", tag);
|
||||
var el;
|
||||
|
||||
if(!found || found.length === 0) return "";
|
||||
if (!found || found.length === 0) return "";
|
||||
|
||||
el = found[0];
|
||||
|
||||
if(el.childNodes.length){
|
||||
if (el.childNodes.length) {
|
||||
return el.childNodes[0].nodeValue;
|
||||
}
|
||||
|
||||
|
@ -307,10 +307,10 @@ class Packaging {
|
|||
* @param {string} property
|
||||
* @return {string} text
|
||||
*/
|
||||
getPropertyText(xml, property){
|
||||
var el = qsp(xml, "meta", {"property":property});
|
||||
getPropertyText(xml, property) {
|
||||
var el = qsp(xml, "meta", { "property": property });
|
||||
|
||||
if(el && el.childNodes.length){
|
||||
if (el && el.childNodes.length) {
|
||||
return el.childNodes[0].nodeValue;
|
||||
}
|
||||
|
||||
|
@ -326,7 +326,7 @@ class Packaging {
|
|||
this.metadata = json.metadata;
|
||||
|
||||
let spine = json.readingOrder || json.spine;
|
||||
this.spine = spine.map((item, index) =>{
|
||||
this.spine = spine.map((item, index) => {
|
||||
item.index = index;
|
||||
item.linear = item.linear || "yes";
|
||||
return item;
|
||||
|
@ -342,20 +342,20 @@ class Packaging {
|
|||
|
||||
this.spineNodeIndex = 0;
|
||||
|
||||
this.toc = json.toc.map((item, index) =>{
|
||||
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,
|
||||
"metadata": this.metadata,
|
||||
"spine": this.spine,
|
||||
"manifest": this.manifest,
|
||||
"navPath": this.navPath,
|
||||
"ncxPath": this.ncxPath,
|
||||
"coverPath": this.coverPath,
|
||||
"spineNodeIndex" : this.spineNodeIndex,
|
||||
"toc" : this.toc
|
||||
"spineNodeIndex": this.spineNodeIndex,
|
||||
"toc": this.toc
|
||||
};
|
||||
}
|
||||
|
||||
|
|
117
src/rendition.js
117
src/rendition.js
|
@ -64,7 +64,7 @@ class Rendition {
|
|||
|
||||
extend(this.settings, options);
|
||||
|
||||
if (typeof(this.settings.manager) === "object") {
|
||||
if (typeof (this.settings.manager) === "object") {
|
||||
this.manager = this.settings.manager;
|
||||
}
|
||||
|
||||
|
@ -176,6 +176,7 @@ class Rendition {
|
|||
// If manager is a string, try to load from imported managers
|
||||
if (typeof manager === "string" && manager === "default") {
|
||||
viewManager = DefaultViewManager;
|
||||
|
||||
} else if (typeof manager === "string" && manager === "continuous") {
|
||||
viewManager = ContinuousViewManager;
|
||||
} else {
|
||||
|
@ -209,11 +210,11 @@ class Rendition {
|
|||
* Start the rendering
|
||||
* @return {Promise} rendering has started
|
||||
*/
|
||||
start(){
|
||||
start() {
|
||||
if (!this.settings.layout && (this.book.package.metadata.layout === "pre-paginated" || this.book.displayOptions.fixedLayout === "true")) {
|
||||
this.settings.layout = "pre-paginated";
|
||||
}
|
||||
switch(this.book.package.metadata.spread) {
|
||||
switch (this.book.package.metadata.spread) {
|
||||
case 'none':
|
||||
this.settings.spread = 'none';
|
||||
break;
|
||||
|
@ -222,7 +223,7 @@ class Rendition {
|
|||
break;
|
||||
}
|
||||
|
||||
if(!this.manager) {
|
||||
if (!this.manager) {
|
||||
this.ViewManager = this.requireManager(this.settings.manager);
|
||||
this.View = this.requireView(this.settings.view);
|
||||
|
||||
|
@ -273,14 +274,14 @@ class Rendition {
|
|||
* @param {element} element to attach to
|
||||
* @return {Promise}
|
||||
*/
|
||||
attachTo(element){
|
||||
attachTo(element) {
|
||||
|
||||
return this.q.enqueue(function () {
|
||||
|
||||
// Start rendering
|
||||
this.manager.render(element, {
|
||||
"width" : this.settings.width,
|
||||
"height" : this.settings.height
|
||||
"width": this.settings.width,
|
||||
"height": this.settings.height
|
||||
});
|
||||
|
||||
/**
|
||||
|
@ -302,7 +303,7 @@ class Rendition {
|
|||
* @param {string} target Url or EpubCFI
|
||||
* @return {Promise}
|
||||
*/
|
||||
display(target){
|
||||
display(target) {
|
||||
if (this.displaying) {
|
||||
this.displaying.resolve();
|
||||
}
|
||||
|
@ -315,7 +316,7 @@ class Rendition {
|
|||
* @param {string} target Url or EpubCFI
|
||||
* @return {Promise}
|
||||
*/
|
||||
_display(target){
|
||||
_display(target) {
|
||||
if (!this.book) {
|
||||
return;
|
||||
}
|
||||
|
@ -331,10 +332,10 @@ class Rendition {
|
|||
if (this.book.locations.length() && isFloat(target)) {
|
||||
target = this.book.locations.cfiFromPercentage(parseFloat(target));
|
||||
}
|
||||
|
||||
//KEM: Section display
|
||||
section = this.book.spine.get(target);
|
||||
|
||||
if(!section){
|
||||
if (!section) {
|
||||
displaying.reject(new Error("No Section Found"));
|
||||
return displayed;
|
||||
}
|
||||
|
@ -415,7 +416,7 @@ class Rendition {
|
|||
* @private
|
||||
* @param {*} view
|
||||
*/
|
||||
afterDisplayed(view){
|
||||
afterDisplayed(view) {
|
||||
|
||||
view.on(EVENTS.VIEWS.MARK_CLICKED, (cfiRange, data) => this.triggerMarkEvent(cfiRange, data, view.contents));
|
||||
|
||||
|
@ -444,7 +445,7 @@ class Rendition {
|
|||
* @private
|
||||
* @param {*} view
|
||||
*/
|
||||
afterRemoved(view){
|
||||
afterRemoved(view) {
|
||||
this.hooks.unloaded.trigger(view, this).then(() => {
|
||||
/**
|
||||
* Emit that a section has been removed
|
||||
|
@ -461,7 +462,7 @@ class Rendition {
|
|||
* Report resize events and display the last seen location
|
||||
* @private
|
||||
*/
|
||||
onResized(size, epubcfi){
|
||||
onResized(size, epubcfi) {
|
||||
|
||||
/**
|
||||
* Emit that the rendition has been resized
|
||||
|
@ -486,7 +487,7 @@ class Rendition {
|
|||
* Report orientation events and display the last seen location
|
||||
* @private
|
||||
*/
|
||||
onOrientationChange(orientation){
|
||||
onOrientationChange(orientation) {
|
||||
/**
|
||||
* Emit that the rendition has been rotated
|
||||
* @event orientationchange
|
||||
|
@ -501,7 +502,7 @@ class Rendition {
|
|||
* Usually you would be better off calling display()
|
||||
* @param {object} offset
|
||||
*/
|
||||
moveTo(offset){
|
||||
moveTo(offset) {
|
||||
this.manager.moveTo(offset);
|
||||
}
|
||||
|
||||
|
@ -511,7 +512,7 @@ class Rendition {
|
|||
* @param {number} [height]
|
||||
* @param {string} [epubcfi] (optional)
|
||||
*/
|
||||
resize(width, height, epubcfi){
|
||||
resize(width, height, epubcfi) {
|
||||
if (width) {
|
||||
this.settings.width = width;
|
||||
}
|
||||
|
@ -524,7 +525,7 @@ class Rendition {
|
|||
/**
|
||||
* Clear all rendered views
|
||||
*/
|
||||
clear(){
|
||||
clear() {
|
||||
this.manager.clear();
|
||||
}
|
||||
|
||||
|
@ -532,7 +533,7 @@ class Rendition {
|
|||
* Go to the next "page" in the rendition
|
||||
* @return {Promise}
|
||||
*/
|
||||
next(){
|
||||
next() {
|
||||
return this.q.enqueue(this.manager.next.bind(this.manager))
|
||||
.then(this.reportLocation.bind(this));
|
||||
}
|
||||
|
@ -541,7 +542,7 @@ class Rendition {
|
|||
* Go to the previous "page" in the rendition
|
||||
* @return {Promise}
|
||||
*/
|
||||
prev(){
|
||||
prev() {
|
||||
return this.q.enqueue(this.manager.prev.bind(this.manager))
|
||||
.then(this.reportLocation.bind(this));
|
||||
}
|
||||
|
@ -553,7 +554,7 @@ class Rendition {
|
|||
* @param {object} metadata
|
||||
* @return {object} properties
|
||||
*/
|
||||
determineLayoutProperties(metadata){
|
||||
determineLayoutProperties(metadata) {
|
||||
var properties;
|
||||
var layout = this.settings.layout || metadata.layout || "reflowable";
|
||||
var spread = this.settings.spread || metadata.spread || "auto";
|
||||
|
@ -564,17 +565,17 @@ class Rendition {
|
|||
var direction = this.settings.direction || metadata.direction || "ltr";
|
||||
|
||||
if ((this.settings.width === 0 || this.settings.width > 0) &&
|
||||
(this.settings.height === 0 || this.settings.height > 0)) {
|
||||
(this.settings.height === 0 || this.settings.height > 0)) {
|
||||
// viewport = "width="+this.settings.width+", height="+this.settings.height+"";
|
||||
}
|
||||
|
||||
properties = {
|
||||
layout : layout,
|
||||
spread : spread,
|
||||
orientation : orientation,
|
||||
flow : flow,
|
||||
viewport : viewport,
|
||||
minSpreadWidth : minSpreadWidth,
|
||||
layout: layout,
|
||||
spread: spread,
|
||||
orientation: orientation,
|
||||
flow: flow,
|
||||
viewport: viewport,
|
||||
minSpreadWidth: minSpreadWidth,
|
||||
direction: direction
|
||||
};
|
||||
|
||||
|
@ -586,11 +587,11 @@ class Rendition {
|
|||
* (scrolled-continuous vs scrolled-doc are handled by different view managers)
|
||||
* @param {string} flow
|
||||
*/
|
||||
flow(flow){
|
||||
flow(flow) {
|
||||
var _flow = flow;
|
||||
if (flow === "scrolled" ||
|
||||
flow === "scrolled-doc" ||
|
||||
flow === "scrolled-continuous") {
|
||||
flow === "scrolled-doc" ||
|
||||
flow === "scrolled-continuous") {
|
||||
_flow = "scrolled";
|
||||
}
|
||||
|
||||
|
@ -622,7 +623,7 @@ class Rendition {
|
|||
* Adjust the layout of the rendition to reflowable or pre-paginated
|
||||
* @param {object} settings
|
||||
*/
|
||||
layout(settings){
|
||||
layout(settings) {
|
||||
if (settings) {
|
||||
this._layout = new Layout(settings);
|
||||
this._layout.spread(settings.spread, this.settings.minSpreadWidth);
|
||||
|
@ -646,7 +647,7 @@ class Rendition {
|
|||
* @param {string} spread none | auto (TODO: implement landscape, portrait, both)
|
||||
* @param {int} [min] min width to use spreads at
|
||||
*/
|
||||
spread(spread, min){
|
||||
spread(spread, min) {
|
||||
|
||||
this.settings.spread = spread;
|
||||
|
||||
|
@ -667,7 +668,7 @@ class Rendition {
|
|||
* Adjust the direction of the rendition
|
||||
* @param {string} dir
|
||||
*/
|
||||
direction(dir){
|
||||
direction(dir) {
|
||||
|
||||
this.settings.direction = dir || "ltr";
|
||||
|
||||
|
@ -686,12 +687,12 @@ class Rendition {
|
|||
* @fires relocated
|
||||
* @fires locationChanged
|
||||
*/
|
||||
reportLocation(){
|
||||
return this.q.enqueue(function reportedLocation(){
|
||||
reportLocation() {
|
||||
return this.q.enqueue(function reportedLocation() {
|
||||
requestAnimationFrame(function reportedLocationAfterRAF() {
|
||||
var location = this.manager.currentLocation();
|
||||
if (location && location.then && typeof location.then === "function") {
|
||||
location.then(function(result) {
|
||||
location.then(function (result) {
|
||||
let located = this.located(result);
|
||||
|
||||
if (!located || !located.start || !located.end) {
|
||||
|
@ -753,10 +754,10 @@ class Rendition {
|
|||
* Get the Current Location object
|
||||
* @return {displayedLocation | promise} location (may be a promise)
|
||||
*/
|
||||
currentLocation(){
|
||||
currentLocation() {
|
||||
var location = this.manager.currentLocation();
|
||||
if (location && location.then && typeof location.then === "function") {
|
||||
location.then(function(result) {
|
||||
location.then(function (result) {
|
||||
let located = this.located(result);
|
||||
return located;
|
||||
}.bind(this));
|
||||
|
@ -772,12 +773,12 @@ class Rendition {
|
|||
* @returns {displayedLocation}
|
||||
* @private
|
||||
*/
|
||||
located(location){
|
||||
located(location) {
|
||||
if (!location.length) {
|
||||
return {};
|
||||
}
|
||||
let start = location[0];
|
||||
let end = location[location.length-1];
|
||||
let end = location[location.length - 1];
|
||||
|
||||
let located = {
|
||||
start: {
|
||||
|
@ -794,7 +795,7 @@ class Rendition {
|
|||
href: end.href,
|
||||
cfi: end.mapping.end,
|
||||
displayed: {
|
||||
page: end.pages[end.pages.length-1] || 1,
|
||||
page: end.pages[end.pages.length - 1] || 1,
|
||||
total: end.totalPages
|
||||
}
|
||||
}
|
||||
|
@ -823,12 +824,12 @@ class Rendition {
|
|||
}
|
||||
|
||||
if (end.index === this.book.spine.last().index &&
|
||||
located.end.displayed.page >= located.end.displayed.total) {
|
||||
located.end.displayed.page >= located.end.displayed.total) {
|
||||
located.atEnd = true;
|
||||
}
|
||||
|
||||
if (start.index === this.book.spine.first().index &&
|
||||
located.start.displayed.page === 1) {
|
||||
located.start.displayed.page === 1) {
|
||||
located.atStart = true;
|
||||
}
|
||||
|
||||
|
@ -838,7 +839,7 @@ class Rendition {
|
|||
/**
|
||||
* Remove and Clean Up the Rendition
|
||||
*/
|
||||
destroy(){
|
||||
destroy() {
|
||||
// Clear the queue
|
||||
// this.q.clear();
|
||||
// this.q = undefined;
|
||||
|
@ -873,7 +874,7 @@ class Rendition {
|
|||
* @private
|
||||
* @param {Contents} view contents
|
||||
*/
|
||||
passEvents(contents){
|
||||
passEvents(contents) {
|
||||
DOM_EVENTS.forEach((e) => {
|
||||
contents.on(e, (ev) => this.triggerViewEvent(ev, contents));
|
||||
});
|
||||
|
@ -886,7 +887,7 @@ class Rendition {
|
|||
* @private
|
||||
* @param {event} e
|
||||
*/
|
||||
triggerViewEvent(e, contents){
|
||||
triggerViewEvent(e, contents) {
|
||||
this.emit(e.type, e, contents);
|
||||
}
|
||||
|
||||
|
@ -895,7 +896,7 @@ class Rendition {
|
|||
* @private
|
||||
* @param {string} cfirange
|
||||
*/
|
||||
triggerSelectedEvent(cfirange, contents){
|
||||
triggerSelectedEvent(cfirange, contents) {
|
||||
/**
|
||||
* Emit that a text selection has occurred
|
||||
* @event selected
|
||||
|
@ -911,7 +912,7 @@ class Rendition {
|
|||
* @private
|
||||
* @param {EpubCFI} cfirange
|
||||
*/
|
||||
triggerMarkEvent(cfiRange, data, contents){
|
||||
triggerMarkEvent(cfiRange, data, contents) {
|
||||
/**
|
||||
* Emit that a mark was clicked
|
||||
* @event markClicked
|
||||
|
@ -929,10 +930,10 @@ class Rendition {
|
|||
* @param {string} ignoreClass
|
||||
* @return {range}
|
||||
*/
|
||||
getRange(cfi, ignoreClass){
|
||||
getRange(cfi, ignoreClass) {
|
||||
var _cfi = new EpubCFI(cfi);
|
||||
var found = this.manager.visible().filter(function (view) {
|
||||
if(_cfi.spinePos === view.index) return true;
|
||||
if (_cfi.spinePos === view.index) return true;
|
||||
});
|
||||
|
||||
// Should only every return 1 item
|
||||
|
@ -949,7 +950,7 @@ class Rendition {
|
|||
adjustImages(contents) {
|
||||
|
||||
if (this._layout.name === "pre-paginated") {
|
||||
return new Promise(function(resolve){
|
||||
return new Promise(function (resolve) {
|
||||
resolve();
|
||||
});
|
||||
}
|
||||
|
@ -959,7 +960,7 @@ class Rendition {
|
|||
let horizontalPadding = parseFloat(computed.paddingLeft) + parseFloat(computed.paddingRight);
|
||||
|
||||
contents.addStylesheetRules({
|
||||
"img" : {
|
||||
"img": {
|
||||
"max-width": (this._layout.columnWidth ? (this._layout.columnWidth - horizontalPadding) + "px" : "100%") + "!important",
|
||||
"max-height": height + "px" + "!important",
|
||||
"object-fit": "contain",
|
||||
|
@ -967,7 +968,7 @@ class Rendition {
|
|||
"break-inside": "avoid",
|
||||
"box-sizing": "border-box"
|
||||
},
|
||||
"svg" : {
|
||||
"svg": {
|
||||
"max-width": (this._layout.columnWidth ? (this._layout.columnWidth - horizontalPadding) + "px" : "100%") + "!important",
|
||||
"max-height": height + "px" + "!important",
|
||||
"page-break-inside": "avoid",
|
||||
|
@ -975,9 +976,9 @@ class Rendition {
|
|||
}
|
||||
});
|
||||
|
||||
return new Promise(function(resolve, reject){
|
||||
return new Promise(function (resolve, reject) {
|
||||
// Wait to apply
|
||||
setTimeout(function() {
|
||||
setTimeout(function () {
|
||||
resolve();
|
||||
}, 1);
|
||||
});
|
||||
|
@ -987,7 +988,7 @@ class Rendition {
|
|||
* Get the Contents object of each rendered view
|
||||
* @returns {Contents[]}
|
||||
*/
|
||||
getContents () {
|
||||
getContents() {
|
||||
return this.manager ? this.manager.getContents() : [];
|
||||
}
|
||||
|
||||
|
@ -995,7 +996,7 @@ class Rendition {
|
|||
* Get the views member from the manager
|
||||
* @returns {Views}
|
||||
*/
|
||||
views () {
|
||||
views() {
|
||||
let views = this.manager ? this.manager.views : undefined;
|
||||
return views || [];
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import {substitute} from "./utils/replacements";
|
||||
import {createBase64Url, createBlobUrl, blob2base64} from "./utils/core";
|
||||
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";
|
||||
|
@ -30,16 +30,17 @@ class Resources {
|
|||
* Process resources
|
||||
* @param {Manifest} manifest
|
||||
*/
|
||||
process(manifest){
|
||||
process(manifest) {
|
||||
this.manifest = manifest;
|
||||
this.resources = Object.keys(manifest).
|
||||
map(function (key){
|
||||
map(function (key) {
|
||||
return manifest[key];
|
||||
});
|
||||
|
||||
this.replacementUrls = [];
|
||||
|
||||
this.html = [];
|
||||
//KEM: assets and urls are where the urls in items from the manifest are replaced by "blobs"
|
||||
this.assets = [];
|
||||
this.css = [];
|
||||
|
||||
|
@ -54,29 +55,29 @@ class Resources {
|
|||
* Split resources by type
|
||||
* @private
|
||||
*/
|
||||
split(){
|
||||
split() {
|
||||
|
||||
// HTML
|
||||
this.html = this.resources.
|
||||
filter(function (item){
|
||||
filter(function (item) {
|
||||
if (item.type === "application/xhtml+xml" ||
|
||||
item.type === "text/html") {
|
||||
item.type === "text/html") {
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
// Exclude HTML
|
||||
this.assets = this.resources.
|
||||
filter(function (item){
|
||||
filter(function (item) {
|
||||
if (item.type !== "application/xhtml+xml" &&
|
||||
item.type !== "text/html") {
|
||||
item.type !== "text/html") {
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
// Only CSS
|
||||
this.css = this.resources.
|
||||
filter(function (item){
|
||||
filter(function (item) {
|
||||
if (item.type === "text/css") {
|
||||
return true;
|
||||
}
|
||||
|
@ -87,16 +88,16 @@ class Resources {
|
|||
* Convert split resources into Urls
|
||||
* @private
|
||||
*/
|
||||
splitUrls(){
|
||||
splitUrls() {
|
||||
|
||||
// All Assets Urls
|
||||
this.urls = this.assets.
|
||||
map(function(item) {
|
||||
map(function (item) {
|
||||
return item.href;
|
||||
}.bind(this));
|
||||
|
||||
// Css Urls
|
||||
this.cssUrls = this.css.map(function(item) {
|
||||
this.cssUrls = this.css.map(function (item) {
|
||||
return item.href;
|
||||
});
|
||||
|
||||
|
@ -107,12 +108,12 @@ class Resources {
|
|||
* @param {string} url
|
||||
* @return {Promise<string>} Promise resolves with url string
|
||||
*/
|
||||
createUrl (url) {
|
||||
createUrl(url) {
|
||||
var parsedUrl = new Url(url);
|
||||
var mimeType = mime.lookup(parsedUrl.filename);
|
||||
|
||||
//KEM: based on the printout of the parsedUrl here, the url is correct, but the smil file is referring to it differently. May need to modify the replacement to include the ../
|
||||
if (this.settings.archive) {
|
||||
return this.settings.archive.createUrl(url, {"base64": (this.settings.replacements === "base64")});
|
||||
return this.settings.archive.createUrl(url, { "base64": (this.settings.replacements === "base64") });
|
||||
} else {
|
||||
if (this.settings.replacements === "base64") {
|
||||
return this.settings.request(url, 'blob')
|
||||
|
@ -134,27 +135,27 @@ class Resources {
|
|||
* Create blob urls for all the assets
|
||||
* @return {Promise} returns replacement urls
|
||||
*/
|
||||
replacements(){
|
||||
replacements() {
|
||||
if (this.settings.replacements === "none") {
|
||||
return new Promise(function(resolve) {
|
||||
return new Promise(function (resolve) {
|
||||
resolve(this.urls);
|
||||
}.bind(this));
|
||||
}
|
||||
|
||||
var replacements = this.urls.map( (url) => {
|
||||
var absolute = this.settings.resolver(url);
|
||||
var replacements = this.urls.map((url) => {
|
||||
var absolute = this.settings.resolver(url);
|
||||
|
||||
return this.createUrl(absolute).
|
||||
catch((err) => {
|
||||
console.error(err);
|
||||
return null;
|
||||
});
|
||||
});
|
||||
return this.createUrl(absolute).
|
||||
catch((err) => {
|
||||
console.error(err);
|
||||
return null;
|
||||
});
|
||||
});
|
||||
|
||||
return Promise.all(replacements)
|
||||
.then( (replacementUrls) => {
|
||||
.then((replacementUrls) => {
|
||||
this.replacementUrls = replacementUrls.filter((url) => {
|
||||
return (typeof(url) === "string");
|
||||
return (typeof (url) === "string");
|
||||
});
|
||||
return replacementUrls;
|
||||
});
|
||||
|
@ -167,11 +168,11 @@ class Resources {
|
|||
* @param {method} [resolver]
|
||||
* @return {Promise}
|
||||
*/
|
||||
replaceCss(archive, resolver){
|
||||
replaceCss(archive, resolver) {
|
||||
var replaced = [];
|
||||
archive = archive || this.settings.archive;
|
||||
resolver = resolver || this.settings.resolver;
|
||||
this.cssUrls.forEach(function(href) {
|
||||
this.cssUrls.forEach(function (href) {
|
||||
var replacement = this.createCssFile(href, archive, resolver)
|
||||
.then(function (replacementUrl) {
|
||||
// switch the url in the replacementUrls
|
||||
|
@ -193,11 +194,11 @@ class Resources {
|
|||
* @param {string} href the original css file
|
||||
* @return {Promise} returns a BlobUrl to the new CSS file or a data url
|
||||
*/
|
||||
createCssFile(href){
|
||||
createCssFile(href) {
|
||||
var newUrl;
|
||||
|
||||
if (path.isAbsolute(href)) {
|
||||
return new Promise(function(resolve){
|
||||
return new Promise(function (resolve) {
|
||||
resolve();
|
||||
});
|
||||
}
|
||||
|
@ -214,7 +215,7 @@ class Resources {
|
|||
}
|
||||
|
||||
// Get asset links relative to css file
|
||||
var relUrls = this.urls.map( (assetHref) => {
|
||||
var relUrls = this.urls.map((assetHref) => {
|
||||
var resolved = this.settings.resolver(assetHref);
|
||||
var relative = new Path(absolute).relative(resolved);
|
||||
|
||||
|
@ -223,12 +224,12 @@ class Resources {
|
|||
|
||||
if (!textResponse) {
|
||||
// file not found, don't replace
|
||||
return new Promise(function(resolve){
|
||||
return new Promise(function (resolve) {
|
||||
resolve();
|
||||
});
|
||||
}
|
||||
|
||||
return textResponse.then( (text) => {
|
||||
return textResponse.then((text) => {
|
||||
// Replacements in the css text
|
||||
text = substitute(text, relUrls, this.replacementUrls);
|
||||
|
||||
|
@ -242,7 +243,7 @@ class Resources {
|
|||
return newUrl;
|
||||
}, (err) => {
|
||||
// handle response errors
|
||||
return new Promise(function(resolve){
|
||||
return new Promise(function (resolve) {
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
|
@ -255,14 +256,18 @@ class Resources {
|
|||
* @param {resolver} [resolver]
|
||||
* @return {string[]} array with relative Urls
|
||||
*/
|
||||
relativeTo(absolute, resolver){
|
||||
relativeTo(absolute, resolver) {
|
||||
resolver = resolver || this.settings.resolver;
|
||||
|
||||
// Get Urls relative to current sections
|
||||
return this.urls.
|
||||
map(function(href) {
|
||||
map(function (href) {
|
||||
var resolved = resolver(href);
|
||||
var relative = new Path(absolute).relative(resolved);
|
||||
//KEM: hardcoding to fix audio links for testing
|
||||
//KEM: these are the links that are searched for in the content
|
||||
if (relative.includes("audio")) {
|
||||
relative = "../" + relative;
|
||||
}
|
||||
return relative;
|
||||
}.bind(this));
|
||||
}
|
||||
|
@ -278,7 +283,7 @@ class Resources {
|
|||
return;
|
||||
}
|
||||
if (this.replacementUrls.length) {
|
||||
return new Promise(function(resolve, reject) {
|
||||
return new Promise(function (resolve, reject) {
|
||||
resolve(this.replacementUrls[indexInUrls]);
|
||||
}.bind(this));
|
||||
} else {
|
||||
|
@ -300,6 +305,12 @@ class Resources {
|
|||
} else {
|
||||
relUrls = this.urls;
|
||||
}
|
||||
//KEM: this seems to be where the audio urls are going wrong, but it could be that there's a problem with the original file.
|
||||
//KEM: The smil files have relative paths to the resources with an extra folder jump like ../, and the regular pages don't do that
|
||||
//KEM: so there may need to be some kind of check to see where the files actually are before replacing them?
|
||||
/**
|
||||
* Goes through the content and replaces any instances of relUrls with this.replacementUrls
|
||||
*/
|
||||
return substitute(content, relUrls, this.replacementUrls);
|
||||
}
|
||||
|
||||
|
|
141
src/section.js
141
src/section.js
|
@ -14,7 +14,7 @@ import { DOMParser as XMLDOMSerializer } from "@xmldom/xmldom";
|
|||
* @param {object} hooks hooks for serialize and content
|
||||
*/
|
||||
class Section {
|
||||
constructor(item, hooks){
|
||||
constructor(item, hooks) {
|
||||
this.idref = item.idref;
|
||||
this.linear = item.linear === "yes";
|
||||
this.properties = item.properties;
|
||||
|
@ -24,7 +24,7 @@ class Section {
|
|||
this.canonical = item.canonical;
|
||||
this.next = item.next;
|
||||
this.prev = item.prev;
|
||||
|
||||
this.overlay = item.overlay;
|
||||
this.cfiBase = item.cfiBase;
|
||||
|
||||
if (hooks) {
|
||||
|
@ -38,6 +38,7 @@ class Section {
|
|||
this.document = undefined;
|
||||
this.contents = undefined;
|
||||
this.output = undefined;
|
||||
this.mediaOverlay = undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -45,29 +46,60 @@ class Section {
|
|||
* @param {method} [_request] a request method to use for loading
|
||||
* @return {document} a promise with the xml document
|
||||
*/
|
||||
load(_request){
|
||||
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;
|
||||
//KEM: this is where the file is loaded and turned into xml
|
||||
//KEM: add in a load to the smil file and append it?
|
||||
|
||||
//KEM: try to load overlay
|
||||
if (this.overlay) {
|
||||
if (this.contents) {
|
||||
loading.resolve(this.contents);
|
||||
} else {
|
||||
request(this.overlay.url).then(function (overlayXml) {
|
||||
var div = document.createElement("div");
|
||||
div.classList.add("audioContainer");
|
||||
//overlay is returning as a string? possibly because xml instead of xhtml
|
||||
var start = overlayXml.search("<smil");
|
||||
var xmlStr = overlayXml.substring(start);
|
||||
div.insertAdjacentHTML('beforeend', xmlStr);
|
||||
this.mediaOverlay = div;
|
||||
return request(this.url).then(function (xml) {
|
||||
this.document = xml;
|
||||
this.contents = xml.documentElement;
|
||||
this.contents.appendChild(div);
|
||||
return this.hooks.content.trigger(this.document, this);
|
||||
|
||||
}.bind(this)).then(function () {
|
||||
loading.resolve(this.contents);
|
||||
}.bind(this)).catch(function (error) {
|
||||
loading.reject(error);
|
||||
});
|
||||
}
|
||||
.bind(this)).catch(function (error) {
|
||||
loading.reject(error);
|
||||
});
|
||||
}
|
||||
}
|
||||
else {
|
||||
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(){
|
||||
|
||||
}.bind(this)).then(function () {
|
||||
loading.resolve(this.contents);
|
||||
}.bind(this))
|
||||
.catch(function(error){
|
||||
}.bind(this)).catch(function (error) {
|
||||
loading.reject(error);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return loaded;
|
||||
|
@ -77,7 +109,7 @@ class Section {
|
|||
* Adds a base tag for resolving urls in the section
|
||||
* @private
|
||||
*/
|
||||
base(){
|
||||
base() {
|
||||
return replaceBase(this.document, this);
|
||||
}
|
||||
|
||||
|
@ -86,13 +118,13 @@ class Section {
|
|||
* @param {method} [_request] a request method to use for loading
|
||||
* @return {string} output a serialized XML Document
|
||||
*/
|
||||
render(_request){
|
||||
render(_request) {
|
||||
var rendering = new defer();
|
||||
var rendered = rendering.promise;
|
||||
this.output; // TODO: better way to return this from hooks?
|
||||
|
||||
//console.log("rendering section");
|
||||
this.load(_request).
|
||||
then(function(contents){
|
||||
then(function (contents) {
|
||||
var userAgent = (typeof navigator !== 'undefined' && navigator.userAgent) || '';
|
||||
var isIE = userAgent.indexOf('Trident') >= 0;
|
||||
var Serializer;
|
||||
|
@ -105,13 +137,13 @@ class Section {
|
|||
this.output = serializer.serializeToString(contents);
|
||||
return this.output;
|
||||
}.bind(this)).
|
||||
then(function(){
|
||||
then(function () {
|
||||
return this.hooks.serialize.trigger(this.output, this);
|
||||
}.bind(this)).
|
||||
then(function(){
|
||||
then(function () {
|
||||
rendering.resolve(this.output);
|
||||
}.bind(this))
|
||||
.catch(function(error){
|
||||
.catch(function (error) {
|
||||
rendering.reject(error);
|
||||
});
|
||||
|
||||
|
@ -123,11 +155,11 @@ class Section {
|
|||
* @param {string} _query The query string to find
|
||||
* @return {object[]} A list of matches, with form {cfi, excerpt}
|
||||
*/
|
||||
find(_query){
|
||||
find(_query) {
|
||||
var section = this;
|
||||
var matches = [];
|
||||
var query = _query.toLowerCase();
|
||||
var find = function(node){
|
||||
var find = function (node) {
|
||||
var text = node.textContent.toLowerCase();
|
||||
var range = section.document.createRange();
|
||||
var cfi;
|
||||
|
@ -153,7 +185,7 @@ class Section {
|
|||
excerpt = node.textContent;
|
||||
}
|
||||
else {
|
||||
excerpt = node.textContent.substring(pos - limit/2, pos + limit/2);
|
||||
excerpt = node.textContent.substring(pos - limit / 2, pos + limit / 2);
|
||||
excerpt = "..." + excerpt + "...";
|
||||
}
|
||||
|
||||
|
@ -168,7 +200,7 @@ class Section {
|
|||
}
|
||||
};
|
||||
|
||||
sprint(section.document, function(node) {
|
||||
sprint(section.document, function (node) {
|
||||
find(node);
|
||||
});
|
||||
|
||||
|
@ -182,43 +214,43 @@ class Section {
|
|||
* @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") {
|
||||
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)=>{
|
||||
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){
|
||||
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 ){
|
||||
while (endNodeIndex < nodeList.length - 1) {
|
||||
l += nodeList[endNodeIndex].length;
|
||||
if ( endPos <= l){
|
||||
if (endPos <= l) {
|
||||
break;
|
||||
}
|
||||
endNodeIndex += 1;
|
||||
}
|
||||
|
||||
let startNode = nodeList[startNodeIndex] , endNode = nodeList[endNodeIndex];
|
||||
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 );
|
||||
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);
|
||||
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({
|
||||
|
@ -230,15 +262,15 @@ class Section {
|
|||
}
|
||||
|
||||
const treeWalker = document.createTreeWalker(section.document, NodeFilter.SHOW_TEXT, null, false);
|
||||
let node , nodeList = [];
|
||||
let node, nodeList = [];
|
||||
while (node = treeWalker.nextNode()) {
|
||||
nodeList.push(node);
|
||||
if (nodeList.length == maxSeqEle){
|
||||
search(nodeList.slice(0 , maxSeqEle));
|
||||
if (nodeList.length == maxSeqEle) {
|
||||
search(nodeList.slice(0, maxSeqEle));
|
||||
nodeList = nodeList.slice(1, maxSeqEle);
|
||||
}
|
||||
}
|
||||
if (nodeList.length > 0){
|
||||
if (nodeList.length > 0) {
|
||||
search(nodeList);
|
||||
}
|
||||
return matches;
|
||||
|
@ -250,23 +282,23 @@ class Section {
|
|||
* @param {object} globalLayout The global layout settings object, chapter properties string
|
||||
* @return {object} layoutProperties Object with layout properties
|
||||
*/
|
||||
reconcileLayoutSettings(globalLayout){
|
||||
reconcileLayoutSettings(globalLayout) {
|
||||
//-- Get the global defaults
|
||||
var settings = {
|
||||
layout : globalLayout.layout,
|
||||
spread : globalLayout.spread,
|
||||
orientation : globalLayout.orientation
|
||||
layout: globalLayout.layout,
|
||||
spread: globalLayout.spread,
|
||||
orientation: globalLayout.orientation
|
||||
};
|
||||
|
||||
//-- Get the chapter's display type
|
||||
this.properties.forEach(function(prop){
|
||||
this.properties.forEach(function (prop) {
|
||||
var rendition = prop.replace("rendition:", "");
|
||||
var split = rendition.indexOf("-");
|
||||
var property, value;
|
||||
|
||||
if(split != -1){
|
||||
if (split != -1) {
|
||||
property = rendition.slice(0, split);
|
||||
value = rendition.slice(split+1);
|
||||
value = rendition.slice(split + 1);
|
||||
|
||||
settings[property] = value;
|
||||
}
|
||||
|
@ -299,6 +331,7 @@ class Section {
|
|||
this.document = undefined;
|
||||
this.contents = undefined;
|
||||
this.output = undefined;
|
||||
this.mediaOverlay = undefined;
|
||||
}
|
||||
|
||||
destroy() {
|
||||
|
|
50
src/spine.js
50
src/spine.js
|
@ -1,7 +1,7 @@
|
|||
import EpubCFI from "./epubcfi";
|
||||
import Hook from "./utils/hook";
|
||||
import Section from "./section";
|
||||
import {replaceBase, replaceCanonical, replaceMeta} from "./utils/replacements";
|
||||
import { replaceBase, replaceCanonical, replaceMeta } from "./utils/replacements";
|
||||
|
||||
/**
|
||||
* A collection of Spine Items
|
||||
|
@ -45,8 +45,8 @@ class Spine {
|
|||
this.spineNodeIndex = _package.spineNodeIndex;
|
||||
this.baseUrl = _package.baseUrl || _package.basePath || "";
|
||||
this.length = this.items.length;
|
||||
|
||||
this.items.forEach( (item, index) => {
|
||||
//KEM: unpacking opf file
|
||||
this.items.forEach((item, index) => {
|
||||
var manifestItem = this.manifest[item.idref];
|
||||
var spineItem;
|
||||
|
||||
|
@ -58,21 +58,27 @@ class Spine {
|
|||
item.canonical = canonical(item.href);
|
||||
}
|
||||
|
||||
if(manifestItem) {
|
||||
if (manifestItem) {
|
||||
//KEM: This is where spine items get the URL from the manifest, this is where to connect the manifest media-overlay and get SMIL URL
|
||||
item.href = manifestItem.href;
|
||||
item.url = resolver(item.href, true);
|
||||
item.canonical = canonical(item.href);
|
||||
|
||||
if(manifestItem.properties.length){
|
||||
//KEM: added overlay so can load later in section
|
||||
item.overlay = this.manifest[manifestItem.overlay];
|
||||
if (item.overlay) {
|
||||
item.overlay.url = resolver(item.overlay.href, true);
|
||||
item.overlay.canonical = canonical(item.overlay.href);
|
||||
}
|
||||
if (manifestItem.properties.length) {
|
||||
item.properties.push.apply(item.properties, manifestItem.properties);
|
||||
}
|
||||
}
|
||||
|
||||
if (item.linear === "yes") {
|
||||
item.prev = function() {
|
||||
item.prev = function () {
|
||||
let prevIndex = item.index;
|
||||
while (prevIndex > 0) {
|
||||
let prev = this.get(prevIndex-1);
|
||||
let prev = this.get(prevIndex - 1);
|
||||
if (prev && prev.linear) {
|
||||
return prev;
|
||||
}
|
||||
|
@ -80,10 +86,10 @@ class Spine {
|
|||
}
|
||||
return;
|
||||
}.bind(this);
|
||||
item.next = function() {
|
||||
item.next = function () {
|
||||
let nextIndex = item.index;
|
||||
while (nextIndex < this.spineItems.length-1) {
|
||||
let next = this.get(nextIndex+1);
|
||||
while (nextIndex < this.spineItems.length - 1) {
|
||||
let next = this.get(nextIndex + 1);
|
||||
if (next && next.linear) {
|
||||
return next;
|
||||
}
|
||||
|
@ -92,15 +98,15 @@ class Spine {
|
|||
return;
|
||||
}.bind(this);
|
||||
} else {
|
||||
item.prev = function() {
|
||||
item.prev = function () {
|
||||
return;
|
||||
}
|
||||
item.next = function() {
|
||||
item.next = function () {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//KEM: section creation
|
||||
spineItem = new Section(item, this.hooks);
|
||||
|
||||
this.append(spineItem);
|
||||
|
@ -131,14 +137,14 @@ class Spine {
|
|||
}
|
||||
index += 1;
|
||||
}
|
||||
} else if(this.epubcfi.isCfiString(target)) {
|
||||
} else if (this.epubcfi.isCfiString(target)) {
|
||||
let cfi = new EpubCFI(target);
|
||||
index = cfi.spinePos;
|
||||
} else if(typeof target === "number" || isNaN(target) === false){
|
||||
} else if (typeof target === "number" || isNaN(target) === false) {
|
||||
index = target;
|
||||
} else if(typeof target === "string" && target.indexOf("#") === 0) {
|
||||
} else if (typeof target === "string" && target.indexOf("#") === 0) {
|
||||
index = this.spineById[target.substring(1)];
|
||||
} else if(typeof target === "string") {
|
||||
} else if (typeof target === "string") {
|
||||
// Remove fragments
|
||||
target = target.split("#")[0];
|
||||
index = this.spineByHref[target] || this.spineByHref[encodeURI(target)];
|
||||
|
@ -180,7 +186,7 @@ class Spine {
|
|||
this.spineById[section.idref] = 0;
|
||||
|
||||
// Re-index
|
||||
this.spineItems.forEach(function(item, index){
|
||||
this.spineItems.forEach(function (item, index) {
|
||||
item.index = index;
|
||||
});
|
||||
|
||||
|
@ -199,7 +205,7 @@ class Spine {
|
|||
remove(section) {
|
||||
var index = this.spineItems.indexOf(section);
|
||||
|
||||
if(index > -1) {
|
||||
if (index > -1) {
|
||||
delete this.spineByHref[section.href];
|
||||
delete this.spineById[section.idref];
|
||||
|
||||
|
@ -229,7 +235,7 @@ class Spine {
|
|||
return next;
|
||||
}
|
||||
index += 1;
|
||||
} while (index < this.spineItems.length) ;
|
||||
} while (index < this.spineItems.length);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -237,7 +243,7 @@ class Spine {
|
|||
* @return {Section} last section
|
||||
*/
|
||||
last() {
|
||||
let index = this.spineItems.length-1;
|
||||
let index = this.spineItems.length - 1;
|
||||
|
||||
do {
|
||||
let prev = this.get(index);
|
||||
|
|
85
src/test.html
Normal file
85
src/test.html
Normal file
|
@ -0,0 +1,85 @@
|
|||
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:epub="http://www.idpf.org/2007/ops" xml:lang="en-US">
|
||||
|
||||
<head>
|
||||
|
||||
|
||||
<title>Me and My Cat</title>
|
||||
|
||||
<link href="CSS/default.css" rel="stylesheet" type="text/css" />
|
||||
<link href="CSS/css_1.css" rel="stylesheet" type="text/css" />
|
||||
|
||||
<meta name="viewport" content="width=1400, height=1685" />
|
||||
|
||||
</head>
|
||||
|
||||
<body epub:type="frontmatter" lang="en-US" xml:lang="en-US">
|
||||
<div class="pg1">
|
||||
<section epub:type="titlepage">
|
||||
<img src="images/1.jpg" class="img" alt="written " />
|
||||
<p id="para1">
|
||||
|
||||
<span id="p1s1"><span id="written">written</span></span>
|
||||
<span id="Me"></span>
|
||||
<span id="and"></span>
|
||||
<span id="My"></span>
|
||||
<span id="Cat"></span>
|
||||
<span id="p1s2"><span id="by">by</span></span> <br />
|
||||
<span id="p1s4"><span id="Michael">Michael</span></span>
|
||||
<span id="p1s5"><span id="Dahl">Dahl</span></span> <br />
|
||||
<span id="p1s6"><span id="art">art</span></span>
|
||||
<span id="p1s7"><span id="by1">by</span></span> <br />
|
||||
<span id="p1s9"><span id="Zoe">Zoe</span></span>
|
||||
<span id="p1s10"><span id="Persico">Persico</span></span> <br />
|
||||
</p>
|
||||
</section>
|
||||
</div>
|
||||
</body>
|
||||
<smil xmlns="http://www.w3.org/ns/SMIL" xmlns:epub="http://www.idpf.org/2007/ops" version="3.0">
|
||||
|
||||
<body>
|
||||
<par id="id0">
|
||||
<text src="../P1.xhtml#Me">
|
||||
</text>
|
||||
<audio clipBegin="4.15" clipEnd="4.479" src="../audio/mmp_mecat_f16_masteraudio.mp3">
|
||||
</audio>
|
||||
</par>
|
||||
<par id="id1">
|
||||
<text src="../P1.xhtml#and">
|
||||
</text>
|
||||
<audio clipBegin="4.479" clipEnd="4.674" src="../audio/mmp_mecat_f16_masteraudio.mp3">
|
||||
</audio>
|
||||
</par>
|
||||
<par id="id2">
|
||||
<text src="../P1.xhtml#My">
|
||||
</text>
|
||||
<audio clipBegin="4.674" clipEnd="4.895" src="../audio/mmp_mecat_f16_masteraudio.mp3">
|
||||
</audio>
|
||||
</par>
|
||||
<par id="id3">
|
||||
<text src="../P1.xhtml#Cat">
|
||||
</text>
|
||||
<audio clipBegin="4.895" clipEnd="5.31" src="../audio/mmp_mecat_f16_masteraudio.mp3">
|
||||
</audio>
|
||||
</par>
|
||||
<par id="id4">
|
||||
<text src="../P1.xhtml#by">
|
||||
</text>
|
||||
<audio clipBegin="5.76" clipEnd="5.919" src="../audio/mmp_mecat_f16_masteraudio.mp3">
|
||||
</audio>
|
||||
</par>
|
||||
<par id="id5">
|
||||
<text src="../P1.xhtml#Michael">
|
||||
</text>
|
||||
<audio clipBegin="5.919" clipEnd="6.37" src="../audio/mmp_mecat_f16_masteraudio.mp3">
|
||||
</audio>
|
||||
</par>
|
||||
<par id="id6">
|
||||
<text src="../P1.xhtml#Dahl">
|
||||
</text>
|
||||
<audio clipBegin="6.371" clipEnd="6.778" src="../audio/mmp_mecat_f16_masteraudio.mp3">
|
||||
</audio>
|
||||
</par>
|
||||
</body>
|
||||
</smil>
|
||||
|
||||
</html>
|
|
@ -2,20 +2,20 @@ import { qs, qsa } from "./core";
|
|||
import Url from "./url";
|
||||
import Path from "./path";
|
||||
|
||||
export function replaceBase(doc, section){
|
||||
export function replaceBase(doc, section) {
|
||||
var base;
|
||||
var head;
|
||||
var url = section.url;
|
||||
var absolute = (url.indexOf("://") > -1);
|
||||
|
||||
if(!doc){
|
||||
if (!doc) {
|
||||
return;
|
||||
}
|
||||
|
||||
head = qs(doc, "head");
|
||||
base = qs(head, "base");
|
||||
|
||||
if(!base) {
|
||||
if (!base) {
|
||||
base = doc.createElement("base");
|
||||
head.insertBefore(base, head.firstChild);
|
||||
}
|
||||
|
@ -28,12 +28,12 @@ export function replaceBase(doc, section){
|
|||
base.setAttribute("href", url);
|
||||
}
|
||||
|
||||
export function replaceCanonical(doc, section){
|
||||
export function replaceCanonical(doc, section) {
|
||||
var head;
|
||||
var link;
|
||||
var url = section.canonical;
|
||||
|
||||
if(!doc){
|
||||
if (!doc) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -50,11 +50,11 @@ export function replaceCanonical(doc, section){
|
|||
}
|
||||
}
|
||||
|
||||
export function replaceMeta(doc, section){
|
||||
export function replaceMeta(doc, section) {
|
||||
var head;
|
||||
var meta;
|
||||
var id = section.idref;
|
||||
if(!doc){
|
||||
if (!doc) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -79,35 +79,34 @@ export function replaceLinks(contents, fn) {
|
|||
if (!links.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
var base = qs(contents.ownerDocument, "base");
|
||||
var location = base ? base.getAttribute("href") : undefined;
|
||||
var replaceLink = function(link){
|
||||
var replaceLink = function (link) {
|
||||
var href = link.getAttribute("href");
|
||||
|
||||
if(href.indexOf("mailto:") === 0){
|
||||
if (href.indexOf("mailto:") === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
var absolute = (href.indexOf("://") > -1);
|
||||
|
||||
if(absolute){
|
||||
if (absolute) {
|
||||
|
||||
link.setAttribute("target", "_blank");
|
||||
|
||||
}else{
|
||||
} else {
|
||||
var linkUrl;
|
||||
try {
|
||||
linkUrl = new Url(href, location);
|
||||
} catch(error) {
|
||||
} catch (error) {
|
||||
// NOOP
|
||||
}
|
||||
|
||||
link.onclick = function(){
|
||||
link.onclick = function () {
|
||||
|
||||
if(linkUrl && linkUrl.hash) {
|
||||
if (linkUrl && linkUrl.hash) {
|
||||
fn(linkUrl.Path.path + linkUrl.hash);
|
||||
} else if(linkUrl){
|
||||
} else if (linkUrl) {
|
||||
fn(linkUrl.Path.path);
|
||||
} else {
|
||||
fn(href);
|
||||
|
@ -126,7 +125,7 @@ export function replaceLinks(contents, fn) {
|
|||
}
|
||||
|
||||
export function substitute(content, urls, replacements) {
|
||||
urls.forEach(function(url, i){
|
||||
urls.forEach(function (url, i) {
|
||||
if (url && replacements[i]) {
|
||||
// Account for special characters in the file name.
|
||||
// See https://stackoverflow.com/a/6318729.
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue