User:Evad37/XFDcloser/v3.js: Difference between revisions
Appearance
< User:Evad37 | XFDcloser
Content deleted Content added
Version 3.5.4: Prevent query http errors when retrieving page information (by just rejecting those with more than 50 pages) |
Fix script |
||
Line 1,751: | Line 1,751: | ||
), |
), |
||
// Rcats |
// Rcats |
||
/* |
|||
$('<div>').append( |
$('<div>').append( |
||
$('<input>').attr({ |
$('<input>').attr({ |
||
Line 1,767: | Line 1,768: | ||
'{{R to anchor}}". Leave blank if unsure which rcat to use.') |
'{{R to anchor}}". Leave blank if unsure which rcat to use.') |
||
), |
), |
||
self.rcatSelector.$element.detach() |
//self.rcatSelector.$element.detach() |
||
) |
)*/ |
||
), |
), |
||
Line 2,052: | Line 2,053: | ||
if ( config.xfd.type !== 'ffd' && !self.discussion.isBasicMode() ) { |
if ( config.xfd.type !== 'ffd' && !self.discussion.isBasicMode() ) { |
||
this.rcatSelector = extraJs.makeRcatCapsuleMultiselect(self.storeRcatData, self); |
//this.rcatSelector = extraJs.makeRcatCapsuleMultiselect(self.storeRcatData, self); |
||
} |
} |
||
Line 2,072: | Line 2,073: | ||
} else { |
} else { |
||
if ( config.xfd.type === 'afd' ) { |
if ( config.xfd.type === 'afd' ) { |
||
this.rcatSelector.setItemsFromData(['{{R to related topic}}']); |
//this.rcatSelector.setItemsFromData(['{{R to related topic}}']); |
||
} |
} |
||
this.addToBody(this.makeAfterActions()); |
this.addToBody(this.makeAfterActions()); |
||
Line 2,093: | Line 2,094: | ||
if ( config.xfd.type !== 'ffd' && !self.discussion.isBasicMode() ) { |
if ( config.xfd.type !== 'ffd' && !self.discussion.isBasicMode() ) { |
||
this.rcatSelector = extraJs.makeRcatCapsuleMultiselect(self.storeRcatData, self); |
//this.rcatSelector = extraJs.makeRcatCapsuleMultiselect(self.storeRcatData, self); |
||
} |
} |
||
Line 2,109: | Line 2,110: | ||
this.addToBody(this.makeAfterActions(true)); |
this.addToBody(this.makeAfterActions(true)); |
||
if ( config.xfd.type === 'afd' ) { |
if ( config.xfd.type === 'afd' ) { |
||
this.rcatSelector.setItemsFromData(['{{R to related topic}}']); |
//this.rcatSelector.setItemsFromData(['{{R to related topic}}']); |
||
} |
} |
||
this.addToBody(this.makeOptions(true)); |
this.addToBody(this.makeOptions(true)); |
Revision as of 23:53, 20 October 2018
/*******************************************************************************
XFDcloser --- by Evad37
> A script that assists in closing AFD, CFD, FFD, MFD, RFD, and TFD discussions
--------------------------------------------------------------------------------
* See [[User:Evad37/XFDcloser]] for installation details and documentation
--------------------------------------------------------------------------------
* Version 3.5.4-sandbox
* See page history for change log
--------------------------------------------------------------------------------
* IMPORTANT NOTE:
You are responsible for any edits/actions performed using XFDcloser! Please
make sure you understand relevant Wikipedia policies and procedures, and use
this tool accordingly.
(Some pertinent pages are: [[WP:CON]], [[WP:CLOSE]], [[WP:NAC]])
--------------------------------------------------------------------------------
* Licencing note:
Originally published on the English Wikipedia at
< https://en.wikipedia.org/wiki/User:Evad37/XFDcloser.js > ( and/or
< https://en.wikipedia.org/wiki/User:Evad37/XFDcloser/sandbox.js > and/or
< https://en.wikipedia.org/wiki/User:Evad37/XFDcloser/v3.js > )
and available under Creative Commons Attribution-ShareAlike 3.0 Unported
License (CC BY-SA 3.0) < https://creativecommons.org/licenses/by-sa/3.0/ > and
GNU Free Documentation License (GFDL) < http://www.gnu.org/copyleft/fdl.html >
* Attribution note:
This script incorporates code derived from, and is a direct
replacement for, these other scripts also written by Evad37:
- https://en.wikipedia.org/wiki/User:Evad37/CFDcloser.js
- https://en.wikipedia.org/wiki/User:Evad37/FFDcloser.js
- https://en.wikipedia.org/wiki/User:Evad37/TFDcloser.js
Futhermore, it incorporates code copied/derived from:
- https://en.wikipedia.org/wiki/User:Mr.Z-man/closeAFD2.js
- https://en.wikipedia.org/wiki/MediaWiki:Gadget-twinkleunlink.js
- https://en.wikipedia.org/wiki/MediaWiki:Gadget-morebits.js
Further notes:
- See these pages' "History" pages for their authors.
- All of the these pages are also published on Wikipedia, and thus also
available under the terms of the CC BY-SA 3.0 license and GFDL
--------------------------------------------------------------------------------
Have fun, and happy editing! - Evad37
*******************************************************************************/
/* <nowiki> */
/* ========== Dependencies ====================================================================== */
// Resource loader modules
mw.loader.using( ['mediawiki.util', 'mediawiki.api', 'mediawiki.Title', 'mediawiki.RegExp',
'oojs-ui-core', 'oojs-ui-widgets', 'oojs-ui-windows', 'jquery.ui.dialog'], function () {
// Load Morebits gadget if not already available
if ( window.Morebits == null ) {
importScript('MediaWiki:Gadget-morebits.js');
importStylesheet( 'MediaWiki:Gadget-morebits.css' );
}
// Load extra.js if not already available
if ( window.extraJs == null ) {
importScript('User:Evad37/extra.js');
}
// Wait for page load
$( function($) {
/* ========== Config ============================================================================ */
// A global object that stores all the page and user configuration and settings
var config = {};
// Script info
config.script = {
// Advert to append to edit summaries
advert: ' ([[WP:XFDC|XFDcloser]])',
version: '3.5.4'
};
// MediaWiki configuration values
config.mw = mw.config.get( [
'wgPageName',
'wgUserGroups',
'wgUserName',
'wgFormattedNamespaces',
'wgMonthNames'
] );
/* --------- Quick checks that script should be running ----------------------------------------- */
if ( /(?:\?|&)(?:action|diff|oldid)=/.test(window.location.href) ) {
console.log('[XFDcloser] Page is in edit, history, diff, or oldid mode');
return;
}
if (
-1 === $.inArray('extendedconfirmed', config.mw.wgUserGroups) &&
-1 === $.inArray('sysop', config.mw.wgUserGroups)
) {
console.log('[XFDcloser] User is not extendedconfirmed or sysop');
return;
}
var xfdpage_regex = /(Articles_for_deletion\/|Miscellany_for_deletion|User:Cyberbot_I\/AfD's_requiring_attention|Wikipedia:WikiProject_Deletion_sorting\/(?!(Flat|Compact)$)|(Categories|Files|Templates|Redirects)_for_discussion(?!\/(Working|Holding_cell)))(?!\/?Administrator_instructions$)/;
if ( !xfdpage_regex.test(config.mw.wgPageName) ) {
console.log('[XFDcloser] Current page is not an XfD page');
return;
}
/* --------- CSS rules -------------------------------------------------------------------------- */
mw.util.addCSS(
[ // Inline links
'.xfdc-status { font-size:85%; margin-left:13px; font-weight:normal; }',
'.xfdc-action { margin-left:13px; }',
'.xfdc-action a { cursor:pointer; }',
'.xfdc-qc-cancel { cursor: pointer; border: 1px solid #777; border-radius: 10px;'+
'font-weight: bold; font-size: 90%; color: #777; padding: 0px; margin: 0px 1px; }',
// Dialog
'#xfdc-dialog { background-color:#f0f0f0; padding:0.5em 1em; font-size:110%; }',
'.xfdc-dialog-option { white-space:nowrap; min-width:8em; margin-right:0.3em; '+
'display:block; float:left; }',
'.xfdc-dialog-bracketedOption { white-space:nowrap; margin-left:2em; }',
'.xfdc-dialog-bracketedOption::before, .xfdc-bracketed::before { content: "(\\200A "; }',
'.xfdc-dialog-bracketedOption::after, .xfdc-bracketed::after { content: "\\200A )"; }',
'.xfdc-dialog-container { float:left; max-width:34%; margin-left:0.5em; }',
'.xfdc-dialog-disabled { color:#777; }',
'.xfdc-dialog-actionInfo { border:2px solid #fff; padding:0.2em; margin:0.1em; }',
'.xfdc-dialog-actionInfo > strong { display:block; }',
'.xfdc-dialog-actionInfo > span { margin-left:1.5em; }',
'#closeXFD-interface-buttons > button { margin:1em; }',
'#closeXFD-preview-output { background-color:#fff; border:1px solid #777; '+
'margin-top: 0px; padding:0px 10px; }',
'.closeXFD-errorNote { border:1px solid #ff0000 }',
// 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; }',
// Show/hide box
'#XFDcloser-showhide { bottom:0; display:block; position:fixed; right:0; z-index:100; '+
'padding:5px; box-shadow:0 2px 4px rgba(0,0,0,0.5); background-color:#FEF9E6; '+
'border:1px solid #aaa; border-radius:5px; font-size:85% }'
]
.join('\n')
);
/* --------- Additional configuration ----------------------------------------------------------- */
// Set description for namespace 0
config.mw.wgFormattedNamespaces['0'] = 'article';
// Remove empty string from start of wgMonthNames array
config.mw.wgMonthNames = config.mw.wgMonthNames.slice(1);
// Set aliases
config.mw.namespaces = config.mw.wgFormattedNamespaces;
config.mw.monthNames = config.mw.wgMonthNames;
// Set sysop status
if ( -1 !== $.inArray('sysop', config.mw.wgUserGroups) ) {
config.user = {
'isSysop': true,
'sig': '~~~~'
};
} else {
config.user = {
'isSysop': false,
'sig': '<small>[[Wikipedia:NACD|(non-admin closure)]]</small> ~~~~'
};
}
// Start time, for detecting edit conflicts
config.startTime = new Date();
// Variables for tracking across multiple discussions
config.track = {
// Track Afd logpage edits using deferred objects, to know when it is safe to read the wikitext
'afdLogEdit': [$.Deferred().resolve()],
// Track how many closes/relists have been started and completed
'started': 0,
'finished': 0,
'discussions': []
};
//Warn if unloading before closes/relists are completed
$(window).on('beforeunload', function(e) {
if ( config.track.started > config.track.finished ) {
e.returnValue = '';
return '';
}
});
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;
};
/* ========== XfdVenue class ====================================================================
Each instance represents an XfD venue, with properties/function specific to that venue
---------------------------------------------------------------------------------------------- */
// Constructor
var XfdVenue = function(type, settings) {
this.type = type;
for ( var key in settings ) {
this[key] = settings[key];
}
};
// ---------- XfdVenue prototype --------------------------------------------------------------- */
XfdVenue.prototype.hasNomTemplate = function(wikitext) {
var pattern = new RegExp(this.regex.nomTemplate);
return pattern.test(wikitext);
};
XfdVenue.prototype.removeNomTemplate = function(wikitext) {
var pattern = new RegExp(this.regex.nomTemplate);
return wikitext.replace(pattern, '');
};
// ---------- Venue-specific classes ----------------------------------------------------------- */
// MFD
var MfdVenue = function() {
XfdVenue.call(this, 'mfd', {
path: 'Wikipedia:Miscellany for deletion',
subpagePath: 'Wikipedia:Miscellany for deletion/',
ns_number: null,
html: {
head: 'h4',
list: 'dl',
listitem: 'dd'
},
wikitext: {
closeTop: "{{subst:Mfd top|'''__RESULT__'''}}__TO_TARGET____RATIONALE__ __SIG__",
closeBottom: "{{subst:Mfd bottom}}",
oldXfd: "{{Old MfD |date=__DATE__ |result='''__RESULT__''' |page=__SUBPAGE__}}"+
"\n",
mergeFrom: "{{mfd-mergefrom|__NOMINATED__|__DEBATE__|__DATE__}}\n",
mergeTo: "{{mfd-mergeto|__TARGET__|__DEBATE__|__DATE__|__TARGETTALK__}}\n",
alreadyClosed: "{{#ifeq:{{FULLPAGENAME}}|Wikipedia:Miscellany for deletion|"+
"{{collapse bottom}}|}}"
},
regex: {
nomTemplate: /(?:<noinclude>\s*)?(?:{{mfd[^}}]*}}|<span id="mfd".*?<\/span> \[\[Category:Miscellaneous pages for deletion\|?.*\]\]\s*)(?:\s*<\/noinclude>)?/gi
}
});
};
MfdVenue.prototype = Object.create(XfdVenue.prototype);
// CFD
var CfdVenue = function() {
XfdVenue.call(this, 'cfd', {
path: 'Wikipedia:Categories for discussion/Log/',
ns_number: [14],
html: {
head: 'h4',
list: 'ul',
listitem: 'li',
nthSpan: '2'
},
wikitext: {
closeTop: "{{subst:cfd top}} '''__RESULT__'''__TO_TARGET____RATIONALE__ __SIG__",
closeBottom: "{{subst:cfd bottom}}",
oldXfd: "{{Old CfD |__SECTION__ |date=__DATE__ |action=__ACTION__ "+
"|result=__RESULT__}}\n",
alreadyClosed: "<!-- Template:Cfd top -->"
}
});
};
CfdVenue.prototype = Object.create(XfdVenue.prototype);
// FFD
var FfdVenue = function() {
XfdVenue.call(this, 'ffd', {
path: 'Wikipedia:Files for discussion/',
ns_number: [6],
ns_unlink: ['0', '10', '100', '118'], // main, Template, Portal, Draft
html: {
head: 'h4',
list: 'dl',
listitem: 'dd',
nthSpan: '1'
},
wikitext: {
closeTop: "{{subst:ffd top|'''__RESULT__'''}}__TO_TARGET____RATIONALE__ __SIG__",
closeBottom: "{{subst:ffd bottom}}",
oldXfd: "{{oldffdfull |date=__DATE__ |result='''__RESULT__''' "+
"|page=__SECTION__}}\n",
pagelinks: "{{subst:ffd2|__PAGE__|multi=yes}}\n",
relistReplace: "{{ffd|log=__TODAY__",
alreadyClosed: "<!--Template:Ffd top-->"
},
regex: {
nomTemplate: /{{ffd[^}}]*}}/gi,
relistPattern: /{{\s*ffd\s*\|\s*log\s*=\s*[^|}}]*/gi
}
});
};
FfdVenue.prototype = Object.create(XfdVenue.prototype);
// TFD
var TfdVenue = function() {
XfdVenue.call(this, 'tfd', {
path: 'Wikipedia:Templates for discussion/Log/',
subpagePath: 'Wikipedia:Templates for discussion/',
ns_number: [10, 828],
html: {
head: 'h4',
list: 'ul',
listitem: 'li',
nthSpan: '1'
},
wikitext: {
closeTop: "{{subst:Tfd top|'''__RESULT__'''}}__TO_TARGET____RATIONALE__ __SIG__",
closeBottom: "{{subst:Tfd bottom}}",
oldXfd: "{{oldtfdfull|date= __DATE__ |result=__RESULT__ |disc=__SECTION__}}\n",
pagelinks: "* {{tfd links|__PAGE__}}\n",
relistReplace: "Wikipedia:Templates for discussion/Log/__TODAY__#",
alreadyClosed: "<!-- Tfd top -->"
},
regex: {
nomTemplate: /(<noinclude>[\n\s]*)?{{(?:Template for discussion|Tfm)\/dated[^{}]*(?:{{[^}}]*}}[^}}]*)*?}}([\n\s]*<\/noinclude>)?(\n)?/gi,
relistPattern: /Wikipedia:Templates(_|\s){1}for(_|\s){1}discussion\/Log\/\d{4}(_|\s){1}\w*(_|\s){1}\d{1,2}#(?=[^}]*}{2})/gi
},
holdingCellSectionNumber: {
"review": 2,
"convert": 11,
"substitute": 12,
"orphan": 13,
"ready": 14, // (ready for deletion)
"merge-arts": 4,
"merge-geopolgov": 5, // (geography, politics and governance)
"merge-religion": 6,
"merge-sports": 7,
"merge-trasnport": 8,
"merge-other": 9,
"merge-meta": 10
}
});
};
TfdVenue.prototype = Object.create(XfdVenue.prototype);
TfdVenue.prototype.removeNomTemplate = function(wikitext) {
var pattern = new RegExp(config.xfd.regex.nomTemplate);
var matches = pattern.exec(wikitext);
var logical_xor = function(first, second) {
return (first ? true : false) !== (second ? true : false);
};
var unbalancedNoincludeTags = logical_xor(matches[1], matches[2]);
var replacement = ( unbalancedNoincludeTags ) ? "$1$2" : "";
return wikitext.replace(pattern, replacement);
};
// RFD
var RfdVenue = function() {
XfdVenue.call(this, 'rfd', {
type: 'rfd',
path: 'Wikipedia:Redirects for discussion/Log/',
ns_number: null,
html: {
head: 'h4',
list: 'ul',
listitem: 'li'
},
wikitext: {
closeTop: "{{subst:Rfd top|'''__RESULT__'''}}__TO_TARGET____RATIONALE__ __SIG__",
closeBottom: "{{subst:Rfd bottom}}",
oldXfd: "{{Old RfD |date={{subst:date|__FIRSTDATE__}} |result='''__RESULT__'''"+
" |page=__DATE__#__SECTION__}}\n",
alreadyClosed: "<!-- Template:Rfd top-->"
},
regex: {
nomTemplate: /(^{{.*#invoke:RfD(?:.|\n)*?-->\|content=\n?|\n?<!-- Don't add anything after this line.*? -->\n}}|\[\[Category:Temporary maintenance holdings\]\]\n?)/g,
fullNomTemplate: /(^{{.*#invoke:RfD(?:.|\n)*?<!-- Don't add anything after this line.*? -->\n}}|\[\[Category:Temporary maintenance holdings\]\]\n?)/g
},
});
};
RfdVenue.prototype = Object.create(XfdVenue.prototype);
// AFD
var AfdVenue = function(transcludedOnly) {
XfdVenue.call(this, 'afd', {
type: 'afd',
path: 'Wikipedia:Articles for deletion/Log/',
subpagePath: 'Wikipedia:Articles for deletion/',
ns_number: [0], // main
ns_logpages: 4, // Wikipedia
ns_unlink: ['0', '10', '100', '118'], // main, Template, Portal, Draft
html: {
head: 'h3',
list: 'dl',
listitem: 'dd',
nthSpan: '2'
},
wikitext: {
closeTop: "{{subst:Afd top|'''__RESULT__'''}}__TO_TARGET____RATIONALE__ __SIG__",
closeBottom: "{{subst:Afd bottom}}",
mergeFrom: "{{Afd-merge from|__NOMINATED__|__DEBATE__|__DATE__}}\n",
mergeTo: "{{Afd-merge to|__TARGET__|__DEBATE__|__DATE__}}\n",
alreadyClosed: "<!--Template:Afd bottom-->"
},
regex: {
nomTemplate: /(?:{{[Aa]rticle for deletion\/dated|<!-- Please do not remove or change this AfD message)(?:.|\n)*?}}(?:(?:.|\n)+this\ point -->)?\s*/g
},
transcludedOnly: transcludedOnly
});
};
AfdVenue.prototype = Object.create(XfdVenue.prototype);
// Create xfd venue object
var isAfd = /(Articles_for_deletion|User:Cyberbot_I|Wikipedia:WikiProject_Deletion_sorting)/.test(config.mw.wgPageName);
var afdTranscludedOnly = /(User:Cyberbot_I|Wikipedia:WikiProject_Deletion_sorting)/.test(config.mw.wgPageName);
if ( config.mw.wgPageName.includes('Wikipedia:Miscellany_for_deletion') ) {
config.xfd = new MfdVenue();
} else if ( config.mw.wgPageName.includes('Categories_for_discussion/') ) {
config.xfd = new CfdVenue();
} else if ( config.mw.wgPageName.includes('Files_for_discussion') ) {
config.xfd = new FfdVenue();
} else if ( config.mw.wgPageName.includes('Templates_for_discussion') ) {
config.xfd = new TfdVenue();
} else if ( config.mw.wgPageName.includes('Redirects_for_discussion') ) {
config.xfd = new RfdVenue();
} else if ( isAfd ) {
config.xfd = new AfdVenue(afdTranscludedOnly);
} else {
return;
}
/* ========== API =============================================================================== */
var API = new mw.Api( {
ajax: {
headers: {
'Api-User-Agent': 'XFDcloser/' + config.script.version +
' ( https://en.wikipedia.org/wiki/WP:XFDC )'
}
}
} );
/* ========== Page class ========================================================================
An extended/modified version of the mw.Title class. Each instance
represents a page nominated in an XfD discussion.
---------------------------------------------------------------------------------------------- */
// Constructor
var Page = function(title) {
try {
mw.Title.call(this, decodeURIComponent(title));
} catch(e) {
throw new Error('Unable to parse title "'+title+'"');
}
this.exists = null;
this.talk = null;
this.talkExists = null;
};
//Constructor with a null return instead of an exception for invalid titles.
Page.newFromText = function(t) {
if ( mw.Title.newFromText(t) ) {
return new Page(t);
} else {
return null;
}
};
// ---------- Page prototype ------------------------------------------------------------------- */
// Inherited from mw.Title
Page.prototype = Object.create(mw.Title.prototype);
Page.prototype.constructor = Page;
// Set aliases
Page.prototype.getTitle = Page.prototype.getPrefixedText;
// Additional functions
Page.prototype.getTalk = function() {
if ( this.talk === null ) {
// talk page not yet set, so set it now
if ( this.getNamespaceId()%2 === 1 ) {
// Page is itself a talk page - set to empty string
this.talk = '';
} else {
this.talk = mw.Title.newFromText(
this.getMain(),
this.getNamespaceId()+1
).getPrefixedText();
}
}
return this.talk;
};
Page.prototype.hasCorrectNamespace = function() {
if (
config.xfd.ns_number === null ||
config.xfd.ns_number.indexOf(this.getNamespaceId()) !== -1
) {
return true;
} else {
return false;
}
};
/* ========== Discusssion class =================================================================
Each instance represents an XfD discussion.
---------------------------------------------------------------------------------------------- */
// Constructor
var Discussion = function(uniqueID, nomPage, sectionHeader, sectionNumber, pageObjects, firstDate) {
this.id = uniqueID;
this.nomPage = nomPage;
this.sectionHeader = sectionHeader;
this.sectionNumber = sectionNumber;
this.pages = pageObjects; // or false if in in 'basic' mode
this.nomDate = null;
this.firstDate = firstDate;
this.deferred = {};
//this.notices = $('<div>')
//.attr({'id':uniqueID+'-notices', 'class':'xfdc-notices'})
//.after($('#'+this.id).closest(config.xfd.html.head));
};
// Construct from headline span element
Discussion.newFromHeadlineSpan = function (headingIndex, context) {
var $headlineSpan = $(context);
var $heading = $headlineSpan.parent();
// Fix for "Auto-number headings" preference
$('.mw-headline-number', context).prependTo($heading);
// Get section header
var sectionHeader = $headlineSpan.text().trim();
// Check if already closed. Closed AfDs and MfDs have the box above the heading.
if ( /(afd|mfd)/.test(config.xfd.type) && $heading.parent().attr('class') && $heading.parent().attr('class').includes('xfd-closed') ) {
// Skip
return;
} else if ( !/(afd|mfd)/.test(config.xfd.type) && $heading.next().attr('class') ) {
// Only for closed discussion will the next element after the heading have any class set
// Skip, add class to enable hiding of closed discussions
$heading.addClass('xfd-closed');
return;
}
var sectionlink = $heading.find('.mw-editsection a')
.not('.mw-editsection-visualeditor, .autoCloserButton').attr('href');
var editsection = sectionlink.split('section=')[1].split('&')[0];
var nompage = '';
if ( /T/.test(editsection) ) {
// Section is transcluded from another page
nompage = Page.newFromText(
decodeURIComponent(sectionlink.split("title=")[1].split("&")[0])
).getTitle();
if ( -1 !== $.inArray(nompage, [
'Wikipedia:Redirects for discussion/Header',
'Wikipedia:Redirect/Deletion reasons',
'Wikipedia:Templates for discussion/Holding cell'
])
) {
// ignore headings transcuded from these pages
return;
}
// remove "T-" from section number
editsection = editsection.substr(2);
} else {
// Section is on current page, not transcluded
if ( config.xfd.transcludedOnly ) {
return;
}
nompage = Page.newFromText( config.mw.wgPageName ).getTitle();
}
var pages=[];
var firstdate = ( config.xfd.type === 'rfd' ) ? '' : null;
if ( config.xfd.type === 'cfd' ) {
//CFDs have basic closure only
pages = false;
} else if ( config.xfd.type === 'rfd' || config.xfd.type === 'mfd' ) {
// For MFD, closed discussion are within a collapsed table
$('table.collapsible').has('div.xfd-closed').addClass('xfd-closed');
// MFD & RFD have nominated page links prior to span with classes plainlinks, lx
pages = $heading
.nextUntil(config.xfd.html.head + ', div.xfd-closed, table.xfd-closed')
.find(config.xfd.html.listitem)
.has('span.plainlinks.lx')
.children('span')
.filter(':first-child')
.children('a, span.plainlinks:not(.lx)')
.filter(':first-child')
.map(function() { return Page.newFromText($(this).text()); })
.get();
if ( config.xfd.type === 'rfd' ) {
var discussionNodes = $heading
.nextUntil(config.xfd.html.head + ', div.xfd-closed, table.xfd-closed')
.clone();
// Fix for "Comments in Local Time" gadget
discussionNodes.find('span.localcomments').each(function(){
var utcTime = $(this).attr('title');
$(this).text(utcTime);
});
var discussionText = discussionNodes.text();
// Ignore relisted discussions, and non-boxed closed discussions
if (
discussionText.includes('Relisted, see Wikipedia:Redirects for discussion') ||
discussionText.includes('Closed discussion, see full discussion')
) {
return;
}
// Find first timestamp date
var firstdatePatt = /(?:\d\d:\d\d, )(\d{1,2} \w+ \d{4})(?: \(UTC\))/;
var firstdateMatch = firstdatePatt.exec(discussionText);
firstdate = firstdateMatch && firstdateMatch[1];
}
} else {
// AFD, FFD, TFD: nominated page links inside span with classes plainlinks, nourlexpansion
pages = $heading
.nextUntil(config.xfd.html.head + ', div.xfd-closed')
.find(config.xfd.html.listitem + ' > span.plainlinks.nourlexpansion')
.filter(':nth-of-type(' + config.xfd.html.nthSpan + ')')
.children('a')
.filter(':first-child')
.map(function() { return Page.newFromText($(this).text()); })
.get();
}
// Sanity check - check if any pages are null
if ( !pages || pages.length === 0 || pages.some(function(p) { return !p; }) ) {
//Still offer a "basic" close using the section header
pages = false;
}
// Create status span and notices div with unique id based on headingIndex
var uniqueID = 'XFDC' + headingIndex;
$headlineSpan.after(
$('<span>')
.attr({'id':uniqueID, 'class':'xfdc-status'})
.text('[XFDcloser loading...]')
);
$heading.after(
$('<div>')
.attr({'id':uniqueID+'-notices', 'class':'xfdc-notices'})
);
// Create discussion object
return new Discussion(uniqueID, nompage, sectionHeader, editsection, pages, firstdate);
};
// ---------- Discusssion prototype ------------------------------------------------------------- */
// Get status element (jQuery object)
Discussion.prototype.get$status = function() {
return $('#'+this.id);
};
// Set status
Discussion.prototype.setStatus = function($status) {
this.get$status().empty().append($status);
};
// Open dialog
Discussion.prototype.openDialog = function(relisting) {
this.dialog = new Dialog(this, !!relisting);
this.dialog.setup();
};
// Mark as finished
Discussion.prototype.setFinished = function(aborted) {
var self = this;
var msg;
if ( aborted != null ) {
msg = [
$('<strong>').text( ( self.dialog && self.dialog.relisting ) ? 'Aborted relist' : 'Aborted close' ),
( aborted === '' ) ? '' : ': ' + aborted
];
} else if ( self.dialog && self.dialog.relisting ) {
msg = [
'Discussion ',
$('<strong>').text('relisted'),
' (reload page to see the actual relist)'
];
} else {
msg = [
'Closed as ',
$('<strong>').text(self.taskManager.inputData.getResult()),
' (reload page to see the actual close)'
];
}
self.setStatus(msg);
self.get$status().prev().css('text-decoration', 'line-through');
};
// Get notices element (jQuery object)
Discussion.prototype.get$notices = function() {
return $('#'+this.id+'-notices');
};
// Set notices element
Discussion.prototype.setNotices = function($content) {
this.get$notices().empty().append($content);
};
// Get an array of page titles
Discussion.prototype.getPageTitles = function(pagearray, options) {
var titles = (pagearray || this.pages).map(function(p) {
return p.getTitle();
});
if ( options && options.moduledocs ) {
return titles.map(function(t) {
var isModule = ( t.indexOf('Module:') === 0 );
return ( isModule ) ? t + '/doc' : t;
});
}
return titles;
};
// Get an array of page' talkpage titles (excluding pages which are themselves talkpages)
Discussion.prototype.getTalkTitles = function(pagearray) {
return (pagearray || this.pages).map(function(p) {
return p.getTalk();
}).filter(function(t) { return t !== ''; });
};
// Get link text for a wikiink to the discussion - including anchor, except for AfDs
Discussion.prototype.getNomPageLink = function() {
if (config.xfd.type === 'afd') {
return this.nomPage;
} else {
return this.nomPage + '#' + mw.util.wikiUrlencode(this.sectionHeader);
}
};
// Get nomination subpage
Discussion.prototype.getNomSubpage = function() {
return this.nomPage.replace(config.xfd.subpagePath, '');
};
// Get page object by matching the title
Discussion.prototype.getPageByTitle = function(title, options) {
var convertModuleDoc = ( options && options.moduledocs && title.indexOf('Module:') === 0 );
var titleToCheck = ( convertModuleDoc ) ? title.replace(/\/doc$/,'') : title;
var search = mw.Title.newFromText(decodeURIComponent(titleToCheck)).getPrefixedText();
for ( var i=0; i<this.pages.length; i++ ) {
if ( search === this.pages[i].getTitle() ) {
return this.pages[i];
}
}
return false;
};
// Get page object by matching the talkpage's title
Discussion.prototype.getPageByTalkTitle = function(t) {
var search = mw.Title.newFromText(decodeURIComponent(t)).getPrefixedText();
for ( var i=0; i<this.pages.length; i++ ) {
if ( search === this.pages[i].getTalk() ) {
return this.pages[i];
}
}
return false;
};
// Show links for closing/relisting
Discussion.prototype.showLinks = function(additonal) {
// Preserve reference to self object
var self = this;
// Close link
var $close = $('<span>')
.addClass('xfdc-action')
.append(
'[',
$('<a>')
.attr('title', 'Close discussion...')
.text('Close'),
']'
)
.click(function() {
self.setStatus('Closing...');
self.openDialog();
});
// Relist link
var $relist = $('<span>')
.addClass('xfdc-action')
.append(
'[',
$('<a>')
.attr({title:'Relist discussion...', class:'XFDcloser-link-relist'})
.text('Relist'),
']'
)
.click(function() {
self.setStatus('Relisting...');
self.openDialog(true);
});
// quickKeep
var $qk = $('<a>')
.attr('title', 'quickKeep: close as "keep", remove nomination templates, '+
'add old xfd templates to talk pages')
.text('qK')
.click(function(){
var inputData = new InputData(self);
inputData.result = 'keep';
inputData.after = 'doKeepActions';
self.setStatus('Closing...');
self.taskManager = new TaskManager(self, inputData);
self.taskManager.start();
});
// quickDelete
var $qd = ( !config.user.isSysop && config.xfd.type !== 'tfd' ) ? '' : $('<a>')
.attr({
'title': 'quickDelete: close as "delete", delete nominated pages & their talk pages'+
(( config.xfd.type === 'rfd' ) ? '' :' & redirects')+
(( config.xfd.type === 'afd' || config.xfd.type === 'ffd' ) ? ', optionally '+
'unlink backlinks' : ''),
'class': 'xfdc-qd'
})
.text('qD');
if ( !config.user.isSysop && config.xfd.type == 'tfd' ) {
$qd.attr('title', 'quickDelete: close as "delete", tag nominated templates with '+
'{{being deleted}}, add nominated templates to the holding cell as "orphan"')
.click(function(){
var inputData = new InputData(self);
inputData.result = 'delete';
inputData.after = 'holdingCell';
inputData.holdcell = 'orphan';
self.setStatus('Closing...');
self.taskManager = new TaskManager(self, inputData);
self.taskManager.start();
});
} else if ( config.user.isSysop ) {
$qd.click(function(){
var inputData = new InputData(self);
inputData.result = 'delete';
inputData.after = 'doDeleteActions';
inputData.deleteredir = ( config.xfd.type === 'rfd' ) ? null : true;
inputData.unlinkbackl = ( config.xfd.type === 'afd' ||
config.xfd.type === 'ffd' ) ? true : null;
self.setStatus('Closing...');
self.taskManager = new TaskManager(self, inputData);
self.taskManager.start();
});
}
// quickClose links
var $quick = $('<span>')
.addClass('xfdc-action')
.css('font-size', '92%')
.append(
'[',
$('<a>')
.attr('title', 'quickClose discussion...')
.text('quickClose')
.click(function(){
$(this).hide().next().show();
}),
$('<span>')
.hide()
.append(
' ',
$qk,
" ",
$('<strong>').html('·'),
" ",
$qd,
' ',
$('<span>')
.attr({title: 'Cancel', class: 'xfdc-qc-cancel'})
.html(" x ")
.click(function(){
$(this).parent().hide().prev().show();
})
),
']'
);
//Add links in place of status
self.setStatus([
$close,
( self.pages === false ) ? '' : $quick,
( config.xfd.type==='cfd') ? '' : $relist,
additonal || ''
]);
};
// Retrieve extra information - pages' existance, nomination date(s)
Discussion.prototype.retrieveExtraInfo = function() {
// Preserve reference to discussion object
var self = this;
// If already in basic mode, just show links
if ( self.isBasicMode() ) {
self.showLinks();
return;
}
// Use Deferred objects to track when both queries are done, or either has failed
self.deferred.extraInfoExists = $.Deferred();
self.deferred.extraInfoDates = $.Deferred();
$.when(self.deferred.extraInfoExists, self.deferred.extraInfoDates)
.done( function() {
//Show links
self.showLinks();
} )
.fail( function(code, jqxhr) {
//Set basic mode
self.pages = false;
//Show basic-mode links
failDetails = $('<span>').addClass('xfdc-notice-error').append(
'Error retrieving page information (reload the page to try again) ',
$('<span>').addClass('xfdc-notice-error').append(
makeErrorMsg(code, jqxhr)
)
);
self.showLinks(failDetails);
} );
// Check if pages and talk pages exist
apiCallback_getPagesInfo = function(result) {
if ( !result.query || !result.query.pageids ) {
self.deferred.extraInfoExists.reject();
return;
}
var page_ids = result.query.pageids;
for (var i=0; i<page_ids.length; i++) {
title = result.query.pages[ page_ids[i] ].title;
var pageObject = self.getPageByTitle(title);
if ( !pageObject ) {
self.deferred.extraInfoExists.reject();
return;
}
pageObject.exists = page_ids[i] > 0;
pageObject.talkExists = result.query.pages[ page_ids[i] ].talkid > 0;
}
self.deferred.extraInfoExists.resolve();
};
// get page info from api
if ( self.getPageTitles().length > 50 ) {
self.deferred.extraInfoExists.reject('Too many pages');
} else {
API.get( {
action: 'query',
titles: self.getPageTitles().join('|'),
prop: 'info',
inprop: 'talkid',
indexpageids: 1,
rawcontinue: ''
} )
.done( apiCallback_getPagesInfo )
.fail( function(code, jqxhr) {
self.deferred.extraInfoExists.reject(code, jqxhr);
} );
}
// Get nomination date:
if ( config.xfd.type !== "afd" && config.xfd.type !== "mfd" ) {
self.nomDate = self.nomPage.split(config.xfd.path)[1];
if ( config.xfd.type === "rfd" && !self.firstDate ) {
// For an RfD with no first comment date detected, use the nom page date in dmy format
self.firstDate = self.nomDate.replace(/(\d+) (\w*) (\d+)/g, "$3 $2 $1");
}
self.deferred.extraInfoDates.resolve();
} else {
//Api query to get nomination page content
apiCallback_getNomInfo = function(result) {
//get nomination date from when nom page was created
var p_id = result.query.pageids[0];
var timestamp = result.query.pages[p_id].revisions[0].timestamp;
var d = new Date(timestamp);
self.nomDate = d.getUTCDate().toString() + ' ' + config.mw.monthNames[d.getUTCMonth()] +
' ' + d.getUTCFullYear().toString();
self.deferred.extraInfoDates.resolve();
};
API.get( {
action: 'query',
titles: self.nomPage,
prop: 'revisions',
rvprop: 'timestamp',
rvdir: 'newer',
rvlimit: '1',
indexpageids: 1,
rawcontinue: ''
} )
.done( apiCallback_getNomInfo )
.fail( function(code, jqxhr) {
self.deferred.extraInfoDates.reject(code, jqxhr);
} );
}
};
// Check if discussion is in 'basic' mode - i.e. no pages
Discussion.prototype.isBasicMode = function() {
return !this.pages;
};
/* ========== Dialog class ======================================================================
The user interface for closing/relisting XfD discussions
---------------------------------------------------------------------------------------------- */
// Constructor
var Dialog = function(discussion, relist) {
this.discussion = discussion;
this.relisting = !!relist;
// Make an new, empty, not-displayed dialog/interface window
this.interfaceWindow = new Morebits.simpleWindow(
Math.min(900, Math.floor(window.innerWidth*0.8)),
Math.floor(window.innerHeight*0.9)
);
this.interfaceWindow.setTitle( 'XFDcloser' );
this.interfaceWindow.setScriptName('(v' + config.script.version + ')');
this.interfaceWindow.addFooterLink('script documentation', 'WP:XFDC');
this.interfaceWindow.addFooterLink('feedback', 'WT:XFDC');
this.interfaceWindow.setContent(
$('<div>')
.attr('id', 'xfdc-dialog')
.append(
$('<div>').attr('id', 'xfdc-dialog-header'),
$('<div>').attr('id', 'xfdc-dialog-body'),
$('<div>').attr('id', 'xfdc-dialog-footer')
)
.get(0)
);
$('a.ui-dialog-titlebar-close.ui-corner-all').remove();
$('#xfdc-dialog').parent().css('background-color', '#f0f0f0');
};
// ---------- Dialog prototype ------------------------------------------------------------------ */
// --- Basic manipulation: ---
// Append content to header
Dialog.prototype.addToHeader = function($content) {
$('#xfdc-dialog-header').append($content);
};
// Append content to body
Dialog.prototype.addToBody = function($content) {
$('#xfdc-dialog-body').append($content);
};
// Append content to footer
Dialog.prototype.addToFooter = function($content) {
$('#xfdc-dialog-footer').append($content);
};
// Clear dialog
Dialog.prototype.emptyContent = function() {
$('#xfdc-dialog-header, #xfdc-dialog-body, #xfdc-dialog-footer').empty();
};
// Display dialog
Dialog.prototype.display = function() {
this.interfaceWindow.display();
};
// Reset height
Dialog.prototype.resetHeight = function() {
this.interfaceWindow.setHeight(Math.floor(window.innerHeight*0.9));
};
// Close dialog
Dialog.prototype.close = function() {
this.interfaceWindow.close();
$('#ejs-rcats-overlay').remove();
};
// --- Make interface elements: ---
Dialog.prototype.makeHeader = function(multimode) {
var self = this;
// Title, pagecount
var $header = $('<h4>')
.attr('id', 'closeXFD-interface-header')
.append(
$('<span>')
.attr('id', 'closeXFD-interface-header-action')
.append(
( self.relisting ) ? 'Relisting discussion ' : 'Closing discussion ',
$('<em>').text(self.discussion.sectionHeader),
' ',
$('<span>')
.attr({'id':'closeXFD-pagecount', 'class':'xfdc-bracketed'})
.css('font-size', '80%')
.append(
$('<span>')
.attr('id', 'closeXFD-pagecount-pages')
.css({'background':'#ff8', 'padding':'1px'})
.text(
( self.discussion.isBasicMode() ) ? 'basic mode '+
'only' : self.discussion.pages.length +
( ( self.discussion.pages.length === 1 ) ? ' page' : ' pages' )
),
( multimode ) ? '' : ' ',
( multimode ) ? '' : $('<a>').attr('id', 'closeXFD-pagecount-show').text('[show]'),
( multimode ) ? '' : $('<a>').attr('id', 'closeXFD-pagecount-hide').text('[hide]').hide()
)
)
);
$header.find('#closeXFD-pagecount-show, #closeXFD-pagecount-hide').click(function() {
$('#closeXFD-nominatedpages, #closeXFD-pagecount-show, #closeXFD-pagecount-hide').toggle();
});
// Multi/single-mode button
if ( !self.relisting && self.discussion.pages && self.discussion.pages.length > 1 ) {
$header.prepend(
$('<button>')
.css('float', 'right')
.text(( multimode ) ? 'Single result...' : 'Multiple results...')
.click(function() {
if ( multimode ) {
self.setup();
} else {
self.setupForMultiClose();
}
})
);
}
return $header;
};
Dialog.prototype.makePagesList = function(multimode) {
var self = this;
// Template for per-page actions for multimode
var $action_dropdown = $('<select>')
.attr('class', 'closeXFD-pagelist-actionDropdown')
.css('margin-right', '0.5em')
.append(
$('<option>').attr('value', 'default').text(''),
$('<option>').attr('value', 'keep').text('Keep'),
( config.xfd.type === 'ffd' ) ? '' : $('<option>')
.attr('value', 'redirect').text('Redirect'),
( config.xfd.type === 'ffd' || config.xfd.type === 'rfd' ) ? '' : $('<option>')
.attr('value', 'merge').text('Merge'),
( config.xfd.type !== 'rfd' ) ? '' : $('<option>')
.attr('value', 'disambig').text('Disambiguate'),
$('<option>').attr('value', 'del').text('Delete'),
$('<option>').attr('value', 'na').text('(no action)')
);
var $target = $('<span>')
.addClass('closeXFD-pagelist-target')
.append(
' to:',
$('<input>').attr('type', 'text')
)
.css('display', 'none');
// List of pages (or info on basic mode)
var $pagesList = $('<ul>')
.attr('id', 'closeXFD-nominatedpages')
.css('font-size', '95%');
if ( self.discussion.pages ) {
for ( var i=0; i<self.discussion.pages.length; i++ ) {
var pageTitle = self.discussion.pages[i].getTitle();
$('<li>')
.append(
( !multimode ) ? '' : $action_dropdown
.clone()
.attr('id', 'closeXFD-pagelist-action-'+i)
.data('pageTitle', pageTitle),
$('<span>').addClass('closeXFD-pagelist-title').text(pageTitle),
' ',
( !multimode ) ? '' : $target
.clone()
.attr('id', 'closeXFD-pagelist-target-'+i)
.data('pageTitle', pageTitle)
)
.appendTo($pagesList);
if ( multimode ) {
self.inputData.pageActions[pageTitle] = { 'id':'closeXFD-pagelist-action-'+i };
}
}
} else {
$('<li>')
.append(
'Nominated pages were not detected. You can still ',
( self.relisting ) ? 'relist' : 'close',
' this ',
config.xfd.type.toUpperCase(),
' discussion, but updating the nomination templates will need to be done manually '+
'– see ',
extraJs.makeLink('WP:'+config.xfd.type.toUpperCase()+'AI'),
' for instructions.'
)
.appendTo($pagesList);
}
// On changing a multimode action, show/hide corresponding options as appropriate
$pagesList.find('select').change(function(){
if ( $(this).children().first().val() === 'default' ) {
$(this).children().first().remove();
}
var $li = $(this).parent();
var v = $(this).val();
var t = $li.find('.closeXFD-pagelist-title').text();
self.inputData.pageActions[t].action = v;
if ( v === 'redirect' || v === 'merge' ) {
$li.find('.closeXFD-pagelist-target').show();
} else {
$li.find('.closeXFD-pagelist-target').val('').change().hide();
}
self.updateMultimodeOptions();
});
$pagesList.find('input').change(function(){
var t = $(this).parent().parent().find('.closeXFD-pagelist-title').text();
self.inputData.pageActions[t].target = Page.newFromText(
$(this).val().trim()
);
});
if ( !multimode ) {
$pagesList.hide();
}
return $pagesList;
};
Dialog.prototype.makeNonAdminNote = function() {
var $NACD_note = $('<div>')
.css({'text-align':'center', 'clear':'both'})
.append(
$('<em>')
.append(
'See the ',
extraJs.makeLink('WP:NACD'),
' guideline for advice on appropriate and inappropriate closures'
),
$('<hr>')
);
return $NACD_note;
};
Dialog.prototype.makeCloseResult = function() {
var self = this;
$resultContainer = $('<div>')
.attr('id', 'closeXFD-resultContainer')
.css({'float':'left', 'width':'99%', 'padding-bottom':'1%'})
.append(
$('<div>').append(
$('<strong>').text('Result'),
// Speedy
$('<span>').addClass('xfdc-dialog-bracketedOption').append(
$('<input>')
.attr({'type':'checkbox', 'name':'closeXFD-speedy', 'id':'closeXFD-speedy'})
.change(function(){
self.inputData.speedy = ( $(this).prop('checked') );
}),
$('<label>').attr('for', 'closeXFD-speedy').text('Speedy')
)
),
$('<div>').attr('id', 'closeXFD-resultOptions').css('overflow','hidden').append(
// Keep
$('<span>').attr('class', 'xfdc-dialog-option').append(
$('<input>').attr({'type':'radio', 'name':'closeXFD-result',
'class':'closeXFD-keepResult', 'id':'closeXFD-result-keep', 'value':'keep'}),
$('<label>').attr('for', 'closeXFD-result-keep').text('Keep')
),
// Redirect
( config.xfd.type === 'ffd' ) ? '' : $('<span>')
.attr('class', 'xfdc-dialog-option').append(
$('<input>').attr({'type':'radio', 'name':'closeXFD-result',
'class':'closeXFD-keepResult', 'id':'closeXFD-result-redir',
'value':(( config.xfd.type === 'rfd' ) ? 'retarget' : 'redirect')}),
$('<label>')
.attr('for', 'closeXFD-result-redir')
.text(( config.xfd.type === 'rfd' ) ? 'Retarget' : 'Redirect'),
( !config.user.isSysop ) ? '' : $('<label>')
.attr('for', 'closeXFD-result-redir')
.text(( config.xfd.type === 'rfd' ) ? 'Delete and retarget' : 'Delete and redirect')
.hide()
),
// Soft redirect
( config.xfd.type !== 'rfd' ) ? '' : $('<span>')
.attr('class', 'xfdc-dialog-option').append(
$('<input>').attr({'type':'radio', 'name':'closeXFD-result',
'class':'closeXFD-keepResult', 'id':'closeXFD-result-softredir',
'value':'soft redirect'}),
$('<label>').attr('for', 'closeXFD-result-softredir').text('Soft redirect'),
( !config.user.isSysop ) ? '' : $('<label>')
.attr('for', 'closeXFD-result-softredir').text('Delete and soft redirect').hide()
),
// No consensus
$('<span>').attr('class', 'xfdc-dialog-option').append(
$('<input>').attr({'type':'radio', 'name':'closeXFD-result',
'class':'closeXFD-keepResult', 'id':'closeXFD-result-nocon',
'value':'no consensus'}),
$('<label>').attr('for', 'closeXFD-result-nocon').text('No consensus')
),
// Merge
( config.xfd.type === 'ffd' || config.xfd.type === 'rfd' ) ? '' : $('<span>')
.attr('class', 'xfdc-dialog-option').append(
$('<input>').attr({'type':'radio', 'name':'closeXFD-result',
'id':'closeXFD-result-merge', 'value':'merge'}),
$('<label>').attr('for', 'closeXFD-result-merge').text('Merge')
),
// Disambiguate
( config.xfd.type !== 'rfd' ) ? '' : $('<span>')
.attr('class', 'xfdc-dialog-option').append(
$('<input>').attr({'type':'radio', 'name':'closeXFD-result',
'id':'closeXFD-result-disambig', 'value':'disambiguate'}),
$('<label>').attr('for', 'closeXFD-result-disambig').text('Disambiguate')
),
// Delete
( !config.user.isSysop && config.xfd.type !== 'tfd') ? '' : $('<span>')
.attr('class', 'xfdc-dialog-option').append(
$('<input>').attr({'type':'radio', 'name':'closeXFD-result',
'class':'closeXFD-deleteResult', 'id':'closeXFD-result-delete', 'value':'delete'}),
$('<label>').attr('for', 'closeXFD-result-delete').text('Delete')
),
// Delete (disabled)
( config.user.isSysop || config.xfd.type === 'tfd') ? '' : $('<span>')
.attr('class', 'xfdc-dialog-option').append(
$('<input>').attr({'type':'radio', 'name':'closeXFD-result',
'class':'closeXFD-deleteResult', 'id':'closeXFD-result-delete-disabled',
'disabled':'disabled'}),
$('<label>').attr('for', 'closeXFD-result-delete-disabled').append(
'Delete ',
extraJs.makeTooltip('Non-admin closure is not appropriate when the result '+
'will require action by an administrator (per [[WP:BADNAC]])')
)
),
// Soft delete
( !config.user.isSysop || config.xfd.type === 'rfd' ) ? '' : $('<span>')
.attr({'class':'xfdc-dialog-option', 'id':'closeXFD-resultContainer-softdel'}).append(
$('<input>').attr({'type':'radio', 'name':'closeXFD-result',
'class':'closeXFD-deleteResult', 'id':'closeXFD-result-softdel',
'value':'soft delete'}),
$('<label>').attr('for', 'closeXFD-result-softdel').text('Soft delete')
),
// Custom
$('<span>').attr('class', 'xfdc-dialog-option').append(
$('<input>').attr({'type':'radio', 'name':'closeXFD-result',
'id':'closeXFD-result-custom', 'value':'custom'}),
$('<label>').attr('for', 'closeXFD-result-custom').text('Custom')
)
),
$('<div>')
.attr({'id':'closeXFD-resultContainer-custom'})
.css({'clear':'both', 'padding-bottom':'1%', 'display':'none'})
.append(
$('<label>').attr('for', 'closeXFD-result-custom-input').text('Custom result:'),
$('<input>')
.attr({'type':'text', 'name':'closeXFD-result-custom-input',
'id':'closeXFD-result-custom-input'})
.change(function(){
self.inputData.customResult = $('#closeXFD-result-custom-input').val().trim();
})
),
// Target page
$('<div>')
.attr({'id':'closeXFD-resultContainer-target'})
.css({'clear':'both', 'padding-bottom':'1%', 'display':'none'})
.append(
$('<label>').attr({'id':'closeXFD-result-target-label', 'for':'closeXFD-result-target'})
.text('Target:'),
$('<input>')
.attr({'type':'text', 'name':'closeXFD-result-target', 'id':'closeXFD-result-target'})
.change(function(){
self.inputData.target = Page.newFromText(
$('#closeXFD-result-target').val().trim()
);
})
)
);
$resultContainer.find('span.xfdc-dialog-option').after('<wbr>');
$resultContainer.find("input[type=radio][name='closeXFD-result']").change(function(){
var v = $(this).val();
self.inputData.result = v;
var currentAfter = $('input[name="closeXFD-after"]:checked').val() || false;
// Show/hide options
switch(v) {
default:
case 'keep':
case 'no consensus':
$('.closeXFD-keepOptions').show();
$('.closeXFD-redirectOptions, .closeXFD-mergeOptions, .closeXFD-disambigOptions, .closeXFD-deleteOptions, '+
'#closeXFD-customOptions, #closeXFD-resultContainer-target, '+
'#closeXFD-resultContainer-custom').hide();
// default options:
if (
!currentAfter ||
( currentAfter !== 'doKeepActions' && currentAfter !== 'noAction' )
) {
$('#closeXFD-after-doKeepActions').prop('checked', true).change();
}
break;
case 'retarget':
case 'redirect':
case 'soft redirect':
$('.closeXFD-redirectOptions, #closeXFD-resultContainer-target').show();
$('.closeXFD-keepOptions, .closeXFD-mergeOptions, .closeXFD-disambigOptions, .closeXFD-deleteOptions, '+
'#closeXFD-customOptions, #closeXFD-resultContainer-custom').hide();
$('#closeXFD-result-target-label').text(extraJs.toSentenceCase(v) + ' to: ');
// default options:
if (
!currentAfter ||
( currentAfter !== 'doRedirectActions' && currentAfter !== 'noAction' )
) {
$('#closeXFD-after-doRedirectActions')
.prop('checked', true).change();
}
break;
case 'merge':
$('.closeXFD-mergeOptions, #closeXFD-resultContainer-target').show();
$('.closeXFD-keepOptions, .closeXFD-redirectOptions, .closeXFD-disambigOptions, .closeXFD-deleteOptions, '+
'#closeXFD-customOptions, #closeXFD-resultContainer-custom').hide();
$('#closeXFD-result-target-label').text('Merge to:');
// default options:
if (
!currentAfter ||
( currentAfter !== 'doMergeActions' && currentAfter !== 'noAction' )
) {
$('#closeXFD-after-doMergeActions').prop('checked', true).change();
}
break;
case 'disambiguate':
$('.closeXFD-disambigOptions').show();
$('.closeXFD-keepOptions, .closeXFD-redirectOptions, .closeXFD-mergeOptions, .closeXFD-deleteOptions, '+
'#closeXFD-customOptions, #closeXFD-resultContainer-target, '+
'#closeXFD-resultContainer-custom').hide();
// default options:
if (
!currentAfter ||
( currentAfter !== 'doDisambigActions' && currentAfter !== 'noAction' )
) {
$('#closeXFD-after-doDisambigActions').prop('checked', true).change();
}
break;
case 'delete':
case 'soft delete':
$('.closeXFD-deleteOptions').show();
$('.closeXFD-keepOptions, .closeXFD-redirectOptions, .closeXFD-mergeOptions, .closeXFD-disambigOptions, '+
'#closeXFD-customOptions, #closeXFD-resultContainer-target, '+
'#closeXFD-resultContainer-custom').hide();
// default options:
if (
!currentAfter ||
( currentAfter !== 'doDeleteActions' &&
currentAfter !== 'holdingCell' &&
currentAfter !== 'noAction' )
) {
$('#closeXFD-after-' +
(( config.user.isSysop ) ? 'doDeleteActions' : 'holdingCell') +
', #closeXFD-del-deleteredir, #closeXFD-del-unlinkbackl')
.prop('checked', true).change();
}
// When soft delete is selected, prepend rationale with REFUND message
if ( v === 'soft delete' ) {
var old_rationale = $('#closeXFD-rationale').val();
if (!old_rationale.includes('[[WP:REFUND]]')) {
$('#closeXFD-rationale').val('[[WP:REFUND]] applies. ' + old_rationale)
.change();
}
}
break;
case 'custom':
$('#closeXFD-customOptions, #closeXFD-resultContainer-custom, '+
'.closeXFD-keepOptions, .closeXFD-deleteOptions').show();
$('.closeXFD-redirectOptions, .closeXFD-mergeOptions, .closeXFD-disambigOptions, '+
'#closeXFD-resultContainer-target').hide();
// default options:
if ( !currentAfter || currentAfter !== 'noAction' ) {
$('#closeXFD-after-noAction').prop('checked', true).change();
}
}
self.resetHeight();
});
return $resultContainer;
};
Dialog.prototype.makeRationaleBox = function(multimode) {
var self = this;
var $rationale = $('<div>')
.css('clear', 'both')
.append(
$('<strong>').attr('id', 'closeXFD-rationale-label')
.text( ( self.relisting ) ? 'Relist comment' : (( multimode ) ? 'Rationale' : 'Additional '+
'rationale')),
( !multimode ) ? ' (optional):' : $('<a>')
.addClass('xfdc-dialog-bracketedOption')
.text('copy from above')
.click(function(){
var results = '';
for ( var p in self.inputData.pageActions ) {
var a = self.inputData.pageActions[p].action;
if ( a ) {
a = a
.replace('del', 'delete')
.replace(/(default|na)/, ' ');
} else {
a = ' ';
}
var t = ( /(merge|redirect)/.test(a) ) ? ' to [[' +
self.inputData.pageActions[p].target + ']]\n' : '\n';
results += "*'''" + extraJs.toSentenceCase(a) + "''' [[" + p + "]]" + t;
}
$('#closeXFD-rationale').val(results + self.inputData.rationale).change();
}),
$('<br>'),
$('<textarea>')
.attr({'id':'closeXFD-rationale', 'rows':(( self.relisting ) ? 2 : 4)})
.css('width', '99%')
.change(function(){
self.inputData.rationale = $(this).val().trim();
})
.change(),
( self.relisting ) ? '' : $('<div>')
.css({'clear':'both', 'padding-bottom':'1%'})
.append(
$('<input>')
.attr({'type':'checkbox', 'name':'closeXFD-rationale-sentence',
'id':'closeXFD-rationale-sentence'})
.change(function(){
if ( $(this).prop('checked') ) {
self.inputData.resultPunct = '';
} else {
self.inputData.resultPunct = '.';
}
})
.change(),
$('<label>').attr('for', 'closeXFD-rationale-sentence')
.text('Rationale is not a new sentence')
)
);
if ( multimode ) {
$('<div>')
.css({'clear':'both', 'padding-bottom':'1%'})
.append(
$('<label>').attr('for', 'closeXFD-resultSummary-input').append(
'Result summary',
extraJs.makeTooltip('Appears in bold text before the rationale; also used in '+
'edit summaries, and in Old ' + config.xfd.type + ' templates'),
': '
),
$('<input>')
.attr({'type':'text', 'name':'closeXFD-resultSummary-input',
'id':'closeXFD-resultSummary-input'})
.change(function(){
self.inputData.result = $(this).val().trim();
})
)
.prependTo($rationale);
}
return $rationale;
};
Dialog.prototype.makeAfterActions = function(multimode) {
var self = this;
var $after = $('<div>')
.attr('id', 'closeXFD-afterContainer')
.addClass('xfdc-dialog-container')
.css('max-width', ( multimode ) ? '30%' : '40%')
.append(
$('<strong>').text('After closing:').css('display','block'),
// KeepActions
$('<div>')
.addClass('closeXFD-keepOptions')
.hide()
.append(
( multimode ) ? $('<strong>')
.text('"Keep" pages: ') : $('<input>')
.attr({
'type':'radio',
'name':'closeXFD-after',
'id':'closeXFD-after-doKeepActions',
'value':'doKeepActions'
}),
$(( multimode ) ? '<span>' : '<label>')
.attr('for', 'closeXFD-after-doKeepActions')
.append(
'Update pages and talk pages ',
extraJs.makeTooltip('Remove nomination templates, and add old xfd templates '+
'to talk pages')
)
),
// RedirectActions
$('<div>')
.addClass('closeXFD-redirectOptions')
.hide()
.append(
( multimode ) ? $('<strong>')
.text('"Redirect" pages: ') : $('<input>').attr({
'type':'radio',
'name':'closeXFD-after',
'id':'closeXFD-after-doRedirectActions',
'value': 'doRedirectActions'
}),
$(( multimode ) ? '<span>' : '<label>')
.attr('for', 'closeXFD-after-doRedirectActions')
.append(
'Redirect pages and update talk pages ',
extraJs.makeTooltip('Redirect nominated pages to the specified target, '+
'and add old xfd templates to talk pages')
)
),
// MergeActions
$('<div>')
.addClass('closeXFD-mergeOptions')
.hide()
.append(
( multimode ) ? $('<strong>')
.text('"Merge" pages: ') : $('<input>').attr({
'type':'radio',
'name':'closeXFD-after',
'id':'closeXFD-after-doMergeActions',
'value': 'doMergeActions'
}),
$(( multimode ) ? '<span>' : '<label>')
.attr('for', 'closeXFD-after-doMergeActions')
.append(
'Update pages and talk pages ',
extraJs.makeTooltip('Replace nomination templates with merge templates, '+
'and add old xfd templates to talk pages')
)
),
// DisambigActions
$('<div>')
.addClass('closeXFD-disambigOptions')
.hide()
.append(
( multimode ) ? $('<strong>')
.text('"Disambiguate" pages: ') : $('<input>')
.attr({
'type':'radio',
'name':'closeXFD-after',
'id':'closeXFD-after-doDisambigActions',
'value':'doDisambigActions'
}),
$(( multimode ) ? '<span>' : '<label>')
.attr('for', 'closeXFD-after-doDisambigActions')
.append(
'Update pages and talk pages ',
extraJs.makeTooltip('Remove nomination templates, and add old xfd templates '+
'to talk pages')
)
),
// Delete actions
( multimode || !config.user.isSysop ) ? '' : $('<div>')
.addClass('closeXFD-deleteOptions')
.hide()
.append(
$('<input>').attr({
'type':'radio',
'name':'closeXFD-after',
'id':'closeXFD-after-doDeleteActions',
'value': 'doDeleteActions'
}),
$('<label>').attr('for', 'closeXFD-after-doDeleteActions').append(
'Delete pages ',
extraJs.makeTooltip('Delete nominated pages and (unless otherwise specified) '+
'their talk pages')
)
),
// Holding cell
( multimode || config.xfd.type !== 'tfd' ) ? '' : $('<div>')
.addClass('closeXFD-deleteOptions')
.hide()
.append(
$('<input>').attr({
'type':'radio',
'name':'closeXFD-after',
'id':'closeXFD-after-holdingCell',
'value':'holdingCell'
}),
$('<label>').attr('for', 'closeXFD-after-holdingCell').append(
'List pages at holding cell ',
extraJs.makeTooltip('Replace nomination template with {{Being deleted}}, '+
'and list at the specified holding cell section')
)
),
// Multimode delete/holding cell
( !multimode ) ? '' : $('<div>')
.addClass('closeXFD-deleteOptions')
.hide()
.append(
$('<strong>').text('"Delete" pages: '),
$('<span>').append(
( !config.user.isSysop ) ? '' : 'Delete pages ',
( !config.user.isSysop ) ? '' : extraJs.makeTooltip('Delete nominated pages and '+
'(unless otherwise specified) their talk pages'),
( config.xfd.type !== 'tfd' ) ? '' : (( config.user.isSysop ) ? ' or l' : 'L') +
'ist pages at holding cell ',
( config.xfd.type !== 'tfd' ) ? '' : extraJs.makeTooltip('Replace nomination '+
'template with {{Being deleted}}, and list at the specified '+
'holding cell section')
)
),
// No actions
$('<div>')
.attr('id', 'closeXFD-noAction')
.toggle(!multimode)
.append(
( multimode ) ? $('<strong>')
.text('"(no action)" pages: ') : $('<input>').attr({
'type':'radio',
'name':'closeXFD-after',
'id':'closeXFD-after-noAction',
'value':'noAction'
}),
$(( multimode ) ? '<span>' : '<label>')
.attr('for', 'closeXFD-after-noAction').text('No automated actions')
)
)
.find('input').change(function() {
self.inputData.after = $(this).val();
// Hide options if no action selected, or 'doKeepActions' is selected
$('#closeXFD-optionsContainer').toggle(!/(noAction|doKeepActions)/.test(self.inputData.after));
// Show or hide holding cell sections and 'delete redirects', if holding cell selected
if ( config.xfd.type === 'tfd' ) {
$('#closeXFD-del-holdcell').toggle(self.inputData.after === 'holdingCell');
$('#closeXFD-del-deleteredir').parent()
.toggle(self.inputData.after === 'doDeleteActions');
// Disable 'don't delete talk' option for holding cell (except 'ready for deletion')
if ( self.inputData.after === 'holdingCell' && self.inputData.holdcell !== 'ready' ) {
$('#closeXFD-del-deletetalk').prop('disabled', true)
.next().addClass('xfdc-dialog-disabled');
} else {
$('#closeXFD-del-deletetalk').prop('disabled', false)
.next().removeClass('xfdc-dialog-disabled');
}
}
} )
.end();
if ( multimode ) {
$after.children('div').addClass('xfdc-dialog-actionInfo');
}
return $after;
};
Dialog.prototype.makeOptions = function(multimode) {
var self = this;
var $options = $('<div>')
.attr('id', 'closeXFD-optionsContainer')
.append(
// Redirect options:
( config.xfd.type === 'ffd' ) ? '' : $('<div>')
.addClass('closeXFD-redirectOptions')
.hide()
.append(
$('<strong>').text(( multimode ) ? 'Redirect options' : 'Options')
.css('display','block'),
// Delete before redirecting
( !config.user.isSysop ) ? '' : $('<div>').append(
$('<input>')
.attr({
'type':'checkbox',
'name':'closeXFD-redir-deleteFirst',
'id':'closeXFD-redir-deleteFirst'
})
.change(function(){
self.inputData.deleteFirst = $(this).prop('checked');
if ( !multimode ) {
// toggle result labels
$('#closeXFD-result-redir').nextAll().toggle();
$('#closeXFD-result-softredir').nextAll().toggle();
}
}),
$('<label>').attr('for', 'closeXFD-redir-deleteFirst')
.text('Delete before redirecting')
),
// Rcats
/*
$('<div>').append(
$('<input>').attr({
'type':'checkbox',
'name':'closeXFD-redir-addRcats',
'id': 'closeXFD-redir-rcats',
'disabled': true
}).prop('checked', ( config.xfd.type === 'afd' )),
$('<label>').attr('for', 'closeXFD-redir-rcats').append(
$('<a>')
.attr({'href':'https://en.wikipedia.org/wiki/Template:R_template_index',
'target':'_blank'}).text('Rcats'),
": ",
extraJs.makeTooltip('Full markup required, e.g. "{{R to section}}";. '+
'Multiple rcats can be specified, e.g. "{{R from book}}'+
'{{R to anchor}}". Leave blank if unsure which rcat to use.')
),
//self.rcatSelector.$element.detach()
)*/
),
// Merge options:
( config.xfd.type !== 'tfd' ) ? '' : $('<div>')
.attr('id', 'closeXFD-merge-holdcell')
.addClass('closeXFD-mergeOptions')
.hide()
.append(
$('<strong>').text('Holding cell merge subsection:').css('display','block'),
// Arts
$('<div>').append(
$('<input>').attr({'type':'radio', 'name':'closeXFD-merge-holdingcell',
'id':'closeXFD-holdingcell-merge-arts', 'value':'merge-arts'}),
$('<label>').attr('for', 'closeXFD-holdingcell-merge-arts').text('Merge (Arts)')
),
// Geography, politics and governance
$('<div>').append(
$('<input>').attr({'type':'radio', 'name':'closeXFD-merge-holdingcell',
'id':'closeXFD-holdingcell-merge-geopolgov', 'value':'merge-geopolgov'}),
$('<label>').attr('for', 'closeXFD-holdingcell-merge-geopolgov')
.text('Merge (Geography, politics and governance)')
),
// Religion
$('<div>').append(
$('<input>').attr({'type':'radio', 'name':'closeXFD-merge-holdingcell',
'id':'closeXFD-holdingcell-merge-religion', 'value':'merge-religion'}),
$('<label>').attr('for', 'closeXFD-holdingcell-merge-religion')
.text('Merge (Religion)')
),
// Sports
$('<div>').append(
$('<input>').attr({'type':'radio', 'name':'closeXFD-merge-holdingcell',
'id':'closeXFD-holdingcell-merge-sports', 'value':'merge-sports'}),
$('<label>').attr('for', 'closeXFD-holdingcell-merge-sports')
.text('Merge (Sports)')
),
// Other
$('<div>').append(
$('<input>').attr({'type':'radio', 'name':'closeXFD-merge-holdingcell',
'id':'closeXFD-holdingcell-merge-other', 'value':'merge-other'}),
$('<label>').attr('for', 'closeXFD-holdingcell-merge-other')
.text('Merge (Other)')
),
// Meta
$('<div>').append(
$('<input>').attr({'type':'radio', 'name':'closeXFD-merge-holdingcell',
'id':'closeXFD-holdingcell-merge-meta', 'value':'merge-meta'}),
$('<label>').attr('for', 'closeXFD-holdingcell-merge-meta')
.text('Merge (Meta)')
)
)
.find("input[type=radio][name='closeXFD-merge-holdingcell']").change(function(){
self.inputData.mergeHoldcell = $(this).val();
})
.end(),
// Deletion options
( !config.user.isSysop && config.xfd.type !== 'tfd' ) ? '' : $('<div>')
.addClass('closeXFD-deleteOptions')
.hide()
.append(
$('<strong>')
.text(( multimode ) ? 'Delete options' : 'Options').css('display','block'),
// Don't delete talk pages
( !config.user.isSysop && config.xfd.type !== 'tfd' ) ? '' : $('<div>').append(
$('<input>').attr({'type':'checkbox', 'name':'closeXFD-del-deletetalk',
'id':'closeXFD-del-deletetalk', 'value':'dontdeletetalk'}),
$('<label>').attr('for', 'closeXFD-del-deletetalk').append(
( config.user.isSysop ) ? 'Do not delete talk pages' : 'Do not tag talk '+
'pages for speedy deletion'
)
),
// Delete redirects
( !config.user.isSysop || config.xfd.type === 'rfd' ) ? '' : $('<div>').append(
$('<input>').attr({'type':'checkbox', 'name':'closeXFD-del-deleteredir',
'id':'closeXFD-del-deleteredir', 'value':'deleteredir',
'checked':'checked'}),
$('<label>').attr('for', 'closeXFD-del-deleteredir')
.text('Also delete redirects')
),
// Unlink backlinks
( config.xfd.type !== 'afd' && config.xfd.type !== 'ffd' ) ? '' : $('<div>')
.append(
$('<input>').attr({'type':'checkbox', 'name':'closeXFD-del-unlinkbackl',
'id':'closeXFD-del-unlinkbackl', 'value':'unlinkbackl',
'checked':'checked'}),
$('<label>').attr('for', 'closeXFD-del-unlinkbackl').text('Unlink backlinks')
),
// Delete and redirect
( multimode || !config.user.isSysop || config.xfd.type === 'ffd' ) ? '' : $('<div>')
.append(
// Hidden checkbox, so that text lines up
$('<input>').attr('type', 'checkbox').css('visibility', 'hidden'),
// Shortcut for switching to 'Redirect' and selects 'Delete before redirecting'
$('<a>').attr('id', 'closeXFD-del-deleteAndRedir')
.text('Delete and redirect...')
.click(function(){
$('#closeXFD-result-redir').prop('checked', true).change();
$('#closeXFD-redir-deleteFirst').prop('checked', true);
// Switch result labels to 'Delete and ...' versions, if needed
if ( $('#closeXFD-result-redir').next().is(':visible') ) {
$('#closeXFD-redir-deleteFirst').change();
}
} )
),
// Holding cell
( config.xfd.type !== 'tfd' ) ? '' : $('<div>')
.attr('id', 'closeXFD-del-holdcell')
.append(
$('<strong>').css('display', 'block').append(
( !multimode ) ? 'Holding cell section:' : $('<input>')
.attr({'type':'checkbox', 'name':'closeXFD-del-useHoldingCell',
'id':'closeXFD-del-useHoldingCell', 'value':'useHoldingCell'})
.on('change', function(){
self.inputData.useHoldingCell = $(this).prop('checked');
if ( self.inputData.useHoldingCell ) {
$('#closeXFD-del-deletetalk, #closeXFD-del-deleteredir')
.prop('disabled', true)
.next().addClass('xfdc-dialog-disabled');
$('#closeXFD-del-holdcell').children('div').show();
} else {
$('#closeXFD-del-deletetalk, #closeXFD-del-deleteredir')
.prop('disabled', false)
.next().removeClass('xfdc-dialog-disabled');
$('#closeXFD-del-holdcell').children('div').hide();
}
})
.prop('checked', !config.user.isSysop).change()
.prop('disabled', !config.user.isSysop)
.toggle(config.user.isSysop),
( !multimode ) ? '' : $('<label>')
.attr('for', 'closeXFD-del-useHoldingCell')
.text((config.user.isSysop) ? 'Add to holding cell '+
'instead of deleting:' : 'Holding cell section:')
),
// Review
$('<div>').append(
$('<input>').attr({'type':'radio', 'name':'closeXFD-holdingcell',
'id':'closeXFD-holdingcell-review', 'value':'review'}),
$('<label>').attr('for', 'closeXFD-holdingcell-review').text('Review')
),
// Convert
$('<div>').append(
$('<input>').attr({'type':'radio', 'name':'closeXFD-holdingcell',
'id':'closeXFD-holdingcell-convert', 'value':'convert'}),
$('<label>').attr('for', 'closeXFD-holdingcell-convert').text('Convert')
),
// Substitute
$('<div>').append(
$('<input>').attr({'type':'radio', 'name':'closeXFD-holdingcell',
'id':'closeXFD-holdingcell-substitute', 'value':'substitute'}),
$('<label>').attr('for', 'closeXFD-holdingcell-substitute').text('Substitute')
),
// Orphan
$('<div>').append(
$('<input>').attr({'type':'radio', 'name':'closeXFD-holdingcell',
'id':'closeXFD-holdingcell-orphan', 'value':'orphan'}),
$('<label>').attr('for', 'closeXFD-holdingcell-orphan').text('Orphan')
),
// Ready for deletion
$('<div>').append(
$('<input>').attr({'type':'radio', 'name':'closeXFD-holdingcell',
'id':'closeXFD-holdingcell-ready', 'value':'ready'}),
$('<label>').attr('for', 'closeXFD-holdingcell-ready')
.text('Ready for deletion')
)
)
.children('div').toggle(!multimode || !config.user.isSysop).end()
.find("input[type=radio][name='closeXFD-holdingcell']").change(function() {
self.inputData.holdcell = $(this).val();
// Disable 'don't delete talk' option unless 'ready for deletion' selected
if ( self.inputData.holdcell !== 'ready' ) {
$('#closeXFD-del-deletetalk').prop('disabled', true)
.next().addClass('xfdc-dialog-disabled');
} else {
$('#closeXFD-del-deletetalk').prop('disabled', false)
.next().removeClass('xfdc-dialog-disabled');
}
} )
.end()
)
)
.find('input[type=checkbox]')
.not('#closeXFD-redir-deleteFirst, #closeXFD-del-useHoldingCell')
.change(function(){
var v = $(this).val();
self.inputData[v] = $(this).prop('checked');
})
.change()
.end()
.end();
if ( multimode ) {
$options.children().addClass('xfdc-dialog-container');
} else {
$options.css({'float':'right', 'width':'59%'});
}
return $options;
};
Dialog.prototype.makeButtons = function(multimode) {
var self = this;
var $buttons = $('<div>')
.attr('id', 'closeXFD-interface-buttons')
.css({'text-align':'center', 'clear':'both'})
.append(
$('<button>')
.attr('id', 'closeXFD-interface-cancel')
.text('Cancel')
.click(function() {
self.close();
self.discussion.showLinks();
})
);
if ( !self.relisting ) {
$buttons.prepend(
$('<button>')
.attr('id', 'closeXFD-interface-closedisc')
.text('Close Discussion')
.click(function() {
if ( multimode ) {
self.evaluateMultimodeClose();
} else {
self.evaluateClose();
}
}),
$('<button>')
.attr('id', 'closeXFD-interface-preview')
.text('Preview')
.click(function() {
if ( multimode ) {
self.evaluateMultimodeClose(true);
} else {
self.evaluateClose(true);
}
})
);
} else {
$buttons.prepend(
$('<button>').attr('id', 'closeXFD-interface-relistdisc').text('Relist Discussion')
.click(function() {
self.close();
self.discussion.taskManager = new TaskManager(self.discussion);
self.discussion.taskManager.start();
}),
$('<button>').attr('id', 'closeXFD-interface-previewRelist').text('Preview')
.click(function() {
self.showPreview(true);
})
);
}
return $buttons;
};
Dialog.prototype.makePreviewBox = function() {
return $('<p>').attr('id', 'closeXFD-preview-output').hide();
};
Dialog.prototype.updateMultimodeOptions = function() {
$('.closeXFD-keepOptions').toggle(this.inputData.inPageActions('keep'));
$('.closeXFD-redirectOptions').toggle(this.inputData.inPageActions('redirect'));
$('.closeXFD-mergeOptions').toggle(this.inputData.inPageActions('merge'));
$('.closeXFD-disambigOptions').toggle(this.inputData.inPageActions('disambig'));
$('.closeXFD-deleteOptions').toggle(this.inputData.inPageActions('del'));
$('#closeXFD-noAction').toggle(this.inputData.inPageActions('na'));
this.resetHeight();
};
Dialog.prototype.storeRcatData = function(rcatData) {
this.inputData.rcats = rcatData.join('\n').trim();
$('#closeXFD-redir-rcats').prop('checked', this.inputData.rcats !== '');
};
// --- Set up for close or relist: ---
Dialog.prototype.setup = function() {
var self = this;
this.emptyContent();
if ( config.xfd.type !== 'ffd' && !self.discussion.isBasicMode() ) {
//this.rcatSelector = extraJs.makeRcatCapsuleMultiselect(self.storeRcatData, self);
}
this.inputData = new InputData(this.discussion);
this.addToHeader(this.makeHeader());
this.addToBody(this.makePagesList());
if ( this.relisting ) {
this.addToBody(this.makeRationaleBox());
} else {
if ( !config.user.isSysop ) {
this.addToBody(this.makeNonAdminNote());
}
this.addToBody(this.makeCloseResult());
this.addToBody(this.makeRationaleBox());
if ( this.discussion.isBasicMode() ) {
this.inputData.after = 'noAction';
} else {
if ( config.xfd.type === 'afd' ) {
//this.rcatSelector.setItemsFromData(['{{R to related topic}}']);
}
this.addToBody(this.makeAfterActions());
this.addToBody(this.makeOptions());
}
}
this.addToFooter(this.makeButtons());
this.addToFooter(this.makePreviewBox());
this.resetHeight();
this.display();
};
// --- Set up for multimode close: ---
Dialog.prototype.setupForMultiClose = function() {
var self = this;
this.emptyContent();
if ( config.xfd.type !== 'ffd' && !self.discussion.isBasicMode() ) {
//this.rcatSelector = extraJs.makeRcatCapsuleMultiselect(self.storeRcatData, self);
}
this.inputData = new InputData(this.discussion);
this.inputData.initialiseMultimode();
this.addToHeader(this.makeHeader(true));
if ( !config.user.isSysop ) {
this.addToBody(this.makeNonAdminNote());
}
this.addToBody(this.makePagesList(true));
this.addToBody(this.makeRationaleBox(true));
this.addToBody(this.makeAfterActions(true));
if ( config.xfd.type === 'afd' ) {
//this.rcatSelector.setItemsFromData(['{{R to related topic}}']);
}
this.addToBody(this.makeOptions(true));
this.addToFooter(this.makeButtons(true));
this.addToFooter(this.makePreviewBox());
this.resetHeight();
this.display();
};
// Preview
Dialog.prototype.showPreview = function(relist) {
var preview_wikitext = '';
if ( relist ) {
preview_wikitext = '{{Relist|1=' + this.inputData.getRelistComment() + '}}';
} else {
preview_wikitext = "The result of the discussion was '''" +
this.inputData.getResult()+
"'''" +
(( this.inputData.getTargetLink() ) ? ' to ' + this.inputData.getTargetLink() : '') +
( (this.inputData.getRationale()) || '.' ) +
' ' +
config.user.sig;
}
API.get({
action: 'parse',
contentmodel: 'wikitext',
text: preview_wikitext,
})
.done(function(result) {
preview_html = result.parse.text['*'];
$('#closeXFD-preview-output').html(preview_html).show().find('a').attr('target', '_blank');
})
.fail(function(code, jqxhr) {
$('#closeXFD-preview-output')
.empty()
.append(
$('<strong>')
.css('color', '#f00')
.append(
'Error: Preview failed.',
$('<br>'),
'Details: ',
makeErrorMsg(code, jqxhr)
)
)
.show();
});
};
// --- Evaluate if required form elements have been completed ---
// Show errors for anything required but not completed
Dialog.prototype.showErrors = function(element_ids) {
// remove any old error messages
//$('.closeXFD-errorNote').parent().css('border', '').end().remove();
$(element_ids.join(', '))
.filter(':visible')
.addClass('closeXFD-errorNote')
.fadeTo('fast', 0.33).fadeTo('fast', 0.8).fadeTo('fast', 0.4).fadeTo('fast', 1)
.on('change.errorNote', function() {
$(this)
.removeClass('closeXFD-errorNote')
.off('change.errorNote');
});
};
// Evaluate standard close
Dialog.prototype.evaluateClose = function(preview) {
self = this;
var errors = [];
// Result
if ( self.inputData.result ) {
switch ( self.inputData.result ) {
case 'custom':
if ( !self.inputData.customResult ) {
// Custom result not specified
errors.push('#closeXFD-resultContainer-custom');
}
break;
case 'retarget':
case 'redirect':
case 'soft redirect':
case 'merge':
if ( $('#closeXFD-result-target').val().trim() === '' ) {
// Target not specified
errors.push('#closeXFD-resultContainer-target');
} else {
if ( !self.inputData.target ) {
// Invalid target
alert('Bad ' + self.inputData.result +
' target: the title is invalid.');
errors.push('#closeXFD-resultContainer-target');
}
}
break;
default:
break;
}
} else {
// Result not selected
errors.push('#closeXFD-resultOptions');
}
// Rationale
var r = self.inputData.rationale;
if ( r ) {
// Prepend newline if it starts with a * or #
r = r.replace(/^(\*|#)/, '\n$1');
// Append a newline if the last line starts with a * or #
var n = r.lastIndexOf('\n');
if ( n > 0 && /(\*|#)/.test(r.slice(n+1,n+2) ) ) {
r += '\n:';
}
self.inputData.rationale = r;
}
// After actions & options
if ( self.inputData.after ) {
if ( self.inputData.after === 'holdingCell' && !self.inputData.holdcell ) {
// Holding cell section not selected
errors.push('#closeXFD-del-holdcell');
} else if (
config.xfd.type === 'tfd' &&
self.inputData.after === 'doMergeActions' &&
!self.inputData.mergeHoldcell
) {
// Holding cell merge subsection not selected
errors.push('#closeXFD-merge-holdcell');
}
} else {
// After action not selected
errors.push('#closeXFD-afterContainer');
}
if ( errors.length > 0 ) {
// Show errors
self.showErrors(errors);
} else if ( preview ) {
// Show preview
self.showPreview();
} else {
// Start closing
if (
( config.xfd.type === 'tfd' && self.inputData.after === 'doMergeActions' ) ||
( self.inputData.after === 'holdingCell' && self.inputData.holdcell !== 'ready' )
) {
// Don't delete/tag talkpages when using holding cell (except for 'ready for deletion')
self.inputData.dontdeletetalk = true;
}
self.close();
self.discussion.taskManager = new TaskManager(self.discussion);
self.discussion.taskManager.start();
}
};
// Evaluate multimode close
Dialog.prototype.evaluateMultimodeClose = function(preview) {
self = this;
var errors = [];
// Check result summary specified
if ( !self.inputData.result ) {
errors.push('#closeXFD-resultSummary-input');
}
// Rationale
var r = self.inputData.rationale;
if ( r ) {
// Prepend newline if it starts with a * or #
r = r.replace(/^(\*|#)/, '\n$1');
// Append a newline if the last line starts with a * or #
var n = r.lastIndexOf('\n');
if ( n > 0 && /(\*|#)/.test(r.slice(n+1,n+2) ) ) {
r += '\n:';
}
self.inputData.rationale = r;
}
// Check each page action / target
for ( var p in self.inputData.pageActions ) {
// Check action specified
if ( !self.inputData.pageActions[p].action ) {
errors.push('#'+self.inputData.pageActions[p].id);
} else if (
self.inputData.pageActions[p] === 'redirect' ||
self.inputData.pageActions[p] === 'merge'
) {
// Check target specified
var targetInputId = self.inputData.pageActions[p].id.replace('action', 'tagert');
if ( $('#'+targetInputId).val().trim() === '' ) {
errors.push('#'+targetInputId);
} else if ( !self.inputData.pageActions[p].target ) {
alert('Bad ' + self.inputData.pageActions[p].action + 'target for ' +
p +' – the title is invalid.');
errors.push('#'+targetInputId);
}
}
}
// Check holding cell selection (merge)
if (
config.xfd.type === 'tfd' &&
self.inputData.inPageActions('merge') &&
!self.inputData.mergeHoldcell
) {
errors.push('#closeXFD-merge-holdcell');
}
// Check holding cell selection (delete)
if (
config.xfd.type === 'tfd' &&
self.inputData.inPageActions('del') &&
self.inputData.useHoldingCell &&
!self.inputData.holdcell
) {
errors.push('#closeXFD-del-holdcell');
}
if ( errors.length > 0 ) {
// Show errors
self.showErrors(errors);
} else if ( preview ) {
// Show preview
self.showPreview();
} else {
// Start closing
if ( self.inputData.useHoldingCell && self.inputData.holdcell !== 'ready' ) {
// Don't delete/tag talkpages when using holding cell (except for 'ready for deletion')
self.inputData.dontdeletetalk = true;
}
self.close();
self.discussion.taskManager = new TaskManager(self.discussion);
self.discussion.taskManager.start();
}
};
/* ========== InputData class ===================================================================
The raw data that the user entered in a dialog, and prototype get
functions to obtain ready to use data
---------------------------------------------------------------------------------------------- */
// Constructor
var InputData = function(discussion) {
this.discussion = discussion;
/* Defined by user interaction with dialog:
this.speedy : {Boolean}
this.customResult : {String}
this.target : {Page object}
this.result : {String}
this.rationale : {String}
this.resultPunct : {String}
this.after : {String}
this.deleteFirst : {Boolean}
this.rcats : {String|Boolean false}
this.useHoldingCell : {Boolean}
this.holdcell : {String}
this.mergeHoldcell : {String}
this.dontdeletetalk : {Boolean}
this.deleteredir : {Boolean}
this.unlinkbackl : {Boolean}
*/
};
// ---------- InputData prototype --------------------------------------------------------------- */
// Setup for multimode close
InputData.prototype.initialiseMultimode = function() {
this.multimode = true;
this.pageActions = {};
};
// Check if an action has been specified for any of the pages (for multimode)
InputData.prototype.inPageActions = function(action) {
var self = this;
if ( !self.multimode ) return false;
for ( var p in self.pageActions ) {
if ( self.pageActions[p].action === action ) return true;
}
return false;
};
// --- Get functions ---
// Get array of Page objects for a result (for multimode)
InputData.prototype.getPages = function(result, result2, result3) {
var self = this;
if ( !self.multimode ) return false;
if ( result2 == null ) result2 = false;
if ( result3 == null ) result3 = false;
var output = [];
for ( var p in self.pageActions ) {
if (
self.pageActions[p].action === result ||
self.pageActions[p].action === result2 ||
self.pageActions[p].action === result3
) {
output.push(self.discussion.getPageByTitle(p));
}
}
return output;
};
// Get the relist comment, escaping pipes which aren't within wikilinks or templates
InputData.prototype.getRelistComment = function() {
return this.rationale.replace(/(\|)(?!(?:[^\[]*]|[^\{]*}))/g, '|');
};
// Get the full result, for use as bolded result text or in edit summaries etc.
InputData.prototype.getResult = function() {
if ( this.multimode ) {
return this.result;
} else {
var output = '';
if ( this.speedy ) {
output += 'speedy ';
}
if (
this.deleteFirst &&
/(?:retarget|redirect|soft redirect)/.test(this.result)
) {
output += 'delete and ';
}
output += ( this.result === 'custom') ? this.customResult : this.result;
return output;
}
};
// Get the page title of the target - for a particular page if in multimode
InputData.prototype.getTarget = function(p) {
if ( this.multimode && p ) {
return this.pageActions[p].target.getPrefixedText();
} else if ( /(?:retarget|redirect|soft redirect|merge)/.test(this.result) ) {
return this.target && this.target.getPrefixedText();
} else {
return false;
}
};
// Multimode: Get an array of page titles of targets for a particular action ('redirect' or 'merge')
// Single result: Return an array with one item, the target page title
InputData.prototype.getTargetsArray = function(action) {
var self = this;
var output = [];
if ( self.multimode ) {
for ( var p in self.pageActions ) {
if ( self.pageActions[p] && self.pageActions[p].action === action ) {
output.push(self.pageActions[p].target.getPrefixedText());
}
}
} else {
output.push(self.target.getPrefixedText());
}
return output;
};
// Multimode: Get an array of Titles objects of targets, for either 'redirect' or 'merge'
// Single result: Return an array with one item, the Title object for the target
InputData.prototype.getTargetsObjectsArray = function() {
var self = this;
var output = [];
if ( self.multimode ) {
for ( var p in self.pageActions ) {
if ( self.pageActions[p] && /(redirect|merge)/.test(self.pageActions[p].action) ) {
output.push(self.pageActions[p].target);
}
}
} else {
output.push(self.target);
}
return output;
};
// Get an array of page titles to be merged to a particlar target (for multimode)
InputData.prototype.getPagesToBeMerged = function(target) {
var self = this;
var output = [];
for ( var p in self.pageActions ) {
if (
self.pageActions[p] &&
self.pageActions[p].action === 'merge' &&
self.pageActions[p].target.getPrefixedText() === target
) {
output.push(p);
}
}
return output;
};
// Get a link to the target - for a particular page if multimode - including fragment.
// Is formatted as a wikilink, with preceding colon if needed, unless raw is true.
InputData.prototype.getTargetLink = function(p, raw) {
var targetObject = ( this.multimode && p ) ? this.pageActions[p].target : this.target;
if ( !targetObject ) {
return null;
}
var targetFrag = ( targetObject.getFragment() ) ? '#' + targetObject.getFragment() : '';
var targetNS = targetObject.getNamespaceId();
if ( raw ) {
return targetObject.getPrefixedText() + targetFrag;
} else if ( targetNS === 6 || targetNS === 14 ) {
return "[[:" + targetObject.getPrefixedText() + targetFrag + "]]";
} else {
return "[[" + targetObject.getPrefixedText() + targetFrag + "]]";
}
};
// Gets the rational, preceded by a period (unless its not a new sentence) and a space
InputData.prototype.getRationale = function() {
return ( this.rationale ) ? this.resultPunct + ' ' + this.rationale : '';
};
/* ========== TaskManager class =================================================================
Manages tasks - chooses which tasks to do, what data to pass to them, and
keeps track of when (all) tasks are completed
---------------------------------------------------------------------------------------------- */
// Constructor
var TaskManager = function(discussion, inputDataObject) {
this.discussion = discussion;
if ( inputDataObject != null ) {
this.inputData = inputDataObject;
} else {
this.inputData = discussion.dialog.inputData;
}
this.tasks = [];
this.dfd = {};
};
// ---------- TaskManager prototype ------------------------------------------------------------- */
// Sanity checks - confirm with user if there are a lot of pages or pages in unexpected namespaces
TaskManager.prototype.makeSanityCheckWarnings = function() {
var self = this;
var relisting = !self.inputData.result;
var multimode = self.inputData.multimode;
var warnings = [];
// Check for mass actions when closing:
if ( !relisting ) {
// Only if nominated pages will be edited
if (
( relisting && /(ffd|tfd)/.test(config.xfd.type) ) ||
( !relisting && !multimode && self.inputData.after !== 'noAction' ) ||
( multimode && self.inputData.getPages('na').length !== self.discussion.pages.length )
) {
if ( self.discussion.pages.length > 3 ) {
warnings.push('Mass actions will be peformed (' + self.discussion.pages.length +
' nominated pages detected).');
if ( self.discussion.pages.length > 50 ) {
warnings.push('Only the first 50 pages may be processed, depending on the ' +
'closing options selected.');
}
}
}
}
// Check target page namespace:
var targetObjectsArray = self.inputData.getTargetsObjectsArray();
if ( targetObjectsArray ) {
$.each(targetObjectsArray, function(_i, t) {
if ( t && !t.hasCorrectNamespace()) {
warnings.push( 'Target page [["' + t.getPrefixedText() + '"]] is not in the ' +
config.mw.namespaces[(config.xfd.ns_number[0]).toString()] + ' namespace.');
}
});
}
//Check namespaces of nominated pages
var wrongNamespacePages = self.discussion.pages && self.discussion.pages.filter(function(p) {
return !p.hasCorrectNamespace();
});
if ( wrongNamespacePages.length > 0 ) {
warnings.push(
'The following pages are not in the ' +
config.mw.namespaces[(config.xfd.ns_number[0]).toString()] +
' namespace:<ul><li>[[' +
wrongNamespacePages.map(function(p){ return p.getTitle(); }).join(']]</li><li>[[') +
']]</li>'
);
}
if ( warnings.length === 0 ) {
return false;
} else {
return '<p>' + warnings.join('</p><p>') + '</p>';
}
};
// Resolve redirects:
// If nominated pages are redirects (at venues other than RfD), the script can't know if this was
// appropriate, where results such as 'Delete' should be applied to the target, or an out-of-process
// redirection, where results such as 'Delete' should be applied to the redirect
TaskManager.prototype.resolveRedirects = function(callback) {
var self = this;
// No need to process for RfD, or if basic closure, or no after actions
var relisting = !self.inputData.result;
var multimode = self.inputData.multimode;
if (
// At RfD
config.xfd.type === 'rfd' ||
// Basic mode (no pages detected)
!self.discussion.pages ||
// Relisting for other than FfD/TfD
( relisting && !/(ffd|tfd)/.test(config.xfd.type) ) ||
// Not multimode, no after actions
( !relisting && !multimode && self.inputData.after === 'noAction' ) ||
// Multimode, no action for every page
( multimode && self.inputData.getPages('na').length === self.discussion.pages.length )
) {
// Nominate pages expected to be redirects, no need to resolve them
callback.call(self);
return;
}
var processPages = function(result) {
if ( result.query && result.query.redirects ) {
// Redirects are present, need to ask user what to do
var redir_from = new Array(result.query.redirects.length);
var redir_to = new Array(result.query.redirects.length);
var redir_list = new Array(result.query.redirects.length);
for (var i=0; i<result.query.redirects.length; i++) {
redir_from[i] = result.query.redirects[i].from;
redir_to[i] = result.query.redirects[i].to;
redir_list[i] = '[[' + redir_from[i] + ']] → [[' + redir_to[i] + ']]';
}
var redirects_msg = "The following nominated pages are redirects to other pages:<ul><li>" +
redir_list.join("</li><li>") + "</li></ul>";
// Proccess user's choice
processRedirects = function(action) {
if ( action === 'accept' ) {
// Build a reference object linking titles to their page ids
var title_ids = {};
for (var tt=0; tt<result.query.pageids.length; tt++) {
title_ids[ result.query.pageids[tt] ] = result.query.pages[ result.query.pageids[tt] ].title;
}
// Update discussion's page data
self.discussion.pages = self.discussion.pages.map(function(p) {
var rIndex = $.inArray(p.getTitle(), redir_from);
if ( rIndex === -1 ) {
// This is not a redirect
return p;
} else {
// This is a redirect - create new page object for target
var targetPage = Page.newFromText(redir_to[rIndex]);
var targetId = extraJs.val2key(redir_to[rIndex], title_ids);
targetPage.exists = targetId > 0;
targetPage.talkExists = result.query.pages[targetId].talkid > 0;
return targetPage;
}
});
// Execute callback
callback.call(self);
} else if ( action === 'reject' ) {
// Just get on with it: execute callback
callback.call(self);
} else {
// Abort closing, reset discussion links
self.discussion.showLinks();
return;
}
};
// Ask for user confirmation
extraJs.multiButtonConfirm('Use redirects or targets?', redirects_msg, [
{ label: 'Cancel', flags: 'safe' },
{ label: 'Use redirects', action: 'reject' },
{ label: 'Use targets', action: 'accept', flags: 'progressive' }
], processRedirects, {'verbose': true, 'unescape': true, 'wikilinks': true} );
} else {
// No redirects present, just get on with it: execute callback
callback.call(self);
}
};
// Get redirect status / targets, if any, from Api
API.get( {
action: 'query',
titles: self.discussion.getPageTitles().join('|'),
redirects: 1,
prop: 'info',
inprop: 'talkid',
indexpageids: 1
} ).done( processPages )
.fail( function() {
// TODO....
} );
};
// --- Initialise ---
// Set up for relist
TaskManager.prototype.initialiseForRelist = function() {
var self = this;
self.tasks.push(new Task('getRelistInfo', self.discussion));
// Object to store gathered info
self.relistInfo = {};
switch ( config.xfd.type ) {
case 'afd':
Array.prototype.push.apply(self.tasks, [
new Task('updateDiscussion', self.discussion),
new Task('updateOldLogPage', self.discussion),
new Task('updateNewLogPage', self.discussion)
]);
// Deferred for when this relist is finished, keep track of it via its array index
self.afdLogEditIndex = config.track.afdLogEdit.push($.Deferred()) - 1;
// Notify user
self.discussion.setStatus('Waiting... (to avoid edit conflicts, previous relistings '+
'need to be completed)');
break;
case 'mfd':
self.tasks.push(new Task('updateDiscussion', self.discussion));
break;
case 'rfd':
Array.prototype.push.apply(self.tasks, [
new Task('updateOldLogPage', self.discussion),
new Task('updateNewLogPage', self.discussion)
]);
break;
default: // ffd, tfd
Array.prototype.push.apply(self.tasks, [
new Task('updateOldLogPage', self.discussion),
new Task('updateNewLogPage', self.discussion),
( self.discussion.pages ) ? new Task('updateNomTemplates', self.discussion) : ''
]
.filter(function(v){ return !!v; })
);
break;
}
self.initialiseFinally();
};
TaskManager.prototype.initialiseForMultimodeClose = function() {
// Set up for multimode
var self = this;
// Close discussion
self.tasks.push(new Task('closeDisc', self.discussion));
// Add Old xfd templates
if (
self.inputData.inPageActions('keep') ||
self.inputData.inPageActions('redirect') ||
( self.inputData.inPageActions('merge') && config.xfd.type !== 'tfd' ) ||
self.inputData.inPageActions('disambig')
) {
self.tasks.push(new Task('addOldXfd',
self.discussion, self.inputData.getPages(
'keep', 'redirect', ( /(afd|mfd)/.test(config.xfd.type) ) ? 'merge' : 'disambig'
)
));
}
// For keep results:
if ( self.inputData.inPageActions('keep') ) {
self.tasks.push(new Task('removeNomTemplates',
self.discussion, self.inputData.getPages('keep')));
}
// For redirect results:
if ( self.inputData.inPageActions('redirect') ) {
self.tasks.push(new Task('redirect',
self.discussion, self.inputData.getPages('redirect')));
}
// For merge (not holding cell) results:
if ( config.xfd.type !== 'tfd' && self.inputData.inPageActions('merge') ) {
self.tasks.push(new Task('addMergeTemplates',
self.discussion, self.inputData.getPages('merge')));
}
// For disambiguate results:
if ( self.inputData.inPageActions('disambig') ) {
self.tasks.push(new Task('disambiguate',
self.discussion, self.inputData.getPages('disambig')));
}
// For delete (not holding cell) results:
if ( self.inputData.inPageActions('del') && !self.inputData.useHoldingCell ) {
Array.prototype.push.apply(self.tasks, [
new Task('peformDeletion', self.discussion, self.inputData.getPages('del')),
( self.inputData.dontdeletetalk ) ? null : new Task('deleteTalk',
self.discussion, self.inputData.getPages('del')),
( self.inputData.deleteredir ) ? new Task('deleteRedirects',
self.discussion, self.inputData.getPages('del')) : null,
( self.inputData.unlinkbackl ) ? new Task('unlinkBacklinks',
self.discussion, self.inputData.getPages('del')) : null
]
.filter(function(v){ return !!v; })
);
}
// For TfD holding cell results
if ( config.xfd.type === 'tfd' ) {
var doHcMerge = self.inputData.inPageActions('merge');
var doHcDelete = self.inputData.inPageActions('del') && self.inputData.useHoldingCell;
if ( doHcMerge && doHcDelete ) {
// Both 'merge' and 'delete' (via holding cell) results
Array.prototype.push.apply(self.tasks, [
new Task('addBeingDeleted', self.discussion,
self.inputData.getPages('merge', 'del')),
new Task('addToHoldingCell', self.discussion,
self.inputData.getPages('merge', 'del')),
( self.inputData.dontdeletetalk ) ? null : new Task('tagTalkWithSpeedy',
self.discussion, self.inputData.getPages('merge', 'del'))
]
.filter(function(v){ return !!v; })
);
} else if ( doHcMerge ) {
// Only 'merge' (via holding cell) results
Array.prototype.push.apply(self.tasks, [
new Task('addBeingDeleted', self.discussion, self.inputData.getPages('merge')),
new Task('addToHoldingCell', self.discussion, self.inputData.getPages('merge'))
]
);
} else if ( doHcDelete ) {
// Only 'delete' (via holding cell) results
Array.prototype.push.apply(self.tasks, [
new Task('addBeingDeleted', self.discussion, self.inputData.getPages('del')),
new Task('addToHoldingCell', self.discussion, self.inputData.getPages('del')),
( self.inputData.dontdeletetalk ) ? null : new Task('tagTalkWithSpeedy',
self.discussion, self.inputData.getPages('del'))
]
.filter(function(v){ return !!v; })
);
}
}
self.initialiseFinally();
};
TaskManager.prototype.initialiseForClose = function() {
var self = this;
// Set up for close
self.tasks.push(new Task('closeDisc', self.discussion));
var addHoldcellTasks = function(){
Array.prototype.push.apply(self.tasks, [
new Task('addBeingDeleted', self.discussion),
new Task('addToHoldingCell', self.discussion),
( self.inputData.dontdeletetalk ) ? null : new Task('tagTalkWithSpeedy',
self.discussion)
]
.filter(function(v){ return !!v; })
);
};
switch ( self.inputData.after ) {
case 'doKeepActions':
Array.prototype.push.apply(self.tasks, [
new Task('addOldXfd', self.discussion),
new Task('removeNomTemplates', self.discussion)
]);
break;
case 'doRedirectActions':
Array.prototype.push.apply(self.tasks, [
new Task('addOldXfd', self.discussion),
new Task('redirect', self.discussion),
( self.inputData.result === 'soft redirect' ) ? null : new Task(
'removeCircularLinks', self.discussion)
]
.filter(function(v){ return !!v; })
);
break;
case 'doMergeActions':
if ( config.xfd.type === 'tfd' ) {
addHoldcellTasks();
} else {
Array.prototype.push.apply(self.tasks, [
new Task('addOldXfd', self.discussion),
new Task('addMergeTemplates', self.discussion)
]);
}
break;
case 'doDisambigActions':
Array.prototype.push.apply(self.tasks, [
new Task('addOldXfd', self.discussion),
new Task('disambiguate', self.discussion)
]);
break;
case 'doDeleteActions':
Array.prototype.push.apply(self.tasks, [
new Task('peformDeletion', self.discussion),
( self.inputData.dontdeletetalk ) ? null : new Task('deleteTalk',
self.discussion),
( self.inputData.deleteredir ) ? new Task('deleteRedirects',
self.discussion) : null,
( self.inputData.unlinkbackl ) ? new Task('unlinkBacklinks',
self.discussion) : null
]
.filter(function(v){ return !!v; })
);
break;
case 'holdingCell':
addHoldcellTasks();
break;
}
self.initialiseFinally();
};
// After initialising for relist/close, set up deferred objects and start initial task
TaskManager.prototype.initialiseFinally = function() {
var self = this;
// Deferred objects
self.dfd.initialTask = $.Deferred().done(function() {
// When initial task is done, start others (if any)
if ( self.tasks.length > 1 ) {
$.each(self.tasks.slice(1), function(_i, t) {
if ( t.start ) {
t.start(); // TODO: set task status, errors, and warning here, from the returned promise
} else { // TODO: Investigate what the point of this is.
self.tasks[_i+1].start();
}
});
}
});
if ( self.getTaskByName('unlinkBacklinks') ) {
// When unlinkBacklinks query is completed, it is okay to delete redirects
self.dfd.ublQuery = $.Deferred();
} else if ( self.getTaskByName('deleteRedirects') ) {
self.dfd.ublQuery = $.Deferred().resolve();
}
// Start initial task
if ( self.afdLogEditIndex ) {
$.when( config.track.afdLogEdit[self.afdLogEditIndex-1] )
.then( function() {
self.discussion.setStatus('Relisting...');
self.tasks[0].start();
} );
} else {
self.tasks[0].start();
}
};
TaskManager.prototype.start = function() {
var self = this;
// Initialise
var doAfterSanityCheck = function(action) {
if ( action !== 'proceed' ) {
// Reset discussion links
self.discussion.showLinks();
return;
}
// Resolve redirects, then initialise
if ( !self.inputData.result ) {
self.resolveRedirects(self.initialiseForRelist);
} else if ( self.inputData.multimode ) {
self.resolveRedirects(self.initialiseForMultimodeClose);
} else {
self.resolveRedirects(self.initialiseForClose);
}
};
// Sanity checks
var sanityCheckWarnings = self.makeSanityCheckWarnings();
if ( sanityCheckWarnings ) {
// Check with user before preceding
extraJs.multiButtonConfirm('Warning', sanityCheckWarnings, [
{ label: 'Cancel', flags: 'safe' },
{ label: 'Continue', action: 'proceed', flags: 'progressive' }
], doAfterSanityCheck, {'verbose': true, 'unescape': true, 'wikilinks':true} );
} else {
// No need to check before preceding
doAfterSanityCheck('proceed');
}
};
TaskManager.prototype.getTaskByName = function(name) {
var self = this;
for ( var i=0; i<self.tasks.length; i++ ) {
if ( self.tasks[i].name === name ) {
return self.tasks[i];
}
}
return false;
};
TaskManager.prototype.makeTaskNote = function(task) {
$notice = $('<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)
);
return $notice;
};
TaskManager.prototype.updateTaskNotices = function(task, skipFinishedCheck) {
var self = this;
var $notices = self.discussion.get$notices();
if ( task && $notices.find('.'+task.name).length ) {
// Update specified task
var $taskNotice = self.discussion.get$notices().find('.'+task.name);
$taskNotice.after(self.makeTaskNote(task)).remove();
} else {
// Update all tasks
self.discussion.setNotices(
self.tasks.map(function(t) { return self.makeTaskNote(t); })
);
}
var allFin = self.tasks.every(function(t) {
return t.isFinished();
});
// If every task is finished
if ( !skipFinishedCheck && allFin ) {
if ( self.afdLogEditIndex ) {
config.track.afdLogEdit[self.afdLogEditIndex].resolve();
}
self.discussion.setFinished();
}
};
TaskManager.prototype.abortTasks = function(reason) {
var self = this;
$.each(self.tasks, function(_i, t) {
t.status = 'aborted';
});
self.updateTaskNotices(null, true);
if ( self.afdLogEditIndex ) {
config.track.afdLogEdit[self.afdLogEditIndex].resolve();
}
self.discussion.setFinished(reason);
};
/* ========== Task class =========================================================================
Tasks represent the action or series of actions the script will peform for
the closer. Each task object also has a description, status, and (if any)
error or warning messages, which the TaskManager uses to display the task
notice on the page.
---------------------------------------------------------------------------------------------- */
// Constructor
var Task = function(taskname, discussion, pages) {
this.discussion = discussion;
this.inputData = discussion.taskManager.inputData;
this.name = taskname;
this.status = 'waiting';
this.errors = [];
this.warnings = [];
this.tracking = {};
if ( pages != null ) {
this.pages = pages;
} else {
this.pages = null;
}
var plural = ( this.pages ) ? this.pages.length > 1 : this.discussion.pages.length > 1;
switch ( taskname ) {
case 'closeDisc':
this.description = 'Closing discussion';
break;
case 'addOldXfd':
this.description = 'Updating talk page' + (( plural ) ? 's' : '');
break;
case 'removeNomTemplates':
case 'addMergeTemplates':
case 'disambiguate':
this.description = 'Updating page' + (( plural ) ? 's' : '');
break;
case 'peformDeletion':
this.description = 'Deleting page' + (( plural ) ? 's' : '');
break;
case 'addBeingDeleted':
this.description = 'Updating template' + (( plural ) ? 's' : '');
break;
case 'addToHoldingCell':
this.description = 'Listing at holding cell';
break;
case 'deleteTalk':
this.description = 'Deleting talk page' + (( plural ) ? 's' : '');
break;
case 'tagTalkWithSpeedy':
this.description = 'Tagging talk page' + (( plural ) ? 's' : '') +
' for speedy deletion';
break;
case 'deleteRedirects':
this.description = 'Deleting redirects';
break;
case 'unlinkBacklinks':
this.description = 'Unlinking backlinks';
break;
case 'redirect':
if ( discussion.dialog.inputData.deleteFirst ) {
this.description = 'Deleting page' + (( plural ) ? 's' : '') +
' and replacing with redirect' + (( plural ) ? 's' : '');
} else if ( config.xfd.type === 'rfd' ) {
this.description = 'Retargeting redirect' + (( plural ) ? 's' : '');
} else {
this.description = 'Making page' + (( plural ) ? 's' : '') +
' into redirect' + (( plural ) ? 's' : '');
}
break;
case 'removeCircularLinks':
this.description = 'Unlinking circular links on redirect target';
break;
case 'getRelistInfo':
this.description = 'Preparing to relist';
break;
case 'updateDiscussion':
this.description = 'Updating discussion';
break;
case 'updateOldLogPage':
this.description = 'Removing from old log page';
break;
case 'updateNewLogPage':
this.description = 'Adding to today\'s log page';
break;
case 'updateNomTemplates':
this.description = 'Updating link in nomination template' +
(( plural ) ? 's' : '');
break;
default:
this.description = taskname;
break;
}
};
// ---------- Task prototype -------------------------------------------------------------------- */
// Notice-related functions
Task.prototype.setDescription = function(d) {
var self = this;
this.description = d;
this.discussion.taskManager.updateTaskNotices(self);
};
Task.prototype.setStatus = function(s) {
var self = this;
this.status = s;
this.discussion.taskManager.updateTaskNotices(self);
};
Task.prototype.getStatusText = function() {
var self = this;
switch ( self.status ) {
// Not yet started:
case 'waiting':
return 'Waiting...';
// In progress:
case 'started':
return $('<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'
});
// Finished:
case 'done':
if (
self.discussion.taskManager.dfd.initialTask.state() === 'pending' &&
self.name === self.discussion.taskManager.tasks[0].name
) {
self.discussion.taskManager.dfd.initialTask.resolve();
}
return 'Done!';
case 'aborted':
case 'failed':
case 'skipped':
return extraJs.toSentenceCase(self.status) + '.';
default:
// unknown
return '';
}
};
Task.prototype.isFinished = function() {
return $.inArray(this.status, ['done', 'aborted', 'failed', 'skipped']) !== -1;
};
Task.prototype.addError = function(e, critical) {
var self = this;
this.errors.push($('<span>').addClass('xfdc-notice-error').append(e));
if ( critical ) {
this.status = 'failed';
}
this.discussion.taskManager.updateTaskNotices(self);
};
Task.prototype.addWarning = function(w) {
var self = this;
this.warnings.push($('<span>').addClass('xfdc-notice-warning').append(w));
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.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 ( 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.start = function() {
return this.doTask[this.name](this);
};
// Code to actually do the tasks
Task.prototype.doTask = {};
// --- Closing tasks ---
// Close discussion
Task.prototype.doTask.closeDisc = function(self) {
// Notify task is started -- current (to-be-deprecated) method
self.setStatus('started');
// Get nomination page content and remove {Closing} etc templates if present
return API.get( {
action: 'query',
titles: self.discussion.nomPage,
prop: 'revisions',
rvprop: 'content|timestamp',
rvsection: self.discussion.sectionNumber,
indexpageids: 1
} )
.then( function(response) {
var id = response.query.pageids;
return response.query.pages[ id ].revisions[ 0 ];
} )
.then( function(revision) {
var contents = revision['*'];
var lastEditTime = revision.timestamp;
if ( contents.includes(config.xfd.wikitext.alreadyClosed) ) {
return $.Deferred().reject('aborted', null, 'Discussion already closed (reload page to see '+
'the actual close)');
}
var section_heading = contents.slice(0, contents.indexOf('\n'));
var decodeHtml = function(t) {
return $('<div>').html(t).text();
};
var plain_section_heading = decodeHtml( section_heading
.replace(/(?:^\s*=*\s*|\s*=*\s*$)/g, '') // remove heading markup
.replace(/\[\[\:?(?:[^\]]+\|)?([^\]]+)\]\]/g, '$1') // replace link markup with link text
.replace(/{{\s*[Tt]l[a-z]?\s*\|\s*([^}]+)}}/g, '{{$1}}') // replace tl templates
.replace(/s*}}/, '}}') // remove any extra spaces after replacing tl templates
.replace(/\s{2,}/g, ' ') // collapse multiple spaces into a single space
.trim()
);
var isCorrectSection = plain_section_heading === self.discussion.sectionHeader;
if ( !isCorrectSection ) {
return $.Deferred().reject('aborted: possible edit conflict (found section heading `' +
plain_section_heading + '`)');
}
var xfd_close_top = config.xfd.wikitext.closeTop
.replace(/__RESULT__/, self.inputData.getResult() || ' ')
.replace(/__TO_TARGET__/, ( self.inputData.getTarget() ) ? ' to ' +
self.inputData.getTargetLink() : '')
.replace(/__RATIONALE__/, ( self.inputData.getRationale() || '.'))
.replace(/__SIG__/, config.user.sig);
var section_contents = contents.slice(contents.indexOf('\n')+1)
.replace(
/({{closing}}|{{AfDh}}|{{AfDb}}|\{\{REMOVE THIS TEMPLATE WHEN CLOSING THIS AfD\|.?\}\}|<noinclude>\[\[Category:Relisted AfD debates\|.*?\]\]<\/noinclude>)/gi,
'');
var updated_top = ( config.xfd.type === 'afd' || config.xfd.type === 'mfd' ) ?
xfd_close_top + '\n' + section_heading :
section_heading + '\n' + xfd_close_top;
var updated_section = updated_top + '\n' + section_contents.trim() + '\n' + config.xfd.wikitext.closeBottom;
return $.Deferred().resolve(updated_section, lastEditTime);
} )
.then( function(updated_section, lastEditTime) {
return API.postWithToken( 'csrf', {
action: 'edit',
title: self.discussion.nomPage,
section: self.discussion.sectionNumber,
text: updated_section,
summary: '/* ' + self.discussion.sectionHeader + ' */ Closed as ' +
self.inputData.getResult() + config.script.advert,
basetimestamp: lastEditTime
} );
} )
.then(
function() {
self.setStatus('done'); // Current (to-be-deprecated) method of setting status
return 'done'; // Future method of setting status
},
function(code, jqxhr, abortReason) {
var message = [
'Could not edit page ',
extraJs.makeLink(self.discussion.nomPage),
'; could not close discussion'
];
self.addApiError(code, jqxhr, message);
var abortMessage = ( code.indexOf("aborted") === 0 && abortReason ) || '';
self.discussion.taskManager.abortTasks(abortMessage);
return $.Deferred().reject(code, jqxhr, message);
}
);
};
// Add old xfd template to talk pages
Task.prototype.doTask.addOldXfd = function(self) {
// Notify task is started
self.setStatus('started');
// Function to make an edit with the api.
// Mode can be 'text' (overwrite existing content) or 'appendtext' or 'prependtext'
var apiEditTalk = function (pageTitle, newWikitext, mode, redirect) {
var req = {
action: 'edit',
title: pageTitle,
section: '0',
summary: 'Old ' + config.xfd.type.toUpperCase() + ' – ' + self.discussion.nomDate +
': ' + self.inputData.getResult() + config.script.advert,
redirect: !!redirect
};
req[mode] = ( mode === 'appendtext' ) ? '\n' + newWikitext : newWikitext;
API.postWithToken( 'csrf', req )
.done( function() {
self.track('processed', true);
})
.fail( function(code, jqxhr) {
self.track('processed', false);
self.addApiError(code, jqxhr, [
'Could not edit talk page ',
extraJs.makeLink(pageTitle)
]);
} );
};
// Make wikitext of olf xfd template (non-AFD)
var makeOldxfdWikitext = function(altpage) {
result = config.xfd.wikitext.oldXfd
.replace(/__DATE__/, self.discussion.nomDate)
.replace(/__SECTION__/, self.discussion.sectionHeader)
.replace(/__RESULT__/, self.inputData.getResult())
.replace(/__FIRSTDATE__/, self.discussion.firstDate)
.replace(/__SUBPAGE__/, self.discussion.getNomSubpage());
if ( altpage ) {
result = result.replace('}}', ' |altpage='+altpage+'}}');
}
return result;
};
// Add or update oldafdmulti template in section wikitext
var makeNewWikitext = function(wikitext, pageTitle) {
//Parts of this derived from https://en.wikipedia.org/wiki/User:Mr.Z-man/closeAFD2.js
var titleObject = mw.Title.newFromText(pageTitle);
var PAGENAME = titleObject.getMain();
var SUBJECTPAGENAME = config.mw.monthNames[(titleObject.getNamespaceId()-1).toString()] +
PAGENAME;
var oldafdmulti = '{{Old AfD multi';
var numtest = /[A-z]+([0-9]+)/i;
var count = 0;
var i = 0;
var makeTemplateRegexPatt = function(templateNameRegexString, flags, prevLine) {
return new RegExp((( prevLine ) ? '\\n?' : '') +
// start template
'\\{\\{' + templateNameRegexString + '\\s*' +
// start capturing group
"(\\|" +
// account for sub-templates within the template
"(?:.|\\n)*?(?:(?:\\{\\{" +
// account for sub-sub-templates within sub-templates
"(?:.|\\n)*?(?:(?:\\{\\{" +
"(?:.|\\n)*?" +
"\\}\\})(?:.|\\n)*?)*?" +
"\\}\\})(?:.|\\n)*?)*" +
// end capturing group, end template
")\\}\\}\\n?", flags);
};
var makeParams = function(pattern) {
var params = {};
// Pattern to find `|param=value` or `|value`, where `value` can only contain a pipe
// if within square brackets (i.e. wikilinks) or braces (i.e. templates)
var partsPatt = /\|(?!(?:[^{]+}|[^\[]+]))(?:.|\s)*?(?=(?:\||$)(?!(?:[^{]+}|[^\[]+])))/g;
// Execute pattern to obtain capturing group match (at index 1 of the resulting array)
var old = pattern.exec(wikitext);
if ( old ) {
var unnamedParamCount = 0;
var parts = old[1].match(partsPatt);
for ( var i=0; i<parts.length; i++ ) {
if ( parts[i].trim() === '|' ) {
continue;
}
var equalsIndex = parts[i].indexOf('=');
if ( equalsIndex === -1 ) {
//unnamed parameter
unnamedParamCount++;
params[unnamedParamCount.toString()] = parts[i].slice(1).trim();
} else {
params[parts[i].slice(1, equalsIndex).trim()] = parts[i]
.slice(equalsIndex+1).trim();
}
}
}
return params;
};
// Find old AFDs
var oldAfdPatt = makeTemplateRegexPatt('(?:old|afd) ?(?:old|afd) ?(?:multi|full)?', 'i');
var oldAfdParams = makeParams(oldAfdPatt);
for (var p in oldAfdParams) {
oldafdmulti += ' |' + p + '=' + oldAfdParams[p];
var num = numtest.exec(p);
var res = ( num ) ? parseInt(num[1]) : 1;
if (res > count) {
count = res;
}
}
// Find old TFDs
var oldTfdPatt = makeTemplateRegexPatt('(?:old|tfd|Previous) ?(?:tfd|tfd|end)(?:full)?', 'gi');
var oldTfdTemplates = wikitext.match(oldTfdPatt);
if ( oldTfdTemplates ) {
for (i=0; i<oldTfdTemplates.length; i++) {
count++;
var oldTfdParams = makeParams(oldTfdPatt);
var oldTfdDate = oldTfdParams.date;
var oldTfdResult = oldTfdParams.result || 'keep';
var oldTfdLink = "{{subst:#ifexist:Wikipedia:Templates for deletion/Log/"+
( oldTfdParams.link || "{{subst:Date|"+oldTfdParams.date+"|ymd}}" )+
"|Wikipedia:Templates for deletion/Log/" +
( oldTfdParams.link || "{{subst:Date|"+oldTfdParams.date+"|ymd}}" )+
"#" + ( oldTfdParams['1'] || oldTfdParams.disc || "Template:"+PAGENAME )+
"|Wikipedia:Templates for discussion/Log/"+
( oldTfdParams.link || "{{subst:Date|"+oldTfdParams.date+"|ymd}}" )+
"#" + ( oldTfdParams['1'] || oldTfdParams.disc || "Template:"+PAGENAME )+
"}}";
oldafdmulti += " |date" + count + "=" + oldTfdDate +
" |result" + count + "='''" + extraJs.toSentenceCase(oldTfdResult) + "'''" +
" |link" + count + "={{canonicalurl:" + oldTfdLink + "}}";
wikitext = wikitext.replace(oldTfdTemplates[i], '');
}
}
// Find old FFDs
var oldFfdPatt = makeTemplateRegexPatt('old ?(?:f|i)fd(?:full)?', 'gi');
var oldFfdTemplates = wikitext.match(oldFfdPatt);
if ( oldFfdTemplates ) {
for (i=0; i<oldFfdTemplates.length; i++) {
count++;
var oldFfdParams = makeParams(oldFfdPatt);
var oldFfdDate = oldFfdParams.date || '';
var oldFfdResult = oldFfdParams.result || 'keep';
var oldFfdLink = "{{subst:#ifexist:Wikipedia:Images and media for deletion/"+
"{{subst:#iferror:{{subst:#time:|"+oldFfdParams.date+"}}|"+oldFfdParams.date+
"|{{subst:#time:Y F j|"+oldFfdParams.date+"}}}}"+
"|Wikipedia:Images and media for deletion/{{subst:#iferror:{{subst:#time:|"+
oldFfdParams.date+"}}|"+oldFfdParams.date+"|{{#time:Y F j|"+oldFfdParams.date+
"}}}}#File:" + ( oldFfdParams.page || PAGENAME ) +
"|{{subst:#ifexist:Wikipedia:Files for deletion/{{subst:#iferror:{{subst:#time:|"+
oldFfdParams.date+"}}|"+oldFfdParams.date+"|{{subst:#time:Y F j|"+
oldFfdParams.date+"}}}}"+
"|Wikipedia:Files for deletion/{{subst:#iferror:{{subst:#time:|"+oldFfdParams.date+"}}|"+
oldFfdParams.date+"|{{subst:#time:Y F j|"+oldFfdParams.date+"}}}}#" +
( oldFfdParams.page || SUBJECTPAGENAME )+
"|Wikipedia:Files for discussion/{{subst:#iferror:{{subst:#time:|"+oldFfdParams.date+"}}|"+
oldFfdParams.date+"|{{subst:#time:Y F j|"+oldFfdParams.date+"}}}}#" +
( oldFfdParams.page || SUBJECTPAGENAME )+
"}}}}";
oldafdmulti += " |date" + count + "=" + oldFfdDate +
" |result" + count + "='''" +
extraJs.toSentenceCase(oldFfdResult.replace(/'''/g, '')) + "'''" +
" |link" + count + "={{canonicalurl:" + oldFfdLink + "}}";
wikitext = wikitext.replace(oldFfdTemplates[i], '');
}
}
// Find old MFDs
var oldMfdPatt = makeTemplateRegexPatt('(?:old ?mfd|mfdend|mfdold)(?:full)?', 'gi');
var oldMfdTemplates = wikitext.match(oldMfdPatt);
if ( oldMfdTemplates ) {
for (i=0; i<oldMfdTemplates.length; i++) {
count++;
var oldMfdParams = makeParams(oldMfdPatt);
var oldMfdDate = oldMfdParams.date || '';
var oldMfdResult = oldMfdParams.result || 'keep';
var oldMfdLink = "Wikipedia:Miscellany for deletion/" +
( oldMfdParams.votepage || oldMfdParams.title ||
oldMfdParams.page || SUBJECTPAGENAME );
oldafdmulti += " |date" + count + "=" + oldMfdDate +
" |result" + count + "='''" +
extraJs.toSentenceCase(oldMfdResult.replace(/'''/g, '')) + "'''" +
" |link" + count + "={{canonicalurl:" + oldMfdLink + "}}";
wikitext = wikitext.replace(oldMfdTemplates[i], '');
}
}
// Find old RFDs
var oldRfdPatt = makeTemplateRegexPatt('old?(?: |-)?rfd(?:full)?', 'gi');
var oldRfdTemplates = wikitext.match(oldRfdPatt);
if ( oldRfdTemplates ) {
for (i=0; i<oldRfdTemplates.length; i++) {
count++;
var oldRfdParams = makeParams(oldRfdPatt);
var oldRfdDate = oldRfdParams.date || '';
var oldRfdResult = oldRfdParams.result || 'keep';
var oldRfdLink;
if ( oldRfdParams.rawlink ) {
oldRfdLink = oldRfdParams.rawlink.slice(2, oldRfdParams.rawlink.indexOf('|'));
} else {
oldRfdLink = "Wikipedia:Redirects for discussion/Log/" +
( oldRfdParams.page || oldRfdParams.date + "#" + SUBJECTPAGENAME );
}
oldafdmulti += " |date" + count + "=" + oldRfdDate +
" |result" + count + "='''" +
extraJs.toSentenceCase(oldRfdResult.replace(/'''/g, '')) + "'''" +
" |link" + count + "={{canonicalurl:" + oldRfdLink + "}}";
wikitext = wikitext.replace(oldRfdTemplates[i], '');
}
}
// For non-AFDs, if no old banners were found, prepend process-specific banner to content
if ( config.xfd.type !== 'afd' && count === 0 ) {
return config.xfd.wikitext.oldXfd
.replace(/__DATE__/, self.discussion.nomDate)
.replace(/__SECTION__/, self.discussion.sectionHeader)
.replace(/__RESULT__/, self.inputData.getResult())
.replace(/__FIRSTDATE__/, self.discussion.firstDate)
.replace(/__SUBPAGE__/, self.discussion.getNomSubpage()) + wikitext;
}
// Otherwise, add current discussion to oldafdmulti
count++;
var c = count.toString();
var currentResult = self.inputData.getResult();
if (count === 1) {
c = '';
} else {
currentResult = extraJs.toSentenceCase(currentResult);
}
var currentNompageParamAndValue = ( config.xfd.type === 'afd' ) ? ' |page'+c + '=' +
self.discussion.getNomSubpage() : ' |link'+c + '='+
'{{canonicalurl:' + self.discussion.getNomPageLink() + '}}';
oldafdmulti += ' |date'+c + '=' + self.discussion.nomDate +
' |result'+c+ "='''" + currentResult + "'''" + currentNompageParamAndValue + '}}';
if ( oldAfdPatt.test(wikitext) ) {
// Override the existing oldafdmulti
newtext = wikitext.replace(oldAfdPatt, oldafdmulti+'\n');
} else {
// Prepend to content ([[WP:Talk page layout]] is too complicated to automate)
newtext = oldafdmulti+'\n'+wikitext;
}
return newtext;
};
var confirmRedirectReplacement = function(talkTitle) {
OO.ui.confirm('"' + talkTitle + '" is currently a redirect. Okay ' +
'to replace with Old RFD template?')
.done( function ( confirmed ) {
if ( confirmed ) {
// Make the edit, replacing previous content
apiEditTalk(talkTitle, makeOldxfdWikitext(), 'text');
} else {
// Skip
self.addWarning([
extraJs.makeLink(talkTitle),
' skipped'
]);
self.track('processed', true);
}
} );
};
var processTalkTitles = function (result) {
var page_ids = result.query.pageids;
for ( var i=0; i < page_ids.length; i++ ) {
var talkTitle = result.query.pages[ page_ids[i] ].title;
// Check there's a corresponding nominated page
var pageObj = self.discussion.getPageByTalkTitle(talkTitle);
if ( !pageObj ) {
self.addError([
'API query result included unexpected talk page title ',
extraJs.makeLink(talkTitle),
'; this talk page will not be edited'
]);
self.track('processed', false);
continue;
}
// Check corresponding page exists
if ( !pageObj.exists ) {
self.addError([
'The subject page for ',
extraJs.makeLink(talkTitle),
' does not exist; this talk page will not be edited'
]);
self.track('processed', false);
continue;
}
// Check redirect status
if ( config.xfd.type !== 'afd' && result.query.pages[ page_ids[i] ].redirect === '' ) {
// Is a redirect...
if ( config.xfd.type === 'rfd' ) {
// for RFD, ask what to do
confirmRedirectReplacement(talkTitle);
} else if ( config.xfd.type === 'mfd' ) {
// For MFD, edit the redirect's target page, using the altpage parameter
apiEditTalk(talkTitle, makeOldxfdWikitext(pageObj.getTitle()),
'prependtext', true);
} else {
// For other venues, append rather than prepend old xfd template
apiEditTalk(talkTitle, makeOldxfdWikitext(), 'appendtext');
}
} else {
// Not a redirect. Attempt to find and consolidate existing banners
var oldwikitext = '';
if ( parseInt(page_ids[i]) > 0 ) {
oldwikitext = result.query.pages[ page_ids[i] ].revisions[0]['*'];
}
apiEditTalk(talkTitle, makeNewWikitext(oldwikitext, talkTitle), 'text');
}
}
};
// Get talk pages
var talkTitles = self.discussion.getTalkTitles(self.pages);
if ( talkTitles.length === 0 ) {
self.addWarning('none found');
self.setStatus('done');
} else {
// get talk page redirect status from api
self.setupTracking('processed', talkTitles.length);
API.get( {
action: 'query',
prop: 'revisions|info',
rvprop: 'content',
rvsection: '0',
titles: talkTitles,
indexpageids: 1
} )
.done( processTalkTitles )
.fail( function(code, jqxhr) {
self.addApiError(code, jqxhr, 'Could not read contents of talk page' +
( talkTitles.length > 1 ) ? 's' : '');
self.setStatus('failed');
} );
}
};
Task.prototype.doTask.removeNomTemplates = function(self, mergeWikitext) {
// Notify task is started
self.setStatus('started');
apiEditPage = function(pageTitle, updatedWikitext) {
API.postWithToken( 'csrf', {
action: 'edit',
title: pageTitle,
text: updatedWikitext,
summary: config.xfd.type.toUpperCase() + ' closed as ' +
self.inputData.getResult() + config.script.advert
} )
.done( function() {
self.track('edit', true);
} )
.fail( function(code, jqxhr) {
self.track('edit', false);
self.addApiError(code, jqxhr, [
'Could not edit page ',
extraJs.makeLink(pageTitle)
]);
} );
};
var processPages = function (result) {
//Get old wikitext, or make error if page doesn't exist, or if page is in wrong namespace
var page_ids = result.query.pageids;
for (var i = 0; i < page_ids.length; i++) {
var pageTitle = result.query.pages[ page_ids[i] ].title;
// Check there's a corresponding nominated page
var pageObj = self.discussion.getPageByTitle(pageTitle, {'moduledocs': true});
if ( !pageObj ) {
self.addError([
'API query result included unexpected title ',
extraJs.makeLink(pageTitle),
'; this page will not be edited'
]);
self.track('edit', false);
continue;
}
// Check that page exists
if ( parseInt(page_ids[i]) < 0 ) {
self.addError([
extraJs.makeLink(pageTitle),
' does not exist and will not be edited'
]);
self.track('edit', false);
continue;
}
// Existing wikitext
var oldWikitext = result.query.pages[ page_ids[i] ].revisions[0]['*'];
// Start building updated wikitext
var updatedWikitext = '';
// For merging, unless the page is itself a merge target, prepend the mergeWikitext
if (
mergeWikitext != null &&
$.inArray(pageTitle, self.inputData.getTargetsArray('merge')) === -1
) {
updatedWikitext = mergeWikitext[pageTitle];
}
// Remove nom template if present, or warn if not found
if ( config.xfd.hasNomTemplate(oldWikitext) ) {
updatedWikitext += config.xfd.removeNomTemplate(oldWikitext);
} else {
self.addWarning([
'Nomination template not found on page ',
extraJs.makeLink(pageTitle)
]);
if ( updatedWikitext === '' ) {
// Skip - nothing to change
self.track('edit', false);
continue;
} else {
updatedWikitext += oldWikitext;
}
}
// Make the edit
apiEditPage(pageTitle, updatedWikitext);
}
};
//get each page's wikitext through api
var pageTitles = self.discussion.getPageTitles(self.pages, {'moduledocs': true});
if ( pageTitles.length === 0 ) {
self.addWarning('none found');
self.setStatus('failed');
return;
}
self.setupTracking(
'edit',
pageTitles.length,
( mergeWikitext ) ? function() { this.track('alldone', true); } : null,
( mergeWikitext ) ? function() { this.track('alldone', false); } : null
);
API.get( {
action: 'query',
titles: pageTitles.join('|'),
prop: 'revisions',
rvprop: 'content',
indexpageids: 1,
rawcontinue: ''
} )
.done( processPages )
.fail( function(code, jqxhr) {
self.addApiError(code, jqxhr, 'Could not read contents of nominated page' +
( pageTitles.length > 1 ) ? 's' : '');
self.setStatus('failed');
} );
};
Task.prototype.doTask.addMergeTemplates = function(self) {
// Notify task is started
self.setStatus('started');
// Edit function
editTargetTalk = function(pageTitle, prependWikitext) {
API.postWithToken( 'csrf', {
action: 'edit',
title: pageTitle,
prependtext: prependWikitext,
summary: '[[:' + self.discussion.getNomPageLink() + ']] closed as ' +
self.inputData.getResult() + config.script.advert
} )
.done( function() {
self.track('editTargetTalk', true);
})
.fail( function(code, jqxhr) {
self.track('editTargetTalk', false);
self.addApiError(code, jqxhr, [
'Could not edit page ',
extraJs.makeLink(pageTitle)
]);
} );
};
// Get targets and their talk pages
var targets = extraJs.uniqueArray(self.inputData.getTargetsArray('merge'));
self.setupTracking('alldone', 2);
self.setupTracking(
'editTargetTalk',
targets.length,
function() { this.track('alldone', true); },
function() { this.track('alldone', false); }
);
// Strings for merge templates
var debate = self.discussion.getNomSubpage();
var today = new Date();
var curdate = today.getUTCDate().toString() + ' ' + config.mw.monthNames[today.getUTCMonth()] + ' ' +
today.getUTCFullYear().toString();
// Object to hold the 'merge to' template for each page
var mergetoWikitext = {};
// For each target...
for ( var i=0; i<targets.length; i++ ) {
// Pages to be merged to target
var mergeTitles;
if ( self.inputData.multimode ) {
mergeTitles = self.inputData.getPagesToBeMerged(targets[i]);
} else {
mergeTitles = self.discussion.getPageTitles(self.pages);
}
// Make 'merge to' template for pages to be merged
var mergetoTemplate = config.xfd.wikitext.mergeTo
.replace(/__TARGET__/, targets[i])
.replace(/__DEBATE__/, debate)
.replace(/__DATE__/, curdate)
.replace(/__TARGETTALK__/, Page.newFromText(targets[i]).getTalk());
// Make 'merge from' template for the target's talk page
var mergefromTemplates = [];
for (var ii=0; ii<mergeTitles.length; ii++) {
mergefromTemplates.push(
config.xfd.wikitext.mergeFrom
.replace(/__NOMINATED__/, mergeTitles[ii])
.replace(/__DEBATE__/, debate)
.replace(/__DATE__/, curdate)
);
// Add 'merge to' template to holding object
mergetoWikitext[mergeTitles[ii]] = mergetoTemplate;
}
// Check if the target is one of the nominated pages
if ( $.inArray(targets[i], self.discussion.getPageTitles(self.pages)) !== -1 ) {
// No need to add 'merge from' template - this was a nominated page, and will have an
// old xfd template put on its talkpage specify the merge result.
self.track('editTargetTalk', false);
continue;
} else {
// Edit target talkpage
editTargetTalk(Page.newFromText(targets[i]).getTalk(), mergefromTemplates.join(''));
}
}
// Replace nomination templates with 'merge to' templates
self.doTask.removeNomTemplates(self, mergetoWikitext);
};
Task.prototype.doTask.disambiguate = function(self) {
// Notify task is started
self.setStatus('started');
apiEditPage = function(pageTitle, updatedWikitext) {
API.postWithToken( 'csrf', {
action: 'edit',
title: pageTitle,
text: updatedWikitext,
summary: config.xfd.type.toUpperCase() + ' closed as ' +
self.inputData.getResult() + config.script.advert
} )
.done( function() {
self.track('edit', true);
} )
.fail( function(code, jqxhr) {
self.track('edit', false);
self.addApiError(code, jqxhr, [
'Could not edit page ',
extraJs.makeLink(pageTitle)
]);
} );
};
var processPages = function (result) {
//Get old wikitext, or make error if page doesn't exist, or if page is in wrong namespace
var page_ids = result.query.pageids;
for (var i = 0; i < page_ids.length; i++) {
var pageTitle = result.query.pages[ page_ids[i] ].title;
// Check there's a corresponding nominated page
var pageObj = self.discussion.getPageByTitle(pageTitle);
if ( !pageObj ) {
self.addError([
'API query result included unexpected title ',
extraJs.makeLink(talkTitle),
'; this page will not be edited'
]);
self.track('edit', false);
continue;
}
// Check that page exists
if ( parseInt(page_ids[i]) < 0 ) {
self.addError([
extraJs.makeLink(talkTitle),
' does not exist and will not be edited'
]);
self.track('edit', false);
continue;
}
var oldWikitext = result.query.pages[ page_ids[i] ].revisions[0]['*'];
var updatedWikitext = '';
if ( config.xfd.regex.fullNomTemplate.test(oldWikitext) ) {
updatedWikitext = oldWikitext.replace(config.xfd.regex.fullNomTemplate, '').trim();
} else {
self.addWarning([
'Nomination template not found on page ',
extraJs.makeLink(pageTitle)
]);
updatedWikitext = oldWikitext.replace(/^#REDIRECT/mi, '*');
}
if ( !/(?:disambiguation|disambig|dab|Mil-unit-dis|Numberdis)[^{]*}}/i.test(updatedWikitext) ) {
updatedWikitext += '\n{{Disambiguation cleanup|{{subst:DATE}}}}';
updatedWikitext.trim();
}
apiEditPage(pageTitle, updatedWikitext);
}
};
//get each page's wikitext through api
var pageTitles = self.discussion.getPageTitles(self.pages);
if ( pageTitles.length === 0 ) {
self.addWarning('none found');
self.setStatus('failed');
return;
}
self.setupTracking('edit', pageTitles.length);
API.get( {
action: 'query',
titles: pageTitles.join('|'),
prop: 'revisions',
rvprop: 'content',
indexpageids: 1,
rawcontinue: ''
} )
.done( processPages )
.fail( function(code, jqxhr) {
self.addApiError(code, jqxhr, 'Could not read contents of nominated page' +
( pageTitles.length > 1 ) ? 's' : '');
self.setStatus('failed');
} );
};
Task.prototype.doTask.peformDeletion = function(self) {
// Notify task is started
self.setStatus('started');
// Delete with the api
apiDeletePage = function(pageTitle) {
API.postWithToken( 'csrf', {
action: 'delete',
title: pageTitle,
reason: '[[' + self.discussion.getNomPageLink() + ']]' + config.script.advert
} )
.done( function() {
self.track('del', true);
} )
.fail( function(code, jqxhr) {
self.track('del', false);
self.addApiError(code, jqxhr, [
'Could not delete page ',
extraJs.makeLink(pageTitle)
]);
} );
};
var pages = self.pages || self.discussion.pages;
self.setupTracking('del', pages.length);
// For each page, check it exists, then delete with api or add warning
for ( var i=0; i<pages.length; i++ ) {
if ( pages[i].exists ) {
apiDeletePage(pages[i].getTitle());
} else {
self.addWarning([
extraJs.makeLink(pages[i].getTitle()),
' skipped: does not exist (may have already been deleted by others)'
]);
self.track('del', false);
}
}
};
Task.prototype.doTask.addBeingDeleted = function(self) {
// Notify task is started
self.setStatus('started');
// Merge targets and pages to be merged (if any)
var mergeTargets = [];
var mergeTitles = [];
if ( self.inputData.inPageActions('merge') || self.inputData.result === 'merge' ) {
mergeTargets = self.inputData.getTargetsArray('merge');
mergeTitles = self.discussion.getPageTitles(
(self.inputData.multimode ) ? self.inputData.getPages('merge') : self.pages
);
}
// Edit with the Api
var apiEditTemplate = function(pageTitle, newWikitext, editsum) {
API.postWithToken( 'csrf', {
action: 'edit',
title: pageTitle,
text: newWikitext,
summary: editsum
} )
.done( function() {
self.track('edit', true);
} )
.fail( function(code, jqxhr) {
self.track('edit', false);
self.addApiError(code, jqxhr, [
'Could not edit page ',
extraJs.makeLink(pageTitle)
]);
} );
};
var processTemplates = function (result) {
var page_ids = result.query.pageids;
for (var i = 0; i < page_ids.length; i++) {
var pageTitle = result.query.pages[ page_ids[i] ].title;
// Check there's a corresponding nominated page
var pageObj = self.discussion.getPageByTitle(pageTitle, {'moduledocs': true});
if ( !pageObj ) {
self.addError([
'API query result included unexpected page ',
extraJs.makeLink(pageTitle),
'; this page will not be edited'
]);
self.track('edit', false);
continue;
}
// Check that page exists
if ( parseInt(page_ids[i]) < 0 ) {
self.addError([
extraJs.makeLink(pageTitle),
' does not exist (may have been deleted)'
]);
self.track('edit', false);
continue;
}
// Replace {Template for discussion/dated} or {Tfm/dated} (which
// may or may not be noincluded)
var isModule = ( pageTitle.indexOf('Module:') === 0 );
var inclusionTag = ( isModule ) ? 'includeonly' : 'noinclude';
var oldWikitext = result.query.pages[ page_ids[i] ].revisions[0]['*'];
var updatedWikitext = '';
var editsum = '';
if ( $.inArray(pageTitle, mergeTargets) !== -1 ) {
// If this is a merge target, don't add anything - just remove nom template
updatedWikitext = config.xfd.removeNomTemplate(oldWikitext);
editsum = '[[' + self.discussion.getNomPageLink() + ']] closed as ' +
self.inputData.getResult() + config.script.advert;
} else if ( self.inputData.holdcell === 'ready' ) {
// If holding cell section is 'ready for deletion', tag for speedy deletion
updatedWikitext = '<' + inclusionTag + '>{{Db-xfd|fullvotepage=' +
self.discussion.getNomPageLink() + '}}</' + inclusionTag + '>' +
config.xfd.removeNomTemplate(oldWikitext);
editsum = '[[WP:G6|G6]] Speedy deletion nomination, per [[' +
self.discussion.getNomPageLink() + ']]' + config.script.advert;
} else {
// Add Being deleted template, remove nom template
updatedWikitext = '<' + inclusionTag + '>{{Being deleted|' + self.discussion.nomDate + '|' +
mw.util.wikiUrlencode(self.discussion.sectionHeader);
if ( $.inArray(pageTitle, mergeTitles) !== -1 ) {
// If being merged, set merge parameter
updatedWikitext += '|merge=' + self.inputData.getTarget(pageTitle);
}
updatedWikitext += '}}</' + inclusionTag + '>' + config.xfd.removeNomTemplate(oldWikitext);
editsum = 'Per [[' + self.discussion.getNomPageLink() + ']], '+
'added {{being deleted}}' + config.script.advert;
}
// Make the edit
apiEditTemplate(pageTitle, updatedWikitext, editsum);
}
};
var templateTitles = self.discussion.getPageTitles(self.pages, {'moduledocs': true});
self.setupTracking('edit', templateTitles.length);
//get page wikitext through api
API.get( {
action: 'query',
titles: templateTitles.join('|'),
prop: 'revisions',
rvprop: 'content',
indexpageids: 1,
rawcontinue: ''
} )
.done( processTemplates )
.fail( function(code, jqxhr) {
self.addApiError(code, jqxhr, 'Could not read contents of nominated page' +
( templateTitles.length > 1 ) ? 's' : '');
self.setStatus('failed');
} );
};
Task.prototype.doTask.addToHoldingCell = function(self) {
// Notify task is started
self.setStatus('started');
holdCellSections = [
self.inputData.holdcell || null,
self.inputData.mergeHoldcell || null
]
.filter(function(v){ return !!v; })
.map(function(v){ return config.xfd.holdingCellSectionNumber[v]; });
self.setupTracking('processed', holdCellSections.length);
//Function to make an edit
var apiEditHoldingCell = function(newWikitext, sectionNum) {
API.postWithToken( 'csrf', {
action: 'edit',
title: config.xfd.subpagePath + 'Holding cell',
section: sectionNum,
text: newWikitext,
summary: 'Listing template(s) per [[:' + self.discussion.getNomPageLink() + ']]' +
config.script.advert
} )
.done( function() {
self.track('processed', true);
})
.fail( function(code, jqxhr) {
self.track('processed', false);
self.addApiError(code, jqxhr, 'Could not add templates to holding cell');
} );
};
var processHoldingCellPage = function (result) {
var p_id = result.query.pageids;
// Get page contents, make all headings level 3 so sections can be counted
var p_contents = result.query.pages[ p_id ].revisions[0]['*'].replace(/====/gi, '===');
var sectionsArray = p_contents.split('===');
for ( var i=0; i<holdCellSections.length; i++) {
var isMergeSection = (4 <= holdCellSections[i]) && ( holdCellSections[i] <= 10);
var listingPages;
if ( !self.inputData.multimode ) {
listingPages = self.discussion.pages;
} else if ( isMergeSection ) {
listingPages = self.inputData.getPages('merge');
} else {
listingPages = self.inputData.getPages('del');
}
var tfdlTemplates = '';
for (ii = 0; ii < listingPages.length; ii++) {
//Check namespace and existance
if ( !listingPages[ii].hasCorrectNamespace() ) {
self.addError([
extraJs.makeLink(listingPages[ii]),
' is not in the ' + config.mw.namespaces[config.xfd.ns_number[0].toString()] +
' namespace, and will not be listed at the holding cell'
]);
} else if ( !listingPages[ii].exists ) {
self.addError([
extraJs.makeLink(listingPages[ii]),
' does not exist, and will not be listed at the holding cell'
]);
} else {
tfdlTemplates += '*{{tfdl|' + listingPages[ii].getMain() +
'|' + self.discussion.nomDate +
'|section=' + self.discussion.sectionHeader +
(( holdCellSections[i] === 14 ) ? '|delete=1' : '') +
(( listingPages[ii].getNamespaceId() === 828 ) ? '|ns=Module' : '') +
'}}\n';
}
}
if ( tfdlTemplates === '' ) {
// If all don't exist or are in wrong namespace, then there's nothing to do
self.track('processed', false);
continue;
}
// Make new section wikitext
var heading = sectionsArray[(holdCellSections[i]*2-1)];
var contents = sectionsArray[(holdCellSections[i]*2)];
// Remove "* ''None currently''" except if inside a <!--html comment-->
contents = contents.replace(/\n*^\*\s*''None currently''\s*$(?![^<]*?-->)/gim, '');
// Merge subsections have level-4 headings
var headingLevel = ( (4 <= holdCellSections[i]) &&
( holdCellSections[i] <= 10) ) ? '====' : '===';
var newWikitext = headingLevel + heading + headingLevel + '\n' +
contents.trim() + '\n' + tfdlTemplates;
// If this isn't the first iteration of for-loop, wait a bit to avoid self-edit conflict
if ( i > 0 ) {
var start = new Date().getTime();
var waited = 0;
while ( waited < 1000 ) {
waited = new Date().getTime() - start;
}
}
apiEditHoldingCell(newWikitext, holdCellSections[i]);
}
};
//get holding cell contents
API.get( {
action: 'query',
titles: config.xfd.subpagePath + 'Holding cell',
prop: 'revisions',
rvprop: 'content',
indexpageids: 1,
rawcontinue: ''
} )
.done( processHoldingCellPage )
.fail( function(code, jqxhr) {
self.addApiError(code, jqxhr, 'Could not read contents of holding cell');
self.setStatus('failed');
} );
};
Task.prototype.doTask.deleteTalk = function(self) {
// Notify task is started
self.setStatus('started');
// Delete with the api
apiDeletePage = function(pageTitle) {
API.postWithToken( 'csrf', {
action: 'delete',
title: pageTitle,
reason: '[[WP:CSD#G8|G8]]: Talk page of deleted page.' + config.script.advert
} )
.done( function() {
self.track('del', true);
} )
.fail( function(code, jqxhr) {
self.track('del', false);
self.addApiError(code, jqxhr, [
'Could not delete page ',
extraJs.makeLink(pageTitle)
]);
} );
};
// Get talk pages
var talkTitles = self.discussion.getTalkTitles(self.pages);
if ( talkTitles.length === 0 ) {
self.addWarning('none found');
self.setStatus('skipped');
} else {
// get talk page redirect status from api
self.setupTracking('del', talkTitles.length);
//For each talk page, check that it exists, then delete it
for ( var i=0; i < talkTitles.length; i++ ) {
var subjectPage = self.discussion.getPageByTalkTitle(talkTitles[i]);
if ( subjectPage.talkExists ) {
apiDeletePage(talkTitles[i]);
} else {
self.addWarning([
extraJs.makeLink(talkTitles[i]),
' skipped: does not exist (may have already been deleted by others)'
]);
self.track('del', false);
}
}
}
};
Task.prototype.doTask.tagTalkWithSpeedy = function(self) {
// Notify task is started
self.setStatus('started');
var apiTagPage = function(pageTitle) {
API.postWithToken( 'csrf', {
action: 'edit',
title: pageTitle,
prependtext: '{{Db-talk}}\n',
summary: '[[WP:G8|G8]] Speedy deletion nomination, per [[:' +
self.discussion.getNomPageLink() + ']]' + config.script.advert
} )
.done( function() {
self.track('tag', true);
} )
.fail( function(code, jqxhr) {
self.track('tag', false);
self.addApiError(code, jqxhr, [
'Could not delete page ',
extraJs.makeLink(pageTitle)
]);
} );
};
// Get talk pages
var talkTitles = self.discussion.getTalkTitles(self.pages);
if ( talkTitles.length === 0 ) {
self.addWarning('none found');
self.setStatus('skipped');
} else {
// get talk page redirect status from api
self.setupTracking('tag', talkTitles.length);
//For each talk page, check that it exists, then tag it
for ( var i=0; i < talkTitles.length; i++ ) {
var subjectPage = self.discussion.getPageByTalkTitle(talkTitles[i]);
if ( subjectPage.talkExists ) {
apiTagPage(talkTitles[i]);
} else {
self.addWarning([
extraJs.makeLink(talkTitles[i]),
' skipped: does not exist (may have already been deleted by others)'
]);
self.track('tag', false);
}
}
}
};
Task.prototype.doTask.deleteRedirects = function(self) {
// If unlinking backlinks, wait until that task has the info it needs
$.when(self.discussion.taskManager.dfd.ublQuery).then(function(){
// Notify task is started
self.setStatus('started');
self.setupTracking('alldone', 2);
var redirectTitles = [];
var redirectTalkPageIds = [];
// Delete redirect with the api
apiDeleteRedir = function(_i, pageTitle) {
API.postWithToken( 'csrf', {
action: 'delete',
title: pageTitle,
reason: 'Delete redirect: [[' + self.discussion.getNomPageLink() + ']] closed as ' +
self.inputData.getResult() + config.script.advert
} )
.done( function() {
self.track('delRedir', true);
} )
.fail( function(code, jqxhr) {
self.track('delRedir', false);
self.addApiError(code, jqxhr, [
'Could not delete redirect ',
extraJs.makeLink(pageTitle)
]);
} );
};
// Delete redirect talkpage with the api
apiDeleteRedirTalk = function(_i, talkpageid) {
API.postWithToken( 'csrf', {
action: 'delete',
pageid: talkpageid,
reason: '[[WP:CSD#G8|G8]]: Talk page of deleted page.' + config.script.advert
} )
.done( function() {
self.track('delRedirTalk', true);
} )
.fail( function(code, jqxhr) {
self.track('delRedirTalk', false);
self.addApiError(code, jqxhr, [
'Could not delete ',
$('<a>').attr({
'href':'https://en.wikipedia.org/wiki/?curid='+talkpageid,
'target':'_blank'
}).text('redirect talk page <'+talkpageid+'>')
]);
} );
};
// If okay to delete redirects
var doDeleteRedirects = function() {
// Set tracking
self.setupTracking(
'delRedir',
redirectTitles.length,
function() { this.track('alldone', true); },
function() { this.track('alldone', false); }
);
// Delete each redirect
$.each(redirectTitles, apiDeleteRedir);
// Check if talk pages were found
if ( redirectTalkPageIds.length === 0 ) {
self.track('alldone', true);
return;
}
// Set tracking
self.setupTracking(
'delRedirTalk',
redirectTalkPageIds.length,
function() { this.track('alldone', true); },
function() { this.track('alldone', false); }
);
// Delete each talk page
$.each(redirectTalkPageIds, apiDeleteRedirTalk);
};
// If user cancelled
var doSkipRedirects = function() {
self.addWarning('Cancelled by user');
self.setStatus('skipped');
};
var processRedirects = function(result) {
if ( !result.query || !result.query.pages ) {
// No results
self.addWarning('none found');
self.setStatus('skipped');
return;
}
// Gather redirect titles into array
$.each(result.query.pages, function(_i, info) {
redirectTitles.push(info.title);
if ( info.talkid ) { redirectTalkPageIds.push(info.talkid); }
});
// Continue query if needed
if ( result.continue ) {
apiQuery($.extend(query, result.continue));
return;
}
// Check if redirects were found
if ( redirectTitles.length === 0 ) {
self.addWarning('none found');
self.setStatus('skipped');
return;
}
// Warn if there will be mass action, allow user to cancel
if ( redirectTitles.length >= 10 ) {
var massActWarningCallback = function(action) {
if ( action === 'accept' ) {
doDeleteRedirects();
} else if ( action === 'show' ) {
extraJs.multiButtonConfirm(
'Warning',
'Mass action to be peformed: delete '+ redirectTitles.length +
' redirects:<ul><li>[[' + redirectTitles.join(']]</li><li>[[') + ']]</li></ul>',
[
{ label:'Cancel', flags:'safe' },
{ label:'Delete redirects', action:'accept', flags:'progressive' }
],
massActWarningCallback,
{'verbose': true, 'unescape': true, 'wikilinks':true}
);
} else {
doSkipRedirects();
}
};
extraJs.multiButtonConfirm(
'Warning',
'Mass action to be peformed: delete '+ redirectTitles.length + ' redirects.',
[
{ label:'Cancel', flags:'safe' },
{ label:'View redirects...', action:'show' },
{ label:'Delete redirects', action:'accept', flags:'progressive' }
],
massActWarningCallback
);
} else {
doDeleteRedirects();
}
};
// Get redirects
var query = {
action: 'query',
titles: self.discussion.getPageTitles(self.pages).join('|'),
generator: 'redirects',
grdlimit: 500,
prop: 'info',
inprop: 'talkid'
};
var apiQuery = function(q) {
API.get( q )
.done( processRedirects )
.fail( function(code, jqxhr) {
self.addApiError(code, jqxhr, 'Could not obtain redirects');
self.setStatus('failed');
} );
};
apiQuery(query);
});
};
Task.prototype.doTask.unlinkBacklinks = function(self) {
// Notify task is started
self.setStatus('started');
var pageTitles = self.discussion.getPageTitles(self.pages);
var redirectTitles = [];
var ignoreTitles = [
'Template:WPUnited States Article alerts',
'Template:Article alerts columns',
'Template:Article alerts columns/doc'
];
var blresults = [];
var iuresults = [];
//convert results (arrays of objects) to titles (arrays of strings), removing duplicates
var flattenToTitles = function(arr) {
return arr.reduce(
function(a, b) {
if ( b.redirlinks ) {
if ( $.inArray(b.title, redirectTitles) === -1 ) {
redirectTitles.push(b.title);
}
return a.concat(
b.redirlinks.reduce(
function(aa, bb) {
if (
$.inArray(bb.title, a) !== -1 ||
$.inArray(bb.title, pageTitles) !== -1 ||
$.inArray(bb.title, ignoreTitles)!== -1
) {
return aa;
} else {
return aa.concat(bb.title);
}
},
[]
)
);
} else if (
b.redirect === '' ||
$.inArray(b.title, a) !== -1 ||
$.inArray(b.title, pageTitles) !== -1 ||
$.inArray(b.title, ignoreTitles)!== -1
) {
return a;
} else {
return a.concat(b.title);
}
},
[]
);
};
var apiEditPage = function(pageTitle, newWikitext) {
API.postWithToken( 'csrf', {
action: 'edit',
title: pageTitle,
text: newWikitext,
summary: '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)
]);
} );
};
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, so make the edit
apiEditPage(pageTitle, wikitext);
} else {
// 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
extraJs.multiButtonConfirm(
'Review unlinked list item',
'[[' + pageTitle +
( ( heading ) ? '#' +
mw.util.wikiUrlencode(
heading.replace(/\[\[([^\|\]]*?)\|([^\]]*?)\]\]/, '$2')
.replace(/\[\[([^\|\]]*?)\]\]/, '$1')
) + ']]' : ']]' ) +
': ' +
'<pre>' + toReview[1] + '</pre>',
[{ label:'Keep item', action:'keep' }, { label:'Remove item', action:'remove'}],
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
checkListItems(pageTitle, wikitext);
},
{ verbose:true, unescape:true, wikilinks:true }
);
}
};
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
$.each(result.query.pages, function(_i, page) {
var oldWikitext = page.revisions[0]['*'];
var newWikitext = extraJs.unlink(
oldWikitext,
pageTitles.concat(redirectTitles),
page.ns,
!!page.categories
);
if ( oldWikitext !== newWikitext ) {
checkListItems(page.title, newWikitext);
} else {
self.addWarning(['Skipped ',
extraJs.makeLink(page.title),
' (no direct links)'
]);
self.track('unlink', false);
}
});
};
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 Assisted editing guidelines '+
'(WP:ASSISTED)</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>';
extraJs.multiButtonConfirm(
heading,
para + list,
[
{ label: 'Cancel', flags: 'safe' },
{ label: 'Remove backlinks', action: 'accept', flags: 'progressive' }
],
function(action) {
if ( action ) {
var unlinkTitles = iuresults.concat(blresults);
self.setupTracking('unlink', unlinkTitles.length);
// 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');
}
},
{'verbose': true, 'unescape': true, 'wikilinks': true}
);
};
// 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
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
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.prototype.doTask.redirect = function(self) {
// Notify task is started
self.setStatus('started');
var pageTitles = self.discussion.getPageTitles(self.pages);
var deleteFirst = self.inputData.deleteFirst;
var softRedirect = self.inputData.result === 'soft redirect';
var rcats = self.inputData.rcats;
var rcatshell = ( rcats ) ? "\n\n{{Rcat shell|\n" + rcats + "\n}}" : '';
self.setupTracking('redir', pageTitles.length);
// Make a redirect
apiMakeRedirect = function(pageTitle) {
var newWikitext;
if ( pageTitle.indexOf('Module:') === 0 ) {
var targetPage = self.inputData.getTargetLink(pageTitle, true);
if ( targetPage.indexOf('Module:') !== 0 ) {
self.track('redir', false);
self.addError([
'Could not redirect ',
extraJs.makeLink(pageTitle),
' because target (',
extraJs.makeLink(targetPage),
') is not a module'
], true);
return false;
}
newWikitext = 'return require( "' + targetPage + '" )';
} else if ( softRedirect ) {
newWikitext = '{{Soft redirect|' + self.inputData.getTargetLink(pageTitle, true) +
'}}' + rcatshell;
} else {
newWikitext = "#REDIRECT " + self.inputData.getTargetLink(pageTitle) + rcatshell;
}
API.postWithToken( 'csrf', {
action: 'edit',
title: pageTitle,
text: newWikitext,
summary: '[[:' + self.discussion.getNomPageLink() + ']] closed as ' +
self.inputData.getResult() + config.script.advert
} )
.done( function() {
self.track('redir', true);
} )
.fail( function(code, jqxhr) {
self.track('redir', false);
self.addApiError(code, jqxhr, [
'Could not edit page ',
extraJs.makeLink(pageTitle)
]);
} );
};
// Delete before redirecting
apiDelAndRedir = function(pageTitle) {
API.postWithToken( 'csrf', {
action: 'delete',
title: pageTitle,
reason: '[[' + self.discussion.getNomPageLink() + ']]' + config.script.advert
} )
.done( function() {
apiMakeRedirect(pageTitle);
} )
.fail( function(code, jqxhr) {
self.track('redir', false);
self.addApiError(code, jqxhr, [
'Could not delete page ',
extraJs.makeLink(pageTitle)
]);
} );
};
// For each page, check that it isn't a target, then redirect, or delete and redirect
for ( var i=0; i<pageTitles.length; i++ ) {
if ( $.inArray(pageTitles[i], self.inputData.getTargetsArray('redirect')) !== -1 ) {
//Skip (don't make a page redirect to itself)
self.track('redir', false);
} else if ( deleteFirst ) {
apiDelAndRedir(pageTitles[i]);
} else {
apiMakeRedirect(pageTitles[i]);
}
}
};
Task.prototype.doTask.removeCircularLinks = function(self) {
// Notify task is started
self.setStatus('started');
var pageTitles = self.discussion.getPageTitles();
var targetTitles = self.inputData.getTargetsArray('redirect');
self.setupTracking('uncircle', targetTitles.length);
// Edit with the Api
var apiEditPage = function(pageTitle, newWikitext) {
API.postWithToken( 'csrf', {
action: 'edit',
title: pageTitle,
text: newWikitext,
summary: 'Unlinking circular redirects: [[:' + self.discussion.getNomPageLink() +
']] closed as ' + self.inputData.getResult() + config.script.advert
} )
.done( function() {
self.track('uncircle', true);
} )
.fail( function(code, jqxhr) {
self.track('uncircle', false);
self.addApiError(code, jqxhr, [
'Could not edit page ',
extraJs.makeLink(pageTitle)
]);
} );
};
processTargets = function (result) {
$.each(result.query.pages, function(_i, page) {
console.log('_i = ' + _i + '\npage.title = ' + page.title);
var oldWikitext = page.revisions[ 0 ][ '*' ];
// Don't remove selflinks
var unlinkPageTitles = pageTitles.filter(function(t){
return t !== page.title;
});
var newWikitext = extraJs.unlink(oldWikitext, unlinkPageTitles);
// Check if any changes need to be made; if redirect target is a nominated page, remove
// nomination template and adjust edit summary
if ( newWikitext === oldWikitext ) {
// No links to unlink
self.addWarning('none found');
self.track('uncircle', false);
} else if ( $.inArray(page.title, pageTitles) !== -1 ) {
// Target is one of the nominated pages - also remove nom template if present
newWikitext = config.xfd.removeNomTemplate(newWikitext);
apiEditPage(page.title, newWikitext);
} else {
apiEditPage(page.title, newWikitext);
}
});
};
// Get the wikitext of each target
API.get( {
action: 'query',
titles: targetTitles.join('|'),
prop: 'revisions',
rvprop: 'content',
indexpageids: 1,
rawcontinue: ''
} )
.done( processTargets )
.fail( function(code, jqxhr) {
self.track('redir', false);
self.addApiError(code, jqxhr,
'Could not read contents of redirect target' + ( targetTitles.length > 1 ) ? 's' : '');
} );
};
// --- Relisting tasks ---
Task.prototype.doTask.getRelistInfo = function(self) {
// Notify task is started
self.setStatus('started');
self.setupTracking('done', 1);
var now = new Date();
var today = now.getUTCFullYear() + ' ' + config.mw.monthNames[now.getUTCMonth()] +
' ' + now.getUTCDate();
var todaysLogpage = config.xfd.path + today;
// Set log page link in task message
if ( config.xfd.type !== 'mfd' ) {
self.discussion.taskManager.getTaskByName('updateNewLogPage').setDescription([
'Adding to ',
extraJs.makeLink(todaysLogpage, "today's log page")
]);
}
var processAfdLogpages = function(result) {
// Get wikitext
var newlogtext = '';
var oldlogtext = '';
var ids = result.query.pageids;
// Check how many log pages were found
if ( ids.length === 1 ) {
// Abort if only one log page found
self.discussion.taskManager.abortTasks(
'discussion already transcluded to today\'s log page'
);
return;
}
// Identify log pages
if ( result.query.pages[ ids[0] ].title === todaysLogpage ) {
// ids[0] is the new log page
newlogtext = result.query.pages[ ids[0] ].revisions[0]['*'];
oldlogtext = result.query.pages[ ids[1] ].revisions[0]['*'];
} else {
// ids[0] is the old log page
newlogtext = result.query.pages[ ids[1] ].revisions[0]['*'];
oldlogtext = result.query.pages[ ids[0] ].revisions[0]['*'];
}
// Abort if already relisted
var t = mw.RegExp.escape(self.discussion.nomPage);
var oldPatt = new RegExp('<!-- ?\\{\\{' + t + '\\}\\} ?-->', 'i');
var newPatt = new RegExp('\\{\\{' + t + '\\}\\}', 'i');
if ( oldPatt.test(oldlogtext) || newPatt.test(newlogtext) ) {
self.discussion.taskManager.abortTasks('discussion has been relisted already');
return;
}
// Updated new log wikitext:
var newlogreg = new RegExp('<!-- Add new entries to the TOP of the following list -->','i');
self.discussion.taskManager.relistInfo.newLogWikitext = newlogtext.replace(
newlogreg,
'<!-- Add new entries to the TOP of the following list -->\n{{' +
self.discussion.nomPage + '}}<!--Relisted-->'
);
// Updated old log wikitext:
var oldlogreg = new RegExp("(\\{\\{" + t + "\\}\\})", 'i' );
self.discussion.taskManager.relistInfo.oldlogTransclusion = oldlogreg.test(oldlogtext);
self.discussion.taskManager.relistInfo.oldLogWikitext = oldlogtext.replace(
oldlogreg, '<!-- $1 -->');
// Ready to proceed to next tasks
self.track('done', true);
};
// TFD, RRD
var processTodaysLogpage = function(result) {
var _id = result.query.pageids;
var _contents = result.query.pages[ _id ].revisions[ 0 ][ '*' ];
var _h4 = _contents.match('====');
if ( _h4 ) {
// there is at least 1 level 4 heading on page - can prepend to section #2
self.discussion.taskManager.relistInfo.newLogEditType = 'prependtext';
self.discussion.taskManager.relistInfo.newLogSection = 2;
} else {
// there are no level 4 headings on page - can append to section #1
self.discussion.taskManager.relistInfo.newLogEditType = 'appendtext';
self.discussion.taskManager.relistInfo.newLogSection = 1;
}
// Ready to proceed to next tasks
self.track('done', true);
};
var processNomPage = function (result) {
// Discussion wikitext
var id = result.query.pageids;
var oldWikitext = result.query.pages[ id ].revisions[0]['*'];
var heading = oldWikitext.slice(0, oldWikitext.indexOf('\n'));
// Abort if discussion is already closed
if ( oldWikitext.indexOf('xfd-closed') !== -1 ) {
self.discussion.taskManager.abortTasks('discussion has been closed');
return;
}
// Relist template
var relists = oldWikitext
.match(/\[\[Wikipedia:Deletion process#Relisting discussions\|Relisted\]\]/g);
var relistTemplate = '\n{{subst:Relist|1=' + self.inputData.getRelistComment() +
'|2=' + (( relists ) ? relists.length + 1 : 1) + '}}\n';
// New/relisted discussion
var newWikitext = oldWikitext.trim() + relistTemplate;
// Wikitext for old log page
var oldLogWikitext = '';
if ( config.xfd.type === 'afd' ) {
// Update link to log page
newWikitext = newWikitext.replace(
/\[\[Wikipedia:Articles for deletion\/Log\/\d{4} \w+ \d{1,2}#/,
'[[' + todaysLogpage + '#'
);
} else if ( config.xfd.type === 'ffd' || config.xfd.type === 'tfd' ) {
// Discussion on old log page gets closed
var xfdCloseTop = config.xfd.wikitext.closeTop
.replace(/__RESULT__/, 'relisted')
.replace(/__TO_TARGET__/, ' on [[' + todaysLogpage + '#' +
self.discussion.sectionHeader + '|' + today + ']]')
.replace(/__RATIONALE__/, '.')
.replace(/__SIG__/, config.user.sig);
// List of nominated pages
var pagesList = '';
if ( !self.discussion.isBasicMode() ) {
pagesList = self.discussion.pages.reduce(function(list, page) {
var namespaceParam = ( page.getNamespaceId() === 828 ) ? '|module=Module' : '';
return list + config.xfd.wikitext.pagelinks.replace('__PAGE__', page.getMain() + namespaceParam);
}, '');
}
oldLogWikitext = heading + '\n' + xfdCloseTop + '\n' +
pagesList + config.xfd.wikitext.closeBottom;
} else if ( config.xfd.type === 'mfd' ) {
// Find first linebreak after last pagelinks templats
var splitIndex = newWikitext.indexOf('\n', newWikitext.lastIndexOf(':{{pagelinks'));
// Add time stamp for bot to properly relist
newWikitext = (newWikitext.slice(0, splitIndex).trim() +
'\n{{subst:mfdr}}\n' + newWikitext.slice(splitIndex+1).trim());
} else if ( config.xfd.type === 'rfd' ) {
var topWikitext = '====' + self.discussion.sectionHeader + '====';
if ( oldWikitext.indexOf('*<span id=') !== oldWikitext.lastIndexOf('*<span id=') ) {
// Multiple redirects were nominted, so keep nominated redirects' anchors
// Find linebreak prior to first span with an id
var splitIndex1 = oldWikitext.indexOf('\n', oldWikitext.indexOf('*<span id=')-2);
// Find linebreak after the last span with an id
var splitIndex2 = oldWikitext.indexOf('\n', oldWikitext.lastIndexOf('*<span id='));
var rfdAnchors = oldWikitext.slice(splitIndex1, splitIndex2)
.replace(/\*<span/g, '<span') // remove bullets
.replace(/^(?!<span).*$\n?/gm, '') // remove lines which don't start with a span
.replace(/>.*$\s*/gm, '></span>') // remove content within or after span
.trim();
topWikitext += '\n<noinclude>' + rfdAnchors + '</noinclude>';
}
oldLogWikitext = topWikitext + '\n{{subst:rfd relisted|page=' + today +
'|' + self.discussion.sectionHeader + '}}';
}
// Store wikitext in task manager
self.discussion.taskManager.relistInfo = {
'today': today,
'newWikitext': newWikitext,
'oldLogWikitext': oldLogWikitext
};
// For AfDs, check that it's still transcluded to old log page...
if ( config.xfd.type === 'afd' ) {
// Get array of "embeddedin" pages which are logpages (should only be one)
var eiLogpages = result.query.embeddedin.filter(function(ei) {
return ei.title.indexOf(config.xfd.path) !== -1;
});
// Abort if none found
if ( eiLogpages.length === 0 ) {
self.addError('Old log page not found');
self.discussion.taskManager.abortTasks('');
return;
}
// Warn if multiple log pages were found
if ( eiLogpages.length > 1 ) {
for (var i = 1; i < eiLogpages.length; i++) {
self.addWarning([
'Note: transcluded on additional log page: ',
extraJs.makeLink(
eiLogpages[i].title,
eiLogpages[i].title.replace(config.xfd.path, '')
)
]);
}
}
// Set old log page
var oldLogpage = eiLogpages[0];
// Abort if old log page is actually today's logpage
if ( oldLogpage.title === todaysLogpage ) {
self.addError('Already transcluded to today\'s log page');
self.discussion.taskManager.abortTasks('');
return;
}
// Add old log page link in task message
self.discussion.taskManager.getTaskByName('updateOldLogPage').setDescription([
'Removing from ',
extraJs.makeLink(oldLogpage.title, 'old log page')
]);
// Store old log title
self.discussion.taskManager.relistInfo.oldlogtitle = oldLogpage.title;
self.discussion.taskManager.relistInfo.newLogEditType = 'text';
// Get logpages' wikitext
API.get( {
action: 'query',
titles: oldLogpage.title + '|' + todaysLogpage,
prop: 'revisions',
rvprop: 'content',
indexpageids: 1,
rawcontinue: ''
} )
.done( processAfdLogpages )
.fail( function(code, jqxhr) {
self.addApiError(code, jqxhr, 'Could not read contents of log pages');
self.discussion.taskManager.abortTasks('');
} );
} else if ( config.xfd.type === 'tfd' || config.xfd.type === 'rfd' ) {
//New discussions on top of log page, so need to check out current log page wikitext
API.get( {
action: 'query',
titles: todaysLogpage,
prop: 'revisions',
rvprop: 'content',
indexpageids: 1,
rawcontinue: ''
} )
.done( processTodaysLogpage )
.fail( function(code, jqxhr) {
self.addApiError(code, jqxhr, [
'Could not read contents of ',
extraJs.makeLink(todaysLogpage, "today's log page")
]);
self.discussion.taskManager.abortTasks('');
} );
} else { // ffd, mfd
self.discussion.taskManager.relistInfo.newLogEditType = 'appendtext';
// Ready to proceed to next task(s)
self.track('done', true);
}
};
var query = {
action: 'query',
titles: self.discussion.nomPage,
prop: 'revisions',
indexpageids: 1,
rawcontinue: 1,
rvprop: 'content',
rvsection: self.discussion.sectionNumber
};
if ( config.xfd.type === 'afd' ) {
$.extend(query, {
list: 'embeddedin',
eititle: self.discussion.nomPage,
einamespace: config.xfd.ns_logpages,
eifilterredir: 'nonredirects',
eilimit: 500
});
// Need to fetch whole page, in order to check if afd is already closed
delete query.rvsection;
}
API.get( query )
.done( processNomPage )
.fail( function(code, jqxhr) {
self.addApiError(code, jqxhr,
[ 'Could not read contents of page ',
extraJs.makeLink(self.discussion.nomPage),
'; could not relist discussion' ]
);
self.discussion.taskManager.abortTasks('');
} );
};
Task.prototype.doTask.updateDiscussion = function(self) {
// Notify task is started
self.setStatus('started');
self.setupTracking('edit', 1);
var relistInfo = self.discussion.taskManager.relistInfo;
var params = {
action: 'edit',
title: self.discussion.nomPage,
text: relistInfo.newWikitext,
summary: 'Relisting discussion' + config.script.advert
};
if ( config.xfd.type === 'mfd' ) {
params.section = self.discussion.sectionNumber;
}
API.postWithToken( 'csrf', params )
.done( function() {
self.track('edit', true);
} )
.fail( function(code, jqxhr) {
self.track('edit', false);
self.addApiError(code, jqxhr, 'Could not edit ' + config.xfd.type.toUpperCase() +
' discussion');
} );
};
Task.prototype.doTask.updateOldLogPage = function(self) {
// Notify task is started
self.setStatus('started');
self.setupTracking('edit', 1);
var relistInfo = self.discussion.taskManager.relistInfo;
var params = {
action: 'edit',
title: ( config.xfd.type === 'afd' ) ? relistInfo.oldlogtitle : self.discussion.nomPage,
text: relistInfo.oldLogWikitext,
summary: (( config.xfd.type === 'afd' ) ? 'Relisting [[:' + self.discussion.nomPage +
']]' : '/* ' + self.discussion.sectionHeader + ' */ Relisted on [[:' +
config.xfd.path + relistInfo.today + '#' + self.discussion.sectionHeader +
'|' + relistInfo.today + ']]') + config.script.advert
};
if ( config.xfd.type === 'afd' ) {
// Skip if transclusion wasn't found
if ( !relistInfo.oldlogTransclusion ) {
self.track('edit', false);
self.addError('Transclusion not found on old log page; could not be commented out');
return;
}
} else {
params.section = self.discussion.sectionNumber;
}
API.postWithToken( 'csrf', params )
.done( function() {
self.track('edit', true);
} )
.fail( function(code, jqxhr) {
self.track('edit', false);
self.addApiError(code, jqxhr, 'Could not edit old ' + config.xfd.type.toUpperCase() +
' log page');
} );
};
Task.prototype.doTask.updateNewLogPage = function(self) {
// Notify task is started
self.setStatus('started');
self.setupTracking('edit', 1);
var relistInfo = self.discussion.taskManager.relistInfo;
if ( relistInfo.newLogEditType === 'appendtext' ) {
relistInfo.newWikitext = '\n' + relistInfo.newWikitext;
}
var params = {
action: 'edit',
title: config.xfd.path + relistInfo.today,
summary: 'Relisting ' + (( config.xfd.type === 'afd' ) ? '[[:' + self.discussion.nomPage +
']]' : '"' + self.discussion.sectionHeader + '"') + config.script.advert
};
if ( config.xfd.type === 'afd' ) {
params.text = relistInfo.newLogWikitext;
} else {
params[relistInfo.newLogEditType] = relistInfo.newWikitext;
}
if ( /(tfd|rfd)/.test(config.xfd.type) ) {
params.section = relistInfo.newLogSection;
}
API.postWithToken( 'csrf', params )
.done( function() {
self.track('edit', true);
} )
.fail( function(code, jqxhr) {
self.track('edit', false);
self.addApiError(code, jqxhr, 'Could not edit today\'s ' + config.xfd.type.toUpperCase() +
' log page');
} );
};
Task.prototype.doTask.updateNomTemplates = function(self) {
// Notify task is started
self.setStatus('started');
var relistInfo = self.discussion.taskManager.relistInfo;
var pageTitles = self.discussion.getPageTitles(null, {'moduledocs':true});
self.setupTracking('edit', pageTitles.length);
var apiEditPage = function(pageTitle, updatedWikitext) {
API.postWithToken( 'csrf', {
action: 'edit',
title: pageTitle,
text: updatedWikitext,
summary: 'Updating ' + config.xfd.type.toUpperCase() +
' template: discussion was relisted' + config.script.advert
} )
.done( function() {
self.track('edit', true);
} )
.fail( function(code, jqxhr) {
self.track('edit', false);
self.addApiError(code, jqxhr, [
'Could not edit page ',
extraJs.makeLink(pageTitle)
]);
} );
};
var processPages = function(result) {
//Get old wikitext, or make error if page doesn't exist, or if page is in wrong namespace
var ids = result.query.pageids;
for (var i = 0; i < ids.length; i++) {
var pageTitle = result.query.pages[ ids[i] ].title;
// Check there's a corresponding nominated page
var pageObj = self.discussion.getPageByTitle(pageTitle, {'moduledocs':true});
if ( !pageObj ) {
self.addError([
'API query result included unexpected title ',
extraJs.makeLink(pageTitle),
'; this page will not be edited'
]);
self.track('edit', false);
continue;
}
// Check that page exists
if ( parseInt(ids[i]) < 0 ) {
self.addError([
extraJs.makeLink(pageTitle),
' does not exist and will not be edited'
]);
self.track('edit', false);
continue;
}
// Update wikitext
var oldWikitext = result.query.pages[ ids[i] ].revisions[0]['*'];
var newWikitext = oldWikitext.replace(
config.xfd.regex.relistPattern,
config.xfd.wikitext.relistReplace.replace("__TODAY__", relistInfo.today)
);
// Skip if no changes made
if ( newWikitext === oldWikitext ) {
self.track('edit', false);
self.addWarning([
'Skipped ',
extraJs.makeLink(pageTitle),
': nomination template not found'
]);
continue;
}
// Make the edit
apiEditPage(pageTitle, newWikitext);
}
};
// Get each nominated page's wikitext
new mw.Api().get( {
action: 'query',
titles: pageTitles.join('|'),
prop: 'revisions',
rvprop: 'content',
indexpageids: 1
} )
.done( processPages )
.fail( function(code, jqxhr) {
self.addApiError(code, jqxhr, 'Could not read contents of nominated page' +
( pageTitles.length > 1 ) ? 's' : '');
self.setStatus('failed');
} );
};
/* ========== ShowHideTag class =================================================================
The 'tag' at the bottom of screen that toggles the visibility of closed discussions.
---------------------------------------------------------------------------------------------- */
// Constructor
var ShowHideTag = function() {
// Determine previous state from localStorage
try {
if ( !!localStorage.getItem('xfdc-closedHidden') ) {
this.isHidden = true;
} else {
this.isHidden = false;
}
} catch(e) {
// If localStorage not available, default to not hidden
this.isHidden = false;
}
};
// ---------- ShowHideTag prototype ------------------------------------------------------------- */
ShowHideTag.prototype.hideClosed = function() {
this.isHidden = true;
try {
localStorage.setItem('xfdc-closedHidden', true);
} catch(e) {}
$('.xfd-closed, .tfd-closed, #XFDcloser-showhide-hide').hide();
$('#XFDcloser-showhide-show').show();
};
ShowHideTag.prototype.showClosed = function() {
this.isHidden = false;
try {
localStorage.setItem('xfdc-closedHidden', '');
} catch(e) {}
$('.xfd-closed, .tfd-closed, #XFDcloser-showhide-hide').show();
$('#XFDcloser-showhide-show').hide();
};
ShowHideTag.prototype.initialise = function() {
var self = this;
$('<div>')
.attr('id', 'XFDcloser-showhide')
.append(
$('<a>')
.attr('id', 'XFDcloser-showhide-hide')
.text('Hide closed discussions')
.toggle(!self.isHidden)
.on('click', self.hideClosed),
$('<a>')
.attr('id', 'XFDcloser-showhide-show')
.text('Show closed discussions')
.toggle(self.isHidden)
.on('click', self.showClosed)
)
.appendTo('body');
};
/* ========== Get started ======================================================================= */
// Initialise show/hide closed discussions tag, unless there is only one discussion on the page
if ( $('#mw-content-text ' + config.xfd.html.head).length > 1 ) {
config.showHide = new ShowHideTag();
config.showHide.initialise();
}
// Set up discussion object for each discussion
$(config.xfd.html.head + ' > span.mw-headline')
.not('.XFDcloser-ignore')
.each(function(i) {
var d = Discussion.newFromHeadlineSpan(i, this);
if ( d ) {
try {
d.retrieveExtraInfo();
} catch(e) {
console.warn('[XFDcloser] Could not retrieve page info for ' + $(this).text() +
' [see error below]');
console.warn(e);
}
}
});
// If showHide state is hidden, hide any headings that may have had class 'xfd-closed' added
if ( config.showHide && config.showHide.isHidden ) {
config.showHide.hideClosed();
}
/* ========== End of full file closure wrappers ================================================ */
});
});
/* </nowiki> */