summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAndrew Dolgov <[email protected]>2021-03-14 14:17:18 +0300
committerAndrew Dolgov <[email protected]>2021-03-14 14:17:18 +0300
commit1c9afba5f058adace5e005b51fc533882f9af0fa (patch)
tree99f4a228acf9f969d5411048c210338ba0fd126a
parent2b8b845abe7c13ecbb266613910484310cffe8e1 (diff)
* add CSRF protection to xhr requests
* force ORM to use SQLITE WAL * add .editorconfig * cleanup a few things
-rw-r--r--.editorconfig9
-rw-r--r--backend.php21
-rw-r--r--classes/db.php15
-rw-r--r--dist/app.min.js2
-rw-r--r--include/common.php4
-rw-r--r--include/sessions.php4
-rw-r--r--index.php13
-rw-r--r--js/app.js1039
-rw-r--r--js/reader.js25
-rw-r--r--login.php3
-rw-r--r--logout.php12
11 files changed, 612 insertions, 535 deletions
diff --git a/.editorconfig b/.editorconfig
new file mode 100644
index 0000000..2b1a9fb
--- /dev/null
+++ b/.editorconfig
@@ -0,0 +1,9 @@
+[*]
+end_of_line = lf
+insert_final_newline = true
+
+[*.php]
+indent_style = tab
+
+[*.js]
+indent_style = tab
diff --git a/backend.php b/backend.php
index 42fc5e9..0cbd6f4 100644
--- a/backend.php
+++ b/backend.php
@@ -27,6 +27,18 @@
$owner = $_SESSION["owner"] ?? "";
$op = $_REQUEST["op"] ?? "";
+ if (!empty($_SESSION['owner'])) {
+ $csrf_ignore = [ "cover", "download", "getpagination" ];
+
+ $csrf_token = $_POST['csrf_token'] ?? "";
+
+ if (!in_array($op, $csrf_ignore) && !validate_csrf($csrf_token)) {
+ header($_SERVER["SERVER_PROTOCOL"]." 401 Unauthorized");
+ echo "Unauthorized (CSRF)";
+ exit;
+ }
+ }
+
switch ($op) {
case "cover":
$id = (int) $_REQUEST["id"];
@@ -55,10 +67,6 @@
}
break;
- case "getowner":
- print json_encode(["owner" => $owner]);
- break;
-
case "getinfo":
$id = (int) $_REQUEST["id"];
@@ -300,6 +308,11 @@
}
break;
+ case "logout":
+ logout_user();
+ print json_encode(["result" => "OK"]);
+ break;
+
default:
header($_SERVER["SERVER_PROTOCOL"]." 404 Not Found");
echo "Method not found.";
diff --git a/classes/db.php b/classes/db.php
index cc835b7..61467eb 100644
--- a/classes/db.php
+++ b/classes/db.php
@@ -7,14 +7,21 @@ class Db {
try {
$this->pdo = new PDO(self::get_dsn());
} catch (Exception $e) {
- die("Unable to initialize database driver (SQLite): $e");
+ user_error($e, E_USER_WARNING);
+ die("Unable to initialize database driver (SQLite).");
}
//$this->dbh->busyTimeout(30*1000);
$this->pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
- $this->pdo->query('PRAGMA journal_mode = wal;');
+ $this->pdo->query('PRAGMA journal_mode = wal');
- ORM::configure(self::get_dsn());
- ORM::configure('return_result_sets', true);
+ try {
+ ORM::configure(self::get_dsn());
+ ORM::configure('return_result_sets', true);
+ ORM::raw_execute('PRAGMA journal_mode = wal');
+ } catch (Exception $e) {
+ user_error($e, E_USER_WARNING);
+ die("Unable to initialize ORM layer.");
+ }
}
public static function get_dsn() {
diff --git a/dist/app.min.js b/dist/app.min.js
index eb50a31..d454dfd 100644
--- a/dist/app.min.js
+++ b/dist/app.min.js
@@ -1 +1 @@
-"use strict";$.urlParam=function(e){try{const t=new RegExp("[?&]"+e+"=([^&#]*)").exec(window.location.href);return decodeURIComponent(t[1].replace(/\+/g," "))||0}catch(e){return 0}};const Cookie={set:function(e,t,o){const n=new Date;n.setTime(n.getTime()+1e3*o);const a="expires="+n.toUTCString();document.cookie=e+"="+encodeURIComponent(t)+"; "+a},get:function(e){e+="=";const t=document.cookie.split(";");for(let o=0;o<t.length;o++){let n=t[o];for(;" "==n.charAt(0);)n=n.substring(1);if(0==n.indexOf(e))return decodeURIComponent(n.substring(e.length,n.length))}return""},delete:function(e){document.cookie=e+"=; expires=Thu, 01-Jan-1970 00:00:01 GMT"}},App={_dl_progress_timeout:!1,index_mode:"",last_mtime:-1,init:function(){let e=0;"undefined"!=typeof EpubeApp&&($(".navbar").hide(),$(".epube-app-filler").show(),$(".separate-search").show(),"favorites"==$.urlParam("mode")?EpubeApp.setPage("PAGE_FAVORITES"):EpubeApp.setPage("PAGE_LIBRARY")),App.initNightMode(),"serviceWorker"in navigator?navigator.serviceWorker.addEventListener("message",(function(t){"refresh-started"==t.data&&(console.log("cache refresh started"),e=0,$(".dl-progress").fadeIn().text("Loading, please wait...")),t.data&&0==t.data.indexOf("refreshed:")&&(++e,$(".dl-progress").fadeIn().text("Updated "+e+" files...")),"client-reload"==t.data&&(localforage.setItem("epube.cache-timestamp",App.last_mtime),window.location.reload())})):$(".container-main").addClass("alert alert-danger").html("Service worker support missing in browser (are you using plain HTTP?)."),App.showCovers(),App.Offline.markBooks(),App.refreshCache()},showSummary:function(e){const t=e.getAttribute("data-book-id");return $.post("backend.php",{op:"getinfo",id:t},(function(e){const t=e.comment?e.comment:"No description available";$("#summary-modal .modal-title").html(e.title),$("#summary-modal .book-summary").html(t),$("#summary-modal").modal()})),!1},showCovers:function(){$("img[data-book-id]").each((e,t)=>{if((t=$(t)).attr("data-cover-link")){const e=$("<img>").on("load",(function(){t.css("background-image","url("+t.attr("data-cover-link")+")").fadeIn(),e.attr("src",null)})).attr("src",t.attr("data-cover-link"))}else t.attr("src","holder.js/130x190?auto=yes").fadeIn()}),Holder.run()},toggleFavorite:function(e){const t=e.getAttribute("data-book-id");return("0"==e.getAttribute("data-is-fav")||confirm("Remove favorite?"))&&$.post("backend.php",{op:"togglefav",id:t},(function(o){if(o){let n="[Error]";0==o.status?n="Add to favorites":1==o.status&&(n="Remove from favorites"),$(e).html(n).attr("data-is-fav",o.status),"favorites"==App.index_mode&&0==o.status&&$("#cell-"+t).remove()}})),!1},refreshCache:function(e){"serviceWorker"in navigator?localforage.getItem("epube.cache-timestamp").then((function(t){console.log("stamp",t,"last mtime",App.last_mtime),(e||t!=App.last_mtime)&&(console.log("asking worker to refresh cache"),navigator.serviceWorker.controller?navigator.serviceWorker.controller.postMessage("refresh-cache"):localforage.getItem("epube.initial-load-done").then((function(e){console.log("initial load done",e),e?$(".dl-progress").show().addClass("alert-danger").html("Could not communicate with service worker. Try reloading the page."):localforage.setItem("epube.initial-load-done",!0).then((function(){$(".dl-progress").show().addClass("alert-info").html("Page will reload to activate service worker..."),window.setTimeout((function(){window.location.reload()}),3e3)}))})))})):$(".dl-progress").show().addClass("alert-danger").html("Could not communicate with service worker. Try reloading the page.")},isOnline:function(){return"undefined"!=typeof EpubeApp&&void 0!==EpubeApp.isOnline?EpubeApp.isOnline():navigator.onLine},appCheckOffline:function(){EpubeApp.setOffline(!App.isOnline)},initNightMode:function(){if("undefined"==typeof EpubeApp){if(window.matchMedia){const e=window.matchMedia("(prefers-color-scheme: dark)");e.addEventListener("change",()=>{App.applyNightMode(e.matches)}),App.applyNightMode(e.matches)}}else App.applyNightMode(EpubeApp.isNightMode())},applyNightMode:function(e){console.log("night mode changed to",e),$("#theme_css").attr("href","lib/bootstrap/v3/css/"+(e?"theme-dark.min.css":"bootstrap-theme.min.css"))},Offline:{init:function(){"undefined"!=typeof EpubeApp&&($(".navbar").hide(),$(".epube-app-filler").show(),EpubeApp.setPage("PAGE_OFFLINE")),App.initNightMode();const e=$.urlParam("query");e&&$(".search_query").val(e),App.Offline.populateList()},get:function(e,t){console.log("offline cache: "+e),$.post("backend.php",{op:"getinfo",id:e},(function(o){if(o){const n="epube-book."+e;localforage.setItem(n,o).then((function(o){console.log(n+" got data");const a=[];a.push(fetch("backend.php?op=download&id="+o.epub_id,{credentials:"same-origin"}).then((function(e){200==e.status&&(console.log(n+" got book"),t(),localforage.setItem(n+".book",e.blob()))}))),a.push(fetch("backend.php?op=getpagination&id="+o.epub_id,{credentials:"same-origin"}).then((function(e){200==e.status&&(console.log(n+" got pagination"),e.text().then((function(e){localforage.setItem(n+".locations",JSON.parse(e))})))}))),a.push(fetch("backend.php?op=getlastread&id="+o.epub_id,{credentials:"same-origin"}).then((function(e){200==e.status&&(console.log(n+" got lastread"),e.text().then((function(e){localforage.setItem(n+".lastread",JSON.parse(e))})))}))),o.has_cover&&a.push(fetch("backend.php?op=cover&id="+e,{credentials:"same-origin"}).then((function(e){200==e.status&&(console.log(n+" got cover"),localforage.setItem(n+".cover",e.blob()))}))),Promise.all(a).then((function(){$(".dl-progress").show().html("Finished downloading <b>"+o.title+"</b>"),window.clearTimeout(App._dl_progress_timeout),App._dl_progress_timeout=window.setTimeout((function(){$(".dl-progress").fadeOut()}),5e3)}))}))}}))},getAll:function(){confirm("Download all books on this page?")&&$(".row > div").each((function(e,t){const o=$(t).attr("id").replace("cell-",""),n=$(t).find(".offline_dropitem")[0];if(o){const e="epube-book."+o;localforage.getItem(e).then((function(e){e||App.Offline.get(o,(function(){App.Offline.mark(n)}))}))}}))},markBooks:function(){const e=$(".offline_dropitem");$.each(e,(function(e,t){App.Offline.mark(t)}))},mark:function(e){const t=e.getAttribute("data-book-id"),o="epube-book."+t;localforage.getItem(o).then((function(o){o?(e.onclick=function(){return App.Offline.remove(t,(function(){App.Offline.mark(e)})),!1},e.innerHTML="Remove offline data"):(e.onclick=function(){return App.Offline.get(t,(function(){App.Offline.mark(e)})),!1},e.innerHTML="Make available offline")}))},removeFromList:function(e){const t=e.getAttribute("data-book-id");return App.Offline.remove(t,(function(){$("#cell-"+t).remove()}))},remove:function(e,t){if(confirm("Remove download?")){const o="epube-book."+e,n=[];console.log("offline remove: "+e),localforage.iterate((function(e,t){t.match(o)&&n.push(localforage.removeItem(t))})),Promise.all(n).then((function(){window.setTimeout((function(){t()}),500)}))}},search:function(){const e=$(".search_query").val();return localforage.setItem("epube.search-query",e).then((function(){App.Offline.populateList()})),!1},removeAll:function(){if(confirm("Remove all downloaded books?")){const e=[];localforage.iterate((function(t,o){o.match("epube-book")&&e.push(localforage.removeItem(o))})),Promise.all(e).then((function(){window.setTimeout((function(){App.Offline.populateList()}),500)}))}},showSummary:function(e){const t=e.getAttribute("data-book-id");return localforage.getItem("epube-book."+t).then((function(e){const t=e.comment?e.comment:"No description available";$("#summary-modal .modal-title").html(e.title),$("#summary-modal .book-summary").html(t),$("#summary-modal").modal()})),!1},populateList:function(){let e=$.urlParam("query");e&&(e=e.toLowerCase());const t=$("#books_container");t.html(""),localforage.iterate((function(o,n){n.match(/epube-book\.\d{1,}$/)&&Promise.all([localforage.getItem(n),localforage.getItem(n+".cover"),localforage.getItem(n+".lastread"),localforage.getItem(n+".book")]).then((function(o){if(o[0]&&o[3]){const n=o[0];if(e){if(!(n.series_name&&n.series_name.toLowerCase().match(e)||n.title&&n.title.toLowerCase().match(e)||n.author_sort&&n.author_sort.toLowerCase().match(e)))return}let a=!1;o&&o[1]&&(a=URL.createObjectURL(o[1]));let i=!1,r=!1;const l=o[2];l&&(i=l.page>0,r=l.total>0&&l.total-l.page<5);const c=r?"read":"",s=i?"in_progress":"",d=n.series_name?`<div><a class="series_link" href="#">${n.series_name+" ["+n.series_index+"]"}</a></div>`:"",p=$(`<div class="col-xxs-6 col-xs-4 col-sm-3 col-md-2" id="cell-${n.id}">\n\t\t\t\t\t\t\t<a class="thumbnail ${c}" href="read.html?id=${n.epub_id}&b=${n.id}">\n\t\t\t\t\t\t\t\t<img style="display : none">\n\t\t\t\t\t\t\t</a>\n\t\t\t\t\t\t\t<div class="caption">\n\t\t\t\t\t\t\t\t<div><a class="${s}" href="read.html?id=${n.epub_id}&b=${n.id}">${n.title}</a></div>\n\t\t\t\t\t\t\t\t<div><a class="author_link" href="#">${n.author_sort}</a></div>\n\t\t\t\t\t\t\t\t${d}\n\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t<div class="dropdown" style="white-space : nowrap">\n\t\t\t\t\t\t\t\t<a href="#" data-toggle="dropdown" role="button">More...<span class="caret"></span></a>\n\t\t\t\t\t\t\t\t<ul class="dropdown-menu">\n\t\t\t\t\t\t\t\t\t<li><a href="#" data-book-id="${n.id}" onclick="return App.Offline.showSummary(this)">Summary</a></li>\n\t\t\t\t\t\t\t\t\t<li><a href="#" data-book-id="${n.id}" onclick="App.Offline.removeFromList(this)">Remove offline data</a></li>\n\t\t\t\t\t\t\t\t</ul>\n\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t</div>`);a?p.find("img").css("background-image","url("+a+")").fadeIn():p.find("img").attr("data-src","holder.js/130x190?auto=yes").fadeIn(),p.find(".series_link").attr("title",n.series_name+" ["+n.series_index+"]").attr("href","offline.html?query="+encodeURIComponent(n.series_name)),p.find(".author_link").attr("title",n.author_sort).attr("href","offline.html?query="+encodeURIComponent(n.author_sort)),t.append(p),Holder.run()}}))}))}}},DEFAULT_FONT_SIZE=16,DEFAULT_FONT_FAMILY="Georgia",DEFAULT_LINE_HEIGHT=140,MIN_LENGTH_TO_JUSTIFY=32,PAGE_RESET_PROGRESS=-1,Reader={init:function(){$(document).on("keyup",(function(e){Reader.hotkeyHandler(e)})),$("#left").on("mouseup",(function(){Reader.Page.prev()})),$("#right").on("mouseup",(function(){Reader.Page.next()})),Reader.Loader.init()},onOfflineModeChanged:function(e){if(console.log("onOfflineModeChanged",e),!e&&window.book){const e=window.book;console.log("we're online, storing lastread");const t=e.rendition.currentLocation().start.cfi,o=parseInt(100*e.locations.percentageFromCfi(t));$.post("backend.php",{op:"storelastread",id:$.urlParam("id"),page:o,cfi:t,timestamp:(new Date).getTime()},(function(e){e.cfi&&(Reader.Page._last_position_sync=(new Date).getTime()/1e3)})).fail((function(e){e&&401==e.status&&(window.location="index.php")}))}},initSecondStage:function(){return"undefined"!=typeof EpubeApp?EpubeApp.setPage("PAGE_READER"):($(window).on("online",(function(){Reader.onOfflineModeChanged(!1)})),$(window).on("offline",(function(){Reader.onOfflineModeChanged(!0)}))),Reader.applyTheme(),localforage.getItem(Reader.cacheId()).then((function(t){if(!t)return console.log("requesting bookinfo..."),new Promise((t,o)=>{const n=$.urlParam("b");$.post("backend.php",{op:"getinfo",id:n}).success((function(e){if(e)return e.has_cover&&fetch("backend.php?op=cover&id="+n,{credentials:"same-origin"}).then((function(e){200==e.status&&localforage.setItem(Reader.cacheId("cover"),e.blob())})),localforage.setItem(Reader.cacheId(),e).then((function(){console.log("bookinfo saved"),t()}));o(new Error("unable to load book info: blank"))})).error((function(t){$(".loading_message").html("Unable to load book info.<br/><small>"+t.status+"</small>"),o(new Error("unable to load book info: "+e))}))});console.log("bookinfo already stored")})).then((function(){console.log("trying to load book..."),localforage.getItem(Reader.cacheId("book")).then((function(t){if(t)return console.log("loading from local storage"),new Promise((function(o,n){const a=new FileReader;a.onload=function(){try{return e.open(this.result).then((function(){o()}))}catch(e){$(".loading_message").html("Unable to load book (local)."),console.log(e),n(new Error("Unable to load book (local):"+e))}},a.readAsArrayBuffer(t)}));if(console.log("loading from network"),App.isOnline()){const t="backend.php?op=download&id="+$.urlParam("id");return $(".loading_message").html("Downloading..."),fetch(t,{credentials:"same-origin"}).then((function(t){if(200==t.status)return t.blob().then((function(t){return new Promise((function(o,n){const a=new FileReader;a.onload=function(){e.open(this.result).then((function(){localforage.setItem(Reader.cacheId("book"),t).then((function(){o()}))})).catch(e=>{$(".loading_message").html("Unable to open book.<br/><small>"+e+"</small>"),n(new Error("Unable to open book: "+e))})},a.onerror=function(e){console.log("filereader error",e),$(".loading_message").html("Unable to open book.<br/><small>"+e+"</small>"),n(new Error("Unable to open book: "+e))},a.readAsArrayBuffer(t)}))})).catch(e=>{console.log("blob error",e),$(".loading_message").html("Unable to download book.<br/><small>"+e+"</small>")});$(".loading_message").html("Unable to download book: "+t.status+".")})).catch((function(e){console.warn(e),$(".loading").is(":visible")&&$(".loading_message").html("Unable to load book (remote).<br/><small>"+e+"</small>")}))}$(".loading_message").html("This book is not available offline.")}));const e=ePub();window.book=e;const t=e.renderTo("reader",{width:"100%",height:"100%",minSpreadWidth:961});function o(t){try{const o=e.spine.get(t).cfiBase,n=e.locations._locations.find((function(e){return-1!=e.indexOf(o)}));return window.book.locations.locationFromCfi(n)}catch(e){console.warn(e)}return""}localforage.getItem("epube.enable-hyphens").then((function(e){e&&(Reader.hyphenateHTML=createHyphenator(hyphenationPatternsEnUs,{html:!0})),Reader.applyStyles(!0),t.display().then((function(){console.log("book displayed")}))})),t.hooks.content.register((function(t){t.on("linkClicked",(function(t){console.log("linkClicked",t),-1==t.indexOf("://")&&($(".prev_location_btn").attr("data-location-cfi",e.rendition.currentLocation().start.cfi).show(),window.setTimeout((function(){Reader.showUI(!0)}),50))}));const o=window.location.href.match(/^.*\//)[0],n=["dist/app-libs.min.js","dist/reader_iframe.min.js"],a=t.document;for(let e=0;e<n.length;e++){const t=a.createElement("script");t.type="text/javascript",t.text=Reader.Loader._res_data[o+n[e]],a.head.appendChild(t)}return $(t.document.head).append($("<style type='text/css'>").text(Reader.Loader._res_data[o+"dist/reader_iframe.min.css"])),localforage.getItem("epube.theme").then((function(e){e||(e="default"),$(t.document).find("body").attr("class","undefined"!=typeof EpubeApp?"is-epube-app":"").addClass("theme-"+e)}))})),$("#settings-modal").on("shown.bs.modal",(function(){localforage.getItem(Reader.cacheId("lastread")).then(e=>{e&&e.cfi&&$(".lastread_input").val(e.page+"%"),$.post("backend.php",{op:"getlastread",id:$.urlParam("id")},(function(e){$(".lastread_input").val(e.page+"%")}))}),localforage.getItem("epube.enable-hyphens").then((function(e){$(".enable_hyphens_checkbox").attr("checked",e).off("click").on("click",(function(e){localforage.setItem("epube.enable-hyphens",e.target.checked),confirm("Toggling hyphens requires page reload. Reload now?")&&window.location.reload()}))})),localforage.getItem("epube.keep-ui-visible").then((function(e){$(".keep_ui_checkbox").attr("checked",e).off("click").on("click",(function(e){localforage.setItem("epube.keep-ui-visible",e.target.checked)}))})),localforage.getItem("epube.cache-timestamp").then((function(e){let t="V: ";parseInt(e)?t+=new Date(1e3*e).toLocaleString("en-GB"):t+="Unknown",t+=" ("+(App.isOnline()?"Online":"Offline")+")",$(".last-mod-timestamp").text(t)})),localforage.getItem("epube.fontFamily").then((function(e){e||(e="Georgia"),$(".font_family").val(e)})),localforage.getItem("epube.theme").then((function(e){$(".theme_name").val(e)})),localforage.getItem("epube.fontSize").then((function(e){e||(e=16);const t=$(".font_size").html("");for(let e=10;e<=32;e++){const o=$("<option>").val(e).html(e+" px");t.append(o)}t.val(e)})),localforage.getItem("epube.lineHeight").then((function(e){e||(e=140);const t=$(".line_height").html("");for(let e=100;e<=220;e+=10){const o=$("<option>").val(e).html(e+"%");t.append(o)}t.val(e)}))})),$("#dict-modal").on("shown.bs.modal",(function(){$(".dict_result").scrollTop(0)})),$(".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(e=>{try{let t="";$.each(e.query.pages,(e,o)=>{t+=o.extract}),$(".dict_result").html(t&&"undefined"!=t?t:"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.")})})),$("#toc-modal").on("shown.bs.modal",(function(){const t=e.navigation.toc,n=$(".toc_list");n.html(""),$.each(t,(function(t,a){try{const t=$("<a>").attr("href","#").html("<b class='pull-right'>"+o(a.href)+"</b>"+a.label).attr("data-href",a.href).click((function(){e.rendition.display(t.attr("data-href"))}));n.append($("<li>").append(t)),function t(n,a,i){if(3==i)return!1;if(n.subitems){const r=$("<ul class='toc_sublist list-unstyled'>");$.each(n.subitems,(function(n,a){const l=$("<a>").attr("href","#").html("<b class='pull-right'>"+o(a.href)+"</b>"+a.label).attr("data-href",a.href).click((function(){e.rendition.display(l.attr("data-href"))}));r.append($("<li>").append(l)),t(a,r,i+1)})),a.append(r)}}(a,n,0)}catch(e){console.warn(e)}})),n.children().length<=1&&(n.html(""),$.each(e.spine.items,(function(t,o){const a=$("<a>").attr("href","#").attr("title",o.url).html("Section "+(t+1)).attr("data-href",o.href).click((function(){e.rendition.display(a.attr("data-href"))}));n.append($("<li>").append(a))})))})),e.spine.hooks.content.register((function(e){$(e).find("p").filter((e,t)=>{if($(t).text().length>=32)return t}).css("text-align","justify"),$(e).find("a, p, span, em, i, strong, b, body, div, big, small").attr("class","").css("font-family","inherit").css("font-size","inherit").css("color","").css("background","").css("background-color",""),void 0!==Reader.hyphenateHTML&&$(e).find("p").each((e,t)=>{(t=$(t)).html(Reader.hyphenateHTML(t.html()))})})),e.ready.then((function(){return localforage.getItem(Reader.cacheId()).then(t=>{let o,n;if(t)o=t.title,n=t.author_sort;else{const t=e.package.metadata;o=t.title,n=t.creator}return document.title=o+" – "+n+" – The Epube",$(".title").text(o).attr("title",o+" – "+n),"undefined"!=typeof EpubeApp&&(EpubeApp.setTitle(o),EpubeApp.showActionBar(!1)),localforage.getItem(Reader.cacheId("locations")).then((function(t){if(console.log("stored pagination",null!=t),t&&"string"==typeof t[0])return Reader.Page._pagination_stored=1,e.locations.load(t);{console.log("requesting pagination...");const t="backend.php?op=getpagination&id="+encodeURIComponent($.urlParam("id"));return fetch(t,{credentials:"same-origin"}).then((function(t){return t.ok?t.json().then((function(t){return t&&"string"==typeof t[0]?(Reader.Page._pagination_stored=1,e.locations.load(t)):($(".loading_message").html("Paginating..."),e.locations.generate(1600))})):($(".loading_message").html("Paginating..."),e.locations.generate(1600))})).catch((function(){return $(".loading_message").html("Paginating..."),e.locations.generate(1600)}))}}))})})).then((function(t){console.log("locations ready, stored=",Reader.Page._pagination_stored),t?(App.isOnline()&&!Reader.Page._pagination_stored&&$.post("backend.php",{op:"storepagination",id:$.urlParam("id"),payload:JSON.stringify(t),total:100}),localforage.getItem(Reader.cacheId("locations")).then((function(e){e||localforage.setItem(Reader.cacheId("locations"),t)})),$(".location").click((function(){const t=e.rendition.currentLocation().start.location,o=e.locations.length(),n=prompt("Jump to location [1-"+o+"]",t);n&&e.rendition.display(e.locations._locations[n])})),Reader.Page.openLastRead(),window.setTimeout((function(){Reader.Page.openLastRead(),$(".loading").hide()}),250)):$(".loading_message").html("Pagination failed.")})),t.on("keyup",e=>{Reader.hotkeyHandler(e)}),t.on("resized",(function(){console.log("resized"),$(".loading").show(),$(".loading_message").html("Opening chapter..."),window.setTimeout((function(){Reader.resizeSideColumns(),Reader.Page.openLastRead(),$(".loading").hide()}),250)})),t.on("rendered",(function(){$(".chapter").html($("<span>").addClass("glyphicon glyphicon-th-list")),Reader.applyTheme(),Reader.resizeSideColumns();try{const t=e.rendition.currentLocation();if(t.start){const o=e.canonical(t.start.href);let n=!1;$.each(Reader.flattenToc(e),(function(t,a){e.spine.get(a.href).canonical!=o||(n=a)})),n&&n.label&&$(".chapter").append("&nbsp;"+n.label.trim()+" | "),Reader.generateTocBar(e,Reader.flattenToc(e))}}catch(e){console.warn(e)}})),t.on("relocated",(function(t){if(0==e.locations.length())return;const o=t.start.cfi,n=parseInt(100*e.locations.percentageFromCfi(o));$("#cur_page").text(t.start.location),$("#total_pages").text(e.locations.length()),$("#page_pct").text(parseInt(100*e.locations.percentageFromCfi(o))+"%"),Reader.updateTocBarPosition(e,t);const a=t.start.displayed;if(a&&($("#chapter_cur_page").text(a.page),$("#chapter_total_pages").text(a.total),a.total>0&&$("#chapter_pct").text(parseInt(a.page/a.total*100)+"%")),Reader.Page._store_position){Reader.Page._store_position=0;const e=(new Date).getTime();console.log("storing lastread",n,o,e),localforage.setItem(Reader.cacheId("lastread"),{cfi:o,page:n,total:100,timestamp:e}),(new Date).getTime()/1e3-Reader.Page._last_position_sync>15&&(App.isOnline()?(console.log("updating remote lastread..."),$.post("backend.php",{op:"storelastread",id:$.urlParam("id"),page:n,cfi:o,timestamp:e},(function(e){e.cfi&&(Reader.Page._last_position_sync=(new Date).getTime()/1e3)})).fail((function(e){e&&401==e.status&&(window.location="index.php")}))):Reader.Page._last_position_sync=0)}}))}))},flattenTocSubItems:function(e,t){let o=[];return 3!=t&&(e.subitems&&$.each(e.subitems,(function(e,n){n._nest=t,o.push(n),o=o.concat(Reader.flattenTocSubItems(n,t+1))})),o)},flattenToc:function(e){if(this._flattened_toc)return this._flattened_toc;{let t=[];return $.each(e.navigation.toc,(function(e,o){o._nest=0,t.push(o),t=t.concat(Reader.flattenTocSubItems(o,1))})),this._flattened_toc=t,t}},generateTocBar:function(e,t){$(".spacer").html(""),$.each(t,(function(t,o){try{const t=e.spine.get(o.href).cfiBase,n=e.locations._locations.find((function(e){return-1!=e.indexOf(t)}));if(n){const t=Math.round(100*e.locations.percentageFromCfi(n));$(".spacer").append($("<div class='toc-bar-entry'>").attr("data-nest-level",o._nest).css("left",t+"%").css("_width",3-o._nest+"px").attr("title",o.label))}}catch(e){console.warn(e)}})),$(".spacer").append($("<div class='toc-bar-entry current-position'>")),Reader.updateTocBarPosition(e,e.rendition.currentLocation())},updateTocBarPosition:function(e,t){const o=Math.round(t.start.location/e.locations.length()*100);$(".toc-bar-entry.current-position").css("left",o+"%")},applyStyles:function(e){Promise.all([localforage.getItem("epube.fontSize"),localforage.getItem("epube.fontFamily"),localforage.getItem("epube.lineHeight"),localforage.getItem("epube.theme")]).then((function(t){const o=t[0]?t[0]+"px":"16px",n=t[1]?t[1]:"Georgia",a=t[2]?t[2]+"%":"140%";console.log("style",n,o,a),console.log("applying default theme..."),window.book.rendition.themes.default({html:{"font-size":o,"font-family":"'"+n+"'","line-height":a,"text-align":"justify","text-indent":"1em"}}),e||(console.log("applying rendition themes..."),$.each(window.book.rendition.getContents(),(function(e,t){t.css("font-size",o),t.css("font-family","'"+n+"'"),t.css("line-height",a),t.css("text-align","justify")}))),Reader.applyTheme()}))},applyTheme:function(){localforage.getItem("epube.theme").then((function(e){e||(e="default"),console.log("called for theme",e),"default"==e&&"undefined"!=typeof EpubeApp&&EpubeApp.isNightMode()&&(e="night"),console.log("setting main UI theme",e),$("body").attr("class","undefined"!=typeof EpubeApp?"is-epube-app":"").addClass("epube-reader theme-"+e).attr("data-is-loading","false"),"undefined"!=typeof EpubeApp&&window.setTimeout((function(){const e=window.getComputedStyle(document.querySelector("body"),null).getPropertyValue("background-color").match(/rgb\((\d{1,}), (\d{1,}), (\d{1,})\)/);e&&(console.log("sending bgcolor",e),EpubeApp.setStatusBarColor(parseInt(e[1]),parseInt(e[2]),parseInt(e[3])))}),250),window.book&&$.each(window.book.rendition.getContents(),(function(t,o){console.log("applying rendition theme",e,"to",o,o.document),$(o.document).find("body").attr("class","undefined"!=typeof EpubeApp?"is-epube-app":"").addClass("theme-"+e)}))}))},hotkeyHandler:function(e){try{if($(".modal").is(":visible"))return;39!=e.which&&32!=e.which&&34!=e.which||(e.preventDefault(),Reader.Page.next()),37!=e.which&&33!=e.which||(e.preventDefault(),Reader.Page.prev()),27==e.which&&(e.preventDefault(),Reader.showUI(!0))}catch(e){console.warn(e)}},resizeSideColumns:function(){let e=$("#reader").position().left;const t=$("#reader iframe")[0];t&&t.contentWindow.$&&(e+=parseInt(t.contentWindow.$("body").css("padding-left"))),$("#left, #right").width(e)},markAsRead:function(){if(confirm("Mark book as read?")){const e=100,t=window.book.locations.cfiFromPercentage(1),o=(new Date).getTime();App.isOnline()&&$.post("backend.php",{op:"storelastread",page:e,cfi:t,id:$.urlParam("id"),timestamp:o},(function(e){$(".lastread_input").val(e.page+"%")})),localforage.setItem(Reader.cacheId("lastread"),{cfi:t,page:e,total:e,timestamp:o})}},close:function(){const e=window.book.rendition.currentLocation().start.cfi,t=parseInt(100*window.book.locations.percentageFromCfi(e)),o=(new Date).getTime();localforage.setItem(Reader.cacheId("lastread"),{cfi:e,page:t,total:100,timestamp:o}),App.isOnline()?$.post("backend.php",{op:"storelastread",id:$.urlParam("id"),page:t,cfi:e,timestamp:o},(function(){window.location=$.urlParam("rt")?"index.php?mode="+$.urlParam("rt"):"index.php"})).fail((function(){window.location="index.php"})):window.location="index.php"},cacheId:function(e){return"epube-book."+$.urlParam("b")+(e?"."+e:"")},toggleFullscreen:function(){if("undefined"!=typeof EpubeApp);else{const e=document.documentElement,t=document.webkitIsFullScreen||document.mozFullScreen||!1;e.requestFullScreen=e.requestFullScreen||e.webkitRequestFullScreen||e.mozRequestFullScreen||function(){return!1},document.cancelFullScreen=document.cancelFullScreen||document.webkitCancelFullScreen||document.mozCancelFullScreen||function(){return!1},t?document.cancelFullScreen():e.requestFullScreen()}},showUI:function(e){e?$(".header,.footer").fadeIn():$(".header,.footer").fadeOut()},toggleUI:function(){$(".header").is(":visible")?$(".header,.footer").fadeOut():$(".header,.footer").fadeIn()},lookupWord:function(e,t){e=e.replace(/­/g,""),$(".dict_result").html("Loading, please wait..."),$("#dict-modal").modal("show"),$.post("backend.php",{op:"define",word:e},(function(o){o&&($(".dict_result").html(o.result.join("<br/>")),$(".dict_query").val(e),t&&t())})).fail((function(e){console.warn(e),$(".dict_result").html("Network error while looking up word: "+e.statusText)}))},search:function(){const e=$(".search_input").val(),t=$(".search_results");t.html(""),e&&Promise.all(window.book.spine.spineItems.map(t=>t.load(window.book.load.bind(window.book)).then(t.find.bind(t,e)).finally(t.unload.bind(t)))).then(e=>Promise.resolve([].concat.apply([],e))).then((function(e){$.each(e,(function(e,o){const n=$("<a>").attr("href","#").html("<b class='pull-right'>"+window.book.locations.locationFromCfi(o.cfi)+"</b>"+o.excerpt).attr("data-cfi",o.cfi).attr("data-id",o.id).click((function(){window.book.rendition.display(n.attr("data-cfi"))}));t.append($("<li>").append(n))}))}))},Loader:{_res_data:[],init:function(){const e=["dist/app-libs.min.js","dist/reader_iframe.min.js","dist/reader_iframe.min.css"];for(let t=0;t<e.length;t++)fetch(e[t],{credentials:"same-origin"}).then((function(o){200==o.status?o.text().then((function(e){const t=new URL(o.url);t.searchParams.delete("ts"),Reader.Loader._res_data[t.toString()]=e})):console.warn("loader failed for resource",e[t],o)}));Reader.Loader.checkProgress(e,Reader.Loader._res_data,0)},checkProgress:function(e,t,o){console.log("check_resource_load",o,e.length,Object.keys(t).length,Reader,Reader.Loader),5!=o?e.length!=Object.keys(t).length?window.setTimeout((function(){Reader.Loader.checkProgress(e,t,o+1)}),250):Reader.initSecondStage():$(".loading_message").html("Unable to load resources.")}},Page:{_store_position:0,_last_position_sync:0,_pagination_stored:0,next:function(){Reader.Page._store_position=1,window.book.rendition.next(),"undefined"!=typeof EpubeApp?EpubeApp.showActionBar(!1):localforage.getItem("epube.keep-ui-visible").then((function(e){e||Reader.showUI(!1)}))},prev:function(){window.book.rendition.prev(),"undefined"!=typeof EpubeApp?EpubeApp.showActionBar(!1):localforage.getItem("epube.keep-ui-visible").then((function(e){e||Reader.showUI(!1)}))},openPrevious:function(e){const t=$(e).attr("data-location-cfi");t&&window.book.rendition.display(t),$(e).fadeOut()},clearLastRead:function(){if(confirm("Clear stored last read location?")){const e=window.book.locations.length(),t=(new Date).getTime();App.isOnline()&&$.post("backend.php",{op:"storelastread",page:-1,cfi:"",id:$.urlParam("id"),timestamp:t},(function(e){$(".lastread_input").val(e.page+"%")})),localforage.setItem(Reader.cacheId("lastread"),{cfi:"",page:0,total:e,timestamp:t}),window.setTimeout((function(){window.book.rendition.display(window.book.locations.cfiFromPercentage(0))}),250)}},openLastRead:function(e){localforage.getItem(Reader.cacheId("lastread")).then((function(t){console.log("lr local",t),t=t||{};try{t.cfi&&window.book.rendition.display(t.cfi).then(()=>{$(".loading").hide(),t.cfi&&window.book.rendition.display(t.cfi)})}catch(e){console.warn(e)}App.isOnline()&&!e&&$.post("backend.php",{op:"getlastread",id:$.urlParam("id")},(function(e){if(console.log("lr remote",e),App.isOnline()&&e)try{e.cfi&&t.cfi!=e.cfi&&e.timestamp>t.timestamp&&console.log("using remote lastread (timestamp is newer)"),localforage.setItem(Reader.cacheId("lastread"),{cfi:e.cfi,page:e.page,total:e.total,timestamp:e.timestamp}),window.book.rendition.display(e.cfi).then(()=>{window.book.rendition.display(e.cfi)})}catch(e){console.warn(e)}})).fail((function(e){e&&401==e.status&&(window.location="index.php")}))}))}},Settings:{onThemeChanged:function(e){const t=$(e).val();localforage.setItem("epube.theme",t).then((function(){Reader.applyTheme()}))},onLineHeightChanged:function(e){const t=$(e).val();localforage.setItem("epube.lineHeight",t).then((function(){Reader.applyStyles()}))},onTextSizeChanged:function(e){const t=$(e).val();localforage.setItem("epube.fontSize",t).then((function(){Reader.applyStyles()}))},onFontChanged:function(e){const t=$(e).val();localforage.setItem("epube.fontFamily",t).then((function(){Reader.applyStyles()}))}}};function __get_reader(){return Reader}function __get_app(){return App} \ No newline at end of file
+"use strict";$.urlParam=function(e){try{const t=new RegExp("[?&]"+e+"=([^&#]*)").exec(window.location.href);return decodeURIComponent(t[1].replace(/\+/g," "))||0}catch(e){return 0}};const Cookie={set:function(e,t,o){const n=new Date;n.setTime(n.getTime()+1e3*o);const a="expires="+n.toUTCString();document.cookie=e+"="+encodeURIComponent(t)+"; "+a},get:function(e){e+="=";const t=document.cookie.split(";");for(let o=0;o<t.length;o++){let n=t[o];for(;" "==n.charAt(0);)n=n.substring(1);if(0==n.indexOf(e))return decodeURIComponent(n.substring(e.length,n.length))}return""},delete:function(e){document.cookie=e+"=; expires=Thu, 01-Jan-1970 00:00:01 GMT"}},App={_dl_progress_timeout:!1,index_mode:"",last_mtime:-1,csrf_token:"",init:function(){let e=0;this.csrf_token=Cookie.get("epube_csrf_token"),console.log("setting prefilter for token",this.csrf_token),$.ajaxPrefilter((function(e,t,o){if("post"!==t.type||"post"!==e.type)return;const n=typeof t.data;"object"==n?e.data=$.param($.extend(t.data,{csrf_token:App.csrf_token})):"string"==n&&(e.data=t.data+"&csrf_token="+encodeURIComponent(App.srf_token)),console.log(">>>",e)})),"undefined"!=typeof EpubeApp&&($(".navbar").hide(),$(".epube-app-filler").show(),$(".separate-search").show(),"favorites"==$.urlParam("mode")?EpubeApp.setPage("PAGE_FAVORITES"):EpubeApp.setPage("PAGE_LIBRARY")),App.initNightMode(),"serviceWorker"in navigator?navigator.serviceWorker.addEventListener("message",(function(t){"refresh-started"==t.data&&(console.log("cache refresh started"),e=0,$(".dl-progress").fadeIn().text("Loading, please wait...")),t.data&&0==t.data.indexOf("refreshed:")&&(++e,$(".dl-progress").fadeIn().text("Updated "+e+" files...")),"client-reload"==t.data&&(localforage.setItem("epube.cache-timestamp",App.last_mtime),window.location.reload())})):$(".container-main").addClass("alert alert-danger").html("Service worker support missing in browser (are you using plain HTTP?)."),App.showCovers(),App.Offline.markBooks(),App.refreshCache()},logout:function(){$.post("backend.php",{op:"logout"}).then(()=>{window.location.reload()})},showSummary:function(e){const t=e.getAttribute("data-book-id");return $.post("backend.php",{op:"getinfo",id:t},(function(e){const t=e.comment?e.comment:"No description available";$("#summary-modal .modal-title").html(e.title),$("#summary-modal .book-summary").html(t),$("#summary-modal").modal()})),!1},showCovers:function(){$("img[data-book-id]").each((e,t)=>{if((t=$(t)).attr("data-cover-link")){const e=$("<img>").on("load",(function(){t.css("background-image","url("+t.attr("data-cover-link")+")").fadeIn(),e.attr("src",null)})).attr("src",t.attr("data-cover-link"))}else t.attr("src","holder.js/130x190?auto=yes").fadeIn()}),Holder.run()},toggleFavorite:function(e){const t=e.getAttribute("data-book-id");return("0"==e.getAttribute("data-is-fav")||confirm("Remove favorite?"))&&$.post("backend.php",{op:"togglefav",id:t},(function(o){if(o){let n="[Error]";0==o.status?n="Add to favorites":1==o.status&&(n="Remove from favorites"),$(e).html(n).attr("data-is-fav",o.status),"favorites"==App.index_mode&&0==o.status&&$("#cell-"+t).remove()}})),!1},refreshCache:function(e){"serviceWorker"in navigator?localforage.getItem("epube.cache-timestamp").then((function(t){console.log("stamp",t,"last mtime",App.last_mtime),(e||t!=App.last_mtime)&&(console.log("asking worker to refresh cache"),navigator.serviceWorker.controller?navigator.serviceWorker.controller.postMessage("refresh-cache"):localforage.getItem("epube.initial-load-done").then((function(e){console.log("initial load done",e),e?$(".dl-progress").show().addClass("alert-danger").html("Could not communicate with service worker. Try reloading the page."):localforage.setItem("epube.initial-load-done",!0).then((function(){$(".dl-progress").show().addClass("alert-info").html("Page will reload to activate service worker..."),window.setTimeout((function(){window.location.reload()}),3e3)}))})))})):$(".dl-progress").show().addClass("alert-danger").html("Could not communicate with service worker. Try reloading the page.")},isOnline:function(){return"undefined"!=typeof EpubeApp&&void 0!==EpubeApp.isOnline?EpubeApp.isOnline():navigator.onLine},appCheckOffline:function(){EpubeApp.setOffline(!App.isOnline)},initNightMode:function(){if("undefined"==typeof EpubeApp){if(window.matchMedia){const e=window.matchMedia("(prefers-color-scheme: dark)");e.addEventListener("change",()=>{App.applyNightMode(e.matches)}),App.applyNightMode(e.matches)}}else App.applyNightMode(EpubeApp.isNightMode())},applyNightMode:function(e){console.log("night mode changed to",e),$("#theme_css").attr("href","lib/bootstrap/v3/css/"+(e?"theme-dark.min.css":"bootstrap-theme.min.css"))},Offline:{init:function(){"undefined"!=typeof EpubeApp&&($(".navbar").hide(),$(".epube-app-filler").show(),EpubeApp.setPage("PAGE_OFFLINE")),App.initNightMode();const e=$.urlParam("query");e&&$(".search_query").val(e),App.Offline.populateList()},get:function(e,t){console.log("offline cache: "+e),$.post("backend.php",{op:"getinfo",id:e},(function(o){if(o){const n="epube-book."+e;localforage.setItem(n,o).then((function(o){console.log(n+" got data");const a=[];a.push(fetch("backend.php?op=download&id="+o.epub_id,{credentials:"same-origin"}).then((function(e){200==e.status&&(console.log(n+" got book"),t(),localforage.setItem(n+".book",e.blob()))}))),a.push(fetch("backend.php?op=getpagination&id="+o.epub_id,{credentials:"same-origin"}).then((function(e){200==e.status&&(console.log(n+" got pagination"),e.text().then((function(e){localforage.setItem(n+".locations",JSON.parse(e))})))}))),a.push(fetch("backend.php?op=getlastread&id="+o.epub_id,{credentials:"same-origin"}).then((function(e){200==e.status&&(console.log(n+" got lastread"),e.text().then((function(e){localforage.setItem(n+".lastread",JSON.parse(e))})))}))),o.has_cover&&a.push(fetch("backend.php?op=cover&id="+e,{credentials:"same-origin"}).then((function(e){200==e.status&&(console.log(n+" got cover"),localforage.setItem(n+".cover",e.blob()))}))),Promise.all(a).then((function(){$(".dl-progress").show().html("Finished downloading <b>"+o.title+"</b>"),window.clearTimeout(App._dl_progress_timeout),App._dl_progress_timeout=window.setTimeout((function(){$(".dl-progress").fadeOut()}),5e3)}))}))}}))},getAll:function(){confirm("Download all books on this page?")&&$(".row > div").each((function(e,t){const o=$(t).attr("id").replace("cell-",""),n=$(t).find(".offline_dropitem")[0];if(o){const e="epube-book."+o;localforage.getItem(e).then((function(e){e||App.Offline.get(o,(function(){App.Offline.mark(n)}))}))}}))},markBooks:function(){const e=$(".offline_dropitem");$.each(e,(function(e,t){App.Offline.mark(t)}))},mark:function(e){const t=e.getAttribute("data-book-id"),o="epube-book."+t;localforage.getItem(o).then((function(o){o?(e.onclick=function(){return App.Offline.remove(t,(function(){App.Offline.mark(e)})),!1},e.innerHTML="Remove offline data"):(e.onclick=function(){return App.Offline.get(t,(function(){App.Offline.mark(e)})),!1},e.innerHTML="Make available offline")}))},removeFromList:function(e){const t=e.getAttribute("data-book-id");return App.Offline.remove(t,(function(){$("#cell-"+t).remove()}))},remove:function(e,t){if(confirm("Remove download?")){const o="epube-book."+e,n=[];console.log("offline remove: "+e),localforage.iterate((function(e,t){t.match(o)&&n.push(localforage.removeItem(t))})),Promise.all(n).then((function(){window.setTimeout((function(){t()}),500)}))}},search:function(){const e=$(".search_query").val();return localforage.setItem("epube.search-query",e).then((function(){App.Offline.populateList()})),!1},removeAll:function(){if(confirm("Remove all downloaded books?")){const e=[];localforage.iterate((function(t,o){o.match("epube-book")&&e.push(localforage.removeItem(o))})),Promise.all(e).then((function(){window.setTimeout((function(){App.Offline.populateList()}),500)}))}},showSummary:function(e){const t=e.getAttribute("data-book-id");return localforage.getItem("epube-book."+t).then((function(e){const t=e.comment?e.comment:"No description available";$("#summary-modal .modal-title").html(e.title),$("#summary-modal .book-summary").html(t),$("#summary-modal").modal()})),!1},populateList:function(){let e=$.urlParam("query");e&&(e=e.toLowerCase());const t=$("#books_container");t.html(""),localforage.iterate((function(o,n){n.match(/epube-book\.\d{1,}$/)&&Promise.all([localforage.getItem(n),localforage.getItem(n+".cover"),localforage.getItem(n+".lastread"),localforage.getItem(n+".book")]).then((function(o){if(o[0]&&o[3]){const n=o[0];if(e){if(!(n.series_name&&n.series_name.toLowerCase().match(e)||n.title&&n.title.toLowerCase().match(e)||n.author_sort&&n.author_sort.toLowerCase().match(e)))return}let a=!1;o&&o[1]&&(a=URL.createObjectURL(o[1]));let i=!1,r=!1;const l=o[2];l&&(i=l.page>0,r=l.total>0&&l.total-l.page<5);const c=r?"read":"",s=i?"in_progress":"",d=n.series_name?`<div><a class="series_link" href="#">${n.series_name+" ["+n.series_index+"]"}</a></div>`:"",p=$(`<div class="col-xxs-6 col-xs-4 col-sm-3 col-md-2" id="cell-${n.id}">\n\t\t\t\t\t\t\t<a class="thumbnail ${c}" href="read.html?id=${n.epub_id}&b=${n.id}">\n\t\t\t\t\t\t\t\t<img style="display : none">\n\t\t\t\t\t\t\t</a>\n\t\t\t\t\t\t\t<div class="caption">\n\t\t\t\t\t\t\t\t<div><a class="${s}" href="read.html?id=${n.epub_id}&b=${n.id}">${n.title}</a></div>\n\t\t\t\t\t\t\t\t<div><a class="author_link" href="#">${n.author_sort}</a></div>\n\t\t\t\t\t\t\t\t${d}\n\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t<div class="dropdown" style="white-space : nowrap">\n\t\t\t\t\t\t\t\t<a href="#" data-toggle="dropdown" role="button">More...<span class="caret"></span></a>\n\t\t\t\t\t\t\t\t<ul class="dropdown-menu">\n\t\t\t\t\t\t\t\t\t<li><a href="#" data-book-id="${n.id}" onclick="return App.Offline.showSummary(this)">Summary</a></li>\n\t\t\t\t\t\t\t\t\t<li><a href="#" data-book-id="${n.id}" onclick="App.Offline.removeFromList(this)">Remove offline data</a></li>\n\t\t\t\t\t\t\t\t</ul>\n\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t</div>`);a?p.find("img").css("background-image","url("+a+")").fadeIn():p.find("img").attr("data-src","holder.js/130x190?auto=yes").fadeIn(),p.find(".series_link").attr("title",n.series_name+" ["+n.series_index+"]").attr("href","offline.html?query="+encodeURIComponent(n.series_name)),p.find(".author_link").attr("title",n.author_sort).attr("href","offline.html?query="+encodeURIComponent(n.author_sort)),t.append(p),Holder.run()}}))}))}}},DEFAULT_FONT_SIZE=16,DEFAULT_FONT_FAMILY="Georgia",DEFAULT_LINE_HEIGHT=140,MIN_LENGTH_TO_JUSTIFY=32,PAGE_RESET_PROGRESS=-1,Reader={csrf_token:"",init:function(){this.csrf_token=Cookie.get("epube_csrf_token"),console.log("setting prefilter for token",this.csrf_token),$.ajaxPrefilter((function(e,t){if("post"!==t.type||"post"!==e.type)return;const o=typeof t.data;"object"==o?e.data=$.param($.extend(t.data,{csrf_token:Reader.csrf_token})):"string"==o&&(e.data=t.data+"&csrf_token="+encodeURIComponent(Reader.srf_token)),console.log(">>>",e)})),$(document).on("keyup",(function(e){Reader.hotkeyHandler(e)})),$("#left").on("mouseup",(function(){Reader.Page.prev()})),$("#right").on("mouseup",(function(){Reader.Page.next()})),Reader.Loader.init()},onOfflineModeChanged:function(e){if(console.log("onOfflineModeChanged",e),!e&&window.book){const e=window.book;console.log("we're online, storing lastread");const t=e.rendition.currentLocation().start.cfi,o=parseInt(100*e.locations.percentageFromCfi(t));$.post("backend.php",{op:"storelastread",id:$.urlParam("id"),page:o,cfi:t,timestamp:(new Date).getTime()},(function(e){e.cfi&&(Reader.Page._last_position_sync=(new Date).getTime()/1e3)})).fail((function(e){e&&401==e.status&&(window.location="index.php")}))}},initSecondStage:function(){return"undefined"!=typeof EpubeApp?EpubeApp.setPage("PAGE_READER"):($(window).on("online",(function(){Reader.onOfflineModeChanged(!1)})),$(window).on("offline",(function(){Reader.onOfflineModeChanged(!0)}))),Reader.applyTheme(),localforage.getItem(Reader.cacheId()).then((function(t){if(!t)return console.log("requesting bookinfo..."),new Promise((t,o)=>{const n=$.urlParam("b");$.post("backend.php",{op:"getinfo",id:n}).success((function(e){if(e)return e.has_cover&&fetch("backend.php?op=cover&id="+n,{credentials:"same-origin"}).then((function(e){200==e.status&&localforage.setItem(Reader.cacheId("cover"),e.blob())})),localforage.setItem(Reader.cacheId(),e).then((function(){console.log("bookinfo saved"),t()}));o(new Error("unable to load book info: blank"))})).error((function(t){$(".loading_message").html("Unable to load book info.<br/><small>"+t.status+"</small>"),o(new Error("unable to load book info: "+e))}))});console.log("bookinfo already stored")})).then((function(){console.log("trying to load book..."),localforage.getItem(Reader.cacheId("book")).then((function(t){if(t)return console.log("loading from local storage"),new Promise((function(o,n){const a=new FileReader;a.onload=function(){try{return e.open(this.result).then((function(){o()}))}catch(e){$(".loading_message").html("Unable to load book (local)."),console.log(e),n(new Error("Unable to load book (local):"+e))}},a.readAsArrayBuffer(t)}));if(console.log("loading from network"),App.isOnline()){const t="backend.php?op=download&id="+$.urlParam("id");return $(".loading_message").html("Downloading..."),fetch(t,{credentials:"same-origin"}).then((function(t){if(200==t.status)return t.blob().then((function(t){return new Promise((function(o,n){const a=new FileReader;a.onload=function(){e.open(this.result).then((function(){localforage.setItem(Reader.cacheId("book"),t).then((function(){o()}))})).catch(e=>{$(".loading_message").html("Unable to open book.<br/><small>"+e+"</small>"),n(new Error("Unable to open book: "+e))})},a.onerror=function(e){console.log("filereader error",e),$(".loading_message").html("Unable to open book.<br/><small>"+e+"</small>"),n(new Error("Unable to open book: "+e))},a.readAsArrayBuffer(t)}))})).catch(e=>{console.log("blob error",e),$(".loading_message").html("Unable to download book.<br/><small>"+e+"</small>")});$(".loading_message").html("Unable to download book: "+t.status+".")})).catch((function(e){console.warn(e),$(".loading").is(":visible")&&$(".loading_message").html("Unable to load book (remote).<br/><small>"+e+"</small>")}))}$(".loading_message").html("This book is not available offline.")}));const e=ePub();window.book=e;const t=e.renderTo("reader",{width:"100%",height:"100%",minSpreadWidth:961});function o(t){try{const o=e.spine.get(t).cfiBase,n=e.locations._locations.find((function(e){return-1!=e.indexOf(o)}));return window.book.locations.locationFromCfi(n)}catch(e){console.warn(e)}return""}localforage.getItem("epube.enable-hyphens").then((function(e){e&&(Reader.hyphenateHTML=createHyphenator(hyphenationPatternsEnUs,{html:!0})),Reader.applyStyles(!0),t.display().then((function(){console.log("book displayed")}))})),t.hooks.content.register((function(t){t.on("linkClicked",(function(t){console.log("linkClicked",t),-1==t.indexOf("://")&&($(".prev_location_btn").attr("data-location-cfi",e.rendition.currentLocation().start.cfi).show(),window.setTimeout((function(){Reader.showUI(!0)}),50))}));const o=window.location.href.match(/^.*\//)[0],n=["dist/app-libs.min.js","dist/reader_iframe.min.js"],a=t.document;for(let e=0;e<n.length;e++){const t=a.createElement("script");t.type="text/javascript",t.text=Reader.Loader._res_data[o+n[e]],a.head.appendChild(t)}return $(t.document.head).append($("<style type='text/css'>").text(Reader.Loader._res_data[o+"dist/reader_iframe.min.css"])),localforage.getItem("epube.theme").then((function(e){e||(e="default"),$(t.document).find("body").attr("class","undefined"!=typeof EpubeApp?"is-epube-app":"").addClass("theme-"+e)}))})),$("#settings-modal").on("shown.bs.modal",(function(){localforage.getItem(Reader.cacheId("lastread")).then(e=>{e&&e.cfi&&$(".lastread_input").val(e.page+"%"),$.post("backend.php",{op:"getlastread",id:$.urlParam("id")},(function(e){$(".lastread_input").val(e.page+"%")}))}),localforage.getItem("epube.enable-hyphens").then((function(e){$(".enable_hyphens_checkbox").attr("checked",e).off("click").on("click",(function(e){localforage.setItem("epube.enable-hyphens",e.target.checked),confirm("Toggling hyphens requires page reload. Reload now?")&&window.location.reload()}))})),localforage.getItem("epube.keep-ui-visible").then((function(e){$(".keep_ui_checkbox").attr("checked",e).off("click").on("click",(function(e){localforage.setItem("epube.keep-ui-visible",e.target.checked)}))})),localforage.getItem("epube.cache-timestamp").then((function(e){let t="V: ";parseInt(e)?t+=new Date(1e3*e).toLocaleString("en-GB"):t+="Unknown",t+=" ("+(App.isOnline()?"Online":"Offline")+")",$(".last-mod-timestamp").text(t)})),localforage.getItem("epube.fontFamily").then((function(e){e||(e="Georgia"),$(".font_family").val(e)})),localforage.getItem("epube.theme").then((function(e){$(".theme_name").val(e)})),localforage.getItem("epube.fontSize").then((function(e){e||(e=16);const t=$(".font_size").html("");for(let e=10;e<=32;e++){const o=$("<option>").val(e).html(e+" px");t.append(o)}t.val(e)})),localforage.getItem("epube.lineHeight").then((function(e){e||(e=140);const t=$(".line_height").html("");for(let e=100;e<=220;e+=10){const o=$("<option>").val(e).html(e+"%");t.append(o)}t.val(e)}))})),$("#dict-modal").on("shown.bs.modal",(function(){$(".dict_result").scrollTop(0)})),$(".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(e=>{try{let t="";$.each(e.query.pages,(e,o)=>{t+=o.extract}),$(".dict_result").html(t&&"undefined"!=t?t:"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.")})})),$("#toc-modal").on("shown.bs.modal",(function(){const t=e.navigation.toc,n=$(".toc_list");n.html(""),$.each(t,(function(t,a){try{const t=$("<a>").attr("href","#").html("<b class='pull-right'>"+o(a.href)+"</b>"+a.label).attr("data-href",a.href).click((function(){e.rendition.display(t.attr("data-href"))}));n.append($("<li>").append(t)),function t(n,a,i){if(3==i)return!1;if(n.subitems){const r=$("<ul class='toc_sublist list-unstyled'>");$.each(n.subitems,(function(n,a){const l=$("<a>").attr("href","#").html("<b class='pull-right'>"+o(a.href)+"</b>"+a.label).attr("data-href",a.href).click((function(){e.rendition.display(l.attr("data-href"))}));r.append($("<li>").append(l)),t(a,r,i+1)})),a.append(r)}}(a,n,0)}catch(e){console.warn(e)}})),n.children().length<=1&&(n.html(""),$.each(e.spine.items,(function(t,o){const a=$("<a>").attr("href","#").attr("title",o.url).html("Section "+(t+1)).attr("data-href",o.href).click((function(){e.rendition.display(a.attr("data-href"))}));n.append($("<li>").append(a))})))})),e.spine.hooks.content.register((function(e){$(e).find("p").filter((e,t)=>$(t).text().length>=32?t:null).css("text-align","justify"),$(e).find("a, p, span, em, i, strong, b, body, div, big, small").attr("class","").css("font-family","inherit").css("font-size","inherit").css("color","").css("background","").css("background-color",""),void 0!==Reader.hyphenateHTML&&$(e).find("p").each((e,t)=>{(t=$(t)).html(Reader.hyphenateHTML(t.html()))})})),e.ready.then((function(){return localforage.getItem(Reader.cacheId()).then(t=>{let o,n;if(t)o=t.title,n=t.author_sort;else{const t=e.package.metadata;o=t.title,n=t.creator}return document.title=o+" – "+n+" – The Epube",$(".title").text(o).attr("title",o+" – "+n),"undefined"!=typeof EpubeApp&&(EpubeApp.setTitle(o),EpubeApp.showActionBar(!1)),localforage.getItem(Reader.cacheId("locations")).then((function(t){if(console.log("stored pagination",null!=t),t&&"string"==typeof t[0])return Reader.Page._pagination_stored=1,e.locations.load(t);{console.log("requesting pagination...");const t="backend.php?op=getpagination&id="+encodeURIComponent($.urlParam("id"));return fetch(t,{credentials:"same-origin"}).then((function(t){return t.ok?t.json().then((function(t){return t&&"string"==typeof t[0]?(Reader.Page._pagination_stored=1,e.locations.load(t)):($(".loading_message").html("Paginating..."),e.locations.generate(1600))})):($(".loading_message").html("Paginating..."),e.locations.generate(1600))})).catch((function(){return $(".loading_message").html("Paginating..."),e.locations.generate(1600)}))}}))})})).then((function(t){console.log("locations ready, stored=",Reader.Page._pagination_stored),t?(App.isOnline()&&!Reader.Page._pagination_stored&&$.post("backend.php",{op:"storepagination",id:$.urlParam("id"),payload:JSON.stringify(t),total:100}),localforage.getItem(Reader.cacheId("locations")).then((function(e){e||localforage.setItem(Reader.cacheId("locations"),t)})),$(".location").click((function(){const t=e.rendition.currentLocation().start.location,o=e.locations.length(),n=prompt("Jump to location [1-"+o+"]",t);n&&e.rendition.display(e.locations._locations[n])})),Reader.Page.openLastRead(),window.setTimeout((function(){Reader.Page.openLastRead(),$(".loading").hide()}),250)):$(".loading_message").html("Pagination failed.")})),t.on("keyup",e=>{Reader.hotkeyHandler(e)}),t.on("resized",(function(){console.log("resized"),$(".loading").show(),$(".loading_message").html("Opening chapter..."),window.setTimeout((function(){Reader.resizeSideColumns(),Reader.Page.openLastRead(),$(".loading").hide()}),250)})),t.on("rendered",(function(){$(".chapter").html($("<span>").addClass("glyphicon glyphicon-th-list")),Reader.applyTheme(),Reader.resizeSideColumns();try{const t=e.rendition.currentLocation();if(t.start){const o=e.canonical(t.start.href);let n=!1;$.each(Reader.flattenToc(e),(function(t,a){e.spine.get(a.href).canonical!=o||(n=a)})),n&&n.label&&$(".chapter").append("&nbsp;"+n.label.trim()+" | "),Reader.generateTocBar(e,Reader.flattenToc(e))}}catch(e){console.warn(e)}})),t.on("relocated",(function(t){if(0==e.locations.length())return;const o=t.start.cfi,n=parseInt(100*e.locations.percentageFromCfi(o));$("#cur_page").text(t.start.location),$("#total_pages").text(e.locations.length()),$("#page_pct").text(parseInt(100*e.locations.percentageFromCfi(o))+"%"),Reader.updateTocBarPosition(e,t);const a=t.start.displayed;if(a&&($("#chapter_cur_page").text(a.page),$("#chapter_total_pages").text(a.total),a.total>0&&$("#chapter_pct").text(parseInt(a.page/a.total*100)+"%")),Reader.Page._store_position){Reader.Page._store_position=0;const e=(new Date).getTime();console.log("storing lastread",n,o,e),localforage.setItem(Reader.cacheId("lastread"),{cfi:o,page:n,total:100,timestamp:e}),(new Date).getTime()/1e3-Reader.Page._last_position_sync>15&&(App.isOnline()?(console.log("updating remote lastread..."),$.post("backend.php",{op:"storelastread",id:$.urlParam("id"),page:n,cfi:o,timestamp:e},(function(e){e.cfi&&(Reader.Page._last_position_sync=(new Date).getTime()/1e3)})).fail((function(e){e&&401==e.status&&(window.location="index.php")}))):Reader.Page._last_position_sync=0)}}))}))},flattenTocSubItems:function(e,t){let o=[];return 3!=t&&(e.subitems&&$.each(e.subitems,(function(e,n){n._nest=t,o.push(n),o=o.concat(Reader.flattenTocSubItems(n,t+1))})),o)},flattenToc:function(e){if(this._flattened_toc)return this._flattened_toc;{let t=[];return $.each(e.navigation.toc,(function(e,o){o._nest=0,t.push(o),t=t.concat(Reader.flattenTocSubItems(o,1))})),this._flattened_toc=t,t}},generateTocBar:function(e,t){$(".spacer").html(""),$.each(t,(function(t,o){try{const t=e.spine.get(o.href).cfiBase,n=e.locations._locations.find((function(e){return-1!=e.indexOf(t)}));if(n){const t=Math.round(100*e.locations.percentageFromCfi(n));$(".spacer").append($("<div class='toc-bar-entry'>").attr("data-nest-level",o._nest).css("left",t+"%").css("_width",3-o._nest+"px").attr("title",o.label))}}catch(e){console.warn(e)}})),$(".spacer").append($("<div class='toc-bar-entry current-position'>")),Reader.updateTocBarPosition(e,e.rendition.currentLocation())},updateTocBarPosition:function(e,t){const o=Math.round(t.start.location/e.locations.length()*100);$(".toc-bar-entry.current-position").css("left",o+"%")},applyStyles:function(e){Promise.all([localforage.getItem("epube.fontSize"),localforage.getItem("epube.fontFamily"),localforage.getItem("epube.lineHeight"),localforage.getItem("epube.theme")]).then((function(t){const o=t[0]?t[0]+"px":"16px",n=t[1]?t[1]:"Georgia",a=t[2]?t[2]+"%":"140%";console.log("style",n,o,a),console.log("applying default theme..."),window.book.rendition.themes.default({html:{"font-size":o,"font-family":"'"+n+"'","line-height":a,"text-align":"justify","text-indent":"1em"}}),e||(console.log("applying rendition themes..."),$.each(window.book.rendition.getContents(),(function(e,t){t.css("font-size",o),t.css("font-family","'"+n+"'"),t.css("line-height",a),t.css("text-align","justify")}))),Reader.applyTheme()}))},applyTheme:function(){localforage.getItem("epube.theme").then((function(e){e||(e="default"),console.log("called for theme",e),"default"==e&&"undefined"!=typeof EpubeApp&&EpubeApp.isNightMode()&&(e="night"),console.log("setting main UI theme",e),$("body").attr("class","undefined"!=typeof EpubeApp?"is-epube-app":"").addClass("epube-reader theme-"+e).attr("data-is-loading","false"),"undefined"!=typeof EpubeApp&&window.setTimeout((function(){const e=window.getComputedStyle(document.querySelector("body"),null).getPropertyValue("background-color").match(/rgb\((\d{1,}), (\d{1,}), (\d{1,})\)/);e&&(console.log("sending bgcolor",e),EpubeApp.setStatusBarColor(parseInt(e[1]),parseInt(e[2]),parseInt(e[3])))}),250),window.book&&$.each(window.book.rendition.getContents(),(function(t,o){console.log("applying rendition theme",e,"to",o,o.document),$(o.document).find("body").attr("class","undefined"!=typeof EpubeApp?"is-epube-app":"").addClass("theme-"+e)}))}))},hotkeyHandler:function(e){try{if($(".modal").is(":visible"))return;39!=e.which&&32!=e.which&&34!=e.which||(e.preventDefault(),Reader.Page.next()),37!=e.which&&33!=e.which||(e.preventDefault(),Reader.Page.prev()),27==e.which&&(e.preventDefault(),Reader.showUI(!0))}catch(e){console.warn(e)}},resizeSideColumns:function(){let e=$("#reader").position().left;const t=$("#reader iframe")[0];t&&t.contentWindow.$&&(e+=parseInt(t.contentWindow.$("body").css("padding-left"))),$("#left, #right").width(e)},markAsRead:function(){if(confirm("Mark book as read?")){const e=100,t=window.book.locations.cfiFromPercentage(1),o=(new Date).getTime();App.isOnline()&&$.post("backend.php",{op:"storelastread",page:e,cfi:t,id:$.urlParam("id"),timestamp:o},(function(e){$(".lastread_input").val(e.page+"%")})),localforage.setItem(Reader.cacheId("lastread"),{cfi:t,page:e,total:e,timestamp:o})}},close:function(){const e=window.book.rendition.currentLocation().start.cfi,t=parseInt(100*window.book.locations.percentageFromCfi(e)),o=(new Date).getTime();localforage.setItem(Reader.cacheId("lastread"),{cfi:e,page:t,total:100,timestamp:o}),App.isOnline()?$.post("backend.php",{op:"storelastread",id:$.urlParam("id"),page:t,cfi:e,timestamp:o},(function(){window.location=$.urlParam("rt")?"index.php?mode="+$.urlParam("rt"):"index.php"})).fail((function(){window.location="index.php"})):window.location="index.php"},cacheId:function(e){return"epube-book."+$.urlParam("b")+(e?"."+e:"")},toggleFullscreen:function(){if("undefined"!=typeof EpubeApp);else{const e=document.documentElement,t=document.webkitIsFullScreen||document.mozFullScreen||!1;e.requestFullScreen=e.requestFullScreen||e.webkitRequestFullScreen||e.mozRequestFullScreen||function(){return!1},document.cancelFullScreen=document.cancelFullScreen||document.webkitCancelFullScreen||document.mozCancelFullScreen||function(){return!1},t?document.cancelFullScreen():e.requestFullScreen()}},showUI:function(e){e?$(".header,.footer").fadeIn():$(".header,.footer").fadeOut()},toggleUI:function(){$(".header").is(":visible")?$(".header,.footer").fadeOut():$(".header,.footer").fadeIn()},lookupWord:function(e,t){e=e.replace(/­/g,""),$(".dict_result").html("Loading, please wait..."),$("#dict-modal").modal("show"),$.post("backend.php",{op:"define",word:e},(function(o){o&&($(".dict_result").html(o.result.join("<br/>")),$(".dict_query").val(e),t&&t())})).fail((function(e){console.warn(e),$(".dict_result").html("Network error while looking up word: "+e.statusText)}))},search:function(){const e=$(".search_input").val(),t=$(".search_results");t.html(""),e&&Promise.all(window.book.spine.spineItems.map(t=>t.load(window.book.load.bind(window.book)).then(t.find.bind(t,e)).finally(t.unload.bind(t)))).then(e=>Promise.resolve([].concat.apply([],e))).then((function(e){$.each(e,(function(e,o){const n=$("<a>").attr("href","#").html("<b class='pull-right'>"+window.book.locations.locationFromCfi(o.cfi)+"</b>"+o.excerpt).attr("data-cfi",o.cfi).attr("data-id",o.id).click((function(){window.book.rendition.display(n.attr("data-cfi"))}));t.append($("<li>").append(n))}))}))},Loader:{_res_data:[],init:function(){const e=["dist/app-libs.min.js","dist/reader_iframe.min.js","dist/reader_iframe.min.css"];for(let t=0;t<e.length;t++)fetch(e[t],{credentials:"same-origin"}).then((function(o){200==o.status?o.text().then((function(e){const t=new URL(o.url);t.searchParams.delete("ts"),Reader.Loader._res_data[t.toString()]=e})):console.warn("loader failed for resource",e[t],o)}));Reader.Loader.checkProgress(e,Reader.Loader._res_data,0)},checkProgress:function(e,t,o){console.log("check_resource_load",o,e.length,Object.keys(t).length,Reader,Reader.Loader),5!=o?e.length!=Object.keys(t).length?window.setTimeout((function(){Reader.Loader.checkProgress(e,t,o+1)}),250):Reader.initSecondStage():$(".loading_message").html("Unable to load resources.")}},Page:{_store_position:0,_last_position_sync:0,_pagination_stored:0,next:function(){Reader.Page._store_position=1,window.book.rendition.next(),"undefined"!=typeof EpubeApp?EpubeApp.showActionBar(!1):localforage.getItem("epube.keep-ui-visible").then((function(e){e||Reader.showUI(!1)}))},prev:function(){window.book.rendition.prev(),"undefined"!=typeof EpubeApp?EpubeApp.showActionBar(!1):localforage.getItem("epube.keep-ui-visible").then((function(e){e||Reader.showUI(!1)}))},openPrevious:function(e){const t=$(e).attr("data-location-cfi");t&&window.book.rendition.display(t),$(e).fadeOut()},clearLastRead:function(){if(confirm("Clear stored last read location?")){const e=window.book.locations.length(),t=(new Date).getTime();App.isOnline()&&$.post("backend.php",{op:"storelastread",page:-1,cfi:"",id:$.urlParam("id"),timestamp:t},(function(e){$(".lastread_input").val(e.page+"%")})),localforage.setItem(Reader.cacheId("lastread"),{cfi:"",page:0,total:e,timestamp:t}),window.setTimeout((function(){window.book.rendition.display(window.book.locations.cfiFromPercentage(0))}),250)}},openLastRead:function(e){localforage.getItem(Reader.cacheId("lastread")).then((function(t){console.log("lr local",t),t=t||{};try{t.cfi&&window.book.rendition.display(t.cfi).then(()=>{$(".loading").hide(),t.cfi&&window.book.rendition.display(t.cfi)})}catch(e){console.warn(e)}App.isOnline()&&!e&&$.post("backend.php",{op:"getlastread",id:$.urlParam("id")},(function(e){if(console.log("lr remote",e),App.isOnline()&&e)try{e.cfi&&t.cfi!=e.cfi&&e.timestamp>t.timestamp&&console.log("using remote lastread (timestamp is newer)"),localforage.setItem(Reader.cacheId("lastread"),{cfi:e.cfi,page:e.page,total:e.total,timestamp:e.timestamp}),window.book.rendition.display(e.cfi).then(()=>{window.book.rendition.display(e.cfi)})}catch(e){console.warn(e)}})).fail((function(e){e&&401==e.status&&(window.location="index.php")}))}))}},Settings:{onThemeChanged:function(e){const t=$(e).val();localforage.setItem("epube.theme",t).then((function(){Reader.applyTheme()}))},onLineHeightChanged:function(e){const t=$(e).val();localforage.setItem("epube.lineHeight",t).then((function(){Reader.applyStyles()}))},onTextSizeChanged:function(e){const t=$(e).val();localforage.setItem("epube.fontSize",t).then((function(){Reader.applyStyles()}))},onFontChanged:function(e){const t=$(e).val();localforage.setItem("epube.fontFamily",t).then((function(){Reader.applyStyles()}))}}};function __get_reader(){return Reader}function __get_app(){return App} \ No newline at end of file
diff --git a/include/common.php b/include/common.php
index 8f57b91..ee3921b 100644
--- a/include/common.php
+++ b/include/common.php
@@ -13,6 +13,10 @@
return sprintf(...$args);
}
+ function validate_csrf($csrf_token) {
+ return isset($csrf_token) && hash_equals($_SESSION['csrf_token'] ?? "", $csrf_token);
+ }
+
function sql_bool_to_bool($s) {
return $s && ($s !== "f" && $s !== "false"); //no-op for PDO, backwards compat for legacy layer
}
diff --git a/include/sessions.php b/include/sessions.php
index b39a983..b4d901b 100644
--- a/include/sessions.php
+++ b/include/sessions.php
@@ -35,6 +35,10 @@
setcookie(session_name(), '', time()-42000, '/');
}
+ if (isset($_COOKIE["epube_csrf_token"])) {
+ setcookie("epube_csrf_token", '', time()-42000, '/');
+ }
+
session_commit();
}
}
diff --git a/index.php b/index.php
index b2d5fef..d5b3b76 100644
--- a/index.php
+++ b/index.php
@@ -13,7 +13,7 @@
Config::sanity_check();
if (!validate_session()) {
- header("Location: logout.php");
+ header("Location: login.php");
exit;
}
@@ -24,6 +24,9 @@
exit;
}
+ setcookie("epube_csrf_token", $_SESSION["csrf_token"], time() + Config::get(Config::SESSION_LIFETIME),
+ "/", "", Config::is_server_https());
+
// TODO: this should be unified with the service worker cache list
$check_files_mtime = [
'manifest.json',
@@ -72,10 +75,10 @@
<link rel="manifest" href="manifest.json">
<meta name="mobile-web-app-capable" content="yes">
<script src="dist/app.min.js"></script>
- <script type="text/javascript">
- 'use strict';
+ <script type="text/javascript">
+ 'use strict';
- if ('serviceWorker' in navigator) {
+ if ('serviceWorker' in navigator) {
navigator.serviceWorker
.register('worker.js')
.then(function() {
@@ -141,7 +144,7 @@
<span class="glyphicon glyphicon-refresh"></span> <span class='hidden-sm hidden-md hidden-lg'>Refresh script cache</span></a></li>
</li>
<?php if ($mode !== "favorites") { ?>
- <li><a href="logout.php" title="Log out">
+ <li><a href="#" onclick="App.logout()" title="Log out">
<span class="glyphicon glyphicon-log-out"></span> <span class='hidden-sm hidden-md hidden-lg'>Log out</span>
</a></li>
<?php } ?>
diff --git a/js/app.js b/js/app.js
index f3e30bc..8608f89 100644
--- a/js/app.js
+++ b/js/app.js
@@ -1,508 +1,533 @@
'use strict';
-/* global localforage, EpubeApp */
+/* global localforage, EpubeApp, $ */
$.urlParam = function(name){
- try {
- const results = new RegExp('[\?&]' + name + '=([^&#]*)').exec(window.location.href);
- return decodeURIComponent(results[1].replace(/\+/g, " ")) || 0;
- } catch (e) {
- return 0;
- }
+ try {
+ const results = new RegExp('[\?&]' + name + '=([^&#]*)').exec(window.location.href);
+ return decodeURIComponent(results[1].replace(/\+/g, " ")) || 0;
+ } catch (e) {
+ return 0;
+ }
};
/* exported Cookie */
const Cookie = {
- set: function (name, value, lifetime) {
- const d = new Date();
- d.setTime(d.getTime() + lifetime * 1000);
- const expires = "expires=" + d.toUTCString();
- document.cookie = name + "=" + encodeURIComponent(value) + "; " + expires;
- },
- get: function (name) {
- name = name + "=";
- const ca = document.cookie.split(';');
- for (let i=0; i < ca.length; i++) {
- let c = ca[i];
- while (c.charAt(0) == ' ') c = c.substring(1);
- if (c.indexOf(name) == 0) return decodeURIComponent(c.substring(name.length, c.length));
- }
- return "";
- },
- delete: function(name) {
- const expires = "expires=Thu, 01-Jan-1970 00:00:01 GMT";
- document.cookie = name + "=; " + expires;
- }
+ set: function (name, value, lifetime) {
+ const d = new Date();
+ d.setTime(d.getTime() + lifetime * 1000);
+ const expires = "expires=" + d.toUTCString();
+ document.cookie = name + "=" + encodeURIComponent(value) + "; " + expires;
+ },
+ get: function (name) {
+ name = name + "=";
+ const ca = document.cookie.split(';');
+ for (let i=0; i < ca.length; i++) {
+ let c = ca[i];
+ while (c.charAt(0) == ' ') c = c.substring(1);
+ if (c.indexOf(name) == 0) return decodeURIComponent(c.substring(name.length, c.length));
+ }
+ return "";
+ },
+ delete: function(name) {
+ const expires = "expires=Thu, 01-Jan-1970 00:00:01 GMT";
+ document.cookie = name + "=; " + expires;
+ }
};
const App = {
- _dl_progress_timeout: false,
- index_mode: "",
- last_mtime: -1,
- init: function() {
- let refreshed_files = 0;
-
- if (typeof EpubeApp != "undefined") {
- $(".navbar").hide();
- $(".epube-app-filler").show();
- $(".separate-search").show();
-
- if ($.urlParam("mode") == "favorites")
- EpubeApp.setPage("PAGE_FAVORITES");
- else
- EpubeApp.setPage("PAGE_LIBRARY");
- }
-
- App.initNightMode();
-
- if ('serviceWorker' in navigator) {
- navigator.serviceWorker.addEventListener('message', function(event) {
-
- if (event.data == 'refresh-started') {
- console.log('cache refresh started');
- refreshed_files = 0;
-
- $(".dl-progress")
- .fadeIn()
- .text("Loading, please wait...");
- }
-
- if (event.data && event.data.indexOf("refreshed:") == 0) {
- ++refreshed_files;
-
- $(".dl-progress")
- .fadeIn()
- .text("Updated " + refreshed_files + " files...");
- }
-
- if (event.data == 'client-reload') {
- localforage.setItem("epube.cache-timestamp", App.last_mtime);
- window.location.reload()
- }
-
- });
- } else {
- $(".container-main")
- .addClass("alert alert-danger")
- .html("Service worker support missing in browser (are you using plain HTTP?).");
- }
-
- App.showCovers();
- App.Offline.markBooks();
- App.refreshCache();
-
- },
- showSummary: function(elem) {
- const id = elem.getAttribute("data-book-id");
-
- $.post("backend.php", {op: 'getinfo', id: id}, function(data) {
-
- const comment = data.comment ? data.comment : 'No description available';
-
- $("#summary-modal .modal-title").html(data.title);
- $("#summary-modal .book-summary").html(comment);
-
- $("#summary-modal").modal();
-
- });
-
- return false;
- },
- showCovers: function() {
- $("img[data-book-id]").each((i,e) => {
- e = $(e);
-
- if (e.attr('data-cover-link')) {
- const img = $("<img>")
- .on("load", function() {
- e.css("background-image", "url(" + e.attr('data-cover-link') + ")")
- .fadeIn();
-
- img.attr("src", null);
- })
- .attr("src", e.attr('data-cover-link'));
- } else {
- e.attr('src', 'holder.js/130x190?auto=yes').fadeIn();
- }
- });
-
- /* global Holder */
- Holder.run();
- },
- toggleFavorite: function(elem) {
- const bookId = elem.getAttribute("data-book-id");
-
- if (elem.getAttribute("data-is-fav") == "0" || confirm("Remove favorite?")) {
-
- $.post("backend.php", {op: "togglefav", id: bookId}, function(data) {
- if (data) {
- let msg = "[Error]";
-
- if (data.status == 0) {
- msg = "Add to favorites";
- } else if (data.status == 1) {
- msg = "Remove from favorites";
- }
-
- $(elem).html(msg).attr('data-is-fav', data.status);
-
- if (App.index_mode == "favorites" && data.status == 0) {
- $("#cell-" + bookId).remove();
- }
- }
- });
- }
-
- return false;
- },
- refreshCache: function(force) {
- if ('serviceWorker' in navigator) {
- localforage.getItem("epube.cache-timestamp").then(function(stamp) {
- console.log('stamp', stamp, 'last mtime', App.last_mtime);
-
- if (force || stamp != App.last_mtime) {
- console.log('asking worker to refresh cache');
-
- if (navigator.serviceWorker.controller) {
- navigator.serviceWorker.controller.postMessage("refresh-cache");
- } else {
- localforage.getItem("epube.initial-load-done").then(function(done) {
-
- console.log("initial load done", done);
-
- if (done) {
- $(".dl-progress")
- .show()
- .addClass("alert-danger")
- .html("Could not communicate with service worker. Try reloading the page.");
- } else {
- localforage.setItem("epube.initial-load-done", true).then(function() {
- $(".dl-progress")
- .show()
- .addClass("alert-info")
- .html("Page will reload to activate service worker...");
-
- window.setTimeout(function() {
- window.location.reload();
- }, 3*1000);
-
- });
- }
-
- });
- }
- }
- });
- } else {
- $(".dl-progress")
- .show()
- .addClass("alert-danger")
- .html("Could not communicate with service worker. Try reloading the page.");
- }
- },
- isOnline: function() {
- if (typeof EpubeApp != "undefined" && typeof EpubeApp.isOnline != "undefined")
- return EpubeApp.isOnline();
- else
- return navigator.onLine;
- },
- appCheckOffline: function() {
- EpubeApp.setOffline(!App.isOnline);
- },
- initNightMode: function() {
- if (typeof EpubeApp != "undefined") {
- App.applyNightMode(EpubeApp.isNightMode());
- return;
- }
-
- if (window.matchMedia) {
- const mql = window.matchMedia('(prefers-color-scheme: dark)');
-
- mql.addEventListener("change", () => {
- App.applyNightMode(mql.matches);
- });
-
- App.applyNightMode(mql.matches);
- }
- },
- applyNightMode: function(is_night) {
- console.log("night mode changed to", is_night);
-
- $("#theme_css").attr("href",
- "lib/bootstrap/v3/css/" + (is_night ? "theme-dark.min.css" : "bootstrap-theme.min.css"));
- },
- Offline: {
- init: function() {
- if (typeof EpubeApp != "undefined") {
- $(".navbar").hide();
- $(".epube-app-filler").show();
-
- EpubeApp.setPage("PAGE_OFFLINE");
- }
-
- App.initNightMode();
-
- const query = $.urlParam("query");
-
- if (query)
- $(".search_query").val(query);
-
- App.Offline.populateList();
- },
- get: function(bookId, callback) {
- console.log("offline cache: " + bookId);
-
- $.post("backend.php", {op: "getinfo", id: bookId}, function(data) {
-
- if (data) {
- const cacheId = 'epube-book.' + bookId;
-
- localforage.setItem(cacheId, data).then(function(data) {
-
- console.log(cacheId + ' got data');
-
- const promises = [];
-
- promises.push(fetch('backend.php?op=download&id=' + data.epub_id, {credentials: 'same-origin'}).then(function(resp) {
- if (resp.status == 200) {
- console.log(cacheId + ' got book');
-
- callback();
-
- localforage.setItem(cacheId + '.book', resp.blob());
- }
- }));
-
- promises.push(fetch("backend.php?op=getpagination&id=" + data.epub_id, {credentials: 'same-origin'}).then(function(resp) {
- if (resp.status == 200) {
- console.log(cacheId + ' got pagination');
-
- resp.text().then(function(text) {
- localforage.setItem(cacheId + '.locations', JSON.parse(text));
- });
- }
- }));
-
- promises.push(fetch("backend.php?op=getlastread&id=" + data.epub_id, {credentials: 'same-origin'}).then(function(resp) {
- if (resp.status == 200) {
- console.log(cacheId + ' got lastread');
- resp.text().then(function(text) {
- localforage.setItem(cacheId + '.lastread', JSON.parse(text));
- });
- }
- }));
-
- if (data.has_cover) {
-
- promises.push(fetch("backend.php?op=cover&id=" + bookId, {credentials: 'same-origin'}).then(function(resp) {
-
- if (resp.status == 200) {
- console.log(cacheId + ' got cover');
- localforage.setItem(cacheId + '.cover', resp.blob());
- }
-
- }));
-
- }
-
- Promise.all(promises).then(function() {
- $(".dl-progress")
- .show()
- .html("Finished downloading <b>" + data.title + "</b>");
-
- window.clearTimeout(App._dl_progress_timeout);
-
- App._dl_progress_timeout = window.setTimeout(function() {
- $(".dl-progress").fadeOut();
- }, 5*1000);
- });
- });
- }
- });
- },
- getAll: function() {
- if (confirm("Download all books on this page?")) {
-
- $(".row > div").each(function (i, row) {
- const bookId = $(row).attr("id").replace("cell-", "");
- const dropitem = $(row).find(".offline_dropitem")[0];
-
- if (bookId) {
-
- const cacheId = 'epube-book.' + bookId;
- localforage.getItem(cacheId).then(function(book) {
-
- if (!book) {
- App.Offline.get(bookId, function() {
- App.Offline.mark(dropitem);
- });
- }
-
- });
-
- }
- });
- }
- },
- markBooks: function() {
- const elems = $(".offline_dropitem");
-
- $.each(elems, function (i, elem) {
- App.Offline.mark(elem);
- });
- },
- mark: function(elem) {
- const bookId = elem.getAttribute("data-book-id");
- const cacheId = "epube-book." + bookId;
-
- localforage.getItem(cacheId).then(function(book) {
- if (book) {
- elem.onclick = function() {
- App.Offline.remove(bookId, function() {
- App.Offline.mark(elem);
- });
- return false;
- };
-
- elem.innerHTML = "Remove offline data";
-
- } else {
- elem.onclick = function() {
- App.Offline.get(bookId, function() {
- App.Offline.mark(elem);
- });
- return false;
- };
-
- elem.innerHTML = "Make available offline";
- }
- });
- },
-
- removeFromList: function(elem) {
- const bookId = elem.getAttribute("data-book-id");
-
- return App.Offline.remove(bookId, function() {
- $("#cell-" + bookId).remove();
- });
- },
- remove: function(id, callback) {
- if (confirm("Remove download?")) {
-
- const cacheId = "epube-book." + id;
- const promises = [];
-
- console.log("offline remove: " + id);
-
- localforage.iterate(function(value, key /*, i */) {
- if (key.match(cacheId)) {
- promises.push(localforage.removeItem(key));
- }
- });
-
- Promise.all(promises).then(function() {
- window.setTimeout(function() {
- callback();
- }, 500);
- });
- }
- },
- search: function() {
- const query = $(".search_query").val();
-
- localforage.setItem("epube.search-query", query).then(function() {
- App.Offline.populateList();
- });
-
- return false;
- },
- removeAll: function() {
- if (confirm("Remove all downloaded books?")) {
-
- const promises = [];
-
- localforage.iterate(function(value, key/*, i*/) {
-
- if (key.match("epube-book")) {
- promises.push(localforage.removeItem(key));
- }
- });
-
- Promise.all(promises).then(function() {
- window.setTimeout(function() {
- App.Offline.populateList();
- }, 500);
- });
- }
- },
- showSummary: function(elem) {
- const bookId = elem.getAttribute("data-book-id");
-
- localforage.getItem("epube-book." + bookId).then(function(data) {
-
- const comment = data.comment ? data.comment : 'No description available';
-
- $("#summary-modal .modal-title").html(data.title);
- $("#summary-modal .book-summary").html(comment);
-
- $("#summary-modal").modal();
-
- });
-
- return false;
- },
- populateList: function() {
- let query = $.urlParam("query");
+ _dl_progress_timeout: false,
+ index_mode: "",
+ last_mtime: -1,
+ csrf_token: "",
+ init: function() {
+ let refreshed_files = 0;
- if (query) query = query.toLowerCase();
-
- const books = $("#books_container");
- books.html("");
+ this.csrf_token = Cookie.get('epube_csrf_token');
+
+ console.log('setting prefilter for token', this.csrf_token);
+
+ $.ajaxPrefilter(function(options, originalOptions, jqXHR) {
+ if (originalOptions.type !== 'post' || options.type !== 'post') {
+ return;
+ }
+
+ const datatype = typeof originalOptions.data;
+
+ if (datatype == 'object')
+ options.data = $.param($.extend(originalOptions.data, {"csrf_token": App.csrf_token}));
+ else if (datatype == 'string')
+ options.data = originalOptions.data + "&csrf_token=" + encodeURIComponent(App.srf_token);
+
+ console.log('>>>', options);
+ });
+
+ if (typeof EpubeApp != "undefined") {
+ $(".navbar").hide();
+ $(".epube-app-filler").show();
+ $(".separate-search").show();
+
+ if ($.urlParam("mode") == "favorites")
+ EpubeApp.setPage("PAGE_FAVORITES");
+ else
+ EpubeApp.setPage("PAGE_LIBRARY");
+ }
+
+ App.initNightMode();
+
+ if ('serviceWorker' in navigator) {
+ navigator.serviceWorker.addEventListener('message', function(event) {
+
+ if (event.data == 'refresh-started') {
+ console.log('cache refresh started');
+ refreshed_files = 0;
+
+ $(".dl-progress")
+ .fadeIn()
+ .text("Loading, please wait...");
+ }
+
+ if (event.data && event.data.indexOf("refreshed:") == 0) {
+ ++refreshed_files;
+
+ $(".dl-progress")
+ .fadeIn()
+ .text("Updated " + refreshed_files + " files...");
+ }
+
+ if (event.data == 'client-reload') {
+ localforage.setItem("epube.cache-timestamp", App.last_mtime);
+ window.location.reload()
+ }
+
+ });
+ } else {
+ $(".container-main")
+ .addClass("alert alert-danger")
+ .html("Service worker support missing in browser (are you using plain HTTP?).");
+ }
+
+ App.showCovers();
+ App.Offline.markBooks();
+ App.refreshCache();
+
+ },
+ logout: function() {
+ $.post("backend.php", {op: "logout"}).then(() => {
+ window.location.reload();
+ });
+ },
+ showSummary: function(elem) {
+ const id = elem.getAttribute("data-book-id");
+
+ $.post("backend.php", {op: 'getinfo', id: id}, function(data) {
+
+ const comment = data.comment ? data.comment : 'No description available';
+
+ $("#summary-modal .modal-title").html(data.title);
+ $("#summary-modal .book-summary").html(comment);
+
+ $("#summary-modal").modal();
+
+ });
+
+ return false;
+ },
+ showCovers: function() {
+ $("img[data-book-id]").each((i,e) => {
+ e = $(e);
+
+ if (e.attr('data-cover-link')) {
+ const img = $("<img>")
+ .on("load", function() {
+ e.css("background-image", "url(" + e.attr('data-cover-link') + ")")
+ .fadeIn();
+
+ img.attr("src", null);
+ })
+ .attr("src", e.attr('data-cover-link'));
+ } else {
+ e.attr('src', 'holder.js/130x190?auto=yes').fadeIn();
+ }
+ });
+
+ /* global Holder */
+ Holder.run();
+ },
+ toggleFavorite: function(elem) {
+ const bookId = elem.getAttribute("data-book-id");
+
+ if (elem.getAttribute("data-is-fav") == "0" || confirm("Remove favorite?")) {
+
+ $.post("backend.php", {op: "togglefav", id: bookId}, function(data) {
+ if (data) {
+ let msg = "[Error]";
+
+ if (data.status == 0) {
+ msg = "Add to favorites";
+ } else if (data.status == 1) {
+ msg = "Remove from favorites";
+ }
+
+ $(elem).html(msg).attr('data-is-fav', data.status);
+
+ if (App.index_mode == "favorites" && data.status == 0) {
+ $("#cell-" + bookId).remove();
+ }
+ }
+ });
+ }
+
+ return false;
+ },
+ refreshCache: function(force) {
+ if ('serviceWorker' in navigator) {
+ localforage.getItem("epube.cache-timestamp").then(function(stamp) {
+ console.log('stamp', stamp, 'last mtime', App.last_mtime);
+
+ if (force || stamp != App.last_mtime) {
+ console.log('asking worker to refresh cache');
+
+ if (navigator.serviceWorker.controller) {
+ navigator.serviceWorker.controller.postMessage("refresh-cache");
+ } else {
+ localforage.getItem("epube.initial-load-done").then(function(done) {
+
+ console.log("initial load done", done);
+
+ if (done) {
+ $(".dl-progress")
+ .show()
+ .addClass("alert-danger")
+ .html("Could not communicate with service worker. Try reloading the page.");
+ } else {
+ localforage.setItem("epube.initial-load-done", true).then(function() {
+ $(".dl-progress")
+ .show()
+ .addClass("alert-info")
+ .html("Page will reload to activate service worker...");
+
+ window.setTimeout(function() {
+ window.location.reload();
+ }, 3*1000);
+
+ });
+ }
+
+ });
+ }
+ }
+ });
+ } else {
+ $(".dl-progress")
+ .show()
+ .addClass("alert-danger")
+ .html("Could not communicate with service worker. Try reloading the page.");
+ }
+ },
+ isOnline: function() {
+ if (typeof EpubeApp != "undefined" && typeof EpubeApp.isOnline != "undefined")
+ return EpubeApp.isOnline();
+ else
+ return navigator.onLine;
+ },
+ appCheckOffline: function() {
+ EpubeApp.setOffline(!App.isOnline);
+ },
+ initNightMode: function() {
+ if (typeof EpubeApp != "undefined") {
+ App.applyNightMode(EpubeApp.isNightMode());
+ return;
+ }
+
+ if (window.matchMedia) {
+ const mql = window.matchMedia('(prefers-color-scheme: dark)');
+
+ mql.addEventListener("change", () => {
+ App.applyNightMode(mql.matches);
+ });
+
+ App.applyNightMode(mql.matches);
+ }
+ },
+ applyNightMode: function(is_night) {
+ console.log("night mode changed to", is_night);
+
+ $("#theme_css").attr("href",
+ "lib/bootstrap/v3/css/" + (is_night ? "theme-dark.min.css" : "bootstrap-theme.min.css"));
+ },
+ Offline: {
+ init: function() {
+ if (typeof EpubeApp != "undefined") {
+ $(".navbar").hide();
+ $(".epube-app-filler").show();
+
+ EpubeApp.setPage("PAGE_OFFLINE");
+ }
+
+ App.initNightMode();
+
+ const query = $.urlParam("query");
+
+ if (query)
+ $(".search_query").val(query);
+
+ App.Offline.populateList();
+ },
+ get: function(bookId, callback) {
+ console.log("offline cache: " + bookId);
+
+ $.post("backend.php", {op: "getinfo", id: bookId}, function(data) {
+
+ if (data) {
+ const cacheId = 'epube-book.' + bookId;
+
+ localforage.setItem(cacheId, data).then(function(data) {
+
+ console.log(cacheId + ' got data');
+
+ const promises = [];
+
+ promises.push(fetch('backend.php?op=download&id=' + data.epub_id, {credentials: 'same-origin'}).then(function(resp) {
+ if (resp.status == 200) {
+ console.log(cacheId + ' got book');
+
+ callback();
+
+ localforage.setItem(cacheId + '.book', resp.blob());
+ }
+ }));
+
+ promises.push(fetch("backend.php?op=getpagination&id=" + data.epub_id, {credentials: 'same-origin'}).then(function(resp) {
+ if (resp.status == 200) {
+ console.log(cacheId + ' got pagination');
+
+ resp.text().then(function(text) {
+ localforage.setItem(cacheId + '.locations', JSON.parse(text));
+ });
+ }
+ }));
+
+ promises.push(fetch("backend.php?op=getlastread&id=" + data.epub_id, {credentials: 'same-origin'}).then(function(resp) {
+ if (resp.status == 200) {
+ console.log(cacheId + ' got lastread');
+ resp.text().then(function(text) {
+ localforage.setItem(cacheId + '.lastread', JSON.parse(text));
+ });
+ }
+ }));
+
+ if (data.has_cover) {
+
+ promises.push(fetch("backend.php?op=cover&id=" + bookId, {credentials: 'same-origin'}).then(function(resp) {
+
+ if (resp.status == 200) {
+ console.log(cacheId + ' got cover');
+ localforage.setItem(cacheId + '.cover', resp.blob());
+ }
+
+ }));
+
+ }
+
+ Promise.all(promises).then(function() {
+ $(".dl-progress")
+ .show()
+ .html("Finished downloading <b>" + data.title + "</b>");
+
+ window.clearTimeout(App._dl_progress_timeout);
+
+ App._dl_progress_timeout = window.setTimeout(function() {
+ $(".dl-progress").fadeOut();
+ }, 5*1000);
+ });
+ });
+ }
+ });
+ },
+ getAll: function() {
+ if (confirm("Download all books on this page?")) {
+
+ $(".row > div").each(function (i, row) {
+ const bookId = $(row).attr("id").replace("cell-", "");
+ const dropitem = $(row).find(".offline_dropitem")[0];
+
+ if (bookId) {
+
+ const cacheId = 'epube-book.' + bookId;
+ localforage.getItem(cacheId).then(function(book) {
+
+ if (!book) {
+ App.Offline.get(bookId, function() {
+ App.Offline.mark(dropitem);
+ });
+ }
+
+ });
+
+ }
+ });
+ }
+ },
+ markBooks: function() {
+ const elems = $(".offline_dropitem");
+
+ $.each(elems, function (i, elem) {
+ App.Offline.mark(elem);
+ });
+ },
+ mark: function(elem) {
+ const bookId = elem.getAttribute("data-book-id");
+ const cacheId = "epube-book." + bookId;
+
+ localforage.getItem(cacheId).then(function(book) {
+ if (book) {
+ elem.onclick = function() {
+ App.Offline.remove(bookId, function() {
+ App.Offline.mark(elem);
+ });
+ return false;
+ };
+
+ elem.innerHTML = "Remove offline data";
+
+ } else {
+ elem.onclick = function() {
+ App.Offline.get(bookId, function() {
+ App.Offline.mark(elem);
+ });
+ return false;
+ };
+
+ elem.innerHTML = "Make available offline";
+ }
+ });
+ },
+
+ removeFromList: function(elem) {
+ const bookId = elem.getAttribute("data-book-id");
+
+ return App.Offline.remove(bookId, function() {
+ $("#cell-" + bookId).remove();
+ });
+ },
+ remove: function(id, callback) {
+ if (confirm("Remove download?")) {
+
+ const cacheId = "epube-book." + id;
+ const promises = [];
+
+ console.log("offline remove: " + id);
+
+ localforage.iterate(function(value, key /*, i */) {
+ if (key.match(cacheId)) {
+ promises.push(localforage.removeItem(key));
+ }
+ });
+
+ Promise.all(promises).then(function() {
+ window.setTimeout(function() {
+ callback();
+ }, 500);
+ });
+ }
+ },
+ search: function() {
+ const query = $(".search_query").val();
+
+ localforage.setItem("epube.search-query", query).then(function() {
+ App.Offline.populateList();
+ });
+
+ return false;
+ },
+ removeAll: function() {
+ if (confirm("Remove all downloaded books?")) {
+
+ const promises = [];
+
+ localforage.iterate(function(value, key/*, i*/) {
+
+ if (key.match("epube-book")) {
+ promises.push(localforage.removeItem(key));
+ }
+ });
+
+ Promise.all(promises).then(function() {
+ window.setTimeout(function() {
+ App.Offline.populateList();
+ }, 500);
+ });
+ }
+ },
+ showSummary: function(elem) {
+ const bookId = elem.getAttribute("data-book-id");
+
+ localforage.getItem("epube-book." + bookId).then(function(data) {
+
+ const comment = data.comment ? data.comment : 'No description available';
+
+ $("#summary-modal .modal-title").html(data.title);
+ $("#summary-modal .book-summary").html(comment);
+
+ $("#summary-modal").modal();
+
+ });
+
+ return false;
+ },
+ populateList: function() {
+ let query = $.urlParam("query");
- localforage.iterate(function(value, key/*, i*/) {
- if (key.match(/epube-book\.\d{1,}$/)) {
+ if (query) query = query.toLowerCase();
+
+ const books = $("#books_container");
+ books.html("");
- Promise.all([
- localforage.getItem(key),
- localforage.getItem(key + ".cover"),
- localforage.getItem(key + ".lastread"),
- localforage.getItem(key + ".book")
- ]).then(function(results) {
+ localforage.iterate(function(value, key/*, i*/) {
+ if (key.match(/epube-book\.\d{1,}$/)) {
- if (results[0] && results[3]) {
- const info = results[0];
+ Promise.all([
+ localforage.getItem(key),
+ localforage.getItem(key + ".cover"),
+ localforage.getItem(key + ".lastread"),
+ localforage.getItem(key + ".book")
+ ]).then(function(results) {
- if (query) {
- const match =
- (info.series_name && info.series_name.toLowerCase().match(query)) ||
- (info.title && info.title.toLowerCase().match(query)) ||
- (info.author_sort && info.author_sort.toLowerCase().match(query));
+ if (results[0] && results[3]) {
+ const info = results[0];
- if (!match) return;
- }
+ if (query) {
+ const match =
+ (info.series_name && info.series_name.toLowerCase().match(query)) ||
+ (info.title && info.title.toLowerCase().match(query)) ||
+ (info.author_sort && info.author_sort.toLowerCase().match(query));
+ if (!match) return;
+ }
- let cover = false;
- if (results && results[1]) {
- cover = URL.createObjectURL(results[1]);
- }
+ let cover = false;
- let in_progress = false;
- let is_read = false;
+ if (results && results[1]) {
+ cover = URL.createObjectURL(results[1]);
+ }
- const lastread = results[2];
- if (lastread) {
- in_progress = lastread.page > 0;
- is_read = lastread.total > 0 && lastread.total - lastread.page < 5;
- }
+ let in_progress = false;
+ let is_read = false;
- const thumb_class = is_read ? "read" : "";
- const title_class = in_progress ? "in_progress" : "";
+ const lastread = results[2];
+ if (lastread) {
+ in_progress = lastread.page > 0;
+ is_read = lastread.total > 0 && lastread.total - lastread.page < 5;
+ }
- const series_link = info.series_name ? `<div><a class="series_link" href="#">${info.series_name + " [" + info.series_index + "]"}</a></div>` : "";
+ const thumb_class = is_read ? "read" : "";
+ const title_class = in_progress ? "in_progress" : "";
- const cell = $(`<div class="col-xxs-6 col-xs-4 col-sm-3 col-md-2" id="cell-${info.id}">
+ const series_link = info.series_name ? `<div><a class="series_link" href="#">${info.series_name + " [" + info.series_index + "]"}</a></div>` : "";
+
+ const cell = $(`<div class="col-xxs-6 col-xs-4 col-sm-3 col-md-2" id="cell-${info.id}">
<a class="thumbnail ${thumb_class}" href="read.html?id=${info.epub_id}&b=${info.id}">
<img style="display : none">
</a>
@@ -520,31 +545,31 @@ const App = {
</div>
</div>`);
- if (cover) {
- cell.find("img")
- .css("background-image", "url(" + cover + ")")
- .fadeIn();
- } else {
- cell
- .find("img").attr("data-src", 'holder.js/130x190?auto=yes')
- .fadeIn();
- }
-
- cell.find(".series_link")
- .attr("title", info.series_name + " [" + info.series_index + "]")
- .attr("href", "offline.html?query=" + encodeURIComponent(info.series_name));
-
- cell.find(".author_link")
- .attr("title", info.author_sort)
- .attr("href", "offline.html?query=" + encodeURIComponent(info.author_sort));
-
- books.append(cell);
-
- Holder.run();
- }
- });
- }
- });
- }
- },
+ if (cover) {
+ cell.find("img")
+ .css("background-image", "url(" + cover + ")")
+ .fadeIn();
+ } else {
+ cell
+ .find("img").attr("data-src", 'holder.js/130x190?auto=yes')
+ .fadeIn();
+ }
+
+ cell.find(".series_link")
+ .attr("title", info.series_name + " [" + info.series_index + "]")
+ .attr("href", "offline.html?query=" + encodeURIComponent(info.series_name));
+
+ cell.find(".author_link")
+ .attr("title", info.author_sort)
+ .attr("href", "offline.html?query=" + encodeURIComponent(info.author_sort));
+
+ books.append(cell);
+
+ Holder.run();
+ }
+ });
+ }
+ });
+ }
+ },
};
diff --git a/js/reader.js b/js/reader.js
index 70807a9..2dfa4ea 100644
--- a/js/reader.js
+++ b/js/reader.js
@@ -1,6 +1,6 @@
'use strict';
-/* global localforage, EpubeApp, App */
+/* global localforage, EpubeApp, App, Cookie, $ */
const DEFAULT_FONT_SIZE = 16;
const DEFAULT_FONT_FAMILY = "Georgia";
@@ -10,7 +10,28 @@ const MIN_LENGTH_TO_JUSTIFY = 32; /* characters */
const PAGE_RESET_PROGRESS = -1;
const Reader = {
+ csrf_token: "",
init: function() {
+ this.csrf_token = Cookie.get('epube_csrf_token');
+
+ console.log('setting prefilter for token', this.csrf_token);
+
+ $.ajaxPrefilter(function(options, originalOptions/*, jqXHR*/) {
+
+ if (originalOptions.type !== 'post' || options.type !== 'post') {
+ return;
+ }
+
+ const datatype = typeof originalOptions.data;
+
+ if (datatype == 'object')
+ options.data = $.param($.extend(originalOptions.data, {"csrf_token": Reader.csrf_token}));
+ else if (datatype == 'string')
+ options.data = originalOptions.data + "&csrf_token=" + encodeURIComponent(Reader.srf_token);
+
+ console.log('>>>', options);
+ });
+
$(document).on("keyup", function(e) {
Reader.hotkeyHandler(e);
});
@@ -492,7 +513,7 @@ const Reader = {
book.spine.hooks.content.register(function(doc/*, section */) {
$(doc).find("p")
- .filter((i, e) => { if ($(e).text().length >= MIN_LENGTH_TO_JUSTIFY) return e; })
+ .filter((i, e) => (($(e).text().length >= MIN_LENGTH_TO_JUSTIFY) ? e : null))
.css("text-align", "justify");
$(doc).find("a, p, span, em, i, strong, b, body, div, big, small")
diff --git a/login.php b/login.php
index 76f7a7f..37bed4b 100644
--- a/login.php
+++ b/login.php
@@ -28,6 +28,7 @@
$_SESSION["owner"] = $username;
$_SESSION["pass_hash"] = sha1($user->pass);
+ $_SESSION["csrf_token"] = bin2hex(random_bytes(16));
header("Location: index.php");
exit;
@@ -35,6 +36,8 @@
} else {
$login_notice = "Incorrect username or password";
}
+ } else {
+ logout_user();
}
?>
<!DOCTYPE html>
diff --git a/logout.php b/logout.php
deleted file mode 100644
index 214699e..0000000
--- a/logout.php
+++ /dev/null
@@ -1,12 +0,0 @@
-<?php
- set_include_path(__DIR__ ."/include" . PATH_SEPARATOR .
- get_include_path());
-
- require_once "common.php";
- require_once "sessions.php";
-
- Config::sanity_check();
-
- logout_user();
-
- header("Location: login.php"); \ No newline at end of file