MediaWiki:Gadget-nuxTBKeys.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.
/* ------------------------------------------------------------------------ *\
	copyright:  ©2010-2014 Maciej Jaros (pl:User:Nux, en:User:EcceNux)
	license:
				GNU General Public License v2,
                http://opensource.org/licenses/gpl-license.php
			OR
				CC-BY-SA
				http://creativecommons.org/licenses/by-sa/3.0/deed.pl

	Libraries used:
		* jQuery (keypress, preventDefault)
		* sel_t
		* wikibits

	Best <del>viewed</del> edited in Netbeans 7.3+. ;-)
	Jest be sure to add:
	-J-Dfile.encoding=UTF-8
	in [netbeans installation dir]\etc\netbeans.conf
	in netbeans_default_options
\* ------------------------------------------------------------------------ */

/**
 * Text box keys listener class that allows to add actions for certain key combinations.
 *
 * @param {String} textBoxId Id of the text box.
 * @returns {nuxTBKeys}
 */
function nuxTBKeys(textBoxId)
{
	/** @type nuxTBKeys */
	var _this = this;

	/**
	 * Version - just for show ;-)
	 * @type String
	 */
	this.ver = this.version = '1.0.3';

	// Colectors and helpers
	var $input, input;				// jQuery and DOM input
	var pressedCharsBuffer = '';	// pressed chars buffer
	var interestingChars = '';		// chars of interest within the script
	var maxChars = 0;				// max chars that might be interesting for the script
	var isDisabled = false;

	/**
	 * Replacements array (filled by addReplacements).
	 *
	 * @type Array
	 */
	var arrReplacements = [];

	/**
	 * Escape string before creating a RegExp.
	 * 
	 * @param {String} str String to be used in new RegExp.
	 * @returns {String}
	 */
	var escapeStr4RegExp = function(str)
	{
		return str.replace(/([\[\]\{\}\|\.\*\?\(\)\$\^\\])/g,'\\$1');
	};

	/**
	 * Adds a single replacement rule, should be called before init()
	 *
	 * Note: rules added at the begining will be checked first.
	 * Also: rule match will prevent any following from matching.
	 *
	 * from - string to be replaced
	 * to - string to insert instead
	 * beforeForbidden - string that must not be appear just before or text before nust not match: `{re:/.../,maxCheck:123}`
	 * beforeMustHave - string that must appear just before or text before must match `{re:/.../,maxCheck:123}`
	 * use `$` in `re` to match just before string to be replaced
	 *
	 * @param {type} atr
	 * @returns {undefined}
	 */
	this.addReplacements = function(atr/*{from, to}*/)
	{
		// add the replacement rule with some precalculated attributes
		var atrRewritten = jQuery.extend({}, atr);
		atrRewritten.from = atr.from.substr(0, atr.from.length-1);
		arrReplacements[arrReplacements.length] = {
			chLastChar : atr.from.charAt(atr.from.length-1),
			reMatch : new RegExp(escapeStr4RegExp(atr.from)+'$'),
			objReplaceAtrs : atrRewritten
		};

		// re-calculte some main attributes
		if (maxChars<atr.from.length)
		{
			maxChars=atr.from.length;
		}
		for (var i=0; i<atr.from.length; i++)
		{
			var ch = atr.from.charAt(i);
			if (interestingChars.indexOf(ch)===-1)
			{
				interestingChars += ch;
			}
		}
	};

	/**
	 * Init must be run after the text box is available
	 * @returns {unresolved}
	 */
	this.init = function()
	{
		if (!jQuery)
			return;

		var _this = this;

		$input = jQuery('#'+textBoxId);
		input = document.getElementById(textBoxId);

		// main collector
		$input.keypress(function(evt) {onKeyPress(evt);});
		// remove anything collected on the way (so that moving a cursor with a mouse wouldn't cause anything weird to happen)
		$input.click(function(evt) {pressedCharsBuffer = '';});
	};

	/**
	 * Enable/disable listener.
	 */
	this.toggleDisablance = function()
	{
		isDisabled = !isDisabled;
	};
	/**
	 * Set disablance of the listener.
	 *
	 * @param {Boolean} is2BeDisabled
	 */
	this.setDisablance = function(is2BeDisabled)
	{
		isDisabled = is2BeDisabled;
	};
	/**
	 * Get disablance state of the listener.
	 *
	 * @return {Boolean} Current state.
	 */
	this.getDisablance = function()
	{
		return isDisabled;
	};

	/**
	 * Collecting and checking characters.
	 *
	 * @param {Event} evt KeyPress event data
	 */
	var onKeyPress = function(evt)
	{
		// no rules added or disabled?
		if (arrReplacements.length<=0 || isDisabled)
		{
			return;
		}

		//
		// collect chars
		//
		var ch;
		// normal key? (for a PgDown issue in FF)
		if (typeof(evt.which)=='undefined' || evt.which)
		{
			ch = evt.charCode || evt.keyCode;
		}
		else
		{
			return;
		}
		ch = String.fromCharCode(ch);
		pressedCharsBuffer += ch;

		//
		// check for matches
		//
		for (var i=0; i<arrReplacements.length; i++)
		{
			// single replacement rule check
			var repRule = arrReplacements[i];
			if (ch==repRule.chLastChar && pressedCharsBuffer.search(repRule.reMatch)!=-1)
			{
				if (replaceLatestStr(repRule.objReplaceAtrs, ch))	// note ch is not out yet!
				{
					evt.preventDefault();	// prevent output of ch
					break;	// prevent other replacements
				}
			}
		}

		//
		// Check for finish
		//
		// overflow?
		if (pressedCharsBuffer.length>maxChars)
		{
			pressedCharsBuffer = pressedCharsBuffer.substr(1);	// cut out least important character
		}
		// unneeded char
		if (interestingChars.indexOf(ch)==-1)
		{
			pressedCharsBuffer = '';
		}
	};

	/**
	 * Replace text that was just put in the input from `atr.from` to `atr.to`.
	 *
	 * @param {type} atr
	 * @param {type} chLastChar
	 * @returns {Boolean} true if replaced
	 */
	var replaceLatestStr = function(atr/*{from, to}*/, chLastChar)
	{
		// get current position
		var startScroll = input.scrollY;
		var bounds = sel_t.getSelBound(input);

		/**
		 * Get string just before match.
		 */
		var getStringBeforeMatch = function(numOfChars)
		{
			var start = bounds.end - atr.from.length - numOfChars;
			var length = numOfChars;
			if (start < 0) {
				start = 0;
				length = bounds.end - atr.from.length;
			}
			return input.value.substr(start, length);
		};

		// check if the selected text would be correct
		var strBeforeCursor = input.value.substr(bounds.end - atr.from.length, atr.from.length);
		if (strBeforeCursor != atr.from)	// something went wrong (probably cursor jumped somehow)
		{
			return false;
		}

		// bail out when matched (mostly for quotes)
		if ('beforeForbidden' in atr)
		{
			var o = atr.beforeForbidden;
			if (typeof(o) === 'string')
			{
				if (o == getStringBeforeMatch(o.length))
				{
					return false;
				}
			}
			// `{re:/.../,maxCheck:123}`
			else
			{
				if (o.re.test(getStringBeforeMatch(o.maxCheck)))
				{
					return false;
				}
			}
		}
		// bail out when NOT matched (mostly for quotes)
		if ('beforeMustHave' in atr)
		{
			var o = atr.beforeMustHave;
			if (typeof(o) === 'string')
			{
				if (o != getStringBeforeMatch(o.length))
				{
					return false;
				}
			}
			// `{re:/.../,maxCheck:123}`
			else
			{
				if (!o.re.test(getStringBeforeMatch(o.maxCheck)))
				{
					return false;
				}
			}
		}

		// add last character to enable undo (note: it might replace selected text)
		sel_t.setSelStr(input, chLastChar, false);
		//bounds = sel_t.getSelBound(input);	// re-read bounds	// don't work as expected on Opera (Opera seems to reset bounds to the end of the whole text of the input)
		bounds.start += chLastChar.length;
		bounds.end = bounds.start;

		// prepare new bounds
		bounds.start = bounds.end - atr.from.length - chLastChar.length;
		if (bounds.start<0)		// probably interpretation error
		{
			return false;
		}

		// select chars to be replaced
		sel_t.setSelBound(input, bounds, false);

		// replace
		sel_t.setSelStr(input, atr.to, false);

		// reset cursor to the end of inserted text
		//bounds = sel_t.getSelBound(inp);	// don't work as expected on IE (IE seems to reset bounds to a start of the selection)
		bounds.start += atr.to.length;
		bounds.end = bounds.start;
		sel_t.setSelBound(input, bounds, false);

		// reset position
		input.scrollY = startScroll;
		return true;
	};
}

