summaryrefslogtreecommitdiffstats
path: root/mailnews/base/content
diff options
context:
space:
mode:
authorMatt A. Tobin <email@mattatobin.com>2019-11-03 00:17:46 -0400
committerMatt A. Tobin <email@mattatobin.com>2019-11-03 00:17:46 -0400
commit302bf1b523012e11b60425d6eee1221ebc2724eb (patch)
treeb191a895f8716efcbe42f454f37597a545a6f421 /mailnews/base/content
parent21b3f6247403c06f85e1f45d219f87549862198f (diff)
downloadUXP-302bf1b523012e11b60425d6eee1221ebc2724eb.tar
UXP-302bf1b523012e11b60425d6eee1221ebc2724eb.tar.gz
UXP-302bf1b523012e11b60425d6eee1221ebc2724eb.tar.lz
UXP-302bf1b523012e11b60425d6eee1221ebc2724eb.tar.xz
UXP-302bf1b523012e11b60425d6eee1221ebc2724eb.zip
Issue #1258 - Part 1: Import mailnews, ldap, and mork from comm-esr52.9.1
Diffstat (limited to 'mailnews/base/content')
-rw-r--r--mailnews/base/content/charsetList.css5
-rw-r--r--mailnews/base/content/charsetList.xml69
-rw-r--r--mailnews/base/content/dateFormat.js225
-rw-r--r--mailnews/base/content/folderProps.js387
-rw-r--r--mailnews/base/content/folderProps.xul216
-rw-r--r--mailnews/base/content/folderWidgets.xml829
-rw-r--r--mailnews/base/content/jsTreeView.js235
-rw-r--r--mailnews/base/content/junkCommands.js456
-rw-r--r--mailnews/base/content/junkLog.js33
-rw-r--r--mailnews/base/content/junkLog.xul46
-rw-r--r--mailnews/base/content/junkMailInfo.xul35
-rw-r--r--mailnews/base/content/markByDate.js133
-rw-r--r--mailnews/base/content/markByDate.xul34
-rw-r--r--mailnews/base/content/msgAccountCentral.js341
-rw-r--r--mailnews/base/content/msgAccountCentral.xul251
-rw-r--r--mailnews/base/content/msgFolderPickerOverlay.js99
-rw-r--r--mailnews/base/content/msgPrintEngine.js209
-rw-r--r--mailnews/base/content/msgSynchronize.js199
-rw-r--r--mailnews/base/content/msgSynchronize.xul43
-rw-r--r--mailnews/base/content/newFolderDialog.js85
-rw-r--r--mailnews/base/content/newFolderDialog.xul57
-rw-r--r--mailnews/base/content/newmailalert.css35
-rw-r--r--mailnews/base/content/newmailalert.js186
-rw-r--r--mailnews/base/content/newmailalert.xul35
-rw-r--r--mailnews/base/content/renameFolderDialog.js47
-rw-r--r--mailnews/base/content/renameFolderDialog.xul23
-rw-r--r--mailnews/base/content/retention.js45
-rw-r--r--mailnews/base/content/shareglue.js20
-rw-r--r--mailnews/base/content/shutdownWindow.js99
-rw-r--r--mailnews/base/content/shutdownWindow.xul31
-rw-r--r--mailnews/base/content/virtualFolderListDialog.js136
-rw-r--r--mailnews/base/content/virtualFolderListDialog.xul91
-rw-r--r--mailnews/base/content/virtualFolderProperties.js266
-rw-r--r--mailnews/base/content/virtualFolderProperties.xul103
34 files changed, 5104 insertions, 0 deletions
diff --git a/mailnews/base/content/charsetList.css b/mailnews/base/content/charsetList.css
new file mode 100644
index 000000000..cfc9af542
--- /dev/null
+++ b/mailnews/base/content/charsetList.css
@@ -0,0 +1,5 @@
+@namespace url("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul");
+
+menulist[type="charset"] {
+ -moz-binding: url("chrome://messenger/content/charsetList.xml#charsetpicker");
+}
diff --git a/mailnews/base/content/charsetList.xml b/mailnews/base/content/charsetList.xml
new file mode 100644
index 000000000..bff43059e
--- /dev/null
+++ b/mailnews/base/content/charsetList.xml
@@ -0,0 +1,69 @@
+<?xml version="1.0"?>
+<!-- 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/.
+-->
+
+<bindings id="charsetListBinding"
+ xmlns="http://www.mozilla.org/xbl"
+ xmlns:xbl="http://www.mozilla.org/xbl"
+ xmlns:html="http://www.w3.org/1999/xhtml"
+ xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+ <binding id="charsetpicker"
+ extends="chrome://global/content/bindings/menulist.xml#menulist">
+ <implementation>
+ <constructor><![CDATA[
+ let charsetValues = "";
+
+ if (this.getAttribute("subset") == "sending")
+ charsetValues = ["UTF-8", "EUC-KR", "gbk", "gb18030", "ISO-2022-JP",
+ "ISO-8859-1", "ISO-8859-7", "windows-1252"];
+
+ else if (!this.getAttribute("subset")
+ || this.getAttribute("subset") == "viewing")
+ charsetValues = ["UTF-8", "Big5", "EUC-KR", "gbk", "ISO-2022-JP",
+ "ISO-8859-1", "ISO-8859-2", "ISO-8859-7",
+ "windows-874", "windows-1250", "windows-1251",
+ "windows-1252", "windows-1255", "windows-1256",
+ "windows-1257", "windows-1258"];
+
+ Components.utils.import("resource://gre/modules/Services.jsm");
+
+ let charsetBundle = Services.strings.createBundle(
+ "chrome://messenger/locale/charsetTitles.properties");
+ let aMenuList = this;
+ let menuLabels = [];
+
+ charsetValues.forEach(function(item) {
+ let strCharset = charsetBundle.GetStringFromName(
+ item.toLowerCase() + ".title");
+
+ menuLabels.push({label: strCharset, value: item});
+ });
+
+ menuLabels.sort(function(a, b) {
+ if (a.value == "UTF-8" || a.label < b.label)
+ return -1;
+ else if (b.value == "UTF-8" || a.label > b.label)
+ return 1;
+ return 0;
+ });
+
+ menuLabels.forEach(function(item) {
+ aMenuList.appendItem(item.label, item.value);
+ });
+
+ // Selecting appropiate menu item corresponding to preference stored
+ // value.
+ if (this.hasAttribute("preference")) {
+ const Ci = Components.interfaces;
+
+ let preference = Services.prefs.getComplexValue(
+ this.getAttribute("preference"), Ci.nsIPrefLocalizedString);
+ this.value = preference.data;
+ }
+ ]]></constructor>
+ </implementation>
+ </binding>
+</bindings>
diff --git a/mailnews/base/content/dateFormat.js b/mailnews/base/content/dateFormat.js
new file mode 100644
index 000000000..71c628697
--- /dev/null
+++ b/mailnews/base/content/dateFormat.js
@@ -0,0 +1,225 @@
+/* -*- Mode: Java; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* 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/. */
+
+Components.utils.import("resource://gre/modules/Services.jsm");
+
+var gSearchDateFormat = 0;
+var gSearchDateSeparator;
+var gSearchDateLeadingZeros;
+
+/**
+ * Get the short date format option of the current locale.
+ * This supports the common case which the date separator is
+ * either '/', '-', '.' and using Christian year.
+ */
+function initLocaleShortDateFormat()
+{
+ // default to mm/dd/yyyy
+ gSearchDateFormat = 3;
+ gSearchDateSeparator = "/";
+ gSearchDateLeadingZeros = true;
+
+ try {
+ var dateFormatService = Components.classes["@mozilla.org/intl/scriptabledateformat;1"]
+ .getService(Components.interfaces.nsIScriptableDateFormat);
+ var dateString = dateFormatService.FormatDate("",
+ dateFormatService.dateFormatShort,
+ 1999,
+ 12,
+ 1);
+
+ // find out the separator
+ var possibleSeparators = "/-.";
+ var arrayOfStrings;
+ for ( var i = 0; i < possibleSeparators.length; ++i )
+ {
+ arrayOfStrings = dateString.split( possibleSeparators[i] );
+ if ( arrayOfStrings.length == 3 )
+ {
+ gSearchDateSeparator = possibleSeparators[i];
+ break;
+ }
+ }
+
+ // check the format option
+ if ( arrayOfStrings.length != 3 ) // no successfull split
+ {
+ dump("getLocaleShortDateFormat: could not analyze the date format, defaulting to mm/dd/yyyy\n");
+ }
+ else
+ {
+ // the date will contain a zero if that system settings include leading zeros
+ gSearchDateLeadingZeros = dateString.includes("0");
+
+ // match 1 as number, since that will match both "1" and "01"
+ if ( arrayOfStrings[0] == 1 )
+ {
+ // 01.12.1999 or 01.1999.12
+ gSearchDateFormat = arrayOfStrings[1] == "12" ? 5 : 6;
+ }
+ else if ( arrayOfStrings[1] == 1 )
+ {
+ // 12.01.1999 or 1999.01.12
+ gSearchDateFormat = arrayOfStrings[0] == "12" ? 3 : 2;
+ }
+ else // implies arrayOfStrings[2] == 1
+ {
+ // 12.1999.01 or 1999.12.01
+ gSearchDateFormat = arrayOfStrings[0] == "12" ? 4 : 1;
+ }
+ }
+ }
+ catch (e)
+ {
+ dump("getLocaleShortDateFormat: caught an exception!\n");
+ }
+}
+
+function initializeSearchDateFormat()
+{
+ if (gSearchDateFormat)
+ return;
+
+ // get a search date format option and a seprator
+ try {
+ gSearchDateFormat =
+ Services.prefs.getComplexValue("mailnews.search_date_format",
+ Components.interfaces.nsIPrefLocalizedString);
+ gSearchDateFormat = parseInt(gSearchDateFormat);
+
+ // if the option is 0 then try to use the format of the current locale
+ if (gSearchDateFormat == 0)
+ initLocaleShortDateFormat();
+ else
+ {
+ // initialize the search date format based on preferences
+ if ( gSearchDateFormat < 1 || gSearchDateFormat > 6 )
+ gSearchDateFormat = 3;
+
+ gSearchDateSeparator =
+ Services.prefs.getComplexValue("mailnews.search_date_separator",
+ Components.interfaces.nsIPrefLocalizedString);
+
+ gSearchDateLeadingZeros =
+ (Services.prefs.getComplexValue(
+ "mailnews.search_date_leading_zeros",
+ Components.interfaces.nsIPrefLocalizedString).data == "true");
+ }
+ }
+ catch (e)
+ {
+ Components.utils.reportError("initializeSearchDateFormat: caught an exception: " + e);
+ // set to mm/dd/yyyy in case of error
+ gSearchDateFormat = 3;
+ gSearchDateSeparator = "/";
+ gSearchDateLeadingZeros = true;
+ }
+}
+
+function convertPRTimeToString(tm)
+{
+ var time = new Date();
+ // PRTime is in microseconds, JavaScript time is in milliseconds
+ // so divide by 1000 when converting
+ time.setTime(tm / 1000);
+
+ return convertDateToString(time);
+}
+
+function convertDateToString(time)
+{
+ initializeSearchDateFormat();
+
+ var year = time.getFullYear();
+ var month = time.getMonth() + 1; // since js month is 0-11
+ if ( gSearchDateLeadingZeros && month < 10 )
+ month = "0" + month;
+ var date = time.getDate();
+ if ( gSearchDateLeadingZeros && date < 10 )
+ date = "0" + date;
+
+ var dateStr;
+ var sep = gSearchDateSeparator;
+
+ switch (gSearchDateFormat)
+ {
+ case 1:
+ dateStr = year + sep + month + sep + date;
+ break;
+ case 2:
+ dateStr = year + sep + date + sep + month;
+ break;
+ case 3:
+ dateStr = month + sep + date + sep + year;
+ break;
+ case 4:
+ dateStr = month + sep + year + sep + date;
+ break;
+ case 5:
+ dateStr = date + sep + month + sep + year;
+ break;
+ case 6:
+ dateStr = date + sep + year + sep + month;
+ break;
+ default:
+ dump("valid search date format option is 1-6\n");
+ }
+
+ return dateStr;
+}
+
+function convertStringToPRTime(str)
+{
+ initializeSearchDateFormat();
+
+ var arrayOfStrings = str.split(gSearchDateSeparator);
+ var year, month, date;
+
+ // set year, month, date based on the format option
+ switch (gSearchDateFormat)
+ {
+ case 1:
+ year = arrayOfStrings[0];
+ month = arrayOfStrings[1];
+ date = arrayOfStrings[2];
+ break;
+ case 2:
+ year = arrayOfStrings[0];
+ month = arrayOfStrings[2];
+ date = arrayOfStrings[1];
+ break;
+ case 3:
+ year = arrayOfStrings[2];
+ month = arrayOfStrings[0];
+ date = arrayOfStrings[1];
+ break;
+ case 4:
+ year = arrayOfStrings[1];
+ month = arrayOfStrings[0];
+ date = arrayOfStrings[2];
+ break;
+ case 5:
+ year = arrayOfStrings[2];
+ month = arrayOfStrings[1];
+ date = arrayOfStrings[0];
+ break;
+ case 6:
+ year = arrayOfStrings[1];
+ month = arrayOfStrings[2];
+ date = arrayOfStrings[0];
+ break;
+ default:
+ dump("valid search date format option is 1-6\n");
+ }
+
+ month -= 1; // since js month is 0-11
+
+ var time = new Date(year, month, date);
+
+ // JavaScript time is in milliseconds, PRTime is in microseconds
+ // so multiply by 1000 when converting
+ return (time.getTime() * 1000);
+}
+
diff --git a/mailnews/base/content/folderProps.js b/mailnews/base/content/folderProps.js
new file mode 100644
index 000000000..98e1312e8
--- /dev/null
+++ b/mailnews/base/content/folderProps.js
@@ -0,0 +1,387 @@
+/* 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/. */
+
+Components.utils.import("resource://gre/modules/Services.jsm");
+Components.utils.import("resource:///modules/gloda/gloda.js");
+
+var gMsgFolder;
+var gLockedPref = null;
+
+// The folderPropsSink is the class that gets notified of an imap folder's properties
+
+var gFolderPropsSink = {
+ setFolderType: function(folderTypeString)
+ {
+ var typeLabel = document.getElementById("folderType.text");
+ if (typeLabel)
+ {
+ typeLabel.setAttribute("value",folderTypeString);
+ }
+ // get the element for the folder type label and set value on it.
+ },
+
+ setFolderTypeDescription: function(folderDescription)
+ {
+ var folderTypeLabel = document.getElementById("folderDescription.text");
+ if (folderTypeLabel)
+ folderTypeLabel.setAttribute("value", folderDescription);
+ },
+
+ setFolderPermissions: function(folderPermissions)
+ {
+ var permissionsLabel = document.getElementById("folderPermissions.text");
+ var descTextNode = document.createTextNode(folderPermissions);
+ permissionsLabel.appendChild(descTextNode);
+ },
+
+ serverDoesntSupportACL : function()
+ {
+ var typeLabel = document.getElementById("folderTypeLabel");
+ if (typeLabel)
+ typeLabel.setAttribute("hidden", "true");
+ var permissionsLabel = document.getElementById("permissionsDescLabel");
+ if (permissionsLabel)
+ permissionsLabel.setAttribute("hidden", "true");
+
+ },
+
+ setQuotaStatus : function(folderQuotaStatus)
+ {
+ var quotaStatusLabel = document.getElementById("folderQuotaStatus");
+ if(quotaStatusLabel)
+ quotaStatusLabel.setAttribute("value", folderQuotaStatus);
+ },
+
+ showQuotaData : function(showData)
+ {
+ var quotaStatusLabel = document.getElementById("folderQuotaStatus");
+ var folderQuotaData = document.getElementById("folderQuotaData");
+
+ if(quotaStatusLabel && folderQuotaData)
+ {
+ quotaStatusLabel.hidden = showData;
+ folderQuotaData.hidden = ! showData;
+ }
+ },
+
+ setQuotaData : function(root, usedKB, maxKB)
+ {
+ var quotaRoot = document.getElementById("quotaRoot");
+ if (quotaRoot)
+ quotaRoot.setAttribute("value", '"' + root + '"');
+
+ var percentage = (maxKB != 0) ? Math.round(usedKB / maxKB * 100) : 0;
+
+ var quotaPercentageBar = document.getElementById("quotaPercentageBar");
+ if (quotaPercentageBar)
+ quotaPercentageBar.setAttribute("value", percentage);
+
+ var bundle = document.getElementById("bundle_messenger");
+ if(bundle)
+ {
+ var usedFreeCaption = bundle.getFormattedString("quotaUsedFree", [usedKB, maxKB], 2);
+ var quotaCaption = document.getElementById("quotaUsedFree");
+ if(quotaCaption)
+ quotaCaption.setAttribute("value", usedFreeCaption);
+
+ var percentUsedCaption = bundle.getFormattedString("quotaPercentUsed", [percentage], 1);
+ var percentUsed = document.getElementById("quotaPercentUsed");
+ if(percentUsed)
+ percentUsed.setAttribute("value", percentUsedCaption);
+ }
+ }
+
+};
+
+function doEnabling()
+{
+ var nameTextbox = document.getElementById("name");
+ document.documentElement.getButton("accept").disabled = !nameTextbox.value;
+}
+
+function folderPropsOKButton()
+{
+ if (gMsgFolder)
+ {
+ const nsMsgFolderFlags = Components.interfaces.nsMsgFolderFlags;
+ // set charset attributes
+ var folderCharsetList = document.getElementById("folderCharsetList");
+
+ // Log to the Error Console the charset value for the folder
+ // if it is unknown to us. Value will be preserved by the menu-item.
+ if (folderCharsetList.selectedIndex == -1)
+ {
+ Components.utils.reportError("Unknown folder encoding; folder=" +
+ gMsgFolder.name + ", charset=" + gMsgFolder.charset);
+ }
+
+ gMsgFolder.charset = folderCharsetList.getAttribute("value");
+ gMsgFolder.charsetOverride = document.getElementById("folderCharsetOverride")
+ .checked;
+
+ if(document.getElementById("offline.selectForOfflineFolder").checked ||
+ document.getElementById("offline.selectForOfflineNewsgroup").checked)
+ gMsgFolder.setFlag(nsMsgFolderFlags.Offline);
+ else
+ gMsgFolder.clearFlag(nsMsgFolderFlags.Offline);
+
+ if(document.getElementById("folderCheckForNewMessages").checked)
+ gMsgFolder.setFlag(nsMsgFolderFlags.CheckNew);
+ else
+ gMsgFolder.clearFlag(nsMsgFolderFlags.CheckNew);
+
+ let glodaCheckbox = document.getElementById("folderIncludeInGlobalSearch");
+ if (!glodaCheckbox.hidden) {
+ if(glodaCheckbox.checked) {
+ // We pass true here so that folders such as trash and junk can still
+ // have a priority set.
+ Gloda.resetFolderIndexingPriority(gMsgFolder, true);
+ } else {
+ Gloda.setFolderIndexingPriority(gMsgFolder,
+ Gloda.getFolderForFolder(gMsgFolder).kIndexingNeverPriority);
+ }
+ }
+
+ var retentionSettings = saveCommonRetentionSettings(gMsgFolder.retentionSettings);
+ retentionSettings.useServerDefaults = document.getElementById("retention.useDefault").checked;
+ gMsgFolder.retentionSettings = retentionSettings;
+
+ }
+
+ try
+ {
+ // This throws an exception when an illegal folder name was entered.
+ okCallback(document.getElementById("name").value, window.arguments[0].name,
+ gMsgFolder.URI);
+
+ return true;
+ }
+ catch (e)
+ {
+ return false;
+ }
+}
+
+function folderPropsOnLoad()
+{
+ // look in arguments[0] for parameters
+ if (window.arguments && window.arguments[0]) {
+ if ( window.arguments[0].title ) {
+ document.title = window.arguments[0].title;
+ }
+ if ( window.arguments[0].okCallback ) {
+ top.okCallback = window.arguments[0].okCallback;
+ }
+ }
+
+ // fill in folder name, based on what they selected in the folder pane
+ if (window.arguments[0].folder) {
+ gMsgFolder = window.arguments[0].folder;
+ } else {
+ dump("passed null for folder, do nothing\n");
+ }
+
+ if(window.arguments[0].name)
+ {
+ // Initialize name textbox with the given name and remember this
+ // value so we can tell whether the folder needs to be renamed
+ // when the dialog is accepted.
+ var nameTextbox = document.getElementById("name");
+ nameTextbox.value = window.arguments[0].name;
+
+// name.setSelectionRange(0,-1);
+// name.focusTextField();
+ }
+
+ const nsMsgFolderFlags = Components.interfaces.nsMsgFolderFlags;
+ const serverType = window.arguments[0].serverType;
+
+ // Do this first, because of gloda we may want to override some of the hidden
+ // statuses.
+ hideShowControls(serverType);
+
+ if (gMsgFolder) {
+ // We really need a functioning database, so we'll detect problems
+ // and create one if we have to.
+ try {
+ var db = gMsgFolder.getDatabase(null);
+ }
+ catch (e) {
+ gMsgFolder.updateFolder(window.arguments[0].msgWindow);
+ }
+
+ var locationTextbox = document.getElementById("location");
+
+ // Decode the displayed mailbox:// URL as it's useful primarily for debugging,
+ // whereas imap and news urls are sent around.
+ locationTextbox.value = (serverType == "imap" || serverType == "nntp") ?
+ gMsgFolder.folderURL : decodeURI(gMsgFolder.folderURL);
+
+ if (gMsgFolder.canRename)
+ document.getElementById("name").removeAttribute("readonly");
+
+ if (gMsgFolder.flags & nsMsgFolderFlags.Offline) {
+
+ if(serverType == "imap" || serverType == "pop3")
+ document.getElementById("offline.selectForOfflineFolder").checked = true;
+
+ if(serverType == "nntp")
+ document.getElementById("offline.selectForOfflineNewsgroup").checked = true;
+ }
+ else {
+ if(serverType == "imap" || serverType == "pop3")
+ document.getElementById("offline.selectForOfflineFolder").checked = false;
+
+ if(serverType == "nntp")
+ document.getElementById("offline.selectForOfflineNewsgroup").checked = false;
+ }
+
+ // select the menu item
+ var folderCharsetList = document.getElementById("folderCharsetList");
+ folderCharsetList.value = gMsgFolder.charset;
+
+ // set override checkbox
+ document.getElementById("folderCharsetOverride").checked = gMsgFolder.charsetOverride;
+
+ // set check for new mail checkbox
+ document.getElementById("folderCheckForNewMessages").checked = gMsgFolder.flags & nsMsgFolderFlags.CheckNew;
+
+ // if gloda indexing is off, hide the related checkbox
+ var glodaCheckbox = document.getElementById("folderIncludeInGlobalSearch");
+ var glodaEnabled = Services.prefs
+ .getBoolPref("mailnews.database.global.indexer.enabled");
+ if (!glodaEnabled || (gMsgFolder.flags & (nsMsgFolderFlags.Queue |
+ nsMsgFolderFlags.Newsgroup))) {
+ glodaCheckbox.hidden = true;
+ } else {
+ // otherwise, the user can choose whether this file gets indexed
+ let glodaFolder = Gloda.getFolderForFolder(gMsgFolder);
+ glodaCheckbox.checked =
+ glodaFolder.indexingPriority != glodaFolder.kIndexingNeverPriority;
+ }
+ }
+
+ if (serverType == "imap")
+ {
+ var imapFolder = gMsgFolder.QueryInterface(Components.interfaces.nsIMsgImapMailFolder);
+ if (imapFolder)
+ imapFolder.fillInFolderProps(gFolderPropsSink);
+ }
+
+ var retentionSettings = gMsgFolder.retentionSettings;
+ initCommonRetentionSettings(retentionSettings);
+ document.getElementById("retention.useDefault").checked = retentionSettings.useServerDefaults;
+
+ // set folder sizes
+ let numberOfMsgs = gMsgFolder.getTotalMessages(false);
+ if (numberOfMsgs >= 0)
+ document.getElementById("numberOfMessages").value = numberOfMsgs;
+
+ try {
+ let sizeOnDisk = Components.classes["@mozilla.org/messenger;1"]
+ .createInstance(Components.interfaces.nsIMessenger)
+ .formatFileSize(gMsgFolder.sizeOnDisk, true);
+ document.getElementById("sizeOnDisk").value = sizeOnDisk;
+ } catch (e) { }
+
+ // select the initial tab
+ if (window.arguments[0].tabID) {
+ try {
+ document.getElementById("folderPropTabBox").selectedTab =
+ document.getElementById(window.arguments[0].tabID);
+ }
+ catch (ex) {}
+ }
+ onCheckKeepMsg();
+ onUseDefaultRetentionSettings();
+}
+
+function hideShowControls(serverType)
+{
+ let controls = document.querySelectorAll("[hidefor]");
+ var len = controls.length;
+ for (var i=0; i<len; i++) {
+ var control = controls[i];
+ var hideFor = control.getAttribute("hidefor");
+ if (!hideFor)
+ throw "hidefor empty";
+
+ // hide unsupported server type
+ // adding support for hiding multiple server types using hideFor="server1,server2"
+ var hideForBool = false;
+ var hideForTokens = hideFor.split(",");
+ for (var j = 0; j < hideForTokens.length; j++) {
+ if (hideForTokens[j] == serverType) {
+ hideForBool = true;
+ break;
+ }
+ }
+ control.hidden = hideForBool;
+ }
+
+ // hide the priviliges button if the imap folder doesn't have an admin url
+ // mabye should leave this hidden by default and only show it in this case instead
+ try {
+ var imapFolder = gMsgFolder.QueryInterface(Components.interfaces.nsIMsgImapMailFolder);
+ if (imapFolder)
+ {
+ var privilegesButton = document.getElementById("imap.FolderPrivileges");
+ if (privilegesButton)
+ {
+ if (!imapFolder.hasAdminUrl)
+ privilegesButton.setAttribute("hidden", "true");
+ }
+ }
+ }
+ catch (ex) {}
+
+ if (gMsgFolder)
+ {
+ const nsMsgFolderFlags = Components.interfaces.nsMsgFolderFlags;
+ // Hide "check for new mail" checkbox if this is an Inbox.
+ if (gMsgFolder.flags & nsMsgFolderFlags.Inbox)
+ document.getElementById("folderCheckForNewMessages").hidden = true;
+ // Retention policy doesn't apply to Drafts/Templates/Outbox.
+ if (gMsgFolder.isSpecialFolder(nsMsgFolderFlags.Drafts |
+ nsMsgFolderFlags.Templates |
+ nsMsgFolderFlags.Queue, true))
+ document.getElementById("Retention").hidden = true;
+ }
+}
+
+function onOfflineFolderDownload()
+{
+ // we need to create a progress window and pass that in as the second parameter here.
+ gMsgFolder.downloadAllForOffline(null, window.arguments[0].msgWindow);
+}
+
+function onFolderPrivileges()
+{
+ var imapFolder = gMsgFolder.QueryInterface(Components.interfaces.nsIMsgImapMailFolder);
+ if (imapFolder)
+ imapFolder.folderPrivileges(window.arguments[0].msgWindow);
+ // let's try closing the modal dialog to see if it fixes the various problems running this url
+ window.close();
+}
+
+
+function onUseDefaultRetentionSettings()
+{
+ var useDefault = document.getElementById("retention.useDefault").checked;
+ document.getElementById('retention.keepMsg').disabled = useDefault;
+ document.getElementById('retention.keepNewMsgMinLabel').disabled = useDefault;
+ document.getElementById('retention.keepOldMsgMinLabel').disabled = useDefault;
+
+ var keepMsg = document.getElementById("retention.keepMsg").value;
+ const nsIMsgRetentionSettings = Components.interfaces.nsIMsgRetentionSettings;
+ document.getElementById('retention.keepOldMsgMin').disabled =
+ useDefault || (keepMsg != nsIMsgRetentionSettings.nsMsgRetainByAge);
+ document.getElementById('retention.keepNewMsgMin').disabled =
+ useDefault || (keepMsg != nsIMsgRetentionSettings.nsMsgRetainByNumHeaders);
+}
+
+function RebuildSummaryInformation()
+{
+ window.arguments[0].rebuildSummaryCallback(gMsgFolder);
+}
diff --git a/mailnews/base/content/folderProps.xul b/mailnews/base/content/folderProps.xul
new file mode 100644
index 000000000..23b9c3d9b
--- /dev/null
+++ b/mailnews/base/content/folderProps.xul
@@ -0,0 +1,216 @@
+<?xml version="1.0"?> <!-- -*- Mode: SGML; indent-tabs-mode: nil; -*- -->
+<!--
+
+ 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/. -->
+
+<?xml-stylesheet href="chrome://global/skin/global.css" type="text/css"?>
+<?xml-stylesheet href="chrome://messenger/skin/preferences/preferences.css" type="text/css"?>
+<?xml-stylesheet href="chrome://messenger/content/charsetList.css" type="text/css"?>
+
+<!DOCTYPE dialog SYSTEM "chrome://messenger/locale/folderProps.dtd">
+
+<dialog
+ id="folderPropertiesDialog"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ title="&folderProps.windowtitle.label;"
+ buttons="accept,cancel"
+ onload="folderPropsOnLoad();" style="width: 56ch;"
+ ondialogaccept="return folderPropsOKButton();">
+
+ <stringbundle id="bundle_messenger" src="chrome://messenger/locale/messenger.properties"/>
+ <script type="application/javascript" src="chrome://global/content/globalOverlay.js"/>
+ <script type="application/javascript" src="chrome://messenger/content/retention.js"/>
+ <script type="application/javascript" src="chrome://messenger/content/folderProps.js"/>
+ <script type="application/javascript">
+ <![CDATA[
+ Components.utils.import("resource://gre/modules/Services.jsm");
+ Services.obs.notifyObservers(null, "charsetmenu-selected", "other");
+ ]]>
+ </script>
+
+<tabbox id="folderPropTabBox">
+ <tabs id="folderPropTabs">
+ <tab id="GeneralTab" label="&generalInfo.label;"/>
+ <tab id="Retention" label="&retention.label;"/>
+ <tab id="SynchronizationTab" hidefor="movemail,pop3,rss,none" label="&folderSynchronizationTab.label;"/>
+ <tab id="SharingTab" hidefor="movemail,pop3,rss,none,nntp" label="&folderSharingTab.label;"/>
+ <tab id="QuotaTab" hidefor="movemail,pop3,rss,none,nntp" label="&folderQuotaTab.label;"/>
+ </tabs>
+ <tabpanels id="folderPropTabPanels">
+
+ <vbox id="GeneralPanel">
+ <hbox id="nameBox" align="center">
+ <label value="&folderProps.name.label;" control="name"
+ accesskey="&folderProps.name.accesskey;"/>
+ <textbox id="name" readonly="true" oninput="doEnabling();" flex="1"/>
+ </hbox>
+ <hbox align="center">
+ <label value="&folderProps.location.label;" control="location"
+ accesskey="&folderProps.location.accesskey;"/>
+ <textbox id="location" readonly="true" flex="1" class="uri-element"/>
+ </hbox>
+ <vbox>
+ <spacer height="2"/>
+ <hbox align="center">
+ <label value="&numberOfMessages.label;"/>
+ <label id="numberOfMessages" value="&numberUnknown.label;"/>
+ <spacer flex="1"/>
+ <label value="&sizeOnDisk.label;"/>
+ <label id="sizeOnDisk" value="&sizeUnknown.label;"/>
+ </hbox>
+ <spacer height="2"/>
+ </vbox>
+ <checkbox id="folderIncludeInGlobalSearch" hidefor="nntp"
+ label="&folderIncludeInGlobalSearch.label;"
+ accesskey="&folderIncludeInGlobalSearch.accesskey;"/>
+ <checkbox hidefor="movemail,pop3,none,nntp"
+ id="folderCheckForNewMessages"
+ label="&folderCheckForNewMessages2.label;"
+ accesskey="&folderCheckForNewMessages2.accesskey;"/>
+ <vbox>
+ <hbox align="center" valign="middle">
+ <label value="&folderCharsetFallback2.label;"
+ accesskey="&folderCharsetFallback2.accesskey;"
+ control="folderCharsetList"/>
+ <menulist id="folderCharsetList" type="charset"/>
+ </hbox>
+ <checkbox class="indent" id="folderCharsetOverride"
+ label="&folderCharsetEnforce2.label;"
+ accesskey="&folderCharsetEnforce2.accesskey;"/>
+ </vbox>
+ <groupbox id="folderRebuildSummaryGroupBox" align="baseline">
+ <grid>
+ <columns>
+ <column flex="1"/>
+ <column/>
+ </columns>
+ <rows>
+ <row>
+ <description id="folderRebuildSummaryExplanation">&folderRebuildSummaryFile.explanation;</description>
+ <vbox>
+ <button id="folderRebuildSummaryButton"
+ label="&folderRebuildSummaryFile2.label;"
+ oncommand="RebuildSummaryInformation();"
+ accesskey="&folderRebuildSummaryFile2.accesskey;"
+ tooltiptext="&folderRebuildSummaryFileTip2.label;"
+ align="center"/>
+ </vbox>
+ </row>
+ </rows>
+ </grid>
+ </groupbox>
+ </vbox>
+
+ <vbox id="RetentionPanel" align="start">
+ <description hidefor="imap,pop3" class="desc">&retentionCleanup.label;</description>
+ <description hidefor="movemail,pop3,rss,none,nntp" class="desc">&retentionCleanupImap.label;</description>
+ <description hidefor="movemail,imap,rss,none,nntp" class="desc">&retentionCleanupPop.label;</description>
+
+ <hbox align="center" class="indent">
+ <checkbox wsm_persist="true" id="retention.useDefault" accesskey="&retentionUseAccount.accesskey;"
+ label="&retentionUseAccount.label;" checked="true" oncommand="onUseDefaultRetentionSettings()"/>
+ </hbox>
+ <vbox class="indent">
+ <hbox class="indent">
+ <radiogroup wsm_persist="true" id="retention.keepMsg" aria-labelledby="retention.useDefault">
+ <radio wsm_persist="true" value="1" accesskey="&retentionKeepAll.accesskey;"
+ label="&retentionKeepAll.label;" oncommand="onCheckKeepMsg();"/>
+ <hbox flex="1" align="center">
+ <radio wsm_persist="true" id="keepNewMsg" accesskey="&retentionKeepRecent.accesskey;"
+ value="3" label="&retentionKeepRecent.label;" oncommand="onCheckKeepMsg();"/>
+ <textbox wsm_persist="true" id="retention.keepNewMsgMin"
+ type="number" min="1" increment="10" size="4" value="2000"
+ aria-labelledby="keepNewMsg retention.keepNewMsgMin retention.keepNewMsgMinLabel"/>
+ <label value="&message.label;" control="retention.keepNewMsgMin" id="retention.keepNewMsgMinLabel"/>
+ </hbox>
+ <hbox flex="1" align="center">
+ <radio wsm_persist="true" id="keepMsg" accesskey="&retentionDeleteMsg.accesskey;"
+ value="2" label="&retentionDeleteMsg.label;" oncommand="onCheckKeepMsg();"/>
+ <textbox wsm_persist="true" id="retention.keepOldMsgMin"
+ type="number" min="1" size="2" value="30"
+ aria-labelledby="keepMsg retention.keepOldMsgMin retention.keepOldMsgMinLabel"/>
+ <label value="&daysOld.label;" control="retention.keepOldMsgMin" id="retention.keepOldMsgMinLabel"/>
+ </hbox>
+ </radiogroup>
+ </hbox>
+ <hbox class="indent">
+ <checkbox id="retention.applyToFlagged" wsm_persist="true"
+ label="&retentionApplyToFlagged.label;"
+ accesskey="&retentionApplyToFlagged.accesskey;"
+ observes="retention.keepMsg" checked="true"/>
+ </hbox>
+ </vbox>
+ </vbox>
+
+ <vbox id="SyncPanel" valign="top" align="start">
+ <vbox>
+ <checkbox hidefor="nntp"
+ wsm_persist="true" id="offline.selectForOfflineFolder"
+ label="&offlineFolder.check.label;"
+ accesskey="&offlineFolder.check.accesskey;"/>
+ <checkbox hidefor="imap"
+ wsm_persist="true" id="offline.selectForOfflineNewsgroup"
+ label="&selectofflineNewsgroup.check.label;"
+ accesskey="&selectofflineNewsgroup.check.accesskey;"/>
+ </vbox>
+ <button hidefor="nntp" label="&offlineFolder.button.label;"
+ oncommand="onOfflineFolderDownload();" accesskey="&offlineFolder.button.accesskey;"
+ id="offline.offlineFolderDownloadButton" orient="right"/>
+ <button hidefor="imap" label="&offlineNewsgroup.button.label;"
+ oncommand="onOfflineFolderDownload();" accesskey="&offlineNewsgroup.button.accesskey;"
+ id="offline.offlineNewsgroupDownloadButton" orient="right"/>
+ </vbox>
+
+ <vbox id="SharingPanel" valign="top">
+ <hbox align="start">
+ <label value="&folderType.label;" id="folderTypeLabel"/>
+ <label value="" id="folderType.text"/>
+ </hbox>
+ <vbox align="start">
+ <label value="" id="folderDescription.text"/>
+ <label value=" "/>
+ <label value="&permissionsDesc.label;" id="permissionsDescLabel"/>
+
+ <description id="folderPermissions.text"></description>
+ </vbox>
+ <spacer flex="100%"/>
+ <vbox align="start">
+ <button hidefor="movemail,pop3,none,rss,nntp" label="&privileges.button.label;"
+ oncommand="onFolderPrivileges();" accesskey="&privileges.button.accesskey;"
+ id="imap.FolderPrivileges" orient="right"/>
+ </vbox>
+ </vbox>
+
+ <vbox id="QuotaPanel" valign="top">
+ <label id="folderQuotaStatus" flex="1"/>
+
+ <grid id="folderQuotaData" hidden="true" flex="1">
+ <columns>
+ <column/>
+ <column flex="1"/>
+ </columns>
+ <rows>
+ <row align="center">
+ <label value="&folderQuotaRoot.label;" control="quotaRoot"/>
+ <textbox id="quotaRoot" readonly="true"/>
+ </row>
+ <row>
+ <label value="&folderQuotaUsage.label;"/>
+ <description id="quotaUsedFree"/>
+ </row>
+ <row align="center">
+ <label value="&folderQuotaStatus.label;"/>
+ <hbox align="center">
+ <progressmeter id="quotaPercentageBar" mode="determined" value="0%"/>
+ <label id="quotaPercentUsed"/>
+ </hbox>
+ </row>
+ </rows>
+ </grid>
+ </vbox>
+ </tabpanels>
+</tabbox>
+
+</dialog>
diff --git a/mailnews/base/content/folderWidgets.xml b/mailnews/base/content/folderWidgets.xml
new file mode 100644
index 000000000..605f4230c
--- /dev/null
+++ b/mailnews/base/content/folderWidgets.xml
@@ -0,0 +1,829 @@
+<?xml version="1.0"?>
+<!-- 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/.
+-->
+
+<bindings id="mailFolderBindings"
+ xmlns="http://www.mozilla.org/xbl"
+ xmlns:xbl="http://www.mozilla.org/xbl"
+ xmlns:html="http://www.w3.org/1999/xhtml"
+ xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+ <binding id="folder-menupopup"
+ extends="chrome://global/content/bindings/popup.xml#popup">
+ <implementation>
+ <constructor><![CDATA[
+ Components.utils.import("resource:///modules/FeedUtils.jsm", this);
+ Components.utils.import("resource:///modules/folderUtils.jsm", this);
+ Components.utils.import("resource:///modules/iteratorUtils.jsm", this);
+ Components.utils.import("resource:///modules/mailServices.js", this);
+ Components.utils.import("resource:///modules/MailUtils.js", this);
+ Components.utils.import("resource:///modules/StringBundle.js", this);
+ this._stringBundle = new this
+ .StringBundle("chrome://messenger/locale/folderWidgets.properties");
+
+ // Get the displayformat if set.
+ if (this.parentNode && this.parentNode.localName == "menulist")
+ this._displayformat = this.parentNode.getAttribute("displayformat");
+
+ // Find out if we are in a wrapper (customize toolbars mode is active).
+ let inWrapper = false;
+ let node = this;
+ while (node instanceof XULElement) {
+ if (node.id.startsWith("wrapper-")) {
+ inWrapper = true;
+ break;
+ }
+ node = node.parentNode;
+ }
+
+ if (!inWrapper) {
+ if (this.hasAttribute("original-width")) {
+ // If we were in a wrapper before and have a width stored, restore it now.
+ if (this.getAttribute("original-width") == "none")
+ this.removeAttribute("width");
+ else
+ this.setAttribute("width", this.getAttribute("original-width"));
+
+ this.removeAttribute("original-width");
+ }
+
+ // If we are a child of a menulist, and we aren't in a wrapper, we
+ // need to build our content right away, otherwise the menulist
+ // won't have proper sizing.
+ if (this.parentNode && this.parentNode.localName == "menulist")
+ this._ensureInitialized();
+ } else {
+ // But if we're in a wrapper, remove our children, because we're
+ // getting re-created when the toolbar customization closes.
+ this._teardown();
+
+ // Store our current width and set a safe small width when we show
+ // in a wrapper.
+ if (!this.hasAttribute("original-width")) {
+ this.setAttribute("original-width", this.hasAttribute("width") ?
+ this.getAttribute("width") : "none");
+ this.setAttribute("width", "100");
+ }
+ }
+ ]]></constructor>
+ <destructor><![CDATA[
+ // Clean up when being destroyed.
+ this._removeListener();
+ ]]></destructor>
+ <!--
+ - Make sure we remove our listener when the window is being destroyed
+ - or the widget teared down.
+ -->
+ <method name="_removeListener">
+ <body><![CDATA[
+ if (!this._initialized)
+ return;
+
+ this.MailServices.mailSession.RemoveFolderListener(this._listener);
+ ]]></body>
+ </method>
+
+ <field name="_stringBundle">null</field>
+
+ <!--
+ - If non-null, the subFolders of this nsIMsgFolder will be used to
+ - populate this menu. If this is null, the menu will be populated
+ - using the root-folders for all accounts
+ -->
+ <field name="_parentFolder">null</field>
+ <property name="parentFolder"
+ onget="return this._parentFolder;"
+ onset="return this._parentFolder = val;"/>
+
+ <!--
+ - Various filtering modes can be used with this menu-binding. To use
+ - one of them, append the mode="foo" attribute to the element. When
+ - building the menu, we will then use this._filters[mode] as a filter
+ - function to eliminate folders that should not be shown.
+ -
+ - Note that extensions should feel free to plug in here!
+ -->
+ <field name="_filters"><![CDATA[({
+ // Returns true if messages can be filed in the folder
+ filing: function filter_filing(aFolder) {
+ if (!aFolder.server.canFileMessagesOnServer)
+ return false;
+
+ return (aFolder.canFileMessages || aFolder.hasSubFolders);
+ },
+
+ // Returns true if we can get mail for this folder. (usually this just
+ // means the "root" fake folder)
+ getMail: function filter_getMail(aFolder) {
+ if (aFolder.isServer && aFolder.server.type != "none")
+ return true;
+ if (aFolder.server.type == "nntp" || aFolder.server.type == "rss")
+ return true;
+ return false;
+ },
+
+ // Returns true if we can add filters to this folder/account
+ filters: function filter_filter(aFolder) {
+ // We can always filter news
+ if (aFolder.server.type == "nntp")
+ return true;
+
+ return aFolder.server.canHaveFilters;
+ },
+
+ subscribe: function filter_subscribe(aFolder) {
+ return aFolder.canSubscribe;
+ },
+
+ newFolder: function filter_newFolder(aFolder) {
+ return aFolder.canCreateSubfolders &&
+ aFolder.server.canCreateFoldersOnServer;
+ },
+
+ deferred: function filter_defered(aFolder) {
+ return aFolder.server.canCreateFoldersOnServer &&
+ !aFolder.supportsOffline;
+ },
+
+ // Folders that are not in a deferred account
+ notDeferred: function(aFolder) {
+ let server = aFolder.server;
+ return !(server instanceof Components.interfaces.nsIPop3IncomingServer &&
+ server.deferredToAccount);
+ },
+
+ // Folders that can be searched.
+ search: function filter_search(aFolder) {
+ if (!aFolder.server.canSearchMessages ||
+ aFolder.getFlag(Components.interfaces.nsMsgFolderFlags.Virtual))
+ return false;
+ return true;
+ },
+
+ // Folders that can subscribe feeds.
+ feeds: function filter_feeds(aFolder) {
+ if (aFolder.server.type != "rss" ||
+ aFolder.getFlag(Components.interfaces.nsMsgFolderFlags.Trash) ||
+ aFolder.getFlag(Components.interfaces.nsMsgFolderFlags.Virtual))
+ return false;
+ return true;
+ },
+
+ junk: function filter_junk(aFolder) {
+ // Don't show servers (nntp & any others) which do not allow search or filing
+ // I don't really understand why canSearchMessages is needed, but it was included in
+ // earlier code, so I include it as well.
+ if (!aFolder.server.canFileMessagesOnServer || !aFolder.server.canSearchMessages)
+ return false;
+ // show parents that might have usable subfolders, or usable folders
+ return aFolder.hasSubFolders || aFolder.canFileMessages;
+ }
+ })]]></field>
+
+ <!--
+ - The maximum number of entries in the "Recent" menu
+ -->
+ <field name="_MAXRECENT">15</field>
+
+ <!--
+ - Is this list containing only servers (accounts) and no real folders?
+ -->
+ <field name="_serversOnly">true</field>
+
+ <!--
+ - Our listener to let us know when folders change/appear/disappear so
+ - we can know to rebuild ourselves.
+ -->
+ <field name="_listener">
+ <![CDATA[({
+ _menu: this,
+ _clearMenu: function(aMenu) {
+ if (aMenu._teardown)
+ aMenu._teardown();
+ },
+ OnItemAdded: function act_add(aRDFParentItem, aItem) {
+ if (!(aItem instanceof Components.interfaces.nsIMsgFolder))
+ return;
+ if (this._filterFunction && !this._filterFunction(aItem)) {
+ return;
+ }
+ //xxx we can optimize this later
+ //xxx I'm not quite sure why this isn't always a function
+ if (this._menu._teardown)
+ this._menu._teardown();
+ },
+
+ OnItemRemoved: function act_remove(aRDFParentItem, aItem) {
+ if (!(aItem instanceof Components.interfaces.nsIMsgFolder))
+ return;
+ if (this._filterFunction && !this._filterFunction(aItem)) {
+ return;
+ }
+ //xxx we can optimize this later
+ if (this._menu._teardown)
+ this._menu._teardown();
+ },
+
+ //xxx I stole this listener list from nsMsgFolderDatasource.cpp, but
+ // someone should really document what events are fired when, so that
+ // we make sure we're updating at the right times.
+ OnItemPropertyChanged: function(aItem, aProperty, aOld, aNew) {},
+ OnItemIntPropertyChanged: function(aItem, aProperty, aOld, aNew) {
+ var child = this._getChildForItem(aItem);
+ if (child)
+ this._menu._setCssSelectors(child._folder, child);
+ },
+ OnItemBoolPropertyChanged: function(aItem, aProperty, aOld, aNew) {
+ var child = this._getChildForItem(aItem);
+ if (child)
+ this._menu._setCssSelectors(child._folder, child);
+ },
+ OnItemUnicharPropertyChanged: function(aItem, aProperty, aOld, aNew) {
+ var child = this._getChildForItem(aItem);
+ if (child)
+ this._menu._setCssSelectors(child._folder, child);
+ },
+ OnItemPropertyFlagChanged: function(aItem, aProperty, aOld, aNew) {},
+ OnItemEvent: function(aFolder, aEvent) {
+ if (aEvent.toString() == "MRMTimeChanged") {
+ if (this._menu.getAttribute("showRecent") != "true" ||
+ !this._menu.firstChild || !this._menu.firstChild.firstChild)
+ return;
+ // if this folder is already in the recent menu, return.
+ if (this._getChildForItem(aFolder,
+ this._menu.firstChild.firstChild))
+ return;
+ }
+ // Special casing folder renames here, since they require more work
+ // since sort-order may have changed.
+ else if (aEvent.toString() == "RenameCompleted") {
+ if (!this._getChildForItem(aFolder))
+ return;
+ }
+ else
+ return;
+ // folder renamed, or new recent folder, so rebuild.
+ setTimeout(this._clearMenu, 0, this._menu);
+ },
+
+ /**
+ * Helper function to check and see whether we have a menuitem for this
+ * particular nsIMsgFolder
+ *
+ * @param aItem the nsIMsgFolder to check
+ * @param aMenu (optional) menu to look in, defaults to this._menu.
+ * @returns null if no child for that folder exists, otherwise the
+ * menuitem for that child
+ */
+ _getChildForItem: function act__itemIsChild(aItem, aMenu) {
+ aMenu = aMenu || this._menu;
+ if (!aMenu || !aMenu.childNodes)
+ return null;
+
+ if (!(aItem instanceof Components.interfaces.nsIMsgFolder))
+ return null;
+ for (let i = 0; i < aMenu.childNodes.length; i++) {
+ let folder = aMenu.childNodes[i]._folder;
+ if (folder && folder.URI == aItem.URI)
+ return aMenu.childNodes[i];
+ }
+ return null;
+ }
+ })]]></field>
+
+ <!--
+ - True if we have already built our menu-items and are now just
+ - listening for changes
+ -->
+ <field name="_initialized">false</field>
+
+ <!--
+ - Call this if you are unsure whether the menu-items have been built,
+ - but know that they need to be built now if they haven't.
+ -->
+ <method name="_ensureInitialized">
+ <body><![CDATA[
+ if (this._initialized)
+ return;
+
+ // I really wish they'd just make this global...
+ const Ci = Components.interfaces;
+
+ let folders;
+
+ // Figure out which folders to build. If we don't have a parent, then
+ // we assume we should build the top-level accounts. (Actually we
+ // build the fake root folders for those accounts.)
+ if (!this._parentFolder) {
+ let accounts = this.allAccountsSorted(true);
+
+ // Now generate our folder-list. Note that we'll special case this
+ // situation below, to avoid destroying the sort order we just made
+ folders = accounts.map(acct => acct.incomingServer.rootFolder);
+ } else {
+ // If we do have a parent folder, then we just build based on those
+ // subFolders for that parent.
+ folders = this.toArray(this.fixIterator(this._parentFolder.subFolders,
+ Ci.nsIMsgFolder));
+ }
+
+ this._build(folders);
+
+ // Lastly, we add a listener to get notified of changes in the folder
+ // structure.
+ this.MailServices.mailSession.AddFolderListener(this._listener,
+ Ci.nsIFolderListener.all);
+
+ this._initialized = true;
+ ]]></body>
+ </method>
+
+ <!--
+ - Actually constructs the menu-items based on the folders given.
+ -
+ - @param aFolders An array of nsIMsgFolders to use for building.
+ -->
+ <method name="_build">
+ <parameter name="aFolders"/>
+ <body><![CDATA[
+ let folders;
+ let excludeServers = [];
+ let disableServers = [];
+
+ // excludeServers attribute is a comma separated list of server keys.
+ if (this.hasAttribute("excludeServers"))
+ excludeServers = this.getAttribute("excludeServers").split(",");
+
+ // disableServers attribute is a comma separated list of server keys.
+ if (this.hasAttribute("disableServers"))
+ disableServers = this.getAttribute("disableServers").split(",");
+
+ // Extensions and other consumers can add to these modes too, see the
+ // above note on the _filters field.
+ var mode = this.getAttribute("mode");
+ if (mode && mode != "") {
+ var filterFunction = this._filters[mode];
+ folders = aFolders.filter(filterFunction);
+ this._listener._filterFunction = filterFunction;
+ } else {
+ folders = aFolders;
+ }
+
+ if (excludeServers.length > 0) {
+ folders = folders.filter(function(aFolder) {
+ return !(excludeServers.indexOf(aFolder.server.key) != -1); });
+ }
+
+ /* This code block will do the following: Add a menu item that refers
+ back to the parent folder when there is a showFileHereLabel
+ attribute or no mode attribute. However the code won't add such a
+ menu item if one of the following conditions is met:
+ (*) There is no parent folder
+ (*) Folder is server and showAccountsFileHere is explicitly false
+ (*) Current folder has a mode, the parent folder can be selected,
+ no messages can be filed into the parent folder (e.g. when the
+ parent folder is a news group or news server) and the folder
+ mode is not equal to newFolder
+
+ The menu item will have the value of the fileHereLabel attribute as
+ label or if the attribute does not exist the name of the parent
+ folder instead.
+ */
+ let parent = this._parentFolder;
+ if (parent && (this.getAttribute("showFileHereLabel") == "true" || !mode)) {
+ let showAccountsFileHere = this.getAttribute("showAccountsFileHere");
+ if ((!parent.isServer || showAccountsFileHere != "false") &&
+ (!mode || mode == "newFolder" || parent.noSelect ||
+ parent.canFileMessages || showAccountsFileHere == "true")) {
+ var menuitem = document.createElement("menuitem");
+ menuitem._folder = this._parentFolder;
+ menuitem.setAttribute("generated", "true");
+ if (this.hasAttribute("fileHereLabel")) {
+ menuitem.setAttribute("label", this.getAttribute("fileHereLabel"));
+ menuitem.setAttribute("accesskey", this.getAttribute("fileHereAccessKey"));
+ } else {
+ menuitem.setAttribute("label", this._parentFolder.prettyName);
+ menuitem.setAttribute("class", "folderMenuItem menuitem-iconic");
+ this._setCssSelectors(this._parentFolder, menuitem);
+ }
+ // Eww. have to support some legacy code here...
+ menuitem.setAttribute("id", this._parentFolder.URI);
+ this.appendChild(menuitem);
+
+ if (this._parentFolder.noSelect)
+ menuitem.setAttribute("disabled", "true");
+
+ var sep= document.createElement("menuseparator");
+ sep.setAttribute("generated", "true");
+ this.appendChild(sep);
+ }
+ }
+
+ let globalInboxFolder = null;
+ // See if this is the toplevel menu (usually with accounts).
+ if (!this._parentFolder) {
+ // Some menus want a "Recent" option, but that should only be on our
+ // top-level menu
+ if (this.getAttribute("showRecent") == "true")
+ this._buildRecentMenu();
+ // If we are showing the accounts for deferring, move Local Folders to the top.
+ if (mode == "deferred") {
+ globalInboxFolder = this.MailServices.accounts.localFoldersServer
+ .rootFolder;
+ let localFoldersIndex = folders.indexOf(globalInboxFolder);
+ if (localFoldersIndex != -1) {
+ folders.splice(localFoldersIndex, 1);
+ folders.unshift(globalInboxFolder);
+ }
+ }
+ // If we're the root of the folder hierarchy, then we actually don't
+ // want to sort the folders, but rather the accounts to which the
+ // folders belong. Since that sorting was already done, we don't need
+ // to do anything for that case here.
+ } else {
+ // Sorts the list of folders. We give first priority to the sortKey
+ // property if it is available, otherwise a case-insensitive
+ // comparison of names.
+ folders = folders.sort(function nameCompare(a, b) {
+ return a.compareSortKeys(b);
+ });
+ }
+
+ /* In some cases, the user wants to have a list of subfolders for only
+ * some account types (or maybe all of them). So we use this to
+ * determine what the user wanted.
+ */
+ var shouldExpand;
+ var labels = null;
+ if (this.getAttribute("expandFolders") == "true" ||
+ !this.hasAttribute("expandFolders")) {
+ shouldExpand = function (e) { return true; };
+ } else if (this.getAttribute("expandFolders") == "false") {
+ shouldExpand = function (e) { return false; };
+ } else {
+ /* We want a subfolder list for only some servers. We also may need
+ * to create headers to select the servers. If so, then headlabels
+ * is a comma-delimited list of labels corresponding to the server
+ * types specified in expandFolders.
+ */
+ var types = this.getAttribute("expandFolders").split(/ *, */);
+ // Set the labels. labels[type] = label
+ if (this.hasAttribute("headlabels")) {
+ var labelNames = this.getAttribute("headlabels").split(/ *, */);
+ labels = {};
+ // If the length isn't equal, don't give them any of the labels,
+ // since any combination will probably be wrong.
+ if (labelNames.length == types.length) {
+ for (var index in types)
+ labels[types[index]] = labelNames[index];
+ }
+ }
+ shouldExpand = function (e) { return types.indexOf(e) != -1; };
+ }
+
+ // We need to call this, or hasSubFolders will always return false.
+ // Remove this workaround when Bug 502900 is fixed.
+ this.MailUtils.discoverFolders();
+ this._serversOnly = true;
+
+ for (let folder of folders) {
+ let node;
+ if (!folder.isServer)
+ this._serversOnly = false;
+
+ // If we're going to add subFolders, we need to make menus, not
+ // menuitems.
+ if (!folder.hasSubFolders || !shouldExpand(folder.server.type)) {
+ node = document.createElement("menuitem");
+ // Grumble, grumble, legacy code support
+ node.setAttribute("id", folder.URI);
+ node.setAttribute("class", "folderMenuItem menuitem-iconic");
+ node.setAttribute("generated", "true");
+ this.appendChild(node);
+ } else {
+ this._serversOnly = false;
+ //xxx this is slightly problematic in that we haven't confirmed
+ // whether any of the subfolders will pass the filter
+ node = document.createElement("menu");
+ node.setAttribute("class", "folderMenuItem menu-iconic");
+ node.setAttribute("generated", "true");
+ this.appendChild(node);
+
+ // Create the submenu
+ // (We must use cloneNode here because on OS X the native menu
+ // functionality and very sad limitations of XBL1 cause the bindings
+ // to never get created for popup if we create a new element. We
+ // perform a shallow clone to avoid picking up any of our children.)
+ var popup = this.cloneNode(false);
+ popup._parentFolder = folder;
+ popup.setAttribute("class", this.getAttribute("class"));
+ popup.setAttribute("type", this.getAttribute("type"));
+ if (this.hasAttribute("fileHereLabel"))
+ popup.setAttribute("fileHereLabel",
+ this.getAttribute("fileHereLabel"));
+ popup.setAttribute("showFileHereLabel",
+ this.getAttribute("showFileHereLabel"));
+ popup.setAttribute("oncommand",
+ this.getAttribute("oncommand"));
+ popup.setAttribute("mode",
+ this.getAttribute("mode"));
+ if (this.hasAttribute("disableServers"))
+ popup.setAttribute("disableServers",
+ this.getAttribute("disableServers"));
+ if (this.hasAttribute("position"))
+ popup.setAttribute("position",
+ this.getAttribute("position"));
+
+ // If there are labels, add the labels now
+ if (labels) {
+ var serverNode = document.createElement("menuitem");
+ serverNode.setAttribute("label", labels[folder.server.type]);
+ serverNode._folder = folder;
+ serverNode.setAttribute("generated", "true");
+ popup.appendChild(serverNode);
+ var sep = document.createElement("menuseparator");
+ sep.setAttribute("generated", "true");
+ popup.appendChild(sep);
+ }
+
+ popup.setAttribute("generated", "true");
+ node.appendChild(popup);
+ }
+
+ if (disableServers.indexOf(folder.server.key) != -1)
+ node.setAttribute("disabled", "true");
+
+ node._folder = folder;
+ let label = "";
+ if (mode == "deferred" && folder.isServer &&
+ folder.server.rootFolder == globalInboxFolder) {
+ label = this._stringBundle.get("globalInbox", [folder.prettyName]);
+ } else {
+ label = folder.prettyName;
+ }
+ node.setAttribute("label", label);
+ this._setCssSelectors(folder, node);
+ }
+ ]]></body>
+ </method>
+
+ <!--
+ - Builds a submenu with all of the recently used folders in it, to
+ - allow for easy access.
+ -->
+ <method name="_buildRecentMenu">
+ <body><![CDATA[
+ const Ci = Components.interfaces;
+
+ // Iterate through all folders in all accounts, and find 15 (_MAXRECENT)
+ // of most recently modified ones.
+ let allFolders = this.toArray(
+ this.fixIterator(this.MailServices.accounts.allFolders, Ci.nsIMsgFolder));
+
+ allFolders = allFolders.filter(f => f.canFileMessages);
+
+ let recentFolders = this.getMostRecentFolders(allFolders,
+ this._MAXRECENT,
+ "MRMTime");
+
+ // Cache the pretty names so that they do not need to be fetched
+ // _MAXRECENT^2 times later.
+ recentFolders = recentFolders.map(
+ function (f) { return { folder: f, name: f.prettyName } });
+
+ // Because we're scanning across multiple accounts, we can end up with
+ // several folders with the same name. Find those dupes.
+ let dupeNames = new Set();
+ for (let i = 0; i < recentFolders.length; i++) {
+ for (let j = i + 1; j < recentFolders.length; j++) {
+ if (recentFolders[i].name == recentFolders[j].name)
+ dupeNames.add(recentFolders[i].name);
+ }
+ }
+
+ for (let folderItem of recentFolders) {
+ // If this folder name appears multiple times in the recent list,
+ // append the server name to disambiguate.
+ // TODO:
+ // - maybe this could use verboseFolderFormat from messenger.properties
+ // instead of hardcoded " - ".
+ // - disambiguate folders with same name in same account
+ // (in different subtrees).
+ let label = folderItem.name;
+ if (dupeNames.has(label))
+ label += " - " + folderItem.folder.server.prettyName;
+
+ folderItem.label = label;
+ }
+
+ // Make sure the entries are sorted alphabetically.
+ recentFolders.sort((a, b) => this.folderNameCompare(a.label, b.label));
+
+ // Now create the Recent folder and its children
+ var menu = document.createElement("menu");
+ menu.setAttribute("label", this.getAttribute("recentLabel"));
+ menu.setAttribute("accesskey", this.getAttribute("recentAccessKey"));
+ var popup = document.createElement("menupopup");
+ popup.setAttribute("class", this.getAttribute("class"));
+ popup.setAttribute("generated", "true");
+ menu.appendChild(popup);
+
+ // Create entries for each of the recent folders.
+ for (let folderItem of recentFolders) {
+ let node = document.createElement("menuitem");
+
+ node.setAttribute("label", folderItem.label);
+ node._folder = folderItem.folder;
+
+ node.setAttribute("class", "folderMenuItem menuitem-iconic");
+ this._setCssSelectors(folderItem.folder, node);
+ node.setAttribute("generated", "true");
+ popup.appendChild(node);
+ }
+ menu.setAttribute("generated", "true");
+ this.appendChild(menu);
+ if (!recentFolders.length)
+ menu.setAttribute("disabled", "true");
+
+ var sep = document.createElement("menuseparator");
+ sep.setAttribute("generated", "true");
+ this.appendChild(sep);
+ ]]></body>
+ </method>
+
+ <!--
+ - This function adds attributes on menu/menuitems to make it easier for
+ - css to style them.
+ -
+ - @param aFolder the folder that corresponds to the menu/menuitem
+ - @param aMenuNode the actual DOM node to set attributes on
+ -->
+ <method name="_setCssSelectors">
+ <parameter name="aFolder"/>
+ <parameter name="aMenuNode"/>
+ <body><![CDATA[
+
+ // First set the SpecialFolder attribute
+ aMenuNode.setAttribute("SpecialFolder", this.getSpecialFolderString(aFolder));
+
+ // Now set the biffState
+ let biffStates = ["NewMail", "NoMail", "UnknownMail"];
+ for (let state of biffStates) {
+ if (aFolder.biffState ==
+ Components.interfaces.nsIMsgFolder["nsMsgBiffState_" + state]) {
+ aMenuNode.setAttribute("BiffState", state);
+ break;
+ }
+ }
+
+ aMenuNode.setAttribute("IsServer", aFolder.isServer);
+ aMenuNode.setAttribute("IsSecure", aFolder.server.isSecure);
+ aMenuNode.setAttribute("ServerType", aFolder.server.type);
+ aMenuNode.setAttribute("IsFeedFolder",
+ (this.FeedUtils.getFeedUrlsInFolder(aFolder) ? true : false));
+ ]]></body>
+ </method>
+
+ <!--
+ - This function returns a formatted display name for a menulist
+ - selected folder. The desired format is set as the 'displayformat'
+ - attribute of the folderpicker's <menulist>, one of:
+ - 'name' (default) - Folder
+ - 'verbose' - Folder on Account
+ - 'path' - Account/Folder/Subfolder
+ -
+ - @param aFolder the folder that corresponds to the menu/menuitem
+ - @return string display name
+ -->
+ <field name="_displayformat">null</field>
+ <method name="getDisplayName">
+ <parameter name="aFolder"/>
+ <body><![CDATA[
+ if (aFolder.isServer)
+ return aFolder.prettyName;
+
+ if (this._displayformat == "verbose")
+ return this._stringBundle.getFormattedString("verboseFolderFormat",
+ [aFolder.prettyName, aFolder.server.prettyName]);
+
+ if (this._displayformat == "path")
+ return this.FeedUtils.getFolderPrettyPath(aFolder) || aFolder.name;
+
+ return aFolder.name;
+ ]]></body>
+ </method>
+
+ <!--
+ - Makes a given folder selected.
+ -
+ - @param aFolder the folder to select (if none, then Choose Folder)
+ - @note If aFolder is not in this popup, but is instead a descendant of
+ - a member of the popup, that ancestor will be selected.
+ - @return true if any usable folder was found, otherwise false.
+ -->
+ <method name="selectFolder">
+ <parameter name="aFolder"/>
+ <body><![CDATA[
+ // Set the label of the menulist element as if aFolder had been selected.
+ function setupParent(aFolder, aMenulist, aNoFolders) {
+ let menupopup = aMenulist.menupopup;
+ if (aFolder) {
+ aMenulist.setAttribute("label", menupopup.getDisplayName(aFolder));
+ } else {
+ aMenulist.setAttribute("label", menupopup._stringBundle.getString(
+ aNoFolders ? "noFolders" :
+ (menupopup._serversOnly ? "chooseAccount" : "chooseFolder")));
+ }
+ aMenulist.setAttribute("value",
+ aFolder ? aFolder.URI : "");
+ aMenulist.setAttribute("IsServer",
+ aFolder ? aFolder.isServer : false);
+ aMenulist.setAttribute("IsSecure",
+ aFolder ? aFolder.server.isSecure : false);
+ aMenulist.setAttribute("ServerType",
+ aFolder ? aFolder.server.type : "none");
+ aMenulist.setAttribute("SpecialFolder",
+ aFolder ? menupopup.getSpecialFolderString(aFolder) : "none");
+ aMenulist.setAttribute("IsFeedFolder", Boolean(
+ aFolder && menupopup.FeedUtils.getFeedUrlsInFolder(aFolder)));
+ }
+
+ let folder;
+ if (aFolder) {
+ for (let child of this.childNodes) {
+ if (child && child._folder && !child.disabled &&
+ (child._folder.URI == aFolder.URI ||
+ (child.tagName == "menu" &&
+ child._folder.isAncestorOf(aFolder)))) {
+ if (child._folder.URI == aFolder.URI)
+ this.parentNode.selectedItem = child;
+ folder = aFolder;
+ break;
+ }
+ }
+ }
+
+ // If the caller specified a folder to select and it was not
+ // found, or if the caller didn't pass a folder (meaning a logical
+ // and valid folder wasn't determined), don't blow up but reset
+ // attributes and set a nice Choose Folder label so the user may
+ // select a valid folder per the filter for this picker. If there are
+ // no children, then no folder passed the filter; disable the menulist
+ // as there's nothing to choose from.
+ let noFolders;
+ if (!this.childElementCount)
+ {
+ this.parentNode.setAttribute("disabled", true);
+ noFolders = true;
+ }
+ else
+ {
+ this.parentNode.removeAttribute("disabled");
+ noFolders = false;
+ }
+
+ setupParent(folder, this.parentNode, noFolders);
+ return folder ? true : false;
+ ]]></body>
+ </method>
+
+ <!--
+ - Removes all menu-items for this popup, resets all fields, and
+ - removes the listener. This function is invoked when a change
+ - that affects this menu is detected by our listener.
+ -->
+ <method name="_teardown">
+ <body><![CDATA[
+ for (let i = this.childNodes.length - 1; i >= 0; i--) {
+ let child = this.childNodes[i];
+ if (child.getAttribute("generated") != "true")
+ continue;
+ if ("_teardown" in child)
+ child._teardown();
+ child.remove();
+ }
+
+ this._removeListener();
+
+ this._initialized = false;
+ ]]></body>
+ </method>
+ </implementation>
+
+ <handlers>
+ <!--
+ - In order to improve performance, we're not going to build any of the
+ - menu until we're shown (unless we're the child of a menulist, see
+ - note in the constructor).
+ -
+ - @note _ensureInitialized can be called repeatedly without issue, so
+ - don't worry about it here.
+ -->
+ <handler event="popupshowing" phase="capturing">
+ this._ensureInitialized();
+ </handler>
+ </handlers>
+ </binding>
+</bindings>
diff --git a/mailnews/base/content/jsTreeView.js b/mailnews/base/content/jsTreeView.js
new file mode 100644
index 000000000..abec7df4a
--- /dev/null
+++ b/mailnews/base/content/jsTreeView.js
@@ -0,0 +1,235 @@
+/* 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/. */
+
+/**
+ * This file contains a prototype object designed to make the implementation of
+ * nsITreeViews in javascript simpler. This object requires that consumers
+ * override the _rebuild function. This function must set the _rowMap object to
+ * an array of objects fitting the following interface:
+ *
+ * readonly attribute string id - a unique identifier for the row/object
+ * readonly attribute integer level - the hierarchy level of the row
+ * attribute boolean open - whether or not this item's children are exposed
+ * string getText(aColName) - return the text to display for this row in the
+ * specified column
+ * string getProperties() - return the css-selectors
+ * attribute array children - return an array of child-objects also meeting this
+ * interface
+ */
+
+function PROTO_TREE_VIEW() {
+ this._tree = null;
+ this._rowMap = [];
+ this._persistOpenMap = [];
+}
+
+PROTO_TREE_VIEW.prototype = {
+ get rowCount() {
+ return this._rowMap.length;
+ },
+
+ /**
+ * CSS files will cue off of these. Note that we reach into the rowMap's
+ * items so that custom data-displays can define their own properties
+ */
+ getCellProperties: function jstv_getCellProperties(aRow, aCol) {
+ return this._rowMap[aRow].getProperties(aCol);
+ },
+
+ /**
+ * The actual text to display in the tree
+ */
+ getCellText: function jstv_getCellText(aRow, aCol) {
+ return this._rowMap[aRow].getText(aCol.id);
+ },
+
+ /**
+ * The jstv items take care of assigning this when building children lists
+ */
+ getLevel: function jstv_getLevel(aIndex) {
+ return this._rowMap[aIndex].level;
+ },
+
+ /**
+ * This is easy since the jstv items assigned the _parent property when making
+ * the child lists
+ */
+ getParentIndex: function jstv_getParentIndex(aIndex) {
+ return this._rowMap.indexOf(this._rowMap[aIndex]._parent);
+ },
+
+ /**
+ * This is duplicative for our normal jstv views, but custom data-displays may
+ * want to do something special here
+ */
+ getRowProperties: function jstv_getRowProperties(aRow) {
+ return this._rowMap[aRow].getProperties();
+ },
+
+ /**
+ * If an item in our list has the same level and parent as us, it's a sibling
+ */
+ hasNextSibling: function jstv_hasNextSibling(aIndex, aNextIndex) {
+ let targetLevel = this._rowMap[aIndex].level;
+ for (let i = aNextIndex + 1; i < this._rowMap.length; i++) {
+ if (this._rowMap[i].level == targetLevel)
+ return true;
+ if (this._rowMap[i].level < targetLevel)
+ return false;
+ }
+ return false;
+ },
+
+ /**
+ * If we have a child-list with at least one element, we are a container.
+ */
+ isContainer: function jstv_isContainer(aIndex) {
+ return this._rowMap[aIndex].children.length > 0;
+ },
+
+ isContainerEmpty: function jstv_isContainerEmpty(aIndex) {
+ // If the container has no children, the container is empty.
+ return !this._rowMap[aIndex].children.length;
+ },
+
+ /**
+ * Just look at the jstv item here
+ */
+ isContainerOpen: function jstv_isContainerOpen(aIndex) {
+ return this._rowMap[aIndex].open;
+ },
+
+ isEditable: function jstv_isEditable(aRow, aCol) {
+ // We don't support editing rows in the tree yet.
+ return false;
+ },
+
+ isSeparator: function jstv_isSeparator(aIndex) {
+ // There are no separators in our trees
+ return false;
+ },
+
+ isSorted: function jstv_isSorted() {
+ // We do our own customized sorting
+ return false;
+ },
+
+ setTree: function jstv_setTree(aTree) {
+ this._tree = aTree;
+ },
+
+ recursivelyAddToMap: function jstv_recursivelyAddToMap(aChild, aNewIndex) {
+ // When we add sub-children, we're going to need to increase our index
+ // for the next add item at our own level.
+ let currentCount = this._rowMap.length;
+ if (aChild.children.length && aChild.open) {
+ for (let [i, child] in Iterator(this._rowMap[aNewIndex].children)) {
+ let index = aNewIndex + i + 1;
+ this._rowMap.splice(index, 0, child);
+ aNewIndex += this.recursivelyAddToMap(child, index);
+ }
+ }
+ return this._rowMap.length - currentCount;
+ },
+
+ /**
+ * Opens or closes a container with children. The logic here is a bit hairy, so
+ * be very careful about changing anything.
+ */
+ toggleOpenState: function jstv_toggleOpenState(aIndex) {
+
+ // Ok, this is a bit tricky.
+ this._rowMap[aIndex]._open = !this._rowMap[aIndex].open;
+
+ if (!this._rowMap[aIndex].open) {
+ // We're closing the current container. Remove the children
+
+ // Note that we can't simply splice out children.length, because some of
+ // them might have children too. Find out how many items we're actually
+ // going to splice
+ let level = this._rowMap[aIndex].level;
+ let row = aIndex + 1;
+ while (row < this._rowMap.length && this._rowMap[row].level > level) {
+ row++;
+ }
+ let count = row - aIndex - 1;
+ this._rowMap.splice(aIndex + 1, count);
+
+ // Remove us from the persist map
+ let index = this._persistOpenMap.indexOf(this._rowMap[aIndex].id);
+ if (index != -1)
+ this._persistOpenMap.splice(index, 1);
+
+ // Notify the tree of changes
+ if (this._tree) {
+ this._tree.rowCountChanged(aIndex + 1, -count);
+ }
+ } else {
+ // We're opening the container. Add the children to our map
+
+ // Note that these children may have been open when we were last closed,
+ // and if they are, we also have to add those grandchildren to the map
+ let oldCount = this._rowMap.length;
+ this.recursivelyAddToMap(this._rowMap[aIndex], aIndex);
+
+ // Add this container to the persist map
+ let id = this._rowMap[aIndex].id;
+ if (this._persistOpenMap.indexOf(id) == -1)
+ this._persistOpenMap.push(id);
+
+ // Notify the tree of changes
+ if (this._tree)
+ this._tree.rowCountChanged(aIndex + 1, this._rowMap.length - oldCount);
+ }
+
+ // Invalidate the toggled row, so that the open/closed marker changes
+ if (this._tree)
+ this._tree.invalidateRow(aIndex);
+ },
+
+ // We don't implement any of these at the moment
+ canDrop: function jstv_canDrop(aIndex, aOrientation) {},
+ drop: function jstv_drop(aRow, aOrientation) {},
+ performAction: function jstv_performAction(aAction) {},
+ performActionOnCell: function jstv_performActionOnCell(aAction, aRow, aCol) {},
+ performActionOnRow: function jstv_performActionOnRow(aAction, aRow) {},
+ selectionChanged: function jstv_selectionChanged() {},
+ setCellText: function jstv_setCellText(aRow, aCol, aValue) {},
+ setCellValue: function jstv_setCellValue(aRow, aCol, aValue) {},
+ getCellValue: function jstv_getCellValue(aRow, aCol) {},
+ getColumnProperties: function jstv_getColumnProperties(aCol) { return ""; },
+ getImageSrc: function jstv_getImageSrc(aRow, aCol) {},
+ getProgressMode: function jstv_getProgressMode(aRow, aCol) {},
+ cycleCell: function jstv_cycleCell(aRow, aCol) {},
+ cycleHeader: function jstv_cycleHeader(aCol) {},
+
+ _tree: null,
+
+ /**
+ * An array of jstv items, where each item corresponds to a row in the tree
+ */
+ _rowMap: null,
+
+ /**
+ * This is a javascript map of which containers we had open, so that we can
+ * persist their state over-time. It is designed to be used as a JSON object.
+ */
+ _persistOpenMap: null,
+
+ _restoreOpenStates: function jstv__restoreOpenStates() {
+ // Note that as we iterate through here, .length may grow
+ for (let i = 0; i < this._rowMap.length; i++) {
+ if (this._persistOpenMap.indexOf(this._rowMap[i].id) != -1)
+ this.toggleOpenState(i);
+ }
+ },
+
+ QueryInterface: function QueryInterface(aIID) {
+ if (aIID.equals(Components.interfaces.nsITreeView) ||
+ aIID.equals(Components.interfaces.nsISupports))
+ return this;
+
+ throw Components.results.NS_ERROR_NO_INTERFACE;
+ }
+};
diff --git a/mailnews/base/content/junkCommands.js b/mailnews/base/content/junkCommands.js
new file mode 100644
index 000000000..6332d193f
--- /dev/null
+++ b/mailnews/base/content/junkCommands.js
@@ -0,0 +1,456 @@
+/* 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/. */
+
+ /* functions use for junk processing commands
+ *
+ * TODO: These functions make the false assumption that a view only contains
+ * a single folder. This is not true for XF saved searches.
+ *
+ * globals prerequisites used:
+ *
+ * window.MsgStatusFeedback
+ *
+ * One of:
+ * GetSelectedIndices(view) (in suite)
+ * gFolderDisplay (in mail)
+ *
+ * messenger
+ * gMessengerBundle
+ * gDBView
+ * either gMsgFolderSelected or gFolderDisplay
+ * MsgJunkMailInfo(aCheckFirstUse)
+ * SetNextMessageAfterDelete()
+ * pref
+ * msgWindow
+ */
+
+Components.utils.import("resource:///modules/mailServices.js");
+Components.utils.import("resource:///modules/MailUtils.js");
+Components.utils.import("resource://gre/modules/Services.jsm");
+
+/*
+ * determineActionsForJunkMsgs
+ *
+ * Determines the actions that should be carried out on the messages
+ * that are being marked as junk
+ *
+ * @param aFolder
+ * the folder with messages being marked as junk
+ *
+ * @return an object with two properties: 'markRead' (boolean) indicating
+ * whether the messages should be marked as read, and 'junkTargetFolder'
+ * (nsIMsgFolder) specifying where the messages should be moved, or
+ * null if they should not be moved.
+ */
+function determineActionsForJunkMsgs(aFolder)
+{
+ var actions = { markRead: false, junkTargetFolder: null };
+ var spamSettings = aFolder.server.spamSettings;
+
+ // note we will do moves/marking as read even if the spam
+ // feature is disabled, since the user has asked to use it
+ // despite the disabling
+
+ actions.markRead = spamSettings.markAsReadOnSpam;
+ actions.junkTargetFolder = null;
+
+ // move only when the corresponding setting is activated
+ // and the currently viewed folder is not the junk folder.
+ if (spamSettings.moveOnSpam &&
+ !(aFolder.flags & Components.interfaces.nsMsgFolderFlags.Junk))
+ {
+ var spamFolderURI = spamSettings.spamFolderURI;
+ if (!spamFolderURI)
+ {
+ // XXX TODO
+ // we should use nsIPromptService to inform the user of the problem,
+ // e.g. when the junk folder was accidentally deleted.
+ dump('determineActionsForJunkMsgs: no spam folder found, not moving.');
+ }
+ else
+ actions.junkTargetFolder = MailUtils.getFolderForURI(spamFolderURI);
+ }
+
+ return actions;
+}
+
+/**
+ * performActionsOnJunkMsgs
+ *
+ * Performs required operations on a list of newly-classified junk messages
+ *
+ * @param aFolder
+ * the folder with messages being marked as junk
+ *
+ * @param aJunkMsgHdrs
+ * nsIArray containing headers (nsIMsgDBHdr) of new junk messages
+ *
+ * @param aGoodMsgHdrs
+ * nsIArray containing headers (nsIMsgDBHdr) of new good messages
+ */
+ function performActionsOnJunkMsgs(aFolder, aJunkMsgHdrs, aGoodMsgHdrs)
+{
+ if (aFolder instanceof Components.interfaces.nsIMsgImapMailFolder) // need to update IMAP custom flags
+ {
+ if (aJunkMsgHdrs.length)
+ {
+ var junkMsgKeys = new Array();
+ for (var i = 0; i < aJunkMsgHdrs.length; i++)
+ junkMsgKeys[i] = aJunkMsgHdrs.queryElementAt(i, Components.interfaces.nsIMsgDBHdr).messageKey;
+ aFolder.storeCustomKeywords(null, "Junk", "NonJunk", junkMsgKeys, junkMsgKeys.length);
+ }
+
+ if (aGoodMsgHdrs.length)
+ {
+ var goodMsgKeys = new Array();
+ for (var i = 0; i < aGoodMsgHdrs.length; i++)
+ goodMsgKeys[i] = aGoodMsgHdrs.queryElementAt(i, Components.interfaces.nsIMsgDBHdr).messageKey;
+ aFolder.storeCustomKeywords(null, "NonJunk", "Junk", goodMsgKeys, goodMsgKeys.length);
+ }
+ }
+
+ if (aJunkMsgHdrs.length)
+ {
+ var actionParams = determineActionsForJunkMsgs(aFolder);
+ if (actionParams.markRead)
+ aFolder.markMessagesRead(aJunkMsgHdrs, true);
+
+ if (actionParams.junkTargetFolder)
+ MailServices.copy
+ .CopyMessages(aFolder, aJunkMsgHdrs, actionParams.junkTargetFolder,
+ true /* isMove */, null, msgWindow, true /* allow undo */);
+ }
+}
+
+/**
+ * MessageClassifier
+ *
+ * Helper object storing the list of pending messages to process,
+ * and implementing junk processing callback
+ *
+ * @param aFolder
+ * the folder with messages to be analyzed for junk
+ * @param aTotalMessages
+ * Number of messages to process, used for progress report only
+ */
+
+function MessageClassifier(aFolder, aTotalMessages)
+{
+ this.mFolder = aFolder;
+ this.mJunkMsgHdrs = Components.classes["@mozilla.org/array;1"]
+ .createInstance(Components.interfaces.nsIMutableArray);
+ this.mGoodMsgHdrs = Components.classes["@mozilla.org/array;1"]
+ .createInstance(Components.interfaces.nsIMutableArray);
+ this.mMessages = new Object();
+ this.mMessageQueue = new Array();
+ this.mTotalMessages = aTotalMessages;
+ this.mProcessedMessages = 0;
+ this.firstMessage = true;
+ this.lastStatusTime = Date.now();
+}
+
+MessageClassifier.prototype =
+{
+ /**
+ * analyzeMessage
+ *
+ * Starts the message classification process for a message. If the message
+ * sender's address is whitelisted, the message is skipped.
+ *
+ * @param aMsgHdr
+ * The header (nsIMsgDBHdr) of the message to classify.
+ * @param aSpamSettings
+ * nsISpamSettings object with information about whitelists
+ */
+ analyzeMessage: function(aMsgHdr, aSpamSettings)
+ {
+ var junkscoreorigin = aMsgHdr.getStringProperty("junkscoreorigin");
+ if (junkscoreorigin == "user") // don't override user-set junk status
+ return;
+
+ // check whitelisting
+ if (aSpamSettings.checkWhiteList(aMsgHdr))
+ {
+ // message is ham from whitelist
+ var db = aMsgHdr.folder.msgDatabase;
+ db.setStringProperty(aMsgHdr.messageKey, "junkscore",
+ Components.interfaces.nsIJunkMailPlugin.IS_HAM_SCORE);
+ db.setStringProperty(aMsgHdr.messageKey, "junkscoreorigin", "whitelist");
+ this.mGoodMsgHdrs.appendElement(aMsgHdr, false);
+ return;
+ }
+
+ var messageURI = aMsgHdr.folder.generateMessageURI(aMsgHdr.messageKey) + "?fetchCompleteMessage=true";
+ this.mMessages[messageURI] = aMsgHdr;
+ if (this.firstMessage)
+ {
+ this.firstMessage = false;
+ MailServices.junk.classifyMessage(messageURI, msgWindow, this);
+ }
+ else
+ this.mMessageQueue.push(messageURI);
+ },
+
+ /*
+ * nsIJunkMailClassificationListener implementation
+ * onMessageClassified
+ *
+ * Callback function from nsIJunkMailPlugin with classification results
+ *
+ * @param aClassifiedMsgURI
+ * URI of classified message
+ * @param aClassification
+ * Junk classification (0: UNCLASSIFIED, 1: GOOD, 2: JUNK)
+ * @param aJunkPercent
+ * 0 - 100 indicator of junk likelihood, with 100 meaning probably junk
+ */
+ onMessageClassified: function(aClassifiedMsgURI, aClassification, aJunkPercent)
+ {
+ if (!aClassifiedMsgURI)
+ return; // ignore end of batch
+ var nsIJunkMailPlugin = Components.interfaces.nsIJunkMailPlugin;
+ var score = (aClassification == nsIJunkMailPlugin.JUNK) ?
+ nsIJunkMailPlugin.IS_SPAM_SCORE : nsIJunkMailPlugin.IS_HAM_SCORE;
+ const statusDisplayInterval = 1000; // milliseconds between status updates
+
+ // set these props via the db (instead of the message header
+ // directly) so that the nsMsgDBView knows to update the UI
+ //
+ var msgHdr = this.mMessages[aClassifiedMsgURI];
+ var db = msgHdr.folder.msgDatabase;
+ db.setStringProperty(msgHdr.messageKey, "junkscore", score);
+ db.setStringProperty(msgHdr.messageKey, "junkscoreorigin", "plugin");
+ db.setStringProperty(msgHdr.messageKey, "junkpercent", aJunkPercent);
+
+ if (aClassification == nsIJunkMailPlugin.JUNK)
+ this.mJunkMsgHdrs.appendElement(msgHdr, false);
+ else if (aClassification == nsIJunkMailPlugin.GOOD)
+ this.mGoodMsgHdrs.appendElement(msgHdr, false);
+
+ var nextMsgURI = this.mMessageQueue.shift();
+ if (nextMsgURI)
+ {
+ ++this.mProcessedMessages;
+ if (Date.now() > this.lastStatusTime + statusDisplayInterval)
+ {
+ this.lastStatusTime = Date.now();
+ var percentDone = 0;
+ if (this.mTotalMessages)
+ percentDone = Math.round(this.mProcessedMessages * 100 / this.mTotalMessages);
+ var percentStr = percentDone + "%";
+ window.MsgStatusFeedback.showStatusString(
+ document.getElementById("bundle_messenger")
+ .getFormattedString("junkAnalysisPercentComplete",
+ [percentStr]));
+ }
+
+ MailServices.junk.classifyMessage(nextMsgURI, msgWindow, this);
+ }
+ else
+ {
+ window.MsgStatusFeedback.showStatusString(
+ document.getElementById("bundle_messenger")
+ .getString("processingJunkMessages"));
+ performActionsOnJunkMsgs(this.mFolder, this.mJunkMsgHdrs, this.mGoodMsgHdrs);
+ window.MsgStatusFeedback.showStatusString("");
+ }
+ }
+}
+
+/*
+ * filterFolderForJunk
+ *
+ * Filter all messages in the current folder for junk
+ */
+function filterFolderForJunk() { processFolderForJunk(true); }
+
+/*
+ * analyzeMessagesForJunk
+ *
+ * Filter selected messages in the current folder for junk
+ */
+function analyzeMessagesForJunk() { processFolderForJunk(false); }
+
+/*
+ * processFolderForJunk
+ *
+ * Filter messages in the current folder for junk
+ *
+ * @param aAll: true to filter all messages, else filter selection
+ */
+function processFolderForJunk(aAll)
+{
+ MsgJunkMailInfo(true);
+
+ if (aAll)
+ {
+ // need to expand all threads, so we analyze everything
+ gDBView.doCommand(nsMsgViewCommandType.expandAll);
+ var treeView = gDBView.QueryInterface(Components.interfaces.nsITreeView);
+ var count = treeView.rowCount;
+ if (!count)
+ return;
+ }
+ else
+ {
+ // suite uses GetSelectedIndices, mail uses gFolderDisplay.selectedMessages
+ var indices = typeof GetSelectedIndices != "undefined" ?
+ GetSelectedIndices(gDBView) :
+ gFolderDisplay.selectedIndices;
+ if (!indices || !indices.length)
+ return;
+ }
+ var totalMessages = aAll ? count : indices.length;
+
+ // retrieve server and its spam settings via the header of an arbitrary message
+ for (var i = 0; i < totalMessages; i++)
+ {
+ var index = aAll ? i : indices[i];
+ try
+ {
+ var tmpMsgURI = gDBView.getURIForViewIndex(index);
+ break;
+ }
+ catch (e)
+ {
+ // dummy headers will fail, so look for another
+ continue;
+ }
+ }
+ if (!tmpMsgURI)
+ return;
+
+ var tmpMsgHdr = messenger.messageServiceFromURI(tmpMsgURI).messageURIToMsgHdr(tmpMsgURI);
+ var spamSettings = tmpMsgHdr.folder.server.spamSettings;
+
+ // create a classifier instance to classify messages in the folder.
+ var msgClassifier = new MessageClassifier(tmpMsgHdr.folder, totalMessages);
+
+ for ( i = 0; i < totalMessages; i++)
+ {
+ var index = aAll ? i : indices[i];
+ try
+ {
+ var msgURI = gDBView.getURIForViewIndex(index);
+ var msgHdr = messenger.messageServiceFromURI(msgURI).messageURIToMsgHdr(msgURI);
+ msgClassifier.analyzeMessage(msgHdr, spamSettings);
+ }
+ catch (ex)
+ {
+ // blow off errors here - dummy headers will fail
+ var msgURI = null;
+ }
+ }
+ if (msgClassifier.firstMessage) // the async plugin was not used, maybe all whitelisted?
+ performActionsOnJunkMsgs(msgClassifier.mFolder,
+ msgClassifier.mJunkMsgHdrs,
+ msgClassifier.mGoodMsgHdrs);
+}
+
+function JunkSelectedMessages(setAsJunk)
+{
+ MsgJunkMailInfo(true);
+
+ // When the user explicitly marks a message as junk, we can mark it as read,
+ // too. This is independent of the "markAsReadOnSpam" pref, which applies
+ // only to automatically-classified messages.
+ // Note that this behaviour should match the one in the back end for marking
+ // as junk via clicking the 'junk' column.
+
+ if (setAsJunk && Services.prefs.getBoolPref("mailnews.ui.junk.manualMarkAsJunkMarksRead"))
+ MarkSelectedMessagesRead(true);
+
+ gDBView.doCommand(setAsJunk ? nsMsgViewCommandType.junk
+ : nsMsgViewCommandType.unjunk);
+}
+
+/**
+ * Delete junk messages in the current folder. This provides the guarantee that
+ * the method will be synchronous if no messages are deleted.
+ *
+ * @returns The number of messages deleted.
+ */
+function deleteJunkInFolder()
+{
+ MsgJunkMailInfo(true);
+
+ // use direct folder commands if possible so we don't mess with the selection
+ let selectedFolder = gFolderDisplay.displayedFolder;
+ if ( !(selectedFolder.flags & Components.interfaces.nsMsgFolderFlags.Virtual) )
+ {
+ var junkMsgHdrs = Components.classes["@mozilla.org/array;1"]
+ .createInstance(Components.interfaces.nsIMutableArray);
+ var enumerator = gDBView.msgFolder.messages;
+ while (enumerator.hasMoreElements())
+ {
+ var msgHdr = enumerator.getNext().QueryInterface(Components.interfaces.nsIMsgDBHdr);
+ var junkScore = msgHdr.getStringProperty("junkscore");
+ if (junkScore == Components.interfaces.nsIJunkMailPlugin.IS_SPAM_SCORE)
+ junkMsgHdrs.appendElement(msgHdr, false);
+ }
+
+ if (junkMsgHdrs.length)
+ gDBView.msgFolder.deleteMessages(junkMsgHdrs, msgWindow, false, false, null, true);
+ return junkMsgHdrs.length;
+ }
+
+ // Folder is virtual, let the view do the work (but we lose selection)
+
+ // need to expand all threads, so we find everything
+ gDBView.doCommand(nsMsgViewCommandType.expandAll);
+
+ var treeView = gDBView.QueryInterface(Components.interfaces.nsITreeView);
+ var count = treeView.rowCount;
+ if (!count)
+ return 0;
+
+ var treeSelection = treeView.selection;
+
+ var clearedSelection = false;
+
+ // select the junk messages
+ var messageUri;
+ let numMessagesDeleted = 0;
+ for (var i = 0; i < count; ++i)
+ {
+ try {
+ messageUri = gDBView.getURIForViewIndex(i);
+ }
+ catch (ex) {continue;} // blow off errors for dummy rows
+ var msgHdr = messenger.messageServiceFromURI(messageUri).messageURIToMsgHdr(messageUri);
+ var junkScore = msgHdr.getStringProperty("junkscore");
+ var isJunk = (junkScore == Components.interfaces.nsIJunkMailPlugin.IS_SPAM_SCORE);
+ // if the message is junk, select it.
+ if (isJunk)
+ {
+ // only do this once
+ if (!clearedSelection)
+ {
+ // clear the current selection
+ // since we will be deleting all selected messages
+ treeSelection.clearSelection();
+ clearedSelection = true;
+ treeSelection.selectEventsSuppressed = true;
+ }
+ treeSelection.rangedSelect(i, i, true /* augment */);
+ numMessagesDeleted++;
+ }
+ }
+
+ // if we didn't clear the selection
+ // there was no junk, so bail.
+ if (!clearedSelection)
+ return 0;
+
+ treeSelection.selectEventsSuppressed = false;
+ // delete the selected messages
+ //
+ // We'll leave no selection after the delete
+ gNextMessageViewIndexAfterDelete = nsMsgViewIndex_None;
+ gDBView.doCommand(nsMsgViewCommandType.deleteMsg);
+ treeSelection.clearSelection();
+ ClearMessagePane();
+ return numMessagesDeleted;
+}
+
diff --git a/mailnews/base/content/junkLog.js b/mailnews/base/content/junkLog.js
new file mode 100644
index 000000000..148f0e507
--- /dev/null
+++ b/mailnews/base/content/junkLog.js
@@ -0,0 +1,33 @@
+/* -*- Mode: JavaScript; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 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/. */
+
+Components.utils.import("resource://gre/modules/Services.jsm");
+
+var gLogView;
+var gLogFile;
+
+function onLoad()
+{
+ gLogView = document.getElementById("logView");
+ gLogView.docShell.allowJavascript = false; // for security, disable JS
+
+ gLogFile = Services.dirsvc.get("ProfD", Components.interfaces.nsIFile);
+ gLogFile.append("junklog.html");
+
+ if (gLogFile.exists())
+ {
+ // convert the file to a URL so we can load it.
+ gLogView.setAttribute("src", Services.io.newFileURI(gLogFile).spec);
+ }
+}
+
+function clearLog()
+{
+ if (gLogFile.exists())
+ {
+ gLogFile.remove(false);
+ gLogView.setAttribute("src", "about:blank"); // we don't have a log file to show
+ }
+}
diff --git a/mailnews/base/content/junkLog.xul b/mailnews/base/content/junkLog.xul
new file mode 100644
index 000000000..49e8796c3
--- /dev/null
+++ b/mailnews/base/content/junkLog.xul
@@ -0,0 +1,46 @@
+<?xml version="1.0"?>
+
+<!-- -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ - 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/. -->
+
+<?xml-stylesheet href="chrome://messenger/skin/messenger.css" type="text/css"?>
+
+<!DOCTYPE dialog SYSTEM "chrome://messenger/locale/junkLog.dtd">
+
+<dialog id="viewLogWindow"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ onload="onLoad();"
+ title="&adaptiveJunkLog.title;"
+ windowtype="mailnews:junklog"
+ buttons="accept"
+ buttonlabelaccept="&closeLog.label;"
+ buttonaccesskeyaccept="&closeLog.accesskey;"
+ ondialogaccept="window.close();"
+ persist="screenX screenY width height"
+ style="width: 40em; height: 25em;">
+
+ <script type="application/javascript" src="chrome://messenger/content/junkLog.js"/>
+
+ <vbox flex="1">
+ <hbox>
+ <label value="&adaptiveJunkLogInfo.label;"/>
+ <spacer flex="1"/>
+ <button label="&clearLog.label;"
+ accesskey="&clearLog.accesskey;"
+ oncommand="clearLog();"/>
+ </hbox>
+ <separator class="thin"/>
+ <hbox flex="1">
+ <browser id="logView"
+ class="inset"
+ type="content"
+ disablehistory="true"
+ disablesecurity="true"
+ src="about:blank"
+ autofind="false"
+ flex="1"/>
+ </hbox>
+ </vbox>
+</dialog>
diff --git a/mailnews/base/content/junkMailInfo.xul b/mailnews/base/content/junkMailInfo.xul
new file mode 100644
index 000000000..f4658f1c2
--- /dev/null
+++ b/mailnews/base/content/junkMailInfo.xul
@@ -0,0 +1,35 @@
+<?xml version="1.0"?>
+
+<!-- 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/. -->
+
+<!DOCTYPE dialog [
+<!ENTITY % brandDTD SYSTEM "chrome://branding/locale/brand.dtd" >
+%brandDTD;
+<!ENTITY % junkMailInfoDTD SYSTEM "chrome://messenger/locale/junkMailInfo.dtd" >
+%junkMailInfoDTD;
+]>
+
+<?xml-stylesheet href="chrome://messenger/skin/messenger.css" type="text/css"?>
+<?xml-stylesheet href="chrome://messenger/skin/primaryToolbar.css" type="text/css"?>
+
+<dialog id="junkMailInfo"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ title="&window.title;"
+ windowtype="mailnews:junkmailinfo"
+ buttons="accept">
+
+ <vbox flex="1" width="&window.width;">
+ <spacer/>
+ <description>&info1a.label;<image align="center" id="junkIcon"/>&info1b.label;</description>
+ <spacer/>
+ <description>&info2.label;</description>
+ <spacer/>
+ <description>&info3.label;</description>
+#ifndef MOZ_THUNDERBIRD
+ <spacer/>
+ <description>&info4.label;</description>
+#endif
+ </vbox>
+</dialog>
diff --git a/mailnews/base/content/markByDate.js b/mailnews/base/content/markByDate.js
new file mode 100644
index 000000000..9a35f64a5
--- /dev/null
+++ b/mailnews/base/content/markByDate.js
@@ -0,0 +1,133 @@
+/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 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 MILLISECONDS_PER_HOUR = 60 * 60 * 1000;
+var MICROSECONDS_PER_DAY = 1000 * MILLISECONDS_PER_HOUR * 24;
+
+function onLoad()
+{
+ var upperDateBox = document.getElementById("upperDate");
+ // focus the upper bound control - this is where we expect most users to enter
+ // a date
+ upperDateBox.focus();
+
+ // and give it an initial date - "yesterday"
+ var initialDate = new Date();
+ initialDate.setHours( 0 );
+ initialDate.setTime( initialDate.getTime() - MILLISECONDS_PER_HOUR );
+ // note that this is sufficient - though it is at the end of the previous day,
+ // we convert it to a date string, and then the time part is truncated
+ upperDateBox.value = convertDateToString( initialDate );
+ upperDateBox.select(); // allows to start overwriting immediately
+}
+
+function onAccept()
+{
+ // get the times as entered by the user
+ var lowerDateString = document.getElementById( "lowerDate" ).value;
+ // the fallback for the lower bound, if not entered, is the "beginning of
+ // time" (1970-01-01), which actually is simply 0 :)
+ var prLower = lowerDateString ? convertStringToPRTime( lowerDateString ) : 0;
+
+ var upperDateString = document.getElementById( "upperDate" ).value;
+ var prUpper;
+ if ( upperDateString == "" )
+ {
+ // for the upper bound, the fallback is "today".
+ var dateThisMorning = new Date();
+ dateThisMorning.setMilliseconds( 0 );
+ dateThisMorning.setSeconds( 0 );
+ dateThisMorning.setMinutes( 0 );
+ dateThisMorning.setHours( 0 );
+ // Javascript time is in milliseconds, PRTime is in microseconds
+ prUpper = dateThisMorning.getTime() * 1000;
+ }
+ else
+ prUpper = convertStringToPRTime( upperDateString );
+
+ // for the upper date, we have to do a correction:
+ // if the user enters a date, then she means (hopefully) that all messages sent
+ // at this day should be marked, too, but the PRTime calculated from this would
+ // point to the beginning of the day. So we need to increment it by
+ // [number of micro seconds per day]. This will denote the first microsecond of
+ // the next day then, which is later used as exclusive boundary
+ prUpper += MICROSECONDS_PER_DAY;
+
+ markInDatabase( prLower, prUpper );
+
+ return true; // allow closing
+}
+
+/** marks all headers in the database, whose time is between the two
+ given times, as read.
+ @param lower
+ PRTime for the lower bound - this boundary is inclusive
+ @param upper
+ PRTime for the upper bound - this boundary is exclusive
+*/
+function markInDatabase( lower, upper )
+{
+ var messageFolder;
+ var messageDatabase;
+ // extract the database
+ if ( window.arguments && window.arguments[0] )
+ {
+ messageFolder = window.arguments[0];
+ messageDatabase = messageFolder.msgDatabase;
+ }
+
+ if ( !messageDatabase )
+ {
+ dump( "markByDate::markInDatabase: there /is/ no database to operate on!\n" );
+ return;
+ }
+
+ // the headers which are going to be marked
+ var headers = Components.classes["@mozilla.org/array;1"].createInstance( Components.interfaces.nsIMutableArray );
+ var searchSession = Components.classes["@mozilla.org/messenger/searchSession;1"].createInstance( Components.interfaces.nsIMsgSearchSession );
+ var searchTerms = Components.classes["@mozilla.org/array;1"].createInstance( Components.interfaces.nsIMutableArray );
+ searchSession.addScopeTerm( Components.interfaces.nsMsgSearchScope.offlineMail, messageFolder );
+
+ const nsMsgSearchAttrib = Components.interfaces.nsMsgSearchAttrib;
+ const nsMsgSearchOp = Components.interfaces.nsMsgSearchOp;
+
+ var searchTerm = searchSession.createTerm();
+ searchTerm.attrib = nsMsgSearchAttrib.Date;
+ searchTerm.op = nsMsgSearchOp.IsBefore;
+ var value = searchTerm.value;
+ value.attrib = nsMsgSearchAttrib.Date;
+ value.date = upper;
+ searchTerm.value = value;
+ searchTerms.appendElement( searchTerm, false );
+
+ if ( lower )
+ {
+ searchTerm = searchSession.createTerm();
+ searchTerm.booleanAnd = true;
+ searchTerm.attrib = nsMsgSearchAttrib.Date;
+ searchTerm.op = nsMsgSearchOp.IsAfter;
+ value = searchTerm.value;
+ value.attrib = nsMsgSearchAttrib.Date;
+ value.date = lower;
+ searchTerm.value = value;
+ searchTerms.appendElement( searchTerm, false );
+ }
+
+ var filterEnumerator = messageDatabase.getFilterEnumerator( searchTerms );
+
+ if ( filterEnumerator )
+ {
+ var keepGoing;
+ var numMatches = {};
+ do
+ {
+ keepGoing = messageDatabase.nextMatchingHdrs(filterEnumerator, 0, 0, headers, numMatches);
+ }
+ while ( keepGoing );
+ }
+
+ if ( headers.length )
+ messageFolder.markMessagesRead( headers, true );
+}
diff --git a/mailnews/base/content/markByDate.xul b/mailnews/base/content/markByDate.xul
new file mode 100644
index 000000000..cf472fa33
--- /dev/null
+++ b/mailnews/base/content/markByDate.xul
@@ -0,0 +1,34 @@
+<?xml version="1.0"?>
+
+<!-- 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/. -->
+
+<?xml-stylesheet href="chrome://messenger/skin/dialogs.css" type="text/css"?>
+
+<!DOCTYPE dialog SYSTEM "chrome://messenger/locale/markByDate.dtd">
+
+<dialog
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ title="&messageMarkByDate.label;"
+ buttons="accept,cancel"
+ onload="onLoad();"
+ ondialogaccept="return onAccept();">
+
+ <script type="application/javascript" src="chrome://messenger/content/markByDate.js"/>
+ <script type="application/javascript" src="chrome://messenger/content/dateFormat.js"/>
+
+ <hbox align="center" pack="end">
+ <label control="lowerDate"
+ value="&markByDateLower.label;"
+ accesskey="&markByDateLower.accesskey;"/>
+ <textbox size="11" id="lowerDate"/>
+ </hbox>
+ <hbox align="center" pack="end">
+ <label control="upperDate"
+ value="&markByDateUpper.label;"
+ accesskey="&markByDateUpper.accesskey;"/>
+ <textbox size="11" id="upperDate"/>
+ </hbox>
+
+</dialog>
diff --git a/mailnews/base/content/msgAccountCentral.js b/mailnews/base/content/msgAccountCentral.js
new file mode 100644
index 000000000..de5543dff
--- /dev/null
+++ b/mailnews/base/content/msgAccountCentral.js
@@ -0,0 +1,341 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * 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/. */
+
+Components.utils.import("resource://gre/modules/Services.jsm");
+
+var selectedServer = null;
+
+function OnInit()
+{
+ // Set the header for the page.
+ // Title containts the brand name of the application and the account
+ // type (mail/news) and the name of the account
+ try {
+ let msgFolder = null;
+ let title;
+
+ // Get the brand name
+ let brandName = document.getElementById("bundle_brand")
+ .getString("brandShortName");
+ let messengerBundle = document.getElementById("bundle_messenger");
+
+ selectedServer = GetSelectedServer();
+ if (selectedServer) {
+ // Get the account type
+ let serverType = selectedServer.type;
+ let acctType;
+ if (serverType == "nntp")
+ acctType = messengerBundle.getString("newsAcctType");
+ else if (serverType == "rss")
+ acctType = messengerBundle.getString("feedsAcctType");
+ else
+ acctType = messengerBundle.getString("mailAcctType");
+
+ // Get the account name
+ msgFolder = GetSelectedMsgFolder();
+ ArrangeAccountCentralItems(selectedServer, msgFolder);
+
+ let acctName = msgFolder.prettyName;
+ // Display and collapse items presented to the user based on account type
+ title = messengerBundle.getFormattedString("acctCentralTitleFormat",
+ [brandName, acctType, acctName]);
+ } else {
+ // If there is no selectedServer, we are in a brand new profile with
+ // no accounts - show the create account rows.
+ title = brandName;
+ SetItemDisplay("AccountsHeader", true);
+ SetItemDisplay("CreateAccount", true);
+ SetItemDisplay("CreateAccounts", true);
+ }
+
+ // Set the title for the document
+ document.getElementById("AccountCentralTitle").setAttribute("value", title);
+ }
+ catch(ex) {
+ Components.utils.reportError("Error getting selected account: " + ex + "\n");
+ }
+}
+
+// Show items in the AccountCentral page depending on the capabilities
+// of the given account
+function ArrangeAccountCentralItems(server, msgFolder)
+{
+ let exceptions = [];
+ let protocolInfo = null;
+ try {
+ protocolInfo = server.protocolInfo;
+ } catch (e) {
+ exceptions.push(e);
+ }
+
+ // Is this a RSS account?
+ let displayRssHeader = server && server.type == 'rss';
+
+ /***** Email header and items : Begin *****/
+
+ // Read Messages
+ let canGetMessages = false;
+ try {
+ canGetMessages = protocolInfo && protocolInfo.canGetMessages;
+ SetItemDisplay("ReadMessages", canGetMessages && !displayRssHeader);
+ } catch (e) { exceptions.push(e); }
+
+ // Compose Messages link
+ let showComposeMsgLink = false;
+ try {
+ showComposeMsgLink = protocolInfo && protocolInfo.showComposeMsgLink;
+ SetItemDisplay("ComposeMessage", showComposeMsgLink);
+ } catch (e) { exceptions.push(e); }
+
+ // Junk mail settings (false, until ready for prime time)
+ let canControlJunkEmail = false
+ try {
+ canControlJunkEmail = false && protocolInfo &&
+ protocolInfo.canGetIncomingMessages &&
+ protocolInfo.canGetMessages;
+ SetItemDisplay("JunkSettingsMail", canControlJunkEmail);
+ } catch (e) { exceptions.push(e); }
+
+ // Display Email header, only if any of the items are displayed
+ let displayEmailHeader = !displayRssHeader &&
+ (canGetMessages || showComposeMsgLink ||
+ canControlJunkEmail);
+ SetItemDisplay("EmailHeader", displayEmailHeader);
+
+ /***** Email header and items : End *****/
+
+ /***** News header and items : Begin *****/
+
+ // Subscribe to Newsgroups
+ let canSubscribe = false;
+ try {
+ canSubscribe = msgFolder && msgFolder.canSubscribe &&
+ protocolInfo && !protocolInfo.canGetMessages;
+ SetItemDisplay("SubscribeNewsgroups", canSubscribe);
+ } catch (e) { exceptions.push(e); }
+
+ // Junk news settings (false, until ready for prime time)
+ let canControlJunkNews = false;
+ try {
+ canControlJunkNews = false && protocolInfo &&
+ protocolInfo.canGetIncomingMessages &&
+ !protocolInfo.canGetMessages;
+ SetItemDisplay("JunkSettingsNews", canControlJunkNews);
+ } catch (e) { exceptions.push(e); }
+
+ // Display News header, only if any of the items are displayed
+ let displayNewsHeader = canSubscribe || canControlJunkNews;
+ SetItemDisplay("NewsHeader", displayNewsHeader);
+
+ /***** News header and items : End *****/
+
+ /***** RSS header and items : Begin *****/
+
+ // Display RSS header, only if this is RSS account
+ SetItemDisplay("rssHeader", displayRssHeader);
+
+ // Subscribe to RSS Feeds
+ SetItemDisplay("SubscribeRSS", displayRssHeader);
+
+ /***** RSS header and items : End *****/
+
+ // If either of above sections exists, show section separators
+ SetItemDisplay("MessagesSection",
+ displayNewsHeader || displayEmailHeader || displayRssHeader);
+
+ /***** Accounts : Begin *****/
+
+ // Account Settings if a server is found
+ let canShowAccountSettings = server != null;
+ SetItemDisplay("AccountSettings", canShowAccountSettings);
+
+ // Show New Mail Account Wizard if not prohibited by pref
+ let canShowCreateAccount = false;
+ try {
+ canShowCreateAccount = !Services.prefs
+ .prefIsLocked("mail.disable_new_account_addition");
+ SetItemDisplay("CreateAccount", canShowCreateAccount);
+ SetItemDisplay("CreateAccounts", canShowCreateAccount);
+ } catch (e) { exceptions.push(e); }
+
+ // Display Accounts header, only if any of the items are displayed
+ let displayAccountsHeader = canShowAccountSettings || canShowCreateAccount;
+ SetItemDisplay("AccountsHeader", canShowCreateAccount);
+
+ /***** Accounts : End *****/
+
+ /***** Advanced Features header and items : Begin *****/
+
+ // Search Messages
+ let canSearchMessages = false;
+ try {
+ canSearchMessages = server && server.canSearchMessages;
+ SetItemDisplay("SearchMessages", canSearchMessages);
+ } catch (e) { exceptions.push(e); }
+
+ // Create Filters
+ let canHaveFilters = false;
+ try {
+ canHaveFilters = server && server.canHaveFilters;
+ SetItemDisplay("CreateFilters", canHaveFilters);
+ } catch (e) { exceptions.push(e); }
+
+ // Subscribe to IMAP Folders
+ let canSubscribeImapFolders = false;
+ try {
+ canSubscribeImapFolders = msgFolder && msgFolder.canSubscribe &&
+ protocolInfo && protocolInfo.canGetMessages;
+ SetItemDisplay("SubscribeImapFolders", canSubscribeImapFolders);
+ } catch (e) { exceptions.push(e); }
+
+ // Offline Settings
+ let supportsOffline = false;
+ try {
+ supportsOffline = server && server.offlineSupportLevel != 0;
+ SetItemDisplay("OfflineSettings", supportsOffline);
+ } catch (e) { exceptions.push(e); }
+
+ // Display Adv Features header, only if any of the items are displayed
+ let displayAdvFeatures = canSearchMessages || canHaveFilters ||
+ canSubscribeImapFolders|| supportsOffline;
+ SetItemDisplay("AdvancedFeaturesHeader", displayAdvFeatures);
+
+ /***** Advanced Featuers header and items : End *****/
+
+ // If either of above features exist, show section separators
+ SetItemDisplay("AccountsSection", displayAdvFeatures);
+
+ while (exceptions.length) {
+ Components.utils.reportError("Error in setting AccountCentral Items: "
+ + exceptions.pop() + "\n");
+ }
+}
+
+// Show the item if the item feature is supported
+function SetItemDisplay(elemId, displayThisItem)
+{
+ if (displayThisItem) {
+ let elem = document.getElementById(elemId);
+ if (elem)
+ elem.setAttribute("collapsed", false);
+
+ let elemSpacer = document.getElementById(elemId + ".spacer");
+ if (elemSpacer)
+ elemSpacer.setAttribute("collapsed", false);
+ }
+}
+
+// From the current folder tree, return the selected server or null
+function GetSelectedServer()
+{
+ let currentFolder = GetSelectedMsgFolder();
+ return currentFolder ? currentFolder.server : null;
+}
+
+// From the current folder tree, return the selected folder,
+// the root folder of default account or null
+function GetSelectedMsgFolder()
+{
+ return window.parent.GetSelectedMsgFolders()[0] ||
+ window.parent.GetDefaultAccountRootFolder();
+}
+
+/**
+ * Open Inbox for selected server.
+ * If needed, open the twisty and select Inbox.
+ */
+function ReadMessages()
+{
+ if (!selectedServer)
+ return;
+ try {
+ window.parent.OpenInboxForServer(selectedServer);
+ }
+ catch(ex) {
+ Components.utils
+ .reportError("Error opening Inbox for server: " + ex + "\n");
+ }
+}
+
+// Trigger composer for a new message
+function ComposeAMessage(event)
+{
+ // Pass event to allow holding Shift key for toggling HTML vs. plaintext format
+ window.parent.MsgNewMessage(event);
+}
+
+/**
+ * Open AccountManager to view settings for a given account
+ * @param selectPage the xul file name for the viewing page,
+ * null for the account main page, other pages are
+ * 'am-server.xul', 'am-copies.xul', 'am-offline.xul',
+ * 'am-addressing.xul', 'am-smtp.xul'
+ */
+function ViewSettings(selectPage)
+{
+ window.parent.MsgAccountManager(selectPage);
+}
+
+// Open AccountWizard to create an account
+function CreateNewAccount()
+{
+ window.parent.msgOpenAccountWizard();
+}
+
+function CreateNewAccountTB(type)
+{
+ if (type == "mail") {
+ if (Services.prefs.getBoolPref("mail.provider.enabled"))
+ NewMailAccountProvisioner(null);
+ else
+ AddMailAccount();
+ return;
+ }
+
+ if (type == "feeds") {
+ AddFeedAccount();
+ return;
+ }
+
+ window.parent.msgOpenAccountWizard(
+ function(state) {
+ let win = getMostRecentMailWindow();
+ if (state && win && win.gFolderTreeView && this.gCurrentAccount) {
+ win.gFolderTreeView.selectFolder(
+ this.gCurrentAccount.incomingServer.rootMsgFolder);
+ }
+ },
+ type);
+}
+
+// Bring up search interface for selected account
+function SearchMessages()
+{
+ window.parent.MsgSearchMessages();
+}
+
+// Open filters window
+function CreateMsgFilters()
+{
+ window.parent.MsgFilters(null, null);
+}
+
+// Open Subscribe dialog
+function Subscribe()
+{
+ if (!selectedServer)
+ return;
+ if (selectedServer.type == 'rss')
+ window.parent.openSubscriptionsDialog(selectedServer.rootFolder);
+ else
+ window.parent.MsgSubscribe();
+}
+
+// Open junk mail settings dialog
+function JunkSettings()
+{
+ // TODO: function does not exist yet, will throw an exception if exposed
+ window.parent.MsgJunkMail();
+}
diff --git a/mailnews/base/content/msgAccountCentral.xul b/mailnews/base/content/msgAccountCentral.xul
new file mode 100644
index 000000000..7ac6f94cb
--- /dev/null
+++ b/mailnews/base/content/msgAccountCentral.xul
@@ -0,0 +1,251 @@
+<?xml version="1.0"?>
+<!-- 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/. -->
+
+<?xml-stylesheet href="chrome://messenger/skin/accountCentral.css" type="text/css"?>
+
+<!DOCTYPE page [
+ <!ENTITY % brandDTD SYSTEM "chrome://branding/locale/brand.dtd">
+ %brandDTD;
+ <!ENTITY % globalDTD SYSTEM "chrome://global/locale/global.dtd">
+ %globalDTD;
+ <!ENTITY % acctCentralDTD SYSTEM "chrome://messenger/locale/msgAccountCentral.dtd">
+ %acctCentralDTD;
+]>
+
+<page
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ onload="OnInit();">
+
+ <stringbundle id="bundle_brand"
+ src="chrome://branding/locale/brand.properties"/>
+ <stringbundle id="bundle_messenger"
+ src="chrome://messenger/locale/messenger.properties"/>
+ <stringbundle id="bundle_prefs"
+ src="chrome://messenger/locale/prefs.properties"/>
+ <script type="application/javascript"
+ src="chrome://messenger/content/AccountManager.js"/>
+ <script type="application/javascript"
+ src="chrome://messenger/content/accountUtils.js"/>
+ <script type="application/javascript"
+ src="chrome://messenger/content/commandglue.js"/>
+ <script type="application/javascript"
+ src="chrome://messenger/content/mailWindowOverlay.js"/>
+ <script type="application/javascript"
+ src="chrome://messenger/content/mailWindow.js"/>
+ <script type="application/javascript"
+ src="chrome://messenger/content/msgMail3PaneWindow.js"/>
+ <script type="application/javascript"
+ src="chrome://messenger/content/msgAccountCentral.js"/>
+ <script type="application/javascript"
+ src="chrome://messenger/content/mailCore.js"/>
+
+ <grid id="acctCentralGrid" flex="1" style="overflow: auto;">
+ <columns id="acctCentralColumns">
+ <column flex="40" id="acctCentralActionsColumn"/>
+ <column flex="60" id="acctCentralHelpDataColumn"/>
+ </columns>
+
+ <rows id="acctCentralRows">
+ <row id="acctCentralHeaderRow">
+ <label id="AccountCentralTitle"/>
+ </row>
+ <spacer id="acctCentralHeader.spacer" flex="2"/>
+
+ <row id="EmailHeader" class="acctCentralTitleRow" collapsed="true">
+ <hbox class="acctCentralRowTitleBox">
+ <description>&emailSectionHdr.label;</description>
+ </hbox>
+ </row>
+
+ <spacer id="ReadMessages.spacer" flex="1" collapsed="true"/>
+ <row id="ReadMessages" class="acctCentralRow" collapsed="true">
+ <hbox>
+ <label class="acctCentralText acctCentralLinkText"
+ value="&readMsgsLink.label;"
+ chromedir="&locale.dir;"
+ onclick="ReadMessages();"/>
+ </hbox>
+ </row>
+
+ <spacer id="ComposeMessage.spacer" flex="1" collapsed="true"/>
+ <row id="ComposeMessage" class="acctCentralRow" collapsed="true">
+ <hbox>
+ <label class="acctCentralText acctCentralLinkText"
+ value="&composeMsgLink.label;"
+ chromedir="&locale.dir;"
+ onclick="ComposeAMessage(event);"/>
+ </hbox>
+ </row>
+
+ <spacer id="JunkSettingsMail.spacer" flex="1" collapsed="true"/>
+ <row id="JunkSettingsMail" class="acctCentralRow" collapsed="true">
+ <hbox>
+ <label class="acctCentralText acctCentralLinkText"
+ value="&junkSettings.label;"
+ onclick="JunkSettings();"/>
+ </hbox>
+ </row>
+
+ <spacer id="NewsHeader.spacer" flex="1" collapsed="true"/>
+ <row id="NewsHeader" class="acctCentralTitleRow" collapsed="true">
+ <hbox class="acctCentralRowTitleBox">
+ <description>&newsSectionHdr.label;</description>
+ </hbox>
+ </row>
+
+ <spacer id="SubscribeNewsgroups.spacer" flex="1" collapsed="true"/>
+ <row id="SubscribeNewsgroups" class="acctCentralRow" collapsed="true">
+ <hbox>
+ <label class="acctCentralText acctCentralLinkText"
+ value="&subscribeNewsLink.label;"
+ chromedir="&locale.dir;"
+ onclick="Subscribe();"/>
+ </hbox>
+ </row>
+
+ <spacer id="JunkSettingsNews.spacer" flex="1" collapsed="true"/>
+ <row id="JunkSettingsNews" class="acctCentralRow" collapsed="true">
+ <hbox>
+ <label class="acctCentralText acctCentralLinkText"
+ value="&junkSettings.label;"
+ onclick="JunkSettings();"/>
+ </hbox>
+ </row>
+
+ <spacer id="rssHeader.spacer" flex="1" collapsed="true"/>
+ <row id="rssHeader" class="acctCentralTitleRow" collapsed="true">
+ <hbox class="acctCentralRowTitleBox">
+ <description>&feedsSectionHdr.label;</description>
+ </hbox>
+ </row>
+
+ <spacer id="SubscribeRSS.spacer" flex="1" collapsed="true"/>
+ <row id="SubscribeRSS" class="acctCentralRow" collapsed="true">
+ <hbox>
+ <label class="acctCentralText acctCentralLinkText"
+ value="&subscribeFeeds.label;"
+ chromedir="&locale.dir;"
+ onclick="Subscribe();"/>
+ </hbox>
+ </row>
+
+ <spacer id="MessagesSection.spacer" class="big" flex="5" collapsed="true"/>
+
+ <row id="AccountsHeader" class="acctCentralTitleRow" collapsed="true">
+ <hbox class="acctCentralRowTitleBox">
+ <description>&accountsSectionHdr.label;</description>
+ </hbox>
+ </row>
+
+ <spacer id="AccountSettings.spacer" flex="1" collapsed="true"/>
+ <row id="AccountSettings" class="acctCentralRow" collapsed="true">
+ <hbox>
+ <label class="acctCentralText acctCentralLinkText"
+ value="&settingsLink.label;"
+ chromedir="&locale.dir;"
+ onclick="ViewSettings(null);"/>
+ </hbox>
+ </row>
+
+ <spacer id="CreateAccount.spacer" flex="1" collapsed="true"/>
+ <row id="CreateAccount" class="acctCentralRow" collapsed="true">
+ <hbox>
+#ifdef MOZ_THUNDERBIRD
+ <label class="acctCentralText"
+ value="&newAcct.label;"
+ chromedir="&locale.dir;"/>
+#else
+ <label class="acctCentralText acctCentralLinkText"
+ value="&newAcctLink.label;"
+ chromedir="&locale.dir;"
+ onclick="CreateNewAccount();"/>
+#endif
+ </hbox>
+ </row>
+#ifdef MOZ_THUNDERBIRD
+ <row id="CreateAccounts" class="acctCentralRow" collapsed="true">
+ <vbox id="CreateAccountsList">
+ <label id="CreateAccountMail"
+ class="acctCentralNewAcctText acctCentralLinkText"
+ value="&emailSectionHdr.label;"
+ chromedir="&locale.dir;"
+ onclick="CreateNewAccountTB('mail');"/>
+ <label id="CreateAccountChat"
+ class="acctCentralNewAcctText acctCentralLinkText"
+ value="&chat.label;"
+ chromedir="&locale.dir;"
+ onclick="openIMAccountWizard();"/>
+ <label id="CreateAccountNewsgroups"
+ class="acctCentralNewAcctText acctCentralLinkText"
+ value="&newsSectionHdr.label;"
+ chromedir="&locale.dir;"
+ onclick="CreateNewAccountTB('newsgroups');"/>
+ <label id="CreateAccountRSS"
+ class="acctCentralNewAcctText acctCentralLinkText"
+ value="&feedsSectionHdr.label;"
+ chromedir="&locale.dir;"
+ onclick="CreateNewAccountTB('feeds');"/>
+#ifdef HAVE_MOVEMAIL
+ <label id="CreateAccountMovemail"
+ class="acctCentralNewAcctText acctCentralLinkText"
+ value="&movemail.label;"
+ chromedir="&locale.dir;"
+ onclick="CreateNewAccountTB('movemail');"/>
+#endif
+ </vbox>
+ </row>
+#endif
+
+ <spacer id="AccountsSection.spacer" class="big" flex="5" collapsed="true"/>
+
+ <row id="AdvancedFeaturesHeader" class="acctCentralTitleRow" collapsed="true">
+ <hbox class="acctCentralRowTitleBox">
+ <description>&advFeaturesSectionHdr.label;</description>
+ </hbox>
+ </row>
+
+ <spacer id="SearchMessages.spacer" flex="1" collapsed="true"/>
+ <row id="SearchMessages" class="acctCentralRow" collapsed="true">
+ <hbox>
+ <label class="acctCentralText acctCentralLinkText"
+ value="&searchMsgsLink.label;"
+ chromedir="&locale.dir;"
+ onclick="SearchMessages();"/>
+ </hbox>
+ </row>
+
+ <spacer id="CreateFilters.spacer" flex="1" collapsed="true"/>
+ <row id="CreateFilters" class="acctCentralRow" collapsed="true">
+ <hbox>
+ <label class="acctCentralText acctCentralLinkText"
+ value="&filtersLink.label;"
+ chromedir="&locale.dir;"
+ onclick="CreateMsgFilters();"/>
+ </hbox>
+ </row>
+
+ <spacer id="SubscribeImapFolders.spacer" flex="1" collapsed="true"/>
+ <row id="SubscribeImapFolders" class="acctCentralRow" collapsed="true">
+ <hbox>
+ <label class="acctCentralText acctCentralLinkText"
+ value="&subscribeImapFolders.label;"
+ chromedir="&locale.dir;"
+ onclick="Subscribe();"/>
+ </hbox>
+ </row>
+
+ <spacer id="OfflineSettings.spacer" flex="1" collapsed="true"/>
+ <row id="OfflineSettings" class="acctCentralRow" collapsed="true">
+ <hbox>
+ <label class="acctCentralText acctCentralLinkText"
+ value="&offlineLink.label;"
+ chromedir="&locale.dir;"
+ onclick="ViewSettings('am-offline.xul');"/>
+ </hbox>
+ </row>
+ </rows>
+ </grid>
+</page>
diff --git a/mailnews/base/content/msgFolderPickerOverlay.js b/mailnews/base/content/msgFolderPickerOverlay.js
new file mode 100644
index 000000000..35b1e86cf
--- /dev/null
+++ b/mailnews/base/content/msgFolderPickerOverlay.js
@@ -0,0 +1,99 @@
+/* -*- Mode: Java; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* 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/. */
+
+Components.utils.import("resource:///modules/MailUtils.js");
+
+var gMessengerBundle;
+
+// call this from dialog onload() to set the menu item to the correct value
+function MsgFolderPickerOnLoad(pickerID)
+{
+ var uri = null;
+ try {
+ uri = window.arguments[0].preselectedURI;
+ }
+ catch (ex) {
+ uri = null;
+ }
+
+ if (uri) {
+ //dump("on loading, set titled button to " + uri + "\n");
+
+ // verify that the value we are attempting to
+ // pre-flight the menu with is valid for this
+ // picker type
+ var msgfolder = MailUtils.getFolderForURI(uri, true);
+ if (!msgfolder) return;
+
+ var verifyFunction = null;
+
+ switch (pickerID) {
+ case "msgNewFolderPicker":
+ verifyFunction = msgfolder.canCreateSubfolders;
+ break;
+ case "msgRenameFolderPicker":
+ verifyFunction = msgfolder.canRename;
+ break;
+ default:
+ verifyFunction = msgfolder.canFileMessages;
+ break;
+ }
+
+ if (verifyFunction) {
+ SetFolderPicker(uri,pickerID);
+ }
+ }
+}
+
+function PickedMsgFolder(selection,pickerID)
+{
+ var selectedUri = selection.getAttribute('id');
+ SetFolderPicker(selectedUri,pickerID);
+}
+
+function SetFolderPickerElement(uri, picker)
+{
+ var msgfolder = MailUtils.getFolderForURI(uri, true);
+
+ if (!msgfolder)
+ return;
+
+ var selectedValue = null;
+ var serverName;
+
+ if (msgfolder.isServer)
+ selectedValue = msgfolder.name;
+ else {
+ if (msgfolder.server)
+ serverName = msgfolder.server.prettyName;
+ else {
+ dump("Can't find server for " + uri + "\n");
+ serverName = "???";
+ }
+
+ switch (picker.id) {
+ case "runFiltersFolder":
+ selectedValue = msgfolder.name;
+ break;
+ case "msgTrashFolderPicker":
+ selectedValue = msgfolder.name;
+ break;
+ default:
+ if (!gMessengerBundle)
+ gMessengerBundle = document.getElementById("bundle_messenger");
+ selectedValue = gMessengerBundle.getFormattedString("verboseFolderFormat",
+ [msgfolder.name, serverName]);
+ break;
+ }
+ }
+
+ picker.setAttribute("label",selectedValue);
+ picker.setAttribute("uri",uri);
+}
+
+function SetFolderPicker(uri,pickerID)
+{
+ SetFolderPickerElement(uri, document.getElementById(pickerID));
+}
diff --git a/mailnews/base/content/msgPrintEngine.js b/mailnews/base/content/msgPrintEngine.js
new file mode 100644
index 000000000..4dc01ca6d
--- /dev/null
+++ b/mailnews/base/content/msgPrintEngine.js
@@ -0,0 +1,209 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 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/. */
+
+/* This is where functions related to the print engine are kept */
+
+Components.utils.import("resource://gre/modules/Services.jsm");
+
+/* globals for a particular window */
+var printEngineContractID = "@mozilla.org/messenger/msgPrintEngine;1";
+var printEngineWindow;
+var printEngine;
+var printSettings = null;
+var printOpener = null;
+
+var kMsgBundle = "chrome://messenger/locale/messenger.properties";
+
+/* Functions related to startup */
+function OnLoadPrintEngine()
+{
+ PrintEngineCreateGlobals();
+ InitPrintEngineWindow();
+ printEngine.startPrintOperation(printSettings);
+}
+
+function PrintEngineCreateGlobals()
+{
+ /* get the print engine instance */
+ printEngine = Components.classes[printEngineContractID].createInstance();
+ printEngine = printEngine.QueryInterface(Components.interfaces.nsIMsgPrintEngine);
+ printSettings = PrintUtils.getPrintSettings();
+ if (printSettings) {
+ printSettings.isCancelled = false;
+ }
+}
+
+var PrintPreviewListener = {
+ getPrintPreviewBrowser: function () {
+ var browser = document.getElementById("ppBrowser");
+ if (!browser) {
+ browser = document.createElement("browser");
+ browser.setAttribute("id", "ppBrowser");
+ browser.setAttribute("flex", "1");
+ browser.setAttribute("disablehistory", "true");
+ browser.setAttribute("disablesecurity", "true");
+ browser.setAttribute("type", "content");
+ document.documentElement.appendChild(browser);
+ }
+ return browser;
+ },
+ getSourceBrowser: function () {
+ return document.getElementById("content");
+ },
+ getNavToolbox: function () {
+ return document.getElementById("content");
+ },
+ onEnter: function () {
+ setPPTitle(document.getElementById("content").contentDocument.title);
+ document.getElementById("content").collapsed = true;
+ printEngine.showWindow(true);
+ },
+ onExit: function () {
+ window.close();
+ }
+};
+
+function getBundle(aURI)
+{
+ if (!aURI)
+ return null;
+
+ var bundle = null;
+ try
+ {
+ bundle = Services.strings.createBundle(aURI);
+ }
+ catch (ex)
+ {
+ bundle = null;
+ debug("Exception getting bundle " + aURI + ": " + ex);
+ }
+
+ return bundle;
+}
+
+function setPPTitle(aTitle)
+{
+ var title = aTitle;
+ try {
+ var gBrandBundle = document.getElementById("bundle_brand");
+ if (gBrandBundle) {
+ var msgBundle = this.getBundle(kMsgBundle);
+ if (msgBundle) {
+ var brandStr = gBrandBundle.getString("brandShortName")
+ var array = [title, brandStr];
+ title = msgBundle.formatStringFromName("PreviewTitle", array, array.length);
+ }
+ }
+ } catch (e) {}
+ document.title = title;
+}
+
+// Pref listener constants
+var gStartupPPObserver =
+{
+ observe: function(subject, topic, prefName)
+ {
+ PrintUtils.printPreview(PrintPreviewListener);
+ }
+};
+
+function ReplaceWithSelection()
+{
+ if (!printOpener.content)
+ return;
+
+ var selection = printOpener.content.getSelection();
+
+ if ( selection != "" ) {
+ var range = selection.getRangeAt( 0 );
+ var contents = range.cloneContents();
+
+ var aBody = window.content.document.querySelector( "body" );
+
+ /* Replace the content of <body> with the users' selection. */
+ if ( aBody ) {
+ aBody.innerHTML = "";
+ aBody.appendChild( contents );
+ }
+ }
+}
+
+function InitPrintEngineWindow()
+{
+ /* Store the current opener for later access in ReplaceWithSelection() */
+ printOpener = opener;
+
+ /* Register the event listener to be able to replace the document
+ * content with the user selection when loading is finished.
+ */
+ document.getElementById("content").addEventListener("load", ReplaceWithSelection, true);
+
+ /* Tell the nsIPrintEngine object what window is rendering the email */
+ printEngine.setWindow(window);
+
+ /* hide the printEngine window. see bug #73995 */
+
+ /* See if we got arguments.
+ * Window was opened via window.openDialog. Copy argument
+ * and perform compose initialization
+ */
+ if ( window.arguments && window.arguments[0] != null ) {
+ var numSelected = window.arguments[0];
+ var uriArray = window.arguments[1];
+ var statusFeedback = window.arguments[2];
+
+ if (window.arguments[3]) {
+ printEngine.doPrintPreview = window.arguments[3];
+ } else {
+ printEngine.doPrintPreview = false;
+ }
+ printEngine.showWindow(false);
+
+ if (window.arguments.length > 4) {
+ printEngine.setMsgType(window.arguments[4]);
+ } else {
+ printEngine.setMsgType(Components.interfaces.nsIMsgPrintEngine.MNAB_START);
+ }
+
+ if (window.arguments.length > 5) {
+ printEngine.setParentWindow(window.arguments[5]);
+ } else {
+ printEngine.setParentWindow(null);
+ }
+
+ printEngine.setStatusFeedback(statusFeedback);
+ printEngine.setStartupPPObserver(gStartupPPObserver);
+
+ if (numSelected > 0) {
+ printEngine.setPrintURICount(numSelected);
+ for (var i = 0; i < numSelected; i++) {
+ printEngine.addPrintURI(uriArray[i]);
+ //dump(uriArray[i] + "\n");
+ }
+ }
+ }
+}
+
+function ClearPrintEnginePane()
+{
+ if (window.frames["content"].location.href != "about:blank")
+ window.frames["content"].location.href = "about:blank";
+}
+
+function StopUrls()
+{
+ printEngine.stopUrls();
+}
+
+function PrintEnginePrint()
+{
+ printEngineWindow = window.openDialog("chrome://messenger/content/msgPrintEngine.xul", "", "chrome,dialog=no,all,centerscreen", false);
+}
+
+function PrintEnginePrintPreview()
+{
+ printEngineWindow = window.openDialog("chrome://messenger/content/msgPrintEngine.xul", "", "chrome,dialog=no,all,centerscreen", true);
+}
diff --git a/mailnews/base/content/msgSynchronize.js b/mailnews/base/content/msgSynchronize.js
new file mode 100644
index 000000000..250c8a26c
--- /dev/null
+++ b/mailnews/base/content/msgSynchronize.js
@@ -0,0 +1,199 @@
+/* -*- Mode: Java; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * 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/. */
+
+Components.utils.import("resource://gre/modules/Services.jsm");
+
+var gSynchronizeTree = null;
+var gParentMsgWindow;
+var gMsgWindow;
+
+var gInitialFolderStates = {};
+
+function OnLoad()
+{
+ if (window.arguments && window.arguments[0]) {
+ if (window.arguments[0].msgWindow) {
+ gParentMsgWindow = window.arguments[0].msgWindow;
+ }
+ }
+
+ document.getElementById("syncMail").checked =
+ Services.prefs.getBoolPref("mailnews.offline_sync_mail");
+ document.getElementById("syncNews").checked =
+ Services.prefs.getBoolPref("mailnews.offline_sync_news");
+ document.getElementById("sendMessage").checked =
+ Services.prefs.getBoolPref("mailnews.offline_sync_send_unsent");
+ document.getElementById("workOffline").checked =
+ Services.prefs.getBoolPref("mailnews.offline_sync_work_offline");
+
+ return true;
+}
+
+function syncOkButton()
+{
+
+ var syncMail = document.getElementById("syncMail").checked;
+ var syncNews = document.getElementById("syncNews").checked;
+ var sendMessage = document.getElementById("sendMessage").checked;
+ var workOffline = document.getElementById("workOffline").checked;
+
+ Services.prefs.setBoolPref("mailnews.offline_sync_mail", syncMail);
+ Services.prefs.setBoolPref("mailnews.offline_sync_news", syncNews);
+ Services.prefs.setBoolPref("mailnews.offline_sync_send_unsent", sendMessage);
+ Services.prefs.setBoolPref("mailnews.offline_sync_work_offline", workOffline);
+
+ if (syncMail || syncNews || sendMessage || workOffline) {
+ var offlineManager = Components.classes["@mozilla.org/messenger/offline-manager;1"]
+ .getService(Components.interfaces.nsIMsgOfflineManager);
+ if(offlineManager)
+ offlineManager.synchronizeForOffline(syncNews, syncMail, sendMessage, workOffline, gParentMsgWindow)
+ }
+
+ return true;
+}
+
+function OnSelect()
+{
+ top.window.openDialog("chrome://messenger/content/msgSelectOffline.xul", "",
+ "centerscreen,chrome,modal,titlebar,resizable=yes");
+ return true;
+}
+
+function selectOkButton()
+{
+ return true;
+}
+
+function selectCancelButton()
+{
+ var RDF = Components.classes["@mozilla.org/rdf/rdf-service;1"]
+ .getService(Components.interfaces.nsIRDFService);
+ for (var resourceValue in gInitialFolderStates) {
+ var resource = RDF.GetResource(resourceValue);
+ var folder = resource.QueryInterface(Components.interfaces.nsIMsgFolder);
+ if (gInitialFolderStates[resourceValue])
+ folder.setFlag(Components.interfaces.nsMsgFolderFlags.Offline);
+ else
+ folder.clearFlag(Components.interfaces.nsMsgFolderFlags.Offline);
+ }
+ return true;
+}
+
+function selectOnLoad()
+{
+ gMsgWindow = Components.classes["@mozilla.org/messenger/msgwindow;1"]
+ .createInstance(Components.interfaces.nsIMsgWindow);
+ gMsgWindow.domWindow = window;
+ gMsgWindow.rootDocShell.appType = Components.interfaces.nsIDocShell.APP_TYPE_MAIL;
+
+ gSynchronizeTree = document.getElementById('synchronizeTree');
+
+ SortSynchronizePane('folderNameCol', '?folderTreeNameSort');
+}
+
+function SortSynchronizePane(column, sortKey)
+{
+ var node = FindInWindow(window, column);
+ if(!node) {
+ dump('Couldnt find sort column\n');
+ return;
+ }
+
+ node.setAttribute("sort", sortKey);
+ node.setAttribute("sortDirection", "natural");
+ var col = gSynchronizeTree.columns[column];
+ gSynchronizeTree.view.cycleHeader(col);
+}
+
+function FindInWindow(currentWindow, id)
+{
+ var item = currentWindow.document.getElementById(id);
+ if(item)
+ return item;
+
+ for(var i = 0; i < currentWindow.frames.length; i++) {
+ var frameItem = FindInWindow(currentWindow.frames[i], id);
+ if(frameItem)
+ return frameItem;
+ }
+
+ return null;
+}
+
+
+function onSynchronizeClick(event)
+{
+ // we only care about button 0 (left click) events
+ if (event.button != 0)
+ return;
+
+ var row = {}
+ var col = {}
+ var elt = {}
+
+ gSynchronizeTree.treeBoxObject.getCellAt(event.clientX, event.clientY, row, col, elt);
+ if (row.value == -1)
+ return;
+
+ if (elt.value == "twisty") {
+ var folderResource = GetFolderResource(gSynchronizeTree, row.value);
+ var folder = folderResource.QueryInterface(Components.interfaces.nsIMsgFolder);
+
+ if (!(gSynchronizeTree.treeBoxObject.view.isContainerOpen(row.value))) {
+ var serverType = folder.server.type;
+ // imap is the only server type that does folder discovery
+ if (serverType != "imap") return;
+
+ if (folder.isServer) {
+ var server = folder.server;
+ server.performExpand(gMsgWindow);
+ }
+ else {
+ var imapFolder = folderResource.QueryInterface(Components.interfaces.nsIMsgImapMailFolder);
+ if (imapFolder) {
+ imapFolder.performExpand(gMsgWindow);
+ }
+ }
+ }
+ }
+ else {
+ if (col.value.id == "syncCol") {
+ UpdateNode(GetFolderResource(gSynchronizeTree, row.value), row.value);
+ }
+ }
+}
+
+function onSynchronizeTreeKeyPress(event)
+{
+ // for now, only do something on space key
+ if (event.charCode != KeyEvent.DOM_VK_SPACE)
+ return;
+
+ var treeSelection = gSynchronizeTree.view.selection;
+ for (var i=0;i<treeSelection.getRangeCount();i++) {
+ var start = {}, end = {};
+ treeSelection.getRangeAt(i,start,end);
+ for (var k=start.value;k<=end.value;k++)
+ UpdateNode(GetFolderResource(gSynchronizeTree, k), k);
+ }
+}
+
+function UpdateNode(resource, row)
+{
+ var folder = resource.QueryInterface(Components.interfaces.nsIMsgFolder);
+
+ if (folder.isServer)
+ return;
+
+ if (!(resource.Value in gInitialFolderStates)) {
+ gInitialFolderStates[resource.Value] = folder.getFlag(Components.interfaces.nsMsgFolderFlags.Offline);
+ }
+
+ folder.toggleFlag(Components.interfaces.nsMsgFolderFlags.Offline);
+}
+
+function GetFolderResource(aTree, aIndex) {
+ return aTree.builderView.getResourceAtIndex(aIndex);
+}
diff --git a/mailnews/base/content/msgSynchronize.xul b/mailnews/base/content/msgSynchronize.xul
new file mode 100644
index 000000000..e3cf90c2c
--- /dev/null
+++ b/mailnews/base/content/msgSynchronize.xul
@@ -0,0 +1,43 @@
+<?xml version="1.0"?>
+
+<!--
+
+ 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/. -->
+
+<?xml-stylesheet href="chrome://messenger/skin/dialogs.css" type="text/css"?>
+
+<!DOCTYPE dialog SYSTEM "chrome://messenger/locale/msgSynchronize.dtd" >
+<dialog xmlns:NC="http://home.netscape.com/NC-rdf#"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ id="msg-synchronizer"
+ title="&MsgSynchronize.label;"
+ onload="OnLoad();"
+ style="width: 35em;"
+ ondialogaccept="return syncOkButton();">
+
+<script type="application/javascript" src="chrome://messenger/content/msgSynchronize.js"/>
+
+ <keyset id="keyset"/>
+ <label hidden="true" wsm_persist="true" id="server.type"/>
+
+ <description class="desc">&MsgSyncDesc.label;</description>
+ <separator class="thin"/>
+ <label value="&MsgSyncDirections.label;"/>
+
+ <vbox class="indent" align="start">
+ <checkbox id="syncMail" hidable="true" hidefor="movemail,pop3" label="&syncTypeMail.label;"
+ accesskey="&syncTypeMail.accesskey;"/>
+ <checkbox id="syncNews" label="&syncTypeNews.label;" accesskey="&syncTypeNews.accesskey;"/>
+ </vbox>
+ <vbox align="start">
+ <checkbox id="sendMessage" label="&sendMessage.label;" accesskey="&sendMessage.accesskey;"/>
+ <checkbox id="workOffline" label="&workOffline.label;" accesskey="&workOffline.accesskey;"/>
+ </vbox>
+ <separator class="thin"/>
+ <hbox align="right">
+ <button id="select" label="&selectButton.label;" accesskey="&selectButton.accesskey;"
+ oncommand="OnSelect();"/>
+ </hbox>
+</dialog>
diff --git a/mailnews/base/content/newFolderDialog.js b/mailnews/base/content/newFolderDialog.js
new file mode 100644
index 000000000..31a24c286
--- /dev/null
+++ b/mailnews/base/content/newFolderDialog.js
@@ -0,0 +1,85 @@
+/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 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 FOLDERS = 1;
+var MESSAGES = 2;
+var dialog;
+
+function onLoad()
+{
+ var windowArgs = window.arguments[0];
+
+ dialog = {};
+
+ dialog.nameField = document.getElementById("name");
+ dialog.nameField.focus();
+
+ // call this when OK is pressed
+ dialog.okCallback = windowArgs.okCallback;
+
+ // pre select the folderPicker, based on what they selected in the folder pane
+ dialog.folder = windowArgs.folder;
+ try {
+ document.getElementById("MsgNewFolderPopup").selectFolder(windowArgs.folder);
+ } catch(ex) {
+ // selected a child folder
+ document.getElementById("msgNewFolderPicker")
+ .setAttribute("label", windowArgs.folder.prettyName);
+ }
+
+ // can folders contain both folders and messages?
+ if (windowArgs.dualUseFolders) {
+ dialog.folderType = FOLDERS | MESSAGES;
+
+ // hide the section when folder contain both folders and messages.
+ var newFolderTypeBox = document.getElementById("newFolderTypeBox");
+ newFolderTypeBox.setAttribute("hidden", "true");
+ } else {
+ // set our folder type by calling the default selected type's oncommand
+ var selectedFolderType = document.getElementById("folderGroup").selectedItem;
+ eval(selectedFolderType.getAttribute("oncommand"));
+ }
+
+ doEnabling();
+}
+
+function onFolderSelect(event) {
+ dialog.folder = event.target._folder;
+ document.getElementById("msgNewFolderPicker")
+ .setAttribute("label", dialog.folder.prettyName);
+}
+
+function onOK()
+{
+ var name = dialog.nameField.value;
+ var uri = dialog.folder;
+
+ // do name validity check?
+
+ // make sure name ends in "/" if folder to create can only contain folders
+ if ((dialog.folderType == FOLDERS) && !name.endsWith("/"))
+ dialog.okCallback(name + "/", dialog.folder);
+ else
+ dialog.okCallback(name, dialog.folder);
+
+ return true;
+}
+
+function onFoldersOnly()
+{
+ dialog.folderType = FOLDERS;
+}
+
+function onMessagesOnly()
+{
+ dialog.folderType = MESSAGES;
+}
+
+function doEnabling()
+{
+ document.documentElement.getButton("accept").disabled = !dialog.nameField.value;
+}
+
diff --git a/mailnews/base/content/newFolderDialog.xul b/mailnews/base/content/newFolderDialog.xul
new file mode 100644
index 000000000..1587bfdae
--- /dev/null
+++ b/mailnews/base/content/newFolderDialog.xul
@@ -0,0 +1,57 @@
+<?xml version="1.0"?>
+
+<!-- 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/. -->
+
+<?xml-stylesheet href="chrome://messenger/skin/dialogs.css" type="text/css"?>
+<?xml-stylesheet href="chrome://messenger/skin/messenger.css" type="text/css"?>
+<?xml-stylesheet href="chrome://messenger/skin/folderMenus.css" type="text/css"?>
+
+<!DOCTYPE dialog [
+<!ENTITY % newFolderDTD SYSTEM "chrome://messenger/locale/newFolderDialog.dtd">%newFolderDTD;
+]>
+
+<dialog xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ title="&newFolderDialog.title;"
+ onload="onLoad();"
+ buttonlabelaccept="&accept.label;"
+ buttonaccesskeyaccept="&accept.accesskey;"
+ ondialogaccept="return onOK();">
+
+ <stringbundleset id="stringbundleset"/>
+ <script type="application/javascript" src="chrome://messenger/content/newFolderDialog.js"/>
+
+ <label value="&name.label;" accesskey="&name.accesskey;" control="name"/>
+ <textbox id="name" oninput="doEnabling();"/>
+
+ <separator/>
+
+ <label value="&description.label;" accesskey="&description.accesskey;" control="msgNewFolderPicker"/>
+
+ <menulist id="msgNewFolderPicker" class="folderMenuItem"
+ displayformat="verbose">
+ <menupopup id="MsgNewFolderPopup" type="folder" showFileHereLabel="true"
+ class="menulist-menupopup"
+ mode="newFolder"
+ oncommand="onFolderSelect(event)"/>
+ </menulist>
+
+ <vbox id="newFolderTypeBox">
+
+ <separator class="thin"/>
+
+ <label value="&folderRestriction1.label;"/>
+ <label value="&folderRestriction2.label;"/>
+
+ <separator class="thin"/>
+
+ <radiogroup id="folderGroup" orient="horizontal" class="indent">
+ <radio oncommand="onFoldersOnly();" label="&foldersOnly.label;"/>
+ <radio oncommand="onMessagesOnly();" label="&messagesOnly.label;" selected="true"/>
+ </radiogroup>
+
+ </vbox>
+
+</dialog>
diff --git a/mailnews/base/content/newmailalert.css b/mailnews/base/content/newmailalert.css
new file mode 100644
index 000000000..52746fae0
--- /dev/null
+++ b/mailnews/base/content/newmailalert.css
@@ -0,0 +1,35 @@
+/* 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/. */
+
+#alertContainer {
+ opacity: 0;
+}
+
+#alertContainer[noanimation] {
+ opacity: 1;
+}
+
+#alertContainer[fade-in] {
+ animation-timing-function: ease-out;
+ animation-duration: 2s;
+ animation-fill-mode: both;
+ animation-name: fade-in;
+}
+
+@keyframes fade-in {
+ from {opacity: 0;}
+ to {opacity: 1;}
+}
+
+#alertContainer[fade-out] {
+ animation-timing-function: ease-in;
+ animation-duration: 2s;
+ animation-fill-mode: both;
+ animation-name: fade-out;
+}
+
+@keyframes fade-out {
+ from {opacity: 1;}
+ to {opacity: 0;}
+}
diff --git a/mailnews/base/content/newmailalert.js b/mailnews/base/content/newmailalert.js
new file mode 100644
index 000000000..243934092
--- /dev/null
+++ b/mailnews/base/content/newmailalert.js
@@ -0,0 +1,186 @@
+/* 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/. */
+
+Components.utils.import("resource://gre/modules/Services.jsm");
+Components.utils.import("resource:///modules/iteratorUtils.jsm");
+
+// Copied from nsILookAndFeel.h, see comments on eMetric_AlertNotificationOrigin
+var NS_ALERT_HORIZONTAL = 1;
+var NS_ALERT_LEFT = 2;
+var NS_ALERT_TOP = 4;
+
+var gNumNewMsgsToShowInAlert = 4; // the more messages we show in the alert, the larger it will be
+var gOpenTime = 4000; // total time the alert should stay up once we are done animating.
+
+var gAlertListener = null;
+var gPendingPreviewFetchRequests = 0;
+var gUserInitiated = false;
+var gOrigin = 0; // Default value: alert from bottom right.
+
+function prefillAlertInfo()
+{
+ const Ci = Components.interfaces;
+ // unwrap all the args....
+ // arguments[0] --> nsIArray of folders with new mail
+ // arguments[1] --> the observer to call back with notifications about the alert
+ // arguments[2] --> user initiated boolean. true if the user initiated opening the alert
+ // (which means skip the fade effect and don't auto close the alert)
+ // arguments[3] --> the alert origin returned by the look and feel
+ var foldersWithNewMail = window.arguments[0];
+ gAlertListener = window.arguments[1];
+ gUserInitiated = window.arguments[2];
+ gOrigin = window.arguments[3];
+
+ // For now just grab the first folder which should be a root folder
+ // for the account that has new mail. If we can't find a folder, just
+ // return to avoid the exception and empty dialog in upper left-hand corner.
+ if (!foldersWithNewMail || foldersWithNewMail.length < 1)
+ return;
+ let rootFolder = foldersWithNewMail.queryElementAt(0, Ci.nsIWeakReference)
+ .QueryReferent(Ci.nsIMsgFolder);
+
+ // Generate an account label string based on the root folder.
+ var label = document.getElementById('alertTitle');
+ var totalNumNewMessages = rootFolder.getNumNewMessages(true);
+ var message = totalNumNewMessages == 1 ? "newMailNotification_message"
+ : "newMailNotification_messages";
+ label.value = document.getElementById('bundle_messenger')
+ .getFormattedString(message,
+ [rootFolder.prettiestName,
+ totalNumNewMessages]);
+
+ // This is really the root folder and we have to walk through the list to
+ // find the real folder that has new mail in it...:(
+ let allFolders = rootFolder.descendants;
+ var folderSummaryInfoEl = document.getElementById('folderSummaryInfo');
+ folderSummaryInfoEl.mMaxMsgHdrsInPopup = gNumNewMsgsToShowInAlert;
+ for (let folder in fixIterator(allFolders, Components.interfaces.nsIMsgFolder))
+ {
+ if (folder.hasNewMessages && !folder.getFlag(Ci.nsMsgFolderFlags.Virtual))
+ {
+ var asyncFetch = {};
+ folderSummaryInfoEl.parseFolder(folder, new urlListener(folder), asyncFetch);
+ if (asyncFetch.value)
+ gPendingPreviewFetchRequests++;
+ }
+ }
+}
+
+function urlListener(aFolder)
+{
+ this.mFolder = aFolder;
+}
+
+urlListener.prototype =
+{
+ OnStartRunningUrl: function(aUrl)
+ {
+ },
+
+ OnStopRunningUrl: function(aUrl, aExitCode)
+ {
+ var folderSummaryInfoEl = document.getElementById('folderSummaryInfo');
+ folderSummaryInfoEl.parseFolder(this.mFolder, null, {});
+ gPendingPreviewFetchRequests--;
+
+ // when we are done running all of our urls for fetching the preview text,
+ // start the alert.
+ if (!gPendingPreviewFetchRequests)
+ showAlert();
+ }
+}
+
+function onAlertLoad()
+{
+ prefillAlertInfo();
+ // read out our initial settings from prefs.
+ try
+ {
+ gOpenTime = Services.prefs.getIntPref("alerts.totalOpenTime");
+ } catch (ex) {}
+
+ // bogus call to make sure the window is moved offscreen until we are ready for it.
+ resizeAlert(true);
+
+ // if we aren't waiting to fetch preview text, then go ahead and
+ // start showing the alert.
+ if (!gPendingPreviewFetchRequests)
+ setTimeout(showAlert, 0); // let the JS thread unwind, to give layout
+ // a chance to recompute the styles and widths for our alert text.
+}
+
+// If the user initiated the alert, show it right away, otherwise start opening the alert with
+// the fade effect.
+function showAlert()
+{
+ if (!document.getElementById("folderSummaryInfo").hasMessages) {
+ closeAlert(); // no mail, so don't bother showing the alert...
+ return;
+ }
+
+ // resize the alert based on our current content
+ resizeAlert(false);
+
+ var alertContainer = document.getElementById("alertContainer");
+ // Don't fade in if the user opened the alert or the pref is true.
+ if (gUserInitiated ||
+ Services.prefs.getBoolPref("alerts.disableSlidingEffect")) {
+ alertContainer.setAttribute("noanimation", true);
+ setTimeout(closeAlert, gOpenTime);
+ return;
+ }
+
+ alertContainer.addEventListener("animationend", function hideAlert(event) {
+ if (event.animationName == "fade-in") {
+ alertContainer.removeEventListener("animationend", hideAlert, false);
+ let remaining = Math.max(Math.round(gOpenTime - event.elapsedTime * 1000), 0);
+ setTimeout(fadeOutAlert, remaining);
+ }
+ }, false);
+ alertContainer.setAttribute("fade-in", true);
+}
+
+function resizeAlert(aMoveOffScreen)
+{
+ var alertTextBox = document.getElementById("alertTextBox");
+ var alertImageBox = document.getElementById("alertImageBox");
+ alertImageBox.style.minHeight = alertTextBox.scrollHeight + "px";
+
+ sizeToContent();
+
+ // leftover hack to get the window properly hidden when we first open it
+ if (aMoveOffScreen)
+ window.outerHeight = 1;
+
+ // Determine position
+ var x = gOrigin & NS_ALERT_LEFT ? screen.availLeft :
+ screen.availLeft + screen.availWidth - window.outerWidth;
+ var y = gOrigin & NS_ALERT_TOP ? screen.availTop :
+ screen.availTop + screen.availHeight - window.outerHeight;
+
+ // Offset the alert by 10 pixels from the edge of the screen
+ y += gOrigin & NS_ALERT_TOP ? 10 : -10;
+ x += gOrigin & NS_ALERT_LEFT ? 10 : -10;
+
+ window.moveTo(x, y);
+}
+
+function fadeOutAlert()
+{
+ var alertContainer = document.getElementById("alertContainer");
+ alertContainer.addEventListener("animationend", function fadeOut(event) {
+ if (event.animationName == "fade-out") {
+ alertContainer.removeEventListener("animationend", fadeOut, false);
+ closeAlert();
+ }
+ }, false);
+ alertContainer.setAttribute("fade-out", true);
+}
+
+function closeAlert()
+{
+ if (gAlertListener)
+ gAlertListener.observe(null, "alertfinished", "");
+ window.close();
+}
diff --git a/mailnews/base/content/newmailalert.xul b/mailnews/base/content/newmailalert.xul
new file mode 100644
index 000000000..2df37494f
--- /dev/null
+++ b/mailnews/base/content/newmailalert.xul
@@ -0,0 +1,35 @@
+<?xml version="1.0"?>
+
+<!-- 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/. -->
+
+<?xml-stylesheet href="chrome://messenger/content/newmailalert.css" type="text/css"?>
+<?xml-stylesheet href="chrome://messenger/skin/newmailalert.css" type="text/css"?>
+
+<window id="newMailAlertNotification"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ windowtype="alert:alert"
+ role="alert"
+ align="start"
+ onload="onAlertLoad()">
+
+ <stringbundle id="bundle_messenger" src="chrome://messenger/locale/messenger.properties"/>
+ <script type="application/javascript" src="chrome://messenger/content/newmailalert.js"/>
+
+ <stack id="alertContainer" mousethrough="always">
+ <hbox id="alertBox">
+ <hbox id ="alertImageBox" align="center" pack="center">
+ <image id="alertImage"/>
+ </hbox>
+
+ <vbox id="alertTextBox">
+ <label id="alertTitle"/>
+ <separator id="alertGroove" class="groove"/>
+ <folderSummary id="folderSummaryInfo" mousethrough="never"/>
+ </vbox>
+ </hbox>
+
+ <toolbarbutton id="closeButton" top="0" right="0" onclick="closeAlert();"/>
+ </stack>
+</window>
diff --git a/mailnews/base/content/renameFolderDialog.js b/mailnews/base/content/renameFolderDialog.js
new file mode 100644
index 000000000..74a176249
--- /dev/null
+++ b/mailnews/base/content/renameFolderDialog.js
@@ -0,0 +1,47 @@
+/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 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 dialog;
+
+function onLoad()
+{
+ var windowArgs = window.arguments[0];
+
+ dialog = {};
+
+ dialog.OKButton = document.documentElement.getButton("accept");
+
+ dialog.nameField = document.getElementById("name");
+ dialog.nameField.value = windowArgs.name;
+ dialog.nameField.select();
+ dialog.nameField.focus();
+
+ // call this when OK is pressed
+ dialog.okCallback = windowArgs.okCallback;
+
+ // pre select the folderPicker, based on what they selected in the folder pane
+ dialog.preselectedFolderURI = windowArgs.preselectedURI;
+
+ doEnabling();
+}
+
+function onOK()
+{
+ dialog.okCallback(dialog.nameField.value, dialog.preselectedFolderURI);
+
+ return true;
+}
+
+function doEnabling()
+{
+ if (dialog.nameField.value) {
+ if (dialog.OKButton.disabled)
+ dialog.OKButton.disabled = false;
+ } else {
+ if (!dialog.OKButton.disabled)
+ dialog.OKButton.disabled = true;
+ }
+}
diff --git a/mailnews/base/content/renameFolderDialog.xul b/mailnews/base/content/renameFolderDialog.xul
new file mode 100644
index 000000000..d94d62204
--- /dev/null
+++ b/mailnews/base/content/renameFolderDialog.xul
@@ -0,0 +1,23 @@
+<?xml version="1.0"?> <!-- -*- Mode: xml; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 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/. -->
+
+<?xml-stylesheet href="chrome://messenger/skin/dialogs.css" type="text/css"?>
+
+<!DOCTYPE dialog SYSTEM "chrome://messenger/locale/renameFolderDialog.dtd">
+
+<dialog xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ title="&renameFolderDialog.title;"
+ buttonlabelaccept="&accept.label;"
+ buttonaccesskeyaccept="&accept.accesskey;"
+ ondialogaccept="return onOK();"
+ onload="onLoad();">
+
+ <stringbundleset id="stringbundleset"/>
+ <script type="application/javascript" src="chrome://messenger/content/renameFolderDialog.js"/>
+
+ <label value="&rename.label;" accesskey="&rename.accesskey;" control="name"/>
+ <textbox id="name" oninput="doEnabling();"/>
+</dialog>
diff --git a/mailnews/base/content/retention.js b/mailnews/base/content/retention.js
new file mode 100644
index 000000000..1db195b81
--- /dev/null
+++ b/mailnews/base/content/retention.js
@@ -0,0 +1,45 @@
+/*
+ * -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 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/. */
+
+
+function initCommonRetentionSettings(retentionSettings)
+{
+ document.getElementById("retention.keepMsg").value = retentionSettings.retainByPreference;
+ document.getElementById("retention.keepOldMsgMin").value =
+ (retentionSettings.daysToKeepHdrs > 0) ? retentionSettings.daysToKeepHdrs : 30;
+ document.getElementById("retention.keepNewMsgMin").value =
+ (retentionSettings.numHeadersToKeep > 0) ? retentionSettings.numHeadersToKeep : 2000;
+
+ document.getElementById("retention.applyToFlagged").checked =
+ !retentionSettings.applyToFlaggedMessages;
+}
+
+function saveCommonRetentionSettings(aRetentionSettings)
+{
+ aRetentionSettings.retainByPreference = document.getElementById("retention.keepMsg").value;
+
+ aRetentionSettings.daysToKeepHdrs = document.getElementById("retention.keepOldMsgMin").value;
+ aRetentionSettings.numHeadersToKeep = document.getElementById("retention.keepNewMsgMin").value;
+
+ aRetentionSettings.applyToFlaggedMessages =
+ !document.getElementById("retention.applyToFlagged").checked;
+
+ return aRetentionSettings;
+}
+
+function onCheckKeepMsg()
+{
+ if (gLockedPref && gLockedPref["retention.keepMsg"]) {
+ // if the pref associated with the radiobutton is locked, as indicated
+ // by the gLockedPref, skip this function. All elements in this
+ // radiogroup have been locked by the function onLockPreference.
+ return;
+ }
+
+ var keepMsg = document.getElementById("retention.keepMsg").value;
+ document.getElementById("retention.keepOldMsgMin").disabled = keepMsg != 2;
+ document.getElementById("retention.keepNewMsgMin").disabled = keepMsg != 3;
+}
diff --git a/mailnews/base/content/shareglue.js b/mailnews/base/content/shareglue.js
new file mode 100644
index 000000000..29026033c
--- /dev/null
+++ b/mailnews/base/content/shareglue.js
@@ -0,0 +1,20 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * 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/. */
+
+/*
+ * code in here is generic, shared utility code across all messenger
+ * components. There should be no command or widget specific code here
+ */
+
+function MessengerSetForcedCharacterSet(aCharset)
+{
+ messenger.setDocumentCharset(aCharset);
+ msgWindow.mailCharacterSet = aCharset;
+ msgWindow.charsetOverride = true;
+
+ // DO NOT try to reload the message here. we do this automatically now in
+ // messenger.SetDocumentCharset. You'll just break things and reak havoc
+ // if you call ReloadMessage() here...
+}
diff --git a/mailnews/base/content/shutdownWindow.js b/mailnews/base/content/shutdownWindow.js
new file mode 100644
index 000000000..e7b853eec
--- /dev/null
+++ b/mailnews/base/content/shutdownWindow.js
@@ -0,0 +1,99 @@
+/*
+# 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 curTaskIndex = 0;
+var numTasks = 0;
+var stringBundle;
+
+var msgShutdownService = Components.classes["@mozilla.org/messenger/msgshutdownservice;1"]
+ .getService(Components.interfaces.nsIMsgShutdownService);
+
+function onLoad()
+{
+ numTasks = msgShutdownService.getNumTasks();
+
+ stringBundle = document.getElementById("bundle_shutdown");
+ document.title = stringBundle.getString("shutdownDialogTitle");
+
+ updateTaskProgressLabel(1);
+ updateProgressMeter(0);
+
+ msgShutdownService.startShutdownTasks();
+}
+
+function updateProgressLabel(inTaskName)
+{
+ var curTaskLabel = document.getElementById("shutdownStatus_label");
+ curTaskLabel.value = inTaskName;
+}
+
+function updateTaskProgressLabel(inCurTaskNum)
+{
+ var taskProgressLabel = document.getElementById("shutdownTask_label");
+ taskProgressLabel.value = stringBundle.getFormattedString("taskProgress", [inCurTaskNum, numTasks]);
+}
+
+function updateProgressMeter(inPercent)
+{
+ var taskProgressmeter = document.getElementById('shutdown_progressmeter');
+ taskProgressmeter.value = inPercent;
+}
+
+function onCancel()
+{
+ msgShutdownService.cancelShutdownTasks();
+}
+
+function nsMsgShutdownTaskListener()
+{
+ msgShutdownService.setShutdownListener(this);
+}
+
+nsMsgShutdownTaskListener.prototype =
+{
+ QueryInterface : function(iid)
+ {
+ if (iid.equals(Components.interfaces.nsIWebProgressListener) ||
+ iid.equals(Components.interfaces.nsISupportsWeakReference) ||
+ iid.equals(Components.interfaces.nsISupports))
+ return this;
+
+ throw Components.results.NS_NOINTERFACE;
+ },
+
+ onStateChange: function(aWebProgress, aRequest, aStateFlags, aStatus)
+ {
+ if (aStateFlags & Components.interfaces.nsIWebProgressListener.STATE_STOP)
+ {
+ window.close();
+ }
+ },
+
+ onProgressChange: function(aWebProgress, aRequest, aCurSelfProgress, aMaxSelfProgress, aCurTotalProgress, aMaxTotalProgress)
+ {
+ updateProgressMeter(((aCurTotalProgress / aMaxTotalProgress) * 100));
+ updateTaskProgressLabel(aCurTotalProgress + 1);
+ },
+
+ onLocationChange: function(aWebProgress, aRequest, aLocation, aFlags)
+ {
+ // we can ignore this notification
+ },
+
+ onStatusChange: function(aWebProgress, aRequest, aStatus, aMessage)
+ {
+ if (aMessage)
+ updateProgressLabel(aMessage);
+ },
+
+ onSecurityChange: function(aWebProgress, aRequest, state)
+ {
+ // we can ignore this notification
+ }
+}
+
+var MsgShutdownTaskListener = new nsMsgShutdownTaskListener();
+
diff --git a/mailnews/base/content/shutdownWindow.xul b/mailnews/base/content/shutdownWindow.xul
new file mode 100644
index 000000000..3158ccc6d
--- /dev/null
+++ b/mailnews/base/content/shutdownWindow.xul
@@ -0,0 +1,31 @@
+<?xml version="1.0"?>
+<!--
+# 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/.
+-->
+
+<?xml-stylesheet href="chrome://messenger/skin/dialogs.css" type="text/css"?>
+
+<dialog xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ style="padding: 20px; width: 350px"
+ onload="onLoad()"
+ buttons="cancel"
+ ondialogcancel="return onCancel();">
+
+ <script type="application/javascript" src="chrome://messenger/content/shutdownWindow.js"/>
+ <stringbundle id="bundle_shutdown" src="chrome://messenger/locale/shutdownWindow.properties"/>
+
+ <vbox align="center">
+ <label id="shutdownStatus_label" value="" />
+ <separator class="thin" />
+ </vbox>
+
+ <progressmeter id="shutdown_progressmeter" mode="determined" />
+
+ <vbox align="center">
+ <label id="shutdownTask_label" value="" />
+ <separator class="thick" />
+ </vbox>
+
+</dialog>
diff --git a/mailnews/base/content/virtualFolderListDialog.js b/mailnews/base/content/virtualFolderListDialog.js
new file mode 100644
index 000000000..bfe3c0b69
--- /dev/null
+++ b/mailnews/base/content/virtualFolderListDialog.js
@@ -0,0 +1,136 @@
+/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 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/. */
+
+Components.utils.import("resource:///modules/mailServices.js");
+Components.utils.import("resource:///modules/iteratorUtils.jsm");
+Components.utils.import("resource:///modules/MailUtils.js");
+
+var gFolderPickerTree = null;
+
+function onLoad()
+{
+ gFolderPickerTree = document.getElementById("folderPickerTree");
+
+ if (window.arguments[0].searchFolderURIs)
+ {
+ // for each folder uri,
+ var srchFolderUriArray = window.arguments[0].searchFolderURIs.split('|');
+ // get the folder for each search URI and set the searchThisFolder flag on it
+ for (var i in srchFolderUriArray)
+ {
+ var realFolder = MailUtils.getFolderForURI(srchFolderUriArray[i]);
+ if (realFolder)
+ realFolder.setInVFEditSearchScope(true, false);
+ }
+ }
+}
+
+function onUnLoad()
+{
+ resetFolderToSearchAttribute();
+}
+
+function onOK()
+{
+ if ( window.arguments[0].okCallback )
+ window.arguments[0].okCallback(generateFoldersToSearchList());
+}
+
+function onCancel()
+{
+ // onunload will clear out the folder attributes we changed
+}
+
+function addFolderToSearchListString(aFolder, aCurrentSearchURIString)
+{
+ if (aCurrentSearchURIString)
+ aCurrentSearchURIString += '|';
+ aCurrentSearchURIString += aFolder.URI;
+
+ return aCurrentSearchURIString;
+}
+
+function processSearchSettingForFolder(aFolder, aCurrentSearchURIString)
+{
+ if (aFolder.inVFEditSearchScope)
+ aCurrentSearchURIString = addFolderToSearchListString(aFolder, aCurrentSearchURIString);
+
+ aFolder.setInVFEditSearchScope(false, false);
+ return aCurrentSearchURIString;
+}
+
+// warning: this routine also clears out the search property list from all of the msg folders
+function generateFoldersToSearchList()
+{
+ let uriSearchString = "";
+ let allFolders = MailServices.accounts.allFolders;
+ for (let folder in fixIterator(allFolders, Components.interfaces.nsIMsgFolder))
+ uriSearchString = processSearchSettingForFolder(folder, uriSearchString);
+
+ return uriSearchString;
+}
+
+function resetFolderToSearchAttribute()
+{
+ // iterates over all accounts and all folders, clearing out the inVFEditScope property in case
+ // we set it.
+ let allFolders = MailServices.accounts.allFolders;
+ for (let folder in fixIterator(allFolders, Components.interfaces.nsIMsgFolder))
+ folder.setInVFEditSearchScope(false, false);
+}
+
+function ReverseStateFromNode(row)
+{
+ var folder = GetFolderResource(row).QueryInterface(Components.interfaces.nsIMsgFolder);
+ var currentState = folder.inVFEditSearchScope;
+
+ folder.setInVFEditSearchScope(!currentState, false);
+}
+
+function GetFolderResource(rowIndex)
+{
+ return gFolderPickerTree.builder.QueryInterface(Components.interfaces.nsIXULTreeBuilder).getResourceAtIndex(rowIndex);
+}
+
+function selectFolderTreeOnClick(event)
+{
+ // we only care about button 0 (left click) events
+ if (event.button != 0 || event.originalTarget.localName != "treechildren")
+ return;
+
+ var row = {}, col = {}, obj = {};
+ gFolderPickerTree.treeBoxObject.getCellAt(event.clientX, event.clientY, row, col, obj);
+ if (row.value == -1 || row.value > (gFolderPickerTree.view.rowCount - 1))
+ return;
+
+ if (event.detail == 2) {
+ // only toggle the search folder state when double clicking something
+ // that isn't a container
+ if (!gFolderPickerTree.view.isContainer(row.value)) {
+ ReverseStateFromNode(row.value);
+ return;
+ }
+ }
+ else if (event.detail == 1)
+ {
+ if (obj.value != "twisty" && col.value.id == "selectedColumn")
+ ReverseStateFromNode(row.value)
+ }
+}
+
+function onSelectFolderTreeKeyPress(event)
+{
+ // for now, only do something on space key
+ if (event.charCode != KeyEvent.DOM_VK_SPACE)
+ return;
+
+ var treeSelection = gFolderPickerTree.view.selection;
+ for (var i=0;i<treeSelection.getRangeCount();i++) {
+ var start = {}, end = {};
+ treeSelection.getRangeAt(i,start,end);
+ for (var k=start.value;k<=end.value;k++)
+ ReverseStateFromNode(k);
+ }
+}
diff --git a/mailnews/base/content/virtualFolderListDialog.xul b/mailnews/base/content/virtualFolderListDialog.xul
new file mode 100644
index 000000000..7dcc5e8fe
--- /dev/null
+++ b/mailnews/base/content/virtualFolderListDialog.xul
@@ -0,0 +1,91 @@
+<?xml version="1.0"?>
+<!-- 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/.
+ -->
+
+<?xml-stylesheet href="chrome://messenger/skin/virtualFolderListDialog.css" type="text/css"?>
+
+<!DOCTYPE window SYSTEM "chrome://messenger/locale/virtualFolderListDialog.dtd">
+
+<dialog id="searchFolderWindow"
+ xmlns:NC="http://home.netscape.com/NC-rdf#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ title="&virtualFolderListTitle.title;"
+ style="width: 27em; height: 25em;"
+ persist="width height screenX screenY"
+ onload="onLoad();"
+ onunload="onUnLoad();"
+ ondialogaccept="return onOK();"
+ ondialogcancel="return onCancel();">
+
+<script type="application/javascript" src="chrome://messenger/content/virtualFolderListDialog.js"/>
+
+ <label control="folderPickerTree">&virtualFolderDesc.label;</label>
+
+ <tree id="folderPickerTree"
+ treelines="true"
+ flex="1"
+ hidecolumnpicker="true"
+ seltype="multiple"
+ disableKeyNavigation="true"
+ datasources="rdf:msgaccountmanager rdf:mailnewsfolders"
+ ref="msgaccounts:/"
+ flags="dont-build-content"
+ onkeypress="onSelectFolderTreeKeyPress(event);"
+ onclick="selectFolderTreeOnClick(event);">
+ <template>
+ <rule>
+ <conditions>
+ <content uri="?container"/>
+ <member container="?container" child="?member"/>
+ <triple subject="?member"
+ predicate="http://home.netscape.com/NC-rdf#Virtual"
+ object="false"/>
+ <triple subject="?member"
+ predicate="http://home.netscape.com/NC-rdf#IsDeferred"
+ object="false"/>
+ </conditions>
+
+ <bindings>
+ <binding subject="?member"
+ predicate="http://home.netscape.com/NC-rdf#FolderTreeName"
+ object="?folderTreeName"/>
+ <binding subject="?member"
+ predicate="http://home.netscape.com/NC-rdf#FolderTreeName?sort=true"
+ object="?folderTreeNameSort"/>
+ <binding subject="?member"
+ predicate="http://home.netscape.com/NC-rdf#ServerType"
+ object="?serverType"/>
+ <binding subject="?member"
+ predicate="http://home.netscape.com/NC-rdf#SpecialFolder"
+ object="?specialFolder"/>
+ <binding subject="?member"
+ predicate="http://home.netscape.com/NC-rdf#HasUnreadMessages"
+ object="?hasUnreadMessages"/>
+ <binding subject="?member"
+ predicate="http://home.netscape.com/NC-rdf#InVFEditSearchScope"
+ object="?inVFEditSearchScope"/>
+ </bindings>
+
+ <action>
+ <treechildren>
+ <treeitem uri="?member">
+ <treerow>
+ <treecell label="?folderTreeName" properties="ServerType-?ServerType specialFolder-?specialFolder hasUnreadMessages-?hasUnreadMessages"/>
+ <treecell properties="inVFEditSearchScope-?inVFEditSearchScope"/>/>
+ </treerow>
+ </treeitem>
+ </treechildren>
+ </action>
+ </rule>
+ </template>
+
+ <treecols>
+ <treecol id="folderNameCol" sort="?folderTreeNameSort" sortActive="true" sortDirection="ascending"
+ flex="10" primary="true" hideheader="true" crop="center"/>
+ <treecol id="selectedColumn" hideheader="true" flex="1"/>
+ </treecols>
+ </tree>
+</dialog>
diff --git a/mailnews/base/content/virtualFolderProperties.js b/mailnews/base/content/virtualFolderProperties.js
new file mode 100644
index 000000000..b9a984bd1
--- /dev/null
+++ b/mailnews/base/content/virtualFolderProperties.js
@@ -0,0 +1,266 @@
+/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 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 gPickedFolder;
+var gMailView = null;
+var msgWindow; // important, don't change the name of this variable. it's really a global used by commandglue.js
+var gSearchTermSession; // really an in memory temporary filter we use to read in and write out the search terms
+var gSearchFolderURIs = "";
+var gMessengerBundle = null;
+
+var nsMsgSearchScope = Components.interfaces.nsMsgSearchScope;
+
+Components.utils.import("resource://gre/modules/Services.jsm");
+Components.utils.import("resource://gre/modules/PluralForm.jsm");
+Components.utils.import("resource:///modules/mailServices.js");
+Components.utils.import("resource:///modules/virtualFolderWrapper.js");
+Components.utils.import("resource:///modules/iteratorUtils.jsm");
+Components.utils.import("resource:///modules/MailUtils.js");
+
+function onLoad()
+{
+ var windowArgs = window.arguments[0];
+ var acceptButton = document.documentElement.getButton("accept");
+
+ gMessengerBundle = document.getElementById("bundle_messenger");
+
+ // call this when OK is pressed
+ msgWindow = windowArgs.msgWindow;
+
+ initializeSearchWidgets();
+
+ setSearchScope(nsMsgSearchScope.offlineMail);
+ if (windowArgs.editExistingFolder)
+ {
+ acceptButton.label =
+ document.documentElement.getAttribute("editFolderAcceptButtonLabel");
+ acceptButton.accesskey =
+ document.documentElement.getAttribute("editFolderAcceptButtonAccessKey");
+ InitDialogWithVirtualFolder(windowArgs.folder);
+ }
+ else // we are creating a new virtual folder
+ {
+ acceptButton.label =
+ document.documentElement.getAttribute("newFolderAcceptButtonLabel");
+ acceptButton.accesskey =
+ document.documentElement.getAttribute("newFolderAcceptButtonAccessKey");
+ // it is possible that we were given arguments to pre-fill the dialog with...
+ gSearchTermSession = Components.classes["@mozilla.org/messenger/searchSession;1"]
+ .createInstance(Components.interfaces.nsIMsgSearchSession);
+
+ if (windowArgs.searchTerms) // then add them to our search session
+ {
+ for (let searchTerm in fixIterator(windowArgs.searchTerms,
+ Components.interfaces.nsIMsgSearchTerm))
+ gSearchTermSession.appendTerm(searchTerm);
+ }
+ if (windowArgs.folder)
+ {
+ // pre select the folderPicker, based on what they selected in the folder pane
+ gPickedFolder = windowArgs.folder;
+ try {
+ document.getElementById("msgNewFolderPopup").selectFolder(windowArgs.folder);
+ } catch(ex) {
+ document.getElementById("msgNewFolderPicker")
+ .setAttribute("label", windowArgs.folder.prettyName);
+ }
+
+ // if the passed in URI is not a server then pre-select it as the folder to search
+ if (!windowArgs.folder.isServer)
+ gSearchFolderURIs = windowArgs.folder.URI;
+ }
+
+ let folderNameField = document.getElementById("name");
+ folderNameField.hidden = false;
+ folderNameField.focus();
+ if (windowArgs.newFolderName)
+ folderNameField.value = windowArgs.newFolderName;
+ if (windowArgs.searchFolderURIs)
+ gSearchFolderURIs = windowArgs.searchFolderURIs;
+
+ setupSearchRows(gSearchTermSession.searchTerms);
+ doEnabling(); // we only need to disable/enable the OK button for new virtual folders
+ }
+
+ if (typeof windowArgs.searchOnline != "undefined")
+ document.getElementById('searchOnline').checked = windowArgs.searchOnline;
+ updateOnlineSearchState();
+ updateFoldersCount();
+}
+
+function setupSearchRows(aSearchTerms)
+{
+ if (aSearchTerms && aSearchTerms.Count() > 0)
+ initializeSearchRows(nsMsgSearchScope.offlineMail, aSearchTerms); // load the search terms for the folder
+ else
+ onMore(null);
+}
+
+function updateOnlineSearchState()
+{
+ var enableCheckbox = false;
+ var checkbox = document.getElementById('searchOnline');
+ // only enable the checkbox for selection, for online servers
+ var srchFolderUriArray = gSearchFolderURIs.split('|');
+ if (srchFolderUriArray[0])
+ {
+ var realFolder = MailUtils.getFolderForURI(srchFolderUriArray[0]);
+ enableCheckbox = realFolder.server.offlineSupportLevel; // anything greater than 0 is an online server like IMAP or news
+ }
+
+ if (enableCheckbox)
+ checkbox.removeAttribute('disabled');
+ else
+ {
+ checkbox.setAttribute('disabled', true);
+ checkbox.checked = false;
+ }
+}
+
+function InitDialogWithVirtualFolder(aVirtualFolder)
+{
+ let virtualFolderWrapper =
+ VirtualFolderHelper.wrapVirtualFolder(window.arguments[0].folder);
+
+ // when editing an existing folder, hide the folder picker that stores the parent location of the folder
+ document.getElementById("chooseFolderLocationRow").collapsed = true;
+ let folderNameField = document.getElementById("existingName");
+ folderNameField.hidden = false;
+
+ gSearchFolderURIs = virtualFolderWrapper.searchFolderURIs;
+ updateFoldersCount();
+ document.getElementById('searchOnline').checked = virtualFolderWrapper.onlineSearch;
+ gSearchTermSession = virtualFolderWrapper.searchTermsSession;
+
+ setupSearchRows(gSearchTermSession.searchTerms);
+
+ // set the name of the folder
+ let folderBundle = document.getElementById("bundle_folder");
+ let name = folderBundle.getFormattedString("verboseFolderFormat",
+ [aVirtualFolder.prettyName, aVirtualFolder.server.prettyName]);
+ folderNameField.setAttribute("label", name);
+ // update the window title based on the name of the saved search
+ document.title = gMessengerBundle.getFormattedString("editVirtualFolderPropertiesTitle",
+ [aVirtualFolder.prettyName]);
+}
+
+function onFolderPick(aEvent) {
+ gPickedFolder = aEvent.target._folder;
+ document.getElementById("msgNewFolderPopup")
+ .selectFolder(gPickedFolder);
+}
+
+function onOK()
+{
+ var name = document.getElementById("name").value;
+ var searchOnline = document.getElementById('searchOnline').checked;
+
+ if (!gSearchFolderURIs)
+ {
+ Services.prompt.alert(window, null,
+ gMessengerBundle.getString('alertNoSearchFoldersSelected'));
+ return false;
+ }
+
+ if (window.arguments[0].editExistingFolder)
+ {
+ // update the search terms
+ saveSearchTerms(gSearchTermSession.searchTerms, gSearchTermSession);
+ // save the settings
+ let virtualFolderWrapper =
+ VirtualFolderHelper.wrapVirtualFolder(window.arguments[0].folder);
+ virtualFolderWrapper.searchTerms = gSearchTermSession.searchTerms;
+ virtualFolderWrapper.searchFolders = gSearchFolderURIs;
+ virtualFolderWrapper.onlineSearch = searchOnline;
+ virtualFolderWrapper.cleanUpMessageDatabase();
+
+ MailServices.accounts.saveVirtualFolders();
+
+ if (window.arguments[0].onOKCallback)
+ window.arguments[0].onOKCallback(virtualFolderWrapper.virtualFolder.URI);
+ return true;
+ }
+ var uri = gPickedFolder.URI;
+ if (name && uri) // create a new virtual folder
+ {
+ // check to see if we already have a folder with the same name and alert the user if so...
+ var parentFolder = MailUtils.getFolderForURI(uri);
+
+ // sanity check the name based on the logic used by nsMsgBaseUtils.cpp. It can't start with a '.', it can't end with a '.', '~' or ' '.
+ // it can't contain a ';' or '#'.
+ if (/^\.|[\.\~ ]$|[\;\#]/.test(name))
+ {
+ Services.prompt.alert(window, null,
+ gMessengerBundle.getString("folderCreationFailed"));
+ return false;
+ }
+ else if (parentFolder.containsChildNamed(name))
+ {
+ Services.prompt.alert(window, null,
+ gMessengerBundle.getString("folderExists"));
+ return false;
+ }
+
+ saveSearchTerms(gSearchTermSession.searchTerms, gSearchTermSession);
+ VirtualFolderHelper.createNewVirtualFolder(name, parentFolder, gSearchFolderURIs,
+ gSearchTermSession.searchTerms,
+ searchOnline);
+ }
+
+ return true;
+}
+
+function doEnabling()
+{
+ var acceptButton = document.documentElement.getButton("accept");
+ acceptButton.disabled = !document.getElementById("name").value;
+}
+
+function chooseFoldersToSearch()
+{
+ // if we have some search folders already, then root the folder picker dialog off the account
+ // for those folders. Otherwise fall back to the preselectedfolderURI which is the parent folder
+ // for this new virtual folder.
+ var dialog = window.openDialog("chrome://messenger/content/virtualFolderListDialog.xul", "",
+ "chrome,titlebar,modal,centerscreen,resizable",
+ {searchFolderURIs:gSearchFolderURIs,
+ okCallback:onFolderListDialogCallback});
+}
+
+// callback routine from chooseFoldersToSearch
+function onFolderListDialogCallback(searchFolderURIs)
+{
+ gSearchFolderURIs = searchFolderURIs;
+ updateFoldersCount();
+ updateOnlineSearchState(); // we may have changed the server type we are searching...
+}
+
+function updateFoldersCount()
+{
+ let srchFolderUriArray = gSearchFolderURIs.split('|');
+ let folderCount = gSearchFolderURIs ? srchFolderUriArray.length : 0;
+ let foldersList = document.getElementById("chosenFoldersCount");
+ foldersList.textContent =
+ PluralForm.get(folderCount, gMessengerBundle.getString("virtualFolderSourcesChosen"))
+ .replace("#1", folderCount);
+ if (folderCount > 0) {
+ let folderNames = [];
+ for (let folderURI of srchFolderUriArray) {
+ let folder = MailUtils.getFolderForURI(folderURI);
+ let name = this.gMessengerBundle.getFormattedString("verboseFolderFormat",
+ [folder.prettyName, folder.server.prettyName]);
+ folderNames.push(name);
+ }
+ foldersList.setAttribute("tooltiptext", folderNames.join("\n"));
+ } else {
+ foldersList.removeAttribute("tooltiptext");
+ }
+}
+
+function onEnterInSearchTerm()
+{
+ // stub function called by the core search widget code...
+ // nothing for us to do here
+}
diff --git a/mailnews/base/content/virtualFolderProperties.xul b/mailnews/base/content/virtualFolderProperties.xul
new file mode 100644
index 000000000..99c140e82
--- /dev/null
+++ b/mailnews/base/content/virtualFolderProperties.xul
@@ -0,0 +1,103 @@
+<?xml version="1.0"?>
+<!-- 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/.
+ -->
+
+<?xml-stylesheet href="chrome://messenger/skin/dialogs.css" type="text/css"?>
+<?xml-stylesheet href="chrome://messenger/skin/searchDialog.css" type="text/css"?>
+<?xml-stylesheet href="chrome://messenger/skin/messenger.css" type="text/css"?>
+<?xml-stylesheet href="chrome://messenger/skin/folderMenus.css" type="text/css"?>
+
+<?xul-overlay href="chrome://messenger/content/searchTermOverlay.xul"?>
+
+<!DOCTYPE dialog [
+<!ENTITY % folderDTD SYSTEM "chrome://messenger/locale/virtualFolderProperties.dtd">
+ %folderDTD;
+]>
+
+<dialog xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ id="virtualFolderPropertiesDialog"
+ title="&virtualFolderProperties.title;"
+ onload="onLoad();"
+ buttons="accept,cancel"
+ newFolderAcceptButtonLabel="&newFolderButton.label;"
+ newFolderAcceptButtonAccessKey="&newFolderButton.accesskey;"
+ editFolderAcceptButtonLabel="&editFolderButton.label;"
+ editFolderAcceptButtonAccessKey="&editFolderButton.accesskey;"
+ style="width: 50em; height: 28em;"
+ windowtype="mailnews:virtualFolderProperties"
+ ondialogaccept="return onOK();">
+
+ <stringbundleset id="stringbundleset">
+ <stringbundle id="bundle_search" src="chrome://messenger/locale/search.properties"/>
+ <stringbundle id="bundle_messenger" src="chrome://messenger/locale/messenger.properties"/>
+ <stringbundle id="bundle_folder" src="chrome://messenger/locale/folderWidgets.properties"/>
+ </stringbundleset>
+
+ <script type="application/javascript" src="chrome://messenger/content/mailCommands.js"/>
+ <script type="application/javascript" src="chrome://messenger/content/commandglue.js"/>
+ <script type="application/javascript" src="chrome://messenger/content/virtualFolderProperties.js"/>
+
+ <grid>
+ <columns>
+ <column/>
+ <column flex="1"/>
+ <column flex="2"/>
+ </columns>
+
+ <rows>
+ <row align="center">
+ <label value="&name.label;" accesskey="&name.accesskey;" control="name"/>
+ <textbox id="name" hidden="true"
+ oninput="doEnabling();"/>
+ <filefield id="existingName" class="folderMenuItem"
+ hidden="true" tabindex="0"
+ SpecialFolder="Virtual"/>
+ <spring/>
+ </row>
+
+ <row align="center" id="chooseFolderLocationRow">
+ <label value="&description.label;" accesskey="&description.accesskey;"
+ control="msgNewFolderPicker"/>
+ <menulist id="msgNewFolderPicker" class="folderMenuItem" flex="1"
+ displayformat="verbose">
+ <menupopup id="msgNewFolderPopup"
+ class="menulist-menupopup"
+ type="folder"
+ mode="newFolder"
+ showFileHereLabel="true"
+ oncommand="onFolderPick(event);"/>
+ </menulist>
+ <spring/>
+ </row>
+
+ <row align="center">
+ <label value="&folderSelectionCaption.label;"/>
+ <hbox align="center">
+ <label id="chosenFoldersCount"/>
+ <spacer flex="1"/>
+ <button label="&chooseFoldersButton.label;"
+ accesskey="&chooseFoldersButton.accesskey;"
+ oncommand="chooseFoldersToSearch();"/>
+ </hbox>
+ </row>
+ </rows>
+ </grid>
+
+ <hbox flex="1">
+ <checkbox id="searchOnline" label="&searchOnline.label;"
+ accesskey="&searchOnline.accesskey;"/>
+ </hbox>
+
+ <separator class="thin"/>
+
+ <vbox flex="2">
+ <label value="&searchTermCaption.label;"/>
+ <hbox flex="1">
+ <vbox id="searchTermListBox" flex="2"/>
+ </hbox>
+ </vbox>
+
+</dialog>
+