User:Stumblean/common.js: Difference between revisions
Appearance
No edit summary Tags: Mobile edit Mobile web edit Advanced mobile edit |
No edit summary Tags: Mobile edit Mobile web edit Advanced mobile edit |
||
Line 2: | Line 2: | ||
const iconURL = "https://files.catbox.moe/wk78nl.jpg"; | const iconURL = "https://files.catbox.moe/wk78nl.jpg"; | ||
// Create menu | |||
const menu = document.createElement("div"); | const menu = document.createElement("div"); | ||
menu.id = "mahitoMenu"; | menu.id = "mahitoMenu"; | ||
Line 20: | Line 21: | ||
}); | }); | ||
// Header | |||
const header = document.createElement("div"); | const header = document.createElement("div"); | ||
Object.assign(header.style, { | Object.assign(header.style, { | ||
Line 57: | Line 59: | ||
menu.appendChild(header); | menu.appendChild(header); | ||
// Content area | |||
const content = document.createElement("div"); | const content = document.createElement("div"); | ||
// Mass edit button | // Mass edit button | ||
const massEditBtn = document.createElement("button"); | const massEditBtn = document.createElement("button"); | ||
massEditBtn.textContent = "Mass edit"; | massEditBtn.textContent = "Mass edit"; | ||
Line 73: | Line 76: | ||
textAlign: "left", | textAlign: "left", | ||
}); | }); | ||
// Open Explanation button | // Open Explanation button | ||
const openBtn = document.createElement("button"); | const openBtn = document.createElement("button"); | ||
openBtn.textContent = "Open Explanation"; | openBtn.textContent = "Open Explanation"; | ||
Line 91: | Line 90: | ||
}); | }); | ||
openBtn.onclick = () => console.log("Open Explanation clicked"); | openBtn.onclick = () => console.log("Open Explanation clicked"); | ||
content.appendChild(massEditBtn); | |||
content.appendChild(openBtn); | 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(/[.*+?|(){}\/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, | let isDragging = false, | ||
offsetX = 0, | offsetX = 0, | ||
Line 121: | Line 331: | ||
} | } | ||
header.addEventListener("mousedown", function (e) { | header.addEventListener("mousedown", function (e) { | ||
startDrag(e.clientX, e.clientY); | startDrag(e.clientX, e.clientY); | ||
Line 133: | Line 342: | ||
document.addEventListener("mouseup", stopDrag); | document.addEventListener("mouseup", stopDrag); | ||
header.addEventListener("touchstart", function (e) { | header.addEventListener("touchstart", function (e) { | ||
const touch = e.touches[0]; | const touch = e.touches[0]; |
Revision as of 01:02, 27 June 2025
(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(/[.*+?|(){}\/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);
})();