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