svncommit
2006-06-29 a894ba5029a09fb9d0453b5cf9c944ce313f8a48
commit | author | age
e0ed97 1 /*
4e17e6 2  +-----------------------------------------------------------------------+
T 3  | RoundCube Webmail Client Script                                       |
4  |                                                                       |
5  | This file is part of the RoundCube Webmail client                     |
6  | Copyright (C) 2005, RoundCube Dev, - Switzerland                      |
30233b 7  | Licensed under the GNU GPL                                            |
4e17e6 8  |                                                                       |
T 9  +-----------------------------------------------------------------------+
8c2e58 10  | Authors: Thomas Bruederli <roundcube@gmail.com>                       |
T 11  |          Charles McNulty <charles@charlesmcnulty.com>                 |
4e17e6 12  +-----------------------------------------------------------------------+
15a9d1 13  
T 14   $Id$
4e17e6 15 */
b11a00 16 // Constants
T 17 var CONTROL_KEY = 1;
18 var SHIFT_KEY = 2;
19 var CONTROL_SHIFT_KEY = 3;
4e17e6 20
T 21 var rcube_webmail_client;
22
23 function rcube_webmail()
24   {
25   this.env = new Object();
10a699 26   this.labels = new Object();
4e17e6 27   this.buttons = new Object();
T 28   this.gui_objects = new Object();
29   this.commands = new Object();
30   this.selection = new Array();
e8adc7 31   this.last_selected = 0;
1c5853 32   this.in_message_list = false;
4e17e6 33
T 34   // create public reference to myself
35   rcube_webmail_client = this;
36   this.ref = 'rcube_webmail_client';
37  
38   // webmail client settings
39   this.dblclick_time = 600;
40   this.message_time = 5000;
e66f5b 41   this.request_timeout = 180000;
ecf759 42   this.kepp_alive_interval = 60000;
4e17e6 43   this.mbox_expression = new RegExp('[^0-9a-z\-_]', 'gi');
T 44   this.env.blank_img = 'skins/default/images/blank.gif';
45   
46   // mimetypes supported by the browser (default settings)
47   this.mimetypes = new Array('text/plain', 'text/html', 'text/xml',
48                              'image/jpeg', 'image/gif', 'image/png',
49                              'application/x-javascript', 'application/pdf',
50                              'application/x-shockwave-flash');
51
52
53   // set environment variable
54   this.set_env = function(name, value)
55     {
56     //if (!this.busy)
57       this.env[name] = value;    
58     };
10a699 59
T 60
61   // add a localized label to the client environment
62   this.add_label = function(key, value)
63     {
64     this.labels[key] = value;
65     };
66
4e17e6 67
T 68   // add a button to the button list
69   this.register_button = function(command, id, type, act, sel, over)
70     {
71     if (!this.buttons[command])
72       this.buttons[command] = new Array();
73       
74     var button_prop = {id:id, type:type};
75     if (act) button_prop.act = act;
76     if (sel) button_prop.sel = sel;
77     if (over) button_prop.over = over;
78
79     this.buttons[command][this.buttons[command].length] = button_prop;    
80     };
81
82
83   // register a specific gui object
84   this.gui_object = function(name, id)
85     {
86     this.gui_objects[name] = id;
87     };
88
89
90   // initialize webmail client
91   this.init = function()
92     {
93     this.task = this.env.task;
94     
95     // check browser
a95e0e 96     if (!bw.dom || !bw.xmlhttp_test())
4e17e6 97       {
T 98       location.href = this.env.comm_path+'&_action=error&_code=0x199';
99       return;
100       }
101     
102     // find all registered gui objects
103     for (var n in this.gui_objects)
104       this.gui_objects[n] = rcube_find_object(this.gui_objects[n]);
105       
106     // tell parent window that this frame is loaded
107     if (this.env.framed && parent.rcmail && parent.rcmail.set_busy)
108       parent.rcmail.set_busy(false);
109
110     // enable general commands
111     this.enable_command('logout', 'mail', 'addressbook', 'settings', true);
112     
113     switch (this.task)
114       {
115       case 'mail':
d58c69 116         var msg_list_frame = this.gui_objects.mailcontframe;
4e17e6 117         var msg_list = this.gui_objects.messagelist;
T 118         if (msg_list)
119           {
d58c69 120           msg_list_frame.onmousedown = function(e){return rcube_webmail_client.click_on_list(e);};
4e17e6 121           this.init_messagelist(msg_list);
857a38 122           this.enable_command('toggle_status', true);
4e17e6 123           }
T 124
125         // enable mail commands
c8c1e0 126         this.enable_command('list', 'checkmail', 'compose', 'add-contact', 'search', 'reset-search', true);
4e17e6 127         
T 128         if (this.env.action=='show')
129           {
583f1c 130           this.enable_command('show', 'reply', 'reply-all', 'forward', 'moveto', 'delete', 'viewsource', 'print', 'load-attachment', true);
4e17e6 131           if (this.env.next_uid)
T 132             this.enable_command('nextmessage', true);
133           if (this.env.prev_uid)
134             this.enable_command('previousmessage', true);
135           }
136
137         if (this.env.action=='show' && this.env.blockedobjects)
138           {
139           if (this.gui_objects.remoteobjectsmsg)
140             this.gui_objects.remoteobjectsmsg.style.display = 'block';
141           this.enable_command('load-images', true);
142           }  
143
144         if (this.env.action=='compose')
ed5d29 145           {
a894ba 146           this.enable_command('add-attachment', 'send-attachment', 'remove-attachment', 'send', true);
ed5d29 147           if (this.env.spellcheck)
T 148             this.enable_command('spellcheck', true);
1966c5 149       if (this.env.drafts_mailbox)
S 150         this.enable_command('savedraft', true);
ed5d29 151           }
4e17e6 152           
T 153         if (this.env.messagecount)
15a9d1 154           this.enable_command('select-all', 'select-none', 'sort', 'expunge', true);
4e17e6 155
5e3512 156         if (this.env.messagecount && this.env.mailbox==this.env.trash_mailbox)
T 157           this.enable_command('purge', true);
158
4e17e6 159         this.set_page_buttons();
T 160
161         // focus this window
162         window.focus();
163
164         // init message compose form
165         if (this.env.action=='compose')
166           this.init_messageform();
167
168         // show printing dialog
169         if (this.env.action=='print')
170           window.print();
15a9d1 171           
T 172         // get unread count for each mailbox
173         if (this.gui_objects.mailboxlist)
174           this.http_request('getunread', '');
4e17e6 175
T 176         break;
177
178
179       case 'addressbook':
d1d2c4 180         var contacts_list      = this.gui_objects.contactslist;
S 181         var ldap_contacts_list = this.gui_objects.ldapcontactslist;
182
4e17e6 183         if (contacts_list)
T 184           this.init_contactslist(contacts_list);
185       
d1d2c4 186         if (ldap_contacts_list)
S 187           this.init_ldapsearchlist(ldap_contacts_list);
188
4e17e6 189         this.set_page_buttons();
T 190           
191         if (this.env.cid)
192           this.enable_command('show', 'edit', true);
193
194         if ((this.env.action=='add' || this.env.action=='edit') && this.gui_objects.editform)
195           this.enable_command('save', true);
196       
c0da98 197         this.enable_command('list', 'add', true);
S 198
199         this.enable_command('ldappublicsearch', this.env.ldappublicsearch);
200
4e17e6 201         break;
T 202
203
204       case 'settings':
205         this.enable_command('preferences', 'identities', 'save', 'folders', true);
206         
207         if (this.env.action=='identities' || this.env.action=='edit-identity' || this.env.action=='add-identity')
208           this.enable_command('edit', 'add', 'delete', true);
209
210         if (this.env.action=='edit-identity' || this.env.action=='add-identity')
211           this.enable_command('save', true);
212           
213         if (this.env.action=='folders')
c8c1e0 214           this.enable_command('subscribe', 'unsubscribe', 'create-folder', 'rename-folder', 'delete-folder', true);
4e17e6 215           
T 216         var identities_list = this.gui_objects.identitieslist;
217         if (identities_list)
218           this.init_identitieslist(identities_list);
219
220         break;
221
222       case 'login':
223         var input_user = rcube_find_object('_user');
224         var input_pass = rcube_find_object('_pass');
225         if (input_user && input_user.value=='')
226           input_user.focus();
227         else if (input_pass)
228           input_pass.focus();
229           
230         this.enable_command('login', true);
231         break;
232       
233       default:
234         break;
235       }
236
237
238     // enable basic commands
239     this.enable_command('logout', true);
240
241     // disable browser's contextmenus
8c2e58 242     // document.oncontextmenu = function(){ return false; }
4e17e6 243
d58c69 244     // load body click event
S 245     document.onmousedown = function(){ return rcube_webmail_client.reset_click(); };
356b6e 246     document.onkeydown   = function(e){ return rcube_webmail_client.key_pressed(e, msg_list_frame); };
d58c69 247
S 248     
4e17e6 249     // flag object as complete
T 250     this.loaded = true;
7902df 251           
4e17e6 252     // show message
T 253     if (this.pending_message)
254       this.display_message(this.pending_message[0], this.pending_message[1]);
ecf759 255       
15a9d1 256     // start interval for keep-alive/recent_check signal
3f9edb 257     if (this.kepp_alive_interval && this.task=='mail' && this.gui_objects.messagelist)
T 258       this.kepp_alive_int = setInterval(this.ref+'.check_for_recent()', this.kepp_alive_interval);
8c2e58 259     else if (this.task!='login')
3f9edb 260       this.kepp_alive_int = setInterval(this.ref+'.send_keep_alive()', this.kepp_alive_interval);
4e17e6 261     };
T 262
d58c69 263   // reset last clicked if user clicks on anything other than the message table
952860 264   this.reset_click = function() {
1c5853 265     this.in_message_list = false;
952860 266     for (var n=0; n<this.selection.length; n++) {
S 267       id = this.selection[n];
268       if (this.list_rows[id].obj) {
269         this.set_classname(this.list_rows[id].obj, 'selected', false);
270         this.set_classname(this.list_rows[id].obj, 'unfocused', true);
271       }
272     }
273   };
d58c69 274     
S 275   this.click_on_list = function(e)
276     {
277     if (!e)
278       e = window.event;
952860 279
S 280     for (var n=0; n<this.selection.length; n++) {
281       id = this.selection[n];
282       if (this.list_rows[id].obj) {
283         this.set_classname(this.list_rows[id].obj, 'selected', true);
284         this.set_classname(this.list_rows[id].obj, 'unfocused', false);
285       }
286     }
d58c69 287
S 288     this.in_message_list = true;
289     e.cancelBubble = true;
290     };
291
356b6e 292   this.key_pressed = function(e, msg_list_frame) {
d58c69 293     if (this.in_message_list != true) 
S 294       return true;
295     var keyCode = document.layers ? e.which : document.all ? event.keyCode : document.getElementById ? e.keyCode : 0;
296     var mod_key = this.get_modifier(e);
356b6e 297     switch (keyCode) {
ff9107 298       case 13:
S 299         this.command('show','',this);
300         break;
356b6e 301       case 40:
S 302       case 38: 
303         return this.use_arrow_key(keyCode, mod_key, msg_list_frame);
304         break;
305       case 46:
306         return this.use_delete_key(keyCode, mod_key, msg_list_frame);
307         break;
308       default:
309         return true;
310     }
ff9107 311     return true;
356b6e 312   }
S 313
314   this.use_arrow_key = function(keyCode, mod_key, msg_list_frame) {
d58c69 315     var scroll_to = 0;
S 316     if (keyCode == 40) { // down arrow key pressed
e8adc7 317       new_row = this.get_next_row();
d58c69 318       if (!new_row) return false;
S 319       scroll_to = (Number(new_row.offsetTop) + Number(new_row.offsetHeight)) - Number(msg_list_frame.offsetHeight);
320     } else if (keyCode == 38) { // up arrow key pressed
e8adc7 321       new_row = this.get_prev_row();
d58c69 322       if (!new_row) return false;
S 323       scroll_to = new_row.offsetTop;
f108b9 324     } else {return true;}
d58c69 325     
e8adc7 326     this.select_row(new_row.uid,mod_key,true);
d58c69 327
S 328     if (((Number(new_row.offsetTop)) < (Number(msg_list_frame.scrollTop))) || 
329        ((Number(new_row.offsetTop) + Number(new_row.offsetHeight)) > (Number(msg_list_frame.scrollTop) + Number(msg_list_frame.offsetHeight)))) {
330       msg_list_frame.scrollTop = scroll_to;
331     }
332     return false;
333   };
356b6e 334   
S 335   this.use_delete_key = function(keyCode, mod_key, msg_list_frame){
336     this.command('delete','',this);
337     return false;
338   }
4e17e6 339
T 340   // get all message rows from HTML table and init each row
341   this.init_messagelist = function(msg_list)
342     {
343     if (msg_list && msg_list.tBodies[0])
344       {
d58c69 345           
4e17e6 346       this.message_rows = new Array();
T 347
348       var row;
349       for(var r=0; r<msg_list.tBodies[0].childNodes.length; r++)
350         {
351         row = msg_list.tBodies[0].childNodes[r];
1c5853 352         while (row && (row.nodeType != 1 || row.style.display == 'none')) {
S 353           row = row.nextSibling;
354           r++;
355         }
4e17e6 356         //row = msg_list.tBodies[0].rows[r];
1c5853 357         if (row) this.init_message_row(row);
4e17e6 358         }
T 359       }
360       
361     // alias to common rows array
362     this.list_rows = this.message_rows;
363     };
364     
365     
366   // make references in internal array and set event handlers
367   this.init_message_row = function(row)
368     {
369     var uid, msg_icon;
370     
371     if (String(row.id).match(/rcmrow([0-9]+)/))
372       {
373       uid = RegExp.$1;
374       row.uid = uid;
375               
376       this.message_rows[uid] = {id:row.id, obj:row,
377                                 classname:row.className,
857a38 378                                 deleted:this.env.messages[uid] ? this.env.messages[uid].deleted : null,
4e17e6 379                                 unread:this.env.messages[uid] ? this.env.messages[uid].unread : null,
T 380                                 replied:this.env.messages[uid] ? this.env.messages[uid].replied : null};
381               
382       // set eventhandlers to table row
383       row.onmousedown = function(e){ return rcube_webmail_client.drag_row(e, this.uid); };
384       row.onmouseup = function(e){ return rcube_webmail_client.click_row(e, this.uid); };
d58c69 385
b11a00 386       if (document.all)
T 387         row.onselectstart = function() { return false; };
388
4e17e6 389       // set eventhandler to message icon
T 390       if ((msg_icon = row.cells[0].childNodes[0]) && row.cells[0].childNodes[0].nodeName=='IMG')
391         {                
392         msg_icon.id = 'msgicn_'+uid;
393         msg_icon._row = row;
857a38 394         msg_icon.onmousedown = function(e) { rcube_webmail_client.command('toggle_status', this); };
4e17e6 395                 
T 396         // get message icon and save original icon src
397         this.message_rows[uid].icon = msg_icon;
398         }
399       }
400     };
401
402
403   // init message compose form: set focus and eventhandlers
404   this.init_messageform = function()
405     {
406     if (!this.gui_objects.messageform)
407       return false;
408     
409     //this.messageform = this.gui_objects.messageform;
1cded8 410     var input_from = rcube_find_object('_from');
4e17e6 411     var input_to = rcube_find_object('_to');
T 412     var input_cc = rcube_find_object('_cc');
413     var input_bcc = rcube_find_object('_bcc');
414     var input_replyto = rcube_find_object('_replyto');
415     var input_subject = rcube_find_object('_subject');
416     var input_message = rcube_find_object('_message');
417     
418     // init live search events
419     if (input_to)
420       this.init_address_input_events(input_to);
421     if (input_cc)
422       this.init_address_input_events(input_cc);
423     if (input_bcc)
424       this.init_address_input_events(input_bcc);
1cded8 425       
T 426     // add signature according to selected identity
427     if (input_from && input_from.type=='select-one')
428       this.change_identity(input_from);
4e17e6 429
T 430     if (input_to && input_to.value=='')
431       input_to.focus();
432     else if (input_subject && input_subject.value=='')
433       input_subject.focus();
434     else if (input_message)
435       this.set_caret2start(input_message); // input_message.focus();
977a29 436     
T 437     // get summary of all field values
438     this.cmp_hash = this.compose_field_hash();
4e17e6 439     };
T 440
441
442   this.init_address_input_events = function(obj)
443     {
444     var handler = function(e){ return rcube_webmail_client.ksearch_keypress(e,this); };
445     var handler2 = function(e){ return rcube_webmail_client.ksearch_blur(e,this); };
446     
447     if (bw.safari)
448       {
449       obj.addEventListener('keydown', handler, false);
450       // obj.addEventListener('blur', handler2, false);
451       }
452     else if (bw.mz)
453       {
454       obj.addEventListener('keypress', handler, false);
455       obj.addEventListener('blur', handler2, false);
456       }
457     else if (bw.ie)
458       {
459       obj.onkeydown = handler;
460       //obj.attachEvent('onkeydown', handler);
461       // obj.attachEvent('onblur', handler2, false);
462       }
463     
464     obj.setAttribute('autocomplete', 'off');       
465     };
466
467
468
469   // get all contact rows from HTML table and init each row
470   this.init_contactslist = function(contacts_list)
471     {
472     if (contacts_list && contacts_list.tBodies[0])
473       {
474       this.contact_rows = new Array();
475
476       var row;
477       for(var r=0; r<contacts_list.tBodies[0].childNodes.length; r++)
478         {
479         row = contacts_list.tBodies[0].childNodes[r];
480         this.init_table_row(row, 'contact_rows');
481         }
482       }
483
484     // alias to common rows array
485     this.list_rows = this.contact_rows;
486     
487     if (this.env.cid)
2a1e18 488       this.highlight_row(this.env.cid);
4e17e6 489     };
T 490
491
d1d2c4 492   // get all contact rows from HTML table and init each row
S 493   this.init_ldapsearchlist = function(ldap_contacts_list)
494     {
495     if (ldap_contacts_list && ldap_contacts_list.tBodies[0])
496       {
497       this.ldap_contact_rows = new Array();
498
499       var row;
500       for(var r=0; r<ldap_contacts_list.tBodies[0].childNodes.length; r++)
501         {
502         row = ldap_contacts_list.tBodies[0].childNodes[r];
503         this.init_table_row(row, 'ldap_contact_rows');
504         }
505       }
506
507     // alias to common rows array
508     this.list_rows = this.ldap_contact_rows;
509     };
510
511
4e17e6 512   // make references in internal array and set event handlers
T 513   this.init_table_row = function(row, array_name)
514     {
515     var cid;
516     
517     if (String(row.id).match(/rcmrow([0-9]+)/))
518       {
519       cid = RegExp.$1;
520       row.cid = cid;
521
522       this[array_name][cid] = {id:row.id,
523                                obj:row,
524                                classname:row.className};
525
526       // set eventhandlers to table row
527       row.onmousedown = function(e) { rcube_webmail_client.in_selection_before=this.cid; return false; };  // fake for drag handler
528       row.onmouseup = function(e){ return rcube_webmail_client.click_row(e, this.cid); };
529       }
530     };
531
532
533   // get all contact rows from HTML table and init each row
534   this.init_identitieslist = function(identities_list)
535     {
536     if (identities_list && identities_list.tBodies[0])
537       {
538       this.identity_rows = new Array();
539
540       var row;
541       for(var r=0; r<identities_list.tBodies[0].childNodes.length; r++)
542         {
543         row = identities_list.tBodies[0].childNodes[r];
544         this.init_table_row(row, 'identity_rows');
545         }
546       }
547
548     // alias to common rows array
549     this.list_rows = this.identity_rows;
550     
551     if (this.env.iid)
2a1e18 552       this.highlight_row(this.env.iid);    
4e17e6 553     };
T 554     
555
556
557   /*********************************************************/
558   /*********       client command interface        *********/
559   /*********************************************************/
560
561
562   // execute a specific command on the web client
563   this.command = function(command, props, obj)
564     {
565     if (obj && obj.blur)
566       obj.blur();
567
568     if (this.busy)
569       return false;
570
571     // command not supported or allowed
572     if (!this.commands[command])
573       {
574       // pass command to parent window
575       if (this.env.framed && parent.rcmail && parent.rcmail.command)
576         parent.rcmail.command(command, props);
577
578       return false;
579       }
15a9d1 580       
T 581       
582    // check input before leaving compose step
583    if (this.task=='mail' && this.env.action=='compose' && (command=='list' || command=='mail' || command=='addressbook' || command=='settings'))
584      {
585      if (this.cmp_hash != this.compose_field_hash() && !confirm(this.get_label('notsentwarning')))
586         return false;
587      }
588
4e17e6 589
T 590     // process command
591     switch (command)
592       {
593       case 'login':
594         if (this.gui_objects.loginform)
595           this.gui_objects.loginform.submit();
596         break;
597
598       case 'logout':
599         location.href = this.env.comm_path+'&_action=logout';
600         break;      
601
602       // commands to switch task
603       case 'mail':
604       case 'addressbook':
605       case 'settings':
606         this.switch_task(command);
607         break;
608
609
610       // misc list commands
611       case 'list':
612         if (this.task=='mail')
4647e1 613           {
ac6b87 614           if (this.env.search_request<0 || (this.env.search_request && props != this.env.mailbox))
4647e1 615             this.reset_qsearch();
c8c1e0 616
S 617       // Reset message list header, unless returning from compose/read/etc
618       if (this.env.mailbox != props && this.message_rows)
619         this.clear_message_list_header();
620
4e17e6 621           this.list_mailbox(props);
4647e1 622           }
4e17e6 623         else if (this.task=='addressbook')
T 624           this.list_contacts();
f3b659 625         break;
c8c1e0 626
f3b659 627
T 628       case 'sort':
629         // get the type of sorting
b076a4 630         var a_sort = props.split('_');
T 631         var sort_col = a_sort[0];
1cded8 632         var sort_order = a_sort[1] ? a_sort[1].toUpperCase() : null;
b076a4 633         var header;
1cded8 634         
T 635         // no sort order specified: toggle
636         if (sort_order==null)
637           {
638           if (this.env.sort_col==sort_col)
639             sort_order = this.env.sort_order=='ASC' ? 'DESC' : 'ASC';
640           else
641             sort_order = this.env.sort_order;
642           }
b076a4 643         
T 644         if (this.env.sort_col==sort_col && this.env.sort_order==sort_order)
645           break;
646
647         // set table header class
648         if (header = document.getElementById('rcmHead'+this.env.sort_col))
649           this.set_classname(header, 'sorted'+(this.env.sort_order.toUpperCase()), false);
650         if (header = document.getElementById('rcmHead'+sort_col))
651           this.set_classname(header, 'sorted'+sort_order, true);
652
653         // save new sort properties
654         this.env.sort_col = sort_col;
655         this.env.sort_order = sort_order;
656
657         // reload message list
1cded8 658         this.list_mailbox('', '', sort_col+'_'+sort_order);
4e17e6 659         break;
T 660
661       case 'nextpage':
662         this.list_page('next');
663         break;
664
665       case 'previouspage':
666         this.list_page('prev');
15a9d1 667         break;
T 668
669       case 'expunge':
670         if (this.env.messagecount)
671           this.expunge_mailbox(this.env.mailbox);
672         break;
673
5e3512 674       case 'purge':
T 675       case 'empty-mailbox':
676         if (this.env.messagecount)
677           this.purge_mailbox(this.env.mailbox);
4e17e6 678         break;
T 679
680
681       // common commands used in multiple tasks
682       case 'show':
683         if (this.task=='mail')
684           {
685           var uid = this.get_single_uid();
686           if (uid && (!this.env.uid || uid != this.env.uid))
1966c5 687         {
S 688             if (this.env.mailbox==this.env.drafts_mailbox)
689               {
690               this.set_busy(true);
691               location.href = this.env.comm_path+'&_action=compose&_draft_uid='+uid+'&_mbox='+escape(this.env.mailbox);
692               }
693             else
694               {
695               this.show_message(uid);
696           }
697         }
4e17e6 698           }
T 699         else if (this.task=='addressbook')
700           {
701           var cid = props ? props : this.get_single_cid();
702           if (cid && !(this.env.action=='show' && cid==this.env.cid))
703             this.load_contact(cid, 'show');
704           }
705         break;
706
707       case 'add':
708         if (this.task=='addressbook')
d1d2c4 709           if (!window.frames[this.env.contentframe].rcmail)
S 710             this.load_contact(0, 'add');
711           else
712             {
713             if (window.frames[this.env.contentframe].rcmail.selection.length)
714               this.add_ldap_contacts();
715             else
716               this.load_contact(0, 'add');
717             }
4e17e6 718         else if (this.task=='settings')
T 719           {
720           this.clear_selection();
721           this.load_identity(0, 'add-identity');
722           }
723         break;
724
725       case 'edit':
726         var cid;
727         if (this.task=='addressbook' && (cid = this.get_single_cid()))
728           this.load_contact(cid, 'edit');
729         else if (this.task=='settings' && props)
730           this.load_identity(props, 'edit-identity');
731         break;
732
733       case 'save-identity':
734       case 'save':
735         if (this.gui_objects.editform)
10a699 736           {
T 737           var input_pagesize = rcube_find_object('_pagesize');
738           var input_name  = rcube_find_object('_name');
739           var input_email = rcube_find_object('_email');
740
741           // user prefs
e66f5b 742           if (input_pagesize && isNaN(input_pagesize.value))
10a699 743             {
T 744             alert(this.get_label('nopagesizewarning'));
745             input_pagesize.focus();
746             break;
747             }
748           // contacts/identities
749           else
750             {
751             if (input_name && input_name.value == '')
752               {
753               alert(this.get_label('nonamewarning'));
754               input_name.focus();
755               break;
756               }
757             else if (input_email && !rcube_check_email(input_email.value))
758               {
759               alert(this.get_label('noemailwarning'));
760               input_email.focus();
761               break;
762               }
763             }
764
4e17e6 765           this.gui_objects.editform.submit();
10a699 766           }
4e17e6 767         break;
T 768
769       case 'delete':
770         // mail task
857a38 771         if (this.task=='mail')
4e17e6 772           this.delete_messages();
T 773         // addressbook task
774         else if (this.task=='addressbook')
775           this.delete_contacts();
776         // user settings task
777         else if (this.task=='settings')
778           this.delete_identity();
779         break;
780
781
782       // mail task commands
783       case 'move':
784       case 'moveto':
785         this.move_messages(props);
786         break;
787         
857a38 788       case 'toggle_status':
4e17e6 789         if (props && !props._row)
T 790           break;
791         
792         var uid;
793         var flag = 'read';
794         
795         if (props._row.uid)
796           {
797           uid = props._row.uid;
798           this.dont_select = true;
799           // toggle read/unread
857a38 800           if (this.message_rows[uid].deleted) {
S 801               flag = 'undelete';
802           } else if (!this.message_rows[uid].unread)
4e17e6 803             flag = 'unread';
T 804           }
805           
806         this.mark_message(flag, uid);
807         break;
808         
809       case 'load-images':
810         if (this.env.uid)
811           this.show_message(this.env.uid, true);
812         break;
813
814       case 'load-attachment':
815         var url = this.env.comm_path+'&_action=get&_mbox='+this.env.mailbox+'&_uid='+this.env.uid+'&_part='+props.part;
816         
817         // open attachment in frame if it's of a supported mimetype
818         if (this.env.uid && props.mimetype && find_in_array(props.mimetype, this.mimetypes)>=0)
819           {
820           this.attachment_win = window.open(url+'&_frame=1', 'rcubemailattachment');
821           if (this.attachment_win)
822             {
823             setTimeout(this.ref+'.attachment_win.focus()', 10);
824             break;
825             }
826           }
827
828         location.href = url;
829         break;
830         
831       case 'select-all':
832         this.select_all(props);
833         break;
834
835       case 'select-none':
836         this.clear_selection();
837         break;
838
839       case 'nextmessage':
840         if (this.env.next_uid)
09941e 841           this.show_message(this.env.next_uid);
T 842           //location.href = this.env.comm_path+'&_action=show&_uid='+this.env.next_uid+'&_mbox='+this.env.mailbox;
4e17e6 843         break;
T 844
845       case 'previousmessage':
846         if (this.env.prev_uid)
09941e 847           this.show_message(this.env.prev_uid);
T 848           //location.href = this.env.comm_path+'&_action=show&_uid='+this.env.prev_uid+'&_mbox='+this.env.mailbox;
4e17e6 849         break;
d1d2c4 850       
c8c1e0 851       case 'checkmail':
S 852         this.check_for_recent();
853         break;
d1d2c4 854       
4e17e6 855       case 'compose':
T 856         var url = this.env.comm_path+'&_action=compose';
1966c5 857        
S 858         if (this.task=='mail' && this.env.mailbox==this.env.drafts_mailbox)
859           {
860           var uid = this.get_single_uid();
861           url += '&_draft_uid='+uid+'&_mbox='+escape(this.env.mailbox);
862           } 
4e17e6 863         // modify url if we're in addressbook
1966c5 864         else if (this.task=='addressbook')
4e17e6 865           {
T 866           url = this.get_task_url('mail', url);            
867           var a_cids = new Array();
868           
869           // use contact_id passed as command parameter
870           if (props)
871             a_cids[a_cids.length] = props;
872             
873           // get selected contacts
874           else
875             {
d1d2c4 876             if (!window.frames[this.env.contentframe].rcmail.selection.length)
S 877               {
878               for (var n=0; n<this.selection.length; n++)
879                 a_cids[a_cids.length] = this.selection[n];
880               }
881             else
882               {
883               var frameRcmail = window.frames[this.env.contentframe].rcmail;
884               // get the email address(es)
885               for (var n=0; n<frameRcmail.selection.length; n++)
886                 a_cids[a_cids.length] = frameRcmail.ldap_contact_rows[frameRcmail.selection[n]].obj.cells[1].innerHTML;
887               }
4e17e6 888             }
T 889           if (a_cids.length)
890             url += '&_to='+a_cids.join(',');
891           else
892             break;
d1d2c4 893             
4e17e6 894           }
T 895         else if (props)
896            url += '&_to='+props;
bc8dc6 897
d1d2c4 898         // don't know if this is necessary...
S 899         url = url.replace(/&_framed=1/, "");
4e17e6 900
T 901         this.set_busy(true);
d1d2c4 902
S 903         // need parent in case we are coming from the contact frame
bc8dc6 904         if (this.env.framed)
T 905           parent.location.href = url;
906         else
907           location.href = url;
ed5d29 908         break;
T 909         
910       case 'spellcheck':
911         if (this.env.spellcheck && this.env.spellcheck.spellCheck)
912           this.env.spellcheck.spellCheck(this.env.spellcheck.check_link);
913         break;
4e17e6 914
1966c5 915       case 'savedraft':
S 916         if (!this.gui_objects.messageform)
917           break;
918         
919     // if saving Drafts is disabled in main.inc.php
920     if (!this.env.drafts_mailbox)
921       break;
922  
923         this.set_busy(true, 'savingmessage');
924         var form = this.gui_objects.messageform;
925         form.submit();
926         break;
927
4e17e6 928       case 'send':
T 929         if (!this.gui_objects.messageform)
930           break;
931           
977a29 932         if (!this.check_compose_input())
10a699 933           break;
T 934
935         // all checks passed, send message
936         this.set_busy(true, 'sendingmessage');
937         var form = this.gui_objects.messageform;
1966c5 938     form._draft.value='';
10a699 939         form.submit();
4e17e6 940         break;
T 941
942       case 'add-attachment':
943         this.show_attachment_form(true);
944         
945       case 'send-attachment':
946         this.upload_file(props)      
947         break;
a894ba 948       
S 949       case 'remove-attachment':
950         this.remove_attachment(props);
951         break;
4e17e6 952
583f1c 953       case 'reply-all':
4e17e6 954       case 'reply':
T 955         var uid;
956         if (uid = this.get_single_uid())
957           {
958           this.set_busy(true);
583f1c 959           location.href = this.env.comm_path+'&_action=compose&_reply_uid='+uid+'&_mbox='+escape(this.env.mailbox)+(command=='reply-all' ? '&_all=1' : '');
4e17e6 960           }
T 961         break;      
962
963       case 'forward':
964         var uid;
965         if (uid = this.get_single_uid())
966           {
967           this.set_busy(true);
968           location.href = this.env.comm_path+'&_action=compose&_forward_uid='+uid+'&_mbox='+escape(this.env.mailbox);
969           }
970         break;
971         
972       case 'print':
973         var uid;
974         if (uid = this.get_single_uid())
975           {
976           this.printwin = window.open(this.env.comm_path+'&_action=print&_uid='+uid+'&_mbox='+escape(this.env.mailbox)+(this.env.safemode ? '&_safe=1' : ''));
977           if (this.printwin)
978             setTimeout(this.ref+'.printwin.focus()', 20);
979           }
980         break;
981
982       case 'viewsource':
983         var uid;
984         if (uid = this.get_single_uid())
985           {          
986           this.sourcewin = window.open(this.env.comm_path+'&_action=viewsource&_uid='+this.env.uid+'&_mbox='+escape(this.env.mailbox));
987           if (this.sourcewin)
988             setTimeout(this.ref+'.sourcewin.focus()', 20);
989           }
990         break;
991
992       case 'add-contact':
993         this.add_contact(props);
994         break;
d1d2c4 995       
4647e1 996       // mail quicksearch
T 997       case 'search':
998         if (!props && this.gui_objects.qsearchbox)
999           props = this.gui_objects.qsearchbox.value;
1000         if (props)
1001           this.qsearch(escape(props), this.env.mailbox);
1002         break;
1003
1004       // reset quicksearch        
1005       case 'reset-search':
1006         var s = this.env.search_request;
1007         this.reset_qsearch();
1008         
1009         if (s)
1010           this.list_mailbox(this.env.mailbox);
1011         break;
d1d2c4 1012
S 1013       // ldap search
1014       case 'ldappublicsearch':
1015         if (this.gui_objects.ldappublicsearchform) 
1016           this.gui_objects.ldappublicsearchform.submit();
1017         else 
1018           this.ldappublicsearch(command);
1019         break; 
4e17e6 1020
T 1021
1022       // user settings commands
1023       case 'preferences':
1024         location.href = this.env.comm_path;
1025         break;
1026
1027       case 'identities':
1028         location.href = this.env.comm_path+'&_action=identities';
1029         break;
1030           
1031       case 'delete-identity':
1032         this.delete_identity();
1033         
1034       case 'folders':
1035         location.href = this.env.comm_path+'&_action=folders';
1036         break;
1037
1038       case 'subscribe':
1039         this.subscribe_folder(props);
1040         break;
1041
1042       case 'unsubscribe':
1043         this.unsubscribe_folder(props);
1044         break;
1045         
1046       case 'create-folder':
1047         this.create_folder(props);
c8c1e0 1048         break;
S 1049
1050       case 'rename-folder':
1051         this.rename_folder(props);
4e17e6 1052         break;
T 1053
1054       case 'delete-folder':
1cded8 1055         if (confirm(this.get_label('deletefolderconfirm')))
4e17e6 1056           this.delete_folder(props);
T 1057         break;
1058
1059       }
1060
1061     return obj ? false : true;
1062     };
1063
1064
1065   // set command enabled or disabled
1066   this.enable_command = function()
1067     {
1c5853 1068     var args = arguments;
4e17e6 1069     if(!args.length) return -1;
T 1070
1071     var command;
1072     var enable = args[args.length-1];
1073     
1074     for(var n=0; n<args.length-1; n++)
1075       {
1076       command = args[n];
1077       this.commands[command] = enable;
1078       this.set_button(command, (enable ? 'act' : 'pas'));
1079       }
1c5853 1080       return true;
4e17e6 1081     };
T 1082
1083
a95e0e 1084   // lock/unlock interface
4e17e6 1085   this.set_busy = function(a, message)
T 1086     {
1087     if (a && message)
10a699 1088       {
T 1089       var msg = this.get_label(message);
1090       if (msg==message)        
1091         msg = 'Loading...';
1092
1093       this.display_message(msg, 'loading', true);
1094       }
4e17e6 1095     else if (!a && this.busy)
T 1096       this.hide_message();
1097
1098     this.busy = a;
1099     //document.body.style.cursor = a ? 'wait' : 'default';
1100     
1101     if (this.gui_objects.editform)
1102       this.lock_form(this.gui_objects.editform, a);
a95e0e 1103       
T 1104     // clear pending timer
1105     if (this.request_timer)
1106       clearTimeout(this.request_timer);
1107
1108     // set timer for requests
1109     if (a && this.request_timeout)
1110       this.request_timer = setTimeout(this.ref+'.request_timed_out()', this.request_timeout);
4e17e6 1111     };
T 1112
1113
10a699 1114   // return a localized string
T 1115   this.get_label = function(name)
1116     {
1117     if (this.labels[name])
1118       return this.labels[name];
1119     else
1120       return name;
1121     };
1122
1123
1124   // switch to another application task
4e17e6 1125   this.switch_task = function(task)
T 1126     {
01bb03 1127     if (this.task===task && task!='mail')
4e17e6 1128       return;
T 1129
01bb03 1130     var url = this.get_task_url(task);
T 1131     if (task=='mail')
1132       url += '&_mbox=INBOX';
1133
4e17e6 1134     this.set_busy(true);
01bb03 1135     location.href = url;
4e17e6 1136     };
T 1137
1138
1139   this.get_task_url = function(task, url)
1140     {
1141     if (!url)
1142       url = this.env.comm_path;
1143
1144     return url.replace(/_task=[a-z]+/, '_task='+task);
a95e0e 1145     };
T 1146     
1147   
1148   // called when a request timed out
1149   this.request_timed_out = function()
1150     {
1151     this.set_busy(false);
1152     this.display_message('Request timed out!', 'error');
4e17e6 1153     };
T 1154
1155
1156   /*********************************************************/
1157   /*********        event handling methods         *********/
1158   /*********************************************************/
1159
1160
1161   // onmouseup handler for mailboxlist item
1162   this.mbox_mouse_up = function(mbox)
1163     {
1164     if (this.drag_active)
1165       this.command('moveto', mbox);
1166     else
1167       this.command('list', mbox);
1168       
1169     return false;
1170     };
1171
1172
1173   // onmousedown-handler of message list row
1174   this.drag_row = function(e, id)
1175     {
1176     this.in_selection_before = this.in_selection(id) ? id : false;
1177
1178     // don't do anything (another action processed before)
1179     if (this.dont_select)
1180       return false;
1181
b11a00 1182     // selects currently unselected row
ff9107 1183     if (!this.in_selection_before && !this.list_rows[id].clicked)
b11a00 1184     {
d58c69 1185       var mod_key = this.get_modifier(e);
e8adc7 1186       this.select_row(id,mod_key,false);
b11a00 1187     }
4e17e6 1188     
T 1189     if (this.selection.length)
1190       {
1191       this.drag_start = true;
1192       document.onmousemove = function(e){ return rcube_webmail_client.drag_mouse_move(e); };
1193       document.onmouseup = function(e){ return rcube_webmail_client.drag_mouse_up(e); };
1194       }
1195
1196     return false;
1197     };
1198
1199
1200   // onmouseup-handler of message list row
1201   this.click_row = function(e, id)
1202     {
8c2e58 1203     var mod_key = this.get_modifier(e);
d58c69 1204
4e17e6 1205     // don't do anything (another action processed before)
T 1206     if (this.dont_select)
1207       {
1208       this.dont_select = false;
1209       return false;
1210       }
1211     
b11a00 1212     // unselects currently selected row    
ff9107 1213     if (!this.drag_active && this.in_selection_before==id && !this.list_rows[id].clicked)
e8adc7 1214       this.select_row(id,mod_key,false);
8c2e58 1215
4e17e6 1216     this.drag_start = false;
T 1217     this.in_selection_before = false;
1218         
1219     // row was double clicked
ff9107 1220     if (this.task=='mail' && this.list_rows && this.list_rows[id].clicked && this.in_selection(id))
4e17e6 1221       {
1966c5 1222       if (this.env.mailbox==this.env.drafts_mailbox)
S 1223         {
1224         this.set_busy(true);
1225         location.href = this.env.comm_path+'&_action=compose&_draft_uid='+id+'&_mbox='+escape(this.env.mailbox);
1226         }
1227       else
1228         {
1229         this.show_message(id);
1230         }
4e17e6 1231       return false;
T 1232       }
1233     else if (this.task=='addressbook')
1234       {
d1d2c4 1235       if (this.contact_rows && this.selection.length==1)
S 1236         {
4e17e6 1237         this.load_contact(this.selection[0], 'show', true);
d1d2c4 1238         // change the text for the add contact button
S 1239         var links = parent.document.getElementById('abooktoolbar').getElementsByTagName('A');
1240         for (i = 0; i < links.length; i++)
1241           {
1242           var onclickstring = new String(links[i].onclick);
1243           if (onclickstring.search('\"add\"') != -1)
1244             links[i].title = this.env.newcontact;
1245           }
1246         }
1247       else if (this.contact_rows && this.contact_rows[id].clicked)
4e17e6 1248         {
T 1249         this.load_contact(id, 'show');
1250         return false;
1251         }
d1d2c4 1252       else if (this.ldap_contact_rows && !this.ldap_contact_rows[id].clicked)
S 1253         {
1254         // clear selection
1255         parent.rcmail.clear_selection();
1256
1257         // disable delete
1258         parent.rcmail.set_button('delete', 'pas');
1259
1260         // change the text for the add contact button
1261         var links = parent.document.getElementById('abooktoolbar').getElementsByTagName('A');
1262         for (i = 0; i < links.length; i++)
1263           {
1264           var onclickstring = new String(links[i].onclick);
1265           if (onclickstring.search('\"add\"') != -1)
1266             links[i].title = this.env.addcontact;
1267           }
1268         }
1269       // handle double click event
1270       else if (this.ldap_contact_rows && this.selection.length==1 && this.ldap_contact_rows[id].clicked)
1271         this.command('compose', this.ldap_contact_rows[id].obj.cells[1].innerHTML);
4e17e6 1272       else if (this.env.contentframe)
T 1273         {
1274         var elm = document.getElementById(this.env.contentframe);
1275         elm.style.visibility = 'hidden';
1276         }
1277       }
1278     else if (this.task=='settings')
1279       {
1280       if (this.selection.length==1)
1281         this.command('edit', this.selection[0]);
1282       }
1283
1284     this.list_rows[id].clicked = true;
1285     setTimeout(this.ref+'.list_rows['+id+'].clicked=false;', this.dblclick_time);
1286       
1287     return false;
1288     };
1289
1290
1291
1292   /*********************************************************/
1293   /*********     (message) list functionality      *********/
1294   /*********************************************************/
1295
e8adc7 1296   // get next and previous rows that are not hidden
S 1297   this.get_next_row = function(){
dab065 1298       if (!this.list_rows) return false;
e8adc7 1299     var last_selected_row = this.list_rows[this.last_selected];
S 1300     var new_row = last_selected_row.obj.nextSibling;
1301     while (new_row && (new_row.nodeType != 1 || new_row.style.display == 'none')) {
1302       new_row = new_row.nextSibling;
1303     }
1304     return new_row;
1305   }
1306   
1307   this.get_prev_row = function(){
dab065 1308     if (!this.list_rows) return false;
e8adc7 1309     var last_selected_row = this.list_rows[this.last_selected];
S 1310     var new_row = last_selected_row.obj.previousSibling;
1311     while (new_row && (new_row.nodeType != 1 || new_row.style.display == 'none')) {
1312       new_row = new_row.previousSibling;
1313     }
1314     return new_row;
1315   }
1316   
d58c69 1317   // highlight/unhighlight a row
S 1318   this.highlight_row = function(id, multiple)
4e17e6 1319     {
T 1320     var selected = false
1321     
1322     if (this.list_rows[id] && !multiple)
1323       {
1324       this.clear_selection();
1325       this.selection[0] = id;
1326       this.list_rows[id].obj.className += ' selected';
1327       selected = true;
1328       }
1329     
1330     else if (this.list_rows[id])
1331       {
1332       if (!this.in_selection(id))  // select row
1333         {
1334         this.selection[this.selection.length] = id;
952860 1335         this.set_classname(this.list_rows[id].obj, 'selected', true);
4e17e6 1336         }
T 1337       else  // unselect row
1338         {
1339         var p = find_in_array(id, this.selection);
1340         var a_pre = this.selection.slice(0, p);
1341         var a_post = this.selection.slice(p+1, this.selection.length);
1342         this.selection = a_pre.concat(a_post);
1343         this.set_classname(this.list_rows[id].obj, 'selected', false);
952860 1344         this.set_classname(this.list_rows[id].obj, 'unfocused', false);
4e17e6 1345         }
T 1346       selected = (this.selection.length==1);
1347       }
1348
1349     // enable/disable commands for message
1350     if (this.task=='mail')
1351       {
1966c5 1352       if (this.env.mailbox==this.env.drafts_mailbox)
S 1353     {
1354     this.enable_command('show', selected);
1355     this.enable_command('delete', this.selection.length>0 ? true : false);
1356         }
1357       else
1358         {
1359         this.enable_command('show', 'reply', 'reply-all', 'forward', 'print', selected);
1360         this.enable_command('delete', 'moveto', this.selection.length>0 ? true : false);
1361         }
4e17e6 1362       }
T 1363     else if (this.task=='addressbook')
1364       {
1365       this.enable_command('edit', /*'print',*/ selected);
1366       this.enable_command('delete', 'compose', this.selection.length>0 ? true : false);
1367       }
1368     };
1369
b11a00 1370
d58c69 1371 // selects or unselects the proper row depending on the modifier key pressed
857a38 1372   this.select_row = function(id,mod_key,with_mouse)  { 
d58c69 1373       if (!mod_key) {
S 1374       this.shift_start = id;
1375         this.highlight_row(id, false);
1376     } else {
1377       switch (mod_key) {
1378         case SHIFT_KEY: { 
1379           this.shift_select(id,false); 
1380           break; }
1381         case CONTROL_KEY: { 
1382           this.shift_start = id;
857a38 1383           if (!with_mouse)
e8adc7 1384             this.highlight_row(id, true); 
d58c69 1385           break; 
S 1386           }
1387         case CONTROL_SHIFT_KEY: { 
1388           this.shift_select(id,true);
1389           break;
1390           }
1391         default: {
1392           this.highlight_row(id, false); 
1393           break;
1394           }
1395       }
1396     }
cbd62d 1397     if (this.last_selected != 0) { this.set_classname(this.list_rows[this.last_selected].obj, 'focused', false);}
e8adc7 1398     this.last_selected = id;
cbd62d 1399     this.set_classname(this.list_rows[id].obj, 'focused', true);        
d58c69 1400   };
S 1401
1402   this.shift_select = function(id, control) {
1403     var from_rowIndex = this.list_rows[this.shift_start].obj.rowIndex;
b11a00 1404     var to_rowIndex = this.list_rows[id].obj.rowIndex;
T 1405         
1406     var i = ((from_rowIndex < to_rowIndex)? from_rowIndex : to_rowIndex);
1407     var j = ((from_rowIndex > to_rowIndex)? from_rowIndex : to_rowIndex);
1408     
1409     // iterate through the entire message list
1410     for (var n in this.list_rows) {
1411       if ((this.list_rows[n].obj.rowIndex >= i) && (this.list_rows[n].obj.rowIndex <= j)) {
1412         if (!this.in_selection(n))
d58c69 1413           this.highlight_row(n, true);
b11a00 1414       } else {
T 1415         if  (this.in_selection(n) && !control)
d58c69 1416           this.highlight_row(n, true);
b11a00 1417       }
T 1418     }
1419   };
1420   
4e17e6 1421
T 1422   this.clear_selection = function()
1423     {
1424     for(var n=0; n<this.selection.length; n++)
952860 1425       if (this.list_rows[this.selection[n]]) {
4e17e6 1426         this.set_classname(this.list_rows[this.selection[n]].obj, 'selected', false);
952860 1427         this.set_classname(this.list_rows[this.selection[n]].obj, 'unfocused', false);
S 1428       }
4e17e6 1429     this.selection = new Array();    
T 1430     };
1431
1432
1433   // check if given id is part of the current selection
1434   this.in_selection = function(id)
1435     {
1436     for(var n in this.selection)
1437       if (this.selection[n]==id)
1438         return true;
1439
1440     return false;    
1441     };
1442
1443
1444   // select each row in list
1445   this.select_all = function(filter)
1446     {
1447     if (!this.list_rows || !this.list_rows.length)
1448       return false;
1449       
1450     // reset selection first
1451     this.clear_selection();
1452     
1c5853 1453     for (var n in this.list_rows) {
4e17e6 1454       if (!filter || this.list_rows[n][filter]==true)
1c5853 1455         this.highlight_row(n, true);
S 1456     }
1457     return true;  
4e17e6 1458     };
T 1459     
1460
1461   // when user doble-clicks on a row
1462   this.show_message = function(id, safe)
1463     {
1464     var add_url = '';
1465     var target = window;
1466     if (this.env.contentframe && window.frames && window.frames[this.env.contentframe])
1467       {
1468       target = window.frames[this.env.contentframe];
1469       add_url = '&_framed=1';
1470       }
1471       
1472     if (safe)
1473       add_url = '&_safe=1';
1474
1475     if (id)
1476       {
09941e 1477       this.set_busy(true, 'loading');
4e17e6 1478       target.location.href = this.env.comm_path+'&_action=show&_uid='+id+'&_mbox='+escape(this.env.mailbox)+add_url;
T 1479       }
1480     };
1481
1482
1483
1484   // list a specific page
1485   this.list_page = function(page)
1486     {
1487     if (page=='next')
1488       page = this.env.current_page+1;
1489     if (page=='prev' && this.env.current_page>1)
1490       page = this.env.current_page-1;
1491       
1492     if (page > 0 && page <= this.env.pagecount)
1493       {
1494       this.env.current_page = page;
1495       
1496       if (this.task=='mail')
1497         this.list_mailbox(this.env.mailbox, page);
1498       else if (this.task=='addressbook')
1499         this.list_contacts(page);
1500       }
1501     };
1502
1503
1504   // list messages of a specific mailbox
f3b659 1505   this.list_mailbox = function(mbox, page, sort)
4e17e6 1506     {
cbd62d 1507     this.last_selected = 0;
4e17e6 1508     var add_url = '';
T 1509     var target = window;
1510
1511     if (!mbox)
1512       mbox = this.env.mailbox;
1513
f3b659 1514     // add sort to url if set
T 1515     if (sort)
1516       add_url += '&_sort=' + sort;
1517       
4e17e6 1518     // set page=1 if changeing to another mailbox
T 1519     if (!page && mbox != this.env.mailbox)
1520       {
1521       page = 1;
9fee0e 1522       add_url += '&_refresh=1';
4e17e6 1523       this.env.current_page = page;
T 1524       this.clear_selection();
1525       }
4647e1 1526     
T 1527     // also send search request to get the right messages
1528     if (this.env.search_request)
1529       add_url += '&_search='+this.env.search_request;
4e17e6 1530       
T 1531     if (this.env.mailbox!=mbox)
1532       this.select_mailbox(mbox);
1533
1534     // load message list remotely
1535     if (this.gui_objects.messagelist)
1536       {
9fee0e 1537       this.list_mailbox_remote(mbox, page, add_url);
4e17e6 1538       return;
T 1539       }
1540     
1541     if (this.env.contentframe && window.frames && window.frames[this.env.contentframe])
1542       {
1543       target = window.frames[this.env.contentframe];
9fee0e 1544       add_url += '&_framed=1';
4e17e6 1545       }
T 1546
1547     // load message list to target frame/window
1548     if (mbox)
1549       {
1550       this.set_busy(true, 'loading');
1551       target.location.href = this.env.comm_path+'&_mbox='+escape(mbox)+(page ? '&_page='+page : '')+add_url;
1552       }
1553     };
1554
1555
1556   // send remote request to load message list
9fee0e 1557   this.list_mailbox_remote = function(mbox, page, add_url)
4e17e6 1558     {
15a9d1 1559     // clear message list first
T 1560     this.clear_message_list();
1561
1562     // send request to server
1563     var url = '_mbox='+escape(mbox)+(page ? '&_page='+page : '');
1564     this.set_busy(true, 'loading');
1565     this.http_request('list', url+add_url, true);
1566     };
1567
1568
1569   this.clear_message_list = function()
1570     {
4e17e6 1571     var table = this.gui_objects.messagelist;
c8c1e0 1572    
4e17e6 1573     var tbody = document.createElement('TBODY');
T 1574     table.insertBefore(tbody, table.tBodies[0]);
1575     table.removeChild(table.tBodies[1]);
1576     
1577     this.message_rows = new Array();
1578     this.list_rows = this.message_rows;
15a9d1 1579     
T 1580     };
1581
c8c1e0 1582   this.clear_message_list_header = function()
S 1583     {
1584     var table = this.gui_objects.messagelist;
1585
1586     var colgroup = document.createElement('COLGROUP');
1587     table.removeChild(table.colgroup);
1588     table.insertBefore(colgroup, table.thead);
1589
1590     var thead = document.createElement('THEAD');
1591     table.removeChild(table.thead);
1592     table.insertBefore(thead, table.tBodies[0]);
1593     };
15a9d1 1594
T 1595   this.expunge_mailbox = function(mbox)
1596     {
1597     var lock = false;
1598     var add_url = '';
1599     
1600     // lock interface if it's the active mailbox
1601     if (mbox == this.env.mailbox)
1602        {
1603        lock = true;
1604        this.set_busy(true, 'loading');
1605        add_url = '&_reload=1';
1606        }
4e17e6 1607
T 1608     // send request to server
15a9d1 1609     var url = '_mbox='+escape(mbox);
T 1610     this.http_request('expunge', url+add_url, lock);
4e17e6 1611     };
T 1612
1613
5e3512 1614   this.purge_mailbox = function(mbox)
T 1615     {
1616     var lock = false;
1617     var add_url = '';
1618     
1619     if (!confirm(this.get_label('purgefolderconfirm')))
1620       return false;
1621     
1622     // lock interface if it's the active mailbox
1623     if (mbox == this.env.mailbox)
1624        {
1625        lock = true;
1626        this.set_busy(true, 'loading');
1627        add_url = '&_reload=1';
1628        }
1629
1630     // send request to server
1631     var url = '_mbox='+escape(mbox);
1632     this.http_request('purge', url+add_url, lock);
1c5853 1633     return true;
5e3512 1634     };
T 1635     
1636
4e17e6 1637   // move selected messages to the specified mailbox
T 1638   this.move_messages = function(mbox)
1639     {
1640     // exit if no mailbox specified or if selection is empty
1641     if (!mbox || !(this.selection.length || this.env.uid) || mbox==this.env.mailbox)
1642       return;
1643     
1644     var a_uids = new Array();
1645
1646     if (this.env.uid)
1647       a_uids[a_uids.length] = this.env.uid;
1648     else
1649       {
1650       var id;
1651       for (var n=0; n<this.selection.length; n++)
1652         {
1653         id = this.selection[n];
1654         a_uids[a_uids.length] = id;
1655       
1656         // 'remove' message row from list (just hide it)
1657         if (this.message_rows[id].obj)
1658           this.message_rows[id].obj.style.display = 'none';
1659         }
cbd62d 1660       next_row = this.get_next_row();
S 1661       prev_row = this.get_prev_row();
1662       new_row = (next_row) ? next_row : prev_row;
dab065 1663       if (new_row) this.select_row(new_row.uid,false,false);
4e17e6 1664       }
ecf759 1665       
T 1666     var lock = false;
4e17e6 1667
T 1668     // show wait message
1669     if (this.env.action=='show')
ecf759 1670       {
T 1671       lock = true;
4e17e6 1672       this.set_busy(true, 'movingmessage');
ecf759 1673       }
4e17e6 1674     // send request to server
ecf759 1675     this.http_request('moveto', '_uid='+a_uids.join(',')+'&_mbox='+escape(this.env.mailbox)+'&_target_mbox='+escape(mbox)+'&_from='+(this.env.action ? this.env.action : ''), lock);
4e17e6 1676     };
T 1677
857a38 1678   this.permanently_remove_messages = function() {
4e17e6 1679     // exit if no mailbox specified or if selection is empty
T 1680     if (!(this.selection.length || this.env.uid))
1681       return;
1682     
1683     var a_uids = new Array();
1684
1685     if (this.env.uid)
1686       a_uids[a_uids.length] = this.env.uid;
1687     else
1688       {
1689       var id;
1690       for (var n=0; n<this.selection.length; n++)
1691         {
1692         id = this.selection[n];
1693         a_uids[a_uids.length] = id;
1694       
1695         // 'remove' message row from list (just hide it)
1696         if (this.message_rows[id].obj)
1697           this.message_rows[id].obj.style.display = 'none';
1698         }
1699       }
cbd62d 1700       next_row = this.get_next_row();
S 1701       prev_row = this.get_prev_row();
1702       new_row = (next_row) ? next_row : prev_row;
dab065 1703       if (new_row) this.select_row(new_row.uid,false,false);
4e17e6 1704
T 1705     // send request to server
1706     this.http_request('delete', '_uid='+a_uids.join(',')+'&_mbox='+escape(this.env.mailbox)+'&_from='+(this.env.action ? this.env.action : ''));
857a38 1707   }
S 1708     
1709     
1710   // delete selected messages from the current mailbox
1711   this.delete_messages = function()
1712     {
1713     // exit if no mailbox specified or if selection is empty
1714     if (!(this.selection.length || this.env.uid))
1715       return;
1716     // if there is a trash mailbox defined and we're not currently in it:
1717     if (this.env.trash_mailbox && String(this.env.mailbox).toLowerCase()!=String(this.env.trash_mailbox).toLowerCase())
1718       this.move_messages(this.env.trash_mailbox);
1719     // if there is a trash mailbox defined but we *are* in it:
1720     else if (this.env.trash_mailbox && String(this.env.mailbox).toLowerCase() == String(this.env.trash_mailbox).toLowerCase())
1721       this.permanently_remove_messages();
1722     // if there isn't a defined trash mailbox and the config is set to flag for deletion
1723     else if (!this.env.trash_mailbox && this.env.flag_for_deletion) {
1c5853 1724       flag = 'delete';
S 1725       this.mark_message(flag);
dab065 1726       if(this.env.action=="show"){
S 1727         this.command('nextmessage','',this);
1c5853 1728       } else if (this.selection.length == 1) {
dab065 1729         next_row = this.get_next_row();
S 1730         prev_row = this.get_prev_row();
1731         new_row = (next_row) ? next_row : prev_row;
1732         if (new_row) this.select_row(new_row.uid,false,false);
1733       }
857a38 1734     // if there isn't a defined trash mailbox and the config is set NOT to flag for deletion
S 1735     }else if (!this.env.trash_mailbox && !this.env.flag_for_deletion) {
1736       this.permanently_remove_messages();
1737     }
1738     return;
1739   };
4e17e6 1740
T 1741
1742   // set a specific flag to one or more messages
1743   this.mark_message = function(flag, uid)
1744     {
1745     var a_uids = new Array();
1746     
1747     if (uid)
1748       a_uids[0] = uid;
1749     else if (this.env.uid)
1750       a_uids[0] = this.env.uid;
1751     else
1752       {
1753       var id;
1754       for (var n=0; n<this.selection.length; n++)
1755         {
1756         id = this.selection[n];
1757         a_uids[a_uids.length] = id;
1758         }
1759       }
857a38 1760       switch (flag) {
S 1761         case 'read':
1762         case 'unread':
1763           this.toggle_read_status(flag,a_uids);
1764           break;
1765         case 'delete':
1766         case 'undelete':
1c5853 1767           this.toggle_delete_status(a_uids);
857a38 1768           break;
S 1769       }
1770     };
4e17e6 1771
857a38 1772   // set class to read/unread
S 1773   this.toggle_read_status = function(flag, a_uids) {
4e17e6 1774     // mark all message rows as read/unread
T 1775     var icn_src;
1776     for (var i=0; i<a_uids.length; i++)
1777       {
1778       uid = a_uids[i];
1779       if (this.message_rows[uid])
1780         {
1781         this.message_rows[uid].unread = (flag=='unread' ? true : false);
1782         
1783         if (this.message_rows[uid].classname.indexOf('unread')<0 && this.message_rows[uid].unread)
1784           {
1785           this.message_rows[uid].classname += ' unread';
fd660a 1786           this.set_classname(this.message_rows[uid].obj, 'unread', true);
T 1787
4e17e6 1788           if (this.env.unreadicon)
T 1789             icn_src = this.env.unreadicon;
1790           }
1791         else if (!this.message_rows[uid].unread)
1792           {
1793           this.message_rows[uid].classname = this.message_rows[uid].classname.replace(/\s*unread/, '');
fd660a 1794           this.set_classname(this.message_rows[uid].obj, 'unread', false);
4e17e6 1795
T 1796           if (this.message_rows[uid].replied && this.env.repliedicon)
1797             icn_src = this.env.repliedicon;
1798           else if (this.env.messageicon)
1799             icn_src = this.env.messageicon;
1800           }
1801
1802         if (this.message_rows[uid].icon && icn_src)
1803           this.message_rows[uid].icon.src = icn_src;
1804         }
1805       }
1c5853 1806       this.http_request('mark', '_uid='+a_uids.join(',')+'&_flag='+flag);
857a38 1807   }
S 1808   
1809   // mark all message rows as deleted/undeleted
1c5853 1810   this.toggle_delete_status = function(a_uids) {
857a38 1811     if (this.env.read_when_deleted) {
S 1812       this.toggle_read_status('read',a_uids);
1813     }
dab065 1814     // if deleting message from "view message" don't bother with delete icon
S 1815     if (this.env.action == "show")
1816       return false;
4e17e6 1817
1c5853 1818     if (a_uids.length==1){
717169 1819       if(this.message_rows[a_uids[0]].classname.indexOf('deleted') < 0 ){
1c5853 1820           this.flag_as_deleted(a_uids)
S 1821       } else {
1822           this.flag_as_undeleted(a_uids)
1823       }
1824       return true;
1825     }
1826     
1827     var all_deleted = true;
1828     
1829     for (var i=0; i<a_uids.length; i++) {
857a38 1830       uid = a_uids[i];
1c5853 1831       if (this.message_rows[uid]) {
S 1832         if (this.message_rows[uid].classname.indexOf('deleted')<0) {
1833           all_deleted = false;
1834           break;
857a38 1835         }
S 1836       }
1c5853 1837     }
S 1838     
1839     if (all_deleted)
1840       this.flag_as_undeleted(a_uids);
1841     else
1842       this.flag_as_deleted(a_uids);
1843     
1844     return true;
857a38 1845   }
4e17e6 1846
1c5853 1847   this.flag_as_undeleted = function(a_uids){
S 1848     // if deleting message from "view message" don't bother with delete icon
1849     if (this.env.action == "show")
1850       return false;
1851
1852     var icn_src;
1853       
1854     for (var i=0; i<a_uids.length; i++) {
1855       uid = a_uids[i];
1856       if (this.message_rows[uid]) {
1857         this.message_rows[uid].deleted = false;
1858         
1859         if (this.message_rows[uid].classname.indexOf('deleted') > 0) {
1860           this.message_rows[uid].classname = this.message_rows[uid].classname.replace(/\s*deleted/, '');
1861           this.set_classname(this.message_rows[uid].obj, 'deleted', false);
1862         }
1863         if (this.message_rows[uid].unread && this.env.unreadicon)
1864           icn_src = this.env.unreadicon;
1865         else if (this.message_rows[uid].replied && this.env.repliedicon)
1866           icn_src = this.env.repliedicon;
1867         else if (this.env.messageicon)
1868           icn_src = this.env.messageicon;
1869         if (this.message_rows[uid].icon && icn_src)
1870           this.message_rows[uid].icon.src = icn_src;
1871       }
1872     }
1873     this.http_request('mark', '_uid='+a_uids.join(',')+'&_flag=undelete');
1874     return true;
1875   }
1876   
1877   this.flag_as_deleted = function(a_uids) {
1878     // if deleting message from "view message" don't bother with delete icon
1879     if (this.env.action == "show")
1880       return false;
1881
1882     for (var i=0; i<a_uids.length; i++) {
1883       uid = a_uids[i];
1884       if (this.message_rows[uid]) {
1885         this.message_rows[uid].deleted = true;
1886         
1887         if (this.message_rows[uid].classname.indexOf('deleted')<0) {
1888           this.message_rows[uid].classname += ' deleted';
1889           this.set_classname(this.message_rows[uid].obj, 'deleted', true);
1890         }
1891         if (this.message_rows[uid].icon && this.env.deletedicon)
1892           this.message_rows[uid].icon.src = this.env.deletedicon;
1893       }
1894     }
1895     this.http_request('mark', '_uid='+a_uids.join(',')+'&_flag=delete');
1896     return true;  
1897   }
4e17e6 1898
T 1899   /*********************************************************/
1900   /*********        message compose methods        *********/
1901   /*********************************************************/
1cded8 1902   
T 1903   
977a29 1904   // checks the input fields before sending a message
T 1905   this.check_compose_input = function()
1906     {
1907     // check input fields
1908     var input_to = rcube_find_object('_to');
1909     var input_subject = rcube_find_object('_subject');
1910     var input_message = rcube_find_object('_message');
1911
1912     // check for empty recipient
1913     if (input_to && !rcube_check_email(input_to.value, true))
1914       {
1915       alert(this.get_label('norecipientwarning'));
1916       input_to.focus();
1917       return false;
1918       }
1919
1920     // display localized warning for missing subject
1921     if (input_subject && input_subject.value == '')
1922       {
1923       var subject = prompt(this.get_label('nosubjectwarning'), this.get_label('nosubject'));
1924
1925       // user hit cancel, so don't send
1926       if (!subject && subject !== '')
1927         {
1928         input_subject.focus();
1929         return false;
1930         }
1931       else
1932         {
1933         input_subject.value = subject ? subject : this.get_label('nosubject');            
1934         }
1935       }
1936
1937     // check for empty body
1938     if (input_message.value=='')
1939       {
1940       if (!confirm(this.get_label('nobodywarning')))
1941         {
1942         input_message.focus();
1943         return false;
1944         }
1945       }
1946
1947     return true;
1948     };
1949     
1950     
1951   this.compose_field_hash = function()
1952     {
1953     // check input fields
1954     var input_to = rcube_find_object('_to');
1955     var input_cc = rcube_find_object('_to');
1956     var input_bcc = rcube_find_object('_to');
1957     var input_subject = rcube_find_object('_subject');
1958     var input_message = rcube_find_object('_message');
1959     
1960     var str = '';
1961     if (input_to && input_to.value)
1962       str += input_to.value+':';
1963     if (input_cc && input_cc.value)
1964       str += input_cc.value+':';
1965     if (input_bcc && input_bcc.value)
1966       str += input_bcc.value+':';
1967     if (input_subject && input_subject.value)
1968       str += input_subject.value+':';
1969     if (input_message && input_message.value)
1970       str += input_message.value;
1971
1972     return str;
1973     };
1974     
1975   
1cded8 1976   this.change_identity = function(obj)
T 1977     {
1978     if (!obj || !obj.options)
1979       return false;
1980
1981     var id = obj.options[obj.selectedIndex].value;
1982     var input_message = rcube_find_object('_message');
1983     var message = input_message ? input_message.value : '';
749b07 1984     var sig, p;
1cded8 1985
1966c5 1986     if (!this.env.identity)
S 1987       this.env.identity = id
1988
1cded8 1989     // remove the 'old' signature
T 1990     if (this.env.identity && this.env.signatures && this.env.signatures[this.env.identity])
1991       {
749b07 1992       sig = this.env.signatures[this.env.identity];
1966c5 1993       if (sig.indexOf('--')!=0)
S 1994         sig = '--\n'+sig;
749b07 1995
T 1996       p = message.lastIndexOf(sig);
1997       if (p>=0)
1cded8 1998         message = message.substring(0, p-1) + message.substring(p+sig.length, message.length);
T 1999       }
2000
2001     // add the new signature string
2002     if (this.env.signatures && this.env.signatures[id])
2003       {
749b07 2004       sig = this.env.signatures[id];
1966c5 2005       if (sig.indexOf('--')!=0)
S 2006         sig = '--\n'+sig;
1cded8 2007       message += '\n'+sig;
T 2008       }
2009
749b07 2010     if (input_message)
1cded8 2011       input_message.value = message;
T 2012       
2013     this.env.identity = id;
1c5853 2014     return true;
1cded8 2015     };
4e17e6 2016
T 2017
2018   this.show_attachment_form = function(a)
2019     {
2020     if (!this.gui_objects.uploadbox)
2021       return false;
2022       
2023     var elm, list;
2024     if (elm = this.gui_objects.uploadbox)
2025       {
2026       if (a &&  (list = this.gui_objects.attachmentlist))
2027         {
2028         var pos = rcube_get_object_pos(list);
2029         var left = pos.x;
2030         var top = pos.y + list.offsetHeight + 10;
2031       
2032         elm.style.top = top+'px';
2033         elm.style.left = left+'px';
2034         }
2035       
2036       elm.style.visibility = a ? 'visible' : 'hidden';
2037       }
2038       
2039     // clear upload form
2040     if (!a && this.gui_objects.attachmentform && this.gui_objects.attachmentform!=this.gui_objects.messageform)
2041       this.gui_objects.attachmentform.reset();
1c5853 2042     
S 2043     return true;  
4e17e6 2044     };
T 2045
2046
2047   // upload attachment file
2048   this.upload_file = function(form)
2049     {
2050     if (!form)
2051       return false;
2052       
2053     // get file input fields
2054     var send = false;
2055     for (var n=0; n<form.elements.length; n++)
2056       if (form.elements[n].type=='file' && form.elements[n].value)
2057         {
2058         send = true;
2059         break;
2060         }
2061     
2062     // create hidden iframe and post upload form
2063     if (send)
2064       {
2065       var ts = new Date().getTime();
2066       var frame_name = 'rcmupload'+ts;
2067
2068       // have to do it this way for IE
2069       // otherwise the form will be posted to a new window
2070       if(document.all && !window.opera)
2071         {
2072         var html = '<iframe name="'+frame_name+'" src="program/blank.gif" style="width:0;height:0;visibility:hidden;"></iframe>';
2073         document.body.insertAdjacentHTML('BeforeEnd',html);
2074         }
2075       else  // for standards-compilant browsers
2076         {
2077         var frame = document.createElement('IFRAME');
2078         frame.name = frame_name;
2079         frame.width = 10;
2080         frame.height = 10;
2081         frame.style.visibility = 'hidden';
2082         document.body.appendChild(frame);
2083         }
2084
2085       form.target = frame_name;
2086       form.action = this.env.comm_path+'&_action=upload';
2087       form.setAttribute('enctype', 'multipart/form-data');
2088       form.submit();
2089       }
2090     
2091     // set reference to the form object
2092     this.gui_objects.attachmentform = form;
1c5853 2093     return true;
4e17e6 2094     };
T 2095
2096
2097   // add file name to attachment list
2098   // called from upload page
a894ba 2099   this.add2attachment_list = function(name,content)
4e17e6 2100     {
T 2101     if (!this.gui_objects.attachmentlist)
2102       return false;
2103       
2104     var li = document.createElement('LI');
a894ba 2105     li.id = name;
S 2106     li.innerHTML = content;
4e17e6 2107     this.gui_objects.attachmentlist.appendChild(li);
1c5853 2108     return true;
4e17e6 2109     };
T 2110
a894ba 2111   this.remove_from_attachment_list = function(name)
S 2112     {
2113     if (!this.gui_objects.attachmentlist)
2114       return false;
2115
2116     var list = this.gui_objects.attachmentlist.getElementsByTagName("li");
2117     for (i=0;i<list.length;i++)
2118       if (list[i].id == name)
2119     this.gui_objects.attachmentlist.removeChild(list[i]);
2120     }
2121
2122   this.remove_attachment = function(name)
2123     {
2124     if (name)
2125       this.http_request('remove-attachment', '_filename='+escape(name));
2126
2127     return true;
2128     }
4e17e6 2129
T 2130   // send remote request to add a new contact
2131   this.add_contact = function(value)
2132     {
2133     if (value)
2134       this.http_request('addcontact', '_address='+value);
1c5853 2135     
S 2136     return true;
4e17e6 2137     };
T 2138
4647e1 2139   // send remote request to search mail
T 2140   this.qsearch = function(value, mbox)
2141     {
2142     if (value && mbox)
2143       {
2144       this.clear_message_list();
2145       this.set_busy(true, 'searching');
2146       this.http_request('search', '_search='+value+'&_mbox='+mbox, true);
2147       }
1c5853 2148     return true;
4647e1 2149     };
T 2150
2151   // reset quick-search form
2152   this.reset_qsearch = function()
2153     {
2154     if (this.gui_objects.qsearchbox)
2155       this.gui_objects.qsearchbox.value = '';
2156       
2157     this.env.search_request = null;
1c5853 2158     return true;
4647e1 2159     };
T 2160     
4e17e6 2161
T 2162   /*********************************************************/
2163   /*********     keyboard live-search methods      *********/
2164   /*********************************************************/
2165
2166
2167   // handler for keyboard events on address-fields
2168   this.ksearch_keypress = function(e, obj)
2169     {
2170     if (typeof(this.env.contacts)!='object' || !this.env.contacts.length)
2171       return true;
2172
2173     if (this.ksearch_timer)
2174       clearTimeout(this.ksearch_timer);
2175
2176     if (!e)
2177       e = window.event;
2178       
2179     var highlight;
2180     var key = e.keyCode ? e.keyCode : e.which;
2181
2182     switch (key)
2183       {
2184       case 38:  // key up
2185       case 40:  // key down
2186         if (!this.ksearch_pane)
2187           break;
2188           
2189         var dir = key==38 ? 1 : 0;
2190         var next;
2191         
2192         highlight = document.getElementById('rcmksearchSelected');
2193         if (!highlight)
2194           highlight = this.ksearch_pane.ul.firstChild;
2195         
2196         if (highlight && (next = dir ? highlight.previousSibling : highlight.nextSibling))
2197           {
2198           highlight.removeAttribute('id');
2199           //highlight.removeAttribute('class');
2200           this.set_classname(highlight, 'selected', false);
952860 2201           this.set_classname(highlight, 'unfocused', false);
4e17e6 2202           }
T 2203
2204         if (next)
2205           {
2206           next.setAttribute('id', 'rcmksearchSelected');
2207           this.set_classname(next, 'selected', true);
2208           this.ksearch_selected = next._rcm_id;
2209           }
2210
2211         if (e.preventDefault)
2212           e.preventDefault();
2213         return false;
2214
2215       case 9:  // tab
2216         if(e.shiftKey)
2217           break;
2218
2219       case 13:  // enter     
2220         if (this.ksearch_selected===null || !this.ksearch_input || !this.ksearch_value)
2221           break;
2222
2223         // get cursor pos
2224         var inp_value = this.ksearch_input.value.toLowerCase();
2225         var cpos = this.get_caret_pos(this.ksearch_input);
2226         var p = inp_value.lastIndexOf(this.ksearch_value, cpos);
2227         
2228         // replace search string with full address
2229         var pre = this.ksearch_input.value.substring(0, p);
2230         var end = this.ksearch_input.value.substring(p+this.ksearch_value.length, this.ksearch_input.value.length);
2231         var insert = this.env.contacts[this.ksearch_selected]+', ';
2232         this.ksearch_input.value = pre + insert + end;
2233         
2234         //this.ksearch_input.value = this.ksearch_input.value.substring(0, p)+insert;
2235         
2236         // set caret to insert pos
2237         cpos = p+insert.length;
2238         if (this.ksearch_input.setSelectionRange)
2239           this.ksearch_input.setSelectionRange(cpos, cpos);
2240         
2241         // hide ksearch pane
2242         this.ksearch_hide();
2243       
2244         if (e.preventDefault)
2245           e.preventDefault();
2246         return false;
2247
2248       case 27:  // escape
2249         this.ksearch_hide();
2250         break;
2251
2252       }
2253
2254     // start timer
2255     this.ksearch_timer = setTimeout(this.ref+'.ksearch_get_results()', 200);      
2256     this.ksearch_input = obj;
2257     
2258     return true;
2259     };
2260
2261
2262   // address search processor
2263   this.ksearch_get_results = function()
2264     {
2265     var inp_value = this.ksearch_input ? this.ksearch_input.value : null;
2266     if (inp_value===null)
2267       return;
2268
2269     // get string from current cursor pos to last comma
2270     var cpos = this.get_caret_pos(this.ksearch_input);
2271     var p = inp_value.lastIndexOf(',', cpos-1);
2272     var q = inp_value.substring(p+1, cpos);
2273
2274     // trim query string
2275     q = q.replace(/(^\s+|\s+$)/g, '').toLowerCase();
2276
2277     if (!q.length || q==this.ksearch_value)
2278       {
2279       if (!q.length && this.ksearch_pane && this.ksearch_pane.visible)
2280         this.ksearch_pane.show(0);
2281
2282       return;
2283       }
2284
2285     this.ksearch_value = q;
2286     
2287     // start searching the contact list
2288     var a_results = new Array();
2289     var a_result_ids = new Array();
2290     var c=0;
2291     for (var i=0; i<this.env.contacts.length; i++)
2292       {
2293       if (this.env.contacts[i].toLowerCase().indexOf(q)>=0)
2294         {
2295         a_results[c] = this.env.contacts[i];
2296         a_result_ids[c++] = i;
2297         
2298         if (c==15)  // limit search results
2299           break;
2300         }
2301       }
2302
2303     // display search results
2304     if (c && a_results.length)
2305       {
2306       var p, ul, li;
2307       
2308       // create results pane if not present
2309       if (!this.ksearch_pane)
2310         {
2311         ul = document.createElement('UL');
2312         this.ksearch_pane = new rcube_layer('rcmKSearchpane', {vis:0, zindex:30000});
2313         this.ksearch_pane.elm.appendChild(ul);
2314         this.ksearch_pane.ul = ul;
2315         }
2316       else
2317         ul = this.ksearch_pane.ul;
2318
2319       // remove all search results
2320       ul.innerHTML = '';
2321             
2322       // add each result line to list
2323       for (i=0; i<a_results.length; i++)
2324         {
2325         li = document.createElement('LI');
2326         li.innerHTML = a_results[i].replace(/</, '&lt;').replace(/>/, '&gt;');
2327         li._rcm_id = a_result_ids[i];
2328         ul.appendChild(li);
2329         }
2330
2331       // check if last selected item is still in result list
2332       if (this.ksearch_selected!==null)
2333         {
2334         p = find_in_array(this.ksearch_selected, a_result_ids);
2335         if (p>=0 && ul.childNodes)
2336           {
2337           ul.childNodes[p].setAttribute('id', 'rcmksearchSelected');
2338           this.set_classname(ul.childNodes[p], 'selected', true);
2339           }
2340         else
2341           this.ksearch_selected = null;
2342         }
2343       
2344       // if no item selected, select the first one
2345       if (this.ksearch_selected===null)
2346         {
2347         ul.firstChild.setAttribute('id', 'rcmksearchSelected');
2348         this.set_classname(ul.firstChild, 'selected', true);
2349         this.ksearch_selected = a_result_ids[0];
2350         }
2351
2352       // resize the containing layer to fit the list
2353       //this.ksearch_pane.resize(ul.offsetWidth, ul.offsetHeight);
2354     
2355       // move the results pane right under the input box and make it visible
2356       var pos = rcube_get_object_pos(this.ksearch_input);
2357       this.ksearch_pane.move(pos.x, pos.y+this.ksearch_input.offsetHeight);
2358       this.ksearch_pane.show(1); 
2359       }
2360     // hide results pane
2361     else
2362       this.ksearch_hide();
2363     };
2364
2365
2366   this.ksearch_blur = function(e, obj)
2367     {
2368     if (this.ksearch_timer)
2369       clearTimeout(this.ksearch_timer);
2370
2371     this.ksearch_value = '';      
2372     this.ksearch_input = null;
2373     
2374     this.ksearch_hide();
2375     };
2376
2377
2378   this.ksearch_hide = function()
2379     {
2380     this.ksearch_selected = null;
2381     
2382     if (this.ksearch_pane)
2383       this.ksearch_pane.show(0);    
2384     };
2385
2386
2387
2388   /*********************************************************/
2389   /*********         address book methods          *********/
2390   /*********************************************************/
2391
2392
2393   this.list_contacts = function(page)
2394     {
2395     var add_url = '';
2396     var target = window;
2397     
2398     if (page && this.current_page==page)
2399       return false;
2400
2401     // load contacts remotely
2402     if (this.gui_objects.contactslist)
2403       {
2404       this.list_contacts_remote(page);
2405       return;
2406       }
2407
2408     if (this.env.contentframe && window.frames && window.frames[this.env.contentframe])
2409       {
2410       target = window.frames[this.env.contentframe];
2411       add_url = '&_framed=1';
2412       }
2413
2414     this.set_busy(true, 'loading');
2415     location.href = this.env.comm_path+(page ? '&_page='+page : '')+add_url;
2416     };
2417
2418
2419   // send remote request to load contacts list
2420   this.list_contacts_remote = function(page)
2421     {
2422     // clear list
2423     var table = this.gui_objects.contactslist;
2424     var tbody = document.createElement('TBODY');
2425     table.insertBefore(tbody, table.tBodies[0]);
2426     table.tBodies[1].style.display = 'none';
2427     
2428     this.contact_rows = new Array();
2429     this.list_rows = this.contact_rows;
2430
2431     // send request to server
2432     var url = page ? '&_page='+page : '';
2433     this.set_busy(true, 'loading');
ecf759 2434     this.http_request('list', url, true);
4e17e6 2435     };
T 2436
2437
2438   // load contact record
2439   this.load_contact = function(cid, action, framed)
2440     {
2441     var add_url = '';
2442     var target = window;
2443     if (this.env.contentframe && window.frames && window.frames[this.env.contentframe])
2444       {
2445       add_url = '&_framed=1';
2446       target = window.frames[this.env.contentframe];
2447       document.getElementById(this.env.contentframe).style.visibility = 'inherit';
2448       }
2449     else if (framed)
2450       return false;
2451       
2452     //if (this.env.framed && add_url=='')
5e3512 2453     
4e17e6 2454     //  add_url = '&_framed=1';
T 2455     
2456     if (action && (cid || action=='add'))
2457       {
2458       this.set_busy(true);
2459       target.location.href = this.env.comm_path+'&_action='+action+'&_cid='+cid+add_url;
2460       }
1c5853 2461     return true;
4e17e6 2462     };
T 2463
2464
2465   this.delete_contacts = function()
2466     {
2467     // exit if no mailbox specified or if selection is empty
5e3512 2468     if (!(this.selection.length || this.env.cid) || !confirm(this.get_label('deletecontactconfirm')))
4e17e6 2469       return;
5e3512 2470       
4e17e6 2471     var a_cids = new Array();
T 2472
2473     if (this.env.cid)
2474       a_cids[a_cids.length] = this.env.cid;
2475     else
2476       {
2477       var id;
2478       for (var n=0; n<this.selection.length; n++)
2479         {
2480         id = this.selection[n];
2481         a_cids[a_cids.length] = id;
2482       
2483         // 'remove' row from list (just hide it)
2484         if (this.contact_rows[id].obj)
2485           this.contact_rows[id].obj.style.display = 'none';
2486         }
2487
2488       // hide content frame if we delete the currently displayed contact
2489       if (this.selection.length==1 && this.env.contentframe)
2490         {
2491         var elm = document.getElementById(this.env.contentframe);
2492         elm.style.visibility = 'hidden';
2493         }
2494       }
2495
2496     // send request to server
2497     this.http_request('delete', '_cid='+a_cids.join(',')+'&_from='+(this.env.action ? this.env.action : ''));
1c5853 2498     return true;
4e17e6 2499     };
T 2500
2501
2502   // update a contact record in the list
2503   this.update_contact_row = function(cid, cols_arr)
2504     {
2505     if (!this.contact_rows[cid] || !this.contact_rows[cid].obj)
2506       return false;
2507       
2508     var row = this.contact_rows[cid].obj;
1c5853 2509     for (var c=0; c<cols_arr.length; c++){
4e17e6 2510       if (row.cells[c])
T 2511         row.cells[c].innerHTML = cols_arr[c];
1c5853 2512     }
S 2513     return true;
4e17e6 2514     };
T 2515   
d1d2c4 2516   
S 2517   // load ldap search form
2518   this.ldappublicsearch = function(action)
2519     {
2520     var add_url = '';
2521     var target = window;
2522     if (this.env.contentframe && window.frames && window.frames[this.env.contentframe])
2523       {
2524       add_url = '&_framed=1';
2525       target = window.frames[this.env.contentframe];
2526       document.getElementById(this.env.contentframe).style.visibility = 'inherit';
2527       }
2528     else
2529       return false; 
2530
2531
2532     if (action == 'ldappublicsearch')
2533       target.location.href = this.env.comm_path+'&_action='+action+add_url;
1c5853 2534       
S 2535     return true;
d1d2c4 2536     };
S 2537  
2538   // add ldap contacts to address book
2539   this.add_ldap_contacts = function()
2540     {
2541     if (window.frames[this.env.contentframe].rcmail)
2542       {
2543       var frame = window.frames[this.env.contentframe];
2544
2545       // build the url
2546       var url    = '&_framed=1';
2547       var emails = '&_emails=';
2548       var names  = '&_names=';
2549       var end    = '';
2550       for (var n=0; n<frame.rcmail.selection.length; n++)
2551         {
2552         end = n < frame.rcmail.selection.length - 1 ? ',' : '';
2553         emails += frame.rcmail.ldap_contact_rows[frame.rcmail.selection[n]].obj.cells[1].innerHTML + end;
2554         names  += frame.rcmail.ldap_contact_rows[frame.rcmail.selection[n]].obj.cells[0].innerHTML + end;
2555         }
2556        
2557       frame.location.href = this.env.comm_path + '&_action=save&_framed=1' + emails + names;
2558       }
2559     return false;
2560     }
2561   
4e17e6 2562
T 2563
2564   /*********************************************************/
2565   /*********        user settings methods          *********/
2566   /*********************************************************/
2567
2568
2569   // load contact record
2570   this.load_identity = function(id, action)
2571     {
2572     if (action=='edit-identity' && (!id || id==this.env.iid))
1c5853 2573       return false;
4e17e6 2574
T 2575     var add_url = '';
2576     var target = window;
2577     if (this.env.contentframe && window.frames && window.frames[this.env.contentframe])
2578       {
2579       add_url = '&_framed=1';
2580       target = window.frames[this.env.contentframe];
2581       document.getElementById(this.env.contentframe).style.visibility = 'inherit';
2582       }
2583
2584     if (action && (id || action=='add-identity'))
2585       {
2586       this.set_busy(true);
2587       target.location.href = this.env.comm_path+'&_action='+action+'&_iid='+id+add_url;
2588       }
1c5853 2589     return true;
4e17e6 2590     };
T 2591
2592
2593
2594   this.delete_identity = function(id)
2595     {
2596     // exit if no mailbox specified or if selection is empty
2597     if (!(this.selection.length || this.env.iid))
2598       return;
2599     
2600     if (!id)
2601       id = this.env.iid ? this.env.iid : this.selection[0];
2602
2603 /*
2604     // 'remove' row from list (just hide it)
2605     if (this.identity_rows && this.identity_rows[id].obj)
2606       {
2607       this.clear_selection();
2608       this.identity_rows[id].obj.style.display = 'none';
2609       }
2610 */
2611
2612     // if (this.env.framed && id)
2613       this.set_busy(true);
1c5853 2614       location.href = this.env.comm_path+'&_action=delete-identity&_iid='+id;     
4e17e6 2615     // else if (id)
T 2616     //  this.http_request('delete-identity', '_iid='+id);
1c5853 2617     return true;
4e17e6 2618     };
T 2619
2620
2621   this.create_folder = function(name)
2622     {
2623     var form;
2624     if ((form = this.gui_objects.editform) && form.elements['_folder_name'])
2625       name = form.elements['_folder_name'].value;
2626
2627     if (name)
ecf759 2628       this.http_request('create-folder', '_name='+escape(name), true);
4e17e6 2629     else if (form.elements['_folder_name'])
T 2630       form.elements['_folder_name'].focus();
2631     };
2632
c8c1e0 2633   this.rename_folder = function(props)
S 2634     {
2635     var form;
2636     if ((form = this.gui_objects.editform) && form.elements['_folder_oldname'] && form.elements['_folder_newname'])
2637       {    
2638       oldname = form.elements['_folder_oldname'].value;
2639       newname = form.elements['_folder_newname'].value;
2640       }
2641
2642     if (oldname && newname)
2643       this.http_request('rename-folder', '_folder_oldname='+escape(oldname)+'&_folder_newname='+escape(newname));
a894ba 2644
c8c1e0 2645     };
S 2646
4e17e6 2647
T 2648   this.delete_folder = function(folder)
2649     {
2650     if (folder)
2651       {
2652       this.http_request('delete-folder', '_mboxes='+escape(folder));
2653       }
1cded8 2654     };
T 2655
2656
2657   this.remove_folder_row = function(folder)
2658     {
2659     for (var id in this.env.subscriptionrows)
2660       if (this.env.subscriptionrows[id]==folder)
2661         break;
2662
2663     var row;
2664     if (id && (row = document.getElementById(id)))
2665       row.style.display = 'none';    
c8c1e0 2666
S 2667     // remove folder from rename-folder list
2668     var form;
2669     if ((form = this.gui_objects.editform) && form.elements['_folder_oldname'])
2670       {
2671       for (var i=0;i<form.elements['_folder_oldname'].options.length;i++)
2672         {
2673         if (form.elements['_folder_oldname'].options[i].value == folder) 
2674       {
2675           form.elements['_folder_oldname'].options[i] = null;
2676       break;
2677           }
2678         }
2679       }
a894ba 2680       form.elements['_folder_newname'].value='';
4e17e6 2681     };
T 2682
2683
2684   this.subscribe_folder = function(folder)
2685     {
2686     var form;
2687     if ((form = this.gui_objects.editform) && form.elements['_unsubscribed'])
2688       this.change_subscription('_unsubscribed', '_subscribed', 'subscribe');
2689     else if (folder)
2690       this.http_request('subscribe', '_mboxes='+escape(folder));
2691     };
2692
2693
2694   this.unsubscribe_folder = function(folder)
2695     {
2696     var form;
2697     if ((form = this.gui_objects.editform) && form.elements['_subscribed'])
2698       this.change_subscription('_subscribed', '_unsubscribed', 'unsubscribe');
2699     else if (folder)
2700       this.http_request('unsubscribe', '_mboxes='+escape(folder));
2701     };
2702     
2703
2704   this.change_subscription = function(from, to, action)
2705     {
2706     var form;
2707     if (form = this.gui_objects.editform)
2708       {
2709       var a_folders = new Array();
2710       var list_from = form.elements[from];
2711
2712       for (var i=0; list_from && i<list_from.options.length; i++)
2713         {
2714         if (list_from.options[i] && list_from.options[i].selected)
2715           {
2716           a_folders[a_folders.length] = list_from.options[i].value;
2717           list_from[i] = null;
2718           i--;
2719           }
2720         }
2721
2722       // yes, we have some folders selected
2723       if (a_folders.length)
2724         {
2725         var list_to = form.elements[to];
2726         var index;
2727         
2728         for (var n=0; n<a_folders.length; n++)
2729           {
2730           index = list_to.options.length;
2731           list_to[index] = new Option(a_folders[n]);
2732           }
2733           
2734         this.http_request(action, '_mboxes='+escape(a_folders.join(',')));
2735         }
2736       }
2737       
2738     };
2739
2740
2741    // add a new folder to the subscription list by cloning a folder row
2742    this.add_folder_row = function(name)
2743      {
a894ba 2744      name = name.replace('\\',"");
4e17e6 2745      if (!this.gui_objects.subscriptionlist)
T 2746        return false;
2747
2748      var tbody = this.gui_objects.subscriptionlist.tBodies[0];
2749      var id = tbody.childNodes.length+1;
a894ba 2750    
S 2751      if (!tbody.rows[0]) 
2752        {
2753        // Refresh to create the first table row
2754        location.href = this.env.comm_path+'&_action=folders';
2755        }
2756      else
2757        {
2758        // clone a table row if there are existing rows
2759        var row = this.clone_table_row(tbody.rows[0]);
2760        row.id = 'rcmrow'+id;
2761        tbody.appendChild(row);
2762        }
4e17e6 2763
T 2764      // add to folder/row-ID map
2765      this.env.subscriptionrows[row.id] = name;
2766
2767      // set folder name
2768      row.cells[0].innerHTML = name;
2769      if (row.cells[1].firstChild.tagName=='INPUT')
2770        {
2771        row.cells[1].firstChild.value = name;
2772        row.cells[1].firstChild.checked = true;
2773        }
2774      if (row.cells[2].firstChild.tagName=='A')
a894ba 2775        row.cells[2].firstChild.onclick = new Function(this.ref+".command('delete-folder','"+name.replace('\'','\\\'')+"')");
14eafe 2776
c8c1e0 2777      var form;
S 2778      if ((form = this.gui_objects.editform) && form.elements['_folder_name'])
2779        form.elements['_folder_name'].value = '';
2780  
2781      // add new folder to rename-folder list
a894ba 2782      form.elements['_folder_oldname'].options[form.elements['_folder_oldname'].options.length] = new Option(name,name);
c8c1e0 2783
4e17e6 2784      };
T 2785
2786
2787   // duplicate a specific table row
2788   this.clone_table_row = function(row)
2789     {
2790     var cell, td;
2791     var new_row = document.createElement('TR');
2792     for(var n=0; n<row.childNodes.length; n++)
2793       {
2794       cell = row.childNodes[n];
2795       td = document.createElement('TD');
2796
2797       if (cell.className)
2798         td.className = cell.className;
2799       if (cell.align)
2800         td.setAttribute('align', cell.align);
2801         
2802       td.innerHTML = cell.innerHTML;
2803       new_row.appendChild(td);
2804       }
2805     
2806     return new_row;
2807     };
2808
2809
2810   /*********************************************************/
2811   /*********           GUI functionality           *********/
2812   /*********************************************************/
2813
2814
2815   // eable/disable buttons for page shifting
2816   this.set_page_buttons = function()
2817     {
2818     this.enable_command('nextpage', (this.env.pagecount > this.env.current_page));
2819     this.enable_command('previouspage', (this.env.current_page > 1));
2820     }
2821
2822
2823   // set button to a specific state
2824   this.set_button = function(command, state)
2825     {
2826     var a_buttons = this.buttons[command];
2827     var button, obj;
2828
2829     if(!a_buttons || !a_buttons.length)
2830       return;
2831
2832     for(var n=0; n<a_buttons.length; n++)
2833       {
2834       button = a_buttons[n];
2835       obj = document.getElementById(button.id);
2836
2837       // get default/passive setting of the button
2838       if (obj && button.type=='image' && !button.status)
2839         button.pas = obj._original_src ? obj._original_src : obj.src;
2840       else if (obj && !button.status)
2841         button.pas = String(obj.className);
2842
2843       // set image according to button state
2844       if (obj && button.type=='image' && button[state])
2845         {
2846         button.status = state;        
2847         obj.src = button[state];
2848         }
2849       // set class name according to button state
2850       else if (obj && typeof(button[state])!='undefined')
2851         {
2852         button.status = state;        
2853         obj.className = button[state];        
2854         }
2855       // disable/enable input buttons
2856       if (obj && button.type=='input')
2857         {
2858         button.status = state;
2859         obj.disabled = !state;
2860         }
2861       }
2862     };
2863
2864
2865   // mouse over button
2866   this.button_over = function(command, id)
2867     {
2868     var a_buttons = this.buttons[command];
2869     var button, img;
2870
2871     if(!a_buttons || !a_buttons.length)
2872       return;
2873
2874     for(var n=0; n<a_buttons.length; n++)
2875       {
2876       button = a_buttons[n];
2877       if(button.id==id && button.status=='act')
2878         {
2879         img = document.getElementById(button.id);
2880         if (img && button.over)
2881           img.src = button.over;
2882         }
2883       }
2884     };
2885
c8c1e0 2886   // mouse down on button
S 2887   this.button_sel = function(command, id)
2888     {
2889     var a_buttons = this.buttons[command];
2890     var button, img;
2891
2892     if(!a_buttons || !a_buttons.length)
2893       return;
2894
2895     for(var n=0; n<a_buttons.length; n++)
2896       {
2897       button = a_buttons[n];
2898       if(button.id==id && button.status=='act')
2899         {
2900         img = document.getElementById(button.id);
2901         if (img && button.sel)
2902           img.src = button.sel;
2903         }
2904       }
2905     };
4e17e6 2906
T 2907   // mouse out of button
2908   this.button_out = function(command, id)
2909     {
2910     var a_buttons = this.buttons[command];
2911     var button, img;
2912
2913     if(!a_buttons || !a_buttons.length)
2914       return;
2915
2916     for(var n=0; n<a_buttons.length; n++)
2917       {
2918       button = a_buttons[n];
2919       if(button.id==id && button.status=='act')
2920         {
2921         img = document.getElementById(button.id);
2922         if (img && button.act)
2923           img.src = button.act;
2924         }
2925       }
2926     };
2927
2928
2929   // set/unset a specific class name
2930   this.set_classname = function(obj, classname, set)
2931     {
2932     var reg = new RegExp('\s*'+classname, 'i');
2933     if (!set && obj.className.match(reg))
2934       obj.className = obj.className.replace(reg, '');
2935     else if (set && !obj.className.match(reg))
2936       obj.className += ' '+classname;
2937     };
2938
2939
2940   // display a specific alttext
2941   this.alttext = function(text)
2942     {
2943     
2944     };
2945
2946
2947   // display a system message
2948   this.display_message = function(msg, type, hold)
2949     {
2950     if (!this.loaded)  // save message in order to display after page loaded
2951       {
2952       this.pending_message = new Array(msg, type);
2953       return true;
2954       }
2955     
2956     if (!this.gui_objects.message)
2957       return false;
2958       
2959     if (this.message_timer)
2960       clearTimeout(this.message_timer);
2961     
2962     var cont = msg;
2963     if (type)
2964       cont = '<div class="'+type+'">'+cont+'</div>';
a95e0e 2965
T 2966     this.gui_objects.message._rcube = this;
4e17e6 2967     this.gui_objects.message.innerHTML = cont;
T 2968     this.gui_objects.message.style.display = 'block';
a95e0e 2969     
T 2970     if (type!='loading')
2971       this.gui_objects.message.onmousedown = function(){ this._rcube.hide_message(); return true; };
4e17e6 2972     
T 2973     if (!hold)
2974       this.message_timer = setTimeout(this.ref+'.hide_message()', this.message_time);
2975     };
2976
2977
2978   // make a message row disapear
2979   this.hide_message = function()
2980     {
2981     if (this.gui_objects.message)
a95e0e 2982       {
4e17e6 2983       this.gui_objects.message.style.display = 'none';
a95e0e 2984       this.gui_objects.message.onmousedown = null;
T 2985       }
4e17e6 2986     };
T 2987
2988
2989   // mark a mailbox as selected and set environment variable
2990   this.select_mailbox = function(mbox)
2991     {
2992     if (this.gui_objects.mailboxlist)
2993       {
2994       var item, reg, text_obj;
6a35c8 2995       var s_current = this.env.mailbox.toLowerCase().replace(this.mbox_expression, '');
4e17e6 2996       var s_mbox = String(mbox).toLowerCase().replace(this.mbox_expression, '');
T 2997       var s_current = this.env.mailbox.toLowerCase().replace(this.mbox_expression, '');
597170 2998       
6a35c8 2999       var current_li = document.getElementById('rcmbx'+s_current);
T 3000       var mbox_li = document.getElementById('rcmbx'+s_mbox);
3001       
952860 3002       if (current_li) {
6a35c8 3003         this.set_classname(current_li, 'selected', false);
952860 3004         this.set_classname(current_li, 'unfocused', false);
S 3005         }
6a35c8 3006       if (mbox_li)
T 3007         this.set_classname(mbox_li, 'selected', true);
4e17e6 3008       }
T 3009     
3010     this.env.mailbox = mbox;
3011     };
3012
3013
3014   // create a table row in the message list
15a9d1 3015   this.add_message_row = function(uid, cols, flags, attachment, attop)
4e17e6 3016     {
T 3017     if (!this.gui_objects.messagelist || !this.gui_objects.messagelist.tBodies[0])
3018       return false;
3019     
3020     var tbody = this.gui_objects.messagelist.tBodies[0];
3021     var rowcount = tbody.rows.length;
3022     var even = rowcount%2;
3023     
857a38 3024     this.env.messages[uid] = {deleted:flags.deleted?1:0,
S 3025                               replied:flags.replied?1:0,
4e17e6 3026                               unread:flags.unread?1:0};
T 3027     
3028     var row = document.createElement('TR');
3029     row.id = 'rcmrow'+uid;
15a9d1 3030     row.className = 'message '+(even ? 'even' : 'odd')+(flags.unread ? ' unread' : '')+(flags.deleted ? ' deleted' : '');
T 3031     
4e17e6 3032     if (this.in_selection(uid))
T 3033       row.className += ' selected';
3034
857a38 3035     var icon = flags.deleted && this.env.deletedicon ? this.env.deletedicon:
S 3036                (flags.unread && this.env.unreadicon ? this.env.unreadicon :
3037                (flags.replied && this.env.repliedicon ? this.env.repliedicon : this.env.messageicon));
4e17e6 3038
T 3039     var col = document.createElement('TD');
3040     col.className = 'icon';
3041     col.innerHTML = icon ? '<img src="'+icon+'" alt="" border="0" />' : '';
3042     row.appendChild(col);
3043
3044     // add each submitted col
3045     for (var c in cols)
3046       {
3047       col = document.createElement('TD');
3048       col.className = String(c).toLowerCase();
3049       col.innerHTML = cols[c];
3050       row.appendChild(col);
3051       }
3052
3053     col = document.createElement('TD');
3054     col.className = 'icon';
3055     col.innerHTML = attachment && this.env.attachmenticon ? '<img src="'+this.env.attachmenticon+'" alt="" border="0" />' : '';
3056     row.appendChild(col);
3057     
15a9d1 3058     if (attop && tbody.rows.length)
T 3059       tbody.insertBefore(row, tbody.firstChild);
3060     else
3061       tbody.appendChild(row);
3062       
4e17e6 3063     this.init_message_row(row);
T 3064     };
3065
3066
3067   // replace content of row count display
3068   this.set_rowcount = function(text)
3069     {
3070     if (this.gui_objects.countdisplay)
3071       this.gui_objects.countdisplay.innerHTML = text;
3072
3073     // update page navigation buttons
3074     this.set_page_buttons();
3075     };
3076
58e360 3077   // replace content of quota display
T 3078    this.set_quota = function(text)
3079      {
3080      if (this.gui_objects.quotadisplay)
3081        this.gui_objects.quotadisplay.innerHTML = text;
3082      };
3083                  
4e17e6 3084
T 3085   // update the mailboxlist
15a9d1 3086   this.set_unread_count = function(mbox, count, set_title)
4e17e6 3087     {
T 3088     if (!this.gui_objects.mailboxlist)
3089       return false;
f108b9 3090       
4e17e6 3091     var item, reg, text_obj;
15a9d1 3092     mbox = String(mbox).toLowerCase().replace(this.mbox_expression, '');
T 3093     item = document.getElementById('rcmbx'+mbox);
3094
3095     if (item && item.className && item.className.indexOf('mailbox '+mbox)>=0)
4e17e6 3096       {
15a9d1 3097       // set new text
T 3098       text_obj = item.firstChild;
3099       reg = /\s+\([0-9]+\)$/i;
4e17e6 3100
15a9d1 3101       if (count && text_obj.innerHTML.match(reg))
T 3102         text_obj.innerHTML = text_obj.innerHTML.replace(reg, ' ('+count+')');
3103       else if (count)
3104         text_obj.innerHTML += ' ('+count+')';
3105       else
3106         text_obj.innerHTML = text_obj.innerHTML.replace(reg, '');
4e17e6 3107           
15a9d1 3108       // set the right classes
T 3109       this.set_classname(item, 'unread', count>0 ? true : false);
3110       }
3111
3112     // set unread count to window title
01c86f 3113     reg = /^\([0-9]+\)\s+/i;
T 3114     if (set_title && count && document.title)    
15a9d1 3115       {
T 3116       var doc_title = String(document.title);
3117
3118       if (count && doc_title.match(reg))
3119         document.title = doc_title.replace(reg, '('+count+') ');
3120       else if (count)
3121         document.title = '('+count+') '+doc_title;
3122       else
3123         document.title = doc_title.replace(reg, '');
4e17e6 3124       }
01c86f 3125     // remove unread count from window title
T 3126     else if (document.title)
3127       {
3128       document.title = document.title.replace(reg, '');
3129       }
4e17e6 3130     };
T 3131
3132
3133   // add row to contacts list
3134   this.add_contact_row = function(cid, cols)
3135     {
3136     if (!this.gui_objects.contactslist || !this.gui_objects.contactslist.tBodies[0])
3137       return false;
3138     
3139     var tbody = this.gui_objects.contactslist.tBodies[0];
3140     var rowcount = tbody.rows.length;
3141     var even = rowcount%2;
3142     
3143     var row = document.createElement('TR');
3144     row.id = 'rcmrow'+cid;
3145     row.className = 'contact '+(even ? 'even' : 'odd');
3146     
3147     if (this.in_selection(cid))
3148       row.className += ' selected';
3149
3150     // add each submitted col
3151     for (var c in cols)
3152       {
3153       col = document.createElement('TD');
3154       col.className = String(c).toLowerCase();
3155       col.innerHTML = cols[c];
3156       row.appendChild(col);
3157       }
3158     
3159     tbody.appendChild(row);
3160     this.init_table_row(row, 'contact_rows');
3161     };
3162
3163
3164
3165   /********************************************************/
3166   /*********          drag & drop methods         *********/
3167   /********************************************************/
3168
3169
3170   this.drag_mouse_move = function(e)
3171     {
3172     if (this.drag_start)
3173       {
3174       if (!this.draglayer)
3175         this.draglayer = new rcube_layer('rcmdraglayer', {x:0, y:0, width:300, vis:0, zindex:2000});
3176       
3177       // get subjects of selectedd messages
3178       var names = '';
3179       var c, subject, obj;
3180       for(var n=0; n<this.selection.length; n++)
3181         {
3182         if (n>12)  // only show 12 lines
3183           {
3184           names += '...';
3185           break;
3186           }
3187
3188         if (this.message_rows[this.selection[n]].obj)
3189           {
3190           obj = this.message_rows[this.selection[n]].obj;
3191           subject = '';
3192
3193           for(c=0; c<obj.childNodes.length; c++)
3194             if (!subject && obj.childNodes[c].nodeName=='TD' && obj.childNodes[c].firstChild && obj.childNodes[c].firstChild.nodeType==3)
3195               {
3196               subject = obj.childNodes[c].firstChild.data;
3197               names += (subject.length > 50 ? subject.substring(0, 50)+'...' : subject) + '<br />';
3198               }
3199           }
3200         }
3201         
3202       this.draglayer.write(names);
3203       this.draglayer.show(1);
3204       }
3205
3206     var pos = this.get_mouse_pos(e);
3207     this.draglayer.move(pos.x+20, pos.y-5);
3208     
3209     this.drag_start = false;
3210     this.drag_active = true;
3211     
3212     return false;
3213     };
3214
3215
3216   this.drag_mouse_up = function()
3217     {
3218     document.onmousemove = null;
3219     
3220     if (this.draglayer && this.draglayer.visible)
3221       this.draglayer.show(0);
3222       
3223     this.drag_active = false;
3224     
3225     return false;
3226     };
3227
3228
3229
3230   /********************************************************/
3231   /*********        remote request methods        *********/
3232   /********************************************************/
3233
3234
ecf759 3235   this.http_sockets = new Array();
T 3236   
3237   // find a non-busy socket or create a new one
3238   this.get_request_obj = function()
4e17e6 3239     {
ecf759 3240     for (var n=0; n<this.http_sockets.length; n++)
4e17e6 3241       {
ecf759 3242       if (!this.http_sockets[n].busy)
T 3243         return this.http_sockets[n];
4e17e6 3244       }
ecf759 3245     
T 3246     // create a new XMLHTTP object
3247     var i = this.http_sockets.length;
3248     this.http_sockets[i] = new rcube_http_request();
4e17e6 3249
ecf759 3250     return this.http_sockets[i];
T 3251     };
3252   
3253
3254   // send a http request to the server
3255   this.http_request = function(action, querystring, lock)
3256     {
3257     var request_obj = this.get_request_obj();
4e17e6 3258     querystring += '&_remote=1';
T 3259     
3260     // add timestamp to request url to avoid cacheing problems in Safari
3261     if (bw.safari)
3262       querystring += '&_ts='+(new Date().getTime());
3263
3264     // send request
ecf759 3265     if (request_obj)
4e17e6 3266       {
T 3267       // prompt('request', this.env.comm_path+'&_action='+escape(action)+'&'+querystring);
3268       console('HTTP request: '+this.env.comm_path+'&_action='+escape(action)+'&'+querystring);
ecf759 3269
T 3270       if (lock)
3271         this.set_busy(true);
3272
3273       request_obj.__lock = lock ? true : false;
3274       request_obj.__action = action;
3275       request_obj.onerror = function(o){ rcube_webmail_client.http_error(o); };
3276       request_obj.oncomplete = function(o){ rcube_webmail_client.http_response(o); };
3277       request_obj.GET(this.env.comm_path+'&_action='+escape(action)+'&'+querystring);
4e17e6 3278       }
T 3279     };
3280
3281
ecf759 3282   // handle HTTP response
T 3283   this.http_response = function(request_obj)
4e17e6 3284     {
ecf759 3285     var ctype = request_obj.get_header('Content-Type');
e1cf7c 3286     if (ctype){
ecf759 3287       ctype = String(ctype).toLowerCase();
e1cf7c 3288       var ctype_array=ctype.split(";");
S 3289       ctype = ctype_array[0];
3290     }
4e17e6 3291
c8c1e0 3292     this.set_busy(false);
4e17e6 3293
5e3512 3294   console(request_obj.get_text());
4e17e6 3295
ecf759 3296     // if we get javascript code from server -> execute it
c03095 3297     if (request_obj.get_text() && (ctype=='text/javascript' || ctype=='application/x-javascript'))
T 3298       eval(request_obj.get_text());
4e17e6 3299
ecf759 3300     // process the response data according to the sent action
T 3301     switch (request_obj.__action)
3302       {
3303       case 'delete':
3304       case 'moveto':
3305         if (this.env.action=='show')
3306           this.command('list');
3307         break;
15a9d1 3308
ecf759 3309       case 'list':
5e3512 3310         if (this.env.messagecount)
T 3311           this.enable_command('purge', (this.env.mailbox==this.env.trash_mailbox));
3312
15a9d1 3313       case 'expunge':
T 3314         this.enable_command('select-all', 'select-none', 'expunge', this.env.messagecount ? true : false);
5e3512 3315         break;      
4e17e6 3316       }
ecf759 3317
T 3318     request_obj.reset();
4e17e6 3319     };
ecf759 3320
T 3321
3322   // handle HTTP request errors
3323   this.http_error = function(request_obj)
3324     {
3325     alert('Error sending request: '+request_obj.url);
3326
3327     if (request_obj.__lock)
3328       this.set_busy(false);
3329
3330     request_obj.reset();
3331     request_obj.__lock = false;
3332     };
3333
3334
3335   // use an image to send a keep-alive siganl to the server
3336   this.send_keep_alive = function()
3337     {
3338     var d = new Date();
3339     this.http_request('keep-alive', '_t='+d.getTime());
3340     };
15a9d1 3341
ecf759 3342     
15a9d1 3343   // send periodic request to check for recent messages
T 3344   this.check_for_recent = function()
3345     {
c8c1e0 3346     this.set_busy(true, 'checkingmail');
15a9d1 3347     var d = new Date();
T 3348     this.http_request('check-recent', '_t='+d.getTime());
3349     };
4e17e6 3350
T 3351
3352   /********************************************************/
3353   /*********            helper methods            *********/
3354   /********************************************************/
3355   
3356   // check if we're in show mode or if we have a unique selection
3357   // and return the message uid
3358   this.get_single_uid = function()
3359     {
3360     return this.env.uid ? this.env.uid : (this.selection.length==1 ? this.selection[0] : null);
3361     };
3362
3363   // same as above but for contacts
3364   this.get_single_cid = function()
3365     {
3366     return this.env.cid ? this.env.cid : (this.selection.length==1 ? this.selection[0] : null);
3367     };
3368
3369
8c2e58 3370 /* deprecated methods
T 3371
4e17e6 3372   // check if Shift-key is pressed on event
T 3373   this.check_shiftkey = function(e)
3374     {
3375     if(!e && window.event)
3376       e = window.event;
3377
3378     if(bw.linux && bw.ns4 && e.modifiers)
3379       return true;
3380     else if((bw.ns4 && e.modifiers & Event.SHIFT_MASK) || (e && e.shiftKey))
3381       return true;
3382     else
3383       return false;
3384     }
3385
1cded8 3386   // check if Shift-key is pressed on event
T 3387   this.check_ctrlkey = function(e)
3388     {
3389     if(!e && window.event)
3390       e = window.event;
3391
3392     if(bw.linux && bw.ns4 && e.modifiers)
3393       return true;
3394    else if (bw.mac)
3395        return this.check_shiftkey(e);
3396     else if((bw.ns4 && e.modifiers & Event.CTRL_MASK) || (e && e.ctrlKey))
3397       return true;
3398     else
3399       return false;
3400     }
8c2e58 3401 */
4e17e6 3402
8c2e58 3403   // returns modifier key (constants defined at top of file)
b11a00 3404   this.get_modifier = function(e)
T 3405     {
3406     var opcode = 0;
8c2e58 3407     e = e || window.event;
T 3408
3409     if (bw.mac && e)
3410       {
3411       opcode += (e.metaKey && CONTROL_KEY) + (e.shiftKey && SHIFT_KEY);
3412       return opcode;    
3413       }
3414     if (e)
3415       {
b11a00 3416       opcode += (e.ctrlKey && CONTROL_KEY) + (e.shiftKey && SHIFT_KEY);
8c2e58 3417       return opcode;
T 3418       }
b11a00 3419     if (e.cancelBubble)
8c2e58 3420       {
b11a00 3421       e.cancelBubble = true;
T 3422       e.returnValue = false;
8c2e58 3423       }
b11a00 3424     else if (e.preventDefault)
T 3425       e.preventDefault();
3426   }
3427
3428
4e17e6 3429   this.get_mouse_pos = function(e)
T 3430     {
3431     if(!e) e = window.event;
3432     var mX = (e.pageX) ? e.pageX : e.clientX;
3433     var mY = (e.pageY) ? e.pageY : e.clientY;
3434
3435     if(document.body && document.all)
3436       {
3437       mX += document.body.scrollLeft;
3438       mY += document.body.scrollTop;
3439       }
3440
3441     return { x:mX, y:mY };
3442     };
3443     
3444   
3445   this.get_caret_pos = function(obj)
3446     {
3447     if (typeof(obj.selectionEnd)!='undefined')
3448       return obj.selectionEnd;
3449
3450     else if (document.selection && document.selection.createRange)
3451       {
3452       var range = document.selection.createRange();
3453       if (range.parentElement()!=obj)
3454         return 0;
3455
3456       var gm = range.duplicate();
3457       if (obj.tagName=='TEXTAREA')
3458         gm.moveToElementText(obj);
3459       else
3460         gm.expand('textedit');
3461       
3462       gm.setEndPoint('EndToStart', range);
3463       var p = gm.text.length;
3464
3465       return p<=obj.value.length ? p : -1;
3466       }
3467
3468     else
3469       return obj.value.length;
3470     };
3471
3472
3473   this.set_caret2start = function(obj)
3474     {
3475     if (obj.createTextRange)
3476       {
3477       var range = obj.createTextRange();
3478       range.collapse(true);
3479       range.select();
3480       }
3481     else if (obj.setSelectionRange)
3482       obj.setSelectionRange(0,0);
3483
3484     obj.focus();
3485     };
3486
3487
3488   // set all fields of a form disabled
3489   this.lock_form = function(form, lock)
3490     {
3491     if (!form || !form.elements)
3492       return;
3493     
3494     var type;
3495     for (var n=0; n<form.elements.length; n++)
3496       {
3497       type = form.elements[n];
3498       if (type=='hidden')
3499         continue;
3500         
3501       form.elements[n].disabled = lock;
3502       }
3503     };
3504     
3505   }  // end object rcube_webmail
3506
3507
3508
ecf759 3509 // class for HTTP requests
T 3510 function rcube_http_request()
3511   {
3512   this.url = '';
3513   this.busy = false;
3514   this.xmlhttp = null;
3515
3516
3517   // reset object properties
3518   this.reset = function()
3519     {
3520     // set unassigned event handlers
3521     this.onloading = function(){ };
3522     this.onloaded = function(){ };
3523     this.oninteractive = function(){ };
3524     this.oncomplete = function(){ };
3525     this.onabort = function(){ };
3526     this.onerror = function(){ };
3527     
3528     this.url = '';
3529     this.busy = false;
3530     this.xmlhttp = null;
3531     }
3532
3533
3534   // create HTMLHTTP object
3535   this.build = function()
3536     {
3537     if (window.XMLHttpRequest)
3538       this.xmlhttp = new XMLHttpRequest();
3539     else if (window.ActiveXObject)
3540       this.xmlhttp = new ActiveXObject("Microsoft.XMLHTTP");
3541     else
3542       {
3543       
3544       }
3545     }
3546
3547   // sedn GET request
3548   this.GET = function(url)
3549     {
3550     this.build();
3551
3552     if (!this.xmlhttp)
3553       {
3554       this.onerror(this);
3555       return false;
3556       }
3557
3558     var ref = this;
3559     this.url = url;
3560     this.busy = true;
3561
3562     this.xmlhttp.onreadystatechange = function(){ ref.xmlhttp_onreadystatechange(); };
3563     this.xmlhttp.open('GET', url);
3564     this.xmlhttp.send(null);
3565     };
3566
3567
3568   this.POST = function(url, a_param)
3569     {
3570     // not implemented yet
3571     };
3572
3573
3574   // handle onreadystatechange event
3575   this.xmlhttp_onreadystatechange = function()
3576     {
3577     if(this.xmlhttp.readyState == 1)
3578       this.onloading(this);
3579
3580     else if(this.xmlhttp.readyState == 2)
3581       this.onloaded(this);
3582
3583     else if(this.xmlhttp.readyState == 3)
3584       this.oninteractive(this);
3585
3586     else if(this.xmlhttp.readyState == 4)
3587       {
3588       if(this.xmlhttp.status == 0)
3589         this.onabort(this);
3590       else if(this.xmlhttp.status == 200)
3591         this.oncomplete(this);
3592       else
3593         this.onerror(this);
3594         
3595       this.busy = false;
3596       }
3597     }
3598
3599   // getter method for HTTP headers
3600   this.get_header = function(name)
3601     {
3602     return this.xmlhttp.getResponseHeader(name);
3603     };
3604
c03095 3605   this.get_text = function()
T 3606     {
3607     return this.xmlhttp.responseText;
3608     };
3609
3610   this.get_xml = function()
3611     {
3612     return this.xmlhttp.responseXML;
3613     };
ecf759 3614
T 3615   this.reset();
3616   
3617   }  // end class rcube_http_request
3618
3619
4e17e6 3620
T 3621 function console(str)
3622   {
3623   if (document.debugform && document.debugform.console)
3624     document.debugform.console.value += str+'\n--------------------------------------\n';
3625   }
3626
3627
3628 // set onload handler
3629 window.onload = function(e)
3630   {
3631   if (window.rcube_webmail_client)
3632     rcube_webmail_client.init();
3633   };