* Back out previous 'safe view default'
[citadel.git] / webcit / static / summaryview.js
1 /**
2  * Webcit Summary View v2
3  *   All comments, flowers and death threats to Mathew McBride
4  *   <matt@mcbridematt.dhs.org> / <matt@comalies>
5  * Copyright 2009 The Citadel Team
6  * Licensed under the GPL V3
7  */
8 document.observe("dom:loaded", createMessageView);
9
10 var msgs = null;
11 var message_view = null;
12 var loadingMsg = null;
13 var rowArray = null;
14 var currentSortMode = null;
15
16 // Header elements
17 var mlh_date = null;
18 var mlh_subject = null;
19 var mlh_from = null;
20 var currentSorterToggle = null;
21 var query = "";
22 var currentlyMarkedRows = new Object();
23 var markedRowId = null;
24
25 var mouseDownEvent = null;
26 var exitedMouseDown = false;
27
28 var trTemplate = new Array(11);
29 trTemplate[0] = "<tr id=\"";
30 trTemplate[2] = "\" citadel:dropenabled=\"dropenabled\" class=\"";
31 trTemplate[4] = "\" citadel:dndelement=\"summaryViewDragAndDropHandler\" citadel:msgID=\"";
32 trTemplate[6] = "\"><td class=\"col1\">";
33 trTemplate[8] = "</td><td class=\"col2\">";
34 trTemplate[10] = "</td><td class=\"col3\">";
35 trTemplate[12] = "</td></tr>";
36
37 var currentPage = 0;
38 var sortModes = {
39   "rdate" : sortRowsByDateDescending,
40   "date" : sortRowsByDateAscending,
41   "subj" : sortRowsBySubjectAscending,
42   "rsubj" : sortRowsBySubjectDescending,
43   "sender": sortRowsByFromAscending,
44   "rsender" : sortRowsByFromDescending
45 };
46 var toggles = {};
47
48 var nummsgs = 0;
49 var startmsg = 0;
50
51 function createMessageView() {
52   message_view = document.getElementById("message_list_body");
53   loadingMsg = document.getElementById("loading");
54   getMessages();
55   mlh_date = $("mlh_date");
56   mlh_subject = $('mlh_subject');
57   mlh_from = $('mlh_from');
58   toggles["rdate"] = mlh_date;
59   toggles["date"] = mlh_date;
60   toggles["subj"] = mlh_subject;
61   toggles["rsubj"] = mlh_subject;
62   toggles["sender"] = mlh_from;
63   toggles["rsender"] = mlh_from;
64   mlh_date.observe('click',ApplySort);
65   mlh_subject.observe('click',ApplySort);
66   mlh_from.observe('click',ApplySort);
67   $(document).observe('keyup',CtdlMessageListKeyUp,false);
68   $('resize_msglist').observe('mousedown', CtdlResizeMouseDown);
69   $('m_refresh').observe('click', getMessages);
70   document.getElementById('m_refresh').setAttribute("href","#");
71   Event.observe(document.onresize ? document : window, "resize", normalizeHeaderTable);
72   Event.observe(document.onresize ? document : window, "resize", sizePreviewPane);
73   $('summpage').observe('change', getPage);
74   takeOverSearchOMatic();
75   setupDragDrop(); // here for now
76 }
77 function getMessages() {
78   if (loadingMsg.parentNode == null) {
79     message_view.innerHTML = "";
80     message_view.appendChild(loadingMsg);
81   }
82 roomName = getTextContent(document.getElementById("rmname"));
83  var parameters = {'room':roomName, 'startmsg': startmsg, 'stopmsg': -1};
84  if (is_safe_mode) {
85    parameters['stopmsg'] = parseInt(startmsg)+499;
86    //parameters['maxmsgs'] = 500;
87    if (currentSortMode != null) {
88      var SortBy = currentSortMode[0];
89      if (SortBy.charAt(0) == 'r') {
90        SortBy = SortBy.substr(1);
91        parameters["SortOrder"] = "0";
92      }
93      parameters["SortBy"] = SortBy;
94    }
95  } 
96  if (query.length > 0) {
97    parameters["query"] = query;
98  }
99 new Ajax.Request("roommsgs", {
100     method: 'get',
101         onSuccess: loadMessages,
102         parameters: parameters,
103         sanitize: false,
104       evalJSON: false,
105       onFailure: function(e) { alert("Failure: " + e);}
106         });
107 }
108 function evalJSON(data) {
109   if (typeof(JSON) === 'object' && typeof(JSON.parse) === 'function') {
110     return JSON.parse(data);
111   } else {
112     return eval('('+data+')');
113   }
114 }
115 function loadMessages(transport) {
116   try {
117     var data = evalJSON(transport.responseText);
118   if (!!data && transport.responseText.length < 2) {
119     alert("Message loading failed");
120   } 
121   nummsgs = data['nummsgs'];
122   msgs = data['msgs'];
123   var length = msgs.length;
124   rowArray = new Array(length); // store so they can be sorted
125   WCLog("Row array length: "+rowArray.length);
126   } catch (e) {
127     //window.alert(e+"|"+e.description);
128   }
129   if (currentSortMode == null) {
130   if (sortmode.length < 1) {
131     sortmode = "rdate";
132   }
133   currentSortMode = [sortmode, sortModes[sortmode]];
134   currentSorterToggle = toggles[sortmode];
135   }
136   if (!is_safe_mode) {
137   resortAndDisplay(currentSortMode[1]);
138   } else {
139     setupPageSelector();
140     resortAndDisplay(null);
141   }
142   if (loadingMsg.parentNode != null) {
143     loadingMsg.parentNode.removeChild(loadingMsg);
144   }
145   sizePreviewPane();
146 }
147 function resortAndDisplay(sortMode) {
148   WCLog("Begin resortAndDisplay");
149   
150   /* We used to try and clear out the message_view element,
151      but stupid IE doesn't even do that properly */
152   var message_view_parent = message_view.parentNode;
153   message_view_parent.removeChild(message_view);
154   var startSort = new Date();
155   if (sortMode != null) {
156     msgs.sort(sortMode);
157   }
158   var endSort = new Date();
159   WCLog("Sort rowArray in " + (endSort-startSort));
160   var start = new Date();
161   var length = msgs.length;
162   var compiled = new Array(length+2);
163   compiled[0] = "<tbody xmlns:citadel=\"http://citadel.org\" id=\"message_list_body\" class=\"mailbox_summary\">";
164   for(var x=0; x<length; ++x) {
165     try {
166       var currentRow = msgs[x];
167       trTemplate[1] = "msg_"+currentRow[0];
168       var className = "";
169     if (((x-1) % 2) == 0) {
170       className += "table-alt-row";
171     } else {
172       className += "table-row";
173     }
174     if (currentRow[5]) {
175       className += " new_message";
176     }
177     trTemplate[3] = className;
178     trTemplate[5] = currentRow[0];
179     trTemplate[7] = currentRow[1];
180     trTemplate[9] = currentRow[2];
181     trTemplate[11] = currentRow[4];
182     var i = x+1;
183     compiled[i] = trTemplate.join("");
184     } catch (e) {
185       alert("Exception on row " +  x + ":" + e);
186     }
187   }
188   compiled[length+2] = "</tbody>";
189   var end = new Date();
190   WCLog("iterate: " + (end-start));
191   var compile = compiled.join("");
192   start = new Date();
193   $(message_view_parent).update(compile);
194   message_view_parent.onclick = CtdlMessageListClick;
195   message_view = message_view_parent.firstChild;
196   end = new Date();
197     var delta = end.getTime() - start.getTime();
198     WCLog("append: " + delta);
199   ApplySorterToggle();
200   normalizeHeaderTable();
201 }
202 function sortRowsByDateAscending(a, b) {
203   var dateOne = a[3];
204   var dateTwo = b[3];
205   return (dateOne - dateTwo);
206 };
207 function sortRowsByDateDescending(a, b) {
208   var dateOne = a[3];
209   var dateTwo = b[3];
210   return (dateTwo - dateOne);
211 };
212 function sortRowsBySubjectAscending(a, b) {
213   var subjectOne = a[1];
214   var subjectTwo = b[1];
215   return strcmp(subjectOne, subjectTwo);
216 };
217 function sortRowsBySubjectDescending(a, b) {
218   var subjectOne = a[1];
219   var subjectTwo = b[1];
220   return strcmp(subjectTwo, subjectOne);
221 };
222 function sortRowsByFromAscending(a, b) {
223   var fromOne = a[2];
224   var fromTwo = b[2];
225   return strcmp(fromOne, fromTwo);
226 };
227 function sortRowsByFromDescending(a, b) {
228   var fromOne = a[2];
229   var fromTwo = b[2];
230   return strcmp(fromTwo, fromOne);
231 };
232 function CtdlMessageListClick(evt) {
233   /* Since element.onload is used here, test to see if evt is defined */
234   var event = evt ? evt : window.event; 
235   var target = event.target ? event.target: event.srcElement; // and again..
236   var parent = target.parentNode;
237   var msgId = parent.getAttribute("citadel:msgid");
238   // If the ctrl key modifier wasn't used, unmark all rows and load the message
239   if (!event.shiftKey && !event.ctrlKey && !event.altKey) {
240     unmarkAllRows();
241     markedRowId = parent.ctdlRowId;
242     document.getElementById("preview_pane").innerHTML = "";
243     new Ajax.Updater('preview_pane', 'msg/'+msgId, {method: 'get'});
244     markRow(parent);
245     new Ajax.Request('ajax_servcmd', {
246       method: 'post',
247           parameters: 'g_cmd=SEEN ' + msgId + '|1',
248           onComplete: CtdlMarkRowAsRead(parent)});
249   // If the shift key modifier is used, mark a range...
250   } else if (event.button != 2 && event.shiftKey) {
251     markRow(parent);
252     var rowId = parent.ctdlRowId;
253     var startMarkingFrom = 0;
254     var finish = 0;
255     if (rowId > markedRowId) {
256       startMarkingFrom = markedRowId+1;
257       finish = rowId;
258     } else if (rowId < markedRowId) {
259       startMarkingFrom = rowId+1;
260       finish = markedRowId;
261     } 
262     for(var x = startMarkingFrom; x<finish; x++) {
263       WCLog("Marking row "+x);
264       markRow(rowArray[x]);
265     }
266   // If the ctrl key modifier is used, toggle one message
267   } else if (event.button != 2 && (event.ctrlKey || event.altKey)) {
268     if (parent.ctdlMarked == true) {
269       unmarkRow(parent);
270     }
271     else {
272       markRow(parent);
273     }
274   }
275 }
276 function CtdlMarkRowAsRead(rowElement) {
277   var classes = rowElement.className;
278   classes = classes.replace("new_message","");
279   rowElement.className = classes;
280 }
281 function ApplySort(event) {
282   var target = event.target;
283   var sortId = target.id;
284   removeOldSortClass();
285   currentSorterToggle = target;
286   var sortModes = getSortMode(target); // returns [[key, func],[key,func]]
287   var sortModeToUse = null;
288   if (currentSortMode[0] == sortModes[0][0]) {
289     sortModeToUse = sortModes[1];
290   } else {
291     sortModeToUse = sortModes[0];
292   }
293   currentSortMode = sortModeToUse;
294   if (is_safe_mode) {
295     getMessages(); // in safe mode, we load from server already sorted
296   } else {
297   resortAndDisplay(sortModeToUse[1]);
298   }
299 }
300 function getSortMode(toggleElem) {
301   var forward = null;
302   var reverse = null;
303   for(var key in toggles) {
304     var kr = (key.charAt(0) == 'r');
305     if (toggles[key] == toggleElem && !kr) {
306       forward = [key, sortModes[key]];
307     } else if (toggles[key] == toggleElem && kr) {
308       reverse = [key, sortModes[key]];
309     }
310   }
311   return [forward, reverse];
312 }
313 function removeOldSortClass() {
314   if (currentSorterToggle) {
315     var classes = currentSorterToggle.className;
316     /* classes = classes.replace("current_sort_mode","");
317     classes = classes.replace("sort_ascending","");
318     classes = classes.replace("sort_descending",""); */
319     currentSorterToggle.className = "";
320   }
321 }
322 function markRow(row) {
323   var msgId = row.getAttribute("citadel:msgid");
324   row.className = row.className += " marked_row";
325   row.setAttribute("citadel:marked","marked");
326   currentlyMarkedRows[msgId] = row;
327 }
328 function unmarkRow(row) {
329   var msgId = row.getAttribute("citadel:msgid");
330   row.className = row.className.replace("marked_row","");
331   row.removeAttribute("citadel:marked");
332   delete currentlyMarkedRows[msgId];
333 }
334 function unmarkAllRows() {
335   for(msgId in currentlyMarkedRows) {
336     unmarkRow(currentlyMarkedRows[msgId]);
337   }
338 }
339 function deleteAllMarkedRows() {
340   for(msgId in currentlyMarkedRows) {
341     var row = currentlyMarkedRows[msgId];
342     var rowArrayId = row.ctdlRowId;
343     row.parentNode.removeChild(row);
344     delete currentlyMarkedRows[msgId];
345     delete rowArray[rowArrayId];
346   }
347   // Now we have to reconstruct rowarray as the array length has changed */
348   var newRowArray = new Array();
349   var x=0;
350   for(var i=0; i<rowArray.length; i++) {
351     var currentRow = rowArray[i];
352     if (currentRow != null) {
353       newRowArray[x] = currentRow;
354       x++;
355     }
356   }
357   rowArray = newRowArray;
358   resortAndDisplay(null);
359 }
360
361 function deleteAllSelectedMessages() {
362     for(msgId in currentlyMarkedRows) {
363       if (!room_is_trash) {
364       new Ajax.Request('ajax_servcmd', 
365                        {method: 'post',
366                            parameters: 'g_cmd=MOVE ' + msgId + '|_TRASH_|0'
367                            });
368       } else {
369         new Ajax.Request('ajax_servcmd', {method: 'post',
370               parameters: 'g_cmd=DELE '+msgId});
371       }
372     }
373     document.getElementById("preview_pane").innerHTML = "";
374     deleteAllMarkedRows();
375 }
376
377 function CtdlMessageListKeyUp(event) {
378         var key = event.which;
379         if (key == 46) { // DELETE
380                 deleteAllSelectedMessages();
381         }
382 }
383
384 function clearMessage(msgId) {
385   var row = document.getElementById('msg_'+msgId);
386   row.parentNode.removeChild(row);
387   delete currentlyMarkedRows[msgId];
388 }
389
390 function summaryViewContextMenu() {
391   if (!exitedMouseDown) {
392     var contextSource = document.getElementById("listViewContextMenu");
393     CtdlSpawnContextMenu(mouseDownEvent, contextSource);
394   }
395 }
396
397 function summaryViewDragAndDropHandler() {
398   var element = document.createElement("div");
399   var msgList = document.createElement("ul");
400   element.appendChild(msgList);
401   for(msgId in currentlyMarkedRows) {
402     msgRow = currentlyMarkedRows[msgId];
403     var subject = getTextContent(msgRow.getElementsByTagName("td")[0]);
404     var li = document.createElement("li");
405     msgList.appendChild(li);
406     setTextContent(li, subject);
407     li.ctdlMsgId = msgId;
408   }
409   return element;
410 }
411
412 var saved_y = 0;
413 function CtdlResizeMouseDown(event) {
414   $(document).observe('mousemove', CtdlResizeMouseMove);
415   $(document).observe('mouseup', CtdlResizeMouseUp);
416   saved_y = event.clientY;
417 }
418
419 function sizePreviewPane() {
420   var preview_pane = document.getElementById("preview_pane");
421   var summary_view = document.getElementById("summary_view");
422   var banner = document.getElementById("banner");
423   var message_list_hdr = document.getElementById("message_list_hdr");
424   var content = $('global');  // we'd like to use prototype methods here
425   var childElements = content.childElements();
426   var sizeOfElementsAbove = 0;
427   var heightOfViewPort = document.viewport.getHeight() // prototypejs method
428   var bannerHeight = banner.offsetHeight;
429   var contentViewPortHeight = heightOfViewPort-banner.offsetHeight-message_list_hdr.offsetHeight;
430   contentViewPortHeight = 0.98 * contentViewPortHeight; // leave some error
431   // Set summary_view to 20%;
432   var summary_height = ctdlLocalPrefs.readPref("svheight");
433   if (summary_height == null) {
434     summary_height = 0.20 * contentViewPortHeight;
435   }
436   // Set preview_pane to the remainder
437   var preview_height = contentViewPortHeight - summary_height;
438   
439   summary_view.style.height = (summary_height)+"px";
440   preview_pane.style.height = (preview_height)+"px";
441 }
442 function CtdlResizeMouseMove(event) {
443   var clientX = event.clientX;
444   var clientY = event.clientY;
445   var summary_view = document.getElementById("summary_view");
446   var summaryViewHeight = summary_view.offsetHeight;
447   var increment = clientY-saved_y;
448   var summary_view_height = increment+summaryViewHeight;
449   summary_view.style.height = (summary_view_height)+"px";
450   // store summary view height 
451   ctdlLocalPrefs.setPref("svheight",summary_view_height);
452   var msglist = document.getElementById("preview_pane");
453   var msgListHeight = msglist.offsetHeight;
454   msglist.style.height = (msgListHeight-increment)+"px";
455   saved_y = clientY;
456   /* For some reason the grippy doesn't work without position: absolute
457      so we need to set its top pos manually all the time */
458   var resize = document.getElementById("resize_msglist");
459   var resizePos = resize.offsetTop;
460   resize.style.top = (resizePos+increment)+"px";
461 }
462 function CtdlResizeMouseUp(event) {
463   $(document).stopObserving('mousemove', CtdlResizeMouseMove);
464   $(document).stopObserving('mouseup', CtdlResizeMouseUp);
465 }
466 function ApplySorterToggle() {
467   var className = currentSorterToggle.className;
468   className += " current_sort_mode";
469   if (currentSortMode[1] == sortRowsByDateDescending ||
470       currentSortMode[1] == sortRowsBySubjectDescending ||
471       currentSortMode[1] == sortRowsByFromDescending) {
472     className += " sort_descending";
473   } else {
474     className += " sort_ascending";
475   }
476   currentSorterToggle.className = className;
477 }
478 /** Hack to make the header table line up with the data */
479 function normalizeHeaderTable() {
480   var message_list_hdr = document.getElementById("message_list_hdr");
481   var summary_view = document.getElementById("summary_view");
482   var resize_msglist = document.getElementById("resize_msglist");
483   var headerTable = message_list_hdr.getElementsByTagName("table")[0];
484   var dataTable = summary_view.getElementsByTagName("table")[0];
485   var dataTableWidth = dataTable.offsetWidth;
486   headerTable.style.width = dataTableWidth+"px";
487 }
488
489 function setupPageSelector() {
490   var summpage = document.getElementById("summpage");
491   var select_page = document.getElementById("selectpage");
492   summpage.innerHTML = "";
493   if (is_safe_mode) {
494     WCLog("unhiding parent page");
495     select_page.className = "";
496   } else {
497     return;
498   }
499   var pages = nummsgs / 499;
500   for(var i=0; i<pages; i++) {
501     var opt = document.createElement("option");
502     var startmsg = i * 499;
503     opt.setAttribute("value",startmsg);
504     if (currentPage == i) {
505       opt.setAttribute("selected","selected");
506     }
507     opt.appendChild(document.createTextNode((i+1)));
508     summpage.appendChild(opt);
509   }
510 }
511 function getPage(event) {
512   var target = event.target;
513   startmsg = target.options.item(target.selectedIndex).value;
514   currentPage = target.selectedIndex;
515   //query = ""; // We are getting a page from the _entire_ msg list, don't query
516   getMessages();
517 }
518 function takeOverSearchOMatic() {
519   var searchForm = document.getElementById("searchomatic").getElementsByTagName("form")[0];
520   // First disable the form post
521   searchForm.setAttribute("action","javascript:void();");
522   searchForm.removeAttribute("method");
523   $(searchForm).observe('submit', doSearch);
524 }
525 function doSearch() {
526   query = document.getElementById("srchquery").value;
527   getMessages();
528   return false;
529 }