User:Polygnotus/Scripts/WikiTextExpander.js

// <nowiki> // WikiTextExpander // This script allows you to expand acronyms and shorthand phrases using a configurable hotkey // - Acronyms with colons (…

// <nowiki>
// 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
//The CodeMirror stuff was written by DVRTed https://en.wikipedia.org/wiki/User:DVRTed

// 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 text in CM editor
function expandTextInCodeMirror() {
    var $textarea = $('#wpTextbox1');
    if (!$textarea.length) return 0;
    
    var selectedText = $textarea.textSelection('getSelection');
    
    if (!selectedText) {
        mw.notify('Please select text to expand', {type: 'info'});
        return 0;
    }
    
    var expansionRegex = getExpansionRegex();
    var expandedCount = 0;
    
    var newContent = selectedText.replace(expansionRegex, function(match, plainText, bracketedText) {
        expandedCount++;
        
        if (plainText) {
            var expansion = textExpanderConfig.expansionMap[plainText];
            if (shouldFormatAsWikiLink(plainText)) {
                return '[[' + plainText + '|' + expansion + ']]';
            } else {
                return expansion;
            }
        } else if (bracketedText) {
            if (match.indexOf('|') > -1) {
                return match;
            } else {
                var expansion = textExpanderConfig.expansionMap[bracketedText];
                return '[[' + bracketedText + '|' + expansion + ']]';
            }
        }
        return match;
    });
    
    if (expandedCount > 0) {
        $textarea.textSelection('replaceSelection', newContent);
        return expandedCount;
    } else {
        mw.notify('No expandable text found in the selection', {type: 'info'});
        return 0;
    }
}

// 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 a CM editor
    if (activeElement && activeElement.tagName === 'DIV' && $(activeElement).hasClass('cm-content')) {
        return expandTextInCodeMirror();
    }
    
    // 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'});
    }
});
// </nowiki>

Content Disclaimer

Informasi ini disarikan dari Wikipedia dan disajikan kembali untuk tujuan edukasi. Konten tersedia di bawah lisensi CC BY-SA 3.0. Kami tidak bertanggung jawab atas ketidakakuratan data yang bersumber dari kontribusi publik tersebut.

  1. The information displayed on this website is sourced in part or in whole from Wikipedia and has been adapted for the purpose of restating it. We strive to provide accurate and relevant information, however:
  2. There is no guarantee of absolute accuracy. Wikipedia is an open, collaborative project that can be edited by anyone, so information is subject to change.
  3. It is not intended to constitute professional advice. The content displayed is for informational and educational purposes only. For important decisions (e.g., medical, legal, or financial), please consult a professional.
  4. Content copyright. Wikipedia is licensed under the Creative Commons Attribution-ShareAlike License (CC BY-SA). This means that content may be reused with appropriate attribution and shared under a similar license.
  5. Responsible use. Any risk arising from the use of information from this website is entirely the responsibility of the user.