From bad31ae944f074dab12f7a6d1362775d8f2b18dd Mon Sep 17 00:00:00 2001 From: Andreas Gohr Date: Sat, 29 Oct 2005 02:26:52 +0200 Subject: JavaScript refactoring This patch addes a first go on a central javascript and CSS dispatcher which builds a single script from all needed scripts, does optimizing and caching. darcs-hash:20051029002652-7ad00-7558b569c2bf65f5e41820644580d97c62edd0d6.gz --- _test/cases/lib/exe/jscss_js_compress.test.php | 68 ++++++ inc/init.php | 6 +- inc/template.php | 40 ++-- lib/exe/jscss.php | 293 +++++++++++++++++++++++++ lib/scripts/ajax.js | 35 +-- lib/scripts/domLib.js | 38 ++-- lib/scripts/edit.js | 42 ++-- lib/scripts/events.js | 62 ++++++ lib/scripts/script.js | 33 +-- lib/scripts/spellcheck.js | 42 ++-- lib/scripts/tw-sack.js | 243 ++++++++++---------- 11 files changed, 677 insertions(+), 225 deletions(-) create mode 100644 _test/cases/lib/exe/jscss_js_compress.test.php create mode 100644 lib/exe/jscss.php create mode 100644 lib/scripts/events.js diff --git a/_test/cases/lib/exe/jscss_js_compress.test.php b/_test/cases/lib/exe/jscss_js_compress.test.php new file mode 100644 index 000000000..3d9a8b627 --- /dev/null +++ b/_test/cases/lib/exe/jscss_js_compress.test.php @@ -0,0 +1,68 @@ +assertEqual(js_compress($text), ''); + } + + function test_mlcom2(){ + $text = 'var foo=6;/* another comment */'; + $this->assertEqual(js_compress($text), 'var foo=6;'); + } + + function test_slcom1(){ + $text = '// an comment'; + $this->assertEqual(js_compress($text), ''); + } + + function test_slcom2(){ + $text = 'var foo=6;// another comment '; + $this->assertEqual(js_compress($text), 'var foo=6;'); + } + + function test_slcom3(){ + $text = 'var foo=6;// another comment / or something with // comments '; + $this->assertEqual(js_compress($text), 'var foo=6;'); + } + + function test_regex1(){ + $text = 'foo.split( /[a-Z\/]*/ );'; + $this->assertEqual(js_compress($text), 'foo.split(/[a-Z\/]*/);'); + } + + function test_dquot1(){ + $text = 'var foo="Now what \'do we//get /*here*/ ?";'; + $this->assertEqual(js_compress($text), $text); + } + + function test_squot1(){ + $text = "var foo='Now what \"do we//get /*here*/ ?';"; + $this->assertEqual(js_compress($text), $text); + } + + function test_nl1(){ + $text = "var foo=6;\nvar baz=7;"; + $this->assertEqual(js_compress($text), 'var foo=6;var baz=7;'); + } + + function test_lws1(){ + $text = " \t var foo=6;"; + $this->assertEqual(js_compress($text), 'var foo=6;'); + } + + function test_tws1(){ + $text = "var foo=6; \t "; + $this->assertEqual(js_compress($text), 'var foo=6;'); + } +} + +//Setup VIM: ex: et ts=4 enc=utf-8 : diff --git a/inc/init.php b/inc/init.php index 108ed615c..41363f63d 100644 --- a/inc/init.php +++ b/inc/init.php @@ -46,8 +46,10 @@ @ini_set('arg_separator.output', '&'); // init session - session_name("DokuWiki"); - if (!headers_sent()) session_start(); + if (!headers_sent() && !defined(NOSESSION)){ + session_name("DokuWiki"); + session_start(); + } // kill magic quotes if (get_magic_quotes_gpc()) { diff --git a/inc/template.php b/inc/template.php index 0deeff32d..7401c3e62 100644 --- a/inc/template.php +++ b/inc/template.php @@ -195,7 +195,9 @@ function tpl_metaheaders(){ ptln('',$it); } - // include some JavaScript language strings +/* + + // include some JavaScript language strings #FIXME still needed? ptln('',$it); // load the default JavaScript files + ptln('',$it); ptln('',$it); ptln('',$it); - + ptln('',$it); // editing functions if($ACT=='edit' || $ACT=='preview'){ // add size control ptln('',$it); if($INFO['writable']){ @@ -243,21 +250,21 @@ function tpl_metaheaders(){ // add toolbar require_once(DOKU_INC.'inc/toolbar.php'); toolbar_JSdefines('toolbar'); - ptln("addEvent(window,'onload',function(){initToolbar('toolbar','wikitext',toolbar);});",$it+2); + ptln("addEvent(window,'load',function(){initToolbar('toolbar','wikitext',toolbar);});",$it+2); // add pageleave check - ptln("addEvent(window,'onload',function(){initChangeCheck('". + ptln("addEvent(window,'load',function(){initChangeCheck('". str_replace('\\\\n','\\n',addslashes($lang['notsavedyet']))."');});",$it); // add lock timer - ptln("addEvent(window,'onload',function(){init_locktimer(". + ptln("addEvent(window,'load',function(){init_locktimer(". ($conf['locktime']-60).",'". str_replace('\\\\n','\\n',addslashes($lang['willexpire']))."');});",$it); // add spellchecker if($conf['spellchecker']){ //init here - ptln("addEvent(window,'onload',function(){ ajax_spell.init('". + ptln("addEvent(window,'load',function(){ ajax_spell.init('". $lang['spell_start']."','". $lang['spell_stop']."','". $lang['spell_wait']."','". @@ -268,6 +275,14 @@ function tpl_metaheaders(){ ptln('',$it); } } +*/ + + $js_edit = ($ACT=='edit' || $ACT=='preview') ? 1 : 0; + $js_write = ($INFO['writable']) ? 1 : 0; + + ptln('',$it); + // plugin stylesheets and Scripts plugin_printCSSJS(); @@ -533,20 +548,17 @@ function tpl_actionlink($type,$pre='',$suf=''){ * * @author Andreas Gohr */ -function tpl_searchform(){ +function tpl_searchform($withajax=true){ global $lang; global $ACT; print ''; } diff --git a/lib/exe/jscss.php b/lib/exe/jscss.php new file mode 100644 index 000000000..33d67eece --- /dev/null +++ b/lib/exe/jscss.php @@ -0,0 +1,293 @@ + + */ + +if(!defined('DOKU_INC')) define('DOKU_INC',realpath(dirname(__FILE__).'/../../').'/'); +define('NOSESSION',true); // we do not use a session or authentication here (better caching) +require_once(DOKU_INC.'inc/init.php'); +require_once(DOKU_INC.'inc/pageutils.php'); +require_once(DOKU_INC.'inc/io.php'); + +// Main (don't run when UNIT test) +if(!defined('SIMPLE_TEST')){ + if($_REQUEST['type'] == 'css'){ + css_out(); + }else{ + header('Content-Type: text/javascript; charset=utf-8'); + js_out(); + } +} + + +// ---------------------- functions ------------------------------ + +/** + * Output all needed JavaScript + * + * @todo Add Whitespace and Comment Compression + * @author Andreas Gohr + */ +function js_out(){ + global $conf; + global $lang; + $edit = (bool) $_REQUEST['edit']; // edit or preview mode? + $write = (bool) $_REQUEST['write']; // writable? + + // The generated script depends on some dynamic options + $cache = getCacheName($conf['lang'].$edit.$write,$ext='.js'); + + // Array of needed files + $files = array( + DOKU_INC.'lib/scripts/events.js', + DOKU_INC.'lib/scripts/script.js', + DOKU_INC.'lib/scripts/tw-sack.js', + DOKU_INC.'lib/scripts/ajax.js', + DOKU_INC.'lib/scripts/domLib.js', + DOKU_INC.'lib/scripts/domTT.js', + ); + if($edit && $write){ + $files[] = DOKU_INC.'lib/scripts/edit.js'; + if($conf['spellchecker']){ + $files[] = DOKU_INC.'lib/scripts/spellcheck.js'; + } + } + + // FIXME load plugin scripts + + // check cache age here + if(js_cacheok($cache,$files)){ + readfile($cache); + return; + } + + // start output buffering and build the script + ob_start(); + + // add some translation strings and global variables + print "var alertText = '".str_replace('\\\\n','\\n',addslashes($lang['qb_alert']))."';"; + print "var notSavedYet = '".str_replace('\\\\n','\\n',addslashes($lang['notsavedyet']))."';"; + print "var DOKU_BASE = '".DOKU_BASE."';"; + + // load files + foreach($files as $file){ + readfile($file); + } + + // init stuff + js_runonstart("ajax_qsearch.init('qsearch_in','qsearch_out')"); + js_runonstart("addEvent(document,'click',closePopups)"); + + if($edit){ + // size controls + js_runonstart("initSizeCtl('sizectl','wikitext')"); + + if($write){ + require_once(DOKU_INC.'inc/toolbar.php'); + toolbar_JSdefines('toolbar'); + js_runonstart("initToolbar('toolbar','wikitext',toolbar)"); + + // add pageleave check + js_runonstart("initChangeCheck('".js_escape($lang['notsavedyet'])."')"); + + // add lock timer + js_runonstart("init_locktimer(".($conf['locktime']-60).",'".js_escape($lang['willexpire'])."')"); + + // load spell checker + if($conf['spellchecker']){ + js_runonstart("ajax_spell.init('". + js_escape($lang['spell_start'])."','". + js_escape($lang['spell_stop'])."','". + js_escape($lang['spell_wait'])."','". + js_escape($lang['spell_noerr'])."','". + js_escape($lang['spell_nosug'])."','". + js_escape($lang['spell_change'])."')"); + } + } + } + + + // load user script + if(@file_exists(DOKU_INC.'conf/userscript.js')){ + readfile(DOKU_INC.'conf/userscript.js'); + } + + // end output buffering and get contents + $js = ob_get_contents(); + ob_end_clean(); + + // compress whitespace and comments + $js = js_compress($js); + + // save cache file + io_saveFile($cache,$js); + + // finally send output + print $js; +} + +/** + * Checks if a JavaScript Cache file still is valid + * + * @author Andreas Gohr + */ +function js_cacheok($cache,$files){ + $ctime = @filemtime($cache); + if(!$ctime) return false; //There is no cache + + // some additional files to check + $files[] = DOKU_INC.'conf/dokuwiki.conf'; + $files[] = DOKU_INC.'conf/local.conf'; + $files[] = DOKU_INC.'conf/userscript.js'; + + // now walk the files + foreach($files as $file){ + if(@filemtime($file) > $ctime){ + return false; + } + } + return true; +} + +/** + * Escapes a String to be embedded in a JavaScript call, keeps \n + * as newline + * + * @author Andreas Gohr + */ +function js_escape($string){ + return str_replace('\\\\n','\\n',addslashes($string)); +} + +/** + * Adds the given JavaScript code to the window.onload() event + * + * @author Andreas Gohr + */ +function js_runonstart($func){ + print "addEvent(window,'load',function(){ $func; });"; +} + +//http://modp.com/release/jsstrip/jsstrip.py +function js_compress($s){ + $i = 0; + $line = 0; + $s .= "\n"; + $len = strlen($s); + + // items that don't need spaces next to them + $chars = '^&|!+\-*\/%=:;,{}()<>% \t\n\r'; + + ob_start(); + while($i < $len){ + $ch = $s{$i}; + + // multiline comments + if($ch == '/' && $s{$i+1} == '*'){ + $endC = strpos($s,'*/',$i+2); + if($endC === false) trigger_error('Found invalid /*..*/ comment', E_USER_ERROR); + $i = $endC + 2; + continue; + } + + // singleline + if($ch == '/' && $s{$i+1} == '/'){ + $endC = strpos($s,"\n",$i+2); + if($endC === false) trigger_error('Invalid comment', E_USER_ERROR); + $i = $endC; + continue; + } + + // tricky. might be an RE + if($ch == '/'){ + // rewind, skip white space + $j = 1; + while($s{$i-$j} == ' '){ + $j = $j + 1; + } + if( ($s{$i-$j} == '=') || ($s{$i-$j} == '(') ){ + // yes, this is an re + // now move forward and find the end of it + $j = 1; + while($s{$i+$j} != '/'){ + while( ($s{$i+$j} != '\\') && ($s{$i+$j} != '/')){ + $j = $j + 1; + } + if($s{$i+$j} == '\\') $j = $j + 2; + } + echo substr($s,$i,$j+1); + $i = $i + $j + 1; + continue; + } + } + + // double quote strings + if($ch == '"'){ + $j = 1; + while( $s{$i+$j} != '"' ){ + while( ($s{$i+$j} != '\\') && ($s{$i+$j} != '"') ){ + $j = $j + 1; + } + if($s{$i+$j} == '\\') $j = $j + 2; + } + echo substr($s,$i,$j+1); + $i = $i + $j + 1; + continue; + } + + // single quote strings + if($ch == "'"){ + $j = 1; + while( $s{$i+$j} != "'" ){ + while( ($s{$i+$j} != '\\') && ($s{$i+$j} != "'") ){ + $j = $j + 1; + } + if ($s{$i+$j} == '\\') $j = $j + 2; + } + echo substr($s,$i,$j+1); + $i = $i + $j + 1; + continue; + } + + // newlines + if($ch == "\n" || $ch == "\r"){ + $i = $i+1; + continue; + } + + // leading spaces + if( ( $ch == ' ' || + $ch == "\n" || + $ch == "\t" ) && + !preg_match('/['.$chars.']/',$s{$i+1}) ){ + $i = $i+1; + continue; + } + + // trailing spaces + if( ( $ch == ' ' || + $ch == "\n" || + $ch == "\t" ) && + !preg_match('/['.$chars.']/',$s{$i-1}) ){ + $i = $i+1; + continue; + } + + // other chars + echo $ch; + $i = $i + 1; + } + + + $out = ob_get_contents(); + ob_end_clean(); + return $out; +} + +//http://csstidy.sourceforge.net/download.php + +//Setup VIM: ex: et ts=4 enc=utf-8 : +?> diff --git a/lib/scripts/ajax.js b/lib/scripts/ajax.js index 0a4183463..c0323f09e 100644 --- a/lib/scripts/ajax.js +++ b/lib/scripts/ajax.js @@ -23,39 +23,46 @@ ajax_qsearch.sack.AjaxFailedAlert = ''; ajax_qsearch.sack.encodeURIString = false; ajax_qsearch.init = function(inID,outID){ - if(ajax_qsearch.inObj == null) - ajax_qsearch.inObj = document.getElementById(inID); - if(ajax_qsearch.outObj == null) - ajax_qsearch.outObj = document.getElementById(outID); -} + ajax_qsearch.inObj = document.getElementById(inID); + ajax_qsearch.outObj = document.getElementById(outID); + + // objects found? + if(ajax_qsearch.inObj === null){ return; } + if(ajax_qsearch.outObj === null){ return; } + + // attach eventhandler to search field + addEvent(ajax_qsearch.inObj,'keyup',ajax_qsearch.call); + + // attach eventhandler to output field + addEvent(ajax_qsearch.outObj,'click',function(){ ajax_qsearch.outObj.style.display='none'; }); +}; ajax_qsearch.clear = function(){ ajax_qsearch.outObj.style.display = 'none'; ajax_qsearch.outObj.innerHTML = ''; - if(ajax_qsearch.timer != null){ + if(ajax_qsearch.timer !== null){ window.clearTimeout(ajax_qsearch.timer); ajax_qsearch.timer = null; } -} +}; ajax_qsearch.exec = function(){ ajax_qsearch.clear(); var value = ajax_qsearch.inObj.value; - if(value == '') return; + if(value === ''){ return; } ajax_qsearch.sack.runAJAX('call=qsearch&q='+encodeURI(value)); -} +}; ajax_qsearch.sack.onCompletion = function(){ var data = ajax_qsearch.sack.response; - if(data == '') return; + if(data === ''){ return; } ajax_qsearch.outObj.innerHTML = data; ajax_qsearch.outObj.style.display = 'block'; -} +}; -ajax_qsearch.call = function(inID,outID){ - ajax_qsearch.init(inID,outID); +ajax_qsearch.call = function(){ ajax_qsearch.clear(); ajax_qsearch.timer = window.setTimeout("ajax_qsearch.exec()",500); -} +}; diff --git a/lib/scripts/domLib.js b/lib/scripts/domLib.js index 9dd3af463..e46c6fecf 100644 --- a/lib/scripts/domLib.js +++ b/lib/scripts/domLib.js @@ -65,7 +65,7 @@ var domLib_isIE6up = domLib_isIE55up && !domLib_isIE55; var domLib_standardsMode = (document.compatMode && document.compatMode == 'CSS1Compat'); var domLib_useLibrary = (domLib_isOpera7up || domLib_isKHTML || domLib_isIE5up || domLib_isGecko || domLib_isMacIE || document.defaultView); // fixed in Konq3.2 -var domLib_hasBrokenTimeout = (domLib_isMacIE || (domLib_isKonq && domLib_userAgent.match(/konqueror\/3.([2-9])/) == null)); +var domLib_hasBrokenTimeout = (domLib_isMacIE || (domLib_isKonq && domLib_userAgent.match(/konqueror\/3.([2-9])/) === null)); var domLib_canFade = (domLib_isGecko || domLib_isIE || domLib_isSafari || domLib_isOpera); var domLib_canDrawOverSelect = (domLib_isMac || domLib_isOpera || domLib_isGecko); var domLib_canDrawOverFlash = (domLib_isMac || domLib_isWin); @@ -109,7 +109,7 @@ function domLib_clone(obj) var value = obj[i]; try { - if (value != null && typeof(value) == 'object' && value != window && !value.nodeType) + if (value !== null && typeof(value) == 'object' && value != window && !value.nodeType) { copy[i] = domLib_clone(value); } @@ -153,7 +153,7 @@ function Hash() Hash.prototype.get = function(in_key) { return this.elementData[in_key]; -} +}; Hash.prototype.set = function(in_key, in_value) { @@ -168,11 +168,12 @@ Hash.prototype.set = function(in_key, in_value) } } - return this.elementData[in_key] = in_value; + this.elementData[in_key] = in_value; + return this.elementData[in_key]; } return false; -} +}; Hash.prototype.remove = function(in_key) { @@ -190,17 +191,17 @@ Hash.prototype.remove = function(in_key) } return tmp_value; -} +}; Hash.prototype.size = function() { return this.length; -} +}; Hash.prototype.has = function(in_key) { return typeof(this.elementData[in_key]) != 'undefined'; -} +}; Hash.prototype.find = function(in_obj) { @@ -211,7 +212,7 @@ Hash.prototype.find = function(in_obj) return tmp_key; } } -} +}; Hash.prototype.merge = function(in_hash) { @@ -228,7 +229,7 @@ Hash.prototype.merge = function(in_hash) this.elementData[tmp_key] = in_hash.elementData[tmp_key]; } -} +}; Hash.prototype.compare = function(in_hash) { @@ -246,7 +247,7 @@ Hash.prototype.compare = function(in_hash) } return true; -} +}; // }}} // {{{ domLib_isDescendantOf() @@ -341,7 +342,7 @@ function domLib_detectCollisions(in_object, in_recover, in_useCache) return; } - else if (domLib_collisionElements.length == 0) + else if (domLib_collisionElements.length === 0) { return; } @@ -349,9 +350,9 @@ function domLib_detectCollisions(in_object, in_recover, in_useCache) // okay, we have a tip, so hunt and destroy var objectOffsets = domLib_getOffsets(in_object); - for (var cnt = 0; cnt < domLib_collisionElements.length; cnt++) + for (cnt = 0; cnt < domLib_collisionElements.length; cnt++) { - var thisElement = domLib_collisionElements[cnt]; + thisElement = domLib_collisionElements[cnt]; // if collision element is in active element, move on // WARNING: is this too costly? @@ -442,8 +443,7 @@ function domLib_getOffsets(in_object) 'bottom', offsetTop + originalHeight, 'leftCenter', offsetLeft + originalWidth/2, 'topCenter', offsetTop + originalHeight/2, - 'radius', Math.max(originalWidth, originalHeight) - ); + 'radius', Math.max(originalWidth, originalHeight) ); } // }}} @@ -461,7 +461,7 @@ function domLib_setTimeout(in_function, in_timeout, in_args) // timeout event is disabled return; } - else if (in_timeout == 0) + else if (in_timeout === 0) { in_function(in_args); return 0; @@ -500,7 +500,7 @@ function domLib_clearTimeout(in_id) { if (domLib_timeoutStates.has(in_id)) { - clearTimeout(domLib_timeoutStates.get(in_id).get('timeoutId')) + clearTimeout(domLib_timeoutStates.get(in_id).get('timeoutId')); domLib_timeoutStates.remove(in_id); } } @@ -631,7 +631,7 @@ function domLib_getComputedStyle(in_obj, in_property) // getComputedStyle() is broken in konqueror, so let's go for the style object else if (domLib_isKonq) { - var humpBackProp = in_property.replace(/-(.)/, function (a, b) { return b.toUpperCase(); }); + humpBackProp = in_property.replace(/-(.)/, function (a, b) { return b.toUpperCase(); }); return eval('in_obj.style.' + in_property); } else diff --git a/lib/scripts/edit.js b/lib/scripts/edit.js index 43e6843b0..fd4cb8d0b 100644 --- a/lib/scripts/edit.js +++ b/lib/scripts/edit.js @@ -59,7 +59,7 @@ function createPicker(id,list,icobase,edid){ for(var key in list){ var btn = document.createElement('button'); - btn.className = 'pickerbutton' + btn.className = 'pickerbutton'; // associative array? if(isNaN(key)){ @@ -69,16 +69,14 @@ function createPicker(id,list,icobase,edid){ btn.appendChild(ico); eval("btn.onclick = function(){pickerInsert('"+id+"','"+ jsEscape(key)+"','"+ - jsEscape(edid) - +"');return false;}"); + jsEscape(edid)+"');return false;}"); }else{ var txt = document.createTextNode(list[key]); btn.title = list[key]; btn.appendChild(txt); eval("btn.onclick = function(){pickerInsert('"+id+"','"+ jsEscape(list[key])+"','"+ - jsEscape(edid) - +"');return false;}"); + jsEscape(edid)+"');return false;}"); } picker.appendChild(btn); @@ -127,7 +125,7 @@ function showPicker(pickerid,btn){ * @author Andreas Gohr */ function initToolbar(tbid,edid,tb){ - if(!document.getElementById) return; + if(!document.getElementById){ return; } var toolbar = document.getElementById(tbid); var cnt = tb.length; for(i=0; i