// Add a custom reference button to Wikipedia's Visual Editor that inserts a pre-formatted source citation
// Add this code to your common.js page on Wikipedia
// (e.g., https://en.wikipedia.org/wiki/User:YourUsername/common.js)
// Wait for the VisualEditor to be ready
mw.loader.using(['ext.visualEditor.desktopArticleTarget']).then(function() {
// Register our custom reference tool with VisualEditor
mw.hook('ve.loadModules').add(function(addPlugin) {
addPlugin(makeRefTool);
});
// Also add the button when the editor is activated
mw.hook('ve.activationComplete').add(function() {
console.log('VE activation hook fired');
// Wait a bit for the toolbar to fully initialize
setTimeout(function() {
addRefButton();
}, 1000);
// Also add the button when the surface is ready
if (typeof ve !== 'undefined' && ve.init && ve.init.target) {
ve.init.target.on('surfaceReady', function() {
console.log('Surface ready event fired');
addRefButton();
});
}
});
});
// Function to create and register the reference command and tool
function makeRefTool() {
// Create a reference with the source template
// Using Parsoid-style structure to ensure proper template parsing
var refContent = [{
type: 'mwReference',
attributes: {
mw: {
name: 'ref-' + Math.random().toString(36).substring(2, 8), // Generate a unique ID
body: {
id: 'body-' + Math.random().toString(36).substring(2, 8), // Generate a unique ID
type: 'mwReferenceContents',
// This is the key part - using proper Parsoid structure for the template
html: '<span typeof="mw:Transclusion" data-mw=\'{"parts":[{"template":{"target":{"wt":"source","href":"./Template:Source"},"params":{"url":{"wt":"https://www.atlantanewsfirst.com/2025/04/07/questions-remain-after-dead-horse-found-downtown-atlanta/"},"title":{"wt":"Questions remain after dead horse found in downtown Atlanta"},"author":{"wt":"Adam Murphy"},"date":{"wt":"April 7, 2025"},"pub":{"wt":"Atlanta News First"}},"i":0}}]}\'></span>'
}
}
}
}];
// Register the command with VisualEditor
ve.ui.commandRegistry.register(
new ve.ui.Command('myrefcommand', 'content', 'insert', {
args: [refContent, false, true],
supportedSelections: ['linear']
})
);
// Create and register tool
function RefTool() {
RefTool.parent.apply(this, arguments);
}
OO.inheritClass(RefTool, ve.ui.Tool);
RefTool.static.name = 'reftool';
RefTool.static.group = 'insert';
RefTool.static.title = 'Insert Atlanta News Reference';
RefTool.static.commandName = 'myrefcommand';
RefTool.static.icon = 'reference'; // Using built-in reference icon
ve.ui.toolFactory.register(RefTool);
}
// Function to manually add the reference button to the toolbar
function addRefButton() {
console.log('Attempting to add Reference button');
// Check if our button already exists to avoid duplicates
if ($('.oo-ui-tool-name-customRefTool').length) {
console.log('Reference button already exists');
return;
}
// Create a proper new group for our button
var $toolGroup = $('<div>')
.addClass('ve-ui-toolbar-group-reference oo-ui-widget oo-ui-toolGroup oo-ui-barToolGroup oo-ui-widget-enabled')
.attr('title', 'Reference Tools');
var $toolsContainer = $('<div>')
.addClass('oo-ui-toolGroup-tools oo-ui-barToolGroup-tools oo-ui-toolGroup-enabled-tools')
.appendTo($toolGroup);
// Create the button itself matching other buttons' structure
var $button = $('<span>')
.addClass('oo-ui-widget oo-ui-iconElement oo-ui-tool-with-icon oo-ui-tool oo-ui-tool-name-customRefTool oo-ui-widget-enabled')
.appendTo($toolsContainer);
// Create the link element
var $link = $('<a>')
.addClass('oo-ui-tool-link')
.attr('role', 'button')
.attr('tabindex', '0')
.attr('title', 'Insert Atlanta News Reference')
.appendTo($button);
// Add the icon structure
$('<span>')
.addClass('oo-ui-tool-checkIcon oo-ui-widget oo-ui-widget-enabled oo-ui-iconElement oo-ui-iconElement-icon oo-ui-icon-check oo-ui-labelElement-invisible oo-ui-iconWidget')
.appendTo($link);
$('<span>')
.addClass('oo-ui-iconElement-icon oo-ui-icon-reference')
.appendTo($link);
$('<span>')
.addClass('oo-ui-tool-title')
.text('Atlanta Ref')
.appendTo($link);
// Add click event to insert the reference at cursor position
$button.on('click', function(e) {
e.preventDefault();
e.stopPropagation();
// Get the current visual editor surface
var surface = ve.init.target.getSurface();
// Create reference content with the template using Parsoid structure
var refContent = [{
type: 'mwReference',
attributes: {
mw: {
name: 'ref-' + Math.random().toString(36).substring(2, 8),
body: {
id: 'body-' + Math.random().toString(36).substring(2, 8),
type: 'mwReferenceContents',
// Using proper Parsoid structure for the template
html: '<span typeof="mw:Transclusion" data-mw=\'{"parts":[{"template":{"target":{"wt":"source","href":"./Template:Source"},"params":{"url":{"wt":"https://www.atlantanewsfirst.com/2025/04/07/questions-remain-after-dead-horse-found-downtown-atlanta/"},"title":{"wt":"Questions remain after dead horse found in downtown Atlanta"},"author":{"wt":"Adam Murphy"},"date":{"wt":"April 7, 2025"},"pub":{"wt":"Atlanta News First"}},"i":0}}]}\'></span>'
}
}
}
}];
// Insert the reference at the cursor position
var fragment = surface.getModel().getFragment();
fragment.insertContent(refContent, true);
// Optionally, you can add a notification to confirm the reference was added
if (ve.init.target.showNotifications) {
ve.init.target.showNotifications([
{ type: 'success', message: 'Atlanta News reference added' }
]);
}
});
// Insert our group at an appropriate ___location in the toolbar
// Find the cite or insert group to add after
var $insertPosition = $('.ve-ui-toolbar-group-cite, .ve-ui-toolbar-group-insert').last();
if ($insertPosition.length) {
$toolGroup.insertAfter($insertPosition);
console.log('Reference button added successfully after', $insertPosition.attr('class'));
} else {
// Fallback: add to main toolbar
$('.oo-ui-toolbar-tools').first().append($toolGroup);
console.log('Reference button added to main toolbar');
}
}