2 // $Id: wclib.js,v 625.2 2005/09/18 04:04:32 ajc Exp $
4 // JavaScript function library for WebCit.
10 var room_is_trash = 0;
12 if (document.layers) {browserType = "nn4"}
13 if (document.all) {browserType = "ie"}
14 if (window.navigator.userAgent.toLowerCase().match("gecko")) {
18 var ns6=document.getElementById&&!document.all;
22 // We love string tokenizers.
23 function extract_token(source_string, token_num, delimiter) {
25 var extracted_string = source_string;
28 for (i=0; i<token_num; ++i) {
29 var j = extracted_string.indexOf(delimiter);
31 extracted_string = extracted_string.substr(j+1);
36 j = extracted_string.indexOf(delimiter);
38 extracted_string = extracted_string.substr(0, j);
41 return extracted_string;
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\']');
53 document.poppedLayer = eval('document.layers[\'`important_message\']');
55 document.poppedLayer.style.visibility = "hidden";
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', {} );
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();
74 function switch_to_room_list() {
75 $('iconbar').innerHTML = $('iconbar').innerHTML.substr(0, $('iconbar').innerHTML.indexOf('switch'));
76 CtdlLoadScreen('iconbar');
77 new Ajax.Updater('iconbar', 'iconbar_ajax_rooms', { method: 'get' } );
80 function expand_floor(floor_div) {
81 if (which_div_expanded != null) {
82 if ($(which_div_expanded) != null) {
83 $(which_div_expanded).style.display = 'none' ;
87 // clicking on the already-expanded floor causes the whole list to collapse
88 if (which_div_expanded == floor_div) {
89 which_div_expanded = null;
91 // notify the server that no floors are expanded
93 'set_floordiv_expanded/-1', {
100 // expand the requested floor
101 $(floor_div).style.display = 'block';
102 which_div_expanded = floor_div;
104 // notify the server of which floor is expanded
106 'set_floordiv_expanded/'+floor_div, {
112 function switch_to_menu_buttons() {
113 which_div_expanded = null;
114 num_drop_targets = 0;
115 CtdlLoadScreen('iconbar');
116 new Ajax.Updater('iconbar', 'iconbar_ajax_menu', { method: 'get' } );
120 // Static variables for mailbox view...
122 var CtdlNumMsgsSelected = 0;
123 var CtdlMsgsSelected = new Array();
124 var CtdlLastMsgnumSelected = 0;
126 // This gets called when you single click on a message in the mailbox view.
127 // We know that the element id of the table row will be the letter 'm' plus the message number.
129 function CtdlSingleClickMsg(evt, msgnum) {
131 // Clear the preview pane until we load the new message
132 $('preview_pane').innerHTML = '';
134 // De-select any messages that were already selected, *unless* the Ctrl or
135 // Shift key is being pressed, in which case the user wants multi select
137 if ( (!evt.ctrlKey) && (!evt.shiftKey) ) {
138 if (CtdlNumMsgsSelected > 0) {
139 for (i=0; i<CtdlNumMsgsSelected; ++i) {
140 $('m'+CtdlMsgsSelected[i]).style.backgroundColor = '#fff';
141 $('m'+CtdlMsgsSelected[i]).style.color = '#000';
143 CtdlNumMsgsSelected = 0;
147 // For multi select ... is the message being clicked already selected?
148 already_selected = 0;
149 if ( (evt.ctrlKey) && (CtdlNumMsgsSelected > 0) ) {
150 for (i=0; i<CtdlNumMsgsSelected; ++i) {
151 if (CtdlMsgsSelected[i] == msgnum) {
152 already_selected = 1;
153 already_selected_pos = i;
158 // Now select (or de-select) the message
159 if ( (evt.ctrlKey) && (already_selected == 1) ) {
161 // Deselect: first un-highlight it...
162 $('m'+msgnum).style.backgroundColor = '#fff';
163 $('m'+msgnum).style.color = '#000';
165 // Then remove it from the selected messages list.
166 for (i=already_selected_pos; i<(CtdlNumMsgsSelected-1); ++i) {
167 CtdlMsgsSelected[i] = CtdlMsgsSelected[i+1];
169 CtdlNumMsgsSelected = CtdlNumMsgsSelected - 1;
173 else if (evt.shiftKey) {
175 // Group select: first clear everything out...
176 if (CtdlNumMsgsSelected > 0) {
177 for (i=0; i<CtdlNumMsgsSelected; ++i) {
178 $('m'+CtdlMsgsSelected[i]).style.backgroundColor = '#fff';
179 $('m'+CtdlMsgsSelected[i]).style.color = '#000';
182 CtdlNumMsgsSelected = 0;
184 // Then highlight and select the group.
185 // Traverse the table looking for a row whose ID contains the desired msgnum
187 var in_the_group = 0;
189 var table = $('summary_headers');
191 for (var r = 0; r < table.rows.length; r++) {
192 var thename = table.rows[r].id;
193 if ( (thename.substr(1) == msgnum) || (thename.substr(1) == CtdlLastMsgnumSelected) ) {
194 in_the_group = 1 - in_the_group;
200 if ( (in_the_group == 1) || (is_edge == 1) ) {
202 table.rows[r].style.backgroundColor='#69aaff';
203 table.rows[r].style.color='#fff';
205 // And add it to the selected messages list.
206 CtdlNumMsgsSelected = CtdlNumMsgsSelected + 1;
207 CtdlMsgsSelected[CtdlNumMsgsSelected-1] = thename.substr(1);
214 // Select: first highlight it...
215 $('m'+msgnum).style.backgroundColor='#69aaff';
216 $('m'+msgnum).style.color='#fff';
218 // Then add it to the selected messages list.
219 CtdlNumMsgsSelected = CtdlNumMsgsSelected + 1;
220 CtdlMsgsSelected[CtdlNumMsgsSelected-1] = msgnum;
223 CtdlLoadScreen('preview_pane');
224 // Update the preview pane
225 new Ajax.Updater('preview_pane', 'msg/'+msgnum, { method: 'get' } );
227 // Mark the message as read
231 parameters: 'g_cmd=SEEN '+msgnum+'|1',
232 onComplete: CtdlRemoveTheUnseenBold(msgnum)
237 // Save the selected position in case the user does a group select next time.
238 CtdlLastMsgnumSelected = msgnum;
240 return false; // try to defeat the default click behavior
243 // Delete selected messages.
244 function CtdlDeleteSelectedMessages(evt) {
246 if (CtdlNumMsgsSelected < 1) {
247 // Nothing to delete, so exit silently.
250 for (i=0; i<CtdlNumMsgsSelected; ++i) {
251 if (parseInt(room_is_trash) > 0) {
255 parameters: 'g_cmd=DELE ' + CtdlMsgsSelected[i],
256 onComplete: CtdlClearDeletedMsg(CtdlMsgsSelected[i])
264 parameters: 'g_cmd=MOVE ' + CtdlMsgsSelected[i] + '|_TRASH_|0',
265 onComplete: CtdlClearDeletedMsg(CtdlMsgsSelected[i])
270 CtdlNumMsgsSelected = 0;
272 // Clear the preview pane too.
273 $('preview_pane').innerHTML = '';
277 // Move selected messages.
278 function CtdlMoveSelectedMessages(evt, target_roomname) {
280 if (CtdlNumMsgsSelected < 1) {
281 // Nothing to delete, so exit silently.
284 for (i=0; i<CtdlNumMsgsSelected; ++i) {
288 parameters:'g_cmd=MOVE ' + CtdlMsgsSelected[i] + '|' + target_roomname + '|0',
289 onComplete:CtdlClearDeletedMsg(CtdlMsgsSelected[i])
293 CtdlNumMsgsSelected = 0;
295 // Clear the preview pane too.
296 $('preview_pane').innerHTML = '';
301 // This gets called when the user touches the keyboard after selecting messages...
302 function CtdlMsgListKeyPress(evt) {
303 if(document.all) { // aIEeee
304 var whichKey = window.event.keyCode;
306 else { // non-sux0r browsers
307 var whichKey = evt.which;
309 if (whichKey == 46) { // DELETE key
310 CtdlDeleteSelectedMessages(evt);
315 // Take the boldface away from a message to indicate that it has been seen.
316 function CtdlRemoveTheUnseenBold(msgnum) {
317 $('m'+msgnum).style.fontWeight='normal';
320 // A message has been deleted, so yank it from the list.
321 function CtdlClearDeletedMsg(msgnum) {
324 // Traverse the table looking for a row whose ID contains the desired msgnum
325 var table = $('summary_headers');
327 for (var r = 0; r < table.rows.length; r++) {
328 var thename = table.rows[r].id;
329 if (thename.substr(1) == msgnum) {
334 alert('error: browser failed to clear row ' + r);
339 else { // if we can't delete it,
340 new Effect.Squish('m'+msgnum); // just hide it.
346 // These functions called when the user down-clicks on the message list resizer bar
351 function CtdlResizeMsgListMouseUp(evt) {
352 document.onmouseup = null;
353 document.onmousemove = null;
354 if (document.layers) {
355 document.releaseEvents(Event.MOUSEUP | Event.MOUSEMOVE);
360 function CtdlResizeMsgListMouseMove(evt) {
361 y = (ns6 ? evt.clientY : event.clientY);
362 increment = y - saved_y;
364 // First move the bottom of the message list...
365 d = $('message_list');
367 divHeight = d.offsetHeight;
369 else if (d.style.pixelHeight) {
370 divHeight = d.style.pixelHeight;
372 d.style.height = (divHeight + increment) + 'px';
374 // Then move the top of the preview pane...
375 d = $('preview_pane');
377 divTop = d.offsetTop;
379 else if (d.style.pixelTop) {
380 divTop = d.style.pixelTop;
382 d.style.top = (divTop + increment) + 'px';
384 // Resize the bottom of the preview pane...
385 d = $('preview_pane');
387 divHeight = d.offsetHeight;
389 else if (d.style.pixelHeight) {
390 divHeight = d.style.pixelHeight;
392 d.style.height = (divHeight - increment) + 'px';
394 // Then move the top of the slider bar.
395 d = $('resize_msglist');
397 divTop = d.offsetTop;
399 else if (d.style.pixelTop) {
400 divTop = d.style.pixelTop;
402 d.style.top = (divTop + increment) + 'px';
408 function CtdlResizeMsgListMouseDown(evt) {
409 saved_y = (ns6 ? evt.clientY : event.clientY);
410 document.onmouseup = CtdlResizeMsgListMouseUp;
411 document.onmousemove = CtdlResizeMsgListMouseMove;
412 if (document.layers) {
413 document.captureEvents(Event.MOUSEUP | Event.MOUSEMOVE);
415 return false; // disable the default action
420 // These functions handle drag and drop message moving
424 function CtdlMoveMsgMouseDown(evt, msgnum) {
426 // do the highlight first
427 CtdlSingleClickMsg(evt, msgnum);
429 // Now handle the possibility of dragging
430 saved_x = (ns6 ? evt.clientX : event.clientX);
431 saved_y = (ns6 ? evt.clientY : event.clientY);
432 document.onmouseup = CtdlMoveMsgMouseUp;
433 document.onmousemove = CtdlMoveMsgMouseMove;
434 if (document.layers) {
435 document.captureEvents(Event.MOUSEUP | Event.MOUSEMOVE);
441 function CtdlMoveMsgMouseMove(evt) {
442 x = (ns6 ? evt.clientX : event.clientX);
443 y = (ns6 ? evt.clientY : event.clientY);
445 if ( (x == saved_x) && (y == saved_y) ) {
449 if (CtdlNumMsgsSelected < 1) {
456 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>";
457 for (i=0; i<CtdlNumMsgsSelected; ++i) {
458 drag_o_text = drag_o_text +
459 ctdl_ts_getInnerText(
460 $('m'+CtdlMsgsSelected[i]).cells[0]
463 drag_o_text = drag_o_text + "<div>";
465 mm_div = document.createElement("DIV");
466 mm_div.style.position='absolute';
467 mm_div.style.top = y + 'px';
468 mm_div.style.left = x + 'px';
469 mm_div.style.pixelHeight = '300';
470 mm_div.style.pixelWidth = '300';
471 mm_div.innerHTML = drag_o_text;
472 document.body.appendChild(mm_div);
475 mm_div.style.top = y + 'px';
476 mm_div.style.left = x + 'px';
479 return false; // prevent the default mouse action from happening?
482 function CtdlMoveMsgMouseUp(evt) {
483 document.onmouseup = null;
484 document.onmousemove = null;
485 if (document.layers) {
486 document.releaseEvents(Event.MOUSEUP | Event.MOUSEMOVE);
490 document.body.removeChild(mm_div);
494 if (num_drop_targets < 1) { // nowhere to drop
498 // Did we release the mouse button while hovering over a drop target?
499 // NOTE: this only works cross-browser because the iconbar div is always
500 // positioned at 0,0. Browsers differ in whether the 'offset'
501 // functions return pos relative to the document or parent.
503 for (i=0; i<num_drop_targets; ++i) {
505 x = (ns6 ? evt.clientX : event.clientX);
506 y = (ns6 ? evt.clientY : event.clientY);
508 l = parseInt(drop_targets_elements[i].offsetLeft);
509 t = parseInt(drop_targets_elements[i].offsetTop);
510 r = parseInt(drop_targets_elements[i].offsetLeft)
511 + parseInt(drop_targets_elements[i].offsetWidth);
512 b = parseInt(drop_targets_elements[i].offsetTop)
513 + parseInt(drop_targets_elements[i].offsetHeight);
515 /* alert('Offsets are: ' + l + ' ' + t + ' ' + r + ' ' + b + '.'); */
517 if ( (x >= l) && (x <= r) && (y >= t) && (y <= b) ) {
518 // Yes, we dropped it on a hotspot.
519 CtdlMoveSelectedMessages(evt, drop_targets_roomnames[i]);
528 function ctdl_ts_getInnerText(el) {
529 if (typeof el == "string") return el;
530 if (typeof el == "undefined") { return el };
531 if (el.innerText) return el.innerText; //Not needed but it is faster
534 var cs = el.childNodes;
536 for (var i = 0; i < l; i++) {
537 switch (cs[i].nodeType) {
538 case 1: //ELEMENT_NODE
539 str += ts_getInnerText(cs[i]);
542 str += cs[i].nodeValue;
551 // This function handles the creation of new notes in the "Notes" view.
553 function add_new_note() {
555 new_eid = Math.random() + '';
556 new_eid = new_eid.substr(3);
558 $('new_notes_here').innerHTML = $('new_notes_here').innerHTML
559 + '<IMG ALIGN=MIDDLE src=\"static/storenotes_48x.gif\">'
560 + '<span id=\"note' + new_eid + '\">' + Date() + '</span><br />'
563 new Ajax.InPlaceEditor('note' + new_eid,
564 'updatenote?eid=' + new_eid , {rows:5,cols:72});
567 function CtdlShowRaw(msgnum) {
568 var customnav = document.createElement("span");
569 var mode_citadel = document.createElement("a");
570 mode_citadel.appendChild(document.createTextNode("Citadel Source"));
571 var mode_rfc822 = document.createElement("a");
572 mode_rfc822.appendChild(document.createTextNode(" RFC822 Source"));
573 mode_citadel.setAttribute("href","#");
574 mode_rfc822.setAttribute("href","#");
575 mode_rfc822.setAttribute("onclick","rawSwitch822('" + msgnum + "');");
576 mode_citadel.setAttribute("onclick","rawSwitchCitadel('" + msgnum + "');");
577 customnav.appendChild(mode_citadel);
578 customnav.appendChild(mode_rfc822);
579 customnav.setAttribute("class","floatcustomnav");
580 floatwindow("headerscreen","pre",customnav);
581 rawSwitch822(msgnum);
584 function rawSwitch822(msgnum) {
585 CtdlLoadScreen("headerscreen");
586 new Ajax.Updater("headerscreen",
588 { method: 'post',parameters: 'g_cmd=MSG2 ' +msgnum } );
592 function rawSwitchCitadel(msgnum) {
593 CtdlLoadScreen("headerscreen");
594 new Ajax.Updater("headerscreen",
596 { method: 'post',parameters: 'g_cmd=MSG0 ' +msgnum } );
600 function floatwindow(newdivid,contentelementtype,customnav) {
601 var windiv = document.createElement("div");
602 windiv.setAttribute("class","floatwindow");
603 var winid = newdivid+"_window";
604 windiv.setAttribute("id",winid);
605 var nav = document.createElement("div");
606 if (customnav != null) {
607 nav.appendChild(customnav);
609 var minimizeA = document.createElement("a");
610 var minimizeButton = document.createTextNode("Close");
611 minimizeA.appendChild(minimizeButton);
612 minimizeA.setAttribute("onclick","killFloatWindow(this);");
613 minimizeA.setAttribute("href","#");
614 nav.appendChild(minimizeA);
615 nav.setAttribute("class","floatnav");
616 windiv.appendChild(nav);
617 var contentarea = document.createElement("pre");
618 contentarea.setAttribute("class","floatcontent");
619 contentarea.setAttribute("id",newdivid);
620 windiv.appendChild(contentarea);
621 document.body.appendChild(windiv);
623 function killFloatWindow(caller) {
624 var span = caller.parentNode;
625 var fwindow = span.parentNode;
626 fwindow.parentNode.removeChild(fwindow);
628 // Place a gradient loadscreen on an element, e.g to use before Ajax.updater
629 function CtdlLoadScreen(elementid) {
630 var elem = document.getElementById(elementid);
631 elem.innerHTML = "<div align=center><br><table border=0 cellpadding=10 bgcolor=\"#ffffff\"><tr><td><img src=\"static/throbber.gif\" /><font color=\"#AAAAAA\"> Loading....</font></td></tr></table><br /></div>";
635 // Show info for a user, basically replaces showuser()
636 // matt@comalies is to blame for this poorly coded masterpiece.
637 function CtdlShowUserInfoPopup(Element) {
639 // hopefully no one needs to use the class attribute... could be better done
640 // with xmlns though..
641 var user = Element.getAttribute("class");
642 var updname = "biospace_"+user;
643 if (document.getElementById(updname) == null) {
644 // insert a space for the bio
645 var pNode = Element.parentNode;
646 var newdiv = document.createElement("div");
648 newdiv.innerHTML = "Getting user info....";
649 pNode.appendChild(newdiv);
650 CtdlLoadScreen(updname);
651 new Ajax.Updater(updname, 'showuser_ajax?who='+user, { method: 'get' } );
661 // Pop open the address book
662 function PopOpenAddressBook() {
663 $('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\"> Loading....</font></td></tr></table><br /></div>";
664 Effect.Appear('address_book_popup', { duration: 0.5 } );
666 'address_book_inner_div',
667 'display_address_book_inner_div',
668 { method: 'get', parameters: Math.random() }