diff options
-rw-r--r-- | js/app.js | 2 | ||||
-rw-r--r-- | js/dict.js | 2 | ||||
-rw-r--r-- | js/read.js | 1055 | ||||
-rw-r--r-- | js/reader.js | 1050 | ||||
-rw-r--r-- | js/reader_iframe.js | 61 | ||||
-rw-r--r-- | read.html | 24 | ||||
-rw-r--r-- | worker.js | 2 |
7 files changed, 1090 insertions, 1106 deletions
@@ -1,4 +1,4 @@ -'use strict'
+'use strict';
/* global localforage, EpubeApp */
@@ -7,7 +7,7 @@ $(document).ready(function() { const sel = getSelection().toString().trim(); if (sel.match(/^\w+$/)) { - parent.dict_lookup(sel, function() { + parent.Reader.lookupWord(sel, function() { getSelection().removeAllRanges(); }); } diff --git a/js/read.js b/js/read.js deleted file mode 100644 index c22b4d0..0000000 --- a/js/read.js +++ /dev/null @@ -1,1055 +0,0 @@ -'use strict'; - -/* globals ePub, localforage, book, cacheId, EpubeApp */ - -let _pagination_stored = 0; -let _last_position_sync = 0; -let _store_position = 0; - -//const _is_ios = (/iPad|iPhone|iPod/).test(navigator.userAgent) && !window.MSStream; -const _res_data = []; - -const DEFAULT_FONT_SIZE = 16; -const DEFAULT_FONT_FAMILY = "Georgia"; -const DEFAULT_LINE_HEIGHT = 140; - -function cacheId(suffix) { - return "epube-book." + $.urlParam("b") + (suffix ? "." + suffix : ""); -} - -function init_loader() { - // we need to preload resources for reader iframe because it can't utilize our - // service worker because while offline it is created outside our base server context - const res_names = [ "lib/bootstrap/v3/js/jquery.js", "lib/jquery.mobile-events.min.js", - "css/transitions.css", - "js/reader.js", "css/reader.css", "js/dict.js", - "themes/default.css", "themes/light.css", "themes/mocca.css", "themes/night.css", - "themes/plan9.css", "themes/gray.css", "themes/sepia.css" ]; - - for (let i = 0; i < res_names.length; i++) { - fetch(res_names[i], {credentials: 'same-origin'}).then(function(resp) { - if (resp.status == 200) { - resp.text().then(function(data) { - const url = new URL(resp.url); - url.searchParams.delete("ts"); - - _res_data[url.toString()] = data; - }) - } else { - console.warn('loader failed for resource', res_names[i], resp); - } - }); - } - - check_resource_load(res_names, _res_data, 0); -} - -function check_resource_load(res_names, res_data, attempt) { - console.log("check_resource_load", attempt, res_names.length, Object.keys(res_data).length); - - if (attempt == 5) { - $(".loading_message").html("Unable to load resources."); - return; - } - - if (res_names.length != Object.keys(res_data).length) { - window.setTimeout(function() { - check_resource_load(res_names, res_data, attempt+1); - }, 250); - } else { - init_reader(); - } -} - -function init_reader() { - if (typeof EpubeApp != "undefined") { - EpubeApp.setPage("PAGE_READER"); - } - - apply_theme(); - - /* global Cookie */ - - if (Cookie.get("is-epube-app") == "true") { - $("body").addClass("is-epube-app"); - } - - $(window).on('online', function() { - console.log("we're online, storing lastread"); - - const currentCfi = book.rendition.currentLocation().start.cfi; - const currentPage = parseInt(book.locations.percentageFromCfi(currentCfi) * 100); - - $.post("backend.php", { op: "storelastread", id: $.urlParam("id"), page: currentPage, - cfi: currentCfi }, function(data) { - - if (data.cfi) { - _last_position_sync = new Date().getTime()/1000; - } - }) - .fail(function(e) { - if (e && e.status == 401) { - window.location = "index.php"; - } - }); - }); - - localforage.getItem(cacheId("book")).then(function(item) { - - // ios doesn't work with FileReader for whatever reason - if (/*!_is_ios &&*/ item) { - - console.log("loading from local storage"); - - const fileReader = new FileReader(); - - fileReader.onload = function() { - try { - book.open(this.result); - } catch (e) { - $(".loading_message").html("Unable to load book (local)."); - console.log(e); - } - }; - - fileReader.readAsArrayBuffer(item); - - } else { - - console.log("loading from network"); - - if (navigator.onLine) { - const book_url = "backend.php?op=download&id=" + $.urlParam("id"); - - $(".loading_message").html("Downloading..."); - - fetch(book_url, {credentials: 'same-origin'}).then(function(resp) { - - if (resp.status == 200) { - const bookId = $.urlParam("b"); - - resp.blob().then(function(blob) { - - // if there's no base information cached yet, let's do that too - localforage.getItem(cacheId()).then(function(info) { - if (!info) { - $.post("backend.php", {op: "getinfo", id: bookId }, function(data) { - if (data) { - localforage.setItem(cacheId(), data); - - if (data.has_cover) { - fetch("backend.php?op=cover&id=" + bookId, {credentials: 'same-origin'}).then(function(resp) { - if (resp.status == 200) { - localforage.setItem(cacheId('cover'), resp.blob()); - } - }); - } - } - }); - } - }); - - const fileReader = new FileReader(); - - fileReader.onload = function() { - book.open(this.result).then(() => { - - // let's store this for later - localforage.setItem(cacheId('book'), blob); - - }).catch((e) => { - $(".loading_message").html("Unable to open book.<br/><small>" + e + "</small>"); - }); - }; - - fileReader.onerror = function(e) { - console.log('filereader error', e); - $(".loading_message").html("Unable to open book.<br/><small>" + e + "</small>"); - } - - fileReader.readAsArrayBuffer(blob); - - }).catch((e) => { - console.log('blob error', e); - $(".loading_message").html("Unable to download book.<br/><small>" + e + "</small>"); - }); - } else { - $(".loading_message").html("Unable to download book: " + resp.status + "."); - } - }).catch(function(e) { - console.warn(e); - - if ($(".loading").is(":visible")) { - $(".loading_message").html("Unable to load book (remote).<br/><small>" + e + "</small>"); - } - }); - - } else { - $(".loading_message").html("This book is not available offline."); - } - } - }); - - const book = ePub(); - window.book = book; - - const rendition = book.renderTo("reader", { - width: '100%', - height: '100%', - minSpreadWidth: 961 - }); - - const displayed = rendition.display(); - - // this sets default theme, then we apply CSS to already rendered content - // with apply_styles() - displayed.then(function () { - - let fontSize; - let fontFamily; - let lineHeight; - //let themeName; - - Promise.all([ - localforage.getItem("epube.fontSize"), - localforage.getItem("epube.fontFamily"), - localforage.getItem("epube.lineHeight"), - localforage.getItem("epube.theme") - ]).then(function(res) { - fontSize = res[0] ? res[0] + "px" : DEFAULT_FONT_SIZE + "px"; - fontFamily = res[1] ? res[1] : DEFAULT_FONT_FAMILY; - lineHeight = res[2] ? res[2] + "%" : DEFAULT_LINE_HEIGHT + "%"; - //themeName = res[3] ? res[3] : 'default'; - - rendition.themes.default({ - html: { - 'font-size': fontSize, - 'font-family': "'" + fontFamily + "'", - 'line-height': lineHeight, - 'text-align': 'justify' - } - }); - - }); - }); - - rendition.hooks.content.register(function(contents) { - - contents.on("linkClicked", function(href) { - console.log('linkClicked', href); - - if (href.indexOf("://") == -1) { - $(".prev_location_btn") - .attr("data-location-cfi", book.rendition.currentLocation().start.cfi) - .show(); - - window.setTimeout(function() { - show_ui(true); - }, 50); - } - - }); - - const base_url = window.location.href.match(/^.*\//)[0]; - const res_names = [ "lib/bootstrap/v3/js/jquery.js", "lib/jquery.mobile-events.min.js", - "js/reader.js", "js/dict.js" ]; - const doc = contents.document; - - for (let i = 0; i < res_names.length; i++) { - - // we need to create script element with proper context, that is inside the iframe - const elem = doc.createElement("script"); - elem.type = 'text/javascript'; - elem.text = _res_data[base_url + res_names[i]]; - - doc.head.appendChild(elem); - } - - $(contents.document.head) - .append($("<style type='text/css'>") - .text(_res_data[base_url + 'css/reader.css'])); - - return localforage.getItem("epube.theme").then(function(theme) { - if (!theme) theme = 'default'; - - const theme_url = base_url + 'themes/' + theme + '.css'; - - $(contents.document.head) - .append($("<style type='text/css' id='theme_css'>") - .text(_res_data[theme_url])); - }); - - }); - - $('#settings-modal').on('shown.bs.modal', function() { - - localforage.getItem(cacheId("lastread")).then((item) => { - if (item && item.cfi) { - $(".lastread_input").val(item.page + '%'); - } - - $.post("backend.php", { op: "getlastread", id: $.urlParam("id") }, function(data) { - $(".lastread_input").val(data.page + '%'); - }); - - }); - - localforage.getItem("epube.keep-ui-visible").then(function(keep) { - $(".keep_ui_checkbox") - .attr("checked", keep) - .off("click") - .on("click", function(evt) { - localforage.setItem("epube.keep-ui-visible", evt.target.checked); - }); - }); - - localforage.getItem("epube.fontFamily").then(function(font) { - if (!font) font = DEFAULT_FONT_FAMILY; - - $(".font_family").val(font); - }); - - localforage.getItem("epube.theme").then(function(theme) { - $(".theme_name").val(theme); - }); - - localforage.getItem("epube.fontSize").then(function(size) { - - if (!size) size = DEFAULT_FONT_SIZE; - - const zoom = $(".font_size").html(""); - - for (let i = 10; i <= 32; i++) { - const opt = $("<option>").val(i).html(i + " px"); - zoom.append(opt); - } - - zoom.val(size); - - }); - - localforage.getItem("epube.lineHeight").then(function(height) { - - if (!height) height = DEFAULT_LINE_HEIGHT; - - const zoom = $(".line_height").html(""); - - for (let i = 100; i <= 220; i += 10) { - const opt = $("<option>").val(i).html(i + "%"); - zoom.append(opt); - } - - zoom.val(height); - - }); - }); - - $('#dict-modal').on('shown.bs.modal', function() { - $(".dict_result").scrollTop(0); - }) - - // TODO: make configurable - $(".dict_search_btn").on("click", function() { - $("#dict-modal").modal('hide'); - window.open("https://duckduckgo.com/?q=" + $(".dict_query").val()); - }); - - $(".wiki_search_btn").on("click", function() { - $(".dict_result").html("Loading, please wait..."); - - $.post("backend.php", {op: "wikisearch", query: $(".dict_query").val()}) - .then((resp) => { - try { - let tmp = ""; - - $.each(resp.query.pages, (i,p) => { - tmp += p.extract; - }); - - $(".dict_result").html(tmp && tmp != "undefined" ? tmp : "No definition found for " + $(".dict_query").val() + "."); - } catch (e) { - console.error(e); - $(".dict_result").text("Error while processing data: " + e); - } - }) - .fail((e) => { - console.error(e); - $(".dict_result").text("Error while retrieving data."); - }) - }); - - function toc_loc_msg(href) { - try { - const cfiBase = book.spine.get(href).cfiBase; - - const loc = book.locations._locations.find(function(k) { - return k.indexOf(cfiBase) != -1 - }); - - return window.book.locations.locationFromCfi(loc); - - } catch (e) { - console.warn(e); - } - - return ""; - } - - function process_toc_sublist(row, list, nest) { - - if (nest == 3) return false; - - if (row.subitems) { - - const sublist = $("<ul class='toc_sublist list-unstyled'>"); - - $.each(row.subitems, function(i, row) { - - const a = $("<a>") - .attr('href', '#') - .html("<b class='pull-right'>" + toc_loc_msg(row.href) + "</b>" + row.label) - .attr('data-href', row.href) - .click(function() { - book.rendition.display(a.attr('data-href')); - }); - - sublist.append($("<li>").append(a)); - - process_toc_sublist(row, sublist, nest + 1); - - }); - - list.append(sublist); - } - } - - $('#toc-modal').on('shown.bs.modal', function() { - - const toc = book.navigation.toc; - - const list = $(".toc_list"); - list.html(""); - - $.each(toc, function(i, row) { - - // if anything fails here the toc entry is likely useless anyway (i.e. no cfi) - try { - const a = $("<a>") - .attr('href', '#') - .html("<b class='pull-right'>" + toc_loc_msg(row.href) + "</b>" + row.label) - .attr('data-href', row.href) - .click(function() { - book.rendition.display(a.attr('data-href')); - }); - - list.append($("<li>").append(a)); - - process_toc_sublist(row, list, 0); - - } catch (e) { - console.warn(e); - } - }); - - // well the toc didn't work out, might as well generate one - if (list.children().length <= 1) { - - list.html(""); - - $.each(book.spine.items, function (i, row) { - - const a = $("<a>") - .attr('href', '#') - .attr('title', row.url) - .html("Section " + (i+1)) - .attr('data-href', row.href) - .click(function() { - book.rendition.display(a.attr('data-href')); - }); - - list.append($("<li>").append(a)); - - }); - } - - }); - - book.ready.then(function() { - - const meta = book.package.metadata; - - document.title = meta.title + " – " + meta.creator + " – The Epube"; - $(".title").text(meta.title); - - if (typeof EpubeApp != "undefined") { - EpubeApp.setTitle(meta.title); - EpubeApp.showActionBar(false); - } - - return localforage.getItem(cacheId("locations")).then(function(locations) { - - console.log('stored pagination', locations != null); - - // legacy format is array of objects {cfi: ..., page: ...} - if (locations && typeof locations[0] == "string") { - _pagination_stored = 1; - return book.locations.load(locations); - } else { - console.log("requesting pagination..."); - - const url = "backend.php?op=getpagination&id=" + encodeURIComponent($.urlParam("id")); - - return fetch(url, {credentials:'same-origin'}).then(function(resp) { - - if (resp.ok) { - return resp.json().then(function(locations) { - if (locations && typeof locations[0] == "string") { - _pagination_stored = 1; - return book.locations.load(locations); - } else { - $(".loading_message").html("Paginating..."); - return book.locations.generate(1600); - } - }); - } else { - $(".loading_message").html("Paginating..."); - return book.locations.generate(1600); - } - }).catch(function() { - $(".loading_message").html("Paginating..."); - return book.locations.generate(1600); - }); - } - - }); - - }).then(function(locations) { - - console.log("locations ready, stored=", _pagination_stored); - - if (locations) { - if (navigator.onLine && !_pagination_stored) { - $.post("backend.php", { op: "storepagination", id: $.urlParam("id"), - payload: JSON.stringify(locations), total: 100}); - } - - // store if needed - localforage.getItem(cacheId("locations")).then(function(item) { - if (!item) localforage.setItem(cacheId("locations"), locations); - }); - - } else { - $(".loading_message").html("Pagination failed."); - return; - } - - $(".location").click(function() { - const current = book.rendition.currentLocation().start.location; - const total = book.locations.length(); - - const page = prompt("Jump to location [1-" + total + "]", current); - - if (page) { - book.rendition.display(book.locations._locations[page]); - } - }); - - open_lastread(); - - window.setTimeout(function() { - open_lastread(); - - $(".loading").hide(); - }, 250); - }); - - rendition.on("keyup", hotkey_handler); - - rendition.on('resized', function() { - console.log('resized'); - - $(".loading").show(); - $(".loading_message").html("Opening chapter..."); - - window.setTimeout(function() { - open_lastread(); - - $(".loading").hide(); - }, 250); - }); - - rendition.on('rendered', function(/*chapter*/) { - $(".chapter").html($("<span>").addClass("glyphicon glyphicon-th-list")); - - resize_side_columns(); - - try { - const location = book.rendition.currentLocation(); - - if (location.start) { - const cur_href = book.canonical(location.start.href); - let toc_entry = false; - - /* eslint-disable no-inner-declarations */ - function iterate_sublist(row, nest) { - if (nest == 2) return false; - - if (row.subitems) { - $.each(row.subitems, function (i, r) { - - if (book.spine.get(r.href).canonical == cur_href) { - toc_entry = r; - return true; - } - - if (iterate_sublist(r, nest + 0)) - return true; - }); - } - - return false; - } - /* eslint-enable no-inne-declarations */ - - $.each(book.navigation.toc, function(i, a) { - if (book.spine.get(a.href).canonical == cur_href) { - toc_entry = a; - return; - } - - if (iterate_sublist(a, 0)) return; - - }); - - if (toc_entry && toc_entry.label.trim()) - $(".chapter").append(" " + toc_entry.label); - } - - } catch (e) { - console.warn(e); - } - }); - - rendition.on('relocated', function(location) { - - // locations not generated yet - if (book.locations.length() == 0) - return; - - const currentCfi = location.start.cfi; - const currentPage = parseInt(book.locations.percentageFromCfi(currentCfi) * 100); - const pct = book.locations.percentageFromCfi(currentCfi); - - $("#cur_page").html(location.start.location); - $("#total_pages").html(book.locations.length()); - - $("#page_pct").html(parseInt(pct*100) + '%'); - - if (_store_position && new Date().getTime()/1000 - _last_position_sync > 15) { - console.log("storing lastread", currentPage, currentCfi); - - if (navigator.onLine) { - - $.post("backend.php", { op: "storelastread", id: $.urlParam("id"), page: currentPage, - cfi: currentCfi }, function(data) { - - if (data.cfi) { - _last_position_sync = new Date().getTime()/1000; - } - - }) - .fail(function(e) { - if (e && e.status == 401) { - window.location = "index.php"; - } - }); - - _store_position = 0; - } else { - _last_position_sync = 0; - } - - localforage.setItem(cacheId("lastread"), - {cfi: currentCfi, page: currentPage, total: 100}); - - } - }); - -} - -/* exported toggle_fullscreen */ -function toggle_fullscreen() { - if (typeof EpubeApp != "undefined") { - EpubeApp.toggleSystemUI(); - } else { - const element = document.documentElement; - const isFullscreen = document.webkitIsFullScreen || document.mozFullScreen || false; - - element.requestFullScreen = element.requestFullScreen || element.webkitRequestFullScreen || element.mozRequestFullScreen || - function () { return false; }; - - document.cancelFullScreen = document.cancelFullScreen || document.webkitCancelFullScreen || document.mozCancelFullScreen || - function () { return false; }; - - isFullscreen ? document.cancelFullScreen() : element.requestFullScreen(); - } -} - -function show_ui(show) { - if (show) - $(".header,.footer").fadeIn(); - else - $(".header,.footer").fadeOut(); -} - -/* exported toggle_ui */ -function toggle_ui() { - if ($(".header").is(":visible")) - $(".header,.footer").fadeOut(); - else - $(".header,.footer").fadeIn(); -} - -function open_lastread() { - localforage.getItem(cacheId("lastread")).then(function(item) { - console.log('lr local', item); - - item = item || {}; - - // CFI missing or w/e - try { - - // this is ridiculous tbh - if (item.cfi) book.rendition.display(item.cfi).then(() => { - book.rendition.display(item.cfi); - }); - - } catch (e) { - console.warn(e); - } - - if (navigator.onLine) { - - $.post("backend.php", { op: "getlastread", id: $.urlParam("id") }, function(data) { - console.log('lr remote', data); - - if (navigator.onLine && data) { - localforage.setItem(cacheId("lastread"), - {cfi: data.cfi, page: data.page, total: data.total}); - - try { - if (item.cfi != data.cfi && (!item.page || data.page >= item.page)) - console.log('using remote lastread...'); - - book.rendition.display(data.cfi).then(() => { - book.rendition.display(data.cfi); - }); - } catch (e) { - console.warn(e); - } - - } - }) - .fail(function(e) { - if (e && e.status == 401) { - window.location = "index.php"; - } - }); - } - - }); -} - -function next_page() { - _store_position = 1; - - window.book.rendition.next(); - - if (typeof EpubeApp != "undefined") - EpubeApp.showActionBar(false); - else - localforage.getItem("epube.keep-ui-visible").then(function(keep) { - if (!keep) show_ui(false); - }); -} - -function prev_page() { - window.book.rendition.prev(); - - if (typeof EpubeApp != "undefined") - EpubeApp.showActionBar(false); - else - localforage.getItem("epube.keep-ui-visible").then(function(keep) { - if (!keep) show_ui(false); - }); -} - -function hotkey_handler(e) { - try { - //console.log('K3:' + e.which, e); - - if ($(".modal").is(":visible")) - return; - - // right or space - if (e.which == 39 || e.which == 32) { - e.preventDefault(); - next_page(); - } - - // left - if (e.which == 37) { - e.preventDefault(); - prev_page(); - } - - // esc - if (e.which == 27) { - e.preventDefault(); - show_ui(true); - } - } catch (e) { - console.warn(e); - } -} - -function resize_side_columns() { - let width = $("#reader").position().left; - const iframe = $("#reader iframe")[0]; - - if (iframe && iframe.contentWindow.$) - width += parseInt(iframe.contentWindow.$("body").css("padding-left")); - - $("#left, #right").width(width); -} - -$(document).ready(function() { - if ('serviceWorker' in navigator) { - navigator.serviceWorker - .register('worker.js') - .then(function() { - console.log("service worker registered"); - - init_loader(); - }); - } else { - alert("Service worker support missing in browser (are you using plain HTTP?)."); - } - - $(document).on("keyup", function(e) { - hotkey_handler(e); - }); - - $("#left").on("mouseup", function() { - prev_page(); - }); - - $("#right").on("mouseup", function() { - next_page(); - }); -}); - -/* exported apply_line_height */ -function apply_line_height(elem) { - const height = $(elem).val(); - - localforage.setItem("epube.lineHeight", height).then(function() { - apply_styles(); - }); -} - -/* exported apply_font */ -function apply_font(elem) { - const font = $(elem).val(); - - localforage.setItem("epube.fontFamily", font).then(function() { - apply_styles(); - }); - -} - -/* exported apply_font_size */ -function apply_font_size(elem) { - const size = $(elem).val(); - - localforage.setItem("epube.fontSize", size).then(function() { - apply_styles(); - }); -} - -function apply_styles() { - - Promise.all([ - localforage.getItem("epube.fontSize"), - localforage.getItem("epube.fontFamily"), - localforage.getItem("epube.lineHeight"), - localforage.getItem("epube.theme") - ]).then(function(res) { - const fontSize = res[0] ? res[0] + "px" : DEFAULT_FONT_SIZE + "px"; - const fontFamily = res[1] ? res[1] : DEFAULT_FONT_FAMILY; - const lineHeight = res[2] ? res[2] + "%" : DEFAULT_LINE_HEIGHT + "%"; - //const themeName = res[3] ? res[3] : false; - - console.log('style', fontFamily, fontSize, lineHeight); - - $.each(window.book.rendition.getContents(), function(i, c) { - c.css("font-size", fontSize); - c.css("font-family", "'" + fontFamily + "'"); - c.css("line-height", lineHeight); - }); - - apply_theme(); - }); - -} - -/* exported clear_lastread */ -function clear_lastread() { - if (confirm("Clear stored last read location?")) { - const total = window.book.locations.length(); - - if (navigator.onLine) { - $.post("backend.php", { op: "storelastread", page: -1, cfi: "", id: $.urlParam("id") }, function(data) { - $(".lastread_input").val(data.page + '%'); - }); - } - - localforage.setItem(cacheId("lastread"), - {cfi: "", page: 0, total: total}); - - } -} - -/* exported mark_as_read */ -function mark_as_read() { - if (confirm("Mark book as read?")) { - const total = 100; - const lastCfi = window.book.locations.cfiFromPercentage(1); - - if (navigator.onLine) { - $.post("backend.php", { op: "storelastread", page: total, cfi: lastCfi, id: $.urlParam("id") }, function(data) { - $(".lastread_input").val(data.page + '%'); - }); - } - - localforage.setItem(cacheId("lastread"), - {cfi: lastCfi, page: total, total: total}); - - } -} - -/* exported save_and_close */ -function save_and_close() { - const location = window.book.rendition.currentLocation(); - - const currentCfi = location.start.cfi; - const currentPage = parseInt(window.book.locations.percentageFromCfi(currentCfi) * 100); - const totalPages = 100; - - localforage.setItem(cacheId("lastread"), - {cfi: currentCfi, page: currentPage, total: totalPages}); - - if (navigator.onLine) { - $.post("backend.php", { op: "storelastread", id: $.urlParam("id"), page: currentPage, - cfi: currentCfi }, function() { - window.location = $.urlParam("rt") ? "index.php?mode=" + $.urlParam("rt") : "index.php"; - }) - .fail(function() { - window.location = "index.php"; - }); - } else { - window.location = "index.php"; - } -} - -/* exported change_theme */ -function change_theme(elem) { - const theme = $(elem).val(); - - localforage.setItem("epube.theme", theme).then(function() { - apply_styles(); - }); -} - -function apply_theme() { - localforage.getItem("epube.theme").then(function(theme) { - console.log('theme', theme); - - const base_url = window.location.href.match(/^.*\//)[0]; - - if (!theme) theme = 'default'; - const theme_url = base_url + "themes/" + theme + ".css"; - const theme_data = _res_data[theme_url]; - - if (!theme_data) { - console.error('theme data not found for', theme, '- check resource loader configuration'); - return; - } - - $("#theme_css").attr("href", theme_url); - - $.each(window.book.rendition.getContents(), function(i,c) { - $(c.document).find("#theme_css").text(_res_data[theme_url]) - }); - - }); -} - -/* exported search */ -function search() { - const query = $(".search_input").val(); - const list = $(".search_results"); - - list.html(""); - - if (query) { - - /* eslint-disable prefer-spread */ - Promise.all( - book.spine.spineItems.map( - (item) => item.load(book.load.bind(book)) - .then(item.find.bind(item, query)) - .finally(item.unload.bind(item))) - ) - .then((results) => Promise.resolve([].concat.apply([], results))) - .then(function(results) { - $.each(results, function (i, row) { - const a = $("<a>") - .attr('href', '#') - .html("<b class='pull-right'>" + window.book.locations.locationFromCfi(row.cfi) + "</b>" + row.excerpt) - .attr('data-cfi', row.cfi) - .attr('data-id', row.id) - .click(function() { - window.book.rendition.display(a.attr('data-cfi')); - }); - - list.append($("<li>").append(a)); - }); - }); - } -} - -/* exported dict_lookup */ -function dict_lookup(word, callback) { - $.post("backend.php", {op: 'define', word: word}, function(data) { - if (data) { - - $(".dict_result").html(data.result.join("<br/>")); - $(".dict_query").val(word); - $("#dict-modal").modal('show'); - - if (callback) callback(); - } - }); -} - -/* exported open_previous_location */ -function open_previous_location(elem) { - const cfi = $(elem).attr("data-location-cfi"); - - if (cfi) { - window.book.rendition.display(cfi); - } - - $(elem).fadeOut(); -} diff --git a/js/reader.js b/js/reader.js index 1f6509a..d98e867 100644 --- a/js/reader.js +++ b/js/reader.js @@ -1,58 +1,1036 @@ 'use strict'; -/* global EpubeApp */ +/* global localforage, EpubeApp */ -function enable_swipes() { - $(window).off("swipeleft swiperight"); +const DEFAULT_FONT_SIZE = 16; +const DEFAULT_FONT_FAMILY = "Georgia"; +const DEFAULT_LINE_HEIGHT = 140; - $(window).on("swipeleft", function() { - parent.next_page(); - }); +const Reader = { + init: function() { + if ('serviceWorker' in navigator) { + navigator.serviceWorker + .register('worker.js') + .then(function() { + console.log("service worker registered"); - $(window).on("swiperight", function() { - parent.prev_page(); - }); -} + Reader.Loader.init(); + }); + } else { + alert("Service worker support missing in browser (are you using plain HTTP?)."); + } -$(document).ready(function() { + $(document).on("keyup", function(e) { + Reader.hotkeyHandler(e); + }); + + $("#left").on("mouseup", function() { + Reader.Page.prev(); + }); + + $("#right").on("mouseup", function() { + Reader.Page.next(); + }); + }, + initSecondStage: function() { + + if (typeof EpubeApp != "undefined") { + EpubeApp.setPage("PAGE_READER"); + } + + Reader.applyTheme(); + + /* global Cookie */ + + if (Cookie.get("is-epube-app") == "true") { + $("body").addClass("is-epube-app"); + } + + $(window).on('online', function() { + console.log("we're online, storing lastread"); + + const currentCfi = book.rendition.currentLocation().start.cfi; + const currentPage = parseInt(book.locations.percentageFromCfi(currentCfi) * 100); + + $.post("backend.php", { op: "storelastread", id: $.urlParam("id"), page: currentPage, + cfi: currentCfi }, function(data) { + + if (data.cfi) { + Reader.Page._last_position_sync = new Date().getTime()/1000; + } + }) + .fail(function(e) { + if (e && e.status == 401) { + window.location = "index.php"; + } + }); + }); + + localforage.getItem(Reader.cacheId("book")).then(function(item) { + + // ios doesn't work with FileReader for whatever reason + if (/*!_is_ios &&*/ item) { + + console.log("loading from local storage"); + + const fileReader = new FileReader(); + + fileReader.onload = function() { + try { + book.open(this.result); + } catch (e) { + $(".loading_message").html("Unable to load book (local)."); + console.log(e); + } + }; + + fileReader.readAsArrayBuffer(item); + + } else { + + console.log("loading from network"); + + if (navigator.onLine) { + const book_url = "backend.php?op=download&id=" + $.urlParam("id"); + + $(".loading_message").html("Downloading..."); + + fetch(book_url, {credentials: 'same-origin'}).then(function(resp) { + + if (resp.status == 200) { + const bookId = $.urlParam("b"); + + resp.blob().then(function(blob) { + + // if there's no base information cached yet, let's do that too + localforage.getItem(Reader.cacheId()).then(function(info) { + if (!info) { + $.post("backend.php", {op: "getinfo", id: bookId }, function(data) { + if (data) { + localforage.setItem(Reader.cacheId(), data); + + if (data.has_cover) { + fetch("backend.php?op=cover&id=" + bookId, {credentials: 'same-origin'}).then(function(resp) { + if (resp.status == 200) { + localforage.setItem(Reader.cacheId('cover'), resp.blob()); + } + }); + } + } + }); + } + }); + + const fileReader = new FileReader(); + + fileReader.onload = function() { + book.open(this.result).then(() => { + + // let's store this for later + localforage.setItem(Reader.cacheId('book'), blob); + + }).catch((e) => { + $(".loading_message").html("Unable to open book.<br/><small>" + e + "</small>"); + }); + }; + + fileReader.onerror = function(e) { + console.log('filereader error', e); + $(".loading_message").html("Unable to open book.<br/><small>" + e + "</small>"); + }; + + fileReader.readAsArrayBuffer(blob); + + }).catch((e) => { + console.log('blob error', e); + $(".loading_message").html("Unable to download book.<br/><small>" + e + "</small>"); + }); + } else { + $(".loading_message").html("Unable to download book: " + resp.status + "."); + } + }).catch(function(e) { + console.warn(e); + + if ($(".loading").is(":visible")) { + $(".loading_message").html("Unable to load book (remote).<br/><small>" + e + "</small>"); + } + }); + + } else { + $(".loading_message").html("This book is not available offline."); + } + } + }); + + const book = ePub(); + window.book = book; + + const rendition = book.renderTo("reader", { + width: '100%', + height: '100%', + minSpreadWidth: 961 + }); + + rendition.hooks.content.register(function() { + Reader.applyStyles(); + }); + + /*rendition.display().then(function() { + Reader.applyStyles(); + });*/ + + // this sets default theme, then we apply CSS to already rendered content + // with apply_styles() + /* displayed.then(function () { + + let fontSize; + let fontFamily; + let lineHeight; + //let themeName; + + Promise.all([ + localforage.getItem("epube.fontSize"), + localforage.getItem("epube.fontFamily"), + localforage.getItem("epube.lineHeight"), + localforage.getItem("epube.theme") + ]).then(function(res) { + fontSize = res[0] ? res[0] + "px" : DEFAULT_FONT_SIZE + "px"; + fontFamily = res[1] ? res[1] : DEFAULT_FONT_FAMILY; + lineHeight = res[2] ? res[2] + "%" : DEFAULT_LINE_HEIGHT + "%"; + //themeName = res[3] ? res[3] : 'default'; + + rendition.themes.default({ + html: { + 'font-size': fontSize, + 'font-family': "'" + fontFamily + "'", + 'line-height': lineHeight, + 'text-align': 'justify' + } + }); + + }); + }); */ + + rendition.hooks.content.register(function(contents) { + + contents.on("linkClicked", function(href) { + console.log('linkClicked', href); + + if (href.indexOf("://") == -1) { + $(".prev_location_btn") + .attr("data-location-cfi", book.rendition.currentLocation().start.cfi) + .show(); + + window.setTimeout(function() { + show_ui(true); + }, 50); + } + + }); + + const base_url = window.location.href.match(/^.*\//)[0]; + const res_names = [ "lib/bootstrap/v3/js/jquery.js", "lib/jquery.mobile-events.min.js", + "js/reader_iframe.js", "js/dict.js" ]; + const doc = contents.document; + + for (let i = 0; i < res_names.length; i++) { + + // we need to create script element with proper context, that is inside the iframe + const elem = doc.createElement("script"); + elem.type = 'text/javascript'; + elem.text = Reader.Loader._res_data[base_url + res_names[i]]; + + doc.head.appendChild(elem); + } + + $(contents.document.head) + .append($("<style type='text/css'>") + .text(Reader.Loader._res_data[base_url + 'css/reader.css'])); + + return localforage.getItem("epube.theme").then(function(theme) { + if (!theme) theme = 'default'; - $(window).on("doubletap", function(/* evt */) { - const sel = getSelection().toString().trim(); + const theme_url = base_url + 'themes/' + theme + '.css'; - if (sel.match(/^$/)) { - parent.toggle_fullscreen(); + $(contents.document.head) + .append($("<style type='text/css' id='theme_css'>") + .text(Reader.Loader._res_data[theme_url])); + }); + + }); + + $('#settings-modal').on('shown.bs.modal', function() { + + localforage.getItem(Reader.cacheId("lastread")).then((item) => { + if (item && item.cfi) { + $(".lastread_input").val(item.page + '%'); + } + + $.post("backend.php", { op: "getlastread", id: $.urlParam("id") }, function(data) { + $(".lastread_input").val(data.page + '%'); + }); + + }); + + localforage.getItem("epube.keep-ui-visible").then(function(keep) { + $(".keep_ui_checkbox") + .attr("checked", keep) + .off("click") + .on("click", function(evt) { + localforage.setItem("epube.keep-ui-visible", evt.target.checked); + }); + }); + + localforage.getItem("epube.fontFamily").then(function(font) { + if (!font) font = DEFAULT_FONT_FAMILY; + + $(".font_family").val(font); + }); + + localforage.getItem("epube.theme").then(function(theme) { + $(".theme_name").val(theme); + }); + + localforage.getItem("epube.fontSize").then(function(size) { + + if (!size) size = DEFAULT_FONT_SIZE; + + const zoom = $(".font_size").html(""); + + for (let i = 10; i <= 32; i++) { + const opt = $("<option>").val(i).html(i + " px"); + zoom.append(opt); + } + + zoom.val(size); + + }); + + localforage.getItem("epube.lineHeight").then(function(height) { + + if (!height) height = DEFAULT_LINE_HEIGHT; + + const zoom = $(".line_height").html(""); + + for (let i = 100; i <= 220; i += 10) { + const opt = $("<option>").val(i).html(i + "%"); + zoom.append(opt); + } + + zoom.val(height); + + }); + }); + + $('#dict-modal').on('shown.bs.modal', function() { + $(".dict_result").scrollTop(0); + }); + + // TODO: make configurable + $(".dict_search_btn").on("click", function() { + $("#dict-modal").modal('hide'); + window.open("https://duckduckgo.com/?q=" + $(".dict_query").val()); + }); + + $(".wiki_search_btn").on("click", function() { + $(".dict_result").html("Loading, please wait..."); + + $.post("backend.php", {op: "wikisearch", query: $(".dict_query").val()}) + .then((resp) => { + try { + let tmp = ""; + + $.each(resp.query.pages, (i,p) => { + tmp += p.extract; + }); + + $(".dict_result").html(tmp && tmp != "undefined" ? tmp : "No definition found for " + $(".dict_query").val() + "."); + } catch (e) { + console.error(e); + $(".dict_result").text("Error while processing data: " + e); + } + }) + .fail((e) => { + console.error(e); + $(".dict_result").text("Error while retrieving data."); + }) + }); + + function toc_loc_msg(href) { + try { + const cfiBase = book.spine.get(href).cfiBase; + + const loc = book.locations._locations.find(function(k) { + return k.indexOf(cfiBase) != -1 + }); + + return window.book.locations.locationFromCfi(loc); + + } catch (e) { + console.warn(e); + } + + return ""; } - }); - $(window).on("click tap", function(evt) { - if (evt.button == 0) { + function process_toc_sublist(row, list, nest) { + + if (nest == 3) return false; + + if (row.subitems) { + + const sublist = $("<ul class='toc_sublist list-unstyled'>"); + + $.each(row.subitems, function(i, row) { + + const a = $("<a>") + .attr('href', '#') + .html("<b class='pull-right'>" + toc_loc_msg(row.href) + "</b>" + row.label) + .attr('data-href', row.href) + .click(function() { + book.rendition.display(a.attr('data-href')); + }); + + sublist.append($("<li>").append(a)); + + process_toc_sublist(row, sublist, nest + 1); + + }); + + list.append(sublist); + } + } + + $('#toc-modal').on('shown.bs.modal', function() { + + const toc = book.navigation.toc; + + const list = $(".toc_list"); + list.html(""); + + $.each(toc, function(i, row) { + + // if anything fails here the toc entry is likely useless anyway (i.e. no cfi) + try { + const a = $("<a>") + .attr('href', '#') + .html("<b class='pull-right'>" + toc_loc_msg(row.href) + "</b>" + row.label) + .attr('data-href', row.href) + .click(function() { + book.rendition.display(a.attr('data-href')); + }); + + list.append($("<li>").append(a)); + + process_toc_sublist(row, list, 0); + + } catch (e) { + console.warn(e); + } + }); + + // well the toc didn't work out, might as well generate one + if (list.children().length <= 1) { + + list.html(""); + + $.each(book.spine.items, function (i, row) { + + const a = $("<a>") + .attr('href', '#') + .attr('title', row.url) + .html("Section " + (i+1)) + .attr('data-href', row.href) + .click(function() { + book.rendition.display(a.attr('data-href')); + }); + + list.append($("<li>").append(a)); + + }); + } + + }); + + book.ready.then(function() { + + const meta = book.package.metadata; + + document.title = meta.title + " – " + meta.creator + " – The Epube"; + $(".title").text(meta.title); + + if (typeof EpubeApp != "undefined") { + EpubeApp.setTitle(meta.title); + EpubeApp.showActionBar(false); + } + + return localforage.getItem(Reader.cacheId("locations")).then(function(locations) { + + console.log('stored pagination', locations != null); + + // legacy format is array of objects {cfi: ..., page: ...} + if (locations && typeof locations[0] == "string") { + Reader.Page._pagination_stored = 1; + return book.locations.load(locations); + } else { + console.log("requesting pagination..."); + + const url = "backend.php?op=getpagination&id=" + encodeURIComponent($.urlParam("id")); + + return fetch(url, {credentials:'same-origin'}).then(function(resp) { + + if (resp.ok) { + return resp.json().then(function(locations) { + if (locations && typeof locations[0] == "string") { + Reader.Page._pagination_stored = 1; + return book.locations.load(locations); + } else { + $(".loading_message").html("Paginating..."); + return book.locations.generate(1600); + } + }); + } else { + $(".loading_message").html("Paginating..."); + return book.locations.generate(1600); + } + }).catch(function() { + $(".loading_message").html("Paginating..."); + return book.locations.generate(1600); + }); + } + + }); + + }).then(function(locations) { + + console.log("locations ready, stored=", Reader.Page._pagination_stored); + + if (locations) { + if (navigator.onLine && !Reader.Page._pagination_stored) { + $.post("backend.php", { op: "storepagination", id: $.urlParam("id"), + payload: JSON.stringify(locations), total: 100}); + } + + // store if needed + localforage.getItem(Reader.cacheId("locations")).then(function(item) { + if (!item) localforage.setItem(Reader.cacheId("locations"), locations); + }); + + } else { + $(".loading_message").html("Pagination failed."); + return; + } + + $(".location").click(function() { + const current = book.rendition.currentLocation().start.location; + const total = book.locations.length(); + + const page = prompt("Jump to location [1-" + total + "]", current); + + if (page) { + book.rendition.display(book.locations._locations[page]); + } + }); + + Reader.Page.openLastRead(); + + window.setTimeout(function() { + Reader.Page.openLastRead(); + + $(".loading").hide(); + }, 250); + }); + + rendition.on("keyup", (e) => { Reader.hotkeyHandler(e) }); + + rendition.on('resized', function() { + console.log('resized'); + + $(".loading").show(); + $(".loading_message").html("Opening chapter..."); + + window.setTimeout(function() { + Reader.Page.openLastRead(); + + $(".loading").hide(); + }, 250); + }); + + rendition.on('rendered', function(/*chapter*/) { + $(".chapter").html($("<span>").addClass("glyphicon glyphicon-th-list")); + + Reader.resizeSideColumns(); + + try { + const location = book.rendition.currentLocation(); + + if (location.start) { + const cur_href = book.canonical(location.start.href); + let toc_entry = false; + + /* eslint-disable no-inner-declarations */ + function iterate_sublist(row, nest) { + if (nest == 2) return false; + + if (row.subitems) { + $.each(row.subitems, function (i, r) { + + if (book.spine.get(r.href).canonical == cur_href) { + toc_entry = r; + return true; + } + + if (iterate_sublist(r, nest + 0)) + return true; + }); + } + + return false; + } + /* eslint-enable no-inne-declarations */ + + $.each(book.navigation.toc, function(i, a) { + if (book.spine.get(a.href).canonical == cur_href) { + toc_entry = a; + return; + } + + if (iterate_sublist(a, 0)) return; + + }); + + if (toc_entry && toc_entry.label.trim()) + $(".chapter").append(" " + toc_entry.label); + } + + } catch (e) { + console.warn(e); + } + }); + + rendition.on('relocated', function(location) { + + // locations not generated yet + if (book.locations.length() == 0) + return; + + const currentCfi = location.start.cfi; + const currentPage = parseInt(book.locations.percentageFromCfi(currentCfi) * 100); + const pct = book.locations.percentageFromCfi(currentCfi); + + $("#cur_page").html(location.start.location); + $("#total_pages").html(book.locations.length()); + + $("#page_pct").html(parseInt(pct*100) + '%'); + + if (Reader.Page._store_position && new Date().getTime()/1000 - Reader.Page._last_position_sync > 15) { + console.log("storing lastread", currentPage, currentCfi); + + if (navigator.onLine) { + + $.post("backend.php", { op: "storelastread", id: $.urlParam("id"), page: currentPage, + cfi: currentCfi }, function(data) { + + if (data.cfi) { + Reader.Page._last_position_sync = new Date().getTime()/1000; + } + + }) + .fail(function(e) { + if (e && e.status == 401) { + window.location = "index.php"; + } + }); + + Reader.Page._store_position = 0; + } else { + Reader.Page._last_position_sync = 0; + } + + localforage.setItem(Reader.cacheId("lastread"), + {cfi: currentCfi, page: currentPage, total: 100}); + + } + }); + }, + applyStyles: function() { + Promise.all([ + localforage.getItem("epube.fontSize"), + localforage.getItem("epube.fontFamily"), + localforage.getItem("epube.lineHeight"), + localforage.getItem("epube.theme") + ]).then(function(res) { + const fontSize = res[0] ? res[0] + "px" : DEFAULT_FONT_SIZE + "px"; + const fontFamily = res[1] ? res[1] : DEFAULT_FONT_FAMILY; + const lineHeight = res[2] ? res[2] + "%" : DEFAULT_LINE_HEIGHT + "%"; + //const themeName = res[3] ? res[3] : false; + + console.log('style', fontFamily, fontSize, lineHeight); + + $.each(window.book.rendition.getContents(), function(i, c) { + c.css("font-size", fontSize); + c.css("font-family", "'" + fontFamily + "'"); + c.css("line-height", lineHeight); + }); + + Reader.applyTheme(); + }); + + }, + applyTheme: function() { + localforage.getItem("epube.theme").then(function(theme) { + console.log('theme', theme); + + const base_url = window.location.href.match(/^.*\//)[0]; + + if (!theme) theme = 'default'; + + const theme_url = base_url + "themes/" + theme + ".css"; + const theme_data = Reader.Loader._res_data[theme_url]; + + if (!theme_data) { + console.error('theme data not found for', theme, '- check resource loader configuration'); + return; + } + + $("#theme_css").attr("href", theme_url); + + /* apply to existing reader */ + $.each(window.book.rendition.getContents(), function(i, c) { + $(c.document).find("#theme_css").text(Reader.Loader._res_data[theme_url]) + }); + + }); + }, + hotkeyHandler: function(e) { + try { + //console.log('K3:' + e.which, e); if ($(".modal").is(":visible")) - return; + return; + + // right or space + if (e.which == 39 || e.which == 32) { + e.preventDefault(); + Reader.Page.next(); + } + + // left + if (e.which == 37) { + e.preventDefault(); + Reader.Page.prev(); + } + + // esc + if (e.which == 27) { + e.preventDefault(); + Reader.showUI(true); + } + } catch (e) { + console.warn(e); + } + }, + resizeSideColumns: function() { + let width = $("#reader").position().left; + const iframe = $("#reader iframe")[0]; + + if (iframe && iframe.contentWindow.$) + width += parseInt(iframe.contentWindow.$("body").css("padding-left")); + + //console.log("resize columns, width=", width); + + $("#left, #right").width(width); + }, + markAsRead: function() { + if (confirm("Mark book as read?")) { + const total = 100; + const lastCfi = window.book.locations.cfiFromPercentage(1); + + if (navigator.onLine) { + $.post("backend.php", { op: "storelastread", page: total, cfi: lastCfi, id: $.urlParam("id") }, function(data) { + $(".lastread_input").val(data.page + '%'); + }); + } + + localforage.setItem(Reader.cacheId("lastread"), + {cfi: lastCfi, page: total, total: total}); + + } + }, + close: function() { + const location = window.book.rendition.currentLocation(); + + const currentCfi = location.start.cfi; + const currentPage = parseInt(window.book.locations.percentageFromCfi(currentCfi) * 100); + const totalPages = 100; + + localforage.setItem(Reader.cacheId("lastread"), + {cfi: currentCfi, page: currentPage, total: totalPages}); + + if (navigator.onLine) { + $.post("backend.php", { op: "storelastread", id: $.urlParam("id"), page: currentPage, + cfi: currentCfi }, function() { + window.location = $.urlParam("rt") ? "index.php?mode=" + $.urlParam("rt") : "index.php"; + }) + .fail(function() { + window.location = "index.php"; + }); + } else { + window.location = "index.php"; + } + }, + cacheId: function(suffix) { + return "epube-book." + $.urlParam("b") + (suffix ? "." + suffix : ""); + }, + toggleFullscreen: function() { + if (typeof EpubeApp != "undefined") { + EpubeApp.toggleSystemUI(); + } else { + const element = document.documentElement; + const isFullscreen = document.webkitIsFullScreen || document.mozFullScreen || false; + + element.requestFullScreen = element.requestFullScreen || element.webkitRequestFullScreen || element.mozRequestFullScreen || + function () { return false; }; + + document.cancelFullScreen = document.cancelFullScreen || document.webkitCancelFullScreen || document.mozCancelFullScreen || + function () { return false; }; + + isFullscreen ? document.cancelFullScreen() : element.requestFullScreen(); + } + }, + showUI: function(show) { + if (show) + $(".header,.footer").fadeIn(); + else + $(".header,.footer").fadeOut(); + }, + toggleUI: function() { + if ($(".header").is(":visible")) + $(".header,.footer").fadeOut(); + else + $(".header,.footer").fadeIn(); + }, + lookupWord: function(word, callback) { + $.post("backend.php", {op: 'define', word: word}, function (data) { + if (data) { + + $(".dict_result").html(data.result.join("<br/>")); + $(".dict_query").val(word); + $("#dict-modal").modal('show'); + + if (callback) callback(); + } + }); + }, + search: function() { + const query = $(".search_input").val(); + const list = $(".search_results"); + + list.html(""); + + if (query) { + + /* eslint-disable prefer-spread */ + Promise.all( + book.spine.spineItems.map( + (item) => item.load(book.load.bind(book)) + .then(item.find.bind(item, query)) + .finally(item.unload.bind(item))) + ) + .then((results) => Promise.resolve([].concat.apply([], results))) + .then(function(results) { + $.each(results, function (i, row) { + const a = $("<a>") + .attr('href', '#') + .html("<b class='pull-right'>" + window.book.locations.locationFromCfi(row.cfi) + "</b>" + row.excerpt) + .attr('data-cfi', row.cfi) + .attr('data-id', row.id) + .click(function() { + window.book.rendition.display(a.attr('data-cfi')); + }); + + list.append($("<li>").append(a)); + }); + }); + } + }, + Loader: { + _res_data: [], + init: function() { + // we need to preload resources for reader iframe because it can't utilize our + // service worker because while offline it is created outside our base server context + const res_names = [ "lib/bootstrap/v3/js/jquery.js", "lib/jquery.mobile-events.min.js", + "css/transitions.css", "js/reader_iframe.js", "css/reader.css", "js/dict.js", + "themes/default.css", "themes/light.css", "themes/mocca.css", "themes/night.css", + "themes/plan9.css", "themes/gray.css", "themes/sepia.css" ]; + + for (let i = 0; i < res_names.length; i++) { + fetch(res_names[i], {credentials: 'same-origin'}).then(function(resp) { + if (resp.status == 200) { + resp.text().then(function(data) { + const url = new URL(resp.url); + url.searchParams.delete("ts"); + + Reader.Loader._res_data[url.toString()] = data; + }) + } else { + console.warn('loader failed for resource', res_names[i], resp); + } + }); + } + Reader.Loader.checkProgress(res_names, Reader.Loader._res_data, 0); + }, + checkProgress: function(res_names, res_data, attempt) { + console.log("check_resource_load", attempt, res_names.length, Object.keys(res_data).length, Reader, Reader.Loader); + + if (attempt == 5) { + $(".loading_message").html("Unable to load resources."); + return; + } + + if (res_names.length != Object.keys(res_data).length) { + window.setTimeout(function() { + Reader.Loader.checkProgress(res_names, res_data, attempt+1); + }, 250); + } else { + Reader.initSecondStage(); + } + }, + }, + Page: { + _store_position: 0, + _last_position_sync: 0, + _pagination_stored: 0, + next: function() { + Reader.Page._store_position = 1; + + window.book.rendition.next(); if (typeof EpubeApp != "undefined") - EpubeApp.toggleActionBar(); + EpubeApp.showActionBar(false); else - parent.show_ui(true); - } - }); + localforage.getItem("epube.keep-ui-visible").then(function(keep) { + if (!keep) Reader.showUI(false); + }); + }, + prev: function() { + window.book.rendition.prev(); + + if (typeof EpubeApp != "undefined") + EpubeApp.showActionBar(false); + else + localforage.getItem("epube.keep-ui-visible").then(function(keep) { + if (!keep) Reader.showUI(false); + }); + }, + openPrevious: function(elem) { + const cfi = $(elem).attr("data-location-cfi"); + + if (cfi) { + window.book.rendition.display(cfi); + } + + $(elem).fadeOut(); + }, + clearLastRead: function() { + if (confirm("Clear stored last read location?")) { + const total = window.book.locations.length(); + + if (navigator.onLine) { + $.post("backend.php", { op: "storelastread", page: -1, cfi: "", id: $.urlParam("id") }, function(data) { + $(".lastread_input").val(data.page + '%'); + }); + } - $(window).on("touchstart", function() { - enable_swipes(); - }); + localforage.setItem(Reader.cacheId("lastread"), + {cfi: "", page: 0, total: total}); - $(window).on("mousedown", function() { - $(window).off("swipeleft swiperight"); - }); + } + }, + openLastRead: function() { + localforage.getItem(Reader.cacheId("lastread")).then(function(item) { + console.log('lr local', item); - $(window).on("wheel", function(evt) { - if (evt.originalEvent.deltaY > 0) { - parent.next_page(); - } else if (evt.originalEvent.deltaY < 0) { - parent.prev_page(); + item = item || {}; + + // CFI missing or w/e + try { + + // this is ridiculous tbh + if (item.cfi) book.rendition.display(item.cfi).then(() => { + book.rendition.display(item.cfi); + }); + + } catch (e) { + console.warn(e); + } + + if (navigator.onLine) { + + $.post("backend.php", { op: "getlastread", id: $.urlParam("id") }, function(data) { + console.log('lr remote', data); + + if (navigator.onLine && data) { + localforage.setItem(Reader.cacheId("lastread"), + {cfi: data.cfi, page: data.page, total: data.total}); + + try { + if (item.cfi != data.cfi && (!item.page || data.page >= item.page)) + console.log('using remote lastread...'); + + book.rendition.display(data.cfi).then(() => { + book.rendition.display(data.cfi); + }); + } catch (e) { + console.warn(e); + } + + } + }).fail(function(e) { + if (e && e.status == 401) { + window.location = "index.php"; + } + }); + } + }); + }, + }, + Settings: { + onThemeChanged: function(elem) { + const theme = $(elem).val(); + + localforage.setItem("epube.theme", theme).then(function() { + Reader.applyTheme(); + }); + }, + onLineHeightChanged: function(elem) { + const height = $(elem).val(); + + localforage.setItem("epube.lineHeight", height).then(function() { + Reader.applyStyles(); + }); + }, + onTextSizeChanged: function(elem) { + const size = $(elem).val(); + + localforage.setItem("epube.fontSize", size).then(function() { + Reader.applyStyles(); + }); + }, + onFontChanged: function(elem) { + const font = $(elem).val(); + + localforage.setItem("epube.fontFamily", font).then(function() { + Reader.applyStyles(); + }); } - }); + } +}; - enable_swipes(); +$(document).ready(function() { + Reader.init(); }); +function __get_reader() { + return Reader; +}
\ No newline at end of file diff --git a/js/reader_iframe.js b/js/reader_iframe.js new file mode 100644 index 0000000..a31b9ac --- /dev/null +++ b/js/reader_iframe.js @@ -0,0 +1,61 @@ +'use strict'; + +/* global EpubeApp */ + +let Reader; + +function enable_swipes() { + $(window).off("swipeleft swiperight"); + + $(window).on("swipeleft", function() { + Reader.Page.next(); + }); + + $(window).on("swiperight", function() { + Reader.Page.prev(); + }); +} + +$(document).ready(function() { + Reader = parent.__get_reader(); + + $(window).on("doubletap", function(/* evt */) { + const sel = getSelection().toString().trim(); + + if (sel.match(/^$/)) { + Reader.toggleFullscreen(); + } + }); + + $(window).on("click tap", function(evt) { + if (evt.button == 0) { + + if ($(".modal").is(":visible")) + return; + + if (typeof EpubeApp != "undefined") + EpubeApp.toggleActionBar(); + else + Reader.showUI(true); + } + }); + + $(window).on("touchstart", function() { + enable_swipes(); + }); + + $(window).on("mousedown", function() { + $(window).off("swipeleft swiperight"); + }); + + $(window).on("wheel", function(evt) { + if (evt.originalEvent.deltaY > 0) { + Reader.Page.prev(); + } else if (evt.originalEvent.deltaY < 0) { + Reader.Page.next(); + } + }); + + enable_swipes(); +}); + @@ -16,7 +16,7 @@ <script src="lib/epub.js"></script> <script src="js/app.js"></script> - <script src="js/read.js"></script> + <script src="js/reader.js"></script> <link id="favicon" rel="shortcut icon" type="image/png" href="img/favicon.png" /> <link type="text/css" rel="stylesheet" media="screen" href="css/read.css" /> @@ -38,7 +38,7 @@ <div class="form-group"> <label class="col-sm-3 control-label">Font:</label> <div class="col-sm-9"> - <select class="font_family form-control" onchange="apply_font(this)"> + <select class="font_family form-control" onchange="Reader.Settings.onFontChanged(this)"> <option>Arial</option> <option>Caecilia</option> <option>Times New Roman</option> @@ -51,21 +51,21 @@ <div class="form-group"> <label class="col-sm-3 control-label">Text size:</label> <div class="col-sm-9"> - <select class="font_size form-control" onchange="apply_font_size(this)"></select> + <select class="font_size form-control" onchange="Reader.Settings.onTextSizeChanged(this)"></select> </div> </div> <div class="form-group"> <label class="col-sm-3 control-label">Line height:</label> <div class="col-sm-9"> - <select class="line_height form-control" onchange="apply_line_height(this)"></select> + <select class="line_height form-control" onchange="Reader.Settings.onLineHeightChanged(this)"></select> </div> </div> <div class="form-group"> <label class="col-sm-3 control-label">Theme:</label> <div class="col-sm-9"> - <select class="theme_name form-control" onchange="change_theme(this)"> + <select class="theme_name form-control" onchange="Reader.Settings.onThemeChanged(this)"> <option value="default">Default</option> <option value="light">Light</option> <option value="gray">Gray</option> @@ -100,7 +100,7 @@ <div class="input-group"> <input type="numeric" disabled="disabled" class="form-control lastread_input"> <span class="input-group-btn"> - <button class="btn btn-danger" type="button" onclick="clear_lastread()">Clear</button> + <button class="btn btn-danger" type="button" onclick="Reader.Page.clearLastRead()">Clear</button> </span> </div> </div> @@ -109,8 +109,8 @@ <div class="form-group"> <label class="col-sm-3 control-label"></label> <div class="col-sm-9"> - <button class="btn btn-default" type="button" onclick="open_lastread()">Open last read page</button> - <button class="btn btn-primary" type="button" onclick="mark_as_read()">Mark as read</button> + <button class="btn btn-default" type="button" onclick="Reader.Page.openLastRead()">Open last read page</button> + <button class="btn btn-primary" type="button" onclick="Reader.markAsRead()">Mark as read</button> </div> </div> @@ -183,7 +183,7 @@ </div> <div class="modal-footer"> - <button type="button" class="btn btn-primary" onclick="search()">Search</button> + <button type="button" class="btn btn-primary" onclick="Reader.search()">Search</button> <button type="button" class="btn btn-default" data-dismiss="modal">Close</button> </div> </div> @@ -192,14 +192,14 @@ <div class="container"> <div class="header"> - <a href="#" onclick="save_and_close()"> + <a href="#" onclick="Reader.close()"> <span class="glyphicon glyphicon-chevron-left" aria-hidden="true"></span> Back </a> <div class="title"> </div> <div class="toolbar"> <div class="prev_location_btn" - onclick="open_previous_location(this)" style="display : none"> + onclick="Reader.Page.openPrevious(this)" style="display : none"> <div class="glyphicon glyphicon-chevron-left" aria-hidden="true"></div> </div> @@ -207,7 +207,7 @@ <div class="glyphicon glyphicon-search" aria-hidden="true"></div> </div> - <div onclick="toggle_fullscreen()"> + <div onclick="Reader.toggleFullscreen()"> <div class="glyphicon glyphicon-fullscreen" aria-hidden="true"></div> </div> @@ -8,8 +8,8 @@ const CACHE_URLS = [ 'img/favicon.png', 'read.html', 'js/app.js', - 'js/read.js', 'js/reader.js', + 'js/reader_iframe.js', 'js/dict.js', 'css/read.css', 'css/reader.css', |