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 03:37 5 abr 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.
(difs.) ← Revisión anterior | Ver revisión actual (difs.) | Revisión siguiente → (difs.)

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 inserting references on 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)
 */

( function ( mw, $ ) {
/**
 * Second parameter (pre-existing proveit object, if any) passed to extend overrides first.
 * Gives users the option to easily override initial constants, such as shouldAddSummary.
 * If proveit is unintentionally imported more than once, the first import will take precedence.
 */

var proveit = window.proveit = $.extend({

	LANGUAGE: 'es',

	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',

	//Should ProveIt start visible?
 	startVisible: true,

	//Should ProveIt start maximized?
	startMaximized: false,

	//Should ProveIt add an edit summary?
	shouldAddSummary: true,

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

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

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

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

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

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

			var dependencies = [
				'jquery.ui.tabs',
				'jquery.ui.button',
				'jquery.effects.highlight',
				'jquery.textSelection'
			];
			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() {
								$( '#proveit' ).toggle();
							}
						}
					};
					delete section.groups.insert.tools.reference; //Also delete the references button
				});

				proveit.createGUI();

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

	/**
	 * 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;
		}
		$( 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} targetStr the string in the edit textbox to highlight
	 * @return {Boolean} true if successful, false otherwise
	 */
	highlightTargetString: function( targetString ) {
		var referenceStringText = this.getTextboxText();
		var startIndex = referenceStringText.indexOf( targetString );
		if ( startIndex == -1 ) {
			return false;
		}
		return this.highlightLengthAtIndex( startIndex, targetString.length );
	},

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

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

		//Remove all the fields
		var editFields = $( '#proveit-edit-fields' );
		editFields.children().remove( '.input-row' );

		var refNameRow = $( '<div/>', { 'class': 'ref-name-row input-row' } );
		var refNameLabel = $( '<label/>', { 'for': 'edit-ref-name', 'text': this.getMessage( 'ref_name_label' ) } );
		var refNameInput = $( '<input/>', { id: 'edit-ref-name', 'class': 'param-value', 'value': reference.name } );
		var deleteFieldButton = $( '<button/>', { 'class': 'delete-field-button', 'text': '×' } );
		refNameRow.append( refNameLabel );
		refNameRow.append( refNameInput );
		refNameRow.append( deleteFieldButton );
		editFields.append( refNameRow );

		//Insert one row for each parameter, registered or not
		//Hide the non-default parameters, unless they are filled
		//Skip the delete button in the required parameters
		var registeredParams = reference.getRegisteredParams();
		var defaultParams = reference.getDefaultParams();
		var requiredParams = reference.getRequiredParams();

		for ( var paramName in registeredParams ) {

			var paramLabel = reference.getParamLabel( paramName );
			var paramValue = reference.params[ paramName ];

			var row = $( '<div/>', { 'class': 'input-row param-row' } );
			if ( ! ( paramName in defaultParams ) && ! paramValue ) {
				row.addClass( 'hidden' );
			}
			var label = $( '<label/>', { 'for': paramName, 'text': paramLabel } );
			var nameInput = $( '<input/>', { 'type': 'hidden', 'class': 'param-name', 'value': paramName } );
			var valueInput = $( '<input/>', { 'id': paramName, 'class': 'param-value', 'value': paramValue } );
			row.append( label );
			row.append( nameInput );
			row.append( valueInput );
			if ( ! ( paramName in requiredParams ) ) {
				row.append( deleteFieldButton.clone() );
			}
			editFields.append( row );
		}

		$( '.update-button' ).click( function() {
			reference.update();
		});

		$( '#proveit-edit-buttons .params-button' ).click( function() {
			$( this ).hide();
			$( '#proveit-edit-buttons .add-field-button' ).show();
			$( '#proveit-edit-fields .param-row' ).show();
		});

		$( '.delete-field-button' ).click( function() {
			//First hide the row with a quick animation, then remove it
			$( this ).parent().hide( 'fast', function() {
				$( this ).remove();
			});
		});
	},

	addCustomField: function( fields ) {

		var row = $( '<div/>', { 'class': 'input-row param-row' } );
		var nameInput = $( '<input/>', { 'class': 'param-name' } );
		var valueInput = $( '<input/>', { 'class': 'param-value' } );
		var deleteFieldButton = $( '<button/>', { 'class': 'delete-field-button', 'text': '×' } );
		row.append( nameInput );
		row.append( valueInput );
		row.append( deleteFieldButton );

		fields.append( row );

		deleteFieldButton.click( function() {
			$( this ).parent().hide( 'fast', function() {
				$( this ).remove();
			});
		});
	},

	/**
	 * 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} str 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( str, separator, limit ) {
		//If 'separator' is not a regex, use the native 'split'
		if ( Object.prototype.toString.call( separator ) !== "[object RegExp]" ) {
			return proveit._nativeSplit.call( str, 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;

		str = str + ""; //type conversion
		if ( ! proveit._compliantExecNpcg ) {
			separator2 = RegExp( "^" + separator.source + "$(?!\\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( str ) ) {
			lastIndex = match.index + match[0].length; //separator.lastIndex is not reliable cross-browser

			if ( lastIndex > lastLastIndex ) {
				output.push( str.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 < str.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 === str.length ) {
			if ( lastLength || ! separator.test("") ) {
				output.push("");
			}
		} else {
			output.push( str.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 MWEditBox, and create a reference object and refBoxRow for each.
	 */
	scanForReferences: function() {

		$( '#proveit-view-pane' ).children().remove();

		//First look for all the citations
		var	text = this.getTextboxText();
		var citations = [];
		//Three possibilities: <ref name="foo" />, <ref name='bar' /> or <ref name=baz />
		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 ) {
			console.log( this.getMessage( 'no_references' ) );
			var noReferencesMessage = $( '<div/>', { 'id': 'proveit-no-references-message', 'text': this.getMessage( 'no_references' ) } );
			$( '#proveit-view-pane' ).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 view pane
			var referenceRow = this.makeReferenceRow( reference );
			$( '#proveit-view-pane' ).append( referenceRow );
		}

	},

	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 possibilities: <ref name="foo">, <ref name='bar'>, and <ref name=baz>
		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 key = keys[ i ];
				var value = values[ i ];

				//Drop blank space, and |'s without params, which are never correct for citation templates
				key = $.trim( key ).replace( /(?:\s*\|)*(.*)/, "$1" );
				value = $.trim( value );

				//TODO: Should there be a setParam function?
				//It could handle empty values, and even drop (siliently or otherwise) invalid parameters
				//Alternatively, should params be passed in the constructor?
				if ( value ) {
					reference.params[ key ] = value;
				}
			}
		}
		return reference;
	},


	/**
	 * Generates refbox row and all children, to be used by scanForReferences
	 * @param {AbstractReference} ref reference to generate from
	 * @return {Node} new row for refbox
	 */
	makeReferenceRow: function( reference ) {

		//Create an empty row
		//Right now this rows only contain one td, so they could be replaced by divs, but in the near future they may not
		var row = $( '<div/>', { 'class': 'reference-row' } );

		//Fill the row
		if ( reference.type == 'RawReference' ) {

			var rowContent = reference.string;

		} else if ( reference.type == 'TemplateReference' ) {
	
			var rowContent = '<span class="template">' + reference.template + '</span>';
			
			//TODO: Display the first few params, not the required ones
			var requiredParams = reference.getRequiredParams();
			for ( var requiredParam in requiredParams ) {
				var label = reference.getParamLabel( requiredParam );
				var value = reference.params[ requiredParam ];
				rowContent += '<span class="label">' + label + '</span>: <span class="value">' + value + '</span>';
			}
		}

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

		row.html( rowContent );

		//Event handlers
		$( row ).click( function() {
			proveit.highlightTargetString( reference.string );
			if ( reference.type == 'TemplateReference' ) {
				proveit.fillEditPane( reference );
				$( '#proveit-view-pane' ).hide();
				$( '#proveit-edit-pane' ).show();
			}
		});

		$( 'a.citation', row ).click( function( event ) {
			event.stopPropagation();
			var i = $( 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 row;
	},

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

		var addTab = $( '#proveit-add-tab' );

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

		var paramRows = $( 'div.param-row', addTab );
		var paramRow, paramName, paramValue;
		for ( var i = 0; i < paramRows.length; i++ ) {
			paramRow =  paramRows[ i ];
			paramName = $( '.param-name', paramRow ).val();
			paramValue = $( '.param-value', paramRow ).val();

			if ( paramName != '' && paramValue != '' ) { //Non-blank
				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
		});
		var caretPos = textbox.textSelection( 'getCaretPosition', { startAndEnd: true } );

		this.addSummary();

		//Update the view pane by scanning the references again
		proveit.scanForReferences()
	},

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

		var reference = new this.TemplateReference( { 'template': template } );

		var addFields = $( '#proveit-add-fields' );

		//Remove all the fields except the <ref> name and the dropdown menu
		addFields.children().remove( '.param-row' );

		//Insert one row for each parameter, registered or not
		//Hide the non-default parameters, unless they are filled
		//Skip the delete button in the required parameters
		var registeredParams = reference.getRegisteredParams();
		var defaultParams = reference.getDefaultParams();
		var requiredParams = reference.getRequiredParams();

		for ( var paramName in registeredParams ) {

			var paramLabel = reference.getParamLabel( paramName );
			var paramValue = reference.params[ paramName ];

			var row = $( '<div/>', { 'class': 'input-row param-row' } );
			if ( ! ( paramName in defaultParams ) && ! paramValue ) {
				row.addClass( 'hidden' );
			}
			var label = $( '<label/>', { 'for': paramName, 'text': paramLabel } );
			var nameInput = $( '<input/>', { 'type': 'hidden', 'class': 'param-name', 'value': paramName } );
			var valueInput = $( '<input/>', { 'id': paramName, 'class': 'param-value', 'value': paramValue } );
			var deleteFieldButton = $( '<button/>', { 'class': 'delete-field-button', 'text': '×' } );
			row.append( label );
			row.append( nameInput );
			row.append( valueInput );
			if ( ! ( paramName in requiredParams ) ) {
				row.append( deleteFieldButton );
			}
			addFields.append( row );
		}
	},

	createGUI: function() {

		//TODO: There should be no need for this check
		if ( $( '#proveit' ).length > 0 ) {
			return false; //GUI already created
		}

		var gui = $( '<div/>', { id: 'proveit' } );

		var tabs = $( '<div/>', { id: 'proveit-tabs' } );

		//Logo
		var logo = $( '<img/>', { id: 'proveit-logo', src: proveit.LOGO, alt: 'ProveIt' } );
		tabs.append( logo );


		/* Tab list */
		var list = $( '<ul/>' );

		//View tab link
		var viewItem = $( '<li/>' );
		var viewLink = $( '<a/>', { id: 'view-link', 'class': 'tab-link', href: '#proveit-view-tab', 'text': this.getMessage( 'edit_tab' ) } );
		viewItem.append( viewLink );
		list.append( viewItem );

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

		tabs.append( list );


		/* View tab */
		var viewTab = $( '<div/>', { id: 'proveit-view-tab', 'class': 'hidden' } );

		//View pane
		var viewPane = $( '<div/>', { id: 'proveit-view-pane' } );
		viewTab.append( viewPane );

		//Edit pane
		var editPane = $( '<div/>', { id: 'proveit-edit-pane', 'class': 'hidden scroll' } );

		//Edit fields
		var editFields = $( '<div/>', { id: 'proveit-edit-fields' } );
		editPane.append( editFields );

		//Edit buttons
		var editButtons = $( '<div/>', { id: 'proveit-edit-buttons' } );
		var addFieldButton = $( '<button/>', { 'class': 'add-field-button', 'text': this.getMessage( 'add_field_button' ) } );
		var paramsButton = $( '<button/>', { 'class': 'params-button', 'text': this.getMessage( 'params_button' ) } );
		var updateButton = $( '<button/>', { 'class': 'update-button', 'text': this.getMessage( 'update_button' ) } );

		editButtons.append( addFieldButton );
		editButtons.append( paramsButton );
		editButtons.append( updateButton );
		editPane.append( editButtons );
		viewTab.append( editPane );
		tabs.append( viewTab );


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

		//First insert the row for the <ref> name
		var refNameRow = $( '<div/>', { 'class': 'ref-name-row input-row' } );
		var refNameLabel = $( '<label/>', { 'for': 'add-ref-name', 'text': this.getMessage( 'ref_name_label' ) } );
		var refNameInput = $( '<input/>', { id: 'add-ref-name', 'class': 'param-value' } );
		var deleteFieldButton = $( '<button/>', { 'class': 'delete-field-button', 'text': '×' } );
		refNameRow.append( refNameLabel );
		refNameRow.append( refNameInput );
		refNameRow.append( deleteFieldButton );
		addFields.append( refNameRow );

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

		addTab.append( addFields );

		//After the fields, insert the buttons
		var addButtons = $( '<div/>', { id: 'proveit-add-buttons' } );
		var insertButton = $( '<button/>', { 'class': 'insert-button', 'text': this.getMessage( 'insert_button' ) } );
		var paramsButton = $( '<button/>', { 'class': 'params-button', 'text': this.getMessage( 'params_button' ) } );
		addButtons.append( addFieldButton.clone() );
		addButtons.append( paramsButton );
		addButtons.append( insertButton );
		addTab.append( addButtons );

		//Fourth, add everything to the DOM
		tabs.append( addTab );
		gui.append( tabs );
		$( document.body ).prepend( gui );

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

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

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

		viewLink.click( function() {
			if ( viewTab.is( ':visible' ) ) {
				viewPane.show();
				editPane.hide();
			} else {
				viewTab.show();
			}
		});

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

		insertButton.click( function() {
			proveit.insertReference();
			$( '#proveit-tabs' ).tabs( { selected: '#proveit-view-tab' } );
			$( 'div.scroll, #proveit-view-pane' ).scrollTop( 100000 ); //Scroll to the new reference
		});

		$( '#proveit-add-buttons .add-field-button' ).click( function() {
			proveit.addCustomField( $( '#proveit-add-fields' ) );
		});

		$( '#proveit-edit-buttons .add-field-button' ).click( function() {
			proveit.addCustomField( $( '#proveit-edit-fields' ) );
		});

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

		$( '#proveit-add-buttons .params-button' ).click( function() {
			$( this ).hide();
			$( '#proveit-add-buttons .add-field-button' ).show();
			$( '#proveit-add-fields .param-row' ).show();
		});

		$( '.delete-field-button' ).click( function() {
			//First hide the row with a quick animation, then remove it
			$( this ).parent().hide( 'fast', function() {
				$( this ).remove();
			});
		});

		this.scanForReferences();
	},


	RawReference: function( argObj ) {

		this.type = 'RawReference';

		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();
			textbox[0].focus();

			var text = proveit.getTextboxText();
			text = text.replace( this.string, this.toString() );

			//Do replacement in textarea
			textbox.val( text );

			//Baseline for future modifications

			this.string = this.toString();
			this.save = true;

			proveit.highlightTargetString( this.toString() );
		};
	},

	TemplateReference: function( argObj ) {

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

		//Override
		this.type = 'TemplateReference';

		this.params = {};

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

		this.update = function() {

			var editPane = $( '#proveit-edit-pane' );
		
			var name = $( '#edit-ref-name' ).val();
			this.name = name ? name : null; //Save blank names as null
	
			//Clear old params
			this.params = {};

			var paramRows = $( 'div.param-row', editPane );
			var paramRow, paramName, paramValue;
			for ( var i = 0; i < paramRows.length; i++ ) {
				paramRow = paramRows[ i ];
				paramName = $( '.param-name', paramRow ).val();
				paramValue = $( '.param-value', paramRow ).val();
				if ( paramName != '' && paramValue != '' ) {
					this.params[ paramName ] = paramValue;
				}
			}

			this.updateInTextbox();
			proveit.scanForReferences();
			proveit.addSummary();
			$( '#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() {
			return proveit.getRegisteredTemplates()[ this.template ];
		};

		this.getParamLabel = function( param ) {
			return this.getRegisteredParams()[ param ][ 'label' ];
		};

		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.getDefaultParams = function() {
			var defaultParams = {};
			var registeredParams = this.getRegisteredParams();
			for ( var registeredParam in registeredParams ) {
				if ( registeredParams[ registeredParam ][ 'default' ] === true ) {
					defaultParams[ registeredParam ] = registeredParams[ registeredParam ];
				}
			}
			return defaultParams;
		};
	}

}, window.proveit );

$( proveit.start );

}( mediaWiki, jQuery ) );