bb3f3783f7d376a1b285e42eb70a8f05d8d40903
[citadel.git] / webcit / static / wclib.js
1 //
2 // $Id: wclib.js,v 625.2 2005/09/18 04:04:32 ajc Exp $
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) {browserType = "nn4"}
13 if (document.all) {browserType = "ie"}
14 if (window.navigator.userAgent.toLowerCase().match("gecko")) {
15         browserType= "gecko"
16 }
17
18 var ns6=document.getElementById&&!document.all;
19
20
21
22 // We love string tokenizers.
23 function extract_token(source_string, token_num, delimiter) {
24         var i = 0;
25         var extracted_string = source_string;
26
27         if (token_num > 0) {
28                 for (i=0; i<token_num; ++i) {
29                         var j = extracted_string.indexOf(delimiter);
30                         if (j >= 0) {
31                                 extracted_string = extracted_string.substr(j+1);
32                         }
33                 }
34         }
35
36         j = extracted_string.indexOf(delimiter);
37         if (j >= 0) {
38                 extracted_string = extracted_string.substr(0, j);
39         }
40
41         return extracted_string;
42 }
43
44
45
46 // This code handles the popups for important-messages.
47 function hide_imsg_popup() {
48         if (browserType == "gecko" )
49                 document.poppedLayer = eval('document.getElementById(\'important_message\')');
50         else if (browserType == "ie")
51                 document.poppedLayer = eval('document.all[\'important_message\']');
52         else
53                 document.poppedLayer = eval('document.layers[\'`important_message\']');
54
55         document.poppedLayer.style.visibility = "hidden";
56 }
57
58
59 // This function activates the ajax-powered recipient autocompleters on the message entry screen.
60 function activate_entmsg_autocompleters() {
61         new Ajax.Autocompleter('cc_id', 'cc_name_choices', 'cc_autocomplete', {} );
62         new Ajax.Autocompleter('bcc_id', 'bcc_name_choices', 'bcc_autocomplete', {} );
63         new Ajax.Autocompleter('recp_id', 'recp_name_choices', 'recp_autocomplete', {} );
64 }
65
66
67
68 // Toggle the icon bar between menu/roomlist...
69 var which_div_expanded = null;
70 var num_drop_targets = 0;
71 var drop_targets_elements = new Array();
72 var drop_targets_roomnames = new Array();
73
74 function switch_to_room_list() {
75         $('iconbar').innerHTML = $('iconbar').innerHTML.substr(0, $('iconbar').innerHTML.indexOf('switch'));
76         new Ajax.Updater('iconbar', 'iconbar_ajax_rooms', { method: 'get' } );
77 }
78
79 function expand_floor(floor_div) {
80         if (which_div_expanded != null) {
81                 if ($(which_div_expanded) != null) {
82                         $(which_div_expanded).style.display = 'none' ;
83                 }
84         }
85
86         // clicking on the already-expanded floor causes the whole list to collapse
87         if (which_div_expanded == floor_div) {
88                 which_div_expanded = null;
89
90                 // notify the server that no floors are expanded
91                 new Ajax.Request(
92                         'set_floordiv_expanded/-1', {
93                                 method: 'post'
94                         }
95                 );
96                 return true;
97         }
98
99         // expand the requested floor
100         $(floor_div).style.display = 'block';
101         which_div_expanded = floor_div;
102
103         // notify the server of which floor is expanded
104         new Ajax.Request(
105                 'set_floordiv_expanded/'+floor_div, {
106                         method: 'post'
107                 }
108         );
109 }
110
111 function switch_to_menu_buttons() {
112         which_div_expanded = null;
113         num_drop_targets = 0;
114         new Ajax.Updater('iconbar', 'iconbar_ajax_menu', { method: 'get' } );
115 }
116
117
118 // Static variables for mailbox view...
119 //
120 var CtdlNumMsgsSelected = 0;
121 var CtdlMsgsSelected = new Array();
122 var CtdlLastMsgnumSelected = 0;
123
124 // This gets called when you single click on a message in the mailbox view.
125 // We know that the element id of the table row will be the letter 'm' plus the message number.
126 //
127 function CtdlSingleClickMsg(evt, msgnum) {
128
129         // Clear the preview pane until we load the new message
130         $('preview_pane').innerHTML = '';
131
132         // De-select any messages that were already selected, *unless* the Ctrl or
133         // Shift key is being pressed, in which case the user wants multi select
134         // or group select.
135         if ( (!evt.ctrlKey) && (!evt.shiftKey) ) {
136                 if (CtdlNumMsgsSelected > 0) {
137                         for (i=0; i<CtdlNumMsgsSelected; ++i) {
138                                 $('m'+CtdlMsgsSelected[i]).style.backgroundColor = '#fff';
139                                 $('m'+CtdlMsgsSelected[i]).style.color = '#000';
140                         }
141                         CtdlNumMsgsSelected = 0;
142                 }
143         }
144
145         // For multi select ... is the message being clicked already selected?
146         already_selected = 0;
147         if ( (evt.ctrlKey) && (CtdlNumMsgsSelected > 0) ) {
148                 for (i=0; i<CtdlNumMsgsSelected; ++i) {
149                         if (CtdlMsgsSelected[i] == msgnum) {
150                                 already_selected = 1;
151                                 already_selected_pos = i;
152                         }
153                 }
154         }
155
156         // Now select (or de-select) the message
157         if ( (evt.ctrlKey) && (already_selected == 1) ) {
158
159                 // Deselect: first un-highlight it...
160                 $('m'+msgnum).style.backgroundColor = '#fff';
161                 $('m'+msgnum).style.color = '#000';
162
163                 // Then remove it from the selected messages list.
164                 for (i=already_selected_pos; i<(CtdlNumMsgsSelected-1); ++i) {
165                         CtdlMsgsSelected[i] = CtdlMsgsSelected[i+1];
166                 }
167                 CtdlNumMsgsSelected = CtdlNumMsgsSelected - 1;
168                 
169         }
170
171         else if (evt.shiftKey) {
172                 
173                 // Group select: first clear everything out...
174                 if (CtdlNumMsgsSelected > 0) {
175                         for (i=0; i<CtdlNumMsgsSelected; ++i) {
176                                 $('m'+CtdlMsgsSelected[i]).style.backgroundColor = '#fff';
177                                 $('m'+CtdlMsgsSelected[i]).style.color = '#000';
178                         }
179                 }
180                 CtdlNumMsgsSelected = 0;
181
182                 // Then highlight and select the group.
183                 // Traverse the table looking for a row whose ID contains the desired msgnum
184
185                 var in_the_group = 0;
186                 var is_edge = 0;
187                 var table = $('summary_headers');
188                 if (table) {
189                         for (var r = 0; r < table.rows.length; r++) {
190                                 var thename = table.rows[r].id;
191                                 if ( (thename.substr(1) == msgnum) || (thename.substr(1) == CtdlLastMsgnumSelected) ) {
192                                         in_the_group = 1 - in_the_group;
193                                         is_edge = 1;
194                                 }
195                                 else {
196                                         is_edge = 0;
197                                 }
198                                 if ( (in_the_group == 1) || (is_edge == 1) ) {
199                                         // Highlight it...
200                                         table.rows[r].style.backgroundColor='#69aaff';
201                                         table.rows[r].style.color='#fff';
202
203                                         // And add it to the selected messages list.
204                                         CtdlNumMsgsSelected = CtdlNumMsgsSelected + 1;
205                                         CtdlMsgsSelected[CtdlNumMsgsSelected-1] = thename.substr(1);
206                                 }
207                         }
208                 }
209         }
210
211         else {
212                 // Select: first highlight it...
213                 $('m'+msgnum).style.backgroundColor='#69aaff';
214                 $('m'+msgnum).style.color='#fff';
215
216                 // Then add it to the selected messages list.
217                 CtdlNumMsgsSelected = CtdlNumMsgsSelected + 1;
218                 CtdlMsgsSelected[CtdlNumMsgsSelected-1] = msgnum;
219
220                 // Update the preview pane
221                 new Ajax.Updater('preview_pane', 'msg/'+msgnum, { method: 'get' } );
222         
223                 // Mark the message as read
224                 new Ajax.Request(
225                         'ajax_servcmd', {
226                                 method: 'post',
227                                 parameters: 'g_cmd=SEEN '+msgnum+'|1',
228                                 onComplete: CtdlRemoveTheUnseenBold(msgnum)
229                         }
230                 );
231         }
232         
233         // Save the selected position in case the user does a group select next time.
234         CtdlLastMsgnumSelected = msgnum;
235
236         return false;           // try to defeat the default click behavior
237 }
238
239 // Delete selected messages.
240 function CtdlDeleteSelectedMessages(evt) {
241         
242         if (CtdlNumMsgsSelected < 1) {
243                 // Nothing to delete, so exit silently.
244                 return false;
245         }
246         for (i=0; i<CtdlNumMsgsSelected; ++i) {
247                 if (parseInt(room_is_trash) > 0) {
248                         new Ajax.Request(
249                                 'ajax_servcmd', {
250                                         method: 'post',
251                                         parameters: 'g_cmd=DELE ' + CtdlMsgsSelected[i],
252                                         onComplete: CtdlClearDeletedMsg(CtdlMsgsSelected[i])
253                                 }
254                         );
255                 }
256                 else {
257                         new Ajax.Request(
258                                 'ajax_servcmd', {
259                                         method: 'post',
260                                         parameters: 'g_cmd=MOVE ' + CtdlMsgsSelected[i] + '|_TRASH_|0',
261                                         onComplete: CtdlClearDeletedMsg(CtdlMsgsSelected[i])
262                                 }
263                         );
264                 }
265         }
266         CtdlNumMsgsSelected = 0;
267
268         // Clear the preview pane too.
269         $('preview_pane').innerHTML = '';
270 }
271
272
273 // Move selected messages.
274 function CtdlMoveSelectedMessages(evt, target_roomname) {
275         
276         if (CtdlNumMsgsSelected < 1) {
277                 // Nothing to delete, so exit silently.
278                 return false;
279         }
280         for (i=0; i<CtdlNumMsgsSelected; ++i) {
281                 new Ajax.Request(
282                         'ajax_servcmd', {
283                                 method:'post',
284                                 parameters:'g_cmd=MOVE ' + CtdlMsgsSelected[i] + '|' + target_roomname + '|0',
285                                 onComplete:CtdlClearDeletedMsg(CtdlMsgsSelected[i])
286                         }
287                 );
288         }
289         CtdlNumMsgsSelected = 0;
290
291         // Clear the preview pane too.
292         $('preview_pane').innerHTML = '';
293 }
294
295
296
297 // This gets called when the user touches the keyboard after selecting messages...
298 function CtdlMsgListKeyPress(evt) {
299         if(document.all) {                              // aIEeee
300                 var whichKey = window.event.keyCode;
301         }
302         else {                                          // non-sux0r browsers
303                 var whichKey = evt.which;
304         }
305         if (whichKey == 46) {                           // DELETE key
306                 CtdlDeleteSelectedMessages(evt);
307         }
308         return true;
309 }
310
311 // Take the boldface away from a message to indicate that it has been seen.
312 function CtdlRemoveTheUnseenBold(msgnum) {
313         $('m'+msgnum).style.fontWeight='normal';
314 }
315
316 // A message has been deleted, so yank it from the list.
317 function CtdlClearDeletedMsg(msgnum) {
318
319
320         // Traverse the table looking for a row whose ID contains the desired msgnum
321         var table = $('summary_headers');
322         if (table) {
323                 for (var r = 0; r < table.rows.length; r++) {
324                         var thename = table.rows[r].id;
325                         if (thename.substr(1) == msgnum) {
326                                 try {
327                                         table.deleteRow(r);
328                                 }
329                                 catch(e) {
330                                         alert('error: browser failed to clear row ' + r);
331                                 }
332                         }
333                 }
334         }
335         else {                                          // if we can't delete it,
336                 new Effect.Squish('m'+msgnum);          // just hide it.
337         }
338
339
340 }
341
342 // These functions called when the user down-clicks on the message list resizer bar
343
344 var saved_x = 0;
345 var saved_y = 0;
346
347 function CtdlResizeMsgListMouseUp(evt) {
348         document.onmouseup = null;
349         document.onmousemove = null;
350         if (document.layers) {
351                 document.releaseEvents(Event.MOUSEUP | Event.MOUSEMOVE);
352         }
353         return true;
354 }
355
356 function CtdlResizeMsgListMouseMove(evt) {
357         y = (ns6 ? evt.clientY : event.clientY);
358         increment = y - saved_y;
359
360         // First move the bottom of the message list...
361         d = $('message_list');
362         if (d.offsetHeight){
363                 divHeight = d.offsetHeight;
364         }
365         else if (d.style.pixelHeight) {
366                 divHeight = d.style.pixelHeight;
367         }
368         d.style.height = (divHeight + increment) + 'px';
369
370         // Then move the top of the preview pane...
371         d = $('preview_pane');
372         if (d.offsetTop){
373                 divTop = d.offsetTop;
374         }
375         else if (d.style.pixelTop) {
376                 divTop = d.style.pixelTop;
377         }
378         d.style.top = (divTop + increment) + 'px';
379
380         // Resize the bottom of the preview pane...
381         d = $('preview_pane');
382         if (d.offsetHeight){
383                 divHeight = d.offsetHeight;
384         }
385         else if (d.style.pixelHeight) {
386                 divHeight = d.style.pixelHeight;
387         }
388         d.style.height = (divHeight - increment) + 'px';
389
390         // Then move the top of the slider bar.
391         d = $('resize_msglist');
392         if (d.offsetTop){
393                 divTop = d.offsetTop;
394         }
395         else if (d.style.pixelTop) {
396                 divTop = d.style.pixelTop;
397         }
398         d.style.top = (divTop + increment) + 'px';
399
400         saved_y = y;
401         return true;
402 }
403
404 function CtdlResizeMsgListMouseDown(evt) {
405         saved_y = (ns6 ? evt.clientY : event.clientY);
406         document.onmouseup = CtdlResizeMsgListMouseUp;
407         document.onmousemove = CtdlResizeMsgListMouseMove;
408         if (document.layers) {
409                 document.captureEvents(Event.MOUSEUP | Event.MOUSEMOVE);
410         }
411         return false;           // disable the default action
412 }
413
414
415
416 // These functions handle drag and drop message moving
417
418 var mm_div = null;
419
420 function CtdlMoveMsgMouseDown(evt, msgnum) {
421
422         // do the highlight first
423         CtdlSingleClickMsg(evt, msgnum);
424
425         // Now handle the possibility of dragging
426         saved_x = (ns6 ? evt.clientX : event.clientX);
427         saved_y = (ns6 ? evt.clientY : event.clientY);
428         document.onmouseup = CtdlMoveMsgMouseUp;
429         document.onmousemove = CtdlMoveMsgMouseMove;
430         if (document.layers) {
431                 document.captureEvents(Event.MOUSEUP | Event.MOUSEMOVE);
432         }
433
434         return false;
435 }
436
437 function CtdlMoveMsgMouseMove(evt) {
438         x = (ns6 ? evt.clientX : event.clientX);
439         y = (ns6 ? evt.clientY : event.clientY);
440
441         if ( (x == saved_x) && (y == saved_y) ) {
442                 return true;
443         }
444
445         if (CtdlNumMsgsSelected < 1) { 
446                 return true;
447         }
448
449         if (!mm_div) {
450
451
452                 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>";
453                 for (i=0; i<CtdlNumMsgsSelected; ++i) {
454                         drag_o_text = drag_o_text + 
455                                 ctdl_ts_getInnerText(
456                                         $('m'+CtdlMsgsSelected[i]).cells[0]
457                                 ) + '<br>';
458                 }
459                 drag_o_text = drag_o_text + "<div>";
460
461                 mm_div = document.createElement("DIV");
462                 mm_div.style.position='absolute';
463                 mm_div.style.top = y + 'px';
464                 mm_div.style.left = x + 'px';
465                 mm_div.style.pixelHeight = '300';
466                 mm_div.style.pixelWidth = '300';
467                 mm_div.innerHTML = drag_o_text;
468                 document.body.appendChild(mm_div);
469         }
470         else {
471                 mm_div.style.top = y + 'px';
472                 mm_div.style.left = x + 'px';
473         }
474
475         return false;   // prevent the default mouse action from happening?
476 }
477
478 function CtdlMoveMsgMouseUp(evt) {
479         document.onmouseup = null;
480         document.onmousemove = null;
481         if (document.layers) {
482                 document.releaseEvents(Event.MOUSEUP | Event.MOUSEMOVE);
483         }
484
485         if (mm_div) {
486                 document.body.removeChild(mm_div);      
487                 mm_div = null;
488         }
489
490         if (num_drop_targets < 1) {     // nowhere to drop
491                 return true;
492         }
493
494         // Did we release the mouse button while hovering over a drop target?
495         // NOTE: this only works cross-browser because the iconbar div is always
496         //      positioned at 0,0.  Browsers differ in whether the 'offset'
497         //      functions return pos relative to the document or parent.
498
499         for (i=0; i<num_drop_targets; ++i) {
500
501                 x = (ns6 ? evt.clientX : event.clientX);
502                 y = (ns6 ? evt.clientY : event.clientY);
503
504                 l = parseInt(drop_targets_elements[i].offsetLeft);
505                 t = parseInt(drop_targets_elements[i].offsetTop);
506                 r = parseInt(drop_targets_elements[i].offsetLeft)
507                   + parseInt(drop_targets_elements[i].offsetWidth);
508                 b = parseInt(drop_targets_elements[i].offsetTop)
509                   + parseInt(drop_targets_elements[i].offsetHeight);
510
511                 /* alert('Offsets are: ' + l + ' ' + t + ' ' + r + ' ' + b + '.'); */
512         
513                 if ( (x >= l) && (x <= r) && (y >= t) && (y <= b) ) {
514                         // Yes, we dropped it on a hotspot.
515                         CtdlMoveSelectedMessages(evt, drop_targets_roomnames[i]);
516                         return true;
517                 }
518         }
519
520         return true;
521 }
522
523
524 function ctdl_ts_getInnerText(el) {
525         if (typeof el == "string") return el;
526         if (typeof el == "undefined") { return el };
527         if (el.innerText) return el.innerText;  //Not needed but it is faster
528         var str = "";
529         
530         var cs = el.childNodes;
531         var l = cs.length;
532         for (var i = 0; i < l; i++) {
533                 switch (cs[i].nodeType) {
534                         case 1: //ELEMENT_NODE
535                                 str += ts_getInnerText(cs[i]);
536                                 break;
537                         case 3: //TEXT_NODE
538                                 str += cs[i].nodeValue;
539                                 break;
540                 }
541         }
542         return str;
543 }
544
545
546
547 // This function handles the creation of new notes in the "Notes" view.
548 //
549 function add_new_note() {
550
551         new_eid = Math.random() + '';
552         new_eid = new_eid.substr(3);
553
554         $('new_notes_here').innerHTML = $('new_notes_here').innerHTML
555                 + '<IMG ALIGN=MIDDLE src=\"static/storenotes_48x.gif\">'
556                 + '<span id=\"note' + new_eid + '\">' + Date() + '</span><br />'
557         ;
558
559         new Ajax.InPlaceEditor('note' + new_eid,
560                 'updatenote?eid=' + new_eid , {rows:5,cols:72});
561 }