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