Jump to content

User:Polygnotus/Scripts/WikiTextExpander.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.
// WikiTextExpander
// This script allows you to expand acronyms and shorthand phrases using a configurable hotkey
// - Acronyms with colons (e.g. "WP:COI" and "[[WP:COI]]") are expanded as wiki links [[WP:COI|the conflict of interest guideline]]
// - Regular phrases are expanded without wiki links

// Default configuration
var textExpanderConfig = {
    // Define your acronyms and phrases with their expansions here
    expansionMap: {
        // Wiki acronyms (will be expanded with [[ ]] format)
        "WP:COI": "the conflict of interest guideline",
        "WP:NPOV": "the neutral point of view policy",
        "WP:RS": "the reliable sources guideline",
        "WP:V": "the verifiability policy",
        "WP:NOR": "the no original research policy",
        "WP:BLP": "the biographies of living persons policy",
        "WP:CITE": "the citation needed guideline",
        "WP:N": "the notability guideline",
        "MOS:LAYOUT": "the layout guideline",
        "WP:TALK": "the talk page guideline",
        
        // Regular phrases (will be expanded without wiki formatting)
        "dupe": "This appears to be a duplicate of a previous submission. Please check the existing entries before submitting.",
        "notref": "This is not a reliable reference according to our guidelines. Please provide a source that meets our reliability criteria.",
        "format": "Please format your submission according to our style guide before resubmitting.",
        "thanks": "Thank you for your contribution. I've reviewed it and made some minor edits for clarity.",
        "sorry": "I apologize for the confusion. Let me clarify what I meant in my previous comment."
        // Add more phrases as needed
    },
    // Default hotkey configuration: Ctrl+Shift+Z
    hotkey: {
        ctrlKey: true,
        shiftKey: true,
        altKey: false,
        key: 'z'
    }
};

// Try to load user configuration from localStorage if it exists
try {
    var savedConfig = localStorage.getItem('textExpanderConfig');
    if (savedConfig) {
        var parsedConfig = JSON.parse(savedConfig);
        // Merge saved configuration with defaults
        if (parsedConfig.expansionMap) {
            textExpanderConfig.expansionMap = parsedConfig.expansionMap;
        }
        if (parsedConfig.hotkey) {
            textExpanderConfig.hotkey = parsedConfig.hotkey;
        }
    }
} catch (e) {
    console.error('Error loading text expander config:', e);
}

// Function to save the configuration
function saveTextExpanderConfig() {
    try {
        localStorage.setItem('textExpanderConfig', JSON.stringify(textExpanderConfig));
    } catch (e) {
        console.error('Error saving text expander config:', e);
    }
}

// Function to create a regular expression pattern for all expandable text
function getExpansionRegex() {
    var escapedKeys = Object.keys(textExpanderConfig.expansionMap).map(function(key) {
        return key.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); // Escape special characters
    });
    
    // Pattern to match both plain text and those inside wiki brackets
    return new RegExp(
        '(?<!\\[)\\b(' + escapedKeys.join('|') + ')\\b(?!\\])' + '|' +
        '\\[\\[(' + escapedKeys.join('|') + ')(?:\\|[^\\]]*?)?\\]\\]',
        'g'
    );
}

// Function to determine if a key should be formatted as a wiki link
function shouldFormatAsWikiLink(key) {
    return key.indexOf(':') > -1;
}

