2024-05-25 15:44:05 +00:00
/ * *
* Roundcube webmail functions for the Elastic skin
*
* Copyright ( c ) 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 : 90 dc5c0be029de84e523b9b3922520e79e0e6f08 & dn = cc0 . txt CC0 - 1.0
* /
2024-06-13 21:17:39 +00:00
log = function ( ) { return Function . prototype . bind . call ( console . log , console ) ; } ( ) ;
warn = function ( ) { return Function . prototype . bind . call ( console . warn , console ) ; } ( ) ;
error = function ( ) { return Function . prototype . bind . call ( console . error , console ) ; } ( ) ;
2024-05-25 15:44:05 +00:00
"use strict" ;
function rcube _elastic _ui ( )
{
var prefs , ref = this ,
mode = 'normal' , // one of: large, normal, small, phone
color _mode = 'light' , // 'light' or 'dark'
touch = false ,
ios = false ,
popups _close _lock ,
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.task-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 . props _dialog = props _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;
//this.toggle_list_selection = toggle_list_selection;
this . get _screen _mode = get _screen _mode ;
this . screen _resize _headers = screen _resize _headers ;
this . is _mobile = is _mobile ;
this . is _touch = is _touch ;
// Detect screen size/mode
screen _mode ( ) ;
2024-06-18 22:10:06 +00:00
screen _resize _html ( ) //prevent ugly blinking on phone
2024-05-25 15:44:05 +00:00
// Initialize layout
layout _init ( ) ;
// Convert some elements to Bootstrap style
// bootstrap_style();
bootstrap _style _elastic2022 ( )
// 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 . menu . 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 = layout . content . find ( '.boxtitle' ) . first ( ) . detach ( ) . text ( ) ;
if ( ! title ) {
title = $ ( 'h1.voice' ) . first ( ) . text ( ) ;
}
if ( title ) {
layout . content . find ( '.header > .header-title' ) . 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 menu 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 ) ;
}
* /
/ *
console . log ( mode )
console . log ( UI )
console . log ( document . URL )
* /
//console.log(rcmail.env.action)
/ *
if ( rcmail . env . action == "preview" )
{
setTimeout ( function ( ) {
var mode = UI . get _screen _mode ( )
if ( mode == 'small' ) { $ ( '.sebmenu' , window . parent . document ) . hide ( ) ; }
else $ ( '.sebmenu' , window . parent . document ) . show ( ) ;
} , 1 ) ;
}
* /
// 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 ; } ) . find ( 'button' ) . each ( function ( ) {
var target = $ ( this ) ;
// skip non-content buttons
if ( ! is _framed && ! target . parents ( '#layout-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 switcher 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 ) ;
} ) ;
2024-10-03 11:21:07 +00:00
// Taskmenu items added by plugins do not use elastic classes (e.g help plugin)
setTimeout ( function ( ) { $ ( '.button-selected' ) . addClass ( 'selected' ) ; } , 50 ) ;
2024-05-25 15:44:05 +00:00
// Some plugins use 'listbutton' 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' ) ;
2024-09-14 10:05:07 +00:00
// issue https://github.com/seb1k/Elastic2022/issues/10
/ *
2024-05-25 15:44:05 +00:00
if ( ios = $ ( 'html' ) . is ( '.ipad,.iphone' ) ) {
$ ( '.iframe-wrapper, .scroller' ) . addClass ( 'ios-scroll' ) ;
}
2024-09-14 10:05:07 +00:00
* /
2024-05-25 15:44:05 +00:00
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 ) ; } ) ;
} ) ;
} ;
/ * *
* 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 = btn _class . replace ( 'btn-primary' , 'primary' ) . replace ( /(btn[a-z-]*|button|disabled)/g , '' ) . trim ( )
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 ( )
{
// Initialize light/dark mode
color _mode _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 ( 'destroy-entity-selector' , function ( o ) { menu _destroy ( o . name ) ; } )
. addEventListener ( 'clonerow' , pretty _checkbox _fix )
. addEventListener ( 'init' , init )
if ( layout . list . length || layout . content . length ) {
var fabuttons = [ ] ;
$ ( '[data-fab]' ) . each ( function ( ) {
var button = $ ( this ) ,
task = button . data ( 'fab-task' ) || '*' ,
action = button . data ( 'fab-action' ) || '*' ;
if ( ( task == '*' || task == rcmail . env . 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 ) ;
}
}
// Initialize column resizers (must be after floating buttons)
if ( layout . sidebar . length ) {
splitter _init ( layout . sidebar ) ;
}
if ( layout . list . length ) {
splitter _init ( layout . list ) ;
}
} ;
/ * *
* rcmail 'init' event handler
* /
function init ( )
{
// Additional functionality on list widgets
$ ( '[data-list]' ) . filter ( 'ul,table' ) . each ( function ( ) {
var table = $ ( this ) ,
list = table . data ( 'list' ) ;
if ( rcmail [ list ] && rcmail [ list ] . multiselect ) {
var repl , button ,
parent = table . parents ( 'layout-sidebar,#layout-list,#layout-content' ) . last ( ) ,
header = parent . find ( '.header' ) ,
toolbar = header . find ( 'ul' ) ;
if ( ! toolbar . length ) {
toolbar = header ;
}
else if ( button = toolbar . find ( 'a.select' ) . data ( 'toggle-button' ) ) {
button = $ ( '#' + button ) ;
}
// Enable checkbox selection on list widgets
rcmail [ list ] . enable _checkbox _selection ( ) ;
//if (get_pref('list-selection') === true) {
table . addClass ( 'withselection' ) ;
//}
// Add Select button to the list navigation bar
if ( ! button ) {
button = $ ( '<a>' ) . attr ( { 'class' : 'button selection disabled' , role : 'button' , title : rcmail . gettext ( 'select' ) } )
. on ( 'click' , function ( ) { UI . toggle _list _selection ( this , table . attr ( 'id' ) ) ; } )
. append ( $ ( '<span class="inner">' ) . text ( rcmail . gettext ( 'select' ) ) ) ;
if ( toolbar . is (
'.menu' ) ) {
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 ( '#layout-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 ) ;
}
}
if ( rcmail . task == "mail" )
2024-06-11 19:46:20 +00:00
{
2024-05-25 15:44:05 +00:00
listupdate _go _msgs ( )
2024-06-11 19:46:20 +00:00
if ( is _plugin _ident _switch _account ( ) )
init _plugin _ident _switch _account ( )
}
2024-05-25 15:44:05 +00:00
} ) ;
}
// 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 ;
}
// Also disable double-click to prevent from opening items
// in a new page, and prevent from zoom issues (#7732)
rcmail [ list ] . dblclick _time = 0 ;
}
} ) ;
// Display "List is empty..." on the list
if ( window . MutationObserver ) {
$ ( '[data-label-msg]' ) . filter ( 'ul,table' ) . each ( function ( ) {
var 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
var observer = new MutationObserver ( callback ) ;
observer . observe ( table [ 0 ] , { childList : true , subtree : true , attributes : true , attributeFilter : [ 'style' ] } ) ;
// initialize the message
callback ( ) ;
} ) ;
}
// 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' ) ;
}
}
} )
2024-06-13 21:17:39 +00:00
. addEventListener ( 'managesieve.insertrow' , function ( o ) { bootstrap _style _elastic2022 ( o . obj ) ; } )
2024-05-25 15:44:05 +00:00
. addEventListener ( 'add-recipient' , function ( ) { phone _confirmation ( 'recipientsadded' ) ; } ) ;
rcmail . init _pagejumper ( '.pagenav > input' ) ;
if ( rcmail . task == 'mail' ) {
if ( rcmail . env . action == 'compose' ) {
rcmail . addEventListener ( 'compose-encrypted' , function ( e ) {
$ ( "a.mode-html, button.attach" ) . prop ( 'disabled' , e . active ) ;
$ ( 'a.attach, a.responses:not(.edit)' ) [ e . active ? 'addClass' : 'removeClass' ] ( 'disabled' ) ;
} ) ;
$ ( '#layout-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 ( ) ;
}
* /
2024-06-13 21:17:39 +00:00
if ( $ ( 'a.button.enigma' ) . length ) //enigma plugin
{
$ ( 'a.button.enigma' ) . remove ( )
$ ( '#enigmamenu div.form-group' ) . each ( function ( ) {
$ ( '#compose-options' ) . prepend ( this )
} ) ;
}
2024-06-18 22:10:06 +00:00
//From button
var fromButton = rcmail . get _cookie ( 'fromButton' ) ;
if ( fromButton == null ) // if no pref in cookie
{
fromButton = true ;
if ( _from . options . length == 1 ) //by default, show nothing if there is only 1 address
fromButton = "false" ;
}
if ( fromButton == "false" )
$ ( '#compose_from' ) . hide ( ) ;
else
$ ( '#fromButton' ) . prop ( "checked" , true ) ;
2024-05-25 15:44:05 +00:00
}
2024-06-18 22:10:06 +00:00
$ ( '#fromButton' ) . prop ( "disabled" , false ) ;
2024-05-25 15:44:05 +00:00
// In compose/preview 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 && ( rcmail . env . action == 'compose' || rcmail . env . action == 'show' ) ) {
$ ( 'a.mail' , layout . menu ) . attr ( {
'aria-disabled' : false ,
onclick : "return rcmail.command('list','',this,event);"
} ) ;
}
// 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 ) ;
} ) ;
// restore headers view to last state
//headers_show();
2024-06-18 22:10:06 +00:00
elastic2022 _change _mailheader ( )
2024-09-30 23:52:05 +00:00
elastic2022 _change _attachmentslist ( )
2024-05-25 15:44:05 +00:00
}
}
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);
// });
}
2024-06-13 21:17:39 +00:00
else if ( rcmail . env . task == 'login' ) {
2024-09-30 23:52:05 +00:00
// plugin persistent login
setTimeout ( function ( ) {
if ( ! $ ( '#ifplcontainer' ) . length ) return ;
ifplcontainer . remove ( ) // remove ugly previous one
// and recreate it
var html = `
< tr class = "form-group row" >
< td class = "title" style = "display: none;" >
< label for = "rcmloginuser" > Username < / l a b e l >
< / t d >
< td class = "input input-group input-group-lg" >
< div class = "custom-control custom-switch" >
< input type = "checkbox" class = "custom-control-input" id = "_ifpl" name = "_ifpl" value = "1" >
< label class = "custom-control-label" for = "_ifpl" > ` + rcmail.gettext('ifpl_rememberme', 'persistent_login') + ` < / l a b e l >
< / d i v >
< / t d >
< / t r >
< tr id = "ifpl-hint" class = "form-group row" style = "display: none;" >
< td class = "ifpl-hint" colspan = "2" > ` + rcmail.gettext('ifpl_rememberme_hint', 'persistent_login') + ` < / t d >
< / t r >
2024-06-13 21:17:39 +00:00
`
$ ( '#login-form table tbody' ) . append ( html ) ;
} )
}
2024-05-25 15:44:05 +00:00
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 ( ) ;
// Re-focus the focused input field on mail compose
if ( rcmail . env . compose _focus _elem ) {
$ ( rcmail . env . compose _focus _elem ) . focus ( ) ;
}
} ) ;
}
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 ) ;
}
} ;
/ * *
* Initializes light / dark mode
* /
function color _mode _init ( )
{
if ( rcmail . env . action == 'print' ) {
return ;
}
// We deliberately use only cookies here, not local storage
2024-06-10 16:11:53 +00:00
var pref = rcmail . get _cookie ( 'colorMode' ) ,
color _scheme = window . matchMedia ( '(prefers-color-scheme: dark)' ) ,
2024-05-25 15:44:05 +00:00
reset _cookie = function ( ) {
rcmail . set _cookie ( 'colorMode' , '' , new Date ( ) ) ; // delete the cookie
} ,
switch _iframe _color _mode = function ( ) {
try {
$ ( this . contentWindow . document ) . find ( 'html' ) [ color _mode == 'dark' ? 'addClass' : 'removeClass' ] ( 'dark-mode' ) ;
}
catch ( e ) { /* ignore */ }
} ,
switch _color _mode = function ( ) {
if ( color _mode == 'dark' ) {
$ ( '#taskmenu a.theme' ) . removeClass ( 'dark' ) . addClass ( 'light' ) . find ( 'span' ) . text ( rcmail . gettext ( 'lightmode' ) ) ;
$ ( '.click_change_theme' ) . removeClass ( 'dark' ) . addClass ( 'light' ) . find ( 'span' )
$ ( '.sebicon_contrast_txt' ) . text ( rcmail . gettext ( 'lightmode' ) ) ;
$ ( 'html' ) . addClass ( 'dark-mode' ) ;
2024-06-18 22:10:06 +00:00
$ ( 'meta[name="theme-color"]' ) . attr ( 'content' , '#1A1C1E' )
2024-05-25 15:44:05 +00:00
}
else {
$ ( '#taskmenu a.theme' ) . removeClass ( 'light' ) . addClass ( 'dark' ) . find ( 'span' ) . text ( rcmail . gettext ( 'darkmode' ) ) ;
$ ( '.click_change_theme' ) . removeClass ( 'light' ) . addClass ( 'dark' ) . find ( 'span' )
$ ( '.sebicon_contrast_txt' ) . text ( rcmail . gettext ( 'darkmode' ) ) ;
$ ( 'html' ) . removeClass ( 'dark-mode' ) ;
2024-06-18 22:10:06 +00:00
$ ( 'meta[name="theme-color"]' ) . attr ( 'content' , '#ffffff' )
2024-05-25 15:44:05 +00:00
}
2024-06-18 22:10:06 +00:00
2024-05-25 15:44:05 +00:00
screen _logo ( mode ) ;
$ ( 'iframe' ) . each ( switch _iframe _color _mode ) ;
} ;
if ( rcmail . env . dark _mode _support === false ) {
if ( pref == 'dark' ) {
reset _cookie ( ) ;
$ ( 'iframe' ) . each ( switch _iframe _color _mode ) ;
}
return ;
}
// Add onclick action to the menu button
$ ( '#taskmenu a.theme' ) . on ( 'click' , function ( ) {
color _mode = $ ( this ) . is ( '.dark' ) ? 'dark' : 'light' ;
switch _color _mode ( ) ;
rcmail . set _cookie ( 'colorMode' , color _mode , false ) ;
} ) ;
$ ( '.click_change_theme' ) . on ( 'click' , function ( ) {
color _mode = $ ( this ) . is ( '.dark' ) ? 'dark' : 'light' ;
switch _color _mode ( ) ;
rcmail . set _cookie ( 'colorMode' , color _mode , false ) ;
} ) ;
// Note: this does not work in IE and Safari
color _scheme . addListener ( function ( e ) {
color _mode = e . matches ? 'dark' : 'light' ;
switch _color _mode ( ) ;
reset _cookie ( ) ;
} ) ;
if ( pref ) {
color _mode = pref ;
}
else if ( color _scheme . matches ) {
color _mode = 'dark' ;
}
switch _color _mode ( ) ;
$ ( 'iframe' ) . on ( 'load' , switch _iframe _color _mode ) ;
} ;
/ * *
* Apply bootstrap classes to html elements
* /
function bootstrap _style _elastic2022 ( 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' ) ;
var supported _controls = 'input:not(.button,.no-bs,[type=button],[type=radio],[type=checkbox],[type=file]),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' ) ;
2024-06-13 21:17:39 +00:00
// $('[type=checkbox]', $('.propform', context)).addClass('form-check-input');
2024-05-25 15:44:05 +00:00
}
// 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[type=checkbox], .popupmenu.form input[type=checkbox], .menu 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 ) ; } ) ;
}
// 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 = String ( $ ( 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' ) ;
} ) ;
2024-06-13 21:17:39 +00:00
$ ( 'td.rowbuttons > a' , context ) . addClass ( 'btn' ) ;
// Make tables prettier
$ ( 'table:not(.table,.compact-table,.propform,.listing,.ui-datepicker-calendar)' , context )
. filter ( function ( ) {
// exclude direct propform children and external content
return ! $ ( this ) . parent ( ) . is ( '.propform' )
&& ! $ ( this ) . parents ( '#message-header,.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' ) ;
} ) ;
// Make logon form prettier
if ( rcmail . env . task == 'login' && context == document ) {
$ ( '#rcmloginsubmit' ) . addClass ( 'btn-lg text-uppercase w-100' ) ;
$ ( '#rcmloginoauth' ) . addClass ( 'btn btn-secondary btn-lg 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' ) ;
} ) ;
}
2024-05-25 15:44:05 +00:00
}
function bootstrap _style ( context )
{
console . log ( 'func bootstrap_style used' ) // all deactivated for elastic2022
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=radio],[type=checkbox],[type=file]),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 ;
var col _sizes = [ 'sm' , 4 , 8 ] ;
if ( $ ( this ) . attr ( 'class' ) . match ( /cols-([a-z]+)-(\d)-(\d)/ ) ) {
col _sizes = [ RegExp . $1 , RegExp . $2 , RegExp . $3 ] ;
}
$ ( this ) . find ( '> tbody > tr, > 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-' + col _sizes [ 0 ] + '-' + col _sizes [ 1 ] ) ;
last . addClass ( 'col-' + col _sizes [ 0 ] + '-' + col _sizes [ 2 ] ) ;
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 ( ) . slice ( 1 ) . addClass ( 'input-group-append' ) ;
} ) ;
// Other forms, e.g. Contact advanced search
$ ( 'fieldset.propform:not(.grouped) 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.grouped 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' ) ;
} ) ;
} ) ;
// Advanced options form
$ ( 'fieldset.advanced' , context ) . each ( function ( ) {
var table = $ ( this ) . children ( '.propform' ) . first ( ) ;
table . wrap ( $ ( '<div>' ) . addClass ( 'collapse' ) ) ;
$ ( this ) . children ( 'legend' ) . first ( ) . addClass ( 'closed' ) . on ( 'click' , function ( ) {
table . parent ( ) . collapse ( 'toggle' ) ;
$ ( this ) . toggleClass ( 'closed' ) ;
} ) ;
} ) ;
// 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' , fieldset ) . first ( ) . 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' , fieldset ) . first ( ) . hide ( ) ;
tabs . push ( tab ) ;
} ) ;
// create the navigation bar
nav . append ( tabs ) . insertBefore ( item ) ;
// activate the first tab
$ ( 'a.nav-link' , nav ) . first ( ) . click ( ) ;
} ) ;
$ ( 'input[type=file]:not(.custom-file-input)' , context ) . each ( function ( ) {
var label _text = rcmail . gettext ( 'choosefile' + ( this . multiple ? 's' : '' ) ) ,
label = $ ( '<label>' ) . attr ( { 'class' : 'custom-file-label' ,
'data-browse' : rcmail . gettext ( 'browse' ) } ) . text ( label _text ) ;
$ ( this ) . addClass ( 'custom-file-input' ) . wrap ( '<div class="custom-file">' ) ;
$ ( this ) . on ( 'change' , function ( ) {
var text = label _text ;
if ( this . files . length ) {
text = this . files [ 0 ] . name ;
if ( this . files . length > 1 ) {
text += ', ...' ;
}
}
// Note: We don't use label variable to allow cloning of the input
$ ( this ) . next ( ) . text ( text ) ;
} )
. parent ( ) . append ( label ) ;
} ) ;
// Make tables prettier
$ ( 'table:not(.table,.compact-table,.propform,.listing,.ui-datepicker-calendar)' , context )
. filter ( function ( ) {
// exclude direct propform children and external content
return ! $ ( this ) . parent ( ) . is ( '.propform' )
&& ! $ ( this ) . parents ( '#message-header,.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[type=checkbox], .popupmenu.form input[type=checkbox], .menu 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' , context ) . first ( ) . 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 = String ( $ ( 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' ) ;
$ ( '#rcmloginoauth' ) . addClass ( 'btn btn-secondary btn-lg 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 ) ; } ) ;
} ;
/ * *
* 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 ( )
{
if ( ! layout . list . length ) {
return ;
}
var last _selected = env . last _selected ,
title _reset = function ( title ) {
if ( typeof title !== 'string' || ! title . length ) {
title = $ ( 'h1.voice' ) . text ( ) || $ ( 'title' ) . text ( ) || '' ;
}
layout . content . find ( '.header > .header-title' ) . text ( title ) ;
} ;
// display or reset the content frame
var common _content _handler = function ( e , href , show , title )
{
if ( is _mobile ( ) && env . frame _nav ) {
content _frame _navigation ( href , e ) ;
}
if ( show && ! layout . content . is ( ':visible' ) ) {
env . last _selected = layout . content [ 0 ] ;
}
else if ( ! show && env . last _selected != last _selected && ! env . content _lock ) {
env . last _selected = last _selected ;
}
screen _resize ( ) ;
title _reset ( title && show ? title : null ) ;
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 win , href = '' , show = true ;
// Reset the scroll position of the iframe-wrapper
$ ( this ) . parent ( '.iframe-wrapper' ) . scrollTop ( 0 ) ;
try {
win = e . target . contentWindow ;
href = win . location . href ;
show = ! href . endsWith ( rcmail . env . blankpage ) ;
// Reset title back to the default
$ ( win ) . 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 && ! $ ( 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 )
{
var onload = [ ] ,
is _editor = $ ( '#' + o . id ) . parent ( ) . is ( '.html-editor' ) ;
// Enable autoresize plugin
o . config . plugins += ' autoresize' ;
if ( is _touch ( ) ) {
// Use minimalistic toolbar
o . config . toolbar = 'undo redo | link image styleselect' ;
}
if ( rcmail . task == 'mail' && rcmail . env . action == 'compose' ) {
var floating = false ,
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
onload . push ( 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 = $ ( '.tox-editor-container' , form ) ,
toolbar = container . find ( '.tox-toolbar-overlord' ) ,
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' } ) ;
floating = true ;
}
else {
// Focusing the subject when scrolling back to the top fixes
// an annoying bouncing scrollbar bug (#8046)
if ( floating ) {
$ ( '#compose-subject' ) . focus ( ) ;
floating = false ;
}
toolbar . css ( { position : 'relative' , top : 0 , width : 'auto' } )
}
} ) ;
$ ( window ) . resize ( function ( ) { form . trigger ( 'scroll' ) ; } ) ;
}
if ( is _editor ) {
o . config . toolbar = 'plaintext | ' + o . config . toolbar ;
// Use setup_callback, we can't use editor-load event
o . config . setup _callback = function ( ed ) {
ed . ui . registry . addButton ( 'plaintext' , {
tooltip : rcmail . gettext ( 'plaintoggle' ) ,
icon : 'close' ,
onAction : function ( e ) {
if ( rcmail . command ( 'toggle-editor' , { id : ed . id , html : false } , '' , e . originalEvent ) ) {
$ ( '#' + ed . id ) . parent ( ) . removeClass ( 'ishtml' ) ;
}
}
} ) ;
} ;
}
// Add styling for TinyMCE dialogs
onload . push ( function ( ed ) {
ed . on ( 'OpenWindow' , function ( e ) {
var dialog = $ ( '.tox-dialog:last' ) [ 0 ] ,
callback = function ( e ) {
var body = $ ( dialog ) . find ( '.tox-dialog__body' ) ,
foot = $ ( dialog ) . find ( '.tox-dialog__footer' ) ,
buttons = foot . find ( 'button' ) ;
if ( ! e ) {
// Fix icons in Find and Replace dialog footer
if ( buttons . length === 4 ) {
body . closest ( '.tox-dialog' ) . addClass ( 'tox-search-dialog' ) ;
}
// Switch Save and Cancel buttons order
else if ( buttons . length == 2 ) {
buttons . first ( ) . insertAfter ( buttons [ 1 ] ) ;
}
// TODO: Styling form elements does not work well because of
// https://github.com/tinymce/tinymce/issues/4867
// also https://github.com/tinymce/tinymce/issues/4869
}
body . find ( '.tox-checkbox > input' ) . each ( function ( ) { pretty _checkbox ( this ) ; } ) ;
body . find ( '.tox-textarea,.tox-textfield' ) . addClass ( 'form-control' ) ;
} ;
// TODO: Maybe some day we'll not have to use MutationObserver
// https://github.com/tinymce/tinymce/issues/4869
if ( window . MutationObserver ) {
( new MutationObserver ( callback ) ) . observe ( $ ( '.tox-dialog__body-content' , dialog ) [ 0 ] , { childList : true } ) ;
}
callback ( ) ;
} ) ;
} ) ;
rcmail . addEventListener ( 'editor-load' , function ( e ) {
$ . each ( onload , function ( ) { this ( e . ref . editor ) ; } ) ;
} ) ;
} ;
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' ] } ) ;
} ) ;
}
} ;
/ *
function toggle _list _selection ( obj , list _id )
{
if ( $ ( obj ) . is ( '.active' ) ) {
set _pref (
'list-selection' ,
$ ( '#' + list _id ) . toggleClass ( 'withselection' ) . is ( '.withselection' )
) ;
}
} ;
* /
/ * *
* Handler for some Roundcube core popups
* /
function rcmail _popup _init ( o )
{
// Add some common styling to the autocomplete/googiespell popups
$ ( 'ul' , o . obj ) . addClass ( 'menu listing iconized' ) ;
$ ( o . obj ) . addClass ( 'popupmenu popover' ) ;
//bootstrap_style(o.obj);
bootstrap _style _elastic2022 ( 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 ( ) ; } ) ;
}
$ ( 'ul,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' ) ;
$ ( '.toolbar a.reply-all' ) . attr ( 'title' , label ) . find ( '.inner' ) . text ( label ) ;
}
break ;
case 'compose-encrypted' :
// show the toolbar button for Mailvelope
$ ( '.toolbar a.encrypt' ) . parent ( ) . show ( ) ;
break ;
case 'compose-encrypted-signed' :
// enable selector for encrypt and sign
$ ( '#encryption-menu-button' ) . show ( ) ;
break ;
}
}
} ;
/ * *
* screen mode
* /
function screen _mode ( ) // elastic2022, only : small, normal, large
{
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' ;
* /
if ( width > 1200 )
size = 'large' ;
else if ( width > 768 )
size = 'normal' ;
else
size = 'small' ;
touch = width <= 1024 ;
mode = size ;
} ;
/ * *
* Get current screen mode
* /
function get _screen _mode ( )
{
return mode ;
} ;
/ * *
* 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' ] ( ) ; } ) ;
rcmail . triggerEvent ( 'skin-resize' , { mode : mode } )
} ;
function screen _resize ( )
{
if ( is _framed && ! layout . sidebar . length && ! layout . list . length ) {
screen _resize _headers ( ) ;
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 _logo ( mode ) ;
screen _resize _headers ( ) ;
// On iOS and Android the content frame height is never correct, fix it.
// Actually I observed the issue on my old iPad with iOS 9.3.
if ( bw . webkit && bw . ipad && bw . agent . match ( /OS 9/ ) ) {
$ ( '.iframe-wrapper' ) . each ( function ( ) {
var h = $ ( this ) . height ( ) ;
if ( h ) {
$ ( this ) . children ( 'iframe' ) . height ( h ) ;
}
} ) ;
}
if ( document . getElementById ( 'messagecontframe' ) )
{
messagecontframe . postMessage ( 'resize_message' , '*' ) ;
}
} ;
/ * *
* 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 _logo ( mode )
{
var logos = rcmail . env . additional _logos ;
if ( logos ) {
// Store default logo path if not already set
if ( ! $ ( '#logo' ) . data ( 'src-default' ) ) {
$ ( '#logo' ) . data ( 'src-default' , $ ( '#logo' ) . attr ( 'src' ) ) ;
}
if ( mode == 'phone' && color _mode == 'dark' && logos [ 'small-dark' ] ) {
$ ( '#logo' ) . attr ( 'src' , logos [ 'small-dark' ] ) ;
}
else if ( mode == 'phone' && logos [ 'small' ] ) {
$ ( '#logo' ) . attr ( 'src' , logos [ 'small' ] ) ;
}
else if ( color _mode == 'dark' && logos [ 'dark' ] ) {
$ ( '#logo' ) . attr ( 'src' , logos [ 'dark' ] ) ;
}
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:not(.position-absolute)' ) . 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 ) ;
if ( layout . list . length ) {
$ ( '.header > ul.menu' , layout . list ) . addClass ( 'popupmenu' ) ;
}
} ;
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 ( ) ;
} ;
function screen _resize _large ( )
{
$ . each ( layout , function ( name , item ) { item . removeClass ( 'hidden' ) ; } ) ;
screen _resize _small _none ( ) ;
if ( layout . list ) {
$ ( '.header > ul.menu.popupmenu' , layout . list ) . removeClass ( 'popupmenu' ) ;
}
} ;
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.menu' , 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.menu' , 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 ( '#layout-sidebar' ) . length == 0 ; } ) . hide ( ) ;
$ ( 'ul.menu.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');
}
screen _resize _headers ( ) ;
// env.last_selected = layout.sidebar[0];
click _menu ( )
} ;
/ *
function show _sidebar ( sticky )
{
click _menu ( )
// 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 ( )
{
//$('.sebmenu').show();
// 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);
bootstrap _style _elastic2022 ( dialog . uiDialog ) ;
} ;
/ * *
* Initializes searchbar widget
* /
function searchbar _init ( bar )
{
var unread _button = $ ( ) ,
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 ;
}
} ,
close _func = function ( ) {
if ( $ ( bar ) . is ( '.open' ) ) {
options _button . click ( ) ;
}
} ,
update _func = function ( ) {
$ ( bar ) [ is _search _pending ( ) ? 'addClass' : 'removeClass' ] ( 'active' ) ;
unread _button [ rcmail . gui _objects . search _filter && $ ( rcmail . gui _objects . search _filter ) . val ( ) == 'UNSEEN' ? 'addClass' : 'removeClass' ] ( 'selected' ) ;
} ;
// Add Unread filter button
if ( input . is ( '#mailsearchform' ) ) {
unread _button = $ ( '<a>' )
. attr ( { 'class' : 'button unread' , href : '#' , role : 'button' , title : rcmail . gettext ( 'showunread' ) } )
. on ( 'click' , function ( e ) {
$ ( rcmail . gui _objects . search _filter ) . val ( $ ( e . target ) . is ( '.selected' ) ? 'ALL' : 'UNSEEN' ) ;
rcmail . command ( 'search' ) ;
} )
. insertBefore ( options _button ) ;
}
options _button . on ( 'click' , function ( e ) {
var id = $ ( this ) . data ( 'target' ) ,
options = $ ( '#' + id ) ,
open = $ ( bar ) . is ( '.open' ) ;
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 . 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' , update _func )
. addEventListener ( 'responsebeforesearch' , update _func )
. addEventListener ( 'beforelist' , close _func )
. addEventListener ( 'afterlist' , update _func )
. addEventListener ( 'beforesearch' , close _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 && this . nodeValue . trim ( ) . length == 0 ) $ ( this ) . remove ( ) ; } ) ;
if ( button . is ( '.spacer' ) ) {
item . addClass ( 'spacer' ) ;
}
else {
item . append ( button ) ;
}
items . push ( item ) ;
} ;
// convert content toolbar to a popup list
layout . content . find ( '.header > .menu' ) . each ( function ( ) {
var toolbar = $ ( this ) ;
toolbar . children ( ) . each ( function ( ) { button _func ( this , items ) ; } ) ;
toolbar . remove ( ) ;
} ) ;
// convert list toolbar to a popup list
layout . list . find ( '.header > .menu' ) . 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' : 'menu 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' : 'menu 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 ) ;
// bind toolbar menu with the menu button in the list header
layout . list . find ( 'a.toolbar-menu-button' ) . 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' ) . 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 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.
. 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 )
{
// Ignore some of propagated click events (see pretty_select())
if ( popups _close _lock && popups _close _lock > ( new Date ( ) . getTime ( ) - 250 ) ) {
return ;
}
$ ( '.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' , p . obj ) . first ( ) ,
target = p . props && p . props . link ? p . props . link : p . originalEvent . target ;
// Sanity check, make sure we have some content to show
if ( ! content . length ) {
return ;
}
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 ( 'menu' ) ) {
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 ( {
event : rcube _event . is _keyboard ( p . originalEvent ) ? 'key' : 'mouse' ,
popup : p . name ,
'popup-pos' : pos ,
'popup-trigger' : 'manual'
} ) ;
popup _init ( target , p . win ) ;
}
menus [ p . name ] = { target : target } ;
// setTimeout fixes Shift + drag'n'drop menu in Chrome (#8107)
setTimeout ( function ( ) { $ ( target ) . popover ( 'show' ) ; } , 1 ) ;
}
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 ( toggle )
{
var key = 'mail.show.envelope' ,
pref = get _pref ( key ) ,
show = toggle ? ! pref : pref ,
mode = show ? 'summary' : 'details' ,
headers = $ ( 'div.header-content' ) ;
$ ( 'div.header-links' ) . find ( 'a.headers-details,a.headers-summary' )
. removeClass ( ) . addClass ( 'headers-' + mode ) . text ( rcmail . gettext ( mode ) ) ;
headers [ show ? 'addClass' : 'removeClass' ] ( 'details-view' ) ;
if ( toggle ) {
// save new pref
set _pref ( key , show ) ;
}
} ;
* /
/ * *
* Mail headers dialog
* /
function headers _dialog ( )
{
// if not the message, open this on message iframe
if ( document . getElementById ( 'messagecontframe' ) )
{
messagecontframe . postMessage ( 'headers_dialog' , '*' ) ;
return ;
}
var props = { _uid : rcmail . env . uid , _mbox : rcmail . env . mailbox , _framed : 1 } ,
dialog = $ ( '<iframe>' ) . attr ( { id : 'headersframe' , src : rcmail . url ( 'headers' , props ) } ) ;
var l = rcmail . simple _dialog ( dialog , 'Headers' , null , {
cancel _button : 'close' ,
height : 600 ,
} ) ;
return true ;
} ;
/ * *
* Attachment properties dialog
* /
function props _dialog ( )
{
var dialog = $ ( '#properties-menu' ) . clone ( ) ;
rcmail . simple _dialog ( dialog , rcmail . gettext ( 'properties' ) , null , {
cancel _button : 'close' ,
height : 400
} ) ;
} ;
/ * *
* Mail import dialog
* /
function import _dialog ( )
{
if ( ! rcmail . commands [ 'import-messages' ] ) {
return ;
}
var content = $ ( '#uploadform' ) ,
dialog = content . clone ( true ) ;
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 ) ,
interval _select = $ ( '#s_interval' , 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 ( ) {
rcmail . env . search _scope = scope _select . val ( ) ;
rcmail . env . search _interval = interval _select . val ( ) ;
} ) ;
}
}
scope _select . val ( scope ) ;
if ( mods ) {
if ( rcmail . env . task == 'mail' ) {
mods = mods [ mbox ] || mods [ '*' ] ;
all = 'text' ;
}
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 ;
if ( task == 'mail' ) {
if ( ! mods [ mbox ] ) {
mods [ mbox ] = rcube _clone _object ( mods [ '*' ] ) ;
}
m = mods [ mbox ] ;
all = 'text' ;
}
else {
// addressbook
m = mods ;
all = '*' ;
}
if ( ! elem . checked ) {
delete ( m [ elem . value ] ) ;
}
else {
m [ elem . value ] = 1 ;
}
// mark all fields
if ( elem . value == all ) {
$ ( 'input[name="s_mods[]"]' , menu ) . not ( elem ) . map ( function ( ) {
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="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 ) {
return rcmail . command ( action + '-attachment' , id , this , e . originalEvent ) ;
} ) ;
} ) ;
// call menu-open so core can set state of menu commands
return 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 ( '.dropdown' ) . length ) {
var label = rcmail . gettext ( 'options' ) ,
fname = item . find ( 'a.filename' ) ;
var button = $ ( '<a>' ) . attr ( {
href : '#' ,
tabindex : fname . attr ( 'tabindex' ) || 0 ,
title : label ,
'class' : 'button icon dropdown skip-content'
} )
. on ( 'click' , function ( e ) {
return attachmentmenu ( $ ( '#attachmentmenu' ) , button , e ) ;
} )
. append ( $ ( '<span>' ) . attr ( 'class' , 'inner' ) . text ( label ) ) ;
if ( fname . length ) {
button . insertAfter ( fname ) ;
}
else {
button . appendTo ( item ) ;
}
}
} ;
/ * *
* Mailto menu
* /
function mailtomenu ( obj , button , event , onclick )
{
var mailto = $ ( button ) . attr ( 'href' ) . replace ( /^mailto:/ , '' ) ;
if ( mailto . indexOf ( '@' ) < 0 ) {
return true ; // let the browser handle this
}
// disable all menu actions
obj . find ( 'a' ) . off ( 'click' ) . removeClass ( 'active' ) ;
if ( rcmail . env . has _writeable _addressbook ) {
$ ( '.addressbook' , obj ) . addClass ( 'active' )
. 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 = '"' + txt . trim ( ) + '" <' + contact + '>' ;
}
return rcmail . command ( 'add-contact' , contact , this , e . originalEvent ) ;
} ) ;
}
$ ( '.compose' , obj ) . addClass ( 'active' ) . on ( 'click' , function ( e ) {
// Execute the original onclick handler to support mailto URL arguments (#6751)
if ( onclick ) {
button . onclick = onclick ;
// use the second argument to tell our handler to not display the menu again
$ ( button ) . trigger ( 'click' , [ true ] ) ;
button . onclick = null ;
}
else {
rcmail . command ( 'compose' , mailto , this , e . originalEvent ) ;
}
return false ; // for Chrome
} ) ;
return rcmail . command ( 'menu-open' , { menu : 'mailto-menu' , link : button } , button , event . originalEvent ) ;
} ;
/ * *
* Appends popup menu to mailto links
* /
function mailtomenu _append ( item )
{
// Remember the original onclick handler and display the menu instead
var onclick = item . onclick ;
item . onclick = null ;
$ ( item ) . on ( 'click' , function ( e , menu ) {
return menu || mailtomenu ( $ ( '#mailto-menu' ) , item , e , onclick ) ;
} ) ;
} ;
/ * *
* 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' ) ;
// set title and reset tooltip's data (needed in case of empty title)
element . attr ( { 'data-original-title' : '' , 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 , selection = '' ,
apply _func = function ( ) {
// update the original input
$ ( obj ) . val ( list . text ( ) + input . val ( ) ) ;
} ,
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 ( ) ) ;
apply _func ( ) ;
} ,
update _func = function ( text ) {
var result ;
text = ( text || input . val ( ) ) . replace ( /[,;\s]+$/ , '' ) ;
result = recipient _input _parser ( text ) ;
$ . each ( result . recipients , function ( ) {
insert _recipient ( this . name , this . email ) ;
} ) ;
input . val ( result . text ) ;
apply _func ( ) ;
return result . recipients . length > 0 ;
} ,
parse _func = function ( e , ac , trigger ) {
var last , paste , value = this . value ;
// #8098: ignore changes when autocomplete_insert is not triggered
if ( trigger === false ) {
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)
if ( e . type == 'paste' ) {
// pasted text
paste = ( e . originalEvent . clipboardData || window . clipboardData ) . getData ( 'text' ) || '' ;
// insert pasted text in place of the selection (or just cursor position)
value = value . substring ( 0 , this . selectionStart ) + paste + value . substring ( this . selectionEnd ) ;
e . preventDefault ( ) ;
}
// #7231: When clicking on autocompletion list a change event
// is fired twice. We have to remove last recipient box if it is
// the same recipient (with incomplete email address).
// FIXME: Anyone with a better solution?
else if ( ac ) {
last = list . find ( 'li.recipient' ) . last ( ) ;
if ( last . length && this . value . indexOf ( last . text ( ) . replace ( /[ ,]+$/ , '' ) ) > - 1 ) {
last . remove ( ) ;
}
}
update _func ( value ) ;
} ,
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 (,;\s) or Enter was pressed,
else if ( e . key == ' ' || e . key == ',' || e . key == ';' || ( e . key == 'Enter' && ! rcmail . ksearch _visible ( ) ) ) {
if ( update _func ( ) ) {
return false ;
}
}
} ;
// Create the input element and "editable" area
input = $ ( '<input>' ) . attr ( { type : 'text' , tabindex : $ ( obj ) . attr ( 'tabindex' ) } )
. on ( 'paste change' , parse _func )
. on ( 'keydown' , keydown _func )
. on ( 'blur' , function ( ) { list . removeClass ( 'focus' ) ; } )
. on ( 'focus mousedown' , function ( ) { list . addClass ( 'focus' ) ; } ) ;
list = $ ( '<ul>' ) . addClass ( 'form-control recipient-input ac-input rounded-left' )
. append ( $ ( '<li class="input">' ) . append ( input ) )
// "selection" hack to allow text selection in the recipient box or multiple boxes (#7129)
. on ( 'mouseup' , function ( ) { selection = window . getSelection ( ) . toString ( ) ; } )
. on ( 'click' , function ( ) { if ( ! selection . length ) input . focus ( ) ; } )
. sortable ( {
appendTo : document . body ,
items : "> .recipient" ,
connectWith : '.recipient-input' ,
receive : function ( event , ui ) {
var recipient = list . text ( ) ;
list . find ( '.recipient' ) . remove ( ) ;
update _func ( recipient ) ;
if ( ui . sender ) {
ui . sender . find ( 'input' ) . change ( ) ;
}
}
} ) ;
// 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 want to parse its value and apply changes
// to the widget element
. on ( 'focus' , function ( e ) { input . focus ( ) ; e . preventDefault ( ) ; } )
. on ( 'change' , function ( ) {
$ ( 'li.recipient' , list ) . remove ( ) ;
input . val ( this . value ) . change ( ) ;
} )
// copy and parse the value already set
. change ( ) ;
// Init autocompletion
rcmail . init _address _input _events ( input ) ;
} ;
/ * *
* 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 = text . replace ( /[,;\s]*[\r\n]+/g , ',' ) . trim ( ) ;
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 , str = this ;
text = text . replace ( str , '' ) ;
// Support space-separated email addresses
while ( str . length && str . indexOf ( RegExp . $1 ) === 0 ) {
email = RegExp . $1 ;
recipients . push ( {
name : '' ,
email : email . replace ( /(^<|>$)/g , '' ) // trim < and > characters
. replace ( /[^a-z]$/gi , '' ) // remove trailing comma or any non-letter character at the end (#7899)
} ) ;
str = str . replace ( email , '' ) . trim ( ) ;
if ( ! recipient _rx1 . test ( str ) && ! recipient _rx2 . test ( str ) ) {
break ;
}
}
if ( email != RegExp . $1 && RegExp . $1 ) {
email = RegExp . $1 ;
recipients . push ( {
name : str . replace ( email , '' ) . trim ( ) ,
email : email . replace ( /(^<|>$)/g , '' )
} ) ;
}
}
} ) ;
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 += '<' ;
break ;
case '>' :
result += '>' ;
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' , 'data-submit' : 'true' } ) . 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 ; } ) ,
img = $ ( obj ) . find ( 'img' ) [ 0 ] ,
img _onload = function ( ) {
var state = ( img . currentSrc || img . src ) . indexOf ( rcmail . env . photo _placeholder ) != - 1 ;
$ ( obj ) [ state ? 'removeClass' : 'addClass' ] ( 'changed' ) ;
} ;
$ ( obj ) . append ( reset _button ) . click ( function ( ) { rcmail . upload _input ( 'upload-form' ) ; } ) ;
// Note: Looks like only Firefox does not need this separate call
img _onload ( ) ;
$ ( img ) . on ( 'load' , img _onload ) ;
} ;
/ * *
* 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' ) ;
}
}
} ;
/ * *
* Convert checkbox input into Bootstrap ' s custom switch
* /
function pretty _checkbox ( checkbox )
{
var label , parent , id ;
checkbox = $ ( checkbox ) ;
if ( checkbox . is ( '.custom-control-input' ) ) {
return ;
}
if ( ! ( id = checkbox . attr ( 'id' ) ) ) {
id = 'icochk' + ( ++ env . checkboxes ) ;
checkbox . attr ( 'id' , id ) ;
}
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 ) ;
} ;
/ * *
* Fix pretty checkbox input in a cloned element
* /
function pretty _checkbox _fix ( params )
{
var id , input = $ ( params . row ) . find ( 'input[id^=icochk]' ) ;
if ( input . length ) {
id = 'icochk' + ( ++ env . checkboxes ) ;
input . attr ( 'id' , id ) . next ( 'label' ) . attr ( 'for' , id ) ;
}
} ;
/ * *
* 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 initialized
// 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 last _char , last _index = - 1 , items = [ ] , index = [ ] ,
dialog = select . closest ( '.ui-dialog' ) [ 0 ] ,
max _height = ( document . documentElement . clientHeight || $ ( document . body ) . height ( ) ) - 75 ,
max _width = $ ( document . body ) . width ( ) - 20 ,
min _width = Math . min ( select . outerWidth ( ) , max _width ) ,
value = select . val ( ) ;
if ( ! is _mobile ( ) ) {
max _height *= 0.5 ;
}
// close other popups
popups _close ( e ) ;
$ ( '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 ) ;
index . push ( this . disabled ? '' : label . charAt ( 0 ) . toLowerCase ( ) ) ;
}
else {
link . html ( ' ' ) ; // link can't be empty
index . push ( '' ) ;
}
items . push ( $ ( '<li>' ) . append ( link ) ) ;
} ) ;
var list = $ ( '<ul class="listing selectable iconized">' )
. attr ( 'data-ident' , select _ident )
. data ( 'button' , select [ 0 ] )
. 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 , char , last , 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' ;
// no-break
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
default :
// A letter key has been pressed, search mode
char = e . originalEvent . key ;
if ( char && char . length == 1 ) {
char = char . toLowerCase ( ) ;
if ( last _char != char ) {
last _index = - 1 ;
}
last = index . indexOf ( char , last _index + 1 ) ;
if ( last > - 1 || ( last = index . indexOf ( char ) ) > - 1 ) {
list . find ( 'a' ) . eq ( last ) . focus ( ) ;
}
last _char = char ;
last _index = last ;
}
}
} ) ;
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 ( ) {
select . focus ( ) ; // for Chrome
// 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 ( ) ;
} )
) ;
// Find the selected item, focus it
var selected = list . find ( 'a.selected' ) . first ( ) ;
if ( selected . focus ( ) . length ) {
var list _parent = list . parent ( ) ;
last _index = list . find ( 'a' ) . index ( selected [ 0 ] ) ;
last _char = index [ last _index ] ;
// try to scroll the list so focused element is in center (for Firefox)
if ( bw . mz && last _index > 5 ) {
list _parent . scrollTop ( list _parent . scrollTop ( ) + list _parent . height ( ) / 2 - 20 ) ;
}
}
// focus first active element on the list
else 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 ) ;
// Prevent from closing the menu by general popover closing handler (popups_close())
// We used to just stop propagation in onclick handler, but it didn't work
// in Chrome where onclick handler wasn't invoked on mobile (#6705)
popups _close _lock = new Date ( ) . getTime ( ) ;
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 class="hidden"></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 ( ) ,
plain _btn = $ ( '<a class="mce-i-html" href="#" tabindex="-1"></a>' )
. attr ( 'title' , rcmail . gettext ( 'htmltoggle' ) )
. on ( 'click' , function ( e ) {
if ( rcmail . command ( 'toggle-editor' , { id : editor . attr ( 'id' ) , html : true } , '' , e . originalEvent ) ) {
parent . addClass ( 'ishtml' ) ;
}
} )
. on ( 'keydown' , function ( e ) {
if ( e . which == 9 ) { // TAB
editor . focus ( ) ;
return false ;
}
} ) ,
toolbar = $ ( '<div class="editor-toolbar">' ) . append ( plain _btn ) ;
if ( parent . is ( 'td' ) ) {
sw = $ ( 'input[type="checkbox"]' , parent . parent ( ) . next ( ) ) ;
is _table = true ;
}
else {
sw = editor . next ( 'select.hidden' ) ;
}
// make the textarea autoresizeable
textarea _autoresize _init ( obj ) ;
// sanity check
if ( sw . length != 1 ) {
return ;
}
parent . addClass ( 'html-editor' ) ;
editor . after ( toolbar ) . data ( 'control' , sw )
. on ( 'keydown' , function ( e ) {
// ALT + F10 is the way to access toolbar in TinyMCE, let's do the same for plain editor
if ( e . altKey && e . which == 121 ) {
plain _btn . focus ( ) ;
}
} ) ;
if ( is _table ) {
// Hide unwanted table cells
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 depending on it ' s content length .
* The way there ' s no vertical scrollbar .
* /
function textarea _autoresize _init ( textarea )
{
var padding , minHeight ,
resize = function ( ) {
// Wait until the textarea is visible
if ( ! textarea . scrollHeight ) {
return setTimeout ( resize , 250 ) ;
}
if ( ! padding ) {
padding = parseInt ( $ ( textarea ) . css ( 'padding-top' ) ) + parseInt ( $ ( textarea ) . css ( 'padding-bottom' ) ) + 2 ;
minHeight = $ ( textarea ) . height ( ) ;
}
if ( textarea . scrollHeight - padding <= minHeight ) {
return ;
}
// To fix scroll-jump we'll re-apply scrollTop to the (scrolled) parent
// after we reset textarea height
var scroll _element , scroll _pos = 0 ;
$ ( textarea ) . parents ( ) . each ( function ( ) {
if ( this . scrollTop > 0 ) {
scroll _element = this ;
scroll _pos = this . scrollTop ;
return false ;
}
} ) ;
var oldHeight = $ ( textarea ) . outerHeight ( ) ;
$ ( textarea ) . outerHeight ( 0 ) ;
var newHeight = Math . max ( minHeight , textarea . scrollHeight ) ;
$ ( textarea ) . outerHeight ( oldHeight ) ;
if ( newHeight !== oldHeight ) {
$ ( textarea ) . height ( newHeight ) ;
}
if ( scroll _pos ) {
scroll _element . scrollTop = scroll _pos ;
}
} ;
$ ( textarea ) . on ( 'input' , resize ) . trigger ( 'input' ) ;
} ;
// Initializes 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 , i , field ) ;
} ) ;
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 , idx , field , after )
{
2024-09-11 10:35:50 +00:00
error ( 'smart_field_row_add' )
2024-05-25 15:44:05 +00:00
// 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>' ) ;
input = elem . find ( 'input' ) . attr ( {
value : value ,
name : field . name + '[]' ,
size : $ ( field ) . data ( 'size' ) ,
title : field . title ,
placeholder : field . placeholder
} )
. keydown ( function ( e ) {
// element creation event (on Enter)
if ( e . which == 13 ) {
var elem = smart _field _row _add ( area , '' , ( new Date ( ) ) . getTime ( ) , field , 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
elem . find ( 'a.reset' ) . 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 , i , field ) ;
} ) ;
} ;
/ * *
* Register form errors , mark fields as invalid , display 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 ] ) ;
$ ( '#' + this [ 0 ] + '_list > .invalid-feedback' ) . text ( 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' , 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' ) ;
}
} ;
/ * *
* Create a splitter ( resizing ) element on a layout column
* /
function splitter _init ( node )
{
// Use id of the list element, if exists, as a part of the key, instead of action.column-id
// This way e.g. the sidebar in Settings is always the same width for all Settings' pages
var list _id = node . find ( '.scroller .listing' ) . first ( ) . attr ( 'id' ) ,
key = rcmail . env . task + '.' + ( list _id || ( rcmail . env . action + '.' + node . attr ( 'id' ) ) ) ,
pos = get _pref ( key ) ,
inverted = node . is ( '.sidebar-right' ) ,
set _width = function ( width ) {
node . css ( {
width : Math . max ( 100 , width ) ,
// reset default properties
// 'min-width': 100,
flex : 'none'
} ) ;
} ;
if ( ! node [ inverted ? 'prev' : 'next' ] ( ) . length ) {
return ;
}
$ ( '<div class="column-resizer">' )
. addClass ( inverted ? 'inverted' : null )
. appendTo ( node )
. on ( 'mousedown' , function ( e ) {
var ts , splitter = $ ( this ) , offset = node . position ( ) . left ;
// Makes col-resize cursor follow the mouse pointer on dragging
// and fixes issues related to iframes
splitter . addClass ( 'active' ) ;
// Disable selection on document while dragging
// It can happen when you move mouse out of window, on top
document . body . style . userSelect = 'none' ;
// Start listening to mousemove events
$ ( document )
. on ( 'mousemove.resizer' , function ( e ) {
// Use of timeouts makes the move more smooth in Chrome
clearTimeout ( ts ) ;
ts = setTimeout ( function ( ) {
// For left-side-splitter we need the current offset
if ( inverted ) {
offset = node . position ( ) . left ;
}
var cursor _position = rcube _event . get _mouse _pos ( e ) . x ,
width = inverted ? node . width ( ) + ( offset - cursor _position ) : cursor _position - offset ;
set _width ( width ) ;
} , 5 ) ;
} )
. on ( 'mouseup.resizer' , function ( ) {
// Remove registered events
$ ( document ) . off ( '.resizer' ) ;
$ ( 'iframe' ) . off ( '.resizer' ) ;
document . body . style . userSelect = 'auto' ;
// Set back the splitter width to normal
splitter . removeClass ( 'active' ) ;
// Save the current position (width)
set _pref ( key , node . width ( ) ) ;
} ) ;
} ) ;
if ( pos ) {
set _width ( pos ) ;
}
} ;
/ * *
* 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 , small , toolbar , force _window )
{
var colorFunc = function ( body ) {
$ ( body ) . css ( {
color : $ ( document . body ) . css ( 'color' ) ,
backgroundColor : $ ( document . body ) . css ( 'background-color' )
} )
} ;
var setColor = color _mode == 'dark' && /_task=mail/ . test ( url ) && /_action=viewsource/ . test ( url ) ;
// Use 4th argument to bypass the dialog-mode e.g. for external windows
if ( ! is _mobile ( ) || force _window === true ) {
// On attachment preview page we do not display the properties sidebar
// so we can use a smaller window, as we do for print pages
if ( /_task=mail/ . test ( url ) && /_action=get/ . test ( url ) ) {
small = true ;
}
var win = env . open _window . call ( rcmail , url , small , toolbar ) ;
// Switch the plain/text window to dark-mode
if ( setColor ) {
$ ( win ) . on ( 'load' , function ( ) { colorFunc ( win . document . body ) ; } ) ;
}
return win ;
}
// _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' ;
}
// Switch the plain/text iframe to dark-mode
if ( setColor ) {
frame . on ( 'load' , function ( ) { colorFunc ( frame [ 0 ] . contentWindow . document . body ) ; } ) ;
}
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 ;
} ;
/ * *
* Get preference stored in browser
* /
function get _pref ( key )
{
if ( ! prefs ) {
prefs = rcmail . local _storage _get _item ( 'prefs.elastic' , { } ) ;
}
// fall-back to cookies
if ( prefs [ key ] == null ) {
var cookie = rcmail . get _cookie ( key ) ;
if ( cookie != null ) {
prefs [ key ] = cookie ;
// copy value to local storage and remove cookie (if localStorage is supported)
if ( rcmail . local _storage _set _item ( 'prefs.elastic' , prefs ) ) {
rcmail . set _cookie ( key , cookie , new Date ( ) ) ; // expire cookie
}
}
}
return prefs [ key ] ;
} ;
/ * *
* Saves preference value to browser storage
* /
function set _pref ( key , val )
{
prefs [ key ] = val ;
// write prefs to local storage (if supported)
if ( ! rcmail . local _storage _set _item ( 'prefs.elastic' , prefs ) ) {
// store value in cookie
rcmail . set _cookie ( key , val , false ) ;
}
} ;
}
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 ;
}
} ) ;
}
/ * !
* pulltorefreshjs v0 . 1.22
* ( c ) Rafael Soto
* Released under the MIT License .
* /
! function ( e , t ) { "object" == typeof exports && "undefined" != typeof module ? module . exports = t ( ) : "function" == typeof define && define . amd ? define ( t ) : ( e = e || self ) . PullToRefresh = t ( ) }
( this , function ( ) { "use strict" ; var e = { pullStartY : null , pullMoveY : null , handlers : [ ] , styleEl : null , events : null , dist : 0 , state : "pending" , timeout : null , distResisted : 0 , supportsPassive : ! 1 , supportsPointerEvents : "undefined" != typeof window && ! ! window . PointerEvent } ;
try { window . addEventListener ( "test" , null , { get passive ( ) { e . supportsPassive = ! 0 } } ) } catch ( e ) { } var t , n = { setupDOM : function ( t ) { if ( ! t . ptrElement ) { var n = document . createElement ( "div" ) ;
t . mainElement !== document . body ? t . mainElement . parentNode . insertBefore ( n , t . mainElement ) : document . body . insertBefore ( n , document . body . firstChild ) , n . classList . add ( t . classPrefix + "ptr" ) , n . innerHTML = t . getMarkup ( ) . replace ( /__PREFIX__/g , t . classPrefix ) ,
t . ptrElement = n , "function" == typeof t . onInit && t . onInit ( t ) , e . styleEl || ( e . styleEl = document . createElement ( "style" ) , e . styleEl . setAttribute ( "id" , "pull-to-refresh-js-style" ) , document . head . appendChild ( e . styleEl ) ) , e . styleEl . textContent = t . getStyles ( ) . replace ( /__PREFIX__/g , t . classPrefix ) . replace ( /\s+/g , " " ) }
return t } , onReset : function ( t ) { t . ptrElement && ( t . ptrElement . classList . remove ( t . classPrefix + "refresh" ) , t . ptrElement . style [ t . cssProp ] = "0px" , setTimeout ( function ( ) { t . ptrElement && t . ptrElement . parentNode && ( t . ptrElement . parentNode . removeChild ( t . ptrElement ) , t . ptrElement = null ) , e . state = "pending" } ,
t . refreshTimeout ) ) } , update : function ( t ) { var n = t . ptrElement . querySelector ( "." + t . classPrefix + "icon" ) , s = t . ptrElement . querySelector ( "." + t . classPrefix + "text" ) ;
n && ( "refreshing" === e . state ? n . innerHTML = t . iconRefreshing : n . innerHTML = t . iconArrow ) , s && ( "releasing" === e . state && ( s . innerHTML = t . instructionsReleaseToRefresh ) , "pulling" !== e . state && "pending" !== e . state || ( s . innerHTML = t . instructionsPullToRefresh ) , "refreshing" === e . state && ( s . innerHTML = t . instructionsRefreshing ) ) } } , s = function ( t ) { return e . pointerEventsEnabled && e . supportsPointerEvents ? t . screenY : t . touches [ 0 ] . screenY } , r = function ( ) { var r ;
function i ( t ) { var i = e . handlers . filter ( function ( e ) { return e . contains ( t . target ) } ) [ 0 ] ; e . enable = ! ! i , i && "pending" === e . state && ( r = n . setupDOM ( i ) , i . shouldPullToRefresh ( ) && ( e . pullStartY = s ( t ) ) , clearTimeout ( e . timeout ) , n . update ( i ) ) }
function o ( t ) { r && r . ptrElement && e . enable && ( e . pullStartY ? e . pullMoveY = s ( t ) : r . shouldPullToRefresh ( ) && ( e . pullStartY = s ( t ) ) , "refreshing" !== e . state ? ( "pending" === e . state && ( r . ptrElement . classList . add ( r . classPrefix + "pull" ) , e . state = "pulling" , n . update ( r ) ) , e . pullStartY && e . pullMoveY && ( e . dist = e . pullMoveY - e . pullStartY ) , e . distExtra = e . dist - r . distIgnore , e . distExtra > 0 && ( t . cancelable && t . preventDefault ( ) , r . ptrElement . style [ r . cssProp ] = e . distResisted + "px" , e . distResisted = r . resistanceFunction ( e . distExtra / r . distThreshold ) * Math . min ( r . distMax , e . distExtra ) , "pulling" === e . state && e . distResisted > r . distThreshold && ( r . ptrElement . classList . add ( r . classPrefix + "release" ) , e . state = "releasing" , n . update ( r ) ) , "releasing" === e . state && e . distResisted < r . distThreshold && ( r . ptrElement . classList . remove ( r . classPrefix + "release" ) , e . state = "pulling" , n . update ( r ) ) ) ) : t . cancelable && r . shouldPullToRefresh ( ) && e . pullStartY < e . pullMoveY && t . preventDefault ( ) ) }
function l ( ) { if ( r && r . ptrElement && e . enable ) { if ( clearTimeout ( t ) , t = setTimeout ( function ( ) { r && r . ptrElement && "pending" === e . state && n . onReset ( r ) } , 500 ) , "releasing" === e . state && e . distResisted > r . distThreshold ) e . state = "refreshing" , r . ptrElement . style [ r . cssProp ] = r . distReload + "px" , r . ptrElement . classList . add ( r . classPrefix + "refresh" ) , e . timeout = setTimeout ( function ( ) { var e = r . onRefresh ( function ( ) { return n . onReset ( r ) } ) ; e && "function" == typeof e . then && e . then ( function ( ) { return n . onReset ( r ) } ) , e || r . onRefresh . length || n . onReset ( r ) } , r . refreshTimeout ) ; else { if ( "refreshing" === e . state ) return ; r . ptrElement . style [ r . cssProp ] = "0px" , e . state = "pending" } n . update ( r ) , r . ptrElement . classList . remove ( r . classPrefix + "release" ) , r . ptrElement . classList . remove ( r . classPrefix + "pull" ) , e . pullStartY = e . pullMoveY = null , e . dist = e . distResisted = 0 } }
function a ( ) { r && r . mainElement . classList . toggle ( r . classPrefix + "top" , r . shouldPullToRefresh ( ) ) } var d = e . supportsPassive ? { passive : e . passive || ! 1 } : void 0 ; return e . pointerEventsEnabled && e . supportsPointerEvents ? ( window . addEventListener ( "pointerup" , l ) , window . addEventListener ( "pointerdown" , i ) , window . addEventListener ( "pointermove" , o , d ) ) : ( window . addEventListener ( "touchend" , l ) , window . addEventListener ( "touchstart" , i ) , window . addEventListener ( "touchmove" , o , d ) ) , window . addEventListener ( "scroll" , a ) , { onTouchEnd : l , onTouchStart : i , onTouchMove : o , onScroll : a , destroy : function ( ) { e . pointerEventsEnabled && e . supportsPointerEvents ? ( window . removeEventListener ( "pointerdown" , i ) , window . removeEventListener ( "pointerup" , l ) , window . removeEventListener ( "pointermove" , o , d ) ) : ( window . removeEventListener ( "touchstart" , i ) ,
window . removeEventListener ( "touchend" , l ) , window . removeEventListener ( "touchmove" , o , d ) ) , window . removeEventListener ( "scroll" , a ) } } } , i = { distThreshold : 60 , distMax : 80 , distReload : 80 , distIgnore : 0 , mainElement : "body" , triggerElement : "body" , ptrElement : ".ptr" , classPrefix : "ptr--" , cssProp : "min-height" , iconArrow : "⇣" , iconRefreshing : "…" , instructionsPullToRefresh : "Pull down to refresh" , instructionsReleaseToRefresh : "Release to refresh" , instructionsRefreshing : "Refreshing" , refreshTimeout : 500 ,
getMarkup : function ( ) { return '\n<div class="__PREFIX__box">\n <div class="__PREFIX__content">\n <div class="__PREFIX__text"></div>\n </div>\n</div>\n' } ,
getStyles : function ( ) { return "\n.__PREFIX__ptr {\n pointer-events: none;\n font-size: 0.85em;\n font-weight: bold;\n height: 0;\n transition: height 0.3s, min-height 0.3s;\n text-align: center;\n width: 100%;\n overflow: hidden;\n display: flex;\n align-items: flex-end;\n align-content: stretch;\n}\n\n.__PREFIX__box {\n padding: 10px;\n flex-basis: 100%;\n}\n\n.__PREFIX__pull {\n transition: none;\n}\n\n.__PREFIX__text {\n margin-top: .33em;\n}\n\n.__PREFIX__icon {\n transition: transform .3s;\n}\n\n/*\nWhen at the top of the page, disable vertical overscroll so passive touch\nlisteners can take over.\n*/\n.__PREFIX__top {\n touch-action: pan-x pan-down pinch-zoom;\n}\n\n.__PREFIX__release .__PREFIX__icon {\n transform: rotate(180deg);\n}\n" } ,
onInit : function ( ) { } , onRefresh : function ( ) { return location . reload ( ) } , resistanceFunction : function ( e ) { return Math . min ( 1 , e / 2.5 ) } , shouldPullToRefresh : function ( ) { return ! window . scrollY } } , o = [ "mainElement" , "ptrElement" , "triggerElement" ] , l = function ( t ) { var n = { } ; return Object . keys ( i ) . forEach ( function ( e ) { n [ e ] = t [ e ] || i [ e ] } ) , n . refreshTimeout = "number" == typeof t . refreshTimeout ? t . refreshTimeout : i . refreshTimeout , o . forEach ( function ( e ) { "string" == typeof n [ e ] && ( n [ e ] = document . querySelector ( n [ e ] ) ) } ) , e . events || ( e . events = r ( ) ) , n . contains = function ( e ) { return n . triggerElement . contains ( e ) } , n . destroy = function ( ) { clearTimeout ( e . timeout ) ; var t = e . handlers . indexOf ( n ) ; e . handlers . splice ( t , 1 ) } , n } ; return { setPassiveMode : function ( t ) { e . passive = t } , setPointerEventsMode : function ( t ) { e . pointerEventsEnabled = t } , destroyAll : function ( ) { e . events && ( e . events . destroy ( ) , e . events = null ) , e . handlers . forEach ( function ( e ) { e . destroy ( ) } ) } , init : function ( t ) { void 0 === t && ( t = { } ) ; var n = l ( t ) ; return e . handlers . push ( n ) , n } , _ : { setupHandler : l , setupEvents : r , setupDOM : n . setupDOM , onReset : n . onReset , update : n . update } } } ) ;
if ( UI . get _screen _mode ( ) == "small" && document . getElementById ( "messagelist-content" ) )
{
const ptr = PullToRefresh . init ( {
2024-07-04 16:29:15 +00:00
mainElement : '#messagelist' ,
2024-05-25 15:44:05 +00:00
onRefresh : function ( ) {
return new Promise ( function ( resolve ) {
setTimeout ( function ( ) { UI _pull _to _refresh ( resolve ) ; } , 0 ) ;
} ) ;
} ,
shouldPullToRefresh : function ( ) {
2024-07-04 16:29:15 +00:00
2024-05-25 15:44:05 +00:00
if ( document . querySelector ( "#greydiv" ) )
{
if ( document . querySelector ( "#greydiv" ) . classList . contains ( "fadeoff70create" ) )
return false ;
}
2024-07-04 16:29:15 +00:00
return ! document . getElementById ( "messagelist-content" ) . scrollTop ;
2024-05-25 15:44:05 +00:00
} ,
refreshTimeout : 50 ,
2024-07-04 16:29:15 +00:00
instructionsPullToRefresh : '<i class="sebicon iconrefresh icon_pulltorefresh" ></i>' ,
instructionsRefreshing : '<i class="sebicon iconrefresh icon_pulltorefresh anim_rotatepulltorefresh" ></i>' ,
instructionsReleaseToRefresh : '<i class="sebicon iconrefresh icon_pulltorefresh"></i>'
2024-05-25 15:44:05 +00:00
} ) ;
}
2024-07-04 16:29:15 +00:00
2024-06-13 21:17:39 +00:00
if ( document . getElementById ( "messagelist-content" ) )
{
document . getElementById ( "messagelist-content" ) . addEventListener ( "wheel" , ( e ) => {
scroll _mailbymail ( e )
} )
new ResizeObserver ( resizemessagelist ) . observe ( document . getElementById ( "messagelist-content" ) )
}
function resizemessagelist ( )
{
2024-09-14 10:05:07 +00:00
2024-06-13 21:17:39 +00:00
if ( ! document . getElementById ( "messagelist-content" ) ) return // not loaded yet
2024-07-04 16:29:15 +00:00
if ( ! document . getElementById ( 'searchmenu' ) . classList . contains ( 'hidden' ) ) return //https://github.com/seb1k/Elastic2022/issues/8
2024-06-13 21:17:39 +00:00
var d = document . getElementById ( "messagelist-content" )
var h = d . offsetHeight
if ( ! d . querySelector ( '.message' ) ) return // not loaded yet
2024-09-12 17:09:57 +00:00
var trsize = d . querySelector ( '.message' ) . getBoundingClientRect ( ) . height // more precise than offsetHeight
2024-06-13 21:17:39 +00:00
d . style . paddingBottom = h % trsize + "px"
}
function scroll _mailbymail ( e )
{
2024-07-04 16:29:15 +00:00
e . preventDefault ( ) ;
2024-06-13 21:17:39 +00:00
e . stopPropagation ( ) ;
var mlc = document . getElementById ( "messagelist-content" )
var scrollTop = mlc . scrollTop
2024-06-26 08:29:53 +00:00
// all message
var trs = mlc . querySelectorAll ( 'tr.message' )
// filter hidden
trs = Array . from ( trs ) . filter ( ( msg ) => msg . style . display != "none" ) ;
2024-06-13 21:17:39 +00:00
//find first shown tr
2024-06-26 08:29:53 +00:00
for ( var i = 0 ; i < trs . length ; i ++ )
if ( scrollTop <= trs [ i ] . offsetTop )
2024-06-13 21:17:39 +00:00
break ;
2024-06-26 08:29:53 +00:00
2024-06-13 21:17:39 +00:00
if ( trs [ i ] . offsetTop - scrollTop > 15 && e . deltaY > 0 ) i -- ;
if ( e . deltaY > 0 )
i ++
else
i --
if ( i < 0 ) i = 0 ;
if ( i >= trs . length ) i = trs . length ;
mlc . scrollTop = trs [ i ] . offsetTop
}
2024-05-25 15:44:05 +00:00
function refresh _mail _anim ( )
{
var but = document . getElementsByClassName ( 'refresh' ) [ 0 ]
but . classList . add ( "anim_rotate" ) ;
but . classList . add ( "disabled" ) ;
var stop _anim = function ( ) { but . classList . remove ( "anim_rotate" ) ; but . classList . remove ( "disabled" ) ; }
UI _pull _to _refresh ( stop _anim ) ;
document . activeElement . blur ( ) ; //release focus
}
var r = document . getElementsByClassName ( 'refresh' )
if ( r . length )
{
r [ 0 ] . setAttribute ( "onClick" , "refresh_mail_anim()" ) ; //refresh anim
r [ 0 ] . style . width = "45px" // anim center
}
// function in IFRAME
if ( document . documentElement . classList . contains ( 'iframe' ) )
{
var header = document . querySelector ( ".header-content" )
if ( header )
{
set _onclick _to _class ( header , ".flag" , "return parent.rcmail.command('mark','flagged','',this,event)" )
set _onclick _to _class ( header , ".unread" , "return parent.rcmail.command('mark','unread',this,event)" )
set _onclick _to _class ( header , ".delete" , "return parent.rcmail.command('delete','',this,event)" )
}
if ( parent . rcmail . env . archive _folder ) // Archive plugin
{
// Big button
var h = document . getElementById ( "message-header" )
2024-06-10 16:11:53 +00:00
var delete _icon = h . querySelector ( '.reply' )
2024-05-25 15:44:05 +00:00
var ab = document . createElement ( "a" ) ;
2024-06-10 16:11:53 +00:00
ab . className = "button archive hide_small"
2024-05-25 15:44:05 +00:00
ab . onclick = function ( event ) { return parent . rcmail . command ( 'plugin.archive' , '' , this , event ) } ;
ab . innerHTML = "<span class='inner'>" + parent . rcmail . labels [ 'archive.buttontext' ] + "</span>"
delete _icon . parentElement . insertBefore ( ab , delete _icon ) ;
//menu
2024-06-10 16:11:53 +00:00
var li _prev = document . getElementById ( "message-menu-inner" ) . querySelector ( '.unread' ) . parentNode
2024-05-25 15:44:05 +00:00
var li = document . createElement ( "li" ) ;
li . onclick = function ( event ) { return parent . rcmail . command ( 'plugin.archive' , '' , this , event ) } ;
li . innerHTML = '<a class="archive" id="" role="button" tabindex="0" aria-disabled="false" href="#" onclick="return parent.rcmail.command(\'plugin.archive\',\'\',this,event)" title=""><span class="inner">' + parent . rcmail . labels [ 'archive.buttontext' ] + '</span></a>'
li _prev . parentElement . insertBefore ( li , li _prev ) ;
//small
parent . document . getElementById ( "header_topmenu_archive" ) . style . display = "inline-block"
}
}
if ( rcmail . env . action == "preview" )
{
var contactphoto = $ ( '.contactphoto' ) [ 0 ]
contactphoto . onerror = function ( ) { change _contactphoto _by _divLetter ( ) }
}
function change _contactphoto _by _divLetter ( )
{
var contactphoto = $ ( '.contactphoto' ) [ 0 ]
var d = document . createElement ( "div" ) ;
d . className = "sel_circle maillogo_no_select replacecontactpic"
var txt = textforcircle ( $ ( '.header .from' ) . text ( ) )
var c = colors _from _txt ( txt )
d . style . backgroundColor = "#" + c
d . innerHTML = txt
contactphoto . parentElement . appendChild ( d ) ;
contactphoto . style . display = "none" ;
}
function flag _click ( )
{
if ( window . frameElement ) // is iframe
{
parent . messagelist . querySelector ( ".focused" ) . querySelector ( ".flag" ) . firstChild . click ( )
parent . hide _messagestack _few _secs ( )
}
else
{
var is _flag = document . body . classList . contains ( 'status-flagged' )
if ( is _flag )
rcmail . command ( 'mark' , 'unflagged' , this , event )
else
rcmail . command ( 'mark' , 'flagged' , this , event )
hide _messagestack _few _secs ( )
}
}
function unread _click ( )
{
if ( window . frameElement ) // is iframe
{
parent . messagelist . querySelector ( ".focused" ) . querySelector ( ".msgicon" ) . click ( )
parent . hide _messagestack _few _secs ( )
}
else
{
rcmail . command ( 'mark' , 'unread' , this , event )
}
}
function hide _messagestack _few _secs ( )
{
document . getElementById ( "messagestack" ) . style . visibility = "hidden"
UI _pull _to _refresh _check ( function ( ) { } )
}
function set _onclick _to _class ( elem , classsearch , func )
{
var lnks = elem . querySelectorAll ( classsearch )
for ( var i = 0 ; i < lnks . length ; i ++ )
lnks [ i ] . setAttribute ( "onClick" , func ) ;
}
function UI _pull _to _refresh ( resolve )
{
document . getElementById ( "messagestack" ) . style . visibility = "hidden"
rcmail . command ( 'checkmail' , '' , false , false ) ;
UI _pull _to _refresh _check ( resolve )
}
function UI _pull _to _refresh _check ( resolve )
{
if ( $ ( "#messagestack" ) . html ( ) )
setTimeout ( function ( ) { UI _pull _to _refresh _check ( resolve ) ; } , 100 ) ;
else
{
document . getElementById ( "messagestack" ) . style . visibility = "visible" //return to normal
resolve ( ) ;
}
}
function listupdate _go _msgs ( ) {
var m = document . getElementById ( "messagelist" )
var msgs = m . querySelectorAll ( ".message" )
// load for quick check( prevent blinking)
var LS = localStorage [ 'contactphoto' ] ? ? "{}"
var LSdata = JSON . parse ( LS )
for ( var i = 0 ; i < msgs . length ; i ++ )
{
var tdsel = msgs [ i ] . querySelector ( ".selection" )
if ( ! tdsel . querySelector ( ".sel_circle" ) )
{
tdsel . querySelector ( "input" ) . style . display = "none"
var d = document . createElement ( "div" ) ;
d . className = "sel_circle maillogo_no_select"
d . onclick = function ( event ) { event . preventDefault ( ) ; } ;
var email = ""
if ( msgs [ i ] . querySelector ( ".rcmContactAddress" ) ) // ulgy but prevent blinking
var email = msgs [ i ] . querySelector ( ".rcmContactAddress" ) . title
var emailencode = urlencode ( email )
if ( LSdata [ emailencode ] == 1 )
{
2024-09-30 23:52:05 +00:00
d . style . backgroundImage = "url('?_task=addressbook&_action=photo&_email=" + emailencode + "&_error=1&_bgcolor=transparent')"
d . style . backgroundColor = "" ;
2024-05-25 15:44:05 +00:00
}
else
{
var txt = get _personna _from _mailblock ( msgs [ i ] )
var c = colors _from _txt ( txt )
d . style . backgroundColor = "#" + c
d . innerHTML = txt
}
tdsel . appendChild ( d ) ;
var d = document . createElement ( "div" ) ;
d . className = "sel_circle maillogo_is_select"
d . onclick = function ( event ) { event . preventDefault ( ) ; } ;
d . style . backgroundColor = "#0078D4"
d . innerHTML = "<i class='sebicon selecticon' style='color:#fff' onclick='return function(e){e.preventDefault()}'></i>"
tdsel . appendChild ( d ) ;
}
// 2nd part for attachement
var attachement _span = msgs [ i ] . querySelectorAll ( ".attachment" ) . length
var is _report = msgs [ i ] . querySelectorAll ( ".report" ) . length
if ( attachement _span == 2 || is _report )
msgs [ i ] . querySelector ( ".subject" ) . querySelector ( ".subject" ) . style . paddingRight = "18px"
// delete button
if ( ! msgs [ i ] . querySelector ( ".deleteicon" ) )
{
var spanflags = msgs [ i ] . querySelector ( ".size" )
var d = document . createElement ( "span" ) ;
d . className = "sebicon deleteicon insummary"
d . onclick = function ( e ) {
var uid = e . target . closest ( ".message" ) . uid
if ( rcmail . env . mailbox == rcmail . env . trash _mailbox ) // in Trash, delete
rcmail . with _selected _messages ( 'delete' , { _uid : [ uid ] } )
else
rcmail . command ( 'move' , { id : rcmail . env . trash _mailbox , uids : [ uid ] } , "" , event , true )
} ;
spanflags . appendChild ( d ) ;
}
}
setTimeout ( try _changeletters _by _image , 0 ) ;
}
function try _changeletters _by _image ( )
{
var m = document . getElementById ( "messagelist" )
var msgs = m . querySelectorAll ( ".message" )
for ( var i = 0 ; i < msgs . length ; i ++ )
{
if ( msgs [ i ] . querySelector ( ".maillogo_no_select" ) . innerHTML )
{
var email = msgs [ i ] . querySelector ( ".rcmContactAddress" ) . title
var emailencode = urlencode ( email )
if ( is _contactphoto ( emailencode ) )
{
2024-09-30 23:52:05 +00:00
var div = msgs [ i ] . querySelector ( ".maillogo_no_select" )
div . innerHTML = ""
div . style . backgroundImage = "url('?_task=addressbook&_action=photo&_email=" + emailencode + "&_error=1&_bgcolor=transparent')"
div . style . backgroundColor = "" ;
2024-05-25 15:44:05 +00:00
}
2024-09-30 23:52:05 +00:00
2024-05-25 15:44:05 +00:00
}
}
}
function elastic2022 _logout ( )
{
delete localStorage [ 'contactphoto' ]
rcmail . command ( 'switch-task' , 'logout' , false , false )
}
function is _contactphoto ( email )
{
// 1st check LS
var LS = localStorage [ 'contactphoto' ] ? ? "{}"
var LSdata = JSON . parse ( LS )
if ( LSdata [ email ] !== undefined )
return LSdata [ email ]
var xmlhttp = new XMLHttpRequest ( ) ,
method = 'GET' ,
2024-09-30 23:52:05 +00:00
url = '?_task=addressbook&_action=photo&_email=' + email + '&_error=1&_bgcolor=transparent' ;
2024-05-25 15:44:05 +00:00
xmlhttp . open ( method , url , false ) ; //yeap slow on ugly
xmlhttp . onerror = function ( ) {
console . log ( "** An error occurred during the check" ) ;
} ;
xmlhttp . send ( ) ;
if ( xmlhttp . readyState == 4 )
{
if ( xmlhttp . readyState == 4 && xmlhttp . status == 200 ) {
LS _add _info ( email , 1 )
return true
}
if ( xmlhttp . readyState == 4 && xmlhttp . status == 204 ) {
LS _add _info ( email , 0 )
return false
}
}
else
{
error ( 'xmlhttprequest' )
return ( false ) ;
}
}
function LS _add _info ( email , info )
{
var LS = localStorage [ 'contactphoto' ] ? ? "{}"
var LSdata = JSON . parse ( LS )
LSdata [ email ] = info ;
localStorage [ 'contactphoto' ] = JSON . stringify ( LSdata )
}
function LS _remove _contact ( )
{
var email = urlencode ( $ ( '#ff_email0' ) [ 0 ] . value )
LS _remove ( email )
}
function LS _remove ( email )
{
var LS = localStorage [ 'contactphoto' ] ? ? "{}"
var LSdata = JSON . parse ( LS )
delete LSdata [ email ] ;
localStorage [ 'contactphoto' ] = JSON . stringify ( LSdata )
}
function get _personna _from _mailblock ( m )
{
if ( ! m . querySelector ( ".rcmContactAddress" ) ) return ""
var txt = m . querySelector ( ".rcmContactAddress" ) . textContent
return textforcircle ( txt )
}
function textforcircle ( txt )
{
txt = txt . trim ( ) . replace ( /[|&;$%@"<>()+-.,●]/g , "" ) . trim ( ) ;
var tab = txt . split ( ' ' ) ;
var ret = txt . charAt ( 0 )
if ( tab [ 1 ] )
ret += tab [ 1 ] . charAt ( 0 )
return ret . toUpperCase ( ) ;
}
function colors _from _txt ( txt )
{
if ( ! txt ) return "AAA" ;
var lst _cols = "DF71AE D45345 9877D5 6EB9C2 A47563 E1843B 62BF78 5196D5 EACF47" ;
var tab = lst _cols . split ( ' ' ) ;
var n = 0 ;
for ( var i = 0 ; i < txt . length ; i ++ )
n += txt . charAt ( i ) . charCodeAt ( 0 ) + i * 255
return tab [ n % tab . length ]
}
function click _menu ( )
{
if ( ! document . querySelector ( "#greydiv" ) )
{
var d = document . createElement ( "div" ) ;
d . id = "greydiv"
d . onclick = function ( ) { close _leftmenu ( ) } ;
document . body . appendChild ( d ) ;
}
document . querySelector ( "#greydiv" ) . classList . add ( "fadeoff70create" ) ;
// Pop menu
if ( ! document . querySelector ( "#menuleft" ) )
{
2024-07-04 16:29:15 +00:00
var HTMLmenu = "<div id='menuleft'><div> </div><div id='menuleft_cont'></div><hr style='margin-top:40px;border-top:1px solid #374549;width: 80%;'><div id='menuleft_bottom' class='menu'></div></div>"
2024-05-25 15:44:05 +00:00
document . querySelector ( "#greydiv" ) . insertAdjacentHTML ( 'afterend' , HTMLmenu ) ;
}
var menuleft = document . querySelector ( "#menuleft" )
setTimeout ( function ( ) { menuleft . classList . add ( "menu_marginleft" ) ; } , 0 ) ;
//move menu
2024-06-11 19:46:20 +00:00
if ( rcmail . env . task == "mail" )
{
if ( is _plugin _ident _switch _account ( ) )
$ ( "#menuleft_cont" ) . append ( $ ( '#plugin-ident_switch-account' ) ) ;
$ ( "#menuleft_cont" ) . append ( $ ( '#folderlist-content' ) ) ;
}
2024-09-14 10:05:07 +00:00
if ( rcmail . env . task == "addressbook" )
{
$ ( "#menuleft_cont" ) . append ( $ ( '#directorylist' ) ) ;
$ ( "#directorylist" ) . append ( $ ( '#layout-sidebar .header a' ) ) ;
}
2024-05-25 15:44:05 +00:00
if ( rcmail . env . task == "calendar" )
{
$ ( "#menuleft_cont" ) . append ( $ ( '#layout-sidebar .searchbar' ) ) ;
$ ( "#menuleft_cont" ) . append ( $ ( '#calendars-content' ) ) ;
}
//Add link to close menu if no done
if ( menuleft _cont . classList . contains ( "link_added" ) )
return
var menuHTML = ""
var lnks = document . querySelector ( ".menu_bottom" ) . querySelectorAll ( "a" )
for ( i = 0 ; i < lnks . length ; ++ i ) {
menuHTML += "<div class='menuopt'>" + lnks [ i ] . outerHTML + "</div>"
}
menuHTML += document . querySelector ( "#taskmenu" ) . innerHTML
document . querySelector ( "#menuleft_bottom" ) . innerHTML = menuHTML
$ ( '#mailboxlist a' ) . on ( 'click' , function ( ) { close _leftmenu ( ) } ) ;
$ ( '#menuleft_bottom > .click_change_theme' ) . click ( function ( ) { $ ( '#taskmenu .click_change_theme' ) . click ( ) } )
menuleft _cont . classList . add ( "link_added" ) ;
}
function close _leftmenu ( )
{
if ( ! document . querySelector ( "#greydiv" ) . classList . contains ( "fadeoff70create" ) )
return
document . querySelector ( "#greydiv" ) . classList . remove ( "fadeoff70create" ) ;
document . querySelector ( "#menuleft" ) . classList . remove ( "menu_marginleft" ) ;
2024-06-11 19:46:20 +00:00
if ( rcmail . env . task == "mail" )
{
2024-09-14 10:05:07 +00:00
setTimeout ( function ( ) {
if ( is _plugin _ident _switch _account ( ) )
$ ( '.header-title.username' ) . append ( $ ( "#plugin-ident_switch-account" ) )
$ ( '#folderlist-content' ) . insertBefore ( $ ( '.menu.menu_bottom' ) ) ;
} , 300 ) ;
}
if ( rcmail . env . task == "addressbook" )
{
setTimeout ( function ( ) {
$ ( '#directorylist' ) . insertBefore ( $ ( '#layout-sidebar .scroller' ) ) ;
$ ( "#layout-sidebar .header" ) . append ( $ ( '#directorylist a.sidebar-menu' ) ) ;
} , 300 ) ;
2024-06-11 19:46:20 +00:00
}
2024-05-25 15:44:05 +00:00
if ( rcmail . env . task == "calendar" )
{
setTimeout ( function ( ) { $ ( '#menuleft .searchbar' ) . insertBefore ( $ ( '.menu.menu_bottom' ) ) ; } , 50 ) ;
setTimeout ( function ( ) { $ ( '#calendars-content' ) . insertBefore ( $ ( '.menu.menu_bottom' ) ) ; } , 50 ) ;
}
}
function dtel ( x )
{
$ ( '#mailboxlist a' ) [ 0 ] . text = x
}
function mail _menu _show _attachement ( )
{
$ ( '#layout-sidebar' ) . removeClass ( 'hidden' ) ;
$ ( '#layout-content' ) . addClass ( 'hidden' ) ;
UI . screen _resize _headers ( )
}
window . onmessage = function ( e ) {
if ( e . data == 'headers_dialog' ) {
UI . headers _dialog ( )
}
if ( e . data == 'resize_message' ) {
autoscale _message _reset ( )
}
} ;
var use _autoscale = 1
function autoscale _message _reset ( )
{
if ( ! use _autoscale ) return ;
delete document . body . dataset . originalscrollWidth
2024-06-13 21:17:39 +00:00
2024-05-25 15:44:05 +00:00
var divms = document . querySelector ( "#messagebody" ) . style
divms . width = ""
divms . height = ""
divms . transform = ""
divms . transformOrigin = "" ;
2024-06-13 21:17:39 +00:00
if ( $ ( '#divscale_default' ) . data ( "scale" ) == "no" ) // ask for no more autoscale
return
2024-05-25 15:44:05 +00:00
setTimeout ( function ( ) { autoscale _message ( ) } , 0 ) ;
}
function autoscale _message ( )
{
var scroolmod = window . innerWidth - $ ( document ) . width ( )
if ( scroolmod >= 0 ) // no, scrollbar, it fit
2024-06-13 21:17:39 +00:00
{
$ ( '#divscale_default' ) . css ( 'display' , 'none' )
return
}
$ ( '#divscale_default' ) . css ( 'display' , 'inline-block' )
2024-05-25 15:44:05 +00:00
var s = document . body . scrollWidth
var divms = document . querySelector ( "#messagebody" ) . style
divms . width = s + "px" ;
if ( document . body . dataset . originalscrollWidth )
{
var ow = document . body . dataset . originalscrollWidth
var h = document . body . dataset . originalHeight
}
else
{
var ow = document . body . scrollWidth
var h = document . querySelector ( "#message-htmlpart1" ) . getBoundingClientRect ( ) . height
document . body . dataset . originalscrollWidth = ow
document . body . dataset . originalHeight = h
}
var w = document . body . clientWidth - 7 // for padding
var ratio = w / ow
divms . height = h * ratio + "px"
divms . transform = "scale(" + ratio + ")"
divms . transformOrigin = "0 0" ;
2024-06-13 21:17:39 +00:00
}
function scale _default ( ) {
$ ( '#divscale_default' ) . css ( 'display' , 'none' )
$ ( '#divscale_default' ) . data ( "scale" , "no" )
autoscale _message _reset ( )
2024-06-01 09:28:00 +00:00
}
go _PWA ( )
function go _PWA ( )
{
var href = window . location . href
var url = href . substring ( 0 , href . lastIndexOf ( '/' ) ) + "/" ;
var myDynamicManifest = {
2024-06-01 09:38:49 +00:00
"name" : "Rouncube" ,
2024-06-18 22:10:06 +00:00
"theme_color" : $ ( 'meta[name="theme-color"]' ) . attr ( 'content' ) ,
2024-06-01 09:28:00 +00:00
"start_url" : url ,
"icons" : [ {
"src" : url + "/skins/elastic2022/images/rc192x192.png" ,
"sizes" : "192x192" ,
"purpose" : "maskable" ,
"purpose" : "any" } , {
"src" : url + "/skins/elastic2022/images/rc512x512.png" ,
"sizes" : "512x512" ,
"purpose" : "maskable" ,
"purpose" : "any"
} ] ,
"display" : "standalone" ,
}
const stringManifest = JSON . stringify ( myDynamicManifest ) ;
const blob = new Blob ( [ stringManifest ] , { type : 'application/json' } ) ;
let reader = new FileReader ( ) ;
reader . readAsDataURL ( blob ) ;
reader . onload = function ( ) {
var link = document . createElement ( 'link' ) ;
link . href = reader . result
link . rel = 'manifest' ;
document . getElementsByTagName ( 'head' ) [ 0 ] . appendChild ( link ) ;
}
2024-06-10 16:11:53 +00:00
}
function define _priority ( e )
{
var v = 0
if ( e . checked )
v = 2
$ ( "#compose-priority" ) [ 0 ] . value = v
2024-06-11 19:46:20 +00:00
}
function is _plugin _ident _switch _account ( )
{
return ( $ ( '#plugin-ident_switch-account' ) . length != 0 )
}
function init _plugin _ident _switch _account ( )
{
$sw = $ ( '#plugin-ident_switch-account' ) ;
var isOk = plugin _switchIdent _addCbElastic2022 ( $sw )
if ( isOk )
$sw . show ( ) ;
}
function plugin _switchIdent _addCbElastic2022 ( $sw ) {
var $taskBar = $ ( '.header-title.username' ) ;
if ( $taskBar . length > 0 ) {
$taskBar . prepend ( $sw ) ;
return true ;
}
return false ;
2024-06-13 21:17:39 +00:00
}
2024-06-18 22:10:06 +00:00
function elastic2022 _change _mailheader ( )
{
var trdate = $ ( '.header-headers .date' ) [ 0 ] . closest ( 'tr' )
trdate . querySelector ( '.header-title' ) . style . visibility = "hidden"
trdate . parentElement . appendChild ( trdate ) ; // move date to last line
2024-09-11 10:35:50 +00:00
//ugly safari fix
$ ( '.header-headers .date' ) [ 0 ] . innerHTML = "<span class='tddatemovetoright'>" + $ ( '.header-headers .date' ) . text ( ) + "</span>"
2024-06-18 22:10:06 +00:00
var fromhref = $ ( '.header-headers .from .adr a' ) [ 0 ] . href
var replyto = $ ( '.header-headers .replyto' )
if ( replyto . length ) // delete replyTo if same as "from"
{
var replytohref = replyto [ 0 ] . querySelector ( 'a' ) . href
if ( replytohref == fromhref )
{
replyto . parent ( ) . hide ( )
}
}
var sender = $ ( '.header-headers .sender' )
2024-09-24 13:07:53 +00:00
if ( replyto . length && sender . length ) // delete sender if same as "from"
2024-06-18 22:10:06 +00:00
{
var senderhrefhref = replyto [ 0 ] . querySelector ( 'a' ) . href
if ( senderhrefhref == fromhref )
{
sender . parent ( ) . hide ( )
}
}
}
2024-09-30 23:52:05 +00:00
function elastic2022 _change _attachmentslist ( )
{
$ ( '.attachment-size' ) . each ( function ( ) { this . innerText = this . innerText . replace ( /[()~]/g , '' ) } ) // remove ( ) ~ char
// force direct download on link
$ ( '.attachmentslist .filename' ) . off ( )
$ ( '.attachmentslist .filename' ) . removeAttr ( "onclick" )
$ ( '.attachmentslist .filename' ) . attr ( 'target' , '_blank' ) ;
$ ( '.attachmentslist li:not(.pdf, .image) .filename' ) . attr ( "download" , "" ) // open image & pdf in browser, download everything else (bug for pdf is forced download :/)
// add thumbail for .image
$ ( '.attachmentslist .image' ) . each ( function ( ) {
var bkgurl = jQuery ( this ) . find ( ".filename" ) [ 0 ] . href
bkgurl += "&_embed=1&_mimeclass=image&_thumb=1"
2024-10-03 11:21:07 +00:00
jQuery ( this ) . find ( ".filename" ) . append ( "<div class='attachmentslistthumnb' style='background-image: url(" + bkgurl + ");'></div>" ) ;
2024-09-30 23:52:05 +00:00
} )
2024-10-03 11:21:07 +00:00
// add button for ics
if ( typeof rcube _calendar === "function" ) // rcube_calendar exist ?
{
var hovertxt = $ ( '#attachmentsavecal' ) . text ( )
$ ( '.attachmentslist .ics' ) . each ( function ( ) {
jQuery ( this ) . find ( ".filename" ) . append ( "<div class='attachmentslistics' title='" + hovertxt + "'onclick='click_add_calendar(event,this)'>Add<br>Cal</div>" ) ;
} )
}
2024-09-30 23:52:05 +00:00
if ( $ ( '.zipdownload' ) . length ) // zipdownload plugin
{
var zd = $ ( '.zipdownload' ) [ 0 ]
$ ( '.tdzipdownload' ) . css ( "display" , "table-cell" )
$ ( '.tdzipdownload' ) . prop ( 'title' , zd . innerText ) ;
$ ( '.tdzipdownload' ) . on ( "click" , function ( ) { window . location = zd . href ; } ) ;
2024-10-03 11:21:07 +00:00
$ ( '.tdattachmentslist' ) . attr ( 'style' , 'padding-right: 6px !important' ) ;
2024-09-30 23:52:05 +00:00
$ ( '.zipdownload' ) . remove ( )
}
2024-10-03 11:21:07 +00:00
// force direct download on link
$ ( '.attachmentslist .filename' ) . off ( )
$ ( '.attachmentslist .filename' ) . removeAttr ( "onclick" )
$ ( '.attachmentslist .filename' ) . attr ( 'target' , '_blank' ) ;
$ ( '.attachmentslist li:not(.pdf, .image) .filename' ) . attr ( "download" , "" ) // open image & pdf in browser, download everything else (bug for pdf is forced download :/)
// add button if more than 2 files
if ( $ ( '.attachmentslist li' ) . length > 2 )
{
$ ( '#attach_fullsize' ) . show ( ) ;
setTimeout ( function ( ) { $ ( '#attach_fullsize' ) . show ( ) ; } , 100 ) ; // again because of a bug on small layout
}
2024-09-30 23:52:05 +00:00
}
2024-10-03 11:21:07 +00:00
function click _add _calendar ( e , p )
{
var attachid = p . closest ( "li" ) . id . replace ( 'attach' , '' ) ;
rcmail . http _post ( 'calendar/mailimportattach' , {
_uid : rcmail . env . uid ,
_mbox : rcmail . env . mailbox ,
_part : attachid
} , rcmail . set _busy ( true , 'itip.savingdata' ) ) ;
2024-09-30 23:52:05 +00:00
2024-10-03 11:21:07 +00:00
e . preventDefault ( ) ;
}
2024-09-30 23:52:05 +00:00
2024-10-03 11:21:07 +00:00
function attach _fullsize ( )
{
$ ( '.attachmentslist' ) . css ( 'max-height' , 'unset' ) ;
$ ( '#attach_fullsize' ) . hide ( ) ;
}
2024-06-18 22:10:06 +00:00
function click _from _button ( e )
{
if ( e . disabled ) return ;
var ischecked = e . checked
rcmail . set _cookie ( 'fromButton' , ischecked , false ) ;
if ( ischecked ) $ ( '#compose_from' ) . show ( ) ;
else $ ( '#compose_from' ) . hide ( ) ;
}