Ir al contenido

MediaWiki:Gadget-ProveIt.js

De Wikipedia, la enciclopedia libre
Esta es una versión antigua de esta página, editada a las 05:31 2 jul 2014 por XanaG (discusión · contribs.). La dirección URL es un enlace permanente a esta versión, que puede ser diferente de la versión actual.

Nota: Después de guardar, debes refrescar la caché de tu navegador para ver los cambios. Internet Explorer: mantén presionada Ctrl mientras pulsas Actualizar. Firefox: mientras presionas Mayús pulsas el botón Actualizar, (o presiona Ctrl-Shift-R). Los usuarios de Google Chrome y Safari pueden simplemente pulsar el botón Recargar. Para más detalles e instrucciones acerca de otros exploradores, véase Ayuda:Cómo limpiar la caché.

/**
 * ProveIt is a powerful GUI tool for viewing, editing and creating references in Wikipedia
 *
 * Copyright 2008-2011 Georgia Tech Research Corporation, Atlanta, GA 30332-0415
 * Copyright 2011-? Matthew Flaschen
 * Rewritten and internationalized by Luis Felipe Schenone in 2014
 *
 * ProveIt is available under the GNU Free Documentation License (http://www.gnu.org/copyleft/fdl.html),
 * Creative Commons Attribution/Share-Alike License 3.0 (http://creativecommons.org/licenses/by-sa/3.0/),
 * and the GNU General Public License 2 (http://www.gnu.org/licenses/gpl-2.0.html)
 */

