(function ($, mw) {
"use strict";
mw.loader.using(["mediawiki.util", "mediawiki.api"]).then(function () {
const api = new mw.Api();
console.log("GlaMainBot gallery.js loaded");
const nameToFileName = (name) => {
name = name.normalize();
name = name.replace(/ /g, "_");
name = name
.split(".")
.map((x, i, arr) => (i === arr.length - 1 ? x.toLowerCase() : x))
.join(".");
return name;
};
const normalizeJpgExt = (name) => name.replace(/\.jpe?g$/i, ".jpg");
const normalizeForMatch = (name) => normalizeJpgExt(name).replace(/"/g, "_");
const fileNameToName = (name, names) => {
return names.find(
(x) => normalizeForMatch(nameToFileName(x)) === normalizeForMatch(name.normalize())
)?.normalize();
};
const glamaingallery = document.querySelectorAll(".glamaingallery");
const bulkUpload = (links) => {
if (document.getElementById("bulkUploadContainer")) {
return;
}
const names = links.map((x) => {
const url = new URL(x.href);
return url.searchParams.get("wpDestFile").normalize();
});
const renderFileList = (files, container) => {
container.innerHTML = "";
files.forEach(([name, color], i) => {
if (i > 0) container.appendChild(document.createElement("br"));
const a = document.createElement("a");
const resolved = fileNameToName(name, names) || name;
a.href = "/wiki/File:" + encodeURIComponent(resolved);
a.target = "_blank";
a.style.color = color || "black";
a.style.display = "inline-block";
a.style.maxWidth = "100%";
a.style.overflow = "hidden";
a.style.textOverflow = "ellipsis";
a.style.whiteSpace = "nowrap";
a.style.verticalAlign = "bottom";
a.title = name;
a.textContent = name;
container.appendChild(a);
});
};
const fileNames = names.map(nameToFileName);
const container = document.createElement("div");
container.id = "bulkUploadContainer";
container.style.position = "fixed";
container.style.top = "50%";
container.style.left = "50%";
container.style.transform = "translate(-50%, -50%)";
container.style.zIndex = "1000";
container.style.backgroundColor = "white";
container.style.padding = "20px";
container.style.border = "1px solid black";
container.style.width = "100%";
container.style.maxWidth = "400px";
container.innerHTML = `
<form method="post">
<h2>Bulk upload</h2>
<input type="file" name="file" multiple style="display: block; margin-bottom: 10px">
<small style="display: block; margin-bottom: 10px; font-size: 10px;">Only files with the following names are allowed: <br><span></span></small>
<input type="text" name="comment" placeholder="Removed watermark" style="display: block; margin-bottom: 10px">
<input type="submit" value="Upload" disabled style="display: block; margin-bottom: 10px">
<pre style="font-size: 10px; display: block; white-space: pre-wrap; margin-bottom: 10px; max-height: 14em; overflow-y: auto;"></pre>
<button type="button" onclick="this.closest('div').remove()" style="display: block; margin-bottom: 10px">Close</button>
</form>
`;
document.body.appendChild(container);
const form = container.querySelector("form");
const input = form.querySelector("input[type=file]");
const commentInput = form.querySelector("input[name=comment]");
const small = form.querySelector("small");
const span = small.querySelector("span");
const submit = form.querySelector("input[type=submit]");
const pre = form.querySelector("pre");
pre.textContent =
"Note: If the file is not in the list, it will be ignored\n";
const log = (msg) => {
pre.textContent += msg + "\n";
pre.scrollTop = pre.scrollHeight;
};
renderFileList(fileNames.map((x) => [x, ""]), span);
let files = [];
input.addEventListener("change", (e) => {
let allFiles = Array.from(e.target.files);
files = allFiles.filter((x) =>
fileNames.some(
(f) => normalizeForMatch(f) === normalizeForMatch(x.name.normalize())
)
);
const notAllowedFiles = allFiles.filter(
(x) =>
!fileNames.some(
(f) => normalizeForMatch(f) === normalizeForMatch(x.name.normalize())
)
);
renderFileList([
...fileNames.map((x) => [
x,
files
.map((f) => normalizeForMatch(f.name.normalize()))
.includes(normalizeForMatch(x))
? "green"
: "",
]),
...notAllowedFiles.map((x) => [x.name, "red"]),
], span);
submit.value = `Upload (${files.length}/${allFiles.length})`;
submit.disabled = files.length === 0;
log(`Selected ${files.length} files`);
});
form.addEventListener("submit", (e) => {
e.preventDefault();
if (submit.disabled || files.length === 0) {
return;
}
input.disabled = true;
submit.disabled = true;
const comment = commentInput.value
? commentInput.value
: commentInput.placeholder;
log(`Uploading ${files.length} files...`);
const fileColors = fileNames.map((x) => [x, ""]);
const setFileColor = (name, color) => {
fileColors[fileColors.findIndex((x) => normalizeForMatch(x[0]) === normalizeForMatch(name.normalize()))][1] = color;
renderFileList(fileColors, span);
};
renderFileList(fileColors, span);
const successes = [];
const upload = (i) => {
if (i >= files.length) {
log(`Uploaded ${successes.length}/${files.length} files`);
for (const success of successes) {
window.open(success, "_blank");
}
return;
}
const file = files[i];
log(`Uploading ${file.name} (${i + 1}/${files.length})...`);
const formData = new FormData();
formData.append("token", mw.user.tokens.get("csrfToken"));
formData.append("action", "upload");
formData.append("format", "json");
formData.append("file", file);
formData.append("filename", fileNameToName(file.name, names));
formData.append("comment", comment);
formData.append("ignorewarnings", 1);
fetch("/w/api.php", {
method: "POST",
body: formData,
})
.then((x) => x.json())
.then((response) => {
console.debug(`Upload response for ${file.name}`, response);
if (
response &&
response.upload &&
response.upload.result === "Success"
) {
log(`Uploaded ${file.name} (${i + 1}/${files.length})`);
setFileColor(file.name, "green");
successes.push(response.upload.imageinfo.descriptionurl);
} else {
log(`Failed to upload ${file.name} (${i + 1}/${files.length})`);
if (
response &&
response.upload &&
response.upload.warnings
) {
log(`${file.name} warnings: ${response.upload.warnings.join(", ")}`);
}
setFileColor(file.name, "red");
}
upload(i + 1);
})
.catch((e) => {
console.error(e);
log(`${file.name} error: ${e}`);
setFileColor(file.name, "red");
upload(i + 1);
});
};
upload(0);
});
input.click();
};
glamaingallery.forEach((glamaingallery) => {
const items = glamaingallery.querySelectorAll(
"li.gallerybox div.gallerytext"
);
let firstRowY = null;
let cols = 1;
items.forEach((item) => {
const rowY = item.getBoundingClientRect().top;
if (firstRowY === null) {
firstRowY = rowY;
} else if (rowY === firstRowY) {
cols++;
}
const links = item.querySelectorAll("a");
links.forEach((link) => {
const url = link.getAttribute("href");
if (url.includes("croptool") || url.includes("Special:Upload")) {
link.setAttribute("target", "_blank");
}
});
});
const bulkLinks = Array.from(
glamaingallery.querySelectorAll(".gallerytext .plainlinks a")
);
for (const link of bulkLinks) {
for (let multiple = 2; multiple >= 1; multiple--) {
const bulkLink = link.cloneNode(true);
bulkLink.textContent = "+" + (multiple * cols - 1);
link.after(bulkLink);
link.after(document.createTextNode(" "));
bulkLink.addEventListener("click", (e) => {
e.preventDefault();
const sameLinks = bulkLinks.filter(
(x) => x.textContent === link.textContent
);
const links = sameLinks.slice(
sameLinks.indexOf(link),
sameLinks.indexOf(link) + multiple * cols
);
if (link.href.includes("Special:Upload")) {
bulkUpload(links);
return;
}
links.forEach((el) => {
window.open(el.getAttribute("href") || "", "_blank");
});
});
}
}
});
});
})(jQuery, mw);