removed output-disable in dbms-output fetching procedure
[mediawiki.git] / js2 / mwEmbed / mv_embed.js
blob9c29c4c18703c9038907f62ea4116dabf36fa720
1 /*
2  * ~mv_embed ~
3  * For details see: http://metavid.org/wiki/index.php/Mv_embed
4  *
5  * All Metavid Wiki code is released under the GPL2.
6  * For more information visit http://metavid.org/wiki/Code
7  *
8  * @url http://metavid.org
9  *
10  * mw.parseUri:
11  * http://stevenlevithan.com/demo/parseuri/js/
12  *
13  * Config values: you can manually set the location of the mv_embed folder here
14  * (in cases where media will be hosted in a different place than the embedding page)
15  *
16  */
18 /**
19  * AutoLoader paths 
20  * @path The path to the file (or set of files) with ending slash
21  * @gClasses The set of classes
22  *              if it's an array, $j.className becomes jquery.className.js
23  *              if it's an associative object then key => value pairs are used
24  */
25 if ( typeof mvAutoLoadClasses == 'undefined' )
26         mvAutoLoadClasses = { };
28 // The script that loads the class set
29 function lcPaths( classSet ) {
30         for ( var i in classSet ) {
31                 mvAutoLoadClasses[i] = classSet[i];
32         }
35 function mvGetClassPath( k ) {
36         if ( mvAutoLoadClasses[k] ) {
37                 // js_log('got class path:' + k +  ' : '+ mvClassPaths[k]);
38                 return mvAutoLoadClasses[k];
39         } else {
40                 js_log( 'Error:: Could not find path for requested class ' + k );
41                 return false;
42         }
45 if ( typeof mvCssPaths == 'undefined' )
46         mvCssPaths = { };
48 function lcCssPath( cssSet ) {
49         for ( var i in cssSet ) {
50                 mvCssPaths[i] = cssSet[i];
51         }
55  * --  Load Class Paths --
56  *
57  * MUST BE VALID JSON (NOT JS)
58  * This is used by the script loader to auto-load classes (so we only define
59  * this once for PHP & JavaScript)
60  *
61  * Right now the PHP AutoLoader only reads this mv_embed.js file.
62  * In the future we could have multiple lcPath calls that PHP reads
63  * (if our autoloading class list becomes too long) 
64  * or 
65  * we could support direct file requests to the script loader instead 
66  * of shared class names read from a central location.
67  */
68 lcPaths( {
69         "mv_embed"                      : "mv_embed.js",
70         "window.jQuery"         : "jquery/jquery-1.3.2.js",
71         "$j.fn.pngFix"          : "jquery/plugins/jquery.pngFix.js",
72         "$j.fn.autocomplete": "jquery/plugins/jquery.autocomplete.js",
73         "$j.fn.hoverIntent"     : "jquery/plugins/jquery.hoverIntent.js",
74         "$j.fn.datePicker"      : "jquery/plugins/jquery.datePicker.js",
75         "$j.ui"                         : "jquery/jquery.ui/ui/ui.core.js",
76         "$j.fn.ColorPicker"     : "libClipEdit/colorpicker/js/colorpicker.js",
77         "$j.Jcrop"                      : "libClipEdit/Jcrop/js/jquery.Jcrop.js",
78         "$j.fn.simpleUploadForm" : "libAddMedia/simpleUploadForm.js",
79         
80         "mw.proxy"              : "libMwApi/mw.proxy.js",
81         
82         "mw.testLang"   :  "tests/testLang.js",
83         
84         "ctrlBuilder"   : "skins/ctrlBuilder.js",
85         "kskinConfig"   : "skins/kskin/kskin.js",
86         "mvpcfConfig"   : "skins/mvpcf/mvpcf.js",
88         "JSON"                          : "libMwApi/json2.js",
89         "$j.cookie"                     : "jquery/plugins/jquery.cookie.js",
90         "$j.contextMenu"        : "jquery/plugins/jquery.contextMenu.js",
91         "$j.fn.suggestions"     : "jquery/plugins/jquery.suggestions.js",
92         "$j.fn.textSelection"   : "jquery/plugins/jquery.textSelection.js",
94         "$j.effects.blind"              : "jquery/jquery.ui/ui/effects.blind.js",
95         "$j.effects.drop"               : "jquery/jquery.ui/ui/effects.drop.js",
96         "$j.effects.pulsate"    : "jquery/jquery.ui/ui/effects.pulsate.js",
97         "$j.effects.transfer"   : "jquery/jquery.ui/ui/effects.transfer.js",
98         "$j.ui.droppable"               : "jquery/jquery.ui/ui/ui.droppable.js",
99         "$j.ui.slider"                  : "jquery/jquery.ui/ui/ui.slider.js",
100         "$j.effects.bounce"             : "jquery/jquery.ui/ui/effects.bounce.js",
101         "$j.effects.explode"    : "jquery/jquery.ui/ui/effects.explode.js",
102         "$j.effects.scale"              : "jquery/jquery.ui/ui/effects.scale.js",
103         "$j.ui.datepicker"              : "jquery/jquery.ui/ui/ui.datepicker.js",
104         "$j.ui.progressbar"             : "jquery/jquery.ui/ui/ui.progressbar.js",
105         "$j.ui.sortable"                : "jquery/jquery.ui/ui/ui.sortable.js",
106         "$j.effects.clip"               : "jquery/jquery.ui/ui/effects.clip.js",
107         "$j.effects.fold"               : "jquery/jquery.ui/ui/effects.fold.js",
108         "$j.effects.shake"              : "jquery/jquery.ui/ui/effects.shake.js",
109         "$j.ui.dialog"                  : "jquery/jquery.ui/ui/ui.dialog.js",
110         "$j.ui.resizable"               : "jquery/jquery.ui/ui/ui.resizable.js",
111         "$j.ui.tabs"                    : "jquery/jquery.ui/ui/ui.tabs.js",
112         "$j.effects.core"               : "jquery/jquery.ui/ui/effects.core.js",
113         "$j.effects.highlight"  : "jquery/jquery.ui/ui/effects.highlight.js",
114         "$j.effects.slide"              : "jquery/jquery.ui/ui/effects.slide.js",
115         "$j.ui.accordion"               : "jquery/jquery.ui/ui/ui.accordion.js",
116         "$j.ui.draggable"               : "jquery/jquery.ui/ui/ui.draggable.js",
117         "$j.ui.selectable"              : "jquery/jquery.ui/ui/ui.selectable.js",
119         "$j.fn.dragDropFile"            : "libAddMedia/dragDropFile.js",
120         "mvFirefogg"                    : "libAddMedia/mvFirefogg.js",
121         "mvAdvFirefogg"                 : "libAddMedia/mvAdvFirefogg.js",
122         "mvBaseUploadInterface" : "libAddMedia/mvBaseUploadInterface.js",
123         "remoteSearchDriver"    : "libAddMedia/remoteSearchDriver.js",
124         "seqRemoteSearchDriver" : "libSequencer/seqRemoteSearchDriver.js",
126         "baseRemoteSearch"              : "libAddMedia/searchLibs/baseRemoteSearch.js",
127         "mediaWikiSearch"               : "libAddMedia/searchLibs/mediaWikiSearch.js",
128         "metavidSearch"                 : "libAddMedia/searchLibs/metavidSearch.js",
129         "archiveOrgSearch"              : "libAddMedia/searchLibs/archiveOrgSearch.js",
130         "flickrSearch"                  : "libAddMedia/searchLibs/flickrSearch.js",
131         "baseRemoteSearch"              : "libAddMedia/searchLibs/baseRemoteSearch.js",
133         "mvClipEdit"                    : "libClipEdit/mvClipEdit.js",
135         "embedVideo"            : "libEmbedVideo/embedVideo.js",
136         "flowplayerEmbed"       : "libEmbedVideo/flowplayerEmbed.js",
137         "kplayerEmbed"          : "libEmbedVideo/kplayerEmbed.js",
138         "genericEmbed"          : "libEmbedVideo/genericEmbed.js",
139         "htmlEmbed"                     : "libEmbedVideo/htmlEmbed.js",
140         "javaEmbed"                     : "libEmbedVideo/javaEmbed.js",
141         "nativeEmbed"           : "libEmbedVideo/nativeEmbed.js",
142         "quicktimeEmbed"        : "libEmbedVideo/quicktimeEmbed.js",
143         "vlcEmbed"                      : "libEmbedVideo/vlcEmbed.js",
145         "mvPlayList"            : "libSequencer/mvPlayList.js",
146         "mvSequencer"           : "libSequencer/mvSequencer.js",
147         "mvFirefoggRender"      : "libSequencer/mvFirefoggRender.js",
148         "mvTimedEffectsEdit": "libSequencer/mvTimedEffectsEdit.js",
150         "mvTextInterface"       : "libTimedText/mvTextInterface.js",
151         "mvTimeTextEdit"        : "libTimedText/mvTimeTextEdit.js"
152 } );
154 // Dependency mapping for CSS files for self-contained included plugins:
155 lcCssPath( {
156         '$j.Jcrop'                      : 'libClipEdit/Jcrop/css/jquery.Jcrop.css',
157         '$j.fn.ColorPicker'     : 'libClipEdit/colorpicker/css/colorpicker.css'
160 // For use when mv_embed with script-loader is in the root MediaWiki path
161 var mediaWiki_mvEmbed_path = 'js2/mwEmbed/';
163 // The global scope: will be depreciated once we get everything into mw
164 var _global = this;
167 * setup the empty global mw object
168 * will ensure all our functions and variables are properly namespaced
169 * reducing chance of conflicts
171 if ( !window['mw'] ) {
172         window['mw'] = { }
176 // Inherit the default global config
177 var mwDefaultConf = {
178         'skin_name' : 'mvpcf',
179         'jui_skin' : 'redmond',
180         'video_size' : '400x300',
181         'k_attribution' : true          
183 if( !mw.conf )
184         mw.conf = { }
185         
186 for(var i in mwDefaultConf){
187         if( typeof mw.conf[ i ] == 'undefined' )
188                 mw.conf[ i ] = mwDefaultConf[ i ];
192 // @@todo move these into mw
193 var global_req_cb = new Array(); // The global request callback array
196 * The global mw object:
198 * Any global functions/classes that are not jQuery plugins should make
199 * there way into the mw namespace
201 ( function( $ ) {       
202         // list valid skins here:
203         $.valid_skins = ['mvpcf', 'kskin'];
204         // the version of mwEmbed
205         $.version = '1.0r21';
206         
207         // special case of commons api url 
208         // (used for default subtitles server for media with a "wikiTitleKey" atm)
209         // (@@todo eventually we should have  wikiTitleKey be namespaced with interwiki ns
210         $.commons_api_url = 'http://commons.wikimedia.org/w/api.php';
211         /*
212         * some global containers flags 
213         */
214         $.skin_list = new Array();
215         $.init_done = false;
216         $.cb_count = 0;
217         $.player_list = new Array(), // The global player list per page
218         $.req_cb = new Array() // The global request callback array     
220         /*
221         * Language classes mw.lang
222         *
223         * Localized Language support attempts to mirror the functionality of Language.php in MediaWiki
224         * It contains methods for loading and transforming msg text
225         *
226         */
227         $.lang = { };
228         /**
229         * Setup the lang object
230         */
231         var gMsg = { };
232         var gRuleSet = { };
234         /**
235         * loadGM function
236         * Loads a set of json messages into the lng object.
237         *
238         * @param json msgSet The set of msgs to be loaded
239         */
240         $.lang.loadGM = function( msgSet ) {
241                 for ( var i in msgSet ) {
242                         gMsg[ i ] = msgSet[i];
243                 }
244         }
246         /**
247         * loadRS function
248         * Loads a ruleset by given template key ie PLURAL : { //ruleSetObj }
249         *
250         * @param json ruleSet The ruleset object ( extends  gRuleSet )
251         */
252         $.lang.loadRS = function( ruleSet ) {
253                 for ( var i in ruleSet ) {
254                         gRuleSet[ i ] = ruleSet[ i ];
255                 }
256         }
258         /**
259          * Returns a transformed msg string
260          *
261          * it take a msg key and array of replacement values of form
262          * $1, $2 and does relevant msgkey transformation returning
263          * the user msg.
264          *
265          * @param string key The msg key as set by loadGm
266          * @param [mixed] args  An array of replacement strings
267          * @return string
268          */
269         $.lang.gM = function( key , args ) {
270                 if ( ! gMsg[ key ] )
271                         return '<' + key + '>';// Missing key placeholder
273                 // swap in the arg values
274                 var ms =  $.lang.gMsgSwap( key, args );                         
275                 
276                 // a quick check to see if we need to send the msg via the 'parser'
277                 // (we can add more detailed check once we support more wiki syntax)
278                 if ( ms.indexOf( '{{' ) === -1 && ms.indexOf( '[' ) === -1 ) {
279                         return ms;
280                 }
282                 // make sure we have the lagMagic setup:
283                 // @@todo move to init
284                 $.lang.magicSetup();
285                 // send the msg key through the parser
286                 var pObj = $.parser.pNew( ms );
287                 // return the transformed msg
288                 return pObj.getHTML();
289         }
290         /**
291         * gMsgSwap
292         *
293         * @param string key The msg key as set by loadGm
294         * @param [mixed] args  An array or string to be replaced
295         * @return string
296         */
297         $.lang.gMsgSwap = function( key , args ) {
298                 if ( ! gMsg[ key ] )
299                         return '<' + key + '>';// Missing key placeholder
300                 // get the message string:
301                 var ms = gMsg[ key ];
302                 
303                 // replace values
304                 if ( typeof args == 'object' || typeof args == 'array' ) {
305                         for ( var v in args ) {
306                                 // Message test replace arguments start at 1 instead of zero:
307                                 var rep = new RegExp( '\\$' + ( parseInt( v ) + 1 ), 'g' );
308                                 ms = ms.replace( rep, args[v] );
309                         }
310                 } else if ( typeof args == 'string' || typeof args == 'number' ) {
311                         ms = ms.replace( /\$1/g, args );
312                 }
313                 return ms;
314         }
316         /**
317         * gMsgNoTrans
318         *
319         * @returns string The msg key without transforming it
320         */
321         $.lang.gMsgNoTrans = function( key ) {
322                 if ( gMsg[ key ] )
323                         return gMsg[ key ]
325                 // Missing key placeholder
326                 return '<' + key + '>';
327         }
328         /**
329         * Add Supported Magic Words to parser
330         */
331         // Set the setupflag to false:
332         $.lang.doneSetup = false;
333         $.lang.magicSetup = function() {
334                 if ( !$.lang.doneSetup ) {
335                         $.parser.addMagic ( {
336                                 'PLURAL' : $.lang.procPLURAL
337                         } )
339                         $.lang.doneSetup = true;
340                 }
342         }
343         /**
344         * Process the PLURAL special language template key:
345         */
346         $.lang.procPLURAL = function( tObj ) {
347                 // setup shortcuts
348                 // (gRuleSet is loaded from script-loader to contains local ruleset)
349                 var rs = gRuleSet['PLURAL'];
351                 /*
352                  * Plural matchRuleTest
353                  */
354                 function matchRuleTest( cRule, val ) {
355                         js_log("matchRuleTest:: " + typeof cRule + ' ' + cRule + ' == ' + val );
356                         function checkValue( compare, val ) {
357                                 if ( typeof compare == 'string' ) {
358                                         range = compare.split( '-' );
359                                         if ( range.length >= 1 ) {
360                                                 if ( val >= range[0] &&  val <= range[1] )
361                                                         return true;
362                                         }
363                                 }
364                                 // else do a direct compare
365                                 if ( compare == val ) {
366                                         return true;
367                                 }
368                                 return false;
369                         }
370                         // check for simple cRule type:
371                         if ( typeof cRule == 'number' ) {
372                                 return ( parseInt( val ) == parseInt( cRule ) );
373                         } else if ( typeof cRule == 'object' ) {
374                                 var cmatch = { };
375                                 // if a list we need to match all for rule match
376                                 for ( var i in  cRule ) {
377                                         var cr = cRule[i];
378                                         // set cr type
379                                         var crType =  '';
380                                         for ( var j in cr ) {
381                                                 if ( j == 'mod' )
382                                                         crType = 'mod'
383                                         }
384                                         switch( crType ) {
385                                                 case 'mod':
386                                                         if ( cr ['is'] ) {
387                                                                 if ( checkValue( val % cr['mod'], cr ['is'] ) )
388                                                                         cmatch[i] = true;
389                                                         } else if ( cr['not'] ) {
390                                                                 if ( ! checkValue( val % cr['mod'], cr ['not'] ) )
391                                                                         cmatch[i] = true;
392                                                         }
393                                                 break;
394                                         }
395                                 }
396                                 // check all the matches (taking into consideration "or" order)
397                                 for ( var i in cRule ) {
398                                         if ( ! cmatch[i] )
399                                                 return false;
400                                 }
401                                 return true;
403                         }
404                 }
405                 /**
406                  * Maps a given rule Index to template params:
407                  *
408                  * if index is out of range return last param
409                  * @param
410                  */
411                 function getTempParamFromRuleInx( tObj, ruleInx ) {
412                         // js_log('getTempParamFromRuleInx: ruleInx: ' + ruleInx + ' tempParamLength ' + tObj.param.length );
413                         if ( ruleInx    >= tObj.param.length )
414                                 return  tObj.param[  tObj.param.length - 1 ];
415                         // else return the requested index:
416                         return tObj.param[ ruleInx ];
417                 }
418                 var rCount = 0
419                 // run the actual rule lookup:
420                 for ( var ruleInx in rs ) {
421                         cRule = rs[ruleInx];
422                         if ( matchRuleTest( cRule, tObj.arg ) ) {
423                                 js_log("matched rule: " + ruleInx );
424                                 return getTempParamFromRuleInx( tObj, rCount );
425                         }
426                         rCount ++;
427                 }
428                 js_log('no match found for: ' + tObj.arg + ' using last/other : ' +  tObj.param [ tObj.param.length -1 ] );
429                 //debugger;
430                 // return the last /"other" template param
431                 return tObj.param [ tObj.param.length - 1 ];
432         }
434         /**
435          * gMsgLoadRemote loads remote msg strings
436          *
437          * @param mixed msgSet the set of msg to load remotely
438          * @param function callback  the callback to issue once string is ready
439          */
440         $.lang.gMsgLoadRemote = function( msgSet, callback ) {
441                 var ammessages = '';
442                 if ( typeof msgSet == 'object' ) {
443                         for ( var i in msgSet ) {
444                                 ammessages += msgSet[i] + '|';
445                         }
446                 } else if ( typeof msgSet == 'string' ) {
447                         ammessages += msgSet;
448                 }
449                 if ( ammessages == '' ) {
450                         js_log( 'gMsgLoadRemote: no message set requested' );
451                         return false;
452                 }
453                 do_api_req( {
454                         'data': {
455                                 'meta': 'allmessages',
456                                 'ammessages': ammessages
457                         }
458                 }, function( data ) {
459                         if ( data.query.allmessages ) {
460                                 var msgs = data.query.allmessages;
461                                 for ( var i in msgs ) {
462                                         var ld = { };
463                                         ld[ msgs[i]['name'] ] = msgs[i]['*'];
464                                         loadGM( ld );
465                                 }
466                         }
467                         callback();
468                 } );
469         }
470         /**
471          * Format a size in bytes for output, using an appropriate
472          * unit (B, KB, MB or GB) according to the magnitude in question
473          *
474          * @param size Size to format
475          * @return string Plain text (not HTML)
476          */
477         $.lang.formatSize = function ( size ) {
478                 // For small sizes no decimal places are necessary
479                 var round = 0;
480                 var msg = '';
481                 if ( size > 1024 ) {
482                         size = size / 1024;
483                         if ( size > 1024 ) {
484                                 size = size / 1024;
485                                 // For MB and bigger two decimal places are smarter
486                                 round = 2;
487                                 if ( size > 1024 ) {
488                                         size = size / 1024;
489                                         msg = 'mwe-size-gigabytes';
490                                 } else {
491                                         msg = 'mwe-size-megabytes';
492                                 }
493                         } else {
494                                 msg = 'mwe-size-kilobytes';
495                         }
496                 } else {
497                         msg = 'mwe-size-bytes';
498                 }
499                 // JavaScript does not let you choose the precision when rounding
500                 var p = Math.pow( 10, round );
501                 var size = Math.round( size * p ) / p;
502                 return gM( msg , size );
503         };
504         
505         $.lang.formatNumber = function( num ) {
506                 /*
507                 *       addSeparatorsNF
508                 * @param Str: The number to be formatted, as a string or number.                
509                 * @param outD: The decimal character for the output, such as ',' for the number 100,2
510                 * @param sep: The separator character for the output, such as ',' for the number 1,000.2
511                 */
512                 function addSeparatorsNF( nStr, outD, sep ) {
513                         nStr += '';
514                         var dpos = nStr.indexOf( '.' );
515                         var nStrEnd = '';
516                         if ( dpos != -1 ) {
517                                 nStrEnd = outD + nStr.substring( dpos + 1, nStr.length );
518                                 nStr = nStr.substring( 0, dpos );
519                         }
520                         var rgx = /(\d+)(\d{3})/;
521                         while ( rgx.test( nStr ) ) {
522                                 nStr = nStr.replace( rgx, '$1' + sep + '$2' );
523                         }
524                         return nStr + nStrEnd;
525                 }
526                 // @@todo read language code and give periods or comas: 
527                 return addSeparatorsNF( num, '.', ',' );
528         }
529         
530         
531         
532         /**
533         * MediaWiki wikitext "Parser"
534         *
535         * This is not feature complete but we need a way to get at template properties
536         *
537         *
538         * @param {String} wikiText the wikitext to be parsed
539         * @return {Object} parserObj returns a parser object that has methods for getting at
540         * things you would want
541         */
542         $.parser = { };
543         var pMagicSet = { };
544         /**
545          * parser addMagic
546          *
547          * Lets you add a set of magic keys and associated callback functions
548          *
549          * @param object magicSet key:callback
550          */
551         $.parser.addMagic = function( magicSet ) {
552                 for ( var i in magicSet )
553                         pMagicSet[ i ] = magicSet[i];
554         }
556         // actual parse call (returns parser object)
557         $.parser.pNew = function( wikiText, opt ) {
558                 var parseObj = function( wikiText, opt ) {
559                         return this.init( wikiText, opt )
560                 }
561                 parseObj.prototype = {
562                         // the wikiText "DOM"... stores the parsed wikiText structure
563                         // wtDOM : {}, (not yet supported )
565                         pOut : '', // the parser output string container
566                         init  :function( wikiText ) {
567                                 this.wikiText = wikiText;
568                         },
569                         updateText : function( wikiText ) {
570                                 this.wikiText = wikiText;
571                                 // invalidate the output (will force a re-parse )
572                                 this.pOut = '';
573                         },
574                         parse : function() {
575                                 /*
576                                  * quickly recursive / parse out templates:
577                                  */
579                                 // ~ probably a better algorithm out there / should mirror php parser flow ~
580                                 //       (we are already running white-space issues ie php parse strips whitespace differently)
581                                 // or at least expose something similar to: http://www.mediawiki.org/wiki/Extension:Page_Object_Model
583                                 // ... but I am having fun with recursion so here it is...
584                                 function rdpp ( txt , cn ) {
585                                         var node = { };
586                                         // inspect each char
587                                         for ( var a = 0; a < txt.length; a++ ) {
588                                                 if ( txt[a] == '{' && txt[a + 1] == '{' ) {
589                                                         a = a + 2;
590                                                         node['p'] = node;
591                                                         if ( !node['c'] )
592                                                                 node['c'] = new Array();
594                                                         node['c'].push( rdpp( txt.substr( a ), true ) );
595                                                 } else if ( txt[a] == '}' && txt[a + 1] == '}' ) {
596                                                         a = a + 2;
597                                                         if ( !node['p'] ) {
598                                                                 return node;
599                                                         }
600                                                         node = node['p'];
601                                                 }
602                                                 if ( !node['t'] )
603                                                         node['t'] = '';
604                                                 // don't put closures into output:
605                                                 if ( txt[a] &&  txt[a] != '}' )
606                                                                 node['t'] += txt[a];
607                                                                 
608                                         }
609                                         return node;
610                                 }
611                                 /**
612                                  * parse template text as template name and named params
613                                  */
614                                 function parseTmplTxt( ts ) {
615                                         var tObj = { };
616                                         // Get template name:
617                                         tname = ts.split( '\|' ).shift() ;
618                                         tname = tname.split( '\{' ).shift() ;
619                                         tname = tname.replace( /^\s+|\s+$/g, "" ); //trim
621                                         // check for arguments:
622                                         if ( tname.split( ':' ).length == 1 ) {
623                                                 tObj["name"] = tname;
624                                         } else {
625                                                 tObj["name"] = tname.split( ':' ).shift();
626                                                 tObj["arg"] = tname.split( ':' ).pop();
627                                         }
628                                                                                 
629                                         var pSet = ts.split( '\|' );
630                                         pSet.splice( 0, 1 );
631                                         if ( pSet.length ) {
632                                                 tObj.param = new Array();
633                                                 for ( var pInx in pSet ) {
634                                                         var tStr = pSet[ pInx ];
635                                                         // check for empty param
636                                                         if ( tStr == '' ) {
637                                                                 tObj.param[ pInx ] = '';
638                                                                 continue;
639                                                         }
640                                                         for ( var b = 0 ; b < tStr.length ; b++ ) {
641                                                                 if ( tStr[b] == '=' && b > 0 && b < tStr.length && tStr[b - 1] != '\\' ) {
642                                                                         // named param
643                                                                         tObj.param[ tStr.split( '=' ).shift() ] =       tStr.split( '=' ).pop();
644                                                                 } else {
645                                                                         // indexed param
646                                                                         tObj.param[ pInx ] = tStr;
647                                                                 }
648                                                         }
649                                                 }
650                                         }
651                                         return tObj;
652                                 }
653                                 function getMagicTxtFromTempNode( node ) {
654                                         node.tObj = parseTmplTxt ( node.t );
655                                         // do magic swap if template key found in pMagicSet
656                                         if ( node.tObj.name in pMagicSet ) {
657                                                 var nt = pMagicSet[ node.tObj.name ]( node.tObj );
658                                                 return nt;
659                                         } else {
660                                                 // don't swap just return text
661                                                 return node.t;
662                                         }
663                                 }
664                                 /**
665                                  * recurse_magic_swap
666                                  *
667                                  * go last child first swap upward: (could probably be integrated above somehow)
668                                  */
669                                 var pNode = null;
670                                 function recurse_magic_swap( node ) {
671                                         if ( !pNode )
672                                                 pNode = node;
674                                         if ( node['c'] ) {
675                                                 // swap all the kids:
676                                                 for ( var i in node['c'] ) {
677                                                         var nt = recurse_magic_swap( node['c'][i] );
678                                                         // swap it into current
679                                                         if ( node.t ) {
680                                                                 node.t = node.t.replace( node['c'][i].t, nt );
681                                                         }
682                                                         // swap into parent
683                                                         pNode.t  = pNode.t.replace( node['c'][i].t, nt );
684                                                 }
685                                                 // do the current node:
686                                                 var nt = getMagicTxtFromTempNode( node );
687                                                 pNode.t = pNode.t.replace( node.t , nt );
688                                                 // run the swap for the outer most node
689                                                 return node.t;
690                                         } else {
691                                                 // node.t = getMagicFromTempObj( node.t )
692                                                 return getMagicTxtFromTempNode( node );
693                                         }
694                                 }
695                                 // parse out the template node structure:
696                                 this.pNode = rdpp ( this.wikiText );
697                                 // strip out the parent from the root   
698                                 this.pNode['p'] = null;
699                                 
700                                 // do the recursive magic swap text:
701                                 this.pOut = recurse_magic_swap( this.pNode );
703                         },                      
704                         /*
705                          * parsed template api ~loosely based off of ~POM~
706                          * http://www.mediawiki.org/wiki/Extension:Page_Object_Model
707                          */
708                         
709                         /**
710                          * templates
711                          * 
712                          * gets a requested template from the wikitext (if available)
713                          *  
714                          */
715                         templates: function( tname ) {
716                                 this.parse();
717                                 var tmplSet = new Array();
718                                 function getMatchingTmpl( node ) {
719                                         if ( node['c'] ) {
720                                                 for ( var i in node['c'] ) {
721                                                         getMatchingTmpl( node['c'] );
722                                                 }
723                                         }
724                                         if ( tname && node.tObj ) {
725                                                 if ( node.tObj['name'] == tname )
726                                                         tmplSet.push( node.tObj );
727                                         } else if ( node.tObj ) {
728                                                 tmplSet.push( node.tObj );
729                                         }
730                                 }
731                                 getMatchingTmpl( this.pNode );
732                                 return tmplSet;
733                         },
734                         /**
735                          * Returns the transformed wikitext
736                          * 
737                          * Build output from swapable index 
738                          *              (all transforms must be expanded in parse stage and linearly rebuilt)  
739                          * Alternatively we could build output using a place-holder & replace system 
740                          *              (this lets us be slightly more sloppy with ordering and indexes, but probably slower)
741                          * 
742                          * Ideal: we build a 'wiki DOM' 
743                          *              When editing you update the data structure directly
744                          *              Then in output time you just go DOM->html-ish output without re-parsing anything                           
745                          */
746                         getHTML : function() {
747                                 // wikiText updates should invalidate pOut
748                                 if ( this.pOut == '' ) {
749                                         this.parse();
750                                 }
751                                 return this.pOut;
752                         }
753                 };
754                 // return the parserObj
755                 return new parseObj( wikiText, opt ) ;
756         }
757                 
758         /* 
759         * API and request functions
760         */
761         $.getLocalApiUrl = function() {
762                 if ( typeof wgServer != 'undefined' && typeof wgScriptPath  != 'undefined' ) {
763                         return wgServer + wgScriptPath + '/api.php';
764                 }
765                 return false;
766         }
767         
768         /**
769         * Utility Functions
770         */
771                 
772         
773         /**
774         * parseUri 1.2.2
775         * (c) Steven Levithan <stevenlevithan.com>
776         *  MIT License
777         */              
778         $.parseUri = function (str) {
779                 var     o   = $.parseUri.options,
780                         m   = o.parser[o.strictMode ? "strict" : "loose"].exec(str),
781                         uri = {},
782                         i   = 14;
783         
784                 while (i--) uri[o.key[i]] = m[i] || "";
785         
786                 uri[o.q.name] = {};
787                 uri[o.key[12]].replace(o.q.parser, function ($0, $1, $2) {
788                         if ($1) uri[o.q.name][$1] = $2;
789                 });
790         
791                 return uri;
792         };
793         $.parseUri.options = {
794                 strictMode: false,
795                 key: ["source","protocol","authority","userInfo","user","password","host","port","relative","path","directory","file","query","anchor"],
796                 q:   {
797                         name:   "queryKey",
798                         parser: /(?:^|&)([^&=]*)=?([^&]*)/g
799                 },
800                 parser: {
801                         strict: /^(?:([^:\/?#]+):)?(?:\/\/((?:(([^:@]*)(?::([^:@]*))?)?@)?([^:\/?#]*)(?::(\d*))?))?((((?:[^?#\/]*\/)*)([^?#]*))(?:\?([^#]*))?(?:#(.*))?)/,
802                         loose:  /^(?:(?![^:@]+:[^:@\/]*@)([^:\/?#.]+):)?(?:\/\/)?((?:(([^:@]*)(?::([^:@]*))?)?@)?([^:\/?#]*)(?::(\d*))?)(((\/(?:[^?#](?![^?#\/]*\.[^?#\/.]+(?:[?#]|$)))*\/?)?([^?#\/]*))(?:\?([^#]*))?(?:#(.*))?)/
803                 }
804         };      
805         
806         /*
807         * getAbsoluteUrl takes a src and returns the aboluste location given the document.URL
808         * @param {String} src path or url
809         */
810         $.absoluteUrl = function( src ){
811                 var pSrc =  mw.parseUri( src );
812                 if( pSrc.protocol != '')
813                         return src;                             
814                 
815                 // Get  the document path               
816                 var pDoc = mw.parseUri( document.URL );
817                 // If a leading slash:  
818                 if( src.indexOf( '/' ) == 1 ){
819                         return pDoc.protocol + '://' + pDoc.authority + src;
820                 }else{
821                         return pDoc.protocol + '://' + pDoc.authority + pDoc.directory + src;
822                 }
823         };
824         /**
825         * Takes in a string returns an xml dom object 
826         */
827         $.parseXML = function ( str ){
828                 if ( $j.browser.msie ) {
829                         // Attempt to parse as XML for IE
830                         var xmldata = new ActiveXObject( "Microsoft.XMLDOM" );
831                         xmldata.async = "false";
832                         try{
833                                 xmldata.loadXML( str );
834                                 return xmldata;
835                         } catch (e){
836                                 js_log( 'XML parse ERROR: ' + e.message );
837                                 return false;
838                         }
839                 }
840                 
841                 // For others (Firefox, Safari etc, older browsers 
842                 // Some don't have native DOMParser either fallback defined bellow.
843                 try {
844                         var xmldata = ( new DOMParser() ).parseFromString( str, "text/xml" );
845                 } catch ( e ) {
846                         js_log( 'XML parse ERROR: ' + e.message );
847                         return false;
848                 }               
849                 return xmldata;
850         }       
851 } )( window.mw );
853 // Get the mv_embed location if it has not been set
854 if ( !mv_embed_path ) {
855         var mv_embed_path = getMvEmbedPath();
858 // load in js2 stopgap into proper location: 
859 if ( typeof gMsg != 'undefined' ) {
860         mw.lang.loadGM( gMsg )
863 // setup legacy global shortcuts:
864 var loadGM = mw.lang.loadGM;
865 var loadRS = mw.lang.loadRS;
866 var gM = mw.lang.gM;
868 // All default messages in [English] should be overwritten by the CMS language message system.
869 mw.lang.loadGM( {
870         "mwe-loading_txt" : "Loading ...",
871         "mwe-size-gigabytes" : "$1 GB",
872         "mwe-size-megabytes" : "$1 MB",
873         "mwe-size-kilobytes" : "$1 K",
874         "mwe-size-bytes" : "$1 B",
875         "mwe-error_load_lib" : "Error: JavaScript $1 was not retrievable or does not define $2",
876         "mwe-loading-add-media-wiz" : "Loading add media wizard",
877         "mwe-apiproxy-setup" : "Setting up API proxy",
878         "mwe-load-drag-item" : "Loading dragged item",
879         "mwe-ok" : "OK",
880         "mwe-cancel" : "Cancel"
881 } );
884 // Get the loading image
885 function mv_get_loading_img( style, class_attr ) {
886         var style_txt = ( style ) ? style:'';
887         var class_attr = ( class_attr ) ? 'class="' + class_attr + '"' : 'class="mv_loading_img"';
888         return '<div ' + class_attr + ' style="' + style + '"></div>';
891 function mv_set_loading( target, load_id ) {
892         var id_attr = ( load_id ) ? ' id="' + load_id + '" ':'';
893         $j( target ).append( '<div ' + id_attr + ' style="position:absolute;top:0px;left:0px;height:100%;width:100%;' +
894                 'background-color:#FFF;">' +
895                         mv_get_loading_img( 'top:30px;left:30px' ) +
896                 '</div>' );
900 * mvJsLoader class handles initialization and js file loads
902 var mvJsLoader = {
903         libreq : { },
904         libs : { },
906         // Base lib flags
907         onReadyEvents: new Array(),
908         doneReadyEvents: false,
909         jQuerySetupFlag: false,
911         // To keep consistency across threads
912         ptime: 0,
913         ctime: 0,
915         load_error: false, // Load error flag (false by default)
916         load_time: 0,
917         callbacks: new Array(),
918         cur_path: null,
919         missing_path : null,
920         doLoad: function( loadLibs, callback ) {
921                 this.ctime++;
922                 if ( loadLibs && loadLibs.length != 0 ) {
923                         // js_log("doLoad setup::" + JSON.stringify( loadLibs ) );
924                         // Set up this.libs
925                         // First check if we already have this library loaded
926                         var all_libs_loaded = true;
927                         for ( var i = 0; i < loadLibs.length; i++ ) {
928                                 // Check if the library is already loaded
929                                 if ( ! this.checkObjPath( loadLibs[i] ) ) {
930                                         all_libs_loaded = false;
931                                 }
932                         }
933                         if ( all_libs_loaded ) {
934                                 js_log( 'Libraries ( ' + loadLibs  +  ') already loaded... skipping load request' );
935                                 callback();
936                                 return;
937                         }
938                         
939                         // Do a check for any CSS we may need and get it
940                         for ( var i = 0; i < loadLibs.length; i++ ) {
941                                 if ( typeof mvCssPaths[ loadLibs[i] ] != 'undefined' ) {
942                                         loadExternalCss( mv_embed_path + mvCssPaths[ loadLibs[i] ] );
943                                 }
944                         }
946                         // Check if we should use the script loader to combine all the requests into one
947                         // ( the scriptloader defines the mwSlScript global )
948                         if ( typeof mwSlScript != 'undefined' ) {
949                                 var class_set = '';
950                                 var last_class = '';
951                                 var coma = '';
952                                 for ( var i = 0; i < loadLibs.length; i++ ) {
953                                         var curLib = loadLibs[i];
954                                         // Only add if not included yet:
955                                         if ( ! this.checkObjPath( curLib ) ) {
956                                                 class_set += coma + curLib;
957                                                 last_class = curLib;
958                                                 coma = ',';
959                                         }
960                                 }
961                                 // Build the url to the scriptServer striping its request parameters:
962                                 var puri = mw.parseUri( getMvEmbedURL() );
963                                 if ( ( getMvEmbedURL().indexOf( '://' ) != -1 )
964                                         && puri.host != mw.parseUri( document.URL ).host )
965                                 {
966                                         var scriptPath = puri.protocol + '://' + puri.authority + puri.path;
967                                 } else {
968                                         var scriptPath = puri.path;
969                                 }
970                                 // js_log('scriptServer Path is: ' + scriptPath + "\n host script path:" + getMvEmbedURL() );                           
971                                 this.libs[ last_class ] = scriptPath + '?class=' + class_set +
972                                         '&' + getMwReqParam();
974                         } else {
975                                 // Do many requests
976                                 for ( var i = 0; i < loadLibs.length; i++ ) {
977                                         var curLib = loadLibs[i];
978                                         if ( curLib ) {
979                                                 var libLoc = mvGetClassPath( curLib );
980                                                 // Do a direct load of the file (pass along unique request id from
981                                                 // request or mv_embed Version )
982                                                 var qmark = ( libLoc.indexOf( '?' ) !== true ) ? '?' : '&';
983                                                 this.libs[curLib] = mv_embed_path + libLoc + qmark + getMwReqParam();
984                                         }
985                                 }
986                         }
987                 }
988                 
989                 if ( callback ) {
990                         this.callbacks.push( callback );
991                 }
992                 if ( this.checkLoading() ) {
993                         // @@todo we should check the <script> Element .onLoad property to
994                         // make sure its just not a very slow connection
995                         // (even though the class is not loaded)
996                         if ( this.load_time++ > 4000 ) { // Time out after ~80 seconds
997                                 js_log( gM( 'mwe-error_load_lib', [mvGetClassPath( this.missing_path ),  this.missing_path] ) );
998                                 this.load_error = true;
999                         } else {
1000                                 setTimeout( 'mvJsLoader.doLoad()', 20 );
1001                         }
1002                 } else {
1003                         // js_log('checkLoading passed. Running callbacks...');
1004                         // Only do callbacks if we are in the same instance (weird concurrency issue)
1005                         var cb_count = 0;
1006                         for ( var i = 0; i < this.callbacks.length; i++ )
1007                                 cb_count++;
1008                         // js_log('RESET LIBS: loading is: '+ loading + ' callback count: '+cb_count +
1009                         //      ' p:'+ this.ptime +' c:'+ this.ctime);
1011                         // Reset the libs
1012                         this.libs = { };
1013                         // js_log('done loading, do call: ' + this.callbacks[0] );
1014                         while ( this.callbacks.length != 0 ) {
1015                                 if ( this.ptime == this.ctime - 1 ) { // Enforce thread consistency
1016                                         this.callbacks.pop()();
1017                                         // func = this.callbacks.pop();
1018                                         // js_log(' run: '+this.ctime+ ' p: ' + this.ptime + ' ' +loading+ ' :'+ func);
1019                                         // func();
1020                                 } else {
1021                                         // Re-issue doLoad ( ptime will be set to ctime so we should catch up)
1022                                         setTimeout( 'mvJsLoader.doLoad()', 25 );
1023                                         break;
1024                                 }
1025                         }
1026                 }
1027                 this.ptime = this.ctime;
1028         },
1029         doLoadDepMode: function( loadChain, callback ) {
1030                 // Firefox executes JS in the order in which it is included, so just directly issue the request
1031                 if ( $j.browser.firefox ) {
1032                         var loadSet = [];
1033                         for ( var i = 0; i < loadChain.length; i++ ) {
1034                                 for ( var j = 0; j < loadChain[i].length; j++ ) {
1035                                         loadSet.push( loadChain[i][j] );
1036                                 }
1037                         }
1038                         mvJsLoader.doLoad( loadSet, callback );
1039                 } else {
1040                         // Safari and IE tend to execute out of order so load with dependency checks
1041                         mvJsLoader.doLoad( loadChain.shift(), function() {
1042                                 if ( loadChain.length != 0 ) {
1043                                         mvJsLoader.doLoadDepMode( loadChain, callback );
1044                                 } else {
1045                                         callback();
1046                                 }
1047                         } );
1048                 }
1049         },
1050         checkLoading: function() {
1051                 var loading = 0;
1052                 var i = null;
1053                 for ( var i in this.libs ) { // for/in loop is OK on an object
1054                         if ( !this.checkObjPath( i ) ) {
1055                                 if ( !this.libreq[i] ) {
1056                                         loadExternalJs( this.libs[i] );
1057                                 }
1058                                 this.libreq[i] = 1;
1059                                 // js_log("has not yet loaded: " + i);
1060                                 loading = 1;
1061                         }
1062                 }
1063                 return loading;
1064         },
1065         checkObjPath: function( libVar ) {
1066                 if ( !libVar )
1067                         return false;
1068                 var objPath = libVar.split( '.' )
1069                 var cur_path = '';
1070                 for ( var p = 0; p < objPath.length; p++ ) {
1071                         cur_path = ( cur_path == '' ) ? cur_path + objPath[p] : cur_path + '.' + objPath[p];
1072                         eval( 'var ptest = typeof ( ' + cur_path + ' ); ' );
1073                         if ( ptest == 'undefined' ) {
1074                                 this.missing_path = cur_path;
1075                                 return false;
1076                         }
1077                 }
1078                 this.cur_path = cur_path;
1079                 return true;
1080         },
1081         /**
1082          * checks for jQuery and adds the $j noConflict var
1083          */
1084         jQueryCheck: function( callback ) {
1085                 // js_log( 'jQueryCheck::' + this.jQuerySetupFlag);
1086                 var _this = this;
1087                 if ( _global['$j'] && _this.jQuerySetupFlag ) {                 
1088                         callback(); // call the callback now                    
1089                 }
1090                 // Load jQuery
1091                 _this.doLoad( [
1092                         'window.jQuery'
1093                 ], function() {
1094                         // only do the $j setup once:
1095                         if ( !_global['$j'] ) {
1096                                 _global['$j'] = jQuery.noConflict();
1097                         }
1098                         if ( _this.jQuerySetupFlag == false ) {
1099                                 // js_log('setup mv_embed jQuery bindings');
1100                                 // Setup our global settings using the (jQuery helper)
1102                                 // Set up the skin path
1103                                 _global['mv_jquery_skin_path'] = mv_embed_path + 'jquery/jquery.ui/themes/' + mw.conf['jui_skin'] + '/';
1104                                 _global['mv_skin_img_path'] = mv_embed_path + 'skins/' + mw.conf['skin_name'] + '/images/';
1105                                 _global['mv_default_thumb_url'] = mv_skin_img_path + 'vid_default_thumb.jpg';
1107                                 // Make sure the skin/style sheets are always available:
1108                                 loadExternalCss( mv_jquery_skin_path + 'jquery-ui-1.7.1.custom.css' );
1109                                 loadExternalCss( mv_embed_path + 'skins/' + mw.conf['skin_name'] + '/styles.css' );
1111                                 // Set up AJAX to not send dynamic URLs for loading scripts (we control that with
1112                                 // the scriptLoader)
1113                                 $j.ajaxSetup( {
1114                                         cache: true
1115                                 } );
1117                                 js_log( 'jQuery loaded into $j' );
1118                                 // Set up mvEmbed jQuery bindings and config based dependencies
1119                                 mv_jqueryBindings();
1120                                 _this.jQuerySetupFlag = true;
1121                                 
1122                                 // js_log('should run callback: ' + callback);
1123                                 // Run the callback if not already run above
1124                                 if ( callback ) {
1125                                         callback();
1126                                 }
1127                         }
1128                 } );
1129         },
1130         embedVideoCheck:function( callback ) {
1131                 var _this = this;
1132                 js_log( 'embedVideoCheck:' );
1133                 // Make sure we have jQuery
1134                 _this.jQueryCheck( function() {
1135                         // set class videonojs to loading
1136                         $j( '.videonojs' ).html( gM( 'mwe-loading_txt' ) );
1137                         // Set up the embed video player class request: (include the skin js as well)
1138                         var depReq = [
1139                                 [
1140                                         '$j.ui',
1141                                         'embedVideo',
1142                                         'ctrlBuilder',
1143                                         '$j.cookie'
1144                                 ],
1145                                 [
1146                                         '$j.ui.slider'
1147                                 ]
1148                         ];
1149                         
1150                         // add any requested skins (supports multiple skins per single page)
1151                         if ( mw.skin_list ) {
1152                                 for ( var i in mw.skin_list  ) {
1153                                         depReq[0].push( mw.skin_list[i] + 'Config' );
1154                                 }
1155                         }
1157                         // Add PNG fix if needed:
1158                         if ( $j.browser.msie || $j.browser.version < 7 )
1159                                 depReq[0].push( '$j.fn.pngFix' );
1161                         // load the video libs:
1162                         _this.doLoadDepMode( depReq, function() {
1163                                 embedTypes.init();
1164                                 callback();
1165                                 $j( '.videonojs' ).remove();
1166                         } );
1167                 } );
1168         },
1169         addLoadEvent: function( fn ) {
1170                 // js_log('add ready event: ' + fn );
1171                 this.onReadyEvents.push( fn );
1172         },
1173         // Check the jQuery flag. This way, when remote embedding, we don't load jQuery
1174         // unless js2AddOnloadHook was used or there is video on the page.
1175         runQueuedFunctions: function() {
1176                 js_log( "runQueuedFunctions" );
1177                 var _this = this;
1178                 this.jQueryCheck( function() {
1179                         _this.runReadyEvents();
1180                         _this.doneReadyEvents = true;
1181                 } );
1182         },
1183         runReadyEvents: function() {
1184                 js_log( "runReadyEvents" +  this.onReadyEvents.length );
1185                 while ( this.onReadyEvents.length ) {
1186                         var func = this.onReadyEvents.shift();
1187                         // js_log('run onReady:: ' + func );
1188                         func();
1189                 }
1190         }
1193 // Shortcut ( @@todo consolidate shortcuts & re-factor mvJsLoader )
1194 function mwLoad( loadSet, callback ) {
1195         mvJsLoader.doLoad( loadSet, callback );
1197 // mw.shortcut
1198 mw.load = mwLoad;
1200 // Load an external JS file. Similar to jquery .require plugin,
1201 // but checks for object availability rather than load state.
1203 /*********** INITIALIZATION CODE *************
1204  * This will get called when the DOM is ready
1205  *********************************************/
1206 /* jQuery .ready does not work when jQuery is loaded dynamically.
1207  * For an example of the problem see: 1.1.3 working: http://pastie.caboo.se/92588
1208  * and >= 1.1.4 not working: http://pastie.caboo.se/92595
1209  * $j(document).ready( function(){ */
1210 function mwdomReady( force ) {
1211         js_log( 'f:mwdomReady:' );
1212         if ( !force && mw.init_done ) {
1213                 js_log( "mw done, do nothing..." );
1214                 return false;
1215         }
1216         mw.init_done = true;
1217         // Handle the execution of queued functions with jQuery "ready"
1219         // Check if this page has a video, audio or playlist tag
1220         var e = [
1221                 document.getElementsByTagName( "video" ),
1222                 document.getElementsByTagName( "audio" ),
1223                 document.getElementsByTagName( "playlist" )
1224         ];
1225         if ( e[0].length != 0 || e[1].length != 0 || e[2].length != 0 ) {
1226                 // look for any skin classes we have to load: 
1227                 for ( var j in e ) {
1228                         for ( var k in e[j] ) {
1229                                 if ( e[j][k] && typeof( e[j][k] ) == 'object' ) {
1230                                         var     sn = e[j][k].getAttribute( 'class' );
1231                                         // Try "className" for good old IE                              
1232                                         if( !sn ){
1233                                                 var     sn = e[j][k].getAttribute( 'className' );
1234                                         }                                       
1235                                         if ( sn && sn != '' ) {
1236                                                 for ( var n = 0; n < mw.valid_skins.length; n++ ) {
1237                                                         if ( sn.indexOf( mw.valid_skins[n] ) !== -1 ) {
1238                                                                 mw.skin_list.push( mw.valid_skins[n] );
1239                                                         }
1240                                                 }
1241                                         }
1242                                 }
1243                         }
1244                 }
1245                 // Load libs and process videos
1246                 mvJsLoader.embedVideoCheck( function() {
1247                         // Run any queued global events:
1248                         mv_video_embed( function() {
1249                                 mvJsLoader.runQueuedFunctions();
1250                         } );
1251                 } );
1252         } else {
1253                 mvJsLoader.runQueuedFunctions();
1254         }
1257 // js2AddOnloadHook: ensure jQuery and the DOM are ready
1258 function js2AddOnloadHook( func ) {
1259         // js_log('js2AddOnloadHook:: jquery:' +func);  
1260         // If we are ready run directly else add load event: 
1261         if ( mvJsLoader.doneReadyEvents ) {
1262                 //js_log( 'run queued event: ' + func );
1263                 func();
1264         } else {
1265                 //js_log( 'add to load event: ' + func );
1266                 mvJsLoader.addLoadEvent( func );
1267         }
1269 // Deprecated mwAddOnloadHook in favour of js2 naming (for clear separation of js2 code from old MW code
1270 var mwAddOnloadHook = js2AddOnloadHook;
1272  * This function allows for targeted rewriting
1273  */
1274 function rewrite_by_id( vid_id, ready_callback ) {
1275         js_log( 'f:rewrite_by_id: ' + vid_id );
1276         // Force a re-check of the DOM for playlist or video elements:
1277         mvJsLoader.embedVideoCheck( function() {
1278                 mv_video_embed( ready_callback, vid_id );
1279         } );
1283 /*********** INITIALIZATION CODE *************
1284  * set DOM-ready callback to init_mv_embed
1285  *********************************************/
1286 // for Mozilla / modern browsers
1287 if ( document.addEventListener ) {
1288         document.addEventListener( "DOMContentLoaded", mwdomReady, false );
1290 var temp_f;
1291 if ( window.onload ) {
1292     temp_f = window.onload;
1294 // Use the onload method as a backup
1295 window.onload = function () {
1296     if ( temp_f )
1297         temp_f();
1298         mwdomReady();
1302  * Store all the mwEmbed jQuery-specific bindings
1303  * (set up after jQuery is available).
1305  * These functions are generally are loaders that do the dynamic mapping of
1306  * dependencies for a given component
1307  * 
1309  */
1310 function mv_jqueryBindings() {
1311         js_log( 'mv_jqueryBindings' );
1312         ( function( $ ) {
1313                 /*
1314                 * dragDrop file loader 
1315                 */
1316                 $.fn.dragFileUpload = function ( conf ) {
1317                         if ( this.selector ) {
1318                                 var _this = this;
1319                                 // load the dragger and "setup"
1320                                 mw.load( ['$j.fn.dragDropFile'], function() {
1321                                         $j( _this.selector ).dragDropFile();
1322                                 } );
1323                         }
1324                 }
1325                 /*
1326                  * apiProxy Loader loader:
1327                  * 
1328                  * @param mode is either 'server' or 'client'
1329                  */
1330                 $.apiProxy = function( mode, pConf, callback ) {
1331                         js_log( 'do apiProxy setup' );
1332                         mvJsLoader.doLoad( [
1333                                 'mw.proxy',
1334                                 'JSON'
1335                         ], function() {
1336                                 // do the proxy setup or 
1337                                 if ( mode == 'client' ) {
1338                                         // just do the setup (no callbcak for client setup) 
1339                                         mw.proxy.client( pConf );
1340                                         if ( callback )
1341                                                 callback();
1342                                 } else if ( mode == 'server' ) {
1343                                         // do the request with the callback
1344                                         mw.proxy.server( pConf , callback );
1345                                 }
1346                         } );
1347                 }
1348                 
1349                 // non selector based add-media-wizard direct invocation with loader
1350                 $.addMediaWiz = function( iObj, callback ) {
1351                         js_log( ".addMediaWiz call" );
1352                         // check if already loaded:
1353                         if ( _global['rsdMVRS'] ) {
1354                                 _global['rsdMVRS'].doReDisplay();
1355                                 if ( callback )
1356                                         callback( _global['rsdMVRS'] );
1357                                 return ;
1358                         }
1359                         // display a loader: 
1360                         $.addLoaderDialog( gM( 'mwe-loading-add-media-wiz' ) );
1361                         // load the addMedia wizard without a target: 
1362                         $.fn.addMediaWiz ( iObj, function( amwObj ) {
1363                                 // close the dialog
1364                                 $.closeLoaderDialog();
1365                                 // do the add-media-wizard display
1366                                 amwObj.doInitDisplay();
1367                                 // call the parent callback:
1368                                 if ( callback )
1369                                         callback( _global['rsdMVRS'] );
1370                         } );
1371                 }
1372                 $.fn.addMediaWiz = function( iObj, callback ) {
1373                         if ( this.selector ) {
1374                                 // First set the cursor for the button to "loading"
1375                                 $j( this.selector ).css( 'cursor', 'wait' ).attr( 'title', gM( 'mwe-loading_txt' ) );
1376                                 // set the target: 
1377                                 iObj['target_invocation'] = this.selector;
1378                         }
1380                         // Load the mv_embed_base skin:
1381                         loadExternalCss( mv_jquery_skin_path + 'jquery-ui-1.7.1.custom.css' );
1382                         loadExternalCss( mv_embed_path + 'skins/' + mw.conf['skin_name'] + '/styles.css' );
1383                         // Load all the required libs:
1384                         mvJsLoader.jQueryCheck( function() {
1385                                 // Load with staged dependencies (for IE that does not execute in order)
1386                                 mvJsLoader.doLoadDepMode( [
1387                                         [       'remoteSearchDriver',
1388                                                 '$j.cookie',
1389                                                 '$j.fn.textSelection',
1390                                                 '$j.ui'
1391                                         ], [
1392                                                 '$j.ui.resizable',
1393                                                 '$j.ui.draggable',
1394                                                 '$j.ui.dialog',
1395                                                 '$j.ui.tabs',
1396                                                 '$j.ui.sortable'
1397                                         ]
1398                                 ], function() {
1399                                         iObj['instance_name'] = 'rsdMVRS';
1400                                         if ( ! _global['rsdMVRS'] )
1401                                                 _global['rsdMVRS'] = new remoteSearchDriver( iObj );
1402                                         if ( callback ) {
1403                                                 callback( _global['rsdMVRS'] );
1404                                         }
1405                                 } );
1406                         } );
1407                 }
1408                 /*
1409                 * Sequencer loader
1410                 */
1411                 $.fn.sequencer = function( iObj, callback ) {
1412                         // Debugger
1413                         iObj['target_sequence_container'] = this.selector;
1414                         // Issue a request to get the CSS file (if not already included):
1415                         loadExternalCss( mv_jquery_skin_path + 'jquery-ui-1.7.1.custom.css' );
1416                         loadExternalCss( mv_embed_path + 'skins/' + mw.conf['skin_name'] + '/mv_sequence.css' );
1417                         // Make sure we have the required mv_embed libs (they are not loaded when no video
1418                         // element is on the page)
1419                         mvJsLoader.embedVideoCheck( function() {
1420                                 // Load the playlist object and then the jQuery UI stuff:
1421                                 mvJsLoader.doLoadDepMode( [
1422                                         [
1423                                                 'mvPlayList',
1424                                                 '$j.ui',
1425                                                 '$j.contextMenu',
1426                                                 'JSON',
1427                                                 'mvSequencer'
1428                                         ],
1429                                         [
1430                                                 '$j.ui.accordion',
1431                                                 '$j.ui.dialog',
1432                                                 '$j.ui.droppable',
1433                                                 '$j.ui.draggable',
1434                                                 '$j.ui.progressbar',
1435                                                 '$j.ui.sortable',
1436                                                 '$j.ui.resizable',
1437                                                 '$j.ui.slider',
1438                                                 '$j.ui.tabs'
1439                                         ]
1440                                 ], function() {
1441                                         js_log( 'calling new mvSequencer' );
1442                                         // Initialise the sequence object (it will take over from there)
1443                                         // No more than one mvSeq obj for now:
1444                                         if ( !_global['mvSeq'] ) {
1445                                                 _global['mvSeq'] = new mvSequencer( iObj );
1446                                         } else {
1447                                                 js_log( 'mvSeq already init' );
1448                                         }
1449                                 } );
1450                         } );
1451                 }
1452                 /*
1453                  * The Firefogg jQuery function:
1454                  * @@note This Firefogg invocation could be made to work more like real jQuery plugins
1455                  */
1456                 var queuedFirefoggConf = { };
1457                 $.fn.firefogg = function( iObj, callback ) {
1458                         if ( !iObj )
1459                                 iObj = { };
1460                         // Add the base theme CSS:
1461                         loadExternalCss( mv_jquery_skin_path + 'jquery-ui-1.7.1.custom.css' );
1462                         loadExternalCss( mv_embed_path + 'skins/' + mw.conf['skin_name'] + '/styles.css' );
1464                         // Check if we already have Firefogg loaded (the call just updates the element's
1465                         // properties)
1466                         var sElm = $j( this.selector ).get( 0 );
1467                         if ( sElm['firefogg'] ) {
1468                                 if ( sElm['firefogg'] == 'loading' ) {
1469                                         js_log( "Queued firefogg operations ( firefogg " +
1470                                                 "not done loading ) " );
1471                                         $j.extend( queuedFirefoggConf, iObj );
1472                                         return false;
1473                                 }
1474                                 // Update properties
1475                                 for ( var i in iObj ) {
1476                                         js_log( "firefogg::updated: " + i + ' to ' + iObj[i] );
1477                                         sElm['firefogg'][i] = iObj[i];
1478                                 }
1479                                 return sElm['firefogg'];
1480                         } else {
1481                                 // Avoid concurrency
1482                                 sElm['firefogg'] = 'loading';
1483                         }
1484                         // Add the selector
1485                         iObj['selector'] = this.selector;
1487                         var loadSet = [
1488                                 [
1489                                         'mvBaseUploadInterface',
1490                                         'mvFirefogg',
1491                                         '$j.ui'
1492                                 ],
1493                                 [
1494                                         '$j.ui.progressbar',
1495                                         '$j.ui.dialog',
1496                                         '$j.ui.draggable'
1497                                 ]
1498                         ];
1499                         if ( iObj.encoder_interface ) {
1500                                 loadSet.push( [
1501                                         'mvAdvFirefogg',
1502                                         '$j.cookie',
1503                                         '$j.ui.accordion',
1504                                         '$j.ui.slider',
1505                                         '$j.ui.datepicker'
1506                                 ] );
1507                         }
1508                         // Make sure we have everything loaded that we need:
1509                         mvJsLoader.doLoadDepMode( loadSet, function() {
1510                                         js_log( 'firefogg libs loaded. target select:' + iObj.selector );
1511                                         // Select interface provider based on whether we want to include the
1512                                         // encoder interface or not
1513                                         if ( iObj.encoder_interface ) {
1514                                                 var myFogg = new mvAdvFirefogg( iObj );
1515                                         } else {
1516                                                 var myFogg = new mvFirefogg( iObj );
1517                                         }
1518                                         if ( myFogg ) {
1519                                                 myFogg.doRewrite( callback );
1520                                                 var selectorElement = $j( iObj.selector ).get( 0 );
1521                                                 selectorElement['firefogg'] = myFogg;
1522                                                 
1523                                                 js_log( 'pre:' + selectorElement['firefogg']['firefogg_form_action'] )
1524                                                 if ( queuedFirefoggConf )
1525                                                         $j.extend( selectorElement['firefogg'], queuedFirefoggConf );
1526                                                 js_log( 'post:' + selectorElement['firefogg']['firefogg_form_action'] )
1527                                         }
1528                         } );
1529                 }
1530                 // Take an input player as the selector and expose basic rendering controls
1531                 $.fn.firefoggRender = function( iObj, callback ) {
1532                         // Check if we already have render loaded then just pass on updates/actions
1533                         var sElm = $j( this.selector ).get( 0 );
1534                         //add a special attribute to the selector: 
1535                         if ( sElm['fogg_render'] ) {
1536                                 if ( sElm['fogg_render'] == 'loading' ) {
1537                                         js_log( "Error: called firefoggRender while loading" );
1538                                         return false;
1539                                 }
1540                                 // Call or update the property:
1541                         }
1542                         sElm['fogg_render'] = 'loading';
1543                         // Add the selector
1544                         iObj['player_target'] = this.selector;
1545                         mvJsLoader.doLoad( [
1546                                 'mvFirefogg',
1547                                 'mvFirefoggRender'
1548                         ], function() {
1549                                 // Attach the firefoggRender obj to the selected elm: 
1550                                 sElm['fogg_render'] = new mvFirefoggRender( iObj );
1551                                 if ( callback && typeof callback == 'function' )
1552                                         callback( sElm['fogg_render'] );
1553                         } );
1554                 }
1556                 $.fn.baseUploadInterface = function( iObj ) {
1557                         mvJsLoader.doLoadDepMode( [
1558                                 [
1559                                         'mvBaseUploadInterface',
1560                                         '$j.ui',
1561                                 ],
1562                                 [
1563                                         '$j.ui.progressbar',
1564                                         '$j.ui.dialog'
1565                                 ]
1566                         ], function() {
1567                                 myUp = new mvBaseUploadInterface( iObj );
1568                                 myUp.setupForm();
1569                         } );
1570                 }
1572                 // Shortcut to a themed button
1573                 $.btnHtml = function( msg, className, iconId, opt ) {
1574                         if ( !opt )
1575                                 opt = { };
1576                         var href = ( opt.href ) ? opt.href : '#';
1577                         var target_attr = ( opt.target ) ? ' target="' + opt.target + '" ' : '';
1578                         var style_attr = ( opt.style ) ? ' style="' + opt.style + '" ' : '';
1579                         return '<a href="' + href + '" ' + target_attr + style_attr +
1580                                 ' class="ui-state-default ui-corner-all ui-icon_link ' +
1581                                 className + '"><span class="ui-icon ui-icon-' + iconId + '" ></span>' +
1582                                 '<span class="btnText">' + msg + '</span></a>';
1583                 }
1584                 // Shortcut to bind hover state
1585                 $.fn.btnBind = function() {
1586                         $j( this ).hover(
1587                                 function() {
1588                                         $j( this ).addClass( 'ui-state-hover' );
1589                                 },
1590                                 function() {
1591                                         $j( this ).removeClass( 'ui-state-hover' );
1592                                 }
1593                         )
1594                         return this;
1595                 }
1596                 /**
1597                 * resize the dialog to fit the window
1598                 */
1599                 $.fn.dialogFitWindow = function( opt ) {
1600                         var opt_default = { 'hspace':50, 'vspace':50 };
1601                         if ( !opt )
1602                                 var opt = { };
1603                         $j.extend( opt, opt_default );
1604                         $j( this.selector ).dialog( 'option', 'width', $j( window ).width() - opt.hspace );
1605                         $j( this.selector ).dialog( 'option', 'height', $j( window ).height() - opt.vspace );
1606                         $j( this.selector ).dialog( 'option', 'position', 'center' );
1607                                 // update the child position: (some of this should be pushed up-stream via dialog config options
1608                         $j( this.selector + '~ .ui-dialog-buttonpane' ).css( {
1609                                 'position':'absolute',
1610                                 'left':'0px',
1611                                 'right':'0px',
1612                                 'bottom':'0px'
1613                         } );
1614                 }
1615                 
1616                 /**
1617                 * addLoaderDialog
1618                 *  small helper for putting a loading dialog box on top of everything
1619                 * (helps block for request that
1620                 *
1621                 * @param msg text text of the loader msg
1622                 */
1623                 $.addLoaderDialog = function( msg_txt ) {
1624                         $.addDialog( msg_txt, msg_txt + '<br>' + mv_get_loading_img() );
1625                 }
1626                 
1627                 $.addDialog = function ( title, msg_txt, btn ) {
1628                         $( '#mwe_tmp_loader' ).remove();
1629                         // append the style free loader ontop: 
1630                         $( 'body' ).append( '<div id="mwe_tmp_loader" style="display:none" title="' + title + '" >' +
1631                                         msg_txt +
1632                         '</div>' );
1633                         // special btn == ok gives empty give a single "oky" -> "close"
1634                         if ( btn == 'ok' ) {
1635                                 btn[ gM( 'mwe-ok' ) ] = function() {
1636                                         $j( '#mwe_tmp_loader' ).close();
1637                                 }
1638                         }
1639                         // turn the loader into a real dialog loader: 
1640                         mvJsLoader.doLoadDepMode( [
1641                                 [
1642                                         '$j.ui'
1643                                 ],
1644                                 [
1645                                         '$j.ui.dialog'
1646                                 ]
1647                         ], function() {
1648                                 $( '#mwe_tmp_loader' ).dialog( {
1649                                         bgiframe: true,
1650                                         draggable: false,
1651                                         resizable: false,
1652                                         modal: true,
1653                                         width:400,
1654                                         buttons: btn
1655                                 } );
1656                         } );
1657                 }
1658                 $.closeLoaderDialog = function() {
1659                         mvJsLoader.doLoadDepMode( [
1660                                 [
1661                                         '$j.ui'
1662                                 ],
1663                                 [
1664                                         '$j.ui.dialog'
1665                                 ]
1666                         ], function() {
1667                                 $j( '#mwe_tmp_loader' ).dialog( 'destroy' ).remove();
1668                         } );
1669                 }
1670         
1671                 $.mwProxy = function( apiConf ) {
1672                         mvJsLoader.doLoad( ['mw.apiProxy'],
1673                         function() {
1674                                 mw.apiProxy( apiConf );
1675                         } );
1676                 }
1677         } )( jQuery );
1680 * Utility functions:
1682 // Simple URL rewriter (could probably be refactored into an inline regular exp)
1683 function getURLParamReplace( url, opt ) {
1684         var pSrc = mw.parseUri( url );
1685         if ( pSrc.protocol != '' ) {
1686                 var new_url = pSrc.protocol + '://' + pSrc.authority + pSrc.path + '?';
1687         } else {
1688                 var new_url = pSrc.path + '?';
1689         }
1690         var amp = '';
1691         for ( var key in pSrc.queryKey ) {
1692                 var val = pSrc.queryKey[ key ];
1693                 // Do override if requested
1694                 if ( opt[ key ] )
1695                         val = opt[ key ];
1696                 new_url += amp + key + '=' + val;
1697                 amp = '&';
1698         };
1699         // Add any vars that were not already there:
1700         for ( var i in opt ) {
1701                 if ( !pSrc.queryKey[i] ) {
1702                         new_url += amp + i + '=' + opt[i];
1703                         amp = '&';
1704                 }
1705         }
1706         return new_url;
1709  * Given a float number of seconds, returns npt format response.
1711  * @param float Seconds
1712  * @param boolean If we should show milliseconds or not.
1713  */
1714 function seconds2npt( sec, show_ms ) {
1715         if ( isNaN( sec ) ) {
1716                 // js_log("warning: trying to get npt time on NaN:" + sec);
1717                 return '0:0:0';
1718         }
1719         var hours = Math.floor( sec / 3600 );
1720         var minutes = Math.floor( ( sec / 60 ) % 60 );
1721         var seconds = sec % 60;
1722         // Round the number of seconds to the required number of significant digits
1723         if ( show_ms ) {
1724                 seconds = Math.round( seconds * 1000 ) / 1000;
1725         } else {
1726                 seconds = Math.round( seconds );
1727         }
1728         if ( seconds < 10 )
1729                 seconds = '0' + seconds;
1730         if ( minutes < 10 )
1731                 minutes = '0' + minutes;
1733         return hours + ":" + minutes + ":" + seconds;
1736  * Take hh:mm:ss,ms or hh:mm:ss.ms input, return the number of seconds
1737  */
1738 function npt2seconds( npt_str ) {
1739         if ( !npt_str ) {
1740                 // js_log('npt2seconds:not valid ntp:'+ntp);
1741                 return false;
1742         }
1743         // Strip {npt:}01:02:20 or 32{s} from time  if present
1744         npt_str = npt_str.replace( /npt:|s/g, '' );
1746         var hour = 0;
1747         var min = 0;
1748         var sec = 0;
1750         times = npt_str.split( ':' );
1751         if ( times.length == 3 ) {
1752                 sec = times[2];
1753                 min = times[1];
1754                 hour = times[0];
1755         } else if ( times.length == 2 ) {
1756                 sec = times[1];
1757                 min = times[0];
1758         } else {
1759                 sec = times[0];
1760         }
1761         // Sometimes a comma is used instead of period for ms
1762         sec = sec.replace( /,\s?/, '.' );
1763         // Return seconds float
1764         return parseInt( hour * 3600 ) + parseInt( min * 60 ) + parseFloat( sec );
1767  * Simple helper to grab an edit token
1769  * @param title The wiki page title you want to edit
1770  * @param api_url 'optional' The target API URL
1771  * @param callback The callback function to pass the token to
1772  */
1773 function get_mw_token( title, api_url, callback ) {
1774         js_log( ':get_mw_token:' );
1775         if ( !title && wgUserName ) {
1776                 title = 'User:' + wgUserName;
1777         }
1778         var reqObj = {
1779                         'action': 'query',
1780                         'prop': 'info',
1781                         'intoken': 'edit',
1782                         'titles': title
1783                 };
1784         do_api_req( {
1785                 'data': reqObj,
1786                 'url' : api_url
1787                 }, function( data ) {
1788                         for ( var i in data.query.pages ) {
1789                                 if ( data.query.pages[i]['edittoken'] ) {
1790                                         if ( typeof callback == 'function' )
1791                                                 callback ( data.query.pages[i]['edittoken'] );
1792                                 }
1793                         }
1794                         // No token found:
1795                         return false;
1796                 }
1797         );
1799 // Do a remote or local API request based on request URL
1800 // @param options: url, data, cbParam, callback
1801 function do_api_req( options, callback ) {
1802         if ( typeof options.data != 'object' ) {
1803                 return js_error( 'Error: request paramaters must be an object' );
1804         }
1805         // Generate the URL if it's missing
1806         if ( typeof options.url == 'undefined' || !options.url ) {
1807                 if ( typeof wgServer == 'undefined' ) {
1808                         return js_error( 'Error: no api url for api request' );
1809                 }
1810                 options.url = mw.getLocalApiUrl();
1811         }
1812         if ( typeof options.data == 'undefined' )
1813                 options.data = { };
1815         // Force format to JSON
1816         options.data['format'] = 'json';
1818         // If action is not set, assume query
1819         if ( ! options.data['action'] )
1820                 options.data['action'] = 'query';
1822         // js_log('do api req: ' + options.url +'?' + jQuery.param(options.data) );     
1823         if ( options.url == 'proxy' && mw.proxy ) {
1824                 // assume the proxy is already "setup" since mw.proxy is defined.
1825                 // @@todo should probably integrate that setup into the api call
1826                 mw.proxy.doRequest( options.data,  callback );
1827         } else if ( mw.parseUri( document.URL ).host == mw.parseUri( options.url ).host ) {
1828                 // Local request: do API request directly
1829                 $j.ajax( {
1830                         type: "POST",
1831                         url: options.url,
1832                         data: options.data,
1833                         dataType: 'json', // API requests _should_ always return JSON data:
1834                         async: false,
1835                         success: function( data ) {
1836                                 callback( data );
1837                         },
1838                         error: function( e ) {
1839                                 js_error( ' error' + e + ' in getting: ' + options.url );
1840                         }
1841                 } );
1842         } else {
1843                 // Remote request
1844                 // Set the callback param if it's not already set
1845                 if ( typeof options.jsonCB == 'undefined' )
1846                         options.jsonCB = 'callback';
1848                 var req_url = options.url;
1849                 var paramAnd = ( req_url.indexOf( '?' ) == -1 ) ? '?' : '&';
1850                 // Put all the parameters into the URL
1851                 for ( var i in options.data ) {
1852                         req_url += paramAnd + encodeURIComponent( i ) + '=' + encodeURIComponent( options.data[i] );
1853                         paramAnd = '&';
1854                 }
1855                 var fname = 'mycpfn_' + ( mw.cb_count++ );
1856                 _global[ fname ] = callback;
1857                 req_url += '&' + options.jsonCB + '=' + fname;
1858                 loadExternalJs( req_url );
1859         }
1861 // Do a request:
1862 // @@note this contains metavid specific local vs remote api remapping.
1863 // this should be depreciated and we should use "$j.get" or an explicate api call 
1864 // (we should not mix the two request types) 
1865 function do_request( req_url, callback ) {
1866         js_log( 'do_request::req_url:' + mw.parseUri( document.URL ) + ' != ' +  mw.parseUri( req_url ).host );
1867         // If we are doing a request to the same domain or relative link, do a normal GET
1868         if ( mw.parseUri( document.URL ).host == mw.parseUri( req_url ).host ||
1869                 req_url.indexOf( '://' ) == -1 ){ // if its a relative url go directly as well
1870                 // Do a direct request
1871                 $j.ajax( {
1872                         type: "GET",
1873                         url: req_url,
1874                         async: false,
1875                         success: function( data ) {
1876                                 callback( data );
1877                         }
1878                 } );
1879         } else {
1880                 // Get data via DOM injection with callback
1881                 global_req_cb.push( callback );
1882                 // Prepend json_ to feed_format if not already requesting json format (metavid specific) 
1883                 if ( req_url.indexOf( "feed_format=" ) != -1 && req_url.indexOf( "feed_format=json" ) == -1 )
1884                         req_url = req_url.replace( /feed_format=/, 'feed_format=json_' );               
1885                 loadExternalJs( req_url + '&cb=mv_jsdata_cb&cb_inx=' + ( global_req_cb.length -1 ) );
1886         }
1889 function mv_jsdata_cb( response ) {
1890         js_log( 'f:mv_jsdata_cb:' + response['cb_inx'] );
1891         // Run the callback from the global request callback object
1892         if ( !global_req_cb[response['cb_inx']] ) {
1893                 js_log( 'missing req cb index' );
1894                 return false;
1895         }
1896         if ( !response['pay_load'] ) {
1897                 js_log( "missing pay load" );
1898                 return false;
1899         }
1900         switch( response['content-type'] ) {
1901                 case 'text/plain':
1902                 break;
1903                 case 'text/xml':
1904                         if ( typeof response['pay_load'] == 'string' ) {
1905                                  response['pay_load'] = mw.parseXML( response['pay_load'] );
1906                         }
1907                 break
1908                 default:
1909                         js_log( 'bad response type' + response['content-type'] );
1910                         return false;
1911                 break;
1912         }
1913         global_req_cb[response['cb_inx']]( response['pay_load'] );
1915 // Load external JS via DOM injection
1916 function loadExternalJs( url, callback ) {
1917         js_log( 'load js: ' + url );
1918         // if(window['$j']) // use jquery call:
1919                 /*$j.ajax({
1920                         type: "GET",
1921                         url: url,
1922                         dataType: 'script',
1923                         cache: true
1924                 });*/
1925         // else{
1926                 var e = document.createElement( "script" );
1927                 e.setAttribute( 'src', url );
1928                 e.setAttribute( 'type', "text/javascript" );
1929                 /*if(callback)
1930                         e.onload = callback;
1931                 */
1932                 // e.setAttribute('defer', true);
1933                 document.getElementsByTagName( "head" )[0].appendChild( e );
1934         // }
1936 function styleSheetPresent( url ) {
1937         style_elements = document.getElementsByTagName( 'link' );
1938         if ( style_elements.length > 0 ) {
1939                 for ( i = 0; i < style_elements.length; i++ ) {
1940                         if ( style_elements[i].href == url )
1941                                 return true;
1942                 }
1943         }
1944         return false;
1946 function loadExternalCss( url ) {
1947         // We could have the script loader group these CSS requests.
1948         // But it's debatable: it may hurt more than it helps with caching and all
1949         if ( typeof url == 'object' ) {
1950                 for ( var i in url ) {
1951                         loadExternalCss( url[i] );
1952                 }
1953                 return ;
1954         }
1956         if ( url.indexOf( '?' ) == -1 ) {
1957                 url += '?' + getMwReqParam();
1958         }
1959         if ( !styleSheetPresent( url ) ) {
1960                 js_log( 'load css: ' + url );
1961                 var e = document.createElement( "link" );
1962                 e.href = url;
1963                 e.type = "text/css";
1964                 e.rel = 'stylesheet';
1965                 document.getElementsByTagName( "head" )[0].appendChild( e );
1966         }
1968 function getMvEmbedURL() {
1969         if ( _global['mv_embed_url'] )
1970                 return _global['mv_embed_url'];
1971         var js_elements = document.getElementsByTagName( "script" );
1972         for ( var i = 0; i < js_elements.length; i++ ) {
1973                 // Check for mv_embed.js and/or script loader
1974                 var src = js_elements[i].getAttribute( "src" );
1975                 if ( src ) {
1976                         if ( src.indexOf( 'mv_embed.js' ) != -1 || (
1977                                 ( src.indexOf( 'mwScriptLoader.php' ) != -1 || src.indexOf( 'jsScriptLoader.php' ) != -1 )
1978                                 && src.indexOf( 'mv_embed' ) != -1 ) ) // (check for class=mv_embed script_loader call)
1979                         {
1980                                 _global['mv_embed_url'] = src;
1981                                 return src;
1982                         }
1983                 }
1984         }
1985         js_error( 'Error: getMvEmbedURL failed to get Embed Path' );
1986         return false;
1988 // Get a unique request ID to ensure fresh JavaScript
1989 function getMwReqParam() {
1990         if ( _global['req_param'] )
1991                 return _global['req_param'];
1992         var mv_embed_url = getMvEmbedURL();
1993         
1994         var req_param = '';
1995         
1996         // If we have a URI, add it to the req
1997         var urid = mw.parseUri( mv_embed_url ).queryKey['urid']
1998         // If we're in debug mode, get a fresh unique request key and pass on "debug" param
1999         if ( mw.parseUri( mv_embed_url ).queryKey['debug'] == 'true' ) {
2000                 var d = new Date();
2001                 req_param += 'urid=' + d.getTime() + '&debug=true';
2002         } else if ( urid ) {
2003                 // Set from request urid:
2004                 req_param += 'urid=' + urid;
2005         } else {
2006                 // Otherwise, just use the mv_embed version
2007                 req_param += 'urid=' + mw.version;
2008         }
2009         // add the lang param:
2010         var langKey = mw.parseUri( mv_embed_url ).queryKey['uselang'];
2011         if ( langKey )
2012                 req_param += '&uselang=' + langKey;
2013                         
2014         _global['req_param'] = req_param;
2015                 
2016         return _global['req_param'];
2019  * Set the global mv_embed path based on the script's location
2020  */
2021 function getMvEmbedPath() {
2022         if ( _global['mv_embed_path'] )
2023                 return _global['mv_embed_path'];
2024         var mv_embed_url = getMvEmbedURL();
2025         if ( mv_embed_url.indexOf( 'mv_embed.js' ) !== -1 ) {
2026                 mv_embed_path = mv_embed_url.substr( 0, mv_embed_url.indexOf( 'mv_embed.js' ) );
2027         } else if ( mv_embed_url.indexOf( 'mwScriptLoader.php' ) !== -1 ) {
2028                 // Script loader is in the root of MediaWiki, so include the default mv_embed extension path
2029                 mv_embed_path = mv_embed_url.substr( 0, mv_embed_url.indexOf( 'mwScriptLoader.php' ) )
2030                         + mediaWiki_mvEmbed_path;
2031         } else {
2032                 mv_embed_path = mv_embed_url.substr( 0, mv_embed_url.indexOf( 'jsScriptLoader.php' ) );
2033         }
2034         // Make an absolute URL (if it's relative and we don't have an mv_embed path)
2035         if ( mv_embed_path.indexOf( '://' ) == -1 ) {
2036                 var pURL = mw.parseUri( document.URL );
2037                 if ( mv_embed_path.charAt( 0 ) == '/' ) {
2038                         mv_embed_path = pURL.protocol + '://' + pURL.authority + mv_embed_path;
2039                 } else {
2040                         // Relative
2041                         if ( mv_embed_path == '' ) {
2042                                 mv_embed_path = pURL.protocol + '://' + pURL.authority + pURL.directory + mv_embed_path;
2043                         }
2044                 }
2045         }
2046         _global['mv_embed_path'] = mv_embed_path;
2047         return mv_embed_path;
2050 if ( typeof DOMParser == "undefined" ) {
2051         DOMParser = function () { }
2052         DOMParser.prototype.parseFromString = function ( str, contentType ) {
2053                 if ( typeof ActiveXObject != "undefined" ) {
2054                         var d = new ActiveXObject( "MSXML.DomDocument" );
2055                         d.loadXML( str );
2056                         return d;
2057                 } else if ( typeof XMLHttpRequest != "undefined" ) {
2058                         var req = new XMLHttpRequest;
2059                         req.open( "GET", "data:" + ( contentType || "application/xml" ) +
2060                                         ";charset=utf-8," + encodeURIComponent( str ), false );
2061                         if ( req.overrideMimeType ) {
2062                                 req.overrideMimeType( contentType );
2063                         }
2064                         req.send( null );
2065                         return req.responseXML;
2066                 }
2067         }
2070 * Utility functions
2072 function js_log( string ) {
2073         // Add any prepend debug strings if necessary (used for cross browser)
2074         if ( mw.conf['debug_pre'] )
2075                 string = mw.conf['debug_pre'] + string;
2076                         
2077         if ( window.console ) {
2078                 window.console.log( string );
2079         } else {
2080                 /*
2081                  * IE and non-Firebug debug:
2082                  */
2083                 /*var log_elm = document.getElementById('mv_js_log');
2084                 if(!log_elm){
2085                         document.getElementsByTagName("body")[0].innerHTML = document.getElementsByTagName("body")[0].innerHTML +
2086                                 '<div style="position:absolute;z-index:500;top:0px;left:0px;right:0px;height:10px;">'+
2087                                 '<textarea id="mv_js_log" cols="120" rows="5"></textarea>'+
2088                                 '</div>';
2090                         var log_elm = document.getElementById('mv_js_log');
2091                 }
2092                 if(log_elm){
2093                         log_elm.value+=string+"\n";
2094                 }*/
2095         }
2096         return false;
2099 function js_error( string ) {
2100         alert( string );
2101         return false;