diff --git a/Gruntfile.js b/Gruntfile.js index 3a796e943d..77616e164d 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -15,12 +15,15 @@ * npm install grunt --save-dev * npm install grunt-contrib-uglify --save-dev * npm install grunt-newer --save-dev + * npm install grunt-contrib-cssmin --save-dev * * Building happens by running in your EGroupware directory: * - * grunt # runs uglify for all targets with changed files + * grunt # runs uglify and cssmin for all targets with changed files * or * grunt [newer:]uglify: # targets: api, et2, pixelegg, mobile, mail, calendar, ... + * or + * grunt [newer:]cssmin: # targets: pixelegg, jdots * * app.js files can be added like mail target or, if you want automatic dependencies, * you need to add them in egw_framework::$bundle2minurl and egw_framework::get_bundles(). @@ -258,14 +261,136 @@ module.exports = function (grunt) { ] } } + }, + cssmin: { + options: { + shorthandCompacting: false, + sourceMap: true, + relativeTo: "pixelegg\/css\/" + }, + pixelegg: { + files: { + "pixelegg\/css\/pixelegg.min.css": [ + "api\/js\/jquery\/chosen\/chosen.css", + "api\/js\/jquery\/jquery-ui\/redmond\/jquery-ui.css", + "api\/js\/jquery\/magicsuggest\/magicsuggest.css", + "api\/js\/jquery\/jpicker\/css\/jPicker-1.1.6.min.css", + "api\/js\/jquery\/jquery-ui-timepicker-addon.css", + "api\/js\/jquery\/blueimp\/css\/blueimp-gallery.min.css", + "api\/js\/dhtmlxtree\/codebase\/dhtmlXTree.css", + "api\/js\/egw_action\/test\/skins\/dhtmlxmenu_egw.css", + "api\/js\/etemplate\/lib\/jsdifflib\/diffview.css", + "api\/templates\/default\/etemplate2.css", + "pixelegg\/css\/pixelegg.css", + "phpgwapi\/templates\/idots\/print.css", + "jdots\/print.css", + "pixelegg\/print.css" + ], + "pixelegg\/css\/mobile.min.css": [ + "api\/js\/jquery\/chosen\/chosen.css", + "api\/js\/jquery\/jquery-ui\/redmond\/jquery-ui.css", + "api\/js\/jquery\/magicsuggest\/magicsuggest.css", + "api\/js\/jquery\/jpicker\/css\/jPicker-1.1.6.min.css", + "api\/js\/jquery\/jquery-ui-timepicker-addon.css", + "api\/js\/jquery\/blueimp\/css\/blueimp-gallery.min.css", + "api\/js\/dhtmlxtree\/codebase\/dhtmlXTree.css", + "api\/js\/egw_action\/test\/skins\/dhtmlxmenu_egw.css", + "api\/js\/etemplate\/lib\/jsdifflib\/diffview.css", + "api\/templates\/default\/etemplate2.css", + "pixelegg\/css\/mobile.css", + "phpgwapi\/templates\/idots\/print.css", + "jdots\/print.css", + "pixelegg\/print.css" + ], + "pixelegg\/mobile\/fw_mobile.min.css": [ + "api\/js\/jquery\/chosen\/chosen.css", + "api\/js\/jquery\/jquery-ui\/redmond\/jquery-ui.css", + "api\/js\/jquery\/magicsuggest\/magicsuggest.css", + "api\/js\/jquery\/jpicker\/css\/jPicker-1.1.6.min.css", + "api\/js\/jquery\/jquery-ui-timepicker-addon.css", + "api\/js\/jquery\/blueimp\/css\/blueimp-gallery.min.css", + "api\/js\/dhtmlxtree\/codebase\/dhtmlXTree.css", + "api\/js\/egw_action\/test\/skins\/dhtmlxmenu_egw.css", + "api\/js\/etemplate\/lib\/jsdifflib\/diffview.css", + "api\/templates\/default\/etemplate2.css", + "pixelegg\/mobile\/fw_mobile.css", + "phpgwapi\/templates\/idots\/print.css", + "jdots\/print.css", + "pixelegg\/print.css" + ] + } + }, + jdots: { + files: { + "jdots\/css\/high-contrast.min.css": [ + "api\/js\/jquery\/chosen\/chosen.css", + "api\/js\/jquery\/jquery-ui\/redmond\/jquery-ui.css", + "api\/js\/jquery\/magicsuggest\/magicsuggest.css", + "api\/js\/jquery\/jpicker\/css\/jPicker-1.1.6.min.css", + "api\/js\/jquery\/jquery-ui-timepicker-addon.css", + "api\/js\/jquery\/blueimp\/css\/blueimp-gallery.min.css", + "api\/js\/dhtmlxtree\/codebase\/dhtmlXTree.css", + "api\/js\/egw_action\/test\/skins\/dhtmlxmenu_egw.css", + "api\/js\/etemplate\/lib\/jsdifflib\/diffview.css", + "api\/templates\/default\/etemplate2.css", + "phpgwapi\/templates\/default\/def_tutorials.css", + "phpgwapi\/templates\/idots\/css\/traditional.css", + "jdots\/egw_fw.css", + "jdots\/css\/jdots.css", + "jdots\/css\/high-contrast.css", + "phpgwapi\/templates\/idots\/print.css", + "jdots\/print.css" + ], + "jdots\/css\/jdots.min.css": [ + "api\/js\/jquery\/chosen\/chosen.css", + "api\/js\/jquery\/jquery-ui\/redmond\/jquery-ui.css", + "api\/js\/jquery\/magicsuggest\/magicsuggest.css", + "api\/js\/jquery\/jpicker\/css\/jPicker-1.1.6.min.css", + "api\/js\/jquery\/jquery-ui-timepicker-addon.css", + "api\/js\/jquery\/blueimp\/css\/blueimp-gallery.min.css", + "api\/js\/dhtmlxtree\/codebase\/dhtmlXTree.css", + "api\/js\/egw_action\/test\/skins\/dhtmlxmenu_egw.css", + "api\/js\/etemplate\/lib\/jsdifflib\/diffview.css", + "api\/templates\/default\/etemplate2.css", + "phpgwapi\/templates\/default\/def_tutorials.css", + "phpgwapi\/templates\/idots\/css\/traditional.css", + "jdots\/egw_fw.css", + "jdots\/css\/jdots.css", + "phpgwapi\/templates\/idots\/print.css", + "jdots\/print.css" + ], + "jdots\/css\/orange-green.min.css": [ + "api\/js\/jquery\/chosen\/chosen.css", + "api\/js\/jquery\/jquery-ui\/redmond\/jquery-ui.css", + "api\/js\/jquery\/magicsuggest\/magicsuggest.css", + "api\/js\/jquery\/jpicker\/css\/jPicker-1.1.6.min.css", + "api\/js\/jquery\/jquery-ui-timepicker-addon.css", + "api\/js\/jquery\/blueimp\/css\/blueimp-gallery.min.css", + "api\/js\/dhtmlxtree\/codebase\/dhtmlXTree.css", + "api\/js\/egw_action\/test\/skins\/dhtmlxmenu_egw.css", + "api\/js\/etemplate\/lib\/jsdifflib\/diffview.css", + "api\/templates\/default\/etemplate2.css", + "phpgwapi\/templates\/default\/def_tutorials.css", + "phpgwapi\/templates\/idots\/css\/traditional.css", + "jdots\/egw_fw.css", + "jdots\/css\/jdots.css", + "jdots\/css\/orange-green.css", + "phpgwapi\/templates\/idots\/print.css", + "jdots\/print.css" + ] + } + } } }); // Load the plugin that provides the "uglify" task. grunt.loadNpmTasks('grunt-contrib-uglify'); + // Load plugin for css minificaton + grunt.loadNpmTasks('grunt-contrib-cssmin'); + // Load the plugin that runs tasks only on modified files grunt.loadNpmTasks('grunt-newer'); // Default task(s). - grunt.registerTask('default', ['newer:uglify']); + grunt.registerTask('default', ['newer:uglify', 'newer:cssmin']); }; diff --git a/api/js/egw_action/egw_menu_dhtmlx.js b/api/js/egw_action/egw_menu_dhtmlx.js index 24bf44deb2..cf56ea272b 100644 --- a/api/js/egw_action/egw_menu_dhtmlx.js +++ b/api/js/egw_action/egw_menu_dhtmlx.js @@ -17,7 +17,12 @@ */ // Need CSS, or it doesn't really work -if(typeof egw == 'function') egw(window).includeCSS(egw.webserverUrl + "/api/js/egw_action/test/skins/dhtmlxmenu_egw.css"); +//if(typeof egw == 'function') egw(window).includeCSS(egw.webserverUrl + "/api/js/egw_action/test/skins/dhtmlxmenu_egw.css"); + +/** + * + * @param {type} _structure + */ function egwMenuImpl(_structure) { //Create a new dhtmlxmenu object @@ -160,7 +165,7 @@ egwMenuImpl.prototype._translateStructure = function(_structure, _parentId, _idC } return counter; -} +}; egwMenuImpl.prototype.showAt = function(_x, _y, _onHide) @@ -182,11 +187,9 @@ egwMenuImpl.prototype.showAt = function(_x, _y, _onHide) self.dhtmlxmenu.showContextMenu(_x, _y); // TODO: Get keybard focus }, 0); -} +}; egwMenuImpl.prototype.hide = function() { this.dhtmlxmenu.hide(); -} - - +}; diff --git a/api/js/etemplate/et2_widget_color.js b/api/js/etemplate/et2_widget_color.js index 42cb6286de..6e1dae7b1f 100644 --- a/api/js/etemplate/et2_widget_color.js +++ b/api/js/etemplate/et2_widget_color.js @@ -56,7 +56,8 @@ var et2_color = (function(){ "use strict"; return et2_inputWidget.extend( init: function() { this._super.apply(this, arguments); - this.egw().includeCSS("phpgwapi/js/jquery/jpicker/css/jPicker-1.1.6.min.css"); + // included via etemplate2.css + //this.egw().includeCSS("phpgwapi/js/jquery/jpicker/css/jPicker-1.1.6.min.css"); this.input = this.$node = jQuery(document.createElement("span")); // Translations diff --git a/api/js/etemplate/et2_widget_diff.js b/api/js/etemplate/et2_widget_diff.js index 98262ecec1..5f1b7cfbde 100644 --- a/api/js/etemplate/et2_widget_diff.js +++ b/api/js/etemplate/et2_widget_diff.js @@ -40,7 +40,8 @@ var et2_diff = (function(){ "use strict"; return et2_valueWidget.extend([et2_IDe this._super.apply(this, arguments); this.mini = true; - this.egw().includeCSS('etemplate/js/lib/jsdifflib/diffview.css'); + // included via etemplate2.css + //this.egw().includeCSS('etemplate/js/lib/jsdifflib/diffview.css'); this.div = document.createElement("div"); jQuery(this.div).addClass('diff'); }, @@ -106,6 +107,8 @@ var et2_diff = (function(){ "use strict"; return et2_valueWidget.extend([et2_IDe /** * Make the diff into a mini-diff + * + * @param {DOMNode|String} view */ minify: function(view) { view = jQuery(view) @@ -118,6 +121,11 @@ var et2_diff = (function(){ "use strict"; return et2_valueWidget.extend([et2_IDe .prevAll().hide(); }, + /** + * Expand mini-diff + * + * @param {DOMNode|String} view + */ un_minify: function(view) { jQuery(view).removeClass('mini').show(); jQuery('th',view).show(); @@ -133,6 +141,8 @@ var et2_diff = (function(){ "use strict"; return et2_valueWidget.extend([et2_IDe * Build a list of attributes which can be set when working in the * "detached" mode in the _attrs array which is provided * by the calling code. + * + * @param {object} _attrs */ getDetachedAttributes: function(_attrs) { _attrs.push("value", "label"); diff --git a/api/js/etemplate/et2_widget_taglist.js b/api/js/etemplate/et2_widget_taglist.js index 21240e1079..ec35e3e379 100644 --- a/api/js/etemplate/et2_widget_taglist.js +++ b/api/js/etemplate/et2_widget_taglist.js @@ -358,7 +358,7 @@ var et2_taglist = (function(){ "use strict"; return et2_selectbox.extend([et2_IR $j('.ms-trigger',this.div).on('click', function(e) { e.stopPropagation(); - }) + }); // Unbind change handler of widget's ancestor to stop it from bubbling // taglist has its own onchange $j(this.getDOMNode()).unbind('change.et2_inputWidget'); @@ -458,7 +458,7 @@ var et2_taglist = (function(){ "use strict"; return et2_selectbox.extend([et2_IR this._query_server = false; // Turn on local filtering, or trust server to do it - cfg.mode = typeof return_value === 'string' ? 'remote' : 'local' + cfg.mode = typeof return_value === 'string' ? 'remote' : 'local'; return return_value; }, @@ -1251,5 +1251,5 @@ var et2_taglist_ro = (function(){ "use strict"; return et2_selectbox_ro.extend( et2_register_widget(et2_taglist_ro, ["taglist_ro","taglist_email_ro", "taglist_account_ro" ]); // Require css -// TODO: merge into etemplate2.css with all other widgets when done -if(typeof egw == 'function') egw(window).includeCSS(egw.webserverUrl + "/api/js/jquery/magicsuggest/magicsuggest.css"); +// included via etemplate2.css +//if(typeof egw == 'function') egw(window).includeCSS(egw.webserverUrl + "/api/js/jquery/magicsuggest/magicsuggest.css"); diff --git a/api/src/Etemplate.php b/api/src/Etemplate.php index 41ef2e8b65..8b6b298025 100644 --- a/api/src/Etemplate.php +++ b/api/src/Etemplate.php @@ -211,15 +211,6 @@ class Etemplate extends Etemplate\Widget\Template } else // first call { - // missing dependency, thought egw:uses jquery.jquery.tools does NOT work, maybe we should rename it to jquery-tools - // Framework::includeJS('jquery','jquery.tools.min'); - - // Include the jQuery-UI CSS - many more complex widgets use it - $theme = 'redmond'; - Framework::includeCSS("/api/js/jquery/jquery-ui/$theme/jquery-ui-1.10.3.custom.css"); - // Load our CSS after jQuery-UI, so we can override it - Framework::includeCSS('/api/templates/default/etemplate2.css'); - // check if application of template has a app.js file --> load it list($app) = explode('.',$this->name); if (file_exists(EGW_SERVER_ROOT.'/'.$app.'/js/app.js')) diff --git a/api/src/Framework.php b/api/src/Framework.php index 2ac746f38d..d1065eba6f 100644 --- a/api/src/Framework.php +++ b/api/src/Framework.php @@ -814,43 +814,54 @@ abstract class Framework extends Framework\Extra if (self::$load_default_css) { - // Load these first - // Cascade should go: - // Libs < etemplate2 < framework/theme < app < print - // Enhanced selectboxes (et1) - self::includeCSS('/api/js/jquery/chosen/chosen.css'); - - // eTemplate2 uses jQueryUI, so load it first so et2 can override if needed - self::includeCSS("/api/js/jquery/jquery-ui/redmond/jquery-ui.css"); - - // eTemplate2 - load in top so sidebox has styles too - self::includeCSS('/api/templates/default/etemplate2.css'); - - // Category styles - Categories::css(Categories::GLOBAL_APPNAME); - // For mobile user-agent we prefer mobile theme over selected one with a final fallback to theme named as template $themes_to_check = array(); - if (Header\UserAgent::mobile()) $themes_to_check[] = $this->template_dir.'/mobile/fw_mobile.css'; + if (Header\UserAgent::mobile() || $GLOBALS['egw_info']['user']['preferences']['common']['theme'] == 'fw_mobile') + { + $themes_to_check[] = $this->template_dir.'/mobile/fw_mobile.css'; + } $themes_to_check[] = $this->template_dir.'/css/'.$GLOBALS['egw_info']['user']['preferences']['common']['theme'].'.css'; $themes_to_check[] = $this->template_dir.'/css/'.$this->template.'.css'; foreach($themes_to_check as $theme_css) { if (file_exists(EGW_SERVER_ROOT.$theme_css)) break; } - self::includeCSS($theme_css); + $debug_minify = $GLOBALS['egw_info']['server']['debug_minify'] === 'True'; + if (!$debug_minify && file_exists(EGW_SERVER_ROOT.($theme_min_css = str_replace('.css', '.min.css', $theme_css)))) + { + error_log(__METHOD__."() Framework\CssIncludes::get()=".array2string(Framework\CssIncludes::get())); + self::includeCSS($theme_min_css); + } + else + { + // Load these first + // Cascade should go: + // Libs < etemplate2 < framework/theme < app < print + // Enhanced selectboxes (et1) + self::includeCSS('/api/js/jquery/chosen/chosen.css'); + // eTemplate2 uses jQueryUI, so load it first so et2 can override if needed + self::includeCSS("/api/js/jquery/jquery-ui/redmond/jquery-ui.css"); + + // eTemplate2 - load in top so sidebox has styles too + self::includeCSS('/api/templates/default/etemplate2.css'); + + // Category styles + Categories::css(Categories::GLOBAL_APPNAME); + + self::includeCSS($theme_css); + + // sending print css last, so it can overwrite anything + $print_css = $this->template_dir.'/print.css'; + if(!file_exists(EGW_SERVER_ROOT.$print_css)) + { + $print_css = '/phpgwapi/templates/idots/print.css'; + } + self::includeCSS($print_css); + } // search for app specific css file, so it can customize the theme self::includeCSS($GLOBALS['egw_info']['flags']['currentapp'], 'app-'.$GLOBALS['egw_info']['user']['preferences']['common']['theme']) || self::includeCSS($GLOBALS['egw_info']['flags']['currentapp'], 'app'); - - // sending print css last, so it can overwrite anything - $print_css = $this->template_dir.'/print.css'; - if(!file_exists(EGW_SERVER_ROOT.$print_css)) - { - $print_css = '/phpgwapi/templates/idots/print.css'; - } - self::includeCSS($print_css); } return array( 'app_css' => $app_css, @@ -950,7 +961,7 @@ abstract class Framework extends Framework\Extra function list_themes() { $list = array(); - if (($dh = @opendir(EGW_SERVER_ROOT.$this->template_dir . SEP . 'css'))) + if (($dh = @opendir(EGW_SERVER_ROOT.$this->template_dir.'/css'))) { while (($file = readdir($dh))) { @@ -1293,6 +1304,7 @@ abstract class Framework extends Framework\Extra { self::$load_default_css = false; } + error_log(__METHOD__."('$app', '$name', append=$append, no_default=$no_default_css) ".function_backtrace()); return Framework\CssIncludes::add($app, $name, $append, $no_default_css); } @@ -1310,6 +1322,7 @@ abstract class Framework extends Framework\Extra // add all css files from Framework::includeCSS() $query = null; +error_log(__METHOD__."() Framework\CssIncludes::get()=".array2string(Framework\CssIncludes::get())); foreach(Framework\CssIncludes::get() as $path) { unset($query); diff --git a/api/src/Framework/CssIncludes.php b/api/src/Framework/CssIncludes.php index 20caed86b1..ce9c60f277 100644 --- a/api/src/Framework/CssIncludes.php +++ b/api/src/Framework/CssIncludes.php @@ -79,9 +79,21 @@ class CssIncludes * * @return string */ - public static function get() + public static function get($resolve=false) { - return self::$files; + if (!$resolve) + { + return self::$files; + } + $files = array(); + foreach(self::$files as $path) + { + foreach(self::resolve_css_includes($path) as $path) + { + $files[] = $path; + } + } + return $files; } /** @@ -93,7 +105,7 @@ class CssIncludes { // add all css files from self::includeCSS $max_modified = 0; - $debug_minify = $GLOBALS['egw_info']['server']['debug_minify'] === 'True'; + $debug_minify = true; //no more dynamic minifying: $GLOBALS['egw_info']['server']['debug_minify'] === 'True'; $base_path = $GLOBALS['egw_info']['server']['webserver_url']; if ($base_path[0] != '/') $base_path = parse_url($base_path, PHP_URL_PATH); $css_files = ''; @@ -109,12 +121,14 @@ class CssIncludes { $css_files .= ''."\n"; } + /* no more dynamic minifying else { $css_file .= ($css_file ? ',' : '').substr($path, 1); - } + }*/ } } + /* no more dynamic minifying if (!$debug_minify) { $css = $GLOBALS['egw_info']['server']['webserver_url'].'/phpgwapi/inc/min/?'; @@ -123,7 +137,7 @@ class CssIncludes ($GLOBALS['egw_info']['server']['debug_minify'] === 'debug' ? '&debug' : ''). '&'.$max_modified; $css_files = ''."\n".$css_files; - } + }*/ return $css_files; } diff --git a/api/templates/default/etemplate2.css b/api/templates/default/etemplate2.css index 2cc3344616..74bc3434ad 100644 --- a/api/templates/default/etemplate2.css +++ b/api/templates/default/etemplate2.css @@ -13,6 +13,7 @@ /*@import url("../../js/jquery/blueimp/css/blueimp-gallery.min.css");*/ /*@import url("../../js/dhtmlxtree/codebase/dhtmlXTree.css");*/ /*@import url("../../js/egw_action/test/skins/dhtmlxmenu_egw.css");*/ +/*@import url("../../js/etemplate/lib/jsdifflib/diffview.css");*/ /** diff --git a/package.json b/package.json index eee7c02d42..3863c18321 100644 --- a/package.json +++ b/package.json @@ -7,6 +7,7 @@ "repository": {}, "devDependencies": { "grunt": "^0.4.5", + "grunt-contrib-cssmin": "^1.0.1", "grunt-contrib-uglify": "^0.11.1", "grunt-newer": "^1.1.2" } diff --git a/updateGruntfile.php b/updateGruntfile.php index cf5cdad8e9..58994deb80 100755 --- a/updateGruntfile.php +++ b/updateGruntfile.php @@ -10,6 +10,7 @@ * @version $Id$ */ +use EGroupware\Api\Framework; use EGroupware\Api\Framework\Bundle; if (php_sapi_name() !== 'cli') die("This is a commandline ONLY tool!\n"); @@ -77,6 +78,39 @@ foreach(Bundle::all() as $name => $files) } } +// add css for all templates and themes +$cssmin =& $config['cssmin']; +$GLOBALS['egw_info']['flags']['currentapp'] = '*grunt*'; // to no find any app.css files +$GLOBALS['egw_info']['server']['debug_minify'] = 'True'; // otherwise we would only get minified file +foreach(array('pixelegg','jdots')/*array_keys(Framework::list_templates())*/ as $template) +{ + $GLOBALS['egw_info']['server']['template_set'] = $template; + $tpl = Framework::factory(); + $themes = $tpl->list_themes(); + if ($template == 'pixelegg') $themes[] = 'fw_mobile'; // this is for mobile devices + foreach($themes as $theme) + { + // skip not working cssmin of pixelegg/traditional: Broken @import declaration of "../../etemplate/templates/default/etemplate2.css" + if ($template == 'pixelegg' && $theme == 'traditional') continue; + $GLOBALS['egw_info']['user']['preferences']['common']['theme'] = $theme; + // empty include list by not-existing file plus last true + Framework\CssIncludes::add('*grunt*', null, true, true); + $tpl->_get_css(); + $dest = substr($tpl->template_dir, 1).($theme == 'fw_mobile' ? '/mobile/' : '/css/').$theme.'.min.css'; + $cssmin[$template]['files'][$dest] = + // remove leading slash from src path + array_map(function($path) + { + return substr($path, 1); + }, + // filter out all dynamic css, like categories.php + array_values(array_filter(Framework\CssIncludes::get(true), function($path) + { + return strpos($path, '.php?') === false; + }))); + } +} + $new_json = str_replace("\n", "\n\t", preg_replace_callback('/^( *)/m', function($matches) {