Merge branch 'master' into dev/elastic

Conflicts:
	skins/elastic/ui.js
pull/5578/merge
Aleksander Machniak 6 years ago
commit da5080a980

@ -52,6 +52,7 @@ CHANGELOG Roundcube Webmail
- Support for 'link' objects
- Support including files with path relative to templates directory
- Use <button> instead of <input> for submit button on logon screen
- Support skin localization (#5853)
- Reset onerror on images if placeholder does not exist to prevent from requests storm
- Unified and simplified code for loading content frame for responses and identities
- Display contact import and advanced search in popup dialogs
@ -71,6 +72,7 @@ CHANGELOG Roundcube Webmail
- Handle inline images also inside multipart/mixed messages (#5905)
- Allow style tags in HTML editor on composed/reply messages (#5751)
- Use Github API as a fallback to fetch js dependencies to workaround throttling issues (#6248)
- Show confirm dialog when moving folders using drag and drop (#6119)
- Fix bug where new_user_dialog email check could have been circumvented by deleting / abandoning session (#5929)
- Fix skin extending for assets (#5115)
- Fix handling of forwarded messages inside of a TNEF message (#5632)

@ -307,6 +307,13 @@ EOF;
$value = array_merge((array) $this->config->get('dont_override'), array_keys($meta['config']));
$this->config->set('dont_override', $value, true);
}
if (!empty($meta['localization'])) {
$locdir = $meta['localization'] === true ? 'localization' : $meta['localization'];
if ($texts = $this->app->read_localization(RCUBE_INSTALL_PATH . $skin_path . '/' . $locdir)) {
$this->app->load_language($_SESSION['language'], $texts);
}
}
}
/**

@ -6857,7 +6857,7 @@ function rcube_webmail()
}
else if (colprop.type == 'select') {
input = $('<select>')
.addClass('form-control ff_' + col)
.addClass('custom-select ff_' + col)
.attr({ name: '_' + col + name_suffix, id: input_id });
var options = input.attr('options');
@ -7272,10 +7272,10 @@ function rcube_webmail()
// on the list when dragging starts (and stops), this is slow, but
// I didn't find a method to check droptarget on over event
accept: function(node) {
if (!$(node).is('.mailbox'))
if (!node.is('.mailbox'))
return false;
var source_folder = ref.folder_id2name($(node).attr('id')),
var source_folder = ref.folder_id2name(node.attr('id')),
dest_folder = ref.folder_id2name(this.id),
source = ref.env.subscriptionrows[source_folder],
dest = ref.env.subscriptionrows[dest_folder];
@ -7322,8 +7322,10 @@ function rcube_webmail()
newname = to === '' || to === '*' ? basename : to + this.env.delimiter + basename;
if (newname != from) {
this.http_post('rename-folder', {_folder_oldname: from, _folder_newname: newname},
this.set_busy(true, 'foldermoving'));
this.confirm_dialog(this.get_label('movefolderconfirm'), 'move', function() {
ref.http_post('rename-folder', {_folder_oldname: from, _folder_newname: newname},
ref.set_busy(true, 'foldermoving'));
}, {button_class: 'save move'});
}
}
};
@ -7510,7 +7512,7 @@ function rcube_webmail()
row = row.show().get(0);
if (row.scrollIntoView)
row.scrollIntoView();
row.scrollIntoView(false);
return row;
};

@ -648,9 +648,9 @@ class rcube
}
}
// specified domain
else if ($domain) {
else if ($domain && isset($this->texts[$domain.'.'.$name])) {
$ref_domain = $domain;
return isset($this->texts[$domain.'.'.$name]);
return true;
}
return false;
@ -710,6 +710,65 @@ class rcube
}
}
/**
* Read localized texts from an additional location (plugins, skins).
* Then you can use the result as 2nd arg to load_language().
*
* @param string $dir Directory to search in
*
* @return array Localization texts
*/
public function read_localization($dir)
{
$lang = $_SESSION['language'];
$langs = array_unique(array('en_US', $lang));
$locdir = slashify($dir);
$texts = array();
// Language aliases used to find localization in similar lang, see below
$aliases = array(
'de_CH' => 'de_DE',
'es_AR' => 'es_ES',
'fa_AF' => 'fa_IR',
'nl_BE' => 'nl_NL',
'pt_BR' => 'pt_PT',
'zh_CN' => 'zh_TW',
);
// use buffering to handle empty lines/spaces after closing PHP tag
ob_start();
foreach ($langs as $lng) {
$fpath = $locdir . $lng . '.inc';
if (is_file($fpath) && is_readable($fpath)) {
include $fpath;
$texts = (array) $labels + (array) $messages + (array) $texts;
}
else if ($lng != 'en_US') {
// Find localization in similar language (#1488401)
$alias = null;
if (!empty($aliases[$lng])) {
$alias = $aliases[$lng];
}
else if ($key = array_search($lng, $aliases)) {
$alias = $key;
}
if (!empty($alias)) {
$fpath = $locdir . $alias . '.inc';
if (is_file($fpath) && is_readable($fpath)) {
include $fpath;
$texts = (array) $labels + (array) $messages + (array) $texts;
}
}
}
}
ob_end_clean();
return $texts;
}
/**
* Check the given string and return a valid language code
*

@ -199,62 +199,19 @@ abstract class rcube_plugin
*/
public function add_texts($dir, $add2client = false)
{
$domain = $this->ID;
$lang = $_SESSION['language'];
$langs = array_unique(array('en_US', $lang));
$locdir = slashify(realpath(slashify($this->home) . $dir));
$texts = array();
// Language aliases used to find localization in similar lang, see below
$aliases = array(
'de_CH' => 'de_DE',
'es_AR' => 'es_ES',
'fa_AF' => 'fa_IR',
'nl_BE' => 'nl_NL',
'pt_BR' => 'pt_PT',
'zh_CN' => 'zh_TW',
);
// use buffering to handle empty lines/spaces after closing PHP tag
ob_start();
foreach ($langs as $lng) {
$fpath = $locdir . $lng . '.inc';
if (is_file($fpath) && is_readable($fpath)) {
include $fpath;
$texts = (array)$labels + (array)$messages + (array)$texts;
}
else if ($lng != 'en_US') {
// Find localization in similar language (#1488401)
$alias = null;
if (!empty($aliases[$lng])) {
$alias = $aliases[$lng];
}
else if ($key = array_search($lng, $aliases)) {
$alias = $key;
}
if (!empty($alias)) {
$fpath = $locdir . $alias . '.inc';
if (is_file($fpath) && is_readable($fpath)) {
include $fpath;
$texts = (array)$labels + (array)$messages + (array)$texts;
}
}
}
}
ob_end_clean();
$rcube = rcube::get_instance();
$texts = $rcube->read_localization(realpath(slashify($this->home) . $dir));
// prepend domain to text keys and add to the application texts repository
if (!empty($texts)) {
$add = array();
$domain = $this->ID;
$add = array();
foreach ($texts as $key => $value) {
$add[$domain.'.'.$key] = $value;
}
$rcube = rcube::get_instance();
$rcube->load_language($lang, $add);
$rcube->load_language($_SESSION['language'], $add);
// add labels to client
if ($add2client && method_exists($rcube->output, 'add_label')) {
@ -264,6 +221,7 @@ abstract class rcube_plugin
else {
$js_labels = array_keys($add);
}
$rcube->output->add_label($js_labels);
}
}

@ -32,7 +32,7 @@ $messages['erroroverquotadelete'] = 'No free disk space. Use SHIFT+DEL to delete
$messages['invalidrequest'] = 'Invalid request! No data was saved.';
$messages['invalidhost'] = 'Invalid server name.';
$messages['nomessagesfound'] = 'No messages found in this mailbox.';
$messages['loggedout'] = 'You have successfully terminated the session. Good bye!';
$messages['loggedout'] = 'You have successfully terminated the session. Goodbye!';
$messages['mailboxempty'] = 'Mailbox is empty';
$messages['nomessages'] = 'No messages';
$messages['refreshing'] = 'Refreshing...';

@ -34,7 +34,7 @@ $messages['erroroverquotadelete'] = 'No free disk space. Use SHIFT+DEL to delete
$messages['invalidrequest'] = 'Invalid request! No data was saved.';
$messages['invalidhost'] = 'Invalid server name.';
$messages['nomessagesfound'] = 'No messages found in this mailbox.';
$messages['loggedout'] = 'You have successfully terminated the session. Good bye!';
$messages['loggedout'] = 'You have successfully terminated the session. Goodbye!';
$messages['mailboxempty'] = 'Mailbox is empty';
$messages['nomessages'] = 'No messages';
$messages['refreshing'] = 'Refreshing...';

@ -36,7 +36,7 @@ $messages['erroroverquotadelete'] = 'No free disk space. Use SHIFT+DEL to delete
$messages['invalidrequest'] = 'Invalid request! No data was saved.';
$messages['invalidhost'] = 'Invalid server name.';
$messages['nomessagesfound'] = 'No messages found in this mailbox.';
$messages['loggedout'] = 'You have successfully terminated the session. Good bye!';
$messages['loggedout'] = 'You have successfully terminated the session. Goodbye!';
$messages['mailboxempty'] = 'Mailbox is empty';
$messages['nomessages'] = 'No messages';
$messages['refreshing'] = 'Refreshing...';
@ -85,6 +85,7 @@ $messages['deletecontactconfirm'] = 'Do you really want to delete selected cont
$messages['deletegroupconfirm'] = 'Do you really want to delete selected group?';
$messages['deletemessagesconfirm'] = 'Do you really want to delete selected message(s)?';
$messages['deletefolderconfirm'] = 'Do you really want to delete this folder?';
$messages['movefolderconfirm'] = 'Do you really want to move this folder?';
$messages['purgefolderconfirm'] = 'Do you really want to delete all messages in this folder?';
$messages['contactdeleting'] = 'Deleting contact(s)...';
$messages['groupdeleting'] = 'Deleting group...';

@ -567,7 +567,7 @@ function rcmail_contact_form($form, $record, $attrib = null)
foreach ($coltypes as $col => $prop) {
if ($prop['subtypes']) {
$subtype_names = array_map('rcmail_get_type_label', $prop['subtypes']);
$select_subtype = new html_select(array('name' => '_subtype_'.$col.'[]', 'class' => 'contactselectsubtype form-control', 'title' => $prop['label'] . ' ' . $RCMAIL->gettext('type')));
$select_subtype = new html_select(array('name' => '_subtype_'.$col.'[]', 'class' => 'contactselectsubtype custom-select', 'title' => $prop['label'] . ' ' . $RCMAIL->gettext('type')));
$select_subtype->add($subtype_names, $prop['subtypes']);
$coltypes[$col]['subtypes_select'] = $select_subtype->show();
}

@ -182,8 +182,9 @@ $OUTPUT->set_env('quota', (bool) $STORAGE->get_capability('QUOTA'));
$OUTPUT->include_script('treelist.js');
// add some labels to client
$OUTPUT->add_label('deletefolderconfirm', 'purgefolderconfirm', 'folderdeleting',
'foldermoving', 'foldersubscribing', 'folderunsubscribing', 'quota');
$OUTPUT->add_label('deletefolderconfirm', 'purgefolderconfirm', 'movefolderconfirm',
'folderdeleting', 'foldermoving', 'foldersubscribing', 'folderunsubscribing',
'move', 'quota');
// register UI objects
$OUTPUT->add_handlers(array(

@ -47,6 +47,7 @@
.popover {
box-shadow: 3px 3px 5px @color-popover-shadow;
border-color: @color-layout-border;
padding: 0;
.popover-header {
@ -67,7 +68,7 @@
html.layout-small,
html.layout-phone {
.popover {
.popover:not(.select-menu) {
margin: 0 !important;
padding: 0;
right: 0;
@ -148,6 +149,20 @@ html.touch .popover {
}
}
.select-menu {
max-width: initial;
margin: 0;
.listing li a {
padding-left: .25rem;
outline: 0; // for Android browser
}
.popover-header {
border-radius: .25rem .25rem 0 0 !important;
}
}
/** PGP Key search/import dialog **/
.pgpkeyimport {

@ -336,7 +336,7 @@ input.smart-upload {
.propform,
.formcontent {
fieldset:nth-of-type(n+2) {
fieldset:not(.tab-pane):nth-of-type(n+2) {
margin-top: 1em;
}

@ -112,9 +112,11 @@
span.secondary {
color: @color-list-secondary;
}
}
// Focus indicator
@media screen and (min-width: @screen-width-large) {
// Focus indicator
html:not(.touch) {
.listing {
li > a,
tbody tr > td:first-child,
&:not(.withselection) tbody tr > td.selection + td {

@ -34,7 +34,7 @@
<div class="input-group">
<roundcube:object name="composeHeaders" part="cc" id="_cc" form="form" tabindex="1" data-recipient-input="true" />
<span class="input-group-append">
<a href="#delete" onclick="$('#_cc').val('').change()" class="input-group-text icon cancel" title="<roundcube:label name='delete' />" tabindex="1"><span class="inner"><roundcube:label name="delete" /></span></a>
<a href="#delete" onclick="UI.header_reset('_cc')" class="input-group-text icon cancel" title="<roundcube:label name='delete' />" tabindex="1"><span class="inner"><roundcube:label name="delete" /></span></a>
</span>
</div>
</div>
@ -45,7 +45,7 @@
<div class="input-group">
<roundcube:object name="composeHeaders" part="bcc" id="_bcc" form="form" tabindex="1" data-recipient-input="true" />
<span class="input-group-append">
<a href="#delete" onclick="$('#_bcc').val('').change()" class="input-group-text icon cancel" title="<roundcube:label name='delete' />" tabindex="1"><span class="inner"><roundcube:label name="delete" /></span></a>
<a href="#delete" onclick="UI.header_reset('_bcc')" class="input-group-text icon cancel" title="<roundcube:label name='delete' />" tabindex="1"><span class="inner"><roundcube:label name="delete" /></span></a>
</span>
</div>
</div>

@ -62,14 +62,14 @@
<div class="form-group row">
<label for="compose-priority" class="col-form-label col-6"><roundcube:label name="priority" /></label>
<div class="col-6">
<roundcube:object name="prioritySelector" id="compose-priority" noform="true" tabindex="2" class="form-control" />
<roundcube:object name="prioritySelector" id="compose-priority" noform="true" tabindex="2" />
</div>
</div>
<roundcube:if condition="!config:no_save_sent_messages" />
<div class="form-group row">
<label for="compose-store-target" class="col-form-label col-6"><roundcube:label name="savesentmessagein" /></label>
<div class="col-6">
<roundcube:object name="storetarget" id="compose-store-target" noform="true" tabindex="2" class="form-control" />
<roundcube:object name="storetarget" id="compose-store-target" noform="true" tabindex="2" />
</div>
</div>
<roundcube:endif />
@ -78,7 +78,7 @@
<div class="form-group row hidden">
<label for="editor-selector" class="col-form-label col-6"><roundcube:label name="editortype" /></label>
<div class="col-6">
<roundcube:object name="editorSelector" id="editor-selector" editorid="composebody" noform="true" tabindex="2" class="form-control" />
<roundcube:object name="editorSelector" id="editor-selector" editorid="composebody" noform="true" tabindex="2" />
</div>
</div>
<roundcube:endif />
@ -181,7 +181,7 @@
<div class="input-group">
<roundcube:object name="composeHeaders" part="cc" id="_cc" form="form" tabindex="1" data-recipient-input="true" />
<span class="input-group-append">
<a href="#delete" onclick="$('#_cc').val('').change()" class="input-group-text icon cancel" title="<roundcube:label name='delete' />" tabindex="1"><span class="inner"><roundcube:label name="delete" /></span></a>
<a href="#delete" onclick="UI.header_reset('_cc')" class="input-group-text icon cancel" title="<roundcube:label name='delete' />" tabindex="1"><span class="inner"><roundcube:label name="delete" /></span></a>
</span>
</div>
</div>
@ -192,7 +192,7 @@
<div class="input-group">
<roundcube:object name="composeHeaders" part="bcc" id="_bcc" form="form" tabindex="1" data-recipient-input="true" />
<span class="input-group-append">
<a href="#delete" onclick="$('#_bcc').val('').change()" class="input-group-text icon cancel" title="<roundcube:label name='delete' />" tabindex="1"><span class="inner"><roundcube:label name="delete" /></span></a>
<a href="#delete" onclick="UI.header_reset('_bcc')" class="input-group-text icon cancel" title="<roundcube:label name='delete' />" tabindex="1"><span class="inner"><roundcube:label name="delete" /></span></a>
</span>
</div>
</div>
@ -203,7 +203,7 @@
<div class="input-group">
<roundcube:object name="composeHeaders" part="replyto" id="_replyto" form="form" tabindex="1" data-recipient-input="true" />
<span class="input-group-append">
<a href="#delete" onclick="$('#_replyto').val('').change()" class="input-group-text icon cancel" title="<roundcube:label name='delete' />" tabindex="1"><span class="inner"><roundcube:label name="delete" /></span></a>
<a href="#delete" onclick="UI.header_reset('_replyto')" class="input-group-text icon cancel" title="<roundcube:label name='delete' />" tabindex="1"><span class="inner"><roundcube:label name="delete" /></span></a>
</span>
</div>
</div>
@ -214,7 +214,7 @@
<div class="input-group">
<roundcube:object name="composeHeaders" part="followupto" id="_followupto" form="form" tabindex="1" data-recipient-input="true" />
<span class="input-group-append">
<a href="#delete" onclick="$('#_followupto').val('').change()" class="input-group-text icon cancel" title="<roundcube:label name='delete' />" tabindex="1"><span class="inner"><roundcube:label name="delete" /></span></a>
<a href="#delete" onclick="UI.header_reset('_followupto')" class="input-group-text icon cancel" title="<roundcube:label name='delete' />" tabindex="1"><span class="inner"><roundcube:label name="delete" /></span></a>
</span>
</div>
</div>

@ -165,7 +165,7 @@
<div class="form-group row">
<label for="listoptions-sortcol" class="col-form-label col-sm-4"><roundcube:label name="listsorting" /></label>
<div class="col-sm-8">
<select id="listoptions-sortcol" name="sort_col" class="form-control">
<select id="listoptions-sortcol" name="sort_col">
<option value=""><roundcube:label name="nonesort" /></option>
<option value="arrival"><roundcube:label name="arrival" /></option>
<option value="date"><roundcube:label name="sentdate" /></option>
@ -183,7 +183,7 @@
<div class="form-group row">
<label for="listoptions-sortord" class="col-form-label col-sm-4"><roundcube:label name="listorder" /></label>
<div class="col-sm-8">
<select id="listoptions-sortord" name="sort_ord" class="form-control">
<select id="listoptions-sortord" name="sort_ord">
<option value="ASC"><roundcube:label name="asc" /></option>
<option value="DESC"><roundcube:label name="desc" /></option>
</select>
@ -194,7 +194,7 @@
<div class="form-group row">
<label for="listoptions-threads" class="col-form-label col-sm-4"><roundcube:label name="lmode" /></label>
<div class="col-sm-8">
<select id="listoptions-threads" name="mode" class="form-control">
<select id="listoptions-threads" name="mode">
<option value="list"><roundcube:label name="list" /></option>
<option value="threads"><roundcube:label name="threads" /></option>
</select>

@ -63,6 +63,7 @@ function rcube_elastic_ui()
this.spellmenu = spellmenu;
this.searchmenu = searchmenu;
this.headersmenu = headersmenu;
this.header_reset = header_reset;
this.attachmentmenu = attachmentmenu;
this.mailtomenu = mailtomenu;
this.show_list = show_list;
@ -73,6 +74,7 @@ function rcube_elastic_ui()
this.switch_nav_list = switch_nav_list;
this.searchbar_init = searchbar_init;
this.pretty_checkbox = pretty_checkbox;
this.pretty_select = pretty_select;
// Initialize layout
@ -104,16 +106,21 @@ function rcube_elastic_ui()
{
var title, form, content_buttons = [];
// Intercept jQuery-UI dialogs to re-style them
if ($.ui) {
$.widget('ui.dialog', $.ui.dialog, {
open: function() {
this._super();
dialog_open(this);
return this;
}
});
}
// Intercept jQuery-UI dialogs...
$.ui && $.widget('ui.dialog', $.ui.dialog, {
open: function() {
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; });
@ -649,6 +656,7 @@ function rcube_elastic_ui()
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');
@ -687,9 +695,15 @@ function rcube_elastic_ui()
}
// Forms
var supported_controls = 'input:not(.button,[type=button],[type=file],[type=radio],[type=checkbox]),select,textarea';
var supported_controls = 'input:not(.button,[type=button],[type=file],[type=radio],[type=checkbox]),textarea';
$(supported_controls, $('.propform', context)).addClass('form-control');
$('[type=checkbox]', $('.propform', context)).addClass('form-check-input');
$('select', context).addClass('custom-select');
if (context != document) {
$(supported_controls, context).addClass('form-control');
}
$('table.propform', context).each(function() {
var text_rows = 0, form_rows = 0;
@ -850,11 +864,6 @@ function rcube_elastic_ui()
table.find('thead').addClass('thead-default');
});
$('.toolbarmenu select', context).addClass('form-control');
if (context != document) {
$(supported_controls, context).addClass('form-control');
}
// 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)
@ -896,12 +905,14 @@ function rcube_elastic_ui()
$(this).addClass('form-group row');
label.parent().css('display', 'none');
input.addClass('form-control')
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');
});
}
$('select:not([multiple])', context).each(function() { pretty_select(this); });
};
/**
@ -1514,15 +1525,8 @@ function rcube_elastic_ui()
alert_style(p.object, p.type, true);
$(p.object).attr('role', 'alert');
/*
$('a', p.object).addClass('alert-link');
// show a popup dialog on errors
if (p.type == 'error' && rcmail.env.task != 'login') {
// hide original message object, we don't want both
rcmail.hide_message(p.object);
}
*/
// $('a', p.object).addClass('alert-link');
};
/**
@ -2450,11 +2454,21 @@ function rcube_elastic_ui()
$(this)[$(target).is(':visible') ? 'removeClass' : 'addClass']('active')
.off().on('click', function() {
$(target).removeClass('hidden').find('.recipient-input > input').focus();
$(target).removeClass('hidden').find('.recipient-input input').focus();
});
});
};
/**
* 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();
};
/**
* Create/Update quota widget (setquota event handler)
*/
@ -2792,6 +2806,147 @@ function rcube_elastic_ui()
);
};
/**
* 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);
var close_func = function() {
var open = $('.select-menu').length;
select.popover('dispose').focus();
return !open;
};
var open_func = function(e) {
var items = [],
dialog = select.parents('.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 link = $('<a href="#">').text($(this).text())
.data('value', this.value)
.addClass(this.disabled ? 'disabled' : 'active' + (this.value == value ? ' selected' : ''));
items.push($('<li>').append(link));
});
var list = $('<ul class="toolbarmenu listing selectable iconized">')
.append(items)
.data('button', select[0]) // needed for dropdown closing code
.on('click', 'a.active', function() {
select.val($(this).data('value')).change();
return close_func();
})
.on('keydown', 'a.active', function(e) {
var item, node, mode = 'next';
// Close popup on ESC key
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;
}
}
break;
}
});
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 || 'body',
content: list[0],
placement: 'bottom',
trigger: 'manual',
boundary: 'viewport',
html: true,
offset: '0,2',
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
$('#' + select.attr('aria-describedby') + ' > .popover-header')
.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();
}
})
.popover('show');
};
select.on('mousedown keydown', function(e) {
// Do nothing on disabled select or on TAB key
if (select.prop('disabled') || e.which == 9) {
return;
}
// Close popup on ESC key
if (e.which == 27) {
return close_func();
}
// Close popup on click if already open
if (e.type == 'mousedown' && $('.select-menu:visible .listing').data('button') == select[0]) {
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;
});
};
/**
* HTML editor textarea wrapper with nice looking tabs-like switch
*/

Loading…
Cancel
Save