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