mirror of
https://github.com/futurepress/epub.js.git
synced 2025-10-04 15:09:16 +02:00
added storage and chapters
This commit is contained in:
parent
a6a8661b74
commit
ca3e2c24c2
32 changed files with 2421 additions and 256 deletions
|
@ -1,44 +1,49 @@
|
|||
FP.Book = function(elem, bookUrl){
|
||||
|
||||
|
||||
//-- Takes a string or a element
|
||||
if (typeof elem == "string") {
|
||||
this.el = FP.core.getEl(elem);
|
||||
} else {
|
||||
this.el = elem;
|
||||
}
|
||||
|
||||
this.events = new FP.Events(this, this.el);
|
||||
|
||||
this.events = {};
|
||||
this.createEvent("book:tocReady");
|
||||
this.createEvent("book:metadataReady");
|
||||
this.createEvent("book:spineReady");
|
||||
this.createEvent("book:bookReady");
|
||||
this.createEvent("book:chapterReady");
|
||||
this.createEvent("book:resized");
|
||||
|
||||
|
||||
this.initialize(this.el);
|
||||
this.listeners();
|
||||
|
||||
//-- Determine storage type
|
||||
// options: none | ram
|
||||
FP.storage.storageMethod("none");
|
||||
|
||||
// BookUrl is optional, but if present start loading process
|
||||
if(bookUrl) {
|
||||
this.loadEpub(bookUrl);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
//-- Build up any html needed
|
||||
FP.Book.prototype.initialize = function(el){
|
||||
this.iframe = document.createElement('iframe');
|
||||
this.resizeIframe(false, this.el.clientWidth, this.el.clientHeight);
|
||||
|
||||
|
||||
this.listen("book:resized", this.resizeIframe, this);
|
||||
|
||||
|
||||
//this.listen("book:bookReady", function(){console.log("rready")});
|
||||
|
||||
|
||||
|
||||
this.el.appendChild(this.iframe);
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
FP.Book.prototype.listeners = function(){
|
||||
|
@ -49,32 +54,32 @@ FP.Book.prototype.listeners = function(){
|
|||
|
||||
FP.Book.prototype.loadEpub = function(bookUrl){
|
||||
this.bookUrl = bookUrl;
|
||||
|
||||
|
||||
//-- TODO: Check what storage types are available
|
||||
//-- TODO: Checks if the url is a zip file and unpack
|
||||
if(this.isContained(bookUrl)){
|
||||
console.log("Zipped!");
|
||||
}
|
||||
|
||||
|
||||
//-- Gets the root of the book and url of the opf
|
||||
this.parseContainer(function(){
|
||||
//-- Gets all setup of the book from xml file
|
||||
//-- TODO: add promise for this instead of callback?
|
||||
this.parseContents();
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
FP.Book.prototype.isContained = function(bookUrl){
|
||||
var tester=/\.[0-9a-z]+$/i,
|
||||
ext = tester.exec(bookUrl);
|
||||
|
||||
|
||||
if(ext && (ext[0] == ".epub" || ext[0] == ".zip")){
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -88,7 +93,7 @@ FP.Book.prototype.onResized = function(){
|
|||
|
||||
FP.Book.prototype.resizeIframe = function(e, cWidth, cHeight){
|
||||
var width, height;
|
||||
|
||||
|
||||
//-- Can be resized by the window resize event, or by passed height
|
||||
if(!e){
|
||||
width = cWidth;
|
||||
|
@ -97,7 +102,7 @@ FP.Book.prototype.resizeIframe = function(e, cWidth, cHeight){
|
|||
width = e.msg.width;
|
||||
height = e.msg.height;
|
||||
}
|
||||
|
||||
|
||||
this.iframe.height = height;
|
||||
|
||||
if(width % 2 != 0){
|
||||
|
@ -112,37 +117,36 @@ FP.Book.prototype.parseContainer = function(callback){
|
|||
url = this.bookUrl + "META-INF/container.xml";
|
||||
FP.core.loadXML(url, function(container){
|
||||
var fullpath;
|
||||
|
||||
|
||||
//-- <rootfile full-path="OPS/package.opf" media-type="application/oebps-package+xml"/>
|
||||
rootfiles = container.getElementsByTagName("rootfile");
|
||||
|
||||
|
||||
//-- Should only be one
|
||||
rootfile = rootfiles[0];
|
||||
|
||||
|
||||
fullpath = rootfile.getAttribute('full-path').split("/");
|
||||
|
||||
|
||||
that.basePath = that.bookUrl + fullpath[0] + "/";
|
||||
that.contentsPath = fullpath[1];
|
||||
//-- Now that we have the path we can parse the contents
|
||||
//-- TODO: move this
|
||||
that.parseContents(that.contentsPath);
|
||||
});
|
||||
|
||||
|
||||
}
|
||||
|
||||
FP.Book.prototype.parseContents = function(){
|
||||
var that = this,
|
||||
url = this.basePath + this.contentsPath;
|
||||
|
||||
|
||||
FP.core.loadXML(url, function(contents){
|
||||
var metadata = contents.getElementsByTagName("metadata")[0],
|
||||
manifest = contents.getElementsByTagName("manifest")[0],
|
||||
spine = contents.getElementsByTagName("spine")[0];
|
||||
|
||||
that.parseMetadata(metadata);
|
||||
that.parseManifest(manifest);
|
||||
that.parseSpine(spine);
|
||||
|
||||
|
||||
that.startDisplay();
|
||||
});
|
||||
}
|
||||
|
@ -153,16 +157,16 @@ FP.Book.prototype.parseMetadata = function(metadata){
|
|||
creator = metadata.getElementsByTagNameNS("http://purl.org/dc/elements/1.1/","creator")[0];
|
||||
|
||||
this.metadata = {};
|
||||
|
||||
|
||||
this.metadata["bookTitle"] = title ? title.childNodes[0].nodeValue : "";
|
||||
this.metadata["creator"] = creator ? creator.childNodes[0].nodeValue : "";
|
||||
|
||||
|
||||
this.tell("book:metadataReady");
|
||||
}
|
||||
|
||||
FP.Book.prototype.parseManifest = function(manifest){
|
||||
var that = this;
|
||||
|
||||
|
||||
this.assets = {};
|
||||
//-- Turn items into an array
|
||||
items = Array.prototype.slice.call(manifest.querySelectorAll("item"));
|
||||
|
@ -171,7 +175,7 @@ FP.Book.prototype.parseManifest = function(manifest){
|
|||
var id = item.getAttribute('id'),
|
||||
href = item.getAttribute('href');
|
||||
that.assets[id] = that.basePath + href;
|
||||
|
||||
|
||||
//-- Find NCX: media-type="application/x-dtbncx+xml" href="toc.ncx"
|
||||
if(item.getAttribute('media-type') == "application/x-dtbncx+xml"){
|
||||
that.parseTOC(href);
|
||||
|
@ -181,19 +185,19 @@ FP.Book.prototype.parseManifest = function(manifest){
|
|||
|
||||
FP.Book.prototype.parseSpine = function(spine){
|
||||
var that = this;
|
||||
|
||||
|
||||
this.spine = [];
|
||||
|
||||
|
||||
this.spineIndex = {}; //-- For quick reference by id, might be a better way
|
||||
|
||||
|
||||
//-- Turn items into an array
|
||||
items = Array.prototype.slice.call(spine.getElementsByTagName("itemref"));
|
||||
|
||||
|
||||
//-- Add to array to mantain ordering and cross reference with manifest
|
||||
items.forEach(function(item, index){
|
||||
var id = item.getAttribute('idref'),
|
||||
href = that.assets[id];
|
||||
|
||||
|
||||
that.spine.push({"id": id, "href": href});
|
||||
that.spineIndex[id] = index;
|
||||
});
|
||||
|
@ -203,53 +207,53 @@ FP.Book.prototype.parseSpine = function(spine){
|
|||
FP.Book.prototype.parseTOC = function(path){
|
||||
var that = this,
|
||||
url = this.basePath + path;
|
||||
|
||||
|
||||
this.toc = [];
|
||||
|
||||
|
||||
FP.core.loadXML(url, function(contents){
|
||||
var navMap = contents.getElementsByTagName("navMap")[0];
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
function getTOC(nodes, parent){
|
||||
var list = [];
|
||||
|
||||
|
||||
//-- Turn items into an array
|
||||
items = Array.prototype.slice.call(nodes);
|
||||
|
||||
|
||||
items.forEach(function(item){
|
||||
var id = item.getAttribute('id'),
|
||||
href = that.assets[id],
|
||||
navLabel = item.getElementsByTagName("navLabel")[0],
|
||||
navLabel = item.getElementsByTagName("navLabel")[0], //-- TODO: replace with query
|
||||
text = navLabel.textContent ? navLabel.textContent : "",
|
||||
subitems = item.getElementsByTagName("navPoint") || false,
|
||||
subs = false,
|
||||
childof = (item.parentNode == parent);
|
||||
|
||||
|
||||
|
||||
|
||||
if(!childof) return; //-- Only get direct children, should xpath for this eventually?
|
||||
|
||||
|
||||
if(subitems){
|
||||
subs = getTOC(subitems, item)
|
||||
}
|
||||
|
||||
|
||||
list.push({
|
||||
"id": id,
|
||||
"href": href,
|
||||
"label": text,
|
||||
"spinePos": that.spineIndex[id],
|
||||
"spinePos": parseInt(that.spineIndex[id]),
|
||||
"subitems" : subs
|
||||
});
|
||||
|
||||
|
||||
});
|
||||
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
that.toc = getTOC(navMap.getElementsByTagName("navPoint"), navMap);
|
||||
|
||||
|
||||
|
||||
|
||||
that.tell("book:tocReady");
|
||||
/*
|
||||
<navPoint class="chapter" id="xtitlepage" playOrder="1">
|
||||
|
@ -259,7 +263,7 @@ FP.Book.prototype.parseTOC = function(path){
|
|||
*/
|
||||
|
||||
});
|
||||
|
||||
|
||||
}
|
||||
|
||||
FP.Book.prototype.destroy = function(){
|
||||
|
@ -280,146 +284,57 @@ FP.Book.prototype.chapterTitle = function(){
|
|||
|
||||
FP.Book.prototype.startDisplay = function(){
|
||||
//-- get previous saved positions
|
||||
var spinePos = localStorage.getItem("spinePos") || 0;
|
||||
|
||||
var spinePos = parseInt(localStorage.getItem("spinePos")) || 0;
|
||||
|
||||
this.tell("book:bookReady");
|
||||
|
||||
|
||||
this.displayChapter(spinePos);
|
||||
|
||||
|
||||
}
|
||||
|
||||
FP.Book.prototype.displayChapter = function(pos, end){
|
||||
var that = this;
|
||||
|
||||
|
||||
if(pos >= this.spine.length){
|
||||
console.log("Reached End of Book")
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
if(pos < 0){
|
||||
console.log("Reached Start of Book")
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
localStorage.setItem("spinePos", pos);
|
||||
|
||||
|
||||
this.spinePos = pos;
|
||||
this.chapterPos = 1;
|
||||
this.leftPos = 0;
|
||||
|
||||
if(this.bodyEl) this.bodyEl.style.visibility = "hidden";
|
||||
this.iframe.src = this.spine[this.spinePos].href;
|
||||
|
||||
this.iframe.onload = function() {
|
||||
//that.bodyEl = that.iframe.contentDocument.documentElement.getElementsByTagName('body')[0];
|
||||
//that.bodyEl = that.iframe.contentDocument.querySelector('body, html');
|
||||
that.bodyEl = that.iframe.contentDocument.body;
|
||||
|
||||
console.log(that.bodyEl);
|
||||
//-- Create a new chapter
|
||||
this.currentChapter = new FP.Chapter(this);
|
||||
|
||||
|
||||
this.currentChapter.afterLoaded = function(chapter) {
|
||||
//-- TODO: Choose between single and spread
|
||||
that.formatSpread();
|
||||
if(end) that.goToChapterEnd();
|
||||
|
||||
that.listen("book:resized", that.formatSpread, that);
|
||||
|
||||
//that.formatSpread();
|
||||
|
||||
if(end) chapter.goToChapterEnd();
|
||||
|
||||
that.tell("book:chapterReady");
|
||||
|
||||
that.preloadNextChapter();
|
||||
}
|
||||
}
|
||||
|
||||
FP.Book.prototype.formatSpread = function(){
|
||||
var divisor = 2,
|
||||
cutoff = 800;
|
||||
|
||||
if(this.colWidth){
|
||||
this.OldcolWidth = this.colWidth;
|
||||
this.OldspreadWidth = this.spreadWidth;
|
||||
}
|
||||
//this.bodyEl.setAttribute("style", "background: #777");
|
||||
|
||||
//-- Check the width and decied on columns
|
||||
//-- Todo: a better place for this?
|
||||
this.elWidth = this.iframe.width;
|
||||
|
||||
this.gap = this.gap || Math.ceil(this.elWidth / 8);
|
||||
|
||||
if(this.elWidth < cutoff) {
|
||||
divisor = 1;
|
||||
this.colWidth = Math.floor(this.elWidth / divisor);
|
||||
}else{
|
||||
this.colWidth = Math.floor((this.elWidth - this.gap) / divisor);
|
||||
|
||||
//-- Must be even for firefox
|
||||
if(this.colWidth % 2 != 0){
|
||||
this.colWidth -= 1;
|
||||
}
|
||||
}
|
||||
|
||||
this.spreadWidth = (this.colWidth + this.gap) * divisor;
|
||||
|
||||
//-- Clear Margins
|
||||
this.bodyEl.style.visibility = "hidden";
|
||||
this.bodyEl.style.margin = "0";
|
||||
this.bodyEl.style.overflow = "hidden";
|
||||
|
||||
this.bodyEl.style.width = this.elWidth;
|
||||
|
||||
//-- Adjust height
|
||||
this.bodyEl.style.height = this.el.clientHeight + "px";
|
||||
|
||||
|
||||
|
||||
this.bodyEl.style[FP.core.columnAxis] = "horizontal";
|
||||
this.bodyEl.style[FP.core.columnGap] = this.gap+"px";
|
||||
this.bodyEl.style[FP.core.columnWidth] = this.colWidth+"px";
|
||||
|
||||
this.calcPages();
|
||||
|
||||
//-- Go to current page after resize
|
||||
if(this.OldcolWidth){
|
||||
this.leftPos = this.chapterPos * this.spreadWidth;
|
||||
this.bodyEl.scrollLeft = this.leftPos;
|
||||
}
|
||||
}
|
||||
|
||||
FP.Book.prototype.goToChapterEnd = function(){
|
||||
this.chapterEnd();
|
||||
}
|
||||
|
||||
FP.Book.prototype.calcPages = function(){
|
||||
this.totalWidth = this.iframe.contentDocument.documentElement.scrollWidth; //this.bodyEl.scrollWidth;
|
||||
|
||||
this.displayedPages = Math.ceil(this.totalWidth / this.spreadWidth);
|
||||
this.bodyEl.style.visibility = "visible";
|
||||
|
||||
//-- I work for Chrome
|
||||
//this.iframe.contentDocument.body.scrollLeft = 200;
|
||||
|
||||
//-- I work for Firefox
|
||||
//this.iframe.contentDocument.documentElement.scrollLeft = 200;
|
||||
|
||||
}
|
||||
|
||||
|
||||
FP.Book.prototype.nextPage = function(){
|
||||
if(this.chapterPos < this.displayedPages){
|
||||
this.chapterPos++;
|
||||
|
||||
this.leftPos += this.spreadWidth;
|
||||
//this.bodyEl.scrollLeft = this.leftPos;
|
||||
this.bodyEl.style.marginLeft = -this.leftPos + "px";
|
||||
}else{
|
||||
var next = this.currentChapter.nextPage();
|
||||
if(!next){
|
||||
this.nextChapter();
|
||||
}
|
||||
}
|
||||
|
||||
FP.Book.prototype.prevPage = function(){
|
||||
if(this.chapterPos > 1){
|
||||
this.chapterPos--;
|
||||
|
||||
this.leftPos -= this.spreadWidth;
|
||||
//this.bodyEl.scrollLeft = this.leftPos;
|
||||
this.bodyEl.style.marginLeft = -this.leftPos + "px";
|
||||
}else{
|
||||
var prev = this.currentChapter.prevPage();
|
||||
if(!prev){
|
||||
this.prevChapter();
|
||||
}
|
||||
}
|
||||
|
@ -436,55 +351,24 @@ FP.Book.prototype.prevChapter = function(){
|
|||
this.displayChapter(this.spinePos, true);
|
||||
}
|
||||
|
||||
FP.Book.prototype.chapterEnd = function(){
|
||||
this.chapterPos = this.displayedPages;
|
||||
this.leftPos = this.totalWidth - this.colWidth;
|
||||
|
||||
this.bodyEl.scrollLeft = this.leftPos;
|
||||
}
|
||||
|
||||
|
||||
FP.Book.prototype.getTOC = function(){
|
||||
return this.toc;
|
||||
|
||||
}
|
||||
|
||||
FP.Book.prototype.createEvent = function(evt){
|
||||
var e = new CustomEvent(evt);
|
||||
this.events[evt] = e;
|
||||
return e;
|
||||
FP.Book.prototype.preloadNextChapter = function(){
|
||||
var next = this.spinePos + 1,
|
||||
path = this.spine[next].href;
|
||||
|
||||
file = FP.storage.preload(path);
|
||||
}
|
||||
|
||||
FP.Book.prototype.tell = function(evt, msg){
|
||||
var e;
|
||||
FP.Book.prototype.preloadAll = function(){
|
||||
|
||||
if(!this.events[evt]){
|
||||
console.warn("No event:", evt, "defined yet, creating.");
|
||||
e = this.createEvent(evt)
|
||||
}else{
|
||||
e = this.events[evt];
|
||||
}
|
||||
|
||||
if(msg) e.msg = msg;
|
||||
|
||||
this.el.dispatchEvent(e);
|
||||
|
||||
}
|
||||
|
||||
FP.Book.prototype.listen = function(evt, func, bindto){
|
||||
if(!this.events[evt]){
|
||||
console.warn("No event:", evt, "defined yet, creating.");
|
||||
this.createEvent(evt);
|
||||
return;
|
||||
}
|
||||
|
||||
if(bindto){
|
||||
this.el.addEventListener(evt, func.bind(bindto), false);
|
||||
}else{
|
||||
this.el.addEventListener(evt, func, false);
|
||||
}
|
||||
FP.Book.prototype.preloadResources = function(){
|
||||
|
||||
}
|
||||
|
||||
FP.Book.prototype.deafen = function(evt, func){
|
||||
this.el.removeEventListener(evt, func, false);
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue