Järjestelmäviesti:Gadget-ProveItCore.js

Wikipediasta
Siirry navigaatioon Siirry hakuun

Huomautus: Selaimen välimuisti pitää tyhjentää asetusten tallentamisen jälkeen, jotta muutokset tulisivat voimaan.

  • Firefox ja Safari: Napsauta Shift-näppäin pohjassa Päivitä, tai paina Ctrl-F5 tai Ctrl-R (⌘-R Macilla)
  • Google Chrome: Paina Ctrl-Shift-R (⌘-Shift-R Macilla)
  • Internet Explorer ja Edge: Napsauta Ctrl-näppäin pohjassa Päivitä tai paina Ctrl-F5
  • Opera: Paina Ctrl-F5.
/**
 * ProveIt is a Wikipedia gadget that makes it easy to find, edit, add and delete references when editing Wikipedia articles.
 * Full documentation at https://commons.wikimedia.org/wiki/Help:Gadget-ProveIt
 *
 * Copyright 2008-2011 Georgia Tech Research Corporation, Atlanta, GA 30332-0415, ALL RIGHTS RESERVED
 * Copyright 2011- Matthew Flaschen
 * Rewritten, internationalized, enhanced and maintained by Felipe Schenone since 2014show-all-parameters-
 *
 * ProveIt is available under the GNU Free Documentation License (http://www.gnu.org/copyleft/fdl.html),
 * the 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, $ ) {

var proveit = {

	/**
	 * URL of the ProveIt icons hosted at Commons
	 */
	ICON: '//upload.wikimedia.org/wikipedia/commons/3/3f/VisualEditor_-_Icon_-_add-citation.svg',

	OLDICON: '//upload.wikimedia.org/wikipedia/commons/d/df/ProveitOldIcon.png',

	/**
	 * Template data retrieved from the wiki
	 *
	 * @type {object} mapping template name to templateData
	 */
	templateData: {},

	/**
	 * Content language of the wiki
	 *
	 * @type {string} defaults to English
	 */
	contentLanguage: 'en',

	/**
	 * Convenience method to get a ProveIt option
	 *
	 * @param {string} option key without the "proveit-" prefix
	 * @return {string} option value
	 */
	getOption: function ( key ) {
		return mw.config.get( 'proveit-' + key );
	},

	/**
	 * Convenience method to get a ProveIt message
	 *
	 * @param {string} message key without the "proveit-" prefix
	 * @param {int} number of references
	 * @return {string} message value
	 */
	getMessage: function ( key, param ) {
		return mw.message( 'proveit-' + key, param );
	},

	/**
	 * Convenience method to get the edit textbox
	 *
	 * @return {jQuery} edit textbox
	 */
	getTextbox: function () {
		return $( '#wpTextbox1' );
	},

	/**
	 * Initialization script
	 *
	 * @return {void}
	 */
	init: function () {

		// Set the content language
		proveit.contentLanguage = mw.config.get( 'wgContentLanguage' );

		// Get the interface messages from Commons
		var userLanguage = mw.config.get( 'wgUserLanguage' );
		$.get( '//fi.wikipedia.org/w/api.php', {
			'titles': 'MediaWiki:Gadget-ProveIt-' + userLanguage + '.json|MediaWiki:Gadget-ProveIt-en.json', // Get the English messages as fallback
			'action': 'query',
			'prop': 'revisions',
			'rvprop': 'content',
			'format': 'json',
			'origin': '*' // Allow requests from any origin so that ProveIt can be used on localhost and non-Wikimedia sites
		}).done( function ( data ) {
			//console.log( data );
			var page, enMessages, userLanguageMessages;
			for ( page in data.query.pages ) {
				page = data.query.pages[ page ];
				if ( page.title === 'MediaWiki:Gadget-ProveIt-en.json' ) {
					enMessages = JSON.parse( page.revisions[0]['*'] );
				} else if ( 'revisions' in page ) {
					userLanguageMessages = JSON.parse( page.revisions[0]['*'] );
				}
			}
			if ( userLanguageMessages ) {
				mw.messages.set( userLanguageMessages );
			} else {
				mw.messages.set( enMessages ); // Fallback to English
			}

			// Build the interface
			proveit.build();

			// Get the template data from the wiki
			var templates = proveit.getOption( 'templates' );
			new mw.Api().get({
				'action': 'templatedata',
				'titles': templates ? templates.join( '|' ) : null,
				'format': 'json'
			}).done( function ( data ) {
				//console.log( data );
				for ( var page in data.pages ) {
					page = data.pages[ page ];
					proveit.templateData[ page.title ] = page;
				}
				proveit.parse();
				proveit.automatic();
			});
		});

		// Replace the Reference button for the ProveIt button in the new toolbar
		if ( mw.user.options.get( 'usebetatoolbar' ) === 1 ) {
			proveit.getTextbox().wikiEditor( 'removeFromToolbar', {
				'section': 'main',
				'group': 'insert',
				'tool': 'reference'
			});
			proveit.getTextbox().wikiEditor( 'addToToolbar', {
				'section': 'main',
				'group': 'insert',
				'tools': {
					'proveit': {
						'label': 'ProveIt',
						'type': 'button',
						'icon': proveit.ICON,
						'action': {
							'type': 'callback',
							'execute': function () {
								$( '#proveit-logo' ).click();
							}
						}
					}
				}
			});
		// Add the ProveIt button in the old toolbar
		} else if ( mw.user.options.get( 'showtoolbar' ) === 1 ) {
			mw.loader.using( 'mediawiki.toolbar', function () {
				$( '<div>' )
				.addClass( 'mw-toolbar-editbutton' )
				.attr( 'title', 'ProveIt' )
				.css( 'background-image', 'url(' + proveit.OLDICON + ')' )
				.appendTo( '#toolbar' ).click( function () {
					$( '#proveit-logo' ).click();
				});
			});
		}
	},

	/**
	 * Build the GUI and add it to the DOM
	 *
	 * @return {void}
	 */
	build: function () {

		// Define the basic elements
		var gui = $( '<div>' ).attr( 'id', 'proveit' ),
			header = $( '<div>' ).attr( 'id', 'proveit-header' ),
//			logo = $( '<span>' ).attr( 'id', 'proveit-logo' ).text( 'ProveIt' ),
			logo = $( '<span>' ).attr( 'id', 'proveit-logo' ).text( proveit.getMessage( 'text-logo' ) ),
			leftBracket = $( '<span>' ).attr( 'id', 'proveit-left-bracket' ).text( '[' ),
			rightBracket = $( '<span>' ).attr( 'id', 'proveit-right-bracket' ).text( ']' ),
			automaticTab = $( '<span>' ).attr( 'id', 'proveit-automatic-add-tab' ).addClass( 'active' ).text( proveit.getMessage( 'automatic-add-tab' ) ),
			addTab = $( '<span>' ).attr( 'id', 'proveit-manual-add-tab' ).text( proveit.getMessage( 'manual-add-tab' ) ),
			listTab = $( '<span>' ).attr( 'id', 'proveit-list-tab' ).text( proveit.getMessage( 'list-tab', 0 ) ),
			content = $( '<div>' ).attr( 'id', 'proveit-content' );

		// Put everything together and add it to the DOM
		logo.prepend( leftBracket ).append( rightBracket );
		header.append( logo, automaticTab, addTab, listTab );
		gui.append( header,	content );
		$( 'body' ).prepend( gui );

		// Make the GUI draggable
		var dragged = false;
		mw.loader.using( 'jquery.ui' ).then( function () {
			gui.draggable({
				handle: header,
				containment: 'window',
				start: function ( event ) {
					if ( event.toElement.id !== 'proveit-header' ) {
						dragged = true;
					}
					gui.css({
						'right': 'auto',
						'bottom': 'auto'
					});
				}
			});
		});

		// Lastly, bind events
		logo.click( function () {
			if ( dragged ) {
				dragged = false; // Reset the flag
				return;
			}
			listTab.toggle();
			addTab.toggle();
			automaticTab.toggle();
			content.toggle();
			gui.css({
				'top': 'auto',
				'left': 'auto',
				'right': 0,
				'bottom': 0
			});
		}).click(); // Click the logo to hide the gadget by default

		listTab.click( function () {
			if ( dragged ) {
				dragged = false; // Reset the flag
				return;
			}
			$( this ).addClass( 'active' ).siblings().removeClass( 'active' );

			proveit.parse();
		});

		addTab.click( function () {
			if ( dragged ) {
				dragged = false; // Reset the flag
				return;
			}
			// Create a dummy reference and a dummy form out of it
			var template = mw.cookie.get( 'proveit-last-template' ), // Remember the user choice
				dummyReference = new proveit.Reference({ 'template': template }),
				dummyForm = dummyReference.toForm();

			content.html( dummyForm );

			// Switch the GUI to add mode
			$( this ).addClass( 'active' ).siblings().removeClass( 'active' );
			$( '#proveit-insert-button' ).show();
			$( '#proveit-cite-button, #proveit-remove-button, #proveit-update-button' ).hide();
		});
		
		automaticTab.click( function () {
			if ( dragged ) {
				dragged = false; // Reset the flag
				return;
			}
			// Create a search form
			proveit.automatic();


			// Switch the GUI to add mode
			$( this ).addClass( 'active' ).siblings().removeClass( 'active' );
			$( '#proveit-insert-button' ).show();
			$( '#proveit-cite-button, #proveit-remove-button, #proveit-update-button' ).hide();
		});
	},

	automatic: function() {
		function createTemplateOptionRow(templateName, templateValue, defaultTemplate)
		{
			templateOptionRow=$('<li>');
			templateOption = $( '<input>' )
								.attr( 'type', 'radio' )
								.attr('name', 'automatic-reference-type' )
								.attr('id', 'automatic-reference-type' ).val(templateValue);
			if (templateName==defaultTemplate) templateOption.prop('checked',true);
			
			templateOptionLabel= $('<span>').text(templateName);
			templateOptionRow.append(templateOption);	
			templateOptionRow.append(templateOptionLabel);	
			return templateOptionRow;
		}
		
		var searchFormInfoText=$("<div>").attr('id', 'proveit-automatic-info-text').text(proveit.getMessage( 'automatic-info-text'));
		var searchForm = $('<form>').attr('id', 'proveit-citoid-form');
		var inputField=$('<input>')
			.attr('type','text')
			.attr('name','proveit-citoid-search')
			.attr('id','proveit-citoid-search')
			.val(proveit.getMessage( 'citoid-search-default-value' ))
			.css('color', 'silver');
		var submitButton=$('<input>')
			.attr('type','button')
			.css('float', 'right')
			.addClass('mw-ui-button mw-ui-progressive')
			.attr('value',proveit.getMessage( 'automatic-search-button' ));

		templateOptionRows=$('<ul>').addClass('automatic-reference-type-select');		
		var defaultTemplate = mw.cookie.get( 'proveit-last-template' ); // Remember the user choice
		if (defaultTemplate==="") defaultTemplate="Verkkoviite";
		
		templateName = proveit.getMessage( 'no-template' ),
		templateOptionRows.append(createTemplateOptionRow(templateName, "", defaultTemplate));

		for ( var templateTitle in proveit.templateData ) {
			templateName = templateTitle.substr( templateTitle.indexOf( ':' ) + 1 ); // Remove the namespace
			templateOptionRows.append(createTemplateOptionRow(templateName, templateName, defaultTemplate));
		}
			
		searchForm.append(searchFormInfoText);
		searchForm.append(inputField);
		searchForm.append(submitButton);
		searchForm.append(templateOptionRows);
		$( '#proveit-content' ).html(searchForm);
		
		inputField.focus(function()
		{
			$(this).off( "focus" );
			$(this).val("");
			$(this).css('color', 'black');
		});
		
		submitButton.click(function() {
			var searchKey=$("#proveit-citoid-search").val();
			var templateName=$('input[name="automatic-reference-type"]:checked').val();
			mw.cookie.set( 'proveit-last-template', templateName ); // Remember the user choice
			
			//alert('"' + templateName +'"');
			

			if (searchKey==="" || searchKey==proveit.getMessage( 'citoid-search-default-value' )) {
				dummyReference = new proveit.Reference({ 'template': templateName });
				dummyForm = dummyReference.toForm();
				$( '#proveit-content' ).html( dummyForm );				

				// Switch the GUI to add mode
				$( '#proveit-manual-add-tab' ).addClass( 'active' ).siblings().removeClass( 'active' );
				$( '#proveit-insert-button' ).show();
				$( '#proveit-cite-button, #proveit-remove-button, #proveit-update-button' ).hide();				
				return;
			}
			$(this).prop('disabled', true);
			$('#proveit-automatic-info-text').text(proveit.getMessage( 'fetching-data'));
//			var url="https://citoid.wikimedia.org/api?action=query&format=mediawiki&basefields=true&search=" + encodeURIComponent(searchKey);
			var url="https://fi.wikipedia.org/api/rest_v1/data/citation/mediawiki/" + encodeURIComponent(searchKey) + "?action=query&format=json";
			$.getJSON(url, function(data) {
				if ((typeof(data)!=="undefined") && (typeof(data[0])!=="undefined"))
				{
					var d=data[0];
	
					var params = {
					   "osoite": "url",
					   "nimeke": "title",
					   "julkaisu": "publicationTitle",
					   "viitattu": "accessDate",
					   "ajankohta": "date",
					   "ietf-kielikoodi": "language"
					};
					dummyReference = new proveit.Reference({ 'template': templateName });
					if (templateName!=="")
					{
						$.each(params, function( paramName, paramValue ) {
							if (typeof(d[paramValue])!=="undefined")
							{
								dummyReference.params[ paramName ] = d[paramValue];
							}
						});
					}
					dummyForm = dummyReference.toForm();
					$( '#proveit-content' ).html( dummyForm );

					// Switch the GUI to add mode
					$( '#proveit-manual-add-tab' ).addClass( 'active' ).siblings().removeClass( 'active' );
					$( '#proveit-insert-button' ).show();
					$( '#proveit-cite-button, #proveit-remove-button, #proveit-update-button' ).hide();
				}
			});
		});		
	},
	/**
	 * Parse the textbox for references and add them to the GUI
	 *
	 * @return {void}
	 */
	parse: function () {

		// First define the list element
		var referenceList = $( '<ol>' ).attr( 'id', 'proveit-reference-list' );

		// Second, look for all the citations in the wikitext and store them in an array for later
		var wikitext = proveit.getTextbox().val(),
			citations = [],
			citationsRegExp = /<\s*ref\s+name\s*=\s*["|']?\s*([^"'\s]+)\s*["|']?\s*\/\s*>/ig, // Three patterns: <ref name="foo" />, <ref name='foo' /> and <ref name=foo />
			match,
			citation;

		while ( ( match = citationsRegExp.exec( wikitext ) ) ) {
			citation = new proveit.Citation({ 'name': match[1], 'index': match.index, 'string': match[0] });
			citations.push( citation );
		}

		// Third, look for all the references
		var matches = wikitext.match( /<\s*ref[^\/]*>[\s\S]*?<\s*\/\s*ref\s*>/ig ); // We use [\s\S]* instead of .* to match newlines

		if ( matches ) {
			var i, j, reference, referenceItem;
			for ( i = 0; i < matches.length; i++ ) {
				// Turn all the matches into reference objects
				reference = proveit.parseReference( matches[ i ] );

				// For each reference, check the citations array for citations to it
				for ( j = 0; j < citations.length; j++ ) {
					citation = citations[ j ];
					if ( reference.name === citation.name ) {
						reference.citations.push( citation );
					}
				}

				// Finally, turn all the references into list items and insert them into the reference list
				referenceItem = reference.toListItem();

				// Add the reference number
				// We don't use the <ol> numbers because of stying reasons
				referenceItem.prepend( $( '<span>' ).addClass( 'proveit-reference-number' ).text( i + 1 ) );

				// Add the item to the list
				referenceList.append( referenceItem );
			}

			$( '#proveit-list-tab' ).text( proveit.getMessage( 'list-tab', i ) );
			$( '#proveit-content' ).html( referenceList );

		} else {
			$( '#proveit-list-tab' ).text( proveit.getMessage( 'list-tab', 0 ) );
			$( '#proveit-content' ).html( $( '<div>' ).attr( 'id', 'proveit-no-references-message' ).text( proveit.getMessage( 'no-references' ) ) );
		}
	},

	/**
	 * Make a reference object out of a reference string
	 *
	 * @param {string} wikitext of the reference
	 * @return {object} reference object
	 */
	parseReference: function ( referenceString ) {
		// Extract the reference name, if any
		// Three patterns: <ref name="foo">, <ref name='foo'> and <ref name=foo>
		var referenceName = null,
			match = referenceString.match( /<[\s]*ref[\s]*name[\s]*=[\s]*(?:(?:\"(.*?)\")|(?:\'(.*?)\')|(?:(.*?)))[\s]*>/i );
		if ( match ) {
			referenceName = match[1] || match[2] || match[3];
		}

		// Get the index
		var referenceIndex = proveit.getTextbox().val().indexOf( referenceString );

		// Extract the content
		var referenceContent = referenceString.match( />([\s\S]*)<\s*\/\s*ref\s*>/i )[1]; // We use [\s\S]* instead of .* to match newlines

		// Build the basic reference
		var reference = new proveit.Reference({
			'name': referenceName,
			'index': referenceIndex,
			'string': referenceString,
			'content': referenceContent
		});

		// Search for the main template of the reference
		var templateTitles = Object.keys( proveit.templateData ).sort().reverse(), // Match "Cite books" before "Cite book"
			templateTitle,
			templateName,
			templateRegex,
			indexStart;
		for ( var i = 0; i < templateTitles.length; i++ ) {
			templateTitle = templateTitles[ i ];
			templateName = templateTitle.substring( templateTitle.indexOf( ':' ) + 1 ); // Remove the namespace
			templateRegex = new RegExp( '{{\\s*' + templateName + '[\\s|]', 'i' );
			indexStart = referenceContent.search( templateRegex );
			if ( indexStart > -1 ) {
				reference.template = templateName;
				break;
			}
		}

		// The rest of the code is for when a main template was found
		if ( reference.template ) {

			// Figure out the indexEnd by searching for the closing "}}"
			// knowing there may be subtemplates and other templates after the main template
			var indexEnd = referenceContent.length,
				templateLevel = 0;
			for ( i = indexStart; i < indexEnd; i++ ) {
				if ( referenceContent[ i ] + referenceContent[ i + 1 ] === '{{' ) {
					templateLevel++;
					i++; // We speed up the loop to avoid multiple matches when two or more templates are found together
				} else if ( referenceContent[ i ] + referenceContent[ i + 1 ] === '}}' ) {
					templateLevel--;
					i++;
				}
				if ( templateLevel === 0 ) {
					indexEnd = i + 1;
					break;
				}
			}
			reference.templateString = referenceContent.substring( indexStart, indexEnd );

			/**
			 * Parse the parameters inside the main template. A complex example may be:
			 * {{Cite book
			 * |anonymous parameter
			 * |param1 = value1
			 * |param2 = http://example.com?query=string
			 * |param3 = [[Some|link]]
			 * |param4 = {{Subtemplate |anon |param=value}}
			 * }}
			 */

			// Remove the outer braces and split by pipe, knowing that we may match pipes inside links and subtemplates
			var paramArray = reference.templateString.substring( 2, reference.templateString.length - 2 ).split( '|' );
			paramArray.shift(); // Get rid of the template name

			var paramString, inLink = 0, inSubtemplate = 0, indexOfEqual, paramNumber = 0, paramName, paramValue;
			for ( i = 0; i < paramArray.length; i++ ) {

				paramString = paramArray[ i ].trim();

				// If we're inside a link or subtemplate, don't disturb it
				if ( inLink || inSubtemplate ) {
					reference.params[ paramName ] += '|' + paramString;
					if ( paramString.indexOf( ']]' ) > -1 ) {
						inLink--;
					}
					if ( paramString.indexOf( '}}' ) > -1 ) {
						inSubtemplate--;
					}
					continue;
				}

				// If we reach this point and there's no equal sign, it's an anonymous parameter
				indexOfEqual = paramString.indexOf( '=' );
				if ( indexOfEqual === -1 ) {
					paramNumber++;
					paramName = paramNumber;
					paramValue = paramString;
					continue;
				}

				paramName = paramString.substring( 0, indexOfEqual ).trim();
				paramValue = paramString.substring( indexOfEqual + 1 ).trim();

				// Check if there's an unclosed link or subtemplate
				if ( paramValue.indexOf( '[[' ) > -1 && paramValue.indexOf( ']]' ) === -1 ) {
					inLink++;
				}
				if ( paramValue.indexOf( '{{' ) > -1 && paramValue.indexOf( '}}' ) === -1 ) {
					inSubtemplate++;
				}

				reference.params[ paramName ] = paramValue;
			}
		}

		return reference;
	},

	/**
	 * Add the ProveIt revision tag
	 *
	 * @return {void}
	 */
	addTag: function () {
		var tag = proveit.getOption( 'tag' );
		if ( !tag ) {
			return; // No tag defined
		}
		if ( $( '#wpChangeTags' ).length > 0 ) {
			return; // Don't add it twice
		}
		var tagInput = $( '<input>' ).attr({
			'id': 'wpChangeTags',
			'type': 'hidden',
			'name': 'wpChangeTags',
			'value': tag
		});
		$( '#editform' ).prepend( tagInput );
	},

	/**
	 * Add the ProveIt edit summary
	 *
	 * @return {void}
	 */
	addSummary: function () {
		var currentSummary = $( '#wpSummary' ).val(),
			proveitSummary = proveit.getOption( 'summary' );
		if ( !proveitSummary ) {
			return; // No summary defined
		}
		if ( currentSummary.indexOf( proveitSummary ) > -1 ) {
			return; // Don't add it twice
		}
		$( '#wpSummary' ).val( currentSummary ? currentSummary + proveitSummary : proveitSummary );
	},

	/**
	 * Citation class
	 *
	 * @param {object} data for constructing the object
	 */
	Citation: function ( data ) {

		/**
		 * Citation name
		 */
		this.name = data.name ? data.name : '';

		/**
		 * Citation location in the edit textbox
		 */
		this.index = data.index ? data.index : 0;

		/**
		 * Citation wikitext
		 *
		 * Example: <ref name="abc" />
		 */
		this.string = data.string ? data.string : '';

		/**
		 * Convert this citation to wikitext
		 */
		this.toString = function () {
			return '<ref name="' + this.name + '" />';
		};

		/**
		 * Insert this citation into the textbox
		 */
		this.insert = function () {
			var textbox = proveit.getTextbox();
			textbox.textSelection( 'encapsulateSelection', {
				'peri': this.toString(),
				'replace': true
			});
		};

		/**
		 * Highlight the citation in the textbox and scroll it to view
		 *
		 * @return {void}
		 */
		this.highlight = function () {
			var textbox = proveit.getTextbox()[0],
				text = textbox.value;

			// Scroll to the string
			textbox.value = text.substring( 0, this.index );
			textbox.focus();
			textbox.scrollTop = 99999999; // Larger than any real textarea (hopefully)
			var currentScrollTop = textbox.scrollTop;
			textbox.value += text.substring( this.index );
			if ( currentScrollTop > 0 ) {
				textbox.scrollTop = currentScrollTop + 300;
			}

			// Highlight the string
			var start = this.index,
				end = start + this.string.length;
			$( textbox ).focus().textSelection( 'setSelection', { 'start': start, 'end': end } );
		};
	},

	/**
	 * Reference class
	 *
	 * @param {object} Data for constructing the object
	 */
	Reference: function ( data ) {

		/**
		 * Extend the Citation class
		 */
		proveit.Citation.call( this, data );

		/**
		 * Reference content (without the <ref> tags)
		 *
		 * Example: Second chapter of {{Cite book |first=Charles |last=Darwin |title=On the Origin of Species}}
		 */
		this.content = data.content ? data.content : '';

		/**
		 * Array of citations to this reference
		 */
		this.citations = [];

		/**
		 * Name of the main template of this reference, if any
		 *
		 * Example: Cite book
		 */
		this.template = data.template ? data.template : null;

		/**
		 * Wikitext of the main template, if any
		 *
		 * Example: {{Cite book |first=Charles |last=Darwin |title=On the Origin of Species}}
		 */
		this.templateString = data.templateString ? data.templateString : '';

		/**
		 * Object mapping the parameter names of this reference to their values
		 *
		 * This object is constructed directly out of the wikitext, so it doesn't include
		 * any information about the parameters other than their names and values.
		 * Also the parameter names here may be aliases.
		 */
		this.params = {};

		/**
		 * Insert a citation to this reference
		 *
		 * @return {void}
		 */
		this.cite = function ( event ) {
			var reference = event.data;

			// If the reference has no name, ask the user for one
			// @todo check if the name is unique?
			// @todo autogenerate names?
			if ( !reference.name ) {
				reference.name = prompt( proveit.getMessage( 'prompt-name' ) );
				if ( !reference.name ) {
					return;
				}
				var table = reference.toTable();
				$( '#proveit-reference-table' ).replaceWith( table );

				var oldString = reference.string;
				reference.loadFromForm();
				var newString = reference.string;

				// Replace the old reference
				var textbox = proveit.getTextbox(),
					text = textbox.val().replace( oldString, newString );
				textbox.val( text );
			}
			else
			{
				// Add the citation
				var citation = new proveit.Citation({ 'name': reference.name });
				citation.insert();
			}
		};

		/**
		 * Remove this reference and all its citations
		 *
		 * @param {object} click event, with the reference as data
		 * @return {void}
		 */
		this.remove = function ( event ) {
			var reference = event.data;

			// Make sure the user understands that the citations will also be removed
			if ( confirm( proveit.getMessage( 'confirm-remove' ) ) ) {

				// Remove the reference
				var textbox = proveit.getTextbox(),
					text = textbox.val();
				text = text.replace( reference.string, '' );

				// Remove all the citations
				// @todo citation.remove()
				reference.citations.forEach( function ( citation ) {
					text = text.replace( citation.string, '' );
				});

				// Update the textbox
				textbox.val( text );

				// Add the tag and summary
				proveit.addTag();
				proveit.addSummary();

				// Switch to add mode
				$( '#proveit-add-tab' ).addClass( 'active' ).siblings().removeClass( 'active' );
				$( '#proveit-insert-button' ).show();
				$( '#proveit-cite-button, #proveit-remove-button, #proveit-update-button' ).hide();
			}
		};

		/**
		 * Update the wikitext in the textbox with the current data of this reference
		 *
		 * @param {object} click event, with the reference as data
		 * @return {void}
		 */
		this.update = function ( event ) {
			var reference = event.data;

			var oldName = reference.name,
				oldString = reference.string;
			reference.loadFromForm();
			var newName = reference.name,
				newString = reference.string;

			// Replace the old reference
			var textbox = proveit.getTextbox(),
				text = textbox.val();
			text = text.replace( oldString, newString );

			// If the name changed, update the citations
			// @todo citation.update()
			if ( oldName !== newName && reference.citations ) {
				var oldCitationString, newCitationString;
				reference.citations.forEach( function ( citation ) {
					oldCitationString = citation.toString();
					citation.name = newName;
					newCitationString = citation.toString();
					text = text.replace( oldCitationString, newCitationString );
				});
			}

			// Update the textbox
			textbox.val( text );

			// Update the index and highlight the reference
			text = textbox.val();
			reference.index = text.indexOf( newString );
			reference.highlight();

			// Update the GUI
			$( '#proveit-reference-table' ).replaceWith( reference.toTable() );

			// Add the tag and summary
			proveit.addTag();
			proveit.addSummary();
		};

		/**
		 * Insert this reference into the textbox
		 *
		 * @param {object} click event, with the reference as data
		 * @return {void}
		 */
		this.insert = function ( event ) {
			var reference = event.data;

			reference.loadFromForm();

			// Replace the existing selection (if any)
			var string = reference.string,
				textbox = proveit.getTextbox();

			textbox.textSelection( 'encapsulateSelection', {
				'peri': string,
				'replace': true
			});

			// Update the index
			reference.index = textbox.val().indexOf( reference.string );

			// Switch to edit mode
//			$( '#proveit-list-tab' ).addClass( 'active' ).siblings().removeClass( 'active' );
			$( '#proveit-reference-table' ).replaceWith( reference.toTable() );
			$( '#proveit-insert-button' ).hide().siblings().show();

			// Add the tag and summary
			proveit.addTag();
			proveit.addSummary();
		};

		/**
		 * Get the template title, like "Template:Cite book"
		 *
		 * @return {string} full template name
		 */
		this.getTemplateTitle = function () {
			var templateTitle = '';
			if ( this.template ) {
				var formattedNamespaces = mw.config.get( 'wgFormattedNamespaces' ),
					templateNamespace = formattedNamespaces[10];
				templateTitle = templateNamespace + ':' + this.template;
			}
			return templateTitle;
		};

		/**
		 * Get the template data for this reference
		 *
		 * @return {object} Template data
		 */
		this.getTemplateData = function () {
			var templateData = {};
			if ( this.template ) {
				var templateTitle = this.getTemplateTitle();
				templateData = proveit.templateData[ templateTitle ];
			}
			return templateData;
		};

		/**
		 * Get the Map object for ProveIt from the template data
		 *
		 * @return {object} Map object
		 */
		this.getTemplateMap = function () {
			var templateMap = {};
			if ( this.template ) {
				var templateData = this.getTemplateData();
				if ( 'maps' in templateData && 'proveit' in templateData.maps ) {
					templateMap = templateData.maps.proveit;
				}
			}
			return templateMap;
		};

		/**
		 * Get the parameter order from the template data
		 *
		 * @return {array}
		 */
		this.getParamOrder = function () {
			var paramOrder = [];
			if ( this.template ) {
				var templateData = this.getTemplateData();
				paramOrder = templateData.paramOrder;
			}
			return paramOrder;
		};

		/**
		 * Get the value of the main parameter for this reference
		 *
		 * @return {string} value of the main parameter for this reference
		 */
		this.getMainValue = function () {
			var mainValue = this.content;
			if ( this.template ) {
				var templateMap = this.getTemplateMap();
				if ( 'main' in templateMap && templateMap.main in this.params ) {
					mainValue = this.params[ templateMap.main ];
				} else {
					var templateData = this.getTemplateData(),
						paramName;
					for ( var i = 0; i < templateData.paramOrder.length; i++ ) {
						paramName = templateData.paramOrder[ i ];
						if ( paramName in this.params ) {
							mainValue = this.params[ paramName ];
							break;
						}
					}
				}
			}
			return mainValue;
		};

		/**
		 * Convert this reference to wikitext
		 *
		 * @return {string} wikitext for this reference
		 */
		this.toString = function () {
			// Build the opening ref tag
			var string = '<ref';
			if ( this.name ) {
				string += ' name="' + this.name + '"';
			}
			string += '>';

			// Build the template string
			if ( this.template ) {
				var templateString = '{{' + this.template;
				for ( var name in this.params ) {
					templateString += ' |' + name + '=' + this.params[ name ];
				}
				templateString += '}}';
				// Build the content string by replacing the old template string with the new
				// By doing this we keep any content that before or after the template string
				this.content = this.content.replace( this.templateString, templateString );
				this.templateString = templateString;
			}
			// Add the content
			string += this.content;

			// Close and return the reference
			string += '</ref>';
			return string;
		};

		/**
		 * Convert this reference to a list item
		 *
		 * @return {jQuery} list item
		 */
		this.toListItem = function () {
			var item = $( '<li>' ).addClass( 'proveit-reference-item' );

			// Add the main template, if any
			if ( this.template ) {
				var templateSpan = $( '<span>' ).addClass( 'proveit-reference-template' ).text( this.template );
				item.html( templateSpan );
			}

			// Add the main value of the reference
			var mainValue = this.getMainValue();
			item.append( mainValue );

			// Add the citations
			var citations = $( '<span>' ).addClass( 'proveit-citations' );
			for ( var i = 1; i <= this.citations.length; i++ ) {
				citations.append( $( '<a>' ).addClass( 'proveit-citation' ).text( i ) );
			}
			item.append( citations );

			// Bind events
			item.click( this, function ( event ) {
				var reference = event.data,
					form = reference.toForm();
				reference.highlight();
				$( '#proveit-content' ).html( form );
				$( '#proveit-insert-button' ).hide().siblings().show();
				$( '#proveit-manual-add-tab' ).addClass( 'active' ).siblings().removeClass( 'active' );
				
			});

			$( '.proveit-citation', item ).click( this, function ( event ) {
				var reference = event.data,
					i = parseInt( $( this ).text(), 10 ) - 1,
					citation = reference.citations[ i ];
				citation.highlight();
				event.stopPropagation();
			});

			return item;
		};

		/**
		 * Convert this reference into a HTML table
		 *
		 * @return {jQuery} table
		 */
		this.toTable = function () {
			var table = $( '<table>' ).attr( 'id', 'proveit-reference-table' );

			// Add the template dropdown menu
			var templateLabel = $( '<label>' ).text( proveit.getMessage( 'reference-template-label' ) ),
				templateSelect = $( '<select>' ).attr( 'name', 'reference-template' ),
				templateName = proveit.getMessage( 'no-template' ),
				templateOption = $( '<option>' ).text( templateName ).val( '' );
			templateSelect.append( templateOption );
			for ( var templateTitle in proveit.templateData ) {
				templateName = templateTitle.substr( templateTitle.indexOf( ':' ) + 1 ); // Remove the namespace
				templateOption = $( '<option>' ).text( templateName ).val( templateName );
				if ( this.template === templateName ) {
					templateOption.attr( 'selected', 'selected' );
				}
				templateSelect.append( templateOption );
			}
			var templateLabelColumn = $( '<td>' ).append( templateLabel ),
				templateSelectColumn = $( '<td>' ).append( templateSelect ),
				templateRow = $( '<tr>' ).append( templateLabelColumn, templateSelectColumn );
			table.append( templateRow );

			// Add the reference name field
			var referenceNameLabel = $( '<label>' ).text( proveit.getMessage( 'reference-name-label' ) ),
				referenceNameInput = $( '<input>' ).attr( 'name', 'reference-name' ).val( this.name ),
				referenceNameLabelColumn = $( '<td>' ).append( referenceNameLabel ),
				referenceNameInputColumn = $( '<td>' ).append( referenceNameInput ),
				referenceNameRow = $( '<tr>' ).append( referenceNameLabelColumn, referenceNameInputColumn );
			table.append( referenceNameRow );

			// Add the reference content area
			var referenceContentLabel = $( '<label>' ).text( proveit.getMessage( 'reference-content-label' ) ),
				referenceContentTextarea = $( '<textarea>' ).attr( 'name', 'reference-content' ).val( this.content ),
				referenceContentLabelColumn = $( '<td>' ).append( referenceContentLabel ),
				referenceContentTextareaColumn = $( '<td>' ).append( referenceContentTextarea ),
				referenceContentRow = $( '<tr>' ).append( referenceContentLabelColumn, referenceContentTextareaColumn );
			table.append( referenceContentRow );

			if ($("#show-all-parameters-checkbox").is(":checked") || (templateSelect.val()===""))
			{
				referenceContentRow.show();
			}
			else
			{
				referenceContentRow.hide();
			}


			// Add the parameter fields
			var templateData = this.getTemplateData(),
				templateMap = this.getTemplateMap(),
				paramOrder = this.getParamOrder(),
				paramName, paramData, paramLabel, paramPlaceholder, paramDescription, paramAlias, paramValue, row, label, paramNameInput, paramValueInput;

			for ( var i = 0; i < paramOrder.length; i++ ) {
				paramName = paramOrder[ i ];
				paramData = templateData.params[ paramName ];

				// Defaults
				paramLabel = paramName;
				paramPlaceholder = '';
				paramDescription = '';
				paramValue = '';

				// Override with template data
				if ( paramData.label ) {
					paramLabel = paramData.label[ proveit.contentLanguage ];
				}

				// If the parameter is a date, put the current date as a placeholder
				// @todo find a better solution
				if ( paramData.label === 'viitattu' ) {
					var date = new Date(),
						yyyy = date.getFullYear(),
						mm = ( '0' + ( date.getMonth() + 1 ) ).slice( -2 ),
						dd = ( '0' + date.getDate() ).slice( -2 );
					paramPlaceholder = yyyy + '-' + mm + '-' + dd;
				}

				if ( paramData.description ) {
					paramDescription = paramData.description[ proveit.contentLanguage ];
				}

				// Extract the parameter value
				if ( paramName in this.params ) {
					paramValue = this.params[ paramName ];
				} else {
					for ( var j = 0; j < paramData.aliases.length; j++ ) {
						paramAlias = paramData.aliases[ j ].trim();
						if ( paramAlias in this.params ) {
							paramValue = this.params[ paramAlias ];
						}
					}
				}

				// Build the table row
				row = $( '<tr>' ).addClass( 'proveit-param-pair' );
				labelColumn = $( '<td>' );
 				inputColumn = $( '<td>' );
				label = $( '<label>' ).attr( 'title', paramDescription ).text( paramLabel );
				paramNameInput = $( '<input>' ).attr( 'type', 'hidden' ).addClass( 'proveit-param-name' ).val( paramName );
				paramValueInput = $( '<input>' ).attr( 'placeholder', paramPlaceholder ).addClass( 'proveit-param-value' ).val( paramValue );

				// Check if the parameter should be shown as a textarea
				if ( 'textarea' in templateMap && ( templateMap.textarea === paramName || templateMap.textarea.indexOf( paramName ) > -1 ) ) {
					paramValueInput = $( '<textarea>' ).attr( 'placeholder', paramPlaceholder ).addClass( 'proveit-param-value' ).val( paramValue );
				}

				// Mark the required/suggested/deprecated parameters
				if ( paramData.deprecated ) {
					row.addClass( 'proveit-deprecated' );
				}
				else if ( paramData.suggested ) {
					row.addClass( 'proveit-suggested' );
				}
				else if ( paramData.required ) {
					row.addClass( 'proveit-required' );
				}
				else
				{
					row.addClass( 'proveit-normal' );
				}
				

				// Put it all together and add it to the table
				labelColumn.append( label );
				inputColumn.append ( paramValueInput );
				row.append( labelColumn, inputColumn, paramNameInput );
				table.append( row );
			}
			
			// Bind events
			templateSelect.change( this, function ( event ) {
				var reference = event.data;
				reference.template = $( this ).val();
				mw.cookie.set( 'proveit-last-template', reference.template ); // Remember the user choice
				table.replaceWith( reference.toTable() );
			});
			
			state=$("#show-all-parameters-checkbox").is(":checked");
			table.find(".proveit-normal").toggle(state);
			table.find(".proveit-deprecated").toggle(state);
			table.find(".proveit-param-pair").each(function() {
				if ($(this).find(".proveit-param-value").val() !== "") $(this).show();	
			});
			var e=$("textarea[name='reference-content']").closest("tr");
			if (state || (templateSelect.val()===""))
			{
				e.show();
			}
			else
			{
				e.hide();
			}
			

			return table;
		};

		/**
		 * Convert this reference to an HTML form filled with its data
		 *
		 * @return {jQuery} form
		 */
		this.toForm = function () {

			var form = $( '<form>' ).attr( 'id', 'proveit-reference-form' ),
				table = this.toTable();

			// Add the buttons
			var buttons = $( '<div>' ).attr( 'id', 'proveit-buttons' ),
			    showAllParamsChecbox=$('<span id="show-all-parameters-container">' + proveit.getMessage( 'show-all-parameters' ) + '<input type="checkbox" id="show-all-parameters-checkbox" />' +'</span>'),
				citeButton = $( '<button>' ).attr( 'id', 'proveit-cite-button' ).addClass('mw-ui-button mw-ui-progressive').text( proveit.getMessage( 'cite-button' ) ),
				removeButton = $( '<button>' ).attr( 'id', 'proveit-remove-button' ).addClass('mw-ui-button mw-ui-progressive').text( proveit.getMessage( 'remove-button' ) ),
				updateButton = $( '<button>' ).attr( 'id', 'proveit-update-button' ).addClass('mw-ui-button mw-ui-progressive').text( proveit.getMessage( 'update-button' ) ),
				insertButton = $( '<button>' ).attr( 'id', 'proveit-insert-button' ).addClass('mw-ui-button mw-ui-progressive').text( proveit.getMessage( 'insert-button' ) );
			buttons.append( showAllParamsChecbox, citeButton, removeButton, updateButton, insertButton );
			form.append( table, buttons );

			// Bind events
			form.submit( false );
			citeButton.click( this, this.cite );
			removeButton.click( this, this.remove );
			updateButton.click( this, this.update );
			insertButton.click( this, this.insert );

			showAllParamsChecbox.find("#show-all-parameters-checkbox").click(function() {
				var target=$("#proveit-reference-table");
				var state=this.checked;
				target.find(".proveit-normal").toggle(state);
				target.find(".proveit-deprecated").toggle(state);
				e=$("textarea[name='reference-content']").closest("tr");
				if (state || ($("input[name='reference-template']").val()==="")) {
					e.toggle(true);
				}
				else
				{
					e.toggle(false);
				}
				target.find(".proveit-param-pair").each(function() {
					if ($(this).find(".proveit-param-value").val() !== "") $(this).show();	
				});
			});
			return form;
		};

		/**
		 * Update the data of this reference with the content of the reference form
		 *
		 * @return {void}
		 */
		this.loadFromForm = function () {

			this.name = $( '#proveit-reference-form input[name="reference-name"]' ).val();

			this.content = $( '#proveit-reference-form textarea[name="reference-content"]' ).val();

			this.template = $( '#proveit-reference-form select[name="reference-template"]' ).val();

			// Load all the parameter key-value pairs
			var params = {}, name, value;
			$( '.proveit-param-pair' ).each( function () {
				name = $( '.proveit-param-name', this ).val();
				value = $( '.proveit-param-value', this ).val();
				if ( name !== '' && value !== '' ) {
					params[ name ] = value;
				}
			});
			this.params = params;

			this.string = this.toString();
		};
	}
};

$( proveit.init );

}( mw, jQuery ) );