コンテンツにスキップ

英文维基 | 中文维基 | 日文维基 | 草榴社区

利用者:Syunsyunminmin/Twinkle/twinkleblock.js

お知らせ: 保存した後、ブラウザのキャッシュをクリアしてページを再読み込みする必要があります。

多くの WindowsLinux のブラウザ

  • Ctrl を押しながら F5 を押す。

Mac における Safari

  • Shift を押しながら、更新ボタン をクリックする。

Mac における ChromeFirefox

  • Cmd Shift を押しながら R を押す。

詳細についてはWikipedia:キャッシュを消すをご覧ください。

// <nowiki>


(function($) {

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

/*
 ****************************************
 *** twinkleblock.js: Block module
 ****************************************
 * Mode of invocation:     Tab ("Block")
 * Active on:              Any page with relevant user name (userspace, contribs, etc.)
 */

Twinkle.block = function twinkleblock() {
	relevantUserName = mw.config.get('wgRelevantUserName');
	// should show on Contributions or Block pages, anywhere there's a relevant user
	// Ignore ranges wider than the CIDR limit
	if (Morebits.userIsSysop && relevantUserName && (!Morebits.ip.isRange(relevantUserName) || Morebits.ip.validCIDR(relevantUserName))) {
		Twinkle.addPortletLink(Twinkle.block.callback, 'Block', 'tw-block', '利用者をブロック');
	}
};

Twinkle.block.callback = function twinkleblockCallback() {
	if (relevantUserName === mw.config.get('wgUserName') &&
			!confirm('自分自身をブロックしようとしています!続行しますか?')) {
		return;
	}

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

	var Window = new Morebits.simpleWindow(650, 530);
	// need to be verbose about who we're blocking
	Window.setTitle(relevantUserName + 'をブロックまたは投稿ブロックテンプレートを送信する');
	Window.setScriptName('Twinkle');
	Window.addFooterLink('投稿ブロックテンプレート', 'Wikipedia:主要なテンプレート/利用者‐会話名前空間#投稿ブロックテンプレート');
	Window.addFooterLink('投稿ブロックの方針', 'WP:BLOCK');
	Window.addFooterLink('ブロック設定', 'User:Syunsyunminmin/Twinkle/Preferences#block');
	Window.addFooterLink('Twinkle help', ':en:WP:TW/DOC#block');
	Window.addFooterLink('フィードバック', 'User talk:Syunsyunminmin/Twinkle');

	// Always added, hidden later if actual user not blocked
	Window.addFooterLink('ブロックを解除', 'Special:Unblock/' + relevantUserName, true);

	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: '指定されたオプションで対象利用者をブロックします。部分ブロックのチェックが外れている場合、サイト全体のブロックとなります。',
				checked: true
			},
			{
				label: '部分ブロック',
				value: 'partial',
				tooltip: '部分ブロックと部分ブロックテンプレートを有効にする。',
				checked: Twinkle.getPref('defaultToPartialBlocks') // Overridden if already blocked
			},
			{
				label: '利用者会話ページに投稿ブロックテンプレートを追加',
				value: 'template',
				tooltip: '(※未対応)ブロックした管理者がブロックテンプレートを送信するのを忘れた場合、あるいは、テンプレートを送信せずに利用者をブロックした場合、これを使って適切なテンプレートを送信することができます。部分ブロックテンプレートは部分ブロックボックスにチェックを入れてください。',
				// Disallow when viewing the block dialog on an IP range
				disabled: Morebits.ip.isRange(relevantUserName)
			}
		]
	});

	/*
	  Add option for IPv6 ranges smaller than /64 to upgrade to the 64
	  CIDR ([[WP:/64]]).  This is one of the few places where we want
	  wgRelevantUserName since this depends entirely on the original user.
	  In theory, we shouldn't use Morebits.ip.get64 here since since we want
	  to exclude functionally-equivalent /64s.  That'd be:
	  // if (mw.util.isIPv6Address(mw.config.get('wgRelevantUserName'), true) &&
	  // (mw.util.isIPv6Address(mw.config.get('wgRelevantUserName')) || parseInt(mw.config.get('wgRelevantUserName').replace(/^(.+?)\/?(\d{1,3})?$/, '$2'), 10) > 64)) {
	  In practice, though, since functionally-equivalent ranges are
	  (mis)treated as separate by MediaWiki's logging ([[phab:T146628]]),
	  using Morebits.ip.get64 provides a modicum of relief in thise case.
	*/
	var sixtyFour = Morebits.ip.get64(mw.config.get('wgRelevantUserName'));
	if (sixtyFour && sixtyFour !== mw.config.get('wgRelevantUserName')) {
		var block64field = form.append({
			type: 'field',
			label: '/64範囲ブロックに変換',
			name: 'field_64'
		});
		block64field.append({
			type: 'div',
			style: 'margin-bottom: 0.5em',
			label: ['通常は、より良いとは言わないまでも、', $.parseHTML('<a target="_blank" href="' + mw.util.getUrl(':en:WP:/64') + '">/64の範囲ブロック</a>')[0], 'するだけで問題ないです (',
				$.parseHTML('<a target="_blank" href="' + mw.util.getUrl('Special:Contributions/' + sixtyFour) + '">' + sixtyFour + '</a>)')[0], ').']
		});
		block64field.append({
			type: 'checkbox',
			name: 'block64',
			event: Twinkle.block.callback.change_block64,
			list: [{
				checked: Twinkle.getPref('defaultToBlock64'),
				label: '/64を代わりにブロックする',
				value: 'block64',
				tooltip: Morebits.ip.isRange(mw.config.get('wgRelevantUserName')) ? 'Will eschew leaving a template.' : 'Any template issued will go to the original IP: ' + mw.config.get('wgRelevantUserName')
			}]
		});
	}

	form.append({ type: 'field', label: 'プリセット', name: 'field_preset' });
	form.append({ type: 'field', label: 'テンプレートオプション', name: 'field_template_options' });
	form.append({ type: 'field', label: 'ブロックオプション', name: 'field_block_options' });

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

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

	Twinkle.block.fetchUserInfo(function() {
		// Toggle initial partial state depending on prior block type,
		// will override the defaultToPartialBlocks pref
		if (blockedUserName === relevantUserName) {
			$(result).find('[name=actiontype][value=partial]').prop('checked', Twinkle.block.currentBlockInfo.partial === '');
		}

		// 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);

		if (result.block64 && result.block64.checked) {
			// Calls the same change_action event once finished
			result.block64.dispatchEvent(evt);
		} else {
			result.actiontype[0].dispatchEvent(evt);
		}
	});
};

