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 a91297e10..35cd0532d 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 f194c9252..f154ed47d 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 653fadd2f..dcdf7755e 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 000000000..8256d9c1d --- /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 000000000..ea50fecfe --- /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 ea50fecfe..ca059e684 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 ca059e684..b72167f27 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 b72167f27..e6f1d81b6 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 2bce3bb68..feca68b2e 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 085777aab..750cddf61 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 feca68b2e..57a2158ac 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 750cddf61..c10c599c9 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 57a2158ac..2d7d6f3e9 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 c10c599c9..971c21220 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 2d7d6f3e9..93bd9f4b5 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 4b53914c2..d1ceda998 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 85dc8cb8f..9c66f7baf 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 179cd97e3..6696343f8 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 bedc93281..000000000 --- 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 52384b547..000000000 --- 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 fab98abe3..000000000 --- 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 93bd9f4b5..234890d2a 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 @@ class TestContentIn404HtmlResponse : public ExpectedResponseData 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 f154ed47d..0e8dc6aa1 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 d1ceda998..a19c5a7c1 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 9c66f7baf..78b6fc39f 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 @@ class ContentResponseBlueprint } - 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 @@ class ContentResponseBlueprint 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 a19c5a7c1..8cf5c0c62 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 78b6fc39f..34b9676dd 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 0e8dc6aa1..9ff0dddb0 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 8cf5c0c62..a38ef9789 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 34b9676dd..657ecc5d7 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 9ff0dddb0..f249acded 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 bc7f1adae..f6314855f 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 a38ef9789..d82e9e7be 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 657ecc5d7..182210d50 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 f249acded..d996c9ab0 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 82574378c..ee2c20724 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 f6314855f..842c25441 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 d82e9e7be..df0517e92 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 182210d50..ff78ff0a7 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 df0517e92..ddda140ac 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 ff78ff0a7..a19079b64 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 6696343f8..5e1b30f73 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 6bc32baec..000000000 --- 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 971c21220..021c05e0f 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 6bb0a0c21..000000000 --- a/static/templates/external_blocker_part.html +++ /dev/null @@ -1 +0,0 @@ - diff --git a/static/viewer.html b/static/viewer.html index 7948cd17c..51667ffc2 100644 --- a/static/viewer.html +++ b/static/viewer.html @@ -56,7 +56,7 @@ diff --git a/test/server.cpp b/test/server.cpp index 234890d2a..874b6cf36 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 ddda140ac..9bf061df0 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 a19079b64..35d53c342 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 c7ffaf0d2..42bbc6a1b 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 5e540ce94..435763d70 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 9bf061df0..fab72b2c1 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 35d53c342..6a114ba52 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 d996c9ab0..604d8503d 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 ee2c20724..3e4943ad5 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 842c25441..b082dd1c1 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 fab72b2c1..7067ff207 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 6a114ba52..55c8fde4a 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 @@ struct HTTP500Response : HTTPErrorResponse 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 604d8503d..b1bb904e2 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 3e4943ad5..a73a7d424 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 5e1b30f73..4ff6cc3ce 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 021c05e0f..326d3c4c3 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 000000000..26922d393 --- /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 51667ffc2..8ddc940e7 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 b1bb904e2..07bc77791 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 7f29b316e..6bf46db68 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 42bbc6a1b..61fd9f841 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 d76b9c403..42290245e 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 07bc77791..b1bb904e2 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 6bf46db68..7f29b316e 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 61fd9f841..42bbc6a1b 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 42290245e..d76b9c403 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 326d3c4c3..a8222c5ff 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 8ddc940e7..c006bcc9b 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 a8222c5ff..d31445473 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 3d1da25c1..ad8ceb0a5 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 d31445473..28f436e6b 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 c006bcc9b..15838a906 100644 --- a/static/viewer.html +++ b/static/viewer.html @@ -25,8 +25,8 @@ - - +
+
@@ -52,8 +52,8 @@
- - +
+