MediaWiki
LiLiZip.js
Note: After saving, you may have to bypass your browser's cache to see the changes.
- Firefox / Safari: Hold Shift while clicking Reload, or press either Ctrl-F5 or Ctrl-R (⌘-R on a Mac)
- Google Chrome: Press Ctrl-Shift-R (⌘-Shift-R on a Mac)
- Internet Explorer / Edge: Hold Ctrl while clicking Refresh, or press Ctrl-F5
- Opera: Go to Menu → Settings (Opera → Preferences on a Mac) and then to Privacy & security → Clear browsing data → Cached images and files.
/**
@description: Manual mirror of https://commons.wikimedia.org/wiki/User:WikiLucas00/LinguaZip. Allows downloading files for a given word as a ZIP archive.
@revision: 2024-08
@required modules: mediawiki.util, mediawiki.user, jquery.ui, ext.gadget.libCommons, ext.gadget.libJQuery, ext.gadget.libUtil, JSZip, FileSaver
* <nowiki>
**/
(function (mw, $) {
"use strict";
var downloadQueue = [],
downloadCount = 0,
zip,
zipFileName,
isCancelled = false,
librariesLoaded = false;
// Asynchronous loading of JSZip and FileSaver libraries
$.when(
$.getScript("https://tools-static.wmflabs.org/cdnjs/ajax/libs/jszip/3.10.1/jszip.min.js"),
$.getScript("https://tools-static.wmflabs.org/cdnjs/ajax/libs/FileSaver.js/2.0.2/FileSaver.min.js")
).done(function() {
librariesLoaded = true;
});
function createInterface() {
var container = document.createElement('div');
container.innerHTML = `
<div id="toolContainer" style="font-family: 'Charter', serif; max-width: 500px; margin: 50px auto; text-align: center; padding: 20px; border-radius: 10px; background-color: #fef6e7; box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);">
<div id="iconContainer" style="margin-bottom: 20px;">
<a href="https://commons.wikimedia.org/wiki/User:WikiLucas00/LinguaZip">
<img src="https://upload.wikimedia.org/wikipedia/commons/thumb/0/08/LinguaZip_icon.png/480px-LinguaZip_icon.png" alt="LinguaZip icon" style="width: 50%; border-radius: 10px;">
</a>
</div>
<h1 style="font-weight: bold; color: #3366CC; margin-bottom: 20px;"><a href="https://commons.wikimedia.org/wiki/User:WikiLucas00/LinguaZip">LinguaZip.js</a></h1>
<div id="inputContainer" style="margin-bottom: 20px;">
<input type="text" id="wordInput" placeholder="Enter a word to query" style="padding: 10px; border-radius: 5px; border: 1px solid #ccc; width: 100%; box-sizing: border-box; margin-bottom: 10px;">
<button id="submitWord" style="padding: 10px 20px; border-radius: 5px; background-color: #3366CC; color: white; border: none; cursor: pointer; width: 100%; font-size: 16px;">Submit</button>
</div>
<div id="progressContainer" style="display: none; margin-top: 20px;">
<div id="progressBar" style="width: 100%; height: 25px; background-color: #f1f1f1; border-radius: 10px;">
<div id="progressBarFill" style="width: 0%; height: 100%; background-color: #00af89; border-radius: 10px; transition: width 0.3s ease;"></div>
</div>
<p id="progressText" style="color: #333; margin-top: 10px;"></p>
<button id="cancelDownload" style="margin-top: 15px; padding: 10px 20px; border-radius: 5px; background-color: #dd3333; color: white; border: none; cursor: pointer; width: 100%; font-size: 16px;">Cancel</button>
</div>
<footer style="margin-top: 30px; font-size: 14px; color: #333;">
A tool from <a href="https://meta.wikimedia.org/wiki/User:WikiLucas00" target="_blank" style="color: #3366CC; text-decoration: none;">WikiLucas00</a>
</footer>
</div>
<!-- Load Charter Font -->
<link href="https://fonts.googleapis.com/css2?family=Charter:wght@400;700&display=swap" rel="stylesheet">
<!-- CSS -->
<style>
body {background-color: #fef6e7;}
#toolContainer {
font-family: 'Charter', serif;
}
</style>
`;
document.body.innerHTML = ''; // Clear existing content
document.body.appendChild(container);
document.getElementById('submitWord').addEventListener('click', function() {
var word = document.getElementById('wordInput').value.trim();
if (word) {
console.log("User submitted word:", word);
initDownload(word);
} else {
console.log("No word entered, please try again.");
}
});
document.getElementById('cancelDownload').addEventListener('click', function() {
isCancelled = true;
document.getElementById('progressContainer').style.display = 'none';
document.getElementById('inputContainer').style.display = 'block';
console.log("Download process cancelled by user.");
});
document.getElementById('wordInput').addEventListener('keypress', function(e) {
if (e.key === 'Enter') {
document.getElementById('submitWord').click();
}
});
}
function resetUI() {
var progressBarFill = document.getElementById('progressBarFill');
var progressText = document.getElementById('progressText');
if (progressBarFill && progressText) {
progressBarFill.style.transition = 'none';
progressBarFill.style.width = "0%";
progressBarFill.style.backgroundColor = "green";
progressText.textContent = "";
}
document.getElementById('progressContainer').style.display = 'none';
document.getElementById('inputContainer').style.display = 'block';
}
function updateProgressBar(percent, processedCount, totalFiles) {
var progressBarFill = document.getElementById('progressBarFill');
var progressText = document.getElementById('progressText');
if (progressBarFill && progressText) {
progressBarFill.style.width = percent + "%";
progressText.textContent = processedCount + "/" + totalFiles + " files downloaded";
}
}
function smoothTransitionToBlueBar() {
var progressBarFill = document.getElementById('progressBarFill');
if (progressBarFill) {
progressBarFill.style.transition = 'background-color 1s ease';
progressBarFill.style.backgroundColor = "blue";
}
}
function smoothIncrementTo100() {
var currentWidth = parseFloat(document.getElementById('progressBarFill').style.width);
if (currentWidth < 100) {
setTimeout(() => {
currentWidth += 1;
updateProgressBar(currentWidth, downloadCount, downloadCount);
if (currentWidth < 100) {
smoothIncrementTo100();
}
}, 10);
}
}
function updateProgressBarForZipping() {
smoothTransitionToBlueBar();
var progressText = document.getElementById('progressText');
if (progressText) {
progressText.textContent = "Zipping files...";
}
smoothIncrementTo100();
}
function fetchQueryResults(word, cont, callback) {
var params = {
action: "query",
format: "json",
prop: "imageinfo",
generator: "search",
utf8: 1,
iiprop: "url",
gsrsearch: 'intitle:/LL-Q150 \\(fra\\)-[^-]*-' + word + '\\.wav/',
gsrnamespace: 6,
gsrlimit: "max",
gsrwhat: "text",
origin: "*"
};
if (cont) {
params.continue = cont.continue;
params.gsroffset = cont.gsroffset;
}
$.ajax({
url: "https://commons.wikimedia.org/w/api.php",
data: params,
dataType: "json",
success: function (data) {
var files = [];
if (data.query && data.query.pages) {
for (var pageId in data.query.pages) {
if (data.query.pages.hasOwnProperty(pageId)) {
var page = data.query.pages[pageId];
if (page.imageinfo && page.imageinfo.length > 0) {
files.push({
title: page.title,
url: page.imageinfo[0].url
});
}
}
}
}
callback(files, data.continue);
},
error: function (xhr, status, error) {
console.error("API request failed:", status, error);
}
});
}
function processFilesInParallel(files) {
const maxConcurrentDownloads = 100;
let currentDownloads = 0;
const maxRetries = 10;
function downloadFile(fileData, retryCount = 0) {
fetch(fileData.url)
.then(response => {
if (!response.ok) {
throw new Error("Network response was not ok for " + fileData.title);
}
return response.blob();
})
.then(blob => {
zip.file(fileData.title, blob, { binary: true });
currentDownloads--;
var processedCount = downloadCount - files.length - currentDownloads;
var percentComplete = (processedCount / downloadCount) * 100;
updateProgressBar(percentComplete, processedCount, downloadCount);
if (isCancelled) return;
downloadNextFile();
})
.catch(error => {
console.error("Error downloading file:", fileData.title, "; Error: ", error);
if (retryCount < maxRetries) {
console.log(`Retrying download for ${fileData.title} (Attempt ${retryCount + 1}/${maxRetries})`);
setTimeout(() => downloadFile(fileData, retryCount + 1), 1000); // Wait 1 second before retrying
} else {
console.error(`Failed to download ${fileData.title} after ${maxRetries} attempts`);
currentDownloads--;
if (isCancelled) return;
downloadNextFile();
}
});
}
function downloadNextFile() {
if (currentDownloads >= maxConcurrentDownloads || files.length === 0) {
if (files.length === 0 && currentDownloads === 0 && !isCancelled) {
console.log("All files downloaded, starting zip process.");
updateProgressBarForZipping();
zip.generateAsync({ type: "blob" })
.then(function(content) {
saveAs(content, zipFileName);
resetUI();
console.log("Zip file saved as:", zipFileName);
});
}
return;
}
const fileData = files.shift();
currentDownloads++;
if (isCancelled) {
console.log("Download process cancelled, exiting...");
return;
}
downloadFile(fileData);
downloadNextFile();
}
downloadNextFile();
}
function initDownload(word) {
if (!librariesLoaded) {
console.log("Libraries not loaded yet. Please try again in a moment.");
return;
}
resetUI();
downloadQueue = [];
downloadCount = 0;
zip = new JSZip();
isCancelled = false;
zipFileName = "lingualibre-Q150-" + word + ".zip";
document.getElementById('inputContainer').style.display = 'none';
document.getElementById('progressContainer').style.display = 'block';
function fetchAndProcess(cont) {
fetchQueryResults(word, cont, function (files, nextCont) {
if (files.length === 0 && !nextCont) {
// No results found
resetUI();
alert("No results found for the word: " + word);
return;
}
downloadQueue = downloadQueue.concat(files);
downloadCount += files.length;
updateProgressBar(0, 0, downloadCount);
processFilesInParallel(files);
if (nextCont) {
fetchAndProcess(nextCont);
} else if (downloadQueue.length === 0) {
// No results found after all pages have been checked
resetUI();
alert("No results found for the word: " + word);
}
});
}
fetchAndProcess();
}
// Run it all
mw.loader.using(['jquery.ui'], function () {
// Check if we're on the tool page
if (mw.config.get('wgPageName') === 'LinguaLibre:LinguaZip/run') {
$(document).ready(function () {
createInterface();
});
}
});
}(mediaWiki, jQuery));
// </nowiki>
// [[Category:LinguaZip]]