(function($) {

var api = new mw.Api, relevantUserName;
var menuFormattedNamespaces = $.extend({}, mw.config.get('wgFormattedNamespaces'));
menuFormattedNamespaces[0] = '(일반)';

/*

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

Twinkle.block.callback = function twinkleblockCallback {
	if (mw.config.get('wgRelevantUserName') === mw.config.get('wgUserName') &&
		return;

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

	var Window = new Morebits.simpleWindow;
	// need to be verbose about who we're blocking
	Window.setTitle('Block or issue block template to ' + mw.config.get('wgRelevantUserName'));
	Window.setScriptName('트윙클');
	Window.addFooterLink;
	Window.addFooterLink;
	Window.addFooterLink;

	var form = new Morebits.quickForm(Twinkle.block.callback.evaluate);
	var actionfield = form.append({
		type: 'field',
		label: '동작 종류'
	actionfield.append({
		type: 'checkbox',
		name: 'actiontype',
		event: Twinkle.block.callback.change_action,
		list: [
				label: '사용자 차단',
				value: 'block',
				tooltip: 'Block the relevant user with the given options. If partial block is unchecked, this will be a sitewide block.',
				checked: true
				label: '부분 차단',
				value: 'partial',
				tooltip: 'Enable partial blocks and partial block templates.',
				checked: Twinkle.getPref('defaultToPartialBlocks')
				label: '사용자 토론 문서에 차단 틀 추가',
				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. Check the partial block box for partial block templates.',
				checked: true
		]

	form.append;
	form.append;
	form.append;

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

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

	Twinkle.block.fetchUserInfo(function {
		// clean up preset data , done exactly once, must be before Twinkle.block.callback.change_action is called
		Twinkle.block.transformBlockPresets;
		if (Twinkle.block.currentBlockInfo) {
			Window.addFooterLink('이 사용자의 차단 해제', 'Special:Unblock/' + mw.config.get('wgRelevantUserName'), true);

		// init the controls after user and block info have been fetched
		var evt = document.createEvent('Event');
		evt.initEvent;
		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'),
		usprop: 'groupmemberships',
		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;
			if (Twinkle.block.isRegistered) {
				relevantUserName = 'User:' + mw.config.get('wgRelevantUserName');
				Twinkle.block.userIsBot = !!userinfo.groupmemberships && userinfo.groupmemberships.map(function(e) {
					return e.group;
				relevantUserName = mw.config.get('wgRelevantUserName');
				Twinkle.block.userIsBot = false;

			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;
			// Used later to check if block status changed while filling out the form
			Twinkle.block.blockLogId = Twinkle.block.hasBlockLog ? data.query.logevents[0].logid : false;

			if (typeof fn === 'function') {
				return fn;
			Morebits.status.init($('div[name="currentblock"] span').last[0]);
			Morebits.status.warn;

Twinkle.block.callback.saveFieldset = function twinkleblockCallbacksaveFieldset(fieldset) {
	Twinkle.block[$(fieldset).prop('name')] = {};
	$(fieldset).serializeArray.forEach(function(el) {
		// namespaces and pages for partial blocks are overwritten
		// here, but we're handling them elsewhere so that's fine
		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);
	// Make ifs shorter
	var blockBox = $form.find('[name=actiontype][value=block]').is(':checked');
	var templateBox = $form.find('[name=actiontype][value=template]').is(':checked');
	var partial = $form.find('[name=actiontype][value=partial]');
	var partialBox = partial.is(':checked');
	var blockGroup = partialBox ? Twinkle.block.blockGroupsPartial : Twinkle.block.blockGroups;

	partial.prop;

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

	if (blockBox) {
		field_preset = new Morebits.quickForm.element;
		field_preset.append({
			type: 'select',
			name: 'preset',
			label: 'Choose a preset:',
			event: Twinkle.block.callback.change_preset,
			list: Twinkle.block.callback.filtered_block_groups(blockGroup)

		field_block_options = new Morebits.quickForm.element;
		field_block_options.append;
		field_block_options.append;
		field_block_options.append({
			type: 'select',
			name: 'expiry_preset',
			label: '기한:',
			event: Twinkle.block.callback.change_expiry,
			list: [
			]
		field_block_options.append({
			type: 'input',
			name: 'expiry',
			label: '사용자 지정 기한',
			tooltip: 'You can use relative times, like "1 minute" or "19 days", or absolute timestamps, "yyyymmddhhmm" .',
			value: Twinkle.block.field_block_options.expiry || Twinkle.block.field_template_options.template_expiry

		if (partialBox) { // Partial block
			field_block_options.append({
				type: 'select',
				multiple: true,
				name: 'pagerestrictions',
				label: '편집을 차단한 특정 문서',
				value: '',
				tooltip: '최대 문서 10개'
			var ns = field_block_options.append({
				type: 'select',
				multiple: true,
				name: 'namespacerestrictions',
				label: '이름공간 차단',
				value: '',
				tooltip: '이 이름공간의 편집을 차단합니다.'
			$.each(menuFormattedNamespaces, function {
				// Ignore -1: Special; -2: Media; and 2300-2303: Gadget (talk) and Gadget definition (talk)
				if (number >= 0 && number < 830) {
					ns.append;

		var blockoptions = [
				checked: Twinkle.block.field_block_options.nocreate,
				label: '계정 생성 막음',
				name: 'nocreate',
				value: '1'
				checked: Twinkle.block.field_block_options.noemail,
				label: '이메일 보내기 막음',
				name: 'noemail',
				value: '1'
				checked: Twinkle.block.field_block_options.disabletalk,
				label: '차단 중 자신의 사용자 토론 문서를 편집하지 못하게 막음',
				name: 'disabletalk',
				value: '1',
				tooltip: partialBox ? '부분 차단을 수행하는 경우, 사용자토론 공간의 편집을 막지 않게 하려면 이 항목을 체크하지 말아야 합니다' : ''
		];

		if (Twinkle.block.isRegistered) {
			blockoptions.push({
				checked: Twinkle.block.field_block_options.autoblock,
				label: '사용 중인 IP 주소 자동 차단',
				name: 'autoblock',
				value: '1'
			blockoptions.push({
				checked: Twinkle.block.field_block_options.hardblock,
				label: '이 IP 주소를 사용하여 로그인하는 사용자의 편집을 막음',
				name: 'hardblock',
				value: '1'

		blockoptions.push({
			checked: Twinkle.block.field_block_options.watchuser,
			label: '사용자 및 사용자 토론 문서 주시',
			name: 'watchuser',
			value: '1'

		field_block_options.append({
			type: 'checkbox',
			name: 'blockoptions',
			list: blockoptions
		field_block_options.append({
			type: 'textarea',
			label: '이유 (차단 기록):',
			name: 'reason',
			tooltip: 'Consider adding helpful details to the default message.',
			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;

	if (templateBox) {
		field_template_options = new Morebits.quickForm.element;
		field_template_options.append({
			type: 'select',
			name: 'template',
			label: '토론 문서 틀 선택:',
			event: Twinkle.block.callback.change_template,
			list: Twinkle.block.callback.filtered_block_groups,
			value: Twinkle.block.field_template_options.template
		field_template_options.append({
			type: 'input',
			name: 'article',
			display: 'none',
			label: '연결된 문서',
			value: '',
			tooltip: 'A page can be linked within the notice, perhaps if it was the primary target of disruption. Leave empty for no page to be linked.'

		// Only visible if partial and not blocking
		field_template_options.append({
			type: 'input',
			name: 'area',
			display: 'none',
			label: 'Area blocked from',
			value: '',
			tooltip: 'Optional explanation of the pages or namespaces the user was blocked from editing.'

		if (!blockBox) {
			field_template_options.append({
				type: 'input',
				name: 'template_expiry',
				display: 'none',
				label: '차단 기간: ',
				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 (blockBox) {
			field_template_options.append({
				type: 'checkbox',
				name: 'blank_duration',
				list: [
						label: '틀에 차단 기한을 포함하지 않음',
						checked: Twinkle.block.field_template_options.blank_duration,
						tooltip: 'Instead of including the duration, make the block template read "You have been blocked temporarily..."'
				]
			field_template_options.append({
				type: 'checkbox',
				list: [
						label: '토론 문서 접근 비활성화',
						name: 'notalk',
						checked: Twinkle.block.field_template_options.notalk,
						tooltip: 'Make the block template state that the user\'s talk page access has been removed'
						label: 'User blocked from sending email',
						name: 'noemail_template',
						checked: Twinkle.block.field_template_options.noemail_template,
						tooltip: 'If the area is not provided, make the block template state that the user\'s email access has been removed'
						label: 'User blocked from creating accounts',
						name: 'nocreate_template',
						checked: Twinkle.block.field_template_options.nocreate_template,
						tooltip: 'If the area is not provided, make the block template state that the user\'s ability to create accounts has been removed'
				]

		var $previewlink = $('');
		$previewlink.off('click').on('click', function {
			Twinkle.block.callback.preview($form[0]);
		$previewlink.css({cursor: 'pointer'});
		field_template_options.append;
		field_template_options.append;

	var oldfield;
	if (field_preset) {
		oldfield = $form.find('fieldset[name="field_preset"]')[0];
		oldfield.parentNode.replaceChild;
		$form.find('fieldset[name="field_preset"]').hide;
	if (field_block_options) {
		oldfield = $form.find('fieldset[name="field_block_options"]')[0];
		oldfield.parentNode.replaceChild;

		$form.find('[name=pagerestrictions]').select2({
			width: '100%',
			placeholder: 'Select pages to block user from',
			language: {
				errorLoading: function {
					return 'Incomplete or invalid search term';
			maximumSelectionLength: 10, // Software limitation phab:T202776
			minimumInputLength: 1, // prevent ajax call when empty
			ajax: {
				url: mw.util.wikiScript('api'),
				dataType: 'json',
				delay: 100,
				data: function(params) {
					var title = mw.Title.newFromText(params.term);
					if (!title) {
						return;
					return {
						'action': 'query',
						'format': 'json',
						'list': 'allpages',
						'apfrom': title.title,
						'apnamespace': title.namespace,
						'aplimit': '10'
				processResults: function(data) {
					return {
						results: data.query.allpages.map(function(page) {
							var title = mw.Title.newFromText.toText;
							return {
								id: title,
								text: title
			templateSelection: function(choice) {
				return $('').text(choice.text).attr({
					href: mw.util.getUrl(choice.text),
					target: '_blank'

		$form.find('[name=namespacerestrictions]').select2({
			width: '100%',
			matcher: Morebits.select2.matchers.wordBeginning,
			language: {
				searching: Morebits.select2.queryInterceptor
			templateResult: Morebits.select2.highlightSearchMatches,
			placeholder: 'Select namespaces to block user from'

		mw.util.addCSS(
			// Reduce padding
			'.select2-results .select2-results__option { padding-top: 1px; padding-bottom: 1px; }' +
			// Adjust font size
			'.select2-container .select2-dropdown .select2-results { font-size: 13px; }' +
			'.select2-container .selection .select2-selection__rendered { font-size: 13px; }' +
			// Remove black border
			'.select2-container--default.select2-container--focus .select2-selection--multiple { border: 1px solid #aaa; }' +
			// Make the tiny cross larger
			'.select2-selection__choice__remove { font-size: 130%; }'
		);
		$form.find('fieldset[name="field_block_options"]').hide;
		// Clear select2 options
		$form.find('[name=pagerestrictions]').val(null).trigger('change');
		$form.find('[name=namespacerestrictions]').val(null).trigger('change');
	if (field_template_options) {
		oldfield = $form.find('fieldset[name="field_template_options"]')[0];
		oldfield.parentNode.replaceChild;
		e.target.form.root.previewer = new Morebits.wiki.preview($(e.target.form.root).find('#twinkleblock-previewbox').last[0]);
		$form.find('fieldset[name="field_template_options"]').hide;

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

		Morebits.status.init($('div[name="hasblocklog"] span').last[0]);
		Morebits.status.warn;

	if (Twinkle.block.currentBlockInfo) {
		Morebits.status.init($('div[name="currentblock"] span').last[0]);
		// list=blocks without bkprops (as we do in fetchUerInfo)
		// returns partial: '' if the user is partially blocked
		var statusStr = relevantUserName + ' is ' + (Twinkle.block.currentBlockInfo.partial === '' ? 'partially blocked' : 'blocked sitewide');
		if (Twinkle.block.currentBlockInfo.expiry === 'infinity') {
			statusStr += ' (indef)';
			statusStr += ' (expires ' + new Morebits.date(Twinkle.block.currentBlockInfo.expiry).calendar('utc') + ')';
		var infoStr = 'Submit query to change the block';
		if (Twinkle.block.currentBlockInfo.partial === undefined && partialBox) {
			infoStr += ', converting to a partial block';
			infoStr += ', converting to a sitewide block';
		Morebits.status.warn;
		Twinkle.block.callback.update_form;
	if (templateBox) {
		// make sure all the fields are correct based on defaults
		if (blockBox) {
			Twinkle.block.callback.change_preset(e);
			Twinkle.block.callback.change_template(e);

/*
Twinkle.block.blockPresetsInfo = {
	'anonblock': {
		expiry: '31 hours',
		forAnonOnly: true,
		nocreate: true,
		nonstandard: true,
		reason: '',
		sig: '~~~~'
	'anonblock - school': {
		expiry: '36 hours',
		forAnonOnly: true,
		nocreate: true,
		nonstandard: true,
		reason: ' ',
		templateName: 'anonblock',
		sig: '~~~~'
	'blocked proxy': {
		expiry: '1 year',
		forAnonOnly: true,
		nocreate: true,
		nonstandard: true,
		hardblock: 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'
	'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-nothereblock': {
		autoblock: true,
		expiry: 'infinity',
		nocreate: true,
		reason: 'Clearly not here to build an 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-socialmediablock': {
		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-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-wellknown': {
		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-upeblock': {
		autoblock: true,
		expiry: 'infinity',
		forRegisteredOnly: true,
		nocreate: true,
		pageParam: true,
		reason: 'Undisclosed paid editing in violation of the WMF Terms of Use',
		summary: 'You have been indefinitely blocked from editing because your account is being used in violation of Wikipedia policy on undisclosed paid advocacy'
	'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 to prevent further 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

	// Begin partial block templates, accessed in Twinkle.block.blockGroupsPartial
	'uw-acpblock': {
		autoblock: true,
		expiry: '48 hours',
		nocreate: true,
		pageParam: false,
		reasonParam: true,
		reason: 'Misusing multiple accounts',
		summary: 'You have been blocked from creating accounts for misusing multiple accounts'
	'uw-acpblockindef': {
		autoblock: true,
		expiry: 'infinity',
		forRegisteredOnly: true,
		nocreate: true,
		pageParam: false,
		reasonParam: true,
		reason: 'Misusing multiple accounts',
		summary: 'You have been indefinitely blocked from creating accounts for misusing multiple accounts'
	'uw-aepblock': {
		autoblock: true,
		nocreate: false,
		pageParam: false,
		reason: 'Arbitration enforcement',
		reasonParam: true,
		summary: 'You have been blocked from editing for violating an arbitration decision'
	'uw-epblock': {
		autoblock: true,
		expiry: 'infinity',
		forRegisteredOnly: true,
		nocreate: false,
		noemail: true,
		pageParam: false,
		reasonParam: true,
		reason: 'Email harassment',
		summary: 'You have been blocked from emailing other editors for harassment'
	'uw-ewpblock': {
		autoblock: true,
		expiry: '24 hours',
		nocreate: false,
		pageParam: false,
		reasonParam: true,
		reason: 'Edit warring',
		summary: 'You have been blocked from editing certain areas of the encyclopedia to prevent further disruption due to edit warring'
	'uw-pblock': {
		autoblock: true,
		expiry: '24 hours',
		nocreate: false,
		pageParam: false,
		reasonParam: true,
		summary: 'You have been partially blocked from certain areas of the encyclopedia'
	'uw-pblockindef': {
		autoblock: true,
		expiry: 'infinity',
		forRegisteredOnly: true,
		nocreate: false,
		pageParam: false,
		reasonParam: true,
		summary: 'You have been indefinitely partially blocked from certain areas of the encyclopedia'

Twinkle.block.transformBlockPresets = function twinkleblockTransformBlockPresets {
	// supply sensible defaults
	$.each(Twinkle.block.blockPresetsInfo, function {
		settings.summary = settings.summary || settings.reason;
		settings.sig = settings.sig !== undefined ? settings.sig : 'yes';
		settings.indefinite = settings.indefinite || Morebits.string.isInfinity(settings.expiry);

		if (!Twinkle.block.isRegistered && settings.indefinite) {
			settings.expiry = '31 hours';
			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: 
// value: 
Twinkle.block.blockGroups = [
		label: 'Common block reasons',
		list: [
		]
		label: 'Extended reasons',
		list: [
		]
		label: 'Username violations',
		list: [
		]
		label: 'Templated reasons',
		list: [
			// { label: 'rangeblock', value: 'rangeblock' }, // placeholder for when we add support for rangeblocks
		]
];

Twinkle.block.blockGroupsPartial = [
		label: 'Common partial block reasons',
		list: [
		]
		label: 'Extended partial block reasons',
		list: [
		]
];

Twinkle.block.callback.filtered_block_groups = function twinkleblockCallbackFilteredBlockGroups {
	return $.map(group, 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,
					disabled: !!blockPreset.disabled
		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;
	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;
		Morebits.quickForm.setElementVisibility;
		expiry.value = e.target.value;

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

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

	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;
		this.form.reason.value = reason + ' ';
		this.form.reason.value = reason + '; see also ' + seeAlsoMessage;

Twinkle.block.callback.update_form = function twinkleblockCallbackUpdateForm {
	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';
			form.expiry_preset.value = data.expiry || 'custom';

		form.expiry.value = expiry;
		if (form.expiry_preset.value === 'custom') {
			Morebits.quickForm.setElementVisibility;
			Morebits.quickForm.setElementVisibility;

	// 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.userIsBot || /bot\b/i.test(relevantUserName)) {
		data.autoblock = false;

	$(form).find('[name=field_block_options]').find(':checkbox').each(function {
		// 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;

	if (data.prependReason && data.reason) {
		form.reason.value = data.reason + '; ' + form.reason.value;
		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 = 'infinity';
			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;
		Morebits.quickForm.setElementVisibility(form.noemail_template.parentNode, $(form).find('[name=actiontype][value=partial]').is(':checked') && !$(form).find('[name=actiontype][value=block]').is(':checked'));
		Morebits.quickForm.setElementVisibility(form.nocreate_template.parentNode, $(form).find('[name=actiontype][value=partial]').is(':checked') && !$(form).find('[name=actiontype][value=block]').is(':checked'));
		Morebits.quickForm.setElementVisibility;
	Morebits.quickForm.setElementVisibility;
	Morebits.quickForm.setElementVisibility;

	// Partial block
	Morebits.quickForm.setElementVisibility(form.area.parentNode, $(form).find('[name=actiontype][value=partial]').is(':checked') && !$(form).find('[name=actiontype][value=block]').is(':checked'));

	form.root.previewer.closePreview;
Twinkle.block.prev_template_expiry = 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: Morebits.string.isInfinity(form.template_expiry ? form.template_expiry.value : form.expiry.value),
		reason: form.block_reason.value,
		template: form.template.value,
		partial: $(form).find('[name=actiontype][value=partial]').is(':checked'),
		pagerestrictions: $(form.pagerestrictions).val || [],
		namespacerestrictions: $(form.namespacerestrictions).val || [],
		noemail: form.noemail.checked || (form.noemail_template ? form.noemail_template.checked : false),
		nocreate: form.nocreate.checked || (form.nocreate_template ? form.nocreate_template.checked : false),
		area: form.area.value

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

	form.previewer.beginRender(templateText, 'User_talk:' + mw.config.get('wgRelevantUserName')); // Force wikitext/correct username

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'),
		toPartial = $form.find('[name=actiontype][value=partial]').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

	// Partial API requires this to be gone, not false or 0
	if (toPartial) {
		blockoptions.partial = templateoptions.partial = true;
	templateoptions.pagerestrictions = $form.find('[name=pagerestrictions]').val || [];
	templateoptions.namespacerestrictions = $form.find('[name=namespacerestrictions]').val || [];
	// Format for API here rather than in saveFieldset
	blockoptions.pagerestrictions = templateoptions.pagerestrictions.join('|');
	blockoptions.namespacerestrictions = templateoptions.namespacerestrictions.join('|');

	// 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.partial) {
			if (blockoptions.disabletalk && blockoptions.namespacerestrictions.indexOf('3') === -1) {
				return alert('Partial blocks cannot prevent talk page access unless also restricting them from editing User talk space!');
			if (!blockoptions.namespacerestrictions && !blockoptions.pagerestrictions) {
				if (!blockoptions.noemail && !blockoptions.nocreate) { // Blank entries technically allowed phab:T208645
					return alert;
					return;
		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;

		/*
		 Check if block status changed while processing the form.

		 There's a lot to consider here. list=blocks provides the
		 current block status, but there are at least two issues with
		 relying on it. First, the id doesn't update on a reblock,
		 meaning the individual parameters need to be compared. This
		 can be done roughly with JSON.stringify - we can thankfully
		 rely on order from the server, although sorting would be
		 fine if not - but falsey values are problematic and is
		 non-ideal. More importantly, list=blocks won't indicate if a
		 non-blocked user is blocked then unblocked. This should be
		 exceedingy rare, but regardless, we thus need to check
		 list=logevents, which has a nicely updating logid
		 parameter. We can't rely just on that, though, since it
		 doesn't account for blocks that have expired on their own.

		 As such, we use both. Using some ternaries, the logid
		 variables are false if there's no logevents, so if they
		 aren't equal we defintely have a changed entry (send
		 confirmation). If they are equal, then either the user was
		 never blocked  or there's no new block, in which case either
		 a block expired  or the
		 same block is still active .
		api.get({
			format: 'json',
			action: 'query',
			list: 'blocks|logevents',
			letype: 'block',
			lelimit: 1,
			letitle: 'User:' + blockoptions.user,
			bkusers: blockoptions.user
			var block = data.query.blocks[0];
			var logevents = data.query.logevents[0];
			var logid = data.query.logevents.length ? logevents.logid : false;

			if (logid !== Twinkle.block.blockLogId || !!block !== !!Twinkle.block.currentBlockInfo) {
				var message = 'The block status of ' + mw.config.get('wgRelevantUserName') + ' has changed. ';
				if (block) {
					message += 'New status: ';
					message += 'Last entry: ';

				var logExpiry = '';
				if (logevents.params.duration) {
					if (logevents.params.duration === 'infinity') {
						logExpiry = 'indefinitely';
						var expiryDate = new Morebits.date(logevents.params.expiry);
						logExpiry += (expiryDate.isBefore(new Date) ? ', expired ' : ' until ') + expiryDate.calendar;
					logExpiry = ' ' + new Morebits.date(logevents.timestamp).calendar;
				message += Morebits.string.toUpperCaseFirstChar(logevents.action) + 'ed by ' + logevents.user + logExpiry +
					' for "' + logevents.comment + '". Do you want to override with your settings?';

				if (!confirm(message)) {
					Morebits.status.info;
					return;
				blockoptions.reblock = 1; // Writing over a block will fail otherwise
			// execute block
			blockoptions.token = mw.user.tokens.get('csrfToken');
			var mbApi = new Morebits.wiki.api('Executing block', blockoptions, function {
				statusElement.info('Completed');
				if (toWarn) {
					Twinkle.block.callback.issue_template(templateoptions);
			mbApi.post;
		Morebits.simpleWindow.setButtonsEnabled(false);

		Morebits.status.init(e.target);
		Twinkle.block.callback.issue_template(templateoptions);
		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,
		noemail: Twinkle.block.field_template_options.noemail_template,
		nocreate: Twinkle.block.field_template_options.nocreate_template

	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;
	wikipedia_page.setCallbackParameters(params);
	wikipedia_page.setFollowRedirect;
	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 Morebits.date(pageobj.getLoadTime);

	var dateHeaderRegex = date.monthHeaderRegex, 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';

	params.indefinite = Morebits.string.isInfinity(params.expiry);

	if (Twinkle.getPref('blankTalkpageOnIndefBlock') && params.template !== 'uw-lblock' && params.indefinite) {
		Morebits.status.info;
		text = date.monthHeader + '\n';
		Morebits.status.info;
		text += date.monthHeader + '\n';

	params.expiry = typeof params.template_expiry !== 'undefined' ? params.template_expiry : params.expiry;

	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;

Twinkle.addInitCallback;