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