User:Stumblean/common.js
Note: After publishing, 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)
- Edge: Hold Ctrl while clicking Refresh, or press Ctrl-F5.
(function () {
const iconURL = "https://files.catbox.moe/wk78nl.jpg";
// Create menu
const menu = document.createElement("div");
menu.id = "mahitoMenu";
Object.assign(menu.style, {
position: "fixed",
top: "20px",
right: "20px",
padding: "10px",
backgroundColor: "#111",
color: "#fff",
border: "2px solid #666",
zIndex: "9999",
fontFamily: "monospace",
borderRadius: "10px",
boxShadow: "0 0 10px rgba(0,0,0,0.5)",
userSelect: "none",
touchAction: "none",
});
// Header
const header = document.createElement("div");
Object.assign(header.style, {
cursor: "grab",
display: "flex",
justifyContent: "center",
alignItems: "center",
marginBottom: "8px",
gap: "6px",
});
// Left icon
const leftIcon = document.createElement("img");
leftIcon.src = iconURL;
leftIcon.width = 15;
leftIcon.height = 15;
leftIcon.alt = "Mahito icon";
leftIcon.style.borderRadius = "3px";
// Title text
const title = document.createElement("div");
title.textContent = "Mahito Menu";
title.style.fontWeight = "bold";
// Right icon
const rightIcon = document.createElement("img");
rightIcon.src = iconURL;
rightIcon.width = 15;
rightIcon.height = 15;
rightIcon.alt = "Mahito icon";
rightIcon.style.borderRadius = "3px";
header.appendChild(leftIcon);
header.appendChild(title);
header.appendChild(rightIcon);
menu.appendChild(header);
// Content area
const content = document.createElement("div");
// Mass edit button
const massEditBtn = document.createElement("button");
massEditBtn.textContent = "Mass edit";
massEditBtn.title = "It allows you to edit any line on any page(s)";
Object.assign(massEditBtn.style, {
background: "#555",
color: "#fff",
border: "none",
padding: "5px 10px",
cursor: "pointer",
marginBottom: "6px",
width: "100%",
textAlign: "left",
});
// Open Explanation button
const openBtn = document.createElement("button");
openBtn.textContent = "Open Explanation";
Object.assign(openBtn.style, {
background: "#444",
color: "#fff",
border: "none",
padding: "5px 10px",
cursor: "pointer",
width: "100%",
textAlign: "left",
});
openBtn.onclick = () => console.log("Open Explanation clicked");
content.appendChild(massEditBtn);
content.appendChild(openBtn);
menu.appendChild(content);
document.body.appendChild(menu);
// Modal overlay & form for mass edit
const modal = document.createElement("div");
Object.assign(modal.style, {
display: "none",
position: "fixed",
top: 0,
left: 0,
width: "100vw",
height: "100vh",
backgroundColor: "rgba(0,0,0,0.8)",
zIndex: "10000",
justifyContent: "center",
alignItems: "center",
});
const form = document.createElement("form");
Object.assign(form.style, {
backgroundColor: "#222",
padding: "20px",
borderRadius: "8px",
color: "#fff",
maxWidth: "400px",
width: "90%",
display: "flex",
flexDirection: "column",
gap: "10px",
});
form.innerHTML = `
<label>Pages to edit (one per line):</label>
<textarea id="massEditPages" rows="5" style="width:100%;"></textarea>
<label>Text 1:</label>
<textarea id="massEditText1" rows="3" style="width:100%;"></textarea>
<label>Text 2:</label>
<textarea id="massEditText2" rows="3" style="width:100%;"></textarea>
<label>Edit type:</label>
<select id="massEditType" style="width:100%;">
<option value="prepend">Prepend text 1</option>
<option value="append">Append text 1</option>
<option value="bothpend">Prepend text 1 and append text 2</option>
<option value="replacetext">Replace first instance of text 1 with text 2</option>
<option value="replacetextg">Replace all instances of text 1 with text 2</option>
<option value="replacepage">Replace page with text 1</option>
</select>
<label>Edit summary:</label>
<input id="massEditSummary" type="text" style="width:100%;" />
<label><input id="massEditMinor" type="checkbox" /> Mark edit as minor</label>
<button type="submit" style="padding:8px; background:#555; border:none; color:#fff; cursor:pointer;">
Submit Mass Edit
</button>
<button type="button" id="massEditCancel" style="padding:8px; background:#333; border:none; color:#fff; cursor:pointer;">
Cancel
</button>
<div id="massEditResult" style="margin-top:10px;"></div>
`;
modal.appendChild(form);
document.body.appendChild(modal);
massEditBtn.onclick = () => {
modal.style.display = "flex";
};
document.getElementById("massEditCancel").onclick = (e) => {
e.preventDefault();
modal.style.display = "none";
document.getElementById("massEditResult").textContent = "";
};
// Mass Edit function (your code slightly adapted for async)
async function doMassEdit(pages, text1, text2, editType, summary, minor) {
let edited = 0;
const failed = [];
const errors = [];
function getPageText(title) {
return fetch(
mw.config.get("wgScriptPath") +
"/api.php?action=query&prop=revisions&rvprop=content&format=json&indexpageids=1&titles=" +
encodeURIComponent(title)
)
.then((res) => res.json())
.then((response) => {
const pageid = response.query.pageids[0];
if (pageid === "-1") return "";
return response.query.pages[pageid].revisions[0]["*"];
});
}
for (let article of pages) {
if (!article.trim()) continue;
try {
// Get token
const tokenResp = await fetch(
mw.config.get("wgScriptPath") +
"/api.php?format=json&action=query&prop=info&meta=tokens&type=csrf&titles=" +
encodeURIComponent(article)
);
const tokenJson = await tokenResp.json();
const edittoken = tokenJson.query.tokens.csrftoken;
// Get original page text if needed
let newPageText = "";
if (
editType === "replacetext" ||
editType === "replacetextg" ||
editType === "bothpend"
) {
let pagetext = await getPageText(article);
if (editType === "replacetextg") {
const escapedText1 = text1.replace(new RegExp("[.*+?|(){}\\\\\", "g"), "\\$&");
const reg = new RegExp(escapedText1, "g");
pagetext = pagetext.replace(reg, text2);
} else if (editType === "replacetext") {
pagetext = pagetext.replace(text1, text2);
} else {
pagetext = text1 + pagetext + text2;
}
newPageText = pagetext;
}
let postData = new URLSearchParams();
postData.append("format", "json");
postData.append("action", "edit");
postData.append("watchlist", "nochange");
postData.append("title", article);
postData.append("summary", summary);
postData.append("token", edittoken);
if (minor) postData.append("minor", "1");
else postData.append("notminor", "1");
if (editType === "prepend") {
postData.append("prependtext", text1 + "\n");
} else if (editType === "append") {
postData.append("appendtext", "\n" + text1);
} else if (editType === "replacepage") {
postData.append("text", text1);
} else if (
editType === "replacetext" ||
editType === "replacetextg" ||
editType === "bothpend"
) {
postData.append("text", newPageText);
}
const editResp = await fetch(mw.config.get("wgScriptPath") + "/api.php", {
method: "POST",
headers: {
"Content-Type": "application/x-www-form-urlencoded",
},
body: postData.toString(),
});
const editJson = await editResp.json();
if (editJson.edit) {
edited++;
document.getElementById("massEditResult").textContent =
`Edited ${edited} pages...`;
} else {
failed.push(article);
errors.push(editJson.error?.info || "Unknown error");
}
} catch (err) {
failed.push(article);
errors.push(err.message);
}
}
// Show summary
if (failed.length > 0) {
document.getElementById("massEditResult").innerHTML =
`<b>Done with errors.</b><br>Edited: ${edited}<br>Failed:` +
failed
.map(
(page, i) =>
`<div>${page}: ${errors[i]}</div>`
)
.join("");
} else {
document.getElementById("massEditResult").innerHTML =
`<b>All done!</b><br>Edited: ${edited}`;
}
}
// Handle form submit
form.onsubmit = async (e) => {
e.preventDefault();
document.getElementById("massEditResult").textContent = "Working...";
const pages = document
.getElementById("massEditPages")
.value.split("\n")
.map((x) => x.trim())
.filter((x) => x);
const text1 = document.getElementById("massEditText1").value;
const text2 = document.getElementById("massEditText2").value;
const editType = document.getElementById("massEditType").value;
const summary = document.getElementById("massEditSummary").value;
const minor = document.getElementById("massEditMinor").checked;
await doMassEdit(pages, text1, text2, editType, summary, minor);
};
// Drag logic
let isDragging = false,
offsetX = 0,
offsetY = 0;
function startDrag(x, y) {
isDragging = true;
const rect = menu.getBoundingClientRect();
offsetX = x - rect.left;
offsetY = y - rect.top;
menu.style.right = "auto";
}
function doDrag(x, y) {
if (isDragging) {
menu.style.left = x - offsetX + "px";
menu.style.top = y - offsetY + "px";
}
}
function stopDrag() {
isDragging = false;
}
header.addEventListener("mousedown", function (e) {
startDrag(e.clientX, e.clientY);
e.preventDefault();
});
document.addEventListener("mousemove", function (e) {
doDrag(e.clientX, e.clientY);
});
document.addEventListener("mouseup", stopDrag);
header.addEventListener("touchstart", function (e) {
const touch = e.touches[0];
startDrag(touch.clientX, touch.clientY);
e.preventDefault();
});
document.addEventListener("touchmove", function (e) {
if (!isDragging) return;
const touch = e.touches[0];
doDrag(touch.clientX, touch.clientY);
});
document.addEventListener("touchend", stopDrag);
})();