From 8dfbb5dd44b81e1282b5c3ccfd1c8a0e1a1f95a2 Mon Sep 17 00:00:00 2001 From: Patrick G Date: Thu, 10 Aug 2017 16:33:04 -0400 Subject: [PATCH] Updated ePubViewer with search feature and improved TOC --- README.md | 1 + static/reader/epub/index.html | 17 +++ static/reader/epub/script.js | 208 +++++++++++++++++++++++++--------- static/reader/epub/style.css | 104 +++++++++++++++-- 4 files changed, 265 insertions(+), 65 deletions(-) diff --git a/README.md b/README.md index 38c61995..77904e4c 100644 --- a/README.md +++ b/README.md @@ -24,6 +24,7 @@ A easy-to-use tool to generate a web-based ePub and PDF ebook browser. All you n - Web based reader - Custom fonts, colors, sizing, spacing - Remembers your position + - Book search - And more - Search - And more diff --git a/static/reader/epub/index.html b/static/reader/epub/index.html index 028419e5..fdf935b2 100644 --- a/static/reader/epub/index.html +++ b/static/reader/epub/index.html @@ -25,6 +25,9 @@ info + + search +
@@ -185,6 +188,20 @@
+
diff --git a/static/reader/epub/script.js b/static/reader/epub/script.js index 732c374e..be875b1f 100644 --- a/static/reader/epub/script.js +++ b/static/reader/epub/script.js @@ -4,7 +4,7 @@ e.style.display = fw; e.style.display = f; c += (e.style.display == f || e.style.display == fw) ? f : "no-"+f; - } catch(e) { + } catch(ex) { c += "no-"+f; } d.documentElement.className += c; @@ -90,7 +90,7 @@ ePubViewer.settings = { "line-height": "1.5", "font-size": "11pt", "margin": "5%" -} +}; ePubViewer.elements = {}; ePubViewer.events = {}; ePubViewer.functions = {}; @@ -129,7 +129,7 @@ ePubViewer.functions.updateIndicators = function (message) { ePubViewer.state["current-cfi"] = ePubViewer.state.book.getCurrentLocationCfi(); } catch (e) {} try { - ePubViewer.state["current-chapter"] = ePubViewer.state.book.toc[ePubViewer.state.book.spinePos].label + ePubViewer.state["current-chapter"] = ePubViewer.state.book.toc[ePubViewer.state.book.spinePos].label; } catch (e) {} try { document.title = ePubViewer.state["book-title"] + " - " + ePubViewer.state["book-author"]; @@ -141,41 +141,43 @@ ePubViewer.functions.updateIndicators = function (message) { } var els = document.querySelectorAll("[data-text]"); - for (var i = 0; i < els.length; i++) { + var i = 0; + var nv = null; + for (i = 0; i < els.length; i++) { try { - var nv = ePubViewer.state[els[i].getAttribute("data-text")]; + nv = ePubViewer.state[els[i].getAttribute("data-text")]; if (els[i].innerHTML != nv) { els[i].innerHTML = nv; } } catch (e) {} } - var els = document.querySelectorAll("[data-href]"); - for (var i = 0; i < els.length; i++) { + els = document.querySelectorAll("[data-href]"); + for (i = 0; i < els.length; i++) { try { - var nv = ePubViewer.state[els[i].getAttribute("data-href")]; + nv = ePubViewer.state[els[i].getAttribute("data-href")]; if (els[i].href != nv) { els[i].href = nv; } } catch (e) {} } - var els = document.querySelectorAll("[data-src]"); - for (var i = 0; i < els.length; i++) { + els = document.querySelectorAll("[data-src]"); + for (i = 0; i < els.length; i++) { try { - var nv = ePubViewer.state[els[i].getAttribute("data-src")]; + nv = ePubViewer.state[els[i].getAttribute("data-src")]; if (els[i].src != nv) { els[i].src = nv; } } catch (e) {} } - var els = document.querySelectorAll("[data-if]"); - for (var i = 0; i < els.length; i++) { + els = document.querySelectorAll("[data-if]"); + for (i = 0; i < els.length; i++) { try { - var e = ePubViewer.state[els[i].getAttribute("data-if")]; - if (e) { - if (e != "" && e != 0 && e != false) { + var ea = ePubViewer.state[els[i].getAttribute("data-if")]; + if (ea) { + if (ea != "" && ea != 0 && ea != false) { els[i].classList.remove("hidden"); } else { els[i].classList.add("hidden"); @@ -183,15 +185,15 @@ ePubViewer.functions.updateIndicators = function (message) { } else { els[i].classList.add("hidden"); } - } catch (e) {} + } catch (ex) {} } - var els = document.querySelectorAll("[data-if-not]"); - for (var i = 0; i < els.length; i++) { + els = document.querySelectorAll("[data-if-not]"); + for (i = 0; i < els.length; i++) { try { - var e = ePubViewer.state[els[i].getAttribute("data-if-not")]; - if (e) { - if (e != "" && e != 0 && e != false) { + var eb = ePubViewer.state[els[i].getAttribute("data-if-not")]; + if (eb) { + if (eb != "" && eb != 0 && eb != false) { els[i].classList.add("hidden"); } else { els[i].classList.remove("hidden"); @@ -199,12 +201,12 @@ ePubViewer.functions.updateIndicators = function (message) { } else { els[i].classList.remove("hidden"); } - } catch (e) {} + } catch (ex) {} } }; ePubViewer.functions.loadSettings = function () { - for (k in ePubViewer.settings) { + for (var k in ePubViewer.settings) { if (ePubViewer.settings.hasOwnProperty(k)) { var v = localStorage.getItem("ePubViewer|settings|" + k); if (v !== null) { @@ -223,7 +225,7 @@ ePubViewer.functions.loadSettings = function () { }; ePubViewer.functions.updateSettingsFromSelectors = function () { - for (k in ePubViewer.settings) { + for (var k in ePubViewer.settings) { if (ePubViewer.settings.hasOwnProperty(k)) { var el = document.querySelector(".reader [data-setting=" + k + "]"); if (el) { @@ -243,10 +245,10 @@ ePubViewer.functions.updateSettingsFromSelectors = function () { }; ePubViewer.functions.saveSettings = function () { - for (k in ePubViewer.settings) { + for (var k in ePubViewer.settings) { if (ePubViewer.settings.hasOwnProperty(k)) { var v = ePubViewer.settings[k]; - localStorage.setItem("ePubViewer|settings|" + k, v) + localStorage.setItem("ePubViewer|settings|" + k, v); console.log("Saved setting: ", k, v); } } @@ -257,7 +259,7 @@ ePubViewer.functions.applySettings = function () { var theme = ePubViewer.themes[ePubViewer.settings.theme] || ePubViewer.themes.SepiaLight; try { - if (theme["light"]) { + if (theme.light) { document.body.classList.remove("dark"); document.body.classList.add("light"); } else { @@ -276,7 +278,7 @@ ePubViewer.functions.applySettings = function () { "html, body {", "font-family: " + font["font-family"] + ";", "font-size: " + ePubViewer.settings["font-size"] + ";", - "color: " + theme["color"] + " !important;", + "color: " + theme.color + " !important;", "background-color: " + theme["background-color"] + " !important;", "line-height: " + ePubViewer.settings["line-height"] + " !important;", "}", @@ -286,9 +288,12 @@ ePubViewer.functions.applySettings = function () { "}" ].join("\n"); if (font.link) { - var el = doc.body.appendChild(doc.createElement("link")); + if (doc.getElementById("ePubViewerFontLink") === null) { + doc.body.appendChild(doc.createElement("link")).id = "ePubViewerFontLink"; + } + var el = document.getElementById("ePubViewerFontLink"); el.setAttribute("rel", "stylesheet"); - el.setAttribute("href", font.link) + el.setAttribute("href", font.link); } } catch (e) {} @@ -299,28 +304,31 @@ ePubViewer.functions.applySettings = function () { styleEla.innerHTML = [ ".reader {", "font-family: " + font["font-family"] + ";", - "color: " + theme["color"] + ";", + "color: " + theme.color + ";", "background-color: " + theme["background-color"] + ";", "}", ".reader .main .content {", - "margin: 0 " + ePubViewer.settings["margin"] + ";", + "margin: 5px " + ePubViewer.settings.margin + ";", "}" ].join("\n"); if (font.link) { - var el = document.body.appendChild(document.createElement("link")); - el.setAttribute("rel", "stylesheet"); - el.setAttribute("href", font.link) + if (document.getElementById("ePubViewerAppFontLink") === null) { + document.body.appendChild(document.createElement("link")).id = "ePubViewerAppFontLink"; + } + var ela = document.getElementById("ePubViewerAppFontLink"); + ela.setAttribute("rel", "stylesheet"); + ela.setAttribute("href", font.link); } }; ePubViewer.functions.getCoverURL = function (callback) { ePubViewer.state.book.coverUrl().then(function (blobUrl) { console.log(blobUrl); - var xhr = new XMLHttpRequest; + var xhr = new XMLHttpRequest(); xhr.responseType = 'blob'; xhr.onload = function () { var recoveredBlob = xhr.response; - var reader = new FileReader; + var reader = new FileReader(); reader.onload = function () { callback(reader.result); }; @@ -329,10 +337,10 @@ ePubViewer.functions.getCoverURL = function (callback) { xhr.open('GET', blobUrl); xhr.send(); }); -} +}; ePubViewer.actions = {}; ePubViewer.actions.settingsReset = function () { - for (k in ePubViewer.settings) { + for (var k in ePubViewer.settings) { if (ePubViewer.settings.hasOwnProperty(k)) { try { delete localStorage["ePubViewer|settings|" + k]; @@ -386,7 +394,73 @@ ePubViewer.actions.gotoChapter = function (chapter) { ePubViewer.state.book.gotoHref(chapter); } }; +ePubViewer.actions.doSearch = function(q) { + return new Promise(function(resolve, reject) { + var r = ePubViewer.elements.searchResults; + + r.innerHTML = ""; + + var resultPromises = []; + + q = q.replace(/^\s+|\s+$/g,''); + + if (q.length < 3) { + r.innerHTML = 'Please enter at least 3 characters'; + resolve([]); + return; + } + + for (var i = 0; i < ePubViewer.state.book.spine.length; i++) { + var spineItem = ePubViewer.state.book.spine[i]; + resultPromises.push(new Promise(function(resolve, reject) { + new Promise(function(resolve, reject) { + resolve(new EPUBJS.Chapter(spineItem, ePubViewer.state.book.store, ePubViewer.state.book.credentials)); + }).then(function(chapter) { + return new Promise(function(resolve, reject) { + chapter.load().then(function() { + resolve(chapter); + }).catch(reject); + }); + }).then(function(chapter) { + return Promise.resolve(chapter.find(q)); + }).then(function(result) { + resolve(result); + }); + })); + } + Promise.all(resultPromises).then(function(results) { + return new Promise(function(resolve, reject) { + resolve(results); + var mergedResults = [].concat.apply([], results); + console.log(mergedResults); + var max = mergedResults.length; + max = max > 100 ? 100 : max; + var fragment = document.createDocumentFragment() + for (var i = 0; i < max; i++) { + try { + var er = document.createElement("a"); + er.classList.add("result"); + er.href = "javascript:void(0);"; + er.addEventListener("click", function() { + console.log(this.getAttribute("data-location")); + ePubViewer.state.book.goto(this.getAttribute("data-location")); + }); + er.setAttribute("data-location", mergedResults[i].cfi); + er.innerHTML = mergedResults[i].excerpt; + fragment.appendChild(er); + } catch (e) { + console.warn(e); + } + } + r.appendChild(fragment); + }); + }); + }); +}; ePubViewer.actions.loadBook = function (urlOrArrayBuffer) { + ePubViewer.actions.clearSearch(); + ePubViewer.elements.tocList.innerHTML = ""; + ePubViewer.elements.content.innerHTML = ""; ePubViewer.state.book = ePub({ "storage": false @@ -416,11 +490,11 @@ ePubViewer.actions.loadBook = function (urlOrArrayBuffer) { var curpostmp = localStorage.getItem("ePubViewer|" + ePubViewer.state["book-id"] + "|curPosCfi"); if (curpostmp) { - ePubViewer.state.book.goto(curpostmp) + ePubViewer.state.book.goto(curpostmp); } ePubViewer.state.book.on('renderer:locationChanged', function (locationCfi) { - localStorage.setItem("ePubViewer|" + ePubViewer.state["book-id"] + "|curPosCfi", ePubViewer.state.book.getCurrentLocationCfi()) + localStorage.setItem("ePubViewer|" + ePubViewer.state["book-id"] + "|curPosCfi", ePubViewer.state.book.getCurrentLocationCfi()); }); ePubViewer.state.book.locations.generate().then(function () { @@ -442,12 +516,13 @@ ePubViewer.actions.loadBook = function (urlOrArrayBuffer) { ePubViewer.state.book.getToc().then(function (toc) { ePubViewer.state.toc = toc; - var containerel = document.querySelector(".reader .toc"); + var containerel = ePubViewer.elements.tocList; + containerel.innerHTML = ""; for (var i = 0; i < toc.length; i++) { - console.log(toc[i]) + console.log(toc[i]); var entryel = document.createElement("a"); entryel.classList.add("toc-entry"); - entryel.innerText = toc[i].label; + entryel.innerHTML = toc[i].label; entryel.setAttribute("data-cfi", toc[i].href); entryel.href = "javascript:void(0);"; entryel.onclick = function (e) { @@ -456,16 +531,30 @@ ePubViewer.actions.loadBook = function (urlOrArrayBuffer) { containerel.appendChild(entryel); if (toc[i].subitems) { for (var j = 0; j < toc[i].subitems.length; j++) { - var entryel = document.createElement("a"); - entryel.classList.add("toc-entry"); - entryel.style.paddingLeft = "20px"; - entryel.innerText = toc[i].subitems[j].label; - entryel.setAttribute("data-cfi", toc[i].subitems[j].href); - entryel.href = "javascript:void(0);"; - entryel.onclick = function (e) { + var entryela = document.createElement("a"); + entryela.classList.add("toc-entry"); + entryela.style.paddingLeft = "20px"; + entryela.innerHTML = toc[i].subitems[j].label; + entryela.setAttribute("data-cfi", toc[i].subitems[j].href); + entryela.href = "javascript:void(0);"; + entryela.onclick = function (e) { ePubViewer.actions.gotoChapter(e.target.getAttribute("data-cfi")); }; - containerel.appendChild(entryel); + containerel.appendChild(entryela); + if (toc[i].subitems[j].subitems) { + for (var k = 0; k < toc[i].subitems[j].subitems.length; k++) { + var entryelb = document.createElement("a"); + entryelb.classList.add("toc-entry"); + entryelb.style.paddingLeft = "40px"; + entryelb.innerHTML = toc[i].subitems[j].subitems[k].label; + entryelb.setAttribute("data-cfi", toc[i].subitems[j].subitems[k].href); + entryelb.href = "javascript:void(0);"; + entryelb.onclick = function (e) { + ePubViewer.actions.gotoChapter(e.target.getAttribute("data-cfi")); + }; + containerel.appendChild(entryelb); + } + } } } } @@ -481,7 +570,7 @@ ePubViewer.actions.loadBook = function (urlOrArrayBuffer) { toclist[e].classList.remove("active"); } } - } catch (e) {} + } catch (ex) {} }); ePubViewer.functions.applySettings(); @@ -491,6 +580,12 @@ ePubViewer.actions.loadBook = function (urlOrArrayBuffer) { ePubViewer.functions.updateIndicators(); ePubViewer.state.book.renderTo(ePubViewer.elements.content); }; +ePubViewer.actions.handleSearch = function() { + ePubViewer.actions.doSearch(ePubViewer.elements.searchBox.value); +}; +ePubViewer.actions.clearSearch = function() { + ePubViewer.elements.searchResults.innerHTML = ""; +}; ePubViewer.actions.openBook = function () { var fi = document.createElement("input"); fi.accept = "application/epub+zip"; @@ -508,7 +603,7 @@ ePubViewer.actions.openBook = function () { if (header == "504b") { ePubViewer.actions.loadBook(reader.result); } else { - ePubViewer.functions.showFatalError("The book you chose is not a valid epub book. Please try again.") + ePubViewer.functions.showFatalError("The book you chose is not a valid epub book. Please try again."); } }, false); if (fi.files[0]) { @@ -538,6 +633,9 @@ ePubViewer.actions.fullScreen = function () { ePubViewer.init = function () { ePubViewer.elements.content = document.querySelector(".reader .content"); ePubViewer.elements.openButton = document.querySelector(".reader .header .open-button"); + ePubViewer.elements.searchResults = document.querySelector(".reader .search-results"); + ePubViewer.elements.searchBox = document.querySelector('.reader .search-box'); + ePubViewer.elements.tocList = document.querySelector(".reader .toc"); EPUBJS.Hooks.register('beforeChapterDisplay').swipeDetection = function (callback, renderer) { function detectSwipe() { diff --git a/static/reader/epub/style.css b/static/reader/epub/style.css index 04e663c1..8dfc5df4 100644 --- a/static/reader/epub/style.css +++ b/static/reader/epub/style.css @@ -167,6 +167,7 @@ max-width: 250px; overflow-y: auto; border-right: 1px solid rgba(0,0,0,0.1); + font-size: 14px; } .reader .main .sidebar .sidebar-inner { @@ -194,13 +195,15 @@ .reader .main .sidebar .sidebar-header .x { display: block; float: right; - font-size: 130%; + font-size: 100%; background: rgba(0, 0, 0, 0.001); + color: inherit; } .reader .main .sidebar .toc-sidebar, .reader .main .sidebar .info-sidebar, -.reader .main .sidebar .settings-sidebar { +.reader .main .sidebar .settings-sidebar, +.reader .main .sidebar .search-sidebar { display: none; min-width: 200px; } @@ -214,12 +217,12 @@ .reader .main .sidebar .toc-sidebar .toc .toc-entry:visited { display: block; white-space: nowrap; - width: 100%; text-overflow: ellipsis; - padding: 3px; color: inherit; text-decoration: none; - overflow-x: hidden; + overflow: hidden; + padding: 7px 3px; + margin-right: 3px; } .reader .main .sidebar .toc-sidebar .toc .toc-entry:hover { @@ -265,6 +268,62 @@ margin: 3px auto; } +.reader .main .sidebar .search-sidebar .search-bar { + display: flex; +} + +.reader .main .sidebar .search-sidebar .search-bar .search-box, +.reader .main .sidebar .search-sidebar .search-bar button { + border-radius: 0 !important; + padding: 3px !important; + border-color: rgba(0,0,0,0.3); + outline: 0; +} + +.reader .main .sidebar .search-sidebar .search-bar .search-box { + flex: 1; +} +.reader .main .sidebar .search-sidebar .search-bar button { + flex: 0 0 30px; +} + +.reader .main .sidebar .search-sidebar .search-results { + display: block; +} + +.reader .main .sidebar .search-sidebar .search-results a.result, +.reader .main .sidebar .search-sidebar .search-results a.result:link, +.reader .main .sidebar .search-sidebar .search-results a.result:visited { + display: block; + text-decoration: none; + color: inherit; + padding: 14px; + border-bottom: 1px solid rgba(0,0,0,0.1); + font-size: 85%; +} + +.reader .main .sidebar .search-sidebar .search-results a.result:hover { + background: rgba(0,0,0,0.05); +} + +.reader .main .sidebar .search-sidebar .search-results a.result:active { + background: rgba(0,0,0,0.10); +} + +body.dark .reader .main .sidebar .search-sidebar .search-results a.result, +body.dark .reader .main .sidebar .search-sidebar .search-results a.result:link, +body.dark .reader .main .sidebar .search-sidebar .search-results a.result:visited { + border-bottom: 1px solid rgba(255,255,255,0.1); +} + +body.dark .reader .main .sidebar .search-sidebar .search-results a.result:hover { + background: rgba(255,255,255,0.05); +} + +body.dark .reader .main .sidebar .search-sidebar .search-results a.result:active { + background: rgba(255,255,255,0.10); +} + .reader .main .sidebar .control { display: flex; margin: 5px 2px; @@ -283,7 +342,8 @@ } select, -button { +button, +input[type="text"] { background: transparent; width: 130px; padding: 5px; @@ -302,19 +362,22 @@ select { } select:hover, -button:hover { +button:hover, +input[type="text"]:hover { background: rgba(0,0,0,0.45); cursor: pointer; } body.light select, -body.light button { +body.light button , +body.light input[type="text"]{ background: rgba(255,255,255,0.3); color: rgba(0,0,0,0.8); } body.light select:hover, -body.light button:hover { +body.light button:hover, +body.light input[type="text"]:hover { background: rgba(255,255,255,0.45); } @@ -359,4 +422,25 @@ html.no-flex .error.banner.incompatible-browser { html.load-error .error.banner.load-error { display: block; z-index: 1000000004; -} \ No newline at end of file +} + +::-webkit-scrollbar-track { + background-color: transparent; +} + +::-webkit-scrollbar { + width: 6px; + background-color: rgba(0,0,0,0.07); +} + +::-webkit-scrollbar:hover { + background-color: rgba(0,0,0,0.1); +} + +::-webkit-scrollbar-thumb { + background-color: rgba(0,0,0,0.3); +} + +::-webkit-scrollbar-thumb:hover{ + background-color: rgba(0,0,0,0.4); +}