Hinweis: Leere nach dem Veröffentlichen den Browser-Cache, um die Änderungen sehen zu können.

  • Firefox/Safari: Umschalttaste drücken und gleichzeitig Aktualisieren anklicken oder entweder Strg+F5 oder Strg+R (⌘+R auf dem Mac) drücken
  • Google Chrome: Umschalttaste+Strg+R (⌘+Umschalttaste+R auf dem Mac) drücken
  • Internet Explorer/Edge: Strg+F5 drücken oder Strg drücken und gleichzeitig Aktualisieren anklicken
  • Opera: Strg+F5
/*
 * ProveIt (http://code.google.com/p/proveit-js/) is a new tool for reliable referencing on Wikipedia
 *
 * Copyright 2008 - 2011
 *
 * Georgia Tech Research Corporation
 *
 * Copyright 2011 -
 *
 * Matthew Flaschen
 *
 * Georgia Tech Research Corporation
 *
 * Atlanta, GA 30332-0415
 *
 * ALL RIGHTS RESERVED
 *
 * ProveIt is available under the GNU Free Documentation License (GFDL-1.3.txt), Creative Commons
 * Attribution/Share-Alike License 3.0 (http://creativecommons.org/licenses/by-sa/3.0/), and the GNU
 * General Public License 2 (GPL-2.txt)
 */

/**
 * Electronic Learning Communities
 * @module elc
 */

/*
 Second parameter (pre-existing proveit object, if any) passed to extend overrides first.
 Gives users option to easily override initial constants, such as shouldAddSummary.

 If proveit is unintentionally imported more than once, the first import will take precedence.
*/
/**
 * Main class and namespace for ProveIt software.  This is the only global variable.
 * @class proveit
 */
 // <nowiki>
