Usuario:Liangent/Twinkle.js

do Galizionario, dicionario galego na Internet.

Nota: Após grabar, cómpre limpar a memoria caché do seu navegador para ver os cambios: Mozilla: prema Recargar (ou Ctrl-R), IE / Opera: Ctrl-F5, Safari: Cmd-R, Konqueror Ctrl-R.

/**
 * vim: set noet sts=0 sw=8:
 * +-------------------------------------------------------------------------+
 * |                       === 警告:全局小工具文件 ===                      |
 * |                      对此文件的修改会影响许多用户。                     |
 * |                           修改前请联系维护者。                          |
 * +-------------------------------------------------------------------------+
 *
 * 从Github导入[https://github.com/jimmyxu/twinkle]
 * 要从Github更新此脚本,你必须设立一个本地仓库,再
 * 跟随[https://github.com/jimmyxu/twinkle/blob/master/README.md]的指引。
 *
 * ----------
 *
 * 这是Twinkle,新手、管理员及他们之间的用户的
 * 好搭档。请参见[[WP:TW]]以获取更多信息。
 *
 * 维护者:[[User:Jimmy_xu_wrk|Jimmy Xu]] <sup>[[User:Jimmy_xu_wrk|查]]{{·}}[[User talk:Jimmy_xu_wrk|论]]{{·}}[[Special:Contributions/Jimmy_xu_wrk|编]]</sup>
 */

//<nowiki>
//
if (navigator.userAgent.indexOf("MSIE") == -1) {
//

var Twinkle = {};
window.Twinkle = Twinkle;  // allow global access

// for use by custom modules (normally empty)
Twinkle.initCallbacks = [];
Twinkle.addInitCallback = function twinkleAddInitCallback(func) {
	Twinkle.initCallbacks.push(func);
};

Twinkle.defaultConfig = {};
/**
 * Twinkle.defaultConfig.twinkle and Twinkle.defaultConfig.friendly
 *
 * This holds the default set of preferences used by Twinkle. (The |friendly| object holds preferences stored in the FriendlyConfig object.)
 * It is important that all new preferences added here, especially admin-only ones, are also added to
 * |Twinkle.config.sections| in twinkleconfig.js, so they are configurable via the Twinkle preferences panel.
 * For help on the actual preferences, see the comments in twinkleconfig.js.
 */
Twinkle.defaultConfig.twinkle = {
	 // General
	summaryAd: " ([[WP:TW|TW]])",
	deletionSummaryAd: " ([[WP:TW|TW]])",
	protectionSummaryAd: " ([[WP:TW|TW]])",
	userTalkPageMode: "window",
	dialogLargeFont: false,
	 // Fluff (revert and rollback)
	openTalkPage: [  ],
	openTalkPageOnAutoRevert: false,
	markRevertedPagesAsMinor: [ "vand" ],
	watchRevertedPages: [ ],
	offerReasonOnNormalRevert: true,
	confirmOnFluff: false,
	showRollbackLinks: [ "diff", "others" ],
	 // DI (twinkleimage)
	notifyUserOnDeli: true,
	deliWatchPage: "default",
	deliWatchUser: "default",
	 // PROD
	watchProdPages: false,
	prodReasonDefault: "",
	logProdPages: false,
	prodLogPageName: "PROD log",
	 // CSD
	watchSpeedyPages: [ ],
	markSpeedyPagesAsPatrolled: true,
	// these next two should probably be identical by default
	notifyUserOnSpeedyDeletionNomination: [ "db", "g1", "g2", "g3", "g5", "g11", "g12", "g13", "g16", "a1", "a2", "f6", "r2", "r3", "r4" ],
	welcomeUserOnSpeedyDeletionNotification: [ "db", "g1", "g2", "g3", "g5", "g11", "g12", "g13", "g16", "a1", "a2", "f6", "r2", "r3", "r4" ],
	promptForSpeedyDeletionSummary: [ "db"/*, "g1", "g2", "g3", "g5", "g11", "g12", "g13", "g16", "a1", "a2", "f6", "r2", "r3", "r4"*/ ],
	openUserTalkPageOnSpeedyDelete: [ /*"db", "g1", "g2", "g3", "g5", "g11", "g12", "g13", "g16", "a1", "a2", "f6", "r2", "r3", "r4"*/ ],
	deleteTalkPageOnDelete: false,
	deleteSysopDefaultToTag: false,
	speedyWindowHeight: 500,
	speedyWindowWidth: 800,
	logSpeedyNominations: false,
	speedyLogPageName: "CSD日志",
	noLogOnSpeedyNomination: [ "o1" ],
	 // Unlink
	unlinkNamespaces: [ "0", "100" ],
	 // Warn
	defaultWarningGroup: "1",
	showSharedIPNotice: true,
	watchWarnings: false,
	blankTalkpageOnIndefBlock: false,
	 // XfD
	xfdWatchDiscussion: "default",
	xfdWatchPage: "default",
	xfdWatchUser: "default",
	markXfdPagesAsPatrolled: true,
	 // Copyvio
	copyvioWatchPage: "default",
	copyvioWatchUser: "default",
	markCopyvioPagesAsPatrolled: true,
	 // Hidden preferences
	revertMaxRevisions: 50,
	batchdeleteChunks: 50,
	batchDeleteMinCutOff: 5,
	batchMax: 5000,
	batchProtectChunks: 50,
	batchProtectMinCutOff: 5,
	batchundeleteChunks: 50,
	batchUndeleteMinCutOff: 5,
	deliChunks: 500,
	deliMax: 5000,
	proddeleteChunks: 50
};

// now some skin dependent config.
if (mw.config.get("skin") === 'vector') {
	Twinkle.defaultConfig.twinkle.portletArea = 'right-navigation';
	Twinkle.defaultConfig.twinkle.portletId   = 'p-twinkle';
	Twinkle.defaultConfig.twinkle.portletName = 'TW';
	Twinkle.defaultConfig.twinkle.portletType = 'menu';
	Twinkle.defaultConfig.twinkle.portletNext = 'p-search';
} else {
	Twinkle.defaultConfig.twinkle.portletArea =  null;
	Twinkle.defaultConfig.twinkle.portletId   = 'p-cactions';
	Twinkle.defaultConfig.twinkle.portletName = null;
	Twinkle.defaultConfig.twinkle.portletType = null;
	Twinkle.defaultConfig.twinkle.portletNext = null;
}

Twinkle.defaultConfig.friendly = {
	 // Tag
	groupByDefault: true,
	watchTaggedPages: false,
	markTaggedPagesAsMinor: false,
	markTaggedPagesAsPatrolled: true,
	tagArticleSortOrder: "cat",
	customTagList: [],
	 // Welcome
	topWelcomes: true,
	watchWelcomes: false,
	welcomeHeading: "欢迎",
	insertHeadings: true,
	insertUsername: false,
	insertSignature: true,  // sign welcome templates, where appropriate
	markWelcomesAsMinor: true,
	quickWelcomeMode: "norm",
	quickWelcomeTemplate: "welcome",
	maskTemplateInSummary: true,
	customWelcomeList: [],
	 // Talkback
	markTalkbackAsMinor: true,
	insertTalkbackSignature: true,  // always sign talkback templates
	talkbackHeading: "回复通告",
	adminNoticeHeading: "提醒",
	 // Shared
	markSharedIPAsMinor: true
};

Twinkle.getPref = function twinkleGetPref(name) {
	var result;
	if (typeof(Twinkle.prefs) === "object" && typeof(Twinkle.prefs.twinkle) === "object") {
		// look in Twinkle.prefs (twinkleoptions.js)
		result = Twinkle.prefs.twinkle[name];
	} else if (typeof(window.TwinkleConfig) === "object") {
		// look in TwinkleConfig
		result = window.TwinkleConfig[name];
	}

	if (typeof(result) === "undefined") {
		return Twinkle.defaultConfig.twinkle[name];
	}
	return result;
};

Twinkle.getFriendlyPref = function twinkleGetFriendlyPref(name) {
	var result;
	if (typeof(Twinkle.prefs) === "object" && typeof(Twinkle.prefs.friendly) === "object") {
		// look in Twinkle.prefs (twinkleoptions.js)
		result = Twinkle.prefs.friendly[name];
	} else if (typeof(window.FriendlyConfig) === "object") {
		// look in FriendlyConfig
		result = window.FriendlyConfig[name];
	}

	if (typeof(result) === "undefined") {
		return Twinkle.defaultConfig.friendly[name];
	}
	return result;
};



/**
 * **************** twAddPortlet() ****************
 *
 * Adds a portlet menu to one of the navigation areas on the page.
 * This is necessarily quite a hack since skins, navigation areas, and
 * portlet menu types all work slightly different.
 *
 * Available navigation areas depend on the skin used.
 * Monobook:
 *  "column-one", outer div class "portlet", inner div class "pBody". Existing portlets: "p-cactions", "p-personal", "p-logo", "p-navigation", "p-search", "p-interaction", "p-tb", "p-coll-print_export"
 *  Special layout of p-cactions and p-personal through specialized styles.
 * Vector:
 *  "mw-panel", outer div class "portal", inner div class "body". Existing portlets/elements: "p-logo", "p-navigation", "p-interaction", "p-tb", "p-coll-print_export"
 *  "left-navigation", outer div class "vectorTabs" or "vectorMenu", inner div class "" or "menu". Existing portlets: "p-namespaces", "p-variants" (menu)
 *  "right-navigation", outer div class "vectorTabs" or "vectorMenu", inner div class "" or "menu". Existing portlets: "p-views", "p-cactions" (menu), "p-search"
 *  Special layout of p-personal portlet (part of "head") through specialized styles.
 * Modern:
 *  "mw_contentwrapper" (top nav), outer div class "portlet", inner div class "pBody". Existing portlets or elements: "p-cactions", "mw_content"
 *  "mw_portlets" (sidebar), outer div class "portlet", inner div class "pBody". Existing portlets: "p-navigation", "p-search", "p-interaction", "p-tb", "p-coll-print_export"
 *
 * @param String navigation -- id of the target navigation area (skin dependant, on vector either of "left-navigation", "right-navigation", or "mw-panel")
 * @param String id -- id of the portlet menu to create, preferably start with "p-".
 * @param String text -- name of the portlet menu to create. Visibility depends on the class used.
 * @param String type -- type of portlet. Currently only used for the vector non-sidebar portlets, pass "menu" to make this portlet a drop down menu.
 * @param Node nextnodeid -- the id of the node before which the new item should be added, should be another item in the same list, or undefined to place it at the end.
 *
 * @return Node -- the DOM node of the new item (a DIV element) or null
 */
function twAddPortlet( navigation, id, text, type, nextnodeid )
{
	//sanity checks, and get required DOM nodes
	var root = document.getElementById( navigation );
	if ( !root ) {
		return null;
	}

	var item = document.getElementById( id );
	if (item) {
		if (item.parentNode && item.parentNode === root) {
			return item;
		}
		return null;
	}

	var nextnode;
	if (nextnodeid) {
		nextnode = document.getElementById(nextnodeid);
	}

	//verify/normalize input
	type = (skin === "vector" && type === "menu" && (navigation === "left-navigation" || navigation === "right-navigation")) ? "menu" : "";
	var outerDivClass;
	var innerDivClass;
	switch (skin)
	{
		case "vector":
			if (navigation !== "portal" && navigation !== "left-navigation" && navigation !== "right-navigation") {
				navigation = "mw-panel";
			}
			outerDivClass = (navigation === "mw-panel") ? "portal" : (type === "menu" ? "vectorMenu extraMenu" : "vectorTabs extraMenu");
			innerDivClass = (navigation === "mw-panel") ? 'body' : (type === 'menu' ? 'menu':'');
			break;
		case "modern":
			if (navigation !== "mw_portlets" && navigation !== "mw_contentwrapper") {
				navigation = "mw_portlets";
			}
			outerDivClass = "portlet";
			innerDivClass = "pBody";
			break;
		default:
			navigation = "column-one";
			outerDivClass = "portlet";
			innerDivClass = "pBody";
			break;
	}

	//Build the DOM elements.
	var outerDiv = document.createElement( 'div' );
	outerDiv.className = outerDivClass+" emptyPortlet";
	outerDiv.id = id;
	if (type === "menu") {
		// fix drop-down arrow image in Vector skin
		outerDiv.style.backgroundImage = 'url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABYAAAAQCAMAAAAlM38UAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAA9QTFRFsbGxmpqa3d3deXl58/n79CzHcQAAAAV0Uk5T/////wD7tg5TAAAAMklEQVR42mJgwQoYBkqYiZEZAhiZUFRDxWGicEPA4nBRhNlAcYQokpVMDEwD6kuAAAMAyGMFQVv5ldcAAAAASUVORK5CYII=")';
		outerDiv.style.backgroundPosition = 'right 60%';
	}
	if ( nextnode && nextnode.parentNode === root ) {
		root.insertBefore( outerDiv, nextnode );
	} else {
		root.appendChild( outerDiv );
	}

	var h5 = document.createElement( 'h5' );
	if (type === 'menu') {
		var span = document.createElement( 'span' );
		span.appendChild( document.createTextNode( text ) );
		h5.appendChild( span );

		var a = document.createElement( 'a' );
		a.href = "#";
		span = document.createElement( 'span' );
		span.appendChild( document.createTextNode( text ) );
		a.appendChild( span );
		h5.appendChild( a );
	} else {
		h5.appendChild( document.createTextNode( text ) );
	}
	outerDiv.appendChild( h5 );

	var innerDiv = document.createElement( 'div' ); //not strictly necessary with type vectorTabs, or other skins.
	innerDiv.className = innerDivClass;
	outerDiv.appendChild(innerDiv);

	var ul = document.createElement( 'ul' );
	innerDiv.appendChild( ul );

	return outerDiv;
}


/**
 * **************** twAddPortletLink() ****************
 * Builds a portlet menu if it doesn't exist yet, and add the portlet link.
 */
function twAddPortletLink( href, text, id, tooltip, accesskey, nextnode )
{
	if (Twinkle.getPref("portletArea") !== null) {
		twAddPortlet(Twinkle.getPref("portletArea"), Twinkle.getPref("portletId"), Twinkle.getPref("portletName"), Twinkle.getPref("portletType"), Twinkle.getPref("portletNext"));
	}
	return mw.util.addPortletLink( Twinkle.getPref("portletId"), href, text, id, tooltip, accesskey, nextnode );
}

// check if account is experienced enough for more advanced functions
var twinkleUserAuthorized = userIsInGroup( 'autoconfirmed' ) || userIsInGroup( 'confirmed' );

/*
 * vim: set noet sts=0 sw=8:
 ****************************************
 *** twinkleimage.js: Image CSD module
 ****************************************
 * Mode of invocation:     Tab ("DI")
 * Active on:              File pages with a corresponding file which is local (not on Commons)
 * Config directives in:   TwinkleConfig
 */

Twinkle.image = function twinkleimage() {
	if (mw.config.get('wgNamespaceNumber') === 6 &&
	    !document.getElementById("mw-sharedupload") &&
	    document.getElementById("mw-imagepage-section-filehistory"))
	{
		if(twinkleUserAuthorized) {
			$(twAddPortletLink("#", "图版", "tw-di", "提交文件快速删除", "")).click(Twinkle.image.callback);
		} else {
			$(twAddPortletLink("#", "图版", "tw-di", "提交文件快速删除", "")).click(function(){
				alert("您尚未达到自动确认。");
			});
		}
	}
};

Twinkle.image.callback = function twinkleimageCallback() {
	var Window = new SimpleWindow( 600, 300 );
	Window.setTitle( "文件快速删除候选" );
	Window.setScriptName( "Twinkle" );
	Window.addFooterLink( "快速删除方针", "WP:CSD" );
	Window.addFooterLink( "Twinkle帮助", "WP:TW/DOC#image" );

	var form = new QuickForm( Twinkle.image.callback.evaluate );
	form.append( {
			type: 'checkbox',
			list: [
				{
					label: '通知上传者',
					value: 'notify',
					name: 'notify',
					tooltip: "如果您在标记同一用户的很多文件,请取消此复选框以避免使用户对话页过载。",
					checked: Twinkle.getPref('notifyUserOnDeli')
				}
			]
		}
	);
	var field = form.append( {
			type: 'field',
			label: '需要的动作'
		} );
	field.append( {
			type: 'radio',
			name: 'type',
			list: [
				{
					label: '没有来源(CSD F3)',
					value: 'no source',
					checked: true,
					tooltip: '本图像并未注明原始出处,其声称的版权信息无法予以查证'
				},
				{
					label: '没有版权(CSD F4)',
					value: 'no license',
					tooltip: '本档案缺少版权信息'
				}
			]
		} );
	form.append( { type:'submit' } );

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

Twinkle.image.callback.evaluate = function twinkleimageCallbackEvaluate(event) {
	var type;
	mw.config.set('wgPageName', mw.config.get('wgPageName').replace(/_/g, ' '));  // for queen/king/whatever and country!

	var notify = event.target.notify.checked;
	var types = event.target.type;
	for( var i = 0; i < types.length; ++i ) {
		if( types[i].checked ) {
			type = types[i].values;
			break;
		}
	}

	var csdcrit;
	switch( type ) {
		case 'no source':
			csdcrit = "f3";
			break;
		case 'no license':
			csdcrit = "f4";
			break;
		default:
			throw new Error( "Twinkle.image.callback.evaluate:未知条款" );
	}

	var lognomination = Twinkle.getPref('logSpeedyNominations') && Twinkle.getPref('noLogOnSpeedyNomination').indexOf(csdcrit) === -1;

	var params = {
		'type': type,
		'normalized': csdcrit,
		'lognomination': lognomination
	};
	SimpleWindow.setButtonsEnabled( false );
	Status.init( event.target );

	Wikipedia.actionCompleted.redirect = mw.config.get('wgPageName');
	Wikipedia.actionCompleted.notice = "标记完成";

	// Tagging image
	var wikipedia_page = new Wikipedia.page( mw.config.get('wgPageName'), '添加删除标记' );
	wikipedia_page.setCallbackParameters( params );
	wikipedia_page.load( Twinkle.image.callbacks.taggingImage );

	// Notifying uploader
	if( notify ) {
		wikipedia_page.lookupCreator(Twinkle.image.callbacks.userNotification);
	} else {
		// add to CSD log if desired
		if (lognomination) {
			params.fromDI = true;
			Twinkle.speedy.callbacks.user.addToLog(params, null);
		}
		// No auto-notification, display what was going to be added.
		var noteData = document.createElement( 'pre' );
		noteData.appendChild( document.createTextNode( "{{subst:Uploadvionotice|" + mw.config.get('wgPageName') + "}}--~~~~" ) );
		Status.info( '提示', [ '这些内容应贴进上传者对话页:', document.createElement( 'br' ),  noteData ] );
	}
};

Twinkle.image.callbacks = {
	taggingImage: function(pageobj) {
		var text = pageobj.getPageText();
		var params = pageobj.getCallbackParameters();

		// Adding discussion
		wikipedia_page = new Wikipedia.page("Wikipedia:檔案存廢討論/無版權訊息或檔案來源", "添加快速删除记录项");
		wikipedia_page.setFollowRedirect(true);
		wikipedia_page.setCallbackParameters(params);
		wikipedia_page.load(Twinkle.image.callbacks.imageList);

		var tag = "{{subst:" + params.type + "/auto";
		tag += "}}\n";

		pageobj.setPageText(tag + text);
		pageobj.setEditSummary("请求快速删除([[WP:CSD#" + params.normalized.toUpperCase() + "|CSD " + params.normalized.toUpperCase() + "]]):" + params.type + "。" + Twinkle.getPref('summaryAd'));
		switch (Twinkle.getPref('deliWatchPage')) {
			case 'yes':
				pageobj.setWatchlist(true);
				break;
			case 'no':
				pageobj.setWatchlistFromPreferences(false);
				break;
			default:
				pageobj.setWatchlistFromPreferences(true);
				break;
		}
		pageobj.setCreateOption('nocreate');
		pageobj.save();
	},
	userNotification: function(pageobj) {
		var params = pageobj.getCallbackParameters();
		var initialContrib = pageobj.getCreator();
		var usertalkpage = new Wikipedia.page('User talk:' + initialContrib, "通知上传者(" + initialContrib + ")");
		var notifytext = "\n{{subst:Uploadvionotice|" + mw.config.get('wgPageName');
		notifytext += "}}--~~~~";
		usertalkpage.setAppendText(notifytext);
		usertalkpage.setEditSummary("通知:文件[[" + mw.config.get('wgPageName') + "]]快速删除提名。" + Twinkle.getPref('summaryAd'));
		usertalkpage.setCreateOption('recreate');
		switch (Twinkle.getPref('deliWatchUser')) {
			case 'yes':
				usertalkpage.setWatchlist(true);
				break;
			case 'no':
				usertalkpage.setWatchlistFromPreferences(false);
				break;
			default:
				usertalkpage.setWatchlistFromPreferences(true);
				break;
		}
		usertalkpage.setFollowRedirect(true);
		usertalkpage.append();

		// add this nomination to the user's userspace log, if the user has enabled it
		if (params.lognomination) {
			params.fromDI = true;
			Twinkle.speedy.callbacks.user.addToLog(params, initialContrib);
		}
	},
	imageList: function(pageobj) {
		var text = pageobj.getPageText();
		var params = pageobj.getCallbackParameters();

		pageobj.setPageText(text + "\n* [[:" + mw.config.get('wgPageName') + "]]--~~~~");
		pageobj.setEditSummary("添加[[" + mw.config.get('wgPageName') + "]]。" + Twinkle.getPref('summaryAd'));
		pageobj.setCreateOption('recreate');
		pageobj.save();
	}

};

/*
 * vim: set noet sts=0 sw=8:
****************************************
*** twinkledelimages.js: Batch deletion of images (sysops only)
****************************************
* Mode of invocation:     Tab ("Deli-batch")
* Active on:              Existing non-special pages
* Config directives in:   TwinkleConfig
*/

Twinkle.delimages = function twinkledeli() {
	if( mw.config.get( 'wgNamespaceNumber' ) < 0 || !mw.config.get( 'wgCurRevisionId' ) ) {
		return;
	}
	if( userIsInGroup( 'sysop' ) ) {
		$(twAddPortletLink("#", "批图", "tw-deli", "批量删除此页内的文件", "")).click(Twinkle.delimages.callback);
	}
};

Twinkle.delimages.unlinkCache = {};
Twinkle.delimages.callback = function twinkledeliCallback() {
	var Window = new SimpleWindow( 800, 400 );
	Window.setTitle( "批量文件删除" );
	Window.setScriptName( "Twinkle" );
	Window.addFooterLink( "Twinkle帮助", "WP:TW/DOC#delimages" );

	var form = new QuickForm( Twinkle.delimages.callback.evaluate );
	form.append( {
		type: 'checkbox',
		list: [
			{
				label: '删除文件',
				name: 'delete_image',
				value: 'delete',
				checked: true
			},
			{
				label: '取消此文件的使用',
				name: 'unlink_image',
				value: 'unlink',
				checked: true
			}
		]
	} );
	form.append( {
		type: 'textarea',
		name: 'reason',
		label: '理由:'
	} );
	var query;
	if( mw.config.get( 'wgNamespaceNumber' ) === Namespace.CATEGORY ) {
		query = {
			'action': 'query',
			'generator': 'categorymembers',
			'gcmtitle': mw.config.get( 'wgPageName' ),
			'gcmnamespace': Namespace.IMAGE,
			'gcmlimit' : Twinkle.getPref('deliMax'), 
			'prop': [ 'imageinfo', 'categories', 'revisions' ],
			'grvlimit': 1,
			'grvprop': [ 'user' ]
		};
	} else {
		query = {
			'action': 'query',
			'generator': 'images',
			'titles': mw.config.get( 'wgPageName' ),
			'prop': [ 'imageinfo', 'categories', 'revisions' ],
			'gimlimit': 'max'
		};
	}
	var wikipedia_api = new Wikipedia.api( '抓取文件', query, function( self ) {
		var xmlDoc = self.responseXML;
		var images = $(xmlDoc).find('page[imagerepository="local"]');
		var list = [];

		$.each(images, function() {
			var $self = $(this);
			var image = $self.attr('title');
			var user = $self.find('imageinfo ii').attr('user');
			var last_edit = $self.find('revisions rev').attr('user');
			var disputed = $self.find('categories cl[title="Category:快速删除候选"]').length > 0;
			list.push( {
				'label': image + '—作者:' + user + ',上次编辑:' + last_edit + ( disputed ? '(争议' : '' ),
				'value': image,
				'checked': !disputed
			});
		});

		self.params.form.append( {
			type: 'checkbox',
			name: 'images',
			list: list
		}
	);
	self.params.form.append( { type:'submit' } );

	var result = self.params.form.render();
	self.params.Window.setContent( result );


}  );

wikipedia_api.params = { form:form, Window:Window };
wikipedia_api.post();
var root = document.createElement( 'div' );
Status.init( root );
Window.setContent( root );
Window.display();
};

Twinkle.delimages.currentDeleteCounter = 0;
Twinkle.delimages.currentUnlinkCounter = 0;
Twinkle.delimages.currentdeletor = 0;
Twinkle.delimages.callback.evaluate = function twinkledeliCallbackEvaluate(event) {
	mw.config.set('wgPageName', mw.config.get('wgPageName').replace(/_/g, ' '));  // for queen/king/whatever and country!
	var images = event.target.getChecked( 'images' );
	var reason = event.target.reason.value;
	var delete_image = event.target.delete_image.checked;
	var unlink_image = event.target.unlink_image.checked;
	if( ! reason ) {
		return;
	}

	SimpleWindow.setButtonsEnabled( false );
	Status.init( event.target );

	function toCall( work ) {
		if( work.length === 0 && Twinkle.delimages.currentDeleteCounter <= 0 && Twinkle.delimages.currentUnlinkCounter <= 0 ) {
			window.clearInterval( Twinkle.delimages.currentdeletor );
			Wikipedia.removeCheckpoint();
			return;
		} else if( work.length !== 0 && Twinkle.delimages.currentDeleteCounter <= Twinkle.getPref('batchDeleteMinCutOff') && Twinkle.delimages.currentUnlinkCounter <= Twinkle.getPref('batchDeleteMinCutOff') ) {
			Twinkle.delimages.unlinkCache = []; // Clear the cache
			var images = work.shift();
			Twinkle.delimages.currentDeleteCounter = images.length;
			Twinkle.delimages.currentUnlinkCounter = images.length;
			var i;
			for( i = 0; i < images.length; ++i ) {
				var image = images[i];
				var query = {
					'action': 'query',
					'titles': image
				};
				var wikipedia_api = new Wikipedia.api( '检查文件 ' + image + ' 是否存在', query, Twinkle.delimages.callbacks.main );
				wikipedia_api.params = { image:image, reason:reason, unlink_image:unlink_image, delete_image:delete_image };
				wikipedia_api.post();
			}
		}
	}
	var work = images.chunk( Twinkle.getPref('deliChunks') );
	Wikipedia.addCheckpoint();
	Twinkle.delimages.currentdeletor = window.setInterval( toCall, 1000, work );
};
Twinkle.delimages.callbacks = {
	main: function( self ) {
		var xmlDoc = self.responseXML;
		var $data = $(xmlDoc);

		var normal = $data.find('normalized n').attr('to');

		if( normal ) {
			self.params.image = normal;
		}

		var exists = $data.find('pages page[title="'+self.params.image.replace( /"/g, '\\"')+'"]:not([missing])').length > 0;

		if( ! exists ) {
			self.statelem.error( "文件不存在,可能已被删除" );
			return;
		}
		if( self.params.unlink_image ) {
			var query = {
				'action': 'query',
				'list': 'imageusage',
				'iutitle': self.params.image,
				'iulimit': userIsInGroup( 'sysop' ) ? 5000 : 500 // 500 is max for normal users, 5000 for bots and sysops
			};
			var wikipedia_api = new Wikipedia.api( '抓取文件链接', query, Twinkle.delimages.callbacks.unlinkImageInstancesMain );
			wikipedia_api.params = self.params;
			wikipedia_api.post();
		}
		if( self.params.delete_image ) {

			var imagepage = new Wikipedia.page( self.params.image, '删除文件');
			imagepage.setEditSummary( self.params.reason + Twinkle.getPref('deletionSummaryAd'));
			imagepage.deletePage();
		}
	},
	unlinkImageInstancesMain: function( self ) {
		var xmlDoc = self.responseXML;
		var instances = [];
		$(xmlDoc).find('imageusage iu').each(function(){
			instances.push($(this).attr('title'));
		});
		if( instances.length === 0 ) {
			--twinklebatchdelete.currentUnlinkCounter;
			return;
		}

		$.each( instances, function(k,title) {
			page = new Wikipedia.page(title, "取消文件在 " + title + " 上的使用");
			page.setFollowRedirect(true);
			page.setCallbackParameters({'image': self.params.image, 'reason': self.params.reason});
			page.load(Twinkle.delimages.callbacks.unlinkImageInstances);

		});
	},
	unlinkImageInstances: function( self ) {
		var params = self.getCallbackParameters();
		var statelem = self.getStatusElement();

		var image = params.image.replace( /^(?:Image|File|文件):/, '' );
		var old_text = self.getPageText();
		var wikiPage = new Mediawiki.Page( old_text );
		wikiPage.commentOutImage( image , '注释此文件因其已被删除' );
		var text = wikiPage.getText();

		if( text === old_text ) {
			statelem.error( '取消 ' + image + ' 在 ' + self.getPageName() + ' 上的使用失败' );
			return;
		}
		self.setPageText(text);
		self.setEditSummary('移除文件 ' + image + " 因其已被删除,理由为“" + params.reason + "”。" + Twinkle.getPref('deletionSummaryAd'));
		self.setCreateOption('nocreate');
		self.save();
	}
};

/*
 * vim: set noet sts=0 sw=8:
 ****************************************
 *** twinklediff.js: Diff module
 ****************************************
 * Mode of invocation:     Tab on non-diff pages ("Last"); tabs on diff pages ("Since", "Since mine", "Current")
 * Active on:              Existing non-special pages
 * Config directives in:   TwinkleConfig
 */

Twinkle.diff = function twinklediff() { 
	if( mw.config.get('wgNamespaceNumber') < 0 || !mw.config.get('wgArticleId') ) {
		return;
	}

	var query = {
		'title': mw.config.get('wgPageName'),
		'diff': 'cur',
		'oldid': 'prev'
	};

	twAddPortletLink( mw.config.get('wgServer') + mw.config.get('wgScriptPath') + '/index.php?' + QueryString.create( query ), '最后', 'tw-lastdiff', '显示最后修改' );

	// Show additional tabs only on diff pages
	if (QueryString.exists('diff')) {
		$(twAddPortletLink("#", '自前', 'tw-since', '显示与上一修订版本间的差异' )).click(function(){Twinkle.diff.evaluate(false);});
		$(twAddPortletLink("#", '自我', 'tw-sincemine', '显示与我做出的修订版本的差异' )).click(function(){Twinkle.diff.evaluate(true);});

		var oldid = /oldid=(.+)/.exec($('div#mw-diff-ntitle1 strong a').first().attr("href"))[1];
		query = {
			'title': mw.config.get('wgPageName'),
			'diff': 'cur',
			'oldid' : oldid
		};
		twAddPortletLink( mw.config.get('wgServer') + mw.config.get('wgScriptPath') + '/index.php?' + QueryString.create( query ), '当前', 'tw-curdiff', '显示与当前版本间的差异' );
	}
};

Twinkle.diff.evaluate = function twinklediffEvaluate(me) {
	var ntitle = getElementsByClassName( document.getElementById('bodyContent'), 'td' , 'diff-ntitle' )[0];

	var user;
	if( me ) {
		user = mw.config.get('wgUserName');
	} else {
		var node = document.getElementById( 'mw-diff-ntitle2' );
		if( ! node ) {
			// nothing to do?
			return;
		}
		user = $(node).find('a').first().text();
	}
	var query = {
		'prop': 'revisions',
		'action': 'query',
		'titles': mw.config.get('wgPageName'),
		'rvlimit': 1, 
		'rvprop': [ 'ids', 'user' ],
		'rvstartid': mw.config.get('wgCurRevisionId') - 1, // i.e. not the current one
		'rvuser': user
	};
	Status.init( document.getElementById('bodyContent') );
	var wikipedia_api = new Wikipedia.api( '抓取最初贡献者信息', query, Twinkle.diff.callbacks.main );
	wikipedia_api.params = { user: user };
	wikipedia_api.post();
};

Twinkle.diff.callbacks = {
	main: function( self ) {
		var xmlDoc = self.responseXML;
		var revid = $(xmlDoc).find('rev').attr('revid');

		if( ! revid ) {
			self.statelem.error( '未找到合适的早期版本,或 ' + self.params.user + ' 是唯一贡献者。取消。' );
			return;
		}
		var query = {
			'title': mw.config.get('wgPageName'),
			'oldid': revid,
			'diff': mw.config.get('wgCurRevisionId')
		};
		window.location = mw.config.get('wgServer') + mw.config.get('wgScriptPath') + '/index.php?' + QueryString.create( query );
	}
};

/*
 * vim: set noet sts=0 sw=8:
 ****************************************
 *** twinkleconfig.js: Preferences module
 ****************************************
 * Mode of invocation:     Adds configuration form to Wikipedia:Twinkle/Preferences and user 
                           subpages named "/Twinkle preferences", and adds ad box to the top of user 
                           subpages belonging to the currently logged-in user which end in '.js'
 * Active on:              What I just said.  Yeah.
 * Config directives in:   TwinkleConfig

 I, [[User:This, that and the other]], originally wrote this.  If the code is misbehaving, or you have any
 questions, don't hesitate to ask me.  (This doesn't at all imply [[WP:OWN]]ership - it's just meant to
 point you in the right direction.)  -- TTO
 */


Twinkle.config = {};

Twinkle.config.commonEnums = {
	watchlist: { yes: "添加到监视列表", no: "不添加到监视列表", "default": "遵守站点设置" },
	talkPageMode: { window: "在窗口中,替换其它用户对话页", tab: "在新标签页中", blank: "在全新的窗口中" }
};

Twinkle.config.commonSets = {
	csdCriteria: {
		db: "自定义理由",
		g1: "G1", g2: "G2", g3: "G3", g5: "G5", g10: "G10", g11: "G11", g12: "G12", g13: "G13", g14: "G14", g15: "G15", g16: "G16",
		a1: "A1", a2: "A2", a3: "A3",
		o1: "O1", o3: "O3", o4: "O4",
		f1: "F1", f3: "F3", f4: "F4", f5: "F5", f6: "F6", f7: "F7",
		r2: "R2", r3: "R3", r4: "R4", r5: "R5" // db-multiple is not listed here because it is treated differently within twinklespeedy
	},
	csdCriteriaDisplayOrder: [
		"db",
		"g1", "g2", "g3", "g5", "g10", "g11", "g12", "g13", "g14", "g15", "g16",
		"a1", "a2", "a3",
		"o1", "o3", "o4",
		"f1", "f3", "f4", "f5", "f6", "f7",
		"r2", "r3", "r4", "r5"
	],
	csdCriteriaNotificationDisplayOrder: [
		"db",
		"g1", "g2", "g3", "g5", "g10", "g11", "g12", "g13", "g14", "g15", "g16",
		"a1", "a2", "a3",
		"o1", "o3", "o4",
		"f1", "f3", "f4", "f5", "f6", "f7",
		"r2", "r3", "r4", "r5"
	],
	csdAndDICriteria: {
		db: "自定义理由",
		g1: "G1", g2: "G2", g3: "G3", g5: "G5", g10: "G10", g11: "G11", g12: "G12", g13: "G13", g14: "G14", g15: "G15", g16: "G16",
		a1: "A1", a2: "A2", a3: "A3",
		o1: "O1", o3: "O3", o4: "O4",
		f1: "F1", f3: "F3", f4: "F4", f5: "F5", f6: "F6", f7: "F7",
		r2: "R2", r3: "R3", r4: "R4", r5: "R5" // db-multiple is not listed here because it is treated differently within twinklespeedy
	},
	csdAndDICriteriaDisplayOrder: [
		"db",
		"g1", "g2", "g3", "g5", "g10", "g11", "g12", "g13", "g14", "g15", "g16",
		"a1", "a2", "a3",
		"o1", "o3", "o4",
		"f1", "f3", "f4", "f5", "f6", "f7",
		"r2", "r3", "r4", "r5"
	],
	namespacesNoSpecial: {
		"0": "(条目)",
		"1": "Talk",
		"2": "User",
		"3": "User talk",
		"4": "Wikipedia",
		"5": "Wikipedia talk",
		"6": "File",
		"7": "File talk",
		"8": "MediaWiki",
		"9": "MediaWiki talk",
		"10": "Template",
		"11": "Template talk",
		"12": "Help",
		"13": "Help talk",
		"14": "Category",
		"15": "Category talk",
		"100": "Portal",
		"101": "Portal talk"/*,
		"108": "Book",
		"109": "Book talk"*/
	}
};

/**
 * Section entry format:
 *
 * {
 *   title: <human-readable section title>,
 *   adminOnly: <true for admin-only sections>,
 *   hidden: <true for advanced preferences that rarely need to be changed - they can still be modified by manually editing twinkleoptions.js>,
 *   inFriendlyConfig: <true for preferences located under FriendlyConfig rather than TwinkleConfig>,
 *   preferences: [
 *     {
 *       name: <TwinkleConfig property name>,
 *       label: <human-readable short description - used as a form label>,
 *       helptip: <(optional) human-readable text (using valid HTML) that complements the description, like limits, warnings, etc.>
 *       adminOnly: <true for admin-only preferences>,
 *       type: <string|boolean|integer|enum|set|customList> (customList stores an array of JSON objects { value, label }),
 *       enumValues: <for type = "enum": a JSON object where the keys are the internal names and the values are human-readable strings>,
 *       setValues: <for type = "set": a JSON object where the keys are the internal names and the values are human-readable strings>,
 *       setDisplayOrder: <(optional) for type = "set": an array containing the keys of setValues (as strings) in the order that they are displayed>,
 *       customListValueTitle: <for type = "customList": the heading for the left "value" column in the custom list editor>,
 *       customListLabelTitle: <for type = "customList": the heading for the right "label" column in the custom list editor>
 *     },
 *     . . .
 *   ]
 * },
 * . . .
 *
 */

Twinkle.config.sections = [
{
	title: "常规",
	preferences: [
		// TwinkleConfig.summaryAd (string)
		// Text to be appended to the edit summary of edits made using Twinkle
		{
			name: "summaryAd",
			label: "编辑摘要后缀",
			helptip: "应当由一个空格开头,并尽可能短。",
			type: "string"
		},

		// TwinkleConfig.deletionSummaryAd (string)
		// Text to be appended to the edit summary of deletions made using Twinkle
		{
			name: "deletionSummaryAd",
			label: "删除摘要后缀",
			helptip: "通常和编辑摘要后缀一样。",
			adminOnly: true,
			type: "string"
		},

		// TwinkleConfig.protectionSummaryAd (string)
		// Text to be appended to the edit summary of page protections made using Twinkle
		{
			name: "protectionSummaryAd",
			label: "保护摘要后缀",
			helptip: "通常和编辑摘要后缀一样。",
			adminOnly: true,
			type: "string"
		},

		// TwinkleConfig.userTalkPageMode may take arguments:
		// 'window': open a new window, remember the opened window
		// 'tab': opens in a new tab, if possible.
		// 'blank': force open in a new window, even if such a window exists
		{
			name: "userTalkPageMode",
			label: "当要打开用户对话页时,",
			type: "enum",
			enumValues: Twinkle.config.commonEnums.talkPageMode
		},

		// TwinkleConfig.dialogLargeFont (boolean)
		{
			name: "dialogLargeFont",
			label: "在Twinkle对话框中使用大号字体",
			type: "boolean"
		}
	]
},

{
	title: "图片删除",
	preferences: [
		// TwinkleConfig.notifyUserOnDeli (boolean)
		// If the user should be notified after placing a file deletion tag
		{
			name: "notifyUserOnDeli",
			label: "默认勾选“通知创建者”",
			type: "boolean"
		},

		// TwinkleConfig.deliWatchPage (string)
		// The watchlist setting of the page tagged for deletion. Either "yes", "no", or "default". Default is "default" (Duh).
		{
			name: "deliWatchPage",
			label: "标记图片时添加到监视列表",
			type: "enum",
			enumValues: Twinkle.config.commonEnums.watchlist
		},

		// TwinkleConfig.deliWatchUser (string)
		// The watchlist setting of the user talk page if a notification is placed. Either "yes", "no", or "default". Default is "default" (Duh).
		{
			name: "deliWatchUser",
			label: "标记图片时添加创建者对话页到监视列表",
			type: "enum",
			enumValues: Twinkle.config.commonEnums.watchlist
		}
	]
},

{
	title: "回退",  // twinklefluff module
	preferences: [
		// TwinkleConfig.openTalkPage (array)
		// What types of actions that should result in opening of talk page
		{
			name: "openTalkPage",
			label: "在这些类型的回退后打开用户对话页",
			type: "set",
			setValues: { agf: "善意回退", norm: "常规回退", vand: "破坏回退", torev: "“恢复此版本”" }
		},

		// TwinkleConfig.openTalkPageOnAutoRevert (bool)
		// Defines if talk page should be opened when calling revert from contrib page, because from there, actions may be multiple, and opening talk page not suitable. If set to true, openTalkPage defines then if talk page will be opened.
		{
			name: "openTalkPageOnAutoRevert",
			label: "在从用户贡献中发起回退时打开用户对话页",
			helptip: "您经常会在破坏者的用户贡献中发起许多回退,总是打开用户对话页可能不太适当,所以这个选项默认关闭。当它被打开时,依赖上一个设定。",
			type: "boolean"
		},

		// TwinkleConfig.markRevertedPagesAsMinor (array)
		// What types of actions that should result in marking edit as minor
		{
			name: "markRevertedPagesAsMinor",
			label: "将这些类型的回退标记为小修改",
			type: "set",
			setValues: { agf: "善意回退", norm: "常规回退", vand: "破坏回退", torev: "“恢复此版本”" }
		},

		// TwinkleConfig.watchRevertedPages (array)
		// What types of actions that should result in forced addition to watchlist
		{
			name: "watchRevertedPages",
			label: "把这些类型的回退加入监视列表",
			type: "set",
			setValues: { agf: "善意回退", norm: "常规回退", vand: "破坏回退", torev: "“恢复此版本”" }
		},

		// TwinkleConfig.offerReasonOnNormalRevert (boolean)
		// If to offer a prompt for extra summary reason for normal reverts, default to true
		{
			name: "offerReasonOnNormalRevert",
			label: "常规回退时询问理由",
			helptip: "“常规”回退是中间的那个[回退]链接。",
			type: "boolean"
		},

		{
			name: "confirmOnFluff",
			label: "回退前要求确认",
			helptip: "给那些手持手持设备的用户,或者意志不坚定的。",
			type: "boolean"
		},

		// TwinkleConfig.showRollbackLinks (array)
		// Where Twinkle should show rollback links (diff, others, mine, contribs)
		// Note from TTO: |contribs| seems to be equal to |others| + |mine|, i.e. redundant, so I left it out heres
		{
			name: "showRollbackLinks",
			label: "在这些页面上显示回退链接",
			type: "set",
			setValues: { diff: "差异", others: "其它用户的贡献", mine: "我的贡献" }
		}
	]
},

{
	title: "共享IP标记",
	inFriendlyConfig: true,
	preferences: [
		{
			name: "markSharedIPAsMinor",
			label: "将共享IP标记标记为小修改",
			type: "boolean"
		}
	]
},

{
	title: "快速删除",
	preferences: [
		// TwinkleConfig.watchSpeedyPages (array)
		// Whether to add speedy tagged pages to watchlist
		{
			name: "watchSpeedyPages",
			label: "将以下理由添加到监视列表",
			type: "set",
			setValues: Twinkle.config.commonSets.csdCriteria,
			setDisplayOrder: Twinkle.config.commonSets.csdCriteriaDisplayOrder
		},

		// TwinkleConfig.markSpeedyPagesAsPatrolled (boolean)
		// If, when applying speedy template to page, to mark the page as patrolled (if the page was reached from NewPages)
		{
			name: "markSpeedyPagesAsPatrolled",
			label: "标记时标记页面为已巡查(如可能)",
			helptip: "基于技术原因,页面仅会在由Special:NewPages到达时被标记为已巡查。",
			type: "boolean"
		},

		// TwinkleConfig.notifyUserOnSpeedyDeletionNomination (array)
		// What types of actions should result that the author of the page being notified of nomination
		{
			name: "notifyUserOnSpeedyDeletionNomination",
			label: "仅在使用以下理由时通知页面创建者",
			helptip: "尽管您在对话框中选择通知,通知仍只会在使用这些理由时发出。",
			type: "set",
			setValues: Twinkle.config.commonSets.csdCriteria,
			setDisplayOrder: Twinkle.config.commonSets.csdCriteriaNotificationDisplayOrder
		},

		// TwinkleConfig.welcomeUserOnSpeedyDeletionNotification (array of strings)
		// On what types of speedy deletion notifications shall the user be welcomed
		// with a "firstarticle" notice if his talk page has not yet been created.
		{
			name: "welcomeUserOnSpeedyDeletionNotification",
			label: "在使用以下理由时欢迎页面创建者",
			helptip: "欢迎模板仅在用户被通知时加入,使用的模板是{{firstarticle}}。",
			type: "set",
			setValues: Twinkle.config.commonSets.csdCriteria,
			setDisplayOrder: Twinkle.config.commonSets.csdCriteriaNotificationDisplayOrder
		},

		// TwinkleConfig.promptForSpeedyDeletionSummary (array of strings)
		{
			name: "promptForSpeedyDeletionSummary",
			label: "使用以下理由删除时允许编辑删除理由",
			adminOnly: true,
			type: "set",
			setValues: Twinkle.config.commonSets.csdAndDICriteria,
			setDisplayOrder: Twinkle.config.commonSets.csdAndDICriteriaDisplayOrder
		},

		// TwinkleConfig.openUserTalkPageOnSpeedyDelete (array of strings)
		// What types of actions that should result user talk page to be opened when speedily deleting (admin only)
		{
			name: "openUserTalkPageOnSpeedyDelete",
			label: "使用以下理由是打开用户对话页",
			adminOnly: true,
			type: "set",
			setValues: Twinkle.config.commonSets.csdAndDICriteria,
			setDisplayOrder: Twinkle.config.commonSets.csdAndDICriteriaDisplayOrder
		},

		// TwinkleConfig.deleteTalkPageOnDelete (boolean)
		// If talk page if exists should also be deleted (CSD G8) when spedying a page (admin only)
		{
			name: "deleteTalkPageOnDelete",
			label: "默认勾选“同时删除讨论页”",
			adminOnly: true,
			type: "boolean"
		},

		// TwinkleConfig.deleteSysopDefaultToTag (boolean)
		// Make the CSD screen default to "tag" instead of "delete" (admin only)
		{
			name: "deleteSysopDefaultToTag",
			label: "默认为标记而不是直接删除",
			adminOnly: true,
			type: "boolean"
		},

		// TwinkleConfig.speedyWindowWidth (integer)
		// Defines the width of the Twinkle SD window in pixels
		{
			name: "speedyWindowWidth",
			label: "快速删除对话框宽度(像素)",
			type: "integer"
		},

		// TwinkleConfig.speedyWindowWidth (integer)
		// Defines the width of the Twinkle SD window in pixels
		{
			name: "speedyWindowHeight",
			label: "快速删除对话框高度(像素)",
			helptip: "如果您有一只很大的监视器,您可以将此调高。",
			type: "integer"
		},

		{
			name: "logSpeedyNominations",
			label: "在用户空间中记录所有快速删除提名",
			helptip: "非管理员无法访问到已删除的贡献,用户空间日志提供了一个很好的方法来记录这些历史。",
			type: "boolean"
		},
		{
			name: "speedyLogPageName",
			label: "在此页保留日志",
			helptip: "如:User:<i>用户名</i>/<i>子页面</i>,仅在打开日志时工作。",
			type: "string"
		},
		{
			name: "noLogOnSpeedyNomination",
			label: "在使用以下理由时不做记录",
			type: "set",
			setValues: Twinkle.config.commonSets.csdAndDICriteria,
			setDisplayOrder: Twinkle.config.commonSets.csdAndDICriteriaDisplayOrder
		}
	]
},

{
	title: "标记",
	inFriendlyConfig: true,
	preferences: [
		{
			name: "watchTaggedPages",
			label: "标记时添加到监视列表",
			type: "boolean"
		},
		{
			name: "markTaggedPagesAsMinor",
			label: "将标记标记为小修改",
			type: "boolean"
		},
		{
			name: "markTaggedPagesAsPatrolled",
			label: "标记时标记页面为已巡查(如可能)",
			helptip: "基于技术原因,页面仅会在由Special:NewPages到达时被标记为已巡查。",
			type: "boolean"
		},
		{
			name: "groupByDefault",
			label: "默认勾选“合并到{{multiple issues}}”复选框",
			type: "boolean"
		},
		{
			name: "tagArticleSortOrder",
			label: "条目标记的默认察看方式",
			type: "enum",
			enumValues: { "cat": "按类别", "alpha": "按字母" }
		},
		{
			name: "customTagList",
			label: "自定义条目维护标记",
			helptip: "这些会出现在列表的末尾。",
			type: "customList",
			customListValueTitle: "模板名(不含大括号)",
			customListLabelTitle: "显示的文字"
		}
	]
},

{
	title: "回复",
	inFriendlyConfig: true,
	preferences: [
		{
			name: "markTalkbackAsMinor",
			label: "将回复标记为小修改",
			type: "boolean"
		},
		{
			name: "insertTalkbackSignature",
			label: "回复时添加签名",
			type: "boolean"
		},
		{
			name: "talkbackHeading",
			label: "回复所用的小节标题",
			type: "string"
		},
		{
			name: "adminNoticeHeading",
			label: "管理员通告板所用的小节标题",
			type: "string"
		}
	]
},

{
	title: "反链",
	preferences: [
		// TwinkleConfig.unlinkNamespaces (array)
		// In what namespaces unlink should happen, default in 0 (article) and 100 (portal)
		{
			name: "unlinkNamespaces",
			label: "取消以下名字空间中的反链",
			helptip: "请避免选择讨论页,因这样会导致Twinkle试图修改讨论存档。",
			type: "set",
			setValues: Twinkle.config.commonSets.namespacesNoSpecial
		}
	]
},

{
	title: "警告用户",
	preferences: [
		// TwinkleConfig.defaultWarningGroup (int)
		// if true, watch the page which has been dispatched an warning or notice, if false, default applies
		{
			name: "defaultWarningGroup",
			label: "默认警告级别",
			type: "enum",
			enumValues: { "1": "层级1", "2": "层级2", "3": "层级3", "4": "层级4", "5": "层级4im", "6": "单层级通知", "7": "单层级警告", "8": "封禁" }
		},

		// TwinkleConfig.showSharedIPNotice may take arguments:
		// true: to show shared ip notice if an IP address
		// false: to not print the notice
		{
			name: "showSharedIPNotice",
			label: "在IP对话页上显示附加信息",
			helptip: "使用的模板是{{SharedIPAdvice}}。",
			type: "boolean"
		},

		// TwinkleConfig.watchWarnings (boolean)
		// if true, watch the page which has been dispatched an warning or notice, if false, default applies
		{
			name: "watchWarnings",
			label: "警告时添加用户对话页到监视列表",
			type: "boolean"
		},

		// TwinkleConfig.blankTalkpageOnIndefBlock (boolean)
		// if true, blank the talk page when issuing an indef block notice (per [[WP:UW#Indefinitely blocked users]])
		{
			name: "blankTalkpageOnIndefBlock",
			label: "无限期封禁时清空对话页",
			adminOnly: true,
			type: "boolean"
		}
	]
},

{
	title: "欢迎用户",
	inFriendlyConfig: true,
	preferences: [
		{
			name: "topWelcomes",
			label: "将欢迎置于对话页最顶",
			type: "boolean"
		},
		{
			name: "watchWelcomes",
			label: "欢迎时添加用户对话页到监视列表",
			helptip: "您将可以更好地帮助他。",
			type: "boolean"
		},
		{
			name: "insertHeadings",
			label: "在欢迎之上插入小节标题",
			type: "boolean"
		},
		{
			name: "welcomeHeading",
			label: "欢迎所使用的小节标题",
			helptip: "仅当上一项启用且模板不含标题时有效。",
			type: "string"
		},
		{
			name: "insertUsername",
			label: "添加您的用户名到模板(如适用)",
			type: "boolean"
		},
		{
			name: "insertSignature",
			label: "在欢迎后添加您的签名",
			helptip: "强烈推荐。",
			type: "boolean"
		},
		{
			name: "markWelcomesAsMinor",
			label: "将欢迎标记为小修改",
			type: "boolean"
		},
		{
			name: "maskTemplateInSummary",
			label: "在编辑摘要中隐藏模板名",
			helptip: "一些模板的名字(如“welcome-anon-vandal”)可能被用户当成人身攻击,所以最好别在摘要里包含它们。",
			type: "boolean"
		},
		{
			name: "quickWelcomeMode",
			label: "点击差异上的“欢迎”链接会",
			helptip: "如果您选择自动欢迎,您选择的模板将会被使用。",
			type: "enum",
			enumValues: { auto: "自动欢迎", norm: "提示您选择一个模板" }
		},
		{
			name: "quickWelcomeTemplate",
			label: "自动欢迎时所用的模板",
			helptip: "输入模板名,不带大括号,条目名将会被当作参数。",
			type: "string"
		},
		{
			name: "customWelcomeList",
			label: "自定义欢迎模板",
			helptip: "您可以添加其它模板或用户子页面,请记住它们将被替换引用。",
			type: "customList",
			customListValueTitle: "模板名(不带大括号)",
			customListLabelTitle: "显示的文本"
		}
	]
},

{
	title: "存废讨论",
	preferences: [
		// TwinkleConfig.xfdWatchPage (string)
		// The watchlist setting of the page being nominated for XfD. Either "yes" (add to watchlist), "no" (don't
		// add to watchlist), or "default" (use setting from preferences). Default is "default" (duh).
		{
			name: "xfdWatchPage",
			label: "添加提名的页面到监视列表",
			type: "enum",
			enumValues: Twinkle.config.commonEnums.watchlist
		},

		// TwinkleConfig.xfdWatchDiscussion (string)
		// The watchlist setting of the newly created XfD page (for those processes that create discussion pages for each nomination),
		// or the list page for the other processes.
		// Either "yes" (add to watchlist), "no" (don't add to watchlist), or "default" (use setting from preferences). Default is "default" (duh).
		{
			name: "xfdWatchDiscussion",
			label: "添加存废讨论页到监视列表",
			helptip: "当日的页面。",
			type: "enum",
			enumValues: Twinkle.config.commonEnums.watchlist
		},

		// TwinkleConfig.xfdWatchUser (string)
		// The watchlist setting of the user if he receives a notification. Either "yes" (add to watchlist), "no" (don't
		// add to watchlist), or "default" (use setting from preferences). Default is "default" (duh).
		{
			name: "xfdWatchUser",
			label: "添加创建者对话页到监视列表(在通知时)",
			type: "enum",
			enumValues: Twinkle.config.commonEnums.watchlist
		},

		// TwinkleConfig.markXfdPagesAsPatrolled (boolean)
		// If, when applying xfd template to page, to mark the page as patrolled (if the page was reached from NewPages)
		{
			name: "markXfdPagesAsPatrolled",
			label: "标记时标记页面为已巡查(如可能)",
			helptip: "基于技术原因,页面仅会在由Special:NewPages到达时被标记为已巡查。",
			type: "boolean"
		}
	]

},

{
	title: "侵犯版权",
	preferences: [
		// TwinkleConfig.copyvioWatchPage (string)
		// The watchlist setting of the page being nominated for XfD. Either "yes" (add to watchlist), "no" (don't
		// add to watchlist), or "default" (use setting from preferences). Default is "default" (duh).
		{
			name: "copyvioWatchPage",
			label: "添加提报的页面到监视列表",
			type: "enum",
			enumValues: Twinkle.config.commonEnums.watchlist
		},

		// TwinkleConfig.copyvioWatchUser (string)
		// The watchlist setting of the user if he receives a notification. Either "yes" (add to watchlist), "no" (don't
		// add to watchlist), or "default" (use setting from preferences). Default is "default" (duh).
		{
			name: "copyvioWatchUser",
			label: "添加创建者对话页到监视列表(在通知时)",
			type: "enum",
			enumValues: Twinkle.config.commonEnums.watchlist
		},

		// TwinkleConfig.markCopyvioPagesAsPatrolled (boolean)
		// If, when applying copyvio template to page, to mark the page as patrolled (if the page was reached from NewPages)
		{
			name: "markCopyvioPagesAsPatrolled",
			label: "标记时标记页面为已巡查(如可能)",
			helptip: "基于技术原因,页面仅会在由Special:NewPages到达时被标记为已巡查。",
			type: "boolean"
		},

	]
},

{
	title: "隐藏",
	hidden: true,
	preferences: [
		// twinkle.header.js: portlet setup
		{
			name: "portletArea",
			type: "string"
		},
		{
			name: "portletId",
			type: "string"
		},
		{
			name: "portletName",
			type: "string"
		},
		{
			name: "portletType",
			type: "string"
		},
		{
			name: "portletNext",
			type: "string"
		},
		// twinklefluff.js: defines how many revision to query maximum, maximum possible is 50, default is 50
		{
			name: "revertMaxRevisions",
			type: "integer"
		},
		// twinklebatchdelete.js: How many pages should be processed at a time
		{
			name: "batchdeleteChunks",
			type: "integer"
		},
		// twinklebatchdelete.js: How many pages left in the process of being completed should allow a new batch to be initialized
		{
			name: "batchDeleteMinCutOff",
			type: "integer"
		},
		// twinklebatchdelete.js: How many pages should be processed maximum
		{
			name: "batchMax",
			type: "integer"
		},
		// twinklebatchprotect.js: How many pages should be processed at a time
		{
			name: "batchProtectChunks",
			type: "integer"
		},
		// twinklebatchprotect.js: How many pages left in the process of being completed should allow a new batch to be initialized
		{
			name: "batchProtectMinCutOff",
			type: "integer"
		},
		// twinklebatchundelete.js: How many pages should be processed at a time
		{
			name: "batchundeleteChunks",
			type: "integer"
		},
		// twinklebatchundelete.js: How many pages left in the process of being completed should allow a new batch to be initialized
		{
			name: "batchUndeleteMinCutOff",
			type: "integer"
		},
		// twinkledelimages.js: How many files should be processed at a time
		{
			name: "deliChunks",
			type: "integer"
		},
		// twinkledelimages.js: How many files should be processed maximum
		{
			name: "deliMax",
			type: "integer"
		},
		// twinkledeprod.js: How many pages should be processed at a time
		{
			name: "proddeleteChunks",
			type: "integer"
		}
	]
}

]; // end of Twinkle.config.sections

//{
//			name: "",
//			label: "",
//			type: ""
//		},


Twinkle.config.init = function twinkleconfigInit() {

	if ((mw.config.get("wgPageName") === "Wikipedia:Twinkle/参数设置" ||
	    (mw.config.get("wgNamespaceNumber") === 2 && mw.config.get("wgTitle").lastIndexOf("/Twinkle参数设置") === (mw.config.get("wgTitle").length - 20))) &&
	    mw.config.get("wgAction") === "view") {
		// create the config page at Wikipedia:Twinkle/参数设置, and at user subpages (for testing purposes)

		if (!document.getElementById("twinkle-config")) {
			return;  // maybe the page is misconfigured, or something - but any attempt to modify it will be pointless
		}

		// set style (the url() CSS function doesn't seem to work from wikicode - ?!)
		document.getElementById("twinkle-config-titlebar").style.backgroundImage = "url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAAkCAMAAAB%2FqqA%2BAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAEhQTFRFr73ZobTPusjdsMHZp7nVwtDhzNbnwM3fu8jdq7vUt8nbxtDkw9DhpbfSvMrfssPZqLvVztbno7bRrr7W1d%2Fs1N7qydXk0NjpkW7Q%2BgAAADVJREFUeNoMwgESQCAAAMGLkEIi%2FP%2BnbnbpdB59app5Vdg0sXAoMZCpGoFbK6ciuy6FX4ABAEyoAef0BXOXAAAAAElFTkSuQmCC)";

		var contentdiv = document.getElementById("twinkle-config-content");
		contentdiv.textContent = "";  // clear children

		// let user know about possible conflict with monobook.js/vector.js file
		// (settings in that file will still work, but they will be overwritten by twinkleoptions.js settings)
		var contentnotice = document.createElement("p");
		// I hate innerHTML, but this is one thing it *is* good for...
		contentnotice.innerHTML = "<b>在这里修改您的参数设置之前,</b>确认您已移除了<a href=\"" + mw.util.getUrl("Special:MyPage/skin.js") + "\" title=\"Special:MyPage/skin.js\">用户JavaScript文件</a>中任何旧的<code>FriendlyConfig</code>设置。";
		contentdiv.appendChild(contentnotice);

		// look and see if the user does in fact have any old settings in their skin JS file
		var skinjs = new Wikipedia.page("User:" + mw.config.get("wgUserName") + "/" + mw.config.get("skin") + ".js");
		skinjs.setCallbackParameters(contentnotice);
		skinjs.load(Twinkle.config.legacyPrefsNotice);

		// start a table of contents
		var toctable = document.createElement("table");
		toctable.className = "toc";
		toctable.style.marginLeft = "0.4em";
		var toctr = document.createElement("tr");
		var toctd = document.createElement("td");
		// create TOC title
		var toctitle = document.createElement("div");
		toctitle.id = "toctitle";
		var toch2 = document.createElement("h2");
		toch2.textContent = "目录 ";
		toctitle.appendChild(toch2);
		// add TOC show/hide link
		var toctoggle = document.createElement("span");
		toctoggle.className = "toctoggle";
		toctoggle.appendChild(document.createTextNode("["));
		var toctogglelink = document.createElement("a");
		toctogglelink.className = "internal";
		toctogglelink.setAttribute("href", "#tw-tocshowhide");
		toctogglelink.textContent = "隐藏";
		toctoggle.appendChild(toctogglelink);
		toctoggle.appendChild(document.createTextNode("]"));
		toctitle.appendChild(toctoggle);
		toctd.appendChild(toctitle);
		// create item container: this is what we add stuff to
		var tocul = document.createElement("ul");
		toctogglelink.addEventListener("click", function twinkleconfigTocToggle() {
			var $tocul = $(tocul);
			$tocul.toggle();
			if ($tocul.find(":visible").length) {
				toctogglelink.textContent = "隐藏";
			} else {
				toctogglelink.textContent = "显示";
			}
		}, false);
		toctd.appendChild(tocul);
		toctr.appendChild(toctd);
		toctable.appendChild(toctr);
		contentdiv.appendChild(toctable);

		var tocnumber = 1;

		var contentform = document.createElement("form");
		contentform.setAttribute("action", "javascript:void(0)");  // was #tw-save - changed to void(0) to work around Chrome issue
		contentform.addEventListener("submit", Twinkle.config.save, true);
		contentdiv.appendChild(contentform);

		var container = document.createElement("table");
		container.style.width = "100%";
		contentform.appendChild(container);

		$(Twinkle.config.sections).each(function(sectionkey, section) {
			if (section.hidden || (section.adminOnly && !userIsInGroup("sysop"))) {
				return true;  // i.e. "continue" in this context
			}

			var configgetter;  // retrieve the live config values
			if (section.inFriendlyConfig) {
				configgetter = Twinkle.getFriendlyPref;
			} else {
				configgetter = Twinkle.getPref;
			}

			// add to TOC
			var tocli = document.createElement("li");
			tocli.className = "toclevel-1";
			var toca = document.createElement("a");
			toca.setAttribute("href", "#twinkle-config-section-" + tocnumber.toString());
			toca.appendChild(document.createTextNode(section.title));
			tocli.appendChild(toca);
			tocul.appendChild(tocli);

			var row = document.createElement("tr");
			var cell = document.createElement("td");
			cell.setAttribute("colspan", "3");
			var heading = document.createElement("h4");
			heading.style.borderBottom = "1px solid gray";
			heading.style.marginTop = "0.2em";
			heading.id = "twinkle-config-section-" + (tocnumber++).toString();
			heading.appendChild(document.createTextNode(section.title));
			cell.appendChild(heading);
			row.appendChild(cell);
			container.appendChild(row);

			var rowcount = 1;  // for row banding

			// add each of the preferences to the form
			$(section.preferences).each(function(prefkey, pref) {
				if (pref.adminOnly && !userIsInGroup("sysop")) {
					return true;  // i.e. "continue" in this context
				}

				row = document.createElement("tr");
				row.style.marginBottom = "0.2em";
				// create odd row banding
				if (rowcount++ % 2 === 0) {
					row.style.backgroundColor = "rgba(128, 128, 128, 0.1)";
				}
				cell = document.createElement("td");

				var label, input;
				switch (pref.type) {

					case "boolean":  // create a checkbox
						cell.setAttribute("colspan", "2");

						label = document.createElement("label");
						input = document.createElement("input");
						input.setAttribute("type", "checkbox");
						input.setAttribute("id", pref.name);
						input.setAttribute("name", pref.name);
						if (configgetter(pref.name) === true) {
							input.setAttribute("checked", "checked");
						}
						label.appendChild(input);
						label.appendChild(document.createTextNode(" " + pref.label));
						cell.appendChild(label);
						break;

					case "string":  // create an input box
					case "integer":
						// add label to first column
						cell.style.textAlign = "right";
						cell.style.paddingRight = "0.5em";
						label = document.createElement("label");
						label.setAttribute("for", pref.name);
						label.appendChild(document.createTextNode(pref.label + ":"));
						cell.appendChild(label);
						row.appendChild(cell);

						// add input box to second column
						cell = document.createElement("td");
						cell.style.paddingRight = "1em";
						input = document.createElement("input");
						input.setAttribute("type", "text");
						input.setAttribute("id", pref.name);
						input.setAttribute("name", pref.name);
						if (pref.type === "integer") {
							input.setAttribute("size", 6);
							input.setAttribute("type", "number");
							input.setAttribute("step", "1");  // integers only
						}
						if (configgetter(pref.name)) {
							input.setAttribute("value", configgetter(pref.name));
						}
						cell.appendChild(input);
						break;

					case "enum":  // create a combo box
						// add label to first column
						// note: duplicates the code above, under string/integer
						cell.style.textAlign = "right";
						cell.style.paddingRight = "0.5em";
						label = document.createElement("label");
						label.setAttribute("for", pref.name);
						label.appendChild(document.createTextNode(pref.label + ":"));
						cell.appendChild(label);
						row.appendChild(cell);

						// add input box to second column
						cell = document.createElement("td");
						cell.style.paddingRight = "1em";
						input = document.createElement("select");
						input.setAttribute("id", pref.name);
						input.setAttribute("name", pref.name);
						$.each(pref.enumValues, function(enumvalue, enumdisplay) {
							var option = document.createElement("option");
							option.setAttribute("value", enumvalue);
							if (configgetter(pref.name) == enumvalue) {
								option.setAttribute("selected", "selected");
							}
							option.appendChild(document.createTextNode(enumdisplay));
							input.appendChild(option);
						});
						cell.appendChild(input);
						break;

					case "set":  // create a set of check boxes
						// add label first of all
						cell.setAttribute("colspan", "2");
						label = document.createElement("label");  // not really necessary to use a label element here, but we do it for consistency of styling
						label.appendChild(document.createTextNode(pref.label + ":"));
						cell.appendChild(label);

						var checkdiv = document.createElement("div");
						checkdiv.style.paddingLeft = "1em";
						var worker = function(itemkey, itemvalue) {
							var checklabel = document.createElement("label");
							checklabel.style.marginRight = "0.7em";
							checklabel.style.display = "inline-block";
							var check = document.createElement("input");
							check.setAttribute("type", "checkbox");
							check.setAttribute("id", pref.name + "_" + itemkey);
							check.setAttribute("name", pref.name + "_" + itemkey);
							if (configgetter(pref.name) && configgetter(pref.name).indexOf(itemkey) !== -1) {
								check.setAttribute("checked", "checked");
							}
							// cater for legacy integer array values for unlinkNamespaces (this can be removed a few years down the track...)
							if (pref.name === "unlinkNamespaces") {
								if (configgetter(pref.name) && configgetter(pref.name).indexOf(parseInt(itemkey, 10)) !== -1) {
									check.setAttribute("checked", "checked");
								}
							}
							checklabel.appendChild(check);
							checklabel.appendChild(document.createTextNode(itemvalue));
							checkdiv.appendChild(checklabel);
						};
						if (pref.setDisplayOrder) {
							// add check boxes according to the given display order
							$.each(pref.setDisplayOrder, function(itemkey, item) {
								worker(item, pref.setValues[item]);
							});
						} else {
							// add check boxes according to the order it gets fed to us (probably strict alphabetical)
							$.each(pref.setValues, worker);
						}
						cell.appendChild(checkdiv);
						break;

					case "customList":
						// add label to first column
						cell.style.textAlign = "right";
						cell.style.paddingRight = "0.5em";
						label = document.createElement("label");
						label.setAttribute("for", pref.name);
						label.appendChild(document.createTextNode(pref.label + ":"));
						cell.appendChild(label);
						row.appendChild(cell);

						// add button to second column
						cell = document.createElement("td");
						cell.style.paddingRight = "1em";
						var button = document.createElement("button");
						button.setAttribute("id", pref.name);
						button.setAttribute("name", pref.name);
						button.setAttribute("type", "button");
						button.addEventListener("click", Twinkle.config.listDialog.display, false);
						// use jQuery data on the button to store the current config value
						$(button).data({
							value: configgetter(pref.name),
							pref: pref,
							inFriendlyConfig: section.inFriendlyConfig
						});
						button.appendChild(document.createTextNode("编辑项目"));
						cell.appendChild(button);
						break;

					default:
						alert("twinkleconfig: 未知类型的属性 " + pref.name);
						break;
				}
				row.appendChild(cell);

				// add help tip
				cell = document.createElement("td");
				cell.style.fontSize = "90%";

				cell.style.color = "gray";
				if (pref.helptip) {
					cell.innerHTML = pref.helptip;
				}
				// add reset link (custom lists don't need this, as their config value isn't displayed on the form)
				if (pref.type !== "customList") {
					var resetlink = document.createElement("a");
					resetlink.setAttribute("href", "#tw-reset");
					resetlink.setAttribute("id", "twinkle-config-reset-" + pref.name);
					resetlink.addEventListener("click", Twinkle.config.resetPrefLink, false);
					if (resetlink.style.styleFloat) {  // IE (inc. IE9)
						resetlink.style.styleFloat = "right";
					} else {  // standards
						resetlink.style.cssFloat = "right";
					}
					resetlink.style.margin = "0 0.6em";
					resetlink.appendChild(document.createTextNode("复位"));
					cell.appendChild(resetlink);
				}
				row.appendChild(cell);

				container.appendChild(row);
				return true;
			});
			return true;
		});

		var footerbox = document.createElement("div");
		footerbox.setAttribute("id", "twinkle-config-buttonpane");
		footerbox.style.backgroundColor = "#BCCADF";
		footerbox.style.padding = "0.5em";
		var button = document.createElement("button");
		button.setAttribute("id", "twinkle-config-submit");
		button.setAttribute("type", "submit");
		button.appendChild(document.createTextNode("保存修改"));
		footerbox.appendChild(button);
		var footerspan = document.createElement("span");
		footerspan.className = "plainlinks";
		footerspan.style.marginLeft = "2.4em";
		footerspan.style.fontSize = "90%";
		var footera = document.createElement("a");
		footera.setAttribute("href", "#tw-reset-all");
		footera.setAttribute("id", "twinkle-config-resetall");
		footera.addEventListener("click", Twinkle.config.resetAllPrefs, false);
		footera.appendChild(document.createTextNode("恢复默认"));
		footerspan.appendChild(footera);
		footerbox.appendChild(footerspan);
		contentform.appendChild(footerbox);

		// since all the section headers exist now, we can try going to the requested anchor
		if (location.hash) {
			location.hash = location.hash;
		}

	} else if (mw.config.get("wgNamespaceNumber") === 2) {

		var box = document.createElement("div");
		box.setAttribute("id", "twinkle-config-headerbox");
		box.style.border = "1px #f60 solid";
		box.style.background = "#fed";
		box.style.padding = "0.6em";
		box.style.margin = "0.5em auto";
		box.style.textAlign = "center";

		var link;
		if (mw.config.get("wgTitle") === mw.config.get("wgUserName") + "/twinkleoptions.js") {
			// place "why not try the preference panel" notice
			box.style.fontWeight = "bold";
			box.style.width = "80%";
			box.style.borderWidth = "2px";

			if (mw.config.get("wgArticleId") > 0) {  // page exists
				box.appendChild(document.createTextNode("这页包含您的Twinkle参数设置,您可使用"));
			} else {  // page does not exist
				box.appendChild(document.createTextNode("您可配置您的Twinkle,通过使用"));
			}
			link = document.createElement("a");
			link.setAttribute("href", mw.util.getUrl("Wikipedia:Twinkle/参数设置") );
			link.appendChild(document.createTextNode("Twinkle参数设置面板"));
			box.appendChild(link);
			box.appendChild(document.createTextNode(",或直接编辑本页。"));
			$(box).insertAfter($("#contentSub"));

		} else if (mw.config.get("wgTitle").indexOf(mw.config.get("wgUserName")) === 0 && mw.config.get("wgTitle").lastIndexOf(".js") == mw.config.get("wgTitle").length - 3) {
			// place "Looking for Twinkle options?" notice
			box.style.width = "60%";

			box.appendChild(document.createTextNode("如果您想配置您的Twinkle,请使用"));
			link = document.createElement("a");
			link.setAttribute("href", mw.util.getUrl("Wikipedia:Twinkle/参数设置") );
			link.appendChild(document.createTextNode("Twinkle参数设置面板"));
			box.appendChild(link);
			box.appendChild(document.createTextNode("。"));
			$(box).insertAfter($("#contentSub"));
		}
	}
};

// Wikipedia.page callback from init code
Twinkle.config.legacyPrefsNotice = function twinkleconfigLegacyPrefsNotice(pageobj) {
	var text = pageobj.getPageText();
	var contentnotice = pageobj.getCallbackParameters();
	if (text.indexOf("TwinkleConfig") !== -1 || text.indexOf("FriendlyConfig") !== -1) {
		contentnotice.innerHTML = '<table class="plainlinks ombox ombox-content"><tr><td class="mbox-image">' +
			'<img alt="" src="http://upload.wikimedia.org/wikipedia/en/3/38/Imbox_content.png" /></td>' +
			'<td class="mbox-text"><p><big><b>在这里修改您的参数设置之前,</b>您必须移除在用户JavaScript文件中任何旧的Friendly设置。</big></p>' +
			'<p>要这样做,您可以<a href="' + mw.config.get("wgScript") + '?title=User:' + encodeURIComponent(mw.config.get("wgUserName")) + '/' + mw.config.get("skin") + '.js&action=edit" target="_tab"><b>编辑您的个人JavaScript</b></a>。删除提到<code>FriendlyConfig</code>的代码。</p>' +
			'</td></tr></table>';
	} else {
		$(contentnotice).remove();
	}
};

// custom list-related stuff

Twinkle.config.listDialog = {};

Twinkle.config.listDialog.addRow = function twinkleconfigListDialogAddRow(dlgtable, value, label) {
	var contenttr = document.createElement("tr");
	// "remove" button
	var contenttd = document.createElement("td");
	var removeButton = document.createElement("button");
	removeButton.setAttribute("type", "button");
	removeButton.addEventListener("click", function() { $(contenttr).remove(); }, false);
	removeButton.textContent = "移除";
	contenttd.appendChild(removeButton);
	contenttr.appendChild(contenttd);

	// value input box
	contenttd = document.createElement("td");
	var input = document.createElement("input");
	input.setAttribute("type", "text");
	input.className = "twinkle-config-customlist-value";
	input.style.width = "97%";
	if (value) {
		input.setAttribute("value", value);
	}
	contenttd.appendChild(input);
	contenttr.appendChild(contenttd);

	// label input box
	contenttd = document.createElement("td");
	input = document.createElement("input");
	input.setAttribute("type", "text");
	input.className = "twinkle-config-customlist-label";
	input.style.width = "98%";
	if (label) {
		input.setAttribute("value", label);
	}
	contenttd.appendChild(input);
	contenttr.appendChild(contenttd);

	dlgtable.appendChild(contenttr);
};

Twinkle.config.listDialog.display = function twinkleconfigListDialogDisplay(e) {
	var $prefbutton = $(e.target);
	var curvalue = $prefbutton.data("value");
	var curpref = $prefbutton.data("pref");

	var dialog = new SimpleWindow(720, 400);
	dialog.setTitle(curpref.label);
	dialog.setScriptName("Twinkle参数设置");

	var dialogcontent = document.createElement("div");
	var dlgtable = document.createElement("table");
	dlgtable.className = "wikitable";
	dlgtable.style.margin = "1.4em 1em";
	dlgtable.style.width = "auto";

	var dlgtbody = document.createElement("tbody");

	// header row
	var dlgtr = document.createElement("tr");
	// top-left cell
	var dlgth = document.createElement("th");
	dlgth.style.width = "5%";
	dlgtr.appendChild(dlgth);
	// value column header
	dlgth = document.createElement("th");
	dlgth.style.width = "35%";
	dlgth.textContent = (curpref.customListValueTitle ? curpref.customListValueTitle : "数值");
	dlgtr.appendChild(dlgth);
	// label column header
	dlgth = document.createElement("th");
	dlgth.style.width = "60%";
	dlgth.textContent = (curpref.customListLabelTitle ? curpref.customListLabelTitle : "标签");
	dlgtr.appendChild(dlgth);
	dlgtbody.appendChild(dlgtr);

	// content rows
	var gotRow = false;
	$.each(curvalue, function(k, v) {
		gotRow = true;
		Twinkle.config.listDialog.addRow(dlgtbody, v.value, v.label);
	});
	// if there are no values present, add a blank row to start the user off
	if (!gotRow) {
		Twinkle.config.listDialog.addRow(dlgtbody);
	}

	// final "add" button
	var dlgtfoot = document.createElement("tfoot");
	dlgtr = document.createElement("tr");
	var dlgtd = document.createElement("td");
	dlgtd.setAttribute("colspan", "3");
	var addButton = document.createElement("button");
	addButton.style.minWidth = "8em";
	addButton.setAttribute("type", "button");
	addButton.addEventListener("click", function(e) {
		Twinkle.config.listDialog.addRow(dlgtbody);
	}, false);
	addButton.textContent = "添加";
	dlgtd.appendChild(addButton);
	dlgtr.appendChild(dlgtd);
	dlgtfoot.appendChild(dlgtr);

	dlgtable.appendChild(dlgtbody);
	dlgtable.appendChild(dlgtfoot);
	dialogcontent.appendChild(dlgtable);

	// buttonpane buttons: [Save changes] [Reset] [Cancel]
	var button = document.createElement("button");
	button.setAttribute("type", "submit");  // so SimpleWindow puts the button in the button pane
	button.addEventListener("click", function(e) {
		Twinkle.config.listDialog.save($prefbutton, dlgtbody);
		dialog.close();
	}, false);
	button.textContent = "保存修改";
	dialogcontent.appendChild(button);
	button = document.createElement("button");
	button.setAttribute("type", "submit");  // so SimpleWindow puts the button in the button pane
	button.addEventListener("click", function(e) {
		Twinkle.config.listDialog.reset($prefbutton, dlgtbody);
	}, false);
	button.textContent = "复位";
	dialogcontent.appendChild(button);
	button = document.createElement("button");
	button.setAttribute("type", "submit");  // so SimpleWindow puts the button in the button pane
	button.addEventListener("click", function(e) {
		dialog.close();  // the event parameter on this function seems to be broken
	}, false);
	button.textContent = "取消";
	dialogcontent.appendChild(button);

	dialog.setContent(dialogcontent);
	dialog.display();
};

// Resets the data value, re-populates based on the new (default) value, then saves the
// old data value again (less surprising behaviour)
Twinkle.config.listDialog.reset = function twinkleconfigListDialogReset(button, tbody) {
	// reset value on button
	var $button = $(button);
	var curpref = $button.data("pref");
	var oldvalue = $button.data("value");
	Twinkle.config.resetPref(curpref, $button.data("inFriendlyConfig"));

	// reset form
	var $tbody = $(tbody);
	$tbody.find("tr").slice(1).remove();  // all rows except the first (header) row
	// add the new values
	var curvalue = $button.data("value");
	$.each(curvalue, function(k, v) {
		Twinkle.config.listDialog.addRow(tbody, v.value, v.label);
	});

	// save the old value
	$button.data("value", oldvalue);
};

Twinkle.config.listDialog.save = function twinkleconfigListDialogSave(button, tbody) {
	var result = [];
	var current = {};
	$(tbody).find('input[type="text"]').each(function(inputkey, input) {
		if ($(input).hasClass("twinkle-config-customlist-value")) {
			current = { value: input.value };
		} else {
			current.label = input.value;
			// exclude totally empty rows
			if (current.value || current.label) {
				result.push(current);
			}
		}
	});
	$(button).data("value", result);
};

// reset/restore defaults

Twinkle.config.resetPrefLink = function twinkleconfigResetPrefLink(e) {
	var wantedpref = e.target.id.substring(21); // "twinkle-config-reset-" prefix is stripped

	// search tactics
	$(Twinkle.config.sections).each(function(sectionkey, section) {
		if (section.hidden || (section.adminOnly && !userIsInGroup("sysop"))) {
			return true;  // continue: skip impossibilities
		}

		var foundit = false;

		$(section.preferences).each(function(prefkey, pref) {
			if (pref.name !== wantedpref) {
				return true;  // continue
			}
			Twinkle.config.resetPref(pref, section.inFriendlyConfig);
			foundit = true;
			return false;  // break
		});

		if (foundit) {
			return false;  // break
		}
	});
	return false;  // stop link from scrolling page
};

Twinkle.config.resetPref = function twinkleconfigResetPref(pref, inFriendlyConfig) {
	switch (pref.type) {

		case "boolean":
			document.getElementById(pref.name).checked = (inFriendlyConfig ?
				Twinkle.defaultConfig.friendly[pref.name] : Twinkle.defaultConfig.twinkle[pref.name]);
			break;

		case "string":
		case "integer":
		case "enum":
			document.getElementById(pref.name).value = (inFriendlyConfig ?
				Twinkle.defaultConfig.friendly[pref.name] : Twinkle.defaultConfig.twinkle[pref.name]);
			break;

		case "set":
			$.each(pref.setValues, function(itemkey, itemvalue) {
				if (document.getElementById(pref.name + "_" + itemkey)) {
					document.getElementById(pref.name + "_" + itemkey).checked = ((inFriendlyConfig ?
						Twinkle.defaultConfig.friendly[pref.name] : Twinkle.defaultConfig.twinkle[pref.name]).indexOf(itemkey) !== -1);
				}
			});
			break;

		case "customList":
			$(document.getElementById(pref.name)).data("value", (inFriendlyConfig ?
				Twinkle.defaultConfig.friendly[pref.name] : Twinkle.defaultConfig.twinkle[pref.name]));
			break;

		default:
			alert("twinkleconfig: unknown data type for preference " + pref.name);
			break;
	}
};

Twinkle.config.resetAllPrefs = function twinkleconfigResetAllPrefs() {
	// no confirmation message - the user can just refresh/close the page to abort
	$(Twinkle.config.sections).each(function(sectionkey, section) {
		if (section.hidden || (section.adminOnly && !userIsInGroup("sysop"))) {
			return true;  // continue: skip impossibilities
		}
		$(section.preferences).each(function(prefkey, pref) {
			if (!pref.adminOnly || userIsInGroup("sysop")) {
				Twinkle.config.resetPref(pref, section.inFriendlyConfig);
			}
		});
		return true;
	});
	return false;  // stop link from scrolling page
};

Twinkle.config.save = function twinkleconfigSave(e) {
	Status.init( document.getElementById("twinkle-config-content") );

	Wikipedia.actionCompleted.notice = "保存";

	var userjs = "User:" + mw.config.get("wgUserName") + "/twinkleoptions.js";
	var wikipedia_page = new Wikipedia.page(userjs, "保存参数设置到 " + userjs);
	wikipedia_page.setCallbackParameters(e.target);
	wikipedia_page.load(Twinkle.config.writePrefs);

	return false;
};

// The JSON stringify method in the following code was excerpted from
// http://www.JSON.org/json2.js
// version of 2011-02-23

// Douglas Crockford, the code's author, has released it into the Public Domain.
// See http://www.JSON.org/js.html

var JSON;
if (!JSON) {
	JSON = {};
}

(function() {
	var escapable = /[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,
		gap,
		indent = '  ',  // hardcoded indent
		meta = { '\b': '\\b', '\t': '\\t', '\n': '\\n', '\f': '\\f', '\r': '\\r', '"' : '\\"', '\\': '\\\\' };

	function quote(string) {
		escapable.lastIndex = 0;
		return escapable.test(string) ? '"' + string.replace(escapable, function (a) {
			var c = meta[a];
			return typeof c === 'string' ? c :	'\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4);
		}) + '"' : '"' + string + '"';
	}

	function str(key, holder) {
		var i, k, v, length, mind = gap, partial, value = holder[key];

		if (value && typeof value === 'object' && typeof value.toJSON === 'function') {
			value = value.toJSON(key);
		}

		switch (typeof value) {
		case 'string':
			return quote(value);
		case 'number':
			return isFinite(value) ? String(value) : 'null';
		case 'boolean':
		case 'null':
			return String(value);
		case 'object':
			if (!value) {
				return 'null';
			}
			gap += indent;
			partial = [];
			if (Object.prototype.toString.apply(value) === '[object Array]') {
				length = value.length;
				for (i = 0; i < length; i += 1) {
					partial[i] = str(i, value) || 'null';
				}
				v = partial.length === 0 ? '[]' : gap ?
					'[\n' + gap + partial.join(',\n' + gap) + '\n' + mind + ']' :
					'[' + partial.join(',') + ']';
				gap = mind;
				return v;
			}
			for (k in value) {
				if (Object.prototype.hasOwnProperty.call(value, k)) {
					v = str(k, value);
					if (v) {
						partial.push(quote(k) + (gap ? ': ' : ':') + v);
					}
				}
			}
			v = partial.length === 0 ? '{}' : gap ?
				'{\n' + gap + partial.join(',\n' + gap) + '\n' + mind + '}' :
				'{' + partial.join(',') + '}';
			gap = mind;
			return v;
		default:
			throw new Error( "JSON.stringify: unknown data type" );
		}
	}

	if (typeof JSON.stringify !== 'function') {
		JSON.stringify = function (value, ignoredParam1, ignoredParam2) {
			ignoredParam1 = ignoredParam2;  // boredom
			gap = '';
			return str('', {'': value});
		};
	}
}());

Twinkle.config.writePrefs = function twinkleconfigWritePrefs(pageobj) {
	var form = pageobj.getCallbackParameters();
	var statelem = pageobj.getStatusElement();

	// this is the object which gets serialized into JSON
	var newConfig = {
		twinkle: {},
		friendly: {}
	};

	// keeping track of all preferences that we encounter
	// any others that are set in the user's current config are kept
	// this way, preferences that this script doesn't know about are not lost
	// (it does mean obsolete prefs will never go away, but... ah well...)
	var foundTwinklePrefs = [], foundFriendlyPrefs = [];

	// a comparison function is needed later on
	// it is just enough for our purposes (i.e. comparing strings, numbers, booleans,
	// arrays of strings, and arrays of { value, label })
	// and it is not very robust: e.g. compare([2], ["2"]) === true, and
	// compare({}, {}) === false, but it's good enough for our purposes here
	var compare = function(a, b) {
		if (Object.prototype.toString.apply(a) === "[object Array]") {
			if (a.length !== b.length) {
				return false;
			}
			var asort = a.sort(), bsort = b.sort();
			for (var i = 0; asort[i]; i++) {
				// comparison of the two properties of custom lists
				if ((typeof asort[i] === "object") && (asort[i].label !== bsort[i].label ||
					asort[i].value !== bsort[i].value)) {
					return false;
				} else if (asort[i].toString() !== bsort[i].toString()) { 
					return false;
				}
			}
			return true;
		} else {
			return a === b;
		}
	};

	$(Twinkle.config.sections).each(function(sectionkey, section) {
		if (section.adminOnly && !userIsInGroup("sysop")) {
			return;  // i.e. "continue" in this context
		}

		// reach each of the preferences from the form
		$(section.preferences).each(function(prefkey, pref) {
			var userValue;  // = undefined

			// only read form values for those prefs that have them
			if (!section.hidden && (!pref.adminOnly || userIsInGroup("sysop"))) {
				switch (pref.type) {

					case "boolean":  // read from the checkbox
						userValue = form[pref.name].checked;
						break;

					case "string":  // read from the input box or combo box
					case "enum":
						userValue = form[pref.name].value;
						break;

					case "integer":  // read from the input box
						userValue = parseInt(form[pref.name].value, 10);
						if (isNaN(userValue)) {
							Status.warn("保存", "您为 " + pref.name + "指定的值(" + pref.value + ")不合法,会继续保存操作,但此值将会跳过。");
							userValue = null;
						}
						break;

					case "set":  // read from the set of check boxes
						userValue = [];
						if (pref.setDisplayOrder) {
							// read only those keys specified in the display order
							$.each(pref.setDisplayOrder, function(itemkey, item) {
								if (form[pref.name + "_" + item].checked) {
									userValue.push(item);
								}
							});
						} else {
							// read all the keys in the list of values
							$.each(pref.setValues, function(itemkey, itemvalue) {
								if (form[pref.name + "_" + itemkey].checked) {
									userValue.push(itemkey);
								}
							});
						}
						break;

					case "customList":  // read from the jQuery data stored on the button object
						userValue = $(form[pref.name]).data("value");
						break;

					default:
						alert("twinkleconfig: 未知数据类型,属性 " + pref.name);
						break;
				}
			}

			// only save those preferences that are *different* from the default
			if (section.inFriendlyConfig) {
				if (typeof userValue !== "undefined" && !compare(userValue, Twinkle.defaultConfig.friendly[pref.name])) {
					newConfig.friendly[pref.name] = userValue;
				}
				foundFriendlyPrefs.push(pref.name);
			} else {
				if (typeof userValue !== "undefined" && !compare(userValue, Twinkle.defaultConfig.twinkle[pref.name])) {
					newConfig.twinkle[pref.name] = userValue;
				}
				foundTwinklePrefs.push(pref.name);
			}
		});
	});

	if (Twinkle.prefs) {
		$.each(Twinkle.prefs.twinkle, function(tkey, tvalue) {
			if (foundTwinklePrefs.indexOf(tkey) === -1) {
				newConfig.twinkle[tkey] = tvalue;
			}
		});
		$.each(Twinkle.prefs.friendly, function(fkey, fvalue) {
			if (foundFriendlyPrefs.indexOf(fkey) === -1) {
				newConfig.friendly[fkey] = fvalue;
			}
		});
	}

	var text =
		"// twinkleoptions.js:用户Twinkle参数设置文件\n" +
		"//\n" +
		"// 注:修改您的参数设置最简单的办法是使用\n" +
		"// Twinkle参数设置面板,在[[" + mw.config.get("wgPageName") + "]]。\n" +
		"//\n" +
		"// 这个文件是自动生成的,您所做的任何修改(除了\n" +
		"// 以一种合法的JavaScript的方式来修改这些属性值)会\n" +
		"// 在下一次您点击“保存”时被覆盖。\n" +
		"// 修改此文件时,请记得使用合法的JavaScript。\n" +
		"\n" +
		"window.Twinkle.prefs = ";
	text += JSON.stringify(newConfig, null, 2);
	text +=
		";\n" +
		"\n" +
		"// twinkleoptions.js到此为止\n";

	pageobj.setPageText(text);
	pageobj.setEditSummary("保存Twinkle参数设置:从[[" + mw.config.get("wgPageName") + "]]的自动编辑。 ([[WP:TW|TW]])");
	pageobj.setCreateOption("recreate");
	pageobj.save(Twinkle.config.saveSuccess);
};

Twinkle.config.saveSuccess = function twinkleconfigSaveSuccess(pageobj) {
	pageobj.getStatusElement().info("成功");

	var noticebox = document.createElement("div");
	noticebox.className = "successbox";
	noticebox.style.fontSize = "100%";
	noticebox.style.marginTop = "2em";
	noticebox.innerHTML = "<p><b>您的Twinkle参数设置已被保存。</b></p><p>要看到这些更改,您可能需要<a href=\"" + mw.util.getUrl("WP:BYPASS") + "\" title=\"WP:BYPASS\"><b>绕过浏览器缓存</b></a>。</p>";
	Status.root.appendChild(noticebox);
	var noticeclear = document.createElement("br");
	noticeclear.style.clear = "both";
	Status.root.appendChild(noticeclear);
};

/*
 * vim: set noet sts=0 sw=8:
 ****************************************
 *** twinkleunlink.js: Unlink module
 ****************************************
 * Mode of invocation:     Tab ("Unlink")
 * Active on:              Non-special pages
 * Config directives in:   TwinkleConfig
 */

Twinkle.unlink = function twinkleunlink() {
	if( mw.config.get('wgNamespaceNumber') < 0 ) {
		return;
	}
	$(twAddPortletLink("#", "链入", "tw-unlink", "取消链入", "")).click(function(){Twinkle.unlink.callback()}); //wrap call in function, callback expects a reason parameter.
};

Twinkle.unlink.getChecked2 = function twinkleunlinkGetChecked2( nodelist ) {
	if( !( nodelist instanceof NodeList ) && !( nodelist instanceof HTMLCollection ) ) {
		return nodelist.checked ? [ nodelist.values ] : [];
	}
	var result = [];
	for(var i  = 0; i < nodelist.length; ++i ) {
		if( nodelist[i].checked ) {
			result.push( nodelist[i].values );
		}
	}
	return result;
};

// the parameter is used when invoking unlink from admin speedy
Twinkle.unlink.callback = function(presetReason) {
	var Window = new SimpleWindow( 800, 400 );
	Window.setTitle( "取消链入" );
	Window.setScriptName( "Twinkle" );
	Window.addFooterLink( "Twinkle帮助", "WP:TW/DOC#unlink" );

	var form = new QuickForm( Twinkle.unlink.callback.evaluate );
	form.append( {
		type: 'textarea',
		name: 'reason',
		label: '理由:',
		value: (presetReason ? presetReason : '')
	} );

	var query;
	if(mw.config.get('wgNamespaceNumber') === Namespace.IMAGE) {
		query = {
			'action': 'query',
			'list': [ 'backlinks', 'imageusage' ],
			'bltitle': mw.config.get('wgPageName'),
			'iutitle': mw.config.get('wgPageName'),
			'bllimit': userIsInGroup( 'sysop' ) ? 5000 : 500, // 500 is max for normal users, 5000 for bots and sysops
			'iulimit': userIsInGroup( 'sysop' ) ? 5000 : 500, // 500 is max for normal users, 5000 for bots and sysops
			'blnamespace': Twinkle.getPref('unlinkNamespaces') // Main namespace and portal namespace only, keep on talk pages.
		};
	} else {
		query = {
			'action': 'query',
			'list': 'backlinks',
			'bltitle': mw.config.get('wgPageName'),
			'blfilterredir': 'nonredirects',
			'bllimit': userIsInGroup( 'sysop' ) ? 5000 : 500, // 500 is max for normal users, 5000 for bots and sysops
			'blnamespace': Twinkle.getPref('unlinkNamespaces') // Main namespace and portal namespace only, keep on talk pages.
		};
	}
	var wikipedia_api = new Wikipedia.api( '抓取链入', query, Twinkle.unlink.callbacks.display.backlinks );
	wikipedia_api.params = { form: form, Window: Window, image: mw.config.get('wgNamespaceNumber') === Namespace.IMAGE };
	wikipedia_api.post();

	var root = document.createElement( 'div' );
	root.style.padding = '15px';  // just so it doesn't look broken
	Status.init( root );
	wikipedia_api.statelem.status( "载入中…" );
	Window.setContent( root );
	Window.display();
};

Twinkle.unlink.callback.evaluate = function twinkleunlinkCallbackEvaluate(event) {
	mw.config.set('wgPageName', mw.config.get('wgPageName').replace(/_/g, ' '));  // for queen/king/whatever and country!

	Twinkle.unlink.backlinksdone = 0;
	Twinkle.unlink.imageusagedone = 0;

	function processunlink(pages, imageusage) {
		var statusIndicator = new Status((imageusage ? '取消文件使用' : '取消链入'), '0%');
		var total = pages.length;  // removing doubling of this number - no apparent reason for it

		Wikipedia.addCheckpoint();

		if( !pages.length ) {
			statusIndicator.info( '100%(完成)' );
			Wikipedia.removeCheckpoint();
			return;
		}

		// get an edit token
		var params = { reason: reason, imageusage: imageusage, globalstatus: statusIndicator, current: 0, total: total };
		for (var i = 0; i < pages.length; ++i)
		{
			var myparams = clone(params);
			var articlepage = new Wikipedia.page(pages[i], '在条目:“' + pages[i] + '”中');
			articlepage.setCallbackParameters(myparams);
			articlepage.load(imageusage ? Twinkle.unlink.callbacks.unlinkImageInstances : Twinkle.unlink.callbacks.unlinkBacklinks);
		}
	}

	var reason = event.target.reason.value;
	var backlinks, imageusage;
	if( event.target.backlinks ) {
		backlinks = Twinkle.unlink.getChecked2(event.target.backlinks);
	}
	if( event.target.imageusage ) {
		imageusage = Twinkle.unlink.getChecked2(event.target.imageusage);
	}

	SimpleWindow.setButtonsEnabled( false );
	Status.init( event.target );
	Wikipedia.addCheckpoint();
	if (backlinks) {
		processunlink(backlinks, false);
	}
	if (imageusage) {
		processunlink(imageusage, true);
	}
	Wikipedia.removeCheckpoint();
};

Twinkle.unlink.backlinksdone = 0;
Twinkle.unlink.imageusagedone = 0;

Twinkle.unlink.callbacks = {
	display: {
		backlinks: function twinkleunlinkCallbackDisplayBacklinks(apiobj) {
			var xmlDoc = apiobj.responseXML;
			var havecontent = false;
			var list, namespaces, i;

			if( apiobj.params.image ) {
				var imageusage = $(xmlDoc).find('query imageusage iu');
				list = [];
				for ( i = 0; i < imageusage.length; ++i ) {
					var usagetitle = imageusage[i].getAttribute('title');
					list.push( { label: usagetitle, value: usagetitle, checked: true } );
				}
				if (!list.length)
				{
					apiobj.params.form.append( { type: 'div', label: '未找到文件使用。' } );
				}
				else
				{
					apiobj.params.form.append( { type:'header', label: '文件使用' } );
					namespaces = [];
					$.each(Twinkle.getPref('unlinkNamespaces'), function(k, v) {
						namespaces.push(Wikipedia.namespacesFriendly[v]);
					});
					apiobj.params.form.append( {
						type: 'div',
						label: "已选择的名字空间:" + namespaces.join(', '),
						tooltip: "您可在Twinkle属性中更改这个,请参见[[WP:TWPREFS]]"
					});
					if ($(xmlDoc).find('query-continue').length) {
						apiobj.params.form.append( {
							type: 'div',
							label: "显示头 " + list.length.toString() + " 个文件使用。"
						});
					}
					apiobj.params.form.append( {
						type: 'checkbox',
						name: 'imageusage',
						list: list
					} );
					havecontent = true;
				}
			}

			var backlinks = $(xmlDoc).find('query backlinks bl');
			if( backlinks.length > 0 ) {
				list = [];
				for ( i = 0; i < backlinks.length; ++i ) {
					var title = backlinks[i].getAttribute('title');
					list.push( { label: title, value: title, checked: true } );
				}
				apiobj.params.form.append( { type:'header', label: 'Backlinks' } );
				namespaces = [];
				$.each(Twinkle.getPref('unlinkNamespaces'), function(k, v) {
					namespaces.push(Wikipedia.namespacesFriendly[v]);
				});
				apiobj.params.form.append( {
					type: 'div',
					label: "已选择的名字空间:" + namespaces.join(', '),
					tooltip: "您可在Twinkle属性中更改这个,请参见[[WP:TWPREFS]]"
				});
				if ($(xmlDoc).find('query-continue').length) {
					apiobj.params.form.append( {
						type: 'div',
						label: "显示头 " + list.length.toString() + " 个链入。"
					});
				}
				apiobj.params.form.append( {
					type: 'checkbox',
					name: 'backlinks',
					list: list
				});
				havecontent = true;
			}
			else
			{
				apiobj.params.form.append( { type: 'div', label: '未找到链入。' } );
			}

			if (havecontent) {
				apiobj.params.form.append( { type:'submit' } );
			}

			var result = apiobj.params.form.render();
			apiobj.params.Window.setContent( result );
		}
	},
	unlinkBacklinks: function twinkleunlinkCallbackUnlinkBacklinks(pageobj) {
		var text, oldtext;
		text = oldtext = pageobj.getPageText();
		var params = pageobj.getCallbackParameters();

		var wikiPage = new Mediawiki.Page(text);
		wikiPage.removeLink(mw.config.get('wgPageName'));
		text = wikiPage.getText();
		if (text === oldtext) {
			// Nothing to do, return
			Twinkle.unlink.callbacks.success(pageobj);
			Wikipedia.actionCompleted();
			return;
		}

		pageobj.setPageText(text);
		pageobj.setEditSummary("取消到页面“" + mw.config.get('wgPageName') + "”的链接:" + params.reason + "。" + Twinkle.getPref('summaryAd'));
		pageobj.setCreateOption('nocreate');
		pageobj.save(Twinkle.unlink.callbacks.success);
	},
	unlinkImageInstances: function twinkleunlinkCallbackUnlinkImageInstances(pageobj) {
		var text, oldtext;
		text = oldtext = pageobj.getPageText();
		var params = pageobj.getCallbackParameters();

		var wikiPage = new Mediawiki.Page(text);
		wikiPage.commentOutImage(mw.config.get('wgTitle'), '注释出');
		text = wikiPage.getText();
		if (text === oldtext) {
			// Nothing to do, return
			Twinkle.unlink.callbacks.success(pageobj);
			Wikipedia.actionCompleted();
			return;
		}

		pageobj.setPageText(text);
		pageobj.setEditSummary("注释出对文件“" + mw.config.get('wgPageName') + "的使用:" + params.reason + "。" + Twinkle.getPref('summaryAd'));
		pageobj.setCreateOption('nocreate');
		pageobj.save(Twinkle.unlink.callbacks.success);
	},
	success: function twinkleunlinkCallbackSuccess(pageobj) {
		var statelem = pageobj.getStatusElement();
		statelem.info('完成');

		var params = pageobj.getCallbackParameters();
		var total = params.total;
		var now = parseInt( 100 * (params.imageusage ? ++(Twinkle.unlink.imageusagedone) : ++(Twinkle.unlink.backlinksdone))/total, 10 ) + '%';
		params.globalstatus.update( now );
		if((params.imageusage ? Twinkle.unlink.imageusagedone : Twinkle.unlink.backlinksdone) >= total) {
			params.globalstatus.info( now + '(完成)' );
			Wikipedia.removeCheckpoint();
		}
	}
};

/*
 * vim: set noet sts=0 sw=8:
 ****************************************
 *** friendlytag.js: Tag module
 ****************************************
 * Mode of invocation:     Tab ("Tag")
 * Active on:              Existing articles; file pages with a corresponding file
 *                         which is local (not on Commons); existing user subpages
 *                         and existing subpages of Wikipedia:Articles for creation;
 *                         all redirects
 * Config directives in:   FriendlyConfig
 */

Twinkle.tag = function friendlytag() {
	// redirect tagging
	if( Wikipedia.isPageRedirect() ) {
		Twinkle.tag.mode = '重定向';
		$(twAddPortletLink("#", "标记", "friendly-tag", "标记重定向", "")).click(Twinkle.tag.callback);
	}
	// file tagging
	/*
	else if( mw.config.get('wgNamespaceNumber') === 6 && !document.getElementById("mw-sharedupload") && document.getElementById("mw-imagepage-section-filehistory") ) {
		Twinkle.tag.mode = 'file';
		$(twAddPortletLink("#", "标记", "friendly-tag", "标记文件", "")).click(Twinkle.tag.callback);
	}
	*/
	// article tagging
	else if( mw.config.get('wgNamespaceNumber') === 0 && mw.config.get('wgCurRevisionId') ) {
		Twinkle.tag.mode = '条目';
		$(twAddPortletLink("#", "标记", "friendly-tag", "标记条目", "")).click(Twinkle.tag.callback);
	}
	// tagging of draft articles
	/*else if( ((mw.config.get('wgNamespaceNumber') === 2 && mw.config.get('wgPageName').indexOf("/") !== -1) || /^Wikipedia\:Articles[ _]for[ _]creation\//.exec(mw.config.get('wgPageName')) ) && mw.config.get('wgCurRevisionId') ) {
		Twinkle.tag.mode = 'draft';
		$(twAddPortletLink("#", "Tag", "friendly-tag", "Add review tags to draft article", "")).click(Twinkle.tag.callback);
	}*/
};

Twinkle.tag.callback = function friendlytagCallback( uid ) {
	var Window = new SimpleWindow( 630, 400 );
	Window.setScriptName( "Twinkle" );
	// anyone got a good policy/guideline/info page/instructional page link??
	Window.addFooterLink( "Twinkle帮助", "WP:TW/DOC#tag" );

	var form = new QuickForm( Twinkle.tag.callback.evaluate );

	switch( Twinkle.tag.mode ) {
		case '条目':
			Window.setTitle( "条目维护标记" );

			form.append( {
					type: 'checkbox',
					list: [
						{
							label: '如可能,合并到{{multiple issues}}',
							value: 'group',
							name: 'group',
							tooltip: '如果添加{{multiple issues}}支持的三个以上的模板,所有支持的模板都会被合并至一个{{multiple issues}}模板中。',
							checked: Twinkle.getFriendlyPref('groupByDefault')
						}
					]
				}
			);

			form.append({
				type: 'select',
				name: 'sortorder',
				label: '察看列表:',
				tooltip: '您可以在Twinkle参数设置中更改此项。',
				event: Twinkle.tag.updateSortOrder,
				list: [
					{ type: 'option', value: 'cat', label: '按类别', selected: Twinkle.getFriendlyPref('tagArticleSortOrder') === 'cat' },
					{ type: 'option', value: 'alpha', label: '按字母', selected: Twinkle.getFriendlyPref('tagArticleSortOrder') === 'alpha' }
				]
			});

			form.append( { type: 'div', id: 'tagWorkArea' } );

			if( Twinkle.getFriendlyPref('customTagList').length ) {
				form.append( { type:'header', label:'自定义模板' } );
				form.append( { type: 'checkbox', name: 'articleTags', list: Twinkle.getFriendlyPref('customTagList') } );
			}
			break;

		/*case 'file':
			Window.setTitle( "文件维护标记" );

			// TODO: perhaps add custom tags TO list of checkboxes

			form.append({ type: 'header', label: '版权和来源问题模板' });
			form.append({ type: 'checkbox', name: 'imageTags', list: Twinkle.tag.file.licenseList } );

			form.append({ type: 'header', label: '清理模板' } );
			form.append({ type: 'checkbox', name: 'imageTags', list: Twinkle.tag.file.cleanupList } );

			form.append({ type: 'header', label: '图片质量模板' } );
			form.append({ type: 'checkbox', name: 'imageTags', list: Twinkle.tag.file.qualityList } );

			form.append({ type: 'header', label: '维基媒体相关模板' });
			form.append({ type: 'checkbox', name: 'imageTags', list: Twinkle.tag.file.commonsList } );

			form.append({ type: 'header', label: '替换模板' });
			form.append({ type: 'checkbox', name: 'imageTags', list: Twinkle.tag.file.replacementList } );
			break;*/

		case '重定向':
			Window.setTitle( "重定向标记" );

			form.append({ type: 'header', label:'拼写、错误拼写、时态和大小写模板' });
			form.append({ type: 'checkbox', name: 'redirectTags', list: Twinkle.tag.spellingList });

			form.append({ type: 'header', label:'其他名称模板' });
			form.append({ type: 'checkbox', name: 'redirectTags', list: Twinkle.tag.alternativeList });

			form.append({ type: 'header', label:'杂项和管理用重定向模板' });
			form.append({ type: 'checkbox', name: 'redirectTags', list: Twinkle.tag.administrativeList });
			break;

		/*case 'draft':
			Window.setTitle( "Article draft tagging" );

			form.append({ type: 'header', label:'Draft article tags' });
			form.append({ type: 'checkbox', name: 'draftTags', list: Twinkle.tag.draftList });
			break;*/

		default:
			alert("Twinkle.tag:未知模式 " + Twinkle.tag.mode);
			break;
	}

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

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

	if (Twinkle.tag.mode === "条目") {
		// fake a change event on the sort dropdown, to initialize the tag list
		var evt = document.createEvent("Event");
		evt.initEvent("change", true, true);
		result.sortorder.dispatchEvent(evt);
	}
};

Twinkle.tag.checkedTags = [];

Twinkle.tag.updateSortOrder = function(e) {
	var sortorder = e.target.value;
	var $workarea = $(e.target.form).find("div#tagWorkArea");

	Twinkle.tag.checkedTags = e.target.form.getChecked("articleTags");
	if (!Twinkle.tag.checkedTags) {
		Twinkle.tag.checkedTags = [];
	}

	// function to generate a checkbox, with appropriate subgroup if needed
	var makeCheckbox = function(tag, description) {
		var checkbox = { value: tag, label: "{{" + tag + "}}: " + description };
		if (Twinkle.tag.checkedTags.indexOf(tag) !== -1) {
			checkbox.checked = true;
		}
		return checkbox;
	};

	// categorical sort order
	if (sortorder === "cat") {
		var div = new QuickForm.element({
			type: "div",
			id: "tagWorkArea"
		});

		// function to iterate through the tags and create a checkbox for each one
		var doCategoryCheckboxes = function(subdiv, array) {
			var checkboxes = [];
			$.each(array, function(k, tag) {
				var description = Twinkle.tag.article.tags[tag];
				checkboxes.push(makeCheckbox(tag, description));
			});
			subdiv.append({
				type: "checkbox",
				name: "articleTags",
				list: checkboxes
			});
		};

		var i = 0;
		// go through each category and sub-category and append lists of checkboxes
		$.each(Twinkle.tag.article.tagCategories, function(title, content) {
			div.append({ type: "header", id: "tagHeader" + i, label: title });
			var subdiv = div.append({ type: "div", id: "tagSubdiv" + i++ });
			if ($.isArray(content)) {
				doCategoryCheckboxes(subdiv, content);
			} else {
				$.each(content, function(subtitle, subcontent) {
					subdiv.append({ type: "div", label: [ htmlNode("b", subtitle) ] });
					doCategoryCheckboxes(subdiv, subcontent);
				});
			}
		});

		var rendered = div.render();
		$workarea.replaceWith(rendered);
		var $rendered = $(rendered);
		$rendered.find("h5").css({ 'font-size': '110%', 'margin-top': '1em' });
		$rendered.find("div").filter(":has(span.quickformDescription)").css({ 'margin-top': '0.4em' });
	}
	// alphabetical sort order
	else {
		var checkboxes = [];
		$.each(Twinkle.tag.article.tags, function(tag, description) {
			checkboxes.push(makeCheckbox(tag, description));
		});
		var tags = new QuickForm.element({
			type: "checkbox",
			name: "articleTags",
			list: checkboxes
		});
		$workarea.empty().append(tags.render());
	}
};


// Tags for ARTICLES start here

Twinkle.tag.article = {};

// A list of all article tags, in alphabetical order
// To ensure tags appear in the default "categorized" view, add them to the tagCategories hash below.

Twinkle.tag.article.tags = {
	"advert": "此条目或章节类似广告",
	"BLPsources": "当前传记条目需要补充更多来源",
	"BLP unsourced": "此生者传记条目没有列出任何参考或来源",
	"catimprove": "此条目需要更多页面分类",
	"citation style": "这篇条目的参考文献需要进行清理",
	"cleanup": "此条目可能需要进行清理,以符合维基百科的质量标准",
	"COI": "这个条目的一位主要贡献者似乎与本条目的主题存在利益冲突",
	"copyedit": "此条目或章节需要编修,以确保语法、用语、语气、风格、表达恰当",
	"disputed": "此条目的准确性有争议",
	"expand": "此条目或章节需要扩充",
	"expert-subject": "本条目或段落需要专家的关注",
	"external links": "维基百科不是连结集",
	"fansite": "此条目或章节类似爱好者站点",
	"globalize": "此条目仅具有一部分地区的观点或资讯,无法完整表达普世通用,并包含广泛区域的观点",
	"hoax": "此条目的真实性被质疑",
	"in-universe": "本条目或章节使用小说故事内的观点描述一个虚构事物",
	"inuse": "本条目正在进行重大修改",
	"lead section": "此文的导言部分也许不足以概括其内容",
	"merge": "建议此页面与页面合并",
	"merge from": "建议将页面合并到本页面",
	"merge to": "建议将此页面合并至页面",
	"nofootnotes": "此条目列出了一些参考来源或外部链接,但由于缺少内文脚注,条目中部分信息的来源仍然不明确",
	"non-free": "此条目或章节可能过多或不当地使用了受版权保护的文字、图像或/及多媒体文件",
	"notability": "此条目可能不符合通用关注度指引",
	"notmandarin": "此条目包含过多不是现代标准汉语的内容",
	"original research": "此条目或章节可能包含原创研究或未查证内容",
	"orphan": "这个条目没有或只有很少链入页面",
	"overlinked": "此条目可能含有太多的内部链接",
	"POV": "此条目的中立性有争议。内容、语调可能带有明显的个人观点或地方色彩",
	"prose": "此条目使用了列表式记述,可能需要改写为连贯的叙述性文字",
	"refimprove": "此条目需要补充更多来源",
	"roughtranslation": "此条目翻译粗劣",
	"substub": "请您改写这篇过于短小的文章",
	"tone": "此条目或章节的语调或风格可能不适合百科全书的写作方式",
	"toolong": "此条目或章节可能过于冗长",
	"uncategorized": "此条目缺少页面分类",
	"unreferenced": "此条目没有列出任何参考或来源",
	"update": "当前条目或章节需要更新",
	"weasel": "此条目或章节可能因为语意模棱两可而损及其中立性",
	"wikify": "此条目或章节需要被修正为维基格式以符合质量标准"
};

// A list of tags in order of category
// Tags should be in alphabetical order within the categories
// Add new categories with discretion - the list is long enough as is!

Twinkle.tag.article.tagCategories = {
	"清理和维护模板": {
		"常规清理": [
			"cleanup",
			"copyedit",
			"wikify"
		],
		"可能多余的内容": [
			"external links",
			"non-free"
		],
		"结构和导言": [
			"lead section",
			"toolong"
		],
		"小说相关清理": [
			"in-universe"
		]
	},
	"常规条目问题": {
		"重要性和知名度": [
			"notability"
		],
		"写作风格": [
			"advert",
			"fansite",
			"prose",
			"tone"
		],
		"内容": [
			"expand",
			"substub"
		],
		"信息和细节": [
			"expert-subject",
		],
		"时间性": [
			"update"
		],
		"中立、偏见和事实准确性": [
			"COI",
			"disputed",
			"hoax",
			"globalize",
			"POV",
			"weasel"
		],
		"可供查证和来源": [
			"BLPsources",
			"BLP unsourced",
			"nofootnotes",
			"original research",
			"refimprove",
			"unreferenced"
		]
	},
	"具体内容问题": {
		"语言": [
			"notmandarin",
			"roughtranslation"
		],
		"链接": [
			"orphan",
			"overlinked",
			"wikify"  // this tag is listed twice because it used to focus mainly on links, but now it's a more general cleanup tag
		],
		"参考技术": [
			"citation style"
		],
		"分类": [
			"catimprove",
			"uncategorized"
		]
	},
	"合并": [
		"merge",
		"merge from",
		"merge to"
	],
	"信息": [
		"inuse"
	]
};

// Tags for REDIRECTS start here

Twinkle.tag.spellingList = [
	{
		label: "{{簡繁重定向}}: 引导简体至繁体,或繁体至简体",
		value: '簡繁重定向'
	},
	{
		label: "{{模板重定向}}: 指向模板",
		value: '模板重定向'
	},
	{
		label: "{{别名重定向}}: 标题的其他名称、笔名、绰号、同义字等",
		value: '别名重定向'
	},
	{
		label: "{{縮寫重定向}}: 标题缩写",
		value: '縮寫重定向'
	},
	{
		label: "{{拼寫重定向}}: 标题的其他不同拼写",
		value: '拼寫重定向'
	},
	{
		label: "{{錯字重定向}}: 标题的常见错误拼写或误植",
		value: '錯字重定向'
	},
];

Twinkle.tag.alternativeList = [
	{
		label: "{{全名重定向}}: 标题的完整或更完整名称",
		value: '全名重定向'
	},
	{
		label: "{{短名重定向}}: 完整标题名称或人物全名的部分、不完整的名称或简称",
		value: '短名重定向'
	},
	{
		label: "{{姓氏重定向}}: 人物姓氏",
		value: '姓氏重定向'
	},
	{
		label: "{{人名重定向}}: 人物人名",
		value: '人名重定向'
	},
	{
		label: "{{非中文重定向}}: 非中文标题",
		value: '非中文重定向'
	},
	{
		label: "{{日文重定向}}: 日语名称",
		value: '日文重定向'
	}
];

Twinkle.tag.administrativeList = [
	{
		label: "{{角色重定向}}: 电视剧、电影、书籍等作品的角色",
		value: '角色重定向'
	},
	{
		label: "{{章節重定向}}: 导向至较高密度(散文般密集)组织的页面",
		value: '章節重定向'
	},
	{
		label: "{{列表重定向}}: 导向至低密度的列表",
		value: '列表重定向'
	},
	{
		label: "{{可能性重定向}}: 导向至当前提供内容更为详尽的目标页面、或该页面的章节段落",
		value: '可能性重定向'
	},
	{
		label: "{{關聯字重定向}}: 标题名称关联字",
		value: '關聯字重定向'
	},
	{
		label: "{{捷徑重定向}}: 维基百科捷径",
		value: '捷徑重定向'
	},
	{
		label: "{{重定向模板用重定向}}: 重定向模板用",
		value: '重定向模板用重定向'
	},
	{
		label: "{{EXIF重定向}}: JPEG图像包含EXIF信息",
		value: 'EXIF重定向'
	}
];

// maintenance tags for FILES start here
/* TODO(jimmyxu)

Twinkle.tag.file = {};

Twinkle.tag.file.licenseList = [
	{ label: '{{Bsr}}: source info consists of bare image URL/generic base URL only', value: 'Bsr' },
	{ label: '{{Non-free reduce}}: non-low-resolution fair use image (or too-long audio clip, etc)', value: 'Non-free reduce' },
	{ label: '{{Non-free reduced}}: fair use media which has been reduced (old versions need to be deleted)', value: 'Non-free reduced' }
];

Twinkle.tag.file.cleanupList = [
	{ label: '{{Artifacts}}: PNG contains residual compression artifacts', value: 'Artifacts' },
	{ label: '{{Bad font}}: SVG uses fonts not available on the thumbnail server', value: 'Bad font' },
	{ label: '{{Bad format}}: PDF/DOC/... file should be converted to a more useful format', value: 'Bad format' },
	{ label: '{{Bad GIF}}: GIF that should be PNG, JPEG, or SVG', value: 'Bad GIF' },
	{ label: '{{Bad JPEG}}: JPEG that should be PNG or SVG', value: 'Bad JPEG' },
	{ label: '{{Bad trace}}: auto-traced SVG requiring cleanup', value: 'Bad trace' },
	{ label: '{{Cleanup image}}: general cleanup', value: 'Cleanup image' },
	{ label: '{{Cleanup SVG}}: SVG needing code and/or appearance cleanup', value: 'Cleanup SVG' },
	{ label: '{{ClearType}}: image (not screenshot) with ClearType anti-aliasing', value: 'ClearType' },
	{ label: '{{Imagewatermark}}: image contains visible or invisible watermarking', value: 'Imagewatermark' },
	{ label: '{{NoCoins}}: image using coins to indicate scale', value: 'NoCoins' },
	{ label: '{{Overcompressed JPEG}}: JPEG with high levels of artifacts', value: 'Overcompressed JPEG' },
	{ label: '{{Opaque}}: opaque background should be transparent', value: 'Opaque' },
	{ label: '{{Remove border}}: unneeded border, white space, etc.', value: 'Remove border' },
	{ label: '{{Rename media}}: file should be renamed according to the criteria at [[WP:FMV]]', value: 'Rename media' },
	{ label: '{{Should be PNG}}: GIF or JPEG should be lossless', value: 'Should be PNG' },
	{
		label: '{{Should be SVG}}: PNG, GIF or JPEG should be vector graphics', value: 'Should be SVG',
		subgroup: {
			name: 'svgCategory',
			type: 'select',
			list: [
				{ label: '{{Should be SVG|other}}', value: 'other' },
				{ label: '{{Should be SVG|alphabet}}: character images, font examples, etc.', value: 'alphabet' },
				{ label: '{{Should be SVG|chemical}}: chemical diagrams, etc.', value: 'chemical' },
				{ label: '{{Should be SVG|circuit}}: electronic circuit diagrams, etc.', value: 'circuit' },
				{ label: '{{Should be SVG|coat of arms}}: coats of arms', value: 'coat of arms' },
				{ label: '{{Should be SVG|diagram}}: diagrams that do not fit any other subcategory', value: 'diagram' },
				{ label: '{{Should be SVG|emblem}}: emblems, free/libre logos, insignias, etc.', value: 'emblem' },
				{ label: '{{Should be SVG|fair use}}: fair-use images, fair-use logos', value: 'fair use' },
				{ label: '{{Should be SVG|flag}}: flags', value: 'flag' },
				{ label: '{{Should be SVG|graph}}: visual plots of data', value: 'graph' },
				{ label: '{{Should be SVG|logo}}: logos', value: 'logo' },
				{ label: '{{Should be SVG|map}}: maps', value: 'map' },
				{ label: '{{Should be SVG|music}}: musical scales, notes, etc.', value: 'music' },
				{ label: '{{Should be SVG|physical}}: "realistic" images of physical objects, people, etc.', value: 'physical' },
				{ label: '{{Should be SVG|symbol}}: miscellaneous symbols, icons, etc.', value: 'symbol' }
			]
		}
	},
	{ label: '{{Should be text}}: image should be represented as text, tables, or math markup', value: 'Should be text' }
];

Twinkle.tag.file.qualityList = [
	{ label: '{{Image-blownout}}', value: 'Image-blownout' },
	{ label: '{{Image-out-of-focus}}', value: 'Image-out-of-focus' },
	{ label: '{{Image-Poor-Quality}}', value: 'Image-Poor-Quality' },
	{ label: '{{Image-underexposure}}', value: 'Image-underexposure' },
	{ label: '{{Low quality chem}}: disputed chemical structures', value: 'Low quality chem' }
];

Twinkle.tag.file.commonsList = [
	{ label: '{{Copy to Commons}}: free media that should be copied to Commons', value: 'Copy to Commons' },
	{ label: '{{Do not move to Commons}} (PD issue): file is PD in the US but not in country of origin', value: 'Do not move to Commons' },
	{ label: '{{Do not move to Commons}} (other reason)', value: 'Do not move to Commons_reason' },
	{ label: '{{Keep local}}: request to keep local copy of a Commons file', value: 'Keep local' },
	{ label: '{{Now Commons}}: file has been copied to Commons', value: 'subst:ncd' },
	{ label: '{{Shadows Commons}}: a different file is present on Commons under the same filename', value: 'Shadows Commons' }
];

Twinkle.tag.file.replacementList = [
	{ label: '{{Obsolete}}: improved version available', value: 'Obsolete' },
	{ label: '{{Redundant}}: exact duplicate of another file, but not yet orphaned', value: 'Redundant' },
	{ label: '{{PNG version available}}', value: 'PNG version available' },
	{ label: '{{SVG version available}}', value: 'SVG version available' }
];

// Tags for DRAFT ARTICLES start here

Twinkle.tag.draftList = [
	{ label: '{{New unreviewed article}}: mark article for later review', value: 'new unreviewed article' }
];
*/

// Contains those article tags that can be grouped into {{multiple issues}}.
// This list includes synonyms.
Twinkle.tag.groupHash = [
	'advert',
	'attention',
	'autobiography',
	'biased',
	'blpdispute',
	'citations missing',
	'citation style',
	'citecheck',
	'cleanup',
	'COI',
	'coi',
	'colloquial',
	'confusing',
	'context',
	'contradict',
	'copyedit',
	'criticisms',
	'crystal',
	'deadend',
	'disputed',
	'do-attempt',
	'essay',
	'examplefarm',
	'expand',
	'expert',
	'external links',
	'fancruft',
	'fansite',
	'fiction',
	'gameguide',
	'globalize',
	'histinfo',
	'hoax',
	'howto',
	'inappropriate person',
	'in-universe',
	'importance',
	'incomplete',
	'intro length',
	'intromissing',
	'introrewrite',
	'jargon',
	'laundrylists',
	'likeresume',
	'long',
	'newsrelease',
	'nofootnotes',
	'notability',
	'onesource',
	'OR',
	'orphan',
	'do-attempt',
	'out of date',
	'peacock',
	'plot',
	'POV',
	'primarysources',
	'prose',
	'proseline',
	'quotefarm',
	'recent',
	'refimprove',
	'refimproveBLP',
	'restructure',
	'review',
	'rewrite',
	'roughtranslation',
	'sections',
	'self-published',
	'story',
	'synthesis',
	'tone',
	'tooshort',
	'travelguide',
	'trivia',
	'unbalanced',
	'unencyclopedic',
	'unreferenced',
	'unreferencedBLP',
	'update',
	'weasel',
	'wikify'
];

Twinkle.tag.callbacks = {
	main: function( pageobj ) {
		var params = pageobj.getCallbackParameters();
		var tagRe, tagText = '', summaryText = '添加';
		var tags = [], groupableTags = [];

		// Remove tags that become superfluous with this action
		var pageText = pageobj.getPageText().replace(/\{\{\s*(New unreviewed article|Userspace draft)\s*(\|(?:\{\{[^{}]*\}\}|[^{}])*)?\}\}\s*/ig, "");

		var i;
		if( Twinkle.tag.mode !== '重定向' ) {
			// Check for preexisting tags and separate tags into groupable and non-groupable arrays
			for( i = 0; i < params.tags.length; i++ ) {
				tagRe = new RegExp( '(\\{\\{' + params.tags[i] + '(\\||\\}\\}))', 'im' );
				if( !tagRe.exec( pageText ) ) {
					if( params.tags[i] == 'notability' ) {
						wikipedia_page = new Wikipedia.page("Wikipedia:关注度/提报", "添加关注度记录项");
						wikipedia_page.setFollowRedirect(true);
						wikipedia_page.setCallbackParameters(params);
						wikipedia_page.load(Twinkle.tag.callbacks.notabilityList);
					}
					if( Twinkle.tag.groupHash.indexOf(params.tags[i]) !== -1 ) {
						groupableTags = groupableTags.concat( params.tags[i] );
					} else {
						tags = tags.concat( params.tags[i] );
					}
				} else {
					Status.info( '信息', '在条目上找到{{' + params.tags[i] +
						'}}…已排除' );
				}
			}

			if( params.group && groupableTags.length >= 3 ) {
				Status.info( '信息', '合并支持的模板到{{multiple issues}}' );

				groupableTags.sort();
				tagText += '{{multiple issues';
				summaryText += ' {{[[Template:multiple issues|multiple issues]]}}带有参数';
				for( i = 0; i < groupableTags.length; i++ ) {
					tagText += '|' + groupableTags[i] +
						'={{subst:#time:c}}';

					if( i === (groupableTags.length - 1) ) {
						summaryText += '和';
					} else if ( i < (groupableTags.length - 1) && i > 0 ) {
						summaryText += '、';
					}
					summaryText += groupableTags[i];
				}
				tagText += '}}\n';
			} else {
				tags = tags.concat( groupableTags );
			}
		} else {
			// Check for pre-existing tags
			for( i = 0; i < params.tags.length; i++ ) {
				tagRe = new RegExp( '(\\{\\{' + params.tags[i] + '(\\||\\}\\}))', 'im' );
				if( !tagRe.exec( pageText ) ) {
					tags = tags.concat( params.tags[i] );
				} else {
					Status.info( '信息', '在重定向上找到{{' + params.tags[i] +
						'}}…已排除' );
				}
			}
		}

		tags.sort();
		for( i = 0; i < tags.length; i++ ) {
			var currentTag = "";
			if( tags[i] === 'uncategorized' || tags[i] === 'cat improve' ) {
				pageText += '\n\n{{' + tags[i] +
					'|time={{subst:#time:c}}}}';
			} else {
				currentTag += ( Twinkle.tag.mode === '重定向' ? '\n' : '' ) + '{{' + tags[i];

				// prompt for other parameters, based on the tag
				switch( tags[i] ) {
					case 'merge':
					case 'merge to':
					case 'merge from':
						var param = prompt('请输入合并相关的条目名。\n' +
							"要指定多个条目,请用管道符(|)分开。\n" +
							"这个信息是必须的。完成时请点击确定,或点击取消以略过此标记。", "");
						if (param === null) {
							continue;
						} else if (param !== "") {
							currentTag += '|' + param;
						}
						break;
					default:
						break;
				}

				currentTag += Twinkle.tag.mode === '重定向' ? '}}' : '|time={{subst:#time:c}}}}\n';
				tagText += currentTag;
			}

			if ( i > 0 || groupableTags.length > 3 ) {
				if( i === (tags.length - 1) ) {
					summaryText += '和';
				} else if ( i < (tags.length - 1) ) {
					summaryText += '、';
				}
			}

			summaryText += ' {{[[Template:';
			summaryText += tags[i] + '|' + tags[i];
			summaryText += ']]}}';
		}

		if( Twinkle.tag.mode === '重定向' ) {
			pageText += tagText;
		} else {
			// smartly insert the new tags after any hatnotes. Regex is a bit more
			// complicated than it'd need to be, to allow templates as parameters,
			// and to handle whitespace properly.
			pageText = pageText.replace(/^\s*(?:((?:\s*\{\{\s*(?:about|correct title|dablink|distinguish|for|other\s?(?:hurricaneuses|people|persons|places|uses(?:of)?)|redirect(?:-acronym)?|see\s?(?:also|wiktionary)|selfref|the)\d*\s*(\|(?:\{\{[^{}]*\}\}|[^{}])*)?\}\})+(?:\s*\n)?)\s*)?/i,
				"$1" + tagText);
		}
		summaryText += '标记' +
			'到' + Twinkle.tag.mode + Twinkle.getPref('summaryAd');

		pageobj.setPageText(pageText);
		pageobj.setEditSummary(summaryText);
		pageobj.setWatchlist(Twinkle.getFriendlyPref('watchTaggedPages'));
		pageobj.setMinorEdit(Twinkle.getFriendlyPref('markTaggedPagesAsMinor'));
		pageobj.setCreateOption('nocreate');
		pageobj.save();

		if( Twinkle.getFriendlyPref('markTaggedPagesAsPatrolled') ) {
			pageobj.patrol();
		}
	},

	notabilityList: function(pageobj) {
		var text = pageobj.getPageText();
		var params = pageobj.getCallbackParameters();

		pageobj.setAppendText("\n{{subst:Wikipedia:关注度/提报/item|title=" + mw.config.get('wgPageName') + "}}");
		pageobj.setEditSummary("添加[[" + mw.config.get('wgPageName') + "]]。" + Twinkle.getPref('summaryAd'));
		pageobj.setCreateOption('recreate');
		pageobj.append();
	}

/*
	file: function friendlytagCallbacksFile(pageobj) {
		var text = pageobj.getPageText();
		var params = pageobj.getCallbackParameters();
		var summary = "Adding ";

		// Add in maintenance tags
		if (params.tags.length) {

			var tagtext = "";
			$.each(params.tags, function(k, tag) {
				tagtext += "{{" + (tag === "Do not move to Commons_reason" ? "Do not move to Commons" : tag);

				var input;
				switch (tag) {
					case "subst:ncd":
						/* falls through * /
					case "Keep local":
						input = prompt( "{{" + (tag === "subst:ncd" ? "Now Commons" : tag) +
							"}} - Enter the name of the image on Commons (if different from local name), excluding the File: prefix:", "" );
						if (input === null) {
							return true;  // continue
						} else if (input !== "") {
							tagtext += '|1=' + input;
						}
						break;
					case "Rename media":
						input = prompt( "{{Rename media}} - Enter the new name for the image (optional):", "" );
						if (input === null) {
							return true;  // continue
						} else if (input !== "") {
							tagtext += "|1=" + input;
						}
						input = prompt( "{{Rename media}} - Enter the reason for the rename (optional):", "" );
						if (input === null) {
							return true;  // continue
						} else if (input !== "") {
							tagtext += "|2=" + input;
						}
						break;
					case "Cleanup image":
						/* falls through * /
					case "Cleanup SVG":
						input = prompt( "{{" + tag + "}} - Enter the reason for cleanup (required). To skip the tag, click Cancel:", "" );
						if (input === null) {
							return true;  // continue
						} else if (input !== "") {
							tagtext += "|1=" + input;
						}
						break;
					case "Image-Poor-Quality":
						input = prompt( "{{Image-Poor-Quality}} - Enter the reason why this image is so bad (required). To skip the tag, click Cancel:", "" );
						if (input === null) {
							return true;  // continue
						} else if (input !== "") {
							tagtext += "|1=" + input;
						}
						break;
					case "Low quality chem":
						input = prompt( "{{Low quality chem}} - Enter the reason why the diagram is disputed (required). To skip the tag, click Cancel:", "" );
						if (input === null) {
							return true;  // continue
						} else if (input !== "") {
							tagtext += "|1=" + input;
						}
						break;
					case "PNG version available":
						/* falls through * /
					case "SVG version available":
						/* falls through * /
					case "Obsolete":
						/* falls through * /
					case "Redundant":
						input = prompt( "{{" + tag + "}} - Enter the name of the file which replaces this one (required). To skip the tag, click Cancel:", "" );
						if (input === null) {
							return true;  // continue
						} else if (input !== "") {
							tagtext += "|1=" + input;
						}
						break;
					case "Do not move to Commons_reason":
						input = prompt( "{{Do not move to Commons}} - Enter the reason why this image should not be moved to Commons (required). To skip the tag, click Cancel:", "" );
						if (input === null) {
							return true;  // continue
						} else if (input !== "") {
							tagtext += "|reason=" + input;
						}
						break;
					case "Non-free reduced":
						tagtext += "|date={{subst:date}}";
						break;
					default:
						break;  // don't care
				}

				if (tag === "Should be SVG") {
					tagtext += "|" + params.svgSubcategory;
				}

				tagtext += "}}\n";

				summary += "{{" + tag + "}}, ";

				return true;  // continue
			});

			text = tagtext + text;
		}

		pageobj.setPageText(text);
		pageobj.setEditSummary(summary.substring(0, summary.length - 2) + Twinkle.getPref('summaryAd'));
		pageobj.setWatchlist(Twinkle.getFriendlyPref('watchTaggedPages'));
		pageobj.setMinorEdit(Twinkle.getFriendlyPref('markTaggedPagesAsMinor'));
		pageobj.setCreateOption('nocreate');
		pageobj.save();

		if( Twinkle.getFriendlyPref('markTaggedPagesAsPatrolled') ) {
			pageobj.patrol();
		}
	}
*/
};

Twinkle.tag.callback.evaluate = function friendlytagCallbackEvaluate(e) {
	var form = e.target;
	var params = {};

	switch (Twinkle.tag.mode) {
		case '条目':
			params.tags = form.getChecked( 'articleTags' );
			params.group = form.group.checked;
			break;
		/*case 'file':
			params.svgSubcategory = form["imageTags.svgCategory"] ? form["imageTags.svgCategory"].value : null;
			params.tags = form.getChecked( 'imageTags' );
			break;*/
		case '重定向':
			params.tags = form.getChecked( 'redirectTags' );
			break;
		/*case 'draft':
			params.tags = form.getChecked( 'draftTags' );
			Twinkle.tag.mode = 'article';
			break;*/
		default:
			alert("Twinkle.tag:未知模式 " + Twinkle.tag.mode);
			break;
	}

	if( !params.tags.length ) {
		alert( '必须选择至少一个标记!' );
		return;
	}

	SimpleWindow.setButtonsEnabled( false );
	Status.init( form );

	Wikipedia.actionCompleted.redirect = mw.config.get('wgPageName');
	Wikipedia.actionCompleted.notice = "标记完成,在几秒内刷新页面";
	if (Twinkle.tag.mode === '重定向') {
		Wikipedia.actionCompleted.followRedirect = false;
	}

	var wikipedia_page = new Wikipedia.page(mw.config.get('wgPageName'), "正在标记" + Twinkle.tag.mode);
	wikipedia_page.setCallbackParameters(params);
	switch (Twinkle.tag.mode) {
		case '条目':
			/* falls through */
		case '重定向':
			wikipedia_page.load(Twinkle.tag.callbacks.main);
			return;
		/*case 'file':
			wikipedia_page.load(Twinkle.tag.callbacks.file);
			return;*/
		default:
			alert("Twinkle.tag:未知模式 " + Twinkle.tag.mode);
			break;
	}
};

/*
 * vim: set noet sts=0 sw=8:
 ****************************************
 *** twinklespeedy.js: CSD module
 ****************************************
 * Mode of invocation:     Tab ("CSD")
 * Active on:              Non-special, existing pages
 * Config directives in:   TwinkleConfig
 *
 * NOTE FOR DEVELOPERS:
 *   If adding a new criterion, check out the default values of the CSD preferences
 *   in twinkle.header.js, and add your new criterion to those if you think it would
 *   be good. 
 */

Twinkle.speedy = function twinklespeedy() {
	// Disable on:
	// * special pages
	// * non-existent pages
	if (mw.config.get('wgNamespaceNumber') < 0 || !mw.config.get('wgArticleId')) {
		return;
	}

	if ( userIsInGroup( 'sysop' ) ) {
		$(twAddPortletLink("#", "速删", "tw-csd", "快速删除", "")).click(Twinkle.speedy.callback);
	} else if (twinkleUserAuthorized) {
		$(twAddPortletLink("#", "速删", "tw-csd", "请求快速删除", "")).click(Twinkle.speedy.callback);
	} else {
		$(twAddPortletLink("#", '速删', 'tw-csd', '请求快速删除', '')).click(function() {
			alert("您还未达到自动确认。");
		});
	}
};

// This function is run when the CSD tab/header link is clicked
Twinkle.speedy.callback = function twinklespeedyCallback() {
	Twinkle.speedy.initDialog(userIsInGroup( 'sysop' ) ? Twinkle.speedy.callback.evaluateSysop : Twinkle.speedy.callback.evaluateUser, true);
};

Twinkle.speedy.dialog = null;
// Prepares the speedy deletion dialog and displays it
// Parameters:
//  - callbackfunc: the function to call when the dialog box is submitted
//  - firstTime: is this the first time? (false during a db-multiple run, true otherwise)
//  - content: (optional) a div element in which the form content should be rendered - allows
//    for placing content in an existing dialog box
Twinkle.speedy.initDialog = function twinklespeedyInitDialog(callbackfunc, firstTime, content) {
	var dialog;
	if (!content)
	{
		Twinkle.speedy.dialog = new SimpleWindow( Twinkle.getPref('speedyWindowWidth'), Twinkle.getPref('speedyWindowHeight') );
		dialog = Twinkle.speedy.dialog;
		dialog.setTitle( "选择快速删除理由" );
		dialog.setScriptName( "Twinkle" );
		dialog.addFooterLink( "快速删除方针", "WP:CSD" );
		dialog.addFooterLink( "Twinkle帮助", "WP:TW/DOC#speedy" );
	}

	var form = new QuickForm( callbackfunc, 'change' );
	if( firstTime && userIsInGroup( 'sysop' ) ) {
		form.append( {
				type: 'checkbox',
				list: [
					{
						label: '只标记,不删除',
						value: 'tag_only',
						name: 'tag_only',
						tooltip: '如果您只想标记此页面而不是删除它',
						checked : Twinkle.getPref('deleteSysopDefaultToTag'),
						event: function( event ) {
							// enable/disable notify checkbox
							event.target.form.notify.disabled = !event.target.checked;
							event.target.form.notify.checked = event.target.checked;
							// enable/disable talk page checkbox
							if (event.target.form.talkpage) {
								event.target.form.talkpage.disabled = event.target.checked;
								event.target.form.talkpage.checked = !event.target.checked && Twinkle.getPref('deleteTalkPageOnDelete');
							}
							// enable/disable redirects checkbox
							event.target.form.redirects.disabled = event.target.checked;
							event.target.form.redirects.checked = !event.target.checked;
							// enable/disable multiple
							$(event.target.form).find('input[name="csd"][value="multiple"]')[0].disabled = !event.target.checked;
							event.stopPropagation();
						}
					}
				]
			} );
		form.append( { type: 'header', label: '删除相关选项' } );
		if (mw.config.get('wgNamespaceNumber') % 2 === 0 && (mw.config.get('wgNamespaceNumber') !== 2 || (/\//).test(mw.config.get('wgTitle')))) {  // hide option for user pages, to avoid accidentally deleting user talk page
			form.append( {
				type: 'checkbox',
				list: [
					{
						label: '删除讨论页',
						value: 'talkpage',
						name: 'talkpage',
						tooltip: "删除时附带删除此页面的讨论页。",
						checked: Twinkle.getPref('deleteTalkPageOnDelete'),
						disabled: Twinkle.getPref('deleteSysopDefaultToTag'),
						event: function( event ) {
							event.stopPropagation();
						}
					}
				]
			} );
		}
		form.append( {
				type: 'checkbox',
				list: [
					{
						label: '删除重定向',
						value: 'redirects',
						name: 'redirects',
						tooltip: "删除到此页的重定向。",
						checked: true,
						disabled: Twinkle.getPref('deleteSysopDefaultToTag'),
						event: function( event ) {
							event.stopPropagation();
						}
					}
				]
			} );
		form.append( { type: 'header', label: '标记相关选项' } );
	}

	// don't show this notification checkbox for db-multiple, as the value is ignored
	// XXX currently not possible to turn off notification when using db-multiple
	if (firstTime) {
		form.append( {
				type: 'checkbox',
				list: [
					{
						label: '如可能,通知创建者',
						value: 'notify',
						name: 'notify',
						tooltip: "一个通知模板将会被加入创建者的对话页。",
						checked: !userIsInGroup( 'sysop' ) || Twinkle.getPref('deleteSysopDefaultToTag'),
						disabled: userIsInGroup( 'sysop' ) && !Twinkle.getPref('deleteSysopDefaultToTag'),
						event: function( event ) {
							event.stopPropagation();
						}
					}
				]
			}
		);
	} else {
		form.append( { type:'header', label: '多个理由:第 ' + (Twinkle.speedy.dbmultipleCriteria.length + 1) + ' 个' } );
	}

	if (firstTime) {
		form.append( { type: 'radio', name: 'csd',
			list: [
				{
					label: '应用多个理由',
					value: 'multiple',
					tooltip: '开启一些新的对话框,让您选择多个理由。',
					disabled: userIsInGroup('sysop') && !Twinkle.getPref('deleteSysopDefaultToTag')
				}
			]
		} );
	} else if (Twinkle.speedy.dbmultipleCriteria.length > 0) {
		form.append( { type: 'radio', name: 'csd',
			list: [
				{
					label: '没有更多理由了,结束标记',
					value: 'multiple-finish'
				}
			]
		} );
	}

	var namespace = mw.config.get('wgNamespaceNumber');
	/*if (namespace % 2 === 1 && namespace !== 3) {  // talk pages, but not user talk pages
		form.append( { type: 'header', label: '讨论页' } );
		form.append( { type: 'radio', name: 'csd', list: Twinkle.speedy.talkList } );
	}*/

	switch (namespace) {
		case 0:  // article
		case 1:  // talk
			form.append( { type: 'header', label: '条目' } );
			form.append( { type: 'radio', name: 'csd', list: Twinkle.speedy.getArticleList(!firstTime) } );
			break;

		case 2:  // user
		case 3:  // user talk
			form.append( { type: 'header', label: '用户页' } );
			form.append( { type: 'radio', name: 'csd', list: Twinkle.speedy.userList } );
			break;

		case 6:  // file
		case 7:  // file talk
			form.append( { type: 'header', label: '文件' } );
			form.append( { type: 'radio', name: 'csd', list: Twinkle.speedy.getFileList(!firstTime) } );
			form.append( { type: 'div', label: '标记CSD F3、F4,请使用Twinkle的“图版”功能。' } );
			break;

		/*case 10:  // template
		case 11:  // template talk
			form.append( { type: 'header', label: '模板' } );
			form.append( { type: 'radio', name: 'csd', list: Twinkle.speedy.getTemplateList(!firstTime) } );
			break;*/

		case 14:  // category
		case 15:  // category talk
			form.append( { type: 'header', label: '分类' } );
			form.append( { type: 'radio', name: 'csd', list: Twinkle.speedy.categoryList } );
			break;

		/*case 100:  // portal
		case 101:  // portal talk
			form.append( { type: 'header', label: '主题' } );
			form.append( { type: 'radio', name: 'csd', list: Twinkle.speedy.getPortalList(!firstTime) } );
			break;*/

		default:
			break;
	}

	form.append( { type: 'header', label: '常规' } );
	form.append( { type: 'radio', name: 'csd', list: Twinkle.speedy.getGeneralList(!firstTime) });

	form.append( { type: 'header', label: '重定向' } );
	form.append( { type: 'radio', name: 'csd', list: Twinkle.speedy.redirectList } );

	var result = form.render();
	if (dialog)
	{
		// render new dialog
		dialog.setContent( result );
		dialog.display();
	}
	else
	{
		// place the form content into the existing dialog box
		content.textContent = ''; // clear children
		content.appendChild(result);
	}
};

// this is a function to allow for db-multiple filtering
Twinkle.speedy.getFileList = function twinklespeedyGetFileList(multiple) {
	var result = [];
	result.push({
		label: 'F1: 重复的文件(完全相同或缩小),而且所有的链入连接已经被修改为指向保留的文件。',
		value: 'f1'
	});
	if (userIsInGroup('sysop')) {
		result.push({
			label: 'F3: 所有未知版权的文件和来源不明文件。',
			value: 'f3'
		});
		result.push({
			label: 'F4: 没有依据上载页面指示提供版权状况、来源等资讯的文件。',
			value: 'f4'
		});
	}
	result.push({
		label: 'F5: 被高分辨率或SVG文件取代的图片。',
		value: 'f5'
	});
	result.push({
		label: 'F6: 孤立而没有被条目使用的非自由版权文件。',
		value: 'f6'
	});
	if (!multiple) {
		result.push({
			label: 'F7: 被维基共享资源文件取代的文件。',
			value: 'f7'
		});
	}
	return result;
};

Twinkle.speedy.getArticleList = function twinklespeedyGetArticleList(multiple) {
	var result = [];
	result.push({
		label: 'A1: 非常短,而且没有定义或内容。',
		value: 'a1',
		tooltip: '例如:“他是一个很有趣的人,他创建了工厂和庄园。并且,顺便提一下,他的妻子也很好。”如果能够发现任何相关的内容,可以将这个页面重定向到相关的条目上。'
	});
	result.push({
		label: 'A2: 没有内容。',
		value: 'a2',
		tooltip: '任何内容只包括外部连接、参见、图书参考、类别标签、模板标签、跨语言连接的条目(消歧义页、重定向、软重定向除外)。请注意:有些维基人创建条目时会分开多次保存,请避免删除有人正在工作的页面。带有{{inuse}}的不适用。'
	});
	result.push({
		label: 'A3: 跨维基条目。',
		value: 'a3',
		tooltip: '复制自其他中文维基计划,或是与其他中文维基计划内容相同的文章,或者是透过Transwiki系统移动的文章。'
	});
	return result;
};

Twinkle.speedy.categoryList = [
	{
		label: 'O4: 空的类别(没有条目也没有子类别)。',
		value: 'o4',
		tooltip: '不适用于Category:不要删除的分类中的空分类。'
	}
];

Twinkle.speedy.userList = [
	{
		label: 'O1: 用户请求删除的他们自己的用户页或用户讨论页及其子页面。',
		value: 'o1',
		tooltip: '如果是从其他名字空间移动来的,须附有合理原因。'
	},
	{
		label: 'O3: 匿名用户的用户讨论页,其中的内容不再有用(避免给使用同一IP地址的用户带来混淆)。',
		value: 'o3'
	}
];

/*Twinkle.speedy.getTemplateList = function twinklespeedyGetTemplateList(multiple) {
	var result = [];
	result.push({
		label: 'T2: Templates that are blatant misrepresentations of established policy',
		value: 'policy',
		tooltip: 'This includes "speedy deletion" templates for issues that are not speedy deletion criteria and disclaimer templates intended to be used in articles'
	});
	if (!multiple) {
		result.push({
			label: 'T3: Templates that are not employed in any useful fashion',
			value: 't3',
			tooltip: 'Templates that are either substantial duplications of another template or hardcoded instances of another template where the same functionality could be provided by that other template'
		});
	}
	return result;
};*/

/*Twinkle.speedy.getPortalList = function twinklespeedyGetPortalList(multiple) {
	var result = [];
	if (!multiple) {
		result.push({
			label: 'P1: Portal that would be subject to speedy deletion if it were an article',
			value: 'p1',
			tooltip: 'You must specify the article criterion that applies in this case (A1, A3, A7, or A10).'
		});
	}
	result.push({
		label: 'P2: Underpopulated portal',
		value: 'emptyportal',
		tooltip: 'Any Portal based on a topic for which there is not a non-stub header article, and at least three non-stub articles detailing subject matter that would be appropriate to discuss under the title of that Portal'
	});
	return result;
};*/

Twinkle.speedy.getGeneralList = function twinklespeedyGetGeneralList(multiple) {
	var result = [];
	if (!multiple) {
		result.push({
			label: '自定义理由' + (userIsInGroup('sysop') ? '(自定义删除理由)' : ''),
			value: 'reason'
		});
	}
	result.push({
		label: 'G1: 没有实际内容或历史纪录的文章。',
		value: 'g1',
		tooltip: '如“adfasddd”。参见Wikipedia:胡言乱语。但注意:图片也算是内容。'
	});
	result.push({
		label: 'G2: 测试页面。',
		value: 'g2',
		tooltip: '例如:“这是一个测试。”'
	});
	result.push({
		label: 'G3: 纯粹破坏。',
		value: 'g3'
	});
	result.push({
		label: 'G5: 曾经根据Wikipedia:页面存废讨论、Wikipedia:页面存废讨论/疑似侵权、Wikipedia:文件存废讨论被删除后又重新创建的内容,无论标题是否相同。',
		value: 'g5',
		tooltip: '该内容之前必须是经存废讨论删除,如之前属于快速删除,请以相同理由重新提送快速删除。不适用于根据恢复守则被恢复的内容。在某些情况下,重新创建的条目有发展的机会。那么不应提交快速删除,而应该提交删除投票进行讨论。'
	});
	result.push({
		label: 'G10: 原作者清空页面或提出删除,且贡献者只有一人。提请须出于善意,及附有合理原因。',
		value: 'g10',
		tooltip: '如果贡献者只有一人(对条目内容无实际修改的除外),并附有合理原因,适用此项。'
	});
	result.push({
		label: 'G11: 明显以广告宣传为目的而建立的页面,或任何只有该页面名称中的人物或团体的联系方法的页面。',
		value: 'g11',
		tooltip: '只针对专门用于宣传的页面,这些页面需要经过完全重写才能体现百科全书性。需注意,仅仅以某公司或产品为主题的条目,并不直接导致其自然满足此速删标准。'
	});
	result.push({
		label: 'G12: 未列明来源且语调负面的生者传记,无任何版本可回退。',
		value: 'g12',
		tooltip: '注意是未列明来源且语调负面,必须2项均符合。'
	});
	result.push({
		label: 'G13: 明显的、拙劣的机器翻译。',
		value: 'g13'
	});
	if (userIsInGroup('sysop')) {
		result.push({
			label: 'G14: 未翻译页面。',
			value: 'g14',
			tooltip: '复制自其他维基媒体计划,超过两周没有进行任何翻译的非现代标准汉语页面,包括所有未翻译的外语、汉语方言以及文言文。'
		});
	}
	result.push({
		label: 'G15: 孤立页面。',
		value: 'g15',
		tooltip: '包括以下几种类型:1. 没有对应文件的文件页面;2. 没有对应母页面的子页面,用户页子页面除外;3. 指向不存在页面的重定向;4. 没有对应内容页面的讨论页,讨论页存档和用户讨论页除外;5. 对应内容页面为重定向的讨论页,前提是讨论页建立于重定向之后,或者讨论内容已经存档;6. 不存在用户的用户页及用户页子页面,随用户更名产生的用户页重定向除外。'
	});
	result.push({
		label: 'G16: 临时页面依然侵权。',
		value: 'g16',
		tooltip: '因为主页面侵权而创建的临时页面仍然侵权。'
	});
	return result;
};

Twinkle.speedy.redirectList = [
	{
		label: 'R2: 跨名字空间重定向。',
		value: 'r2',
		tooltip: '由条目的名字空间重定向至非条目名字空间,或将用户页移出条目名字空间时遗留的重定向。'
	},
	{
		label: 'R3: 名称错误的重定向,包括条目标题繁简混用、消歧义使用括号或空格错误、间隔号使用错误。',
		value: 'r3',
		tooltip: '不包括常见的拼写错误。为常见的拼写错误建立指向正确题目的重定向页面,可使百科用户纵使在查找文章时拼写错误,也能够找到寻求的文章。参阅:Wikipedia:命名常规。'
	},
	{
		label: 'R4: 故意破坏的结果。',
		value: 'r4',
		tooltip: '如将一个页面移动到一个没有意义的标题上,当重新移动回正确名称时,就会留下一个重定向页。'
	},
	{
		label: 'R5: 指向本身的重定向或循环的重定向。',
		value: 'r5',
		tooltip: '如A→B→C→……→A。'
	}
];

Twinkle.speedy.normalizeHash = {
	'reason': 'db',
	'multiple': 'multiple',
	'multiple-finish': 'multiple-finish',
	'g1': 'g1',
	'g2': 'g2',
	'g3': 'g3',
	'g5': 'g5',
	'g10': 'g10',
	'g11': 'g11',
	'g12': 'g12',
	'g13': 'g13',
	'g14': 'g14',
	'g15': 'g15',
	'g16': 'g16',
	'a1': 'a1',
	'a2': 'a2',
	'a3': 'a3',
	'r2': 'r2',
	'r3': 'r3',
	'r4': 'r4',
	'r5': 'r5',
	'f1': 'f1',
	'f3': 'f3',
	'f4': 'f4',
	'f5': 'f5',
	'f6': 'f6',
	'f7': 'f7',
	'o1': 'o1',
	'o3': 'o3',
	'o4': 'o4'
};

// keep this synched with [[MediaWiki:Deletereason-dropdown]]
Twinkle.speedy.reasonHash = {
	'reason': '',
// General
	'g1': '无实际内容',
	'g2': '测试页',
	'g3': '破坏',
	'g5': '曾经依存废讨论被删除的重建内容',
	'g10': '作者请求',
	'g11': '广告或宣传',
	'g12': '未列明来源或违反[[Wikipedia:生者传记]]的负面内容',
	'g13': '明显且拙劣的机器翻译',
	'g14': '超过两周没有翻译的非现代标准汉语页面',
	'g15': '孤立页面',
	'g16': '临时页面依然侵权',
// Articles
	'a1': '非常短而无定义或内容',
	'a2': '内容只包含参考、链接、模板或/及分类',
	'a3': '与其他中文维基计划内容相同的文章',
// Redirects
	'r2': '跨名字空间重定向',
	'r3': '名称错误的重定向',
	'r4': '重定向破坏',
	'r5': '指向本身的重定向或循环的重定向',
// Images and media
	'f1': '重复的图片',
	'f3': '[[:Category:未知版权的档案]]',
	'f4': '[[:Category:來源不明檔案]]',
	'f5': '已有高分辨率的图片取代',
	'f6': '孤立而没有被条目使用的非自由版权图片',
	'f7': '[[:Category:与维基共享资源重复的档案]]',
// User pages
	'o1': '用户请求删除自己的用户页或其子页面',
	'o3': '匿名用户的讨论页',
// Categories
	'o4': '空的类别'
// Templates
// Portals
};

Twinkle.speedy.callbacks = {
	sysop: {
		main: function( params ) {
			var thispage = new Wikipedia.page( mw.config.get('wgPageName'), "删除页面" );

			// delete page
			var reason;
			if (params.normalized === 'db') {
				reason = prompt("输入删除理由:", "");
			} else {
				var presetReason = "[[WP:CSD#" + params.normalized.toUpperCase() + "|" + params.normalized.toUpperCase() + "]]: " + params.reason;
				if (Twinkle.getPref("promptForSpeedyDeletionSummary").indexOf(params.normalized) !== -1) {
					reason = prompt("输入删除理由,或点击确定以接受自动生成的:", presetReason);
				} else {
					reason = presetReason;
				}
			}
			if (!reason || !reason.replace(/^\s*/, "").replace(/\s*$/, "")) {
				Status.error("询问理由", "您没有提供理由,取消操作。");
				return;
			}
			thispage.setEditSummary( reason + Twinkle.getPref('deletionSummaryAd') );
			thispage.deletePage();

			// delete talk page
			if (params.deleteTalkPage &&
			    params.normalized !== 'f7' &&
			    params.normalized !== 'o1' &&
			    document.getElementById( 'ca-talk' ).className !== 'new') {
				var talkpage = new Wikipedia.page( Wikipedia.namespaces[ mw.config.get('wgNamespaceNumber') + 1 ] + ':' + mw.config.get('wgTitle'), "删除讨论页" );
				talkpage.setEditSummary('[[WP:CSD#G15|CSD G15]]: 孤立页面: 已删除页面 [[' + mw.config.get('wgPageName') + "]] 的讨论页" + Twinkle.getPref('deletionSummaryAd'));
				talkpage.deletePage();
			}

			// promote Unlink tool
			var $link, $bigtext;
			if( mw.config.get('wgNamespaceNumber') === 6 && params.normalized !== 'f7' ) {
				$link = $('<a/>', {
					'href': '#',
					'text': '点击这里前往反链工具',
					'css': { 'fontSize': '130%', 'fontWeight': 'bold' },
					'click': function(){
						Wikipedia.actionCompleted.redirect = null;
						Twinkle.speedy.dialog.close();
						Twinkle.unlink.callback("取消对已删除文件 " + mw.config.get('wgPageName') + " 的使用");
					}
				});
				$bigtext = $('<span/>', {
					'text': '取消对已删除文件的使用',
					'css': { 'fontSize': '130%', 'fontWeight': 'bold' }
				});
				Status.info($bigtext[0], $link[0]);
			} else if (params.normalized !== 'f7') {
				$link = $('<a/>', {
					'href': '#',
					'text': '点击这里前往反链工具',
					'css': { 'fontSize': '130%', 'fontWeight': 'bold' },
					'click': function(){
						Wikipedia.actionCompleted.redirect = null;
						Twinkle.speedy.dialog.close();
						Twinkle.unlink.callback("取消对已删除页面 " + mw.config.get('wgPageName') + " 的链接");
					}
				});
				$bigtext = $('<span/>', {
					'text': '取消对已删除页面的链接',
					'css': { 'fontSize': '130%', 'fontWeight': 'bold' }
				});
				Status.info($bigtext[0], $link[0]);
			}

			// open talk page of first contributor
			if( params.openusertalk ) {
				thispage = new Wikipedia.page( mw.config.get('wgPageName') );  // a necessary evil, in order to clear incorrect Status.text
				thispage.setCallbackParameters( params );
				thispage.lookupCreator( Twinkle.speedy.callbacks.sysop.openUserTalkPage );
			}

			// delete redirects
			if (params.deleteRedirects) {
				var query = {
					'action': 'query',
					'list': 'backlinks',
					'blfilterredir': 'redirects',
					'bltitle': mw.config.get('wgPageName'),
					'bllimit': 5000  // 500 is max for normal users, 5000 for bots and sysops
				};
				var wikipedia_api = new Wikipedia.api( '取得重定向列表…', query, Twinkle.speedy.callbacks.sysop.deleteRedirectsMain,
					new Status( '删除重定向' ) );
				wikipedia_api.params = params;
				wikipedia_api.post();
			}
		},
		openUserTalkPage: function( pageobj ) {
			pageobj.getStatusElement().unlink();  // don't need it anymore
			var user = pageobj.getCreator();
			var statusIndicator = new Status('打开用户 ' + user + ' 的对话页', '正在打开…');

			var query = {
				'title': 'User talk:' + user,
				'action': 'edit',
				'preview': 'yes',
				'vanarticle': mw.config.get('wgPageName').replace(/_/g, ' ')
			};
			switch( Twinkle.getPref('userTalkPageMode') ) {
			case 'tab':
				window.open( mw.config.get('wgServer') + mw.config.get('wgScriptPath') + '/index.php?' + QueryString.create( query ), '_tab' );
				break;
			case 'blank':
				window.open( mw.config.get('wgServer') + mw.config.get('wgScriptPath') + '/index.php?' + QueryString.create( query ), '_blank', 'location=no,toolbar=no,status=no,directories=no,scrollbars=yes,width=1200,height=800' );
				break;
			case 'window':
				/* falls through */
				default :
				window.open( mw.config.get('wgServer') + mw.config.get('wgScriptPath') + '/index.php?' + QueryString.create( query ), 'twinklewarnwindow', 'location=no,toolbar=no,status=no,directories=no,scrollbars=yes,width=1200,height=800' );
				break;
			}

			statusIndicator.info( '完成' );
		},
		deleteRedirectsMain: function( apiobj ) {
			var xmlDoc = apiobj.getXML();
			var $snapshot = $(xmlDoc).find('backlinks bl');

			var total = $snapshot.length;

			if( !total ) {
				return;
			}

			var statusIndicator = apiobj.statelem;
			statusIndicator.status("0%");

			var onsuccess = function( apiobj ) {
				var obj = apiobj.parent.params.obj;
				var total = apiobj.parent.params.total;
				var now = parseInt( 100 * ++(apiobj.parent.params.current)/total, 10 ) + '%';
				obj.update( now );
				apiobj.statelem.unlink();
				if( apiobj.parent.params.current >= total ) {
					obj.info( now + '(完成)' );
					Wikipedia.removeCheckpoint();
				}
			};

			Wikipedia.addCheckpoint();

			var params = clone( apiobj.params );
			params.current = 0;
			params.total = total;
			params.obj = statusIndicator;

			$snapshot.each(function(key, value) {
				var title = $(value).attr('title');
				var page = new Wikipedia.page(title, '删除重定向 "' + title + '"');
				page.setEditSummary('[[WP:CSD#G15|CSD G15]]: 孤立页面: 重定向到已删除页面 [[' + mw.config.get('wgPageName') + "]]" + Twinkle.getPref('deletionSummaryAd'));
				page.params = params;
				page.deletePage(onsuccess);
			});
		}
	},





	user: {
		main: function(pageobj) {
			var statelem = pageobj.getStatusElement();

			if (!pageobj.exists()) {
				statelem.error( "页面不存在,可能已被删除" );
				return;
			}

			var text = pageobj.getPageText();
			var params = pageobj.getCallbackParameters();

			statelem.status( '检查页面已有标记…' );

			// check for existing deletion tags
			var tag = /(?:\{\{\s*(db|d|delete|db-.*?)(?:\s*\||\s*\}\}))/i.exec( text );
			if( tag ) {
				statelem.error( [ htmlNode( 'strong', tag[1] ) , " 已被置于页面中。" ] );
				return;
			}

			var xfd = /(?:\{\{([rsaiftcm]fd|md1)[^{}]*?\}\})/i.exec( text );
			if( xfd && !confirm( "删除相关模板 {{" + xfd[1] + "}} 已被置于页面中,您是否仍想添加一个快速删除模板?" ) ) {
				return;
			}

			var code, parameters, i;
			if (params.normalized === 'multiple')
			{
				code = "{{delete";
				for (i in Twinkle.speedy.dbmultipleCriteria) {
					if (typeof Twinkle.speedy.dbmultipleCriteria[i] === 'string') {
						code += "|" + Twinkle.speedy.dbmultipleCriteria[i].toUpperCase();
					}
				}
				for (i in Twinkle.speedy.dbmultipleParameters) {
					if (typeof Twinkle.speedy.dbmultipleParameters[i] === 'string') {
						code += "|" + Twinkle.speedy.dbmultipleParameters[i];
					}
				}
				code += "}}";
				params.utparams = [];
			}
			else
			{
				parameters = Twinkle.speedy.getParameters(params.value, params.normalized, statelem);
				if (!parameters) {
					return;  // the user aborted
				}
				code = "{{delete|";
				if (params.value !== 'reason') {
					code += params.value;
				}
				for (i in parameters) {
					if (typeof parameters[i] === 'string') {
						code += "|" + parameters[i];
					}
				}
				code += "}}";
				params.utparams = Twinkle.speedy.getUserTalkParameters(params.normalized, parameters);
			}

			var thispage = new Wikipedia.page(mw.config.get('wgPageName'));
			// patrol the page, if reached from Special:NewPages
			if( Twinkle.getPref('markSpeedyPagesAsPatrolled') ) {
				thispage.patrol();
			}

			// Notification to first contributor
			if (params.usertalk) {
				var callback = function(pageobj) {
					var initialContrib = pageobj.getCreator();
					var usertalkpage = new Wikipedia.page('User talk:' + initialContrib, "通知页面创建者(" + initialContrib + ")");
					var notifytext;

					// specialcase "db" and "db-multiple"
					switch (params.normalized)
					{
						case 'db':
						case 'multiple':
						default:
							notifytext = "\n\n{{subst:db-notice|target=" + mw.config.get('wgPageName');
							break;
					}
					/*for (var i in params.utparams) {
						if (typeof params.utparams[i] === 'string') {
							notifytext += "|" + i + "=" + params.utparams[i];
						}
					}*/
					notifytext += (params.welcomeuser ? "" : "|nowelcome=yes") + "}}--~~~~";

					usertalkpage.setAppendText(notifytext);
					usertalkpage.setEditSummary("通知:页面[[" + mw.config.get('wgPageName') + "]]快速删除提名。" + Twinkle.getPref('summaryAd'));
					usertalkpage.setCreateOption('recreate');
					usertalkpage.setFollowRedirect(true);
					usertalkpage.append();

					// add this nomination to the user's userspace log, if the user has enabled it
					if (params.lognomination) {
						Twinkle.speedy.callbacks.user.addToLog(params, initialContrib);
					}
				};
				thispage.lookupCreator(callback);
			}
			// or, if not notifying, add this nomination to the user's userspace log without the initial contributor's name
			else if (params.lognomination) {
				Twinkle.speedy.callbacks.user.addToLog(params, null);
			}

			// Wrap SD template in noinclude tags if we are in template space.
			// Won't work with userboxes in userspace, or any other transcluded page outside template space
			if (mw.config.get('wgNamespaceNumber') === 10) {  // Template:
				code = "<noinclude>" + code + "</noinclude>";
			}

			// Remove tags that become superfluous with this action
			//text = text.replace(/\{\{\s*(New unreviewed article|Userspace draft)\s*(\|(?:\{\{[^{}]*\}\}|[^{}])*)?\}\}\s*/ig, "");

			// Generate edit summary for edit
			var editsummary;
			switch (params.normalized)
			{
				case 'db':
					editsummary = '请求[[WP:CSD|快速删除]]:' + parameters["1"];
					break;
				case 'multiple':
					editsummary = '请求快速删除(';
					for (i in Twinkle.speedy.dbmultipleCriteria) {
						if (typeof Twinkle.speedy.dbmultipleCriteria[i] === 'string') {
							editsummary += '[[WP:CSD#' + Twinkle.speedy.dbmultipleCriteria[i].toUpperCase() + '|CSD ' + Twinkle.speedy.dbmultipleCriteria[i].toUpperCase() + ']]、';
						}
					}
					editsummary = editsummary.substr(0, editsummary.length - 1); // remove trailing comma
					editsummary += ')。';
					break;
				default:
					editsummary = "请求快速删除([[WP:CSD#" + params.normalized.toUpperCase() + "|CSD " + params.normalized.toUpperCase() + "]])。";
					break;
			}

			pageobj.setPageText(code + "\n" + text );
			pageobj.setEditSummary(editsummary + Twinkle.getPref('summaryAd'));
			pageobj.setWatchlist(params.watch);
			pageobj.setCreateOption('nocreate');
			pageobj.save();
		},

		// note: this code is also invoked from twinkleimage
		// the params used are:
		//   for all: params.normalized
		//   for CSD: params.value
		//   for DI: params.fromDI = true, params.type
		addToLog: function(params, initialContrib) {
			var wikipedia_page = new Wikipedia.page("User:" + mw.config.get('wgUserName') + "/" + Twinkle.getPref('speedyLogPageName'), "添加项目到用户日志");
			params.logInitialContrib = initialContrib;
			wikipedia_page.setCallbackParameters(params);
			wikipedia_page.load(Twinkle.speedy.callbacks.user.saveLog);
		},

		saveLog: function(pageobj) {
			var text = pageobj.getPageText();
			var params = pageobj.getCallbackParameters();

			// add blurb if log page doesn't exist
			if (!pageobj.exists()) {
				text =
					"这是该用户使用[[WP:TW|Twinkle]]的速删模块做出的[[WP:CSD|快速删除]]提名列表。\n\n" +
					"如果您不再想保留此日志,请在[[Wikipedia:Twinkle/参数设置|参数设置]]中关掉,并" +
					"使用[[WP:CSD#O1|CSD O1]]提交快速删除。\n";
				if (userIsInGroup("sysop")) {
					text += "\n此日志并不记录用Twinkle直接执行的删除。\n";
				}
			}

			// create monthly header
			var date = new Date();
			var headerRe = new RegExp("^==+\\s*" + date.getUTCFullYear() + "\\s*年\\s*" + (date.getUTCMonth() + 1) + "\\s*月\\s*==+", "m");
			if (!headerRe.exec(text)) {
				text += "\n\n=== " + date.getUTCFullYear() + "年" + (date.getUTCMonth() + 1) + "月 ===";
			}

			text += "\n# [[:" + mw.config.get('wgPageName') + "]]: ";
			if (params.fromDI) {
				text += "图版[[WP:CSD#" + params.normalized.toUpperCase() + "|CSD " + params.normalized.toUpperCase() + "]](" + params.type + ")";
			} else {
				switch (params.normalized)
				{
					case 'db':
						text += "自定义理由";
						break;
					case 'multiple':
						text += "多个理由(";
						for (var i in Twinkle.speedy.dbmultipleCriteria) {
							if (typeof Twinkle.speedy.dbmultipleCriteria[i] === 'string') {
								text += '[[WP:CSD#' + Twinkle.speedy.dbmultipleCriteria[i].toUpperCase() + '|' + Twinkle.speedy.dbmultipleCriteria[i].toUpperCase() + ']]、';
							}
						}
						text = text.substr(0, text.length - 1);  // remove trailing comma
						text += ')';
						break;
					default:
						text += "[[WP:CSD#" + params.normalized.toUpperCase() + "|CSD " + params.normalized.toUpperCase() + "]]";
						break;
				}
			}

			if (params.logInitialContrib) {
				text += ";通知{{user|" + params.logInitialContrib + "}}";
			}
			text += " ~~~~~\n";

			pageobj.setPageText(text);
			pageobj.setEditSummary("记录对[[" + mw.config.get('wgPageName') + "]]的快速删除提名。" + Twinkle.getPref('summaryAd'));
			pageobj.setCreateOption("recreate");
			pageobj.save();
		}
	}
};

// prompts user for parameters to be passed into the speedy deletion tag
Twinkle.speedy.getParameters = function twinklespeedyGetParameters(value, normalized, statelem)
{
	var parameters = [];
	switch( normalized ) {
		case 'db':
			var dbrationale = prompt('这个页面应当被快速删除,因为:', "");
			if (!dbrationale || !dbrationale.replace(/^\s*/, "").replace(/\s*$/, ""))
			{
				statelem.error( '您必须提供理由,用户取消操作。' );
				return null;
			}
			parameters["1"] = dbrationale;
			break;
		case 'f7':
			var pagenamespaces = mw.config.get('wgPageName').replace( '_', ' ' );
			var filename = prompt( '请输入维基共享上的文件名:', pagenamespaces );
			if (filename === null)
			{
				statelem.error( '用户取消操作。' );
				return null;
			}
			if (filename !== '' && filename !== pagenamespaces)
			{
				if (filename.indexOf("Image:") === 0 || filename.indexOf("File:") === 0)
				{
					parameters["1"] = filename;
				}
				else
				{
					statelem.error("缺少File:前缀,取消操作。");
					return null;
				}
			}
			break;
		case 'f1':
			var img = prompt( '输入与此文件相同的文件名(不含“File:”前缀):', "" );
			if (img === null)
			{
				statelem.error( '用户取消操作。' );
				return null;
			}
			parameters.filename = img;
			break;
		default:
			break;
	}
	return parameters;
};

// function for processing talk page notification template parameters
Twinkle.speedy.getUserTalkParameters = function twinklespeedyGetUserTalkParameters(normalized, parameters)
{
	var utparams = [];
	switch (normalized)
	{
		default:
			break;
	}
	return utparams;
};

Twinkle.speedy.callback.evaluateSysop = function twinklespeedyCallbackEvaluateSysop(e)
{
	mw.config.set('wgPageName', mw.config.get('wgPageName').replace(/_/g, ' ')); // for queen/king/whatever and country!

	var tag_only = e.target.form.tag_only;
	if( tag_only && tag_only.checked ) {
		Twinkle.speedy.callback.evaluateUser(e);
		return;
	}

	var value = e.target.values;
	var normalized = Twinkle.speedy.normalizeHash[ value ];

	var params = {
		value: value,
		normalized: normalized,
		watch: Twinkle.getPref('watchSpeedyPages').indexOf( normalized ) !== -1,
		reason: Twinkle.speedy.reasonHash[ value ],
		openusertalk: Twinkle.getPref('openUserTalkPageOnSpeedyDelete').indexOf( normalized ) !== -1,
		deleteTalkPage: e.target.form.talkpage && e.target.form.talkpage.checked,
		deleteRedirects: e.target.form.redirects.checked
	};
	Status.init( e.target.form );

	Twinkle.speedy.callbacks.sysop.main( params );
};

Twinkle.speedy.callback.evaluateUser = function twinklespeedyCallbackEvaluateUser(e) {
	mw.config.set('wgPageName', mw.config.get('wgPageName').replace(/_/g, ' '));  // for queen/king/whatever and country!
	var value = e.target.values;

	if (value === 'multiple')
	{
		e.target.form.style.display = "none"; // give the user a cue that the dialog is being changed
		setTimeout(function() {
			Twinkle.speedy.initDialog(Twinkle.speedy.callback.doMultiple, false, e.target.form.parentNode);
		}, 150);
		return;
	}

	if (value === 'multiple-finish') {
		value = 'multiple';
	}
	else
	{
		// clear these out, whatever the case, to avoid errors
		Twinkle.speedy.dbmultipleCriteria = [];
		Twinkle.speedy.dbmultipleParameters = [];
	}

	var normalized = Twinkle.speedy.normalizeHash[ value ];

	// for sysops only
	if (['f3', 'f4'].indexOf(normalized) !== -1) {
		alert("您不能使用此工具标记CSD F3、F4,请使用“图版”工具,或取消勾选“仅标记”。");
		return;
	}

	var i;

	// analyse each db-multiple criterion to determine whether to watch the page/notify the creator
	var watchPage = false;
	if (value === 'multiple')
	{
		for (i in Twinkle.speedy.dbmultipleCriteria)
		{
			if (typeof Twinkle.speedy.dbmultipleCriteria[i] === 'string' &&
				Twinkle.getPref('watchSpeedyPages').indexOf(Twinkle.speedy.dbmultipleCriteria[i]) !== -1)
			{
				watchPage = true;
				break;
			}
		}
	}
	else
	{
		watchPage = Twinkle.getPref('watchSpeedyPages').indexOf(normalized) !== -1;
	}

	var notifyuser = false;
	if (value === 'multiple')
	{
		for (i in Twinkle.speedy.dbmultipleCriteria)
		{
			if (typeof Twinkle.speedy.dbmultipleCriteria[i] === 'string' &&
				Twinkle.getPref('notifyUserOnSpeedyDeletionNomination').indexOf(Twinkle.speedy.dbmultipleCriteria[i]) !== -1)
			{
				notifyuser = true;
				break;
			}
		}
	}
	else
	{
		notifyuser = (Twinkle.getPref('notifyUserOnSpeedyDeletionNomination').indexOf(normalized) !== -1) && e.target.form.notify.checked;
	}

	var welcomeuser = false;
	if (notifyuser)
	{
		if (value === 'multiple')
		{
			for (i in Twinkle.speedy.dbmultipleCriteria)
			{
				if (typeof Twinkle.speedy.dbmultipleCriteria[i] === 'string' &&
					Twinkle.getPref('welcomeUserOnSpeedyDeletionNotification').indexOf(Twinkle.speedy.dbmultipleCriteria[i]) !== -1)
				{
					welcomeuser = true;
					break;
				}
			}
		}
		else
		{
			welcomeuser = Twinkle.getPref('welcomeUserOnSpeedyDeletionNotification').indexOf(normalized) !== -1;
		}
	}

	var csdlog = false;
	if (Twinkle.getPref('logSpeedyNominations') && value === 'multiple')
	{
		for (i in Twinkle.speedy.dbmultipleCriteria)
		{
			if (typeof Twinkle.speedy.dbmultipleCriteria[i] === 'string' &&
				Twinkle.getPref('noLogOnSpeedyNomination').indexOf(Twinkle.speedy.dbmultipleCriteria[i]) === -1)
			{
				csdlog = true;
				break;
			}
		}
	}
	else
	{
		csdlog = Twinkle.getPref('logSpeedyNominations') && Twinkle.getPref('noLogOnSpeedyNomination').indexOf(normalized) === -1;
	}

	var params = {
		value: value,
		normalized: normalized,
		watch: watchPage,
		usertalk: notifyuser,
		welcomeuser: welcomeuser,
		lognomination: csdlog
	};

	Status.init( e.target.form );

	Wikipedia.actionCompleted.redirect = mw.config.get('wgPageName');
	Wikipedia.actionCompleted.notice = "标记完成";

	var wikipedia_page = new Wikipedia.page(mw.config.get('wgPageName'), "标记页面");
	wikipedia_page.setCallbackParameters(params);
	wikipedia_page.load(Twinkle.speedy.callbacks.user.main);
};

Twinkle.speedy.dbmultipleCriteria = [];
Twinkle.speedy.dbmultipleParameters = [];
Twinkle.speedy.callback.doMultiple = function twinklespeedyCallbackDoMultiple(e)
{
	var value = e.target.values;
	var normalized = Twinkle.speedy.normalizeHash[value];
	if (value !== 'multiple-finish')
	{
		if (Twinkle.speedy.dbmultipleCriteria.indexOf(normalized) !== -1)
		{
			alert('您已经选择了此理由,请换一个。');
		}
		else
		{
			var parameters = Twinkle.speedy.getParameters(value, normalized, Status);
			if (parameters)
			{
				for (var i in parameters) {
					if (typeof parameters[i] === 'string') {
						Twinkle.speedy.dbmultipleParameters[i] = parameters[i];
					}
				}
				Twinkle.speedy.dbmultipleCriteria.push(normalized);
			}
		}
		e.target.form.style.display = "none"; // give the user a cue that the dialog is being changed
		setTimeout(function() {
			Twinkle.speedy.initDialog(Twinkle.speedy.callback.doMultiple, false, e.target.form.parentNode);
		}, 150);
	}
	else
	{
		Twinkle.speedy.callback.evaluateUser(e);
	}
};

/*
 * vim: set noet sts=0 sw=8:
 ****************************************
 *** friendlytalkback.js: Talkback module
 ****************************************
 * Mode of invocation:     Tab ("TB")
 * Active on:              Existing user talk pages
 * Config directives in:   FriendlyConfig
 */

Twinkle.talkback = function friendlytalkback() {
	if( mw.config.get('wgNamespaceNumber') === 3 ) {
		var username = mw.config.get('wgTitle').split( '/' )[0].replace( /\"/, "\\\""); // only first part before any slashes
		$(twAddPortletLink("#", "回复", "friendly-talkback", "回复通告", "")).click(function() { Twinkle.talkback.callback(username); });
	}
};

Twinkle.talkback.callback = function friendlytalkbackCallback( uid ) {
	if( uid === mw.config.get('wgUserName') ){
		alert( '请不要回复自己。' );
		return;
	}

	var Window = new SimpleWindow( 600, 350 );
	Window.setTitle( "回复通告" );
	Window.setScriptName( "Twinkle" );
	Window.addFooterLink( "关于{{talkback}}", "Template:Talkback" );
	Window.addFooterLink( "Twinkle帮助", "WP:TW/DOC#talkback" );

	var form = new QuickForm( Twinkle.talkback.callback.evaluate );

	form.append( { type: 'radio', name: 'tbtarget',
				list: [ {
						label: '我的对话页',
						value: 'mytalk',
						checked: 'true' },
					{
						label: '其他用户的对话页',
						value: 'usertalk' },
					/*{
						label: "管理员通告板",
						value: 'an' },*/
					{
						label: '其它页面',
						value: 'other' } ],
				event: Twinkle.talkback.callback.change_target
			} );

	form.append( {
			type: 'field',
			label: '工作区',
			name: 'work_area'
		} );

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

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

	// We must init the
	var evt = document.createEvent( "Event" );
	evt.initEvent( 'change', true, true );
	result.tbtarget[0].dispatchEvent( evt );
};

Twinkle.talkback.prev_page = '';
Twinkle.talkback.prev_section = '';
Twinkle.talkback.prev_message = '';

Twinkle.talkback.callback.change_target = function friendlytagCallbackChangeTarget(e) {
	var value = e.target.values;
	var root = e.target.form;
	var old_area;

	if(root.section) {
		Twinkle.talkback.prev_section = root.section.value;
	}
	if(root.message) {
		Twinkle.talkback.prev_message = root.message.value;
	}
	if(root.page) {
		Twinkle.talkback.prev_page = root.page.value;
	}

	for( var i = 0; i < root.childNodes.length; ++i ) {
		var node = root.childNodes[i];
		if (node instanceof Element && node.getAttribute( 'name' ) === 'work_area' ) {
			old_area = node;
			break;
		}
	}
	var work_area = new QuickForm.element( { 
			type: 'field',
			label: '回复通告信息',
			name: 'work_area'
		} );

	switch( value ) {
		case 'mytalk':
			/* falls through */
		default:
			work_area.append( { 
					type:'input',
					name:'section',
					label:'小节(可选)',
					tooltip:'您对话页中留言的小节标题,留空以不加入链接。',
					value: Twinkle.talkback.prev_section
				} );
			break;
		case 'usertalk':
			work_area.append( { 
					type:'input',
					name:'page',
					label:'用户',
					tooltip:'您在其对话页上留言的用户的名字。',
					value: Twinkle.talkback.prev_page
				} );
			
			work_area.append( { 
					type:'input',
					name:'section',
					label:'小节(可选)',
					tooltip:'您留言的小节标题,留空以不加入链接。',
					value: Twinkle.talkback.prev_section
				} );
			break;
		/*case 'an':
			var noticeboard = work_area.append( {
					type: 'select',
					name: 'noticeboard',
					label: '通告板:'
				} );
			noticeboard.append( {
					type: 'option',
					label: "WP:AN(管理员通告板)",
					value: "Wikipedia:管理员通告板"
				} );
			noticeboard.append( {
					type: 'option',
					label: 'WP:ANI(Wikipedia:管理员通告板/界面的修改)',
					selected: true,
					value: "Wikipedia:管理员通告板/界面的修改"
				} );
			work_area.append( {
					type:'input',
					name:'section',
					label:'小节',
					tooltip:'AN或ANI中相关的小节。',
					value: Twinkle.talkback.prev_section
				} );
			break;*/
		case 'other':
			work_area.append( { 
					type:'input',
					name:'page',
					label:'完整页面名',
					tooltip:'您留下信息的完整页面名,如“Wikipedia talk:Twinkle”。',
					value: Twinkle.talkback.prev_page
				} );
			
			work_area.append( { 
					type:'input',
					name:'section',
					label:'小节(可选)',
					tooltip:'您留言的小节标题,留空以不加入链接。',
					value: Twinkle.talkback.prev_section
				} );
			break;
	}

	if (value !== "an") {
		work_area.append( { type:'textarea', label:'附加信息(可选):', name:'message', tooltip:'一段将会出现在模板下的附加信息,您的签名将会被自动加入。' } );
	}

	work_area = work_area.render();
	root.replaceChild( work_area, old_area );
	if (root.message) {
		root.message.value = Twinkle.talkback.prev_message;
	}
};

Twinkle.talkback.callback.evaluate = function friendlytalkbackCallbackEvaluate(e) {
	var tbtarget = e.target.getChecked( 'tbtarget' )[0];
	var page = null;
	var section = e.target.section.value;
	if( tbtarget === 'usertalk' || tbtarget === 'other' ) {
		page = e.target.page.value;
		
		if( tbtarget === 'usertalk' ) {
			if( !page ) {
				alert( '您必须指定用户名。' );
				return;
			}
		} else {
			if( !page ) {
				alert( '您必须指定页面名。' );
				return;
			}
		}
	} /*else if (tbtarget === "an") {
		page = e.target.noticeboard.value;
	}*/

	var message;
	if (e.target.message) {
		message = e.target.message.value;
	}

	SimpleWindow.setButtonsEnabled( false );
	Status.init( e.target );

	Wikipedia.actionCompleted.redirect = mw.config.get('wgPageName');
	Wikipedia.actionCompleted.notice = "回复通告完成,将在几秒后刷新";

	var talkpage = new Wikipedia.page(mw.config.get('wgPageName'), "添加回复通告");
	var tbPageName = (tbtarget === 'mytalk') ? mw.config.get('wgUserName') : page;

	var text;
	/*if ( tbtarget === "an" ) {
		text = "\n== " + Twinkle.getFriendlyPref('adminNoticeHeading') + " ==\n{{subst:ANI-notice|thread=";
		text += section + "|noticeboard=" + tbPageName + "}} ~~~~";

		talkpage.setEditSummary("Notice of AN/ANI discussion" + Twinkle.getPref('summaryAd'));
	} else {*/
		//clean talkback heading: strip section header markers, were erroneously suggested in the documentation
		text = '\n==' + Twinkle.getFriendlyPref('talkbackHeading').replace(/^\s*=+\s*(.*?)\s*=+$\s*/, "$1") + '==\n{{talkback|';
		text += tbPageName;

		if( section ) {
			text += '|' + section;
		}

		text += '|ts=~~~~~}}';

		if( message ) {
			text += '\n' + message + '--~~~~';
		} else if( Twinkle.getFriendlyPref('insertTalkbackSignature') ) {
			text += '\n~~~~';
		}

		talkpage.setEditSummary("回复通告([[" + (tbtarget === 'other' ? '' : 'User talk:') + tbPageName +
			(section ? ('#' + section) : '') + "]])" + Twinkle.getPref('summaryAd'));
	/*}*/

	talkpage.setAppendText(text);
	talkpage.setCreateOption('recreate');
	talkpage.setMinorEdit(Twinkle.getFriendlyPref('markTalkbackAsMinor'));
	talkpage.setFollowRedirect(true);
	talkpage.append();
};

/*
 * vim: set noet sts=0 sw=8:
 ****************************************
 *** twinklefluff.js: Revert/rollback module
 ****************************************
 * Mode of invocation:     Links on history, contributions, and diff pages
 * Active on:              Diff pages, history pages, contributions pages
 * Config directives in:   TwinkleConfig
 */

/**
 Twinklefluff revert and antivandalism utility
 */

Twinkle.fluff = {
	auto: function() {
		if( parseInt( QueryString.get('oldid'), 10) !== mw.config.get('wgCurRevisionId') ) {
			// not latest revision
			alert("无法回退,页面在此期间已被修改。");
			return;
		}

		var ntitle = getElementsByClassName( document.getElementById('bodyContent'), 'td' , 'diff-ntitle' )[0];
		vandal = ntitle.getElementsByTagName('a')[3].firstChild.nodeValue;

		Twinkle.fluff.revert( QueryString.get( 'twinklerevert' ), vandal, true );
	},
	normal: function() {

		var spanTag = function( color, content ) {
			var span = document.createElement( 'span' );
			span.style.color = color;
			span.appendChild( document.createTextNode( content ) );
			return span;
		};

		if( mw.config.get('wgNamespaceNumber') === -1 && mw.config.get('wgCanonicalSpecialPageName') === "Contributions" ) {
			//Get the username these contributions are for
			username = decodeURIComponent(/wiki\/Special:Log\/(.+)$/.exec($('div#contentSub a[title^="Special:Log"]').last().attr("href").replace(/_/g, "%20"))[1]);
			if( Twinkle.getPref('showRollbackLinks').indexOf('contribs') !== -1 || 
				( mw.config.get('wgUserName') !== username && Twinkle.getPref('showRollbackLinks').indexOf('others') !== -1 ) || 
				( mw.config.get('wgUserName') === username && Twinkle.getPref('showRollbackLinks').indexOf('mine') !== -1 ) ) {
				var list = $("div#bodyContent ul li:has(span.mw-uctop)");

				var revNode = document.createElement('strong');
				var revLink = document.createElement('a');
				revLink.appendChild( spanTag( 'Black', '[' ) );
				revLink.appendChild( spanTag( 'SteelBlue', '回退' ) );
				revLink.appendChild( spanTag( 'Black', ']' ) );
				revNode.appendChild(revLink);

				var revVandNode = document.createElement('strong');
				var revVandLink = document.createElement('a');
				revVandLink.appendChild( spanTag( 'Black', '[' ) );
				revVandLink.appendChild( spanTag( 'Red', '破坏' ) );
				revVandLink.appendChild( spanTag( 'Black', ']' ) );
				revVandNode.appendChild(revVandLink);

				list.each(function(key, current) {
					var href = $(current).children("a:eq(1)").attr("href");
					current.appendChild( document.createTextNode(' ') );
					var tmpNode = revNode.cloneNode( true );
					tmpNode.firstChild.setAttribute( 'href', href + '&' + QueryString.create( { 'twinklerevert': 'norm' } ) );
					current.appendChild( tmpNode );
					current.appendChild( document.createTextNode(' ') );
					tmpNode = revVandNode.cloneNode( true );
					tmpNode.firstChild.setAttribute( 'href', href + '&' + QueryString.create( { 'twinklerevert': 'vand' } ) );
					current.appendChild( tmpNode );
				});
			}
		} else {
                        
			if( mw.config.get('wgCanonicalSpecialPageName') === "Special:Undelete" ) {
				//You can't rollback deleted pages!
				return;
			}

			var body = document.getElementById('bodyContent');

			var firstRev = $("div.firstrevisionheader").length;
			if( firstRev ) {
				// we have first revision here, nothing to do.
				return;
			}

			var otitle, ntitle;
			try {
				var otitle1 = document.getElementById('mw-diff-otitle1'); 
				var ntitle1 = document.getElementById('mw-diff-ntitle1'); 
				if (!otitle1 || !ntitle1) {
					return;
				}
				otitle = otitle1.parentNode;
				ntitle = ntitle1.parentNode;
			} catch( e ) {
				// no old, nor new title, nothing to do really, return;
				return;
			}

			var old_rev_url = $("div#mw-diff-otitle1 strong a").attr("href");

			// Lets first add a [edit this revision] link
			var query = new QueryString( old_rev_url.split( '?', 2 )[1] );

			var oldrev = query.get('oldid');

			var revertToRevision = document.createElement('div');
			revertToRevision.setAttribute( 'id', 'tw-revert-to-orevision' );
			revertToRevision.style.fontWeight = 'bold';

			var revertToRevisionLink = revertToRevision.appendChild( document.createElement('a') );
			revertToRevisionLink.href = "#";
			$(revertToRevisionLink).click(function(){
				Twinkle.fluff.revertToRevision(oldrev);
			});
			revertToRevisionLink.appendChild( spanTag( 'Black', '[' ) );
			revertToRevisionLink.appendChild( spanTag( 'SaddleBrown', '恢复此版本' ) );
			revertToRevisionLink.appendChild( spanTag( 'Black', ']' ) );

			otitle.insertBefore( revertToRevision, otitle.firstChild );

			if( document.getElementById('differences-nextlink') ) {
				// Not latest revision
				curVersion = false;

				var new_rev_url = $("div#mw-diff-ntitle1 strong a").attr("href");
				query = new QueryString( new_rev_url.split( '?', 2 )[1] );
				var newrev = query.get('oldid');
				revertToRevision = document.createElement('div');
				revertToRevision.setAttribute( 'id', 'tw-revert-to-nrevision' );
				revertToRevision.style.fontWeight = 'bold';
				revertToRevisionLink = revertToRevision.appendChild( document.createElement('a') );
				revertToRevisionLink.href = "#";
				$(revertToRevisionLink).click(function(){
					Twinkle.fluff.revertToRevision(newrev);
				});
				revertToRevisionLink.appendChild( spanTag( 'Black', '[' ) );
				revertToRevisionLink.appendChild( spanTag( 'SaddleBrown', '恢复此版本' ) );
				revertToRevisionLink.appendChild( spanTag( 'Black', ']' ) );
				ntitle.insertBefore( revertToRevision, ntitle.firstChild );

				return;
			}
			if( Twinkle.getPref('showRollbackLinks').indexOf('diff') != -1 ) {
				var vandal = $("#mw-diff-ntitle2 a").first().text();

				var revertNode = document.createElement('div');
				revertNode.setAttribute( 'id', 'tw-revert' );

				var agfNode = document.createElement('strong');
				var vandNode = document.createElement('strong');
				var normNode = document.createElement('strong');

				var agfLink = document.createElement('a');
				var vandLink = document.createElement('a');
				var normLink = document.createElement('a');

				agfLink.href = "#"; 
				vandLink.href = "#"; 
				normLink.href = "#"; 
				$(agfLink).click(function(){
					Twinkle.fluff.revert('agf', vandal);
				});
				$(vandLink).click(function(){
					Twinkle.fluff.revert('vand', vandal);
				});
				$(normLink).click(function(){
					Twinkle.fluff.revert('norm', vandal);
				});

				agfLink.appendChild( spanTag( 'Black', '[' ) );
				agfLink.appendChild( spanTag( 'DarkOliveGreen', '回退(AGF)' ) );
				agfLink.appendChild( spanTag( 'Black', ']' ) );

				vandLink.appendChild( spanTag( 'Black', '[' ) );
				vandLink.appendChild( spanTag( 'Red', '回退(破坏)' ) );
				vandLink.appendChild( spanTag( 'Black', ']' ) );

				normLink.appendChild( spanTag( 'Black', '[' ) );
				normLink.appendChild( spanTag( 'SteelBlue', '回退' ) );
				normLink.appendChild( spanTag( 'Black', ']' ) );

				agfNode.appendChild(agfLink);
				vandNode.appendChild(vandLink);
				normNode.appendChild(normLink);

				revertNode.appendChild( agfNode );
				revertNode.appendChild( document.createTextNode(' || ') );
				revertNode.appendChild( normNode );
				revertNode.appendChild( document.createTextNode(' || ') );
				revertNode.appendChild( vandNode );

				ntitle.insertBefore( revertNode, ntitle.firstChild );
			}
		}
	}
};

Twinkle.fluff.revert = function revertPage( type, vandal, autoRevert, rev, page ) {

	var pagename = page || mw.config.get('wgPageName');
	var revid = rev || mw.config.get('wgCurRevisionId');

	Status.init( document.getElementById('bodyContent') );
	var params = {
		type: type,
		user: vandal,
		pagename: pagename,
		revid: revid,
		autoRevert: !!autoRevert
	};
	var query = {
		'action': 'query',
		'prop': ['info', 'revisions'],
		'titles': pagename,
		'rvlimit': 50, // max possible
		'rvprop': [ 'ids', 'timestamp', 'user', 'comment' ],
		'intoken': 'edit'
	};
	var wikipedia_api = new Wikipedia.api( '抓取较早修订版本信息', query, Twinkle.fluff.callbacks.main );
	wikipedia_api.params = params;
	wikipedia_api.post();
};

Twinkle.fluff.revertToRevision = function revertToRevision( oldrev ) {

	Status.init( document.getElementById('bodyContent') );

	var query = {
		'action': 'query',
		'prop': ['info',  'revisions'],
		'titles': mw.config.get('wgPageName'),
		'rvlimit': 1,
		'rvstartid': oldrev,
		'rvprop': [ 'ids', 'timestamp', 'user', 'comment' ],
		'intoken': 'edit',
		'format': 'xml'
	};
	var wikipedia_api = new Wikipedia.api( '抓取较早修订版本信息', query, Twinkle.fluff.callbacks.toRevision.main );
	wikipedia_api.params = { rev: oldrev };
	wikipedia_api.post();
};

Twinkle.fluff.userIpLink = function( user ) {
	return (isIPAddress(user) ? "[[Special:Contributions/" : "[[User:" ) + user + "|" + user + "]]";
};

Twinkle.fluff.callbacks = {
	toRevision: {
		main: function( self ) {
			var xmlDoc = self.responseXML;

			var lastrevid = parseInt( $(xmlDoc).find('page').attr('lastrevid'), 10);
			var touched = $(xmlDoc).find('page').attr('touched');
			var starttimestamp = $(xmlDoc).find('page').attr('starttimestamp');
			var edittoken = $(xmlDoc).find('page').attr('edittoken');
			var revertToRevID = $(xmlDoc).find('rev').attr('revid');
			var revertToUser = $(xmlDoc).find('rev').attr('user');

			if (revertToRevID !== self.params.rev) {
				self.statitem.error( '抓取到的修订版本与请求的修订版本不符,取消。' );
				return;
			}

			var optional_summary = prompt( "请输入回退理由:", "" );
			if (optional_summary === null)
			{
				self.statelem.error( '由用户取消。' );
				return;
			}
			var summary = "回退到由" + revertToUser + "做出的修订版本" + revertToRevID + (optional_summary ? ":" + optional_summary : '') +
				Twinkle.getPref('summaryAd');
		
			var query = { 
				'action': 'edit',
				'title': mw.config.get('wgPageName'),
				'summary': summary,
				'token': edittoken,
				'undo': lastrevid,
				'undoafter': revertToRevID,
				'basetimestamp': touched,
				'starttimestamp': starttimestamp,
				'watchlist': Twinkle.getPref('watchRevertedPages').indexOf( self.params.type ) !== -1 ? 'watch' : undefined,
				'minor': Twinkle.getPref('markRevertedPagesAsMinor').indexOf( self.params.type ) !== -1  ? true : undefined
			};

			Wikipedia.actionCompleted.redirect = mw.config.get('wgPageName');
			Wikipedia.actionCompleted.notice = "修订版本完成";

			var wikipedia_api = new Wikipedia.api( '保存回退内容', query, null/*Twinkle.fluff.callbacks.toRevision.complete*/, self.statelem);
			wikipedia_api.params = self.params;
			wikipedia_api.post();

		},
		complete: function (self) {
		}
	},
	main: function( self ) {
		var xmlDoc = self.responseXML;

		var lastrevid = parseInt( $(xmlDoc).find('page').attr('lastrevid'), 10);
		var touched = $(xmlDoc).find('page').attr('touched');
		var starttimestamp = $(xmlDoc).find('page').attr('starttimestamp');
		var edittoken = $(xmlDoc).find('page').attr('edittoken');
		var lastuser = $(xmlDoc).find('rev').attr('user');

		var revs = $(xmlDoc).find('rev');

		if( revs.length < 1 ) {
			self.statelem.error( '没有其它修订版本,无法回退' );
			return;
		}
		var top = revs[0];
		if( lastrevid < self.params.revid ) {
			Status.error( '错误', [ '从服务器取得的最新修订版本ID ', htmlNode( 'strong', lastrevid ), ' 小于目前所显示的修订版本ID。这可能意味着当前修订版本已被删除、服务器延迟、或抓取到了坏掉的信息。取消。' ] );
			return;
		}
		var index = 1;
		if( self.params.revid !== lastrevid  ) {
			Status.warn( '警告', [ '最新修订版本 ', htmlNode( 'strong', lastrevid ), ' 与我们的修订版本 ', htmlNode( 'strong', self.params.revid ) , '不等'] );
			if( lastuser === self.params.user ) {
				switch( self.params.type ) {
				case 'vand':
					Status.info( '信息', [ '最新修订版本由 ', htmlNode( 'strong', self.params.user ) , ' 做出,因我们假定破坏,继续回退操作。' ]);
					break;
				case 'agf':
					Status.warn( '警告', [ '最新修订版本由 ', htmlNode( 'strong', self.params.user ) , ' 做出,因我们假定善意,取消回退操作,因为问题可能已被修复。' ]);
					return;
				default:
					Status.warn( '提示', [ '最新修订版本由 ', htmlNode( 'strong', self.params.user ) , ' 做出,但我们还是不回退了。' ] );
					return;
				}
			}
			else if(self.params.type === 'vand' && 
					Twinkle.fluff.whiteList.indexOf( top.getAttribute( 'user' ) ) !== -1 && revs.length > 1 &&
					revs[1].getAttribute( 'pageId' ) === self.params.revid) {
				Status.info( '信息', [ '最新修订版本由 ', htmlNode( 'strong', lastuser ), ',一个可信的机器人做出,之前的版本被认为是破坏,继续回退操作。' ] );
				index = 2;
			} else {
				Status.error( '错误', [ '最新修订版本由 ', htmlNode( 'strong', lastuser ), ' 做出,所以这个修订版本可能已经被回退了,取消回退操作。'] );
				return;
			}

		}

		if( Twinkle.fluff.whiteList.indexOf( self.params.user ) !== -1  ) {
			switch( self.params.type ) {
			case 'vand':
				Status.info( '信息', [ '将对 ', htmlNode( 'strong', self.params.user ), ' 执行破坏回退,这是一个可信的机器人,我们假定您要回退前一个修订版本。' ] );
				index = 2;
				vandal = revs[1].getAttribute( 'user' );
				self.params.user = revs[1].getAttribute( 'user' );
				break;
			case 'agf':
				Status.warn( '提示', [ '将对 ', htmlNode( 'strong', self.params.user ), ' 执行善意回退,这是一个可信的机器人,取消回退操作。' ] );
				return;
			case 'norm':
				/* falls through */
			default:
				var cont = confirm( '选择了常规回退,但最新修改是由一个可信的机器人(' + self.params.user + ')做出的。您是否想回退前一个修订版本?' );
				if( cont ) {
					Status.info( '信息', [ '将对 ', htmlNode( 'strong', self.params.user ), ' 执行常规回退,这是一个可信的机器人,基于确认,我们将回退前一个修订版本。' ] );
					index = 2;
					self.params.user = revs[1].getAttribute( 'user' );
				} else {
					Status.warn( '提示', [ '将对 ', htmlNode( 'strong', self.params.user ), ' 执行常规回退,这是一个可信的机器人,基于确认,我们仍将回退这个修订版本。' ] );
				}
				break;
			}
		}
		var found = false;
		var count = 0;

		for( var i = index; i < revs.length; ++i ) {
			++count;
			if( revs[i].getAttribute( 'user' ) != self.params.user ) {
				found = i;
				break;
			}
		}

		if( ! found ) {
			self.statelem.error( [ '未找到之前的修订版本,可能 ', htmlNode( 'strong', self.params.user ), ' 是唯一贡献者,或这个用户连续做出了超过 ' + Twinkle.getPref('revertMaxRevisions') + ' 次编辑。' ] );
			return;
		}

		if( ! count ) {
			Status.error( '错误', "我们将要回退0个修订版本,这没有意义,所以取消回退操作。可能是因为这个修订版本已经被回退,但修订版本ID仍是一样的。" );
			return;
		}

		var good_revision = revs[ found ];
		var userHasAlreadyConfirmedAction = false;
		if (self.params.type !== 'vand' && count > 1) {
			if ( !confirm( self.params.user + ' 连续做出了 ' + count + ' 次编辑,是否要回退所有这些?') ) {
				Status.info( '提示', '用户取消操作' );
				return;
			}
			userHasAlreadyConfirmedAction = true;
		}

		self.params.count = count;

		self.params.goodid = good_revision.getAttribute( 'revid' );
		self.params.gooduser = good_revision.getAttribute( 'user' );

		self.statelem.status( [ htmlNode( 'strong', count ), ' 个修订版本之前由 ', htmlNode( 'strong', self.params.gooduser ), ' 做出的修订版本 ', htmlNode( 'strong', self.params.goodid ) ] );

		var summary, extra_summary, userstr, gooduserstr;
		switch( self.params.type ) {
		case 'agf':
			extra_summary = prompt( "可选的编辑摘要:", "" );
			if (extra_summary === null)
			{
				self.statelem.error( '用户取消操作。' );
				return;
			}
			userHasAlreadyConfirmedAction = true;

			userstr = self.params.user;
			summary = "回退[[Special:Contributions/" + userstr + "|" + userstr + "]] ([[User talk:" + 
				userstr + "|讨论]])做出的出于[[WP:AGF|善意]]的编辑" + Twinkle.fluff.formatSummaryPostfix(extra_summary) + Twinkle.getPref('summaryAd');
			break;

		case 'vand':

			userstr = self.params.user;
			gooduserstr = self.params.gooduser;
			summary = "回退[[Special:Contributions/" + userstr + "|" + userstr + "]] ([[User talk:" +
				userstr + "|讨论]])做出的被认为是[[WP:VAND|破坏]]的 " + self.params.count + " 次编辑,到由" +
				gooduserstr + "做出的前一个修订版本。 "  + Twinkle.getPref('summaryAd');
			break;

		case 'norm':
			/* falls through */
		default:
			if( Twinkle.getPref('offerReasonOnNormalRevert') ) {
				extra_summary = prompt( "可选的编辑摘要:", "" );
				if (extra_summary === null)
				{
					self.statelem.error( '用户取消操作。' );
					return;
				}
				userHasAlreadyConfirmedAction = true;
			}

			userstr = self.params.user;
			summary = "回退[[Special:Contributions/" + userstr + "|" + userstr + "]] ([[User talk:" +
				userstr + "|讨论]])做出的 " + self.params.count + " 次编辑" + Twinkle.fluff.formatSummaryPostfix(extra_summary) +
				Twinkle.getPref('summaryAd');
			break;
		}

		if (Twinkle.getPref('confirmOnFluff') && !userHasAlreadyConfirmedAction && !confirm("回退页面:您确定吗?")) {
			self.statelem.error( '用户取消操作。' );
			return;
		}

		var query;
		if( (!self.params.autoRevert || Twinkle.getPref('openTalkPageOnAutoRevert')) && 
				Twinkle.getPref('openTalkPage').indexOf( self.params.type ) !== -1 &&
				mw.config.get('wgUserName') !== self.params.user ) {
			Status.info( '信息', [ '打开用户 ', htmlNode( 'strong', self.params.user ), ' 的对话页' ] );
			
			query = {
				'title': 'User talk:' + self.params.user,
				'action': 'edit',
				'preview': 'yes',
				'vanarticle': self.params.pagename.replace(/_/g, ' '),
				'vanarticlerevid': self.params.revid,
				'vanarticlegoodrevid': self.params.goodid,
				'type': self.params.type,
				'count': self.params.count
			};

			switch( Twinkle.getPref('userTalkPageMode') ) {
			case 'tab':
				window.open( mw.config.get('wgServer') + mw.config.get('wgScriptPath') + '/index.php?' + QueryString.create( query ), '_tab' );
				break;
			case 'blank':
				window.open( mw.config.get('wgServer') + mw.config.get('wgScriptPath') + '/index.php?' + QueryString.create( query ), '_blank', 'location=no,toolbar=no,status=no,directories=no,scrollbars=yes,width=1200,height=800' );
				break;
			case 'window':
				/* falls through */
			default:
				window.open( mw.config.get('wgServer') + mw.config.get('wgScriptPath') + '/index.php?' + QueryString.create( query ), 'twinklewarnwindow', 'location=no,toolbar=no,status=no,directories=no,scrollbars=yes,width=1200,height=800' );
				break;
			}
		}
		
		query = {
			'action': 'edit',
			'title': self.params.pagename,
			'summary': summary,
			'token': edittoken,
			'undo': lastrevid,
			'undoafter': self.params.goodid,
			'basetimestamp': touched,
			'starttimestamp': starttimestamp,
			'watchlist' :  Twinkle.getPref('watchRevertedPages').indexOf( self.params.type ) != -1 ? 'watch' : undefined,
			'minor': Twinkle.getPref('markRevertedPagesAsMinor').indexOf( self.params.type ) != -1  ? true : undefined
		};

		Wikipedia.actionCompleted.redirect = self.params.pagename;
		Wikipedia.actionCompleted.notice = "回退完成";

		var wikipedia_api = new Wikipedia.api( '保存回退内容', query, Twinkle.fluff.callbacks.complete, self.statelem);
		wikipedia_api.params = self.params;
		wikipedia_api.post();

	},
	complete: function (self) {
		self.statelem.info("完成");
	}
};

Twinkle.fluff.formatSummaryPostfix = function(stringToAdd) {
	if (stringToAdd) {
		stringToAdd = ':' + stringToAdd.toUpperCaseFirstChar();
		if (stringToAdd.search(/[。?!]$/) == -1) {
			stringToAdd = stringToAdd + '。';
		}
		return stringToAdd;
	}
	else {
		return '。';
	}
};

Twinkle.fluff.init = function twinklefluffinit() {
	if (twinkleUserAuthorized)
	{
		// a list of usernames, usually only bots, that vandalism revert is jumped over, that is
		// if vandalism revert was chosen on such username, then it's target is on the revision before.
		// This is for handeling quick bots that makes edits seconds after the original edit is made.
		// This only affect vandalism rollback, for good faith rollback, it will stop, indicating a bot 
		// has no faith, and for normal rollback, it will rollback that edit.
		Twinkle.fluff.whiteList = [
			/*'AnomieBOT',
			'ClueBot NG',
			'SineBot'*/
		];

		if ( QueryString.exists( 'twinklerevert' ) ) {
			Twinkle.fluff.auto();
		} else {
			Twinkle.fluff.normal();
		}
	}
};

/*
 * vim: set noet sts=0 sw=8:
 ****************************************
 *** friendlywelcome.js: Welcome module
 ****************************************
 * Mode of invocation:     Tab ("Wel"), or from links on diff pages
 * Active on:              Existing user talk pages, diff pages
 * Config directives in:   FriendlyConfig
 */

Twinkle.welcome = function friendlywelcome() {
	if( QueryString.exists( 'friendlywelcome' ) ) {
		if( QueryString.get( 'friendlywelcome' ) === 'auto' ) {
			Twinkle.welcome.auto();
		} else {
			Twinkle.welcome.semiauto();
		}
	} else {
		Twinkle.welcome.normal();
	}
};

Twinkle.welcome.auto = function() {
	if( QueryString.get( 'action' ) !== 'edit' ) {
		// userpage not empty, aborting auto-welcome
		return;
	}

	Twinkle.welcome.welcomeUser();
};

Twinkle.welcome.semiauto = function() {
	Twinkle.welcome.callback( mw.config.get( 'wgTitle' ).split( '/' )[0].replace( /\"/, "\\\"") );
};

Twinkle.welcome.normal = function() {
	if( QueryString.exists( 'diff' ) ) {
		// check whether the contributors' talk pages exist yet
		var $oList = $("div#mw-diff-otitle2 span.mw-usertoollinks a.new:contains(talk)").first();
		var $nList = $("div#mw-diff-ntitle2 span.mw-usertoollinks a.new:contains(talk)").first();

		if( $oList.length > 0 || $nList.length > 0 ) {
			var spanTag = function( color, content ) {
				var span = document.createElement( 'span' );
				span.style.color = color;
				span.appendChild( document.createTextNode( content ) );
				return span;
			};

			var welcomeNode = document.createElement('strong');
			var welcomeLink = document.createElement('a');
			welcomeLink.appendChild( spanTag( 'Black', '[' ) );
			welcomeLink.appendChild( spanTag( 'Goldenrod', '欢迎' ) );
			welcomeLink.appendChild( spanTag( 'Black', ']' ) );
			welcomeNode.appendChild(welcomeLink);

			if( $oList.length > 0 ) {
				var oHref = $oList.attr("href");

				var oWelcomeNode = welcomeNode.cloneNode( true );
				oWelcomeNode.firstChild.setAttribute( 'href', oHref + '&' + QueryString.create( { 'friendlywelcome': Twinkle.getFriendlyPref('quickWelcomeMode')==='auto'?'auto':'norm' } ) + '&' + QueryString.create( { 'vanarticle': mw.config.get( 'wgPageName' ).replace(/_/g, ' ') } ) );
				$oList[0].parentNode.parentNode.appendChild( document.createTextNode( ' ' ) );
				$oList[0].parentNode.parentNode.appendChild( oWelcomeNode );
			}

			if( $nList.length > 0 ) {
				var nHref = $nList.attr("href");

				var nWelcomeNode = welcomeNode.cloneNode( true );
				nWelcomeNode.firstChild.setAttribute( 'href', nHref + '&' + QueryString.create( { 'friendlywelcome': Twinkle.getFriendlyPref('quickWelcomeMode')==='auto'?'auto':'norm' } ) + '&' + QueryString.create( { 'vanarticle': mw.config.get( 'wgPageName' ).replace(/_/g, ' ') } ) );
				$nList[0].parentNode.parentNode.appendChild( document.createTextNode( ' ' ) );
				$nList[0].parentNode.parentNode.appendChild( nWelcomeNode );
			}
		}
	}
	if( mw.config.get( 'wgNamespaceNumber' ) === 3 ) {
		var username = mw.config.get( 'wgTitle' ).split( '/' )[0].replace( /\"/, "\\\""); // only first part before any slashes
		$(twAddPortletLink("#", "欢迎", "friendly-welcome", "欢迎用户", "")).click(function() { Twinkle.welcome.callback(username); });
	}
};

Twinkle.welcome.welcomeUser = function welcomeUser() {
	Status.init( document.getElementById('bodyContent') );

	var params = {
		value: Twinkle.getFriendlyPref('quickWelcomeTemplate'),
		article: QueryString.exists( 'vanarticle' ) ? QueryString.get( 'vanarticle' ) : '',
		mode: 'auto'
	};

	Wikipedia.actionCompleted.redirect = mw.config.get('wgPageName');
	Wikipedia.actionCompleted.notice = "欢迎完成,将在几秒钟后刷新";

	var wikipedia_page = new Wikipedia.page(mw.config.get('wgPageName'), "用户讨论页修改");
	wikipedia_page.setFollowRedirect(true);
	wikipedia_page.setCallbackParameters(params);
	wikipedia_page.load(Twinkle.welcome.callbacks.main);
};

Twinkle.welcome.callback = function friendlywelcomeCallback( uid ) {
	if( uid === mw.config.get('wgUserName') ){
		alert( '我们非常、非常欢迎你的到来!' );
		return;
	}
	
	var Window = new SimpleWindow( 600, 400 );
	Window.setTitle( "欢迎用户" );
	Window.setScriptName( "Twinkle" );
	//Window.addFooterLink( "Welcoming Committee", "WP:WC" );
	Window.addFooterLink( "Twinkle帮助", "WP:TW/DOC#welcome" );

	var form = new QuickForm( Twinkle.welcome.callback.evaluate, 'change' );

	form.append( {
			type: 'input',
			name: 'article',
			label: '条目名(如模板支持)',
			value:( QueryString.exists( 'vanarticle' ) ? QueryString.get( 'vanarticle' ) : '' ),
			tooltip: '如果模板支持,您可在此处加入一个条目名。支持的模板已用星号标记出来。',
			event: function( event ) {
				event.stopPropagation();
			}
		} );

	form.append( { type:'header', label:'简单模板' } );
	form.append( { type: 'radio', name: 'simple', list: Twinkle.welcome.standardList } );

	if( Twinkle.getFriendlyPref('customWelcomeList').length ) {
		form.append( { type:'header', label:'自定义模板' } );
		form.append( { type: 'radio', name: 'custom', list: Twinkle.getFriendlyPref('customWelcomeList') } );
	}

	/*form.append( { type:'header', label:'Welcoming committee templates' } );
	form.append( { type: 'radio', name: 'welcomingCommittee', list: Twinkle.welcome.welcomingCommitteeList } );*/

	form.append( { type:'header', label:'问题用户模板' } );
	form.append( { type: 'radio', name: 'problem', list: Twinkle.welcome.problemList } );

	form.append( { type:'header', label:'匿名用户模板' } );
	form.append( { type: 'radio', name: 'anonymous', list: Twinkle.welcome.anonymousList } );

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

Twinkle.welcome.standardList = [
	{
		label: '{{Welcome}}: 标准欢迎模板*',
		value: 'Welcome'
	},
	{
		label: '{{Welcome plain}}: 纯文本欢迎模板',
		value: 'Welcome plain'
	}
];

/*Twinkle.welcome.welcomingCommitteeList = [
	{ 
		label: '{{Wel}}: similar to {{Welcome}}, but automatically identifies anonymous and registered users*',
		value: 'Wel',
		tooltip: 'This template checks whether the username contains any letters. If there are any, {{Welcome-reg}} will be shown. If there are none, {{Welcome-anon}} will be shown.'
	},
	{ 
		label: '{{W-basic}}: standard template, similar to {{Welcome}} with additional options',
		value: 'W-basic',
		tooltip: 'This template is similar to {{Welcome}} but supports many different options.  Includes a signature.'
	},
	{ 
		label: '{{W-shout}}: extroverted message with bold advice',
		value: 'W-shout',
		tooltip: 'This template is similar to {{WelcomeShout}} but supports many different options.  Includes a signature.'
	},
	{ 
		label: '{{W-short}}: concise; won\'t overwhelm',
		value: 'W-short||',
		tooltip: 'This template is similar to {{Welcomeshort}} but supports many different options.  Includes a signature.'
	},
	{ 
		label: '{{W-link}}: shortest greeting, links to Welcoming committee\'s greetings page',
		value: 'W-link',
		tooltip: 'This template is similar to {{Welcom}} but supports many different options.  Includes a signature.'
	},
	{ 
		label: '{{W-graphical}}: graphical menu format to ease transition from the graphic-heavy web',
		value: 'W-graphical',
		tooltip: 'This template is similar to {{Welcomeg}} but has fewer links.  Supports many different options.  Includes a signature.'
	},
	{ 
		label: '{{W-screen}}: graphical; designed to fit the size of the user\'s screen',
		value: 'W-screen',
		tooltip: 'This template is a nice graphical welcome with many different options.  Includes a signature.'
	}
];*/

Twinkle.welcome.problemList = [
	{
		label: '{{Firstarticle}}: 用户的第一篇条目不符合方针*',
		value: 'Firstarticle'
	},
	{
		label: '{{Welcomevandal}}: 用户的初始动作像是破坏',
		value: 'Welcomevandal'
	},
	{
		label: '{{Welcomeipvandal}}: 匿名用户的初始动作像是破坏',
		value: 'Welcomeipvandal'
	}
];

Twinkle.welcome.anonymousList = [
	{
		label: '{{Welcome-anon}}: 匿名用户',
		value: 'Welcome-anon'
	}
];

// Set to true if template does not already have heading
Twinkle.welcome.headingHash = {
	'Welcome': false,
	'Welcome plain': false,
	'Firstarticle': false,
	'Welcomevandal': false,
	'Welcomeipvandal': false,
	'Welcome-anon': false,
};

// Set to true if template already has signature
Twinkle.welcome.signatureHash = {
	'Welcome': true,
	'Welcome plain': true,
	'Firstarticle': true,
	'Welcomevandal': true,
	'Welcomeipvandal': true,
	'Welcome-anon': true,
};

/* Set to true if template supports article
 * name from art template parameter 
 */
Twinkle.welcome.artHash = {
	'Welcome': true,
	'Welcome plain': false,
	'Firstarticle': true,
	'Welcomevandal': false,
	'Welcomeipvandal': false,
	'Welcome-anon': false,
};

/* Set to true if template supports article
 * name from vanarticle template parameter 
 */
Twinkle.welcome.vandalHash = {
	'Welcome': false,
	'Welcome plain': false,
	'Firstarticle': false,
	'Welcomevandal': false,
	'Welcomeipvandal': false,
	'Welcome-anon': false,
};

Twinkle.welcome.callbacks = {
	main: function( pageobj ) {
		var params = pageobj.getCallbackParameters();
		var oldText = pageobj.getPageText();
		
		// abort if mode is auto and form is not empty
		if( pageobj.exists() && params.mode === 'auto' ) {
			Status.info( '警告', '用户对话页非空,取消自动欢迎' );
			Wikipedia.actionCompleted.event();
			return;
		}
		
		var text = '';
		Status.info( '信息', '将添加欢迎模板到对话页' +
			( Twinkle.getFriendlyPref('topWelcomes') ? '顶' : '底' ) +
			'部。' );
		if( !Twinkle.getFriendlyPref('topWelcomes') ) {
			text += oldText + '\n';
		}
		
		if( Twinkle.welcome.headingHash[ params.value ] && Twinkle.getFriendlyPref('insertHeadings') ) {
			Status.info( '信息', '将创建小节标题' );
			// strip section header markers from pref, to preserve backwards compatibility
			text += "== " + Twinkle.getFriendlyPref('welcomeHeading').replace(/^\s*=+\s*(.*?)\s*=+$\s*/, "$1") + " ==\n";
		}
		
		Status.info( '信息', '将替换引用{{' + params.value + '}}欢迎模板' );
		text += '{{subst:' + params.value;
		
		if( Twinkle.welcome.artHash[ params.value ] ) {
			if( Twinkle.getFriendlyPref('insertUsername') && params.value.substring(2,0) !== 'W-' ) {
				Status.info( '信息', '将添加您的用户名到模板' );
				text += '|' + mw.config.get('wgUserName');
			}
			
			if( params.article ) {
				Status.info( '信息', '将添加条目链接到模板' );
				text += '|art=' + params.article;
			}
		} else if( Twinkle.welcome.vandalHash[ params.value ] ) {
			if( params.article ) {
				Status.info( '信息', '将添加条目链接到模板' );
			}
			text += '|' + params.article;
			
			if( Twinkle.getFriendlyPref('insertUsername') ) {
				Status.info( '信息', '将添加您的用户名到模板' );
				text += '|' + mw.config.get('wgUserName');
			}
		} else if( Twinkle.getFriendlyPref('insertUsername') ) {
			Status.info( '信息', '将添加您的用户名到模板' );
			text += '|' + mw.config.get('wgUserName');
		} 
		
		text += '}}';
		
		if( !Twinkle.welcome.signatureHash[ params.value ] && Twinkle.getFriendlyPref('insertSignature') ) {
			Status.info( '信息', '将添加您的签名' );
			text += ' \n~~~~';
		}
		
		if( Twinkle.getFriendlyPref('topWelcomes') ) {
			text += '\n\n' + oldText;
		}
 
		var summaryText = "添加" + ( Twinkle.getFriendlyPref('maskTemplateInSummary') ? '欢迎' : ( '{{[[Template:' + params.value + '|' + params.value + ']]}}' ) ) +
			"模板到用户对话页";
		pageobj.setPageText(text);
		pageobj.setEditSummary(summaryText + Twinkle.getPref('summaryAd'));
		pageobj.setMinorEdit(Twinkle.getFriendlyPref('markWelcomesAsMinor'));
		pageobj.setWatchlist(Twinkle.getFriendlyPref('watchWelcomes'));
		pageobj.setCreateOption('recreate');
		pageobj.save();
	}
};

Twinkle.welcome.callback.evaluate = function friendlywelcomeCallbackEvaluate(e) {
	// Ignore if a change to the text field triggered this event
	if( e.target.name === 'article' ) {
		return;
	}
	
	var params = {
		value: e.target.values,
		article: e.target.form.article.value,
		mode: 'manual'
	};

	SimpleWindow.setButtonsEnabled( false );
	Status.init( e.target.form );

	Wikipedia.actionCompleted.redirect = mw.config.get('wgPageName');
	Wikipedia.actionCompleted.notice = "欢迎完成,将在几秒钟后刷新";

	var wikipedia_page = new Wikipedia.page(mw.config.get('wgPageName'), "用户对话页修改");
	wikipedia_page.setFollowRedirect(true);
	wikipedia_page.setCallbackParameters(params);
	wikipedia_page.load(Twinkle.welcome.callbacks.main);
};

/*
 * vim: set noet sts=0 sw=8:
 ****************************************
 *** twinklewarn.js: Warn module
 ****************************************
 * Mode of invocation:     Tab ("Warn")
 * Active on:              User talk pages
 * Config directives in:   TwinkleConfig
 */

Twinkle.warn = function twinklewarn() {
	if( mw.config.get('wgNamespaceNumber') === 3 ) {
		if(twinkleUserAuthorized) {
			$(twAddPortletLink("#", "警告", "tw-warn", "警告或提醒用户", "")).click(Twinkle.warn.callback);
		} else {
			$(twAddPortletLink("#", "警告", "tw-warn", "警告或提醒用户", "")).click(function() {
				alert("您还未达到自动确认。");
			});
		}
	}

	// modify URL of talk page on rollback success pages
	if( mw.config.get('wgAction') === 'rollback' ) {
		var $vandalTalkLink = $("#mw-rollback-success .mw-usertoollinks a").first();
		$vandalTalkLink.css("font-weight", "bold");

		var extraParam = "vanarticle=" + mw.util.rawurlencode(mw.config.get("wgPageName").replace(/_/g, " "));
		var href = $vandalTalkLink.attr("href");
		if (href.indexOf("?") === -1) {
			$vandalTalkLink.attr("href", href + "?" + extraParam);
		} else {
			$vandalTalkLink.attr("href", href + "&" + extraParam);
		}
	}
};

Twinkle.warn.callback = function twinklewarnCallback() {
	if( mw.config.get('wgTitle').split( '/' )[0].replace( /\"/, "\\\"") === mw.config.get('wgUserName') ){
		alert( '好吧,你已经被警告了!' );
		return;
	}
	
	var Window = new SimpleWindow( 600, 440 );
	Window.setTitle( "警告、通知用户" );
	Window.setScriptName( "Twinkle" );
	Window.addFooterLink( "选择警告级别", "WP:WARN" );
	Window.addFooterLink( "Twinkle帮助", "WP:TW/DOC#warn" );

	var form = new QuickForm( Twinkle.warn.callback.evaluate );
	var main_select = form.append( {
			type:'field',
			label:'选择要发送的警告或通知类别',
			tooltip:'首先选择一组,再选择具体的警告模板。'
		} );

	var main_group = main_select.append( {
			type:'select',
			name:'main_group',
			event:Twinkle.warn.callback.change_category
		} );

	var defaultGroup = parseInt(Twinkle.getPref('defaultWarningGroup'), 10);
	main_group.append( { type:'option', label:'层级1', value:'level1', selected: ( defaultGroup === 1 || defaultGroup < 1 || ( userIsInGroup( 'sysop' ) ? defaultGroup > 8 : defaultGroup > 7 ) ) } );
	main_group.append( { type:'option', label:'层级2', value:'level2', selected: ( defaultGroup === 2 ) } );
	main_group.append( { type:'option', label:'层级3', value:'level3', selected: ( defaultGroup === 3 ) } );
	main_group.append( { type:'option', label:'层级4', value:'level4', selected: ( defaultGroup === 4 ) } );
	main_group.append( { type:'option', label:'层级4im', value:'level4im', selected: ( defaultGroup === 5 ) } );
	main_group.append( { type:'option', label:'单层级通知', value:'singlenotice', selected: ( defaultGroup === 6 ) } );
	main_group.append( { type:'option', label:'单层级警告', value:'singlewarn', selected: ( defaultGroup === 7 ) } );
	if( userIsInGroup( 'sysop' ) ) {
		main_group.append( { type:'option', label:'封禁', value:'block', selected: ( defaultGroup === 8 ) } );
	}

	main_select.append( { type:'select', name:'sub_group', event:Twinkle.warn.callback.change_subcategory } ); //Will be empty to begin with.

	form.append( {
			type:'input',
			name:'article',
			label:'条目链接',
			value:( QueryString.exists( 'vanarticle' ) ? QueryString.get( 'vanarticle' ) : '' ),
			tooltip:'给模板中加入一条目链接,可留空。'
		} );

	var more = form.append( { type:'field', label:'填入可选的理由并点击“提交”' } );
	more.append( { type:'textarea', label:'更多:', name:'reason', tooltip:'可以填入理由或是更详细的通知。' } );

	var previewlink = document.createElement( 'a' );
	$(previewlink).click(function(){
		Twinkle.warn.callbacks.preview();
	});
	previewlink.style.cursor = "pointer";
	previewlink.textContent = '预览';
	more.append( { type: 'div', name: 'warningpreview', label: [ previewlink ] } );

	more.append( { type:'submit', label:'提交' } );

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

	// We must init the first choice (General Note);
	var evt = document.createEvent( "Event" );
	evt.initEvent( 'change', true, true );
	result.main_group.dispatchEvent( evt );
};

// This is all the messages that might be dispatched by the code
// Each of the individual templates require the following information:
//   label (required): A short description displayed in the dialog
//   summary (required): The edit summary used. If an article name is entered, the summary is postfixed with "on [[article]]", and it is always postfixed with ". $summaryAd"
//   suppressArticleInSummary (optional): Set to true to suppress showing the article name in the edit summary. Useful if the warning relates to attack pages, or some such.
Twinkle.warn.messages = {
	level1: {
		"uw-vandalism1": {
			label:"破坏",
			summary:"层级1:破坏"
		},
		"uw-test1": {
			label:"编辑测试",
			summary:"层级1:编辑测试"
		},
		"uw-delete1": {
			label:"清空页面、移除内容或模板",
			summary:"层级1:清空页面、移除内容或模板"
		},
		"uw-redirect1": { 
			label:"创建恶意重定向", 
			summary:"层级1:创建恶意重定向" 
		},
		"uw-tdel1": { 
			label:"移除维护性模板", 
			summary:"层级1:移除维护性模板" 
		},
		"uw-joke1": { 
			label:"加入不当玩笑", 
			summary:"层级1:加入不当玩笑" 
		},
		"uw-create1": { 
			label:"创建不当页面", 
			summary:"层级1:创建不当页面" 
		},
		"uw-upload1": { 
			label:"上传不当图像", 
			summary:"层级1:上传不当图像" 
		},
		"uw-image1": { 
			label:"与图像相关之破坏", 
			summary:"层级1:与图像相关之破坏" 
		},
		"uw-spam1": { 
			label:"增加垃圾连结", 
			summary:"层级1:增加垃圾连结" 
		},
		"uw-advert1": { 
			label:"利用维基百科来发布广告或推广", 
			summary:"层级1:利用维基百科来发布广告或推广" 
		},
		"uw-npov1": { 
			label:"不遵守中立的观点方针", 
			summary:"层级1:不遵守中立的观点方针" 
		},
		"uw-unsourced1": { 
			label:"没有使用适当的引用方法而增加没有来源的资料", 
			summary:"层级1:没有使用适当的引用方法而增加没有来源的资料" 
		},
		"uw-error1": { 
			label:"故意加入不实内容", 
			summary:"层级1:故意加入不实内容" 
		},
		"uw-biog1": { 
			label:"加入有关在生人物而又缺乏来源的资料", 
			summary:"层级1:加入有关在生人物而又缺乏来源的资料" 
		},
		"uw-defamatory1": { 
			label:"没有特定目标的诽谤", 
			summary:"层级1:没有特定目标的诽谤" 
		},
		"uw-notcensored1": { 
			label:"资料的审查", 
			summary:"层级1:资料的审查" 
		},
		"uw-mos1": { 
			label:"格式、日期、语言等", 
			summary:"层级1:格式、日期、语言等" 
		},
		"uw-move1": { 
			label:"页面移动", 
			summary:"层级1:页面移动" 
		},
		"uw-cd1": { 
			label:"把讨论页清空", 
			summary:"层级1:把讨论页清空" 
		},
		"uw-cd1": { 
			label:"把讨论页当为论坛", 
			summary:"层级1:把讨论页当为论坛" 
		},
		"uw-tpv1": { 
			label:"改写其他用户在讨论页留下的意见", 
			summary:"层级1:改写其他用户在讨论页留下的意见" 
		},
		"uw-afd1": { 
			label:"移除{{afd}}模板",
			summary:"层级1:移除{{afd}}模板"
		},
		"uw-speedy1": { 
			label:"移除{{delete}}模板",
			summary:"层级1:移除{{delete}}模板"
		},
		"uw-npa1": { 
			label:"针对特定用户的人身攻击", 
			summary:"层级1:针对特定用户的人身攻击" 
		},
		"uw-agf1": { 
			label:"没有善意推定", 
			summary:"层级1:没有善意推定" 
		},
		"uw-own1": { 
			label:"条目的所有权", 
			summary:"层级1:条目的所有权"
		},
		"uw-tempabuse1": { 
			label:"不当使用警告或封锁模板", 
			summary:"层级1:不当使用警告或封锁模板"
		}
	},
	level2: {
		"uw-vandalism2": {
			label:"破坏",
			summary:"层级2:破坏"
		},
		"uw-test2": {
			label:"编辑测试",
			summary:"层级2:编辑测试"
		},
		"uw-delete2": {
			label:"清空页面、移除内容或模板",
			summary:"层级2:清空页面、移除内容或模板"
		},
		"uw-redirect2": { 
			label:"创建恶意重定向", 
			summary:"层级2:创建恶意重定向" 
		},
		"uw-tdel2": { 
			label:"移除维护性模板", 
			summary:"层级2:移除维护性模板" 
		},
		"uw-joke2": { 
			label:"加入不当玩笑", 
			summary:"层级2:加入不当玩笑" 
		},
		"uw-create2": { 
			label:"创建不当页面", 
			summary:"层级2:创建不当页面" 
		},
		"uw-upload2": { 
			label:"上传不当图像", 
			summary:"层级2:上传不当图像" 
		},
		"uw-image2": { 
			label:"与图像相关之破坏", 
			summary:"层级2:与图像相关之破坏" 
		},
		"uw-spam2": { 
			label:"增加垃圾连结", 
			summary:"层级2:增加垃圾连结" 
		},
		"uw-advert2": { 
			label:"利用维基百科来发布广告或推广", 
			summary:"层级2:利用维基百科来发布广告或推广" 
		},
		"uw-npov2": { 
			label:"不遵守中立的观点方针", 
			summary:"层级2:不遵守中立的观点方针" 
		},
		"uw-unsourced2": { 
			label:"没有使用适当的引用方法而增加没有来源的资料", 
			summary:"层级2:没有使用适当的引用方法而增加没有来源的资料" 
		},
		"uw-error2": { 
			label:"故意加入不实内容", 
			summary:"层级2:故意加入不实内容" 
		},
		"uw-biog2": { 
			label:"加入有关在生人物而又缺乏来源的资料", 
			summary:"层级2:加入有关在生人物而又缺乏来源的资料" 
		},
		"uw-defamatory2": { 
			label:"没有特定目标的诽谤", 
			summary:"层级2:没有特定目标的诽谤" 
		},
		"uw-notcensored2": { 
			label:"资料的审查", 
			summary:"层级2:资料的审查" 
		},
		"uw-mos2": { 
			label:"格式、日期、语言等", 
			summary:"层级2:格式、日期、语言等" 
		},
		"uw-move2": { 
			label:"页面移动", 
			summary:"层级2:页面移动" 
		},
		"uw-cd2": { 
			label:"把讨论页清空", 
			summary:"层级2:把讨论页清空" 
		},
		"uw-cd2": { 
			label:"把讨论页当为论坛", 
			summary:"层级2:把讨论页当为论坛" 
		},
		"uw-tpv2": { 
			label:"改写其他用户在讨论页留下的意见", 
			summary:"层级2:改写其他用户在讨论页留下的意见" 
		},
		"uw-afd2": { 
			label:"移除{{afd}}模板",
			summary:"层级2:移除{{afd}}模板"
		},
		"uw-speedy2": { 
			label:"移除{{delete}}模板",
			summary:"层级2:移除{{delete}}模板"
		},
		"uw-npa2": { 
			label:"针对特定用户的人身攻击", 
			summary:"层级2:针对特定用户的人身攻击" 
		},
		"uw-agf2": { 
			label:"没有善意推定", 
			summary:"层级2:没有善意推定" 
		},
		"uw-own2": { 
			label:"条目的所有权", 
			summary:"层级2:条目的所有权"
		},
		"uw-tempabuse2": { 
			label:"不当使用警告或封锁模板", 
			summary:"层级2:不当使用警告或封锁模板"
		}
	},
	level3: {
		"uw-vandalism3": {
			label:"破坏",
			summary:"层级3:破坏"
		},
		"uw-test3": {
			label:"编辑测试",
			summary:"层级3:编辑测试"
		},
		"uw-delete3": {
			label:"清空页面、移除内容或模板",
			summary:"层级3:清空页面、移除内容或模板"
		},
		"uw-redirect3": { 
			label:"创建恶意重定向", 
			summary:"层级3:创建恶意重定向" 
		},
		"uw-tdel3": { 
			label:"移除维护性模板", 
			summary:"层级3:移除维护性模板" 
		},
		"uw-joke3": { 
			label:"加入不当玩笑", 
			summary:"层级3:加入不当玩笑" 
		},
		"uw-create3": { 
			label:"创建不当页面", 
			summary:"层级3:创建不当页面" 
		},
		"uw-upload3": { 
			label:"上传不当图像", 
			summary:"层级3:上传不当图像" 
		},
		"uw-image3": { 
			label:"与图像相关之破坏", 
			summary:"层级3:与图像相关之破坏" 
		},
		"uw-spam3": { 
			label:"增加垃圾连结", 
			summary:"层级3:增加垃圾连结" 
		},
		"uw-advert3": { 
			label:"利用维基百科来发布广告或推广", 
			summary:"层级3:利用维基百科来发布广告或推广" 
		},
		"uw-npov3": { 
			label:"不遵守中立的观点方针", 
			summary:"层级3:不遵守中立的观点方针" 
		},
		"uw-unsourced3": { 
			label:"没有使用适当的引用方法而增加没有来源的资料", 
			summary:"层级3:没有使用适当的引用方法而增加没有来源的资料" 
		},
		"uw-error3": { 
			label:"故意加入不实内容", 
			summary:"层级3:故意加入不实内容" 
		},
		"uw-biog3": { 
			label:"加入有关在生人物而又缺乏来源的资料", 
			summary:"层级3:加入有关在生人物而又缺乏来源的资料" 
		},
		"uw-defamatory3": { 
			label:"没有特定目标的诽谤", 
			summary:"层级3:没有特定目标的诽谤" 
		},
		"uw-notcensored3": { 
			label:"资料的审查", 
			summary:"层级3:资料的审查" 
		},
		"uw-mos3": { 
			label:"格式、日期、语言等", 
			summary:"层级3:格式、日期、语言等" 
		},
		"uw-move3": { 
			label:"页面移动", 
			summary:"层级3:页面移动" 
		},
		"uw-cd3": { 
			label:"把讨论页清空", 
			summary:"层级3:把讨论页清空" 
		},
		"uw-cd3": { 
			label:"把讨论页当为论坛", 
			summary:"层级3:把讨论页当为论坛" 
		},
		"uw-tpv3": { 
			label:"改写其他用户在讨论页留下的意见", 
			summary:"层级3:改写其他用户在讨论页留下的意见" 
		},
		"uw-afd3": { 
			label:"移除{{afd}}模板",
			summary:"层级3:移除{{afd}}模板"
		},
		"uw-speedy3": { 
			label:"移除{{delete}}模板",
			summary:"层级3:移除{{delete}}模板"
		},
		"uw-npa3": { 
			label:"针对特定用户的人身攻击", 
			summary:"层级3:针对特定用户的人身攻击" 
		},
		"uw-agf3": { 
			label:"没有善意推定", 
			summary:"层级3:没有善意推定" 
		},
		"uw-own3": { 
			label:"条目的所有权", 
			summary:"层级3:条目的所有权"
		},
		"uw-tempabuse3": { 
			label:"不当使用警告或封锁模板", 
			summary:"层级3:不当使用警告或封锁模板"
		}
	},
	level4: {
		"uw-vandalism4": {
			label:"破坏",
			summary:"层级4:破坏"
		},
		"uw-test4": {
			label:"编辑测试",
			summary:"层级4:编辑测试"
		},
		"uw-delete4": {
			label:"清空页面、移除内容或模板",
			summary:"层级4:清空页面、移除内容或模板"
		},
		"uw-redirect4": { 
			label:"创建恶意重定向", 
			summary:"层级4:创建恶意重定向" 
		},
		"uw-tdel4": { 
			label:"移除维护性模板", 
			summary:"层级4:移除维护性模板" 
		},
		"uw-joke4": { 
			label:"加入不当玩笑", 
			summary:"层级4:加入不当玩笑" 
		},
		"uw-create4": { 
			label:"创建不当页面", 
			summary:"层级4:创建不当页面" 
		},
		"uw-upload4": { 
			label:"上传不当图像", 
			summary:"层级4:上传不当图像" 
		},
		"uw-image4": { 
			label:"与图像相关之破坏", 
			summary:"层级4:与图像相关之破坏" 
		},
		"uw-spam4": { 
			label:"增加垃圾连结", 
			summary:"层级4:增加垃圾连结" 
		},
		"uw-advert4": { 
			label:"利用维基百科来发布广告或推广", 
			summary:"层级4:利用维基百科来发布广告或推广" 
		},
		"uw-npov4": { 
			label:"不遵守中立的观点方针", 
			summary:"层级4:不遵守中立的观点方针" 
		},
		"uw-unsourced4": { 
			label:"没有使用适当的引用方法而增加没有来源的资料", 
			summary:"层级4:没有使用适当的引用方法而增加没有来源的资料" 
		},
		"uw-error4": { 
			label:"故意加入不实内容", 
			summary:"层级4:故意加入不实内容" 
		},
		"uw-biog4": { 
			label:"加入有关在生人物而又缺乏来源的资料", 
			summary:"层级4:加入有关在生人物而又缺乏来源的资料" 
		},
		"uw-defamatory4": { 
			label:"没有特定目标的诽谤", 
			summary:"层级4:没有特定目标的诽谤" 
		},
		"uw-notcensored4": { 
			label:"资料的审查", 
			summary:"层级4:资料的审查" 
		},
		"uw-mos4": { 
			label:"格式、日期、语言等", 
			summary:"层级4:格式、日期、语言等" 
		},
		"uw-move4": { 
			label:"页面移动", 
			summary:"层级4:页面移动" 
		},
		"uw-cd4": { 
			label:"把讨论页清空", 
			summary:"层级4:把讨论页清空" 
		},
		"uw-cd4": { 
			label:"把讨论页当为论坛", 
			summary:"层级4:把讨论页当为论坛" 
		},
		"uw-tpv4": { 
			label:"改写其他用户在讨论页留下的意见", 
			summary:"层级4:改写其他用户在讨论页留下的意见" 
		},
		"uw-afd4": { 
			label:"移除{{afd}}模板",
			summary:"层级4:移除{{afd}}模板"
		},
		"uw-speedy4": { 
			label:"移除{{delete}}模板",
			summary:"层级4:移除{{delete}}模板"
		},
		"uw-npa4": { 
			label:"针对特定用户的人身攻击", 
			summary:"层级4:针对特定用户的人身攻击" 
		},
		"uw-agf4": { 
			label:"没有善意推定", 
			summary:"层级4:没有善意推定" 
		},
		"uw-own4": { 
			label:"条目的所有权", 
			summary:"层级4:条目的所有权"
		},
		"uw-tempabuse4": { 
			label:"不当使用警告或封锁模板", 
			summary:"层级4:不当使用警告或封锁模板"
		}
	},
	level4im: {
		"uw-vandalism4im": {
			label:"破坏",
			summary:"层级4im:破坏"
		},
		"uw-delete4im": {
			label:"清空页面、移除内容或模板",
			summary:"层级4im:清空页面、移除内容或模板"
		},
		"uw-redirect4im": { 
			label:"创建恶意重定向", 
			summary:"层级4im:创建恶意重定向" 
		},
		"uw-joke4im": { 
			label:"加入不当玩笑", 
			summary:"层级4im:加入不当玩笑" 
		},
		"uw-create4im": { 
			label:"创建不当页面", 
			summary:"层级4im:创建不当页面" 
		},
		"uw-upload4im": { 
			label:"上传不当图像", 
			summary:"层级4im:上传不当图像" 
		},
		"uw-image4im": { 
			label:"与图像相关之破坏", 
			summary:"层级4im:与图像相关之破坏" 
		},
		"uw-spam4im": { 
			label:"增加垃圾连结", 
			summary:"层级4im:增加垃圾连结" 
		},
		"uw-biog4im": { 
			label:"加入有关在生人物而又缺乏来源的资料", 
			summary:"层级4im:加入有关在生人物而又缺乏来源的资料" 
		},
		"uw-defamatory4im": { 
			label:"没有特定目标的诽谤", 
			summary:"层级4im:没有特定目标的诽谤" 
		},
		"uw-move4im": { 
			label:"页面移动", 
			summary:"层级4im:页面移动" 
		},
		"uw-npa4im": { 
			label:"针对特定用户的人身攻击", 
			summary:"层级4im:针对特定用户的人身攻击" 
		},
		"uw-tempabuse4im": { 
			label:"不当使用警告或封锁模板", 
			summary:"层级4im:不当使用警告或封锁模板"
		}
	},
	singlenotice: {
		"uw-2redirect": { 
			label:"透过不适当的页面移动建立双重重定向", 
			summary:"单层级通知:透过不适当的页面移动建立双重重定向" 
		},
		"uw-aiv": { 
			label:"不恰当的破坏回报", 
			summary:"单层级通知:不恰当的破坏回报" 
		},
		"uw-articlesig": { 
			label:"在条目页中签名", 
			summary:"单层级通知:在条目页中签名" 
		},
		"uw-autobiography": { 
			label:"建立自传", 
			summary:"单层级通知:建立自传" 
		},
		"uw-badcat": { 
			label:"加入错误的页面分类", 
			summary:"单层级通知:加入错误的页面分类" 
		},
		"uw-bite": { 
			label:"伤害新手", 
			summary:"单层级通知:伤害新手" 
		},
		"uw-booktitle": {
			label:"没有使用书名号来标示书籍、电影、音乐专辑等",
			summary:"单层级通知:没有使用书名号来标示书籍、电影、音乐专辑等"
		},
		"uw-c&pmove": {
			label:"剪贴移动",
			summary:"单层级通知:剪贴移动"
		},
		"uw-chinese": { 
			label:"不是以中文进行沟通", 
			summary:"单层级通知:不是以中文进行沟通" 
		},
		"uw-coi": { 
			label:"利益冲突", 
			summary:"单层级通知:利益冲突" 
		},
		"uw-copyright-friendly": {
			label:"初次加入侵犯版权的内容",
			summary:"单层级通知:初次加入侵犯版权的内容"
		},
		"uw-copyviorewrite": {
			label:"在侵权页面直接重写条目",
			summary:"单层级通知:在侵权页面直接重写条目"
		},
		"uw-date": { 
			label:"不必要地更换日期格式", 
			summary:"单层级通知:不必要地更换日期格式" 
		},
		"uw-editsummary": { 
			label:"没有使用编辑摘要", 
			summary:"单层级通知:没有使用编辑摘要" 
		},
		"uw-hangon": { 
			label:"没有在讨论页说明暂缓快速删除理由", 
			summary:"单层级通知:没有在讨论页说明暂缓快速删除理由"
		},
		"uw-lang": { 
			label:"不必要地将条目所有文字换成简体或繁体中文", 
			summary:"单层级通知:不必要地将条目所有文字换成简体或繁体中文" 
		},
		"uw-linking": { 
			label:"过度加入红字连结或重复蓝字连结", 
			summary:"单层级通知:过度加入红字连结或重复蓝字连结" 
		},
		"uw-minor": { 
			label:"不适当地使用小修改选项", 
			summary:"单层级通知:不适当地使用小修改选项" 
		},
		"uw-notaiv": { 
			label:"不要向当前的破坏回报复杂的用户纷争", 
			summary:"单层级通知:不要向当前的破坏回报复杂的用户纷争" 
		},
		"uw-notvote": {
			label:"我们是以共识处事,不仅是投票", 
			summary:"单层级通知:我们是以共识处事,不仅是投票" 
		},
		"uw-preview": { 
			label:"使用预览按钮来避免不必要的错误", 
			summary:"单层级通知:使用预览按钮来避免不必要的错误" 
		},
		"uw-sandbox": { 
			label:"移除沙盒的置顶模板{{sandbox}}", 
			summary:"单层级通知:移除沙盒的置顶模板{{sandbox}}" 
		},
		"uw-selfrevert": { 
			label:"回退个人的测试", 
			summary:"单层级通知:回退个人的测试" 
		},
		"uw-subst": { 
			label:"谨记要替代模板", 
			summary:"单层级通知:谨记要替代模板" 
		},
		"uw-talkinarticle": { 
			label:"在条目页中留下意见", 
			summary:"单层级通知:在条目页中留下意见" 
		},
		"uw-tilde": { 
			label:"没有在讨论页上签名", 
			summary:"单层级通知:没有在讨论页上签名" 
		},
		"uw-uaa": { 
			label:"向更改用户名回报的用户名称并不违反方针", 
			summary:"单层级通知:向更改用户名回报的用户名称并不违反方针" 
		},
		"uw-warn": { 
			label:"警告破坏用户", 
			summary:"单层级通知:警告破坏用户"
		}
	},
	singlewarn: {
		"uw-3rr": { 
			label:"用户潜在违反回退不过三原则的可能性",
			summary:"单层级警告:用户潜在违反回退不过三原则的可能性"
		},
		"uw-attack": {
			label:"建立人身攻击页面",
			summary:"单层级警告:建立人身攻击页面",
			suppressArticleInSummary: true
		},
		"uw-bv": {
			label:"公然的破坏",
			summary:"单层级警告:公然的破坏"
		},
		"uw-canvass": {
			label:"不恰当的拉票",
			summary:"单层级警告:不恰当的拉票"
		},
		"uw-copyright": {
			label:"侵犯版权",
			summary:"单层级警告:侵犯版权"
		},
		"uw-copyright-link": { 
			label:"连结到有版权的材料",
			summary:"单层级警告:连结到有版权的材料" 
		},
		"uw-hoax": { 
			label:"建立恶作剧", 
			summary:"单层级警告:建立恶作剧" 
		},
		"uw-legal": { 
			label:"诉诸法律威胁", 
			summary:"单层级警告:诉诸法律威胁" 
		},
		"uw-longterm": { 
			label:"长期的破坏", 
			summary:"单层级警告:长期的破坏" 
		},
		"uw-multipleIPs": { 
			label:"使用多个IP地址", 
			summary:"单层级警告:使用多个IP地址" 
		},
		"uw-npov-tvd": { 
			label:"在剧集条目中加入奸角等非中立描述", 
			summary:"单层级警告:在剧集条目中加入奸角等非中立描述"
		},
		"uw-pinfo": { 
			label:"个人资料", 
			summary:"单层级警告:个人资料" 
		},
		"uw-redirect": {
			label:"建立恶意重定向",
			summary:"单层级警告:建立恶意重定向"
		},
		"uw-upv": { 
			label:"用户页破坏", 
			summary:"单层级警告:用户页破坏"
		},
		"uw-substub": { 
			label:"创建小小作品", 
			summary:"单层级警告:创建小小作品"
		},
		"uw-username": { 
			label:"不恰当的用户名", 
			summary:"单层级警告:不恰当的用户名"
		}
	},
	block: {
		"uw-block1": {
			label: "层级1封禁",
			summary: "层级1封禁"
		},
		"uw-block2": {
			label: "层级2封禁",
			summary: "层级2封禁"
		},
		"uw-block3": {
			label: "层级3封禁",
			summary: "层级3封禁",
			indefinite: true
		},
		"uw-3block": {
			label: "回退不过三原则封禁",
			summary: "回退不过三原则封禁"
		},
		"uw-ablock": {
			label: "匿名封禁",
			summary: "匿名封禁"
		},
		"uw-bblock": {
			label: "机器人失灵封禁",
			summary: "机器人失灵封禁"
		},
		"uw-dblock": {
			label: "删除封禁",
			summary: "删除封禁"
		},
		"uw-sblock": {
			label: "垃圾连结封禁",
			summary: "垃圾连结封禁"
		},
		"uw-ublock": {
			label: "用户名称封禁",
			summary: "用户名称封禁",
			indefinite: true
		},
		"uw-vblock": {
			label: "破坏封禁",
			summary: "破坏封禁"
		}
	}
};

// Set to true if the template supports the reason parameter and isn't the same as its super-template when a reason is provided
// NOTE: I have removed this reason hash; I have an updated version on my machine, which I am yet to integrate. -- TTO

Twinkle.warn.prev_block_timer = null;
Twinkle.warn.prev_article = null;
Twinkle.warn.prev_reason = null;

Twinkle.warn.callback.change_category = function twinklewarnCallbackChangeCategory(e) {
	var value = e.target.value;
	var sub_group = e.target.root.sub_group;
	var messages = Twinkle.warn.messages[ value ];
	sub_group.main_group = value;
	var old_subvalue = sub_group.value;
	var old_subvalue_re;
	if( old_subvalue ) {
		old_subvalue = old_subvalue.replace(/\d*(im)?$/, '' );
		old_subvalue_re = new RegExp( RegExp.escape( old_subvalue ) + "(\\d*(?:im)?)$" );
	}

	while( sub_group.hasChildNodes() ){
		sub_group.removeChild( sub_group.firstChild );
	}

	for( var i in messages ) {
		var selected = false;
		if( old_subvalue && old_subvalue_re.test( i ) ) {
			selected = true;
		}
		var elem = new QuickForm.element( { type:'option', label:"[" + i + "]: " + messages[i].label, value:i, selected: selected } );
		
		sub_group.appendChild( elem.render() );
	}

	if( value === 'block' ) {
		var more = new QuickForm.element( {
				type: 'input',
				name: 'block_timer',
				label: '封禁时间:',
				tooltip: '例如24小时、2天等。'
			} );
		e.target.root.insertBefore( more.render(), e.target.root.lastChild );
		if(Twinkle.warn.prev_block_timer !== null) {
			e.target.root.block_timer.value = Twinkle.warn.prev_block_timer;
			Twinkle.warn.prev_block_timer = null;
		}		
		if(Twinkle.warn.prev_article === null) {
			Twinkle.warn.prev_article = e.target.root.article.value;
		}
		e.target.root.article.disabled = false;
		e.target.root.article.value = '';
		$(e.target.root).find("fieldset:has(textarea)").hide();
	} else if( e.target.root.block_timer ) {
		if(!e.target.root.block_timer.disabled && Twinkle.warn.prev_block_timer === null) {
			Twinkle.warn.prev_block_timer = e.target.root.block_timer.value;
		}
		e.target.root.removeChild( e.target.root.block_timer.parentNode );
		if(e.target.root.article.disabled && Twinkle.warn.prev_article !== null) {
			e.target.root.article.value = Twinkle.warn.prev_article;
			Twinkle.warn.prev_article = null;
		}
		e.target.root.article.disabled = false;
		if(e.target.root.reason.disabled && Twinkle.warn.prev_reason !== null) {
			e.target.root.reason.value = Twinkle.warn.prev_reason;
			Twinkle.warn.prev_reason = null;
		}
		e.target.root.reason.disabled = false;
		$(e.target.root).find("fieldset:has(textarea)").show();
	}
};

Twinkle.warn.callback.change_subcategory = function twinklewarnCallbackChangeSubcategory(e) {
	var main_group = e.target.form.main_group.value;
	var value = e.target.form.sub_group.value;

	if( main_group === 'singlewarn' ) {
		if( value === 'uw-username' ) {
			if(Twinkle.warn.prev_article === null) {
				Twinkle.warn.prev_article = e.target.form.article.value;
			}
			e.target.form.article.disabled = true;
			e.target.form.article.value = '';
		} else if( e.target.form.article.disabled ) {
			if(Twinkle.warn.prev_article !== null) {
				e.target.form.article.value = Twinkle.warn.prev_article;
				Twinkle.warn.prev_article = null;
			}
			e.target.form.article.disabled = false;
		}
	} else if( main_group === 'block' ) {
		if( Twinkle.warn.messages.block[value].indefinite ) {
			if(Twinkle.warn.prev_block_timer === null) {
				Twinkle.warn.prev_block_timer = e.target.form.block_timer.value;
			}
			e.target.form.block_timer.disabled = true;
			e.target.form.block_timer.value = 'indef';
		} else if( e.target.form.block_timer.disabled ) {
			if(Twinkle.warn.prev_block_timer !== null) {
				e.target.form.block_timer.value = Twinkle.warn.prev_block_timer;
				Twinkle.warn.prev_block_timer = null;
			}
			e.target.form.block_timer.disabled = false;
		}

		if( Twinkle.warn.messages.block[value].pageParam ) {
			if(Twinkle.warn.prev_article !== null) {
				e.target.form.article.value = Twinkle.warn.prev_article;
				Twinkle.warn.prev_article = null;
			}
			e.target.form.article.disabled = false;
		} else if( !e.target.form.article.disabled ) {
			if(Twinkle.warn.prev_article === null) {
				Twinkle.warn.prev_article = e.target.form.article.value;
			}
			e.target.form.article.disabled = true;
			e.target.form.article.value = '';
		}

		//if( Twinkle.warn.messages.block[value].reasonParam ) {
		//	if(Twinkle.warn.prev_reason !== null) {
		//		e.target.form.reason.value = Twinkle.warn.prev_reason;
		//		Twinkle.warn.prev_reason = null;
		//	}
		//	e.target.form.reason.disabled = false;
		//} else if( !e.target.form.reason.disabled ) {
		//	if(Twinkle.warn.prev_reason === null) {
		//		Twinkle.warn.prev_reason = e.target.form.reason.value;
		//	}
		//	e.target.form.reason.disabled = true;
		//	e.target.form.reason.value = '';
		//}
	}

	var $article = $(e.target.form.article);
	$("span#tw-spi-article-username").remove();
	$article.prev().show();
};

Twinkle.warn.callbacks = {
	preview: function() {
		var templatename = $('select[name="sub_group"]:visible').last()[0].value;

		var previewdiv = $('div[name="warningpreview"]:visible').last();
		if (!previewdiv.length) {
			return;  // just give up
		}
		previewdiv = previewdiv[0];

		var previewbox = $('div#twinklewarn-previewbox:visible').last();
		if (!previewbox.length) {
			previewbox = document.createElement('div');
			previewbox.style.background = "white";
			previewbox.style.border = "2px inset";
			previewbox.style.marginTop = "0.4em";
			previewbox.style.padding = "0.2em 0.4em";
			previewbox.setAttribute('id', 'twinklewarn-previewbox');
			previewdiv.parentNode.appendChild(previewbox);
		} else {
			previewbox = previewbox[0];
		}

		var statusspan = document.createElement('span');
		previewbox.appendChild(statusspan);
		Status.init(statusspan);
		
		var templatetext = '{{subst:' + templatename;
		var linkedarticle = $('input[name="article"]:visible').last();
		if (templatename in Twinkle.warn.messages.block) {
			if( linkedarticle.length && Twinkle.warn.messages.block[templatename].pageParam ) {
				templatetext += '|page=' + linkedarticle;
			}

			var blocktime = $('input[name="block_timer"]:visible').last();
			if( /te?mp|^\s*$|min/.exec( blocktime ) || Twinkle.warn.messages.block[templatename].indefinite ) {
				; // nothing
			} else if( /indef|\*|max/.exec( blocktime ) ) {
				templatetext += '|indef=yes';
			} else {
				templatetext += '|time=' + blocktime;
			}

			templatetext += "|sig=true";
		} else if (linkedarticle.length) {
			// add linked article for user warnings (non-block templates)
			templatetext += '|1=' + linkedarticle[0].value;
		}
		var reason = $('textarea[name="reason"]:visible').last();
		if (reason.length && reason[0].value) {
			templatetext += '|2=' + reason[0].value;
		}
		templatetext += '}}';
		var query = {
			action: 'parse',
			prop: 'text',
			pst: 'true',  // PST = pre-save transform; this makes substitution work properly
			text: templatetext,
			title: mw.config.get('wgPageName')
		};
		var wikipedia_api = new Wikipedia.api("加载…", query, Twinkle.warn.callbacks.previewRender, new Status("预览"));
		wikipedia_api.params = { previewbox: previewbox };
		wikipedia_api.post();
	},
	previewRender: function( apiobj ) {
		var params = apiobj.params;
		var xml = apiobj.getXML();
		var html = $(xml).find('text').text();
		if (!html) {
			apiobj.statelem.error("不能读取预览,或警告模板被清空");
			return;
		}
		params.previewbox.innerHTML = html;
		// fix vertical alignment
		$(params.previewbox).find(':not(img)').css('vertical-align', 'baseline');
	},
	main: function( pageobj ) {
		var text = pageobj.getPageText();
		var params = pageobj.getCallbackParameters();
		var messageData = Twinkle.warn.messages[params.main_group][params.sub_group];

		if (!pageobj.exists()) {
			text = "{{subst:welcome/auto}}\n";
		}

		var history_re = /<!-- Template:(uw-.*?) -->.*?(\d{1,2}:\d{1,2}, \d{1,2} \w+ \d{4}) \(UTC\)/g;
		var history = {};
		var latest = { date:new Date( 0 ), type:'' };
		var current;

		while( ( current = history_re.exec( text ) ) ) {
			var current_date = new Date( current[2] + ' UTC' );
			if( !( current[1] in history ) ||  history[ current[1] ] < current_date ) {
				history[ current[1] ] = current_date;
			}
			if( current_date > latest.date ) {
				latest.date = current_date;
				latest.type = current[1];
			}
		}

		var date = new Date();

		if( params.sub_group in history ) {
			var temp_time = new Date( history[ params.sub_group ] );
			temp_time.setUTCHours( temp_time.getUTCHours() + 24 );

			if( temp_time > date ) {
				if( !confirm( "近24小时内一个同样的 " + params.sub_group + " 模板已被发出。\n是否继续?" ) ) {
					pageobj.statelem.info( '用户取消' );
					return;
				}
			}
		}

		latest.date.setUTCMinutes( latest.date.getUTCMinutes() + 1 ); // after long debate, one minute is max

		if( latest.date > date ) {
			if( !confirm( "近1分钟内一个同样的 " + latest.type + " 模板已被发出。\n是否继续?" ) ) {
				pageobj.statelem.info( '用户取消' );
				return;
			}
		}
		
		var headerRe = new RegExp( "^==+\\s*" + date.getUTCFullYear() + "年" + (date.getUTCMonth() + 1) + "月" + "\\s*==+", 'm' );

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

		if( params.main_group === 'block' ) {
			var article = '', reason = '', time = null;
			
			if( Twinkle.getPref('blankTalkpageOnIndefBlock') && ( Twinkle.warn.messages.block[params.sub_group].indefinite || (/indef|\*|max/).exec( params.block_timer ) ) ) {
				Status.info( '信息', '根据参数设置清空讨论页并创建新标题' );
				text = "== " + date.getUTCFullYear() + "年" + (date.getUTCMonth() + 1) + "月 " + " ==\n";
			} else if( !headerRe.exec( text ) ) {
				Status.info( '信息', '未找到当月标题,将创建新的' );
				text += "== " + date.getUTCFullYear() + "年" + (date.getUTCMonth() + 1) + "月 " + " ==\n";
			}
			
			if( params.article && Twinkle.warn.messages.block[params.sub_group].pageParam ) {
				article = '|page=' + params.article;
			}
			
			//if( params.reason && Twinkle.warn.reasonHash[ params.sub_group ] ) {
			//	reason = '|reason=' + params.reason;
			//}
			
			if( /te?mp|^\s*$|min/.exec( params.block_timer ) || Twinkle.warn.messages.block[params.sub_group].indefinite ) {
				time = '';
			} else if( /indef|\*|max/.exec( params.block_timer ) ) {
				time = '|indef=yes';
			} else {
				time = '|time=' + params.block_timer;
			}

			text += "{{subst:" + params.sub_group + article + time + reason + "|sig=true|subst=subst:}}";
		} else {
			if( !headerRe.exec( text ) ) {
				Status.info( '信息', '未找到当月标题,将创建新的' );
				text += "== " + date.getUTCFullYear() + "年" + (date.getUTCMonth() + 1) + "月 " + " ==\n";
			}

			switch( params.sub_group ) {
				case 'uw-username':
					text += "{{subst:" + params.sub_group + ( params.reason ? '|1=' + params.reason : '' ) + "|subst=subst:}} ~~~~";
					break;
				default:
					text += "{{subst:" + params.sub_group + ( params.article ? '|1=' + params.article : '' ) + (params.reason ? '|2=' + params.reason : '' ) + "|subst=subst:}}" + "--~~~~";
					break;
			}
		}
		
		if ( Twinkle.getPref('showSharedIPNotice') && isIPAddress( mw.config.get('wgTitle') ) ) {
			Status.info( '信息', '添加共享IP说明' );
			text +=  "\n{{subst:SharedIPAdvice}}";
		}

		var summary = messageData.summary;
		if ( messageData.suppressArticleInSummary !== true && params.article ) {
			summary += ",关于[[" + params.article + "]]";
		}
		summary += "。" + Twinkle.getPref("summaryAd");

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

Twinkle.warn.callback.evaluate = function twinklewarnCallbackEvaluate(e) {

	// First, check to make sure a reason was filled in if uw-username was selected
	
	if(e.target.sub_group.value === 'uw-username' && e.target.reason.value.trim() === '') {
		alert("必须给{{uw-username}}提供理由。");
		return;
	}

	// Then, grab all the values provided by the form
	
	var params = {
		reason: e.target.reason.value,
		main_group: e.target.main_group.value,
		sub_group: e.target.sub_group.value,
		article: e.target.article.value,  // .replace( /^(Image|Category):/i, ':$1:' ),  -- apparently no longer needed...
		block_timer: e.target.block_timer ? e.target.block_timer.value : null
	};

	SimpleWindow.setButtonsEnabled( false );
	Status.init( e.target );

	Wikipedia.actionCompleted.redirect = mw.config.get('wgPageName');
	Wikipedia.actionCompleted.notice = "警告完成,将在几秒后刷新";

	var wikipedia_page = new Wikipedia.page( mw.config.get('wgPageName'), '用户对话页修改' );
	wikipedia_page.setCallbackParameters( params );
	wikipedia_page.setFollowRedirect( true );
	wikipedia_page.load( Twinkle.warn.callbacks.main );
};

/*
 * vim: set noet sts=0 sw=8:
 ****************************************
 *** twinkleprotect.js: Protect/RPP module
 ****************************************
 * Mode of invocation:     Tab ("PP"/"RPP")
 * Active on:              Non-special pages
 * Config directives in:   TwinkleConfig
 */

Twinkle.protect = function twinkleprotect() {
	if ( mw.config.get('wgNamespaceNumber') < 0 ) {
		return;
	}

	if ( userIsInGroup( 'sysop' ) ) {
		$(twAddPortletLink("#", "保护", "tw-rpp", "保护页面", "")).click(Twinkle.protect.callback);
	} else if (twinkleUserAuthorized) {
		$(twAddPortletLink("#", "保护", "tw-rpp", "请求保护页面", "")).click(Twinkle.protect.callback);
	} else {
		$(twAddPortletLink("#", '保护', 'tw-rpp', '请求保护页面', '')).click(function() {
			alert("您还未达到自动确认。");
		});
	}
};

Twinkle.protect.callback = function twinkleprotectCallback() {
	var Window = new SimpleWindow( 620, 530 );
	Window.setTitle( userIsInGroup( 'sysop' ) ? "施行或请求保护页面" : "请求保护页面" );
	Window.setScriptName( "Twinkle" );
	Window.addFooterLink( "保护模板", "Template:Protection templates" );
	Window.addFooterLink( "保护方针", "WP:PROT" );
	Window.addFooterLink( "Twinkle帮助", "WP:TW/DOC#protect" );

	var form = new QuickForm( Twinkle.protect.callback.evaluate );
	var actionfield = form.append( {
			type: 'field',
			label: '操作类型'
		} );
	if( userIsInGroup( 'sysop' ) ) {
		actionfield.append( {
				type: 'radio',
				name: 'actiontype',
				event: Twinkle.protect.callback.changeAction,
				list: [
					{
						label: '保护页面',
						value: 'protect',
						checked: true
					}
				]
			} );
	}
	actionfield.append( {
			type: 'radio',
			name: 'actiontype',
			event: Twinkle.protect.callback.changeAction,
			list: [
				{
					label: '请求保护页面',
					value: 'request',
					tooltip: '如果您想在WP:RFPP请求保护此页' + (userIsInGroup('sysop') ? '而不是自行完成。' : '。'),
					checked: !userIsInGroup('sysop')
				},
				{
					label: '用保护模板标记此页',
					value: 'tag',
					tooltip: '可以用此为页面加上合适的保护模板。'
				}
			]
		} );

	form.append({ type: 'field', label: '预设', name: 'field_preset' });
	form.append({ type: 'field', label: '1', name: 'field1' });
	form.append({ type: 'field', label: '2', name: 'field2' });

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

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

	// We must init the controls
	var evt = document.createEvent( "Event" );
	evt.initEvent( 'change', true, true );
	result.actiontype[0].dispatchEvent( evt );

	// reduce vertical height of dialog
	$('select[name="editlevel"], select[name="editexpiry"], select[name="moveexpiry"], select[name="movelevel"], select[name="createexpiry"], select[name="createlevel"]').
		parent().css({ display: 'inline-block', marginRight: '0.5em' });

	// get current protection level asynchronously
	Wikipedia.actionCompleted.postfix = false;  // avoid Action: completed notice
	if (userIsInGroup('sysop')) {
		var query = {
			action: 'query',
			prop: 'info',
			inprop: 'protection',
			titles: mw.config.get('wgPageName')
		};
		Status.init($('div[name="currentprot"] span').last()[0]);
		var statelem = new Status("当前保护级别");
		var wpapi = new Wikipedia.api("抓取…", query, Twinkle.protect.callback.protectionLevel, statelem);
		wpapi.post();
	}
};

Twinkle.protect.protectionLevel = null;

Twinkle.protect.callback.protectionLevel = function twinkleprotectCallbackProtectionLevel(apiobj) {
	var xml = apiobj.getXML();
	var result = [];
	$(xml).find('pr').each(function(index, pr) {
		var $pr = $(pr);
		var boldnode = document.createElement('b');
		boldnode.textContent = $pr.attr('type').toUpperCaseFirstChar() + ": " + $pr.attr('level');
		result.push(boldnode);
		if ($pr.attr('expiry') === 'infinity') {
			result.push("(永久)");
		} else {
			result.push("(过期:" + new Date($pr.attr('expiry')).toUTCString() + ")");
		}
		if ($pr.attr('cascade') === '') {
			result.push("(联锁)");
		}
	});
	if (!result.length) {
		var boldnode = document.createElement('b');
		boldnode.textContent = "未被保护";
		result.push(boldnode);
	}
	Twinkle.protect.protectionLevel = result;
	apiobj.statelem.info(result);
	window.setTimeout(function() { Wikipedia.actionCompleted.postfix = "完成"; }, 500);  // restore actionCompleted message
};

Twinkle.protect.callback.changeAction = function twinkleprotectCallbackChangeAction(e) {
	var field_preset;
	var field1;
	var field2;

	switch (e.target.values) {
		case 'protect':
			field_preset = new QuickForm.element({ type: 'field', label: '预设', name: 'field_preset' });
			field_preset.append({
					type: 'select',
					name: 'category',
					label: '选择预设:',
					event: Twinkle.protect.callback.changePreset,
					list: (mw.config.get('wgArticleId') ? Twinkle.protect.protectionTypes : Twinkle.protect.protectionTypesCreate)
				});

			field2 = new QuickForm.element({ type: 'field', label: '保护选项', name: 'field2' });
			field2.append({ type: 'div', name: 'currentprot', label: ' ' });  // holds the current protection level, as filled out by the async callback
			// for existing pages
			if (mw.config.get('wgArticleId')) {
				field2.append({
						type: 'checkbox',
						name: 'editmodify',
						event: Twinkle.protect.formevents.editmodify,
						list: [
							{
								label: '修改编辑权限',
								value: 'editmodify',
								tooltip: '如果此项被关闭,编辑权限将不被修改。',
								checked: true
							}
						]
					});
				var editlevel = field2.append({
						type: 'select',
						name: 'editlevel',
						label: '编辑权限:',
						event: Twinkle.protect.formevents.editlevel
					});
				editlevel.append({
						type: 'option',
						label: '(站点默认)',
						value: 'all'
					});
				editlevel.append({
						type: 'option',
						label: '禁止未注册用户',
						value: 'autoconfirmed'
					});
				editlevel.append({
						type: 'option',
						label: '仅管理员',
						value: 'sysop',
						selected: true
					});
				field2.append({
						type: 'select',
						name: 'editexpiry',
						label: '终止时间:',
						event: function(e) {
							if (e.target.value === 'custom') {
								Twinkle.protect.doCustomExpiry(e.target);
							}
						},
						list: [
							{ label: '1小时', value: '1 hour' },
							{ label: '2小时', value: '2 hours' },
							{ label: '3小时', value: '3 hours' },
							{ label: '6小时', value: '6 hours' },
							{ label: '12小时', value: '12 hours' },
							{ label: '1日', value: '1 day' },
							{ label: '2日', selected: true, value: '2 days' },
							{ label: '3日', value: '3 days' },
							{ label: '4日', value: '4 days' },
							{ label: '1周', value: '1 week' },
							{ label: '2周', value: '2 weeks' },
							{ label: '1月', value: '1 month' },
							{ label: '2月', value: '2 months' },
							{ label: '3月', value: '3 months' },
							{ label: '1年', value: '1 year' },
							{ label: '无限期', value:'indefinite' },
							{ label: '自定义…', value: 'custom' }
						]
					});
				field2.append({
						type: 'checkbox',
						name: 'movemodify',
						event: Twinkle.protect.formevents.movemodify,
						list: [
							{
								label: '修改移动权限',
								value: 'movemodify',
								tooltip: '如果此项被关闭,移动权限将不被修改。',
								checked: true
							}
						]
					});
				var movelevel = field2.append({
						type: 'select',
						name: 'movelevel',
						label: '移动权限:',
						event: Twinkle.protect.formevents.movelevel
					});
				movelevel.append({
						type: 'option',
						label: '(站点默认)',
						value: 'all'
					});
				movelevel.append({
						type: 'option',
						label: '禁止未注册用户',
						value: 'autoconfirmed'
					});
				movelevel.append({
						type: 'option',
						label: '仅管理员',
						value: 'sysop',
						selected: true
					});
				field2.append({
						type: 'select',
						name: 'moveexpiry',
						label: '终止时间:',
						event: function(e) {
							if (e.target.value === 'custom') {
								Twinkle.protect.doCustomExpiry(e.target);
							}
						},
						list: [
							{ label: '1小时', value: '1 hour' },
							{ label: '2小时', value: '2 hours' },
							{ label: '3小时', value: '3 hours' },
							{ label: '6小时', value: '6 hours' },
							{ label: '12小时', value: '12 hours' },
							{ label: '1日', value: '1 day' },
							{ label: '2日', selected: true, value: '2 days' },
							{ label: '3日', value: '3 days' },
							{ label: '4日', value: '4 days' },
							{ label: '1周', value: '1 week' },
							{ label: '2周', value: '2 weeks' },
							{ label: '1月', value: '1 month' },
							{ label: '2月', value: '2 months' },
							{ label: '3月', value: '3 months' },
							{ label: '1年', value: '1 year' },
							{ label: '无限期', value:'indefinite' },
							{ label: '自定义…', value: 'custom' }
						]
					});
			} else {  // for non-existing pages
				var createlevel = field2.append({
						type: 'select',
						name: 'createlevel',
						label: '创建权限:',
						event: Twinkle.protect.formevents.createlevel
					});
				createlevel.append({
						type: 'option',
						label: '(站点默认)',
						value: 'all'
					});
				createlevel.append({
						type: 'option',
						label: '禁止未注册用户',
						value: 'autoconfirmed'
					});
				createlevel.append({
						type: 'option',
						label: '仅管理员',
						value: 'sysop',
						selected: true
					});
				field2.append({
						type: 'select',
						name: 'createexpiry',
						label: '终止时间:',
						event: function(e) {
							if (e.target.value === 'custom') {
								Twinkle.protect.doCustomExpiry(e.target);
							}
						},
						list: [
							{ label: '1小时', value: '1 hour' },
							{ label: '2小时', value: '2 hours' },
							{ label: '3小时', value: '3 hours' },
							{ label: '6小时', value: '6 hours' },
							{ label: '12小时', value: '12 hours' },
							{ label: '1日', value: '1 day' },
							{ label: '2日', value: '2 days' },
							{ label: '3日', value: '3 days' },
							{ label: '4日', value: '4 days' },
							{ label: '1周', value: '1 week' },
							{ label: '2周', value: '2 weeks' },
							{ label: '1月', value: '1 month' },
							{ label: '2月', value: '2 months' },
							{ label: '3月', value: '3 months' },
							{ label: '1年', value: '1 year' },
							{ label: '无限期', selected: true, value:'indefinite' },
							{ label: '自定义…', value: 'custom' }
						]
					});
			}
			field2.append({
					type: 'textarea',
					name: 'protectReason',
					label: '保护理由(日志):'
				});
			if (!mw.config.get('wgArticleId')) {  // tagging isn't relevant for non-existing pages
				break;
			}
			/* falls through */
		case 'tag':
			field1 = new QuickForm.element({ type: 'field', label: '标记选项', name: 'field1' });
			field1.append( {
					type: 'select',
					name: 'tagtype',
					label: '选择保护模板:',
					list: Twinkle.protect.protectionTags,
					event: Twinkle.protect.formevents.tagtype
				} );
			field1.append( {
					type: 'checkbox',
					list: [
						{
							name: 'small',
							label: '使用图标(small=yes)',
							tooltip: '将给模板加上|small=yes参数,显示成右上角的一把挂锁。',
							checked: true
						},
						{
							name: 'noinclude',
							label: '用<noinclude>包裹保护模板',
							tooltip: '将保护模板包裹在&lt;noinclude&gt;中',
							checked: (mw.config.get('wgNamespaceNumber') === 10)
						}
					]
				} );
			break;

		case 'request':
			field_preset = new QuickForm.element({ type: 'field', label: '保护类型', name: 'field_preset' });
			field_preset.append({
					type: 'select',
					name: 'category',
					label: '类型和理由:',
					event: Twinkle.protect.callback.changePreset,
					list: (mw.config.get('wgArticleId') ? Twinkle.protect.protectionTypes : Twinkle.protect.protectionTypesCreate)
				});

			field1 = new QuickForm.element({ type: 'field', label: '选项', name: 'field1' });
			field1.append( {
					type: 'select',
					name: 'expiry',
					label: '时长:',
					list: [
						{ label: '临时', value: 'temporary' },
						{ label: '永久', value: 'indefinite' },
						{ label: '', selected: true, value: '' }
					]
				} );
			field1.append({
					type: 'textarea',
					name: 'reason',
					label: '理由:'
				});
			break;
		default:
			alert("这玩意儿坏掉了!");
			break;
	}

	var oldfield;
	if (field_preset) {
		oldfield = $(e.target.form).find('fieldset[name="field_preset"]')[0];
		oldfield.parentNode.replaceChild(field_preset.render(), oldfield);
	} else {
		$(e.target.form).find('fieldset[name="field_preset"]').css('display', 'none');
	}
	if (field1) {
		oldfield = $(e.target.form).find('fieldset[name="field1"]')[0];
		oldfield.parentNode.replaceChild(field1.render(), oldfield);
	} else {
		$(e.target.form).find('fieldset[name="field1"]').css('display', 'none');
	}
	if (field2) {
		oldfield = $(e.target.form).find('fieldset[name="field2"]')[0];
		oldfield.parentNode.replaceChild(field2.render(), oldfield);
	} else {
		$(e.target.form).find('fieldset[name="field2"]').css('display', 'none');
	}

	if (e.target.values === 'protect') {
		// fake a change event on the preset dropdown
		var evt = document.createEvent( "Event" );
		evt.initEvent( 'change', true, true );
		e.target.form.category.dispatchEvent( evt );

		// re-add protection level text, if it's available
		if (Twinkle.protect.protectionLevel) {
			Status.init($('div[name="currentprot"] span').last()[0]);
			Status.info("当前保护级别", Twinkle.protect.protectionLevel);
		}
	}
};

Twinkle.protect.formevents = {
	editmodify: function twinkleprotectFormEditmodifyEvent(e) {
		e.target.form.editlevel.disabled = !e.target.checked;
		e.target.form.editexpiry.disabled = !e.target.checked || (e.target.form.editlevel.value === 'all');
		e.target.form.editlevel.style.color = e.target.form.editexpiry.style.color = (e.target.checked ? "" : "transparent");
	},
	editlevel: function twinkleprotectFormEditlevelEvent(e) {
		e.target.form.editexpiry.disabled = (e.target.value === 'all');
	},
	movemodify: function twinkleprotectFormMovemodifyEvent(e) {
		e.target.form.movelevel.disabled = !e.target.checked;
		e.target.form.moveexpiry.disabled = !e.target.checked || (e.target.form.movelevel.value === 'all');
		e.target.form.movelevel.style.color = e.target.form.moveexpiry.style.color = (e.target.checked ? "" : "transparent");
	},
	movelevel: function twinkleprotectFormMovelevelEvent(e) {
		e.target.form.moveexpiry.disabled = (e.target.value === 'all');
	},
	createlevel: function twinkleprotectFormCreatelevelEvent(e) {
		e.target.form.createexpiry.disabled = (e.target.value === 'all');
	},
	tagtype: function twinkleprotectFormTagtypeEvent(e) {
		e.target.form.small.disabled = e.target.form.noinclude.disabled = (e.target.value === 'none') || (e.target.value === 'noop');
	}
};

Twinkle.protect.doCustomExpiry = function twinkleprotectDoCustomExpiry(target) {
	var custom = prompt('输入自定义终止时间。\n您可以使用相对时间,如“1 minute”或“19 days”,或绝对时间“yyyymmddhhmm”(如“200602011406”是2006年02月01日14:06(UTC))', '');
	if (custom) {
		var option = document.createElement('option');
		option.setAttribute('value', custom);
		option.textContent = custom;
		target.appendChild(option);
		target.value = custom;
	}
};

Twinkle.protect.protectionTypes = [
	{
		label: '全保护',
		list: [
			{ label: '常规(全)', value: 'pp-protected' },
			{ label: '争议、编辑战(全)', value: 'pp-dispute' },
			{ label: '长期破坏(全)', value: 'pp-vandalism' },
			{ label: '高风险模板(全)', value: 'pp-template' },
			{ label: '已封禁用户的讨论页(全)', value: 'pp-usertalk' }
		]
	},
	{
		label: '半保护',
		list: [
			{ label: '常规(半)', value: 'pp-semi-protected' },
			{ label: '长期破坏(半)', value: 'pp-semi-vandalism' },
			{ label: '违反生者传记方针(半)', value: 'pp-semi-blp' },
			{ label: '傀儡破坏(半)', value: 'pp-semi-sock' },
			{ label: '高风险模板(半)', value: 'pp-semi-template' },
			{ label: '已封禁用户的讨论页(半)', value: 'pp-semi-usertalk' }
		]
	},
	{
		label: '移动保护',
		list: [
			{ label: '常规(移动)', value: 'pp-move' },
			{ label: '争议、移动战(移动)', value: 'pp-move-dispute' },
			{ label: '移动破坏(移动)', value: 'pp-move-vandalism' },
			{ label: '高风险页面(移动)', value: 'pp-move-indef' }
		]
	},
	{ label: '解除保护', value: 'unprotect' }
];

Twinkle.protect.protectionTypesCreate = [
	{
		label: '白纸保护',
		list: [
			{ label: '常规', value: 'pp-create' }
		]
	},
	{ label: '解除保护', value: 'unprotect' }
];

// NOTICE: keep this synched with [[MediaWiki:Protect-dropdown]]
Twinkle.protect.protectionPresetsInfo = {
	'pp-protected': {
		edit: 'sysop',
		move: 'sysop',
		reason: null
	},
	'pp-dispute': {
		edit: 'sysop',
		move: 'sysop',
		reason: '编辑战'
	},
	'pp-vandalism': {
		edit: 'sysop',
		move: 'sysop',
		reason: '被自动确认用户破坏'
	},
	'pp-template': {
		edit: 'sysop',
		move: 'sysop',
		reason: '高风险模板'
	},
	'pp-usertalk': {
		edit: 'sysop',
		move: 'sysop',
		reason: '已封禁用户滥用其对话页'
	},
	'pp-semi-vandalism': {
		edit: 'autoconfirmed',
		reason: '被IP用户或新用户破坏',
		template: 'pp-vandalism'
	},
	'pp-semi-blp': {
		edit: 'autoconfirmed',
		reason: 'IP用户或新用户违反生者传记方针'
	},
	'pp-semi-usertalk': {
		edit: 'autoconfirmed',
		move: 'sysop',
		reason: '已封禁用户滥用其对话页'
	},
	'pp-semi-template': {
		edit: 'autoconfirmed',
		move: 'sysop',
		reason: '高风险模板',
		template: 'pp-template'
	},
	'pp-semi-sock': {
		edit: 'autoconfirmed',
		reason: '持续的傀儡破坏'
	},
	'pp-semi-protected': {
		edit: 'autoconfirmed',
		reason: null,
		template: 'pp-protected'
	},
	'pp-move': {
		move: 'sysop',
		reason: null
	},
	'pp-move-dispute': {
		move: 'sysop',
		reason: '页面移动战'
	},
	'pp-move-vandalism': {
		move: 'sysop',
		reason: '移动破坏'
	},
	'pp-move-indef': {
		move: 'sysop',
		reason: '高风险页面'
	},
	'unprotect': {
		edit: 'all',
		move: 'all',
		create: 'all',
		reason: null,
		template: 'none'
	},
	'pp-create': {
		create: 'sysop',
		reason: '{{pp-create}}'
	}
};

Twinkle.protect.protectionTags = [
	{
		label: '无(移除现有模板)',
		value: 'none'
	},
	{
		label: '无(不移除现有模板)',
		value: 'noop'
	},
	{
		label: '全保护模板',
		list: [
			{ label: '{{pp-dispute}}: 争议', value: 'pp-dispute', selected: true },
			{ label: '{{pp-usertalk}}: 封禁的用户', value: 'pp-usertalk' }
		]
	},
	{
		label: '全、半保护模板',
		list: [
			{ label: '{{pp-vandalism}}: 破坏', value: 'pp-vandalism' },
			{ label: '{{pp-template}}: 高风险模板', value: 'pp-template' },
			{ label: '{{pp-protected}}: 常规', value: 'pp-protected' }
		]
	},
	{
		label: '半保护模板',
		list: [
			{ label: '{{pp-semi-usertalk}}: 封禁的用户', value: 'pp-semi-usertalk' },
			{ label: '{{pp-semi-sock}}: 傀儡', value: 'pp-semi-sock' },
			{ label: '{{pp-semi-blp}}: 生者传记', value: 'pp-semi-blp' },
			{ label: '{{pp-semi-indef}}: 长期', value: 'pp-semi-indef' }
		]
	},
	{
		label: '移动保护模板',
		list: [
			{ label: '{{pp-move-dispute}}: 争议', value: 'pp-move-dispute' },
			{ label: '{{pp-move-vandalism}}: 破坏', value: 'pp-move-vandalism' },
			{ label: '{{pp-move-indef}}: 长期', value: 'pp-move-indef' },
			{ label: '{{pp-move}}: 常规', value: 'pp-move' }
		]
	}
];

Twinkle.protect.callback.changePreset = function twinkleprotectCallbackChangePreset(e) {
	var form = e.target.form;

	var actiontypes = form.actiontype;
	var actiontype;
	for( var i = 0; i < actiontypes.length; i++ )
	{
		if( !actiontypes[i].checked ) {
			continue;
		}
		actiontype = actiontypes[i].values;
		break;
	}

	if (actiontype === 'protect') {  // actually protecting the page
		var item = Twinkle.protect.protectionPresetsInfo[form.category.value];
		if (mw.config.get('wgArticleId')) {
			if (item.edit) {
				form.editmodify.checked = true;
				Twinkle.protect.formevents.editmodify({ target: form.editmodify });
				form.editlevel.value = item.edit;
				Twinkle.protect.formevents.editlevel({ target: form.editlevel });
			} else {
				form.editmodify.checked = false;
				Twinkle.protect.formevents.editmodify({ target: form.editmodify });
			}

			if (item.move) {
				form.movemodify.checked = true;
				Twinkle.protect.formevents.movemodify({ target: form.movemodify });
				form.movelevel.value = item.move;
				Twinkle.protect.formevents.movelevel({ target: form.movelevel });
			} else {
				form.movemodify.checked = false;
				Twinkle.protect.formevents.movemodify({ target: form.movemodify });
			}
		} else {
			if (item.create) {
				form.createlevel.value = item.create;
				Twinkle.protect.formevents.createlevel({ target: form.createlevel });
			}
		}

		var reasonField = (actiontype === "protect" ? form.protectReason : form.reason);
		if (item.reason) {
			reasonField.value = item.reason;
		} else {
			reasonField.value = '';
		}

		// sort out tagging options
		if (mw.config.get('wgArticleId')) {
			if( form.category.value === 'unprotect' ) {
				form.tagtype.value = 'none';
			} else {
				form.tagtype.value = (item.template ? item.template : form.category.value);
			}
			Twinkle.protect.formevents.tagtype({ target: form.tagtype });

			if( /template/.test( form.category.value ) ) {
				form.noinclude.checked = true;
				form.editexpiry.value = form.moveexpiry.value = "indefinite";
			} else {
				form.noinclude.checked = false;
			}
		}

	} else {  // RPP request
		if( form.category.value === 'unprotect' ) {
			form.expiry.value = '';
			form.expiry.disabled = true;
		} else {
			form.expiry.disabled = false;
		}
	}
};

Twinkle.protect.callback.evaluate = function twinkleprotectCallbackEvaluate(e) {
	var form = e.target;

	var actiontypes = form.actiontype;
	var actiontype;
	for( var i = 0; i < actiontypes.length; i++ )
	{
		if( !actiontypes[i].checked ) {
			continue;
		}
		actiontype = actiontypes[i].values;
		break;
	}

	if( !mw.config.get('wgArticleId') ) {
		tagparams = {
			tag: 'noop'
		};
	} else if( actiontype === 'tag' || actiontype === 'protect' ) {
		tagparams = {
			tag: form.tagtype.value,
			reason: ((form.tagtype.value === 'pp-protected' || form.tagtype.value === 'pp-semi-protected' || form.tagtype.value === 'pp-move') && form.protectReason) ? form.protectReason.value : null,
			expiry: (actiontype === 'protect') ? (form.editmodify.checked ? form.editexpiry.value : (form.movemodify.checked ?
				form.moveexpiry.value : null)) : null,
			small: form.small.checked,
			noinclude: form.noinclude.checked
		};
	}

	switch (actiontype) {
		case 'protect':
			// protect the page
			var thispage = new Wikipedia.page(mw.config.get('wgPageName'), "保护页面");
			if (mw.config.get('wgArticleId')) {
				if (form.editmodify.checked) {
					thispage.setEditProtection(form.editlevel.value, form.editexpiry.value);
				}
				if (form.movemodify.checked) {
					thispage.setMoveProtection(form.movelevel.value, form.moveexpiry.value);
				}
			} else {
				thispage.setCreateProtection(form.createlevel.value, form.createexpiry.value);
			}
			if (form.protectReason.value) {
				thispage.setEditSummary(form.protectReason.value);
			} else {
				alert("您必须输入保护理由,这会出现在日志中。");
				return;
			}

			SimpleWindow.setButtonsEnabled( false );
			Status.init( form );

			Wikipedia.actionCompleted.redirect = mw.config.get('wgPageName');
			Wikipedia.actionCompleted.notice = "保护完成";

			thispage.protect();
			/* falls through */
		case 'tag':

			if (actiontype === 'tag') {
				SimpleWindow.setButtonsEnabled( false );
				Status.init( form );
				Wikipedia.actionCompleted.redirect = mw.config.get('wgPageName');
				Wikipedia.actionCompleted.followRedirect = false;
				Wikipedia.actionCompleted.notice = "标记完成";
			}

			if (tagparams.tag === 'noop') {
				Status.info("应用保护模板", "无关");
				break;
			}

			var protectedPage = new Wikipedia.page( mw.config.get('wgPageName'), '标记页面');
			protectedPage.setCallbackParameters( tagparams );
			protectedPage.load( Twinkle.protect.callbacks.taggingPage );
			break;

		case 'request':
			// file request at RPP
			var typename, typereason;
			switch( form.category.value ) {
				case 'pp-dispute':
				case 'pp-vandalism':
				case 'pp-template':
				case 'pp-usertalk':
				case 'pp-protected':
					typename = '全保护';
					break;
				case 'pp-semi-vandalism':
				case 'pp-semi-usertalk':
				case 'pp-semi-template':
				case 'pp-semi-sock':
				case 'pp-semi-blp':
				case 'pp-semi-protected':
					typename = '半保护';
					break;
				case 'pp-move':
				case 'pp-move-dispute':
				case 'pp-move-indef':
				case 'pp-move-vandalism':
					typename = '移动保护';
					break;
				case 'pp-create':
				case 'pp-create-offensive':
				case 'pp-create-blp':
				case 'pp-create-salt':
					typename = '白纸保护';
					break;
				case 'unprotect':
					/* falls through */
				default:
					typename = '解除保护';
					break;
			}
			switch (form.category.value) {
				case 'pp-dispute':
					typereason = '争议、编辑战';
					break;
				case 'pp-vandalism':
				case 'pp-semi-vandalism':
					typereason = '长期破坏';
					break;
				case 'pp-template':
				case 'pp-semi-template':
					typereason = '高风险模板';
					break;
				case 'pp-usertalk':
				case 'pp-semi-usertalk':
					typereason = '已封禁用户的讨论页';
					break;
				case 'pp-semi-sock':
					typereason = '傀儡破坏';
					break;
				case 'pp-semi-blp':
					typereason = '违反生者传记方针';
					break;
				case 'pp-move-dispute':
					typereason = '争议、移动战';
					break;
				case 'pp-move-vandalism':
					typereason = '移动破坏';
					break;
				case 'pp-move-indef':
					typereason = '高风险页面';
					break;
				default:
					typereason = '';
					break;
			}

			var reason = typereason;
			if( form.reason.value !== '') {
				if ( typereason !== '' ) {
					reason += ":";
				}
				reason += form.reason.value;
			}
			if( reason !== '' && reason.charAt( reason.length - 1 ) !== '。' ) {
				reason += '。';
			}

			var rppparams = {
				reason: reason,
				typename: typename,
				category: form.category.value,
				expiry: form.expiry.value
			};

			SimpleWindow.setButtonsEnabled( false );
			Status.init( form );

			rppName = 'Wikipedia:请求保护页面';

			// Updating data for the action completed event
			Wikipedia.actionCompleted.redirect = rppName;
			Wikipedia.actionCompleted.notice = "提名完成,重定向到讨论页";

			var rppPage = new Wikipedia.page( rppName, '请求保护页面');
			rppPage.setFollowRedirect( true );
			rppPage.setCallbackParameters( rppparams );
			rppPage.load( Twinkle.protect.callbacks.fileRequest );
			break;
		default:
			alert("twinkleprotect: 未知操作类型");
			break;
	}
};

Twinkle.protect.callbacks = {
	taggingPage: function( protectedPage ) {
		var params = protectedPage.getCallbackParameters();
		var text = protectedPage.getPageText();
		var tag, summary;

		var oldtag_re = /\s*(?:<noinclude>)?\s*\{\{\s*(pp-[^{}]*?|protected|(?:t|v|s|p-|usertalk-v|usertalk-s|sb|move)protected(?:2)?|protected template|privacy protection)\s*?\}\}\s*(?:<\/noinclude>)?\s*/gi;

		text = text.replace( oldtag_re, '' );

		if ( params.tag !== 'none' ) {
			tag = params.tag;
			if( params.reason ) {
				tag += '|reason=' + params.reason;
			}
			if( ['indefinite', 'infinite', 'never', null].indexOf(params.expiry) === -1 ) {
				tag += '|expiry={{subst:#time:c|' + (/^\s*\d+\s*$/.exec(params.expiry) ? params.expiry : '+' + params.expiry) + '}}';
			}
			if( params.small ) {
				tag += '|small=yes';
			}
		}

		if( params.tag === 'none' ) {
			summary = '移除保护模板' + Twinkle.getPref('summaryAd');
		} else {
			if( params.noinclude ) {
				text = "<noinclude>{{" + tag + "}}</noinclude>" + text;
			} else {
				text = "{{" + tag + "}}\n" + text;
			}
			summary = "添加{{" + params.tag + "}}" + Twinkle.getPref('summaryAd');
		}

		protectedPage.setEditSummary( summary );
		protectedPage.setPageText( text );
		protectedPage.setCreateOption( 'nocreate' );
		protectedPage.save();
	},

	fileRequest: function( rppPage ) {

		var params = rppPage.getCallbackParameters();
		var text = rppPage.getPageText();
		var statusElement = rppPage.getStatusElement();

		var rppRe = new RegExp( '===\\s*[[' + RegExp.escape( mw.config.get('wgPageName'), true ) + ']]\\s*===', 'm' );
		var tag = rppRe.exec( text );

		var rppLink = document.createElement('a');
		rppLink.setAttribute('href', mw.util.getUrl(rppPage.getPageName()) );
		rppLink.appendChild(document.createTextNode(rppPage.getPageName()));

		if ( tag ) {
			statusElement.error( [ '已有对此条目的保护提名,在 ', rppLink, ',取消操作。' ] );
			return;
		}

		var newtag = '=== [[' + mw.config.get('wgPageName') +  ']] ===' + "\n";
		if( ( new RegExp( '^' + RegExp.escape( newtag ).replace( /\s+/g, '\\s*' ), 'm' ) ).test( text ) ) {
			statusElement.error( [ '已有对此条目的保护提名,在 ', rppLink, ',取消操作。' ] );
			return;
		}

		var words;
		switch( params.expiry ) {
		case 'temporary':
			words = "临时";
			break;
		case 'indefinite':
			words = "永久";
			break;
		default:
			words = "";
			break;
		}

		words += params.typename;

		newtag += "请求" + words.toUpperCaseFirstChar() + ( params.reason !== '' ? ":" + params.reason : "。" ) + "--~~~~";

		var reg;
		if ( params.category === 'unprotect' ) {
			reg = /(==\s*请求解除保护\s*==\n)/;
		} else {
			reg = /(==\s*请求保护\s*==\n)/;
		}
		var originalTextLength = text.length;
		text = text.replace( reg, "$1" + newtag + "\n");
		if (text.length === originalTextLength)
		{
			statusElement.error( '无法在WP:RFPP上找到相关位点标记,取消操作。' );
			return;
		}
		statusElement.status( '添加新提名…' );
		rppPage.setEditSummary( '请求对[[' + mw.config.get('wgPageName').replace(/_/g, ' ') + ']]' + params.typename + Twinkle.getPref('summaryAd') );
		rppPage.setPageText( text );
		rppPage.setCreateOption( 'recreate' );
		rppPage.save();
	}
};

/*
 * vim: set noet sts=0 sw=8:
 ****************************************
 *** twinklexfd.js: XFD module
 ****************************************
 * Mode of invocation:     Tab ("XFD")
 * Active on:              Existing, non-special pages, except for file pages with no local (non-Commons) file which are not redirects
 * Config directives in:   TwinkleConfig
 */

Twinkle.xfd = function twinklexfd() {
	// Disable on:
	// * special pages
	// * non-existent pages
	// * files on Commons, whether there is a local page or not (unneeded local pages of files on Commons are eligible for CSD F2)
	// * file pages without actual files (these are eligible for CSD G8)
	if ( mw.config.get('wgNamespaceNumber') < 0 || !mw.config.get('wgArticleId') || (mw.config.get('wgNamespaceNumber') === 6 && (document.getElementById('mw-sharedupload') || (!document.getElementById('mw-imagepage-section-filehistory') && !Wikipedia.isPageRedirect()))) ) {
		return;
	}
	if (twinkleUserAuthorized) {
		$(twAddPortletLink("#", "提删", "tw-xfd", "提交删除讨论", "")).click(Twinkle.xfd.callback);
	} else {
		$(twAddPortletLink("#", '提删', 'tw-xfd', '提交删除讨论', '')).click(function() {
			alert("您尚未达到自动确认。");
		});
	}
};

Twinkle.xfd.currentRationale = null;

// error callback on Status object
Twinkle.xfd.printRationale = function twinklexfdPrintRationale() {
	if (Twinkle.xfd.currentRationale) {
		var p = document.createElement("p");
		p.textContent = "您理由已在下方提供,如果您想重新提交,请将其复制到一新窗口中:";
		var pre = document.createElement("pre");
		pre.className = "toccolours";
		pre.style.marginTop = "0";
		pre.textContent = Twinkle.xfd.currentRationale;
		p.appendChild(pre);
		Status.root.appendChild(p);
		// only need to print the rationale once
		Twinkle.xfd.currentRationale = null;
	}
};

Twinkle.xfd.callback = function twinklexfdCallback() {
	var Window = new SimpleWindow( 600, 350 );
	Window.setTitle( "提交存废讨论" );
	Window.setScriptName( "Twinkle" );
	Window.addFooterLink( "关于存废讨论", "WP:XFD" );
	Window.addFooterLink( "Twinkle帮助", "WP:TW/DOC#xfd" );

	var form = new QuickForm( Twinkle.xfd.callback.evaluate );
	var categories = form.append( {
			type: 'select',
			name: 'category',
			label: '提交类型:',
			event: Twinkle.xfd.callback.change_category
		} );
	categories.append( {
			type: 'option',
			label: '页面存废讨论',
			selected: mw.config.get('wgNamespaceNumber') !== Namespace.IMAGE,
			value: 'afd'
		} );
	categories.append( {
			type: 'option',
			label: '文件存废讨论',
			selected: mw.config.get('wgNamespaceNumber') === Namespace.IMAGE,
			value: 'ffd'
		} );
	form.append( {
			type: 'checkbox',
			list: [
				{
					label: '如可能,通知页面创建者',
					value: 'notify',
					name: 'notify',
					tooltip: "在页面创建者对话页上放置一通知模板。",
					checked: true
				}
			]
		}
	);
	form.append( {
			type: 'field',
			label:'工作区',
			name: 'work_area'
		} );
	form.append( { type:'submit' } );

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

	// We must init the controls
	var evt = document.createEvent( "Event" );
	evt.initEvent( 'change', true, true );
	result.category.dispatchEvent( evt );
};

Twinkle.xfd.callback.change_category = function twinklexfdCallbackChangeCategory(e) {
	var value = e.target.value;
	var root = e.target.form;
	var old_area;
	var childNodes = root.childNodes;
	for( var i = 0; i < childNodes.length; ++i ) {
		var node = childNodes[i];
		if (node instanceof Element &&
		    node.getAttribute( 'name' ) === 'work_area') {
			old_area = node;
			break;
		}
	}
	var work_area = null;

	var oldreasontextbox = e.target.form.getElementsByTagName('textarea')[0];
	var oldreason = (oldreasontextbox ? oldreasontextbox.value : '');

	switch( value ) {
	case 'afd':
		work_area = new QuickForm.element( {
				type: 'field',
				label: '页面存废讨论',
				name: 'work_area'
			} );
		work_area.append( {
				type: 'checkbox',
				list: [
						{
							label: '使用<noinclude>包裹模板',
							value: 'noinclude',
							name: 'noinclude',
							checked: mw.config.get('wgNamespaceNumber') === Namespace.TEMPLATE,
							tooltip: '使其不会在被包含时出现。'
						}
					]
		} );
		var afd_category = work_area.append( {
				type:'select',
				name:'xfdcat',
				label:'选择提删类别:',
				event:Twinkle.xfd.callback.change_afd_category
			} );

		afd_category.append( { type:'option', label:'删除', value:'delete', selected:true } );
		afd_category.append( { type:'option', label:'合并', value:'merge' } );
		afd_category.append( { type:'option', label:'移动到维基辞典', value:'vmd' } );
		afd_category.append( { type:'option', label:'移动到维基文库', value:'vms' } );
		afd_category.append( { type:'option', label:'移动到维基教科书', value:'vmb' } );
		afd_category.append( { type:'option', label:'移动到维基语录', value:'vmq' } );

		work_area.append( {
				type: 'input',
				name: 'mergeinto',
				label: '合并到:',
				disabled: true
			} );

		work_area.append( {
				type: 'textarea',
				name: 'xfdreason',
				label: '理由:',
				value: oldreason
			} );
		work_area = work_area.render();
		old_area.parentNode.replaceChild( work_area, old_area );
		break;
	case 'ffd':
		work_area = new QuickForm.element( {
				type: 'field',
				label: '文件存废讨论',
				name: 'work_area'
			} );
		work_area.append( {
				type: 'textarea',
				name: 'xfdreason',
				label: '理由:',
				value: oldreason
			} );
		work_area = work_area.render();
		old_area.parentNode.replaceChild( work_area, old_area );
		break;
	default:
		work_area = new QuickForm.element( {
				type: 'field',
				label: '未定义',
				name: 'work_area'
			} );
		work_area = work_area.render();
		old_area.parentNode.replaceChild( work_area, old_area );
		break;
	}
};

Twinkle.xfd.callback.change_afd_category = function twinklexfdCallbackChangeAfdCategory(e) {
	if( e.target.value === 'merge' ) {
		e.target.form.mergeinto.disabled = false;
	} else {
		e.target.form.mergeinto.disabled = true;
	}
}

Twinkle.xfd.callbacks = {
	afd: {
		main: function(pageobj) {
			// this is coming in from lookupCreator...!
			var params = pageobj.getCallbackParameters();

			// Adding discussion
			wikipedia_page = new Wikipedia.page(params.logpage, "添加讨论到当日列表");
			wikipedia_page.setFollowRedirect(true);
			wikipedia_page.setCallbackParameters(params);
			wikipedia_page.load(Twinkle.xfd.callbacks.afd.todaysList);

			// Notification to first contributor
			if(params.usertalk) {
				var initialContrib = pageobj.getCreator();
				var usertalkpage = new Wikipedia.page('User talk:' + initialContrib, "通知页面创建者(" + initialContrib + ")");
				var notifytext = "\n{{subst:AFDNote|" + mw.config.get('wgPageName') + "}}--~~~~";
				usertalkpage.setAppendText(notifytext);
				usertalkpage.setEditSummary("通知:页面[[" + mw.config.get('wgPageName') + "]]存废讨论提名。" + Twinkle.getPref('summaryAd'));
				usertalkpage.setCreateOption('recreate');
				switch (Twinkle.getPref('xfdWatchUser')) {
					case 'yes':
						usertalkpage.setWatchlist(true);
						break;
					case 'no':
						usertalkpage.setWatchlistFromPreferences(false);
						break;
					default:
						usertalkpage.setWatchlistFromPreferences(true);
						break;
				}
				usertalkpage.setFollowRedirect(true);
				usertalkpage.append();
			}
		},
		taggingArticle: function(pageobj) {
			var text = pageobj.getPageText();
			var params = pageobj.getCallbackParameters();
			var tag = '{{subst:vfd/auto|' + params.reason;

			switch ( params.xfdcat ) {
				case 'vmd':
					tag += '|wikt';
					break;
				case 'vms':
					tag += '|s';
					break;
				case 'vmb':
					tag += '|b';
					break;
				case 'vmq':
					tag += '|q';
					break;
				default:
					break;
			}
			tag += '}}';
			if ( params.noinclude ) {
				tag = '<noinclude>' + tag + '</noinclude>';
			}

			pageobj.setPageText(tag + "\n" + text);
			pageobj.setEditSummary("页面存废讨论:[[" + params.logpage + "#" + mw.config.get('wgPageName') + "]]" + Twinkle.getPref('summaryAd'));
			switch (Twinkle.getPref('xfdWatchPage')) {
				case 'yes':
					pageobj.setWatchlist(true);
					break;
				case 'no':
					pageobj.setWatchlistFromPreferences(false);
					break;
				default:
					pageobj.setWatchlistFromPreferences(true);
					break;
			}
			// pageobj.setCreateOption('recreate');
			pageobj.save();

			if( Twinkle.getPref('markXfdPagesAsPatrolled') ) {
				pageobj.patrol();
			}
		},
		todaysList: function(pageobj) {
			var text = pageobj.getPageText();
			var params = pageobj.getCallbackParameters();
			var type = '';
			var to = '';

			switch ( params.xfdcat ) {
				case 'vmd':
				case 'vms':
				case 'vmb':
				case 'vmq':
					type = 'vm';
					to = params.xfdcat;
					break;
				case 'merge':
					to = params.mergeinto;
					/* Fall through */
				default:
					type = params.xfdcat;
					break;
			}

			pageobj.setAppendText("\n{{subst:DRItem|Type=" + type + "|DRarticles=" + mw.config.get('wgPageName') + "|Reason=" + params.reason + "|To=" + to + "}}--~~~~");
			pageobj.setEditSummary("添加[[" + mw.config.get('wgPageName') + "]]。" + Twinkle.getPref('summaryAd'));
			switch (Twinkle.getPref('xfdWatchDiscussion')) {
				case 'yes':
					pageobj.setWatchlist(true);
					break;
				case 'no':
					pageobj.setWatchlistFromPreferences(false);
					break;
				default:
					pageobj.setWatchlistFromPreferences(true);
					break;
			}
			pageobj.setCreateOption('recreate');
			pageobj.append();
			Twinkle.xfd.currentRationale = null;  // any errors from now on do not need to print the rationale, as it is safely saved on-wiki
		}
	},

	ffd: {
		main: function(pageobj) {
			// this is coming in from lookupCreator...!
			var params = pageobj.getCallbackParameters();
			var initialContrib = pageobj.getCreator();
			params.uploader = initialContrib;

			// Adding discussion
			wikipedia_page = new Wikipedia.page(params.logpage, "添加讨论到当日列表");
			wikipedia_page.setFollowRedirect(true);
			wikipedia_page.setCallbackParameters(params);
			wikipedia_page.load(Twinkle.xfd.callbacks.ffd.todaysList);

			// Notification to first contributor
			if(params.usertalk) {
				var usertalkpage = new Wikipedia.page('User talk:' + initialContrib, "通知页面创建者(" + initialContrib + ")");
				var notifytext = "\n{{subst:idw|File:" + mw.config.get('wgTitle') + "}}";
				usertalkpage.setAppendText(notifytext);
				usertalkpage.setEditSummary("通知:文件[[" + mw.config.get('wgPageName') + "]]存废讨论提名。" + Twinkle.getPref('summaryAd'));
				usertalkpage.setCreateOption('recreate');
				switch (Twinkle.getPref('xfdWatchUser')) {
					case 'yes':
						usertalkpage.setWatchlist(true);
						break;
					case 'no':
						usertalkpage.setWatchlistFromPreferences(false);
						break;
					default:
						usertalkpage.setWatchlistFromPreferences(true);
						break;
				}
				usertalkpage.setFollowRedirect(true);
				usertalkpage.append();
			}
		},
		taggingImage: function(pageobj) {
			var text = pageobj.getPageText();
			var params = pageobj.getCallbackParameters();
i
			pageobj.setPageText("{{ifd|" + params.reason + "|date={{subst:#time:c}}}}\n" + text);
			pageobj.setEditSummary("文件存废讨论:[[" + params.logpage + "#" + mw.config.get('wgPageName') + "]]" + Twinkle.getPref('summaryAd'));
			switch (Twinkle.getPref('xfdWatchPage')) {
				case 'yes':
					pageobj.setWatchlist(true);
					break;
				case 'no':
					pageobj.setWatchlistFromPreferences(false);
					break;
				default:
					pageobj.setWatchlistFromPreferences(true);
					break;
			}
			pageobj.setCreateOption('recreate');  // it might be possible for a file to exist without a description page
			pageobj.save();
		},
		todaysList: function(pageobj) {
			var text = pageobj.getPageText();
			var params = pageobj.getCallbackParameters();

			pageobj.setAppendText("\n{{subst:IfdItem|Filename=" + mw.config.get('wgTitle') + "|Uploader=" + params.uploader + "|Reason=" + params.reason + "}}--~~~~");
			pageobj.setEditSummary("添加[[" + mw.config.get('wgPageName') + "]]。" + Twinkle.getPref('summaryAd'));
			switch (Twinkle.getPref('xfdWatchDiscussion')) {
				case 'yes':
					pageobj.setWatchlist(true);
					break;
				case 'no':
					pageobj.setWatchlistFromPreferences(false);
					break;
				default:
					pageobj.setWatchlistFromPreferences(true);
					break;
			}
			pageobj.setCreateOption('recreate');
			pageobj.append();
			Twinkle.xfd.currentRationale = null;  // any errors from now on do not need to print the rationale, as it is safely saved on-wiki
		}
	}
};



Twinkle.xfd.callback.evaluate = function(e) {
	mw.config.set('wgPageName', mw.config.get('wgPageName').replace(/_/g, ' '));  // for queen/king/whatever and country!

	var type =  e.target.category.value;
	var usertalk = e.target.notify.checked;
	var reason = e.target.xfdreason.value;
	var xfdcat, mergeinto, noinclude;
	if( type === 'afd' ) {
		xfdcat = e.target.xfdcat.value;
		if( xfdcat === 'merge' ) {
			mergeinto = e.target.mergeinto.value;
		}
	}

	SimpleWindow.setButtonsEnabled( false );
	Status.init( e.target );

	Twinkle.xfd.currentRationale = reason;
	Status.onError(Twinkle.xfd.printRationale);

	if( !type ) {
		Status.error( '错误', '未定义的动作' );
		return;
	}

	var query, wikipedia_page, wikipedia_api, logpage, params;
	var date = new Date();
	function twodigits(num) {
		return num < 10 ? '0' + num : num;
	};
	switch( type ) {

	case 'afd': // AFD
		var dateString = date.getUTCFullYear() + '/' + twodigits(date.getUTCMonth() + 1) + '/' + twodigits(date.getUTCDate());
		logpage = 'Wikipedia:頁面存廢討論/記錄/' + dateString;
		params = { usertalk: usertalk, xfdcat: xfdcat, mergeinto: mergeinto, noinclude: noinclude, reason: reason, logpage: logpage };

		Wikipedia.addCheckpoint();
		// Updating data for the action completed event
		Wikipedia.actionCompleted.redirect = logpage;
		Wikipedia.actionCompleted.notice = "提名完成,重定向到讨论页";

		// Tagging file
		wikipedia_page = new Wikipedia.page(mw.config.get('wgPageName'), "添加存废讨论模板到页面");
		wikipedia_page.setFollowRedirect(true);
		wikipedia_page.setCallbackParameters(params);
		wikipedia_page.load(Twinkle.xfd.callbacks.afd.taggingArticle);

		// Contributor specific edits
		wikipedia_page = new Wikipedia.page(mw.config.get('wgPageName'));
		wikipedia_page.setCallbackParameters(params);
		wikipedia_page.lookupCreator(Twinkle.xfd.callbacks.afd.main);

		Wikipedia.removeCheckpoint();
		break;

	case 'ffd': // FFD
		var dateString = date.getUTCFullYear() + '/' + twodigits(date.getUTCMonth() + 1) + '/' + twodigits(date.getUTCDate());
		logpage = 'Wikipedia:檔案存廢討論/記錄/' + dateString;
		params = { usertalk: usertalk, reason: reason, logpage: logpage };

		Wikipedia.addCheckpoint();
		// Updating data for the action completed event
		Wikipedia.actionCompleted.redirect = logpage;
		Wikipedia.actionCompleted.notice = "提名完成,重定向到讨论页";

		// Tagging file
		wikipedia_page = new Wikipedia.page(mw.config.get('wgPageName'), "添加存废讨论模板到文件描述页");
		wikipedia_page.setFollowRedirect(true);
		wikipedia_page.setCallbackParameters(params);
		wikipedia_page.load(Twinkle.xfd.callbacks.ffd.taggingImage);

		// Contributor specific edits
		wikipedia_page = new Wikipedia.page(mw.config.get('wgPageName'));
		wikipedia_page.setCallbackParameters(params);
		wikipedia_page.lookupCreator(Twinkle.xfd.callbacks.ffd.main);

		Wikipedia.removeCheckpoint();
		break;

	default:
		alert("twinklexfd:未定义的类别");
		break;
	}
};

/*
 * vim: set noet sts=0 sw=8:
 ****************************************
 *** twinklebatchdelete.js: Batch delete module (sysops only)
 ****************************************
 * Mode of invocation:     Tab ("D-batch")
 * Active on:              Existing and non-existing non-articles, and Special:PrefixIndex
 * Config directives in:   TwinkleConfig
 */


Twinkle.batchdelete = function twinklebatchdelete() {
	//if( userIsInGroup( 'sysop' ) && (mw.config.get( 'wgNamespaceNumber' ) > 0 || mw.config.get( 'wgCanonicalSpecialPageName' ) === 'Prefixindex') ) {
		$(twAddPortletLink("#", "批删", "tw-batch", "删除此分类或页面中的所有链接", "")).click(Twinkle.batchdelete.callback);
	//}
};

Twinkle.batchdelete.unlinkCache = {};
Twinkle.batchdelete.callback = function twinklebatchdeleteCallback() {
	var Window = new SimpleWindow( 800, 400 );
	Window.setTitle( "批量删除" );
	Window.setScriptName( "Twinkle" );
	Window.addFooterLink( "Twinkle帮助", "WP:TW/DOC#batchdelete" );

	var form = new QuickForm( Twinkle.batchdelete.callback.evaluate );
	form.append( {
			type: 'checkbox',
			list: [
				{ 
					label: '删除页面',
					name: 'delete_page',
					value: 'delete',
					checked: true
				},
				{
					label: '取消链入',
					name: 'unlink_page',
					value: 'unlink',
					checked: true
				},
				{
					label: '删除重定向',
					name: 'delete_redirects',
					value: 'delete_redirects',
					checked: true
				}
			]
		} );
	form.append( {
			type: 'textarea',
			name: 'reason',
			label: '理由:'
		} );

	var query;
	if( mw.config.get( 'wgNamespaceNumber' ) === Namespace.CATEGORY ) {

		query = {
			'action': 'query',
			'generator': 'categorymembers',
			'gcmtitle': mw.config.get( 'wgPageName' ),
			'gcmlimit' : Twinkle.getPref('batchMax'), // the max for sysops
			'prop': [ 'categories', 'revisions' ],
			'rvprop': [ 'size' ]
		};
	} else if( mw.config.get( 'wgCanonicalSpecialPageName' ) === 'Prefixindex' ) {

		var gapnamespace, gapprefix;
		if(QueryString.exists( 'from' ) )
		{
			gapnamespace = QueryString.get( 'namespace' );
			gapprefix = QueryString.get( 'from' ).toUpperCaseFirstChar();
		}
		else
		{
			var pathSplit = location.pathname.split('/');
			if (pathSplit.length < 3 || pathSplit[2] !== "Special:PrefixIndex") {
				return;
			}
			var titleSplit = pathSplit[3].split(':');
			gapnamespace = Namespace[titleSplit[0].toUpperCase()];
			if ( titleSplit.length < 2 || typeof(gapnamespace) === 'undefined' )
			{
				gapnamespace = Namespace.MAIN;
				gapprefix = pathSplit.splice(3).join('/');
			}
			else
			{
				pathSplit = pathSplit.splice(4);
				pathSplit.splice(0,0,titleSplit.splice(1).join(':'));
				gapprefix = pathSplit.join('/');
			}
		}

		query = {
			'action': 'query',
			'generator': 'allpages',
			'gapnamespace': gapnamespace ,
			'gapprefix': gapprefix,
			'gaplimit' : Twinkle.getPref('batchMax'), // the max for sysops
			'prop' : ['categories', 'revisions' ],
			'rvprop': [ 'size' ]
		};
	} else {
		query = {
			'action': 'query',
			'generator': 'links',
			'titles': mw.config.get( 'wgPageName' ),
			'gpllimit' : Twinkle.getPref('batchMax'), // the max for sysops
			'prop': [ 'categories', 'revisions' ],
			'rvprop': [ 'size' ]
		};
	}

	var wikipedia_api = new Wikipedia.api( '抓取页面', query, function( self ) {
			var xmlDoc = self.responseXML;
			var snapshot = xmlDoc.evaluate('//page[@ns != "' + Namespace.IMAGE + '" and not(@missing)]', xmlDoc, null, XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE, null );
			var list = [];
			for ( var i = 0; i < snapshot.snapshotLength; ++i ) {
				var object = snapshot.snapshotItem(i);
				var page = xmlDoc.evaluate( '@title', object, null, XPathResult.STRING_TYPE, null ).stringValue;
				var size = xmlDoc.evaluate( 'revisions/rev/@size', object, null, XPathResult.NUMBER_TYPE, null ).numberValue;

				var disputed = xmlDoc.evaluate( 'boolean(categories/cl[@title="Category:快速删除候选"])', object, null, XPathResult.BOOLEAN_TYPE, null ).booleanValue;
				list.push( {label:page + '(' + size + '字节)' + ( disputed ? '(速删争议)' : '' ), value:page, checked:!disputed });
			}
			self.params.form.append( {
					type: 'checkbox',
					name: 'pages',
					list: list
				} );
			self.params.form.append( { type:'submit' } );

			var result = self.params.form.render();
			self.params.Window.setContent( result );
		} );

	wikipedia_api.params = { form:form, Window:Window };
	wikipedia_api.post();
	var root = document.createElement( 'div' );
	Status.init( root );
	Window.setContent( root );
	Window.display();
};

Twinkle.batchdelete.currentDeleteCounter = 0;
Twinkle.batchdelete.currentUnlinkCounter = 0;
Twinkle.batchdelete.currentdeletor = 0;
Twinkle.batchdelete.callback.evaluate = function twinklebatchdeleteCallbackEvaluate(event) {
	Wikipedia.actionCompleted.notice = '状态';
	Wikipedia.actionCompleted.postfix = '批量删除已完成';
	mw.config.set('wgPageName', mw.config.get('wgPageName').replace(/_/g, ' '));  // for queen/king/whatever and country!
	var pages = event.target.getChecked( 'pages' );
	var reason = event.target.reason.value;
	var delete_page = event.target.delete_page.checked;
	var unlink_page = event.target.unlink_page.checked;
	var delete_redirects = event.target.delete_redirects.checked;
	if( ! reason ) {
		return;
	}
	SimpleWindow.setButtonsEnabled( false );
	Status.init( event.target );
	if( !pages ) {
		Status.error( '错误', '没什么要删的,取消操作' );
		return;
	}

	function toCall( work ) {
		if( work.length === 0 &&  Twinkle.batchdelete.currentDeleteCounter <= 0 && Twinkle.batchdelete.currentUnlinkCounter <= 0 ) {
			window.clearInterval( Twinkle.batchdelete.currentdeletor );
			Wikipedia.removeCheckpoint();
			return;
		} else if( work.length !== 0 && ( Twinkle.batchdelete.currentDeleteCounter <= Twinkle.getPref('batchDeleteMinCutOff') || Twinkle.batchdelete.currentUnlinkCounter <= Twinkle.getPref('batchDeleteMinCutOff')  ) ) {
			Twinkle.batchdelete.unlinkCache = []; // Clear the cache
			var pages = work.shift();
			Twinkle.batchdelete.currentDeleteCounter += pages.length;
			Twinkle.batchdelete.currentUnlinkCounter += pages.length;
			for( var i = 0; i < pages.length; ++i ) {
				var page = pages[i];
				var query = {
					'action': 'query',
					'titles': page
				};
				var wikipedia_api = new Wikipedia.api( '检查页面 ' + page + ' 是否存在', query, Twinkle.batchdelete.callbacks.main );
				wikipedia_api.params = { page:page, reason:reason, unlink_page:unlink_page, delete_page:delete_page, delete_redirects:delete_redirects };
				wikipedia_api.post();
			}
		}
	}
	var work = pages.chunk( Twinkle.getPref('batchdeleteChunks') );
	Wikipedia.addCheckpoint();
	Twinkle.batchdelete.currentdeletor = window.setInterval( toCall, 1000, work );
};

Twinkle.batchdelete.callbacks = {
	main: function( self ) {
		var xmlDoc = self.responseXML;
		var normal = xmlDoc.evaluate( '//normalized/n/@to', xmlDoc, null, XPathResult.STRING_TYPE, null ).stringValue;
		if( normal ) {
			self.params.page = normal;
		}
		var exists = xmlDoc.evaluate( 'boolean(//pages/page[not(@missing)])', xmlDoc, null, XPathResult.BOOLEAN_TYPE, null ).booleanValue;

		if( ! exists ) {
			self.statelem.error( "页面不存在,可能已被删除" );
			return;
		}

		var query, wikipedia_api;
		if( self.params.unlink_page ) {
			query = {
				'action': 'query',
				'list': 'backlinks',
				'blfilterredir': 'nonredirects',
				'blnamespace': [0, 100], // main space and portal space only
				'bltitle': self.params.page,
				'bllimit': userIsInGroup( 'sysop' ) ? 5000 : 500 // 500 is max for normal users, 5000 for bots and sysops
			};
			wikipedia_api = new Wikipedia.api( '抓取链入', query, Twinkle.batchdelete.callbacks.unlinkBacklinksMain );
			wikipedia_api.params = self.params;
			wikipedia_api.post();
		} else {
			--Twinkle.batchdelete.currentUnlinkCounter;
		}
		if( self.params.delete_page ) {
			if (self.params.delete_redirects)
			{
				query = {
					'action': 'query',
					'list': 'backlinks',
					'blfilterredir': 'redirects',
					'bltitle': self.params.page,
					'bllimit': userIsInGroup( 'sysop' ) ? 5000 : 500 // 500 is max for normal users, 5000 for bots and sysops
				};
				wikipedia_api = new Wikipedia.api( '抓取重定向', query, Twinkle.batchdelete.callbacks.deleteRedirectsMain );
				wikipedia_api.params = self.params;
				wikipedia_api.post();
			}

			var wikipedia_page = new Wikipedia.page( self.params.page, '删除页面 ' + self.params.page );
			wikipedia_page.setEditSummary(self.params.reason + Twinkle.getPref('deletionSummaryAd'));
			wikipedia_page.deletePage(function( apiobj ) { 
					--Twinkle.batchdelete.currentDeleteCounter;
					var link = document.createElement( 'a' );
					link.setAttribute( 'href', mw.util.getUrl(self.params.page) );
					link.setAttribute( 'title', self.params.page );
					link.appendChild( document.createTextNode( self.params.page ) );
					apiobj.statelem.info( [ '完成(' , link , ')' ] );
				} );	
		} else {
			--Twinkle.batchdelete.currentDeleteCounter;
		}
	},
	deleteRedirectsMain: function( self ) {
		var xmlDoc = self.responseXML;
		var snapshot = xmlDoc.evaluate('//backlinks/bl/@title', xmlDoc, null, XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE, null );

		var total = snapshot.snapshotLength;

		if( snapshot.snapshotLength === 0 ) {
			return;
		}

		var statusIndicator = new Status('删除到 ' + self.params.page + ' 的重定向', '0%');

		var onsuccess = function( self ) {
			var obj = self.params.obj;
			var total = self.params.total;
			var now = parseInt( 100 * ++(self.params.current)/total, 10 ) + '%';
			obj.update( now );
			self.statelem.unlink();
			if( self.params.current >= total ) {
				obj.info( now + '(完成)' );
				Wikipedia.removeCheckpoint();
			}
		};


		Wikipedia.addCheckpoint();
		if( snapshot.snapshotLength === 0 ) {
			statusIndicator.info( '100%(完成)' );
			Wikipedia.removeCheckpoint();
			return;
		}

		var params = clone( self.params );
		params.current = 0;
		params.total = total;
		params.obj = statusIndicator;


		for ( var i = 0; i < snapshot.snapshotLength; ++i ) {
			var title = snapshot.snapshotItem(i).value;
			var wikipedia_page = new Wikipedia.page( title, "删除 " + title );
			wikipedia_page.setEditSummary('[[WP:CSD#G15|G15]]: 孤立页面: 重定向到已删除页面“' + self.params.page + '”' + Twinkle.getPref('deletionSummaryAd'));
			wikipedia_page.setCallbackParameters(params);
			wikipedia_page.deletePage(onsuccess);
		}
	},
	unlinkBacklinksMain: function( self ) {
		var xmlDoc = self.responseXML;
		var snapshot = xmlDoc.evaluate('//backlinks/bl/@title', xmlDoc, null, XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE, null );

		if( snapshot.snapshotLength === 0 ) {
			--Twinkle.batchdelete.currentUnlinkCounter;
			return;
		}

		var statusIndicator = new Status('取消到 ' + self.params.page + ' 的链接', '0%');

		var total = snapshot.snapshotLength * 2;

		var onsuccess = function( self ) {
			var obj = self.params.obj;
			var total = self.params.total;
			var now = parseInt( 100 * ++(self.params.current)/total, 10 ) + '%';
			obj.update( now );
			self.statelem.unlink();
			if( self.params.current >= total ) {
				obj.info( now + '(完成)' );
				--Twinkle.batchdelete.currentUnlinkCounter;
				Wikipedia.removeCheckpoint();
			}
		};

		Wikipedia.addCheckpoint();
		if( snapshot.snapshotLength === 0 ) {
			statusIndicator.info( '100%(完成)' );
			--Twinkle.batchdelete.currentUnlinkCounter;
			Wikipedia.removeCheckpoint();
			return;
		}
		self.params.total = total;
		self.params.obj = statusIndicator;
		self.params.current =   0;

		for ( var i = 0; i < snapshot.snapshotLength; ++i ) {
			var title = snapshot.snapshotItem(i).value;
			var wikipedia_page = new Wikipedia.page( title, "在页面 " + title + " 中");
			var params = clone( self.params );
			params.title = title;
			params.onsuccess = onsuccess;
			wikipedia_page.setCallbackParameters(params);
			wikipedia_page.load(Twinkle.batchdelete.callbacks.unlinkBacklinks);
		}
	},
	unlinkBacklinks: function( pageobj ) {
		var params = pageobj.getCallbackParameters();
		if( ! pageobj.exists() ) {
			// we probably just deleted it, as a recursive backlink
			params.onsuccess( { params: params, statelem: pageobj.getStatusElement() } );
			Wikipedia.actionCompleted();
			return;
		}
		var text;

		if( params.title in Twinkle.batchdelete.unlinkCache ) {
			text = Twinkle.batchdelete.unlinkCache[ params.title ];
		} else {
			text = pageobj.getPageText();
		}
		var old_text = text;
		var wikiPage = new Mediawiki.Page( text );
		wikiPage.removeLink( params.page );

		text = wikiPage.getText();
		Twinkle.batchdelete.unlinkCache[ params.title ] = text;
		if( text === old_text ) {
			// Nothing to do, return
			params.onsuccess( { params: params, statelem: pageobj.getStatusElement() } );
			Wikipedia.actionCompleted();
			return;
		}
		pageobj.setEditSummary('取消到页面 ' + self.params.page + ' 的链接' + Twinkle.getPref('deletionSummaryAd'));
		pageobj.setPageText(text);
		pageobj.setCreateOption('nocreate');
		pageobj.save(params.onsuccess);
	}
};

/*
 * vim: set noet sts=0 sw=8:
 ****************************************
 *** twinklecopyvio.js: Copyvio module
 ****************************************
 * Mode of invocation:     Tab ("Copyvio")
 * Active on:              Existing, non-special pages, except for file pages with no local (non-Commons) file which are not redirects
 * Config directives in:   TwinkleConfig
 */

Twinkle.copyvio = function twinklecopyvio() {
	// Disable on:
	// * special pages
	// * non-existent pages
	// * files on Commons, whether there is a local page or not (unneeded local pages of files on Commons are eligible for CSD F2)
	// * file pages without actual files (these are eligible for CSD G8)
	if ( mw.config.get('wgNamespaceNumber') < 0 || !mw.config.get('wgArticleId') || (mw.config.get('wgNamespaceNumber') === 6 && (document.getElementById('mw-sharedupload') || (!document.getElementById('mw-imagepage-section-filehistory') && !Wikipedia.isPageRedirect()))) ) {
		return;
	}
	if (twinkleUserAuthorized) {
		$(twAddPortletLink("#", "侵权", "tw-copyvio", "提报侵权页面", "")).click(Twinkle.copyvio.callback);
	} else {
		$(twAddPortletLink("#", '侵权', 'tw-copyvio', '提报侵权页面', '')).click(function() {
			alert("您尚未达到自动确认。");
		});
	}
};

Twinkle.copyvio.callback = function twinklecopyvioCallback() {
	var Window = new SimpleWindow( 600, 350 );
	Window.setTitle( "提报侵权页面" );
	Window.setScriptName( "Twinkle" );
	Window.addFooterLink( "Twinkle帮助", "WP:TW/DOC#copyvio" );

	var form = new QuickForm( Twinkle.copyvio.callback.evaluate );
	form.append( {
			type: 'textarea',
			label:'侵权来源:',
			name: 'source'
		} );
	form.append( { type:'submit' } );

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

Twinkle.copyvio.callbacks = {
	main: function(pageobj) {
		// this is coming in from lookupCreator...!
		var params = pageobj.getCallbackParameters();
		var initialContrib = pageobj.getCreator();

		// Adding discussion
		wikipedia_page = new Wikipedia.page(params.logpage, "添加侵权记录项");
		wikipedia_page.setFollowRedirect(true);
		wikipedia_page.setCallbackParameters(params);
		wikipedia_page.load(Twinkle.copyvio.callbacks.copyvioList);

		// Notification to first contributor
		var usertalkpage = new Wikipedia.page('User talk:' + initialContrib, "通知页面创建者(" + initialContrib + ")");
		var notifytext = "\n{{subst:CopyvioNotice|" + mw.config.get('wgPageName') +  "}}";
		usertalkpage.setAppendText(notifytext);
		usertalkpage.setEditSummary("通知:页面[[" + mw.config.get('wgPageName') + "]]疑似侵犯版权。" + Twinkle.getPref('summaryAd'));
		usertalkpage.setCreateOption('recreate');
		switch (Twinkle.getPref('copyvioWatchUser')) {
			case 'yes':
				usertalkpage.setWatchlist(true);
				break;
			case 'no':
				usertalkpage.setWatchlistFromPreferences(false);
				break;
			default:
				usertalkpage.setWatchlistFromPreferences(true);
				break;
		}
		usertalkpage.setFollowRedirect(true);
		usertalkpage.append();
	},
	taggingArticle: function(pageobj) {
		var params = pageobj.getCallbackParameters();
		var tag = "{{subst:Copyvio/auto|url=" + params.source.replace(/http/g, '&#104;ttp').replace(/\n+/g, '\n').replace(/^\s*([^\*])/gm, '* $1').replace(/^\* $/m, '') + "}}";
		if ( /\/temp$/i.test( mw.config.get('wgPageName') ) ) {
			tag = "{{D|G16}}\n" + tag;
		}

		pageobj.setPageText(tag);
		pageobj.setEditSummary("本页面疑似侵犯版权" + Twinkle.getPref('summaryAd'));
		switch (Twinkle.getPref('copyvioWatchPage')) {
			case 'yes':
				pageobj.setWatchlist(true);
				break;
			case 'no':
				pageobj.setWatchlistFromPreferences(false);
				break;
			default:
				pageobj.setWatchlistFromPreferences(true);
				break;
		}
		// pageobj.setCreateOption('recreate');
		pageobj.save();

		if( Twinkle.getPref('markCopyvioPagesAsPatrolled') ) {
			pageobj.patrol();
		}
	},
	copyvioList: function(pageobj) {
		var text = pageobj.getPageText();
		var params = pageobj.getCallbackParameters();

		pageobj.setAppendText("\n{{subst:CopyvioVFDRecord|" + mw.config.get('wgPageName') + "}}");
		pageobj.setEditSummary("添加[[" + mw.config.get('wgPageName') + "]]。" + Twinkle.getPref('summaryAd'));
		pageobj.setCreateOption('recreate');
		pageobj.append();
	}
};


Twinkle.copyvio.callback.evaluate = function(e) {
	mw.config.set('wgPageName', mw.config.get('wgPageName').replace(/_/g, ' '));  // for queen/king/whatever and country!

	var source = e.target.source.value;

	SimpleWindow.setButtonsEnabled( false );
	Status.init( e.target );

	if( !source.trim() ) {
		Status.error( '错误', '未指定侵权来源' );
		return;
	}

	var query, wikipedia_page, wikipedia_api, logpage, params;
	logpage = 'Wikipedia:頁面存廢討論/疑似侵權';
	params = { source: source, logpage: logpage };

	Wikipedia.addCheckpoint();
	// Updating data for the action completed event
	Wikipedia.actionCompleted.redirect = mw.config.get('wgPageName');
	Wikipedia.actionCompleted.notice = "提报完成,将在几秒内刷新";

	// Tagging file
	wikipedia_page = new Wikipedia.page(mw.config.get('wgPageName'), "添加侵权模板到页面");
	wikipedia_page.setFollowRedirect(true);
	wikipedia_page.setCallbackParameters(params);
	wikipedia_page.load(Twinkle.copyvio.callbacks.taggingArticle);

	// Contributor specific edits
	wikipedia_page = new Wikipedia.page(mw.config.get('wgPageName'));
	wikipedia_page.setCallbackParameters(params);
	wikipedia_page.lookupCreator(Twinkle.copyvio.callbacks.main);

	Wikipedia.removeCheckpoint();
};

/*
 ****************************************
 *** twinklearv.js: ARV module
 ****************************************
 * Mode of invocation:     Tab ("ARV")
 * Active on:              Existing and non-existing user pages, user talk pages, contributions pages
 * Config directives in:   TwinkleConfig
 */

Twinkle.arv = function twinklearv() {
	if ( mw.config.get('wgNamespaceNumber') === 2 || mw.config.get('wgNamespaceNumber') === 3 || 
	    ( mw.config.get('wgNamespaceNumber') === -1 && mw.config.get('wgCanonicalSpecialPageName') === "Contributions" )) {

		// If we are on the contributions page, need to parse some then
		var username;
		if( mw.config.get('wgNamespaceNumber') === -1 && mw.config.get('wgCanonicalSpecialPageName') === "Contributions" ) {
			username = decodeURIComponent(/wiki\/Special:Log\/(.+)$/.exec($('div#contentSub a[title^="Special:Log"]').last().attr("href").replace(/_/g, "%20"))[1]);
		} else {
			username = mw.config.get('wgTitle').split( '/' )[0]; // only first part before any slashes
		}

		if ( !username ) {
			return;
		}

		var title = isIPAddress( username ) ? 'Report IP to administrators' : 'Report user to administrators';
		
		if (twinkleUserAuthorized) {
			$(twAddPortletLink("#", "ARV", "tw-arv", title, "" )).click(function() { Twinkle.arv.callback(username.replace( /\"/g, "\\\"")); } );
		} else {
			$(twAddPortletLink("#", 'ARV', 'tw-arv', title, "" )).click(function() { alert("Your account is too new to use Twinkle."); } );
		}
	}
};

Twinkle.arv.callback = function ( uid ) {
	if( uid === mw.config.get('wgUserName') ){
		alert( 'You don\'t want to report yourself, do you?' );
		return;
	}

	var Window = new SimpleWindow( 600, 500 );
	Window.setTitle( "Advance Reporting and Vetting" ); //Backronym
	Window.setScriptName( "Twinkle" );
	Window.addFooterLink( "Guide to AIV", "WP:GAIV" );
	Window.addFooterLink( "UAA instructions", "WP:UAAI" );
	Window.addFooterLink( "About SPI", "WP:SPI" );
	Window.addFooterLink( "Twinkle help", "WP:TW/DOC#arv" );

	var form = new QuickForm( Twinkle.arv.callback.evaluate );
	var categories = form.append( {
			type: 'select',
			name: 'category',
			label: 'Select report type: ',
			event: Twinkle.arv.callback.changeCategory
		} );
	categories.append( {
			type: 'option',
			label: 'Vandalism (WP:AIV)',
			value: 'aiv'
		} );
	categories.append( {
			type: 'option',
			label: 'Username (WP:UAA)',
			value: 'username'
		} );
	categories.append( {
			type: 'option',
			label: 'Sockpuppeteer (WP:SPI)',
			value: 'sock'
		} );
	categories.append( {
			type: 'option',
			label: 'Sockpuppet (WP:SPI)',
			value: 'puppet'
		} );
	form.append( {
			type: 'field',
			label:'Work area',
			name: 'work_area'
		} );
	form.append( { type:'submit' } );
	form.append( {
			type: 'hidden',
			name: 'uid',
			value: uid
		} );
	
	var result = form.render();
	Window.setContent( result );
	Window.display();

	// We must init the
	var evt = document.createEvent( "Event" );
	evt.initEvent( 'change', true, true );
	result.category.dispatchEvent( evt );
};

Twinkle.arv.callback.changeCategory = function (e) {
	var value = e.target.value;
	var root = e.target.form;
	var old_area;
	for( var i = 0; i < root.childNodes.length; ++i ) {
		var node = root.childNodes[i];
		if (node instanceof Element && node.getAttribute( 'name' ) === 'work_area') {
			old_area = node;
			break;
		}
	}
	var work_area = null;

	switch( value ) {
	case 'aiv':
		/* falls through */
	default:
		work_area = new QuickForm.element( { 
				type: 'field',
				label: 'Report user for vandalism',
				name: 'work_area'
			} );
		work_area.append( {
				type: 'input',
				name: 'page',
				label: 'Primary linked page: ',
				tooltip: 'Leave blank to not link to the page in the report',
				value: QueryString.exists( 'vanarticle' ) ? QueryString.get( 'vanarticle' ) : '',
				event: function(e) {
					var value = e.target.value;
					var root = e.target.form;
					if( value === '' ) {
						root.badid.disabled = root.goodid.disabled = true;
					} else {
						root.badid.disabled = false;
						root.goodid.disabled = root.badid.value === '';
					}
				}
			} );
		work_area.append( {
				type: 'input',
				name: 'badid',
				label: 'Revision ID for target page when vandalised: ',
				tooltip: 'Leave blank for no diff link',
				value: QueryString.exists( 'vanarticlerevid' ) ? QueryString.get( 'vanarticlerevid' ) : '',
				disabled: !QueryString.exists( 'vanarticle' ),
				event: function(e) {
					var value = e.target.value;
					var root = e.target.form;
					root.goodid.disabled = value === '';
				}
			} );
		work_area.append( {
				type: 'input',
				name: 'goodid',
				label: 'Last good revision ID before vandalism of target page: ',
				tooltip: 'Leave blank for diff link to previous revision',
				value: QueryString.exists( 'vanarticlegoodrevid' ) ? QueryString.get( 'vanarticlegoodrevid' ) : '',
				disabled: !QueryString.exists( 'vanarticle' ) || QueryString.exists( 'vanarticlerevid' )
			} );
		work_area.append( {
				type: 'checkbox',
				name: 'arvtype',
				list: [
					{ 
						label: 'Vandalism after final (level 4 or 4im) warning given',
						value: 'final'
					},
					{ 
						label: 'Vandalism after recent (within 1 day) release of block',
						value: 'postblock'
					},
					{ 
						label: 'Evidently a vandalism-only account',
						value: 'vandalonly',
						disabled: isIPAddress( root.uid.value )
					},
					{ 
						label: 'Account is evidently a spambot or a compromised account',
						value: 'spambot'
					},
					{ 
						label: 'Account is a promotion-only account',
						value: 'promoonly'
					}
				]
			} );
		work_area.append( {
				type: 'textarea',
				name: 'reason',
				label: 'Comment: '
			} );
		work_area = work_area.render();
		old_area.parentNode.replaceChild( work_area, old_area );
		break;
	case 'username':
		work_area = new QuickForm.element( { 
				type: 'field',
				label: 'Report username violation',
				name: 'work_area'
			} );
		work_area.append ( { 
				type:'header', 
				label:'Type(s) of inappropriate username',
				tooltip: 'Wikipedia does not allow usernames that are misleading, promotional, offensive or disruptive. Domain names and e-mail addresses are likewise prohibited. These criteria apply to both usernames and signatures. Usernames that are inappropriate in another language, or that represent an inappropriate name with misspellings and substitutions, or do so indirectly or by implication, are still considered inappropriate.'
			} );
		work_area.append( {
				type: 'checkbox',
				name: 'arvtype',
				list: [
					{
						label: 'Misleading username',
						value: 'misleading',
						tooltip: 'Misleading usernames imply relevant, misleading things about the contributor. For example, misleading points of fact, an impression of undue authority, or the suggestion that the account is operated by a group, project or collective rather than one individual.'
					},
					{ 
						label: 'Promotional username',
						value: 'promotional',
						tooltip: 'Promotional usernames are advertisements for a company or group.'
					},
					{ 
						label: 'Offensive username',
						value: 'offensive',
						tooltip: 'Offensive usernames make harmonious editing difficult or impossible.'
					},
					{ 
						label: 'Disruptive username',
						value: 'disruptive',
						tooltip: 'Disruptive usernames include outright trolling or personal attacks, or otherwise show a clear intent to disrupt Wikipedia.'
					}
				]
			} );
		work_area.append( {
				type: 'textarea',
				name: 'reason',
				label: 'Comment:'
			} );
		work_area = work_area.render();
		old_area.parentNode.replaceChild( work_area, old_area );
		break;

	case 'puppet':
		work_area = new QuickForm.element( { 
				type: 'field',
				label: 'Report suspected sockpuppet',
				name: 'work_area'
			} );
		work_area.append(
			{
				type: 'input',
				name: 'sockmaster',
				label: 'Sockpuppeteer',
				tooltip: 'The username of the sockpuppeteer (sockmaster) without the User:-prefix'
			}
		);
		work_area.append( {
				type: 'textarea',
				label: 'Evidence:',
				name: 'evidence',
				tooltip: 'Enter your evidence. It should make clear that each of these users is likely to be abusing multiple accounts. Usually this means diffs, page histories or other information that justifies why the users are a) the same and b) disruptive. This should purely be evidence and information needed to judge the matter. Avoid all other discussion that is not evidence of sockpuppetry or other multiple account abuse.'
			} );
		work_area.append( {
				type: 'checkbox',
				list: [
					{
						label: 'Request CheckUser evidence',
						name: 'checkuser',
						tooltip: 'CheckUser is a tool used to obtain technical evidence related to a sock-puppetry allegation. It will not be used without good cause, which you must clearly demonstrate. Make sure your evidence explains why CheckUser is appropriate.'
					},
					{
						label: 'Notify reported users',
						name: 'notify',
						tooltip: 'Notification is not mandatory. In many cases, especially of chronic sockpuppeteers, notification may be counterproductive. However, especially in less egregious cases involving users who has not been reported before, notification may make the cases fairer and also appear to be fairer in the eyes of the accused. Use your judgment.'
					}
				]
			} );
		work_area = work_area.render();
		old_area.parentNode.replaceChild( work_area, old_area );
		break;
	case 'sock':
		work_area = new QuickForm.element( { 
				type: 'field',
				label: 'Report suspected sockpuppeteer',
				name: 'work_area'
			} );
		work_area.append(
			{
				type: 'dyninput',
				name: 'sockpuppet',
				label: 'Sockpuppets',
				sublabel: 'Sock: ',
				tooltip: 'The username of the sockpuppet without the User:-prefix',
				min: 2
			}
		);
		work_area.append( {
				type: 'textarea',
				label: 'Evidence:',
				name: 'evidence',
				tooltip: 'Enter your evidence. It should make clear that each of these users is likely to be abusing multiple accounts. Usually this means diffs, page histories or other information that justifies why the users are a) the same and b) disruptive. This should purely be evidence and information needed to judge the matter. Avoid all other discussion that is not evidence of sockpuppetry or other multiple account abuse.'
			} );
		work_area.append( {
				type: 'checkbox',
				list: [ {
					label: 'Request CheckUser evidence',
					name: 'checkuser',
					tooltip: 'CheckUser is a tool used to obtain technical evidence related to a sock-puppetry allegation. It will not be used without good cause, which you must clearly demonstrate. Make sure your evidence explains why CheckUser is appropriate.'
				}, {
					label: 'Notify reported users',
					name: 'notify',
					tooltip: 'Notification is not mandatory. In many cases, especially of chronic sockpuppeteers, notification may be counterproductive. However, especially in less egregious cases involving users who has not been reported before, notification may make the cases fairer and also appear to be fairer in the eyes of the accused. Use your judgment.'
				} ]
			} );
		work_area = work_area.render();
		old_area.parentNode.replaceChild( work_area, old_area );
		break;
	}
};

Twinkle.arv.callback.evaluate = function(e) {

	var form = e.target;
	var reason = "";
	var comment = "";
	if ( form.reason ) {
		comment = form.reason.value;
	}
	var uid = form.uid.value;

	var types;
	switch( form.category.value ) {

		// Report user for vandalism
		case 'aiv':
			/* falls through */
		default:
			types = form.getChecked( 'arvtype' );
			if( !types.length && comment === '' ) {
				alert( 'You must specify some reason' );
				return;
			}

			types = types.map( function(v) {
					switch(v) {
						case 'final':
							return 'vandalism after final warning';
						case 'postblock':
							return 'vandalism after recent release of block';
						case 'spambot':
							return 'account is evidently a spambot or a compromised account';
						case 'vandalonly':
							return 'actions evidently indicate a vandalism-only account';
						case 'promoonly':
							return 'account is being used only for promotional purposes';
						default:
							return 'unknown reason';
					}
				} ).join( '; ' );


			if ( form.page.value !== '' ) {
			
				// add a leading : on linked page namespace to prevent transclusion
				reason = 'On [[' + form.page.value.replace( /^(Image|Category|File):/i, ':$1:' ) + ']]';

				if ( form.badid.value !== '' ) {
					var query = {
						'title': form.page.value,
						'diff': form.badid.value,
						'oldid': form.goodid.value
					};
					reason += ' ({{diff|' + form.page.value + '|' + form.badid.value + '|' + form.goodid.value + '|diff}})';
				}
				reason += ':';
			}

			if ( types ) {
				reason += " " + types;
			}
			if (comment !== "" ) {
				reason += (reason === "" ? "" : ". ") + comment;
			}
			reason += ". ~~~~";
			reason = reason.replace(/\r?\n/g, "\n*:");  // indent newlines

			SimpleWindow.setButtonsEnabled( false );
			Status.init( form );

			Wikipedia.actionCompleted.redirect = "Wikipedia:Administrator intervention against vandalism";
			Wikipedia.actionCompleted.notice = "Reporting complete";

			var aivPage = new Wikipedia.page( 'Wikipedia:Administrator intervention against vandalism', 'Processing AIV request' );
			aivPage.setPageSection( 1 );
			aivPage.setFollowRedirect( true );
			
			aivPage.load( function() {
				var text = aivPage.getPageText();

				// check if user has already been reported
				if (new RegExp( "\\{\\{\\s*(?:(?:[Ii][Pp])?[Vv]andal|[Uu]serlinks)\\s*\\|\\s*(?:1=)?\\s*" + RegExp.escape( uid, true ) + "\\s*\\}\\}" ).test(text)) {
					aivPage.getStatusElement().info( 'Report already present, will not add a new one' );
					return;
				}
				aivPage.getStatusElement().status( 'Adding new report...' );
				aivPage.setEditSummary( 'Reporting [[Special:Contributions/' + uid + '|' + uid + ']].' + Twinkle.getPref('summaryAd') );
				aivPage.setAppendText( '\n*{{' + ( isIPAddress( uid ) ? 'IPvandal' : 'vandal' ) + '|' + (/\=/.test( uid ) ? '1=' : '' ) + uid + '}} &ndash; ' + reason );
				aivPage.append();
			} );
			break;
			
		// Report inappropriate username
		case 'username':
			types = form.getChecked( 'arvtype' );
			if( !types.length ) {
				alert( 'You must specify at least one breached violation' );
				return;
			}
			types = types.map( function( v ) { return v.toLowerCaseFirstChar(); } );

			if ( types.length <= 2 ) {
				types = types.join( ' and ' );
			} else {
				types = [ types.slice( 0, -1 ).join( ', ' ), types.slice( -1 ) ].join( ' and ' );
			}
			var article = 'a';
			if ( /[aeiouwyh]/.test( types[0] ) ) { // non 100% correct, but whatever, inlcuding 'h' for Cockney
				article = 'an';
			}
			reason = "*{{user-uaa|1=" + uid + "}} &ndash; Violation of username policy as " + article + " " + types + " username. ";
			if (comment !== '' ) {
				reason += comment.toUpperCaseFirstChar() + ". ";
			}
			reason += "~~~~";
			reason = reason.replace(/\r?\n/g, "\n*:");  // indent newlines

			SimpleWindow.setButtonsEnabled( false );
			Status.init( form );

			Wikipedia.actionCompleted.redirect = "Wikipedia:Usernames for administrator attention";
			Wikipedia.actionCompleted.notice = "Reporting complete";

			var uaaPage = new Wikipedia.page( 'Wikipedia:Usernames for administrator attention', 'Processing UAA request' );
			uaaPage.setFollowRedirect( true );

			uaaPage.load( function() {
				var text = uaaPage.getPageText();
				
				// check if user has already been reported
				if (new RegExp( "\\{\\{\\s*user-uaa\\s*\\|\\s*(1\\s*=\\s*)?" + RegExp.escape(uid, true) + "\\s*(\\||\\})" ).test(text)) {
					uaaPage.getStatusElement().error( 'User is already listed.' );
					return;
				}
				uaaPage.getStatusElement().status( 'Adding new report...' );
				uaaPage.setEditSummary( 'Reporting [[Special:Contributions/' + uid + '|' + uid + ']].'+ Twinkle.getPref('summaryAd') );
				uaaPage.setPageText( text.replace( /List begins below this line.\s*-->/, "List begins below this line.\n-->\n" + reason ) );  // add at top
				uaaPage.save();
			} );
			break;
			
		// WP:SPI
		case "sock":
			/* falls through */
		case "puppet":
			var sockParameters = {
				evidence: form.evidence.value.rtrim(), 
				checkuser: form.checkuser.checked, 
				notify: form.notify.checked
			};

			var puppetReport = form.category.value === "puppet";
			if (puppetReport && !(form.sockmaster.value.trim())) {
				if (!confirm("You have not entered a sockmaster account for this puppet. Do you want to report this account as a sockpuppeteer instead?")) {
					return;
				}
				puppetReport = false;
			}

			sockParameters.uid = puppetReport ? form.sockmaster.value.rtrim() : uid;
			sockParameters.sockpuppets = puppetReport ? [uid] : $.map( $('input:text[@name=sockpuppet]',form), function(o){ return $(o).val(); });

			SimpleWindow.setButtonsEnabled( false );
			Status.init( form );
			Twinkle.arv.processSock( sockParameters );
			break;

	}
};

Twinkle.arv.processSock = function( params ) {
	Wikipedia.addCheckpoint(); // prevent notification events from causing an erronous "action completed"
	
	// notify all user accounts if requested
	if (params.notify && params.sockpuppets.length>0) {
	
		var notifyEditSummary = "Notifying about suspicion of sockpuppeteering." + Twinkle.getPref('summaryAd');
		var notifyText = "\n\n{{subst:socksuspectnotice|1=" + params.uid + "}} ~~~~";
		
		// notify user's master account
		var masterTalkPage = new Wikipedia.page( 'User talk:' + params.uid, 'Notifying suspected sockpuppeteer' );
		masterTalkPage.setFollowRedirect( true );
		masterTalkPage.setEditSummary( notifyEditSummary );
		masterTalkPage.setAppendText( notifyText );
		masterTalkPage.append();

		var statusIndicator = new Status( 'Notifying suspected sockpuppets', '0%' );
		var total = params.sockpuppets.length;
		var current =   0;
		
		// display status of notifications as they progress
		var onSuccess = function( sockTalkPage ) {
			var now = parseInt( 100 * ++(current)/total, 10 ) + '%';
			statusIndicator.update( now );
			sockTalkPage.getStatusElement().unlink();
			if ( current >= total ) {
				statusIndicator.info( now + ' (completed)' );
			}
		};
		
		var socks = params.sockpuppets;

		// notify each puppet account
		for( var i = 0; i < socks.length; ++i ) {
			var sockTalkPage = new Wikipedia.page( 'User talk:' + socks[i], "Notification for " +  socks[i] );
			sockTalkPage.setFollowRedirect( true );
			sockTalkPage.setEditSummary( notifyEditSummary );
			sockTalkPage.setAppendText( notifyText );
			sockTalkPage.append( onSuccess );
		}
	}

	// prepare the SPI report
	var text = "\n\n{{subst:SPI report|socksraw=" +
		params.sockpuppets.map( function(v) { 
				return "* {{" + ( isIPAddress( v ) ? "checkip" : "checkuser" ) + "|1=" + v + "}}";
			} ).join( "\n" ) + "\n|evidence=" + params.evidence + " \n";
		
	if ( params.checkuser ) {
		text += "|checkuser=yes";
	}
	text += "}}";

	var reportpage = 'Wikipedia:Sockpuppet investigations/' + params.uid;

	Wikipedia.actionCompleted.redirect = reportpage;
	Wikipedia.actionCompleted.notice = "Reporting complete";

	var spiPage = new Wikipedia.page( reportpage, 'Retrieving discussion page' );
	spiPage.setFollowRedirect( true );
	spiPage.setEditSummary( 'Adding new report for [[Special:Contributions/' + params.uid + '|' + params.uid + ']].'+ Twinkle.getPref('summaryAd') );
	spiPage.setAppendText( text );
	spiPage.append();
	
	Wikipedia.removeCheckpoint();  // all page updates have been started
};

/*
 * vim: set noet sts=0 sw=8:
 ****************************************
 *** friendlyshared.js: Shared IP tagging module
 ****************************************
 * Mode of invocation:     Tab ("Shared")
 * Active on:              Existing IP user talk pages
 * Config directives in:   FriendlyConfig
 */

Twinkle.shared = function friendlyshared() {
	if( mw.config.get('wgNamespaceNumber') === 3 && isIPAddress(mw.config.get('wgTitle')) ) {
		var username = mw.config.get('wgTitle').split( '/' )[0].replace( /\"/, "\\\""); // only first part before any slashes
		$(twAddPortletLink("#", "Shared IP", "friendly-shared", "Shared IP tagging", "")).click(function() { Twinkle.shared.callback(username); });
	}
};

Twinkle.shared.callback = function friendlysharedCallback( uid ) {
	var Window = new SimpleWindow( 600, 400 );
	Window.setTitle( "Shared IP address tagging" );
	Window.setScriptName( "Twinkle" );
	Window.addFooterLink( "Twinkle help", "WP:TW/DOC#shared" );

	var form = new QuickForm( Twinkle.shared.callback.evaluate );

	form.append( { type:'header', label:'Shared IP address templates' } );
	form.append( { type: 'radio', name: 'shared', list: Twinkle.shared.standardList,
		event: function( e ) {
			Twinkle.shared.callback.change_shared( e );
			e.stopPropagation();
		} } );

	var org = form.append( { type:'field', label:'Fill in IP address owner/operator, hostname and contact information (if applicable) and hit \"Submit\"' } );
	org.append( {
			type: 'input',
			name: 'organization',
			label: 'Organization name',
			disabled: true,
			tooltip: 'Some of these templates support an optional parameter for the organization name that owns/operates the IP address.  The organization name can be entered here for those templates, including wikimarkup if necessary.'
		}
	);
	org.append( {
			type: 'input',
			name: 'host',
			label: 'Host name (optional)',
			disabled: true,
			tooltip: 'These templates support an optional parameter for the host name.  The host name (for example, proxy.example.com) can be entered here and will be linked by the template.'
		}
	);
	org.append( {
			type: 'input',
			name: 'contact',
			label: 'Contact information (only if requested)',
			disabled: true,
			tooltip: 'Some of these templates support an optional parameter for the organization\'s contact information.  Use this parameter only if the organization has specifically request that it be added.  This contact information can be entered here for those templates, including wikimarkup if necessary.'
		}
	);
	
	form.append( { type:'submit' } );

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

Twinkle.shared.standardList = [
	{
		label: '{{shared IP}}: standard shared IP address template',
		value: 'shared IP',
		tooltip: 'IP user talk page template that shows helpful information to IP users and those wishing to warn, block or ban them'
	},
	{ 
		label: '{{shared IP edu}}: shared IP address template modified for educational institutions',
		value: 'shared IP edu'
	},
	{
		label: '{{shared IP corp}}: shared IP address template modified for businesses',
		value: 'shared IP corp'
	},
	{
		label: '{{shared IP public}}: shared IP address template modified for public terminals',
		value: 'shared IP public'
	},
	{
		label: '{{shared IP gov}}: shared IP address template modified for government agencies or facilities',
		value: 'shared IP gov'
	},
	{
		label: '{{dynamicIP}}: shared IP address template modified for organizations with dynamic addressing',
		value: 'dynamicIP'
	},
	{ 
		label: '{{ISP}}: shared IP address template modified for ISP organizations (specifically proxies)',
		value: 'ISP'
	},
	{ 
		label: '{{mobileIP}}: shared IP address template modified for mobile phone companies and their customers',
		value: 'mobileIP'
	}
];

Twinkle.shared.callback.change_shared = function friendlytagCallbackChangeShared(e) {
	if( e.target.value === 'shared IP edu' ) {
		e.target.form.contact.disabled = false;
	} else {
		e.target.form.contact.disabled = true;
	}
	e.target.form.organization.disabled=false;
	e.target.form.host.disabled=false;
};

Twinkle.shared.callbacks = {
	main: function( pageobj ) {
		var params = pageobj.getCallbackParameters();
		var pageText = pageobj.getPageText();
		var found = false;
		var text = '{{';

		for( var i=0; i < Twinkle.shared.standardList.length; i++ ) {
			tagRe = new RegExp( '(\\{\\{' + Twinkle.shared.standardList[i].value + '(\\||\\}\\}))', 'im' );
			if( tagRe.exec( pageText ) ) {
				Status.warn( 'Info', 'Found {{' + Twinkle.shared.standardList[i].value + '}} on the user\'s talk page already...aborting' );
				found = true;
			}
		}

		if( found ) {
			return;
		}

		Status.info( 'Info', 'Will add the shared IP address template to the top of the user\'s talk page.' );
		text += params.value + '|' + params.organization;
		if( params.value === 'shared IP edu' && params.contact !== '') {
			text += '|' + params.contact;
		}
		if( params.host !== '' ) {
			text += '|host=' + params.host;
		}
		text += '}}\n\n';

		var summaryText = 'Added {{[[Template:' + params.value + '|' + params.value + ']]}} template.';
		pageobj.setPageText(text + pageText);
		pageobj.setEditSummary(summaryText + Twinkle.getPref('summaryAd'));
		pageobj.setMinorEdit(Twinkle.getFriendlyPref('markSharedIPAsMinor'));
		pageobj.setCreateOption('recreate');
		pageobj.save();
	}
};

Twinkle.shared.callback.evaluate = function friendlysharedCallbackEvaluate(e) {
	var shared = e.target.getChecked( 'shared' );
	if( !shared || shared.length <= 0 ) {
		alert( 'You must select a shared IP address template to use!' );
		return;
	}
	
	var value = shared[0];
	
	if( e.target.organization.value === '') {
		alert( 'You must input an organization for the {{' + value + '}} template!' );
		return;
	}
	
	var params = {
		value: value,
		organization: e.target.organization.value,
		host: e.target.host.value,
		contact: e.target.contact.value
	};

	SimpleWindow.setButtonsEnabled( false );
	Status.init( e.target );

	Wikipedia.actionCompleted.redirect = mw.config.get('wgPageName');
	Wikipedia.actionCompleted.notice = "Tagging complete, reloading talk page in a few seconds";

	var wikipedia_page = new Wikipedia.page(mw.config.get('wgPageName'), "User talk page modification");
	wikipedia_page.setFollowRedirect(true);
	wikipedia_page.setCallbackParameters(params);
	wikipedia_page.load(Twinkle.shared.callbacks.main);
};

/**
 * vim: set noet sts=0 sw=8:
 * General initialization code
 */

var scriptpathbefore = mw.config.get('wgServer') + mw.config.get('wgScript') + "?title=";
var scriptpathafter = "&action=raw&ctype=text/javascript&happy=yes";

// retrieve the user's Twinkle preferences
$.ajax({
	url: scriptpathbefore + "User:" + encodeURIComponent(mw.config.get('wgUserName')) + "/twinkleoptions.js" + scriptpathafter,
	dataType: 'text',
	error: function(){ jsMsg("不能加载twinkleoptions.js"); },
	success: function(optionsText){

		//quick pass if user has no options
		if ( optionsText === "" ) {
			return;
		}

		//twinkle options are basically a JSON object with some comments. Strip those:
		optionsText = optionsText.replace(/(?:^(?:\/\/[^\n]*\n)*\n*|(?:\/\/[^\n]*(?:\n|$))*$)/g, "");

		//first version of options had some boilerplate code to make it eval-able -- strip that too. This part may become obsolete down the line.
		if (optionsText.lastIndexOf("window.Twinkle.prefs = ", 0) === 0) {
			optionsText = optionsText.replace(/(?:^window.Twinkle.prefs = |;\n*$)/g, "");
		}

		try {
			var options = JSON.parse(optionsText);

			// Assuming that our options evolve, we will want to transform older versions:
			//if (options.optionsVersion === undefined) {
			// ...
			// options.optionsVersion = 1;
			//}
			//if (options.optionsVersion === 1) {
			// ...
			// options.optionsVersion = 2;
			//}
			// At the same time, twinkleconfig.js needs to be adapted to write a higher version number into the options.

			if( options ) {
				Twinkle.prefs = options;
			}
		}
		catch (e) {
			jsMsg("不能解析twinkleoptions.js");
		}
	},
	complete: function(){
		$(document).ready(Twinkle.load);
	}
});

// Developers: you can import custom Twinkle modules here
// for example, mw.loader.load(scriptpathbefore + "User:UncleDouggie/morebits-test.js" + scriptpathafter);

Twinkle.load = function(){
	// Don't activate on special pages other than "Contributions" so that they load faster, especially the watchlist.
	// Also, Twinkle is incompatible with Internet Explorer versions 8 or lower, so don't load there either.
	if ( (mw.config.get('wgNamespaceNumber') === -1 && mw.config.get('wgCanonicalSpecialPageName') !== "Contributions") ||
		($.client.profile().name === 'msie' && $.client.profile().versionNumber < 9) ) {
		return;
	}

	// load the modules in the order that the tabs should appears
	// user/user talk-related
	//Twinkle.arv();
	Twinkle.warn();
	Twinkle.welcome();
	Twinkle.shared();
	Twinkle.talkback();
	// deletion
	Twinkle.speedy();
	Twinkle.copyvio();
	//Twinkle.prod();
	Twinkle.xfd();
	Twinkle.image();
	// maintenance
	Twinkle.protect();
	Twinkle.tag();
	// misc. ones last
	Twinkle.diff();
	Twinkle.unlink();
	Twinkle.config.init();
	Twinkle.fluff.init();
	//if (userIsInGroup('sysop')) {
		//Twinkle.closer();  -- disabled for the moment, as it is disliked among Twinkle users -- TTO 2011-05-30
		Twinkle.delimages();
	//	Twinkle.deprod();
		Twinkle.batchdelete();
	//	Twinkle.batchprotect();
	//	Twinkle.imagetraverse();
	//	Twinkle.batchundelete();
	//}
	// run the initialization callbacks for any custom modules
	$(Twinkle.initCallbacks).each(function(k, v) { v(); });
	Twinkle.addInitCallback = function(func) { func(); };

	// increates text size in Twinkle dialogs bigger, if so configured
	if (Twinkle.getPref("dialogLargeFont")) {
		mw.util.addCSS(".morebits-dialog-content, .morebits-dialog-footerlinks { font-size: 100% !important; } " +
			".morebits-dialog input, .morebits-dialog select, .morebits-dialog-content button { font-size: inherit !important; }");
	}
};

//
}
//
// </nowiki>