alecpl
2011-11-16 fa9edc3daa049e894e76a2034018c4b970bd8e06
commit | author | age
e0ed97 1 /*
4e17e6 2  +-----------------------------------------------------------------------+
e019f2 3  | Roundcube Webmail Client Script                                       |
4e17e6 4  |                                                                       |
e019f2 5  | This file is part of the Roundcube Webmail client                     |
1a716d 6  | Copyright (C) 2005-2011, The Roundcube Dev Team                       |
T 7  | Copyright (C) 2011, Kolab Systems AG                                  |
30233b 8  | Licensed under the GNU GPL                                            |
4e17e6 9  |                                                                       |
T 10  +-----------------------------------------------------------------------+
8c2e58 11  | Authors: Thomas Bruederli <roundcube@gmail.com>                       |
f52c93 12  |          Aleksander 'A.L.E.C' Machniak <alec@alec.pl>                 |
8c2e58 13  |          Charles McNulty <charles@charlesmcnulty.com>                 |
4e17e6 14  +-----------------------------------------------------------------------+
cc97ea 15  | Requires: jquery.js, common.js, list.js                               |
6b47de 16  +-----------------------------------------------------------------------+
T 17
15a9d1 18   $Id$
4e17e6 19 */
24053e 20
4e17e6 21 function rcube_webmail()
cc97ea 22 {
62c861 23   this.env = { recipients_separator:',', recipients_delimiter:', ' };
8fa922 24   this.labels = {};
A 25   this.buttons = {};
26   this.buttons_sel = {};
27   this.gui_objects = {};
28   this.gui_containers = {};
29   this.commands = {};
30   this.command_handlers = {};
31   this.onloads = [];
ad334a 32   this.messages = {};
4e17e6 33
cfdf04 34   // create protected reference to myself
cc97ea 35   this.ref = 'rcmail';
cfdf04 36   var ref = this;
8fa922 37
4e17e6 38   // webmail client settings
b19097 39   this.dblclick_time = 500;
b37e69 40   this.message_time = 2000;
8fa922 41
f11541 42   this.identifier_expr = new RegExp('[^0-9a-z\-_]', 'gi');
8fa922 43
9a5261 44   // default environment vars
7139e3 45   this.env.keep_alive = 60;        // seconds
9a5261 46   this.env.request_timeout = 180;  // seconds
e170b4 47   this.env.draft_autosave = 0;     // seconds
f11541 48   this.env.comm_path = './';
T 49   this.env.blankpage = 'program/blank.gif';
cc97ea 50
T 51   // set jQuery ajax options
8fa922 52   $.ajaxSetup({
da8f11 53     cache:false,
cc97ea 54     error:function(request, status, err){ ref.http_error(request, status, err); },
e019f2 55     beforeSend:function(xmlhttp){ xmlhttp.setRequestHeader('X-Roundcube-Request', ref.env.request_token); }
cc97ea 56   });
9a5261 57
f11541 58   // set environment variable(s)
T 59   this.set_env = function(p, value)
8fa922 60   {
d8cf6d 61     if (p != null && typeof p === 'object' && !value)
f11541 62       for (var n in p)
T 63         this.env[n] = p[n];
64     else
65       this.env[p] = value;
8fa922 66   };
10a699 67
T 68   // add a localized label to the client environment
4dcd43 69   this.add_label = function(p, value)
8fa922 70   {
4dcd43 71     if (typeof p == 'string')
T 72       this.labels[p] = value;
73     else if (typeof p == 'object')
74       $.extend(this.labels, p);
8fa922 75   };
4e17e6 76
T 77   // add a button to the button list
78   this.register_button = function(command, id, type, act, sel, over)
8fa922 79   {
4e17e6 80     if (!this.buttons[command])
8fa922 81       this.buttons[command] = [];
A 82
4e17e6 83     var button_prop = {id:id, type:type};
T 84     if (act) button_prop.act = act;
85     if (sel) button_prop.sel = sel;
86     if (over) button_prop.over = over;
87
0e7b66 88     this.buttons[command].push(button_prop);
699a25 89
e639c5 90     if (this.loaded)
T 91       init_button(command, button_prop);
8fa922 92   };
4e17e6 93
T 94   // register a specific gui object
95   this.gui_object = function(name, id)
8fa922 96   {
e639c5 97     this.gui_objects[name] = this.loaded ? rcube_find_object(id) : id;
8fa922 98   };
A 99
cc97ea 100   // register a container object
T 101   this.gui_container = function(name, id)
102   {
103     this.gui_containers[name] = id;
104   };
8fa922 105
cc97ea 106   // add a GUI element (html node) to a specified container
T 107   this.add_element = function(elm, container)
108   {
109     if (this.gui_containers[container] && this.gui_containers[container].jquery)
110       this.gui_containers[container].append(elm);
111   };
112
113   // register an external handler for a certain command
114   this.register_command = function(command, callback, enable)
115   {
116     this.command_handlers[command] = callback;
8fa922 117
cc97ea 118     if (enable)
T 119       this.enable_command(command, true);
120   };
8fa922 121
a7d5c6 122   // execute the given script on load
T 123   this.add_onload = function(f)
cc97ea 124   {
0e7b66 125     this.onloads.push(f);
cc97ea 126   };
4e17e6 127
T 128   // initialize webmail client
129   this.init = function()
8fa922 130   {
6b47de 131     var p = this;
4e17e6 132     this.task = this.env.task;
8fa922 133
4e17e6 134     // check browser
da8f11 135     if (!bw.dom || !bw.xmlhttp_test()) {
6b47de 136       this.goto_url('error', '_code=0x199');
4e17e6 137       return;
8fa922 138     }
9e953b 139
cc97ea 140     // find all registered gui containers
T 141     for (var n in this.gui_containers)
142       this.gui_containers[n] = $('#'+this.gui_containers[n]);
143
4e17e6 144     // find all registered gui objects
T 145     for (var n in this.gui_objects)
146       this.gui_objects[n] = rcube_find_object(this.gui_objects[n]);
8fa922 147
29f977 148     // init registered buttons
T 149     this.init_buttons();
a7d5c6 150
4e17e6 151     // tell parent window that this frame is loaded
27acfd 152     if (this.is_framed()) {
ad334a 153       parent.rcmail.set_busy(false, null, parent.rcmail.env.frame_lock);
A 154       parent.rcmail.env.frame_lock = null;
155     }
4e17e6 156
T 157     // enable general commands
3d74c1 158     this.enable_command('logout', 'mail', 'addressbook', 'settings', 'save-pref', 'compose', 'undo', true);
8fa922 159
a25d39 160     if (this.env.permaurl)
T 161       this.enable_command('permaurl', true);
9e953b 162
8fa922 163     switch (this.task) {
A 164
4e17e6 165       case 'mail':
f52c93 166         // enable mail commands
3d74c1 167         this.enable_command('list', 'checkmail', 'add-contact', 'search', 'reset-search', 'collapse-folder', true);
8fa922 168
A 169         if (this.gui_objects.messagelist) {
170
9800a8 171           this.message_list = new rcube_list_widget(this.gui_objects.messagelist, {
A 172             multiselect:true, multiexpand:true, draggable:true, keyboard:true,
6c9d49 173             column_movable:this.env.col_movable, dblclick_time:this.dblclick_time
9800a8 174             });
6b47de 175           this.message_list.row_init = function(o){ p.init_message_row(o); };
T 176           this.message_list.addEventListener('dblclick', function(o){ p.msglist_dbl_click(o); });
186537 177           this.message_list.addEventListener('click', function(o){ p.msglist_click(o); });
6b47de 178           this.message_list.addEventListener('keypress', function(o){ p.msglist_keypress(o); });
T 179           this.message_list.addEventListener('select', function(o){ p.msglist_select(o); });
b75488 180           this.message_list.addEventListener('dragstart', function(o){ p.drag_start(o); });
9489ad 181           this.message_list.addEventListener('dragmove', function(e){ p.drag_move(e); });
T 182           this.message_list.addEventListener('dragend', function(e){ p.drag_end(e); });
f52c93 183           this.message_list.addEventListener('expandcollapse', function(e){ p.msglist_expand(e); });
b62c48 184           this.message_list.addEventListener('column_replace', function(e){ p.msglist_set_coltypes(e); });
da8f11 185
cc97ea 186           document.onmouseup = function(e){ return p.doc_mouse_up(e); };
da8f11 187           this.gui_objects.messagelist.parentNode.onmousedown = function(e){ return p.click_on_list(e); };
6b47de 188
T 189           this.message_list.init();
f52c93 190           this.enable_command('toggle_status', 'toggle_flag', 'menu-open', 'menu-save', true);
8fa922 191
f52c93 192           // load messages
9800a8 193           this.command('list');
8fa922 194         }
1f020b 195
da8f11 196         if (this.gui_objects.qsearchbox) {
A 197           if (this.env.search_text != null) {
198             this.gui_objects.qsearchbox.value = this.env.search_text;
4e17e6 199           }
8fa922 200           $(this.gui_objects.qsearchbox).focusin(function() { rcmail.message_list.blur(); });
A 201         }
eb6842 202
7f554c 203         if (!this.env.flag_for_deletion && this.env.trash_mailbox && this.env.mailbox != this.env.trash_mailbox)
eb6842 204           this.set_alttext('delete', 'movemessagetotrash');
4e17e6 205
e25a35 206         this.env.message_commands = ['show', 'reply', 'reply-all', 'reply-list', 'forward',
A 207           'moveto', 'copy', 'delete', 'open', 'mark', 'edit', 'viewsource', 'download',
a208a4 208           'print', 'load-attachment', 'load-headers', 'forward-attachment'];
14259c 209
46cdbf 210         if (this.env.action == 'show' || this.env.action == 'preview') {
64e3e8 211           this.enable_command(this.env.message_commands, this.env.uid);
e25a35 212           this.enable_command('reply-list', this.env.list_post);
da8f11 213
29b397 214           if (this.env.action == 'show') {
A 215             this.http_request('pagenav', '_uid='+this.env.uid+'&_mbox='+urlencode(this.env.mailbox),
216               this.display_message('', 'loading'));
8fa922 217           }
A 218
da8f11 219           if (this.env.blockedobjects) {
A 220             if (this.gui_objects.remoteobjectsmsg)
221               this.gui_objects.remoteobjectsmsg.style.display = 'block';
222             this.enable_command('load-images', 'always-load', true);
8fa922 223           }
da8f11 224
A 225           // make preview/message frame visible
27acfd 226           if (this.env.action == 'preview' && this.is_framed()) {
da8f11 227             this.enable_command('compose', 'add-contact', false);
A 228             parent.rcmail.show_contentframe(true);
229           }
8fa922 230         }
da8f11 231         else if (this.env.action == 'compose') {
4591de 232           this.env.compose_commands = ['send-attachment', 'remove-attachment', 'send', 'cancel', 'toggle-editor'];
d7f9eb 233
A 234           if (this.env.drafts_mailbox)
235             this.env.compose_commands.push('savedraft')
236
237           this.enable_command(this.env.compose_commands, 'identities', true);
da8f11 238
A 239           if (this.env.spellcheck) {
86958f 240             this.env.spellcheck.spelling_state_observer = function(s){ ref.set_spellcheck_state(s); };
d7f9eb 241             this.env.compose_commands.push('spellcheck')
e170b4 242             this.set_spellcheck_state('ready');
cc97ea 243             if ($("input[name='_is_html']").val() == '1')
4ca10b 244               this.display_spellcheck_controls(false);
8fa922 245           }
A 246
69ad1e 247           document.onmouseup = function(e){ return p.doc_mouse_up(e); };
f05834 248
A 249           // init message compose form
250           this.init_messageform();
8fa922 251         }
da8f11 252         // show printing dialog
64e3e8 253         else if (this.env.action == 'print' && this.env.uid)
951960 254           if (bw.safari)
T 255             window.setTimeout('window.print()', 10);
256           else
257             window.print();
4e17e6 258
15a9d1 259         // get unread count for each mailbox
da8f11 260         if (this.gui_objects.mailboxlist) {
85360d 261           this.env.unread_counts = {};
f11541 262           this.gui_objects.folderlist = this.gui_objects.mailboxlist;
15a9d1 263           this.http_request('getunread', '');
8fa922 264         }
A 265
fba1f5 266         // ask user to send MDN
da8f11 267         if (this.env.mdn_request && this.env.uid) {
fba1f5 268           var mdnurl = '_uid='+this.env.uid+'&_mbox='+urlencode(this.env.mailbox);
T 269           if (confirm(this.get_label('mdnrequest')))
270             this.http_post('sendmdn', mdnurl);
271           else
272             this.http_post('mark', mdnurl+'&_flag=mdnsent');
8fa922 273         }
4e17e6 274
T 275         break;
276
277       case 'addressbook':
a61bbb 278         if (this.gui_objects.folderlist)
T 279           this.env.contactfolders = $.extend($.extend({}, this.env.address_sources), this.env.contactgroups);
8fa922 280
487173 281         this.enable_command('add', 'import', this.env.writable_source);
A 282         this.enable_command('list', 'listgroup', 'listsearch', 'advanced-search', true);
283
8fa922 284         if (this.gui_objects.contactslist) {
A 285
a61bbb 286           this.contact_list = new rcube_list_widget(this.gui_objects.contactslist,
T 287             {multiselect:true, draggable:this.gui_objects.folderlist?true:false, keyboard:true});
465d38 288           this.contact_list.row_init = function(row){ p.triggerEvent('insertrow', { cid:row.uid, row:row }); };
6b47de 289           this.contact_list.addEventListener('keypress', function(o){ p.contactlist_keypress(o); });
T 290           this.contact_list.addEventListener('select', function(o){ p.contactlist_select(o); });
b75488 291           this.contact_list.addEventListener('dragstart', function(o){ p.drag_start(o); });
9489ad 292           this.contact_list.addEventListener('dragmove', function(e){ p.drag_move(e); });
T 293           this.contact_list.addEventListener('dragend', function(e){ p.drag_end(e); });
6b47de 294           this.contact_list.init();
d1d2c4 295
6b47de 296           if (this.env.cid)
T 297             this.contact_list.highlight_row(this.env.cid);
298
da8f11 299           this.gui_objects.contactslist.parentNode.onmousedown = function(e){ return p.click_on_list(e); };
A 300           document.onmouseup = function(e){ return p.doc_mouse_up(e); };
301           if (this.gui_objects.qsearchbox) {
302             $(this.gui_objects.qsearchbox).focusin(function() { rcmail.contact_list.blur(); });
6b47de 303           }
9382b6 304
62811c 305           this.update_group_commands();
487173 306           this.command('list');
8fa922 307         }
d1d2c4 308
4e17e6 309         this.set_page_buttons();
8fa922 310
cb7d32 311         if (this.env.cid) {
4e17e6 312           this.enable_command('show', 'edit', true);
cb7d32 313           // register handlers for group assignment via checkboxes
T 314           if (this.gui_objects.editform) {
2c77f5 315             $('input.groupmember').change(function() {
A 316               ref.group_member_change(this.checked ? 'add' : 'del', ref.env.cid, ref.env.source, this.value);
cb7d32 317             });
T 318           }
319         }
4e17e6 320
e9a9f2 321         if (this.gui_objects.editform) {
4e17e6 322           this.enable_command('save', true);
e9a9f2 323           if (this.env.action == 'add' || this.env.action == 'edit')
A 324               this.init_contact_form();
9d2a3a 325         }
487173 326
e9a9f2 327         if (this.gui_objects.qsearchbox) {
d4a2c0 328           this.enable_command('search', 'reset-search', 'moveto', true);
9d2a3a 329         }
8fa922 330
4e17e6 331         break;
T 332
333       case 'settings':
334         this.enable_command('preferences', 'identities', 'save', 'folders', true);
8fa922 335
e50551 336         if (this.env.action == 'identities') {
ec0171 337           this.enable_command('add', this.env.identities_level < 2);
875ac8 338         }
e50551 339         else if (this.env.action == 'edit-identity' || this.env.action == 'add-identity') {
f05834 340           this.enable_command('add', this.env.identities_level < 2);
3940ba 341           this.enable_command('save', 'delete', 'edit', 'toggle-editor', true);
875ac8 342         }
e50551 343         else if (this.env.action == 'folders') {
af3c04 344           this.enable_command('subscribe', 'unsubscribe', 'create-folder', 'rename-folder', true);
A 345         }
346         else if (this.env.action == 'edit-folder' && this.gui_objects.editform) {
347           this.enable_command('save', 'folder-size', true);
348           parent.rcmail.env.messagecount = this.env.messagecount;
349           parent.rcmail.enable_command('purge', this.env.messagecount);
350           $("input[type='text']").first().select();
351         }
6b47de 352
fb4663 353         if (this.gui_objects.identitieslist) {
6b47de 354           this.identity_list = new rcube_list_widget(this.gui_objects.identitieslist, {multiselect:false, draggable:false, keyboard:false});
T 355           this.identity_list.addEventListener('select', function(o){ p.identity_select(o); });
356           this.identity_list.init();
357           this.identity_list.focus();
358
359           if (this.env.iid)
360             this.identity_list.highlight_row(this.env.iid);
fb4663 361         }
A 362         else if (this.gui_objects.sectionslist) {
f05834 363           this.sections_list = new rcube_list_widget(this.gui_objects.sectionslist, {multiselect:false, draggable:false, keyboard:false});
A 364           this.sections_list.addEventListener('select', function(o){ p.section_select(o); });
365           this.sections_list.init();
366           this.sections_list.focus();
875ac8 367         }
f05834 368         else if (this.gui_objects.subscriptionlist)
b0dbf3 369           this.init_subscription_list();
S 370
4e17e6 371         break;
T 372
373       case 'login':
cc97ea 374         var input_user = $('#rcmloginuser');
74a2d7 375         input_user.bind('keyup', function(e){ return rcmail.login_user_keyup(e); });
8fa922 376
cc97ea 377         if (input_user.val() == '')
4e17e6 378           input_user.focus();
cc97ea 379         else
T 380           $('#rcmloginpwd').focus();
c8ae24 381
T 382         // detect client timezone
65082b 383         var tz = new Date().getTimezoneOffset() / -60;
T 384         var stdtz = new Date().getStdTimezoneOffset() / -60;
385         $('#rcmlogintz').val(stdtz);
801b69 386         $('#rcmlogindst').val(tz > stdtz ? 1 : 0);
c8ae24 387
effdb3 388         // display 'loading' message on form submit, lock submit button
e94706 389         $('form').submit(function () {
491133 390           $('input[type=submit]', this).prop('disabled', true);
effdb3 391           rcmail.display_message('', 'loading');
A 392         });
cecf46 393
4e17e6 394         this.enable_command('login', true);
T 395         break;
8fa922 396
4e17e6 397       default:
T 398         break;
399       }
400
3ef524 401     // prevent from form submit with Enter key in file input fields
A 402     if (bw.ie)
403       $('input[type=file]').keydown(function(e) { if (e.keyCode == '13') e.preventDefault(); });
404
4e17e6 405     // flag object as complete
T 406     this.loaded = true;
9a5261 407
4e17e6 408     // show message
T 409     if (this.pending_message)
7f5a84 410       this.display_message(this.pending_message[0], this.pending_message[1], this.pending_message[2]);
8fa922 411
cc97ea 412     // map implicit containers
T 413     if (this.gui_objects.folderlist)
414       this.gui_containers.foldertray = $(this.gui_objects.folderlist);
9a5261 415
cc97ea 416     // trigger init event hook
T 417     this.triggerEvent('init', { task:this.task, action:this.env.action });
8fa922 418
a7d5c6 419     // execute all foreign onload scripts
cc97ea 420     // @deprecated
0e7b66 421     for (var i in this.onloads) {
d8cf6d 422       if (typeof this.onloads[i] === 'string')
a7d5c6 423         eval(this.onloads[i]);
d8cf6d 424       else if (typeof this.onloads[i] === 'function')
a7d5c6 425         this.onloads[i]();
T 426       }
cc97ea 427
T 428     // start keep-alive interval
429     this.start_keepalive();
430   };
4e17e6 431
b0eb95 432   this.log = function(msg)
A 433   {
434     if (window.console && console.log)
435       console.log(msg);
436   };
4e17e6 437
T 438   /*********************************************************/
439   /*********       client command interface        *********/
440   /*********************************************************/
441
442   // execute a specific command on the web client
443   this.command = function(command, props, obj)
8fa922 444   {
14d494 445     var ret;
A 446
4e17e6 447     if (obj && obj.blur)
T 448       obj.blur();
449
450     if (this.busy)
451       return false;
452
453     // command not supported or allowed
8fa922 454     if (!this.commands[command]) {
4e17e6 455       // pass command to parent window
27acfd 456       if (this.is_framed())
4e17e6 457         parent.rcmail.command(command, props);
T 458
459       return false;
8fa922 460     }
A 461
462     // check input before leaving compose step
46cdbf 463     if (this.task == 'mail' && this.env.action == 'compose' && $.inArray(command, this.env.compose_commands)<0) {
8fa922 464       if (this.cmp_hash != this.compose_field_hash() && !confirm(this.get_label('notsentwarning')))
15a9d1 465         return false;
8fa922 466     }
15a9d1 467
cc97ea 468     // process external commands
d8cf6d 469     if (typeof this.command_handlers[command] === 'function') {
14d494 470       ret = this.command_handlers[command](props, obj);
d8cf6d 471       return ret !== undefined ? ret : (obj ? false : true);
cc97ea 472     }
d8cf6d 473     else if (typeof this.command_handlers[command] === 'string') {
14d494 474       ret = window[this.command_handlers[command]](props, obj);
d8cf6d 475       return ret !== undefined ? ret : (obj ? false : true);
cc97ea 476     }
8fa922 477
2bb1f6 478     // trigger plugin hooks
A 479     this.triggerEvent('actionbefore', {props:props, action:command});
14d494 480     ret = this.triggerEvent('before'+command, props);
7fc056 481     if (ret !== undefined) {
14d494 482       // abort if one of the handlers returned false
7fc056 483       if (ret === false)
cc97ea 484         return false;
T 485       else
7fc056 486         props = ret;
cc97ea 487     }
14d494 488
A 489     ret = undefined;
cc97ea 490
T 491     // process internal command
8fa922 492     switch (command) {
A 493
4e17e6 494       case 'login':
T 495         if (this.gui_objects.loginform)
496           this.gui_objects.loginform.submit();
497         break;
498
499       // commands to switch task
500       case 'mail':
501       case 'addressbook':
502       case 'settings':
3a2b27 503       case 'logout':
4e17e6 504         this.switch_task(command);
T 505         break;
506
a25d39 507       case 'permaurl':
T 508         if (obj && obj.href && obj.target)
509           return true;
510         else if (this.env.permaurl)
511           parent.location.href = this.env.permaurl;
512         break;
513
f52c93 514       case 'menu-open':
T 515       case 'menu-save':
a61bbb 516         this.triggerEvent(command, {props:props});
T 517         return false;
f52c93 518
49dfb0 519       case 'open':
a25d39 520         var uid;
da8f11 521         if (uid = this.get_single_uid()) {
a25d39 522           obj.href = '?_task='+this.env.task+'&_action=show&_mbox='+urlencode(this.env.mailbox)+'&_uid='+uid;
T 523           return true;
524         }
525         break;
4e17e6 526
T 527       case 'list':
f8e48d 528         this.reset_qsearch();
A 529         if (this.task == 'mail') {
2483a8 530           this.list_mailbox(props);
eb6842 531
7f554c 532           if (this.env.trash_mailbox && !this.env.flag_for_deletion)
eb6842 533             this.set_alttext('delete', this.env.mailbox != this.env.trash_mailbox ? 'movemessagetotrash' : 'deletemessage');
da8f11 534         }
ecf295 535         else if (this.task == 'addressbook') {
f11541 536           this.list_contacts(props);
da8f11 537         }
a61bbb 538         break;
T 539
e5686f 540       case 'load-headers':
A 541         this.load_headers(obj);
f3b659 542         break;
T 543
544       case 'sort':
23387e 545         var sort_order, sort_col = props;
d59aaa 546
23387e 547         if (this.env.sort_col==sort_col)
9f3579 548           sort_order = this.env.sort_order=='ASC' ? 'DESC' : 'ASC';
23387e 549         else
5e9a56 550           sort_order = 'ASC';
T 551
f52c93 552         // set table header and update env
T 553         this.set_list_sorting(sort_col, sort_order);
b076a4 554
T 555         // reload message list
1cded8 556         this.list_mailbox('', '', sort_col+'_'+sort_order);
4e17e6 557         break;
T 558
559       case 'nextpage':
560         this.list_page('next');
561         break;
562
d17008 563       case 'lastpage':
S 564         this.list_page('last');
565         break;
566
4e17e6 567       case 'previouspage':
T 568         this.list_page('prev');
d17008 569         break;
S 570
571       case 'firstpage':
572         this.list_page('first');
15a9d1 573         break;
T 574
575       case 'expunge':
576         if (this.env.messagecount)
577           this.expunge_mailbox(this.env.mailbox);
578         break;
579
5e3512 580       case 'purge':
T 581       case 'empty-mailbox':
582         if (this.env.messagecount)
583           this.purge_mailbox(this.env.mailbox);
4e17e6 584         break;
T 585
586       // common commands used in multiple tasks
587       case 'show':
e9a9f2 588         if (this.task == 'mail') {
4e17e6 589           var uid = this.get_single_uid();
da8f11 590           if (uid && (!this.env.uid || uid != this.env.uid)) {
6b47de 591             if (this.env.mailbox == this.env.drafts_mailbox)
T 592               this.goto_url('compose', '_draft_uid='+uid+'&_mbox='+urlencode(this.env.mailbox), true);
1966c5 593             else
S 594               this.show_message(uid);
4e17e6 595           }
da8f11 596         }
e9a9f2 597         else if (this.task == 'addressbook') {
4e17e6 598           var cid = props ? props : this.get_single_cid();
e9a9f2 599           if (cid && !(this.env.action == 'show' && cid == this.env.cid))
4e17e6 600             this.load_contact(cid, 'show');
da8f11 601         }
4e17e6 602         break;
T 603
604       case 'add':
e9a9f2 605         if (this.task == 'addressbook')
6b47de 606           this.load_contact(0, 'add');
e9a9f2 607         else if (this.task == 'settings') {
6b47de 608           this.identity_list.clear_selection();
4e17e6 609           this.load_identity(0, 'add-identity');
da8f11 610         }
4e17e6 611         break;
T 612
613       case 'edit':
614         var cid;
615         if (this.task=='addressbook' && (cid = this.get_single_cid()))
616           this.load_contact(cid, 'edit');
617         else if (this.task=='settings' && props)
618           this.load_identity(props, 'edit-identity');
069704 619         else if (this.task=='mail' && (cid = this.get_single_uid())) {
141c9e 620           var url = (this.env.mailbox == this.env.drafts_mailbox) ? '_draft_uid=' : '_uid=';
069704 621           this.goto_url('compose', url+cid+'&_mbox='+urlencode(this.env.mailbox), true);
141c9e 622         }
4e17e6 623         break;
T 624
625       case 'save':
e9a9f2 626         var input, form = this.gui_objects.editform;
A 627         if (form) {
628           // adv. search
629           if (this.env.action == 'search') {
630           }
10a699 631           // user prefs
e9a9f2 632           else if ((input = $("input[name='_pagesize']", form)) && input.length && isNaN(parseInt(input.val()))) {
10a699 633             alert(this.get_label('nopagesizewarning'));
e9a9f2 634             input.focus();
10a699 635             break;
8fa922 636           }
10a699 637           // contacts/identities
8fa922 638           else {
1a3c91 639             // reload form
5b3ac3 640             if (props == 'reload') {
A 641               form.action += '?_reload=1';
642             }
e9a9f2 643             else if (this.task == 'settings' && (this.env.identities_level % 2) == 0  &&
1a3c91 644               (input = $("input[name='_email']", form)) && input.length && !rcube_check_email(input.val())
e9a9f2 645             ) {
10a699 646               alert(this.get_label('noemailwarning'));
e9a9f2 647               input.focus();
10a699 648               break;
T 649             }
4737e5 650
0501b6 651             // clear empty input fields
T 652             $('input.placeholder').each(function(){ if (this.value == this._placeholder) this.value = ''; });
8fa922 653           }
1a3c91 654
A 655           // add selected source (on the list)
656           if (parent.rcmail && parent.rcmail.env.source)
657             form.action = this.add_url(form.action, '_orig_source', parent.rcmail.env.source);
10a699 658
e9a9f2 659           form.submit();
8fa922 660         }
4e17e6 661         break;
T 662
663       case 'delete':
664         // mail task
476407 665         if (this.task == 'mail')
4e17e6 666           this.delete_messages();
T 667         // addressbook task
476407 668         else if (this.task == 'addressbook')
4e17e6 669           this.delete_contacts();
T 670         // user settings task
476407 671         else if (this.task == 'settings')
4e17e6 672           this.delete_identity();
T 673         break;
674
675       // mail task commands
676       case 'move':
677       case 'moveto':
f11541 678         if (this.task == 'mail')
T 679           this.move_messages(props);
680         else if (this.task == 'addressbook' && this.drag_active)
681           this.copy_contact(null, props);
9b3fdc 682         break;
A 683
684       case 'copy':
685         if (this.task == 'mail')
686           this.copy_messages(props);
4e17e6 687         break;
b85bf8 688
T 689       case 'mark':
690         if (props)
691           this.mark_message(props);
692         break;
8fa922 693
857a38 694       case 'toggle_status':
4e17e6 695         if (props && !props._row)
T 696           break;
8fa922 697
A 698         var uid, flag = 'read';
699
da8f11 700         if (props._row.uid) {
4e17e6 701           uid = props._row.uid;
8fa922 702
4e17e6 703           // toggle read/unread
6b47de 704           if (this.message_list.rows[uid].deleted) {
T 705             flag = 'undelete';
4e17e6 706           }
da8f11 707           else if (!this.message_list.rows[uid].unread)
A 708             flag = 'unread';
709         }
8fa922 710
4e17e6 711         this.mark_message(flag, uid);
T 712         break;
8fa922 713
e189a6 714       case 'toggle_flag':
A 715         if (props && !props._row)
716           break;
717
8fa922 718         var uid, flag = 'flagged';
e189a6 719
da8f11 720         if (props._row.uid) {
e189a6 721           uid = props._row.uid;
A 722           // toggle flagged/unflagged
723           if (this.message_list.rows[uid].flagged)
724             flag = 'unflagged';
725           }
726         this.mark_message(flag, uid);
727         break;
728
62e43d 729       case 'always-load':
T 730         if (this.env.uid && this.env.sender) {
731           this.add_contact(urlencode(this.env.sender));
732           window.setTimeout(function(){ ref.command('load-images'); }, 300);
733           break;
734         }
8fa922 735
4e17e6 736       case 'load-images':
T 737         if (this.env.uid)
b19097 738           this.show_message(this.env.uid, true, this.env.action=='preview');
4e17e6 739         break;
T 740
741       case 'load-attachment':
c5418b 742         var qstring = '_mbox='+urlencode(this.env.mailbox)+'&_uid='+this.env.uid+'&_part='+props.part;
8fa922 743
4e17e6 744         // open attachment in frame if it's of a supported mimetype
f7b58a 745         if (this.env.uid && props.mimetype && this.env.mimetypes && $.inArray(props.mimetype, this.env.mimetypes)>=0) {
cfdf04 746           if (props.mimetype == 'text/html')
853b2e 747             qstring += '&_safe=1';
b19097 748           this.attachment_win = window.open(this.env.comm_path+'&_action=get&'+qstring+'&_frame=1', 'rcubemailattachment');
da8f11 749           if (this.attachment_win) {
dbbd1f 750             window.setTimeout(function(){ ref.attachment_win.focus(); }, 10);
4e17e6 751             break;
T 752           }
da8f11 753         }
4e17e6 754
4b9efb 755         this.goto_url('get', qstring+'&_download=1', false);
4e17e6 756         break;
8fa922 757
4e17e6 758       case 'select-all':
fb7ec5 759         this.select_all_mode = props ? false : true;
196d04 760         this.dummy_select = true; // prevent msg opening if there's only one msg on the list
528185 761         if (props == 'invert')
A 762           this.message_list.invert_selection();
141c9e 763         else
fb7ec5 764           this.message_list.select_all(props == 'page' ? '' : props);
196d04 765         this.dummy_select = null;
4e17e6 766         break;
T 767
768       case 'select-none':
349cbf 769         this.select_all_mode = false;
6b47de 770         this.message_list.clear_selection();
f52c93 771         break;
T 772
773       case 'expand-all':
774         this.env.autoexpand_threads = 1;
775         this.message_list.expand_all();
776         break;
777
778       case 'expand-unread':
779         this.env.autoexpand_threads = 2;
780         this.message_list.collapse_all();
781         this.expand_unread();
782         break;
783
784       case 'collapse-all':
785         this.env.autoexpand_threads = 0;
786         this.message_list.collapse_all();
4e17e6 787         break;
T 788
789       case 'nextmessage':
790         if (this.env.next_uid)
b19097 791           this.show_message(this.env.next_uid, false, this.env.action=='preview');
4e17e6 792         break;
T 793
a7d5c6 794       case 'lastmessage':
d17008 795         if (this.env.last_uid)
S 796           this.show_message(this.env.last_uid);
797         break;
798
4e17e6 799       case 'previousmessage':
T 800         if (this.env.prev_uid)
b19097 801           this.show_message(this.env.prev_uid, false, this.env.action=='preview');
d17008 802         break;
S 803
804       case 'firstmessage':
805         if (this.env.first_uid)
806           this.show_message(this.env.first_uid);
4e17e6 807         break;
8fa922 808
c8c1e0 809       case 'checkmail':
41b43b 810         this.check_for_recent(true);
c8c1e0 811         break;
8fa922 812
4e17e6 813       case 'compose':
3d74c1 814         var url = this.url('mail/compose');
8fa922 815
cf58ce 816         if (this.task == 'mail') {
a9ab9f 817           url += '&_mbox='+urlencode(this.env.mailbox);
46cdbf 818           if (props)
a9ab9f 819              url += '&_to='+urlencode(props);
T 820         }
4e17e6 821         // modify url if we're in addressbook
cf58ce 822         else if (this.task == 'addressbook') {
f11541 823           // switch to mail compose step directly
da8f11 824           if (props && props.indexOf('@') > 0) {
f11541 825             url = this.get_task_url('mail', url);
T 826             this.redirect(url + '&_to='+urlencode(props));
827             break;
da8f11 828           }
8fa922 829
4e17e6 830           // use contact_id passed as command parameter
cf58ce 831           var n, len, a_cids = [];
4e17e6 832           if (props)
0e7b66 833             a_cids.push(props);
4e17e6 834           // get selected contacts
da8f11 835           else if (this.contact_list) {
6b47de 836             var selection = this.contact_list.get_selection();
cf58ce 837             for (n=0, len=selection.length; n<len; n++)
0e7b66 838               a_cids.push(selection[n]);
da8f11 839           }
8fa922 840
4e17e6 841           if (a_cids.length)
dc6c4f 842             this.http_post('mailto', { _cid: a_cids.join(','), _source: this.env.source}, true);
T 843           else if (this.env.group)
844             this.http_post('mailto', { _gid: this.env.group, _source: this.env.source}, true);
f11541 845
T 846           break;
da8f11 847         }
ef4998 848         else if (props)
T 849           url += '&_to='+urlencode(props);
d1d2c4 850
f11541 851         this.redirect(url);
ed5d29 852         break;
8fa922 853
ed5d29 854       case 'spellcheck':
a01b3b 855         if (window.tinyMCE && tinyMCE.get(this.env.composebody)) {
4ca10b 856           tinyMCE.execCommand('mceSpellCheck', true);
T 857         }
858         else if (this.env.spellcheck && this.env.spellcheck.spellCheck && this.spellcheck_ready) {
ffa6c1 859           this.env.spellcheck.spellCheck();
309d2f 860           this.set_spellcheck_state('checking');
4ca10b 861         }
ed5d29 862         break;
4e17e6 863
1966c5 864       case 'savedraft':
41fa0b 865         // Reset the auto-save timer
T 866         self.clearTimeout(this.save_timer);
f0f98f 867
1966c5 868         if (!this.gui_objects.messageform)
S 869           break;
f0f98f 870
41fa0b 871         // if saving Drafts is disabled in main.inc.php
e170b4 872         // or if compose form did not change
T 873         if (!this.env.drafts_mailbox || this.cmp_hash == this.compose_field_hash())
41fa0b 874           break;
f0f98f 875
ad334a 876         var form = this.gui_objects.messageform,
A 877           msgid = this.set_busy(true, 'savingmessage');
878
41fa0b 879         form.target = "savetarget";
4d0413 880         form._draft.value = '1';
ad334a 881         form.action = this.add_url(form.action, '_unlock', msgid);
1966c5 882         form.submit();
S 883         break;
884
4e17e6 885       case 'send':
T 886         if (!this.gui_objects.messageform)
887           break;
41fa0b 888
977a29 889         if (!this.check_compose_input())
10a699 890           break;
4315b0 891
9a5261 892         // Reset the auto-save timer
T 893         self.clearTimeout(this.save_timer);
10a699 894
T 895         // all checks passed, send message
644e3a 896         var lang = this.spellcheck_lang(),
A 897           form = this.gui_objects.messageform,
ad334a 898           msgid = this.set_busy(true, 'sendingmessage');
A 899
2bb1f6 900         form.target = 'savetarget';
41fa0b 901         form._draft.value = '';
ad334a 902         form.action = this.add_url(form.action, '_unlock', msgid);
644e3a 903         form.action = this.add_url(form.action, '_lang', lang);
10a699 904         form.submit();
8fa922 905
9a5261 906         // clear timeout (sending could take longer)
T 907         clearTimeout(this.request_timer);
50f56d 908         break;
8fa922 909
4e17e6 910       case 'send-attachment':
f0f98f 911         // Reset the auto-save timer
41fa0b 912         self.clearTimeout(this.save_timer);
f0f98f 913
2bb1f6 914         this.upload_file(props)
4e17e6 915         break;
8fa922 916
0207c4 917       case 'insert-sig':
T 918         this.change_identity($("[name='_from']")[0], true);
a894ba 919         break;
4e17e6 920
583f1c 921       case 'reply-all':
e25a35 922       case 'reply-list':
4e17e6 923       case 'reply':
T 924         var uid;
e25a35 925         if (uid = this.get_single_uid()) {
A 926           var url = '_reply_uid='+uid+'&_mbox='+urlencode(this.env.mailbox);
927           if (command == 'reply-all')
928             // do reply-list, when list is detected and popup menu wasn't used 
929             url += '&_all=' + (!props && this.commands['reply-list'] ? 'list' : 'all');
930           else if (command == 'reply-list')
931             url += '&_all=list';
932
933           this.goto_url('compose', url, true);
934         }
2bb1f6 935         break;
4e17e6 936
a208a4 937       case 'forward-attachment':
4e17e6 938       case 'forward':
a509bb 939         var uid, url;
A 940         if (uid = this.get_single_uid()) {
941           url = '_forward_uid='+uid+'&_mbox='+urlencode(this.env.mailbox);
942           if (command == 'forward-attachment' || (!props && this.env.forward_attachment))
943             url += '&_attachment=1';
944           this.goto_url('compose', url, true);
945         }
4e17e6 946         break;
8fa922 947
4e17e6 948       case 'print':
T 949         var uid;
8fa922 950         if (uid = this.get_single_uid()) {
cfdf04 951           ref.printwin = window.open(this.env.comm_path+'&_action=print&_uid='+uid+'&_mbox='+urlencode(this.env.mailbox)+(this.env.safemode ? '&_safe=1' : ''));
8fa922 952           if (this.printwin) {
dbbd1f 953             window.setTimeout(function(){ ref.printwin.focus(); }, 20);
4d3f3b 954             if (this.env.action != 'show')
5d97ac 955               this.mark_message('read', uid);
4e17e6 956           }
4d3f3b 957         }
4e17e6 958         break;
T 959
960       case 'viewsource':
961         var uid;
8fa922 962         if (uid = this.get_single_uid()) {
49dfb0 963           ref.sourcewin = window.open(this.env.comm_path+'&_action=viewsource&_uid='+uid+'&_mbox='+urlencode(this.env.mailbox));
4e17e6 964           if (this.sourcewin)
dbbd1f 965             window.setTimeout(function(){ ref.sourcewin.focus(); }, 20);
4e17e6 966           }
49dfb0 967         break;
A 968
969       case 'download':
970         var uid;
971         if (uid = this.get_single_uid())
972           this.goto_url('viewsource', '&_uid='+uid+'&_mbox='+urlencode(this.env.mailbox)+'&_save=1');
4e17e6 973         break;
T 974
f11541 975       // quicksearch
4647e1 976       case 'search':
T 977         if (!props && this.gui_objects.qsearchbox)
978           props = this.gui_objects.qsearchbox.value;
8fa922 979         if (props) {
f11541 980           this.qsearch(props);
T 981           break;
982         }
4647e1 983
ed132e 984       // reset quicksearch
4647e1 985       case 'reset-search':
db0408 986         var n, s = this.env.search_request || this.env.qsearch;
5271bf 987
4647e1 988         this.reset_qsearch();
5271bf 989         this.select_all_mode = false;
8fa922 990
f11541 991         if (s && this.env.mailbox)
b7fd98 992           this.list_mailbox(this.env.mailbox, 1);
ecf295 993         else if (s && this.task == 'addressbook') {
A 994           if (this.env.source == '') {
db0408 995             for (n in this.env.address_sources) break;
ecf295 996             this.env.source = n;
A 997             this.env.group = '';
998           }
b7fd98 999           this.list_contacts(this.env.source, this.env.group, 1);
ecf295 1000         }
a61bbb 1001         break;
T 1002
edfe91 1003       case 'listgroup':
f8e48d 1004         this.reset_qsearch();
edfe91 1005         this.list_contacts(props.source, props.id);
4647e1 1006         break;
4e17e6 1007
ed132e 1008       case 'import':
T 1009         if (this.env.action == 'import' && this.gui_objects.importform) {
1010           var file = document.getElementById('rcmimportfile');
1011           if (file && !file.value) {
1012             alert(this.get_label('selectimportfile'));
1013             break;
1014           }
1015           this.gui_objects.importform.submit();
1016           this.set_busy(true, 'importwait');
1017           this.lock_form(this.gui_objects.importform, true);
1018         }
1019         else
d4a2c0 1020           this.goto_url('import', (this.env.source ? '_target='+urlencode(this.env.source)+'&' : ''));
0dbac3 1021         break;
8fa922 1022
0dbac3 1023       case 'export':
T 1024         if (this.contact_list.rowcount > 0) {
0501b6 1025           this.goto_url('export', { _source:this.env.source, _gid:this.env.group, _search:this.env.search_request });
0dbac3 1026         }
0501b6 1027         break;
4737e5 1028
0501b6 1029       case 'upload-photo':
T 1030         this.upload_contact_photo(props);
1031         break;
1032
1033       case 'delete-photo':
1034         this.replace_contact_photo('-del-');
0dbac3 1035         break;
ed132e 1036
4e17e6 1037       // user settings commands
T 1038       case 'preferences':
1039       case 'identities':
1040       case 'folders':
4737e5 1041         this.goto_url('settings/' + command);
4e17e6 1042         break;
T 1043
7f5a84 1044       case 'undo':
A 1045         this.http_request('undo', '', this.display_message('', 'loading'));
1046         break;
1047
edfe91 1048       // unified command call (command name == function name)
A 1049       default:
2fc459 1050         var func = command.replace(/-/g, '_');
14d494 1051         if (this[func] && typeof this[func] === 'function') {
A 1052           ret = this[func](props);
1053         }
4e17e6 1054         break;
8fa922 1055     }
A 1056
14d494 1057     if (this.triggerEvent('after'+command, props) === false)
A 1058       ret = false;
2bb1f6 1059     this.triggerEvent('actionafter', {props:props, action:command});
4e17e6 1060
14d494 1061     return ret === false ? false : obj ? false : true;
8fa922 1062   };
4e17e6 1063
14259c 1064   // set command(s) enabled or disabled
4e17e6 1065   this.enable_command = function()
8fa922 1066   {
d470f9 1067     var args = Array.prototype.slice.call(arguments),
A 1068       enable = args.pop(), cmd;
8fa922 1069
d470f9 1070     for (var n=0; n<args.length; n++) {
A 1071       cmd = args[n];
1072       // argument of type array
1073       if (typeof cmd === 'string') {
1074         this.commands[cmd] = enable;
1075         this.set_button(cmd, (enable ? 'act' : 'pas'));
14259c 1076       }
d470f9 1077       // push array elements into commands array
A 1078       else {
1079         for (var i in cmd)
1080           args.push(cmd[i]);
1081       }
8fa922 1082     }
A 1083   };
4e17e6 1084
a95e0e 1085   // lock/unlock interface
ad334a 1086   this.set_busy = function(a, message, id)
8fa922 1087   {
A 1088     if (a && message) {
10a699 1089       var msg = this.get_label(message);
fb4663 1090       if (msg == message)
10a699 1091         msg = 'Loading...';
T 1092
ad334a 1093       id = this.display_message(msg, 'loading');
8fa922 1094     }
ad334a 1095     else if (!a && id) {
A 1096       this.hide_message(id);
554d79 1097     }
4e17e6 1098
T 1099     this.busy = a;
1100     //document.body.style.cursor = a ? 'wait' : 'default';
8fa922 1101
4e17e6 1102     if (this.gui_objects.editform)
T 1103       this.lock_form(this.gui_objects.editform, a);
8fa922 1104
a95e0e 1105     // clear pending timer
T 1106     if (this.request_timer)
1107       clearTimeout(this.request_timer);
1108
1109     // set timer for requests
9a5261 1110     if (a && this.env.request_timeout)
dbbd1f 1111       this.request_timer = window.setTimeout(function(){ ref.request_timed_out(); }, this.env.request_timeout * 1000);
ad334a 1112
A 1113     return id;
8fa922 1114   };
4e17e6 1115
10a699 1116   // return a localized string
cc97ea 1117   this.get_label = function(name, domain)
8fa922 1118   {
cc97ea 1119     if (domain && this.labels[domain+'.'+name])
T 1120       return this.labels[domain+'.'+name];
1121     else if (this.labels[name])
10a699 1122       return this.labels[name];
T 1123     else
1124       return name;
8fa922 1125   };
A 1126
cc97ea 1127   // alias for convenience reasons
T 1128   this.gettext = this.get_label;
10a699 1129
T 1130   // switch to another application task
4e17e6 1131   this.switch_task = function(task)
8fa922 1132   {
01bb03 1133     if (this.task===task && task!='mail')
4e17e6 1134       return;
T 1135
01bb03 1136     var url = this.get_task_url(task);
T 1137     if (task=='mail')
1138       url += '&_mbox=INBOX';
1139
f11541 1140     this.redirect(url);
8fa922 1141   };
4e17e6 1142
T 1143   this.get_task_url = function(task, url)
8fa922 1144   {
4e17e6 1145     if (!url)
T 1146       url = this.env.comm_path;
1147
1148     return url.replace(/_task=[a-z]+/, '_task='+task);
8fa922 1149   };
A 1150
a95e0e 1151   // called when a request timed out
T 1152   this.request_timed_out = function()
8fa922 1153   {
a95e0e 1154     this.set_busy(false);
T 1155     this.display_message('Request timed out!', 'error');
8fa922 1156   };
A 1157
141c9e 1158   this.reload = function(delay)
T 1159   {
27acfd 1160     if (this.is_framed())
141c9e 1161       parent.rcmail.reload(delay);
T 1162     else if (delay)
1163       window.setTimeout(function(){ rcmail.reload(); }, delay);
1164     else if (window.location)
af3c04 1165       location.href = this.env.comm_path + (this.env.action ? '&_action='+this.env.action : '');
141c9e 1166   };
4e17e6 1167
ad334a 1168   // Add variable to GET string, replace old value if exists
A 1169   this.add_url = function(url, name, value)
1170   {
1171     value = urlencode(value);
1172
1173     if (/(\?.*)$/.test(url)) {
1174       var urldata = RegExp.$1,
1175         datax = RegExp('((\\?|&)'+RegExp.escape(name)+'=[^&]*)');
1176
1177       if (datax.test(urldata)) {
1178         urldata = urldata.replace(datax, RegExp.$2 + name + '=' + value);
1179       }
1180       else
1181         urldata += '&' + name + '=' + value
1182
1183       return url.replace(/(\?.*)$/, urldata);
1184     }
1185     else
1186       return url + '?' + name + '=' + value;
1187   };
27acfd 1188
A 1189   this.is_framed = function()
1190   {
0501b6 1191     return (this.env.framed && parent.rcmail && parent.rcmail != this && parent.rcmail.command);
27acfd 1192   };
A 1193
9b6c82 1194   this.save_pref = function(prop)
A 1195   {
4fb6a2 1196     var request = {'_name': prop.name, '_value': prop.value};
9b6c82 1197
A 1198     if (prop.session)
4fb6a2 1199       request['_session'] = prop.session;
9b6c82 1200     if (prop.env)
A 1201       this.env[prop.env] = prop.value;
1202
1203     this.http_post('save-pref', request);
1204   };
1205
4e17e6 1206
T 1207   /*********************************************************/
1208   /*********        event handling methods         *********/
1209   /*********************************************************/
1210
a61bbb 1211   this.drag_menu = function(e, target)
9b3fdc 1212   {
8fa922 1213     var modkey = rcube_event.get_modifier(e),
b6a069 1214       menu = this.gui_objects.message_dragmenu;
9b3fdc 1215
a61bbb 1216     if (menu && modkey == SHIFT_KEY && this.commands['copy']) {
9b3fdc 1217       var pos = rcube_event.get_mouse_pos(e);
a61bbb 1218       this.env.drag_target = target;
b6a069 1219       $(menu).css({top: (pos.y-10)+'px', left: (pos.x-10)+'px'}).show();
9b3fdc 1220       return true;
A 1221     }
8fa922 1222
a61bbb 1223     return false;
9b3fdc 1224   };
A 1225
1226   this.drag_menu_action = function(action)
1227   {
b6a069 1228     var menu = this.gui_objects.message_dragmenu;
9b3fdc 1229     if (menu) {
b6a069 1230       $(menu).hide();
9b3fdc 1231     }
a61bbb 1232     this.command(action, this.env.drag_target);
T 1233     this.env.drag_target = null;
f89f03 1234   };
f5aa16 1235
b75488 1236   this.drag_start = function(list)
f89f03 1237   {
a61bbb 1238     var model = this.task == 'mail' ? this.env.mailboxes : this.env.contactfolders;
b75488 1239
A 1240     this.drag_active = true;
3a003c 1241
b75488 1242     if (this.preview_timer)
A 1243       clearTimeout(this.preview_timer);
bc4960 1244     if (this.preview_read_timer)
T 1245       clearTimeout(this.preview_read_timer);
1246
b75488 1247     // save folderlist and folders location/sizes for droptarget calculation in drag_move()
d808ba 1248     if (this.gui_objects.folderlist && model) {
111be7 1249       this.initialBodyScrollTop = bw.ie ? 0 : window.pageYOffset;
A 1250       this.initialListScrollTop = this.gui_objects.folderlist.parentNode.scrollTop;
1251
b75488 1252       var li, pos, list, height;
cc97ea 1253       list = $(this.gui_objects.folderlist);
T 1254       pos = list.offset();
1255       this.env.folderlist_coords = { x1:pos.left, y1:pos.top, x2:pos.left + list.width(), y2:pos.top + list.height() };
b75488 1256
8fa922 1257       this.env.folder_coords = [];
f89f03 1258       for (var k in model) {
cc97ea 1259         if (li = this.get_folder_li(k)) {
72f5b1 1260           // only visible folders
0061e7 1261           if (height = li.firstChild.offsetHeight) {
72f5b1 1262             pos = $(li.firstChild).offset();
T 1263             this.env.folder_coords[k] = { x1:pos.left, y1:pos.top,
1264               x2:pos.left + li.firstChild.offsetWidth, y2:pos.top + height, on:0 };
1265           }
b75488 1266         }
f89f03 1267       }
cc97ea 1268     }
f89f03 1269   };
b75488 1270
91d1a1 1271   this.drag_end = function(e)
A 1272   {
1273     this.drag_active = false;
12989a 1274     this.env.last_folder_target = null;
8fa922 1275
72f5b1 1276     if (this.folder_auto_timer) {
T 1277       window.clearTimeout(this.folder_auto_timer);
1278       this.folder_auto_timer = null;
1279       this.folder_auto_expand = null;
1280     }
91d1a1 1281
A 1282     // over the folders
1283     if (this.gui_objects.folderlist && this.env.folder_coords) {
1284       for (var k in this.env.folder_coords) {
12989a 1285         if (this.env.folder_coords[k].on)
91d1a1 1286           $(this.get_folder_li(k)).removeClass('droptarget');
A 1287       }
1288     }
1289   };
8fa922 1290
b75488 1291   this.drag_move = function(e)
cc97ea 1292   {
T 1293     if (this.gui_objects.folderlist && this.env.folder_coords) {
432a61 1294       // offsets to compensate for scrolling while dragging a message
A 1295       var boffset = bw.ie ? -document.documentElement.scrollTop : this.initialBodyScrollTop;
111be7 1296       var moffset = this.initialListScrollTop-this.gui_objects.folderlist.parentNode.scrollTop;
432a61 1297       var toffset = -moffset-boffset;
ca38db 1298       var li, div, pos, mouse, check, oldclass,
T 1299         layerclass = 'draglayernormal';
7f5a84 1300
ca38db 1301       if (this.contact_list && this.contact_list.draglayer)
T 1302         oldclass = this.contact_list.draglayer.attr('class');
8fa922 1303
b75488 1304       mouse = rcube_event.get_mouse_pos(e);
A 1305       pos = this.env.folderlist_coords;
432a61 1306       mouse.y += toffset;
A 1307
b75488 1308       // if mouse pointer is outside of folderlist
cc97ea 1309       if (mouse.x < pos.x1 || mouse.x >= pos.x2 || mouse.y < pos.y1 || mouse.y >= pos.y2) {
72f5b1 1310         if (this.env.last_folder_target) {
cc97ea 1311           $(this.get_folder_li(this.env.last_folder_target)).removeClass('droptarget');
72f5b1 1312           this.env.folder_coords[this.env.last_folder_target].on = 0;
T 1313           this.env.last_folder_target = null;
1314         }
ca38db 1315         if (layerclass != oldclass && this.contact_list && this.contact_list.draglayer)
T 1316           this.contact_list.draglayer.attr('class', layerclass);
cc97ea 1317         return;
T 1318       }
8fa922 1319
b75488 1320       // over the folders
cc97ea 1321       for (var k in this.env.folder_coords) {
T 1322         pos = this.env.folder_coords[k];
bb8012 1323         if (mouse.x >= pos.x1 && mouse.x < pos.x2 && mouse.y >= pos.y1 && mouse.y < pos.y2){
ca38db 1324          if ((check = this.check_droptarget(k))) {
bb8012 1325             li = this.get_folder_li(k);
A 1326             div = $(li.getElementsByTagName('div')[0]);
72f5b1 1327
bb8012 1328             // if the folder is collapsed, expand it after 1sec and restart the drag & drop process.
A 1329             if (div.hasClass('collapsed')) {
1330               if (this.folder_auto_timer)
1331                 window.clearTimeout(this.folder_auto_timer);
72f5b1 1332
bb8012 1333               this.folder_auto_expand = k;
A 1334               this.folder_auto_timer = window.setTimeout(function() {
1335                   rcmail.command('collapse-folder', rcmail.folder_auto_expand);
1336                   rcmail.drag_start(null);
1337                 }, 1000);
1338             } else if (this.folder_auto_timer) {
72f5b1 1339               window.clearTimeout(this.folder_auto_timer);
bb8012 1340               this.folder_auto_timer = null;
A 1341               this.folder_auto_expand = null;
1342             }
8fa922 1343
bb8012 1344             $(li).addClass('droptarget');
A 1345             this.env.folder_coords[k].on = 1;
1346             this.env.last_folder_target = k;
ca38db 1347             layerclass = 'draglayer' + (check > 1 ? 'copy' : 'normal');
bb8012 1348           } else { // Clear target, otherwise drag end will trigger move into last valid droptarget
A 1349             this.env.last_folder_target = null;
72f5b1 1350           }
T 1351         }
12989a 1352         else if (pos.on) {
72f5b1 1353           $(this.get_folder_li(k)).removeClass('droptarget');
T 1354           this.env.folder_coords[k].on = 0;
1355         }
b75488 1356       }
176c76 1357
ca38db 1358       if (layerclass != oldclass && this.contact_list && this.contact_list.draglayer)
T 1359         this.contact_list.draglayer.attr('class', layerclass);
cc97ea 1360     }
T 1361   };
0061e7 1362
f5aa16 1363   this.collapse_folder = function(id)
da8f11 1364   {
8fa922 1365     var li = this.get_folder_li(id),
A 1366       div = $(li.getElementsByTagName('div')[0]);
1367
da8f11 1368     if (!div || (!div.hasClass('collapsed') && !div.hasClass('expanded')))
A 1369       return;
8fa922 1370
da8f11 1371     var ul = $(li.getElementsByTagName('ul')[0]);
8fa922 1372
da8f11 1373     if (div.hasClass('collapsed')) {
A 1374       ul.show();
1375       div.removeClass('collapsed').addClass('expanded');
1376       var reg = new RegExp('&'+urlencode(id)+'&');
9f07d1 1377       this.env.collapsed_folders = this.env.collapsed_folders.replace(reg, '');
da8f11 1378     }
A 1379     else {
1380       ul.hide();
1381       div.removeClass('expanded').addClass('collapsed');
9f07d1 1382       this.env.collapsed_folders = this.env.collapsed_folders+'&'+urlencode(id)+'&';
f1f17f 1383
da8f11 1384       // select parent folder if one of its childs is currently selected
A 1385       if (this.env.mailbox.indexOf(id + this.env.delimiter) == 0)
1386         this.command('list', id);
1387     }
8b7f5a 1388
da8f11 1389     // Work around a bug in IE6 and IE7, see #1485309
A 1390     if (bw.ie6 || bw.ie7) {
1391       var siblings = li.nextSibling ? li.nextSibling.getElementsByTagName('ul') : null;
1392       if (siblings && siblings.length && (li = siblings[0]) && li.style && li.style.display != 'none') {
1393         li.style.display = 'none';
1394         li.style.display = '';
f5aa16 1395       }
f11541 1396     }
da8f11 1397
98597a 1398     this.command('save-pref', { name: 'collapsed_folders', value: this.env.collapsed_folders });
da8f11 1399     this.set_unread_count_display(id, false);
A 1400   };
1401
1402   this.doc_mouse_up = function(e)
1403   {
476407 1404     var model, list, li, id;
da8f11 1405
6c1eae 1406     // ignore event if jquery UI dialog is open
T 1407     if ($(rcube_event.get_target(e)).closest('.ui-dialog, .ui-widget-overlay').length)
1408       return;
1409
476407 1410     if (list = this.message_list) {
A 1411       if (!rcube_mouse_is_over(e, list.list.parentNode))
1412         list.blur();
da8f11 1413       else
476407 1414         list.focus();
da8f11 1415       model = this.env.mailboxes;
A 1416     }
476407 1417     else if (list = this.contact_list) {
A 1418       if (!rcube_mouse_is_over(e, list.list.parentNode))
1419         list.blur();
da8f11 1420       else
476407 1421         list.focus();
da8f11 1422       model = this.env.contactfolders;
A 1423     }
1424     else if (this.ksearch_value) {
1425       this.ksearch_blur();
1426     }
1427
1428     // handle mouse release when dragging
1429     if (this.drag_active && model && this.env.last_folder_target) {
1430       var target = model[this.env.last_folder_target];
8fa922 1431
da8f11 1432       $(this.get_folder_li(this.env.last_folder_target)).removeClass('droptarget');
A 1433       this.env.last_folder_target = null;
1434       list.draglayer.hide();
1435
1436       if (!this.drag_menu(e, target))
1437         this.command('moveto', target);
1438     }
1439
1440     // reset 'pressed' buttons
1441     if (this.buttons_sel) {
476407 1442       for (id in this.buttons_sel)
d8cf6d 1443         if (typeof id !== 'function')
da8f11 1444           this.button_out(this.buttons_sel[id], id);
A 1445       this.buttons_sel = {};
1446     }
1447   };
f11541 1448
6b47de 1449   this.click_on_list = function(e)
186537 1450   {
58c9dd 1451     if (this.gui_objects.qsearchbox)
A 1452       this.gui_objects.qsearchbox.blur();
1453
6b47de 1454     if (this.message_list)
T 1455       this.message_list.focus();
1456     else if (this.contact_list)
58c9dd 1457       this.contact_list.focus();
4e17e6 1458
da8f11 1459     return true;
186537 1460   };
4e17e6 1461
6b47de 1462   this.msglist_select = function(list)
186537 1463   {
b19097 1464     if (this.preview_timer)
T 1465       clearTimeout(this.preview_timer);
bc4960 1466     if (this.preview_read_timer)
T 1467       clearTimeout(this.preview_read_timer);
1468
f52c93 1469     var selected = list.get_single_selection() != null;
4b9efb 1470
14259c 1471     this.enable_command(this.env.message_commands, selected);
e25a35 1472     if (selected) {
A 1473       // Hide certain command buttons when Drafts folder is selected
1474       if (this.env.mailbox == this.env.drafts_mailbox)
a208a4 1475         this.enable_command('reply', 'reply-all', 'reply-list', 'forward', 'forward-attachment', false);
e25a35 1476       // Disable reply-list when List-Post header is not set
A 1477       else {
1478         var msg = this.env.messages[list.get_single_selection()];
1479         if (!msg.ml)
1480           this.enable_command('reply-list', false);
1481       }
14259c 1482     }
A 1483     // Multi-message commands
a1f7e9 1484     this.enable_command('delete', 'moveto', 'copy', 'mark', (list.selection.length > 0 ? true : false));
A 1485
1486     // reset all-pages-selection
488074 1487     if (selected || (list.selection.length && list.selection.length != list.rowcount))
c6a6d2 1488       this.select_all_mode = false;
068f6a 1489
S 1490     // start timer for message preview (wait for double click)
196d04 1491     if (selected && this.env.contentframe && !list.multi_selecting && !this.dummy_select)
26f5b0 1492       this.preview_timer = window.setTimeout(function(){ ref.msglist_get_preview(); }, 200);
068f6a 1493     else if (this.env.contentframe)
f11541 1494       this.show_contentframe(false);
186537 1495   };
A 1496
1497   // This allow as to re-select selected message and display it in preview frame
1498   this.msglist_click = function(list)
1499   {
1500     if (list.multi_selecting || !this.env.contentframe)
1501       return;
1502
1503     if (list.get_single_selection() && window.frames && window.frames[this.env.contentframe]) {
1504       if (window.frames[this.env.contentframe].location.href.indexOf(this.env.blankpage)>=0) {
3a003c 1505         if (this.preview_timer)
A 1506           clearTimeout(this.preview_timer);
1507         if (this.preview_read_timer)
1508           clearTimeout(this.preview_read_timer);
186537 1509         this.preview_timer = window.setTimeout(function(){ ref.msglist_get_preview(); }, 200);
A 1510       }
1511     }
1512   };
d1d2c4 1513
6b47de 1514   this.msglist_dbl_click = function(list)
186537 1515   {
A 1516     if (this.preview_timer)
1517       clearTimeout(this.preview_timer);
bc4960 1518
186537 1519     if (this.preview_read_timer)
A 1520       clearTimeout(this.preview_read_timer);
b19097 1521
6b47de 1522     var uid = list.get_single_selection();
T 1523     if (uid && this.env.mailbox == this.env.drafts_mailbox)
1524       this.goto_url('compose', '_draft_uid='+uid+'&_mbox='+urlencode(this.env.mailbox), true);
1525     else if (uid)
b19097 1526       this.show_message(uid, false, false);
186537 1527   };
6b47de 1528
T 1529   this.msglist_keypress = function(list)
186537 1530   {
699a25 1531     if (list.modkey == CONTROL_KEY)
A 1532       return;
1533
6b47de 1534     if (list.key_pressed == list.ENTER_KEY)
T 1535       this.command('show');
699a25 1536     else if (list.key_pressed == list.DELETE_KEY || list.key_pressed == list.BACKSPACE_KEY)
6e6e89 1537       this.command('delete');
33e2e4 1538     else if (list.key_pressed == 33)
A 1539       this.command('previouspage');
1540     else if (list.key_pressed == 34)
1541       this.command('nextpage');
186537 1542   };
4e17e6 1543
b19097 1544   this.msglist_get_preview = function()
T 1545   {
1546     var uid = this.get_single_uid();
f11541 1547     if (uid && this.env.contentframe && !this.drag_active)
b19097 1548       this.show_message(uid, false, true);
T 1549     else if (this.env.contentframe)
f11541 1550       this.show_contentframe(false);
T 1551   };
8fa922 1552
f52c93 1553   this.msglist_expand = function(row)
T 1554   {
1555     if (this.env.messages[row.uid])
1556       this.env.messages[row.uid].expanded = row.expanded;
1557   };
176c76 1558
b62c48 1559   this.msglist_set_coltypes = function(list)
A 1560   {
1561     var i, found, name, cols = list.list.tHead.rows[0].cells;
176c76 1562
b62c48 1563     this.env.coltypes = [];
176c76 1564
b62c48 1565     for (i=0; i<cols.length; i++)
A 1566       if (cols[i].id && cols[i].id.match(/^rcm/)) {
1567         name = cols[i].id.replace(/^rcm/, '');
0e7b66 1568         this.env.coltypes.push(name == 'to' ? 'from' : name);
b62c48 1569       }
A 1570
1571     if ((found = $.inArray('flag', this.env.coltypes)) >= 0)
9f07d1 1572       this.env.flagged_col = found;
b62c48 1573
8e32dc 1574     if ((found = $.inArray('subject', this.env.coltypes)) >= 0)
9f07d1 1575       this.env.subject_col = found;
8e32dc 1576
9b6c82 1577     this.command('save-pref', { name: 'list_cols', value: this.env.coltypes, session: 'list_attrib/columns' });
b62c48 1578   };
8fa922 1579
f11541 1580   this.check_droptarget = function(id)
T 1581   {
ca38db 1582     var allow = false, copy = false;
176c76 1583
f11541 1584     if (this.task == 'mail')
ca38db 1585       allow = (this.env.mailboxes[id] && this.env.mailboxes[id].id != this.env.mailbox && !this.env.mailboxes[id].virtual);
b0dbf3 1586     else if (this.task == 'settings')
af3c04 1587       allow = (id != this.env.mailbox);
ca38db 1588     else if (this.task == 'addressbook') {
T 1589       if (id != this.env.source && this.env.contactfolders[id]) {
1590         if (this.env.contactfolders[id].type == 'group') {
1591           var target_abook = this.env.contactfolders[id].source;
1592           allow = this.env.contactfolders[id].id != this.env.group && !this.env.contactfolders[target_abook].readonly;
1593           copy = target_abook != this.env.source;
1594         }
1595         else {
1596           allow = !this.env.contactfolders[id].readonly;
1597           copy = true;
1598         }
1599       }
1600     }
56f41a 1601
ca38db 1602     return allow ? (copy ? 2 : 1) : 0;
b19097 1603   };
T 1604
4e17e6 1605
T 1606   /*********************************************************/
1607   /*********     (message) list functionality      *********/
1608   /*********************************************************/
f52c93 1609
T 1610   this.init_message_row = function(row)
1611   {
98f2c9 1612     var expando, self = this, uid = row.uid,
A 1613       status_icon = (this.env.status_col != null ? 'status' : 'msg') + 'icn' + row.uid;
8fa922 1614
f52c93 1615     if (uid && this.env.messages[uid])
T 1616       $.extend(row, this.env.messages[uid]);
1617
98f2c9 1618     // set eventhandler to status icon
A 1619     if (row.icon = document.getElementById(status_icon)) {
f52c93 1620       row.icon._row = row.obj;
e94706 1621       row.icon.onmousedown = function(e) { self.command('toggle_status', this); rcube_event.cancel(e); };
f52c93 1622     }
T 1623
98f2c9 1624     // save message icon position too
A 1625     if (this.env.status_col != null)
1626       row.msgicon = document.getElementById('msgicn'+row.uid);
1627     else
1628       row.msgicon = row.icon;
1629
f52c93 1630     // set eventhandler to flag icon, if icon found
98f2c9 1631     if (this.env.flagged_col != null && (row.flagicon = document.getElementById('flagicn'+row.uid))) {
A 1632       row.flagicon._row = row.obj;
1633       row.flagicon.onmousedown = function(e) { self.command('toggle_flag', this); rcube_event.cancel(e); };
f52c93 1634     }
T 1635
1636     if (!row.depth && row.has_children && (expando = document.getElementById('rcmexpando'+row.uid))) {
0e7b66 1637       row.expando = expando;
f52c93 1638       expando.onmousedown = function(e) { return self.expand_message_row(e, uid); };
T 1639     }
1640
1641     this.triggerEvent('insertrow', { uid:uid, row:row });
1642   };
1643
1644   // create a table row in the message list
1645   this.add_message_row = function(uid, cols, flags, attop)
1646   {
1647     if (!this.gui_objects.messagelist || !this.message_list)
1648       return false;
519aed 1649
bba252 1650     // Prevent from adding messages from different folder (#1487752)
A 1651     if (flags.mbox != this.env.mailbox && !flags.skip_mbox_check)
1652       return false;
1653
f52c93 1654     if (!this.env.messages[uid])
T 1655       this.env.messages[uid] = {};
519aed 1656
f52c93 1657     // merge flags over local message object
T 1658     $.extend(this.env.messages[uid], {
1659       deleted: flags.deleted?1:0,
609d39 1660       replied: flags.answered?1:0,
A 1661       unread: !flags.seen?1:0,
f52c93 1662       forwarded: flags.forwarded?1:0,
T 1663       flagged: flags.flagged?1:0,
1664       has_children: flags.has_children?1:0,
1665       depth: flags.depth?flags.depth:0,
0e7b66 1666       unread_children: flags.unread_children?flags.unread_children:0,
A 1667       parent_uid: flags.parent_uid?flags.parent_uid:0,
56f41a 1668       selected: this.select_all_mode || this.message_list.in_selection(uid),
e25a35 1669       ml: flags.ml?1:0,
6b4929 1670       ctype: flags.ctype,
56f41a 1671       // flags from plugins
A 1672       flags: flags.extra_flags
f52c93 1673     });
T 1674
c84d33 1675     var c, n, col, html, tree = '', expando = '',
488074 1676       list = this.message_list,
A 1677       rows = list.rows,
8fa922 1678       message = this.env.messages[uid],
A 1679       css_class = 'message'
609d39 1680         + (!flags.seen ? ' unread' : '')
f52c93 1681         + (flags.deleted ? ' deleted' : '')
T 1682         + (flags.flagged ? ' flagged' : '')
609d39 1683         + (flags.unread_children && flags.seen && !this.env.autoexpand_threads ? ' unroot' : '')
488074 1684         + (message.selected ? ' selected' : ''),
8fa922 1685       // for performance use DOM instead of jQuery here
c84d33 1686       row = document.createElement('tr');
f52c93 1687
T 1688     row.id = 'rcmrow'+uid;
1689     row.className = css_class;
519aed 1690
4438d6 1691     // message status icons
e94706 1692     css_class = 'msgicon';
98f2c9 1693     if (this.env.status_col === null) {
A 1694       css_class += ' status';
1695       if (flags.deleted)
1696         css_class += ' deleted';
609d39 1697       else if (!flags.seen)
98f2c9 1698         css_class += ' unread';
A 1699       else if (flags.unread_children > 0)
1700         css_class += ' unreadchildren';
1701     }
609d39 1702     if (flags.answered)
4438d6 1703       css_class += ' replied';
A 1704     if (flags.forwarded)
1705       css_class += ' forwarded';
519aed 1706
488074 1707     // update selection
A 1708     if (message.selected && !list.in_selection(uid))
1709       list.selection.push(uid);
1710
8fa922 1711     // threads
519aed 1712     if (this.env.threading) {
f52c93 1713       if (message.depth) {
c84d33 1714         // This assumes that div width is hardcoded to 15px,
A 1715         tree += '<span id="rcmtab' + uid + '" class="branch" style="width:' + (message.depth * 15) + 'px;">&nbsp;&nbsp;</span>';
1716
488074 1717         if ((rows[message.parent_uid] && rows[message.parent_uid].expanded === false)
A 1718           || ((this.env.autoexpand_threads == 0 || this.env.autoexpand_threads == 2) &&
1719             (!rows[message.parent_uid] || !rows[message.parent_uid].expanded))
1720         ) {
f52c93 1721           row.style.display = 'none';
T 1722           message.expanded = false;
1723         }
1724         else
1725           message.expanded = true;
488074 1726       }
f52c93 1727       else if (message.has_children) {
d8cf6d 1728         if (message.expanded === undefined && (this.env.autoexpand_threads == 1 || (this.env.autoexpand_threads == 2 && message.unread_children))) {
f52c93 1729           message.expanded = true;
T 1730         }
1731
1732         expando = '<div id="rcmexpando' + uid + '" class="' + (message.expanded ? 'expanded' : 'collapsed') + '">&nbsp;&nbsp;</div>';
c84d33 1733       }
519aed 1734     }
f52c93 1735
e94706 1736     tree += '<span id="msgicn'+uid+'" class="'+css_class+'">&nbsp;</span>';
8fa922 1737
f52c93 1738     // build subject link 
T 1739     if (!bw.ie && cols.subject) {
1740       var action = flags.mbox == this.env.drafts_mailbox ? 'compose' : 'show';
1741       var uid_param = flags.mbox == this.env.drafts_mailbox ? '_draft_uid' : '_uid';
1742       cols.subject = '<a href="./?_task=mail&_action='+action+'&_mbox='+urlencode(flags.mbox)+'&'+uid_param+'='+uid+'"'+
bc3745 1743         ' onclick="return rcube_event.cancel(event)" onmouseover="rcube_webmail.long_subject_title(this,'+(message.depth+1)+')">'+cols.subject+'</a>';
f52c93 1744     }
T 1745
1746     // add each submitted col
c84d33 1747     for (n in this.env.coltypes) {
dbd069 1748       c = this.env.coltypes[n];
f52c93 1749       col = document.createElement('td');
T 1750       col.className = String(c).toLowerCase();
1751
dbd069 1752       if (c == 'flag') {
e94706 1753         css_class = (flags.flagged ? 'flagged' : 'unflagged');
A 1754         html = '<span id="flagicn'+uid+'" class="'+css_class+'">&nbsp;</span>';
1755       }
1756       else if (c == 'attachment') {
6b4929 1757         if (/application\/|multipart\/m/.test(flags.ctype))
A 1758           html = '<span class="attachment">&nbsp;</span>';
32c657 1759         else if (/multipart\/report/.test(flags.ctype))
A 1760           html = '<span class="report">&nbsp;</span>';
6b4929 1761         else
A 1762           html = '&nbsp;';
4438d6 1763       }
A 1764       else if (c == 'status') {
1765         if (flags.deleted)
1766           css_class = 'deleted';
609d39 1767         else if (!flags.seen)
4438d6 1768           css_class = 'unread';
98f2c9 1769         else if (flags.unread_children > 0)
A 1770           css_class = 'unreadchildren';
4438d6 1771         else
A 1772           css_class = 'msgicon';
1773         html = '<span id="statusicn'+uid+'" class="'+css_class+'">&nbsp;</span>';
f52c93 1774       }
6c9d49 1775       else if (c == 'threads')
A 1776         html = expando;
065d70 1777       else if (c == 'subject') {
A 1778         if (bw.ie)
1779           col.onmouseover = function() { rcube_webmail.long_subject_title_ie(this, message.depth+1); };
f52c93 1780         html = tree + cols[c];
065d70 1781       }
7a2bad 1782       else if (c == 'priority') {
A 1783         if (flags.prio > 0 && flags.prio < 6)
1784           html = '<span class="prio'+flags.prio+'">&nbsp;</span>';
1785         else
1786           html = '&nbsp;';
1787       }
f52c93 1788       else
T 1789         html = cols[c];
1790
1791       col.innerHTML = html;
1792
1793       row.appendChild(col);
1794     }
1795
488074 1796     list.insert_row(row, attop);
f52c93 1797
T 1798     // remove 'old' row
488074 1799     if (attop && this.env.pagesize && list.rowcount > this.env.pagesize) {
A 1800       var uid = list.get_last_row();
1801       list.remove_row(uid);
1802       list.clear_selection(uid);
f52c93 1803     }
T 1804   };
1805
1806   this.set_list_sorting = function(sort_col, sort_order)
186537 1807   {
f52c93 1808     // set table header class
T 1809     $('#rcm'+this.env.sort_col).removeClass('sorted'+(this.env.sort_order.toUpperCase()));
1810     if (sort_col)
1811       $('#rcm'+sort_col).addClass('sorted'+sort_order);
8fa922 1812
f52c93 1813     this.env.sort_col = sort_col;
T 1814     this.env.sort_order = sort_order;
186537 1815   };
f52c93 1816
T 1817   this.set_list_options = function(cols, sort_col, sort_order, threads)
186537 1818   {
f52c93 1819     var update, add_url = '';
T 1820
d8cf6d 1821     if (sort_col === undefined)
b5002a 1822       sort_col = this.env.sort_col;
A 1823     if (!sort_order)
1824       sort_order = this.env.sort_order;
b62c48 1825
f52c93 1826     if (this.env.sort_col != sort_col || this.env.sort_order != sort_order) {
T 1827       update = 1;
1828       this.set_list_sorting(sort_col, sort_order);
186537 1829     }
8fa922 1830
f52c93 1831     if (this.env.threading != threads) {
T 1832       update = 1;
b5002a 1833       add_url += '&_threads=' + threads;
186537 1834     }
f52c93 1835
b62c48 1836     if (cols && cols.length) {
A 1837       // make sure new columns are added at the end of the list
1838       var i, idx, name, newcols = [], oldcols = this.env.coltypes;
1839       for (i=0; i<oldcols.length; i++) {
1840         name = oldcols[i] == 'to' ? 'from' : oldcols[i];
1841         idx = $.inArray(name, cols);
1842         if (idx != -1) {
c3eab2 1843           newcols.push(name);
b62c48 1844           delete cols[idx];
6c9d49 1845         }
b62c48 1846       }
A 1847       for (i=0; i<cols.length; i++)
1848         if (cols[i])
c3eab2 1849           newcols.push(cols[i]);
b5002a 1850
6c9d49 1851       if (newcols.join() != oldcols.join()) {
b62c48 1852         update = 1;
A 1853         add_url += '&_cols=' + newcols.join(',');
1854       }
186537 1855     }
f52c93 1856
T 1857     if (update)
1858       this.list_mailbox('', '', sort_col+'_'+sort_order, add_url);
186537 1859   };
4e17e6 1860
T 1861   // when user doble-clicks on a row
b19097 1862   this.show_message = function(id, safe, preview)
186537 1863   {
dbd069 1864     if (!id)
A 1865       return;
186537 1866
f94639 1867     var target = window,
A 1868       action = preview ? 'preview': 'show',
1869       url = '&_action='+action+'&_uid='+id+'&_mbox='+urlencode(this.env.mailbox);
186537 1870
A 1871     if (preview && this.env.contentframe && window.frames && window.frames[this.env.contentframe]) {
4e17e6 1872       target = window.frames[this.env.contentframe];
f94639 1873       url += '&_framed=1';
186537 1874     }
6b47de 1875
4e17e6 1876     if (safe)
f94639 1877       url += '&_safe=1';
4e17e6 1878
1f020b 1879     // also send search request to get the right messages
S 1880     if (this.env.search_request)
f94639 1881       url += '&_search='+this.env.search_request;
cc97ea 1882
bf2f39 1883     if (action == 'preview' && String(target.location.href).indexOf(url) >= 0)
A 1884       this.show_contentframe(true);
bc4960 1885     else {
dc0be3 1886       this.location_href(this.env.comm_path+url, target, true);
ca3c73 1887
bf2f39 1888       // mark as read and change mbox unread counter
bc4960 1889       if (action == 'preview' && this.message_list && this.message_list.rows[id] && this.message_list.rows[id].unread && this.env.preview_pane_mark_read >= 0) {
T 1890         this.preview_read_timer = window.setTimeout(function() {
1891           ref.set_message(id, 'unread', false);
1892           ref.update_thread_root(id, 'read');
1893           if (ref.env.unread_counts[ref.env.mailbox]) {
1894             ref.env.unread_counts[ref.env.mailbox] -= 1;
1895             ref.set_unread_count(ref.env.mailbox, ref.env.unread_counts[ref.env.mailbox], ref.env.mailbox == 'INBOX');
cc97ea 1896           }
bc4960 1897           if (ref.env.preview_pane_mark_read > 0)
1555ac 1898             ref.http_post('mark', '_uid='+id+'&_flag=read&_quiet=1');
bc4960 1899         }, this.env.preview_pane_mark_read * 1000);
4e17e6 1900       }
bc4960 1901     }
T 1902   };
b19097 1903
f11541 1904   this.show_contentframe = function(show)
186537 1905   {
9601f0 1906     var frm, win;
186537 1907     if (this.env.contentframe && (frm = $('#'+this.env.contentframe)) && frm.length) {
9601f0 1908       if (!show && (win = window.frames[this.env.contentframe])) {
A 1909         if (win.location && win.location.href.indexOf(this.env.blankpage)<0)
1910           win.location.href = this.env.blankpage;
186537 1911       }
ca3c73 1912       else if (!bw.safari && !bw.konq)
cc97ea 1913         frm[show ? 'show' : 'hide']();
b19097 1914       }
ca3c73 1915
b19097 1916     if (!show && this.busy)
d808ba 1917       this.set_busy(false, null, this.env.frame_lock);
a16400 1918   };
A 1919
1920   this.lock_frame = function()
1921   {
1922     if (!this.env.frame_lock)
1923       (this.is_framed() ? parent.rcmail : this).env.frame_lock = this.set_busy(true, 'loading');
186537 1924   };
4e17e6 1925
T 1926   // list a specific page
1927   this.list_page = function(page)
186537 1928   {
dbd069 1929     if (page == 'next')
4e17e6 1930       page = this.env.current_page+1;
b0fd4c 1931     else if (page == 'last')
d17008 1932       page = this.env.pagecount;
b0fd4c 1933     else if (page == 'prev' && this.env.current_page > 1)
4e17e6 1934       page = this.env.current_page-1;
b0fd4c 1935     else if (page == 'first' && this.env.current_page > 1)
d17008 1936       page = 1;
186537 1937
A 1938     if (page > 0 && page <= this.env.pagecount) {
4e17e6 1939       this.env.current_page = page;
8fa922 1940
dbd069 1941       if (this.task == 'mail')
4e17e6 1942         this.list_mailbox(this.env.mailbox, page);
dbd069 1943       else if (this.task == 'addressbook')
053e5a 1944         this.list_contacts(this.env.source, this.env.group, page);
186537 1945     }
A 1946   };
4e17e6 1947
e538b3 1948   // list messages of a specific mailbox using filter
A 1949   this.filter_mailbox = function(filter)
186537 1950   {
e9c47c 1951     var lock = this.set_busy(true, 'searching');
e538b3 1952
bb2699 1953     this.clear_message_list();
186537 1954
A 1955     // reset vars
1956     this.env.current_page = 1;
e9c47c 1957     this.http_request('search', this.search_params(false, filter), lock);
186537 1958   };
e538b3 1959
4e17e6 1960   // list messages of a specific mailbox
f52c93 1961   this.list_mailbox = function(mbox, page, sort, add_url)
186537 1962   {
dbd069 1963     var url = '', target = window;
4e17e6 1964
T 1965     if (!mbox)
4da0be 1966       mbox = this.env.mailbox ? this.env.mailbox : 'INBOX';
4e17e6 1967
f52c93 1968     if (add_url)
T 1969       url += add_url;
1970
f3b659 1971     // add sort to url if set
T 1972     if (sort)
f52c93 1973       url += '&_sort=' + sort;
f11541 1974
T 1975     // also send search request to get the right messages
1976     if (this.env.search_request)
f52c93 1977       url += '&_search='+this.env.search_request;
e737a5 1978
4e17e6 1979     // set page=1 if changeing to another mailbox
488074 1980     if (this.env.mailbox != mbox) {
4e17e6 1981       page = 1;
T 1982       this.env.current_page = page;
488074 1983       this.select_all_mode = false;
186537 1984     }
be9d4d 1985
A 1986     // unselect selected messages and clear the list and message data
1987     this.clear_message_list();
e737a5 1988
06895c 1989     if (mbox != this.env.mailbox || (mbox == this.env.mailbox && !page && !sort))
f52c93 1990       url += '&_refresh=1';
d9c83e 1991
f8e48d 1992     this.select_folder(mbox);
f11541 1993     this.env.mailbox = mbox;
4e17e6 1994
T 1995     // load message list remotely
186537 1996     if (this.gui_objects.messagelist) {
f52c93 1997       this.list_mailbox_remote(mbox, page, url);
4e17e6 1998       return;
186537 1999     }
8fa922 2000
186537 2001     if (this.env.contentframe && window.frames && window.frames[this.env.contentframe]) {
4e17e6 2002       target = window.frames[this.env.contentframe];
f52c93 2003       url += '&_framed=1';
186537 2004     }
4e17e6 2005
T 2006     // load message list to target frame/window
186537 2007     if (mbox) {
4e17e6 2008       this.set_busy(true, 'loading');
d7167e 2009       this.location_href(this.env.comm_path+'&_mbox='+urlencode(mbox)+(page ? '&_page='+page : '')+url, target);
186537 2010     }
be9d4d 2011   };
A 2012
2013   this.clear_message_list = function()
2014   {
2015       this.env.messages = {};
2016       this.last_selected = 0;
2017
2018       this.show_contentframe(false);
2019       if (this.message_list)
2020         this.message_list.clear(true);
186537 2021   };
4e17e6 2022
T 2023   // send remote request to load message list
9fee0e 2024   this.list_mailbox_remote = function(mbox, page, add_url)
186537 2025   {
15a9d1 2026     // clear message list first
6b47de 2027     this.message_list.clear();
15a9d1 2028
T 2029     // send request to server
ad334a 2030     var url = '_mbox='+urlencode(mbox)+(page ? '&_page='+page : ''),
A 2031       lock = this.set_busy(true, 'loading');
2032     this.http_request('list', url+add_url, lock);
186537 2033   };
488074 2034
A 2035   // removes messages that doesn't exists from list selection array
2036   this.update_selection = function()
2037   {
2038     var selected = this.message_list.selection,
2039       rows = this.message_list.rows,
2040       i, selection = [];
2041
2042     for (i in selected)
2043       if (rows[selected[i]])
2044         selection.push(selected[i]);
2045
2046     this.message_list.selection = selection;
2047   }
15a9d1 2048
f52c93 2049   // expand all threads with unread children
T 2050   this.expand_unread = function()
186537 2051   {
5d04a8 2052     var r, tbody = this.gui_objects.messagelist.tBodies[0],
dbd069 2053       new_row = tbody.firstChild;
8fa922 2054
f52c93 2055     while (new_row) {
609d39 2056       if (new_row.nodeType == 1 && (r = this.message_list.rows[new_row.uid]) && r.unread_children) {
186537 2057         this.message_list.expand_all(r);
dbd069 2058         this.set_unread_children(r.uid);
f52c93 2059       }
186537 2060       new_row = new_row.nextSibling;
A 2061     }
f52c93 2062     return false;
186537 2063   };
4e17e6 2064
b5002a 2065   // thread expanding/collapsing handler
f52c93 2066   this.expand_message_row = function(e, uid)
186537 2067   {
f52c93 2068     var row = this.message_list.rows[uid];
5e3512 2069
f52c93 2070     // handle unread_children mark
T 2071     row.expanded = !row.expanded;
2072     this.set_unread_children(uid);
2073     row.expanded = !row.expanded;
2074
2075     this.message_list.expand_row(e, uid);
186537 2076   };
f11541 2077
f52c93 2078   // message list expanding
T 2079   this.expand_threads = function()
b5002a 2080   {
f52c93 2081     if (!this.env.threading || !this.env.autoexpand_threads || !this.message_list)
T 2082       return;
186537 2083
f52c93 2084     switch (this.env.autoexpand_threads) {
T 2085       case 2: this.expand_unread(); break;
2086       case 1: this.message_list.expand_all(); break;
2087     }
8fa922 2088   };
f52c93 2089
0e7b66 2090   // Initializes threads indicators/expanders after list update
bba252 2091   this.init_threads = function(roots, mbox)
0e7b66 2092   {
bba252 2093     // #1487752
A 2094     if (mbox && mbox != this.env.mailbox)
2095       return false;
2096
0e7b66 2097     for (var n=0, len=roots.length; n<len; n++)
54531f 2098       this.add_tree_icons(roots[n]);
A 2099     this.expand_threads();
0e7b66 2100   };
A 2101
2102   // adds threads tree icons to the list (or specified thread)
2103   this.add_tree_icons = function(root)
2104   {
2105     var i, l, r, n, len, pos, tmp = [], uid = [],
2106       row, rows = this.message_list.rows;
2107
2108     if (root)
2109       row = rows[root] ? rows[root].obj : null;
2110     else
2111       row = this.message_list.list.tBodies[0].firstChild;
2112
2113     while (row) {
2114       if (row.nodeType == 1 && (r = rows[row.uid])) {
2115         if (r.depth) {
2116           for (i=tmp.length-1; i>=0; i--) {
2117             len = tmp[i].length;
2118             if (len > r.depth) {
2119               pos = len - r.depth;
2120               if (!(tmp[i][pos] & 2))
2121                 tmp[i][pos] = tmp[i][pos] ? tmp[i][pos]+2 : 2;
2122             }
2123             else if (len == r.depth) {
2124               if (!(tmp[i][0] & 2))
2125                 tmp[i][0] += 2;
2126             }
2127             if (r.depth > len)
2128               break;
2129           }
2130
2131           tmp.push(new Array(r.depth));
2132           tmp[tmp.length-1][0] = 1;
2133           uid.push(r.uid);
2134         }
2135         else {
2136           if (tmp.length) {
2137             for (i in tmp) {
2138               this.set_tree_icons(uid[i], tmp[i]);
2139             }
2140             tmp = [];
2141             uid = [];
2142           }
2143           if (root && row != rows[root].obj)
2144             break;
2145         }
2146       }
2147       row = row.nextSibling;
2148     }
2149
2150     if (tmp.length) {
2151       for (i in tmp) {
2152         this.set_tree_icons(uid[i], tmp[i]);
2153       }
2154     }
fb4663 2155   };
0e7b66 2156
A 2157   // adds tree icons to specified message row
2158   this.set_tree_icons = function(uid, tree)
2159   {
2160     var i, divs = [], html = '', len = tree.length;
2161
2162     for (i=0; i<len; i++) {
2163       if (tree[i] > 2)
2164         divs.push({'class': 'l3', width: 15});
2165       else if (tree[i] > 1)
2166         divs.push({'class': 'l2', width: 15});
2167       else if (tree[i] > 0)
2168         divs.push({'class': 'l1', width: 15});
2169       // separator div
2170       else if (divs.length && !divs[divs.length-1]['class'])
2171         divs[divs.length-1].width += 15;
2172       else
2173         divs.push({'class': null, width: 15});
2174     }
fb4663 2175
0e7b66 2176     for (i=divs.length-1; i>=0; i--) {
A 2177       if (divs[i]['class'])
2178         html += '<div class="tree '+divs[i]['class']+'" />';
2179       else
2180         html += '<div style="width:'+divs[i].width+'px" />';
2181     }
fb4663 2182
0e7b66 2183     if (html)
A 2184       $('#rcmtab'+uid).html(html);
2185   };
2186
f52c93 2187   // update parent in a thread
T 2188   this.update_thread_root = function(uid, flag)
0dbac3 2189   {
f52c93 2190     if (!this.env.threading)
T 2191       return;
2192
bc2acc 2193     var root = this.message_list.find_root(uid);
8fa922 2194
f52c93 2195     if (uid == root)
T 2196       return;
2197
2198     var p = this.message_list.rows[root];
2199
2200     if (flag == 'read' && p.unread_children) {
2201       p.unread_children--;
dbd069 2202     }
A 2203     else if (flag == 'unread' && p.has_children) {
f52c93 2204       // unread_children may be undefined
T 2205       p.unread_children = p.unread_children ? p.unread_children + 1 : 1;
dbd069 2206     }
A 2207     else {
f52c93 2208       return;
T 2209     }
2210
2211     this.set_message_icon(root);
2212     this.set_unread_children(root);
0dbac3 2213   };
f52c93 2214
T 2215   // update thread indicators for all messages in a thread below the specified message
2216   // return number of removed/added root level messages
2217   this.update_thread = function (uid)
2218   {
2219     if (!this.env.threading)
2220       return 0;
2221
dbd069 2222     var r, parent, count = 0,
A 2223       rows = this.message_list.rows,
2224       row = rows[uid],
2225       depth = rows[uid].depth,
2226       roots = [];
f52c93 2227
T 2228     if (!row.depth) // root message: decrease roots count
2229       count--;
2230     else if (row.unread) {
2231       // update unread_children for thread root
dbd069 2232       parent = this.message_list.find_root(uid);
f52c93 2233       rows[parent].unread_children--;
T 2234       this.set_unread_children(parent);
186537 2235     }
f52c93 2236
T 2237     parent = row.parent_uid;
2238
2239     // childrens
2240     row = row.obj.nextSibling;
2241     while (row) {
2242       if (row.nodeType == 1 && (r = rows[row.uid])) {
186537 2243         if (!r.depth || r.depth <= depth)
A 2244           break;
f52c93 2245
186537 2246         r.depth--; // move left
0e7b66 2247         // reset width and clear the content of a tab, icons will be added later
A 2248         $('#rcmtab'+r.uid).width(r.depth * 15).html('');
f52c93 2249         if (!r.depth) { // a new root
186537 2250           count++; // increase roots count
A 2251           r.parent_uid = 0;
2252           if (r.has_children) {
2253             // replace 'leaf' with 'collapsed'
2254             $('#rcmrow'+r.uid+' '+'.leaf:first')
f52c93 2255               .attr('id', 'rcmexpando' + r.uid)
186537 2256               .attr('class', (r.obj.style.display != 'none' ? 'expanded' : 'collapsed'))
f52c93 2257               .bind('mousedown', {uid:r.uid, p:this},
186537 2258                 function(e) { return e.data.p.expand_message_row(e, e.data.uid); });
f52c93 2259
186537 2260             r.unread_children = 0;
0e7b66 2261             roots.push(r);
186537 2262           }
A 2263           // show if it was hidden
2264           if (r.obj.style.display == 'none')
2265             $(r.obj).show();
f52c93 2266         }
186537 2267         else {
A 2268           if (r.depth == depth)
2269             r.parent_uid = parent;
2270           if (r.unread && roots.length)
2271             roots[roots.length-1].unread_children++;
f52c93 2272         }
T 2273       }
186537 2274       row = row.nextSibling;
A 2275     }
8fa922 2276
f52c93 2277     // update unread_children for roots
T 2278     for (var i=0; i<roots.length; i++)
2279       this.set_unread_children(roots[i].uid);
2280
2281     return count;
2282   };
2283
2284   this.delete_excessive_thread_rows = function()
2285   {
dbd069 2286     var rows = this.message_list.rows,
A 2287       tbody = this.message_list.list.tBodies[0],
2288       row = tbody.firstChild,
2289       cnt = this.env.pagesize + 1;
8fa922 2290
f52c93 2291     while (row) {
T 2292       if (row.nodeType == 1 && (r = rows[row.uid])) {
186537 2293         if (!r.depth && cnt)
A 2294           cnt--;
f52c93 2295
T 2296         if (!cnt)
186537 2297           this.message_list.remove_row(row.uid);
A 2298       }
2299       row = row.nextSibling;
2300     }
2301   };
0dbac3 2302
25c35c 2303   // set message icon
A 2304   this.set_message_icon = function(uid)
2305   {
e94706 2306     var css_class,
98f2c9 2307       row = this.message_list.rows[uid];
25c35c 2308
98f2c9 2309     if (!row)
25c35c 2310       return false;
e94706 2311
98f2c9 2312     if (row.icon) {
A 2313       css_class = 'msgicon';
2314       if (row.deleted)
2315         css_class += ' deleted';
2316       else if (row.unread)
2317         css_class += ' unread';
2318       else if (row.unread_children)
2319         css_class += ' unreadchildren';
2320       if (row.msgicon == row.icon) {
2321         if (row.replied)
2322           css_class += ' replied';
2323         if (row.forwarded)
2324           css_class += ' forwarded';
2325         css_class += ' status';
2326       }
4438d6 2327
98f2c9 2328       row.icon.className = css_class;
4438d6 2329     }
A 2330
98f2c9 2331     if (row.msgicon && row.msgicon != row.icon) {
e94706 2332       css_class = 'msgicon';
98f2c9 2333       if (!row.unread && row.unread_children)
e94706 2334         css_class += ' unreadchildren';
98f2c9 2335       if (row.replied)
4438d6 2336         css_class += ' replied';
98f2c9 2337       if (row.forwarded)
4438d6 2338         css_class += ' forwarded';
e94706 2339
98f2c9 2340       row.msgicon.className = css_class;
f52c93 2341     }
e94706 2342
98f2c9 2343     if (row.flagicon) {
A 2344       css_class = (row.flagged ? 'flagged' : 'unflagged');
2345       row.flagicon.className = css_class;
186537 2346     }
A 2347   };
25c35c 2348
A 2349   // set message status
2350   this.set_message_status = function(uid, flag, status)
186537 2351   {
98f2c9 2352     var row = this.message_list.rows[uid];
25c35c 2353
98f2c9 2354     if (!row)
A 2355       return false;
25c35c 2356
A 2357     if (flag == 'unread')
98f2c9 2358       row.unread = status;
25c35c 2359     else if(flag == 'deleted')
98f2c9 2360       row.deleted = status;
25c35c 2361     else if (flag == 'replied')
98f2c9 2362       row.replied = status;
25c35c 2363     else if (flag == 'forwarded')
98f2c9 2364       row.forwarded = status;
25c35c 2365     else if (flag == 'flagged')
98f2c9 2366       row.flagged = status;
186537 2367   };
25c35c 2368
A 2369   // set message row status, class and icon
2370   this.set_message = function(uid, flag, status)
186537 2371   {
98f2c9 2372     var row = this.message_list.rows[uid];
25c35c 2373
98f2c9 2374     if (!row)
A 2375       return false;
8fa922 2376
25c35c 2377     if (flag)
A 2378       this.set_message_status(uid, flag, status);
f52c93 2379
98f2c9 2380     var rowobj = $(row.obj);
f52c93 2381
98f2c9 2382     if (row.unread && !rowobj.hasClass('unread'))
cc97ea 2383       rowobj.addClass('unread');
98f2c9 2384     else if (!row.unread && rowobj.hasClass('unread'))
cc97ea 2385       rowobj.removeClass('unread');
8fa922 2386
98f2c9 2387     if (row.deleted && !rowobj.hasClass('deleted'))
cc97ea 2388       rowobj.addClass('deleted');
98f2c9 2389     else if (!row.deleted && rowobj.hasClass('deleted'))
cc97ea 2390       rowobj.removeClass('deleted');
25c35c 2391
98f2c9 2392     if (row.flagged && !rowobj.hasClass('flagged'))
cc97ea 2393       rowobj.addClass('flagged');
98f2c9 2394     else if (!row.flagged && rowobj.hasClass('flagged'))
cc97ea 2395       rowobj.removeClass('flagged');
163a13 2396
f52c93 2397     this.set_unread_children(uid);
25c35c 2398     this.set_message_icon(uid);
186537 2399   };
f52c93 2400
T 2401   // sets unroot (unread_children) class of parent row
2402   this.set_unread_children = function(uid)
186537 2403   {
f52c93 2404     var row = this.message_list.rows[uid];
186537 2405
0e7b66 2406     if (row.parent_uid)
f52c93 2407       return;
T 2408
2409     if (!row.unread && row.unread_children && !row.expanded)
2410       $(row.obj).addClass('unroot');
2411     else
2412       $(row.obj).removeClass('unroot');
186537 2413   };
9b3fdc 2414
A 2415   // copy selected messages to the specified mailbox
2416   this.copy_messages = function(mbox)
186537 2417   {
d8cf6d 2418     if (mbox && typeof mbox === 'object')
488074 2419       mbox = mbox.id;
A 2420
9b3fdc 2421     // exit if current or no mailbox specified or if selection is empty
A 2422     if (!mbox || mbox == this.env.mailbox || (!this.env.uid && (!this.message_list || !this.message_list.get_selection().length)))
2423       return;
2424
0e7b66 2425     var a_uids = [],
c50d88 2426       lock = this.display_message(this.get_label('copyingmessage'), 'loading'),
0e7b66 2427       add_url = '&_target_mbox='+urlencode(mbox)+'&_from='+(this.env.action ? this.env.action : '');
9b3fdc 2428
A 2429     if (this.env.uid)
2430       a_uids[0] = this.env.uid;
186537 2431     else {
9b3fdc 2432       var selection = this.message_list.get_selection();
0e7b66 2433       for (var n in selection) {
A 2434         a_uids.push(selection[n]);
9b3fdc 2435       }
A 2436     }
2437
c0c0c0 2438     add_url += '&_uid='+this.uids_to_list(a_uids);
A 2439
9b3fdc 2440     // send request to server
c0c0c0 2441     this.http_post('copy', '_mbox='+urlencode(this.env.mailbox)+add_url, lock);
186537 2442   };
0dbac3 2443
4e17e6 2444   // move selected messages to the specified mailbox
T 2445   this.move_messages = function(mbox)
186537 2446   {
d8cf6d 2447     if (mbox && typeof mbox === 'object')
a61bbb 2448       mbox = mbox.id;
8fa922 2449
e4bbb2 2450     // exit if current or no mailbox specified or if selection is empty
faebf4 2451     if (!mbox || mbox == this.env.mailbox || (!this.env.uid && (!this.message_list || !this.message_list.get_selection().length)))
aa9836 2452       return;
e4bbb2 2453
c0c0c0 2454     var lock = false,
A 2455       add_url = '&_target_mbox='+urlencode(mbox)+'&_from='+(this.env.action ? this.env.action : '');
4e17e6 2456
T 2457     // show wait message
ad334a 2458     if (this.env.action == 'show') {
A 2459       lock = this.set_busy(true, 'movingmessage');
186537 2460     }
0b2ce9 2461     else
f11541 2462       this.show_contentframe(false);
6b47de 2463
faebf4 2464     // Hide message command buttons until a message is selected
14259c 2465     this.enable_command(this.env.message_commands, false);
faebf4 2466
0b2ce9 2467     this._with_selected_messages('moveto', lock, add_url);
186537 2468   };
4e17e6 2469
857a38 2470   // delete selected messages from the current mailbox
S 2471   this.delete_messages = function()
84a331 2472   {
476407 2473     var uid, i, len, trash = this.env.trash_mailbox,
A 2474       list = this.message_list,
2475       selection = list ? $.merge([], list.get_selection()) : [];
3d3531 2476
857a38 2477     // exit if no mailbox specified or if selection is empty
1c7b97 2478     if (!this.env.uid && !selection.length)
dc2fc0 2479       return;
8fa922 2480
84a331 2481     // also select childs of collapsed rows
476407 2482     for (i=0, len=selection.length; i<len; i++) {
84a331 2483       uid = selection[i];
476407 2484       if (list.rows[uid].has_children && !list.rows[uid].expanded)
A 2485         list.select_childs(uid);
84a331 2486     }
8fa922 2487
0b2ce9 2488     // if config is set to flag for deletion
f52c93 2489     if (this.env.flag_for_deletion) {
0b2ce9 2490       this.mark_message('delete');
f52c93 2491       return false;
84a331 2492     }
0b2ce9 2493     // if there isn't a defined trash mailbox or we are in it
476407 2494     // @TODO: we should check if defined trash mailbox exists
A 2495     else if (!trash || this.env.mailbox == trash)
0b2ce9 2496       this.permanently_remove_messages();
A 2497     // if there is a trash mailbox defined and we're not currently in it
2498     else {
31c171 2499       // if shift was pressed delete it immediately
699a25 2500       if (list && list.modkey == SHIFT_KEY) {
31c171 2501         if (confirm(this.get_label('deletemessagesconfirm')))
S 2502           this.permanently_remove_messages();
84a331 2503       }
31c171 2504       else
476407 2505         this.move_messages(trash);
84a331 2506     }
f52c93 2507
T 2508     return true;
857a38 2509   };
cfdf04 2510
T 2511   // delete the selected messages permanently
2512   this.permanently_remove_messages = function()
186537 2513   {
cfdf04 2514     // exit if no mailbox specified or if selection is empty
T 2515     if (!this.env.uid && (!this.message_list || !this.message_list.get_selection().length))
2516       return;
8fa922 2517
f11541 2518     this.show_contentframe(false);
0b2ce9 2519     this._with_selected_messages('delete', false, '&_from='+(this.env.action ? this.env.action : ''));
186537 2520   };
cfdf04 2521
f52c93 2522   // Send a specifc moveto/delete request with UIDs of all selected messages
cfdf04 2523   // @private
f52c93 2524   this._with_selected_messages = function(action, lock, add_url)
d22455 2525   {
c50d88 2526     var a_uids = [], count = 0, msg;
3d3531 2527
cfdf04 2528     if (this.env.uid)
3d3531 2529       a_uids[0] = this.env.uid;
186537 2530     else {
0e7b66 2531       var n, id, root, roots = [],
A 2532         selection = this.message_list.get_selection();
2533
2534       for (n=0, len=selection.length; n<len; n++) {
cfdf04 2535         id = selection[n];
0e7b66 2536         a_uids.push(id);
A 2537
2538         if (this.env.threading) {
2539           count += this.update_thread(id);
2540           root = this.message_list.find_root(id);
2541           if (root != id && $.inArray(root, roots) < 0) {
2542             roots.push(root);
2543           }
2544         }
e54bb7 2545         this.message_list.remove_row(id, (this.env.display_next && n == selection.length-1));
cfdf04 2546       }
e54bb7 2547       // make sure there are no selected rows
A 2548       if (!this.env.display_next)
2549         this.message_list.clear_selection();
0e7b66 2550       // update thread tree icons
A 2551       for (n=0, len=roots.length; n<len; n++) {
2552         this.add_tree_icons(roots[n]);
2553       }
d22455 2554     }
132aae 2555
c50d88 2556     // also send search request to get the right messages
ab10d6 2557     if (this.env.search_request)
cfdf04 2558       add_url += '&_search='+this.env.search_request;
dc2fc0 2559
e54bb7 2560     if (this.env.display_next && this.env.next_uid)
dc2fc0 2561       add_url += '&_next_uid='+this.env.next_uid;
f52c93 2562
T 2563     if (count < 0)
2564       add_url += '&_count='+(count*-1);
2565     else if (count > 0) 
2566       // remove threads from the end of the list
2567       this.delete_excessive_thread_rows();
cfdf04 2568
fb7ec5 2569     add_url += '&_uid='+this.uids_to_list(a_uids);
c50d88 2570
A 2571     if (!lock) {
2572       msg = action == 'moveto' ? 'movingmessage' : 'deletingmessage';
2573       lock = this.display_message(this.get_label(msg), 'loading');
2574     }
fb7ec5 2575
cfdf04 2576     // send request to server
fb7ec5 2577     this.http_post(action, '_mbox='+urlencode(this.env.mailbox)+add_url, lock);
d22455 2578   };
4e17e6 2579
T 2580   // set a specific flag to one or more messages
2581   this.mark_message = function(flag, uid)
186537 2582   {
0e7b66 2583     var a_uids = [], r_uids = [], len, n, id,
8fa922 2584       selection = this.message_list ? this.message_list.get_selection() : [];
3d3531 2585
4e17e6 2586     if (uid)
T 2587       a_uids[0] = uid;
2588     else if (this.env.uid)
2589       a_uids[0] = this.env.uid;
186537 2590     else if (this.message_list) {
0e7b66 2591       for (n=0, len=selection.length; n<len; n++) {
A 2592           a_uids.push(selection[n]);
5d97ac 2593       }
186537 2594     }
5d97ac 2595
3d3531 2596     if (!this.message_list)
A 2597       r_uids = a_uids;
2598     else
0e7b66 2599       for (n=0, len=a_uids.length; n<len; n++) {
5d97ac 2600         id = a_uids[n];
A 2601         if ((flag=='read' && this.message_list.rows[id].unread) 
d22455 2602             || (flag=='unread' && !this.message_list.rows[id].unread)
T 2603             || (flag=='delete' && !this.message_list.rows[id].deleted)
2604             || (flag=='undelete' && this.message_list.rows[id].deleted)
2605             || (flag=='flagged' && !this.message_list.rows[id].flagged)
2606             || (flag=='unflagged' && this.message_list.rows[id].flagged))
2607         {
0e7b66 2608           r_uids.push(id);
d22455 2609         }
4e17e6 2610       }
3d3531 2611
1a98a6 2612     // nothing to do
f3d37f 2613     if (!r_uids.length && !this.select_all_mode)
1a98a6 2614       return;
3d3531 2615
186537 2616     switch (flag) {
857a38 2617         case 'read':
S 2618         case 'unread':
5d97ac 2619           this.toggle_read_status(flag, r_uids);
857a38 2620           break;
S 2621         case 'delete':
2622         case 'undelete':
5d97ac 2623           this.toggle_delete_status(r_uids);
e189a6 2624           break;
A 2625         case 'flagged':
2626         case 'unflagged':
2627           this.toggle_flagged_status(flag, a_uids);
857a38 2628           break;
186537 2629     }
A 2630   };
4e17e6 2631
857a38 2632   // set class to read/unread
6b47de 2633   this.toggle_read_status = function(flag, a_uids)
T 2634   {
4fb6a2 2635     var i, len = a_uids.length,
A 2636       url = '_uid='+this.uids_to_list(a_uids)+'&_flag='+flag,
c50d88 2637       lock = this.display_message(this.get_label('markingmessage'), 'loading');
4fb6a2 2638
A 2639     // mark all message rows as read/unread
2640     for (i=0; i<len; i++)
2641       this.set_message(a_uids[i], 'unread', (flag=='unread' ? true : false));
ab10d6 2642
A 2643     // also send search request to get the right messages
2644     if (this.env.search_request)
2645       url += '&_search='+this.env.search_request;
2646
c50d88 2647     this.http_post('mark', url, lock);
f52c93 2648
4fb6a2 2649     for (i=0; i<len; i++)
f52c93 2650       this.update_thread_root(a_uids[i], flag);
6b47de 2651   };
6d2714 2652
e189a6 2653   // set image to flagged or unflagged
A 2654   this.toggle_flagged_status = function(flag, a_uids)
2655   {
4fb6a2 2656     var i, len = a_uids.length,
A 2657       url = '_uid='+this.uids_to_list(a_uids)+'&_flag='+flag,
c50d88 2658       lock = this.display_message(this.get_label('markingmessage'), 'loading');
4fb6a2 2659
A 2660     // mark all message rows as flagged/unflagged
2661     for (i=0; i<len; i++)
2662       this.set_message(a_uids[i], 'flagged', (flag=='flagged' ? true : false));
ab10d6 2663
A 2664     // also send search request to get the right messages
2665     if (this.env.search_request)
2666       url += '&_search='+this.env.search_request;
2667
c50d88 2668     this.http_post('mark', url, lock);
e189a6 2669   };
8fa922 2670
857a38 2671   // mark all message rows as deleted/undeleted
6b47de 2672   this.toggle_delete_status = function(a_uids)
T 2673   {
4fb6a2 2674     var len = a_uids.length,
A 2675       i, uid, all_deleted = true,
2676       rows = this.message_list ? this.message_list.rows : [];
8fa922 2677
4fb6a2 2678     if (len == 1) {
25c35c 2679       if (!rows.length || (rows[a_uids[0]] && !rows[a_uids[0]].deleted))
6b47de 2680         this.flag_as_deleted(a_uids);
T 2681       else
2682         this.flag_as_undeleted(a_uids);
2683
1c5853 2684       return true;
S 2685     }
8fa922 2686
4fb6a2 2687     for (i=0; i<len; i++) {
857a38 2688       uid = a_uids[i];
f3d37f 2689       if (rows[uid] && !rows[uid].deleted) {
A 2690         all_deleted = false;
2691         break;
857a38 2692       }
1c5853 2693     }
8fa922 2694
1c5853 2695     if (all_deleted)
S 2696       this.flag_as_undeleted(a_uids);
2697     else
2698       this.flag_as_deleted(a_uids);
8fa922 2699
1c5853 2700     return true;
6b47de 2701   };
4e17e6 2702
6b47de 2703   this.flag_as_undeleted = function(a_uids)
T 2704   {
4fb6a2 2705     var i, len=a_uids.length,
A 2706       url = '_uid='+this.uids_to_list(a_uids)+'&_flag=undelete',
c50d88 2707       lock = this.display_message(this.get_label('markingmessage'), 'loading');
4fb6a2 2708
A 2709     for (i=0; i<len; i++)
2710       this.set_message(a_uids[i], 'deleted', false);
ab10d6 2711
A 2712     // also send search request to get the right messages
2713     if (this.env.search_request)
2714       url += '&_search='+this.env.search_request;
2715
c50d88 2716     this.http_post('mark', url, lock);
1c5853 2717     return true;
6b47de 2718   };
T 2719
2720   this.flag_as_deleted = function(a_uids)
2721   {
fb7ec5 2722     var add_url = '',
8fa922 2723       r_uids = [],
A 2724       rows = this.message_list ? this.message_list.rows : [],
fb7ec5 2725       count = 0;
f52c93 2726
0e7b66 2727     for (var i=0, len=a_uids.length; i<len; i++) {
1c5853 2728       uid = a_uids[i];
fb7ec5 2729       if (rows[uid]) {
d22455 2730         if (rows[uid].unread)
T 2731           r_uids[r_uids.length] = uid;
0b2ce9 2732
fb7ec5 2733         if (this.env.skip_deleted) {
A 2734           count += this.update_thread(uid);
e54bb7 2735           this.message_list.remove_row(uid, (this.env.display_next && i == this.message_list.selection.length-1));
fb7ec5 2736         }
A 2737         else
2738           this.set_message(uid, 'deleted', true);
3d3531 2739       }
fb7ec5 2740     }
e54bb7 2741
A 2742     // make sure there are no selected rows
f52c93 2743     if (this.env.skip_deleted && this.message_list) {
T 2744       if(!this.env.display_next)
fb7ec5 2745         this.message_list.clear_selection();
f52c93 2746       if (count < 0)
T 2747         add_url += '&_count='+(count*-1);
2748       else if (count > 0) 
2749         // remove threads from the end of the list
2750         this.delete_excessive_thread_rows();
fb7ec5 2751     }
3d3531 2752
c50d88 2753     add_url = '&_from='+(this.env.action ? this.env.action : ''),
A 2754       lock = this.display_message(this.get_label('markingmessage'), 'loading');
8fa922 2755
fb7ec5 2756     // ??
3d3531 2757     if (r_uids.length)
fb7ec5 2758       add_url += '&_ruid='+this.uids_to_list(r_uids);
3d3531 2759
0b2ce9 2760     if (this.env.skip_deleted) {
e54bb7 2761       if (this.env.display_next && this.env.next_uid)
0b2ce9 2762         add_url += '&_next_uid='+this.env.next_uid;
A 2763     }
8fa922 2764
ab10d6 2765     // also send search request to get the right messages
A 2766     if (this.env.search_request)
2767       add_url += '&_search='+this.env.search_request;
2768
c50d88 2769     this.http_post('mark', '_uid='+this.uids_to_list(a_uids)+'&_flag=delete'+add_url, lock);
A 2770     return true;
6b47de 2771   };
3d3531 2772
A 2773   // flag as read without mark request (called from backend)
2774   // argument should be a coma-separated list of uids
2775   this.flag_deleted_as_read = function(uids)
2776   {
4fb6a2 2777     var icn_src, uid, i, len,
A 2778       rows = this.message_list ? this.message_list.rows : [];
3d3531 2779
4fb6a2 2780     uids = String(uids).split(',');
A 2781
2782     for (i=0, len=uids.length; i<len; i++) {
2783       uid = uids[i];
3d3531 2784       if (rows[uid])
132aae 2785         this.set_message(uid, 'unread', false);
8fa922 2786     }
3d3531 2787   };
f52c93 2788
fb7ec5 2789   // Converts array of message UIDs to comma-separated list for use in URL
A 2790   // with select_all mode checking
2791   this.uids_to_list = function(uids)
2792   {
2793     return this.select_all_mode ? '*' : uids.join(',');
2794   };
8fa922 2795
f52c93 2796
T 2797   /*********************************************************/
2798   /*********       mailbox folders methods         *********/
2799   /*********************************************************/
2800
2801   this.expunge_mailbox = function(mbox)
8fa922 2802   {
b7fd98 2803     var lock, url = '_mbox='+urlencode(mbox);
8fa922 2804
f52c93 2805     // lock interface if it's the active mailbox
8fa922 2806     if (mbox == this.env.mailbox) {
b7fd98 2807       lock = this.set_busy(true, 'loading');
A 2808       url += '&_reload=1';
2809       if (this.env.search_request)
2810         url += '&_search='+this.env.search_request;
2811     }
f52c93 2812
T 2813     // send request to server
af3c04 2814     this.http_post('expunge', url, lock);
8fa922 2815   };
f52c93 2816
T 2817   this.purge_mailbox = function(mbox)
8fa922 2818   {
af3c04 2819     var lock = false,
A 2820       url = '_mbox='+urlencode(mbox);
8fa922 2821
f52c93 2822     if (!confirm(this.get_label('purgefolderconfirm')))
T 2823       return false;
8fa922 2824
f52c93 2825     // lock interface if it's the active mailbox
8fa922 2826     if (mbox == this.env.mailbox) {
ad334a 2827        lock = this.set_busy(true, 'loading');
af3c04 2828        url += '&_reload=1';
8fa922 2829      }
f52c93 2830
T 2831     // send request to server
af3c04 2832     this.http_post('purge', url, lock);
8fa922 2833   };
f52c93 2834
T 2835   // test if purge command is allowed
2836   this.purge_mailbox_test = function()
2837   {
fb4663 2838     return (this.env.messagecount && (this.env.mailbox == this.env.trash_mailbox || this.env.mailbox == this.env.junk_mailbox
A 2839       || this.env.mailbox.match('^' + RegExp.escape(this.env.trash_mailbox) + RegExp.escape(this.env.delimiter))
f52c93 2840       || this.env.mailbox.match('^' + RegExp.escape(this.env.junk_mailbox) + RegExp.escape(this.env.delimiter))));
T 2841   };
2842
8fa922 2843
b566ff 2844   /*********************************************************/
S 2845   /*********           login form methods          *********/
2846   /*********************************************************/
2847
2848   // handler for keyboard events on the _user field
65444b 2849   this.login_user_keyup = function(e)
b566ff 2850   {
9bbb17 2851     var key = rcube_event.get_keycode(e);
cc97ea 2852     var passwd = $('#rcmloginpwd');
b566ff 2853
S 2854     // enter
cc97ea 2855     if (key == 13 && passwd.length && !passwd.val()) {
T 2856       passwd.focus();
2857       return rcube_event.cancel(e);
b566ff 2858     }
8fa922 2859
cc97ea 2860     return true;
b566ff 2861   };
f11541 2862
4e17e6 2863
T 2864   /*********************************************************/
2865   /*********        message compose methods        *********/
2866   /*********************************************************/
8fa922 2867
f52c93 2868   // init message compose form: set focus and eventhandlers
T 2869   this.init_messageform = function()
2870   {
2871     if (!this.gui_objects.messageform)
2872       return false;
8fa922 2873
9be483 2874     var input_from = $("[name='_from']"),
A 2875       input_to = $("[name='_to']"),
2876       input_subject = $("input[name='_subject']"),
2877       input_message = $("[name='_message']").get(0),
2878       html_mode = $("input[name='_is_html']").val() == '1',
0213f8 2879       ac_fields = ['cc', 'bcc', 'replyto', 'followupto'],
A 2880       ac_props;
2881
2882     // configure parallel autocompletion
2883     if (this.env.autocomplete_threads > 0) {
2884       ac_props = {
2885         threads: this.env.autocomplete_threads,
e3acfa 2886         sources: this.env.autocomplete_sources
0213f8 2887       };
A 2888     }
f52c93 2889
T 2890     // init live search events
0213f8 2891     this.init_address_input_events(input_to, ac_props);
9be483 2892     for (var i in ac_fields) {
0213f8 2893       this.init_address_input_events($("[name='_"+ac_fields[i]+"']"), ac_props);
9be483 2894     }
8fa922 2895
a4c163 2896     if (!html_mode) {
500af6 2897       this.set_caret_pos(input_message, this.env.top_posting ? 0 : $(input_message).val().length);
a4c163 2898       // add signature according to selected identity
A 2899       // if we have HTML editor, signature is added in callback
02e079 2900       if (input_from.prop('type') == 'select-one' && $("input[name='_draft_saveid']").val() == '') {
a4c163 2901         this.change_identity(input_from[0]);
A 2902       }
f52c93 2903     }
T 2904
2905     if (input_to.val() == '')
2906       input_to.focus();
2907     else if (input_subject.val() == '')
2908       input_subject.focus();
1f019c 2909     else if (input_message)
f52c93 2910       input_message.focus();
1f019c 2911
A 2912     this.env.compose_focus_elem = document.activeElement;
f52c93 2913
T 2914     // get summary of all field values
2915     this.compose_field_hash(true);
8fa922 2916
f52c93 2917     // start the auto-save timer
T 2918     this.auto_save_start();
2919   };
2920
0213f8 2921   this.init_address_input_events = function(obj, props)
f52c93 2922   {
62c861 2923     this.env.recipients_delimiter = this.env.recipients_separator + ' ';
T 2924
0213f8 2925     obj[bw.ie || bw.safari || bw.chrome ? 'keydown' : 'keypress'](function(e) { return ref.ksearch_keydown(e, this, props); })
8cfbc4 2926       .attr('autocomplete', 'off');
f52c93 2927   };
T 2928
977a29 2929   // checks the input fields before sending a message
T 2930   this.check_compose_input = function()
f52c93 2931   {
977a29 2932     // check input fields
736790 2933     var ed, input_to = $("[name='_to']"),
A 2934       input_cc = $("[name='_cc']"),
2935       input_bcc = $("[name='_bcc']"),
2936       input_from = $("[name='_from']"),
2937       input_subject = $("[name='_subject']"),
2938       input_message = $("[name='_message']");
977a29 2939
fd51e0 2940     // check sender (if have no identities)
02e079 2941     if (input_from.prop('type') == 'text' && !rcube_check_email(input_from.val(), true)) {
fd51e0 2942       alert(this.get_label('nosenderwarning'));
A 2943       input_from.focus();
2944       return false;
a4c163 2945     }
fd51e0 2946
977a29 2947     // check for empty recipient
cc97ea 2948     var recipients = input_to.val() ? input_to.val() : (input_cc.val() ? input_cc.val() : input_bcc.val());
a4c163 2949     if (!rcube_check_email(recipients.replace(/^\s+/, '').replace(/[\s,;]+$/, ''), true)) {
977a29 2950       alert(this.get_label('norecipientwarning'));
T 2951       input_to.focus();
2952       return false;
a4c163 2953     }
977a29 2954
ebf872 2955     // check if all files has been uploaded
01ffe0 2956     for (var key in this.env.attachments) {
d8cf6d 2957       if (typeof this.env.attachments[key] === 'object' && !this.env.attachments[key].complete) {
01ffe0 2958         alert(this.get_label('notuploadedwarning'));
T 2959         return false;
2960       }
ebf872 2961     }
8fa922 2962
977a29 2963     // display localized warning for missing subject
f52c93 2964     if (input_subject.val() == '') {
977a29 2965       var subject = prompt(this.get_label('nosubjectwarning'), this.get_label('nosubject'));
T 2966
2967       // user hit cancel, so don't send
f52c93 2968       if (!subject && subject !== '') {
977a29 2969         input_subject.focus();
T 2970         return false;
2971       }
f52c93 2972       else
T 2973         input_subject.val((subject ? subject : this.get_label('nosubject')));
2974     }
977a29 2975
898249 2976     // Apply spellcheck changes if spell checker is active
A 2977     this.stop_spellchecking();
2978
736790 2979     if (window.tinyMCE)
A 2980       ed = tinyMCE.get(this.env.composebody);
2981
2982     // check for empty body
2983     if (!ed && input_message.val() == '' && !confirm(this.get_label('nobodywarning'))) {
2984       input_message.focus();
2985       return false;
2986     }
2987     else if (ed) {
2988       if (!ed.getContent() && !confirm(this.get_label('nobodywarning'))) {
2989         ed.focus();
2990         return false;
2991       }
2992       // move body from html editor to textarea (just to be sure, #1485860)
6b42d5 2993       tinyMCE.triggerSave();
736790 2994     }
3940ba 2995
A 2996     return true;
2997   };
2998
2999   this.toggle_editor = function(props)
3000   {
3001     if (props.mode == 'html') {
3002       this.display_spellcheck_controls(false);
3003       this.plain2html($('#'+props.id).val(), props.id);
3004       tinyMCE.execCommand('mceAddControl', false, props.id);
3005     }
3006     else {
5cff85 3007       var thisMCE = tinyMCE.get(props.id), existingHtml;
T 3008       if (thisMCE.plugins.spellchecker && thisMCE.plugins.spellchecker.active)
3009         thisMCE.execCommand('mceSpellCheck', false);
be9d4d 3010
5cff85 3011       if (existingHtml = thisMCE.getContent()) {
3940ba 3012         if (!confirm(this.get_label('editorwarning'))) {
A 3013           return false;
5cff85 3014         }
3940ba 3015         this.html2plain(existingHtml, props.id);
A 3016       }
3017       tinyMCE.execCommand('mceRemoveControl', false, props.id);
3018       this.display_spellcheck_controls(true);
3019     }
6b42d5 3020
977a29 3021     return true;
f52c93 3022   };
41fa0b 3023
898249 3024   this.stop_spellchecking = function()
f52c93 3025   {
736790 3026     var ed;
A 3027     if (window.tinyMCE && (ed = tinyMCE.get(this.env.composebody))) {
33bfe1 3028       if (ed.plugins.spellchecker && ed.plugins.spellchecker.active)
2d2764 3029         ed.execCommand('mceSpellCheck');
736790 3030     }
A 3031     else if ((ed = this.env.spellcheck) && !this.spellcheck_ready) {
3032       $(ed.spell_span).trigger('click');
898249 3033       this.set_spellcheck_state('ready');
f52c93 3034     }
T 3035   };
898249 3036
4ca10b 3037   this.display_spellcheck_controls = function(vis)
f52c93 3038   {
4ca10b 3039     if (this.env.spellcheck) {
f4b868 3040       // stop spellchecking process
b8ae50 3041       if (!vis)
f52c93 3042         this.stop_spellchecking();
ffa6c1 3043
309d2f 3044       $(this.env.spellcheck.spell_container).css('visibility', vis ? 'visible' : 'hidden');
a4c163 3045     }
f52c93 3046   };
41fa0b 3047
e170b4 3048   this.set_spellcheck_state = function(s)
a4c163 3049   {
309d2f 3050     this.spellcheck_ready = (s == 'ready' || s == 'no_error_found');
e170b4 3051     this.enable_command('spellcheck', this.spellcheck_ready);
a4c163 3052   };
e170b4 3053
644e3a 3054   // get selected language
A 3055   this.spellcheck_lang = function()
3056   {
3057     var ed;
3058     if (window.tinyMCE && (ed = tinyMCE.get(this.env.composebody)) && ed.plugins.spellchecker) {
3059       return ed.plugins.spellchecker.selectedLang;
3060     }
3061     else if (this.env.spellcheck) {
3062       return GOOGIE_CUR_LANG;
3063     }
3064   };
3065
340546 3066   // resume spellchecking, highlight provided mispellings without new ajax request
A 3067   this.spellcheck_resume = function(ishtml, data)
3068   {
3069     if (ishtml) {
3070       var ed = tinyMCE.get(this.env.composebody);
3071         sp = ed.plugins.spellchecker;
3072
3073       sp.active = 1;
3074       sp._markWords(data);
3075       ed.nodeChanged();
3076     }
3077     else {
3078       var sp = this.env.spellcheck;
3079       sp.prepare(false, true);
3080       sp.processData(data);
3081     }
3082   }
3083
f11541 3084   this.set_draft_id = function(id)
a4c163 3085   {
cc97ea 3086     $("input[name='_draft_saveid']").val(id);
a4c163 3087   };
f11541 3088
f0f98f 3089   this.auto_save_start = function()
a4c163 3090   {
9a5261 3091     if (this.env.draft_autosave)
cfdf04 3092       this.save_timer = self.setTimeout(function(){ ref.command("savedraft"); }, this.env.draft_autosave * 1000);
4b9efb 3093
S 3094     // Unlock interface now that saving is complete
3095     this.busy = false;
a4c163 3096   };
41fa0b 3097
f11541 3098   this.compose_field_hash = function(save)
a4c163 3099   {
977a29 3100     // check input fields
d1d9fd 3101     var ed, str = '',
A 3102       value_to = $("[name='_to']").val(),
3103       value_cc = $("[name='_cc']").val(),
3104       value_bcc = $("[name='_bcc']").val(),
3105       value_subject = $("[name='_subject']").val();
8fa922 3106
cc97ea 3107     if (value_to)
T 3108       str += value_to+':';
3109     if (value_cc)
3110       str += value_cc+':';
3111     if (value_bcc)
3112       str += value_bcc+':';
3113     if (value_subject)
3114       str += value_subject+':';
8fa922 3115
d1d9fd 3116     if (window.tinyMCE && (ed = tinyMCE.get(this.env.composebody)))
A 3117       str += ed.getContent();
c5fb69 3118     else
cc97ea 3119       str += $("[name='_message']").val();
b4d940 3120
A 3121     if (this.env.attachments)
3122       for (var upload_id in this.env.attachments)
3123         str += upload_id;
3124
f11541 3125     if (save)
T 3126       this.cmp_hash = str;
b4d940 3127
977a29 3128     return str;
a4c163 3129   };
8fa922 3130
50f56d 3131   this.change_identity = function(obj, show_sig)
655bd9 3132   {
1cded8 3133     if (!obj || !obj.options)
T 3134       return false;
3135
50f56d 3136     if (!show_sig)
A 3137       show_sig = this.env.show_sig;
3138
500af6 3139     var cursor_pos, p = -1,
1f019c 3140       id = obj.options[obj.selectedIndex].value,
A 3141       input_message = $("[name='_message']"),
3142       message = input_message.val(),
3143       is_html = ($("input[name='_is_html']").val() == '1'),
500af6 3144       sig = this.env.identity,
1f019c 3145       sig_separator = this.env.sig_above && (this.env.compose_mode == 'reply' || this.env.compose_mode == 'forward') ? '---' : '-- ';
8fa922 3146
0207c4 3147     // enable manual signature insert
d7f9eb 3148     if (this.env.signatures && this.env.signatures[id]) {
0207c4 3149       this.enable_command('insert-sig', true);
d7f9eb 3150       this.env.compose_commands.push('insert-sig');
A 3151     }
0207c4 3152     else
T 3153       this.enable_command('insert-sig', false);
50f56d 3154
655bd9 3155     if (!is_html) {
a0109c 3156       // remove the 'old' signature
500af6 3157       if (show_sig && sig && this.env.signatures && this.env.signatures[sig]) {
f9a2a6 3158
500af6 3159         sig = this.env.signatures[sig].is_html ? this.env.signatures[sig].plain_text : this.env.signatures[sig].text;
f9a2a6 3160         sig = sig.replace(/\r\n/g, '\n');
edaf6a 3161
3d247e 3162         if (!sig.match(/^--[ -]\n/m))
a96183 3163           sig = sig_separator + '\n' + sig;
dd792e 3164
655bd9 3165         p = this.env.sig_above ? message.indexOf(sig) : message.lastIndexOf(sig);
T 3166         if (p >= 0)
3167           message = message.substring(0, p) + message.substring(p+sig.length, message.length);
0207c4 3168       }
a0109c 3169       // add the new signature string
655bd9 3170       if (show_sig && this.env.signatures && this.env.signatures[id]) {
T 3171         sig = this.env.signatures[id]['is_html'] ? this.env.signatures[id]['plain_text'] : this.env.signatures[id]['text'];
f9a2a6 3172         sig = sig.replace(/\r\n/g, '\n');
edaf6a 3173
3d247e 3174         if (!sig.match(/^--[ -]\n/m))
a96183 3175           sig = sig_separator + '\n' + sig;
50f56d 3176
0207c4 3177         if (this.env.sig_above) {
655bd9 3178           if (p >= 0) { // in place of removed signature
T 3179             message = message.substring(0, p) + sig + message.substring(p, message.length);
3180             cursor_pos = p - 1;
1f019c 3181           }
655bd9 3182           else if (pos = this.get_caret_pos(input_message.get(0))) { // at cursor position
T 3183             message = message.substring(0, pos) + '\n' + sig + '\n\n' + message.substring(pos, message.length);
3184             cursor_pos = pos;
3185           }
3186           else { // on top
3187             cursor_pos = 0;
3188             message = '\n\n' + sig + '\n\n' + message.replace(/^[\r\n]+/, '');
1f019c 3189           }
a0109c 3190         }
655bd9 3191         else {
T 3192           message = message.replace(/[\r\n]+$/, '');
3193           cursor_pos = !this.env.top_posting && message.length ? message.length+1 : 0;
3194           message += '\n\n' + sig;
0207c4 3195         }
1cded8 3196       }
655bd9 3197       else
T 3198         cursor_pos = this.env.top_posting ? 0 : message.length;
3199
3200       input_message.val(message);
186537 3201
655bd9 3202       // move cursor before the signature
T 3203       this.set_caret_pos(input_message.get(0), cursor_pos);
3204     }
186537 3205     else if (show_sig && this.env.signatures) {  // html
1f019c 3206       var editor = tinyMCE.get(this.env.composebody),
A 3207         sigElem = editor.dom.get('_rc_sig');
a0109c 3208
655bd9 3209       // Append the signature as a div within the body
T 3210       if (!sigElem) {
1f019c 3211         var body = editor.getBody(),
A 3212           doc = editor.getDoc();
186537 3213
655bd9 3214         sigElem = doc.createElement('div');
T 3215         sigElem.setAttribute('id', '_rc_sig');
186537 3216
655bd9 3217         if (this.env.sig_above) {
T 3218           // if no existing sig and top posting then insert at caret pos
1f019c 3219           editor.getWin().focus(); // correct focus in IE & Chrome
186537 3220
655bd9 3221           var node = editor.selection.getNode();
T 3222           if (node.nodeName == 'BODY') {
3223             // no real focus, insert at start
3224             body.insertBefore(sigElem, body.firstChild);
3225             body.insertBefore(doc.createElement('br'), body.firstChild);
cc97ea 3226           }
655bd9 3227           else {
T 3228             body.insertBefore(sigElem, node.nextSibling);
3229             body.insertBefore(doc.createElement('br'), node.nextSibling);
0207c4 3230           }
T 3231         }
655bd9 3232         else {
T 3233           if (bw.ie)  // add empty line before signature on IE
3234             body.appendChild(doc.createElement('br'));
0207c4 3235
655bd9 3236           body.appendChild(sigElem);
dd792e 3237         }
1cded8 3238       }
a0109c 3239
655bd9 3240       if (this.env.signatures[id]) {
T 3241         if (this.env.signatures[id].is_html) {
3242           sig = this.env.signatures[id].text;
3d247e 3243           if (!this.env.signatures[id].plain_text.match(/^--[ -]\r?\n/m))
a96183 3244             sig = sig_separator + '<br />' + sig;
655bd9 3245         }
T 3246         else {
3247           sig = this.env.signatures[id].text;
3d247e 3248           if (!sig.match(/^--[ -]\r?\n/m))
a96183 3249             sig = sig_separator + '\n' + sig;
655bd9 3250           sig = '<pre>' + sig + '</pre>';
T 3251         }
0207c4 3252
655bd9 3253         sigElem.innerHTML = sig;
T 3254       }
3255     }
0207c4 3256
1cded8 3257     this.env.identity = id;
1c5853 3258     return true;
655bd9 3259   };
4e17e6 3260
T 3261   // upload attachment file
3262   this.upload_file = function(form)
a4c163 3263   {
4e17e6 3264     if (!form)
T 3265       return false;
8fa922 3266
7fc056 3267     // get file input field, count files on capable browser
fe0cb6 3268     var i, size = 0, field = $('input[type=file]', form).get(0),
7fc056 3269       files = field.files ? field.files.length : field.value ? 1 : 0;
8fa922 3270
4e17e6 3271     // create hidden iframe and post upload form
7fc056 3272     if (files) {
fe0cb6 3273       // check file size
A 3274       if (field.files && this.env.max_filesize && this.env.filesizeerror) {
3275         for (i=0; i<files; i++)
3276           size += field.files[i].size;
3277         if (size && size > this.env.max_filesize) {
3278           this.display_message(this.env.filesizeerror, 'error');
3279           return;
3280         }
3281       }
3282
b649c4 3283       var frame_name = this.async_upload_form(form, 'upload', function(e) {
87a868 3284         var d, content = '';
ebf872 3285         try {
A 3286           if (this.contentDocument) {
87a868 3287             d = this.contentDocument;
01ffe0 3288           } else if (this.contentWindow) {
87a868 3289             d = this.contentWindow.document;
01ffe0 3290           }
T 3291           content = d.childNodes[0].innerHTML;
b649c4 3292         } catch (err) {}
ebf872 3293
87a868 3294         if (!content.match(/add2attachment/) && (!bw.opera || (rcmail.env.uploadframe && rcmail.env.uploadframe == e.data.ts))) {
A 3295           if (!content.match(/display_message/))
3296             rcmail.display_message(rcmail.get_label('fileuploaderror'), 'error');
01ffe0 3297           rcmail.remove_from_attachment_list(e.data.ts);
ebf872 3298         }
01ffe0 3299         // Opera hack: handle double onload
T 3300         if (bw.opera)
3301           rcmail.env.uploadframe = e.data.ts;
ebf872 3302       });
8fa922 3303
3f9712 3304       // display upload indicator and cancel button
4171c5 3305       var content = '<span>' + this.get_label('uploading' + (files > 1 ? 'many' : '')) + '</span>',
b649c4 3306         ts = frame_name.replace(/^rcmupload/, '');
A 3307
7da13a 3308       if (this.env.loadingicon)
ebf872 3309         content = '<img src="'+this.env.loadingicon+'" alt="" />'+content;
3f9712 3310       if (this.env.cancelicon)
V 3311         content = '<a title="'+this.get_label('cancel')+'" onclick="return rcmail.cancel_attachment_upload(\''+ts+'\', \''+frame_name+'\');" href="#cancelupload"><img src="'+this.env.cancelicon+'" alt="" /></a>'+content;
01ffe0 3312       this.add2attachment_list(ts, { name:'', html:content, complete:false });
4171c5 3313
A 3314       // upload progress support
3315       if (this.env.upload_progress_time) {
3316         this.upload_progress_start('upload', ts);
3317       }
a4c163 3318     }
8fa922 3319
4e17e6 3320     // set reference to the form object
T 3321     this.gui_objects.attachmentform = form;
1c5853 3322     return true;
a4c163 3323   };
4e17e6 3324
T 3325   // add file name to attachment list
3326   // called from upload page
01ffe0 3327   this.add2attachment_list = function(name, att, upload_id)
T 3328   {
4e17e6 3329     if (!this.gui_objects.attachmentlist)
T 3330       return false;
8fa922 3331
7fc056 3332     var indicator, li = $('<li>').attr('id', name).html(att.html);
8fa922 3333
ebf872 3334     // replace indicator's li
A 3335     if (upload_id && (indicator = document.getElementById(upload_id))) {
01ffe0 3336       li.replaceAll(indicator);
T 3337     }
3338     else { // add new li
3339       li.appendTo(this.gui_objects.attachmentlist);
3340     }
8fa922 3341
01ffe0 3342     if (upload_id && this.env.attachments[upload_id])
T 3343       delete this.env.attachments[upload_id];
8fa922 3344
01ffe0 3345     this.env.attachments[name] = att;
8fa922 3346
1c5853 3347     return true;
01ffe0 3348   };
4e17e6 3349
a894ba 3350   this.remove_from_attachment_list = function(name)
01ffe0 3351   {
T 3352     if (this.env.attachments[name])
3353       delete this.env.attachments[name];
8fa922 3354
a894ba 3355     if (!this.gui_objects.attachmentlist)
S 3356       return false;
3357
3358     var list = this.gui_objects.attachmentlist.getElementsByTagName("li");
7fc056 3359     for (i=0; i<list.length; i++)
a894ba 3360       if (list[i].id == name)
9a5261 3361         this.gui_objects.attachmentlist.removeChild(list[i]);
01ffe0 3362   };
a894ba 3363
S 3364   this.remove_attachment = function(name)
a4c163 3365   {
01ffe0 3366     if (name && this.env.attachments[name])
4591de 3367       this.http_post('remove-attachment', { _id:this.env.compose_id, _file:name });
a894ba 3368
S 3369     return true;
a4c163 3370   };
4e17e6 3371
3f9712 3372   this.cancel_attachment_upload = function(name, frame_name)
a4c163 3373   {
3f9712 3374     if (!name || !frame_name)
V 3375       return false;
3376
3377     this.remove_from_attachment_list(name);
3378     $("iframe[name='"+frame_name+"']").remove();
3379     return false;
4171c5 3380   };
A 3381
3382   this.upload_progress_start = function(action, name)
3383   {
3384     window.setTimeout(function() { rcmail.http_request(action, {_progress: name}); },
3385       this.env.upload_progress_time * 1000);
3386   };
3387
3388   this.upload_progress_update = function(param)
3389   {
3390     var elem = $('#'+param.name + '> span');
3391
3392     if (!elem.length || !param.text)
3393       return;
3394
3395     elem.text(param.text);
3396
3397     if (!param.done)
3398       this.upload_progress_start(param.action, param.name);
a4c163 3399   };
3f9712 3400
4e17e6 3401   // send remote request to add a new contact
T 3402   this.add_contact = function(value)
a4c163 3403   {
4e17e6 3404     if (value)
f11541 3405       this.http_post('addcontact', '_address='+value);
8fa922 3406
1c5853 3407     return true;
a4c163 3408   };
4e17e6 3409
f11541 3410   // send remote request to search mail or contacts
30b152 3411   this.qsearch = function(value)
a4c163 3412   {
A 3413     if (value != '') {
e9c47c 3414       var n, lock = this.set_busy(true, 'searching');
3cacf9 3415
e9c47c 3416       if (this.message_list)
be9d4d 3417         this.clear_message_list();
e9c47c 3418       else if (this.contact_list)
e9a9f2 3419         this.list_contacts_clear();
e9c47c 3420
A 3421       // reset vars
3422       this.env.current_page = 1;
3423       r = this.http_request('search', this.search_params(value)
3424         + (this.env.source ? '&_source='+urlencode(this.env.source) : '')
3425         + (this.env.group ? '&_gid='+urlencode(this.env.group) : ''), lock);
3426
3427       this.env.qsearch = {lock: lock, request: r};
3428     }
3429   };
3430
3431   // build URL params for search
3432   this.search_params = function(search, filter)
3433   {
3434     var n, url = [], mods_arr = [],
3435       mods = this.env.search_mods,
3436       mbox = this.env.mailbox;
3437
3438     if (!filter && this.gui_objects.search_filter)
3439       filter = this.gui_objects.search_filter.value;
3440
3441     if (!search && this.gui_objects.qsearchbox)
3442       search = this.gui_objects.qsearchbox.value;
3443
3444     if (filter)
3445       url.push('_filter=' + urlencode(filter));
3446
3447     if (search) {
3448       url.push('_q='+urlencode(search));
3449
3450       if (mods && this.message_list)
3451         mods = mods[mbox] ? mods[mbox] : mods['*'];
3cacf9 3452
A 3453       if (mods) {
3454         for (n in mods)
3455           mods_arr.push(n);
e9c47c 3456         url.push('_headers='+mods_arr.join(','));
a4c163 3457       }
A 3458     }
e9c47c 3459
A 3460     if (mbox)
3461       url.push('_mbox='+urlencode(mbox));
3462
3463     return url.join('&');
a4c163 3464   };
4647e1 3465
T 3466   // reset quick-search form
3467   this.reset_qsearch = function()
a4c163 3468   {
4647e1 3469     if (this.gui_objects.qsearchbox)
T 3470       this.gui_objects.qsearchbox.value = '';
8fa922 3471
d96151 3472     if (this.env.qsearch)
A 3473       this.abort_request(this.env.qsearch);
db0408 3474
A 3475     this.env.qsearch = null;
4647e1 3476     this.env.search_request = null;
f8e48d 3477     this.env.search_id = null;
a4c163 3478   };
41fa0b 3479
9a5762 3480   this.sent_successfully = function(type, msg)
a4c163 3481   {
ad334a 3482     this.display_message(msg, type);
538e1c 3483     // before redirect we need to wait some time for Chrome (#1486177)
A 3484     window.setTimeout(function(){ ref.list_mailbox(); }, 500);
a4c163 3485   };
41fa0b 3486
4e17e6 3487
T 3488   /*********************************************************/
3489   /*********     keyboard live-search methods      *********/
3490   /*********************************************************/
3491
3492   // handler for keyboard events on address-fields
0213f8 3493   this.ksearch_keydown = function(e, obj, props)
2c8e84 3494   {
4e17e6 3495     if (this.ksearch_timer)
T 3496       clearTimeout(this.ksearch_timer);
3497
74f0a6 3498     var highlight,
A 3499       key = rcube_event.get_keycode(e),
3500       mod = rcube_event.get_modifier(e);
4e17e6 3501
8fa922 3502     switch (key) {
4e17e6 3503       case 38:  // key up
T 3504       case 40:  // key down
3505         if (!this.ksearch_pane)
3506           break;
8fa922 3507
4e17e6 3508         var dir = key==38 ? 1 : 0;
8fa922 3509
4e17e6 3510         highlight = document.getElementById('rcmksearchSelected');
T 3511         if (!highlight)
cc97ea 3512           highlight = this.ksearch_pane.__ul.firstChild;
8fa922 3513
2c8e84 3514         if (highlight)
T 3515           this.ksearch_select(dir ? highlight.previousSibling : highlight.nextSibling);
4e17e6 3516
86958f 3517         return rcube_event.cancel(e);
4e17e6 3518
7f0388 3519       case 9:   // tab
A 3520         if (mod == SHIFT_KEY || !this.ksearch_visible()) {
3521           this.ksearch_hide();
3522           return;
3523         }
3524
0213f8 3525       case 13:  // enter
7f0388 3526         if (!this.ksearch_visible())
A 3527           return false;
4e17e6 3528
86958f 3529         // insert selected address and hide ksearch pane
T 3530         this.insert_recipient(this.ksearch_selected);
4e17e6 3531         this.ksearch_hide();
86958f 3532
T 3533         return rcube_event.cancel(e);
4e17e6 3534
T 3535       case 27:  // escape
3536         this.ksearch_hide();
bd3891 3537         return;
8fa922 3538
ca3c73 3539       case 37:  // left
A 3540       case 39:  // right
3541         if (mod != SHIFT_KEY)
8fa922 3542           return;
A 3543     }
4e17e6 3544
T 3545     // start timer
0213f8 3546     this.ksearch_timer = window.setTimeout(function(){ ref.ksearch_get_results(props); }, 200);
4e17e6 3547     this.ksearch_input = obj;
8fa922 3548
4e17e6 3549     return true;
2c8e84 3550   };
8fa922 3551
7f0388 3552   this.ksearch_visible = function()
A 3553   {
3554     return (this.ksearch_selected !== null && this.ksearch_selected !== undefined && this.ksearch_value);
3555   };
3556
2c8e84 3557   this.ksearch_select = function(node)
T 3558   {
cc97ea 3559     var current = $('#rcmksearchSelected');
T 3560     if (current[0] && node) {
3561       current.removeAttr('id').removeClass('selected');
2c8e84 3562     }
T 3563
3564     if (node) {
cc97ea 3565       $(node).attr('id', 'rcmksearchSelected').addClass('selected');
2c8e84 3566       this.ksearch_selected = node._rcm_id;
T 3567     }
3568   };
86958f 3569
T 3570   this.insert_recipient = function(id)
3571   {
609d39 3572     if (id === null || !this.env.contacts[id] || !this.ksearch_input)
86958f 3573       return;
8fa922 3574
86958f 3575     // get cursor pos
c296b8 3576     var inp_value = this.ksearch_input.value,
A 3577       cpos = this.get_caret_pos(this.ksearch_input),
3578       p = inp_value.lastIndexOf(this.ksearch_value, cpos),
ec65ad 3579       trigger = false,
c296b8 3580       insert = '',
A 3581       // replace search string with full address
3582       pre = inp_value.substring(0, p),
3583       end = inp_value.substring(p+this.ksearch_value.length, inp_value.length);
0213f8 3584
A 3585     this.ksearch_destroy();
8fa922 3586
a61bbb 3587     // insert all members of a group
d8cf6d 3588     if (typeof this.env.contacts[id] === 'object' && this.env.contacts[id].id) {
62c861 3589       insert += this.env.contacts[id].name + this.env.recipients_delimiter;
53d626 3590       this.group2expand = $.extend({}, this.env.contacts[id]);
T 3591       this.group2expand.input = this.ksearch_input;
ec65ad 3592       this.http_request('mail/group-expand', '_source='+urlencode(this.env.contacts[id].source)+'&_gid='+urlencode(this.env.contacts[id].id), false);
a61bbb 3593     }
ec65ad 3594     else if (typeof this.env.contacts[id] === 'string') {
62c861 3595       insert = this.env.contacts[id] + this.env.recipients_delimiter;
ec65ad 3596       trigger = true;
T 3597     }
a61bbb 3598
86958f 3599     this.ksearch_input.value = pre + insert + end;
7b0eac 3600
86958f 3601     // set caret to insert pos
T 3602     cpos = p+insert.length;
3603     if (this.ksearch_input.setSelectionRange)
3604       this.ksearch_input.setSelectionRange(cpos, cpos);
ec65ad 3605
T 3606     if (trigger)
3607       this.triggerEvent('autocomplete_insert', { field:this.ksearch_input, insert:insert });
53d626 3608   };
8fa922 3609
53d626 3610   this.replace_group_recipients = function(id, recipients)
T 3611   {
3612     if (this.group2expand && this.group2expand.id == id) {
3613       this.group2expand.input.value = this.group2expand.input.value.replace(this.group2expand.name, recipients);
ec65ad 3614       this.triggerEvent('autocomplete_insert', { field:this.group2expand.input, insert:recipients });
53d626 3615       this.group2expand = null;
T 3616     }
c0297f 3617   };
4e17e6 3618
T 3619   // address search processor
0213f8 3620   this.ksearch_get_results = function(props)
2c8e84 3621   {
4e17e6 3622     var inp_value = this.ksearch_input ? this.ksearch_input.value : null;
c296b8 3623
2c8e84 3624     if (inp_value === null)
4e17e6 3625       return;
8fa922 3626
cc97ea 3627     if (this.ksearch_pane && this.ksearch_pane.is(":visible"))
T 3628       this.ksearch_pane.hide();
4e17e6 3629
T 3630     // get string from current cursor pos to last comma
c296b8 3631     var cpos = this.get_caret_pos(this.ksearch_input),
62c861 3632       p = inp_value.lastIndexOf(this.env.recipients_separator, cpos-1),
c296b8 3633       q = inp_value.substring(p+1, cpos),
f8ca74 3634       min = this.env.autocomplete_min_length,
A 3635       ac = this.ksearch_data;
4e17e6 3636
T 3637     // trim query string
ef17c5 3638     q = $.trim(q);
4e17e6 3639
297a43 3640     // Don't (re-)search if the last results are still active
cea956 3641     if (q == this.ksearch_value)
ca3c73 3642       return;
8fa922 3643
48a065 3644     this.ksearch_destroy();
A 3645
2b3a8e 3646     if (q.length && q.length < min) {
5f7129 3647       if (!this.ksearch_info) {
A 3648         this.ksearch_info = this.display_message(
2b3a8e 3649           this.get_label('autocompletechars').replace('$min', min));
c296b8 3650       }
A 3651       return;
3652     }
3653
297a43 3654     var old_value = this.ksearch_value;
4e17e6 3655     this.ksearch_value = q;
241450 3656
297a43 3657     // ...string is empty
cea956 3658     if (!q.length)
A 3659       return;
297a43 3660
f8ca74 3661     // ...new search value contains old one and previous search was not finished or its result was empty
A 3662     if (old_value && old_value.length && q.indexOf(old_value) == 0 && (!ac || !ac.num) && this.env.contacts && !this.env.contacts.length)
297a43 3663       return;
0213f8 3664
A 3665     var i, lock, source, xhr, reqid = new Date().getTime(),
3666       threads = props && props.threads ? props.threads : 1,
3667       sources = props && props.sources ? props.sources : [],
3668       action = props && props.action ? props.action : 'mail/autocomplete';
3669
f8ca74 3670     this.ksearch_data = {id: reqid, sources: sources.slice(), action: action,
A 3671       locks: [], requests: [], num: sources.length};
0213f8 3672
A 3673     for (i=0; i<threads; i++) {
3674       source = this.ksearch_data.sources.shift();
3675       if (threads > 1 && source === null)
3676         break;
3677
3678       lock = this.display_message(this.get_label('searching'), 'loading');
3679       xhr = this.http_post(action, '_search='+urlencode(q)+'&_id='+reqid
3680         + (source ? '&_source='+urlencode(source) : ''), lock);
3681
3682       this.ksearch_data.locks.push(lock);
3683       this.ksearch_data.requests.push(xhr);
3684     }
2c8e84 3685   };
4e17e6 3686
0213f8 3687   this.ksearch_query_results = function(results, search, reqid)
2c8e84 3688   {
5f5cf8 3689     // search stopped in meantime?
A 3690     if (!this.ksearch_value)
3691       return;
3692
aaffbe 3693     // ignore this outdated search response
5f5cf8 3694     if (this.ksearch_input && search != this.ksearch_value)
aaffbe 3695       return;
8fa922 3696
4e17e6 3697     // display search results
48a065 3698     var ul, li, text, init,
A 3699       value = this.ksearch_value,
3700       data = this.ksearch_data,
0213f8 3701       maxlen = this.env.autocomplete_max ? this.env.autocomplete_max : 15;
8fa922 3702
0213f8 3703     // create results pane if not present
A 3704     if (!this.ksearch_pane) {
3705       ul = $('<ul>');
3706       this.ksearch_pane = $('<div>').attr('id', 'rcmKSearchpane')
3707         .css({ position:'absolute', 'z-index':30000 }).append(ul).appendTo(document.body);
3708       this.ksearch_pane.__ul = ul[0];
3709     }
4e17e6 3710
0213f8 3711     ul = this.ksearch_pane.__ul;
A 3712
3713     // remove all search results or add to existing list if parallel search
3714     if (reqid && this.ksearch_pane.data('reqid') == reqid) {
3715       maxlen -= ul.childNodes.length;
3716     }
3717     else {
3718       this.ksearch_pane.data('reqid', reqid);
3719       init = 1;
3720       // reset content
4e17e6 3721       ul.innerHTML = '';
0213f8 3722       this.env.contacts = [];
A 3723       // move the results pane right under the input box
3724       var pos = $(this.ksearch_input).offset();
3725       this.ksearch_pane.css({ left:pos.left+'px', top:(pos.top + this.ksearch_input.offsetHeight)+'px', display: 'none'});
3726     }
812abd 3727
0213f8 3728     // add each result line to list
A 3729     if (results && results.length) {
3730       for (i=0; i < results.length && maxlen > 0; i++) {
3731         text = typeof results[i] === 'object' ? results[i].name : results[i];
4e17e6 3732         li = document.createElement('LI');
48a065 3733         li.innerHTML = text.replace(new RegExp('('+RegExp.escape(value)+')', 'ig'), '##$1%%').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/##([^%]+)%%/g, '<b>$1</b>');
2c8e84 3734         li.onmouseover = function(){ ref.ksearch_select(this); };
69ad1e 3735         li.onmouseup = function(){ ref.ksearch_click(this) };
187199 3736         li._rcm_id = this.env.contacts.length + i;
4e17e6 3737         ul.appendChild(li);
0213f8 3738         maxlen -= 1;
2c8e84 3739       }
T 3740     }
0213f8 3741
A 3742     if (ul.childNodes.length) {
3743       this.ksearch_pane.show();
3744       // select the first
3745       if (!this.env.contacts.length) {
3746         $('li:first', ul).attr('id', 'rcmksearchSelected').addClass('selected');
3747         this.ksearch_selected = 0;
3748       }
3749     }
3750
3751     if (results && results.length)
3752       this.env.contacts = this.env.contacts.concat(results);
3753
3754     // run next parallel search
48a065 3755     if (data.id == reqid) {
f8ca74 3756       data.num--;
48a065 3757       if (maxlen > 0 && data.sources.length) {
A 3758         var lock, xhr, source = data.sources.shift();
5f7129 3759         if (source) {
A 3760           lock = this.display_message(this.get_label('searching'), 'loading');
48a065 3761           xhr = this.http_post(data.action, '_search='+urlencode(value)+'&_id='+reqid
5f7129 3762             +'&_source='+urlencode(source), lock);
0213f8 3763
5f7129 3764           this.ksearch_data.locks.push(lock);
A 3765           this.ksearch_data.requests.push(xhr);
3766         }
0213f8 3767       }
48a065 3768       else if (!maxlen) {
A 3769         if (!this.ksearch_msg)
3770           this.ksearch_msg = this.display_message(this.get_label('autocompletemore'));
3771         // abort pending searches
3772         this.ksearch_abort();
3773       }
0213f8 3774     }
2c8e84 3775   };
8fa922 3776
2c8e84 3777   this.ksearch_click = function(node)
T 3778   {
7b0eac 3779     if (this.ksearch_input)
A 3780       this.ksearch_input.focus();
3781
2c8e84 3782     this.insert_recipient(node._rcm_id);
T 3783     this.ksearch_hide();
3784   };
4e17e6 3785
2c8e84 3786   this.ksearch_blur = function()
8fa922 3787   {
4e17e6 3788     if (this.ksearch_timer)
T 3789       clearTimeout(this.ksearch_timer);
3790
3791     this.ksearch_input = null;
3792     this.ksearch_hide();
8fa922 3793   };
4e17e6 3794
T 3795   this.ksearch_hide = function()
8fa922 3796   {
4e17e6 3797     this.ksearch_selected = null;
0213f8 3798     this.ksearch_value = '';
8fa922 3799
4e17e6 3800     if (this.ksearch_pane)
cc97ea 3801       this.ksearch_pane.hide();
31f05c 3802
A 3803     this.ksearch_destroy();
3804   };
4e17e6 3805
48a065 3806   // Clears autocomplete data/requests
0213f8 3807   this.ksearch_destroy = function()
A 3808   {
48a065 3809     this.ksearch_abort();
0213f8 3810
5f7129 3811     if (this.ksearch_info)
A 3812       this.hide_message(this.ksearch_info);
3813
3814     if (this.ksearch_msg)
3815       this.hide_message(this.ksearch_msg);
3816
0213f8 3817     this.ksearch_data = null;
5f7129 3818     this.ksearch_info = null;
A 3819     this.ksearch_msg = null;
0213f8 3820   }
4e17e6 3821
48a065 3822   // Aborts pending autocomplete requests
A 3823   this.ksearch_abort = function()
3824   {
3825     var i, len, ac = this.ksearch_data;
3826
3827     if (!ac)
3828       return;
3829
3830     for (i=0, len=ac.locks.length; i<len; i++)
3831       this.abort_request({request: ac.requests[i], lock: ac.locks[i]});
3832   };
3833
3834
4e17e6 3835   /*********************************************************/
T 3836   /*********         address book methods          *********/
3837   /*********************************************************/
3838
6b47de 3839   this.contactlist_keypress = function(list)
8fa922 3840   {
A 3841     if (list.key_pressed == list.DELETE_KEY)
3842       this.command('delete');
3843   };
6b47de 3844
T 3845   this.contactlist_select = function(list)
8fa922 3846   {
A 3847     if (this.preview_timer)
3848       clearTimeout(this.preview_timer);
f11541 3849
ecf295 3850     var n, id, sid, ref = this, writable = false,
A 3851       source = this.env.source ? this.env.address_sources[this.env.source] : null;
3852
8fa922 3853     if (id = list.get_single_selection())
A 3854       this.preview_timer = window.setTimeout(function(){ ref.load_contact(id, 'show'); }, 200);
3855     else if (this.env.contentframe)
3856       this.show_contentframe(false);
6b47de 3857
ecf295 3858     // no source = search result, we'll need to detect if any of
A 3859     // selected contacts are in writable addressbook to enable edit/delete
3860     if (list.selection.length) {
3861       if (!source) {
3862         for (n in list.selection) {
3863           sid = String(list.selection[n]).replace(/^[^-]+-/, '');
3864           if (sid && this.env.address_sources[sid] && !this.env.address_sources[sid].readonly) {
3865             writable = true;
3866             break;
3867           }
3868         }
3869       }
3870       else {
3871         writable = !source.readonly;
3872       }
3873     }
3874
dc6c4f 3875     this.enable_command('compose', this.env.group || list.selection.length > 0);
ecf295 3876     this.enable_command('edit', id && writable);
A 3877     this.enable_command('delete', list.selection.length && writable);
6b47de 3878
8fa922 3879     return false;
A 3880   };
6b47de 3881
a61bbb 3882   this.list_contacts = function(src, group, page)
8fa922 3883   {
f8e48d 3884     var folder, add_url = '',
053e5a 3885       target = window;
8fa922 3886
bb8012 3887     if (!src)
f11541 3888       src = this.env.source;
8fa922 3889
a61bbb 3890     if (page && this.current_page == page && src == this.env.source && group == this.env.group)
4e17e6 3891       return false;
8fa922 3892
A 3893     if (src != this.env.source) {
053e5a 3894       page = this.env.current_page = 1;
6b603d 3895       this.reset_qsearch();
8fa922 3896     }
a61bbb 3897     else if (group != this.env.group)
T 3898       page = this.env.current_page = 1;
f11541 3899
f8e48d 3900     if (this.env.search_id)
A 3901       folder = 'S'+this.env.search_id;
3902     else
3903       folder = group ? 'G'+src+group : src;
3904
3905     this.select_folder(folder);
8fa922 3906
f11541 3907     this.env.source = src;
a61bbb 3908     this.env.group = group;
4e17e6 3909
T 3910     // load contacts remotely
8fa922 3911     if (this.gui_objects.contactslist) {
a61bbb 3912       this.list_contacts_remote(src, group, page);
4e17e6 3913       return;
8fa922 3914     }
4e17e6 3915
8fa922 3916     if (this.env.contentframe && window.frames && window.frames[this.env.contentframe]) {
4e17e6 3917       target = window.frames[this.env.contentframe];
T 3918       add_url = '&_framed=1';
8fa922 3919     }
A 3920
a61bbb 3921     if (group)
T 3922       add_url += '&_gid='+group;
3923     if (page)
3924       add_url += '&_page='+page;
4e17e6 3925
f11541 3926     // also send search request to get the correct listing
T 3927     if (this.env.search_request)
3928       add_url += '&_search='+this.env.search_request;
3929
4e17e6 3930     this.set_busy(true, 'loading');
d7167e 3931     this.location_href(this.env.comm_path + (src ? '&_source='+urlencode(src) : '') + add_url, target);
8fa922 3932   };
4e17e6 3933
T 3934   // send remote request to load contacts list
a61bbb 3935   this.list_contacts_remote = function(src, group, page)
8fa922 3936   {
6b47de 3937     // clear message list first
e9a9f2 3938     this.list_contacts_clear();
4e17e6 3939
T 3940     // send request to server
ad334a 3941     var url = (src ? '_source='+urlencode(src) : '') + (page ? (src?'&':'') + '_page='+page : ''),
A 3942       lock = this.set_busy(true, 'loading');
3943
f11541 3944     this.env.source = src;
a61bbb 3945     this.env.group = group;
8fa922 3946
a61bbb 3947     if (group)
T 3948       url += '&_gid='+group;
8fa922 3949
f8e48d 3950     // also send search request to get the right messages
A 3951     if (this.env.search_request)
f11541 3952       url += '&_search='+this.env.search_request;
T 3953
ad334a 3954     this.http_request('list', url, lock);
e9a9f2 3955   };
A 3956
3957   this.list_contacts_clear = function()
3958   {
3959     this.contact_list.clear(true);
3960     this.show_contentframe(false);
dc6c4f 3961     this.enable_command('delete', false);
T 3962     this.enable_command('compose', this.env.group ? true : false);
8fa922 3963   };
4e17e6 3964
T 3965   // load contact record
3966   this.load_contact = function(cid, action, framed)
8fa922 3967   {
356a79 3968     var add_url = '', target = window;
A 3969
8fa922 3970     if (this.env.contentframe && window.frames && window.frames[this.env.contentframe]) {
4e17e6 3971       add_url = '&_framed=1';
T 3972       target = window.frames[this.env.contentframe];
f11541 3973       this.show_contentframe(true);
1a3c91 3974
A 3975       // load dummy content
3976       if (!cid) {
3977         // unselect selected row(s)
3978         this.contact_list.clear_selection();
3979         this.enable_command('delete', 'compose', false);
3980       }
8fa922 3981     }
4e17e6 3982     else if (framed)
T 3983       return false;
8fa922 3984
A 3985     if (action && (cid || action=='add') && !this.drag_active) {
356a79 3986       if (this.env.group)
A 3987         add_url += '&_gid='+urlencode(this.env.group);
3988
a16400 3989       this.location_href(this.env.comm_path+'&_action='+action
A 3990         +'&_source='+urlencode(this.env.source)
dc0be3 3991         +'&_cid='+urlencode(cid) + add_url, target, true);
8fa922 3992     }
1c5853 3993     return true;
8fa922 3994   };
f11541 3995
2c77f5 3996   // add/delete member to/from the group
A 3997   this.group_member_change = function(what, cid, source, gid)
3998   {
3999     what = what == 'add' ? 'add' : 'del';
4000     var lock = this.display_message(this.get_label(what == 'add' ? 'addingmember' : 'removingmember'), 'loading');
4001
4002     this.http_post('group-'+what+'members', '_cid='+urlencode(cid)
4003       + '&_source='+urlencode(source)
4004       + '&_gid='+urlencode(gid), lock);
4005   };
4006
f11541 4007   // copy a contact to the specified target (group or directory)
T 4008   this.copy_contact = function(cid, to)
8fa922 4009   {
f11541 4010     if (!cid)
T 4011       cid = this.contact_list.get_selection().join(',');
4012
2c77f5 4013     if (to.type == 'group' && to.source == this.env.source)
A 4014       this.group_member_change('add', cid, to.source, to.id);
ca38db 4015     else if (to.type == 'group' && !this.env.address_sources[to.source].readonly) {
2c77f5 4016       var lock = this.display_message(this.get_label('copyingcontact'), 'loading');
ca38db 4017       this.http_post('copy', '_cid='+urlencode(cid)
T 4018         + '&_source='+urlencode(this.env.source)
4019         + '&_to='+urlencode(to.source)
4020         + '&_togid='+urlencode(to.id)
2c77f5 4021         + (this.env.group ? '&_gid='+urlencode(this.env.group) : ''), lock);
ca38db 4022     }
T 4023     else if (to.id != this.env.source && cid && this.env.address_sources[to.id] && !this.env.address_sources[to.id].readonly) {
2c77f5 4024       var lock = this.display_message(this.get_label('copyingcontact'), 'loading');
ca38db 4025       this.http_post('copy', '_cid='+urlencode(cid)
T 4026         + '&_source='+urlencode(this.env.source)
4027         + '&_to='+urlencode(to.id)
2c77f5 4028         + (this.env.group ? '&_gid='+urlencode(this.env.group) : ''), lock);
ca38db 4029     }
8fa922 4030   };
4e17e6 4031
T 4032   this.delete_contacts = function()
8fa922 4033   {
4e17e6 4034     // exit if no mailbox specified or if selection is empty
b17539 4035     var selection = this.contact_list.get_selection(),
A 4036       undelete = this.env.address_sources[this.env.source].undelete;
4037
d06e57 4038     if (!(selection.length || this.env.cid) || (!undelete && !confirm(this.get_label('deletecontactconfirm'))))
4e17e6 4039       return;
8fa922 4040
ecf295 4041     var id, n, a_cids = [], qs = '';
4e17e6 4042
T 4043     if (this.env.cid)
0e7b66 4044       a_cids.push(this.env.cid);
8fa922 4045     else {
ecf295 4046       for (n=0; n<selection.length; n++) {
6b47de 4047         id = selection[n];
0e7b66 4048         a_cids.push(id);
f4f8c6 4049         this.contact_list.remove_row(id, (n == selection.length-1));
8fa922 4050       }
4e17e6 4051
T 4052       // hide content frame if we delete the currently displayed contact
f11541 4053       if (selection.length == 1)
T 4054         this.show_contentframe(false);
8fa922 4055     }
4e17e6 4056
8458c7 4057     if (this.env.group)
T 4058       qs += '&_gid='+urlencode(this.env.group);
4059
b15568 4060     // also send search request to get the right records from the next page
ecf295 4061     if (this.env.search_request)
b15568 4062       qs += '&_search='+this.env.search_request;
T 4063
4e17e6 4064     // send request to server
b17539 4065     this.http_post('delete', '_cid='+urlencode(a_cids.join(','))
A 4066       +'&_source='+urlencode(this.env.source)
4067       +'&_from='+(this.env.action ? this.env.action : '')+qs,
4068       this.display_message(this.get_label('contactdeleting'), 'loading'));
8fa922 4069
1c5853 4070     return true;
8fa922 4071   };
4e17e6 4072
T 4073   // update a contact record in the list
5db6f9 4074   this.update_contact_row = function(cid, cols_arr, newcid, source)
cc97ea 4075   {
3a24a1 4076     var c, row, list = this.contact_list;
ce988a 4077
3a24a1 4078     cid = String(cid).replace(this.identifier_expr, '_');
A 4079
5db6f9 4080     // when in searching mode, concat cid with the source name
A 4081     if (!list.rows[cid]) {
4082       cid = cid+'-'+source;
4083       if (newcid)
4084         newcid = newcid+'-'+source;
4085     }
4086
3a24a1 4087     if (list.rows[cid] && (row = list.rows[cid].obj)) {
A 4088       for (c=0; c<cols_arr.length; c++)
6b47de 4089         if (row.cells[c])
cc97ea 4090           $(row.cells[c]).html(cols_arr[c]);
6b47de 4091
e83f03 4092       // cid change
A 4093       if (newcid) {
3a24a1 4094         newcid = String(newcid).replace(this.identifier_expr, '_');
a61bbb 4095         row.id = 'rcmrow' + newcid;
3a24a1 4096         list.remove_row(cid);
A 4097         list.init_row(row);
4098         list.selection[0] = newcid;
27ea62 4099         row.style.display = '';
e83f03 4100       }
cc97ea 4101     }
T 4102   };
e83f03 4103
A 4104   // add row to contacts list
4105   this.add_contact_row = function(cid, cols, select)
8fa922 4106   {
c84d33 4107     if (!this.gui_objects.contactslist)
e83f03 4108       return false;
8fa922 4109
c84d33 4110     var c, list = this.contact_list,
8fa922 4111       row = document.createElement('tr');
A 4112
ce988a 4113     row.id = 'rcmrow'+String(cid).replace(this.identifier_expr, '_');
57863c 4114     row.className = 'contact';
8fa922 4115
c84d33 4116     if (list.in_selection(cid))
e83f03 4117       row.className += ' selected';
A 4118
4119     // add each submitted col
57863c 4120     for (c in cols) {
e83f03 4121       col = document.createElement('td');
A 4122       col.className = String(c).toLowerCase();
4123       col.innerHTML = cols[c];
4124       row.appendChild(col);
4125     }
8fa922 4126
c84d33 4127     list.insert_row(row);
8fa922 4128
c84d33 4129     this.enable_command('export', list.rowcount > 0);
e50551 4130   };
A 4131
4132   this.init_contact_form = function()
4133   {
4134     var ref = this, col;
4135
4136     this.set_photo_actions($('#ff_photo').val());
4137
4138     for (col in this.env.coltypes)
4139       this.init_edit_field(col, null);
4140
4141     $('.contactfieldgroup .row a.deletebutton').click(function() {
4142       ref.delete_edit_field(this);
4143       return false;
4144     });
4145
4146     $('select.addfieldmenu').change(function(e) {
4147       ref.insert_edit_field($(this).val(), $(this).attr('rel'), this);
4148       this.selectedIndex = 0;
4149     });
4150
537c39 4151     // enable date pickers on date fields
T 4152     if ($.datepicker && this.env.date_format) {
4153       $.datepicker.setDefaults({
4154         dateFormat: this.env.date_format,
4155         changeMonth: true,
4156         changeYear: true,
4157         yearRange: '-100:+10',
4158         showOtherMonths: true,
4159         selectOtherMonths: true,
4160         monthNamesShort: this.env.month_names,
4161         onSelect: function(dateText) { $(this).focus().val(dateText) }
4162       });
4163       $('input.datepicker').datepicker();
4164     }
4165
e50551 4166     $("input[type='text']:visible").first().focus();
8fa922 4167   };
A 4168
edfe91 4169   this.group_create = function()
a61bbb 4170   {
f8e48d 4171     this.add_input_row('contactgroup');
a61bbb 4172   };
8fa922 4173
edfe91 4174   this.group_rename = function()
3baa72 4175   {
T 4176     if (!this.env.group || !this.gui_objects.folderlist)
4177       return;
8fa922 4178
3baa72 4179     if (!this.name_input) {
f4f452 4180       this.enable_command('list', 'listgroup', false);
86f3aa 4181       this.name_input = $('<input>').attr('type', 'text').val(this.env.contactgroups['G'+this.env.source+this.env.group].name);
1fe60e 4182       this.name_input.bind('keydown', function(e){ return rcmail.add_input_keydown(e); });
3baa72 4183       this.env.group_renaming = true;
T 4184
86f3aa 4185       var link, li = this.get_folder_li(this.env.source+this.env.group, 'rcmliG');
3baa72 4186       if (li && (link = li.firstChild)) {
86f3aa 4187         $(link).hide().before(this.name_input);
3baa72 4188       }
T 4189     }
4190
9601f0 4191     this.name_input.select().focus();
3baa72 4192   };
8fa922 4193
edfe91 4194   this.group_delete = function()
3baa72 4195   {
5731d6 4196     if (this.env.group && confirm(this.get_label('deletegroupconfirm'))) {
A 4197       var lock = this.set_busy(true, 'groupdeleting');
4198       this.http_post('group-delete', '_source='+urlencode(this.env.source)+'&_gid='+urlencode(this.env.group), lock);
4199     }
3baa72 4200   };
8fa922 4201
3baa72 4202   // callback from server upon group-delete command
bb8012 4203   this.remove_group_item = function(prop)
3baa72 4204   {
bb8012 4205     var li, key = 'G'+prop.source+prop.id;
3baa72 4206     if ((li = this.get_folder_li(key))) {
fc1b72 4207       this.triggerEvent('group_delete', { source:prop.source, id:prop.id, li:li });
8fa922 4208
3baa72 4209       li.parentNode.removeChild(li);
T 4210       delete this.env.contactfolders[key];
4211       delete this.env.contactgroups[key];
4212     }
8fa922 4213
bb8012 4214     this.list_contacts(prop.source, 0);
3baa72 4215   };
8fa922 4216
f8e48d 4217   // @TODO: maybe it would be better to use popup instead of inserting input to the list?
A 4218   this.add_input_row = function(type)
4219   {
4220     if (!this.gui_objects.folderlist)
4221       return;
4222
4223     if (!this.name_input) {
4224       this.name_input = $('<input>').attr('type', 'text').data('tt', type);
4225       this.name_input.bind('keydown', function(e){ return rcmail.add_input_keydown(e); });
4226       this.name_input_li = $('<li>').addClass(type).append(this.name_input);
4227
4228       var li = type == 'contactsearch' ? $('li:last', this.gui_objects.folderlist) : this.get_folder_li(this.env.source);
4229       this.name_input_li.insertAfter(li);
4230     }
4231
4232     this.name_input.select().focus();
4233   };
4234
a61bbb 4235   // handler for keyboard events on the input field
1fe60e 4236   this.add_input_keydown = function(e)
a61bbb 4237   {
f8e48d 4238     var key = rcube_event.get_keycode(e),
A 4239       input = $(e.target), itype = input.data('tt');
a61bbb 4240
T 4241     // enter
4242     if (key == 13) {
f8e48d 4243       var newname = input.val();
8fa922 4244
a61bbb 4245       if (newname) {
ad334a 4246         var lock = this.set_busy(true, 'loading');
f8e48d 4247
A 4248         if (itype == 'contactsearch')
4249           this.http_post('search-create', '_search='+urlencode(this.env.search_request)+'&_name='+urlencode(newname), lock);
4250         else if (this.env.group_renaming)
ad334a 4251           this.http_post('group-rename', '_source='+urlencode(this.env.source)+'&_gid='+urlencode(this.env.group)+'&_name='+urlencode(newname), lock);
3baa72 4252         else
ad334a 4253           this.http_post('group-create', '_source='+urlencode(this.env.source)+'&_name='+urlencode(newname), lock);
a61bbb 4254       }
T 4255       return false;
4256     }
4257     // escape
4258     else if (key == 27)
4259       this.reset_add_input();
8fa922 4260
a61bbb 4261     return true;
T 4262   };
8fa922 4263
a61bbb 4264   this.reset_add_input = function()
T 4265   {
4266     if (this.name_input) {
3baa72 4267       if (this.env.group_renaming) {
86f3aa 4268         var li = this.name_input.parent();
T 4269         li.children().last().show();
3baa72 4270         this.env.group_renaming = false;
T 4271       }
8fa922 4272
86f3aa 4273       this.name_input.remove();
fb4663 4274
86f3aa 4275       if (this.name_input_li)
T 4276         this.name_input_li.remove();
fb4663 4277
86f3aa 4278       this.name_input = this.name_input_li = null;
a61bbb 4279     }
f4f452 4280
T 4281     this.enable_command('list', 'listgroup', true);
a61bbb 4282   };
8fa922 4283
a61bbb 4284   // callback for creating a new contact group
T 4285   this.insert_contact_group = function(prop)
4286   {
4287     this.reset_add_input();
8fa922 4288
0dc5bc 4289     prop.type = 'group';
1564d4 4290     var key = 'G'+prop.source+prop.id,
A 4291       link = $('<a>').attr('href', '#')
4292         .attr('rel', prop.source+':'+prop.id)
4293         .click(function() { return rcmail.command('listgroup', prop, this); })
4294         .html(prop.name),
4295       li = $('<li>').attr({id: 'rcmli'+key.replace(this.identifier_expr, '_'), 'class': 'contactgroup'})
4296         .append(link);
0dc5bc 4297
1564d4 4298     this.env.contactfolders[key] = this.env.contactgroups[key] = prop;
A 4299     this.add_contact_group_row(prop, li);
8fa922 4300
fc1b72 4301     this.triggerEvent('group_insert', { id:prop.id, source:prop.source, name:prop.name, li:li[0] });
3baa72 4302   };
8fa922 4303
3baa72 4304   // callback for renaming a contact group
bb8012 4305   this.update_contact_group = function(prop)
3baa72 4306   {
T 4307     this.reset_add_input();
8fa922 4308
360bd3 4309     var key = 'G'+prop.source+prop.id,
T 4310       li = this.get_folder_li(key),
4311       link;
8fa922 4312
360bd3 4313     // group ID has changed, replace link node and identifiers
T 4314     if (li && prop.newid) {
1564d4 4315       var newkey = 'G'+prop.source+prop.newid,
A 4316         newprop = $.extend({}, prop);;
4317
360bd3 4318       li.id = String('rcmli'+newkey).replace(this.identifier_expr, '_');
T 4319       this.env.contactfolders[newkey] = this.env.contactfolders[key];
ec6c39 4320       this.env.contactfolders[newkey].id = prop.newid;
360bd3 4321       this.env.group = prop.newid;
d1d9fd 4322
1564d4 4323       delete this.env.contactfolders[key];
A 4324       delete this.env.contactgroups[key];
4325
360bd3 4326       newprop.id = prop.newid;
T 4327       newprop.type = 'group';
d1d9fd 4328
360bd3 4329       link = $('<a>').attr('href', '#')
T 4330         .attr('rel', prop.source+':'+prop.newid)
1564d4 4331         .click(function() { return rcmail.command('listgroup', newprop, this); })
360bd3 4332         .html(prop.name);
T 4333       $(li).children().replaceWith(link);
4334     }
4335     // update displayed group name
4336     else if (li && (link = li.firstChild) && link.tagName.toLowerCase() == 'a')
bb8012 4337       link.innerHTML = prop.name;
8fa922 4338
0bc51d 4339     this.env.contactfolders[key].name = this.env.contactgroups[key].name = prop.name;
1564d4 4340     this.add_contact_group_row(prop, $(li), true);
A 4341
360bd3 4342     this.triggerEvent('group_update', { id:prop.id, source:prop.source, name:prop.name, li:li[0], newid:prop.newid });
62811c 4343   };
A 4344
1564d4 4345   // add contact group row to the list, with sorting
A 4346   this.add_contact_group_row = function(prop, li, reloc)
4347   {
4348     var row, name = prop.name.toUpperCase(),
4349       sibling = this.get_folder_li(prop.source),
4350       prefix = 'rcmliG'+(prop.source).replace(this.identifier_expr, '_');
4351
4352     // When renaming groups, we need to remove it from DOM and insert it in the proper place
4353     if (reloc) {
4354       row = li.clone(true);
4355       li.remove();
4356     }
4357     else
4358       row = li;
4359
4360     $('li[id^="'+prefix+'"]', this.gui_objects.folderlist).each(function(i, elem) {
4361       if (name >= $(this).text().toUpperCase())
4362         sibling = elem;
4363       else
4364         return false;
4365     });
4366
4367     row.insertAfter(sibling);
4368   };
4369
62811c 4370   this.update_group_commands = function()
A 4371   {
4372     var source = this.env.source != '' ? this.env.address_sources[this.env.source] : null;
4373     this.enable_command('group-create', (source && source.groups && !source.readonly));
4374     this.enable_command('group-rename', 'group-delete', (source && source.groups && this.env.group && !source.readonly));
3baa72 4375   };
4e17e6 4376
0501b6 4377   this.init_edit_field = function(col, elem)
T 4378   {
4379     if (!elem)
4380       elem = $('.ff_' + col);
d1d9fd 4381
0501b6 4382     elem.focus(function(){ ref.focus_textfield(this); })
T 4383       .blur(function(){ ref.blur_textfield(this); })
2429cf 4384       .each(function(){ this._placeholder = this.title = (ref.env.coltypes[col].label || ''); ref.blur_textfield(this); });
0501b6 4385   };
T 4386
4387   this.insert_edit_field = function(col, section, menu)
4388   {
4389     // just make pre-defined input field visible
4390     var elem = $('#ff_'+col);
4391     if (elem.length) {
4392       elem.show().focus();
491133 4393       $(menu).children('option[value="'+col+'"]').prop('disabled', true);
0501b6 4394     }
T 4395     else {
4396       var lastelem = $('.ff_'+col),
4397         appendcontainer = $('#contactsection'+section+' .contactcontroller'+col);
e9a9f2 4398
0501b6 4399       if (!appendcontainer.length)
T 4400         appendcontainer = $('<fieldset>').addClass('contactfieldgroup contactcontroller'+col).insertAfter($('#contactsection'+section+' .contactfieldgroup').last());
4401
4402       if (appendcontainer.length && appendcontainer.get(0).nodeName == 'FIELDSET') {
4403         var input, colprop = this.env.coltypes[col],
4404           row = $('<div>').addClass('row'),
4405           cell = $('<div>').addClass('contactfieldcontent data'),
4406           label = $('<div>').addClass('contactfieldlabel label');
e9a9f2 4407
0501b6 4408         if (colprop.subtypes_select)
T 4409           label.html(colprop.subtypes_select);
4410         else
4411           label.html(colprop.label);
4412
4413         var name_suffix = colprop.limit != 1 ? '[]' : '';
4414         if (colprop.type == 'text' || colprop.type == 'date') {
4415           input = $('<input>')
4416             .addClass('ff_'+col)
491133 4417             .attr({type: 'text', name: '_'+col+name_suffix, size: colprop.size})
0501b6 4418             .appendTo(cell);
T 4419
4420           this.init_edit_field(col, input);
537c39 4421           
T 4422           if (colprop.type == 'date' && $.datepicker)
4423             input.datepicker();
0501b6 4424         }
T 4425         else if (colprop.type == 'composite') {
b0c70b 4426           var childcol, cp, first, templ, cols = [], suffices = [];
T 4427           // read template for composite field order
4428           if ((templ = this.env[col+'_template'])) {
4429             for (var j=0; j < templ.length; j++) {
4430               cols.push(templ[j][1]);
4431               suffices.push(templ[j][2]);
4432             }
4433           }
4434           else {  // list fields according to appearance in colprop
4435             for (childcol in colprop.childs)
4436               cols.push(childcol);
4437           }
ecf295 4438
b0c70b 4439           for (var i=0; i < cols.length; i++) {
T 4440             childcol = cols[i];
0501b6 4441             cp = colprop.childs[childcol];
T 4442             input = $('<input>')
4443               .addClass('ff_'+childcol)
b0c70b 4444               .attr({ type: 'text', name: '_'+childcol+name_suffix, size: cp.size })
0501b6 4445               .appendTo(cell);
b0c70b 4446             cell.append(suffices[i] || " ");
0501b6 4447             this.init_edit_field(childcol, input);
T 4448             if (!first) first = input;
4449           }
4450           input = first;  // set focus to the first of this composite fields
4451         }
4452         else if (colprop.type == 'select') {
4453           input = $('<select>')
4454             .addClass('ff_'+col)
4455             .attr('name', '_'+col+name_suffix)
4456             .appendTo(cell);
e9a9f2 4457
0501b6 4458           var options = input.attr('options');
T 4459           options[options.length] = new Option('---', '');
4460           if (colprop.options)
4461             $.each(colprop.options, function(i, val){ options[options.length] = new Option(val, i); });
4462         }
4463
4464         if (input) {
4465           var delbutton = $('<a href="#del"></a>')
4466             .addClass('contactfieldbutton deletebutton')
491133 4467             .attr({title: this.get_label('delete'), rel: col})
0501b6 4468             .html(this.env.delbutton)
T 4469             .click(function(){ ref.delete_edit_field(this); return false })
4470             .appendTo(cell);
e9a9f2 4471
0501b6 4472           row.append(label).append(cell).appendTo(appendcontainer.show());
T 4473           input.first().focus();
e9a9f2 4474
0501b6 4475           // disable option if limit reached
T 4476           if (!colprop.count) colprop.count = 0;
4477           if (++colprop.count == colprop.limit && colprop.limit)
491133 4478             $(menu).children('option[value="'+col+'"]').prop('disabled', true);
0501b6 4479         }
T 4480       }
4481     }
4482   };
4483
4484   this.delete_edit_field = function(elem)
4485   {
4486     var col = $(elem).attr('rel'),
4487       colprop = this.env.coltypes[col],
4488       fieldset = $(elem).parents('fieldset.contactfieldgroup'),
4489       addmenu = fieldset.parent().find('select.addfieldmenu');
e9a9f2 4490
0501b6 4491     // just clear input but don't hide the last field
T 4492     if (--colprop.count <= 0 && colprop.visible)
4493       $(elem).parent().children('input').val('').blur();
4494     else {
4495       $(elem).parents('div.row').remove();
4496       // hide entire fieldset if no more rows
4497       if (!fieldset.children('div.row').length)
4498         fieldset.hide();
4499     }
e9a9f2 4500
0501b6 4501     // enable option in add-field selector or insert it if necessary
T 4502     if (addmenu.length) {
4503       var option = addmenu.children('option[value="'+col+'"]');
4504       if (option.length)
491133 4505         option.prop('disabled', false);
0501b6 4506       else
T 4507         option = $('<option>').attr('value', col).html(colprop.label).appendTo(addmenu);
4508       addmenu.show();
4509     }
4510   };
4511
4512   this.upload_contact_photo = function(form)
4513   {
4514     if (form && form.elements._photo.value) {
4515       this.async_upload_form(form, 'upload-photo', function(e) {
4516         rcmail.set_busy(false, null, rcmail.photo_upload_id);
4517       });
4518
4519       // display upload indicator
4520       this.photo_upload_id = this.set_busy(true, 'uploading');
4521     }
4522   };
e50551 4523
0501b6 4524   this.replace_contact_photo = function(id)
T 4525   {
4526     var img_src = id == '-del-' ? this.env.photo_placeholder :
4527       this.env.comm_path + '&_action=photo&_source=' + this.env.source + '&_cid=' + this.env.cid + '&_photo=' + id;
e50551 4528
A 4529     this.set_photo_actions(id);
0501b6 4530     $(this.gui_objects.contactphoto).children('img').attr('src', img_src);
T 4531   };
e50551 4532
0501b6 4533   this.photo_upload_end = function()
T 4534   {
4535     this.set_busy(false, null, this.photo_upload_id);
4536     delete this.photo_upload_id;
4537   };
4538
e50551 4539   this.set_photo_actions = function(id)
A 4540   {
4541     var n, buttons = this.buttons['upload-photo'];
27eb27 4542     for (n=0; buttons && n < buttons.length; n++)
e50551 4543       $('#'+buttons[n].id).html(this.get_label(id == '-del-' ? 'addphoto' : 'replacephoto'));
A 4544
4545     $('#ff_photo').val(id);
4546     this.enable_command('upload-photo', this.env.coltypes.photo ? true : false);
4547     this.enable_command('delete-photo', this.env.coltypes.photo && id != '-del-');
4548   };
4549
e9a9f2 4550   // load advanced search page
A 4551   this.advanced_search = function()
4552   {
4553     var add_url = '&_form=1', target = window;
4554
4555     if (this.env.contentframe && window.frames && window.frames[this.env.contentframe]) {
4556       add_url += '&_framed=1';
4557       target = window.frames[this.env.contentframe];
4558       this.contact_list.clear_selection();
4559     }
4560
dc0be3 4561     this.location_href(this.env.comm_path+'&_action=search'+add_url, target, true);
e9a9f2 4562
A 4563     return true;
ecf295 4564   };
A 4565
4566   // unselect directory/group
4567   this.unselect_directory = function()
4568   {
f8e48d 4569     this.select_folder('');
A 4570     this.enable_command('search-delete', false);
4571   };
4572
4573   // callback for creating a new saved search record
4574   this.insert_saved_search = function(name, id)
4575   {
4576     this.reset_add_input();
4577
4578     var key = 'S'+id,
4579       link = $('<a>').attr('href', '#')
4580         .attr('rel', id)
4581         .click(function() { return rcmail.command('listsearch', id, this); })
4582         .html(name),
4583       li = $('<li>').attr({id: 'rcmli'+key.replace(this.identifier_expr, '_'), 'class': 'contactsearch'})
4584         .append(link),
4585       prop = {name:name, id:id, li:li[0]};
4586
4587     this.add_saved_search_row(prop, li);
4588     this.select_folder('S'+id);
4589     this.enable_command('search-delete', true);
4590     this.env.search_id = id;
4591
4592     this.triggerEvent('abook_search_insert', prop);
4593   };
4594
4595   // add saved search row to the list, with sorting
4596   this.add_saved_search_row = function(prop, li, reloc)
4597   {
4598     var row, sibling, name = prop.name.toUpperCase();
4599
4600     // When renaming groups, we need to remove it from DOM and insert it in the proper place
4601     if (reloc) {
4602       row = li.clone(true);
4603       li.remove();
ecf295 4604     }
f8e48d 4605     else
A 4606       row = li;
4607
4608     $('li[class~="contactsearch"]', this.gui_objects.folderlist).each(function(i, elem) {
4609       if (!sibling)
4610         sibling = this.previousSibling;
4611
4612       if (name >= $(this).text().toUpperCase())
4613         sibling = elem;
4614       else
4615         return false;
4616     });
4617
4618     if (sibling)
4619       row.insertAfter(sibling);
4620     else
4621       row.appendTo(this.gui_objects.folderlist);
4622   };
4623
4624   // creates an input for saved search name
4625   this.search_create = function()
4626   {
4627     this.add_input_row('contactsearch');
4628   };
4629
4630   this.search_delete = function()
4631   {
4632     if (this.env.search_request) {
4633       var lock = this.set_busy(true, 'savedsearchdeleting');
4634       this.http_post('search-delete', '_sid='+urlencode(this.env.search_id), lock);
4635     }
4636   };
4637
4638   // callback from server upon search-delete command
4639   this.remove_search_item = function(id)
4640   {
4641     var li, key = 'S'+id;
4642     if ((li = this.get_folder_li(key))) {
4643       this.triggerEvent('search_delete', { id:id, li:li });
4644
4645       li.parentNode.removeChild(li);
4646     }
4647
4648     this.env.search_id = null;
4649     this.env.search_request = null;
4650     this.list_contacts_clear();
4651     this.reset_qsearch();
4652     this.enable_command('search-delete', 'search-create', false);
4653   };
4654
4655   this.listsearch = function(id)
4656   {
4657     var folder, lock = this.set_busy(true, 'searching');
4658
4659     if (this.contact_list) {
4660       this.list_contacts_clear();
4661     }
4662
4663     this.reset_qsearch();
4664     this.select_folder('S'+id);
4665
4666     // reset vars
4667     this.env.current_page = 1;
4668     this.http_request('search', '_sid='+urlencode(id), lock);
e9a9f2 4669   };
A 4670
0501b6 4671
4e17e6 4672   /*********************************************************/
T 4673   /*********        user settings methods          *********/
4674   /*********************************************************/
4675
f05834 4676   // preferences section select and load options frame
A 4677   this.section_select = function(list)
8fa922 4678   {
dc0be3 4679     var id = list.get_single_selection(), add_url = '', target = window;
8fa922 4680
f05834 4681     if (id) {
A 4682       if (this.env.contentframe && window.frames && window.frames[this.env.contentframe]) {
4683         add_url = '&_framed=1';
4684         target = window.frames[this.env.contentframe];
4685       }
dc0be3 4686       this.location_href(this.env.comm_path+'&_action=edit-prefs&_section='+id+add_url, target, true);
8fa922 4687     }
f05834 4688
A 4689     return true;
8fa922 4690   };
f05834 4691
6b47de 4692   this.identity_select = function(list)
8fa922 4693   {
6b47de 4694     var id;
T 4695     if (id = list.get_single_selection())
4696       this.load_identity(id, 'edit-identity');
8fa922 4697   };
4e17e6 4698
e83f03 4699   // load identity record
4e17e6 4700   this.load_identity = function(id, action)
8fa922 4701   {
4e17e6 4702     if (action=='edit-identity' && (!id || id==this.env.iid))
1c5853 4703       return false;
4e17e6 4704
9601f0 4705     var add_url = '', target = window;
8fa922 4706
A 4707     if (this.env.contentframe && window.frames && window.frames[this.env.contentframe]) {
4e17e6 4708       add_url = '&_framed=1';
T 4709       target = window.frames[this.env.contentframe];
4710       document.getElementById(this.env.contentframe).style.visibility = 'inherit';
8fa922 4711     }
4e17e6 4712
8fa922 4713     if (action && (id || action=='add-identity')) {
4e17e6 4714       this.set_busy(true);
d7167e 4715       this.location_href(this.env.comm_path+'&_action='+action+'&_iid='+id+add_url, target);
8fa922 4716     }
A 4717
1c5853 4718     return true;
8fa922 4719   };
4e17e6 4720
T 4721   this.delete_identity = function(id)
8fa922 4722   {
4e17e6 4723     // exit if no mailbox specified or if selection is empty
6b47de 4724     var selection = this.identity_list.get_selection();
T 4725     if (!(selection.length || this.env.iid))
4e17e6 4726       return;
8fa922 4727
4e17e6 4728     if (!id)
6b47de 4729       id = this.env.iid ? this.env.iid : selection[0];
4e17e6 4730
2a5d02 4731     // append token to request
T 4732     this.goto_url('delete-identity', '_iid='+id+'&_token='+this.env.request_token, true);
8fa922 4733
1c5853 4734     return true;
254d5e 4735   };
A 4736
4737
4738   /*********************************************************/
4739   /*********        folder manager methods         *********/
4740   /*********************************************************/
4741
4742   this.init_subscription_list = function()
4743   {
4744     var p = this;
4745     this.subscription_list = new rcube_list_widget(this.gui_objects.subscriptionlist,
4746       {multiselect:false, draggable:true, keyboard:false, toggleselect:true});
4747     this.subscription_list.addEventListener('select', function(o){ p.subscription_select(o); });
4748     this.subscription_list.addEventListener('dragstart', function(o){ p.drag_active = true; });
4749     this.subscription_list.addEventListener('dragend', function(o){ p.subscription_move_folder(o); });
4750     this.subscription_list.row_init = function (row) {
4751       row.obj.onmouseover = function() { p.focus_subscription(row.id); };
4752       row.obj.onmouseout = function() { p.unfocus_subscription(row.id); };
4753     };
4754     this.subscription_list.init();
71cc6b 4755     $('#mailboxroot')
T 4756       .mouseover(function(){ p.focus_subscription(this.id); })
4757       .mouseout(function(){ p.unfocus_subscription(this.id); })
8fa922 4758   };
b0dbf3 4759
S 4760   this.focus_subscription = function(id)
8fa922 4761   {
A 4762     var row, folder,
ef17c5 4763       delim = RegExp.escape(this.env.delimiter),
A 4764       reg = RegExp('['+delim+']?[^'+delim+']+$');
3e71ab 4765
af3c04 4766     if (this.drag_active && this.env.mailbox && (row = document.getElementById(id)))
3e71ab 4767       if (this.env.subscriptionrows[id] &&
d0de4e 4768           (folder = this.env.subscriptionrows[id][0]) !== null
A 4769       ) {
3e71ab 4770         if (this.check_droptarget(folder) &&
af3c04 4771             !this.env.subscriptionrows[this.get_folder_row_id(this.env.mailbox)][2] &&
A 4772             (folder != this.env.mailbox.replace(reg, '')) &&
d0de4e 4773             (!folder.match(new RegExp('^'+RegExp.escape(this.env.mailbox+this.env.delimiter))))
A 4774         ) {
9f07d1 4775           this.env.dstfolder = folder;
cc97ea 4776           $(row).addClass('droptarget');
b0dbf3 4777         }
8fa922 4778       }
A 4779   };
b0dbf3 4780
S 4781   this.unfocus_subscription = function(id)
8fa922 4782   {
A 4783     var row = $('#'+id);
4784
9f07d1 4785     this.env.dstfolder = null;
8fa922 4786     if (this.env.subscriptionrows[id] && row[0])
A 4787       row.removeClass('droptarget');
4788     else
4789       $(this.subscription_list.frame).removeClass('droptarget');
4790   };
b0dbf3 4791
S 4792   this.subscription_select = function(list)
8fa922 4793   {
68b6a9 4794     var id, folder;
8fa922 4795
af3c04 4796     if (list && (id = list.get_single_selection()) &&
A 4797         (folder = this.env.subscriptionrows['rcmrow'+id])
4798     ) {
9f07d1 4799       this.env.mailbox = folder[0];
af3c04 4800       this.show_folder(folder[0]);
A 4801       this.enable_command('delete-folder', !folder[2]);
4802     }
4803     else {
4804       this.env.mailbox = null;
4805       this.show_contentframe(false);
4806       this.enable_command('delete-folder', 'purge', false);
4807     }
8fa922 4808   };
b0dbf3 4809
S 4810   this.subscription_move_folder = function(list)
8fa922 4811   {
ef17c5 4812     var delim = RegExp.escape(this.env.delimiter),
A 4813       reg = RegExp('['+delim+']?[^'+delim+']+$');
4814
d0de4e 4815     if (this.env.mailbox && this.env.dstfolder !== null && (this.env.dstfolder != this.env.mailbox) &&
af3c04 4816         (this.env.dstfolder != this.env.mailbox.replace(reg, ''))
A 4817     ) {
4818       reg = new RegExp('[^'+delim+']*['+delim+']', 'g');
71cc6b 4819       var basename = this.env.mailbox.replace(reg, ''),
d0de4e 4820         newname = this.env.dstfolder === '' ? basename : this.env.dstfolder+this.env.delimiter+basename;
0213f8 4821
71cc6b 4822       if (newname != this.env.mailbox) {
T 4823         this.http_post('rename-folder', '_folder_oldname='+urlencode(this.env.mailbox)+'&_folder_newname='+urlencode(newname), this.set_busy(true, 'foldermoving'));
4824         this.subscription_list.draglayer.hide();
4825       }
8fa922 4826     }
b0dbf3 4827     this.drag_active = false;
68b6a9 4828     this.unfocus_subscription(this.get_folder_row_id(this.env.dstfolder));
8fa922 4829   };
4e17e6 4830
24053e 4831   // tell server to create and subscribe a new mailbox
af3c04 4832   this.create_folder = function()
8fa922 4833   {
af3c04 4834     this.show_folder('', this.env.mailbox);
8fa922 4835   };
24053e 4836
T 4837   // delete a specific mailbox with all its messages
af3c04 4838   this.delete_folder = function(name)
8fa922 4839   {
af3c04 4840     var id = this.get_folder_row_id(name ? name : this.env.mailbox),
A 4841       folder = this.env.subscriptionrows[id][0];
fdbb19 4842
8fa922 4843     if (folder && confirm(this.get_label('deletefolderconfirm'))) {
ad334a 4844       var lock = this.set_busy(true, 'folderdeleting');
af3c04 4845       this.http_post('delete-folder', '_mbox='+urlencode(folder), lock);
8fa922 4846     }
A 4847   };
24053e 4848
254d5e 4849   // Add folder row to the table and initialize it
8fc0f9 4850   this.add_folder_row = function (name, display_name, is_protected, subscribed, skip_init, class_name)
8fa922 4851   {
24053e 4852     if (!this.gui_objects.subscriptionlist)
T 4853       return false;
4854
71cc6b 4855     var row, n, i, tmp, folders, rowid, list = [], slist = [],
8fa922 4856       tbody = this.gui_objects.subscriptionlist.tBodies[0],
71cc6b 4857       refrow = $('tr', tbody).get(1),
254d5e 4858       id = 'rcmrow'+((new Date).getTime());
8fa922 4859
254d5e 4860     if (!refrow) {
24053e 4861       // Refresh page if we don't have a table row to clone
6b47de 4862       this.goto_url('folders');
c5c3ae 4863       return false;
8fa922 4864     }
681a59 4865
5cd00e 4866     // clone a table row if there are existing rows
1a0343 4867     row = $(refrow).clone(true);
A 4868
4869     // set ID, reset css class
254d5e 4870     row.attr('id', id);
1a0343 4871     row.attr('class', class_name);
24053e 4872
T 4873     // set folder name
254d5e 4874     row.find('td:first').html(display_name);
8fa922 4875
254d5e 4876     // update subscription checkbox
A 4877     $('input[name="_subscribed[]"]', row).val(name)
8fc0f9 4878       .prop({checked: subscribed ? true : false, disabled: is_protected ? true : false});
c5c3ae 4879
254d5e 4880     // add to folder/row-ID map
A 4881     this.env.subscriptionrows[id] = [name, display_name, 0];
4882
4883     // sort folders, to find a place where to insert the row
71cc6b 4884     folders = [];
T 4885     $.each(this.env.subscriptionrows, function(k,v){ folders.push(v) });
4886     folders.sort(function(a,b){ return a[0] < b[0] ? -1 : (a[0] > b[0] ? 1 : 0) });
4887
254d5e 4888     for (n in folders) {
A 4889       // protected folder
4890       if (folders[n][2]) {
4891         slist.push(folders[n][0]);
4892         tmp = folders[n][0]+this.env.delimiter;
4893       }
4894       // protected folder's child
4895       else if (tmp && folders[n][0].indexOf(tmp) == 0)
4896         slist.push(folders[n][0]);
4897       // other
4898       else {
4899         list.push(folders[n][0]);
4900         tmp = null;
4901       }
4902     }
71cc6b 4903
T 4904     // check if subfolder of a protected folder
4905     for (n=0; n<slist.length; n++) {
4906       if (name.indexOf(slist[n]+this.env.delimiter) == 0)
4907         rowid = this.get_folder_row_id(slist[n]);
4908     }
254d5e 4909
A 4910     // find folder position after sorting
71cc6b 4911     for (n=0; !rowid && n<list.length; n++) {
T 4912       if (n && list[n] == name)
4913         rowid = this.get_folder_row_id(list[n-1]);
8fa922 4914     }
24053e 4915
254d5e 4916     // add row to the table
71cc6b 4917     if (rowid)
T 4918       $('#'+rowid).after(row);
254d5e 4919     else
A 4920       row.appendTo(tbody);
b0dbf3 4921
254d5e 4922     // update list widget
A 4923     this.subscription_list.clear_selection();
4924     if (!skip_init)
4925       this.init_subscription_list();
4926
4927     row = row.get(0);
4928     if (row.scrollIntoView)
4929       row.scrollIntoView();
4930
4931     return row;
8fa922 4932   };
24053e 4933
254d5e 4934   // replace an existing table row with a new folder line (with subfolders)
8fc0f9 4935   this.replace_folder_row = function(oldfolder, newfolder, display_name, is_protected, class_name)
8fa922 4936   {
254d5e 4937     if (!this.gui_objects.subscriptionlist)
A 4938       return false;
8fa922 4939
254d5e 4940     var i, n, len, name, dispname, oldrow, tmprow, row, level,
A 4941       tbody = this.gui_objects.subscriptionlist.tBodies[0],
4942       folders = this.env.subscriptionrows,
4943       id = this.get_folder_row_id(oldfolder),
4944       regex = new RegExp('^'+RegExp.escape(oldfolder)),
4945       subscribed = $('input[name="_subscribed[]"]', $('#'+id)).prop('checked'),
4946       // find subfolders of renamed folder
4947       list = this.get_subfolders(oldfolder);
4948
4949     // replace an existing table row
4950     this._remove_folder_row(id);
8fc0f9 4951     row = $(this.add_folder_row(newfolder, display_name, is_protected, subscribed, true, class_name));
254d5e 4952
A 4953     // detect tree depth change
4954     if (len = list.length) {
4955       level = (oldfolder.split(this.env.delimiter)).length - (newfolder.split(this.env.delimiter)).length;
4956     }
4957
4958     // move subfolders to the new branch
4959     for (n=0; n<len; n++) {
4960       id = list[n];
4961       name = this.env.subscriptionrows[id][0];
4962       dispname = this.env.subscriptionrows[id][1];
4963       oldrow = $('#'+id);
4964       tmprow = oldrow.clone(true);
4965       oldrow.remove();
4966       row.after(tmprow);
4967       row = tmprow;
4968       // update folder index
4969       name = name.replace(regex, newfolder);
4970       $('input[name="_subscribed[]"]', row).val(name);
4971       this.env.subscriptionrows[id][0] = name;
4972       // update the name if level is changed
4973       if (level != 0) {
4974         if (level > 0) {
4975           for (i=level; i>0; i--)
4976             dispname = dispname.replace(/^&nbsp;&nbsp;&nbsp;&nbsp;/, '');
4977         }
4978         else {
4979           for (i=level; i<0; i++)
4980             dispname = '&nbsp;&nbsp;&nbsp;&nbsp;' + dispname;
4981         }
4982         row.find('td:first').html(dispname);
4983         this.env.subscriptionrows[id][1] = dispname;
4984       }
4985     }
4986
4987     // update list widget
4988     this.init_subscription_list();
8fa922 4989   };
24053e 4990
T 4991   // remove the table row of a specific mailbox from the table
254d5e 4992   this.remove_folder_row = function(folder, subs)
8fa922 4993   {
254d5e 4994     var n, len, list = [], id = this.get_folder_row_id(folder);
8fa922 4995
254d5e 4996     // get subfolders if any
A 4997     if (subs)
4998       list = this.get_subfolders(folder);
4999
5000     // remove old row
5001     this._remove_folder_row(id);
5002
5003     // remove subfolders
5004     for (n=0, len=list.length; n<len; n++)
5005       this._remove_folder_row(list[n]);
8fa922 5006   };
254d5e 5007
A 5008   this._remove_folder_row = function(id)
5009   {
5010     this.subscription_list.remove_row(id.replace(/^rcmrow/, ''));
5011     $('#'+id).remove();
5012     delete this.env.subscriptionrows[id];
5013   }
5014
5015   this.get_subfolders = function(folder)
5016   {
5017     var name, list = [],
5018       regex = new RegExp('^'+RegExp.escape(folder)+RegExp.escape(this.env.delimiter)),
5019       row = $('#'+this.get_folder_row_id(folder)).get(0);
5020
5021     while (row = row.nextSibling) {
5022       if (row.id) {
5023         name = this.env.subscriptionrows[row.id][0];
5024         if (regex.test(name)) {
5025           list.push(row.id);
5026         }
5027         else
5028           break;
5029       }
5030     }
5031
5032     return list;
5033   }
4e17e6 5034
edfe91 5035   this.subscribe = function(folder)
8fa922 5036   {
af3c04 5037     if (folder) {
5be0d0 5038       var lock = this.display_message(this.get_label('foldersubscribing'), 'loading');
af3c04 5039       this.http_post('subscribe', '_mbox='+urlencode(folder), lock);
A 5040     }
8fa922 5041   };
4e17e6 5042
edfe91 5043   this.unsubscribe = function(folder)
8fa922 5044   {
af3c04 5045     if (folder) {
5be0d0 5046       var lock = this.display_message(this.get_label('folderunsubscribing'), 'loading');
af3c04 5047       this.http_post('unsubscribe', '_mbox='+urlencode(folder), lock);
A 5048     }
8fa922 5049   };
f52c93 5050
24053e 5051   // helper method to find a specific mailbox row ID
T 5052   this.get_folder_row_id = function(folder)
8fa922 5053   {
254d5e 5054     var id, folders = this.env.subscriptionrows;
A 5055     for (id in folders)
5056       if (folders[id] && folders[id][0] == folder)
24053e 5057         break;
8fa922 5058
24053e 5059     return id;
8fa922 5060   };
9bebdf 5061
af3c04 5062   // when user select a folder in manager
A 5063   this.show_folder = function(folder, path, force)
5064   {
5065     var target = window,
5066       url = '&_action=edit-folder&_mbox='+urlencode(folder);
5067
5068     if (path)
5069       url += '&_path='+urlencode(path);
5070
5071     if (this.env.contentframe && window.frames && window.frames[this.env.contentframe]) {
5072       target = window.frames[this.env.contentframe];
5073       url += '&_framed=1';
5074     }
5075
5076     if (String(target.location.href).indexOf(url) >= 0 && !force) {
5077       this.show_contentframe(true);
5078     }
5079     else {
dc0be3 5080       this.location_href(this.env.comm_path+url, target, true);
af3c04 5081     }
A 5082   };
5083
e81a30 5084   // disables subscription checkbox (for protected folder)
A 5085   this.disable_subscription = function(folder)
5086   {
5087     var id = this.get_folder_row_id(folder);
5088     if (id)
491133 5089       $('input[name="_subscribed[]"]', $('#'+id)).prop('disabled', true);
e81a30 5090   };
A 5091
af3c04 5092   this.folder_size = function(folder)
A 5093   {
5094     var lock = this.set_busy(true, 'loading');
5095     this.http_post('folder-size', '_mbox='+urlencode(folder), lock);
5096   };
5097
5098   this.folder_size_update = function(size)
5099   {
5100     $('#folder-size').replaceWith(size);
5101   };
5102
4e17e6 5103
T 5104   /*********************************************************/
5105   /*********           GUI functionality           *********/
5106   /*********************************************************/
5107
e639c5 5108   var init_button = function(cmd, prop)
T 5109   {
5110     var elm = document.getElementById(prop.id);
5111     if (!elm)
5112       return;
5113
5114     var preload = false;
5115     if (prop.type == 'image') {
5116       elm = elm.parentNode;
5117       preload = true;
5118     }
5119
5120     elm._command = cmd;
5121     elm._id = prop.id;
5122     if (prop.sel) {
5123       elm.onmousedown = function(e){ return rcmail.button_sel(this._command, this._id); };
5124       elm.onmouseup = function(e){ return rcmail.button_out(this._command, this._id); };
5125       if (preload)
5126         new Image().src = prop.sel;
5127     }
5128     if (prop.over) {
5129       elm.onmouseover = function(e){ return rcmail.button_over(this._command, this._id); };
5130       elm.onmouseout = function(e){ return rcmail.button_out(this._command, this._id); };
5131       if (preload)
5132         new Image().src = prop.over;
5133     }
5134   };
5135
f9a2a6 5136   // enable/disable buttons for page shifting
4e17e6 5137   this.set_page_buttons = function()
29f977 5138   {
fb4663 5139     this.enable_command('nextpage', 'lastpage', (this.env.pagecount > this.env.current_page));
A 5140     this.enable_command('previouspage', 'firstpage', (this.env.current_page > 1));
29f977 5141   };
8fa922 5142
29f977 5143   // set event handlers on registered buttons
T 5144   this.init_buttons = function()
5145   {
5146     for (var cmd in this.buttons) {
d8cf6d 5147       if (typeof cmd !== 'string')
29f977 5148         continue;
8fa922 5149
29f977 5150       for (var i=0; i< this.buttons[cmd].length; i++) {
e639c5 5151         init_button(cmd, this.buttons[cmd][i]);
29f977 5152       }
4e17e6 5153     }
29f977 5154   };
4e17e6 5155
T 5156   // set button to a specific state
5157   this.set_button = function(command, state)
8fa922 5158   {
A 5159     var button, obj, a_buttons = this.buttons[command];
4e17e6 5160
8fa922 5161     if (!a_buttons || !a_buttons.length)
4da1d7 5162       return false;
4e17e6 5163
8fa922 5164     for (var n=0; n<a_buttons.length; n++) {
4e17e6 5165       button = a_buttons[n];
T 5166       obj = document.getElementById(button.id);
5167
5168       // get default/passive setting of the button
104ee3 5169       if (obj && button.type=='image' && !button.status) {
4e17e6 5170         button.pas = obj._original_src ? obj._original_src : obj.src;
104ee3 5171         // respect PNG fix on IE browsers
T 5172         if (obj.runtimeStyle && obj.runtimeStyle.filter && obj.runtimeStyle.filter.match(/src=['"]([^'"]+)['"]/))
5173           button.pas = RegExp.$1;
5174       }
4e17e6 5175       else if (obj && !button.status)
T 5176         button.pas = String(obj.className);
5177
5178       // set image according to button state
8fa922 5179       if (obj && button.type=='image' && button[state]) {
c833ed 5180         button.status = state;
4e17e6 5181         obj.src = button[state];
8fa922 5182       }
4e17e6 5183       // set class name according to button state
d8cf6d 5184       else if (obj && button[state] !== undefined) {
c833ed 5185         button.status = state;
A 5186         obj.className = button[state];
8fa922 5187       }
4e17e6 5188       // disable/enable input buttons
8fa922 5189       if (obj && button.type=='input') {
4e17e6 5190         button.status = state;
T 5191         obj.disabled = !state;
5192       }
8fa922 5193     }
A 5194   };
4e17e6 5195
eb6842 5196   // display a specific alttext
T 5197   this.set_alttext = function(command, label)
8fa922 5198   {
A 5199     if (!this.buttons[command] || !this.buttons[command].length)
5200       return;
5201
5202     var button, obj, link;
5203     for (var n=0; n<this.buttons[command].length; n++) {
5204       button = this.buttons[command][n];
5205       obj = document.getElementById(button.id);
5206
5207       if (button.type=='image' && obj) {
5208         obj.setAttribute('alt', this.get_label(label));
5209         if ((link = obj.parentNode) && link.tagName.toLowerCase() == 'a')
5210           link.setAttribute('title', this.get_label(label));
eb6842 5211       }
8fa922 5212       else if (obj)
A 5213         obj.setAttribute('title', this.get_label(label));
5214     }
5215   };
4e17e6 5216
T 5217   // mouse over button
5218   this.button_over = function(command, id)
356a67 5219   {
8fa922 5220     var button, elm, a_buttons = this.buttons[command];
4e17e6 5221
8fa922 5222     if (!a_buttons || !a_buttons.length)
4da1d7 5223       return false;
4e17e6 5224
8fa922 5225     for (var n=0; n<a_buttons.length; n++) {
4e17e6 5226       button = a_buttons[n];
8fa922 5227       if (button.id == id && button.status == 'act') {
356a67 5228         elm = document.getElementById(button.id);
T 5229         if (elm && button.over) {
5230           if (button.type == 'image')
5231             elm.src = button.over;
5232           else
5233             elm.className = button.over;
4e17e6 5234         }
T 5235       }
356a67 5236     }
T 5237   };
4e17e6 5238
c8c1e0 5239   // mouse down on button
S 5240   this.button_sel = function(command, id)
356a67 5241   {
8fa922 5242     var button, elm, a_buttons = this.buttons[command];
c8c1e0 5243
8fa922 5244     if (!a_buttons || !a_buttons.length)
c8c1e0 5245       return;
S 5246
8fa922 5247     for (var n=0; n<a_buttons.length; n++) {
c8c1e0 5248       button = a_buttons[n];
8fa922 5249       if (button.id == id && button.status == 'act') {
356a67 5250         elm = document.getElementById(button.id);
T 5251         if (elm && button.sel) {
5252           if (button.type == 'image')
5253             elm.src = button.sel;
5254           else
5255             elm.className = button.sel;
c8c1e0 5256         }
e0896d 5257         this.buttons_sel[id] = command;
c8c1e0 5258       }
356a67 5259     }
T 5260   };
4e17e6 5261
T 5262   // mouse out of button
5263   this.button_out = function(command, id)
356a67 5264   {
8fa922 5265     var button, elm, a_buttons = this.buttons[command];
4e17e6 5266
8fa922 5267     if (!a_buttons || !a_buttons.length)
4e17e6 5268       return;
T 5269
8fa922 5270     for (var n=0; n<a_buttons.length; n++) {
4e17e6 5271       button = a_buttons[n];
8fa922 5272       if (button.id == id && button.status == 'act') {
356a67 5273         elm = document.getElementById(button.id);
T 5274         if (elm && button.act) {
5275           if (button.type == 'image')
5276             elm.src = button.act;
5277           else
5278             elm.className = button.act;
4e17e6 5279         }
T 5280       }
356a67 5281     }
T 5282   };
4e17e6 5283
0501b6 5284
T 5285   this.focus_textfield = function(elem)
5286   {
5287     elem._hasfocus = true;
5288     var $elem = $(elem);
5289     if ($elem.hasClass('placeholder') || $elem.val() == elem._placeholder)
5290       $elem.val('').removeClass('placeholder').attr('spellcheck', true);
5291   };
5292
5293   this.blur_textfield = function(elem)
5294   {
5295     elem._hasfocus = false;
5296     var $elem = $(elem);
5297     if (elem._placeholder && (!$elem.val() || $elem.val() == elem._placeholder))
5298       $elem.addClass('placeholder').attr('spellcheck', false).val(elem._placeholder);
5299   };
7f5a84 5300
5eee00 5301   // write to the document/window title
T 5302   this.set_pagetitle = function(title)
5303   {
5304     if (title && document.title)
5305       document.title = title;
8fa922 5306   };
5eee00 5307
ad334a 5308   // display a system message, list of types in common.css (below #message definition)
7f5a84 5309   this.display_message = function(msg, type, timeout)
8fa922 5310   {
b716bd 5311     // pass command to parent window
27acfd 5312     if (this.is_framed())
7f5a84 5313       return parent.rcmail.display_message(msg, type, timeout);
b716bd 5314
ad334a 5315     if (!this.gui_objects.message) {
A 5316       // save message in order to display after page loaded
5317       if (type != 'loading')
7f5a84 5318         this.pending_message = new Array(msg, type, timeout);
4e17e6 5319       return false;
ad334a 5320     }
f9c107 5321
ad334a 5322     type = type ? type : 'notice';
8fa922 5323
b37e69 5324     var ref = this,
7f5a84 5325       key = String(msg).replace(this.identifier_expr, '_'),
b37e69 5326       date = new Date(),
7f5a84 5327       id = type + date.getTime();
A 5328
5329     if (!timeout)
ef292e 5330       timeout = this.message_time * (type == 'error' || type == 'warning' ? 2 : 1);
7f5a84 5331
ef292e 5332     if (type == 'loading') {
T 5333       key = 'loading';
5334       timeout = this.env.request_timeout * 1000;
5335       if (!msg)
5336         msg = this.get_label('loading');
5337     }
29b397 5338
b37e69 5339     // The same message is already displayed
ef292e 5340     if (this.messages[key]) {
57e38f 5341       // replace label
ef292e 5342       if (this.messages[key].obj)
T 5343         this.messages[key].obj.html(msg);
57e38f 5344       // store label in stack
A 5345       if (type == 'loading') {
5346         this.messages[key].labels.push({'id': id, 'msg': msg});
5347       }
5348       // add element and set timeout
ef292e 5349       this.messages[key].elements.push(id);
57e38f 5350       window.setTimeout(function() { ref.hide_message(id, type == 'loading'); }, timeout);
b37e69 5351       return id;
ad334a 5352     }
8fa922 5353
57e38f 5354     // create DOM object and display it
A 5355     var obj = $('<div>').addClass(type).html(msg).data('key', key),
5356       cont = $(this.gui_objects.message).append(obj).show();
5357
5358     this.messages[key] = {'obj': obj, 'elements': [id]};
8fa922 5359
ad334a 5360     if (type == 'loading') {
57e38f 5361       this.messages[key].labels = [{'id': id, 'msg': msg}];
ad334a 5362     }
A 5363     else {
57e38f 5364       obj.click(function() { return ref.hide_message(obj); });
70cfb4 5365     }
57e38f 5366
fcc7f8 5367     if (timeout > 0)
T 5368       window.setTimeout(function() { ref.hide_message(id, type == 'loading'); }, timeout);
57e38f 5369     return id;
8fa922 5370   };
4e17e6 5371
ad334a 5372   // make a message to disapear
A 5373   this.hide_message = function(obj, fade)
554d79 5374   {
ad334a 5375     // pass command to parent window
27acfd 5376     if (this.is_framed())
ad334a 5377       return parent.rcmail.hide_message(obj, fade);
A 5378
57e38f 5379     var k, n, i, msg, m = this.messages;
A 5380
5381     // Hide message by object, don't use for 'loading'!
d8cf6d 5382     if (typeof obj === 'object') {
ad334a 5383       $(obj)[fade?'fadeOut':'hide']();
57e38f 5384       msg = $(obj).data('key');
b37e69 5385       if (this.messages[msg])
T 5386         delete this.messages[msg];
ad334a 5387     }
57e38f 5388     // Hide message by id
ad334a 5389     else {
ee72e4 5390       for (k in m) {
A 5391         for (n in m[k].elements) {
5392           if (m[k] && m[k].elements[n] == obj) {
5393             m[k].elements.splice(n, 1);
57e38f 5394             // hide DOM element if last instance is removed
ee72e4 5395             if (!m[k].elements.length) {
A 5396               m[k].obj[fade?'fadeOut':'hide']();
5397               delete m[k];
ad334a 5398             }
57e38f 5399             // set pending action label for 'loading' message
A 5400             else if (k == 'loading') {
5401               for (i in m[k].labels) {
5402                 if (m[k].labels[i].id == obj) {
5403                   delete m[k].labels[i];
5404                 }
5405                 else {
5406                   msg = m[k].labels[i].msg;
5407                 }
5408                 m[k].obj.html(msg);
5409               }
5410             }
ad334a 5411           }
A 5412         }
5413       }
5414     }
554d79 5415   };
A 5416
4e17e6 5417   // mark a mailbox as selected and set environment variable
f8e48d 5418   this.select_folder = function(name, prefix)
f11541 5419   {
8fa922 5420     if (this.gui_objects.folderlist) {
f11541 5421       var current_li, target_li;
8fa922 5422
f8e48d 5423       if ((current_li = $('li.selected', this.gui_objects.folderlist))) {
A 5424         current_li.removeClass('selected').addClass('unfocused');
4e17e6 5425       }
a61bbb 5426       if ((target_li = this.get_folder_li(name, prefix))) {
cc97ea 5427         $(target_li).removeClass('unfocused').addClass('selected');
fa4cd2 5428       }
8fa922 5429
99d866 5430       // trigger event hook
f8e48d 5431       this.triggerEvent('selectfolder', { folder:name, prefix:prefix });
f11541 5432     }
T 5433   };
5434
5435   // helper method to find a folder list item
a61bbb 5436   this.get_folder_li = function(name, prefix)
f11541 5437   {
a61bbb 5438     if (!prefix)
T 5439       prefix = 'rcmli';
8fa922 5440
A 5441     if (this.gui_objects.folderlist) {
8ca0c7 5442       name = String(name).replace(this.identifier_expr, '_');
a61bbb 5443       return document.getElementById(prefix+name);
f11541 5444     }
T 5445
5446     return null;
5447   };
24053e 5448
f52c93 5449   // for reordering column array (Konqueror workaround)
T 5450   // and for setting some message list global variables
5451   this.set_message_coltypes = function(coltypes, repl)
c3eab2 5452   {
5b67d3 5453     var list = this.message_list,
A 5454       thead = list ? list.list.tHead : null,
5455       cell, col, n, len, th, tr;
8fa922 5456
5b67d3 5457     this.env.coltypes = coltypes;
f52c93 5458
T 5459     // replace old column headers
c3eab2 5460     if (thead) {
A 5461       if (repl) {
5b67d3 5462         th = document.createElement('thead');
A 5463         tr = document.createElement('tr');
5464
c3eab2 5465         for (c=0, len=repl.length; c < len; c++) {
f52c93 5466           cell = document.createElement('td');
c3eab2 5467           cell.innerHTML = repl[c].html;
A 5468           if (repl[c].id) cell.id = repl[c].id;
5469           if (repl[c].className) cell.className = repl[c].className;
5470           tr.appendChild(cell);
f52c93 5471         }
c3eab2 5472         th.appendChild(tr);
A 5473         thead.parentNode.replaceChild(th, thead);
accdd0 5474         thead = th;
c3eab2 5475       }
A 5476
5477       for (n=0, len=this.env.coltypes.length; n<len; n++) {
5478         col = this.env.coltypes[n];
5479         if ((cell = thead.rows[0].cells[n]) && (col=='from' || col=='to')) {
5480           cell.id = 'rcm'+col;
5481           // if we have links for sorting, it's a bit more complicated...
5482           if (cell.firstChild && cell.firstChild.tagName.toLowerCase()=='a') {
5483             cell = cell.firstChild;
5484             cell.onclick = function(){ return rcmail.command('sort', this.__col, this); };
5485             cell.__col = col;
5486           }
5487           cell.innerHTML = this.get_label(col);
5488         }
f52c93 5489       }
T 5490     }
095d05 5491
f52c93 5492     this.env.subject_col = null;
T 5493     this.env.flagged_col = null;
98f2c9 5494     this.env.status_col = null;
c4b819 5495
c3eab2 5496     if ((n = $.inArray('subject', this.env.coltypes)) >= 0) {
9f07d1 5497       this.env.subject_col = n;
5b67d3 5498       if (list)
A 5499         list.subject_col = n;
8fa922 5500     }
c3eab2 5501     if ((n = $.inArray('flag', this.env.coltypes)) >= 0)
9f07d1 5502       this.env.flagged_col = n;
98f2c9 5503     if ((n = $.inArray('status', this.env.coltypes)) >= 0)
9f07d1 5504       this.env.status_col = n;
b62c48 5505
5b67d3 5506     if (list)
A 5507       list.init_header();
f52c93 5508   };
4e17e6 5509
T 5510   // replace content of row count display
bba252 5511   this.set_rowcount = function(text, mbox)
8fa922 5512   {
bba252 5513     // #1487752
A 5514     if (mbox && mbox != this.env.mailbox)
5515       return false;
5516
cc97ea 5517     $(this.gui_objects.countdisplay).html(text);
4e17e6 5518
T 5519     // update page navigation buttons
5520     this.set_page_buttons();
8fa922 5521   };
6d2714 5522
ac5d15 5523   // replace content of mailboxname display
T 5524   this.set_mailboxname = function(content)
8fa922 5525   {
ac5d15 5526     if (this.gui_objects.mailboxname && content)
T 5527       this.gui_objects.mailboxname.innerHTML = content;
8fa922 5528   };
ac5d15 5529
58e360 5530   // replace content of quota display
6d2714 5531   this.set_quota = function(content)
8fa922 5532   {
7415c0 5533     if (content && this.gui_objects.quotadisplay) {
d8cf6d 5534       if (typeof content === 'object' && content.type == 'image')
7415c0 5535         this.percent_indicator(this.gui_objects.quotadisplay, content);
A 5536       else
5537         $(this.gui_objects.quotadisplay).html(content);
8fa922 5538     }
A 5539   };
6b47de 5540
4e17e6 5541   // update the mailboxlist
15a9d1 5542   this.set_unread_count = function(mbox, count, set_title)
8fa922 5543   {
4e17e6 5544     if (!this.gui_objects.mailboxlist)
T 5545       return false;
25d8ba 5546
85360d 5547     this.env.unread_counts[mbox] = count;
T 5548     this.set_unread_count_display(mbox, set_title);
8fa922 5549   };
7f9d71 5550
S 5551   // update the mailbox count display
5552   this.set_unread_count_display = function(mbox, set_title)
8fa922 5553   {
de06fc 5554     var reg, link, text_obj, item, mycount, childcount, div;
dbd069 5555
8fa922 5556     if (item = this.get_folder_li(mbox)) {
07d367 5557       mycount = this.env.unread_counts[mbox] ? this.env.unread_counts[mbox] : 0;
de06fc 5558       link = $(item).children('a').eq(0);
T 5559       text_obj = link.children('span.unreadcount');
5560       if (!text_obj.length && mycount)
5561         text_obj = $('<span>').addClass('unreadcount').appendTo(link);
15a9d1 5562       reg = /\s+\([0-9]+\)$/i;
7f9d71 5563
835a0c 5564       childcount = 0;
S 5565       if ((div = item.getElementsByTagName('div')[0]) &&
8fa922 5566           div.className.match(/collapsed/)) {
7f9d71 5567         // add children's counters
07d367 5568         for (var k in this.env.unread_counts) 
cc97ea 5569           if (k.indexOf(mbox + this.env.delimiter) == 0)
85360d 5570             childcount += this.env.unread_counts[k];
8fa922 5571       }
4e17e6 5572
de06fc 5573       if (mycount && text_obj.length)
T 5574         text_obj.html(' ('+mycount+')');
5575       else if (text_obj.length)
5576         text_obj.remove();
25d8ba 5577
7f9d71 5578       // set parent's display
07d367 5579       reg = new RegExp(RegExp.escape(this.env.delimiter) + '[^' + RegExp.escape(this.env.delimiter) + ']+$');
7f9d71 5580       if (mbox.match(reg))
S 5581         this.set_unread_count_display(mbox.replace(reg, ''), false);
5582
15a9d1 5583       // set the right classes
cc97ea 5584       if ((mycount+childcount)>0)
T 5585         $(item).addClass('unread');
5586       else
5587         $(item).removeClass('unread');
8fa922 5588     }
15a9d1 5589
T 5590     // set unread count to window title
01c86f 5591     reg = /^\([0-9]+\)\s+/i;
8fa922 5592     if (set_title && document.title) {
dbd069 5593       var new_title = '',
A 5594         doc_title = String(document.title);
15a9d1 5595
85360d 5596       if (mycount && doc_title.match(reg))
T 5597         new_title = doc_title.replace(reg, '('+mycount+') ');
5598       else if (mycount)
5599         new_title = '('+mycount+') '+doc_title;
15a9d1 5600       else
5eee00 5601         new_title = doc_title.replace(reg, '');
8fa922 5602
5eee00 5603       this.set_pagetitle(new_title);
8fa922 5604     }
A 5605   };
4e17e6 5606
712b30 5607   this.toggle_prefer_html = function(checkbox)
8fa922 5608   {
dbd069 5609     var elem;
A 5610     if (elem = document.getElementById('rcmfd_addrbook_show_images'))
5611       elem.disabled = !checkbox.checked;
8fa922 5612   };
712b30 5613
bc4960 5614   this.toggle_preview_pane = function(checkbox)
8fa922 5615   {
dbd069 5616     var elem;
A 5617     if (elem = document.getElementById('rcmfd_preview_pane_mark_read'))
5618       elem.disabled = !checkbox.checked;
8fa922 5619   };
bc4960 5620
e5686f 5621   // display fetched raw headers
A 5622   this.set_headers = function(content)
cc97ea 5623   {
ad334a 5624     if (this.gui_objects.all_headers_row && this.gui_objects.all_headers_box && content)
cc97ea 5625       $(this.gui_objects.all_headers_box).html(content).show();
T 5626   };
a980cb 5627
e5686f 5628   // display all-headers row and fetch raw message headers
A 5629   this.load_headers = function(elem)
8fa922 5630   {
e5686f 5631     if (!this.gui_objects.all_headers_row || !this.gui_objects.all_headers_box || !this.env.uid)
A 5632       return;
8fa922 5633
cc97ea 5634     $(elem).removeClass('show-headers').addClass('hide-headers');
T 5635     $(this.gui_objects.all_headers_row).show();
8fa922 5636     elem.onclick = function() { rcmail.hide_headers(elem); };
e5686f 5637
A 5638     // fetch headers only once
8fa922 5639     if (!this.gui_objects.all_headers_box.innerHTML) {
ad334a 5640       var lock = this.display_message(this.get_label('loading'), 'loading');
A 5641       this.http_post('headers', '_uid='+this.env.uid, lock);
e5686f 5642     }
8fa922 5643   };
e5686f 5644
A 5645   // hide all-headers row
5646   this.hide_headers = function(elem)
8fa922 5647   {
e5686f 5648     if (!this.gui_objects.all_headers_row || !this.gui_objects.all_headers_box)
A 5649       return;
5650
cc97ea 5651     $(elem).removeClass('hide-headers').addClass('show-headers');
T 5652     $(this.gui_objects.all_headers_row).hide();
8fa922 5653     elem.onclick = function() { rcmail.load_headers(elem); };
A 5654   };
e5686f 5655
7415c0 5656   // percent (quota) indicator
A 5657   this.percent_indicator = function(obj, data)
8fa922 5658   {
7415c0 5659     if (!data || !obj)
A 5660       return false;
5661
dbd069 5662     var limit_high = 80,
A 5663       limit_mid  = 55,
5664       width = data.width ? data.width : this.env.indicator_width ? this.env.indicator_width : 100,
5665       height = data.height ? data.height : this.env.indicator_height ? this.env.indicator_height : 14,
5666       quota = data.percent ? Math.abs(parseInt(data.percent)) : 0,
5667       quota_width = parseInt(quota / 100 * width),
5668       pos = $(obj).position();
7415c0 5669
b7b7cf 5670     // workarounds for Opera and Webkit bugs
5c0240 5671     pos.top = Math.max(0, pos.top);
b7b7cf 5672     pos.left = Math.max(0, pos.left);
5c0240 5673
7415c0 5674     this.env.indicator_width = width;
A 5675     this.env.indicator_height = height;
8fa922 5676
7415c0 5677     // overlimit
A 5678     if (quota_width > width) {
5679       quota_width = width;
5680       quota = 100; 
8fa922 5681     }
A 5682
a2e817 5683     if (data.title)
A 5684       data.title = this.get_label('quota') + ': ' +  data.title;
5685
7415c0 5686     // main div
A 5687     var main = $('<div>');
5688     main.css({position: 'absolute', top: pos.top, left: pos.left,
8fa922 5689         width: width + 'px', height: height + 'px', zIndex: 100, lineHeight: height + 'px'})
A 5690       .attr('title', data.title).addClass('quota_text').html(quota + '%');
7415c0 5691     // used bar
A 5692     var bar1 = $('<div>');
5693     bar1.css({position: 'absolute', top: pos.top + 1, left: pos.left + 1,
8fa922 5694         width: quota_width + 'px', height: height + 'px', zIndex: 99});
7415c0 5695     // background
A 5696     var bar2 = $('<div>');
5697     bar2.css({position: 'absolute', top: pos.top + 1, left: pos.left + 1,
8fa922 5698         width: width + 'px', height: height + 'px', zIndex: 98})
A 5699       .addClass('quota_bg');
7415c0 5700
A 5701     if (quota >= limit_high) {
5702       main.addClass(' quota_text_high');
5703       bar1.addClass('quota_high');
8fa922 5704     }
7415c0 5705     else if(quota >= limit_mid) {
A 5706       main.addClass(' quota_text_mid');
5707       bar1.addClass('quota_mid');
8fa922 5708     }
7415c0 5709     else {
a1621c 5710       main.addClass(' quota_text_low');
7415c0 5711       bar1.addClass('quota_low');
8fa922 5712     }
7415c0 5713
A 5714     // replace quota image
677e1f 5715     $(obj).html('').append(bar1).append(bar2).append(main);
a2e817 5716     // update #quotaimg title
A 5717     $('#quotaimg').attr('title', data.title);
8fa922 5718   };
4e17e6 5719
T 5720   /********************************************************/
3bd94b 5721   /*********  html to text conversion functions   *********/
A 5722   /********************************************************/
5723
5724   this.html2plain = function(htmlText, id)
8fa922 5725   {
dbd069 5726     var rcmail = this,
ad334a 5727       url = '?_task=utils&_action=html2text',
A 5728       lock = this.set_busy(true, 'converting');
3bd94b 5729
b0eb95 5730     this.log('HTTP POST: ' + url);
3bd94b 5731
cc97ea 5732     $.ajax({ type: 'POST', url: url, data: htmlText, contentType: 'application/octet-stream',
ad334a 5733       error: function(o, status, err) { rcmail.http_error(o, status, err, lock); },
b0eb95 5734       success: function(data) { rcmail.set_busy(false, null, lock); $(document.getElementById(id)).val(data); rcmail.log(data); }
8fa922 5735     });
A 5736   };
3bd94b 5737
962085 5738   this.plain2html = function(plainText, id)
8fa922 5739   {
ad334a 5740     var lock = this.set_busy(true, 'converting');
fa9edc 5741     $(document.getElementById(id)).val(plainText ? '<pre>'+plainText+'</pre>' : '');
ad334a 5742     this.set_busy(false, null, lock);
8fa922 5743   };
962085 5744
3bd94b 5745
A 5746   /********************************************************/
4e17e6 5747   /*********        remote request methods        *********/
T 5748   /********************************************************/
0213f8 5749
0501b6 5750   // compose a valid url with the given parameters
T 5751   this.url = function(action, query)
5752   {
d8cf6d 5753     var querystring = typeof query === 'string' ? '&' + query : '';
A 5754
5755     if (typeof action !== 'string')
0501b6 5756       query = action;
d8cf6d 5757     else if (!query || typeof query !== 'object')
0501b6 5758       query = {};
d8cf6d 5759
0501b6 5760     if (action)
T 5761       query._action = action;
5762     else
5763       query._action = this.env.action;
d8cf6d 5764
0501b6 5765     var base = this.env.comm_path;
T 5766
5767     // overwrite task name
50077d 5768     if (query._action.match(/([a-z]+)\/([a-z0-9-_.]+)/)) {
0501b6 5769       query._action = RegExp.$2;
T 5770       base = base.replace(/\_task=[a-z]+/, '_task='+RegExp.$1);
5771     }
d8cf6d 5772
0501b6 5773     // remove undefined values
T 5774     var param = {};
5775     for (var k in query) {
d8cf6d 5776       if (query[k] !== undefined && query[k] !== null)
0501b6 5777         param[k] = query[k];
T 5778     }
d8cf6d 5779
0501b6 5780     return base + '&' + $.param(param) + querystring;
T 5781   };
6b47de 5782
4b9efb 5783   this.redirect = function(url, lock)
8fa922 5784   {
719a25 5785     if (lock || lock === null)
4b9efb 5786       this.set_busy(true);
S 5787
d7167e 5788     if (this.is_framed())
a41dcf 5789       parent.rcmail.redirect(url, lock);
c5c3ae 5790     else
d7167e 5791       this.location_href(url, window);
8fa922 5792   };
6b47de 5793
T 5794   this.goto_url = function(action, query, lock)
8fa922 5795   {
0501b6 5796     this.redirect(this.url(action, query));
8fa922 5797   };
4e17e6 5798
dc0be3 5799   this.location_href = function(url, target, frame)
d7167e 5800   {
dc0be3 5801     if (frame)
A 5802       this.lock_frame();
5803
d7167e 5804     // simulate real link click to force IE to send referer header
T 5805     if (bw.ie && target == window)
5806       $('<a>').attr('href', url).appendTo(document.body).get(0).click();
5807     else
5808       target.location.href = url;
5809   };
5810
ecf759 5811   // send a http request to the server
6465a9 5812   this.http_request = function(action, query, lock)
cc97ea 5813   {
0501b6 5814     var url = this.url(action, query);
614c64 5815
7ceabc 5816     // trigger plugin hook
6465a9 5817     var result = this.triggerEvent('request'+action, query);
614c64 5818
d8cf6d 5819     if (result !== undefined) {
7ceabc 5820       // abort if one the handlers returned false
A 5821       if (result === false)
5822         return false;
5823       else
6465a9 5824         query = result;
7ceabc 5825     }
8fa922 5826
0501b6 5827     url += '&_remote=1';
56f41a 5828
cc97ea 5829     // send request
b0eb95 5830     this.log('HTTP GET: ' + url);
0213f8 5831
A 5832     return $.ajax({
ad334a 5833       type: 'GET', url: url, data: { _unlock:(lock?lock:0) }, dataType: 'json',
A 5834       success: function(data){ ref.http_response(data); },
5835       error: function(o, status, err) { rcmail.http_error(o, status, err, lock); }
5836     });
cc97ea 5837   };
T 5838
5839   // send a http POST request to the server
5840   this.http_post = function(action, postdata, lock)
5841   {
0501b6 5842     var url = this.url(action);
8fa922 5843
d8cf6d 5844     if (postdata && typeof postdata === 'object') {
cc97ea 5845       postdata._remote = 1;
ad334a 5846       postdata._unlock = (lock ? lock : 0);
cc97ea 5847     }
T 5848     else
ad334a 5849       postdata += (postdata ? '&' : '') + '_remote=1' + (lock ? '&_unlock='+lock : '');
4e17e6 5850
7ceabc 5851     // trigger plugin hook
A 5852     var result = this.triggerEvent('request'+action, postdata);
d8cf6d 5853     if (result !== undefined) {
7ceabc 5854       // abort if one the handlers returned false
A 5855       if (result === false)
5856         return false;
5857       else
5858         postdata = result;
5859     }
5860
4e17e6 5861     // send request
b0eb95 5862     this.log('HTTP POST: ' + url);
0213f8 5863
A 5864     return $.ajax({
ad334a 5865       type: 'POST', url: url, data: postdata, dataType: 'json',
A 5866       success: function(data){ ref.http_response(data); },
5867       error: function(o, status, err) { rcmail.http_error(o, status, err, lock); }
5868     });
cc97ea 5869   };
4e17e6 5870
d96151 5871   // aborts ajax request
A 5872   this.abort_request = function(r)
5873   {
5874     if (r.request)
5875       r.request.abort();
5876     if (r.lock)
241450 5877       this.set_busy(false, null, r.lock);
d96151 5878   };
A 5879
ecf759 5880   // handle HTTP response
cc97ea 5881   this.http_response = function(response)
T 5882   {
ad334a 5883     if (!response)
A 5884       return;
5885
cc97ea 5886     if (response.unlock)
e5686f 5887       this.set_busy(false);
4e17e6 5888
2bb1f6 5889     this.triggerEvent('responsebefore', {response: response});
A 5890     this.triggerEvent('responsebefore'+response.action, {response: response});
5891
cc97ea 5892     // set env vars
T 5893     if (response.env)
5894       this.set_env(response.env);
5895
5896     // we have labels to add
d8cf6d 5897     if (typeof response.texts === 'object') {
cc97ea 5898       for (var name in response.texts)
d8cf6d 5899         if (typeof response.texts[name] === 'string')
cc97ea 5900           this.add_label(name, response.texts[name]);
T 5901     }
4e17e6 5902
ecf759 5903     // if we get javascript code from server -> execute it
cc97ea 5904     if (response.exec) {
b0eb95 5905       this.log(response.exec);
cc97ea 5906       eval(response.exec);
0e99d3 5907     }
f52c93 5908
50067d 5909     // execute callback functions of plugins
A 5910     if (response.callbacks && response.callbacks.length) {
5911       for (var i=0; i < response.callbacks.length; i++)
5912         this.triggerEvent(response.callbacks[i][0], response.callbacks[i][1]);
14259c 5913     }
50067d 5914
ecf759 5915     // process the response data according to the sent action
cc97ea 5916     switch (response.action) {
ecf759 5917       case 'delete':
0dbac3 5918         if (this.task == 'addressbook') {
ecf295 5919           var sid, uid = this.contact_list.get_selection(), writable = false;
A 5920
5921           if (uid && this.contact_list.rows[uid]) {
5922             // search results, get source ID from record ID
5923             if (this.env.source == '') {
5924               sid = String(uid).replace(/^[^-]+-/, '');
5925               writable = sid && this.env.address_sources[sid] && !this.env.address_sources[sid].readonly;
5926             }
5927             else {
5928               writable = !this.env.address_sources[this.env.source].readonly;
5929             }
5930           }
0dbac3 5931           this.enable_command('compose', (uid && this.contact_list.rows[uid]));
ecf295 5932           this.enable_command('delete', 'edit', writable);
0dbac3 5933           this.enable_command('export', (this.contact_list && this.contact_list.rowcount > 0));
T 5934         }
8fa922 5935
ecf759 5936       case 'moveto':
dc2fc0 5937         if (this.env.action == 'show') {
5e9a56 5938           // re-enable commands on move/delete error
14259c 5939           this.enable_command(this.env.message_commands, true);
e25a35 5940           if (!this.env.list_post)
A 5941             this.enable_command('reply-list', false);
dbd069 5942         }
13e155 5943         else if (this.task == 'addressbook') {
A 5944           this.triggerEvent('listupdate', { folder:this.env.source, rowcount:this.contact_list.rowcount });
5945         }
8fa922 5946
2eb032 5947       case 'purge':
cc97ea 5948       case 'expunge':
13e155 5949         if (this.task == 'mail') {
A 5950           if (!this.env.messagecount) {
5951             // clear preview pane content
5952             if (this.env.contentframe)
5953               this.show_contentframe(false);
5954             // disable commands useless when mailbox is empty
5955             this.enable_command(this.env.message_commands, 'purge', 'expunge',
5956               'select-all', 'select-none', 'sort', 'expand-all', 'expand-unread', 'collapse-all', false);
5957           }
172e33 5958           if (this.message_list)
A 5959             this.triggerEvent('listupdate', { folder:this.env.mailbox, rowcount:this.message_list.rowcount });
0dbac3 5960         }
T 5961         break;
fdccdb 5962
d41d67 5963       case 'check-recent':
fdccdb 5964       case 'getunread':
f52c93 5965       case 'search':
db0408 5966         this.env.qsearch = null;
0dbac3 5967       case 'list':
T 5968         if (this.task == 'mail') {
5969           this.enable_command('show', 'expunge', 'select-all', 'select-none', 'sort', (this.env.messagecount > 0));
5970           this.enable_command('purge', this.purge_mailbox_test());
f52c93 5971           this.enable_command('expand-all', 'expand-unread', 'collapse-all', this.env.threading && this.env.messagecount);
T 5972
c833ed 5973           if (response.action == 'list' || response.action == 'search') {
A 5974             this.msglist_select(this.message_list);
99d866 5975             this.triggerEvent('listupdate', { folder:this.env.mailbox, rowcount:this.message_list.rowcount });
c833ed 5976           }
0dbac3 5977         }
99d866 5978         else if (this.task == 'addressbook') {
0dbac3 5979           this.enable_command('export', (this.contact_list && this.contact_list.rowcount > 0));
8fa922 5980
c833ed 5981           if (response.action == 'list' || response.action == 'search') {
f8e48d 5982             this.enable_command('search-create', this.env.source == '');
A 5983             this.enable_command('search-delete', this.env.search_id);
62811c 5984             this.update_group_commands();
99d866 5985             this.triggerEvent('listupdate', { folder:this.env.source, rowcount:this.contact_list.rowcount });
a61bbb 5986           }
99d866 5987         }
0dbac3 5988         break;
cc97ea 5989     }
2bb1f6 5990
ad334a 5991     if (response.unlock)
A 5992       this.hide_message(response.unlock);
5993
2bb1f6 5994     this.triggerEvent('responseafter', {response: response});
A 5995     this.triggerEvent('responseafter'+response.action, {response: response});
cc97ea 5996   };
ecf759 5997
T 5998   // handle HTTP request errors
ad334a 5999   this.http_error = function(request, status, err, lock)
8fa922 6000   {
9ff9f5 6001     var errmsg = request.statusText;
ecf759 6002
ad334a 6003     this.set_busy(false, null, lock);
9ff9f5 6004     request.abort();
8fa922 6005
7fbd94 6006     if (request.status && errmsg)
74d421 6007       this.display_message(this.get_label('servererror') + ' (' + errmsg + ')', 'error');
8fa922 6008   };
ecf759 6009
0501b6 6010   // post the given form to a hidden iframe
T 6011   this.async_upload_form = function(form, action, onload)
6012   {
b649c4 6013     var ts = new Date().getTime(),
A 6014       frame_name = 'rcmupload'+ts;
0501b6 6015
4171c5 6016     // upload progress support
A 6017     if (this.env.upload_progress_name) {
6018       var fname = this.env.upload_progress_name,
6019         field = $('input[name='+fname+']', form);
6020
6021       if (!field.length) {
6022         field = $('<input>').attr({type: 'hidden', name: fname});
65b61c 6023         field.prependTo(form);
4171c5 6024       }
A 6025
6026       field.val(ts);
6027     }
6028
0501b6 6029     // have to do it this way for IE
T 6030     // otherwise the form will be posted to a new window
6031     if (document.all) {
6032       var html = '<iframe name="'+frame_name+'" src="program/blank.gif" style="width:0;height:0;visibility:hidden;"></iframe>';
6033       document.body.insertAdjacentHTML('BeforeEnd', html);
6034     }
6035     else { // for standards-compilant browsers
6036       var frame = document.createElement('iframe');
6037       frame.name = frame_name;
6038       frame.style.border = 'none';
6039       frame.style.width = 0;
6040       frame.style.height = 0;
6041       frame.style.visibility = 'hidden';
6042       document.body.appendChild(frame);
6043     }
6044
6045     // handle upload errors, parsing iframe content in onload
6046     $(frame_name).bind('load', {ts:ts}, onload);
6047
c269b4 6048     $(form).attr({
A 6049         target: frame_name,
6050         action: this.url(action, { _id:this.env.compose_id||'', _uploadid:ts }),
6051         method: 'POST'})
6052       .attr(form.encoding ? 'encoding' : 'enctype', 'multipart/form-data')
6053       .submit();
b649c4 6054
A 6055     return frame_name;
0501b6 6056   };
ecf295 6057
488074 6058   // starts interval for keep-alive/check-recent signal
f52c93 6059   this.start_keepalive = function()
8fa922 6060   {
488074 6061     if (this._int)
A 6062       clearInterval(this._int);
6063
61248f 6064     if (this.env.keep_alive && !this.env.framed && this.task == 'mail' && this.gui_objects.mailboxlist)
f52c93 6065       this._int = setInterval(function(){ ref.check_for_recent(false); }, this.env.keep_alive * 1000);
ec045b 6066     else if (this.env.keep_alive && !this.env.framed && this.task != 'login' && this.env.action != 'print')
93a35c 6067       this._int = setInterval(function(){ ref.keep_alive(); }, this.env.keep_alive * 1000);
A 6068   };
6069
6070   // sends keep-alive signal
6071   this.keep_alive = function()
6072   {
6073     if (!this.busy)
6074       this.http_request('keep-alive');
488074 6075   };
A 6076
6077   // sends request to check for recent messages
5e9a56 6078   this.check_for_recent = function(refresh)
8fa922 6079   {
aade7b 6080     if (this.busy)
T 6081       return;
6082
c14fa8 6083     var lock, addurl = '_mbox=' + urlencode(this.env.mailbox);
2e1809 6084
5e9a56 6085     if (refresh) {
ad334a 6086       lock = this.set_busy(true, 'checkingmail');
5e9a56 6087       addurl += '&_refresh=1';
488074 6088       // reset check-recent interval
A 6089       this.start_keepalive();
5e9a56 6090     }
T 6091
2e1809 6092     if (this.gui_objects.messagelist)
A 6093       addurl += '&_list=1';
6094     if (this.gui_objects.quotadisplay)
6095       addurl += '&_quota=1';
6096     if (this.env.search_request)
6097       addurl += '&_search=' + this.env.search_request;
6098
ad334a 6099     this.http_request('check-recent', addurl, lock);
8fa922 6100   };
4e17e6 6101
T 6102
6103   /********************************************************/
6104   /*********            helper methods            *********/
6105   /********************************************************/
8fa922 6106
4e17e6 6107   // check if we're in show mode or if we have a unique selection
T 6108   // and return the message uid
6109   this.get_single_uid = function()
8fa922 6110   {
6b47de 6111     return this.env.uid ? this.env.uid : (this.message_list ? this.message_list.get_single_selection() : null);
8fa922 6112   };
4e17e6 6113
T 6114   // same as above but for contacts
6115   this.get_single_cid = function()
8fa922 6116   {
6b47de 6117     return this.env.cid ? this.env.cid : (this.contact_list ? this.contact_list.get_single_selection() : null);
8fa922 6118   };
4e17e6 6119
8fa922 6120   // gets cursor position
4e17e6 6121   this.get_caret_pos = function(obj)
8fa922 6122   {
d8cf6d 6123     if (obj.selectionEnd !== undefined)
4e17e6 6124       return obj.selectionEnd;
8fa922 6125     else if (document.selection && document.selection.createRange) {
4e17e6 6126       var range = document.selection.createRange();
T 6127       if (range.parentElement()!=obj)
6128         return 0;
6129
6130       var gm = range.duplicate();
8fa922 6131       if (obj.tagName == 'TEXTAREA')
4e17e6 6132         gm.moveToElementText(obj);
T 6133       else
6134         gm.expand('textedit');
8fa922 6135
4e17e6 6136       gm.setEndPoint('EndToStart', range);
T 6137       var p = gm.text.length;
6138
6139       return p<=obj.value.length ? p : -1;
8fa922 6140     }
4e17e6 6141     else
T 6142       return obj.value.length;
8fa922 6143   };
4e17e6 6144
8fa922 6145   // moves cursor to specified position
40418d 6146   this.set_caret_pos = function(obj, pos)
8fa922 6147   {
40418d 6148     if (obj.setSelectionRange)
A 6149       obj.setSelectionRange(pos, pos);
8fa922 6150     else if (obj.createTextRange) {
4e17e6 6151       var range = obj.createTextRange();
T 6152       range.collapse(true);
40418d 6153       range.moveEnd('character', pos);
A 6154       range.moveStart('character', pos);
4e17e6 6155       range.select();
40418d 6156     }
8fa922 6157   };
4e17e6 6158
b0d46b 6159   // disable/enable all fields of a form
4e17e6 6160   this.lock_form = function(form, lock)
8fa922 6161   {
4e17e6 6162     if (!form || !form.elements)
T 6163       return;
8fa922 6164
b0d46b 6165     var n, len, elm;
A 6166
6167     if (lock)
6168       this.disabled_form_elements = [];
6169
6170     for (n=0, len=form.elements.length; n<len; n++) {
6171       elm = form.elements[n];
6172
6173       if (elm.type == 'hidden')
4e17e6 6174         continue;
b0d46b 6175       // remember which elem was disabled before lock
A 6176       if (lock && elm.disabled)
6177         this.disabled_form_elements.push(elm);
1b3ce7 6178       // check this.disabled_form_elements before inArray() as a workaround for FF5 bug
A 6179       // http://bugs.jquery.com/ticket/9873
070bc8 6180       else if (lock || (this.disabled_form_elements && $.inArray(elm, this.disabled_form_elements)<0))
b0d46b 6181         elm.disabled = lock;
8fa922 6182     }
A 6183   };
6184
cc97ea 6185 }  // end object rcube_webmail
4e17e6 6186
bc3745 6187
T 6188 // some static methods
6189 rcube_webmail.long_subject_title = function(elem, indent)
6190 {
6191   if (!elem.title) {
6192     var $elem = $(elem);
6193     if ($elem.width() + indent * 15 > $elem.parent().width())
6194       elem.title = $elem.html();
6195   }
6196 };
6197
065d70 6198 rcube_webmail.long_subject_title_ie = function(elem, indent)
A 6199 {
6200   if (!elem.title) {
6201     var $elem = $(elem),
eb616c 6202       txt = $.trim($elem.text()),
065d70 6203       tmp = $('<span>').text(txt)
A 6204         .css({'position': 'absolute', 'float': 'left', 'visibility': 'hidden',
6205           'font-size': $elem.css('font-size'), 'font-weight': $elem.css('font-weight')})
6206         .appendTo($('body')),
6207       w = tmp.width();
6208
6209     tmp.remove();
6210     if (w + indent * 15 > $elem.width())
6211       elem.title = txt;
6212   }
6213 };
6214
cc97ea 6215 // copy event engine prototype
T 6216 rcube_webmail.prototype.addEventListener = rcube_event_engine.prototype.addEventListener;
6217 rcube_webmail.prototype.removeEventListener = rcube_event_engine.prototype.removeEventListener;
6218 rcube_webmail.prototype.triggerEvent = rcube_event_engine.prototype.triggerEvent;
3940ba 6219