// Store fetched user data, only relevant if switching IPv6 to a /64
Twinkle.block.fetchedData = {};
// Processes the data from a a query response, separated from
// Twinkle.block.fetchUserInfo to allow reprocessing of already-fetched data
Twinkle.block.processUserInfo = function twinkleblockProcessUserInfo(data, fn) {
	var blockinfo = data.query.blocks[0],
		userinfo = data.query.users[0];
	// If an IP is blocked *and* rangeblocked, the above finds
	// whichever block is more recent, not necessarily correct.
	// Three seems... unlikely
	if (data.query.blocks.length > 1 && blockinfo.user !== relevantUserName) {
		blockinfo = data.query.blocks[1];
	}
	// Cache response, used when toggling /64 blocks
	Twinkle.block.fetchedData[userinfo.name] = data;

	Twinkle.block.isRegistered = !!userinfo.userid;
	if (Twinkle.block.isRegistered) {
		Twinkle.block.userIsBot = !!userinfo.groupmemberships && userinfo.groupmemberships.map(function(e) {
			return e.group;
		}).indexOf('bot') !== -1;
	} else {
		Twinkle.block.userIsBot = false;
	}

	if (blockinfo) {
		// handle frustrating system of inverted boolean values
		blockinfo.disabletalk = blockinfo.allowusertalk === undefined;
		blockinfo.hardblock = blockinfo.anononly === undefined;
	}
	// will undefine if no blocks present
	Twinkle.block.currentBlockInfo = blockinfo;
	blockedUserName = Twinkle.block.currentBlockInfo && Twinkle.block.currentBlockInfo.user;

	// Toggle unblock link if not the user in question; always first
	var unblockLink = document.querySelector('.morebits-dialog-footerlinks a');
	if (blockedUserName !== relevantUserName) {
		unblockLink.hidden = true, unblockLink.nextSibling.hidden = true; // link+trailing bullet
	} else {
		unblockLink.hidden = false, unblockLink.nextSibling.hidden = false; // link+trailing bullet
	}

	// Semi-busted on ranges, see [[phab:T270737]] and [[phab:T146628]].
	// Basically, logevents doesn't treat functionally-equivalent ranges
	// as equivalent, meaning any functionally-equivalent IP range is
	// misinterpreted by the log throughout.  Without logevents
	// redirecting (like Special:Block does) we would need a function to
	// parse ranges, which is a pain.  IPUtils has the code, but it'd be a
	// lot of cruft for one purpose.
	Twinkle.block.hasBlockLog = !!data.query.logevents.length;
	Twinkle.block.blockLog = Twinkle.block.hasBlockLog && data.query.logevents;
	// 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();
	}
};

