/* TemplateEditor module for wikiEditor */ ( 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: { mark: function( context, event ) { // 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 level = 0; 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; var templatesMatched = false; 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, ca2 ) { 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; } } }, keyup: function( context, event ) { // Rest our ignoreKeypress variable if it's set to true if ( context.$iframe.data( 'ignoreKeypress' ) ) { context.$iframe.data( 'ignoreKeypress', false ); } return true; }, keypress: function( context, event ) { // 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, config ) { // Initialize module within the context context.modules.templateEditor = {}; }, /** * Turns a simple template wrapper (really just a ) into a complex one * @param $wrapper Wrapping */ wrapTemplate: function( $wrapper ) { var model = $wrapper.data( 'model' ); var context = $wrapper.data( 'marker' ).context; var $template = $wrapper .wrap( '' ) .addClass( 'wikiEditor-template-text wikiEditor-template-text-shrunken' ) .parent() .addClass( 'wikiEditor-template-collapsed' ) .prepend( '' + '' + '' + $.wikiEditor.modules.templateEditor.fn.getTemplateDisplayName( model ) + '' + '' + '' ); }, /** * Turn a complex template wrapper back into a simple one * @param $wrapper Wrapping */ 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': '\
\
\
\
', 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 = $( '
' ) .addClass( 'wikiEditor-template-dialog-field-wrapper' ); $( '