User:Polygnotus/Scripts/WikiTextExpander.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/WikiTextExpander. |
// 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'});
}
});