MediaWiki:Gadget-sk.js

Z Wikipedii, wolnej encyklopedii

Uwaga: aby zobaczyć zmiany po opublikowaniu, może zajść potrzeba wyczyszczenia pamięci podręcznej przeglądarki.

  • Firefox / Safari: Przytrzymaj Shift podczas klikania Odśwież bieżącą stronę, lub naciśnij klawisze Ctrl+F5, lub Ctrl+R (⌘-R na komputerze Mac)
  • Google Chrome: Naciśnij Ctrl-Shift-R (⌘-Shift-R na komputerze Mac)
  • Internet Explorer / Edge: Przytrzymaj Ctrl, jednocześnie klikając Odśwież, lub naciśnij klawisze Ctrl+F5
  • Opera: Naciśnij klawisze Ctrl+F5.
/* eslint-disable array-element-newline */
/* eslint-disable array-bracket-newline */
/* eslint-disable no-empty */
/* eslint-disable no-redeclare */
/* eslint-disable no-useless-escape */
/* eslint-disable no-control-regex */
/* globals mw, jQuery, $, OO */
/* globals sel_t, toolbarGadget */
/* globals wp_sk_show_as_button, wp_sk_redir_enabled */

// <nowiki>
/* ------------------------------------------------------------------------ *\
	Moduł sprzątania kodu

	Opis:
		http://pl.wikipedia.org/wiki/WP:SK

	Copyright:  ©2007-2023 Maciej Jaros (pl:User:Nux, en:User:EcceNux)
	 Licencja:  GNU General Public License v2
		http://opensource.org/licenses/gpl-license.php

	User (release) versions:
		https://pl.wikipedia.org/w/index.php?title=MediaWiki:Gadget-sk.js&action=history
	Dev (alfa) versions:
		https://pl.wikipedia.org/w/index.php?title=Wikipedysta:Nux/wp_sk.js&action=history

	Szczególne podziękowania dla:
	* Wikipedysta:ABach - za zebranie i opracowanie długiej listy elementów do sprzątania
	* Wikipedysta:BartekChom - za pomysły i gotowe wyrażenia regularne
	* Wikipedysta:Beau - za inspiracje i poprawki
	* Wikipedysta:Beno - za pomysły i gotowe wyrażenia regularne
	* Wikipedysta:Gregul - za garść wyrażeń regularnych
	* Wikipedysta:Malarz pl - za porawki i gotowe wyrażenia regularne
	* Wikipedysta:PMG - za wytrwałe i szczegółowe testowanie
	* Wikipedysta:ToSter - za testy i pomysły na nowe rozwiązania
	* Wikipedysta:Wargo - za przygotowanie obsługi wiki-edytora 2017

	Edycje:
		main: https://pl.wikipedia.org/wiki/MediaWiki:Gadget-sk.js
		dev: https://pl.wikipedia.org/wiki/Wikipedysta:Nux/wp_sk.js
\* ------------------------------------------------------------------------ */

/* =====================================================
	Object Init
   ===================================================== */

if ( typeof( wp_sk_show_as_button ) === 'undefined' ) {
	window.wp_sk_show_as_button = true;
}
if ( typeof( wp_sk_redir_enabled ) === 'undefined' ) {
	window.wp_sk_redir_enabled = false;
}
// możliwość wyłączenia zmiany R na ref
if ( typeof( wp_sk_r_replace_enabled ) === 'undefined' ) {
	window.wp_sk_r_replace_enabled = false;
}

if (window.wp_sk)
{
	console.error('Błąd krytyczny - konflikt nazw!\n\nJeden ze skryptów używa już nazwy wp_sk jako zmienną globalną.');
}
var wp_sk = {};
window.wp_sk = wp_sk;
wp_sk.version = '2.18.1';
wp_sk.extension = '';

/* =====================================================
	Function: wp_sk.debug(htxt)

	@deprecated use brower's debugger logpoints instead
   ===================================================== */
wp_sk.debug = function () {};

/* =====================================================
	Function: wp_sk.button()

	Dodaje przycisk sprzątania
   ===================================================== */
wp_sk.button = function(callback) {
	var that = this;
	mw.loader.using( "ext.gadget.lib-toolbar", function() {
		// main button
		toolbarGadget.addButton( {
			title: 'Sprzątanie kodu (wer. ' + that.version + ')',
			alt: 'SK',
			id: 'wp_sk_img_btn',
			oldIcon: 'https://upload.wikimedia.org/wikipedia/commons/2/2e/Button_broom.png',
			newIcon: 'https://upload.wikimedia.org/wikipedia/commons/thumb/2/2c/Broom_icon.svg/22px-Broom_icon.svg.png',
			onclick: function() {
				that.cleanup( document.getElementById( 'wpTextbox1' ) );
			},
			oncreate: function(buttonImage) {
				if (typeof callback === 'function') {
					callback(buttonImage);
				}
			},
		} );
		// ref button
		if (!window.wp_sk_r_replace_enabled) {
			toolbarGadget.addButton( {
				title: 'SK: zamiana R na ref',
				alt: 'SK R na ref',
				id: 'wp_sk_img_ref_btn',
				oldIcon: 'https://upload.wikimedia.org/wikipedia/commons/thumb/b/b3/Broom_icon_ref.svg/22px-Broom_icon_ref.svg.png',
				newIcon: 'https://upload.wikimedia.org/wikipedia/commons/thumb/b/b3/Broom_icon_ref.svg/22px-Broom_icon_ref.svg.png',
				onclick: function() {
					that.cleanup( document.getElementById( 'wpTextbox1' ), function(str) {
						str = wp_sk.cleanerRefs(str, true);
						return str;
					} );
				},
			} );
		}
	} );
};

/* =====================================================
	Function: wp_sk.warning(input)

	Dodaje ostrzeżenie i likwiduje je
	po wciśnięciu odpowiedniego przycisku
   ===================================================== */
wp_sk.warning = function() {
	var isOOUI = $( '.mw-editform-ooui' ).length !== 0;
	if ( isOOUI ) {
		var summaryWidget = OO.ui.infuse( $( '#wpSummaryWidget' ) );
	} else {
		var $summary = jQuery( '#wpSummary' );
	}
	if ( this.nochanges ) {
		if ( !isOOUI ) {
			// kolorowanka, gdy bez zmian
			$summary.css( 'border', '2px solid #696' );
		}
	} else if ( mw.config.get( 'wgArticleId' ) > 0 ) {
		var summaryReview = 'po czyszczeniu kodu przejrzyj wykonane zmiany!';
		var summaryOk = '[[WP:SK]]';
		
		if(wp_sk.used_in_2017WTE)
		{
			mw.notify(summaryReview);
			return;
		}
		
		if ( !isOOUI ) {
			$summary.css( 'border', '' );
		}

		var text = isOOUI ? summaryWidget.getValue() : $summary.val();

		if ( text.indexOf( summaryReview ) > -1 || text.indexOf( summaryOk ) > -1 ) {
			// opis już jest, nie potrzeba następnego
			return;
		}

		if ( text !== '' ) {
			text += ', ';
		}
		text += summaryReview;
		if ( isOOUI ) {
			summaryWidget.setValue( text );
			summaryWidget.setValidation( function ( value ) {
				return value.indexOf( summaryReview ) === -1;
			} );

			var saveButton = OO.ui.infuse( $( '#wpSaveWidget' ) );
			var diffButton = OO.ui.infuse( $( '#wpDiffWidget' ) );
			saveButton.setFlags( { primary: false, constructive: false } );
			diffButton.setFlags( { primary: true, progressive: true } );

			diffButton.once( 'click', function () {
				summaryWidget.setValue( summaryWidget.getValue().replace( summaryReview, summaryOk + wp_sk.extension ) );
				saveButton.setFlags( { primary: true, constructive: true } );
				diffButton.setFlags( { primary: false, progressive: false } );
				// preventDefault() jest domyślne, więc musimy "kliknąć" przycisk jeszcze raz
				diffButton.$input.click();
			} );
		} else {
			$summary.val( text );
			$summary.addClass( 'summaryWarning' );

			var $diff = jQuery( '#wpDiff' );
			$diff.addClass( 'summaryWarning' );
			$diff.one( 'click', function() {
				$summary.val( $summary.val().replace( summaryReview, summaryOk + wp_sk.extension ) );
				$diff.removeClass( 'summaryWarning' );
				$summary.removeClass( 'summaryWarning' );
			} );
		}
	}
};

/**
 * Replaces value of the input
 * (or replaces selected text).
 * 
 * @param {Element} input 
 * @param {Sring} str 
 * @param {Boolean} hadSelection 
 */
wp_sk.replaceText = function (input, str, hadSelection)
{
	// don't use for non-textarea
	// (execCommand does work, but replacing whole text doesn't work)
	if (input.style.display === 'none' || sel_t.getEditor() == '2017') {
		sel_t.qsetSelStr(input, str, true);
		return;
	}

	input.focus();
	if (!hadSelection) {
		input.select();
	}
	
	// attempting to paste to preserve undo functionality
	var pasted = true;
	try {
		if (!document.execCommand("insertText", false, str)) {
			pasted = false;
		}
	} catch (e) {
		console.warn('error caught:', e);
		pasted = false;
	}
	// fallback
	if (!pasted) {
		console.warn('paste unsuccessful, execCommand not supported');
		sel_t.qsetSelStr(input, str, true);
	}
};

/**
 * Główna funkcja inicjująca i wywołująca funkcję czyszczącą (cleaner).
 * 
 * @param {Element} input Text field.
 * @param {Function?} customCleaner [optional] Custom cleaner (gets str without nowiki, returns str).
 */
