* fix issues with room list in firefox 2 (didn't detect console.log properly, again)
[citadel.git] / webcit / static / summaryview.js
1 /** Webcit Summary View v2
2     All comments, flowers and death threats to Mathew McBride
3     <matt@mcbridematt.dhs.org> / <matt@comalies>
4 */
5 document.observe("dom:loaded", createMessageView);
6
7 var message_view = null;
8 var loadingMsg = null;
9 var rowArray = null;
10 var currentSortMode = null;
11
12 // Header elements
13 var mlh_date = null;
14 var mlh_subject = null;
15 var mlh_from = null;
16 var currentSorterToggle = null;
17 var query = "";
18 var currentlyMarkedRows = new Object();
19
20 var mouseDownEvent = null;
21 var exitedMouseDown = false;
22
23 var sortModes = {
24   "rdate" : sortRowsByDateDescending,
25   "date" : sortRowsByDateAscending,
26   //  "reverse" : sortRowsByDateDescending,
27   "subj" : sortRowsBySubjectAscending,
28   "rsubj" : sortRowsBySubjectDescending,
29   "sender": sortRowsByFromAscending,
30   "rsender" : sortRowsByFromDescending
31 };
32 var toggles = {};
33
34 var nummsgs = 0;
35 var startmsg = 0;
36 function createMessageView() {
37   message_view = document.getElementById("message_list_body");
38   loadingMsg = document.getElementById("loading");
39   getMessages();
40   mlh_date = $("mlh_date");
41   mlh_subject = $('mlh_subject');
42   mlh_from = $('mlh_from');
43   toggles["rdate"] = mlh_date;
44   toggles["date"] = mlh_date;
45   // toggles["reverse"] = mlh_date;
46   toggles["subj"] = mlh_subject;
47   toggles["rsubj"] = mlh_subject;
48   toggles["sender"] = mlh_from;
49   toggles["rsender"] = mlh_from;
50   mlh_date.observe('click',ApplySort);
51   mlh_subject.observe('click',ApplySort);
52   mlh_from.observe('click',ApplySort);
53   $(document).observe('keyup',CtdlMessageListKeyUp,false);
54   window.oncontextmenu = function() { return false; };  
55   $('resize_msglist').observe('mousedown', CtdlResizeMouseDown);
56   $('m_refresh').observe('click', getMessages);
57   document.getElementById('m_refresh').setAttribute("href","#");
58   Event.observe(document.onresize ? document : window, "resize", normalizeHeaderTable);
59   sizePreviewPane();
60   Event.observe(document.onresize ? document : window, "resize", sizePreviewPane);
61   $('summpage').observe('change', getPage);
62   takeOverSearchOMatic();
63   setupDragDrop(); // here for now
64 }
65 function getMessages() {
66   if (loadingMsg.parentNode == null) {
67     message_view.innerHTML = "";
68     message_view.appendChild(loadingMsg);
69   }
70 roomName = getTextContent(document.getElementById("rmname"));
71  var parameters = {'room':roomName, 'startmsg': startmsg};
72  if (is_safe_mode) {
73    parameters['maxmsgs'] = 500;
74    if (currentSortMode != null) {
75      var SortBy = currentSortMode[0];
76      if (SortBy.charAt(0) == 'r') {
77        SortBy = SortBy.substr(1);
78        parameters["SortOrder"] = "2";
79      }
80      parameters["SortBy"] = SortBy;
81    }
82  } 
83  if (query.length > 0) {
84    parameters["query"] = query;
85  }
86 new Ajax.Request("roommsgs", {
87     method: 'get',
88         onSuccess: loadMessages,
89         parameters: parameters,
90         sanitize: false,
91       evalJSON: false,
92       onFailure: function(e) { alert("Failure: " + e);}
93         });
94 }
95 function loadMessages(transport) {
96   try {
97   var data = eval('('+transport.responseText+')');
98   if (!!data && transport.responseText.length < 2) {
99     alert("Message loading failed");
100   } 
101   nummsgs = data['nummsgs'];
102   var msgs = data['msgs'];
103   var length = msgs.length;
104   rowArray = new Array(); // store so they can be sorted
105   var start = new Date();
106   for(var i=0; i<length;i++) {
107     var trElement = document.createElement("tr");
108     var data = msgs[i];
109     var msgId = data[0];
110     var rowId = "msg_" + msgId;
111     trElement.setAttribute("id",rowId);
112     //$(trElement).observe('click', CtdlMessageListClick);
113     trElement.ctdlMsgId = msgId;
114     for(var j=1; j<5;j++) { // 1=msgId (hidden), 4 date timestamp (hidden) 6 = isNew etc. 
115       var content = data[j];
116       if(content.length < 1) {
117         content = "(blank)";
118       }
119       if (j==3) {
120         trElement.ctdlDate = content;
121       } else { 
122         try {
123       var tdElement = document.createElement("td");
124       trElement.appendChild(tdElement);
125       var txtContent = document.createTextNode(content);
126       tdElement.appendChild(txtContent);
127       var x=j;
128       if (x==4) x=3;
129       var classStmt = "col"+x;
130       tdElement.setAttribute("class", classStmt);
131         } catch (e) {
132           WCLog("Error on #"+msgId +" col"+j+":"+e);
133         }
134       }
135     }
136     if (data[5]) {
137       trElement.setAttribute("class", "new_message");
138     }
139     trElement.dropEnabled = true;
140     trElement.ctdlRowId = i;
141     trElement.ctdlMarked = false;
142     rowArray[i] = trElement; 
143   } 
144   var end = new Date();
145   var delta = end.getTime() - start.getTime();
146     WCLog("loadMessages construct: " + delta);
147   } catch (e) {
148     window.alert(e);
149   }
150   if (currentSortMode == null) {
151   if (sortmode.length < 1) {
152     sortmode = "rdate";
153   }
154   currentSortMode = [sortmode, sortModes[sortmode]];
155   currentSorterToggle = toggles[sortmode];
156   }
157   if (!is_safe_mode) {
158   resortAndDisplay(sortRowsByDateDescending);
159   } else {
160     resortAndDisplay(null);
161   }
162   loadingMsg.parentNode.removeChild(loadingMsg);
163   setupPageSelector();
164   query = "";
165 }
166 function resortAndDisplay(sortMode) {
167   var start = new Date();
168   emptyElement(message_view);
169   var fragment = document.createDocumentFragment();
170   if (sortMode != null) {
171     rowArray.sort(sortMode);
172   }
173   var length = rowArray.length;
174   for(var x=0; x<length; ++x) {
175     try {
176       var currentRow = rowArray[x];
177       var className = currentRow.className;
178     className = className.replace("table-alt-row","");
179     className = className.replace("table-row","");
180     if (((x-1) % 2) == 0) {
181       className += " table-alt-row";
182     } else {
183       className += " table-row";
184     }
185     currentRow.className = className;
186     /* Using element.onclick is evil, but until IE 
187        supports addEventListener, it is much faster
188        than prototype observe */
189     currentRow.onclick = CtdlMessageListClick;
190     currentRow.ctdlDnDElement = summaryViewDragAndDropHandler;
191     fragment.appendChild(currentRow);
192     } catch (e) {
193       alert("Exception" + e);
194     }
195   }
196   message_view.appendChild(fragment);
197   var end = new Date();
198     var delta = end.getTime() - start.getTime();
199     WCLog("resortAndDisplay sort and append: " + delta);
200   ApplySorterToggle();
201   normalizeHeaderTable();
202 }
203 function sortRowsByDateAscending(a, b) {
204   var dateOne = a.ctdlDate;
205   var dateTwo = b.ctdlDate;
206   return (dateOne - dateTwo);
207 }
208 function sortRowsByDateDescending(a, b) {
209   var dateOne = a.ctdlDate;
210   var dateTwo = b.ctdlDate;
211   return (dateTwo - dateOne);
212 }
213
214 function sortRowsBySubjectAscending(a, b) {
215   var subjectOne = getTextContent(a.getElementsByTagName("td")[0]).toLowerCase();
216   var subjectTwo = getTextContent(b.getElementsByTagName("td")[0]).toLowerCase();
217   return (subjectOne.charCodeAt(0) - subjectTwo.charCodeAt(0));
218 }
219
220 function sortRowsBySubjectDescending(a, b) {
221   var subjectOne = getTextContent(a.getElementsByTagName("td")[0]).toLowerCase();
222   var subjectTwo = getTextContent(b.getElementsByTagName("td")[0]).toLowerCase();
223   return (subjectTwo.charCodeAt(0) - subjectOne.charCodeAt(0));
224 }
225
226 function sortRowsByFromAscending(a, b) {
227   var fromOne = getTextContent(a.getElementsByTagName("td")[1]).toLowerCase();
228   var fromTwo = getTextContent(b.getElementsByTagName("td")[1]).toLowerCase();
229   return (fromOne.charCodeAt(0) - fromTwo.charCodeAt(0));
230 }
231
232 function sortRowsByFromDescending(a, b) {
233   var fromOne = getTextContent(a.getElementsByTagName("td")[1]).toLowerCase();
234   var fromTwo = getTextContent(b.getElementsByTagName("td")[1]).toLowerCase();
235   return (fromTwo.charCodeAt(0) - fromOne.charCodeAt(0));
236 }
237
238 function CtdlMessageListClick(evt) {
239   /* Since element.onload is used here, test to see if evt is defined */
240   var event = evt ? evt : window.event; 
241   var target = event.target ? event.target: event.srcElement; // and again..
242   var parent = target.parentNode;
243   var msgId = parent.ctdlMsgId;
244   // If the ctrl key modifier wasn't used, unmark all rows and load the message
245   if (!event.shiftKey && !event.ctrlKey) {
246     unmarkAllRows();
247     new Ajax.Updater('preview_pane', 'msg/'+msgId, {method: 'get'});
248     markRow(parent);
249     new Ajax.Request('ajax_servcmd', {
250       method: 'post',
251           parameters: 'g_cmd=SEEN ' + msgId + '|1',
252           onComplete: CtdlMarkRowAsRead(parent)});
253   } else if (event.button != 2) {
254     markRow(parent);
255     // TODO: introduce code to mark rows inbetween
256   }
257 }
258 function CtdlMarkRowAsRead(rowElement) {
259   var classes = rowElement.className;
260   classes = classes.replace("new_message","");
261   rowElement.className = classes;
262 }
263 function ApplySort(event) {
264   var target = event.target;
265   var sortId = target.id;
266   removeOldSortClass();
267   currentSorterToggle = target;
268   var sortModes = getSortMode(target); // returns [[key, func],[key,func]]
269   var sortModeToUse = null;
270   if (currentSortMode[0] == sortModes[0][0]) {
271     sortModeToUse = sortModes[1];
272   } else {
273     sortModeToUse = sortModes[0];
274   }
275   currentSortMode = sortModeToUse;
276   if (is_safe_mode) {
277     getMessages(); // in safe mode, we load from server already sorted
278   } else {
279   resortAndDisplay(sortModeToUse[1]);
280   }
281 }
282 function getSortMode(toggleElem) {
283   var forward = null;
284   var reverse = null;
285   for(var key in toggles) {
286     var kr = (key.charAt(0) == 'r');
287     if (toggles[key] == toggleElem && !kr) {
288       forward = [key, sortModes[key]];
289     } else if (toggles[key] == toggleElem && kr) {
290       reverse = [key, sortModes[key]];
291     }
292   }
293   return [forward, reverse];
294 }
295 function removeOldSortClass() {
296   if (currentSorterToggle) {
297     var classes = currentSorterToggle.className;
298     classes = classes.replace("current_sort_mode","");
299     classes = classes.replace("sort_ascending","");
300     classes = classes.replace("sort_descending","");
301     currentSorterToggle.className = classes;
302   }
303 }
304 function markRow( row) {
305   var msgId = row.ctdlMsgId;
306   row.className = row.className += " marked_row";
307   row.ctdlMarked = true;
308   currentlyMarkedRows[msgId] = row;
309 }
310 function unmarkRow(row) {
311   var msgId = row.ctdlMsgId;
312   row.className = row.className.replace("marked_row","");
313   row.ctdlMarked = false;
314   delete currentlyMarkedRows[msgId];
315 }
316 function unmarkAllRows() {
317   for(msgId in currentlyMarkedRows) {
318     unmarkRow(currentlyMarkedRows[msgId]);
319   }
320 }
321 function deleteAllMarkedRows() {
322   for(msgId in currentlyMarkedRows) {
323     var row = currentlyMarkedRows[msgId];
324     var rowArrayId = row.ctdlRowId;
325     row.parentNode.removeChild(row);
326     delete currentlyMarkedRows[msgId];
327     delete rowArray[rowArrayId];
328   }
329 }
330 function CtdlMessageListKeyUp(event) {
331   var key = event.which;
332   if (key == 46) { // DELETE
333     for(msgId in currentlyMarkedRows) {
334       new Ajax.Request('ajax_servcmd', 
335                        {method: 'post',
336                            parameters: 'g_cmd=MOVE ' + msgId + '|_TRASH_|0',
337                            });
338     }
339     deleteAllMarkedRows();
340   }
341 }
342
343 function clearMessage(msgId) {
344   var row = document.getElementById('msg_'+msgId);
345   row.parentNode.removeChild(row);
346   delete currentlyMarkedRows[msgId];
347 }
348
349 function summaryViewContextMenu() {
350   if (!exitedMouseDown) {
351     var contextSource = document.getElementById("listViewContextMenu");
352     CtdlSpawnContextMenu(mouseDownEvent, contextSource);
353   }
354 }
355
356 function summaryViewDragAndDropHandler() {
357   var element = document.createElement("div");
358   var msgList = document.createElement("ul");
359   element.appendChild(msgList);
360   for(msgId in currentlyMarkedRows) {
361     msgRow = currentlyMarkedRows[msgId];
362     var subject = getTextContent(msgRow.getElementsByTagName("td")[0]);
363     var li = document.createElement("li");
364     msgList.appendChild(li);
365     setTextContent(li, subject);
366     li.ctdlMsgId = msgId;
367   }
368   return element;
369 }
370
371 var saved_y = 0;
372 function CtdlResizeMouseDown(event) {
373   $(document).observe('mousemove', CtdlResizeMouseMove);
374   $(document).observe('mouseup', CtdlResizeMouseUp);
375   saved_y = event.clientY;
376 }
377
378 function sizePreviewPane() {
379   var preview_pane = document.getElementById("preview_pane");
380   var content = $('content');  // we'd like to use prototype methods here
381   var childElements = content.childElements();
382   var sizeOfElementsAbove = 0;
383   var heightOfContent = content.offsetHeight;
384   for(var i=0; i<childElements.length; i++) {
385     var element = childElements[i];
386     if (element.id != 'preview_pane') {
387       var height = element.offsetHeight;
388       sizeOfElementsAbove += height;
389     }
390   }
391   preview_pane.style.height = (heightOfContent-sizeOfElementsAbove)+"px";
392 }
393 function CtdlResizeMouseMove(event) {
394   var clientX = event.clientX;
395   var clientY = event.clientY;
396   var summary_view = document.getElementById("summary_view");
397   var summaryViewHeight = summary_view.offsetHeight;
398   var increment = clientY-saved_y;
399   summary_view.style.height = (increment+summaryViewHeight)+"px";
400   var msglist = document.getElementById("preview_pane");
401   var msgListHeight = msglist.offsetHeight;
402   msglist.style.height = (msgListHeight-increment)+"px";
403   saved_y = clientY;
404   /* For some reason the grippy doesn't work without position: absolute
405      so we need to set its top pos manually all the time */
406   var resize = document.getElementById("resize_msglist");
407   var resizePos = resize.offsetTop;
408   resize.style.top = (resizePos+increment)+"px";
409 }
410 function CtdlResizeMouseUp(event) {
411   $(document).stopObserving('mousemove', CtdlResizeMouseMove);
412   $(document).stopObserving('mouseup', CtdlResizeMouseUp);
413 }
414 function ApplySorterToggle() {
415   var className = currentSorterToggle.className;
416   className += " current_sort_mode";
417   if (currentSortMode[1] == sortRowsByDateDescending ||
418       currentSortMode[1] == sortRowsBySubjectDescending ||
419       currentSortMode[1] == sortRowsByFromDescending) {
420     className += " sort_descending";
421   } else {
422     className += " sort_ascending";
423   }
424   currentSorterToggle.className = className;
425 }
426 /** Hack to make the header table line up with the data */
427 function normalizeHeaderTable() {
428   var message_list_hdr = document.getElementById("message_list_hdr");
429   var summary_view = document.getElementById("summary_view");
430   var headerTable = message_list_hdr.getElementsByTagName("table")[0];
431   var dataTable = summary_view.getElementsByTagName("table")[0];
432   var dataTableWidth = dataTable.offsetWidth;
433   headerTable.style.width = dataTableWidth+"px";
434 }
435
436 function setupPageSelector() {
437   var summpage = document.getElementById("summpage");
438   summpage.innerHTML = "";
439   if (is_safe_mode) {
440     summpage.parentNode.style.display="inline !important"; //override webcit.css
441   } else {
442     return;
443   }
444   var pages = nummsgs / 500;
445   for(var i=0; i<pages; i++) {
446     var opt = document.createElement("option");
447     var startmsg = i * 500;
448     opt.setAttribute("value",startmsg);
449     opt.appendChild(document.createTextNode((i+1)));
450     summpage.appendChild(opt);
451   }
452 }
453 function getPage(event) {
454   var target = event.target;
455   startmsg = target.options.item(target.selectedIndex).value;
456   getMessages();
457 }
458 function takeOverSearchOMatic() {
459   var searchForm = document.getElementById("searchomatic").getElementsByTagName("form")[0];
460   // First disable the form post
461   searchForm.setAttribute("action","javascript:void();");
462   searchForm.removeAttribute("method");
463   $(searchForm).observe('submit', doSearch);
464 }
465 function doSearch() {
466   query = document.getElementById("srchquery").value;
467   getMessages();
468   return false;
469 }