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