User:Kaniivel/RefConsolidate.js

/**

* Reference Organizer is a Wikipedia gadget for organizing references in articles. With the gadget,

* you can easily move all references into reference list template, or vice versa. You can select which references

* to move based on citation count, or select references individually. The gadget detects all article's references

* and lists them in a table, where you can see their current location (in reference list template or in article text),

* sort references in various ways, and rename them.

*

* Copyright 2016–2017 Kaniivel

*

* Some parts of RefCon are derived from Wikipedia gadget ProveIt. Credit for these parts goes to:

* Copyright 2008-2011 Georgia Tech Research Corporation, Atlanta, GA 30332-0415, ALL RIGHTS RESERVED

* Copyright 2011- Matthew Flaschen

* Rewritten, internationalized, enhanced and maintained by Felipe Schenone since 2014

*

* RefCon is available under the GNU Free Documentation License (http://www.gnu.org/copyleft/fdl.html),

* the Creative Commons Attribution/Share-Alike License 3.0 (http://creativecommons.org/licenses/by-sa/3.0/),

* and the GNU General Public License 2 (http://www.gnu.org/licenses/gpl-2.0.html)

*/

( function ( mw, $ ) {

var 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( '×' ).attr('title', refcon.getMessage( 'closetitle' )),

content = $( '

' ).attr( 'id', 'refcon-content' ),

form = $( '

' ).attr( 'id', 'refcon-form' ),

table = $( '

' ).attr( 'id', 'refcon-table' );

// Put everything together and add it to DOM

header.append( title, closer );

content.append( form ).append( table );

container.append( header, content );

gui.append( container );

$( 'body' ).prepend( gui );

// Make GUI draggable

container.draggable({

handle: header

});

// Set GUI width and height to 80% of user's window size (fallback is CSS-predefined values, if this fails)

var width = $(window).width();

var height = $(window).height();

if ( ( Number.isInteger( width ) && width > 0 ) && ( Number.isInteger( height ) && height > 0 ) ) {

content.css("width", Math.floor( width * 0.8 ));

content.css("height", Math.floor( height * 0.8 ));

}

// Build table and fill it with reference data

table.append('

\

\

\

\

\

\

\

');

var i;

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

var refTemplate = refcon.refTemplates[ i ];

table.append('

');

var j, k = 0;

for ( j = 0; j < refTemplate.references.length; j++ ) {

var reference = refTemplate.references[ j ];

if ( reference ) {

k++;

var cssClass = k % 2 == 0 ? 'refcon-even' : 'refcon-odd';

table.append(

'

'

+ '

'

+ '

'

+ '

'

+ '

'

+ '

'

+ '

'

+ '

');

}

}

}

table.append('

');

table.append('

');

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

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

if ( $('#refcon-keepnames').prop('checked') )

refcon.userOptions['keepnames'] = true;

else

refcon.userOptions['keepnames'] = false;

// If user has checked "separate copies" checkbox

if ( $('#refcon-makecopies').prop('checked') )

refcon.userOptions['makecopies'] = true;

else

refcon.userOptions['makecopies'] = 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 the body of article"...

} 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 user has requested every article body reference to be separate copy...

if ( refcon.userOptions.makecopies === true ) {

textPart.string = textPart.string.replace( citation.string, templateRef.toStringText( refcon.userOptions.keepnames ) );

// if copies option was not requested...

} 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 || 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';

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 {{R}} for shorter citation format

useTemplateR = refcon.useConfigOption( refcon.getOption( 'usetemplateR' ), 'usetemplateR' );

var startString = useTemplateR ? '{{r' : '

var groupString = useTemplateR ? '|g=' + this.group : ' group="' + this.group + '"';

var nameString = useTemplateR ? '|' + this.name : ' name="' + this.name + '"';

var endString = 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 tags)

*

* Example: Second chapter of {{Cite book |first=Charles |last=Darwin |title=On the Origin of Species}}

*/

this.content = data.content ? data.content : '';

/**

* Array that contains citations to this reference

*/

this.citations = [];

/**

* Boolean for reference location. True (the default) means in reference list template. False means in article text

*/

this.inRefTemplate = typeof data.inRefTemplate !== 'undefined' ? data.inRefTemplate : true;

/**

* Boolean for reference output. False (the default) means the reference has not been printed yet. True means it has been printed.

*/

this.wasPrinted = false;

/**

* Convert this reference to wikitext (inside reference list template)

*/

this.toString = function () {

var string = '' + this.content + '';

return string;

};

/**

* Convert this reference to wikitext (in article text)

*/

this.toStringText = function ( named ) {

var string = '

if ( this.group )

string += ' group="' + this.group + '"';

if ( named )

string += ' name="' + this.name + '"';

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( 5 );

prefix = typeof oldName !== 'undefined' ? oldName + '_' : '';

newName = prefix + randomValue;

while ( newName in this.keys ) {

randomValue = refcon.randomString( 5 );

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 );

}( mw, jQuery ) );

# '+refcon.getMessage( 'name' )+' '+refcon.getMessage( 'reference' )+' '+refcon.getMessage( 'referenceuses' )+'
'

+ refcon.getMessage( 'refstemplateno' ) + ' ' + ( i + 1 )

+ (refcon.templateGroups[ i ].length > 0 ? ' (' + refcon.getMessage( 'referencegroup' ) + ': ' + refcon.templateGroups[ i ] + ')' : '')

+ '

' + k + ' ' + reference.content + ' ' + reference.citations.length + '
\

\

\

\

\

' + refcon.getMessage( 'optionsheaderreflocation' ) + '' + refcon.getMessage( 'optionsheaderother' ) + '
' + refcon.getMessage( 'optionlocation1' ) + ''+ refcon.getMessage( 'checkboxsortorder' ) +'
' + refcon.getMessage( 'optionlocation2' ) + ''+ refcon.getMessage( 'checkboxkeepnames' ) +'
' + refcon.getMessage( 'optionlocation3', [ '' ]) + ''+ refcon.getMessage( 'checkboxmakecopies' ) +'