MediaWiki:Gadget-LinkSuggest.js

De ARK Wiki
Ir a la navegación Ir a la búsqueda

En otros idiomas: DeutschEnglishFrançaisItaliano日本語PolskiPortuguês do BrasilРусский


Los cambios de CSS y Javascript deben cumplir con las normas de diseño del wiki.


Nota: tras guardar, quizás necesites actualizar la caché de tu navegador para ver los cambios.

  • Firefox/Safari: Mantén presionada la tecla Mayús mientras pulsas el botón Actualizar, o presiona Ctrl+F5 o Ctrl+R (⌘+R en Mac)
  • Google Chrome: presiona Ctrl+Shift+R (⌘+Mayús+R en Mac)
  • Internet Explorer: mantén presionada Ctrl mientras pulsas Actualizar, o presiona Ctrl+F5
  • Opera: vacía la caché en Herramientas → Preferencias
/*
 * jQuery MediaWiki LinkSuggest 1.9
 * JavaScript for LinkSuggest extension
 * https://github.com/wikimedia/mediawiki-extensions-LinkSuggest
 *
 * Copyright © 2010-2015
 * Authors: Inez Korczyński (korczynski at gmail dot com)
 *          Jesús Martínez Novo (martineznovo at gmail dot com)
 * Licensed under the GPL license.
 *
 * Adapted as a gadget for ARK Wiki.
 */
