Медиавики:Gadget-qualityArticles.js

Мавод аз Википедиа — донишномаи озод

Эзоҳ: Баъди захира намудан, Шумо метавонед тағйиротҳои худро аз хотираи браузер гузариш карда, бубинед. Дар браузерҳои Mozilla / Firefox / Safari: тугмаи Shift-ро пахш намуда бо мушак Reload-ро пахш кунед, ё Ctrl-Shift-R-ро пахш намоед (Cmd-Shift-R барои компютерҳои Apple Mac); дар браузери IE: тугмаи Ctrl-ро пахш намуда бо мушак Refresh-ро пахш намоед, ё Ctrl-F5-ро пахш намоед; дар браузери Konqueror:: бо мушак Reload-ро пахш кунед, ё тугмаи F5-ро пахш намоед; дар браузери Opera ба Шумо пурра тоза кардани хотираи браузер ба воситаи Tools→Preferences лозим аст.

/*
Скрипт автоматизирует ряд действий для проекта Добротные статьи:
1. Добавляет ссылку "Номинировать в ДС" в группе "Инструменты" слева -> номинация статей на ВП:КДС.
   См. addButtonsNominate
2. Добавляет ссылку "Номинировать на лишение статуса ДС" в группе "Инструменты" слева -> номинация на снятие статуса ДС.
   См. addNominateToCancellationButtons
3. На подстраницах ВП:КДС добавляет кнопки За, Против и Комментарий,
   а для избрирающих - кнопки Избрать, Избрать + в ХС, Отказать, Отказать + в ХС/ИС.
   См. addButtonsDiscussion
4. На страницу ВП:КДС добавляет кнопку для архивирования завершенных обсуждений.
   См. addButtonsArchive
5. На страницы добротных статей добавляет кнопку "Категории ДС" для изменения категорий.
   См. addButtonsChangeCategories

Список избирающих задается ниже в isOfficer.

Developers: актуальная разработческая версия скрипта здесь: Участник:Нирваньчик/ds.js
 */
