summaryrefslogtreecommitdiffstats
path: root/comm/suite/mailnews/content/threadPane.js
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--comm/suite/mailnews/content/threadPane.js598
1 files changed, 598 insertions, 0 deletions
diff --git a/comm/suite/mailnews/content/threadPane.js b/comm/suite/mailnews/content/threadPane.js
new file mode 100644
index 0000000000..ac4943d91f
--- /dev/null
+++ b/comm/suite/mailnews/content/threadPane.js
@@ -0,0 +1,598 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+var { AppConstants } =
+ ChromeUtils.import("resource://gre/modules/AppConstants.jsm");
+
+var gLastMessageUriToLoad = null;
+var gThreadPaneCommandUpdater = null;
+
+function ThreadPaneOnClick(event)
+{
+ // usually, we're only interested in tree content clicks, not scrollbars etc.
+ let t = event.originalTarget;
+
+ // we may want to open the message in a new tab on middle click
+ if (event.button == kMouseButtonMiddle)
+ {
+ if (t.localName == "treechildren" && AllowOpenTabOnMiddleClick())
+ {
+ // we don't allow new tabs in the search dialog
+ if (document.documentElement.id != "searchMailWindow")
+ {
+ OpenMessageInNewTab(event);
+ RestoreSelectionWithoutContentLoad(GetThreadTree());
+ }
+ return;
+ }
+ }
+
+ // otherwise, we only care about left click events
+ if (event.button != kMouseButtonLeft)
+ return;
+
+ // We are already handling marking as read and flagging in nsMsgDBView.cpp,
+ // so all we need to worry about here is double clicks and column header.
+ // We also get in here for clicks on the "treecol" (headers) and the
+ // "scrollbarbutton" (scrollbar buttons), but we don't want those events to
+ // cause a "double click".
+ if (t.localName == "treecol")
+ {
+ HandleColumnClick(t.id);
+ }
+ else if (t.localName == "treechildren")
+ {
+ let tree = GetThreadTree();
+ // figure out what cell the click was in
+ var cell = tree.treeBoxObject.getCellAt(event.clientX, event.clientY);
+ if (cell.row == -1)
+ return;
+
+ // If the cell is in a "cycler" column or if the user double clicked on the
+ // twisty, don't open the message in a new window.
+ if (event.detail == 2 && !cell.col.cycler && (cell.childElt != "twisty"))
+ {
+ ThreadPaneDoubleClick(event);
+ // Double clicking should not toggle the open/close state of the thread.
+ // This will happen if we don't prevent the event from bubbling to the
+ // default handler in tree.xml.
+ event.stopPropagation();
+ }
+ else if (cell.col.id == "junkStatusCol")
+ {
+ MsgJunkMailInfo(true);
+ }
+ else if (cell.col.id == "threadCol" && !event.shiftKey && (event.ctrlKey || event.metaKey))
+ {
+ gDBView.ExpandAndSelectThreadByIndex(cell.row, true);
+ event.stopPropagation();
+ }
+ }
+}
+
+function nsMsgDBViewCommandUpdater()
+{}
+
+nsMsgDBViewCommandUpdater.prototype =
+{
+ updateCommandStatus : function()
+ {
+ // the back end is smart and is only telling us to update command status
+ // when the # of items in the selection has actually changed.
+ UpdateMailToolbar("dbview driven, thread pane");
+ },
+
+ displayMessageChanged : function(aFolder, aSubject, aKeywords)
+ {
+ if (!gDBView.suppressMsgDisplay)
+ setTitleFromFolder(aFolder, aSubject);
+ ClearPendingReadTimer(); // we are loading / selecting a new message so kill the mark as read timer for the currently viewed message
+ gHaveLoadedMessage = true;
+ goUpdateCommand("button_delete");
+ goUpdateCommand("button_junk");
+ },
+
+ updateNextMessageAfterDelete : function()
+ {
+ SetNextMessageAfterDelete();
+ },
+
+ summarizeSelection: function() {return false},
+
+ QueryInterface : function(iid)
+ {
+ if (iid.equals(Ci.nsIMsgDBViewCommandUpdater) ||
+ iid.equals(Ci.nsISupports))
+ return this;
+
+ throw Cr.NS_NOINTERFACE;
+ }
+}
+
+function HandleColumnClick(columnID)
+{
+ const columnMap = {dateCol: 'byDate',
+ receivedCol: 'byReceived',
+ senderCol: 'byAuthor',
+ recipientCol: 'byRecipient',
+ subjectCol: 'bySubject',
+ locationCol: 'byLocation',
+ accountCol: 'byAccount',
+ unreadButtonColHeader: 'byUnread',
+ statusCol: 'byStatus',
+ sizeCol: 'bySize',
+ priorityCol: 'byPriority',
+ flaggedCol: 'byFlagged',
+ threadCol: 'byThread',
+ tagsCol: 'byTags',
+ junkStatusCol: 'byJunkStatus',
+ idCol: 'byId',
+ attachmentCol: 'byAttachments'};
+
+
+ var sortType;
+ if (columnID in columnMap) {
+ sortType = columnMap[columnID];
+ } else {
+ // If the column isn't in the map, check and see if it's a custom column
+ try {
+ // try to grab the columnHandler (an error is thrown if it does not exist)
+ columnHandler = gDBView.getColumnHandler(columnID);
+
+ // it exists - save this column ID in the customSortCol property of
+ // dbFolderInfo for later use (see nsIMsgDBView.cpp)
+ gDBView.db.dBFolderInfo.setProperty('customSortCol', columnID);
+
+ sortType = "byCustom";
+ } catch(err) {
+ dump("unsupported sort column: " + columnID + " - no custom handler installed. (Error was: " + err + ")\n");
+ return; // bail out
+ }
+ }
+
+ var dbview = GetDBView();
+ var simpleColumns = false;
+ try {
+ simpleColumns = !Services.prefs.getBoolPref("mailnews.thread_pane_column_unthreads");
+ }
+ catch (ex) {
+ }
+ if (sortType == "byThread") {
+ if (simpleColumns)
+ MsgToggleThreaded();
+ else if (dbview.viewFlags & nsMsgViewFlagsType.kThreadedDisplay)
+ MsgReverseSortThreadPane();
+ else
+ MsgSortByThread();
+ }
+ else {
+ if (!simpleColumns && (dbview.viewFlags & nsMsgViewFlagsType.kThreadedDisplay)) {
+ dbview.viewFlags &= ~nsMsgViewFlagsType.kThreadedDisplay;
+ MsgSortThreadPane(sortType);
+ }
+ else if (dbview.sortType == nsMsgViewSortType[sortType]) {
+ MsgReverseSortThreadPane();
+ }
+ else {
+ MsgSortThreadPane(sortType);
+ }
+ }
+}
+
+function ThreadPaneDoubleClick(event) {
+ if (IsSpecialFolderSelected(Ci.nsMsgFolderFlags.Drafts, true))
+ {
+ MsgComposeDraftMessage();
+ }
+ else if (IsSpecialFolderSelected(Ci.nsMsgFolderFlags.Templates, true))
+ {
+ ComposeMsgByType(Ci.nsIMsgCompType.Template, null,
+ Ci.nsIMsgCompFormat.Default);
+ }
+ else if (AllowOpenTabOnDoubleClick() &&
+ document.documentElement.id != "searchMailWindow")
+ { // we don't allow new tabs in the search dialog
+ // open the message in a new tab on double click
+ OpenMessageInNewTab(event);
+ RestoreSelectionWithoutContentLoad(GetThreadTree());
+ }
+ else
+ {
+ MsgOpenSelectedMessages();
+ }
+}
+
+function ThreadPaneKeyPress(event)
+{
+ if (event.keyCode == KeyEvent.DOM_VK_RETURN) {
+ if ((AppConstants.platform == "macosx" ? event.metaKey : event.ctrlKey) &&
+ AllowOpenTabOnMiddleClick()) {
+ OpenMessageInNewTab(event);
+ } else {
+ ThreadPaneDoubleClick(event);
+ }
+ }
+}
+
+function MsgSortByThread()
+{
+ var dbview = GetDBView();
+ dbview.viewFlags |= nsMsgViewFlagsType.kThreadedDisplay;
+ dbview.viewFlags &= ~nsMsgViewFlagsType.kGroupBySort;
+ MsgSortThreadPane('byDate');
+}
+
+function MsgSortThreadPane(sortName)
+{
+ var sortType = nsMsgViewSortType[sortName];
+ var dbview = GetDBView();
+
+ // turn off grouping
+ dbview.viewFlags &= ~nsMsgViewFlagsType.kGroupBySort;
+
+ dbview.sort(sortType, nsMsgViewSortOrder.ascending);
+ UpdateSortIndicators(sortType, nsMsgViewSortOrder.ascending);
+}
+
+function MsgReverseSortThreadPane()
+{
+ var dbview = GetDBView();
+ if (dbview.sortOrder == nsMsgViewSortOrder.ascending) {
+ MsgSortDescending();
+ }
+ else {
+ MsgSortAscending();
+ }
+}
+
+function MsgToggleThreaded()
+{
+ var dbview = GetDBView();
+ var newViewFlags = dbview.viewFlags ^ nsMsgViewFlagsType.kThreadedDisplay;
+ newViewFlags &= ~nsMsgViewFlagsType.kGroupBySort;
+ dbview.viewFlags = newViewFlags;
+
+ dbview.sort(dbview.sortType, dbview.sortOrder);
+ UpdateSortIndicators(dbview.sortType, dbview.sortOrder);
+}
+
+function MsgSortThreaded()
+{
+ var dbview = GetDBView();
+ var viewFlags = dbview.viewFlags;
+ let wasGrouped = viewFlags & nsMsgViewFlagsType.kGroupBySort;
+ dbview.viewFlags &= ~nsMsgViewFlagsType.kGroupBySort;
+ // if we were grouped, and not a saved search, just rebuild the view
+ if (wasGrouped && !(gMsgFolderSelected.flags &
+ Ci.nsMsgFolderFlags.Virtual))
+ SwitchView("cmd_viewAllMsgs");
+ // Toggle if not already threaded.
+ else if ((viewFlags & nsMsgViewFlagsType.kThreadedDisplay) == 0)
+ MsgToggleThreaded();
+}
+
+function MsgGroupBySort()
+{
+ var dbview = GetDBView();
+ var viewFlags = dbview.viewFlags;
+ var sortOrder = dbview.sortOrder;
+ var sortType = dbview.sortType;
+ var count = new Object;
+ var msgFolder = dbview.msgFolder;
+
+ var sortTypeSupportsGrouping = (sortType == nsMsgViewSortType.byAuthor
+ || sortType == nsMsgViewSortType.byDate || sortType == nsMsgViewSortType.byReceived || sortType == nsMsgViewSortType.byPriority
+ || sortType == nsMsgViewSortType.bySubject || sortType == nsMsgViewSortType.byTags
+ || sortType == nsMsgViewSortType.byStatus || sortType == nsMsgViewSortType.byRecipient
+ || sortType == nsMsgViewSortType.byAccount || sortType == nsMsgViewSortType.byFlagged
+ || sortType == nsMsgViewSortType.byAttachments);
+
+ if (!sortTypeSupportsGrouping)
+ return; // we shouldn't be trying to group something we don't support grouping for...
+
+ viewFlags |= nsMsgViewFlagsType.kThreadedDisplay | nsMsgViewFlagsType.kGroupBySort;
+ if (gDBView &&
+ gMsgFolderSelected.flags & Ci.nsMsgFolderFlags.Virtual)
+ {
+ gDBView.viewFlags = viewFlags;
+ UpdateSortIndicators(sortType, nsMsgViewSortOrder.ascending);
+ return;
+ }
+ // null this out, so we don't try sort.
+ if (gDBView) {
+ gDBView.close();
+ gDBView = null;
+ }
+ gDBView = Cc["@mozilla.org/messenger/msgdbview;1?type=group"]
+ .createInstance(Ci.nsIMsgDBView);
+
+ if (!gThreadPaneCommandUpdater)
+ gThreadPaneCommandUpdater = new nsMsgDBViewCommandUpdater();
+
+
+ gDBView.init(messenger, msgWindow, gThreadPaneCommandUpdater);
+ gDBView.open(msgFolder, sortType, sortOrder, viewFlags, count);
+ RerootThreadPane();
+ UpdateSortIndicators(sortType, nsMsgViewSortOrder.ascending);
+ Services.obs.notifyObservers(msgFolder, "MsgCreateDBView",
+ Ci.nsMsgViewType.eShowAllThreads + ":" + viewFlags);
+}
+
+function MsgSortUnthreaded()
+{
+ // Toggle if not already unthreaded.
+ if ((GetDBView().viewFlags & nsMsgViewFlagsType.kThreadedDisplay) != 0)
+ MsgToggleThreaded();
+}
+
+function MsgSortAscending()
+{
+ var dbview = GetDBView();
+ dbview.sort(dbview.sortType, nsMsgViewSortOrder.ascending);
+ UpdateSortIndicators(dbview.sortType, nsMsgViewSortOrder.ascending);
+}
+
+function MsgSortDescending()
+{
+ var dbview = GetDBView();
+ dbview.sort(dbview.sortType, nsMsgViewSortOrder.descending);
+ UpdateSortIndicators(dbview.sortType, nsMsgViewSortOrder.descending);
+}
+
+function groupedBySortUsingDummyRow()
+{
+ return (gDBView.viewFlags & nsMsgViewFlagsType.kGroupBySort) &&
+ (gDBView.sortType != nsMsgViewSortType.bySubject);
+}
+
+function UpdateSortIndicators(sortType, sortOrder)
+{
+ // Remove the sort indicator from all the columns
+ var treeColumns = document.getElementById('threadCols').childNodes;
+ for (var i = 0; i < treeColumns.length; i++)
+ treeColumns[i].removeAttribute('sortDirection');
+
+ // show the twisties if the view is threaded
+ var threadCol = document.getElementById("threadCol");
+ var sortedColumn;
+ // set the sort indicator on the column we are sorted by
+ var colID = ConvertSortTypeToColumnID(sortType);
+ if (colID)
+ sortedColumn = document.getElementById(colID);
+
+ var dbview = GetDBView();
+ var currCol = dbview.viewFlags & nsMsgViewFlagsType.kGroupBySort
+ ? sortedColumn : document.getElementById("subjectCol");
+
+ if (dbview.viewFlags & nsMsgViewFlagsType.kGroupBySort)
+ {
+ var threadTree = document.getElementById("threadTree");
+ var subjectCol = document.getElementById("subjectCol");
+
+ if (groupedBySortUsingDummyRow())
+ {
+ currCol.removeAttribute("primary");
+ subjectCol.setAttribute("primary", "true");
+ }
+
+ // hide the threaded column when in grouped view since you can't do
+ // threads inside of a group.
+ document.getElementById("threadCol").collapsed = true;
+ }
+
+ // clear primary attribute from group column if going to a non-grouped view.
+ if (!(dbview.viewFlags & nsMsgViewFlagsType.kGroupBySort))
+ document.getElementById("threadCol").collapsed = false;
+
+ if ((dbview.viewFlags & nsMsgViewFlagsType.kThreadedDisplay) && !groupedBySortUsingDummyRow()) {
+ threadCol.setAttribute("sortDirection", "ascending");
+ currCol.setAttribute("primary", "true");
+ }
+ else {
+ threadCol.removeAttribute("sortDirection");
+ currCol.removeAttribute("primary");
+ }
+
+ if (sortedColumn) {
+ if (sortOrder == nsMsgViewSortOrder.ascending) {
+ sortedColumn.setAttribute("sortDirection","ascending");
+ }
+ else {
+ sortedColumn.setAttribute("sortDirection","descending");
+ }
+ }
+}
+
+function IsSpecialFolderSelected(flags, checkAncestors)
+{
+ var folder = GetThreadPaneFolder();
+ return folder && folder.isSpecialFolder(flags, checkAncestors);
+}
+
+function GetThreadTree()
+{
+ return document.getElementById("threadTree")
+}
+
+function GetThreadPaneFolder()
+{
+ try {
+ return gDBView.msgFolder;
+ }
+ catch (ex) {
+ return null;
+ }
+}
+
+function EnsureRowInThreadTreeIsVisible(index)
+{
+ if (index < 0)
+ return;
+
+ var tree = GetThreadTree();
+ tree.treeBoxObject.ensureRowIsVisible(index);
+}
+
+function RerootThreadPane()
+{
+ SetNewsFolderColumns();
+
+ var treeView = gDBView.QueryInterface(Ci.nsITreeView);
+ if (treeView)
+ {
+ var tree = GetThreadTree();
+ tree.view = treeView;
+ }
+}
+
+function ThreadPaneOnLoad()
+{
+ var tree = GetThreadTree();
+ // We won't have the tree if we're in a message window, so exit silently
+ if (!tree)
+ return;
+
+ tree.addEventListener("click",ThreadPaneOnClick,true);
+
+ // The mousedown event listener below should only be added in the thread
+ // pane of the mailnews 3pane window, not in the advanced search window.
+ if(tree.parentNode.id == "searchResultListBox")
+ return;
+
+ tree.addEventListener("mousedown",TreeOnMouseDown,true);
+ var delay = Services.prefs.getIntPref("mailnews.threadpane_select_delay");
+ document.getElementById("threadTree")._selectDelay = delay;
+}
+
+function ThreadPaneSelectionChanged()
+{
+ UpdateStatusMessageCounts(gMsgFolderSelected);
+ if (!gRightMouseButtonDown)
+ GetThreadTree().view.selectionChanged();
+}
+
+var ThreadPaneDND = {
+ onDragStart(aEvent) {
+ if (aEvent.originalTarget.localName != "treechildren")
+ return;
+
+ let messageUris = gFolderDisplay.selectedMessageUris;
+ if (!messageUris)
+ return;
+
+ // A message can be dragged from one window and dropped on another window.
+ // Therefore we setNextMessageAfterDelete() here since there is no major
+ // disadvantage, even if it is a copy operation.
+ SetNextMessageAfterDelete();
+ let messengerBundle = document.getElementById("bundle_messenger");
+ let noSubject = messengerBundle.getString("defaultSaveMessageAsFileName");
+ if (noSubject.endsWith(".eml")) {
+ noSubject = noSubject.slice(0, -4);
+ }
+ let fileNames = [];
+ let dataTransfer = aEvent.dataTransfer;
+
+ for (let [index, msgUri] of messageUris.entries()) {
+ let msgService = messenger.messageServiceFromURI(msgUri);
+ let msgHdr = msgService.messageURIToMsgHdr(msgUri);
+ let subject = msgHdr.mime2DecodedSubject || noSubject;
+ if (msgHdr.flags & Ci.nsMsgMessageFlags.HasRe) {
+ subject = "Re: " + subject;
+ }
+ let uniqueFileName = suggestUniqueFileName(subject.substr(0, 120), ".eml",
+ fileNames);
+ fileNames[index] = uniqueFileName;
+ let msgUrl = {};
+ msgService.GetUrlForUri(msgUri, msgUrl, null);
+ dataTransfer.mozSetDataAt("text/x-moz-message", msgUri, index);
+ dataTransfer.mozSetDataAt("text/x-moz-url", msgUrl.value.spec, index);
+ dataTransfer.mozSetDataAt("application/x-moz-file-promise-url",
+ msgUrl.value.spec + "?fileName=" +
+ encodeURIComponent(uniqueFileName),
+ index);
+ dataTransfer.mozSetDataAt("application/x-moz-file-promise",
+ new messageFlavorDataProvider(), index);
+ }
+ dataTransfer.effectAllowed = "copyMove";
+ dataTransfer.addElement(aEvent.originalTarget);
+ },
+
+ onDragOver(aEvent) {
+ if (!gMsgFolderSelected.canFileMessages ||
+ gMsgFolderSelected.server.type == "rss")
+ return;
+ let dt = aEvent.dataTransfer;
+ dt.effectAllowed = "copy";
+ for (let i = 0; i < dt.mozItemCount; i++) {
+ if (Array.from(dt.mozTypesAt(i)).includes("application/x-moz-file")) {
+ let extFile = dt.mozGetDataAt("application/x-moz-file", i);
+ if (!extFile) {
+ return;
+ }
+
+ extFile = extFile.QueryInterface(Ci.nsIFile);
+ if (extFile.isFile() && /\.eml$/i.test(extFile.leafName)) {
+ aEvent.preventDefault();
+ return;
+ }
+ }
+ }
+ },
+
+ onDrop(aEvent) {
+ let dt = aEvent.dataTransfer;
+ for (let i = 0; i < dt.mozItemCount; i++) {
+ let extFile = dt.mozGetDataAt("application/x-moz-file", i);
+ if (!extFile) {
+ continue;
+ }
+
+ extFile = extFile.QueryInterface(Ci.nsIFile);
+ if (extFile.isFile() && /\.eml$/i.test(extFile.leafName))
+ MailServices.copy.CopyFileMessage(extFile, gMsgFolderSelected, null,
+ false, 1, "", null, msgWindow);
+ }
+ },
+}
+
+function messageFlavorDataProvider() {}
+
+messageFlavorDataProvider.prototype = {
+ QueryInterface: ChromeUtils.generateQI(["nsIFlavorDataProvider"]),
+
+ getFlavorData(aTransferable, aFlavor, aData, aDataLen) {
+ if (aFlavor !== "application/x-moz-file-promise") {
+ return;
+ }
+ let fileUriPrimitive = {};
+ let dataSize = {};
+ aTransferable.getTransferData("application/x-moz-file-promise-url",
+ fileUriPrimitive, dataSize);
+
+ let fileUriStr = fileUriPrimitive.value
+ .QueryInterface(Ci.nsISupportsString);
+ let fileUri = Services.io.newURI(fileUriStr.data);
+ let fileUrl = fileUri.QueryInterface(Ci.nsIURL);
+ let fileName = fileUrl.fileName;
+
+ let destDirPrimitive = {};
+ aTransferable.getTransferData("application/x-moz-file-promise-dir",
+ destDirPrimitive, dataSize);
+ let destDirectory = destDirPrimitive.value.QueryInterface(Ci.nsIFile);
+ let file = destDirectory.clone();
+ file.append(fileName);
+
+ let messageUriPrimitive = {};
+ aTransferable.getTransferData("text/x-moz-message", messageUriPrimitive,
+ dataSize);
+ let messageUri = messageUriPrimitive.value
+ .QueryInterface(Ci.nsISupportsString);
+
+ messenger.saveAs(messageUri.data, true, null, decodeURIComponent(file.path),
+ true);
+ },
+};
+
+addEventListener("load",ThreadPaneOnLoad,true);