summaryrefslogtreecommitdiff
path: root/js/reader.js
diff options
context:
space:
mode:
Diffstat (limited to 'js/reader.js')
-rw-r--r--js/reader.js1045
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}
- &mdash; ${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("&nbsp;" + 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}
+ &mdash; ${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("&nbsp;" + 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: {