User:Polygnotus/Scripts/TypoFixer.js
Appearance
Code that you insert on this page could contain malicious content capable of compromising your account. If you import a script from another page with "importScript", "mw.loader.load", "iusc", or "lusc", take note that this causes you to dynamically load a remote script, which could be changed by others. Editors are responsible for all edits and actions they perform, including by scripts. User scripts are not centrally supported and may malfunction or become inoperable due to software changes. A guide to help you find broken scripts is available. If you are unsure whether code you are adding to this page is safe, you can ask at the appropriate village pump. This code will be executed when previewing this page. |
This user script seems to have a documentation page at User:Polygnotus/Scripts/TypoFixer. |
//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>