diff options
Diffstat (limited to 'mailnews/base/content')
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> + |