MediaWiki
Difference between revisions of "LiLiZip.js"
WikiLucas00 (talk | contribs) m |
WikiLucas00 (talk | contribs) m |
||
(4 intermediate revisions by the same user not shown) | |||
Line 13: | Line 13: | ||
zipFileName, | zipFileName, | ||
isCancelled = false, | isCancelled = false, | ||
− | librariesLoaded = false; | + | librariesLoaded = false, |
+ | languageData = []; | ||
// Asynchronous loading of JSZip and FileSaver libraries | // Asynchronous loading of JSZip and FileSaver libraries | ||
$.when( | $.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/jszip/3.10.1/jszip.min.js"), | ||
− | $.getScript("https://tools-static.wmflabs.org/cdnjs/ajax/libs/FileSaver.js/2.0.2/FileSaver.min.js") | + | $.getScript("https://tools-static.wmflabs.org/cdnjs/ajax/libs/FileSaver.js/2.0.2/FileSaver.min.js"), |
+ | loadLanguageData() | ||
).done(function() { | ).done(function() { | ||
librariesLoaded = true; | librariesLoaded = true; | ||
+ | console.log("Libraries loaded successfully.") | ||
+ | mw.loader.using(['jquery.ui'], function () { | ||
+ | console.log("MediaWiki UI loaded. Initializing interface..."); | ||
+ | |||
+ | // Check if we're on the tool page | ||
+ | if (mw.config.get('wgPageName') === 'LinguaLibre:LiLiZip/run') { | ||
+ | $(document).ready(function () { | ||
+ | createInterface(); | ||
+ | addEventListeners(); | ||
+ | }); | ||
+ | } | ||
+ | }); | ||
+ | |||
+ | }).fail(function() { | ||
+ | console.error("Failed to load required libraries. The tool may not function correctly."); | ||
}); | }); | ||
+ | |||
+ | function loadLanguageData() { | ||
+ | return $.ajax({ | ||
+ | url: "https://lingualibre.org/index.php?title=MediaWiki:LiLiZip.js/Data.js&action=raw&ctype=text/javascript", | ||
+ | dataType: "text", | ||
+ | success: function(data) { | ||
+ | var jsonStr = data.replace(/^\s*var\s+\w+\s*=\s*/, '').replace(/;?\s*$/, ''); | ||
+ | languageData = JSON.parse(jsonStr); | ||
+ | }, | ||
+ | error: function(xhr, status, error) { | ||
+ | console.error("Error loading language data:", status, error); | ||
+ | } | ||
+ | }); | ||
+ | } | ||
function createInterface() { | function createInterface() { | ||
var container = document.createElement('div'); | var container = document.createElement('div'); | ||
container.innerHTML = ` | 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: # | + | <div id="toolContainer" style="font-family: 'Charter', serif; max-width: 500px; margin: 50px auto; text-align: center; padding: 20px; border-radius: 10px; background-color: #eeeeee; box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);"> |
<div id="iconContainer" style="margin-bottom: 20px;"> | <div id="iconContainer" style="margin-bottom: 20px;"> | ||
− | <a href="https:// | + | <a href="https://lingualibre.org/wiki/LinguaLibre:LiLiZip"> |
− | <img src="https://upload.wikimedia.org/wikipedia/commons/thumb/ | + | <img src="https://upload.wikimedia.org/wikipedia/commons/thumb/d/d5/LiLiZip_icon.png/480px-LiLiZip_icon.png" alt="LiLiZip icon" style="width: 50%; border-radius: 10px;"> |
</a> | </a> | ||
</div> | </div> | ||
− | <h1 style="font-weight: bold; color: #3366CC; margin-bottom: 20px;"><a href="https:// | + | <h1 style="font-weight: bold; color: #3366CC; margin-bottom: 20px;"><a href="https://lingualibre.org/wiki/LinguaLibre:LiLiZip">LiLiZip</a></h1> |
<div id="inputContainer" style="margin-bottom: 20px;"> | <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 | + | <select id="languageSelect" 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> | + | <option value="">Select a language</option> |
+ | ${languageData.map(lang => `<option value="${lang.wikidata}|${lang.iso}">${lang.languageLabel}</option>`).join('')} | ||
+ | </select> | ||
+ | <div style="position: relative;"> | ||
+ | <input type="text" id="wordInput" placeholder="Enter a word to query" style="padding: 10px 30px 10px 10px; border-radius: 5px; border: 1px solid #ccc; width: 100%; box-sizing: border-box;"> | ||
+ | </div> | ||
+ | <button id="submitWord" style="margin-top: 10px; padding: 10px 20px; border-radius: 5px; background-color: #3366CC; color: white; border: none; cursor: pointer; width: 100%; font-size: 16px;">Submit</button> | ||
</div> | </div> | ||
<div id="progressContainer" style="display: none; margin-top: 20px;"> | <div id="progressContainer" style="display: none; margin-top: 20px;"> | ||
Line 51: | Line 88: | ||
<!-- Load Charter Font --> | <!-- Load Charter Font --> | ||
<link href="https://fonts.googleapis.com/css2?family=Charter:wght@400;700&display=swap" rel="stylesheet"> | <link href="https://fonts.googleapis.com/css2?family=Charter:wght@400;700&display=swap" rel="stylesheet"> | ||
+ | <!-- Load Font Awesome --> | ||
+ | <link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.3/css/all.min.css" rel="stylesheet"> | ||
<!-- CSS --> | <!-- CSS --> | ||
<style> | <style> | ||
− | body {background-color: # | + | body {background-color: #eeeeee;} |
#toolContainer { | #toolContainer { | ||
font-family: 'Charter', serif; | font-family: 'Charter', serif; | ||
} | } | ||
</style> | </style> | ||
− | + | `; | |
− | |||
document.body.innerHTML = ''; // Clear existing content | document.body.innerHTML = ''; // Clear existing content | ||
document.body.appendChild(container); | document.body.appendChild(container); | ||
+ | } | ||
+ | function handleInput() { | ||
+ | var languageSelect = document.getElementById('languageSelect'); | ||
+ | var wordInput = document.getElementById('wordInput'); | ||
+ | var submitButton = document.getElementById('submitWord'); | ||
+ | |||
+ | if (languageSelect.value && wordInput.value.trim()) { | ||
+ | submitButton.disabled = false; | ||
+ | } else { | ||
+ | submitButton.disabled = true; | ||
+ | } | ||
+ | } | ||
− | + | function addEventListeners() { | |
− | + | document.getElementById('languageSelect').addEventListener('change', handleInput); | |
− | + | document.getElementById('wordInput').addEventListener('input', handleInput); | |
− | + | ||
− | + | document.getElementById('submitWord').addEventListener('click', function() { | |
− | + | var languageSelect = document.getElementById('languageSelect'); | |
− | + | var wordInput = document.getElementById('wordInput').value.trim(); | |
− | + | ||
− | + | if (languageSelect.value && wordInput) { | |
− | + | var [wikidata, iso] = languageSelect.value.split('|'); | |
− | + | console.log("User submitted word:", wordInput, "for language:", wikidata, iso); | |
− | + | initDownload(wordInput, wikidata, iso); | |
− | + | } else { | |
− | + | console.log("Please select a language and enter a word."); | |
− | + | } | |
− | + | }); | |
− | + | ||
− | + | 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() { | function resetUI() { | ||
Line 141: | Line 198: | ||
} | } | ||
− | + | function fetchQueryResults(word, wikidata, iso, cont, callback) { | |
− | + | var params = { | |
− | + | action: "query", | |
− | + | format: "json", | |
− | + | prop: "imageinfo", | |
− | + | generator: "search", | |
− | + | utf8: 1, | |
− | + | iiprop: "url", | |
− | + | gsrsearch: `intitle:/LL-${wikidata} \\(${iso}\\)-[^-]*-${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); | |
− | + | callback([], null); | |
− | + | } | |
− | + | }); | |
− | + | } | |
function processFilesInParallel(files) { | function processFilesInParallel(files) { | ||
const maxConcurrentDownloads = 100; | const maxConcurrentDownloads = 100; | ||
Line 255: | Line 312: | ||
} | } | ||
− | function initDownload(word) { | + | function initDownload(word, wikidata, iso) { |
if (!librariesLoaded) { | if (!librariesLoaded) { | ||
console.log("Libraries not loaded yet. Please try again in a moment."); | console.log("Libraries not loaded yet. Please try again in a moment."); | ||
Line 266: | Line 323: | ||
zip = new JSZip(); | zip = new JSZip(); | ||
isCancelled = false; | isCancelled = false; | ||
− | zipFileName = | + | zipFileName = `lingualibre-${wikidata}-${word}.zip`; |
document.getElementById('inputContainer').style.display = 'none'; | document.getElementById('inputContainer').style.display = 'none'; | ||
document.getElementById('progressContainer').style.display = 'block'; | document.getElementById('progressContainer').style.display = 'block'; | ||
function fetchAndProcess(cont) { | function fetchAndProcess(cont) { | ||
− | fetchQueryResults( | + | var searchTerm = word; |
+ | if (/^Q\d+$/.test(word)) { | ||
+ | // If the input is a Wikidata QID, search for it in addition to the word | ||
+ | searchTerm = `${word}|${wikidata}`; | ||
+ | } | ||
+ | fetchQueryResults(searchTerm, wikidata, iso, cont, function (files, nextCont) { | ||
if (files.length === 0 && !nextCont) { | if (files.length === 0 && !nextCont) { | ||
// No results found | // No results found | ||
Line 295: | Line 357: | ||
fetchAndProcess(); | fetchAndProcess(); | ||
} | } | ||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
}(mediaWiki, jQuery)); | }(mediaWiki, jQuery)); | ||
// </nowiki> | // </nowiki> | ||
− | // [[Category: | + | // [[Category:LiLiZip]] |
Latest revision as of 11:27, 3 September 2024
/**
@description: JavaScript Gadget that allows downloading files for a given word of a given language 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,
languageData = [];
// 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"),
loadLanguageData()
).done(function() {
librariesLoaded = true;
console.log("Libraries loaded successfully.")
mw.loader.using(['jquery.ui'], function () {
console.log("MediaWiki UI loaded. Initializing interface...");
// Check if we're on the tool page
if (mw.config.get('wgPageName') === 'LinguaLibre:LiLiZip/run') {
$(document).ready(function () {
createInterface();
addEventListeners();
});
}
});
}).fail(function() {
console.error("Failed to load required libraries. The tool may not function correctly.");
});
function loadLanguageData() {
return $.ajax({
url: "https://lingualibre.org/index.php?title=MediaWiki:LiLiZip.js/Data.js&action=raw&ctype=text/javascript",
dataType: "text",
success: function(data) {
var jsonStr = data.replace(/^\s*var\s+\w+\s*=\s*/, '').replace(/;?\s*$/, '');
languageData = JSON.parse(jsonStr);
},
error: function(xhr, status, error) {
console.error("Error loading language data:", status, error);
}
});
}
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: #eeeeee; box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);">
<div id="iconContainer" style="margin-bottom: 20px;">
<a href="https://lingualibre.org/wiki/LinguaLibre:LiLiZip">
<img src="https://upload.wikimedia.org/wikipedia/commons/thumb/d/d5/LiLiZip_icon.png/480px-LiLiZip_icon.png" alt="LiLiZip icon" style="width: 50%; border-radius: 10px;">
</a>
</div>
<h1 style="font-weight: bold; color: #3366CC; margin-bottom: 20px;"><a href="https://lingualibre.org/wiki/LinguaLibre:LiLiZip">LiLiZip</a></h1>
<div id="inputContainer" style="margin-bottom: 20px;">
<select id="languageSelect" style="padding: 10px; border-radius: 5px; border: 1px solid #ccc; width: 100%; box-sizing: border-box; margin-bottom: 10px;">
<option value="">Select a language</option>
${languageData.map(lang => `<option value="${lang.wikidata}|${lang.iso}">${lang.languageLabel}</option>`).join('')}
</select>
<div style="position: relative;">
<input type="text" id="wordInput" placeholder="Enter a word to query" style="padding: 10px 30px 10px 10px; border-radius: 5px; border: 1px solid #ccc; width: 100%; box-sizing: border-box;">
</div>
<button id="submitWord" style="margin-top: 10px; 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">
<!-- Load Font Awesome -->
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.3/css/all.min.css" rel="stylesheet">
<!-- CSS -->
<style>
body {background-color: #eeeeee;}
#toolContainer {
font-family: 'Charter', serif;
}
</style>
`;
document.body.innerHTML = ''; // Clear existing content
document.body.appendChild(container);
}
function handleInput() {
var languageSelect = document.getElementById('languageSelect');
var wordInput = document.getElementById('wordInput');
var submitButton = document.getElementById('submitWord');
if (languageSelect.value && wordInput.value.trim()) {
submitButton.disabled = false;
} else {
submitButton.disabled = true;
}
}
function addEventListeners() {
document.getElementById('languageSelect').addEventListener('change', handleInput);
document.getElementById('wordInput').addEventListener('input', handleInput);
document.getElementById('submitWord').addEventListener('click', function() {
var languageSelect = document.getElementById('languageSelect');
var wordInput = document.getElementById('wordInput').value.trim();
if (languageSelect.value && wordInput) {
var [wikidata, iso] = languageSelect.value.split('|');
console.log("User submitted word:", wordInput, "for language:", wikidata, iso);
initDownload(wordInput, wikidata, iso);
} else {
console.log("Please select a language and enter a word.");
}
});
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, wikidata, iso, cont, callback) {
var params = {
action: "query",
format: "json",
prop: "imageinfo",
generator: "search",
utf8: 1,
iiprop: "url",
gsrsearch: `intitle:/LL-${wikidata} \\(${iso}\\)-[^-]*-${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);
callback([], null);
}
});
}
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, wikidata, iso) {
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-${wikidata}-${word}.zip`;
document.getElementById('inputContainer').style.display = 'none';
document.getElementById('progressContainer').style.display = 'block';
function fetchAndProcess(cont) {
var searchTerm = word;
if (/^Q\d+$/.test(word)) {
// If the input is a Wikidata QID, search for it in addition to the word
searchTerm = `${word}|${wikidata}`;
}
fetchQueryResults(searchTerm, wikidata, iso, 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();
}
}(mediaWiki, jQuery));
// </nowiki>
// [[Category:LiLiZip]]