User:MusikAnimal/Gadget-twinkleblock.js

//

(function($){

var api = new mw.Api, relevantUserName;

/* **************************************** *** twinkleblock.js: Block module **************************************** * Mode of invocation:    Tab ("Block") * Active on:             any page with relevant user name (userspace, contribs, etc.) * Config directives in:  [soon to be TwinkleConfig] */

Twinkle.block = function twinkleblock { // should show on Contributions pages, anywhere there's a relevant user if ( Morebits.userIsInGroup('sysop') && mw.config.get('wgRelevantUserName') ) { Twinkle.addPortletLink(Twinkle.block.callback, 'Block', 'tw-block', 'Block relevant user' ); } };

Twinkle.block.callback = function twinkleblockCallback { if( mw.config.get('wgRelevantUserName') === mw.config.get('wgUserName') &&			!confirm( 'You are about to block yourself! Are you sure you want to proceed?' ) ) { return; }

var Window = new Morebits.simpleWindow( 650, 530 ); // need to be verbose about who we're blocking Window.setTitle( 'Block or issue block template to ' + mw.config.get('wgRelevantUserName') ); Window.setScriptName( 'Twinkle' ); Window.addFooterLink( 'Block templates', 'Template:Uw-block/doc/Block_templates' ); Window.addFooterLink( 'Block policy', 'WP:BLOCK' ); Window.addFooterLink( 'Twinkle help', 'WP:TW/DOC#block' );

Twinkle.block.currentBlockInfo = undefined; Twinkle.block.field_block_options = {}; Twinkle.block.field_template_options = {};

var form = new Morebits.quickForm( Twinkle.block.callback.evaluate ); var actionfield = form.append( {			type: 'field',			label: 'Type of action'		} ); actionfield.append({			type: 'checkbox',			name: 'actiontype',			event: Twinkle.block.callback.change_action,			list: [				{					label: 'Block user',					value: 'block',					tooltip: 'Block the relevant user with given options.',					checked: true				},				{					label: 'Add block template to user talk page',					value: 'template',					tooltip: 'If the blocking admin forgot to issue a block template, or you have just blocked the user without templating them, you can use this to issue the appropriate template.',					checked: true				}			]		});

form.append({ type: 'field', label: 'Preset', name: 'field_preset' }); form.append({ type: 'field', label: 'Template options', name: 'field_template_options' }); form.append({ type: 'field', label: 'Block options', name: 'field_block_options' });

form.append( { type:'submit' } );

var result = form.render; Window.setContent( result ); Window.display; result.root = result;

Twinkle.block.fetchUserInfo(function {		// clean up preset data (defaults, etc.), done exactly once, must be before Twinkle.block.callback.change_action is called		Twinkle.block.transformBlockPresets;

// init the controls after user and block info have been fetched var evt = document.createEvent( 'Event' ); evt.initEvent( 'change', true, true ); result.actiontype[0].dispatchEvent( evt ); }); };

Twinkle.block.fetchUserInfo = function twinkleblockFetchUserInfo(fn) {

api.get({		format: 'json',		action: 'query',		list: 'blocks|users|logevents',		letype: 'block',		lelimit: 1,		bkusers: mw.config.get('wgRelevantUserName'),		ususers: mw.config.get('wgRelevantUserName'),		letitle: 'User:' + mw.config.get('wgRelevantUserName')	}) .then(function(data){		var blockinfo = data.query.blocks[0],			userinfo = data.query.users[0];

Twinkle.block.isRegistered = !!userinfo.userid; relevantUserName = Twinkle.block.isRegistered ? 'User:' + mw.config.get('wgRelevantUserName') : mw.config.get('wgRelevantUserName');

if (blockinfo) { // handle frustrating system of inverted boolean values blockinfo.disabletalk = blockinfo.allowusertalk === undefined; blockinfo.hardblock = blockinfo.anononly === undefined; Twinkle.block.currentBlockInfo = blockinfo; }

Twinkle.block.hasBlockLog = !!data.query.logevents.length;

if (typeof fn === 'function') return fn; }, function(msg) { Morebits.status.init($('div[name="currentblock"] span').last[0]); Morebits.status.warn('Error fetching user info', msg); }); };

Twinkle.block.callback.saveFieldset = function twinkleblockCallbacksaveFieldset(fieldset) { Twinkle.block[$(fieldset).prop('name')] = {}; $(fieldset).serializeArray.forEach(function(el) {		Twinkle.block[$(fieldset).prop('name')][el.name] = el.value;	}); };

Twinkle.block.callback.change_action = function twinkleblockCallbackChangeAction(e) { var field_preset, field_template_options, field_block_options, $form = $(e.target.form);

Twinkle.block.callback.saveFieldset($('[name=field_block_options]')); Twinkle.block.callback.saveFieldset($('[name=field_template_options]'));

if ($form.find('[name=actiontype][value=block]').is(':checked')) { field_preset = new Morebits.quickForm.element({ type: 'field', label: 'Preset', name: 'field_preset' }); field_preset.append({				type: 'select',				name: 'preset',				label: 'Choose a preset:',				event: Twinkle.block.callback.change_preset,				list: Twinkle.block.callback.filtered_block_groups			});

field_block_options = new Morebits.quickForm.element({ type: 'field', label: 'Block options', name: 'field_block_options' }); field_block_options.append({ type: 'div', name: 'hasblocklog', label: ' ' }); field_block_options.append({ type: 'div', name: 'currentblock', label: ' ' }); field_block_options.append({				type: 'select',				name: 'expiry_preset',				label: 'Expiry:',				event: Twinkle.block.callback.change_expiry,				list: [					{ label: 'custom', value: 'custom', selected: true },					{ label: 'indefinite', value: 'infinity' },					{ label: '3 hours', value: '3 hours' },					{ label: '12 hours', value: '12 hours' },					{ label: '24 hours', value: '24 hours' },					{ label: '31 hours', value: '31 hours' },					{ label: '36 hours', value: '36 hours' },					{ label: '48 hours', value: '48 hours' },					{ label: '60 hours', value: '60 hours' },					{ label: '72 hours', value: '72 hours' },					{ label: '1 week', value: '1 week' },					{ label: '2 weeks', value: '2 weeks' },					{ label: '1 month', value: '1 month' },					{ label: '3 months', value: '3 months' },					{ label: '6 months', value: '6 months' },					{ label: '1 year', value: '1 year' },					{ label: '2 years', value: '2 years' }, { label: '3 years', value: '3 years' } ]			});			field_block_options.append({ type: 'input', name: 'expiry', label: 'Custom expiry', tooltip: 'You can use relative times, like "1 minute" or "19 days", or absolute timestamps, "yyyymmddhhmm" (e.g. "200602011405" is Feb 1, 2006, at 14:05 UTC).', value: Twinkle.block.field_block_options.expiry || Twinkle.block.field_template_options.template_expiry });		var blockoptions = [				{					checked: Twinkle.block.field_block_options.nocreate,					label: 'Block account creation',					name: 'nocreate',					value: '1'				},				{					checked: Twinkle.block.field_block_options.noemail,					label: 'Block user from sending email',					name: 'noemail',					value: '1'				},				{					checked: Twinkle.block.field_block_options.disabletalk,					label: 'Prevent this user from editing their own talk page while blocked',					name: 'disabletalk',					value: '1'				}			];

if (Twinkle.block.isRegistered) { blockoptions.push({					checked: Twinkle.block.field_block_options.autoblock,					label: 'Autoblock any IP addresses used (hardblock)',					name: 'autoblock',					value: '1'				}); } else { blockoptions.push({					checked: Twinkle.block.field_block_options.hardblock,					label: 'Prevent logged-in users from editing from this IP address (hardblock)',					name: 'hardblock',					value: '1'				}); }

blockoptions.push({				checked: Twinkle.block.field_block_options.watchuser,				label: 'Watch user and user talk pages',				name: 'watchuser',				value: '1'			});

field_block_options.append({				type: 'checkbox',				name: 'blockoptions',				list: blockoptions			}); field_block_options.append({				type: 'textarea',				label: 'Reason (for block log):',				name: 'reason',				value: Twinkle.block.field_block_options.reason			});

field_block_options.append({				type: 'div',				name: 'filerlog_label',				label: 'See also:',				style: 'display:inline-block;font-style:normal !important',				tooltip: 'Insert a "see also" message to indicate whether the filter log or deleted contributions played a role in the decision to block.'			}); field_block_options.append({				type: 'checkbox',				name: 'filter_see_also',				event: Twinkle.block.callback.toggle_see_alsos,				style: 'display:inline-block; margin-right:5px',				list: [					{						label: 'Filter log',						checked: false,						value: 'filter log'					}				]			} ); field_block_options.append({				type: 'checkbox',				name: 'deleted_see_also',				event: Twinkle.block.callback.toggle_see_alsos,				style: 'display:inline-block',				list: [					{						label: 'Deleted contribs',						checked: false,						value: 'deleted contribs'					}				]			} );

if (Twinkle.block.currentBlockInfo) { field_block_options.append( { type: 'hidden', name: 'reblock', value: '1' } ); }	}

if ($form.find('[name=actiontype][value=template]').is(':checked')) { field_template_options = new Morebits.quickForm.element({ type: 'field', label: 'Template options', name: 'field_template_options' }); field_template_options.append( {				type: 'select',				name: 'template',				label: 'Choose talk page template:',				event: Twinkle.block.callback.change_template,				list: Twinkle.block.callback.filtered_block_groups(true),				value: Twinkle.block.field_template_options.template			} ); field_template_options.append( {				type: 'input',				name: 'article',				display: 'none',				label: 'Linked article',				value: '',				tooltip: 'An article can be linked within the notice, perhaps if it was the primary target of disruption. Leave empty for no article to be linked.'			} ); if (!$form.find('[name=actiontype][value=block]').is(':checked')) { field_template_options.append( {				type: 'input',				name: 'template_expiry',				display: 'none',				label: 'Period of blocking: ',				value: '',				tooltip: 'The period the blocking is due for, for example 24 hours, 2 weeks, indefinite etc...'			} ); }		field_template_options.append( {			type: 'input',			name: 'block_reason',			label: '"You have been blocked for ..." ',			display: 'none',			tooltip: 'An optional reason, to replace the default generic reason. Only available for the generic block templates.',			value: Twinkle.block.field_template_options.block_reason		} );

if ($form.find('[name=actiontype][value=block]').is(':checked')) { field_template_options.append( {				type: 'checkbox',				name: 'blank_duration',				list: [					{						label: 'Do not include expiry in template',						checked: Twinkle.block.field_template_options.blank_duration,						tooltip: 'Instead of including the duration, make the block template read \"You have been blocked from editing temporarily for...\"'					}				]			} ); } else { field_template_options.append( {				type: 'checkbox',				name: 'notalk',				list: [					{						label: 'Talk page access disabled',						checked: Twinkle.block.field_template_options.notalk,						tooltip: 'Use this to make the block template state that the user\'s talk page access has been removed'					}				]			} ); }

var $previewlink = $( 'Preview' ); $previewlink.off('click').on('click', function{			Twinkle.block.callback.preview($form[0]);		}); $previewlink.css({cursor: 'pointer'}); field_template_options.append( { type: 'div', id: 'blockpreview', label: [ $previewlink[0] ] } ); field_template_options.append( { type: 'div', id: 'twinkleblock-previewbox', style: 'display: none' } ); }

var oldfield; if (field_preset) { oldfield = $form.find('fieldset[name="field_preset"]')[0]; oldfield.parentNode.replaceChild(field_preset.render, oldfield); } else { $form.find('fieldset[name="field_preset"]').hide; }	if (field_block_options) { oldfield = $form.find('fieldset[name="field_block_options"]')[0]; oldfield.parentNode.replaceChild(field_block_options.render, oldfield); } else { $form.find('fieldset[name="field_block_options"]').hide; }	if (field_template_options) { oldfield = $form.find('fieldset[name="field_template_options"]')[0]; oldfield.parentNode.replaceChild(field_template_options.render, oldfield); e.target.form.root.previewer = new Morebits.wiki.preview($(e.target.form.root).find('#twinkleblock-previewbox').last[0]); } else { $form.find('fieldset[name="field_template_options"]').hide; }

if (Twinkle.block.hasBlockLog) { var $blockloglink = $( 'block log)' );

Morebits.status.init($('div[name="hasblocklog"] span').last[0]); Morebits.status.warn('This user has been blocked in the past', $blockloglink[0]); }

if (Twinkle.block.currentBlockInfo) { Morebits.status.init($('div[name="currentblock"] span').last[0]); Morebits.status.warn(relevantUserName + ' is already blocked', 'Submit query to reblock with supplied options'); Twinkle.block.callback.update_form(e, Twinkle.block.currentBlockInfo); } else if ($form.find('[name=actiontype][value=template]').is(':checked')) { // make sure all the fields are correct based on defaults if ($form.find('[name=actiontype][value=block]').is(':checked')) { Twinkle.block.callback.change_preset(e); } else { Twinkle.block.callback.change_template(e); }	} };

/* * Keep alphabetized by key name, Twinkle.block.blockGroups establishes *   the order they will appear in the interface * * Block preset format, all keys accept only 'true' (omit for false) except where noted: * : { *  autoblock:  *  disabletalk:  *  expiry:  *  forAnonOnly:  *  forRegisteredOnly:  *  label:  *  noemail: prevent the user from sending email through Special:Emailuser *  pageParam:  *  prependReason:  *  nocreate:  *  nonstandard:  *  reason:  *  reasonParam:  *  sig: <string - set to ~ if block template does not accept "true" as the value, or set null to omit sig param altogether> *  sharedIP: <boolean - for anonblock and schoolblock, forcing the template to be shown at the top of the talk page> *  summary: <string - edit summary for when adding block template to user's talk page, if not set, 'reason' is used> *  suppressArticleInSummary: <set to suppress showing the article name in the edit summary, as with attack pages> *  templateName: <string - name of template to use (instead of key name), entry will be omitted from the Templates list. *                 (e.g. use another template but with different block options)> *  useInitialOptions: <when preset is chosen, only change given block options, leave others as they were> * * WARNING: 'anononly' and 'allowusertalk' are enabled by default. *  To disable, set 'hardblock' and 'disabletalk', respectively */ Twinkle.block.blockPresetsInfo = { 'anonblock' : { expiry: '31 hours', forAnonOnly: true, nocreate: true, nonstandard: true, reason: '', sharedIP: true },	'anonblock - school' : { expiry: '36 hours', forAnonOnly: true, nocreate: true, nonstandard: true, reason: ' ', templateName: 'anonblock', sharedIP: true },	'blocked proxy' : { expiry: '1 year', forAnonOnly: true, nocreate: true, nonstandard: true, reason: '', sig: null },	'CheckUser block' : { expiry: '1 week', forAnonOnly: true, nocreate: true, nonstandard: true, reason: '', sig: '~' },	'checkuserblock-account' : { autoblock: true, expiry: 'infinity', forRegisteredOnly: true, nocreate: true, nonstandard: true, reason: '', sig: '~' },	'checkuserblock-wide' : { forAnonOnly: true, nocreate: true, nonstandard: true, reason: '', sig: '~' },	'colocationwebhost' : { expiry: '1 year', forAnonOnly: true, nonstandard: true, reason: '', sig: null },	'oversightblock' : { autoblock: true, expiry: 'infinity', nocreate: true, nonstandard: true, reason: '', sig: '~' },	'school block' : { forAnonOnly: true, nocreate: true, nonstandard: true, reason: '', sig: '~' },	'spamblacklistblock' : { forAnonOnly: true, expiry: '1 month', disabletalk: true, nocreate: true, reason: ' ' },	// Placeholder for when we add support for rangeblocks // 'rangeblock' : { //  reason: '', //  nocreate: true, //  nonstandard: true, //  forAnonOnly: true, //  sig: '~' // },	'tor' : { expiry: '1 year', forAnonOnly: true, nonstandard: true, reason: '', sig: null },	'webhostblock' : { expiry: '1 year', forAnonOnly: true, nonstandard: true, reason: '', sig: null },	// uw-prefixed 'uw-3block' : { autoblock: true, expiry: '24 hours', nocreate: true, pageParam: true, reason: 'Violation of the three-revert rule', summary: 'You have been blocked from editing for violation of the three-revert rule' },	'uw-ablock' : { autoblock: true, expiry: '31 hours', forAnonOnly: true, nocreate: true, pageParam: true, reasonParam: true, summary: 'Your IP address has been blocked from editing', suppressArticleInSummary: true },	'uw-adblock' : { autoblock: true, nocreate: true, pageParam: true, reason: 'Using Wikipedia for spam or advertising purposes', summary: 'You have been blocked from editing for advertising or self-promotion' },	'uw-aeblock' : { autoblock: true, nocreate: true, pageParam: true, reason: 'Arbitration enforcement', reasonParam: true, summary: 'You have been blocked from editing for violating an arbitration decision with your edits' },	'uw-bioblock' : { autoblock: true, nocreate: true, pageParam: true, reason: 'Violations of the biographies of living persons policy', summary: 'You have been blocked from editing for violations of Wikipedia\'s biographies of living persons policy' },	'uw-block' : { autoblock: true, expiry: '24 hours', forRegisteredOnly: true, nocreate: true, pageParam: true, reasonParam: true, summary: 'You have been blocked from editing', suppressArticleInSummary: true },	'uw-blockindef' : { autoblock: true, expiry: 'infinity', forRegisteredOnly: true, nocreate: true, pageParam: true, reasonParam: true, summary: 'You have been indefinitely blocked from editing', suppressArticleInSummary: true },	'uw-blocknotalk' : { disabletalk: true, pageParam: true, reasonParam: true, summary: 'You have been blocked from editing and your user talk page access has been disabled', suppressArticleInSummary: true },	'uw-botblock': { forRegisteredOnly: true, pageParam: true, reason: 'Running a bot script without approval', summary: 'You have been blocked from editing because it appears you are running a bot script without approval' },	'uw-botublock': { expiry: 'infinity', forRegisteredOnly: true, reason: ' ', summary: 'You have been indefinitely blocked from editing because your username indicates this is a bot account, which is currently not approved' },	'uw-causeblock' : { expiry: 'infinity', forRegisteredOnly: true, reason: ' ', summary: 'You have been indefinitely blocked from editing because your username gives the impression that the account represents a group, organization or website' },	'uw-compblock': { autoblock: true, expiry: 'infinity', forRegisteredOnly: true, nocreate: true, reason: 'Compromised account', summary: 'You have been indefinitely blocked from editing because it is believed that your account has been compromised' },	'uw-copyrightblock' : { autoblock: true, expiry: '24 hours', nocreate: true, pageParam: true, reason: 'Copyright violations', summary: 'You have been blocked from editing for continued copyright infringement' },	'uw-dblock': { autoblock: true, nocreate: true, reason: 'Persistent removal of content', pageParam: true, summary: 'You have been blocked from editing for continued removal of material' },	'uw-disruptblock' : { autoblock: true, nocreate: true, reason: 'Disruptive editing', summary: 'You have been blocked from editing for disruptive editing' },	'uw-efblock' : { autoblock: true, nocreate: true, reason: 'Deliberately triggering the Edit filter', summary: 'You have been blocked from editing for making disruptive edits that repeatedly triggered the edit filter' },	'uw-ewblock' : { autoblock: true, expiry: '24 hours', nocreate: true, pageParam: true, reason: 'Edit warring', summary: 'You have been blocked from editing to prevent further disruption caused by your engagement in an edit war' },	'uw-hblock' : { autoblock: true, nocreate: true, pageParam: true, reason: 'Personal attacks or harassment', summary: 'You have been blocked from editing for attempting to harass other users' },	'uw-ipevadeblock' : { forAnonOnly: true, nocreate: true, reason: 'Block evasion', summary: 'Your IP address has been blocked from editing because it has been used to evade a previous block' },	'uw-lblock' : { autoblock: true, expiry: 'infinity', nocreate: true, reason: 'Making legal threats', summary: 'You have been blocked from editing for making legal threats or taking legal action' },	'uw-memorialblock': { forRegisteredOnly: true, expiry: 'infinity', reason: ' ', summary: 'You have been indefinitely blocked from editing because your username indicates this account may be used as a memorial or tribute to someone' },	'uw-myblock': { autoblock: true, nocreate: true, pageParam: true, reason: 'Using Wikipedia as a blog, web host, social networking site or forum', summary: 'You have been blocked from editing for using user and/or article pages as a blog, web host, social networking site or forum' },	'uw-nothereblock' : { autoblock: true, expiry: 'infinity', nocreate: true, reason: 'Clearly not here to contribute to the encyclopedia', forRegisteredOnly: true, summary: 'You have been indefinitely blocked from editing because it appears that you are not here to build an encyclopedia' },	'uw-npblock' : { autoblock: true, nocreate: true, pageParam: true, reason: 'Creating patent nonsense or other inappropriate pages', summary: 'You have been blocked from editing for creating nonsense pages' },	'uw-pablock' : { autoblock: true, expiry: '31 hours', nocreate: true, reason: 'Personal attacks or harassment', summary: 'You have been blocked from editing for making personal attacks toward other users' },	'uw-sblock' : { autoblock: true, nocreate: true, reason: 'Using Wikipedia for spam purposes', summary: 'You have been blocked from editing for using Wikipedia for spam purposes' },	'uw-soablock' : { autoblock: true, expiry: 'infinity', forRegisteredOnly: true, nocreate: true, pageParam: true, reason: 'Spam / advertising-only account', summary: 'You have been indefinitely blocked from editing because your account is being used only for spam, advertising, or promotion' },	'uw-sockblock' : { autoblock: true, forRegisteredOnly: true, nocreate: true, reason: 'Abusing multiple accounts', summary: 'You have been blocked from editing for abusing multiple accounts' },	'uw-softerblock' : { expiry: 'infinity', forRegisteredOnly: true, reason: ' ', summary: 'You have been indefinitely blocked from editing because your username gives the impression that the account represents a group, organization or website' },	'uw-spamublock' : { autoblock: true, expiry: 'infinity', forRegisteredOnly: true, nocreate: true, reason: ' ', summary: 'You have been indefinitely blocked from editing because your account is being used only for spam or advertising and your username is a violation of the username policy' },	'uw-spoablock' : { autoblock: true, expiry: 'infinity', forRegisteredOnly: true, nocreate: true, reason: 'Sock puppetry', summary: 'This account has been blocked as a sock puppet created to violate Wikipedia policy' },	'uw-talkrevoked' : { disabletalk: true, reason: 'Revoking talk page access: inappropriate use of user talk page while blocked', prependReason: true, summary: 'Your user talk page access has been disabled', useInitialOptions: true },	'uw-ublock' : { expiry: 'infinity', forRegisteredOnly: true, reason: ' ', reasonParam: true, summary: 'You have been indefinitely blocked from editing because your username is a violation of the username policy' },	'uw-ublock-double': { expiry: 'infinity', forRegisteredOnly: true, reason: ' ', summary: 'You have been indefinitely blocked from editing because your username is too similar to the username of another Wikipedia user' },	'uw-ucblock' : { autoblock: true, expiry: '31 hours', nocreate: true, pageParam: true, reason: 'Persistent addition of unsourced content', summary: 'You have been blocked from editing for persistent addition of unsourced content' },	'uw-uhblock' : { autoblock: true, expiry: 'infinity', forRegisteredOnly: true, nocreate: true, reason: ' ', reasonParam: true, summary: 'You have been indefinitely blocked from editing because your username is a blatant violation of the username policy' },	'uw-ublock-famous' : { expiry: 'infinity', forRegisteredOnly: true, reason: ' ', summary: 'You have been indefinitely blocked from editing because your username matches the name of a well-known living individual' },	'uw-uhblock-double': { autoblock: true, expiry: 'infinity', forRegisteredOnly: true, nocreate: true, reason: ' ', summary: 'You have been indefinitely blocked from editing because your username appears to impersonate another established Wikipedia user' },	'uw-vaublock' : { autoblock: true, expiry: 'infinity', forRegisteredOnly: true, nocreate: true, pageParam: true, reason: ' ', summary: 'You have been indefinitely blocked from editing because your account is being used only for vandalism and your username is a blatant violation of the username policy' },	'uw-vblock' : { autoblock: true, expiry: '31 hours', nocreate: true, pageParam: true, reason: 'Vandalism', summary: 'You have been blocked from editing for persistent vandalism' },	'uw-voablock' : { autoblock: true, expiry: 'infinity', forRegisteredOnly: true, nocreate: true, pageParam: true, reason: 'Vandalism-only account', summary: 'You have been indefinitely blocked from editing because your account is being used only for vandalism' },	'zombie proxy' : { expiry: '1 month', forAnonOnly: true, nocreate: true, nonstandard: true, reason: '', sig: null } };

Twinkle.block.transformBlockPresets = function twinkleblockTransformBlockPresets { // supply sensible defaults $.each(Twinkle.block.blockPresetsInfo, function(preset, settings) {		settings.summary = settings.summary || settings.reason;		settings.sig = settings.sig !== undefined ? settings.sig : 'yes';		// despite this it's preferred that you use 'infinity' as the value for expiry		settings.indefinite = settings.indefinite || settings.expiry === 'infinity' || settings.expiry === 'indefinite' || settings.expiry === 'never';

if (!Twinkle.block.isRegistered && settings.indefinite) { settings.expiry = '31 hours'; } else { settings.expiry = settings.expiry || '31 hours'; }

Twinkle.block.blockPresetsInfo[preset] = settings; }); };

// These are the groups of presets and defines the order in which they appear. For each list item: //  label: <string, the description that will be visible in the dropdown> //  value: <string, the key of a preset in blockPresetsInfo> Twinkle.block.blockGroups = [ {		label: 'Common block reasons', list: [ { label: 'anonblock', value: 'anonblock' }, { label: 'anonblock - likely a school', value: 'anonblock - school' }, { label: 'school block', value: 'school block' }, { label: 'Generic block (custom reason)', value: 'uw-block' }, // ends up being default for registered users { label: 'Generic block (custom reason) – IP', value: 'uw-ablock', selected: true }, // set only when blocking IP			{ label: 'Generic block (custom reason) – indefinite', value: 'uw-blockindef' }, { label: 'Disruptive editing', value: 'uw-disruptblock' }, { label: 'Inappropriate use of user talk page while blocked', value: 'uw-talkrevoked' }, { label: 'Not here to contribute to the encyclopedia', value: 'uw-nothereblock' }, { label: 'Unsourced content', value: 'uw-ucblock' }, { label: 'Vandalism', value: 'uw-vblock' }, { label: 'Vandalism-only account', value: 'uw-voablock' } ],	},	{		label: 'Extended reasons', list: [ { label: 'Advertising', value: 'uw-adblock' }, { label: 'Arbitration enforcement', value: 'uw-aeblock' }, { label: 'Block evasion – IP', value: 'uw-ipevadeblock' }, { label: 'BLP violations', value: 'uw-bioblock' }, { label: 'Copyright violations', value: 'uw-copyrightblock' }, { label: 'Creating inappropriate pages', value: 'uw-npblock' }, { label: 'Edit filter-related', value: 'uw-efblock' }, { label: 'Edit warring', value: 'uw-ewblock' }, { label: 'Generic block with talk page access revoked', value: 'uw-blocknotalk' }, { label: 'Harassment', value: 'uw-hblock' }, { label: 'Legal threats', value: 'uw-lblock' }, { label: 'Personal attacks or harassment', value: 'uw-pablock' }, { label: 'Possible compromised account', value: 'uw-compblock' }, { label: 'Removal of content', value: 'uw-dblock' }, { label: 'Sock puppetry (master)', value: 'uw-sockblock' }, { label: 'Sock puppetry (puppet)', value: 'uw-spoablock' }, { label: 'Social networking', value: 'uw-myblock' }, { label: 'Spam', value: 'uw-sblock' }, { label: 'Spam/advertising-only account', value: 'uw-soablock' }, { label: 'Unapproved bot', value: 'uw-botblock' }, { label: 'Violating the three-revert rule', value: 'uw-3block' } ]	},	{		label: 'Username violations', list: [ { label: 'Bot username', value: 'uw-botublock' }, { label: 'Memorial username soft block', value: 'uw-memorialblock' }, { label: 'Promotional username, hard block', value: 'uw-spamublock' }, { label: 'Promotional username, soft block', value: 'uw-softerblock' }, { label: 'Similar username soft block', value: 'uw-ublock-double' }, { label: 'Username violation, soft block', value: 'uw-ublock' }, { label: 'Username violation, hard block', value: 'uw-uhblock' }, { label: 'Username impersonation hard block', value: 'uw-uhblock-double' }, { label: 'Username represents a famous person, soft block', value: 'uw-ublock-famous' }, { label: 'Username represents a non-profit, soft block', value: 'uw-causeblock' }, { label: 'Username violation, vandalism-only account', value: 'uw-vaublock' } ]	},	{		label: 'Templated reasons', list: [ { label: 'blocked proxy', value: 'blocked proxy' }, { label: 'CheckUser block', value: 'CheckUser block' }, { label: 'checkuserblock-account', value: 'checkuserblock-account' }, { label: 'checkuserblock-wide', value: 'checkuserblock-wide' }, { label: 'colocationwebhost', value: 'colocationwebhost' }, { label: 'oversightblock', value: 'oversightblock' }, // { label: 'rangeblock', value: 'rangeblock' }, // placeholder for when we add support for rangeblocks { label: 'spamblacklistblock', value: 'spamblacklistblock' }, { label: 'tor', value: 'tor' }, { label: 'webhostblock', value: 'webhostblock' }, { label: 'zombie proxy', value: 'zombie proxy' } ]	} ];

Twinkle.block.callback.filtered_block_groups = function twinkleblockCallbackFilteredBlockGroups(show_template) { return $.map(Twinkle.block.blockGroups, function(blockGroup) {		var list = $.map(blockGroup.list, function(blockPreset) { // only show uw-talkrevoked if reblocking if (!Twinkle.block.currentBlockInfo && blockPreset.value === "uw-talkrevoked") return;

var blockSettings = Twinkle.block.blockPresetsInfo[blockPreset.value]; var registrationRestrict = blockSettings.forRegisteredOnly ? Twinkle.block.isRegistered : (blockSettings.forAnonOnly ? !Twinkle.block.isRegistered : true); if (!(blockSettings.templateName && show_template) && registrationRestrict) { var templateName = blockSettings.templateName || blockPreset.value; return { label: (show_template ? ': ' : '') + blockPreset.label, value: blockPreset.value, data: [{ name: 'template-name', value: templateName }],						selected: !!blockPreset.selected };				}			});		if (list.length) return {				label: blockGroup.label,				list: list			};	}); };

Twinkle.block.callback.change_preset = function twinkleblockCallbackChangePreset(e) { var key = e.target.form.preset.value; if (!key) return;

e.target.form.template.value = Twinkle.block.blockPresetsInfo[key].templateName || key; Twinkle.block.callback.update_form(e, Twinkle.block.blockPresetsInfo[key]); Twinkle.block.callback.change_template(e); };

Twinkle.block.callback.change_expiry = function twinkleblockCallbackChangeExpiry(e) { var expiry = e.target.form.expiry; if (e.target.value === 'custom') { Morebits.quickForm.setElementVisibility(expiry.parentNode, true); } else { Morebits.quickForm.setElementVisibility(expiry.parentNode, false); expiry.value = e.target.value; } };

Twinkle.block.seeAlsos = []; Twinkle.block.callback.toggle_see_alsos = function twinkleblockCallbackToggleSeeAlso(e) { var reason = this.form.reason.value.replace(		new RegExp('?'), ''	);

Twinkle.block.seeAlsos = Twinkle.block.seeAlsos.filter(function(el) {		return el !== this.value;	}.bind(this));

if (this.checked) Twinkle.block.seeAlsos.push(this.value); var seeAlsoMessage = Twinkle.block.seeAlsos.join(' and ');

if (!Twinkle.block.seeAlsos.length) { this.form.reason.value = reason; } else if (reason.indexOf('{{') !== -1) { this.form.reason.value = reason + ' '; } else { this.form.reason.value = reason + '; see also ' + seeAlsoMessage; } };

Twinkle.block.callback.update_form = function twinkleblockCallbackUpdateForm(e, data) { var form = e.target.form, expiry = data.expiry;

// don't override original expiry if useInitialOptions is set if (!data.useInitialOptions) { if (Date.parse(expiry)) { expiry = new Date(expiry).toGMTString; form.expiry_preset.value = 'custom'; } else { form.expiry_preset.value = data.expiry || 'custom'; }

form.expiry.value = expiry; if (form.expiry_preset.value === 'custom') { Morebits.quickForm.setElementVisibility(form.expiry.parentNode, true); } else { Morebits.quickForm.setElementVisibility(form.expiry.parentNode, false); }	}

// boolean-flipped options, more at mw:API:Block data.disabletalk = data.disabletalk !== undefined ? data.disabletalk : false; data.hardblock = data.hardblock !== undefined ? data.hardblock : false;

// disable autoblock if blocking a bot if (Twinkle.block.isRegistered && relevantUserName.search(/bot$/i) > 0) { data.autoblock = false; }

$(form.field_block_options).find(':checkbox').each(function(i, el) {		// don't override original options if useInitialOptions is set		if (data.useInitialOptions && data[el.name] === undefined) return;

var check = data[el.name] === '' || !!data[el.name]; $(el).prop('checked', check); });

if (data.prependReason && data.reason) { form.reason.value = data.reason + '; ' + form.reason.value; } else { form.reason.value = data.reason || ''; } };

Twinkle.block.callback.change_template = function twinkleblockcallbackChangeTemplate(e) { var form = e.target.form, value = form.template.value, settings = Twinkle.block.blockPresetsInfo[value];

if (!$(form).find('[name=actiontype][value=block]').is(':checked')) { if (settings.indefinite || settings.nonstandard) { if (Twinkle.block.prev_template_expiry === null) { Twinkle.block.prev_template_expiry = form.template_expiry.value || ''; }			form.template_expiry.parentNode.style.display = 'none'; form.template_expiry.value = 'indefinite'; } else if ( form.template_expiry.parentNode.style.display === 'none' ) { if(Twinkle.block.prev_template_expiry !== null) { form.template_expiry.value = Twinkle.block.prev_template_expiry; Twinkle.block.prev_template_expiry = null; }			form.template_expiry.parentNode.style.display = 'block'; }		if (Twinkle.block.prev_template_expiry) form.expiry.value = Twinkle.block.prev_template_expiry; Morebits.quickForm.setElementVisibility(form.notalk.parentNode, !settings.nonstandard); } else { Morebits.quickForm.setElementVisibility(			form.blank_duration.parentNode,			!settings.indefinite && !settings.nonstandard		); }

Morebits.quickForm.setElementVisibility(form.article.parentNode, !!settings.pageParam); Morebits.quickForm.setElementVisibility(form.block_reason.parentNode, !!settings.reasonParam);

form.root.previewer.closePreview; }; Twinkle.block.prev_template_expiry = null; Twinkle.block.prev_block_reason = null; Twinkle.block.prev_article = null; Twinkle.block.prev_reason = null;

Twinkle.block.callback.preview = function twinkleblockcallbackPreview(form) { var params = { article: form.article.value, blank_duration: form.blank_duration ? form.blank_duration.checked : false, disabletalk: form.disabletalk.checked || (form.notalk ? form.notalk.checked : false), expiry: form.template_expiry ? form.template_expiry.value : form.expiry.value, hardblock: Twinkle.block.isRegistered ? form.autoblock.checked : form.hardblock.checked, indefinite: (/indef|infinity|never|\*|max/).test( form.template_expiry ? form.template_expiry.value : form.expiry.value ), reason: form.block_reason.value, template: form.template.value };

var templateText = Twinkle.block.callback.getBlockNoticeWikitext(params);

form.previewer.beginRender(templateText); };

Twinkle.block.callback.evaluate = function twinkleblockCallbackEvaluate(e) { var $form = $(e.target), toBlock = $form.find('[name=actiontype][value=block]').is(':checked'), toWarn = $form.find('[name=actiontype][value=template]').is(':checked'), blockoptions = {}, templateoptions = {};

Twinkle.block.callback.saveFieldset($form.find('[name=field_block_options]')); Twinkle.block.callback.saveFieldset($form.find('[name=field_template_options]'));

blockoptions = Twinkle.block.field_block_options;

templateoptions = Twinkle.block.field_template_options; templateoptions.disabletalk = !!(templateoptions.disabletalk || blockoptions.disabletalk); templateoptions.hardblock = !!blockoptions.hardblock; delete blockoptions.expiry_preset; // remove extraneous

// use block settings as warn options where not supplied templateoptions.summary = templateoptions.summary || blockoptions.reason; templateoptions.expiry = templateoptions.template_expiry || blockoptions.expiry;

if (toBlock) { if (!blockoptions.expiry) return alert('Please provide an expiry!'); if (!blockoptions.reason) return alert('Please provide a reason for the block!');

Morebits.simpleWindow.setButtonsEnabled( false ); Morebits.status.init( e.target ); var statusElement = new Morebits.status('Executing block'); blockoptions.action = 'block'; blockoptions.user = mw.config.get('wgRelevantUserName');

// boolean-flipped options blockoptions.anononly = blockoptions.hardblock ? undefined : true; blockoptions.allowusertalk = blockoptions.disabletalk ? undefined : true;

// fix for bug with block API, see T68646 if (blockoptions.expiry === 'infinity') blockoptions.expiry = 'infinite';

// execute block api.getToken('block').then(function(token) {			statusElement.status('Processing...');			blockoptions.token = token;			var mbApi = new Morebits.wiki.api( 'Executing block', blockoptions, function(data) { statusElement.info('Completed'); if (toWarn) Twinkle.block.callback.issue_template(templateoptions); });			mbApi.post;		}, function {			statusElement.error('Unable to fetch block token');		}); } else if (toWarn) { Morebits.simpleWindow.setButtonsEnabled( false );

Morebits.status.init( e.target ); Twinkle.block.callback.issue_template(templateoptions); } else { return alert('Please give Twinkle something to do!'); } };

Twinkle.block.callback.issue_template = function twinkleblockCallbackIssueTemplate(formData) { var userTalkPage = 'User_talk:' + mw.config.get('wgRelevantUserName');

var params = $.extend(formData, {		messageData: Twinkle.block.blockPresetsInfo[formData.template],		reason: Twinkle.block.field_template_options.block_reason,		disabletalk: Twinkle.block.field_template_options.notalk	});

Morebits.wiki.actionCompleted.redirect = userTalkPage; Morebits.wiki.actionCompleted.notice = 'Actions complete, loading user talk page in a few seconds';

var wikipedia_page = new Morebits.wiki.page( userTalkPage, 'User talk page modification' ); wikipedia_page.setCallbackParameters( params ); wikipedia_page.setFollowRedirect( true ); wikipedia_page.load( Twinkle.block.callback.main ); };

Twinkle.block.callback.getBlockNoticeWikitext = function(params) { var text = ''; };

Twinkle.block.callback.main = function twinkleblockcallbackMain( pageobj ) { var text = pageobj.getPageText, params = pageobj.getCallbackParameters, messageData = params.messageData, date = new Date;

params.indefinite = (/indef|infinity|never|\*|max/).test( params.expiry ); params.expiry = typeof params.template_expiry !== "undefined" ? params.template_expiry : params.expiry;

if (messageData.sharedIP && !Twinkle.getPref('blankTalkpageOnIndefBlock')) { // ^ Skip this logic in the very (very) rare case they are indef'ing an anonymous user and want to blank the talk page.

var anonTemplateMatch = text.match(/^\n*/i) || [""], textWithoutAnonTemplate = text.replace(anonTemplateMatch[0], "");

if(new RegExp("^\n*\s*{{" + params.template, 'i').test(textWithoutAnonTemplate)) { // State the template has already been added. return Morebits.status.info( 'Info', '{{' + params.template + '}} already found on talk page, aborting.' ); } else { // Find index in string just after the shared IP template, // and that's where our block message goes, or just put it at the top. var topTemplate = (anonTemplateMatch[0] + '\n' + Twinkle.block.callback.getBlockNoticeWikitext(params)).replace(/^\n/, ''); text = text.replace(anonTemplateMatch[0], topTemplate + '\n'); }	} else { // Date header stuff for standardized messages. var dateHeaderRegex = new RegExp( '^==+\\s*(?:' + date.getUTCMonthName + '|' + date.getUTCMonthNameAbbrev + ')\\s+' + date.getUTCFullYear + '\\s*==+', 'mg' ); var dateHeaderRegexLast, dateHeaderRegexResult; while ((dateHeaderRegexLast = dateHeaderRegex.exec( text )) !== null) { dateHeaderRegexResult = dateHeaderRegexLast; }		// If dateHeaderRegexResult is null then lastHeaderIndex is never checked. If it is not null but // \n== is not found, then the date header must be at the very start of the page. lastIndexOf // returns -1 in this case, so lastHeaderIndex gets set to 0 as desired. var lastHeaderIndex = text.lastIndexOf( '\n==' ) + 1;

if ( text.length > 0 ) { text += '\n\n'; }

if ( Twinkle.getPref('blankTalkpageOnIndefBlock') && params.template !== 'uw-lblock' && params.indefinite ) { Morebits.status.info( 'Info', 'Blanking talk page per preferences and creating a new level 2 heading for the date' ); text = '== ' + date.getUTCMonthName + ' ' + date.getUTCFullYear + ' ==\n'; } else if( !dateHeaderRegexResult || dateHeaderRegexResult.index !== lastHeaderIndex ) { Morebits.status.info( 'Info', 'Will create a new level 2 heading for the date, as none was found for this month' ); text += '== ' + date.getUTCMonthName + ' ' + date.getUTCFullYear + ' ==\n'; }

text += Twinkle.block.callback.getBlockNoticeWikitext(params); }

// build the edit summary var summary = messageData.summary; if ( messageData.suppressArticleInSummary !== true && params.article ) { summary += ' on ' + params.article + ''; }	summary += '.' + Twinkle.getPref('summaryAd');

pageobj.setPageText( text ); pageobj.setEditSummary( summary ); pageobj.setWatchlist( Twinkle.getPref('watchWarnings') ); pageobj.save; };

})(jQuery);

//