/* -*- 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);