User:Polygnotus/Scripts/WikiTextExpander.js
// 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 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(
'(?
'\\[\\[(' + 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 '' + 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 '' + 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 '' + 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 '' + 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 = $('
.attr('id', 'text-settings-dialog')
.attr('title', 'WikiTextExpander Settings')
.css({
'display': 'none'
});
// Create the dialog content
var $content = $('
// Create tabs
var $tabs = $('
var $tabList = $('
- ');
- ').append($('').attr('href', '#expansion-tab').text('Expansions')));
$tabList.append($('
- ').append($('').attr('href', '#hotkey-tab').text('Hotkey')));
$tabs.append($tabList);
// Expansions tab
var $expansionTab = $('
').attr('id', 'expansion-tab');$expansionTab.append($('
').text('Edit your shorthand text and their expansions:'));
var $expansionTable = $('
').addClass('wikitable').css('width', '100%');
// Table header
var $tableHeader = $('
'); $tableHeader.append($('
').text('Shorthand')); $tableHeader.append($('
').text('Expansion')); $tableHeader.append($('
').text('Type')); $tableHeader.append($('
').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 = $('
.text('Add New Expansion')
.click(function() {
addExpansionRow($expansionTable, , );
});
// Import/Export area
var $importExportArea = $('
').css('margin-top', '15px');var $exportButton = $('
.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 = $('
.val(jsonData)
.css({
'width': '100%',
'height': '100px',
'margin-top': '10px',
'font-family': 'monospace'
});
$importExportArea.find('textarea').remove();
$importExportArea.append($textarea);
$textarea.select();
});
var $importButton = $('
.text('Import from JSON')
.click(function() {
var $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 = $('
.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($('
').css('margin-top', '10px').append($addButton));$expansionTab.append($importExportArea);
// Hotkey tab
var $hotkeyTab = $('
').attr('id', 'hotkey-tab');$hotkeyTab.append($('
').text('Configure the hotkey for expanding text:'));
var $hotkeyForm = $('
').addClass('mw-widget-aeInputs');// Checkboxes for modifier keys
var $modifiers = $('
').css('margin-bottom', '10px');var $ctrlLabel = $('
var $ctrlCheck = $('').attr('type', 'checkbox').prop('checked', textExpanderConfig.hotkey.ctrlKey);
$ctrlLabel.append($ctrlCheck).append(' Ctrl');
var $shiftLabel = $('
var $shiftCheck = $('').attr('type', 'checkbox').prop('checked', textExpanderConfig.hotkey.shiftKey);
$shiftLabel.append($shiftCheck).append(' Shift');
var $altLabel = $('
var $altCheck = $('').attr('type', 'checkbox').prop('checked', textExpanderConfig.hotkey.altKey);
$altLabel.append($altCheck).append(' Alt');
$modifiers.append($ctrlLabel).append($shiftLabel).append($altLabel);
// Key input
var $keyLabel = $('
var $keyInput = $('')
.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 = $('
').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('Current Hotkey: ' + 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 = $('
.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 = $('
'); var $keyCell = $('
'); var $keyInput = $('')
.addClass('shorthand')
.attr('type', 'text')
.val(key)
.css('width', '100%');
$keyCell.append($keyInput);
var $expansionCell = $('
'); var $expansionInput = $('')
.addClass('expansion')
.attr('type', 'text')
.val(expansion)
.css('width', '100%');
$expansionCell.append($expansionInput);
var $typeCell = $('
').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 = $('
').css('text-align', 'center'); var $deleteButton = $('
.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 = $('
- ')
.attr('id', 'ca-text-expander')
.addClass('mw-list-item mw-list-item-js')
.append(
$('')
.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'});
}
});
- ').append($('').attr('href', '#hotkey-tab').text('Hotkey')));
$tabList.append($('