diff --git a/src/server/internalServer.cpp b/src/server/internalServer.cpp index 89b2efb13..76d1494f7 100644 --- a/src/server/internalServer.cpp +++ b/src/server/internalServer.cpp @@ -97,14 +97,19 @@ inline std::string normalizeRootUrl(std::string rootUrl) std::string fullURL2LocalURL(const std::string& fullUrl, const std::string& rootLocation) { - assert(rootLocation.size() > 0 && rootLocation.back() == '/'); if ( kiwix::startsWith(fullUrl, rootLocation) ) { - return fullUrl.substr(rootLocation.size() - 1); + return fullUrl.substr(rootLocation.size()); } else { - return ""; + return "INVALID URL"; } } +std::string getSearchComponent(const RequestContext& request) +{ + const std::string query = request.get_query(); + return query.empty() ? query : "?" + query; +} + Filter get_search_filter(const RequestContext& request, const std::string& prefix="") { auto filter = kiwix::Filter().valid(true).local(true); @@ -415,7 +420,7 @@ InternalServer::InternalServer(Library* library, m_addr(addr), m_port(port), m_root(normalizeRootUrl(root)), - m_rootPrefixOfDecodedURL(m_root + "/"), + m_rootPrefixOfDecodedURL(m_root), m_nbThreads(nbThreads), m_multizimSearchLimit(multizimSearchLimit), m_verbose(verbose), @@ -585,6 +590,13 @@ std::unique_ptr InternalServer::handle_request(const RequestContext& r + urlNotFoundMsg; } + if ( request.get_url() == "" ) { + // Redirect /ROOT_LOCATION to /ROOT_LOCATION/ (note the added slash) + // so that relative URLs are resolved correctly + const std::string query = getSearchComponent(request); + return Response::build_redirect(*this, m_root + "/" + query); + } + const ETag etag = get_matching_if_none_match_etag(request, getLibraryId()); if ( etag ) return Response::build_304(*this, etag); @@ -623,11 +635,9 @@ std::unique_ptr InternalServer::handle_request(const RequestContext& r if (isEndpointUrl(url, "catch")) return handle_catch(request); - std::string contentUrl = m_root + "/content" + urlEncode(url); - const std::string query = request.get_query(); - if ( ! query.empty() ) - contentUrl += "?" + query; - return Response::build_redirect(*this, contentUrl); + const std::string contentUrl = m_root + "/content" + urlEncode(url); + const std::string query = getSearchComponent(request); + return Response::build_redirect(*this, contentUrl + query); } catch (std::exception& e) { fprintf(stderr, "===== Unhandled error : %s\n", e.what()); return HTTP500Response(*this, request) diff --git a/src/server/request_context.cpp b/src/server/request_context.cpp index 017521666..9e0b965c0 100644 --- a/src/server/request_context.cpp +++ b/src/server/request_context.cpp @@ -181,7 +181,7 @@ std::string RequestContext::get_root_path() const { } bool RequestContext::is_valid_url() const { - return !url.empty(); + return url.empty() || url[0] == '/'; } ByteRange RequestContext::get_range() const { diff --git a/static/skin/i18n.js b/static/skin/i18n.js index d451f11b4..53c11c7f9 100644 --- a/static/skin/i18n.js +++ b/static/skin/i18n.js @@ -97,6 +97,62 @@ function setUserLanguage(lang, callback) { Translations.whenReady(callback); } +function createModalUILanguageSelector() { + document.body.insertAdjacentHTML('beforeend', + ``); + + window.modalUILanguageSelector = { + show: () => { + document.getElementById('uiLanguageSelector').style.display = 'flex'; + }, + + close: () => { + document.getElementById('uiLanguageSelector').style.display = 'none'; + } + }; +} + +function initUILanguageSelector(activeLanguage, languageChangeCallback) { + if ( document.getElementById("ui_language") == null ) { + createModalUILanguageSelector(); + } + const languageSelector = document.getElementById("ui_language"); + for (const lang of uiLanguages ) { + const lang_name = Object.getOwnPropertyNames(lang)[0]; + const lang_code = lang[lang_name]; + const is_selected = lang_code == activeLanguage; + languageSelector.appendChild(new Option(lang_name, lang_code, is_selected, is_selected)); + } + languageSelector.onchange = languageChangeCallback; +} + window.$t = $t; window.getUserLanguage = getUserLanguage; window.setUserLanguage = setUserLanguage; +window.initUILanguageSelector = initUILanguageSelector; diff --git a/static/skin/i18n/en.json b/static/skin/i18n/en.json index bf8441fc1..406e0def2 100644 --- a/static/skin/i18n/en.json +++ b/static/skin/i18n/en.json @@ -28,4 +28,22 @@ , "random-page-button-text": "Go to a randomly selected page" , "searchbox-tooltip": "Search '{{BOOK_TITLE}}'" , "confusion-of-tongues": "Two or more books in different languages would participate in search, which may lead to confusing results." + , "welcome-page-overzealous-filter": "No result. Would you like to reset filter?" + , "powered-by-kiwix-html": "Powered by Kiwix" + , "search": "Search" + , "book-filtering-all-categories": "All categories" + , "book-filtering-all-languages": "All languages" + , "count-of-matching-books": "{{COUNT}} book(s)" + , "download": "Download" + , "direct-download-link-text": "Direct" + , "direct-download-alt-text": "direct download" + , "hash-download-link-text": "Sha256 hash" + , "hash-download-alt-text": "download hash" + , "magnet-link-text": "Magnet link" + , "magnet-alt-text": "download magnet" + , "torrent-download-link-text": "Torrent file" + , "torrent-download-alt-text": "download torrent" + , "library-opds-feed": "Library OPDS Feed" + , "filter-by-tag": "Filter by tag \"{{TAG}}\"" + , "stop-filtering-by-tag": "Stop filtering by tag \"{{TAG}}\"" } diff --git a/static/skin/i18n/qqq.json b/static/skin/i18n/qqq.json index 2175d24a1..62089d562 100644 --- a/static/skin/i18n/qqq.json +++ b/static/skin/i18n/qqq.json @@ -29,5 +29,23 @@ "library-button-text": "Tooltip of the button leading to the welcome page", "home-button-text": "Tooltip of the button leading to the main page of a book", "random-page-button-text": "Tooltip of the button opening a randomly selected page", - "searchbox-tooltip": "Tooltip displayed for the search box" + "searchbox-tooltip": "Tooltip displayed for the search box in the viewer" + , "welcome-page-overzealous-filter": "Text shown when book filtering on the welcome page produces zero results" + , "powered-by-kiwix-html": "Link to Kiwix website" + , "search": "A general search action (text displayed on search buttons or as aplaceholder in searchboxes)" + , "book-filtering-all-categories": "Choosing this filter will disable filtering of books by category" + , "book-filtering-all-languages": "Choosing this filter will disable filtering of books by language" + , "count-of-matching-books": "Reporting the count of books matching the filter" + , "download": "A general download action" + , "direct-download-link-text": "Link text for a direct download" + , "direct-download-alt-text": "Hint for a direct download icon" + , "hash-download-link-text": "Link text for downloading the hash" + , "hash-download-alt-text": "Hint for the icon of hash download" + , "magnet-link-text": "Link text for a magnet link" + , "magnet-alt-text": "Hint for the icon of a magnet link" + , "torrent-download-link-text": "Link text for downloading the torrent file" + , "torrent-download-alt-text": "Hint for the icon of torrent download" + , "library-opds-feed": "Hint for the library OPDS feed link" + , "filter-by-tag": "Hint for a link that would load results filtered by a single tag" + , "stop-filtering-by-tag": "Tooltip for the button that cancels filtering by tag" } diff --git a/static/skin/i18n/test.json b/static/skin/i18n/test.json index a96eac241..cc4a2acef 100644 --- a/static/skin/i18n/test.json +++ b/static/skin/i18n/test.json @@ -17,4 +17,22 @@ , "home-button-text": "[I18N TESTING] Jump to the main page of '{{BOOK_TITLE}}'" , "random-page-button-text": "[I18N TESTING] I am tired of determinism" , "searchbox-tooltip": "[I18N TESTING] Let's search in '{{BOOK_TITLE}}'" + , "welcome-page-overzealous-filter": "[I18N TESTING] Nothing found. Reset filter" + , "powered-by-kiwix-html": "[I18N TESTING] Powered by Kiwix (nominal power: 1.23 kW)" + , "search": "[I18N Search TESTING]" + , "book-filtering-all-categories": "All [I18N TESTING] categories" + , "book-filtering-all-languages": "All [I18N TESTING] languages" + , "count-of-matching-books": "[I18N TESTING] Number of matching books: {{COUNT}}" + , "download": "[I18N Download TESTING]" + , "direct-download-link-text": "[I18N TESTING] HTTP(S)" + , "direct-download-alt-text": "[I18N TESTING] download directly" + , "hash-download-link-text": "Sha256 [I18N TESTING] hash" + , "hash-download-alt-text": "download [I18N TESTING] hash" + , "magnet-link-text": "Magnet [I18N TESTING] link" + , "magnet-alt-text": "download [I18N TESTING] magnet" + , "torrent-download-link-text": "Torrent [I18N TESTING] file" + , "torrent-download-alt-text": "download [I18N TESTING] torrent" + , "library-opds-feed": "Library [I18N] OPDS [TESTING] Feed" + , "filter-by-tag": "Filter [I18N] by [TESTING] tag \"{{TAG}}\"" + , "stop-filtering-by-tag": "[I18N] Stop filtering [TESTING] by tag \"{{TAG}}\"" } diff --git a/static/skin/index.css b/static/skin/index.css index ae9294fa2..b9c1cb4bc 100644 --- a/static/skin/index.css +++ b/static/skin/index.css @@ -157,6 +157,28 @@ body { font-weight: bolder; } +#uiLanguageSelector { + display: none; +} + +#uiLanguageSelector .modal { + height: 140px; +} + +#uiLanguageSelector .modal-heading { + height: 40%; +} + +#uiLanguageSelector .modal-content #ui_language { + font-size: 1.6rem; + width: 100%; +} + +#uiLanguageSelectorButton { + margin: 16px 12px 0 0; + float: right; +} + .book__list { position: relative; margin: 0 auto; @@ -472,11 +494,11 @@ body { .kiwixHomeBody { min-height: calc(100vh - 287px); } - + .kiwixSearch { margin-top: 11px; } - + .kiwixButton { margin: 15px 0; width: 229px; @@ -489,8 +511,4 @@ body { .kiwixNav__filters { grid-template-columns: 1fr; } - - .feedLogo { - display: none; - } } diff --git a/static/skin/index.js b/static/skin/index.js index 036e18516..52328eb54 100644 --- a/static/skin/index.js +++ b/static/skin/index.js @@ -15,6 +15,7 @@ let noResultInjected = false; let filters = getCookie(filterCookieName); let params = new URLSearchParams(window.location.search || filters || ''); + params.delete('userlang'); let timer; let languages = {}; @@ -31,6 +32,14 @@ document.querySelector('#feedLink').href = feedLink; } + function changeUILanguage() { + window.modalUILanguageSelector.close(); + const s = document.getElementById("ui_language"); + const lang = s.options[s.selectedIndex].value; + setPermanentGlobalCookie('userlang', lang); + window.location.reload(); + } + function queryUrlBuilder() { let url = `${root}/catalog/search?`; url += Object.keys(incrementalLoadingParams).map(key => `${key}=${incrementalLoadingParams[key]}`).join("&"); @@ -38,10 +47,14 @@ return (url); } - function setCookie(cookieName, cookieValue) { - const date = new Date(); - date.setTime(date.getTime() + oneDayDelta); - document.cookie = `${cookieName}=${cookieValue};expires=${date.toUTCString()};sameSite=Strict`; + function setCookie(cookieName, cookieValue, ttl) { + let exp = ""; + if ( ttl ) { + const date = new Date(); + date.setTime(date.getTime() + ttl); + exp = `expires=${date.toUTCString()};`; + } + document.cookie = `${cookieName}=${cookieValue};${exp}sameSite=Strict`; } function getCookie(cookieName) { @@ -93,7 +106,7 @@ function generateTagLink(tagValue) { tagValue = tagValue.toLowerCase(); const humanFriendlyTagValue = humanFriendlyTitle(tagValue); - const tagMessage = `Filter by tag "${humanFriendlyTagValue}"`; + const tagMessage = $t("filter-by-tag", {TAG: humanFriendlyTagValue}); return `${humanFriendlyTagValue}` } @@ -143,7 +156,7 @@
${title}
- ${downloadLink ? `
Download ${humanFriendlyZimSize ? ` - ${humanFriendlyZimSize}
`: ''}` : ''} + ${downloadLink ? `
${$t("download")} ${humanFriendlyZimSize ? ` - ${humanFriendlyZimSize}
`: ''}` : ''}
${description}
@@ -209,27 +222,27 @@ @@ -262,16 +275,10 @@ } else { toggleFooter(); } - const kiwixResultText = document.querySelector('.kiwixHomeBody__results') - if (results) { - let resultText = `${results} books`; - if (results === 1) { - resultText = `${results} book`; - } - kiwixResultText.innerHTML = resultText; - } else { - kiwixResultText.innerHTML = ``; - } + const text = results + ? $t("count-of-matching-books", {COUNT: results}) + : ''; + document.querySelector('.kiwixHomeBody__results').innerHTML = text; loader.style.display = 'none'; return books; }); @@ -298,7 +305,7 @@ const kiwixHomeBody = document.querySelector('.kiwixHomeBody'); const divTag = document.createElement('div'); divTag.setAttribute('class', 'noResults'); - divTag.innerHTML = `No result. Would you like to reset filter?`; + divTag.innerHTML = $t("welcome-page-overzealous-filter"); kiwixHomeBody.append(divTag); kiwixHomeBody.setAttribute('style', 'display: flex; justify-content: center; align-items: center'); loader.setAttribute('style', 'position: absolute; top: 50%'); @@ -373,7 +380,7 @@ if (filterType) { params.set(filterType, filterValue); window.history.pushState({}, null, `?${params.toString()}`); - setCookie(filterCookieName, params.toString()); + setCookie(filterCookieName, params.toString(), oneDayDelta); } updateFilterColors(); updateFeedLink(); @@ -411,7 +418,7 @@ tagElement.style.display = 'inline-block'; const humanFriendlyTagValue = humanFriendlyTitle(tagValue); tagElement.innerHTML = `${humanFriendlyTagValue}`; - const tagMessage = `Stop filtering by tag "${humanFriendlyTagValue}"`; + const tagMessage = $t("stop-filtering-by-tag", {TAG: humanFriendlyTagValue}); tagElement.setAttribute('aria-label', tagMessage); tagElement.setAttribute('title', tagMessage); if (resetFilter) @@ -462,7 +469,22 @@ } }); - window.onload = async () => { + function updateUIText() { + footer.innerHTML = $t("powered-by-kiwix-html"); + const searchText = $t("search"); + document.getElementById('searchFilter').placeholder = searchText; + document.getElementById('searchButton').value = searchText; + document.getElementById('categoryFilter').children[0].innerHTML = $t("book-filtering-all-categories"); + document.getElementById('languageFilter').children[0].innerHTML = $t("book-filtering-all-languages"); + const feedLogoElem = document.getElementById('feedLogo'); + const libraryOpdsFeedHint = $t("library-opds-feed"); + for (const attr of ["alt", "aria-label", "title"] ) { + feedLogoElem.setAttribute(attr, libraryOpdsFeedHint); + } + } + + async function onload() { + initUILanguageSelector(getUserLanguage(), changeUILanguage); iso = new Isotope( '.book__list', { itemSelector: '.book', getSortData:{ @@ -478,6 +500,7 @@ } }); footer = document.getElementById('kiwixfooter'); + updateUIText(); fadeOutDiv = document.getElementById('fadeOut'); loader = document.querySelector('.loader'); await loadAndDisplayOptions('#languageFilter', `${root}/catalog/v2/languages`, 'language'); @@ -507,7 +530,14 @@ } } updateFeedLink(); - setCookie(filterCookieName, params.toString()); + setCookie(filterCookieName, params.toString(), oneDayDelta); + }; + + // required by i18n.js:setUserLanguage() + window.setPermanentGlobalCookie = function(name, value) { + document.cookie = `${name}=${value};path=${root};max-age=31536000`; } + + window.onload = () => { setUserLanguage(getUserLanguage(), onload); } })(); diff --git a/static/skin/taskbar.css b/static/skin/taskbar.css index 87fbdc319..b67e9b9ee 100644 --- a/static/skin/taskbar.css +++ b/static/skin/taskbar.css @@ -44,10 +44,6 @@ margin: 0 auto; } -.kiwix #ui_language { - float: left; -} - #kiwix_button_show_toggle { display: none; } @@ -84,7 +80,6 @@ label[for="kiwix_button_show_toggle"], float: right; } -.kiwix #ui_language, .kiwix #kiwixtoolbar button, .kiwix #kiwixtoolbar input[type="submit"] { box-sizing: border-box !important; @@ -134,6 +129,79 @@ a.suggest, a.suggest:visited, a.suggest:hover, a.suggest:active { column-count: 1 !important; } +.modal-wrapper { + position: fixed; + z-index: 100; + top: 0; + left: 0; + width: 100vw; + height: 100vh; + display: flex; + justify-content: center; + align-items: center; + align-content: center; + background-color: rgba(0, 0, 0, 30%); +} + +.modal { + color: #444343; + height: 280px; + width: 250px; + margin: 15px; + background-color: #f7f7f7; + border: 1px solid #ececec; + border-radius: 3px; +} + +.modal-heading { + background-color: #f0f0f0; + height: 20%; + width: 100%; + border-bottom: 1px solid #ececec; + display: grid; + grid-template-columns: 3fr 1fr; +} + +.modal-title { + display: flex; + font-size: 15px; + align-items: center; + padding-left: 20px; + font-family: poppins; +} + +.modal-close-button { + cursor: pointer; + display: flex; + justify-content: center; + align-items: center; +} + +.modal-content { + padding: 20px; +} + +#uiLanguageSelector { + display: none; +} + +#uiLanguageSelector .modal { + height: 140px; +} + +#uiLanguageSelector .modal-heading { + height: 40%; +} + +#uiLanguageSelector .modal-content #ui_language { + width: 100%; +} + +#uiLanguageSelectorButton { + margin: 0px 12px 6px 12px; + float: right; +} + @media(min-width:420px) { .kiwix_button_cont { display: inline-block !important; diff --git a/static/skin/viewer.js b/static/skin/viewer.js index db7bc2ba8..2c3c80a14 100644 --- a/static/skin/viewer.js +++ b/static/skin/viewer.js @@ -416,16 +416,6 @@ function makeURL(search, hash) { return url; } -function initUILanguageSelector() { - const languageSelector = document.getElementById("ui_language"); - for (const lang of uiLanguages ) { - const lang_name = Object.getOwnPropertyNames(lang)[0]; - const lang_code = lang[lang_name]; - const is_selected = lang_code == viewerState.uiLanguage; - languageSelector.appendChild(new Option(lang_name, lang_code, is_selected, is_selected)); - } -} - function updateUILanguageSelector(userLang) { console.log(`updateUILanguageSelector(${userLang})`); const languageSelector = document.getElementById("ui_language"); @@ -446,6 +436,7 @@ function handle_history_state_change(event) { } function changeUILanguage() { + window.modalUILanguageSelector.close(); const s = document.getElementById("ui_language"); const lang = s.options[s.selectedIndex].value; viewerState.uiLanguage = lang; @@ -481,7 +472,7 @@ function setupViewer() { document.getElementById("kiwix_serve_taskbar_library_button").remove(); } - initUILanguageSelector(); + initUILanguageSelector(viewerState.uiLanguage, changeUILanguage); setupSuggestions(); // cybook hack diff --git a/static/templates/index.html b/static/templates/index.html index 0274e9a3a..6bbe6d126 100644 --- a/static/templates/index.html +++ b/static/templates/index.html @@ -10,11 +10,11 @@ href="{{root}}/skin/index.css?KIWIXCACHEID" rel="Stylesheet" /> - @@ -37,13 +37,31 @@ src: url("{{root}}/skin/fonts/Roboto.ttf?KIWIXCACHEID") format("truetype"); } + + - + + + + + + + +
@@ -61,7 +79,7 @@
- +
@@ -76,7 +94,11 @@ diff --git a/static/viewer.html b/static/viewer.html index 4c66abbcd..571bc5780 100644 --- a/static/viewer.html +++ b/static/viewer.html @@ -29,9 +29,17 @@