wp_sk.cleanup = function (input, customCleaner)
{
	// default input
	if (!input)
	{
		input = document.getElementById('wpTextbox1');
	}
	//
	// Pobierz zaznaczony fragment (całość jeśli nic nie zaznaczone)
	//
	var str = sel_t.getSelStr(input, true);
	var hadSelection = !sel_t.noSelection;
	// OMG - IE & Opera fix
	str = str.replace(/\r\n/g, '\n');

	//
	// Wywołanie czyściciela
	//
	str = str.replace(/\n+$/,''); // bez końcowych enterów
	var str_pre = str;

	if (typeof customCleaner === 'function')
	{
		str = wp_sk.nowiki.hide(str);
		str = customCleaner(str);
		str = wp_sk.nowiki.show(str);
	}
	else
	{
		str = wp_sk.cleaner(str);
	}


	wp_sk.nochanges = (str==str_pre);

	//
	// zapisanie zmian
	//
	if (!wp_sk.nochanges)
	{
		//sel_t.qsetSelStr(input, str, true);
		wp_sk.replaceText(input, str, hadSelection);
	}

	input.focus();

	wp_sk.warning();
};

/* =====================================================
	Function: wp_sk.cleaner(str)

	Funkcja czyszcząca podany na wejściu ciąg znaków str.
	Zwraca przetworzony ciąg znaków.
   ===================================================== */
wp_sk.cleaner = function (str)
{
	//
	// ukrywanie obszarów w tagach: nowiki, pre, source i math
	str = wp_sk.nowiki.hide(str);

	//
	// sprzątanie podstawowe
	str = wp_sk.cleanerLinks(str);		// wikilinki
	str = wp_sk.cleanerTpls(str);		// szablony
	str = wp_sk.nowiki.filehide(str);	// ukrywamy nazwy plików

	str = wp_sk.cleanerWikiVaria(str);	// pozostałe wikiskładniowe
	str = wp_sk.cleanerTXT(str);		// poza składniowe

	if (wp_sk.projectSpecificCleanup) {
		str = wp_sk.projectSpecificCleanup(str);
	}

	//
	// końcowe porządkowanie międzywiki itp
	str = wp_sk.cleanerMagicLinks(str);

	//
	// przywrócenie ukrytych tagów
	str = wp_sk.nowiki.show(str);

	return str;
};

/* =====================================================
	Function: wp_sk.cleanerLinks(str)

	Sprzątanie wikilinków
   ===================================================== */