Twinkle.block.fetchUserInfo = function twinkleblockFetchUserInfo(fn) {
	var query = {
		format: 'json',
		action: 'query',
		list: 'blocks|users|logevents',
		letype: 'block',
		lelimit: 1,
		letitle: 'User:' + relevantUserName,
		bkprop: 'expiry|reason|flags|restrictions|range|user',
		ususers: relevantUserName
	};

	// bkusers doesn't catch single IPs blocked as part of a range block
	if (mw.util.isIPAddress(relevantUserName, true)) {
		query.bkip = relevantUserName;
	} else {
		query.bkusers = relevantUserName;
		// groupmemberships only relevant for registered users
		query.usprop = 'groupmemberships';
	}

	api.get(query).then(function(data) {
		Twinkle.block.processUserInfo(data, fn);
	}, function(msg) {
		Morebits.status.init($('div[name="currentblock"] span').last()[0]);
		Morebits.status.warn('利用者情報の取得エラー', msg);
	});
};

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_block64 = function twinkleblockCallbackChangeBlock64(e) {
	var $form = $(e.target.form), $block64 = $form.find('[name=block64]');

	// Show/hide block64 button
	// Single IPv6, or IPv6 range smaller than a /64
	var priorName = relevantUserName;
	if ($block64.is(':checked')) {
		relevantUserName = Morebits.ip.get64(mw.config.get('wgRelevantUserName'));
	} else {
		relevantUserName = mw.config.get('wgRelevantUserName');
	}
	// No templates for ranges, but if the original user is a single IP, offer the option
	// (done separately in Twinkle.block.callback.issue_template)
	var originalIsRange = Morebits.ip.isRange(mw.config.get('wgRelevantUserName'));
	$form.find('[name=actiontype][value=template]').prop('disabled', originalIsRange)/*.prop('checked', !originalIsRange)*/;

	// Refetch/reprocess user info then regenerate the main content
	var regenerateForm = function() {
		// Tweak titlebar text.  In theory, we could save the dialog
		// at initialization and then use `.setTitle` or
		// `dialog('option', 'title')`, but in practice that swallows
		// the scriptName and requires `.display`ing, which jumps the
		// window.  It's just a line of text, so this is fine.
		var titleBar = document.querySelector('.ui-dialog-title').firstChild.nextSibling;
		titleBar.nodeValue = titleBar.nodeValue.replace(priorName, relevantUserName);
		// Tweak unblock link
		var unblockLink = document.querySelector('.morebits-dialog-footerlinks a');
		unblockLink.href = unblockLink.href.replace(priorName, relevantUserName);
		unblockLink.title = unblockLink.title.replace(priorName, relevantUserName);

		// Correct partial state
		$form.find('[name=actiontype][value=partial]').prop('checked', Twinkle.getPref('defaultToPartialBlocks'));
		if (blockedUserName === relevantUserName) {
			$form.find('[name=actiontype][value=partial]').prop('checked', Twinkle.block.currentBlockInfo.partial === '');
		}

		// Set content appropriately
		Twinkle.block.callback.change_action(e);
	};

	if (Twinkle.block.fetchedData[relevantUserName]) {
		Twinkle.block.processUserInfo(Twinkle.block.fetchedData[relevantUserName], regenerateForm);
	} else {
		Twinkle.block.fetchUserInfo(regenerateForm);
	}
};

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('disabled', !blockBox && !templateBox);

	// Add current block parameters as default preset
	var prior = { label: '前ブロック' };
	if (blockedUserName === relevantUserName) {
		Twinkle.block.blockPresetsInfo.prior = Twinkle.block.currentBlockInfo;
		// value not a valid template selection, chosen below by setting templateName
		prior.list = [{ label: '前のブロック設定', value: 'prior', selected: true }];

		// Arrays of objects are annoying to check
		if (!blockGroup.some(function(bg) {
			return bg.label === prior.label;
		})) {
			blockGroup.push(prior);
		}

		// Always ensure proper template exists/is selected when switching modes
		if (partialBox) {
			Twinkle.block.blockPresetsInfo.prior.templateName = Morebits.string.isInfinity(Twinkle.block.currentBlockInfo.expiry) ? 'uw-pblockindef' : 'uw-pblock';
		} else {
			if (!Twinkle.block.isRegistered) {
				Twinkle.block.blockPresetsInfo.prior.templateName = 'uw-ablock';
			} else {
				Twinkle.block.blockPresetsInfo.prior.templateName = Morebits.string.isInfinity(Twinkle.block.currentBlockInfo.expiry) ? 'uw-blockindef' : 'uw-block';
			}
		}
	} else {
		// But first remove any prior prior
		blockGroup = blockGroup.filter(function(bg) {
			return bg.label !== prior.label;
		});
	}

	// Can be in preset or template field, so the old one in the template
	// field will linger. No need to keep the old value around, so just
	// remove it; saves trouble when hiding/evaluating

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

	if (blockBox) {
		field_preset = new Morebits.quickForm.element({ type: 'field', label: 'プリセット', name: 'field_preset' });
		field_preset.append({
			type: 'select',
			name: 'preset',
			label: 'プリセットを選択:',
			event: Twinkle.block.callback.change_preset,
			list: Twinkle.block.callback.filtered_block_groups(blockGroup)
		});

		field_block_options = new Morebits.quickForm.element({ type: 'field', label: 'ブロックオプション', name: 'field_block_options' });
		field_block_options.append({ type: 'div', name: 'currentblock', label: ' ' });
		field_block_options.append({ type: 'div', name: 'hasblocklog', label: ' ' });
		field_block_options.append({
			type: 'select',
			name: 'expiry_preset',
			label: '期限:',
			event: Twinkle.block.callback.change_expiry,
			list: [
				{ label: 'その他', value: 'custom', selected: true },
				{ label: '無期限', value: 'infinity' },
				{ label: '3時間', value: '3 hours' },
				{ label: '12時間', value: '12 hours' },
				{ label: '24時間', value: '24 hours' },
				{ label: '31時間', value: '31 hours' },
				{ label: '36時間', value: '36 hours' },
				{ label: '48時間', value: '48 hours' },
				{ label: '60時間', value: '60 hours' },
				{ label: '72時間', value: '72 hours' },
				{ label: '1週間', value: '1 week' },
				{ label: '2週間', value: '2 weeks' },
				{ label: '1ヶ月間', value: '1 month' },
				{ label: '3ヶ月間', value: '3 months' },
				{ label: '6ヶ月間', value: '6 months' },
				{ label: '1年間', value: '1 year' },
				{ label: '2年間', value: '2 years' },
				{ label: '3年間', value: '3 years' }
			]
		});
		field_block_options.append({
			type: 'input',
			name: 'expiry',
			label: 'その他の期限',
			tooltip: '"1 minute" (1分) や "19 days" (19日) のような相対的な時間や、"yyyymmddhhmm"のような絶対的なタイムスタンプを使用することができます。 (例: "200602011405"は2006年2月1日 14:05 UTCを意味します)',
			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(number, name) {
				// Ignore -1: Special; -2: Media; and 2300-2303: Gadget (talk) and Gadget definition (talk)
				if (number >= 0 && number < 830) {
					ns.append({ type: 'option', label: name, value: number });
				}
			});
		}

		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'
			});
		} else {
			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: '既定のブロックメッセージに追加して役立つ詳細を入力してください',
			value: Twinkle.block.field_block_options.reason
		});

		field_block_options.append({
			type: 'div',
			name: 'filerlog_label',
			label: '参照:',
			style: 'display:inline-block;font-style:normal !important',
			tooltip: 'フィルター記録や削除された投稿が、ブロックの決定に関与したかどうかを示す"参照"メッセージを挿入する。'
		});
		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: 'フィルター記録',
					checked: false,
					value: 'フィルター記録'
				}
			]
		});
		field_block_options.append({
			type: 'checkbox',
			name: 'deleted_see_also',
			event: Twinkle.block.callback.toggle_see_alsos,
			style: 'display:inline-block',
			list: [
				{
					label: '削除された投稿',
					checked: false,
					value: '削除された投稿記録'
				}
			]
		});

		// Yet-another-logevents-doesn't-handle-ranges-well
		if (blockedUserName === relevantUserName) {
			field_block_options.append({ type: 'hidden', name: 'reblock', value: '1' });
		}
	}

	if (templateBox) {
		field_template_options = new Morebits.quickForm.element({ type: 'field', label: 'テンプレートオプション', name: 'field_template_options' });
		field_template_options.append({
			type: 'select',
			name: 'template',
			label: '会話ページのテンプレートを選択:',
			event: Twinkle.block.callback.change_template,
			list: Twinkle.block.callback.filtered_block_groups(blockGroup, true),
			value: Twinkle.block.field_template_options.template
		});

		field_template_options.append({
			type: 'input',
			name: 'article',
			label: 'リンクするページ',
			value: '',
			tooltip: 'そのページが破壊行為の主な対象であった場合、通知内でリンクされることがあります。リンクするページがない場合は空欄にしてください。'
		});

		// Only visible if partial and not blocking
		field_template_options.append({
			type: 'input',
			name: 'area',
			label: 'ブロックされた場所',
			value: '',
			tooltip: 'オプションで、利用者が編集をブロックされたページまたは名前空間の説明。'
		});

		if (!blockBox) {
			field_template_options.append({
				type: 'input',
				name: 'template_expiry',
				label: 'ブロック期間:',
				value: '',
				tooltip: 'ブロックする期間。例えば24 hours、2 weeks、indefiniteなど'
			});
		}
		field_template_options.append({
			type: 'input',
			name: 'block_reason',
			label: '"あなたがブロックされたのは..."',
			tooltip: 'デフォルトの一般的な理由を置き換えるオプションの理由。全般の投稿ブロックテンプレートでのみ利用可能です。',
			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: '期間を含める代わりに、ブロックテンプレートは「あなたは一時的にブロックされました」と表示します..."'
					}
				]
			});
		} else {
			field_template_options.append({
				type: 'checkbox',
				list: [
					{
						label: 'Talk page access disabled',
						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 = $('<a id="twinkleblock-preview-link">プレビュー</a>');
		$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);
		$form.find('fieldset[name="field_64"]').show();


		$form.find('[name=pagerestrictions]').select2({
			width: '100%',
			placeholder: '利用者をブロックするページを選択',
			language: {
				errorLoading: function() {
					return '不完全または無効な検索キーワード';
				}
			},
			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(page.title, page.ns).toText();
							return {
								id: title,
								text: title
							};
						})
					};
				}
			},
			templateSelection: function(choice) {
				return $('<a>').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: '利用者をブロックする名前空間を選択'
		});

		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%; }'
		);
	} else {
		$form.find('fieldset[name="field_block_options"]').hide();
		$form.find('fieldset[name="field_64"]').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(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();
	}

	// Any block, including ranges
	if (Twinkle.block.currentBlockInfo) {
		// false for an ip covered by a range or a smaller range within a larger range;
		// true for a user, single ip block, or the exact range for a range block
		var sameUser = blockedUserName === relevantUserName;

		Morebits.status.init($('div[name="currentblock"] span').last()[0]);
		var statusStr = relevantUserName + 'は' + (Twinkle.block.currentBlockInfo.partial === '' ? '部分ブロック' : 'サイト全体でブロック');

		// Range blocked
		if (Twinkle.block.currentBlockInfo.rangestart !== Twinkle.block.currentBlockInfo.rangeend) {
			if (sameUser) {
				statusStr = '範囲ブロックとして' + statusStr;
			} else {
				statusStr = (Morebits.ip.get64(relevantUserName) === blockedUserName ? '/64' : '') + '範囲ブロックの範囲内にある' + statusStr;
				// Link to the full range
				var $rangeblockloglink = $('<span>').append($('<a target="_blank" href="' + mw.util.getUrl('Special:Log', {action: 'view', page: blockedUserName, type: 'block'}) + '">' + blockedUserName + '</a>)'));
				statusStr += ' (' + $rangeblockloglink.html() + ')';
			}
		}

		if (Twinkle.block.currentBlockInfo.expiry === 'infinity') {
			statusStr += ' (無期限)';
		} else if (new Morebits.date(Twinkle.block.currentBlockInfo.expiry).isValid()) {
			statusStr += ' (期限 ' + new Morebits.date(Twinkle.block.currentBlockInfo.expiry).calendar('utc') + ')';
		}


		var infoStr = 'このフォームは';
		if (sameUser) {
			infoStr += 'ブロックを変更';
			if (Twinkle.block.currentBlockInfo.partial === undefined && partialBox) {
				infoStr += 'し、部分ブロックに変換';
			} else if (Twinkle.block.currentBlockInfo.partial === '' && !partialBox) {
				infoStr += 'し、サイト全体のブロックに変換';
			}
			infoStr += 'します。';
		} else {
			infoStr += 'さらなる' + (partialBox ? '部分' : '') + 'ブロックを追加します。';
		}

		Morebits.status.warn(statusStr, infoStr);

		// Default to the current block conditions on intial form generation
		Twinkle.block.callback.update_form(e, Twinkle.block.currentBlockInfo);
	}

	// This is where T146628 really comes into play: a rangeblock will
	// only return the correct block log if wgRelevantUserName is the
	// exact range, not merely a funtional equivalent
	if (Twinkle.block.hasBlockLog) {
		var $blockloglink = $('<span>').append($('<a target="_blank" href="' + mw.util.getUrl('Special:Log', {action: 'view', page: relevantUserName, type: 'block'}) + '">ブロック記録</a>)'));
		if (!Twinkle.block.currentBlockInfo) {
			var lastBlockAction = Twinkle.block.blockLog[0];
			if (lastBlockAction.action === 'unblock') {
				$blockloglink.append(' (' + new Morebits.date(lastBlockAction.timestamp).calendar('utc') + ' にブロック解除)');
			} else { // block or reblock
				$blockloglink.append(' (' + lastBlockAction.params.duration + ', ' + new Morebits.date(lastBlockAction.params.expiry).calendar('utc') + ' に期限切れ)');
			}
		}

		Morebits.status.init($('div[name="hasblocklog"] span').last()[0]);
		Morebits.status.warn(Twinkle.block.currentBlockInfo ? '以前のブロック' : 'この' + (Morebits.ip.isRange(relevantUserName) ? '範囲' : '利用者') + 'は過去にブロックされています', $blockloglink[0]);
	}

	// Make sure all the fields are correct based on initial defaults
	if (blockBox) {
		Twinkle.block.callback.change_preset(e);
	} else if (templateBox) {
		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:
 * <title of block template> : {
 *   autoblock: <autoblock any IP addresses used (for registered users only)>
 *   disabletalk: <disable user from editing their own talk page while blocked>
 *   expiry: <string - expiry timestamp, can include relative times like "5 months", "2 weeks" etc>
 *   forAnonOnly: <show block option in the interface only if the relevant user is an IP>
 *   forRegisteredOnly: <show block option in the interface only if the relevant user is registered>
 *   label: <string - label for the option of the dropdown in the interface (keep brief)>
 *   noemail: prevent the user from sending email through Special:Emailuser
 *   pageParam: <set if the associated block template accepts a page parameter>
 *   prependReason: <string - prepends the value of 'reason' to the end of the existing reason, namely for when revoking talk page access>
 *   nocreate: <block account creation from the user's IP (for anonymous users only)>
 *   nonstandard: <template does not conform to stewardship of WikiProject User Warnings and may not accept standard parameters>
 *   reason: <string - block rationale, as would appear in the block log,
 *            and the edit summary for when adding block template, unless 'summary' is set>
 *   reasonParam: <set if the associated block template accepts a reason parameter>
 *   sig: <string - set to ~~~~ if block template does not accept "true" as the value, or set null to omit sig param altogether>
 *   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 = {
	'blocked proxy': {
		expiry: '1 year',
		forAnonOnly: true,
		nocreate: true,
		nonstandard: true,
		hardblock: true,
		reason: '{{blocked proxy}}',
		sig: null
	},
	'CheckUser block': {
		expiry: '1 week',
		forAnonOnly: true,
		nocreate: true,
		nonstandard: true,
		reason: '{{CheckUser block}}',
		sig: '~~~~'
	},
	'checkuserblock-account': {
		autoblock: true,
		expiry: 'infinity',
		forRegisteredOnly: true,
		nocreate: true,
		nonstandard: true,
		reason: '{{checkuserblock-account}}',
		sig: '~~~~'
	},
	'checkuserblock-wide': {
		forAnonOnly: true,
		nocreate: true,
		nonstandard: true,
		reason: '{{checkuserblock-wide}}',
		sig: '~~~~'
	},
	'oversightblock': {
		autoblock: true,
		expiry: 'infinity',
		nocreate: true,
		nonstandard: true,
		reason: '{{OversightBlock}}',
		sig: '~~~~'
	},
	'range block login': {
		reason: '{{range block login|<!-- 割当先を入力 -->}}',
		nocreate: true,
		nonstandard: true,
		forAnonOnly: true,
		sig: '~~~~'
	},
	'3rr': {
		autoblock: true,
		nocreate: true,
		pageParam: true,
		reason: '[[WP:3RR|スリー・リバート・ルール]]違反',
		summary: 'あなたは[[WP:3RR|スリー・リバート・ルール]]違反の編集を行ったためブロックされました。'
	},
	// uw-prefixed
	'uw-ablock': {
		autoblock: true,
		forAnonOnly: true,
		nocreate: true,
		pageParam: true,
		reasonParam: true,
		summary: 'このIPアドレスは投稿ブロックされました',
		suppressArticleInSummary: true
	},
	'uw-adblock': {
		autoblock: true,
		nocreate: true,
		pageParam: true,
		reason: '宣伝・広告投稿はお止めください',
		summary: 'あなたは[[WP:SOAP|広告または自己宣伝]]を行ったためブロックされました'
	},
	'uw-bioblock': {
		autoblock: true,
		nocreate: true,
		pageParam: true,
		reason: '[[WP:LIVING|存命人物に関する不適切な編集]]',
		summary: 'あなたは[[WP:BLP|存命人物の伝記]]に関する方針に違反する編集を行ったため投稿ブロックされました'
	},
	'uw-block': {
		autoblock: true,
		forRegisteredOnly: true,
		nocreate: true,
		pageParam: true,
		reasonParam: true,
		summary: 'あなたは投稿ブロックされました',
		suppressArticleInSummary: true
	},
	'uw-blockindef': {
		autoblock: true,
		expiry: 'infinity',
		forRegisteredOnly: true,
		nocreate: true,
		pageParam: true,
		reasonParam: true,
		summary: 'あなたは無期限投稿ブロックされました',
		suppressArticleInSummary: true
	},
	'uw-blocknotalk': {
		disabletalk: true,
		pageParam: true,
		reasonParam: true,
		summary: 'あなたは投稿ブロックされました。また、会話ページの編集をすることはできません',
		suppressArticleInSummary: true
	},
	'uw-botblock': {
		expiry: 'infinity',
		forRegisteredOnly: true,
		pageParam: true,
		reason: '[[WP:BRFA|承認]]されていない[[WP:BOT|ボット]]の実行',
		summary: '[[WP:BRFA|承認]]されていない[[WP:BOT|ボット]]を実行したようであるため、あなたは投稿ブロックされました'
	},
	'uw-causeblock': {
		expiry: 'infinity',
		forRegisteredOnly: true,
		reason: '不適切な[[WP:U|利用者名]] <!-- 団体を表す利用者名, ソフトブロック -->',
		summary: 'あなたの[[WP:U|利用者名]]は団体または組織、ウェブサイトを代表するアカウントかのような印象を与えるため無期限投稿ブロックされました'
	},
	'uw-compblock': {
		autoblock: true,
		expiry: 'infinity',
		forRegisteredOnly: true,
		nocreate: true,
		reason: 'パスワード公開',
		summary: 'あなたはパスワードを公開したため投稿ブロックされました'
	},
	'uw-ncblock': {
		autoblock: true,
		expiry: 'infinity',
		nocreate: true,
		pageParam: true,
		reason: '悪戯投稿',
		summary: 'あなたは悪戯投稿を行ったため投稿ブロックされました'
	},
	'uw-sncblock': {
		autoblock: true,
		expiry: 'infinity',
		nocreate: true,
		pageParam: true,
		reason: '悪戯投稿はお止めください',
		summary: 'あなたは悪戯投稿を行ったため投稿ブロックされました'
	},
	'uw-dblock': {
		autoblock: true,
		nocreate: true,
		reason: '内容の無差別な除去',
		pageParam: true,
		summary: 'あなたは内容の無差別な除去を行ったため投稿ブロックされました'
	},
	'uw-efblock': {
		autoblock: true,
		nocreate: true,
		reason: '[[特別:不正利用記録|編集フィルター記録]]',
		summary: 'あなたは[[WP:EF|編集フィルター]]が繰り返し発動するような破壊的な編集を行ったため投稿ブロックされました'
	},
	'uw-ewblock': {
		autoblock: true,
		nocreate: true,
		pageParam: true,
		reason: '[[WP:EW|過度の編集合戦]]',
		summary: 'あなたは[[WP:EW|編集合戦]]を行ったため投稿ブロックされました'
	},
	'uw-evadeblock': {
		expiry: 'infinity',
		nocreate: true,
		reason: '[[WP:投稿ブロックの方針#投稿ブロック回避|別アカウントなどによるブロック破り]]',
		summary: '[[WP:EVASION|投稿ブロックの回避]]に使用されたため投稿ブロックされました'
	},
	'uw-fblock': {
		autoblock: true,
		nocreate: true,
		reason: '虚偽情報の記載',
		summary: 'あなたは虚偽情報の記載を行ったため投稿ブロックされました'
	},
	'uw-lblock': {
		autoblock: true,
		expiry: 'infinity',
		nocreate: true,
		reason: '[[WP:法的な脅迫をしない|法的な脅迫]]',
		summary: 'あなたは[[WP:NLT|法的な脅迫]]を行ったため投稿ブロックされました'
	},
	'uw-oblock': {
		autoblock: true,
		nocreate: true,
		reason: '目的外利用',
		summary: 'あなたは目的外利用を行ったため投稿ブロックされました'
	},
	'uw-pablock': {
		autoblock: true,
		nocreate: true,
		reason: '[[WP:ATTACK|暴言または嫌がらせ]]',
		summary: 'あなたは他者に対して[[WP:ATTACK|個人攻撃]]を行ったため投稿ブロックされました'
	},
	'uw-readtalkblock': {
		autoblock: true,
		nocreate: true,
		reason: '[[Special:Mytalk|会話ページ]]をお読みください',
		forRegisteredOnly: true,
		summary: 'あなたは会話ページのメッセージをご覧になっていないため投稿ブロックされました'
	},
	'uw-rjtblock': {
		autoblock: true,
		nocreate: true,
		reason: '対話拒否',
		forRegisteredOnly: true,
		summary: 'あなたは対話拒否を行ったため投稿ブロックされました'
	},
	'uw-sblock': {
		autoblock: true,
		nocreate: true,
		reason: 'spam',
		summary: 'あなたはspam目的でウィキペディアの利用を行ったため投稿ブロックされました'
	},
	'uw-sockblock': {
		autoblock: true,
		expiry: 'infinity',
		forRegisteredOnly: true,
		nocreate: true,
		reason: '[[Wikipedia:多重アカウント|多重アカウント]]の不適切な使用',
		summary: 'あなたは[[WP:SOCK|多重アカウント]]の不適切な使用を行ったため投稿ブロックされました'
	},
	'uw-softerblock': {
		expiry: 'infinity',
		forRegisteredOnly: true,
		reason: '不適切な[[WP:U|利用者名]] <!-- 宣伝目的の名前, ソフトブロック -->',
		summary: 'あなたの[[WP:U|利用者名]]は団体または組織、ウェブサイトを代表するアカウントかのような印象を与えるため無期限投稿ブロックされました'
	},
	'uw-spamublock': {
		autoblock: true,
		expiry: 'infinity',
		forRegisteredOnly: true,
		nocreate: true,
		reason: '不適切な[[WP:U|利用者名]] <!-- 宣伝目的の名前, 宣伝目的の編集 -->',
		summary: 'あなたのアカウントは宣伝・広告に使用され、また[[WP:U|利用者名の方針]]に違反する利用者名であるため無期限投稿ブロックされました'
	},
	'uw-spoablock': {
		autoblock: true,
		disabletalk: true,
		expiry: 'infinity',
		forRegisteredOnly: true,
		nocreate: true,
		noemail: true,
		reason: '[[WP:SOCK|sockpuppet]]',
		summary: 'このアカウントはウィキペディアの方針に違反するために作成された[[WP:SOCK|ソックパペット]]として投稿ブロックされました'
	},
	'uw-talkrevoked': {
		disabletalk: true,
		reason: '会話ページの編集権限を剥奪: ブロック中の会話ページの不適切な使用',
		prependReason: true,
		summary: '会話ページの編集権限は剥奪されました',
		useInitialOptions: true
	},
	'uw-ublock': {
		expiry: 'infinity',
		forRegisteredOnly: true,
		reason: '不適切な[[WP:U|利用者名]] <!-- 不適切な利用者名, ソフトブロック -->',
		reasonParam: true,
		summary: 'あなたの利用者名は[[WP:U|利用者名の方針]]に違反しているため無期限投稿ブロックされました'
	},
	'uw-ublock-double': {
		expiry: 'infinity',
		forRegisteredOnly: true,
		reason: '不適切な[[WP:U|利用者名]] <!-- 他の利用者に似ている利用者名, ソフトブロック -->',
		summary: 'あなたの[[WP:U|利用者名]]は、既存のウィキペディア利用者の名前と似ているため無期限投稿ブロックされました'
	},
	'uw-readpoliciesblock': {
		autoblock: true,
		nocreate: true,
		pageParam: true,
		reason: '各種方針の熟読期間',
		summary: 'あなたは各種方針の熟読期間が必要であるため投稿ブロックされました'
	},
	'uw-uhblock': {
		autoblock: true,
		expiry: 'infinity',
		forRegisteredOnly: true,
		nocreate: true,
		reason: '不適切な[[WP:U|利用者名]] <!-- 不適切な利用者名, ハードブロック -->',
		reasonParam: true,
		summary: 'あなたの利用者名は明らかに[[WP:U|利用者名の方針]]に違反しているため無期限投稿ブロックされました'
	},
	'uw-ublock-wellknown': {
		expiry: 'infinity',
		forRegisteredOnly: true,
		reason: '不適切な[[WP:U|利用者名]] <!-- 有名人を表す利用者名, ソフトブロック -->',
		summary: 'あなたの[[WP:U|利用者名]]は存命の著名人の名前と一致しているため無期限投稿ブロックされました'
	},
	'uw-uhblock-double': {
		autoblock: true,
		expiry: 'infinity',
		forRegisteredOnly: true,
		nocreate: true,
		reason: '不適切な[[WP:U|利用者名]] <!-- 他の利用者のなりすまし, ハードブロック -->',
		summary: 'あなたの[[WP:U|利用者名]]は他の既存の利用者になりすましているように見えるため無期限投稿ブロックされました'
	},
	'uw-upeblock': {
		autoblock: true,
		expiry: 'infinity',
		forRegisteredOnly: true,
		nocreate: true,
		pageParam: true,
		reason: '[[wmf:Special:MyLanguage/Terms of use|利用規約]]に違反して[[WP:PAID|有償の寄稿の開示]]に応じない利用者',
		summary: 'あなたのアカウントはウィキペディアの方針である[[WP:PAID|有償の寄稿の開示]]に違反してしようされているため無期限投稿ブロックされました'
	},
	'uw-vaublock': {
		autoblock: true,
		expiry: 'infinity',
		forRegisteredOnly: true,
		nocreate: true,
		pageParam: true,
		reason: '不適切な[[WP:U|利用者名]] <!-- 荒らし目的のアカウント -->',
		summary: 'あなたのアカウントは荒らしのみに使用されており、また明らかに[[WP:U|利用者名の方針]]に違反する利用者名であるため無期限投稿ブロックされました'
	},
	'uw-vblock': {
		autoblock: true,
		expiry: 'infinity',
		nocreate: true,
		pageParam: true,
		reason: '[[Wikipedia:荒らし|荒らし]]',
		summary: 'さらなる[[Wikipedia:荒らし|荒らし]]を防止するため、あなたは投稿ブロックされました。'
	},

	// Begin partial block templates, accessed in Twinkle.block.blockGroupsPartial
	'uw-epblock': {
		autoblock: true,
		expiry: 'infinity',
		forRegisteredOnly: true,
		nocreate: false,
		noemail: true,
		pageParam: false,
		reasonParam: true,
		reason: 'メールを用いた嫌がらせ',
		summary: 'あなたは嫌がらせを行ったため他の利用者にメールを送ることをブロックされました'
	},
	'uw-ewpblock': {
		autoblock: true,
		nocreate: false,
		pageParam: false,
		reasonParam: true,
		reason: '[[WP:EW|編集合戦]]',
		summary: 'あなたは[[WP:EW|編集合戦]]を行ったため部分ブロックされました'
	},
	'uw-pblock': {
		autoblock: true,
		nocreate: false,
		pageParam: false,
		reasonParam: true,
		summary: 'あなたはウィキペディアの一部から部分ブロックされました'
	},
	'uw-pblockindef': {
		autoblock: true,
		expiry: 'infinity',
		forRegisteredOnly: true,
		nocreate: false,
		pageParam: false,
		reasonParam: true,
		summary: 'あなたはウィキペディアの一部から無期限部分ブロックされました'
	}
};

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';
		settings.indefinite = settings.indefinite || Morebits.string.isInfinity(settings.expiry);

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

		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: 'よくある理由',
		list: [
			{ label: '全般ブロック (カスタム理由)', value: 'uw-block' }, // ends up being default for registered users
			{ label: '全般ブロック (カスタム理由) - IP', value: 'uw-ablock', selected: true }, // set only when blocking IP
			{ label: '全般ブロック (カスタム理由) - 無期限', value: 'uw-blockindef' },
			{ label: 'ブロック中の会話ページの不適切な使用', value: 'uw-talkrevoked' },
			{ label: '全般ブロック - 会話ページ禁止', value: 'uw-blocknotalk' },
			{ label: 'sockpuppet', value: 'uw-spoablock' },
			{ label: '荒らし', value: 'uw-vblock' },
			{ label: '各種方針の熟読期間', value: 'uw-readpoliciesblock' },
			{ label: '会話ページをお読みください', value: 'uw-readtalkblock' },
		]
	},
	{
		label: '方針',
		list: [
			{ label: 'スリー・リバート・ルール違反', value: '3rr' },
			{ label: '過度の編集合戦', value: 'uw-ewblock' },
			{ label: 'パスワード公開', value: 'uw-compblock' },
			{ label: '別アカウントなどによるブロック破り', value: 'uw-evadeblock' },
			{ label: '多重アカウントの不適切な使用', value: 'uw-sockblock' },
			{ label: '未承認のボット', value: 'uw-botblock' },
			{ label: '有償の寄稿の開示に応じない利用者', value: 'uw-upeblock' },
		]
	},
	{
		label: '荒らし',
		list: [
			{ label: '存命人物に関する不適切な編集', value: 'uw-bioblock' },
			{ label: '虚偽情報の記載', value: 'uw-fblock' },
			{ label: '内容の無差別な除去', value: 'uw-dblock' },
			{ label: '宣伝・広告投稿はお止めください', value: 'uw-adblock' },
			{ label: '悪戯投稿はお止めください', value: 'uw-sncblock' },
			{ label: '悪戯投稿', value: 'uw-ncblock' },
			{ label: '暴言または嫌がらせ', value: 'uw-pablock' },
			{ label: '対話拒否', value: 'uw-rjtblock' },
			{ label: '目的外利用', value: 'uw-oblock' },
			{ label: '編集フィルター記録', value: 'uw-efblock' },
			{ label: '法的な脅迫', value: 'uw-lblock' },
			{ label: 'Spam', value: 'uw-sblock' },
		]
	},
	{
		label: '不適切な利用者名',
		list: [
			{ label: '宣伝目的の名前, ソフトブロック', value: 'uw-softerblock' },
			{ label: '宣伝目的の名前, ハードブロック', value: 'uw-spamublock' },
			{ label: '不適切な利用者名, ソフトブロック', value: 'uw-ublock' },
			{ label: '不適切な利用者名, ハードブロック', value: 'uw-uhblock' },
			{ label: 'よく似ている利用者名, ソフトブロック', value: 'uw-ublock-double' },
			{ label: 'なりすましの利用者名, ハードブロック', value: 'uw-uhblock-double' },
			{ label: '有名人を表す利用者名, ソフトブロック', value: 'uw-ublock-wellknown' },
			{ label: '団体を表す利用者名, ソフトブロック', value: 'uw-causeblock' },
			{ label: '不適切な利用者名, 荒らし目的のアカウント', value: 'uw-vaublock' }
		]
	},
	{
		label: 'テンプレート化された理由',
		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: 'oversightblock', value: 'oversightblock' },
			{ label: 'range block login', value: 'range block login' }, // Only for IP ranges, selected for non-/64 ranges in filtered_block_groups
		]
	}
];

