Issue #1258 - Part 1: Import mailnews, ldap, and mork from comm-esr52.9.1

This commit is contained in:
Matt A. Tobin 2019-11-03 00:17:46 -04:00 • committed by Roy Tam
commit e400f4130a
1564 changed files with 510348 additions and 0 deletions

View file

@ -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");
}

View file

@ -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>

View file

@ -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);
}

View file

@ -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);
}

View file

@ -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>

View file

@ -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>

View file

@ -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;
}
};

View file

@ -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;
}

View file

@ -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
}
}

View file

@ -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>

View file

@ -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>

View file

@ -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 );
}

View file

@ -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>

View file

@ -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();
}

View file

@ -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>

View file

@ -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));
}

View file

@ -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);
}

View file

@ -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);
}

View file

@ -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>

View file

@ -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;
}

View file

@ -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>

View file

@ -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;}
}

View file

@ -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();
}

View file

@ -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>

View file

@ -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;
}
}

View file

@ -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>

View file

@ -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;
}

View file

@ -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...
}

View file

@ -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();

View file

@ -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>

View file

@ -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);
}
}

View file

@ -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>

View file

@ -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
}

View file

@ -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>