diff --git a/CHANGELOG.txt b/CHANGELOG.txt index 4e0ba2530be00a61c18f0cf9c125f57a833b95cb..708bbf28eac5c1ade255ca1f7fb679eec5f98faa 100644 --- a/CHANGELOG.txt +++ b/CHANGELOG.txt @@ -36,8 +36,8 @@ Drupal 6.0, xxxx-xx-xx (development version) * Added .info files to themes and made it easier to specify regions and features. * Added theme registry: modules can directly provide .tpl.php files for their themes without having to create theme_ functions. * Used the Garland theme for the installation and maintenance pages. - * Added theme preprocess functions for themes that are templates -- Refactored update.php to a generic batch API to be able to run time consuming operations in multiple subsequent HTTP requests + * Added theme preprocess functions for themes that are templates. +- Refactored update.php to a generic batch API to be able to run time consuming operations in multiple subsequent HTTP requests. - Installer: * Themed the installer with the Garland theme. * Added form to provide initial site information during installation. @@ -48,6 +48,7 @@ Drupal 6.0, xxxx-xx-xx (development version) * Tags are now automatically closed at the end of the teaser. - Performance: * Made it easier to conditionally load include files. + * Added a JavaScript aggregator and compressor. - File handling improvements: * Entries in the files table are now keyed to a user, and not a node. * Added re-usable validation functions to check for uploaded file sizes, extensions, and image resolution. diff --git a/includes/batch.inc b/includes/batch.inc index 80971434bf2a359523e543ffc41227d228561faf..2808f698a9420b35f7d434b818f5719765834913 100644 --- a/includes/batch.inc +++ b/includes/batch.inc @@ -66,7 +66,7 @@ function _batch_progress_page_js() { $current_set = _batch_current_set(); drupal_set_title($current_set['title']); - drupal_add_js('misc/progress.js', 'core', 'header'); + drupal_add_js('misc/progress.js', 'core', 'header', FALSE, FALSE); $url = url($batch['url'], array('query' => array('id' => $batch['id']))); $js_setting = array( @@ -77,7 +77,7 @@ function _batch_progress_page_js() { ), ); drupal_add_js($js_setting, 'setting'); - drupal_add_js('misc/batch.js', 'core', 'header', FALSE, TRUE); + drupal_add_js('misc/batch.js', 'core', 'header', FALSE, FALSE); $output = '<div id="progress"></div>'; return $output; diff --git a/includes/common.inc b/includes/common.inc index 37fc163208e7891a53d456a9a760fc3bd31a00bc..ae5d177f85cba8ecb0dcac48d8a050acec0a22b9 100644 --- a/includes/common.inc +++ b/includes/common.inc @@ -1681,24 +1681,26 @@ function drupal_clear_css_cache() { * (optional) If set to FALSE, the JavaScript file is loaded anew on every page * call, that means, it is not cached. Defaults to TRUE. Used only when $type * references a JavaScript file. + * @param $preprocess + * (optional) Should this JS file be aggregated if this + * feature has been turned on under the performance section? * @return * If the first parameter is NULL, the JavaScript array that has been built so * far for $scope is returned. */ -function drupal_add_js($data = NULL, $type = 'module', $scope = 'header', $defer = FALSE, $cache = TRUE) { - if (!is_null($data)) { - _drupal_add_js('misc/jquery.js', 'core', 'header', FALSE, $cache); - _drupal_add_js('misc/drupal.js', 'core', 'header', FALSE, $cache); - } - return _drupal_add_js($data, $type, $scope, $defer, $cache); -} - -/** - * Helper function for drupal_add_js(). - */ -function _drupal_add_js($data, $type, $scope, $defer, $cache) { +function drupal_add_js($data = NULL, $type = 'module', $scope = 'header', $defer = FALSE, $cache = TRUE, $preprocess = TRUE) { static $javascript = array(); + // Add jquery.js and drupal.js the first time a Javascript file is added. + if ($data && empty($javascript)) { + $javascript['header'] = array( + 'core' => array( + 'misc/jquery.js' => array('cache' => TRUE, 'defer' => FALSE, 'preprocess' => TRUE), + 'misc/drupal.js' => array('cache' => TRUE, 'defer' => FALSE, 'preprocess' => TRUE), + ), + 'module' => array(), 'theme' => array(), 'setting' => array(), 'inline' => array(), + ); + } if (!isset($javascript[$scope])) { $javascript[$scope] = array('core' => array(), 'module' => array(), 'theme' => array(), 'setting' => array(), 'inline' => array()); } @@ -1707,7 +1709,7 @@ function _drupal_add_js($data, $type, $scope, $defer, $cache) { $javascript[$scope][$type] = array(); } - if (!is_null($data)) { + if (isset($data)) { switch ($type) { case 'setting': $javascript[$scope][$type][] = $data; @@ -1716,7 +1718,8 @@ function _drupal_add_js($data, $type, $scope, $defer, $cache) { $javascript[$scope][$type][] = array('code' => $data, 'defer' => $defer); break; default: - $javascript[$scope][$type][$data] = array('cache' => $cache, 'defer' => $defer); + // If cache is FALSE, don't preprocess the JS file. + $javascript[$scope][$type][$data] = array('cache' => $cache, 'defer' => $defer, 'preprocess' => (!$cache ? FALSE : $preprocess)); } } @@ -1739,13 +1742,25 @@ function _drupal_add_js($data, $type, $scope, $defer, $cache) { * @return * All JavaScript code segments and includes for the scope as HTML tags. */ -function drupal_get_js($scope = 'header', $javascript = NULL) { - $output = ''; - if (is_null($javascript)) { +function drupal_get_js($scope = 'header', $javascript = NULL) { + if (!isset($javascript)) { $javascript = drupal_add_js(NULL, NULL, $scope); } + if (count($javascript) < 1) { + return ''; + } + + $output = ''; + $preprocessed = ''; + $no_preprocess = array('core' => '', 'module' => '', 'theme' => ''); + $files = array(); + $preprocess_js = variable_get('preprocess_js', FALSE); + $directory = file_directory_path(); + $is_writable = is_dir($directory) && is_writable($directory) && (variable_get('file_downloads', FILE_DOWNLOADS_PUBLIC) == FILE_DOWNLOADS_PUBLIC); + foreach ($javascript as $type => $data) { + if (!$data) continue; switch ($type) { @@ -1758,15 +1773,297 @@ function drupal_get_js($scope = 'header', $javascript = NULL) { } break; default: + // If JS preprocessing is off, we still need to output the scripts. + // Additionally, go through any remaining scripts if JS preprocessing is on and output the non-cached ones. foreach ($data as $path => $info) { - $output .= '<script type="text/javascript"'. ($info['defer'] ? ' defer="defer"' : '') .' src="'. check_url(base_path() . $path) . ($info['cache'] ? '' : '?'. time()) ."\"></script>\n"; + if (!$info['preprocess'] || !$is_writable || !$preprocess_js) { + $no_preprocess[$type] .= '<script type="text/javascript"'. ($info['defer'] ? ' defer="defer"' : '') .' src="'. base_path() . $path . ($info['cache'] ? '' : '?'. time()) ."\"></script>\n"; + } + else { + $files[$path] = $info; + } } } } - + + // Aggregate any remaining JS files that haven't already been output. + if ($is_writable && $preprocess_js && count($files) > 0) { + $filename = md5(serialize($files)) .'.js'; + $preprocess_file = drupal_build_js_cache($files, $filename); + $preprocessed .= '<script type="text/javascript" src="'. base_path() . $preprocess_file .'"></script>'. "\n"; + } + + // Keep the order of JS files consistent as some are preprocessed and others are not. + // Make sure any inline or JS setting variables appear last after libraries have loaded. + $output = $preprocessed . implode('', $no_preprocess) . $output; + return $output; } +/** + * Aggregate JS files, putting them in the files directory. + * + * @param $files + * An array of JS files to aggregate and compress into one file. + * @param $filename + * The name of the aggregate JS file. + * @return + * The name of the JS file. + */ +function drupal_build_js_cache($files, $filename) { + $contents = ''; + + // Create the js/ within the files folder. + $jspath = file_create_path('js'); + file_check_directory($jspath, FILE_CREATE_DIRECTORY); + + if (!file_exists($jspath .'/'. $filename)) { + // Build aggregate JS file. + foreach ($files as $path => $info) { + if ($info['preprocess']) { + // Append a ';' after each JS file to prevent them from running together. + $contents .= _drupal_compress_js(file_get_contents($path). ';'); + } + } + + // Create the JS file. + file_save_data($contents, $jspath .'/'. $filename, FILE_EXISTS_REPLACE); + } + + return $jspath .'/'. $filename; +} + +/** + * Perform basic code compression for JavaScript. + * + * Helper function for drupal_pack_js(). + */ +function _drupal_compress_js($script) { + $regexps = array( + // Protect strings. + array('/\'[^\'\\n\\r]*\'/', '$0'), + array('/"[^"\\n\\r]*"/', '$0'), + // Remove comments. + array('/\\/\\/[^\\n\\r]*[\\n\\r]/', ''), + array('/\\/\\*[^*]*\\*+((?:[^\\/][^*]*\\*+)*)\\//', ''), + // Protect regular expressions + array('/\\s+(\\/[^\\/\\n\\r\\*][^\\/\\n\\r]*\\/g?i?)/', '$1'), + array('/[^\\w\\x24\\/\'"*)\\?:]\\/[^\\/\\n\\r\\*][^\\/\\n\\r]*\\/g?i?/', '$0'), + // Protect spaces between keywords and variables + array('/(\\b|\\x24)\\s+(\\b|\\x24)/', '$1 $2'), + array('/([+\\-])\\s+([+\\-])/', '$1 $2'), + // Remove all other white-space + array('/\\s+/', ''), + ); + $script = _packer_apply($script, $regexps, TRUE); + + return $script; +} + +/** + * Multi-regexp replacements. + * + * Allows you to perform multiple regular expression replacements at once, + * without overlapping matches. + * + * @param $script + * The text to modify. + * @param $regexps + * An array of replacement instructions, each being a tuple with values: + * - A stand-alone regular expression without modifiers (slash-delimited) + * - A replacement expression, which may include placeholders. + * @param $escape + * Whether to ignore slash-escaped characters for matching. This allows you + * to match e.g. quote-delimited strings with /'[^']+'/ without having to + * worry about \'. Otherwise, you'd have to mess with look-aheads and + * look-behinds to match these. + */ +function _packer_apply($script, $regexps, $escape = FALSE) { + + $_regexps = array(); + // Process all regexps + foreach ($regexps as $regexp) { + list($expression, $replacement) = $regexp; + + // Count the number of matching groups (including the whole). + $length = 1 + preg_match_all('/(?<!\\\\)\((?!\?)/', $expression, $out); + + // Treat only strings $replacement + if (is_string($replacement)) { + // Does the pattern deal with sub-expressions? + if (preg_match('/\$\d/', $replacement)) { + if (preg_match('/^\$\d+$/', $replacement)) { + // A simple lookup (e.g. "$2") + // Store the index (used for fast retrieval of matched strings) + $replacement = (int)(substr($replacement, 1)); + } + else { + // A complicated lookup (e.g. "Hello $2 $1"). + // Build a function to do the lookup. + $replacement = array( + 'fn' => 'backreferences', + 'data' => array( + 'replacement' => $replacement, + 'length' => $length, + ) + ); + } + } + } + // Store the modified expression. + if (!empty($expression)) { + $_regexps[] = array($expression, $replacement, $length); + } + else { + $_regexps[] = array('/^$/', $replacement, $length); + } + } + + // Execute the global replacement + + // Build one mega-regexp out of the smaller ones. + $regexp = '/'; + foreach ($_regexps as $_regexp) { + list($expression) = $_regexp; + $regexp .= '(' . substr($expression, 1, -1) . ')|'; + } + $regexp = substr($regexp, 0, -1) . '/'; + + // In order to simplify the regexps that look e.g. for quoted strings, we + // remove all escaped characters (such as \' or \") from the data. Then, we + // put them back as they were. + + if ($escape) { + // Remove escaped characters + $script = preg_replace_callback( + '/\\\\(.)' .'/', + '_packer_escape_char', + $script + ); + $escaped = _packer_escape_char(NULL, TRUE); + } + + _packer_replacement(NULL, $_regexps, $escape); + $script = preg_replace_callback( + $regexp, + '_packer_replacement', + $script + ); + + if ($escape) { + // Restore escaped characters + _packer_unescape_char(NULL, $escaped); + $script = preg_replace_callback( + '/\\\\' .'/', + '_packer_unescape_char', + $script + ); + + // We only delete portions of data afterwards to ensure the escaped character + // replacements don't go out of sync. We mark all sections to delete with + // ASCII 01 bytes. + $script = preg_replace('/\\x01[^\\x01]*\\x01/', '', $script); + } + + return $script; +} + +/** + * Helper function for _packer_apply(). + */ +function _packer_escape_char($match, $return = FALSE) { + // Build array of escaped characters that were removed. + static $_escaped = array(); + if ($return) { + $escaped = $_escaped; + $_escaped = array(); + return $escaped; + } + else { + $_escaped[] = $match[1]; + return '\\'; + } +} + +/** + * Helper function for _packer_apply(). + * + * Performs replacements for the multi-regexp. + */ +function _packer_replacement($arguments, $regexps = NULL, $escape = NULL) { + // Cache regexps + static $_regexps, $_escape; + if (isset($regexps)) { + $_regexps = $regexps; + } + if (isset($escape)) { + $_escape = $escape; + } + + if (empty($arguments)) { + return ''; + } + + $i = 1; $j = 0; + // Loop through the regexps + while (isset($_regexps[$j])) { + list($expression, $replacement, $length) = $_regexps[$j++]; + + // Do we have a result? + if (isset($arguments[$i]) && ($arguments[$i] != '')) { + if (is_array($replacement) && isset($replacement['fn'])) { + return call_user_func('_packer_'. $replacement['fn'], $arguments, $i, $replacement['data']); + } + elseif (is_int($replacement)) { + return $arguments[$replacement + $i]; + } + else { + $delete = !$escape || strpos($arguments[$i], '\\') === FALSE + ? '' : "\x01" . $arguments[$i] . "\x01"; + return $delete . $replacement; + } + // skip over references to sub-expressions + } + else { + $i += $length; + } + } +} + +/** + * Helper function for _packer_apply(). + */ +function _packer_unescape_char($match, $escaped = NULL) { + // Store array of escaped characters to insert back. + static $_escaped, $i; + if ($escaped) { + $_escaped = $escaped; + $i = 0; + } + else { + return '\\'. array_shift($_escaped); + } +} + +/** + * Helper function for _packer_replacement(). + */ +function _packer_backreferences($match, $offset, $data) { + $replacement = $data['replacement']; + $i = $data['length']; + while ($i) { + $replacement = str_replace('$'.$i--, $match[$offset + $i], $replacement); + } + return $replacement; +} + +/** + * Delete all cached JS files. + */ +function drupal_clear_js_cache() { + file_scan_directory(file_create_path('js'), '.*', array('.', '..', 'CVS'), 'file_delete', TRUE); +} + /** * Converts a PHP variable into its Javascript equivalent. * diff --git a/misc/autocomplete.js b/misc/autocomplete.js index 166192891cbfb6a81a7c0e810d3115dd0a3530ad..fc0e96b2cb02fbd66a6af5af7f273b3560102003 100644 --- a/misc/autocomplete.js +++ b/misc/autocomplete.js @@ -15,7 +15,7 @@ Drupal.autocompleteAutoAttach = function () { $(input.form).submit(Drupal.autocompleteSubmit); new Drupal.jsAC(input, acdb[uri]); }); -} +}; /** * Prevents the form from submitting if the suggestions popup is open @@ -25,7 +25,7 @@ Drupal.autocompleteSubmit = function () { return $('#autocomplete').each(function () { this.owner.hidePopup(); }).size() == 0; -} +}; /** * An AutoComplete object @@ -59,7 +59,7 @@ Drupal.jsAC.prototype.onkeydown = function (input, e) { default: // all other keys return true; } -} +}; /** * Handler for the "keyup" event @@ -96,14 +96,14 @@ Drupal.jsAC.prototype.onkeyup = function (input, e) { this.hidePopup(e.keyCode); return true; } -} +}; /** * Puts the currently highlighted suggestion into the autocomplete field */ Drupal.jsAC.prototype.select = function (node) { this.input.value = node.autocompleteValue; -} +}; /** * Highlights the next suggestion @@ -118,7 +118,7 @@ Drupal.jsAC.prototype.selectDown = function () { this.highlight(lis.get(0)); } } -} +}; /** * Highlights the previous suggestion @@ -127,7 +127,7 @@ Drupal.jsAC.prototype.selectUp = function () { if (this.selected && this.selected.previousSibling) { this.highlight(this.selected.previousSibling); } -} +}; /** * Highlights a suggestion @@ -138,7 +138,7 @@ Drupal.jsAC.prototype.highlight = function (node) { } $(node).addClass('selected'); this.selected = node; -} +}; /** * Unhighlights a suggestion @@ -146,7 +146,7 @@ Drupal.jsAC.prototype.highlight = function (node) { Drupal.jsAC.prototype.unhighlight = function (node) { $(node).removeClass('selected'); this.selected = false; -} +}; /** * Hides the autocomplete suggestions @@ -163,7 +163,7 @@ Drupal.jsAC.prototype.hidePopup = function (keycode) { $(popup).fadeOut('fast', function() { $(popup).remove(); }); } this.selected = false; -} +}; /** * Positions the suggestions popup and starts a search @@ -187,7 +187,7 @@ Drupal.jsAC.prototype.populatePopup = function () { // Do search this.db.owner = this; this.db.search(this.input.value); -} +}; /** * Fills the suggestion popup with any matches received @@ -222,7 +222,7 @@ Drupal.jsAC.prototype.found = function (matches) { this.hidePopup(); } } -} +}; Drupal.jsAC.prototype.setStatus = function (status) { switch (status) { @@ -235,7 +235,7 @@ Drupal.jsAC.prototype.setStatus = function (status) { $(this.input).removeClass('throbbing'); break; } -} +}; /** * An AutoComplete DataBase object @@ -244,7 +244,7 @@ Drupal.ACDB = function (uri) { this.uri = uri; this.delay = 300; this.cache = {}; -} +}; /** * Performs a cached and delayed search @@ -286,7 +286,7 @@ Drupal.ACDB.prototype.search = function (searchString) { } }); }, this.delay); -} +}; /** * Cancels the current autocomplete request @@ -295,7 +295,7 @@ Drupal.ACDB.prototype.cancel = function() { if (this.owner) this.owner.setStatus('cancel'); if (this.timer) clearTimeout(this.timer); this.searchString = ''; -} +}; // Global Killswitch if (Drupal.jsEnabled) { diff --git a/misc/batch.js b/misc/batch.js index 43117e2431476bc9e4f927fb6349afebdd99e268..8cf1f910f69123aec6772935f144475109c8c523 100644 --- a/misc/batch.js +++ b/misc/batch.js @@ -12,7 +12,7 @@ if (Drupal.jsEnabled) { pb.stopMonitoring(); window.location = uri+'&op=finished'; } - } + }; var errorCallback = function (pb) { var div = document.createElement('p'); @@ -20,7 +20,7 @@ if (Drupal.jsEnabled) { $(div).html(errorMessage); $(holder).prepend(div); $('#wait').hide(); - } + }; var progress = new Drupal.progressBar('updateprogress', updateCallback, "POST", errorCallback); progress.setProgress(-1, initMessage); diff --git a/misc/collapse.js b/misc/collapse.js index 5abe06c14e6021c8f5e201a23cd5dac188329348..58aae89fa7f7e28e761db9e76e7140e8c9a49e2b 100644 --- a/misc/collapse.js +++ b/misc/collapse.js @@ -14,7 +14,7 @@ Drupal.toggleFieldset = function(fieldset) { Drupal.collapseScrollIntoView(this.parentNode); this.parentNode.animating = false; }); - if (typeof Drupal.textareaAttach != 'undefined') { + if (typeof(Drupal.textareaAttach) != 'undefined') { // Initialize resizable textareas that are now revealed Drupal.textareaAttach(null, fieldset); } @@ -25,7 +25,7 @@ Drupal.toggleFieldset = function(fieldset) { this.parentNode.animating = false; }); } -} +}; /** * Scroll a given fieldset into view as much as possible. @@ -42,7 +42,7 @@ Drupal.collapseScrollIntoView = function (node) { window.scrollTo(0, pos.y + node.offsetHeight - h + fudge); } } -} +}; // Global Killswitch if (Drupal.jsEnabled) { diff --git a/misc/drupal.js b/misc/drupal.js index c4fa5d8ab18694bea47187d742039e08f2f3b977..8d46fea5863a6f93c16299e94c96acebef7673c6 100644 --- a/misc/drupal.js +++ b/misc/drupal.js @@ -50,7 +50,7 @@ Drupal.redirectFormButton = function (uri, button, handler) { // Restore form submission button.form.action = action; button.form.target = target; - + // Get response from iframe body try { response = (iframe.contentWindow || iframe.contentDocument || iframe).document.body.innerHTML; @@ -64,7 +64,7 @@ Drupal.redirectFormButton = function (uri, button, handler) { catch (e) { response = null; } - + response = Drupal.parseJson(response); // Check response code if (response.status == 0) { @@ -74,14 +74,14 @@ Drupal.redirectFormButton = function (uri, button, handler) { handler.oncomplete(response.data); return true; - } + }; return true; - } - } + }; + }; button.onmouseout = button.onblur = function() { button.onclick = null; - } + }; }; /** @@ -218,7 +218,7 @@ Drupal.getSelection = function (element) { return { 'start': start, 'end': end }; } return { 'start': element.selectionStart, 'end': element.selectionEnd }; -} +}; // Global Killswitch on the <html> element if (Drupal.jsEnabled) { diff --git a/misc/farbtastic/farbtastic.js b/misc/farbtastic/farbtastic.js index f37df7d721a1b30e44dae25e1329e2e5db08764e..c89e94029a7d5873320fdcf6429707f1da1ac154 100644 --- a/misc/farbtastic/farbtastic.js +++ b/misc/farbtastic/farbtastic.js @@ -9,7 +9,7 @@ jQuery.fn.farbtastic = function (callback) { jQuery.farbtastic = function (container, callback) { var container = $(container).get(0); return container.farbtastic || (container.farbtastic = new jQuery._farbtastic(container, callback)); -} +}; jQuery._farbtastic = function (container, callback) { // Store farbtastic object @@ -62,12 +62,12 @@ jQuery._farbtastic = function (container, callback) { } } return this; - } + }; fb.updateValue = function (event) { if (this.value && this.value != fb.color) { fb.setColor(this.value); } - } + }; /** * Change color with HTML syntax #123456 @@ -81,7 +81,7 @@ jQuery._farbtastic = function (container, callback) { fb.updateDisplay(); } return this; - } + }; /** * Change color with HSL triplet [0..1, 0..1, 0..1] @@ -92,7 +92,7 @@ jQuery._farbtastic = function (container, callback) { fb.color = fb.pack(fb.rgb); fb.updateDisplay(); return this; - } + }; ///////////////////////////////////////////////////// @@ -121,7 +121,7 @@ jQuery._farbtastic = function (container, callback) { // Look for the coordinates starting from the wheel widget. var e = reference; - var offset = { x: 0, y: 0 } + var offset = { x: 0, y: 0 }; while (e) { if (typeof e.mouseX != 'undefined') { x = e.mouseX - offset.x; @@ -149,7 +149,7 @@ jQuery._farbtastic = function (container, callback) { } // Subtract distance to middle return { x: x - fb.width / 2, y: y - fb.width / 2 }; - } + }; /** * Mousedown handler @@ -168,7 +168,7 @@ jQuery._farbtastic = function (container, callback) { // Process fb.mousemove(event); return false; - } + }; /** * Mousemove handler @@ -189,7 +189,7 @@ jQuery._farbtastic = function (container, callback) { fb.setHSL([fb.hsl[0], sat, lum]); } return false; - } + }; /** * Mouseup handler @@ -199,7 +199,7 @@ jQuery._farbtastic = function (container, callback) { $(document).unbind('mousemove', fb.mousemove); $(document).unbind('mouseup', fb.mouseup); document.dragging = false; - } + }; /** * Update the markers and styles @@ -238,7 +238,7 @@ jQuery._farbtastic = function (container, callback) { else if (typeof fb.callback == 'function') { fb.callback.call(fb, fb.color); } - } + }; /** * Get absolute position of element @@ -262,7 +262,7 @@ jQuery._farbtastic = function (container, callback) { return '#' + (r < 16 ? '0' : '') + r.toString(16) + (g < 16 ? '0' : '') + g.toString(16) + (b < 16 ? '0' : '') + b.toString(16); - } + }; fb.unpack = function (color) { if (color.length == 7) { @@ -275,7 +275,7 @@ jQuery._farbtastic = function (container, callback) { parseInt('0x' + color.substring(2, 3)) / 15, parseInt('0x' + color.substring(3, 4)) / 15]; } - } + }; fb.HSLToRGB = function (hsl) { var m1, m2, r, g, b; @@ -285,7 +285,7 @@ jQuery._farbtastic = function (container, callback) { return [this.hueToRGB(m1, m2, h+0.33333), this.hueToRGB(m1, m2, h), this.hueToRGB(m1, m2, h-0.33333)]; - } + }; fb.hueToRGB = function (m1, m2, h) { h = (h < 0) ? h + 1 : ((h > 1) ? h - 1 : h); @@ -293,7 +293,7 @@ jQuery._farbtastic = function (container, callback) { if (h * 2 < 1) return m2; if (h * 3 < 2) return m1 + (m2 - m1) * (0.66666 - h) * 6; return m1; - } + }; fb.RGBToHSL = function (rgb) { var min, max, delta, h, s, l; @@ -314,7 +314,7 @@ jQuery._farbtastic = function (container, callback) { h /= 6; } return [h, s, l]; - } + }; // Install mousedown handler (the others are set on the document on-demand) $('*', e).mousedown(fb.mousedown); @@ -326,4 +326,4 @@ jQuery._farbtastic = function (container, callback) { if (callback) { fb.linkTo(callback); } -} \ No newline at end of file +}; \ No newline at end of file diff --git a/misc/progress.js b/misc/progress.js index cf5c120179e8788a3805d97bbca3de642aef8d3f..e9e07ee482f1224234c04e68904d705259ca4eef 100644 --- a/misc/progress.js +++ b/misc/progress.js @@ -23,7 +23,7 @@ Drupal.progressBar = function (id, updateCallback, method, errorCallback) { $(this.element).html('<div class="bar"><div class="filled"></div></div>'+ '<div class="percentage"></div>'+ '<div class="message"> </div>'); -} +}; /** * Set the percentage and status message for the progressbar. @@ -37,7 +37,7 @@ Drupal.progressBar.prototype.setProgress = function (percentage, message) { if (this.updateCallback) { this.updateCallback(percentage, message, this); } -} +}; /** * Start monitoring progress via Ajax. @@ -46,7 +46,7 @@ Drupal.progressBar.prototype.startMonitoring = function (uri, delay) { this.delay = delay; this.uri = uri; this.sendPing(); -} +}; /** * Stop monitoring progress via Ajax. @@ -55,7 +55,7 @@ Drupal.progressBar.prototype.stopMonitoring = function () { clearTimeout(this.timer); // This allows monitoring to be stopped from within the callback this.uri = null; -} +}; /** * Request progress data from server. @@ -90,7 +90,7 @@ Drupal.progressBar.prototype.sendPing = function () { } }); } -} +}; /** * Display errors on the page. @@ -105,4 +105,4 @@ Drupal.progressBar.prototype.displayError = function (string) { if (this.errorCallback) { this.errorCallback(this); } -} +}; diff --git a/misc/tableheader.js b/misc/tableheader.js index 470d890cf0dcb9378b274d0abab70be50d8873ab..5b84126ca4f0c73f7940d38f2ba00300f0368bd4 100644 --- a/misc/tableheader.js +++ b/misc/tableheader.js @@ -86,7 +86,7 @@ if (Drupal.jsEnabled) { // Precalculate table heights $('table.sticky-table').each(function () { this.height = $(this).height(); - }) + }); $(cells).each(function () { // Get position. diff --git a/misc/tableselect.js b/misc/tableselect.js index 052af02dd6b58ea584f3b4eeaa24f7306e2cdc75..5020f7931500c6d99d878a3e66e976bde8d5fa97 100644 --- a/misc/tableselect.js +++ b/misc/tableselect.js @@ -40,7 +40,7 @@ Drupal.tableSelect = function() { // Keep track of the last checked checkbox. lastChecked = e.target; }); -} +}; Drupal.tableSelectRange = function(from, to, state) { // We determine the looping mode based on the the order of from and to. @@ -65,7 +65,7 @@ Drupal.tableSelectRange = function(from, to, state) { else if (jQuery.filter(to, [i]).r.length) break; } -} +}; // Global Killswitch if (Drupal.jsEnabled) { diff --git a/misc/teaser.js b/misc/teaser.js index 12275fc397e638dd06444c3fce2698f0884ec71f..eeb8473058bff911d0728357bd0f3f18fee37dfc 100644 --- a/misc/teaser.js +++ b/misc/teaser.js @@ -73,7 +73,7 @@ Drupal.teaserAttach = function() { } }); -} +}; if (Drupal.jsEnabled) { $(document).ready(Drupal.teaserAttach); diff --git a/misc/textarea.js b/misc/textarea.js index 8e523235ed921cc02af9617f6e3bf8ea097b865e..9cd5a1c8499928fab7691fc853c3047e6a7d7209 100644 --- a/misc/textarea.js +++ b/misc/textarea.js @@ -36,7 +36,7 @@ Drupal.textareaAttach = function() { textarea.css('opacity', 1); } }); -} +}; if (Drupal.jsEnabled) { $(document).ready(Drupal.textareaAttach); diff --git a/misc/upload.js b/misc/upload.js index 5dd32cf81d80f4d460620bb39dde3d44921dae4d..bcc1e6accbf489155d3f7bd6c7142b72c10b5178 100644 --- a/misc/upload.js +++ b/misc/upload.js @@ -13,7 +13,7 @@ Drupal.uploadAutoAttach = function() { var hide = base + '-hide'; var upload = new Drupal.jsUpload(uri, button, wrapper, hide); }); -} +}; /** * JS upload object. @@ -25,7 +25,7 @@ Drupal.jsUpload = function(uri, button, wrapper, hide) { this.wrapper = '#'+ wrapper; this.hide = '#'+ hide; Drupal.redirectFormButton(uri, $(this.button).get(0), this); -} +}; /** * Handler for the form redirection submission. @@ -49,7 +49,7 @@ Drupal.jsUpload.prototype.onsubmit = function () { $(hide).after(el); $(el).fadeIn('slow'); $(hide).fadeOut('slow'); -} +}; /** * Handler for the form redirection completion. @@ -92,7 +92,7 @@ Drupal.jsUpload.prototype.oncomplete = function (data) { Drupal.uploadAutoAttach(); } Drupal.unfreezeHeight(); -} +}; /** * Handler for the form redirection error. @@ -107,7 +107,7 @@ Drupal.jsUpload.prototype.onerror = function (error) { position: 'static', left: '0px' }); -} +}; // Global killswitch diff --git a/modules/color/color.js b/modules/color/color.js index 366e73d973443336d29142e74aa167dc391fefa2..481e4e34ef9d0f25228b4d995ff7b8b082b39d39 100644 --- a/modules/color/color.js +++ b/modules/color/color.js @@ -223,7 +223,7 @@ if (Drupal.jsEnabled) { ); $(this).after(lock); locks.push(lock); - } + }; // Add hook var hook = $('<div class="hook"></div>'); @@ -236,7 +236,7 @@ if (Drupal.jsEnabled) { }) .focus(focus); - $('#palette label', form) + $('#palette label', form); // Focus first color focus.call(inputs[0]); diff --git a/modules/system/system.js b/modules/system/system.js index 4d254033c91fc5f36915b78433b770e23bd68b21..900d11ac17ca4c6200226db801568358774a2093 100644 --- a/modules/system/system.js +++ b/modules/system/system.js @@ -23,7 +23,7 @@ Drupal.cleanURLsSettingsCheck = function() { $("#clean-url .description span").append('<div class="warning">'+ Drupal.settings.cleanURL.failure +"</div>"); } }}); -} +}; /** * Internal function to check using Ajax if clean URLs can be enabled on the @@ -50,12 +50,12 @@ Drupal.cleanURLsInstallCheck = function() { $("#clean-url .description span").append('<div class="warning">'+ Drupal.settings.cleanURL.failure +"</div>"); } }}); -} +}; Drupal.installDefaultTimezone = function() { var offset = new Date().getTimezoneOffset() * -60; $("#edit-date-default-timezone").val(offset); -} +}; /** * Show/hide custom format sections on the date-time settings page. diff --git a/modules/system/system.module b/modules/system/system.module index 3a3e094c3bc6b897aa4f71e3e7d31798e5d6e9f0..ea2142dfca0fb1f931176d55f44d85404cc1c45d 100644 --- a/modules/system/system.module +++ b/modules/system/system.module @@ -262,7 +262,7 @@ function system_menu() { ); $items['admin/settings/performance'] = array( 'title' => 'Performance', - 'description' => 'Enable or disable page caching for anonymous users, and enable or disable CSS preprocessor.', + 'description' => 'Enable or disable page caching for anonymous users and set CSS and JS bandwidth optimization options.', 'page callback' => 'drupal_get_form', 'page arguments' => array('system_performance_settings'), ); @@ -700,18 +700,26 @@ function system_performance_settings() { $form['bandwidth_optimizations'] = array( '#type' => 'fieldset', '#title' => t('Bandwidth optimizations'), - '#description' => t('These options can help reduce both the size and number of requests made to your website. This can reduce the server load, the bandwidth used, and the average page loading time for your visitors.') + '#description' => t('<p>Drupal can automatically aggregate and compress external resources like CSS and JavaScript into a single cached file. This can help reduce both the size and number of requests made to your website. This in turn reduces the server load, the bandwidth used, and the average page loading time for your visitors.</p><p>These options are disabled if you have not set up your files directory, or if your download method is set to private.</p>') ); $directory = file_directory_path(); $is_writable = is_dir($directory) && is_writable($directory) && (variable_get('file_downloads', FILE_DOWNLOADS_PUBLIC) == FILE_DOWNLOADS_PUBLIC); $form['bandwidth_optimizations']['preprocess_css'] = array( '#type' => 'radios', - '#title' => t('Aggregate and compress CSS files'), - '#default_value' => variable_get('preprocess_css', FALSE) && $is_writable, + '#title' => t('Optimize CSS files'), + '#default_value' => variable_get('preprocess_css', 0) && $is_writable, '#disabled' => !$is_writable, '#options' => array(t('Disabled'), t('Enabled')), - '#description' => t("Some Drupal modules include their own CSS files. When these modules are enabled, each module's CSS file adds an additional HTTP request to the page, which can increase the load time of each page. These HTTP requests can also slightly increase server load. It is recommended to only turn this option on when your site is in production, as it can interfere with theme development. This option is disabled if you have not set up your files directory, or if your download method is set to private."), + '#description' => t("This option can interfere with theme development. It is recommended to only turn this on when your site is complete."), + ); + $form['bandwidth_optimizations']['preprocess_js'] = array( + '#type' => 'radios', + '#title' => t('Optimize JavaScript files'), + '#default_value' => variable_get('preprocess_js', 0) && $is_writable, + '#disabled' => !$is_writable, + '#options' => array(t('Disabled'), t('Enabled')), + '#description' => t("This option can interfere with module development. It is recommended to only turn this on when your site is complete."), ); $form['reverse_proxy'] = array( @@ -729,6 +737,7 @@ function system_performance_settings() { ); $form['#submit'][] = 'drupal_clear_css_cache'; + $form['#submit'][] = 'drupal_clear_js_cache'; return system_settings_form($form); } @@ -1735,6 +1744,7 @@ function system_modules_submit($form, &$form_state, $form_values) { } drupal_clear_css_cache(); + drupal_clear_js_cache(); $form_state['redirect'] = 'admin/build/modules'; diff --git a/update.php b/update.php index feb970a7f5691cb1e8305a1c552fa0c2ab1c023e..2dcda2914534e92eb85672b9aef0856139029990 100644 --- a/update.php +++ b/update.php @@ -400,6 +400,7 @@ function update_finished($success, $results, $operations) { cache_clear_all('*', 'cache_page', TRUE); cache_clear_all('*', 'cache_filter', TRUE); drupal_clear_css_cache(); + drupal_clear_js_cache(); $_SESSION['update_results'] = $results; $_SESSION['update_success'] = $success;