├── README.md ├── composer.json ├── elasticlayouts.php ├── layouts.js ├── localization ├── en_GB.inc └── en_US.inc └── styles ├── colors.less ├── dark.less ├── layout.less ├── lists.less ├── styles.less └── styles.min.css /README.md: -------------------------------------------------------------------------------- 1 | Roundcube Webmail ElasticLayouts 2 | ================================ 3 | This plugin adds list and desktop layouts to the Elastic skin. Similar to 4 | layouts in the Larry skin this means the preview pane is hidden. It also adds 5 | support for choosing the columns shown in the message list (when in list mode) 6 | and support for fixed table headers. 7 | 8 | A similar result can also be achieved by [extending][wiki] the Elastic skin. 9 | 10 | **This plugin is intended as a proof of concept only.** 11 | 12 | ATTENTION 13 | --------- 14 | This is just a snapshot from the GIT repository and is **NOT A STABLE version 15 | of ElasticLayouts**. It is Intended for use with the **GIT-master** version 16 | of Roundcube and it may not be compatible with older versions. 17 | 18 | License 19 | ------- 20 | This plugin is released under the [GNU General Public License Version 3+][gpl]. 21 | 22 | Even if skins might contain some programming work, they are not considered 23 | as a linked part of the plugin and therefore skins DO NOT fall under the 24 | provisions of the GPL license. See the README file located in the core skins 25 | folder for details on the skin license. 26 | 27 | Install 28 | ------- 29 | * Place this plugin folder into plugins directory of Roundcube 30 | * Add elasticlayouts to $config['plugins'] in your Roundcube config 31 | 32 | **NB:** When downloading the plugin from GitHub you will need to create a 33 | directory called elasticlayouts and place the files in there, ignoring the 34 | root directory in the downloaded archive. 35 | 36 | Config 37 | ------ 38 | This plugin uses the `layout` and `list_cols` settings from the core. 39 | 40 | Customisation 41 | ------------- 42 | Please refer to the README file included with the Elastic skin for details of 43 | how to customise the skin. 44 | 45 | [wiki]: https://github.com/roundcube/roundcubemail/wiki/Skins#extending-skins 46 | [gpl]: https://www.gnu.org/licenses/gpl.html -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "johndoh/elasticlayouts", 3 | "description": "Add support for desktop and list layouts to Elastic skin", 4 | "keywords": ["list","layout"], 5 | "homepage": "https://github.com/johndoh/roundcube-elasticlayouts/", 6 | "license": "GPL-3.0-or-later", 7 | "type": "roundcube-plugin", 8 | "version": "0.1", 9 | "authors": [ 10 | { 11 | "name": "Philip Weir", 12 | "email": "roundcube@tehinterweb.co.uk", 13 | "role": "Developer" 14 | } 15 | ], 16 | "require": { 17 | "php": ">=5.4.0", 18 | "roundcube/plugin-installer": ">=0.1.2" 19 | }, 20 | "extra": { 21 | "roundcube": { 22 | "min-version": "1.5-dev" 23 | } 24 | } 25 | } -------------------------------------------------------------------------------- /elasticlayouts.php: -------------------------------------------------------------------------------- 1 | rcube = rcube::get_instance(); 37 | 38 | // this plugin only works with Elastic skin 39 | if ($this->rcube->config->get('skin') == 'elastic') { 40 | // set supported layouts 41 | $this->rcube->config->set('supported_layouts', ['widescreen', 'desktop', 'list'], true); 42 | $this->add_texts('localization/'); 43 | 44 | if ($this->rcube->task == 'mail' && $this->rcube->action == '') { 45 | $this->rcube->output->add_label('elasticlayouts.columnoptionstitle'); 46 | $this->include_stylesheet('styles/styles.css'); 47 | 48 | $this->dont_override = $this->rcube->config->get('dont_override', []); 49 | 50 | $this->add_hook('template_object_messages', [$this, 'message_list']); 51 | $this->add_hook('template_container', [$this, 'template_container']); 52 | $this->add_hook('render_page', [$this, 'render_page']); 53 | } 54 | } 55 | } 56 | 57 | public function message_list($attrib) 58 | { 59 | unset($attrib['content']); 60 | 61 | $list_options = [ 62 | 'optionsmenuicon' => !in_array('list_cols', $this->dont_override), 63 | 'optionsmenuref' => 'messagelistcolsmenu', 64 | 'optionsmenulabel' => 'elasticlayouts.columnoptions', 65 | 'optionsmenuid' => 'colmenulink', 66 | ]; 67 | 68 | // rebuild the message list template object with new params 69 | $task_handler = new rcmail_action_mail_index(); 70 | $attrib['content'] = $task_handler->message_list($attrib + $list_options); 71 | 72 | return $attrib; 73 | } 74 | 75 | public function template_container($attrib) 76 | { 77 | if ($attrib['name'] == 'listoptions' && !in_array('layout', $this->dont_override)) { 78 | // layout option in list menu 79 | $field_id = 'listoptions-layout'; 80 | 81 | $select = new html_select(['name' => 'layout', 'id' => $field_id]); 82 | $select->add($this->rcube->gettext('layoutwidescreendesc'), 'widescreen'); 83 | $select->add($this->rcube->gettext('layoutdesktopdesc'), 'desktop'); 84 | $select->add($this->rcube->gettext('layoutlistdesc'), 'list'); 85 | 86 | $select_div = html::div('col-sm-8', $select->show($this->rcube->config->get('layout', 'widescreen'))); 87 | $label = html::label(['for' => $field_id, 'class' => 'col-form-label col-sm-4'], rcube::Q($this->rcube->gettext('layout'))); 88 | $content = html::div('form-group row hidden-phone hidden-small', $label . $select_div); 89 | 90 | $attrib['content'] .= $content; 91 | } 92 | 93 | return $attrib; 94 | } 95 | 96 | public function render_page($attrib) 97 | { 98 | $html = ''; 99 | 100 | if (!in_array('list_cols', $this->dont_override)) { 101 | // columns selection menu 102 | $required_cols = ['threads', 'subject']; 103 | $cols = [ 104 | 'threads' => 'threads', 105 | 'subject' => 'subject', 106 | 'fromto' => 'fromto', 107 | 'from' => 'from', 108 | 'to' => 'to', 109 | 'replyto' => 'replyto', 110 | 'cc' => 'cc', 111 | 'date' => 'date', 112 | 'size' => 'size', 113 | 'status' => 'readstatus', 114 | 'attachment' => 'attachment', 115 | 'flag' => 'flag', 116 | 'priority' => 'priority', 117 | ]; 118 | 119 | $lis = []; 120 | foreach ($cols as $name => $label) { 121 | $props = ['type' => 'checkbox', 'id' => 'listmodechk-' . $name, 'name' => 'list_col[]', 'value' => $name]; 122 | if (in_array($name, $required_cols)) { 123 | $props['disabled'] = 'disabled'; 124 | } 125 | 126 | $span = html::span(null, rcube::Q($this->rcube->gettext($label))); 127 | $input = new html_checkbox($props); 128 | $label = html::label(in_array($name, $required_cols) ? ['class' => 'disabled'] : [], $input->show() . ' ' . $span); 129 | 130 | $lis[] = html::tag('li', null, $label); 131 | } 132 | 133 | $col_size = round(count($cols) / 2); 134 | $col1 = html::tag('ul', 'proplist', implode('', array_slice($lis, 0, $col_size))); 135 | $col2 = html::tag('ul', 'proplist', implode('', array_slice($lis, $col_size))); 136 | 137 | $col1 = html::div('col-sm-6', $col1); 138 | $col2 = html::div('col-sm-6', $col2); 139 | 140 | $row = html::div('row', $col1 . $col2); 141 | 142 | $title = html::tag('h3', ['id' => 'aria-label-coloptions', 'class' => 'voice'], rcube::Q($this->gettext('arialabelmessagecoloptions'))); 143 | $html .= html::div(['id' => 'coloptions-menu', 'class' => 'popupmenu propform', 'role' => 'dialo', 'aria-labelledby' => 'aria-label-coloptions'], $title . $row); 144 | } 145 | 146 | // list mode JS 147 | $html .= html::script(['src' => $this->api->url . 'elasticlayouts/layouts.js']); 148 | 149 | $attrib['content'] = str_replace('', $html . '', $attrib['content']); 150 | 151 | return $attrib; 152 | } 153 | } 154 | -------------------------------------------------------------------------------- /layouts.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Elastic Layouts plugin script 3 | * 4 | * @licstart The following is the entire license notice for the 5 | * JavaScript code in this file. 6 | * 7 | * Copyright (C) Philip Weir 8 | * 9 | * The JavaScript code in this page is free software: you can redistribute it 10 | * and/or modify it under the terms of the GNU General Public License 11 | * as published by the Free Software Foundation, either version 3 of 12 | * the License, or (at your option) any later version. 13 | * 14 | * @licend The above is the entire license notice 15 | * for the JavaScript code in this file. 16 | */ 17 | 18 | "use strict"; 19 | 20 | function rcube_elastic_layouts_ui() 21 | { 22 | var prefs, layout = { 23 | menu: $('#layout-menu'), 24 | sidebar: $('#layout-sidebar'), 25 | list: $('#layout-list'), 26 | content: $('#layout-content'), 27 | }; 28 | 29 | this.toggle_list_selection = toggle_list_selection; 30 | this.get_list_layout = get_list_layout; 31 | 32 | setup(); 33 | 34 | function setup() 35 | { 36 | if (rcmail.is_framed() && $('.formcontent').length > 0) { 37 | // Set the scrollable parent object for the table's fixed header 38 | rcube_list_widget.prototype.container = 'div.formcontent'; 39 | } 40 | 41 | rcmail 42 | .addEventListener('layout-change', mail_layout) 43 | .addEventListener('skin-resize', resize) 44 | .addEventListener('menu-open', menu_open); 45 | 46 | $('.column-resizer').on('mousemove', function() { 47 | if ($('.messagelist').hasClass('layout-list')) { 48 | rcmail.message_list.resize(); 49 | } 50 | 51 | mail_layout(); 52 | }); 53 | }; 54 | 55 | function toggle_list_selection(obj, list_id) 56 | { 57 | if ($(obj).is('.active')) { 58 | $('#' + list_id + ',#' + list_id + '-fixedcopy').toggleClass('withselection'); 59 | var list = $('#' + list_id).data('list'); 60 | rcmail[list].resize(); 61 | } 62 | }; 63 | 64 | function mail_layout(p) 65 | { 66 | if (rcmail.env.task != 'mail' || rcmail.env.action != '') 67 | return; 68 | 69 | var cur_layout = p ? p.new_layout : rcmail.env.layout, 70 | list_header = layout.list.find('.header'), 71 | content_header = layout.content.find('.header'), 72 | current_layout = get_list_layout(cur_layout), 73 | mode = UI.get_screen_mode(), 74 | reset_layout = function() { 75 | rcmail.env.contentframe = cur_layout == 'list' ? null : 'messagecontframe'; 76 | $('#layout-list > .iframe-wrapper').appendTo('#layout-content'); 77 | $('.iframe-wrapper > .column-resizer').remove(); 78 | }; 79 | 80 | if (cur_layout == 'desktop' || cur_layout == 'list') { 81 | // when in 'list' mode remove any inline styles like those applied by the splitter 82 | layout.list.removeAttr('style'); 83 | } 84 | 85 | if (mode == 'phone' || mode == 'small') { 86 | // on small screens the layout is always widescreen 87 | cur_layout = 'widescreen'; 88 | } 89 | else if (cur_layout == 'desktop' && $(window).height() < 600) { 90 | // do not use desktop layout on short screens 91 | cur_layout = 'list'; 92 | } 93 | 94 | if (cur_layout == 'desktop' || cur_layout == 'list') { 95 | // hide the fixed header until the message list is loaded (wait for correct col widths) 96 | $('#messagelist-fixedcopy').data('header-hidden', true).hide(); 97 | 98 | if (!$('#layout').hasClass('layout-' + cur_layout)) { 99 | reset_layout(); 100 | content_header.children().attr('data-source', 'content-header').appendTo(list_header); 101 | 102 | if (cur_layout == 'desktop') { 103 | $('#layout-content > .iframe-wrapper').appendTo('#layout-list'); 104 | splitter_init(); 105 | } 106 | 107 | $('#layout').removeClass().addClass('layout-' + cur_layout); 108 | } 109 | } 110 | else if (list_header.find("[data-source='content-header']").length > 0) { 111 | reset_layout(); 112 | list_header.find("[data-source='content-header']").appendTo(content_header); 113 | $('#layout').removeClass().addClass('layout-widescreen'); 114 | current_layout = 'widescreen'; 115 | } 116 | 117 | if (!$('.messagelist').hasClass('layout-' + current_layout)) { 118 | $('.messagelist').removeClass('layout-widescreen layout-list').addClass('layout-' + current_layout); 119 | $('.listing-hover-menu')[current_layout == 'widescreen' ? 'removeClass' : 'addClass']('hidden'); 120 | 121 | if (p) { 122 | // the user has changed layout mode, redraw the UI 123 | rcmail.env.layout = cur_layout; 124 | resize(); 125 | } 126 | else { 127 | // refresh the message list because list format has changed 128 | rcmail.command('list'); 129 | } 130 | } 131 | }; 132 | 133 | function get_list_layout(cur_layout = rcmail.env.layout) 134 | { 135 | var list_layout = cur_layout, 136 | mode = UI.get_screen_mode(); 137 | 138 | if (mode == 'phone' || mode == 'small') { 139 | list_layout = 'widescreen'; 140 | } 141 | else if (cur_layout == 'widescreen' && $('#layout-list').width() > 740) { 142 | list_layout = 'list'; 143 | } 144 | else if (cur_layout == 'desktop') { 145 | list_layout = 'list'; 146 | } 147 | else if (cur_layout == 'list' && $(window).width() < 1000) { 148 | list_layout = 'widescreen'; 149 | } 150 | 151 | return list_layout; 152 | }; 153 | 154 | function resize(p) 155 | { 156 | mail_layout(); 157 | 158 | $('.header > ul.menu', layout.list).filter("[data-source='content-header']").removeClass('popupmenu'); 159 | 160 | if (rcmail.env.layout == 'desktop' || rcmail.env.layout == 'list') 161 | layout.sidebar[!UI.is_mobile() ? 'removeClass' : 'addClass']('hidden'); 162 | } 163 | 164 | /** 165 | * Handler for menu-open event 166 | */ 167 | function menu_open(p) 168 | { 169 | if (p.name == 'messagelistmenu') { 170 | $('select[name="layout"]').val(rcmail.env.layout); 171 | } 172 | else if (p.name == 'messagelistcolsmenu') { 173 | menu_collist(p); 174 | } 175 | }; 176 | 177 | /** 178 | * Messages list columns options dialog 179 | */ 180 | function menu_collist(obj) 181 | { 182 | var content = $('#coloptions-menu'), 183 | dialog = content.clone(true); 184 | 185 | // set form values 186 | $.each(rcmail.env.listcols, function() { 187 | $('input[name="list_col[]"][value="' + this + '"]', dialog).prop('checked', true); 188 | }); 189 | 190 | // Fix id/for attributes 191 | $('input', dialog).each(function() { this.id = this.id + '-clone'; }); 192 | $('label', dialog).each(function() { $(this).attr('for', $(this).attr('for') + '-clone'); }); 193 | 194 | var save_func = function(e) { 195 | if (rcube_event.is_keyboard(e.originalEvent)) { 196 | $('#colmenulink').focus(); 197 | } 198 | 199 | var cols = []; 200 | $.each($('input[name="list_col[]"]', dialog), function() { 201 | if ($(this).is(':checked')) { 202 | cols.push($(this).val()); 203 | } 204 | }); 205 | 206 | rcmail.set_list_options(cols, rcmail.env.sort_col, rcmail.env.sort_col, rcmail.env.threading, rcmail.env.layout); 207 | return true; 208 | }; 209 | 210 | dialog = rcmail.simple_dialog(dialog, rcmail.gettext('elasticlayouts.columnoptionstitle'), save_func, { 211 | closeOnEscape: true, 212 | minWidth: 400 213 | }); 214 | }; 215 | 216 | /** 217 | * Create a splitter (resizing) element in desktop layout 218 | */ 219 | function splitter_init() 220 | { 221 | var node = $('#layout-list > .iframe-wrapper'), 222 | key = 'mail.messagecontframe', 223 | height = get_pref(key), 224 | set_height = function(height) { 225 | node.css('height', Math.max(300, height)); 226 | }; 227 | 228 | $('
') 229 | .appendTo(node) 230 | .on('mousedown', function(e) { 231 | var ts, splitter = $(this), offset = node.position().top; 232 | 233 | // Makes col-resize cursor follow the mouse pointer on dragging 234 | // and fixes issues related to iframes 235 | splitter.addClass('active'); 236 | 237 | // Disable selection on document while dragging 238 | // It can happen when you move mouse out of window, on top 239 | document.body.style.userSelect = 'none'; 240 | 241 | // Start listening to mousemove events 242 | $(document) 243 | .on('mousemove.resizer', function(e) { 244 | // Use of timeouts makes the move more smooth in Chrome 245 | clearTimeout(ts); 246 | ts = setTimeout(function() { 247 | offset = node.position().top; 248 | 249 | var cursor_position = rcube_event.get_mouse_pos(e).y, 250 | height = node.height() + (offset - cursor_position) 251 | 252 | set_height(height); 253 | }, 5); 254 | }) 255 | .on('mouseup.resizer', function() { 256 | // Remove registered events 257 | $(document).off('.resizer'); 258 | $('iframe').off('.resizer'); 259 | document.body.style.userSelect = 'auto'; 260 | 261 | // Set back the splitter width to normal 262 | splitter.removeClass('active'); 263 | 264 | // Save the current position (width) 265 | save_pref(key, node.height()); 266 | }); 267 | }); 268 | 269 | if (height) { 270 | set_height(height); 271 | } 272 | }; 273 | 274 | /** 275 | * Get preference stored in browser 276 | */ 277 | function get_pref(key) 278 | { 279 | if (!prefs) { 280 | prefs = rcmail.local_storage_get_item('prefs.elastic', {}); 281 | } 282 | 283 | // fall-back to cookies 284 | if (prefs[key] == null) { 285 | var cookie = rcmail.get_cookie(key); 286 | if (cookie != null) { 287 | prefs[key] = cookie; 288 | 289 | // copy value to local storage and remove cookie (if localStorage is supported) 290 | if (rcmail.local_storage_set_item('prefs.elastic', prefs)) { 291 | rcmail.set_cookie(key, cookie, new Date()); // expire cookie 292 | } 293 | } 294 | } 295 | 296 | return prefs[key]; 297 | }; 298 | 299 | /** 300 | * Saves preference value to browser storage 301 | */ 302 | function save_pref(key, val) 303 | { 304 | prefs[key] = val; 305 | 306 | // write prefs to local storage (if supported) 307 | if (!rcmail.local_storage_set_item('prefs.elastic', prefs)) { 308 | // store value in cookie 309 | var exp = new Date(); 310 | exp.setYear(exp.getFullYear() + 1); 311 | rcmail.set_cookie(key, val, exp); 312 | } 313 | }; 314 | } 315 | 316 | var UI_layouts = new rcube_elastic_layouts_ui(); 317 | 318 | // Override Elastic list selection toggle 319 | UI.toggle_list_selection = UI_layouts.toggle_list_selection; 320 | 321 | // Inject the layout option into the list options dialog save function 322 | rcmail.set_list_options_core = rcmail.set_list_options; 323 | rcmail.set_list_options = function(cols, sort_col, sort_order, threads, layout) 324 | { 325 | var layout = $('select[name="layout"]:visible').val(); 326 | rcmail.set_list_options_core(cols, sort_col, sort_order, threads, layout); 327 | }; 328 | 329 | // Inject the layout option into the add_message_row function 330 | rcmail.add_message_row_core = rcmail.add_message_row; 331 | rcmail.add_message_row = function(uid, cols, flags, attop) 332 | { 333 | rcmail.add_message_row_core(uid, cols, flags, attop); 334 | 335 | if (rcmail.env.msglist_layout == 'list' && $('#messagelist-fixedcopy').data('header-hidden')) { 336 | // once the message list has loaded add the fixed header 337 | // short delay prevents headers from flickering 338 | $('#messagelist-fixedcopy').data('header-hidden', false); 339 | setTimeout(function() { $('#messagelist-fixedcopy').show(); }, 200); 340 | } 341 | }; 342 | 343 | // Override list layout for list headers 344 | rcmail.addEventListener('msglist_layout', function(p) { 345 | return UI_layouts.get_list_layout(); 346 | }); -------------------------------------------------------------------------------- /localization/en_GB.inc: -------------------------------------------------------------------------------- 1 | thead { 14 | background-color: @color-dark-list-header-background; 15 | } 16 | 17 | thead th { 18 | border-bottom-color: @color-dark-border; 19 | } 20 | 21 | thead th { 22 | color: @color-dark-list-header; 23 | 24 | a { 25 | color: @color-dark-list-header; 26 | } 27 | 28 | &.sortedDESC, 29 | &.sortedASC, { 30 | > a:after { 31 | color: @color-dark-list-header-icon; 32 | } 33 | } 34 | } 35 | } 36 | 37 | .messagelist.layout-list { 38 | th { 39 | &.flag, 40 | &.attachment { 41 | span:before { 42 | color: @color-dark-list-header !important; 43 | } 44 | } 45 | } 46 | 47 | tr.deleted td, 48 | tr.deleted td.subject > a, 49 | tr.deleted td.subject span.subject a, 50 | tr.deleted td.subject span.date, 51 | tr.deleted td.subject span.fromto, 52 | tr.deleted td.flags span span { 53 | color: @color-dark-list-deleted !important; 54 | } 55 | 56 | td.priority span, 57 | span.priority { 58 | &.prio3:before { 59 | color: @color-dark-list-priority-3; 60 | } 61 | } 62 | 63 | tr.thread.expanded { 64 | background-color: @color-dark-list-thread-background; 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /styles/layout.less: -------------------------------------------------------------------------------- 1 | /** 2 | * Elastic Layouts layout styles 3 | */ 4 | 5 | #layout-list { 6 | #layout.layout-desktop &, 7 | #layout.layout-list & { 8 | flex: 9; 9 | max-width: none; 10 | 11 | html.layout-normal & { 12 | #toolbar-menu { 13 | width: 100%; 14 | } 15 | 16 | a.toolbar-button { 17 | order: 98; 18 | } 19 | } 20 | 21 | html.layout-large & { 22 | & > .header > #toolbar-menu { 23 | margin-left: 2.5em; 24 | } 25 | 26 | & > .footer { 27 | padding-left: 30% !important; 28 | padding-right: 30% !important; 29 | } 30 | } 31 | } 32 | 33 | #layout.layout-desktop & { 34 | #messagelist-content { 35 | min-height: 20%; 36 | } 37 | 38 | .iframe-wrapper { 39 | position: relative; 40 | width: 100%; 41 | height: 60%; 42 | max-height: 75%; 43 | border-top: 1px solid @color-layout-border; 44 | 45 | iframe { 46 | width: 100%; 47 | height: 100%; 48 | border: 0; 49 | } 50 | 51 | > .column-resizer { 52 | cursor: row-resize; 53 | top: -3px; 54 | right: auto; 55 | width: 100%; 56 | height: 6px; 57 | 58 | &.active { 59 | height: 10000px; 60 | top: -5000px; 61 | } 62 | } 63 | } 64 | } 65 | 66 | html.layout-large body.task-mail.action-none & { 67 | max-width: 60%; 68 | } 69 | } 70 | 71 | .column-resizer { 72 | #layout.layout-desktop > #layout-list > &, 73 | #layout.layout-list > #layout-list > & { 74 | display: none; 75 | } 76 | } 77 | 78 | html.layout-large, 79 | html.layout-normal { 80 | #layout.layout-desktop, 81 | #layout.layout-list { 82 | a.back-list-button, 83 | a.back-sidebar-button, 84 | #layout-list span.header-title { 85 | display: none !important; 86 | } 87 | 88 | #layout-content { 89 | display: none; 90 | } 91 | } 92 | } 93 | 94 | .listing-hover-menu.hidden { 95 | display: none !important; 96 | } -------------------------------------------------------------------------------- /styles/lists.less: -------------------------------------------------------------------------------- 1 | /** 2 | * Elastic Layouts list widget styles 3 | */ 4 | 5 | .listing { 6 | > thead { 7 | background-color: @color-list-header-background; 8 | } 9 | 10 | thead th { 11 | border-bottom: 1px solid @color-list-border; 12 | cursor: default; 13 | font-weight: normal; 14 | line-height: @listing-line-height; 15 | } 16 | 17 | thead th { 18 | padding: .25rem .5rem; 19 | white-space: nowrap; 20 | vertical-align: middle; 21 | font-weight: bold; 22 | color: @color-list-header; 23 | .overflow-ellipsis; 24 | outline: none; 25 | 26 | a { 27 | color: @color-list-header; 28 | 29 | &:hover { 30 | text-decoration: none; 31 | } 32 | } 33 | 34 | &.sortedDESC, 35 | &.sortedASC, { 36 | > a:after { 37 | &:extend(.font-icon-class); 38 | float: none; 39 | display: inline-block; 40 | margin-left: .1em; 41 | font-size: .9rem; 42 | color: @color-list-header-icon; 43 | content: @fa-var-long-arrow-alt-up; 44 | } 45 | } 46 | 47 | &.sortedASC > a:after { 48 | content: @fa-var-long-arrow-alt-down; 49 | } 50 | } 51 | 52 | th.selection > input { 53 | display: none; 54 | } 55 | 56 | &:not(.withselection) th.selection { 57 | display: none; 58 | } 59 | } 60 | 61 | table.fixedcopy { 62 | display: table; 63 | } 64 | 65 | .messagelist.layout-list > thead { 66 | display: table-header-group; 67 | } 68 | 69 | .messagelist .branch { 70 | display: block; 71 | float: left; 72 | } 73 | 74 | .messagelist.layout-list { 75 | th { 76 | border-left: 0; 77 | width: 2em; 78 | vertical-align: top; 79 | font-size: 1rem !important; 80 | } 81 | 82 | th { 83 | &.threads, 84 | &.flag, 85 | &.attachment, 86 | &.status, 87 | &.priority { 88 | text-indent: -999px; 89 | white-space: nowrap; 90 | overflow: hidden; 91 | width: 2.5em; 92 | } 93 | 94 | &.threads a:before { 95 | &:extend(.font-icon-class); 96 | content: @fa-var-columns; 97 | height: auto; 98 | text-indent: 0; 99 | } 100 | 101 | &.flag, 102 | &.attachment { 103 | span:before { 104 | color: @color-list-header !important; 105 | } 106 | } 107 | 108 | &.status span:before { 109 | .font-icon-solid(@fa-var-envelope); 110 | } 111 | 112 | &.fromto, 113 | &.from, 114 | &.to, 115 | &.cc, 116 | &.replyto { 117 | width: 15em; 118 | } 119 | 120 | &.date { 121 | width: 11em; 122 | } 123 | 124 | &.size { 125 | width: 4em; 126 | } 127 | 128 | &.subject { 129 | width: 99%; 130 | } 131 | } 132 | 133 | td.subject { 134 | width: auto; 135 | display: table-cell; 136 | } 137 | 138 | tr.flagged td.subject > a { 139 | color: @color-list-flagged; 140 | } 141 | 142 | tr.deleted td.subject > a { 143 | color: @color-list-deleted; 144 | } 145 | 146 | 147 | td.subject span.msgicon { 148 | &:before { 149 | margin-top: .2rem; 150 | } 151 | 152 | &.status:before { 153 | cursor: pointer; 154 | } 155 | } 156 | 157 | th.attachment, 158 | td.attachment, 159 | span.attachment { 160 | span { 161 | &:before { 162 | &:extend(.font-icon-class); 163 | color: @color-list-icon; 164 | 165 | margin: 0; 166 | content: @fa-var-paperclip; 167 | 168 | height: auto; 169 | text-indent: 0; 170 | } 171 | &.report:before { 172 | .font-icon-regular(@fa-var-file-alt); 173 | } 174 | &.encrypted:before { 175 | content: @fa-var-lock; 176 | } 177 | &.vcard:before { 178 | .font-icon-regular(@fa-var-user); // vcard_attachments plugin 179 | } 180 | } 181 | } 182 | 183 | th.flag span, 184 | td.flag span, 185 | tr.flaggedroot:not(:hover) td.flag span, 186 | span.flag span { 187 | &:before { 188 | &:extend(.font-icon-class); 189 | .font-icon-regular(@fa-var-flag); 190 | color: transparent; 191 | height: auto !important; 192 | text-indent: 0; 193 | } 194 | 195 | &.flagged:before { 196 | .font-icon-solid(@fa-var-flag); 197 | color: @color-list-flagged; 198 | } 199 | } 200 | 201 | tr.flaggedroot:not(:hover) td.flag span.unflagged:before { 202 | .font-icon-solid(@fa-var-flag); 203 | color: @color-list-icon; 204 | } 205 | 206 | td.status span, 207 | span.status { 208 | &:before { 209 | &:extend(.font-icon-class); 210 | .font-icon-solid(@fa-var-envelope); 211 | height: auto; 212 | text-indent: 0; 213 | } 214 | 215 | &.unread:before { 216 | color: @color-list-unread-status !important; 217 | } 218 | 219 | &.deleted:before { 220 | color: @color-list-icon !important; 221 | .font-icon-solid(@fa-var-ban); 222 | } 223 | } 224 | 225 | td.status span { 226 | color: transparent; 227 | } 228 | 229 | td.priority span, 230 | span.priority { 231 | &:before { 232 | &:extend(.font-icon-class); 233 | content: @fa-var-exclamation; 234 | height: auto; 235 | text-indent: 0; 236 | } 237 | 238 | &.prio1:before { 239 | content: @fa-var-exclamation; 240 | color: @color-list-priority-1; 241 | } 242 | 243 | &.prio2:before { 244 | content: @fa-var-long-arrow-alt-up; 245 | color: @color-list-priority-2; 246 | } 247 | 248 | &.prio3:before { 249 | content: @fa-var-arrows-alt-v; 250 | color: @color-list-priority-3; 251 | } 252 | 253 | &.prio4:before { 254 | content: @fa-var-long-arrow-alt-down; 255 | color: @color-list-priority-4; 256 | } 257 | 258 | &.prio5:before { 259 | content: @fa-var-long-arrow-alt-down; 260 | color: @color-list-priority-5; 261 | } 262 | } 263 | 264 | tr:not(.deleted):hover { 265 | span.attachment { 266 | display: block !important; 267 | } 268 | 269 | td.status, 270 | td.flag { 271 | > span:before { 272 | color: @color-list-icon; 273 | cursor: pointer; 274 | } 275 | } 276 | } 277 | 278 | tr.deleted:hover { 279 | span.attachment { 280 | display: block !important; 281 | } 282 | 283 | td.status > span.deleted:before { 284 | cursor: pointer; 285 | } 286 | } 287 | 288 | td > span.branch > div { 289 | display: inline-block; 290 | } 291 | 292 | tr.unread td { 293 | font-weight: bold; 294 | } 295 | 296 | tr.thread.expanded { 297 | background-color: @color-list-thread-background; 298 | } 299 | } 300 | -------------------------------------------------------------------------------- /styles/styles.less: -------------------------------------------------------------------------------- 1 | /** 2 | * Elastic Layouts skin styles 3 | */ 4 | 5 | @import (reference) "colors"; 6 | @import (reference) "../../../skins/elastic/styles/variables"; 7 | @import (reference) "../../../skins/elastic/styles/mixins"; 8 | 9 | @import "layout"; 10 | @import "lists"; 11 | 12 | & when (@dark-mode-enabled = true) { 13 | @import "dark"; 14 | } 15 | -------------------------------------------------------------------------------- /styles/styles.min.css: -------------------------------------------------------------------------------- 1 | .listing thead th.sortedASC>a:after,.listing thead th.sortedDESC>a:after,.messagelist.layout-list span.attachment span:before,.messagelist.layout-list span.flag span:before,.messagelist.layout-list span.priority:before,.messagelist.layout-list span.status:before,.messagelist.layout-list td.attachment span:before,.messagelist.layout-list td.flag span:before,.messagelist.layout-list td.priority span:before,.messagelist.layout-list td.status span:before,.messagelist.layout-list th.attachment span:before,.messagelist.layout-list th.flag span:before,.messagelist.layout-list th.threads a:before,.messagelist.layout-list tr.flaggedroot:not(:hover) td.flag span:before{font-size:1.25em;display:block;float:left;margin:0 .25rem 0 0;width:1.18em;height:1em;font-family:Icons;font-style:normal;font-weight:900;text-decoration:inherit;text-align:center;speak:none;font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;-webkit-font-smoothing:antialiased}#layout.layout-desktop #layout-list,#layout.layout-list #layout-list{flex:9;max-width:none}html.layout-normal #layout.layout-desktop #layout-list #toolbar-menu,html.layout-normal #layout.layout-list #layout-list #toolbar-menu{width:100%}html.layout-normal #layout.layout-desktop #layout-list a.toolbar-button,html.layout-normal #layout.layout-list #layout-list a.toolbar-button{order:98}html.layout-large #layout.layout-desktop #layout-list>.header>#toolbar-menu,html.layout-large #layout.layout-list #layout-list>.header>#toolbar-menu{margin-left:2.5em}html.layout-large #layout.layout-desktop #layout-list>.footer,html.layout-large #layout.layout-list #layout-list>.footer{padding-left:30%!important;padding-right:30%!important}#layout.layout-desktop #layout-list #messagelist-content{min-height:20%}#layout.layout-desktop #layout-list .iframe-wrapper{position:relative;width:100%;height:60%;max-height:75%;border-top:1px solid #d4dbde}#layout.layout-desktop #layout-list .iframe-wrapper iframe{width:100%;height:100%;border:0}#layout.layout-desktop #layout-list .iframe-wrapper>.column-resizer{cursor:row-resize;top:-3px;right:auto;width:100%;height:6px}#layout.layout-desktop #layout-list .iframe-wrapper>.column-resizer.active{height:10000px;top:-5000px}html.layout-large body.task-mail.action-none #layout-list{max-width:60%}#layout.layout-desktop>#layout-list>.column-resizer,#layout.layout-list>#layout-list>.column-resizer{display:none}html.layout-large #layout.layout-desktop #layout-list span.header-title,html.layout-large #layout.layout-desktop a.back-list-button,html.layout-large #layout.layout-desktop a.back-sidebar-button,html.layout-large #layout.layout-list #layout-list span.header-title,html.layout-large #layout.layout-list a.back-list-button,html.layout-large #layout.layout-list a.back-sidebar-button,html.layout-normal #layout.layout-desktop #layout-list span.header-title,html.layout-normal #layout.layout-desktop a.back-list-button,html.layout-normal #layout.layout-desktop a.back-sidebar-button,html.layout-normal #layout.layout-list #layout-list span.header-title,html.layout-normal #layout.layout-list a.back-list-button,html.layout-normal #layout.layout-list a.back-sidebar-button{display:none!important}html.layout-large #layout.layout-desktop #layout-content,html.layout-large #layout.layout-list #layout-content,html.layout-normal #layout.layout-desktop #layout-content,html.layout-normal #layout.layout-list #layout-content{display:none}.listing-hover-menu.hidden{display:none!important}.listing>thead{background-color:#fff}.listing thead th{border-bottom:1px solid #f1f3f4;cursor:default;line-height:35px;padding:.25rem .5rem;white-space:nowrap;vertical-align:middle;font-weight:700;color:#2c363a;overflow:hidden;text-overflow:ellipsis;outline:0}.listing thead th a{color:#2c363a}.listing thead th a:hover{text-decoration:none}.listing thead th.sortedASC>a:after,.listing thead th.sortedDESC>a:after{float:none;display:inline-block;margin-left:.1em;font-size:.9rem;color:rgba(115,118,119,.75);content:"\f30c"}.listing thead th.sortedASC>a:after{content:"\f309"}.listing th.selection>input,.listing:not(.withselection) th.selection{display:none}table.fixedcopy{display:table}.messagelist.layout-list>thead{display:table-header-group}.messagelist .branch{display:block;float:left}.messagelist.layout-list th{border-left:0;width:2em;vertical-align:top;font-size:1rem!important}.messagelist.layout-list th.attachment,.messagelist.layout-list th.flag,.messagelist.layout-list th.priority,.messagelist.layout-list th.status,.messagelist.layout-list th.threads{text-indent:-999px;white-space:nowrap;overflow:hidden;width:2.5em}.messagelist.layout-list th.threads a:before{content:"\f0db";height:auto;text-indent:0}.messagelist.layout-list th.attachment span:before,.messagelist.layout-list th.flag span:before{color:#2c363a!important}.messagelist.layout-list th.status span:before{content:"\f0e0";font-weight:900}.messagelist.layout-list th.cc,.messagelist.layout-list th.from,.messagelist.layout-list th.fromto,.messagelist.layout-list th.replyto,.messagelist.layout-list th.to{width:15em}.messagelist.layout-list th.date{width:11em}.messagelist.layout-list th.size{width:4em}.messagelist.layout-list th.subject{width:99%}.messagelist.layout-list td.subject{width:auto;display:table-cell}.messagelist.layout-list tr.flagged td.subject>a{color:#ff5552}.messagelist.layout-list tr.deleted td.subject>a{color:rgba(44,54,58,.5)}.messagelist.layout-list td.subject span.msgicon:before{margin-top:.2rem}.messagelist.layout-list td.subject span.msgicon.status:before{cursor:pointer}.messagelist.layout-list span.attachment span:before,.messagelist.layout-list td.attachment span:before,.messagelist.layout-list th.attachment span:before{color:rgba(115,118,119,.75);margin:0;content:"\f0c6";height:auto;text-indent:0}.messagelist.layout-list span.attachment span.report:before,.messagelist.layout-list td.attachment span.report:before,.messagelist.layout-list th.attachment span.report:before{content:"\f15c";font-weight:400}.messagelist.layout-list span.attachment span.encrypted:before,.messagelist.layout-list td.attachment span.encrypted:before,.messagelist.layout-list th.attachment span.encrypted:before{content:"\f023"}.messagelist.layout-list span.attachment span.vcard:before,.messagelist.layout-list td.attachment span.vcard:before,.messagelist.layout-list th.attachment span.vcard:before{content:"\f007";font-weight:400}.messagelist.layout-list span.flag span:before,.messagelist.layout-list td.flag span:before,.messagelist.layout-list th.flag span:before,.messagelist.layout-list tr.flaggedroot:not(:hover) td.flag span:before{content:"\f024";font-weight:400;color:transparent;height:auto!important;text-indent:0}.messagelist.layout-list span.flag span.flagged:before,.messagelist.layout-list td.flag span.flagged:before,.messagelist.layout-list th.flag span.flagged:before,.messagelist.layout-list tr.flaggedroot:not(:hover) td.flag span.flagged:before{content:"\f024";font-weight:900;color:#ff5552}.messagelist.layout-list tr.flaggedroot:not(:hover) td.flag span.unflagged:before{content:"\f024";font-weight:900;color:rgba(115,118,119,.75)}.messagelist.layout-list span.status:before,.messagelist.layout-list td.status span:before{content:"\f0e0";font-weight:900;height:auto;text-indent:0}.messagelist.layout-list span.status.unread:before,.messagelist.layout-list td.status span.unread:before{color:#ffd452!important}.messagelist.layout-list span.status.deleted:before,.messagelist.layout-list td.status span.deleted:before{color:rgba(115,118,119,.75)!important;content:"\f05e";font-weight:900}.messagelist.layout-list td.status span{color:transparent}.messagelist.layout-list span.priority:before,.messagelist.layout-list td.priority span:before{content:"\f12a";height:auto;text-indent:0}.messagelist.layout-list span.priority.prio1:before,.messagelist.layout-list td.priority span.prio1:before{content:"\f12a";color:#ff5552}.messagelist.layout-list span.priority.prio2:before,.messagelist.layout-list td.priority span.prio2:before{content:"\f30c";color:#ffd452}.messagelist.layout-list span.priority.prio3:before,.messagelist.layout-list td.priority span.prio3:before{content:"\f338";color:#2c363a}.messagelist.layout-list span.priority.prio4:before,.messagelist.layout-list td.priority span.prio4:before{content:"\f309";color:#41b849}.messagelist.layout-list span.priority.prio5:before,.messagelist.layout-list td.priority span.prio5:before{content:"\f309";color:#37beff}.messagelist.layout-list tr:not(.deleted):hover span.attachment{display:block!important}.messagelist.layout-list tr:not(.deleted):hover td.flag>span:before,.messagelist.layout-list tr:not(.deleted):hover td.status>span:before{color:rgba(115,118,119,.75);cursor:pointer}.messagelist.layout-list tr.deleted:hover span.attachment{display:block!important}.messagelist.layout-list tr.deleted:hover td.status>span.deleted:before{cursor:pointer}.messagelist.layout-list td>span.branch>div{display:inline-block}.messagelist.layout-list tr.unread td{font-weight:700}.messagelist.layout-list tr.thread.expanded{background-color:#f5fcff}html.dark-mode #layout.layout-desktop #layout-list .iframe-wrapper{border-top-color:#4d6066}html.dark-mode .listing>thead{background-color:#21292c}html.dark-mode .listing thead th{border-bottom-color:#4d6066;color:#c5d1d3}html.dark-mode .listing thead th a{color:#c5d1d3}html.dark-mode .listing thead th.sortedASC>a:after,html.dark-mode .listing thead th.sortedDESC>a:after{color:rgba(197,209,211,.5)}html.dark-mode .messagelist.layout-list th.attachment span:before,html.dark-mode .messagelist.layout-list th.flag span:before{color:#c5d1d3!important}html.dark-mode .messagelist.layout-list tr.deleted td,html.dark-mode .messagelist.layout-list tr.deleted td.flags span span,html.dark-mode .messagelist.layout-list tr.deleted td.subject span.date,html.dark-mode .messagelist.layout-list tr.deleted td.subject span.fromto,html.dark-mode .messagelist.layout-list tr.deleted td.subject span.subject a,html.dark-mode .messagelist.layout-list tr.deleted td.subject>a{color:#637e82!important}html.dark-mode .messagelist.layout-list span.priority.prio3:before,html.dark-mode .messagelist.layout-list td.priority span.prio3:before{color:#c5d1d3}html.dark-mode .messagelist.layout-list tr.thread.expanded{background-color:#373e41} --------------------------------------------------------------------------------