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