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