MediaWiki:Gadget-disFixer.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.
var disFixerVersion = '2.8.2';
/*
DisFixer - disambig links fixer.
by Matma Rex
Co-authors: ToSter, Nux
See also: https://xtools.wmcloud.org/articleinfo/pl.wikipedia.org/MediaWiki:Gadget-disFixer.js?uselang=pl
License CC-BY-SA 3.0: https://creativecommons.org/licenses/by-sa/3.0/deed

Użycie: wybierz disFixer w preferencjach

Szerszy opis: [[Wikipedia:Narzędzia/disFixer]].

== Hooks (advanced customization) ==

Main hooks (search other with "userjs.disFixer"):
<li>userjs.disFixer.actioninit
<li>userjs.disFixer.actionready

=== Be carefull with actioninit ===
mw.hook('userjs.disFixer.actioninit').add(function (disFixer, disambigs, redirs) {
	console.log('disFixer.actioninit');

	// Replace one of the functions (class methods).
	// Note! Any function might change and you might need to adjust yours in future.
	disFixer.disHeaderButton = function(title)
	{
		$('#content').prepend('<input id="disBeginButton" type="submit" class="disFixerButton" value="" title="'+title+'" />');
		var $button = $('#disBeginButton');
		return $button;
	};
});

Note! Changing some functions and `disHeaderButton` specifically might be tricky as you need to register your function before the button is created.
So you might want to use `mw.loader` to load disFixer __after__ registering your function.

=== Using actionready ===

Another approach is to adjust things when the main action is ready.

// move exisiting button (should preserve events, might brake styles)
mw.hook('userjs.disFixer.actionready').add(function (disFixer, disambigs, redirs) {
	console.log('disFixer.actionready');
	if (disBeginButton) {
		document.querySelector('#content').prepend(disBeginButton);
	}
});

== TODO ==

TODO: Should remove most if not all dis* globals.
<li>Should make them class properties or pass as parameters.
<li>Should make sure other gadgets don't use them.
<li>Might expose some of them via hooks.

<nowiki>
*/
/* globals $, jQuery, mw, OO */
/* globals gConfig, jsMsg, markDisambigsGadget */
/* globals dis, disRedirs, disResolvedRedirs, disHighlightedLinkTimeout, disHighlightedLink */
/* eslint-disable no-redeclare */
/* eslint-disable indent */
/* eslint-disable no-useless-escape */
/* eslint-disable no-empty */

/**
 * The strings of DisFixer.
 * Translate this!
 */
var disStr = window.disStr =
{
	name: 'disFixer',
	descriptionPage: 'WP:Narzędzia/disFixer',

	and:			' i ', //used in summary
	autosummaryBegin:       'poprawa',
	dabsShort:		'ujedn.',
	redirsShort:		'przek.',

	categoryDabPages:       'Kategoria:Strony ujednoznaczniające',
	wikipediaDabPage:       'Wikipedia:Strona ujednoznaczniająca',

	fixLinks:		'Popraw linki do ujednoznacznień i przekierowań', //main button
	fixLinksDisam:		'Popraw linki do ujednoznacznień',
	fixLinksRedir:		'Popraw linki do przekierowań',
	wait:			'Czekaj...', //main button after click

	noRedirLinks:		'Brak linków do przekierowań.',
	fixingInProgress:       'Rozwiązywanie przekierowań: trwa...',

	fixRedirsOnly:		'Popraw przekierowania (Wykonaj także inne zmiany! Sama poprawa przekierowań nie ma sensu!)', //fix button if no dabs
	fixButton:		'Popraw', //fix button

	fixDabs:		'Popraw ujednoznacznienia:', //before list of dabs
	viewDabPage:		'Zobacz stronę ujedn.', //title
	scrollToLink:		'Przewiń do pozycji linku w tekście', //title
	delink:			'odlinkuj', //last element in every list

	fixRedirsCheckbox:      'popraw przekierowania',

	otherTarget:		'inny cel...',
	setNewLinkTarget:       'Dokąd ma prowadzić link?',

	markDisambigsMissing:   'disFixer wymaga włączonego gadżetu <i>Kolorowanie linków wewnętrznych do stron ujednoznaczniających</i>'
};

