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