diff options
author | Andrew Dolgov <[email protected]> | 2021-03-14 14:17:18 +0300 |
---|---|---|
committer | Andrew Dolgov <[email protected]> | 2021-03-14 14:17:18 +0300 |
commit | 1c9afba5f058adace5e005b51fc533882f9af0fa (patch) | |
tree | 99f4a228acf9f969d5411048c210338ba0fd126a | |
parent | 2b8b845abe7c13ecbb266613910484310cffe8e1 (diff) |
* add CSRF protection to xhr requests
* force ORM to use SQLITE WAL
* add .editorconfig
* cleanup a few things
-rw-r--r-- | .editorconfig | 9 | ||||
-rw-r--r-- | backend.php | 21 | ||||
-rw-r--r-- | classes/db.php | 15 | ||||
-rw-r--r-- | dist/app.min.js | 2 | ||||
-rw-r--r-- | include/common.php | 4 | ||||
-rw-r--r-- | include/sessions.php | 4 | ||||
-rw-r--r-- | index.php | 13 | ||||
-rw-r--r-- | js/app.js | 1039 | ||||
-rw-r--r-- | js/reader.js | 25 | ||||
-rw-r--r-- | login.php | 3 | ||||
-rw-r--r-- | logout.php | 12 |
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(" "+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(" "+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(); } } @@ -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 } ?> @@ -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") @@ -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 |