window.proveit = jQuery.extend({
	/**
	 * Approximately half the height of the edit box.  Used in scrolling when highlighting text.
	 * @type Number
	 */
	HALF_EDIT_BOX_HEIGHT : 200,

	// This could be preference-controlled, instead of hard-coded.
	/**
	 * Language used for descriptions
	 * @type String
	 */
	LANG : "de",

	/**
	 * Text before param name (e.g. url, title, etc.) in creation box, to avoid collisions with unrelated ids.
	 * @type String
	 */
	NEW_PARAM_PREFIX : "newparam",

	/**
	 * Text before param name (e.g. url, title, etc.) in edit box, to avoid collisions with unrelated ids.
	 * @type String
	 */
	EDIT_PARAM_PREFIX : "ediparam",

	GUI_ID : "proveit",

	/**
	 * Base URL used for static content
	 *
	 * This directory includes icons from the Silk set (http://www.famfamfam.com/lab/icons/silk/), by Mark James
	 * @type String
	 */
	STATIC_BASE : "//proveit-js.googlecode.com/hg/static/",

	/**
	 * URL to jQueryUI stylesheet
	 * @type String
	 */
	JQUERYUI_STYLES_URL : "http://ajax.googleapis.com/ajax/libs/jqueryui/1.8.3/themes/base/jquery-ui.css",

	/* Used to map between parameter name and human-readable.  It can be
	 * internationalized easily.  Add descriptions.xx , where xx is
	 * the ISO 639-1 code for a language, then set proveit.LANG to "xx"
	 * to use the new descriptions.
	 */

	descriptions :
	{
		de :
		{
Literatur: "Literatur", Autor: "Autoren (Vorname Nachname)", Herausgeber: "Herausgeber", Titel: "Titel", TitelErg: "Ergänzende Angaben zum Titel", Sammelwerk: "Übergeordnetes Werk", WerkErg: "Ergänzende Angaben zum Werk", Band: "Band", Nummer: "Nummer", Auflage: "Auflage", Verlag: "Verlag", Ort: "Ort", Jahr: "Jahr", Monat: "Monat", Tag: "Tag", Kapitel: "Kapitel", Seiten: "Seiten", Spalten: "Spalten", ISBN: "ISBN", ISBNistFormalFalsch: "J bei falscher ISBN", DNB: "DNB-Nummer", ISSN: "ISSN", ZDB: "ZDB-Nummer", LCCN: "LCCN", DOI: "Document Object Identifier", Kommentar: "Kommentar", Originaltitel: "Originaltitel", Originalsprache: "Originalsprache", Übersetzer: "Übersetzer", Online: "Internet-Adresse", arxiv: "arXiv-Nummer", Zugriff: "Datum des Zugriffs", Typ: "wl bei Werklisten",

Internetquelle: "Internetquelle", autor: "Autoren (Vorname Nachname)", hrsg: "Herausgeber", titel: "Titel", zugriff: "Datum des Zugriffs", sprache: "Sprache", titelerg: "Ergänzende Angaben zum Titel", werk: "Übergeordnetes Werk", seiten: "Seiten", datum: "Datum", "archiv-url": "Archivierte Internet-Adresse", "archiv-datum": "Datum des Archivs", kommentar: "Kommentar", zitat: "Zitat", offline: "Offline",

BibISBN: "BibISBN",
BibDOI: "BibDOI", Seite: "Seite",

agency: "Agentur", name: "Name", author: "Autor (Vorname Nachname)", author2: "2. Autor (Vorname Nachname)", author3: "3. Autor (Vorname Nachname)", author4: "4. Autor (Vorname Nachname)", author5: "5. Autor (Vorname Nachname)", author6: "6. Autor (Vorname Nachname)", author7: "7. Autor (Vorname Nachname)", author8: "8. Autor (Vorname Nachname)", author9: "9. Autor (Vorname Nachname)", last: "Nachname", last2: "Nachname (2. Autor)", last3: "Nachname (3. Autor)", last4: "Nachname (4. Autor)", last5: "Nachname (5. Autor)", last6: "Nachname (6. Autor)", last7: "Nachname (7. Autor)", last8: "Nachname (8. Autor)", last9: "Nachname (9. Autor)", first: "Vorname", first2: "Vorname (2. Autor)", first3: "Vorname (3. Autor)", first4: "Vorname (4. Autor)", first5: "Vorname (5. Autor)", first6: "Vorname (6. Autor)", first7: "Vorname (7. Autor)", first8: "Vorname (8. Autor)", first9: "Vorname (9. Autor)", authorlink: "Wikipedia-Artikel des Autors", title: "Titel", publisher: "Verlag", year: "Jahr", location: "Ort", place: "Standort des Werks", isbn: "ISBN", id: "ID", doi: "Document Object Identifier", page: "Seite", pages: "Seiten", quote: "Zitat", month: "Monat", journal: "Zeitschrift", edition: "Auflage", volume: "Band", issue: "Nummer", url: "Internet-Adresse", date: "Datum (JJJJ-MM-TT)", accessdate: "Datum des Zugriffs (JJJJ-MM-TT)", coauthors: "Co-Autoren", booktitle: "Titel", contribution: "Beitrag/Kapitel", encyclopedia: "Enzyklopädie", newsgroup: "Newsgroup", version: "Version", site: "Site", newspaper: "Zeitung", "publication-place": "Ort", editor: "Herausgeber (Vorname Nachname)", article: "Artikel", pubplace: "Ort", pubyear: "Jahr", inventor: "Erfinder (Vorname Nachname)", "issue-date": "Datum der Veröffentlichung (JJJJ-MM-TT)", "patent-number": "Patent-Nummer", "country-code": "Länderkennung (XX)", work: "Werk", format: "Format", issn: "ISSN", pmid: "PMID", chapter: "Kapitel", web: "Web", book: "Buch", conference: "Konferenz", news: "News", paper: "Paper", "press release": "Pressemitteilung", interview: "Interview", subject: "Thema", subjectlink: "Wikipedia-Artikel zum Thema", subject2: "2. Thema", subjectlink2: "Wikipedia-Artikel zum 2. Thema", subject3: "3. Thema", subjectlink3: "Wikipedia-Artikel zum 3. Thema", subject4: "4. Thema", interviewer: "Interviewer", cointerviewers: "Co-Interviewer", type: "Typ", program: "Programm", callsign: "Call sign", city: "Stadt", archiveurl: "Archiv-URL", archivedate: "Datum der Archivierung", episode: "Episode", episodelink: "Wikipedia-Artikel zur Episode", series: "Serie", serieslink: "Wikipedia-Artikel zur Serie", credits: "Credits", network: "Network", station: "Station", airdate: "Datum der Sendung", began: "Startzeit", ended: "Endzeit", season: "Staffel Nummer", seriesno: "Staffel Nummer", number: "Nummer", minutes: "Minuten", transcript: "Transkript", transcripturl: "Transkript-URL", video: "Video", people: "People", medium: "Production medium", language: "Sprache", time: "Zeit", oclc: "OCLC", ref: "Anchor ID", months: ['Januar', 'Februar', 'März', 'April', 'Mai', 'Juni', 'Juli', 'August', 'September', 'Oktober', 'November', 'Dezember']
		}
	},
	/**
	 * Returns descriptions for the current language.
	 * @return {Object} descriptions
	*/
	getDescriptions : function()
	{
		//this could be made Cite-specific if needed.
		return this.descriptions[proveit.LANG];
	},

        /**
         * String added to logs for easy search
         * @type String
         */
        LOG_MARKER : "[ProveIt] ",

        /**
         * Convenience log function
         * @param {String} msg message to log
         */
        log : function(msg)
        {
                if(typeof(console) === "object" && console.log)
                {
                        console.log(this.LOG_MARKER + "%o", msg);
                }
        },

        /**
         * Log error object if possible, using error (preferable), or log, if available.
         * @param {Error} ex error object
         */
        logException: function(ex)
        {
                var args = [this.LOG_MARKER, ex, ex.stack];
                if(typeof(console) === "object")
                {
                        if(console.error)
                        {
                                console.error.apply(null, args);
                        }
                        else if(console.log)
                        {
                                console.log.apply(null, args);
                        }
                }
        },

        /**
         * Returns true if the page has an edit box
         *
         * @return {Boolean} true if the page has an edit box, false otherwise
         */
        isEditPage : function()
        {
                return wgAction == 'edit' || wgAction == 'submit';
        },

        /**
         * Returns true if the page is likely to contain references
         * @return {Boolean} true if page is supported, false otherwise
         */
        isSupportedPage : function()
        {
                // "Regular" article, userspace, or Wikipedia:Sandbox (exception for testing).
                return (wgCanonicalNamespace == '' || wgCanonicalNamespace == 'User' || wgPageName == 'Wikipedia:Sandbox');
        },

	/**
	 * Convenience function.  Returns the refbox element.
	 * @return {jQueryNode} reference box
	 */
	getRefBox : function()
	{
		return jQuery("#refs");
	},

	/**
	 * 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.
	 */
	getPosition : 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, at a particular index.
	 * @param {Number} startInd start index in Wikipedia edit box
	 * @param {Number} length length of string to highlight
	 * @return {Boolean} always true
	*/
	highlightLengthAtIndex : function(startInd, length)
	{
		if(startInd < 0 || length < 0)
		{
			this.log("highlightStringAtIndex: invalid negative arguments");
		}
		var box = this.getMWEditBox();
		var origText = box.value;
//		var editTop = this.getPosition(box).top;
		box.value = origText.substring(0, startInd);
		box.focus();
		box.scrollTop = 1000000; //Larger than any real textarea (hopefully)
		var curScrollTop = box.scrollTop;
		box.value += origText.substring(startInd);
		if(curScrollTop > 0)
		{
			box.scrollTop = curScrollTop + this.HALF_EDIT_BOX_HEIGHT;
		}
		jQuery(box).focus().textSelection('setSelection',
		{
			start: startInd,
			end: startInd + length
		});
		var editTop = this.getPosition(box).top;
		window.scroll(0, editTop);
		return true;
	},

	/**
	 * Highlights the first instance of a given string in the MediaWiki edit box.
	 * @param {String} targetStr the string in the edit box to highlight
	 * @return {Boolean} true if successful, false otherwise
	*/
	highlightTargetString : function(targetStr)
	{
		var origText = this.getMWEditValue();
		var startInd = origText.indexOf(targetStr);
		if(startInd == -1)
		{
			this.log("Target string \"" + targetStr + "\" not found.");
			return false;
		}
		return this.highlightLengthAtIndex(startInd, targetStr.length);
	},

	/**
	 * Convenience function. Returns the raw MediaWiki textarea element.
	 * @return {Node} the edit box element
	*/
	getMWEditBox : function()
	{
		return jQuery("#wpTextbox1")[0];
	},

	/**
	 * Provides value of edit box with CR normalization
	 *
	 * @return {String} value of edit box with CRs stripped if document.selection exists
	 */
	getMWEditValue : function()
	{
		var box = this.getMWEditBox();
		var value = box.value;
		if(!box.selectionStart && document.selection) // IE 8-like behavior
		{
			value = value.replace(/\r\n/g, "\n");
		}
		return value;
	},

	/**
	 * Returns raw edit form element, which contains MWEditBox, among other things.
	 * @return {Node} the edit form element
	*/
	getMWEditForm : function()
	{
		return jQuery("#editform")[0];
	},

	/**
	 * ProveIt should be visible on load (rather than requiring toolbar button click) on supported edit pages
	 * @type Boolean
	 */
	loadVisible : true,

 	/**
 	* Maximize ProveIt when it first becomes visible. If false, it will start minimized. This has no effect on when it becomes visible.
 	* @type Boolean
 	*/
	loadMaximized : false,

        /**
         * Setup button so users can load ProveIt on demand
         */
        setupButton : function()
        {
                var $box = jQuery(this.getMWEditBox());

                // Ensures wikiEditor is loaded
                $box.bind('wikiEditor-toolbar-buildSection-main', function(event, section)
                {
                        delete section.groups.insert.tools.reference;

                        section.groups.insert.tools.proveit = {
                                label: 'ProveIt',
                                type: 'button',
                                icon: 'http://upload.wikimedia.org/wikipedia/commons/thumb/1/19/ProveIt_logo_for_user_boxes.svg/22px-ProveIt_logo_for_user_boxes.svg.png',
                                action: {
                                        type: 'callback',
                                        execute: function()
                                        {
                                                proveit.toggleVisibility();
                                        }
                                }
                        };
                });
        },

        /**
         * Sets up ProveIt if we're on an edit page.  This includes setting up the toolbar button.  Depending on configuration and the current page, it may also call load to show ProveIt.
         */
        setup : function()
        {
                if(this.isEditPage())
                {
                        if(this.loadVisible && this.isSupportedPage())
                        {
                                this.load();
                        }

                        this.setupButton();
                }
        },

        /**
         * Loads dependencies and creates GUI
         */
        load : function()
        {
                addOnloadHook(function()
                {
                        var dependencies = ['jquery.ui'];
                        mw.loader.using(dependencies, function()
                        {
                                try
                                {
                                        proveit.createGUI();
                                        if(proveit.loadMaximized)
                                        {
                                                proveit.toggleViewAddVisibility();
                                        }
                                }
                                catch(ex)
                                {
                                        proveit.logException(ex);
                                }
                        }, function(ex, errorDependencies)
                        {
                                proveit.log('Failed to load one of: ' + errorDependencies);
                        });
                });
        },

	/**
	 * Clears the refBox of refBoxRows, except for dummy rows.
	 * @return {Boolean} false if refBox wasn't found
	 */

	clearRefBox : function(box)
	{
		//var box = jQuery("#refs");
		if(box == null)
		{
			this.log("Ref box is not loaded yet.");
			return false;
		}
		var refs = jQuery("tr:not('tr#dummyRef')", box);
		jQuery(refs).remove();

	},

	/** Inserts ref text into MW edit box.
	 * @param {String} ref Reference text to insert
	 * @param {Boolean} full Insert the full reference text if true, citation otherwise.
	 * @return {Boolean} false if errors
	 */
	insertRefIntoMWEditBox : function(ref, full)
	{
		var txtarea = this.getMWEditBox();
		if(!txtarea)
		{
			this.log("insertRefIntoMWEditBox: txtarea is null");
			return false;
		}
		txtarea = jQuery(txtarea);
		var insertionText = ref.getInsertionText(full);
		 // Replace existing selection (if any), then scroll
		txtarea.textSelection('encapsulateSelection',
		{
			peri: insertionText,
			replace: true
		});
		var caretPos = txtarea.textSelection('getCaretPosition', {startAndEnd: true});

		// This is slightly redundant.  It is called primarily for the scrollig workaround
		this.highlightLengthAtIndex(caretPos[0], caretPos[1] - caretPos[0]);

		this.includeProveItEditSummary();
	},

	/**
	 * Modifies reference object from user-edited GUI. The reference object is mutated in place, so the return value is only for convenience.
	 *
	 * @param {Node} editPane the raw element of the editPane
	 * @param {AbstractReference} ref the original citation object we're modifying
	 *
	 * @return {AbstractReference} same ref that was passed in
	 */
	changeRefFromEditPane : function(ref, editPane)
	{
		var paramBoxes = jQuery("div.input-row", editPane);

		var refName = jQuery('#editrefname').val();
		ref.name = refName != "" ? refName : null; // Save blank names as null

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

		var paramName, paramVal;
		for (var i = 0; i < paramBoxes.length; i++)
		{
			// this.log(item + ":" + paramBoxes[item].id);
			//this.log("item: " + i);
			var paramRow = paramBoxes[i];
			var valueTextbox = jQuery(".paramvalue", paramRow)[0];
			if(jQuery(paramRow).hasClass("addedrow")) // Added with "Add another field"
			{
				paramName = jQuery(".paramdesc", paramRow)[0].value.trim();
			}
			else
			{
				paramName = valueTextbox.id.substring(this.EDIT_PARAM_PREFIX.length);
			}
			this.log("paramName: " + paramName);
			paramVal = valueTextbox.value.trim();

			this.log("paramVal: " + paramVal);

			if (paramName != "" && paramVal != "")
			{
				//this.log("Setting " + paramName + "= " + paramVal);
				ref.params[paramName] = paramVal;
			}
		}
		if (ref.toString() != ref.orig)
		{
			ref.save = false;
		}
		ref.update();
		return ref;
	},

	/**
	 * Creates refBoxRow, updates numbering for all refBoxRows, replaces old refBoxRow with new one, and updates ref text in MWEditBox.
	 * @param {AbstractReference} ref the ref we want to save.
	 */
	saveRefFromEdit : function(ref)
	{
		if(!ref.save)
		{
			var newRichItem = this.makeRefBoxRow(ref, true);
			var oldRichItem = jQuery('.selected', this.getRefBox()).get(0);
//			this.log('newRichItem: ' + newRichItem + ', oldRichItem: ' + oldRichItem + 'oldRichItem.parentNode: ' + oldRichItem.parentNode);
			var oldNumber = jQuery('td.number',oldRichItem).text();
			jQuery('td.number',newRichItem).text(oldNumber); // preserve old numbering
			oldRichItem.parentNode.replaceChild(newRichItem, oldRichItem);
			jQuery(newRichItem).addClass('selected');

			ref.updateInText();
			this.includeProveItEditSummary();
		}
	},

	/**
	 * Updates the edit pane when you choose a reference to edit.
	 * @param {AbstractReference} ref the ref that was chosen.
	 */
	updateEditPane : function(ref, pane)
	{
		var addOrEdit = (pane == "edit") ? jQuery("#edit-pane").get() : jQuery("#add-tab").get();


		// Don't contaminate actual object with junk params.
		var tempParams = {};
		for(var param in ref.params)
		{
			tempParams[param] = ref.params[param];
		}

		// Add default params with blank values.
		var defaults = ref.getDefaultParams();
		for(var i = 0; i < defaults.length; i++)
		{
			if(!tempParams[defaults[i]])
			{
				this.log("Setting default blank parameter: defaults[i] = " + defaults[i]);
				tempParams[defaults[i]] = "";
			}
		}

		var required = ref.getRequiredParams();

		var paramNames = new Array();

		for(var item in tempParams)	//First run through just to get names.
		{
			this.log(item);
			paramNames.push(item);
		}

		var sorter = ref.getSorter();
		if(sorter)
		{
			paramNames.sort(sorter);
		}
		else
		{
			paramNames.sort();
		}
		/* Sort them to provide consistent interface.  Uses custom sort order (which is easily tweaked)
		   where possible.

		   Javascript does destructive sorting, which in this case, is convenient...
		*/

//		jQuery('#edit-fields').children('.paramlist').children().remove('div:not(.hidden)'); // clear all fields in the edit box (except the hidden ones)
		jQuery('.paramlist', addOrEdit).children().remove('div:not(.hidden)'); // clear all fields in the edit box (except the hidden ones)

		for(var i = 0; i < paramNames.length; i++)
		{
			this.log("Calling addPaneRow on tempParams." + item);
			this.log("i: " + i + ", paramNames[i]: " + paramNames[i]);
			this.addPaneRow(addOrEdit, tempParams, this.getDescriptions(), paramNames[i], required[paramNames[i]], true);
		}
		if (pane == "edit") {
			jQuery('#editrefname').val(ref.name || "");
			var acceptButton = jQuery('#edit-buttons .accept');
			var acceptEdit = function()
			{
				proveit.log("Entering acceptEdit");
				proveit.changeRefFromEditPane(ref, jQuery("#edit-pane").get());
				proveit.saveRefFromEdit(ref);
				acceptButton.unbind('click', acceptEdit);
				jQuery("#edit-pane").hide();
				jQuery("#view-pane").show();
			};

			// Without setTimeout, scoll reset doesn't work in Firefox.
			setTimeout(function()
			{
			    // Reset scroll
			    jQuery('#edit-fields').scrollTop(0);
			}, 0);

			acceptButton.click(acceptEdit);

			jQuery('.tab-link').one('click', function()
			{
				acceptButton.unbind('click', acceptEdit);
			});
		} else {
			jQuery('#addrefname').val(ref.name || "");
		}
	},

	/**
	 * Add a row to an editPane or addPane.
	 * @param {Node} root root element for pane
	 * @param {Object} params the param object from the reference, or null for added rows.
	 * @param {Object} descs description object to use, or null for no description
	 * @param {String} item the current param name
	 * @param {Boolean} req true if current param name is required, otherwise not required.
	 * @param {Boolean} fieldType true for label, false for textbox.
	 */
	addPaneRow : function(root, params, descs, item, req, fieldType)
	{
		this.log('entering addPaneRow: ' + root + params + descs + item + req + fieldType);
		var id = fieldType ? "preloadedparamrow" : "addedparamrow";
		var newline = jQuery('#'+id).clone(); // clone the hidden row
		jQuery(newline).attr('id',''); // clear the ID (can't have two elements with same ID)

		var paramName = jQuery('.paramdesc', newline).eq(0);
		var paramValue = jQuery('.paramvalue', newline).eq(0);

		jQuery('.paramlist', root).append(newline);

		if(req) // if field is required...
		{
			jQuery(paramName).addClass('required'); // visual indicator that label is required
			jQuery('.delete-field', newline).remove(); // don't let people remove required fields
		}
		else
		{
		// Add event handler to Delete Field button in Add/Edit Reference panes

			jQuery('.delete-field', newline).click(function() {
				jQuery(newline).hide(
					'highlight',{},'slow',
					function() {
						jQuery(newline).remove();
						}
					);
			});
		}

		if ((item == 'ISBN') || (item == 'DOI')) {
			jQuery('.paramvalue', newline).blur(function() {
				if (this.value != "") {
					var id = (item == 'ISBN') ? this.value.replace(/[^0-9X]/g, "") : this.value;
					var recordURL = mw.config.get('wgServer') + mw.config.get('wgScriptPath') + "/index.php?&title=Vorlage:Bib" + item + "/" + id + "&action=raw";
					jQuery.ajax({ url : recordURL, success : function(data) {
						// test for existing BibRecord
						if (jQuery("#citemenu").get(0).value == "Literatur") {
							var oldname = jQuery('#addrefname').get(0).value;
							var menu = jQuery("#citemenu").get(0);
							menu.value = "Bib" + item;
							proveit.changeAddPane(menu);
							jQuery('#addrefname').get(0).value = oldname;
							jQuery('.paramvalue', '#add-tab').get(1).value = id;
						}
					},
					error : function(){
						if (item == 'ISBN') {
							// get params from Openlibrary
							var bibkey = 'http://openlibrary.org/api/books?bibkeys=ISBN:' + id + '&jscmd=data';
							jQuery.ajax({ url : bibkey, dataType : 'jsonp', success : function(data) {
								var record = { Autor : "", Titel : "", Verlag : "", Ort : "", Jahr : "", Sammelwerk : "", Band : "", Nummer : "", DOI : ""};
								record.ISBN = id;
								for (var j in data) {
									var Autoren = [];
									if (data[j].authors) {
										for (var i = 0; i < data[j].authors.length; i++) {
											Autoren[i] = data[j].authors[i].name;
										}
										record.Autor = Autoren.join(', ');
									}
									if (data[j].title) { record.Titel = data[j].title; }
									if (data[j].publishers) { record.Verlag = data[j].publishers[0].name; }
									if (data[j].publish_places) { record.Ort = data[j].publish_places[0].name;}
									if (data[j].publish_date) { record.Jahr = data[j].publish_date; }
								}
								proveit.insertLoadedData( record );
							}});
						} else {
							// get params from Crossref
//							var bibkey = 'http://nurture.nature.com/cgi-bin/opensearch?db=crossref&out=jsonp&q=doi:' + id;
//							var bibkey = 'http://dx.doi.org/' + id;
							var bibkey = 'http://data.crossref.org/' + id;
							jQuery.ajax({ beforeSend: function(req) {
        req.setRequestHeader("Accept", "application/unixref+xml");
    },
url : bibkey, dataType : "jsonp", success : function(data) {
alert(data);
								var record = {};
								record.DOI = id;
								if (data.feed.entry[0]["dc:creator"]) { record.Autor = data.feed.entry[0]["dc:creator"].join(', '); }
								if (data.feed.entry[0].title) { record.Titel = data.feed.entry[0].title; }
								if (data.feed.entry[0]["prism:publicationName"]) { record.Sammelwerk = data.feed.entry[0]["prism:publicationName"]; }
								if (data.feed.entry[0]["prism:volume"]) { record.Band = "Bd. " + data.feed.entry[0]["prism:volume"];}
								if (data.feed.entry[0]["prism:number"]) { record.Nummer = data.feed.entry[0]["prism:number"]; }
								if (data.feed.entry[0]["prism:publicationDate"]) { record.Jahr = data.feed.entry[0]["prism:publicationDate"].substring(0,4); }
								proveit.insertLoadedData( record );
							}});
						}
					}});
				}
			});
		}

		if(fieldType) // the description/name is a label (not a textbox)
		{
			paramName.attr("for", this.EDIT_PARAM_PREFIX + item);
			paramValue.attr('id',this.EDIT_PARAM_PREFIX + item);

			var desc = descs[item];
			if(!desc)
			{
				this.log("Undefined description for param: " + item + ".  Using directly as description.");
				desc = item;
			}
			jQuery(paramName).text(desc);
			jQuery(paramName).attr('title',item);
			jQuery(paramValue).val(params[item]);
			if((item == 'accessdate') || (item == 'zugriff'))
				jQuery('.paramvalue', newline).val(this.formatDate(new Date));

			jQuery(newline).show();
		}
		else
		{
			// added a new row, so make it fancy
			jQuery(newline).show('highlight',{},'slow');
			jQuery('.inputs', root).scrollTop(100000);
		}
	},

	insertLoadedData : function (loadedRef)
	{
		var menuVal = jQuery("#citemenu").get(0).value;
		if (menuVal == "Literatur") {
			var newRef = new proveit.CiteLit({"type" : "Literatur", "params" : loadedRef, "name" :jQuery('#addrefname').val});
			proveit.updateEditPane(newRef, "add");
		} else if ((menuVal == "BibISBN") || (menuVal == "BibDOI")) {
			if (confirm("Die Vorlage:" + menuVal + "/" + loadedRef.id + " existiert noch nicht. Möchtest Du sie jetzt anlegen?")) {
				var newBibRecordText = "{{BibRecord\n"
				+ "| Autor           = " + loadedRef.Autor + "\n"
				+ "| Titel           = " + loadedRef.Titel + "\n"
				+ "| ISBN            = " + loadedRef.ISBN + "\n"
				+ "| Herausgeber     = \n"
				+ "| TitelErg        = \n"
				+ "| Sammelwerk      = " + loadedRef.Sammelwerk + "\n"
				+ "| Band            = " + loadedRef.Band + "\n"
				+ "| Nummer          = " + loadedRef.Nummer + "\n"
				+ "| Auflage         = \n"
				+ "| Verlag          = " + loadedRef.Verlag + "\n"
				+ "| Ort             = " + loadedRef.Ort + "\n"
				+ "| Jahr            = " + loadedRef.Jahr + "\n"
				+ "| Monat           = \n"
				+ "| Tag             = \n"
				+ "| ISSN            = \n"
				+ "| LCCN            = \n"
				+ "| Originaltitel   = \n"
				+ "| Originalsprache = \n"
				+ "| Übersetzer      = \n"
				+ "| Online          = \n"
				+ "| DOI             = " + loadedRef.DOI + "\n"
				+ "| arxiv           = \n"
				+ "| Zugriff         = \n"
				+ "| Typ             = \n"
				+ "| Seite           = {{{Seite|}}}\n"
				+ "| Seiten          = {{{Seiten|}}}\n"
				+ "| Kommentar       = {{{Kommentar|}}}\n"
				+ "| format          = {{{format|}}}\n"
				+ "| record          = {{{record|}}}\n"
				+ "}}";
				var newBibRecord = window.open(mw.config.get('wgServer') + mw.config.get('wgScriptPath') + "/index.php?title=Vorlage:" + menuVal + "/" + loadedRef.id + "&action=edit", "_blank");
				newBibRecord.addEventListener("load", function() {
					newBibRecord.document.getElementById('wpTextbox1').value = newBibRecordText;
				}, false);
			}
		}
	},

	/* Cross-Browser Split 1.0.1
	 (c) Steven Levithan <stevenlevithan.com>; MIT License
	 http://blog.stevenlevithan.com/archives/cross-browser-split
	 An ECMA-compliant, uniform cross-browser split method
	 */
	/**
	 * Cross-browser implementation of ECMAScript String.prototype.split function.
	 *
	 * @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
	 */
	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.split._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.split._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.split._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 name and val), iteratively. Regex.find?  Make name and val 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 name and value pairs.
	 *
	 * names is an array of all names, and values is an array of all values.  They have equal lengths.
	 *
	 * @param {String} workingString template string to parse.
	 * @return {Object} object with two properties, names and values.
	 */
	splitNameVals : function (workingString)
	{
		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.names = proveit.split(workingString.substring(workingString.indexOf("|") + 1), /=(?:[^|]*?(?:\[\[[^|\]]*(?:\|(?:[^|\]]*))?\]\])?)+(?:\||\}\})/);
		split.names.length--; // Remove single empty element at end

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

	/**
	 * Scan for references in the MWEditBox, and create a reference object and refBoxRow for each.
	 */
	scanForRefs : function(box, textValue)
	{
		this.log("Entering scanForRefs.");
		// these are strings used to allow the correct parsing of the ref
		var workingstring;
		var cutupstring;

		this.clearRefBox(box);

		// since we should pick the name out before we get to the reference type, here's a variable to hold it
		var name;

		// key - name
		// value -
		//      object - key - "reference", value - reference obj .  Avoids repeating same object in references array.
                //               key - "strings", value - array of orig strings
		var citations = {};

		// Array of reference objects.  At end of function, addNewElement called on each.
		var references = [];
		 // allRefs should count opening refs, but not ref citation (not <ref name="..."" />)
		var allRefs = textValue.match(/<[\s]*ref[^\/>]*>/gi);
		// currentScan holds the parsed (match objects) list of references.  Regex matches full or name-only reference.
		var currentScan = textValue.match(/<[\s]*ref[^>]*>(?:[^<]*<[\s]*\/[\s]*ref[\s]*>)?/gi); // [^<]* doesn't handle embedded HTML tags (or comments) correctly.
		// if there are results,
		if (currentScan)
		{
			for (var i = 0; i < currentScan.length; i++)
			{
//				this.log("currentScan[" + i + "]: " + currentScan[i]);
				var reference = this.makeRef(currentScan[i]);
				if (reference)  // Full reference object
				{
					name = reference.name;
					if(!name) // with no name, no possibility of repeat name.
					{
						references.push(reference);
					}
				}
				else // Not full reference.  Possibly citation.
				{
					var match = currentScan[i].match(this.REF_REGEX);
					name = match && (match[1] || match[2] || match[3]);
				}

				if(name)
				{
					if(!citations[name])
					{
						// Create array of original reference strings
						citations[name] = {};
						if(!citations[name].strings)
						{
							citations[name].strings = [];
						}
					}
					if(reference && !citations[name].reference) // reference, and not already one for this name
					{
						citations[name].reference = reference;
						references.push(reference);
					}

					// Add to array
					citations[name].strings.push(currentScan[i]);
				}
			}
		}
		for(var j = 0; j < references.length; j++)
		{
			if(references[j].name)
			{
				var citation = citations[references[j].name];
				references[j].setCitationStrings(citation.strings);
			}
			// this.addNewElement(references[j]);
references[j].box = jQuery(box).attr("id");
			jQuery(box).append(this.makeRefBoxRow(references[j], false));
		}
	},

	/**
	 * Regex for parsing any reference text.
	 * @type RegExp
	*/
	REF_REGEX : /<[\s]*ref[\s]*name[\s]*=[\s]*(?:(?:\"(.*?)\")|(?:\'(.*?)\')|(?:(.*?)))[\s]*\/?[\s]*>/,

	/**
	 * Factory function for references.  Takes text of a reference, and returns instance of the appropriate class.
	 * @param {String} refText reference string
	 * @return {AbstractReference} null if refText isn't a ref, otherwise the reference object
	 */
	makeRef : function(refText)
	{
		var isReference = /<[\s]*ref[^>]*>[^<]*\S[^<]*<[\s]*\/[\s]*ref[\s]*>/.test(refText); // Tests for reference (non-citation);
		this.log("refText: " + refText + "; isReference: " + isReference);
		if(!isReference)
		{
			return null;
		}
		var citeFunction = refText.match(/{{[\s]*Literatur/i) ? this.CiteLit : refText.match(/{{[\s]*Internetquelle/i) ? this.CiteLit : refText.match(/{{[\s]*BibRecord/i) ? this.CiteLit : refText.match(/{{[\s]*cite/i) ? this.CiteReference : refText.match(/{{[\s]*BibISBN/i) ? this.CiteBib : refText.match(/{{[\s]*BibDOI/i) ? this.CiteBib : this.RawReference;

			var match = refText.match(this.REF_REGEX);
			if(match && match != null)
			{
				var name = match[1] || match[2] || match[3]; // 3 possibilities, corresponding to above regex, are <ref name="foo">, <ref name='bar'>, and <ref name=baz>

			}

		if(citeFunction != this.RawReference)
		{
			var workingstring = refText.match(/{{[\s]*(cite|Literatur|Internetquelle|BibISBN|BibDOI|BibRecord)[\s\S]*?}}/i)[0];

			this.log("scanForRefs: workingstring: " + workingstring);
			var cutupstring = workingstring.split(/\|/g);

			// This little hack relies on the fact that 'e' appears first as the last letter of 'cite', and the type is next.
			if(citeFunction == this.CiteReference)
			{
				var typestart = cutupstring[0].toLowerCase().indexOf('e');
				// First end curly brace
				var rightcurly = cutupstring[0].indexOf('}');
				// Usually, rightcurly will be -1.  But this takes into account empty references like <ref>{{cite web}}</ref>
				var typeend = rightcurly != -1 ? rightcurly : cutupstring[0].length;
				// grab the type, then trim it.
				var type = cutupstring[0].substring(typestart + 1, typeend).trim();
			}
			if(citeFunction == this.CiteLit)
			{
				var type = refText.match(/{{[\s]*Literatur/i) ? "Literatur" : refText.match(/{{[\s]*Internetquelle/i) ? "Internetquelle" : "BibRecord";
//				if (refText.match(/{{[\s]*Literatur/i)) { type = 'Literatur'; } else { type = 'Internetquelle'; };
			}
			if(citeFunction == this.CiteBib)
			{
				var type = '';
				if (refText.match(/{{[\s]*BibISBN/i)) {
					type = 'BibISBN';
					cutupstring[1] = 'ISBN=' + cutupstring[1];
				} else {
					type = 'BibDOI';
					cutupstring[1] = 'doi=' + cutupstring[1];
				};
				workingstring = cutupstring.join('|');
			}
		}
		// type may be undefined, but that's okay.

 var pa = {};
		if(citeFunction != this.RawReference)
		{
			var split = this.splitNameVals(workingstring);
			var names = split.names;
			var values = split.values;

			for (var j = 0; j < names.length; j++)
			{
				/* Drop blank space, and |'s without params, which are never correct for
				 citation templates.*/
				var paramName = names[j].trim().replace(/(?:\s*\|)*(.*)/, "$1");
				var paramVal = values[j].trim(); 
						       // 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 (paramVal != "")
				{
					pa[paramName] = paramVal;
				}
			}
		}

		var citation = new citeFunction({"name": name, "type": type, "save": true, "inMWEditBox": true, "orig": refText, "params": pa});
		return citation;
	},

	/**
	 * Root reference type. Parent of RawReference, CiteReference, and CitationReference.
	 * @class AbstractReference
	 * @for	proveit
	 * @constructor
	 * @param {Object} argObj argument object with keys for each option
	*/
	AbstractReference : function(argObj)
	{
		// CiteReference has a non-trivial override of this.  This is defined early (and conditionally) because it is used in the constructor.
		if(!this.setType)
		{
			/**
			 * @param {String} type type of reference
			 */
			this.setType = function(type)
			{
				this.type = type;
			};
		}

		/**
		 * Update citation strings after changing reference.  This runs after modifying a reference's fields (name, params), but before changing orig
		 */
		this.update = function()
		{
			var newCiteText = this.toString();
			var strings = this.getCitationStrings();

			/*
			 * Update main citation in strings list.
			 *
			 * TODO:
			 * Use strings array here to find and update citations that are not main references.  As is, they are orphaned.
			 * Both array and textbox should be updated.
			 * It may be enough to just set all non-main citations in text and array to this.getInsertionText(false).
			 * However, if they remove the name entirely (not recommended), that would be a problem.
			 */
			if(strings.length > 0) // This implies there was a name before
			{
				for(var i = 0; i < strings.length; i++)
				{
					// If we find the full citation as a citation, update to the new text.
					if(strings[i] == this.orig)
					{
						// this.orig itself is updated in updateInText
						proveit.log("Updating " + strings[i] + " to " + newCiteText);
						strings[i] = newCiteText;
					}
				}
			}
			else if(this.name != null) // They have added a name, so we should have a main citation.
			{
				// Now that it has a name, it is a citation to itself.
				proveit.log("Adding " + newCiteText + " to citationStrings");
				strings.push(newCiteText);
			}
		};
		/**
		 * &lt;ref name /&gt; for reference
		 * @type String
		 */
		 this.name = argObj.name != "" ? argObj.name : null; // Save blank names as null

		/*
		  type of reference, e.g. cite web, cite news.  Also used (including for CitationReference objects) to determine default fields.
		 */
		this.setType(argObj.type);

 		 //TODO: Re-examine whether both (or indeed either) of save or inMWEditBox are really necessary.  Can it be determined from context?

 		/**
		 * flag to determine whether citation must be saved.  false indicates "dirty" citation that has yet to be updated in text and metadata.
		 * @type Boolean
		*/
		this.save = argObj.save;

		/**
		 * true if and only if the ref is in the MW edit box with the same value as this object's orig.
		 * @type Boolean
 		 */
		this.inMWEditBox = argObj.inMWEditBox;

		/**
		 * original wikitext for reference
		 * @type String
		 */
		this.orig = argObj.orig;

		/**
		 * mapping of parameter names to values
		 * @type Object
		 */
		this.params = argObj.params || {};
		this.record = argObj.record || {};

		/**
		 * Convenience method.  Returns sorter for parameters.
		 * @return {Function} sorter for parameters
		*/
		this.getSorter = function()
		{
			var thisCite = this; // Make closure work as intended.
			// Sorter uses paramSortKey first, then falls back on alphabetical order.
			return function(paramA, paramB)
			{
				var aInd = thisCite.getSortIndex(paramA);
				var bInd = thisCite.getSortIndex(paramB);
				if(aInd != -1 && bInd != -1)
				{
					return aInd - bInd;
				}
				else
				{
					if(paramA < paramB)
					{
						return -1;
					}
					else if(paramA == paramB)
					{
						return 0;
					}
					else
					{
						return 1;
					}
				}
			};
		};

		/**
		 * Returns true if this reference is valid, false otherwise.
		 * Assume all AbstractReference objects are valid.  Can be overridden in subtypes.
		 * @return {Boolean} AbstractReference.isValid always returns true
		*/
		this.isValid = function(){return true;};

		/**
		 * Generates label for reference using title, author, etc.
		 * @return {String} the label that was generated
		 */
		this.getLabel = function()
		{
			var label = this.params.title ? this.params.title : this.params.titel ? this.params.titel : this.params.Titel ? this.params.Titel : this.record.Titel ? this.record.Titel : this.params.Sammelwerk ? this.params.Sammelwerk : this.record.Sammelwerk ? this.record.Sammelwerk : "";

			if(label == "")
			{
				var value;
				for (value in this.params)
				{
					break;
				}
				if(value) // There could be no parameters
				{
					label = value;
				}
			}
			return label;
		};


		/**
		 * Gets insertion text (for edit box).
		 *
		 * TODO: Generate a regex object instead (getInsertionRegExp), so highlighting would not fail due to trivial changes (e.g. spacing).
		 * @param {Boolean} full If true, insert full text, otherwise ref name only
		 * @return {String} insertion text
		 */
		this.getInsertionText = function(full)
		{
			proveit.log("getInsertionText");
			if(full)
			{
				return this.toString();
			}
			else
			{
				if(this.name)
				{
					return "<ref name=\""
						+ this.name + "\" />";
				}
				else
				{
					throw new Error("getInsertionText: ref.name is null");
				}
			}
		};

		/**
		 * Updates this reference in the edit box.
		 */
		this.updateInText = function()
		{
			var txtarea = proveit.getMWEditBox();

			if (!txtarea || txtarea == null)
				return;

			txtarea.focus();
			var text = proveit.getMWEditValue();

			text = text.replace(this.orig, this.toString());

			// Do replacement in textarea.
			txtarea.value = text;

			// Baseline for future modifications

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

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

		/**
		 * Internal helper method for toString.
		 * @param {String} template template for ref (currently "cite" or "Citation"
		 * @param {Boolean} includeType true to include this.type, false otherwise
		 * @return {String} string for current reference
		 */
		this.toStringInternal = function(template, includeType)
		{
			if(this.name)
			{
				var returnstring = "<ref name=\"" + this.name + "\">";
			}
			else
			{
				var returnstring = "<ref>";
			}
			returnstring += "{{" + template + (includeType ? " " + this.type : "");
			for (var name in this.params)
			{
				returnstring += " |" + name + "=" + this.params[name];
			}
			returnstring += "}}</ref>";
			return returnstring;
		};

		/**
		 * Array of citation strings for this reference.
		 * @type Array
		*/
		this.citationStrings = [];

		/**
		 * Sets citationStrings to an array
		 * @param {Array} strings array of citation strings, not null
		 */
		this.setCitationStrings = function(strings)
		{
			this.citationStrings = strings;
		};

		/**
		 * Gets array of citationStrings.
		 * @return {Array} (possibly empty) array of citation strings.  Will not return null.
		 */
		this.getCitationStrings = function()
		{
			return this.citationStrings;
		};

		/**
		 * Get icon URL for reference
		 * @return {String} icon URL
		 */
		this.getIcon = function()
		{
			return proveit.STATIC_BASE + "page_white.png";
		};
	},

	/**
	 * Constructor for CiteReference type.
	 * @class CiteReference
	 * @for proveit
	 * @constructor
	 * @extends AbstractReference
	 * @param {Object} argObj the argument object, with keys for each option
	*/
	CiteReference : function(argObj)
	{
		/* Mostly an identity mapping, except for redirects.  I think
		 * having the self-mappings is better than some kind of special case array.
		 */
		var typeNameMappings =
		{
			web:"web",
			book:"book",
			journal:"journal",
			conference:"conference",
			encyclopedia:"encyclopedia",
			news:"news",
			newsgroup:"newsgroup",
			paper:"journal",
			"press release":"press release",
		        "pressrelease":"press release",
			interview:"interview",
		        episode:"episode",
			video:"video"
		};

		// Sets the type (e.g. web for cite web), applying the mappings.  This is up top because it is used in AbstractReference constructor.
		this.setType = function(rawType)
		{
			var mappedType = typeNameMappings[rawType];
			if(mappedType != null)
				this.type = mappedType;
			else
				this.type = rawType; // Use naive type as fallback.
		};

		proveit.AbstractReference.call(this, argObj);

		// TODO: Should CiteReference.getSortIndex and CitationReference.getSortIndex be merged into AbstractCitation?  Less fine-grained, but simpler to maintain.
		/**
		 * Returns the sort index for a given parameter
		 * @param {String} param parameter name
		 * @return {Number} sort index if found, otherwise -1
		 */
		this.getSortIndex = function(param)
		{
			// This is the order fields will be displayed or outputted.

			return jQuery.inArray(param, [
				"url", "title", "encyclopedia", "publisher", "work", "date", "agency", "accessdate", "author", "last", "first", "subject", "subjectlink", "inventor", "editor", "author2", "last2", "first2", "subject2", "subjectlink2", "author3", "last3", "first3", "subject3", "subjectlink3", "author4", "last4", "first4", "subject4", "author5", "last5", "first5", "author6", "last6", "first6", "author7", "last7", "first7", "author8", "last8", "first8", "author9", "last9", "first9", "authorlink", "coauthors", "interviewer", "cointerviewers", "type", "newsgroup", "journal", "booktitle", "program", "episodelink", "series", "serieslink", "credits", "network", "station", "callsign", "city", "airdate", "began", "ended", "season", "seriesno", "number", "minutes", "transcript", "transcripturl", "people", "year", "month", "article", "contribution", "format", "medium", "newspaper", "conference", "volume", "edition", "issue", "location", "pages", "page", "language", "isbn", "issn", "oclc", "doi", "pmid", "id", "archiveurl", "archivedate", "time", "quote", "ref"
			]);
		};

		/**
		 * Returns this reference as a string.
		 * @return {String} reference as string
		 */
		this.toString = function()
		{
			return this.toStringInternal("cite", true);
		};

		// References without these parameters will be flagged in red.
		// True indicates required (null, or undefined, means not required)
		var requiredParams =
		{
			web : { "url": true, "title": true},
			book : { "title": true },
			journal : { "title": true },
			conference : { "title": true },
			encyclopedia: { "title": true, "encyclopedia": true },
			news: { "title": true, "work": true, "date": true },
			newsgroup : { "title": true },
			"press release"	: { "title": true },
			interview: { "last" : true }, // TODO: Interview requires last *or* subject.  Currently, we can't represent that.
			episode : { "title": true },
			video : { "title" : true }
		};

		/**
		 * Return required parameters for this citation type.
		 * @return {Object} object with required parameters as keys and true as value; empty object for unknown type
		*/
		this.getRequiredParams = function()
		{
			var curReq = requiredParams[this.type];
			if(curReq)
				return curReq;
			else
				return {}; // Return empty object rather than null to avoid dereferencing null.
		};

		// These paramaters will be auto-suggested when editing.
		var defaultParams =
		{
		        web : [ "url", "title", "author", "accessdate", "work", "publisher", "date", "pages"],
		        book : [ "title", "author", "authorlink", "year", "isbn", "publisher", "location", "pages" ],
		        journal : [ "title", "author", "journal", "volume", "issue", "year", "month", "pages", "url", "doi" ],
		        conference : [ "conference", "title", "booktitle", "author", "editor", "year", "month", "url", "id", "accessdate", "location", "pages", "publisher" ],
			encyclopedia: [ "title", "encyclopedia", "author", "editor", "accessdate", "edition", "year",
			"publisher", "volume", "location", "pages" ],
		        news: [ "title", "author", "url", "work", "date", "accessdate", "pages", "location", "agency" ],
			newsgroup : [ "title", "author", "date", "newsgroup", "id", "url", "accessdate" ],
		        "press release"	: [ "title", "url", "publisher", "date", "accessdate" ],
			interview : ["last", "first", "subjectlink", "interviewer", "title", "callsign", "city", "date", "program", "accessdate"],
		        episode : ["title", "series", "credits", "airdate", "city", "network", "season"],
			video : ["people", "date", "url", "title", "medium", "location", "publisher"]
		};

		/**
		 * Returns default parameters (to be suggested when editing) for current reference
		 * @return {Array} array of default parameter names; empty array if unknown
		*/
		this.getDefaultParams = function()
		{
			var curDefault = defaultParams[this.type];
			if(curDefault)
				return curDefault;
			else
				return []; // Return empty array rather than null to avoid dereferencing null.
		};

		this.isValid = function()
		{
		        if(this.type == '')
			{
			    return false;
			}
			var req = this.getRequiredParams();
			var i = 0;
			var allFound = true;
			for(var reqParam in req)
			{
				/* Ignore parameters in req object that are null, undefined, or false.
				   They are not required. */
				if(!req[reqParam])
					continue;
				allFound &= (reqParam in this.params);
				if(!allFound)
					break;
			}
			return allFound;
		};

		var iconMapping =
		{
			web : "page_white_world.png",
			book : "book.png",
			journal : "page_white_text.png",
			news : "newspaper.png",
			newsgroup : "comments.png",
			"press release" : "transmit_blue.png",
			interview : "telephone.png",
			episode : "television.png",
			video : "film.png"
		};

		var superGetIcon = this.getIcon;
		this.getIcon = function()
		{
			var icon = iconMapping[this.type];
			if(icon)
			{
				return proveit.STATIC_BASE + icon;
			}
			return superGetIcon.call(this);
		};
	},

	/**
	 * Versuch, an die Vorlage:Literatur anzupassen.
	 * @class CiteLit
	 * @for proveit
	 * @constructor
	 * @extends AbstractReference
	 * @param {Object} argObj the argument object, with keys for each option
	*/
	CiteLit : function(argObj)
	{
		/* Mostly an identity mapping, except for redirects.  I think
		 * having the self-mappings is better than some kind of special case array.
		 */
		var typeNameMappings =
		{
			Literatur:"Literatur",
			Internetquelle:"Internetquelle"
		};

		// Sets the type (e.g. web for cite web), applying the mappings.  This is up top because it is used in AbstractReference constructor.
		this.setType = function(rawType)
		{
			var mappedType = typeNameMappings[rawType];
			if(mappedType != null)
				this.type = mappedType;
			else
				this.type = rawType; // Use naive type as fallback.
		};

		proveit.AbstractReference.call(this, argObj);

		// TODO: Should CiteReference.getSortIndex and CitationReference.getSortIndex be merged into AbstractCitation?  Less fine-grained, but simpler to maintain.
		/**
		 * Returns the sort index for a given parameter
		 * @param {String} param parameter name
		 * @return {Number} sort index if found, otherwise -1
		 */
		this.getSortIndex = function(param)
		{
			// This is the order fields will be displayed or outputted.

			return jQuery.inArray(param, [
				"ISBN", "DOI", "Autor", "Herausgeber", "Titel", "TitelErg", "Sammelwerk", "WerkErg", "Band", "Nummer", "Auflage", "Verlag", "Ort", "Jahr", "Monat", "Tag", "Kapitel", "Seiten", "Spalten", "ISBNistFormalFalsch", "DNB", "ISSN", "ZDB", "LCCN", "Online", "Kommentar", "Originaltitel", "Originalsprache", "Übersetzer", "arxiv", "Zugriff", "Typ", 
"autor", "hrsg", "titel", "titelerg", "url", "zugriff", "sprache", "werk", "seiten", "datum", "archiv-url", "archiv-datum", "kommentar", "zitat", "offline", "format"
			]);
		};

		/**
		 * Returns this reference as a string.
		 * @return {String} reference as string
		 */
		this.toString = function()
		{
			return this.toStringInternal(this.type, false);
		};

		// References without these parameters will be flagged in red.
		// True indicates required (null, or undefined, means not required)
		var requiredParams =
		{
			Literatur : { "Titel": true },
			Internetquelle : { "url": true, "titel":true, "zugriff": true}
		};

		/**
		 * Return required parameters for this citation type.
		 * @return {Object} object with required parameters as keys and true as value; empty object for unknown type
		*/
		this.getRequiredParams = function()
		{
			var curReq = requiredParams[this.type];
			if(curReq)
				return curReq;
			else
				return {}; // Return empty object rather than null to avoid dereferencing null.
		};

		// These paramaters will be auto-suggested when editing.
		var defaultParams =
		{
			Literatur : [ "ISBN", "DOI", "Autor", "Titel", "Sammelwerk", "Verlag", "Ort", "Jahr", "Seiten"],
			Internetquelle : [ "url", "titel", "autor", "zugriff", "hrsg"]
		};

		/**
		 * Returns default parameters (to be suggested when editing) for current reference
		 * @return {Array} array of default parameter names; empty array if unknown
		*/
		this.getDefaultParams = function()
		{
			var curDefault = defaultParams[this.type];
			if(curDefault)
				return curDefault;
			else
				return []; // Return empty array rather than null to avoid dereferencing null.
		};

		this.isValid = function()
		{
		        if(this.type == '')
			{
			    return false;
			}
			var req = this.getRequiredParams();
			var i = 0;
			var allFound = true;
			for(var reqParam in req)
			{
				/* Ignore parameters in req object that are null, undefined, or false.
				   They are not required. */
				if(!req[reqParam])
					continue;
				allFound &= (reqParam in this.params);
				if(!allFound)
					break;
			}
			return allFound;
		};

		var iconMapping =
		{
			Literatur : "book.png",
			Internetquelle : "page_white_world.png",
		};

		var superGetIcon = this.getIcon;
		this.getIcon = function()
		{
			var icon = iconMapping[this.type];
			if(icon)
			{
				return proveit.STATIC_BASE + icon;
			}
			return superGetIcon.call(this);
		};
	},

	/**
	 * Versuch, an die Vorlage:BibRecord anzupassen.
	 * @class CiteBib
	 * @for proveit
	 * @constructor
	 * @extends AbstractReference
	 * @param {Object} argObj the argument object, with keys for each option
	*/
	CiteBib : function(argObj)
	{
		/* Mostly an identity mapping, except for redirects.  I think
		 * having the self-mappings is better than some kind of special case array.
		 */
		var typeNameMappings =
		{
			BibISBN:"BibISBN",
			BibDOI:"BibDOI"
		};

		// Sets the type (e.g. web for cite web), applying the mappings.  This is up top because it is used in AbstractReference constructor.
		this.setType = function(rawType)
		{
			var mappedType = typeNameMappings[rawType];
			if(mappedType != null)
				this.type = mappedType;
			else
				this.type = rawType; // Use naive type as fallback.
		};

		proveit.AbstractReference.call(this, argObj);

		// TODO: Should CiteReference.getSortIndex and CitationReference.getSortIndex be merged into AbstractCitation?  Less fine-grained, but simpler to maintain.
		/**
		 * Returns the sort index for a given parameter
		 * @param {String} param parameter name
		 * @return {Number} sort index if found, otherwise -1
		 */
		this.getSortIndex = function(param)
		{
			// This is the order fields will be displayed or outputted.

			return jQuery.inArray(param, [
				"ISBN", "doi", "Seite", "Kommentar", "format"
			]);
		};

		/**
		 * Returns this reference as a string.
		 * @return {String} reference as string
		 */
		this.toString = function()
		{
			return this.toStringInternal(this.type, false).replace(/ISBN=|doi=/g, "");
		};

		// References without these parameters will be flagged in red.
		// True indicates required (null, or undefined, means not required)
		var requiredParams =
		{
			BibISBN : { "ISBN": true },
			BibDOI : { "doi": true }
		};

		/**
		 * Return required parameters for this citation type.
		 * @return {Object} object with required parameters as keys and true as value; empty object for unknown type
		*/
		this.getRequiredParams = function()
		{
			var curReq = requiredParams[this.type];
			if(curReq)
				return curReq;
			else
				return {}; // Return empty object rather than null to avoid dereferencing null.
		};

		// These paramaters will be auto-suggested when editing.
		var defaultParams =
		{
			BibISBN : [ "ISBN", "Seite", "Kommentar", "format"],
			BibDOI : [ "doi", "Seite", "Kommentar", "format"]
		};

		/**
		 * Returns default parameters (to be suggested when editing) for current reference
		 * @return {Array} array of default parameter names; empty array if unknown
		*/
		this.getDefaultParams = function()
		{
			var curDefault = defaultParams[this.type];
			if(curDefault)
				return curDefault;
			else
				return []; // Return empty array rather than null to avoid dereferencing null.
		};

		this.isValid = function()
		{
		        if(this.type == '')
			{
			    return false;
			}
			var req = this.getRequiredParams();
			var i = 0;
			var allFound = true;
			for(var reqParam in req)
			{
				/* Ignore parameters in req object that are null, undefined, or false.
				   They are not required. */
				if(!req[reqParam])
					continue;
				allFound &= (reqParam in this.params);
				if(!allFound)
					break;
			}
			return allFound;
		};

		var iconMapping =
		{
			BibISBN : "book.png",
			BibDOI : "page_white_text.png"
		};

		var superGetIcon = this.getIcon;
		this.getIcon = function()
		{
			var icon = iconMapping[this.type];
			if(icon)
			{
				return proveit.STATIC_BASE + icon;
			}
			return superGetIcon.call(this);
		};

		this.getInsertionText = function(full)
		{
			proveit.log("getInsertionText");
			if(full)
			{
				return this.toString();
			}
			else
			{
				if(this.name)
				{
					return "<ref name=\""
						+ this.name + "\" />";
				}
				else
				{
					throw new Error("getInsertionText: ref.name is null");
				}
			}
		};

		this.recordExists = false;
		this.getID = this.params.ISBN ? this.params.ISBN.replace(/-/g, "") : this.params.doi ? this.params.doi : "";
		if (this.getID != "") {
			var recordURL = mw.config.get('wgServer') + mw.config.get('wgScriptPath') + "/index.php?&title=Vorlage:" + this.type + "/" + this.getID + "&action=raw";
			jQuery.ajax({ url : recordURL, context : this, success : function(data) {
				var loadedRef = proveit.makeRef("<ref>" + data + "</ref>");
				this.record = loadedRef.params;
				this.recordExists = true;
				if (this.rowNumber) {
					var oldRow = jQuery("td.number:contains(" + this.rowNumber + ")").get(0).parentNode;
					var newRichItem = proveit.makeRefBoxRow(this, true);
					jQuery('td.number',newRichItem).text(this.rowNumber); // preserve old numbering
					oldRow.parentNode.replaceChild(newRichItem, oldRow);
				}
			}});
		};

	},

	/**
	 * Constructor for RawReference type.
	 * @class RawReference
	 * @for proveit
	 * @constructor
	 * @extends AbstractReference
	 * @param {Object} argObj the argument object, with keys for each option
	 */
	RawReference : function(argObj)
	{
		proveit.AbstractReference.call(this, argObj);
		this.type = 'raw';

		/**
		 * Returns this reference as a string.
		 * @return {String} reference as string
		 */
		this.toString = function()
		{
			return this.orig;
		};
		this.params['title'] = this.orig;

		this.getIcon = function()
		{
			return proveit.STATIC_BASE + 'raw.png';
		};
	},

	// TODO: This should be unified with changeRefFromEditPane
	/**
	 * Convert the current contents of the add citation panel to a reference (i.e CiteReference(), CitationReference())
	 * @for proveit
	 * @param {Node} box typepane root of add GUI (pane for specific type, e.g. journal)
         * @return {AbstractReference} ref or null if no panel exists yet.
	 */
	getRefFromAddPane : function(box)
	{
		// get this working, lots of typing here.

		var type = box.id;

		// get <ref> name
		var refName = jQuery('#addrefname').val();

		var citeFunc = type == 'Literatur' ? this.CiteLit : type == 'Internetquelle' ? this.CiteLit : type == 'BibISBN' ? this.CiteBib : type == 'BibDOI' ? this.CiteBib : this.CiteReference;

		var pa = {};

		var paramName, paramVal;

		var paramList = jQuery(".paramlist", box)[0];
		var paramRows = jQuery('div', paramList);
		for (var i = 0; i < paramRows.length; i++)
		{
			var paramRow =  paramRows[i];
			this.log("getRefFromAddPane: i: " + i + ", paramRow: " + paramRow);
			var valueTextbox = jQuery(".paramvalue", paramRow)[0];

			if(jQuery(paramRow).hasClass("addedrow")) // Added with "Add another field"
			{
				paramName = jQuery(".paramdesc", paramRow)[0].value.trim();
			}
			else
			{
				paramName = valueTextbox.id.substring(this.NEW_PARAM_PREFIX.length);
			}
			this.log("getRefFromAddPane: paramRow.childNodes.length: " + paramRow.childNodes.length);
			this.log("getRefFromAddPane: valueTextbox.refName: " + valueTextbox.refName);
			this.log("getRefFromAddPane: valueTextbox.id: " + valueTextbox.id);

			paramVal = valueTextbox.value.trim();
			this.log("getRefFromAddPane: paramName: " + paramName + "; paramVal: " + paramVal);
			if(paramName != "" && paramVal != "")
			{ // Non-blank
				pa[paramName] = paramVal;
			}
		}
		var ref = new citeFunc({"name": refName, "type": type, "params": pa});
		ref.update();
		this.log("Exiting getRefFromAddPane");
		return ref;
	},

	/**
	 * Called from the add citation panel, this is the function used to
	 * add the actual citation.
	 *
	 * @param {AbstractReference} ref reference being added
	 */
	addReference : function(ref) {
		// get this working, lots of typing here.

		//this.addNewElement(ref);
		jQuery("#refs").append(this.makeRefBoxRow(ref, false));

		ref.orig = ref.toString();
		/*
		 * Cycle through the boxes and grab the id's versus the values, watch
		 * for the final box and make sure to grab the type as well
		 */

		this.insertRefIntoMWEditBox(ref, true); // true means insert full text here, regardless of global toggle.
		ref.save = true;
		ref.inMWEditBox = true;
	},

	/**
	 * Clear all rows of passed in add citation panes.
	 * @param {Node} citePanes raw DOM element
	 */
	clearCitePanes : function(citePanes)
	{
		if(citePanes.hasChildNodes())
		{
			citePanes.removeChild(citePanes.firstChild);
		}
	},

	/**
	 * Changes the panel for the add reference panel to the correct type of entry
	 * @param {Node} menu Raw HTML menu element
	 */
	changeAddPane : function(menu) {
		//this.log("menu.id: " + menu.id);

		// Reset scroll
		jQuery('#add-fields').scrollTop(0);
		jQuery(menu.parentNode).show(); // cite/citation vbox.

		var citePanes = jQuery(".addpanes", menu.parentNode.parentNode).get(0);
		//this.log("citePanes: " + citePanes);
		this.clearCitePanes(citePanes);
		var newRefType = menu.value;

		var genPane = document.getElementById("dummyCitePane").cloneNode(true);
		genPane.id = newRefType.replace(' ', '_');

		// name the ref-name-row
		jQuery('.ref-name-row',genPane).children('input').attr('id','addrefname');
		jQuery('.ref-name-row',genPane).children('label').attr('for','addrefname');

		// Somewhat hackish.  What's a better way?
		var newRef;
		if((menu.value == "Literatur") || (menu.value == "Internetquelle"))
		{
			newRef = new this.CiteLit({});
		}
		else if((menu.value == "BibISBN") || (menu.value == "BibDOI"))
		{
			newRef = new this.CiteBib({});
		}
		else
		{
			newRef = new this.CiteReference({});
		}
		newRef.type = newRefType;
		var descs = this.getDescriptions();
		var defaultParams = newRef.getDefaultParams().slice(0); // copy
		defaultParams.sort(newRef.getSorter());
		//var required = newRef.getRequiredParams();

		// Possibly, Cite objects should automatically include default parameters in their param maps.  That would seem to make this simpler.
		for(var i = 0; i < defaultParams.length; i++)
                {
			newRef.params[defaultParams[i]] = "";
		}

		this.log("changeAddPane: newRef: " + newRef);

		// Should there be a getParamKeys or similar function for this, or even getSortedParamKeys?
		var newParams = [];
		for(param in newRef.params)
		{
			newParams.push(param);
		}
		newParams.sort(newRef.getSorter());
		var required = newRef.getRequiredParams();

		var paramList = jQuery(".paramlist", genPane)[0];
		for(var i = 0; i < newParams.length; i++)
		{
			var param = newParams[i];
			this.addPaneRow(paramList.parentNode, newParams, descs, param, required[param], true);
		}
		jQuery(genPane).show();
		citePanes.insertBefore(genPane, citePanes.firstChild);
		this.log("Exiting changeAddPane");
	},

	/**
	 * Create ProveIt HTML GUI
	 */
	createGUI : function()
	{
                if(this.getGUI().length > 0)
                {
                        // GUI already created
                        return false;
                }

		importStylesheetURI(this.STATIC_BASE + 'styles.css');
		importStylesheetURI(this.JQUERYUI_STYLES_URL);

		// more JqueryUI CSS: http://blog.jqueryui.com/2009/06/jquery-ui-172/
		var gui = jQuery('<div/>', {id: this.GUI_ID});
		var tabs = jQuery('<div/>', {id: 'tabs'});
		var created = jQuery('<h1/>').width('40%');
		var createdLink = jQuery('<a/>', {title: 'Created by the ELC Lab at Georgia Tech',
			                     href: 'http://proveit.cc.gatech.edu',
					     target: '_blank'});
		// Main logo in upper-right
		var logo = jQuery('<img/>', {src: this.STATIC_BASE + 'logo.png', alt: 'ProveIt', height: 30, width: 118 });
		createdLink.append(logo);
		created.append(createdLink);
		// Minimize/maximize button
		var showHideButton = jQuery('<button/>', {text: 'einklappen/ausklappen'});
		created.append(showHideButton);
		tabs.append(created);
		var header = jQuery('<ul/>');
		var view = jQuery('<li/>');
		// View tab link
		var viewLink = jQuery('<a/>', {id: 'view-link', "class": 'tab-link', href: '#view-tab'});
		viewLink.append('Nachweise (');
		var numRefs = jQuery('<span/>', {id: 'numRefs'}).
			append('0');
		viewLink.append(numRefs).
			append(')');
		view.append(viewLink);
		header.append(view);
		var add = jQuery('<li/>');
		// Add tab link
		var addLink = jQuery('<a/>', {id: 'add-link', "class": 'tab-link', href: '#add-tab'}).
			append('Neuer Nachweis');
		add.append(addLink);
		header.append(add);
		var bib = jQuery('<li/>');
		// Add bibliothek link
		var bibLink = jQuery('<a/>', {id: 'bib-link', "class": 'tab-link', href: '#bib-tab'}).
			append('Bibliothek');
		bib.append(bibLink);
		header.append(bib);
		tabs.append(header);
		// View tab
		var viewTab = jQuery('<div/>', {id: 'view-tab', css: {display: 'none'}});
		// View pane used for displaying references; within view tab
		var viewPane = jQuery('<div/>', {id: 'view-pane'});
		var viewScroll = jQuery('<div/>', {"class": 'scroll',
					      style: 'height: 210px;'});
		// Ref list root element
		var refTable = jQuery('<table/>', {id: 'refs'});
		var dummyRef = jQuery('<tr/>', {id: 'dummyRef',
					   style: 'display: none;'});
		dummyRef.append(jQuery('<td/>', {"class": 'number'})).
			append(jQuery('<td/>', {"class": 'type'})).
			append(jQuery('<td/>', {"class": 'title'}));
			//append(jQuery('<td/>', {"class": 'details'}));
		var editTd = jQuery('<td/>', {"class": 'edit'}).
			append(jQuery('<button/>', {text: 'bearbeiten'}));
		dummyRef.append(editTd);
		refTable.append(dummyRef);
		viewScroll.append(refTable);
		viewPane.append(viewScroll);
		viewTab.append(viewPane);
		// div#edit-pane, within view tab
		var editPane = jQuery('<div/>', {id: 'edit-pane', style: 'display: none'});
		// div#edit-fields
		var editFields = jQuery('<div/>', {id: 'edit-fields',
					      "class": 'inputs scroll',
					      style: 'height: 170px',
					      tabindex: 0});
		// div.ref-name-row
     		var refNameRow = jQuery('<div/>', {"class": 'ref-name-row',
					      tabindex: -1});
		var refLabel = jQuery('<label/>', {'for': 'editrefname',
					      title: 'Eine eindeutige Bezeichnung, um den Nachweis mehrmals im Artikel verwenden zu können.',
					      "class": 'paramdesc'}).
			append('&lt;ref&gt; name');
		refNameRow.append(refLabel);
		refNameRow.append(jQuery('<input/>', {id: 'editrefname',
	                                       "class": 'paramvalue'}));
		// div.paramlist
		var paramList = jQuery('<div/>', {"class": 'paramlist'});

		editFields.append(refNameRow);
		editFields.append(paramList);
		editPane.append(editFields);

		// div#edit-buttons, part of edit pane
		var editButtons = jQuery('<div/>', {id: 'edit-buttons'});
		var addFieldButton = jQuery('<button/>', {style: 'margin-right: 50px;'}).
			append('Eingabefeld hinzufügen');
		editButtons.append(addFieldButton);
		var reqSpan = jQuery('<span/>', {"class": 'required',
					    text: 'fett'});
		editButtons.append(reqSpan).
			append(' = benötigte Angabe');
		var saveButton = jQuery('<button/>', {"class": 'right-side accept',
		                                 text: 'Artikeltext aktualisieren'});
		editButtons.append(saveButton);
		var cancelButton = jQuery('<button/>', {"class": 'right-side cancel',
			                           text: 'abbrechen'});
		editButtons.append(cancelButton);
		editPane.append(editButtons);
		viewTab.append(editPane);
		tabs.append(viewTab);

		// dumy cite pane
		var dummyCite = jQuery('<div/>', {id: 'dummyCitePane',
					     "class": 'typepane',
					     style: 'display: none'});
		var addRefNameRow = refNameRow.clone();
		//jQuery('input', addRefNameRow).attr('id', 'addrefname');
		//jQuery('label', addRefNameRow).attr('for', 'addrefname');
		dummyCite.append(addRefNameRow);
		dummyCite.append(jQuery('<div/>', {"class": 'paramlist'}));
		tabs.append(dummyCite);

		var preloadedparam = jQuery('<div/>', {id: 'preloadedparamrow',
						  "class": 'preloadedrow input-row',
						  style: 'display: none'}).
			append(jQuery('<label/>', {"class": 'paramdesc'}));
		var paramvalue = jQuery('<input/>', {"class": 'paramvalue',
				                tabindex: -1});
	        preloadedparam.append(paramvalue);
		var deleteButton = jQuery('<button/>', {"class": 'delete-field'}).
			append('Eingabefeld beseitigen');
		preloadedparam.append(deleteButton);
		tabs.append(preloadedparam);
		var addedparam = jQuery('<div/>', {id: 'addedparamrow',
					      "class": 'addedrow input-row',
 					      style: 'display: none'}).
		        append(jQuery('<input/>', {"class": 'paramdesc',
					      tabindex: -1})).
			append(paramvalue.clone()).
			append(deleteButton.clone());
		tabs.append(addedparam);
		// Add tab
		var addTab = jQuery('<div/>', {id: 'add-tab', css: {display: 'none'}});
		var addFields = jQuery('<div/>', {id: 'add-fields',
					     "class": 'inputs scroll',
					     style: 'height: 170px'});
		var cite = jQuery('<div/>', {style: 'display: none',
					id: 'cite',
				        "class": 'input-row'});
		var refCiteTypeLabel = jQuery('<label/>', {'for': 'citemenu',
						  "class": 'paramdesc required',
						  text: 'Art des Nachweises'});
		cite.append(refCiteTypeLabel);
		var citemenu = jQuery('<select/>', {id: 'citemenu',
					       change: function()
					       {
						       proveit.changeAddPane(citemenu.get(0));
					       }});
         	var citeTypes = this.CiteReference.getTypes();
		var descs = this.getDescriptions();
		for(var i = 0; i < citeTypes.length; i++)
		{
			citemenu.append(jQuery('<option/>', {value: citeTypes[i],
						        text: descs[citeTypes[i]]}));
		}
		cite.append(citemenu);
		addFields.append(cite);
		addFields.append(jQuery('<div/>', {"class": 'addpanes',
					      id: 'citepanes',
					      tabindex: 0}));
		addTab.append(addFields);
		// Add buttons, part of add tab
		var addButtons = jQuery('<div/>', {id: 'add-buttons'});
		addButtons.append(jQuery('<button/>', {style: 'margin-right: 50px;',
						  text: 'Eingabefeld hinzufügen'})).
			append(reqSpan.clone()).
			append(" = benötigt").
			append(saveButton.clone().text('in Artikeltext einfügen')).
			append(cancelButton.clone());
		addTab.append(addButtons);
		tabs.append(addTab);
		// Bib tab
		var bibTab = jQuery('<div/>', {id: 'bib-tab', css: {display: 'none', padding: '5px'}});
		var bibScroll = jQuery('<div/>', {"class": 'scroll',
					      style: 'height: 190px;'});
		var bibSelect = jQuery('<div/>', {
					id: 'bibselect',
				        "class": 'inputs input-row', "style": 'padding: 3px'});
		var bibSelectLabel = jQuery('<label/>', {'for': 'bibmenu',
						  "class": 'paramdesc required',
						  text: 'Bibliothek auswählen:'});
		bibSelect.append(bibSelectLabel);
		var bibmenu = jQuery('<select/>', {id: 'bibmenu', change: function()
			{
				if (bibmenu.get(0).value == 'andere') {
					var newBib = prompt('Neue Bibliothek auswählen:');
					if (newBib && (newBib != '')) {
						var newBibURL = mw.config.get('wgServer') + mw.config.get('wgScriptPath') + '/index.php?&title=' + mw.util.wikiUrlencode(newBib) + '&action=raw'; 
						jQuery("#bibmenu").prepend(jQuery('<option/>', {value: newBib, text: newBib, selected: true}));
						jQuery.ajax({ url : newBibURL, success : function(data) {
							proveit.scanForRefs(jQuery("#bibs"), data);
							window.bibURL.push(jQuery("#bibmenu").get(0).value); }});
					}
				} else {
					var newBibURL =  mw.config.get('wgServer') + mw.config.get('wgScriptPath') + '/index.php?&title=' + bibmenu.get(0).value + '&action=raw';
					jQuery.ajax({ url : newBibURL, context : this, success : function(data) { proveit.scanForRefs(jQuery("#bibs"), data); }});
				}
			}});
		var bURL = mw.config.get('wgServer') + mw.config.get('wgScriptPath') + '/index.php?&title='
		if (window.bibURL) {
			bURL += window.bibURL[0]; 
			for(var i = 0; i < window.bibURL.length; i++)
			{
				bibmenu.append(jQuery('<option/>', {value: window.bibURL[i], text: window.bibURL[i]}));
			}
		} else {
			bURL += 'Benutzer:' + wgUserName + '/References';
			bibmenu.append(jQuery('<option/>', {value: 'Benutzer:' + wgUserName + '/References', text: 'Benutzer:' + wgUserName + '/References'}));
		}
		bibmenu.append(jQuery('<option/>', {id: 'bibSelectNew', value: 'andere', text: 'neue Bibliothek auswählen...'}));
		bibSelect.append(bibmenu);
		bibTab.append(bibSelect);
		// Ref list root element
		var bibTable = jQuery('<table/>', {id: 'bibs'});
		var dummyRef = jQuery('<tr/>', {id: 'dummyRef',
					   style: 'display: none;'});
		dummyRef.append(jQuery('<td/>', {"class": 'number'})).
			append(jQuery('<td/>', {"class": 'type'})).
			append(jQuery('<td/>', {"class": 'title'}));
			//append(jQuery('<td/>', {"class": 'details'}));
		var editTd = jQuery('<td/>', {"class": 'edit'}).
			append(jQuery('<button/>', {text: 'bearbeiten'}));
		dummyRef.append(editTd);
		bibTable.append(dummyRef);
		bibScroll.append(bibTable);
		bibTab.append(bibScroll);
		tabs.append(bibTab);

		gui.append(tabs);
		jQuery(document.body).prepend(gui);

		var cancelEdit = function() {
				jQuery("#edit-pane").hide();
				jQuery("#view-pane").show();
		};

		// set up tabs
		jQuery("#tabs").tabs({
			selected: 0,
				show: function(event,ui)
				{
					switch(ui.index)
					{
						case 0: // view
						//jQuery('tr.selected').focus();
						break;

						case 1: // add
						    cancelEdit();
						    proveit.changeAddPane(citemenu.get(0));
						break;

				      //	case 1: // edit
						// proveit.updateEditPane();
						//	jQuery('tr.selected').dblclick();
						//break;

						default:
						// nothing
					}
				}
		});

		// handle clicking on tabs
		jQuery(viewLink).click(function(){
				if(jQuery(viewTab).is(":hidden"))
					proveit.toggleViewAddVisibility();
				else
					cancelEdit();	// Edit and view are the same tab, so we handle this specially.
			});
		jQuery(addLink).click(function(){
				if(jQuery(addTab).is(":hidden"))
					proveit.toggleViewAddVisibility();
			});
		jQuery(bibLink).click(function(){
				if(jQuery(bibTab).is(":hidden"))
					proveit.toggleViewAddVisibility();
			});


		// add panel buttons
		jQuery("#add-buttons button:first").button({
			icons: {
				primary: 'ui-icon-circle-plus'
			}
		}).click(function()
			 {
				 proveit.addPaneRow(document.getElementById("add-tab"));
			 })
		.next().next().button({
			icons: {
				primary: 'ui-icon-circle-check',
				secondary: 'ui-icon-circle-arrow-e'
			}
		}).click(function()
			 {
				 proveit.addReference(proveit.getRefFromAddPane(jQuery('#add-tab .typepane').get(0)));
				 jQuery("#tabs").tabs( { selected: '#view-tab' } );
				 jQuery("div.scroll, #view-pane").scrollTop(100000); // scroll to new ref
			 }).next().
		button({
			icons: {
				primary: 'ui-icon-circle-close'
				}
		}).click(function()
			 {
				 jQuery("#tabs").tabs( { selected: '#view-tab' } );
			 });

		// cancel buttons
		jQuery("button.cancel").click(cancelEdit);

		// edit panel buttons
		jQuery("#edit-buttons button:first").button({
			icons: {
				primary: 'ui-icon-circle-plus'
			}
		}).click(function()
			 {
				 proveit.addPaneRow(jQuery("#edit-pane"));
			 }).
		next().next().
		button({
			icons: {
				primary: 'ui-icon-circle-check'
			}
		}).next().button({
			icons: {
				primary: 'ui-icon-circle-close'
			}
		});

		// delete field button
		jQuery(".delete-field").button({
			icons: {
				primary: 'ui-icon-close'
			},
			text: false
		});

		// create the minimize button
		showHideButton.button({
			icons: {
				primary: 'ui-icon-triangle-1-n'
			},
			text: false
		});

		var viewAndAdd = jQuery("#view-tab, #add-tab, #bib-tab");
		this.viewAndAddPanes = viewAndAdd;

		function minimize()
		{
			viewAndAdd.hide();
			showHideButton.button("option", "icons", { primary: 'ui-icon-triangle-1-n' } );
		}

		function maximize()
		{
			viewAndAdd.show();
			showHideButton.button("option", "icons", { primary: 'ui-icon-triangle-1-s' } );
		}

		// set up the minimize button
		showHideButton.toggle(
			maximize,
			minimize
		);

		this.toggleViewAddVisibility = function()
		{
			showHideButton.click();
		}

		jQuery.ajax({ url : bURL + '&action=raw', context : this, success : function(data) {
			this.scanForRefs(jQuery("#bibs"), data);
		}});
		var MWBoxToScan = jQuery("#refs");
		var MWTextToScan = this.getMWEditValue();
		this.scanForRefs(MWBoxToScan, MWTextToScan);

		jQuery("#refs tr").eq(0).click().click(); // select first item in list.  TODO: Why two .click?
	},

        /**
         * A reference to the set containing two items, the view and add tabs.  Will be initialized by createGUI, so it is non-null if ProveIt is visible
         *
         * @type {jQueryNodeSet}
         */
        viewAndAddPanes : null,

        /*
         * Gets jQuery set for ProveIt GUI, which will be empty if ProveIt has not initialized
         *
         * @return {jQueryNode} root of ProveIt
         */
        getGUI : function()
        {
               return jQuery('#' + this.GUI_ID);
        },

        /**
         * Hides ProveIt completely
         */
        hide : function()
        {
                this.getGUI().hide();
        },

        /**
         * Show ProveIt
         */
        show : function()
        {
                this.createGUI();
                this.getGUI().show();
        },

        /**
         * Toggle overall visiblility.  If currently hidden, go to minimized.  If minimized, maximize.  If maximize, hide
         */
        toggleVisibility : function()
        {
                if(this.getGUI().is(':visible'))
                {
                        if(this.viewAndAddPanes.is(':visible')) // maximized
                        {
                                this.hide();
                        }

                        /*
                         * If previously maximized, we minimize after hiding, so when we show, it will already be minimized.
                         * If minimized, we maximize
                         */
                        this.toggleViewAddVisibility();
                }
                else
                {
                        this.show();
                }
        },

        /**
         * Toggle visibility of view and add panes.  Initialized by createGUI
         *
         * @method toggleViewAddVisibility
         */
         toggleViewAddVisibility : null,

	/**
	 * Generates refbox row and all children, to be used by addNewElement, and when updating
	 *
	 * @param {AbstractReference} ref reference to generate from
	 * @param {Boolean} isReplacement if true, this replaces another refbox item, so no number will be assigned, and the count will not be updated.
	 * @return {Node} new refbox row for refbox
	 */
	makeRefBoxRow : function(ref, isReplacement)
	{
		var refName = ref.name; //may be null or blank

		//var refbox = this.getRefBox();

		var newchild = jQuery('<tr><td class="number"></td><td class="type"></td><td class="title"></td><td class="edit" id="edit"></td><td class="edit" id="ins"></td></tr>').get(0);

		if(!ref.isValid())
		{
			// Flag as invalid.
			jQuery(newchild).addClass('invalid');
		}
		// grab the nodes that need changed out of it
		var thisproveit = this;

		var title = ref.getLabel();
		jQuery('td.title', newchild).text(this.truncateTitle(title));
		jQuery('td.title', newchild).attr('title', title);

		// deal with variations of date info
		var formattedYear = '';

		if(ref.params['year'])
			formattedYear = ref.params['year'];
		else if(ref.params['Jahr'])
			formattedYear = ref.params['Jahr'];
		else if(ref.params['datum'])
			formattedYear = ref.params['datum'];
		else if(ref.record['Jahr'])
			formattedYear = ref.record['Jahr'];
		else if (ref.params['date'])
		{
		        var yearMatch = ref.params['date'].match(/^([12]\d{3})/);
			if(yearMatch)
			{
				formattedYear = yearMatch[1];
			}
		}

		// deal with variations of author info
		var formattedAuthor = ref.params.author ? ref.params.author : ref.params.Autor ? ref.params.Autor : ref.params.autor ? ref.params.autor : ref.record.Autor ? ref.record.Autor : ref.params.Herausgeber ? ref.params.Herausgeber : ref.params.hrsg ? ref.params.hrsg : ref.params.last ? ref.params.last : '';

		if(ref.params['coauthors'] || ref.params['last2'])
			formattedAuthor += ' <i>et al.</i>';

		// generate a URL based on ref type
		var icon = ref.getIcon(), url = '', refType = ref.type;

		switch(refType)
		{
			case 'Literatur':
			case 'BibISBN':
			case 'BibDOI':
			case 'book':
			case 'journal':
			case 'conference':
				if(ref.params['ISBN'] != null) {
					url = wgServer + '/w/index.php?title=Special%3ABookSources&isbn=' + ref.params['ISBN'];
				} else if(ref.params['isbn'] != null) {
					url = wgServer + '/w/index.php?title=Special%3ABookSources&isbn=' + ref.params['isbn'];
				} else if(ref.params['DOI'] != null) {
					url = 'http://dx.doi.org/' + ref.params['DOI'];
				} else if(ref.params['doi'] != null) {
					url = 'http://dx.doi.org/' + ref.params['doi'];
				}
				break;
			case 'Internetquelle':
			case 'web':
			case 'news':
				url = ref.params['url'];
				break;
			case 'episode':
				url = 'http://www.imdb.com/find?s=ep&q=' + escape(ref.params['title']);
				break;
		}
		jQuery('td.type', newchild).css('background-image','url('+icon+')');
		jQuery('td.type', newchild).attr('title',ref.type);


		var authorByline = '', yearByline = '', refTypeByline = '', byline = '', separator = ' | ';
		if(url != '')
		{
			refType = '<a href="' + url + '" target="_blank">' + refType + '</a>';
		}
		refTypeByline = 'Art: <span class="type">' + refType + '</span>';

		if(refType == 'raw')
		{
			byline = refTypeByline + separator + ref.toString();
		}
		else
		{
			if(formattedAuthor != '')
			{
				byline = 'Von: <span class="author">' + formattedAuthor + '</span>' + separator;
			}
			if(formattedYear != '') {
				byline += 'Datum: <span class="date">' + formattedYear + '</span>' + separator;
			}
			byline += refTypeByline;
		}
 
		byline = '<p>' + byline + '</p>';

		// create expanded <div>
		var expanded = jQuery('<div />',{
							"class": 'expanded'
						});

		// append the infobar to the expanded info box
		jQuery(expanded).append(byline);

		// append the expanded info box to the title <td>
		jQuery('td.title', newchild).append(expanded);

		if (ref.box == "bibs") {
			// get ref number by counting number of refs (this includes dummy ref, but not the one we're creating)
			var numRefs = jQuery('#bibs tr').length;
			jQuery('td.number', newchild).text(numRefs);
			ref.rowNumber = numRefs;
			// event handler for selecting a ref)
			jQuery(newchild).click(function() {
				jQuery("#bibs tr").removeClass('selected');
				jQuery(newchild).addClass('selected');
			});
			// SMALL IBID BUTTON
			// create button
			var smallibidBtn = jQuery('<button />',{
					text: 'Nachweis in den Artikel einfügen'
				});

			// transform button
			jQuery(smallibidBtn).button({
				icons: {
					primary: 'ui-icon-arrowthick-1-e'
				},
				text: false
			});

			// button click event handler
			smallibidBtn.click(function(){
					thisproveit.insertRefIntoMWEditBox(ref, true);
					return false;
				});

			// append button
			jQuery('#ins', newchild).append(smallibidBtn);
			// LARGE IBID BUTTON
			// create button
			var ibidBtn = jQuery('<button />',{
					"class": 'insert',
					text: 'Nachweis in den Artikel einfügen'
				});

			// transform button
			jQuery(ibidBtn).button({
				icons: {
					primary: 'ui-icon-arrowthick-1-e'
				},
				text: true
			});

			// button click event handler
			ibidBtn.click(function(){
					thisproveit.insertRefIntoMWEditBox(ref, true);
					return false;
				});

			// append button
			expanded.append(ibidBtn);
		}
		else {
			if(!isReplacement)
			{
				// get ref number by counting number of refs (this includes dummy ref, but not the one we're creating)
				var numRefs = jQuery('#refs tr').length;
				jQuery('td.number', newchild).text(numRefs);
			 	jQuery('#numRefs').text(numRefs); // update the number of refs in the view tab
				ref.rowNumber = numRefs;
			}

			// event handler for selecting a ref)
			jQuery(newchild).click(function() {
				//thisproveit.highlightTargetString(ref.orig);
				thisproveit.highlightTargetString(ref.orig);
				jQuery("#refs tr").removeClass('selected');
				jQuery(newchild).addClass('selected');
			});

			var doEdit = function() {
				thisproveit.updateEditPane(ref, "edit");

				jQuery("#view-pane").hide();
				jQuery("#edit-pane").show();
			};

			var citationStrings = ref.getCitationStrings();

			//var pointers = jQuery('.pointers', newchild);

			var allCitations = jQuery('<span class="all-citations" />');

			for(var i = 0; i < citationStrings.length; i++)
			{
				var dividend = i + 1;
				var colName = "";

				while(dividend > 0)
				{
					var mod = --dividend % 26;
					colName = String.fromCharCode(97 + mod) + colName;  // a = 97
					dividend = Math.floor(dividend / 26);
				}
				var citationHolder = jQuery('<a href="#">' + colName + '</a>');
				// Bind i
				var clickFunc = (function(i)
				{
					return function()
					{
						var last = 0, j = 0;
						var text = proveit.getMWEditValue();
						for(j = 0; j < i; j++)
						{
							last = text.indexOf(citationStrings[j], last);

							// Shouldn't happen.  Indicates citation strings are out of date.
							if(last == -1)
							{
								proveit.log("citationStrings[" + j + "]: " + citationStrings[j] + " not found.  Returning.");
								return false;
							}
							last += citationStrings[j].length;
						}
						var startInd = text.indexOf(citationStrings[i], last);
						if(startInd == -1)
						{
							proveit.log("citationStrings[" + i + "]: " + citationStrings[i] + " not found.");
						}
						else
						{
							proveit.highlightLengthAtIndex(startInd, citationStrings[i].length);
						}
						return false;
					};
				})(i);

				citationHolder.click(clickFunc);
				allCitations.append(citationHolder);
			}


			if(citationStrings.length > 1)
			{
				var newP = jQuery('<p />');
				newP.append('Dieser Nachweis wird im Artikel <span class="num-citations">' + citationStrings.length + ' mal verwendet</span>: ').append(allCitations);
				expanded.append(newP);
			}

			// edit buttons
			if(ref.type != 'raw')
			{
				// SMALL EDIT BUTTON

				// create button
				var smallEditBtn = jQuery('<button />',{
					text: 'Nachweis bearbeiten'
				});

				// transform button
				jQuery(smallEditBtn).button({
					icons: {
						primary: 'ui-icon-pencil'
					},
					text: false
				});

				// button click event handler
				smallEditBtn.click(doEdit);

				// append button
				jQuery('#edit', newchild).append(smallEditBtn);

				// LARGE EDIT BUTTON

				// create button
				var editBtn = jQuery('<button />',{
						"class": 'edit',
						text: 'Nachweis bearbeiten'
					});

				// transform button
				jQuery(editBtn).button({
					icons: {
						primary: 'ui-icon-pencil'
					},
					text: true
				});

				// button click event handler
				editBtn.click(doEdit);

				// append button
				expanded.append(editBtn);

				// ROW EVENT HANDLER
				jQuery(newchild).dblclick(doEdit);
			}
			else
			{
				// needed to keep all rows the same height
				jQuery('.edit', newchild).append('&nbsp;');
			}

			if(citationStrings.length > 0)
			{
				// SMALL IBID BUTTON
				// create button
				var smallibidBtn = jQuery('<button />',{
					text: 'Nachweis in den Artikel einfügen'
				});

				// transform button
				jQuery(smallibidBtn).button({
					icons: {
						primary: 'ui-icon-arrowthick-1-e'
					},
					text: false
				});

				// button click event handler
				smallibidBtn.click(function(){
					thisproveit.insertRefIntoMWEditBox(ref, false);
					return false;
				});

				// append button
				jQuery('#ins', newchild).append(smallibidBtn);
				// LARGE IBID BUTTON
				// create button
				var ibidBtn = jQuery('<button />',{
					"class": 'insert',
					text: 'Nachweis in den Artikel einfügen'
				});

				// transform button
				jQuery(ibidBtn).button({
					icons: {
						primary: 'ui-icon-arrowthick-1-e'
					},
					text: true
				});

				// button click event handler
				ibidBtn.click(function(){
					thisproveit.insertRefIntoMWEditBox(ref, false);
					return false;
				});

				// append button
				expanded.append(ibidBtn);
			}
		}
		// alternate row colors
		jQuery(newchild).addClass((ref.rowNumber % 2) ? 'dark' : 'light');
		return newchild;
	},

	/**
	 * Truncates title to fit ProveIt refbox row.
	 * @param {String} title title to truncate
	 * @return {String} truncated title
	*/
	truncateTitle : function(title)
	{
		var MAX_LENGTH = 86;
		var truncated = title;
		if(title.length > MAX_LENGTH)
		{
			truncated = truncated.substring(0, MAX_LENGTH);
			truncated += "…";
		}
		return truncated;
	},

	/**
	 * Formats date as Monthname DD, YYYY
	 * @param {Date} date1 date to format
	 * @return {String} formatted date as String
	 */
	formatDate : function(date1)
	{
		var year = date1.getFullYear();
		var month = this.getDescriptions().months[date1.getMonth()];
		var day = (date1.getDate() < 10 ? '0' : '') + date1.getDate();
		return month + ' ' + day + ', ' + year;
	},

	/**
	 * Only to be used internally to add the citations to the list
	 *
	 * @param {AbstractReference} ref the reference to add
	 */
	addNewElement : function(ref)
	{
		var refbox = this.getRefBox();
		jQuery(refbox).append(this.makeRefBoxRow(ref, false));
	}

}, window.proveit);

/**
 * Static method.  Returns valid Cite reference types
 * @for CiteReference
 * @static
 * @return {Array} array of cite method types
 */
proveit.CiteReference.getTypes = function()
{
	return ["Literatur", "Internetquelle", "BibISBN", "BibDOI"];
};

if(!String.prototype.trim)
{
/**
 * Generic trim function, trims all leading and trailing whitespace.
	 * @for proveit
	 * @return {String} the trimmed string
 */
	String.prototype.trim = function() {
		return this.replace(/^\s+|\s+$/g, "");
	};
};

proveit.split._compliantExecNpcg = /()??/.exec("")[1] === undefined; // NPCG: nonparticipating capturing group
proveit.split._nativeSplit = String.prototype.split;

proveit.setup();

// var bibURL = ['Benutzer:Dietzel/Banksia', 'Benutzer:Dietzel/References', 'Venedig'];
// Local Variables:
// js2-basic-offset: 8
// End:
// <ref name="Test">{{Literatur | ISBN=3-8001-5531-1 | Autor=testautor | Titel=testtitel | Verlag=testverlager}}</ref>
// <ref name="Test2">{{Internetquelle | autor=testautor2 | titel=testtitel2 | url=testurl2}}</ref>
// <ref name="Test3">{{BibISBN | 3800141442 | Seite=102 | Kommentar=tester}}</ref>
// <ref>{{BibDOI | 10.1038/35057062 | Seite=102 | Kommentar=test}}</ref>
// <ref name="doitest">{{BibDOI | aaa.bbb/cccc | Seite=102 | Kommentar=doitest}}</ref>
// <ref name="Test4">testtesttest</ref>
// </nowiki>