(function() { const root = document.querySelector(`link[type='root']`).getAttribute('href'); const incrementalLoadingParams = { start: 0, count: viewPortToCount() }; const bookOrderMap = new Map(); const filterCookieName = 'filters'; const oneDayDelta = 86400000; let loader; let footer; let fadeOutDiv; let iso; let isFetching = false; let noResultInjected = false; let filters = getCookie(filterCookieName); let params = new URLSearchParams(window.location.search || filters || ''); params.delete('userlang'); let timer; let languages = {}; function updateFeedLink() { const inputParams = new URLSearchParams(window.location.search); const filteredParams = new URLSearchParams(); for (const [key, value] of inputParams) { if ( value != '' ) { filteredParams.append(key, value); } } const feedLink = `${root}/catalog/v2/entries?${filteredParams.toString()}`; document.querySelector('#headFeedLink').href = feedLink; 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("&"); params.forEach((value, key) => {url+= value ? `&${key}=${value}` : ''}); return (url); } 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) { const name = cookieName + "="; let result; decodeURIComponent(document.cookie).split('; ').forEach(val => { if (val.indexOf(name) === 0) { result = val.substring(name.length); } }); return result; } const humanFriendlySize = (fileSize) => { if (fileSize === 0) { return ''; } const units = ['bytes', 'kB', 'MB', 'GB', 'TB']; let quotient = Math.floor(Math.log10(fileSize) / 3); quotient = quotient < units.length ? quotient : units.length - 1; fileSize /= (1000 ** quotient); return `${+fileSize.toFixed(2)} ${units[quotient]}`; }; const humanFriendlyTitle = (title) => { if (typeof title === 'string' && title.length > 0) { title = title.replace(/_/g, ' '); if (title.length > 0) { return htmlEncode(title[0].toUpperCase() + title.slice(1)); } } return ''; } function htmlEncode(str) { return str.replace(/[\u00A0-\u9999<>\&]/gim, (i) => `&#${i.charCodeAt(0)};`); } function viewPortToCount(){ const zoom = Math.floor((( window.outerWidth - 10 ) / window.innerWidth) * 100); return Math.floor((window.innerHeight/(3*zoom) + 1)*(window.innerWidth/(2.5*zoom) + 1)); } function getInnerHtml(node, query) { const queryNode = node.querySelector(query); return queryNode != null ? queryNode.innerHTML : ""; } function generateTagLink(tagValue) { tagValue = tagValue.toLowerCase(); const humanFriendlyTagValue = humanFriendlyTitle(tagValue); const tagMessage = `Filter by tag "${humanFriendlyTagValue}"`; return `${humanFriendlyTagValue}` } function generateBookHtml(book, sort = false) { const link = book.querySelector('link[type="text/html"]').getAttribute('href'); let iconUrl; book.querySelectorAll('link[rel="http://opds-spec.org/image/thumbnail"]').forEach(link => { if (link.getAttribute('type').split(';')[1] == 'width=48' && !iconUrl) { iconUrl = link.getAttribute('href'); } }); const title = getInnerHtml(book, 'title'); const description = getInnerHtml(book, 'summary'); const id = getInnerHtml(book, 'id'); const langCode = getInnerHtml(book, 'language'); const language = languages[langCode]; const tags = getInnerHtml(book, 'tags'); const tagList = tags.split(';').filter(tag => {return !(tag.startsWith('_'))}); const tagFilterLinks = tagList.map((tagValue) => generateTagLink(tagValue)); const tagHtml = tagFilterLinks.join(' | '); let downloadLink; let zimSize = 0; try { const downloadBookLink = book.querySelector('link[type="application/x-zim"]') zimSize = parseInt(downloadBookLink.getAttribute('length')); downloadLink = downloadBookLink.getAttribute('href').split('.meta4')[0]; } catch { downloadLink = ''; } const bookName = link.split('/').pop(); const viewerLink = `${root}/viewer#${bookName}`; const humanFriendlyZimSize = humanFriendlySize(zimSize); const divTag = document.createElement('div'); divTag.setAttribute('class', 'book'); divTag.setAttribute('data-id', id); if (sort) { divTag.setAttribute('data-idx', bookOrderMap.get(id)); } const faviconAttr = iconUrl != undefined ? `style="background-image: url('${iconUrl}')"` : ''; const languageAttr = langCode != '' ? `title="${language}" aria-label="${language}"` : 'style="background-color: transparent"'; divTag.innerHTML = `
${getLanguageCodeToDisplay(langCode)}
${tagHtml}
`; return divTag; } function getLanguageCodeToDisplay(langCode3Letter) { const langCode2Letter = (Object.keys(iso6391To3).find(key => iso6391To3[key] === langCode3Letter)); const res = (langCode2Letter != undefined) ? langCode2Letter : langCode3Letter; return res.toUpperCase(); } function toggleFooter(show=false) { if (show) { footer.style.display = 'block'; } else { footer.style.display = 'none'; fadeOutDiv.style.display = 'block'; } } async function getMagnetLink(downloadLink) { const magnetUrl = downloadLink + '.magnet'; const controller = new AbortController(); setTimeout(() => controller.abort(), 5000); const magnetLink = await fetch(magnetUrl, { signal: controller.signal }).then(response => { return response.ok ? response.text() : ''; }).catch((_error) => ''); return magnetLink; } function insertModal(button) { const downloadLink = button.getAttribute('data-link'); button.addEventListener('click', async (event) => { event.preventDefault(); const magnetLink = await getMagnetLink(downloadLink); document.body.insertAdjacentHTML('beforeend', ``); }) } async function getBookCount(query) { const url = `${root}/catalog/search?${query}`; return await fetch(url).then(async (resp) => { const data = new window.DOMParser().parseFromString(await resp.text(), 'application/xml'); return parseInt(data.querySelector('totalResults').innerHTML); }); } async function loadBooks() { loader.style.display = 'block'; return await fetch(queryUrlBuilder()).then(async (resp) => { const data = new window.DOMParser().parseFromString(await resp.text(), 'application/xml'); const books = data.querySelectorAll('entry'); books.forEach((book, idx) => { bookOrderMap.set(getInnerHtml(book, 'id'), idx); }); incrementalLoadingParams.start += books.length; const results = parseInt(data.querySelector('totalResults').innerHTML) if (results === bookOrderMap.size) { incrementalLoadingParams.count = 0; toggleFooter(true); } else { toggleFooter(); } const text = results ? $t("count-of-matching-books", {COUNT: results}) : ''; document.querySelector('.kiwixHomeBody__results').innerHTML = text; loader.style.display = 'none'; return books; }); } async function loadAndDisplayOptions(nodeQuery, query, valueEntryNode) { await fetch(query).then(async (resp) => { const data = new window.DOMParser().parseFromString(await resp.text(), 'application/xml'); let optionStr = ''; data.querySelectorAll('entry').forEach(entry => { const title = getInnerHtml(entry, 'title'); const value = getInnerHtml(entry, valueEntryNode); const hfTitle = humanFriendlyTitle(title); if (valueEntryNode == 'language') { languages[value] = hfTitle; } optionStr += (hfTitle != '') ? `` : ''; }); document.querySelector(nodeQuery).innerHTML += optionStr; }); } function setNoResultsContent() { const kiwixHomeBody = document.querySelector('.kiwixHomeBody'); const divTag = document.createElement('div'); divTag.setAttribute('class', 'noResults'); 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%'); } function checkAndInjectEmptyMessage() { const kiwixHomeBody = document.querySelector('.kiwixHomeBody'); if (!bookOrderMap.size) { if (!noResultInjected) { noResultInjected = true; iso.remove(document.getElementsByClassName('book__list')[0].getElementsByTagName('div')); iso.layout(); setTimeout(setNoResultsContent, 300); } return true; } else if (noResultInjected) { noResultInjected = false; document.getElementsByClassName('noResults')[0].remove(); kiwixHomeBody.removeAttribute('style'); } loader.removeAttribute('style'); return false; } async function loadAndDisplayBooks(sort = false) { if (isFetching) return; isFetching = true; await loadAndDisplayBooksUnguarded(sort); isFetching = false; } async function loadAndDisplayBooksUnguarded(sort) { let books = await loadBooks(); if (checkAndInjectEmptyMessage()) {return} const booksToFilter = new Set(); const booksToDelete = new Set(); iso.arrange({ filter: function (elem) { const id = elem.getAttribute('data-id'); const retVal = bookOrderMap.has(id); if (retVal) { booksToFilter.add(id); if (sort) { elem.setAttribute('data-idx', bookOrderMap.get(id)); iso.updateSortData(elem); } } else { booksToDelete.add(elem); } return retVal; } }); books = [...books].filter((book) => {return !booksToFilter.has(getInnerHtml(book, 'id'))}); booksToDelete.forEach(book => {iso.remove(book);}); books.forEach((book) => { iso.insert(generateBookHtml(book, sort)) const downloadButton = document.querySelector(`[data-id="${getInnerHtml(book, 'id')}"] .book__download span`); if (downloadButton) { insertModal(downloadButton); } }); refreshTagLinks(); } async function resetAndFilter(filterType = '', filterValue = '') { isFetching = false; incrementalLoadingParams.start = 0; incrementalLoadingParams.count = viewPortToCount(); fadeOutDiv.style.display = 'none'; bookOrderMap.clear(); params = new URLSearchParams(window.location.search); if (filterType) { params.set(filterType, filterValue); window.history.pushState({}, null, `?${params.toString()}`); setCookie(filterCookieName, params.toString(), oneDayDelta); } updateFilterColors(); updateFeedLink(); await loadAndDisplayBooks(true); } window.addEventListener('popstate', async () => { await resetAndFilter(); updateVisibleParams(); }); async function loadSubset() { if (window.innerHeight + window.scrollY >= (document.body.offsetHeight * 0.98)) { if (incrementalLoadingParams.count) { loadAndDisplayBooks(); } else { fadeOutDiv.style.display = 'none'; } } } function updateFilterColors() { document.querySelectorAll('.filter').forEach(filter => { if (filter.value) { filter.style = 'background-color: #858585; color: #fff'; } else { filter.style = 'background-color: #ffffff; color: black'; } }); } function addTagElement(tagValue, resetFilter) { const tagElement = document.getElementsByClassName('tagFilterLabel')[0]; tagElement.style.display = 'inline-block'; const humanFriendlyTagValue = humanFriendlyTitle(tagValue); tagElement.innerHTML = `${humanFriendlyTagValue}`; const tagMessage = `Stop filtering by tag "${humanFriendlyTagValue}"`; tagElement.setAttribute('aria-label', tagMessage); tagElement.setAttribute('title', tagMessage); if (resetFilter) resetAndFilter('tag', tagValue); } function refreshTagLinks() { const tagLinks = document.getElementsByClassName('tag__link'); [...tagLinks].forEach(elem => { if (!elem.getAttribute('click-listener')) { elem.addEventListener('click', () => addTagElement(elem.dataset.tag, true)); elem.setAttribute('click-listener', 'true'); } }); } function removeTagElement(resetFilter) { const tagElement = document.getElementsByClassName('tagFilterLabel')[0]; tagElement.style.display = 'none'; if (resetFilter) resetAndFilter('tag', ''); } function updateVisibleParams() { document.querySelectorAll('.filter').forEach(filter => {filter.value = params.get(filter.name) || ''}); updateFilterColors(); const tagKey = params.get('tag'); if (tagKey !== null && tagKey.trim() !== '') { addTagElement(tagKey, false); } else { removeTagElement(false); } } window.addEventListener('resize', (event) => { if (timer) {clearTimeout(timer)} timer = setTimeout(() => { incrementalLoadingParams.count = incrementalLoadingParams.count && viewPortToCount(); loadSubset(); }, 100, event); }); window.addEventListener('scroll', loadSubset); window.addEventListener('keydown', function (event) { if (event.key === "Escape" ) { closeModal(); } }); 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:{ weight: function( itemElem ) { const index = itemElem.getAttribute('data-idx'); return index ? parseInt(index) : Infinity; } }, sortBy: 'weight', layoutMode: 'masonry', masonry: { fitWidth: true } }); footer = document.getElementById('kiwixfooter'); updateUIText(); fadeOutDiv = document.getElementById('fadeOut'); loader = document.querySelector('.loader'); await loadAndDisplayOptions('#languageFilter', `${root}/catalog/v2/languages`, 'language'); await loadAndDisplayOptions('#categoryFilter', `${root}/catalog/v2/categories`, 'title'); await loadAndDisplayBooks(); document.querySelectorAll('.filter').forEach(filter => { filter.addEventListener('change', () => {resetAndFilter(filter.name, filter.value)}); }); const tagElement = document.getElementsByClassName('tagFilterLabel')[0]; tagElement.addEventListener('click', () => removeTagElement(true)); if (filters) { const currentLink = window.location.search; const newLink = `?${params.toString()}`; if (currentLink != newLink) { window.history.pushState({}, null, newLink); } } updateVisibleParams(); document.getElementById('kiwixSearchForm').onsubmit = (event) => {event.preventDefault()}; if (!window.location.search) { const browserLang = navigator.language.split('-')[0]; const langFilter = document.getElementById('languageFilter'); const lang = browserLang.length === 3 ? browserLang : iso6391To3[browserLang]; if (await getBookCount(`lang=${lang}`)) { langFilter.value = lang; langFilter.dispatchEvent(new Event('change')); } } updateFeedLink(); 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); } })();