// ----------------------------------------------------------------
//
// Autokorekta (pl)
//

//
// init object
//
window.oAutokorekta = new nuxTBKeys('wpTextbox1');
// minus/dywiz na myślnik
oAutokorekta.addReplacements ({from:' - ', to:' – '});
// arrows
oAutokorekta.addReplacements ({from:' -> ', to:' → '});
oAutokorekta.addReplacements ({from:' <- ', to:' ← '});
// quotes
oAutokorekta.addReplacements ({from:' "', to:' „'});
oAutokorekta.addReplacements ({from:'"', to:'”', 
	beforeForbidden:'=', beforeMustHave:{re:/„[^"”]*$/, maxCheck:5000}
});

/**
 * Button settings
 */
oAutokorekta.oImgbtn = {
	on : {
		src : '//upload.wikimedia.org/wikipedia/commons/thumb/7/77/Vista-clean.png/22px-Vista-clean.png',
		alt : 'włączona',
		title : 'Autokorekta jest włączona'
	},
	off : {
		src : '//upload.wikimedia.org/wikipedia/commons/thumb/6/65/Gnome-window-close.svg/22px-Gnome-window-close.svg.png',
		alt : 'wyłączona',
		title : 'Autokorekta jest wyłączona'
	}
};

/**
 * Button adder
 * @returns {Boolean}
 */
oAutokorekta.addDisableButon = function()
{
	// add a group in a standard way
	jQuery('#wpTextbox1').wikiEditor('addToToolbar', {
		'section': 'advanced',
		'groups': {
			'autorepcontrol': {
				'label': 'Autokorekta'
			}
		}
	});

	// get added group
	var elGroup = $("#wikiEditor-ui-toolbar div.section-advanced div.group-autorepcontrol")[0];
	if (!elGroup)
	{
		return false;
	}
	// add button
	var nel = document.createElement('img');
	if (!oAutokorekta.getDisablance())
	{
		nel.src = oAutokorekta.oImgbtn.on.src;
		nel.alt = oAutokorekta.oImgbtn.on.alt;
		nel.title = oAutokorekta.oImgbtn.on.title;
	}
	else
	{
		nel.src = oAutokorekta.oImgbtn.off.src;
		nel.alt = oAutokorekta.oImgbtn.off.alt;
		nel.title = oAutokorekta.oImgbtn.off.title;
	}
	nel.className = "tool tool-button";
	nel.style.cssText = "width: 22px; height: 22px; padding-top:2px";
	nel.onclick = oAutokorekta.onClickDisableBtn;
	elGroup.appendChild(nel);
	// show group
	elGroup.style.display = '';
	// border
	elGroup.style.cssText = "border-left: 1px solid #DDDDDD";
	
	// redraw
	oAutokorekta.redrawWikiEditorGroup('advanced', 'autorepcontrol');

	// seems OK
	return true;
};

/**
 * Redraw Group after manipulation
 */
oAutokorekta.redrawWikiEditorGroup = function(section, group) {
	// add whatever button
	$( '#wpTextbox1' ).wikiEditor( 'addToToolbar', {
		'section': section,
		'group': group,
		'tools': {
			'smile': {
				label: 'Smile!',
				type: 'button',
				icon: '//upload.wikimedia.org/wikipedia/commons/thumb/a/a4/Gnome-face-smile.svg/22px-Gnome-face-smile.svg.png',
				action: {
					type: 'encapsulate',
					options: {
						pre: ":)" // text to be inserted
					}
				}
			}
		}
	} );
	// remove whatever button
	$( '#wpTextbox1' ).wikiEditor( 'removeFromToolbar', {
		'section': section,
		'group': group,
		'tool': 'smile'
	});
}

/**
 * Button click.
 */
oAutokorekta.onClickDisableBtn = function()
{
	oAutokorekta.toggleDisablance();
	if (this.alt == oAutokorekta.oImgbtn.on.alt)
	{
		this.src = oAutokorekta.oImgbtn.off.src;
		this.alt = oAutokorekta.oImgbtn.off.alt;
		this.title = oAutokorekta.oImgbtn.off.title;
		jQuery.cookie("js_oAutokorekta_setup", "off", { expires: 7 });
	}
	else
	{
		this.src = oAutokorekta.oImgbtn.on.src;
		this.alt = oAutokorekta.oImgbtn.on.alt;
		this.title = oAutokorekta.oImgbtn.on.title;
		jQuery.cookie("js_oAutokorekta_setup", "on", { expires: 7 });
	}
	// re-set focus
	try
	{
		input.focus();
	}
	catch (e) {}
};

/**
 * Sets up the gadget.
 */
oAutokorekta.setup = function()
{
	// Cookie setup stuff
	var js_oAutokorekta_setup = '';
	if (document.cookie.search(/(^|[ ;])js_oAutokorekta_setup=/) != -1)
	{
		document.cookie.replace(/(^|[ ;])js_oAutokorekta_setup=(.*?)(;|$)/, function( a, s, val, e )
		{
			js_oAutokorekta_setup = val;
		});
		// re-set cookie
		jQuery.cookie("js_oAutokorekta_setup", js_oAutokorekta_setup, {expires: 7});
	}
	// setup default state
	if (js_oAutokorekta_setup != 'on') // defaults to off
	{
		oAutokorekta.setDisablance(true);
	}

	if (mw.config.get('wgNamespaceNumber' ) < 0)
	{
		return;
	}

	// Check if the new toolbar is available
	if (mw.user.options.get( 'usebetatoolbar' ) !== 1) {
		return;
	}

	mw.loader.using("ext.wikiEditor", function()
	{
		$(function()
		{
			// wyłączone przez użytkownika
			if (window.isAutokorektaWylaczona)
			{
				return;
			}
			if (oAutokorekta.addDisableButon() || window.isAutokorektaAlwaysOn)
			{
				oAutokorekta.init();
			}
		});
	});
};

if (mw.config.get( 'wgAction' ) == 'edit' || mw.config.get( 'wgAction' ) == 'submit')
{
	oAutokorekta.setup();
}