diff options
Diffstat (limited to 'js/reader.js')
-rw-r--r-- | js/reader.js | 1045 |
1 files changed, 471 insertions, 574 deletions
diff --git a/js/reader.js b/js/reader.js index 8d65d8e..0981876 100644 --- a/js/reader.js +++ b/js/reader.js @@ -88,7 +88,7 @@ const Reader = { }); } }, - initSecondStage: function() { + initSecondStage: async function() { if (typeof EpubeApp != "undefined") { EpubeApp.setPage("PAGE_READER"); @@ -104,697 +104,617 @@ const Reader = { Reader.applyTheme(); - // TODO: rework promise chain to async/await - return localforage.getItem(Reader.cacheId()).then(function(item) { - if (!item) { - console.log('requesting bookinfo...') - - return new Promise((resolve, reject) => { - const bookId = $.urlParam("b"); - - $.post("backend.php", {op: "getinfo", id: bookId }).success(function(data) { - if (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()); - } - }); - } - - return localforage.setItem(Reader.cacheId(), data).then(function() { - console.log('bookinfo saved'); - resolve(); - }) - } + const bookinfo = await localforage.getItem(Reader.cacheId()); - reject(new Error("unable to load book info: blank")); + if (!bookinfo) { + const bookId = $.urlParam("b"); - }).error(function(xhr) { - $(".loading-message").html("Unable to load book info.<br/><small>" + xhr.status + "</small>"); + console.log('requesting bookinfo for book', bookId) - reject(new Error("unable to load book info: " + xhr.status)); - }); + try { + const resp = await $.post("backend.php", {op: "getinfo", id: bookId }); - }); - } else { - console.log('bookinfo already stored'); - } - }).then(function() { + if (resp.has_cover) { + const cover = await fetch("backend.php?op=cover&id=" + bookId); - console.log('trying to load book...'); + if (cover.ok) + await localforage.setItem(Reader.cacheId('cover'), cover.blob()); + } - localforage.getItem(Reader.cacheId("book")).then(function(item) { + await localforage.setItem(Reader.cacheId(), resp); - if (item) { + console.log('bookinfo saved'); - console.log("loading from local storage"); + } catch (e) { + $(".loading-message").html(`Unable to load book info.<br/><small>${e.responseText}</small>`); + return; + } + } else { + console.log('bookinfo already stored'); + } - return new Promise(function (resolve, reject) { + console.log('trying to load book...'); - const fileReader = new FileReader(); + let book_data = await localforage.getItem(Reader.cacheId("book")); - fileReader.onload = function() { - try { - return book.open(this.result).then(function() { - resolve(); - }) - } catch (e) { - $(".loading-message").html("Unable to load book (local)."); - console.log(e); + if (!book_data) { + console.log("local data not found, loading from network..."); - reject(new Error("Unable to load book (local):" + e)); - } - }; + const book_resp = await fetch("backend.php?op=download&id=" + $.urlParam("id")); - fileReader.readAsArrayBuffer(item); - }); + if (book_resp.ok) { + book_data = await book_resp.blob(); + if (book_data) { + await localforage.setItem(Reader.cacheId('book'), book_data); + console.log('saved local data for book'); } else { + $(".loading-message").html("Unable to get book blob data."); + return; + } - console.log("loading from network"); - - if (App.isOnline()) { - const book_url = "backend.php?op=download&id=" + $.urlParam("id"); - - $(".loading-message").html("Downloading…"); + } else { + $(".loading-message").html(`Unable to download book: ${book_resp.status}`); + return; + } - return fetch(book_url, {credentials: 'same-origin'}).then(function(resp) { + } else { + console.log("local data already present for book"); + } - if (resp.status == 200) { - return resp.blob().then(function(blob) { + const fileReader = new FileReader(); - return new Promise(function(resolve, reject) { + fileReader.onload = function() { + try { + Reader.openBookData(this.result); + } catch (e) { + console.error(e); + $(".loading-message").html("Unable to load book blob: " + e); + } + }; - const fileReader = new FileReader(); + fileReader.onerror = (e) => { + console.log('filereader error', e); + $(".loading-message").html("Unable to open book.<br/><small>" + e + "</small>"); + }; - fileReader.onload = function() { - book.open(this.result).then(function() { + fileReader.readAsArrayBuffer(book_data); + }, + openBookData: async function(book_data) { + /* global ePub */ + const book = ePub(); + window.book = book; - // let's store this for later - localforage.setItem(Reader.cacheId('book'), blob).then(function() { - resolve(); - }) + try { + await book.open(book_data); + } catch (e) { + console.error(e); + $(".loading-message").html("Unable to open book.<br/><small>" + e.message + "</small>"); + return; + } - }).catch((e) => { - $(".loading-message").html("Unable to open book.<br/><small>" + e + "</small>"); + await book.ready; - reject(new Error("Unable to open book: " + e)); - }); - }; + console.log('book is ready'); - fileReader.onerror = function(e) { - console.log('filereader error', e); - $(".loading-message").html("Unable to open book.<br/><small>" + e + "</small>"); + await Reader.showBookInfo(book); + await Reader.loadLocations(book); - reject(new Error("Unable to open book: " + e)); - }; + const rendition = book.renderTo("reader", { + width: '100%', + height: '100%', + allowScriptedContent: true, + minSpreadWidth: 961 + }); - fileReader.readAsArrayBuffer(blob); - }); + $(".location").click(async function() { + const current = Math.floor(rendition.currentLocation().start.location / LOCATION_DIVISOR); + const total = Math.floor(book.locations.length() / LOCATION_DIVISOR); - }).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); + const page = prompt("Jump to location [1-" + total + "]", current); - if ($(".loading").is(":visible")) { - $(".loading-message").html("Unable to load book (remote).<br/><small>" + e + "</small>"); - } - }); + if (page) + await rendition.display(book.locations._locations[page * LOCATION_DIVISOR]); + }); - } else { - $(".loading-message").html("This book is not available offline."); - } - } - }); + const enable_hyphens = await localforage.getItem("epube.enable-hyphens"); - /* global ePub */ - const book = ePub(); - window.book = book; + if (enable_hyphens) { + /* global hyphenationPatternsEnUs, hyphenationPatternsRu, createHyphenator */ + Reader.hyphenate.en = createHyphenator(hyphenationPatternsEnUs, { html: true }); + Reader.hyphenate.ru = createHyphenator(hyphenationPatternsRu, { html: true }); + } - const rendition = book.renderTo("reader", { - width: '100%', - height: '100%', - allowScriptedContent: true, - minSpreadWidth: 961 - }); + Reader.applyStyles(true); - localforage.getItem("epube.enable-hyphens").then(function(enable_hyphens) { - if (enable_hyphens) { - /* global hyphenationPatternsEnUs, hyphenationPatternsRu, createHyphenator */ - Reader.hyphenate.en = createHyphenator(hyphenationPatternsEnUs, { html: true }); - Reader.hyphenate.ru = createHyphenator(hyphenationPatternsRu, { html: true }); - } + console.log('book displayed'); - Reader.applyStyles(true); + Reader.initRenditionHooks(book, rendition); + Reader.initModals(); + Reader.initToc(book); + Reader.initStyleHooks(book); + Reader.Page.openLastRead(rendition); + }, + showBookInfo: async function(book) { + const bookinfo = await localforage.getItem(Reader.cacheId()); - /* rendition.hooks.content.register(function() { - Reader.applyStyles(); - }); */ + let title; + let author; - rendition.display().then(function() { - console.log("book displayed"); - }); + if (bookinfo) { + title = bookinfo.title; + author = bookinfo.author_sort; + } else { + const metadata = book.package.metadata; - }); + title = metadata.title; + author = metadata.creator; + } - rendition.hooks.content.register(async function(contents) { + document.title = title + " – " + author + " – The Epube"; - contents.on("linkClicked", function(href) { - console.log('linkClicked', href); + $(".title") + .text(title) + .attr("title", title + " – " + author); - Reader.prevent_lastread_update = true; + if (typeof EpubeApp != "undefined") { + EpubeApp.setTitle(title); + EpubeApp.showActionBar(false); + } + }, + loadLocations: async function(book) { + console.log('init locations...'); - if (href.indexOf("://") == -1) { - $(".prev_location_btn") - .attr("data-location-cfi", book.rendition.currentLocation().start.cfi) - .show(); + let locations = await localforage.getItem(Reader.cacheId("locations")); - window.setTimeout(function() { - Reader.showUI(true); - }, 50); - } + // legacy format is array of objects {cfi: ..., page: ...} + if (locations && typeof locations[0] == "string") { + console.log('loading local locations...'); - }); + return book.locations.load(locations); + } else { + console.log("requesting locations..."); - const base_url = window.location.href.match(/^.*\//)[0]; + const resp = await fetch("backend.php?op=getpagination&id=" + $.urlParam("id")); - if (typeof EpubeApp != "undefined") { - $(contents.document.head).append($(`<link type="text/css" rel="stylesheet" media="screen" href="/assets/android.css" />`)); - } else { - $(contents.document.head) - .append($("<style type='text/css'>") - .text(Reader.generateFontsCss(base_url))); - } + if (resp.ok) { + console.log("got locations from server"); - const script_names = [ - "dist/app-libs.min.js", - "dist/reader_iframe.min.js" - ]; + locations = await resp.json(); - for (let i = 0; i < script_names.length; i++) { + if (locations && typeof locations[0] == "string") { + console.log('saving locations locally...'); - // we need to create script element with proper context, that is inside the iframe - const elem = contents.document.createElement("script"); - elem.type = 'text/javascript'; - elem.text = Reader.Loader._res_data[base_url + script_names[i]]; + await localforage.setItem(Reader.cacheId("locations"), locations); - contents.document.head.appendChild(elem); + return book.locations.load(locations); } + } - $(contents.document.head) - .append($("<style type='text/css'>") - .text(Reader.Loader._res_data[base_url + 'dist/reader_iframe.min.css'])); + $(".loading-message").html("Preparing locations…"); - const theme = await localforage.getItem("epube.theme") || 'default'; + locations = await book.locations.generate(100); - $(contents.document).find("body") - .attr("class", typeof EpubeApp != "undefined" ? "is-epube-app" : "") - .addClass("theme-" + theme); + $.post("backend.php", { op: "storepagination", id: $.urlParam("id"), + payload: JSON.stringify(locations), total: 100}); - }); + await localforage.setItem(Reader.cacheId("locations"), locations); + } + }, + initRenditionHooks: function(book, rendition) { + rendition.hooks.content.register(async (contents) => { + contents.on("linkClicked", (href) => { + console.log('linkClicked', href); - $('#settings-modal').on('shown.bs.modal', async function() { + Reader.prevent_lastread_update = true; - const last_read = await localforage.getItem(Reader.cacheId("lastread")); + if (href.indexOf("://") == -1) { + $(".prev_location_btn") + .attr("data-location-cfi", rendition.currentLocation().start.cfi) + .show(); - if (last_read && last_read.cfi) { - $(".lastread_input") - .val(last_read.page + '%') - .attr('title', `[local] ${last_read.cfi}`); + window.setTimeout(function() { + Reader.showUI(true); + }, 50); } + }); - if (navigator.onLine) - $.post("backend.php", { op: "getlastread", id: $.urlParam("id") }, (data) => { - $(".lastread_input") - .val(data.page + '%') - .attr('title', `[remote] ${data.cfi}`); - }); + const base_url = window.location.href.match(/^.*\//)[0]; - $(".enable_hyphens_checkbox") - .attr("checked", await localforage.getItem("epube.enable-hyphens")) - .off("click") - .on("click", function(evt) { - localforage.setItem("epube.enable-hyphens", evt.target.checked); + if (typeof EpubeApp != "undefined") { + $(contents.document.head).append($(`<link type="text/css" rel="stylesheet" media="screen" href="/assets/android.css" />`)); + } else { + $(contents.document.head) + .append($("<style type='text/css'>") + .text(Reader.generateFontsCss(base_url))); + } - if (confirm("Toggling hyphens requires page reload. Reload now?")) { - window.location.reload(); - } - }); + const script_names = [ + "dist/app-libs.min.js", + "dist/reader_iframe.min.js" + ]; - if (typeof EpubeApp != "undefined") { - $(".keep_ui_checkbox").parent().parent().hide(); - } else { - $(".keep_ui_checkbox") - .attr("checked", await localforage.getItem("epube.keep-ui-visible")) - .off("click") - .on("click", function(evt) { - localforage.setItem("epube.keep-ui-visible", evt.target.checked); - }); - } + for (let i = 0; i < script_names.length; i++) { - const enable_hacks = await localforage.getItem("epube.enable-column-hacks"); + // we need to create script element with proper context, that is inside the iframe + const elem = contents.document.createElement("script"); + elem.type = 'text/javascript'; + elem.text = Reader.Loader._res_data[base_url + script_names[i]]; - $(".enable_column_hacks_checkbox") - .attr("checked", enable_hacks) - .off("click") - .on("click", function(evt) { - localforage.setItem("epube.enable-column-hacks", evt.target.checked); - }); + contents.document.head.appendChild(elem); + } - const stamp = await localforage.getItem("epube.cache-timestamp"); - const version = await localforage.getItem("epube.cache-version"); + $(contents.document.head) + .append($("<style type='text/css'>") + .text(Reader.Loader._res_data[base_url + 'dist/reader_iframe.min.css'])); - $(".last-mod-timestamp").html(`${version} - — ${parseInt(stamp) ? new Date(stamp*1000).toLocaleString("en-GB") : "Unknown"} - (${App.isOnline() ? `<span class='text-success'>Online</span>` : `<span class='text-danger'>Offline</span>`}) - `); + const theme = await localforage.getItem("epube.theme") || 'default'; - $(".font_family").val(await localforage.getItem("epube.fontFamily") || DEFAULT_FONT_FAMILY); - $(".theme_name").val(await localforage.getItem("epube.theme")); + $(contents.document).find("body") + .attr("class", typeof EpubeApp != "undefined" ? "is-epube-app" : "") + .addClass("theme-" + theme); - const zoom = $(".font_size").html(""); + }); - for (let i = 10; i <= 32; i++) { - const opt = $("<option>").val(i).html(i + " px"); - zoom.append(opt); - } + rendition.on("keyup", (e) => { + Reader.hotkeyHandler(e); + }); - zoom.val(await localforage.getItem("epube.fontSize") || DEFAULT_FONT_SIZE); + rendition.on('rendered', function(/*chapter*/) { + $(".chapter").html($("<span>").addClass("glyphicon glyphicon-th-list")); - const line_height = $(".line_height").html(""); + // TODO: this needs a proper implementation instead of a timeout hack + setTimeout(() => { + Reader.Page._moved_next = 0; + }, 150); - for (let i = 100; i <= 220; i += 10) { - const opt = $("<option>").val(i).html(i + "%"); - line_height.append(opt); - } + Reader.applyTheme(); - line_height.val(await localforage.getItem("epube.lineHeight") || DEFAULT_LINE_HEIGHT); + Reader.resizeSideColumns(); - }); + try { + const location = rendition.currentLocation(); - $('#dict-modal').on('shown.bs.modal', function() { - $(".dict_result").scrollTop(0); - }); + if (location.start) { + const cur_href = book.canonical(location.start.href); + let toc_entry = false; - // TODO: make configurable - $(".dict_search_btn").on("click", function() { - $("#dict-modal").modal('hide'); - window.open("https://duckduckgo.com/?q=" + $(".dict_query").val()); - }); + $.each(Reader.flattenToc(book), function(i, r) { - $(".wiki_search_btn").on("click", function() { - $(".dict_result").html("Loading, please wait..."); + if (book.spine.get(r.href).canonical == cur_href) { + toc_entry = r; + return; + } + }); - $.post("backend.php", {op: "wikisearch", query: $(".dict_query").val()}) - .then((resp) => { - try { - console.log('wikisearch resp', resp); + if (toc_entry && toc_entry.label) + $(".chapter").append(" " + toc_entry.label.trim() + " | "); - let tmp = ""; + Reader.generateTocBar(book, Reader.flattenToc(book)); + } - $.each(resp.query.pages, (i,p) => { - tmp += p.extract; - }); + } catch (e) { + console.warn(e); + } + }); - $(".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."); - }) - }); + rendition.on('relocated', async (location) => { - function toc_loc_msg(href) { - try { - const cfiBase = book.spine.get(href).cfiBase; + // locations not generated yet + if (book.locations.length() == 0) + return; - const loc = book.locations._locations.find(function(k) { - return k.indexOf(cfiBase) != -1 - }); + localforage.getItem("epube.enable-column-hacks").then((enable) => { + if (enable && Reader.Page._moved_next >= 20) { + console.log('forcing re-render because of column hacks'); - return parseInt(window.book.locations.locationFromCfi(loc) / LOCATION_DIVISOR); + rendition.onResized($("#reader").width()); - } catch (e) { - console.warn(e); + Reader.Page._moved_next = 0; } + }); - return ""; - } + const currentCfi = location.start.cfi; + const currentPct = parseInt(book.locations.percentageFromCfi(currentCfi) * 100); - function process_toc_sublist(row, list, nest) { + $("#cur_page").text(Math.floor(location.start.location / LOCATION_DIVISOR)); + $("#total_pages").text(Math.floor(book.locations.length() / LOCATION_DIVISOR)); + $("#page_pct").text(parseInt(book.locations.percentageFromCfi(currentCfi)*100) + '%'); - if (nest == 3) return false; + Reader.updateTocBarPosition(book, location); - if (row.subitems) { + const displayed = location.start.displayed; - const sublist = $("<ul class='toc_sublist list-unstyled'>"); + if (displayed) { + $("#chapter_cur_page").text(displayed.page); + $("#chapter_total_pages").text(displayed.total); - $.each(row.subitems, function(i, row) { + if (displayed.total > 0) + $("#chapter_pct").text(parseInt(displayed.page / displayed.total * 100) + '%') + } - 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')); - }); + if (Reader.last_stored_cfi != currentCfi && !Reader.prevent_lastread_update) { + const lastread_timestamp = new Date().getTime(); - sublist.append($("<li>").append(a)); + console.log("storing lastread", currentPct, currentCfi, lastread_timestamp); - process_toc_sublist(row, sublist, nest + 1); + await localforage.setItem(Reader.cacheId("lastread"), + {cfi: currentCfi, page: currentPct, total: 100, timestamp: lastread_timestamp}); - }); + if (App.isOnline()) { + console.log("updating remote lastread", currentCfi); + Reader.last_stored_cfi = currentCfi; - list.append(sublist); + $.post("backend.php", { op: "storelastread", id: $.urlParam("id"), page: currentPct, + cfi: currentCfi, timestamp: lastread_timestamp }); } } - $('#toc-modal').on('shown.bs.modal', function() { + Reader.prevent_lastread_update = false; + }); + }, + initModals: function() { + console.log('initializing modals...'); - const toc = book.navigation.toc; + $('#settings-modal').on('shown.bs.modal', async function() { - const list = $(".toc_list"); - list.html(""); + const last_read = await localforage.getItem(Reader.cacheId("lastread")); - $.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')); - }); + if (last_read && last_read.cfi) { + $(".lastread_input") + .val(last_read.page + '%') + .attr('title', `[local] ${last_read.cfi}`); + } - list.append($("<li>").append(a)); + if (navigator.onLine) + $.post("backend.php", { op: "getlastread", id: $.urlParam("id") }, (data) => { + $(".lastread_input") + .val(data.page + '%') + .attr('title', `[remote] ${data.cfi}`); + }); - process_toc_sublist(row, list, 0); + $(".enable_hyphens_checkbox") + .attr("checked", await localforage.getItem("epube.enable-hyphens")) + .off("click") + .on("click", function(evt) { + localforage.setItem("epube.enable-hyphens", evt.target.checked); - } catch (e) { - console.warn(e); + if (confirm("Toggling hyphens requires page reload. Reload now?")) { + window.location.reload(); } }); - // 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')); - }); + if (typeof EpubeApp != "undefined") { + $(".keep_ui_checkbox").parent().parent().hide(); + } else { + $(".keep_ui_checkbox") + .attr("checked", await localforage.getItem("epube.keep-ui-visible")) + .off("click") + .on("click", function(evt) { + localforage.setItem("epube.keep-ui-visible", evt.target.checked); + }); + } - list.append($("<li>").append(a)); + const enable_hacks = await localforage.getItem("epube.enable-column-hacks"); - }); - } + $(".enable_column_hacks_checkbox") + .attr("checked", enable_hacks) + .off("click") + .on("click", function(evt) { + localforage.setItem("epube.enable-column-hacks", evt.target.checked); + }); - }); + const stamp = await localforage.getItem("epube.cache-timestamp"); + const version = await localforage.getItem("epube.cache-version"); - /* embedded styles may conflict with our font sizes, etc */ - book.spine.hooks.content.register(function(doc/*, section */) { - - $(doc).find("p") - .filter((i, e) => (($(e).text().length >= MIN_LENGTH_TO_JUSTIFY) ? e : null)) - .css("text-align", "justify"); - - $(doc).find("html") - .attr("class", ""); - - $(doc).find("pre") - .css("white-space", "pre-wrap"); - - $(doc).find("a, p, span, em, i, strong, b, body, header, section, div, big, small, table, tr, td, ul, ol, li") - .attr("class", "") - .css("font-family", "inherit") - .css("font-size", "inherit") - .css("line-height", "inherit") - .css("color", "") - .css("border", "none ! important") - .css("background", "") - .css("background-color", "") - .attr("width", "") - .attr("height", ""); - - // same as above except for allowed font-size - $(doc).find("h1, h2, h3, h4, h5") - .attr("class", "") - .css("font-family", "inherit") - .css("color", "") - .css("border", "none ! important") - .css("background", "") - .css("background-color", ""); + $(".last-mod-timestamp").html(`${version} + — ${parseInt(stamp) ? new Date(stamp*1000).toLocaleString("en-GB") : "Unknown"} + (${App.isOnline() ? `<span class='text-success'>Online</span>` : `<span class='text-danger'>Offline</span>`}) + `); - if (typeof Reader.hyphenate.en != "undefined") { - $(doc).find('p, div').each((i,p) => { - p = $(p); + $(".font_family").val(await localforage.getItem("epube.fontFamily") || DEFAULT_FONT_FAMILY); + $(".theme_name").val(await localforage.getItem("epube.theme")); - p.html(Reader.hyphenate.en(p.html())); - p.html(Reader.hyphenate.ru(p.html())); - }); - } - }); + const zoom = $(".font_size").html(""); - book.ready.then(function() { - return localforage.getItem(Reader.cacheId()).then((bookinfo) => { + for (let i = 10; i <= 32; i++) { + const opt = $("<option>").val(i).html(i + " px"); + zoom.append(opt); + } - let title; - let author; + zoom.val(await localforage.getItem("epube.fontSize") || DEFAULT_FONT_SIZE); - if (bookinfo) { - title = bookinfo.title; - author = bookinfo.author_sort; - } else { - const metadata = book.package.metadata; + const line_height = $(".line_height").html(""); - title = metadata.title; - author = metadata.creator; - } + for (let i = 100; i <= 220; i += 10) { + const opt = $("<option>").val(i).html(i + "%"); + line_height.append(opt); + } - document.title = title + " – " + author + " – The Epube"; - $(".title") - .text(title) - .attr("title", title + " – " + author); + line_height.val(await localforage.getItem("epube.lineHeight") || DEFAULT_LINE_HEIGHT); - if (typeof EpubeApp != "undefined") { - EpubeApp.setTitle(title); - EpubeApp.showActionBar(false); - } + }); - return localforage.getItem(Reader.cacheId("locations")).then(function(locations) { + $('#dict-modal').on('shown.bs.modal', function() { + $(".dict_result").scrollTop(0); + }); - console.log('stored pagination', locations != null); + // TODO: make configurable + $(".dict_search_btn").on("click", function() { + $("#dict-modal").modal('hide'); + window.open("https://duckduckgo.com/?q=" + $(".dict_query").val()); + }); - // 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("Preparing locations…"); - return book.locations.generate(100); - } - }); - } else { - $(".loading-message").html("Preparing locations…"); - return book.locations.generate(100); - } - }).catch(function() { - $(".loading-message").html("Preparing locations…"); - return book.locations.generate(100); - }); - } - }); + $(".wiki_search_btn").on("click", function() { + $(".dict_result").html("Loading, please wait..."); - }); + $.post("backend.php", {op: "wikisearch", query: $(".dict_query").val()}) + .then((resp) => { + try { + console.log('wikisearch resp', resp); - }).then(function(locations) { + let tmp = ""; - console.log("locations ready, stored=", Reader.Page._pagination_stored); + $.each(resp.query.pages, (i,p) => { + tmp += p.extract; + }); - if (locations) { - if (App.isOnline() && !Reader.Page._pagination_stored) { - $.post("backend.php", { op: "storepagination", id: $.urlParam("id"), - payload: JSON.stringify(locations), total: 100}); + $(".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."); + }) + }); + }, + initToc: function(book) { + console.log('initializing toc modal...'); - // store if needed - localforage.getItem(Reader.cacheId("locations")).then(function(item) { - if (!item) localforage.setItem(Reader.cacheId("locations"), locations); - }); + function toc_loc_msg(href) { + try { + const cfiBase = book.spine.get(href).cfiBase; - } else { - $(".loading-message").html("Could not generate locations."); - return; - } + const loc = book.locations._locations.find(function(k) { + return k.indexOf(cfiBase) != -1 + }); - $(".location").click(function() { - const current = Math.floor(book.rendition.currentLocation().start.location / LOCATION_DIVISOR); - const total = Math.floor(book.locations.length() / LOCATION_DIVISOR); + return parseInt(window.book.locations.locationFromCfi(loc) / LOCATION_DIVISOR); - const page = prompt("Jump to location [1-" + total + "]", current); + } catch (e) { + console.warn(e); + } - if (page) { - book.rendition.display(book.locations._locations[page * LOCATION_DIVISOR]); - } - }); - Reader.Page.openLastRead(); + return ""; + } - window.setTimeout(function() { - Reader.Page.openLastRead(); + function process_toc_sublist(row, list, nest) { - $(".loading").hide(); - }, 250); - }); + if (nest == 3) return false; - rendition.on("keyup", (e) => { - Reader.hotkeyHandler(e); - }); + if (row.subitems) { - /*rendition.on('resized', function() { - console.log('resized'); + const sublist = $("<ul class='toc_sublist list-unstyled'>"); - $(".loading").show(); - $(".loading-message").html("Opening chapter…"); + $.each(row.subitems, function(i, row) { - window.setTimeout(function() { - Reader.resizeSideColumns(); - Reader.Page.openLastRead(); + 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')); + }); - $(".loading").hide(); - }, 250); - });*/ + sublist.append($("<li>").append(a)); - rendition.on('rendered', function(/*chapter*/) { - $(".chapter").html($("<span>").addClass("glyphicon glyphicon-th-list")); + process_toc_sublist(row, sublist, nest + 1); - // TODO: this needs a proper implementation instead of a timeout hack - setTimeout(() => { - Reader.Page._moved_next = 0; - }, 150); + }); - Reader.applyTheme(); + list.append(sublist); + } + } - Reader.resizeSideColumns(); + $('#toc-modal').on('shown.bs.modal', function() { - try { - const location = book.rendition.currentLocation(); + const toc = book.navigation.toc; - if (location.start) { - const cur_href = book.canonical(location.start.href); - let toc_entry = false; + const list = $(".toc_list"); + list.html(""); - $.each(Reader.flattenToc(book), function(i, r) { + $.each(toc, function(i, row) { - if (book.spine.get(r.href).canonical == cur_href) { - toc_entry = r; - return; - } + // 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')); }); - if (toc_entry && toc_entry.label) - $(".chapter").append(" " + toc_entry.label.trim() + " | "); + list.append($("<li>").append(a)); - Reader.generateTocBar(book, Reader.flattenToc(book)); - } + process_toc_sublist(row, list, 0); } catch (e) { console.warn(e); } }); - rendition.on('relocated', function(location) { - - // locations not generated yet - if (book.locations.length() == 0) - return; - - localforage.getItem("epube.enable-column-hacks").then((enable) => { - if (enable && Reader.Page._moved_next >= 20) { - console.log('forcing re-render because of column hacks'); - - window.book.rendition.onResized($("#reader").width()); - - Reader.Page._moved_next = 0; - } - }); + // well the toc didn't work out, might as well generate one + if (list.children().length <= 1) { - const currentCfi = location.start.cfi; - const currentPct = parseInt(book.locations.percentageFromCfi(currentCfi) * 100); + list.html(""); - $("#cur_page").text(Math.floor(location.start.location / LOCATION_DIVISOR)); - $("#total_pages").text(Math.floor(book.locations.length() / LOCATION_DIVISOR)); - $("#page_pct").text(parseInt(book.locations.percentageFromCfi(currentCfi)*100) + '%'); + $.each(book.spine.items, function (i, row) { - Reader.updateTocBarPosition(book, location); + 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')); + }); - const displayed = location.start.displayed; + list.append($("<li>").append(a)); - if (displayed) { - $("#chapter_cur_page").text(displayed.page); - $("#chapter_total_pages").text(displayed.total); + }); + } - if (displayed.total > 0) - $("#chapter_pct").text(parseInt(displayed.page / displayed.total * 100) + '%') - } + }); + }, + initStyleHooks: function(book) { + console.log('registering style hooks...'); - if (Reader.last_stored_cfi != currentCfi && !Reader.prevent_lastread_update) { - const lastread_timestamp = new Date().getTime(); + /* embedded styles may conflict with our font sizes, etc */ + book.spine.hooks.content.register(function(doc/*, section */) { - console.log("storing lastread", currentPct, currentCfi, lastread_timestamp); + $(doc).find("p") + .filter((i, e) => (($(e).text().length >= MIN_LENGTH_TO_JUSTIFY) ? e : null)) + .css("text-align", "justify"); - localforage.setItem(Reader.cacheId("lastread"), - {cfi: currentCfi, page: currentPct, total: 100, timestamp: lastread_timestamp}); + $(doc).find("html") + .attr("class", ""); - if (App.isOnline()) { - console.log("updating remote lastread", currentCfi); - Reader.last_stored_cfi = currentCfi; + $(doc).find("pre") + .css("white-space", "pre-wrap"); - $.post("backend.php", { op: "storelastread", id: $.urlParam("id"), page: currentPct, - cfi: currentCfi, timestamp: lastread_timestamp }).then(() => { - // - }) - .fail(function(e) { - if (e && e.status == 401) { - window.location = "index.php"; - } - }); - } - } - - Reader.prevent_lastread_update = false; - }); + $(doc).find("a, p, span, em, i, strong, b, body, header, section, div, big, small, table, tr, td, ul, ol, li") + .attr("class", "") + .css("font-family", "inherit") + .css("font-size", "inherit") + .css("line-height", "inherit") + .css("color", "") + .css("border", "none ! important") + .css("background", "") + .css("background-color", "") + .attr("width", "") + .attr("height", ""); + + // same as above except for allowed font-size + $(doc).find("h1, h2, h3, h4, h5") + .attr("class", "") + .css("font-family", "inherit") + .css("color", "") + .css("border", "none ! important") + .css("background", "") + .css("background-color", ""); + + if (typeof Reader.hyphenate.en != "undefined") { + $(doc).find('p, div').each((i,p) => { + p = $(p); + + p.html(Reader.hyphenate.en(p.html())); + p.html(Reader.hyphenate.ru(p.html())); + }); + } }); }, generateFontsCss: function(base_url) { @@ -1348,7 +1268,6 @@ const Reader = { }, }, Page: { - _pagination_stored: 0, _moved_next: 0, next: function() { window.book.rendition.next(); @@ -1372,83 +1291,61 @@ const Reader = { if (!keep) Reader.showUI(false); }); }, - openPrevious: function(elem) { + openPrevious: async function(elem) { const cfi = $(elem).attr("data-location-cfi"); - if (cfi) { - window.book.rendition.display(cfi); - } + if (cfi) + await window.book.rendition.display(cfi); $(elem).fadeOut(); }, - clearLastRead: function() { + clearLastRead: async function() { if (confirm("Clear stored last read location?")) { const total = window.book.locations.length(); const lastread_timestamp = new Date().getTime(); if (App.isOnline()) { - $.post("backend.php", { op: "storelastread", page: PAGE_RESET_PROGRESS, cfi: "", id: $.urlParam("id"), timestamp: lastread_timestamp }, function(data) { - $(".lastread_input").val(data.page + '%'); - }); + const data = await $.post("backend.php", { op: "storelastread", page: PAGE_RESET_PROGRESS, cfi: "", id: $.urlParam("id"), timestamp: lastread_timestamp }); + + $(".lastread_input").val(data.page + '%'); } - localforage.setItem(Reader.cacheId("lastread"), + await localforage.setItem(Reader.cacheId("lastread"), {cfi: "", page: 0, total: total, timestamp: lastread_timestamp}); - window.setTimeout(function() { - window.book.rendition.display(window.book.locations.cfiFromPercentage(0)); - }, 250); + await window.book.rendition.display(window.book.locations.cfiFromPercentage(0)); } }, - openLastRead: function(local_only) { - localforage.getItem(Reader.cacheId("lastread")).then(function(lr_local) { - console.log('lr local', lr_local); - - lr_local = lr_local || {}; + openLastRead: async function(rendition) { + const lr_local = await localforage.getItem(Reader.cacheId("lastread")) || {}; - // CFI missing or w/e + if (lr_local && lr_local.cfi) { + console.log('using local lastread cfi', lr_local.cfi); try { - - // this is ridiculous tbh - if (lr_local.cfi) window.book.rendition.display(lr_local.cfi).then(() => { - $(".loading").hide(); - - if (lr_local.cfi) - window.book.rendition.display(lr_local.cfi); - }); - + await rendition.display(lr_local.cfi); } catch (e) { console.warn(e); } + } - if (App.isOnline() && !local_only) { - $.post("backend.php", { op: "getlastread", id: $.urlParam("id") }, function(lr_remote) { - console.log('lr remote', lr_remote); - - if (App.isOnline() && lr_remote) { - try { - if (lr_remote.cfi && lr_local.cfi != lr_remote.cfi && lr_remote.timestamp > lr_local.timestamp) - console.log('using remote lastread (timestamp is newer)'); + if (App.isOnline()) { + const lr_remote = await $.post("backend.php", { op: "getlastread", id: $.urlParam("id") }); - localforage.setItem(Reader.cacheId("lastread"), - {cfi: lr_remote.cfi, page: lr_remote.page, total: lr_remote.total, timestamp: lr_remote.timestamp}); + console.log('got remote lastread', lr_remote, lr_local); - window.book.rendition.display(lr_remote.cfi).then(() => { - window.book.rendition.display(lr_remote.cfi); - }); + if (lr_remote) { + if (lr_remote.cfi && lr_local.cfi != lr_remote.cfi && (!lr_local.timestamp || lr_remote.timestamp <= lr_local.timestamp)) { + console.log('using remote lastread (timestamp is newer or local timestamp is missing)'); - } catch (e) { - console.warn(e); - } + await localforage.setItem(Reader.cacheId("lastread"), + {cfi: lr_remote.cfi, page: lr_remote.page, total: lr_remote.total, timestamp: lr_remote.timestamp}); - } - }).fail(function(e) { - if (e && e.status == 401) { - window.location = "index.php"; - } - }); + await rendition.display(lr_remote.cfi); + } } - }); + } + + $(".loading").hide(); }, }, Settings: { |