mediawiki-extensions-WikiEd.../modules/jquery.wikiEditor.templateEditor.js

887 lines
29 KiB
JavaScript
Raw Normal View History

/* 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"
2011-09-13 08:56:32 +00:00
},
/**
* 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: {
2011-09-13 08:56:32 +00:00
/**
* @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;
}
2011-09-13 08:56:32 +00:00
// 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;
}
2011-09-13 08:56:32 +00:00
// Text changed, regenerate model
var model = $.wikiEditor.modules.templateEditor.fn.updateModel( $( node ) );
2011-09-13 08:56:32 +00:00
// 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 );
}
}
2011-09-13 08:56:32 +00:00
// 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
2011-09-13 08:56:32 +00:00
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
2011-09-13 08:56:32 +00:00
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 }
],
/**
2011-09-13 08:56:32 +00:00
* 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">' +
2011-09-13 08:56:32 +00:00
'<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');
}
2011-09-13 08:56:32 +00:00
2010-12-08 19:20:44 +00:00
$template.click( function(event) {event.preventDefault(); return false;} );
2011-09-13 08:56:32 +00:00
$template.find( '.wikiEditor-template-name' )
2011-09-13 08:56:32 +00:00
.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' )
2011-09-13 08:56:32 +00:00
.click( function( event ) {
$.wikiEditor.modules.templateEditor.fn.toggleWikiTextEditor( $wrapper );
event.stopPropagation();
2011-09-13 08:56:32 +00:00
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' ) ;
2011-09-13 08:56:32 +00:00
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
2011-09-13 08:56:32 +00:00
// Update the model if we need to
if ( $templateText.html() !== $templateText.data( 'oldHTML' ) ) {
var templateModel = $.wikiEditor.modules.templateEditor.fn.updateModel( $templateText );
2011-09-13 08:56:32 +00:00
//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 ) );
}
2011-09-13 08:56:32 +00:00
}
},
/**
* 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();
2011-09-13 08:56:32 +00:00
// 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' );
2011-09-13 08:56:32 +00:00
// 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;
},
2011-09-13 08:56:32 +00:00
/**
* 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;
},
2011-09-13 08:56:32 +00:00
/**
* Builds a template model from given wikitext representation, allowing object-oriented manipulation of the contents
* of the template while preserving whitespace and formatting.
2011-09-13 08:56:32 +00:00
*
* @param wikitext String of wikitext content
*/
model: function( wikitext ) {
2011-09-13 08:56:32 +00:00
/* Private members */
2011-09-13 08:56:32 +00:00
var collapsible = true;
2011-09-13 08:56:32 +00:00
/* Private Functions */
2011-09-13 08:56:32 +00:00
/**
* Builds a Param object.
2011-09-13 08:56:32 +00:00
*
* @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.
2011-09-13 08:56:32 +00:00
*
* @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
2011-09-13 08:56:32 +00:00
*
* @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;
}
2011-09-13 08:56:32 +00:00
/* Public Functions */
2011-09-13 08:56:32 +00:00
/**
* 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)
2011-09-13 08:56:32 +00:00
*
* @param name
*/
this.setName = function( name ) {
ranges[templateNameIndex].newVal = name;
};
/**
* Set value for a given param name / number
2011-09-13 08:56:32 +00:00
*
* @param name
* @param value
*/
this.setValue = function( name, value ) {
return getSetValue( name, value, false );
};
/**
* Get value for a given param name / number
2011-09-13 08:56:32 +00:00
*
* @param name
*/
this.getValue = function( name ) {
return getSetValue( name, null, false );
};
/**
* Get original value of a param
2011-09-13 08:56:32 +00:00
*
* @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;
};
2011-09-13 08:56:32 +00:00
this.isCollapsible = function() {
return collapsible;
};
2011-09-13 08:56:32 +00:00
/**
* 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;
}
};
2011-09-13 08:56:32 +00:00
// 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 );
}
2011-09-13 08:56:32 +00:00
var startIndex, endIndex, sanatizedSegment, openBraces, brace;
2011-09-13 08:56:32 +00:00
//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 );
}
2011-09-13 08:56:32 +00:00
// 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 );
}
2011-09-13 08:56:32 +00:00
/*
* 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
2011-09-13 08:56:32 +00:00
//default values, since we'll allow empty values
valueBeginIndex = oldDivider + 1;
valueEndIndex = oldDivider + 1;
2011-09-13 08:56:32 +00:00
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;
2011-09-13 08:56:32 +00:00
// 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;
2011-09-13 08:56:32 +00:00
//default values, since we'll allow empty values
valueBeginIndex = oldDivider + 1;
valueEndIndex = oldDivider + 1;
2011-09-13 08:56:32 +00:00
// 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 ) );
2011-09-13 08:56:32 +00:00
// Save vars
this.ranges = ranges;
this.wikitext = wikitext;
this.params = params;
this.paramsByName = paramsByName;
this.templateNameIndex = templateNameIndex;
} //model
2011-09-13 08:56:32 +00:00
}
}; } )( jQuery );