(function($) {

/*

Twinkle.batchdelete = function twinklebatchdelete {
	if (
		Morebits.userIsSysop && (
			(mw.config.get('wgCurRevisionId') && mw.config.get('wgNamespaceNumber') > 0) ||
			mw.config.get('wgCanonicalSpecialPageName') === 'Prefixindex'
		)
	) {
		Twinkle.addPortletLink;

Twinkle.batchdelete.unlinkCache = {};

// Has the subpages list been loaded?
var subpagesLoaded;

Twinkle.batchdelete.callback = function twinklebatchdeleteCallback {
	subpagesLoaded = false;
	var Window = new Morebits.simpleWindow;
	Window.setTitle('일괄 삭제');
	Window.setScriptName('트윙클');
	Window.addFooterLink;

	var form = new Morebits.quickForm(Twinkle.batchdelete.callback.evaluate);
	form.append({
		type: 'checkbox',
		list: [
				label: '문서를 삭제합니다',
				name: 'delete_page',
				value: 'delete',
				checked: true,
				subgroup: {
					type: 'checkbox',
					list: [
							label: '연관된 토론 문서를 삭제합니다 (사용자 토론 문서 제외)',
							name: 'delete_talk',
							value: 'delete_talk',
							checked: true
							label: '삭제된 문서의 넘겨주기를 삭제합니다',
							name: 'delete_redirects',
							value: 'delete_redirects',
							checked: true
							label: '삭제된 문서의 하위 문서를 삭제합니다',
							name: 'delete_subpages',
							value: 'delete_subpages',
							checked: false,
							event: Twinkle.batchdelete.callback.toggleSubpages,
							subgroup: {
								type: 'checkbox',
								list: [
										label: '삭제된 하위 문서의 토론 문서를 삭제합니다',
										name: 'delete_subpage_talks',
										value: 'delete_subpage_talks'
										label: '삭제된 하위 문서의 넘겨주기를 삭제합니다',
										name: 'delete_subpage_redirects',
										value: 'delete_subpage_redirects'
										label: '삭제된 하위 문서의 백링크의 링크를 제거합니다 (일반 및 포털 이름공간에만 해당)',
										name: 'unlink_subpages',
										value: 'unlink_subpages'
								]
					]
				label: '각 문서의 백링크의 링크를 제거합니다 (일반 및 포털 이름공간에만 해당)',
				name: 'unlink_page',
				value: 'unlink',
				checked: false
				label: '각 파일의 이용을 제거합니다 (모든 이름공간에 해당)',
				name: 'unlink_file',
				value: 'unlink_file',
				checked: true
		]
	form.append({
		type: 'input',
		name: 'reason',
		label: '이유: ',
		size: 60

	var query = {
		'action': 'query',
		'prop': 'revisions|info|imageinfo',
		'inprop': 'protection',
		'rvprop': 'size|user'

	// On categories
	if (mw.config.get('wgNamespaceNumber') === 14) {
		query.generator = 'categorymembers';
		query.gcmtitle = mw.config.get('wgPageName');
		query.gcmlimit = Twinkle.getPref('batchMax');

	// On Special:PrefixIndex

		query.generator = 'allpages';
		query.gaplimit = Twinkle.getPref('batchMax');
		if (mw.util.getParamValue('prefix')) {
			query.gapnamespace = mw.util.getParamValue('namespace');
			query.gapprefix = mw.util.getParamValue('prefix');
			var pathSplit = decodeURIComponent(location.pathname).split('/');
			if (pathSplit.length < 3 || pathSplit[2] !== '특수:접두어찾기') {
				return;
			var titleSplit = pathSplit[3].split(':');
			query.gapnamespace = mw.config.get('wgNamespaceIds')[titleSplit[0].toLowerCase];
			if (titleSplit.length < 2 || typeof query.gapnamespace === 'undefined') {
				query.gapnamespace = 0; // article namespace
				query.gapprefix = pathSplit.splice(3).join('/');
				pathSplit = pathSplit.splice(4);
				pathSplit.splice(0, 0, titleSplit.splice(1).join(':'));
				query.gapprefix = pathSplit.join('/');

	// On normal pages
		query.generator = 'links';
		query.titles = mw.config.get('wgPageName');
		query.gpllimit = Twinkle.getPref('batchMax');

	var statusdiv = document.createElement('div');
	statusdiv.style.padding = '15px'; // just so it doesn't look broken
	Window.setContent(statusdiv);
	Morebits.status.init(statusdiv);
	Window.display;

	Twinkle.batchdelete.pages = {};

	var statelem = new Morebits.status('문서 목록을 가져옵니다');
	var wikipedia_api = new Morebits.wiki.api('불러오는 중...', query, function(apiobj) {
		var xml = apiobj.responseXML;
		var $pages = $(xml).find('page').filter(':not([missing])'); // :not([imagerepository="shared"])
		$pages.each(function {
			var $page = $(page);
			var ns = $page.attr('ns');
			var title = $page.attr('title');
			var isRedir = $page.attr('redirect') === '';
			var $editprot = $page.find('pr[type="edit"][level="sysop"]');
			var isProtected = $editprot.length > 0;
			var size = $page.find('rev').attr('size');

			var metadata = [];
			if (isRedir) {
				metadata.push('넘겨주기');
			if (isProtected) {
				metadata.push('완전 보호 -' +
				($editprot.attr('expiry') === 'infinity' ? ' 무기한)' : ' 만료일: ' + new Morebits.date($editprot.attr('expiry')).calendar('utc') + ' (UTC)'));
			if (ns === '6') { // mimic what delimages used to show for files
				metadata.push('올린이: ' + $page.find('ii').attr('user'));
				metadata.push('마지막 편집자: ' + $page.find('rev').attr('user'));
				metadata.push(mw.language.convertNumber(size) + ' 바이트');
			Twinkle.batchdelete.pages[title] = {
				label: title + (metadata.length ? ' (' + metadata.join('; ') + ')' : ''),
				value: title,
				checked: true,
				style: isProtected ? 'color:red' : ''

		var form = apiobj.params.form;
		form.append;
		form.append({
			type: 'button',
			label: '모두 선택',
			event: function dBatchSelectAll {
				$(result).find('input[name=pages]:not(:checked)').each(function {
					e.click; // check it, and invoke click event so that subgroup can be shown

				// Check any unchecked subpages too
				$('input[name="pages.subpages"]').prop;
		form.append({
			type: 'button',
			label: '모두 선택 해제',
			event: function dBatchDeselectAll {
				$(result).find('input[name=pages]:checked').each(function {
					e.click; // uncheck it, and invoke click event so that subgroup can be hidden
		form.append({
			type: 'checkbox',
			name: 'pages',
			id: 'tw-dbatch-pages',
			shiftClickSupport: true,
			list: $.map(Twinkle.batchdelete.pages, function (e) {
				return e;
		form.append({ type: 'submit' });

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

		Morebits.quickForm.getElements.forEach(generateArrowLinks);

	wikipedia_api.params = { form: form, Window: Window };
	wikipedia_api.post;

function generateArrowLinks (checkbox) {
	var link = Morebits.htmlNode;
	link.setAttribute;
	link.setAttribute('href', mw.util.getUrl(checkbox.value));
	link.setAttribute;
	checkbox.nextElementSibling.append(link);

Twinkle.batchdelete.generateNewPageList = function(form) {

	// Update the list of checked pages in Twinkle.batchdelete.pages object
	var elements = form.elements.pages;
	if (elements instanceof NodeList) { // if there are multiple pages
		for (var i = 0; i < elements.length; ++i) {
			Twinkle.batchdelete.pages[elements[i].value].checked = elements[i].checked;
		Twinkle.batchdelete.pages[elements.value].checked = elements.checked;

	return new Morebits.quickForm.element({
		type: 'checkbox',
		name: 'pages',
		id: 'tw-dbatch-pages',
		shiftClickSupport: true,
		list: $.map(Twinkle.batchdelete.pages, function (e) {
			return e;

Twinkle.batchdelete.callback.toggleSubpages = function twDbatchToggleSubpages(e) {

	var form = e.target.form;
	var newPageList;

	if (e.target.checked) {

		form.delete_subpage_redirects.checked = form.delete_redirects.checked;
		form.delete_subpage_talks.checked = form.delete_talk.checked;
		form.unlink_subpages.checked = form.unlink_page.checked;

		// If lists of subpages were already loaded once, they are
		// available without use of any API calls
		if (subpagesLoaded) {

			$.each(Twinkle.batchdelete.pages, function {
				// Get back the subgroup from subgroup_, where we saved it
				if (el.subgroup === null && el.subgroup_) {
					el.subgroup = el.subgroup_;

			newPageList = Twinkle.batchdelete.generateNewPageList(form);
			$('#tw-dbatch-pages').replaceWith(newPageList);

			Morebits.quickForm.getElements.forEach(generateArrowLinks);
			Morebits.quickForm.getElements.forEach(generateArrowLinks);

			return;

		// Proceed with API calls to get list of subpages
		var loadingText = '';
		$(e.target).after(loadingText);

		var pages = $(form.pages).map(function {
			return el.value;

		var subpageLister = new Morebits.batchOperation;
		subpageLister.setOption('chunkSize', Twinkle.getPref('batchChunks'));
		subpageLister.setPageList(pages);
		subpageLister.run(function worker (pageName) {
			var pageTitle = mw.Title.newFromText(pageName);

			// No need to look for subpages in main/file/mediawiki space
			if ([0, 6, 8].indexOf(pageTitle.namespace) > -1) {
				subpageLister.workerSuccess;
				return;

			var wikipedia_api = new Morebits.wiki.api(pageName + '의 하위 문서 목록을 가져오는 중', {
				action: 'query',
				prop: 'revisions|info|imageinfo',
				generator: 'allpages',
				rvprop: 'size',
				inprop: 'protection',
				gapprefix: pageTitle.title + '/',
				gapnamespace: pageTitle.namespace,
				gaplimit: 'max', // 500 is max for normal users, 5000 for bots and sysops
				pageNameFull: pageName // Not used by API, but added for access in onSuccess
				var xml = apiobj.responseXML;
				var $pages = $(xml).find('page');
				var subpageList = [];
				$pages.each(function {
					var $page = $(page);
					var ns = $page.attr('ns');
					var title = $page.attr('title');
					var isRedir = $page.attr('redirect') === '';
					var $editprot = $page.find('pr[type="edit"][level="sysop"]');

					var isProtected = $editprot.length > 0;
					var size = $page.find('rev').attr('size');

					var metadata = [];
					if (isRedir) {
						metadata.push('redirect');
					if (isProtected) {
						metadata.push('fully protected' +
						($editprot.attr('expiry') === 'infinity' ? ' indefinitely' : ', expires ' + new Morebits.date($editprot.attr('expiry')).calendar('utc') + ' (UTC)'));
					if (ns === '6') { // mimic what delimages used to show for files
						metadata.push('uploader: ' + $page.find('ii').attr('user'));
						metadata.push('last edit from: ' + $page.find('rev').attr('user'));
						metadata.push(mw.language.convertNumber(size) + ' 바이트');
					subpageList.push({
						label: title + (metadata.length ? ' (' + metadata.join('; ') + ')' : ''),
						value: title,
						checked: true,
						style: isProtected ? 'color:red' : ''
				if (subpageList.length) {
					var pageName = apiobj.query.pageNameFull;
					Twinkle.batchdelete.pages[pageName].subgroup = {
						type: 'checkbox',
						name: 'subpages',
						className: 'dbatch-subpages',
						shiftClickSupport: true,
						list: subpageList
				subpageLister.workerSuccess;
				subpageLister.workerFailure;
			wikipedia_api.post;

			// List 'em on the interface

			newPageList = Twinkle.batchdelete.generateNewPageList(form);
			$('#tw-dbatch-pages').replaceWith(newPageList);

			Morebits.quickForm.getElements.forEach(generateArrowLinks);
			Morebits.quickForm.getElements.forEach(generateArrowLinks);

			subpagesLoaded = true;

			// Remove "Loading... " text
			$('#dbatch-subpage-loading').remove;


		$.each(Twinkle.batchdelete.pages, function {
			if (el.subgroup) {
				// Remove subgroup after saving its contents in subgroup_
				// so that it can be retrieved easily if user decides to
				// delete the subpages again
				el.subgroup_ = el.subgroup;
				el.subgroup = null;

		newPageList = Twinkle.batchdelete.generateNewPageList(form);
		$('#tw-dbatch-pages').replaceWith(newPageList);

		Morebits.quickForm.getElements.forEach(generateArrowLinks);

Twinkle.batchdelete.callback.evaluate = function twinklebatchdeleteCallbackEvaluate(event) {
	Morebits.wiki.actionCompleted.notice = '일괄 삭제가 지금 끝났습니다';

	var form = event.target;

	var numProtected = $(Morebits.quickForm.getElements).filter(function {
		return element.checked && element.nextElementSibling.style.color === 'red';
	if (numProtected > 0 && !confirm('완전 보호 중인 ' + mw.language.convertNumber(numProtected) + '개 문서를 삭제하려고 합니다. 계속하시겠습니까?')) {
		return;

	var input = Morebits.quickForm.getInputData(form);

	if (!input.reason) {
		alert('이유를 지정해야 합니다!');
		return;
	Morebits.simpleWindow.setButtonsEnabled(false);
	Morebits.status.init(form);
	if (input.pages.length === 0) {
		Morebits.status.error;
		return;

	var pageDeleter = new Morebits.batchOperation(input.delete_page ? '문서 삭제 중' : '요청 작업 초기화 중');
	pageDeleter.setOption('chunkSize', Twinkle.getPref('batchChunks'));
	// we only need the initial status lines if we're deleting the pages in the pages array
	pageDeleter.setOption;
	pageDeleter.setPageList(input.pages);
	pageDeleter.run(function worker(pageName) {
		var params = {
			page: pageName,
			delete_page: input.delete_page,
			delete_talk: input.delete_talk,
			delete_redirects: input.delete_redirects,
			unlink_page: input.unlink_page,
			unlink_file: input.unlink_file && /^(File|Image|파일|그림):/i.test(pageName),
			reason: input.reason,
			pageDeleter: pageDeleter

		var wikipedia_page = new Morebits.wiki.page;
		wikipedia_page.setCallbackParameters(params);
		if (input.delete_page) {
			wikipedia_page.setEditSummary(input.reason + Twinkle.getPref('deletionSummaryAd'));
			wikipedia_page.suppressProtectWarning;
			wikipedia_page.deletePage;
			Twinkle.batchdelete.callbacks.doExtras(wikipedia_page);
		if (input.delete_subpages) {
			var subpageDeleter = new Morebits.batchOperation('하위 문서 삭제 중');
			subpageDeleter.setOption('chunkSize', Twinkle.getPref('batchChunks'));
			subpageDeleter.setOption;
			subpageDeleter.setPageList(input.subpages);
			subpageDeleter.run(function(pageName) {
				var params = {
					page: pageName,
					delete_page: true,
					delete_talk: input.delete_subpage_talks,
					delete_redirects: input.delete_subpage_redirects,
					unlink_page: input.unlink_subpages,
					unlink_file: false,
					reason: input.reason,
					pageDeleter: subpageDeleter

				var wikipedia_page = new Morebits.wiki.page;
				wikipedia_page.setCallbackParameters(params);
				wikipedia_page.setEditSummary(input.reason + Twinkle.getPref('deletionSummaryAd'));
				wikipedia_page.suppressProtectWarning;
				wikipedia_page.deletePage;

Twinkle.batchdelete.callbacks = {
	// this stupid parameter name is a temporary thing until I implement an overhaul
	// of Morebits.wiki.* callback parameters
	doExtras: function(thingWithParameters) {
		var params = thingWithParameters.parent ? thingWithParameters.parent.getCallbackParameters :
			thingWithParameters.getCallbackParameters;
		// the initial batch operation's job is to delete the page, and that has
		// succeeded by now
		params.pageDeleter.workerSuccess(thingWithParameters);

		var query, wikipedia_api;

		if (params.unlink_page) {
			Twinkle.batchdelete.unlinkCache = {};
			query = {
				'action': 'query',
				'list': 'backlinks',
				'blfilterredir': 'nonredirects',
				'blnamespace': [0, 100], // main space and portal space only
				'bltitle': params.page,
				'bllimit': 'max' // 500 is max for normal users, 5000 for bots and sysops
			wikipedia_api = new Morebits.wiki.api;
			wikipedia_api.params = params;
			wikipedia_api.post;

		if (params.unlink_file) {
			query = {
				'action': 'query',
				'list': 'imageusage',
				'iutitle': params.page,
				'iulimit': 'max' // 500 is max for normal users, 5000 for bots and sysops
			wikipedia_api = new Morebits.wiki.api;
			wikipedia_api.params = params;
			wikipedia_api.post;

		if (params.delete_page) {
			if (params.delete_redirects) {
				query = {
					'action': 'query',
					'titles': params.page,
					'prop': 'redirects',
					'rdlimit': 'max' // 500 is max for normal users, 5000 for bots and sysops
				wikipedia_api = new Morebits.wiki.api;
				wikipedia_api.params = params;
				wikipedia_api.post;
			if (params.delete_talk) {
				var pageTitle = mw.Title.newFromText(params.page);
				if (pageTitle && pageTitle.namespace % 2 === 0 && pageTitle.namespace !== 2) {
					pageTitle.namespace++; // now pageTitle is the talk page title!
					query = {
						'action': 'query',
						'titles': pageTitle.toText
					wikipedia_api = new Morebits.wiki.api;
					wikipedia_api.params = params;
					wikipedia_api.params.talkPage = pageTitle.toText;
					wikipedia_api.post;
	deleteRedirectsMain: function(apiobj) {
		var xml = apiobj.responseXML;
		var pages = $(xml).find('rd').map(function {
			return $(this).attr('title');
		if (!pages.length) {
			return;

		var redirectDeleter = new Morebits.batchOperation('다음 문서의 넘겨주기 삭제 중: ' + apiobj.params.page);
		redirectDeleter.setOption('chunkSize', Twinkle.getPref('batchChunks'));
		redirectDeleter.setPageList(pages);
		redirectDeleter.run(function(pageName) {
			var wikipedia_page = new Morebits.wiki.page;
			wikipedia_page.setEditSummary('삭제된 문서의 넘겨주기 문서: "' + apiobj.params.page + '"' + Twinkle.getPref('deletionSummaryAd'));
			wikipedia_page.deletePage;
	deleteTalk: function(apiobj) {
		var xml = apiobj.responseXML;
		var exists = $(xml).find('page:not([missing])').length > 0;

		if (!exists) {
			// no talk page; forget about it
			return;

		var page = new Morebits.wiki.page;
		page.setEditSummary('삭제된 문서의 토론 문서: "' + apiobj.params.page + '"' + Twinkle.getPref('deletionSummaryAd'));
		page.deletePage;
	unlinkBacklinksMain: function(apiobj) {
		var xml = apiobj.responseXML;
		var pages = $(xml).find('bl').map(function {
			return $(this).attr('title');
		if (!pages.length) {
			return;

		var unlinker = new Morebits.batchOperation('백링크 제거: ' + apiobj.params.page);
		unlinker.setOption('chunkSize', Twinkle.getPref('batchChunks'));
		unlinker.setPageList(pages);
		unlinker.run(function(pageName) {
			var wikipedia_page = new Morebits.wiki.page;
			var params = $.extend;
			params.title = pageName;
			params.unlinker = unlinker;
			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.unlinker.workerSuccess(pageobj);
			return;

		var text;
		if (params.title in Twinkle.batchdelete.unlinkCache) {
			text = Twinkle.batchdelete.unlinkCache[params.title];
			text = pageobj.getPageText;
		var old_text = text;
		var wikiPage = new Morebits.wikitext.page(text);
		text = wikiPage.removeLink(params.page).getText;

		Twinkle.batchdelete.unlinkCache[params.title] = text;
		if (text === old_text) {
			// Nothing to do, return
			params.unlinker.workerSuccess(pageobj);
			return;
		pageobj.setEditSummary('삭제된 문서의 링크 제거: ' + params.page + Twinkle.getPref('deletionSummaryAd'));
		pageobj.setPageText(text);
		pageobj.setCreateOption('nocreate');
		pageobj.setMaxConflictRetries(10);
		pageobj.save;
	unlinkImageInstancesMain: function(apiobj) {
		var xml = apiobj.responseXML;
		var pages = $(xml).find('iu').map(function {
			return $(this).attr('title');
		if (!pages.length) {
			return;

		var unlinker = new Morebits.batchOperation('백링크의 링크 제거 중: ' + apiobj.params.page);
		unlinker.setOption('chunkSize', Twinkle.getPref('batchChunks'));
		unlinker.setPageList(pages);
		unlinker.run(function(pageName) {
			var wikipedia_page = new Morebits.wiki.page;
			var params = $.extend;
			params.title = pageName;
			params.unlinker = unlinker;
			wikipedia_page.setCallbackParameters(params);
			wikipedia_page.load(Twinkle.batchdelete.callbacks.unlinkImageInstances);
	unlinkImageInstances: function(pageobj) {
		var params = pageobj.getCallbackParameters;
		if (!pageobj.exists) {
			// we probably just deleted it, as a recursive backlink
			params.unlinker.workerSuccess(pageobj);
			return;

		var image = params.page.replace(/^(?:Image|File|그림|파일):/, '');
		var text;
		if (params.title in Twinkle.batchdelete.unlinkCache) {
			text = Twinkle.batchdelete.unlinkCache[params.title];
			text = pageobj.getPageText;
		var old_text = text;
		var wikiPage = new Morebits.wikitext.page(text);
		text = wikiPage.commentOutImage.getText;

		Twinkle.batchdelete.unlinkCache[params.title] = text;
		if (text === old_text) {
			pageobj.getStatusElement.error;
			params.unlinker.workerFailure(pageobj);
			return;
		pageobj.setEditSummary('파일 인스턴스 ' + image + ' 제거. 이유: "' + params.reason + '")' + Twinkle.getPref('deletionSummaryAd'));
		pageobj.setPageText(text);
		pageobj.setCreateOption('nocreate');
		pageobj.setMaxConflictRetries(10);
		pageobj.save;

Twinkle.addInitCallback;