User:Ciencia Al Poder/jquery.mw.linksuggest.js

/* * jQuery MediaWiki LinkSuggest 1.0.0 * JavaScript for LinkSuggest extension * * Copyright (c) 2010 * Authors: Inez Korczynski (korczynski at gmail dot com) *         Jesús Martínez Novo (martineznovo at gmail dot com) * Licensed under the GPL (GPL-LICENSE.txt) license. * * Depends: *	jquery.ui.autocomplete.js */ (function( $ ) {

$.widget( "mw.linksuggest", {	options: {		minLength: 3,		delay: 300,		url: window.wgScriptPath+window.wgScript	},	_create: function {		var self = this;		var opt = {			source: function {				self._sendQuery.apply( self, arguments );			},			focus: function {				// prevent value inserted on focus				return false;			},			select: function( event, ui ) {				self._updateValue( ui.item );				// prevent value inserted on select				return false;			},			open: function {				self._open.apply( self, arguments );			}		};		this.options = $.extend( opt, this.options );		this.element.autocomplete( this.options );		// Overwrite the keydown event of autocomplete to fix some undesired key events		this._legacykeydown = this.element.data( 'events' ).keydown[0].handler;		this.element.unbind( 'keydown.autocomplete' )		.bind( 'keydown.linksuggest', function( thisInstance ) { return function { thisInstance._keydown.apply(thisInstance, arguments); };		}( this ));		// deactivate some menu weird behavior		this.element.data( 'autocomplete' ).menu.options.blur = null;	},	_legacykeydown: null,	_keydown: function( event ) {		var keyCode = $.ui.keyCode;		switch( event.keyCode ) {			case keyCode.UP:			case keyCode.DOWN:				if ( !this.element.data( 'autocomplete' ).menu.element.is( ':visible' ) ) {					// If menu element is not visible, ignore. Autocomplete event handler just prevents default behavior, which is not what we want					return;				}				break;			case keyCode.TAB:				// don't navigate away from the field on tab when selecting an item				if ( this.element.data( 'autocomplete' ).menu.active ) {					event.preventDefault;				}				break;			case keyCode.ESCAPE:				// return without setting any value				this.element.data( 'autocomplete' ).close( event );				return;				break;			case keyCode.PAGE_UP:			case keyCode.PAGE_DOWN:			case keyCode.LEFT: case keyCode.RIGHT: case keyCode.SHIFT: case keyCode.CONTROL: case keyCode.ALT: case keyCode.COMMAND: case keyCode.COMMAND_RIGHT: case keyCode.INSERT: case keyCode.CAPS_LOCK: case keyCode.END: case keyCode.HOME: // ignore metakeys (shift, ctrl, alt) return break; }		// If we not already returned from this function, fire the old autocomplete handler if ( $.isFunction( this._legacykeydown ) ) { this._legacykeydown.apply( this.element.data( 'autocomplete' ), arguments ) }	},	_getCaret: function( control ) { var caretPos = 0; // IE Support if ( $.browser.msie ) { control.focus; var sel = document.selection.createRange; var sel2 = sel.duplicate; sel2.moveToElementText( control ); var caretPos = -1; while( sel2.inRange( sel ) ) { sel2.moveStart( 'character' ); caretPos++; }		// Firefox support } else if ( control.selectionStart || control.selectionStart == '0' ) { caretPos = control.selectionStart; }		return caretPos; },	_sendQuery: function( request, response ) { var emptyset = []; var text = this.element.val.replace( /\r/g, '' ); var caret = this._getCaret( this.element[0] ); var sQueryStartAt = -1; var sQueryReal = ''; var format = ''; var stripPrefix = false;

// Look forward, to see if we closed this one for( var i = caret; i < text.length; i++ ) { var c = text.charAt( i ); var c1 = ( i > 0 ? text.charAt( i - 1 ) : '' ); // A line break, it isn't closed if ( c == '\n' ) { break; }			// A start of a link, so this link isn't closed if( c == '[' && c1 == '[' ) { break; }			// A closing link and this was a link, exit if( c == ']' && c1 == ']' ) { response ( emptyset ); return false; }			// A start of a template, so this template isn't closed if( c == '{' && c1 == '{' ) { break; }			// A closing template and this was a template, exit if( c == '}' && c1 == '}' ) { response ( emptyset ); return false; }		}

// Get the start of the link/template for( var i = caret - 1; i >= 0; i-- ) { var c = text.charAt( i ); // If nothing found after a line break, nothing to match if ( c == '\n' ) { break; }			// Closed link/template, a pipe or a hash. There's no link/template to complete, or we're on a parser function or link hash if( c == ']' || c == '}' || c == '|' || c == '#' ) { response ( emptyset ); return false; }

// It's an open link if( c == '[' && i > 0 && text.charAt( i - 1 ) == '[' ) { sQueryReal = text.substr( i + 1, ( caret - i - 1 ) ); if( sQueryReal.charAt( 0 ) == ':' ) { this._bIsColon = true; sQueryReal = sQueryReal.substr( 1 ); format = '$1'; } else { format = '$1'; }				sQueryStartAt = i;				break; }

// It's an open template if( c == '{' && i > 0 && text.charAt( i - 1 ) == '{' ) { // Exclude template parameters if ( i > 1 && text.charAt( i - 2 ) == '{' ) { response ( emptyset ); return false; }				sQueryReal = text.substr( i + 1, ( caret - i - 1 ) ); if( sQueryReal.length >= 6 && sQueryReal.toLowerCase.substr( 0, 6 ) == 'subst:' ) { if ( sQueryReal.length >= 7 && sQueryReal.charAt( 6 ) == ':' ) { sQueryReal = sQueryReal.substr( 7 ); format = ''; } else { sQueryReal = 'Template:' + sQueryReal.substr( 6 ); stripPrefix = true; format = ''; }				} else if( sQueryReal.charAt( 0 ) == ':' ) { sQueryReal = sQueryReal.substr( 1 ); format = ''; } else { sQueryReal = 'Template:' + sQueryReal; stripPrefix = true; format = ''; }				sQueryStartAt = i;				break; }		}

if( sQueryStartAt >= 0 && sQueryReal.length > this.options.minLength ) { $.get( this.options.url, {action:'ajax', rs: 'getLinkSuggest', query: sQueryReal}, this._responseWrapper( this, response, format, stripPrefix ) ); return true; }		response ( emptyset ); return false },	_responseWrapper: function( thisArg, callback, format, stripPrefix ) { return function( data ) { callback( thisArg._fomatResponse( data, format, stripPrefix ) ); };	},	_fomatResponse: function( data, format, stripPrefix ) { var elements = $.map( data.split( '\n' ), function( n ) {			if ( stripPrefix ) {				var pos = n.indexOf( ':' );				if ( pos >= 0 ) {					n = n.substr( pos + 1 );				}			}			return { label: n, value: format.replace( '$1', n ) };		} ); return elements; },	_updateValue: function( oItem ) { this.element[0].focus;

var scrollTop = this.element[0].scrollTop; var text = this.element.val.replace( /\r/g, '' ); var caret = this._getCaret( this.element[0] ); var prefix = oItem.value.substr( 0, 2 );

for( var i = caret - 2; i >= 0; i-- ) { // break for templates and normal links if ( text.substr(i, 2) == prefix ) { break; }		}

var textBefore = text.substr( 0, i ); var newVal = textBefore + oItem.value + text.substr( caret ); this.element.val( newVal );

this._setCaret( this.element[0], textBefore.length + oItem.value.length ); this.element[0].scrollTop = scrollTop; },	_setCaret: function( control, pos ) { if( control.setSelectionRange ) { control.focus; control.setSelectionRange( pos, pos ); } else if( control.createTextRange ) { var range = control.createTextRange; range.collapse( true ); range.moveEnd( 'character', pos ); range.moveStart( 'character', pos ); range.select; }	},	_getCaretPosition: function( control ) { var left = 0; var top = 0; var tester = $(' '); var $ctrl = $(control); var caret = this._getCaret( control ); var initialcaret = caret; if (caret == 0) { // This should never happen return [ 0, $ctrl.outerHeight ]; }		// Get the position at the start of the link/template for (var i = caret - 1; i >= 0; i--) { var c = control.value.substr(i, 1); if ( c == '[' || c == '{' ) { initialcaret = i;				break; }		}		// Create a tester container to get the size of the text before the caret, and thus the position inside the element var props = 'padding-top padding-right padding-bottom padding-left border-top-style border-right-style border-bottom-style border-left-style border-top-width border-right-width border-bottom-width border-left-width font-size font-family font-weight line-height'.split(' '); for (var i = 0; i < props.length; i++) { tester.css( props[i], $ctrl.css( props[i] ) ); } 		// Using clientWidth because if the textarea has scroll, the effective width for word wrap doesn't include the width used by the scrollbar tester.width( control.clientWidth ).appendTo( document.body ).text( $ctrl.val.substr( 0, caret ) ); left = tester.outerWidth; top = tester.outerHeight - control.scrollTop; var initialheight = tester.height; var paddingText = ''; // Insert the text until the initial position of the element we want to suggest, plus a space, to get the characters needed to force a word wrap to a new line tester.text( $ctrl.val.substr( 0, initialcaret ) + ' ' ); if ( tester.height < initialheight ) { // If the height has been reduced then the element to suggest is forcing a word wrap to a new line and it's on the left side of the textarea left = 0; } else { // Get how many characters we need to force a word wrap to a new line, and then transform it in an actual size // If we go beyond 500, something must be wrong for (var i = 1; i < 500; i++) { paddingText += 'A'; tester[0].firstChild.appendData( 'A' ); if (tester.height > initialheight) { tester.css( 'width', 'auto' ); tester.text(paddingText); left -= tester.outerWidth; break; }			}		}		tester.remove; return [ left, top ]; },	_open: function( event, ui ) { var menu = this.element.data( 'autocomplete' ).menu.element; var offset = this._getCaretPosition( this.element[0] ); var width = menu.outerWidth; var props = { my: 'left top', at: 'left top', of: this.element, offset: offset.join(' '), collision: 'fit none' }; if ( offset.left + width > this.element.outerWidth ) { props.my = 'right top'; }		menu.width( '' ).position( props ); }

});

}( jQuery ));

$(function {	$( "#wpTextbox1" ).linksuggest; });