mediawiki-extensions-WikiEd.../modules/jquery.wikiEditor.templateEditor.js
Thiemo Mättig 772e39caf2 Unify coding style of jQuery( '<element>' ) calls
According to the jQuery documentation (and code) all kinds of
$( '<element>' )
$( '<element/>' )
$( '<element />' )
$( '<element></element>' )
are identical. So yes, this patch does not change anything. All it
does is removing characters that are ignored anyway. Using the same
style everywhere makes the code easier to read and understand and
may save a few bytes when it is gzipped.

The current WikiEditor code contains 67 usages of that jQuery call.
Only very few of them are not in the most simple <element> style.

Personally I consider the style <div/> confusing since a <div> can
not be a void element.

Change-Id: I816b4cccc9ee329e9bcdd9bd2353e5653fd10c36
2014-03-17 22:11:12 +01:00

887 lines
29 KiB
JavaScript

/* TemplateEditor module for wikiEditor */
/*jshint quotmark:false, onevar:false */
( function ( $ ) { $.wikiEditor.modules.templateEditor = {
/**
* Name mappings, dirty hack which will be removed once "TemplateInfo" extension is more fully supported
*/
'nameMappings': { //keep these all lowercase to navigate web of redirects
"infobox skyscraper": "building_name",
"infobox settlement": "official_name"
},
/**
* Compatability map
*/
'browsers': {
// Left-to-right languages
'ltr': {
'msie': [['>=', 8]],
'firefox': [['>=', 3]],
'opera': [['>=', 10]],
'safari': [['>=', 4]]
},
// Right-to-left languages
'rtl': {
'msie': false,
'firefox': [['>=', 3]],
'opera': [['>=', 10]],
'safari': [['>=', 4]]
}
},
/**
* Core Requirements
*/
'req': [ 'iframe' ],
/**
* Event handlers
*/
evt: {
/**
* @param context
* @param event
*/
mark: function( context ) {
// The markers returned by this function are skipped on realchange, so don't regenerate them in that case
if ( context.modules.highlight.currentScope === 'realchange' ) {
return;
}
// Get references to the markers and tokens from the current context
var markers = context.modules.highlight.markers;
var tokenArray = context.modules.highlight.tokenArray;
// Collect matching level 0 template call boundaries from the tokenArray
var tokenIndex = 0;
while ( tokenIndex < tokenArray.length ){
while ( tokenIndex < tokenArray.length && tokenArray[tokenIndex].label !== 'TEMPLATE_BEGIN' ) {
tokenIndex++;
}
//open template
if ( tokenIndex < tokenArray.length ) {
var beginIndex = tokenIndex;
var endIndex = -1; //no match found
var openTemplates = 1;
while ( tokenIndex < tokenArray.length - 1 && endIndex === -1 ) {
tokenIndex++;
if ( tokenArray[tokenIndex].label === 'TEMPLATE_BEGIN' ) {
openTemplates++;
} else if ( tokenArray[tokenIndex].label === 'TEMPLATE_END' ) {
openTemplates--;
if ( openTemplates === 0 ) {
endIndex = tokenIndex;
} //we can stop looping
}
}//while finding template ending
if ( endIndex !== -1 ) {
markers.push( {
start: tokenArray[beginIndex].offset,
end: tokenArray[endIndex].offset,
type: 'template',
anchor: 'wrap',
afterWrap: function( node ) {
// Generate model
var model = $.wikiEditor.modules.templateEditor.fn.updateModel( $( node ) );
if ( model.isCollapsible() ) {
$.wikiEditor.modules.templateEditor.fn.wrapTemplate( $( node ) );
$.wikiEditor.modules.templateEditor.fn.bindTemplateEvents( $( node ) );
} else {
$( node ).addClass( 'wikiEditor-template-text' );
}
},
beforeUnwrap: function( node ) {
if ( $( node ).parent().hasClass( 'wikiEditor-template' ) ) {
$.wikiEditor.modules.templateEditor.fn.unwrapTemplate( $( node ) );
}
},
onSkip: function( node ) {
if ( $( node ).html() === $( node ).data( 'oldHTML' ) ) {
// No change
return;
}
// Text changed, regenerate model
var model = $.wikiEditor.modules.templateEditor.fn.updateModel( $( node ) );
// Update template name if needed
if ( $( node ).parent().hasClass( 'wikiEditor-template' ) ) {
var $label = $( node ).parent().find( '.wikiEditor-template-label' );
var displayName = $.wikiEditor.modules.templateEditor.fn.getTemplateDisplayName( model );
if ( $label.text() !== displayName ) {
$label.text( displayName );
}
}
// Wrap or unwrap the template if needed
if ( $( node ).parent().hasClass( 'wikiEditor-template' ) &&
!model.isCollapsible() ) {
$.wikiEditor.modules.templateEditor.fn.unwrapTemplate( $( node ) );
} else if ( !$( node ).parent().hasClass( 'wikiEditor-template' ) &&
model.isCollapsible() ) {
$.wikiEditor.modules.templateEditor.fn.wrapTemplate( $( node ) );
$.wikiEditor.modules.templateEditor.fn.bindTemplateEvents( $( node ) );
}
},
getAnchor: function( ca1 ) {
return $( ca1.parentNode ).is( 'span.wikiEditor-template-text' ) ?
ca1.parentNode : null;
},
context: context,
skipDivision: 'realchange'
} );
} else { //else this was an unmatched opening
tokenArray[beginIndex].label = 'TEMPLATE_FALSE_BEGIN';
tokenIndex = beginIndex;
}
}//if opentemplates
}
}, //mark
keydown: function( context, event ) {
// Reset our ignoreKeypress variable if it's set to true
if ( context.$iframe.data( 'ignoreKeypress' ) ) {
context.$iframe.data( 'ignoreKeypress', false );
}
var $evtElem = event.jQueryNode;
if ( $evtElem.hasClass( 'wikiEditor-template-label' ) ) {
// Allow anything if the command or control key are depressed
if ( event.ctrlKey || event.metaKey ) {
return true;
}
switch ( event.which ) {
case 13: // Enter
$evtElem.click();
event.preventDefault();
return false;
case 32: // Space
$evtElem.parent().siblings( '.wikiEditor-template-expand' ).click();
event.preventDefault();
return false;
case 37:// Left
case 38:// Up
case 39:// Right
case 40: //Down
return true;
default:
// Set the ignroreKeypress variable so we don't allow typing if the key is held
context.$iframe.data( 'ignoreKeypress', true );
// Can't type in a template name
event.preventDefault();
return false;
}
} else if ( $evtElem.hasClass( 'wikiEditor-template-text' ) ) {
switch ( event.which ) {
case 13: // Enter
// Ensure that the user can't break this by holding in the enter key
context.$iframe.data( 'ignoreKeypress', true );
// FIXME: May be a more elegant way to do this, but this works too
context.fn.encapsulateSelection( { 'pre': '\n', 'peri': '', 'post': '' } );
event.preventDefault();
return false;
default: return true;
}
}
},
/**
* @param context
* @param event
*/
keyup: function( context ) {
// Rest our ignoreKeypress variable if it's set to true
if ( context.$iframe.data( 'ignoreKeypress' ) ) {
context.$iframe.data( 'ignoreKeypress', false );
}
return true;
},
/**
* @param context
* @param event
*/
keypress: function( context ) {
// If this event is from a keydown event which we want to block, ignore it
return ( context.$iframe.data( 'ignoreKeypress' ) ? false : true );
}
},
/**
* Regular expressions that produce tokens
*/
exp: [
{ 'regex': /{{/, 'label': "TEMPLATE_BEGIN" },
{ 'regex': /}}/, 'label': "TEMPLATE_END", 'markAfter': true }
],
/**
* Configuration
*/
cfg: {
},
/**
* Internally used functions
*/
fn: {
/**
* Creates template form module within wikieditor
* @param context Context object of editor to create module in
* @param config Configuration object to create module from
*/
create: function( context ) {
// Initialize module within the context
context.modules.templateEditor = {};
},
/**
* Turns a simple template wrapper (really just a <span>) into a complex one
* @param $wrapper Wrapping <span>
*/
wrapTemplate: function( $wrapper ) {
var model = $wrapper.data( 'model' );
$wrapper
.wrap( '<span class="wikiEditor-template"></span>' )
.addClass( 'wikiEditor-template-text wikiEditor-template-text-shrunken' )
.parent()
.addClass( 'wikiEditor-template-collapsed' )
.prepend(
'<span class="wikiEditor-template-expand wikiEditor-noinclude"></span>' +
'<span class="wikiEditor-template-name wikiEditor-noinclude">' +
'<span class="wikiEditor-template-label wikiEditor-noinclude">' +
$.wikiEditor.modules.templateEditor.fn.getTemplateDisplayName( model ) + '</span>' +
'<span class="wikiEditor-template-dialog wikiEditor-noinclude"></span>' +
'</span>'
);
},
/**
* Turn a complex template wrapper back into a simple one
* @param $wrapper Wrapping <span>
*/
unwrapTemplate: function( $wrapper ) {
$wrapper.parent().replaceWith( $wrapper );
},
/**
* Bind events to a template
* @param $wrapper Original wrapper for the template to bind events to
*/
bindTemplateEvents: function( $wrapper ) {
var $template = $wrapper.parent( '.wikiEditor-template' );
if ( typeof opera === 'undefined' ) {
$template.parent().attr('contentEditable', 'false');
}
$template.click( function(event) {event.preventDefault(); return false;} );
$template.find( '.wikiEditor-template-name' )
.click( function( event ) {
$.wikiEditor.modules.templateEditor.fn.createDialog( $wrapper );
event.stopPropagation();
return false;
} )
.mousedown( function( event ) { event.stopPropagation(); return false; } );
$template.find( '.wikiEditor-template-expand' )
.click( function( event ) {
$.wikiEditor.modules.templateEditor.fn.toggleWikiTextEditor( $wrapper );
event.stopPropagation();
return false;
} )
.mousedown( function( event ) { event.stopPropagation(); return false; } );
},
/**
* Toggle the visisbilty of the wikitext for a given template
* @param $wrapper The origianl wrapper we want expand/collapse
*/
toggleWikiTextEditor: function( $wrapper ) {
var context = $wrapper.data( 'marker' ).context;
var $template = $wrapper.parent( '.wikiEditor-template' );
context.fn.purgeOffsets();
$template
.toggleClass( 'wikiEditor-template-expanded' )
.toggleClass( 'wikiEditor-template-collapsed' ) ;
var $templateText = $template.find( '.wikiEditor-template-text' );
$templateText.toggleClass( 'wikiEditor-template-text-shrunken' );
$templateText.toggleClass( 'wikiEditor-template-text-visible' );
if ( $templateText.hasClass('wikiEditor-template-text-shrunken') ){
//we just closed the template
// Update the model if we need to
if ( $templateText.html() !== $templateText.data( 'oldHTML' ) ) {
var templateModel = $.wikiEditor.modules.templateEditor.fn.updateModel( $templateText );
//this is the only place the template name can be changed; keep the template name in sync
var $tLabel = $template.find( '.wikiEditor-template-label' );
$tLabel.text( $.wikiEditor.modules.templateEditor.fn.getTemplateDisplayName( templateModel ) );
}
}
},
/**
* Create a dialog for editing a given template and open it
* @param $wrapper The origianl wrapper for which to create the dialog
*/
createDialog: function( $wrapper ) {
var context = $wrapper.data( 'marker' ).context;
var $template = $wrapper.parent( '.wikiEditor-template' );
var dialog = {
'titleMsg': 'wikieditor-template-editor-dialog-title',
'id': 'wikiEditor-template-dialog',
'html': '\
<fieldset>\
<div class="wikiEditor-template-dialog-title" />\
<div class="wikiEditor-template-dialog-fields" />\
</fieldset>',
init: function() {
$(this).find( '[rel]' ).each( function() {
$(this).text( mediaWiki.msg( $(this).attr( 'rel' ) ) );
} );
},
immediateCreate: true,
dialog: {
width: 600,
height: 400,
dialogClass: 'wikiEditor-toolbar-dialog',
buttons: {
'wikieditor-template-editor-dialog-submit': function() {
// More user feedback
var $templateDiv = $( this ).data( 'templateDiv' );
context.fn.highlightLine( $templateDiv );
var $templateText = $templateDiv.children( '.wikiEditor-template-text' );
var templateModel = $templateText.data( 'model' );
$( this ).find( '.wikiEditor-template-dialog-field-wrapper textarea' ).each( function() {
// Update the value
templateModel.setValue( $( this ).data( 'name' ), $( this ).val() );
});
//keep text consistent
$.wikiEditor.modules.templateEditor.fn.updateModel( $templateText, templateModel );
$( this ).dialog( 'close' );
},
'wikieditor-template-editor-dialog-cancel': function() {
$(this).dialog( 'close' );
}
},
open: function() {
var $templateDiv = $( this ).data( 'templateDiv' );
var $templateText = $templateDiv.children( '.wikiEditor-template-text' );
var templateModel = $templateText.data( 'model' );
// Update the model if we need to
if ( $templateText.html() !== $templateText.data( 'oldHTML' ) ) {
templateModel = $.wikiEditor.modules.templateEditor.fn.updateModel( $templateText );
}
// Build the table
// TODO: Be smart and recycle existing table
var params = templateModel.getAllInitialParams();
var $fields = $( this ).find( '.wikiEditor-template-dialog-fields' );
// Do some bookkeeping so we can recycle existing rows
var $rows = $fields.find( '.wikiEditor-template-dialog-field-wrapper' );
for ( var paramIndex in params ) {
var param = params[paramIndex];
if ( typeof param.name === 'undefined' ) {
// param is the template name, skip it
continue;
}
var paramText = typeof param === 'string' ?
param.name.replace( /[\_\-]/g, ' ' ) :
param.name;
var paramVal = templateModel.getValue( param.name );
if ( $rows.length > 0 ) {
// We have another row to recycle
var $row = $rows.eq( 0 );
$row.children( 'label' ).text( paramText );
$row.children( 'textarea' )
.data( 'name', param.name )
.val( paramVal )
.each( function() {
$(this).css( 'height', $(this).val().length > 24 ? '4.5em' : '1.5em' );
} );
$rows = $rows.not( $row );
} else {
// Create a new row
var $paramRow = $( '<div>' )
.addClass( 'wikiEditor-template-dialog-field-wrapper' );
$( '<label>' )
.text( paramText )
.appendTo( $paramRow );
$( '<textarea>' )
.data( 'name', param.name )
.val( paramVal )
.each( function() {
$(this).css( 'height', $(this).val().length > 24 ? '4.5em' : '1.5em' );
} )
.data( 'expanded', false )
.bind( 'cut paste keypress click change', function( e ) {
// If this was fired by a tab keypress, let it go
if ( e.keyCode === 9 || e.keyCode === '9' ) {
return true;
}
var $this = $( this );
setTimeout( function() {
var expanded = $this.data( 'expanded' );
if ( $this.val().indexOf( '\n' ) !== -1 || $this.val().length > 24 ) {
if ( !expanded ) {
$this.animate( { 'height': '4.5em' }, 'fast' );
$this.data( 'expanded', true );
}
} else {
if ( expanded ) {
$this.animate( { 'height': '1.5em' }, 'fast' );
$this.data( 'expanded', false );
}
}
}, 0 );
} )
.appendTo( $paramRow );
$paramRow
.append( '<div style="clear: both;"></div>' )
.appendTo( $fields );
}
}
// Remove any leftover rows
$rows.remove();
$fields.find( 'label' ).autoEllipsis();
// Ensure our close button doesn't recieve the ui-state-focus class
$( this ).parent( '.ui-dialog' ).find( '.ui-dialog-titlebar-close' )
.removeClass( 'ui-state-focus' );
// Set tabindexes on form fields if needed
// First unset the tabindexes on the buttons and existing form fields
// so the order doesn't get messed up
var $needTabindex = $( this ).closest( '.ui-dialog' ).find( 'button, textarea' );
if ( $needTabindex.not( '[tabindex]' ).length ) {
// Only do this if there actually are elements missing a tabindex
$needTabindex.removeAttr( 'tabindex' );
$.wikiEditor.modules.dialogs.fn.setTabindexes( $needTabindex );
}
}
}
};
// Lazy-create the dialog at this time
context.$textarea.wikiEditor( 'addDialog', { 'templateEditor': dialog } );
$( '#' + dialog.id )
.data( 'templateDiv', $template )
.dialog( 'open' );
},
/**
* Update a template's model and HTML
* @param $templateText Wrapper <span> containing the template text
* @param model Template model to use, will be generated if not set
* @return model object
*/
updateModel: function( $templateText, model ) {
var context = $templateText.data( 'marker' ).context;
var text;
if ( typeof model === 'undefined' ) {
text = context.fn.htmlToText( $templateText.html() );
} else {
text = model.getText();
}
// To keep stuff simple but not break it, we need to do encode newlines as <br>s
$templateText.text( text );
$templateText.html( $templateText.html().replace( /\n/g, '<br />' ) );
$templateText.data( 'oldHTML', $templateText.html() );
if ( typeof model === 'undefined' ) {
model = new $.wikiEditor.modules.templateEditor.fn.model( text );
$templateText.data( 'model', model );
}
return model;
},
/**
* Gets template display name
*/
getTemplateDisplayName: function ( model ) {
var tName = model.getName();
if( model.getValue( 'name' ) !== '' ) {
return tName + ': ' + model.getValue( 'name' );
} else if( model.getValue( 'Name' ) !== '' ) {
return tName + ': ' + model.getValue( 'Name' );
} else if( tName.toLowerCase() in $.wikiEditor.modules.templateEditor.nameMappings ) {
return tName + ': ' + model.getValue( $.wikiEditor.modules.templateEditor.nameMappings[tName.toLowerCase()] );
}
return tName;
},
/**
* Builds a template model from given wikitext representation, allowing object-oriented manipulation of the contents
* of the template while preserving whitespace and formatting.
*
* @param wikitext String of wikitext content
*/
model: function( wikitext ) {
/* Private members */
var collapsible = true;
/* Private Functions */
/**
* Builds a Param object.
*
* @param name
* @param value
* @param number
* @param nameIndex
* @param equalsIndex
* @param valueIndex
*/
function Param( name, value, number, nameIndex, equalsIndex, valueIndex ) {
this.name = name;
this.value = value;
this.number = number;
this.nameIndex = nameIndex;
this.equalsIndex = equalsIndex;
this.valueIndex = valueIndex;
}
/**
* Builds a Range object.
*
* @param begin
* @param end
*/
function Range( begin, end ) {
this.begin = begin;
this.end = end;
}
/**
* Set 'original' to true if you want the original value irrespective of whether the model's been changed
*
* @param name
* @param value
* @param original
*/
function getSetValue( name, value, original ) {
var valueRange;
var rangeIndex;
var retVal;
if ( isNaN( name ) ) {
// It's a string!
if ( typeof paramsByName[name] === 'undefined' ) {
// Does not exist
return '';
}
rangeIndex = paramsByName[name];
} else {
// It's a number!
rangeIndex = parseInt( name, 10 );
}
if ( typeof params[rangeIndex] === 'undefined' ) {
// Does not exist
return '';
}
valueRange = ranges[params[rangeIndex].valueIndex];
if ( typeof valueRange.newVal === 'undefined' || original ) {
// Value unchanged, return original wikitext
retVal = wikitext.substring( valueRange.begin, valueRange.end );
} else {
// New value exists, return new value
retVal = valueRange.newVal;
}
/*jshint eqnull:true */
if ( value != null ) {
ranges[params[rangeIndex].valueIndex].newVal = value;
}
return retVal;
}
/* Public Functions */
/**
* Get template name
*/
this.getName = function() {
if( typeof ranges[templateNameIndex].newVal === 'undefined' ) {
return wikitext.substring( ranges[templateNameIndex].begin, ranges[templateNameIndex].end );
} else {
return ranges[templateNameIndex].newVal;
}
};
/**
* Set template name (if we want to support this)
*
* @param name
*/
this.setName = function( name ) {
ranges[templateNameIndex].newVal = name;
};
/**
* Set value for a given param name / number
*
* @param name
* @param value
*/
this.setValue = function( name, value ) {
return getSetValue( name, value, false );
};
/**
* Get value for a given param name / number
*
* @param name
*/
this.getValue = function( name ) {
return getSetValue( name, null, false );
};
/**
* Get original value of a param
*
* @param name
*/
this.getOriginalValue = function( name ) {
return getSetValue( name, null, true );
};
/**
* Get a list of all param names (numbers for the anonymous ones)
*/
this.getAllParamNames = function() {
return paramsByName;
};
/**
* Get the initial params
*/
this.getAllInitialParams = function(){
return params;
};
/**
* Get original template text
*/
this.getOriginalText = function() {
return wikitext;
};
/**
* Get modified template text
*/
this.getText = function() {
var newText = "";
for ( var i = 0 ; i < ranges.length; i++ ) {
if( typeof ranges[i].newVal === 'undefined' ) {
newText += wikitext.substring( ranges[i].begin, ranges[i].end );
} else {
newText += ranges[i].newVal;
}
}
return newText;
};
this.isCollapsible = function() {
return collapsible;
};
/**
* Update ranges if there's been a change in one or more 'segments' of the template.
* Removes adjustment function so adjustment is only made once ever.
*/
this.updateRanges = function() {
var adjustment = 0;
for (var i = 0 ; i < ranges.length; i++ ) {
ranges[i].begin += adjustment;
if ( typeof ranges[i].adjust !== 'undefined' ) {
adjustment += ranges[i].adjust();
// NOTE: adjust should be a function that has the information necessary to calculate the length of
// this 'segment'
delete ranges[i].adjust;
}
ranges[i].end += adjustment;
}
};
// Whitespace* {{ whitespace* nonwhitespace:
if ( wikitext.match( /\s*{{\s*[^\s|]*:/ ) ) {
collapsible = false; // is a parser function
}
/*
* Take all template-specific characters that are not particular to the template we're looking at, namely {|=},
* and convert them into something harmless, in this case 'X'
*/
// Get rid of first {{ with whitespace
var sanatizedStr = wikitext.replace( /{{/, " " );
// Replace end
var endBraces = sanatizedStr.match( /}}\s*$/ );
if ( endBraces ) {
sanatizedStr = sanatizedStr.substring( 0, endBraces.index ) + " " +
sanatizedStr.substring( endBraces.index + 2 );
}
var startIndex, endIndex, sanatizedSegment, openBraces, brace;
//treat HTML comments like whitespace
while ( sanatizedStr.indexOf( '<!' ) !== -1 ) {
startIndex = sanatizedStr.indexOf( '<!' );
endIndex = sanatizedStr.indexOf('-->') + 3;
if( endIndex < 3 ){
break;
}
sanatizedSegment = sanatizedStr.substring( startIndex,endIndex ).replace( /\S/g , ' ' );
sanatizedStr =
sanatizedStr.substring( 0, startIndex ) + sanatizedSegment + sanatizedStr.substring( endIndex );
}
// Match the open braces we just found with equivalent closing braces note, works for any level of braces
while ( sanatizedStr.indexOf( '{{' ) !== -1 ) {
startIndex = sanatizedStr.indexOf( '{{' ) + 1;
openBraces = 2;
endIndex = startIndex;
while ( (openBraces > 0) && (endIndex < sanatizedStr.length) ) {
brace = sanatizedStr[++endIndex];
openBraces += brace === '}' ? -1 : brace === '{' ? 1 : 0;
}
sanatizedSegment = sanatizedStr.substring( startIndex,endIndex ).replace( /[{}|=]/g , 'X' );
sanatizedStr =
sanatizedStr.substring( 0, startIndex ) + sanatizedSegment + sanatizedStr.substring( endIndex );
}
//links, images, etc, which also can nest
while ( sanatizedStr.indexOf( '[[' ) !== -1 ) {
startIndex = sanatizedStr.indexOf( '[[' ) + 1;
openBraces = 2;
endIndex = startIndex;
while ( (openBraces > 0) && (endIndex < sanatizedStr.length) ) {
brace = sanatizedStr[++endIndex];
openBraces += brace === ']' ? -1 : brace === '[' ? 1 : 0;
}
sanatizedSegment = sanatizedStr.substring( startIndex,endIndex ).replace( /[\[\]|=]/g , 'X' );
sanatizedStr =
sanatizedStr.substring( 0, startIndex ) + sanatizedSegment + sanatizedStr.substring( endIndex );
}
/*
* Parse 1 param at a time
*/
var ranges = [];
var params = [];
var templateNameIndex = 0;
var doneParsing = false;
var oldDivider = 0;
var divider = sanatizedStr.indexOf( '|', oldDivider );
if ( divider === -1 ) {
divider = sanatizedStr.length;
doneParsing = true;
collapsible = false; //zero params
}
var nameMatch = sanatizedStr.substring( 0, divider ).match( /[^\s]/ );
var nameEndMatch;
/*jshint eqnull:true */
if ( nameMatch != null ) {
ranges.push( new Range( 0 ,nameMatch.index ) ); //whitespace and squiggles upto the name
nameEndMatch = sanatizedStr.substring( 0 , divider ).match( /[^\s]\s*$/ ); //last nonwhitespace character
templateNameIndex = ranges.push( new Range( nameMatch.index,
nameEndMatch.index + 1 ) );
templateNameIndex--; //push returns 1 less than the array
ranges[templateNameIndex].old = wikitext.substring( ranges[templateNameIndex].begin,
ranges[templateNameIndex].end );
} else {
ranges.push(new Range(0,0));
ranges[templateNameIndex].old = "";
}
params.push( ranges[templateNameIndex].old ); //put something in params (0)
/*
* Start looping over params
*/
var currentParamNumber = 0;
var currentField, currentValue, valueBeginIndex, valueBegin, valueEnd;
var nameIndex, equalsIndex, valueIndex;
var currentName, nameBegin, nameBeginIndex, nameEnd, nameEndIndex;
var valueEndIndex = ranges[templateNameIndex].end;
var paramsByName = [];
while ( !doneParsing ) {
currentParamNumber++;
oldDivider = divider;
divider = sanatizedStr.indexOf( '|', oldDivider + 1 );
if ( divider === -1 ) {
divider = sanatizedStr.length;
doneParsing = true;
}
currentField = sanatizedStr.substring( oldDivider+1, divider );
if ( currentField.indexOf( '=' ) === -1 ) {
// anonymous field, gets a number
//default values, since we'll allow empty values
valueBeginIndex = oldDivider + 1;
valueEndIndex = oldDivider + 1;
valueBegin = currentField.match( /\S+/ ); //first nonwhitespace character
if( valueBegin != null ){
valueBeginIndex = valueBegin.index + oldDivider+1;
valueEnd = currentField.match( /[^\s]\s*$/ ); //last nonwhitespace character
if( valueEnd == null ){ //ie
continue;
}
valueEndIndex = valueEnd.index + oldDivider + 2;
}
ranges.push( new Range( ranges[ranges.length-1].end,
valueBeginIndex ) ); //all the chars upto now
nameIndex = ranges.push( new Range( valueBeginIndex, valueBeginIndex ) ) - 1;
equalsIndex = ranges.push( new Range( valueBeginIndex, valueBeginIndex ) ) - 1;
valueIndex = ranges.push( new Range( valueBeginIndex, valueEndIndex ) ) - 1;
params.push( new Param(
currentParamNumber,
wikitext.substring( ranges[valueIndex].begin, ranges[valueIndex].end ),
currentParamNumber,
nameIndex,
equalsIndex,
valueIndex
) );
paramsByName[currentParamNumber] = currentParamNumber;
} else {
// There's an equals, could be comment or a value pair
currentName = currentField.substring( 0, currentField.indexOf( '=' ) );
// Still offset by oldDivider - first nonwhitespace character
nameBegin = currentName.match( /\S+/ );
if ( nameBegin == null ) {
// This is a comment inside a template call / parser abuse. let's not encourage it
currentParamNumber--;
continue;
}
nameBeginIndex = nameBegin.index + oldDivider + 1;
// Last nonwhitespace and non } character
nameEnd = currentName.match( /[^\s]\s*$/ );
if( nameEnd == null ){ //ie
continue;
}
nameEndIndex = nameEnd.index + oldDivider + 2;
// All the chars upto now
ranges.push( new Range( ranges[ranges.length-1].end, nameBeginIndex ) );
nameIndex = ranges.push( new Range( nameBeginIndex, nameEndIndex ) ) - 1;
currentValue = currentField.substring( currentField.indexOf( '=' ) + 1);
oldDivider += currentField.indexOf( '=' ) + 1;
//default values, since we'll allow empty values
valueBeginIndex = oldDivider + 1;
valueEndIndex = oldDivider + 1;
// First nonwhitespace character
valueBegin = currentValue.match( /\S+/ );
if( valueBegin != null ){
valueBeginIndex = valueBegin.index + oldDivider + 1;
// Last nonwhitespace and non } character
valueEnd = currentValue.match( /[^\s]\s*$/ );
if( valueEnd == null ){ //ie
continue;
}
valueEndIndex = valueEnd.index + oldDivider + 2;
}
// All the chars upto now
equalsIndex = ranges.push( new Range( ranges[ranges.length-1].end, valueBeginIndex) ) - 1;
valueIndex = ranges.push( new Range( valueBeginIndex, valueEndIndex ) ) - 1;
params.push( new Param(
wikitext.substring( nameBeginIndex, nameEndIndex ),
wikitext.substring( valueBeginIndex, valueEndIndex ),
currentParamNumber,
nameIndex,
equalsIndex,
valueIndex
) );
paramsByName[wikitext.substring( nameBeginIndex, nameEndIndex )] = currentParamNumber;
}
}
// The rest of the string
ranges.push( new Range( valueEndIndex, wikitext.length ) );
// Save vars
this.ranges = ranges;
this.wikitext = wikitext;
this.params = params;
this.paramsByName = paramsByName;
this.templateNameIndex = templateNameIndex;
} //model
}
}; } )( jQuery );