From 6ae3673e69b99182578423abf26cb2cfcdab1bd0 Mon Sep 17 00:00:00 2001 From: Emmanuel Engelhart Date: Sat, 2 May 2026 16:13:55 +0200 Subject: [PATCH 1/3] Isolate ZIM download modal JS code --- static/resources_list.txt | 1 + static/skin/index.js | 118 +----------------------------------- static/templates/index.html | 1 + 3 files changed, 3 insertions(+), 117 deletions(-) diff --git a/static/resources_list.txt b/static/resources_list.txt index c52095cad..779eada01 100644 --- a/static/resources_list.txt +++ b/static/resources_list.txt @@ -24,6 +24,7 @@ skin/fonts/Poppins.ttf skin/fonts/Roboto.ttf skin/search_results.css skin/blank.html +skin/download.js skin/polyfills.js skin/viewer.js skin/i18n.js diff --git a/static/skin/index.js b/static/skin/index.js index 1818ae793..4ae30ccb8 100644 --- a/static/skin/index.js +++ b/static/skin/index.js @@ -254,122 +254,6 @@ return output.substring(1); } - /* hack for library.kiwix.org magnet links (created by MirrorBrain) - See https://github.com/kiwix/container-images/issues/242 */ - async function getFixedMirrorbrainMagnet(magnetLink) { - // parse as query parameters - const params = new URLSearchParams( - magnetLink.replaceAll('&', '&').replace(/^magnet:/, '')); - - const zimUrl = params.get('as'); // as= is fallback URL - // download metalink to build list of mirrored URLs - let mirrorUrls = []; - - const metalink = await fetch(`${zimUrl}.meta4`).then(response => { - return response.ok ? response.text() : ''; - }).catch((_error) => ''); - if (metalink) { - try { - const parser = new DOMParser(); - const doc = parser.parseFromString(metalink, "application/xml"); - doc.querySelectorAll("url").forEach((node) => { - if (node.hasAttribute("priority")) { // ensures its a mirror link - mirrorUrls.push(node.innerHTML); - } - }); - } catch (err) { - // not a big deal, magnet will only contain primary URL - console.debug(`Failed to parse mirror links for ${zimUrl}`); - } - } - - // set webseed (ws=) URL to primary download URL (redirects to mirror) - params.set('ws', zimUrl); - // if we got metalink mirror URLs, append them all - if (mirrorUrls) { - mirrorUrls.forEach((url) => { - params.append('ws', url); - }); - } - - params.set('xs', `${zimUrl}.torrent`); // adding xs= to point to torrent URL - - return 'magnet:?' + makeURLSearchString(params, ['ws', 'as', 'dn', 'xs', 'tr']); - } - - 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) => ''); - if (magnetLink) { - return await getFixedMirrorbrainMagnet(magnetLink); - } - return magnetLink; - } - - function insertModal(button) { - const downloadSize = button.getAttribute('data-size'); - 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/v2/entries?${query}&count=0`; return await fetch(url).then(async (resp) => { @@ -483,7 +367,7 @@ iso.insert(generateBookHtml(book, sort)) const downloadButton = document.querySelector(`[data-id="${getInnerHtml(book, 'id')}"] .book__download`); if (downloadButton) { - insertModal(downloadButton); + insertDownloadZimModal(downloadButton, `${root}`); } }); refreshTagLinks(); diff --git a/static/templates/index.html b/static/templates/index.html index 31222e9cd..8ed22a98c 100644 --- a/static/templates/index.html +++ b/static/templates/index.html @@ -37,6 +37,7 @@ + From f820e860879eba98187d9f16f3fdee4d0d756c36 Mon Sep 17 00:00:00 2001 From: Emmanuel Engelhart Date: Sat, 2 May 2026 16:15:30 +0200 Subject: [PATCH 2/3] Fixing polyfills.js usage --- static/skin/download.js | 119 ++++++++++++++++++++++++++++++++++++ static/skin/index.js | 4 +- static/templates/index.html | 2 +- 3 files changed, 123 insertions(+), 2 deletions(-) create mode 100644 static/skin/download.js diff --git a/static/skin/download.js b/static/skin/download.js new file mode 100644 index 000000000..59ae2af05 --- /dev/null +++ b/static/skin/download.js @@ -0,0 +1,119 @@ +function downloadButtonText(zimSize) { + return $t("download") + (zimSize ? ` - ${zimSize}`: ''); +} + +/* hack for library.kiwix.org magnet links (created by MirrorBrain) + See https://github.com/kiwix/container-images/issues/242 */ +async function getFixedMirrorbrainMagnet(magnetLink) { + // parse as query parameters + const params = new URLSearchParams( + magnetLink.replaceAll('&', '&').replace(/^magnet:/, '')); + + const zimUrl = params.get('as'); // as= is fallback URL + // download metalink to build list of mirrored URLs + let mirrorUrls = []; + + const metalink = await fetch(`${zimUrl}.meta4`).then(response => { + return response.ok ? response.text() : ''; + }).catch((_error) => ''); + + if (metalink) { + try { + const parser = new DOMParser(); + const doc = parser.parseFromString(metalink, "application/xml"); + doc.querySelectorAll("url").forEach((node) => { + if (node.hasAttribute("priority")) { // ensures its a mirror link + mirrorUrls.push(node.innerHTML); + } + }); + } catch (err) { + // not a big deal, magnet will only contain primary URL + console.debug(`Failed to parse mirror links for ${zimUrl}`); + } + } + + // set webseed (ws=) URL to primary download URL (redirects to mirror) + params.set('ws', zimUrl); + // if we got metalink mirror URLs, append them all + if (mirrorUrls) { + mirrorUrls.forEach((url) => { + params.append('ws', url); + }); + } + + params.set('xs', `${zimUrl}.torrent`); // adding xs= to point to torrent URL + + return 'magnet:?' + makeURLSearchString(params, ['ws', 'as', 'dn', 'xs', 'tr']); +} + +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) => ''); + if (magnetLink) { + return await getFixedMirrorbrainMagnet(magnetLink); + } + return magnetLink; +} + +function insertDownloadZimModal(button, downloadLink, downloadSize, root) { + button.addEventListener('click', async (event) => { + event.preventDefault(); + const magnetLink = await getMagnetLink(downloadLink); + document.body.insertAdjacentHTML('beforeend', ``); + }) +} + diff --git a/static/skin/index.js b/static/skin/index.js index 4ae30ccb8..ae7a02911 100644 --- a/static/skin/index.js +++ b/static/skin/index.js @@ -367,7 +367,9 @@ iso.insert(generateBookHtml(book, sort)) const downloadButton = document.querySelector(`[data-id="${getInnerHtml(book, 'id')}"] .book__download`); if (downloadButton) { - insertDownloadZimModal(downloadButton, `${root}`); + const downloadSize = downloadButton.getAttribute('data-size'); + const downloadLink = downloadButton.getAttribute('data-link'); + insertDownloadZimModal(downloadButton, downloadLink, downloadSize, `${root}`); } }); refreshTagLinks(); diff --git a/static/templates/index.html b/static/templates/index.html index 8ed22a98c..55bdea352 100644 --- a/static/templates/index.html +++ b/static/templates/index.html @@ -32,7 +32,7 @@ - + From e24cca9178793a1b4b7df6b38b61fb7713834609 Mon Sep 17 00:00:00 2001 From: Emmanuel Engelhart Date: Mon, 11 May 2026 06:02:32 +0200 Subject: [PATCH 3/3] Add download button to the kiwix-serve topbar --- static/viewer.html | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/static/viewer.html b/static/viewer.html index f5eebd56f..1730ad843 100644 --- a/static/viewer.html +++ b/static/viewer.html @@ -26,6 +26,7 @@ +