// Global to hold a reference to the OOUI.WindowManager.
var EasyLinks = {};
// Make sure the modules we need are loaded (will only load if not already)
mw.loader.using(['mediawiki.util', 'oojs-ui-core', 'oojs-ui-windows', 'jquery.ui', 'mediawiki.api'], function () {
// Wait for the page to be parsed
$(document).ready(function () {
// TODO: Check for special pages and abort or…
// TODO: …only whitelist namespaces known to make sense.
// Let's at least disable Special:-pages by default to start.
if (mw.config.get('wgCanonicalNamespace') === 'Special') {
return;
}
// Create and append a window manager.
EasyLinks.windowManager = new OO.ui.WindowManager();
$('body').append(EasyLinks.windowManager.$element);
// Predefine a dialog that we'll open by name when needed.
// Specify common defaults here and we'll add the variable stuff when opening.
EasyLinks.windowManager.addWindows({
linkDialog: new OO.ui.MessageDialog({
actions: [
{
action: 'accept',
label: 'Dismiss',
flags: 'primary'
}
]
})
});
// If URL params include &diff= then this is a diff page, so add diffLink portlet
if (mw.util.getParamValue('diff')) {
var diffPortlet = mw.util.addPortletLink(
'p-cactions', '#', 'Get diff link', 'ca-getdiffwl', 'Get a wikilink to this diff'
);
$(diffPortlet).click(function (event) {
event.preventDefault();
doGetDiffWL();
});
}
// Add permalink portlet unconditionally; if &oldid= is missing we use the
// latest version of the page.
var diffPortlet = mw.util.addPortletLink(
'p-cactions', '#', 'Get permalink', 'ca-getpermalink', 'Get a permanent wikilink to this page'
);
$(diffPortlet).click(function (event) {
event.preventDefault();
doGetPermaWL();
});
// If wgAction is "history", add multidiff portlet
if (mw.config.get('wgAction') === "history") {
var multidiffPortlet = mw.util.addPortletLink(
'p-cactions', '#', 'Get multiple diff links', 'ca-getmdiffwl', 'Get wikilinks for multiple diffs'
);
$(multidiffPortlet).click(function (e) {
e.preventDefault();
setupMultiDiff();
});
}
// Add section link and permalink links to all headings (except page title)
$('h2, h3, h4, h5, h6').has('.mw-headline').each(function() {
var headline = $(this).children('span.mw-headline');
var editsection = $(this).children('span.mw-editsection');
var openbracket = $('<span>[ </span>');
var closebracket = $('<span> ]</span>');
var divider = $('<span> | </span>');
var wlspan = $('<a class="getwikilink" href="#">link</a>').click(function(event) {
event.preventDefault();
doGetSectionWL($(headline).attr('id'));
});
var permaspan = $('<a class="getpermalink" href="#">permalink</span>').click(function(event) {
event.preventDefault();
doGetPermaSectionWL($(headline).attr('id'));
});
var containerspan = $('<span class="mw-editsection getlinks"></span>');
$(containerspan).append(openbracket, wlspan, divider, permaspan, closebracket);
if ($(editsection).length > 0) {
$(containerspan).insertAfter(editsection);
} else {
$(containerspan).insertAfter(headline);
}
});
}); // END: $(document).ready()
// Format the timestamp.
function getTimestring (timestamp) {
const monthNames = ["January", "February", "March", "April", "May", "June",
"July", "August", "September", "October", "November", "December"];
var t = new Date(timestamp);
var Y = t.getUTCFullYear();
var M = monthNames[t.getUTCMonth()];
var D = t.getUTCDate();
var h = t.getUTCHours().padStart(2, '0');
var m = t.getUTCMinutes().padStart(2, '0');
var s = t.getUTCSeconds();
return `${h}:${m}, ${D} ${M} ${Y}`;
}
// Cobble together the wikitext string for mdiff output.
function formatMultiDiff(revid, user, revtime, summary) {
var string = '';
string += '*<small>{{noping|' + user + '}}@';
string += '[[Special:Diff/' + revid + '|' + revtime + ']]: </small>';
string += '<span class="easylinks-editsummary">' + summary + '</span>';
string += "\n";
return string;
}
// Format the edit summary.
function getSummary (apicomment) {
let summary = '';
if (apicomment === '') {
summary = '<small><span class="autocomment">[no edit summary provided]</span></small>';
} else if (/^\/\*.*?\*\/$/.test(apicomment)) {
summary = apicomment.replace(/^\/\*.*?\*\//, '<span class="autocomment">$&: </span>');
summary += '<span class="autocomment">[no edit summary provided]</span>';
summary = '<small>' + summary + '</small>';
} else {
summary = apicomment.replace(/^\/\*.*?\*\//, '<span class="autocomment">$&: </span>');
}
return summary;
}
// Common utility function to put the link(s) on the user's clipboard,
// and pop up a notification that it happened.
function copyAndNotify(title, description, data) {
// Create a textInputWidget to hold the link, and a FieldLayout to wrap it.
var textInput = new OO.ui.MultilineTextInputWidget({
value: data,
multiline: true,
autosize: true,
});
var textField = new OO.ui.FieldLayout(textInput, {align: 'top', label: null});
// Configure the message dialog when it is opened with the window manager's openWindow() method.
var instance = EasyLinks.windowManager.openWindow('linkDialog', {
title: title,
message: textField.$element
});
// Select the link in the textInput for easy copying.
// Has to happen after it's finished opening or the button will steal focus.
instance.opened.then(function () {
// Try to close dialog when the user hits Enter (since we steal focus from the button).
textInput.on('enter', function () {
EasyLinks.windowManager.getCurrentWindow().close({action: 'accept'});
});
// Try to close dialog on copy siince we no longer need it.
textInput.on('copy', function () {
EasyLinks.windowManager.getCurrentWindow().close({action: 'accept'});
});
// Select the link, copy it to the clipboard, notify the user, and close the dialog.
textInput.select();
console.log(window.getSelection().toString());
if (document.execCommand('copy')) {
mw.notify(
description + ' was automatically copied to your clipboard.',
{title: 'Link copied', type: 'info', tag: 'EasyLinksCopied'}
);
EasyLinks.windowManager.getCurrentWindow().close({action: 'accept'});
} else {
// Auto-copy failed; don't close the dialog to let the user copy manually.
}
});
} // END: copyAndNotify()
// Get the diff id from URL params and create a diff wikilink to that rev.
function doGetDiffWL() {
var diff = mw.util.getParamValue('diff');
var diffLink = '[[Special:Diff/' + diff + '|' + diff + ']]';
copyAndNotify(
'Internal wikilink to this diff',
'The wikilink for this diff',
diffLink
);
} // END: doGetDiffWL()
// Get the old rev id from URL params, if present, or the latest rev otherwise.
function doGetPermaWL() {
var oldid;
let kind = '';
if (mw.util.getParamValue('oldid')) {
oldid = mw.util.getParamValue('oldid');
kind = 'this';
} else {
oldid = mw.config.get('wgCurRevisionId');
kind = 'the latest';
}
var oldidLink = '[[Special:PermanentLink/' + oldid + '|' + oldid + ']]';
copyAndNotify(
'Internal wikilink to this version',
'A permanent wikilink to ' + kind + ' revision',
oldidLink
);
} // END: doGetPermaWL()
// Creare a wikilink to a section, irrespective of namespace.
function doGetSectionWL(headlineid) {
var h = $('#' + $.escapeSelector(headlineid));
var title = $(h).text();
var page = mw.config.get('wgPageName');
var sectionLink = '[[' + page + '#' + headlineid + '|' + title + ']]';
copyAndNotify(
'Wikilink to this section',
'A wikilink to this section',
sectionLink
);
} // END: doGetSectionWL()
// Create a permalink to a specific section.
function doGetPermaSectionWL(headlineid) {
var h = $('#' + $.escapeSelector(headlineid));
var title = $(h).text();
var oldid;
// If url contains &oldid= then this is a specific revision of the page,
// otherwise grab the latest revision and use that.
let kind = '';
if (mw.util.getParamValue('oldid')) {
oldid = mw.util.getParamValue('oldid');
kind = 'this';
} else {
oldid = mw.config.get('wgCurRevisionId');
kind = 'the latest';
}
var permaSectionLink = '[[Special:PermanentLink/' + oldid + '#' + headlineid + '|' + title + ']]';
copyAndNotify(
'Permanent wikilink to this section',
'A wikilink to this section at ' + kind + ' revision',
permaSectionLink
);
} // END: doGetPermaSectionWL()
// On history pages, let user pick multiple revisions and get a list of diffs
// for those revisions. Unlike the other functions, this one is "modal": when
// activated we pop up a dialog to prompt the user to pick revisions, and let
// them cancel or confirm the selection.
function setupMultiDiff() {
$('ul#pagehistory > li').prepend(function() {
let revid = $(this).data('mwRevid');
let checkbox = $('<input type="checkbox" />')
.addClass('mdiff-checkbox')
.css('margin-left', '1em')
.css('margin-right', '1em')
.css('vertical-aliign', 'middle')
.data('mdiff-revid', revid);
let cspan = $('<span/>')
.addClass('mdiff-span')
.css('background', '#663399')
.append(checkbox)
.fadeIn(1000);
return cspan;
});
var dialog = $('<div id="mdiff-dialog"><p>Choose the revisions for which you want diffs.</p></div>');
dialog.dialog({
autoOpen: false,
title: 'Choose revisions',
position: {my: 'right top', at: 'right top', of: '#mw-content-text'},
buttons: {
Cancel: function() {
$('.mdiff-checkbox').remove();
$(this).dialog('close');
},
Ok: function() {
$(this).dialog('close');
var selectedRevs = $('.mdiff-checkbox:checked');
if (selectedRevs.length === 0) {
// Nothing selected: remove checkboxes and abort.
$('.mdiff-checkbox').remove();
return;
}
let revisions = [];
selectedRevs.each(function() {
let rev = $(this).data('mdiff-revid');
revisions.push(rev);
});
var diffLinks = '';
var api = new mw.Api();
api.get({
action: 'query',
prop: 'revisions',
rvprop: ['ids', 'flags', 'timestamp', 'user', 'size', 'comment', 'tags'],
revids: revisions
}).done(function (apiResult) {
Object.values(apiResult.query.pages).forEach(function(page) {
page.revisions.forEach(function(p) {
var revid = p.revid;
var user = p.user;
var revsize = p.size;
var revtime = getTimestring(p.timestamp);
var summary = getSummary(p.comment);
diffLinks += formatMultiDiff(revid, user, revtime, summary);
});
});
$('.mdiff-checkbox').remove();
copyAndNotify(
'Wikilinks for these diffs',
'A list of wikilinks for these diffs',
diffLinks
);
}); // END: api.get().done()
} // END: Ok()
} // END: buttons: {}
}); // END: dialog.dialog()
dialog.dialog('open');
} // END: setupMultiDiff()
}); // END: mw.loader.using()