// Function to expand all text in the selected text
function expandAllText() {
    // Get the active editor input element
    var activeElement = document.activeElement;
    
    // Check if we're in an editable field
    if (activeElement && (
        activeElement.isContentEditable || 
        activeElement.tagName === 'TEXTAREA' || 
        (activeElement.tagName === 'INPUT' && activeElement.type === 'text')
    )) {
        var selectedText = '';
        var expandedCount = 0;
        
        // Handle different editor types
        if (activeElement.isContentEditable) {
            // Visual editor
            var selection = window.getSelection();
            if (selection.rangeCount > 0) {
                var range = selection.getRangeAt(0);
                selectedText = selection.toString();
                
                // Only proceed if there's selected text
                if (selectedText) {
                    // Create a document fragment for the new content
                    var newContent = selectedText;
                    var expansionRegex = getExpansionRegex();
                    
                    // Replace all expandable text in the selected text
                    newContent = newContent.replace(expansionRegex, function(match, plainText, bracketedText) {
                        expandedCount++;
                        
                        if (plainText) {
                            // This is plain text
                            var expansion = textExpanderConfig.expansionMap[plainText];
                            if (shouldFormatAsWikiLink(plainText)) {
                                // Format as wiki link
                                return '[[' + plainText + '|' + expansion + ']]';
                            } else {
                                // Just replace with the expansion
                                return expansion;
                            }
                        } else if (bracketedText) {
                            // This is already in brackets
                            if (match.indexOf('|') > -1) {
                                // Already has a pipe, don't modify
                                return match;
                            } else {
                                // Add the expansion
                                var expansion = textExpanderConfig.expansionMap[bracketedText];
                                return '[[' + bracketedText + '|' + expansion + ']]';
                            }
                        }
                        return match;
                    });
                    
                    // Replace the selected text with the expanded text
                    if (expandedCount > 0) {
                        document.execCommand('insertText', false, newContent);
                        return expandedCount;
                    }
                }
            }
        } else {
            // Source editor (textarea or input)
            var selStart = activeElement.selectionStart;
            var selEnd = activeElement.selectionEnd;
            selectedText = activeElement.value.substring(selStart, selEnd);
            
            // Only proceed if there's selected text
            if (selectedText) {
                var expansionRegex = getExpansionRegex();
                
                // Replace all expandable text in the selected text
                var newContent = selectedText.replace(expansionRegex, function(match, plainText, bracketedText) {
                    expandedCount++;
                    
                    if (plainText) {
                        // This is plain text
                        var expansion = textExpanderConfig.expansionMap[plainText];
                        if (shouldFormatAsWikiLink(plainText)) {
                            // Format as wiki link
                            return '[[' + plainText + '|' + expansion + ']]';
                        } else {
                            // Just replace with the expansion
                            return expansion;
                        }
                    } else if (bracketedText) {
                        // This is already in brackets
                        if (match.indexOf('|') > -1) {
                            // Already has a pipe, don't modify
                            return match;
                        } else {
                            // Add the expansion
                            var expansion = textExpanderConfig.expansionMap[bracketedText];
                            return '[[' + bracketedText + '|' + expansion + ']]';
                        }
                    }
                    return match;
                });
                
                // Replace the selected text with the expanded text
                if (expandedCount > 0) {
                    activeElement.value = 
                        activeElement.value.substring(0, selStart) + 
                        newContent + 
                        activeElement.value.substring(selEnd);
                    
                    // Position cursor after the expansion
                    activeElement.setSelectionRange(selStart + newContent.length, selStart + newContent.length);
                    return expandedCount;
                }
            }
        }
    }
    
    // If we get here, no expansion happened
    if (selectedText && expandedCount === 0) {
        mw.notify('No expandable text found in the selection', {type: 'info'});
    } else if (!selectedText) {
        mw.notify('Please select text to expand', {type: 'info'});
    }
    
    return 0;
}

// Add keydown event listener for the hotkey
$(document).on('keydown', function(e) {
    var config = textExpanderConfig.hotkey;
    
    if (
        e.ctrlKey === config.ctrlKey && 
        e.shiftKey === config.shiftKey && 
        e.altKey === config.altKey && 
        e.key.toLowerCase() === config.key.toLowerCase()
    ) {
        var expandedCount = expandAllText();
        if (expandedCount > 0) {
            e.preventDefault();
            mw.notify('Expanded ' + expandedCount + ' item' + (expandedCount > 1 ? 's' : ''), {type: 'success'});
        }
    }
});