Twinkle.block.blockGroupsPartial = [
	{
		label: 'よくある部分ブロック理由',
		list: [
			{ label: '全般部分ブロック (カスタム理由)', value: 'uw-pblock', selected: true },
			{ label: '全般部分ブロック (カスタム理由) - 無期限', value: 'uw-pblockindef' },
			{ label: '編集合戦', value: 'uw-ewpblock' },
			{ label: 'メールを用いた嫌がらせ', value: 'uw-epblock' }
		]
	}
];


Twinkle.block.callback.filtered_block_groups = function twinkleblockCallbackFilteredBlockGroups(group, show_template) {
	return $.map(group, function(blockGroup) {
		var list = $.map(blockGroup.list, function(blockPreset) {
			switch (blockPreset.value) {
				case 'uw-talkrevoked':
					if (blockedUserName !== relevantUserName) {
						return;
					}
					break;
				case 'range block login':
					if (!Morebits.ip.isRange(relevantUserName)) {
						return;
					}
					blockPreset.selected = !Morebits.ip.get64(relevantUserName);
					break;
				case 'CheckUser block':
				case 'checkuserblock-account':
				case 'checkuserblock-wide':
					if (!Morebits.userIsInGroup('checkuser')) {
						return;
					}
					break;
				case 'oversightblock':
					if (!Morebits.userIsInGroup('suppress')) {
						return;
					}
					break;
				default:
					break;
			}

			var blockSettings = Twinkle.block.blockPresetsInfo[blockPreset.value];

			var registrationRestrict;
			if (blockSettings.forRegisteredOnly) {
				registrationRestrict = Twinkle.block.isRegistered;
			} else if (blockSettings.forAnonOnly) {
				registrationRestrict = !Twinkle.block.isRegistered;
			} else {
				registrationRestrict = true;
			}

			if (!(blockSettings.templateName && show_template) && registrationRestrict) {
				var templateName = blockSettings.templateName || blockPreset.value;
				return {
					label: (show_template ? '{{' + templateName + '}}: ' : '') + 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 form = e.target.form, key = form.preset.value;
	if (!key) {
		return;
	}

	Twinkle.block.callback.update_form(e, Twinkle.block.blockPresetsInfo[key]);
	if (form.template) {
		form.template.value = Twinkle.block.blockPresetsInfo[key].templateName || 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() {
	var reason = this.form.reason.value.replace(
		new RegExp('( <!--|;) ' + Twinkle.block.seeAlsos.join('および') + '参照( -->)?'), ''
	);

	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('および');

	if (!Twinkle.block.seeAlsos.length) {
		this.form.reason.value = reason;
	} else if (reason.indexOf('{{') !== -1) {
		this.form.reason.value = reason + ' <!-- ' + seeAlsoMessage + '参照 -->';
	} else {
		this.form.reason.value = reason + '; ' + 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.userIsBot || /bot\b/i.test(relevantUserName)) {
		data.autoblock = false;
	}

	$(form).find('[name=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 || '';
	}

	// Clear and/or set any partial page or namespace restrictions
	if (form.pagerestrictions) {
		var $pageSelect = $(form).find('[name=pagerestrictions]');
		var $namespaceSelect = $(form).find('[name=namespacerestrictions]');

		// Respect useInitialOptions by clearing data when switching presets
		// In practice, this will always clear, since no partial presets use it
		if (!data.useInitialOptions) {
			$pageSelect.val(null).trigger('change');
			$namespaceSelect.val(null).trigger('change');
		}

		// Add any preset options; in practice, just used for prior block settings
		if (data.restrictions) {
			if (data.restrictions.pages && !$pageSelect.val().length) {
				var pages = data.restrictions.pages.map(function(pr) {
					return pr.title;
				});
				// since page restrictions use an ajax source, we
				// short-circuit that and just add a new option
				pages.forEach(function(page) {
					if (!$pageSelect.find("option[value='" + $.escapeSelector(page) + "']").length) {
						var newOption = new Option(page, page, true, true);
						$pageSelect.append(newOption);
					}
				});
				$pageSelect.val($pageSelect.val().concat(pages)).trigger('change');
			}
			if (data.restrictions.namespaces) {
				$namespaceSelect.val($namespaceSelect.val().concat(data.restrictions.namespaces)).trigger('change');
			}
		}
	}
};

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

	var blockBox = $(form).find('[name=actiontype][value=block]').is(':checked');
	var partialBox = $(form).find('[name=actiontype][value=partial]').is(':checked');
	var templateBox = $(form).find('[name=actiontype][value=template]').is(':checked');

	// Block form is not present
	if (!blockBox) {
		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';
		} 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 if (templateBox) { // Only present if block && template forms both visible
		Morebits.quickForm.setElementVisibility(
			form.blank_duration.parentNode,
			!settings.indefinite && !settings.nonstandard
		);
	}

	// Only particularly relevant if template form is present
	Morebits.quickForm.setElementVisibility(form.article.parentNode, settings && !!settings.pageParam);
	Morebits.quickForm.setElementVisibility(form.block_reason.parentNode, settings && !!settings.reasonParam);

	// Partial block
	Morebits.quickForm.setElementVisibility(form.area.parentNode, partialBox && !blockBox);

	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,
		nocreate: form.nocreate.checked,
		area: form.area.value
	};

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

	form.previewer.beginRender(templateText, 'User_talk:' + relevantUserName); // 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('利用者会話名前空間の編集を制限しない限り、部分ブロックでは会話ページの編集を禁止することはできません!');
			}
			if (!blockoptions.namespacerestrictions && !blockoptions.pagerestrictions) {
				if (!blockoptions.noemail && !blockoptions.nocreate) { // Blank entries technically allowed [[phab:T208645]]
					return alert('ページや名前空間が選択されておらず、メール送信やアカウント作成の制限を選択されていません; 少なくとも一つオプションを選択してください!');
				} else if ((templateoptions.template !== 'uw-epblock' || $form.find('select[name="preset"]').val() !== 'uw-epblock') &&
					// Don't require confirmation if email harassment defaults are set
					!confirm('You are about to block with no restrictions on page or namespace editing, are you sure you want to proceed?')) {
					return;
				}
			}
		}
		if (!blockoptions.expiry) {
			return alert('有効期限を入力してください!');
		} else if (Morebits.string.isInfinity(blockoptions.expiry) && !Twinkle.block.isRegistered) {
			return alert("IPアドレスを無期限ブロックすることはできません!");
		}
		if (!blockoptions.reason) {
			return alert('ブロック理由を入力してください!');
		}

		Morebits.simpleWindow.setButtonsEnabled(false);
		Morebits.status.init(e.target);
		var statusElement = new Morebits.status('ブロックの実行');
		blockoptions.action = 'block';

		blockoptions.user = relevantUserName;

		// 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 (the block statuses will be equal, no
		  confirmation) or there's no new block, in which case either
		  a block expired (different statuses, confirmation) or the
		  same block is still active (same status, no confirmation).
		*/
		var query = {
			format: 'json',
			action: 'query',
			list: 'blocks|logevents',
			letype: 'block',
			lelimit: 1,
			letitle: 'User:' + blockoptions.user
		};
		// bkusers doesn't catch single IPs blocked as part of a range block
		if (mw.util.isIPAddress(blockoptions.user, true)) {
			query.bkip = blockoptions.user;
		} else {
			query.bkusers = blockoptions.user;
		}
		api.get(query).then(function(data) {
			var block = data.query.blocks[0];
			// As with the initial data fetch, if an IP is blocked
			// *and* rangeblocked, this would only grab whichever
			// block is more recent, which would likely mean a
			// mismatch.  However, if the rangeblock is updated
			// while filling out the form, this won't detect that,
			// but that's probably fine.
			if (data.query.blocks.length > 1 && block.user !== relevantUserName) {
				block = data.query.blocks[1];
			}
			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 ' + blockoptions.user + ' has changed. ';
				if (block) {
					message += 'New status: ';
				} else {
					message += 'Last entry: ';
				}

				var logExpiry = '';
				if (logevents.params.duration) {
					if (logevents.params.duration === 'infinity') {
						logExpiry = 'indefinitely';
					} else {
						var expiryDate = new Morebits.date(logevents.params.expiry);
						logExpiry += (expiryDate.isBefore(new Date()) ? ', expired ' : ' until ') + expiryDate.calendar();
					}
				} else { // no duration, action=unblock, just show timestamp
					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.tags = Twinkle.changeTags;
			blockoptions.token = mw.user.tokens.get('csrfToken');
			var mbApi = new Morebits.wiki.api('ブロックの実行', blockoptions, function() {
				statusElement.info('完了');
				if (toWarn) {
					Twinkle.block.callback.issue_template(templateoptions);
				}
			});
			mbApi.post();
		});
	} 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) {
	// Use wgRelevantUserName to ensure the block template goes to a single IP and not to the
	// "talk page" of an IP range (which does not exist)
	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(userTalkPage, 'User talk page modification');
	wikipedia_page.setCallbackParameters(params);
	wikipedia_page.load(Twinkle.block.callback.main);
};

Twinkle.block.callback.getBlockNoticeWikitext = function(params) {
	var text = '{{', settings = Twinkle.block.blockPresetsInfo[params.template];
	if (!settings.nonstandard) {
		text += 'subst:' + params.template;
		if (params.article && settings.pageParam) {
			text += '|page=' + params.article;
		}

		if (!/te?mp|^\s*$|min/.exec(params.expiry)) {
			if (params.indefinite) {
				text += '|indef=yes';
			} else if (!params.blank_duration && !new Morebits.date(params.expiry).isValid()) {
				// Block template wants a duration, not date
				text += '|time=' + params.expiry;
			}
		}

		if (!Twinkle.block.isRegistered && !params.hardblock) {
			text += '|anon=yes';
		}

		if (params.reason) {
			text += '|reason=' + params.reason;
		}
		if (params.disabletalk) {
			text += '|notalk=yes';
		}

		// Currently, all partial block templates are "standard"
		// Building the template, however, takes a fair bit of logic
		if (params.partial) {
			if (params.pagerestrictions.length || params.namespacerestrictions.length) {
				var makeSentence = function (array) {
					if (array.length < 3) {
						return array.join(' and ');
					}
					var last = array.pop();
					return array.join(', ') + ', and ' + last;

				};
				text += '|area=' + (params.indefinite ? 'certain ' : 'from certain ');
				if (params.pagerestrictions.length) {
					text += 'pages (' + makeSentence(params.pagerestrictions.map(function(p) {
						return '[[:' + p + ']]';
					}));
					text += params.namespacerestrictions.length ? ') and certain ' : ')';
				}
				if (params.namespacerestrictions.length) {
					// 1 => Talk, 2 => User, etc.
					var namespaceNames = params.namespacerestrictions.map(function(id) {
						return menuFormattedNamespaces[id];
					});
					text += '[[Wikipedia:Namespace|namespaces]] (' + makeSentence(namespaceNames) + ')';
				}
			} else if (params.area) {
				text += '|area=' + params.area;
			}
		}
	} else {
		text += params.template;
	}

	if (settings.sig) {
		text += '|sig=' + settings.sig;
	}
	return text + '}}';
};

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

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

	if (Twinkle.getPref('blankTalkpageOnIndefBlock') && params.template !== 'uw-lblock' && params.indefinite) {
		Morebits.status.info('Info', 'Blanking talk page per preferences and creating a new talk page section for this month');
		text = date.monthHeader() + '\n';
	} else {
		text = pageobj.getPageText();

		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';
		}

		if (!dateHeaderRegexResult || dateHeaderRegexResult.index !== lastHeaderIndex) {
			Morebits.status.info('Info', 'Will create a new talk page section for this month, as none was found');
			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 += '.';

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

Twinkle.addInitCallback(Twinkle.block, 'block');
})(jQuery);


// </nowiki>