/***************************************************************************************************
Xunlink --- by Evad37
> The power of XFDcloser's 'unlink backlinks' function, for any page.
***************************************************************************************************/
/* jshint esversion: 6, laxbreak: true, undef: true, maxerr:999 */
/* globals console, window, $, mw, OO, extraJs */
// <nowiki>
$( function($) {
/* ========== Configuration ===================================================================== */
var config = {
// Script info
script: {
// Advert to append to edit summaries
advert: ' ([[User:Evad37/Xunlink|Xunlink]])',
version: '2.0.1'
},
// MediaWiki configuration values
mw: mw.config.get( [
'wgArticleId',
'wgPageName',
'wgUserGroups',
'wgUserName',
'wgFormattedNamespaces',
'wgMonthNames',
'wgNamespaceNumber'
] ),
allowedNamespaces: [0, 6, 100] // article, File, Portal
};
// xfd props, for compatbility with code from XFDcloser
config.xfd = {
// Namespaces to unlink from: main, Template, Portal, Draft
ns_unlink: ['0', '10', '100', '118'],
// Type (files get treated differently)
type: config.mw.wgNamespaceNumber === 6 ? 'ffd' : 'other'
};
/* ========== Validate page suitability ========================================================= */
// Validate namespace
var isCorrectNamespace = config.allowedNamespaces.includes(config.mw.wgNamespaceNumber);
if ( !isCorrectNamespace ) {
return;
}
// If a portal, only make available if deleted
var isPortal = config.mw.wgNamespaceNumber === 100;
var notDeleted = config.mw.wgArticleId > 0;
if ( isPortal && notDeleted ) {
return;
}
/* ========== Dependencies ====================================================================== */
mw.loader.using([
'mediawiki.util', 'mediawiki.api', 'mediawiki.Title',
'oojs-ui-core', 'oojs-ui-widgets', 'oojs-ui-windows', 'jquery.ui',
'ext.gadget.libExtraUtil'
]).then(function() {
/* ========== CSS Styles ========================================================================
* TODO: migrate to css subpage
*/
mw.util.addCSS(
[ // Task notices
'.xfdc-notices { width:80%; font-size:95%; padding-left:2.5em; }',
'.xfdc-notices > p { margin:0; line-height:1.1em; }',
'.xfdc-notice-error { color:#D00000; font-size:92% }',
'.xfdc-notice-warning { color:#9900A2; font-size:92% }',
'.xfdc-notice-error::before, .xfdc-notice-warning::before { content: " ["; }',
'.xfdc-notice-error::after, .xfdc-notice-warning::after { content: "]"; }',
'.xfdc-task-waiting { color:#595959; }',
'.xfdc-task-started { color:#0000D0; }',
'.xfdc-task-done { color:#006800; }',
'.xfdc-task-skipped { color:#697000; }',
'.xfdc-task-aborted { color:#C00049; }',
'.xfdc-task-failed { color:#D00000; }',
// Preview of edit summary
'.xu-preview { background-color:#fafafa; border:1px dotted #777; '+
'margin-top: 0px; padding:0px 10px; font-size: 90%; width: 100%; }'
]
.join('\n')
);
/* ========== Helper functions ==================================================================
* TODO: these should probably be part of one or more script modules/libraries, which could be
* loaded with mw.loader.getScript()
*/
/** safeUnescape
* Un-escapes some HTML tags (<br>, <p>, <ul>, <li>, <hr>, and <pre>); turns wikilinks
* into real links. Ignores anyting within <pre>...</pre> tags.
* Input will first be escaped using mw.html.escape() unless specified
* @param {String} text
* @param {Object} config Configuration options
* @config {Boolean} noEscape - do not escape the input first
* @returns {String} unescaped text
*/
var safeUnescape = function(text, config) {
var path = 'https:' + mw.config.get('wgServer') + '/wiki/';
return ( config && config.noEscape && text || mw.html.escape(text) )
// Step 1: unescape <pre> tags
.replace(
/<(\/?pre\s?\/?)>/g,
'<$1>'
)
// Step 2: replace piped wikilinks with real links (unless inside <pre> tags)
.replace(
/\[\[([^\|\]]*?)\|([^\|\]]*?)\]\](?![^<]*?<\/pre>)/g,
'<a href="' + path + mw.util.wikiUrlencode('$1') + '" target="_blank">$2</a>'
)
// Step 3: replace other wikilinks with real links (unless inside <pre> tags)
.replace(
/\[\[([^\|\]]+?)]\](?![^<]*?<\/pre>)/g,
'<a href="' + path + mw.util.wikiUrlencode('$1') + '" target="_blank">$1</a>'
)
// Step 4: unescape other tags: <br>, <p>, <ul>, <li>, <hr> (unless inside <pre> tags)
.replace(
/<(\/?(?:br|p|ul|li|hr)\s?\/?)>(?![^<]*?<\/pre>)/g,
'<$1>'
);
};
/** multiButtonConfirm
* @param {Object} config
* @config {String} title Title for the dialogue
* @config {String} message Message for the dialogue. HTML tags (except for <br>, <p>, <ul>,
* <li>, <hr>, and <pre> tags) are escaped; wikilinks are turned into real links.
* @config {Array} actions Optional. Array of configuration objects for OO.ui.ActionWidget
* <https://doc.wikimedia.org/oojs-ui/master/js/#!/api/OO.ui.ActionWidget>.
* If not specified, the default actions are 'accept' (with label 'OK') and 'reject' (with
* label 'Cancel').
* @config {String} size Symbolic name of the dialog size: small, medium, large, larger or full.
* @return {Promise<String>} action taken by user
*/
var multiButtonConfirm = function(config) {
var dialogClosed = $.Deferred();
// Wrap message in a HtmlSnippet to prevent escaping
var htmlSnippetMessage = new OO.ui.HtmlSnippet(
safeUnescape(config.message)
);
var windowManager = new OO.ui.WindowManager();
var messageDialog = new OO.ui.MessageDialog();
$('body').append( windowManager.$element );
windowManager.addWindows( [ messageDialog ] );
windowManager.openWindow( messageDialog, {
'title': config.title,
'message': htmlSnippetMessage,
'actions': config.actions,
'size': config.size
} );
windowManager.on('closing', function(_win, promise) {
promise.then(function(data) {
dialogClosed.resolve(data && data.action);
windowManager.destroy();
});
});
return dialogClosed.promise();
};
var makeErrorMsg = function(code, jqxhr) {
var details = '';
if ( code === 'http' && jqxhr.textStatus === 'error' ) {
details = 'HTTP error ' + jqxhr.xhr.status;
} else if ( code === 'http' ) {
details = 'HTTP error: ' + jqxhr.textStatus;
} else if ( code === 'ok-but-empty' ) {
details = 'Error: Got an empty response from the server';
} else {
details = 'API error: ' + code;
}
return details;
};
var arrayFromResponsePages = function(response) {
return $.map(response.query.pages, function(page) { return page; });
};
/* ========== API =============================================================================== */
var API = new mw.Api( {
ajax: {
headers: {
'Api-User-Agent': 'Xunlink/' + config.script.version +
' ( https://en.wikipedia.org/wiki/User:Evad37/Xunlink )'
}
}
} );
/* ========== Unlink backlinks ================================================================== */
/**unlinkBacklinks
*
* Copied from XFDcloser, with minimal changes. Such changes have the original code in comments
* beginning `XFDC:`
*
* TODO: merge code, and import the same copy here and into XFDcloser
*
* @param self Object to hold some input date, and to recieve status messages
*/
var unlinkBacklinks = function(self) {
// Notify task is started
self.setStatus('started');
var pageTitles = [config.mw.wgPageName]; // XFDC: self.discussion.getPageTitles(self.pages)
var redirectTitles = [];
// Ignore the following titles, and any of their subpages
var ignoreTitleBases = [
'Template:WPUnited States Article alerts',
'Template:Article alerts columns',
'Template:Did you know nominations'
];
var getBase = function(title) {
return title.split('/')[0];
};
var blresults = [];
var iuresults = [];
//convert results (arrays of objects) to titles (arrays of strings), removing duplicates
var flattenToTitles = function(results) {
return results.reduce(
function(flatTitles, result) {
if ( result.redirlinks ) {
if ( !redirectTitles.includes(result.title)) {
redirectTitles.push(result.title);
}
return flatTitles.concat(
result.redirlinks.reduce(
function(flatRedirLinks, redirLink) {
if (
flatTitles.includes(redirLink.title) ||
pageTitles.includes(redirLink.title) ||
ignoreTitleBases.includes(getBase(redirLink.title))
) {
return flatRedirLinks;
} else {
return flatRedirLinks.concat(redirLink.title);
}
},
[]
)
);
} else if (
result.redirect === '' ||
flatTitles.includes(result.title) ||
pageTitles.includes(result.title) ||
ignoreTitleBases.includes(getBase(result.title))
) {
return flatTitles;
} else {
return flatTitles.concat(result.title);
}
},
[]
);
};
var apiEditPage = function(pageTitle, newWikitext) {
API.postWithToken( 'csrf', {
action: 'edit',
title: pageTitle,
text: newWikitext,
summary: self.editSummary + config.script.advert, /* XFDC:
'Removing link(s)' +
(( config.xfd.type === 'ffd' ) ? ' / file usage(s)' : '' ) +
': [[' + self.discussion.getNomPageLink() + ']] closed as ' +
self.inputData.getResult() + config.script.advert, */
minor: 1,
nocreate: 1
} )
.done( function() {
self.track('unlink', true);
} )
.fail( function(code, jqxhr) {
self.track('unlink', false);
self.addApiError(code, jqxhr, [
'Could not remove backlinks from ',
extraJs.makeLink(pageTitle)
]);
} );
};
/**
* @param {String} pageTitle
* @param {String} wikitext
* @returns {Promise(String)} updated wikitext, with any list items either removed or unlinked
*/
var checkListItems = function(pageTitle, wikitext) {
// Find lines marked with {{subst:void}}, and the preceding section heading (if any)
var toReview = /^{{subst:void}}(.*)$/m.exec(wikitext);
if ( !toReview ) {
// None found, no changes needed
return $.Deferred().resolve(wikitext).promise();
}
// Find the preceding heading, if any
var precendingText = wikitext.split('{{subst:void}}')[0];
var allHeadings = precendingText.match(/^=+.+?=+$/gm);
var heading = ( !allHeadings ) ? null : allHeadings[allHeadings.length - 1].replace(/(^=* *| *=*$)/g, '');
// Prompt user
return multiButtonConfirm({
title: 'Review unlinked list item',
message: '[[' + pageTitle +
( ( heading ) ? '#' +
mw.util.wikiUrlencode(
heading.replace(/\[\[([^\|\]]*?)\|([^\]]*?)\]\]/, '$2')
.replace(/\[\[([^\|\]]*?)\]\]/, '$1')
) + ']]' : ']]' ) +
': ' +
'<pre>' + toReview[1] + '</pre>',
actions: [
{ label:'Keep item', action:'keep' },
{ label:'Remove item', action:'remove'}
],
size: 'medium'
})
.then(function(action) {
if ( action === 'keep' ) {
// Remove the void from the start of the line
wikitext = wikitext.replace(/^{{subst:void}}/m, '');
} else {
// Remove the whole line
wikitext = wikitext.replace(/^{{subst:void}}.*\n?/m, '');
}
// Iterate, in case there is more to be reviewed
return checkListItems(pageTitle, wikitext);
});
};
var processUnlinkPages = function(result) {
if ( !result.query || !result.query.pages ) {
// No results
self.addApiError('result.query.pages not found', null, 'Could not read contents of pages; '+
'could not remove backlinks');
console.log('[XFDcloser] API error: result.query.pages not found... result =');
console.log(result);
self.setStatus('failed');
return;
}
// For each page, pass the wikitext through the unlink function
var pages = arrayFromResponsePages(result);
pages.reduce(
function(previous, page) {
return $.when(previous).then(function(){
var oldWikitext = page.revisions[0]['*'];
var newWikitext = extraJs.unlink(
oldWikitext,
pageTitles.concat(redirectTitles),
page.ns,
!!page.categories
);
if ( oldWikitext !== newWikitext ) {
var confirmedPromise = checkListItems(page.title, newWikitext);
confirmedPromise.then(function(updatedWikitext) {
apiEditPage(page.title, updatedWikitext);
});
return confirmedPromise;
} else {
self.addWarning(['Skipped ',
extraJs.makeLink(page.title),
' (no direct links)'
]);
self.track('unlink', false);
return true;
}
});
},
true);
};
var apiReadFail = function(code, jqxhr) {
self.addApiError(code, jqxhr, 'Could not read contents of pages; '+
'could not remove backlinks');
self.setStatus('failed');
};
var processResults = function() {
// Flatten results arrays
if ( blresults.length !== 0 ) {
blresults = flattenToTitles(blresults);
}
if ( iuresults.length !== 0 ) {
iuresults = flattenToTitles(iuresults);
// Remove image usage titles that are also in backlikns results
iuresults = iuresults.filter(function(t) { return $.inArray(t, blresults) === -1; });
}
// Check if, after flattening, there are still backlinks or image uses
if ( blresults.length === 0 && iuresults.length === 0 ) {
self.addWarning('none found');
self.setStatus('skipped');
return;
}
// Ask user for confirmation
var heading = 'Unlink backlinks';
if ( iuresults.length !== 0 ) {
heading += '(';
if ( blresults.length !== 0 ) {
heading += 'and ';
}
heading += 'file usage)';
}
heading += ':';
var para = '<p>All '+ (blresults.length + iuresults.length) + ' pages listed below may be '+
'edited (unless backlinks are only present due to transclusion of a template).</p>'+
'<p>To process only some of these pages, use Twinkle\'s unlink tool instead.</p>'+
'<p>Use with caution, after reviewing the pages listed below. '+
'Note that the use of high speed, high volume editing software (such as this tool and '+
'Twinkle\'s unlink tool) is subject to the Bot policy\'s [[WP:ASSISTED|Assisted editing guidelines]] '+
'</p><hr>';
var list = '<ul>';
if ( blresults.length !== 0 ) {
list += '<li>[[' + blresults.join(']]</li><li>[[') + ']]</li>';
}
if ( iuresults.length !== 0 ) {
list += '<li>[[' + iuresults.join(']]</li><li>[[') + ']]</li>';
}
list += '<ul>';
multiButtonConfirm({
title: heading,
message: para + list,
actions: [
{ label: 'Cancel', flags: 'safe' },
{ label: 'Remove backlinks', action: 'accept', flags: 'progressive' }
],
size: 'medium'
})
.then(function(action) {
if ( action ) {
var unlinkTitles = iuresults.concat(blresults);
self.setupTracking('unlink', unlinkTitles.length);
self.showTrackingProgress = 'unlink';
// get wikitext of titles, check if disambig - in lots of 50 (max for Api)
for (var ii=0; ii<unlinkTitles.length; ii+=50) {
API.get( {
action: 'query',
titles: unlinkTitles.slice(ii, ii+49).join('|'),
prop: 'categories|revisions',
clcategories: 'Category:All disambiguation pages',
rvprop: 'content',
indexpageids: 1
} )
.done( processUnlinkPages )
.fail( apiReadFail );
}
} else {
self.addWarning('Cancelled by user');
self.setStatus('skipped');
}
});
};
// Queries
var blParams = {
list: 'backlinks',
blfilterredir: 'nonredirects',
bllimit: 'max',
blnamespace: config.xfd.ns_unlink,
blredirect: 1
};
var iuParams = {
list: 'backlinks|imageusage',
iutitle: '',
iufilterredir: 'nonredirects',
iulimit: 'max',
iunamespace: config.xfd.ns_unlink,
iuredirect: 1
};
var query = pageTitles.map(function(page) {
return $.extend(
{ action: 'query' },
blParams,
{ bltitle: page },
( config.xfd.type === 'ffd' ) ? iuParams : null,
( config.xfd.type === 'ffd' ) ? { iutitle: page } : null
);
});
// Variable for incrementing current query
var qIndex = 0;
// Function to do Api query
var apiQuery = function(q) {
API.get( q )
.done( processBacklinks )
.fail( function(code, jqxhr) {
self.addApiError(code, jqxhr, 'Could not retrieve backlinks');
self.setStatus('failed');
// Allow delete redirects task to begin
// XFDC: self.discussion.taskManager.dfd.ublQuery.resolve();
} );
};
// Process api callbacks
var processBacklinks = function(result) {
// Gather backlink results into array
if ( result.query.backlinks ) {
blresults = blresults.concat(result.query.backlinks);
}
// Gather image usage results into array
if ( result.query.imageusage ) {
iuresults = iuresults.concat(result.query.imageusage);
}
// Continue current query if needed
if ( result.continue ) {
apiQuery($.extend({}, query[qIndex], result.continue));
return;
}
// Start next query, unless this is the final query
qIndex++;
if ( qIndex < query.length ) {
apiQuery(query[qIndex]);
return;
}
// Allow delete redirects task to begin
// XFDC: self.discussion.taskManager.dfd.ublQuery.resolve();
// Check if any backlinks or image uses were found
if ( blresults.length === 0 && iuresults.length === 0 ) {
self.addWarning('none found');
self.setStatus('skipped');
return;
}
// Process the results
processResults();
};
// Get started
apiQuery(query[qIndex]);
};
/* Task class for `self` object in unlinkBacklinks function
* Very minimal copy of Task class from XFDcloser
*/
// Constructor
var Task = function(conf) {
this.description = 'Unlinking backlinks';
this.status = 'waiting';
this.errors = [];
this.warnings = [];
this.tracking = {};
this.editSummary = conf.editSummary;
this.$notices = $('<div>').attr('id','Xunlink-notices');
$('#mw-content-text').prepend(this.$notices);
$('<h2>').text('Xunlink').insertBefore(this.$notices);
$('<hr>').insertAfter(this.$notices);
};
Task.prototype.setStatus = function(s) {
this.status = s;
this.updateTaskNotices();
};
Task.prototype.setupTracking = function(key, total, allDoneCallback, allSkippedCallback) {
var self = this;
if ( allDoneCallback == null && allSkippedCallback == null ) {
allDoneCallback = function() { this.setStatus('done'); };
allSkippedCallback = function() { this.setStatus('skipped'); };
}
this.tracking[key] = {
success: 0,
skipped: 0,
total: total,
dfd: $.Deferred()
.done($.proxy(allDoneCallback, self))
.fail($.proxy(allSkippedCallback, self))
};
};
Task.prototype.track = function(key, success) {
if ( success ) {
this.tracking[key].success++;
} else {
this.tracking[key].skipped++;
}
if ( key === this.showTrackingProgress ) {
this.updateTaskNotices(); // XFDC: this.updateStatus();
}
if ( this.tracking[key].skipped === this.tracking[key].total ) {
this.tracking[key].dfd.reject();
} else if ( this.tracking[key].success + this.tracking[key].skipped === this.tracking[key].total ) {
this.tracking[key].dfd.resolve();
}
};
Task.prototype.addError = function(e, critical) {
// XFDC: var self = this;
this.errors.push($('<span>').addClass('xfdc-notice-error').append(e));
if ( critical ) {
this.status = 'failed';
}
this.updateTaskNotices(); // XFDC: this.discussion.taskManager.updateTaskNotices(self);
};
Task.prototype.addWarning = function(w) {
// XFDC: var self = this;
this.warnings.push($('<span>').addClass('xfdc-notice-warning').append(w));
this.updateTaskNotices(); // XFDC: this.discussion.taskManager.updateTaskNotices(self);
};
Task.prototype.addApiError = function(code, jqxhr, explanation, critical) {
var self = this;
self.addError([
makeErrorMsg(code, jqxhr),
' – ',
$('<span>').append(explanation)
], !!critical);
};
Task.prototype.getStatusText = function() {
var self = this;
switch ( self.status ) {
// Not yet started:
case 'waiting':
return 'Waiting...';
// In progress:
case 'started':
var $msg = $('<span>').append(
$('<img>').attr({
'src':'//upload.wikimedia.org/wikipedia/commons/thumb/f/f8/Ajax-loader%282%29.gif/'+
'40px-Ajax-loader%282%29.gif',
'width':'20',
'height':'5'
})
);
if ( self.showTrackingProgress ) {
var counts = this.tracking[self.showTrackingProgress];
$msg.append(
$('<span>')
.css('font-size', '88%')
.append(
' (' +
(counts.success + counts.skipped) +
' / ' +
counts.total +
')'
)
);
}
return $msg;
// Finished:
case 'done':
return 'Done!';
case 'aborted':
case 'failed':
case 'skipped':
return extraJs.toSentenceCase(self.status) + '.';
default:
// unknown
return '';
}
};
// Based on XFDC's taskManager.prototype.updateTaskNotices
Task.prototype.updateTaskNotices = function() {
var task = this; // XFDC: var self = this;
var $notices = this.$notices;
var note = $('<p>')
.addClass('xfdc-task-' + task.status)
.addClass(task.name)
.append(
$('<span>').append(task.description),
': ',
$('<strong>').append(task.getStatusText()),
$('<span>').append(task.errors),
$('<span>').append(task.warnings)
);
$notices.empty().append(note);
};
/* ========== Main dialog ======================================================================= */
// Make a subclass of ProcessDialog
function MainDialog( config ) {
MainDialog.super.call( this, config );
}
OO.inheritClass( MainDialog, OO.ui.ProcessDialog );
// Specify a name for .addWindows()
MainDialog.static.name = 'mainDialog';
// Specify the static configurations: title and action set
MainDialog.static.title = 'Xunlink';
MainDialog.static.actions = [
{
flags: [ 'primary', 'progressive' ],
label: 'Continue',
action: 'continue'
},
{
flags: 'safe',
label: 'Cancel'
}
];
// Customize the initialize() function to add content and layouts:
MainDialog.prototype.initialize = function () {
MainDialog.super.prototype.initialize.call( this );
this.panel = new OO.ui.PanelLayout( {
padded: true,
expanded: false
} );
this.content = new OO.ui.FieldsetLayout();
this.summaryInput = new OO.ui.TextInputWidget();
this.summaryPreview = new OO.ui.LabelWidget({classes: ['xu-preview']});
this.summaryInputField = new OO.ui.FieldLayout( this.summaryInput, {
label: 'Enter the reason for link removal',
align: 'top'
} );
this.summaryPreviewField = new OO.ui.FieldLayout( this.summaryPreview, {
label: 'Edit summary preview:',
align: 'top'
} );
this.content.addItems( [this.summaryInputField, this.summaryPreviewField] );
this.panel.$element.append( this.content.$element );
this.$body.append( this.panel.$element );
this.summaryInput.connect( this, { 'change': 'onSummaryInputChange' } );
};
// Specify any additional functionality required by the window (disable using an empty summary)
MainDialog.prototype.onSummaryInputChange = function ( value ) {
this.actions.setAbilities( {
continue: !!value.length
} );
var dialog = this;
if ( !value.length ) {
dialog.summaryPreviewField.toggle(false);
dialog.updateSize();
} else {
API.get({
action: 'parse',
contentmodel: 'wikitext',
summary: 'Removing link(s): ' + value + config.script.advert,
})
.then(function(result) {
var $preview = $('<p>').append(result.parse.parsedsummary['*']);
$preview.find('a').attr('target', '_blank');
dialog.summaryPreview.setLabel($preview);
dialog.summaryPreviewField.toggle(true);
dialog.updateSize();
});
}
};
// Specify the dialog height (or don't to use the automatically generated height).
MainDialog.prototype.getBodyHeight = function () {
// Note that "expanded: false" must be set in the panel's configuration for this to work.
return this.panel.$element.outerHeight( true );
};
// Use getSetupProcess() to set up the window with data passed to it at the time
// of opening
MainDialog.prototype.getSetupProcess = function ( data ) {
data = data || {};
return MainDialog.super.prototype.getSetupProcess.call( this, data )
.next( function () {
// Set up contents based on data
var dataSumamary = data.summary || '';
this.summaryInput.setValue( dataSumamary );
this.onSummaryInputChange(dataSumamary);
}, this );
};
// Specify processes to handle the actions.
MainDialog.prototype.getActionProcess = function ( action ) {
var dialog = this;
if ( action === 'continue' ) {
/* Create a new process to handle the action
return new OO.ui.Process( function () {
var task = new Task(this.summaryInput.getValue());
unlinkBacklinks(task);
}, this );
*/
var task = new Task( {editSummary: 'Removing link(s): ' + this.summaryInput.getValue()} );
dialog.close();
task.updateTaskNotices();
unlinkBacklinks(task);
}
// Fallback to parent handler
return MainDialog.super.prototype.getActionProcess.call( this, action );
};
// Use the getTeardownProcess() method to perform actions whenever the dialog is closed.
// This method provides access to data passed into the window's close() method
// or the window manager's closeWindow() method.
MainDialog.prototype.getTeardownProcess = function ( data ) {
return MainDialog.super.prototype.getTeardownProcess.call( this, data )
.first( function () {
// Perform any cleanup as needed
this.summaryInput.setValue("");
}, this );
};
// Create and append a window manager.
var windowManager = new OO.ui.WindowManager();
$( 'body' ).append( windowManager.$element );
// Create a new process dialog window.
var mainDialog = new MainDialog();
// Add the window to window manager using the addWindows() method.
windowManager.addWindows( [ mainDialog ] );
/* ========== Portlet link ====================================================================== */
// handlePortletClick
var handlePortletClick = function(e) {
e.preventDefault();
// Try to find the deletion log comment
var comment = '';
var $commentEl = $('.mw-logline-delete').first().find('.comment').first();
if ( $commentEl.length ) {
var commentEl = $commentEl.get()[0];
var children = commentEl.childNodes;
for (var child of children) {
var nodeName = child.nodeName;
if (nodeName == 'A') {
var target = child.href.replace(/^.*?\/wiki\//, '').replace(/_/g,' ');
var label = child.textContent;
var wikilink = ( target === label ) ?
'[[' + label + ']]' :
'[[' + target + '|' + label + ']]';
comment += wikilink;
} else {
comment += child.nodeValue;
}
}
comment = comment.replace(' ([[Wikipedia:XFDC|XFDcloser]])', '');
comment = comment.slice(1,-1);
}
// Open the window!
windowManager.openWindow( mainDialog, { summary: comment } );
};
var portletLink = mw.util.addPortletLink(
'p-cactions',
'#',
'Xunlink',
'ca-xu',
"Unlink this page's backlinks using Xunlink",
null,
"#ca-move"
);
$(portletLink).on('click', handlePortletClick);
}); // End of dependencies loaded callback
}); // End of page load callback
// </nowiki>