User:Argenti Aertheri/RefConsolidate alt.js

var refcon = {

/**	 * This variable holds edit textbox text that is modified throughout the script *	 * @type {string} */	textBoxText: '',

/**	 * This array holds reference template groups in the order that they appear in article *	 * @type {array} */	templateGroups: [],

/**	 * This array holds reference templates in the order that they appear in article *	 * @type {array} */

refTemplates: [],

/**	 * This array holds article text parts that are between reference templates *	 * @type {array} */

textParts: [],

/**	 * Object for user selectable sort options *	 * @type {object} */

userOptions: {},

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

/**	 * Convenience method to get a RefCon message *	 * @param {string} message key without the "refcon-" prefix * @param {array} array of replacements * @return {string} message value */	getMessage: function ( key, param ) { return new mw.Message( mw.messages, 'refcon-' + key, param ).text; },

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

/**	 * Initialization. Sets up script execution link. If the link is clicked, calls main function *	 * @return {void} */	init: function {

$([ refcon.getOption( 'image-yes' ),			refcon.getOption( 'image-no' )		]).each( function {				$(' ')[0].src = this;			});

var linkname = refcon.getOption( 'linkname' ), linkhover = refcon.getOption( 'linkhover' );

// Add portlet link to the script if ( document.getElementById( 'ca-edit' ) ) { var url = mw.util.getUrl( mw.config.get ( 'wgPageName' ), { action: 'edit', RefCon: 'true' }); var portletlink = $( mw.util.addPortletLink ( 'p-cactions', url, linkname, 'ca-RefCon', linkhover, '',				document.getElementById( 'ca-move' ) ));			// If the portlet link is clicked while on edit page, run the function and do stuff, don't load new page if( typeof document.forms.editform !== 'undefined' ) { portletlink.on('click', function (e) {					e.preventDefault;					refcon.main;				}); }		}

// Only load when editing var action = mw.config.get( 'wgAction' );

if ( action === 'edit' || action === 'submit' ) { // Only if the portlet link was clicked if ( mw.util.getParamValue('RefCon') ) { // Only if there is wpTextbox1 on the page if ( document.getElementById('wpTextbox1') ) { refcon.main; }			}		}	},

/**	 * Main function. Calls specific subfunctions *	 * @return {void} */	main: function { // This is a container function that calls subfunctions and passes their return values to other subfunctions

// First, get indexes of reference templates in article, if there are any var indexes = refcon.parseIndexes, i;

if ( indexes.length > 0 ) {

var templateDataList = [], templatesString = '';

// Go through indexes array for ( i = 0; i < indexes.length; i++ ) { var refStartIndex = indexes[ i ]; var nextRefStartIndex = indexes[ i + 1 ] ? indexes[ i + 1 ] : refcon.textBoxText.length;

var templateData = refcon.getTemplateContent( refStartIndex, nextRefStartIndex, i );

// don't do anything with the reference template if it is not closed if ( templateData['refEndIndex'] !== null ) { templatesString += templateData['templateContent']; templateDataList.push( templateData ); }			}

// Use mw.API to get reflist templates parameter pairs var paramPairsList = refcon.getTemplateParams( templatesString );

for ( i = 0; i < templateDataList.length; i++ ) { var paramsPair = typeof paramPairsList[ i ] !== 'undefined' ? paramPairsList[ i ] : {}; var refTemplate = refcon.getTemplateObject( templateDataList[ i ], paramsPair ); refcon.parseTemplateRefs( refTemplate ); }

// Go through refTemplates array (refTemplates determine the boundaries) and create an array of TextPart objects // These are text parts of an article that are located between reference templates

refcon.storeTextParts;

// Process references in reference templates, remove duplicate keys and values

for ( i = 0; i < refcon.refTemplates.length; i++ ) { refcon.refTemplates[ i ].processDuplicates; }

// Find and store references and citations in each textPart object

for ( i = 0; i < refcon.textParts.length; i++ ) { refcon.parseTextParts( refcon.textParts[ i ] ); }

// Compare references to the ones in reference template(s). Add text part references into reference template. // Create citations to references.

for ( i = 0; i < refcon.textParts.length; i++ ) { refcon.processTextPartRefs( refcon.textParts[ i ] ); }

// Link textPart citations to references

for ( i = 0; i < refcon.textParts.length; i++ ) { refcon.linkCitations( refcon.textParts[ i ] ); }

// Show form with references refcon.showForm;

} else { refcon.showDifferenceView; }	},

/**	 * Continue processing after form. Commit changes and show the differences view *	 * @return {void} */	commit: function {

// Recreate indexes (because names could have been changed in the form) for ( i = 0; i < refcon.refTemplates.length; i++ ) { refcon.refTemplates[ i ].reIndex; }

// Replace references inside text part strings with citations for ( i = 0; i < refcon.textParts.length; i++ ) { refcon.replaceTextPartRefs( refcon.textParts[ i ] ); }			// Build reference templates for ( i = 0; i < refcon.refTemplates.length; i++ ) { refcon.buildRefTemplates( refcon.refTemplates[ i ] ); }			var newText = refcon.writeTextBoxText; var textbox = refcon.getTextbox; var oldText = textbox.val;

if ( oldText != newText ) { // Update textbox textbox.val( newText ); // Add summary refcon.addSummary; }			refcon.showDifferenceView; },

/**	 * Show form with references *	 * @return {void} */	showForm: function {

// Define basic elements var gui = $( ' ' ).attr( 'id', 'refcon' ), container = $( ' ' ).attr( 'id', 'refcon-container' ), header = $( ' ' ).attr( 'id', 'refcon-header' ), title = $( ' ' ).attr( 'id', 'refcon-title' ).text( refcon.getOption( 'gadgetname' ) ), closer = $( ' ' ).attr( 'id', 'refcon-close' ).addClass( 'refcon-abort' ).html( '&times;' ).attr('title', refcon.getMessage( 'closetitle' )), content = $( ' ' ).attr( 'id', 'refcon-content' ), form = $( ' ' ).attr( 'id', 'refcon-form' ), table = $( ' '); table.append(''						+ refcon.getMessage( 'buttonabort' ) + ' '						+ refcon.getMessage( 'buttoncontinue' ) + ' ');

container.css( 'display', 'block' );

// Bind events

// Close window when user clicks on 'x'		$( '.refcon-abort' ).on( 'click', function {			gui.remove;			refcon.cleanUp;		});

// Activate 'Continue' button when user changes some reference name $( '#refcon-table .refcon-refname' ).on( 'input', function {			$( '#refcon-continue-button' ).removeAttr( 'disabled' );		});

// Validate reference names when user clicks 'Continue'. If there are errors, disable 'Continue' button $( '#refcon-continue-button' ).on( 'click', function( event ) {			refcon.validateInput;			if ( table.find('[data-invalid]').length === 0 ) {				refcon.afterScreenSave;			} else {				$( '#refcon-continue-button' ).attr('disabled', true);			}		});

// Sort table if user clicks on sortable table header $( ".refcon-sortable" ).on('click', function {			refcon.sortTable( $(this) );		});

$( "#refcon-table .refcon-refplacement" ).on( 'change', function {			switch( $( this ).val ) {				case 'template':					$( '#refcon-table .refcon-refplace' ).prop('checked', true);					break;				case 'text':					$( '#refcon-table .refcon-refplace' ).prop('checked', false);					break;				case 'usage':					refcon.selectReferencesByUsage;					break;			}		}); // When user clicks on uses input field, select the third radio checkbox $( "#refcon-table-options-uses" ).on( 'focus', function {			$('#refcon-table-options input:radio[name=reference-place]:nth(2)').trigger( "click" );		});

$( "#refcon-table-options-uses" ).on( 'input', function {			refcon.selectReferencesByUsage;		});

},

sortTable: function ( columnHeader ) { var order = $( columnHeader ).hasClass('refcon-asc') ? 'refcon-desc' : 'refcon-asc'; $('.refcon-sortable').removeClass('refcon-asc').removeClass('refcon-desc'); $( columnHeader ).addClass( order );

var colIndex = $( columnHeader ).prevAll.length; var tbod = $( columnHeader ).closest("table").find("tbody");

var i;		for ( i = 0; i < refcon.templateGroups.length; i++ ) { var rows = $( tbod ).children("tr[template='" + i + "']"); rows.sort( function( a,b ) {				var A = $(a).children("td").eq(colIndex).has("input").length ? $(a).children("td").eq(colIndex).children("input").val : $(a).children("td").eq(colIndex).text;				var B = $(b).children("td").eq(colIndex).has("input").length ? $(b).children("td").eq(colIndex).children("input").val : $(b).children("td").eq(colIndex).text;

if ( colIndex === 1 || colIndex === 4 ) { A = Number(A); B = Number(B); return order === 'refcon-asc' ? A - B : B - A;				} else { if ( order === 'refcon-asc' ) { return A.localeCompare( B, mw.config.get( 'wgContentLanguage' ) ); } else { return B.localeCompare( A, mw.config.get( 'wgContentLanguage' ) ); }				}			});			$( rows ).each( function( index ) { $( this ).children("td").removeClass('refcon-even').removeClass('refcon-odd'); $( this ).children("td").addClass( index % 2 == 0 ? 'refcon-odd' : 'refcon-even' ); });

$( columnHeader ).closest("table").find("tbody").children("tr[template='" + i + "']").remove; $( columnHeader ).closest("table").find("#templateheader"+i).after( rows ); }

// Activate 'Continue' button when user changes some reference name $( '#refcon-table .refcon-refname' ).on( 'input', function {			$( '#refcon-continue-button' ).removeAttr( 'disabled' );		}); },

selectReferencesByUsage: function { var usage = $( "#refcon-table-options-uses" ).val; if ( usage.length > 0 ) { var regex = /[^0-9]+/; if ( !usage.match( regex ) ) { usage = Number( usage ); $( '#refcon-table .refcon-refplace' ).each(function {					if ( $(this).attr('value') >= usage )						$(this).prop('checked', true);					else						$(this).prop('checked', false);				}); }		}	},

validateInput: function { var names = {}, duplicateNames = {}, i;

for ( i = 0; i < refcon.templateGroups.length; i++ ) { names[ i ] = {}; duplicateNames[ i ] = {}; }

$( '#refcon-table .refcon-refname' ).each(function {			if ( $(this).val in names[ $(this).attr('template_id') ] ) {				duplicateNames[ $(this).attr('template_id') ][ $(this).val ] = 1;			} else {				names[ $(this).attr('template_id') ][ $(this).val ] = 1;			}		});

$( '#refcon-table .refcon-refname' ).each(function {			if ( $(this).val in duplicateNames[ $(this).attr('template_id') ] ) {				refcon.markFieldAsInvalid( $(this) );			} else if ( $(this).val === '' ) {				refcon.markFieldAsInvalid( $(this) );			} else if ( $(this).val.match(/[<>"]/) !== null ) {				refcon.markFieldAsInvalid( $(this) );			} else {				refcon.markFieldAsValid( $(this) );			}		});	},

markFieldAsValid: function ( inputField ) { $( inputField ).removeAttr( 'data-invalid' ); $( inputField ).closest( 'tr' ).find( 'img' ).attr( 'src', refcon.getOption( 'image-yes' )); },

markFieldAsInvalid: function ( inputField ) { $( inputField ).attr( 'data-invalid', 1 ); $( inputField ).closest( 'tr' ).find( 'img' ).attr( 'src', refcon.getOption( 'image-no' )); },

/**	 * Process form after the Save button was pressed *	 * @return {void} */

afterScreenSave: function { $( '#refcon-table tr[template]' ).each(function {			var refName = $( this ).find( '.refcon-refname' );			var name = refName.val;			var templateId = refName.attr( 'template_id' );			var refId = refName.attr( 'name' );			// change reference names to the ones from the form, in case some name was changed			refcon.refTemplates[ templateId ].references[ refId ].changeName( name );			// save reference location preference from the form into reference object			var refPlace = $( this ).find( '.refcon-refplace' );			refcon.refTemplates[ templateId ].references[ refId ].inRefTemplate = refPlace.prop('checked') ? true : false;		});

// If user has checked "save sorted" checkbox, save sorting preferences if ( $('#refcon-savesorted').prop('checked') ) { var sortOptions = {}; if ( $( '.refcon-asc' ).prevAll.length ) { sortOptions['column'] = $( '.refcon-asc' ).prevAll.length; sortOptions['order'] = 'asc'; } else if ( $( '.refcon-desc' ).prevAll.length ) { sortOptions['column'] = $( '.refcon-desc' ).prevAll.length; sortOptions['order'] = 'desc'; }			refcon.userOptions['sort'] = sortOptions; }		// If user has checked "keep names" checkbox, save name keeping preferences if ( $('#refcon-keepnames').prop('checked') ) refcon.userOptions['keepnames'] = true; else refcon.userOptions['keepnames'] = false;

refcon.commit; },

/**	 * Parse article text and find all reference templates indexes *	 * @return {array} Start indexes of all reference templates */

parseIndexes: function {

var refTemplateNames = refcon.getOption( 'reftemplatenames' );

var wikitext = refcon.getTextbox.val, i, name, re, refTemplateIndexes = [];

// Make all appearances of the reference templates in article text uniform if ( Array.isArray( refTemplateNames ) ) { var refTemplateName = refTemplateNames[0];

for ( i = 0; i < refTemplateNames.length; i++ ) { name = refTemplateNames[ i ]; re = new RegExp( '{{\s*' + name, 'gi' ); wikitext = wikitext.replace( re, '{{' + refTemplateName ); }

// Find all indexes of the reference template in the article and put them into array // Index is the place in article text where references template starts var pos = wikitext.indexOf( '{{' + refTemplateName );

if (pos !== -1) refTemplateIndexes.push( pos );

while (pos !== -1) { pos = wikitext.indexOf( '{{' + refTemplateName, pos + 1 ); if (pos !== -1) refTemplateIndexes.push( pos ); }		} else { // call some error handling function and halt }

// Set the refcon variable with modified wikitext refcon.textBoxText = wikitext;

return ( refTemplateIndexes );

},

/**	 * Get reference template's content and end index *	 * @param {integer} reference template's index in article text * @param {integer} next reference template's index in article text *	 * @return {object} reference template's content string, start and end indexes */

getTemplateContent: function (templateIndex, nextTemplateIndex) {

var	textPart = refcon.textBoxText.substring(templateIndex, nextTemplateIndex); var i, depth = 1, prevChar = , templateEndIndex = 0, templateAbsEndIndex = null, templateContent = ;

// Go through the textPart and find the template's end code '}}' // @todo: could use ProveIt's alternative code here for ( i = 2; i < nextTemplateIndex; i++ ) { if (textPart.charAt(i) === "{" && prevChar === "{") ++depth; if (textPart.charAt(i) === "}" && prevChar === "}") --depth; if (depth === 0) { templateEndIndex = i + 1; break; }			prevChar = textPart.charAt(i); }

// If templateEndIndex is 0, reference template's ending '}}' is missing in the textPart

if ( templateEndIndex > 0 ) { templateContent = textPart.substring(0, templateEndIndex); templateAbsEndIndex = templateIndex + templateEndIndex; }

return ({			'templateContent': templateContent,			'refStartIndex' : templateIndex,			'refEndIndex': templateAbsEndIndex		});

},

/**	 * Get all reference templates' name and value pairs using a single mw.Api call *	 * @param {string} String that contains all article's reflist templates *	 * @return {array} List of reference template objects with parameter names and values */

getTemplateParams: function ( templatesString ) {

var paramPairsList = []; var refTemplateNames = refcon.getOption( 'reftemplatenames' );

if ( Array.isArray( refTemplateNames ) ) { var mainRefTemplateName = refTemplateNames[0]; } else { // call some error handling function and halt }

// We will do a single API call to get all reflist templates parameter pairs new mw.Api.post({			'action': 'expandtemplates',			'text': templatesString,			'prop': 'parsetree'		}, { async: false }).done( function ( data ) {			var parsetree = data.expandtemplates.parsetree;			var result = xmlToJSON.parseString( parsetree );			var i, templateRoot = result.root[0].template;

//@todo: could rewrite the code to use JSON.parse for ( i = 0; i < templateRoot.length; i++ ) { if ( templateRoot[ i ].title[0]['_text'] === mainRefTemplateName ) { var paramPairs = {}; var part = templateRoot[ i ].part; if ( typeof part !== 'undefined' ) { var j, name, value, ext; for ( j = 0; j < part.length; j++ ) { if ( typeof part[ j ].equals !== 'undefined' ) { name = part[ j ].name[0]['_text']; } else { name = part[ j ].name[0]['_attr']['index']['_value']; }							name = typeof name === 'string' ? name.trim : name; // By checking 'ext' first, '_text' second, // if the parameter value is a list of references that contains some text between the reference tags, the text is lost. // But at least we get the references and not the text instead if ( typeof part[ j ].value[0]['ext'] !== 'undefined' ) { ext = part[ j ].value[0]['ext']; if ( Array.isArray( ext ) ) { var k, attr, inner; value = []; for ( k = 0; k < ext.length; k++ ) { if ( typeof ext[ k ]['name'][0]['_text'] !== 'undefined' && ext[ k ]['name'][0]['_text'].toLowerCase === 'ref'											&& typeof ext[ k ]['close'][0]['_text'] !== 'undefined' && ext[ k ]['close'][0]['_text'].toLowerCase === ' ' ) { if ( typeof ext[ k ]['attr'][0]['_text'] !== 'undefined' && typeof ext[ k ]['inner'][0]['_text'] !== 'undefined' ) { value.push({													'attr': ext[ k ]['attr'][0]['_text'],													'inner': ext[ k ]['inner'][0]['_text']												}); }										}									}								}							} else if ( typeof part[ j ].value[0]['_text'] !== 'undefined' ) { value = part[ j ].value[0]['_text']; }							value = typeof value === 'string' ? value.trim : value; paramPairs[ name ] = value; }						paramPairsList.push( paramPairs ); }				}			}		});		return ( paramPairsList );	 },

/**	 * Get reference template object from paramPairs and templateData objects *	 * @param {object} reference template data object with indexes and template content * @param {object} reference template parameter pairs object with param names and values *	 * @return {object} reference template object */

getTemplateObject: function ( templateData, paramPairs ) {

var name, i, groupName; var refGroupNames = refcon.getOption( 'reftemplategroupnames' );

// Go through paramPairs and see if there is a configuration defined group name in parameter names. Get it's value if ( Array.isArray( refGroupNames ) ) { if ( typeof paramPairs === 'object' ) { for ( i = 0; i < refGroupNames.length; i++ ) { var name = refGroupNames[ i ]; if ( typeof paramPairs[ name ] !== 'undefined' ) { groupName = paramPairs[ name ]; break; }				}			}		} else { // call some error handling function and halt }

if ( typeof groupName === 'undefined' ) { groupName = ''; }

refcon.templateGroups.push( groupName );

// Build basic reference template var refTemplate = new refcon.RefTemplate({			'group': groupName,			'string': templateData[ 'templateContent' ],			'start': templateData[ 'refStartIndex' ],			'end': templateData[ 'refEndIndex' ],			'params': paramPairs		});

return ( refTemplate ); },

/**	 * Parse references in reference template's refs field (using mw.Api) *	 * @param {object} refTemplate object * 	 * @return {void} */

parseTemplateRefs: function ( refTemplate ) { var refsNames = refcon.getOption( 'reftemplaterefsnames' ); var refsArray, refsName, i;

if ( Array.isArray( refsNames ) ) { if ( typeof refTemplate.params === 'object' ) { for ( i = 0; i < refsNames.length; i++ ) { refsName = refsNames[ i ]; if ( typeof refTemplate.params[ refsName ] !== 'undefined' ) { refsArray = refTemplate.params[ refsName ]; break; }				}			}		} else { // call some error handling function and halt }

// Look for references inside the reference template's refs parameter

if ( typeof refsArray !== 'undefined' && refsArray.length > 0) { for ( i = 0; i < refsArray.length; i++ ) {

// Turn all matches into reference objects reference = refcon.parseReference( [ '', refsArray[i].attr, refsArray[i].inner ], 'reference' );

// Only add references that have name if ( reference['name'].length > 0 ) { refTemplate.addRef( reference ); }			}		}		refcon.refTemplates.push( refTemplate ); },

/**	 * Make a reference object out of a reference string *	 * @param {array} match array produced by regexp * @param {string} type. can be either "reference" or "citation" *	 * @return {object} returns either reference object or citation object based on type */

parseReference: function ( data, type ) { var params = {}, referenceName, referenceGroup, referenceString = data[0], refParamString = data[1], referenceContent = data[2], referenceIndex = data.index;

if (typeof refParamString !== 'undefined') { refParamString = refParamString.trim;

if (refParamString.length > 0) { //Examples of strings to extract name and group from //group="arvuti" name="refname1" //name="refname2" group="arvuti str" //group="arvuti" //name="refname1 blah"

var re = /(?:(name|group)\s*=\s*(?:"([^"]+)"|'([^']+)'|([^ ]+)))(?:\s+(name|group)\s*=\s*(?:"([^"]+)"|'([^']+)'|([^ ]+)))?/i;

var match = refParamString.match(re);

try { if ( typeof match[1] !== 'undefined' && ( typeof match[2] !== 'undefined' || typeof match[3] !== 'undefined' || typeof match[4] !== 'undefined' ) ) { if ( typeof match[2] !== 'undefined' ) { params[ match[1] ] = match[2]; } else if ( typeof match[3] !== 'undefined' ) { params[ match[1] ] = match[3]; } else { params[ match[1] ] = match[4]; }					}

if ( typeof match[5] !== 'undefined' && ( typeof match[6] !== 'undefined' || typeof match[7] !== 'undefined' || typeof match[8] !== 'undefined' ) ) { if ( typeof match[6] !== 'undefined' ) { params[ match[5] ] = match[6]; } else if ( typeof match[7] !== 'undefined' ) { params[ match[5] ] = match[7]; } else { params[ match[5] ] = match[8]; }					}				} catch ( e ) { refcon.throwReferenceError( referenceString, refcon.getMessage( 'parsereferror', [ referenceString ] ), e ); }

referenceName = params['name'] ? params['name'] : ''; referenceGroup = params['group'] ? params['group'] : ''; }		}

if ( typeof referenceGroup === 'undefined' ) referenceGroup = '';

if ( typeof referenceName === 'undefined' ) referenceName = '';

var found = referenceName.match(/[<>"]/);		if ( found !== null ) {			refcon.throwReferenceError( referenceString, refcon.getMessage( 'parserefforbidden', [ found[0], referenceString ] ));		}

// Clean reference name and content of newlines, double spaces, leading or trailing whitespace and more

referenceName = refcon.cleanString( referenceName, 'name' );

if ( typeof referenceContent !== 'undefined' ) referenceContent = refcon.cleanString( referenceContent, 'content' );

if ( type === 'reference' ) { // Build the basic reference var reference = new refcon.Reference({				'group': referenceGroup,				'name': referenceName,				'content': referenceContent,				'index': referenceIndex,				'string': referenceString			}); } else if ( type === 'citation' ) { // Build the basic citation var reference = new refcon.Citation({				'group': referenceGroup,				'name': referenceName,				'index': referenceIndex,				'string': referenceString			}); }		return reference; },

throwReferenceError: function ( referenceString, message, error ) { var found = refcon.getTextbox.val.match( refcon.escapeRegExp( referenceString ) ); refcon.highlight( found.index, referenceString ); window.alert( message ); refcon.cleanUp; throw new Error( error ); },

/**	 * Clean reference name and content of newlines, double spaces, leading or trailing whitespace, etc *	 * @param {string} reference name or reference content string * @param (string) whether the string is name or content *	 * @return {string} cleaned reference name and content */

cleanString: function ( str, type ) {

// get rid of newlines and trailing/leading space str = str.replace(/(\r\n|\n|\r)/gm,' ').trim;

// get rid of double whitespace inside string str = str.replace(/\s\s+/g, ' ');

// if the str is content, get rid of extra space before template closing / after template opening if ( type === 'content') { str = str.replace(/ }}/g, '}}'); str = str.replace(/{{ /g, '{{'); }

return (str); },

escapeRegExp: function ( str ) { return str.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&"); },

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

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

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

/**	 * Turn all article text parts – parts that are between reference templates – into objects and save into array *	 * @return {void} */

storeTextParts: function { var i, text, refEnd, from, to, textPart;

for ( i = 0; i < refcon.refTemplates.length; i++ ) {

from = refEnd ? refEnd : 0;

to = refcon.refTemplates[ i ]['start']; refEnd = refcon.refTemplates[ i ]['end'];

if ( to === 0 ) { continue; }

text = refcon.textBoxText.substring( from, to );

// Textpart's references can only be in templates that come after the textpart in article text var j, groupName, groupNames = {};

for ( j = i; j < refcon.refTemplates.length; j++ ) { groupName = refcon.templateGroups[ j ]; // Only add the first instance of template group if ( typeof groupNames[ groupName ] === 'undefined' ) { groupNames[ groupName ] = j;				} }

// @todo: check what happens if a reference template follows another reference template without any space. // Does textpart still get correct inTemplate sequence?

// Create new TextPart object and store it

textPart = new refcon.TextPart({				'start': from,				'end': to,				'string': text,				'inTemplates': groupNames			});

refcon.textParts.push( textPart ); }

// Add the last text part after the last reference template if ( typeof refEnd === 'number' && refEnd > 0 ) { if ( refcon.textBoxText.length > refEnd ) {

text = refcon.textBoxText.substring( refEnd, refcon.textBoxText.length );

textPart = new refcon.TextPart({					'start': refEnd,					'end': refcon.textBoxText.length,					'string': text				});

refcon.textParts.push( textPart ); }		}	},

/**	 * Find all references and citations in a TextPart object and store them in the object. *	 * @param {object} TextPart object */

parseTextParts: function ( textPart ) {

if ( typeof textPart.string !== 'undefined' && textPart.string.length > 0 ) {

// Look for all citations // Citations come in two forms: // 1. 			// 2. 			// Ref label can have optional group parameter: // or 			// Group and name parameter values can be between '' or "", or bare (if value has no whitespaces)

var citations = [], citationsRegExp = /]+)(?:\/\s*>|><\/ref>)/ig, match, citation;

while ( ( match = citationsRegExp.exec( textPart.string ) ) ) {

// Turn all the matches into citation objects citation = refcon.parseReference( match, 'citation' );

if ( typeof citation === 'object' && typeof citation.name !== 'undefined' ) { citations.push( citation ); }			}

textPart.citations = citations;

// Look for all references

var references = [], referencesRegExp = /([\s\S]*?)<\/ref>/ig, match, reference;

while ( ( match = referencesRegExp.exec( textPart.string ) ) ) { // Avoid further processing of citations like if ( match[2] === '' ) { continue; }

// Turn all the matches into reference objects reference = refcon.parseReference( match, 'reference' );

references.push( reference ); }

textPart.references = references; }	},

/**	 * Compare references in a TextPart object to the references in reference template (if there are any). Add references into * reference template. Update indexes. For each reference create citation object and link it with reflist template reference. *	 * @param {object} TextPart object */	processTextPartRefs: function ( textPart ) { var i, reference, refTemplate, templateRef, createdCitations = [];

for ( i = 0; i < textPart.references.length; i++ ) { reference = textPart.references[ i ];

refTemplate = refcon.refTemplates[ textPart.inTemplates[ reference.group ] ];

// First add named references, because otherwise we could create new records (and names) // for already existing text part defined references if ( reference.content.length > 0 && reference.name.length > 0 ) {

// First check if this a complete duplicate reference (name and value are the same) templateRef = refcon.getRefByIndex( refTemplate, 'keyValues', reference.name + '_' + reference.content );

if ( typeof templateRef === 'object' ) { if ( templateRef.name === reference.name && templateRef.content === reference.content ) { // found exact duplicate var citation = new refcon.Citation({							'group': reference.group,							'name': reference.name,							'index': reference.index,							'string': reference.string						}); templateRef.citations.push( citation ); createdCitations.push( citation ); continue; }				}				// Check if the reference has the same name but different content than template reference templateRef = refcon.getRefByIndex( refTemplate, 'keys', reference.name );

if ( typeof templateRef === 'object' ) { if ( templateRef.name === reference.name && templateRef.content !== reference.content ) { // found reference with the same name but different content

// add reference content to template references under new name var newName = refTemplate.getNewName( reference.name ); var newRef = new refcon.Reference({							'group': reference.group,							'name': newName,							'content': reference.content,							'inRefTemplate': false						}); var citation = new refcon.Citation({							'group': reference.group,							'name': newName,							'index': reference.index,							'string': reference.string						}); newRef.citations.push( citation ); refTemplate.addRef( newRef ); createdCitations.push( citation ); // add names into replacements object, so we can replace all citation names that use the old name refTemplate.replacements[ reference.name ] = newName; continue; }				}				// Check if the reference has the same content but different name than template reference templateRef = refcon.getRefByIndex( refTemplate, 'values', reference.content );

if ( typeof templateRef === 'object' ) { if ( templateRef.content === reference.content && templateRef.name !== reference.name ) { // Found reference with the same content but different name. // Drop reference name, use reflist template reference name as citation name var citation = new refcon.Citation({							'group': reference.group,							'name': templateRef.name,							'index': reference.index,							'string': reference.string						}); templateRef.citations.push( citation ); createdCitations.push( citation ); // add names into replacements object, so we can replace all citation names that use the old name refTemplate.replacements[ reference.name ] = templateRef.name; continue; }				}				// If we get here, it means we've got a named reference that has not yet been described in reflist template. // Add the reference to reflist references var newRef = new refcon.Reference({					'group': reference.group,					'name': reference.name,					'content': reference.content,					'inRefTemplate': false				}); var citation = new refcon.Citation({					'group': reference.group,					'name': reference.name,					'index': reference.index,					'string': reference.string				}); newRef.citations.push( citation ); refTemplate.addRef( newRef ); createdCitations.push( citation ); }		}		// Now we go through unnamed references for ( i = 0; i < textPart.references.length; i++ ) { reference = textPart.references[ i ];

refTemplate = refcon.refTemplates[ textPart.inTemplates[ reference.group ] ];

if ( reference.content.length > 0 && reference.name.length === 0 ) { templateRef = refcon.getRefByIndex( refTemplate, 'values', reference.content ); if ( typeof templateRef === 'object' ) { if ( templateRef.content === reference.content ) { // found reference with the same content var citation = new refcon.Citation({							'group': reference.group,							'name': templateRef.name,							'index': reference.index,							'string': reference.string						}); templateRef.citations.push( citation ); createdCitations.push( citation ); continue; }				}				// If we get here, we have a completely new unnamed reference // add the reference to template references var newName = refTemplate.getNewName; var newRef = new refcon.Reference({					'group': reference.group,					'name': newName,					'content': reference.content,					'inRefTemplate': false				}); var citation = new refcon.Citation({					'group': reference.group,					'name': newName,					'index': reference.index,					'string': reference.string				}); newRef.citations.push( citation ); refTemplate.addRef( newRef ); createdCitations.push( citation ); }		}		textPart.linkedCitations = createdCitations; },

/**	 * Link citations to their reflist template references *	 * @param {object} TextPart object *	 * @return {void} */	linkCitations: function ( textPart ) {

var citation, refTemplate, replaceName, templateRef, i;

for ( i = 0; i < textPart.citations.length; i++ ) { citation = textPart.citations[ i ];

refTemplate = refcon.refTemplates[ textPart.inTemplates[ citation.group ] ];

if ( citation.name.length > 0 ) {

// If there is replacement name in replacements object, replace the citation name replaceName = refTemplate.replacements[ citation.name ];

if ( typeof replaceName !== 'undefined' ) { citation.name = replaceName; }

// For each citation try to find its reference templateRef = refcon.getRefByIndex( refTemplate, 'keys', citation.name ); if ( typeof templateRef === 'object' ) { if ( templateRef.name === citation.name ) { templateRef.citations.push( citation ); textPart.linkedCitations.push( citation ); }				}			}		}	},

/**	 * Replace all references in TextPart object string with citations. Also replace citation names that were changed in previous steps *	 * @param {object} TextPart object *	 * @return {void} */	replaceTextPartRefs: function ( textPart ) { var i, citation, refTemplate, templateRef; for ( i = 0; i < textPart.linkedCitations.length; i++ ) { citation = textPart.linkedCitations[ i ]; if ( citation.name.length > 0 ) { refTemplate = refcon.refTemplates[ textPart.inTemplates[ citation.group ] ]; templateRef = refcon.getRefByIndex( refTemplate, 'keys', citation.name );

// For the references that are marked as "in reference list template" replace all instances with citation if ( templateRef.inRefTemplate === true ) { textPart.string = textPart.string.replace( citation.string, citation.toString ); // For the references that are marked as "in article text"... } else { // if the reference has just one use, output the reference string w/o name (unless user options "keep names" was selected) if ( templateRef.citations.length == 1 ) { textPart.string = textPart.string.replace( citation.string, templateRef.toStringText( refcon.userOptions.keepnames ) ); // if the reference has more uses... } else { // if the reference has not been output yet, output named reference if ( templateRef.wasPrinted === false ) { textPart.string = textPart.string.replace( citation.string, templateRef.toStringText( true ) ); // mark reference as printed templateRef.wasPrinted = true; // if the reference has already been printed, output citation } else { textPart.string = textPart.string.replace( citation.string, citation.toString ); }					}				}			}		}	},

/**	 * Build reference templates *	 * @param {object} RefTemplate object *	 * @return {void} */	buildRefTemplates: function ( refTemplate ) { var i, reference, referencesString = '', refsAdded = false;

// sort references if user has checked the checkbox if ( typeof refcon.userOptions.sort === 'object' && Object.keys( refcon.userOptions.sort ).length > 0 ) { refcon.sortReferences ( refTemplate ); }

// turn reference data into reflist parameter value string for ( i = 0; i < refTemplate.references.length; i++ ) { reference = refTemplate.references[ i ]; if ( typeof reference === 'object' && reference.inRefTemplate === true ) { referencesString += reference.toString + "\n"; }		}		// Cut the last newline referencesString = referencesString.substr( 0, referencesString.length - 1 );

var refTemplateNames = refcon.getOption( 'reftemplatenames' );

if ( Array.isArray( refTemplateNames ) ) { var refTemplateName = refTemplateNames[0]; } else { // call some error handling function and halt }

var refsNames = refcon.getOption( 'reftemplaterefsnames' );

if ( Array.isArray( refsNames ) ) { var refsName = refsNames[0]; } else { // call some error handling function and halt }

var templateString = '{{' + refTemplateName;

// Build references template string if ( Object.keys( refTemplate.params ).length > 0 ) { // Go through params object for ( var name in refTemplate.params ) { var value = refTemplate.params[ name ]; // If param name matches with config name for reference list template refs param... if ( refsNames.indexOf( name ) > -1 ) { // ... only if there are references in reflist template if ( referencesString.length > 0 ) { // ... add refstring to reflist params templateString += '|' + refsName + '=' + "\n" + referencesString; refsAdded = true; }					continue; } else if ( typeof value !== 'string' && typeof value !== 'number' ) { // If value is anything other than string or number, skip it. // Value is array if, for example, references are incorrectly defined inside unnamed parameter. continue; }				templateString += '|' + name + '=' + value; }		}		// if the reflist template was without any parameters, add parameter and references here if ( refsAdded === false && referencesString.length > 0 ) { templateString += '|' + refsName + "=\n" + referencesString; }		if ( referencesString.length > 0 ) templateString += "\n}}"; else templateString += "}}";

refTemplate.string = templateString; },

/**	 * Sort references inside reflist template according to user preferences *	 * @param {object} Reftemplate object *	 * @return {void} */	sortReferences: function ( refTemplate ) {

if ( refcon.userOptions.sort.column === 1 ) { refTemplate.references = refcon.userOptions.sort.order === 'desc' ? refTemplate.references.reverse : refTemplate.references; } else { refTemplate.references.sort( function( a,b ) {				// order by reference name				if ( refcon.userOptions.sort.column === 2 ) {					return refcon.userOptions.sort.order === 'asc' ? a.name.localeCompare( b.name, mw.config.get( 'wgContentLanguage' ) ) : b.name.localeCompare( a.name, mw.config.get( 'wgContentLanguage' ) );				// order by reference content				} else if ( refcon.userOptions.sort.column === 3 ) {					return refcon.userOptions.sort.order === 'asc' ? a.content.localeCompare( b.content, mw.config.get( 'wgContentLanguage' ) ) : b.content.localeCompare( a.content, mw.config.get( 'wgContentLanguage' ) );				// order by citations count				} else if ( refcon.userOptions.sort.column === 4 ) {					return refcon.userOptions.sort.order === 'asc' ? a.citations.length - b.citations.length : b.citations.length - a.citations.length;				}			}); }	},

/**	 * Verify if configuration option should be used. Return true or false * @param {string} Refcon option as returned by refcon.getOption method

* @param {string} User configuration variable content *	 * @return {boolean} True of false */	useConfigOption: function ( configOptionValue, userConfigOptionName ) { var result = false; switch ( configOptionValue ) { case 'yes': result = true; break; case 'no': result = false; break; case 'user': if ( typeof refConsolidateConfig === 'object' && typeof refConsolidateConfig[ userConfigOptionName ] !== 'undefined' && refConsolidateConfig[ userConfigOptionName ] === true ) { result = true; }			break; default: result = false; }		return ( result ); },

/**	 * Write text parts and reference templates into textbox variable *	 * @return {string} String that contains article text */

writeTextBoxText: function {

var textBoxString = '';

for ( i = 0; i < refcon.textParts.length; i++ ) { textPart = refcon.textParts[ i ];

textBoxString += textPart.string;

if ( typeof refcon.refTemplates[ i ] === 'object' ) { textBoxString += refcon.refTemplates[ i ].string; }		}

return ( textBoxString ); },

/**	 * Index into reference template template objects and return template object *	 * @param {object} reference template object * @param {string} index name * @param {integer} key to index into *	 * @return {object} reference template object */	getRefByIndex: function ( refTemplate, dictname, key ) { var templateRef; var refDict = refTemplate[ dictname ];

if ( key in refDict && Array.isArray( refDict[ key ] ) ) { var refKey = refDict[ key ][0]; var templateRef = refTemplate.getRef( refKey ); }

return ( templateRef ); },

/**	 * Add the RefCon edit summary *	 * @return {void} */	addSummary: function { var currentSummary = $( '#wpSummary' ).val; var	refconSummary = refcon.getOption( 'summary' ); var summarySeparator = refcon.getOption( 'summaryseparator' );

if ( !refconSummary ) { return; // No summary defined }		if ( currentSummary.indexOf( refconSummary ) > -1 ) { return; // Don't add it twice }		$( '#wpSummary' ).val( currentSummary ? currentSummary + summarySeparator + refconSummary : refconSummary ); },

/**	 * Set minor edit checkbox and click View Differences button *	 * @return {void} */	showDifferenceView: function { document.forms.editform.wpMinoredit.checked = true; document.forms.editform.wpDiff.click; },

/**	 * Produces random string with a given length *	 * @param {integer} string length * @param {string} charset (optional) *	 * @return {string} random string */

randomString: function ( len, charSet ) { charSet = charSet || '0123456789'; var randomString = ''; for ( var i = 0; i < len; i++ ) { var randomPoz = Math.floor( Math.random * charSet.length ); randomString += charSet.substring( randomPoz, randomPoz+1 ); }		return randomString; },

/**	 * Empty refcon arrays before script exit *	 * @return {void} */	cleanUp: function { refcon.refTemplates = []; refcon.templateGroups = []; refcon.textParts = []; refcon.textBoxText = []; },

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

/**		 * Article text start index */		this.start = typeof data.start === 'number' ? data.start : null;

/**		 * Article text end index */		this.end = typeof data.end === 'number' ? data.end : null;

/**		 * Article text content string */		this.string = data.string ? data.string : '';

/**		 * Array that has indexes of reference templates that apply to this text part */		this.inTemplates = data.inTemplates ? data.inTemplates : {};

/**		 * Temporary holding array for reference objects */		this.references = [];

/**		 * Temporary holding array for citation objects */		this.citations = [];

/**		 * Array that hold citation objects that are linked to reflist template references */		this.linkedCitations = []; },

/**	 * Citation class *	 * @param {object} data for constructing the object */

Citation: function (data) {

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

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

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

/**		 * Citation wikitext *		 * Example: */		this.string = data.string ? data.string : '';

/**		 * Convert this citation to wikitext */		this.toString = function { var useTemplateR = false; // check if we should use template for shorter citation format useTemplateR = refcon.useConfigOption( refcon.getOption( 'usetemplateR' ), 'usetemplateR' );

var startString = useTemplateR ? '' : ' />';

return ( startString + ( this.group ? groupString : '' ) + ( this.name ? nameString : '' ) + endString ); };	},

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

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

/**		 * Reference content (without the ';			return string;		};

/**		 * Convert this reference to wikitext (in article text) */		this.toStringText = function ( named ) { var string = '' + this.content + ' ';

return string; };

/**		 * Change reference's name and it's citations' names */		this.changeName = function ( newName ) { this.name = newName; var i;			for ( i = 0; i < this.citations.length; i++ ) { this.citations[ i ].name = newName; }		};	},

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

/**		 * Template group */		this.group = data.group ? data.group : '';

/**		 * Template wikitext *		 */		this.string = data.string ? data.string : '';

/**		 * Template start position in the edit textbox */		this.start = data.start ? data.start : 0;

/**		 * Template end position in the edit textbox */		this.end = data.end ? data.end : 0;

/**		 * Template parameters object that holds name-value pairs */		this.params = data.params ? data.params : {};

/**		 * Array of reference objects of this template */		this.references = [];

/**		 * Reference index dicts */

this.keys = {}; this.values = {}; this.keyValues = {};

/**		 * Helper dicts to keep track of duplicate reference keys, values key/values */

this.dupKeys = {}; this.dupValues = {}; this.dupKeyValues = {};

/**		 * Dict that holds citation name replacements */

this.replacements = {};

/**		 * Populate reference template's index dicts * @param {string} reference name * @param (string) reference content * @param (integer) reference order number in template *		 * @return {void} */		this.createIndexes = function ( key, value, ix ) {

if (key in this.keys) { this.keys[key].push(ix); this.dupKeys[key] = this.keys[key]; } else { this.keys[key] = [ix]; }

if (value in this.values) { this.values[value].push(ix); this.dupValues[value] = this.values[value]; } else { this.values[value] = [ix]; }

if (key + '_' + value in this.keyValues) { this.keyValues[key + '_' + value].push(ix); this.dupKeyValues[key + '_' + value] = this.keyValues[key + '_' + value]; } else { this.keyValues[key + '_' + value] = [ix]; }		};

/**		 * Recreate reference list template indexes *		 * @return {void} */		this.reIndex = function { var i, reference; this.keys = {}; this.values = {}; this.keyValues = {};

for ( i = 0; i < this.references.length; i++ ) { reference = this.references[ i ]; if ( typeof reference === 'object' ) { this.keys[ reference.name ] = [ i ]; this.values[ reference.content ] = [ i ]; this.keyValues[ reference.name + '_' + reference.content ] = [ i ]; }			}		};

/**		 * Process references indexes, remove duplicate *		 * @return {void} */

this.processDuplicates = function { this.processIndex( this.dupKeyValues, this.processDupKeyValues, this ); this.processIndex( this.dupKeys, this.processDupKeys, this ); this.processIndex( this.dupValues, this.processDupValues, this ); };

this.processIndex = function ( indexObj, callBack, callbackObj ) { // returnObj and dataObj are a bit of a hack for dupValues index. We need to get back the refIndex of the first duplicate value // to add it into the replacements array with the duplicate values that were deleted var returnObj, dataObj; for (var key in indexObj) { if (indexObj.hasOwnProperty(key)) { indexObj[key].forEach(function ( refIndex, ix ) {						returnObj = callBack.call( callbackObj, refIndex, ix, dataObj );						if ( typeof returnObj === 'object' ) {							dataObj = returnObj;						}					}); }			}		};

this.processDupKeyValues = function ( refIndex, ix, dataObj ) { if (ix > 0) { var refData = this.delRef( refIndex ); this.changeEveryIndex( refData[ 'name' ], refData[ 'content' ], refIndex); }		};

this.processDupKeys = function ( refIndex, ix, dataObj ) { if (ix > 0) { var refData = this.changeRefName( refIndex ); this.changeIndex( refData[ 'oldName' ], refIndex, this.keys ); this.addIndex( refData[ 'newName' ], refIndex, this.keys ); this.removeIndex( refData[ 'oldName' ] + '_' + refData[ 'content' ], this.keyValues ); this.addIndex( refData[ 'newName' ] + '_' + refData[ 'content' ], refIndex, this.keyValues ); }		};

this.processDupValues = function ( refIndex, ix, dataObj ) { if (ix == 0) { // get TemplateReference object var refData = this.getRef( refIndex ); return ( refData ); } else { var delrefData = this.delRef( refIndex ); this.removeIndex( delrefData[ 'name' ], this.keys ); this.changeIndex( delrefData[ 'content' ], refIndex, this.values ); this.removeIndex( delrefData[ 'name' ] + '_' + delrefData[ 'content' ], this.keyValues ); // add old and new reference name into replacements array this.replacements[delrefData['name']] = dataObj['name']; }		};

this.delRef = function ( refIndex ) { var name = this.references[ refIndex ].name; var content = this.references[ refIndex ].content; this.references[ refIndex ] = null; return ({				'name': name,				'content': content			}); };

this.changeRefName = function ( refIndex ) { var oldName = this.references[ refIndex ].name; var content = this.references[ refIndex ].content; var newName = this.getNewName ( oldName ); this.references[ refIndex ].name = newName; return ({				'oldName': oldName,				'content': content,				'newName': newName			}); };

// Creates new reference name while making sure it is unique per template this.getNewName = function ( oldName ) { var prefix, randomValue, newName;

randomValue = refcon.randomString( 6 ); prefix = typeof oldName !== 'undefined' ? oldName + '_' : ':'; newName = prefix + randomValue;

while ( newName in this.keys ) { randomValue = refcon.randomString( 6 ); newName = prefix + randomValue; }			return ( newName ); }

this.changeIndex = function ( key, refIndex, obj ) { var ix = obj[key].indexOf( refIndex ); if (ix > -1) obj[key].splice( ix, 1 ); };

this.addIndex = function ( key, value, obj ) { obj[key] = []; obj[key].push( value ); };

this.removeIndex = function ( key, obj ) { delete obj[key]; };

this.getRef = function ( refIndex ) { return this.references[ refIndex ]; };

this.addRef = function ( reference ) { var count = this.references.push( reference ); this.createIndexes( reference['name'], reference['content'], count - 1 ); }

this.delRef = function ( refIndex ) { var name = this.references[ refIndex ].name; var content = this.references[ refIndex ].content; this.references[ refIndex ] = null; return ({				'name': name,				'content': content			}); };

this.changeEveryIndex = function ( key, value, refIndex ) { this.changeIndex( key, refIndex, this.keys ); this.changeIndex( value, refIndex, this.values ); this.changeIndex( key + '_' + value, refIndex, this.keyValues ); // dupKeys, dupValues and dupKeyValues get changed by reference };	} };

$( refcon.init );