From dea674ef38633bda5a436464f7313bcd2e179724 Mon Sep 17 00:00:00 2001 From: Veloman Yunkan Date: Thu, 11 Aug 2022 17:02:28 +0400 Subject: [PATCH 01/42] Added resources of autoComplete.js to test/server.cpp --- test/server.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/test/server.cpp b/test/server.cpp index a91297e1..35cd0532 100644 --- a/test/server.cpp +++ b/test/server.cpp @@ -44,6 +44,8 @@ const ResourceCollection resources200Compressible{ { WITH_ETAG, "/ROOT/" }, { WITH_ETAG, "/ROOT/skin/taskbar.js" }, + { WITH_ETAG, "/ROOT/skin/autoComplete.min.js" }, + { WITH_ETAG, "/ROOT/skin/css/autoComplete.css" }, { WITH_ETAG, "/ROOT/skin/taskbar.css" }, { WITH_ETAG, "/ROOT/skin/block_external.js" }, @@ -64,6 +66,7 @@ const ResourceCollection resources200Compressible{ const ResourceCollection resources200Uncompressible{ { WITH_ETAG, "/ROOT/skin/caret.png" }, + { WITH_ETAG, "/ROOT/skin/css/images/search.svg" }, { WITH_ETAG, "/ROOT/raw/zimfile/meta/Title" }, { WITH_ETAG, "/ROOT/raw/zimfile/meta/Description" }, From 4db443eca6fc5de820c72acb722a85b3212d73bc Mon Sep 17 00:00:00 2001 From: Veloman Yunkan Date: Tue, 22 Feb 2022 22:42:15 +0400 Subject: [PATCH 02/42] Embryo of iframe-based viewer --- src/server/internalServer.cpp | 11 +++++--- static/resources_list.txt | 2 ++ static/skin/blank.html | 11 ++++++++ static/viewer.html | 50 +++++++++++++++++++++++++++++++++++ 4 files changed, 71 insertions(+), 3 deletions(-) create mode 100644 static/skin/blank.html create mode 100644 static/viewer.html diff --git a/src/server/internalServer.cpp b/src/server/internalServer.cpp index f194c925..f154ed47 100644 --- a/src/server/internalServer.cpp +++ b/src/server/internalServer.cpp @@ -553,7 +553,7 @@ std::unique_ptr InternalServer::handle_request(const RequestContext& r if (url == "/" ) return build_homepage(request); - if (isEndpointUrl(url, "skin")) + if (isEndpointUrl(url, "viewer") || isEndpointUrl(url, "skin")) return handle_skin(request); if (isEndpointUrl(url, "content")) @@ -720,12 +720,17 @@ std::unique_ptr InternalServer::handle_skin(const RequestContext& requ printf("** running handle_skin\n"); } - auto resourceName = request.get_url().substr(1); + const bool isRequestForViewer = request.get_url() == "/viewer"; + auto resourceName = isRequestForViewer + ? "viewer.html" + : request.get_url().substr(1); try { auto response = ContentResponse::build( *this, getResource(resourceName), - getMimeTypeForFile(resourceName)); + getMimeTypeForFile(resourceName), + /*isHomePage=*/false, + /*raw=*/true); response->set_cacheable(); return std::move(response); } catch (const ResourceNotFound& e) { diff --git a/static/resources_list.txt b/static/resources_list.txt index 653fadd2..dcdf7755 100644 --- a/static/resources_list.txt +++ b/static/resources_list.txt @@ -15,6 +15,8 @@ skin/fonts/Poppins.ttf skin/fonts/Roboto.ttf skin/block_external.js skin/search_results.css +skin/blank.html +viewer.html templates/search_result.html templates/search_result.xml templates/error.html diff --git a/static/skin/blank.html b/static/skin/blank.html new file mode 100644 index 00000000..8256d9c1 --- /dev/null +++ b/static/skin/blank.html @@ -0,0 +1,11 @@ + + + + + Blank page + + + + + + diff --git a/static/viewer.html b/static/viewer.html new file mode 100644 index 00000000..ea50fecf --- /dev/null +++ b/static/viewer.html @@ -0,0 +1,50 @@ + + + + + ZIM Viewer + + + + +
Taskbar
+ + + + + + From e5f97d95b1492f076c610c096b73bc57f479d693 Mon Sep 17 00:00:00 2001 From: Veloman Yunkan Date: Sat, 19 Mar 2022 15:54:26 +0400 Subject: [PATCH 03/42] Handling of manual hash component change --- static/viewer.html | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/static/viewer.html b/static/viewer.html index ea50fecf..ca059e68 100644 --- a/static/viewer.html +++ b/static/viewer.html @@ -35,9 +35,20 @@ } const cf = document.getElementById('content_iframe'); - cf.src = userUrl2IframeUrl(window.location.hash.slice(1)); cf.height = window.visualViewport.height - cf.offsetTop - 4; + function handle_location_hash_change() { + const hash = window.location.hash; + const iframeContentUrl = userUrl2IframeUrl(hash.slice(1)); + console.log("handle_location_hash_change: " + hash); + if ( iframeContentUrl != cf.contentWindow.location.pathname ) { + cf.src = iframeContentUrl; + } + } + + window.onhashchange = handle_location_hash_change; + handle_location_hash_change(); + function handle_content_url_change() { document.title = cf.contentDocument.title; const iframeContentUrl = cf.contentWindow.location.pathname; From 4105be9bd22e0fe71ae2148a9b045afb39b60a76 Mon Sep 17 00:00:00 2001 From: Veloman Yunkan Date: Sun, 7 Aug 2022 16:15:49 +0400 Subject: [PATCH 04/42] Improved browsing history tracking & traversal Before this fix, browsing history didn't work at all. Now it mostly works but there are still some quirks that must be debugged further. Since session history handling turns out to be a rather complex topic (see https://html.spec.whatwg.org/multipage/history.html) the work in that direction will be postponed until other features reach a comparable level of readiness. --- static/viewer.html | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/static/viewer.html b/static/viewer.html index ca059e68..b72167f2 100644 --- a/static/viewer.html +++ b/static/viewer.html @@ -42,7 +42,7 @@ const iframeContentUrl = userUrl2IframeUrl(hash.slice(1)); console.log("handle_location_hash_change: " + hash); if ( iframeContentUrl != cf.contentWindow.location.pathname ) { - cf.src = iframeContentUrl; + cf.contentWindow.location.replace(iframeContentUrl); } } @@ -53,7 +53,9 @@ document.title = cf.contentDocument.title; const iframeContentUrl = cf.contentWindow.location.pathname; console.log('handle_content_url_change: ' + iframeContentUrl); - window.location.hash = iframeUrl2UserUrl(iframeContentUrl); + const newHash = '#' + iframeUrl2UserUrl(iframeContentUrl); + const viewerURL = location.origin + location.pathname + location.search; + window.location.replace(viewerURL + newHash); }; From 228e31cddd96f406acc0cc40bfe8dee1a06fa168 Mon Sep 17 00:00:00 2001 From: Veloman Yunkan Date: Sat, 19 Mar 2022 21:31:00 +0400 Subject: [PATCH 05/42] Handling of window size changes --- static/viewer.html | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/static/viewer.html b/static/viewer.html index b72167f2..e6f1d81b 100644 --- a/static/viewer.html +++ b/static/viewer.html @@ -6,7 +6,7 @@ - +
Taskbar
diff --git a/test/server.cpp b/test/server.cpp index 2bce3bb6..feca68b2 100644 --- a/test/server.cpp +++ b/test/server.cpp @@ -202,7 +202,7 @@ R"EXPECTEDRESULT( - + const blankPageUrl = `${root}/skin/blank.html`; From 29efb88d48d901b77a2898b649de399fe95519a6 Mon Sep 17 00:00:00 2001 From: Veloman Yunkan Date: Wed, 10 Aug 2022 13:04:08 +0400 Subject: [PATCH 19/42] Superficial cleanup in static/skin/viewer.js --- static/skin/viewer.js | 23 ++++++++++++----------- test/server.cpp | 2 +- 2 files changed, 13 insertions(+), 12 deletions(-) diff --git a/static/skin/viewer.js b/static/skin/viewer.js index 085777aa..750cddf6 100644 --- a/static/skin/viewer.js +++ b/static/skin/viewer.js @@ -35,13 +35,15 @@ let currentBookTitle = null; const bookUIGroup = document.getElementById('kiwix_serve_taskbar_book_ui_group'); const homeButton = document.getElementById('kiwix_serve_taskbar_home_button'); +const contentIframe = document.getElementById('content_iframe'); + function gotoMainPageOfCurrentBook() { location.hash = currentBook + '/'; } function gotoUrl(url) { - cf.src = url; + contentIframe.src = url; } function gotoRandomPage() { @@ -110,27 +112,26 @@ function iframeUrl2UserUrl(url, query) { return url.split('/').slice(2).join('/'); } -const cf = document.getElementById('content_iframe'); - function handle_visual_viewport_change() { - cf.height = window.visualViewport.height - cf.offsetTop - 4; + contentIframe.height = window.visualViewport.height - contentIframe.offsetTop - 4; } function handle_location_hash_change() { const hash = window.location.hash.slice(1); + console.log("handle_location_hash_change: " + hash); updateCurrentBookIfNeeded(hash); const iframeContentUrl = userUrl2IframeUrl(hash); - console.log("handle_location_hash_change: " + hash); - if ( iframeContentUrl != cf.contentWindow.location.pathname ) { - cf.contentWindow.location.replace(iframeContentUrl); + if ( iframeContentUrl != contentIframe.contentWindow.location.pathname ) { + contentIframe.contentWindow.location.replace(iframeContentUrl); } } function handle_content_url_change() { - document.title = cf.contentDocument.title; - const iframeContentUrl = cf.contentWindow.location.pathname; - const iframeContentQuery = cf.contentWindow.location.search; - console.log('handle_content_url_change: ' + cf.contentWindow.location.href); + const iframeLocation = contentIframe.contentWindow.location; + console.log('handle_content_url_change: ' + iframeLocation.href); + document.title = contentIframe.contentDocument.title; + const iframeContentUrl = iframeLocation.pathname; + const iframeContentQuery = iframeLocation.search; const newHash = '#' + iframeUrl2UserUrl(iframeContentUrl, iframeContentQuery); const viewerURL = location.origin + location.pathname + location.search; window.location.replace(viewerURL + newHash); diff --git a/test/server.cpp b/test/server.cpp index feca68b2..57a2158a 100644 --- a/test/server.cpp +++ b/test/server.cpp @@ -202,7 +202,7 @@ R"EXPECTEDRESULT( - + const blankPageUrl = `${root}/skin/blank.html`; From 2083c390b5b60cc5f8f7f4ea2ba6ca5eeb682877 Mon Sep 17 00:00:00 2001 From: Veloman Yunkan Date: Wed, 10 Aug 2022 12:51:27 +0400 Subject: [PATCH 20/42] Searchbox correctly tracks the current book Before this fix there were two issues with the taskbar search box: 1. The book used for the suggestions API was resolved only once during the page load and didn't change during navigation. 2. The current book could not be resolved from a search URL. Now both issues are fixed. --- static/skin/viewer.js | 58 ++++++++++++++++++++++++++++++++----------- test/server.cpp | 2 +- 2 files changed, 44 insertions(+), 16 deletions(-) diff --git a/static/skin/viewer.js b/static/skin/viewer.js index 750cddf6..c10c599c 100644 --- a/static/skin/viewer.js +++ b/static/skin/viewer.js @@ -25,12 +25,12 @@ function getBookFromUserUrl(url) { if ( url.startsWith('search?') ) { const p = new URLSearchParams(url.slice("search?".length)); - return p.get('books.name'); + return p.get('books.name') || p.get('content'); } return url.split('/')[0]; } -let currentBook = null; +let currentBook = getBookFromUserUrl(location.hash.slice(1)); let currentBookTitle = null; const bookUIGroup = document.getElementById('kiwix_serve_taskbar_book_ui_group'); @@ -56,22 +56,26 @@ function performSearch() { gotoUrl(`${root}/search?books.name=${currentBook}&pattern=${q}`); } +function suggestionsApiURL() +{ + return `${root}/suggest?content=${encodeURIComponent(currentBook)}`; +} + function setCurrentBook(book, title) { currentBook = book; currentBookTitle = title; homeButton.title = `Go to the main page of '${title}'`; homeButton.setAttribute("aria-label", homeButton.title); homeButton.innerHTML = ``; - const searchbox = document.getElementById('kiwixsearchbox'); - searchbox.title = `Search '${title}'`; - searchbox.setAttribute("aria-label", searchbox.title); bookUIGroup.style.display = 'inline'; + updateSearchBoxForBookChange(); } function noCurrentBook() { currentBook = null; currentBookTitle = null; bookUIGroup.style.display = 'none'; + updateSearchBoxForBookChange(); } function updateCurrentBookIfNeeded(userUrl) { @@ -112,6 +116,32 @@ function iframeUrl2UserUrl(url, query) { return url.split('/').slice(2).join('/'); } +function getSearchPattern() { + const url = window.location.hash.slice(1); + if ( url.startsWith('search?') ) { + const p = new URLSearchParams(url.slice("search?".length)); + return p.get("pattern"); + } + return null; +} + +function updateSearchBoxForLocationChange() { + document.getElementById("kiwixsearchbox").value = getSearchPattern(); +} + +function updateSearchBoxForBookChange() { + const searchbox = document.getElementById('kiwixsearchbox'); + const kiwixSearchFormWrapper = document.querySelector('.kiwix_searchform'); + if ( currentBookTitle ) { + searchbox.title = `Search '${currentBookTitle}'`; + searchbox.placeholder = searchbox.title; + searchbox.setAttribute("aria-label", searchbox.title); + kiwixSearchFormWrapper.style.display = 'inline'; + } else { + kiwixSearchFormWrapper.style.display = 'none'; + } +} + function handle_visual_viewport_change() { contentIframe.height = window.visualViewport.height - contentIframe.offsetTop - 4; } @@ -124,6 +154,7 @@ function handle_location_hash_change() { if ( iframeContentUrl != contentIframe.contentWindow.location.pathname ) { contentIframe.contentWindow.location.replace(iframeContentUrl); } + updateSearchBoxForLocationChange(); } function handle_content_url_change() { @@ -132,14 +163,16 @@ function handle_content_url_change() { document.title = contentIframe.contentDocument.title; const iframeContentUrl = iframeLocation.pathname; const iframeContentQuery = iframeLocation.search; - const newHash = '#' + iframeUrl2UserUrl(iframeContentUrl, iframeContentQuery); + const newHash = iframeUrl2UserUrl(iframeContentUrl, iframeContentQuery); const viewerURL = location.origin + location.pathname + location.search; - window.location.replace(viewerURL + newHash); + window.location.replace(viewerURL + '#' + newHash); + updateCurrentBookIfNeeded(newHash); }; window.onresize = handle_visual_viewport_change; window.onhashchange = handle_location_hash_change; +updateCurrentBook(currentBook); handle_location_hash_change(); function htmlDecode(input) { @@ -181,11 +214,6 @@ function setupAutoHidingOfTheToolbar() { } document.addEventListener('DOMContentLoaded', function () { - const p = location.pathname; - const root = p.slice(0, p.length - '/viewer'.length); - - const bookName = location.hash.slice(1).split('/')[0]; - const kiwixSearchBox = document.querySelector('#kiwixsearchbox'); const kiwixSearchFormWrapper = document.querySelector('.kiwix_searchform'); @@ -199,7 +227,7 @@ document.addEventListener('DOMContentLoaded', function () { src: async (query) => { try { // Fetch Data from external Source - const source = await fetch(`${root}/suggest?content=${encodeURIComponent(bookName)}&term=${encodeURIComponent(query)}`); + const source = await fetch(`${suggestionsApiURL()}&term=${encodeURIComponent(query)}`); const data = await source.json(); return data; } catch (error) { @@ -222,9 +250,9 @@ document.addEventListener('DOMContentLoaded', function () { element: (item, data) => { let searchLink; if (data.value.kind == "path") { - searchLink = `${root}/${bookName}/${htmlDecode(data.value.path)}`; + searchLink = `${root}/${currentBook}/${htmlDecode(data.value.path)}`; } else { - searchLink = `${root}/search?content=${encodeURIComponent(bookName)}&pattern=${encodeURIComponent(htmlDecode(data.value.value))}`; + searchLink = `${root}/search?content=${encodeURIComponent(currentBook)}&pattern=${encodeURIComponent(htmlDecode(data.value.value))}`; } item.innerHTML = `${htmlDecode(data.value.label)}`; }, diff --git a/test/server.cpp b/test/server.cpp index 57a2158a..2d7d6f3e 100644 --- a/test/server.cpp +++ b/test/server.cpp @@ -202,7 +202,7 @@ R"EXPECTEDRESULT( - + const blankPageUrl = `${root}/skin/blank.html`; From 9a193735fbf1fe70bc0ce8a6cd30021dd94c352c Mon Sep 17 00:00:00 2001 From: Veloman Yunkan Date: Wed, 10 Aug 2022 13:24:51 +0400 Subject: [PATCH 21/42] Hiding of the suggestions drop-down list - Suggestions disappear when search is performed as a result of pressing enter in the search box. --- static/skin/viewer.js | 13 ++++++++++++- test/server.cpp | 2 +- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/static/skin/viewer.js b/static/skin/viewer.js index c10c599c..971c2122 100644 --- a/static/skin/viewer.js +++ b/static/skin/viewer.js @@ -125,7 +125,17 @@ function getSearchPattern() { return null; } + +let autoCompleteJS = null; + +function closeSuggestions() { + if ( autoCompleteJS ) { + autoCompleteJS.close(); + } +} + function updateSearchBoxForLocationChange() { + closeSuggestions(); document.getElementById("kiwixsearchbox").value = getSearchPattern(); } @@ -217,7 +227,7 @@ document.addEventListener('DOMContentLoaded', function () { const kiwixSearchBox = document.querySelector('#kiwixsearchbox'); const kiwixSearchFormWrapper = document.querySelector('.kiwix_searchform'); - const autoCompleteJS = new autoComplete( + autoCompleteJS = new autoComplete( { selector: "#kiwixsearchbox", placeHolder: kiwixSearchBox.title, @@ -263,6 +273,7 @@ document.addEventListener('DOMContentLoaded', function () { ); document.querySelector('#kiwixsearchform').addEventListener('submit', function(event) { + closeSuggestions(); try { const selectedElem = document.querySelector('.autoComplete_selected > a'); if (selectedElem) { diff --git a/test/server.cpp b/test/server.cpp index 2d7d6f3e..93bd9f4b 100644 --- a/test/server.cpp +++ b/test/server.cpp @@ -202,7 +202,7 @@ R"EXPECTEDRESULT( - + const blankPageUrl = `${root}/skin/blank.html`; From 40c496d40116a7b0aef358058cf32cb695e282cf Mon Sep 17 00:00:00 2001 From: Veloman Yunkan Date: Thu, 11 Aug 2022 17:03:57 +0400 Subject: [PATCH 22/42] Removed old-style taskbar injection Double-toolbar in the viewer has gone. Some clean-up has to be performed after this change. --- src/server/response.cpp | 39 +------- src/server/response.h | 3 - static/resources_list.txt | 3 - static/skin/taskbar.js | 122 ------------------------- static/templates/head_taskbar.html | 4 - static/templates/taskbar_part.html | 25 ------ test/server.cpp | 137 ++--------------------------- 7 files changed, 10 insertions(+), 323 deletions(-) delete mode 100644 static/skin/taskbar.js delete mode 100644 static/templates/head_taskbar.html delete mode 100644 static/templates/taskbar_part.html diff --git a/src/server/response.cpp b/src/server/response.cpp index 4b53914c..d1ceda99 100644 --- a/src/server/response.cpp +++ b/src/server/response.cpp @@ -337,34 +337,6 @@ void print_response_info(int retCode, MHD_Response* response) } -void ContentResponse::introduce_taskbar(const std::string& lang) -{ - i18n::GetTranslatedString t(lang); - kainjow::mustache::object data{ - {"root", m_root}, - {"content", m_bookName}, - {"hascontent", (!m_bookName.empty() && !m_bookTitle.empty())}, - {"title", m_bookTitle}, - {"withlibrarybutton", m_withLibraryButton}, - {"LIBRARY_BUTTON_TEXT", t("library-button-text")}, - {"HOME_BUTTON_TEXT", t("home-button-text", {{"BOOK_TITLE", m_bookTitle}}) }, - {"RANDOM_PAGE_BUTTON_TEXT", t("random-page-button-text") }, - {"SEARCHBOX_TOOLTIP", t("searchbox-tooltip", {{"BOOK_TITLE", m_bookTitle}}) }, - }; - auto head_content = render_template(RESOURCE::templates::head_taskbar_html, data); - m_content = prependToFirstOccurence( - m_content, - "", - head_content); - - auto taskbar_part = render_template(RESOURCE::templates::taskbar_part_html, data); - m_content = appendToFirstOccurence( - m_content, - "]*>", - taskbar_part); -} - - void ContentResponse::inject_externallinks_blocker() { kainjow::mustache::data data; @@ -414,9 +386,6 @@ ContentResponse::create_mhd_response(const RequestContext& request) if (contentDecorationAllowed()) { inject_root_link(); - if (m_withTaskbar) { - introduce_taskbar(request.get_user_language()); - } if (m_blockExternalLinks) { inject_externallinks_blocker(); } @@ -468,14 +437,12 @@ void ContentResponse::set_taskbar(const std::string& bookName, const zim::Archiv } -ContentResponse::ContentResponse(const std::string& root, bool verbose, bool raw, bool withTaskbar, bool withLibraryButton, bool blockExternalLinks, const std::string& content, const std::string& mimetype) : +ContentResponse::ContentResponse(const std::string& root, bool verbose, bool raw, bool /*withTaskbar*/, bool /*withLibraryButton*/, bool blockExternalLinks, const std::string& content, const std::string& mimetype) : Response(verbose), m_root(root), m_content(content), m_mimeType(mimetype), m_raw(raw), - m_withTaskbar(withTaskbar), - m_withLibraryButton(withLibraryButton), m_blockExternalLinks(blockExternalLinks), m_bookName(""), m_bookTitle("") @@ -494,8 +461,8 @@ std::unique_ptr ContentResponse::build( server.m_root, server.m_verbose.load(), raw, - server.m_withTaskbar && !isHomePage, - server.m_withLibraryButton, + /*server.m_withTaskbar && !isHomePage*/ false, // XXX + /*server.m_withLibraryButton*/ false, // XXX server.m_blockExternalLinks, content, mimetype)); diff --git a/src/server/response.h b/src/server/response.h index 85dc8cb8..9c66f7ba 100644 --- a/src/server/response.h +++ b/src/server/response.h @@ -107,7 +107,6 @@ class ContentResponse : public Response { private: MHD_Response* create_mhd_response(const RequestContext& request); - void introduce_taskbar(const std::string& lang); void inject_externallinks_blocker(); void inject_root_link(); bool can_compress(const RequestContext& request) const; @@ -119,8 +118,6 @@ class ContentResponse : public Response { std::string m_content; std::string m_mimeType; bool m_raw; - bool m_withTaskbar; - bool m_withLibraryButton; bool m_blockExternalLinks; std::string m_bookName; std::string m_bookTitle; diff --git a/static/resources_list.txt b/static/resources_list.txt index 179cd97e..6696343f 100644 --- a/static/resources_list.txt +++ b/static/resources_list.txt @@ -4,7 +4,6 @@ skin/magnet.png skin/download.png skin/hash.png skin/search-icon.svg -skin/taskbar.js skin/iso6391To3.js skin/isotope.pkgd.min.js skin/index.js @@ -24,8 +23,6 @@ templates/error.html templates/error.xml templates/index.html templates/suggestion.json -templates/head_taskbar.html -templates/taskbar_part.html templates/external_blocker_part.html templates/captured_external.html templates/catalog_entries.xml diff --git a/static/skin/taskbar.js b/static/skin/taskbar.js deleted file mode 100644 index bedc9328..00000000 --- a/static/skin/taskbar.js +++ /dev/null @@ -1,122 +0,0 @@ -function htmlDecode(input) { - var doc = new DOMParser().parseFromString(input, "text/html"); - return doc.documentElement.textContent; -} - -function setupAutoHidingOfTheToolbar() { - let lastScrollTop = 0; - const delta = 5; - let didScroll = false; - const kiwixToolBar = document.querySelector('#kiwixtoolbar'); - - window.addEventListener('scroll', () => { - didScroll = true; - }); - - setInterval(function() { - if (didScroll) { - hasScrolled(); - didScroll = false; - } - }, 250); - - function hasScrolled() { - const st = document.documentElement.scrollTop || document.body.scrollTop; - if (Math.abs(lastScrollTop - st) <= delta) - return; - - if (st > lastScrollTop) { - kiwixToolBar.style.top = '-100%'; - } else { - kiwixToolBar.style.top = '0'; - } - - lastScrollTop = st; - } - -} - -document.addEventListener('DOMContentLoaded', function () { - const root = document.querySelector(`link[type='root']`).getAttribute("href"); - const bookName = (window.location.pathname == `${root}/search`) - ? (new URLSearchParams(window.location.search)).get('content') - : window.location.pathname.split(`${root}/`)[1].split('/')[0]; - - const autoCompleteJS = new autoComplete( - { - selector: "#kiwixsearchbox", - placeHolder: document.querySelector("#kiwixsearchbox").title, - threshold: 1, - debounce: 300, - data : { - src: async (query) => { - try { - // Fetch Data from external Source - const source = await fetch(`${root}/suggest?content=${encodeURIComponent(bookName)}&term=${encodeURIComponent(query)}`); - const data = await source.json(); - return data; - } catch (error) { - return error; - } - }, - keys: ['label'], - }, - submit: true, - searchEngine: (query, record) => { - // We accept all records - return true; - }, - resultsList: { - noResults: true, - /* We must display 10 results (requested) + 1 potential link to do a full text search. */ - maxResults: 11, - }, - resultItem: { - element: (item, data) => { - let searchLink; - if (data.value.kind == "path") { - searchLink = `${root}/${bookName}/${htmlDecode(data.value.path)}`; - } else { - searchLink = `${root}/search?content=${encodeURIComponent(bookName)}&pattern=${encodeURIComponent(htmlDecode(data.value.value))}`; - } - item.innerHTML = `${htmlDecode(data.value.label)}`; - }, - highlight: "autoComplete_highlight", - selected: "autoComplete_selected" - } - } - ); - - document.querySelector('#kiwixsearchform').addEventListener('submit', function(event) { - try { - const selectedElemLink = document.querySelector('.autoComplete_selected > a').href; - if (selectedElemLink) { - event.preventDefault(); - window.location = selectedElemLink; - } - } catch (err) {} - }); - - const kiwixSearchBox = document.querySelector('#kiwixsearchbox'); - const kiwixSearchForm = document.querySelector('.kiwix_searchform'); - kiwixSearchBox.addEventListener('focus', () => { - kiwixSearchForm.classList.add('full_width'); - document.querySelector('label[for="kiwix_button_show_toggle"]').classList.add('searching'); - document.querySelector('.kiwix_button_cont').classList.add('searching'); - }); - kiwixSearchBox.addEventListener('blur', () => { - kiwixSearchForm.classList.remove('full_width'); - document.querySelector('label[for="kiwix_button_show_toggle"]').classList.remove('searching'); - document.querySelector('.kiwix_button_cont').classList.remove('searching'); - }); - - // cybook hack - if (navigator.userAgent.indexOf("bookeen/cybook") != -1) { - document.querySelector('html').classList.add('cybook'); - } - - if (document.body.clientWidth < 520) { - setupAutoHidingOfTheToolbar(); - } - -}); diff --git a/static/templates/head_taskbar.html b/static/templates/head_taskbar.html deleted file mode 100644 index 52384b54..00000000 --- a/static/templates/head_taskbar.html +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/static/templates/taskbar_part.html b/static/templates/taskbar_part.html deleted file mode 100644 index fab98abe..00000000 --- a/static/templates/taskbar_part.html +++ /dev/null @@ -1,25 +0,0 @@ - - -
-
-
- {{#hascontent}}{{/hascontent}} - - -
-
- - -
- {{#withlibrarybutton}} - - {{/withlibrarybutton}} - {{#hascontent}} - - - {{/hascontent}} -
-
-
-
diff --git a/test/server.cpp b/test/server.cpp index 93bd9f4b..234890d2 100644 --- a/test/server.cpp +++ b/test/server.cpp @@ -43,7 +43,6 @@ typedef std::vector ResourceCollection; const ResourceCollection resources200Compressible{ { WITH_ETAG, "/ROOT/" }, - { WITH_ETAG, "/ROOT/skin/taskbar.js" }, { WITH_ETAG, "/ROOT/skin/autoComplete.min.js" }, { WITH_ETAG, "/ROOT/skin/css/autoComplete.css" }, { WITH_ETAG, "/ROOT/skin/taskbar.css" }, @@ -55,8 +54,6 @@ const ResourceCollection resources200Compressible{ { NO_ETAG, "/ROOT/suggest?content=zimfile&term=ray" }, - { NO_ETAG, "/ROOT/catch/external?source=www.example.com" }, - { WITH_ETAG, "/ROOT/content/zimfile/A/index" }, { WITH_ETAG, "/ROOT/content/zimfile/A/Ray_Charles" }, @@ -79,6 +76,8 @@ const ResourceCollection resources200Uncompressible{ { NO_ETAG, "/ROOT/catalog/v2/illustration/6f1d19d0-633f-087b-fb55-7ac324ff9baf?size=48" }, + { NO_ETAG, "/ROOT/catch/external?source=www.example.com" }, + { WITH_ETAG, "/ROOT/content/zimfile/I/m/Ray_Charles_classic_piano_pose.jpg" }, { WITH_ETAG, "/ROOT/content/corner_cases/A/empty.html" }, @@ -210,23 +209,13 @@ R"EXPECTEDRESULT( - - - - -)EXPECTEDRESULT" + "" }, { // Searching in a ZIM file without a full-text index returns // a page rendered from static/templates/no_search_result_html /* url */ "/ROOT/search?content=poor&pattern=whatever", R"EXPECTEDRESULT( - - - - - )EXPECTEDRESULT" }, }; @@ -442,13 +431,8 @@ public: std::string expectedResponse() const; private: - bool isTranslatedVersion() const; virtual std::string pageTitle() const; std::string pageCssLink() const; - std::string hiddenBookNameInput() const; - std::string searchPatternInput() const; - std::string taskbarLinks() const; - std::string goToWelcomePageText() const; }; std::string TestContentIn404HtmlResponse::expectedResponse() const @@ -464,40 +448,8 @@ std::string TestContentIn404HtmlResponse::expectedResponse() const )FRAG", R"FRAG( - - - - - - - -
-
-
- )FRAG", - - R"FRAG( - -)FRAG", - - R"FRAG(
-
- - -
- - )FRAG", - - R"FRAG( -
-
-
-
-)FRAG", + + )FRAG", R"FRAG( @@ -509,18 +461,8 @@ std::string TestContentIn404HtmlResponse::expectedResponse() const + frag[1] + pageCssLink() + frag[2] - + hiddenBookNameInput() - + frag[3] - + searchPatternInput() - + frag[4] - + goToWelcomePageText() - + frag[5] - + goToWelcomePageText() - + frag[6] - + taskbarLinks() - + frag[7] + expectedBody - + frag[8]; + + frag[3]; } std::string TestContentIn404HtmlResponse::pageTitle() const @@ -540,71 +482,6 @@ std::string TestContentIn404HtmlResponse::pageCssLink() const + R"(" rel="Stylesheet" />)"; } -std::string TestContentIn404HtmlResponse::hiddenBookNameInput() const -{ - return bookName.empty() - ? "" - : R"()"; -} - -std::string TestContentIn404HtmlResponse::searchPatternInput() const -{ - const std::string searchboxTooltip = isTranslatedVersion() - ? "Որոնել '" + bookTitle + "'֊ում" - : "Search '" + bookTitle + "'"; - return R"( -)"; -} - -std::string TestContentIn404HtmlResponse::taskbarLinks() const -{ - if ( bookName.empty() ) - return ""; - - const auto goToMainPageOfBook = isTranslatedVersion() - ? "Դեպի '" + bookTitle + "'֊ի գլխավոր էջը" - : "Go to the main page of '" + bookTitle + "'"; - - const std::string goToRandomPage = isTranslatedVersion() - ? "Բացել պատահական էջ" - : "Go to a randomly selected page"; - - return R"( - )"; -} - -bool TestContentIn404HtmlResponse::isTranslatedVersion() const -{ - return url.find("userlang=hy") != std::string::npos; -} - -std::string TestContentIn404HtmlResponse::goToWelcomePageText() const -{ - return isTranslatedVersion() - ? "Գրադարանի էջ" - : "Go to welcome page"; -} - - class TestContentIn400HtmlResponse : public TestContentIn404HtmlResponse { public: @@ -1156,7 +1033,7 @@ TEST_F(ServerTest, RawEntry) p = zfs1_->GET("/ROOT/content/zimfile/A/Ray_Charles"); EXPECT_EQ(200, p->status); EXPECT_NE(std::string(p->body), std::string(entry.getItem(true).getData())); - EXPECT_TRUE(p->body.find("taskbar") != std::string::npos); + EXPECT_TRUE(p->body.find("") != std::string::npos); } TEST_F(ServerTest, HeadMethodIsSupported) From 0cf4850a9b3aa7afa203c4dfe03b46311492d220 Mon Sep 17 00:00:00 2001 From: Veloman Yunkan Date: Sun, 26 Jun 2022 13:28:30 +0400 Subject: [PATCH 23/42] Dropped TaskbarInfo --- src/server/internalServer.cpp | 26 +++++++++++++------------- src/server/response.cpp | 27 +-------------------------- src/server/response.h | 21 --------------------- 3 files changed, 14 insertions(+), 60 deletions(-) diff --git a/src/server/internalServer.cpp b/src/server/internalServer.cpp index f154ed47..0e8dc6aa 100644 --- a/src/server/internalServer.cpp +++ b/src/server/internalServer.cpp @@ -653,8 +653,7 @@ std::unique_ptr InternalServer::handle_suggest(const RequestContext& r if (archive == nullptr) { return HTTP404Response(*this, request) - + noSuchBookErrorMsg(bookName) - + TaskbarInfo(bookName); + + noSuchBookErrorMsg(bookName); } const auto queryString = request.get_optional_param("term", std::string()); @@ -782,11 +781,15 @@ std::unique_ptr InternalServer::handle_search(const RequestContext& re "404-page-heading", cssUrl); response += nonParameterizedMessage("no-search-results"); + // XXX: Now this has to be handled by the iframe-based viewer which + // XXX: has to resolve if the book selection resulted in a single book. + /* if(bookIds.size() == 1) { auto bookId = *bookIds.begin(); auto bookName = mp_nameMapper->getNameForId(bookId); response += TaskbarInfo(bookName, mp_library->getArchiveById(bookId).get()); } + */ return response; } @@ -821,11 +824,15 @@ std::unique_ptr InternalServer::handle_search(const RequestContext& re /*raw =*/true); } auto response = ContentResponse::build(*this, renderer.getHtml(), "text/html; charset=utf-8"); + // XXX: Now this has to be handled by the iframe-based viewer which + // XXX: has to resolve if the book selection resulted in a single book. + /* if(bookIds.size() == 1) { auto bookId = *bookIds.begin(); auto bookName = mp_nameMapper->getNameForId(bookId); response->set_taskbar(bookName, mp_library->getArchiveById(bookId).get()); } + */ return std::move(response); } catch (const Error& e) { return HTTP400Response(*this, request) @@ -857,8 +864,7 @@ std::unique_ptr InternalServer::handle_random(const RequestContext& re if (archive == nullptr) { return HTTP404Response(*this, request) - + noSuchBookErrorMsg(bookName) - + TaskbarInfo(bookName); + + noSuchBookErrorMsg(bookName); } try { @@ -866,8 +872,7 @@ std::unique_ptr InternalServer::handle_random(const RequestContext& re return build_redirect(bookName, getFinalItem(*archive, entry)); } catch(zim::EntryNotFound& e) { return HTTP404Response(*this, request) - + nonParameterizedMessage("random-article-failure") - + TaskbarInfo(bookName, archive.get()); + + nonParameterizedMessage("random-article-failure"); } } @@ -1015,8 +1020,7 @@ std::unique_ptr InternalServer::handle_content(const RequestContext& r const std::string searchURL = m_root + "/search?pattern=" + kiwix::urlEncode(pattern, true); return HTTP404Response(*this, request) + urlNotFoundMsg - + suggestSearchMsg(searchURL, kiwix::urlDecode(pattern)) - + TaskbarInfo(bookName); + + suggestSearchMsg(searchURL, kiwix::urlDecode(pattern)); } auto urlStr = url.substr(prefixLength + bookName.size()); @@ -1032,9 +1036,6 @@ std::unique_ptr InternalServer::handle_content(const RequestContext& r return build_redirect(bookName, getFinalItem(*archive, entry)); } auto response = ItemResponse::build(*this, request, entry.getItem()); - try { - dynamic_cast(*response).set_taskbar(bookName, archive.get()); - } catch (std::bad_cast& e) {} if (m_verbose.load()) { printf("Found %s\n", entry.getPath().c_str()); @@ -1049,8 +1050,7 @@ std::unique_ptr InternalServer::handle_content(const RequestContext& r std::string searchURL = m_root + "/search?content=" + bookName + "&pattern=" + kiwix::urlEncode(pattern, true); return HTTP404Response(*this, request) + urlNotFoundMsg - + suggestSearchMsg(searchURL, kiwix::urlDecode(pattern)) - + TaskbarInfo(bookName, archive.get()); + + suggestSearchMsg(searchURL, kiwix::urlDecode(pattern)); } } diff --git a/src/server/response.cpp b/src/server/response.cpp index d1ceda99..a19c5a7c 100644 --- a/src/server/response.cpp +++ b/src/server/response.cpp @@ -140,9 +140,6 @@ std::unique_ptr ContentResponseBlueprint::generateResponseObjec { auto r = ContentResponse::build(m_server, m_template, m_data, m_mimeType); r->set_code(m_httpStatusCode); - if ( m_taskbarInfo ) { - r->set_taskbar(m_taskbarInfo->bookName, m_taskbarInfo->archive); - } return r; } @@ -246,19 +243,6 @@ std::unique_ptr HTTP500Response::generateResponseObject() const return r; } -ContentResponseBlueprint& ContentResponseBlueprint::operator+(const TaskbarInfo& taskbarInfo) -{ - this->m_taskbarInfo.reset(new TaskbarInfo(taskbarInfo)); - return *this; -} - -ContentResponseBlueprint& ContentResponseBlueprint::operator+=(const TaskbarInfo& taskbarInfo) -{ - // operator+() is already a state-modifying operator (akin to operator+=) - return *this + taskbarInfo; -} - - std::unique_ptr Response::build_416(const InternalServer& server, size_t resourceLength) { auto response = Response::build(server); @@ -430,22 +414,13 @@ MHD_Result Response::send(const RequestContext& request, MHD_Connection* connect return ret; } -void ContentResponse::set_taskbar(const std::string& bookName, const zim::Archive* archive) -{ - m_bookName = bookName; - m_bookTitle = archive ? getArchiveTitle(*archive) : ""; -} - - ContentResponse::ContentResponse(const std::string& root, bool verbose, bool raw, bool /*withTaskbar*/, bool /*withLibraryButton*/, bool blockExternalLinks, const std::string& content, const std::string& mimetype) : Response(verbose), m_root(root), m_content(content), m_mimeType(mimetype), m_raw(raw), - m_blockExternalLinks(blockExternalLinks), - m_bookName(""), - m_bookTitle("") + m_blockExternalLinks(blockExternalLinks) { add_header(MHD_HTTP_HEADER_CONTENT_TYPE, m_mimeType); } diff --git a/src/server/response.h b/src/server/response.h index 9c66f7ba..78b6fc39 100644 --- a/src/server/response.h +++ b/src/server/response.h @@ -102,8 +102,6 @@ class ContentResponse : public Response { const std::string& mimetype, bool isHomePage = false); - void set_taskbar(const std::string& bookName, const zim::Archive* archive); - private: MHD_Response* create_mhd_response(const RequestContext& request); @@ -119,21 +117,8 @@ class ContentResponse : public Response { std::string m_mimeType; bool m_raw; bool m_blockExternalLinks; - std::string m_bookName; - std::string m_bookTitle; }; -struct TaskbarInfo -{ - const std::string bookName; - const zim::Archive* const archive; - - TaskbarInfo(const std::string& bookName, const zim::Archive* a = nullptr) - : bookName(bookName) - , archive(a) - {} -}; - class ContentResponseBlueprint { public: // functions @@ -162,9 +147,6 @@ public: // functions } - ContentResponseBlueprint& operator+(const TaskbarInfo& taskbarInfo); - ContentResponseBlueprint& operator+=(const TaskbarInfo& taskbarInfo); - protected: // functions std::string getMessage(const std::string& msgId) const; virtual std::unique_ptr generateResponseObject() const; @@ -176,7 +158,6 @@ public: //data const std::string m_mimeType; const std::string m_template; kainjow::mustache::data m_data; - std::unique_ptr m_taskbarInfo; }; struct HTTPErrorResponse : ContentResponseBlueprint @@ -188,8 +169,6 @@ struct HTTPErrorResponse : ContentResponseBlueprint const std::string& headingMsgId, const std::string& cssUrl = ""); - using ContentResponseBlueprint::operator+; - using ContentResponseBlueprint::operator+=; HTTPErrorResponse& operator+(const std::string& msg); HTTPErrorResponse& operator+(const ParameterizedMessage& errorDetails); HTTPErrorResponse& operator+=(const ParameterizedMessage& errorDetails); From c73e6f9a81a5a1856fb1cbdcf944738892ae458f Mon Sep 17 00:00:00 2001 From: Veloman Yunkan Date: Sun, 26 Jun 2022 13:41:15 +0400 Subject: [PATCH 24/42] Dropped unused params from ContentResponse ctor --- src/server/response.cpp | 4 +--- src/server/response.h | 4 ++-- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/src/server/response.cpp b/src/server/response.cpp index a19c5a7c..8cf5c0c6 100644 --- a/src/server/response.cpp +++ b/src/server/response.cpp @@ -414,7 +414,7 @@ MHD_Result Response::send(const RequestContext& request, MHD_Connection* connect return ret; } -ContentResponse::ContentResponse(const std::string& root, bool verbose, bool raw, bool /*withTaskbar*/, bool /*withLibraryButton*/, bool blockExternalLinks, const std::string& content, const std::string& mimetype) : +ContentResponse::ContentResponse(const std::string& root, bool verbose, bool raw, bool blockExternalLinks, const std::string& content, const std::string& mimetype) : Response(verbose), m_root(root), m_content(content), @@ -436,8 +436,6 @@ std::unique_ptr ContentResponse::build( server.m_root, server.m_verbose.load(), raw, - /*server.m_withTaskbar && !isHomePage*/ false, // XXX - /*server.m_withLibraryButton*/ false, // XXX server.m_blockExternalLinks, content, mimetype)); diff --git a/src/server/response.h b/src/server/response.h index 78b6fc39..34b9676d 100644 --- a/src/server/response.h +++ b/src/server/response.h @@ -84,17 +84,17 @@ class ContentResponse : public Response { const std::string& root, bool verbose, bool raw, - bool withTaskbar, - bool withLibraryButton, bool blockExternalLinks, const std::string& content, const std::string& mimetype); + static std::unique_ptr build( const InternalServer& server, const std::string& content, const std::string& mimetype, bool isHomePage = false, bool raw = false); + static std::unique_ptr build( const InternalServer& server, const std::string& template_str, From c9885115615671466a7e2888db2e333442960a95 Mon Sep 17 00:00:00 2001 From: Veloman Yunkan Date: Sun, 26 Jun 2022 13:42:29 +0400 Subject: [PATCH 25/42] Removed unused param from ContentResponse::build() Removed the isHomePage param from one of the variants of `ContentResponse::build()`. The other overload is dangerous since failing to review&update all of its call site may result in changed semantics. Will do it in a couple of separate commits. --- src/server/internalServer.cpp | 2 +- src/server/response.cpp | 5 ++--- src/server/response.h | 3 +-- 3 files changed, 4 insertions(+), 6 deletions(-) diff --git a/src/server/internalServer.cpp b/src/server/internalServer.cpp index 0e8dc6aa..9ff0dddb 100644 --- a/src/server/internalServer.cpp +++ b/src/server/internalServer.cpp @@ -623,7 +623,7 @@ InternalServer::get_matching_if_none_match_etag(const RequestContext& r) const std::unique_ptr InternalServer::build_homepage(const RequestContext& request) { - return ContentResponse::build(*this, m_indexTemplateString, get_default_data(), "text/html; charset=utf-8", true); + return ContentResponse::build(*this, m_indexTemplateString, get_default_data(), "text/html; charset=utf-8"); } /** diff --git a/src/server/response.cpp b/src/server/response.cpp index 8cf5c0c6..a38ef978 100644 --- a/src/server/response.cpp +++ b/src/server/response.cpp @@ -445,11 +445,10 @@ std::unique_ptr ContentResponse::build( const InternalServer& server, const std::string& template_str, kainjow::mustache::data data, - const std::string& mimetype, - bool isHomePage) + const std::string& mimetype) { auto content = render_template(template_str, data); - return ContentResponse::build(server, content, mimetype, isHomePage); + return ContentResponse::build(server, content, mimetype); } ItemResponse::ItemResponse(bool verbose, const zim::Item& item, const std::string& mimetype, const ByteRange& byterange) : diff --git a/src/server/response.h b/src/server/response.h index 34b9676d..657ecc5d 100644 --- a/src/server/response.h +++ b/src/server/response.h @@ -99,8 +99,7 @@ class ContentResponse : public Response { const InternalServer& server, const std::string& template_str, kainjow::mustache::data data, - const std::string& mimetype, - bool isHomePage = false); + const std::string& mimetype); private: MHD_Response* create_mhd_response(const RequestContext& request); From eb0a45b13eb7a3d28980d023f95702111340dab4 Mon Sep 17 00:00:00 2001 From: Veloman Yunkan Date: Sun, 26 Jun 2022 14:02:54 +0400 Subject: [PATCH 26/42] Undefaulted bool params of ContentResponse::build() This resulted in compiler aided discovery of all call sites where the default values were used. For OPDS/catalog requests now passing true for the `raw` parameter, since XML content isn't supposed to undergo any transformations. --- src/server/internalServer.cpp | 8 ++++++-- src/server/internalServer_catalog_v2.cpp | 24 +++++++++++++++++++----- src/server/response.cpp | 2 +- src/server/response.h | 4 ++-- 4 files changed, 28 insertions(+), 10 deletions(-) diff --git a/src/server/internalServer.cpp b/src/server/internalServer.cpp index 9ff0dddb..f249acde 100644 --- a/src/server/internalServer.cpp +++ b/src/server/internalServer.cpp @@ -823,7 +823,9 @@ std::unique_ptr InternalServer::handle_search(const RequestContext& re /*isHomePage =*/false, /*raw =*/true); } - auto response = ContentResponse::build(*this, renderer.getHtml(), "text/html; charset=utf-8"); + auto response = ContentResponse::build(*this, renderer.getHtml(), "text/html; charset=utf-8", + /*isHomePage =*/false, + /*raw =*/false); // XXX: Now this has to be handled by the iframe-based viewer which // XXX: has to resolve if the book selection resulted in a single book. /* @@ -953,7 +955,9 @@ std::unique_ptr InternalServer::handle_catalog(const RequestContext& r auto response = ContentResponse::build( *this, opdsDumper.dumpOPDSFeed(bookIdsToDump, request.get_query()), - "application/atom+xml; profile=opds-catalog; kind=acquisition; charset=utf-8"); + "application/atom+xml; profile=opds-catalog; kind=acquisition; charset=utf-8", + /*isHomePage*/ false, + /*raw*/ true); return std::move(response); } diff --git a/src/server/internalServer_catalog_v2.cpp b/src/server/internalServer_catalog_v2.cpp index bc7f1ada..f6314855 100644 --- a/src/server/internalServer_catalog_v2.cpp +++ b/src/server/internalServer_catalog_v2.cpp @@ -103,7 +103,9 @@ std::unique_ptr InternalServer::handle_catalog_v2_entries(const Reques return ContentResponse::build( *this, opdsFeed, - "application/atom+xml;profile=opds-catalog;kind=acquisition" + "application/atom+xml;profile=opds-catalog;kind=acquisition", + /*isHomePage*/ false, + /*raw*/ true ); } @@ -123,7 +125,9 @@ std::unique_ptr InternalServer::handle_catalog_v2_complete_entry(const return ContentResponse::build( *this, opdsFeed, - "application/atom+xml;type=entry;profile=opds-catalog" + "application/atom+xml;type=entry;profile=opds-catalog", + /*isHomePage*/ false, + /*raw*/ true ); } @@ -135,7 +139,9 @@ std::unique_ptr InternalServer::handle_catalog_v2_categories(const Req return ContentResponse::build( *this, opdsDumper.categoriesOPDSFeed(), - "application/atom+xml;profile=opds-catalog;kind=navigation" + "application/atom+xml;profile=opds-catalog;kind=navigation", + /*isHomePage*/ false, + /*raw*/ true ); } @@ -147,7 +153,9 @@ std::unique_ptr InternalServer::handle_catalog_v2_languages(const Requ return ContentResponse::build( *this, opdsDumper.languagesOPDSFeed(), - "application/atom+xml;profile=opds-catalog;kind=navigation" + "application/atom+xml;profile=opds-catalog;kind=navigation", + /*isHomePage*/ false, + /*raw*/ true ); } @@ -158,7 +166,13 @@ std::unique_ptr InternalServer::handle_catalog_v2_illustration(const R auto book = mp_library->getBookByIdThreadSafe(bookId); auto size = request.get_argument("size"); auto illustration = book.getIllustration(size); - return ContentResponse::build(*this, illustration->getData(), illustration->mimeType); + return ContentResponse::build( + *this, + illustration->getData(), + illustration->mimeType, + /*isHomePage*/ false, + /*raw*/ true + ); } catch(...) { return HTTP404Response(*this, request) + urlNotFoundMsg; diff --git a/src/server/response.cpp b/src/server/response.cpp index a38ef978..d82e9e7b 100644 --- a/src/server/response.cpp +++ b/src/server/response.cpp @@ -448,7 +448,7 @@ std::unique_ptr ContentResponse::build( const std::string& mimetype) { auto content = render_template(template_str, data); - return ContentResponse::build(server, content, mimetype); + return ContentResponse::build(server, content, mimetype, /*isHomePage*/false, /*raw*/false); } ItemResponse::ItemResponse(bool verbose, const zim::Item& item, const std::string& mimetype, const ByteRange& byterange) : diff --git a/src/server/response.h b/src/server/response.h index 657ecc5d..182210d5 100644 --- a/src/server/response.h +++ b/src/server/response.h @@ -92,8 +92,8 @@ class ContentResponse : public Response { const InternalServer& server, const std::string& content, const std::string& mimetype, - bool isHomePage = false, - bool raw = false); + bool isHomePage, + bool raw); static std::unique_ptr build( const InternalServer& server, From 0ce36e624642a64925f6ca10a2a75f4020ad3893 Mon Sep 17 00:00:00 2001 From: Veloman Yunkan Date: Sun, 26 Jun 2022 14:10:17 +0400 Subject: [PATCH 27/42] Got rid of isHomePage in ContentResponse::build() --- src/server/internalServer.cpp | 5 ----- src/server/internalServer.h | 2 +- src/server/internalServer_catalog_v2.cpp | 5 ----- src/server/response.cpp | 5 ++--- src/server/response.h | 1 - 5 files changed, 3 insertions(+), 15 deletions(-) diff --git a/src/server/internalServer.cpp b/src/server/internalServer.cpp index f249acde..d996c9ab 100644 --- a/src/server/internalServer.cpp +++ b/src/server/internalServer.cpp @@ -728,7 +728,6 @@ std::unique_ptr InternalServer::handle_skin(const RequestContext& requ *this, getResource(resourceName), getMimeTypeForFile(resourceName), - /*isHomePage=*/false, /*raw=*/true); response->set_cacheable(); return std::move(response); @@ -820,11 +819,9 @@ std::unique_ptr InternalServer::handle_search(const RequestContext& re renderer.setPageLength(pageLength); if (request.get_requested_format() == "xml") { return ContentResponse::build(*this, renderer.getXml(), "application/rss+xml; charset=utf-8", - /*isHomePage =*/false, /*raw =*/true); } auto response = ContentResponse::build(*this, renderer.getHtml(), "text/html; charset=utf-8", - /*isHomePage =*/false, /*raw =*/false); // XXX: Now this has to be handled by the iframe-based viewer which // XXX: has to resolve if the book selection resulted in a single book. @@ -956,7 +953,6 @@ std::unique_ptr InternalServer::handle_catalog(const RequestContext& r *this, opdsDumper.dumpOPDSFeed(bookIdsToDump, request.get_query()), "application/atom+xml; profile=opds-catalog; kind=acquisition; charset=utf-8", - /*isHomePage*/ false, /*raw*/ true); return std::move(response); } @@ -1146,7 +1142,6 @@ std::unique_ptr InternalServer::handle_locally_customized_resource(con return ContentResponse::build(*this, resourceData, crd.mimeType, - /*isHomePage=*/false, /*raw=*/true); } diff --git a/src/server/internalServer.h b/src/server/internalServer.h index 82574378..ee2c2072 100644 --- a/src/server/internalServer.h +++ b/src/server/internalServer.h @@ -183,7 +183,7 @@ class InternalServer { std::unique_ptr m_customizedResources; friend std::unique_ptr Response::build(const InternalServer& server); - friend std::unique_ptr ContentResponse::build(const InternalServer& server, const std::string& content, const std::string& mimetype, bool isHomePage, bool raw); + friend std::unique_ptr ContentResponse::build(const InternalServer& server, const std::string& content, const std::string& mimetype, bool raw); friend std::unique_ptr ItemResponse::build(const InternalServer& server, const RequestContext& request, const zim::Item& item, bool raw); }; diff --git a/src/server/internalServer_catalog_v2.cpp b/src/server/internalServer_catalog_v2.cpp index f6314855..842c2544 100644 --- a/src/server/internalServer_catalog_v2.cpp +++ b/src/server/internalServer_catalog_v2.cpp @@ -104,7 +104,6 @@ std::unique_ptr InternalServer::handle_catalog_v2_entries(const Reques *this, opdsFeed, "application/atom+xml;profile=opds-catalog;kind=acquisition", - /*isHomePage*/ false, /*raw*/ true ); } @@ -126,7 +125,6 @@ std::unique_ptr InternalServer::handle_catalog_v2_complete_entry(const *this, opdsFeed, "application/atom+xml;type=entry;profile=opds-catalog", - /*isHomePage*/ false, /*raw*/ true ); } @@ -140,7 +138,6 @@ std::unique_ptr InternalServer::handle_catalog_v2_categories(const Req *this, opdsDumper.categoriesOPDSFeed(), "application/atom+xml;profile=opds-catalog;kind=navigation", - /*isHomePage*/ false, /*raw*/ true ); } @@ -154,7 +151,6 @@ std::unique_ptr InternalServer::handle_catalog_v2_languages(const Requ *this, opdsDumper.languagesOPDSFeed(), "application/atom+xml;profile=opds-catalog;kind=navigation", - /*isHomePage*/ false, /*raw*/ true ); } @@ -170,7 +166,6 @@ std::unique_ptr InternalServer::handle_catalog_v2_illustration(const R *this, illustration->getData(), illustration->mimeType, - /*isHomePage*/ false, /*raw*/ true ); } catch(...) { diff --git a/src/server/response.cpp b/src/server/response.cpp index d82e9e7b..df0517e9 100644 --- a/src/server/response.cpp +++ b/src/server/response.cpp @@ -429,7 +429,6 @@ std::unique_ptr ContentResponse::build( const InternalServer& server, const std::string& content, const std::string& mimetype, - bool isHomePage, bool raw) { return std::unique_ptr(new ContentResponse( @@ -448,7 +447,7 @@ std::unique_ptr ContentResponse::build( const std::string& mimetype) { auto content = render_template(template_str, data); - return ContentResponse::build(server, content, mimetype, /*isHomePage*/false, /*raw*/false); + return ContentResponse::build(server, content, mimetype, /*raw*/false); } ItemResponse::ItemResponse(bool verbose, const zim::Item& item, const std::string& mimetype, const ByteRange& byterange) : @@ -468,7 +467,7 @@ std::unique_ptr ItemResponse::build(const InternalServer& server, cons const bool noRange = byteRange.kind() == ByteRange::RESOLVED_FULL_CONTENT; if (noRange && is_compressible_mime_type(mimetype)) { // Return a contentResponse - auto response = ContentResponse::build(server, item.getData(), mimetype, /*isHomePage=*/false, raw); + auto response = ContentResponse::build(server, item.getData(), mimetype, raw); response->set_cacheable(); response->m_byteRange = byteRange; return std::move(response); diff --git a/src/server/response.h b/src/server/response.h index 182210d5..ff78ff0a 100644 --- a/src/server/response.h +++ b/src/server/response.h @@ -92,7 +92,6 @@ class ContentResponse : public Response { const InternalServer& server, const std::string& content, const std::string& mimetype, - bool isHomePage, bool raw); static std::unique_ptr build( From 685e7f8ad4a6dcd4266790fc576c0b0bc7218b9b Mon Sep 17 00:00:00 2001 From: Veloman Yunkan Date: Wed, 29 Jun 2022 11:21:47 +0400 Subject: [PATCH 28/42] Unconditional blocking of external links --- src/server/response.cpp | 15 ---- src/server/response.h | 1 - static/resources_list.txt | 2 - static/skin/block_external.js | 74 ------------------- static/skin/viewer.js | 82 +++++++++++++++++++++ static/templates/external_blocker_part.html | 1 - static/viewer.html | 2 +- test/server.cpp | 1 - 8 files changed, 83 insertions(+), 95 deletions(-) delete mode 100644 static/skin/block_external.js delete mode 100644 static/templates/external_blocker_part.html diff --git a/src/server/response.cpp b/src/server/response.cpp index df0517e9..ddda140a 100644 --- a/src/server/response.cpp +++ b/src/server/response.cpp @@ -321,17 +321,6 @@ void print_response_info(int retCode, MHD_Response* response) } -void ContentResponse::inject_externallinks_blocker() -{ - kainjow::mustache::data data; - data.set("root", m_root); - auto script_tag = render_template(RESOURCE::templates::external_blocker_part_html, data); - m_content = prependToFirstOccurence( - m_content, - "", - script_tag); -} - void ContentResponse::inject_root_link(){ m_content = prependToFirstOccurence( m_content, @@ -369,10 +358,6 @@ ContentResponse::create_mhd_response(const RequestContext& request) { if (contentDecorationAllowed()) { inject_root_link(); - - if (m_blockExternalLinks) { - inject_externallinks_blocker(); - } } const bool isCompressed = can_compress(request) && compress(m_content); diff --git a/src/server/response.h b/src/server/response.h index ff78ff0a..a19079b6 100644 --- a/src/server/response.h +++ b/src/server/response.h @@ -103,7 +103,6 @@ class ContentResponse : public Response { private: MHD_Response* create_mhd_response(const RequestContext& request); - void inject_externallinks_blocker(); void inject_root_link(); bool can_compress(const RequestContext& request) const; bool contentDecorationAllowed() const; diff --git a/static/resources_list.txt b/static/resources_list.txt index 6696343f..5e1b30f7 100644 --- a/static/resources_list.txt +++ b/static/resources_list.txt @@ -12,7 +12,6 @@ skin/taskbar.css skin/index.css skin/fonts/Poppins.ttf skin/fonts/Roboto.ttf -skin/block_external.js skin/search_results.css skin/blank.html skin/viewer.js @@ -23,7 +22,6 @@ templates/error.html templates/error.xml templates/index.html templates/suggestion.json -templates/external_blocker_part.html templates/captured_external.html templates/catalog_entries.xml templates/catalog_v2_root.xml diff --git a/static/skin/block_external.js b/static/skin/block_external.js deleted file mode 100644 index 6bc32bae..00000000 --- a/static/skin/block_external.js +++ /dev/null @@ -1,74 +0,0 @@ -const root = document.querySelector( `link[type='root']` ).getAttribute("href"); -// `block_path` variable used by openzim/warc2zim to detect whether URL blocking is enabled or not -var block_path = `${root}/catch/external`; -// called only on external links -function capture_event(e, target) { target.setAttribute("href", encodeURI(block_path + "?source=" + target.href)); } - -// called on all link clicks. filters external and call capture_event -function on_click_event(e) { - var target = findParent("a", e.target); - if (target !== null && "href" in target) { - var href = target.href; - if (window.location.pathname.indexOf(block_path) == 0) // already in catch page - return; - if (href.indexOf(window.location.origin) == 0) - return; - if (href.substr(0, 2) == "//") - return capture_event(e, target); - if (href.substr(0, 5) == "http:") - return capture_event(e, target); - if (href.substr(0, 6) == "https:") - return capture_event(e, target); - return; - } -} - -// script entrypoint (called on document ready) -function run() { live('a', 'click', on_click_event); } - -// find first parent with tagname -function findParent(tagname, el) { - while (el) { - if ((el.nodeName || el.tagName).toLowerCase() === tagname.toLowerCase()) { - return el; - } - el = el.parentNode; - } - return null; -} - -// matches polyfill -this.Element && function(ElementPrototype) { - ElementPrototype.matches = ElementPrototype.matches || - ElementPrototype.matchesSelector || - ElementPrototype.webkitMatchesSelector || - ElementPrototype.msMatchesSelector || - function(selector) { - var node = this, nodes = (node.parentNode || node.document).querySelectorAll(selector), i = -1; - while (nodes[++i] && nodes[i] != node); - return !!nodes[i]; - } -}(Element.prototype); - -// helper for enabling IE 8 event bindings -function addEvent(el, type, handler) { - if (el.attachEvent) el.attachEvent('on'+type, handler); else el.addEventListener(type, handler); -} - -// live binding helper using matchesSelector -function live(selector, event, callback, context) { - addEvent(context || document, event, function(e) { - var found, el = e.target || e.srcElement; - while (el && el.matches && el !== context && !(found = el.matches(selector))) el = el.parentElement; - if (found) callback.call(el, e); - }); -} - -// in case the document is already rendered -if (document.readyState!='loading') run(); -// modern browsers -else if (document.addEventListener) document.addEventListener('DOMContentLoaded', run); -// IE <= 8 -else document.attachEvent('onreadystatechange', function(){ - if (document.readyState=='complete') run(); -}); diff --git a/static/skin/viewer.js b/static/skin/viewer.js index 971c2122..021c05e0 100644 --- a/static/skin/viewer.js +++ b/static/skin/viewer.js @@ -179,6 +179,88 @@ function handle_content_url_change() { updateCurrentBookIfNeeded(newHash); }; +//////////////////////////////////////////////////////////////////////////////// +// External link blocking +//////////////////////////////////////////////////////////////////////////////// + +function matchingAncestorElement(el, context, selector) { + while (el && el.matches && el !== context) { + if ( el.matches(selector) ) + return el; + el = el.parentElement; + } + return null; +} + +const block_path = `${root}/catch/external`; + +function blockLink(target) { + const encodedHref = encodeURIComponent(target.href); + target.setAttribute("href", block_path + "?source=" + encodedHref); + target.setAttribute("target", "_top"); +} + +function isExternalUrl(url) { + if ( url.startsWith(window.location.origin) ) + return false; + + return url.startsWith("//") + || url.startsWith("http:") + || url.startsWith("https:"); +} + +function onClickEvent(e) { + const iframeDocument = contentIframe.contentDocument; + const target = matchingAncestorElement(e.target, iframeDocument, "a"); + if (target !== null && "href" in target) { + if ( isExternalUrl(target.href) ) + return blockLink(target); + } +} + +// helper for enabling IE 8 event bindings +function addEventHandler(el, eventType, handler) { + if (el.attachEvent) + el.attachEvent('on'+eventType, handler); + else + el.addEventListener(eventType, handler); +} + +function setupEventHandler(context, selector, eventType, callback) { + addEventHandler(context, eventType, function(e) { + const eventElement = e.target || e.srcElement; + const el = matchingAncestorElement(eventElement, context, selector); + if (el) + callback.call(el, e); + }); +} + +// matches polyfill +this.Element && function(ElementPrototype) { + ElementPrototype.matches = ElementPrototype.matches || + ElementPrototype.matchesSelector || + ElementPrototype.webkitMatchesSelector || + ElementPrototype.msMatchesSelector || + function(selector) { + var node = this, nodes = (node.parentNode || node.document).querySelectorAll(selector), i = -1; + while (nodes[++i] && nodes[i] != node); + return !!nodes[i]; + } +}(Element.prototype); + +function setup_external_link_blocker() { + setupEventHandler(contentIframe.contentDocument, 'a', 'click', onClickEvent); +} + +//////////////////////////////////////////////////////////////////////////////// +// End of external link blocking +//////////////////////////////////////////////////////////////////////////////// + +function on_content_load() { + handle_content_url_change(); + setup_external_link_blocker(); +} + window.onresize = handle_visual_viewport_change; window.onhashchange = handle_location_hash_change; diff --git a/static/templates/external_blocker_part.html b/static/templates/external_blocker_part.html deleted file mode 100644 index 6bb0a0c2..00000000 --- a/static/templates/external_blocker_part.html +++ /dev/null @@ -1 +0,0 @@ - diff --git a/static/viewer.html b/static/viewer.html index 7948cd17..51667ffc 100644 --- a/static/viewer.html +++ b/static/viewer.html @@ -56,7 +56,7 @@ diff --git a/test/server.cpp b/test/server.cpp index 234890d2..874b6cf3 100644 --- a/test/server.cpp +++ b/test/server.cpp @@ -46,7 +46,6 @@ const ResourceCollection resources200Compressible{ { WITH_ETAG, "/ROOT/skin/autoComplete.min.js" }, { WITH_ETAG, "/ROOT/skin/css/autoComplete.css" }, { WITH_ETAG, "/ROOT/skin/taskbar.css" }, - { WITH_ETAG, "/ROOT/skin/block_external.js" }, { NO_ETAG, "/ROOT/catalog/search" }, From a67456111098557dcd8223240c25ee6a656f608b Mon Sep 17 00:00:00 2001 From: Veloman Yunkan Date: Wed, 29 Jun 2022 11:53:43 +0400 Subject: [PATCH 29/42] Dropped root link injection The only place that the root link is now used is in /skin/index.js, so added it in static/templates/index.html. But it seems that nothing prevents us from from switching from aboslute paths to relative paths in /skin/index.js, which will eliminate the need for the root link altogether. As a result of this change content is never decorated by kiwix serve. --- src/server/response.cpp | 11 ----------- src/server/response.h | 1 - static/templates/index.html | 1 + test/server.cpp | 5 +++-- test/server_search.cpp | 2 +- 5 files changed, 5 insertions(+), 15 deletions(-) diff --git a/src/server/response.cpp b/src/server/response.cpp index ddda140a..9bf061df 100644 --- a/src/server/response.cpp +++ b/src/server/response.cpp @@ -321,13 +321,6 @@ void print_response_info(int retCode, MHD_Response* response) } -void ContentResponse::inject_root_link(){ - m_content = prependToFirstOccurence( - m_content, - "", - ""); -} - bool ContentResponse::can_compress(const RequestContext& request) const { @@ -356,10 +349,6 @@ Response::create_mhd_response(const RequestContext& request) MHD_Response* ContentResponse::create_mhd_response(const RequestContext& request) { - if (contentDecorationAllowed()) { - inject_root_link(); - } - const bool isCompressed = can_compress(request) && compress(m_content); MHD_Response* response = MHD_create_response_from_buffer( diff --git a/src/server/response.h b/src/server/response.h index a19079b6..35d53c34 100644 --- a/src/server/response.h +++ b/src/server/response.h @@ -103,7 +103,6 @@ class ContentResponse : public Response { private: MHD_Response* create_mhd_response(const RequestContext& request); - void inject_root_link(); bool can_compress(const RequestContext& request) const; bool contentDecorationAllowed() const; diff --git a/static/templates/index.html b/static/templates/index.html index c7ffaf0d..42bbc6a1 100644 --- a/static/templates/index.html +++ b/static/templates/index.html @@ -3,6 +3,7 @@ + Welcome to Kiwix Server "); EXPECT_EQ("" "Welcome to kiwix library" - "" "" "", zfs.GET("/ROOT/")->body); } @@ -447,7 +446,7 @@ std::string TestContentIn404HtmlResponse::expectedResponse() const )FRAG", R"FRAG( - + )FRAG", R"FRAG( @@ -1028,11 +1027,13 @@ TEST_F(ServerTest, RawEntry) EXPECT_EQ(200, p->status); EXPECT_EQ(std::string(p->body), std::string(entry.getItem(true).getData())); + /* Now normal content is not decorated in any way, either // ... but the "normal" content is not p = zfs1_->GET("/ROOT/content/zimfile/A/Ray_Charles"); EXPECT_EQ(200, p->status); EXPECT_NE(std::string(p->body), std::string(entry.getItem(true).getData())); EXPECT_TRUE(p->body.find("") != std::string::npos); + */ } TEST_F(ServerTest, HeadMethodIsSupported) diff --git a/test/server_search.cpp b/test/server_search.cpp index 5e540ce9..435763d7 100644 --- a/test/server_search.cpp +++ b/test/server_search.cpp @@ -112,7 +112,7 @@ std::string makeSearchResultsHtml(const std::string& pattern, Search: %PATTERN% - +
%HEADER% From 6cc677b8adc4409dcc7c8d611426d163a9217c6a Mon Sep 17 00:00:00 2001 From: Veloman Yunkan Date: Wed, 29 Jun 2022 12:11:38 +0400 Subject: [PATCH 30/42] Dropped ContentResponse::contentDecorationAllowed() --- src/server/response.cpp | 18 ++---------------- src/server/response.h | 5 ----- 2 files changed, 2 insertions(+), 21 deletions(-) diff --git a/src/server/response.cpp b/src/server/response.cpp index 9bf061df..fab72b2c 100644 --- a/src/server/response.cpp +++ b/src/server/response.cpp @@ -329,16 +329,6 @@ ContentResponse::can_compress(const RequestContext& request) const && (m_content.size() > KIWIX_MIN_CONTENT_SIZE_TO_COMPRESS); } -bool -ContentResponse::contentDecorationAllowed() const -{ - if (m_raw) { - return false; - } - return (startsWith(m_mimeType, "text/html") - && m_mimeType.find(";raw=true") == std::string::npos); -} - MHD_Response* Response::create_mhd_response(const RequestContext& request) { @@ -388,13 +378,11 @@ MHD_Result Response::send(const RequestContext& request, MHD_Connection* connect return ret; } -ContentResponse::ContentResponse(const std::string& root, bool verbose, bool raw, bool blockExternalLinks, const std::string& content, const std::string& mimetype) : +ContentResponse::ContentResponse(const std::string& root, bool verbose, const std::string& content, const std::string& mimetype) : Response(verbose), m_root(root), m_content(content), - m_mimeType(mimetype), - m_raw(raw), - m_blockExternalLinks(blockExternalLinks) + m_mimeType(mimetype) { add_header(MHD_HTTP_HEADER_CONTENT_TYPE, m_mimeType); } @@ -408,8 +396,6 @@ std::unique_ptr ContentResponse::build( return std::unique_ptr(new ContentResponse( server.m_root, server.m_verbose.load(), - raw, - server.m_blockExternalLinks, content, mimetype)); } diff --git a/src/server/response.h b/src/server/response.h index 35d53c34..6a114ba5 100644 --- a/src/server/response.h +++ b/src/server/response.h @@ -83,8 +83,6 @@ class ContentResponse : public Response { ContentResponse( const std::string& root, bool verbose, - bool raw, - bool blockExternalLinks, const std::string& content, const std::string& mimetype); @@ -104,15 +102,12 @@ class ContentResponse : public Response { MHD_Response* create_mhd_response(const RequestContext& request); bool can_compress(const RequestContext& request) const; - bool contentDecorationAllowed() const; private: std::string m_root; std::string m_content; std::string m_mimeType; - bool m_raw; - bool m_blockExternalLinks; }; class ContentResponseBlueprint From b81cb3a8e9890bf583915b55667116b3bd256b7c Mon Sep 17 00:00:00 2001 From: Veloman Yunkan Date: Sun, 24 Jul 2022 15:31:38 +0400 Subject: [PATCH 31/42] Got rid of raw mode in response generation --- src/server/internalServer.cpp | 19 +++++++------------ src/server/internalServer.h | 4 ++-- src/server/internalServer_catalog_v2.cpp | 15 +++++---------- src/server/response.cpp | 15 +++++---------- src/server/response.h | 5 ++--- 5 files changed, 21 insertions(+), 37 deletions(-) diff --git a/src/server/internalServer.cpp b/src/server/internalServer.cpp index d996c9ab..604d8503 100644 --- a/src/server/internalServer.cpp +++ b/src/server/internalServer.cpp @@ -727,8 +727,7 @@ std::unique_ptr InternalServer::handle_skin(const RequestContext& requ auto response = ContentResponse::build( *this, getResource(resourceName), - getMimeTypeForFile(resourceName), - /*raw=*/true); + getMimeTypeForFile(resourceName)); response->set_cacheable(); return std::move(response); } catch (const ResourceNotFound& e) { @@ -818,11 +817,9 @@ std::unique_ptr InternalServer::handle_search(const RequestContext& re renderer.setSearchProtocolPrefix(m_root + "/search"); renderer.setPageLength(pageLength); if (request.get_requested_format() == "xml") { - return ContentResponse::build(*this, renderer.getXml(), "application/rss+xml; charset=utf-8", - /*raw =*/true); + return ContentResponse::build(*this, renderer.getXml(), "application/rss+xml; charset=utf-8"); } - auto response = ContentResponse::build(*this, renderer.getHtml(), "text/html; charset=utf-8", - /*raw =*/false); + auto response = ContentResponse::build(*this, renderer.getHtml(), "text/html; charset=utf-8"); // XXX: Now this has to be handled by the iframe-based viewer which // XXX: has to resolve if the book selection resulted in a single book. /* @@ -952,8 +949,7 @@ std::unique_ptr InternalServer::handle_catalog(const RequestContext& r auto response = ContentResponse::build( *this, opdsDumper.dumpOPDSFeed(bookIdsToDump, request.get_query()), - "application/atom+xml; profile=opds-catalog; kind=acquisition; charset=utf-8", - /*raw*/ true); + "application/atom+xml; profile=opds-catalog; kind=acquisition; charset=utf-8"); return std::move(response); } @@ -1098,13 +1094,13 @@ std::unique_ptr InternalServer::handle_raw(const RequestContext& reque try { if (kind == "meta") { auto item = archive->getMetadataItem(itemPath); - return ItemResponse::build(*this, request, item, /*raw=*/true); + return ItemResponse::build(*this, request, item); } else { auto entry = archive->getEntryByPath(itemPath); if (entry.isRedirect()) { return build_redirect(bookName, entry.getItem(true)); } - return ItemResponse::build(*this, request, entry.getItem(), /*raw=*/true); + return ItemResponse::build(*this, request, entry.getItem()); } } catch (zim::EntryNotFound& e ) { if (m_verbose.load()) { @@ -1141,8 +1137,7 @@ std::unique_ptr InternalServer::handle_locally_customized_resource(con return ContentResponse::build(*this, resourceData, - crd.mimeType, - /*raw=*/true); + crd.mimeType); } } diff --git a/src/server/internalServer.h b/src/server/internalServer.h index ee2c2072..3e4943ad 100644 --- a/src/server/internalServer.h +++ b/src/server/internalServer.h @@ -183,8 +183,8 @@ class InternalServer { std::unique_ptr m_customizedResources; friend std::unique_ptr Response::build(const InternalServer& server); - friend std::unique_ptr ContentResponse::build(const InternalServer& server, const std::string& content, const std::string& mimetype, bool raw); - friend std::unique_ptr ItemResponse::build(const InternalServer& server, const RequestContext& request, const zim::Item& item, bool raw); + friend std::unique_ptr ContentResponse::build(const InternalServer& server, const std::string& content, const std::string& mimetype); + friend std::unique_ptr ItemResponse::build(const InternalServer& server, const RequestContext& request, const zim::Item& item); }; } diff --git a/src/server/internalServer_catalog_v2.cpp b/src/server/internalServer_catalog_v2.cpp index 842c2544..b082dd1c 100644 --- a/src/server/internalServer_catalog_v2.cpp +++ b/src/server/internalServer_catalog_v2.cpp @@ -103,8 +103,7 @@ std::unique_ptr InternalServer::handle_catalog_v2_entries(const Reques return ContentResponse::build( *this, opdsFeed, - "application/atom+xml;profile=opds-catalog;kind=acquisition", - /*raw*/ true + "application/atom+xml;profile=opds-catalog;kind=acquisition" ); } @@ -124,8 +123,7 @@ std::unique_ptr InternalServer::handle_catalog_v2_complete_entry(const return ContentResponse::build( *this, opdsFeed, - "application/atom+xml;type=entry;profile=opds-catalog", - /*raw*/ true + "application/atom+xml;type=entry;profile=opds-catalog" ); } @@ -137,8 +135,7 @@ std::unique_ptr InternalServer::handle_catalog_v2_categories(const Req return ContentResponse::build( *this, opdsDumper.categoriesOPDSFeed(), - "application/atom+xml;profile=opds-catalog;kind=navigation", - /*raw*/ true + "application/atom+xml;profile=opds-catalog;kind=navigation" ); } @@ -150,8 +147,7 @@ std::unique_ptr InternalServer::handle_catalog_v2_languages(const Requ return ContentResponse::build( *this, opdsDumper.languagesOPDSFeed(), - "application/atom+xml;profile=opds-catalog;kind=navigation", - /*raw*/ true + "application/atom+xml;profile=opds-catalog;kind=navigation" ); } @@ -165,8 +161,7 @@ std::unique_ptr InternalServer::handle_catalog_v2_illustration(const R return ContentResponse::build( *this, illustration->getData(), - illustration->mimeType, - /*raw*/ true + illustration->mimeType ); } catch(...) { return HTTP404Response(*this, request) diff --git a/src/server/response.cpp b/src/server/response.cpp index fab72b2c..7067ff20 100644 --- a/src/server/response.cpp +++ b/src/server/response.cpp @@ -233,11 +233,7 @@ HTTP500Response::HTTP500Response(const InternalServer& server, std::unique_ptr HTTP500Response::generateResponseObject() const { - // We want a 500 response to be a minimalistic one (so that the server doesn't - // have to provide additional resources required for its proper rendering) - // ";raw=true" in the MIME-type below disables response decoration - // (see ContentResponse::contentDecorationAllowed()) - const std::string mimeType = "text/html;charset=utf-8;raw=true"; + const std::string mimeType = "text/html;charset=utf-8"; auto r = ContentResponse::build(m_server, m_template, m_data, mimeType); r->set_code(m_httpStatusCode); return r; @@ -390,8 +386,7 @@ ContentResponse::ContentResponse(const std::string& root, bool verbose, const st std::unique_ptr ContentResponse::build( const InternalServer& server, const std::string& content, - const std::string& mimetype, - bool raw) + const std::string& mimetype) { return std::unique_ptr(new ContentResponse( server.m_root, @@ -407,7 +402,7 @@ std::unique_ptr ContentResponse::build( const std::string& mimetype) { auto content = render_template(template_str, data); - return ContentResponse::build(server, content, mimetype, /*raw*/false); + return ContentResponse::build(server, content, mimetype); } ItemResponse::ItemResponse(bool verbose, const zim::Item& item, const std::string& mimetype, const ByteRange& byterange) : @@ -420,14 +415,14 @@ ItemResponse::ItemResponse(bool verbose, const zim::Item& item, const std::strin add_header(MHD_HTTP_HEADER_CONTENT_TYPE, m_mimeType); } -std::unique_ptr ItemResponse::build(const InternalServer& server, const RequestContext& request, const zim::Item& item, bool raw) +std::unique_ptr ItemResponse::build(const InternalServer& server, const RequestContext& request, const zim::Item& item) { const std::string mimetype = get_mime_type(item); auto byteRange = request.get_range().resolve(item.getSize()); const bool noRange = byteRange.kind() == ByteRange::RESOLVED_FULL_CONTENT; if (noRange && is_compressible_mime_type(mimetype)) { // Return a contentResponse - auto response = ContentResponse::build(server, item.getData(), mimetype, raw); + auto response = ContentResponse::build(server, item.getData(), mimetype); response->set_cacheable(); response->m_byteRange = byteRange; return std::move(response); diff --git a/src/server/response.h b/src/server/response.h index 6a114ba5..55c8fde4 100644 --- a/src/server/response.h +++ b/src/server/response.h @@ -89,8 +89,7 @@ class ContentResponse : public Response { static std::unique_ptr build( const InternalServer& server, const std::string& content, - const std::string& mimetype, - bool raw); + const std::string& mimetype); static std::unique_ptr build( const InternalServer& server, @@ -205,7 +204,7 @@ private: // overrides class ItemResponse : public Response { public: ItemResponse(bool verbose, const zim::Item& item, const std::string& mimetype, const ByteRange& byterange); - static std::unique_ptr build(const InternalServer& server, const RequestContext& request, const zim::Item& item, bool raw = false); + static std::unique_ptr build(const InternalServer& server, const RequestContext& request, const zim::Item& item); private: MHD_Response* create_mhd_response(const RequestContext& request); From 369406fb5d2d6c9ecf90defed330da544be03b46 Mon Sep 17 00:00:00 2001 From: Veloman Yunkan Date: Wed, 10 Aug 2022 13:08:40 +0400 Subject: [PATCH 32/42] Viewer settings Made the viewer respect the `--blockexternal` and `--nolibrarybutton` options of `kiwix-serve`. Those options are passed to the viewer via the dynamically generated resource `/viewer_settings.js`. --- src/server/internalServer.cpp | 16 ++++++++++++++++ src/server/internalServer.h | 1 + static/resources_list.txt | 1 + static/skin/viewer.js | 13 ++++++++++--- static/templates/viewer_settings.js | 4 ++++ static/viewer.html | 1 + test/server.cpp | 2 +- 7 files changed, 34 insertions(+), 4 deletions(-) create mode 100644 static/templates/viewer_settings.js diff --git a/src/server/internalServer.cpp b/src/server/internalServer.cpp index 604d8503..b1bb904e 100644 --- a/src/server/internalServer.cpp +++ b/src/server/internalServer.cpp @@ -556,6 +556,9 @@ std::unique_ptr InternalServer::handle_request(const RequestContext& r if (isEndpointUrl(url, "viewer") || isEndpointUrl(url, "skin")) return handle_skin(request); + if (url == "/viewer_settings.js") + return handle_viewer_settings(request); + if (isEndpointUrl(url, "content")) return handle_content(request); @@ -713,6 +716,19 @@ std::unique_ptr InternalServer::handle_suggest(const RequestContext& r return std::move(response); } +std::unique_ptr InternalServer::handle_viewer_settings(const RequestContext& request) +{ + if (m_verbose.load()) { + printf("** running handle_viewer_settings\n"); + } + + const kainjow::mustache::object data{ + {"enable_link_blocking", m_blockExternalLinks ? "true" : "false" }, + {"enable_library_button", m_withLibraryButton ? "true" : "false" } + }; + return ContentResponse::build(*this, RESOURCE::templates::viewer_settings_js, data, "application/javascript; charset=utf-8"); +} + std::unique_ptr InternalServer::handle_skin(const RequestContext& request) { if (m_verbose.load()) { diff --git a/src/server/internalServer.h b/src/server/internalServer.h index 3e4943ad..a73a7d42 100644 --- a/src/server/internalServer.h +++ b/src/server/internalServer.h @@ -126,6 +126,7 @@ class InternalServer { std::unique_ptr handle_request(const RequestContext& request); std::unique_ptr build_redirect(const std::string& bookName, const zim::Item& item) const; std::unique_ptr build_homepage(const RequestContext& request); + std::unique_ptr handle_viewer_settings(const RequestContext& request); std::unique_ptr handle_skin(const RequestContext& request); std::unique_ptr handle_catalog(const RequestContext& request); std::unique_ptr handle_catalog_v2(const RequestContext& request); diff --git a/static/resources_list.txt b/static/resources_list.txt index 5e1b30f7..4ff6cc3c 100644 --- a/static/resources_list.txt +++ b/static/resources_list.txt @@ -30,6 +30,7 @@ templates/catalog_v2_entry.xml templates/catalog_v2_categories.xml templates/catalog_v2_languages.xml templates/url_of_search_results_css +templates/viewer_settings.js opensearchdescription.xml ft_opensearchdescription.xml catalog_v2_searchdescription.xml diff --git a/static/skin/viewer.js b/static/skin/viewer.js index 021c05e0..326d3c4c 100644 --- a/static/skin/viewer.js +++ b/static/skin/viewer.js @@ -197,7 +197,6 @@ const block_path = `${root}/catch/external`; function blockLink(target) { const encodedHref = encodeURIComponent(target.href); target.setAttribute("href", block_path + "?source=" + encodedHref); - target.setAttribute("target", "_top"); } function isExternalUrl(url) { @@ -213,8 +212,12 @@ function onClickEvent(e) { const iframeDocument = contentIframe.contentDocument; const target = matchingAncestorElement(e.target, iframeDocument, "a"); if (target !== null && "href" in target) { - if ( isExternalUrl(target.href) ) - return blockLink(target); + if ( isExternalUrl(target.href) ) { + target.setAttribute("target", "_top"); + if ( viewerSettings.linkBlockingEnabled ) { + return blockLink(target); + } + } } } @@ -264,6 +267,10 @@ function on_content_load() { window.onresize = handle_visual_viewport_change; window.onhashchange = handle_location_hash_change; +if ( ! viewerSettings.libraryButtonEnabled ) { + document.getElementById("kiwix_serve_taskbar_library_button").remove(); +} + updateCurrentBook(currentBook); handle_location_hash_change(); diff --git a/static/templates/viewer_settings.js b/static/templates/viewer_settings.js new file mode 100644 index 00000000..26922d39 --- /dev/null +++ b/static/templates/viewer_settings.js @@ -0,0 +1,4 @@ +const viewerSettings = { + linkBlockingEnabled: {{enable_link_blocking}}, + libraryButtonEnabled: {{enable_library_button}} +} diff --git a/static/viewer.html b/static/viewer.html index 51667ffc..8ddc940e 100644 --- a/static/viewer.html +++ b/static/viewer.html @@ -6,6 +6,7 @@ + + const blankPageUrl = `${root}/skin/blank.html`; From 2be9ac342ffd31a23561b794bfe47ce08f8e1694 Mon Sep 17 00:00:00 2001 From: Veloman Yunkan Date: Sun, 10 Jul 2022 15:56:41 +0400 Subject: [PATCH 33/42] Partly respecting the kiwix-serve --nosearchbar option `--nosearchbar` option of `kiwix-serve` (despite its misleading name) was used to disable the entire taskbar. This commit accounts for the existence of that option only partially: 1. Links to books on the welcome/library page are affected - by default books are displayed in the viewer, but in a kiwix-serve instance run with --nosearchbar books are loaded in the top window. 2. The `/viewer` endpoint is enabled unconditionally, so if anyone enters the viewer URL in the address bar they will see books in the viewer. --- src/server/internalServer.cpp | 4 +++- static/skin/index.js | 4 +++- static/templates/index.html | 1 + test/server.cpp | 2 +- 4 files changed, 8 insertions(+), 3 deletions(-) diff --git a/src/server/internalServer.cpp b/src/server/internalServer.cpp index b1bb904e..07bc7779 100644 --- a/src/server/internalServer.cpp +++ b/src/server/internalServer.cpp @@ -626,7 +626,9 @@ InternalServer::get_matching_if_none_match_etag(const RequestContext& r) const std::unique_ptr InternalServer::build_homepage(const RequestContext& request) { - return ContentResponse::build(*this, m_indexTemplateString, get_default_data(), "text/html; charset=utf-8"); + auto data = get_default_data(); + data.set("enable_viewer", m_withTaskbar ? "true" : "false"); + return ContentResponse::build(*this, m_indexTemplateString, data, "text/html; charset=utf-8"); } /** diff --git a/static/skin/index.js b/static/skin/index.js index 7f29b316..6bf46db6 100644 --- a/static/skin/index.js +++ b/static/skin/index.js @@ -111,7 +111,9 @@ downloadLink = ''; } const bookName = link.split('/').pop(); - const viewerLink = `${root}/viewer#${bookName}`; + const viewerLink = enableViewer + ? `${root}/viewer#${bookName}` + : link; const humanFriendlyZimSize = humanFriendlySize(zimSize); diff --git a/static/templates/index.html b/static/templates/index.html index 42bbc6a1..61fd9f84 100644 --- a/static/templates/index.html +++ b/static/templates/index.html @@ -32,6 +32,7 @@ + diff --git a/test/server.cpp b/test/server.cpp index d76b9c40..42290245 100644 --- a/test/server.cpp +++ b/test/server.cpp @@ -184,7 +184,7 @@ R"EXPECTEDRESULT( href="/ROOT/skin/index.css?cacheid=3b470cee" src: url("/ROOT/skin/fonts/Roboto.ttf?cacheid=84d10248") format("truetype"); - + )EXPECTEDRESULT" }, { From da23e4eca4f75e9bd2c549ac9fa7c02762959af6 Mon Sep 17 00:00:00 2001 From: Veloman Yunkan Date: Sun, 4 Sep 2022 14:38:00 +0400 Subject: [PATCH 34/42] Revert "Partly respecting the kiwix-serve --nosearchbar option" This reverts commit 436d890893713c5eb98df6893d0e0b41b22e2472. --- src/server/internalServer.cpp | 4 +--- static/skin/index.js | 4 +--- static/templates/index.html | 1 - test/server.cpp | 2 +- 4 files changed, 3 insertions(+), 8 deletions(-) diff --git a/src/server/internalServer.cpp b/src/server/internalServer.cpp index 07bc7779..b1bb904e 100644 --- a/src/server/internalServer.cpp +++ b/src/server/internalServer.cpp @@ -626,9 +626,7 @@ InternalServer::get_matching_if_none_match_etag(const RequestContext& r) const std::unique_ptr InternalServer::build_homepage(const RequestContext& request) { - auto data = get_default_data(); - data.set("enable_viewer", m_withTaskbar ? "true" : "false"); - return ContentResponse::build(*this, m_indexTemplateString, data, "text/html; charset=utf-8"); + return ContentResponse::build(*this, m_indexTemplateString, get_default_data(), "text/html; charset=utf-8"); } /** diff --git a/static/skin/index.js b/static/skin/index.js index 6bf46db6..7f29b316 100644 --- a/static/skin/index.js +++ b/static/skin/index.js @@ -111,9 +111,7 @@ downloadLink = ''; } const bookName = link.split('/').pop(); - const viewerLink = enableViewer - ? `${root}/viewer#${bookName}` - : link; + const viewerLink = `${root}/viewer#${bookName}`; const humanFriendlyZimSize = humanFriendlySize(zimSize); diff --git a/static/templates/index.html b/static/templates/index.html index 61fd9f84..42bbc6a1 100644 --- a/static/templates/index.html +++ b/static/templates/index.html @@ -32,7 +32,6 @@ - diff --git a/test/server.cpp b/test/server.cpp index 42290245..d76b9c40 100644 --- a/test/server.cpp +++ b/test/server.cpp @@ -184,7 +184,7 @@ R"EXPECTEDRESULT( href="/ROOT/skin/index.css?cacheid=3b470cee" src: url("/ROOT/skin/fonts/Roboto.ttf?cacheid=84d10248") format("truetype"); - + )EXPECTEDRESULT" }, { From ae0179037508cf3e66025b82e678b11edf1c1cdd Mon Sep 17 00:00:00 2001 From: Veloman Yunkan Date: Sun, 4 Sep 2022 15:05:03 +0400 Subject: [PATCH 35/42] Introduced setupViewer() --- static/skin/viewer.js | 10 ++++++++-- static/viewer.html | 2 +- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/static/skin/viewer.js b/static/skin/viewer.js index 326d3c4c..a8222c5f 100644 --- a/static/skin/viewer.js +++ b/static/skin/viewer.js @@ -312,7 +312,7 @@ function setupAutoHidingOfTheToolbar() { } -document.addEventListener('DOMContentLoaded', function () { +function setupSuggestions() { const kiwixSearchBox = document.querySelector('#kiwixsearchbox'); const kiwixSearchFormWrapper = document.querySelector('.kiwix_searchform'); @@ -382,6 +382,12 @@ document.addEventListener('DOMContentLoaded', function () { document.querySelector('label[for="kiwix_button_show_toggle"]').classList.remove('searching'); document.querySelector('.kiwix_button_cont').classList.remove('searching'); }); +} + +function setupViewer() { + handle_visual_viewport_change(); + + setupSuggestions(); // cybook hack if (navigator.userAgent.indexOf("bookeen/cybook") != -1) { @@ -391,4 +397,4 @@ document.addEventListener('DOMContentLoaded', function () { if (document.body.clientWidth < 520) { setupAutoHidingOfTheToolbar(); } -}); +} diff --git a/static/viewer.html b/static/viewer.html index 8ddc940e..c006bcc9 100644 --- a/static/viewer.html +++ b/static/viewer.html @@ -24,7 +24,7 @@ - +
From 796e729f52abaa0a701735deac8b943a6933ffa3 Mon Sep 17 00:00:00 2001 From: Veloman Yunkan Date: Sun, 4 Sep 2022 15:08:47 +0400 Subject: [PATCH 36/42] Library button is disabled by setupViewer() --- static/skin/viewer.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/static/skin/viewer.js b/static/skin/viewer.js index a8222c5f..d3144547 100644 --- a/static/skin/viewer.js +++ b/static/skin/viewer.js @@ -267,10 +267,6 @@ function on_content_load() { window.onresize = handle_visual_viewport_change; window.onhashchange = handle_location_hash_change; -if ( ! viewerSettings.libraryButtonEnabled ) { - document.getElementById("kiwix_serve_taskbar_library_button").remove(); -} - updateCurrentBook(currentBook); handle_location_hash_change(); @@ -385,6 +381,10 @@ function setupSuggestions() { } function setupViewer() { + if ( ! viewerSettings.libraryButtonEnabled ) { + document.getElementById("kiwix_serve_taskbar_library_button").remove(); + } + handle_visual_viewport_change(); setupSuggestions(); From 4e06bb6a08ee36904ec11f1d0745dbb833251394 Mon Sep 17 00:00:00 2001 From: Veloman Yunkan Date: Sun, 4 Sep 2022 17:13:43 +0400 Subject: [PATCH 37/42] Partly fixed auto-hiding of the toolbar Auto hiding of the toolbars on narrow screens works only for the first page loaded in the viewer. Navigating to other pages interferes with autohiding as follows: - If the toolbar was hidden, it stays hidden. - If the toolbar was not hidden, it loses the ability to autohide. --- static/skin/taskbar.css | 10 ---------- static/skin/viewer.js | 7 +++++-- static/viewer.html | 8 ++++---- 3 files changed, 9 insertions(+), 16 deletions(-) diff --git a/static/skin/taskbar.css b/static/skin/taskbar.css index 3d1da25c..ad8ceb0a 100644 --- a/static/skin/taskbar.css +++ b/static/skin/taskbar.css @@ -1,11 +1,5 @@ #kiwixtoolbar { - position: fixed; padding: .5em; - left: 0; - right: 0; - top: 0; - z-index: 100; - background-position-y: 0; transition: 0.3s; width: 100%; box-sizing: border-box; @@ -135,10 +129,6 @@ a.suggest, a.suggest:visited, a.suggest:hover, a.suggest:active { column-count: 1 !important; } -body { - padding-top: calc(3em - 5px) !important; -} - @media(min-width:420px) { .kiwix_button_cont { display: inline-block !important; diff --git a/static/skin/viewer.js b/static/skin/viewer.js index d3144547..28f436e6 100644 --- a/static/skin/viewer.js +++ b/static/skin/viewer.js @@ -281,7 +281,7 @@ function setupAutoHidingOfTheToolbar() { let didScroll = false; const kiwixToolBar = document.querySelector('#kiwixtoolbar'); - window.addEventListener('scroll', () => { + contentIframe.contentWindow.addEventListener('scroll', () => { didScroll = true; }); @@ -293,13 +293,16 @@ function setupAutoHidingOfTheToolbar() { }, 250); function hasScrolled() { - const st = document.documentElement.scrollTop || document.body.scrollTop; + const iframeDoc = contentIframe.contentDocument; + const st = iframeDoc.documentElement.scrollTop || iframeDoc.body.scrollTop; if (Math.abs(lastScrollTop - st) <= delta) return; if (st > lastScrollTop) { + kiwixToolBar.style.position = 'fixed'; kiwixToolBar.style.top = '-100%'; } else { + kiwixToolBar.style.position = 'static'; kiwixToolBar.style.top = '0'; } diff --git a/static/viewer.html b/static/viewer.html index c006bcc9..15838a90 100644 --- a/static/viewer.html +++ b/static/viewer.html @@ -25,8 +25,8 @@ - - +
+
@@ -52,8 +52,8 @@
- - +
+