// Create the settings dialog
function createSettingsDialog() {
    var $dialog = $('<div>')
        .attr('id', 'text-settings-dialog')
        .attr('title', 'WikiTextExpander Settings')
        .css({
            'display': 'none'
        });
    
    // Create the dialog content
    var $content = $('<div>');
    
    // Create tabs
    var $tabs = $('<div>').addClass('mw-widget-aeTabs');
    var $tabList = $('<ul>');
    
    $tabList.append($('<li>').append($('<a>').attr('href', '#expansion-tab').text('Expansions')));
    $tabList.append($('<li>').append($('<a>').attr('href', '#hotkey-tab').text('Hotkey')));
    $tabs.append($tabList);
    
    // Expansions tab
    var $expansionTab = $('<div>').attr('id', 'expansion-tab');
    
    $expansionTab.append($('<p>').text('Edit your shorthand text and their expansions:'));
    
    var $expansionTable = $('<table>').addClass('wikitable').css('width', '100%');
    
    // Table header
    var $tableHeader = $('<tr>');
    $tableHeader.append($('<th>').text('Shorthand'));
    $tableHeader.append($('<th>').text('Expansion'));
    $tableHeader.append($('<th>').text('Type'));
    $tableHeader.append($('<th>').text('Actions'));
    $expansionTable.append($tableHeader);
    
    // Add rows for each expansion
    $.each(textExpanderConfig.expansionMap, function(key, expansion) {
        addExpansionRow($expansionTable, key, expansion);
    });
    
    // Add new row button
    var $addButton = $('<button>')
        .text('Add New Expansion')
        .click(function() {
            addExpansionRow($expansionTable, '', '');
        });
    
    // Import/Export area
    var $importExportArea = $('<div>').css('margin-top', '15px');
    
    var $exportButton = $('<button>')
        .text('Export to JSON')
        .css('margin-right', '10px')
        .click(function() {
            var currentMap = {};
            $expansionTable.find('tr').each(function(index) {
                if (index === 0) return; // Skip header row
                
                var $row = $(this);
                var key = $row.find('input.shorthand').val();
                var expansion = $row.find('input.expansion').val();
                
                if (key && expansion) {
                    currentMap[key] = expansion;
                }
            });
            
            var jsonData = JSON.stringify(currentMap, null, 2);
            var $textarea = $('<textarea>')
                .val(jsonData)
                .css({
                    'width': '100%',
                    'height': '100px',
                    'margin-top': '10px',
                    'font-family': 'monospace'
                });
            
            $importExportArea.find('textarea').remove();
            $importExportArea.append($textarea);
            $textarea.select();
        });
    
    var $importButton = $('<button>')
        .text('Import from JSON')
        .click(function() {
            var $textarea = $('<textarea>')
                .css({
                    'width': '100%',
                    'height': '100px',
                    'margin-top': '10px',
                    'font-family': 'monospace'
                })
                .attr('placeholder', '{"WP:ABC": "example expansion", "thanks": "Thank you for your contribution"}');
            
            var $importConfirm = $('<button>')
                .text('Process Import')
                .css('margin-top', '5px')
                .click(function() {
                    try {
                        var importedData = JSON.parse($textarea.val());
                        
                        // Clear existing rows (except header)
                        $expansionTable.find('tr:gt(0)').remove();
                        
                        // Add new rows
                        $.each(importedData, function(key, expansion) {
                            addExpansionRow($expansionTable, key, expansion);
                        });
                        
                        // Remove the import UI
                        $textarea.remove();
                        $importConfirm.remove();
                        
                        mw.notify('Expansions imported successfully', {type: 'success'});
                    } catch (e) {
                        mw.notify('Error parsing JSON: ' + e.message, {type: 'error'});
                    }
                });
            
            $importExportArea.find('textarea, button:not(:first-child)').remove();
            $importExportArea.append($textarea);
            $importExportArea.append($importConfirm);
        });
    
    $importExportArea.append($exportButton);
    $importExportArea.append($importButton);
    
    $expansionTab.append($expansionTable);
    $expansionTab.append($('<div>').css('margin-top', '10px').append($addButton));
    $expansionTab.append($importExportArea);
    
    // Hotkey tab
    var $hotkeyTab = $('<div>').attr('id', 'hotkey-tab');
    
    $hotkeyTab.append($('<p>').text('Configure the hotkey for expanding text:'));
    
    var $hotkeyForm = $('<div>').addClass('mw-widget-aeInputs');
    
    // Checkboxes for modifier keys
    var $modifiers = $('<div>').css('margin-bottom', '10px');
    
    var $ctrlLabel = $('<label>').css('margin-right', '10px');
    var $ctrlCheck = $('<input>').attr('type', 'checkbox').prop('checked', textExpanderConfig.hotkey.ctrlKey);
    $ctrlLabel.append($ctrlCheck).append(' Ctrl');
    
    var $shiftLabel = $('<label>').css('margin-right', '10px');
    var $shiftCheck = $('<input>').attr('type', 'checkbox').prop('checked', textExpanderConfig.hotkey.shiftKey);
    $shiftLabel.append($shiftCheck).append(' Shift');
    
    var $altLabel = $('<label>').css('margin-right', '10px');
    var $altCheck = $('<input>').attr('type', 'checkbox').prop('checked', textExpanderConfig.hotkey.altKey);
    $altLabel.append($altCheck).append(' Alt');
    
    $modifiers.append($ctrlLabel).append($shiftLabel).append($altLabel);
    
    // Key input
    var $keyLabel = $('<label>').css('display', 'block').css('margin-bottom', '5px').text('Key:');
    var $keyInput = $('<input>')
        .attr('type', 'text')
        .css('width', '50px')
        .val(textExpanderConfig.hotkey.key)
        .on('keydown', function(e) {
            e.preventDefault();
            $(this).val(e.key.toLowerCase());
        });
    
    $hotkeyForm.append($modifiers);
    $hotkeyForm.append($keyLabel);
    $hotkeyForm.append($keyInput);
    
    // Current hotkey display
    var $currentHotkey = $('<div>').css('margin-top', '15px');
    function updateCurrentHotkey() {
        var hotkeyText = [];
        if ($ctrlCheck.prop('checked')) hotkeyText.push('Ctrl');
        if ($shiftCheck.prop('checked')) hotkeyText.push('Shift');
        if ($altCheck.prop('checked')) hotkeyText.push('Alt');
        hotkeyText.push($keyInput.val().toUpperCase());
        
        $currentHotkey.html('<strong>Current Hotkey:</strong> ' + hotkeyText.join('+'));
    }
    
    // Update on any change
    $ctrlCheck.on('change', updateCurrentHotkey);
    $shiftCheck.on('change', updateCurrentHotkey);
    $altCheck.on('change', updateCurrentHotkey);
    $keyInput.on('input', updateCurrentHotkey);
    updateCurrentHotkey(); // Initial update
    
    $hotkeyTab.append($hotkeyForm);
    $hotkeyTab.append($currentHotkey);
    
    // Add the tabs to the content
    $tabs.append($expansionTab);
    $tabs.append($hotkeyTab);
    $content.append($tabs);
    
    // Save button
    var $saveButton = $('<button>')
        .text('Save Settings')
        .css('margin-top', '15px')
        .click(function() {
            // Save expansions
            var newExpansionMap = {};
            $expansionTable.find('tr').each(function(index) {
                if (index === 0) return; // Skip header row
                
                var $row = $(this);
                var key = $row.find('input.shorthand').val();
                var expansion = $row.find('input.expansion').val();
                
                if (key && expansion) {
                    newExpansionMap[key] = expansion;
                }
            });
            
            // Save hotkey configuration
            var newHotkey = {
                ctrlKey: $ctrlCheck.prop('checked'),
                shiftKey: $shiftCheck.prop('checked'),
                altKey: $altCheck.prop('checked'),
                key: $keyInput.val().toLowerCase()
            };
            
            // Update the configuration
            textExpanderConfig.expansionMap = newExpansionMap;
            textExpanderConfig.hotkey = newHotkey;
            
            // Save to localStorage
            saveTextExpanderConfig();
            
            // Close the dialog
            $dialog.dialog('close');
            
            // Notify the user
            mw.notify('WikiTextExpander settings saved', {type: 'success'});
        });
    
    $content.append($saveButton);
    
    // Append the content to the dialog
    $dialog.append($content);
    
    // Add to the body and initialize as a jQuery UI dialog
    $('body').append($dialog);
    
    $dialog.dialog({
        autoOpen: false,
        width: 600,
        height: 500,
        modal: true,
        title: 'WikiTextExpander settings',
        close: function() {
            // Cleanup the dialog on close
            $(this).dialog('destroy');
            $(this).remove();
        }
    });
    
    // Initialize tabs
    $tabs.tabs();
    
    return $dialog;
}

