Page MenuHomePhorge

No OneTemporary

Size
261 KB
Referenced Files
None
Subscribers
None
This file is larger than 256 KB, so syntax highlighting was skipped.
diff --git a/plugins/acl/skins/elastic/templates/table.html b/plugins/acl/skins/elastic/templates/table.html
index c8f492924..8a4b8b9c9 100644
--- a/plugins/acl/skins/elastic/templates/table.html
+++ b/plugins/acl/skins/elastic/templates/table.html
@@ -1,37 +1,37 @@
<div id="acllist-container" class="table-widget">
<div id="acllist-content" class="content">
<h2 class="voice" id="aria-label-acltable"><roundcube:label name="acl.ariasummaryacltable" /></h2>
<roundcube:object name="acltable" id="acltable" class="records-table options-table" aria-labelledby="aria-label-acltable" role="listbox" />
</div>
<div id="acllist-footer" class="footer toolbar">
<roundcube:button command="acl-create" type="link"
label="acl.add" title="acl.newuser"
class="button create disabled" classAct="button create" innerClass="inner" />
<roundcube:button name="aclmenulink" type="link"
label="actions" title="acl.actions" href="#acl-actions"
class="button actions" innerClass="inner" data-popup="acl-menu" />
</div>
</div>
<div id="acl-menu" class="popupmenu" aria-hidden="true" data-align="bottom">
<h3 id="aria-label-aclactions" class="voice"><roundcube:label name="acl.arialabelaclactions" /></h3>
<ul class="toolbarmenu listing iconized" role="menu" aria-labelledby="aria-label-aclactions">
<roundcube:button command="acl-edit" label="edit" type="link-menuitem" class="edit" classAct="edit active" />
<roundcube:button command="acl-delete" label="delete" type="link-menuitem" class="delete" classAct="delete active" />
<roundcube:if condition="!in_array('acl_advanced_mode', (array)config:dont_override)" />
- <li role="menuitem" class="checkbox"><label id="acl-switch"><input type="checkbox" onchange="rcmail.command('acl-mode-switch')" /><a class="active"><roundcube:label name="acl.advanced" /></a></label></li>
+ <roundcube:button name="acl-switch" type="link-menuitem" class="active check" id="acl-switch" onclick="rcmail.command('acl-mode-switch')" label="acl.advanced" />
<roundcube:endif />
</ul>
</div>
<div id="aclform" class="popupmenu propform" aria-labelledby="aria-label-aclform" role="form">
<h3 id="aria-label-aclform" class="voice"><roundcube:label name="acl.arialabelaclform" /></h3>
<div class="row form-group">
<label class="col-sm-4 col-form-label"><roundcube:label name="acl.identifier" /></label>
<roundcube:object name="acluser" id="acluser" class="proplist col-sm-8" />
</div>
<div class="row form-group">
<label class="col-sm-4 col-form-label"><roundcube:label name="acl.myrights" /></label>
<roundcube:object name="aclrights" class="proplist col-sm-8" />
</div>
</div>
diff --git a/skins/elastic/styles/colors.less b/skins/elastic/styles/colors.less
index 41b4a547f..90dcb7d2f 100644
--- a/skins/elastic/styles/colors.less
+++ b/skins/elastic/styles/colors.less
@@ -1,207 +1,208 @@
/**
* Roundcube webmail styles for the Elastic skin
*
* Copyright (c) 2017-2018, The Roundcube Dev Team
*
* The contents are subject to the Creative Commons Attribution-ShareAlike
* License. It is allowed to copy, distribute, transmit and to adapt the work
* by keeping credits to the original authors in the README.md file.
* See http://creativecommons.org/licenses/by-sa/3.0/ for details.
*/
@color-main: #37beff;
@color-main-dark: darken(@color-main, 35%);
@color-black: #161b1d;
@color-font: lighten(@color-black, 10%);
@color-link: #00acff;
@color-link-hover: darken(@color-link, 10%);
@color-border: #ddd;
@color-error: #ff5552;
@color-success: #41b849;
@color-warning: #ffd452;
@color-link-secondary: lighten(@color-font, 60%);
@color-black-shade-text: tint(@color-black, 40%);
@color-black-shade-border: lighten(@color-black, 75%);
@color-black-shade-bg: lighten(@color-black, 85%);
// Layout elements
@color-layout-border: @color-black-shade-border;
@color-layout-header: @color-font;
@color-layout-sidebar-background: #fff;
@color-layout-list-background: #fff;
@color-layout-content-background: #fff;
@color-layout-header-background: #f4f4f4;
@color-layout-footer-background: #fff;
@color-layout-mobile-header-background: @color-layout-header-background;
@color-layout-mobile-footer-background: @color-layout-header-background;
// Task menu
@color-taskmenu-background: #2f3a3f;
@color-taskmenu-button: #fff;
@color-taskmenu-button-selected: @color-taskmenu-button;
@color-taskmenu-button-action: @color-main;
@color-taskmenu-button-special: @color-taskmenu-button;
@color-taskmenu-button-selected-background: lighten(@color-taskmenu-background, 10%);
@color-taskmenu-button-action-background: transparent;
@color-taskmenu-button-special-background: @color-taskmenu-background;
@color-taskmenu-button-hover: #fff;
@color-taskmenu-button-selected-hover: #fff;
@color-taskmenu-button-action-hover: @color-main;
@color-taskmenu-button-special-hover: lighten(@color-taskmenu-button-special, 10%);
@color-taskmenu-button-background-hover: lighten(@color-taskmenu-background, 10%);
@color-taskmenu-button-action-background-hover: @color-taskmenu-button-background-hover;
@color-taskmenu-button-special-background-hover:lighten(@color-taskmenu-button-special-background, 10%);
@color-taskmenu-button-logout-hover: @color-error;
// Toolbar
@color-toolbar-button: @color-font;
@color-toolbar-button-background-hover: darken(@color-layout-header-background, 3%);
@color-searchbar-icon-active: green;
@color-searchbar-background: #fbfbfb;
// Toolbar menu
@color-toolbarmenu-hover: #fff;
@color-toolbarmenu-hover-background: @color-main;
// Listings
@color-list: @color-font;
@color-list-selected: @color-font;
@color-list-selected-background: tint(@color-main, 90%);
@color-list-flagged: @color-error;
@color-list-deleted: lighten(@color-black, 50%);
@color-list-secondary: @color-black-shade-text;
@color-list-droptarget-background: #ffffcc;
@color-list-focus-indicator: lighten(@color-main, 20%);
@color-list-border: @color-black-shade-bg;
@color-list-badge: #fff;
@color-list-badge-background: @color-main;
@color-list-recent: blue;
@color-list-recent-badge: #fff;
@color-list-recent-badge-background: @color-list-recent;
@color-list-pagenav: @color-black-shade-text;
@color-list-icon: fadeout(@color-list-secondary, 25%);
@color-list-unread-status: @color-warning;
// Drag-n-drop layer
@color-drag-layer: #fff;
@color-drag-layer-background: @color-taskmenu-background;
@color-drag-layer-shadow: @color-black-shade-bg;
// Messages
@color-message: @color-font;
@color-message-border: rgba(0, 0, 0, 0.15);
@color-message-background: fadeout(@color-main, 95%);
@color-message-link: @color-main;
@color-message-link-font-weight: normal;
@color-message-information: @color-main;
@color-message-success: @color-success;
@color-message-warning: @color-warning;
@color-message-error: @color-error;
@color-message-loading: tint(@color-font, 30%);
@color-message-error-box: @color-message;
@color-message-information-box: @color-message;
@color-message-success-box: @color-message;
@color-message-warning-box: @color-message;
@color-message-error-box-background: fadeout(@color-message-error, 85%);
@color-message-information-box-background: fadeout(@color-message-information, 85%);
@color-message-success-box-background: fadeout(@color-message-success, 85%);
@color-message-warning-box-background: fadeout(#ffff66, 75%);
// Popovers (menus)
@color-popover-header: @color-black-shade-text;
@color-popover-header-background: transparent;
@color-popover-shadow: @color-black-shade-bg;
@color-popover-separator: @color-black-shade-text;
@color-popover-separator-background: @color-black-shade-bg;
@color-popover-mobile-header: #fff;
@color-popover-mobile-header-background: @color-main-dark;
// Dialogs
@color-dialog-overlay-background: fade(@color-font, 50%);
@color-dialog-header: @color-layout-header;
@color-dialog-header-border: @color-border;
@color-spinner-circle: @color-black-shade-bg;
@color-spinner-item: @color-black-shade-text;
// Forms
@color-input: @color-font;
@color-input-border: #ced4da; // from Bootstrap's .form-control
@color-input-border-focus: @color-main;
@color-input-border-focus-shadow: fadeout(@color-main, 75);
@color-input-border-invalid: @color-error;
@color-input-border-invalid-shadow: fadeout(@color-error, 75);
@color-input-addon-background: @color-black-shade-bg;
@color-recipient-input-border: @color-input-border;
@color-recipient-input-background: @color-black-shade-bg;
@color-input-placeholder: #bbb;
@color-checkbox: @color-main;
@color-checkbox-checked: @color-main;
-@color-checkbox-focus: darken(@color-checkbox, 30%);
-@color-checkbox-checked-focus: darken(@color-checkbox-checked, 30%);
+@color-checkbox-checked-disabled: lighten(@color-main, 15%);
+@color-checkbox-focus: @color-input-border-focus;
+@color-checkbox-focus-shadow: @color-input-border-focus-shadow;
@color-form-hint: @color-black-shade-text;
@color-image-upload-background: @color-black-shade-bg;
@color-btn-secondary: #fff;
@color-btn-secondary-background: lighten(@color-black, 50%);
@color-btn-primary: #fff;
@color-btn-primary-background: @color-main;
@color-btn-danger: #fff;
@color-btn-danger-background: @color-error;
@color-quota-background: #fff;
@color-quota-text: @color-black-shade-text;
@color-quota-value: @color-main;
@color-quota-value-warning: @color-error;
@color-blockquote-background: fadeout(@color-black-shade-bg, 50%);
@color-blockquote-0: darken(@color-main, 30%);
@color-blockquote-1: darken(@color-success, 25%);
@color-blockquote-2: darken(@color-error, 20%);
@color-blockquote-0-border: @color-blockquote-0;
@color-blockquote-1-border: @color-blockquote-1;
@color-blockquote-2-border: @color-blockquote-2;
@color-mail-signature: @color-black-shade-text;
@color-mail-headers: @color-black-shade-text;
@color-spellcheck-link: @color-error;
@color-table-border: @color-layout-border;
@color-table-selected: @color-list-selected;
@color-table-selected-background: @color-list-selected-background;
// Datepicker
@color-datepicker-border: @color-layout-border;
@color-datepicker-font: @color-font;
@color-datepicker-highlight: !default;
@color-datepicker-highlight-background: lighten(@color-main, 30%);
@color-datepicker-active: #fff;
@color-datepicker-active-background: @color-main;
// Image tools
@color-image-tools: #fff;
@color-image-tools-background: fadeout(@color-main, 60%);
@color-image-tools-hover: fadeout(@color-main, 50%);
diff --git a/skins/elastic/styles/widgets/common.less b/skins/elastic/styles/widgets/common.less
index 988486f36..e9d0a6206 100644
--- a/skins/elastic/styles/widgets/common.less
+++ b/skins/elastic/styles/widgets/common.less
@@ -1,544 +1,535 @@
/**
* Roundcube webmail styles for the Elastic skin
*
* Copyright (c) 2017-2018, The Roundcube Dev Team
*
* The contents are subject to the Creative Commons Attribution-ShareAlike
* License. It is allowed to copy, distribute, transmit and to adapt the work
* by keeping credits to the original authors in the README.md file.
* See http://creativecommons.org/licenses/by-sa/3.0/ for details.
*/
/*** Common UI elements ***/
.hidden,
.voice {
display: none !important;
}
font.bold {
font-weight: bold;
}
#rcmdraglayer {
min-width: 260px;
width: 260px;
background-color: @color-drag-layer-background;
color: @color-drag-layer;
box-shadow: 3px 3px 5px @color-drag-layer-shadow;
border-radius: .3rem;
z-index: 250;
opacity: .92;
padding: .5rem;
white-space: nowrap;
div {
line-height: 1.6em;
.overflow-ellipsis;
}
}
.frame-content {
padding: 1rem;
h2 {
font-weight: bold;
font-size: 1.5em;
}
h3 {
font-weight: bold;
font-size: 1.25em;
}
}
.listbox {
.scroller {
width: 100%;
overflow-x: hidden;
overflow-y: auto;
}
.navlist {
height: 0;
flex: initial !important;
.listing {
tr:last-child td,
li:last-child {
border-bottom: 0;
}
}
}
.popup & {
height: 100%;
display: flex;
flex-direction: column;
.scroller {
flex: 1;
}
.footer {
border-top: 1px solid @color-layout-border;
background-color: @color-searchbar-background;
}
}
}
.contact-header {
display: flex;
margin-bottom: 1rem;
.contact-photo {
min-width: @layout-contact-icon-width;
}
.contact-head {
margin-left: 1rem;
margin-top: 0 !important;
legend {
display: none;
}
}
}
@image-attachment-size: 250px;
// this is when image thumbnails are enabled
p.image-attachment {
position: relative;
border: 1px solid @color-border;
border-radius: .3rem;
background-color: @color-message-background;
float: left;
margin: .5rem;
min-width: 47%;
min-height: @image-attachment-size;
overflow: hidden;
// use flexbox to center the image
display: flex;
justify-content: center;
@media screen and (max-width: @screen-width-xs) {
float: none;
margin: .5rem 0 .5rem 0;
}
.image-link {
align-self: center;
text-align: center;
margin: 1.6rem .5rem;
}
span {
color: @color-form-hint;
padding: 0 .5rem;
font-size: 90%;
white-space: nowrap;
position: absolute;
line-height: 1.5rem;
}
.image-filename {
.overflow-ellipsis;
left: 0;
top: 0;
right: 0;
padding-right: 4rem;
}
.image-filesize {
right: 0;
top: 0;
}
.attachment-links {
position: absolute;
bottom: 0;
left: 0;
width: 100%;
text-align: center;
a {
text-decoration: none;
display: inline-block;
padding: 0 .5rem;
line-height: 1.5rem;
}
a:before {
&:extend(.font-icon-class);
display: inline-block;
}
a.open:before {
content: @fa-var-external-link-square-alt;
}
a.download:before {
content: @fa-var-download;
}
}
}
// this is when image thumbnails are disabled
fieldset.image-attachment {
margin-top: .5rem;
legend {
color: @color-form-hint;
font-size: .9rem;
border-top: 1px solid lighten(@color-mail-headers, 50%);
margin: 0;
}
img {
max-width: 100%;
}
}
#folder-selector {
overflow-y: auto;
}
.noselect {
user-select: none;
-moz-user-select: none;
-khtml-user-select: none;
-ms-user-select: none;
-webkit-user-select: none;
}
.iframe-loader {
width: 100%;
position: absolute;
top: 0;
bottom: 0;
background-color: rgba(255, 255, 255, .95);
display: flex;
align-items: center;
justify-content: center;
z-index: 3;
.spinner-border {
width: 7rem;
height: 7rem;
color: @color-spinner-circle;
border: 1rem solid;
border-color: currentColor @color-spinner-item currentColor currentColor;
}
}
.footer.toolbar + .iframe-loader {
top: @layout-header-height;
bottom: @layout-header-height;
}
// iOS: Fix scrolling of iframe, display scrollbars on scrollable elements
.ios-scroll {
padding: 0;
-webkit-overflow-scrolling: touch !important;
overflow: scroll !important;
&.iframe-wrapper {
margin-top: 1px; // FIXME: without this scrolling hides the wrapper neighbours' border
}
}
.webkit-scroller {
&::-webkit-scrollbar {
-webkit-appearance: none;
}
&::-webkit-scrollbar:vertical {
width: .6rem;
}
&::-webkit-scrollbar:horizontal {
height: .6rem;
}
&::-webkit-scrollbar-thumb {
background-color: rgba(0, 0, 0, .4);
border-radius: .3rem;
border: 2px solid #fff;
}
}
.quota-widget {
width: 100%;
max-width: 15em;
padding: .5rem 1rem;
display: flex;
align-items: center;
color: @color-quota-text;
&:before {
&:extend(.font-icon-class);
content: @fa-var-hdd;
line-height: 1;
}
.count {
color: @color-quota-text;
font-size: 80%;
order: 2;
}
.bar {
flex: 1;
height: .5rem;
margin: 0 1rem;
background-color: @color-quota-background;
border: 1px solid @color-layout-border;
border-radius: .25rem;
overflow: hidden;
}
.value {
display: block;
background-color: @color-quota-value;
height: 1rem;
opacity: .75;
&.warning {
background-color: @color-quota-value-warning;
}
}
}
.quota-info {
width: 100%;
display: table !important;
td,th {
text-align: center;
white-space: nowrap;
}
th {
border-top: 0;
}
.root {
line-height: 1;
font-style: italic;
color: @color-popover-separator;
background-color: @color-popover-separator-background;
}
th:first-child,
.name {
text-align: left;
}
}
// Make Bootstrap tabs non-wrappable
.nav-tabs {
flex-wrap: nowrap;
.nav-item {
white-space: nowrap;
overflow: hidden;
}
.nav-link {
.overflow-ellipsis;
}
}
.props-table {
td.title {
width: 7em;
}
}
.table-widget {
display: flex;
flex-direction: column;
margin-bottom: .5rem;
border: 1px solid @color-table-border;
& > .content {
overflow-x: auto;
flex-grow: 1;
height: 18.5em;
table th {
border-top: 0;
}
}
& > .footer {
height: 3.5rem;
border-top: 1px solid @color-table-border;
text-align: left;
a.button {
padding: 0;
height: 3.5rem;
}
}
table {
margin: 0;
max-height: 18.5em;
}
// Options table is a table with first column for identifier/description
// and other columns for a state flag. E.g. ACL table
table.options-table {
td,th {
text-align: center;
vertical-align: middle;
&:first-child {
.overflow-ellipsis;
text-align: left;
}
}
tr:last-child td {
border-bottom: 1px solid @color-table-border;
}
tr.selected td {
background-color: @color-table-selected-background;
color: @color-table-selected;
outline: 0;
}
td:not(:first-child) span {
display: inline-block;
line-height: 1.25;
&:before {
&:extend(.font-icon-class);
}
}
td.enabled span:before {
content: @fa-var-check;
}
td.partial span:before {
opacity: .3;
content: @fa-var-check;
}
}
}
table.compact-table {
margin: 0;
*:not(.invalid-feedback) {
font-size: inherit;
}
td {
padding: .25rem;
border: 0;
}
td:first-child {
padding-left: 0;
}
td:last-child {
padding-right: 0;
}
}
table.table {
.checkbox-cell {
width: 3rem;
white-space: nowrap;
overflow: hidden;
text-align: center;
-
- input.icon-checkbox + label {
- padding: 0;
-
- &:before {
- line-height: 1;
- height: 1em;
- }
- }
}
th.checkbox-cell {
padding: .75rem 0;
max-width: 1rem;
&:before {
&:extend(.font-icon-class);
cursor: pointer;
margin: 0 1rem;
line-height: 1;
}
&.subscription:before {
content: @fa-var-rss-square;
}
&.alarm:before {
.font-icon-regular(@fa-var-bell);
}
&.read:before {
content: @fa-var-eye;
}
&.write:before {
content: @fa-var-pencil-alt;
}
}
.buttons-cell {
width: 1%;
white-space: nowrap;
text-align: center;
a.button:before {
line-height: 1;
float: none;
display: inline-block;
}
@media screen and (min-width: @screen-width-xs) {
a.button .inner {
display: inline;
}
}
}
label {
margin: 0;
display: inline;
}
fieldset.tab-pane & thead th {
border: 0;
}
}
html.touch {
table.table {
th.checkbox-cell:before {
font-size: 1.5rem;
}
}
}
/* Bootstrap's .table style overwrites */
.table {
thead th {
border-width: 1px;
white-space: nowrap;
}
}
diff --git a/skins/elastic/styles/widgets/editor.less b/skins/elastic/styles/widgets/editor.less
index a123b51dd..c4e918217 100644
--- a/skins/elastic/styles/widgets/editor.less
+++ b/skins/elastic/styles/widgets/editor.less
@@ -1,943 +1,942 @@
/**
* Roundcube webmail styles for the Elastic skin
*
* Copyright (c) 2017-2018, The Roundcube Dev Team
*
* The contents are subject to the Creative Commons Attribution-ShareAlike
* License. It is allowed to copy, distribute, transmit and to adapt the work
* by keeping credits to the original authors in the README.md file.
* See http://creativecommons.org/licenses/by-sa/3.0/ for details.
*/
/*** Text Editor widget (and TinyMCE editor) ***/
.mce-tinymce {
&.mce-container.mce-panel {
border-radius: .25rem;
border-color: @color-input-border;
overflow: hidden;
}
.mce-btn,
.mce-panel {
background-color: @color-input-addon-background;
}
.mce-panel {
border-color: @color-input-border;
}
}
.mce-top-part::before,
.mce-tinymce,
.mce-window {
box-shadow: none !important;
}
.mce-btn {
&.mce-active {
background: @color-btn-secondary-background !important;
}
}
.mce-window {
&.mce-container {
border: 0;
& :not(.mce-ico) {
.font-family;
}
}
.mce-reset {
background: #fff;
}
.mce-container-body {
&.mce-abs-layout {
overflow: unset;
}
.mce-abs-end {
display: none;
}
}
.mce-window-head {
height: @layout-header-height;
border-bottom: 1px solid @color-dialog-header-border;
padding: 0;
.mce-title {
line-height: @layout-header-height;
font-size: 1.25rem;
padding: 0 3rem 0 1rem;
color: @color-dialog-header;
}
.mce-close {
border: 0;
color: @color-dialog-header;
background: transparent;
right: 0;
top: 0;
position: absolute;
height: (@layout-header-height - .7rem);
width: 1.25em;
margin: .25rem;
padding: .1rem .75rem;
cursor: pointer;
&:before {
&:extend(.font-icon-class);
content: @fa-var-times;
margin: 0;
}
i {
display: none;
}
}
}
.mce-foot {
border: 0;
height: @layout-header-height !important;
position: relative;
@media screen and (max-width: @screen-width-xs) {
border-top: 1px solid @color-dialog-header-border;
}
.mce-container-body {
height: 100% !important;
display: flex;
align-items: center;
justify-content: flex-end; // just 'end' does not work in Chrome
.mce-btn {
position: initial;
margin-right: .5rem;
line-height: 1;
width: auto !important;
height: auto !important;
&:last-child {
margin-right: 1rem;
}
.mce-txt {
line-height: 1.5;
vertical-align: unset;
}
button:before {
&:extend(.font-icon-class);
display: inline;
float: none;
vertical-align: middle;
margin-right: .4rem;
}
}
.mce-abs-end {
position: initial;
width: 1rem;
order: 9;
}
}
.mce-btn {
.btn-secondary;
border-radius: .3rem;
border-color: transparent;
&:focus {
border-color: transparent !important;
color: @color-btn-secondary;
background: @color-btn-secondary-background;
}
&.mce-primary {
.btn-primary;
}
button:hover,
button {
color: @color-btn-secondary;
padding: .5rem .75rem;
}
}
.mce-btn:last-child button:before {
content: @fa-var-times;
}
.mce-btn.mce-primary button:before {
content: @fa-var-check;
}
.mce-search-foot {
div:nth-of-type(2) button:before {
content: @fa-var-search;
}
div:nth-of-type(3) button:before,
div:nth-of-type(4) button:before {
content: @fa-var-pencil-alt;
}
div:nth-of-type(6) button:before {
content: @fa-var-chevron-left;
}
div:nth-of-type(7) button:before {
content: @fa-var-chevron-right;
}
div:nth-of-type(7) button:after {
&:extend(.font-icon-class);
display: inline;
float: none;
margin: 0 0 0 .2rem;
content: @fa-var-chevron-right;
}
@media screen and (min-width: (@screen-width-xs + 1px)) {
div:nth-of-type(6) {
margin-left: .5rem;
}
div:nth-of-type(7) button {
&:before {
display: none;
}
}
}
}
}
// Form elements, let's keep'em in .mce-window to make overwriting simpler
.mce-formitem {
min-width: 450px;
position: unset !important;
& > .mce-container-body {
margin-bottom: .5rem;
& > * {
width: 75% !important;
position: unset !important;
}
& > label {
max-width: 25%;
min-width: 25%;
line-height: 2.5 !important;
}
}
.mce-widget {
border-radius: .25rem;
}
}
.mce-form {
padding: 1rem;
box-sizing: border-box;
.mce-form {
padding: 0;
position: unset !important;
width: 100% !important;
& > .mce-container-body {
flex-wrap: wrap;
height: auto !important;
}
.mce-formitem {
min-width: 100%;
width: 100% !important;
}
}
.mce-container {
height: auto !important;
.mce-container-body {
display: flex;
height: auto !important;
& > input:not([size="5"]) {
position: relative;
left: 0 !important;
flex: 1;
}
}
}
& > .mce-container-body {
box-sizing: border-box;
width: 100% !important;
}
.mce-form-split {
.mce-formitem {
min-width: auto;
& > .mce-container-body {
width: 100% !important;
}
}
}
label {
position: unset;
line-height: 2.5 !important;
height: auto !important;
}
}
.mce-colorpicker {
& + .mce-form {
width: 150px !important;
padding: 0;
.mce-formitem {
min-width: unset;
& + :not(.mce-formitem) {
height: 50px !important;
}
}
}
}
.mce-textbox {
padding: .375rem .75rem !important;
line-height: 1.5;
color: @color-font;
&:not(textarea) {
height: auto !important;
}
&:focus {
color: @color-font;
border-color: @color-input-border-focus;
box-shadow: 0 0 0 .2rem @color-input-border-focus-shadow;
}
&[size="5"] {
width: auto !important;
}
&.mce-multiline {
line-height: 1.25;
width: 100% !important;
position: unset;
box-sizing: border-box;
display: block;
}
}
.mce-listbox {
button {
line-height: 1.5;
padding: .375rem .75rem;
}
&:focus {
border-color: @color-input-border-focus !important;
box-shadow: 0 0 0 .2rem @color-input-border-focus-shadow !important;
}
}
.mce-checkbox {
line-height: 2.5;
i.mce-i-checkbox {
border: 0;
width: auto;
color: @color-checkbox;
text-indent: 0;
&:before {
&:extend(.font-icon-class);
margin: 0;
content: @fa-var-toggle-off;
}
}
&.mce-checked i.mce-i-checkbox:before {
content: @fa-var-toggle-on;
}
&:focus {
i.mce-i-checkbox {
border: 0;
- color: @color-checkbox-focus;
}
}
}
.mce-combobox {
display: flex;
input {
border-radius: .3rem 0 0 .3rem;
flex: 1;
&:focus {
z-index: 1;
}
}
&.mce-combobox-fake {
input {
border-radius: .3rem;
}
}
button {
padding: .4rem .6rem;
}
.mce-btn {
border-radius: 0 .3rem .3rem 0;
background: @color-input-addon-background;
&:focus {
background-color: @color-input-border-focus-shadow;
border-color: #c5c5c5;
}
}
}
.mce-tabs {
padding-top: 1rem;
margin: 0 1rem;
border-color: @color-layout-border;
.mce-tab {
border-radius: .25rem .25rem 0 0;
padding: .5rem 1rem;
height: auto !important;
border: 1px solid transparent;
color: @color-link;
background: transparent;
margin-bottom: -1px;
&.mce-active {
border: 1px solid @color-layout-border;
border-bottom-color: #fff;
color: @color-font !important;
}
&:not(.mce-active):hover {
border: 1px solid @color-list-border;
border-bottom-color: transparent;
border-bottom: 0;
}
&:focus {
outline: unset !important;
}
}
}
.mce-label {
text-shadow: none;
color: @color-font;
}
}
// Menus and popovers, e.g. color selector, emoticons selector, font selector
.mce-menu,
.mce-floatpanel.mce-popover {
box-shadow: 3px 3px 5px @color-popover-shadow !important;
border-color: @color-layout-border !important;
border-radius: .3rem;
}
.mce-menu {
.mce-menu-item.mce-active {
color: @color-toolbarmenu-hover;
background-color: @color-toolbarmenu-hover-background;
}
.popover-header {
display: none !important;
}
}
div.mce-menubtn.mce-opened {
z-index: 65530 !important; // BUG: https://github.com/tinymce/tinymce/issues/4542
}
#mce-modal-block.mce-in {
background-color: @color-dialog-overlay-background;
opacity: 1 !important;
}
@media screen and (max-width: @screen-width-xs) {
.mce-window {
width: 100% !important;
height: 100% !important;
left: 0 !important;
top: 0 !important;
border-width: 0 !important;
& > .mce-reset {
display: flex;
flex-direction: column;
height: 100%;
}
.mce-window-body {
flex: 1;
overflow-y: auto !important;
}
& > .mce-reset > div,
.mce-container-body {
width: 100% !important;
}
.mce-window-head {
background-color: @color-layout-mobile-header-background;
.mce-title {
font-size: 1rem;
text-align: center;
padding: 0 1rem;
}
.mce-close {
display: none;
}
}
.mce-foot {
background-color: @color-layout-mobile-footer-background;
.mce-container-body {
justify-content: space-evenly;
.mce-btn {
position: initial;
height: 100% !important;
margin: 0;
background: transparent;
border-width: 0;
&:focus {
box-shadow: none;
}
&:hover {
background: transparent;
}
&:last-child {
margin: 0;
}
button {
color: @color-font;
padding: .45rem;
font-size: .9rem;
&:before {
display: block;
float: none;
width: 100%;
margin: 0;
line-height: 1.75;
height: 1.75rem;
}
}
}
.mce-abs-end {
display: none;
}
}
.mce-search-foot {
div:nth-of-type(7) button:after {
display: none;
}
}
}
.mce-form,
.mce-form + .mce-container, // for Embed tab in Media dialog
.mce-formitem,
.mce-combobox,
.mce-panel:not(.mce-popover) {
width: 100% !important;
}
.mce-formitem {
min-width: unset;
}
.mce-form {
& > .mce-container-body {
display: flex;
flex-direction: column;
left: 0;
right: 0;
box-sizing: border-box;
}
.mce-container-body {
height: auto !important;
flex-direction: column;
& > label {
position: unset !important;
width: 100% !important;
max-width: 100%;
}
& > label + * {
position: unset !important;
width: auto !important;
}
& > .mce-checkbox {
position: absolute;
left: 0 !important;
top: 3rem !important;
}
}
}
// FIXME: How to fix the input width in less hacky way?
.mce-combobox input {
max-width: ~"calc(100% - 4rem)";
}
.mce-combobox-fake input {
max-width: ~"calc(100% - 1.7rem)";
}
}
.mce-menu {
width: @layout-mobile-menu-width !important;
right: 0;
top: 0 !important;
left: auto !important;
height: 100% !important;
max-height: unset !important;
padding: 0 !important;
margin: 0 !important;
border-radius: 0;
border: 0 !important;
.popover-header {
display: block !important;
a {
font-size: 1.2rem;
line-height: @layout-touch-header-height;
&:before {
content: @fa-var-times;
}
}
}
.mce-container-body {
width: 100% !important;
}
.mce-menu-item {
height: @layout-touch-menu-record-height;
line-height: @layout-touch-menu-record-height;
padding: 0 .5rem;
border-left: 0;
border-bottom: 1px solid @color-list-border;
margin: 0;
.mce-ico {
font-size: 150%;
padding: 0 .7rem 0 .25rem;
margin-top: -.2rem;
}
.mce-text {
font-size: 1.2rem;
.font-family;
line-height: @layout-touch-menu-record-height;
color: @color-font;
}
.mce-caret {
display: none;
}
&.mce-menu-item-preview {
&.mce-active {
border-left: none;
position: relative;
&:after {
.font-icon-class; // :extend() does not work here
content: @fa-var-check;
position: absolute;
right: .5rem;
top: 0;
color: @color-font;
}
}
}
&.mce-menu-item-expand {
position: relative;
&:after {
.font-icon-class; // :extend() does not work here
content: @fa-var-angle-right;
position: absolute;
right: .5rem;
top: 0;
color: @color-font;
}
}
}
}
.mce-menu-item-sep,
.mce-menu-shortcut {
display: none !important;
}
.mce-charmap-dialog {
position: unset !important;
+ .mce-container {
display: none;
}
}
.mce-charmap {
display: block;
tbody {
display: block;
}
tr {
display: flex;
flex-wrap: wrap;
}
td {
flex: 1;
height: auto !important;
min-width: 17%;
padding: 0 !important;
border: 0 !important;
border-bottom: 1px solid @color-list-border !important;
div {
font-size: 3rem;
line-height: 2;
}
}
}
}
html.touch .mce-grid td {
padding: .5rem;
}
/*** Media file selector for TinyMCE ***/
.image-selector {
margin: 1rem 1rem 1rem 1rem !important;
padding: 1rem .5rem 10rem .5rem !important;
&.droptarget {
border: .2rem dashed @color-table-border;
&:after {
margin-top: 2rem;
}
}
button {
.btn-secondary;
padding: .5rem .75rem;
line-height: 1.5;
position: relative;
&:before {
line-height: 1;
}
}
form {
position: absolute;
top: 0;
}
.attachmentslist {
margin-left: 0;
overflow-x: hidden;
overflow-y: auto;
height: 19.1em;
li {
padding: .25rem;
cursor: pointer;
&:before {
display: none;
}
&:hover,
&:focus {
background: @color-list-selected-background;
}
span.name {
flex: 1;
margin: auto;
padding-left: 1rem;
.overflow-ellipsis;
}
span.img {
height: 80px;
width: 80px;
display: flex;
border: 1px solid @color-list-border;
background: white;
border-radius: .75rem;
overflow: hidden;
}
img {
margin: auto;
}
}
html.layout-phone & {
height: auto;
}
}
}
/*** HTML editor widget ***/
.html-editor {
position: relative;
display: flex;
margin-bottom: .25rem;
& > .nav {
border-color: transparent;
z-index: 1;
position: absolute;
right: 1rem;
a.active {
border-color: @color-input-border !important;
&.mode-html {
background-color: @color-input-addon-background;
border-bottom-color: @color-input-addon-background !important;
}
&.mode-plain {
border-bottom-color: #fff !important;
}
}
a:hover,
a:focus {
border-bottom-color: transparent;
outline: 0;
}
}
& > iframe, // e.g. mailvelope frame
& > .googie_edit_layer,
& > .mce-tinymce,
& > textarea {
margin-top: 2.55rem;
font-family: monospace;
width: 100% !important;
}
& > iframe { // e.g. mailvelope frame
border-radius: .3rem;
border: 1px solid @color-input-border;
min-height: 30em;
}
#composebody_ifr {
min-height: 30em;
}
& > .mce-tinymce.focused {
border-color: @color-input-border-focus;
box-shadow: 0 0 0 .2rem @color-input-border-focus-shadow !important;
}
}
/*** GoogieSpell widget ***/
.googie_window {
width: 16rem;
}
.googie_edit_layer {
font-family: monospace;
// from Bootstrap's textarea
padding: .5rem .75rem;
border: 1px solid @color-input-border;
border-radius: .3rem;
line-height: 1.25;
}
.googie_link {
color: @color-spellcheck-link;
text-decoration: underline;
cursor: pointer;
}
.googie_list {
td {
min-width: 8rem;
width: auto;
&.googie_list_onhover {
color: @color-toolbarmenu-hover;
background-color: @color-toolbarmenu-hover-background;
}
.googie_list_revert:before {
&:extend(.font-icon-class);
content: @fa-var-plus-square;
}
.googie_add_to_dict:before {
&:extend(.font-icon-class);
content: @fa-var-plus-square;
}
}
input {
display: inline-block;
margin: .5rem .5rem .5rem 0 !important;
padding: .5rem .75rem !important;
}
}
diff --git a/skins/elastic/styles/widgets/forms.less b/skins/elastic/styles/widgets/forms.less
index 7ca4d35dc..ea869c9bd 100644
--- a/skins/elastic/styles/widgets/forms.less
+++ b/skins/elastic/styles/widgets/forms.less
@@ -1,1341 +1,1317 @@
/**
* Roundcube webmail styles for the Elastic skin
*
* Copyright (c) 2017-2018, The Roundcube Dev Team
*
* The contents are subject to the Creative Commons Attribution-ShareAlike
* License. It is allowed to copy, distribute, transmit and to adapt the work
* by keeping credits to the original authors in the README.md file.
* See http://creativecommons.org/licenses/by-sa/3.0/ for details.
*/
/*** Common form elements ***/
#uploadform {
display: none;
}
form.smart-upload,
input.smart-upload {
visibility: hidden;
width: 1px;
height: 1px;
opacity: 0;
}
.propform {
// TODO: do we need this?
&:not(.popupmenu) {
width: 100%;
}
// This is the way we can have multiple checkboxes in a single form field
.form-check td:not(.title) > label {
display: block;
margin: 0;
line-height: 2rem;
label {
margin-right: .5rem;
}
}
td.datetime {
display: flex;
input:first-child {
margin-right: .5rem;
}
input:last-child {
width: 75%;
}
}
td.rowbuttons {
width: 1%;
white-space: nowrap;
span {
display: none;
}
a {
padding: 0;
line-height: 2.3rem;
height: 2.3rem;
font-size: 1rem;
&:before {
&:extend(.font-icon-class);
content: @fa-var-plus;
vertical-align: unset;
@media screen and (min-width: (@screen-width-bs-phone + 1px)) {
margin: 0 !important;
}
}
&.delete:before {
content: @fa-var-trash-alt;
}
&.advanced:before {
content: @fa-var-cog;
}
&:not(:last-child) {
margin-right: .25rem;
}
}
}
td.rowactions {
width: 1%;
.form-control {
width: auto;
}
}
td.rowtargets {
.composite {
input, textarea, select, .multi-input, .input-group {
margin-bottom: .5rem;
}
.input-group {
input, textarea, select, .multi-input {
margin-bottom: 0;
}
}
br {
display: block;
}
}
.input-group {
margin-bottom: .25rem;
*:first-child.input-group-prepend {
text-align: left;
min-width: 7.5em;
& > * {
width: 100%;
}
}
}
& > .advanced {
margin-top: .25rem;
}
}
td.title {
padding-top: 0;
padding-bottom: 0;
}
td > .flexbox {
display: flex;
& > .multi-input {
width: 100%;
margin-left: .25rem;
}
}
.rulerow {
margin-bottom: .5rem;
}
&.groupped {
&.readonly {
legend {
margin: 0;
}
.row.input-group {
margin-bottom: 0 !important;
}
label {
min-width: 7rem;
// Overwrite Bootstrap .input-group-* style to make the label transparent
background-color: transparent;
border: 0;
border-radius: 0;
}
}
.row.input-group {
margin-bottom: .5rem;
flex-wrap: nowrap;
& > *:first-child {
.overflow-ellipsis;
min-width: 8rem;
&:not(select) {
padding: 0;
}
@media screen and (max-width: @screen-width-xs) {
min-width: 6rem;
width: 6rem;
flex-grow: unset;
}
label {
width: 100%;
}
}
& > *:nth-child(2) {
flex-grow: 30;
}
&:last-child {
margin-bottom: 1rem;
}
select {
text-align: left;
}
&.composite select {
height: auto;
}
.content {
padding: 0;
display: flex;
flex-wrap: wrap;
border-radius: 0;
input {
border-radius: 0;
border-color: transparent;
}
.ff_street {
width: 100%;
}
.ff_locality {
width: 75%;
}
.ff_zipcode {
width: 25%;
}
.ff_country, .ff_region {
width: 50%;
}
}
}
.form-control-plaintext {
flex-grow: 1;
border: 0;
}
}
.addfield {
margin: 0;
select {
width: 8rem;
margin-top: .5rem;
}
}
.form-text {
font-size: 90%;
color: @color-form-hint;
}
// Some dialogs may use simple one-row forms like this
&.row.form-group {
margin-left: 0;
margin-right: 0;
label, div {
padding-left: 0;
padding-right: 0;
}
}
// Some forms may use multiple elements that are not part of .input-group
// add proper spacing
select + select,
select + .input-group {
padding-top: .5rem;
}
&.text-only {
margin-bottom: .25rem;
tr {
margin: 0;
}
label {
padding-bottom: 0 !important;
}
@media screen and (max-width: @screen-width-bs-phone) {
tr {
display: table-row;
}
td {
width: auto;
&:first-child {
width: 33%;
}
}
:not(tr).form-group.row {
.col-form-label {
width: 33%;
}
& > :last-child {
width: 67%;
}
}
}
}
}
@media screen and (max-width: @screen-width-bs-phone) {
.propform {
table.compact-table {
overflow-x: initial;
tbody {
display: block;
}
.rowactions > select,
.flexbox > select {
width: 100%;
}
tr {
display: flex;
flex-direction: column;
td {
width: 100%;
padding-right: 0;
padding-left: 0;
&.rowbuttons {
text-align: right;
a {
margin-right: .5rem;
& > span {
display: inline;
}
}
}
}
}
}
}
}
.propform,
.formcontent {
fieldset:not(.tab-pane):nth-of-type(n+2) {
margin-top: 1em;
}
legend {
font-weight: bold;
font-size: 1.2em;
}
label {
-webkit-hyphens: auto;
-ms-hyphens: auto;
hyphens: auto;
overflow: hidden;
margin-bottom: 0;
}
}
@media screen and (max-width: @screen-width-bs-phone) {
.formcontent .text-only {
.form-group:not(tr) {
margin-bottom: .25rem;
.col-form-label {
width: 33%;
& + span {
width: 67%;
}
}
}
}
html.iframe .formcontent > .propform {
padding: .25rem;
}
}
// Forms fixes for IE and Edge
html.ms .propform {
.row:not(.form-check) > td {
display: flex;
flex-wrap: wrap; // puts "hint" element below the input
}
@media screen and (min-width: @screen-width-bs-phone) {
.row.form-check > td {
display: flex;
}
}
}
.formcontainer {
display: flex;
flex-direction: column;
justify-content:flex-start;
overflow-y: hidden !important;
.formcontent {
overflow-x: hidden;
overflow-y: auto;
}
.formbuttons {
padding: 0.5rem 1rem;
button {
margin-right: .5rem;
}
}
// Prevent button truncation on tablets
html.iframe.ipad &,
html.iframe.webkit.tablet & {
.formbuttons {
min-height: 4rem;
}
}
// We don't need buttons element on small devices
html.layout-small &,
html.layout-phone & {
.formbuttons {
display: none;
}
}
}
.formcontent {
&:not(.popupmenu) {
padding: 1rem;
}
.row {
margin-right: 0; // without these the form is too wide causing horizontal scrollbar appearence
margin-left: 0;
// Note: We never use odd numbers here
.col, .col-2, .col-4, .col-6, .col-8, .col-10, .col-12,
.col-sm, .col-sm-2, .col-sm-4, .col-sm-6, .col-sm-8, .col-sm-10, .col-sm-12 {
// overwrite Bootstrap's redundant padding
padding: 0;
}
.form-control-plaintext,
label.col-form-label {
padding: .375rem .375rem .375rem 0;
}
.form-control-plaintext {
padding-bottom: 0;
border: 0;
}
@media screen and (max-width: @screen-width-bs-phone) {
&.form-group {
& > td label {
padding-bottom: 0;
}
}
}
}
.row.form-check {
padding: 0; // without these e.g. inputs in compose screen are not aligned properly
display: flex; // https://github.com/twbs/bootstrap/issues/22348
flex-wrap: nowrap;
- // fixed checkbox vertical alignment
- td:not(.col-form-label) {
- padding: 0;
- }
-
// alignment fixes needed because we do not stick precissely to Bootstrap's form structure
@media screen and (max-width: @screen-width-bs-phone) {
.col-6 {
max-width: 100%;
flex: auto;
}
& > *:last-child {
width: 1%;
- min-width: 2.2em; // for icon-checkbox
- padding-right: 15px;
+ min-width: 2.6rem; // for .custom-switch
}
&.with-link > *:last-child {
min-width: 8rem;
}
}
.form-check-input {
- margin: 0; // fixes checkbox alignment with other inputs in a form
- margin-top: .5rem;
- margin-bottom: .5rem;
+ margin: .5rem 0; // fixes checkbox alignment with other inputs in a form
+ }
- & + label {
- display: inline;
- }
+ .custom-control-label {
+ overflow: unset;
}
td > label {
padding-bottom: 0;
}
}
.nav-tabs {
margin-bottom: 1rem;
&:empty {
display: none;
}
}
.hint {
font-style: italic;
color: @color-form-hint;
}
// RAW (CodeMirror) editor
&.raweditor {
height: 100%;
form {
height: 100%;
}
textarea {
font-family: monospace;
height: 100%;
}
.CodeMirror {
border: 1px solid @color-input-border;
border-radius: .3rem;
height: 100%;
color: @color-font;
}
.CodeMirror-focused {
border-color: @color-input-border-focus;
box-shadow: 0 0 0 .2rem @color-input-border-focus-shadow;
}
.CodeMirror-linebackground.line-error {
background-color: @color-error;
opacity: 0.4;
}
.errorGutter {
width: .8em;
}
}
}
@media screen and (max-width: @screen-width-mini) {
.formcontent {
.col-form-label {
flex: auto;
max-width: 100%;
}
.col-6, .col-8, .col-10 {
flex: auto;
max-width: 100%;
}
}
}
/* Some common icons for "iconized inputs" */
.input-group .icon {
text-decoration: none;
padding: .375rem .5rem;
&.input-group-text {
min-width: 2.4rem;
}
&:before {
&:extend(.font-icon-class);
margin: 0 !important;
line-height: 1;
font-size: 1.1em;
}
&.user:before {
content: @fa-var-user;
}
&.pass:before {
content: @fa-var-lock;
}
&.host:before {
content: @fa-var-home;
}
&.language.before {
content: @fa-var-globe;
}
&.cancel:before {
content: @fa-var-times;
}
&.delete:before {
content: @fa-var-trash-alt;
}
&.edit:before {
content: @fa-var-pencil-alt;
}
&.add:before {
content: @fa-var-plus;
}
&.add.recipient:before {
content: @fa-var-users;
}
&.search:before {
content: @fa-var-search;
}
&.filter:before {
content: @fa-var-filter;
}
&.key:before {
content: @fa-var-key;
}
.inner {
display: none;
}
}
.input-group a {
&:focus {
background-color: @color-input-border-focus-shadow;
outline: 0;
}
}
@proplist-record-height: 2rem;
.proplist {
margin-bottom: 0;
padding: 0;
li {
list-style-type: none;
line-height: @proplist-record-height;
margin-bottom: .25rem;
display: flex;
&:last-child {
margin-bottom: 0;
}
input[type=radio] {
vertical-align: middle;
margin-right: .5em;
& + .input-group {
flex-grow: 1;
}
}
+ .custom-control-label {
+ overflow: unset; // for .custom-switch
+ }
+
label:not(.input-group-text) {
margin: 0;
line-height: @proplist-record-height;
}
- .icon-checkbox {
- height: @proplist-record-height;
- margin-left: -1.2em;
-
- & + label {
- height: @proplist-record-height;
- margin-right: .5rem;
- padding: 0 !important;
- line-height: 1.25;
-
- html.touch &:before {
- line-height: 1.1;
- }
- }
- }
-
select {
width: auto;
display: inline;
}
}
}
.checklist {
- .form-check-input + label {
- margin-right: .25rem;
- line-height: 1;
+ & > div {
+ line-height: 2rem;
}
- & > label {
- display: inline-block !important;
+ .custom-control-label {
+ &:before,
+ &:after {
+ margin: -.15rem 0 0 0;
+ }
}
-}
+ & > div + br {
+ display: none;
+ }
+}
/*** Forms in popups ***/
.popup form.propform {
padding: .5rem;
overflow-x: hidden;
}
.popupmenu.form {
&.nolist {
padding: 0 .5rem;
}
ul {
list-style-type: none;
padding: 0;
margin: 0;
}
li:not(.separator) {
padding: 0 1rem;
label {
margin: 0;
line-height: @listing-line-height;
input {
margin-right: .5rem;
}
}
}
input {
vertical-align: middle;
}
select {
margin: .5rem 0;
}
.buttons {
text-align: center;
padding: .5rem;
}
}
-html.touch .popupmenu.form {
- li {
- input.icon-checkbox + label:before {
- line-height: 1.4;
- }
- }
-}
-
/*** Smart list (multiple input) field ***/
.multi-input {
& > .content {
max-height: 11.65em;
overflow: hidden;
overflow-y: auto;
border-radius: .25rem;
border: 1px solid @color-input-border;
&.focused {
.style-input-focus;
}
// TODO: style button focus
}
a.icon {
&.reset:before {
&:extend(.font-icon-class);
content: @fa-var-trash-alt;
}
}
input.form-control {
padding-left: .75rem;
height: auto; // fixes input height
}
input,
input:focus,
.input-group-text {
border-radius: 0;
border: 0;
border-bottom: 1px solid @color-input-border;
box-shadow: none;
}
.input-group-text {
border-left: 1px solid @color-input-border;
}
.input-group-append {
margin-left: 0;
}
.input-group {
margin: 0 !important;
flex-wrap: nowrap; // Bootstrap makes them wrappable (imho)
&:last-child * {
border-bottom: 0;
}
}
& + .btn {
margin-top: .5rem;
}
&.is-invalid {
& > .content {
border: 1px solid @color-input-border-invalid;
&.focused {
border-color: @color-input-border-invalid;
box-shadow: 0 0 0 .2rem @color-input-border-invalid-shadow;
}
}
& > .invalid-feedback {
display: inline-block;
}
}
}
/*** Files upload widget with list of files, upload form and drop area ***/
.file-upload {
padding: 1rem 1rem 12rem;
margin: 0 1rem .25rem 1rem;
border-radius: .5rem;
border: .2rem dashed @color-table-border;
fieldset & {
margin: 0;
}
.upload-form {
text-align: center;
padding-bottom: 1em;
a.btn,
button {
margin-bottom: .25rem;
}
}
.hint {
margin-bottom: .5rem;
color: @color-form-hint;
text-align: center;
}
.attachmentslist {
li {
position: relative;
display: flex;
padding-right: 1.5em;
a.filename {
flex: 1;
}
a.delete,
a.cancelupload {
position: absolute;
right: 0;
width: auto; // fix button width if the widget is in a .popupmenu
&:before {
margin: 0;
}
}
.inner {
display: none;
}
}
}
&.droptarget {
padding-bottom: .5rem !important;
&:after {
content: @icon-file-drop;
width: 10rem;
margin: 5rem auto 0 auto;
display: block;
}
&.active {
border-color: darken(@color-toolbar-button-background-hover, 20%);
}
&.hover {
border-color: darken(@color-toolbar-button-background-hover, 20%);
background-color: @color-toolbar-button-background-hover;
}
}
}
/*** Smart recipient input field ***/
@recipient-input-margin-fix: .25rem;
.recipient-input {
display: flex;
flex-wrap: wrap;
padding: 0 .75rem @recipient-input-margin-fix .75rem;
list-style-type: none;
cursor: text;
height: auto; // reset .form-control height
&.focus {
.style-input-focus;
}
.recipient {
display: flex;
position: relative;
max-width: 50%;
border: 1px solid @color-recipient-input-border;
background-color: @color-recipient-input-background;
border-radius: .25em;
padding: 0 .25em;
margin-top: @recipient-input-margin-fix;
margin-right: .2em;
white-space: nowrap;
cursor: default;
@media screen and (max-width: 450px) {
width: 100%;
max-width: 100%;
}
}
.name {
.overflow-ellipsis;
flex-grow: 1;
display: inline-block;
line-height: 1.1;
padding: .25rem;
vertical-align: middle;
}
.email {
text-indent: -5000rem;
display: inline-block;
width: 0;
}
.quotes {
position: absolute;
width: 0;
opacity: 0;
}
a.button.icon {
font-size: .8em;
cursor: pointer;
padding: 0 0 0 .25em;
&:before {
float: none;
display: inline-block;
width: 1em;
margin: 0;
line-height: 1.5;
}
}
li {
max-width: 100%;
}
input {
width: 40px;
max-width: 100%;
background: transparent !important;
border: 0 !important;
margin-top: @recipient-input-margin-fix;
outline: 0;
line-height: 1.5;
&::-ms-clear {
display: none; // removes clear icon in IE11
}
}
}
/*** Tagedit widget (from jqueryui plugin) ***/
.tagedit-list {
display: flex;
flex-wrap: wrap;
padding: 0 .75rem @recipient-input-margin-fix .75rem;
margin: 0;
list-style-type: none;
min-height: 2.3rem;
& + .placeholder {
display: none;
}
&[tabindex="-1"] {
.style-input-focus;
}
li.tagedit-listelement-new {
margin-top: @recipient-input-margin-fix;
input {
width: 15px;
background: transparent !important;
border: 0;
outline: 0;
margin: 0;
padding: 0;
line-height: 1.5;
&.tagedit-input-disabled {
visibility: hidden;
}
}
}
li.tagedit-listelement-old {
max-width: 50%;
border: 1px solid @color-recipient-input-border;
background-color: @color-recipient-input-background;
border-radius: .25em;
margin-top: @recipient-input-margin-fix;
margin-right: .2em;
white-space: nowrap;
a /* TODO: .tagedit-close, .tagedit-break, .tagedit-delete, .tagedit-save */ {
font-size: .8em;
cursor: pointer;
display: inline-block;
width: 1.1em;
overflow: hidden;
vertical-align: middle;
margin-right: .2rem;
&:before {
&:extend(.font-icon-class);
content: @fa-var-times;
width: 1em;
line-height: 1.2;
}
}
span {
.overflow-ellipsis;
flex-grow: 1;
display: inline-block;
line-height: 1.4;
padding: 0 .25rem;
vertical-align: middle;
}
}
li.tagedit-listelement-focus {
// TODO
}
}
/*** Skin selection widget ***/
.skinselection {
white-space: nowrap;
display: table-row;
& > span {
display: table-cell;
vertical-align: middle;
padding: .1em .5em;
white-space: normal;
&:last-child {
padding-right: 0;
}
}
.skinitem input {
width: auto;
}
.skinname {
font-weight: bold;
}
.skinlicense,
.skinlicense a {
font-style: italic;
text-decoration: none;
}
.skinlicense a:hover {
text-decoration: underline;
}
.skinlicense,
.skinauthor {
font-size: 90%;
}
.skinthumbnail {
width: 64px;
height: 64px;
border: 1px solid @color-input-border;
background: #fff;
border-radius: 4px;
}
}
/*** Percent input with jQuery-UI slider ***/
// Structure: <input><span.label><div.ui-slider>
.input-percent-slider {
display: flex;
align-items: center;
input {
max-width: 4em;
}
span.label {
line-height: 2.4;
padding: 0 .5rem 0 .25rem;
}
div.ui-slider {
flex: 1;
margin: 0 .5em;
}
}
/*** Image upload widget ***/
.image-upload {
position: relative;
overflow: hidden;
cursor: pointer;
background-color: @color-image-upload-background;
a.button {
display: none;
position: absolute;
left: 0;
top: 0;
background-color: rgba(255, 255, 255, .85);
border-radius: 5px;
width: 2.5em;
padding: .5em;
margin: .5em;
line-height: 1;
}
&.changed a.button {
display: inline;
}
}
-
-/*** Pretty icon checkbox ***/
-
-input.icon-checkbox {
- opacity: 0;
- margin-left: -1.5em;
- cursor: pointer;
-
- .form-check > * > & {
- margin-left: -1.5em !important;
- }
-
- & + label {
- margin: 0;
- display: inline;
- font-size: 1.2rem;
- cursor: pointer;
- width: 1.5em;
-
- html.touch & {
- font-size: 1.5rem;
- }
-
- &:before {
- &:extend(.font-icon-class);
- content: @fa-var-toggle-off;
- margin: 0;
- color: @color-checkbox;
- height: 2rem;
- }
-
- // A link after checkbox in a form, used in e.g. newmail_notifier plugin
- & + a {
- display: inline-block;
- line-height: 2rem;
- padding-left: .5rem;
- white-space: nowrap;
- }
-
- .input-group & {
- padding: 0 .25rem 0 0;
-
- &:before {
- line-height: 1;
- height: auto;
- }
- }
- }
-
- &:disabled + label {
- opacity: 0.4;
- pointer-events: none;
- }
-
- &:checked + label:before {
- content: @fa-var-toggle-on;
- color: @color-checkbox-checked;
- }
-
- &:focus + label:before {
- color: @color-checkbox-focus;
- }
-
- &:checked:focus + label:before {
- color: @color-checkbox-checked-focus;
- }
-
- &.form-check-input + label {
- font-size: 1.25rem;
- padding-top: .25rem;
- }
-}
-
.input-group-combo {
select:first-of-type {
&.alone {
border-radius: .25rem !important;
}
&:not(.alone) {
flex: unset;
width: auto;
}
}
.input-group {
padding: 0 !important;
flex: 2;
}
select + select,
.input-group :first-child {
border-top-left-radius: 0;
border-bottom-left-radius: 0;
}
}
/*** General browser hacks ***/
// Remove outline on selects in Firefox
@-moz-document url-prefix() {
select:-moz-focusring {
color: transparent !important;
text-shadow: 0 0 0 @color-font !important;
}
}
/*** Bootstrap style overrides ***/
.form-control {
color: @color-font;
&:focus {
color: @color-font;
border-color: @color-input-border-focus;
box-shadow: 0 0 0 .2rem @color-input-border-focus-shadow;
}
// FIXME: This fixes style of required inputs in Firefox/Edge, but
// makes inputs on login form red-bordered, commented for now
// &:invalid,
&.is-invalid {
border-color: @color-input-border-invalid;
box-shadow: none;
&:focus {
border-color: @color-input-border-invalid;
box-shadow: 0 0 0 .2rem @color-input-border-invalid-shadow;
}
}
&::placeholder {
color: @color-input-placeholder;
}
}
.invalid-feedback {
color: @color-error;
}
.form-group {
margin-bottom: .5rem;
}
.input-group-text {
color: @color-input;
background-color: @color-input-addon-background;
input:focus {
z-index: 1;
border-color: @color-input-border-focus !important;
}
}
+
+.custom-switch {
+ .custom-control-input {
+ left: 0;
+
+ &:focus ~ .custom-control-label:before {
+ box-shadow: 0 0 0 .2rem @color-checkbox-focus-shadow;
+ }
+
+ &:focus:not(:checked) ~ .custom-control-label::before {
+ border-color: @color-checkbox-focus;
+ }
+
+ &:checked ~ .custom-control-label::before {
+ border-color: @color-checkbox;
+ background-color: @color-checkbox;
+ }
+
+ &:checked:disabled ~ .custom-control-label::before {
+ border-color: @color-checkbox-checked-disabled;
+ background-color: @color-checkbox-checked-disabled;
+ }
+ }
+
+ // Make switches bigger, we use smaller font than Bootstrap's default
+
+ html.touch & {
+ padding-left: 3rem;
+ }
+
+ .custom-control-label {
+ &:before {
+ width: 2rem;
+ height: 1.2rem;
+ border-radius: .6rem;
+ margin: .125rem 0;
+
+ html.touch & {
+ left: -3rem;
+ top: .2rem;
+ width: 2.6rem;
+ height: 1.6rem;
+ border-radius: .8rem;
+ margin: 0;
+ }
+ }
+
+ &:after {
+ width: ~"calc(1.2rem - 4px)";
+ height: ~"calc(1.2rem - 4px)";
+ border-radius: .6rem;
+ margin: .125rem 0;
+
+ html.touch & {
+ left: ~"calc(-3rem + 2px)";
+ top: ~"calc(.2rem + 2px)";
+ height: ~"calc(1.6rem - 4px)";
+ width: ~"calc(1.6rem - 4px)";
+ border-radius: .8rem;
+ margin: 0;
+ }
+ }
+ }
+
+ .custom-control-input:checked ~ .custom-control-label::after {
+ transform: translateX(.8rem);
+
+ html.touch & {
+ transform: translateX(1rem);
+ }
+ }
+}
diff --git a/skins/elastic/styles/widgets/lists.less b/skins/elastic/styles/widgets/lists.less
index 2a0f4d4da..cea75d42a 100644
--- a/skins/elastic/styles/widgets/lists.less
+++ b/skins/elastic/styles/widgets/lists.less
@@ -1,986 +1,1001 @@
/**
* Roundcube webmail styles for the Elastic skin
*
* Copyright (c) 2017-2018, The Roundcube Dev Team
*
* The contents are subject to the Creative Commons Attribution-ShareAlike
* License. It is allowed to copy, distribute, transmit and to adapt the work
* by keeping credits to the original authors in the README.md file.
* See http://creativecommons.org/licenses/by-sa/3.0/ for details.
*/
/*** List and treelist widgets ***/
.listing {
tbody td,
li {
border-bottom: 1px solid @color-list-border;
cursor: default;
font-weight: normal;
line-height: @listing-line-height;
}
tbody td,
li a {
padding: 0 .5rem;
white-space: nowrap;
vertical-align: middle;
color: @color-list;
}
tbody td {
.overflow-ellipsis;
outline: none;
a {
color: @color-list;
}
}
li a {
display: block;
text-decoration: none;
cursor: default;
width: 100%;
}
li.selected,
tr.selected td {
color: @color-list-selected;
background-color: @color-list-selected-background;
}
td.selection {
padding: 0 0 0 .5em;
width: 2em;
text-align: center;
& > input {
vertical-align: middle;
}
}
&:not(.withselection) td.selection {
display: none;
}
td.name {
.overflow-ellipsis;
}
td.action {
padding: 0 .5em;
width: 2em;
text-align: center;
&:empty {
width: 0;
}
a {
display: block;
overflow: hidden;
text-decoration: none;
&:before {
&:extend(.font-icon-class);
margin: 0;
font-size: 1rem;
}
}
a.pushgroup:before {
content: @fa-var-chevron-right;
}
}
li.droptarget > a,
tr.droptarget > td {
background-color: @color-list-droptarget-background;
}
li.disabled,
tr.disabled td {
color: @color-list-deleted;
}
li > a.virtual,
li.virtual > a {
opacity: .4;
}
span.secondary {
color: @color-list-secondary;
}
}
// Focus indicator
html:not(.touch) {
.listing {
li > a,
tbody tr > td:first-child,
&:not(.withselection) tbody tr > td.selection + td {
border-left: 2px solid transparent;
}
li > a:focus,
&.focus tbody tr.focused > td:first-child,
&.focus:not(.withselection) tbody tr.focused > td.selection + td {
border-left: 2px solid @color-list-focus-indicator;
outline: 0;
}
}
}
table.listing {
width: 100%;
table-layout: fixed;
// border-spacing/border-collapse here fix problem with our focus indicator
// when the table cells use overflow: hidden. I.e. we use border-spacing:0
// instead of Bootstrap's border-collapse:collapse. Is this cross-browser?
border-spacing: 0;
border-collapse: unset;
}
ul.listing {
margin: 0;
padding: 0;
& > ul {
padding: 0;
}
li {
.overflow-ellipsis;
white-space: nowrap;
position: relative;
list-style: none;
ul {
border-top: 1px solid @color-list-border;
padding-left: 1.5em;
li:last-child {
border-bottom: none;
}
}
- input.icon-checkbox + label,
- input[type=checkbox] {
+ .custom-switch {
position: absolute;
padding: 0;
top: 0;
- right: .5rem;
+ right: 0;
+ margin-right: .3rem;
height: @listing-line-height;
vertical-align: middle;
+
+ html.touch & {
+ margin-right: 0;
+ }
+
+ .custom-control-label {
+ &:before,
+ &:after {
+ margin-top: .4rem;
+
+ html.touch & {
+ margin-top: .8rem;
+ }
+ }
+ }
}
}
&.simplelist {
li {
padding: 0 .5rem;
}
}
}
.listing-info {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 80%;
text-align: center;
font-weight: bold;
color: @color-list-secondary;
}
html.touch {
.listing:not(.toolbar) li,
.listing tbody td {
line-height: @listing-touch-line-height;
font-size: 1.2rem;
}
li input[type=checkbox] {
height: @listing-touch-line-height;
}
td.selection {
padding: 0;
width: 3em;
}
}
@media screen and (max-width: @screen-width-large) {
.listing.selection-large-only {
li.selected {
color: @color-list;
background-color: transparent;
}
}
}
/* icons */
.listing.iconized li {
a:before {
&:extend(.font-icon-class);
height: 2em; // TODO: ?
margin-right: .5rem;
}
&.preferences > a:before {
content: @fa-var-sliders-h;
}
&.folders > a:before {
content: @fa-var-folder;
}
&.responses > a:before {
content: @fa-var-comment;
}
&.identities > a:before {
content: @fa-var-id-card;
}
&.password > a:before {
content: @fa-var-lock;
}
&.addressbook a:before {
.font-icon-regular(@fa-var-address-book);
}
&.contactgroup a:before {
.font-icon-solid(@fa-var-users);
}
&.contactsearch a:before {
content: @fa-var-search;
}
&.filter > a:before {
content: @fa-var-filter;
}
&.vacation > a:before {
.font-icon-regular(@fa-var-clock);
}
&.forward > a:before {
content: @fa-var-share-square;
}
&.enigma.keys > a:before {
content: @fa-var-key;
}
&.userinfo > a:before {
content: @fa-var-info-circle;
}
&.twofactorauth > a:before {
content: @fa-var-sign-in-alt;
}
a.help:before {
content: @fa-var-life-ring;
}
a.about:before {
.font-icon-regular(@fa-var-question-circle);
}
a.license:before {
content: @fa-var-shield-alt;
}
// autocomplete popup
& > i:before {
&:extend(.font-icon-class);
content: @fa-var-user;
margin-left: .5rem;
}
&.group > i:before {
content: @fa-var-users;
}
}
html.ie11 .listing.iconized li a:before {
font-size: 1.25rem;
}
.listing.iconized tr {
td:before {
&:extend(.font-icon-class);
margin-right: .5rem;
}
&.contact.person td.name:before {
content: @fa-var-user;
}
&.contact.group td.name:before {
content: @fa-var-users;
}
&.general > td.section:before {
content: @fa-var-desktop;
}
&.mailbox > td.section:before {
.font-icon-regular(@fa-var-envelope);
}
&.mailview > td.section:before {
content: @fa-var-inbox;
}
&.compose > td.section:before {
content: @fa-var-paper-plane;
}
&.addressbook > td.section:before {
content: @fa-var-users;
}
&.folders > td.section:before {
.font-icon-regular(@fa-var-folder);
}
&.server > td.section:before {
content: @fa-var-server;
}
&.enigma > td.section:before {
content: @fa-var-lock;
}
&.calendar > td.section:before {
content: @fa-var-calendar;
}
&.chat > td.section:before {
content: @fa-var-comments;
}
}
/* selecatable list: e.g. spellcheck language selection */
.listing.iconized.selectable li {
a:before {
&:extend(.font-icon-class);
content: "";
}
a.selected:before {
content: @fa-var-check;
}
}
.popupmenu .listing {
li > a {
border-left: 0;
}
li.selected {
color: @color-toolbarmenu-hover;
background-color: @color-toolbarmenu-hover-background;
}
td {
.overflow-ellipsis;
}
}
ul.treelist {
li {
div.treetoggle {
position: absolute;
top: 0;
left: 0;
width: @listing-treetoggle-width;
cursor: pointer;
background-color: transparent;
&:before {
&:extend(.font-icon-class);
content: @fa-var-angle-right;
margin-left: .25em;
font-size: 1em;
}
&.expanded:before {
content: @fa-var-angle-down;
}
}
& > a {
.overflow-ellipsis;
padding-left: @listing-treetoggle-width;
}
&.selected {
// reset .listing selection style
color: inherit;
background-color: transparent;
& > div > a, // this is used e.g. by kolab_addressbook
& > a {
color: @color-list-selected;
background-color: @color-list-selected-background;
}
}
ul {
padding: 0;
li {
padding-left: 0;
a { padding-left: (2 * @listing-treetoggle-width); }
div.treetoggle { left: @listing-treetoggle-width; }
li {
a { padding-left: (3 * @listing-treetoggle-width); }
div.treetoggle { left: (2 * @listing-treetoggle-width); }
li {
a { padding-left: (4 * @listing-treetoggle-width); }
div.treetoggle { left: (3 * @listing-treetoggle-width); }
li {
a { padding-left: (5 * @listing-treetoggle-width); }
div.treetoggle { left: (4 * @listing-treetoggle-width); }
li {
a { padding-left: (6 * @listing-treetoggle-width); }
div.treetoggle { left: (5 * @listing-treetoggle-width); }
}
}
}
}
}
}
}
&.notree {
div.treetoggle {
display: none;
}
li > a {
padding-left: .5em;
}
}
}
/*** Folders list widget ***/
.folderlist {
li {
&.mailbox {
&.unread {
& > a {
padding-right: 2.8em;
font-weight: bold;
}
}
&.recent {
color: @color-list-recent;
}
.unreadcount {
position: absolute;
top: 0;
right: 0;
min-width: 2em;
line-height: 1.4rem;
margin: (@listing-line-height - 1.4rem)/2;
padding: 0 .3em;
border-radius: .4em;
background: @color-list-badge-background;
color: @color-list-badge;
text-align: center;
font-weight: bold;
html.touch & {
line-height: 2rem;
margin: (@listing-touch-line-height - 2rem)/2;
}
}
&.recent > .unreadcount {
background: @color-list-recent-badge-background;
color: @color-list-recent-badge;
}
&.root {
display: none !important;
// FIXME: This element is confusing, I propose to not use it
}
}
a:before {
&:extend(.font-icon-class);
.font-icon-regular(@fa-var-folder);
margin-right: .5rem;
}
&.inbox > a:before {
.font-icon-solid(@fa-var-inbox);
}
&.trash a:before {
.font-icon-solid(@fa-var-trash-alt);
}
&.trash.empty > a:before {
.font-icon-regular(@fa-var-trash-alt);
}
&.drafts a:before {
.font-icon-solid(@fa-var-pencil-alt);
}
&.sent a:before {
.font-icon-solid(@fa-var-paper-plane);
}
&.junk a:before {
.font-icon-solid(@fa-var-fire-alt);
}
&.archive > a:before {
.font-icon-solid(@fa-var-archive);
}
}
// folder-selector fix for left padding
&.toolbarmenu a:before {
margin-left: .5em;
}
&.toolbarmenu {
li {
a:before {
margin-right: .25em;
}
}
}
}
/*** Messages list widget ***/
.messagelist > thead,
.messagelist .branch,
table.fixedcopy {
display: none;
}
.messagelist {
td {
border-left: 0;
width: 2em;
vertical-align: top;
font-size: 1rem !important;
}
td.subject {
width: 100%;
padding-right: 0;
display: flex;
flex-wrap: wrap;
a {
text-decoration: none;
cursor: default;
}
span {
line-height: 2em;
&.date {
font-size: 90%;
color: @color-list-secondary;
}
&.fromto {
.overflow-ellipsis;
flex: 1;
font-size: 90%;
color: @color-list-secondary;
padding-left: 1.5em;
padding-right: .5rem;
}
&.subject {
.overflow-ellipsis;
width: 100%;
}
}
}
td.threads {
padding: 0 0 0 .25rem;
width: 1.5em;
}
td.flags {
width: 2.5em;
& > span {
height: 1.7em;
line-height: 1.7em;
display: block;
&.flag {
cursor: pointer;
}
}
}
tr.flagged td,
tr.flagged td.subject span.subject a,
tr.flagged td.subject span.date,
tr.flagged td.subject span.fromto {
color: @color-list-flagged;
}
tr.deleted td,
tr.deleted td.subject span.subject a,
tr.deleted td.subject span.date,
tr.deleted td.subject span.fromto {
color: @color-list-deleted;
}
tr.unread td.subject span.subject {
font-weight: bold;
}
// thread parent message with unread children
tr.unroot td.subject a {
text-decoration: underline;
}
tr.thread td.threads div:before {
&:extend(.font-icon-class);
content: @fa-var-angle-right;
cursor: pointer;
width: 1em;
}
tr.thread.expanded td.threads div:before {
content: @fa-var-angle-down;
}
td.subject span.msgicon.status {
&:before {
&:extend(.font-icon-class);
content: @fa-var-circle;
cursor: pointer;
font-size: .4rem;
width: 1.1rem;
height: 2rem;
}
&.unread:before {
content: @fa-var-circle;
color: @color-list-unread-status;
font-size: .5rem;
}
&.unreadchildren:before {
.font-icon-regular(@fa-var-circle);
font-size: .5rem;
}
&.replied:before {
.font-icon-solid(@fa-var-reply);
font-size: 1rem;
}
&.forwarded:before {
.font-icon-solid(@fa-var-share);
font-size: 1rem;
}
&.replied.forwarded:before {
.font-icon-solid(@fa-var-reply); // TODO
font-size: 1rem;
}
tr.deleted &:before {
.font-icon-solid(@fa-var-ban);
font-size: 1rem;
}
}
span.attachment span {
&:extend(.font-icon-class);
color: @color-list-icon;
&:before {
margin: 0;
content: @fa-var-paperclip;
}
&.report:before {
.font-icon-regular(@fa-var-file-alt);
}
&.encrypted:before {
content: @fa-var-lock;
}
&.vcard:before {
.font-icon-regular(@fa-var-user); // vcard_attachments plugin
}
}
span.flagged:before {
&:extend(.font-icon-class);
content: @fa-var-flag;
}
tr:hover span.unflagged:before {
&:extend(.font-icon-class);
.font-icon-regular(@fa-var-flag);
}
}
// On touch devices hide flag icon, but do it in a way
// that saves as much room as possible, keeping the attachment icon
html.layout-phone,
html.touch {
.messagelist {
tr {
position: relative;
}
td.flags {
top: .25rem;
right: 0;
bottom: 0;
.flag {
visibility: hidden;
}
}
td.subject {
padding-right: .5em;
.subject {
padding-right: 1.5rem;
}
}
}
}
/* Contacts list */
.contactlist {
.contact.readonly td {
font-style: italic;
}
td.action {
// TODO
a {
// TODO
}
}
// for contacts list in mail compose
td.contact:before {
&:extend(.font-icon-class);
content: @fa-var-user;
}
// for contacts list in mail compose
td.contactgroup:before {
&:extend(.font-icon-class);
content: @fa-var-users;
}
span.email {
display: inline;
color: @color-list-secondary;
font-style: italic;
margin-left: .5em;
}
li {
a:before {
&:extend(.font-icon-class);
margin-right: .5rem;
}
a.addressbook::before {
.font-icon-regular(@fa-var-address-book);
}
a.contactgroup::before {
.font-icon-solid(@fa-var-users);
}
}
}
/* Attachments list */
@attachmentslist-item-height: 2rem;
.attachmentslist {
padding: 0;
margin: 0;
li {
list-style: none;
display: inline-flex;
white-space: nowrap;
line-height: @attachmentslist-item-height;
max-width: 100%;
&:before {
&:extend(.font-icon-class);
.font-icon-regular(@fa-var-file);
height: @attachmentslist-item-height;
}
&.txt:before,
&.text:before {
.font-icon-regular(@fa-var-file-alt);
}
&.pdf:before {
.font-icon-regular(@fa-var-file-pdf);
}
&.odt:before,
&.doc:before,
&.docx:before,
&.msword:before {
.font-icon-regular(@fa-var-file-word);
}
&.ods:before,
&.xls:before,
&.xlsx:before,
&.msexcel:before {
.font-icon-regular(@fa-var-file-excel);
}
&.rar:before,
&.zip:before,
&.gz:before {
.font-icon-regular(@fa-var-file-archive);
}
&.image:before,
&.jpg:before,
&.jpeg:before,
&.png:before {
.font-icon-regular(@fa-var-file-image);
}
&.mp3:before,
&.audio:before {
.font-icon-regular(@fa-var-file-audio);
}
&.m4p:before,
&.video:before {
.font-icon-regular(@fa-var-file-video);
}
&.ics:before,
&.calendar:before {
// TODO
}
&.vcard:before {
.font-icon-regular(@fa-var-address-card);
}
&.html:before {
.font-icon-regular(@fa-var-file-code);
}
&.eml:before,
&.rfc822:before {
// TODO
}
&.odp:before,
&.otp:before,
&.ppt:before,
&.pptx:before,
&.ppsx:before,
&.vnd.mspowerpoint:before {
.font-icon-regular(@fa-var-file-powerpoint);
}
&.sig:before,
&.pgp-signature:before,
&.pkcs7-signature:before {
// TODO
}
&.application.asc:before {
// TODO
}
&.application.pgp-keys:before {
// TODO
}
a {
text-decoration: none;
line-height: @attachmentslist-item-height;
height: @attachmentslist-item-height;
}
a.cancelupload:before,
a.delete:before {
&:extend(.font-icon-class);
content: @fa-var-trash-alt;
line-height: @attachmentslist-item-height;
height: @attachmentslist-item-height;
}
&.uploading:before {
.animated-icon-class;
.font-icon-solid(@fa-var-circle-notch);
}
a.filename {
display: flex;
overflow: hidden;
}
.attachment-name {
.overflow-ellipsis;
color: @color-font;
}
.attachment-size {
color: @color-list-secondary;
padding: 0 .25em;
}
}
}
.keylist {
padding: 0;
list-style: none;
li {
line-height: 2;
&:before {
&:extend(.font-icon-class);
content: @fa-var-key;
line-height: 1.5;
}
}
}
#identities-table {
td.mail:before {
&:extend(.font-icon-class);
content: @fa-var-id-card;
}
}
#responses-table {
td.name:before {
&:extend(.font-icon-class);
content: @fa-var-comment;
}
}
#filterslist {
td.name:before {
&:extend(.font-icon-class);
content: @fa-var-filter;
}
}
#filtersetslist {
td.name:before {
&:extend(.font-icon-class);
content: @fa-var-file-alt;
}
}
#subscription-table {
li.mailbox a {
padding-right: 2.5rem;
}
}
diff --git a/skins/elastic/styles/widgets/toolbar.less b/skins/elastic/styles/widgets/toolbar.less
index d9199765f..798d12695 100644
--- a/skins/elastic/styles/widgets/toolbar.less
+++ b/skins/elastic/styles/widgets/toolbar.less
@@ -1,895 +1,870 @@
/**
* Roundcube webmail styles for the Elastic skin
*
* Copyright (c) 2017-2018, The Roundcube Dev Team
*
* The contents are subject to the Creative Commons Attribution-ShareAlike
* License. It is allowed to copy, distribute, transmit and to adapt the work
* by keeping credits to the original authors in the README.md file.
* See http://creativecommons.org/licenses/by-sa/3.0/ for details.
*/
/*** Toolbar widget ***/
.toolbar {
margin: 0;
font-size: 1rem;
text-align: center;
&.listing a,
a {
text-decoration: none;
cursor: pointer !important; // TODO: re-consider .listing class use on toolbar
color: @color-toolbar-button;
}
a.button {
display: inline-block;
border: 0 !important;
height: @layout-header-height;
min-width: 3.5rem;
max-width: 6rem;
width: auto; // reset width defined for links in .listing
padding: .45rem;
text-align: center;
text-overflow: ellipsis;
overflow-x: hidden;
line-height: 1.5;
&:hover,
&:focus {
outline: 0;
}
&.selected {
color: @color-success;
}
&:before {
&:extend(.font-icon-class);
float: none;
margin: 0;
line-height: 1.75;
}
&.reply:before {
content: @fa-var-reply;
}
&.reply-all:before {
content: @fa-var-reply-all;
}
&.forward:before {
content: @fa-var-share;
}
&.delete:before {
content: @fa-var-trash-alt;
}
&.markmessage:before {
content: @fa-var-tag;
}
&.more:before {
content: @fa-var-ellipsis-h;
}
&.dropdown:before {
content: @fa-var-caret-down;
}
&.settings:before {
content: @fa-var-sliders-h;
}
&.create:before {
content: @fa-var-plus-square;
}
&.move:before {
content: @fa-var-arrows-alt;
}
&.purge:before {
content: @fa-var-eraser;
}
&.print:before {
content: @fa-var-print;
}
&.search:before {
content: @fa-var-search;
}
&.upload:before,
&.import:before {
content: @fa-var-upload;
}
&.download:before,
&.export:before {
content: @fa-var-download;
}
&.compose:before {
content: @fa-var-edit;
}
&.archive:before {
content: @fa-var-archive;
}
&.junk:before {
content: @fa-var-fire-alt;
}
&.notjunk:before {
content: @fa-var-inbox;
}
&.enigma:before,
&.encrypt:before {
content: @fa-var-lock;
}
&.prev:before {
content: @fa-var-arrow-left;
}
&.next:before {
content: @fa-var-arrow-right;
}
&.firstpage:before {
content: @fa-var-angle-double-left;
}
&.prevpage:before {
content: @fa-var-angle-left;
}
&.nextpage:before {
content: @fa-var-angle-right;
}
&.lastpage:before {
content: @fa-var-angle-double-right;
}
&.send:before {
content: @fa-var-paper-plane;
}
&.back:before {
content: @fa-var-arrow-left;
}
&.closewin:before {
content: @fa-var-window-close;
}
&.save:before {
.font-icon-regular(@fa-var-save);
}
&.vcard:before,
&.attach:before {
content: @fa-var-paperclip;
}
&.spellcheck:before {
content: @fa-var-magic; // TODO
}
&.signature:before {
content: @fa-var-signature;
}
&.responses:before {
content: @fa-var-comment;
}
&.select:before {
.font-icon-regular(@fa-var-check-circle);
}
&.threads:before {
content: @fa-var-comments;
}
&.actions:before {
content: @fa-var-cog;
}
&.refresh:before {
content: @fa-var-sync;
}
&.addressbook:before {
content: @fa-var-user;
}
&.expand:before {
content: @fa-var-angle-right;
}
&.collapse:before {
content: @fa-var-angle-down;
}
&.submit:before {
content: @fa-var-check;
}
&.edit:before {
content: @fa-var-pencil-alt;
}
&.qrcode:before {
content: @fa-var-qrcode;
}
&.properties:before {
content: @fa-var-file;
}
&.zoomin:before {
content: @fa-var-search-plus;
}
&.zoomout:before {
content: @fa-var-search-minus;
}
&.rotate:before {
content: @fa-var-redo-alt;
}
}
&:not(.popupmenu) span.inner {
font-size: 90%;
font-weight: normal;
}
.dropbutton {
a.button.dropdown {
font-size: 75%;
overflow: hidden; // for IE11
span.inner {
display: none;
}
}
a.button:first-child {
padding-right: 0;
}
}
& > .spacer {
width: 1em;
}
&.pagenav {
display: flex;
justify-content: space-between;
align-items: center;
a.button {
flex-grow: 1;
min-width: 2rem !important;
height: @layout-footer-small-height;
&:before {
line-height: 1.25;
}
}
.pagenav-text {
.overflow-ellipsis;
color: @color-list-pagenav;
flex-grow: 4;
font-size: 80%;
white-space: nowrap;
}
input {
width: 3rem;
max-width: 5rem;
font-size: 90%;
text-align: center;
max-height: 1.6rem;
html.layout-phone & {
display: none;
}
}
span.inner {
display: none;
}
&.pagenav-list {
cursor: pointer;
background-color: @color-searchbar-background;
border-bottom: 1px solid @color-list-border;
a.button {
flex-grow: unset;
}
.pagenav-text {
text-align: left;
font-size: 100%;
}
& + .navlist {
background-color: #fbfbfb;
}
&.expanded + .navlist {
border-bottom: 1px solid @color-layout-border;
}
}
}
&.footer {
a.button:before {
height: 1.75rem;
float: none;
display: block;
width: auto;
}
}
&.footer.small {
a.button:before {
height: auto; // for Chrome
}
}
&.content-frame-navigation.hide-nav-buttons {
a.next,
a.prev {
display: none;
}
}
.listselectors {
max-width: 100%;
display: flex;
justify-content: space-around;
}
}
.header {
a.button {
color: @color-toolbar-button;
}
.buttons {
display: block;
button {
display: block;
float: left;
cursor: pointer;
color: @color-toolbar-button;
background-color: transparent;
border: 0;
padding: 0;
height: @layout-touch-header-height;
line-height: @layout-touch-header-height;
width: 2.5em;
&:before {
font-size: 1.75rem;
}
}
a.button {
display: inline-block;
&:before {
&:extend(.font-icon-class);
float: none;
}
&.reply:before {
content: @fa-var-reply;
}
&.send:before {
content: @fa-var-paper-plane;
}
}
}
}
.popupmenu.toolbar.listing {
a.button {
max-width: 100%;
width: 100%;
text-align: left;
line-height: @layout-touch-menu-record-height;
height: @layout-touch-menu-record-height;
&:before {
display: inline-block;
line-height: inherit;
margin-right: .5rem;
}
}
.dropbutton {
display: flex;
a.button:first-child {
.overflow-ellipsis;
flex: 1;
}
a.button.dropdown {
font-size: 100%;
cursor: pointer;
width: auto;
&:before {
content: @fa-var-angle-right;
margin-left: .5em;
margin-right: 0;
}
span.inner {
display: none;
}
}
}
li.spacer {
display: none;
}
li:last-child {
border: 0;
}
}
.toolbarmenu li {
&.separator {
// TODO: all separator properties
line-height: 1.5rem !important;
font-size: .75rem !important;
padding: 0 .5rem;
color: @color-popover-separator;
background-color: @color-popover-separator-background;
label {
margin: 0; // Unsets Bootstrap label margin, bug?
}
}
- &.checkbox > label {
- margin: 0;
- width: 100%;
-
- input.icon-checkbox {
- right: auto;
-
- & + label {
- left: 0;
- margin: 0 .2em 0 .35em;
- font-size: 1.1rem;
- }
- }
-
- a {
- padding: 0 !important;
- }
- }
-
a {
opacity: .5;
&.active {
opacity: 1;
}
}
&:last-child {
border-bottom: none;
}
a:before {
&:extend(.font-icon-class);
}
+ a.check:before {
+ content: "";
+ }
+ a.check.selected:before {
+ content: @fa-var-check;
+ }
a.print:before {
content: @fa-var-print;
}
a.copy:before {
content: @fa-var-copy;
}
a.move:before {
content: @fa-var-arrows-alt;
}
a.purge:before {
content: @fa-var-eraser;
}
a.source:before {
content: @fa-var-file-code;
}
a.download:before {
content: @fa-var-download;
}
a.extwin:before {
content: @fa-var-external-link-square-alt;
}
a.create:before {
content: @fa-var-plus-square;
}
a.edit:before {
content: @fa-var-edit;
}
a.edit.asnew:before {
content: @fa-var-pencil-alt;
}
a.rename:before {
content: @fa-var-pencil-alt;
}
a.read:before {
.font-icon-regular(@fa-var-circle);
}
a.unread:before {
content: @fa-var-circle;
}
a.flag:before {
content: @fa-var-flag;
}
a.unflag:before {
.font-icon-regular(@fa-var-flag);
}
a.filterlink:before {
content: @fa-var-filter;
}
a.reply.list:before,
a.reply.all:before {
content: @fa-var-reply-all;
}
a.forward:before,
a.forward.bounce:before,
a.forward.attachment:before,
a.forward.inline:before {
content: @fa-var-share;
}
a.download.mbox:before,
a.download.eml:before,
a.download.maildir:before {
content: @fa-var-download;
}
a.export.selection:before,
a.export.all:before {
content: @fa-var-download;
}
a.select:before {
content: @fa-var-check-circle;
}
a.threads:before {
content: @fa-var-comments;
}
a.selection:before {
content: @fa-var-mouse-pointer;
}
a.select.all:before {
.font-icon-regular(@fa-var-check-square);
}
a.select.none:before {
content: @fa-var-times;
}
a.select.page:before {
content: @fa-var-bars;
}
a.select.flagged:before {
content: @fa-var-flag;
}
a.select.unread:before {
content: @fa-var-circle;
}
a.select.invert:before {
.font-icon-regular(@fa-var-square);
}
a.expand.all:before,
a.expand.unread:before,
a.expand.none:before {
content: @fa-var-comments;
}
a.search:before {
content: @fa-var-search;
}
a.delete:before {
content: @fa-var-trash-alt;
}
a.expunge:before {
content: @fa-var-compress-arrows-alt;
}
a.import:before {
content: @fa-var-upload;
}
a.settings:before {
content: @fa-var-sliders-h;
}
a.insertresponse:before {
content: @fa-var-comment;
}
a.compose:before {
content: @fa-var-edit;
}
a.addressbook:before {
content: @fa-var-user;
}
a.recipient:before {
.font-icon-regular(@fa-var-envelope);
}
a.status:before {
.font-icon-regular(@fa-var-lightbulb);
}
a.folders:before {
content: @fa-var-folder;
}
a.remove:before {
content: @fa-var-eraser;
}
a.showurl:before {
content: @fa-var-link;
}
a.qrcode:before {
content: @fa-var-qrcode;
}
a.assigngroup:before {
content: @fa-var-user-plus;
}
a.removegroup:before {
content: @fa-var-user-times;
}
a.vcard:before {
content: @fa-var-paperclip;
}
a.encrypt:before {
content: @fa-var-lock;
}
a.encrypt.sign:before {
content: @fa-var-lock; // TODO
}
a.junk:before {
content: @fa-var-fire-alt;
}
a.notjunk:before {
content: @fa-var-inbox;
}
}
.toolbarmenu.listing li {
- &.checkbox > label {
- padding: 0 .5rem 0 2.5em;
- }
-
&:hover {
- &.checkbox > label,
- input.icon-checkbox + label:before,
a.active {
color: @color-toolbarmenu-hover;
background-color: @color-toolbarmenu-hover-background;
}
}
}
html.touch {
.toolbarmenu.listing td,
.toolbarmenu.listing li,
#layout > :not(.content) > .header a.button {
font-size: 1.2rem;
}
.toolbarmenu.listing li {
- &.checkbox > label {
- padding: 0 .5rem 0 2.75rem;
- }
}
.toolbarmenu li {
- input.icon-checkbox + label {
- font-size: 1.3rem;
- }
}
}
html.ie11 .toolbar .dropbutton a.dropdown:before {
font-size: 80%;
}
@media screen and (min-width: (@screen-width-small + 1px)) {
.toolbar {
.header > & {
display: flex; // for Safari on desktop
}
a.button {
&:not(.disabled):focus,
&:not(.disabled):hover {
background-color: @color-toolbar-button-background-hover;
}
}
&.listing li {
display: inline-block;
border: 0;
a.button:before {
height: 1.75rem;
float: none;
display: block;
width: auto;
margin: 0;
}
}
.dropbutton {
height: @layout-header-height;
display: inline-block;
&:not(.disabled):hover {
background-color: @color-toolbar-button-background-hover;
}
a.button.dropdown {
min-width: 1.1rem;
padding: 0 .3rem;
&:hover {
background-color: darken(@color-toolbar-button-background-hover, 5%);
}
&:before {
height: @layout-header-height;
line-height: 4.2;
}
}
}
}
.toolbar.content-frame-navigation {
display: none !important;
}
.header a.button.icon {
&:not(.disabled):focus,
&:not(.disabled):hover {
background-color: @color-toolbar-button-background-hover;
outline: 0;
}
&:before {
margin: 0;
}
}
}
@media screen and (max-width: @screen-width-small) {
body > #layout > div > .toolbar.footer {
justify-content: space-around;
& > * {
flex-grow: 1;
}
.buttons {
display: flex;
justify-content: space-around;
}
.listselectors > * {
flex-grow: 1;
}
}
.toolbar.listing a {
color: @color-font;
}
}
a.button.icon.toolbar-button {
order: 8;
@media screen and (min-width: (@screen-width-large + 1px)) {
height: @layout-header-height;
line-height: 1.5;
padding: .45rem;
&:before {
float: none;
height: 1.75rem;
line-height: 1.75;
width: auto;
}
span.inner {
display: inline;
font-weight: normal;
font-size: 90%;
}
}
}
/*** Searchbar and searchoptions widgets ***/
.searchbar {
height: @layout-searchbar-height;
min-height: @layout-searchbar-height; // because of Flexbox
line-height: @layout-searchbar-height;
background-color: @color-searchbar-background;
border-bottom: 1px solid @color-list-border;
display: flex;
align-items: center;
white-space: nowrap;
overflow: hidden;
position: relative;
padding-right: .5rem;
form {
flex: 1;
display: flex;
&:before {
&:extend(.font-icon-class);
content: @fa-var-search;
height: @layout-searchbar-height;
color: @color-list-pagenav;
margin: 0 0 0 .75rem;
}
}
input {
width: 100%;
border: 0;
background: transparent;
padding: .5rem;
line-height: normal; // fixes placeholder misalignment in IE11
outline: 0; // removes focus outline in Chrome
}
a.button {
height: @layout-searchbar-height;
min-width: auto;
color: @color-toolbar-button;
&:before {
&:extend(.font-icon-class);
}
&.options {
&:before {
content: @fa-var-angle-down;
line-height: 1.25;
width: 1em;
}
}
&.reset {
display: none;
&:before {
content: @fa-var-times;
font-size: 1em;
}
}
&.search {
display: none;
}
}
span.inner {
display: none;
}
&.active {
a.button.reset {
display: inline;
}
}
&.open a.button.options:before {
content: @fa-var-angle-up;
}
}
.searchoptions {
button.search {
width: 100%;
}
ul.proplist {
& + div {
margin-top: 1rem;
}
}
.input-group {
&:not(:last-child) {
margin-bottom: .5rem;
}
.input-group-prepend {
width: 30%;
}
label {
width: 100%;
}
}
.formbuttons {
// this is needed because we hide .formbuttons on small devices
// we don't want it for search options form
display: block !important;
}
}
diff --git a/skins/elastic/ui.js b/skins/elastic/ui.js
index ecf063de2..f9be082bf 100644
--- a/skins/elastic/ui.js
+++ b/skins/elastic/ui.js
@@ -1,3919 +1,3929 @@
/**
* Roundcube webmail functions for the Elastic skin
*
* Copyright (c) 2017-2018, The Roundcube Dev Team
*
* The contents are subject to the Creative Commons Attribution-ShareAlike
* License. It is allowed to copy, distribute, transmit and to adapt the work
* by keeping credits to the original autors in the README file.
* See http://creativecommons.org/licenses/by-sa/3.0/ for details.
*
* @license magnet:?xt=urn:btih:90dc5c0be029de84e523b9b3922520e79e0e6f08&dn=cc0.txt CC0-1.0
*/
"use strict";
function rcube_elastic_ui()
{
var ref = this,
mode = 'normal', // one of: large, normal, small, phone
touch = false,
ios = false,
is_framed = rcmail.is_framed(),
env = {
config: {
standard_windows: rcmail.env.standard_windows,
message_extwin: rcmail.env.message_extwin,
compose_extwin: rcmail.env.compose_extwin,
help_open_extwin: rcmail.env.help_open_extwin
},
+ checkboxes: 0,
small_screen_config: {
standard_windows: true,
message_extwin: false,
compose_extwin: false,
help_open_extwin: false
}
},
menus = {},
content_buttons = [],
frame_buttons = [],
layout = {
menu: $('#layout > .menu'),
sidebar: $('#layout > .sidebar'),
list: $('#layout > .list'),
content: $('#layout > .content'),
},
buttons = {
menu: $('a.menu-button'),
back_sidebar: $('a.back-sidebar-button'),
back_list: $('a.back-list-button'),
back_content: $('a.back-content-button'),
};
// Public methods
this.register_content_buttons = register_content_buttons;
this.menu_hide = menu_hide;
this.menu_toggle = menu_toggle;
this.menu_destroy = menu_destroy;
this.popup_init = popup_init;
this.about_dialog = about_dialog;
this.headers_dialog = headers_dialog;
this.import_dialog = import_dialog;
this.headers_show = headers_show;
this.spellmenu = spellmenu;
this.searchmenu = searchmenu;
this.headersmenu = headersmenu;
this.header_reset = header_reset;
this.compose_status = compose_status;
this.attachmentmenu = attachmentmenu;
this.mailtomenu = mailtomenu;
this.recipient_selector = recipient_selector;
this.show_list = show_list;
this.show_sidebar = show_sidebar;
this.smart_field_init = smart_field_init;
this.smart_field_reset = smart_field_reset;
this.form_errors = form_errors;
this.switch_nav_list = switch_nav_list;
this.searchbar_init = searchbar_init;
this.pretty_checkbox = pretty_checkbox;
this.pretty_select = pretty_select;
this.datepicker_init = datepicker_init;
this.bootstrap_style = bootstrap_style;
// Detect screen size/mode
screen_mode();
// Initialize layout
layout_init();
// Convert some elements to Bootstrap style
bootstrap_style();
// Initialize responsive toolbars (have to be before popups init)
toolbar_init();
// Initialize content frame and list handlers
content_frame_init();
// Initialize menu dropdowns
dropdowns_init();
// Setup various UI elements
setup();
// Update layout after initialization
resize();
/**
* Setup procedure
*/
function setup()
{
var title, form, content_buttons = [];
// Intercept jQuery-UI dialogs...
$.ui && $.widget('ui.dialog', $.ui.dialog, {
open: function() {
// .. to unify min width for iframe'd dialogs
if ($(this.element).is('.iframe')) {
this.options.width = Math.max(576, this.options.width);
}
this._super();
// ... to re-style them on dialog open
dialog_open(this);
return this;
},
close: function() {
this._super();
// ... to close custom select dropdowns on dialog close
$('.select-menu:visible').remove();
return this;
}
});
// menu/sidebar/list button
buttons.menu.on('click', function() { app_menu(true); return false; });
buttons.back_sidebar.on('click', function() { show_sidebar(); return false; });
buttons.back_list.on('click', function() { show_list(); return false; });
buttons.back_content.on('click', function() { show_content(true); return false; });
// Initialize search forms
$('.searchbar').each(function() { searchbar_init(this); });
// Set content frame title in parent window (exclude ext-windows and dialog frames)
if (is_framed && !rcmail.env.extwin && !parent.$('.ui-dialog:visible').length) {
if (title = $('h1.voice:first').text()) {
parent.$('#layout > .content > .header > .header-title:not(.constant)').text(title);
}
}
else if (!is_framed) {
title = $('.boxtitle:first', layout.content).detach().text();
if (!title) {
title = $('h1.voice:first').text();
}
if (title) {
$('.header > .header-title', layout.content).text(title);
}
}
// Add content frame toolbar in the footer, for content buttons and navigation
if (!is_framed && layout.content.length && !$(layout.content).is('.no-navbar')
&& !$(layout.content).children('.frame-content').length
) {
env.frame_nav = $('<div class="footer toolbar content-frame-navigation hide-nav-buttons">')
.append($('<a class="button prev">')
.append($('<span class="inner"></span>').text(rcmail.gettext('previous'))))
.append($('<span class="buttons">'))
.append($('<a class="button next">')
.append($('<span class="inner"></span>').text(rcmail.gettext('next'))))
.appendTo(layout.content);
}
// Move some buttons to the frame footer toolbar
$('a[data-content-button]').each(function() {
content_buttons.push(create_cloned_button($(this)));
});
// Move form buttons from the content frame into the frame footer (on parent window)
$('.formbuttons').filter(function() { return !$(this).parent('.searchoptions').length; }).children().each(function() {
var target = $(this);
// skip non-content buttons
if (!is_framed && !target.parents('.content').length) {
return;
}
if (target.is('.cancel')) {
target.addClass('hidden');
return;
}
content_buttons.push(create_cloned_button(target));
});
(is_framed ? parent.UI : ref).register_content_buttons(content_buttons);
// Mail compose features
if (form = rcmail.gui_objects.messageform) {
form = $('form[name="' + form + '"]');
// Show input elements with non-empty value
// These event handlers need to be registered before rcmail 'init' event
$('#_cc, #_bcc, #_replyto, #_followupto', $('.compose-headers')).each(function() {
$(this).on('change', function() {
$('#compose' + $(this).attr('id'))[this.value ? 'removeClass' : 'addClass']('hidden');
});
});
// We put compose options outside of the main form
// Because IE/Edge (<16) does not support 'form' attribute we'll copy
// inputs into the main form as hidden fields
// TODO: Consider doing this for IE/Edge only, just set the 'form' attribute on others
$('#compose-options').find('textarea,input,select').each(function() {
var hidden = $('<input>')
.attr({type: 'hidden', name: $(this).attr('name')})
.appendTo(form);
$(this).attr('tabindex', 2)
.on('change', function() {
hidden.val(this.type != 'checkbox' || this.checked ? $(this).val() : '');
})
.change();
});
}
// Use smart recipient inputs
// This have to be after mail compose feature above
$('[data-recipient-input]').each(function() { recipient_input(this); });
// Image upload widget
$('.image-upload').each(function() { image_upload_input(this); });
// Add HTML/Plain tabs (switch) on top of textarea with TinyMCE editor
$('textarea[data-html-editor]').each(function() { html_editor_init(this); });
$('#dragmessage-menu,#dragcontact-menu').each(function() {
rcmail.gui_object('dragmenu', this.id);
});
// Taskmenu items added by plugins do not use elastic classes (e.g help plugin)
// it's for larry skin compat. We'll assign 'button', 'selected' and icon-specific class.
$('#taskmenu > a').each(function() {
if (/button-([a-z]+)/.test(this.className)) {
var data, name = RegExp.$1,
button = find_button(this.id);
if (button && (data = button.data)) {
if (data.sel) {
data.sel += ' button ' + name;
data.sel = data.sel.replace('button-selected', 'selected');
}
if (data.act) {
data.act += ' button ' + name;
}
rcmail.buttons[button.command][button.index] = data;
rcmail.init_button(button.command, data);
}
$(this).addClass('button ' + name);
$('.button-inner', this).addClass('inner');
}
$(this).on('mouseover', function() { rcube_webmail.long_subject_title(this, 0, $('span.inner', this)); });
});
// Some plugins use 'listbubtton' class, we'll replace it with 'button'
$('.listbutton').each(function() {
var button = find_button(this.id);
$(this).addClass('button').removeClass('listbutton');
if (button.data.sel) {
button.data.sel = button.data.sel.replace('listbutton', 'button');
}
if (button.data.act) {
button.data.act = button.data.act.replace('listbutton', 'button');
}
rcmail.buttons[button.command][button.index] = button.data;
rcmail.init_button(button.command, button.data);
});
// buttons that should be hidden on small screen devices
$('[data-hidden]').each(function() {
var m, v = $(this).data('hidden'),
parent = $(this).parent('li'),
re = /(large|big|small|phone|lbs)/g;
while (m = re.exec(v)) {
$(parent.length ? parent : this).addClass('hidden-' + m[1]);
}
});
// Modify normal checkboxes on lists so they are different
// than those used for row selection, i.e. use icons
$('[data-list]').each(function() {
$('input[type=checkbox]', this).each(function() { pretty_checkbox(this); });
});
// Assign .formcontainer class to the iframe body, when it
// contains .formcontent and .formbuttons.
if (is_framed) {
$('.formcontent').each(function() {
if ($(this).next('.formbuttons').length) {
$(this).parent().addClass('formcontainer');
}
});
}
// move "Download all attachments" button into a better location
$('#attachment-list + a.zipdownload').appendTo('.header-links');
if (ios = $('html').is('.ipad,.iphone')) {
$('.iframe-wrapper, .scroller').addClass('ios-scroll');
}
if ($('html').filter('.ipad,.iphone,.webkit.mobile,.webkit.tablet').addClass('webkit-scroller').length) {
$(layout.menu).addClass('webkit-scroller');
}
// Set .notree class on treelist widget update
$('.treelist').each(function() {
var list = this, callback = function() {
$(list)[$('.treetoggle', list).length > 0 ? 'removeClass' : 'addClass']('notree');
};
if (window.MutationObserver) {
(new MutationObserver(callback)).observe(list, {childList: true, subtree: true});
}
callback();
// Add title with full folder name on hover
// TODO: This should be done in another way, so if an entry is
// added after page load it also works there.
$('li.mailbox > a').on('mouseover', function() { rcube_webmail.long_subject_title_ex(this); });
});
// Store default logo path if not already set
if (!$('#logo').data('src-default')) {
$('#logo').data('src-default', $('#logo').attr('src'));
}
};
/**
* Moves form buttons into the content frame actions toolbar (for mobile)
*/
function register_content_buttons(buttons)
{
// we need these buttons really only in phone mode
if (/*mode == 'phone' && */ env.frame_nav && buttons && buttons.length) {
var toolbar = env.frame_nav.children('.buttons');
content_buttons = [];
$.each(buttons, function() {
if (this.data('target')) {
content_buttons.push(this.data('target'));
}
});
toolbar.html('').append(buttons);
}
};
/**
* Registers cloned button
*/
function register_cloned_button(old_id, new_id, active_class)
{
var button = find_button(old_id);
if (button) {
rcmail.register_button(button.command, new_id, button.data.type, active_class, button.data.sel);
}
};
/**
* Create a button clone for use in toolbar
*/
function create_cloned_button(target, menu_button, add_class, always_active)
{
var popup, click = true,
button = $('<a>'),
target_id = target.attr('id') || new Date().getTime(),
button_id = target_id + '-clone',
btn_class = target[0].className + (add_class ? ' ' + add_class : '');
if (!menu_button) {
btn_class = $.trim(btn_class.replace('btn-primary', 'primary').replace(/(btn[a-z-]*|button|disabled)/g, ''))
btn_class += ' button' + (!always_active ? ' disabled' : '');
}
else if (popup = target.data('popup')) {
button.data({popup: popup, 'toggle-button': target.data('toggle-button')});
popup_init(button[0]);
click = false;
rcmail.register_menu_button(button[0], popup);
}
button.attr({id: button_id, href: '#', 'class': btn_class})
.append($('<span class="inner">').text(target.text()));
if (click) {
button.on('click', function(e) { target.click(); });
}
if (is_framed && !menu_button) {
button.data('target', target);
frame_buttons.push($.extend({button_id: button_id}, find_button(target[0].id)));
}
else {
// Register the button to get active state updates
register_cloned_button(target_id, button_id, btn_class.replace(' disabled', ''));
}
return button;
};
/**
* Finds an rcmail button
*/
function find_button(id)
{
var i, button, command;
for (command in rcmail.buttons) {
for (i = 0; i < rcmail.buttons[command].length; i++) {
button = rcmail.buttons[command][i];
if (button.id == id) {
return {
command: command,
index: i,
data: button
};
}
}
}
};
/**
* Setup environment
*/
function layout_init()
{
// Select current layout element
env.last_selected = $('#layout > div.selected')[0];
if (!env.last_selected && layout.content.length) {
$.each(['sidebar', 'list', 'content'], function() {
if (layout[this].length) {
env.last_selected = layout[this][0];
layout[this].addClass('selected');
return false;
}
});
}
// Register resize handler
$(window).on('resize', function() {
clearTimeout(env.resize_timeout);
env.resize_timeout = setTimeout(function() { resize(); }, 25);
});
// Enable rcmail.open_window intercepting
env.open_window = rcmail.open_window;
rcmail.open_window = window_open;
rcmail
.addEventListener('message', message_displayed)
.addEventListener('menu-open', menu_toggle)
.addEventListener('menu-close', menu_toggle)
.addEventListener('editor-init', tinymce_init)
.addEventListener('autocomplete_create', rcmail_popup_init)
.addEventListener('googiespell_create', rcmail_popup_init)
.addEventListener('setquota', update_quota)
.addEventListener('enable-command', enable_command_handler)
.addEventListener('init', init);
// Add styling for TinyMCE editor popups
// We need to use MutationObserver, as TinyMCE does not provide any events for this
if (window.MutationObserver && window.tinymce) {
var callback = function(list) {
$.each(list, function() {
$.each(this.addedNodes, function() {
tinymce_style(this);
});
});
};
(new MutationObserver(callback)).observe(document.body, {childList: true});
}
};
/**
* rcmail 'init' event handler
*/
function init()
{
// Additional functionality on list widgets
$('table[data-list]').each(function() {
var button,
table = $(this),
list = table.data('list');
if (rcmail[list] && rcmail[list].multiselect) {
var repl, button,
parent = table.parents('.sidebar,.list,.content').last(),
header = parent.find('.header'),
toolbar = header.find('ul');
if (!toolbar.length) {
toolbar = header;
}
else if (button = toolbar.find('a.button.select').data('toggle-button')) {
button = $('#' + button);
}
// Enable checkbox selection on list widgets
rcmail[list].enable_checkbox_selection();
// Add Select button to the list navigation bar
if (!button) {
button = $('<a>').attr({'class': 'button select disabled', role: 'button', title: rcmail.gettext('select')})
.on('click', function() { if ($(this).is('.active')) table.toggleClass('withselection'); })
.append($('<span class="inner">').text(rcmail.gettext('select')));
if (toolbar.is('.toolbar') || toolbar.is('.toolbarmenu')) {
button.prependTo(toolbar).wrap('<li role="menuitem">');
// Add a button to the content toolbar menu too
if (layout.content) {
var button2 = create_cloned_button(button, true, 'hidden-big hidden-large');
$('<li role="menuitem">').append(button2).appendTo('#toolbar-menu');
button = button.add(button2);
}
}
else {
if (repl = table.data('list-select-replace')) {
$(repl).replaceWith(button);
}
else {
button.appendTo(toolbar).addClass('icon');
if (!parent.is('.sidebar')) {
button.addClass('toolbar-button');
}
}
}
}
// Update Select button state on list update
rcmail.addEventListener('listupdate', function(prop) {
if (prop.list && prop.list == rcmail[list]) {
if (prop.rowcount) {
button.addClass('active').removeClass('disabled').attr('tabindex', 0);
}
else {
button.removeClass('active').addClass('disabled').attr('tabindex', -1);
}
}
});
}
// https://github.com/roundcube/elastic/issues/45
// Draggable blocks scrolling on touch devices, we'll disable it there
if (touch && rcmail[list]) {
if (typeof rcmail[list].draggable == 'function') {
rcmail[list].draggable('destroy');
}
else if (typeof rcmail[list].draggable == 'boolean') {
rcmail[list].draggable = false;
}
}
});
// Display "List is empty..." on the list
if (window.MutationObserver) {
$('[data-label-msg]').filter('ul,table').each(function() {
var fn, observer, callback,
info = $('<div class="listing-info hidden">').insertAfter(this),
table = $(this),
fn = function() {
var ext, command,
msg = table.data('label-msg'),
list = table.is('ul') ? table : table.children('tbody');
if (!rcmail.env.search_request && !rcmail.env.qsearch
&& msg && !list.children(':visible').length
) {
ext = table.data('label-ext');
command = table.data('create-command');
if (ext && (!command || rcmail.commands[command])) {
msg += ' ' + ext;
}
info.text(msg).removeClass('hidden');
return;
}
info.addClass('hidden');
};
callback = function() {
// wait until the UI stops loading and the list is visible
if (rcmail.busy || !table.is(':visible')) {
return setTimeout(callback, 250);
}
clearTimeout(env.list_timer);
env.list_timer = setTimeout(fn, 50);
};
// show/hide the message when something changes on the list
observer = new MutationObserver(callback);
observer.observe(table[0], {childList: true, subtree: true, attributes: true, attributeFilter: ['style']});
// initialize the message
callback();
});
}
// Create floating action button(s)
if ((layout.list.length || layout.content.length) && is_mobile()) {
var fabuttons = [];
$('[data-fab]').each(function() {
var button = $(this),
task = button.data('fab-task') || '*',
action = button.data('fab-action') || '*';
if ((task == '*' || task == rcmail.task)
&& (action == '*' || action == rcmail.env.action || (action == 'none' && !rcmail.env.action))
) {
fabuttons.push(create_cloned_button(button, false, false, true));
}
});
if (fabuttons.length) {
$('<div class="floating-action-buttons">').append(fabuttons)
.appendTo(layout.list.length ? layout.list : layout.content);
}
}
// Add menu link for each attachment
if (rcmail.env.action != 'print') {
$('#attachment-list > li').each(function() {
attachmentmenu_append(this);
});
}
var phone_confirmation = function(label) {
if (mode == 'phone') {
rcmail.display_message(rcmail.gettext(label), 'confirmation');
}
};
rcmail.addEventListener('fileappended', function(e) {
if (e.attachment.complete) {
attachmentmenu_append(e.item);
if (e.attachment.mimetype == 'text/vcard' && rcmail.commands['attach-vcard']) {
phone_confirmation('vcard_attachments.vcardattached');
}
}
})
.addEventListener('managesieve.insertrow', function(o) { bootstrap_style(o.obj); })
.addEventListener('add-recipient', function() { phone_confirmation('recipientsadded'); });
rcmail.init_pagejumper('.pagenav > input');
if (rcmail.task == 'mail') {
if (rcmail.env.action == 'compose') {
// In compose window we do not provide "Back' button, instead
// we modify the Mail button in the task menu to act like it (i.e. calls 'list' command)
if (!rcmail.env.extwin) {
$('a.button.mail', layout.menu).attr('onclick', "return rcmail.command('list','',this,event)");
}
rcmail.addEventListener('compose-encrypted', function(e) {
$("a.mode-html, button.attach").prop('disabled', e.active);
$('a.button.attach, a.button.responses')[e.active ? 'addClass' : 'removeClass']('disabled');
});
$('.sidebar > .footer:not(.pagenav) > a.button').click(function() {
if ($(this).is('.disabled')) {
rcmail.display_message(rcmail.gettext('nocontactselected'), 'warning');
}
});
// Update compose status bar on attachments list update
if (window.MutationObserver) {
var observer, list = $('#attachment-list'),
status_callback = function() { compose_status('attach', list.children().length > 0); };
observer = new MutationObserver(status_callback);
observer.observe(list[0], {childList: true});
status_callback();
}
}
// Append contact menu to all mailto: links
if (rcmail.env.action == 'preview' || rcmail.env.action == 'show') {
$('a').filter('[href^="mailto:"]').each(function() {
mailtomenu_append(this);
});
}
}
else if (rcmail.task == 'settings') {
rcmail.addEventListener('identity-encryption-show', function(p) {
bootstrap_style(p.container);
});
rcmail.addEventListener('identity-encryption-update', function(p) {
bootstrap_style(p.container);
});
}
rcmail.set_env({
thread_padding: '1.5rem',
// increase popup windows, so they do not switch to tablet mode
popup_width_small: 1025,
popup_width: 1200
});
// Update layout after initialization (again)
// In devel mode we have to wait until all styles are applied by less
if (rcmail.env.devel_mode && window.less) {
less.pageLoadFinished.then(function() {
resize();
});
}
else {
resize();
}
// Add date format placeholder to datepicker inputs
var func, format = rcmail.env.date_format_localized;
if (format) {
func = function(input) {
$(input).filter('.datepicker').attr('placeholder', format);
// also make selects pretty
$(input).parent().find('select').each(function() { pretty_select(this); });
};
$('input.datepicker').each(function() { func(this); });
rcmail.addEventListener('insert-edit-field', func);
}
};
/**
* Apply bootstrap classes to html elements
*/
function bootstrap_style(context)
{
if (!context) {
context = document;
}
// Buttons
$('input.button,button', context).not('.btn').addClass('btn').not('.btn-primary,.primary,.mainaction').addClass('btn-secondary');
$('input.button.mainaction,button.primary,button.mainaction', context).addClass('btn-primary');
$('button.btn.delete,button.btn.discard', context).addClass('btn-danger');
$.each(['warning', 'error', 'information', 'confirmation'], function() {
var type = this;
$('.box' + type + ':not(.ui.alert)', context).each(function() {
alert_style(this, type, true);
});
});
// Convert structure of single dialogs (one input or just an image),
// e.g. group create, attachment rename where we use <label>Label<input></label>
if (context != document && $('.popup', context).children().length == 1) {
var content = $('.popup', context).children().first();
if (content.is('img')) {
$('.popup', context).addClass('justified');
}
else if (content.is('label')) {
var input = content.find('input').detach(),
label = content.detach(),
id = input.attr('id');
if (!id) {
input.attr('id', id = 'dialog-input-elastic');
}
$('.popup', context).addClass('formcontent').append(
$('<div class="form-group row">')
.append(label.attr('for', id).addClass('col-sm-2 col-form-label'))
.append($('<div class="col-sm-10">').append(input))
);
input.focus();
}
}
// Forms
var supported_controls = 'input:not(.button,.no-bs,[type=button],[type=file],[type=radio],[type=checkbox]),textarea';
$(supported_controls, $('.propform', context)).addClass('form-control');
$('[type=checkbox]', $('.propform', context)).addClass('form-check-input');
// Note: On selects we add form-control to get consistent focus
// and to not have to create separate rules for selects and inputs
$('select', context).addClass('form-control custom-select');
if (context != document) {
$(supported_controls, context).addClass('form-control');
}
$('table.propform', context).each(function() {
var text_rows = 0, form_rows = 0;
$(this).find('> tbody > tr').each(function() {
var first, last, row = $(this),
row_classes = ['form-group', 'row'],
cells = row.children('td');
if (cells.length == 2) {
first = cells.first();
last = cells.last();
$('label', first).addClass('col-form-label');
first.addClass('col-sm-4');
last.addClass('col-sm-8');
if (last.find('[type=checkbox]').length == 1 && !last.find('.proplist').length) {
row_classes.push('form-check');
if (last.find('a').length) {
row_classes.push('with-link');
}
form_rows++;
}
else if (!last.find('input:not([type=hidden]),textarea,radio,select').length) {
last.addClass('form-control-plaintext');
text_rows++;
}
else {
form_rows++;
}
// style some multi-input fields
if (last.children('.datepicker') && last.children('input').length == 2) {
last.addClass('datetime');
}
}
else if (cells.length == 1) {
cells.css('width', '100%');
}
row.addClass(row_classes.join(' '));
});
if (text_rows > form_rows) {
$(this).addClass('text-only');
}
});
// Special input + anything entry
$('td.input-group', context).each(function() {
$(this).children(':not(:first)').addClass('input-group-append');
});
// Other forms, e.g. Contact advanced search
$('fieldset.propform:not(.groupped) div.row', context).each(function() {
var has_input = $('input:not([type=hidden]),select,textarea', this).length > 0;
if (has_input) {
$(supported_controls, this).addClass('form-control');
}
$(this).children().last().addClass('col-sm-8' + (!has_input ? ' form-control-plaintext' : ''));
$(this).children().first().addClass('col-sm-4 col-form-label');
$(this).addClass('form-group');
});
// Contact info/edit form
$('fieldset.propform.groupped fieldset', context).each(function() {
$('.row', this).each(function() {
var label, first,
has_input = $('input,select,textarea', this).length > 0,
items = $(this).children();
if (has_input) {
$(supported_controls, this).addClass('form-control');
}
if (items.length < 2) {
return;
}
first = items.first();
if (first.is('select')) {
first.addClass('input-group-prepend');
}
else {
first.wrap('<span class="input-group-prepend">').addClass('input-group-text');
}
if (!has_input) {
items.last().addClass('form-control-plaintext');
}
$('.content', this).addClass('input-group-prepend input-group-append input-group-text');
$('a.deletebutton', this).addClass('input-group-text icon delete').wrap('<span class="input-group-append">');
$(this).addClass('input-group');
});
});
// Other forms, e.g. Insert response
$('.propform > .prop.block:not(.row)', context).each(function() {
$(this).addClass('form-group row').each(function() {
$('label', this).addClass('col-form-label').wrap($('<div class="col-sm-4">'));
$('input,select,textarea', this).wrap($('<div class="col-sm-8">'));
$(supported_controls, this).addClass('form-control');
});
});
$('td.rowbuttons > a', context).addClass('btn');
// Testing Bootstrap Tabs on contact info/edit page
// Tabs do not scale nicely on very small screen, so can be used
// only with small number of tabs with short text labels
$('form.tabbed,div.tabbed', context).each(function(idx, item) {
var tabs = [], nav = $('<ul>').attr({'class': 'nav nav-tabs', role: 'tablist'});
$(this).addClass('tab-content').children('fieldset').each(function(i, fieldset) {
var tab, id = fieldset.id || ('tab' + idx + '-' + i),
tab_class = $(fieldset).data('navlink-class');
$(fieldset).addClass('tab-pane').attr({id: id, role: 'tabpanel'});
tab = $('<li>').addClass('nav-item').append(
$('<a>').addClass('nav-link' + (tab_class ? ' ' + tab_class : ''))
.attr({role: 'tab', 'href': '#' + id})
.text($('legend:first', fieldset).text())
.click(function(e) {
$(this).tab('show');
// Because we return false we have to close popups
popups_close(e);
// Returning false here prevents from strange scrolling issue
// when the form is in an iframe, e.g. contact edit form
return false;
})
);
$('legend:first', fieldset).hide();
tabs.push(tab);
});
// create the navigation bar
nav.append(tabs).insertBefore(item);
// activate the first tab
$('a.nav-link:first', nav).click();
});
// Make tables pretier
$('table:not(.table,.propform,.listing,.ui-datepicker-calendar)', context)
.filter(function() {
// exclude direct propform children and external content
return !$(this).parent().is('.propform')
&& !$(this).parents('.message-htmlpart,.message-partheaders,.boxinformation,.raw-tables').length;
})
.each(function() {
// TODO: Consider implementing automatic setting of table-responsive on window resize
var table = $(this).addClass('table');
table.parent().addClass('table-responsive-sm');
table.find('thead').addClass('thead-default');
});
// The same for some other checkboxes
// We do this here, not in setup() because we want to cover dialogs
- $('input.pretty-checkbox, .propform input[type=checkbox], .form-check > input, .popupmenu.form input[type=checkbox], .toolbarmenu input[type=checkbox]', context)
+ $('input.pretty-checkbox, .propform input[type=checkbox], .form-check input, .popupmenu.form input[type=checkbox], .toolbarmenu input[type=checkbox]', context)
.each(function() { pretty_checkbox(this); });
// Also when we add action-row of the form, e.g. Managesieve plugin adds them after the page is ready
if ($(context).is('.actionrow')) {
$('input[type=checkbox]', context).each(function() { pretty_checkbox(this); });
}
// Input-group combo is an element with a select field on the left
// and input(s) on right, and where the whole right side can be hidden
// depending on the select position. This code fixes border radius on select
$('.input-group-combo > select:first', context).on('change', function() {
var select = $(this),
fn = function() {
select[select.next().is(':visible') ? 'removeClass' : 'addClass']('alone');
};
setTimeout(fn, 50);
setTimeout(fn, 2000); // for devel mode
}).trigger('change');
// Make message-objects alerts pretty (the same as UI alerts)
$('#message-objects', context).children(':not(.ui.alert)').add('.part-notice').each(function() {
// message objects with notice class are really warnings
var cl = $(this).removeClass('notice part-notice').attr('class').split(/\s/)[0] || 'warning';
alert_style(this, cl);
$(this).addClass('box' + cl);
$('a', this).addClass('btn btn-primary btn-sm');
});
// Form validation errors (managesieve plugin)
$('.error', context).addClass('is-invalid');
// Make logon form prettier
if (rcmail.env.task == 'login' && context == document) {
$('#rcmloginsubmit').addClass('btn-lg text-uppercase w-100');
$('#login-form table tr').each(function() {
var input = $('input,select', this),
label = $('label', this),
icon_name = input.data('icon'),
icon = $('<i>').attr('class', 'input-group-text icon ' + input.attr('name').replace('_', ''));
if (icon_name) {
icon.addClass(icon_name);
}
$(this).addClass('form-group row');
label.parent().css('display', 'none');
input.addClass(input.is('select') ? 'custom-select' : 'form-control')
.attr('placeholder', label.text())
.before($('<span class="input-group-prepend">').append(icon))
.parent().addClass('input-group input-group-lg');
});
}
$('select:not([multiple])', context).each(function() { pretty_select(this); });
};
/**
* Detects if the element is TinyMCE dialog/menu
* and adds Elastic styling to it
*/
function tinymce_style(elem)
{
// TinyMCE dialog widnows
if ($(elem).is('.mce-window')) {
var body = $(elem).find('.mce-window-body'),
foot = $(elem).find('.mce-foot > .mce-container-body');
// Apply basic forms style
if (body.length) {
bootstrap_style(body[0]);
}
body.find('button').filter(function() { return $(this).parent('.mce-btn').length > 0; }).removeClass('btn btn-secondary');
// Fix icons in Find and Replace dialog footer
if (foot.children('.mce-widget').length === 5) {
foot.addClass('mce-search-foot');
}
// Apply some form structure fixes and helper classes
$(elem).find('.mce-charmap').parent().parent().addClass('mce-charmap-dialog');
$(elem).find('.mce-combobox').each(function() {
if (!$(this).children('.mce-btn').length) {
$(this).addClass('mce-combobox-fake');
}
});
$(elem).find('.mce-form > .mce-container-body').each(function() {
if ($(this).children('.mce-formitem').length > 4) {
$(this).addClass('mce-form-split');
}
});
$(elem).find('.mce-form').next(':not(.mce-formitem)').addClass('mce-form');
// Fix dialog height (e.g. Table properties dialog)
if (!is_mobile()) {
var offset, max_height = 0, height = body.height();
$(elem).find('.mce-form').each(function() {
max_height = Math.max(max_height, $(this).height());
});
if (height < max_height) {
max_height += (body.find('.mce-tabs').height() || 0) + 25;
body.height(max_height);
$(elem).height($(elem).height() + (max_height - height));
$(elem).css('top', ($(window).height() - $(elem).height())/2 + 'px');
}
}
}
// TinyMCE menus on mobile
else if ($(elem).is('.mce-menu')) {
$(elem).prepend(
$('<h3 class="popover-header">').append(
$('<a class="button icon "' + 'cancel' + '">')
.text(rcmail.gettext('close'))
.on('click', function() { $(document.body).click(); })));
if (window.MutationObserver) {
var callback = function() {
if (mode != 'phone') {
return;
}
if (!$('.mce-menu:visible').length) {
$('div.mce-overlay').click();
}
else if (!$('div.mce-overlay').length) {
$('<div>').attr('class', 'popover-overlay mce-overlay')
.appendTo('body')
.click(function() { $(this).remove(); });
}
};
(new MutationObserver(callback)).observe(elem, {attributes: true});
}
}
};
/**
* Initializes popup menus
*/
function dropdowns_init()
{
$('[data-popup]').each(function() { popup_init(this); });
$(document).on('click', popups_close);
rcube_webmail.set_iframe_events({mousedown: popups_close, touchstart: popups_close});
};
/**
* Init content frame
*/
function content_frame_init()
{
var last_selected = env.last_selected,
title_reset = function(title) {
if (typeof title !== 'string' || !title.length) {
title = $('h1.voice').text() || $('title').text() || '';
}
$('.header > .header-title', layout.content).text(title);
};
// display or reset the content frame
var common_content_handler = function(e, href, show, title)
{
if (is_mobile()) {
content_frame_navigation(href, e);
}
if (show && !layout.content.is(':visible')) {
env.last_selected = layout.content[0];
screen_resize();
if (title) {
title_reset(title);
}
}
else if (!show) {
if (env.last_selected != last_selected && !env.content_lock) {
env.last_selected = last_selected;
screen_resize();
}
title_reset();
}
env.content_lock = false;
};
var common_list_handler = function(e) {
if (mode != 'large' && !env.content_lock && e.force) {
show_list();
}
env.content_lock = false;
// display current folder name in list header
if (e.title) {
$('.header > .header-title', layout.list).text(e.title);
}
};
var list_handler = function(e) {
var args = {};
if (rcmail.env.task == 'addressbook' || rcmail.env.task == 'mail') {
args.force = true;
}
// display current folder name in list header
if (rcmail.env.task == 'mail' && !rcmail.env.action) {
var name = $.type(e) == 'string' ? e : rcmail.env.mailbox,
folder = rcmail.env.mailboxes[name];
args.title = folder ? folder.name : '';
}
common_list_handler(args);
};
// when loading content-frame in small-screen mode display it
layout.content.find('iframe').on('load', function(e) {
var href = '', show = true;
// Reset the scroll position of the iframe-wrapper
$(this).parent('.iframe-wrapper').scrollTop(0);
try {
href = e.target.contentWindow.location.href;
show = !href.endsWith(rcmail.env.blankpage);
// Reset title back to the default
$(e.target.contentWindow).on('unload', title_reset);
}
catch(e) { /* ignore */ }
common_content_handler(e, href, show);
});
rcmail
.addEventListener('afterlist', list_handler)
.addEventListener('afterlistgroup', list_handler)
.addEventListener('afterlistsearch', list_handler)
// plugins
.addEventListener('show-list', function(e) {
e.force = true;
common_list_handler(e);
})
.addEventListener('show-content', function(e) {
if (!$(e.obj).is('iframe')) {
$(e.scrollElement || e.obj).scrollTop(0);
if (is_mobile()) {
iframe_loader(e.obj);
}
}
common_content_handler(e.event || new Event, '_action=' + (e.mode || 'edit'), true, e.title);
});
};
/**
* Content frame navigation
*/
function content_frame_navigation(href, event)
{
// Don't display navigation for create/add action frames
if (href.match(/_action=(create|add)/) || href.match(/_nav=hide/)) {
$(env.frame_nav).addClass('hide-nav-buttons');
return;
}
var node, uid, list, _list = $('[data-list]', layout.list).data('list');
if (!_list || !(list = rcmail[_list])) {
// hide navbar if there are no visible buttons, e.g. Help plugin UI
if ($(env.frame_nav).is('.hide-nav-buttons') && !$('.buttons', env.frame_nav).children().length) {
$(env.frame_nav).addClass('hidden');
}
return;
}
$(env.frame_nav).removeClass('hide-nav-buttons hidden');
// expand collapsed row so we do not skip the whole thread
// TODO: Unified interface for list and treelist widgets
if (uid = list.get_single_selection()) {
if (list.rows && list.rows[uid] && !list.rows[uid].expanded) {
list.expand_row(event, uid);
}
else if (list.get_node && (node = list.get_node(uid)) && node.collapsed) {
list.expand(uid);
}
}
var prev, next,
frame = $('#' + rcmail.env.contentframe),
next_button = $('a.button.next', env.frame_nav).off('click').addClass('disabled'),
prev_button = $('a.button.prev', env.frame_nav).off('click').addClass('disabled');
if ((next = list.get_next()) || rcmail.env.current_page < rcmail.env.pagecount) {
next_button.removeClass('disabled').on('click', function() {
env.content_lock = true;
iframe_loader(frame);
if (next) {
list.select(next);
}
else {
rcmail.env.list_uid = 'FIRST';
rcmail.command('nextpage');
}
});
}
if (((prev = list.get_prev()) && (prev != '*' || _list != 'subscription_list')) || rcmail.env.current_page > 1) {
prev_button.removeClass('disabled').on('click', function() {
env.content_lock = true;
iframe_loader(frame);
if (prev) {
list.select(prev);
}
else {
rcmail.env.list_uid = 'LAST';
rcmail.command('previouspage');
}
});
}
};
/**
* Handler for editor-init event
*/
function tinymce_init(o)
{
// Enable autoresize plugin
o.config.plugins += ' autoresize';
if (is_touch()) {
// Make the toolbar icons bigger
o.config.toolbar_items_size = null;
// Use minimalistic toolbar
o.config.toolbar = 'undo redo | insert | styleselect';
if (o.config.plugins.match(/emoticons/)) {
o.config.toolbar += ' emoticons';
}
}
if (rcmail.task == 'mail' && rcmail.env.action == 'compose') {
var form = $('#compose-content > form'),
keypress = function(e) {
if (e.key == 'Tab' && e.shiftKey) {
$('#compose-content > form').scrollTop(0);
}
};
// Shift+Tab on mail compose editor scrolls the page to the top
o.config.setup_callback = function(ed) {
ed.on('keypress', keypress);
};
$('#composebody').on('keypress', keypress);
// Keep the editor toolbar on top of the screen on scroll
form.on('scroll', function() {
var container = $('.mce-container-body', form),
toolbar = $('.mce-top-part', container),
editor_offset = container.offset(),
header_top = form.offset().top;
if (editor_offset && (editor_offset.top - header_top < 0)) {
toolbar.css({position: 'fixed', top: header_top + 'px', width: container.width() + 'px'});
}
else {
toolbar.css({position: 'relative', top: 0, width: 'auto'})
}
});
$(window).resize(function() { form.trigger('scroll'); });
}
};
function datepicker_init(datepicker)
{
// Datepicker widget improvements: overlay element, styling updates on calendar element update
// The widget does not provide any event system, so we use MutationObserver
if (window.MutationObserver) {
$(datepicker).not('[data-observed]').each(function() {
var overlay, hidden = true,
win = is_framed ? parent : window,
callback = function(data) {
$.each(data, function(i, v) {
// add/remove overlay on widget show/hide
if (v.type == 'attributes') {
var is_hidden = $(v.target).attr('aria-hidden') == 'true';
if (is_hidden != hidden) {
if (!is_hidden) {
overlay = $('<div>').attr('class', 'ui-widget-overlay datepicker')
.appendTo(win.document.body)
.click(function(e) {
$(this).remove();
if (is_framed) {
$.datepicker._hideDatepicker();
}
});
}
else if (overlay) {
overlay.remove();
}
hidden = is_hidden;
}
}
else if (v.addedNodes.length) {
// apply styles when widget content changed
win.UI.bootstrap_style(v.target);
// Month/Year change handlers do not work from parent, fix it
if (is_framed) {
win.$('select.ui-datepicker-month', v.target).on('change', function() {
$.datepicker._selectMonthYear($.datepicker._lastInput, this, "M");
});
win.$('select.ui-datepicker-year', v.target).on('change', function() {
$.datepicker._selectMonthYear($.datepicker._lastInput, this, "Y");
});
}
}
});
};
$(this).attr('data-observed', '1');
if (is_framed) {
// move the datepicker to parent window
$(this).detach().appendTo(parent.document.body);
// create fake element, so the valid one is not removed by datepicker code
$('<div id="ui-datepicker-div" class="hidden">').appendTo(document.body);
}
(new MutationObserver(callback)).observe(this, {childList: true, subtree: false, attributes: true, attributeFilter: ['aria-hidden']});
});
}
};
/**
* Handler for some Roundcube core popups
*/
function rcmail_popup_init(o)
{
// Add some common styling to the autocomplete/googiespell popups
$('table,ul', o.obj).addClass('toolbarmenu listing iconized');
$(o.obj).addClass('popupmenu popover');
bootstrap_style(o.obj);
// for googiespell list
$('input', o.obj).addClass('form-control');
// Modify the googiespell menu on mobile
if (is_mobile() && $(o.obj).is('.googie_window')) {
// Set popup Close title
var title = rcmail.gettext('close'),
class_name = 'button icon cancel',
close_link = $('<a>').attr('class', class_name).text(title)
.click(function(e) {
e.stopPropagation();
$('.popover-overlay').remove();
$(o.obj).hide();
});
$('<h3 class="popover-header">').append(close_link).prependTo(o.obj);
// add overlay element for phone layout
if (!$('.popover-overlay').length) {
$('<div>').attr('class', 'popover-overlay')
.appendTo('body')
.click(function() { $(this).remove(); });
}
$('table,button', o.obj).click(function(e) {
if (!$(e.target).is('input')) {
$('.popover-overlay').remove();
}
});
}
};
/**
* Handler for 'enable-command' event
*/
function enable_command_handler(args)
{
if (is_framed) {
$.each(frame_buttons, function(i, button) {
if (args.command == button.command) {
parent.$('#' + button.button_id)[args.status ? 'removeClass' : 'addClass']('disabled');
}
});
}
if (rcmail.task == 'mail') {
switch (args.command) {
case 'reply-list':
if (rcmail.env.reply_all_mode == 1) {
var label = rcmail.gettext(args.status ? 'replylist' : 'replyall');
$('a.button.reply-all').attr('title', label).find('.inner').text(label);
}
break;
case 'compose-encrypted':
// show the toolbar button for Mailvelope
if (args.status) {
$('a.button.encrypt:not(.icon)').parent().show();
}
break;
case 'compose-encrypted-signed':
// enable selector for encrypt and sign
$('#encryption-menu-button').show();
break;
}
}
};
/**
* screen mode
*/
function screen_mode()
{
var size, width = $(window).width();
if (width <= 480)
size = 'phone';
else if (width > 1200)
size = 'large';
else if (width > 768)
size = 'normal';
else
size = 'small';
touch = width <= 1024;
mode = size;
};
/**
* Window resize handler
* Does layout reflows e.g. on screen orientation change
*/
function resize()
{
var mobile;
screen_mode();
screen_resize();
screen_resize_html();
// disable ext-windows and other features
if (mobile = is_mobile()) {
rcmail.set_env(env.small_screen_config);
rcmail.enable_command('extwin', false);
}
else {
rcmail.set_env(env.config);
rcmail.enable_command('extwin', true);
}
// Hide content frame buttons on small devices (with frame toolbar in parent window)
$.each(content_buttons, function() { $(this)[mobile ? 'hide' : 'show'](); });
};
function screen_resize()
{
if (is_framed && !layout.sidebar.length && !layout.list.length) {
return;
}
switch (mode) {
case 'phone': screen_resize_phone(); break;
case 'small': screen_resize_small(); break;
case 'normal': screen_resize_normal(); break;
case 'large': screen_resize_large(); break;
}
screen_resize_logo(mode);
screen_resize_headers();
// On iOS and Android the content frame height is never correct, fix it
if (bw.webkit) {
$('.iframe-wrapper').each(function() {
var h = $(this).height();
if (h) {
$(this).children('iframe').height(h);
}
});
}
};
/**
* Assigns layout-* and touch-mode class to the 'html' element
*
* If we're inside an iframe that is small we have to
* check if the parent window is also small (mobile).
* We use that e.g. to still display desktop-like popovers in dialogs
*/
function screen_resize_html()
{
var meta = layout_metadata(),
html = $(document.documentElement);
if (html[0].className.match(/layout-([a-z]+)/)) {
if (RegExp.$1 != meta.mode) {
html.removeClass('layout-' + RegExp.$1)
.addClass('layout-' + meta.mode);
}
}
else {
html.addClass('layout-' + meta.mode);
}
if (meta.touch && !html.is('.touch')) {
html.addClass('touch');
}
else if (!meta.touch && html.is('.touch')) {
html.removeClass('touch');
}
};
function screen_resize_logo(mode)
{
if (mode == 'phone' && $('#logo').data('src-small')) {
$('#logo').attr('src', $('#logo').data('src-small'));
}
else {
$('#logo').attr('src', $('#logo').data('src-default'));
}
}
/**
* Sets left and right margin to the header title element to make it
* properly centered depending on the number of buttons on both sides
*/
function screen_resize_headers()
{
$('#layout > div > .header').each(function() {
var title, right = 0, left = 0, padding = 0,
sizes = {left: 0, right: 0};
$(this).children(':visible').each(function() {
if (!title && $(this).is('.header-title')) {
title = $(this);
return;
}
sizes[title ? 'right' : 'left'] += this.offsetWidth;
});
if (padding + sizes.right >= sizes.left) {
right = 0;
left = sizes.right + padding - sizes.left;
}
else {
left = 0;
right = sizes.left - (padding + sizes.right);
}
$(title).css({
'margin-right': right + 'px',
'margin-left': left + 'px',
'padding-right': padding + 'px'
});
});
};
function screen_resize_phone()
{
screen_resize_small_all();
app_menu(false);
};
function screen_resize_small()
{
screen_resize_small_all();
app_menu(true);
};
function screen_resize_normal()
{
var show;
if (layout.list.length) {
show = layout.list.is(env.last_selected) || (!layout.sidebar.is(env.last_selected) && !layout.sidebar.is('.layout-sticky'));
layout.list[show ? 'removeClass' : 'addClass']('hidden');
}
if (layout.sidebar.length) {
show = !layout.list.length || layout.sidebar.is(env.last_selected) || layout.sidebar.is('.layout-sticky');
layout.sidebar[show ? 'removeClass' : 'addClass']('hidden');
}
layout.content.removeClass('hidden');
app_menu(true);
screen_resize_small_none();
if (layout.list) {
$('.header > ul.toolbar', layout.list).addClass('popupmenu toolbarmenu').removeClass('toolbar');
}
};
function screen_resize_large()
{
$.each(layout, function(name, item) { item.removeClass('hidden'); });
screen_resize_small_none();
if (layout.list) {
$('.header > ul.toolbarmenu.popupmenu', layout.list).removeClass('popupmenu toolbarmenu').addClass('toolbar');
}
};
function screen_resize_small_all()
{
var show, got_content = false;
if (layout.content.length) {
show = got_content = layout.content.is(env.last_selected);
layout.content[show ? 'removeClass' : 'addClass']('hidden');
$('.header > ul.toolbar', layout.content).addClass('popupmenu');
}
if (layout.list.length) {
show = !got_content && layout.list.is(env.last_selected);
layout.list[show ? 'removeClass' : 'addClass']('hidden');
$('.header > ul.toolbar', layout.list).addClass('popupmenu');
}
if (layout.sidebar.length) {
show = !got_content && (layout.sidebar.is(env.last_selected) || !layout.list.length);
layout.sidebar[show ? 'removeClass' : 'addClass']('hidden');
}
if (got_content) {
buttons.back_list.show();
}
};
function screen_resize_small_none()
{
buttons.back_list.filter(function() { return $(this).parents('.sidebar').length == 0; }).hide();
$('ul.toolbar.popupmenu').removeClass('popupmenu');
};
function show_content(unsticky)
{
// show sidebar and hide list
layout.list.addClass('hidden');
layout.sidebar.addClass('hidden');
layout.content.removeClass('hidden');
if (unsticky) {
layout.sidebar.removeClass('layout-sticky');
}
screen_resize_headers();
env.last_selected = layout.content[0];
};
function show_sidebar(sticky)
{
// show sidebar and hide list
layout.list.addClass('hidden');
layout.sidebar.removeClass('hidden');
if (sticky) {
layout.sidebar.addClass('layout-sticky');
}
if (mode == 'small' || mode == 'phone') {
layout.content.addClass('hidden');
}
screen_resize_headers();
env.last_selected = layout.sidebar[0];
};
function show_list(scroll)
{
if (!layout.list.length && !layout.sidebar.length) {
history.back();
}
else {
// show list and hide sidebar and content
layout.sidebar.addClass('hidden').removeClass('layout-sticky');
layout.list.removeClass('hidden');
if (mode == 'small' || mode == 'phone') {
hide_content();
}
if (scroll) {
layout.list.children('.scroller').scrollTop(0);
}
env.last_selected = layout.list[0];
}
screen_resize_headers();
};
function hide_content()
{
// show sidebar or list, hide content frame
env.last_selected = layout.list[0] || layout.sidebar[0];
screen_resize();
// reset content frame, so we can load it again
rcmail.show_contentframe(false);
// now we have to unselect selected row on the list
$('[data-list]', layout.list).each(function() {
var list = $(this).data('list');
if (rcmail[list]) {
if (rcmail[list].clear_selection) {
rcmail[list].clear_selection(); // list widget
}
else if (rcmail[list].select) {
rcmail[list].select(); // treelist widget
}
}
});
};
// show menu widget
function app_menu(show)
{
if (show) {
if (mode == 'phone') {
$('<div id="menu-overlay" class="popover-overlay">')
.on('click', function() { app_menu(false); })
.appendTo('body');
if (!env.menu_initialized) {
env.menu_initialized = true;
$('a', layout.menu).on('click', function(e) { if (mode == 'phone') app_menu(); });
}
layout.menu.addClass('popover');
}
layout.menu.removeClass('hidden');
}
else {
$('#menu-overlay').remove();
layout.menu.addClass('hidden').removeClass('popover');
}
};
/**
* Triggered when a UI message is displayed
*/
function message_displayed(p)
{
if (p.type == 'loading' && $('.iframe-loader:visible').length) {
// hide original message object, we don't need two "loaders"
rcmail.hide_message(p.object);
return;
}
alert_style(p.object, p.type, true);
$(p.object).attr('role', 'alert');
};
/**
* Applies some styling and icon to an alert object
*/
function alert_style(object, type, wrap)
{
var tmp, classes = 'ui alert',
addicon = !$(object).is('.noicon'),
map = {
information: 'alert-info',
notice: 'alert-info',
confirmation: 'alert-success',
warning: 'alert-warning',
error: 'alert-danger',
loading: 'alert-info loading',
uploading: 'alert-info loading',
vcardattachment: 'alert-info' // vcard_attachments plugin
};
// we need the content to be non-text node for best alignment
if (wrap && addicon && !$(object).is('.aligned-buttons')) {
$(object).html($('<span>').html($(object).html()));
}
// Type can be e.g. 'notice chat'
type = type.split(' ')[0];
if (tmp = map[type]) {
classes += ' ' + tmp;
if (addicon) {
$('<i>').attr('class', 'icon').prependTo(object);
}
}
$(object).addClass(classes);
};
/**
* Set UI dialogs size/style depending on screen size
*/
function dialog_open(dialog)
{
var me = $(dialog.uiDialog),
width = me.width(),
height = me.height(),
maxWidth = $(window).width(),
maxHeight = $(window).height();
if (maxWidth <= 480) {
me.css({width: '100%', height: '100%'});
}
else {
if (height > maxHeight) {
me.css('height', '100%');
}
if (width > maxWidth) {
me.css('width', '100%');
}
}
// Close all popovers
$(document).click();
// Display loader when the dialog has an iframe
iframe_loader($('div.popup > iframe', me));
// TODO: style buttons/forms
bootstrap_style(dialog.uiDialog);
};
/**
* Initializes searchbar widget
*/
function searchbar_init(bar)
{
var options_button = $('a.button.options', bar),
input = $('input:not([type=hidden])', bar),
placeholder = input.attr('placeholder'),
form = $('form', bar),
is_search_pending = function() {
if (input.val()) {
return true;
}
if (rcmail.task == 'mail' && $('#s_interval').val()) {
return true;
}
if (rcmail.gui_objects.search_filter && $(rcmail.gui_objects.search_filter).val() != 'ALL') {
return true;
}
if (rcmail.gui_objects.foldersfilter && $(rcmail.gui_objects.foldersfilter).val() != '---') {
return true;
}
},
update_func = function() {
$(bar)[is_search_pending() ? 'addClass' : 'removeClass']('active');
};
options_button.on('click', function(e) {
var id = $(this).data('target'),
options = $('#' + id),
open = options.is(':visible');
if (options.length) {
if (!open) {
if (ref[id]) {
ref[id](options.get(0), this, e);
}
else if (typeof window[id] == 'function') {
window[id](options.get(0), this, e);
}
}
options.next()[open ? 'show' : 'hide']();
options.toggleClass('hidden');
$('.floating-action-buttons').toggleClass('hidden');
$(bar).toggleClass('open');
$('button.search', options).off('click.search').on('click.search', function() {
options_button.trigger('click');
update_func();
});
}
});
input.on('input change', update_func)
.on('focus blur', function(e) { input.attr('placeholder', e.type == 'blur' ? placeholder : ''); });
// Search reset action
$('a.reset', bar).on('click', function(e) {
// for treelist widget's search setting val and keyup.treelist is needed
// in normal search form reset-search command will do the trick
input.val('').change().trigger('keyup.treelist', {keyCode: 27});
if ($(bar).is('.open')) {
options_button.click();
}
// Reset filter
if (rcmail.gui_objects.search_filter) {
$(rcmail.gui_objects.search_filter).val('ALL');
}
if (rcmail.gui_objects.foldersfilter) {
$(rcmail.gui_objects.foldersfilter).val('---').change();
rcmail.folder_filter('---');
}
update_func();
});
rcmail.addEventListener('init', function() { update_func(); })
.addEventListener('beforelist', function() {
if ($(bar).is('.open')) {
options_button.click(); // close options form on 'list' request
}
})
.addEventListener('responsebeforesearch', function() {
update_func();
});
};
/**
* Converts toolbar menu into popup-menu for small screens
*/
function toolbar_init()
{
if (env.got_smart_toolbar) {
return;
}
env.got_smart_toolbar = true;
var list_mark, items = [],
list_items = [],
meta = layout_metadata(),
button_func = function(button, items, cloned) {
var item = $('<li role="menuitem">'),
button = cloned ? create_cloned_button($(button), true, 'hidden-big hidden-large') : $(button).detach();
// Remove empty text nodes that break alignment of text of the menu item
button.contents().filter(function() { if (this.nodeType == 3 && !$.trim(this.nodeValue).length) $(this).remove(); });
if (button.is('.spacer')) {
item.addClass('spacer');
}
else {
item.append(button);
}
items.push(item);
};
// convert content toolbar to a popup list
if (layout.content) {
$('.header > .toolbar', layout.content).each(function() {
var toolbar = $(this);
toolbar.children().each(function() { button_func(this, items); });
toolbar.remove();
});
}
// convert list toolbar to a popup list
if (layout.list) {
$('.header > .toolbar', layout.list).each(function() {
var toolbar = $(this);
list_mark = toolbar.next();
toolbar.children().each(function() {
if (meta.mode != 'large') {
// TODO: Would be better to set this automatically on submenu display
// i.e. in show/shown event (see popup_init()), if possible
$(this).data('popup-pos', 'right');
}
// add items to the content menu too
button_func(this, items, true);
button_func(this, list_items);
});
toolbar.remove();
});
}
// special elements to clone and add to the toolbar (mobile only)
$('ul[data-menu="toolbar-small"] > li > a').each(function() {
var button = $(this).clone();
button.attr('id', this.id + '_clone');
// TODO: rcmail.register_button()
items.push($('<li role="menuitem">').addClass('hidden-big').append(button));
});
// append the new list toolbar and menu button
if (list_items.length) {
var container = layout.list.children('.header'),
menu_attrs = {'class': 'toolbar popupmenu listing iconized', id: 'toolbar-list-menu'},
menu_button = $('<a class="button icon toolbar-list-button" href="#list-menu">')
.attr({'data-popup': 'toolbar-list-menu'}),
// TODO: copy original toolbar attributes (class, role, aria-*)
toolbar = $('<ul>').attr(menu_attrs).data('popup-parent', container).append(list_items);
if (list_mark.length) {
toolbar.insertBefore(list_mark);
}
else {
container.append(toolbar);
}
container.append(menu_button);
}
// append the new toolbar and menu button
if (items.length) {
var container = layout.content.children('.header'),
menu_attrs = {'class': 'toolbar popupmenu listing iconized', id: 'toolbar-menu'},
menu_button = $('<a class="button icon toolbar-menu-button" href="#menu">')
.attr({'data-popup': 'toolbar-menu'});
container
// TODO: copy original toolbar attributes (class, role, aria-*)
.append($('<ul>').attr(menu_attrs).data('popup-parent', container).append(items))
.append(menu_button);
if (layout.list.length) {
// bind toolbar menu with the menu button in the list header
$('a.toolbar-menu-button', layout.list).click(function(e) {
e.stopPropagation();
menu_button.click();
});
}
}
};
/**
* Initialize a popup for specified button element
*/
function popup_init(item, win)
{
// On mobile we display the menu from the frame in the parent window
if (is_framed && is_mobile()) {
return parent.UI.popup_init(item, win || window);
}
if (!win) win = window;
var level,
popup_id = $(item).data('popup'),
popup = $(win.$('#' + popup_id).get(0)), // a "hack" to support elements in frames
popup_orig = popup,
title = $(item).attr('title'),
content_element = function() {
// On mobile we display a menu from the frame in the parent window
// To make menu actions working we have to clone the menu
// and pass click events to it...
if (win != window) {
popup = popup_orig.clone(true, true);
popup.attr('id', popup_id + '-clone')
.appendTo(document.body)
- .find('li > a, li.checkbox > label').attr('onclick', '').off('click').on('click', function(e) {
+ .find('li > a').attr('onclick', '').off('click').on('click', function(e) {
if (!$(this).is('.disabled')) {
$(item).popover('hide');
win.$('#' + $(this).attr('id')).click();
}
return false;
});
}
return popup.get(0);
};
$(item).attr({
'aria-haspopup': 'true',
'aria-expanded': 'false',
'aria-owns': popup_id,
})
.popover({
content: content_element,
trigger: $(item).data('popup-trigger') || 'click',
placement: $(item).data('popup-pos') || 'bottom',
animation: true,
boundary: 'window', // fix for https://github.com/twbs/bootstrap/issues/25428
html: true
})
.on('show.bs.popover', function(event) {
var init_func = popup.data('popup-init');
if (popup_id && menus[popup_id]) {
menus[popup_id].transitioning = true;
}
if (init_func && ref[init_func]) {
ref[init_func](popup.get(0), item, event);
}
else if (init_func && win[init_func]) {
win[init_func](popup.get(0), item, event);
}
level = $('div.popover:visible').length + 1;
popup.removeClass('hidden').attr('aria-hidden', false)
// Stop propagation on menu items that have popups
// to make a click on them not hide their parent menu(s)
.find('[aria-haspopup="true"]')
.data('level', level + 1)
.off('click.popup')
.on('click.popup', function(e) { e.stopPropagation(); });
if (!is_mobile()) {
// Set popup height so it is less than the window height
popup.css('max-height', Math.min(36 * 15 - 1, $(window).height() - 30));
}
})
.on('shown.bs.popover', function(event) {
var mobile = is_mobile(),
popover = $('#' + $(item).attr('aria-describedby'));
level = $(item).data('level') || 1;
// Set popup Back/Close title
if (mobile) {
var label = level > 1 ? 'back' : 'close',
title = rcmail.gettext(label),
class_name = 'button icon ' + (label == 'back' ? 'back' : 'cancel');
$('.popover-header', popover).empty()
.append($('<a>').attr('class', class_name).text(title)
.on('click', function(e) {
$(item).popover('hide');
if (level > 1) {
e.stopPropagation();
}
})
.on('mousedown', function(e) {
// stop propagation to i.e. do not close jQuery-UI dialogs below
e.stopPropagation();
})
);
}
// Hide other menus on the same level
$.each(menus, function(id, prop) {
if ($(prop.target).data('level') == level && id != popup_id) {
menu_hide(id);
}
});
// On keyboard event focus the first (active) entry and enable keyboard navigation
if ($(item).data('event') == 'key') {
popover.off('keydown.popup').on('keydown.popup', 'a.active', function(e) {
var entry, node, mode = 'next';
switch (e.which) {
case 27: // ESC
case 9: // TAB
$(item).popover('toggle').focus();
return false;
case 13: // ENTER
case 32: // SPACE
$(this).trigger('click').data('event', 'key');
return false; // for IE
case 38: // ARROW-UP
case 63232:
mode = 'previous';
case 40: // ARROW-DOWN
case 63233:
entry = e.target.parentNode;
while (entry = entry[mode + 'Sibling']) {
if (node = $(entry).children('.active')[0]) {
node.focus();
break;
}
}
return false; // prevents from scrolling the whole page
}
});
popover.find('a.active:first').focus();
}
if (popup_id && menus[popup_id]) {
menus[popup_id].transitioning = false;
}
// add overlay element for phone layout
if (mobile && !$('.popover-overlay').length) {
$('<div>').attr('class', 'popover-overlay')
.appendTo('body')
.click(function() { $(this).remove(); });
}
$('.popover-body', popover).addClass('webkit-scroller');
})
.on('hide.bs.popover', function() {
if (level == 1) {
$('.popover-overlay').remove();
}
if (popup_id && menus[popup_id] && popup.is(':visible')) {
menus[popup_id].transitioning = true;
}
})
.on('hidden.bs.popover', function() {
if (/-clone$/.test(popup.attr('id'))) {
popup.remove();
}
else {
popup.attr('aria-hidden', true)
// Some menus aren't being hidden, force that
.addClass('hidden')
// Bootstrap will detach the popup element from
// the DOM (https://github.com/twbs/bootstrap/issues/20219)
// making our menus to not update buttons state.
// Work around this by attaching it back to the DOM tree.
popup.appendTo(popup.data('popup-parent') || document.body);
}
// close orphaned popovers, for some reason there are sometimes such dummy elements left
$('.popover-body:empty').each(function() { $(this).parent().remove(); });
if (popup_id && menus[popup_id]) {
delete menus[popup_id];
}
})
// Because Bootstrap does not provide originalEvent in show/shown events
// we have to handle that by our own using click and keydown handlers
.on('click', function() {
$(this).data('event', 'mouse');
})
.on('keydown', function(e) {
if (e.originalEvent) {
switch (e.originalEvent.which) {
case 13:
case 32:
// Open the popup on ENTER or SPACE
e.preventDefault();
$(this).data('event', 'key').popover('toggle');
break;
case 27:
// Close the popup on ESC key
$(this).popover('hide');
break;
}
}
});
// re-add title attribute removed by bootstrap popover
if (title) {
$(item).attr('title', title);
}
popup.attr('aria-hidden', 'true').data('button', item);
// stop propagation to e.g. do not hide the popup when
// clicking inside on form elements
if (popup.data('editable')) {
popup.on('click mousedown', function(e) { e.stopPropagation(); });
}
};
/**
* Closes all popups (for use as event handler)
*/
function popups_close(e)
{
$('.popover.show').each(function() {
var popup = $('.popover-body', this),
button = popup.children().first().data('button');
if (button && e.target != button && !$(button).find(e.target).length && typeof button !== 'string') {
$(button).popover('hide');
}
if (!button) {
$(this).remove();
}
});
};
/**
* Handler for menu-open and menu-close events
*/
function menu_toggle(p)
{
if (!p || !p.name || (p.props && p.props.skinable === false)) {
return;
}
if (is_framed && is_mobile()) {
if (!p.win) {
p.win = window;
}
return parent.UI.menu_toggle(p);
}
if (p.name == 'messagelistmenu') {
menu_messagelist(p);
}
else if (p.event == 'menu-open') {
var fn, pos,
content = $('ul:first', p.obj),
target = p.props && p.props.link ? p.props.link : p.originalEvent.target;
if ($(target).is('span')) {
target = $(target).parents('a,li')[0];
}
if (p.name.match(/^drag/)) {
// create a fake element to position drag menu on the cursor position
pos = rcube_event.get_mouse_pos(p.originalEvent);
target = $('<a>').css({
position: 'absolute',
left: pos.x,
top: pos.y,
height: '1px',
width: '1px',
visibility: 'hidden'
})
.appendTo(document.body).get(0);
}
pos = $(target).data('popup-pos') || 'right';
if (p.name == 'folder-selector') {
content.addClass('listing folderlist');
}
else if (p.name == 'addressbook-selector' || p.name == 'contactgroup-selector') {
content.addClass('listing contactlist');
}
else if (content.hasClass('toolbarmenu')) {
content.addClass('listing');
}
if (p.name == 'pagejump-selector') {
content.addClass('simplelist');
p.obj.addClass('simplelist');
pos = 'top';
}
// There can be only one menu of the same type
if (menus[p.name]) {
menu_hide(p.name, p.originalEvent);
}
// Popover menus use animation. Sometimes the same menu is
// immediately hidden and shown (e.g. folder-selector for copy and move action)
// we have to wait until the previous menu hides before we can open it again
fn = function() {
if (menus[p.name] && menus[p.name].transitioning) {
return setTimeout(fn, 50);
}
if (!$(target).data('popup')) {
$(target).data({
popup: p.name,
'popup-pos': pos,
'popup-trigger': 'manual'
});
popup_init(target, p.win);
}
menus[p.name] = {target: target};
$(target).popover('show');
}
fn();
}
else {
menu_hide(p.name, p.originalEvent);
}
// Stop propagation so multi-level menus work properly
p.originalEvent.stopPropagation();
};
/**
* Close menu by name
*/
function menu_hide(name, event)
{
var target = menu_target(name);
if (name.match(/^drag/)) {
$(target).popover('dispose').remove();
}
else {
$(target).popover('hide');
// In phone mode close all menus when forwardmenu is requested to be closed
// FIXME: This is a hack, we need some generic solution.
if (name == 'forwardmenu') {
popups_close(event);
}
}
};
/**
* Destroys menu by name
*
* This is required when you replace the menu content element
*/
function menu_destroy(name)
{
$('[aria-owns=' + name + ']').popover('dispose').data('popup', null);
};
/**
* Get menu target by name
*/
function menu_target(name)
{
var target;
if (menus[name]) {
target = menus[name].target;
}
else {
target = $('#' + name).data('button');
if (!target) {
// catch cases as 'forwardmenu' where menu suffix has no hyphen
// or try with -menu suffix if it's not in the menu name already
if (name.match(/(?!-)menu$/)) {
name = name.substr(0, name.length - 4);
}
target = $('#' + name + '-menu').data('button');
}
}
return target;
};
/**
* Messages list options dialog
*/
function menu_messagelist(p)
{
var content = $('#listoptions-menu'),
width = content.width() + 25,
dialog = content.clone(true);
// set form values
$('select[name="sort_col"]', dialog).val(rcmail.env.sort_col || '');
$('select[name="sort_ord"]', dialog).val(rcmail.env.sort_order || 'ASC');
$('select[name="mode"]', dialog).val(rcmail.env.threading ? 'threads' : 'list');
// Fix id/for attributes
$('select', dialog).each(function() { this.id = this.id + '-clone'; });
$('label', dialog).each(function() { $(this).attr('for', $(this).attr('for') + '-clone'); });
var save_func = function(e) {
if (rcube_event.is_keyboard(e.originalEvent)) {
$('#listmenulink').focus();
}
var col = $('select[name="sort_col"]', dialog).val(),
ord = $('select[name="sort_ord"]', dialog).val(),
mode = $('select[name="mode"]', dialog).val();
rcmail.set_list_options([], col, ord, mode == 'threads' ? 1 : 0);
return true;
};
dialog = rcmail.simple_dialog(dialog, rcmail.gettext('listoptionstitle'), save_func, {
closeOnEscape: true,
minWidth: 400
});
};
/**
* About dialog
*/
function about_dialog(elem)
{
var support_url, support_func, support_button = false,
dialog = $('<iframe>').attr({id: 'aboutframe', src: rcmail.url('settings/about', {_framed: 1})}),
support_link = $('#supportlink');
if (support_link.length && (support_url = support_link.attr('href'))) {
support_button = support_link.text();
support_func = function(e) { support_url.indexOf('mailto:') < 0 ? window.open(support_url) : location.href = support_url; };
}
rcmail.simple_dialog(dialog, $(elem).text(), support_func, {
button: support_button,
button_class: 'help',
cancel_button: 'close',
height: 400
});
};
/**
* Show/hide more mail headers (envelope)
*/
function headers_show(button)
{
var headers = $(button).parent().prev();
headers[headers.is('.hidden') ? 'removeClass' : 'addClass']('hidden');
};
/**
* Mail headers dialog
*/
function headers_dialog()
{
var props = {_uid: rcmail.env.uid, _mbox: rcmail.env.mailbox, _framed: 1},
dialog = $('<iframe>').attr({id: 'headersframe', src: rcmail.url('headers', props)});
rcmail.simple_dialog(dialog, rcmail.gettext('arialabelmessageheaders'), null, {
cancel_button: 'close',
height: 400
});
};
/**
* Mail import dialog
*/
function import_dialog()
{
if (!rcmail.commands['import-messages']) {
return;
}
var content = $('#uploadform'),
dialog = content.clone();
var save_func = function(e) {
return rcmail.command('import-messages', $(dialog.find('form')[0]));
};
rcmail.simple_dialog(dialog, rcmail.gettext('importmessages'), save_func, {
button: 'import',
closeOnEscape: true,
minWidth: 400
});
};
/**
* Search options menu popup
*/
function searchmenu(obj)
{
var n, all,
list = $('input[name="s_mods[]"]', obj),
scope_select = $('#s_scope', obj),
mbox = rcmail.env.mailbox,
mods = rcmail.env.search_mods,
scope = rcmail.env.search_scope || 'base';
if (!$(obj).data('initialized')) {
$(obj).data('initialized', true);
if (list.length) {
list.on('change', function() { set_searchmod(obj, this); });
rcmail.addEventListener('beforesearch', function() { set_searchmod(obj); });
}
}
if (rcmail.env.search_mods) {
if (rcmail.env.task == 'mail') {
if (scope == 'all') {
mbox = '*';
}
mods = mods[mbox] ? mods[mbox] : mods['*'];
all = 'text';
scope_select.val(scope);
}
else {
all = '*';
}
if (mods[all]) {
list.map(function() {
this.checked = true;
this.disabled = this.value != all;
});
}
else {
list.prop('disabled', false).prop('checked', false);
for (n in mods) {
list.filter('[value="' + n + '"]').prop('checked', true);
}
}
}
};
function set_searchmod(menu, elem)
{
var all, m, task = rcmail.env.task,
mods = rcmail.env.search_mods,
mbox = rcmail.env.mailbox,
scope = $('#s_scope', menu).val(),
interval = $('#s_interval', menu).val();
if (scope == 'all') {
mbox = '*';
}
if (!mods) {
mods = {};
}
if (task == 'mail') {
if (!mods[mbox]) {
mods[mbox] = rcube_clone_object(mods['*']);
}
m = mods[mbox];
all = 'text';
rcmail.env.search_scope = scope;
rcmail.env.search_interval = interval;
}
else { //addressbook
m = mods;
all = '*';
}
if (!elem) {
return;
}
if (!elem.checked) {
delete(m[elem.value]);
}
else {
m[elem.value] = 1;
}
// mark all fields
if (elem.value == all) {
$('input[name="s_mods[]"]', menu).map(function() {
if (this == elem) {
return;
}
this.checked = true;
if (elem.checked) {
this.disabled = true;
delete m[this.value];
}
else {
this.disabled = false;
m[this.value] = 1;
}
});
}
rcmail.set_searchmods(m);
};
/**
* Spellcheck languages list
*/
function spellmenu(obj)
{
var i, link, li, list = [],
lang = rcmail.spellcheck_lang(),
ul = $('ul', obj);
if (!ul.length) {
ul = $('<ul class="toolbarmenu selectable listing iconized" role="menu">');
for (i in rcmail.env.spell_langs) {
li = $('<li role="menuitem">');
link = $('<a href="#'+ i +'" tabindex="0"></a>')
.text(rcmail.env.spell_langs[i])
.addClass('active').data('lang', i)
.on('click keypress', function(e) {
if (e.type != 'keypress' || rcube_event.get_keycode(e) == 13) {
rcmail.spellcheck_lang_set($(this).data('lang'));
rcmail.hide_menu('spell-menu', e);
return false;
}
});
link.appendTo(li);
list.push(li);
}
ul.append(list).appendTo(obj);
}
// select current language
$('li', ul).each(function() {
var el = $('a', this);
if (el.data('lang') == lang) {
el.addClass('selected').attr('aria-selected', 'true');
}
else if (el.hasClass('selected')) {
el.removeClass('selected').removeAttr('aria-selected');
}
});
};
/**
* Add/remove item to/from compose options status bar
*/
function compose_status(id, status)
{
var bar = $('#composestatusbar'), ico = bar.find('a.button.icon.' + id);
if (!status) {
ico.remove();
}
else if (!ico.length) {
$('<a>').attr('class', 'button icon ' + id)
.on('click', function() { show_sidebar(); })
.appendTo(bar);
}
};
/**
* Attachment menu
*/
function attachmentmenu(obj, button, event)
{
var id = $(button).parent().attr('id').replace(/^attach/, '');
$.each(['open', 'download', 'rename'], function() {
var action = this;
$('#attachmenu' + action, obj).off('click').attr('onclick', '').click(function(e) {
rcmail.command(action + '-attachment', id, this, e.originalEvent);
});
});
// call menu-open so core can set state of menu commands
rcmail.command('menu-open', {menu: 'attachmentmenu', id: id}, obj, event);
};
/**
* Appends drop-icon to attachments list item (to invoke attachment menu)
*/
function attachmentmenu_append(item)
{
item = $(item);
if (!item.is('.no-menu') && !item.children('.drop').length) {
var label = rcmail.gettext('options');
var button = $('<a>')
.attr({
href: '#',
tabindex: 0,
title: label,
'class': 'button icon dropdown skip-content'
})
.on('click', function(e) {
attachmentmenu($('#attachmentmenu'), button, e);
})
.append($('<span>').attr('class', 'inner').text(label))
.appendTo(item);
}
};
/**
* Mailto menu
*/
function mailtomenu(obj, button, event)
{
var mailto = $(button).attr('href').replace(/^mailto:/, '');
if (mailto.indexOf('@') < 0) {
return true; // let the browser handle this
}
if (rcmail.env.has_writeable_addressbook) {
$('.addressbook', obj).addClass('active')
.off('click').on('click', function(e) {
var i, contact = mailto,
txt = $(button).filter('.rcmContactAddress').text();
contact = contact.split('?')[0].split(',')[0].replace(/(^<|>$)/g, '');
if (txt) {
txt = txt.replace('<' + contact + '>', '');
contact = '"' + $.trim(txt) + '" <' + contact + '>';
}
rcmail.command('add-contact', contact, this, e.originalEvent);
});
}
$('.compose', obj).off('click').on('click', function(e) {
rcmail.command('compose', mailto, this, e.originalEvent);
});
return rcmail.command('menu-open', {menu: 'mailto-menu', link: button}, button, event);
};
/**
* Appends popup menu to mailto links
*/
function mailtomenu_append(item)
{
$(item).attr('onclick', '').on('click', function(e) {
return mailtomenu($('#mailto-menu'), item, e);
});
};
/**
* Headers menu in mail compose
*/
function headersmenu(obj, button, event)
{
$('li > a', obj).each(function() {
var link = $(this), target = '#compose_' + link.data('target');
link[$(target).is(':visible') ? 'removeClass' : 'addClass']('active')
.off().on('click', function() {
$(target).removeClass('hidden').find('.recipient-input input').focus();
link.removeClass('active');
rcmail.set_menu_buttons();
});
});
};
/**
* Reset/hide compose message recipient input
*/
function header_reset(id)
{
$('#' + id).val('').change()
// jump to the next input
.closest('.form-group').nextAll(':not(.hidden)').first().find('input').focus();
$('a[data-target=' + id.replace(/^_/, '') + ']').addClass('active');
rcmail.set_menu_buttons();
};
/**
* Recipient (contact) selector
*/
function recipient_selector(field, opts)
{
if (!opts) opts = {};
var title = rcmail.gettext(opts.title || 'insertcontact'),
dialog = $('#recipient-dialog'),
parent = dialog.parent(),
close_func = function() {
if (dialog.is(':visible')) {
rcmail.env.recipient_dialog.dialog('close');
}
},
insert_func = function() {
if (opts.action) {
opts.action();
close_func();
return;
}
rcmail.command('add-recipient');
};
if (!rcmail.env.recipient_selector_initialized) {
rcmail.addEventListener('add-recipient', close_func);
rcmail.env.recipient_selector_initialized = true;
}
if (field) {
rcmail.env.focused_field = '#_' + field;
}
rcmail.contact_list.clear_selection();
rcmail.contact_list.multiselect = 'multiselect' in opts ? opts.multiselect : true;
rcmail.env.recipient_dialog = rcmail.simple_dialog(dialog, title, insert_func, {
button: rcmail.gettext(opts.button || 'insert'),
button_class: opts.button_class || 'insert recipient',
height: 600,
classes: {
'ui-dialog-content': 'p-0' // remove padding on dialog content
},
open: function() {
// Don't want focus in the search field, we focus first contacts source record instead
$('#directorylist a:first').focus();
},
close: function() {
dialog.appendTo(parent);
$(this).remove();
$(opts.focus || rcmail.env.focused_field).focus();
}
});
};
/**
* Create/Update quota widget (setquota event handler)
*/
function update_quota(p)
{
var element = $('#quotadisplay'),
bar = element.find('.bar'),
value = p.total ? p.percent : 0;
if (!bar.length) {
bar = $('<span class="bar"><span class="value"></span></span>').appendTo(element);
}
if (value > 0 && value < 10) {
value = 10; // smaller values look not so nice
}
bar.find('.value').css('width', value + '%')[value >= 90 ? 'addClass' : 'removeClass']('warning');
element.attr('title', element.find('.count').attr('title'));
if (p.table) {
element.css('cursor', 'pointer').data('popup-pos', 'top')
.off('click').on('click', function(e) {
rcmail.simple_dialog(p.table, 'quota', null, {cancel_button: 'close'});
});
}
else {
element.tooltip('dispose').tooltip({trigger: is_mobile() ? 'click' : 'hover'});
}
};
/**
* Replaces recipient input with content-editable element that uses "recipient boxes"
*/
function recipient_input(obj)
{
var list, input, ac_props, update_lock,
input_len_update = function() {
input.css('width', Math.max(40, input.val().length * 15 + 25));
},
apply_func = function() {
// update the original input
$(obj).val(list.text() + input.val());
},
focus_func = function() {
list.addClass('focus');
},
insert_recipient = function(name, email, replace) {
var recipient = $('<li class="recipient">'),
name_element = $('<span class="name">').html(recipient_input_name(name || email))
.on('dblclick', function(e) { recipient_input_edit_dialog(e, insert_recipient); }),
email_element = $('<span class="email">'),
// TODO: should the 'close' link have tabindex?
link = $('<a>').attr({'class': 'button icon remove'})
.click(function() {
recipient.remove();
apply_func();
input.focus();
return false;
});
if (name) {
email = ' <' + email + '>';
}
email_element.text((name ? email : '') + ',');
recipient.attr('title', name ? (name + email) : null)
.append([name_element, email_element, link])
if (replace)
replace.replaceWith(recipient);
else
recipient.insertBefore(input.parent());
},
update_func = function(text) {
var result;
if (update_lock) {
return;
}
update_lock = true;
text = (text || input.val()).replace(/[,;\s]+$/, '');
result = recipient_input_parser(text);
$.each(result.recipients, function() {
insert_recipient(this.name, this.email);
});
// setTimeout() here is needed for proper input reset on paste event
// This is also the reason why we need parse_lock
setTimeout(function() {
input.val(result.text);
apply_func();
input_len_update();
update_lock = false;
}, 1);
return result.recipients.length > 0;
},
parse_func = function(e) {
if (e.type == 'blur') {
list.removeClass('focus');
}
// FIXME: This is a workaround for a bug where on a touch device
// selecting a recipient from autocomplete list do not work because
// of some events race condition (?)
if (this.value.indexOf('@') < 0) {
return;
}
// On paste the text is not yet in the input we have to use clipboard.
// Also because on paste new-line characters are replaced by spaces (#6460)
update_func(e.type == 'paste' ? (e.originalEvent.clipboardData || window.clipboardData).getData('text') : null);
},
keydown_func = function(e) {
// On Backspace remove the last recipient
if (e.keyCode == 8 && !input.val().length) {
list.children('li.recipient:last').remove();
apply_func();
return false;
}
// Here we add a recipient box when the separator (,;) or Enter was pressed
else if (e.key == ',' || e.key == ';' || (e.key == 'Enter' && !rcmail.ksearch_visible())) {
if (update_func()) {
return false;
}
}
input_len_update();
};
// Create the input element and "editable" area
input = $('<input>').attr({type: 'text', tabindex: $(obj).attr('tabindex')})
.on('paste change blur', parse_func)
.on('input', input_len_update) // only to fix input length after paste
.on('keydown', keydown_func)
.on('focus mousedown', focus_func);
list = $('<ul>').addClass('form-control recipient-input')
.append($('<li>').append(input))
.on('click', function() { input.focus(); });
// Hide the original input/textarea
// Note: we do not remove the original element, and we do not use
// display: none, because we want to handle onfocus event
// Note: tabindex:-1 to make Shift+TAB working on these widgets
$(obj).css({position: 'absolute', opacity: 0, left: '-5000px', width: '10px'})
.attr('tabindex', -1)
.after(list)
// some core code sometimes focuses or changes the original node
// in such cases we wan't to parse it's value and apply changes
// to the widget element
.on('focus', function(e) { input.focus(); })
.on('change', function(e) {
$('li.recipient', list).remove();
input.val(this.value).change();
})
// copy and parse the value already set
.change();
// this one line is here to fix border of Bootstrap's input-group,
// input-group should not contain any hidden elements
$(obj).detach().insertBefore(list.parent());
if (rcmail.env.autocomplete_threads > 0) {
ac_props = {
threads: rcmail.env.autocomplete_threads,
sources: rcmail.env.autocomplete_sources
};
}
// Init autocompletion
rcmail.init_address_input_events(input, ac_props);
};
/**
* Parses recipient address input and extracts recipients from it
*/
function recipient_input_parser(text)
{
// support new-line as a separator, for paste action (#6460)
text = $.trim(text.replace(/[,;\s]*[\r\n]+/g, ','));
var recipients = [],
address_rx_part = '(\\S+|("[^"]+"))@\\S+',
recipient_rx1 = new RegExp('(<' + address_rx_part + '>)'),
recipient_rx2 = new RegExp('(' + address_rx_part + ')'),
global_rx = /(?=\S)[^",;]*(?:"[^\\"]*(?:\\[,;\S][^\\"]*)*"[^",;]*)*/g,
matches = text.match(global_rx);
$.each(matches || [], function() {
if (this.length && (recipient_rx1.test(this) || recipient_rx2.test(this))) {
var email = RegExp.$1,
name = $.trim(this.replace(email, ''));
recipients.push({
name: name,
email: email.replace(/(^<|>$)/g, ''),
text: this
});
text = text.replace(this, '');
}
});
text = text.replace(/[,;]+/, ',').replace(/^[,;\s]+/, '');
return {recipients: recipients, text: text};
};
/**
* Generates HTML for a text adding <span class="hidden">
* for quote/backslash characters, so they are hidden from the user,
* but still in place to make copying simpler
*
* Note: Selection works in Chrome, but not in Firefox?
*/
function recipient_input_name(text)
{
var i, char, result = '', len = text.length;
if (text.charAt(0) != '"' && text.indexOf('"') > -1) {
text = '"' + text.replace('\\', '\\\\').replace('"', '\\"') + '"';
}
for (i=0; i<len; i++) {
char = text.charAt(i);
switch (char) {
case '"':
if (i > 0 && i < len - 1) {
result += '"';
break;
}
result += '<span class="quotes">' + char + '</span>';
break;
case '\\':
result += '<span class="quotes">' + char + '</span>';
if (text.charAt(i+1) == '\\') {
result += char;
i++;
}
break;
case '<':
result += '&lt;';
break;
case '>':
result += '&gt;';
break;
default:
result += char;
}
}
return result;
};
/**
* Displays dialog to edit a recipient entry
*/
function recipient_input_edit_dialog(e, callback)
{
var element = $(e.target).parents('.recipient'),
recipient = element.text().replace(/,+$/, ''),
input = $('<input>').attr({type: 'text', size: 50}).val(recipient),
content = $('<label>').text(rcmail.gettext('recipient')).append(input);
rcmail.simple_dialog(content, 'recipientedit', function() {
var result, value = input.val();
if (value) {
if (value != recipient) {
result = recipient_input_parser(value);
if (result.recipients.length != 1) {
return false;
}
callback(result.recipients[0].name, result.recipients[0].email, element);
}
return true;
}
});
};
/**
* Adds logic to the contact photo widget
*/
function image_upload_input(obj)
{
var reset_button = $('<a>')
.attr({'class': 'icon button delete', href: '#', })
.click(function(e) { rcmail.command('delete-photo', '', this, e); return false; });
$(obj).append(reset_button).click(function() { rcmail.upload_input('upload-form'); });
$('img', obj).on('load', function() {
// FIXME: will that work in IE?
var state = (this.currentSrc || this.src).indexOf(rcmail.env.photo_placeholder) != -1;
$(obj)[state ? 'removeClass' : 'addClass']('changed');
});
};
/**
* Displays loading... overlay for iframes
*/
function iframe_loader(frame)
{
frame = $(frame);
if (frame.length) {
var loader = $('<div class="iframe-loader">')
.append($('<div class="spinner spinner-border" role="status">')
.append($('<span class="sr-only">').text(rcmail.gettext('loading'))));
// custom 'loaded' event is expected to be triggered by plugins
// when using the loader not on an iframe
frame.on('load error loaded', function() {
// wait some time to make sure the iframe stopped loading
setTimeout(function() { loader.remove(); }, 500);
})
.parent().append(loader);
// fix scrolling in iOS
if (ios) {
frame.parent().addClass('ios-scroll');
}
}
};
/**
- * Checkbox wrapper
+ * Convert checkbox input into Bootstrap's custom switch
*/
function pretty_checkbox(checkbox)
{
- var checkbox = $(checkbox),
- id = checkbox.attr('id');
+ var label, parent, id, checkbox = $(checkbox);
- if (checkbox.is('.icon-checkbox')) {
+ if (checkbox.is('.custom-control-input')) {
return;
}
- if (!id) {
- if (!env.icon_checkbox) env.icon_checkbox = 0;
- id = 'icochk' + (++env.icon_checkbox);
+ if (!(id = checkbox.attr('id'))) {
+ id = 'icochk' + (++env.checkboxes);
checkbox.attr('id', id);
}
- checkbox.addClass('icon-checkbox form-check-input').after(
- $('<label>').attr({'for': id, title: checkbox.attr('title') || ''})
- .on('click', function(e) { e.stopPropagation(); })
- );
+ if (checkbox.parent().is('label')) {
+ label = checkbox.parent();
+ checkbox = checkbox.detach();
+ label.before(checkbox);
+ }
+ else {
+ label = $('<label>');
+ }
+
+ label.attr({'for': id, 'class': 'custom-control-label', title: checkbox.attr('title') || ''})
+ .on('click', function(e) { e.stopPropagation(); });
+
+ checkbox.addClass('form-check-input custom-control-input')
+ .wrap('<div class="custom-control custom-switch">')
+ .parent().append(label);
};
/**
* Make select dropdowns pretty
* TODO: searching, optgroup, [multiple], iPhone/iPad
*/
function pretty_select(select)
{
// iPhone is not supported yet (problem with browser dropdown on focus)
if (bw.iphone || bw.ipad) {
return;
}
select = $(select);
if (select.is('.pretty-select')) {
return;
}
var select_ident = 'select' + select.attr('id') + select.attr('name');
var is_menu_open = function() {
// Use proper window in cases when the select element intialized
// inside an iframe is then used in a dialog inside a parent's window
// For some reason we can't access data-button property in cross-window
// case, we use data-ident attribute instead
var win = select[0].ownerDocument.defaultView;
if (win.$('.select-menu .listing').data('ident') == select_ident) {
return true;
}
};
var close_func = function() {
var open = is_menu_open();
select.popover('dispose').focus();
return !open;
};
var open_func = function(e) {
var items = [],
dialog = select.closest('.ui-dialog')[0],
min_width = select.outerWidth(),
max_height = $(document.body).height() - 75,
max_width = $(document.body).width() - 20,
value = select.val();
if (!is_mobile()) {
max_height *= 0.5;
}
$('option', select).each(function() {
var label = $(this).text(),
link = $('<a href="#">')
.data('value', this.value)
.addClass(this.disabled ? 'disabled' : 'active' + (this.value == value ? ' selected' : ''));
if (label.length) {
link.text(label);
}
else {
link.html('&nbsp;'); // link can't be empty
}
items.push($('<li>').append(link));
});
var list = $('<ul class="toolbarmenu listing selectable iconized">')
.attr('data-ident', select_ident)
.append(items)
.on('click', 'a.active', function() {
// first close the list, then update the select, the order is important
// for cases when the select might be removed in change event (datepicker)
var val = $(this).data('value'), ret = close_func();
select.val(val).change();
return ret;
})
.on('keydown', 'a.active', function(e) {
var item, node, mode = 'next';
switch (e.which) {
case 27: // ESC
case 9: // TAB
return close_func();
case 13: // ENTER
case 32: // SPACE
$(this).click();
return false; // for IE
case 38: // ARROW-UP
case 63232:
mode = 'previous';
case 40: // ARROW-DOWN
case 63233:
item = e.target.parentNode;
while (item = item[mode + 'Sibling']) {
if (node = $(item).children('.active')[0]) {
node.focus();
break;
}
}
return false; // prevents from scrolling the whole page
}
});
select.popover('dispose')
.popover({
// because of focus issues we can't always use body,
// if select is in a dialog, popover has to be a child of this dialog
container: dialog || document.body,
content: list[0],
placement: 'bottom',
trigger: 'manual',
boundary: 'viewport',
html: true,
offset: '0,2',
sanitize: false,
template: '<div class="popover select-menu" style="min-width: ' + min_width + 'px; max-width: ' + max_width + 'px">'
+ '<div class="popover-header"></div>'
+ '<div class="popover-body" style="max-height: ' + max_height + 'px"></div></div>'
})
.on('shown.bs.popover', function() {
// Set popup Close title
list.parent().prev()
.empty()
.append($('<a class="button icon cancel">').text(rcmail.gettext('close'))
.on('click', function(e) {
e.stopPropagation();
return close_func();
})
);
// focus first active element on the list
if (rcube_event.is_keyboard(e)) {
list.find('a.active:first').focus();
}
// don't propagate mousedown event
list.on('mousedown', function(e) { e.stopPropagation(); });
})
.popover('show');
};
select.addClass('pretty-select custom-select form-control')
.on('mousedown keydown', function(e) {
select = $(e.target); // so it works after clone
// Do nothing on disabled select or on TAB key
if (select.prop('disabled')) {
return;
}
if (e.which == 9) {
close_func();
return true;
}
// Close popup on ESC key or on click if already open
if (e.which == 27 || (e.type == 'mousedown' && is_menu_open())) {
return close_func();
}
select.focus();
// prevent displaying browser-default select dropdown
select.prop('disabled', true);
setTimeout(function() { select.prop('disabled', false); }, 0);
e.stopPropagation();
// display options in our way (on SPACE, ENTER, ARROW-DOWN or mousedown)
if (e.type == 'mousedown' || e.which == 13 || e.which == 32 || e.which == 40 || e.which == 63233) {
open_func(e);
return false;
}
})
.on('click', function(e) {
// Stop propagation of click event to prevent from
// disposing the menu by general popover closing handler (popups_close())
e.stopPropagation();
return false;
});
};
/**
* HTML editor textarea wrapper with nice looking tabs-like switch
*/
function html_editor_init(obj)
{
// Here we support two structures
// 1. <div><textarea></textarea><select name="editorSelector"></div>
// 2. <tr><td><td><td><textarea></textarea></td></tr>
// <tr><td><td><td><input type="checkbox"></td></tr>
var sw, is_table = false,
editor = $(obj),
parent = editor.parent(),
tabindex = editor.attr('tabindex'),
mode = function() {
if (is_table) {
return sw.is(':checked') ? 'html' : 'plain';
}
return sw.val();
},
tabs = $('<ul class="nav nav-tabs">')
.append($('<li class="nav-item">')
.append($('<a class="nav-link mode-html" href="#">')
.text(rcmail.gettext('htmltoggle'))))
.append($('<li class="nav-item">')
.append($('<a class="nav-link mode-plain" href="#">')
.text(rcmail.gettext('plaintoggle'))));
if (parent.is('td')) {
sw = $('input[type="checkbox"]', parent.parent().next());
is_table = true;
}
else {
sw = $('[name="editorSelector"]', obj.form);
}
// sanity check
if (sw.length != 1) {
return;
}
parent.addClass('html-editor');
editor.before(tabs);
$('a', tabs).attr('tabindex', tabindex)
.on('click', function(e) {
var id = editor.attr('id'), is_html = $(this).is('.mode-html');
e.preventDefault();
if (rcmail.command('toggle-editor', {id: id, html: is_html}, '', e.originalEvent)) {
$(this).tab('show').prop('tabindex', -1);
$('.mode-' + (is_html ? 'plain' : 'html'), tabs).prop('tabindex', tabindex);
if (is_table) {
sw.prop('checked', is_html);
}
}
})
.filter('.mode-' + mode()).tab('show').prop('tabindex', -1);
if (is_table) {
// Hide unwanted table cells
- sw.parent().parent().hide();
+ sw.parents('tr').first().hide();
parent.prev().hide();
// Modify the textarea cell to use 100% width
parent.addClass('col-sm-12');
}
// make the textarea autoresizeable
textarea_autoresize_init(editor);
};
/**
* Make the textarea autoresizeable depending on it's content length.
* The way there's no vertical scrollbar.
*/
function textarea_autoresize_init(textarea)
{
var resize = function(e) {
clearTimeout(env.textarea_timer);
env.textarea_timer = setTimeout(function() {
var area = $(e.target),
initial_height = area.data('initial-height'),
scroll_height = area[0].scrollHeight;
// do nothing when the area is hidden
if (!scroll_height) {
return;
}
if (!initial_height) {
area.data('initial-height', initial_height = scroll_height);
}
// strange effect in Chrome/Firefox when you delete a line in the textarea
// the scrollHeight is not decreased by the line height, but by 2px
// so jumps up many times in small steps, we'd rather use one big step
if (area.outerHeight() - scroll_height == 2) {
scroll_height -= 19; // 21px is the assumed line height
}
area.outerHeight(Math.max(initial_height, scroll_height));
}, 10);
};
$(textarea).css('overflow-y', 'hidden').on('input', resize).trigger('input');
// Make sure the height is up-to-date also in time intervals
setInterval(function() { $(textarea).trigger('input'); }, 1000);
};
// Inititalizes smart list input
function smart_field_init(field)
{
var tip, id = field.id + '_list',
area = $('<div class="multi-input"><div class="content"></div><div class="invalid-feedback"></div></div>'),
list = field.value ? field.value.split("\n") : [''];
if ($('#' + id).length) {
return;
}
// add input rows
$.each(list, function(i, v) {
smart_field_row_add($('.content', area), v, field.name, i, $(field).data('size'));
});
area.attr('id', id);
field = $(field);
if (field.attr('disabled')) {
area.hide();
}
// disable the original field anyway, we don't want it in POST
else {
field.prop('disabled', true);
}
if (field.data('hidden')) {
area.hide();
}
field.after(area);
if (field.hasClass('is-invalid')) {
area.addClass('is-invalid');
$('.invalid-feedback', area).text(field.data('error-msg'));
}
};
function smart_field_row_add(area, value, name, idx, size, after)
{
// build row element content
var input, elem = $('<div class="input-group">'
+ '<input type="text" class="form-control">'
+ '<span class="input-group-append"><a class="icon reset input-group-text" href="#"></a></span>'
+ '</div>'),
attrs = {value: value, name: name + '[]'};
if (size) {
attrs.size = size;
}
input = $('input', elem).attr(attrs)
.keydown(function(e) {
var input = $(this);
// element creation event (on Enter)
if (e.which == 13) {
var name = input.attr('name').replace(/\[\]$/, ''),
dt = (new Date()).getTime(),
elem = smart_field_row_add(area, '', name, dt, size, input.parent());
$('input', elem).focus();
}
// backspace or delete: remove input, focus previous one
else if ((e.which == 8 || e.which == 46) && input.val() == '') {
var parent = input.parent(),
siblings = area.children();
if (siblings.length > 1) {
if (parent.prev().length) {
parent.prev().children('input').focus();
}
else {
parent.next().children('input').focus();
}
parent.remove();
return false;
}
}
});
// element deletion event
$('a.reset', elem).click(function() {
var record = $(this.parentNode.parentNode);
if (area.children().length > 1) {
$('input', record.next().length ? record.next() : record.prev()).focus();
record.remove();
}
else {
$('input', record).val('').focus();
}
});
$(elem).find('input,a')
.on('focus', function() { area.addClass('focused'); })
.on('blur', function() { area.removeClass('focused'); });
if (after) {
after.after(elem);
}
else {
elem.appendTo(area);
}
return elem;
};
// Reset and fill the smart list input with new data
function smart_field_reset(field, data)
{
var id = field.id + '_list',
list = data.length ? data : [''],
area = $('#' + id).children('.content');
area.empty();
// add input rows
$.each(list, function(i, v) {
smart_field_row_add(area, v, field.name, i, $(field).data('size'));
});
};
/**
* Register form errors, mark fields as invalid, dsplay the error below the input
*/
function form_errors(tips)
{
$.each(tips, function() {
var input = $('#' + this[0]).addClass('is-invalid');
if (input.data('type') == 'list') {
input.data('error-msg', this[2]);
return;
}
input.after($('<span class="invalid-feedback">').text(this[2]));
});
};
/**
* Show/hide the navigation list
*/
function switch_nav_list(obj)
{
var records, height, speed = 250,
button = $('a.button', obj),
navlist = $(obj).next();
if (!navlist.height()) {
records = $('tr,li', navlist).filter(function() { return this.style.display != 'none'; });
height = $(records[0]).height() || 50;
navlist.animate({height: (Math.min(5, records.length) * height + 1) + 'px'}, speed);
button.addClass('collapse').removeClass('expand');
$(obj).addClass('expanded');
}
else {
navlist.animate({height: '0'}, speed);
button.addClass('expand').removeClass('collapse');
$(obj).removeClass('expanded');
}
};
/**
* Wrapper for rcmail.open_window to intercept window opening
* and display a dialog with an iframe instead of a real window.
*/
function window_open(url)
{
// Use 4th argument to bypass the dialog-mode e.g. for external windows
if (!is_mobile() || arguments[3] === true) {
return env.open_window.apply(rcmail, arguments);
}
// _extwin=1, _framed=1 are required to display attachment preview
// layout properly and make mobile menus working
url = rcmail.add_url(url, '_framed', 1);
url = rcmail.add_url(url, '_extwin', 1);
var label, title = '',
props = {cancel_button: 'close', width: 768, height: 768},
frame = $('<iframe>').attr({id: 'windowframe', src: url});
if (/_action=([a-z_]+)/.test(url) && (label = rcmail.labels[RegExp.$1])) {
title = label;
}
if (/_frame=1/.test(url)) {
props.dialogClass = 'no-titlebar';
}
rcmail.simple_dialog(frame, title, null, props);
return true;
};
/**
* Get layout modes. In frame mode returns the parent layout modes.
*/
function layout_metadata()
{
if (is_framed) {
var doc = $(parent.document.documentElement);
return {
mode: doc[0].className.match(/layout-([a-z]+)/) ? RegExp.$1 : mode,
touch: doc.is('.touch'),
};
}
return {mode: mode, touch: touch};
};
/**
* Returns true if the layout is in 'small' or 'phone' mode
*/
function is_mobile()
{
var meta = layout_metadata();
return meta.mode == 'phone' || meta.mode == 'small';
};
/**
* Returns true if the layout is in 'touch' mode
*/
function is_touch()
{
var meta = layout_metadata();
return meta.touch;
};
}
if (window.rcmail) {
/**
* Elastic version of show_menu as we don't need e.g. menu positioning from core
* TODO: keyboard navigation in menus
*/
rcmail.show_menu = function(prop, show, event)
{
var name = typeof prop == 'object' ? prop.menu : prop,
obj = $('#' + name);
if (typeof prop == 'string') {
prop = {menu: name};
}
// just delegate the action to rcube_elastic_ui
return rcmail.triggerEvent(show === false ? 'menu-close' : 'menu-open', {name: name, obj: obj, props: prop, originalEvent: event});
}
/**
* Elastic version of hide_menu as we don't need e.g. menus stack handling
*/
rcmail.hide_menu = function(name, event)
{
// delegate to rcube_elastic_ui
return rcmail.triggerEvent('menu-close', {name: name, props: {menu: name}, originalEvent: event});
}
}
else {
// rcmail does not exists e.g. on the error template inside a frame
// we fake the engine a little
var rcmail = parent.rcmail,
rcube_webmail = parent.rcube_webmail,
bw = {};
}
var UI = new rcube_elastic_ui();
// Improve non-inline datepickers
if ($ && $.datepicker) {
var __newInst = $.datepicker._newInst;
$.extend($.datepicker, {
_newInst: function(target, inline) {
var inst = __newInst.call(this, target, inline);
if (!inst.inline) {
UI.datepicker_init(inst.dpDiv);
}
return inst;
}
});
}

File Metadata

Mime Type
text/x-diff
Expires
Sat, Jan 31, 2:35 PM (20 h, 57 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
426337
Default Alt Text
(261 KB)

Event Timeline