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