mw.loader.using(['ext.gadget.gConfig', 'jquery.textSelection'], function(){

var disPopups = false;
mw.hook('userjs.popups.completed').add(function(pg){
	disPopups = pg;
});
	
// ustawienia
gConfig.register('disFixer', { name: disStr.name, descriptionPage: disStr.descriptionPage }, [
	{
		name: 'markAsMinor',
		desc: 'Oznacz zmiany jako małe.',
		type: 'boolean',
		deflt: false,
		legacy: [window, 'disMarkAsMinor']
	},
	{
		name: 'codeCleanup',
		desc: 'Automatycznie uruchom WP:SK po każdej zmianie.',
		type: 'boolean',
		deflt: false,
		legacy: [window, 'disCodeCleanup']
	},
	{
		name: 'headerButton',
		desc: 'Wyświetl przycisk w nagłówku (zamiast w narzędziach strony).',
		type: 'boolean',
		deflt: false,
	},
	{
		name: 'fixIfRedirsOnly',
		desc: 'Wyświetl przycisk także wtedy, gdy do poprawy są same przekierowania.',
		type: 'boolean',
		deflt: false,
		legacy: [window, 'disFixIfRedirsOnly']
	},
	{
		name: 'fixAllContentTypes',
		desc: '[zaawansowane] Wyświetl przycisk także dla Modułów, JS i innych typów zawartości (nie tylko wikitext).',
		type: 'boolean',
		deflt: false,
	},
	{
		name: 'useOldRedirFixing',
		desc: '[zaawansowane] Używaj starego sposobu rozwiązywania przekierowań.',
		type: 'boolean',
		deflt: false,
		legacy: [window, 'useOldRedirFixing']
	}
]);

class DisFixer {

disCallApi( request, callback ) {
	request.format = 'json';
	jQuery.post( mw.util.wikiScript( 'api' ), request, callback, 'json' );
}

disScrollToLink (target)
{
	if (typeof disHighlightedLinkTimeout != 'undefined' && typeof disHighlightedLink != 'undefined') {
		clearTimeout(disHighlightedLinkTimeout);
		disHighlightedLink.css('background', '');
	}

	window.disHighlightedLink = $(dis).filter('[title="' + target.replace('"', '\\"') + '"]').first();
	disHighlightedLink.css('background', 'red');

	window.disHighlightedLinkTimeout = setTimeout(function() {
		disHighlightedLink.css('background', '');
	}, 3000);
	disHighlightedLink.get(0).scrollIntoView();
}

/** Init action (main button etc). */
initAction()
{
	// Nie poprawiaj linków w szablonach [[Szablon:Inne znaczenia]], [[Szablon:Przekierowanie]], itp.
	window.dis = $('#mw-content-text a.mw-disambig').not('table.disambig a.mw-disambig').get();
	window.disRedirs = $('#mw-content-text a.mw-redirect').not('table.disambig a.mw-redirect').get();

	//nie ma disambigów, na pewno - nic do roboty
	if (dis.length  ===  0) {
		if (!gConfig.get('disFixer', 'fixIfRedirsOnly') || disRedirs.length  ===  0) {
			return; //możliwość wymuszenia sprawdzania mimo braku disambigów, ale tylko, gdy są rediry
		}
	}

	// fire hook to allow advanced customization
	// usage:
	// mw.hook('userjs.disFixer.actioninit').add(function (disFixer, disambigs, redirs) {...});
	mw.hook('userjs.disFixer.actioninit').fire(this, dis, disRedirs);

	this.disMainButton();

	// main form container
	this.disMainContainer();

	mw.hook('userjs.disFixer.actionready').fire(this, dis, disRedirs);
}

/** Prepare main container. */
disMainContainer()
{
	var main = '<div id="disMainContainer"></div>';
	if(mw.config.get('skin') !== 'vector-2022') $('h1:first').before(main);
	else $('.mw-body-header:first').before(main);
}

/**
 * Prepare header button.
 * 
 * @param {String} title Title/tooltip (with version).
 * 
 * @returns jQuery element. Must be an input element!
 */
disHeaderButton(title)
{
	var el = '<input id="disBeginButton" type="submit" class="disFixerButton" value="" title="'+title+'" />';
	// W Wektorze 2022 <h1> znajduje się w kontenerze typu flex, więc nie wkładaj tam przycisku, żeby nie zaburzyć układu
	if(mw.config.get('skin') !== 'vector-2022') $('h1:first').before(el);
	else $('.mw-body-header:first').append(el);

	var $button = $('#disBeginButton');
	return $button;
}

/** Prepare main button. */
disMainButton()
{
	var txt = disStr.fixLinks;
	if (dis.length === 0) {
		txt = disStr.fixLinksRedir;
	}
	else if (disRedirs.length === 0) {
		txt = disStr.fixLinksDisam;
	}

	var title = 'disFixer v.'+disFixerVersion;

	// header button
	if (gConfig.get('disFixer', 'headerButton')) {
		var $button = this.disHeaderButton(title);
		$button.val(txt);
		$button.click(() => {
			try {
				$button.val(disStr.wait);
				$button.prop('disabled', true);
			} catch(ex){
				console.warn('[disFixer]', 'Click problem', ex);
			}
			this.disBegin(function(hasAllPages) {
				if (!hasAllPages) {
					$button.val(txt);
					$button.prop('disabled', false);
				}
				else {
					$button.remove();
				}
			});
		});
	}
	// button in page tools
	else {
		var portletId = mw.config.get('skin') === 'timeless' ? 'p-pagemisc' : 'p-tb';
		var link = mw.util.addPortletLink(portletId, '#', txt, 't-disbeginaction', null);

		link.title = title;
		var $link = $(link);
		$link.click((e) => {
			e.preventDefault();
			if ($link.data('waiting')) {
				console.warn('[disFixer]', 'Still waiting...');
				return;
			}
			$link.data('waiting', true);
			this.disBegin(function() {
				$link.data('waiting', false);
			});
		});
	}
}

/**
 * 
 * @param {Function?} callback [optional] Callback for main disambig query.
 */
disBegin(callback)
{
	const me = this;

	$('#disMainContainer').empty();

	var el = '<div id="disRedirsStatus"></div>';
	$('#disRedirsStatus').append('<input type="checkbox" id="disFixRedirsCheckbox" style="display:none" />');
	if (window.disRedirs.length === 0) {
		$('#disRedirsStatus').append(disStr.noRedirLinks);
	}
	else {
		$('#disRedirsStatus').append(disStr.fixingInProgress);
	}
	$('#disMainContainer').append(el);

	var titles = [];
	var i;
	for (i = 0; i < dis.length; i++) {
		var pageTitle = dis[i].title;
		if (typeof pageTitle == 'string' && pageTitle.length) {
			// avoid dupls
			if (titles.indexOf(pageTitle) < 0) {
				titles.push(pageTitle);
			}
		}
	}

	// not great when there is more then one link in the list item
	// use action=render instead? (get text as option-label and get only first link as propoused link?)
	this.disCallApi( {
		action: 'query',
		prop: 'links',
		titles: titles.join( '|' ),
		plnamespace: 0,
		pllimit: 'max'
	}, function( data ) {
		var hasAllPages = me.disCallback( data, titles.length );
		if (typeof callback === 'function')	callback(hasAllPages);
	} );

	if (window.disRedirs.length > 0) {
		var titles2 = [];
		for (i = 0; i < disRedirs.length; i++) {
			titles2.push(disRedirs[i].title);
		}

		if (gConfig.get('disFixer', 'useOldRedirFixing')) {
			this.disCallApi( {
				action: 'query',
				redirects: '',
				titles: titles2.join( '|' )
			}, function( data ) {
				me.disRedirCallback( data );
			} );
		}
		else {
			this.disCallApi( {
				action: 'query',
				prop: 'revisions',
				rvprop: 'content',
				titles: titles2.join( '|' )
			}, function( data ) {
				me.disRedirCallback2( data );
			} );
		}
	}
}

/** Create OOUI style submit button */
disSubmitButton(text, danger) {
	var submit = new OO.ui.ButtonInputWidget( {
		label: text,
		useInputTag: true,
		type: "submit",
		flags: [
			'primary',
			(danger ? 'destructive' : 'progressive'),
		],
	} );
	return submit;
}

/**
 * Use data read from disambig article.
 * 
 * @param {Object} res Response from links query API.
 * @returns 
 * 	<li>null upon error
 * 	<li>true if there were all pages in the response.
 * 	<li>false if there were some pages missing in the response.
 */
disCallback(res, expectedCount)
{
	if(!res) return null;
	let reCount = (typeof res.query !== 'object') ? false : Object.values(res.query.pages).length;
	let hasAllPages = true;
	if (!reCount) {
		console.warn('[disFixer]', 'Disamb response seems empty. Api error?', res);
		hasAllPages = false;
	} else if (reCount !== expectedCount) {
		console.warn('[disFixer]', 'Disamb returned less pages then expected. Api error?', res, {reCount, expectedCount});
		hasAllPages = false;
	}
	
	if (reCount || gConfig.get('disFixer', 'fixIfRedirsOnly')) {
		var disContainer = document.createElement('div');
		disContainer.id = 'disContainer';
		this.disPrepareForm(disContainer, reCount ? res.query.pages : null);
		document.querySelector('#disMainContainer').prepend(disContainer);
	}

	return hasAllPages;
}

/** Prepare form based on disamb response. */
disPrepareForm(disContainer, pages) {
	if (!pages && gConfig.get('disFixer', 'fixIfRedirsOnly')) {
		var submit = this.disSubmitButton(disStr.fixRedirsOnly, true);
		$(disContainer).append(submit.$element);
		submit.$element.click(() => {this.disSend()});
	}
	else if (pages) {
		var addScrollToLink = $('#content').get(0).scrollIntoView;

		disContainer.innerHTML = '<div>' + disStr.fixDabs + '</div><dislist></dislist>';
		var list = disContainer.querySelector('dislist');
		for (var i in pages) {
			if (Object.hasOwnProperty.call(pages, i)) {
				var page = pages[i];
				this.disPreparePage(list, page, addScrollToLink);
			}
		}

		// popups enhance
		if (disPopups && disPopups.fn.addTooltip) {
			var anchors = list.querySelectorAll('dislabel a[target="_new"]');
			if (anchors.length) {
				anchors.forEach(function(anchor){
					disPopups.fn.addTooltip(anchor);
				});
			}
		}

		var submit = this.disSubmitButton(disStr.fixButton);
		$(disContainer).append(submit.$element);
		submit.$element.click(() => {this.disSend()});
	}
}
/**
 * Prepare page to select link from disambig.
 * 
 * @example Page object:
 {
 	"pageid": 5388688,
 	"ns": 0,
 	"title": "Charmaine Smith",
 	"links": [{
 			"ns": 0,
 			"title": "Charmaine Smith (bowls)"
 		},
 		{
 			"ns": 0,
 			"title": "Charmaine Smith (związek rugby)"
 		}
 	]
 }
 * @param {Object} page Disamb data.
 */
disPreparePage(list, page, addScrollToLink) {
	// create name + links
	list.insertAdjacentHTML('beforeend', 
		'<dislabel>' + page.title + ' '
		+ '<a href="/w/index.php?title=' + encodeURIComponent(page.title) + '"'
		+ ' title="' + disStr.viewDabPage + '"'
		+ ' target="_new">&#8663;</a>'
		+ (addScrollToLink
			? '<a href="javascript:this.disScrollToLink(\'' + page.title + '\')" title="' + disStr.scrollToLink + '">&#8659;</a>'
			: ''
		)
		+ '</dislabel><disitem data-from="'+page.title+'" />'
	);
	// prepare field
	var titles = [];
	if (Array.isArray(page.links)) {
		titles = page.links.map(p=>p.title).sort();
	}
	// option to remove links
	titles.unshift('[' + disStr.delink + ']');
	var combo = new OO.ui.ComboBoxInputWidget( {
		value: '',
		options: titles.map(title=>({data:title})),
		menu: {
			filterFromInput: true,
			filterMode: 'substring',
		},
	} );
	$( 'disitem:last-of-type', list ).append( combo.$element );	
}

disRedirCallback(res)
{
	if (typeof res.query !== 'object') {
		window.disResolvedRedirs = [];
		console.warn('[disFixer]', 'Unable to get redirects, api error?');
		return;
	}
	window.disResolvedRedirs = res.query.redirects;

	var el = '<input type="checkbox" id="disFixRedirsCheckbox" checked="checked" /> '
		+ '<label for="disFixRedirsCheckbox">' + disStr.fixRedirsCheckbox
		+ ' (' + disResolvedRedirs.length + ')</label>';
	$('#disRedirsStatus').html(el);
	$('#disRedirsStatus').css('color', 'green');
}

disRedirCallback2(res)
{
	if (typeof res.query !== 'object') {
		window.disResolvedRedirs = [];
		console.warn('[disFixer]', 'Unable to get redirects2, api error?');
		return;
	}
	var pages = res.query.pages;
	window.disResolvedRedirs = [];

	for (var i in pages) {
		if (isNaN(i)) continue;

		var f = pages[i].title;
		var t = pages[i].revisions[0]['*'].replace(/^#(?:REDIRECT|TAM|PATRZ|PRZEKIERUJ)\s*\[\[([^\]]+)\]\][\s\S]*$/i, '$1');

		if (t.match(/[<>\[\]|{}\r\n]/)) {
			//coś się pomieszało - tych znaków nie powinno być w tytule strony
			continue;
		}

		window.disResolvedRedirs.push({from:f, to:t});
	}

	var el = '<input type="checkbox" id="disFixRedirsCheckbox" checked="checked" /> '
		+ '<label for="disFixRedirsCheckbox">' + disStr.fixRedirsCheckbox
		+ ' (' + disResolvedRedirs.length + ')</label>';
	$('#disRedirsStatus').html(el);
	$('#disRedirsStatus').css('color', 'green');
}

/** Send fixes to the edit form (doesn't save yet). */
disSend()
{
	var items = $('#disContainer disitem');
	var inputs = $('#disContainer disitem input');
	var toFix = [];
	for (var i = 0; i < inputs.length; i++) {
		var from = items[i].getAttribute('data-from');
		var to = inputs[i].value;
		if (from == to || to == "") continue;
		toFix.push(from + '~' + to);
	}
	createCookie('disFixDis' + mw.config.get('wgPageName'), toFix.join('|'), 0);

	if (typeof disResolvedRedirs != 'undefined' && $('#disFixRedirsCheckbox').prop('checked')) {
		toFix = [];
		for (var i = 0; i < disResolvedRedirs.length; i++) {
			var from = disResolvedRedirs[i].from;
			var to = disResolvedRedirs[i].to;
			toFix.push(from + '~' + to);
		}
		createCookie('disFixRedirs' + mw.config.get('wgPageName'), toFix.join('|'), 0);
	}

	window.location = mw.util.getUrl( mw.config.get('wgPageName'), { action: 'submit', disFixer: 1 } );	// edit, but submit-action works also to disable VE code editor
}

disOnloadEdit(wpSkObject)
{
	var whatIsFixed = [];
	if( $('#wpTextbox1').length === 0 ) {
		// This is possible with the new source editor beta feature; we don't handle it (yet)
		return;
	}
	var str = $('#wpTextbox1').textSelection('getContents');

	str = str.replace(/\r\n/g,'\n').replace(/\s*$/, '');

	if (gConfig.get('disFixer', 'codeCleanup') && typeof wpSkObject !== 'undefined') {
		str = wpSkObject.cleaner(str);
	}
	else {
		str = this.disCleanLinks(str);
	}

	var linksDisam = readCookie('disFixDis' + mw.config.get('wgPageName'));
	var linksRedir = readCookie('disFixRedirs' + mw.config.get('wgPageName'));

	var links = new Array();
	if (linksDisam != null && linksDisam != 'undefined' && linksDisam != '') {
		links = linksDisam.split('|');
		whatIsFixed.push(disStr.dabsShort);
	}
	if (linksRedir != null && linksRedir != 'undefined' && linksRedir != '') {
		links = $.merge(links, linksRedir.split('|'));
		whatIsFixed.push(disStr.redirsShort);
	}

	if (links.length > 0) {
		for (var i = 0; i < links.length; i++) {
			var l = links[i].split('~');
			var from = l[0].replace(/([\/\.\*\+\?\|\(\)\[\]\{\}\\])/g, '\\$1'); //regex escape
			var to = l[1];
			var safe_from = '['+from.substring(0,1).toLowerCase()+from.substring(0,1).toUpperCase()+']'+from.substring(1);

			if (to == '[' + disStr.delink + ']') {
				str = str.replace(new RegExp('\\[\\[(' + safe_from + ')(?:#[^\\]\\|]+|)\\]\\]', 'g'), '$1');
				str = str.replace(new RegExp('\\[\\[' + safe_from + '(?:#[^\\]\\|]+|)\\|([^\\]]+)\\]\\]', 'g'), '$1');
				continue;
			}
			var sh = to.indexOf('#') != -1;
			str = str.replace(new RegExp('\\[\\[(' + safe_from + ')(#[^\\]\\|]+|)\\]\\]', 'g'), '[[' + (sh ? to : to + '$2') + '|$1]]');
			str = str.replace(new RegExp('\\[\\[' + safe_from + '(#[^\\]\\|]+|)\\|([^\\]]+)\\]\\]', 'g'), '[[' + (sh ? to : to + '$1') + '|$2]]');
		}
	}
	str = this.disCleanLinks(str);

	eraseCookie('disFixDis' + mw.config.get('wgPageName'));
	eraseCookie('disFixRedirs' + mw.config.get('wgPageName'));

	if (whatIsFixed.length == 0) {
		return;
	}

	$('#wpTextbox1').textSelection('setContents', str);
	var newval = $('#wpSummary').val() + '[[' + disStr.descriptionPage + '|' + disStr.autosummaryBegin
		+ ' ' + whatIsFixed.join(disStr.and) + ']]' + (gConfig.get('disFixer', 'codeCleanup') ? ', [[WP:SK]]' : '');
	$('#wpSummary').val(newval);
	if (gConfig.get('disFixer', 'markAsMinor')) {
		$('#wpMinoredit').prop('checked', 'checked');
	}
	$('#wpDiff').click();
}

/*
window.disSetLinkTarget = function(el, disName) //helper - for buttons
{
	el = $(el); // to jquery object
	var target = prompt(disStr.setNewLinkTarget, disName);
	if (!target || target.trim() === '') {
		return;
	}

	var opt = '<option value="' + target + '">' + target + '</option>';
	var sel = el.parent().find('select:first');
	sel.append(opt);
	sel.find('option:last').prop('selected', 'selected');
}
*/

// based on Nux's code cleanup
// http://pl.wikipedia.org/wiki/Wikipedysta:Nux/wp_sk.js
disCleanLinks(str)
{
	//najprostszy cleanup, głównie po to, żeby regeksy do poprawy linków mogły być prostsze

	// [[Kto%C5%9B_jaki%C5%9B#co.C5.9B|...]]›[[Ktoś jakiś#coś|...]]
	str = str.replace(/\[\[([^|#\]]*)([^|\]]*)(\||\]\])/g, function(a, name, anchor, end)
	{
		try {
			var name = decodeURIComponent(name);
			var anchor = decodeURIComponent(anchor.replace(/\.([0-9A-F]{2})\.([0-9A-F]{2})/g, '%$1%$2'));
			a = '[[' + name + anchor + end;
		} catch(err){} // błąd na linkach typu [[%]]

		return a.replace(/_/g, ' ');
	});

	//
	// (ro)zwijanie wikilinków
	// [[Link|link]] > [[link]] i [[Link|linka]] > [[link]]a
	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-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');

	return str;
}

}

// http://www.quirksmode.org/js/cookies.html
// modified to use sessionStorage when available
function createCookie(name, value, days)
{
	if(window.sessionStorage)
	{
		if (days < 0) sessionStorage.removeItem(name);
		else sessionStorage[name] = value;
	}
	else
	{
		// fall back to cookies
		var expires = "";
		if (days) {
			var date = new Date();
			date.setTime(date.getTime() + (days * 24 * 60 * 60 * 1000));
			expires = "; expires=" + date.toGMTString();
		}
		document.cookie = escape(name) + "=" + escape(value) + expires + "; path=/";
	}
}

function readCookie(name)
{
	if(window.sessionStorage)
	{
		return (sessionStorage[name]) + ''; //weird Firefox fix / FIXME: is this still needed for anything?
	}
	else
	{
		var nameEQ = escape(name) + "=";
		var ca = document.cookie.split(';');
		for (var i = 0; i < ca.length; i++) {
			var c = ca[i];
			while (c.charAt(0) == ' ') c = c.substring(1,c.length);
			if (c.indexOf(nameEQ)  ==  0) return unescape(c.substring(nameEQ.length,c.length));
		}
		return null;
	}
}

function eraseCookie(name)
{
	createCookie(name, "", -1);
}

// main
let disFixer = new DisFixer();

// export for a JS-link
// TODO: re-write to onclick?
window.disScrollToLink = disFixer.disScrollToLink;

/*
AND FINALLY ONLOAD
*/
var isFirstEdit = location.href.indexOf('disFixer=1') > 0;
if ( mw.config.get( 'wgAction' ) == 'submit' && isFirstEdit ) {
	$( function() {
		if ( gConfig.get('disFixer', 'codeCleanup')) {
			// Wait for WP:SK
			mw.hook('userjs.wp_sk.ready').add(function (wp_sk) {
				disFixer.disOnloadEdit(wp_sk);
			} );
		} else {
			disFixer.disOnloadEdit();
		}
	} );
}

// Do not show interface, when previewing the page or on talk pages
if (
	mw.config.get( 'wgAction' ) != 'submit' && mw.config.get( 'wgNamespaceNumber' ) % 2 == 0
	&& (gConfig.get('disFixer', 'fixAllContentTypes') || mw.config.get( 'wgPageContentModel' ) == "wikitext") // only plain code
	&& mw.config.get( 'wgNamespaceNumber' ) !== 2600 // Flow's 'Topic' namespace
) {
	// Make sure the required dependency is loaded
	mw.loader.using( ['ext.gadget.mark-disambigs-core', 'oojs-ui-core'], function() {
		if ( typeof( markDisambigsGadget ) === "undefined" ) {
			jQuery( document ).ready( function() {
				jsMsg( disStr.markDisambigsMissing );
			} );
		} else {
			// register callback
			markDisambigsGadget.addCallback( function() {
				disFixer.initAction();
			} );
		}
	} );
}

}); // end mw.loader: ext.gadget.gConfig, jquery.textSelection
// </nowiki>