User:For Each element In group ... Next/RefConsolidate.js
Note: After publishing, you may have to bypass your browser's cache to see the changes.
- Firefox / Safari: Hold Shift while clicking Reload, or press either Ctrl-F5 or Ctrl-R (⌘-R on a Mac)
- Google Chrome: Press Ctrl-Shift-R (⌘-Shift-R on a Mac)
- Edge: Hold Ctrl while clicking Refresh, or press Ctrl-F5.
/**
* Copied from [[w:en:User:Kaniivel/RefConsolidate.js]] with modification
*
* 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 () {
$('<img/>')[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 = $('<div>').attr('id', 'refcon'), container = $('<div>').attr('id', 'refcon-container'),
header = $('<div>').attr('id', 'refcon-header'),
title = $('<span>').attr('id', 'refcon-title').text(refcon.getOption('gadgetname')),
closer = $('<div>').attr('id', 'refcon-close').addClass('refcon-abort').html('×').attr('title', refcon.getMessage('closetitle')),
content = $('<div>').attr('id', 'refcon-content'), form = $('<form>').attr('id', 'refcon-form'),
table = $('<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('<tr>\
<th></th>\
<th class="refcon-sortable refcon-asc"><span>#</span></th>\
<th class="refcon-sortable"><span>' + refcon.getMessage('name') + '</span></th>\
<th class="refcon-sortable"><span>' + refcon.getMessage('reference') + '</span></th>\
<th class="refcon-sortable"><span>' + refcon.getMessage('referenceuses') + '</span></th>\
<th></th>\
</tr>');
var i;
for (i = 0; i < refcon.refTemplates.length; i++) {
var refTemplate = refcon.refTemplates[i];
table.append('<tr id="templateheader' + i + '"><td class="refcon-templategroup" colspan="5" align="center">' + refcon.getMessage('refstemplateno') + ' ' + (i + 1) + (refcon.templateGroups[i].length > 0 ? ' (' + refcon.getMessage('referencegroup') + ': ' + refcon.templateGroups[i] + ')' : '') + '</td></tr>');
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('<tr template="' + i + '">' + '<td class="' + cssClass + '"><img src="' + refcon.getOption('image-yes') + '"></td>' + '<td class="' + cssClass + '" align="center">' + k + '</td>' + '<td class="' + cssClass + '"><input class="refcon-refname" type="text" template_id="' + i + '" name="' + j + '" value="' + reference.name + '"></td>' + '<td class="' + cssClass + ' refcontent">' + reference.content + '</td>' + '<td class="' + cssClass + '" align="center">' + reference.citations.length + '</td>' + '<td class="' + cssClass + '"><input class="refcon-refplace" type="checkbox" name="' + j + '" value="' + reference.citations.length + '"' + (reference.inRefTemplate === true ? 'checked' : '') + '></td>' + '</tr>');
}
}
}
table.append('<tr><td colspan="5"><table id="refcon-table-options">\
<tr><td><span class="refcon-option-header">' + refcon.getMessage('optionsheaderreflocation') + '</span></td><td width="20"></td><td><span class="refcon-option-header">' + refcon.getMessage('optionsheaderother') + '</span></td></tr>\
<tr><td><span class="refcon-option-point"><input class="refcon-refplacement" type="radio" name="reference-place" value="template"> ' + refcon.getMessage('optionlocation1') + '</span></td><td width="20"></td><td><span class="refcon-option-point"><input type="checkbox" id="refcon-savesorted" name="sort" value="yes">' + refcon.getMessage('checkboxsortorder') + '</span></td></tr>\
<tr><td><span class="refcon-option-point"><input class="refcon-refplacement" type="radio" name="reference-place" value="text"> ' + refcon.getMessage('optionlocation2') + '</span></td><td width="20"></td><td><span class="refcon-option-point"><input type="checkbox" id="refcon-keepnames" name="names" value="yes">' + refcon.getMessage('checkboxkeepnames') + '</span></td></tr>\
<tr><td><span class="refcon-option-point"><input class="refcon-refplacement" type="radio" name="reference-place" value="usage"> ' + refcon.getMessage('optionlocation3', ['<input id="refcon-table-options-uses" type="text" name="min_uses" size="2" value="2">']) + '</span></td><td width="20"></td><td><span class="refcon-option-point"><input type="checkbox" id="refcon-makecopies" name="copies" value="yes">' + refcon.getMessage('checkboxmakecopies') + '</span></td></tr>\
</table></td></tr>');
table.append('<tr id="refcon-buttons"><td colspan="5" align="center"><button type="button" id="refcon-abort-button" class="refcon-abort">' + refcon.getMessage('buttonabort') + '</button><button type="button" id="refcon-continue-button">' + refcon.getMessage('buttoncontinue') + '</button></td></tr>');
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() === '</ref>') {
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. <ref name="CV Kontrollikoda"/>
// 2. <ref name="pm"></ref>
// Ref label can have optional group parameter:
// <ref group="blah" name="CV Kontrollikoda"/> or <ref name="CV Kontrollikoda" group="blah"/>
// Group and name parameter values can be between '' or "", or bare (if value has no whitespaces)
var citations = [], citationsRegExp = /<ref(\s+[^/>]+)(?:\/\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 = /<ref(\s+[^\/]+?)?>([\s\S]*?)<\/ref>/ig, match, reference;
while ((match = referencesRegExp.exec(textPart.string))) {
// Avoid further processing of citations like <ref name="pm"></ref>
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: <ref name="abc" />
*/
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' : '<ref';
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 <ref> tags)
*
* Example: Second chapter of {{Cite book |first=Charles |last=Darwin |title=On the Origin of Species}}
*/
this.content = data.content ? data.content : '';
/**
* Array 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 = '<ref name="' + this.name + '">' + this.content + '</ref>';
return string;
};
/**
* Convert this reference to wikitext (in article text)
*/
this.toStringText = function (named) {
var string = '<ref';
if (this.group) string += ' group="' + this.group + '"';
if (named) string += ' name="' + this.name + '"';
string += '>' + this.content + '</ref>';
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));