var proveit = {

	//Localization to Spanish of the ProveIt gadget
	templates: {
		'Cita web': {
			'url':          { visible: true,  required: true,  type: 'url',  label: 'URL' },
			'título':       { visible: true,  required: false, type: 'text', label: 'Título' },
			'fechaacceso':  { visible: true,  required: false, type: 'date', label: 'Fecha de acceso' },
			'cita':         { visible: true,  required: false, type: 'text', label: 'Cita' },
			'suscripción':  { visible: false, required: false, type: 'text', label: 'Requiere suscripción?' },
			'apellido':     { visible: false, required: false, type: 'text', label: 'Apellido', alias: [ 'apellidos', 'last' ] },
			'nombre':       { visible: false, required: false, type: 'text', label: 'Nombre', alias: [ 'nombres', 'first' ] },
			'autor':        { visible: false, required: false, type: 'text', label: 'Autor' },
			'enlaceautor':  { visible: false, required: false, type: 'text', label: 'Artículo del autor' },
			'coautores':    { visible: false, required: false, type: 'text', label: 'Coautores' },
			'fecha':        { visible: false, required: false, type: 'date', label: 'Fecha' },
			'año':          { visible: false, required: false, type: 'text', label: 'Año' },
			'mes':          { visible: false, required: false, type: 'text', label: 'Mes' },
			'urltrad':      { visible: false, required: false, type: 'url',  label: 'URL de traducción' },
			'enlaceroto':   { visible: false, required: false, type: 'text', label: 'Enlace roto?' },
			'formato':      { visible: false, required: false, type: 'text', label: 'Formato' },
			'obra':         { visible: false, required: false, type: 'text', label: 'Obra' },
			'editor':       { visible: false, required: false, type: 'text', label: 'Editor' },
			'editorial':    { visible: false, required: false, type: 'text', label: 'Editorial' },
			'ubicación':    { visible: false, required: false, type: 'text', label: 'Ubicación' },
			'página':       { visible: false, required: false, type: 'text', label: 'Página' },
			'páginas':      { visible: false, required: false, type: 'text', label: 'Páginas' },
			'idioma':       { visible: false, required: false, type: 'text', label: 'Idioma' },
			'doi':          { visible: false, required: false, type: 'text', label: 'DOI' },
			'urlarchivo':   { visible: false, required: false, type: 'url',  label: 'URL de archivo' },
			'fechaarchivo': { visible: false, required: false, type: 'date', label: 'Fecha de archivo' }
		},
		'Cita libro': {
			'título':      { visible: true,  required: true,  type: 'text', label: 'Título' },
			'apellido':    { visible: true,  required: true,  type: 'text', label: 'Apellido', alias: [ 'apellidos', 'last' ] },
			'nombre':      { visible: true,  required: false, type: 'text', label: 'Nombre' },
			'enlaceautor': { visible: true,  required: false, type: 'text', label: 'Artículo del autor' },
			'año':         { visible: true,  required: false, type: 'text', label: 'Año' },
			'cita':        { visible: true,  required: false, type: 'text', label: 'Cita' },
			'autor':       { visible: false, required: false, type: 'text', label: 'Autor' },
			'url':         { visible: false, required: false, type: 'url',  label: 'URL' },
			'fechaacceso': { visible: false, required: false, type: 'date', label: 'Fecha de acceso' },
			'idioma':      { visible: false, required: false, type: 'text', label: 'Idioma' },
			'otros':       { visible: false, required: false, type: 'text', label: 'Otros' },
			'edición':     { visible: false, required: false, type: 'text', label: 'Edición' },
			'editor':      { visible: false, required: false, type: 'text', label: 'Editor' },
			'editorial':   { visible: false, required: false, type: 'text', label: 'Editorial' },
			'ubicación':   { visible: false, required: false, type: 'text', label: 'Ubicación' },
			'isbn':        { visible: false, required: false, type: 'text', label: 'ISBN' },
			'capítulo':    { visible: false, required: false, type: 'text', label: 'Capítulo' },
			'páginas':     { visible: false, required: false, type: 'text', label: 'Páginas' }
		},
		'Cita enciclopedia': {
			'título':       { visible: true,  required: true,  type: 'text', label: 'Título' },
			'enciclopedia': { visible: true,  required: true,  type: 'text', label: 'Enciclopedia' },
			'apellido':     { visible: true,  required: false, type: 'text', label: 'Apellido' },
			'nombre':       { visible: true,  required: false, type: 'text', label: 'Nombre' },
			'cita':         { visible: true,  required: false, type: 'text', label: 'Cita' },
			'autor':        { visible: false, required: false, type: 'text', label: 'Autor' },
			'enlaceautor':  { visible: false, required: false, type: 'text', label: 'Artículo del autor' },
			'coautores':    { visible: false, required: false, type: 'text', label: 'Coautores' },
			'editor':       { visible: false, required: false, type: 'text', label: 'Editor' },
			'idioma':       { visible: false, required: false, type: 'text', label: 'Idioma' },
			'url':          { visible: false, required: false, type: 'url',  label: 'URL' },
			'fechaacceso':  { visible: false, required: false, type: 'date', label: 'Fecha de acceso' },
			'edición':      { visible: false, required: false, type: 'text', label: 'Edición' },
			'fecha':        { visible: false, required: false, type: 'date', label: 'Fecha' },
			'editorial':    { visible: false, required: false, type: 'text', label: 'Editorial' },
			'volumen':      { visible: false, required: false, type: 'text', label: 'Volumen' },
			'ubicación':    { visible: false, required: false, type: 'text', label: 'Ubicación' },
			'isbn':         { visible: false, required: false, type: 'text', label: 'ISBN' },
			'páginas':      { visible: false, required: false, type: 'text', label: 'Páginas' },
			'isbn':         { visible: false, required: false, type: 'text', label: 'ISBN' },
			'oclc':         { visible: false, required: false, type: 'text', label: 'OCLC' },
			'doi':          { visible: false, required: false, type: 'text', label: 'DOI' }
		},
		'Cita noticia': {
			'título':      { visible: true,  required: true,  type: 'text', label: 'Título' },
			'periódico':   { visible: true,  required: true,  type: 'text', label: 'Periódico' },
			'fecha':       { visible: true,  required: false, type: 'date', label: 'Fecha' },
			'nombre':      { visible: true,  required: false, type: 'text', label: 'Nombre' },
			'apellido':    { visible: true,  required: false, type: 'text', label: 'Apellido' },
			'enlaceautor': { visible: false, required: false, type: 'text', label: 'Artículo del autor' },
			'autor':       { visible: false, required: false, type: 'text', label: 'Autor' },
			'coautores':   { visible: false, required: false, type: 'text', label: 'Coautor' },
			'url':         { visible: false, required: false, type: 'url',  label: 'URL' },
			'formato':     { visible: false, required: false, type: 'text', label: 'Formato' },
			'agencia':     { visible: false, required: false, type: 'text', label: 'Agencia' },
			'editorial':   { visible: false, required: false, type: 'text', label: 'Editorial' },
			'id':          { visible: false, required: false, type: 'text', label: 'Identificador' },
			'páginas':     { visible: false, required: false, type: 'text', label: 'Páginas' },
			'página':      { visible: false, required: false, type: 'text', label: 'Página' },
			'fechaacceso': { visible: false, required: false, type: 'date', label: 'Fecha de acceso' },
			'idioma':      { visible: false, required: false, type: 'text', label: 'Idioma' },
			'ubicación':   { visible: false, required: false, type: 'text', label: 'Ubicación' },
			'cita':        { visible: false, required: false, type: 'text', label: 'Cita' }
		},
		'Cita conferencia': {
			'título':         { visible: true,  required: true,  type: 'text', label: 'Título' },
			'apellido':       { visible: true,  required: true,  type: 'text', label: 'Apellido' },
			'nombre':         { visible: true,  required: false, type: 'text', label: 'Nombre' },
			'fecha':          { visible: true,  required: false, type: 'text', label: 'Fecha' },
			'conferencia':    { visible: true,  required: false, type: 'date', label: 'Conferencia' },
			'enlaceautor':    { visible: false, required: false, type: 'text', label: 'Artículo del autor' },
			'coaturoes':      { visible: false, required: false, type: 'text', label: 'Coautores' },
			'año':            { visible: false, required: false, type: 'text', label: 'Año' },
			'mes':            { visible: false, required: false, type: 'text', label: 'Mes' },
			'urlconferencia': { visible: false, required: false, type: 'url',  label: 'URL de la conferencia' },
			'títulolibro':    { visible: false, required: false, type: 'text', label: 'Título del libro' },
			'editor':         { visible: false, required: false, type: 'text', label: 'Editor' },
			'otros':          { visible: false, required: false, type: 'text', label: 'Otros créditos' },
			'volumen':        { visible: false, required: false, type: 'text', label: 'Volumen' },
			'edición':        { visible: false, required: false, type: 'text', label: 'Edición' },
			'publicación':    { visible: false, required: false, type: 'text', label: 'Publicación' },
			'ubicación':      { visible: false, required: false, type: 'text', label: 'Ubicación' },
			'páginas':        { visible: false, required: false, type: 'text', label: 'Páginas' },
			'url':            { visible: false, required: false, type: 'url',  label: 'URL' },
			'fechaacceso':    { visible: false, required: false, type: 'date', label: 'Fecha de acceso' },
			'formato':        { visible: false, required: false, type: 'text', label: 'Formato' },
			'doi':            { visible: false, required: false, type: 'text', label: 'DOI' },
			'id':             { visible: false, required: false, type: 'text', label: 'Identificador' },
			'isbn':           { visible: false, required: false, type: 'text', label: 'ISBN' },
			'co-isbn':        { visible: false, required: false, type: 'text', label: 'Co-ISBN' }
		},
		'Cita vídeo': {
			'título':       { visible: true,  required: true,  type: 'text', label: 'Título' },
			'año':          { visible: true,  required: false, type: 'text', label: 'Año' },
			'url':          { visible: true,  required: false, type: 'url',  label: 'URL' },
			'fechaacceso':  { visible: true,  required: false, type: 'date', label: 'Fecha de acceso' },
			'persona':      { visible: false, required: false, type: 'text', label: 'Persona' },
			'medio':        { visible: false, required: false, type: 'text', label: 'Medio' },
			'editorial':    { visible: false, required: false, type: 'text', label: 'Editorial' },
			'localización': { visible: false, required: false, type: 'text', label: 'Localización' },
			'tiempo':       { visible: false, required: false, type: 'text', label: 'Tiempo' },
			'cita':         { visible: false, required: false, type: 'text', label: 'Cita' },
			'id':           { visible: false, required: false, type: 'text', label: 'Identificador' }
		},
		'Cita episodio': {
			'título':           { visible: true,  required: true,  type: 'text', label: 'Título' },
			'serie':            { visible: true,  required: true,  type: 'text', label: 'Serie' },
			'fecha':            { visible: true,  required: false, type: 'date', label: 'Fecha' },
			'minutos':          { visible: true,  required: false, type: 'text', label: 'Minutos' },
			'créditos':         { visible: false, required: false, type: 'text', label: 'Créditos' },
			'cadena':           { visible: false, required: false, type: 'text', label: 'Cadena' },
			'cadenalocal':      { visible: false, required: false, type: 'text', label: 'Cadena local' },
			'ubicación':        { visible: false, required: false, type: 'text', label: 'Ubicación' },
			'serial':           { visible: false, required: false, type: 'text', label: 'Serial' },
			'inicio':           { visible: false, required: false, type: 'text', label: 'Fecha de inicio' },
			'fin':              { visible: false, required: false, type: 'text', label: 'Fecha de finalización' },
			'temporada':        { visible: false, required: false, type: 'text', label: 'Temporada' },
			'número':           { visible: false, required: false, type: 'text', label: 'Número' },
			'transcripción':    { visible: false, required: false, type: 'text', label: 'Transcripción' },
			'transcripciónurl': { visible: false, required: false, type: 'url',  label: 'Transcripción URL' }
		},
		'Cita publicación': {
			'título':      { visible: true,  required: true,  type: 'text',   label: 'Título' },
			'apellido':    { visible: true,  required: true,  type: 'text',   label: 'Apellido' },
			'nombre':      { visible: true,  required: false, type: 'text',   label: 'Nombre' },
			'publicación': { visible: true,  required: false, type: 'text',   label: 'Publicación' },
			'año':         { visible: true,  required: false, type: 'text',   label: 'Año' },
			'url':         { visible: true,  required: false, type: 'url',    label: 'URL' },
			'enlaceautor': { visible: false, required: false, type: 'text',   label: 'Artículo del autor' },
			'volumen':     { visible: false, required: false, type: 'number', label: 'Volumen' },
			'número':      { visible: false, required: false, type: 'number', label: 'Número' },
			'páginas':     { visible: false, required: false, type: 'text',   label: 'Páginas' },
			'página':      { visible: false, required: false, type: 'text',   label: 'Página' },
			'ubicación':   { visible: false, required: false, type: 'text',   label: 'Ubicación' },
			'editorial':   { visible: false, required: false, type: 'text',   label: 'Editorial' },
			'issn':        { visible: false, required: false, type: 'text',   label: 'ISSN' },
			'doi':		   { visible: false, required: false, type: 'text',   label: 'DOI' },
			'fechaacceso': { visible: false, required: false, type: 'date',   label: 'Fecha de acceso' }
		}
	},

	messages: {
		edit_tab: 'Editar',
		add_tab: 'Agregar',
		template_label: 'Plantilla',
		ref_name_label: '<ref> name',
		add_field_button: 'Agregar campo',
		insert_button: 'Insertar',
		update_button: 'Actualizar',
		show_all_params_button: 'Mostrar todos los parámetros',
		no_references: 'No se han encontrado referencias',
		summary: 'Editado con ProveIt'
	},

	LOGO: '//proveit-js.googlecode.com/hg/static/logo.png',

	ICON: '/media/wikipedia/commons/thumb/1/19/ProveIt_logo_for_user_boxes.svg/22px-ProveIt_logo_for_user_boxes.svg.png',

	/**
	 * Configuration options
	 * When the gadget is added as a default gadget, users can't change these,
	 * unless they copy the code to their private JavaScript
	 * Instead, these options should be configurable from a GUI and saved in cookies
	 * Or alternatively, they could be removed, and the "best" configuration enforced
	 */
 	startVisible: true,

	startMaximized: false,

	shouldAddSummary: true,

	/**
	 * Initializes ProveIt
	 */
	start: function() {

		//Initialize only when editing
		if ( wgAction == 'edit' || wgAction == 'submit' ) {

			var dependencies = [
				'jquery.ui.tabs',
				'jquery.textSelection',
				'jquery.effects.highlight'
			];
			mw.loader.using( dependencies, function() {

				//If the user is using WikiEditor, add the ProveIt button to the toolbar
				var textbox = proveit.getTextbox();
				textbox.bind( 'wikiEditor-toolbar-buildSection-main', function( event, section ) {
					section.groups.insert.tools.proveit = {
						label: 'ProveIt',
						type: 'button',
						icon: proveit.ICON,
						action: {
							type: 'callback',
							execute: function() {
								jQuery( '#proveit' ).toggle();
							}
						}
					};
					delete section.groups.insert.tools.reference; //Also delete the references button
				});

				proveit.createGUI();
				proveit.scanForReferences();

				//Only show the gadget on pages that are likely to contain references
				if ( wgCanonicalNamespace == '' || wgCanonicalNamespace == 'User' ) {
					if ( proveit.startVisible ) {
						jQuery( '#proveit' ).show();
					}
					if ( proveit.startMaximized ) {
						jQuery( '#proveit-logo' ).click();
					}
				}
			});
		}
	},

	//Convenience function that returns the message for the given key in the current language
	getMessage: function( key ) {
		return this.messages[ key ];
	},

	//Convenience function that returns all the registered templates
	getRegisteredTemplates: function() {
		return this.templates;
	},

	//Convenience function that returns the edit textarea
	getTextbox: function() {
		return jQuery( '#wpTextbox1' );
	},

	//Convenience function that returns the text in the edit textarea
	getTextboxText: function() {
		return this.getTextbox().val();
	},

	/**
	 * Provides the x (left) and y (top) offsets to a given element.
	 * From QuirksMode (http://www.quirksmode.org/js/findpos.html), a freely available site by Peter-Paul Koch
	 * @param {Node} node any HTML node
	 * @return {Object} offsets to node, as object with left and top properties.
	 */
	getOffset: function( node ) {
		var left = 0, top = 0;
		do {
			left += node.offsetLeft;
			top += node.offsetTop;
		} while ( node = node.offsetParent );
		return { 'left': left, 'top': top };
	},

	/**
	 * Highlights a given length of text, starting at a particular index
	 * @param {Number} startInd start index in Wikipedia edit textbox
	 * @param {Number} length length of string to highlight
	 * @return {Boolean} always true
	 */
	highlightLengthAtIndex: function( startIndex, length ) {
		var textbox = this.getTextbox()[0];
		var referenceStringText = textbox.value;
		var editTop = this.getOffset( textbox ).top;
		textbox.value = referenceStringText.substring( 0, startIndex );
		textbox.focus();
		textbox.scrollTop = 1000000; //Larger than any real textarea (hopefully)
		var curScrollTop = textbox.scrollTop;
		textbox.value += referenceStringText.substring( startIndex );
		if ( curScrollTop > 0 ) {
			textbox.scrollTop = curScrollTop + 200;
		}
		jQuery( textbox ).focus().textSelection( 'setSelection', {
			start: startIndex,
			end: startIndex + length
		});
		editTop = this.getOffset( textbox ).top;
		window.scroll( 0, editTop );
		return true;
	},

	/**
	 * Highlights the first instance of a given string in the MediaWiki edit textbox
	 * @param {String} the string in the edit textbox to highlight
	 * @return {Boolean} true if successful, false otherwise
	 */
	highlightString: function( string ) {
		var textboxText = this.getTextboxText();
		var startIndex = textboxText.indexOf( string );
		if ( startIndex == -1 ) {
			return false;
		}
		return this.highlightLengthAtIndex( startIndex, string.length );
	},

	/**
	 * Adds the ProveIt edit summary to the edit summary field
	 */
	addSummary: function() {
		if ( ! this.shouldAddSummary ) {
			return false;
		}
		var summary = jQuery( '#wpSummary' ).val()
		if ( summary ) { //If the user has already added a summary, don't screw it up
			return false;
		}
		summary = this.getMessage( 'summary' );
		jQuery( '#wpSummary' ).val( summary );
		return true;
	},

	/**
	 * Populates the edit form with the data of the selected reference
	 * @param {TemplateReference} the selected reference
	 */
	loadEditForm: function( reference ) {

		//Remove all the fields
		var editFields = jQuery( '#proveit-edit-fields' );
		editFields.children().remove( 'label' );

		var refNameLabel = jQuery( '<label/>', { 'text': this.getMessage( 'ref_name_label' ) } );
		var refNameInput = jQuery( '<input/>', { 'type': 'text', id: 'edit-ref-name', 'value': reference.name } );
		refNameLabel.append( refNameInput );
		editFields.append( refNameLabel );

		var params = reference.params;
		var registeredParams = reference.getRegisteredParams();
		var visibleParams = reference.getVisibleParams();
		var requiredParams = reference.getRequiredParams();

		for ( var paramName in registeredParams ) {

			var paramType = registeredParams[ paramName ][ 'type' ];
			var paramLabel = registeredParams[ paramName ][ 'label' ];
			var paramValue = reference.params[ paramName ];
			if ( 'placeholder' in registeredParams[ paramName ] ) {
				var paramPlaceholder = registeredParams[ paramName ][ 'placeholder' ];
			} else {
				var paramPlaceholder = '';
			}

			var label = jQuery( '<label/>', { 'text': paramLabel } );
			var input = jQuery( '<input/>', { 'type': paramType, 'name': paramName, 'value': paramValue, 'placeholder': paramPlaceholder } );

			//Hide the non-visible parameters, unless they are filled
			if ( ! ( paramName in visibleParams ) && ! paramValue ) {
				label.addClass( 'hidden' );
			}

			label.append( input );
			editFields.append( label );
		}

		//Make sure this button is visible
		jQuery( '#proveit-edit-buttons .show-all-params-button' ).show();

		/* Event handlers */
		jQuery( '.update-button' ).click( function() {
			reference.update();
		});

		jQuery( '#proveit-edit-buttons .show-all-params-button' ).click( function() {
			jQuery( this ).hide();
			jQuery( '#proveit-edit-fields label' ).css({ 'display': 'block' });
		});
	},

	/**
	 * Cross-browser implementation of ECMAScript String.prototype.split function 1.0.1
	 * (c) Steven Levithan <stevenlevithan.com>; MIT License
	 * http://blog.stevenlevithan.com/archives/cross-browser-split
	 *
	 * @param {String} input string to split
	 * @param separator separator to split on, as RegExp or String
	 * @param {Number} limit limit on number of splits.  If the parameter is absent, no limit is imposed.
	 * @return {Array} array resulting from split
	 */
	_compliantExecNpcg: /()??/.exec( '' )[1] === undefined, //NPCG: non-participating capturing group
	_nativeSplit: String.prototype.split,
	split: function( string, separator, limit ) {
		//If 'separator' is not a regex, use the native 'split'
		if ( Object.prototype.toString.call( separator ) !== "[object RegExp]" ) {
			return proveit._nativeSplit.call( string, separator, limit );
		}

		var output = [],
		lastLastIndex = 0,
		flags = ( separator.ignoreCase ? "i" : "" ) + ( separator.multiline ? "m" : "" ) + ( separator.sticky ? "y" : "" ),
			separator = RegExp( separator.source, flags + "g" ), //Make 'global' and avoid 'lastIndex' issues by working with a copy
			separator2,
			match,
			lastIndex,
			lastLength;

		string = string + ""; //Type conversion
		if ( ! proveit._compliantExecNpcg ) {
			separator2 = RegExp( "^" + separator.source + "jQuery(?!\\s)", flags ); //Doesn't need /g or /y, but they don't hurt
		}

		/**
		 * Behavior for 'limit'. If it's...
		 * - undefined: no limit
		 * - NaN or zero: return an empty array
		 * - a positive number: use Math.floor(limit)
		 * - a negative number: no limit
		 * - other: type-convert, then use the above rules
		 */
		if ( limit === undefined || +limit < 0 ) {
			limit = Infinity;
		} else {
			limit = Math.floor( +limit );
			if ( ! limit) {
				return [];
			}
		}

		while ( match = separator.exec( string ) ) {
			lastIndex = match.index + match[0].length; //separator.lastIndex is not reliable cross-browser

			if ( lastIndex > lastLastIndex ) {
				output.push( string.slice( lastLastIndex, match.index ) );

				//Fix browsers whose 'exec' methods don't consistently return 'undefined' for nonparticipating capturing groups
				if ( ! proveit._compliantExecNpcg && match.length > 1 ) {
					match[0].replace( separator2, function () {
						for ( var i = 1; i < arguments.length - 2; i++ ) {
							if ( arguments[ i ] === undefined ) {
								match[ i ] = undefined;
							}
						}
					});
				}

				if ( match.length > 1 && match.index < string.length ) {
					Array.prototype.push.apply( output, match.slice(1) );
				}

				lastLength = match[0].length;
				lastLastIndex = lastIndex;

				if ( output.length >= limit ) {
					break;
				}
			}

			if ( separator.lastIndex === match.index ) {
				separator.lastIndex++; //Avoid an infinite loop
			}
		}

		if ( lastLastIndex === string.length ) {
			if ( lastLength || ! separator.test("") ) {
				output.push("");
			}
		} else {
			output.push( string.slice( lastLastIndex ) );
		}

		return output.length > limit ? output.slice( 0, limit ) : output;
	},

	//TODO: Remove the split code, and just use a regular regex (with two main groups for key and value), iteratively
	//Regex.find? Make key and value indices match, and rework calling code as needed
	//Also, check how this was done in the original code
	/**
	 * Overly clever regex to parse template string (e.g. |last=Smith|first=John|title=My Life Story) into key and value pairs
	 * @param {String} templateString template string to parse
	 * @return {Object} object with two properties, keys and values
	 */
	splitKeysAndValues: function( templateString ) {
		var split = {};

		//The first component is "ordinary" text (no pipes), while the second is a correctly balanced wikilink, with optional pipe. Any combination of the two can appear.
		split.keys = proveit.split( templateString.substring( templateString.indexOf( '|' ) + 1 ), /=(?:[^|]*?(?:\[\[[^|\]]*(?:\|(?:[^|\]]*))?\]\])?)+(?:\||\}\})/ );

		split.keys.length--; //Remove single empty element at end

		split.values = proveit.split( templateString.substring( templateString.indexOf( '=' ) + 1, templateString.indexOf( '}}' ) ), /\|[^|=]*=/ );

		return split;
	},

	/**
	 * Scan for references in the textbox, and create a list item for each
	 */
	scanForReferences: function() {

		jQuery( '#proveit-reference-list' ).children().remove();

		//First look for all the citations
		var	text = this.getTextboxText();
		var citations = [];
		//Three possibilities: <ref name="foo" />, <ref name='foo' /> and <ref name=foo />
		var citationsRegExp = /<\s*ref\s+name\s*=\s*["|']?\s*([^"'\s]+)\s*["|']?\s*\/\s*>/gi;

		//Then create an object for each and store it for later
		while ( match = citationsRegExp.exec( text ) ) {
			citations.push({
				'string': match[0],
				'index': match['index'],
				'name': match[1]
			})
		}

		//Next, look for all the raw references and template references
		var referencesRegExp = new RegExp( '<\s*ref.*?<\s*\/\s*ref\s*>', 'gi' );
		var matches = text.match( referencesRegExp );

		if ( ! matches ) {
			var noReferencesMessage = jQuery( '<div/>', { 'id': 'proveit-no-references-message', 'text': this.getMessage( 'no_references' ) } );
			jQuery( '#proveit-reference-list' ).append( noReferencesMessage );
			return;
		}

		for ( var i = 0; i < matches.length; i++ ) {
			var reference = this.makeReference( matches[ i ] );

			//For each reference, check if there are any citations to it
			for ( var j = 0; j < citations.length; j++ ) {

				var citation = citations[ j ];

				//And if there are, add the citation to the reference
				if ( reference.name == citation.name ) {
					reference.citations.push( citation );
				}
			}
			//Finally, insert all the references into the reference list
			var referenceItem = this.makeReferenceItem( reference );
			jQuery( '#proveit-reference-list' ).append( referenceItem );
		}

	},

	/**
	 * Takes a reference string and returns a reference object
	 */
	makeReference: function( referenceString ) {

		var registeredTemplates = proveit.getRegisteredTemplates();
		var registeredTemplatesArray = [];
		for ( var registeredTemplate in registeredTemplates ) {
			registeredTemplatesArray.push( registeredTemplate );
		}
		var registeredTemplatesDisjunction = registeredTemplatesArray.join( '|' );

		var regExp = new RegExp( '{{(' + registeredTemplatesDisjunction + ').*}}', 'i' );

		var match = referenceString.match( regExp );

		var reference = match ? new this.TemplateReference({}) : new this.RawReference({});
		//Extract the name of the template
		if ( reference.type == 'TemplateReference' ) {
			var templateString = match[0];
			var regExp = new RegExp( registeredTemplatesDisjunction, 'i' );
			var template = templateString.match( regExp )[0];

			//Normalize the uppercase and lowercase letters
			for ( var registeredTemplate in registeredTemplates ) {
				if ( template.toLowerCase() == registeredTemplate.toLowerCase() ) {
					template = registeredTemplate;
				}
			}
			reference.template = template;
		}

		//Extract the name of the reference, if any
		//There are three possible syntaxes: <ref name="foo">, <ref name='foo'> and <ref name=foo>
		var regExp = /<[\s]*ref[\s]*name[\s]*=[\s]*(?:(?:\"(.*?)\")|(?:\'(.*?)\')|(?:(.*?)))[\s]*\/?[\s]*>/i;
		var match = referenceString.match( regExp );
		if ( match ) {
			reference.name = match[1] || match[2] || match[3];
		}

		reference.string = referenceString;

		//Extract the parameters
		if ( reference.type == 'TemplateReference' ) {
			var split = this.splitKeysAndValues( templateString );
			var keys = split.keys;
			var values = split.values;

			for ( var i = 0; i < keys.length; i++ ) {
				var paramName = keys[ i ];
				var paramValue = values[ i ];

				//Drop blank space, and |'s without params, which are never correct for citation templates
				//TODO: Does the splitKeysAndValues method return |'s without params?
				paramName = jQuery.trim( paramName ).replace( /(?:\s*\|)*(.*)/, "jQuery1" );
				paramValue = jQuery.trim( paramValue );

				//If there is no value, forget it
				//TODO: Does the splitKeysAndValues method return empty values?
				if ( ! paramValue ) {
					continue;
				}

				//If the parameter is registered, set the value and continue
				var registeredParams = reference.getRegisteredParams();
				if ( paramName in registeredParams ) {
					reference.params[ paramName ] = paramValue;
					continue;
				}

				//If the parameter is NOT registered, check all the aliases
				var registeredAliases = reference.getRegisteredAliases();
				if ( paramName in registeredAliases ) {
					paramName = registeredAliases[ paramName ]; //Normalize the parameter
					reference.params[ paramName ] = paramValue;
					continue;
				}
			}
		}
		return reference;
	},


	/**
	 * Generates a reference list item, to be used by scanForReferences
	 */
	makeReferenceItem: function( reference ) {

		var item = jQuery( '<li/>', { 'class': 'reference-item' } );

		if ( reference.type == 'RawReference' ) {

			var itemContent = reference.string;

		} else if ( reference.type == 'TemplateReference' ) {
	
			var itemContent = '<span class="template">' + reference.template + '</span>';
			
			var requiredParams = reference.getRequiredParams();
			for ( var requiredParam in requiredParams ) {
				var label = requiredParams[ requiredParam ][ 'label' ];
				var value = reference.params[ requiredParam ];
				itemContent += '<span class="label">' + label + '</span>: <span class="value">' + value + '</span>';
			}
		}

		itemContent += '<span class="citations">';
		for ( var i = 0; i < reference.citations.length; i++ ) {
			var citationIndex = i + 1;
			itemContent += '<a href="#" class="citation">' + citationIndex + '</a>';
		}
		itemContent += '</span>';

		item.html( itemContent );

		//Event handlers
		jQuery( item ).click( function() {
			proveit.highlightString( reference.string );
			if ( reference.type == 'TemplateReference' ) {
				proveit.loadEditForm( reference );
				jQuery( '#proveit-reference-list' ).hide();
				jQuery( '#proveit-edit-form' ).show();
			}
		});

		jQuery( 'a.citation', item ).click( function( event ) {
			event.stopPropagation();
			var i = jQuery( this ).text() - 1;
			var citation = reference.citations[ i ];
			//TODO: If the user edits the textbox manually, the following will cease to highlight correctly
			proveit.highlightLengthAtIndex( citation.index, citation.string.length );
			return false;
		});

		return item;
	},

	/**
	 * Constructs a reference out of the contents of the add form and inserts it into the textbox
	 */
	insertReference: function() {

		var referenceName = jQuery( '#add-ref-name' ).val();
		var referenceTemplate = jQuery( '#add-template' ).val();
		var reference = new this.TemplateReference( { 'name': referenceName, 'template': referenceTemplate } );

		var addTab = jQuery( '#proveit-add-tab' );
		var labels = jQuery( 'label', addTab );
		var label, input, paramName, paramType, paramValue;
		for ( var i = 0; i < labels.length; i++ ) {
			label =  labels[ i ];
			input = jQuery( 'input', label );
			paramName = input.attr( 'name' );
			paramType = input.attr( 'type' );
			paramValue = input.val();

			if ( paramName != '' && paramValue != '' ) {
				reference.params[ paramName ] = paramValue;
			}
		}

		reference.string = reference.toString();

		//Replace existing selection (if any), then scroll
		var textbox = proveit.getTextbox();
		textbox.textSelection( 'encapsulateSelection', {
			peri: reference.string,
			replace: true
		});

		this.addSummary();

		//Update the reference list by scanning the references again
		proveit.scanForReferences()
	},

	/**
	 * Changes the params in the add form according to the template selected
	 * @param {string} template selected from the dropdown menu
	 */
	loadAddForm: function( template ) {

		//Remove all the fields except the <ref> name and the dropdown menu (the first two elements)
		var addFields = jQuery( '#proveit-add-fields' );
		addFields.children().slice(2).remove( 'label' );

		var reference = new this.TemplateReference( { 'template': template } );
		var registeredParams = reference.getRegisteredParams();
		var visibleParams = reference.getVisibleParams();
		var requiredParams = reference.getRequiredParams();

		for ( var paramName in registeredParams ) {

			var paramType = registeredParams[ paramName ][ 'type' ];
			var paramLabel = registeredParams[ paramName ][ 'label' ];
			if ( 'placeholder' in registeredParams[ paramName ] ) {
				var paramPlaceholder = registeredParams[ paramName ][ 'placeholder' ];
			} else {
				var paramPlaceholder = '';
			}

			var label = jQuery( '<label/>', { 'text': paramLabel } );
			var input = jQuery( '<input/>', { 'type': paramType, 'name': paramName, 'placeholder': paramPlaceholder } );

			if ( ! ( paramName in visibleParams ) ) {
				label.addClass( 'hidden' );
			}

			label.append( input );
			addFields.append( label );
		}

		//Make sure this button is visible
		jQuery( '#proveit-add-buttons .show-all-params-button' ).show();
	},

	createGUI: function() {

		var gui = jQuery( '<div/>', { id: 'proveit' } );
		var tabs = jQuery( '<div/>', { id: 'proveit-tabs' } );
		var logo = jQuery( '<img/>', { id: 'proveit-logo', src: proveit.LOGO } );
		tabs.append( logo );


		/* Tabs list */
		var tabsList = jQuery( '<ul/>' );

		//Edit tab link
		var editItem = jQuery( '<li/>' );
		var editLink = jQuery( '<a/>', { id: 'edit-link', 'class': 'tab-link', href: '#proveit-edit-tab', text: this.getMessage( 'edit_tab' ) } );
		editItem.append( editLink );
		tabsList.append( editItem );

		//Add tab link
		var addItem = jQuery( '<li/>' );
		var addLink = jQuery( '<a/>', { id: 'add-link', 'class': 'tab-link', href: '#proveit-add-tab', text: this.getMessage( 'add_tab' ) } );
		addItem.append( addLink );
		tabsList.append( addItem );

		tabs.append( tabsList );

		/* Tabs wrapper */
		var tabsWrapper = jQuery( '<div/>', { id: 'proveit-tabs-wrapper' } );

		/* Edit tab */
		var editTab = jQuery( '<div/>', { id: 'proveit-edit-tab' } );
		var referenceList = jQuery( '<ul/>', { id: 'proveit-reference-list' } );
		var editForm = jQuery( '<div/>', { id: 'proveit-edit-form' } );
		var editFields = jQuery( '<div/>', { id: 'proveit-edit-fields' } );
		var editButtons = jQuery( '<div/>', { id: 'proveit-edit-buttons' } );
		var updateButton = jQuery( '<button/>', { 'class': 'update-button', text: this.getMessage( 'update_button' ) } );
		var showAllParamsButton = jQuery( '<button/>', { 'class': 'show-all-params-button', text: this.getMessage( 'show_all_params_button' ) } );

		editButtons.append( showAllParamsButton );
		editButtons.append( updateButton );
		editForm.append( editFields );
		editForm.append( editButtons );
		editTab.append( referenceList );
		editTab.append( editForm );
		tabsWrapper.append( editTab );


		/* Add tab */
		var addTab = jQuery( '<div/>', { id: 'proveit-add-tab' } );
		var addFields = jQuery( '<div/>', { id: 'proveit-add-fields' } );

		//First insert the <ref> name
		var refNameLabel = jQuery( '<label/>', { 'text': this.getMessage( 'ref_name_label' ) } );
		var refNameInput = jQuery( '<input/>', { 'type': 'text', id: 'add-ref-name' } );
		refNameLabel.append( refNameInput );
		addFields.append( refNameLabel );

		//Then insert the dropdown menu
		var templateLabel = jQuery( '<label/>', { 'text': this.getMessage( 'template_label' ) } );
		var templateSelect = jQuery( '<select/>', { id: 'add-template' } );
		var templates = proveit.getRegisteredTemplates();
		for ( var templateName in templates ) {
			var templateOption = jQuery( '<option/>', { 'value': templateName, 'text': templateName } );
			templateSelect.append( templateOption );
		}
		templateLabel.append( templateSelect );
		addFields.append( templateLabel );

		addTab.append( addFields );

		//After the fields, insert the buttons
		var addButtons = jQuery( '<div/>', { id: 'proveit-add-buttons' } );
		var insertButton = jQuery( '<button/>', { 'class': 'insert-button', 'text': this.getMessage( 'insert_button' ) } );
		var showAllParamsButton = jQuery( '<button/>', { 'class': 'show-all-params-button', 'text': this.getMessage( 'show_all_params_button' ) } );
		addButtons.append( showAllParamsButton );
		addButtons.append( insertButton );
		addTab.append( addButtons );

		//And add the tab to the rest
		tabsWrapper.append( addTab );

		// Finally, add everything to the DOM
		tabs.append( tabsWrapper );
		gui.append( tabs );
		jQuery( document.body ).prepend( gui );

		//Now that everything is on the DOM, we can call this method to generate the rest of the add tab
		this.loadAddForm( templateSelect.val() );

		//Set up tabs
		jQuery( '#proveit-tabs' ).tabs();


		/* Event handlers */
		logo.click( function() {
			tabsWrapper.toggle();
		});

		editLink.click( function() {
			if ( tabsWrapper.is( ':visible' ) && editTab.is( ':visible' ) ) {
				referenceList.show();
				editForm.hide();
			} else {
				tabsWrapper.show();
				editTab.show();
			}
		});

		addLink.click( function() {
			tabsWrapper.show();
			addTab.show();
		});

		insertButton.click( function() {
			proveit.insertReference();
			jQuery( '#proveit-tabs' ).tabs( { selected: '#proveit-view-tab' } );
		});

		templateSelect.change( function() {
			proveit.loadAddForm( templateSelect.val() );
		});

		jQuery( '#proveit-add-buttons .show-all-params-button' ).click( function() {
			jQuery( this ).hide();
			jQuery( '#proveit-add-fields label' ).css({ 'display': 'block' });
		});
	},


	RawReference: function( argObj ) {

		this.type = 'RawReference'; //TODO: This should be extractable in some other way

		this.name = argObj.name ? argObj.name : '';

		this.string = argObj.string ? argObj.string : '';

		this.citations = [];

		this.toString = function() {
			return this.string;
		};

		this.updateInTextbox = function() {

			var textbox = proveit.getTextbox();
			var textboxText = proveit.getTextboxText();
			var oldString = this.string;
			var newString = this.toString();

			textboxText = textboxText.replace( oldString, newString );

			textbox.val( textboxText );

			this.string = newString;

			proveit.highlightString( newString );
		};
	},

	TemplateReference: function( argObj ) {

		//Extend RawReference
		proveit.RawReference.call( this, argObj );

		//Override
		this.type = 'TemplateReference'; //TODO: This should be extractable in some other way

		this.params = {};

		this.template = argObj.template ? argObj.template : '';

		this.update = function() {

			var editForm = jQuery( '#proveit-edit-form' );
		
			var name = jQuery( '#edit-ref-name' ).val();
			this.name = name ? name : null; //Save blank names as null

			//Clear old params
			this.params = {};

			var labels = jQuery( 'label', editForm );
			var label, input, paramName, paramType, paramValue;
			for ( var i = 0; i < labels.length; i++ ) {
				label = labels[ i ];
				input = jQuery( 'input', label );
				paramName = input.attr( 'name' );
				paramType = input.attr( 'type' );
				paramValue = input.val();
				if ( paramName != '' && paramValue != '' ) {
					this.params[ paramName ] = paramValue;
				}
			}

			this.updateInTextbox();
			proveit.scanForReferences();
			proveit.addSummary();
			jQuery( '#proveit-logo' ).click();
			return this;
		}

		//Override
		this.toString = function() {
			var string;

			if ( this.name ) {
				string = '<ref name="' + this.name + '">';
			} else {
				string = '<ref>';
			}

			string += '{{' + this.template;

			for ( var name in this.params ) {
				string += ' |' + name + '=' + this.params[ name ];
			}

			string += '}}</ref>';

			return string;
		};

		this.getRegisteredParams = function() {
			var registeredTemplates = proveit.getRegisteredTemplates();
			return registeredTemplates[ this.template ];
		};

		/**
		 * Returns an object that maps aliases to registered parameters
		 */
		this.getRegisteredAliases = function() {
			var registeredAliases = {};
			var registeredParams = this.getRegisteredParams();
			for ( var registeredParam in registeredParams ) {
				var aliases = registeredParams[ registeredParam ][ 'alias' ];
				if ( jQuery.type( aliases ) === 'array' ) {
					for ( var alias in aliases ) {
						registeredAliases[ alias ] = registeredParam;
					}
				}
				if ( jQuery.type( aliases ) === 'string' ) {
					registeredAliases[ aliases ] = registeredParam;
				}
			}
			return registeredAliases;
		};

		this.getRequiredParams = function() {
			var requiredParams = {};
			var registeredParams = this.getRegisteredParams();
			for ( var registeredParam in registeredParams ) {
				if ( registeredParams[ registeredParam ][ 'required' ] === true ) {
					requiredParams[ registeredParam ] = registeredParams[ registeredParam ];
				}
			}
			return requiredParams;
		};

		this.getVisibleParams = function() {
			var visibleParams = {};
			var registeredParams = this.getRegisteredParams();
			for ( var registeredParam in registeredParams ) {
				if ( registeredParams[ registeredParam ][ 'visible' ] === true ) {
					visibleParams[ registeredParam ] = registeredParams[ registeredParam ];
				}
			}
			return visibleParams;
		};
	}
}

jQuery( proveit.start );