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