wp_sk.cleanerLinks = function (str)
{
	// [[http://]]→[http://...]
	str = str.replace(/\[\[([a-z]+:\/\/[^\|\]]+)\]\]/g, '[$1]');
	// [[Kto%C5%9B_jaki%C5%9B#co.C5.9B|...]]→[[Ktoś jakiś#coś|...]]
	str = str.replace(/\[\[([^|#\]]*)([^|\]]*)(\||\]\])/g, wp_sk.rLinkdecode);
	// wyjątek dla [[Spark-Renault SRT_01E]]
	str = str.replace(/\[\[Spark-Renault SRT 01E\]\]/g, '[[Spark-Renault SRT_01E]]');

	// poprawa nazw przestrzeni i drobne okoliczne
	str = str.replace(/\[\[(:?) *(image|grafika|file) *: *([^ ])/gi, function (a,dw,co,l1) {return '[['+dw+'Plik:'+l1.toUpperCase();} );
	str = str.replace(/\[\[(:?) *(category|kategoria) *: *([^ ])/gi, function (a,dw,co,l1) {return '[['+dw+'Kategoria:'+l1.toUpperCase();} );
	str = str.replace(/\[\[ *(:?) *(template|szablon) *: *([^ ])/gi, function (a,dw,co,l1) {return '[[Szablon:'+l1.toUpperCase();} );
	str = str.replace(/\[\[ *(:?) *(special|specjalna) *: *([^ ])/gi, function (a,dw,co,l1) {return '[[Specjalna:'+l1.toUpperCase();} );

	str = str.replace(/\[\[ *:? *[Dd]yskusja( [a-z]*) *: */g, '[[Dyskusja$1:');

	// usunięcie klucza sortowania kat. jeśli w całości jest prefiksem nazwy artykułu lub nazwą artykułu
	if (str.search(/\{\{[ ]*(DEFAULTSORT|DOMYŚLNIESORTUJ|SORTUJ)[ ]*:/)==-1)
	{
		str = str.replace(/\[\[(Kategoria:[^\|\[\]\n]+)\|([^\|\[\]\n]+)\]\]/gi,
			function (a,kat,klucz)
			{
				if (mw.config.get('wgTitle').indexOf(klucz)===0)
					return '[['+kat+']]'
					;
				return a;
			}
		);
	}

	// zbędne w obrazkach
	str = str.replace(/(\[\[Plik:[^\n\|\]]+?\|(?:thumb|mały))\|(?:prawo|right)/g, '$1');		// niepotrzebne
	str = str.replace(/(\[\[Plik:[^\n\|\]]+?)\|(?:prawo|right)(\|(?:thumb|mały))/g, '$1$2');	// niepotrzebne
	str = str.replace(/(\[\[Plik:[^\|\]]+?\|)frame(\|[0-9x]+px)/, '$1thumb$2');			// prawie na pewno błąd
	str = str.replace(/(\[\[Plik:[^\|\]]+\|[^\|\]]+)\.\]\]/, '$1]]');				// kropka
	// -mid spacje
	/* // zawiesza FF w niektórych warunkach, psuje niektóre opisy
	str = str.replace(/(\[\[Plik:[^\|\[\]]+)(\|[^\[\]\{\}]+ [^\[\]\{\}]*)(\|([^\|\[\]]+|[^\|\[\]]+\[\[[^\[\]]+\]\]){7,}\]\])/g, function(a,g1,gmid,gn)
	{
		return g1+ gmid.replace(/\s/g,'') +gn;
	});
	*/

	// usuwanie [[:pl:
	str = str.replace(/\[\[ *:? *pl *: */g, '[[');

	// stare przestrzenie
	str = str.replace(/\[\[Dyskusja Wikipedysty/g, '[[Dyskusja wikipedysty');

	// [[link|| -> [[link|
	str = str.replace(/\[\[ *([^\]\|:]+) *\| *\| */g, '[[$1|');

	//
	// (ro)zwijanie wikilinków
	// [[Link|link]] > [[link]] i [[Link|linka]] > [[link]]a
	//str = str.replace(/\[\[([^|\]])([^|\]]*)\|([^\]])\2([a-zA-ZżółćęśąźńŻÓŁĆĘŚĄŹŃ]*)\]\]/g, function (a, w1_1, w_rest, w2_1, poza)
	str = str.replace(/\[\[([^|\]])([^|\]]*)\|([^\]])\2([a-zżółćęśąźń]*)\]\]/g, function (a, w1_1, w_rest, w2_1, poza)
	{
		return (w1_1.toUpperCase()==w2_1.toUpperCase()) ? '[['+w2_1+w_rest+']]'+poza : a;
	});
	// [[Link|link]]er > [[Link|linker]]
	//str = str.replace(/\[\[([^|\]]+)\|([^|\]]+)\]\]([a-zA-ZżółćęśąźńŻÓŁĆĘŚĄŹŃ]+)/g, '[[$1|$2$3]]');
	str = str.replace(/\[\[([^|\]]+)\|([^|\[\]]+)\]\]([a-zżółćęśąźń]+)/g, '[[$1|$2$3]]');

	// usuwanie spacji w wikilinkach
	str = str.replace(/\[\[ *([^\]\|:]*[^\]\| ]) *\|/g, '[[$1|');
	str = str.replace(/([^ \t\n])\[\[ +/g, '$1 [[');
	str = str.replace(/\[\[ +/g, '[[');
	str = str.replace(/([^ \t\n])\[\[([^\]\|:]+)\| +/g, '$1 [[$2|');
	str = str.replace(/\[\[([^\]\|:]+)\| +/g, '[[$1|');
	str = str.replace(/([^ \|]) +\]\]([^ \t\na-zA-ZżółćęśąźńŻÓŁĆĘŚĄŹŃ])/g, '$1]] $2');
	str = str.replace(/([^ \|]) +\]\]([^a-zA-ZżółćęśąźńŻÓŁĆĘŚĄŹŃ])/g, '$1]]$2');

	// sklejanie skrótów linkowych
	str = str.replace(/m\.? ?\[\[n\.? ?p\.? ?m\.?\]\]/g, 'm [[n.p.m.]]');

	// korekty dat - niepotrzebny przecinek
	str = str.replace(/(\[\[[0-9]+ (stycznia|lutego|marca|kwietnia|maja|czerwca|lipca|sierpnia|września|października|listopada|grudnia)\]\]), (\[\[[0-9]{4}\]\])/g, '$1 $3');

	// linkowanie do wieków
	str = str.replace(/\[\[([XVI]{1,5}) [wW]\.?\]\]/g, '[[$1 wiek|$1 w.]]');
	str = str.replace(/\[\[([XVI]{1,5}) [wW]\.?\|/g, '[[$1 wiek|');
	str = str.replace(/\[\[(III|II|IV|VIII|VII|VI|IX|XIII|XII|XI|XIV|XV|XVIII|XVII|XVI|XIX|XXI|XX)\]\]/g, '[[$1 wiek|$1]]');
	str = str.replace(/\[\[(III|II|IV|VIII|VII|VI|IX|XIII|XII|XI|XIV|XV|XVIII|XVII|XVI|XIX|XXI|XX)\|/g, '[[$1 wiek|');

	// rozwijanie typowych linków
	str = str.replace(/\[\[ang\.\]\]/g, '[[język angielski|ang.]]');
	str = str.replace(/\[\[cz\.\]\]/g, '[[język czeski|cz.]]');
	str = str.replace(/\[\[fr\.\]\]/g, '[[język francuski|fr.]]');
	str = str.replace(/\[\[łac\.\]\]/g, '[[łacina|łac.]]');
	str = str.replace(/\[\[niem\.\]\]/g, '[[język niemiecki|niem.]]');
	str = str.replace(/\[\[pol\.\]\]/g, '[[język polski|pol.]]');
	str = str.replace(/\[\[pl\.\]\]/g, '[[język polski|pol.]]');
	str = str.replace(/\[\[ros\.\]\]/g, '[[język rosyjski|ros.]]');
	str = str.replace(/\[\[(((G|g)iga|(M|m)ega|(K|k)ilo)herc|[GMk]Hz)\|/g, '[[herc|');

	// skracanie szablonów Dziennik Ustaw i Monitor Polski
	str = str.replace(/\{{2}\s*(Dziennik|Monitor)\s+(Ustaw|Polski)\s*\|\s*rok\s*=\s*(\d+)\s*\|\s*numer\s*=\s*(\d+)\s*\|\s*pozycja\s*=\s*(\d+)\s*\}{2}/gi, '{{$1 $2|$3|$4|$5}}');
	// ale numer=0 powinien wylecieć w wersji skróconej; spacji już nie ma, więc nie szukam
	str = str.replace(/\{{2}(Dziennik Ustaw|Monitor Polski)\|(20[12]\d)\|0+\|(\d+)\}{2}/gi, '{{$1|$2|$3}}');

	return str;
};
/* =====================================================
	Function: wp_sk.cleanerTpls(str)

	Sprzątanie szablonów
   ===================================================== */
wp_sk.cleanerTpls = function (str)
{
	// niepotrzebna przestrzeń
	str = str.replace(/\{\{ *([Tt]emplate|[Ss]zablon|msg) *: */g, '{{');
	
	// polski DEFAULTSORT
	str = str.replace(/\{\{ *(DEFAULTSORT|DOMYŚLNIESORTUJ) *: */g, '{{SORTUJ:');

	// zbędne spacje w szablonach jedno wierszowych
	str = str.replace(/\{\{[ \t]+([^\n\{\} ]+)[ \t]*\}\}/g, '{{$1}}').replace(/\{\{([^\n\{\}]+?)[ \t]+\}\}/g, '{{$1}}');

	// mala litera wewnatrz {{lang|...}}
	str = str.replace(/((\{\{lang\|[^}_]+)((_[^}]+)\}\}))/gi, function (a, fil, first, rest) {
		return first.toLowerCase()+rest;
	});
	// poprawki lang i nowy multilang
	str = str.replace(/\{\{lang\|cz\}\}/g, '{{lang|cs}}');
	str = str.replace(/\{\{lang\|dk\}\}/g, '{{lang|da}}');
	str = str.replace(/\{\{lang\|nb\}\}/g, '{{lang|no}}');
	str = str.replace(/(\{\{lang\|[a-z-]+\}\}[\t ]*){2,10}/g, function(a) {
		return '{{lang'+a.replace(/\{\{lang\|([a-z-]+)\}\}\s*/g, '|$1')+'}}';
	});

	// wciąganie {{lang}} do szablonów cytowania
	str = str.replace(/{{(cytuj [^{}]+?)}} {{lang\|([a-z-]+)}}/gi, '{{$1 | język = $2}}');
	// poprawa nieprawidłowego języka w szablonie Cytuj
	str = str.replace(/{{(cytuj[^{}]+?)}}/gi, function (szablon) {
		// normalizacja
		//szablon = szablon.replace(/\s*\|\s*język\s*=\s*/gi, ' | język = ');
		szablon = szablon.replace(/(\|\s*język\s*=\s*)([a-z\-]+)/gi, function(a, pre, lang) {
			// przytnij
			if (lang.indexOf('pl-') == 0) {
				return pre + 'pl';
			}
			// przytnij
			if (lang.indexOf('en-') == 0) {
				return pre + 'en';
			}
			return a;
		});
		return szablon;
	});

	// ujednolicanie nazw szablonów (tabela poniżej)
	if (Object.keys(wp_sk.sz_redirs_tab).length) {
		str = str.replace(/\{\{([sS]\||)([^{}\n\|]+)(\||\}\})/g, function(a, pre, nazwa, post)
		{
			nazwa = nazwa.toLowerCase();
			if (wp_sk.sz_redirs_tab[nazwa])
			{
				a = '{{'+pre + wp_sk.sz_redirs_tab[nazwa] + post;
			}
			return a;
		});
	}

	str = str.replace(/\{\{commons\|Category:/gi, '{{commonscat|');

	// poprawka, bo FF wywala się na czołgach np. http://pl.wikipedia.org/w/index.php?title=T-72&diff=14511491&oldid=14437344
	str = str.replace(/<!--[\s\S]+?-->/g, function(a) {
		a
			.replace(/\{/g,'###comment_klamra_l###')
			.replace(/\}/g,'###comment_klamra_r###')
		;
		return a;
	});
	// uczłowieczanie szablonów
	str = str.replace(/\{\{([^|}]+?[ _]infobo[^|}]+)((?:[^{}]|[^{}][{}][^{}]|\{\{(?:[^{}]|[^{}][{}][^{}]|\{\{[^{}]+\}\})+\}\})+)\}\}/g, wp_sk.rFriendlyIbox);
	// rev poprawki
	str = str.replace(/<!--[\s\S]+?-->/g, function(a) {
		a
			.replace(/###comment_klamra_l###/g, '{')
			.replace(/###comment_klamra_r###/g, '}')
		;
		return a;
	});
	
	// {{link-interwiki}} bez podanego Q
	str = str.replace(/\{\{link-interwiki\| *([^\{\}\|]+?) *\| *tekst *= *([^\{\}\|]+?) *\| *Q *= *\}\}/g, '[[$1|$2]]');
	str = str.replace(/\{\{link-interwiki\| *([^\{\}\|]+?) *\| *Q *= *\}\}/g, '[[$1]]');

	// {{link-interwiki|Kto%C5%9B jaki%C5%9B|... → {{link-interwiki|Ktoś jakiś...
	str = str.replace(/\{\{[Ll]ink-interwiki\|([^\{\}\|]*)\|/g, wp_sk.rLinkinterwikidecode);

	str = wp_sk.cleanerRefs(str);

	return str;
};

/**
 * Czyszczenie szablonów przypisów (R na ref).
 * 
 * Problem z R jest w ukryciu tagów w szablonie.
 * VE nie wie co dzieje się w szablonie, więc nie umie rozpoznać przypisów.
 * 
 * Zmiana R->ref pozwalana na rozpoznanie przypisów przez VE.
 * Zmiana {{Przypisy|}} na standardowy tag odblokowuje edycję w VE.
 */
wp_sk.cleanerRefs = function (str, forceReplaceR)
{
	// tylko proste parametry (powinno załatawić wszystkie realne przypadki)
	// OK: {{r | a | b | c}}
	// pomijam z "=": {{r | 1=a | 2=b=xyz }}
	// pomijam z cudzysłowem: {{r | "c"}}
	// pomijam z ~tagiem: {{r | a<b | <p>abc }}
	if (forceReplaceR || window.wp_sk_r_replace_enabled) {
		str = str.replace(/\{\{[rR]\s*\|\s*([^}="<>]+)\}\}/g, function(a, params) {
			var names = params.split(/\s*\|\s*/);
			//console.log(params, names);
			var code = "";
			for (var i = 0; i < names.length; i++) {
				code += '<ref name="' + names[i] + '"/>';
			}
			return code;
		});
	}

	// {{Przypisy|...ref -> references
	var result = this.cleanerReflist(str);
	if (result) {
		//console.log('[wp_sk]', 'reflist replaced');
		str = result;
	} else {
		//console.log('[wp_sk]', 'reflist with refs was not found');
	}

	return str;
}

/**
 * Próbuje zmienić {{Przypisy|}} na standardowy tag.
 * 
 * Pomija szablon z dodatkowymi parametrami.
 * 
 * @param {String} str Pełny kod artykułu.
 * @returns Poprawiony kod jeśli znaleziono.
 * 	Zwraca false jeśli nie znaleziono.
 */
wp_sk.cleanerReflist = function (str)
{
	var startIndex = str.search(/\{\{Przypisy\s*\|/i);
	if (startIndex < 0) {
		//console.log('[wp_sk]', 'no ref template with params found');
		return false;
	}

	var ending = str.substring(startIndex);
	var indexes = this.findTemplates(ending);
	if (!indexes.length) {
		console.log('[wp_sk]', 'ref template not found');
		return false;
	}

	var part = indexes[0];
	var tpl = ending.substring(part.start, part.end);

	// spr. resztek po usunięciu przypisów
	var noRefs = tpl
		.replace(/<ref[^>]*>[\s\S]+?<\/ref>/ig, '')
		.replace(/^\{\{\s*\w+/, '') // tpl start
		.replace(/\}\}$/, '') // tpl end
	;
	// console.log(noRefs);

	// nie może zawierać nazwanych parametrów
	// (powinno pominąć `|grupa=uwagi`)
	if (noRefs.search(/\|\s*\w{2,}\s*=/) > 0) {
		console.log('[wp_sk]', 'ref template has extra params');
		return false;
	}

	// oczyść zawartość
	// (zostawia samą treść szablonu, bez kodu szablonu)
	var noTpl = tpl
		.replace(/^\{\{\s*\w+/, '') // tpl start
		.replace(/\}\}$/, '') // tpl end
		.trim()
		.replace(/^\|\s*\w+\s*=/, '') // first param name
		.replace(/^\|/, '') // nameless param
		.trim()
	;
	//console.log(noTpl);
	if (!noTpl.length) {
		console.log('[wp_sk]', 'ref template contents seem empty');
		return false;
	} else if (noTpl.search(/<\/ref>/) < 0 && noTpl.search(/\{\{/) < 0) {
		console.log('[wp_sk]', 'ref template has no refs nor templates');
		return false;
	}

	var fixed = "<references>\n" + noTpl + "\n</references>";
	ending = fixed + ending.substring(part.end);
	var result = str.substring(0, startIndex + part.start) + ending;

	return result;
}

/* =====================================================
	Function: wp_sk.cleanerWikiVaria(str)

	Sprzątanie pozostałych elementów wikiskładni
   ===================================================== */
wp_sk.cleanerWikiVaria = function (str)
{
	// unifikacja nagłówkowa
	str = str.replace(/[ \n\t]*\n'''? *(Zobacz|Patrz) (też|także|również):* *'''?[ \t]*\n[ \t\n]*/gi, '\n\n== Zobacz też ==\n');
	str = str.replace(/[ \n\t]*\n'''? *(Zobacz|Patrz) (też|także|również):* *'''?[ \t]*(.+)/gi, function(a, w1, w2, linki)
	{
		if (linki.indexOf('[')!=-1)
		{
			// add first list el.
			linki = '* ' + linki;
			// next?
			if (linki.indexOf(',')!=-1)
			{
				// escape in-link and in-tpl comma
				var escape_fun = function(a){ return a.replace(/,/g,'<<<#>>>') };
				linki = linki.replace(/\[\[[^\[\]]+\]\]/g, escape_fun);
				linki = linki.replace(/\{\{[^\{\}]+\}\}/g, escape_fun);
				// split
				linki = linki.replace(/,[ \t]*/g, '\n* ');
				// unescape
				linki = linki.replace(/<<<#>>>/g,',');
			}
		}
		return '\n\n== Zobacz też ==\n'+linki;
	});
	str = str.replace(/[ \n\t]*\n(=+) *(Zobacz|Patrz) (te[zźż]|tak[zźż]e|r[oó]wnie[zźż]):* *=+[ \n\t]*/gi, '\n\n$1 Zobacz też $1\n');
	str = str.replace(/[ \n\t]*\n'''? *((Zewn[eę]trzn[ey] )?(Linki?|Łącza|Stron[ay]|Zobacz w (internecie|sieci))( zewn[eę]trzn[aey])?):* *'''?[ \n\t]*/gi, '\n\n== Linki zewnętrzne ==\n');
	str = str.replace(/[ \n\t]*\n(=+) *((Zewn[eę]trzn[ey] )?(Linki?|Łącza|Stron[ay]|Zobacz w (internecie|sieci))( zewn[eę]trzn[aey])?):* *=+[ \n\t]*/gi, '\n\n$1 Linki zewnętrzne $1\n');
	str = str.replace(/[ \n\t]*\n(=+) *([ŹŻZ]r[óo]d[łl]a):* *=+[ \n\t]*/gi, '\n\n$1 Źródła $1\n');

	// tabele
	// zbędny tr przed caption; T292116: Problem merging table cells in VisualEditor in some articles
	str = str.replace(/\{\|(.*)[\n]+\|-\s*[\n]+\|\+/g, '{|$1\n|+');

	// nagłówki
	str = str.replace(/(^|\n)(=+) *([^=\n].*?)[ :]*\2(?=\s)/g, '$1$2 $3 $2'); // =a= > = a =, =a:= > = a =
	str = str.replace(/(^|\n)(=+[^=\n]+=+)[\n]{2,}/g, '$1$2\n');	// jeden \n

	// references
	if (str.indexOf('<references') >= 0) {
		str = str.replace(/<references +responsive(=1|="1"|='1')?>/g, '<references>');
		str = str.replace(/<references>\s*<\/references>/g, '<references />');
		str = str.replace(/<references +responsive *\/>/g, '<references />');
		str = str.replace(/<references +group *= *("uwaga"|uwaga) +responsive(=1|="1"|='1')?>/g, '<references group="uwaga">');
		str = str.replace(/<references +responsive(=1|="1"|='1')? +group *= *("uwaga"|uwaga)>/g, '<references group="uwaga">');
		str = str.replace(/<references +group *= *("uwaga"|uwaga)>\s*<\/references>/g, '<references group="uwaga" />');
		str = str.replace(/<references +group *= *("uwaga"|uwaga) +responsive *\/>/g, '<references group="uwaga" />');
		str = str.replace(/<references +responsive +group *= *("uwaga"|uwaga) *\/>/g, '<references group="uwaga" />');
	}
	// przypisy - szablon
	str = str.replace(/\n== Przypisy ==[ \t\n]+<references *(responsive( *= *\"\d?\")?)? *\/>[ \t\n]*/g, '\n== Przypisy ==\n{{Przypisy}}\n\n');
	
	// szablon przypisy bez parametrów + dokładnie jedna pusta linia po
	str = str.replace(/\{\{\s*[Pp]rzypisy[\s\n\|]*\}\}[\s\n]*/g, '{{Przypisy}}\n\n')

	// przypisy - przyprzątnięcia
	/*
	// rozwijamy {{r}}, bo kod niżej pracuje na <ref/>-ach
	str = str.replace(/{{r((?:\|[^|}]+)*)}}/g, function(a, inside) {
		return inside.replace(/\|([^|}]+)/g, function(b, name) {
			// escape'ujemy " w nazwach
			return '<ref name="' + name.replace(/"/g, "\\\"") + '" />';
		});
	});
	*/
	
	str = str.replace(/<(ref[^<>\/]*?)[ ]*> *<\/ref>/g, "<$1 />");	// puste na pojedynczy
	str = str.replace(/[ \t]+(<ref[ >]|\{\{[Ff]akt(?:\|data=[0-9\-]+)?\}\})/g, '$1');		// bez białych przed
	str = str.replace(/=(<ref[ >]|\{\{[Ff]akt(?:\|data=[0-9\-]+)?\}\})/g, '= $1');		    // z wyjątkiem wartości parametrów
	str = str.replace(/<(ref name=("[^"]+"|'[^']+'))\/>/g, "<$1 />");	// spacja w ref
	// nowe linie przed ref w references
	str = str.replace(/(<references>|\{\{Uwagi[\s\S]*?\|\s*uwagi\s*=|\{\{Uwagi[\s\S]*?\|\s*1\s*=|\{\{Uwagi[\s\S]*?\|)\s*((?:[\*\#]?\s*<ref name[^<>]+>[\s\S]*?<\/ref>\s*)+)/gi,
		function(a, prerefs, refs)
		{
			refs = refs.replace(/^[\*\#]? */, '');
			refs = refs.replace(/<\/ref>[^<>]*<ref/gi, '</ref>\n<ref');
			refs = refs.replace(/<\/ref>\s*$/, '</ref>\n');
			return prerefs + '\n' + refs;
		}
	);
	// porządkujemy nazwane refy
	str = str.replace(/<ref +name *= *(\"[^\"<>]+\") *\/>/g, '<ref name=$1 />');
	str = str.replace(/<ref +name *= *([^\"<> ]+?) *\/>/g, '<ref name="$1" />');
	str = str.replace(/<ref +name *= *(\"[^\"<>]+\") *>/g, '<ref name=$1>');
	str = str.replace(/<ref +name *= *([^\"<> ]+?) *>/g, '<ref name="$1">');
	// przypisy i interpunkcja
	str = str.replace(/[,] *((?:(?:<ref[\s\S]+?(?:<\/ref|\/)>)|(\{{2}(?:odn|r|u)\|[^}]+\}{2}))+[,;:.!?])/gi, "$1");	// del przecinek przed, gdy jakiś INT po
	str = str.replace(/([,])((?:(?:<ref[\s\S]+?(?:<\/ref|\/)>)|(\{{2}(?:odn|r|u)\|[^}]+\}{2}))+)/gi, "$2$1");	// przecinek
	str = str.replace(/((?:\s|^)[^& ]*);((?:(?:<ref[\s\S]+?(?:<\/ref|\/)>)|(\{{2}(?:odn|r|u)\|[^}]+\}{2}))+)/gi, "$1$2;");	// średnik
	str = str.replace(/([a-zA-Z\u00C0-\u017F]{5}|[()\[\]{}"”'>])[.]((?:(?:<ref[\s\S]+?(?:<\/ref|\/)>)|(\{{2}(?:odn|r|u)\|[^}]+\}{2}))+)/gi, "$1$2.");	// długi wyraz lub znak specjalny
	str = str.replace(/(\]\][a-zżółćęśąźń]+)[.]((?:(?:<ref[\s\S]+?(?:<\/ref|\/)>)|(\{{2}(?:odn|r|u)\|[^}]+\}{2}))+)/gi, "$1$2.");	// link z dodatkiem
	str = str.replace(/([a-zA-Z00C0-017F][aeiouyąę])[.]([']*(?:(?:<ref[\s\S]+?(?:<\/ref|\/)>)|(\{{2}(?:odn|r|u)\|[^}]+\}{2}))+)/gi, "$1$2.");	// krótki z samogłoską
	str = str.replace(/(<\/ref>|\{{2}(?:odn|r|u)\|[^}]+\}{2})\.(?=[!?,.:;…]\s)/gi, '$1');	// kropka przed innym interpunkcyjnym
	
	/*
	// zwijamy z powrotem <ref/> do {{r}}
	str = str.replace(/< *ref *name *= *(?:"([^">\n]+)"|'([^'>\n]+)'|([^\s'"\/]+)) *\/ *>/g, function(a, name1, name2, name3) {
		return "{{r|" + (name1||name2||name3) + "}}";
	});
	// łączymy kolejne wywołania postaci {{r}}{{r}}
	// nie działa dla wywołań z parametrami grupaN=
	str = str.replace(/(\{\{r(\|([^|}]+))+\}\}\s*)+/g, function(refs) {
		return refs.replace(/\}\}\s*\{\{r\|/g, '|');
	});
	*/

	// szablon kontrola autorytatywna + dokładnie jedna pusta linia przed + dokładnie jesdna pusta linia po
	str = str.replace(/[\s\n]*\{\{\s*[Kk]ontrola[ _]+autorytatywna[\s\n\|]*\}\}[\s\n]*/g, '\n\n{{Kontrola autorytatywna}}\n\n')

	// fakty i interpunkcja
	str = str.replace(/([,])(\{\{[Ff]akt(?:\|data=[0-9\-]+)?\}\})/g, "$2$1");	// przecinek
	str = str.replace(/((?:\s|^)[^& ]*);(\{\{[Ff]akt(?:\|data=[0-9\-]+)?\}\})/g, "$1$2;");	// średnik
	str = str.replace(/([a-zA-ZąćęłńóśźżĄĆĘŁŃÓŚŹŻ]{5}|[()\[\]{}"”'>])[.](\{\{[Ff]akt(?:\|data=[0-9\-]+)?\}\})/g, "$1$2.");	// długi wyraz lub znak specjalny
	str = str.replace(/([a-zA-ZąćęłńóśźżĄĆĘŁŃÓŚŹŻ][aeiouyąę])[.](\{\{[Ff]akt(?:\|data=[0-9\-]+)?\}\})/g, "$1$2.");	// krótki z samogłoską

	// listy ze spacjami
	str = str.replace(/(\n[#*:;]+)(?![ \t\n#*:;{]|if[a-z]* ?:|switch ?:|time ?:|rel2abs ?:|titleparts ?:)/g, '$1 ');

	// rozwijanie linków w listach
	str = str.replace(/\n\*[ \t]*\[(http:\/\/[^ \n\]]+)\]/g, "\n* [$1 $1]");

	// galerie fix cooked by ToSter
	str = str.replace(/<gallery([^\n>]*)>([\s\S]+?)<\/gallery>/gi, function(a, opcje, zaw) {
		zaw = zaw.replace(/\n(Image|Grafika|File):/gi, '\nPlik:');
		return "<gallery" + opcje + ">" + zaw + "</gallery>";
	});


	return str;
};
/* =====================================================
	Function: wp_sk.cleanerTXT(str)

	Sprzątanie nie związane bezpośrednio z wikiskładnią
   ===================================================== */
wp_sk.cleanerTXT = function (str)
{
	// usuwanie unikodowych znaków sterujących
	str = str.replace(/[\u200B\uFEFF\u200E]/g, '');

	// korekty dat
	// występuje w interwiki (hr)
	//str = str.replace(/([0-9])\. *(stycznia|lutego|marca|kwietnia|maja|czerwca|lipca|sierpnia|września|października|listopada|grudnia)/g, '$1 $2')	// niepotrzebna kropka
	// problem z nazwami plików
	//str = str.replace(/([^0-9])0([0-9]) *(stycznia|lutego|marca|kwietnia|maja|czerwca|lipca|sierpnia|września|października|listopada|grudnia)/g, '$1$2 $3');	// niepotrzebne 0

	// poprawkowate różne (kolejność jest istotna!)
	str = str.replace(/&deg;/g, '°');
	str = str.replace(/&sum;/g, '∑');
	str = str.replace(/&larr;/g, '←');
	str = str.replace(/&rarr;/g, '→');
	str = str.replace(/&uarr;/g, '↑');
	str = str.replace(/&darr;/g, '↓');
	str = str.replace(/&dagger;/g, '†');
	str = str.replace(/<sup>o<\/sup>/g, '°');

	str = str.replace(/(\[[^[\]|]*)([%‰°])/g, '$1<<<$2>>>'); // nie zmieniamy spacji w linkach, nazwach plików ....
	//---- "nowa wersja" komentarz nieaktalny
	// liczba i symbole zawsze bez spacji
	// "między wartością liczbową a literowym oznaczeniem miary, czyli skrótem lub skrótowcem, stawiamy spację, natomiast między wartością liczbową a oznaczeniem miary za pomocą symbolu albo połączenia skrótu/skrótowca i symbolu spacji nie stawiamy"
	// http://www.rjp.pan.pl/index.php?option=com_content&view=article&id=1045:spacje-w-oznaczeniach-miar&catid=44&Itemid=145
	// str = str.replace(/([0-9])(?: |&nbsp;|&#160;)(%|‰|°)/g, '$1$2');
	//---- "nowa wersja"
	// do czasu zakończenia dyskusji 
	// [[Wikipedia:Kawiarenka/Zasady#Pisownia jednostek miary temperatury i wprowadzenie zmian bez zgody wyrażonej w dyskusji]]
	// prosimy o nieprzywracanie "nowej" wersji
	//---- "stara wersja", powyżej jednoinijkowa "nowa wersja" zakomentowana na czas dyskusji
	str = str.replace(/([0-9]) (%[^A-F0-9]|‰)/g, '$1$2');
	str = str.replace(/([0-9]) (°)(?![A-Z])/gi, '$1$2'); // tylko sam stopien; bez np.: °Blg, °C, °F, °Ld itp.
	str = str.replace(/([0-9])(°[CF])/g, '$1 $2'); // spacja
	//---- koniec
	str = str.replace(/(\[[^[\]|]*)<<<([%‰°])>>>/g, '$1$2'); // przywracamy poprzednie linki ....

	str = str.replace(/<\/?br ?\/?>/gi, '<br />');

	// dopisanie kropki itp
	str = str.replace(/ (tzw|tzn) /g, ' $1. ');
	str = str.replace(/([ \n])ok\.([0-9])/g, '$1ok. $2');
	//str = str.replace(/([ \n])ok ([^ ])/g, '$1ok. $2');
	str = str.replace(/ d\/s /g, ' ds. ');
	str = str.replace(/ wg\. /g, ' wg ');

	// sklejanie skrótów
	str = str.replace(/m\.? ?(npm\.?|n[. ]{1,3}p[. ]{1,3}m\.?)/g, 'm n.p.m.');
	str = str.replace(/ m\. in\./g, ' m.in.');
	str = str.replace(/ o\. o\./g, ' o.o.');

	// Sprawy wagi Państwowej ;-)
	str = str.replace(/(gmina wiejska w powiecie [a-zA-ZąćęłńóśźżĄĆĘŁŃÓŚŹŻ\-]+ województwa [a-zA-ZąćęłńóśźżĄĆĘŁŃÓŚŹŻ\-]+) II Rzeczpospolitej/g, '$1 II Rzeczypospolitej');

	return str;
};
/* =====================================================
	Function: wp_sk.cleanerMagicLinks(str)

	Sprzątanie końcowe magicznych linków i elementów
	powiązanych - międzywiki, medale dla nich i kategorie.
   ===================================================== */
wp_sk.cleanerMagicLinks = function (str)
{
	// zbieranie
	str = wp_sk.cat.gather(str);
	str = wp_sk.iWiki.gather(str);

	// usuwanie pozostawionych przy zbieraniu i innych wielokrotnych, pustych wierszy
	str = str.replace(/[\n]{3,}/g, '\n\n');

	// wstawienie na koniec (call not copy to have "this")
	str = str.replace(/\s*$/, function(a) {return wp_sk.cat.output(a)});
	str = str.replace(/\s*$/, function(a) {return wp_sk.iWiki.output(a)});

	return str;
};

/* =====================================================
	Funkcje wspomagające porządkowanie           {START}
   ----------------------------------------------------- */
//
// Sprzątanie infoboksów
//
/**
 *
 * @param {String} a Whole tpl.
 * @param {String} nazwa Tpl name.
 * @param {String} zaw Tpl content.
 */
wp_sk.rFriendlyIbox = function (a,nazwa,zaw)
{
	if (zaw.indexOf('<!--')!=-1 || zaw.indexOf('=')==-1 || zaw.indexOf('\n')==-1)
	{
		return a;
	}
	nazwa = nazwa.replace(/^\s*(\S*(\s+\S+)*)\s*$/, "$1");	// trim

	//
	// escapowanie parametrów
	//
	zaw = wp_sk.escapePipes(zaw);

	//
	// sprzątanie
	//
	// del pustych
	zaw = zaw.replace(/\|\s*(?=\|)/g, function(a) {return (a.indexOf('\n')==-1)?'':'\n'}).replace(/\|\s*$/g, "");
	zaw = zaw.replace(/^\s*(\S*(\s+\S+)*)\s*$/, "$1");	// trim
	// przeniesienie | na początek wiersza
	zaw = '\n'+zaw+'\n';
	zaw = zaw.replace(/\s*\|(\s*)/g, function(a, post)
	{
		if (a.indexOf('\n')==-1)
		{
			return a;
		}
		else if (post.indexOf('\n')==-1)
		{
			return '\n |'+post;
		}
		else
		{
			return '\n | ';
		}
	});

	// puste spacje po znakach =
	zaw = zaw.replace(/=\n/g, '= \n');

	// odescapowanie
	zaw = wp_sk.unescapePipes(zaw);

	//
	// Zakończenie
	//
	return '{{'+nazwa.substring(0,1).toUpperCase()+nazwa.substring(1)+zaw+'}}';
};
//
// Dekodowanie linków
//
wp_sk.rLinkdecode = function(a,name,anchor,end)
{
	try
	{
		name=decodeURIComponent(name);
		anchor=decodeURIComponent(anchor.replace(/\.([2-9A-F]{2})/g,'%$1'));
		a='[['+name+anchor+end;
	}
	catch(err){}

	a = a.replace(/\{\{/g, '.7B.7B');
	a = a.replace(/\}\}/g, '.7D.7D');
	a = a.replace(/802\x11/g, '802.11');
	return a.replace(/_/g,' ');
};

wp_sk.rLinkinterwikidecode = function(a,name)
{
	try
	{
		name=decodeURIComponent(name);
		a='{{link-interwiki\|'+name+'\|';
	}
	catch(err){}

	a = a.replace(/802\x11/g, '802.11');
	return a.replace(/_/g,' ');
};

/**
 * Escape pipes | in templates and links.
 * 
 * @param {String} wiki Wikicode.
 */
wp_sk.escapePipes = function(wiki) {
	var zaw = wiki;
	
	// pre-escape (<<abc>> -> <<#abc#>>)
	zaw = zaw.replace(/<<(.+?)>>/g,'<<#$1#>>');
	
	// inner templates
	if (zaw.indexOf('{{') >= 0) {
		var indexes = this.findTemplates(zaw);
		if (indexes.length) {
			var parts = '';
			var prev = 0;
			for (var index = 0; index < indexes.length; index++) {
				var part = indexes[index];
				//console.log('template: ', part, '(' + wiki.substring(part.start, part.end) + ')');
				parts += zaw.substring(prev, part.start);
				parts += zaw
					.substring(part.start, part.end)
					.replace(/\|/g, '<<p>>')
				;
				prev = part.end;
			}
			zaw = parts + zaw.substr(prev);
		}
	}

	// links
	zaw = zaw.replace(/\[\[[^\]]+\]\]/g,function(a){ return a.replace(/\|/g,'<<p>>') });
	
	return zaw;
}

/**
 * Un-Escape pipes |.
 * 
 * @param {String} wiki Wikicode.
 * @returns Unscaped code.
 */
wp_sk.unescapePipes = function (wiki) {
	var zaw = wiki;

	// pipes
	zaw = zaw.replace(/<<p>>/g, '|');
	// pre-escape (<<abc>> <- <<#abc#>>)
	zaw = zaw.replace(/<<#(.+?)#>>/g,'<<$1>>');

	return zaw;
}


/**
 * Find templates.
 * 
 * Uses state machine search to find all templates that might have sub-templates.
 * 
 * @param {String} wiki Wikicode.
 * @returns Indexes of templates found [{start, end}].
 */
wp_sk.findTemplates = function (wiki) {
	var zaw = wiki;
	var debug = false;
	var result = [];

	function add(start, end) {
		if (debug) {
			console.log('template found: ', {
				start: start,
				end: end
			}, '(' + zaw.substring(start, end) + ')');
		}
		result.push({start: start, end: end});
	}

	var start = zaw.indexOf('{{');
	var lastEnd = zaw.lastIndexOf('}}');
	var state = 'in';
	var sublevel = 0;
	for (var current = start + 2; current < lastEnd;) {
		var chars = zaw.substr(current, 2);
		var found = false;

		// in template
		if (state == 'in') {
			if (chars === '}}') {
				state = 'out';
				found = true;
			} else if (chars === '{{') {
				state = 'sub';
				sublevel = 1;
			}
		// in sub-template
		} else if (state == 'sub') {
			if (chars === '}}') {
				sublevel--;
				if (sublevel <= 0) {
					state = 'in';
				}
			}
			if (chars === '{{') {
				sublevel++;
			}
		// out of template
		} else if (state == 'out') {
			if (chars === '{{') {
				state = 'in';
				start = current;
			}
		}

		if (debug) {
			console.log({
				state: state,
				chars: chars,
				sublevel: sublevel
			});
		}

		// next
		if (chars === '}}' || chars === '}}') {
			current += 2;
		} else {
			current++;
		}
		// template found
		if (found) {
			add(start, current);
		}
	}
	add(start, lastEnd + 2);

	return result;
}
/* -----------------------------------------------------
	Funkcje wspomagające porządkowanie          {KONIEC}
   ===================================================== */

/* =====================================================
	Klasy wspomagające porządkowanie             {START}
   ----------------------------------------------------- */
/* =====================================================
	Class: wp_sk.nowiki

	Ukrywanie obszarów w tagach: nowiki, pre, source i math

	.hide(str)
		ukrywanie tagów specjalnych wraz z ich wnętrzami
	.show(str)
		przywrócenie ukrytych tagów
   ===================================================== */
//
// object init
//
wp_sk.nowiki = {};

//
// .hide(str)
//
wp_sk.nowiki.hide = function(str)
{
	//
	// escapowanie przed nowikowe
	str = str.replace(/<<<(#*[0-9]+)>>>/g, '<<<#$1>>>');

	// reset
	wp_sk.nowiki.t_i = -1;
	wp_sk.nowiki.tags = [];
 
	//
	// self closing tags
	str = str.replace(/<(nowiki|pre)\s*\/>/g, function(tag) {
		wp_sk.nowiki.t_i++;
		wp_sk.nowiki.tags[wp_sk.nowiki.t_i] = tag;
		return "<<<"+wp_sk.nowiki.t_i+">>>";
	});
	wp_sk.nowiki.t_i++;

	//
	// właściwe ukrywanie
	var re = /<(nowiki|pre|source|math|includeonly|noinclude|syntaxhighlight)(|[ \t\n][^>]*)>/g;
	var m;
	// póki znaleziono tag otwierający
	for (; (m=re.exec(str))!==null; wp_sk.nowiki.t_i++)
	{
		var start, end, re_end;
 
		start = m.index;
 
		// odszukanie końca: </tag([ \t\n]*)>
		re_end = new RegExp("</"+m[1]+"([ \t\n]*)>", "g");
		var endMatch = re_end.exec(str.substring(re.lastIndex));
		end = (endMatch===null) ? str.length : re.lastIndex+re_end.lastIndex;
		if (endMatch===null) {
			console.warn('Unclosed tag found.', {tag:m[1], text:str.substring(start, start+20) + '...'});
		}
 
		// dopisanie do tablicy zawartości
		wp_sk.nowiki.tags[wp_sk.nowiki.t_i] = str.substring(start,end);
 
		// zamiana całości znalezionego obszaru na: <<<indeks>>>
		str = str.substring(0,start)+"<<<"+wp_sk.nowiki.t_i+">>>"+str.substring(end);
 
		// szukanie od startu, bo część znaków już usunięto
		re.lastIndex = start;
	}
 
	//
	// komentarze
	str = str.replace(/<!--[\s\S]+?-->/g, function(comment) {
		wp_sk.nowiki.t_i++;
		wp_sk.nowiki.tags[wp_sk.nowiki.t_i] = comment;
		return "<<<"+wp_sk.nowiki.t_i+">>>";
	});
 
	return str;
};
//
// .filehide(str)
//
wp_sk.nowiki.filehide = function(str)
{
	str = str.replace(/\[\[(:? *plik *: *[^\]\|]*)/ig, function(comment,filelink) {
		wp_sk.nowiki.t_i++;
		wp_sk.nowiki.tags[wp_sk.nowiki.t_i] = filelink;
		return "[[<<<"+wp_sk.nowiki.t_i+">>>";
	});
	// domyślna grafika w większości infoboksów
	str = str.replace(/(\| *(?:grafika|zdjęcie) *= *)([^\n\|]+)/ig, function(comment,parametr,filelink) {
		wp_sk.nowiki.t_i++;
		wp_sk.nowiki.tags[wp_sk.nowiki.t_i] = filelink;
		return parametr+"<<<"+wp_sk.nowiki.t_i+">>>";
	});
	// i jeszcze w <gallery>
	str = str.replace(/<gallery([^\n>]*)>([\s\S]+?)<\/gallery>/gi, function(a, opcje, zaw) {
		zaw = zaw.replace(/\n(Plik:[^\|]*)/g, function(comment,filelink) {
			wp_sk.nowiki.t_i++;
			wp_sk.nowiki.tags[wp_sk.nowiki.t_i] = filelink;
			return "\n<<<"+wp_sk.nowiki.t_i+">>>";
		});
		return "<gallery" + opcje + ">" + zaw + "</gallery>";
	});
	// i jeszcze w {{Galeria}}
	str = str.replace(/\{\{Galeria([\s\S]+?)\}\}/gi, function(a, zaw) {
		zaw = zaw.replace(/\|( *\n *Plik:[^\|]*)/g, function(comment,filelink) {
			wp_sk.nowiki.t_i++;
			wp_sk.nowiki.tags[wp_sk.nowiki.t_i] = filelink;
			return "\|<<<"+wp_sk.nowiki.t_i+">>>";
		});
		return "{{Galeria" + zaw + "}}";
	});

	return str;
};
//
// .show(str)
//
wp_sk.nowiki.show = function(str)
{
	var max_depth = 3;
	// tagi
	for (var i=0; i<max_depth; i++) {
		str = str.replace(/<<<([0-9]+)>>>/g, function (a, i)
		{
			return wp_sk.nowiki.tags[i];
		});
		if (str.search(/<<<([0-9]+)>>>/)==-1) {
			break;
		}
	}
	// odescapowanie nowikowe
	str = str.replace(/<<<#(#*[0-9]+)>>>/g, '<<<$1>>>');

	return str;
};

/* =====================================================
	Class: wp_sk.cat

	Zbieranie, porządkowanie i wstawianie kategorii

	.gather(str)
		zbieranie kategorii ze str ze zwrotem nowego str
	.output(a)
		porządkuje i zwraca wikitekst z kategoriami;
		parametr a jest nieistotny
	.getDefSort()
		zwraca wyrażenie regularne dla defaultsort
	.newDefSort()
		szukanie nowego (najpopularniejszego) defaultsort

	.art_def_sort
		znaleziony w artykule defaultsort
	.def_sort
		wybrany dla artykułu defsort
	.arr
		tablica z kategoriami ('nazwa|sorotwanie')
	.arr_i
		indeks pomocniczy, a także liczba elementów w arr
   ===================================================== */
// object init
wp_sk.cat = {};
//
// .gather(str)
//
wp_sk.cat.gather = function(str)
{
	//
	// zbiórka i kasowanie
	wp_sk.cat.arr = [];
	wp_sk.cat.arr_i = 0;
	wp_sk.cat.art_def_sort = '';
	wp_sk.cat.map = [];
	str = str.replace(/\{\{SORTUJ:([^\{\}]+|\{\{[^\{\}]+\}\})\}\}(?:[ \t]+\n)?/g, function(a, ds){wp_sk.cat.art_def_sort=ds; return ''});
	str = str.replace(/(?:\n[ \t]+)?\[\[Kategoria:([^\]\[]+)\]\](?:[ \t]+\n)?/g, function(a, cat) {
		if (!wp_sk.cat.map[cat]) {
			wp_sk.cat.arr[wp_sk.cat.arr_i++]=cat;
			wp_sk.cat.map[cat] = 1;
		}
		return '';
	});
	wp_sk.cat.def_sort = wp_sk.cat.art_def_sort;

	return str;
};
//
// .output(a)
//
wp_sk.cat.output = function (a)
{
	if (wp_sk.cat.arr_i==0)
	{
		return a;
	}
	var str = '\n'; // categories head;

	//
	// ustawienie regexp dla defaultsort (wg starego lub znalezionego)
	var reDefSort = wp_sk.cat.getDefSort();

	//
	// zbędne spacje
	for (var i=0; i<wp_sk.cat.arr_i; i++)
	{
		if (/^.+\| $/.test(wp_sk.cat.arr[i]) == false)
		{
			wp_sk.cat.arr[i] = wp_sk.cat.arr[i].replace(/^\s*(\S*(\s+\S+)*)\s*$/, "$1");	// trim
		}
	}

	//
	// "wyświetlanie" kategorii
	if (reDefSort!="") // jeśli jest jakiś defaultsort
	{
		str += '\n{{SORTUJ:'+wp_sk.cat.def_sort+'}}';
		for (var i=0; i<wp_sk.cat.arr_i; i++)
		{
			// jeśli nie było defaultsort i puste, to dodajemy domyślne, żeby nie psuć
			if (!wp_sk.cat.art_def_sort.length && wp_sk.cat.arr[i].indexOf('|')==-1)
			{
				str += '\n[[Kategoria:'+wp_sk.cat.arr[i]+'|' + mw.config.get('wgPageName').replace(/_/g, ' ') + ']]';
			}
			else
			{
				wp_sk.cat.arr[i] = wp_sk.cat.arr[i].replace(reDefSort,'');	// usuwanie klucza
				str += '\n[[Kategoria:'+wp_sk.cat.arr[i]+']]';
			}
		}
	}
	else
	{
		for (var i=0; i<wp_sk.cat.arr_i; i++)
		{
			str += '\n[[Kategoria:'+wp_sk.cat.arr[i]+']]';
		}
	}

	return str;
}
//
// .getDefSort()
//
wp_sk.cat.getDefSort = function ()
{
	//
	// wybieramy klucz sortowania
	if (wp_sk.cat.art_def_sort.length)
	{
		wp_sk.cat.def_sort = wp_sk.cat.art_def_sort;
	}
	// szukanie nowego jeśli liczba kategorii jest większa od 1
	else if (wp_sk.cat.arr_i>1)
	{
		wp_sk.cat.def_sort = wp_sk.cat.newDefSort();
	}

	var reDefSort="";
	if (wp_sk.cat.def_sort!="")
	{
		// zamiana na regexp (żeby uniknąć częściowych dopasowań)
		reDefSort = wp_sk.cat.def_sort.replace(/([(){}\[\]\\|.*?$^])/g, '\\$1');
		reDefSort = new RegExp('\\|'+reDefSort+'$');
	}

	return reDefSort;
}
//
// .newDefSort()
//
wp_sk.cat.newDefSort = function ()
{
	var def_sort = '';

	//
	// sprawdzenie, czy wybrano jakiś klucz sortowania w kategoriach
	var sort_i;
	for (sort_i=0; sort_i<wp_sk.cat.arr_i && wp_sk.cat.arr[sort_i].indexOf('|')<0; sort_i++);

	//
	// liczenie kategorii z kluczami
	var total_sort_num = 0;	// całkowita liczba kluczy sortowania
	for (var i = sort_i; i<wp_sk.cat.arr_i; i++)	// zaczynamy od już znalezionego
	{
		if (wp_sk.cat.arr[i].indexOf('|')>0)
			total_sort_num++;
	}
	//
	// jeśli mało kluczy (byłoby dużo {{PAGENAME}}), to bez domyślnego klucza
	if (total_sort_num*2<wp_sk.cat.arr_i) //<50%
	{
		return '';
	}

	//
	// jeśli wybrano jakieś sorotwanie, to szukamy nowego klucza (wg popularności)
	if (sort_i!=wp_sk.cat.arr_i)
	{
		//
		//
		var def_sort_num = 0;
		var def_sort_forbiden = ['!', ' ', '*', '+'];
		for (var i = sort_i; i<wp_sk.cat.arr_i; i++)	// zaczynamy od już znalezionego
		{
			var j, tmp_def_sort, tmp_def_sort_re, tmp_def_sort_num;

			// dochodzimy do klucza kandydującego
			for (j = i; j<wp_sk.cat.arr_i && wp_sk.cat.arr[j].indexOf('|')<0; j++);
			if (j==wp_sk.cat.arr_i)
				break;
			i = j;

			// klucz
			tmp_def_sort = wp_sk.cat.arr[j].substr(wp_sk.cat.arr[j].indexOf('|')+1);
			if (def_sort == tmp_def_sort)	// już był
			{
				continue;
			}
			// zamiana na regexp (żeby uniknąć częściowych dopasowań)
			tmp_def_sort_re = tmp_def_sort.replace(/([(){}\[\]\\|.*?$^])/g, '\\$1');	// escapowanie znaków regexpowych
			tmp_def_sort_re = new RegExp('\\|'+tmp_def_sort_re+'$');

			// liczenie wystąpień
			var tmp_def_sort_num=1;
			for (j++; j<wp_sk.cat.arr_i; j++)
			{
				if (tmp_def_sort_re.test(wp_sk.cat.arr[j]))
				{
					tmp_def_sort_num++;
				}
			}

			// kandydyjący = nowy?
			if (tmp_def_sort_num<2 || def_sort_num > tmp_def_sort_num)
			{
				continue;
			}
			if (tmp_def_sort_num*2>wp_sk.cat.arr_i && def_sort_forbiden.indexOf(tmp_def_sort)<0) //>50% || nie niedozwolone
			{
				def_sort_num = tmp_def_sort_num;
				def_sort = tmp_def_sort;
			}
		}
	}

	return def_sort;
}

/* =====================================================
	Class: wp_sk.iWiki

	Zbieranie, porządkowanie i wstawianie interwiki

	.gather(str)
		zbieranie interwiki ze str ze zwrotem nowego str
	.output(a)
		porządkuje i zwraca wikitekst z interwiki;
		parametr a jest nieistotny
	.comp(a, b)
		porównuje a z b i zwraca wartość odpowiednią
		dla funkcji sort()

	.order
		tablica z językami ustawionymi wg kolejności
		wg której mają być sortowane interwiki
	.arr
		tablica z interwiki ([język, artykuł])
	.arr_i
		indeks pomocniczy, a także liczba elementów w arr
   ===================================================== */
// object init
wp_sk.iWiki = new Object();
//
// .gather(str)
//
wp_sk.iWiki.gather = function(str)
{
	wp_sk.iWiki.arr = new Array();
	wp_sk.iWiki.arr_i = 0;
	str = str.replace(
		// wg: http://meta.wikimedia.org/wiki/List_of_Wikipedias
		/\[\[\s*([a-z\-]+)\s*:([^\]\|\[]+)\]\](?:[ \t]+\n)?/gi,
		function (a, lang, art)
		{
			lang = lang.toLowerCase(); // [[DE:blah]]
			// wg: http://svn.wikimedia.org/svnroot/mediawiki/trunk/phase3/maintenance/interwiki.sql
			if (wp_sk.iWiki.order.indexOf(lang) >= 0) // czy na pewno interwiki
			{
				wp_sk.iWiki.arr[wp_sk.iWiki.arr_i] = new Array(lang,art);
				wp_sk.iWiki.arr_i++;
				return '';
			}
			else
			{
				return a;
			}
		}
	);

	return str;
}
//
// .output(a)
//
wp_sk.iWiki.output = function (a)
{
	if (wp_sk.iWiki.arr_i==0)
	{
		return a;
	}
	var str = '\n';

	wp_sk.iWiki.arr.sort(wp_sk.iWiki.comp); // alfabetycznie wg kodu literowego
	for (var i=0; i<wp_sk.iWiki.arr_i; i++)
	{
		str += '\n[['+wp_sk.iWiki.arr[i][0]+':'+wp_sk.iWiki.arr[i][1]+']]';
	}

	return str;
}
//
// .comp(a,b)
//
wp_sk.iWiki.comp = function (a, b)
{
	if (wp_sk.iWiki.order.indexOf(a[0]) < wp_sk.iWiki.order.indexOf(b[0]))
	{
		return -1;
	}
	else if (wp_sk.iWiki.order.indexOf(a[0]) > wp_sk.iWiki.order.indexOf(b[0]))
	{
		return 1;
	}
	// else
	return 0;
}
// wg <del>http://meta.wikimedia.org/wiki/Interwiki_sorting_order#By_order_of_alphabet.2C_based_on_local_language</del>
// Pomoc:Interwiki
wp_sk.iWiki.order = [
	'ace', 'kbd', 'af', 'ak', 'als', 'am', 'ang', 'ab', 'ar', 'an',
	'arc', 'roa-rup', 'frp', 'as', 'ast', 'gn', 'av', 'ay', 'az', 'bm',
	'bn', 'bjn', 'zh-min-nan', 'nan', 'map-bms', 'ba', 'be', 'be-x-old',
	'bh', 'bcl', 'bi', 'bg', 'bar', 'bo', 'bs', 'br', 'bxr', 'ca', 'cv',
	'ceb', 'cs', 'ch', 'cbk-zam', 'ny', 'sn', 'tum', 'cho', 'co', 'cy',
	'da', 'dk', 'pdc', 'de', 'dv', 'nv', 'dsb', 'dz', 'mh', 'et', 'el',
	'eml', 'en', 'myv', 'es', 'eo', 'ext', 'eu', 'ee', 'fa', 'hif',
	'fo', 'fr', 'fy', 'ff', 'fur', 'ga', 'gv', 'gag', 'gd', 'gl', 'gan',
	'ki', 'glk', 'gu', 'got', 'hak', 'xal', 'ko', 'ha', 'haw', 'hy',
	'hi', 'ho', 'hsb', 'hr', 'io', 'ig', 'ilo', 'bpy', 'id', 'ia', 'ie',
	'iu', 'ik', 'os', 'xh', 'zu', 'is', 'it', 'he', 'jv', 'kl', 'kn',
	'kr', 'pam', 'krc', 'ka', 'ks', 'csb', 'kk', 'kw', 'rw', 'rn', 'sw',
	'kv', 'kg', 'ht', 'ku', 'kj', 'ky', 'mrj', 'lad', 'lbe', 'lo', 'ltg',
	'la', 'lv', 'lb', 'lez', 'lt', 'lij', 'li', 'ln', 'jbo', 'lg', 'lmo', 'hu',
	'mk', 'mg', 'ml', 'mt', 'mi', 'mr', 'xmf', 'arz', 'mzn', 'ms', 'cdo',
	'mwl', 'mdf', 'mo', 'mn', 'mus', 'my', 'nah', 'na', 'fj', 'nl',
	'nds-nl', 'cr', 'ne', 'new', 'ja', 'nap', 'ce', 'frr', 'pih', 'no',
	'nb', 'nn', 'nrm', 'nov', 'ii', 'oc', 'mhr', 'or', 'om', 'ng', 'hz',
	'uz', 'pa', 'pi', 'pfl', 'pag', 'pnb', 'pap', 'ps', 'koi', 'km',
	'pcd', 'pms', 'tpi', 'nds', 'pl', 'tokipona', 'tp', 'pnt', 'pt',
	'aa', 'kaa', 'crh', 'ty', 'ksh', 'ro', 'rmy', 'rm', 'qu', 'rue',
	'ru', 'sah', 'se', 'sm', 'sa', 'sg', 'sc', 'sco', 'stq', 'st', 'nso',
	'tn', 'sq', 'scn', 'si', 'simple', 'sd', 'ss', 'sk', 'sl', 'cu',
	'szl', 'so', 'ckb', 'srn', 'sr', 'sh', 'su', 'fi', 'sv', 'tl', 'ta',
	'kab', 'roa-tara', 'shi', 'tt', 'te', 'tet', 'th', 'ti', 'tg', 'to', 'chr',
	'chy', 've', 'tr', 'tk', 'tw', 'udm', 'bug', 'uk', 'ur', 'ug', 'za',
	'vec', 'vep', 'vi', 'vo', 'fiu-vro', 'wa', 'zh-classical', 'vls', 'war',
	'wo', 'wuu', 'ts', 'yi', 'yo', 'zh-yue', 'diq', 'zea', 'bat-smg',
	'zh', 'zh-tw', 'zh-cn',
]

/* =====================================================
	Class: wp_sk.redir

	Poprawianie redrictów. Przynajmniej na razie bazuje
	na podglądzie artykułu, w którym redirecty są oznaczone
	specjalną klasą (mw-redirect).

	.init()
		inicjowanie poprawek przez wstawienie ikonki przetwarzania
		wyszukanie redirectów i wysłanie wstępnego żądania
		do serwera o rozwinięcie redirectów
	.resp(res)
		funkcja przyjmująca odpowiedzieć (res) z serwera
		i przetwarzająca ją na tabelką rozwinięć redirectów

	.arr	- tabela rozwinięć redirectów wykorzystywana wewnętrznie
	.arr_i	- indeks używany przy tworzeniu tabeli
	.url	- url wstępnego zapytania, potrzebny w razie
			konieczności kontynuowania żądań (wymóg API)
   ===================================================== */
//
// object init
//
wp_sk.redir = new Object();

wp_sk.redir.linkPrefix = document.location.protocol + "//" + document.location.hostname + mw.config.get( 'wgArticlePath' ).replace( '$1', '' );

wp_sk.redir.extractTitle = function( link ) {
	if ( link.substring( 0, this.linkPrefix.length ) != this.linkPrefix ) {
		return null;
	}

	return decodeURIComponent( link.substring( this.linkPrefix.length ).replace( /_/g, ' ' ) ).replace( /#.*$/, '' );
}

//
// .init()
//
wp_sk.redir.init = function()
{
	wp_sk.redir.base_url = mw.util.wikiScript('api') + '?action=query&redirects&format=json&titles=';

	// ograniczenie czasowe, ale tylko w podglądzie (żeby nie zamęczyć serwerów)
	if (mw.config.get('wgAction')=='submit')
	{
		if (document.cookie.indexOf('wpsk_redir_time_disable=1')!=-1)
		{
			mw.hook('userjs.wp_sk.redir.done').fire(wp_sk, false);
			return;
		}
		else
		{
			var d = new Date();
			d = new Date(d.getTime()+300000); //+5min (il. sekund * 1000)
			document.cookie = "wpsk_redir_time_disable=1; path=/; expires=" + d.toGMTString();
		}
	}

	var elWikiBody = document.getElementById('wikiPreview');
	if (elWikiBody)
	{
		//
		// szukanie przekierowań
		wp_sk.redir.urls = new Array();
		wp_sk.redir.urls[0] = new Array();
		var url_i, url_j;
		url_i = url_j = 0;
		var as = jQuery("a.mw-redirect");
		for (var i=0; i<as.length; i++)
		{
			var tmp = this.extractTitle( as[i].href );
			if ( tmp == null ) {
				continue;
			}
			// new url?
			var isnew=true;
			for (var ui=0; ui<=url_i; ui++)
			{
				for (var uj=0; uj<url_j; uj++)
				{
					if (wp_sk.redir.urls[ui][uj]==tmp)
					{
						isnew=false;
						break;
					}
				}
				if (!isnew)
					break;
			}
			// add to array
			if (isnew)
			{
				wp_sk.redir.urls[url_i][url_j++] = tmp;
				if (url_j>=50)	// ograniczenie API
				{
					if (url_i>=4)	// max (4+1)x50 linków
					{
						break;
					}
					url_j = 0;
					wp_sk.redir.urls[++url_i] = new Array();
				}
			}
		}
		//
		// ostateczne przygotowanie i wysyłanie żądania
		if (wp_sk.redir.urls[0].length>0)
		{
			var $notice = jQuery('<div id="wp-sk-redir-notice">Sprawdzanie linków do przekierowań...</div>');
			jQuery('#wpTextbox1').before($notice);

			// na znalezione redirecty
			wp_sk.redir.arr = new Array();
			wp_sk.redir.arr_i = 0;

			// przygotowanie pierwszej porcji
			wp_sk.redir.urls_i = 0;
			var url = wp_sk.redir.urls[wp_sk.redir.urls_i].join('|');
			wp_sk.redir.url = wp_sk.redir.base_url+url;
			wp_sk.redir.full_prev_url = wp_sk.redir.url;
			// run
			var that = this;
			jQuery.getJSON( wp_sk.redir.url, null, function( result ) {
				that.resp( result );
			} );
		} else {
			mw.hook('userjs.wp_sk.redir.done').fire(wp_sk, false);
		}
	}
}

//
// .resp(res)
//
wp_sk.redir.resp = function (jres)
{
	var that = this;

	// zbiórka tłumaczenia redirectów
	for (var r in jres.query.redirects)
	{
		r = jres.query.redirects[r];
		wp_sk.redir.arr[wp_sk.redir.arr_i++] = {
			'rdir' : r.from,
			'art' : r.to
		}
	}
	// kontynuacja?
	if (jres['query-continue']!=null)
	{
		var continue_url = wp_sk.redir.url + '&plcontinue='+encodeURIComponent(jres['query-continue'].links.plcontinue);
		if (wp_sk.redir.full_prev_url != continue_url)	// <s>api</s> potential bug workaround
		{
			wp_sk.redir.full_prev_url = continue_url;
			jQuery.getJSON( continue_url, null, function( result ) {
				that.resp( result );
			} );
			return;
		}
	}
	// kolejna porcja linków
	else if (wp_sk.redir.urls_i < wp_sk.redir.urls.length-1)
	{
		var url = wp_sk.redir.urls[++wp_sk.redir.urls_i].join('|');
		wp_sk.redir.url = wp_sk.redir.base_url+url;
		wp_sk.redir.full_prev_url = wp_sk.redir.url;
		jQuery.getJSON( wp_sk.redir.url, null, function( result ) {
			that.resp( result );
		} );
		return;
	}

	// przygotowanie funkcji podmiany redirectów
	wp_sk.cleanerLinks_orig = wp_sk.cleanerLinks;
	wp_sk.cleanerLinks = function (str)
	{
		var reTxtEscape = /([\\^\$\*\+\?\.\(\)\[\]\{\}\:\=\!\|\,\-])/g;
		for (var page in wp_sk.redir.arr)
		{
			page = wp_sk.redir.arr[page];
			var re = page.rdir.replace(reTxtEscape,'\\$1');
			if (re.search(/^[a-zżółćęśąźń]/i)==0)
			{
				re = '['+ re[0].toLowerCase() + re[0].toUpperCase() +']'
					+ re.substr(1);
			}
			var re = new RegExp('\\[\\[('+re+')(\\||\\]\\])', 'g');
			str = str.replace(re, function (a, art, end)
			{
				return '[['+ page.art + (end=='|' ? '|' : '|'+art+']]');
			});
		}

		return wp_sk.cleanerLinks_orig(str);	// dopiero teraz, żeby poprawiać także zmienione linki
	}

	jQuery( "#wp-sk-redir-notice" ).remove();

	var el = document.getElementById( 'wp_sk_img_btn' );
	if ( el ) {
		if ( toolbarGadget.wikieditor ) {
			el.src = 'https://upload.wikimedia.org/wikipedia/commons/thumb/9/99/Broom_icon_R.svg/22px-Broom_icon_R.svg.png';
		} else {
			el.src = 'https://upload.wikimedia.org/wikipedia/commons/3/31/Button_broom_R.png';
		}
	}
	
	// done, hasRedirects=true
	mw.hook('userjs.wp_sk.redir.done').fire(wp_sk, true);
}
/* -----------------------------------------------------
	Klasy wspomagające porządkowanie            {KONIEC}
   ===================================================== */

// ujednolicanie nazw szablonów (ładowane osobno)
wp_sk.sz_redirs_tab = {};

/* =====================================================
	OnLoad
   ===================================================== */
jQuery( document ).ready( function() {
	if ( mw.config.get( 'wgAction' ) != 'submit' && mw.config.get( 'wgAction' ) != 'edit' ) {
		return;
	}
	
	// Moduły zewnętrzne dla projektów siostrzanych.
	if ( ( typeof sel_t ) !== 'object' ) {
		mw.loader.load( 'https://pl.wikipedia.org/w/index.php?title=MediaWiki:Gadget-sel_t.js&action=raw&ctype=text/javascript' );
	}

	mw.hook('userjs.wp_sk.ready').fire(wp_sk);

	if ( wp_sk_show_as_button ) {
		// eslint-disable-next-line no-unused-vars
		wp_sk.button(function(buttonImage){
			mw.hook('userjs.wp_sk.button_created').fire(wp_sk);
			if ( wp_sk_redir_enabled ) {
				wp_sk.redir.init();
			} else {
				mw.hook('userjs.wp_sk.redir.done').fire(wp_sk, false);
			}
		});
	}
} );

// ~export
mw.hook('userjs.wp_sk.loaded').fire(wp_sk);

// </nowiki>