// Function to add a row to the expansions table
function addExpansionRow($table, key, expansion) {
    var $row = $('<tr>');
    
    var $keyCell = $('<td>');
    var $keyInput = $('<input>')
        .addClass('shorthand')
        .attr('type', 'text')
        .val(key)
        .css('width', '100%');
    $keyCell.append($keyInput);
    
    var $expansionCell = $('<td>');
    var $expansionInput = $('<input>')
        .addClass('expansion')
        .attr('type', 'text')
        .val(expansion)
        .css('width', '100%');
    $expansionCell.append($expansionInput);
    
    var $typeCell = $('<td>').css('text-align', 'center');
    var typeText = shouldFormatAsWikiLink(key) ? 'Wiki Link' : 'Plain Text';
    $typeCell.text(typeText);
    
    // Update type display when key changes
    $keyInput.on('input', function() {
        var newKey = $(this).val();
        var newType = shouldFormatAsWikiLink(newKey) ? 'Wiki Link' : 'Plain Text';
        $typeCell.text(newType);
    });
    
    var $actionsCell = $('<td>').css('text-align', 'center');
    var $deleteButton = $('<button>')
        .text('Delete')
        .click(function() {
            $(this).closest('tr').remove();
        });
    $actionsCell.append($deleteButton);
    
    $row.append($keyCell);
    $row.append($expansionCell);
    $row.append($typeCell);
    $row.append($actionsCell);
    
    $table.append($row);
}

