fc5b56dead664d19978f38f0f0b84c7bdecbd5d0
[citadel.git] / webcit / static / wclib.js
1 //
2 // $Id$
3 //
4 // JavaScript function library for WebCit.
5 //
6 //
7
8
9 var browserType;
10 var room_is_trash = 0;
11
12 if (document.layers) {
13         browserType = "nn4";
14 }
15 if (document.all) {
16         browserType = "ie";
17 }
18 if (window.navigator.userAgent.toLowerCase().match("gecko")) {
19         browserType= "gecko";
20 }
21
22 var ns6=document.getElementById&&!document.all;
23 Event.observe(window, 'load', ToggleTaskDateOrNoDateActivate);
24 Event.observe(window, 'load', taskViewActivate);
25 function CtdlRandomString()  {
26         return((Math.random()+'').substr(3));
27 }
28
29
30
31 // We love string tokenizers.
32 function extract_token(source_string, token_num, delimiter) {
33         var i = 0;
34         var extracted_string = source_string;
35
36         if (token_num > 0) {
37                 for (i=0; i<token_num; ++i) {
38                         var j = extracted_string.indexOf(delimiter);
39                         if (j >= 0) {
40                                 extracted_string = extracted_string.substr(j+1);
41                         }
42                 }
43         }
44
45         j = extracted_string.indexOf(delimiter);
46         if (j >= 0) {
47                 extracted_string = extracted_string.substr(0, j);
48         }
49
50         return extracted_string;
51 }
52
53
54
55 // This code handles the popups for important-messages.
56 function hide_imsg_popup() {
57         if (browserType == "gecko") {
58                 document.poppedLayer = eval('document.getElementById(\'important_message\')');
59         }
60         else if (browserType == "ie") {
61                 document.poppedLayer = eval('document.all[\'important_message\']');
62         }
63         else {
64                 document.poppedLayer = eval('document.layers[\'`important_message\']');
65         }
66
67         document.poppedLayer.style.visibility = "hidden";
68 }
69
70
71 // This function activates the ajax-powered recipient autocompleters on the message entry screen.
72 function activate_entmsg_autocompleters() {
73         new Ajax.Autocompleter('cc_id', 'cc_name_choices', 'cc_autocomplete', {} );
74         new Ajax.Autocompleter('bcc_id', 'bcc_name_choices', 'bcc_autocomplete', {} );
75         new Ajax.Autocompleter('recp_id', 'recp_name_choices', 'recp_autocomplete', {} );
76 }
77
78
79
80 // Toggle the icon bar between menu/roomlist...
81 var which_div_expanded = null;
82 var num_drop_targets = 0;
83 var drop_targets_elements = new Array();
84 var drop_targets_roomnames = new Array();
85
86 function switch_to_room_list() {
87         $('iconbar').innerHTML = $('iconbar').innerHTML.substr(0, $('iconbar').innerHTML.indexOf('switch'));
88         CtdlLoadScreen('iconbar');
89         new Ajax.Updater('iconbar', 'iconbar_ajax_rooms', { method: 'get' } );
90 }
91
92 function expand_floor(floor_div) {
93         if (which_div_expanded != null) {
94                 if ($(which_div_expanded) != null) {
95                         $(which_div_expanded).style.display = 'none' ;
96                 }
97         }
98
99         // clicking on the already-expanded floor causes the whole list to collapse
100         if (which_div_expanded == floor_div) {
101                 which_div_expanded = null;
102
103                 // notify the server that no floors are expanded
104                 new Ajax.Request(
105                         'set_floordiv_expanded/-1', {
106                                 method: 'post'
107                         }
108                 );
109                 return true;
110         }
111
112         // expand the requested floor
113         $(floor_div).style.display = 'block';
114         which_div_expanded = floor_div;
115
116         // notify the server of which floor is expanded
117         new Ajax.Request(
118                 'set_floordiv_expanded/'+floor_div, {
119                         method: 'post'
120                 }
121         );
122 }
123
124 function switch_to_menu_buttons() {
125         which_div_expanded = null;
126         num_drop_targets = 0;
127         CtdlLoadScreen('iconbar');
128         new Ajax.Updater('iconbar', 'iconbar_ajax_menu', { method: 'get' } );
129 }
130
131
132 // Static variables for mailbox view...
133 //
134 var CtdlNumMsgsSelected = 0;
135 var CtdlMsgsSelected = new Array();
136 var CtdlLastMsgnumSelected = 0;
137
138 // This gets called when you single click on a message in the mailbox view.
139 // We know that the element id of the table row will be the letter 'm' plus the message number.
140 //
141 function CtdlSingleClickMsg(evt, msgnum) {
142
143         // Clear the preview pane until we load the new message
144         $('preview_pane').innerHTML = '';
145
146         // De-select any messages that were already selected, *unless* the Ctrl or
147         // Shift key is being pressed, in which case the user wants multi select
148         // or group select.
149         if ( (!evt.ctrlKey) && (!evt.shiftKey) ) {
150                 if (CtdlNumMsgsSelected > 0) {
151                         for (i=0; i<CtdlNumMsgsSelected; ++i) {
152                                 $('m'+CtdlMsgsSelected[i]).style.backgroundColor = '#fff';
153                                 $('m'+CtdlMsgsSelected[i]).style.color = '#000';
154                         }
155                         CtdlNumMsgsSelected = 0;
156                 }
157         }
158
159         // For multi select ... is the message being clicked already selected?
160         already_selected = 0;
161         if ( (evt.ctrlKey) && (CtdlNumMsgsSelected > 0) ) {
162                 for (i=0; i<CtdlNumMsgsSelected; ++i) {
163                         if (CtdlMsgsSelected[i] == msgnum) {
164                                 already_selected = 1;
165                                 already_selected_pos = i;
166                         }
167                 }
168         }
169
170         // Now select (or de-select) the message
171         if ( (evt.ctrlKey) && (already_selected == 1) ) {
172
173                 // Deselect: first un-highlight it...
174                 $('m'+msgnum).style.backgroundColor = '#fff';
175                 $('m'+msgnum).style.color = '#000';
176
177                 // Then remove it from the selected messages list.
178                 for (i=already_selected_pos; i<(CtdlNumMsgsSelected-1); ++i) {
179                         CtdlMsgsSelected[i] = CtdlMsgsSelected[i+1];
180                 }
181                 CtdlNumMsgsSelected = CtdlNumMsgsSelected - 1;
182                 
183         }
184
185         else if (evt.shiftKey) {
186                 
187                 // Group select: first clear everything out...
188                 if (CtdlNumMsgsSelected > 0) {
189                         for (i=0; i<CtdlNumMsgsSelected; ++i) {
190                                 $('m'+CtdlMsgsSelected[i]).style.backgroundColor = '#fff';
191                                 $('m'+CtdlMsgsSelected[i]).style.color = '#000';
192                         }
193                 }
194                 CtdlNumMsgsSelected = 0;
195
196                 // Then highlight and select the group.
197                 // Traverse the table looking for a row whose ID contains the desired msgnum
198
199                 var in_the_group = 0;
200                 var is_edge = 0;
201                 var table = $('summary_headers');
202                 if (table) {
203                         for (var r = 0; r < table.rows.length; r++) {
204                                 var thename = table.rows[r].id;
205                                 if ( (thename.substr(1) == msgnum) || (thename.substr(1) == CtdlLastMsgnumSelected) ) {
206                                         in_the_group = 1 - in_the_group;
207                                         is_edge = 1;
208                                 }
209                                 else {
210                                         is_edge = 0;
211                                 }
212                                 if ( (in_the_group == 1) || (is_edge == 1) ) {
213                                         // Highlight it...
214                                         table.rows[r].style.backgroundColor='#69aaff';
215                                         table.rows[r].style.color='#fff';
216
217                                         // And add it to the selected messages list.
218                                         CtdlNumMsgsSelected = CtdlNumMsgsSelected + 1;
219                                         CtdlMsgsSelected[CtdlNumMsgsSelected-1] = thename.substr(1);
220                                 }
221                         }
222                 }
223         }
224
225         else {
226                 // Select: first highlight it...
227                 $('m'+msgnum).style.backgroundColor='#69aaff';
228                 $('m'+msgnum).style.color='#fff';
229
230                 // Then add it to the selected messages list.
231                 CtdlNumMsgsSelected = CtdlNumMsgsSelected + 1;
232                 CtdlMsgsSelected[CtdlNumMsgsSelected-1] = msgnum;
233
234                 // Gradient
235                 CtdlLoadScreen('preview_pane');
236                 // Update the preview pane
237                 new Ajax.Updater('preview_pane', 'msg/'+msgnum, { method: 'get' } );
238         
239                 // Mark the message as read
240                 new Ajax.Request(
241                         'ajax_servcmd', {
242                                 method: 'post',
243                                 parameters: 'g_cmd=SEEN '+msgnum+'|1',
244                                 onComplete: CtdlRemoveTheUnseenBold(msgnum)
245                         }
246                 );
247         }
248         
249         // Save the selected position in case the user does a group select next time.
250         CtdlLastMsgnumSelected = msgnum;
251
252         return false;           // try to defeat the default click behavior
253 }
254
255 // Delete selected messages.
256 function CtdlDeleteSelectedMessages(evt) {
257         
258         if (CtdlNumMsgsSelected < 1) {
259                 // Nothing to delete, so exit silently.
260                 return false;
261         }
262         for (i=0; i<CtdlNumMsgsSelected; ++i) {
263                 if (parseInt(room_is_trash) > 0) {
264                         new Ajax.Request(
265                                 'ajax_servcmd', {
266                                         method: 'post',
267                                         parameters: 'g_cmd=DELE ' + CtdlMsgsSelected[i],
268                                         onComplete: CtdlClearDeletedMsg(CtdlMsgsSelected[i])
269                                 }
270                         );
271                 }
272                 else {
273                         new Ajax.Request(
274                                 'ajax_servcmd', {
275                                         method: 'post',
276                                         parameters: 'g_cmd=MOVE ' + CtdlMsgsSelected[i] + '|_TRASH_|0',
277                                         onComplete: CtdlClearDeletedMsg(CtdlMsgsSelected[i])
278                                 }
279                         );
280                 }
281         }
282         CtdlNumMsgsSelected = 0;
283
284         // Clear the preview pane too.
285         $('preview_pane').innerHTML = '';
286 }
287
288
289 // Move selected messages.
290 function CtdlMoveSelectedMessages(evt, target_roomname) {
291         
292         if (CtdlNumMsgsSelected < 1) {
293                 // Nothing to delete, so exit silently.
294                 return false;
295         }
296         for (i=0; i<CtdlNumMsgsSelected; ++i) {
297                 new Ajax.Request(
298                         'ajax_servcmd', {
299                                 method:'post',
300                                 parameters:'g_cmd=MOVE ' + CtdlMsgsSelected[i] + '|' + target_roomname + '|0',
301                                 onComplete:CtdlClearDeletedMsg(CtdlMsgsSelected[i])
302                         }
303                 );
304         }
305         CtdlNumMsgsSelected = 0;
306
307         // Clear the preview pane too.
308         $('preview_pane').innerHTML = '';
309 }
310
311
312
313 // This gets called when the user touches the keyboard after selecting messages...
314 function CtdlMsgListKeyPress(evt) {
315         if(document.all) {                              // aIEeee
316                 var whichKey = window.event.keyCode;
317         }
318         else {                                          // non-sux0r browsers
319                 var whichKey = evt.which;
320         }
321         if (whichKey == 46) {                           // DELETE key
322                 CtdlDeleteSelectedMessages(evt);
323         }
324         return true;
325 }
326
327 // Take the boldface away from a message to indicate that it has been seen.
328 function CtdlRemoveTheUnseenBold(msgnum) {
329         $('m'+msgnum).style.fontWeight='normal';
330 }
331
332 // A message has been deleted, so yank it from the list.
333 function CtdlClearDeletedMsg(msgnum) {
334
335
336         // Traverse the table looking for a row whose ID contains the desired msgnum
337         var table = $('summary_headers');
338         if (table) {
339                 for (var r = 0; r < table.rows.length; r++) {
340                         var thename = table.rows[r].id;
341                         if (thename.substr(1) == msgnum) {
342                                 try {
343                                         table.deleteRow(r);
344                                 }
345                                 catch(e) {
346                                         alert('error: browser failed to clear row ' + r);
347                                 }
348                         }
349                 }
350         }
351         else {                                          // if we can't delete it,
352                 new Effect.Squish('m'+msgnum);          // just hide it.
353         }
354
355
356 }
357
358 // These functions called when the user down-clicks on the message list resizer bar
359
360 var saved_x = 0;
361 var saved_y = 0;
362
363 function CtdlResizeMsgListMouseUp(evt) {
364         document.onmouseup = null;
365         document.onmousemove = null;
366         if (document.layers) {
367                 document.releaseEvents(Event.MOUSEUP | Event.MOUSEMOVE);
368         }
369         return true;
370 }
371
372 function CtdlResizeMsgListMouseMove(evt) {
373         y = (ns6 ? evt.clientY : event.clientY);
374         increment = y - saved_y;
375
376         // First move the bottom of the message list...
377         d = $('message_list');
378         if (d.offsetHeight){
379                 divHeight = d.offsetHeight;
380         }
381         else if (d.style.pixelHeight) {
382                 divHeight = d.style.pixelHeight;
383         }
384         d.style.height = (divHeight + increment) + 'px';
385
386         // Then move the top of the preview pane...
387         d = $('preview_pane');
388         if (d.offsetTop){
389                 divTop = d.offsetTop;
390         }
391         else if (d.style.pixelTop) {
392                 divTop = d.style.pixelTop;
393         }
394         d.style.top = (divTop + increment) + 'px';
395
396         // Resize the bottom of the preview pane...
397         d = $('preview_pane');
398         if (d.offsetHeight){
399                 divHeight = d.offsetHeight;
400         }
401         else if (d.style.pixelHeight) {
402                 divHeight = d.style.pixelHeight;
403         }
404         d.style.height = (divHeight - increment) + 'px';
405
406         // Then move the top of the slider bar.
407         d = $('resize_msglist');
408         if (d.offsetTop){
409                 divTop = d.offsetTop;
410         }
411         else if (d.style.pixelTop) {
412                 divTop = d.style.pixelTop;
413         }
414         d.style.top = (divTop + increment) + 'px';
415
416         saved_y = y;
417         return true;
418 }
419
420 function CtdlResizeMsgListMouseDown(evt) {
421         saved_y = (ns6 ? evt.clientY : event.clientY);
422         document.onmouseup = CtdlResizeMsgListMouseUp;
423         document.onmousemove = CtdlResizeMsgListMouseMove;
424         if (document.layers) {
425                 document.captureEvents(Event.MOUSEUP | Event.MOUSEMOVE);
426         }
427         return false;           // disable the default action
428 }
429
430
431
432
433
434 // These functions handle moving sticky notes around the screen by dragging them
435
436 var uid_of_note_being_dragged = 0;
437 var saved_cursor_style = 'default';
438 var note_was_dragged = 0;
439
440 function NotesDragMouseUp(evt) {
441         document.onmouseup = null;
442         document.onmousemove = null;
443         if (document.layers) {
444                 document.releaseEvents(Event.MOUSEUP | Event.MOUSEMOVE);
445         }
446
447         d = $('note-' + uid_of_note_being_dragged);
448         d.style.cursor = saved_cursor_style;
449
450         // If any motion actually occurred, submit an ajax http call to record it to the server
451         if (note_was_dragged > 0) {
452                 p = 'note_uid=' + uid_of_note_being_dragged
453                         + '&left=' + d.style.left
454                         + '&top=' + d.style.top
455                         + '&r=' + CtdlRandomString();
456                 new Ajax.Request(
457                         'ajax_update_note',
458                         {
459                                 method: 'post',
460                                 parameters: p
461                         }
462                 );
463         }
464
465         uid_of_note_being_dragged = '';
466         return true;
467 }
468
469 function NotesDragMouseMove(evt) {
470         x = (ns6 ? evt.clientX : event.clientX);
471         x_increment = x - saved_x;
472         y = (ns6 ? evt.clientY : event.clientY);
473         y_increment = y - saved_y;
474
475         // Move the div
476         d = $('note-' + uid_of_note_being_dragged);
477
478         divTop = parseInt(d.style.top);
479         divLeft = parseInt(d.style.left);
480
481         d.style.top = (divTop + y_increment) + 'px';
482         d.style.left = (divLeft + x_increment) + 'px';
483
484         saved_x = x;
485         saved_y = y;
486         note_was_dragged = 1;
487         return true;
488 }
489
490
491 function NotesDragMouseDown(evt, uid) {
492         saved_x = (ns6 ? evt.clientX : event.clientX);
493         saved_y = (ns6 ? evt.clientY : event.clientY);
494         document.onmouseup = NotesDragMouseUp;
495         document.onmousemove = NotesDragMouseMove;
496         if (document.layers) {
497                 document.captureEvents(Event.MOUSEUP | Event.MOUSEMOVE);
498         }
499         uid_of_note_being_dragged = uid;
500         d = $('note-' + uid_of_note_being_dragged);
501         saved_cursor_style = d.style.cursor;
502         d.style.cursor = 'move';
503         return false;           // disable the default action
504 }
505
506
507 // Called when the user clicks on the palette icon of a sticky note to change its color.
508 // It toggles the color selector visible or invisible.
509
510 function NotesClickPalette(evt, uid) {
511         uid_of_note_being_colored = uid;
512         d = $('palette-' + uid_of_note_being_colored);
513
514         if (d.style.display) {
515                 if (d.style.display == 'none') {
516                         d.style.display = 'block';
517                 }
518                 else {
519                         d.style.display = 'none';
520                 }
521         }
522         else {
523                 d.style.display = 'block';
524         }
525
526         return true;
527 }
528
529
530 // Called when the user clicks on one of the colors in an open color selector.
531 // Sets the desired color and then closes the color selector.
532
533 function NotesClickColor(evt, uid, red, green, blue, notecolor, titlecolor) {
534         uid_of_note_being_colored = uid;
535         palette_button = $('palette-' + uid_of_note_being_colored);
536         note_div = $('note-' + uid_of_note_being_colored);
537         titlebar_div = $('titlebar-' + uid_of_note_being_colored);
538
539         // alert('FIXME red=' + red + ' green=' + green + ' blue=' + blue);
540
541         note_div.style.backgroundColor = notecolor;
542         titlebar_div.style.backgroundColor = titlecolor;
543         palette_button.style.display = 'none';
544
545         // submit an ajax http call to record it to the server
546         p = 'note_uid=' + uid_of_note_being_colored
547                 + '&red=' + red
548                 + '&green=' + green
549                 + '&blue=' + blue
550                 + '&r=' + CtdlRandomString();
551         new Ajax.Request(
552                 'ajax_update_note',
553                 {
554                         method: 'post',
555                         parameters: p
556                 }
557         );
558 }
559
560
561
562
563 // These functions handle resizing sticky notes by dragging the resize handle
564
565 var uid_of_note_being_resized = 0;
566 var saved_cursor_style = 'default';
567 var note_was_resized = 0;
568
569 function NotesResizeMouseUp(evt) {
570         document.onmouseup = null;
571         document.onmousemove = null;
572         if (document.layers) {
573                 document.releaseEvents(Event.MOUSEUP | Event.MOUSEMOVE);
574         }
575
576         d = $('note-' + uid_of_note_being_resized);
577         d.style.cursor = saved_cursor_style;
578
579         // If any motion actually occurred, submit an ajax http call to record it to the server
580         if (note_was_resized > 0) {
581                 p = 'note_uid=' + uid_of_note_being_resized
582                         + '&width=' + d.style.width
583                         + '&height=' + d.style.height
584                         + '&r=' + CtdlRandomString();
585                 new Ajax.Request(
586                         'ajax_update_note',
587                         {
588                                 method: 'post',
589                                 parameters: p
590                         }
591                 );
592         }
593
594         uid_of_note_being_resized = '';
595         return false;           // disable the default action
596 }
597
598 function NotesResizeMouseMove(evt) {
599         x = (ns6 ? evt.clientX : event.clientX);
600         x_increment = x - saved_x;
601         y = (ns6 ? evt.clientY : event.clientY);
602         y_increment = y - saved_y;
603
604         // Move the div
605         d = $('note-' + uid_of_note_being_resized);
606
607         divTop = parseInt(d.style.height);
608         divLeft = parseInt(d.style.width);
609
610         d.style.height = (divTop + y_increment) + 'px';
611         d.style.width = (divLeft + x_increment) + 'px';
612
613         saved_x = x;
614         saved_y = y;
615         note_was_resized = 1;
616         return false;           // disable the default action
617 }
618
619
620 function NotesResizeMouseDown(evt, uid) {
621         saved_x = (ns6 ? evt.clientX : event.clientX);
622         saved_y = (ns6 ? evt.clientY : event.clientY);
623         document.onmouseup = NotesResizeMouseUp;
624         document.onmousemove = NotesResizeMouseMove;
625         if (document.layers) {
626                 document.captureEvents(Event.MOUSEUP | Event.MOUSEMOVE);
627         }
628         uid_of_note_being_resized = uid;
629         d = $('note-' + uid_of_note_being_resized);
630         saved_cursor_style = d.style.cursor;
631         d.style.cursor = 'move';
632         return false;           // disable the default action
633 }
634
635
636 function DeleteStickyNote(evt, uid, confirmation_prompt) {
637         uid_of_note_being_deleted = uid;
638         d = $('note-' + uid_of_note_being_deleted);
639
640         if (confirm(confirmation_prompt)) {
641                 new Effect.Puff(d);
642
643                 // submit an ajax http call to delete it on the server
644                 p = 'note_uid=' + uid_of_note_being_deleted
645                         + '&deletenote=yes'
646                         + '&r=' + CtdlRandomString();
647                 new Ajax.Request(
648                         'ajax_update_note',
649                         {
650                                 method: 'post',
651                                 parameters: p
652                         }
653                 );
654         }
655 }
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671 // These functions handle drag and drop message moving
672
673 var mm_div = null;
674
675 function CtdlMoveMsgMouseDown(evt, msgnum) {
676
677         // do the highlight first
678         CtdlSingleClickMsg(evt, msgnum);
679
680         // Now handle the possibility of dragging
681         saved_x = (ns6 ? evt.clientX : event.clientX);
682         saved_y = (ns6 ? evt.clientY : event.clientY);
683         document.onmouseup = CtdlMoveMsgMouseUp;
684         document.onmousemove = CtdlMoveMsgMouseMove;
685         if (document.layers) {
686                 document.captureEvents(Event.MOUSEUP | Event.MOUSEMOVE);
687         }
688
689         return false;
690 }
691
692 function CtdlMoveMsgMouseMove(evt) {
693         x = (ns6 ? evt.clientX : event.clientX);
694         y = (ns6 ? evt.clientY : event.clientY);
695
696         if ( (x == saved_x) && (y == saved_y) ) {
697                 return true;
698         }
699
700         if (CtdlNumMsgsSelected < 1) { 
701                 return true;
702         }
703
704         if (!mm_div) {
705
706
707                 drag_o_text = "<div style=\"overflow:none; background-color:#fff; color:#000; border: 1px solid black; filter:alpha(opacity=75); -moz-opacity:.75; opacity:.75;\"><tr><td>";
708                 for (i=0; i<CtdlNumMsgsSelected; ++i) {
709                         drag_o_text = drag_o_text + 
710                                 ctdl_ts_getInnerText(
711                                         $('m'+CtdlMsgsSelected[i]).cells[0]
712                                 ) + '<br>';
713                 }
714                 drag_o_text = drag_o_text + "<div>";
715
716                 mm_div = document.createElement("DIV");
717                 mm_div.style.position='absolute';
718                 mm_div.style.top = y + 'px';
719                 mm_div.style.left = x + 'px';
720                 mm_div.style.pixelHeight = '300';
721                 mm_div.style.pixelWidth = '300';
722                 mm_div.innerHTML = drag_o_text;
723                 document.body.appendChild(mm_div);
724         }
725         else {
726                 mm_div.style.top = y + 'px';
727                 mm_div.style.left = x + 'px';
728         }
729
730         return false;   // prevent the default mouse action from happening?
731 }
732
733 function CtdlMoveMsgMouseUp(evt) {
734         document.onmouseup = null;
735         document.onmousemove = null;
736         if (document.layers) {
737                 document.releaseEvents(Event.MOUSEUP | Event.MOUSEMOVE);
738         }
739
740         if (mm_div) {
741                 document.body.removeChild(mm_div);      
742                 mm_div = null;
743         }
744
745         if (num_drop_targets < 1) {     // nowhere to drop
746                 return true;
747         }
748
749         // Did we release the mouse button while hovering over a drop target?
750         // NOTE: this only works cross-browser because the iconbar div is always
751         //      positioned at 0,0.  Browsers differ in whether the 'offset'
752         //      functions return pos relative to the document or parent.
753
754         for (i=0; i<num_drop_targets; ++i) {
755
756                 x = (ns6 ? evt.clientX : event.clientX);
757                 y = (ns6 ? evt.clientY : event.clientY);
758
759                 l = parseInt(drop_targets_elements[i].offsetLeft);
760                 t = parseInt(drop_targets_elements[i].offsetTop);
761                 r = parseInt(drop_targets_elements[i].offsetLeft)
762                   + parseInt(drop_targets_elements[i].offsetWidth);
763                 b = parseInt(drop_targets_elements[i].offsetTop)
764                   + parseInt(drop_targets_elements[i].offsetHeight);
765
766                 /* alert('Offsets are: ' + l + ' ' + t + ' ' + r + ' ' + b + '.'); */
767         
768                 if ( (x >= l) && (x <= r) && (y >= t) && (y <= b) ) {
769                         // Yes, we dropped it on a hotspot.
770                         CtdlMoveSelectedMessages(evt, drop_targets_roomnames[i]);
771                         return true;
772                 }
773         }
774
775         return true;
776 }
777
778
779 function ctdl_ts_getInnerText(el) {
780         if (typeof el == "string") return el;
781         if (typeof el == "undefined") { return el };
782         if (el.innerText) return el.innerText;  //Not needed but it is faster
783         var str = "";
784         
785         var cs = el.childNodes;
786         var l = cs.length;
787         for (var i = 0; i < l; i++) {
788                 switch (cs[i].nodeType) {
789                         case 1: //ELEMENT_NODE
790                                 str += ts_getInnerText(cs[i]);
791                                 break;
792                         case 3: //TEXT_NODE
793                                 str += cs[i].nodeValue;
794                                 break;
795                 }
796         }
797         return str;
798 }
799
800
801 function CtdlShowRaw(msgnum) {
802 var customnav = document.createElement("span");
803 var mode_citadel = document.createElement("a");
804 mode_citadel.appendChild(document.createTextNode("Citadel Source"));
805 var mode_rfc822 = document.createElement("a");
806 mode_rfc822.appendChild(document.createTextNode(" RFC822 Source"));
807 mode_citadel.setAttribute("href","#");
808 mode_rfc822.setAttribute("href","#");
809 mode_rfc822.setAttribute("onclick","rawSwitch822('" + msgnum + "');");
810 mode_citadel.setAttribute("onclick","rawSwitchCitadel('" + msgnum + "');");
811 customnav.appendChild(mode_citadel);
812 customnav.appendChild(mode_rfc822);
813 customnav.setAttribute("class","floatcustomnav");
814 floatwindow("headerscreen","pre",customnav);
815 rawSwitch822(msgnum);
816 }
817
818 function rawSwitch822(msgnum) {
819 CtdlLoadScreen("headerscreen");
820 new Ajax.Updater("headerscreen", 
821 'ajax_servcmd_esc',
822  { method: 'post',parameters: 'g_cmd=MSG2 ' +msgnum  } );
823
824 }
825
826 function rawSwitchCitadel(msgnum) {
827 CtdlLoadScreen("headerscreen");
828 new Ajax.Updater("headerscreen", 
829 'ajax_servcmd_esc',
830  { method: 'post',parameters: 'g_cmd=MSG0 ' +msgnum  } );
831
832 }
833
834 function floatwindow(newdivid,contentelementtype,customnav) {
835 var windiv = document.createElement("div");
836 windiv.setAttribute("class","floatwindow");
837 var winid = newdivid+"_window";
838 windiv.setAttribute("id",winid);
839 var nav = document.createElement("div");
840 if (customnav != null) {
841 nav.appendChild(customnav);
842 }
843 var minimizeA = document.createElement("a");
844 var minimizeButton = document.createTextNode("Close");
845 minimizeA.appendChild(minimizeButton);
846 minimizeA.setAttribute("onclick","killFloatWindow(this);");
847 minimizeA.setAttribute("href","#");
848 nav.appendChild(minimizeA);
849 nav.setAttribute("class","floatnav");
850 windiv.appendChild(nav);
851 var contentarea = document.createElement("pre");
852 contentarea.setAttribute("class","floatcontent");
853 contentarea.setAttribute("id",newdivid);
854 windiv.appendChild(contentarea);
855 document.body.appendChild(windiv);
856 }
857 function killFloatWindow(caller) {
858 var span = caller.parentNode;
859 var fwindow = span.parentNode;
860 fwindow.parentNode.removeChild(fwindow);
861 }
862 // Place a gradient loadscreen on an element, e.g to use before Ajax.updater
863 function CtdlLoadScreen(elementid) {
864 var elem = document.getElementById(elementid);
865 elem.innerHTML = "<div align=center><br><table border=0 cellpadding=10 bgcolor=\"#ffffff\"><tr><td><img src=\"static/throbber.gif\" /><font color=\"#AAAAAA\">&nbsp;&nbsp;Loading....</font></td></tr></table><br /></div>";
866 }
867
868
869
870 // Pop open the address book (target_input is the INPUT field to populate)
871
872 function PopOpenAddressBook(target_input) {
873         $('address_book_popup').style.display = 'block';
874         p = 'target_input=' + target_input + '&r=' + CtdlRandomString();
875         new Ajax.Updater(
876                 'address_book_popup_middle_div',
877                 'display_address_book_middle_div',
878                 {
879                         method: 'get',
880                         parameters: p,
881                         evalScripts: true
882                 }
883         );
884 }
885
886 function PopulateAddressBookInnerDiv(which_addr_book, target_input) {
887         $('address_book_inner_div').innerHTML = "<div align=center><br><table border=0 cellpadding=10 bgcolor=\"#ffffff\"><tr><td><img src=\"static/throbber.gif\" /><font color=\"#AAAAAA\">&nbsp;&nbsp;Loading....</font></td></tr></table><br /></div>";
888         p = 'which_addr_book=' + which_addr_book
889           + '&target_input=' + target_input
890           + '&r=' + CtdlRandomString();
891         new Ajax.Updater(
892                 'address_book_inner_div',
893                 'display_address_book_inner_div',
894                 {
895                         method: 'get',
896                         parameters: p
897                 }
898         );
899 }
900
901 // What happens when a contact is selected from the address book popup
902 // (populate the specified target)
903
904 function AddContactsToTarget(target, whichaddr) {
905         while (whichaddr.selectedIndex != -1) {
906                 if (target.value.length > 0) {
907                         target.value = target.value + ', ';
908                 }
909                 target.value = target.value + whichaddr.value;
910                 whichaddr.options[whichaddr.selectedIndex].selected = false;
911         }
912 }
913
914 // Respond to a meeting invitation
915 function RespondToInvitation(question_divname, title_divname, msgnum, cal_partnum, sc) {
916         p = 'msgnum=' + msgnum + '&cal_partnum=' + cal_partnum + '&sc=' + sc ;
917         new Ajax.Updater(title_divname, 'respond_to_request', { method: 'post', parameters: p } );
918         Effect.Fade(question_divname, { duration: 0.5 });
919 }
920
921 // Handle a received RSVP
922 function HandleRSVP(question_divname, title_divname, msgnum, cal_partnum, sc) {
923         p = 'msgnum=' + msgnum + '&cal_partnum=' + cal_partnum + '&sc=' + sc ;
924         new Ajax.Updater(title_divname, 'handle_rsvp', { method: 'post', parameters: p } );
925         Effect.Fade(question_divname, { duration: 0.5 });
926 }
927 var fakeMouse = document.createEvent("MouseEvents");
928 fakeMouse.initMouseEvent("click", true, true, window, 
929         0,0,0,0,0, false, false, false, false, 0, null);
930 // TODO: Collapse into one function
931 function toggleTaskDtStart(event) {
932         var checkBox = $('nodtstart');
933         dtStart = document.getElementById("dtstart");
934         if (checkBox.checked) {
935                 dtStart.disabled = true;
936                 dtStart.style.textDecoration = "line-through";
937         } else {
938                 dtStart.disabled = false;
939                 dtStart.style.textDecoration = "";
940                 if (dtStart.value.length == 0)
941                         dtStart.dpck._initCurrentDate();
942         }
943 }
944 function toggleTaskDue(event) {
945         var checkBox = $('nodue');
946         dueField = document.getElementById("due");
947         if (checkBox.checked) {
948                 dueField.disabled = true;
949                 dueField.style.textDecoration = "line-through";
950         } else {
951                 dueField.disabled = false;
952                 dueField.style.textDecoration = "";
953                 if (dueField.value.length == 0)
954                         dueField.dpck._initCurrentDate();
955         }
956 }
957 function ToggleTaskDateOrNoDateActivate(event) {
958         var dtstart = document.getElementById("nodtstart");
959         if (dtstart != null) {
960                 toggleTaskDtStart(null);
961                 toggleTaskDue(null);
962                 $('nodtstart').observe('click', toggleTaskDtStart);
963                 $('nodue').observe('click', toggleTaskDue);
964         } 
965 }
966 function TaskViewGatherCategoriesFromTable() {
967         var table = $('taskview');
968         
969 }
970 function attachDatePicker(relative, wclang) {
971         var dpck = new DatePicker({
972         relative: relative,
973         language: wclang.substr(0,2),
974         disableFutureDate: false,
975         dateFormat: [ ["yyyy", "mm", "dd"], "-"],
976         showDuration: 0.2,
977         closeEffectDuration: 0.2
978         });
979         document.getElementById(relative).dpck = dpck; // attach a ref to it
980 }
981
982 function eventEditAllDay() {
983         var allDayCheck = $('alldayevent');
984         var dtend = $('dtendcell');
985
986         if (allDayCheck.checked) {
987                 //dtend.disabled = true;
988                 dtend.style.textDecoration = "line-through";
989         } else {
990                 //dtend_day.disabled = false;
991                 dtend.style.textDecoration = "";
992         }
993 }
994
995
996
997
998 // Functions which handle show/hide of various elements in the recurrence editor
999
1000 function RecurrenceShowHide() {
1001
1002         if ($('is_recur').checked) {
1003                 $('rrule_div').style.display = 'block';
1004         }
1005         else {
1006                 $('rrule_div').style.display = 'none';
1007         }
1008
1009         if ($('freq_selector').selectedIndex == 4) {
1010                 $('weekday_selector').style.display = 'block';
1011         }
1012         else {
1013                 $('weekday_selector').style.display = 'none';
1014         }
1015
1016         if ($('freq_selector').selectedIndex == 5) {
1017                 $('monthday_selector').style.display = 'block';
1018         }
1019         else {
1020                 $('monthday_selector').style.display = 'none';
1021         }
1022
1023         if ($('rrend_count').checked) {
1024                 $('rrcount').disabled = false;
1025         }
1026         else {
1027                 $('rrcount').disabled = true;
1028         }
1029
1030         if ($('rrend_until').checked) {
1031                 $('rruntil').disabled = false;
1032         }
1033         else {
1034                 $('rruntil').disabled = true;
1035         }
1036
1037         if ($('rrmonthtype_mday').checked) {
1038                 $('rrmday').disabled = false;
1039         }
1040         else {
1041                 $('rrmday').disabled = true;
1042         }
1043
1044         if ($('rrmonthtype_wday').checked) {
1045                 $('rrmweek').disabled = false;
1046                 $('rrmweekday').disabled = false;
1047         }
1048         else {
1049                 $('rrmweek').disabled = true;
1050                 $('rrmweekday').disabled = true;
1051         }
1052
1053         if ($('freq_selector').selectedIndex == 6) {
1054                 $('yearday_selector').style.display = 'block';
1055         }
1056         else {
1057                 $('yearday_selector').style.display = 'none';
1058         }
1059
1060         $('ymday').innerHTML = 'XXXX-' + $('dtstart').value.substr(5);
1061         $('rrmday').innerHTML = $('dtstart').value.substr(8);
1062
1063         if ($('rryeartype_ywday').checked) {
1064                 $('rrymweek').disabled = false;
1065                 $('rrymweekday').disabled = false;
1066                 $('rrymonth').disabled = false;
1067         }
1068         else {
1069                 $('rrymweek').disabled = true;
1070                 $('rrymweekday').disabled = true;
1071                 $('rrymonth').disabled = true;
1072         }
1073
1074 }