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