Jump to content

User:Polygnotus/Scripts/TypoFixer.js

From Wikipedia, the free encyclopedia
Note: After saving, you have to bypass your browser's cache to see the changes. Google Chrome, Firefox, Microsoft Edge and Safari: Hold down the ⇧ Shift key and click the Reload toolbar button. For details and instructions about other browsers, see Wikipedia:Bypass your cache.
//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">&#9654;</span>|<span style="color: red; cursor: pointer;" role="button" aria-label="Reject spelling change">&#9632;</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>