//Testpage: https://en.wikipedia.org/wiki/User:Polygnotus/TypoFixerTest
//Example: tyop{{verify spelling|reason=tyop=>typo}}
//I am using the reason parameter because there is no suggestion parameter (yet)
//each of these templates is given an id so we can know that they clicked on the nth occurance.
// <nowiki>
mw.loader.using(['mediawiki.util'], function () {
$(document).ready(function () {
'use strict';
const DEBUG = true;
const DEBUG_DELAY = 0; // in ms. It can be useful to have a delay while debugging because when reloading the page the console empties.
function debug(...args) {
if (DEBUG) {
console.log('[TypoFixer]', ...args);
}
}
if ((mw.config.get('wgNamespaceNumber') !== 0 && mw.config.get('wgPageName') !== 'User:Polygnotus/Scripts/TypoFixerTest') ||
mw.config.get('wgAction') !== 'view' ||
!mw.config.get('wgIsProbablyEditable')) {
debug('Script is not allowed to run here.');
return;
}
function replaceVerifySpellingTemplates() {
try {
const verifyElements = document.querySelectorAll('.noprint.Inline-Template, .noprint.Template-Fact');
debug(`Found ${verifyElements.length} verify elements`);
let templateInstances = {};
Array.from(verifyElements).forEach((element, index) => {
try {
let spanElement, from, to;
if (element.classList.contains('Inline-Template')) {
spanElement = element.querySelector('span[title]');
if (!spanElement || !spanElement.title.includes('=>')) {
debug(`Invalid inline template format in element ${index}`);
return;
}
[from, to] = spanElement.title.split('=>').map(s => s.trim());
} else if (element.classList.contains('Template-Fact')) {
const templateContent = element.textContent;
const match = templateContent.match(/reason=([^=>\s]+)\s*=>\s*([^=>\s]+)/);
if (!match) {
debug(`Invalid block template format in element ${index}`);
return;
}
[, from, to] = match;
}
debug(`Processing element: ${from} => ${to}`);
const templateKey = `${from}`;
templateInstances[templateKey] = (templateInstances[templateKey] || 0) + 1;
const instanceNumber = templateInstances[templateKey];
const templateId = `${templateKey}_${instanceNumber}`;
const replacementSpan = document.createElement('span');
replacementSpan.innerHTML = `<sup><small> [Typofix?: ${to}] [<span style="color: green; cursor: pointer;" role="button" aria-label="Accept spelling change">▶</span>|<span style="color: red; cursor: pointer;" role="button" aria-label="Reject spelling change">■</span>]</small></sup>`;
replacementSpan.dataset.templateId = templateId;
replacementSpan.dataset.from = from;
replacementSpan.dataset.to = to;
replacementSpan.dataset.instanceNumber = instanceNumber;
const checkmark = replacementSpan.querySelector('span[style*="green"]');
const cross = replacementSpan.querySelector('span[style*="red"]');
checkmark.addEventListener('click', function() {
debug(`Checkmark clicked for: ${templateId}`);
implementChange(from, to, 'fix', instanceNumber, replacementSpan);
});
cross.addEventListener('click', function() {
debug(`Cross clicked for: ${templateId}`);
implementChange(from, to, 'remove', instanceNumber, replacementSpan);
});
element.parentNode.replaceChild(replacementSpan, element);
debug(`Replaced element for: ${templateId}`);
} catch (elementError) {
debug(`Error processing verify element ${index}:`, elementError.message);
}
});
} catch (error) {
debug('Error in replaceVerifySpellingTemplates:', error.message);
}
}
function implementChange(from, to, action, instanceNumber, replacementSpan) {
debug(`Implementing change: ${action} for ${from} (#${instanceNumber})`);
const title = mw.config.get('wgPageName');
const api = new mw.Api();
showLoading(replacementSpan);
api.get({
action: 'query',
prop: 'revisions',
titles: title,
rvprop: 'content',
rvlimit: 1,
formatversion: 2
}).then(function(data) {
if (!data.query || !data.query.pages || data.query.pages.length === 0) {
debug('Failed to retrieve page content');
}
let content = data.query.pages[0].revisions[0].content;
let summary;
debug('Original content:', content);
const inlineTemplateRegex = new RegExp(`(${escapeRegExp(from)})\\s*{{\\s*verify\\s+spelling\\s*(?:\\|[^}]*)?}}`, 'g');
const blockTemplateRegex = new RegExp(`(${escapeRegExp(from)})\\s*{{\\s*verify\\s+spelling\\s*\\|\\s*reason\\s*=\\s*${escapeRegExp(from)}\\s*=>\\s*${escapeRegExp(to)}\\s*}}`, 'g');
let count = 0;
if (action === 'fix') {
content = content.replace(inlineTemplateRegex, (match, p1) => {
count++;
debug(`Matched inline instance ${count} of ${from}`);
return count === parseInt(instanceNumber) ? to : match;
});
content = content.replace(blockTemplateRegex, (match, p1) => {
count++;
debug(`Matched block instance ${count} of ${from}`);
return count === parseInt(instanceNumber) ? to : match;
});
debug('Content after fixing typo and removing template:', content);
summary = `Fixing spelling: ${from} → ${to} and removing verification template (#${instanceNumber})`;
} else if (action === 'remove') {
content = content.replace(inlineTemplateRegex, (match, p1) => {
count++;
debug(`Matched inline #${count} of ${from} for removal`);
return count === parseInt(instanceNumber) ? p1 : match;
});
content = content.replace(blockTemplateRegex, (match, p1) => {
count++;
debug(`Matched block #${count} of ${from} for removal`);
return count === parseInt(instanceNumber) ? p1 : match;
});
debug('Content after removing template:', content);
summary = `Removing spelling verification template for: ${from} (#${instanceNumber})`;
} else {
debug(`Invalid action: ${action}`);
}
if (count === 0) {
debug(`No matches found for ${from}`);
}
debug('Final content:', content);
debug('Edit summary:', summary);
return api.postWithToken('csrf', {
action: 'edit',
title: title,
text: content,
summary: summary
});
}).then(function() {
debug('Edit successful.');
showSuccess(replacementSpan);
setTimeout(function() {
location.reload();
}, DEBUG_DELAY);
}).catch(function(error) {
debug('Error implementing change:', error.message);
showError(replacementSpan, error.message);
});
}
function showLoading(element) {
element.innerHTML = '<sup><small>[ Working... ]</small></sup>';
}
function showSuccess(element) {
element.innerHTML = '<sup><small>[ Done ]</span>';
}
function showError(element, message) {
element.innerHTML = `<span style="color: red;">Error: ${message}</span>`;
}
// Helper function to escape special characters in regex
function escapeRegExp(string) {
return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
}
try {
replaceVerifySpellingTemplates();
} catch (error) {
debug('Error initializing script:', error.message);
}
});
});
// </nowiki>