http://www.mediawiki.org/wiki/Special:Code/MediaWiki/71238
Revision: 71238 Author: dale Date: 2010-08-18 05:58:07 +0000 (Wed, 18 Aug 2010) Log Message: ----------- * improved sequencer remote stubs * support for data url sequence source loading * added stubs for Sequencer Server abstraction for interacting with server back end ( only mediawiki so far ) Modified Paths: -------------- branches/MwEmbedStandAlone/components/mw.Api.js branches/MwEmbedStandAlone/modules/Sequencer/Sequencer.i18n.php branches/MwEmbedStandAlone/modules/Sequencer/loader.js branches/MwEmbedStandAlone/modules/Sequencer/mw.RemoteSequencer.js branches/MwEmbedStandAlone/modules/Sequencer/mw.Sequencer.js branches/MwEmbedStandAlone/modules/Sequencer/mw.SequencerPlayer.js branches/MwEmbedStandAlone/modules/Sequencer/mw.SequencerTimeline.js branches/MwEmbedStandAlone/modules/SmilPlayer/loader.js branches/MwEmbedStandAlone/modules/SmilPlayer/mw.Smil.js branches/MwEmbedStandAlone/modules/SmilPlayer/mw.SmilBody.js branches/MwEmbedStandAlone/modules/SmilPlayer/mw.SmilHooks.js branches/MwEmbedStandAlone/modules/SmilPlayer/mw.SmilLayout.js branches/MwEmbedStandAlone/mwEmbed.js Added Paths: ----------- branches/MwEmbedStandAlone/modules/Sequencer/mw.SequencerServer.js Modified: branches/MwEmbedStandAlone/components/mw.Api.js =================================================================== --- branches/MwEmbedStandAlone/components/mw.Api.js 2010-08-18 02:52:32 UTC (rev 71237) +++ branches/MwEmbedStandAlone/components/mw.Api.js 2010-08-18 05:58:07 UTC (rev 71238) @@ -8,7 +8,7 @@ /** * - * Helper function to get revision text for a given title + * Helper function to get latest revision text for a given title * * Assumes "follow redirects" * Modified: branches/MwEmbedStandAlone/modules/Sequencer/Sequencer.i18n.php =================================================================== --- branches/MwEmbedStandAlone/modules/Sequencer/Sequencer.i18n.php 2010-08-18 02:52:32 UTC (rev 71237) +++ branches/MwEmbedStandAlone/modules/Sequencer/Sequencer.i18n.php 2010-08-18 05:58:07 UTC (rev 71238) @@ -14,8 +14,9 @@ 'mwe-sequencer-loading-asset' => 'Loading asset ...', 'mwe-sequencer-no_selected_resource' => '<h3>No resource selected</h3> Select a clip to enable editing.', + 'mwe-sequencer-untitled-sequence' => 'Untitled sequence', 'mwe-sequencer-error_edit_multiple' => '<h3>Multiple resources selected</h3> Select a single clip to edit it.', - 'mwe-sequencer-no-sequence-start-new' => 'Empty sequence, [$1 browser for assets] to create a new sequence', + 'mwe-sequencer-no-sequence-start-new' => 'Empty sequence, [$1 add assets] to create a new sequence', 'mwe-sequencer-video-track' => 'Video track', 'mwe-sequencer-audio-track' => 'Audio track', 'mwe-sequencer-sequencer_credit_line' => 'Developed by [$1 Kaltura, Inc] in partnership with the [$1 Wikimedia Foundation]', Modified: branches/MwEmbedStandAlone/modules/Sequencer/loader.js =================================================================== --- branches/MwEmbedStandAlone/modules/Sequencer/loader.js 2010-08-18 02:52:32 UTC (rev 71237) +++ branches/MwEmbedStandAlone/modules/Sequencer/loader.js 2010-08-18 05:58:07 UTC (rev 71238) @@ -71,6 +71,7 @@ 'mw.style.Sequencer' ], [ + '$j.fn.layout', // UI components used in the sequencer interface: '$j.ui.accordion', Modified: branches/MwEmbedStandAlone/modules/Sequencer/mw.RemoteSequencer.js =================================================================== --- branches/MwEmbedStandAlone/modules/Sequencer/mw.RemoteSequencer.js 2010-08-18 02:52:32 UTC (rev 71237) +++ branches/MwEmbedStandAlone/modules/Sequencer/mw.RemoteSequencer.js 2010-08-18 05:58:07 UTC (rev 71238) @@ -25,12 +25,12 @@ }, drawUI: function() { - // Check page type + // Check page action if( this.action == 'view' ) { this.showViewUI(); } }, - /* + /** * Check page for sequence * if not present give link to "create" one. */ @@ -38,7 +38,11 @@ var _this = this; if( wgArticleId == 0 ) { // Update create button - $j('#ca-edit span').text( gM('mwe-sequencer-create-sequence' )); + $j('#ca-edit span a') + .text( gM('mwe-sequencer-create-sequence' )) + .click(function(){ + _this.showEditor(); + }) $j( this.target ).html( gM("mwe-sequencer-no-sequence-create", @@ -47,25 +51,57 @@ }) ) ); - }else{ - // Get the article source? - + } else { + // Display embedding help } }, showEditor: function(){ - $j('body').append( '<div id="seqcontainer" style="position:absolute;top:5px;bottom:10px;left:10px;right:10px;" />' ); - mw.load( 'Sequencer', function(){ - $j('#seqcontainer').sequencer({ - 'smilSource' : 'SampleEditorSequenceSmil.xml', - //set the add media wizard to only include commons: - 'AddMediaConf':{ - 'enabled_providers':[ 'wiki_commons' ], - 'import_url_mode' : 'remote_link', - 'default_query' : 'fish' + var _this = this; + + $j('body').append( + $j('<div />') + .attr('id',"edit_sequence_container") + .css({ + 'position' : 'absolute', + 'font-size' : '.8em', + 'top' : '5px', + 'bottom' : '5px', + 'left' : '5px', + 'right' : '5px', + 'background': '#FFF' + }) + .loadingSpinner() + ) + + mw.load( 'Sequencer', function(){ + $j('#edit_sequence_container').sequencer({ + 'title' : _this.getTitle(), + 'newSequence' : ( wgArticleId == 0 ), + 'server': { + 'type' : 'mediaWiki', + 'url' : _this.getApiUrl(), + 'titleKey' : wgTitle, + }, + // Set the add media wizard to only include commons: + 'addMedia' : { + 'enabled_providers':[ 'wiki_commons' ], + 'default_query' : _this.getTitle() } }); }); }, + + getApiTitleKey: function(){ + return wgTitle; + }, + getTitle: function(){ + return wgTitle.replace( 'Sequence:', '').replace('_', ' '); + }, + // Get the api url ( for now use whatever the page context is ) + getApiUrl: function(){ + return mw.absoluteUrl( wgScript.replace('index.php', 'api.php') ); + }, + // Check page type // "view" page Modified: branches/MwEmbedStandAlone/modules/Sequencer/mw.Sequencer.js =================================================================== --- branches/MwEmbedStandAlone/modules/Sequencer/mw.Sequencer.js 2010-08-18 02:52:32 UTC (rev 71237) +++ branches/MwEmbedStandAlone/modules/Sequencer/mw.Sequencer.js 2010-08-18 05:58:07 UTC (rev 71238) @@ -31,7 +31,7 @@ // Draw the sequencer UI seqContainer['sequencer'].drawUI(); - //Return the sequence jquery object + // Return the sequence jquery object return this; } @@ -45,7 +45,11 @@ */ var mw_sequenceedit_default_options = { 'interfaceContainer' : null, + 'title': null, 'smilSource' : null, + 'newSequence' : null, + 'server' : null, + 'addMedia': null, 'videoAspect' : '4:3' } mw.Sequencer = function( options ) { @@ -55,7 +59,9 @@ // Set up the mvSequencer object mw.Sequencer.prototype = { // lazy init id for the sequencer instance. - id: null, + id: null, + // Holds sequencer configuration options + options: {}, init: function( options ){ if(!options){ @@ -64,16 +70,32 @@ // Validate and set default options : for( var optionName in mw_sequenceedit_default_options ){ if( typeof options[ optionName] != 'undefined'){ - this[optionName] = options[ optionName] ; + this.options[optionName] = options[ optionName] ; } else { - this[optionName] = mw_sequenceedit_default_options[ optionName ] + this.options[optionName] = mw_sequenceedit_default_options[ optionName ] } } + + // Move specific options into core + if( this.options.smilSource ){ + this.smilSource = options.smilSource; + } + if( this.options.interfaceContainer ){ + this.interfaceContainer = this.options.interfaceContainer + } + // For style properties assign top level mwe-sequencer class this.getContainer() .addClass('mwe-sequencer'); }, + getOption: function( optionName ){ + if( this.options[ optionName ]){ + return this.options[ optionName ] + } + return false; + }, + // Return the container id for the sequence getId: function(){ if( !this.id ){ @@ -84,16 +106,9 @@ this.id = this.getContainer().attr('id'); } return this.id; - }, + }, /** - * @return smilSource url - */ - getSmilSource: function(){ - return this.smilSource; - }, - - /** * Update the smil xml and then update the interface */ updateSmilXML: function( smilXML ){ @@ -119,12 +134,12 @@ this.getContainer().html( this.getUiLayout() ) + // Once the layout is in the dom setup resizableLayout "layout" options this.applyLayoutBindings(); - + // Add the smil player - //xxx deal with the case of an empty player~~ - this.getPlayer().drawPlayer( function(){ + _this.getPlayer().drawPlayer( function(){ // Once the player and smil is loaded :: // start buffering _this.getEmbedPlayer().load(); @@ -138,7 +153,60 @@ // initialize the edit stack to support undo / redo actions _this.getActionsEdit().setupEditStack(); }); + }, + + /** + * Load a smil source if newSequence flag is set create new sequence source + * @param {function} callback Function called with smilSource + */ + getSmilSource: function( callback ){ + if( !this.smilSource ){ + if( this.getOption( 'newSequence' ) ){ + this.smilSource = this.getDataUrl( this.getNewSmilXML() ); + } else { + mw.log("Load smil source from server") + // Try to load from the server + this.getServer().getSmilXml(function( smilXml ){ + this.smilSource = this.getDataUrl( smilXml ); + callback( this.smilSource ) + }) + // Wait for server to return smil source + return ; + } + } + // return the smilSource + callback( this.smilSource ) + }, + getDataUrl: function( xmlString ){ + return 'data:text/xml;charset=utf-8,' + escape( xmlString ); + }, + getNewSmilXML: function( ){ + var title = ( this.getOption('title') ) ? + this.getOption('title') : + gM('mwe-sequencer-untitled-sequence'); + return '<?xml version="1.0" encoding="UTF-8"?>' + + "\n" + '<smil baseProfile="Language" version="3.0" xmlns="http://www.w3.org/ns/SMIL">' + + "\n\t" + '<head>' + + "\n\t\t" + '<meta name="title" content="' + mw.escapeQuotesHTML( title ) + '" />' + + "\n\t" + '</head>' + + "\n\t" + '<body>' + + "\n\t\t" + '<par>' + + "\n\t\t\t" + '<seq title="Video Track 1" tracktype="video">' + + "\n\t\t\t" + '</seq>' + + "\n\t\t\t" + '<seq title="Audio track 1" tracktype="audio">' + + "\n\t\t\t" + '</seq>' + + "\n\t\t" + '</par>' + + "\n\t" + '</body>' + + "\n" + '</smil>'; + }, + getServer: function(){ + if( !this.server ){ + this.server = new mw.SequencerServer( this ); + } + return this.server; + }, + getMenu: function(){ if( !this.menu){ this.menu = new mw.SequencerMenu( this ); Modified: branches/MwEmbedStandAlone/modules/Sequencer/mw.SequencerPlayer.js =================================================================== --- branches/MwEmbedStandAlone/modules/Sequencer/mw.SequencerPlayer.js 2010-08-18 02:52:32 UTC (rev 71237) +++ branches/MwEmbedStandAlone/modules/Sequencer/mw.SequencerPlayer.js 2010-08-18 05:58:07 UTC (rev 71238) @@ -21,44 +21,36 @@ drawPlayer: function( callback ){ var _this = this; var $playerTarget = this.sequencer.getContainer().find( '.mwseq-player' ); - var smilSource = this.sequencer.getSmilSource() - if( ! smilSource ){ - $playerTarget.append( - gM( 'mwe-sequencer-no-sequence-start-new', - $j('<a />').click(function(){ - alert( 'Browse for assets / start new sequence' ); + + this.sequencer.getSmilSource( function( smilSource ){ + mw.log("SequencePlayer::drawPlayer: Built player target url length:" + smilSource.length ); + // Add the player + $playerTarget.html( + $j('<video />').css( + _this.getPlayerSize() + ).attr({ + 'id' : _this.getSmilPlayerId() + }).append( + $j('<source />').attr({ + 'type' : 'application/smil', + 'src' : smilSource }) ) - ) - return ; - } - - // Else add the player - $playerTarget.html( - $j('<video />').css( - this.getPlayerSize() - ).attr({ - 'id' : this.getSmilPlayerId() - }).append( - $j('<source />').attr({ - 'type' : 'application/smil', - 'src' : smilSource - }) - ) - ); - // Draw the player ( keep the playhead for now ) - // xxx we will eventually replace the playhead with sequence - // based playhead interface for doing easy trims. - $j( '#' + this.getSmilPlayerId() ).embedPlayer({ - 'overlayControls' : false - }, function(){ - // Set the player interface to autoMargin ( need to fix css propagation in embed player) - $j( '#' + _this.getSmilPlayerId() ).parent('.interface_wrap').css('margin', 'auto'); - if( callback ){ - callback(); - } - }) - + ); + + // Draw the player ( keep the playhead for now ) + // xxx we will eventually replace the playhead with sequence + // based playhead interface for doing easy trims. + $j( '#' + _this.getSmilPlayerId() ).embedPlayer({ + 'overlayControls' : false + }, function(){ + // Set the player interface to autoMargin ( need to fix css propagation in embed player) + $j( '#' + _this.getSmilPlayerId() ).parent('.interface_wrap').css('margin', 'auto'); + if( callback ){ + callback(); + } + }) + }); }, previewClip: function( smilClip ){ @@ -93,8 +85,8 @@ var size = {}; var $playerContainer = this.sequencer.getContainer().find('.mwseq-player'); size.width = $playerContainer.width(); - if( this.sequencer.videoAspect ){ - var aspect = this.sequencer.videoAspect.split( ':' ); + if( this.sequencer.options.videoAspect ){ + var aspect = this.sequencer.options.videoAspect.split( ':' ); var apectRatio = ( aspect[1] / aspect[0] ); size.height = parseInt( size.width * ( aspect[1] / aspect[0] ) ); } else { Added: branches/MwEmbedStandAlone/modules/Sequencer/mw.SequencerServer.js =================================================================== --- branches/MwEmbedStandAlone/modules/Sequencer/mw.SequencerServer.js (rev 0) +++ branches/MwEmbedStandAlone/modules/Sequencer/mw.SequencerServer.js 2010-08-18 05:58:07 UTC (rev 71238) @@ -0,0 +1,32 @@ +/** +* Sequencer Server bridges a server API with sequence actions like 'load', 'save', 'revision history' etc. +* ( for now only mediaWiki api is supported ) +* We will abstract all the method calls once we add another api backend +*/ + +//Wrap in mw closure +( function( mw ) { + +mw.SequencerServer = function( sequencer ) { + return this.init( sequencer ); +}; + +// Set up the SequencerServer prototype method object +mw.SequencerServer.prototype = { + init: function( sequencer ){ + this.sequencer = sequencer; + // Set local config from sequencer options + var serverConfig = this.sequencer.getOption( 'server' ); + + // NOTE this should trigger an apiHandler once we have more than one api backend + this.apiType = serverConfig.type; + this.apiUrl = serverConfig.url; + this.titleKey = serverConfig.titleKey; + }, + getSmilXml: function( callback ){ + mw.getTitleText( this.titleKey, callback ) + } +} + + +} )( window.mw ); \ No newline at end of file Modified: branches/MwEmbedStandAlone/modules/Sequencer/mw.SequencerTimeline.js =================================================================== --- branches/MwEmbedStandAlone/modules/Sequencer/mw.SequencerTimeline.js 2010-08-18 02:52:32 UTC (rev 71237) +++ branches/MwEmbedStandAlone/modules/Sequencer/mw.SequencerTimeline.js 2010-08-18 05:58:07 UTC (rev 71238) @@ -357,7 +357,7 @@ */ insertSmilClipEdit: function( smilClip, trackIndex, clipIndex ){ // Handle optional arguments - if( typeof trackIndex != 'undefined' ){ + if( typeof trackIndex == 'undefined' ){ trackIndex = this.getSelectedTrackIndex(); } var $clipTrackSet = $j( '#' + this.getTrackSetId( trackIndex ) ); Modified: branches/MwEmbedStandAlone/modules/SmilPlayer/loader.js =================================================================== --- branches/MwEmbedStandAlone/modules/SmilPlayer/loader.js 2010-08-18 02:52:32 UTC (rev 71237) +++ branches/MwEmbedStandAlone/modules/SmilPlayer/loader.js 2010-08-18 05:58:07 UTC (rev 71238) @@ -38,7 +38,7 @@ ]; // Add smil library set if needed - if( mw.CheckElementForSMIL( playerElement ) ) { + if( mw.CheckElementForSMIL( playerElement ) ) { $j.merge(resourceRequest, smilPlayerLibrarySet); } } ); Modified: branches/MwEmbedStandAlone/modules/SmilPlayer/mw.Smil.js =================================================================== --- branches/MwEmbedStandAlone/modules/SmilPlayer/mw.Smil.js 2010-08-18 02:52:32 UTC (rev 71237) +++ branches/MwEmbedStandAlone/modules/SmilPlayer/mw.Smil.js 2010-08-18 05:58:07 UTC (rev 71238) @@ -41,7 +41,7 @@ transitions : null, // Stores the smil document for this object ( for relative image paths ) - smilUrl : null, + smilContextUrl : null, // The abstract embed player parent embedPlayer : null, @@ -49,6 +49,8 @@ // The jQuery dom object of the smil xml $dom : null, + // Cache for the duration of the smil sequence + duration: null, /** * Constructor * @@ -70,18 +72,34 @@ */ loadFromUrl : function(url, callback) { var _this = this; - this.smilUrl = url; + // Check for data url + var dataUrlKey = 'data:text/xml;charset=utf-8,'; + if( url.indexOf( dataUrlKey ) === 0 ){ + _this.loadFromString( + unescape( url.substr( dataUrlKey.length ) ) + ); + // xxx Note we could in theory have a data url with remote 'context' + // ie cross domain smil loading ( for now assume document.URL context for data urls ) + this.smilContextUrl = document.URL; + callback(); + return ; + } + // Else context url is the remote + this.smilContextUrl = url; mw.log('Smil::loadFromUrl : ' + url); // Try for direct load ( api cross domain loading is handled outside of // SmilInterface - $j.get(url, function(data) { - _this.loadFromString(data); + $j.get(url, function( xmlData) { + _this.loadFromXMLData( xmlData ) // XXX check success or failure - callback(); - }); + callback(); + }); }, - + // Set the $dom from xmlData + loadFromXMLData: function( xmlData ){ + this.$dom = $j( xmlData ); + }, /** * Set smil from xml string * @@ -90,9 +108,12 @@ */ loadFromString: function( smilXmlString ) { // Load the parsed string into the local "dom" - this.$dom = $j( smilXmlString ); + this.$dom = $j( this.getXMLDomObject( smilXmlString ) ); mw.log("Smil::loadFromString: loaded smil dom: " + this.$dom.length + "\n" + smilXmlString ); }, + /** + * Update the smil dom via an xmlString + */ updateFromString: function( smilXmlString ){ delete this.$dom; // jQuery strips some html native tags when parsing xml passed into jQuery @@ -243,7 +264,7 @@ */ getBody : function() { if (!this.body) { - this.body = new mw.SmilBody(this); + this.body = new mw.SmilBody( this ); } return this.body; }, @@ -270,11 +291,12 @@ * Get the duration form the smil body */ getDuration : function( forceRefresh ) { + //mw.log("Smil::getDuration: refresh: " + this.duration + ' refresh:' + forceRefresh); // return 0 while we don't have the $dom loaded if (!this.$dom) { return 0; } - if (!this.duration || forceRefresh ) { + if ( this.duration == null || forceRefresh === true ) { this.duration = this.getBody().getDuration( forceRefresh ); } return this.duration; @@ -343,7 +365,7 @@ */ getAssetUrl : function( assetPath ) { // Context url is the smil document url: - var contextUrl = mw.absoluteUrl(this.smilUrl); + var contextUrl = mw.absoluteUrl(this.smilContextUrl); return mw.absoluteUrl(assetPath, contextUrl); }, Modified: branches/MwEmbedStandAlone/modules/SmilPlayer/mw.SmilBody.js =================================================================== --- branches/MwEmbedStandAlone/modules/SmilPlayer/mw.SmilBody.js 2010-08-18 02:52:32 UTC (rev 71237) +++ branches/MwEmbedStandAlone/modules/SmilPlayer/mw.SmilBody.js 2010-08-18 05:58:07 UTC (rev 71238) @@ -52,7 +52,7 @@ var _this = this; if( !$node.attr('id') && !$node.attr( 'xml:id' ) - ){ + ){ var idString = _this.getNodeSmilType( $node ) + '_' + _this.idIndex; // Make sure the id does not already exist ( should be a rare case ) while( this.getDom().find( '#' + idString ).length != 0 ){ @@ -323,8 +323,7 @@ * ( wraps getDurationRecurse to get top level node duration ) */ getDuration: function( forceRefresh ){ - this.duration = this.getClipDuration( this.getDom() , forceRefresh ); - mw.log("smilBody:: getDuration: " + this.duration ); + this.duration = this.getClipDuration( this.getDom(), forceRefresh ); return this.duration; }, @@ -333,12 +332,18 @@ * @param {jQueryObject} $node * @param {boolean} forceRefresh If a fresh duration should be calculated */ - getClipDuration: function( $node, forceRefresh ){ + getClipDuration: function( $node, forceRefresh ){ + /* + * mw.log( 'SmilBody::getClipDuration: node children::' + + * $node.children().length + 'calle: ' + arguments.callee.toString() ); + */ + if( !forceRefresh && $node.data('computedDuration') != null ) { return $node.data('computedDuration'); } + if( forceRefresh ){ //clear out implictDuration $node.data( 'implictDuration', 0); @@ -437,7 +442,7 @@ */ getNodeSmilType: function( $node ){ var blockType = $j( $node ).get(0).nodeName; - + //mw.log( 'getNodeSmilType for: ' + blockType ); if( this.smilBlockTypeMap[ blockType ] ){ blockType = this.smilBlockTypeMap[ blockType ]; } Modified: branches/MwEmbedStandAlone/modules/SmilPlayer/mw.SmilHooks.js =================================================================== --- branches/MwEmbedStandAlone/modules/SmilPlayer/mw.SmilHooks.js 2010-08-18 02:52:32 UTC (rev 71237) +++ branches/MwEmbedStandAlone/modules/SmilPlayer/mw.SmilHooks.js 2010-08-18 05:58:07 UTC (rev 71238) @@ -43,7 +43,7 @@ // Make sure there is a smil source: if( embedPlayer.mediaElement.getSources( 'application/smil' ).length ){ // Get the first smil source: - mw.log( "Source is: " + embedPlayer.mediaElement.getSources( 'application/smil' )[0].getSrc() ); + //mw.log( "Source is: " + embedPlayer.mediaElement.getSources( 'application/smil' )[0].getSrc() ); // Add the smil engine to the embed player: embedPlayer.smil = new mw.Smil( embedPlayer ); Modified: branches/MwEmbedStandAlone/modules/SmilPlayer/mw.SmilLayout.js =================================================================== --- branches/MwEmbedStandAlone/modules/SmilPlayer/mw.SmilLayout.js 2010-08-18 02:52:32 UTC (rev 71237) +++ branches/MwEmbedStandAlone/modules/SmilPlayer/mw.SmilLayout.js 2010-08-18 05:58:07 UTC (rev 71238) @@ -114,7 +114,7 @@ }, drawElementThumb: function( $target, $node, relativeTime ){ - mw.log('SmilLayout::drawElementThumb: ' + relativeTime ); + mw.log('SmilLayout::drawElementThumb: ' + $node.attr('id') + ' relative time:' + relativeTime ); if( $target.length == 0 ){ mw.log("Error drawElementThumb to empty target"); return ; Modified: branches/MwEmbedStandAlone/mwEmbed.js =================================================================== --- branches/MwEmbedStandAlone/mwEmbed.js 2010-08-18 02:52:32 UTC (rev 71237) +++ branches/MwEmbedStandAlone/mwEmbed.js 2010-08-18 05:58:07 UTC (rev 71238) @@ -1859,7 +1859,8 @@ } return text; }; - + + // Array of setup functions var mwSetupFunctions = []; _______________________________________________ MediaWiki-CVS mailing list MediaWiki-CVS@lists.wikimedia.org https://lists.wikimedia.org/mailman/listinfo/mediawiki-cvs