svncommit
2005-10-26 43fa235da354c8b53aa69ba745c1d398a758fcaf
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  |                                                                       |
a95e0e 9  | Modified: 2005/10/21 (roundcube)                                      |
4e17e6 10  |                                                                       |
T 11  +-----------------------------------------------------------------------+
12  | Author: Thomas Bruederli <roundcube@gmail.com>                        |
13  +-----------------------------------------------------------------------+
14 */
15
16
17 var rcube_webmail_client;
18
19 function rcube_webmail()
20   {
21   this.env = new Object();
22   this.buttons = new Object();
23   this.gui_objects = new Object();
24   this.commands = new Object();
25   this.selection = new Array();
26
27   // create public reference to myself
28   rcube_webmail_client = this;
29   this.ref = 'rcube_webmail_client';
30  
31   // webmail client settings
32   this.dblclick_time = 600;
33   this.message_time = 5000;
a95e0e 34   this.request_timeout = 120000;
4e17e6 35   this.mbox_expression = new RegExp('[^0-9a-z\-_]', 'gi');
T 36   this.env.blank_img = 'skins/default/images/blank.gif';
37   
38   // mimetypes supported by the browser (default settings)
39   this.mimetypes = new Array('text/plain', 'text/html', 'text/xml',
40                              'image/jpeg', 'image/gif', 'image/png',
41                              'application/x-javascript', 'application/pdf',
42                              'application/x-shockwave-flash');
43
44
45   // set environment variable
46   this.set_env = function(name, value)
47     {
48     //if (!this.busy)
49       this.env[name] = value;    
50     };
51
52   // add a button to the button list
53   this.register_button = function(command, id, type, act, sel, over)
54     {
55     if (!this.buttons[command])
56       this.buttons[command] = new Array();
57       
58     var button_prop = {id:id, type:type};
59     if (act) button_prop.act = act;
60     if (sel) button_prop.sel = sel;
61     if (over) button_prop.over = over;
62
63     this.buttons[command][this.buttons[command].length] = button_prop;    
64     };
65
66
67   // register a specific gui object
68   this.gui_object = function(name, id)
69     {
70     this.gui_objects[name] = id;
71     };
72
73
74   // initialize webmail client
75   this.init = function()
76     {
77     this.task = this.env.task;
78     
79     // check browser
a95e0e 80     if (!bw.dom || !bw.xmlhttp_test())
4e17e6 81       {
T 82       location.href = this.env.comm_path+'&_action=error&_code=0x199';
83       return;
84       }
85     
86     // find all registered gui objects
87     for (var n in this.gui_objects)
88       this.gui_objects[n] = rcube_find_object(this.gui_objects[n]);
89       
90     // tell parent window that this frame is loaded
91     if (this.env.framed && parent.rcmail && parent.rcmail.set_busy)
92       parent.rcmail.set_busy(false);
93
94     // enable general commands
95     this.enable_command('logout', 'mail', 'addressbook', 'settings', true);
96     
97     switch (this.task)
98       {
99       case 'mail':
100         var msg_list = this.gui_objects.messagelist;
101         if (msg_list)
102           {
103           this.init_messagelist(msg_list);
104           this.enable_command('markread', true);
105           }
106
107         // enable mail commands
108         this.enable_command('list', 'compose', 'add-contact', true);
109         
110         if (this.env.action=='show')
111           {
112           this.enable_command('show', 'reply', 'forward', 'moveto', 'delete', 'viewsource', 'print', 'load-attachment', true);
113           if (this.env.next_uid)
114             this.enable_command('nextmessage', true);
115           if (this.env.prev_uid)
116             this.enable_command('previousmessage', true);
117           }
118
119         if (this.env.action=='show' && this.env.blockedobjects)
120           {
121           if (this.gui_objects.remoteobjectsmsg)
122             this.gui_objects.remoteobjectsmsg.style.display = 'block';
123           this.enable_command('load-images', true);
124           }  
125
126         if (this.env.action=='compose')
127           this.enable_command('add-attachment', 'send-attachment', 'send', true);
128           
129         if (this.env.messagecount)
f3b659 130           this.enable_command('select-all', 'select-none', 'sort', true);
4e17e6 131
T 132         this.set_page_buttons();
133
134         // focus this window
135         window.focus();
136
137         // init message compose form
138         if (this.env.action=='compose')
139           this.init_messageform();
140
141         // show printing dialog
142         if (this.env.action=='print')
143           window.print();
144
145         break;
146
147
148       case 'addressbook':
149         var contacts_list = this.gui_objects.contactslist;
150         if (contacts_list)
151           this.init_contactslist(contacts_list);
152       
153         this.set_page_buttons();
154           
155         if (this.env.cid)
156           this.enable_command('show', 'edit', true);
157
158         if ((this.env.action=='add' || this.env.action=='edit') && this.gui_objects.editform)
159           this.enable_command('save', true);
160       
161         this.enable_command('list', 'add', true);
162         break;
163
164
165       case 'settings':
166         this.enable_command('preferences', 'identities', 'save', 'folders', true);
167         
168         if (this.env.action=='identities' || this.env.action=='edit-identity' || this.env.action=='add-identity')
169           this.enable_command('edit', 'add', 'delete', true);
170
171         if (this.env.action=='edit-identity' || this.env.action=='add-identity')
172           this.enable_command('save', true);
173           
174         if (this.env.action=='folders')
175           this.enable_command('subscribe', 'unsubscribe', 'create-folder', 'delete-folder', true);
176           
177         var identities_list = this.gui_objects.identitieslist;
178         if (identities_list)
179           this.init_identitieslist(identities_list);
180
181         break;
182
183       case 'login':
184         var input_user = rcube_find_object('_user');
185         var input_pass = rcube_find_object('_pass');
186         if (input_user && input_user.value=='')
187           input_user.focus();
188         else if (input_pass)
189           input_pass.focus();
190           
191         this.enable_command('login', true);
192         break;
193       
194       default:
195         break;
196       }
197
198
199     // enable basic commands
200     this.enable_command('logout', true);
201
202     // disable browser's contextmenus
203     //document.oncontextmenu = function(){ return false; }
204
205     // flag object as complete
206     this.loaded = true;
7902df 207           
4e17e6 208     // show message
T 209     if (this.pending_message)
210       this.display_message(this.pending_message[0], this.pending_message[1]);
211     };
212
213
214   // get all message rows from HTML table and init each row
215   this.init_messagelist = function(msg_list)
216     {
217     if (msg_list && msg_list.tBodies[0])
218       {
219       this.message_rows = new Array();
220
221       var row;
222       for(var r=0; r<msg_list.tBodies[0].childNodes.length; r++)
223         {
224         row = msg_list.tBodies[0].childNodes[r];
225         //row = msg_list.tBodies[0].rows[r];
226         this.init_message_row(row);
227         }
228       }
229       
230     // alias to common rows array
231     this.list_rows = this.message_rows;
232     };
233     
234     
235   // make references in internal array and set event handlers
236   this.init_message_row = function(row)
237     {
238     var uid, msg_icon;
239     
240     if (String(row.id).match(/rcmrow([0-9]+)/))
241       {
242       uid = RegExp.$1;
243       row.uid = uid;
244               
245       this.message_rows[uid] = {id:row.id, obj:row,
246                                 classname:row.className,
247                                 unread:this.env.messages[uid] ? this.env.messages[uid].unread : null,
248                                 replied:this.env.messages[uid] ? this.env.messages[uid].replied : null};
249               
250       // set eventhandlers to table row
251       row.onmousedown = function(e){ return rcube_webmail_client.drag_row(e, this.uid); };
252       row.onmouseup = function(e){ return rcube_webmail_client.click_row(e, this.uid); };
253               
254       // set eventhandler to message icon
255       if ((msg_icon = row.cells[0].childNodes[0]) && row.cells[0].childNodes[0].nodeName=='IMG')
256         {                
257         msg_icon.id = 'msgicn_'+uid;
258         msg_icon._row = row;
259         msg_icon.onmousedown = function(e) { rcube_webmail_client.command('markread', this); };
260                 
261         // get message icon and save original icon src
262         this.message_rows[uid].icon = msg_icon;
263         }
264       }
265     };
266
267
268   // init message compose form: set focus and eventhandlers
269   this.init_messageform = function()
270     {
271     if (!this.gui_objects.messageform)
272       return false;
273     
274     //this.messageform = this.gui_objects.messageform;
275     var input_to = rcube_find_object('_to');
276     var input_cc = rcube_find_object('_cc');
277     var input_bcc = rcube_find_object('_bcc');
278     var input_replyto = rcube_find_object('_replyto');
279     var input_subject = rcube_find_object('_subject');
280     var input_message = rcube_find_object('_message');
281     
282     // init live search events
283     if (input_to)
284       this.init_address_input_events(input_to);
285     if (input_cc)
286       this.init_address_input_events(input_cc);
287     if (input_bcc)
288       this.init_address_input_events(input_bcc);
289
290     if (input_to && input_to.value=='')
291       input_to.focus();
292     else if (input_subject && input_subject.value=='')
293       input_subject.focus();
294     else if (input_message)
295       this.set_caret2start(input_message); // input_message.focus();
296     };
297
298
299   this.init_address_input_events = function(obj)
300     {
301     var handler = function(e){ return rcube_webmail_client.ksearch_keypress(e,this); };
302     var handler2 = function(e){ return rcube_webmail_client.ksearch_blur(e,this); };
303     
304     if (bw.safari)
305       {
306       obj.addEventListener('keydown', handler, false);
307       // obj.addEventListener('blur', handler2, false);
308       }
309     else if (bw.mz)
310       {
311       obj.addEventListener('keypress', handler, false);
312       obj.addEventListener('blur', handler2, false);
313       }
314     else if (bw.ie)
315       {
316       obj.onkeydown = handler;
317       //obj.attachEvent('onkeydown', handler);
318       // obj.attachEvent('onblur', handler2, false);
319       }
320     
321     obj.setAttribute('autocomplete', 'off');       
322     };
323
324
325
326   // get all contact rows from HTML table and init each row
327   this.init_contactslist = function(contacts_list)
328     {
329     if (contacts_list && contacts_list.tBodies[0])
330       {
331       this.contact_rows = new Array();
332
333       var row;
334       for(var r=0; r<contacts_list.tBodies[0].childNodes.length; r++)
335         {
336         row = contacts_list.tBodies[0].childNodes[r];
337         this.init_table_row(row, 'contact_rows');
338         }
339       }
340
341     // alias to common rows array
342     this.list_rows = this.contact_rows;
343     
344     if (this.env.cid)
345       this.select(this.env.cid);
346     };
347
348
349   // make references in internal array and set event handlers
350   this.init_table_row = function(row, array_name)
351     {
352     var cid;
353     
354     if (String(row.id).match(/rcmrow([0-9]+)/))
355       {
356       cid = RegExp.$1;
357       row.cid = cid;
358
359       this[array_name][cid] = {id:row.id,
360                                obj:row,
361                                classname:row.className};
362
363       // set eventhandlers to table row
364       row.onmousedown = function(e) { rcube_webmail_client.in_selection_before=this.cid; return false; };  // fake for drag handler
365       row.onmouseup = function(e){ return rcube_webmail_client.click_row(e, this.cid); };
366       }
367     };
368
369
370   // get all contact rows from HTML table and init each row
371   this.init_identitieslist = function(identities_list)
372     {
373     if (identities_list && identities_list.tBodies[0])
374       {
375       this.identity_rows = new Array();
376
377       var row;
378       for(var r=0; r<identities_list.tBodies[0].childNodes.length; r++)
379         {
380         row = identities_list.tBodies[0].childNodes[r];
381         this.init_table_row(row, 'identity_rows');
382         }
383       }
384
385     // alias to common rows array
386     this.list_rows = this.identity_rows;
387     
388     if (this.env.iid)
389       this.select(this.env.iid);    
390     };
391     
392
393
394   /*********************************************************/
395   /*********       client command interface        *********/
396   /*********************************************************/
397
398
399   // execute a specific command on the web client
400   this.command = function(command, props, obj)
401     {
402     if (obj && obj.blur)
403       obj.blur();
404
405     if (this.busy)
406       return false;
407
408     // command not supported or allowed
409     if (!this.commands[command])
410       {
411       // pass command to parent window
412       if (this.env.framed && parent.rcmail && parent.rcmail.command)
413         parent.rcmail.command(command, props);
414
415       return false;
416       }
417
418     // process command
419     switch (command)
420       {
421       case 'login':
422         if (this.gui_objects.loginform)
423           this.gui_objects.loginform.submit();
424         break;
425
426       case 'logout':
427         location.href = this.env.comm_path+'&_action=logout';
428         break;      
429
430       // commands to switch task
431       case 'mail':
432       case 'addressbook':
433       case 'settings':
434         this.switch_task(command);
435         break;
436
437
438       // misc list commands
439       case 'list':
440         if (this.task=='mail')
441           this.list_mailbox(props);
442         else if (this.task=='addressbook')
443           this.list_contacts();
f3b659 444         break;
T 445
446       case 'sort':
447         // get the type of sorting
448         this.list_mailbox('', '', props);
4e17e6 449         break;
T 450
451       case 'nextpage':
452         this.list_page('next');
453         break;
454
455       case 'previouspage':
456         this.list_page('prev');
457         break;
458
459
460       // common commands used in multiple tasks
461       case 'show':
462         if (this.task=='mail')
463           {
464           var uid = this.get_single_uid();
465           if (uid && (!this.env.uid || uid != this.env.uid))
466             this.show_message(uid);
467           }
468         else if (this.task=='addressbook')
469           {
470           var cid = props ? props : this.get_single_cid();
471           if (cid && !(this.env.action=='show' && cid==this.env.cid))
472             this.load_contact(cid, 'show');
473           }
474         break;
475
476       case 'add':
477         if (this.task=='addressbook')
478           this.load_contact(0, 'add');
479         else if (this.task=='settings')
480           {
481           this.clear_selection();
482           this.load_identity(0, 'add-identity');
483           }
484         break;
485
486       case 'edit':
487         var cid;
488         if (this.task=='addressbook' && (cid = this.get_single_cid()))
489           this.load_contact(cid, 'edit');
490         else if (this.task=='settings' && props)
491           this.load_identity(props, 'edit-identity');
492         break;
493
494       case 'save-identity':
495       case 'save':
496         if (this.gui_objects.editform)
497           this.gui_objects.editform.submit();
498         break;
499
500       case 'delete':
501         // mail task
502         if (this.task=='mail' && this.env.trash_mailbox && String(this.env.mailbox).toLowerCase()!=String(this.env.trash_mailbox).toLowerCase())
503           this.move_messages(this.env.trash_mailbox);
504         else if (this.task=='mail')
505           this.delete_messages();
506         // addressbook task
507         else if (this.task=='addressbook')
508           this.delete_contacts();
509         // user settings task
510         else if (this.task=='settings')
511           this.delete_identity();
512         break;
513
514
515       // mail task commands
516       case 'move':
517       case 'moveto':
518         this.move_messages(props);
519         break;
520         
521       case 'markread':
522         if (props && !props._row)
523           break;
524         
525         var uid;
526         var flag = 'read';
527         
528         if (props._row.uid)
529           {
530           uid = props._row.uid;
531           this.dont_select = true;
532           
533           // toggle read/unread
534           if (!this.message_rows[uid].unread)
535             flag = 'unread';
536           }
537           
538         this.mark_message(flag, uid);
539         break;
540         
541       case 'load-images':
542         if (this.env.uid)
543           this.show_message(this.env.uid, true);
544         break;
545
546       case 'load-attachment':
547         var url = this.env.comm_path+'&_action=get&_mbox='+this.env.mailbox+'&_uid='+this.env.uid+'&_part='+props.part;
548         
549         // open attachment in frame if it's of a supported mimetype
550         if (this.env.uid && props.mimetype && find_in_array(props.mimetype, this.mimetypes)>=0)
551           {
552           this.attachment_win = window.open(url+'&_frame=1', 'rcubemailattachment');
553           if (this.attachment_win)
554             {
555             setTimeout(this.ref+'.attachment_win.focus()', 10);
556             break;
557             }
558           }
559
560         location.href = url;
561         break;
562         
563       case 'select-all':
564         this.select_all(props);
565         break;
566
567       case 'select-none':
568         this.clear_selection();
569         break;
570
571       case 'nextmessage':
572         if (this.env.next_uid)
09941e 573           this.show_message(this.env.next_uid);
T 574           //location.href = this.env.comm_path+'&_action=show&_uid='+this.env.next_uid+'&_mbox='+this.env.mailbox;
4e17e6 575         break;
T 576
577       case 'previousmessage':
578         if (this.env.prev_uid)
09941e 579           this.show_message(this.env.prev_uid);
T 580           //location.href = this.env.comm_path+'&_action=show&_uid='+this.env.prev_uid+'&_mbox='+this.env.mailbox;
4e17e6 581         break;
T 582
583       case 'compose':
584         var url = this.env.comm_path+'&_action=compose';
585         
586         // modify url if we're in addressbook
587         if (this.task=='addressbook')
588           {
589           url = this.get_task_url('mail', url);            
590           var a_cids = new Array();
591           
592           // use contact_id passed as command parameter
593           if (props)
594             a_cids[a_cids.length] = props;
595             
596           // get selected contacts
597           else
598             {
599             for (var n=0; n<this.selection.length; n++)
600               a_cids[a_cids.length] = this.selection[n];
601             }
602
603           if (a_cids.length)
604             url += '&_to='+a_cids.join(',');
605           else
606             break;
607           }
608         else if (props)
609            url += '&_to='+props;
610
611         this.set_busy(true);
612         location.href = url;
613         break;      
614
615       case 'send':
616         if (!this.gui_objects.messageform)
617           break;
618           
619         // check input fields
620         var input_to = rcube_find_object('_to');
621         var input_subject = rcube_find_object('_subject');
622         var input_message = rcube_find_object('_message');
623         
624         if (input_to.value!='' && input_message.value!='')
625           {
626           this.set_busy(true, 'sendingmessage');
627           var form = this.gui_objects.messageform;
628           form.submit();
629           }
630           
631         break;
632
633       case 'add-attachment':
634         this.show_attachment_form(true);
635         
636       case 'send-attachment':
637         this.upload_file(props)      
638         break;
639
640       case 'reply':
641         var uid;
642         if (uid = this.get_single_uid())
643           {
644           this.set_busy(true);
645           location.href = this.env.comm_path+'&_action=compose&_reply_uid='+uid+'&_mbox='+escape(this.env.mailbox);
646           }
647         break;      
648
649       case 'forward':
650         var uid;
651         if (uid = this.get_single_uid())
652           {
653           this.set_busy(true);
654           location.href = this.env.comm_path+'&_action=compose&_forward_uid='+uid+'&_mbox='+escape(this.env.mailbox);
655           }
656         break;
657         
658       case 'print':
659         var uid;
660         if (uid = this.get_single_uid())
661           {
662           this.printwin = window.open(this.env.comm_path+'&_action=print&_uid='+uid+'&_mbox='+escape(this.env.mailbox)+(this.env.safemode ? '&_safe=1' : ''));
663           if (this.printwin)
664             setTimeout(this.ref+'.printwin.focus()', 20);
665           }
666         break;
667
668       case 'viewsource':
669         var uid;
670         if (uid = this.get_single_uid())
671           {          
672           this.sourcewin = window.open(this.env.comm_path+'&_action=viewsource&_uid='+this.env.uid+'&_mbox='+escape(this.env.mailbox));
673           if (this.sourcewin)
674             setTimeout(this.ref+'.sourcewin.focus()', 20);
675           }
676         break;
677
678       case 'add-contact':
679         this.add_contact(props);
680         break;
681
682
683       // user settings commands
684       case 'preferences':
685         location.href = this.env.comm_path;
686         break;
687
688       case 'identities':
689         location.href = this.env.comm_path+'&_action=identities';
690         break;
691           
692       case 'delete-identity':
693         this.delete_identity();
694         
695       case 'folders':
696         location.href = this.env.comm_path+'&_action=folders';
697         break;
698
699       case 'subscribe':
700         this.subscribe_folder(props);
701         break;
702
703       case 'unsubscribe':
704         this.unsubscribe_folder(props);
705         break;
706         
707       case 'create-folder':
708         this.create_folder(props);
709         break;
710
711       case 'delete-folder':
712         if (confirm('Do you really want to delete this folder?'))
713           this.delete_folder(props);
714         break;
715
716       }
717
718     return obj ? false : true;
719     };
720
721
722   // set command enabled or disabled
723   this.enable_command = function()
724     {
725     var args = this.enable_command.arguments;
726     if(!args.length) return -1;
727
728     var command;
729     var enable = args[args.length-1];
730     
731     for(var n=0; n<args.length-1; n++)
732       {
733       command = args[n];
734       this.commands[command] = enable;
735       this.set_button(command, (enable ? 'act' : 'pas'));
736       }
737     };
738
739
a95e0e 740   // lock/unlock interface
4e17e6 741   this.set_busy = function(a, message)
T 742     {
743     if (a && message)
744       this.display_message('Loading...', 'loading', true);
745     else if (!a && this.busy)
746       this.hide_message();
747
748     this.busy = a;
749     //document.body.style.cursor = a ? 'wait' : 'default';
750     
751     if (this.gui_objects.editform)
752       this.lock_form(this.gui_objects.editform, a);
a95e0e 753       
T 754     // clear pending timer
755     if (this.request_timer)
756       clearTimeout(this.request_timer);
757
758     // set timer for requests
759     if (a && this.request_timeout)
760       this.request_timer = setTimeout(this.ref+'.request_timed_out()', this.request_timeout);
4e17e6 761     };
T 762
763
764   this.switch_task = function(task)
765     {
766     if (this.task===task)
767       return;
768
769     this.set_busy(true);
770     location.href = this.get_task_url(task);      
771     };
772
773
774   this.get_task_url = function(task, url)
775     {
776     if (!url)
777       url = this.env.comm_path;
778
779     return url.replace(/_task=[a-z]+/, '_task='+task);
a95e0e 780     };
T 781     
782   
783   // called when a request timed out
784   this.request_timed_out = function()
785     {
786     this.set_busy(false);
787     this.display_message('Request timed out!', 'error');
4e17e6 788     };
T 789
790
791   /*********************************************************/
792   /*********        event handling methods         *********/
793   /*********************************************************/
794
795
796   // onmouseup handler for mailboxlist item
797   this.mbox_mouse_up = function(mbox)
798     {
799     if (this.drag_active)
800       this.command('moveto', mbox);
801     else
802       this.command('list', mbox);
803       
804     return false;
805     };
806
807
808   // onmousedown-handler of message list row
809   this.drag_row = function(e, id)
810     {
811     this.in_selection_before = this.in_selection(id) ? id : false;
812
813     // don't do anything (another action processed before)
814     if (this.dont_select)
815       return false;
816
817     if (!this.in_selection_before)
818       {
819       var shift = this.check_shiftkey(e);
820       this.select(id, shift);
821       }
822     
823     if (this.selection.length)
824       {
825       this.drag_start = true;
826       document.onmousemove = function(e){ return rcube_webmail_client.drag_mouse_move(e); };
827       document.onmouseup = function(e){ return rcube_webmail_client.drag_mouse_up(e); };
828       }
829
830     return false;
831     };
832
833
834   // onmouseup-handler of message list row
835   this.click_row = function(e, id)
836     {
837     var shift = this.check_shiftkey(e);
838     
839     // don't do anything (another action processed before)
840     if (this.dont_select)
841       {
842       this.dont_select = false;
843       return false;
844       }
845     
846     if (!this.drag_active && this.in_selection_before==id)
847       this.select(id, (shift && this.task!='settings'));
848     
849     this.drag_start = false;
850     this.in_selection_before = false;
851         
852     // row was double clicked
853     if (this.task=='mail' && this.list_rows && this.list_rows[id].clicked && !shift)
854       {
855       this.show_message(id);
856       return false;
857       }
858     else if (this.task=='addressbook')
859       {
860       if (this.selection.length==1 && this.env.contentframe)
861         this.load_contact(this.selection[0], 'show', true);
862       else if (this.task=='addressbook' && this.list_rows && this.list_rows[id].clicked)
863         {
864         this.load_contact(id, 'show');
865         return false;
866         }
867       else if (this.env.contentframe)
868         {
869         var elm = document.getElementById(this.env.contentframe);
870         elm.style.visibility = 'hidden';
871         }
872       }
873     else if (this.task=='settings')
874       {
875       if (this.selection.length==1)
876         this.command('edit', this.selection[0]);
877       }
878
879     this.list_rows[id].clicked = true;
880     setTimeout(this.ref+'.list_rows['+id+'].clicked=false;', this.dblclick_time);
881       
882     return false;
883     };
884
885
886
887
888   /*********************************************************/
889   /*********     (message) list functionality      *********/
890   /*********************************************************/
891
892
893   // select/unselect a row
894   this.select = function(id, multiple)
895     {
896     var selected = false
897     
898     if (this.list_rows[id] && !multiple)
899       {
900       this.clear_selection();
901       this.selection[0] = id;
902       this.list_rows[id].obj.className += ' selected';
903       selected = true;
904       }
905     
906     else if (this.list_rows[id])
907       {
908       if (!this.in_selection(id))  // select row
909         {
910         this.selection[this.selection.length] = id;
911         this.set_classname(this.list_rows[id].obj, 'selected', true);        
912         }
913       else  // unselect row
914         {
915         var p = find_in_array(id, this.selection);
916         var a_pre = this.selection.slice(0, p);
917         var a_post = this.selection.slice(p+1, this.selection.length);
918         this.selection = a_pre.concat(a_post);
919         this.set_classname(this.list_rows[id].obj, 'selected', false);
920         }
921         
922       selected = (this.selection.length==1);
923       }
924
925     // enable/disable commands for message
926     if (this.task=='mail')
927       {
928       this.enable_command('show', 'reply', 'forward', 'print', selected);
929       this.enable_command('delete', 'moveto', this.selection.length>0 ? true : false);
930       }
931     else if (this.task=='addressbook')
932       {
933       this.enable_command('edit', /*'print',*/ selected);
934       this.enable_command('delete', 'compose', this.selection.length>0 ? true : false);
935       }
936     };
937
938
939   this.clear_selection = function()
940     {
941     for(var n=0; n<this.selection.length; n++)
942       if (this.list_rows[this.selection[n]])
943         this.set_classname(this.list_rows[this.selection[n]].obj, 'selected', false);
944
945     this.selection = new Array();    
946     };
947
948
949   // check if given id is part of the current selection
950   this.in_selection = function(id)
951     {
952     for(var n in this.selection)
953       if (this.selection[n]==id)
954         return true;
955
956     return false;    
957     };
958
959
960   // select each row in list
961   this.select_all = function(filter)
962     {
963     if (!this.list_rows || !this.list_rows.length)
964       return false;
965       
966     // reset selection first
967     this.clear_selection();
968     
969     for (var n in this.list_rows)
970       if (!filter || this.list_rows[n][filter]==true)
971       this.select(n, true);
972     };
973     
974
975   // when user doble-clicks on a row
976   this.show_message = function(id, safe)
977     {
978     var add_url = '';
979     var target = window;
980     if (this.env.contentframe && window.frames && window.frames[this.env.contentframe])
981       {
982       target = window.frames[this.env.contentframe];
983       add_url = '&_framed=1';
984       }
985       
986     if (safe)
987       add_url = '&_safe=1';
988
989     if (id)
990       {
09941e 991       this.set_busy(true, 'loading');
4e17e6 992       target.location.href = this.env.comm_path+'&_action=show&_uid='+id+'&_mbox='+escape(this.env.mailbox)+add_url;
T 993       }
994     };
995
996
997
998   // list a specific page
999   this.list_page = function(page)
1000     {
1001     if (page=='next')
1002       page = this.env.current_page+1;
1003     if (page=='prev' && this.env.current_page>1)
1004       page = this.env.current_page-1;
1005       
1006     if (page > 0 && page <= this.env.pagecount)
1007       {
1008       this.env.current_page = page;
1009       
1010       if (this.task=='mail')
1011         this.list_mailbox(this.env.mailbox, page);
1012       else if (this.task=='addressbook')
1013         this.list_contacts(page);
1014       }
1015     };
1016
1017
1018   // list messages of a specific mailbox
f3b659 1019   this.list_mailbox = function(mbox, page, sort)
4e17e6 1020     {
T 1021     var add_url = '';
1022     var target = window;
1023
1024     if (!mbox)
1025       mbox = this.env.mailbox;
1026
f3b659 1027     // add sort to url if set
T 1028     if (sort)
1029       add_url += '&_sort=' + sort;
1030       
4e17e6 1031     // set page=1 if changeing to another mailbox
T 1032     if (!page && mbox != this.env.mailbox)
1033       {
1034       page = 1;
9fee0e 1035       add_url += '&_refresh=1';
4e17e6 1036       this.env.current_page = page;
T 1037       this.clear_selection();
1038       }
1039       
1040     if (this.env.mailbox!=mbox)
1041       this.select_mailbox(mbox);
1042
1043     // load message list remotely
1044     if (this.gui_objects.messagelist)
1045       {
9fee0e 1046       this.list_mailbox_remote(mbox, page, add_url);
4e17e6 1047       return;
T 1048       }
1049     
1050     if (this.env.contentframe && window.frames && window.frames[this.env.contentframe])
1051       {
1052       target = window.frames[this.env.contentframe];
9fee0e 1053       add_url += '&_framed=1';
4e17e6 1054       }
T 1055
1056     // load message list to target frame/window
1057     if (mbox)
1058       {
1059       this.set_busy(true, 'loading');
1060       target.location.href = this.env.comm_path+'&_mbox='+escape(mbox)+(page ? '&_page='+page : '')+add_url;
1061       }
1062     };
1063
1064
1065   // send remote request to load message list
9fee0e 1066   this.list_mailbox_remote = function(mbox, page, add_url)
4e17e6 1067     {
T 1068     // clear message list
1069     var table = this.gui_objects.messagelist;
1070     var tbody = document.createElement('TBODY');
1071     table.insertBefore(tbody, table.tBodies[0]);
1072     table.removeChild(table.tBodies[1]);
1073     
1074     this.message_rows = new Array();
1075     this.list_rows = this.message_rows;
1076
1077     // send request to server
1078     var url = '_mbox='+escape(mbox)+(page ? '&_page='+page : '');
1079     this.set_busy(true, 'loading');
9fee0e 1080     this.http_request('list', url+add_url);
4e17e6 1081     };
T 1082
1083
1084   // move selected messages to the specified mailbox
1085   this.move_messages = function(mbox)
1086     {
1087     // exit if no mailbox specified or if selection is empty
1088     if (!mbox || !(this.selection.length || this.env.uid) || mbox==this.env.mailbox)
1089       return;
1090     
1091     var a_uids = new Array();
1092
1093     if (this.env.uid)
1094       a_uids[a_uids.length] = this.env.uid;
1095     else
1096       {
1097       var id;
1098       for (var n=0; n<this.selection.length; n++)
1099         {
1100         id = this.selection[n];
1101         a_uids[a_uids.length] = id;
1102       
1103         // 'remove' message row from list (just hide it)
1104         if (this.message_rows[id].obj)
1105           this.message_rows[id].obj.style.display = 'none';
1106         }
1107       }
1108
1109     // show wait message
1110     if (this.env.action=='show')
1111       this.set_busy(true, 'movingmessage');
1112
1113     // send request to server
1114     this.http_request('moveto', '_uid='+a_uids.join(',')+'&_mbox='+escape(this.env.mailbox)+'&_target_mbox='+escape(mbox)+'&_from='+(this.env.action ? this.env.action : ''));
1115     };
1116
1117
1118   // delete selected messages from the current mailbox
1119   this.delete_messages = function()
1120     {
1121     // exit if no mailbox specified or if selection is empty
1122     if (!(this.selection.length || this.env.uid))
1123       return;
1124     
1125     var a_uids = new Array();
1126
1127     if (this.env.uid)
1128       a_uids[a_uids.length] = this.env.uid;
1129     else
1130       {
1131       var id;
1132       for (var n=0; n<this.selection.length; n++)
1133         {
1134         id = this.selection[n];
1135         a_uids[a_uids.length] = id;
1136       
1137         // 'remove' message row from list (just hide it)
1138         if (this.message_rows[id].obj)
1139           this.message_rows[id].obj.style.display = 'none';
1140         }
1141       }
1142
1143     // send request to server
1144     this.http_request('delete', '_uid='+a_uids.join(',')+'&_mbox='+escape(this.env.mailbox)+'&_from='+(this.env.action ? this.env.action : ''));
1145     };
1146
1147
1148   // set a specific flag to one or more messages
1149   this.mark_message = function(flag, uid)
1150     {
1151     var a_uids = new Array();
1152     
1153     if (uid)
1154       a_uids[0] = uid;
1155     else if (this.env.uid)
1156       a_uids[0] = this.env.uid;
1157     else
1158       {
1159       var id;
1160       for (var n=0; n<this.selection.length; n++)
1161         {
1162         id = this.selection[n];
1163         a_uids[a_uids.length] = id;
1164       
1165         // 'remove' message row from list (just hide it)
1166         if (this.message_rows[id].obj)
1167           this.message_rows[id].obj.style.display = 'none';
1168         }
1169       }
1170
1171     // mark all message rows as read/unread
1172     var icn_src;
1173     for (var i=0; i<a_uids.length; i++)
1174       {
1175       uid = a_uids[i];
1176       if (this.message_rows[uid])
1177         {
1178         this.message_rows[uid].unread = (flag=='unread' ? true : false);
1179         
1180         if (this.message_rows[uid].classname.indexOf('unread')<0 && this.message_rows[uid].unread)
1181           {
1182           this.message_rows[uid].classname += ' unread';
1183           if (!this.in_selection(uid))
1184             this.message_rows[uid].obj.className += ' unread';
1185           if (this.env.unreadicon)
1186             icn_src = this.env.unreadicon;
1187           }
1188         else if (!this.message_rows[uid].unread)
1189           {
1190           this.message_rows[uid].classname = this.message_rows[uid].classname.replace(/\s*unread/, '');
1191           if (!this.in_selection(uid))
1192             this.message_rows[uid].obj.className = this.message_rows[uid].obj.className.replace(/\s*unread/, '');
1193
1194           if (this.message_rows[uid].replied && this.env.repliedicon)
1195             icn_src = this.env.repliedicon;
1196           else if (this.env.messageicon)
1197             icn_src = this.env.messageicon;
1198           }
1199
1200         if (this.message_rows[uid].icon && icn_src)
1201           this.message_rows[uid].icon.src = icn_src;
1202         }
1203       }
1204
1205     // send request to server
1206     this.http_request('mark', '_uid='+a_uids.join(',')+'&_flag='+flag);
1207     };
1208
1209
1210
1211   /*********************************************************/
1212   /*********        message compose methods        *********/
1213   /*********************************************************/
1214
1215
1216   this.show_attachment_form = function(a)
1217     {
1218     if (!this.gui_objects.uploadbox)
1219       return false;
1220       
1221     var elm, list;
1222     if (elm = this.gui_objects.uploadbox)
1223       {
1224       if (a &&  (list = this.gui_objects.attachmentlist))
1225         {
1226         var pos = rcube_get_object_pos(list);
1227         var left = pos.x;
1228         var top = pos.y + list.offsetHeight + 10;
1229       
1230         elm.style.top = top+'px';
1231         elm.style.left = left+'px';
1232         }
1233       
1234       elm.style.visibility = a ? 'visible' : 'hidden';
1235       }
1236       
1237     // clear upload form
1238     if (!a && this.gui_objects.attachmentform && this.gui_objects.attachmentform!=this.gui_objects.messageform)
1239       this.gui_objects.attachmentform.reset();
1240     };
1241
1242
1243   // upload attachment file
1244   this.upload_file = function(form)
1245     {
1246     if (!form)
1247       return false;
1248       
1249     // get file input fields
1250     var send = false;
1251     for (var n=0; n<form.elements.length; n++)
1252       if (form.elements[n].type=='file' && form.elements[n].value)
1253         {
1254         send = true;
1255         break;
1256         }
1257     
1258     // create hidden iframe and post upload form
1259     if (send)
1260       {
1261       var ts = new Date().getTime();
1262       var frame_name = 'rcmupload'+ts;
1263
1264       // have to do it this way for IE
1265       // otherwise the form will be posted to a new window
1266       if(document.all && !window.opera)
1267         {
1268         var html = '<iframe name="'+frame_name+'" src="program/blank.gif" style="width:0;height:0;visibility:hidden;"></iframe>';
1269         document.body.insertAdjacentHTML('BeforeEnd',html);
1270         }
1271       else  // for standards-compilant browsers
1272         {
1273         var frame = document.createElement('IFRAME');
1274         frame.name = frame_name;
1275         frame.width = 10;
1276         frame.height = 10;
1277         frame.style.visibility = 'hidden';
1278         document.body.appendChild(frame);
1279         }
1280
1281       form.target = frame_name;
1282       form.action = this.env.comm_path+'&_action=upload';
1283       form.setAttribute('enctype', 'multipart/form-data');
1284       form.submit();
1285       }
1286     
1287     // set reference to the form object
1288     this.gui_objects.attachmentform = form;
1289     };
1290
1291
1292   // add file name to attachment list
1293   // called from upload page
1294   this.add2attachment_list = function(name)
1295     {
1296     if (!this.gui_objects.attachmentlist)
1297       return false;
1298       
1299     var li = document.createElement('LI');
1300     li.innerHTML = name;
1301     this.gui_objects.attachmentlist.appendChild(li);
1302     };
1303
1304
1305   // send remote request to add a new contact
1306   this.add_contact = function(value)
1307     {
1308     if (value)
1309       this.http_request('addcontact', '_address='+value);
1310     };
1311
1312
1313   /*********************************************************/
1314   /*********     keyboard live-search methods      *********/
1315   /*********************************************************/
1316
1317
1318   // handler for keyboard events on address-fields
1319   this.ksearch_keypress = function(e, obj)
1320     {
1321     if (typeof(this.env.contacts)!='object' || !this.env.contacts.length)
1322       return true;
1323
1324     if (this.ksearch_timer)
1325       clearTimeout(this.ksearch_timer);
1326
1327     if (!e)
1328       e = window.event;
1329       
1330     var highlight;
1331     var key = e.keyCode ? e.keyCode : e.which;
1332
1333     switch (key)
1334       {
1335       case 38:  // key up
1336       case 40:  // key down
1337         if (!this.ksearch_pane)
1338           break;
1339           
1340         var dir = key==38 ? 1 : 0;
1341         var next;
1342         
1343         highlight = document.getElementById('rcmksearchSelected');
1344         if (!highlight)
1345           highlight = this.ksearch_pane.ul.firstChild;
1346         
1347         if (highlight && (next = dir ? highlight.previousSibling : highlight.nextSibling))
1348           {
1349           highlight.removeAttribute('id');
1350           //highlight.removeAttribute('class');
1351           this.set_classname(highlight, 'selected', false);
1352           }
1353
1354         if (next)
1355           {
1356           next.setAttribute('id', 'rcmksearchSelected');
1357           this.set_classname(next, 'selected', true);
1358           this.ksearch_selected = next._rcm_id;
1359           }
1360
1361         if (e.preventDefault)
1362           e.preventDefault();
1363         return false;
1364
1365       case 9:  // tab
1366         if(e.shiftKey)
1367           break;
1368
1369       case 13:  // enter     
1370         if (this.ksearch_selected===null || !this.ksearch_input || !this.ksearch_value)
1371           break;
1372
1373         // get cursor pos
1374         var inp_value = this.ksearch_input.value.toLowerCase();
1375         var cpos = this.get_caret_pos(this.ksearch_input);
1376         var p = inp_value.lastIndexOf(this.ksearch_value, cpos);
1377         
1378         // replace search string with full address
1379         var pre = this.ksearch_input.value.substring(0, p);
1380         var end = this.ksearch_input.value.substring(p+this.ksearch_value.length, this.ksearch_input.value.length);
1381         var insert = this.env.contacts[this.ksearch_selected]+', ';
1382         this.ksearch_input.value = pre + insert + end;
1383         
1384         //this.ksearch_input.value = this.ksearch_input.value.substring(0, p)+insert;
1385         
1386         // set caret to insert pos
1387         cpos = p+insert.length;
1388         if (this.ksearch_input.setSelectionRange)
1389           this.ksearch_input.setSelectionRange(cpos, cpos);
1390         
1391         // hide ksearch pane
1392         this.ksearch_hide();
1393       
1394         if (e.preventDefault)
1395           e.preventDefault();
1396         return false;
1397
1398       case 27:  // escape
1399         this.ksearch_hide();
1400         break;
1401
1402       }
1403
1404     // start timer
1405     this.ksearch_timer = setTimeout(this.ref+'.ksearch_get_results()', 200);      
1406     this.ksearch_input = obj;
1407     
1408     return true;
1409     };
1410
1411
1412   // address search processor
1413   this.ksearch_get_results = function()
1414     {
1415     var inp_value = this.ksearch_input ? this.ksearch_input.value : null;
1416     if (inp_value===null)
1417       return;
1418
1419     // get string from current cursor pos to last comma
1420     var cpos = this.get_caret_pos(this.ksearch_input);
1421     var p = inp_value.lastIndexOf(',', cpos-1);
1422     var q = inp_value.substring(p+1, cpos);
1423
1424     // trim query string
1425     q = q.replace(/(^\s+|\s+$)/g, '').toLowerCase();
1426
1427     if (!q.length || q==this.ksearch_value)
1428       {
1429       if (!q.length && this.ksearch_pane && this.ksearch_pane.visible)
1430         this.ksearch_pane.show(0);
1431
1432       return;
1433       }
1434
1435     this.ksearch_value = q;
1436     
1437     // start searching the contact list
1438     var a_results = new Array();
1439     var a_result_ids = new Array();
1440     var c=0;
1441     for (var i=0; i<this.env.contacts.length; i++)
1442       {
1443       if (this.env.contacts[i].toLowerCase().indexOf(q)>=0)
1444         {
1445         a_results[c] = this.env.contacts[i];
1446         a_result_ids[c++] = i;
1447         
1448         if (c==15)  // limit search results
1449           break;
1450         }
1451       }
1452
1453     // display search results
1454     if (c && a_results.length)
1455       {
1456       var p, ul, li;
1457       
1458       // create results pane if not present
1459       if (!this.ksearch_pane)
1460         {
1461         ul = document.createElement('UL');
1462         this.ksearch_pane = new rcube_layer('rcmKSearchpane', {vis:0, zindex:30000});
1463         this.ksearch_pane.elm.appendChild(ul);
1464         this.ksearch_pane.ul = ul;
1465         }
1466       else
1467         ul = this.ksearch_pane.ul;
1468
1469       // remove all search results
1470       ul.innerHTML = '';
1471             
1472       // add each result line to list
1473       for (i=0; i<a_results.length; i++)
1474         {
1475         li = document.createElement('LI');
1476         li.innerHTML = a_results[i].replace(/</, '&lt;').replace(/>/, '&gt;');
1477         li._rcm_id = a_result_ids[i];
1478         ul.appendChild(li);
1479         }
1480
1481       // check if last selected item is still in result list
1482       if (this.ksearch_selected!==null)
1483         {
1484         p = find_in_array(this.ksearch_selected, a_result_ids);
1485         if (p>=0 && ul.childNodes)
1486           {
1487           ul.childNodes[p].setAttribute('id', 'rcmksearchSelected');
1488           this.set_classname(ul.childNodes[p], 'selected', true);
1489           }
1490         else
1491           this.ksearch_selected = null;
1492         }
1493       
1494       // if no item selected, select the first one
1495       if (this.ksearch_selected===null)
1496         {
1497         ul.firstChild.setAttribute('id', 'rcmksearchSelected');
1498         this.set_classname(ul.firstChild, 'selected', true);
1499         this.ksearch_selected = a_result_ids[0];
1500         }
1501
1502       // resize the containing layer to fit the list
1503       //this.ksearch_pane.resize(ul.offsetWidth, ul.offsetHeight);
1504     
1505       // move the results pane right under the input box and make it visible
1506       var pos = rcube_get_object_pos(this.ksearch_input);
1507       this.ksearch_pane.move(pos.x, pos.y+this.ksearch_input.offsetHeight);
1508       this.ksearch_pane.show(1); 
1509       }
1510     // hide results pane
1511     else
1512       this.ksearch_hide();
1513     };
1514
1515
1516   this.ksearch_blur = function(e, obj)
1517     {
1518     if (this.ksearch_timer)
1519       clearTimeout(this.ksearch_timer);
1520
1521     this.ksearch_value = '';      
1522     this.ksearch_input = null;
1523     
1524     this.ksearch_hide();
1525     };
1526
1527
1528   this.ksearch_hide = function()
1529     {
1530     this.ksearch_selected = null;
1531     
1532     if (this.ksearch_pane)
1533       this.ksearch_pane.show(0);    
1534     };
1535
1536
1537
1538   /*********************************************************/
1539   /*********         address book methods          *********/
1540   /*********************************************************/
1541
1542
1543   this.list_contacts = function(page)
1544     {
1545     var add_url = '';
1546     var target = window;
1547     
1548     if (page && this.current_page==page)
1549       return false;
1550
1551     // load contacts remotely
1552     if (this.gui_objects.contactslist)
1553       {
1554       this.list_contacts_remote(page);
1555       return;
1556       }
1557
1558     if (this.env.contentframe && window.frames && window.frames[this.env.contentframe])
1559       {
1560       target = window.frames[this.env.contentframe];
1561       add_url = '&_framed=1';
1562       }
1563
1564     this.set_busy(true, 'loading');
1565     location.href = this.env.comm_path+(page ? '&_page='+page : '')+add_url;
1566     };
1567
1568
1569   // send remote request to load contacts list
1570   this.list_contacts_remote = function(page)
1571     {
1572     // clear list
1573     var table = this.gui_objects.contactslist;
1574     var tbody = document.createElement('TBODY');
1575     table.insertBefore(tbody, table.tBodies[0]);
1576     table.tBodies[1].style.display = 'none';
1577     
1578     this.contact_rows = new Array();
1579     this.list_rows = this.contact_rows;
1580
1581     // send request to server
1582     var url = page ? '&_page='+page : '';
1583     this.set_busy(true, 'loading');
1584     this.http_request('list', url);
1585     };
1586
1587
1588   // load contact record
1589   this.load_contact = function(cid, action, framed)
1590     {
1591     var add_url = '';
1592     var target = window;
1593     if (this.env.contentframe && window.frames && window.frames[this.env.contentframe])
1594       {
1595       add_url = '&_framed=1';
1596       target = window.frames[this.env.contentframe];
1597       document.getElementById(this.env.contentframe).style.visibility = 'inherit';
1598       }
1599     else if (framed)
1600       return false;
1601       
1602     //if (this.env.framed && add_url=='')
1603     //  add_url = '&_framed=1';
1604     
1605     if (action && (cid || action=='add'))
1606       {
1607       this.set_busy(true);
1608       target.location.href = this.env.comm_path+'&_action='+action+'&_cid='+cid+add_url;
1609       }
1610     };
1611
1612
1613   this.delete_contacts = function()
1614     {
1615     // exit if no mailbox specified or if selection is empty
1616     if (!(this.selection.length || this.env.cid))
1617       return;
1618     
1619     var a_cids = new Array();
1620
1621     if (this.env.cid)
1622       a_cids[a_cids.length] = this.env.cid;
1623     else
1624       {
1625       var id;
1626       for (var n=0; n<this.selection.length; n++)
1627         {
1628         id = this.selection[n];
1629         a_cids[a_cids.length] = id;
1630       
1631         // 'remove' row from list (just hide it)
1632         if (this.contact_rows[id].obj)
1633           this.contact_rows[id].obj.style.display = 'none';
1634         }
1635
1636       // hide content frame if we delete the currently displayed contact
1637       if (this.selection.length==1 && this.env.contentframe)
1638         {
1639         var elm = document.getElementById(this.env.contentframe);
1640         elm.style.visibility = 'hidden';
1641         }
1642       }
1643
1644     // send request to server
1645     this.http_request('delete', '_cid='+a_cids.join(',')+'&_from='+(this.env.action ? this.env.action : ''));
1646     };
1647
1648
1649   // update a contact record in the list
1650   this.update_contact_row = function(cid, cols_arr)
1651     {
1652     if (!this.contact_rows[cid] || !this.contact_rows[cid].obj)
1653       return false;
1654       
1655     var row = this.contact_rows[cid].obj;
1656     for (var c=0; c<cols_arr.length; c++)
1657       if (row.cells[c])
1658         row.cells[c].innerHTML = cols_arr[c];
1659
1660     };
1661   
1662
1663
1664   /*********************************************************/
1665   /*********        user settings methods          *********/
1666   /*********************************************************/
1667
1668
1669   // load contact record
1670   this.load_identity = function(id, action)
1671     {
1672     if (action=='edit-identity' && (!id || id==this.env.iid))
1673       return;
1674
1675     var add_url = '';
1676     var target = window;
1677     if (this.env.contentframe && window.frames && window.frames[this.env.contentframe])
1678       {
1679       add_url = '&_framed=1';
1680       target = window.frames[this.env.contentframe];
1681       document.getElementById(this.env.contentframe).style.visibility = 'inherit';
1682       }
1683
1684     if (action && (id || action=='add-identity'))
1685       {
1686       this.set_busy(true);
1687       target.location.href = this.env.comm_path+'&_action='+action+'&_iid='+id+add_url;
1688       }
1689     };
1690
1691
1692
1693   this.delete_identity = function(id)
1694     {
1695     // exit if no mailbox specified or if selection is empty
1696     if (!(this.selection.length || this.env.iid))
1697       return;
1698     
1699     if (!id)
1700       id = this.env.iid ? this.env.iid : this.selection[0];
1701
1702 /*
1703     // 'remove' row from list (just hide it)
1704     if (this.identity_rows && this.identity_rows[id].obj)
1705       {
1706       this.clear_selection();
1707       this.identity_rows[id].obj.style.display = 'none';
1708       }
1709 */
1710
1711     // if (this.env.framed && id)
1712       this.set_busy(true);
1713       location.href = this.env.comm_path+'&_action=delete-identity&_iid='+id;
1714     // else if (id)
1715     //  this.http_request('delete-identity', '_iid='+id);
1716     };
1717
1718
1719   this.create_folder = function(name)
1720     {
1721     var form;
1722     if ((form = this.gui_objects.editform) && form.elements['_folder_name'])
1723       name = form.elements['_folder_name'].value;
1724
1725     if (name)
1726       this.http_request('create-folder', '_name='+escape(name));
1727     else if (form.elements['_folder_name'])
1728       form.elements['_folder_name'].focus();
1729     };
1730
1731
1732   this.delete_folder = function(folder)
1733     {
1734     if (folder)
1735       {
1736       for (var id in this.env.subscriptionrows)
1737         if (this.env.subscriptionrows[id]==folder)
1738           break;
1739           
1740       var row;
1741       if (id && (row = document.getElementById(id)))
1742         row.style.display = 'none';
1743
1744       this.http_request('delete-folder', '_mboxes='+escape(folder));
1745       }
1746     };
1747
1748
1749   this.subscribe_folder = function(folder)
1750     {
1751     var form;
1752     if ((form = this.gui_objects.editform) && form.elements['_unsubscribed'])
1753       this.change_subscription('_unsubscribed', '_subscribed', 'subscribe');
1754     else if (folder)
1755       this.http_request('subscribe', '_mboxes='+escape(folder));
1756     };
1757
1758
1759   this.unsubscribe_folder = function(folder)
1760     {
1761     var form;
1762     if ((form = this.gui_objects.editform) && form.elements['_subscribed'])
1763       this.change_subscription('_subscribed', '_unsubscribed', 'unsubscribe');
1764     else if (folder)
1765       this.http_request('unsubscribe', '_mboxes='+escape(folder));
1766     };
1767     
1768
1769   this.change_subscription = function(from, to, action)
1770     {
1771     var form;
1772     if (form = this.gui_objects.editform)
1773       {
1774       var a_folders = new Array();
1775       var list_from = form.elements[from];
1776
1777       for (var i=0; list_from && i<list_from.options.length; i++)
1778         {
1779         if (list_from.options[i] && list_from.options[i].selected)
1780           {
1781           a_folders[a_folders.length] = list_from.options[i].value;
1782           list_from[i] = null;
1783           i--;
1784           }
1785         }
1786
1787       // yes, we have some folders selected
1788       if (a_folders.length)
1789         {
1790         var list_to = form.elements[to];
1791         var index;
1792         
1793         for (var n=0; n<a_folders.length; n++)
1794           {
1795           index = list_to.options.length;
1796           list_to[index] = new Option(a_folders[n]);
1797           }
1798           
1799         this.http_request(action, '_mboxes='+escape(a_folders.join(',')));
1800         }
1801       }
1802       
1803     };
1804
1805
1806    // add a new folder to the subscription list by cloning a folder row
1807    this.add_folder_row = function(name)
1808      {
1809      if (!this.gui_objects.subscriptionlist)
1810        return false;
1811
1812      var tbody = this.gui_objects.subscriptionlist.tBodies[0];
1813      var id = tbody.childNodes.length+1;
1814      
1815      // clone a table row
1816      var row = this.clone_table_row(tbody.rows[0]);
1817      row.id = 'rcmrow'+id;
1818      tbody.appendChild(row);
1819
1820      // add to folder/row-ID map
1821      this.env.subscriptionrows[row.id] = name;
1822
1823      // set folder name
1824      row.cells[0].innerHTML = name;
1825      if (row.cells[1].firstChild.tagName=='INPUT')
1826        {
1827        row.cells[1].firstChild.value = name;
1828        row.cells[1].firstChild.checked = true;
1829        }
1830      if (row.cells[2].firstChild.tagName=='A')
1831        row.cells[2].firstChild.onclick = new Function(this.ref+".command('delete-folder','"+name+"')");
14eafe 1832
T 1833     var form;
1834     if ((form = this.gui_objects.editform) && form.elements['_folder_name'])
1835       form.elements['_folder_name'].value = '';
4e17e6 1836      };
T 1837
1838
1839   // duplicate a specific table row
1840   this.clone_table_row = function(row)
1841     {
1842     var cell, td;
1843     var new_row = document.createElement('TR');
1844     for(var n=0; n<row.childNodes.length; n++)
1845       {
1846       cell = row.childNodes[n];
1847       td = document.createElement('TD');
1848
1849       if (cell.className)
1850         td.className = cell.className;
1851       if (cell.align)
1852         td.setAttribute('align', cell.align);
1853         
1854       td.innerHTML = cell.innerHTML;
1855       new_row.appendChild(td);
1856       }
1857     
1858     return new_row;
1859     };
1860
1861
1862   /*********************************************************/
1863   /*********           GUI functionality           *********/
1864   /*********************************************************/
1865
1866
1867   // eable/disable buttons for page shifting
1868   this.set_page_buttons = function()
1869     {
1870     this.enable_command('nextpage', (this.env.pagecount > this.env.current_page));
1871     this.enable_command('previouspage', (this.env.current_page > 1));
1872     }
1873
1874
1875   // set button to a specific state
1876   this.set_button = function(command, state)
1877     {
1878     var a_buttons = this.buttons[command];
1879     var button, obj;
1880
1881     if(!a_buttons || !a_buttons.length)
1882       return;
1883
1884     for(var n=0; n<a_buttons.length; n++)
1885       {
1886       button = a_buttons[n];
1887       obj = document.getElementById(button.id);
1888
1889       // get default/passive setting of the button
1890       if (obj && button.type=='image' && !button.status)
1891         button.pas = obj._original_src ? obj._original_src : obj.src;
1892       else if (obj && !button.status)
1893         button.pas = String(obj.className);
1894
1895       // set image according to button state
1896       if (obj && button.type=='image' && button[state])
1897         {
1898         button.status = state;        
1899         obj.src = button[state];
1900         }
1901       // set class name according to button state
1902       else if (obj && typeof(button[state])!='undefined')
1903         {
1904         button.status = state;        
1905         obj.className = button[state];        
1906         }
1907       // disable/enable input buttons
1908       if (obj && button.type=='input')
1909         {
1910         button.status = state;
1911         obj.disabled = !state;
1912         }
1913       }
1914     };
1915
1916
1917   // mouse over button
1918   this.button_over = function(command, id)
1919     {
1920     var a_buttons = this.buttons[command];
1921     var button, img;
1922
1923     if(!a_buttons || !a_buttons.length)
1924       return;
1925
1926     for(var n=0; n<a_buttons.length; n++)
1927       {
1928       button = a_buttons[n];
1929       if(button.id==id && button.status=='act')
1930         {
1931         img = document.getElementById(button.id);
1932         if (img && button.over)
1933           img.src = button.over;
1934         }
1935       }
1936     };
1937
1938
1939   // mouse out of button
1940   this.button_out = function(command, id)
1941     {
1942     var a_buttons = this.buttons[command];
1943     var button, img;
1944
1945     if(!a_buttons || !a_buttons.length)
1946       return;
1947
1948     for(var n=0; n<a_buttons.length; n++)
1949       {
1950       button = a_buttons[n];
1951       if(button.id==id && button.status=='act')
1952         {
1953         img = document.getElementById(button.id);
1954         if (img && button.act)
1955           img.src = button.act;
1956         }
1957       }
1958     };
1959
1960
1961   // set/unset a specific class name
1962   this.set_classname = function(obj, classname, set)
1963     {
1964     var reg = new RegExp('\s*'+classname, 'i');
1965     if (!set && obj.className.match(reg))
1966       obj.className = obj.className.replace(reg, '');
1967     else if (set && !obj.className.match(reg))
1968       obj.className += ' '+classname;
1969     };
1970
1971
1972   // display a specific alttext
1973   this.alttext = function(text)
1974     {
1975     
1976     };
1977
1978
1979   // display a system message
1980   this.display_message = function(msg, type, hold)
1981     {
1982     if (!this.loaded)  // save message in order to display after page loaded
1983       {
1984       this.pending_message = new Array(msg, type);
1985       return true;
1986       }
1987     
1988     if (!this.gui_objects.message)
1989       return false;
1990       
1991     if (this.message_timer)
1992       clearTimeout(this.message_timer);
1993     
1994     var cont = msg;
1995     if (type)
1996       cont = '<div class="'+type+'">'+cont+'</div>';
a95e0e 1997
T 1998     this.gui_objects.message._rcube = this;
4e17e6 1999     this.gui_objects.message.innerHTML = cont;
T 2000     this.gui_objects.message.style.display = 'block';
a95e0e 2001     
T 2002     if (type!='loading')
2003       this.gui_objects.message.onmousedown = function(){ this._rcube.hide_message(); return true; };
4e17e6 2004     
T 2005     if (!hold)
2006       this.message_timer = setTimeout(this.ref+'.hide_message()', this.message_time);
2007     };
2008
2009
2010   // make a message row disapear
2011   this.hide_message = function()
2012     {
2013     if (this.gui_objects.message)
a95e0e 2014       {
4e17e6 2015       this.gui_objects.message.style.display = 'none';
a95e0e 2016       this.gui_objects.message.onmousedown = null;
T 2017       }
4e17e6 2018     };
T 2019
2020
2021   // mark a mailbox as selected and set environment variable
2022   this.select_mailbox = function(mbox)
2023     {
2024     if (this.gui_objects.mailboxlist)
2025       {
2026       var item, reg, text_obj;
2027       var s_mbox = String(mbox).toLowerCase().replace(this.mbox_expression, '');
2028       var s_current = this.env.mailbox.toLowerCase().replace(this.mbox_expression, '');
597170 2029       var nodes = this.gui_objects.mailboxlist.getElementsByTagName('LI');
T 2030       
2031       for (var n=0; n<nodes.length; n++)
4e17e6 2032         {
597170 2033         item = nodes[n];
4e17e6 2034         if (item.className && item.className.indexOf('mailbox '+s_mbox+' ')>=0)
T 2035           this.set_classname(item, 'selected', true);
2036         else if (item.className && item.className.indexOf('mailbox '+s_current)>=0)
2037           this.set_classname(item, 'selected', false);          
2038         }
2039       }
2040     
2041     this.env.mailbox = mbox;
2042     };
2043
2044
2045   // create a table row in the message list
2046   this.add_message_row = function(uid, cols, flags, attachment)
2047     {
2048     if (!this.gui_objects.messagelist || !this.gui_objects.messagelist.tBodies[0])
2049       return false;
2050     
2051     var tbody = this.gui_objects.messagelist.tBodies[0];
2052     var rowcount = tbody.rows.length;
2053     var even = rowcount%2;
2054     
2055     this.env.messages[uid] = {replied:flags.replied?1:0,
2056                               unread:flags.unread?1:0};
2057     
2058     var row = document.createElement('TR');
2059     row.id = 'rcmrow'+uid;
2060     row.className = 'message '+(even ? 'even' : 'odd')+(flags.unread ? ' unread' : '');
2061
2062     if (this.in_selection(uid))
2063       row.className += ' selected';
2064
2065     var icon = flags.unread && this.env.unreadicon ? this.env.unreadicon :
2066                (flags.replied && this.env.repliedicon ? this.env.repliedicon : this.env.messageicon);
2067
2068     var col = document.createElement('TD');
2069     col.className = 'icon';
2070     col.innerHTML = icon ? '<img src="'+icon+'" alt="" border="0" />' : '';
2071     row.appendChild(col);
2072
2073     // add each submitted col
2074     for (var c in cols)
2075       {
2076       col = document.createElement('TD');
2077       col.className = String(c).toLowerCase();
2078       col.innerHTML = cols[c];
2079       row.appendChild(col);
2080       }
2081
2082     col = document.createElement('TD');
2083     col.className = 'icon';
2084     col.innerHTML = attachment && this.env.attachmenticon ? '<img src="'+this.env.attachmenticon+'" alt="" border="0" />' : '';
2085     row.appendChild(col);
2086     
2087     tbody.appendChild(row);
2088     this.init_message_row(row);
2089     };
2090
2091
2092   // replace content of row count display
2093   this.set_rowcount = function(text)
2094     {
2095     if (this.gui_objects.countdisplay)
2096       this.gui_objects.countdisplay.innerHTML = text;
2097
2098     // update page navigation buttons
2099     this.set_page_buttons();
2100     };
2101
2102
2103   // update the mailboxlist
2104   this.set_unread_count = function(mbox, count)
2105     {
2106     if (!this.gui_objects.mailboxlist)
2107       return false;
2108       
2109     mbox = String(mbox).toLowerCase().replace(this.mbox_expression, '');
2110     
2111     var item, reg, text_obj;
2112     for (var n=0; n<this.gui_objects.mailboxlist.childNodes.length; n++)
2113       {
2114       item = this.gui_objects.mailboxlist.childNodes[n];
2115
2116       if (item.className && item.className.indexOf('mailbox '+mbox)>=0)
2117         {
2118         // set new text
2119         text_obj = item.firstChild;
2120         reg = /\s+\([0-9]+\)$/i;
2121
2122         if (count && text_obj.innerHTML.match(reg))
2123           text_obj.innerHTML = text_obj.innerHTML.replace(reg, ' ('+count+')');
2124         else if (count)
2125           text_obj.innerHTML += ' ('+count+')';
2126         else
2127           text_obj.innerHTML = text_obj.innerHTML.replace(reg, '');
2128           
2129         // set the right classes
2130         this.set_classname(item, 'unread', count>0 ? true : false);        
2131         break;
2132         }
2133       }
2134     };
2135
2136
2137   // add row to contacts list
2138   this.add_contact_row = function(cid, cols)
2139     {
2140     if (!this.gui_objects.contactslist || !this.gui_objects.contactslist.tBodies[0])
2141       return false;
2142     
2143     var tbody = this.gui_objects.contactslist.tBodies[0];
2144     var rowcount = tbody.rows.length;
2145     var even = rowcount%2;
2146     
2147     var row = document.createElement('TR');
2148     row.id = 'rcmrow'+cid;
2149     row.className = 'contact '+(even ? 'even' : 'odd');
2150     
2151     if (this.in_selection(cid))
2152       row.className += ' selected';
2153
2154     // add each submitted col
2155     for (var c in cols)
2156       {
2157       col = document.createElement('TD');
2158       col.className = String(c).toLowerCase();
2159       col.innerHTML = cols[c];
2160       row.appendChild(col);
2161       }
2162     
2163     tbody.appendChild(row);
2164     this.init_table_row(row, 'contact_rows');
2165     };
2166
2167
2168
2169   /********************************************************/
2170   /*********          drag & drop methods         *********/
2171   /********************************************************/
2172
2173
2174   this.drag_mouse_move = function(e)
2175     {
2176     if (this.drag_start)
2177       {
2178       if (!this.draglayer)
2179         this.draglayer = new rcube_layer('rcmdraglayer', {x:0, y:0, width:300, vis:0, zindex:2000});
2180       
2181       // get subjects of selectedd messages
2182       var names = '';
2183       var c, subject, obj;
2184       for(var n=0; n<this.selection.length; n++)
2185         {
2186         if (n>12)  // only show 12 lines
2187           {
2188           names += '...';
2189           break;
2190           }
2191
2192         if (this.message_rows[this.selection[n]].obj)
2193           {
2194           obj = this.message_rows[this.selection[n]].obj;
2195           subject = '';
2196
2197           for(c=0; c<obj.childNodes.length; c++)
2198             if (!subject && obj.childNodes[c].nodeName=='TD' && obj.childNodes[c].firstChild && obj.childNodes[c].firstChild.nodeType==3)
2199               {
2200               subject = obj.childNodes[c].firstChild.data;
2201               names += (subject.length > 50 ? subject.substring(0, 50)+'...' : subject) + '<br />';
2202               }
2203           }
2204         }
2205         
2206       this.draglayer.write(names);
2207       this.draglayer.show(1);
2208       }
2209
2210     var pos = this.get_mouse_pos(e);
2211     this.draglayer.move(pos.x+20, pos.y-5);
2212     
2213     this.drag_start = false;
2214     this.drag_active = true;
2215     
2216     return false;
2217     };
2218
2219
2220   this.drag_mouse_up = function()
2221     {
2222     document.onmousemove = null;
2223     
2224     if (this.draglayer && this.draglayer.visible)
2225       this.draglayer.show(0);
2226       
2227     this.drag_active = false;
2228     
2229     return false;
2230     };
2231
2232
2233
2234   /********************************************************/
2235   /*********        remote request methods        *********/
2236   /********************************************************/
2237
2238
2239   // send a http request to the server
2240   this.http_request = function(action, querystring)
2241     {
2242     if (window.XMLHttpRequest)
2243       this.request_obj = new XMLHttpRequest();
2244     else if (window.ActiveXObject)
2245       this.request_obj = new ActiveXObject("Microsoft.XMLHTTP");
2246     else
2247       {
2248       
2249       }
2250
2251     querystring += '&_remote=1';
2252     
2253     // add timestamp to request url to avoid cacheing problems in Safari
2254     if (bw.safari)
2255       querystring += '&_ts='+(new Date().getTime());
2256
2257     // send request
2258     if (this.request_obj)
2259       {
2260       // prompt('request', this.env.comm_path+'&_action='+escape(action)+'&'+querystring);
2261       console('HTTP request: '+this.env.comm_path+'&_action='+escape(action)+'&'+querystring);
2262       this.set_busy(true);
2263       this.request_action = action;
2264       this.request_obj.onreadystatechange = function(){ rcube_webmail_client.http_response(); };
2265       this.request_obj.open('GET', this.env.comm_path+'&_action='+escape(action)+'&'+querystring);
2266       this.request_obj.send(null);
2267       }
2268     };
2269
2270
2271   // handle http response
2272   this.http_response = function()
2273     {
2274     if (this.request_obj.readyState == 4) // || this.request_obj.readyState == 2)
2275       {
2276       var ctype = this.request_obj.getResponseHeader('Content-Type');
2277       if (ctype)
2278         ctype = String(ctype).toLowerCase();
2279
2280       this.set_busy(false);
2281
2282   console(this.request_obj.responseText);
2283
2284       // if we get javascript code from server -> execute it
2285       if (this.request_obj.responseText && (ctype=='text/javascript' || ctype=='application/x-javascript'))
2286         eval(this.request_obj.responseText);
2287
2288       // process the response data according to the sent action
2289       switch (this.request_action)
2290         {
2291         case 'delete':
2292         case 'moveto':
2293           if (this.env.action=='show')
2294             this.command('list');
2295           break;
2296           
2297         case 'list':
2298           this.enable_command('select-all', 'select-none', this.env.messagecount ? true : false);
2299           break;
2300         }
2301       }
2302     };
2303
2304
2305   /********************************************************/
2306   /*********            helper methods            *********/
2307   /********************************************************/
2308   
2309   // check if we're in show mode or if we have a unique selection
2310   // and return the message uid
2311   this.get_single_uid = function()
2312     {
2313     return this.env.uid ? this.env.uid : (this.selection.length==1 ? this.selection[0] : null);
2314     };
2315
2316   // same as above but for contacts
2317   this.get_single_cid = function()
2318     {
2319     return this.env.cid ? this.env.cid : (this.selection.length==1 ? this.selection[0] : null);
2320     };
2321
2322
2323   // check if Shift-key is pressed on event
2324   this.check_shiftkey = function(e)
2325     {
2326     if(!e && window.event)
2327       e = window.event;
2328
2329     if(bw.linux && bw.ns4 && e.modifiers)
2330       return true;
2331     else if((bw.ns4 && e.modifiers & Event.SHIFT_MASK) || (e && e.shiftKey))
2332       return true;
2333     else
2334       return false;
2335     }
2336
2337
2338   this.get_mouse_pos = function(e)
2339     {
2340     if(!e) e = window.event;
2341     var mX = (e.pageX) ? e.pageX : e.clientX;
2342     var mY = (e.pageY) ? e.pageY : e.clientY;
2343
2344     if(document.body && document.all)
2345       {
2346       mX += document.body.scrollLeft;
2347       mY += document.body.scrollTop;
2348       }
2349
2350     return { x:mX, y:mY };
2351     };
2352     
2353   
2354   this.get_caret_pos = function(obj)
2355     {
2356     if (typeof(obj.selectionEnd)!='undefined')
2357       return obj.selectionEnd;
2358
2359     else if (document.selection && document.selection.createRange)
2360       {
2361       var range = document.selection.createRange();
2362       if (range.parentElement()!=obj)
2363         return 0;
2364
2365       var gm = range.duplicate();
2366       if (obj.tagName=='TEXTAREA')
2367         gm.moveToElementText(obj);
2368       else
2369         gm.expand('textedit');
2370       
2371       gm.setEndPoint('EndToStart', range);
2372       var p = gm.text.length;
2373
2374       return p<=obj.value.length ? p : -1;
2375       }
2376
2377     else
2378       return obj.value.length;
2379     };
2380
2381
2382   this.set_caret2start = function(obj)
2383     {
2384     if (obj.createTextRange)
2385       {
2386       var range = obj.createTextRange();
2387       range.collapse(true);
2388       range.select();
2389       }
2390     else if (obj.setSelectionRange)
2391       obj.setSelectionRange(0,0);
2392
2393     obj.focus();
2394     };
2395
2396
2397   // set all fields of a form disabled
2398   this.lock_form = function(form, lock)
2399     {
2400     if (!form || !form.elements)
2401       return;
2402     
2403     var type;
2404     for (var n=0; n<form.elements.length; n++)
2405       {
2406       type = form.elements[n];
2407       if (type=='hidden')
2408         continue;
2409         
2410       form.elements[n].disabled = lock;
2411       }
2412     };
2413     
2414   }  // end object rcube_webmail
2415
2416
2417
2418
2419 function console(str)
2420   {
2421   if (document.debugform && document.debugform.console)
2422     document.debugform.console.value += str+'\n--------------------------------------\n';
2423   }
2424
2425
2426 // set onload handler
2427 window.onload = function(e)
2428   {
2429   if (window.rcube_webmail_client)
2430     rcube_webmail_client.init();
2431   };