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