var TgWikiQualityArticles = function() {

	var tgWikiQualityArticles = this;
	var summarySuffix = TgWikiQualityArticles.summarySuffix;

	// Википедиа:Номзад ба мақолаҳои хушсифат/Рӯйхат
	var PAGE_ID_CANDIDATES = 5020826;
	// Википедиа:Мқолаҳои хушсифат/Гурӯҳҳо
	var PAGE_ID_CATEGORIES = 5027966;

	var PAGE_PREFIX_LIST = 'Википедиа:Номзад ба мақолаҳои хушсифат/Рӯйхат/';
	var PAGE_CANCEL_DISC = this.PAGE_CANCEL_DISC = 'Лоиҳа:Мақолаҳои хушсифат/Маҳруми унвон';

	var categories = null;
	
	var COMMONS_UPLOAD = 'https://upload.wikimedia.org/wikipedia/commons/thumb';
	
	var T_ERR = ' Хато!';
	var T_OK = ' Бо муваффақият.';
	var T_SIGN = '~' + '~' + '~' + '~';
	var T_SIGN_A = ' — ' + T_SIGN;

	function isOfficer() {
		var userName = mw.config.get( 'wgUserName' );
		// Список избирающих проекта Добротные статьи (в алфавитном порядке)
		return false 
			|| userName == 'AnimusVox' // 
			|| userName == 'Avner' //
			|| userName == 'Bapak Alex' // 
			|| userName == 'Christian Valentine' // 
			|| userName == 'Dmartyn80' //
			|| userName == 'DZ' //
			|| userName == 'El Presedente' //
			|| userName == 'EKBCitizen' //
			|| userName == 'EvaInCat' //
			|| userName == 'Fastboy' // 
			|| userName == 'Giulini' // 
			|| userName == 'Horim' // 
			|| userName == 'Kosta1974' //
			|| userName == 'Lapsy' //
			|| userName == 'Melissanda' //
			|| userName == 'Minina' // 
			|| userName == 'Pessimist2006' // 
			|| userName == 'P.Fisxo' // 
			|| userName == 'Sir Shurf' //
			|| userName == 'Triumphato' // 
			|| userName == 'Vlsergey'//
			|| userName == 'Vicpeters' // 
			|| userName == 'Voyagerim' //
			|| userName == 'Vyacheslav84' //
			|| userName == 'Wanwa' // 
			|| userName == 'WindWarrior' // 
			|| userName == 'Yuri Rubtcov' // 
			|| userName == 'Есстествоиспытатель' //
			|| userName == 'Иван Богданов' //
			|| userName == 'Красный Партизан' //
			|| userName == 'Люба КБ' //
			|| userName == 'Николай Эйхвальд' //
			|| userName == 'Полиционер' //
			|| userName == 'Роман Курносенко' //
			|| userName == 'Томасина' //
			|| userName == 'Юлия 70';
	};

	var notifyOptions = this.notifyOptions = {
		autoHide: true,
		tag: 'QA-Gadget',
	};

	var notifyOptOk = notifyOptions;

	// Ошибки показываем как ошибки - в красной рамке
	// и не убираем чтобы юзер успел прочитать, и закрыть сам.
	var notifyOptError = {
		autoHide: false,
		type: 'error',
	};
	
	var getFirstObjectValue = TgWikiQualityArticles.getFirstObjectValue;

	var funcArray = function(size) {
		var funcEmpty = function() {
			alert( 'Empty function call (error)' );
		};
		var funcs = new Array( size ).map( function( x, i ) {
			return funcEmpty;
		} );
		return funcs;
	};

	this.addButtonsDiscussion = function() {
		// check if this is nominations page
		if ( $( "tgWikiQualityArticlesNavigation" ).length != 1 )
		return;
		var officer = isOfficer();
		addButtonsHtmlToKDS(officer);
		addOpinionForm();
		if (officer) {
			addSummaryForm();
		}
	};
	
	var addButtonsHtmlToKDS = function(officer) {
		$( "div#mw-content-text > h2" ).each(
		function( index ) {
			var jThis = $( this );
			
			var hasSubsection = false;
			var curr = jThis.next();
			while ( curr.length === 1 ) {
				if ( curr[0].nodeName === "H3" || curr[0].nodeName === "H4" ) {
					hasSubsection = true;
					break;
				}
				if ( curr[0].nodeName === "H2" ) {
					break;
				}
				curr = curr.next();
			}
			if ( hasSubsection ) {
				return;
			}
			
			var sectionTitle = jThis.find( "span.mw-headline" ).text();
			var sectionIndexStr;
			jThis.find( "span.mw-editsection a" ).each( function( i, a ) {
				var jA = $( a );
				var editUrl = jA.attr( 'href' );
				if ( editUrl.indexOf( '&action=edit&' ) !== -1 ) {
					sectionIndexStr = editUrl.substring( editUrl.indexOf( '&section=' ) + '&section='.length );
				}
			} );
			if ( typeof sectionIndexStr === 'undefined' ) {
				mw.log( 'Unable to detect section index for headline «' + sectionTitle + '»' );
				return;
			}
			
			jThis.css( 'clear', 'both' );
			
			var div = $( document.createElement( 'div' ) ).css( {
				float: 'right'
			} );
			$( document.createElement( 'div' ) ) //
			.addClass( 'tgWikiQualityButton' ).addClass( 'tgWikiQualityButtonOpinion' ) //
			.data( 'opinion-type', 'Тарафдор' ).data( 'section-index', sectionIndexStr ).data( 'section-title', sectionTitle )//
			.text( 'Тарафдор' ).appendTo( div );
			$( document.createElement( 'div' ) ) //
			.addClass( 'tgWikiQualityButton' ).addClass( 'tgWikiQualityButtonOpinion' ) //
			.data( 'opinion-type', 'Муқобил' ).data( 'section-index', sectionIndexStr ).data( 'section-title', sectionTitle )//
			.text( 'Муқобил' ).appendTo( div );
			$( document.createElement( 'div' ) ) //
			.addClass( 'tgWikiQualityButton' ).addClass( 'tgWikiQualityButtonOpinion' ) //
			.data( 'opinion-type', 'Шарҳ' ).data( 'section-index', sectionIndexStr ).data( 'section-title', sectionTitle )//
			.text( 'Шарҳ' ).appendTo( div );
			
			var hintText = 'Внимание: большой процент совпадения может ничего не означать. Например, это нужные цитаты и неперерабатываемые словосочетания, или наоборот, скопировано из статьи Википедии, или сайт является клоном Википедии.';
			$( document.createElement( 'br' ) ).appendTo( div );
			$( document.createElement( 'div' ) ).html(
			'&nbsp;&nbsp;<small><a href="//tools.wmflabs.org/copyvios/?lang=ru&project=wikipedia&oldid=&action=search&use_engine=1&use_links=1&title='
			+ encodeURIComponent( sectionTitle )
			+ '">Проверить плагиат</a></small>'
			+ '&nbsp;<img title="' + hintText + '" src="' + COMMONS_UPLOAD 
			+ '/f/fb/Unknown_toxicity_icon.svg/16px-Unknown_toxicity_icon.svg.png" width="16" height="16">' )
			.appendTo( div );
			
			if ( officer ) {
				$( document.createElement( 'br' ) ).appendTo( div );
				$( document.createElement( 'div' ) ) //
				.addClass( 'tgWikiQualityButton' ).addClass( 'tgWikiQualityButtonSummary' ) //
				.data( 'summary-type', 'yes' ).data( 'section-index', sectionIndexStr ).data( 'section-title', sectionTitle )//
				.text( 'Избрать' ).appendTo( div );
				$( document.createElement( 'div' ) ) //
				.addClass( 'tgWikiQualityButton' ).addClass( 'tgWikiQualityButtonSummary' ) //
				.data( 'summary-type', 'togood' ).data( 'section-index', sectionIndexStr ).data( 'section-title', sectionTitle )//
				.text( 'Избрать + в ХС' ).appendTo( div );
				$( document.createElement( 'br' ) ).appendTo( div );
				$( document.createElement( 'div' ) ) //
				.addClass( 'tgWikiQualityButton' ).addClass( 'tgWikiQualityButtonSummary' ) //
				.data( 'summary-type', 'no' ).data( 'section-index', sectionIndexStr ).data( 'section-title', sectionTitle )//
				.text( 'Отказать' ).appendTo( div );
				$( document.createElement( 'div' ) ) //
				.addClass( 'tgWikiQualityButton' ).addClass( 'tgWikiQualityButtonSummary' ) //
				.data( 'summary-type', 'toobig' ).data( 'section-index', sectionIndexStr ).data( 'section-title', sectionTitle )//
				.text( 'Отказать + в ХС/ИС' ).appendTo( div );
			}
			
			jThis.after( div );
		} );
		
		$( "div.tgWikiQualityButton" ).button();		
	};
	
	var addOpinionForm = function() {
		$( "div#mw-content-text" ).after(
		'<div id="tgWikiAddOpinionForm" title="Добавление оценки для статьи"><form><fieldset>' + '<p id="tgWikiAddOpinionFormTitle"></p>'
		+ '<textarea name="opiniontext" id="tgWikiAddOpinionFormOpinionText" style="height: 150px;" class="text ui-widget-content ui-corner-all"></textarea>'
		+ '<p class="validateTips" style="color:gray">Поле комментария обязательно к заполнению</p>'
		+ '<p class="tgWikiAddOpinionFormDesc" style="color:gray"></p>' + '<input type="hidden" name="type" id="tgWikiAddOpinionFormOpinionType" value="">'
		+ '<input type="hidden" name="section" id="tgWikiAddOpinionFormOpinionSectionIndex" value="">'
		+ '<input type="hidden" name="section" id="tgWikiAddOpinionFormOpinionSectionTitle" value="">' + '</fieldset>' + '</form>' + '</div>' );
		$( "div#ruWikiAddOpinionForm" ).hide();
		
		
		$( "div.tgWikiQualityButtonOpinion" ).click(
		function( event ) {
			var opinionType = $( this ).data( 'opinion-type' );
			var sectionIndex = $( this ).data( 'section-index' );
			var sectionTitle = $( this ).data( 'section-title' );
			
			var opinionFormDiv = $( "div#tgWikiAddOpinionForm" );
			var opinionFormDivDesc = opinionFormDiv.find( 'p.tgWikiAddOpinionFormDesc' );
			var opinionTextField = opinionFormDiv.find( '#tgWikiAddOpinionFormOpinionText' );
			var opinionTypeField = opinionFormDiv.find( '#tgWikiAddOpinionFormOpinionType' );
			var opinionSectionIndexField = opinionFormDiv.find( '#tgWikiAddOpinionFormOpinionSectionIndex' );
			var opinionSectionTitleField = opinionFormDiv.find( '#tgWikiAddOpinionFormOpinionSectionTitle' );
			var allFields = $( [] ).add( opinionTextField ).add( opinionTypeField ).add( opinionSectionIndexField ).add( opinionSectionTitleField );
			var tips = opinionFormDiv.find( 'p.validateTips' );
			var addOpinionFormTitle = 'Шарҳ барои мақола  «' + sectionTitle + '»:';
			
			if ( opinionType == 'За' ) {
				addOpinionFormTitle = 'Шарҳи баҳо <img src="' + COMMONS_UPLOAD
				+ '/c/c2/Pictogram_voting_support.svg/15px-Pictogram_voting_support.svg.png"/>'
				+ '<b>За</b> для статьи «' + sectionTitle + '»:';
			} else if ( opinionType == 'Муқобил' ) {
				addOpinionFormTitle = 'Шарҳи баҳо <img src="' + COMMONS_UPLOAD 
				+ '/e/e9/Pictogram_voting_oppose.svg/15px-Pictogram_voting_oppose.svg.png"/>'
				+ '<b>Муқобил</b> барои мақола «' + sectionTitle + '»:';
			}
			$( "#tgWikiAddOpinionFormTitle" ).html( addOpinionFormTitle );
			opinionFormDivDesc.text( 'Шаблон {{' + opinionType + '}} ва имзои корбар (' + T_SIGN + ') бо тарзи автоматӣ илова мешаванд ' );
			opinionTypeField.val( opinionType );
			opinionSectionIndexField.val( sectionIndex );
			opinionSectionTitleField.val( sectionTitle );
			
			opinionFormDiv.dialog( {
				autoOpen: false,
				height: 'auto',
				width: 600,
				modal: true,
				buttons: {
					"Иловаи баҳо": function() {
						var bValid = true;
						allFields.removeClass( "ui-state-error" );
						bValid = bValid && tgWikiQualityArticles.checkNotEmpty( opinionTextField );
						
						if ( bValid ) {
							$( this ).dialog( "close" );
							
							var type = opinionTypeField.val();
							var sectionIndex = opinionSectionIndexField.val();
							var sectionTitle = opinionSectionTitleField.val();
							var newText = '\r\n* {{' + type + '}} ' + opinionTextField.val() + T_SIGN_A;
							
							var operation = 'Захираи матни нави фасл…';
							mw.notify(operation, notifyOptOk);
							new mw.Api().postWithEditToken( {
								action: 'edit',
								pageid: mw.config.get( 'wgArticleId' ),
								section: sectionIndex,
								summary: '/* ' + sectionTitle + ' */ «' + type + '» ' + summarySuffix,
								appendtext: newText,
								} ).done( function( result ) {
								mw.notify(operation + T_OK + ' Саҳифаро нав кунед, барои намоиши тағйирот', notifyOptOk);
								return;
								} ).fail( function( jqXHR, textStatus, errorThrown ) {
								mw.notify(operation + T_ERR, notifyOptError);
								alert( "Саҳифаро захира кардан нашуд: " + textStatus );
								return;
							} );
						}
					},
					"Лағв": function() {
						$( this ).dialog( "close" );
					}
				},
				close: function() {
				}
			} );
			
			opinionFormDiv.dialog( "open" );
		} );
	};
	
	var addSummaryForm = function() {
		var commonTitlePrefix = 'Номзад ба мақолаҳои хушсифат/';
		$( "div#mw-content-text" ).after(
		'<div id="tgWikiSummaryForm" title="Подведение итога по статье">'
		+ '<form>'
		+ '<fieldset>'
		+ '<label for="opiniontext" id="tgWikiSummaryFormSummaryTitle">Комментарий:</label> <br />'
		+ '<textarea name="opiniontext" id="ruWikiSummaryFormSummaryText" style="height: 120px;" class="text ui-widget-content ui-corner-all"></textarea>'
		+ '<p class="validateTips" style="color:gray">Поле комментария обязательно к заполнению</p>'
		+ '<p class="ruWikiSummaryFormDesc" style="color:gray"></p>'
		+ '<span class="tgWikiSummaryCategorySpan">'
		+ '<p id="tgWikiSummaryFormTitle"><br/>А также укажите подходящую категорию из <a href="https://tg.wikipedia.org/wiki/Википедия:Добротные_статьи/Категории">общего списка</a> для размещения статьи на <a href="https://ru.wikipedia.org/wiki/Википедия:ДС">главной странице ВП:ДС</a>:</p>'
		+ '<table border="0"><tr><td><label for="category1">Основная категория:</label></td><td><input type="text" name="category1" class="ruWikiQACategoryTextField" id="ruWikiSummaryFormCategory1" value="" placeholder="Введите первые буквы категории" size="50"></td></tr>'
		+ '<tr><td><label for="category2">Доп. категория:</label></td><td><input type="text" name="category2" class="ruWikiQACategoryTextField" id="ruWikiSummaryFormCategory2" value="" size="50"></td></tr>'
		+ '<tr><td><label for="category3">Доп. категория 2:</label></td><td><input type="text" name="category3" class="ruWikiQACategoryTextField" id="ruWikiSummaryFormCategory3" value="" size="50"></td></tr></table>'
		+ '</span>' + '<input type="hidden" name="type" id="tgWikiSummaryFormSummaryType" value="">'
		+ '<input type="hidden" name="section" id="tgWikiSummaryFormSectionIndex" value="">'
		+ '<input type="hidden" name="section" id="tgWikiSummaryFormSectionTitle" value="">' + '</fieldset>' + '</form>' + '</div>' );
		$( "div#ruWikiSummaryForm" ).hide();
		
		$( "div.ruWikiQualityButtonSummary" ).click(
		function( event ) {
			var summaryType = $( this ).data( 'summary-type' );
			var sectionIndex = $( this ).data( 'section-index' );
			var sectionTitle = $( this ).data( 'section-title' );
			
			var summaryFormDiv = $( "div#ruWikiSummaryForm" );
			summaryFormDiv.hide();
			var summaryFormDivDesc = summaryFormDiv.find( 'p.tgWikiSummaryFormDesc' );
			var summaryCategory1Field = summaryFormDiv.find( '#tgWikiSummaryFormCategory1' );
			var summaryCategory2Field = summaryFormDiv.find( '#tgWikiSummaryFormCategory2' );
			var summaryCategory3Field = summaryFormDiv.find( '#tgWikiSummaryFormCategory3' );
			var summaryTextField = summaryFormDiv.find( '#tgWikiSummaryFormSummaryText' );
			var summaryTypeField = summaryFormDiv.find( '#tgWikiSummaryFormSummaryType' );
			var summarySectionIndexField = summaryFormDiv.find( '#tgWikiSummaryFormSectionIndex' );
			var summarySectionTitleField = summaryFormDiv.find( '#tgWikiSummaryFormSectionTitle' );
			var allFields = $( [] ).add( summaryTextField ).add( summaryTypeField ).add( summarySectionIndexField ).add( summarySectionTitleField );
			var tips = summaryFormDiv.find( 'p.validateTips' );
			
			var summaryText;
			var newStatus;
			
			if ( summaryType == "yes" || summaryType == "togood" ) {
				summaryFormTitle = 'Комментарий при ' + ruWikiQualityArticles.htmlSuccess + ' <b>избрании статьи</b>:';
				$( "#tgWikiSummaryFormSummaryTitle" ).html( summaryFormTitle );
				$( "#tgWikiSummaryFormSummaryText" ).attr( 'placeholder', 'Требованиям [[ВП:ТДС]] соответствует.' );
			}
			
			if ( summaryType == "yes" ) {
				summaryFormDivDesc.text( 'Шаблон {{Сделано|Статья избрана}} и подпись участника (' + T_SIGN + ') будут добавлены автоматически ' );
				summaryText = 'Статья «[[' + sectionTitle + ']]» избрана' + summarySuffix;
				newStatus = 'accepted';
				$( "span.ruWikiSummaryCategorySpan" ).show();
			}
			if ( summaryType == "togood" ) {
				summaryFormDivDesc.text( 'Шаблон {{Сделано|Статья избрана и рекомендована в хорошие}} и подпись участника (' + T_SIGN
				+ ') будут добавлены автоматически ' );
				summaryText = 'Статья «[[' + sectionTitle + ']]» избрана и рекомендована в хорошие' + summarySuffix;
				newStatus = 'accepted';
				$( "span.tgWikiSummaryCategorySpan" ).show();
			}
			if ( summaryType == "no" ) {
				summaryFormTitle = 'Комментарий при <b>отказе в статусе</b>:';
				$( "#tgWikiSummaryFormSummaryTitle" ).html( summaryFormTitle );
				$( "#tgWikiSummaryFormSummaryText" ).attr( 'placeholder', 'Подробная причина отказа со ссылками на требования ВП:ТДС' );
				summaryFormDivDesc.text( 'Шаблон {{Не сделано|Статья не избрана}} и подпись участника (' + T_SIGN + ') будут добавлены автоматически ' );
				summaryText = 'Статья «[[' + sectionTitle + ']]» НЕ избрана' + summarySuffix;
				newStatus = 'declined';
				$( "span.tgWikiSummaryCategorySpan" ).hide();
			}
			if ( summaryType == "toobig" ) {
				summaryFormTitle = 'Комментарий при <b>отказе в статусе</b> из-за большого размера:';
				$( "#tgWikiSummaryFormSummaryTitle" ).html( summaryFormTitle );
				summaryFormDivDesc.text( 'Шаблон {{Не сделано|Статья не избрана}} и подпись участника (' + T_SIGN + ') будут добавлены автоматически ' );
				$( "#tgWikiSummaryFormSummaryText" ).attr( 'placeholder', 'Подробная причина отказа со ссылками на требования ВП:ТДС' );
				summaryText = 'Статья «[[' + sectionTitle + ']]» НЕ избрана' + summarySuffix;
				newStatus = 'declined';
				$( "span.tgWikiSummaryCategorySpan" ).hide();
			}
			summaryTypeField.val( summaryType );
			summarySectionIndexField.val( sectionIndex );
			summarySectionTitleField.val( sectionTitle );
			
			summaryFormDiv.dialog( {
				autoOpen: false,
				height: 'auto',
				width: 600,
				modal: true,
				buttons: {
					"Подвести итог": function() {
						var success = true;
						var bValid = true;
						allFields.removeClass( "ui-state-error" );
						bValid = bValid && ruWikiQualityArticles.checkNotEmpty( summaryTextField );
						if ( !bValid ) {
							return;
						}
						
						if ( summaryType == "yes" || summaryType == "togood" ) {
							bValid = bValid && ruWikiQualityArticles.checkNotEmpty( summaryCategory1Field );
							if ( !bValid ) {
								return;
							}
						}
						
						$( this ).dialog( "close" );
						
						var templText;
						switch ( summaryType ) {
							case "yes": templText = 'Сделано|Статья избрана'; break;
							case "no":
							case "toobig": templText = 'Не сделано|Статья не избрана'; break;
							case "togood": templText = 'Сделано|Статья избрана и рекомендована в хорошие'; break;
						}
						var newText = "\r\n=== Итог ===\r\n{{" + templText + "}} "
						+ summaryTextField.val() + " — ~" + "~" + "~" + "~\r\n";
						var categoriesNames = [ summaryCategory1Field.val(), summaryCategory2Field.val(), summaryCategory3Field.val() ];
						var type = summaryTypeField.val();
						var sectionIndex = summarySectionIndexField.val();
						var sectionTitle = summarySectionTitleField.val();
						
						var newTemplate = '{{Добротная статья';
						for ( var i = 0; i < 3; i++ ) {
							if ( categoriesNames[i] ) {
								newTemplate = newTemplate + '|' + categoriesNames[0];
							}
						}
						newTemplate = newTemplate + '}}';
						
						var funcs = funcArray(9);
						
						// Добавление раздела "Итог" на текущую страницу (в текущую секцию)
						funcs[0] = function() {
							var operation = 'Добавление итога на страницу номинации…';
							mw.notify(operation, notifyOptOk);
							new mw.Api().postWithEditToken( {
								action: 'edit',
								pageid: mw.config.get( 'wgArticleId' ),
								section: sectionIndex,
								summary: summaryText,
								appendtext: newText
								} ).done( function() {
								mw.notify(operation + T_OK, notifyOptOk);
								funcs[1]();
								} ).fail( function() {
								success = false;
								console.log( arguments );
								mw.notify(operation + T_ERR, notifyOptError);
								finalize('Ошибка', 'Не удалась операция: ' + operation, 'error');
							} );
						};
						
						// Замена "inprogress" на новый статус в списке кандидатов
						funcs[1] = function() {
							tgWikiQualityArticles.changeStatusInList( sectionTitle, 'inprogress', newStatus, summaryText )
							.fail(function() { success = false; })
							.always( funcs[2] );
						};
						
						// Замена шаблона КХС или просто добавление шаблона ДС
						funcs[2] = function() {
							var operation1 = 'Получение текста номинированной статьи…';
							mw.notify(operation1, notifyOptOk);
							tgWikiQualityArticles.apiQueryLatestRevision( {
								titles: sectionTitle,
								} ).done( function( result ) {
								var pageInfo = getFirstObjectValue( result.query.pages );
								if ( !pageInfo.revisions || !pageInfo.revisions[0] || !pageInfo.revisions[0]['*'] ) {
									mw.notify(operation1 + T_ERR, notifyOptError);
									success = false;
									funcs[3]();
									return undefined;
								}
								mw.notify(operation1 + T_OK, notifyOptOk);
								var content = pageInfo.revisions[0]['*'];
								var patt = new RegExp( "\\{\\{Кандидат в добротные статьи\\|([0-9а-я ]*)\\}\\}", "i" );
								
								var newContent;
								if ( summaryType == "no" || summaryType == "toobig" ) {
									newContent = content.replace( patt, '' );
								} else {
									newContent = content.replace( patt, newTemplate );
									if ( content === newContent ) {
										newContent = content + '\n' + newTemplate;
									}
								}
								
								var operation2 = 'Обновление текста номинированной статьи…';
								mw.notify(operation2, notifyOptOk);
								new mw.Api().postWithEditToken( {
									action: 'edit',
									nocreate: true,
									title: sectionTitle,
									summary: summaryText,
									text: newContent,
									} ).done( function() {
									mw.notify(operation2 + T_OK, notifyOptOk);
									} ).fail( function() {
									success = false;
									console.log( arguments );
									mw.notify(operation2 + T_ERR, notifyOptError);
								} ).always( funcs[3] );
								
								return {};
								} ).fail( function() {
								success = false;
								mw.notify(operation1 + T_ERR, notifyOptError);
								funcs[3];
							});
						};
						
						// Добавление шаблона {{Сообщение ДС|...}} на страницу обсуждения статьи
						funcs[3] = function() {
							
							var insert_param = '';
							if ( summaryType == "no" ) {
								insert_param = '|Кандидат';
							} else if ( summaryType == "toobig" ) {
								insert_param = '|Кандидат в ХС/ИС';
							}
							var toAppend = '{{Сообщение ДС|' + mw.config.get( 'wgTitle' ).substring( commonTitlePrefix.length ) + '|'
							+ RuWikiQualityArticles.getCurrentDateWikitext() + insert_param + '}}';
							
							appendTemplateToTalkPage('Обсуждение:' + sectionTitle, toAppend, summaryText)
							.fail(function() { success = false; })
							.always( funcs[4] );
						};
						
						// Обновление шаблонов проектов
						funcs[4] = function() {
							if ( summaryType == "no" || summaryType == "toobig" ) {
								funcs[5]();
								return;
							}
							
							updateProjectTemplates('Обсуждение:' + sectionTitle, 'ДС' )
							.fail(function() { success = false; })
							.always( funcs[5] );
						};
						
						// Обновление списков категорий
						var addToCategoryIndex = 5;
						for ( var catIndex = 0; catIndex < 3; catIndex++ ) {
							funcs[addToCategoryIndex + catIndex] = ( function( i ) {
								return function() {
									if ( summaryType == "no" || summaryType == "toobig" || !categoriesNames[i] ) {
										funcs[addToCategoryIndex + i + 1]();
										return;
									}
									ruWikiQualityArticles.addToCategory( categoriesNames[i], sectionTitle )
									//.fail(function() { success = false; }) fail() может не прокатить, см. внутрь функции, надо рефакторить
									.always( funcs[addToCategoryIndex + i + 1] );
								};
							} )( catIndex );
						}
						funcs[addToCategoryIndex + 3] = function() {
							if (success) {
								finalize('Всё сделано', 'Все правки завершены успешно', 'info');
							} else {
								finalize('Завершено', 'Завершено с ошибками. Просмотрите ваш вклад и попробуйте доделать нужные правки вручную.', 'error');
							}
						};
						var finalize = function(title, message, status) {
							ruWikiQualityArticles.reloadWithMessage(title, message, status);
						};
						funcs[0]();
					},
					"Отменить": function() {
						$( this ).dialog( "close" );
					}
				},
				close: function() {
				}
			} );
			
			summaryFormDiv.dialog( "open" );
			summaryTextField.focus();
			
			var summaryTextYes = 'Требованиям [[ВП:ТДС]] соответствует.';
			var summaryTextTooBig = 'Статья слишком велика для ДС (несоответствие п. 8 [[ВП:ТДС]]). '
			+ 'Рекомендуется доработать её и номинировать в [[ВП:КХС|хорошие]]/[[ВП:КИС|избранные]].';
			var newSummaryText = $( "#ruWikiSummaryFormSummaryText" ).text();
			if ( newSummaryText == '' || newSummaryText == summaryTextYes || newSummaryText == summaryTextTooBig ) {
				if ( summaryType == "yes" || summaryType == "togood" ) {
					$( "#ruWikiSummaryFormSummaryText" ).text( summaryTextYes );
				} else if ( summaryType == "no" ) {
					$( "#ruWikiSummaryFormSummaryText" ).text( '' );
				} else if ( summaryType == "toobig" ) {
					$( "#ruWikiSummaryFormSummaryText" ).text( summaryTextTooBig );
				}
			}
			
			if ( ( summaryType == "yes" || summaryType == "togood" ) && tgWikiQualityArticles.categories == null ) {
				tgWikiQualityArticles.loadCategories();
			}
		} );
	};
	
	this.loadCategories = function() {
		mw.notify( 'Загружаем список категорий добротных статей', tgWikiQualityArticles.notifyOptions );

		fillCategoriesFromWikitext = function( wikitext ) {
			var lines = wikitext.split( '\n' );

			var repeat = function( x, n ) {
				"use strict";

				var s = '';
				for ( ;; ) {
					if ( n & 1 )
						s += x;
					n >>= 1;
					if ( n )
						x += x;
					else
						break;
				}
				return s;
			};

			var fill = function( startLine, level, previousLevelPrefix ) {
				"use strict";

				for ( var lineIndex = startLine; lineIndex < lines.length; lineIndex++ ) {

					var re = new RegExp( "^" + repeat( '\\*', level ) + "\\s+(.*)$", "" );
					var match = re.exec( lines[lineIndex] );
					if ( !match ) {
						if ( level == 1 )
							continue;

						return lineIndex - 1;
					}

					var categoryName = match[1];
					tgWikiQualityArticles.categories.push( {
						value: categoryName,
						label: previousLevelPrefix + categoryName,
					} );

					lineIndex = fill( lineIndex + 1, level + 1, previousLevelPrefix + categoryName + ': ' );
				}
			};

			fill( 0, 1, '' );
		};

		new mw.Api().get( {
			action: 'query',
			prop: 'revisions',
			rvprop: 'content',
			pageids: PAGE_ID_CATEGORIES,
		} ).done( function( data ) {
			var text = data.query.pages[PAGE_ID_CATEGORIES].revisions[0]['*'];
			if ( !text ) {
				alert( 'Список категорий не найден' );
				mw.notify( 'Список категорий добротных статей не найден', ruWikiQualityArticles.notifyOptions );
				return;
			}

			tgWikiQualityArticles.categories = [];
			fillCategoriesFromWikitext( text );
			ruWikiQualityArticles.categories.sort();
			$( '.ruWikiQACategoryTextField' ).autocomplete( {
				source: ruWikiQualityArticles.categories
			} );

			mw.notify( 'Список категорий добротных статей успешно загружен', tgWikiQualityArticles.notifyOptions );
		} ).fail( function( jqXHR, textStatus, errorThrown ) {
			mw.notify( 'Проблема с загрузкой списка категорий:\n' + textStatus, ruWikiQualityArticles.notifyOptions );
			alert( 'Проблема с загрузкой списка категорий:\n' + textStatus );
		} );
	};

	this.addFinalDialog = function() {
		$( "div#mw-content-text" ).after(
			'<div id="ruWikiQualityFinalDialog" title="Готово">'
			+ '<table border="0"><tr><td width="70" align="center" id = "ruWikiQualityFinalIcon">'
			+ '</td><td>'
			+ '<p id="ruWikiQualityFinalMessage">Успех</p>'
			+ '<p id="ruWikiQualityCountDownText">Страница будет перезагружена через <b>10</b> секунд</p>'
			+ '</td></tr></table>' 
			+ '</div>' );
		var formDiv = $( "div#ruWikiQualityFinalDialog" );
		formDiv.hide();
		formDiv.dialog( {
			autoOpen: false,
			height: 'auto',
			width: 600,
			modal: true
		} );
	}
	// Simple Countdown class from stackoverflow.com
	function Countdown(options) {
		var timer,
		instance = this,
		seconds = options.seconds || 5,
		updateStatus = options.onUpdateStatus || function () {},
		counterEnd = options.onCounterEnd || function () {};
		
		function decrementCounter() {
			updateStatus(seconds);
			if (seconds === 0) {
				counterEnd();
				instance.stop();
			}
			seconds--;
		}
		
		this.start = function () {
			clearInterval(timer);
			timer = 0;
			seconds = options.seconds;
			timer = setInterval(decrementCounter, 1000);
		};
		
		this.stop = function () {
			clearInterval(timer);
		};
	}
	
	/**
	 * Покажет диалог с обратным счётчиком, по истечении него перезагрузит страницу.
	 * status = 'error', 'warn', 'info'
	 */
	var reloadWithMessage = this.reloadWithMessage = function(title, message, status) {
		var formDiv = $( "div#ruWikiQualityFinalDialog" );
		formDiv.dialog( "option", "title", title );
		formDiv.find( '#ruWikiQualityFinalMessage' ).html(message);
		iconTable = {
			"info": COMMONS_UPLOAD + '/9/99/Nuvola_apps_important_green.svg/64px-Nuvola_apps_important_green.svg.png',
			"warn": COMMONS_UPLOAD +'/d/dc/Nuvola_apps_important_yellow.svg/64px-Nuvola_apps_important_yellow.svg.png',
			"error": COMMONS_UPLOAD + '/f/f7/Nuvola_apps_important.svg/64px-Nuvola_apps_important.svg.png'
		};
		icon = iconTable[status]
		if (!icon) {
			icon = iconTable["info"]
		}
		formDiv.find('#ruWikiQualityFinalIcon').html('<img src="'+ icon +'" height="64" width="64">');
		
		var SECONDS_COUNT = 5;
		if (status=='error') {
			SECONDS_COUNT = 10; // Больше времени чтобы юзер успел обратить внимание и прочитать
		}
		var countDownText = formDiv.find( '#ruWikiQualityCountDownText' );
		countDownText.html('Страница будет перезагружена через <b>' + SECONDS_COUNT + '</b> секунд');
		
		var myCounter = new Countdown({  
			seconds:SECONDS_COUNT,  // number of seconds to count down
			onUpdateStatus: function(sec){
				console.log(sec);
				countDownText.html('Страница будет перезагружена через <b>' + sec + '</b> секунд');
				},
			onCounterEnd: function(){
				formDiv.dialog( "close" );
				ruWikiQualityArticles.purge();
				}
		});
		myCounter.start();
		formDiv.dialog( "option", "buttons", {
			"Перезагрузить сейчас": function() {
				$( this ).dialog( "close" );
				ruWikiQualityArticles.purge();
			},
			"Отмена": function() {
				$( this ).dialog( "close" );
				myCounter.stop();
			}
		});
		formDiv.dialog( "open" );
	};
	
	this.addButtonsNominate = function() {
		if (
		// уже ДС
		$( "#qa-message" ).length !== 0 || $( "#quality-candidate" ).length !== 0 ||
		// уже ХС
		$( "#ga-message" ).length !== 0 || $( "#good-candidate" ).length !== 0 ||
		// уже ИС
		$( "#fa-message" ).length !== 0 || $( "#featured-candidate" ).length !== 0 ||
		// Дисамбиги в ДС не выдвигаются
		$( "table#disambig" ).length !== 0 ||
		// Вообще не статья или режим не просмотра
		mw.config.get( 'wgNamespaceNumber' ) !== 0 || mw.config.get( 'wgAction' ) !== 'view' ) {
			return;
		}

		$( "div#mw-content-text" ).after(
			'<div id="ruWikiQualityNominate" title="Выдвижение статьи в добротные">'
			+ '<table border="0"><tr><td width="50" align="center">'
			+ '<img src="' + COMMONS_UPLOAD + '/6/67/Grey_star_boxed_plus.svg/40px-Grey_star_boxed_plus.svg.png" height="40" width="40"></td><td>'
			+ '<p>Перед выдвижением ознакомьтесь с <a href="https://tg.wikipedia.org/wiki/Википедия:ТДС">требованиями к добротным статьям</a></b>.<br/>'
			+ 'Пожалуйста, не номинируйте <b>более 3 статей в день</b>. Если номинируете статью впервые, укажите это при номинировании и дождитесь итога по первой номинации, прежде чем действовать дальше.</p>'
			+ '</td></tr></table>' + '<form><fieldset>'
			+ '<textarea name="opiniontext" id="tgWikiQualityNominateComment" style="height: 150px;" class="text ui-widget-content ui-corner-all"></textarea>'
			+ '</fieldset></form>'
			+ '<p class="validateTips" style="color:gray">Поле комментария обязательно к заполнению,<br/>ваша подпись будет добавлена автоматически.</p>'
			+ '</div>' );

		var nominateFormDiv = $( "div#ruWikiQualityNominate" );
		nominateFormDiv.hide();
		var nominateCommentField = nominateFormDiv.find( '#tgWikiQualityNominateComment' );
		var allFields = $( [] ).add( nominateCommentField );
		var tips = nominateFormDiv.find( 'p.validateTips' );

		nominateFormDiv.dialog( {
			autoOpen: false,
			height: 'auto',
			width: 600,
			modal: true,
			buttons: {
				"Номинировать": function() {
					var bValid = true;
					allFields.removeClass( "ui-state-error" );
					bValid = bValid && ruWikiQualityArticles.checkNotEmpty( nominateCommentField );

					if ( bValid ) {
						$( this ).dialog( "close" );
						ruWikiQualityArticles.nominateImpl( nominateCommentField.val() );
					}
				},
				"Отменить": function() {
					$( this ).dialog( "close" );
				}
			}
		} );

		RuWikiQualityArticles.addToolboxMenuButton( 'Номинировать в ДС', function() {
			tgWikiQualityArticles.nominate();
		} );
	};

	function appendTemplateToTalkPage(articleTitle, templateText, summaryText) {
		//operation = 'Добавление шаблона сообщения ДС на страницу обсуждения статьи…';
		return pageGetEditSave(articleTitle, 'Добавление шаблона сообщения ДС на страницу обсуждения статьи… ', 
			appendTemplateToTalkPageImpl, {templateText: templateText}, summaryText);
	}
	
	function trim(str) {
        return str.replace(/^\s+|\s+$/g,"");
	}
	
	function appendTemplateToTalkPageImpl(content, params) {
		var templateText = params.templateText;
		if (!content || 0 === content.length) return templateText;
		if (trim(content) == "") return content + templateText;
		var regexp1 = /^\s*\{\{/i;
		var regexp2 = /^\s*\{\{(Википедия:Рецензирование|Архив|Новые|ВП-проекты)/i;
		var next = content.indexOf('\n', 0);
		var index = 0, lastT = 0;
		for (;index < content.length; index = next+1, next = content.indexOf('\n', index)) {
			if (next == -1) next = content.length;
			var line = trim(content.substring(index, next));
			if (line === "") continue;
			if (regexp1.test(line)) {
				if (regexp2.test(line)) break;
				lastT = next+1;
				continue;
			}
			break;
		}
		if (lastT > content.length) lastT = content.length;
		if (lastT > 0 && content.substr(lastT-1, 1) != '\n') templateText = "\n" + templateText;
		return content.substring(0, lastT) + templateText + "\n" + content.substring(lastT);
	}
	
	this.changeStatusInList = function( articleTitle, oldStatus, newStatus, summaryText ) {
		var d = $.Deferred();

		mw.notify( 'Получение служебного списка кандидатов для обновления…', {
			tag: 'QA-Gadget::changeStatusInList',
			type: 'info',
		} );
		ruWikiQualityArticles.apiQueryLatestRevision( {
			pageids: PAGE_ID_CANDIDATES,
		} ).done( function( result ) {
			try {
				var pageInfo = getFirstObjectValue( result.query.pages );
				if ( !pageInfo.revisions || !pageInfo.revisions[0] || !pageInfo.revisions[0]['*'] ) {
					mw.notify( 'Получение служебного списка кандидатов для обновления… Неизвестная ошибка!', {
						tag: 'QA-Gadget::changeStatusInList',
						type: 'error',
					} );
					alert( 'Невозможно получить текст списка кандидатов' );
					d.reject();
					return;
				}

				var content = pageInfo.revisions[0]['*'];
				if ( content.indexOf( '|' + articleTitle + '|' + oldStatus ) === -1 ) {
					mw.notify( 'Получение служебного списка кандидатов для обновления… Статья не найдена в списке кандидатов!', {
						tag: 'QA-Gadget::changeStatusInList',
						type: 'error',
					} );
					alert( 'Статья «' + articleTitle + '» не найдена в списке кандидатов' );
					d.reject();
					return;
				}

				mw.notify( 'Обновление служебного списка кандидатов…', {
					tag: 'QA-Gadget::changeStatusInList',
					type: 'info',
				} );
				content = content.replace( '|' + articleTitle + '|' + oldStatus, '|' + articleTitle + '|' + newStatus );
				new mw.Api().postWithEditToken( {
					action: 'edit',
					nocreate: true,
					pageid: PAGE_ID_CANDIDATES,
					summary: summaryText,
					text: content,
				} ).done( function() {
					mw.notify( 'Обновление служебного списка кандидатов… Успешно.', {
						tag: 'QA-Gadget::changeStatusInList',
						type: 'info',
					} );
					d.resolve();
				} ).fail( function() {
					console.log( arguments );
					mw.notify( 'Обновление служебного списка кандидатов… Неизвестная ошибка!', {
						tag: 'QA-Gadget::changeStatusInList',
						type: 'error',
					} );
					d.reject.apply( d, arguments );
				} );
			} catch ( error ) {
				console.log( error );
				mw.notify( 'Обновление служебного списка кандидатов… Неизвестная ошибка!', {
					tag: 'QA-Gadget::changeStatusInList',
					type: 'error',
				} );
				d.reject( error );
			}
		} ).fail( function() {
			mw.notify( 'Получение служебного списка кандидатов для обновления… Неизвестная ошибка!', {
				tag: 'QA-Gadget::changeStatusInList',
				type: 'error',
			} );
			d.reject.apply( d, arguments );
		} );

		return d.promise();
	};

	var nominate = this.nominate = function() {
		var nominateFormDiv = $( "div#ruWikiQualityNominate" );
		nominateFormDiv.dialog( "open" );
		var nominateCommentField = nominateFormDiv.find( '#tgWikiQualityNominateComment' );
		nominateCommentField.focus();
	};

	var nominateImpl = this.nominateImpl = function( nominateComment ) {
		var commonTitlePrefix = 'Википедия:Кандидаты в добротные статьи/';
		var summaryEditCurrentArticle = '[[ВП:КДС|Номинирование статьи в добротные]]' + summarySuffix;
		var summaryEditNotCurrentArticle = 'Номинирование статьи «[[' + mw.config.get( 'wgTitle' ) + ']]»' + summarySuffix;

		var funcs = funcArray(8);
		var success = true;

		funcs[0] = function() {
			var operation = 'Определение текущей даты по серверу…';
			mw.notify(operation, notifyOptOk );
			new mw.Api().get( {
				action: 'expandtemplates',
				text: '{{CURRENTDAY}} {{CURRENTMONTHNAMEGEN}} {{CURRENTYEAR}}',
			} ).done( funcs[1] ).fail( function( jqXHR, textStatus, errorThrown ) {
				mw.notify( operation + ' не удалось: ' + textStatus, notifyOptError );
				finalize('Ошибка','Не удалась операция: ' + operation + '. Перезагрузите страницу и попробуйте ещё раз', 'warn');
				return;
			} );
		};
		var todayDateStr;
		funcs[1] = function( data ) {
			var operation = 'Определение текущей даты по серверу…';
			todayDateStr = data.expandtemplates['*'];
			if ( !todayDateStr ) {
				console.log( data );
				mw.notify( operation + ' не удалось', notifyOptError );
				finalize('Ошибка','Не удалась операция: ' + operation + '. Перезагрузите страницу и попробуйте ещё раз', 'warn');
				return;
			}
			mw.notify( 'Определение текущей даты по серверу: «' + todayDateStr + '»', notifyOptOk );
			funcs[2]();
		};
		// Добавление шаблона {{Кандидат в добротные статьи|...}} на текущую страницу
		funcs[2] = function() {
			var operation = 'Добавление шаблона кандидата в добротные статьи на страницу статьи…';
			mw.notify(operation, notifyOptOk );
			new mw.Api().postWithEditToken( {
				action: 'edit',
				pageid: mw.config.get( 'wgArticleId' ),
				appendtext: '\r\n{{Кандидат в добротные статьи|' + todayDateStr + '}}\r\n',
				summary: summaryEditCurrentArticle,
			} ).done( function() {
				mw.notify( operation + ' Добавлен.', notifyOptOk );
			} ).fail( function( jqXHR, textStatus, errorThrown ) {
				mw.notify( operation + ' Не удалось: ' + textStatus, notifyOptError );
				success = false;
			} ).always( funcs[3] );
		};
		// Добавление строки кандидата на страницу [[Википедия:Кандидаты в добротные статьи/Список]]
		funcs[3] = function() {
			var operation = 'Добавление информации о кандидате в служебный список…';
			mw.notify(operation, notifyOptOk );
			new mw.Api().postWithEditToken( {
				action: 'edit',
				pageid: PAGE_ID_CANDIDATES,
				appendtext: '\r\n' + todayDateStr + '|' + mw.config.get( 'wgTitle' ) + '|inprogress',
				summary: summaryEditNotCurrentArticle,
			} ).done( function() {
				mw.notify(operation + T_OK, notifyOptOk );
			} ).fail( function() {
				mw.notify(operation + T_ERR, notifyOptError );
				success = false;
			} ).always( funcs[4] );
		};
		// Добавление обсуждения кандидата на страницу [[Википедия:Кандидаты в добротные статьи/...]],  
		// 1) проверяем есть ли такая страница
        // 2) Если нет, создаём, если есть, дописываем в неё
		var KDE_Title;		
		funcs[4] = function() {
			KDE_Title = commonTitlePrefix + todayDateStr;
			new mw.Api().get( {
				action: 'query',
				prop: 'info',
				titles: KDE_Title,
			} ).done( function( result ) {
				if (Object.keys( result.query.pages )[0] == '-1') {
					funcs[5]();
				} else {
					funcs[6]();
				}
			} ).fail( function() {
				mw.notify( 'Получение информации о существовании статьи ' + KDE_Title + ':' + T_ERR, notifyOptError);
				finalize('Ошибка', 'Не удалось определить, существует ли страница ' + KDE_Title +'\nПожалуйста, добавьте обсуждение вручную.', 'error');
			} );
		};
		funcs[5] = function() {
			"use strict";
			var operation = 'Создание страницы с секцией обсуждения кандидата…';
			mw.notify(operation, notifyOptOk );
			new mw.Api().postWithEditToken( {
				action: 'edit',
				title: KDE_Title,
				createonly: true,
				text: '{{Кандидаты в добротные статьи - Навигация}}\r\n\r\n== [[' + mw.config.get( 'wgTitle' ) + ']] ==\r\n' + nominateComment + T_SIGN_A + '\r\n',
				summary: summaryEditNotCurrentArticle,
			} ).done( function() {
				mw.notify( operation + T_OK, notifyOptOk );
			} ).fail( function() {
				mw.notify( operation + T_ERR, notifyOptError );
				success = false;
			}).always(funcs[7]);
		};
		funcs[6] = function() {
			"use strict";
			var operation = 'Добавление секции обсуждения кандидата…';
			mw.notify(operation, notifyOptOk );
			var withExistingPage = new mw.Api().postWithEditToken( {
				action: 'edit',
				title: KDE_Title,
				nocreate: true,
				appendtext: '\r\n== [[' + mw.config.get( 'wgTitle' ) + ']] ==\r\n' + nominateComment + T_SIGN_A + '\r\n',
				summary: summaryEditNotCurrentArticle,
			} ).done( function() {
				mw.notify(operation + T_OK, notifyOptOk );
				} ).fail( function() {
				mw.notify(operation + T_ERR, notifyOptError );
				success = false;
			}).always(funcs[7]);
		};
		funcs[7] = function() {
			if (success) {
				finalize('Всё сделано', 'Все правки завершены успешно', 'info');
			} else {
				finalize('Завершено', 'Завершено с ошибками. Просмотрите ваш вклад и попробуйте доделать нужные правки вручную.', 'error');
			}
		};
		var finalize = function(title, message, status) {
			tgWikiQualityArticles.reloadWithMessage(title, message, status);
		};
		funcs[0]();
	};

	var updateTips = this.updateTips = function( o, t ) {
		o.addClass( "ui-state-error" );

		tips = $( ".validateTips" );
		tips.text( t ).addClass( "ui-state-highlight" );

		setTimeout( function() {
			tips.removeClass( "ui-state-highlight", 1500 );
			o.removeClass( "ui-state-error", 1500 );
		}, 1500 );
	};

	var checkNotEmpty = this.checkNotEmpty = function( o ) {
		if ( o.val().length === 0 ) {
			tgWikiQualityArticles.updateTips( o, "Поле должно быть заполнено." );
			return false;
		} else {
			return true;
		}
	};

	this.addButtonsArchive = function() {
		$( "span.ruWikiQualityCandatatesToArchivButton" ).before( "<br >" ).text( "Отправить в архив" ).button().click(
				function() {
					var dateStr = $( this ).data( "date" )

					var funcs = funcArray(5);

					var archiveTitle;
					var archiveListTitle;
					var list = '\n';

					funcs[0] = function() {
						var tokens = dateStr.split( " " );
						var month = tokens[1];
						var monthNumber;
						switch ( month ) {
						case 'января':
							monthNumber = "01";
							break;
						case 'февраля':
							monthNumber = "02";
							break;
						case 'марта':
							monthNumber = "03";
							break;
						case 'апреля':
							monthNumber = "04";
							break;
						case 'мая':
							monthNumber = "05";
							break;
						case 'июня':
							monthNumber = "06";
							break;
						case 'июля':
							monthNumber = "07";
							break;
						case 'августа':
							monthNumber = "08";
							break;
						case 'сентября':
							monthNumber = "09";
							break;
						case 'октября':
							monthNumber = "10";
							break;
						case 'ноября':
							monthNumber = "11";
							break;
						case 'декабря':
							monthNumber = "12";
							break;
						default: {
							mw.notify( 'Неизвестное название месяца: «' + month + '»', ruWikiQualityArticles.notifyOptions );
							alert( "Неизвестное название месяца: " + month );
							return;
						}
						}
						archiveTitle = "ВП:Кандидаты в добротные статьи/Архив/" + tokens[2] + "-" + monthNumber;
						archiveListTitle = archiveTitle + '/Список';

						var a1 = $( "<a>" ).attr( "href",
								mw.config.get( 'wgServer' ) + mw.config.get( 'wgScriptPath' ) + '/index.php?action=view&title=' + encodeURIComponent( archiveTitle ) )
						a1.html( '<b style="white-space:nowrap;">' + archiveTitle + '</b>' );

						var a2 = $( "<a>" ).attr( "href",
								mw.config.get( 'wgServer' ) + mw.config.get( 'wgScriptPath' ) + '/index.php?action=view&title=' + encodeURIComponent( archiveListTitle ) )
						a2.html( '<b style="white-space:nowrap;">' + archiveListTitle + '</b>' );

						mw.notify( 'Обновление служебного списка кандидатов (удаление)…', ruWikiQualityArticles.notifyOptions );

						tgWikiQualityArticles.apiQueryLatestRevision( {
							pageids: PAGE_ID_CANDIDATES,
						} ).done( function( result ) {
							var pageInfo = getFirstObjectValue( result.query.pages );
							if ( !pageInfo.revisions || !pageInfo.revisions[0] || !pageInfo.revisions[0]['*'] ) {
								alert( 'Невозможно получить текст списка кандидатов' );
								funcs[1]();
								return undefined;
							}
							var oldContent = pageInfo.revisions[0]['*'];
							while ( oldContent.indexOf( dateStr + '|' ) == 0 ) {
								var end = oldContent.indexOf( '\n' );
								var line;
								if ( end == -1 ) {
									line = oldContent + '\n';
									oldContent = '';
								} else {
									line = oldContent.substring( 0, end ) + '\n';
									oldContent = oldContent.substring( end + 1 );
								}
								list = list + line;
							}
							while ( oldContent.indexOf( '\n' + dateStr + '|' ) != -1 ) {
								var start = oldContent.indexOf( '\n' + dateStr + '|' );
								var end = oldContent.indexOf( '\n', start + 1 );
								var line;
								if ( end == -1 ) {
									line = oldContent.substring( start + 1 ) + '\n';
									oldContent = oldContent.substring( 0, start + 1 );
								} else {
									line = oldContent.substring( start + 1, end ) + '\n';
									oldContent = oldContent.substring( 0, start ) + '\n' + oldContent.substring( end + 1 );
								}
								list = list + line;
							}

							new mw.Api().postWithEditToken( {
								action: 'edit',
								pageid: PAGE_ID_CANDIDATES,
								summary: '[[' + archiveTitle + '|В архив]]' + summarySuffix,
								text: oldContent,
							} ).always( funcs[1] );

						} ).fail( funcs[1] );
					};

					funcs[1] = function() {
						mw.notify( 'Обновление служебного списка кандидатов в архиве…', ruWikiQualityArticles.notifyOptions );
						new mw.Api().postWithEditToken( {
							action: 'edit',
							title: archiveListTitle,
							summary: 'Архивация' + summarySuffix,
							appendtext: list,
						} ).always( funcs[2] );
					};

					funcs[2] = function() {
						mw.notify( 'Автоматическое создание страницы отображения архива…', ruWikiQualityArticles.notifyOptions );
						new mw.Api().postWithEditToken( {
							action: 'edit',
							title: archiveTitle,
							summary: 'Автоматическое создание' + summarySuffix,
							text: '{{Навигация по архиву КДС}}\r\n{{Википедия:Кандидаты в добротные статьи/Impl|list={{/Список}}|strike=0}}',
							createonly: true
						} ).always( funcs[3] );
					};

					funcs[3] = function() {
						mw.notify( 'Закрытие страницы номинаций…', tgWikiQualityArticles.notifyOptions );
						var nomTitle = 'Википедия:Кандидаты в добротные статьи/' + dateStr

						tgWikiQualityArticles.apiQueryLatestRevision( {
							titles: nomTitle,
							rvsection: 0,
						} ).done( function( result ) {
							var pageInfo = getFirstObjectValue( result.query.pages );
							if ( !pageInfo.revisions || !pageInfo.revisions[0] || !pageInfo.revisions[0]['*'] ) {
								mw.notify( 'Закрытие страницы номинаций… ' + T_ERR, ruWikiQualityArticles.notifyOptions );
								alert( 'Невозможно получить текст заголовка страницы номинаций' );
								funcs[4]();
								return undefined;
							}
							var oldContent = pageInfo.revisions[0]['*'];
							oldContent = oldContent.replace( '{{Кандидаты в добротные статьи - Навигация}}', '{{Кандидаты в добротные статьи - Навигация|closed=1}}' );

							new mw.Api().postWithEditToken( {
								action: 'edit',
								title: nomTitle,
								section: 0,
								text: oldContent,
								summary: 'Закрытие страницы номинаций' + summarySuffix,
							} ).always( funcs[4] );
						} ).fail( funcs[4] );

					};
					funcs[4] = function() {
						// no op
					};

					funcs[0]();
				} );
	};

	this.addButtonsChangeCategories = function() {
		if (
		// только для ДС
		$( "#qa-message" ).length === 0 || mw.config.get( 'wgAction' ) !== 'view' ) {
			return;
		}

		$( "div#mw-content-text" ).after(
			'<div id="ruWikiQAChangeCategoryForm" title="Изменение категорий добротной статьи">'
			+ '<form>'
			+ '<fieldset>'
			+ '<table border="0"><tr><td><label for="category1">Основная категория:</label></td><td><input type="text" name="category1" class="ruWikiQACategoryTextField" id="ruWikiQAChangeCategory1TextField" value="" placeholder="Введите первые буквы категории" size="50"></td></tr>'
			+ '<tr><td><label for="category2">Доп. категория:</label></td><td><input type="text" name="category2" class="ruWikiQACategoryTextField" id="ruWikiQAChangeCategory2TextField" value="" size="50"></td></tr>'
			+ '<tr><td><label for="category3">Доп. категория 2:</label></td><td><input type="text" name="category3" class="ruWikiQACategoryTextField" id="ruWikiQAChangeCategory3TextField" value="" size="50"></td></tr></table>'
			+ '</fieldset>' + '</form>' + '</div>' );
		$( "#ruWikiQAChangeCategoryForm" ).dialog( {
			autoOpen: false,
			height: 'auto',
			width: 600,
			modal: true,
			open: function( event, ui ) {
				var qaMessage = $( '#qa-message' );
				$( '#ruWikiQAChangeCategory1TextField' ).val( qaMessage.data( 'qa-category-1' ) );
				$( '#ruWikiQAChangeCategory2TextField' ).val( qaMessage.data( 'qa-category-2' ) );
				$( '#ruWikiQAChangeCategory3TextField' ).val( qaMessage.data( 'qa-category-3' ) );

				if ( tgWikiQualityArticles.categories == null ) {
					tgWikiQualityArticles.loadCategories();
				}
			},
			buttons: {
				"Поменять категории": function() {
					$( this ).dialog( 'close' );
					tgWikiQualityArticles.changeCategoriesImpl();
				},
				"Отменить": function() {
					$( this ).dialog( 'close' );
				}
			}
		} );

		RuWikiQualityArticles.addToolboxMenuButton( 'Категории ДС', function() {
			tgWikiQualityArticles.changeCategories();
		} );
	};

	var changeCategories = this.changeCategories = function() {
		$( "#ruWikiQAChangeCategoryForm" ).dialog( 'open' );
	};

	var changeCategoriesImpl = this.changeCategoriesImpl = function() {
		var qaMessage = $( '#qa-message' );
		var oldCategory1 = qaMessage.data( 'qa-category-1' );
		var oldCategory2 = qaMessage.data( 'qa-category-2' );
		var oldCategory3 = qaMessage.data( 'qa-category-3' );
		var oldCategories = [ oldCategory1, oldCategory2, oldCategory3 ];
		var newCategory1 = $( '#ruWikiQAChangeCategory1TextField' ).val();
		var newCategory2 = $( '#ruWikiQAChangeCategory2TextField' ).val();
		var newCategory3 = $( '#ruWikiQAChangeCategory3TextField' ).val();
		var newCategories = [ newCategory1, newCategory2, newCategory3 ];

		var funcs = funcArray(8);

		var functionBuilderAdd = function( i ) {
			return function() {
				if ( newCategories[i] && $.inArray( newCategories[i], oldCategories ) == -1 ) {
					tgWikiQualityArticles.addToCategory( newCategories[i], mw.config.get( 'wgTitle' ) ).always( funcs[0 + i + 1] );
				} else {
					funcs[0 + i + 1]();
				}
			};
		};
		var functionBuilderRemove = function( i ) {
			return function() {
				if ( oldCategories[i] && $.inArray( oldCategories[i], newCategories ) == -1 ) {
					tgWikiQualityArticles.removeFromCategory( oldCategories[i], mw.config.get( 'wgTitle' ) ).always( funcs[3 + i + 1] );
				} else {
					funcs[3 + i + 1]();
				}
			};
		};

		for ( var i = 0; i < 3; i++ ) {
			funcs[0 + i] = functionBuilderAdd( i );
			funcs[3 + i] = functionBuilderRemove( i );
		}

		funcs[6] = function() {
			var newTemplate = '{{Добротная статья';
			for ( var i = 0; i < 3; i++ ) {
				if ( newCategories[i] ) {
					newTemplate = newTemplate + '|' + newCategories[i];
				}
			}
			newTemplate = newTemplate + '}}';

			mw.notify( 'Получение текста статьи…', ruWikiQualityArticles.notifyOptions );
			tgWikiQualityArticles.apiQueryLatestRevision( {
				titles: mw.config.get( 'wgTitle' ),
			} ).done( function( result ) {
				var pageInfo = getFirstObjectValue( result.query.pages );
				if ( !pageInfo.revisions || !pageInfo.revisions[0] || !pageInfo.revisions[0]['*'] ) {
					mw.notify( 'Получение текста статьи… ' + T_ERR, tgWikiQualityArticles.notifyOptions );
					alert( 'Невозможно получить текст статьи' );
					funcs[7]();
					return undefined;
				}

				var content = pageInfo.revisions[0]['*'];
				var patt = new RegExp( "\\{\\{Добротная статья[^\\}]*\\}\\}", "i" );
				var newContent = content.replace( patt, newTemplate );

				if ( content === newContent ) {
					mw.notify( 'Обновление текста статьи… ' + T_ERR, tgWikiQualityArticles.notifyOptions );
					alert( "Не могу найти и обновить шаблон {{Добротная статья}} в тексте" );
					funcs[7]();
					return undefined;
				}

				mw.notify( 'Обновление содержимого шаблона {{Добротная статья}} в тексте статьи…', tgWikiQualityArticles.notifyOptions );
				new mw.Api().postWithEditToken( {
					title: mw.config.get( 'wgTitle' ),
					summary: 'Обновление категорий [[ВП:ДС|добротной статьи]]' + summarySuffix,
					text: newContent,
				} ).always( funcs[7] );
			} ).fail( funcs[7] );
		};
		funcs[7] = function() {
			tgWikiQualityArticles.purge();
		};
		funcs[0]();
	};

	var apiQueryLatestRevision = this.apiQueryLatestRevision = function( args ) {
		args.action = 'query';
		args.prop = 'revisions';
		args.rvprop = 'content';
		return new mw.Api().get( args );
	};

	var addToCategory = this.addToCategory = function( category, title ) {
		mw.notify( 'Добавление статьи «' + title + '» в список статей категории «' + category + '»…', {
			tag: 'QA-Gadget::addToCategory',
			type: 'info',
		} );

		return new mw.Api().postWithEditToken( {
			action: 'edit',
			title: PAGE_PREFIX_LIST + category,
			summary: 'Добавление статьи «' + title + '» в список' + summarySuffix,
			appendtext: '\n[[' + title + ']]',
		} ).done( function() {
			mw.notify( 'Статья «' + title + '» добавлена в список статей категории «' + category + '»', {
				tag: 'QA-Gadget::addToCategory',
				type: 'info',
			} );
		} ).fail( function() {
			console.log( arguments );
			mw.notify( 'Не удалось добавить статью «' + title + '» в список статей категории «' + category + '»', {
				tag: 'QA-Gadget::addToCategory',
				type: 'error',
			} );
		} );
	};

	this.removeFromCategory = function( category, title ) {

		var d = $.Deferred();
		var summaryText = 'Удаление статьи «[[' + title + ']]» из списка' + summarySuffix;

		mw.notify( 'Получение текущего служебного списка категории «' + summarySuffix + '»…', tgWikiQualityArticles.notifyOptions );
		tgWikiQualityArticles.apiQueryLatestRevision( {
			titles: PAGE_PREFIX_LIST + category,
		} ).done(
				function( result ) {
					var pageInfo = getFirstObjectValue( result.query.pages );
					if ( !pageInfo.revisions || !pageInfo.revisions[0] || !pageInfo.revisions[0]['*'] ) {
						mw.notify( 'Получение текущего служебного списка категории «' + summarySuffix + '»… Ошибка!', tgWikiQualityArticles.notifyOptions );
						alert( 'Невозможно получить список категории «' + category + '»' );
						d.reject();
						return undefined;
					}
					var oldContent = pageInfo.revisions[0]['*'] + '\n';
					if ( oldContent.indexOf( '\n[[' + title + ']]\n' ) === -1 ) {
						mw.notify(
								'Удаление статьи «' + title + '» из служебного списка категории «' + summarySuffix + '»… Ошибка: статья не найдена в служебном список категории',
								tgWikiQualityArticles.notifyOptions );
						alert( 'Статья не найдена в списке категории «' + category + '»' );
						d.reject();
						return undefined;
					}
					var newListContent = oldContent.replace( '\n[[' + title + ']]\n', '\n' );
					newListContent = newListContent.substring( 0, newListContent.length - 1 );

					mw.notify( 'Обновление служебного списка категории «' + summarySuffix + '»…', tgWikiQualityArticles.notifyOptions );
					new mw.Api().postWithEditToken( {
						title: PAGE_PREFIX_LIST + category,
						summary: summaryText,
						text: newListContent,
					} ).done( function() {
						d.resolve.apply( d, arguments );
					} ).fail( function() {
						d.reject.apply( d, arguments );
					} );
				} ).fail( function() {
			d.reject.apply( d, arguments );
		} );

		return d.promise();
	};

	this.purge = function() {
		mw.notify( 'Перезагрузка страницы…', notifyOptOk);
		window.location.replace( mw.config.get( 'wgServer' ) + mw.config.get( 'wgScriptPath' ) + '/index.php?action=purge&title='
				+ encodeURIComponent( mw.config.get( 'wgPageName' ) ) );
		return;
	};

	function updateProjectTemplatesImpl(content, params) {
		var newLevel = params.newLevel;
		var patterns = [ "\\{\\{статья проекта[^\\}]*\\}\\}", "\\{\\{проект[^\\}]*\\}\\}" ];
		var result = content;
		for ( var i = 0; i < patterns.length; i++ ) {
			var patt = new RegExp( patterns[i], "gi" );
			result = result.replace( patt, function( found, offset, s ) {
				var separatorIndex = found.indexOf( '\|' );
				if ( separatorIndex > 0 ) {
					if ( found.indexOf( 'уровень' ) > 0 ) {
						return found.replace( new RegExp( 'уровень\s*=\s*[A-Za-z0-9А-Яа-я]*', 'i' ), 'уровень=' + newLevel );
					} else { // ранее не было параметра уровень
						return found.substring( 0, separatorIndex ) + '\|уровень=' + newLevel + found.substring( separatorIndex, found.length );
					}
				} else { // статья ранее не была оценена совсем
					return found.replace( '}}', '\|уровень=' + newLevel + '\|важность=}}' );
				}
			} );
		}
		return result;
	}

	function updateProjectTemplates(articleTitle, newLevel) {
		return pageGetEditSave(articleTitle, 'Обновление шаблонов проектов… ', 
			updateProjectTemplatesImpl, {newLevel: newLevel}, 'Обновление шаблонов проектов', 'ignore');
	};

	function pageGetEditSave(title, operation, editFunc, params, summary, no_page) {
		var d = $.Deferred();

		var op1 = 'Получение текста страницы ' + title + '…';
		mw.notify(operation + op1, notifyOptOk);
		tgWikiQualityArticles.apiQueryLatestRevision( {
			titles: title,
			rvsection: 0,
		} ).done( function( result ) {
			var content = '';
			var pageInfo = getFirstObjectValue( result.query.pages );
			if ( !pageInfo.revisions || !pageInfo.revisions[0] || !pageInfo.revisions[0]['*'] ) {
				if (no_page == 'error') {
					mw.notify( operation + op1 + T_ERR + ' Страница не существует.', notifyOptError);
					d.reject( 'Невозможно получить текст страницы ' + title);
					return;
				} else if (no_page == 'ignore') {
					mw.notify( operation + ' Страница не существует. Обновление не требуется. ', notifyOptOk);
					d.resolve();
					return;
				} else {
					mw.notify(operation + op1 + T_OK + ' Страница ещё не создана. ', notifyOptOk);
				}
			} else {
				mw.notify(operation + op1 + T_OK, notifyOptOk);
				mw.notify(operation + 'Анализ текста страницы ' + title + '…', notifyOptOk);
				content = pageInfo.revisions[0]['*'];
			}			
			var newContent = editFunc(content, params);
			if ( content === newContent ) {
				mw.notify(operation + 'Обновление страницы ' + title + ' не требуется.', notifyOptOk);
				d.resolve();
				return;
			}

			var op2 = 'Сохранение изменений страницы ' + title + '…';
			mw.notify(operation + op2, notifyOptOk);
			var summaryText = summary || title;
			new mw.Api().postWithEditToken( {
				action: 'edit',
				title: title,
				summary: summaryText + summarySuffix,
				section: 0,
				text: newContent,
			} ).done( function() {
				mw.notify(operation + op2 + T_OK, notifyOptOk);
				d.resolve();
			} ).fail( function() {
				mw.notify(operation + op2 + T_ERR, notifyOptError);
				d.reject.apply( d, arguments );
			} );
		} ).fail( function() {
			mw.notify( operation + op1 + T_ERR, notifyOptError);
			d.reject.apply( d, arguments );
		} );

		return d.promise();
	}

	this.addNominateToCancellationButtons = function() {
		if ( $( "#qa-message" ).length === 0 )
		// не является ДС
		return;
		
		TgWikiQualityArticles.addToolboxMenuButton(
		'Номинировать на лишение статуса ДС',
		function() {
			var nominateFormDiv = $( '<div id="tgWikiQualityNominateToCancellation" title="Выдвижение статьи на лишение статуса добротной">'
			+ '<table border="0"><tr><td width="50" align="center">'
			+ '<img src="' + COMMONS_UPLOAD + '/e/e5/Crystal_Clear_action_bookmark_Silver_doubt.svg/40px-Crystal_Clear_action_bookmark_Silver_doubt.svg.png" height="40" width="40"></td><td>'
			+ '<p>Перед выдвижением на лишением статуса ознакомьтесь с <a href="https://ru.wikipedia.org/wiki/Википедия:ТДС">требованиями к добротным статьям</a></b>.<br/>'
			+ 'Пожалуйста, не номинируйте <b>более 3 статей в день</b>. Если номинируете статью впервые, укажите это при номинировании и дождитесь итога по первой номинации, прежде чем действовать дальше.</p>'
			+ '</td></tr></table>'
			+ '<form><fieldset>'
			+ '<textarea name="opiniontext" id="tgWikiQualityNominateToCancellationComment" style="height: 150px;" class="text ui-widget-content ui-corner-all"></textarea>'
			+ '</fieldset></form>'
			+ '<p class="validateTips" style="color:gray">Поле комментария обязательно к заполнению,<br/>ваша подпись будет добавлена автоматически.</p>'
			+ '</div>' );
			
			var nominateCommentField = nominateFormDiv.find( '#ruWikiQualityNominateToCancellationComment' );
			
			nominateFormDiv.dialog( {
				autoOpen: true,
				height: 'auto',
				width: 600,
				modal: true,
				buttons: {
					"Номинировать на лишение статуса": function() {
						var bValid = true;
						nominateCommentField.removeClass( "ui-state-error" );
						bValid = bValid && ruWikiQualityArticles.checkNotEmpty( nominateCommentField );
						
						if ( bValid ) {
							$( this ).dialog( "close" );
							tgWikiQualityArticles.nominateToCancellation( nominateCommentField.val() );
						}
					},
					"Отменить": function() {
						$( this ).dialog( "close" );
					}
				}
			} )
		} );
	};
	
	this.nominateToCancellation = function( comment ) {
		var replaceTemplateInArticle = this.nominateToCancellation_replaceTemplate( mw.config.get( 'wgTitle' ), '{{Добротная статья|', '{{К лишению статуса добротной|'
		+ TgWikiQualityArticles.getCurrentDateWikitext() + '|' );
		var addToDiscussionPage = this.nominateToCancellation_addToDiscussionPage( mw.config.get( 'wgTitle' ), comment );
		
		$.when( replaceTemplateInArticle, addToDiscussionPage ).done( function() {
			tgWikiQualityArticles.purge();
		} );
	};
	
	this.nominateToCancellation_addToDiscussionPage = function( articleTitle, comment ) {
		var operation = 'Номинирование на лишение статуса… Создание секции обсуждения… ';
		mw.notify( operation, notifyOptOk);
		return new mw.Api().postWithEditToken( {
			action: 'edit',
			title: PAGE_CANCEL_DISC,
			section: 0,
			appendtext: '\n\n== [[' + articleTitle + ']] ==\n' + comment + T_SIGN_A + '\n',
			summary: 'Номинирование «[[' + articleTitle + ']]» на лишение статуса добротной' + TgWikiQualityArticles.summarySuffix,
			} ).done( function() {
			mw.notify( operation + T_OK, notifyOptOk);
			} ).fail( function() {
			mw.notify( operation + 'Неизвестная ошибка!', notifyOptError);
		} );
	};
	
	this.nominateToCancellation_replaceTemplate = function( articleTitle, oldTemplateName, newTemplateName ) {
		var d = $.Deferred();
		var operation = 'Номинирование на лишение статуса… Замена шаблона добротной статьи… ';
		var operation_p1 = 'Получение текста статьи… ';
		var operation_p2 = 'Сохранение текста статьи… ';
		mw.notify( operation + operation_p1, notifyOptOk);
		new mw.Api().get( {
			action: 'query',
			prop: 'revisions',
			rvprop: 'content',
			titles: articleTitle,
			} ).done( function( result ) {
			var pageInfo = RuWikiQualityArticles.getFirstObjectValue( result.query.pages );
			var content = pageInfo.revisions[0]['*'];
			var newContent = content.replace( new RegExp( "(" + $.ui.autocomplete.escapeRegex( oldTemplateName ) + ")", "gi" ), newTemplateName );
			mw.notify( operation + operation_p2, notifyOptOk);
			
			new mw.Api().postWithEditToken( {
				action: 'edit',
				title: articleTitle,
				summary: 'Номинирование на лишение статуса добротной' + RuWikiQualityArticles.summarySuffix,
				text: newContent,
				} ).done( function() {
				mw.notify( operation + operation_p2 + T_OK, notifyOptOk);
				d.resolve();
				} ).fail( function() {
				mw.notify( operation + operation_p2 + ' Неизвестная ошибка!', notifyOptError);
				d.reject.apply( d, arguments );
			} );

			} ).fail( function() {
			mw.notify( operation + operation_p1 + T_ERR, notifyOptError);
			console.log( arguments );
			d.reject.apply( d, arguments );
		} );
		
		return d.promise();
	};
};

RuWikiQualityArticles.summarySuffix = ' с помощью гаджета QA (v. ' + mw.loader.moduleRegistry['ext.gadget.qualityArticles'].version + ')';

RuWikiQualityArticles.addToolboxMenuButton = function( label, click ) {
	$( "#p-tb div ul" ).append( $( '<li class="plainlinks"></li>' ).append( $( document.createElement( 'a' ) ).text( label ).css( 'cursor', 'pointer' ).click( click ) ) );
};

RuWikiQualityArticles.getFirstObjectValue = function( obj ) {
	"use strict";
	return obj[Object.keys( obj )[0]];
};

RuWikiQualityArticles.getCurrentDateWikitext = function() {
	"use strict";
	return '{{su' + 'bst:CURRENTDAY}} {{su' + 'bst:CURRENTMONTHNAMEGEN}} {{su' + 'bst:CURRENTYEAR}}';
};

mediaWiki.loader.using( [ 'jquery.ui', 'mediawiki.api' ], function() {

	var ruWikiQualityArticles = new RuWikiQualityArticles();
	tgWikiQualityArticles.addButtonsDiscussion();
	tgWikiQualityArticles.addButtonsArchive();
	tgWikiQualityArticles.addButtonsNominate();
	tgWikiQualityArticles.addButtonsChangeCategories();
	tgWikiQualityArticles.addNominateToCancellationButtons();
	tgWikiQualityArticles.addFinalDialog();
} );