mw.loader.using(["mediawiki.api", "jquery.ui.autocomplete"], function() {
	'use strict';

	// Private static variables
	var testerElement = null, // Tester element, global for caching and not recreate it everytime
		cachedElement = null, // Cached element of the autocomplete to compare
		cachedText = null, // Cached text before the position of the link
		cachedPosition = null, // Cached position of the drop-down
		suppressKeyPress, suppressInput, suppressKeyPressRepeat;
console.log('a');
	$.widget( 'mw.linksuggest', {
		options: {
			minLength: 3,
			delay: 300
		},
		_create: function () {
			var self = this, ac,
				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 );
					}
				};
			// Opera only prevents default behavior on keypress, needed for
			// capturing arrows and enter
			this.options = $.extend( opt, this.options );
			this.element.autocomplete( this.options );
			ac = this.element.data( 'autocomplete' );
			// Overwrite the keydown event of autocomplete to fix some undesired key events
			ac._off( this.element, 'keydown keypress' );
			this._on( this.element, {
				keydown: ( function ( thisInstance ) {
					return function () {
						thisInstance._keydown.apply( thisInstance, arguments );
					};
				}( this ) ),
				keypress: function ( event ) {
					if ( suppressKeyPress ) {
						suppressKeyPress = false;
						event.preventDefault();
					}
					if ( suppressKeyPressRepeat ) {
						return;
					}
				}
			} );
			// deactivate some menu weird behavior
			ac.menu.options.blur = null;
		},
		// function copied from jQuery UI Autocomplete 1.9.2. Keep in sync with the same used in MediaWiki
		_legacyKeydown: function ( event ) {
			var keyCode;
			if ( this.element.prop( 'readOnly' ) ) {
				suppressKeyPress = true;
				suppressInput = true;
				suppressKeyPressRepeat = true;
				return;
			}

			suppressKeyPress = false;
			suppressInput = false;
			suppressKeyPressRepeat = false;
			keyCode = $.ui.keyCode;
			switch ( event.keyCode ) {
				case keyCode.PAGE_UP:
					suppressKeyPress = true;
					this._move( 'previousPage', event );
					break;
				case keyCode.PAGE_DOWN:
					suppressKeyPress = true;
					this._move( 'nextPage', event );
					break;
				case keyCode.UP:
					suppressKeyPress = true;
					this._keyEvent( 'previous', event );
					break;
				case keyCode.DOWN:
					suppressKeyPress = true;
					this._keyEvent( 'next', event );
					break;
				case keyCode.ENTER:
				case keyCode.NUMPAD_ENTER:
				// when menu is open and has focus
					if ( this.menu.active ) {
					// #6055 - Opera still allows the keypress to occur
					// which causes forms to submit
						suppressKeyPress = true;
						event.preventDefault();
						this.menu.select( event );
					}
					break;
				case keyCode.TAB:
					if ( this.menu.active ) {
						this.menu.select( event );
					}
					break;
				case keyCode.ESCAPE:
					if ( this.menu.element.is( ':visible' ) ) {
						this._value( this.term );
						this.close( event );
						// Different browsers have different default behavior for escape
						// Single press can mean undo or clear
						// Double press in IE means clear the whole form
						event.preventDefault();
					}
					break;
				default:
					suppressKeyPressRepeat = true;
					// search timeout should be triggered before the input value is changed
					this._searchTimeout( event );
					break;
			}
		},
		_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;
				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;
			}
			// If we've not returned already from this function, fire the old autocomplete handler
			this._legacyKeydown.apply( this.element.data( 'autocomplete' ), arguments );
		},
		_sendQuery: function ( request, response ) {
			var emptyset = [],
				text = this._getText(),
				caret = this._getCaret(),
				sQueryStartAt = -1,
				sQueryReal = '',
				format = '',
				stripPrefix = false,
				i, c, c1, api;

			// Look forward, to see if we closed this one
			for ( i = caret; i < text.length; i++ ) {
				c = text.charAt( i );
				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 ( i = caret - 1; i >= 0; i-- ) {
				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 ) == ':' ) {
						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 = '{{subst::$1}}';
						} else {
							sQueryReal = 'Template:' + sQueryReal.substr( 6 );
							stripPrefix = true;
							format = '{{subst:$1}}';
						}
					} else if ( sQueryReal.charAt( 0 ) == ':' ) {
						sQueryReal = sQueryReal.substr( 1 );
						format = '{{:$1}}';
					} else {
						sQueryReal = 'Template:' + sQueryReal;
						stripPrefix = true;
						format = '{{$1}}';
					}
					sQueryStartAt = i;
					break;
				}
			}

			if ( sQueryStartAt >= 0 && sQueryReal.length >= this.options.minLength ) {
				api = new mw.Api();
				api.get( {
					action: 'opensearch',
					search: sQueryReal,
					suggest: ''
				} ).done( this._responseWrapper( this, response, format, stripPrefix ) );
				return true;
			}
			response( emptyset );
			return false;
		},
		_responseWrapper: function ( thisArg, callback, format, stripPrefix ) {
			return function ( data ) {
				if ( !data || data.error ) {
					return callback( [] );
				}
				callback( thisArg._formatResponse( data, format, stripPrefix ) );
			};
		},
		_formatResponse: function ( data, format, stripPrefix ) {
			return $.map( data[1], function ( n ) {
				if ( stripPrefix ) {
					var pos = n.indexOf( ':' );
					if ( pos != -1 ) {
						n = n.substr( pos + 1 );
					}
				}
				return { label: n, value: format.replace( '$1', n ) };
			} );
		},
		_updateValue: function ( oItem ) {
			this.element[ 0 ].focus();

			var scrollTop = this.element[ 0 ].scrollTop,
				text = this._getText(),
				caret = this._getCaret(),
				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 ),
				newVal = textBefore + oItem.value + text.substr( caret );
			this.element.val( newVal );

			this._setCaret( textBefore.length + oItem.value.length );
			this.element[ 0 ].scrollTop = scrollTop;
		},
		_getCaret: function () {
			var caretPos = 0,
				control = this.element[ 0 ];
			// IE Support
			if ( document.selection && document.selection.createRange ) {
				control.focus();
				var sel = document.selection.createRange(),
					sel2 = sel.duplicate();
				sel2.moveToElementText( control );
				caretPos = -1;
				while ( sel2.inRange( sel ) ) {
					sel2.moveStart( 'character' );
					caretPos++;
				}
				// Firefox support
			} else if ( control.selectionStart || control.selectionStart == '0' ) {
				caretPos = control.selectionStart;
			}
			return caretPos;
		},
		_getText: function () {
			if ( document.selection && document.selection.createRange ) {
				return this.element.val();
			}
			// jQuery.val() removes \n, we need them so we get the caret position
			// correctly. That does not apply to document.selection
			return this.element[ 0 ].value;
		},
		_setCaret: function ( pos ) {
			var control = this.element[ 0 ];
			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 () {
			var result = [ 0, 0 ],
				control = this.element[ 0 ],
				text = this._getText(),
				caret = this._getCaret(),
				initialCaret = caret,
				i, c, textBeforePosition, props, caretElem, pos;

			if ( caret === 0 ) {
			// This should never happen
				return result;
			}
			// Get the position at the start of the link/template
			for ( i = caret - 1; i >= 0; i-- ) {
				c = text.charAt( i );
				if ( c == '[' || c == '{' ) {
					initialCaret = i + 1;
					break;
				}
			}
			textBeforePosition = text.substr( 0, initialCaret );
			// If the control isnot the same, clear the cached tester element
			if ( cachedElement !== control ) {
				cachedElement = control;
				if ( testerElement !== null ) {
					testerElement.remove();
					testerElement = null;
				}
			}
			// Use the cached tester element. Improves speed
			if ( testerElement === null ) {
				testerElement = $( '<div style="position:absolute;top:-2000px;left:-2000px;white-space:pre-wrap;visibility:hidden;">' );
				// Create a tester container to get the size of the text before the caret, and thus the position inside the element
				// WARNING: You MUST apply a font-family CSS attribute to the textarea (to this particular one, or a generic
				// `textarea {font-famly: whatever;}´) so IE could retrieve the correct font-family used, otherwise it may
				// fail to position the drop-down correctly!
				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 ( i = 0; i < props.length; i++ ) {
					testerElement.css( props[ i ], this.element.css( props[ i ] ) );
				}
			} else {
				// If the element and the text is the same, return the cached results
				if ( cachedText === textBeforePosition ) {
					return cachedPosition;
				}
			}
			// An element that will provide the caret position
			caretElem = $( '<span>' ).text( text.substring( initialCaret, caret ) );
			// Using scrollWidth because if the textarea has scroll, the effective
			// width for word wrap doesn't include the width used by the scrollbar
			testerElement
				.width( control.scrollWidth )
				.text( textBeforePosition )
				.append( caretElem )
				.appendTo( document.body );
			pos = caretElem.position();
			result = [ pos.left, pos.top + caretElem.height() - control.scrollTop ];
			// Store in the cache
			cachedText = textBeforePosition;
			cachedPosition = result;
			return result;
		},
		_open: function ( event, ui ) {
			var menu = this.element.data( 'autocomplete' ).menu.element,
				offset = this._getCaretPosition(),
				width = menu.outerWidth(),
				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 );
		}

	} );

    // Implementation: This should be done injecting this code into MediaWiki, not in this JS file
    $( function () {
	    $( '#wpTextbox1' ).linksuggest();
    } );

} );