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
8 document.observe("dom:loaded", createMessageView);
10 var message_view = null;
11 var loadingMsg = null;
13 var currentSortMode = null;
17 var mlh_subject = null;
19 var currentSorterToggle = null;
21 var currentlyMarkedRows = new Object();
22 var markedRowId = null;
24 var mouseDownEvent = null;
25 var exitedMouseDown = false;
29 "rdate" : sortRowsByDateDescending,
30 "date" : sortRowsByDateAscending,
31 // "reverse" : sortRowsByDateDescending,
32 "subj" : sortRowsBySubjectAscending,
33 "rsubj" : sortRowsBySubjectDescending,
34 "sender": sortRowsByFromAscending,
35 "rsender" : sortRowsByFromDescending
41 var is_safe_mode = true;
42 /* The following code is VERY evil! Hopefully the need for it will evaporate in the future.
43 We only want newer browsers with Javascript JIT's to use the newer message view, unless the user explicitly chooses new/safe view */
44 function determineSafeMode() {
45 if (summary_view_pref == false) {
46 var userAgent = navigator.userAgent;
47 var gecko = userAgent.indexOf("Gecko/");
48 var opera = userAgent.indexOf("Presto/"); // check for rendering engine
49 var chrome = userAgent.indexOf("Chrome/");
50 var safari = userAgent.indexOf("Safari/");
51 var phone = userAgent.indexOf("Mobile");
53 is_safe_mode = true; /* Don't serve to mobiles */
54 } else if (gecko > 0) {
55 var version = userAgent.substring(gecko+6,gecko+15);
56 if (version > 20090600) {
59 } else if (opera > 0) {
60 var prestoVersion = userAgent.substring(opera+7,opera+10);
61 if (prestoVersion >= 2.2) {
64 } else if (chrome > 0) {
66 } else if (safari > 0) {
67 var safariVersion = userAgent.substring(safari+7,safari+10);
68 if (safariVersion >= 525) {
76 function createMessageView() {
78 message_view = document.getElementById("message_list_body");
79 loadingMsg = document.getElementById("loading");
81 mlh_date = $("mlh_date");
82 mlh_subject = $('mlh_subject');
83 mlh_from = $('mlh_from');
84 toggles["rdate"] = mlh_date;
85 toggles["date"] = mlh_date;
86 // toggles["reverse"] = mlh_date;
87 toggles["subj"] = mlh_subject;
88 toggles["rsubj"] = mlh_subject;
89 toggles["sender"] = mlh_from;
90 toggles["rsender"] = mlh_from;
91 mlh_date.observe('click',ApplySort);
92 mlh_subject.observe('click',ApplySort);
93 mlh_from.observe('click',ApplySort);
94 $(document).observe('keyup',CtdlMessageListKeyUp,false);
95 //window.oncontextmenu = function() { return false; };
96 $('resize_msglist').observe('mousedown', CtdlResizeMouseDown);
97 $('m_refresh').observe('click', getMessages);
98 document.getElementById('m_refresh').setAttribute("href","#");
99 Event.observe(document.onresize ? document : window, "resize", normalizeHeaderTable);
100 Event.observe(document.onresize ? document : window, "resize", sizePreviewPane);
101 $('summpage').observe('change', getPage);
102 takeOverSearchOMatic();
103 setupDragDrop(); // here for now
105 function getMessages() {
106 if (loadingMsg.parentNode == null) {
107 message_view.innerHTML = "";
108 message_view.appendChild(loadingMsg);
110 roomName = getTextContent(document.getElementById("rmname"));
111 var parameters = {'room':roomName, 'startmsg': startmsg, 'stopmsg': -1};
113 parameters['stopmsg'] = parseInt(startmsg)+500;
114 //parameters['maxmsgs'] = 500;
115 if (currentSortMode != null) {
116 var SortBy = currentSortMode[0];
117 if (SortBy.charAt(0) == 'r') {
118 SortBy = SortBy.substr(1);
119 parameters["SortOrder"] = "0";
121 parameters["SortBy"] = SortBy;
124 if (query.length > 0) {
125 parameters["query"] = query;
127 new Ajax.Request("roommsgs", {
129 onSuccess: loadMessages,
130 parameters: parameters,
133 onFailure: function(e) { alert("Failure: " + e);}
136 function loadMessages(transport) {
138 var data = eval('('+transport.responseText+')');
139 if (!!data && transport.responseText.length < 2) {
140 alert("Message loading failed");
142 nummsgs = data['nummsgs'];
143 var msgs = data['msgs'];
144 var length = msgs.length;
145 rowArray = new Array(); // store so they can be sorted
146 WCLog("Row array length: "+rowArray.length);
147 var start = new Date();
148 for(var i=0; i<length;i++) {
149 var trElement = document.createElement("tr");
152 var rowId = "msg_" + msgId;
153 trElement.setAttribute("id",rowId);
154 //$(trElement).observe('click', CtdlMessageListClick);
155 trElement.ctdlMsgId = msgId;
156 for(var j=1; j<5;j++) { // 1=msgId (hidden), 4 date timestamp (hidden) 6 = isNew etc.
157 var content = data[j];
158 if(content.length < 1) {
162 trElement.ctdlDate = content;
165 var tdElement = document.createElement("td");
166 trElement.appendChild(tdElement);
167 var txtContent = document.createTextNode(content);
168 tdElement.appendChild(txtContent);
171 var classStmt = "col"+x;
172 //tdElement.setAttribute("class", classStmt);
173 tdElement.className = classStmt;
175 WCLog("Error on #"+msgId +" col"+j+":"+e);
180 trElement.ctdlNewMsg = true;
182 trElement.dropEnabled = true;
183 trElement.ctdlMarked = false;
184 rowArray[i] = trElement;
186 var end = new Date();
187 var delta = end.getTime() - start.getTime();
188 WCLog("loadMessages construct: " + delta);
190 //window.alert(e+"|"+e.description);
192 if (currentSortMode == null) {
193 if (sortmode.length < 1) {
196 currentSortMode = [sortmode, sortModes[sortmode]];
197 currentSorterToggle = toggles[sortmode];
200 resortAndDisplay(currentSortMode[1]);
203 resortAndDisplay(null);
205 if (loadingMsg.parentNode != null) {
206 loadingMsg.parentNode.removeChild(loadingMsg);
210 function resortAndDisplay(sortMode) {
211 WCLog("Begin resortAndDisplay");
212 var start = new Date();
213 /* We used to try and clear out the message_view element,
214 but stupid IE doesn't even do that properly */
215 var message_view_parent = message_view.parentNode;
216 message_view_parent.removeChild(message_view);
217 message_view = document.createElement("tbody");
218 message_view.setAttribute("id","message_list_body");
219 message_view.className="mailbox_summary";
220 message_view_parent.appendChild(message_view);
222 var fragment = document.createDocumentFragment();
223 if (sortMode != null) {
224 rowArray.sort(sortMode);
226 var length = rowArray.length;
227 for(var x=0; x<length; ++x) {
229 var currentRow = rowArray[x];
230 currentRow.setAttribute("class","");
232 if (((x-1) % 2) == 0) {
233 className = "table-alt-row";
235 className = "table-row";
237 if (currentRow.ctdlNewMsg) {
238 className += " new_message";
240 currentRow.className = className;
241 /* Using element.onclick is evil, but until IE
242 supports addEventListener, it is much faster
243 than prototype observe */
244 currentRow.onclick = CtdlMessageListClick;
245 currentRow.ctdlDnDElement = summaryViewDragAndDropHandler;
246 currentRow.ctdlRowId = x;
247 fragment.appendChild(currentRow);
249 alert("Exception" + e);
252 message_view.appendChild(fragment);
253 var end = new Date();
254 var delta = end.getTime() - start.getTime();
255 WCLog("resortAndDisplay sort and append: " + delta);
257 normalizeHeaderTable();
259 function sortRowsByDateAscending(a, b) {
260 var dateOne = a.ctdlDate;
261 var dateTwo = b.ctdlDate;
262 return (dateOne - dateTwo);
264 function sortRowsByDateDescending(a, b) {
265 var dateOne = a.ctdlDate;
266 var dateTwo = b.ctdlDate;
267 return (dateTwo - dateOne);
270 function sortRowsBySubjectAscending(a, b) {
271 var subjectOne = getTextContent(a.getElementsByTagName("td")[0]).toLowerCase();
272 var subjectTwo = getTextContent(b.getElementsByTagName("td")[0]).toLowerCase();
273 return strcmp(subjectOne, subjectTwo);
276 function sortRowsBySubjectDescending(a, b) {
277 var subjectOne = getTextContent(a.getElementsByTagName("td")[0]).toLowerCase();
278 var subjectTwo = getTextContent(b.getElementsByTagName("td")[0]).toLowerCase();
279 return strcmp(subjectTwo, subjectOne);
282 function sortRowsByFromAscending(a, b) {
283 var fromOne = getTextContent(a.getElementsByTagName("td")[1]).toLowerCase();
284 var fromTwo = getTextContent(b.getElementsByTagName("td")[1]).toLowerCase();
285 return strcmp(fromOne, fromTwo);
288 function sortRowsByFromDescending(a, b) {
289 var fromOne = getTextContent(a.getElementsByTagName("td")[1]).toLowerCase();
290 var fromTwo = getTextContent(b.getElementsByTagName("td")[1]).toLowerCase();
291 return strcmp(fromTwo, fromOne);
294 function CtdlMessageListClick(evt) {
295 /* Since element.onload is used here, test to see if evt is defined */
296 var event = evt ? evt : window.event;
297 var target = event.target ? event.target: event.srcElement; // and again..
298 var parent = target.parentNode;
299 var msgId = parent.ctdlMsgId;
300 // If the ctrl key modifier wasn't used, unmark all rows and load the message
301 if (!event.shiftKey && !event.ctrlKey && !event.altKey) {
303 markedRowId = parent.ctdlRowId;
304 document.getElementById("preview_pane").innerHTML = "";
305 new Ajax.Updater('preview_pane', 'msg/'+msgId, {method: 'get'});
307 new Ajax.Request('ajax_servcmd', {
309 parameters: 'g_cmd=SEEN ' + msgId + '|1',
310 onComplete: CtdlMarkRowAsRead(parent)});
311 // If the shift key modifier is used, mark a range...
312 } else if (event.button != 2 && event.shiftKey) {
314 var rowId = parent.ctdlRowId;
315 var startMarkingFrom = 0;
317 if (rowId > markedRowId) {
318 startMarkingFrom = markedRowId+1;
320 } else if (rowId < markedRowId) {
321 startMarkingFrom = rowId+1;
322 finish = markedRowId;
324 for(var x = startMarkingFrom; x<finish; x++) {
325 WCLog("Marking row "+x);
326 markRow(rowArray[x]);
328 // If the ctrl key modifier is used, toggle one message
329 } else if (event.button != 2 && (event.ctrlKey || event.altKey)) {
330 if (parent.ctdlMarked == true) {
338 function CtdlMarkRowAsRead(rowElement) {
339 var classes = rowElement.className;
340 classes = classes.replace("new_message","");
341 rowElement.className = classes;
343 function ApplySort(event) {
344 var target = event.target;
345 var sortId = target.id;
346 removeOldSortClass();
347 currentSorterToggle = target;
348 var sortModes = getSortMode(target); // returns [[key, func],[key,func]]
349 var sortModeToUse = null;
350 if (currentSortMode[0] == sortModes[0][0]) {
351 sortModeToUse = sortModes[1];
353 sortModeToUse = sortModes[0];
355 currentSortMode = sortModeToUse;
357 getMessages(); // in safe mode, we load from server already sorted
359 resortAndDisplay(sortModeToUse[1]);
362 function getSortMode(toggleElem) {
365 for(var key in toggles) {
366 var kr = (key.charAt(0) == 'r');
367 if (toggles[key] == toggleElem && !kr) {
368 forward = [key, sortModes[key]];
369 } else if (toggles[key] == toggleElem && kr) {
370 reverse = [key, sortModes[key]];
373 return [forward, reverse];
375 function removeOldSortClass() {
376 if (currentSorterToggle) {
377 var classes = currentSorterToggle.className;
378 /* classes = classes.replace("current_sort_mode","");
379 classes = classes.replace("sort_ascending","");
380 classes = classes.replace("sort_descending",""); */
381 currentSorterToggle.className = "";
384 function markRow(row) {
385 var msgId = row.ctdlMsgId;
386 row.className = row.className += " marked_row";
387 row.ctdlMarked = true;
388 currentlyMarkedRows[msgId] = row;
390 function unmarkRow(row) {
391 var msgId = row.ctdlMsgId;
392 row.className = row.className.replace("marked_row","");
393 row.ctdlMarked = false;
394 delete currentlyMarkedRows[msgId];
396 function unmarkAllRows() {
397 for(msgId in currentlyMarkedRows) {
398 unmarkRow(currentlyMarkedRows[msgId]);
401 function deleteAllMarkedRows() {
402 for(msgId in currentlyMarkedRows) {
403 var row = currentlyMarkedRows[msgId];
404 var rowArrayId = row.ctdlRowId;
405 row.parentNode.removeChild(row);
406 delete currentlyMarkedRows[msgId];
407 delete rowArray[rowArrayId];
409 // Now we have to reconstruct rowarray as the array length has changed */
410 var newRowArray = new Array();
412 for(var i=0; i<rowArray.length; i++) {
413 var currentRow = rowArray[i];
414 if (currentRow != null) {
415 newRowArray[x] = currentRow;
419 rowArray = newRowArray;
420 resortAndDisplay(null);
423 function deleteAllSelectedMessages() {
424 for(msgId in currentlyMarkedRows) {
425 if (!room_is_trash) {
426 new Ajax.Request('ajax_servcmd',
428 parameters: 'g_cmd=MOVE ' + msgId + '|_TRASH_|0'
431 new Ajax.Request('ajax_servcmd', {method: 'post',
432 parameters: 'g_cmd=DELE '+msgId});
435 document.getElementById("preview_pane").innerHTML = "";
436 deleteAllMarkedRows();
439 function CtdlMessageListKeyUp(event) {
440 var key = event.which;
441 if (key == 46) { // DELETE
442 deleteAllSelectedMessages();
446 function clearMessage(msgId) {
447 var row = document.getElementById('msg_'+msgId);
448 row.parentNode.removeChild(row);
449 delete currentlyMarkedRows[msgId];
452 function summaryViewContextMenu() {
453 if (!exitedMouseDown) {
454 var contextSource = document.getElementById("listViewContextMenu");
455 CtdlSpawnContextMenu(mouseDownEvent, contextSource);
459 function summaryViewDragAndDropHandler() {
460 var element = document.createElement("div");
461 var msgList = document.createElement("ul");
462 element.appendChild(msgList);
463 for(msgId in currentlyMarkedRows) {
464 msgRow = currentlyMarkedRows[msgId];
465 var subject = getTextContent(msgRow.getElementsByTagName("td")[0]);
466 var li = document.createElement("li");
467 msgList.appendChild(li);
468 setTextContent(li, subject);
469 li.ctdlMsgId = msgId;
475 function CtdlResizeMouseDown(event) {
476 $(document).observe('mousemove', CtdlResizeMouseMove);
477 $(document).observe('mouseup', CtdlResizeMouseUp);
478 saved_y = event.clientY;
481 function sizePreviewPane() {
482 var preview_pane = document.getElementById("preview_pane");
483 var summary_view = document.getElementById("summary_view");
484 var banner = document.getElementById("banner");
485 var message_list_hdr = document.getElementById("message_list_hdr");
486 var content = $('global'); // we'd like to use prototype methods here
487 var childElements = content.childElements();
488 var sizeOfElementsAbove = 0;
489 var heightOfViewPort = document.viewport.getHeight() // prototypejs method
490 var bannerHeight = banner.offsetHeight;
491 var contentViewPortHeight = heightOfViewPort-banner.offsetHeight-message_list_hdr.offsetHeight;
492 contentViewPortHeight = 0.98 * contentViewPortHeight; // leave some error
493 // Set summary_view to 20%;
494 var summary_height = ctdlLocalPrefs.readPref("svheight");
495 if (summary_height == null) {
496 summary_height = 0.20 * contentViewPortHeight;
498 // Set preview_pane to the remainder
499 var preview_height = contentViewPortHeight - summary_height;
501 summary_view.style.height = (summary_height)+"px";
502 preview_pane.style.height = (preview_height)+"px";
504 function CtdlResizeMouseMove(event) {
505 var clientX = event.clientX;
506 var clientY = event.clientY;
507 var summary_view = document.getElementById("summary_view");
508 var summaryViewHeight = summary_view.offsetHeight;
509 var increment = clientY-saved_y;
510 var summary_view_height = increment+summaryViewHeight;
511 summary_view.style.height = (summary_view_height)+"px";
512 // store summary view height
513 ctdlLocalPrefs.setPref("svheight",summary_view_height);
514 var msglist = document.getElementById("preview_pane");
515 var msgListHeight = msglist.offsetHeight;
516 msglist.style.height = (msgListHeight-increment)+"px";
518 /* For some reason the grippy doesn't work without position: absolute
519 so we need to set its top pos manually all the time */
520 var resize = document.getElementById("resize_msglist");
521 var resizePos = resize.offsetTop;
522 resize.style.top = (resizePos+increment)+"px";
524 function CtdlResizeMouseUp(event) {
525 $(document).stopObserving('mousemove', CtdlResizeMouseMove);
526 $(document).stopObserving('mouseup', CtdlResizeMouseUp);
528 function ApplySorterToggle() {
529 var className = currentSorterToggle.className;
530 className += " current_sort_mode";
531 if (currentSortMode[1] == sortRowsByDateDescending ||
532 currentSortMode[1] == sortRowsBySubjectDescending ||
533 currentSortMode[1] == sortRowsByFromDescending) {
534 className += " sort_descending";
536 className += " sort_ascending";
538 currentSorterToggle.className = className;
540 /** Hack to make the header table line up with the data */
541 function normalizeHeaderTable() {
542 var message_list_hdr = document.getElementById("message_list_hdr");
543 var summary_view = document.getElementById("summary_view");
544 var resize_msglist = document.getElementById("resize_msglist");
545 var headerTable = message_list_hdr.getElementsByTagName("table")[0];
546 var dataTable = summary_view.getElementsByTagName("table")[0];
547 var dataTableWidth = dataTable.offsetWidth;
548 headerTable.style.width = dataTableWidth+"px";
551 function setupPageSelector() {
552 var summpage = document.getElementById("summpage");
553 var select_page = document.getElementById("selectpage");
554 summpage.innerHTML = "";
556 WCLog("unhiding parent page");
557 select_page.className = "";
561 var pages = nummsgs / 500;
562 for(var i=0; i<pages; i++) {
563 var opt = document.createElement("option");
564 var startmsg = i * 500;
565 opt.setAttribute("value",startmsg);
566 if (currentPage == i) {
567 opt.setAttribute("selected","selected");
569 opt.appendChild(document.createTextNode((i+1)));
570 summpage.appendChild(opt);
573 function getPage(event) {
574 var target = event.target;
575 startmsg = target.options.item(target.selectedIndex).value;
576 currentPage = target.selectedIndex;
577 //query = ""; // We are getting a page from the _entire_ msg list, don't query
580 function takeOverSearchOMatic() {
581 var searchForm = document.getElementById("searchomatic").getElementsByTagName("form")[0];
582 // First disable the form post
583 searchForm.setAttribute("action","javascript:void();");
584 searchForm.removeAttribute("method");
585 $(searchForm).observe('submit', doSearch);
587 function doSearch() {
588 query = document.getElementById("srchquery").value;