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