// <nowiki>
// https://en.wikipedia.org/w/index.php?title=Giraffe&action=edit&typo=the&typofix=teh
//perhaps add some unit tests?
(function() {
const DEBUG = false;
function debug(...args) {
if (DEBUG) {
console.log('[TypoTool]', ...args);
}
}
debug("Script started");
const typo = getUrlParameter('typo');
const typofix = getUrlParameter('typofix');
if (!typo || !typofix || typo === '' || typofix === '') {
debug("Typo or typofix parameters missing or empty. Script will not run.");
return;
}
let hasRun = false;
const disqualificationRegex = /((\[\s*sic\s*\]))|(\(\s*sic\s*\))|\{\s*sic\s*\}|\bsic\b|([sic])|(]sic])|((\{\{\s*sic))(.*?)(\}\})|\{\{\s*bots\s*\}\}|\{\{\s*nobots\s*\}\}/i;
const ignoreRegexes = [
/((http|https):\/\/)(www.)?[-a-z0-9@:%._\+~#?&//=]{2,256}\.[-a-z]{2,26}\b([-a-z0-9@:%._\+~#?&//=]*)/i,
/<blockquote>(.*?)<\/blockquote>/i,
/((\{\{\s*DEFAULTSORT\s*:\s*))(.*?)((\}\}))/i,
/<!--\s+(.*?)\s+-->/i,
/(\[\[(File|Image):(.*?)(\.|\||\]\]))/i,
/Image:(.*?)\./i,
/Category:(.*?)\./i,
/<\s*ref\s+name\s*=\s*"(.*?)"\s*>/i,
/<\s*ref\s+name\s*=\s*(.*?)\s*>/i,
/<\s*gallery\s*.*?<\/\s*gallery\s*>/i
];
// URL regex (more comprehensive than the one in ignoreRegexes)
const urlRegex = /((https?:\/\/)?(?:www\.|(?!www))[a-zA-Z0-9][a-zA-Z0-9-]+[a-zA-Z0-9]\.[^\s]{2,}|www\.[a-zA-Z0-9][a-zA-Z0-9-]+[a-zA-Z0-9]\.[^\s]{2,}|https?:\/\/(?:www\.|(?!www))[a-zA-Z0-9]+\.[^\s]{2,}|www\.[a-zA-Z0-9]+\.[^\s]{2,})/gi;
const ignoreTemplates = [
"Interlanguage link multi", "Illm", "Bquote", "As written", "ILL", "Ill", "Proper name", "Transl",
"Quote", "Quotation", "Block quote", "Notatypo", "III", "Chem name", "Bq", "Ill2", "Translit",
"Nat", "Typo", "Not translated", "Not typo", "Lang-Latn", "Interlanguage", "\"", "Tlit",
"Transliterate", "Cita", "Link-interwiki", "Citation bloc", "C quote", "Propername", "Quotes",
"Quoteblock", "Cquote2", "Langue", "Interlanguage links", "Proper noun", "SIC", "Bug workaround",
"ILLM", "LANG", "Blockquotation", "Block quotation", "LAng", "Zitat", "Cquotetxt", "Ill-wd",
"Ill-WD", "Cquotetext", "PrettyQuotation", "Interlanguage link forced", "Lang-xx",
"Long quotation", "Epigraph", "Red Wikidata link", "InterLanguage Link", "Gquote", "CquoteTxt",
"Blockquote/old", "ISOtranslit", "RedQ", "NAT", "Belg", "Coquote", "Imagequote",
"Block quote next to floating content", "Iw2", "MultiLink", "Gbq", "Interlanguage link Wikidata",
"Imagequote2", "Interlanguage link", "lang", "transliteration", "blockquote", "not a typo", "sic",
"clarify", "Spoken Wikipedia", "Multiple image", "Double image", "Triple image", "Doubleimage",
"Tripleimage", "Multiple images", "Four images", "Auto images", "Autoimages", "Dual image", "Mehrere Bilder",
"Multipleimage", "Multiple iamge", "MImage", "Mimage", "Multimage", "Multiimage", "Mulitple images", "Multi image",
"Multi Image", "Multimg", "Double image stack", "Vertical images list", "Double images", "Multipleimages", "Multiple video", "Mim", "Cite tweet", "cite book"
];
const ignoreParameters = [
"reason", "trans-title", "first", "last", "name", "photo", "image", "title",
"map_image", "image_skyline", "cover", "image_name", "last1", "first1","last2",
"first2","last3", "first3","last4", "first4","last5", "first5","last6", "first6",
"last7", "first7","last8", "first8","last9", "first9","last10", "first10", "logo", "structure1", "structure2", "author", "Ship image"
];
function getUrlParameter(name) {
name = name.replace(/[\[]/, '\\[').replace(/[\]]/, '\\]');
var regex = new RegExp('[\\?&]' + name + '=([^&#]*)');
var results = regex.exec(location.search);
return results === null ? '' : decodeURIComponent(results[1].replace(/\+/g, ' '));
}
function replaceTextInWikitextEditor(typo, typofix) {
var maxAttempts = 20;
var attemptInterval = 500; // 0.5 seconds
function attempt(attemptsLeft) {
var editTextarea = document.getElementById('wpTextbox1');
if (editTextarea && editTextarea.value) {
var currentText = editTextarea.value;
// Check for automatic disqualification
if (disqualificationRegex.test(currentText)) {
debug("Automatic disqualification found. Closing window.");
window.close();
return;
}
var typoPattern = new RegExp(typo, 'gi');
var replacementsMade = false;
var matches = [];
var match;
while ((match = typoPattern.exec(currentText)) !== null) {
matches.push({index: match.index, length: match[0].length, original: match[0]});
}
// Sort matches by length (descending) and then by index (ascending)
matches.sort((a, b) => b.length - a.length || a.index - b.index);
// Process matches in order, skipping overlaps
var newText = currentText;
var offset = 0;
for (let match of matches) {
let adjustedIndex = match.index + offset;
if (shouldReplace(newText, adjustedIndex, match.original)) {
let replacement = match.original.charAt(0) === match.original.charAt(0).toUpperCase()
? typofix.charAt(0).toUpperCase() + typofix.slice(1)
: typofix.toLowerCase();
newText = newText.slice(0, adjustedIndex) + replacement + newText.slice(adjustedIndex + match.length);
offset += replacement.length - match.length;
replacementsMade = true;
}
}
editTextarea.value = newText;
if (replacementsMade) {
var changeSummary = typo + ' → ' + typofix;
var editSummaryField = document.getElementById('wpSummary');
if (editSummaryField) {
editSummaryField.value = changeSummary;
}
var showChangesButton = document.querySelector('input[name="wpDiff"]');
if (showChangesButton) {
showChangesButton.click();
setTimeout(function() {
var form = showChangesButton.form;
if (form) form.submit();
}, 1000); // Increased timeout to 1 second
} else {
debug("Show changes button not found. Unable to submit changes.");
}
debug("Typo replaced and changes submitted.");
} else {
debug("No replacements made. Closing window.");
window.close();
}
} else if (attemptsLeft > 0) {
setTimeout(function() {
attempt(attemptsLeft - 1);
}, attemptInterval);
} else {
debug("Edit textarea not found or empty after multiple attempts. Closing window.");
window.close();
}
}
attempt(maxAttempts);
}
function shouldReplace(text, index, match) {
// Check if the match is within an HTML comment
let commentStart = text.lastIndexOf('<!--', index);
let commentEnd = text.indexOf('-->', index);
if (commentStart !== -1 && commentEnd !== -1 && commentStart < index && index < commentEnd) {
return false;
}
// Check if the match is within a URL
let urlMatch = text.match(urlRegex);
if (urlMatch) {
for (let url of urlMatch) {
let urlIndex = text.indexOf(url);
if (urlIndex <= index && index < urlIndex + url.length) {
return false;
}
}
}
// Check if the match is within any of the ignore regexes
for (let regex of ignoreRegexes) {
let regexMatch = text.match(regex);
if (regexMatch && regexMatch.index <= index && index < regexMatch.index + regexMatch[0].length) {
return false;
}
}
// Check if the match is within a template
let templateDepth = 0;
let i = index;
while (i >= 0) {
if (text.substr(i, 2) === '}}') {
templateDepth++;
i--;
} else if (text.substr(i, 2) === '{{') {
if (templateDepth === 0) {
let templateContent = text.substring(i + 2, index);
if (ignoreTemplates.some(template => templateContent.toLowerCase().startsWith(template.toLowerCase()))) {
return false;
}
break;
}
templateDepth--;
i--;
}
i--;
}
// Check if the match is within a parameter
let parameterDepth = 0;
i = index;
while (i >= 0) {
if (text[i] === '|' && parameterDepth === 0) {
let parameterContent = text.substring(i + 1, index);
let equalSignPos = parameterContent.indexOf('=');
if (equalSignPos !== -1) {
let parameterName = parameterContent.substring(0, equalSignPos).trim();
if (ignoreParameters.some(param => param.toLowerCase() === parameterName.toLowerCase())) {
return false;
}
}
break;
} else if (text.substr(i, 2) === '}}') {
parameterDepth++;
i--;
} else if (text.substr(i, 2) === '{{') {
parameterDepth--;
i--;
}
i--;
}
// Skip if it's just the correct word but with the first letter missing or the last one
let typofix = getUrlParameter('typofix');
if (match.toLowerCase() === typofix.toLowerCase().slice(1) ||
match.toLowerCase() === typofix.toLowerCase().slice(0, -1)) {
return false;
}
// Skip anything between [[File: and | or ]]
let fileStart = text.lastIndexOf('[[File:', index);
let fileSeparator = text.indexOf('|', index);
let fileEnd = text.indexOf(']]', index);
if (fileStart !== -1 && (fileSeparator !== -1 || fileEnd !== -1) &&
fileStart < index && index < (fileSeparator !== -1 ? fileSeparator : fileEnd)) {
return false;
}
// Skip anything between | logo = and | or ]] or a newline
let logoStart = text.lastIndexOf('| logo =', index);
let logoEnd = text.indexOf('|', index);
if (logoEnd === -1) logoEnd = text.indexOf(']]', index);
if (logoEnd === -1) logoEnd = text.indexOf('\n', index);
if (logoStart !== -1 && logoEnd !== -1 && logoStart < index && index < logoEnd) {
return false;
}
return true;
}
function init() {
if (hasRun) return; // Prevent multiple executions
hasRun = true;
replaceTextInWikitextEditor(typo, typofix);
}
// Use both DOMContentLoaded and load events
if (document.readyState === "loading") {
document.addEventListener("DOMContentLoaded", init);
} else {
init();
}
window.addEventListener("load", init);
})();
// </nowiki>