// Add the settings item to the "More" menu
mw.hook('wikipage.content').add(function() {
    // For the modern Vector skin and other skins with a "More" menu
    if ($('#p-cactions').length) {
        // Make sure we don't add it twice
        if (!$('#ca-text-expander').length) {
            var $moreList = $('#p-cactions ul');
            
            var $settingsItem = $('<li>')
                .attr('id', 'ca-text-expander')
                .addClass('mw-list-item mw-list-item-js')
                .append(
                    $('<a>')
                        .attr('href', '#')
                        .text('WikiTextExpander settings')
                        .click(function(e) {
                            e.preventDefault();
                            createSettingsDialog().dialog('open');
                        })
                );
            
            $moreList.append($settingsItem);
        }
    }
});

// Add a notification about the hotkey when in edit mode
mw.hook('ve.activationComplete').add(function() {
    // For Visual Editor
    var hotkeyText = [];
    var config = textExpanderConfig.hotkey;
    if (config.ctrlKey) hotkeyText.push('Ctrl');
    if (config.shiftKey) hotkeyText.push('Shift');
    if (config.altKey) hotkeyText.push('Alt');
    hotkeyText.push(config.key.toUpperCase());
    
    //mw.notify('Text Expander activated. Use ' + hotkeyText.join('+') + ' to expand selected text.', {type: 'info'});
});

// Also show notification in wikitext editor
$(document).ready(function() {
    if (mw.config.get('wgAction') === 'edit' || mw.config.get('wgAction') === 'submit') {
        var hotkeyText = [];
        var config = textExpanderConfig.hotkey;
        if (config.ctrlKey) hotkeyText.push('Ctrl');
        if (config.shiftKey) hotkeyText.push('Shift');
        if (config.altKey) hotkeyText.push('Alt');
        hotkeyText.push(config.key.toUpperCase());
        
        //mw.notify('Text Expander activated. Use ' + hotkeyText.join('+') + ' to expand selected text.', {type: 'info'});
    }
});