diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 17:32:43 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 17:32:43 +0000 |
commit | 6bf0a5cb5034a7e684dcc3500e841785237ce2dd (patch) | |
tree | a68f146d7fa01f0134297619fbe7e33db084e0aa /comm/mailnews/base/prefs | |
parent | Initial commit. (diff) | |
download | thunderbird-6bf0a5cb5034a7e684dcc3500e841785237ce2dd.tar.xz thunderbird-6bf0a5cb5034a7e684dcc3500e841785237ce2dd.zip |
Adding upstream version 1:115.7.0.upstream/1%115.7.0upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
44 files changed, 11631 insertions, 0 deletions
diff --git a/comm/mailnews/base/prefs/.eslintrc.js b/comm/mailnews/base/prefs/.eslintrc.js new file mode 100644 index 0000000000..5816519fbb --- /dev/null +++ b/comm/mailnews/base/prefs/.eslintrc.js @@ -0,0 +1,5 @@ +"use strict"; + +module.exports = { + extends: ["plugin:mozilla/valid-jsdoc"], +}; diff --git a/comm/mailnews/base/prefs/content/AccountManager.js b/comm/mailnews/base/prefs/content/AccountManager.js new file mode 100644 index 0000000000..8b319d51d4 --- /dev/null +++ b/comm/mailnews/base/prefs/content/AccountManager.js @@ -0,0 +1,1949 @@ +/* -*- Mode: JavaScript; tab-width: 4; 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/. */ + +/** + * Here's how this dialog works: + * The main dialog contains a tree on the left (id="accounttree") and an + * iframe which loads a particular preference document (such as am-main.xhtml) + * on the right. + * + * When the user clicks on items in the tree on the left, two things have + * to be determined before the UI can be updated: + * - the relevant account + * - the relevant page + * + * When both of these are known, this is what happens: + * - every form element of the previous page is saved in the account value + * hashtable for the previous account + * - the relevant page is loaded into the iframe + * - each form element in the page is filled in with an appropriate value + * from the current account's hashtable + * - in the iframe inside the page, if there is an onInit() method, + * it is called. The onInit method can further update this page based + * on values set in the previous step. + */ + +/* import-globals-from accountUtils.js */ +/* import-globals-from am-prefs.js */ +/* import-globals-from amUtils.js */ + +var { XPCOMUtils } = ChromeUtils.importESModule( + "resource://gre/modules/XPCOMUtils.sys.mjs" +); +var { MailUtils } = ChromeUtils.import("resource:///modules/MailUtils.jsm"); +var { Gloda } = ChromeUtils.import("resource:///modules/gloda/Gloda.jsm"); +var { MailServices } = ChromeUtils.import( + "resource:///modules/MailServices.jsm" +); +var { UIDensity } = ChromeUtils.import("resource:///modules/UIDensity.jsm"); +var { UIFontSize } = ChromeUtils.import("resource:///modules/UIFontSize.jsm"); + +ChromeUtils.defineModuleGetter( + this, + "FolderUtils", + "resource:///modules/FolderUtils.jsm" +); +var { cleanUpHostName, isLegalHostNameOrIP } = ChromeUtils.import( + "resource:///modules/hostnameUtils.jsm" +); +var { ChatIcons } = ChromeUtils.importESModule( + "resource:///modules/chatIcons.sys.mjs" +); + +XPCOMUtils.defineLazyGetter(this, "gSubDialog", function () { + const { SubDialogManager } = ChromeUtils.importESModule( + "resource://gre/modules/SubDialog.sys.mjs" + ); + return new SubDialogManager({ + dialogStack: document.getElementById("dialogStack"), + dialogTemplate: document.getElementById("dialogTemplate"), + dialogOptions: { + styleSheets: [ + "chrome://messenger/skin/preferences/dialog.css", + "chrome://messenger/skin/preferences/preferences.css", + ], + resizeCallback: ({ title, frame }) => { + UIFontSize.registerWindow(frame.contentWindow); + + // Resize the dialog to fit the content with edited font size. + requestAnimationFrame(() => { + let dialogs = frame.ownerGlobal.gSubDialog._dialogs; + let dialog = dialogs.find( + d => d._frame.contentDocument == frame.contentDocument + ); + if (dialog) { + UIFontSize.resizeSubDialog(dialog); + } + }); + }, + }, + }); +}); + +// If Local directory has changed the app needs to restart. Once this is set +// a restart will be attempted at each attempt to close the Account manager with OK. +var gRestartNeeded = false; + +// This is a hash-map for every account we've touched in the pane. Each entry +// has additional maps of attribute-value pairs that we're going to want to save +// when the user hits OK. +var accountArray; +var gGenericAttributeTypes; + +var currentAccount; +var currentPageId; + +var pendingAccount; +var pendingPageId; + +/** + * This array contains filesystem folders that are deemed inappropriate + * for use as the local directory pref for message storage. + * It is global to allow extensions to add to/remove from it if needed. + * Extensions adding new server types should first consider setting + * nsIMsgProtocolInfo(of the server type).defaultLocalPath properly + * so that the test will allow that directory automatically. + * See the checkLocalDirectoryIsSafe function for description of the members. + */ +var gDangerousLocalStorageDirs = [ + // profile folder + { dirsvc: "ProfD", OS: null }, + // GRE install folder + { dirsvc: "GreD", OS: null }, + // Application install folder + { dirsvc: "CurProcD", OS: null }, + // system temporary folder + { dirsvc: "TmpD", OS: null }, + // Windows system folder + { dirsvc: "SysD", OS: "WINNT" }, + // Windows folder + { dirsvc: "WinD", OS: "WINNT" }, + // Program Files folder + { dirsvc: "ProgF", OS: "WINNT" }, + // trash folder + { dirsvc: "Trsh", OS: "Darwin" }, + // Mac OS system folder + { dir: "/System", OS: "Darwin" }, + // devices folder + { dir: "/dev", OS: "Darwin,Linux" }, + // process info folder + { dir: "/proc", OS: "Linux" }, + // system state folder + { dir: "/sys", OS: "Linux" }, +]; + +// This sets an attribute in a xul element so that we can later +// know what value to substitute in a prefstring. Different +// preference types set different attributes. We get the value +// in the same way as the function getAccountValue() determines it. +function updateElementWithKeys(account, element, type) { + switch (type) { + case "identity": + element.identitykey = account.defaultIdentity.key; + break; + case "pop3": + case "imap": + case "nntp": + case "server": + element.serverkey = account.incomingServer.key; + break; + case "smtp": + if (MailServices.smtp.defaultServer) { + element.serverkey = MailServices.smtp.defaultServer.key; + } + break; + default: + // dump("unknown element type! "+type+"\n"); + } +} + +// called when the whole document loads +// perform initialization here +function onLoad() { + let selectedServer = document.documentElement.server; + let selectPage = document.documentElement.selectPage || null; + + // Arguments can have two properties: (1) "server," the nsIMsgIncomingServer + // to select initially and (2) "selectPage," the page for that server to that + // should be selected. + + accountArray = {}; + gGenericAttributeTypes = {}; + + gAccountTree.load(); + + setTimeout(selectServer, 0, selectedServer, selectPage); + + let contentFrame = document.getElementById("contentFrame"); + contentFrame.addEventListener("load", event => { + let inputElements = contentFrame.contentDocument.querySelectorAll( + "checkbox, input, menulist, textarea, radiogroup, richlistbox" + ); + contentFrame.contentDocument.addEventListener("prefchange", event => { + onAccept(true); + }); + for (let input of inputElements) { + if (input.localName == "input" || input.localName == "textarea") { + input.addEventListener("change", event => { + onAccept(true); + }); + } else { + input.addEventListener("command", event => { + onAccept(true); + }); + } + } + UIFontSize.registerWindow(contentFrame.contentWindow); + }); + + UIDensity.registerWindow(window); + UIFontSize.registerWindow(window); +} + +function onUnload() { + gAccountTree.unload(); +} + +function selectServer(server, selectPageId) { + let accountTree = document.getElementById("accounttree"); + + // Default to showing the first account. + let accountRow = accountTree.rows[0]; + + // Find the tree-node for the account we want to select. + if (server) { + for (let row of accountTree.children) { + let account = row._account; + if (account && server == account.incomingServer) { + accountRow = row; + // Make sure all the panes of the account to be selected are shown. + accountTree.expandRow(accountRow); + break; + } + } + } + + let pageToSelect = accountRow; + + if (selectPageId) { + // Find the page that also corresponds to this server. + // It either is the accountRow itself... + let pageId = accountRow.getAttribute("PageTag"); + if (pageId != selectPageId) { + // ... or one of its children. + pageToSelect = accountRow.querySelector( + '[PageTag="' + selectPageId + '"]' + ); + } + } + + accountTree.selectedIndex = accountTree.rows.indexOf(pageToSelect); +} + +function replaceWithDefaultSmtpServer(deletedSmtpServerKey) { + // First we replace the smtpserverkey in every identity. + for (let identity of MailServices.accounts.allIdentities) { + if (identity.smtpServerKey == deletedSmtpServerKey) { + identity.smtpServerKey = ""; + } + } + + // When accounts have already been loaded in the panel then the first + // replacement will be overwritten when the accountvalues are written out + // from the pagedata. We get the loaded accounts and check to make sure + // that the account exists for the accountid and that it has a default + // identity associated with it (to exclude smtpservers and local folders) + // Then we check only for the identity[type] and smtpServerKey[slot] and + // replace that with the default smtpserverkey if necessary. + + for (var accountid in accountArray) { + var account = accountArray[accountid]._account; + if (account && account.defaultIdentity) { + var accountValues = accountArray[accountid]; + var smtpServerKey = getAccountValue( + account, + accountValues, + "identity", + "smtpServerKey", + null, + false + ); + if (smtpServerKey == deletedSmtpServerKey) { + setAccountValue(accountValues, "identity", "smtpServerKey", ""); + } + } + } +} + +/** + * Called when OK is clicked on the dialog. + * + * @param {boolean} aDoChecks - If true, execute checks on data, otherwise hope + * they were already done elsewhere and proceed directly to saving the data. + */ +function onAccept(aDoChecks) { + if (aDoChecks) { + // Check if user/host have been modified correctly. + if (!checkUserServerChanges(true)) { + return false; + } + + if (!checkAccountNameIsValid()) { + return false; + } + } + + // Run checks as if the page was being left. + if ("onLeave" in top.frames.contentFrame) { + if (!top.frames.contentFrame.onLeave()) { + // Prevent closing Account manager if user declined the changes. + return false; + } + } + + if (!onSave()) { + return false; + } + + // hack hack - save the prefs file NOW in case we crash + Services.prefs.savePrefFile(null); + + if (gRestartNeeded) { + MailUtils.restartApplication(); + // Prevent closing Account manager in case restart failed. If restart did not fail, + // return value does not matter, as we are restarting. + return false; + } + + return true; +} + +/** + * See if the given path to a directory is usable on the current OS. + * + * aLocalPath the nsIFile of a directory to check. + */ +function checkDirectoryIsValid(aLocalPath) { + // Any directory selected in the file picker already exists. + // Any directory specified in prefs.js will be created at start if it does + // not exist yet. + // If at the time of entering Account Manager the directory does not exist, + // it must be invalid in the current OS or not creatable due to permissions. + // Even then, the backend sometimes tries to create a new one + // under the current profile. + if (!aLocalPath.exists() || !aLocalPath.isDirectory()) { + return false; + } + + if (Services.appinfo.OS == "WINNT") { + // Do not allow some special filenames on Windows. + // Taken from mozilla/widget/windows/nsDataObj.cpp::MangleTextToValidFilename() + let dirLeafName = aLocalPath.leafName; + const kForbiddenNames = [ + "COM1", + "COM2", + "COM3", + "COM4", + "COM5", + "COM6", + "COM7", + "COM8", + "COM9", + "LPT1", + "LPT2", + "LPT3", + "LPT4", + "LPT5", + "LPT6", + "LPT7", + "LPT8", + "LPT9", + "CON", + "PRN", + "AUX", + "NUL", + "CLOCK$", + ]; + if (kForbiddenNames.includes(dirLeafName)) { + return false; + } + } + + // The directory must be readable and writable to work as a mail store. + if (!(aLocalPath.isReadable() && aLocalPath.isWritable())) { + return false; + } + + return true; +} + +/** + * Even if the local path is usable, there are some special folders we do not + * want to allow for message storage as they cause problems (see e.g. bug 750781). + * + * aLocalPath The nsIFile of a directory to check. + */ +function checkDirectoryIsAllowed(aLocalPath) { + /** + * Check if the local path (aLocalPath) is 'safe' i.e. NOT a parent + * or subdirectory of the given special system/app directory (aDirToCheck). + * + * @param {object} aDirToCheck - An object describing the special directory. + * @param {string} aDirToCheck.dirsvc - A path keyword to retrieve from the + * Directory service. + * @param {string} aDirToCheck.dir - An absolute filesystem path. + * @param {string} aDirToCheck.OS - A string of comma separated values defining on which. + * Operating systems the folder is unusable: + * - null = all + * - WINNT = Windows + * - Darwin = OS X + * - Linux = Linux + * @param {string} aDirToCheck.safeSubdirs - An array of directory names that + * are allowed to be used under the tested directory. + * @param {nsIFile} aLocalPath - An nsIFile of the directory to check, + * intended for message storage. + */ + function checkLocalDirectoryIsSafe(aDirToCheck, aLocalPath) { + if (aDirToCheck.OS) { + if (!aDirToCheck.OS.split(",").includes(Services.appinfo.OS)) { + return true; + } + } + + let testDir = null; + if ("dirsvc" in aDirToCheck) { + try { + testDir = Services.dirsvc.get(aDirToCheck.dirsvc, Ci.nsIFile); + } catch (e) { + console.error( + "The special folder " + + aDirToCheck.dirsvc + + " cannot be retrieved on this platform: " + + e + ); + } + + if (!testDir) { + return true; + } + } else if ("dir" in aDirToCheck) { + testDir = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile); + testDir.initWithPath(aDirToCheck.dir); + if (!testDir.exists()) { + return true; + } + } else { + console.error("No directory to check?"); + return true; + } + + testDir.normalize(); + + if (testDir.equals(aLocalPath) || aLocalPath.contains(testDir)) { + return false; + } + + if (testDir.contains(aLocalPath)) { + if (!("safeSubdirs" in aDirToCheck)) { + return false; + } + + // While the tested directory may not be safe, + // a subdirectory of some safe subdirectories may be fine. + let isInSubdir = false; + for (let subDir of aDirToCheck.safeSubdirs) { + let checkDir = testDir.clone(); + checkDir.append(subDir); + if (checkDir.contains(aLocalPath)) { + isInSubdir = true; + break; + } + } + return isInSubdir; + } + + return true; + } // end of checkDirectoryIsNotSpecial + + // If the server type has a nsIMsgProtocolInfo.defaultLocalPath set, + // allow that directory. + if (currentAccount.incomingServer) { + try { + let defaultPath = + currentAccount.incomingServer.protocolInfo.defaultLocalPath; + if (defaultPath) { + defaultPath.normalize(); + if (defaultPath.contains(aLocalPath)) { + return true; + } + } + } catch (e) { + /* No problem if this fails. */ + } + } + + for (let tryDir of gDangerousLocalStorageDirs) { + if (!checkLocalDirectoryIsSafe(tryDir, aLocalPath)) { + return false; + } + } + + return true; +} + +/** + * Check if the specified directory does meet all the requirements + * for safe mail storage. + * + * aLocalPath the nsIFile of a directory to check. + */ +function checkDirectoryIsUsable(aLocalPath) { + const kAlertTitle = document + .getElementById("bundle_prefs") + .getString("prefPanel-server"); + const originalPath = aLocalPath; + + let invalidPath = false; + try { + aLocalPath.normalize(); + } catch (e) { + invalidPath = true; + } + + if (invalidPath || !checkDirectoryIsValid(aLocalPath)) { + let alertString = document + .getElementById("bundle_prefs") + .getFormattedString("localDirectoryInvalid", [originalPath.path]); + Services.prompt.alert(window, kAlertTitle, alertString); + return false; + } + + if (!checkDirectoryIsAllowed(aLocalPath)) { + let alertNotAllowed = document + .getElementById("bundle_prefs") + .getFormattedString("localDirectoryNotAllowed", [originalPath.path]); + Services.prompt.alert(window, kAlertTitle, alertNotAllowed); + return false; + } + + // Check that no other account has this same or dependent local directory. + for (let server of MailServices.accounts.allServers) { + if (server.key == currentAccount.incomingServer.key) { + continue; + } + + let serverPath = server.localPath; + try { + serverPath.normalize(); + let alertStringID = null; + if (serverPath.equals(aLocalPath)) { + alertStringID = "directoryAlreadyUsedByOtherAccount"; + } else if (serverPath.contains(aLocalPath)) { + alertStringID = "directoryParentUsedByOtherAccount"; + } else if (aLocalPath.contains(serverPath)) { + alertStringID = "directoryChildUsedByOtherAccount"; + } + + if (alertStringID) { + let alertString = document + .getElementById("bundle_prefs") + .getFormattedString(alertStringID, [server.prettyName]); + + Services.prompt.alert(window, kAlertTitle, alertString); + return false; + } + } catch (e) { + // The other account's path is seriously broken, so we can't compare it. + console.error( + "The Local Directory path of the account " + + server.prettyName + + " seems invalid." + ); + } + } + + return true; +} + +/** + * Check if the user and/or host names have been changed and if so check + * if the new names already exists for an account or are empty. + * Also check if the Local Directory path was changed. + * + * @param {boolean} showAlert - Show and alert if a problem with the host / user + * name is found. + */ +function checkUserServerChanges(showAlert) { + const prefBundle = document.getElementById("bundle_prefs"); + const alertTitle = prefBundle.getString("prefPanel-server"); + var alertText = null; + + var accountValues = getValueArrayFor(currentAccount); + if (!accountValues) { + return true; + } + + let currentServer = currentAccount ? currentAccount.incomingServer : null; + + // If this type doesn't exist (just removed) then return. + if (!("server" in accountValues) || !accountValues.server) { + return true; + } + + // Get the new username, hostname and type from the page. + var typeElem = getPageFormElement("server.type"); + var hostElem = getPageFormElement("server.hostName"); + var userElem = getPageFormElement("server.username"); + if (typeElem && userElem && hostElem) { + var newType = getFormElementValue(typeElem); + var oldHost = getAccountValue( + currentAccount, + accountValues, + "server", + "hostName", + null, + false + ); + var newHost = getFormElementValue(hostElem); + var oldUser = getAccountValue( + currentAccount, + accountValues, + "server", + "username", + null, + false + ); + + var newUser = getFormElementValue(userElem); + var checkUser = true; + // There is no username needed for e.g. news so reset it. + if (currentServer && !currentServer.protocolInfo.requiresUsername) { + oldUser = newUser = ""; + checkUser = false; + } + alertText = null; + // If something is changed then check if the new user/host already exists. + if (oldUser != newUser || oldHost != newHost) { + newUser = newUser.trim(); + newHost = cleanUpHostName(newHost); + if (checkUser && newUser == "") { + alertText = prefBundle.getString("userNameEmpty"); + } else if (!isLegalHostNameOrIP(newHost)) { + alertText = prefBundle.getString("enterValidServerName"); + } else { + let sameServer = MailServices.accounts.findServer( + newUser, + newHost, + newType + ); + if (sameServer && sameServer != currentServer) { + alertText = prefBundle.getString("modifiedAccountExists"); + } else { + // New hostname passed all checks. We may have cleaned it up so set + // the new value back into the input element. + setFormElementValue(hostElem, newHost); + } + } + + if (alertText) { + if (showAlert) { + Services.prompt.alert(window, alertTitle, alertText); + } + // Restore the old values before return + if (checkUser) { + setFormElementValue(userElem, oldUser); + } + setFormElementValue(hostElem, oldHost); + // If no message is shown to the user, silently revert the values + // and consider the check a success. + return !showAlert; + } + + // If username is changed remind users to change Your Name and Email Address. + // If server name is changed and has defined filters then remind users + // to edit rules. + if (showAlert) { + let filterList; + if (currentServer && checkUser) { + filterList = currentServer.getEditableFilterList(null); + } + let changeText = ""; + if ( + oldHost != newHost && + filterList != undefined && + filterList.filterCount + ) { + changeText = prefBundle.getString("serverNameChanged"); + } + // In the event that oldHost == newHost or oldUser == newUser, + // the \n\n will be trimmed off before the message is shown. + if (oldUser != newUser) { + changeText = + changeText + "\n\n" + prefBundle.getString("userNameChanged"); + } + + if (changeText != "") { + Services.prompt.alert(window, alertTitle, changeText.trim()); + } + } + + let l10n = new Localization(["messenger/accountManager.ftl"], true); + let cancel = Services.prompt.confirmEx( + window, + alertTitle, + l10n.formatValueSync("server-change-restart-required"), + Services.prompt.BUTTON_POS_0 * Services.prompt.BUTTON_TITLE_IS_STRING + + Services.prompt.BUTTON_POS_1 * Services.prompt.BUTTON_TITLE_CANCEL, + prefBundle.getString("localDirectoryRestart"), + null, + null, + null, + {} + ); + if (cancel) { + setFormElementValue(hostElem, oldHost); + setFormElementValue(userElem, oldUser); + return false; + } + gRestartNeeded = true; + } + } + + // Check the new value of the server.localPath field for validity. + var pathElem = getPageFormElement("server.localPath"); + if (!pathElem) { + return true; + } + let dir = getFormElementValue(pathElem); + if (!checkDirectoryIsUsable(dir)) { + // return false; // Temporarily disable this. Just show warning but do not block. See bug 921371. + console.error( + `Local directory ${dir.path} of account ${currentAccount.key} is not safe to use. Consider changing it.` + ); + } + + // Warn if the Local directory path was changed. + // This can be removed once bug 2654 is fixed. + let oldLocalDir = getAccountValue( + currentAccount, + accountValues, + "server", + "localPath", + null, + false + ); // both return nsIFile + let newLocalDir = getFormElementValue(pathElem); + if (oldLocalDir && newLocalDir && oldLocalDir.path != newLocalDir.path) { + let brandName = document + .getElementById("bundle_brand") + .getString("brandShortName"); + alertText = prefBundle.getFormattedString("localDirectoryChanged", [ + brandName, + ]); + + let cancel = Services.prompt.confirmEx( + window, + alertTitle, + alertText, + Services.prompt.BUTTON_POS_0 * Services.prompt.BUTTON_TITLE_IS_STRING + + Services.prompt.BUTTON_POS_1 * Services.prompt.BUTTON_TITLE_CANCEL, + prefBundle.getString("localDirectoryRestart"), + null, + null, + null, + {} + ); + if (cancel) { + setFormElementValue(pathElem, oldLocalDir); + return false; + } + gRestartNeeded = true; + } + + return true; +} + +/** + * If account name is not valid, alert the user. + */ +function checkAccountNameIsValid() { + if (!currentAccount) { + return true; + } + + const prefBundle = document.getElementById("bundle_prefs"); + let alertText = null; + + let serverNameElem = getPageFormElement("server.prettyName"); + if (serverNameElem) { + let accountName = getFormElementValue(serverNameElem); + + if (!accountName) { + alertText = prefBundle.getString("accountNameEmpty"); + } else if (accountNameExists(accountName, currentAccount.key)) { + alertText = prefBundle.getString("accountNameExists"); + // Change the account name to prevent UI freeze. + let counter = 2; + while ( + accountNameExists(`${accountName}_${counter}`, currentAccount.key) + ) { + counter++; + } + serverNameElem.value = `${accountName}_${counter}`; + } + + if (alertText) { + const alertTitle = prefBundle.getString("accountWizard"); + Services.prompt.alert(window, alertTitle, alertText); + return false; + } + } + + return true; +} + +function onSave() { + if (pendingPageId) { + dump("ERROR: " + pendingPageId + " hasn't loaded yet! Not saving.\n"); + return false; + } + + // make sure the current visible page is saved + savePage(currentAccount); + + for (var accountid in accountArray) { + var accountValues = accountArray[accountid]; + var account = accountArray[accountid]._account; + if (!saveAccount(accountValues, account)) { + return false; + } + } + + return true; +} + +/** + * Highlight the default account row in the account tree, + * optionally un-highlight the previous one. + * + * @param {?nsIMsgAccount} newDefault - The account that has become the new + * default. Can be given as null if there is none. + * @param {?nsIMsgAccount} oldDefault - The account that has stopped being the + * default. Can be given as null if there was none. + */ +function markDefaultServer(newDefault, oldDefault) { + if (oldDefault == newDefault) { + return; + } + + let accountTree = document.getElementById("accounttree"); + for (let accountRow of accountTree.children) { + if (newDefault && newDefault == accountRow._account) { + accountRow.classList.add("isDefaultServer"); + } + if (oldDefault && oldDefault == accountRow._account) { + accountRow.classList.remove("isDefaultServer"); + } + } +} + +/** + * Notify the UI to rebuild the account tree. + */ +function rebuildAccountTree() { + // TODO: Reimplement or replace. +} + +/** + * Make currentAccount (currently selected in the account tree) the default one. + */ +function onSetDefault(event) { + // Make sure this function was not called while the control item is disabled + if (event.target.getAttribute("disabled") == "true") { + return; + } + + let previousDefault = MailServices.accounts.defaultAccount; + MailServices.accounts.defaultAccount = currentAccount; + markDefaultServer(currentAccount, previousDefault); + + // Update gloda's myContact with the new default account's default identity. + Gloda._initMyIdentities(); + + gAccountTree.load(); +} + +function onRemoveAccount(event) { + if (event.target.getAttribute("disabled") == "true" || !currentAccount) { + return; + } + + let server = currentAccount.incomingServer; + + let canDelete = server.protocolInfo.canDelete || server.canDelete; + if (!canDelete) { + return; + } + + let serverList = []; + let accountTree = document.getElementById("accounttree"); + // build the list of servers in the account tree (order is important) + for (let row of accountTree.children) { + if ("_account" in row) { + let curServer = row._account.incomingServer; + if (!serverList.includes(curServer)) { + serverList.push(curServer); + } + } + } + + // get position of the current server in the server list + let serverIndex = serverList.indexOf(server); + + // After the current server is deleted, choose the next server/account, + // or the previous one if the last one was deleted. + if (serverIndex == serverList.length - 1) { + serverIndex--; + } else { + serverIndex++; + } + + // Need to save these before the account and its server is removed. + let serverId = server.serverURI; + + // Confirm account deletion. + let removeArgs = { + server, + account: currentAccount, + result: false, + }; + + let onCloseDialog = function () { + // If result is true, the account was removed. + if (!removeArgs.result) { + return; + } + + // clear cached data out of the account array + currentAccount = currentPageId = null; + if (serverId in accountArray) { + delete accountArray[serverId]; + } + + if (serverIndex >= 0 && serverIndex < serverList.length) { + selectServer(serverList[serverIndex], null); + } + + // Either the default account was deleted so there is a new one + // or the default account was not changed. Either way, there is + // no need to unmark the old one. + markDefaultServer(MailServices.accounts.defaultAccount, null); + }; + + gSubDialog.open( + "chrome://messenger/content/removeAccount.xhtml", + { + features: "resizable=no", + closingCallback: onCloseDialog, + }, + removeArgs + ); +} + +function saveAccount(accountValues, account) { + var identity = null; + var server = null; + + if (account) { + identity = account.defaultIdentity; + server = account.incomingServer; + } + + for (var type in accountValues) { + var dest; + try { + if (type == "identity") { + dest = identity; + } else if (type == "server") { + dest = server; + } else if (type == "pop3") { + dest = server.QueryInterface(Ci.nsIPop3IncomingServer); + } else if (type == "imap") { + dest = server.QueryInterface(Ci.nsIImapIncomingServer); + } else if (type == "none") { + dest = server.QueryInterface(Ci.nsINoIncomingServer); + } else if (type == "nntp") { + dest = server.QueryInterface(Ci.nsINntpIncomingServer); + } else if (type == "smtp") { + dest = MailServices.smtp.defaultServer; + } + } catch (ex) { + // don't do anything, just means we don't support that + } + if (dest == undefined) { + continue; + } + var typeArray = accountValues[type]; + + for (var slot in typeArray) { + if ( + type in gGenericAttributeTypes && + slot in gGenericAttributeTypes[type] + ) { + var methodName = "get"; + switch (gGenericAttributeTypes[type][slot]) { + case "int": + methodName += "Int"; + break; + case "wstring": + methodName += "Unichar"; + break; + case "string": + methodName += "Char"; + break; + case "bool": + // in some cases + // like for radiogroups of type boolean + // the value will be "false" instead of false + // we need to convert it. + if (typeArray[slot] == "false") { + typeArray[slot] = false; + } else if (typeArray[slot] == "true") { + typeArray[slot] = true; + } + + methodName += "Bool"; + break; + default: + dump( + "unexpected preftype: " + + gGenericAttributeTypes[type][slot] + + "\n" + ); + break; + } + methodName += methodName + "Value" in dest ? "Value" : "Attribute"; + if (dest[methodName](slot) != typeArray[slot]) { + methodName = methodName.replace("get", "set"); + dest[methodName](slot, typeArray[slot]); + } + } else if ( + slot in dest && + typeArray[slot] != undefined && + dest[slot] != typeArray[slot] + ) { + try { + dest[slot] = typeArray[slot]; + } catch (ex) { + // hrm... need to handle special types here + } + } + } + } + + // if we made account changes to the spam settings, we'll need to re-initialize + // our settings object + if (server && server.spamSettings) { + try { + server.spamSettings.initialize(server); + } catch (e) { + let accountName = getAccountValue( + account, + getValueArrayFor(account), + "server", + "prettyName", + null, + false + ); + let alertText = document + .getElementById("bundle_prefs") + .getFormattedString("junkSettingsBroken", [accountName]); + let review = Services.prompt.confirmEx( + window, + null, + alertText, + Services.prompt.BUTTON_POS_0 * Services.prompt.BUTTON_TITLE_YES + + Services.prompt.BUTTON_POS_1 * Services.prompt.BUTTON_TITLE_NO, + null, + null, + null, + null, + {} + ); + if (!review) { + onAccountTreeSelect("am-junk.xhtml", account); + return false; + } + } + } + + return true; +} + +/** + * Set enabled/disabled state for the actions in the Account Actions menu. + * Called only by Thunderbird. + */ +function initAccountActionsButtons(menupopup) { + if (!Services.prefs.getBoolPref("mail.chat.enabled")) { + document.getElementById("accountActionsAddIMAccount").hidden = true; + } + + updateItems( + document.getElementById("accounttree"), + getCurrentAccount(), + document.getElementById("accountActionsAddMailAccount"), + document.getElementById("accountActionsDropdownSetDefault"), + document.getElementById("accountActionsDropdownRemove") + ); + + updateBlockedItems(menupopup.children, true); +} + +/** + * Determine enabled/disabled state for the passed in elements + * representing account actions. + */ +function updateItems( + tree, + account, + addAccountItem, + setDefaultItem, + removeItem +) { + // Start with items disabled and then find out what can be enabled. + let canSetDefault = false; + let canDelete = false; + + if (account && tree.selectedIndex >= 0) { + // Only try to check properties if there was anything selected in the tree + // and it belongs to an account. + // Otherwise we have either selected a SMTP server, or there is some + // problem. Either way, we don't want the user to act on it. + let server = account.incomingServer; + + if ( + account != MailServices.accounts.defaultAccount && + server.canBeDefaultServer && + account.identities.length > 0 + ) { + canSetDefault = true; + } + + canDelete = server.protocolInfo.canDelete || server.canDelete; + } + + setEnabled(addAccountItem, true); + setEnabled(setDefaultItem, canSetDefault); + setEnabled(removeItem, canDelete); +} + +/** + * Disable buttons/menu items if their control preference is locked. + * + * @param {Node[]|NodeList} aItems - Elements to be checked. + * @param {boolean} aMustBeTrue - If true then the pref must be boolean and set + * to true to trigger the disabling. + */ +function updateBlockedItems(aItems, aMustBeTrue) { + for (let item of aItems) { + let prefstring = item.getAttribute("prefstring"); + if (!prefstring) { + continue; + } + + if ( + Services.prefs.prefIsLocked(prefstring) && + (!aMustBeTrue || Services.prefs.getBoolPref(prefstring)) + ) { + item.setAttribute("disabled", true); + } + } +} + +/** + * Set enabled/disabled state for the control. + */ +function setEnabled(control, enabled) { + if (!control) { + return; + } + + if (enabled) { + control.removeAttribute("disabled"); + } else { + control.setAttribute("disabled", true); + } +} + +// Called when someone clicks on an account. Figure out context by what they +// clicked on. This is also called when an account is removed. In this case, +// nothing is selected. +function onAccountTreeSelect(pageId, account) { + let tree = document.getElementById("accounttree"); + + let changeView = pageId && account; + if (!changeView) { + if (tree.selectedIndex < 0) { + return false; + } + + let node = tree.rows[tree.selectedIndex]; + account = "_account" in node ? node._account : null; + + pageId = node.getAttribute("PageTag"); + } + + if (pageId == currentPageId && account == currentAccount) { + return true; + } + + if ( + document + .getElementById("contentFrame") + .contentDocument.getElementById("server.localPath") + ) { + // Check if user/host names have been changed or the Local Directory is invalid. + if (!checkUserServerChanges(false)) { + changeView = true; + account = currentAccount; + pageId = currentPageId; + } + + if (gRestartNeeded) { + onAccept(false); + } + } + + if ( + document + .getElementById("contentFrame") + .contentDocument.getElementById("server.prettyName") + ) { + // Check if account name is valid. + if (!checkAccountNameIsValid()) { + changeView = true; + account = currentAccount; + pageId = currentPageId; + } + } + + if (currentPageId) { + // Change focus to the account tree first so that any 'onchange' handlers + // on elements in the current page have a chance to run before the page + // is saved and replaced by the new one. + tree.focus(); + } + + // Provide opportunity to do cleanups or checks when the current page is being left. + if ("onLeave" in top.frames.contentFrame) { + top.frames.contentFrame.onLeave(); + } + + // save the previous page + savePage(currentAccount); + + let changeAccount = account != currentAccount; + + if (changeView) { + selectServer(account.incomingServer, pageId); + } + + if (pageId != currentPageId) { + // loading a complete different page + + // prevent overwriting with bad stuff + currentAccount = currentPageId = null; + + pendingAccount = account; + pendingPageId = pageId; + loadPage(pageId); + } else if (changeAccount) { + // same page, different server + restorePage(pageId, account); + } + + return true; +} + +// page has loaded +function onPanelLoaded(pageId) { + if (pageId != pendingPageId) { + // if we're reloading the current page, we'll assume the + // page has asked itself to be completely reloaded from + // the prefs. to do this, clear out the the old entry in + // the account data, and then restore theh page + if (pageId == currentPageId) { + var serverId = currentAccount + ? currentAccount.incomingServer.serverURI + : "global"; + delete accountArray[serverId]; + restorePage(currentPageId, currentAccount); + } + } else { + restorePage(pendingPageId, pendingAccount); + } + + // probably unnecessary, but useful for debugging + pendingAccount = null; + pendingPageId = null; +} + +function pageURL(pageId) { + // If we have a special non account manager pane (e.g. about:blank), + // do not translate it into ChromePackageName URL. + if (!pageId.startsWith("am-")) { + return pageId; + } + + let chromePackageName; + try { + // we could compare against "main","server","copies","offline","addressing", + // "smtp" and "advanced" first to save the work, but don't, + // as some of these might be turned into extensions (for thunderbird) + let packageName = pageId.split("am-")[1].split(".xhtml")[0]; + chromePackageName = MailServices.accounts.getChromePackageName(packageName); + } catch (ex) { + chromePackageName = "messenger"; + } + return "chrome://" + chromePackageName + "/content/" + pageId; +} + +function loadPage(pageId) { + const loadURIOptions = { + triggeringPrincipal: Services.scriptSecurityManager.getSystemPrincipal(), + }; + document + .getElementById("contentFrame") + .webNavigation.fixupAndLoadURIString(pageURL(pageId), loadURIOptions); +} + +// save the values of the widgets to the given server +function savePage(account) { + if (!account) { + return; + } + + // tell the page that it's about to save + if ("onSave" in top.frames.contentFrame) { + top.frames.contentFrame.onSave(); + } + + var accountValues = getValueArrayFor(account); + if (!accountValues) { + return; + } + // Reset accountArray so that only the current page will be saved. This is + // needed to prevent resetting prefs unintentionally. An example is when + // changing username/hostname, MsgIncomingServer.jsm will modify identities, + // without this, identities changes may be reverted to old values in + // accountArray. + accountArray = {}; + accountValues = {}; + let serverId = account.incomingServer.serverURI; + accountArray[serverId] = accountValues; + accountArray[serverId]._account = account; + + var pageElements = getPageFormElements(); + if (!pageElements) { + return; + } + + // store the value in the account + for (let i = 0; i < pageElements.length; i++) { + if (pageElements[i].id) { + let vals = pageElements[i].id.split("."); + if (vals.length >= 2) { + let type = vals[0]; + let slot = pageElements[i].id.slice(type.length + 1); + + setAccountValue( + accountValues, + type, + slot, + getFormElementValue(pageElements[i]) + ); + } + } + } +} + +function setAccountValue(accountValues, type, slot, value) { + if (!(type in accountValues)) { + accountValues[type] = {}; + } + + accountValues[type][slot] = value; +} + +function getAccountValue( + account, + accountValues, + type, + slot, + preftype, + isGeneric +) { + if (!(type in accountValues)) { + accountValues[type] = {}; + } + + // fill in the slot from the account if necessary + if ( + !(slot in accountValues[type]) || + accountValues[type][slot] == undefined + ) { + var server; + if (account) { + server = account.incomingServer; + } + var source = null; + try { + if (type == "identity") { + source = account.defaultIdentity; + } else if (type == "server") { + source = account.incomingServer; + } else if (type == "pop3") { + source = server.QueryInterface(Ci.nsIPop3IncomingServer); + } else if (type == "imap") { + source = server.QueryInterface(Ci.nsIImapIncomingServer); + } else if (type == "none") { + source = server.QueryInterface(Ci.nsINoIncomingServer); + } else if (type == "nntp") { + source = server.QueryInterface(Ci.nsINntpIncomingServer); + } else if (type == "smtp") { + source = MailServices.smtp.defaultServer; + } + } catch (ex) {} + + if (source) { + if (isGeneric) { + if (!(type in gGenericAttributeTypes)) { + gGenericAttributeTypes[type] = {}; + } + + // we need the preftype later, for setting when we save. + gGenericAttributeTypes[type][slot] = preftype; + var methodName = "get"; + switch (preftype) { + case "int": + methodName += "Int"; + break; + case "wstring": + methodName += "Unichar"; + break; + case "string": + methodName += "Char"; + break; + case "bool": + methodName += "Bool"; + break; + default: + dump("unexpected preftype: " + preftype + "\n"); + break; + } + methodName += methodName + "Value" in source ? "Value" : "Attribute"; + accountValues[type][slot] = source[methodName](slot); + } else if (slot in source) { + accountValues[type][slot] = source[slot]; + } else { + accountValues[type][slot] = null; + } + } else { + accountValues[type][slot] = null; + } + } + return accountValues[type][slot]; +} + +// restore the values of the widgets from the given server +function restorePage(pageId, account) { + if (!account) { + return; + } + + var accountValues = getValueArrayFor(account); + if (!accountValues) { + return; + } + + if ("onPreInit" in top.frames.contentFrame) { + top.frames.contentFrame.onPreInit(account, accountValues); + } + + var pageElements = getPageFormElements(); + if (!pageElements) { + return; + } + + // restore the value from the account + for (let i = 0; i < pageElements.length; i++) { + if (pageElements[i].id) { + let vals = pageElements[i].id.split("."); + if (vals.length >= 2) { + let type = vals[0]; + let slot = pageElements[i].id.slice(type.length + 1); + + // buttons are lockable, but don't have any data so we skip that part. + // elements that do have data, we get the values at poke them in. + if (pageElements[i].localName != "button") { + var value = getAccountValue( + account, + accountValues, + type, + slot, + pageElements[i].getAttribute("preftype"), + pageElements[i].getAttribute("genericattr") == "true" + ); + setFormElementValue(pageElements[i], value); + } + var element = pageElements[i]; + switch (type) { + case "identity": + element.identitykey = account.defaultIdentity.key; + break; + case "pop3": + case "imap": + case "nntp": + case "server": + element.serverkey = account.incomingServer.key; + break; + case "smtp": + if (MailServices.smtp.defaultServer) { + element.serverkey = MailServices.smtp.defaultServer.key; + } + break; + } + var isLocked = getAccountValueIsLocked(pageElements[i]); + setEnabled(pageElements[i], !isLocked); + } + } + } + + // tell the page that new values have been loaded + if ("onInit" in top.frames.contentFrame) { + top.frames.contentFrame.onInit(pageId, account.incomingServer.serverURI); + } + + // everything has succeeded, vervied by setting currentPageId + currentPageId = pageId; + currentAccount = account; +} + +/** + * Gets the value of a widget in current the account settings page, + * automatically setting the right property of it depending on element type. + * + * @param {HTMLInputElement} formElement - An input element. + */ +function getFormElementValue(formElement) { + try { + var type = formElement.localName; + if (type == "checkbox") { + if (formElement.getAttribute("reversed")) { + return !formElement.checked; + } + return formElement.checked; + } + if (type == "input" && formElement.getAttribute("datatype") == "nsIFile") { + if (formElement.value) { + let localfile = Cc["@mozilla.org/file/local;1"].createInstance( + Ci.nsIFile + ); + + localfile.initWithPath(formElement.value); + return localfile; + } + return null; + } + if (type == "input" || "value" in formElement) { + return formElement.value.trim(); + } + return null; + } catch (ex) { + console.error("getFormElementValue failed, ex=" + ex + "\n"); + } + return null; +} + +/** + * Sets the value of a widget in current the account settings page, + * automatically setting the right property of it depending on element type. + * + * @param {HTMLInputElement} formElement - An input element. + * @param {string|nsIFile} value - The value to store in the element. + */ +function setFormElementValue(formElement, value) { + var type = formElement.localName; + if (type == "checkbox") { + if (value == null) { + formElement.checked = false; + } else if (formElement.getAttribute("reversed")) { + formElement.checked = !value; + } else { + formElement.checked = value; + } + } else if (type == "radiogroup" || type == "menulist") { + if (value == null) { + formElement.selectedIndex = 0; + } else { + formElement.value = value; + } + } else if ( + type == "input" && + formElement.getAttribute("datatype") == "nsIFile" + ) { + // handle nsIFile + if (value) { + let localfile = value.QueryInterface(Ci.nsIFile); + try { + formElement.value = localfile.path; + } catch (ex) { + dump("Still need to fix uninitialized nsIFile problem!\n"); + } + } else { + formElement.value = ""; + } + } else if (type == "input") { + if (value == null) { + formElement.value = null; + } else { + formElement.value = value; + } + } else if (type == "label") { + formElement.value = value || ""; + } else if (value == null) { + // let the form figure out what to do with it + formElement.value = null; + } else { + formElement.value = value; + } +} + +// +// conversion routines - get data associated +// with a given pageId, serverId, etc +// + +// helper routine for account manager panels to get the current account for the selected server +function getCurrentAccount() { + return currentAccount; +} + +/** + * Get the array of persisted form elements for the given page. + */ +function getPageFormElements() { + // Uses getElementsByAttribute() which returns a live NodeList which is usually + // faster than e.g. querySelector(). + if ("getElementsByAttribute" in top.frames.contentFrame.document) { + return top.frames.contentFrame.document.getElementsByAttribute( + "wsm_persist", + "true" + ); + } + + return null; +} + +/** + * Get a single persisted form element in the current page. + * + * @param {srtring} aId - ID of the element requested. + */ +function getPageFormElement(aId) { + let elem = top.frames.contentFrame.document.getElementById(aId); + if (elem && elem.getAttribute("wsm_persist") == "true") { + return elem; + } + + return null; +} + +// get the value array for the given account +function getValueArrayFor(account) { + var serverId = account ? account.incomingServer.serverURI : "global"; + + if (!(serverId in accountArray)) { + accountArray[serverId] = {}; + accountArray[serverId]._account = account; + } + + return accountArray[serverId]; +} + +/** + * Sets the name of the account rowitem in the tree pane. + * + * @param {string} aAccountKey - The key of the account to change. + * @param {string} aLabel - The value of the label to set. + */ +function setAccountLabel(aAccountKey, aLabel) { + let row = document.getElementById(aAccountKey); + if (row) { + row.setAttribute("aria-label", aLabel); + row.title = aLabel; + row.querySelector(".name").textContent = aLabel; + } + rebuildAccountTree(false); +} + +var gAccountTree = { + load() { + this._build(); + + let mainTree = document.getElementById("accounttree"); + mainTree.__defineGetter__("_orderableChildren", function () { + let rows = [...this.children]; + rows.pop(); + return rows; + }); + mainTree.addEventListener("ordering", event => { + if (!event.detail || event.detail.id == "smtp") { + event.preventDefault(); + } + }); + mainTree.addEventListener("ordered", event => { + let accountKeyList = Array.from(mainTree.children, row => row.id); + accountKeyList.pop(); // Remove SMTP. + MailServices.accounts.reorderAccounts(accountKeyList); + rebuildAccountTree(); + }); + mainTree.addEventListener("expanded", event => { + this._dataStore.setValue( + document.documentURI, + event.target.id, + "open", + "true" + ); + }); + mainTree.addEventListener("collapsed", event => { + this._dataStore.setValue( + document.documentURI, + event.target.id, + "open", + "false" + ); + }); + + MailServices.accounts.addIncomingServerListener(this); + }, + unload() { + MailServices.accounts.removeIncomingServerListener(this); + }, + onServerLoaded(server) { + // We assume the newly appeared server was created by the user so we select + // it in the tree. + this._build(server); + }, + onServerUnloaded(aServer) { + this._build(); + }, + onServerChanged(aServer) {}, + + _dataStore: Services.xulStore, + + /** + * Retrieve from XULStore.json whether the account should be expanded (open) + * in the account tree. + * + * @param {string} aAccountKey - Key of the account to check. + */ + _getAccountOpenState(aAccountKey) { + if (!this._dataStore.hasValue(document.documentURI, aAccountKey, "open")) { + // If there was no value stored, use opened state. + return "true"; + } + // Retrieve the persisted value from XULStore.json. + // It is stored under the URI of the current document and ID of the XUL element. + return this._dataStore.getValue(document.documentURI, aAccountKey, "open"); + }, + + _build(newServer) { + var bundle = document.getElementById("bundle_prefs"); + function getString(aString) { + return bundle.getString(aString); + } + var panels = [ + { string: getString("prefPanel-server"), src: "am-server.xhtml" }, + { string: getString("prefPanel-copies"), src: "am-copies.xhtml" }, + { + string: getString("prefPanel-synchronization"), + src: "am-offline.xhtml", + }, + { string: getString("prefPanel-diskspace"), src: "am-offline.xhtml" }, + { string: getString("prefPanel-addressing"), src: "am-addressing.xhtml" }, + { string: getString("prefPanel-junk"), src: "am-junk.xhtml" }, + ]; + + let accounts = FolderUtils.allAccountsSorted(false); + + let mainTree = document.getElementById("accounttree"); + // Clear off all children... + while (mainTree.hasChildNodes()) { + mainTree.lastChild.remove(); + } + + for (let account of accounts) { + let accountName = null; + let accountKey = account.key; + let amChrome = "about:blank"; + let panelsToKeep = []; + let server = null; + + // This "try {} catch {}" block is intentionally very long to catch + // unknown exceptions and confine them to this single account. + // This may happen from broken accounts. See e.g. bug 813929. + // Other accounts can still be shown properly if they are valid. + try { + server = account.incomingServer; + + if ( + server.type == "im" && + !Services.prefs.getBoolPref("mail.chat.enabled") + ) { + continue; + } + + accountName = server.prettyName; + + // Now add our panels. + let idents = MailServices.accounts.getIdentitiesForServer(server); + if (idents.length) { + panelsToKeep.push(panels[0]); // The server panel is valid + panelsToKeep.push(panels[1]); // also the copies panel + panelsToKeep.push(panels[4]); // and addressing + } + + // Everyone except News, RSS and IM has a junk panel + // XXX: unextensible! + // The existence of server.spamSettings can't currently be used for this. + if ( + server.type != "nntp" && + server.type != "rss" && + server.type != "im" + ) { + panelsToKeep.push(panels[5]); + } + + // Check offline/diskspace support level. + let diskspace = server.supportsDiskSpace; + if (server.offlineSupportLevel >= 10 && diskspace) { + panelsToKeep.push(panels[2]); + } else if (diskspace) { + panelsToKeep.push(panels[3]); + } + + // extensions + const CATEGORY = "mailnews-accountmanager-extensions"; + for (let { data } of Services.catMan.enumerateCategory(CATEGORY)) { + try { + let svc = Cc[ + Services.catMan.getCategoryEntry(CATEGORY, data) + ].getService(Ci.nsIMsgAccountManagerExtension); + if (svc.showPanel(server)) { + let bundleName = + "chrome://" + + svc.chromePackageName + + "/locale/am-" + + svc.name + + ".properties"; + let bundle = Services.strings.createBundle(bundleName); + let title = bundle.GetStringFromName("prefPanel-" + svc.name); + panelsToKeep.push({ + string: title, + src: "am-" + svc.name + ".xhtml", + }); + } + } catch (e) { + // Fetching of this extension panel failed so do not show it, + // just log error. + let extName = data || "(unknown)"; + console.error( + "Error accessing panel from extension '" + extName + "': " + e + ); + } + } + amChrome = server.accountManagerChrome; + } catch (e) { + // Show only a placeholder in the account list saying this account + // is broken, with no child panels. + let accountID = accountName || accountKey; + console.error("Error accessing account " + accountID + ": " + e); + accountName = "Invalid account " + accountID; + panelsToKeep.length = 0; + } + + // Create the top level tree-item. + let treeitem = document + .getElementById("accountTreeItem") + .content.firstElementChild.cloneNode(true); + mainTree.appendChild(treeitem); + treeitem.setAttribute("aria-label", accountName); + treeitem.title = accountName; + treeitem.querySelector(".name").textContent = accountName; + treeitem.setAttribute("PageTag", amChrome); + // Add icons based on account type. + if (server) { + treeitem.classList.add("serverType-" + server.type); + if (server.isSecure) { + treeitem.classList.add("isSecure"); + } + // For IM accounts, we can try to fetch a protocol specific icon. + if (server.type == "im") { + treeitem.querySelector(".icon").style.backgroundImage = + "url(" + + ChatIcons.getProtocolIconURI( + server.wrappedJSObject.imAccount.protocol + ) + + ")"; + treeitem.id = accountKey; + } + } + + if (panelsToKeep.length > 0) { + let treekids = treeitem.querySelector("ul"); + for (let panel of panelsToKeep) { + let kidtreeitem = document.createElement("li"); + kidtreeitem.title = panel.string; + treekids.appendChild(kidtreeitem); + let kidtreerow = document.createElement("div"); + kidtreeitem.appendChild(kidtreerow); + let kidtreecell = document.createElement("span"); + kidtreecell.classList.add("name"); + kidtreecell.tabIndex = -1; + kidtreerow.appendChild(kidtreecell); + kidtreecell.textContent = panel.string; + kidtreeitem.setAttribute("PageTag", panel.src); + kidtreeitem._account = account; + kidtreeitem.id = `${accountKey}/${panel.src}`; + } + treeitem.id = accountKey; + // Load the 'open' state of the account from XULStore.json. + if (this._getAccountOpenState(accountKey) != "true") { + treeitem.classList.add("collapsed"); + } + } + treeitem._account = account; + } + + markDefaultServer(MailServices.accounts.defaultAccount, null); + + // Now add the outgoing server node. + let treeitem = document + .getElementById("accountTreeItem") + .content.firstElementChild.cloneNode(true); + mainTree.appendChild(treeitem); + treeitem.id = "smtp"; + treeitem.querySelector(".name").textContent = getString("prefPanel-smtp"); + treeitem.setAttribute("PageTag", "am-smtp.xhtml"); + treeitem.classList.add("serverType-smtp"); + + // If a new server was created, select the server after rebuild of the tree. + if (newServer) { + setTimeout(selectServer, 0, newServer); + } + }, +}; diff --git a/comm/mailnews/base/prefs/content/AccountManager.xhtml b/comm/mailnews/base/prefs/content/AccountManager.xhtml new file mode 100644 index 0000000000..7769ff730b --- /dev/null +++ b/comm/mailnews/base/prefs/content/AccountManager.xhtml @@ -0,0 +1,171 @@ +<?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 html [ + <!ENTITY % accountManagerDTD SYSTEM "chrome://messenger/locale/AccountManager.dtd"> + %accountManagerDTD; + <!ENTITY % utilityDTD SYSTEM "chrome://communicator/locale/utilityOverlay.dtd"> + %utilityDTD; +]> + +<html xmlns="http://www.w3.org/1999/xhtml" + xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + xmlns:html="http://www.w3.org/1999/xhtml" + scrolling="false"> +<head> + <title>&accountManagerTitle.label;</title> + + <meta http-equiv="Content-Security-Policy" content="default-src chrome:; script-src chrome: 'unsafe-inline'; img-src chrome: moz-icon: https: data:; style-src chrome: data: 'unsafe-inline'; object-src 'none'" /> + <meta name="color-scheme" content="light dark" /> + + <link rel="icon" href="chrome://messenger/skin/icons/new/compact/account-settings.svg" /> + + <link rel="stylesheet" href="chrome://messenger/skin/inContentDialog.css" /> + <link rel="stylesheet" href="chrome://messenger/skin/icons.css" /> + <link rel="stylesheet" href="chrome://messenger/skin/folderPane.css" /> + <link rel="stylesheet" href="chrome://messenger/skin/shared/tree-listbox.css" /> + <link rel="stylesheet" href="chrome://messenger/skin/shared/accountManager.css" /> + + <link rel="localization" href="branding/brand.ftl" /> + <link rel="localization" href="messenger/accountManager.ftl" /> + <link rel="localization" href="messenger/addressbook/aboutAddressBook.ftl" /> + + <script defer="defer" src="chrome://messenger/content/mailCore.js"></script> + <script defer="defer" src="chrome://communicator/content/utilityOverlay.js"></script> + <script defer="defer" src="chrome://messenger/content/accountUtils.js"></script> + <script defer="defer" src="chrome://messenger/content/am-prefs.js"></script> + <script defer="defer" src="chrome://messenger/content/AccountManager.js"></script> + <script defer="defer" src="chrome://messenger/content/amUtils.js"></script> + <script defer="defer" src="chrome://messenger/content/tree-listbox.js"></script> + <script> + // FIXME: move to script file. + window.addEventListener("load", event => { onLoad(event); }); + window.addEventListener("unload", event => { onUnload(); }); + </script> +</head> +<html:body xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + <stringbundle id="bundle_brand" src="chrome://branding/locale/brand.properties"/> + <stringbundle id="bundle_prefs" src="chrome://messenger/locale/prefs.properties"/> + <html:aside id="accountTreeBox"> + <html:ol is="orderable-tree-listbox" id="accounttree" class="no-overscroll" + role="tree" + flex="1" + onselect="onAccountTreeSelect(null, null);"> + </html:ol> + <template id="accountTreeItem" xmlns="http://www.w3.org/1999/xhtml"> + <li> + <div draggable="true"> + <div class="twisty"> + <img class="twisty-icon" alt="" + src="chrome://global/skin/icons/arrow-down-12.svg" /> + </div> + <div class="icon"></div> + <span class="name" tabindex="-1"></span> + </div> + <ul></ul> + </li> + </template> + + <button id="accountActionsButton" type="menu" + label="&accountActionsButton.label;" + accesskey="&accountActionsButton.accesskey;"> + <menupopup id="accountActionsDropdown" + onpopupshowing="initAccountActionsButtons(this);"> + <menuitem id="accountActionsAddMailAccount" + label="&addMailAccountButton.label;" + accesskey="&addMailAccountButton.accesskey;" + prefstring="mail.disable_new_account_addition" + oncommand="openAccountSetupTab();"/> + <menuitem id="accountActionsAddIMAccount" + label="&addIMAccountButton.label;" + accesskey="&addIMAccountButton.accesskey;" + prefstring="mail.disable_new_account_addition" + oncommand="AddIMAccount(event); event.stopPropagation();"/> + <menuitem id="accountActionsAddFeedAccount" + label="&addFeedAccountButton.label;" + accesskey="&addFeedAccountButton.accesskey;" + prefstring="mail.disable_new_account_addition" + oncommand="AddFeedAccount(event); event.stopPropagation();"/> + <menuitem id="accountActionsAddOtherAccount" + data-l10n-id="account-action-add-newsgroup-account" + prefstring="mail.disable_new_account_addition" + oncommand="openNewsgroupAccountWizard(); event.stopPropagation();"/> + <menuseparator id="accountActionsDropdownSep1"/> + <menuitem id="accountActionsDropdownSetDefault" + label="&setDefaultButton.label;" + accesskey="&setDefaultButton.accesskey;" + prefstring="mail.disable_button.set_default_account" + oncommand="onSetDefault(event); event.stopPropagation();"/> + <menuitem id="accountActionsDropdownRemove" + label="&removeButton.label;" + accesskey="&removeButton.accesskey;" + prefstring="mail.disable_button.delete_account" + oncommand="onRemoveAccount(event); event.stopPropagation();"/> + </menupopup> + </button> + + <vbox class="sidebar-footer-list"> + <html:a id="prefsButton" class="sidebar-footer-link" + onclick="openOptionsDialog();"> + <html:img class="sidebar-footer-icon" + src="chrome://messenger/skin/icons/new/compact/settings.svg" alt="" /> + <label class="sidebar-footer-label" + data-l10n-id="open-preferences-sidebar-button2" + flex="1"/> + </html:a> + + <html:a id="addonsButton" class="sidebar-footer-link" + onclick="window.browsingContext.topChromeWindow.openAddonsMgr();"> + <html:img class="sidebar-footer-icon" + src="chrome://messenger/skin/icons/new/compact/extension.svg" + alt="" /> + <label class="sidebar-footer-label" + data-l10n-id="open-addons-sidebar-button" + flex="1"/> + </html:a> + </vbox> + </html:aside> + <html:aside> + <iframe id="contentFrame" name="contentFrame" /> + </html:aside> + + <stack id="dialogStack" hidden="true"/> + <vbox id="dialogTemplate" + class="dialogOverlay" + align="center" + pack="center" + topmost="true" + hidden="true"> + <vbox class="dialogBox" + pack="end" + role="dialog" + aria-labelledby="dialogTitle"> + <hbox class="dialogTitleBar" align="center"> + <label class="dialogTitle" flex="1"/> + <button class="dialogClose close-icon" + aria-label="&accountManagerCloseButton.label;"/> + </hbox> + <browser class="dialogFrame" + autoscroll="false" + disablehistory="true"/> + </vbox> + </vbox> + + <dialog xmlns="http://www.w3.org/1999/xhtml" id="editVCardDialog"> + <form autocomplete="off"> +#include ../../../../mail/components/addrbook/content/vcard-edit/vCardTemplates.inc.xhtml + <vcard-edit class="edit"/> + <menu class="dialog-menu-container"> + <button type="reset" class="cancel" + data-l10n-id="edit-vcard-dialog-cancel-button" + data-l10n-attrs="accesskey"></button> + <button type="submit" class="accept primary" + data-l10n-id="edit-vcard-dialog-accept-button" + data-l10n-attrs="accesskey"></button> + </menu> + </form> + </dialog> +</html:body> +</html> diff --git a/comm/mailnews/base/prefs/content/AccountWizard.js b/comm/mailnews/base/prefs/content/AccountWizard.js new file mode 100644 index 0000000000..69047c3f9e --- /dev/null +++ b/comm/mailnews/base/prefs/content/AccountWizard.js @@ -0,0 +1,605 @@ +/* -*- Mode: JavaScript; 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/. */ + +/* import-globals-from accountUtils.js */ +/* import-globals-from amUtils.js */ +/* import-globals-from aw-identity.js */ +/* import-globals-from aw-incoming.js */ +/* import-globals-from aw-accname.js */ +/* import-globals-from aw-done.js */ + +/* NOTE: This Account Wizard is *only* for Newsgroup accounts. + * Historically, it was a generic Account Wizard, hence the generic naming. + */ + +/* + data flow into the account wizard like this: + + For new accounts: + * pageData -> accountData -> createAccount -> finishAccount + + for "unfinished accounts" + * account -> accountData -> pageData -> accountData -> finishAccount +*/ + +var { MailServices } = ChromeUtils.import( + "resource:///modules/MailServices.jsm" +); +var { NntpUtils } = ChromeUtils.import("resource:///modules/NntpUtils.jsm"); + +var contentWindow; + +var gPageData; + +var nsIMsgIdentity = Ci.nsIMsgIdentity; +var nsIMsgIncomingServer = Ci.nsIMsgIncomingServer; +var gPrefsBundle, gMessengerBundle; + +// the current nsIMsgAccount +var gCurrentAccount; + +// The default account before we create a new account. +// We need to store this as just asking for the default account may switch +// it to the newly created one if there was none before. +var gDefaultAccount; + +// the current associative array that +// will eventually be dumped into the account +var gCurrentAccountData = null; + +// default picker mode for copies and folders +var gDefaultSpecialFolderPickerMode = "0"; + +// event handlers +function onAccountWizardLoad() { + document.querySelector("wizard").addEventListener("wizardcancel", onCancel); + document + .querySelector("wizard") + .addEventListener("wizardfinish", FinishAccount); + let identityPage = document.getElementById("identitypage"); + identityPage.addEventListener("pageshow", identityPageInit); + identityPage.addEventListener("pageadvanced", identityPageUnload); + identityPage.next = "newsserver"; + let newsserverPage = document.getElementById("newsserver"); + newsserverPage.addEventListener("pageshow", incomingPageInit); + newsserverPage.addEventListener("pageadvanced", incomingPageUnload); + newsserverPage.next = "accnamepage"; + let accnamePage = document.getElementById("accnamepage"); + accnamePage.addEventListener("pageshow", acctNamePageInit); + accnamePage.addEventListener("pageadvanced", acctNamePageUnload); + accnamePage.next = "done"; + let donePage = document.getElementById("done"); + donePage.addEventListener("pageshow", donePageInit); + + gPrefsBundle = document.getElementById("bundle_prefs"); + gMessengerBundle = document.getElementById("bundle_messenger"); + + checkForInvalidAccounts(); + + // It is fine if there is no default account, this is expected the first + // time you launch mail on a new profile. + gDefaultAccount = MailServices.accounts.defaultAccount; + + identityPageInit(); +} + +function onCancel() { + if ("ActivationOnCancel" in this && this.ActivationOnCancel()) { + return false; + } + var firstInvalidAccount = getInvalidAccounts( + MailServices.accounts.accounts + ).find(account => account.incomingServer.type == "nntp"); + var closeWizard = true; + + // if the user cancels the the wizard when it pops up because of + // an invalid account (example, a webmail account that activation started) + // we just force create it by setting some values and calling the FinishAccount() + // see bug #47521 for the full discussion + if (firstInvalidAccount) { + var pageData = GetPageData(); + // set the fullName if it doesn't exist + if (!pageData.fullName) { + pageData.fullName = ""; + } + + // set the email if it doesn't exist + if (!pageData.email) { + pageData.email = "user@domain.invalid"; + } + + // call FinishAccount() and not onFinish(), since the "finish" + // button may be disabled + FinishAccount(); + } else if (!MailServices.accounts.accounts.length) { + // since this is not an invalid account + // really cancel if the user hits the "cancel" button + // if the length of the account list is less than 1, there are no accounts + let confirmMsg = gPrefsBundle.getString("cancelWizard"); + let confirmTitle = gPrefsBundle.getString("accountWizard"); + let result = Services.prompt.confirmEx( + window, + confirmTitle, + confirmMsg, + Services.prompt.BUTTON_TITLE_IS_STRING * Services.prompt.BUTTON_POS_0 + + Services.prompt.BUTTON_TITLE_IS_STRING * Services.prompt.BUTTON_POS_1, + gPrefsBundle.getString("WizardExit"), + gPrefsBundle.getString("WizardContinue"), + null, + null, + { value: 0 } + ); + + if (result == 1) { + closeWizard = false; + } + } + + return closeWizard; +} + +function FinishAccount() { + try { + var pageData = GetPageData(); + + var accountData = gCurrentAccountData; + + if (!accountData) { + accountData = {}; + } + + // we may need local folders before account is "Finished" + // if it's a pop3 account which defers to Local Folders. + verifyLocalFoldersAccount(); + + PageDataToAccountData(pageData, accountData); + + // we might be simply finishing another account + if (!gCurrentAccount) { + gCurrentAccount = createAccount(accountData); + } + + // transfer all attributes from the accountdata + finishAccount(gCurrentAccount, accountData); + + setupCopiesAndFoldersServer(gCurrentAccount, accountData); + + if (gCurrentAccount.incomingServer.canBeDefaultServer) { + EnableCheckMailAtStartUpIfNeeded(gCurrentAccount); + } + + // in case we crash, force us a save of the prefs file NOW + try { + MailServices.accounts.saveAccountInfo(); + } catch (ex) { + dump("Error saving account info: " + ex + "\n"); + } + let openerWindow = window.opener.top; + // The following block is the same as in feedAccountWizard.js. + if ("selectServer" in openerWindow) { + // Opened from Account Settings. + openerWindow.selectServer(gCurrentAccount.incomingServer); + } + + // Post a message to the main window on successful account setup. + openerWindow.postMessage("account-created", "*"); + + window.close(); + } catch (ex) { + dump("FinishAccount failed, " + ex + "\n"); + } +} + +// prepopulate pageData with stuff from accountData +// use: to prepopulate the wizard with account information +function AccountDataToPageData(accountData, pageData) { + var server = accountData.incomingServer; + pageData.hostname = server.hostName; + pageData.prettyName = server.prettyName || ""; + + var identity; + + if (accountData.identity) { + dump("This is an accountdata\n"); + identity = accountData.identity; + } else if (accountData.identities) { + identity = accountData.identities[0]; + dump("this is an account, id= " + identity + "\n"); + } + + pageData.email = identity.email || ""; + pageData.fullName = identity.fullName || ""; +} + +// take data from each page of pageData and dump it into accountData +// use: to put results of wizard into a account-oriented object +function PageDataToAccountData(pageData, accountData) { + if (!accountData.identity) { + accountData.identity = {}; + } + if (!accountData.incomingServer) { + accountData.incomingServer = {}; + } + + var identity = accountData.identity; + var server = accountData.incomingServer; + + if (pageData.email) { + identity.email = pageData.email; + } + if (pageData.fullName) { + identity.fullName = pageData.fullName; + } + + server.hostName = pageData.hostname; + if (pageData.prettyName) { + server.prettyName = pageData.prettyName; + } +} + +// given an accountData structure, create an account +// (but don't fill in any fields, that's for finishAccount() +function createAccount(accountData) { + let hostName = accountData.incomingServer.hostName; + // If we're here, the server must not be associated with any account, so reuse + // it. + let server = NntpUtils.findServer(hostName); + + if (!server) { + dump(`MailServices.accounts.createIncomingServer(${hostName})\n`); + // Create a (actual) server. + server = MailServices.accounts.createIncomingServer(null, hostName, "nntp"); + } + + dump("MailServices.accounts.createAccount()\n"); + // Create an account. + let account = MailServices.accounts.createAccount(); + + // only create an identity for this account if we really have one + // (use the email address as a check) + if (accountData.identity && accountData.identity.email) { + dump("MailServices.accounts.createIdentity()\n"); + // Create an identity. + let identity = MailServices.accounts.createIdentity(); + + // New nntp identities should use plain text by default; + // we want that GNKSA (The Good Net-Keeping Seal of Approval). + identity.composeHtml = false; + + account.addIdentity(identity); + } + + // we mark the server as invalid so that the account manager won't + // tell RDF about the new server - it's not quite finished getting + // set up yet, in particular, the deferred storage pref hasn't been set. + server.valid = false; + // Set the new account to use the new server. + account.incomingServer = server; + server.valid = true; + return account; +} + +// given an accountData structure, copy the data into the +// given account, incoming server, and so forth +function finishAccount(account, accountData) { + if (accountData.incomingServer) { + var destServer = account.incomingServer; + var srcServer = accountData.incomingServer; + copyObjectToInterface(destServer, srcServer, true); + + // See if there are any protocol-specific attributes. + // If so, we use the type to get the IID, QueryInterface + // as appropriate, then copy the data over. + const typeProperty = "ServerType-" + srcServer.type; + let serverAttrs = + typeProperty in srcServer ? srcServer[typeProperty] : null; + dump(`srcServer.${typeProperty} = ${serverAttrs}\n`); + if (serverAttrs) { + // handle server-specific stuff + var IID; + try { + IID = destServer.protocolInfo.serverIID; + } catch (ex) { + console.error("Could not get IID for " + srcServer.type + ": " + ex); + } + + if (IID) { + let destProtocolServer = destServer.QueryInterface(IID); + let srcProtocolServer = srcServer["ServerType-" + srcServer.type]; + + dump("Copying over " + srcServer.type + "-specific data\n"); + copyObjectToInterface(destProtocolServer, srcProtocolServer, false); + } + } + + account.incomingServer.valid = true; + // hack to cause an account loaded notification now the server is valid + account.incomingServer = account.incomingServer; // eslint-disable-line no-self-assign + } + + // copy identity info + var destIdentity = account.identities.length ? account.identities[0] : null; + + if (destIdentity) { + // does this account have an identity? + if (accountData.identity && accountData.identity.email) { + // fixup the email address if we have a default domain + let emailArray = accountData.identity.email.split("@"); + if (emailArray.length < 2 && accountData.domain) { + accountData.identity.email += "@" + accountData.domain; + } + + copyObjectToInterface(destIdentity, accountData.identity, true); + destIdentity.valid = true; + } + + /** + * If signature file need to be set, get the path to the signature file. + * Signature files, if exist, are placed under default location. Get + * default files location for messenger using directory service. Signature + * file name should be extracted from the account data to build the complete + * path for signature file. Once the path is built, set the identity's signature pref. + */ + if (destIdentity.attachSignature) { + var sigFileName = accountData.signatureFileName; + let sigFile = MailServices.mailSession.getDataFilesDir("messenger"); + sigFile.append(sigFileName); + destIdentity.signature = sigFile; + } + } // if the account has an identity... + + if (this.FinishAccountHook != undefined) { + this.FinishAccountHook(accountData.domain); + } +} + +// Helper method used by copyObjectToInterface which attempts to set dest[attribute] as a generic +// attribute on the xpconnect object, src. +// This routine skips any attribute that begins with ServerType- +function setGenericAttribute(dest, src, attribute) { + if (!attribute.toLowerCase().startsWith("servertype-") && src[attribute]) { + switch (typeof src[attribute]) { + case "string": + dest.setUnicharAttribute(attribute, src[attribute]); + break; + case "boolean": + dest.setBoolAttribute(attribute, src[attribute]); + break; + case "number": + dest.setIntAttribute(attribute, src[attribute]); + break; + default: + dump( + "Error: No Generic attribute " + + attribute + + " found for: " + + dest + + "\n" + ); + break; + } + } +} + +// copy over all attributes from dest into src that already exist in src +// the assumption is that src is an XPConnect interface full of attributes +// @param useGenericFallback if we can't set an attribute directly on src, then fall back +// and try setting it generically. This assumes that src supports setIntAttribute, setUnicharAttribute +// and setBoolAttribute. +function copyObjectToInterface(dest, src, useGenericFallback) { + if (!dest) { + return; + } + if (!src) { + return; + } + + var attribute; + for (attribute in src) { + if (dest.__lookupSetter__(attribute)) { + if (dest[attribute] != src[attribute]) { + dest[attribute] = src[attribute]; + } + } else if (useGenericFallback) { + // fall back to setting the attribute generically + setGenericAttribute(dest, src, attribute); + } + } // for each attribute in src we want to copy +} + +// check if there already is a "Local Folders" +// if not, create it. +function verifyLocalFoldersAccount() { + var localMailServer = null; + try { + localMailServer = MailServices.accounts.localFoldersServer; + } catch (ex) { + // dump("exception in findserver: " + ex + "\n"); + localMailServer = null; + } + + try { + if (!localMailServer) { + // dump("Creating local mail account\n"); + // creates a copy of the identity you pass in + MailServices.accounts.createLocalMailAccount(); + try { + localMailServer = MailServices.accounts.localFoldersServer; + } catch (ex) { + dump( + "error! we should have found the local mail server after we created it.\n" + ); + localMailServer = null; + } + } + } catch (ex) { + dump("Error in verifyLocalFoldersAccount" + ex + "\n"); + } +} + +function setupCopiesAndFoldersServer(account, accountData) { + try { + var server = account.incomingServer; + + if (!account.identities.length) { + return false; + } + + let identity = account.identities[0]; + // For this server, do we default the folder prefs to this server, or to the "Local Folders" server + // If it's deferred, we use the local folders account. + var defaultCopiesAndFoldersPrefsToServer = + server.defaultCopiesAndFoldersPrefsToServer; + + var copiesAndFoldersServer = null; + if (defaultCopiesAndFoldersPrefsToServer) { + copiesAndFoldersServer = server; + } else { + if (!MailServices.accounts.localFoldersServer) { + dump("error! we should have a local mail server at this point\n"); + return false; + } + copiesAndFoldersServer = MailServices.accounts.localFoldersServer; + } + + setDefaultCopiesAndFoldersPrefs( + identity, + copiesAndFoldersServer, + accountData + ); + } catch (ex) { + // return false (meaning we did not setupCopiesAndFoldersServer) + // on any error + dump("Error in setupCopiesAndFoldersServer: " + ex + "\n"); + return false; + } + return true; +} + +function setDefaultCopiesAndFoldersPrefs(identity, server, accountData) { + var rootFolder = server.rootFolder; + + // we need to do this or it is possible that the server's draft, + // stationery fcc folder will not be in rdf + // + // this can happen in a couple cases + // 1) the first account we create, creates the local mail. since + // local mail was just created, it obviously hasn't been opened, + // or in rdf.. + // 2) the account we created is of a type where + // defaultCopiesAndFoldersPrefsToServer is true + // this since we are creating the server, it obviously hasn't been + // opened, or in rdf. + // + // this makes the assumption that the server's draft, stationery fcc folder + // are at the top level (ie subfolders of the root folder.) this works + // because we happen to be doing things that way, and if the user changes + // that, it will work because to change the folder, it must be in rdf, + // coming from the folder cache, in the worst case. + var msgFolder = rootFolder.QueryInterface(Ci.nsIMsgFolder); + + /** + * When a new account is created, folders 'Sent', 'Drafts' + * and 'Templates' are not created then, but created on demand at runtime. + * But we do need to present them as possible choices in the Copies and Folders + * UI. To do that, folder URIs have to be created and stored in the prefs file. + * So, if there is a need to build special folders, append the special folder + * names and create right URIs. + */ + var folderDelim = "/"; + + /* we use internal names known to everyone like Sent, Templates and Drafts */ + /* if folder names were already given in isp rdf, we use them, + otherwise we use internal names known to everyone like Sent, Templates and Drafts */ + + // Note the capital F, D and S! + var draftFolder = + accountData.identity && accountData.identity.DraftFolder + ? accountData.identity.DraftFolder + : "Drafts"; + var stationeryFolder = + accountData.identity && accountData.identity.StationeryFolder + ? accountData.identity.StationeryFolder + : "Templates"; + var fccFolder = + accountData.identity && accountData.identity.FccFolder + ? accountData.identity.FccFolder + : "Sent"; + + identity.draftFolder = msgFolder.server.serverURI + folderDelim + draftFolder; + identity.stationeryFolder = + msgFolder.server.serverURI + folderDelim + stationeryFolder; + identity.fccFolder = msgFolder.server.serverURI + folderDelim + fccFolder; + + // Note the capital F, D and S! + identity.fccFolderPickerMode = + accountData.identity && accountData.identity.FccFolder + ? 1 + : gDefaultSpecialFolderPickerMode; + identity.draftsFolderPickerMode = + accountData.identity && accountData.identity.DraftFolder + ? 1 + : gDefaultSpecialFolderPickerMode; + identity.tmplFolderPickerMode = + accountData.identity && accountData.identity.StationeryFolder + ? 1 + : gDefaultSpecialFolderPickerMode; +} + +function checkForInvalidAccounts() { + var firstInvalidAccount = getInvalidAccounts( + MailServices.accounts.accounts + ).find(account => account.incomingServer.type == "nntp"); + + if (firstInvalidAccount) { + var pageData = GetPageData(); + dump( + "We have an invalid account, " + + firstInvalidAccount + + ", let's use that!\n" + ); + gCurrentAccount = firstInvalidAccount; + + var accountData = {}; + accountData.incomingServer = firstInvalidAccount.incomingServer; + accountData.identity = firstInvalidAccount.identities[0]; + AccountDataToPageData(accountData, pageData); + + gCurrentAccountData = accountData; + } +} + +function getUsernameFromEmail(email) { + return email && email.substr(0, email.indexOf("@")); +} + +function GetPageData() { + if (!gPageData) { + gPageData = {}; + } + + return gPageData; +} + +// flush the XUL cache - just for debugging purposes - not called +function onFlush() { + Services.prefs.setBoolPref("nglayout.debug.disable_xul_cache", true); + Services.prefs.setBoolPref("nglayout.debug.disable_xul_cache", false); +} + +/** If there are no default accounts.. + * this is will be the new default, so enable + * check for mail at startup + */ +function EnableCheckMailAtStartUpIfNeeded(newAccount) { + // Check if default account existed. + // If no such account, make this one the default account + // and turn on the new mail check at startup for the current account + if (!gDefaultAccount) { + MailServices.accounts.defaultAccount = newAccount; + newAccount.incomingServer.loginAtStartUp = true; + newAccount.incomingServer.downloadOnBiff = true; + } +} diff --git a/comm/mailnews/base/prefs/content/AccountWizard.xhtml b/comm/mailnews/base/prefs/content/AccountWizard.xhtml new file mode 100644 index 0000000000..b58db60f60 --- /dev/null +++ b/comm/mailnews/base/prefs/content/AccountWizard.xhtml @@ -0,0 +1,161 @@ +<?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/. --> + +<!-- NOTE: This Account Wizard is *only* for Newsgroup accounts. Historically, + - it was a generic Account Wizard, hence the generic naming. --> + +<?xml-stylesheet href="chrome://messenger/skin/accountWizard.css" type="text/css"?> +<?xml-stylesheet type="text/css" href="chrome://messenger/skin/input-fields.css"?> +<?xml-stylesheet href="chrome://messenger/skin/shared/grid-layout.css" type="text/css"?> +<?xml-stylesheet href="chrome://messenger/skin/themeableDialog.css" type="text/css"?> + +<!DOCTYPE window [ +<!ENTITY % AccountWizardDTD SYSTEM "chrome://messenger/locale/AccountWizard.dtd"> +%AccountWizardDTD; +<!ENTITY % newsblogDTD SYSTEM "chrome://messenger-newsblog/locale/am-newsblog.dtd"> +%newsblogDTD; +]> + +<window id="AccountWizard" title="&windowTitle.label;" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + xmlns:html="http://www.w3.org/1999/xhtml" + onload="onAccountWizardLoad();" + style="width: 40em; height: 38em;" + lightweightthemes="true"> + <stringbundle id="bundle_prefs" src="chrome://messenger/locale/prefs.properties"/> + <stringbundle id="bundle_messenger" src="chrome://messenger/locale/messenger.properties"/> + <linkset> + <html:link rel="localization" href="toolkit/global/wizard.ftl"/> + </linkset> + + <script src="chrome://messenger/content/globalOverlay.js"/> + <script src="chrome://global/content/editMenuOverlay.js"/> + <script src="chrome://messenger/content/accountUtils.js"/> + <script src="chrome://messenger/content/amUtils.js"/> + <script src="chrome://messenger/content/AccountWizard.js"/> + <script src="chrome://messenger/content/aw-identity.js"/> + <script src="chrome://messenger/content/aw-incoming.js"/> + <script src="chrome://messenger/content/aw-accname.js"/> + <script src="chrome://messenger/content/aw-done.js"/> + + <wizard> + <!-- Identity page : Collects user's full name and email address --> + <wizardpage id="identitypage" pageid="identitypage" + label="&identityTitle.label;"> + <vbox> + <description flex="1">&identityDesc.label;</description> + <separator/> + <description flex="1">&fullnameDesc.label; &fullnameExample.label;</description> + <separator class="thin"/> + <hbox align="center" class="input-container"> + <label id="fullnameLabel" class="awIdentityLabel" value="&fullnameLabel.label;" + accesskey="&fullnameLabel.accesskey;" control="fullName"/> + <html:input id="fullName" + type="text" + required="required" + wsm_persist="true" + name="fullName" + class="input-inline" + aria-labelledby="fullnameLabel" + oninput="identityPageValidate();"/> + </hbox> + <separator/> + <vbox> + <hbox> + <description id="emailDescText" flex="1"/> + </hbox> + <hbox align="center"> + <label id="emailFieldLabel" + class="awIdentityLabel" + value="&emailLabel.label;" + accesskey="&emailLabel.accesskey;" + control="email"/> + <hbox class="uri-element input-container" align="center" flex="1"> + <html:input id="email" + type="email" + required="required" + wsm_persist="true" + name="email" + aria-labelledby="emailFieldLabel" + oninput="identityPageValidate();" + class="uri-element input-inline"/> + </hbox> + </hbox> + </vbox> + </vbox> + </wizardpage> + + <!-- News Server page : Collects the News groups server name --> + <wizardpage id="newsserver" pageid="newsserver" + label="&incomingTitle.label;"> + <vbox flex="1"> + <description>&newsServerNameDesc.label;</description> + <separator class="thin"/> + <hbox align="center" class="input-container"> + <label id="newsServerLabel" control="newsServer" + value="&newsServerLabel.label;" + accesskey="&newsServerLabel.accesskey;" + style="width: 8em;"/> + <html:input id="newsServer" + type="text" + wsm_persist="true" + class="uri-element input-inline" + aria-labelledby="newsServerLabel" + oninput="incomingPageValidate();"/> + </hbox> + </vbox> + </wizardpage> + + <!-- Account name page : User gets a choice to enter a pretty name for the account --> + <!-- Defaults : Mail account -> Email address, Newsgroup account -> Newsgroup server name --> + <wizardpage id="accnamepage" pageid="accnamepage" + label="&accnameTitle.label;"> + <vbox flex="1"> + <description>&accnameDesc.label;</description> + <separator class="thin"/> + <hbox align="center" class="input-container"> + <label id="prettyNameLabel" class="label" + value="&accnameLabel.label;" + style="width: 8em;" + accesskey="&accnameLabel.accesskey;" + control="prettyName"/> + <html:input id="prettyName" + type="text" + size="40" + wsm_persist="true" + class="input-inline" + aria-labelledby="prettyNameLabel" + oninput="acctNamePageValidate();"/> + </hbox> + </vbox> + </wizardpage> + + <!-- Done page : this page summarizes information collected to create a mail/news account --> + <wizardpage id="done" pageid="done" + label="&completionTitle.label;"> + <vbox flex="1"> + <description>&completionText.label;</description> + <separator class="thin"/> + <html:div class="grid-two-column-fr grid-items-baseline"> + <label id="account.name.label" flex="1" class="label" value="&accnameLabel.label;"/> + <label id="account.name.text" flex="1" class="label"/> + <label id="identity.email.label" flex="1" class="label" value="&emailLabel.label;"/> + <label id="identity.email.text" flex="1" class="label"/> + <label id="server.username.label" flex="1" class="label" value="&incomingUsername.label;"/> + <label id="server.username.text" flex="1" class="label"/> + <label id="newsServer.name.label" flex="1" class="label" value="&newsServerNamePrefix.label;"/> + <label id="newsServer.name.text" flex="1" class="label"/> + </html:div> + <separator/> + <spacer flex="1"/> +#ifndef XP_MACOSX + <description>&clickFinish.label;</description> +#else + <description>&clickFinish.labelMac;</description> +#endif + </vbox> + </wizardpage> + </wizard> +</window> diff --git a/comm/mailnews/base/prefs/content/SmtpServerEdit.js b/comm/mailnews/base/prefs/content/SmtpServerEdit.js new file mode 100644 index 0000000000..56d6076c5f --- /dev/null +++ b/comm/mailnews/base/prefs/content/SmtpServerEdit.js @@ -0,0 +1,241 @@ +/* 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 { cleanUpHostName, isLegalHostNameOrIP } = ChromeUtils.import( + "resource:///modules/hostnameUtils.jsm" +); +var { MailServices } = ChromeUtils.import( + "resource:///modules/MailServices.jsm" +); +var { OAuth2Providers } = ChromeUtils.import( + "resource:///modules/OAuth2Providers.jsm" +); + +var gSmtpServer; +var gSmtpUsername; +var gSmtpDescription; +var gSmtpUsernameLabel; +var gSmtpHostname; +var gSmtpPort; +var gSmtpAuthMethod; +var gSmtpSocketType; +var gPort; +var gDefaultPort; + +window.addEventListener("DOMContentLoaded", onLoad); +document.addEventListener("dialogaccept", onAccept); + +function onLoad() { + gSmtpServer = window.arguments[0].server; + initSmtpSettings(gSmtpServer); +} + +function onAccept(event) { + if (!isLegalHostNameOrIP(cleanUpHostName(gSmtpHostname.value))) { + let prefsBundle = document.getElementById("bundle_prefs"); + let brandBundle = document.getElementById("bundle_brand"); + let alertTitle = brandBundle.getString("brandShortName"); + let alertMsg = prefsBundle.getString("enterValidServerName"); + Services.prompt.alert(window, alertTitle, alertMsg); + + window.arguments[0].result = false; + event.preventDefault(); + return; + } + + // If we didn't have an SMTP server to initialize with, + // we must be creating one. + try { + if (!gSmtpServer) { + gSmtpServer = MailServices.smtp.createServer(); + window.arguments[0].addSmtpServer = gSmtpServer.key; + } + + saveSmtpSettings(gSmtpServer); + } catch (ex) { + console.error("Error saving smtp server: " + ex); + } + + window.arguments[0].result = true; +} + +function initSmtpSettings(server) { + gSmtpUsername = document.getElementById("smtpUsername"); + gSmtpDescription = document.getElementById("smtp.description"); + gSmtpUsernameLabel = document.getElementById("smtpUsernameLabel"); + gSmtpHostname = document.getElementById("smtp.hostname"); + gSmtpPort = document.getElementById("smtp.port"); + gSmtpAuthMethod = document.getElementById("smtp.authMethod"); + gSmtpSocketType = document.getElementById("smtp.socketType"); + gDefaultPort = document.getElementById("smtp.defaultPort"); + gPort = document.getElementById("smtp.port"); + + if (server) { + gSmtpHostname.value = server.hostname; + gSmtpDescription.value = server.description; + gSmtpPort.value = server.port; + gSmtpUsername.value = server.username; + gSmtpAuthMethod.value = server.authMethod; + gSmtpSocketType.value = server.socketType < 4 ? server.socketType : 1; + } else { + // New server, load default values. + gSmtpAuthMethod.value = Services.prefs.getIntPref( + "mail.smtpserver.default.authMethod" + ); + gSmtpSocketType.value = Services.prefs.getIntPref( + "mail.smtpserver.default.try_ssl" + ); + } + + // Although sslChanged will set a label for cleartext password, + // we need to use the long label so that we can size the dialog. + setLabelFromStringBundle("authMethod-no", "authNo"); + setLabelFromStringBundle( + "authMethod-password-encrypted", + "authPasswordEncrypted" + ); + setLabelFromStringBundle( + "authMethod-password-cleartext", + "authPasswordCleartextInsecurely" + ); + setLabelFromStringBundle("authMethod-kerberos", "authKerberos"); + setLabelFromStringBundle("authMethod-ntlm", "authNTLM"); + setLabelFromStringBundle("authMethod-oauth2", "authOAuth2"); + setLabelFromStringBundle("authMethod-anysecure", "authAnySecure"); + setLabelFromStringBundle("authMethod-any", "authAny"); + + window.sizeToContent(); + + sslChanged(false); + authMethodChanged(false); + + if (MailServices.smtp.defaultServer) { + onLockPreference(); + } + + // Hide OAuth2 option if we can't use it. + let details = server + ? OAuth2Providers.getHostnameDetails(server.hostname) + : null; + document.getElementById("authMethod-oauth2").hidden = !details; + + // Hide deprecated/hidden auth options, unless selected + hideUnlessSelected(document.getElementById("authMethod-anysecure")); + hideUnlessSelected(document.getElementById("authMethod-any")); + + // "STARTTLS, if available" is vulnerable to MITM attacks so we shouldn't + // allow users to choose it anymore. Hide the option unless the user already + // has it set. + hideUnlessSelected(document.getElementById("connectionSecurityType-1")); +} + +function hideUnlessSelected(element) { + element.hidden = !element.selected; +} + +function setLabelFromStringBundle(elementID, stringName) { + document.getElementById(elementID).label = document + .getElementById("bundle_messenger") + .getString(stringName); +} + +// Disables xul elements that have associated preferences locked. +function onLockPreference() { + try { + let allPrefElements = { + hostname: gSmtpHostname, + description: gSmtpDescription, + port: gSmtpPort, + authMethod: gSmtpAuthMethod, + try_ssl: gSmtpSocketType, + }; + disableIfLocked(allPrefElements); + } catch (e) { + // non-fatal + console.error("Error while getting locked prefs: " + e); + } +} + +/** + * Does the work of disabling an element given the array which contains + * id/prefstring pairs. + * + * @param {Element[]} prefstrArray - Elements to check. + * + * TODO: try to merge this with disableIfLocked function in am-offline.js (bug 755885) + */ +function disableIfLocked(prefstrArray) { + let smtpPrefBranch = Services.prefs.getBranch( + "mail.smtpserver." + MailServices.smtp.defaultServer.key + "." + ); + + for (let prefstring in prefstrArray) { + if (smtpPrefBranch.prefIsLocked(prefstring)) { + prefstrArray[prefstring].disabled = true; + } + } +} + +function saveSmtpSettings(server) { + if (server) { + server.hostname = cleanUpHostName(gSmtpHostname.value); + server.description = gSmtpDescription.value; + server.port = gSmtpPort.value; + server.authMethod = gSmtpAuthMethod.value; + server.username = gSmtpUsername.value; + server.socketType = gSmtpSocketType.value; + } +} + +function authMethodChanged(userAction) { + var noUsername = gSmtpAuthMethod.value == Ci.nsMsgAuthMethod.none; + gSmtpUsername.disabled = noUsername; + gSmtpUsernameLabel.disabled = noUsername; +} + +/** + * Resets the default port to SMTP or SMTPS, dependending on + * the |gSmtpSocketType| value, and sets the port to use to this default, + * if that's appropriate. + * + * @param {boolean} userAction - false for dialog initialization, + * true for user action. + */ +function sslChanged(userAction) { + const DEFAULT_SMTP_PORT = "587"; + const DEFAULT_SMTPS_PORT = "465"; + var socketType = gSmtpSocketType.value; + var otherDefaultPort; + var prevDefaultPort = gDefaultPort.value; + + if (socketType == Ci.nsMsgSocketType.SSL) { + gDefaultPort.value = DEFAULT_SMTPS_PORT; + otherDefaultPort = DEFAULT_SMTP_PORT; + } else { + gDefaultPort.value = DEFAULT_SMTP_PORT; + otherDefaultPort = DEFAULT_SMTPS_PORT; + } + + // If the port is not set, + // or the user is causing the default port to change, + // and the port is set to the default for the other protocol, + // then set the port to the default for the new protocol. + if ( + gPort.value == 0 || + (userAction && + gDefaultPort.value != prevDefaultPort && + gPort.value == otherDefaultPort) + ) { + gPort.value = gDefaultPort.value; + } + + // switch "insecure password" label + setLabelFromStringBundle( + "authMethod-password-cleartext", + socketType == Ci.nsMsgSocketType.SSL || + socketType == Ci.nsMsgSocketType.alwaysSTARTTLS + ? "authPasswordCleartextViaSSL" + : "authPasswordCleartextInsecurely" + ); +} diff --git a/comm/mailnews/base/prefs/content/SmtpServerEdit.xhtml b/comm/mailnews/base/prefs/content/SmtpServerEdit.xhtml new file mode 100644 index 0000000000..d0bbda97c6 --- /dev/null +++ b/comm/mailnews/base/prefs/content/SmtpServerEdit.xhtml @@ -0,0 +1,208 @@ +<?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/input-fields.css" type="text/css"?> +<?xml-stylesheet href="chrome://messenger/skin/accountManage.css" type="text/css"?> + +<!DOCTYPE html SYSTEM "chrome://messenger/locale/smtpEditOverlay.dtd"> + +<html + xmlns="http://www.w3.org/1999/xhtml" + xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + xmlns:html="http://www.w3.org/1999/xhtml" + scrolling="false" +> + <head> + <title>&smtpEditTitle.label;</title> + <script + defer="defer" + src="chrome://messenger/content/globalOverlay.js" + ></script> + <script + defer="defer" + src="chrome://global/content/editMenuOverlay.js" + ></script> + <script + defer="defer" + src="chrome://messenger/content/SmtpServerEdit.js" + ></script> + </head> + <html:body + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + > + <dialog style="width: 100vw; height: 100vh"> + <stringbundle + id="bundle_prefs" + src="chrome://messenger/locale/prefs.properties" + /> + <stringbundle + id="bundle_brand" + src="chrome://branding/locale/brand.properties" + /> + <stringbundle + id="bundle_messenger" + src="chrome://messenger/locale/messenger.properties" + /> + + <vbox id="smtpServerEditor"> + <html:div> + <html:fieldset> + <html:legend>&settings.caption;</html:legend> + <hbox flex="1"> + <vbox> + <hbox flex="1" align="center"> + <label + id="smtp.description.label" + value="&serverDescription.label;" + accesskey="&serverDescription.accesskey;" + control="smtp.description" + /> + </hbox> + <hbox flex="1" align="center"> + <label + id="smtp.hostname.label" + value="&serverName.label;" + accesskey="&serverName.accesskey;" + control="smtp.hostname" + /> + </hbox> + <hbox flex="1" align="center"> + <label + value="&serverPort.label;" + accesskey="&serverPort.accesskey;" + control="smtp.port" + /> + </hbox> + </vbox> + <vbox flex="1"> + <html:input + id="smtp.description" + type="text" + preftype="string" + class="input-inline" + prefstring="mail.smtpserver.%serverkey%.description" + aria-labelledby="smtp.description.label" + /> + <html:input + id="smtp.hostname" + type="text" + preftype="string" + class="uri-element input-inline" + prefstring="mail.smtpserver.%serverkey%.hostname" + aria-labelledby="smtp.hostname.label" + /> + <hbox align="center"> + <html:input + id="smtp.port" + type="number" + class="size5 input-inline" + min="0" + max="65535" + preftype="int" + prefstring="mail.smtpserver.%serverkey%.port" + /> + <label value="&serverPortDefault.label;" /> + <label id="smtp.defaultPort" /> + </hbox> + </vbox> + </hbox> + </html:fieldset> + </html:div> + + <separator class="thin" /> + + <html:div> + <html:fieldset> + <html:legend>&security.caption;</html:legend> + + <hbox flex="1"> + <vbox> + <hbox flex="1" align="center"> + <label + value="&connectionSecurity.label;" + accesskey="&connectionSecurity.accesskey;" + control="smtp.socketType" + /> + </hbox> + <hbox flex="1" align="center"> + <label + value="&authMethod.label;" + accesskey="&authMethod.accesskey;" + control="server.authMethod" + /> + </hbox> + <hbox flex="1" align="center"> + <label + id="smtpUsernameLabel" + value="&userName.label;" + accesskey="&userName.accesskey;" + control="smtpUsername" + /> + </hbox> + </vbox> + <vbox> + <menulist + id="smtp.socketType" + oncommand="sslChanged(true);" + prefstring="mail.smtpserver.%serverkey%.try_ssl" + > + <menupopup id="smtp.socketTypePopup"> + <menuitem + value="0" + label="&connectionSecurityType-0.label;" + /> + <menuitem + id="connectionSecurityType-1" + value="1" + label="&connectionSecurityType-1.label;" + disabled="true" + hidden="true" + /> + <menuitem + value="2" + label="&connectionSecurityType-2.label;" + /> + <menuitem + value="3" + label="&connectionSecurityType-3.label;" + /> + </menupopup> + </menulist> + <menulist + id="smtp.authMethod" + oncommand="authMethodChanged(true);" + wsm_persist="true" + preftype="int" + prefstring="mail.smtpserver.%serverkey%.authMethod" + > + <menupopup id="smtp.authMethodPopup"> + <menuitem id="authMethod-no" value="1" /> + <menuitem id="authMethod-password-cleartext" value="3" /> + <menuitem id="authMethod-password-encrypted" value="4" /> + <menuitem id="authMethod-kerberos" value="5" /> + <menuitem id="authMethod-ntlm" value="6" /> + <menuitem id="authMethod-oauth2" value="10" /> + <menuitem id="authMethod-anysecure" value="8" /> + <menuitem id="authMethod-any" value="9" /> + </menupopup> + </menulist> + <hbox class="input-container"> + <html:input + id="smtpUsername" + type="text" + class="input-inline" + preftype="string" + prefstring="mail.smtpserver.%serverkey%.username" + aria-labelledby="smtpUsernameLabel" + /> + </hbox> + </vbox> + </hbox> + </html:fieldset> + </html:div> + </vbox> + </dialog> + </html:body> +</html> diff --git a/comm/mailnews/base/prefs/content/accountUtils.js b/comm/mailnews/base/prefs/content/accountUtils.js new file mode 100644 index 0000000000..8af2e72cf3 --- /dev/null +++ b/comm/mailnews/base/prefs/content/accountUtils.js @@ -0,0 +1,369 @@ +/* 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/. */ + +/* import-globals-from AccountManager.js */ +/* globals openTab */ // From utilityOverlay.js +/* globals SelectFolder */ // From messageWindow.js or messenger.js. +/* globals MsgGetMessage */ // From mailWindowOverlay.js. + +var { MailServices } = ChromeUtils.import( + "resource:///modules/MailServices.jsm" +); + +var gAnyValidIdentity = false; // If there are no valid identities for any account +// returns the first account with an invalid server or identity + +var gNewAccountToLoad = null; // used to load new messages if we come from the mail3pane + +function getInvalidAccounts(accounts) { + let invalidAccounts = []; + for (let account of accounts) { + try { + if (!account.incomingServer.valid) { + invalidAccounts.push(account); + // skip to the next account + continue; + } + } catch (ex) { + // this account is busted, just keep going + continue; + } + + for (let identity of account.identities) { + if (identity.valid) { + gAnyValidIdentity = true; + } else { + invalidAccounts.push(account); + } + } + } + return invalidAccounts; +} + +function showMailIntegrationDialog() { + const nsIShellService = Ci.nsIShellService; + + try { + var shellService = + Cc["@mozilla.org/suite/shell-service;1"].getService(nsIShellService); + var appTypesCheck = + shellService.shouldBeDefaultClientFor & + (nsIShellService.MAIL | nsIShellService.NEWS); + + // show the default client dialog only if we have at least one account, + // if we should check for the default client, and we want to check if we are + // the default for mail/news and are not the default client for mail/news + if ( + appTypesCheck && + shellService.shouldCheckDefaultClient && + !shellService.isDefaultClient(true, appTypesCheck) + ) { + window.browsingContext.topChromeWindow.openDialog( + "chrome://communicator/content/defaultClientDialog.xhtml", + "DefaultClient", + "modal,centerscreen,chrome,resizable=no" + ); + } + } catch (ex) {} +} + +/** + * Check that an account exists which requires Local Folders. + * + * @returns {boolean} - true if at least 1 account exists that requires + * Local Folders, else false. + */ +function requireLocalFoldersAccount() { + return MailServices.accounts.accounts.some(account => + ["imap", "pop3", "nntp"].includes(account.incomingServer?.type) + ); +} + +/** + * Open the Nntp Account Wizard, or focus it if it's already open. + */ +function openNewsgroupAccountWizard() { + window.browsingContext.topChromeWindow.openDialog( + "chrome://messenger/content/AccountWizard.xhtml", + "AccountWizard", + "chrome,modal,titlebar,centerscreen" + ); +} + +function AddIMAccount() { + window.browsingContext.topChromeWindow.openDialog( + "chrome://messenger/content/chat/imAccountWizard.xhtml", + "", + "chrome,modal,titlebar,centerscreen" + ); +} + +function AddFeedAccount() { + window.browsingContext.topChromeWindow.openDialog( + "chrome://messenger-newsblog/content/feedAccountWizard.xhtml", + "", + "chrome,modal,titlebar,centerscreen" + ); +} + +/** + * Opens Address Book tab and triggers the address book creation dialog based on + * the passed type. + * + * @param {string} type - The address book type needing creation. Accepted types + * are "JS", "LDAP", and "CARDDAV". + */ +function addNewAddressBook(type) { + window.browsingContext.topChromeWindow.toAddressBook({ + action: `create_ab_${type}`, + }); +} + +function showCalendarWizard() { + window.browsingContext.topChromeWindow.openDialog( + "chrome://calendar/content/calendar-creation.xhtml", + "caEditServer", + "chrome,titlebar,resizable,centerscreen", + {} + ); +} + +/** + * Opens the account settings window on the specified account + * and page of settings. If the window is already open it is only focused. + * + * @param {?string} selectPage - The file name for the viewing page, or null for + * the account main page. Other pages are 'am-server.xhtml', + * 'am-copies.xhtml', 'am-offline.xhtml', 'am-addressing.xhtml', + * 'am-smtp.xhtml' + * @param {nsIMsgIncomingServer} [server] - The server of the account to select. + */ +async function MsgAccountManager(selectPage, server) { + let win = Services.wm.getMostRecentWindow("mail:3pane"); + if (!win) { + // No window available, so force open a new one. + openTab( + "contentTab", + { + url: "about:accountsettings", + onLoad(event, browser) { + browser.contentDocument.documentElement.server = server; + browser.contentDocument.documentElement.selectPage = selectPage; + browser.contentDocument.getElementById("accounttree").focus(); + }, + }, + "window" + ); + return; + } + + let tabmail = win.document.getElementById("tabmail"); + // If the server wasn't specified, and we have the window open, try + // and use the currently selected folder to work out the server to select. + if (!server) { + server = tabmail.currentAbout3Pane?.gFolder ?? null; + } + // If the server is still not found, account settings will default to + // the first account. + + // If Account settings tab is already open, change the server + // and the selected page, reload the tab and switch to the tab. + for (let tabInfo of tabmail.tabInfo) { + let tab = tabmail.getTabForBrowser(tabInfo.browser); + if (tab?.urlbar?.value == "about:accountsettings") { + tab.browser.contentDocument.documentElement.server = server; + tab.browser.contentDocument.documentElement.selectPage = selectPage; + tab.browser.contentWindow.onLoad(); + tabmail.switchToTab(tabInfo); + return; + } + } + + // Else fallback to opening a new tab in the window. + tabmail.openTab("contentTab", { + url: "about:accountsettings", + onLoad(event, browser) { + browser.contentDocument.documentElement.server = server; + browser.contentDocument.documentElement.selectPage = selectPage; + browser.contentDocument.getElementById("accounttree").focus(); + }, + }); +} + +function loadInboxForNewAccount() { + // gNewAccountToLoad is set in the final screen of the Account Wizard if a POP account + // was created, the download messages box is checked, and the wizard was opened from the 3pane + if (gNewAccountToLoad) { + var rootMsgFolder = gNewAccountToLoad.incomingServer.rootMsgFolder; + const kInboxFlag = Ci.nsMsgFolderFlags.Inbox; + var inboxFolder = rootMsgFolder.getFolderWithFlags(kInboxFlag); + SelectFolder(inboxFolder.URI); + window.focus(); + setTimeout(MsgGetMessage, 0); + gNewAccountToLoad = null; + } +} + +// returns true if we migrated - it knows this because 4.x did not have the +// pref mailnews.quotingPrefs.version, so if it's not set, we're either +// migrating from 4.x, or a much older version of Mozilla. +function migrateGlobalQuotingPrefs(allIdentities) { + // if reply_on_top and auto_quote exist then, if non-default + // migrate and delete, if default just delete. + var reply_on_top = 0; + var auto_quote = true; + var quotingPrefs = Services.prefs.getIntPref( + "mailnews.quotingPrefs.version", + 0 + ); + var migrated = false; + + // If the quotingPrefs version is 0 then we need to migrate our preferences + if (quotingPrefs == 0) { + migrated = true; + try { + reply_on_top = Services.prefs.getIntPref("mailnews.reply_on_top"); + auto_quote = Services.prefs.getBoolPref("mail.auto_quote"); + } catch (ex) {} + + if (!auto_quote || reply_on_top) { + for (let identity of allIdentities) { + if (identity.valid) { + identity.autoQuote = auto_quote; + identity.replyOnTop = reply_on_top; + } + } + } + Services.prefs.setIntPref("mailnews.quotingPrefs.version", 1); + } + return migrated; +} + +/** + * Open the Account Setup Tab or focus it if it's already open. + */ +function openAccountSetupTab() { + let mail3Pane = Services.wm.getMostRecentWindow("mail:3pane"); + let tabmail = mail3Pane.document.getElementById("tabmail"); + + // Switch to the account setup tab if it's already open. + for (let tabInfo of tabmail.tabInfo) { + let tab = tabmail.getTabForBrowser(tabInfo.browser); + if (tab?.urlbar?.value == "about:accountsetup") { + let accountSetup = tabInfo.browser.contentWindow.gAccountSetup; + // Reset the entire UI only if the previously opened setup was completed. + if (accountSetup._currentModename == "success") { + accountSetup.resetSetup(); + } + tabmail.switchToTab(tabInfo); + return; + } + } + + tabmail.openTab("contentTab", { url: "about:accountsetup" }); +} + +/** + * Open the account setup tab and switch to the success view to show the newly + * created account, or show an error if the account wasn't created. + * + * @param {object} account - A newly created account. + * @param {string} name - The account name defined in the provider's website. + * @param {string} email - The newly created email address. + */ +function openAccountSetupTabWithAccount(account, name, email) { + // Define which actions we need to take after the account setup tab has been + // loaded and we have access to its objects. + let onTabLoaded = function (event, browser, account) { + let accountSetup = browser.contentWindow.gAccountSetup; + + if (account) { + // Update the account setup variables before kicking off the success view + // which will start fetching linked services with these values. + accountSetup._realname = name; + accountSetup._email = email; + accountSetup._password = account.incomingServer.password; + accountSetup.showSuccessView(account); + return; + } + + accountSetup.showErrorNotification("account-setup-provisioner-error"); + }; + + let mail3Pane = Services.wm.getMostRecentWindow("mail:3pane"); + let tabmail = mail3Pane.document.getElementById("tabmail"); + + // Switch to the account setup tab if it's already open. + for (let tabInfo of tabmail.tabInfo) { + let tab = tabmail.getTabForBrowser(tabInfo.browser); + if (tab?.urlbar?.value == "about:accountsetup") { + let accountSetup = tabInfo.browser.contentWindow.gAccountSetup; + // Reset the entire UI only if the previously opened setup was completed. + if (accountSetup._currentModename == "success") { + accountSetup.resetSetup(); + } + tabmail.switchToTab(tabInfo); + onTabLoaded(null, tabInfo.browser, account); + return; + } + } + + // Open the account setup tab. + tabmail.openTab("contentTab", { + url: "about:accountsetup", + onLoad(event, browser) { + onTabLoaded(event, browser, account); + }, + }); +} + +/** + * Open the Account Provisioner Tab or focus it if it's already open. + */ +function openAccountProvisionerTab() { + let mail3Pane = Services.wm.getMostRecentWindow("mail:3pane"); + let tabmail = mail3Pane.document.getElementById("tabmail"); + + // Switch to the account setup tab if it's already open. + for (let tabInfo of tabmail.tabInfo) { + let tab = tabmail.getTabForBrowser(tabInfo.browser); + if (tab?.urlbar?.value == "about:accountprovisioner") { + tabmail.switchToTab(tabInfo); + return; + } + } + + tabmail.openTab("contentTab", { url: "about:accountprovisioner" }); +} + +/** + * Reveal the Folder Pane after an account creation callback. + */ +function updateMailPaneUI() { + // Nothing to update since no account has been created. + if (MailServices.accounts.accounts.length == 0) { + return; + } + + let mail3Pane = Services.wm.getMostRecentWindow("mail:3pane"); + // Set the folderPaneVisible to true in the tabmail to prevent collapsing + // on tab switch. + let tabmail = mail3Pane.document.getElementById("tabmail"); + tabmail.tabInfo[0].folderPaneVisible = true; +} + +/** + * Open the OpenPGP Key Manager from outside the Account Settings. + */ +function openKeyManager() { + window.browsingContext.topChromeWindow.openDialog( + "chrome://openpgp/content/ui/enigmailKeyManager.xhtml", + "enigmail:KeyManager", + "dialog,centerscreen,resizable", + { + cancelCallback: null, + okCallback: null, + } + ); +} diff --git a/comm/mailnews/base/prefs/content/am-addressing.inc.xhtml b/comm/mailnews/base/prefs/content/am-addressing.inc.xhtml new file mode 100644 index 0000000000..44d393f4ed --- /dev/null +++ b/comm/mailnews/base/prefs/content/am-addressing.inc.xhtml @@ -0,0 +1,125 @@ +# 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/. + + <vbox flex="1" id="compositionAndAddressing"> + <stringbundle id="bundle_addressBook" src="chrome://messenger/locale/addressbook/addressBook.properties"/> + + <html:div> + <html:fieldset> + <html:legend>&compositionGroupTitle.label;</html:legend> + <hbox align="center"> + <checkbox wsm_persist="true" id="identity.composeHtml" label="&useHtml.label;" + accesskey="&useHtml.accesskey;" + prefattribute="value" + prefstring="mail.identity.%identitykey%.compose_html"/> + </hbox> + + <separator class="thin"/> + + <hbox align="center"> + <checkbox wsm_persist="true" id="identity.autoQuote" label="&autoQuote.label;" + accesskey="&autoQuote.accesskey;" + pref="true" preftype="bool" prefattribute="value" + prefstring="mail.identity.%identitykey%.auto_quote"/> + </hbox> + <hbox align="center"> + <label value=""ing.label;" accesskey=""ing.accesskey;" control="identity.replyOnTop"/> + <menulist wsm_persist="true" id="identity.replyOnTop" oncommand="quoteEnabling();" + pref="true" preftype="int" prefattribute="value" + prefstring="mail.identity.%identitykey%.reply_on_top"> + <menupopup> + <menuitem value="1" label="&aboveQuote.label;"/> + <menuitem value="0" label="&belowQuote.label;"/> + <menuitem value="2" label="&selectAndQuote.label;"/> + </menupopup> + </menulist> + </hbox> + <hbox class="indent" align="center" id="placeBox"> + <label value="&place.label;" accesskey="&place.accesskey;" control="identity.sig_bottom"/> + <menulist wsm_persist="true" id="identity.sig_bottom" genericattr="true" + pref="true" preftype="bool" prefattribute="value" + prefstring="mail.identity.%identitykey%.sig_bottom"> + <menupopup> + <menuitem value="true" label="&belowText.label;"/> + <menuitem value="false" label="&aboveText.label;"/> + </menupopup> + </menulist> + </hbox> + + <checkbox id="identity.sig_on_reply" wsm_persist="true" + label="&includeSigOnReply.label;" + accesskey="&includeSigOnReply.accesskey;" + preftype="bool" genericattr="true" + prefstring="mail.identity.%identitykey%.sig_on_reply"/> + + <checkbox id="identity.sig_on_fwd" wsm_persist="true" + label="&includeSigOnForward.label;" + accesskey="&includeSigOnForward.accesskey;" + preftype="bool" genericattr="true" + prefstring="mail.identity.%identitykey%.sig_on_fwd"/> + + <separator class="thin"/> + + <hbox pack="end"> + <button id="globalComposingPrefsLink" + label="&globalComposingPrefs.label;" + accesskey="&globalComposingPrefs.accesskey;" + oncommand="showGlobalComposingPrefs();"/> + </hbox> + </html:fieldset> + </html:div> + + <separator class="thin"/> + + <html:div> + <html:fieldset> + <html:legend>&addressingGroupTitle.label;</html:legend> +#ifndef MOZ_THUNDERBIRD + <hbox align="center"> + <checkbox wsm_persist="true" id="identity.autocompleteToMyDomain" + label="&autocompleteToMyDomain.label;" + accesskey="&autocompleteToMyDomain.accesskey;" + prefattribute="value" + prefstring="mail.identity.%identitykey%.autocompleteToMyDomain"/> + </hbox> + + <separator class="thin"/> +#endif + + <label control="identity.overrideGlobal_Pref">&addressingText.label;</label> + <radiogroup id="identity.overrideGlobal_Pref" class="indent" + oncommand="LDAPenabling();" wsm_persist="true" + genericattr="true" preftype="bool" + prefstring="mail.identity.%identitykey%.overrideGlobal_Pref"> + <radio value="false" label="&useGlobal.label;" + accesskey="&useGlobal.accesskey;"/> + <radio value="true" id="directories" label="&directories.label;" + accesskey="&directories.accesskey;"/> + <hbox class="indent"> + <menulist is="menulist-addrbooks" id="identity.directoryServer" + none="&directoriesNone.label;" + remoteonly="true" + wsm_persist="true" + preftype="string" + prefstring="mail.identity.%identitykey%.directoryServer" + style="min-width: 16em;" + aria-labelledby="directories" + flex="1"/> + <button id="editButton" label="&editDirectories.label;" + accesskey="&editDirectories.accesskey;" + oncommand="onEditDirectories();"/> + </hbox> + </radiogroup> + + <separator class="thin"/> + + <hbox pack="end"> + <button id="globalAddressingPrefsLink" + label="&globalAddressingPrefs.label;" + accesskey="&globalAddressingPrefs.accesskey;" + oncommand="showGlobalAddressingPrefs();"/> + </hbox> + </html:fieldset> + </html:div> + </vbox> diff --git a/comm/mailnews/base/prefs/content/am-addressing.js b/comm/mailnews/base/prefs/content/am-addressing.js new file mode 100644 index 0000000000..24a3bf0004 --- /dev/null +++ b/comm/mailnews/base/prefs/content/am-addressing.js @@ -0,0 +1,68 @@ +/* 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/. */ + +/* import-globals-from am-prefs.js */ +/* import-globals-from amUtils.js */ + +function onLoad() { + parent.onPanelLoaded("am-addressing.xhtml"); +} + +function onInit(aPageId, aServerId) { + onInitCompositionAndAddressing(); +} + +function onInitCompositionAndAddressing() { + LDAPenabling(); + quoteEnabling(); +} + +function onEditDirectories() { + parent.gSubDialog.open( + "chrome://messenger/content/addressbook/pref-editdirectories.xhtml" + ); +} + +function onPreInit(account, accountValues) {} + +function LDAPenabling() { + onCheckItem("identity.directoryServer", ["directories"]); + onCheckItem("editButton", ["directories"]); +} + +function quoteEnabling() { + var placebox = document.getElementById("placeBox"); + + if (document.getElementById("identity.replyOnTop").value == "1") { + placebox.firstElementChild.removeAttribute("disabled"); + placebox.lastElementChild.removeAttribute("disabled"); + } else { + placebox.firstElementChild.setAttribute("disabled", "true"); + placebox.lastElementChild.setAttribute("disabled", "true"); + } +} + +/** + * Open the Preferences dialog on the tab with Addressing options. + */ +function showGlobalAddressingPrefs() { + openPrefsFromAccountManager( + "paneCompose", + "compositionAddressingCategory", + null, + "addressing_pane" + ); +} + +/** + * Open the Preferences dialog on the tab with Composing options. + */ +function showGlobalComposingPrefs() { + openPrefsFromAccountManager( + "paneCompose", + null, + null, + "composing_messages_pane" + ); +} diff --git a/comm/mailnews/base/prefs/content/am-addressing.xhtml b/comm/mailnews/base/prefs/content/am-addressing.xhtml new file mode 100644 index 0000000000..554ee398c1 --- /dev/null +++ b/comm/mailnews/base/prefs/content/am-addressing.xhtml @@ -0,0 +1,32 @@ +<?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/accountManage.css" type="text/css"?> + +<!DOCTYPE html SYSTEM "chrome://messenger/locale/am-addressing.dtd"> + +<html xmlns="http://www.w3.org/1999/xhtml" + xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + xmlns:html="http://www.w3.org/1999/xhtml"> +<head> + <title>&addressing.label;</title> + <script defer="defer" src="chrome://messenger/content/am-addressing.js"></script> + <script defer="defer" src="chrome://messenger/content/amUtils.js"></script> + <script defer="defer" src="chrome://messenger/content/am-prefs.js"></script> + <script> + // FIXME: move to script file. + window.addEventListener("load", event => { onLoad(); }); + </script> +</head> +<html:body xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + <vbox id="containerBox" flex="1"> + <hbox class="dialogheader"> + <label class="dialogheader-title" value="&addressing.label;"/> + </hbox> +#include am-addressing.inc.xhtml + </vbox> +</html:body> +</html> diff --git a/comm/mailnews/base/prefs/content/am-archiveoptions.js b/comm/mailnews/base/prefs/content/am-archiveoptions.js new file mode 100644 index 0000000000..44c0817149 --- /dev/null +++ b/comm/mailnews/base/prefs/content/am-archiveoptions.js @@ -0,0 +1,74 @@ +/* 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 gIdentity = null; + +window.addEventListener("load", onLoadArchiveOptions); +document.addEventListener("dialogaccept", onAcceptArchiveOptions); + +/** + * Load the archive options dialog, set the radio/checkbox items to the + * appropriate values, and update the archive hierarchy example. + */ +function onLoadArchiveOptions() { + // extract the account + gIdentity = window.arguments[0].identity; + + let granularity = document.getElementById("archiveGranularity"); + granularity.selectedIndex = gIdentity.archiveGranularity; + granularity.addEventListener("command", updateArchiveExample); + + let kfs = document.getElementById("archiveKeepFolderStructure"); + kfs.checked = gIdentity.archiveKeepFolderStructure; + kfs.addEventListener("command", updateArchiveExample); + + updateArchiveExample(); +} + +/** + * Save the archive settings to the current identity. + */ +function onAcceptArchiveOptions() { + gIdentity.archiveGranularity = + document.getElementById("archiveGranularity").selectedIndex; + gIdentity.archiveKeepFolderStructure = document.getElementById( + "archiveKeepFolderStructure" + ).checked; +} + +/** + * Update the example tree to show what the current options would look like. + */ +function updateArchiveExample() { + let granularity = document.getElementById("archiveGranularity").selectedIndex; + let kfs = document.getElementById("archiveKeepFolderStructure").checked; + let hierarchy = [ + document.getElementsByClassName("root"), + document.getElementsByClassName("year"), + document.getElementsByClassName("month"), + ]; + + // First, show/hide the appropriate levels in the hierarchy and turn the + // necessary items into containers. + for (let i = 0; i < hierarchy.length; i++) { + for (let j = 0; j < hierarchy[i].length; j++) { + hierarchy[i][j].setAttribute("container", granularity > i); + hierarchy[i][j].setAttribute("open", granularity > i); + hierarchy[i][j].hidden = granularity < i; + } + } + + // Next, handle the "keep folder structures" case by moving a tree item around + // and making sure its parent is a container. + let folders = document.getElementById("folders"); + folders.hidden = !kfs; + if (kfs) { + let parent = hierarchy[granularity][0]; + parent.setAttribute("container", true); + parent.setAttribute("open", true); + + let treechildren = parent.children[1]; + treechildren.appendChild(folders); + } +} diff --git a/comm/mailnews/base/prefs/content/am-archiveoptions.xhtml b/comm/mailnews/base/prefs/content/am-archiveoptions.xhtml new file mode 100644 index 0000000000..e34de7a420 --- /dev/null +++ b/comm/mailnews/base/prefs/content/am-archiveoptions.xhtml @@ -0,0 +1,136 @@ +<?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/accountManage.css" type="text/css"?> +<?xml-stylesheet href="chrome://messenger/skin/folderPane.css" type="text/css"?> +<?xml-stylesheet href="chrome://messenger/skin/icons.css" type="text/css"?> + +<!DOCTYPE html SYSTEM "chrome://messenger/locale/am-archiveoptions.dtd"> +<html + id="archiveOptions" + xmlns="http://www.w3.org/1999/xhtml" + xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + xmlns:html="http://www.w3.org/1999/xhtml" + style="width: 500px" + scrolling="false" +> + <head> + <title>&dialogTitle.label;</title> + <script + defer="defer" + src="chrome://messenger/content/am-archiveoptions.js" + ></script> + </head> + <html:body + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + > + <dialog style="width: 100vw; height: 100vh"> + <vbox flex="1"> + <label>&archiveGranularityPrefix.label;</label> + <radiogroup id="archiveGranularity"> + <radio + label="&archiveFlat.label;" + accesskey="&archiveFlat.accesskey;" + class="indent" + /> + <radio + label="&archiveYearly.label;" + accesskey="&archiveYearly.accesskey;" + class="indent" + /> + <radio + label="&archiveMonthly.label;" + accesskey="&archiveMonthly.accesskey;" + class="indent" + /> + </radiogroup> + <checkbox + id="archiveKeepFolderStructure" + label="&keepFolderStructure.label;" + accesskey="&keepFolderStructure.accesskey;" + /> + + <html:div> + <html:fieldset flex="1" style="margin-block-end: 5px"> + <html:legend>&archiveExample.label;</html:legend> + <hbox flex="1"> + <tree + id="archiveTree" + hidecolumnpicker="true" + disabled="true" + flex="1" + style="min-height: 8em" + > + <treecols> + <treecol + id="folderNameCol" + primary="true" + hideheader="true" + style="flex: 1 auto" + /> + </treecols> + <treechildren> + <treeitem class="root"> + <treerow> + <treecell + properties="specialFolder-Archive" + label="&archiveFolderName.label;" + /> + </treerow> + <treechildren> + <treeitem id="folders"> + <treerow> + <treecell label="&inboxFolderName.label;" /> + </treerow> + </treeitem> + <treeitem class="year"> + <treerow> + <treecell label="2020" /> + </treerow> + <treechildren> + <treeitem class="month"> + <treerow> + <treecell label="2020-11" /> + </treerow> + <treechildren /> + </treeitem> + <treeitem class="month"> + <treerow> + <treecell label="2020-12" /> + </treerow> + <treechildren /> + </treeitem> + </treechildren> + </treeitem> + <treeitem class="year"> + <treerow> + <treecell label="2021" /> + </treerow> + <treechildren> + <treeitem class="month"> + <treerow> + <treecell label="2021-01" /> + </treerow> + <treechildren /> + </treeitem> + <treeitem class="month"> + <treerow> + <treecell label="2021-02" /> + </treerow> + <treechildren /> + </treeitem> + </treechildren> + </treeitem> + </treechildren> + </treeitem> + </treechildren> + </tree> + </hbox> + </html:fieldset> + </html:div> + </vbox> + </dialog> + </html:body> +</html> diff --git a/comm/mailnews/base/prefs/content/am-copies.inc.xhtml b/comm/mailnews/base/prefs/content/am-copies.inc.xhtml new file mode 100644 index 0000000000..75834fd60a --- /dev/null +++ b/comm/mailnews/base/prefs/content/am-copies.inc.xhtml @@ -0,0 +1,308 @@ +# 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/. + + <vbox id="copiesAndFolders" flex="1"> + + <linkset> + <html:link rel="localization" href="messenger/preferences/am-copies.ftl"/> + </linkset> + + <stringbundle id="bundle_messenger" src="chrome://messenger/locale/messenger.properties"/> + + <label id="identity.fccFolder" hidden="true" wsm_persist="true" + pref="true" preftype="wstring" prefattribute="value" + prefstring="mail.identity.%identitykey%.fcc_folder"/> + <label id="identity.draftFolder" hidden="true" wsm_persist="true" + pref="true" preftype="wstring" prefattribute="value" + prefstring="mail.identity.%identitykey%.draft_folder"/> + <label id="identity.archiveFolder" hidden="true" wsm_persist="true" + pref="true" preftype="wstring" prefattribute="value" + prefstring="mail.identity.%identitykey%.archive_folder"/> + <label id="identity.stationeryFolder" hidden="true" wsm_persist="true" + pref="true" preftype="wstring" prefattribute="value" + prefstring="mail.identity.%identitykey%.stationary_folder"/> + <label id="identity.email" hidden="true" wsm_persist="true"/> + <label id="identity.fccFolderPickerMode" hidden="true" wsm_persist="true" + pref="true" preftype="int" prefattribute="value" + prefstring="mail.identity.%identitykey%.fcc_folder_picker_mode"/> + <label id="identity.draftsFolderPickerMode" hidden="true" wsm_persist="true" + pref="true" preftype="int" prefattribute="value" + prefstring="mail.identity.%identitykey%.drafts_folder_picker_mode"/> + <label id="identity.archivesFolderPickerMode" hidden="true" wsm_persist="true" + pref="true" preftype="int" prefattribute="value" + prefstring="mail.identity.%identitykey%.archives_folder_picker_mode"/> + <label id="identity.tmplFolderPickerMode" hidden="true" wsm_persist="true" + pref="true" preftype="int" prefattribute="value" + prefstring="mail.identity.%identitykey%.tmpl_folder_picker_mode"/> + <html:div> + <html:fieldset id="copiesGroup"> + <html:legend>&sendingPrefix.label;</html:legend> + + <hbox align="center"> + <checkbox id="identity.doFcc" wsm_persist="true" label="&fccMailFolder.label;" + accesskey="&fccMailFolder.accesskey;" + prefattribute="value" + prefstring="mail.identity.%identitykey%.fcc" + oncommand="setupFccItems();"/> + </hbox> + <radiogroup id="doFcc" aria-labelledby="identity.doFcc"> + <hbox class="specialFolderPickerGrid"> + <vbox> + <hbox flex="1" align="center"> + <radio id="fcc_selectAccount" + class="depends-on-do-fcc" + value="0" label="&sentFolderOn.label;" + accesskey="&sentFolderOn.accesskey;" + oncommand="setPickersState('msgFccAccountPicker', 'msgFccFolderPicker', event)"/> + </hbox> + <hbox flex="1" align="center"> + <radio id="fcc_selectFolder" + class="depends-on-do-fcc" + value="1" label="&sentInOtherFolder.label;" + accesskey="&sentInOtherFolder.accesskey;" + oncommand="setPickersState('msgFccFolderPicker', 'msgFccAccountPicker', event)"/> + </hbox> + </vbox> + <vbox flex="1"> + <menulist id="msgFccAccountPicker" + class="folderMenuItem depends-on-do-fcc" + aria-labelledby="fcc_selectAccount"> + <menupopup is="folder-menupopup" id="msgFccAccountPopup" + mode="filing" + expandFolders="false" + oncommand="noteSelectionChange('fcc', 'Account', event)"/> + </menulist> + <menulist id="msgFccFolderPicker" + class="folderMenuItem depends-on-do-fcc" + aria-labelledby="fcc_selectFolder" + displayformat="verbose"> + <menupopup is="folder-menupopup" id="msgFccFolderPopup" + mode="filing" + class="menulist-menupopup" + showFileHereLabel="true" + oncommand="noteSelectionChange('fcc', 'Folder', event)"/> + </menulist> + </vbox> + </hbox> + </radiogroup> + + <hbox align="center" class="fccReplyFollowsParent" hidable="true" hidefor="nntp,rss"> + <checkbox id="identity.fccReplyFollowsParent" wsm_persist="true" + class="depends-on-do-fcc" + label="&fccReplyFollowsParent.label;" + accesskey="&fccReplyFollowsParent.accesskey;" + prefattribute="value" + prefstring="mail.identity.%identitykey%.fcc_reply_follows_parent"/> + </hbox> + + <separator class="thin"/> + + <hbox> + <vbox> + <hbox flex="1" align="center"> + <checkbox id="identity.doCc" wsm_persist="true" label="&ccAddress.label;" + accesskey="&ccAddress.accesskey;" + control="identity.doCcList" + oncommand="identityDoCcBccOnCommand(event);" + prefattribute="value" + prefstring="mail.identity.%identitykey%.doCc"/> + </hbox> + <hbox flex="1" align="center"> + <checkbox id="identity.doBcc" wsm_persist="true" label="&bccAddress.label;" + accesskey="&bccAddress.accesskey;" + control="identity.doBccList" + oncommand="identityDoCcBccOnCommand(event);" + prefattribute="value" + prefstring="mail.identity.%identitykey%.doBcc"/> + </hbox> + </vbox> + <vbox flex="1"> + <hbox flex="1" class="input-container" align="center"> + <html:input id="identity.doCcList" wsm_persist="true" + type="text" + aria-labelledby="identity.doCc" + prefstring="mail.identity.%identitykey%.doCcList" + class="uri-element input-inline" + placeholder="&ccAddressList.placeholder;" + onblur="identityDoCcBccOnBlur(event);"/> + </hbox> + <hbox flex="1" class="input-container" align="center"> + <html:input id="identity.doBccList" wsm_persist="true" + type="text" + aria-labelledby="identity.doBcc" + prefstring="mail.identity.%identitykey%.doBccList" + class="uri-element input-inline" + placeholder="&bccAddressList.placeholder;" + onblur="identityDoCcBccOnBlur(event);"/> + </hbox> + </vbox> + </hbox> + <description class="indent tip-caption" + data-l10n-id="account-prefs-show-address-row-description"/> + </html:fieldset> + </html:div> + + <html:div> + <html:fieldset id="archivesGroup"> + <html:legend>&archivesTitle.label;</html:legend> + + <hbox pack="start"> + <checkbox id="identity.archiveEnabled" wsm_persist="true" + label="&keepArchives.label;" + accesskey="&keepArchives.accesskey;" + prefattribute="value" + prefstring="mail.identity.%identitykey%.archive_enabled" + oncommand="setupArchiveItems();"/> + </hbox> + + <radiogroup id="messageArchives"> + <hbox class="specialFolderPickerGrid"> + <vbox> + <hbox flex="1" align="center"> + <radio id="archive_selectAccount" + class="depends-on-archive" + value="0" label="&archivesFolderOn.label;" + accesskey="&archivesFolderOn.accesskey;" + oncommand="setPickersState('msgArchivesAccountPicker', 'msgArchivesFolderPicker', event)"/> + </hbox> + <hbox flex="1" align="center"> + <radio id="archive_selectFolder" + class="depends-on-archive" + value="1" label="&archiveInOtherFolder.label;" + accesskey="&archiveInOtherFolder.accesskey;" + oncommand="setPickersState('msgArchivesFolderPicker', 'msgArchivesAccountPicker', event)"/> + </hbox> + </vbox> + <vbox flex="1"> + <menulist id="msgArchivesAccountPicker" + class="folderMenuItem depends-on-archive" + aria-labelledby="archive_selectAccount"> + <menupopup is="folder-menupopup" id="msgArchivesAccountPopup" + mode="filing" + expandFolders="false" + oncommand="noteSelectionChange('archive', 'Account', event)"/> + </menulist> + <menulist id="msgArchivesFolderPicker" + class="folderMenuItem depends-on-archive" + aria-labelledby="archive_selectFolder" + displayformat="verbose"> + <menupopup is="folder-menupopup" id="msgArchivesFolderPopup" + mode="filing" + class="menulist-menupopup" + showFileHereLabel="true" + oncommand="noteSelectionChange('archive', 'Folder', event)"/> + </menulist> + </vbox> + </hbox> + </radiogroup> + <hbox pack="end"> + <button id="archiveHierarchyButton" + class="depends-on-archive" + label="&archiveHierarchyButton.label;" + accesskey="&archiveHierarchyButton.accesskey;" + oncommand="ChangeArchiveHierarchy();"/> + </hbox> + </html:fieldset> + </html:div> + + <html:div> + <html:fieldset id="foldersGroup"> + <html:legend>&specialFolders.label;</html:legend> + + <hbox align="center"> + <label value="&keepDrafts2.label;" control="messageDrafts"/> + </hbox> + + <radiogroup id="messageDrafts"> + <hbox class="specialFolderPickerGrid"> + <vbox> + <hbox flex="1" align="center"> + <radio id="draft_selectAccount" + oncommand="setPickersState('msgDraftsAccountPicker', 'msgDraftsFolderPicker', event)" + value="0" label="&draftsFolderOn.label;" + accesskey="&draftsFolderOn.accesskey;"/> + </hbox> + <hbox flex="1" align="center"> + <radio id="draft_selectFolder" + oncommand="setPickersState('msgDraftsFolderPicker', 'msgDraftsAccountPicker', event)" + value="1" label="&draftInOtherFolder.label;" + accesskey="&draftInOtherFolder.accesskey;"/> + </hbox> + </vbox> + <vbox flex="1"> + <menulist id="msgDraftsAccountPicker" + class="folderMenuItem" + aria-labelledby="draft_selectAccount"> + <menupopup is="folder-menupopup" id="msgDraftAccountPopup" + mode="filing" + expandFolders="false" + oncommand="noteSelectionChange('draft', 'Account', event)"/> + </menulist> + <menulist id="msgDraftsFolderPicker" + class="folderMenuItem" + aria-labelledby="draft_selectFolder" + displayformat="verbose"> + <menupopup is="folder-menupopup" id="msgDraftFolderPopup" + mode="filing" + class="menulist-menupopup" + showFileHereLabel="true" + oncommand="noteSelectionChange('draft', 'Folder', event)"/> + </menulist> + </vbox> + </hbox> + </radiogroup> + + <separator class="thin"/> + + <hbox align="center"> + <label value="&keepTemplates.label;" control="messageTemplates"/> + </hbox> + + <radiogroup id="messageTemplates"> + <hbox class="specialFolderPickerGrid"> + <vbox> + <hbox flex="1" align="center"> + <radio id="tmpl_selectAccount" + oncommand="setPickersState('msgStationeryAccountPicker', 'msgStationeryFolderPicker', event)" + value="0" label="&templatesFolderOn.label;" + accesskey="&templatesFolderOn.accesskey;"/> + </hbox> + <hbox flex="1" align="center"> + <radio id="tmpl_selectFolder" + oncommand="setPickersState('msgStationeryFolderPicker', 'msgStationeryAccountPicker', event)" + value="1" label="&templateInOtherFolder.label;" + accesskey="&templateInOtherFolder.accesskey;"/> + </hbox> + </vbox> + <vbox flex="1"> + <menulist id="msgStationeryAccountPicker" + class="folderMenuItem" + aria-labelledby="tmpl_selectAccount"> + <menupopup is="folder-menupopup" id="msgFccAccountPopup" + mode="filing" + expandFolders="false" + oncommand="noteSelectionChange('tmpl', 'Account', event)"/> + </menulist> + <menulist id="msgStationeryFolderPicker" + class="folderMenuItem" + aria-labelledby="tmpl_selectFolder" + displayformat="verbose"> + <menupopup is="folder-menupopup" id="msgTemplFolderPopup" + mode="filing" + class="menulist-menupopup" + showFileHereLabel="true" + oncommand="noteSelectionChange('tmpl', 'Folder', event)"/> + </menulist> + </vbox> + </hbox> + </radiogroup> + <hbox align="center"> + <checkbox id="identity.showSaveMsgDlg" wsm_persist="true" label="&saveMessageDlg.label;" + accesskey="&saveMessageDlg.accesskey;" + prefattribute="value" + prefstring="mail.identity.%identitykey%.showSaveMsgDlg"/> + </hbox> + </html:fieldset> + </html:div> + </vbox> diff --git a/comm/mailnews/base/prefs/content/am-copies.js b/comm/mailnews/base/prefs/content/am-copies.js new file mode 100644 index 0000000000..f848bab0c0 --- /dev/null +++ b/comm/mailnews/base/prefs/content/am-copies.js @@ -0,0 +1,555 @@ +/* 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/. */ + +/* import-globals-from am-prefs.js */ +/* import-globals-from amUtils.js */ + +var { MailUtils } = ChromeUtils.import("resource:///modules/MailUtils.jsm"); + +var gFccRadioElemChoice, + gDraftsRadioElemChoice, + gArchivesRadioElemChoice, + gTmplRadioElemChoice; +var gFccRadioElemChoiceLocked, + gDraftsRadioElemChoiceLocked, + gArchivesRadioElemChoiceLocked, + gTmplRadioElemChoiceLocked; +var gDefaultPickerMode = "1"; + +var gFccFolderWithDelim, + gDraftsFolderWithDelim, + gArchivesFolderWithDelim, + gTemplatesFolderWithDelim; +var gAccount; +var gCurrentServerId; + +function onPreInit(account, accountValues) { + gAccount = account; + var type = parent.getAccountValue( + account, + accountValues, + "server", + "type", + null, + false + ); + hideShowControls(type); +} + +/* + * Set the global radio element choices and initialize folder/account pickers. + * Also, initialize other UI elements (cc, bcc, fcc picker controller checkboxes). + */ +function onInit(aPageId, aServerId) { + gCurrentServerId = aServerId; + onInitCopiesAndFolders(); +} + +function onInitCopiesAndFolders() { + SetGlobalRadioElemChoices(); + + SetFolderDisplay( + gFccRadioElemChoice, + gFccRadioElemChoiceLocked, + "fcc", + "msgFccAccountPicker", + "identity.fccFolder", + "msgFccFolderPicker" + ); + + SetFolderDisplay( + gArchivesRadioElemChoice, + gArchivesRadioElemChoiceLocked, + "archive", + "msgArchivesAccountPicker", + "identity.archiveFolder", + "msgArchivesFolderPicker" + ); + + SetFolderDisplay( + gDraftsRadioElemChoice, + gDraftsRadioElemChoiceLocked, + "draft", + "msgDraftsAccountPicker", + "identity.draftFolder", + "msgDraftsFolderPicker" + ); + + SetFolderDisplay( + gTmplRadioElemChoice, + gTmplRadioElemChoiceLocked, + "tmpl", + "msgStationeryAccountPicker", + "identity.stationeryFolder", + "msgStationeryFolderPicker" + ); + + setupDoCcBccItems("identity.doCc", "identity.doCcList"); + setupDoCcBccItems("identity.doBcc", "identity.doBccList"); + setupFccItems(); + setupArchiveItems(); + + SetSpecialFolderNamesWithDelims(); +} + +// Initialize the picker mode choices (account/folder picker) into global vars +function SetGlobalRadioElemChoices() { + var pickerModeElement = document.getElementById( + "identity.fccFolderPickerMode" + ); + gFccRadioElemChoice = pickerModeElement.getAttribute("value"); + gFccRadioElemChoiceLocked = pickerModeElement.getAttribute("disabled"); + if (!gFccRadioElemChoice) { + gFccRadioElemChoice = gDefaultPickerMode; + } + + pickerModeElement = document.getElementById( + "identity.archivesFolderPickerMode" + ); + gArchivesRadioElemChoice = pickerModeElement.getAttribute("value"); + gArchivesRadioElemChoiceLocked = pickerModeElement.getAttribute("disabled"); + if (!gArchivesRadioElemChoice) { + gArchivesRadioElemChoice = gDefaultPickerMode; + } + + pickerModeElement = document.getElementById( + "identity.draftsFolderPickerMode" + ); + gDraftsRadioElemChoice = pickerModeElement.getAttribute("value"); + gDraftsRadioElemChoiceLocked = pickerModeElement.getAttribute("disabled"); + if (!gDraftsRadioElemChoice) { + gDraftsRadioElemChoice = gDefaultPickerMode; + } + + pickerModeElement = document.getElementById("identity.tmplFolderPickerMode"); + gTmplRadioElemChoice = pickerModeElement.getAttribute("value"); + gTmplRadioElemChoiceLocked = pickerModeElement.getAttribute("disabled"); + if (!gTmplRadioElemChoice) { + gTmplRadioElemChoice = gDefaultPickerMode; + } +} + +/* + * Set Account and Folder elements based on the values read from + * preferences file. Default picker mode, if none specified at this stage, is + * set to 1 i.e., Other picker displaying the folder value read from the + * preferences file. + */ +function SetFolderDisplay( + pickerMode, + disableMode, + radioElemPrefix, + accountPickerId, + folderPickedField, + folderPickerId +) { + if (!pickerMode) { + pickerMode = gDefaultPickerMode; + } + + var selectAccountRadioId = radioElemPrefix + "_selectAccount"; + var selectAccountRadioElem = document.getElementById(selectAccountRadioId); + var selectFolderRadioId = radioElemPrefix + "_selectFolder"; + var selectFolderRadioElem = document.getElementById(selectFolderRadioId); + var accountPicker = document.getElementById(accountPickerId); + var folderPicker = document.getElementById(folderPickerId); + var rg = selectAccountRadioElem.radioGroup; + var folderPickedElement = document.getElementById(folderPickedField); + var uri = folderPickedElement.getAttribute("value"); + // Get message folder from the given uri. + // There is no need to check for the existence of special folders as + // these folders are created on demand at runtime in case of imap accounts. + // For POP3 accounts, special folders are created at the account creation time. + var msgFolder = MailUtils.getOrCreateFolder(uri); + InitFolderDisplay(msgFolder.server.rootFolder, accountPicker); + InitFolderDisplay(msgFolder, folderPicker); + + switch (pickerMode) { + case "0": + rg.selectedItem = selectAccountRadioElem; + SetPickerEnabling(accountPickerId, folderPickerId); + break; + + case "1": + rg.selectedItem = selectFolderRadioElem; + SetPickerEnabling(folderPickerId, accountPickerId); + break; + + default: + dump("Error in setting initial folder display on pickers\n"); + break; + } + + // Check to see if we need to lock page elements. Disable radio buttons + // and account/folder pickers when locked. + if (disableMode) { + selectAccountRadioElem.setAttribute("disabled", "true"); + selectFolderRadioElem.setAttribute("disabled", "true"); + accountPicker.setAttribute("disabled", "true"); + folderPicker.setAttribute("disabled", "true"); + } +} + +// Initialize the folder display based on prefs values +function InitFolderDisplay(folder, folderPicker) { + folderPicker.menupopup.selectFolder(folder); + folderPicker.folder = folder; +} + +/** + * Capture any menulist changes and update the folder property. + * + * @param {string} aGroup - The prefix for the menulist we're handling + * (e.g. "drafts") + * @param {"Account"|"Folder"} aType - "Account" for the account picker or + * "Folder" for the folder picker. + * @param {Event} aEvent - The event that we're responding to. + */ +function noteSelectionChange(aGroup, aType, aEvent) { + var checkedElem = document.getElementById(aGroup + "_select" + aType); + var folder = aEvent.target._folder; + var modeValue = checkedElem.value; + var radioGroup = checkedElem.radioGroup.getAttribute("id"); + var picker; + + switch (radioGroup) { + case "doFcc": + gFccRadioElemChoice = modeValue; + picker = document.getElementById("msgFcc" + aType + "Picker"); + break; + + case "messageArchives": + gArchivesRadioElemChoice = modeValue; + picker = document.getElementById("msgArchives" + aType + "Picker"); + updateArchiveHierarchyButton(folder); + break; + + case "messageDrafts": + gDraftsRadioElemChoice = modeValue; + picker = document.getElementById("msgDrafts" + aType + "Picker"); + break; + + case "messageTemplates": + gTmplRadioElemChoice = modeValue; + picker = document.getElementById("msgStationery" + aType + "Picker"); + break; + } + + picker.folder = folder; + picker.menupopup.selectFolder(folder); +} + +// Need to append special folders when account picker is selected. +// Create a set of global special folder vars to be suffixed to the +// server URI of the selected account. +function SetSpecialFolderNamesWithDelims() { + var folderDelim = "/"; + /* we use internal names known to everyone like "Sent", "Templates" and "Drafts" */ + + gFccFolderWithDelim = folderDelim + "Sent"; + gArchivesFolderWithDelim = folderDelim + "Archives"; + gDraftsFolderWithDelim = folderDelim + "Drafts"; + gTemplatesFolderWithDelim = folderDelim + "Templates"; +} + +// Save all changes on this page +function onSave() { + onSaveCopiesAndFolders(); +} + +function onSaveCopiesAndFolders() { + SaveFolderSettings( + gFccRadioElemChoice, + "doFcc", + gFccFolderWithDelim, + "msgFccAccountPicker", + "msgFccFolderPicker", + "identity.fccFolder", + "identity.fccFolderPickerMode" + ); + + SaveFolderSettings( + gArchivesRadioElemChoice, + "messageArchives", + gArchivesFolderWithDelim, + "msgArchivesAccountPicker", + "msgArchivesFolderPicker", + "identity.archiveFolder", + "identity.archivesFolderPickerMode" + ); + + SaveFolderSettings( + gDraftsRadioElemChoice, + "messageDrafts", + gDraftsFolderWithDelim, + "msgDraftsAccountPicker", + "msgDraftsFolderPicker", + "identity.draftFolder", + "identity.draftsFolderPickerMode" + ); + + SaveFolderSettings( + gTmplRadioElemChoice, + "messageTemplates", + gTemplatesFolderWithDelim, + "msgStationeryAccountPicker", + "msgStationeryFolderPicker", + "identity.stationeryFolder", + "identity.tmplFolderPickerMode" + ); +} + +// Save folder settings and radio element choices +function SaveFolderSettings( + radioElemChoice, + radioGroupId, + folderSuffix, + accountPickerId, + folderPickerId, + folderElementId, + folderPickerModeId +) { + var formElement = document.getElementById(folderElementId); + var uri; + + if ( + radioElemChoice == "0" || + !document.getElementById(folderPickerId).value + ) { + // Default or revert to default if no folder chosen. + radioElemChoice = "0"; + uri = document.getElementById(accountPickerId).folder.URI; + if (uri) { + // Create Folder URI. + uri = uri + folderSuffix; + } + } else if (radioElemChoice == "1") { + uri = document.getElementById(folderPickerId).folder.URI; + } else { + dump("Error saving folder preferences.\n"); + return; + } + + formElement.setAttribute("value", uri); + + formElement = document.getElementById(folderPickerModeId); + formElement.setAttribute("value", radioElemChoice); +} + +// Check the Fcc Self item and setup associated picker state +function setupFccItems() { + let checked = document.getElementById("identity.doFcc").checked; + document.querySelectorAll(".depends-on-do-fcc").forEach(e => { + if (checked) { + e.removeAttribute("disabled"); + } else { + e.setAttribute("disabled", "true"); + } + }); + if (!checked) { + return; + } + + switch (gFccRadioElemChoice) { + case "0": + if (!gFccRadioElemChoiceLocked) { + SetPickerEnabling("msgFccAccountPicker", "msgFccFolderPicker"); + } + SetRadioButtons("fcc_selectAccount", "fcc_selectFolder"); + break; + + case "1": + if (!gFccRadioElemChoiceLocked) { + SetPickerEnabling("msgFccFolderPicker", "msgFccAccountPicker"); + } + SetRadioButtons("fcc_selectFolder", "fcc_selectAccount"); + break; + + default: + dump("Error in setting Fcc elements.\n"); + break; + } +} + +/** + * Handle the initial status and value of the Auto-Cc/Bcc text input fields. + * + * @param {string} checkboxId - The ID of an Auto-Cc/Bcc checkbox. + * @param {string} inputId - The ID of an Auto-Cc/Bcc text input element. + */ +function setupDoCcBccItems(checkboxId, inputId) { + // Enable address input according to the status of the checkbox. + let input = document.getElementById(inputId); + input.disabled = !document.getElementById(checkboxId).checked; + // Safeguard against space-padded address list to ensure list visibility. + input.value = input.value.trim(); +} + +/** + * Handle the command event of the Auto-Cc/Bcc checkboxes. + * Disable the respective text input element if the checkbox is not checked, and + * handle the default value of the input when the checkbox is toggled. + * + * @param {Event} event - The command event of the checkbox. + */ +function identityDoCcBccOnCommand(event) { + let checkbox = event.target; + let checked = checkbox.checked; + // For checkboxes #identity.doCc and #identity.doBcc, get the corresponding + // inputs: #identity.doCcList and #identity.doBccList. + let input = document.getElementById(`${checkbox.id}List`); + input.disabled = !checked; + + // User toggled checkbox. + let identityEmailAddress = document.getElementById("identity.email").value; + if (checked) { + // If user checks the checkbox and there's no address, default to identity's + // email address. + if (!input.value) { + input.value = identityEmailAddress; + } + input.select(); + return; + } + + if (input.value == identityEmailAddress) { + // If user unchecks checkbox and the input has default address, clear input. + input.value = ""; + } +} + +/** + * Handle the blur event of the Auto-Cc/Bcc checkboxes. + * + * @param {Event} event - The blur event of the checkbox. + */ +function identityDoCcBccOnBlur(event) { + let input = event.target; + // Safeguard against space-padded address list to ensure list visibility. + input.value = input.value.trim(); +} + +// Enable and disable pickers based on the radio element clicked +function SetPickerEnabling(enablePickerId, disablePickerId) { + var activePicker = document.getElementById(enablePickerId); + activePicker.removeAttribute("disabled"); + + var inactivePicker = document.getElementById(disablePickerId); + inactivePicker.setAttribute("disabled", "true"); +} + +// Set radio element choices and picker states +function setPickersState(enablePickerId, disablePickerId, event) { + SetPickerEnabling(enablePickerId, disablePickerId); + + var radioElemValue = event.target.value; + + switch (event.target.id) { + case "fcc_selectAccount": + case "fcc_selectFolder": + gFccRadioElemChoice = radioElemValue; + break; + case "archive_selectAccount": + case "archive_selectFolder": + gArchivesRadioElemChoice = radioElemValue; + updateArchiveHierarchyButton( + document.getElementById(enablePickerId).folder + ); + break; + case "draft_selectAccount": + case "draft_selectFolder": + gDraftsRadioElemChoice = radioElemValue; + break; + case "tmpl_selectAccount": + case "tmpl_selectFolder": + gTmplRadioElemChoice = radioElemValue; + break; + default: + dump("Error in setting picker state.\n"); + } +} + +// This routine is to restore the correct radio element +// state when the fcc self checkbox broadcasts the change +function SetRadioButtons(selectPickerId, unselectPickerId) { + var activeRadioElem = document.getElementById(selectPickerId); + activeRadioElem.radioGroup.selectedItem = activeRadioElem; +} + +/** + * Enable/disable the archive hierarchy button depending on what folder is + * currently selected (Gmail IMAP folders should have the button disabled, since + * changing the archive hierarchy does nothing there. + * + * @param {nsIMsgFolder} archiveFolder - The currently-selected folder to store + * archives in + */ +function updateArchiveHierarchyButton(archiveFolder) { + let isGmailImap = + archiveFolder.server.type == "imap" && + archiveFolder.server.QueryInterface(Ci.nsIImapIncomingServer).isGMailServer; + document.getElementById("archiveHierarchyButton").disabled = isGmailImap; +} + +/** + * Enable or disable (as appropriate) the controls for setting archive options + */ +function setupArchiveItems() { + let checked = document.getElementById("identity.archiveEnabled").checked; + document.querySelectorAll(".depends-on-archive").forEach(e => { + if (checked) { + e.removeAttribute("disabled"); + } else { + e.setAttribute("disabled", "true"); + } + }); + if (!checked) { + return; + } + + switch (gArchivesRadioElemChoice) { + case "0": + if (!gArchivesRadioElemChoiceLocked) { + SetPickerEnabling( + "msgArchivesAccountPicker", + "msgArchivesFolderPicker" + ); + } + SetRadioButtons("archive_selectAccount", "archive_selectFolder"); + updateArchiveHierarchyButton( + document.getElementById("msgArchivesAccountPicker").folder + ); + break; + + case "1": + if (!gArchivesRadioElemChoiceLocked) { + SetPickerEnabling( + "msgArchivesFolderPicker", + "msgArchivesAccountPicker" + ); + } + SetRadioButtons("archive_selectFolder", "archive_selectAccount"); + updateArchiveHierarchyButton( + document.getElementById("msgArchivesFolderPicker").folder + ); + break; + + default: + dump("Error in setting Archive elements.\n"); + } +} + +/** + * Open a dialog to edit the folder hierarchy used when archiving messages. + */ +function ChangeArchiveHierarchy() { + let identity = parent.gIdentity || parent.getCurrentAccount().defaultIdentity; + let arg = { identity }; + + parent.gSubDialog.open( + "chrome://messenger/content/am-archiveoptions.xhtml", + undefined, + arg + ); +} diff --git a/comm/mailnews/base/prefs/content/am-copies.xhtml b/comm/mailnews/base/prefs/content/am-copies.xhtml new file mode 100644 index 0000000000..c142cb8eb5 --- /dev/null +++ b/comm/mailnews/base/prefs/content/am-copies.xhtml @@ -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/skin/accountManage.css" type="text/css"?> +<?xml-stylesheet href="chrome://messenger/skin/icons.css" type="text/css"?> +<?xml-stylesheet href="chrome://messenger/skin/folderMenus.css" type="text/css"?> + +<!DOCTYPE html SYSTEM "chrome://messenger/locale/am-copies.dtd"> + +<html xmlns="http://www.w3.org/1999/xhtml" + xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + xmlns:html="http://www.w3.org/1999/xhtml"> +<head> + <title>©AndFolderTitle.label;</title> + <script defer="defer" src="chrome://messenger/content/globalOverlay.js"></script> + <script defer="defer" src="chrome://global/content/editMenuOverlay.js"></script> + <script defer="defer" src="chrome://messenger/content/am-copies.js"></script> + <script defer="defer" src="chrome://messenger/content/am-prefs.js"></script> + <script defer="defer" src="chrome://messenger/content/amUtils.js"></script> + <script> + // FIXME: move to script file. + window.addEventListener("load", event => { parent.onPanelLoaded('am-copies.xhtml'); }); + </script> +</head> +<html:body xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + <vbox id="containerBox" flex="1"> + <hbox class="dialogheader"> + <label class="dialogheader-title" value="©AndFolderTitle.label;"/> + </hbox> +#include am-copies.inc.xhtml + </vbox> +</html:body> +</html> diff --git a/comm/mailnews/base/prefs/content/am-identities-list.js b/comm/mailnews/base/prefs/content/am-identities-list.js new file mode 100644 index 0000000000..02d20e1434 --- /dev/null +++ b/comm/mailnews/base/prefs/content/am-identities-list.js @@ -0,0 +1,206 @@ +/* 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 { Gloda } = ChromeUtils.import("resource:///modules/gloda/Gloda.jsm"); + +var gIdentityListBox; // the root <richlistbox> node +var gAddButton; +var gEditButton; +var gSetDefaultButton; +var gDeleteButton; + +var gAccount = null; // the account we are showing the identities for + +window.addEventListener("DOMContentLoaded", onLoad); + +document.addEventListener("dialogaccept", onOk); +document.addEventListener("dialogcancel", onOk); + +function onLoad() { + gIdentityListBox = document.getElementById("identitiesList"); + gAddButton = document.getElementById("cmd_add"); + gEditButton = document.getElementById("cmd_edit"); + gSetDefaultButton = document.getElementById("cmd_default"); + gDeleteButton = document.getElementById("cmd_delete"); + + // extract the account + gAccount = window.arguments[0].account; + + var accountName = window.arguments[0].accountName; + document.title = document + .getElementById("bundle_prefs") + .getFormattedString("identity-list-title", [accountName]); + + refreshIdentityList(0); +} + +/** + * Rebuilds the listbox holding the list of identities. + * + * @param {number} aSelectIndex - Attempt to select the identity with this index. + */ +function refreshIdentityList(aSelectIndex) { + // Remove all children. + while (gIdentityListBox.hasChildNodes()) { + gIdentityListBox.lastChild.remove(); + } + + // Build the list from the identities array. + for (let identity of gAccount.identities) { + if (identity.valid) { + let label = document.createXULElement("label"); + label.setAttribute("value", identity.identityName); + + let listitem = document.createXULElement("richlistitem"); + listitem.appendChild(label); + listitem.setAttribute("key", identity.key); + gIdentityListBox.appendChild(listitem); + } + } + + // Ensure one identity is always selected. + if (!aSelectIndex || aSelectIndex < 0) { + aSelectIndex = 0; + } else if (aSelectIndex >= gIdentityListBox.itemCount) { + aSelectIndex = gIdentityListBox.itemCount - 1; + } + + // This also fires the onselect event, which in turn calls updateButtons(). + gIdentityListBox.selectedIndex = aSelectIndex; +} + +/** + * Opens the identity editor dialog. + * + * @param {nsIMsgIdentity} identity - The identity (if any) to load in the dialog. + */ +function openIdentityEditor(identity) { + let args = { identity, account: gAccount, result: false }; + + let indexToSelect = identity + ? gIdentityListBox.selectedIndex + : gIdentityListBox.itemCount; + + parent.gSubDialog.open( + "chrome://messenger/content/am-identity-edit.xhtml", + { closingCallback: onCloseIdentity }, + args + ); + + function onCloseIdentity() { + if (args.result) { + refreshIdentityList(indexToSelect); + } + } +} + +function getSelectedIdentity() { + if (gIdentityListBox.selectedItems.length != 1) { + return null; + } + + let identityKey = gIdentityListBox.selectedItems[0].getAttribute("key"); + return ( + gAccount.identities.find(id => id.valid && id.key == identityKey) || null + ); +} + +function onEdit(event) { + var id = getSelectedIdentity(); + openIdentityEditor(id); +} + +/** + * Enable/disable buttons depending on number of identities and current selection. + */ +function updateButtons() { + // In this listbox there should always be one item selected. + if ( + gIdentityListBox.selectedItems.length != 1 || + gIdentityListBox.itemCount == 0 + ) { + // But in case this is not met (e.g. there is no identity for some reason, + // or the list is being rebuilt), disable all buttons. + gEditButton.setAttribute("disabled", "true"); + gDeleteButton.setAttribute("disabled", "true"); + gSetDefaultButton.setAttribute("disabled", "true"); + return; + } + + gEditButton.setAttribute("disabled", "false"); + gDeleteButton.setAttribute( + "disabled", + gIdentityListBox.itemCount <= 1 ? "true" : "false" + ); + gSetDefaultButton.setAttribute( + "disabled", + gIdentityListBox.selectedIndex == 0 ? "true" : "false" + ); + // The Add command is always enabled. +} + +function onSetDefault(event) { + let identity = getSelectedIdentity(); + if (!identity) { + return; + } + + // If the first identity is selected, there is nothing to do. + if (gIdentityListBox.selectedIndex == 0) { + return; + } + + gAccount.defaultIdentity = identity; + // Rebuilt the identity list and select the moved identity again. + refreshIdentityList(0); + // Update gloda's myContact with the new default identity. + Gloda._initMyIdentities(); +} + +function onDelete(event) { + if (gIdentityListBox.itemCount <= 1) { + // don't support deleting the last identity + return; + } + + // get delete confirmation + let selectedIdentity = getSelectedIdentity(); + + let prefsBundle = document.getElementById("bundle_prefs"); + let confirmTitle = prefsBundle.getFormattedString( + "identity-delete-confirm-title", + [window.arguments[0].accountName] + ); + let confirmText = prefsBundle.getFormattedString("identity-delete-confirm", [ + selectedIdentity.identityName, + ]); + let confirmButton = prefsBundle.getString("identity-delete-confirm-button"); + + if ( + Services.prompt.confirmEx( + window, + confirmTitle, + confirmText, + Services.prompt.BUTTON_POS_0 * Services.prompt.BUTTON_TITLE_IS_STRING + + Services.prompt.BUTTON_POS_1 * Services.prompt.BUTTON_TITLE_CANCEL, + confirmButton, + null, + null, + null, + {} + ) + ) { + return; + } + + let selectedItemIndex = gIdentityListBox.selectedIndex; + + gAccount.removeIdentity(selectedIdentity); + + refreshIdentityList(selectedItemIndex); +} + +function onOk() { + window.arguments[0].result = true; +} diff --git a/comm/mailnews/base/prefs/content/am-identities-list.xhtml b/comm/mailnews/base/prefs/content/am-identities-list.xhtml new file mode 100644 index 0000000000..243630bd75 --- /dev/null +++ b/comm/mailnews/base/prefs/content/am-identities-list.xhtml @@ -0,0 +1,101 @@ +<?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/shared/grid-layout.css" type="text/css"?> +<?xml-stylesheet href="chrome://messenger/skin/accountManage.css" type="text/css"?> + +<!DOCTYPE html SYSTEM "chrome://messenger/locale/am-identities-list.dtd"> + +<html + xmlns="http://www.w3.org/1999/xhtml" + xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + xmlns:html="http://www.w3.org/1999/xhtml" + style="min-width: 600px" + scrolling="false" +> + <head> + <title><!-- identity-list-title --></title> + <script + defer="defer" + src="chrome://messenger/content/am-identities-list.js" + ></script> + </head> + <html:body + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + > + <dialog + buttons="accept" + buttonlabelaccept="&identitiesListClose.label;" + buttonaccesskeyaccept="&identitiesListClose.accesskey;" + style="height: 100vh" + > + <stringbundle + id="bundle_prefs" + src="chrome://messenger/locale/prefs.properties" + /> + + <commandset> + <command id="cmd_add" oncommand="openIdentityEditor(null);" /> + <command id="cmd_edit" oncommand="onEdit(event);" disabled="true" /> + <command + id="cmd_default" + oncommand="onSetDefault(event);" + disabled="true" + /> + <command id="cmd_delete" oncommand="onDelete(event);" disabled="true" /> + </commandset> + + <keyset> + <key keycode="VK_INSERT" command="cmd_add" /> + <key keycode="VK_DELETE" command="cmd_delete" /> + </keyset> + + <label control="identitiesList">&identitiesListManageDesc.label;</label> + + <separator class="thin" /> + + <html:div class="grid-two-column-auto-min" style="flex: 1"> + <richlistbox + id="identitiesList" + ondblclick="onEdit(event);" + onselect="updateButtons();" + seltype="single" + flex="1" + style="min-height: 10em; max-height: 20em" + /> + + <html:div class="flex-content-column"> + <button + id="addButton" + command="cmd_add" + label="&identitiesListAdd.label;" + accesskey="&identitiesListAdd.accesskey;" + /> + <button + id="editButton" + disabled="true" + command="cmd_edit" + label="&identitiesListEdit.label;" + accesskey="&identitiesListEdit.accesskey;" + /> + <button + id="setDefaultButton" + disabled="true" + command="cmd_default" + label="&identitiesListDefault.label;" + accesskey="&identitiesListDefault.accesskey;" + /> + <button + id="deleteButton" + disabled="true" + command="cmd_delete" + label="&identitiesListDelete.label;" + accesskey="&identitiesListDelete.accesskey;" + /> + </html:div> + </html:div> + </dialog> + </html:body> +</html> diff --git a/comm/mailnews/base/prefs/content/am-identity-edit.js b/comm/mailnews/base/prefs/content/am-identity-edit.js new file mode 100644 index 0000000000..216edcaa53 --- /dev/null +++ b/comm/mailnews/base/prefs/content/am-identity-edit.js @@ -0,0 +1,577 @@ +/* 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/. */ + +/* import-globals-from am-addressing.js */ +/* import-globals-from am-copies.js */ +/* import-globals-from ../../../../mail/extensions/am-e2e/am-e2e.js */ + +var { MailServices } = ChromeUtils.import( + "resource:///modules/MailServices.jsm" +); + +var gIdentity = null; // the identity we are editing (may be null for a new identity) +var gAccount = null; // the account the identity is (or will be) associated with + +document.addEventListener("dialogaccept", onOk); + +function onLoadIdentityProperties() { + // extract the account + gIdentity = window.arguments[0].identity; + gAccount = window.arguments[0].account; + let prefBundle = document.getElementById("bundle_prefs"); + + if (gIdentity) { + let listName = gIdentity.identityName; + document.title = prefBundle.getFormattedString("identityDialogTitleEdit", [ + listName, + ]); + } else { + document.title = prefBundle.getString("identityDialogTitleAdd"); + } + + loadSMTPServerList(); + + initIdentityValues(gIdentity); + initCopiesAndFolder(gIdentity); + initCompositionAndAddressing(gIdentity); + initE2EEncryption(gIdentity); + + // E2E needs an email... hide until we have one. + document.getElementById("identityE2ETab").hidden = !gIdentity?.email; +} + +// based on the values of gIdentity, initialize the identity fields we expose to the user +function initIdentityValues(identity) { + function initSmtpServer(aServerKey) { + // Select a server in the SMTP server menulist by its key. + // The value of the identity.smtpServerKey is null when the + // "use default server" option is used so, if we get that passed in, select + // the useDefaultItem representing this option by using the value of "". + document.getElementById("identity.smtpServerKey").value = aServerKey || ""; + } + + if (identity) { + document.getElementById("identity.fullName").value = identity.fullName; + document.getElementById("identity.email").value = identity.email; + document.getElementById("identity.replyTo").value = identity.replyTo; + document.getElementById("identity.organization").value = + identity.organization; + document.getElementById("identity.attachSignature").checked = + identity.attachSignature; + document.getElementById("identity.htmlSigText").value = + identity.htmlSigText; + document.getElementById("identity.htmlSigFormat").checked = + identity.htmlSigFormat; + + if (identity.signature) { + document.getElementById("identity.signature").value = + identity.signature.path; + } + + document.getElementById("identity.attachVCard").checked = + identity.attachVCard; + document.getElementById("identity.escapedVCard").value = + identity.escapedVCard || ""; + + document.getElementById("identity.catchAll").checked = identity.catchAll; + document.getElementById("identity.catchAllHint").value = + identity.catchAllHint; + + initSmtpServer(identity.smtpServerKey); + + // In am-main.xhtml this field has no ID, because it's hidden by other means. + let catchAllBox = document.getElementById("identityCatchAllBox"); + if (catchAllBox) { + let servers = MailServices.accounts.getServersForIdentity(identity); + catchAllBox.hidden = servers.length > 0 && servers[0].type == "nntp"; + } + + // This field does not exist for the default identity shown in the am-main.xhtml pane. + let idLabel = document.getElementById("identity.label"); + if (idLabel) { + idLabel.value = identity.label; + } + } else { + // We're adding an identity, use the best default we have. + initSmtpServer(gAccount.defaultIdentity.smtpServerKey); + + // Hide catchAll until we know what this identitity is associated with. + document.getElementById("identityCatchAllBox").hidden = true; + } + + setupSignatureItems(); +} + +function initCopiesAndFolder(identity) { + // if we are editing an existing identity, use it...otherwise copy our values from the default identity + var copiesAndFoldersIdentity = identity ? identity : gAccount.defaultIdentity; + + document.getElementById("identity.fccFolder").value = + copiesAndFoldersIdentity.fccFolder; + document.getElementById("identity.draftFolder").value = + copiesAndFoldersIdentity.draftFolder; + document.getElementById("identity.archiveFolder").value = + copiesAndFoldersIdentity.archiveFolder; + document.getElementById("identity.stationeryFolder").value = + copiesAndFoldersIdentity.stationeryFolder; + + document.getElementById("identity.fccFolderPickerMode").value = + copiesAndFoldersIdentity.fccFolderPickerMode + ? copiesAndFoldersIdentity.fccFolderPickerMode + : 0; + document.getElementById("identity.draftsFolderPickerMode").value = + copiesAndFoldersIdentity.draftsFolderPickerMode + ? copiesAndFoldersIdentity.draftsFolderPickerMode + : 0; + document.getElementById("identity.archivesFolderPickerMode").value = + copiesAndFoldersIdentity.archivesFolderPickerMode + ? copiesAndFoldersIdentity.archivesFolderPickerMode + : 0; + document.getElementById("identity.tmplFolderPickerMode").value = + copiesAndFoldersIdentity.tmplFolderPickerMode + ? copiesAndFoldersIdentity.tmplFolderPickerMode + : 0; + + document.getElementById("identity.doCc").checked = + copiesAndFoldersIdentity.doCc; + document.getElementById("identity.doCcList").value = + copiesAndFoldersIdentity.doCcList; + document.getElementById("identity.doBcc").checked = + copiesAndFoldersIdentity.doBcc; + document.getElementById("identity.doBccList").value = + copiesAndFoldersIdentity.doBccList; + document.getElementById("identity.doFcc").checked = + copiesAndFoldersIdentity.doFcc; + document.getElementById("identity.fccReplyFollowsParent").checked = + copiesAndFoldersIdentity.fccReplyFollowsParent; + document.getElementById("identity.showSaveMsgDlg").checked = + copiesAndFoldersIdentity.showSaveMsgDlg; + document.getElementById("identity.archiveEnabled").checked = + copiesAndFoldersIdentity.archiveEnabled; + + onInitCopiesAndFolders(); // am-copies.js method +} + +function initCompositionAndAddressing(identity) { + // if we are editing an existing identity, use it...otherwise copy our values from the default identity + var addressingIdentity = identity ? identity : gAccount.defaultIdentity; + + document.getElementById("identity.directoryServer").value = + addressingIdentity.directoryServer; + document.getElementById("identity.overrideGlobal_Pref").value = + addressingIdentity.overrideGlobalPref; + let autoCompleteElement = document.getElementById( + "identity.autocompleteToMyDomain" + ); + if (autoCompleteElement) { + // Thunderbird does not have this element. + autoCompleteElement.checked = addressingIdentity.autocompleteToMyDomain; + } + + document.getElementById("identity.composeHtml").checked = + addressingIdentity.composeHtml; + document.getElementById("identity.autoQuote").checked = + addressingIdentity.autoQuote; + document.getElementById("identity.replyOnTop").value = + addressingIdentity.replyOnTop; + document.getElementById("identity.sig_bottom").value = + addressingIdentity.sigBottom; + document.getElementById("identity.sig_on_reply").checked = + addressingIdentity.sigOnReply; + document.getElementById("identity.sig_on_fwd").checked = + addressingIdentity.sigOnForward; + + onInitCompositionAndAddressing(); // am-addressing.js method +} + +function onOk(event) { + if (!validEmailAddress()) { + event.preventDefault(); + return; + } + + // if we are adding a new identity, create an identity, set the fields and add it to the + // account. + if (!gIdentity) { + // ask the account manager to create a new identity for us + gIdentity = MailServices.accounts.createIdentity(); + + // copy in the default identity settings so we inherit lots of stuff like the default drafts folder, etc. + gIdentity.copy(gAccount.defaultIdentity); + + // assume the identity is valid by default? + gIdentity.valid = true; + + // add the identity to the account + gAccount.addIdentity(gIdentity); + + // now fall through to saveFields which will save our new values + } + + // if we are modifying an existing identity, save the fields + saveIdentitySettings(gIdentity); + saveCopiesAndFolderSettings(gIdentity); + saveAddressingAndCompositionSettings(gIdentity); + saveE2EEncryptionSettings(gIdentity); + + window.arguments[0].result = true; +} + +// returns false and prompts the user if +// the identity does not have an email address +function validEmailAddress() { + var emailAddress = document.getElementById("identity.email").value; + + // quickly test for an @ sign to test for an email address. We don't have + // to be anymore precise than that. + if (!emailAddress.includes("@")) { + // alert user about an invalid email address + + var prefBundle = document.getElementById("bundle_prefs"); + + Services.prompt.alert( + window, + prefBundle.getString("identity-edit-req-title"), + prefBundle.getString("identity-edit-req") + ); + return false; + } + + return true; +} + +function saveIdentitySettings(identity) { + if (identity) { + let idLabel = document.getElementById("identity.label"); + if (idLabel) { + identity.label = idLabel.value; + } + identity.fullName = document.getElementById("identity.fullName").value; + identity.email = document.getElementById("identity.email").value; + identity.replyTo = document.getElementById("identity.replyTo").value; + identity.organization = document.getElementById( + "identity.organization" + ).value; + identity.attachSignature = document.getElementById( + "identity.attachSignature" + ).checked; + identity.htmlSigText = document.getElementById( + "identity.htmlSigText" + ).value; + identity.htmlSigFormat = document.getElementById( + "identity.htmlSigFormat" + ).checked; + + identity.attachVCard = document.getElementById( + "identity.attachVCard" + ).checked; + identity.escapedVCard = document.getElementById( + "identity.escapedVCard" + ).value; + identity.catchAll = document.getElementById("identity.catchAll").checked; + identity.catchAllHint = document.getElementById( + "identity.catchAllHint" + ).value; + identity.smtpServerKey = document.getElementById( + "identity.smtpServerKey" + ).value; + + let attachSignaturePath = + document.getElementById("identity.signature").value; + identity.signature = null; // this is important so we don't accidentally inherit the default + + if (attachSignaturePath) { + // convert signature path back into a nsIFile + var sfile = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile); + sfile.initWithPath(attachSignaturePath); + if (sfile.exists()) { + identity.signature = sfile; + } + } + } +} + +function saveCopiesAndFolderSettings(identity) { + onSaveCopiesAndFolders(); // am-copies.js routine + + identity.fccFolder = document.getElementById("identity.fccFolder").value; + identity.draftFolder = document.getElementById("identity.draftFolder").value; + identity.archiveFolder = document.getElementById( + "identity.archiveFolder" + ).value; + identity.stationeryFolder = document.getElementById( + "identity.stationeryFolder" + ).value; + identity.fccFolderPickerMode = document.getElementById( + "identity.fccFolderPickerMode" + ).value; + identity.draftsFolderPickerMode = document.getElementById( + "identity.draftsFolderPickerMode" + ).value; + identity.archivesFolderPickerMode = document.getElementById( + "identity.archivesFolderPickerMode" + ).value; + identity.tmplFolderPickerMode = document.getElementById( + "identity.tmplFolderPickerMode" + ).value; + identity.doCc = document.getElementById("identity.doCc").checked; + identity.doCcList = document.getElementById("identity.doCcList").value; + identity.doBcc = document.getElementById("identity.doBcc").checked; + identity.doBccList = document.getElementById("identity.doBccList").value; + identity.doFcc = document.getElementById("identity.doFcc").checked; + identity.fccReplyFollowsParent = document.getElementById( + "identity.fccReplyFollowsParent" + ).checked; + identity.showSaveMsgDlg = document.getElementById( + "identity.showSaveMsgDlg" + ).checked; + identity.archiveEnabled = document.getElementById( + "identity.archiveEnabled" + ).checked; +} + +function saveAddressingAndCompositionSettings(identity) { + identity.directoryServer = document.getElementById( + "identity.directoryServer" + ).value; + identity.overrideGlobalPref = + document.getElementById("identity.overrideGlobal_Pref").value == "true"; + let autoCompleteElement = document.getElementById( + "identity.autocompleteToMyDomain" + ); + if (autoCompleteElement) { + // Thunderbird does not have this element. + identity.autocompleteToMyDomain = autoCompleteElement.checked; + } + identity.composeHtml = document.getElementById( + "identity.composeHtml" + ).checked; + identity.autoQuote = document.getElementById("identity.autoQuote").checked; + identity.replyOnTop = document.getElementById("identity.replyOnTop").value; + identity.sigBottom = + document.getElementById("identity.sig_bottom").value == "true"; + identity.sigOnReply = document.getElementById( + "identity.sig_on_reply" + ).checked; + identity.sigOnForward = document.getElementById( + "identity.sig_on_fwd" + ).checked; +} + +function selectFile() { + const nsIFilePicker = Ci.nsIFilePicker; + + var fp = Cc["@mozilla.org/filepicker;1"].createInstance(nsIFilePicker); + + var prefBundle = document.getElementById("bundle_prefs"); + var title = prefBundle.getString("choosefile"); + fp.init(window, title, nsIFilePicker.modeOpen); + fp.appendFilters(nsIFilePicker.filterAll); + + // Get current signature folder, if there is one. + // We can set that to be the initial folder so that users + // can maintain their signatures better. + var sigFolder = GetSigFolder(); + if (sigFolder) { + fp.displayDirectory = sigFolder; + } + + fp.open(rv => { + if (rv != nsIFilePicker.returnOK || !fp.file) { + return; + } + document.getElementById("identity.signature").value = fp.file.path; + document + .getElementById("identity.signature") + .dispatchEvent(new CustomEvent("change")); + }); +} + +/** + * Adjust the catch-all hint so that is removes stars from the allowed pattern. + * We only allow to use stars for matching full domains *@example.com, + * not *foo@example.com. + * + * @param {Event} event - the oninput event of the catchAllHint input field. + */ +function handleInputCatchAllHint(event) { + let value = event.target.value; + event.target.value = value + .replace(/(\*[^@]+)/g, "*") + .replace(/(^|\s)@/g, "$1*@") + .replace(/\s*[;,]/g, ",") + .replace(/\s+/g, " "); +} + +function GetSigFolder() { + var sigFolder = null; + try { + var account = parent.getCurrentAccount(); + var identity = account.defaultIdentity; + var signatureFile = identity.signature; + + if (signatureFile) { + signatureFile = signatureFile.QueryInterface(Ci.nsIFile); + sigFolder = signatureFile.parent; + + if (!sigFolder.exists()) { + sigFolder = null; + } + } + } catch (ex) { + dump("failed to get signature folder..\n"); + } + return sigFolder; +} + +// Signature textbox is active unless option to select from file is checked. +// If a signature is need to be attached, the associated items which +// displays the absolute path to the signature (in a textbox) and the way +// to select a new signature file (a button) are enabled. Otherwise, they +// are disabled. Check to see if the attachSignature is locked to block +// broadcasting events. +function setupSignatureItems() { + var signature = document.getElementById("identity.signature"); + var browse = document.getElementById("identity.sigbrowsebutton"); + var htmlSigText = document.getElementById("identity.htmlSigText"); + var htmlSigFormat = document.getElementById("identity.htmlSigFormat"); + var attachSignature = document.getElementById("identity.attachSignature"); + var checked = attachSignature.checked; + + if (checked) { + htmlSigText.setAttribute("disabled", "disabled"); + htmlSigFormat.setAttribute("disabled", "true"); + } else { + htmlSigText.removeAttribute("disabled"); + htmlSigFormat.removeAttribute("disabled"); + } + + if (checked && !getAccountValueIsLocked(signature)) { + signature.removeAttribute("disabled"); + } else { + signature.setAttribute("disabled", "disabled"); + } + + if (checked && !getAccountValueIsLocked(browse)) { + browse.removeAttribute("disabled"); + } else { + browse.setAttribute("disabled", "true"); + } +} + +function editVCard() { + // Read vCard hidden value from UI. + let escapedVCard = document.getElementById("identity.escapedVCard"); + let dialog = top.document.getElementById("editVCardDialog"); + let form = dialog.querySelector("form"); + let vCardEdit = dialog.querySelector("vcard-edit"); + + vCardEdit.vCardString = decodeURIComponent(escapedVCard.value); + + top.addEventListener("keydown", editVCardKeyDown, { capture: true }); + form.addEventListener("submit", editVCardSubmit); + form.addEventListener("reset", editVCardReset); + + top.gSubDialog._topDialog?._overlay.removeAttribute("topmost"); + dialog.showModal(); +} + +function editVCardKeyDown(event) { + let dialog = top.document.getElementById("editVCardDialog"); + if (event.keyCode == KeyboardEvent.DOM_VK_ESCAPE && dialog.open) { + // This is a bit of a hack to prevent other dialogs (particularly + // SubDialogs) from closing when the vCard dialog is open. + event.preventDefault(); + editVCardReset(); + } +} + +function editVCardSubmit(event) { + let escapedVCard = document.getElementById("identity.escapedVCard"); + let dialog = top.document.getElementById("editVCardDialog"); + let form = dialog.querySelector("form"); + let vCardEdit = dialog.querySelector("vcard-edit"); + + vCardEdit.saveVCard(); + escapedVCard.value = encodeURIComponent(vCardEdit.vCardString); + // Trigger a change event so for the am-main view the event listener + // set up in AccountManager.js onLoad() can make sure to save the change. + escapedVCard.dispatchEvent(new CustomEvent("change")); + + top.gSubDialog._topDialog?._overlay.setAttribute("topmost", "true"); + dialog.close(); + + event.preventDefault(); + form.removeEventListener("submit", editVCardSubmit); + form.removeEventListener("reset", editVCardReset); +} + +function editVCardReset() { + let dialog = top.document.getElementById("editVCardDialog"); + let form = dialog.querySelector("form"); + + top.gSubDialog._topDialog?._overlay.setAttribute("topmost", "true"); + dialog.close(); + + form.removeEventListener("submit", editVCardSubmit); + form.removeEventListener("reset", editVCardReset); +} + +function getAccountForFolderPickerState() { + return gAccount; +} + +/** + * Build the SMTP server list for display. + */ +function loadSMTPServerList() { + var smtpServerList = document.getElementById("identity.smtpServerKey"); + let defaultServer = MailServices.smtp.defaultServer; + let currentValue = smtpServerList.value; + + var smtpPopup = smtpServerList.menupopup; + while (smtpPopup.lastChild.nodeName != "menuseparator") { + smtpPopup.lastChild.remove(); + } + + for (let server of MailServices.smtp.servers) { + let serverName = ""; + if (server.description) { + serverName = server.description + " - "; + } else if (server.username) { + serverName = server.username + " - "; + } + serverName += server.hostname; + + if (defaultServer.key == server.key) { + serverName += + " " + + document + .getElementById("bundle_messenger") + .getString("defaultServerTag"); + } + + smtpServerList.appendItem(serverName, server.key); + } + + smtpServerList.value = currentValue; +} + +/** + * Open dialog for editing properties of currently selected SMTP server. + */ +function editCurrentSMTP() { + let smtpKey = document.getElementById("identity.smtpServerKey").value; + let server = + smtpKey === "" + ? MailServices.smtp.defaultServer + : MailServices.smtp.getServerByKey(smtpKey); + let args = { server, result: false, addSmtpServer: "" }; + + parent.gSubDialog.open( + "chrome://messenger/content/SmtpServerEdit.xhtml", + { closingCallback: loadSMTPServerList }, + args + ); +} diff --git a/comm/mailnews/base/prefs/content/am-identity-edit.xhtml b/comm/mailnews/base/prefs/content/am-identity-edit.xhtml new file mode 100644 index 0000000000..d967b18202 --- /dev/null +++ b/comm/mailnews/base/prefs/content/am-identity-edit.xhtml @@ -0,0 +1,239 @@ +<?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/icons.css" type="text/css"?> +<?xml-stylesheet href="chrome://messenger/skin/folderMenus.css" type="text/css"?> +<?xml-stylesheet href="chrome://messenger/skin/accountManage.css" type="text/css"?> +<?xml-stylesheet href="chrome://messenger/skin/openpgp/inlineNotification.css" type="text/css"?> + +<!DOCTYPE html [ +<!ENTITY % identityEditDTD SYSTEM "chrome://messenger/locale/am-identity-edit.dtd" > +%identityEditDTD; +<!ENTITY % identityDTD SYSTEM "chrome://messenger/locale/am-main.dtd" > +%identityDTD; +<!ENTITY % copiesDTD SYSTEM "chrome://messenger/locale/am-copies.dtd"> +%copiesDTD; +<!ENTITY % addressingDTD SYSTEM "chrome://messenger/locale/am-addressing.dtd" > +%addressingDTD; +<!ENTITY % e2eDTD SYSTEM "chrome://messenger/locale/am-smime.dtd" > +%e2eDTD; +]> +<html xmlns="http://www.w3.org/1999/xhtml" + xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + xmlns:html="http://www.w3.org/1999/xhtml" + scrolling="false" + style="height: 900px; min-width: 800px;"> +<head> + <title><!-- identityDialogTitleEdit --></title> + <script defer="defer" src="chrome://messenger/content/globalOverlay.js"></script> + <script defer="defer" src="chrome://global/content/editMenuOverlay.js"></script> + <script defer="defer" src="chrome://global/content/preferencesBindings.js"></script> + <script defer="defer" src="chrome://messenger/content/am-prefs.js"></script> + <script defer="defer" src="chrome://messenger/content/amUtils.js"></script> + <script defer="defer" src="chrome://messenger/content/am-copies.js"></script> + <script defer="defer" src="chrome://messenger/content/am-addressing.js"></script> + <script defer="defer" src="chrome://messenger/content/am-e2e.js"></script> + <script defer="defer" src="chrome://messenger/content/am-identity-edit.js"></script> + <script> + // NOTE: am-identity-edit.js is not only used for this page. + window.addEventListener("DOMContentLoaded", onLoadIdentityProperties); + </script> +</head> +<html:body xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> +<dialog id="identityDialog"> + <stringbundle id="bundle_prefs" + src="chrome://messenger/locale/prefs.properties"/> + <stringbundle id="bundle_messenger" + src="chrome://messenger/locale/messenger.properties"/> + + <tabbox flex="1" style="overflow: hidden;"> + <tabs id="identitySettings"> + <tab id="identitySettingsTab" label="&settingsTab.label;"/> + <tab id="identityCopiesFoldersTab" label="&copiesFoldersTab.label;"/> + <tab id="identityAddressingTab" label="&addressingTab.label;"/> + <tab id="identityE2ETab" label="&e2eTitle.label;"/> + </tabs> + + <tabpanels id="identityTabsPanels" flex="1"> + <!-- Identity Settings Tab --> + <vbox flex="1" name="settings"> + <html:div> + <html:fieldset> + <html:legend>&publicData.label;</html:legend> + <html:table class="identity-table"> + <html:tr> + <html:th> + <label id="identity.fullName.label" + value="&name.label;" + control="identity.fullName" + accesskey="&name.accesskey;"/> + </html:th> + <html:td> + <html:input id="identity.fullName" + type="text" + class="input-inline" + aria-labelledby="identity.fullName.label" + size="30"/> + </html:td> + </html:tr> + <html:tr> + <html:th> + <label id="identity.email.label" + value="&email.label;" + control="identity.email" + accesskey="&email.accesskey;"/> + </html:th> + <html:td> + <html:input id="identity.email" + type="email" + class="uri-element input-inline" + aria-labelledby="identity.email.label"/> + + </html:td> + </html:tr> + <html:tr> + <html:th> + <label id="identity.replyTo.label" + value="&replyTo.label;" + accesskey="&replyTo.accesskey;" + control="identity.replyTo"/> + </html:th> + <html:td> + <html:input id="identity.replyTo" + type="text" + class="uri-element input-inline" + placeholder="&replyTo.placeholder;" + aria-labelledby="identity.replyTo.label"/> + </html:td> + </html:tr> + <html:tr> + <html:th> + <label id="identity.organization.label" + value="&organization.label;" + control="identity.organization" + accesskey="&organization.accesskey;"/> + </html:th> + <html:td> + <html:input id="identity.organization" + type="text" + class="input-inline" + aria-labelledby="identity.organization.label"/> + </html:td> + </html:tr> + <html:tr> + <html:th> + <label value="&signatureText.label;" control="identity.htmlSigText" + accesskey="&signatureText.accesskey;"/> + </html:th> + <html:td style="width:100%;"> + <checkbox id="identity.htmlSigFormat" label="&signatureHtml.label;" + accesskey="&signatureHtml.accesskey;" style="width:100%;"/> + </html:td> + </html:tr> + </html:table> + + <separator class="thin"/> + + <hbox class="indent input-container" flex="1"> + <html:textarea id="identity.htmlSigText" style="flex-grow: 1;" rows="4" class="signatureBox"/> + </hbox> + + <hbox align="center"> + <checkbox id="identity.attachSignature" label="&signatureFile.label;" + flex="1" accesskey="&signatureFile.accesskey;" + oncommand="setupSignatureItems();"/> + </hbox> + + <hbox align="center" class="indent input-container"> + <html:input id="identity.signature" + type="text" + datatype="nsIFile" + name="identity.signature" + aria-labelledby="identity.attachSignature" + class="uri-element input-inline"/> + <button id="identity.sigbrowsebutton" class="push" + label="&choose.label;" accesskey="&choose.accesskey;" + oncommand="selectFile()"/> + </hbox> + + <hbox align="center"> + <checkbox id="identity.attachVCard" label="&attachVCard.label;" + flex="1" accesskey="&attachVCard.accesskey;"/> + <button class="push" label="&editVCard.label;" + accesskey="&editVCard.accesskey;" oncommand="editVCard()"/> + <label id="identity.escapedVCard" hidden="true"/> + </hbox> + </html:fieldset> + </html:div> + + <html:div> + <html:fieldset> + <html:legend>&privateData.label;</html:legend> + + <hbox id="identityCatchAllBox" align="center" class="input-container"> + <checkbox id="identity.catchAll" + label="&catchAll.label;" + accesskey="&catchAll.accesskey;" + style="margin-block:auto;"/> + <html:input id="identity.catchAllHint" + type="text" + oninput="handleInputCatchAllHint(event);" + placeholder="list@example.com, *@example.com" + class="input-inline" + aria-labelledby="identity.catchAll"/> + </hbox> + + <separator class="thin"/> + + <label value="&smtpName.label;" + control="identity.smtpServerKey" + accesskey="&smtpName.accesskey;"/> + <hbox align="center"> + <menulist id="identity.smtpServerKey" flex="1"> + <menupopup id="smtpPopup"> + <menuitem id="useDefaultItem" value="" + label="&smtpDefaultServer.label;"/> + <menuseparator/> + <!-- list will be inserted here --> + </menupopup> + </menulist> + + <button id="editSmtp" + label="&smtpServerEdit.label;" + accesskey="&smtpServerEdit.accesskey;" + oncommand="editCurrentSMTP();"/> + </hbox> + + <separator class="thin"/> + + <hbox align="center" class="input-container"> + <label id="identity.input.label" value="&identityAlias.label;" + accesskey="&identityAlias.accesskey;" + style="margin-block: auto;" + control="identity.label"/> + <html:input id="identity.label" + type="text" + class="input-inline" + aria-labelledby="identity.input.label"/> + </hbox> + </html:fieldset> + </html:div> + + </vbox> + + <!-- Copies & Folders Tab --> +#include am-copies.inc.xhtml + + <!-- Composition & Addressing Tab --> +#include am-addressing.inc.xhtml + + <!-- Security Tab --> +#include ../../../../mail/extensions/am-e2e/am-e2e.inc.xhtml + + </tabpanels> + </tabbox> +</dialog> +</html:body> +</html> diff --git a/comm/mailnews/base/prefs/content/am-junk.js b/comm/mailnews/base/prefs/content/am-junk.js new file mode 100644 index 0000000000..a102efdfae --- /dev/null +++ b/comm/mailnews/base/prefs/content/am-junk.js @@ -0,0 +1,335 @@ +/* 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/. */ + +/* import-globals-from am-prefs.js */ +/* import-globals-from amUtils.js */ + +var { MailServices } = ChromeUtils.import( + "resource:///modules/MailServices.jsm" +); +var { MailUtils } = ChromeUtils.import("resource:///modules/MailUtils.jsm"); + +var gDeferredToAccount = ""; + +function onInit(aPageId, aServerId) { + // manually adjust several pref UI elements + document.getElementById("server.spamLevel.visible").checked = + document.getElementById("server.spamLevel").value > 0; + + let deferredToURI = null; + if (gDeferredToAccount) { + deferredToURI = + MailServices.accounts.getAccount(gDeferredToAccount).incomingServer + .serverURI; + } + + let spamActionTargetAccountElement = document.getElementById( + "server.spamActionTargetAccount" + ); + let spamActionTargetFolderElement = document.getElementById( + "server.spamActionTargetFolder" + ); + + let spamActionTargetAccount = spamActionTargetAccountElement.value; + let spamActionTargetFolder = spamActionTargetFolderElement.value; + + let moveOnSpamCheckbox = document.getElementById("server.moveOnSpam"); + let moveOnSpamValue = moveOnSpamCheckbox.checked; + + // Check if there are any invalid junk targets and fix them. + [spamActionTargetAccount, spamActionTargetFolder, moveOnSpamValue] = + sanitizeJunkTargets( + spamActionTargetAccount, + spamActionTargetFolder, + deferredToURI || aServerId, + document.getElementById("server.moveTargetMode").value, + MailUtils.getOrCreateFolder(aServerId).server.spamSettings, + moveOnSpamValue + ); + + spamActionTargetAccountElement.value = spamActionTargetAccount; + spamActionTargetFolderElement.value = spamActionTargetFolder; + moveOnSpamCheckbox.checked = moveOnSpamValue; + + let server = MailUtils.getOrCreateFolder(spamActionTargetAccount); + document.getElementById("actionAccountPopup").selectFolder(server); + + let folder = MailUtils.getExistingFolder(spamActionTargetFolder); + document.getElementById("actionFolderPopup").selectFolder(folder); + + var currentArray = []; + if (document.getElementById("server.useWhiteList").checked) { + currentArray = document + .getElementById("server.whiteListAbURI") + .value.split(" "); + } + + // set up the whitelist UI + var wList = document.getElementById("whiteListAbURI"); + // Ensure the whitelist is empty + while (wList.hasChildNodes()) { + wList.lastChild.remove(); + } + + // Populate the listbox with address books + let abItems = []; + for (let ab of MailServices.ab.directories) { + // We skip mailing lists and remote address books. + if (ab.isMailList || ab.isRemote) { + continue; + } + + abItems.push({ label: ab.dirName, URI: ab.URI }); + } + + // Sort the list + function sortFunc(a, b) { + return a.label.localeCompare(b.label); + } + abItems.sort(sortFunc); + + // And then append each item to the listbox + for (let abItem of abItems) { + let checkbox = document.createXULElement("checkbox"); + checkbox.setAttribute("label", abItem.label); + checkbox.checked = currentArray.includes(abItem.URI); + + let item = document.createXULElement("richlistitem"); + item.appendChild(checkbox); + item.setAttribute("value", abItem.URI); + wList.appendChild(item); + } + + wList.addEventListener("keypress", event => { + if ([" ", "Enter"].includes(event.key)) { + let checkbox = wList.currentItem.firstElementChild; + checkbox.checked = !checkbox.checked; + wList.dispatchEvent(new CustomEvent("command")); + } + }); + + // enable or disable the whitelist + onAdaptiveJunkToggle(); + + // set up trusted IP headers + var serverFilterList = document.getElementById("useServerFilterList"); + serverFilterList.value = document.getElementById( + "server.serverFilterName" + ).value; + if (!serverFilterList.selectedItem) { + serverFilterList.selectedIndex = 0; + } + + // enable or disable the useServerFilter checkbox + onCheckItem("useServerFilterList", ["server.useServerFilter"]); + + updateJunkTargetsAndRetention(); +} + +function onPreInit(account, accountValues) { + if ( + top.getAccountValue( + account, + accountValues, + "server", + "type", + null, + false + ) == "pop3" + ) { + gDeferredToAccount = top.getAccountValue( + account, + accountValues, + "pop3", + "deferredToAccount", + null, + false + ); + } + + buildServerFilterMenuList(); +} + +/** + * Called when someone checks or unchecks the adaptive junk mail checkbox. + * set the value of the hidden element accordingly + * + * @param {boolean} aValue - The boolean value of the checkbox. + */ +function updateSpamLevel(aValue) { + document.getElementById("server.spamLevel").value = aValue ? 100 : 0; + onAdaptiveJunkToggle(); +} + +/** + * Propagate changes to the server filter menu list back to + * our hidden wsm element. + */ +function onServerFilterListChange() { + document.getElementById("server.serverFilterName").value = + document.getElementById("useServerFilterList").value; +} + +/** + * Called when someone checks or unchecks the adaptive junk mail checkbox. + * We need to enable or disable the whitelist accordingly. + */ +function onAdaptiveJunkToggle() { + onCheckItem("whiteListAbURI", ["server.spamLevel.visible"]); + onCheckItem("whiteListLabel", ["server.spamLevel.visible"]); + + // Enable/disable individual listbox rows. + // Setting enable/disable on the parent listbox does not seem to work. + let wList = document.getElementById("whiteListAbURI"); + let wListDisabled = wList.disabled; + + for (let i = 0; i < wList.getRowCount(); i++) { + let item = wList.getItemAtIndex(i); + item.setAttribute("disabled", wListDisabled); + item.firstElementChild.setAttribute("disabled", wListDisabled); + } +} + +/** + * Called when someone checks or unchecks the "move new junk messages to" + * Enable/disable the radio group accordingly. + */ +function updateJunkTargetsAndRetention() { + onCheckItem("server.moveTargetMode", ["server.moveOnSpam"]); + updateJunkTargets(); + onCheckItem("server.purgeSpam", ["server.moveOnSpam"]); + document.getElementById("purgeLabel").disabled = + document.getElementById("server.purgeSpam").disabled; + updateJunkRetention(); +} + +/** + * Enable/disable the folder pickers depending on which radio item is selected. + */ +function updateJunkTargets() { + onCheckItem("actionTargetAccount", ["server.moveOnSpam", "moveTargetMode0"]); + onCheckItem("actionTargetFolder", ["server.moveOnSpam", "moveTargetMode1"]); +} + +/** + * Enable/disable the junk deletion interval depending on the state + * of the controlling checkbox. + */ +function updateJunkRetention() { + onCheckItem("server.purgeSpamInterval", [ + "server.purgeSpam", + "server.moveOnSpam", + ]); +} + +function onSave() { + onSaveWhiteList(); +} + +/** + * Propagate changes to the whitelist menu list back to + * our hidden wsm element. + */ +function onSaveWhiteList() { + var wList = document.getElementById("whiteListAbURI"); + var wlArray = []; + + for (let i = 0; i < wList.getRowCount(); i++) { + // Due to bug 448582, do not trust any properties of the listitems + // as they may not return the right value or may even not exist. + // Always get the attributes only. + var wlNode = wList.getItemAtIndex(i); + if (wlNode.firstElementChild.getAttribute("checked") == "true") { + let abURI = wlNode.getAttribute("value"); + wlArray.push(abURI); + } + } + var wlValue = wlArray.join(" "); + document + .getElementById("server.whiteListAbURI") + .setAttribute("value", wlValue); + document.getElementById("server.useWhiteList").checked = wlValue != ""; +} + +/** + * Called when a new value is chosen in one of the junk target folder pickers. + * Sets the menu label according to the folder name. + */ +function onActionTargetChange(aEvent, aWSMElementId) { + let folder = aEvent.target._folder; + document.getElementById(aWSMElementId).value = folder.URI; + document.getElementById("actionFolderPopup").selectFolder(folder); +} + +/** + * Enumerates over the "ISPDL" directories, calling buildServerFilterListFromDir + * for each one. + */ +function buildServerFilterMenuList() { + const KEY_ISP_DIRECTORY_LIST = "ISPDL"; + let ispHeaderList = document.getElementById("useServerFilterList"); + + // Ensure the menulist is empty. + ispHeaderList.removeAllItems(); + + // Now walk through the isp directories looking for sfd files. + let ispDirectories = Services.dirsvc.get( + KEY_ISP_DIRECTORY_LIST, + Ci.nsISimpleEnumerator + ); + + let menuEntries = []; + while (ispDirectories.hasMoreElements()) { + let ispDirectory = ispDirectories.getNext().QueryInterface(Ci.nsIFile); + if (ispDirectory) { + menuEntries.push.apply( + menuEntries, + buildServerFilterListFromDir(ispDirectory, menuEntries) + ); + } + } + + menuEntries.sort((a, b) => a.localeCompare(b)); + for (let entry of menuEntries) { + ispHeaderList.appendItem(entry, entry); + } +} + +/** + * Helper function called by buildServerFilterMenuList. Enumerates over the + * passed in directory looking for .sfd files. For each entry found, it gets + * appended to the menu list. + * + * @param {nsIFile} aDir - Directory to look for .sfd files + * @param {string[]} aExistingEntries - Filter names already found. + */ +function buildServerFilterListFromDir(aDir, aExistingEntries) { + let newEntries = []; + // Now iterate over each file in the directory looking for .sfd files. + const kSuffix = ".sfd"; + + for (let entry of aDir.directoryEntries) { + // we only care about files that end in .sfd + if (entry.isFile() && entry.leafName.endsWith(kSuffix)) { + let fileName = entry.leafName.slice(0, -kSuffix.length); + // If we've already added an item with this name, then don't add it again. + if (!aExistingEntries.includes(fileName)) { + newEntries.push(fileName); + } + } + } + return newEntries; +} + +/** + * Open the Preferences dialog on the Junk settings tab. + */ +function showGlobalJunkPrefs() { + openPrefsFromAccountManager( + "panePrivacy", + "privacyJunkCategory", + null, + "junk_pane" + ); +} diff --git a/comm/mailnews/base/prefs/content/am-junk.xhtml b/comm/mailnews/base/prefs/content/am-junk.xhtml new file mode 100644 index 0000000000..216d3c0431 --- /dev/null +++ b/comm/mailnews/base/prefs/content/am-junk.xhtml @@ -0,0 +1,293 @@ +<?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/accountManage.css" type="text/css"?> +<?xml-stylesheet href="chrome://messenger/skin/icons.css" type="text/css"?> +<?xml-stylesheet href="chrome://messenger/skin/folderMenus.css" type="text/css"?> + +<!DOCTYPE html [ <!ENTITY % brandDTD SYSTEM "chrome://branding/locale/brand.dtd"> +%brandDTD; +<!ENTITY % junkMailDTD SYSTEM "chrome://messenger/locale/am-junk.dtd"> +%junkMailDTD; ]> + +<html + xmlns="http://www.w3.org/1999/xhtml" + xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + xmlns:html="http://www.w3.org/1999/xhtml" +> + <head> + <title>&junkSettings.label;</title> + <script defer="defer" src="chrome://messenger/content/am-junk.js"></script> + <script defer="defer" src="chrome://messenger/content/am-prefs.js"></script> + <script defer="defer" src="chrome://messenger/content/amUtils.js"></script> + <script> + // FIXME: move to script file. + window.addEventListener("load", event => { + parent.onPanelLoaded("am-junk.xhtml"); + }); + </script> + </head> + <html:body + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + > + <vbox id="containerBox" flex="1"> + <stringbundle + id="bundle_messenger" + src="chrome://messenger/locale/messenger.properties" + /> + + <hbox class="dialogheader"> + <label class="dialogheader-title" value="&junkSettings.label;" /> + </hbox> + + <html:div> + <html:fieldset> + <html:legend>&junkClassification.label;</html:legend> + + <label + hidden="true" + id="server.spamLevel" + wsm_persist="true" + pref="true" + preftype="int" + prefattribute="value" + genericattr="true" + prefstring="mail.server.%serverkey%.spamLevel" + /> + <label + hidden="true" + id="server.spamActionTargetAccount" + wsm_persist="true" + pref="true" + preftype="string" + prefattribute="value" + genericattr="true" + prefstring="mail.server.%serverkey%.spamActionTargetAccount" + /> + <label + hidden="true" + id="server.spamActionTargetFolder" + wsm_persist="true" + pref="true" + preftype="wstring" + prefattribute="value" + genericattr="true" + prefstring="mail.server.%serverkey%.spamActionTargetFolder" + /> + <label + hidden="true" + id="server.whiteListAbURI" + wsm_persist="true" + pref="true" + preftype="string" + prefattribute="value" + genericattr="true" + prefstring="mail.server.%serverkey%.whiteListAbURI" + /> + <label + hidden="true" + id="server.serverFilterName" + wsm_persist="true" + pref="true" + preftype="string" + prefattribute="value" + genericattr="true" + prefstring="mail.server.%serverkey%.serverFilterName" + /> + + <checkbox + id="server.spamLevel.visible" + oncommand="updateSpamLevel(this.checked);" + accesskey="&level.accesskey;" + prefstring="mail.server.%serverkey%.spamLevel" + label="&level.label;" + /> + + <separator class="thin" /> + + <description width="1">&trainingDescription.label;</description> + + <separator class="thin" /> + <spacer height="3" /> + + <vbox class="indent"> + <checkbox + hidden="true" + id="server.useWhiteList" + genericattr="true" + pref="true" + wsm_persist="true" + preftype="bool" + prefstring="mail.server.%serverkey%.useWhiteList" + /> + <label + id="whiteListLabel" + accesskey="&whitelistHeader.accesskey;" + control="whiteListAbURI" + >&whitelistHeader.label;</label + > + <richlistbox id="whiteListAbURI" height="200px" /> + </vbox> + + <separator /> + + <vbox> + <hbox> + <checkbox + id="server.useServerFilter" + label="&ispHeaders.label;" + accesskey="&ispHeaders.accesskey;" + genericattr="true" + pref="true" + wsm_persist="true" + preftype="bool" + oncommand="onCheckItem('useServerFilterList', [this.id]);" + prefstring="mail.server.%serverkey%.useServerFilter" + /> + <menulist + id="useServerFilterList" + oncommand="onServerFilterListChange();" + aria-labelledby="server.useServerFilter" + /> + </hbox> + </vbox> + + <separator class="thin" /> + + <description class="indent tip-caption" width="1" + >&ispHeadersWarning.label;</description + > + </html:fieldset> + </html:div> + + <separator /> + <separator /> + + <html:div> + <html:fieldset> + <html:legend>&junkActions.label;</html:legend> + + <checkbox + id="server.moveOnSpam" + label="&move.label;" + accesskey="&move.accesskey;" + oncommand="updateJunkTargetsAndRetention();" + wsm_persist="true" + pref="true" + preftype="bool" + genericattr="true" + prefstring="mail.server.%serverkey%.moveOnSpam" + /> + + <radiogroup + id="server.moveTargetMode" + aria-labelledby="server.moveOnSpam" + prefstring="mail.server.%serverkey%.moveTargetMode" + wsm_persist="true" + pref="true" + preftype="int" + genericattr="true" + oncommand="updateJunkTargets();" + prefvalue="value" + > + <hbox class="specialFolderPickerGrid indent"> + <vbox> + <hbox flex="1" align="center"> + <radio + id="moveTargetMode0" + value="0" + label="&junkFolderOn.label;" + accesskey="&junkFolderOn.accesskey;" + /> + </hbox> + <hbox flex="1" align="center"> + <radio + id="moveTargetMode1" + value="1" + label="&otherFolder.label;" + accesskey="&otherFolder.accesskey;" + /> + </hbox> + </vbox> + <vbox flex="1"> + <menulist + id="actionTargetAccount" + class="folderMenuItem" + aria-labelledby="moveTargetMode0" + > + <menupopup + is="folder-menupopup" + id="actionAccountPopup" + class="menulist-menupopup" + expandFolders="false" + mode="filing" + oncommand="onActionTargetChange(event, 'server.spamActionTargetAccount');" + /> + </menulist> + <menulist + id="actionTargetFolder" + class="folderMenuItem" + aria-labelledby="moveTargetMode1" + displayformat="verbose" + > + <menupopup + is="folder-menupopup" + id="actionFolderPopup" + mode="junk" + showFileHereLabel="true" + oncommand="onActionTargetChange(event, 'server.spamActionTargetFolder');" + /> + </menulist> + </vbox> + </hbox> + </radiogroup> + + <hbox align="center" class="indent"> + <checkbox + id="server.purgeSpam" + genericattr="true" + pref="true" + wsm_persist="true" + preftype="bool" + prefstring="mail.server.%serverkey%.purgeSpam" + accesskey="&purge1.accesskey;" + oncommand="updateJunkRetention();" + label="&purge1.label;" + /> + <html:input + id="server.purgeSpamInterval" + type="number" + class="size3 input-inline" + min="1" + genericattr="true" + pref="true" + wsm_persist="true" + preftype="int" + aria-labelledby="server.purgeSpam server.purgeSpamInterval purgeLabel" + prefstring="mail.server.%serverkey%.purgeSpamInterval" + /> + <label + id="purgeLabel" + value="&purge2.label;" + control="server.purgeSpamInterval" + /> + </hbox> + </html:fieldset> + </html:div> + + <separator class="thin" /> + + <hbox pack="end"> + <button + id="globalJunkPrefsLink" + label="&globalJunkPrefs.label;" + accesskey="&globalJunkPrefs.accesskey;" + oncommand="showGlobalJunkPrefs();" + /> + </hbox> + </vbox> + </html:body> +</html> diff --git a/comm/mailnews/base/prefs/content/am-main.js b/comm/mailnews/base/prefs/content/am-main.js new file mode 100644 index 0000000000..b71ad49fb8 --- /dev/null +++ b/comm/mailnews/base/prefs/content/am-main.js @@ -0,0 +1,110 @@ +/* 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/. */ + +/* import-globals-from am-identity-edit.js */ + +var gAccount; + +/** + * Initialize am-main account settings page when it gets shown. + * Update an account's main settings title and set up signature items. + */ +function onInit() { + setAccountTitle(); + setupSignatureItems(); + Services.obs.addObserver( + onDefaultIdentityChange, + "account-default-identity-changed" + ); +} + +window.addEventListener("unload", function () { + Services.obs.removeObserver( + onDefaultIdentityChange, + "account-default-identity-changed" + ); +}); + +/** + * If the default identity for the current account changes, loads the values + * from the new default identity. + */ +function onDefaultIdentityChange(subject, topic, data) { + if (data == gAccount.key) { + initIdentityValues(subject.QueryInterface(Ci.nsIMsgIdentity)); + } +} + +/** + * Handle the blur event of the #server.prettyName pref input. + * Update account name in account manager tree and account settings' main title. + * + * @param {Event} event - Blur event from the pretty name input. + */ +function serverPrettyNameOnBlur(event) { + parent.setAccountLabel(gAccount.key, event.target.value); + setAccountTitle(); +} + +/** + * Update an account's main settings title with the account name if applicable. + */ +function setAccountTitle() { + let accountName = document.getElementById("server.prettyName"); + let title = document.querySelector("#am-main-title .dialogheader-title"); + let titleValue = title.getAttribute("defaultTitle"); + if (accountName.value) { + titleValue += " - " + accountName.value; + } + + title.setAttribute("value", titleValue); + document.title = titleValue; +} + +function onPreInit(account, accountValues) { + gAccount = account; + loadSMTPServerList(); + let type = parent.getAccountValue( + account, + accountValues, + "server", + "type", + null, + false + ); + hideShowControls(type); +} + +function manageIdentities() { + // We want to save the current identity information before bringing up the multiple identities + // UI. This ensures that the changes are reflected in the identity list dialog + // onSave(); + + if (!gAccount) { + return; + } + + var accountName = document.getElementById("server.prettyName").value; + + var args = { account: gAccount, accountName, result: false }; + + // save the current identity settings so they show up correctly + // if the user just changed them in the manage identities dialog + var identity = gAccount.defaultIdentity; + saveIdentitySettings(identity); + + parent.gSubDialog.open( + "chrome://messenger/content/am-identities-list.xhtml", + { closingCallback: onCloseIdentities }, + args + ); + + function onCloseIdentities() { + if (args.result) { + // Refresh the SMTP list in case the user changed server properties + // from the identity dialog. + loadSMTPServerList(); + } + } +} diff --git a/comm/mailnews/base/prefs/content/am-main.xhtml b/comm/mailnews/base/prefs/content/am-main.xhtml new file mode 100644 index 0000000000..09f5453fb8 --- /dev/null +++ b/comm/mailnews/base/prefs/content/am-main.xhtml @@ -0,0 +1,344 @@ +<?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/accountManage.css" type="text/css"?> + +<!DOCTYPE html SYSTEM "chrome://messenger/locale/am-main.dtd"> + +<html + xmlns="http://www.w3.org/1999/xhtml" + xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + xmlns:html="http://www.w3.org/1999/xhtml" +> + <head> + <script + defer="defer" + src="chrome://messenger/content/globalOverlay.js" + ></script> + <script + defer="defer" + src="chrome://global/content/editMenuOverlay.js" + ></script> + <script + defer="defer" + src="chrome://messenger/content/am-identity-edit.js" + ></script> + <script defer="defer" src="chrome://messenger/content/am-main.js"></script> + <script defer="defer" src="chrome://messenger/content/am-prefs.js"></script> + <script defer="defer" src="chrome://messenger/content/amUtils.js"></script> + <script> + // FIXME: move to script file. + window.addEventListener("load", event => { + parent.onPanelLoaded("am-main.xhtml"); + }); + </script> + </head> + <html:body + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + > + <vbox id="containerBox" flex="1"> + <stringbundle + id="bundle_prefs" + src="chrome://messenger/locale/prefs.properties" + /> + <stringbundle + id="bundle_messenger" + src="chrome://messenger/locale/messenger.properties" + /> + + <hbox id="am-main-title" class="dialogheader"> + <label class="dialogheader-title" defaultTitle="&accountTitle.label;" /> + </hbox> + + <separator class="thin" /> + + <hbox class="input-container"> + <label + id="server.prettyName.label" + value="&accountName.label;" + control="server.prettyName" + accesskey="&accountName.accesskey;" + /> + <html:input + id="server.prettyName" + type="text" + wsm_persist="true" + class="input-inline" + onblur="serverPrettyNameOnBlur(event);" + prefstring="mail.server.%serverkey%.name" + aria-labelledby="server.prettyName.label" + /> + </hbox> + + <separator /> + + <html:div> + <html:fieldset> + <html:legend>&identityTitle.label;</html:legend> + <description>&identityDesc.label;</description> + <separator class="thin" /> + <html:table class="identity-table"> + <html:tr> + <html:th> + <label + id="identity.fullName.label.label" + value="&name.label;" + control="identity.fullName" + accesskey="&name.accesskey;" + /> + </html:th> + <html:td> + <html:input + id="identity.fullName" + type="text" + class="input-inline" + aria-labelledby="identity.fullName.label" + wsm_persist="true" + size="30" + prefstring="mail.identity.%identitykey%.fullName" + /> + </html:td> + </html:tr> + <html:tr> + <html:th> + <label + id="identity.email.label" + value="&email.label;" + control="identity.email" + accesskey="&email.accesskey;" + /> + </html:th> + <html:td> + <html:input + id="identity.email" + type="email" + wsm_persist="true" + prefstring="mail.identity.%identitykey%.useremail" + class="uri-element input-inline" + aria-labelledby="identity.email.label" + /> + </html:td> + </html:tr> + <html:tr> + <html:th> + <label + id="identity.replyTo.label" + value="&replyTo.label;" + control="identity.replyTo" + accesskey="&replyTo.accesskey;" + /> + </html:th> + <html:td> + <html:input + id="identity.replyTo" + type="text" + wsm_persist="true" + prefstring="mail.identity.%identitykey%.reply_to" + class="uri-element input-inline" + placeholder="&replyTo.placeholder;" + aria-labelledby="identity.replyTo.label" + /> + </html:td> + </html:tr> + <html:tr> + <html:th> + <label + id="identity.organization.label" + value="&organization.label;" + control="identity.organization" + accesskey="&organization.accesskey;" + /> + </html:th> + <html:td> + <html:input + id="identity.organization" + type="text" + wsm_persist="true" + prefstring="mail.identity.%identitykey%.organization" + class="input-inline" + aria-labelledby="identity.organization.label" + /> + </html:td> + </html:tr> + <html:tr> + <html:th> + <label + value="&signatureText.label;" + control="identity.htmlSigText" + accesskey="&signatureText.accesskey;" + /> + </html:th> + <html:td style="width: 100%"> + <hbox align="center"> + <checkbox + id="identity.htmlSigFormat" + wsm_persist="true" + label="&signatureHtml.label;" + prefattribute="value" + accesskey="&signatureHtml.accesskey;" + style="width: 100%" + prefstring="mail.identity.%identitykey%.htmlSigFormat" + /> + </hbox> + </html:td> + </html:tr> + </html:table> + + <hbox + class="indent" + flex="1" + style="min-height: 100px; display: flex" + > + <html:textarea + id="identity.htmlSigText" + wsm_persist="true" + rows="4" + style="flex-grow: 1" + prefstring="mail.identity.%identitykey%.htmlSigText" + class="signatureBox" + /> + </hbox> + + <hbox align="center"> + <checkbox + id="identity.attachSignature" + wsm_persist="true" + label="&signatureFile.label;" + flex="1" + accesskey="&signatureFile.accesskey;" + oncommand="setupSignatureItems();" + prefattribute="value" + prefstring="mail.identity.%identitykey%.attach_signature" + /> + </hbox> + + <hbox align="center" class="indent input-container"> + <html:input + id="identity.signature" + type="text" + datatype="nsIFile" + wsm_persist="true" + name="identity.signature" + aria-labelledby="identity.attachSignature" + prefstring="mail.identity.%identitykey%.sig_file" + class="uri-element input-inline" + /> + <button + class="push" + name="browse" + label="&choose.label;" + accesskey="&choose.accesskey;" + oncommand="selectFile()" + wsm_persist="true" + id="identity.sigbrowsebutton" + prefstring="mail.identity.%identitykey%.sigbrowse.disable" + /> + </hbox> + + <hbox align="center"> + <checkbox + wsm_persist="true" + id="identity.attachVCard" + label="&attachVCard.label;" + flex="1" + accesskey="&attachVCard.accesskey;" + prefattribute="value" + prefstring="mail.identity.%identitykey%.attach_vcard" + /> + <button + class="push" + name="editVCard" + label="&editVCard.label;" + accesskey="&editVCard.accesskey;" + oncommand="editVCard()" + /> + <html:input + id="identity.escapedVCard" + type="hidden" + value="" + wsm_persist="true" + pref="true" + preftype="string" + prefattribute="value" + prefstring="mail.identity.%identitykey%.escapedVCard" + /> + </hbox> + + <separator class="thin" /> + + <hbox align="center" class="input-container" hidefor="nntp"> + <checkbox + id="identity.catchAll" + wsm_persist="true" + prefattribute="value" + label="&catchAll.label;" + accesskey="&catchAll.accesskey;" + style="margin-block: auto" + prefstring="mail.identity.%identitykey%.catchAll" + /> + <html:input + id="identity.catchAllHint" + type="text" + wsm_persist="true" + prefstring="mail.identity.%identitykey%.catchAllHint" + class="input-inline" + oninput="handleInputCatchAllHint(event);" + placeholder="list@example.com, *@example.com" + aria-labelledby="identity.catchAll" + /> + </hbox> + + <separator class="thin" /> + + <hbox align="center"> + <label + value="&smtpName.label;" + control="identity.smtpServerKey" + accesskey="&smtpName.accesskey;" + /> + <menulist + wsm_persist="true" + id="identity.smtpServerKey" + flex="1" + pref="true" + preftype="string" + prefattribute="value" + prefstring="mail.identity.%identitykey%.smtpServer" + > + <menupopup id="smtpPopup"> + <menuitem + value="" + label="&smtpDefaultServer.label;" + id="useDefaultItem" + /> + <menuseparator /> + <!-- list will be inserted here --> + </menupopup> + </menulist> + <button + id="editSmtp" + label="&smtpServerEdit.label;" + accesskey="&smtpServerEdit.accesskey;" + oncommand="editCurrentSMTP();" + /> + </hbox> + </html:fieldset> + </html:div> + + <separator class="thin" /> + + <hbox align="center"> + <spacer flex="1" /> + <button + label="&manageIdentities.label;" + oncommand="manageIdentities(event);" + accesskey="&manageIdentities.accesskey;" + wsm_persist="true" + id="identity.manageIdentitiesbutton" + /> + </hbox> + </vbox> + </html:body> +</html> diff --git a/comm/mailnews/base/prefs/content/am-offline.js b/comm/mailnews/base/prefs/content/am-offline.js new file mode 100644 index 0000000000..72158edace --- /dev/null +++ b/comm/mailnews/base/prefs/content/am-offline.js @@ -0,0 +1,435 @@ +/* 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/. */ + +/* import-globals-from am-prefs.js */ +/* import-globals-from ../../content/retention.js */ + +var gIncomingServer; +var gServerType; +var gImapIncomingServer; +var gPref = null; +var gLockedPref = {}; +var gOfflineMap = null; // map of folder URLs to offline flags +var gOfflineFolders; // initial state of allFoldersOffline checkbox +var gToggleOccurred = false; + +function onInit(aPageId, aServerId) { + onLockPreference(); + + // init values here + initServerSettings(); + initRetentionSettings(); + initDownloadSettings(); + initOfflineSettings(); + + onCheckItem1("offline.notDownloadMin", "offline.notDownload"); + onCheckItem1("nntp.downloadMsgMin", "nntp.downloadMsg"); + onCheckItem1("nntp.removeBodyMin", "nntp.removeBody"); + onCheckKeepMsg(); +} + +/** + * Store initial offline flag for each folder and the allFoldersOffline + * checkbox. Use to restore the flags and checkbox if edits are canceled. + */ +function initOfflineSettings() { + gOfflineMap = collectOfflineFolders(); + gOfflineFolders = document.getElementById("offline.folders").checked; + gToggleOccurred = false; +} + +function initServerSettings() { + document.getElementById("offline.notDownload").checked = + gIncomingServer.limitOfflineMessageSize; + document.getElementById("autosyncNotDownload").checked = + gIncomingServer.limitOfflineMessageSize; + if (gIncomingServer.maxMessageSize > 0) { + document.getElementById("offline.notDownloadMin").value = + gIncomingServer.maxMessageSize; + } else { + document.getElementById("offline.notDownloadMin").value = "50"; + } + + if (gServerType == "imap") { + gImapIncomingServer = gIncomingServer.QueryInterface( + Ci.nsIImapIncomingServer + ); + document.getElementById("offline.folders").checked = + gImapIncomingServer.offlineDownload; + } +} + +function initRetentionSettings() { + let retentionSettings = gIncomingServer.retentionSettings; + initCommonRetentionSettings(retentionSettings); + + document.getElementById("nntp.removeBody").checked = + retentionSettings.cleanupBodiesByDays; + document.getElementById("nntp.removeBodyMin").value = + retentionSettings.daysToKeepBodies > 0 + ? retentionSettings.daysToKeepBodies + : 30; +} + +function initDownloadSettings() { + let downloadSettings = gIncomingServer.downloadSettings; + document.getElementById("nntp.downloadMsg").checked = + downloadSettings.downloadByDate; + document.getElementById("nntp.notDownloadRead").checked = + downloadSettings.downloadUnreadOnly; + document.getElementById("nntp.downloadMsgMin").value = + downloadSettings.ageLimitOfMsgsToDownload > 0 + ? downloadSettings.ageLimitOfMsgsToDownload + : 30; + + // Figure out what the most natural division of the autosync pref into + // a value and an interval is. + let autosyncSelect = document.getElementById("autosyncSelect"); + let autosyncInterval = document.getElementById("autosyncInterval"); + let autosyncValue = document.getElementById("autosyncValue"); + let autosyncPref = document.getElementById("imap.autoSyncMaxAgeDays"); + let autosyncPrefValue = + autosyncPref.value == "" ? -1 : parseInt(autosyncPref.value, 10); + + // Clear the preference until we're done initializing. + autosyncPref.value = ""; + + if (autosyncPrefValue <= 0) { + // Special-case values <= 0 to have an interval of "All" and disabled + // controls for value and interval. + autosyncSelect.value = 0; + autosyncInterval.value = 1; + autosyncInterval.disabled = true; + autosyncValue.value = 30; + autosyncValue.disabled = true; + } else { + // Otherwise, get the list of possible intervals, in order from + // largest to smallest. + let valuesToTest = []; + for (let i = autosyncInterval.itemCount - 1; i >= 0; i--) { + valuesToTest.push(autosyncInterval.getItemAtIndex(i).value); + } + + // and find the first one that divides the preference evenly. + for (let i in valuesToTest) { + if (!(autosyncPrefValue % valuesToTest[i])) { + autosyncSelect.value = 1; + autosyncInterval.value = valuesToTest[i]; + autosyncValue.value = autosyncPrefValue / autosyncInterval.value; + break; + } + } + autosyncInterval.disabled = false; + autosyncValue.disabled = false; + } + autosyncPref.value = autosyncPrefValue; +} + +function onPreInit(account, accountValues) { + gServerType = top.getAccountValue( + account, + accountValues, + "server", + "type", + null, + false + ); + hideShowControls(gServerType); + gIncomingServer = account.incomingServer; + gIncomingServer.type = gServerType; + + // 10 is OFFLINE_SUPPORT_LEVEL_REGULAR, see nsIMsgIncomingServer.idl + // currently, there is no offline without diskspace + var titleStringID = + gIncomingServer.offlineSupportLevel >= 10 + ? "prefPanel-synchronization" + : "prefPanel-diskspace"; + + var prefBundle = document.getElementById("bundle_prefs"); + document + .querySelector("#headertitle > .dialogheader-title") + .setAttribute("value", prefBundle.getString(titleStringID)); + document.title = prefBundle.getString(titleStringID); + + if (gServerType == "pop3") { + var pop3Server = gIncomingServer.QueryInterface(Ci.nsIPop3IncomingServer); + // hide retention settings for deferred accounts + if (pop3Server.deferredToAccount.length) { + var retentionRadio = document.getElementById("retention.keepMsg"); + retentionRadio.setAttribute("hidden", "true"); + var retentionLabel = document.getElementById("retentionDescriptionPop"); + retentionLabel.setAttribute("hidden", "true"); + var applyToFlaggedCheckbox = document.getElementById( + "retention.applyToFlagged" + ); + applyToFlaggedCheckbox.setAttribute("hidden", "true"); + } + } +} + +function onClickSelect() { + parent.gSubDialog.open( + "chrome://messenger/content/msgSelectOfflineFolders.xhtml" + ); +} + +/** + * Handle updates to the Autosync + */ +function onAutosyncChange() { + let autosyncSelect = document.getElementById("autosyncSelect"); + let autosyncInterval = document.getElementById("autosyncInterval"); + let autosyncValue = document.getElementById("autosyncValue"); + let autosyncPref = document.getElementById("imap.autoSyncMaxAgeDays"); + + // If we're not done initializing, don't do anything. + // (See initDownloadSettings() for more details.) + if (autosyncPref.value == "") { + return; + } + + // If the user selected the All option, disable the autosync and the + // textbox. + if (autosyncSelect.value == 0) { + autosyncPref.value = -1; + autosyncInterval.disabled = true; + autosyncValue.disabled = true; + return; + } + + let max = 0x7fffffff / (60 * 60 * 24 * autosyncInterval.value); + autosyncValue.setAttribute("max", max); + if (autosyncValue.value > max) { + autosyncValue.value = Math.floor(max); + } + + autosyncInterval.disabled = false; + autosyncValue.disabled = false; + autosyncPref.value = autosyncValue.value * autosyncInterval.value; +} + +function onAutosyncNotDownload() { + // This function is called when the autosync version of offline.notDownload + // is changed it simply copies the new checkbox value over to the element + // driving the preference. + document.getElementById("offline.notDownload").checked = + document.getElementById("autosyncNotDownload").checked; + onCheckItem1("offline.notDownloadMin", "offline.notDownload"); +} + +function onCancel() { + // restore the offline flags for all folders + restoreOfflineFolders(gOfflineMap); + document.getElementById("offline.folders").checked = gOfflineFolders; +} + +/** + * Prompt to avoid unexpected folder sync changes. + */ +function onLeave() { + let changed = false; + if (gToggleOccurred) { + for (let folder of gIncomingServer.rootFolder.descendants) { + if ( + gOfflineMap[folder.folderURL] != + folder.getFlag(Ci.nsMsgFolderFlags.Offline) + ) { + // A change to the Offline flag to a folder was made. + changed = true; + break; + } + } + gToggleOccurred = false; + } + + if (changed) { + // The user changed the "Keep messages in all folders..." checkbox and + // caused changes in online/offline status for all folders in this + // account. Prompt whether to restore the original status. + let prefBundle = document.getElementById("bundle_prefs"); + let title = prefBundle.getString("confirmSyncChangesTitle"); + let question = prefBundle.getString("confirmSyncChanges"); + let discard = prefBundle.getString("confirmSyncChangesDiscard"); + let result = Services.prompt.confirmEx( + window, + title, + question, + Services.prompt.BUTTON_TITLE_SAVE * Services.prompt.BUTTON_POS_0 + + Services.prompt.BUTTON_TITLE_IS_STRING * Services.prompt.BUTTON_POS_1, + null, + discard, + null, + null, + { value: 0 } + ); + if (result == 1) { + // User clicked Discard button, so restore the online/offline changes for + // the current account. Changes made through the "Advanced..." dialog to + // other accounts will not be restored. + onCancel(); + return false; + } + } + return true; +} + +function onSave() { + var downloadSettings = Cc[ + "@mozilla.org/msgDatabase/downloadSettings;1" + ].createInstance(Ci.nsIMsgDownloadSettings); + + gIncomingServer.limitOfflineMessageSize = document.getElementById( + "offline.notDownload" + ).checked; + gIncomingServer.maxMessageSize = document.getElementById( + "offline.notDownloadMin" + ).value; + + var retentionSettings = saveCommonRetentionSettings( + gIncomingServer.retentionSettings + ); + + retentionSettings.daysToKeepBodies = + document.getElementById("nntp.removeBodyMin").value; + retentionSettings.cleanupBodiesByDays = + document.getElementById("nntp.removeBody").checked; + + downloadSettings.downloadByDate = + document.getElementById("nntp.downloadMsg").checked; + downloadSettings.downloadUnreadOnly = document.getElementById( + "nntp.notDownloadRead" + ).checked; + downloadSettings.ageLimitOfMsgsToDownload = document.getElementById( + "nntp.downloadMsgMin" + ).value; + + gIncomingServer.retentionSettings = retentionSettings; + gIncomingServer.downloadSettings = downloadSettings; + + if (gImapIncomingServer) { + // Set the pref on the incomingserver, and set the flag on all folders. + gImapIncomingServer.offlineDownload = + document.getElementById("offline.folders").checked; + } +} + +// Does the work of disabling an element given the array which contains xul id/prefstring pairs. +// Also saves the id/locked state in an array so that other areas of the code can avoid +// stomping on the disabled state indiscriminately. +function disableIfLocked(prefstrArray) { + for (let i = 0; i < prefstrArray.length; i++) { + var id = prefstrArray[i].id; + var element = document.getElementById(id); + if (gPref.prefIsLocked(prefstrArray[i].prefstring)) { + element.disabled = true; + gLockedPref[id] = true; + } else { + element.removeAttribute("disabled"); + gLockedPref[id] = false; + } + } +} + +// Disables xul elements that have associated preferences locked. +function onLockPreference() { + var initPrefString = "mail.server"; + var finalPrefString; + + // This panel does not use the code in AccountManager.js to handle + // the load/unload/disable. keep in mind new prefstrings and changes + // to code in AccountManager, and update these as well. + var allPrefElements = [ + { prefstring: "limit_offline_message_size", id: "offline.notDownload" }, + { prefstring: "limit_offline_message_size", id: "autosyncNotDownload" }, + { prefstring: "max_size", id: "offline.notDownloadMin" }, + { prefstring: "downloadUnreadOnly", id: "nntp.notDownloadRead" }, + { prefstring: "downloadByDate", id: "nntp.downloadMsg" }, + { prefstring: "ageLimit", id: "nntp.downloadMsgMin" }, + { prefstring: "retainBy", id: "retention.keepMsg" }, + { prefstring: "daysToKeepHdrs", id: "retention.keepOldMsgMin" }, + { prefstring: "numHdrsToKeep", id: "retention.keepNewMsgMin" }, + { prefstring: "daysToKeepBodies", id: "nntp.removeBodyMin" }, + { prefstring: "cleanupBodies", id: "nntp.removeBody" }, + { prefstring: "applyToFlagged", id: "retention.applyToFlagged" }, + { prefstring: "disable_button.selectFolder", id: "selectNewsgroupsButton" }, + { + prefstring: "disable_button.selectFolder", + id: "selectImapFoldersButton", + }, + ]; + + finalPrefString = initPrefString + "." + gIncomingServer.key + "."; + gPref = Services.prefs.getBranch(finalPrefString); + + disableIfLocked(allPrefElements); +} + +// XXX TODO: Function should be merged with onCheckItem in bug 755885. +function onCheckItem1(changeElementId, checkElementId) { + var element = document.getElementById(changeElementId); + var checked = document.getElementById(checkElementId).checked; + if (checked && !gLockedPref[checkElementId]) { + element.removeAttribute("disabled"); + } else { + element.setAttribute("disabled", "true"); + } +} + +function toggleOffline() { + let offline = document.getElementById("offline.folders").checked; + for (let folder of gIncomingServer.rootFolder.descendants) { + if (offline) { + folder.setFlag(Ci.nsMsgFolderFlags.Offline); + } else { + folder.clearFlag(Ci.nsMsgFolderFlags.Offline); + } + } + gToggleOccurred = true; +} + +function collectOfflineFolders() { + let offlineFolderMap = {}; + for (let folder of gIncomingServer.rootFolder.descendants) { + offlineFolderMap[folder.folderURL] = folder.getFlag( + Ci.nsMsgFolderFlags.Offline + ); + } + + return offlineFolderMap; +} + +function restoreOfflineFolders(offlineFolderMap) { + for (let folder of gIncomingServer.rootFolder.descendants) { + if (offlineFolderMap[folder.folderURL]) { + folder.setFlag(Ci.nsMsgFolderFlags.Offline); + } else { + folder.clearFlag(Ci.nsMsgFolderFlags.Offline); + } + } +} + +/** + * Checks if the user selected a permanent removal of messages from a server + * listed in the confirmfor attribute and warns about it. + * + * @param {Element} aRadio - The radiogroup element containing the retention options. + */ +function warnServerRemove(aRadio) { + let confirmFor = aRadio.getAttribute("confirmfor"); + + if ( + confirmFor && + confirmFor.split(",").includes(gServerType) && + aRadio.value != 1 + ) { + let prefBundle = document.getElementById("bundle_prefs"); + let title = prefBundle.getString("removeFromServerTitle"); + let question = prefBundle.getString("removeFromServer"); + if (!Services.prompt.confirm(window, title, question)) { + // If the user doesn't agree, fall back to not deleting anything. + aRadio.value = 1; + onCheckKeepMsg(); + } + } +} diff --git a/comm/mailnews/base/prefs/content/am-offline.xhtml b/comm/mailnews/base/prefs/content/am-offline.xhtml new file mode 100644 index 0000000000..0d3a12e396 --- /dev/null +++ b/comm/mailnews/base/prefs/content/am-offline.xhtml @@ -0,0 +1,350 @@ +<?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/accountManage.css" type="text/css"?> + +<!DOCTYPE html SYSTEM "chrome://messenger/locale/am-offline.dtd"> + +<html + xmlns="http://www.w3.org/1999/xhtml" + xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + xmlns:html="http://www.w3.org/1999/xhtml" +> + <head> + <script + defer="defer" + src="chrome://messenger/content/retention.js" + ></script> + <script + defer="defer" + src="chrome://messenger/content/am-offline.js" + ></script> + <script defer="defer" src="chrome://messenger/content/am-prefs.js"></script> + <script> + // FIXME: move to script file. + window.addEventListener("load", event => { + parent.onPanelLoaded("am-offline.xhtml"); + }); + </script> + </head> + <html:body + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + > + <vbox id="containerBox" flex="1"> + <stringbundle + id="bundle_prefs" + src="chrome://messenger/locale/prefs.properties" + /> + + <label hidden="true" wsm_persist="true" id="server.type" /> + <label + id="imap.autoSyncMaxAgeDays" + hidden="true" + wsm_persist="true" + preftype="int" + prefstring="mail.server.%serverkey%.autosync_max_age_days" + /> + + <hbox id="headertitle" class="dialogheader"> + <label class="dialogheader-title" /> + </hbox> + + <separator class="thin" /> + + <html:div> + <html:fieldset id="offline.titlebox" hidefor="pop3,none,rss"> + <html:legend>&syncGroupTitle.label;</html:legend> + + <vbox> + <checkbox + hidefor="pop3,nntp,none" + id="offline.folders" + label="&allFoldersOffline2.label;" + oncommand="toggleOffline()" + accesskey="&allFoldersOffline2.accesskey;" + /> + + <description hidefor="pop3,nntp,none,rss" + >&allFoldersOfflineNote.label;</description + > + + <separator class="thin" hidefor="pop3,nntp,none" /> + + <hbox hidefor="pop3,nntp,none" pack="end"> + <button + label="&offlineImapAdvancedOffline.label;" + accesskey="&offlineImapAdvancedOffline.accesskey;" + oncommand="onClickSelect()" + id="selectImapFoldersButton" + class="selectForOfflineUseButton" + /> + </hbox> + + <hbox hidefor="pop3,imap,none" pack="end"> + <button + label="&offlineSelectNntp.label;" + accesskey="&offlineSelectNntp.accesskey;" + oncommand="onClickSelect()" + id="selectNewsgroupsButton" + class="selectForOfflineUseButton" + /> + </hbox> + </vbox> + </html:fieldset> + </html:div> + + <html:div> + <html:fieldset id="diskspace.titlebox"> + <html:legend hidefor="pop3,none,rss" + >&diskspaceGroupTitle.label;</html:legend + > + + <description hidefor="pop3,nntp,none,rss" + >&doNotDownloadImap.label;</description + > + <description hidefor="pop3,imap,none,rss" + >&doNotDownloadNntp.label;</description + > + <description hidefor="imap,nntp,none,rss" + >&doNotDownloadPop3Movemail.label;</description + > + + <!-- IMAP Autosync Preference --> + <radiogroup + hidefor="pop3,nntp,none,rss" + id="autosyncSelect" + class="indent" + > + <radio + id="useAutosync.AllMsg" + value="0" + accesskey="&allAutosync.accesskey;" + label="&allAutosync.label;" + oncommand="onAutosyncChange();" + /> + <hbox flex="1" align="center"> + <radio + id="useAutosync.ByAge" + accesskey="&ageAutosync.accesskey;" + value="1" + label="&ageAutosyncBefore.label;" + oncommand="onAutosyncChange();" + /> + <html:input + id="autosyncValue" + type="number" + class="size4 input-inline autosync" + min="1" + onchange="onAutosyncChange();" + aria-labelledby="ageAutosyncBefore autosyncValue ageAutosyncMiddle autosyncInterval ageAutosyncAfter" + /> + <label + id="ageAutosyncMiddle" + control="autosyncValue" + value="&ageAutosyncMiddle.label;" + /> + <menulist id="autosyncInterval" onselect="onAutosyncChange();"> + <menupopup> + <menuitem label="&dayAgeInterval.label;" value="1" /> + <menuitem label="&weekAgeInterval.label;" value="7" /> + <menuitem label="&monthAgeInterval.label;" value="31" /> + <menuitem label="&yearAgeInterval.label;" value="365" /> + </menupopup> + </menulist> + <label + id="ageAutosyncAfter" + control="autosyncInterval" + value="&ageAutosyncAfter.label;" + /> + </hbox> + </radiogroup> + + <hbox align="center" class="indent" hidefor="rss"> + <checkbox + hidefor="pop3,imap,none" + id="nntp.notDownloadRead" + wsm_persist="true" + label="&nntpNotDownloadRead.label;" + accesskey="&nntpNotDownloadRead.accesskey;" + /> + </hbox> + + <hbox align="center" class="indent" hidefor="none,rss"> + <checkbox + wsm_persist="true" + id="offline.notDownload" + hidefor="imap" + label="&offlineNotDownload.label;" + accesskey="&offlineNotDownload.accesskey;" + oncommand="onCheckItem('offline.notDownloadMin', [this.id]);" + /> + <checkbox + wsm_persist="true" + id="autosyncNotDownload" + hidefor="pop3,nntp" + label="&autosyncNotDownload.label;" + accesskey="&autosyncNotDownload.accesskey;" + oncommand="onAutosyncNotDownload();" + /> + <html:input + id="offline.notDownloadMin" + type="number" + class="size4 input-inline" + min="1" + value="50" + wsm_persist="true" + aria-labelledby="offline.notDownload offline.notDownloadMin kbLabel" + /> + <label + value="&kb.label;" + control="offline.notDownloadMin" + id="kbLabel" + /> + </hbox> + + <hbox align="center" class="indent" hidefor="pop3,imap,none,rss"> + <checkbox + wsm_persist="true" + id="nntp.downloadMsg" + label="&nntpDownloadMsg.label;" + accesskey="&nntpDownloadMsg.accesskey;" + oncommand="onCheckItem('nntp.downloadMsgMin', [this.id]);" + /> + <html:input + id="nntp.downloadMsgMin" + type="number" + class="size2 input-inline" + min="1" + value="30" + wsm_persist="true" + aria-labelledby="nntp.downloadMsg nntp.downloadMsgMin daysOldLabel" + /> + <label + value="&daysOld.label;" + control="nntp.downloadMsgMin" + id="daysOldLabel" + /> + </hbox> + + <vbox align="start"> + <separator hidefor="none,rss" /> + <label + id="retentionDescription" + hidefor="imap,pop3" + class="desc" + control="retention.keepMsg" + >&retentionCleanup.label;</label + > + <label + id="retentionDescriptionImap" + hidefor="pop3,nntp,none,rss" + class="desc" + control="retention.keepMsg" + >&retentionCleanupImap.label;</label + > + <label + id="retentionDescriptionPop" + hidefor="imap,nntp,none,rss" + class="desc" + control="retention.keepMsg" + >&retentionCleanupPop.label;</label + > + + <radiogroup + hidefor="" + confirmfor="imap,pop3" + id="retention.keepMsg" + class="indent" + oncommand="warnServerRemove(this);" + > + <radio + id="retention.keepAllMsg" + value="1" + accesskey="&retentionKeepAll.accesskey;" + label="&retentionKeepAll.label;" + oncommand="onCheckKeepMsg();" + /> + <hbox flex="1" align="center"> + <radio + id="retention.keepNewMsg" + accesskey="&retentionKeepRecent.accesskey;" + value="3" + label="&retentionKeepRecent.label;" + oncommand="onCheckKeepMsg();" + /> + <html:input + id="retention.keepNewMsgMin" + type="number" + class="size4 input-inline" + min="1" + value="2000" + aria-labelledby="retention.keepNewMsg retention.keepNewMsgMin newMsgLabel" + /> + <label + value="&message.label;" + control="retention.keepNewMsgMin" + id="newMsgLabel" + /> + </hbox> + <hbox flex="1" align="center"> + <radio + id="retention.keepOldMsg" + accesskey="&retentionKeepMsg.accesskey;" + value="2" + label="&retentionKeepMsg.label;" + oncommand="onCheckKeepMsg();" + /> + <html:input + id="retention.keepOldMsgMin" + type="number" + class="size4 input-inline" + min="1" + value="30" + aria-labelledby="retention.keepOldMsg retention.keepOldMsgMin oldMsgLabel" + /> + <label + value="&daysOld.label;" + control="retention.keepOldMsgMin" + id="oldMsgLabel" + /> + </hbox> + </radiogroup> + + <hbox align="center" class="indent"> + <checkbox + id="retention.applyToFlagged" + label="&retentionApplyToFlagged.label;" + hidefor="" + accesskey="&retentionApplyToFlagged.accesskey;" + checked="true" + /> + </hbox> + <hbox align="center" class="indent" hidefor="pop3,imap,none,rss"> + <checkbox + id="nntp.removeBody" + accesskey="&nntpRemoveMsgBody.accesskey;" + label="&nntpRemoveMsgBody.label;" + oncommand="onCheckItem('nntp.removeBodyMin', [this.id]);" + /> + <html:input + id="nntp.removeBodyMin" + type="number" + class="size2 input-inline" + min="1" + value="30" + aria-labelledby="nntp.removeBody nntp.removeBodyMin daysOldMsg" + /> + <label + value="&daysOld.label;" + control="nntp.removeBodyMin" + id="daysOldMsg" + /> + </hbox> + </vbox> + </html:fieldset> + </html:div> + </vbox> + </html:body> +</html> diff --git a/comm/mailnews/base/prefs/content/am-prefs.js b/comm/mailnews/base/prefs/content/am-prefs.js new file mode 100644 index 0000000000..cff09a5d93 --- /dev/null +++ b/comm/mailnews/base/prefs/content/am-prefs.js @@ -0,0 +1,142 @@ +/* -*- Mode: C++; tab-width: 4; 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/. */ + +/* functions for disabling front end elements when the appropriate + back-end preference is locked. */ + +/** + * Prefs in MailNews require dynamic portions to indicate + * which of multiple servers or identities. This function + * takes a string and a xul element. + * + * @param {string} aStr - The string is a prefstring with a token %tokenname%. + * @param {Element} aElement - The element has an attribute of name |tokenname| + * whose value is substituted into the string and returned by the function. + * Any tokens which do not have associated attribute value are not + * substituted, and left in the string as-is. + */ +function substPrefTokens(aStr, aElement) { + let tokenpat = /%(\w+)%/; + let token; + let newprefstr = ""; + + let prefPartsArray = aStr.split("."); + /* here's a little loop that goes through + each part of the string separated by a dot, and + if any parts are of the form %string%, it will replace + them with the value of the attribute of that name from + the xul object */ + for (let i = 0; i < prefPartsArray.length; i++) { + token = prefPartsArray[i].match(tokenpat); + if (token) { + // We've got a %% match. + if (token[1]) { + if (aElement[token[1]]) { + newprefstr += aElement[token[1]] + "."; // here's where we get the info + } else { + // All we got was this stinkin %. + newprefstr += prefPartsArray[i] + "."; + } + } + } else { + // token is falsy. + newprefstr += prefPartsArray[i] + "."; + } + } + newprefstr = newprefstr.slice(0, -1); // remove the last char, a dot + if (newprefstr.length <= 0) { + newprefstr = null; + } + + return newprefstr; +} + +/** + * A simple function to check if a pref in an element is locked. + * + * @param {Element} aElement - An element with the pref related attributes + * (pref, preftype, prefstring) + * @returns {boolean} whether the prefstring specified in that element is + * locked (true/false). If it does not have a valid prefstring, a false is + * returned. + */ +function getAccountValueIsLocked(aElement) { + let prefstring = aElement.getAttribute("prefstring"); + if (prefstring) { + let prefstr = substPrefTokens(prefstring, aElement); + // see if the prefstring is locked + if (prefstr) { + return Services.prefs.prefIsLocked(prefstr); + } + } + return false; +} + +/** + * Enables/disables element (slave) according to the checked state + * of another elements (masters). + * + * @param {string} aChangeElementId - Slave element which should be enabled + * if all the checkElementIDs are checked. Otherwise it gets disabled. + * @param {string[]} aCheckElementIds - An array of IDs of the master elements. + * + * @see bug 728681 for the pattern on how this is used. + */ +function onCheckItem(aChangeElementId, aCheckElementIds) { + let elementToControl = document.getElementById(aChangeElementId); + let disabled = false; + + for (let notifyId of aCheckElementIds) { + let notifyElement = document.getElementById(notifyId); + let notifyElementState = null; + if ("checked" in notifyElement) { + notifyElementState = notifyElement.checked; + } else if ("selected" in notifyElement) { + notifyElementState = notifyElement.selected; + } else { + console.error("Unknown type of control element: " + notifyElement.id); + } + + if (!notifyElementState) { + disabled = true; + break; + } + } + + if (!disabled && getAccountValueIsLocked(elementToControl)) { + disabled = true; + } + + elementToControl.disabled = disabled; +} + +/** + * Hides and shows elements relevant for the given server type. + * + * @param {string} serverType - Name of the server type for which to show/hide elements. + */ +function hideShowControls(serverType) { + let controls = document.querySelectorAll("[hidefor]"); + for (let controlNo = 0; controlNo < controls.length; controlNo++) { + let control = controls[controlNo]; + let hideFor = control.getAttribute("hidefor"); + + // Hide unsupported server types using hideFor="servertype1,servertype2". + let hide = false; + let hideForTokens = hideFor.split(","); + for (let tokenNo = 0; tokenNo < hideForTokens.length; tokenNo++) { + if (hideForTokens[tokenNo] == serverType) { + hide = true; + break; + } + } + + if (hide) { + control.setAttribute("hidden", "true"); + } else { + control.removeAttribute("hidden"); + } + } +} diff --git a/comm/mailnews/base/prefs/content/am-server-advanced.js b/comm/mailnews/base/prefs/content/am-server-advanced.js new file mode 100644 index 0000000000..a37a8eced0 --- /dev/null +++ b/comm/mailnews/base/prefs/content/am-server-advanced.js @@ -0,0 +1,151 @@ +/* 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 { MailServices } = ChromeUtils.import( + "resource:///modules/MailServices.jsm" +); + +window.addEventListener("DOMContentLoaded", onLoad); +document.addEventListener("dialogaccept", onOk); + +// pull stuff out of window.arguments +var gServerSettings = window.arguments[0]; + +var gFirstDeferredAccount; +// initialize the controls with the "gServerSettings" argument + +var gControls; +function getControls() { + if (!gControls) { + gControls = document.getElementsByAttribute("amsa_persist", "true"); + } + return gControls; +} + +function getLocalFoldersAccount() { + return MailServices.accounts.FindAccountForServer( + MailServices.accounts.localFoldersServer + ); +} + +function onLoad() { + var prettyName = gServerSettings.serverPrettyName; + + if (prettyName) { + document.getElementById("serverPrettyName").value = document + .getElementById("bundle_prefs") + .getFormattedString("forAccount", [prettyName]); + } + + if (gServerSettings.serverType == "imap") { + document.getElementById("pop3Panel").hidden = true; + } else if (gServerSettings.serverType == "pop3") { + document.getElementById("imapPanel").hidden = true; + let radioGroup = document.getElementById("folderStorage"); + + gFirstDeferredAccount = gServerSettings.deferredToAccount; + let folderPopup = document.getElementById("deferredServerPopup"); + + // The current account should not be shown in the folder picker + // of the "other account" option. + folderPopup._teardown(); + folderPopup.setAttribute( + "excludeServers", + gServerSettings.account.incomingServer.key + ); + folderPopup._ensureInitialized(); + + if (gFirstDeferredAccount.length) { + // The current account is deferred. + let account = MailServices.accounts.getAccount(gFirstDeferredAccount); + radioGroup.value = "otherAccount"; + folderPopup.selectFolder(account.incomingServer.rootFolder); + } else { + // Current account is not deferred. + radioGroup.value = "currentAccount"; + // If there are no suitable accounts to defer to, then the menulist is + // disabled by the picker with an appropriate message. + folderPopup.selectFolder(); + if (gServerSettings.account.incomingServer.isDeferredTo) { + // Some other account already defers to this account + // therefore this one can't be deferred further. + radioGroup.disabled = true; + } + } + + let picker = document.getElementById("deferredServerFolderPicker"); + picker.disabled = radioGroup.selectedIndex != 1; + } + + var controls = getControls(); + + for (let i = 0; i < controls.length; i++) { + var slot = controls[i].id; + if (slot in gServerSettings) { + if (controls[i].localName == "checkbox") { + controls[i].checked = gServerSettings[slot]; + } else { + controls[i].value = gServerSettings[slot]; + } + } + } +} + +function onOk(event) { + // Handle account deferral settings for POP3 accounts. + if (gServerSettings.serverType == "pop3") { + var radioGroup = document.getElementById("folderStorage"); + var gPrefsBundle = document.getElementById("bundle_prefs"); + let picker = document.getElementById("deferredServerFolderPicker"); + + // This account wasn't previously deferred, but is now deferred. + if (radioGroup.value != "currentAccount" && !gFirstDeferredAccount.length) { + // If the user hasn't selected a folder, keep the default. + if (!picker.selectedItem) { + return; + } + + var confirmDeferAccount = gPrefsBundle.getString( + "confirmDeferAccountWarning" + ); + + var confirmTitle = gPrefsBundle.getString("confirmDeferAccountTitle"); + + if (!Services.prompt.confirm(window, confirmTitle, confirmDeferAccount)) { + event.preventDefault(); + return; + } + } + switch (radioGroup.value) { + case "currentAccount": + gServerSettings.deferredToAccount = ""; + break; + case "otherAccount": + let server = picker.selectedItem._folder.server; + let account = MailServices.accounts.FindAccountForServer(server); + gServerSettings.deferredToAccount = account.key; + break; + } + } + + // Save the controls back to the "gServerSettings" array. + var controls = getControls(); + for (let i = 0; i < controls.length; i++) { + var slot = controls[i].id; + if (slot in gServerSettings) { + if (controls[i].localName == "checkbox") { + gServerSettings[slot] = controls[i].checked; + } else { + gServerSettings[slot] = controls[i].value; + } + } + } +} + +// Set radio element choices and picker states +function updateInboxAccount(enablePicker) { + document.getElementById("deferredServerFolderPicker").disabled = + !enablePicker; + document.getElementById("deferGetNewMail").disabled = !enablePicker; +} diff --git a/comm/mailnews/base/prefs/content/am-server-advanced.xhtml b/comm/mailnews/base/prefs/content/am-server-advanced.xhtml new file mode 100644 index 0000000000..b8349af38f --- /dev/null +++ b/comm/mailnews/base/prefs/content/am-server-advanced.xhtml @@ -0,0 +1,222 @@ +<?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/accountManage.css" type="text/css"?> +<?xml-stylesheet href="chrome://messenger/skin/icons.css" type="text/css"?> +<?xml-stylesheet href="chrome://messenger/skin/folderMenus.css" type="text/css"?> + +<!DOCTYPE html SYSTEM "chrome://messenger/locale/am-server-advanced.dtd"> + +<html + xmlns="http://www.w3.org/1999/xhtml" + xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + xmlns:html="http://www.w3.org/1999/xhtml" + scrolling="false" +> + <head> + <title>&serverAdvanced.label;</title> + <script + defer="defer" + src="chrome://messenger/content/globalOverlay.js" + ></script> + <script + defer="defer" + src="chrome://global/content/editMenuOverlay.js" + ></script> + <script + defer="defer" + src="chrome://messenger/content/am-server-advanced.js" + ></script> + </head> + <html:body + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + > + <dialog buttons="accept,cancel" style="width: 100vw; height: 100vh"> + <stringbundle + id="bundle_prefs" + src="chrome://messenger/locale/prefs.properties" + /> + + <label id="serverPrettyName" /> + + <separator class="thin" /> + + <!-- IMAP Panel --> + <vbox id="imapPanel"> + <hbox align="center"> + <label + id="serverDirectoryLabel" + value="&serverDirectory.label;" + accesskey="&serverDirectory.accesskey;" + control="serverDirectory" + /> + <hbox class="input-container"> + <html:input + id="serverDirectory" + type="text" + class="input-inline" + aria-labelledby="serverDirectoryLabel" + amsa_persist="true" + /> + </hbox> + </hbox> + + <checkbox + id="usingSubscription" + amsa_persist="true" + label="&usingSubscription.label;" + accesskey="&usingSubscription.accesskey;" + /> + + <checkbox + id="dualUseFolders" + amsa_persist="true" + label="&dualUseFolders.label;" + accesskey="&dualUseFolders.accesskey;" + /> + + <separator class="groove" /> + <hbox align="center"> + <label + id="maximumConnectionsNumberLabel" + control="maximumConnectionsNumber" + value="&maximumConnectionsNumber.label;" + accesskey="&maximumConnectionsNumber.accesskey;" + /> + <html:input + id="maximumConnectionsNumber" + type="number" + class="size3 input-inline" + min="1" + max="1000" + amsa_persist="true" + aria-labelledby="maximumConnectionsNumberLabel" + /> + </hbox> + + <separator class="groove" /> + <description>&namespaceDesc.label;</description> + <hbox class="indent"> + <vbox> + <hbox flex="1" align="center"> + <label + id="personalNamespaceLabel" + control="personalNamespace" + value="&personalNamespace.label;" + accesskey="&personalNamespace.accesskey;" + /> + </hbox> + <hbox flex="1" align="center"> + <label + id="publicNamespaceLabel" + control="publicNamespace" + value="&publicNamespace.label;" + accesskey="&publicNamespace.accesskey;" + /> + </hbox> + <hbox flex="1" align="center"> + <label + id="otherUsersNamespaceLabel" + control="otherUsersNamespace" + value="&otherUsersNamespace.label;" + accesskey="&otherUsersNamespace.accesskey;" + /> + </hbox> + </vbox> + <vbox> + <html:input + id="personalNamespace" + type="text" + class="input-inline" + aria-labelledby="personalNamespaceLabel" + amsa_persist="true" + /> + <html:input + id="publicNamespace" + type="text" + class="input-inline" + aria-labelledby="publicNamespaceLabel" + amsa_persist="true" + /> + <html:input + id="otherUsersNamespace" + type="text" + class="input-inline" + aria-labelledby="otherUsersNamespaceLabel" + amsa_persist="true" + /> + </vbox> + </hbox> + <hbox class="indent"> + <checkbox + id="overrideNamespaces" + amsa_persist="true" + label="&overrideNamespaces.label;" + accesskey="&overrideNamespaces.accesskey;" + /> + </hbox> + </vbox> + + <!-- POP3 Panel --> + <vbox id="pop3Panel"> + <label flex="1" control="folderStorage" + >&pop3DeferringDesc.label;</label + > + <hbox align="center"> + <radiogroup + id="folderStorage" + flex="1" + orient="horizontal" + amsa_persist="true" + onselect="updateInboxAccount(this.selectedIndex == 1);" + > + <vbox flex="1"> + <hbox> + <radio + id="deferToCurrentAccount" + value="currentAccount" + label="&accountInbox.label;" + accesskey="&accountInbox.accesskey;" + /> + </hbox> + <vbox flex="1"> + <hbox> + <radio + id="deferToOtherAccount" + value="otherAccount" + label="&deferToServer.label;" + accesskey="&deferToServer.accesskey;" + > + </radio> + <menulist + id="deferredServerFolderPicker" + class="folderMenuItem" + flex="1" + aria-labelledby="deferToServer" + > + <menupopup + is="folder-menupopup" + id="deferredServerPopup" + expandFolders="false" + mode="deferred" + oncommand="this.selectFolder(event.target._folder);" + /> + </menulist> + </hbox> + <checkbox + amsa_persist="true" + id="deferGetNewMail" + label="&deferGetNewMail.label;" + class="indent" + accesskey="&deferGetNewMail.accesskey;" + /> + </vbox> + </vbox> + </radiogroup> + </hbox> + </vbox> + </dialog> + </html:body> +</html> diff --git a/comm/mailnews/base/prefs/content/am-server.js b/comm/mailnews/base/prefs/content/am-server.js new file mode 100644 index 0000000000..f73535b35c --- /dev/null +++ b/comm/mailnews/base/prefs/content/am-server.js @@ -0,0 +1,615 @@ +/* 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/. */ + +/* import-globals-from am-prefs.js */ +/* import-globals-from amUtils.js */ + +var { MailUtils } = ChromeUtils.import("resource:///modules/MailUtils.jsm"); + +var gServer; +var gOriginalStoreType; + +/** + * Called when the store type menu is clicked. + * + * @param {object} aStoreTypeElement - store type menu list element. + */ +function clickStoreTypeMenu(aStoreTypeElement) { + if (aStoreTypeElement.value == gOriginalStoreType) { + return; + } + + // Response from migration dialog modal. If the conversion is complete + // 'response.newRootFolder' will hold the path to the new account root folder, + // otherwise 'response.newRootFolder' will be null. + let response = { newRootFolder: null }; + // Send 'response' as an argument to converterDialog.xhtml. + window.browsingContext.topChromeWindow.openDialog( + "converterDialog.xhtml", + "mailnews:mailstoreconverter", + "modal,centerscreen,resizable=no,width=700,height=130", + gServer, + aStoreTypeElement.value, + response + ); + changeStoreType(response); +} + +/** + * Revert store type to the original store type if converter modal closes + * before migration is complete, otherwise change original store type to + * currently selected store type. + * + * @param {object} aResponse - response from migration dialog modal. + */ +function changeStoreType(aResponse) { + if (aResponse.newRootFolder) { + // The conversion is complete. + // Set local path to the new account root folder which is present + // in 'aResponse.newRootFolder'. + if (gServer.type == "nntp") { + let newRootFolder = aResponse.newRootFolder; + let lastSlash = newRootFolder.lastIndexOf("/"); + let newsrc = + newRootFolder.slice(0, lastSlash) + + "/newsrc-" + + newRootFolder.slice(lastSlash + 1); + document.getElementById("nntp.newsrcFilePath").value = newsrc; + } + + document.getElementById("server.localPath").value = aResponse.newRootFolder; + gOriginalStoreType = document.getElementById( + "server.storeTypeMenulist" + ).value; + MailUtils.restartApplication(); + } else { + // The conversion failed or was cancelled. + // Restore selected item to what was selected before conversion. + document.getElementById("server.storeTypeMenulist").value = + gOriginalStoreType; + } +} + +function onSave() { + let storeContractID = document.getElementById("server.storeTypeMenulist") + .selectedItem.value; + document + .getElementById("server.storeContractID") + .setAttribute("value", storeContractID); +} + +function onInit(aPageId, aServerId) { + initServerType(); + + onCheckItem("server.biffMinutes", ["server.doBiff"]); + onCheckItem("nntp.maxArticles", ["nntp.notifyOn"]); + setupMailOnServerUI(); + setupFixedUI(); + let serverType = document.getElementById("server.type").getAttribute("value"); + if (serverType == "imap") { + setupImapDeleteUI(aServerId); + } + + // OAuth2 are only supported on IMAP and POP. + document.getElementById("authMethod-oauth2").hidden = + serverType != "imap" && serverType != "pop3"; + // TLS Cert (External) only supported on IMAP. + document.getElementById("authMethod-external").hidden = serverType != "imap"; + + // "STARTTLS, if available" is vulnerable to MITM attacks so we shouldn't + // allow users to choose it anymore. Hide the option unless the user already + // has it set. + hideUnlessSelected(document.getElementById("connectionSecurityType-1")); + + // UI for account store type. + let storeTypeElement = document.getElementById("server.storeTypeMenulist"); + // set the menuitem to match the account + let currentStoreID = document + .getElementById("server.storeContractID") + .getAttribute("value"); + let targetItem = storeTypeElement.getElementsByAttribute( + "value", + currentStoreID + ); + storeTypeElement.selectedItem = targetItem[0]; + // Disable store type change if store has not been used yet. + storeTypeElement.setAttribute( + "disabled", + gServer.getBoolValue("canChangeStoreType") + ? "false" + : !Services.prefs.getBoolPref("mail.store_conversion_enabled") + ); + // Initialise 'gOriginalStoreType' to the item that was originally selected. + gOriginalStoreType = storeTypeElement.value; +} + +function onPreInit(account, accountValues) { + var type = parent.getAccountValue( + account, + accountValues, + "server", + "type", + null, + false + ); + hideShowControls(type); + + gServer = account.incomingServer; +} + +function initServerType() { + var serverType = document.getElementById("server.type").getAttribute("value"); + var propertyName = "serverType-" + serverType; + + var messengerBundle = document.getElementById("bundle_messenger"); + var verboseName; + try { + verboseName = messengerBundle.getString(propertyName); + } catch (e) { + // Addon-provided server types do not have a description string, + // then display the raw server type. + verboseName = serverType; + } + setDivText("servertypeVerbose", verboseName); + + secureSelect(true); + + setLabelFromStringBundle("authMethod-no", "authNo"); + setLabelFromStringBundle("authMethod-old", "authOld"); + setLabelFromStringBundle("authMethod-kerberos", "authKerberos"); + setLabelFromStringBundle("authMethod-external", "authExternal"); + setLabelFromStringBundle("authMethod-ntlm", "authNTLM"); + setLabelFromStringBundle("authMethod-oauth2", "authOAuth2"); + setLabelFromStringBundle("authMethod-anysecure", "authAnySecure"); + setLabelFromStringBundle("authMethod-any", "authAny"); + setLabelFromStringBundle( + "authMethod-password-encrypted", + "authPasswordEncrypted" + ); + // authMethod-password-cleartext already set in secureSelect() + + // Hide deprecated/hidden auth options, unless selected + hideUnlessSelected(document.getElementById("authMethod-no")); + hideUnlessSelected(document.getElementById("authMethod-old")); + hideUnlessSelected(document.getElementById("authMethod-anysecure")); + hideUnlessSelected(document.getElementById("authMethod-any")); +} + +function hideUnlessSelected(element) { + element.hidden = !element.selected; +} + +function setLabelFromStringBundle(elementID, stringName) { + document.getElementById(elementID).label = document + .getElementById("bundle_messenger") + .getString(stringName); +} + +function setDivText(divname, value) { + var div = document.getElementById(divname); + if (!div) { + return; + } + div.setAttribute("value", value); +} + +function onAdvanced() { + // Store the server type and, if an IMAP or POP3 server, + // the settings needed for the IMAP/POP3 tab into the array + var serverSettings = {}; + var serverType = document.getElementById("server.type").getAttribute("value"); + serverSettings.serverType = serverType; + + serverSettings.serverPrettyName = gServer.prettyName; + serverSettings.account = top.getCurrentAccount(); + + if (serverType == "imap") { + serverSettings.dualUseFolders = document.getElementById( + "imap.dualUseFolders" + ).checked; + serverSettings.usingSubscription = document.getElementById( + "imap.usingSubscription" + ).checked; + serverSettings.maximumConnectionsNumber = document + .getElementById("imap.maximumConnectionsNumber") + .getAttribute("value"); + serverSettings.personalNamespace = document + .getElementById("imap.personalNamespace") + .getAttribute("value"); + serverSettings.publicNamespace = document + .getElementById("imap.publicNamespace") + .getAttribute("value"); + serverSettings.serverDirectory = document + .getElementById("imap.serverDirectory") + .getAttribute("value"); + serverSettings.otherUsersNamespace = document + .getElementById("imap.otherUsersNamespace") + .getAttribute("value"); + serverSettings.overrideNamespaces = document.getElementById( + "imap.overrideNamespaces" + ).checked; + } else if (serverType == "pop3") { + serverSettings.deferGetNewMail = document.getElementById( + "pop3.deferGetNewMail" + ).checked; + serverSettings.deferredToAccount = document + .getElementById("pop3.deferredToAccount") + .getAttribute("value"); + } + + let onCloseAdvanced = function () { + if (serverType == "imap") { + document.getElementById("imap.dualUseFolders").checked = + serverSettings.dualUseFolders; + document.getElementById("imap.usingSubscription").checked = + serverSettings.usingSubscription; + document + .getElementById("imap.maximumConnectionsNumber") + .setAttribute("value", serverSettings.maximumConnectionsNumber); + document + .getElementById("imap.personalNamespace") + .setAttribute("value", serverSettings.personalNamespace); + document + .getElementById("imap.publicNamespace") + .setAttribute("value", serverSettings.publicNamespace); + document + .getElementById("imap.serverDirectory") + .setAttribute("value", serverSettings.serverDirectory); + document + .getElementById("imap.otherUsersNamespace") + .setAttribute("value", serverSettings.otherUsersNamespace); + document.getElementById("imap.overrideNamespaces").checked = + serverSettings.overrideNamespaces; + } else if (serverType == "pop3") { + document.getElementById("pop3.deferGetNewMail").checked = + serverSettings.deferGetNewMail; + document + .getElementById("pop3.deferredToAccount") + .setAttribute("value", serverSettings.deferredToAccount); + let pop3Server = gServer.QueryInterface(Ci.nsIPop3IncomingServer); + // we're explicitly setting this so we'll go through the SetDeferredToAccount method + pop3Server.deferredToAccount = serverSettings.deferredToAccount; + // Setting the server to be deferred causes a rebuild of the account tree, + // losing the current selection. Reselect the current server again as it + // didn't really disappear. + parent.selectServer( + parent.getCurrentAccount().incomingServer, + parent.currentPageId + ); + + // Iterate over all accounts to see if any of their junk targets are now + // invalid (pointed to the account that is now deferred). + // If any such target is found it is reset to a new safe folder + // (the deferred to account or Local Folders). If junk was really moved + // to that folder (moveOnSpam = true) then moving junk is disabled + // (so that the user notices it and checks the settings). + // This is the same sanitization as in am-junk.js, just applied to all POP accounts. + let deferredURI = + serverSettings.deferredToAccount && + MailServices.accounts.getAccount(serverSettings.deferredToAccount) + .incomingServer.serverURI; + + for (let account of MailServices.accounts.accounts) { + let accountValues = parent.getValueArrayFor(account); + let type = parent.getAccountValue( + account, + accountValues, + "server", + "type", + null, + false + ); + // Try to keep this list of account types not having Junk settings + // synchronized with the list in AccountManager.js. + if (type != "nntp" && type != "rss" && type != "im") { + let spamActionTargetAccount = parent.getAccountValue( + account, + accountValues, + "server", + "spamActionTargetAccount", + "string", + true + ); + let spamActionTargetFolder = parent.getAccountValue( + account, + accountValues, + "server", + "spamActionTargetFolder", + "wstring", + true + ); + let moveOnSpam = parent.getAccountValue( + account, + accountValues, + "server", + "moveOnSpam", + "bool", + true + ); + + // Check if there are any invalid junk targets and fix them. + [spamActionTargetAccount, spamActionTargetFolder, moveOnSpam] = + sanitizeJunkTargets( + spamActionTargetAccount, + spamActionTargetFolder, + deferredURI || account.incomingServer.serverURI, + parent.getAccountValue( + account, + accountValues, + "server", + "moveTargetMode", + "int", + true + ), + account.incomingServer.spamSettings, + moveOnSpam + ); + + parent.setAccountValue( + accountValues, + "server", + "moveOnSpam", + moveOnSpam + ); + parent.setAccountValue( + accountValues, + "server", + "spamActionTargetAccount", + spamActionTargetAccount + ); + parent.setAccountValue( + accountValues, + "server", + "spamActionTargetFolder", + spamActionTargetFolder + ); + } + } + } + document.dispatchEvent(new CustomEvent("prefchange")); + }; + + parent.gSubDialog.open( + "chrome://messenger/content/am-server-advanced.xhtml", + { closingCallback: onCloseAdvanced }, + serverSettings + ); +} + +function secureSelect(aLoading) { + var socketType = document.getElementById("server.socketType").value; + var defaultPort = gServer.protocolInfo.getDefaultServerPort(false); + var defaultPortSecure = gServer.protocolInfo.getDefaultServerPort(true); + var port = document.getElementById("server.port"); + var portDefault = document.getElementById("defaultPort"); + var prevDefaultPort = portDefault.value; + + if (socketType == Ci.nsMsgSocketType.SSL) { + portDefault.value = defaultPortSecure; + if ( + port.value == "" || + (!aLoading && + port.value == defaultPort && + prevDefaultPort != portDefault.value) + ) { + port.value = defaultPortSecure; + } + } else { + portDefault.value = defaultPort; + if ( + port.value == "" || + (!aLoading && + port.value == defaultPortSecure && + prevDefaultPort != portDefault.value) + ) { + port.value = defaultPort; + } + } + + // switch "insecure password" label + setLabelFromStringBundle( + "authMethod-password-cleartext", + socketType == Ci.nsMsgSocketType.SSL || + socketType == Ci.nsMsgSocketType.alwaysSTARTTLS + ? "authPasswordCleartextViaSSL" + : "authPasswordCleartextInsecurely" + ); +} + +function setupMailOnServerUI() { + onCheckItem("pop3.deleteMailLeftOnServer", ["pop3.leaveMessagesOnServer"]); + setupAgeMsgOnServerUI(); +} + +function setupAgeMsgOnServerUI() { + const kLeaveMsgsId = "pop3.leaveMessagesOnServer"; + const kDeleteByAgeId = "pop3.deleteByAgeFromServer"; + onCheckItem(kDeleteByAgeId, [kLeaveMsgsId]); + onCheckItem("daysEnd", [kLeaveMsgsId]); + onCheckItem("pop3.numDaysToLeaveOnServer", [kLeaveMsgsId, kDeleteByAgeId]); +} + +function setupFixedUI() { + var controls = [ + document.getElementById("fixedServerName"), + document.getElementById("fixedUserName"), + document.getElementById("fixedServerPort"), + ]; + + var len = controls.length; + for (let i = 0; i < len; i++) { + var fixedElement = controls[i]; + var otherElement = document.getElementById( + fixedElement.getAttribute("use") + ); + + fixedElement.setAttribute("collapsed", "true"); + otherElement.removeAttribute("collapsed"); + } +} + +function BrowseForNewsrc() { + const nsIFilePicker = Ci.nsIFilePicker; + const nsIFile = Ci.nsIFile; + + var newsrcTextBox = document.getElementById("nntp.newsrcFilePath"); + var fp = Cc["@mozilla.org/filepicker;1"].createInstance(nsIFilePicker); + fp.init( + window, + document.getElementById("browseForNewsrc").getAttribute("filepickertitle"), + nsIFilePicker.modeSave + ); + + var currentNewsrcFile; + try { + currentNewsrcFile = Cc["@mozilla.org/file/local;1"].createInstance(nsIFile); + currentNewsrcFile.initWithPath(newsrcTextBox.value); + } catch (e) { + dump("Failed to create nsIFile instance for the current newsrc file.\n"); + } + + if (currentNewsrcFile) { + fp.displayDirectory = currentNewsrcFile.parent; + fp.defaultString = currentNewsrcFile.leafName; + } + + fp.appendFilters(nsIFilePicker.filterAll); + + fp.open(rv => { + if (rv != nsIFilePicker.returnOK || !fp.file) { + return; + } + newsrcTextBox.value = fp.file.path; + newsrcTextBox.dispatchEvent(new CustomEvent("change")); + }); +} + +function setupImapDeleteUI(aServerId) { + // read delete_model preference + let deleteModel = document + .getElementById("imap.deleteModel") + .getAttribute("value"); + selectImapDeleteModel(deleteModel); + + // read trash folder path preference + let trashFolderName = getTrashFolderName(); + + // set folderPicker menulist + let trashPopup = document.getElementById("msgTrashFolderPopup"); + trashPopup._teardown(); + trashPopup._parentFolder = MailUtils.getOrCreateFolder(aServerId); + trashPopup._ensureInitialized(); + + // Escape backslash and double-quote with another backslash before encoding. + let trashEscaped = trashFolderName.replace(/([\\"])/g, "\\$1"); + + // Convert the folder path from JS Unicode to MUTF-7 if necessary. + let imapServer = trashPopup._parentFolder.server.QueryInterface( + Ci.nsIImapIncomingServer + ); + + let trashFolder; + if (imapServer.utf8AcceptEnabled) { + // Trash folder with UTF8=ACCEPT capability in effect. + trashFolder = MailUtils.getOrCreateFolder(aServerId + "/" + trashEscaped); + } else { + // Traditional MUTF-7. + let manager = Cc["@mozilla.org/charset-converter-manager;1"].getService( + Ci.nsICharsetConverterManager + ); + trashFolder = MailUtils.getOrCreateFolder( + aServerId + "/" + manager.unicodeToMutf7(trashEscaped) + ); + } + trashPopup.selectFolder(trashFolder); + trashPopup.parentNode.folder = trashFolder; +} + +function selectImapDeleteModel(choice) { + // set deleteModel to selected mode + document.getElementById("imap.deleteModel").setAttribute("value", choice); + + switch (choice) { + case "0": // markDeleted + // disable folderPicker + document + .getElementById("msgTrashFolderPicker") + .setAttribute("disabled", "true"); + break; + case "1": // moveToTrashFolder + // enable folderPicker + document + .getElementById("msgTrashFolderPicker") + .removeAttribute("disabled"); + break; + case "2": // deleteImmediately + // disable folderPicker + document + .getElementById("msgTrashFolderPicker") + .setAttribute("disabled", "true"); + break; + default: + dump("Error in enabling/disabling server.TrashFolderPicker\n"); + break; + } +} + +// Capture any menulist changes from folderPicker +function folderPickerChange(aEvent) { + let folder = aEvent.target._folder; + // Since we need to deal with localised folder names, we simply use + // the path of the URI like we do in nsImapIncomingServer::DiscoveryDone(). + // Note that the path is returned with a leading slash which we need to remove. + let folderPath = Services.io.newURI(folder.URI).pathQueryRef.substring(1); + let folderPathUnescaped = Services.io.unescapeString( + folderPath, + Ci.nsINetUtil.ESCAPE_URL_PATH + ); + + // Convert the folder path from MUTF-7 or UTF-8 to Unicode. + let imapServer = folder.server.QueryInterface(Ci.nsIImapIncomingServer); + + let trashUnicode; + if (imapServer.utf8AcceptEnabled) { + // UTF8=ACCEPT capability in effect. Unescaping has brought back + // raw UTF-8 bytes, so convert them to JS Unicode. + let typedarray = new Uint8Array(folderPathUnescaped.length); + for (let i = 0; i < folderPathUnescaped.length; i++) { + typedarray[i] = folderPathUnescaped.charCodeAt(i); + } + let utf8Decoder = new TextDecoder("utf-8"); + trashUnicode = utf8Decoder.decode(typedarray); + } else { + // We need to convert that from MUTF-7 to Unicode. + let manager = Cc["@mozilla.org/charset-converter-manager;1"].getService( + Ci.nsICharsetConverterManager + ); + trashUnicode = manager.mutf7ToUnicode(folderPathUnescaped); + } + + // Set the value to be persisted. + document + .getElementById("imap.trashFolderName") + .setAttribute("value", trashUnicode); + + // Update the widget to show/do correct things even for subfolders. + let trashFolderPicker = document.getElementById("msgTrashFolderPicker"); + trashFolderPicker.menupopup.selectFolder(folder); +} + +// Get trash_folder_name from prefs. Despite its name this returns +// a folder path, for example INBOX/Trash. +function getTrashFolderName() { + let trashFolderName = document + .getElementById("imap.trashFolderName") + .getAttribute("value"); + // if the preference hasn't been set, set it to a sane default + if (!trashFolderName) { + trashFolderName = "Trash"; // XXX Is this a useful default? + document + .getElementById("imap.trashFolderName") + .setAttribute("value", trashFolderName); + } + return trashFolderName; +} diff --git a/comm/mailnews/base/prefs/content/am-server.xhtml b/comm/mailnews/base/prefs/content/am-server.xhtml new file mode 100644 index 0000000000..b41e93e290 --- /dev/null +++ b/comm/mailnews/base/prefs/content/am-server.xhtml @@ -0,0 +1,674 @@ +<?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 https://mozilla.org/MPL/2.0/. --> + +<?xml-stylesheet href="chrome://messenger/skin/accountManage.css" type="text/css"?> +<?xml-stylesheet href="chrome://messenger/skin/icons.css" type="text/css"?> +<?xml-stylesheet href="chrome://messenger/skin/folderMenus.css" type="text/css"?> + +<!DOCTYPE html [ <!ENTITY % trashDTD SYSTEM "chrome://messenger/locale/am-server-top.dtd"> +%trashDTD; ]> + +<html + xmlns="http://www.w3.org/1999/xhtml" + xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + xmlns:html="http://www.w3.org/1999/xhtml" +> + <head> + <title>&serverSettings.label;</title> + <script + defer="defer" + src="chrome://messenger/content/globalOverlay.js" + ></script> + <script + defer="defer" + src="chrome://global/content/editMenuOverlay.js" + ></script> + <script + defer="defer" + src="chrome://messenger/content/am-server.js" + ></script> + <script defer="defer" src="chrome://messenger/content/am-prefs.js"></script> + <script defer="defer" src="chrome://messenger/content/amUtils.js"></script> + <script + defer="defer" + src="chrome://messenger/content/menulist-charsetpicker.js" + ></script> + <script> + // FIXME: move to script file. + window.addEventListener("load", event => { + parent.onPanelLoaded("am-server.xhtml"); + }); + </script> + </head> + <html:body + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + > + <vbox id="containerBox" flex="1"> + <stringbundle + id="bundle_messenger" + src="chrome://messenger/locale/messenger.properties" + /> + + <label hidden="true" wsm_persist="true" id="server.type" /> + <label + hidden="true" + wsm_persist="true" + preftype="string" + prefattribute="value" + prefstring="mail.server.%serverkey%.storeContractID" + genericattr="true" + id="server.storeContractID" + /> + + <hbox class="dialogheader"> + <label class="dialogheader-title" value="&serverSettings.label;" /> + </hbox> + + <separator class="thin" /> + + <div xmlns="http://www.w3.org/1999/xhtml" id="amServerSetting"> + <div> + <xul:label value="&serverType.label;" /> + </div> + <div> + <xul:label id="servertypeVerbose" /> + </div> + <div style="grid-row: 2"> + <xul:label + value="&serverName.label;" + accesskey="&serverName.accesskey;" + control="server.hostName" + /> + </div> + <div class="input-container" style="grid-row: 2"> + <xul:label + id="fixedServerName" + collapsed="true" + use="server.hostName" + /> + <html:input + id="server.hostName" + type="text" + wsm_persist="true" + size="20" + prefstring="mail.server.%serverkey%.hostname" + class="uri-element input-flex input-inline" + aria-labelledby="fixedServerName" + /> + </div> + <div class="input-container" style="grid-row: 2"> + <xul:label + value="&port.label;" + accesskey="&port.accesskey;" + control="server.port" + /> + <xul:label id="fixedServerPort" collapsed="true" use="server.port" /> + <html:input + id="server.port" + type="number" + class="size3" + min="1" + max="65535" + wsm_persist="true" + preftype="int" + prefstring="mail.server.%serverkey%.port" + /> + <xul:label value="&serverPortDefault.label;" /> + <xul:label id="defaultPort" /> + </div> + <div hidefor="nntp" style="grid-row: 3"> + <xul:label + value="&userName.label;" + accesskey="&userName.accesskey;" + control="server.username" + /> + </div> + <div class="input-container" hidefor="nntp" style="grid-row: 3"> + <xul:label + id="fixedUserName" + collapsed="true" + use="server.username" + /> + <html:input + id="server.username" + type="text" + wsm_persist="true" + size="20" + class="input-flex input-inline" + prefstring="mail.server.%serverkey%.username" + aria-labelledby="fixedUserName" + /> + </div> + </div> + + <separator /> + + <html:div> + <html:fieldset> + <html:legend>&securitySettings.label;</html:legend> + <html:table> + <html:tr> + <html:td> + <label + value="&connectionSecurity.label;" + accesskey="&connectionSecurity.accesskey;" + control="server.socketType" + /> + </html:td> + <html:td> + <menulist + wsm_persist="true" + id="server.socketType" + oncommand="secureSelect();" + style="width: 100%" + > + <menupopup id="server.socketTypePopup"> + <menuitem + value="0" + label="&connectionSecurityType-0.label;" + /> + <menuitem + id="connectionSecurityType-1" + value="1" + label="&connectionSecurityType-1.label;" + disabled="true" + /> + <menuitem + value="2" + label="&connectionSecurityType-2.label;" + hidefor="nntp" + /> + <menuitem + value="3" + label="&connectionSecurityType-3.label;" + /> + </menupopup> + </menulist> + </html:td> + </html:tr> + <html:tr> + <html:td> + <label + value="&authMethod.label;" + accesskey="&authMethod.accesskey;" + control="server.authMethod" + hidefor="nntp" + /> + </html:td> + <html:td> + <menulist + id="server.authMethod" + wsm_persist="true" + preftype="int" + prefstring="mail.server.%serverkey%.authMethod" + hidefor="nntp" + style="width: 100%" + > + <menupopup id="server.authMethodPopup"> + <menuitem id="authMethod-no" value="1" /> + <menuitem id="authMethod-old" value="2" /> + <menuitem id="authMethod-password-cleartext" value="3" /> + <menuitem id="authMethod-password-encrypted" value="4" /> + <menuitem id="authMethod-kerberos" value="5" /> + <menuitem id="authMethod-ntlm" value="6" /> + <menuitem id="authMethod-external" value="7" /> + <menuitem id="authMethod-oauth2" value="10" /> + <menuitem id="authMethod-anysecure" value="8" /> + <menuitem id="authMethod-any" value="9" /> + </menupopup> + </menulist> + </html:td> + </html:tr> + </html:table> + </html:fieldset> + </html:div> + + <html:div> + <html:fieldset> + <html:legend>&serverSettings.label;</html:legend> + <vbox align="start"> + <checkbox + wsm_persist="true" + id="server.loginAtStartUp" + label="&loginAtStartup.label;" + accesskey="&loginAtStartup.accesskey;" + prefattribute="value" + prefstring="mail.server.%serverkey%.login_at_startup" + /> + </vbox> + <hbox align="center"> + <checkbox + wsm_persist="true" + id="server.doBiff" + label="&biffStart.label;" + accesskey="&biffStart.accesskey;" + oncommand="onCheckItem('server.biffMinutes', [this.id]);" + prefattribute="value" + prefstring="mail.server.%serverkey%.check_new_mail" + /> + <html:input + id="server.biffMinutes" + type="number" + class="size3" + min="1" + wsm_persist="true" + aria-labelledby="server.doBiff server.biffMinutes biffEnd" + preftype="int" + prefstring="mail.server.%serverkey%.check_time" + /> + <label + id="biffEnd" + control="server.biffMinutes" + value="&biffEnd.label;" + /> + </hbox> + <vbox align="start" hidefor="pop3,nntp"> + <checkbox + wsm_persist="true" + id="imap.useIdle" + label="&useIdleNotifications.label;" + accesskey="&useIdleNotifications.accesskey;" + prefattribute="value" + prefstring="mail.server.%serverkey%.use_idle" + /> + </vbox> + + <!-- Necessary for POP3 (Bug 480945) --> + <!-- https://bugzilla.mozilla.org/show_bug.cgi?id=480945 --> + <vbox align="start" hidefor="imap,nntp"> + <checkbox + wsm_persist="true" + id="server.downloadOnBiff" + label="&downloadOnBiff.label;" + prefattribute="value" + accesskey="&downloadOnBiff.accesskey;" + prefstring="mail.server.%serverkey%.download_on_biff" + /> + </vbox> + <!-- POP3 --> + <vbox align="start" hidefor="imap,nntp"> + <checkbox + wsm_persist="true" + id="pop3.headersOnly" + label="&headersOnly.label;" + accesskey="&headersOnly.accesskey;" + prefattribute="value" + prefstring="mail.server.%serverkey%.headers_only" + /> + + <checkbox + wsm_persist="true" + id="pop3.leaveMessagesOnServer" + label="&leaveOnServer.label;" + oncommand="setupMailOnServerUI();" + accesskey="&leaveOnServer.accesskey;" + prefattribute="value" + prefstring="mail.server.%serverkey%.leave_on_server" + /> + + <hbox align="center"> + <checkbox + wsm_persist="true" + id="pop3.deleteByAgeFromServer" + class="indent" + label="&deleteByAgeFromServer.label;" + oncommand="setupAgeMsgOnServerUI();" + accesskey="&deleteByAgeFromServer.accesskey;" + prefattribute="value" + prefstring="mail.server.%serverkey%.delete_by_age_from_server" + /> + <html:input + id="pop3.numDaysToLeaveOnServer" + type="number" + class="size3" + min="1" + wsm_persist="true" + aria-labelledby="pop3.deleteByAgeFromServer pop3.numDaysToLeaveOnServer daysEnd" + preftype="int" + prefstring="mail.server.%serverkey%.num_days_to_leave_on_server" + /> + <label + id="daysEnd" + control="pop3.numDaysToLeaveOnServer" + value="&daysEnd.label;" + /> + </hbox> + + <checkbox + wsm_persist="true" + id="pop3.deleteMailLeftOnServer" + class="indent" + label="&deleteOnServer2.label;" + accesskey="&deleteOnServer2.accesskey;" + prefattribute="value" + prefstring="mail.server.%serverkey%.delete_mail_left_on_server" + /> + + <!-- hidden elements for data transfer to and from advanced... dialog --> + <hbox flex="1" hidefor="imap,nntp" hidden="true"> + <checkbox + hidden="true" + wsm_persist="true" + id="pop3.deferGetNewMail" + prefattribute="value" + prefstring="mail.server.%serverkey%.deferGetNewMail" + /> + <label + hidden="true" + wsm_persist="true" + id="pop3.deferredToAccount" + prefattribute="value" + prefstring="mail.server.%serverkey%.deferredToAccount" + /> + </hbox> + </vbox> + <!-- IMAP --> + <label + hidden="true" + wsm_persist="true" + id="imap.trashFolderName" + prefattribute="value" + prefstring="mail.server.%serverkey%.trash_folder_name" + /> + + <separator class="thin" /> + + <hbox align="center" hidefor="pop3,nntp"> + <label + value="&deleteMessagePrefix.label;" + align="start" + control="imap.deleteModel" + /> + </hbox> + <vbox> + <hbox + align="center" + id="imap.deleteModel.box" + hidefor="pop3,nntp" + flex="1" + > + <radiogroup + id="imap.deleteModel" + wsm_persist="true" + oncommand="selectImapDeleteModel(this.value);" + prefstring="mail.server.%serverkey%.delete_model" + > + <hbox class="specialFolderPickerGrid"> + <vbox> + <hbox flex="1" align="center"> + <radio + id="modelMoveToTrash" + value="1" + label="&modelMoveToTrash.label;" + accesskey="&modelMoveToTrash.accesskey;" + /> + <menulist + id="msgTrashFolderPicker" + class="folderMenuItem" + style="max-width: 300px" + flex="1" + crop="center" + aria-labelledby="modelMoveToTrash" + displayformat="verbose" + > + <menupopup + is="folder-menupopup" + id="msgTrashFolderPopup" + mode="filing" + showFileHereLabel="true" + oncommand="folderPickerChange(event);" + /> + </menulist> + </hbox> + <hbox flex="1" align="center"> + <radio + id="modelMarkDeleted" + value="0" + label="&modelMarkDeleted.label;" + accesskey="&modelMarkDeleted.accesskey;" + /> + </hbox> + <hbox flex="1" align="center"> + <radio + id="modelDeleteImmediately" + value="2" + label="&modelDeleteImmediately.label;" + accesskey="&modelDeleteImmediately.accesskey;" + /> + </hbox> + </vbox> + </hbox> + </radiogroup> + </hbox> + <hbox pack="end"> + <!-- This button should have identical attributes to the + server.popAdvancedButton except the hidefor attribute. --> + <button + label="&advancedButton.label;" + accesskey="&advancedButton.accesskey;" + oncommand="onAdvanced();" + wsm_persist="true" + id="server.imapAdvancedButton" + prefstring="mail.server.%serverkey%.advanced.disable" + hidefor="pop3,nntp" + /> + </hbox> + </vbox> + + <!-- Advanced IMAP settings --> + <hbox flex="1" hidefor="pop3,nntp" hidden="true"> + <checkbox + hidden="true" + wsm_persist="true" + id="imap.dualUseFolders" + prefattribute="value" + prefstring="mail.server.%serverkey%.dual_use_folders" + /> + <checkbox + hidden="true" + wsm_persist="true" + id="imap.usingSubscription" + prefattribute="value" + prefstring="mail.server.%serverkey%.using_subscription" + /> + <label + hidden="true" + wsm_persist="true" + id="imap.maximumConnectionsNumber" + /> + <label + hidden="true" + wsm_persist="true" + id="imap.personalNamespace" + /> + <label hidden="true" wsm_persist="true" id="imap.publicNamespace" /> + <label + hidden="true" + wsm_persist="true" + id="imap.otherUsersNamespace" + /> + <label hidden="true" wsm_persist="true" id="imap.serverDirectory" /> + <checkbox + hidden="true" + wsm_persist="true" + id="imap.overrideNamespaces" + prefattribute="value" + prefstring="mail.server.%serverkey%.override_namespaces" + /> + </hbox> + + <!-- NNTP --> + <hbox hidefor="pop3,imap" align="center"> + <checkbox + id="nntp.notifyOn" + wsm_persist="true" + label="&maxMessagesStart.label;" + accesskey="&maxMessagesStart.accesskey;" + oncommand="onCheckItem('nntp.maxArticles', [this.id]);" + prefattribute="value" + prefstring="mail.server.%serverkey%.notify.on" + /> + <html:input + id="nntp.maxArticles" + type="number" + class="size4" + min="1" + wsm_persist="true" + aria-labelledby="nntp.notifyOn nntp.maxArticles maxMessagesEnd" + preftype="int" + prefstring="mail.server.%serverkey%.max_articles" + /> + <label + control="nntp.maxArticles" + value="&maxMessagesEnd.label;" + id="maxMessagesEnd" + /> + </hbox> + <checkbox + hidefor="pop3,imap" + wsm_persist="true" + id="nntp.pushAuth" + label="&alwaysAuthenticate.label;" + accesskey="&alwaysAuthenticate.accesskey;" + prefattribute="value" + prefstring="mail.server.%serverkey%.always_authenticate" + /> + </html:fieldset> + </html:div> + + <html:div> + <html:fieldset> + <html:legend>&messageStorage.label;</html:legend> + + <hbox align="end"> + <vbox align="start" flex="1" id="exitHandlingBox="> + <checkbox + hidefor="pop3,nntp" + wsm_persist="true" + id="imap.cleanupInboxOnExit" + label="&expungeOnExit.label;" + accesskey="&expungeOnExit.accesskey;" + prefattribute="value" + prefstring="mail.server.%serverkey%.cleanup_inbox_on_exit" + /> + <checkbox + hidefor="nntp" + wsm_persist="true" + id="server.emptyTrashOnExit" + label="&emptyTrashOnExit.label;" + accesskey="&emptyTrashOnExit.accesskey;" + prefattribute="value" + prefstring="mail.server.%serverkey%.empty_trash_on_exit" + /> + </vbox> + <button + label="&advancedButton.label;" + accesskey="&advancedButton.accesskey;" + oncommand="onAdvanced();" + wsm_persist="true" + id="server.popAdvancedButton" + prefstring="mail.server.%serverkey%.advanced.disable" + hidefor="imap,nntp" + /> + </hbox> + <hbox align="center"> + <label + value="&storeType.label;" + accesskey="&storeType.accesskey;" + control="server.storeTypeMenulist" + /> + <menulist + id="server.storeTypeMenulist" + oncommand="clickStoreTypeMenu(this);" + > + <menupopup id="server.storeTypeMenupopup"> + <menuitem + id="server.mboxStore" + value="@mozilla.org/msgstore/berkeleystore;1" + label="&mboxStore2.label;" + /> + <menuitem + id="server.maildirStore" + value="@mozilla.org/msgstore/maildirstore;1" + label="&maildirStore.label;" + /> + </menupopup> + </menulist> + </hbox> + + <hbox align="center" hidefor="imap,pop3"> + <label + id="nntp.newsrcFilePath.label" + value="&newsrcFilePath1.label;" + control="nntp.newsrcFilePath" + /> + <hbox class="input-container" flex="1"> + <html:input + id="nntp.newsrcFilePath" + type="text" + readonly="readonly" + wsm_persist="true" + datatype="nsIFile" + prefstring="mail.server.%serverkey%.newsrc.file" + class="uri-element input-inline" + aria-labelledby="nntp.newsrcFilePath.label" + /> + </hbox> + <button + id="browseForNewsrc" + label="&browseNewsrc.label;" + filepickertitle="&newsrcPicker1.label;" + accesskey="&browseNewsrc.accesskey;" + oncommand="BrowseForNewsrc();" + /> + </hbox> + + <separator class="thin" /> + + <hbox align="center"> + <label + id="server.localPath.label" + value="&localPath1.label;" + control="server.localPath" + /> + <hbox class="input-container" flex="1"> + <html:input + id="server.localPath" + type="text" + readonly="readonly" + wsm_persist="true" + datatype="nsIFile" + prefstring="mail.server.%serverkey%.directory" + class="uri-element input-inline" + aria-labelledby="server.localPath.label" + /> + </hbox> + <button + id="browseForLocalFolder" + label="&browseFolder.label;" + filepickertitle="&localFolderPicker.label;" + accesskey="&browseFolder.accesskey;" + oncommand="BrowseForLocalFolders();" + /> + </hbox> + </html:fieldset> + </html:div> + + <hbox hidefor="imap,pop3" align="center" iscontrolcontainer="true"> + <separator class="thin" /> + <label value="&serverDefaultCharset2.label;" control="nntp.charset" /> + <menulist + is="menulist-charsetpicker-viewing" + id="nntp.charset" + hidable="true" + hidefor="imap,pop3" + wsm_persist="true" + preftype="string" + prefstring="mail.server.%serverkey%.charset" + /> + </hbox> + </vbox> + </html:body> +</html> diff --git a/comm/mailnews/base/prefs/content/am-serverwithnoidentities.js b/comm/mailnews/base/prefs/content/am-serverwithnoidentities.js new file mode 100644 index 0000000000..12c1e7b837 --- /dev/null +++ b/comm/mailnews/base/prefs/content/am-serverwithnoidentities.js @@ -0,0 +1,94 @@ +/* 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 { MailUtils } = ChromeUtils.import("resource:///modules/MailUtils.jsm"); + +var gAccount; +var gOriginalStoreType; + +/** + * Called when the store type menu is clicked. + * + * @param {object} aStoreTypeElement - store type menu list element. + */ +function clickStoreTypeMenu(aStoreTypeElement) { + if (aStoreTypeElement.value == gOriginalStoreType) { + return; + } + + // Response from migration dialog modal. If the conversion is complete + // 'response.newRootFolder' will hold the path to the new account root folder, + // otherwise 'response.newRootFolder' will be null. + let response = { newRootFolder: null }; + // Send 'response' as an argument to converterDialog.xhtml. + window.browsingContext.topChromeWindow.openDialog( + "converterDialog.xhtml", + "mailnews:mailstoreconverter", + "modal,centerscreen,resizable=no,width=700,height=130", + gAccount.incomingServer, + aStoreTypeElement.value, + response + ); + changeStoreType(response); +} + +/** + * Revert store type to the original store type if converter modal closes + * before migration is complete, otherwise change original store type to + * currently selected store type. + * + * @param {object} aResponse - response from migration dialog modal. + */ +function changeStoreType(aResponse) { + if (aResponse.newRootFolder) { + // The conversion is complete. + // Set local path to the new account root folder which is present + // in 'aResponse.newRootFolder'. + document.getElementById("server.localPath").value = aResponse.newRootFolder; + gOriginalStoreType = document.getElementById( + "server.storeTypeMenulist" + ).value; + MailUtils.restartApplication(); + } else { + // The conversion failed or was cancelled. + // Restore selected item to what was selected before conversion. + document.getElementById("server.storeTypeMenulist").value = + gOriginalStoreType; + } +} + +function onInit(aPageId, aServerId) { + // UI for account store type + let storeTypeElement = document.getElementById("server.storeTypeMenulist"); + // set the menuitem to match the account + let currentStoreID = document + .getElementById("server.storeContractID") + .getAttribute("value"); + let targetItem = storeTypeElement.getElementsByAttribute( + "value", + currentStoreID + ); + storeTypeElement.selectedItem = targetItem[0]; + // Disable store type change if store has not been used yet. + storeTypeElement.setAttribute( + "disabled", + gAccount.incomingServer.getBoolValue("canChangeStoreType") + ? "false" + : !Services.prefs.getBoolPref("mail.store_conversion_enabled") + ); + // Initialise 'gOriginalStoreType' to the item that was originally selected. + gOriginalStoreType = storeTypeElement.value; +} + +function onPreInit(account, accountValues) { + gAccount = account; +} + +function onSave() { + let storeContractID = document.getElementById("server.storeTypeMenulist") + .selectedItem.value; + document + .getElementById("server.storeContractID") + .setAttribute("value", storeContractID); +} diff --git a/comm/mailnews/base/prefs/content/am-serverwithnoidentities.xhtml b/comm/mailnews/base/prefs/content/am-serverwithnoidentities.xhtml new file mode 100644 index 0000000000..1cdc36550f --- /dev/null +++ b/comm/mailnews/base/prefs/content/am-serverwithnoidentities.xhtml @@ -0,0 +1,152 @@ +<?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/accountManage.css" type="text/css"?> + +<!DOCTYPE html [ <!ENTITY % accountNoIdentDTD SYSTEM "chrome://messenger/locale/am-serverwithnoidentities.dtd"> +%accountNoIdentDTD; +<!ENTITY % accountServerTopDTD SYSTEM "chrome://messenger/locale/am-server-top.dtd">%accountServerTopDTD; +]> + +<html + xmlns="http://www.w3.org/1999/xhtml" + xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + xmlns:html="http://www.w3.org/1999/xhtml" +> + <head> + <title>&accountTitle.label;</title> + <script + defer="defer" + src="chrome://messenger/content/globalOverlay.js" + ></script> + <script + defer="defer" + src="chrome://global/content/editMenuOverlay.js" + ></script> + <script + defer="defer" + src="chrome://messenger/content/am-serverwithnoidentities.js" + ></script> + <script defer="defer" src="chrome://messenger/content/amUtils.js"></script> + <script> + // FIXME: move to script file. + window.addEventListener("load", event => { + parent.onPanelLoaded("am-serverwithnoidentities.xhtml"); + }); + </script> + </head> + <html:body + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + > + <vbox id="containerBox" flex="1"> + <hbox class="dialogheader"> + <label class="dialogheader-title" value="&accountTitle.label;" /> + </hbox> + + <separator class="thin" /> + + <label + hidden="true" + wsm_persist="true" + preftype="string" + prefattribute="value" + prefstring="mail.server.%serverkey%.storeContractID" + genericattr="true" + id="server.storeContractID" + /> + + <description class="secDesc">&accountSettingsDesc.label;</description> + <hbox class="input-container"> + <label + id="server.prettyName.label" + value="&accountName.label;" + control="server.prettyName" + accesskey="&accountName.accesskey;" + /> + <html:input + id="server.prettyName" + type="text" + wsm_persist="true" + class="input-inline" + onblur="parent.setAccountLabel(gAccount.key, this.value);" + prefstring="mail.server.%serverkey%.name" + aria-labelledby="server.prettyName.label" + /> + </hbox> + + <separator class="thin" /> + + <html:div> + <html:fieldset> + <html:legend>&messageStorage.label;</html:legend> + + <vbox align="start"> + <checkbox + wsm_persist="true" + id="server.emptyTrashOnExit" + label="&emptyTrashOnExit.label;" + accesskey="&emptyTrashOnExit.accesskey;" + prefattribute="value" + prefstring="mail.server.%serverkey%.empty_trash_on_exit" + /> + <hbox align="center"> + <label + value="&storeType.label;" + accesskey="&storeType.accesskey;" + control="server.storeTypeMenulist" + /> + <menulist + id="server.storeTypeMenulist" + oncommand="clickStoreTypeMenu(this);" + > + <menupopup id="server.storeTypeMenupopup"> + <menuitem + id="server.mboxStore" + value="@mozilla.org/msgstore/berkeleystore;1" + label="&mboxStore2.label;" + /> + <menuitem + id="server.maildirStore" + value="@mozilla.org/msgstore/maildirstore;1" + label="&maildirStore.label;" + /> + </menupopup> + </menulist> + </hbox> + </vbox> + + <separator class="thin" /> + + <hbox align="center"> + <label + id="server.localPath.label" + value="&localPath1.label;" + control="server.localPath" + /> + <hbox class="input-container" flex="1"> + <html:input + id="server.localPath" + type="text" + readonly="readonly" + wsm_persist="true" + datatype="nsIFile" + prefstring="mail.server.%serverkey%.directory" + class="uri-element input-inline" + aria-labelledby="server.localPath.label" + /> + </hbox> + <button + id="browseForLocalFolder" + label="&browseFolder.label;" + filepickertitle="&localFolderPicker.label;" + accesskey="&browseFolder.accesskey;" + oncommand="BrowseForLocalFolders()" + /> + </hbox> + </html:fieldset> + </html:div> + </vbox> + </html:body> +</html> diff --git a/comm/mailnews/base/prefs/content/am-smtp.js b/comm/mailnews/base/prefs/content/am-smtp.js new file mode 100644 index 0000000000..4dd48fa795 --- /dev/null +++ b/comm/mailnews/base/prefs/content/am-smtp.js @@ -0,0 +1,277 @@ +/* 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/. */ + +/* import-globals-from amUtils.js */ + +var { MailServices } = ChromeUtils.import( + "resource:///modules/MailServices.jsm" +); + +window.addEventListener("DOMContentLoaded", event => { + gSmtpServerListWindow.onLoad(); +}); + +var gSmtpServerListWindow = { + mBundle: null, + mServerList: null, + mAddButton: null, + mEditButton: null, + mDeleteButton: null, + mSetDefaultServerButton: null, + + onLoad() { + parent.onPanelLoaded("am-smtp.xhtml"); + + this.mBundle = document.getElementById("bundle_messenger"); + this.mServerList = document.getElementById("smtpList"); + this.mAddButton = document.getElementById("addButton"); + this.mEditButton = document.getElementById("editButton"); + this.mDeleteButton = document.getElementById("deleteButton"); + this.mSetDefaultServerButton = document.getElementById("setDefaultButton"); + + this.refreshServerList("", false); + + this.updateButtons(); + }, + + onSelectionChanged(aEvent) { + var server = this.getSelectedServer(); + if (!server) { + return; + } + + this.updateButtons(); + this.updateServerInfoBox(server); + }, + + onDeleteServer(aEvent) { + var server = this.getSelectedServer(); + if (!server) { + return; + } + + // confirm deletion + let cancel = Services.prompt.confirmEx( + window, + this.mBundle.getString("smtpServers-confirmServerDeletionTitle"), + this.mBundle.getFormattedString( + "smtpServers-confirmServerDeletion", + [server.hostname], + 1 + ), + Services.prompt.STD_YES_NO_BUTTONS, + null, + null, + null, + null, + {} + ); + + if (!cancel) { + // Remove password information first. + try { + server.forgetPassword(); + } catch (e) { + /* It is OK if this fails. */ + } + // Remove the server. + MailServices.smtp.deleteServer(server); + parent.replaceWithDefaultSmtpServer(server.key); + this.refreshServerList("", true); + } + }, + + onAddServer(aEvent) { + this.openServerEditor(null); + }, + + onEditServer(aEvent) { + let server = this.getSelectedServer(); + if (!server) { + return; + } + + this.openServerEditor(server); + }, + + onSetDefaultServer(aEvent) { + let server = this.getSelectedServer(); + if (!server) { + return; + } + + MailServices.smtp.defaultServer = server; + this.refreshServerList(MailServices.smtp.defaultServer.key, true); + }, + + updateButtons() { + let server = this.getSelectedServer(); + + // can't delete default server + if (server && MailServices.smtp.defaultServer == server) { + this.mSetDefaultServerButton.setAttribute("disabled", "true"); + this.mDeleteButton.setAttribute("disabled", "true"); + } else { + this.mSetDefaultServerButton.removeAttribute("disabled"); + this.mDeleteButton.removeAttribute("disabled"); + } + + if (!server) { + this.mEditButton.setAttribute("disabled", "true"); + } else { + this.mEditButton.removeAttribute("disabled"); + } + }, + + updateServerInfoBox(aServer) { + var noneSelected = this.mBundle.getString("smtpServerList-NotSpecified"); + + document.getElementById("nameValue").textContent = aServer.hostname; + document.getElementById("descriptionValue").textContent = + aServer.description || noneSelected; + document.getElementById("portValue").textContent = + aServer.port || noneSelected; + document.getElementById("userNameValue").textContent = + aServer.username || noneSelected; + document.getElementById("useSecureConnectionValue").textContent = + this.mBundle.getString( + "smtpServer-ConnectionSecurityType-" + aServer.socketType + ); + + const AuthMethod = Ci.nsMsgAuthMethod; + const SocketType = Ci.nsMsgSocketType; + var authStr = ""; + switch (aServer.authMethod) { + case AuthMethod.none: + authStr = "authNo"; + break; + case AuthMethod.passwordEncrypted: + authStr = "authPasswordEncrypted"; + break; + case AuthMethod.GSSAPI: + authStr = "authKerberos"; + break; + case AuthMethod.NTLM: + authStr = "authNTLM"; + break; + case AuthMethod.secure: + authStr = "authAnySecure"; + break; + case AuthMethod.passwordCleartext: + authStr = + aServer.socketType == SocketType.SSL || + aServer.socketType == SocketType.alwaysSTARTTLS + ? "authPasswordCleartextViaSSL" + : "authPasswordCleartextInsecurely"; + break; + case AuthMethod.OAuth2: + authStr = "authOAuth2"; + break; + default: + // leave empty + console.error( + "Warning: unknown value for smtpserver... authMethod: " + + aServer.authMethod + ); + } + document.getElementById("authMethodValue").textContent = authStr + ? this.mBundle.getString(authStr) + : noneSelected; + }, + + refreshServerList(aServerKeyToSelect, aFocusList) { + while (this.mServerList.hasChildNodes()) { + this.mServerList.lastChild.remove(); + } + for (let server of MailServices.smtp.servers) { + let listitem = this.createSmtpListItem( + server, + MailServices.smtp.defaultServer.key == server.key + ); + this.mServerList.appendChild(listitem); + } + + if (aServerKeyToSelect) { + this.setSelectedServer( + this.mServerList.querySelector('[key="' + aServerKeyToSelect + '"]') + ); + } else { + // Select the default server. + this.setSelectedServer( + this.mServerList.querySelector('[default="true"]') + ); + } + + if (aFocusList) { + this.mServerList.focus(); + } + }, + + createSmtpListItem(aServer, aIsDefault) { + var listitem = document.createXULElement("richlistitem"); + var serverName = ""; + + if (aServer.description) { + serverName = aServer.description + " - "; + } else if (aServer.username) { + serverName = aServer.username + " - "; + } + + serverName += aServer.hostname; + + if (aIsDefault) { + serverName += " " + this.mBundle.getString("defaultServerTag"); + listitem.setAttribute("default", "true"); + } + + let label = document.createXULElement("label"); + label.setAttribute("value", serverName); + listitem.appendChild(label); + listitem.setAttribute("key", aServer.key); + listitem.setAttribute("class", "smtpServerListItem"); + + // give it some unique id + listitem.id = "smtpServer." + aServer.key; + return listitem; + }, + + openServerEditor(aServer) { + let args = editSMTPServer(aServer); + + // now re-select the server which was just added + if (args.result) { + this.refreshServerList(aServer ? aServer.key : args.addSmtpServer, true); + } + + return args.result; + }, + + setSelectedServer(aServer) { + if (!aServer) { + return; + } + + setTimeout( + function (aServerList) { + aServerList.ensureElementIsVisible(aServer); + aServerList.selectItem(aServer); + }, + 0, + this.mServerList + ); + }, + + getSelectedServer() { + // The list of servers is a single selection listbox + // therefore 1 item is always selected. + // But if there are no SMTP servers defined yet, nothing will be selected. + let selection = this.mServerList.selectedItem; + if (!selection) { + return null; + } + + let serverKey = selection.getAttribute("key"); + return MailServices.smtp.getServerByKey(serverKey); + }, +}; diff --git a/comm/mailnews/base/prefs/content/am-smtp.xhtml b/comm/mailnews/base/prefs/content/am-smtp.xhtml new file mode 100644 index 0000000000..851fb8da3e --- /dev/null +++ b/comm/mailnews/base/prefs/content/am-smtp.xhtml @@ -0,0 +1,121 @@ +<?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/accountManage.css" type="text/css"?> + +<!DOCTYPE html SYSTEM "chrome://messenger/locale/am-advanced.dtd"> + +<html + xmlns="http://www.w3.org/1999/xhtml" + xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + xmlns:html="http://www.w3.org/1999/xhtml" +> + <head> + <title>&smtpServer.label;</title> + <script + defer="defer" + src="chrome://messenger/content/globalOverlay.js" + ></script> + <script + defer="defer" + src="chrome://global/content/editMenuOverlay.js" + ></script> + <script defer="defer" src="chrome://messenger/content/amUtils.js"></script> + <script defer="defer" src="chrome://messenger/content/am-smtp.js"></script> + </head> + <html:body + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + > + <stringbundle + id="bundle_messenger" + src="chrome://messenger/locale/messenger.properties" + /> + + <vbox flex="1" style="overflow: auto" + ><vbox id="containerBox" flex="1"> + <hbox class="dialogheader"> + <label class="dialogheader-title" value="&smtpServer.label;" /> + </hbox> + + <separator class="thin" /> + <label control="smtpList">&smtpDescription.label;</label> + <separator class="thin" /> + + <hbox flex="1"> + <richlistbox + id="smtpList" + onselect="gSmtpServerListWindow.onSelectionChanged(event);" + ondblclick="gSmtpServerListWindow.onEditServer(event);" + seltype="single" + flex="1" + style="height: 400px" + /> + + <vbox> + <button + id="addButton" + oncommand="gSmtpServerListWindow.onAddServer(event);" + label="&smtpListAdd.label;" + accesskey="&smtpListAdd.accesskey;" + /> + <button + id="editButton" + oncommand="gSmtpServerListWindow.onEditServer(event);" + label="&smtpListEdit.label;" + accesskey="&smtpListEdit.accesskey;" + /> + <separator /> + <button + id="deleteButton" + disabled="true" + oncommand="gSmtpServerListWindow.onDeleteServer(event);" + label="&smtpListDelete.label;" + accesskey="&smtpListDelete.accesskey;" + /> + <button + id="setDefaultButton" + disabled="true" + oncommand="gSmtpServerListWindow.onSetDefaultServer(event);" + label="&smtpListSetDefault.label;" + accesskey="&smtpListSetDefault.accesskey;" + /> + </vbox> + </hbox> + + <separator /> + + <label class="header">&serverDetails.label;</label> + <html:table id="smtpServerInfoBox"> + <html:tr> + <html:th id="descriptionLabel">&serverDescription.label;</html:th> + <html:td id="descriptionValue"></html:td> + </html:tr> + <html:tr> + <html:th id="nameLabel">&serverName.label;</html:th> + <html:td id="nameValue"></html:td> + </html:tr> + <html:tr> + <html:th id="portLabel">&serverPort.label;</html:th> + <html:td id="portValue"></html:td> + </html:tr> + <html:tr> + <html:th id="userNameLabel">&userName.label;</html:th> + <html:td id="userNameValue"></html:td> + </html:tr> + <html:tr> + <html:th id="authMethodLabel">&authMethod.label;</html:th> + <html:td id="authMethodValue"></html:td> + </html:tr> + <html:tr> + <html:th id="connectionSecurityLabel" + >&connectionSecurity.label;</html:th + > + <html:td id="useSecureConnectionValue"></html:td> + </html:tr> + </html:table> + <separator flex="1" /> </vbox + ></vbox> + </html:body> +</html> diff --git a/comm/mailnews/base/prefs/content/amUtils.js b/comm/mailnews/base/prefs/content/amUtils.js new file mode 100644 index 0000000000..b63f27b6c4 --- /dev/null +++ b/comm/mailnews/base/prefs/content/amUtils.js @@ -0,0 +1,275 @@ +/* -*- Mode: C++; tab-width: 4; 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/. */ + +/* import-globals-from am-smtp.js */ + +var { MailServices } = ChromeUtils.import( + "resource:///modules/MailServices.jsm" +); +var { MailUtils } = ChromeUtils.import("resource:///modules/MailUtils.jsm"); + +function BrowseForLocalFolders() { + const nsIFilePicker = Ci.nsIFilePicker; + const nsIFile = Ci.nsIFile; + + var currentFolderTextBox = document.getElementById("server.localPath"); + var fp = Cc["@mozilla.org/filepicker;1"].createInstance(nsIFilePicker); + + fp.init( + window, + document + .getElementById("browseForLocalFolder") + .getAttribute("filepickertitle"), + nsIFilePicker.modeGetFolder + ); + + var currentFolder = Cc["@mozilla.org/file/local;1"].createInstance(nsIFile); + try { + currentFolder.initWithPath(currentFolderTextBox.value); + fp.displayDirectory = currentFolder; + } catch (e) { + console.error( + `Failed to set folder path from value=${currentFolderTextBox.value}\n` + ); + } + + fp.open(rv => { + if (rv != nsIFilePicker.returnOK || !fp.file) { + return; + } + // Retrieve the selected folder. + let selectedFolder = fp.file; + + // Check if the folder can be used for mail storage. + if (!top.checkDirectoryIsUsable(selectedFolder)) { + return; + } + + currentFolderTextBox.value = selectedFolder.path; + currentFolderTextBox.dispatchEvent(new CustomEvent("change")); + }); +} + +/** + * Return server/folder name formatted with server name if needed. + * + * @param {nsIMsgFolder} aTargetFolder - nsIMsgFolder to format name for + @returns {string} THe formatted name. + * If target.isServer then only its name is returned. + * Otherwise return the name as "<foldername> on <servername>". + */ +function prettyFolderName(aTargetFolder) { + if (aTargetFolder.isServer) { + return aTargetFolder.prettyName; + } + + return document + .getElementById("bundle_messenger") + .getFormattedString("verboseFolderFormat", [ + aTargetFolder.prettyName, + aTargetFolder.server.prettyName, + ]); +} + +/** + * Checks validity of junk target server name and folder. + * + * @param {string} aTargetURI - The URI specification to check. + * @param {boolean} aIsServer - true if the URI specifies only a server + * (without folder) + * + * @returns {string} the value of aTargetURI if it is valid (usable), otherwise null + */ +function checkJunkTargetFolder(aTargetURI, aIsServer) { + try { + // Does the target account exist? + let targetServer; + if (aIsServer) { + targetServer = MailUtils.getOrCreateFolder(aTargetURI + "/Junk").server; + } else { + targetServer = MailUtils.getExistingFolder(aTargetURI).server; + } + + // If the target server has deferred storage, Junk can't be stored into it. + if (targetServer.rootFolder != targetServer.rootMsgFolder) { + return null; + } + } catch (e) { + return null; + } + + return aTargetURI; +} + +/** + * Finds a usable target for storing Junk mail. + * If the passed in server URI is not usable, choose Local Folders. + * + * @param {string} aTargetURI - The URI of a server or folder to try first + * @param {boolean} aIsServer - true if the URI specifies only a server (without folder) + * + * @returns {string} the server/folder URI of a usable target for storing Junk + */ +function chooseJunkTargetFolder(aTargetURI, aIsServer) { + let server = null; + + if (aTargetURI) { + server = MailUtils.getOrCreateFolder(aTargetURI).server; + if ( + !server.canCreateFoldersOnServer || + !server.canSearchMessages || + server.rootFolder != server.rootMsgFolder + ) { + server = null; + } + } + if (!server) { + server = MailServices.accounts.localFoldersServer; + } + + return server.serverURI + (!aIsServer ? "/Junk" : ""); +} + +/** + * Fixes junk target folders if they point to an invalid/unusable (e.g. deferred) + * folder/account. Only returns the new safe values. It is up to the caller + * to push them to the proper elements/prefs. + * + * @param {string} aSpamActionTargetAccount - The value of the + * server.*.spamActionTargetAccount pref value (URI). + * @param {string} aSpamActionTargetFolder - The value of the + * server.*.spamActionTargetFolder pref value (URI). + * @param {string} aProposedTarget - The URI of a new target to try. + * @param {integer} aMoveTargetModeValue - The value of the + * server.*.moveTargetMode pref value (0/1). + * @param {nsISpamSettings} aServerSpamSettings - The nsISpamSettings object + * of any server (used just for the MOVE_TARGET_MODE_* constants). + * @param {boolean} aMoveOnSpam - The server.*.moveOnSpam pref value). + * + * @returns {object[]} an array containing: + * newTargetAccount new safe junk target account + * newTargetAccount new safe junk target folder + * newMoveOnSpam new moveOnSpam value + */ +function sanitizeJunkTargets( + aSpamActionTargetAccount, + aSpamActionTargetFolder, + aProposedTarget, + aMoveTargetModeValue, + aServerSpamSettings, + aMoveOnSpam +) { + // Check if folder targets are valid. + aSpamActionTargetAccount = checkJunkTargetFolder( + aSpamActionTargetAccount, + true + ); + if (!aSpamActionTargetAccount) { + // If aSpamActionTargetAccount is not valid, + // reset to default behavior to NOT move junk messages... + if (aMoveTargetModeValue == aServerSpamSettings.MOVE_TARGET_MODE_ACCOUNT) { + aMoveOnSpam = false; + } + + // ... and find a good default target. + aSpamActionTargetAccount = chooseJunkTargetFolder(aProposedTarget, true); + } + + aSpamActionTargetFolder = checkJunkTargetFolder( + aSpamActionTargetFolder, + false + ); + if (!aSpamActionTargetFolder) { + // If aSpamActionTargetFolder is not valid, + // reset to default behavior to NOT move junk messages... + if (aMoveTargetModeValue == aServerSpamSettings.MOVE_TARGET_MODE_FOLDER) { + aMoveOnSpam = false; + } + + // ... and find a good default target. + aSpamActionTargetFolder = chooseJunkTargetFolder(aProposedTarget, false); + } + + return [aSpamActionTargetAccount, aSpamActionTargetFolder, aMoveOnSpam]; +} + +/** + * Opens Preferences (Options) dialog on the Advanced pane, General tab + * so that the user sees where the global receipts settings can be found. + * + * @param {string} aTBPaneId - Thunderbird pref paneID to open. + * @param {string} aTBScrollPaneTo - Thunderbird ID of the element to scroll into view. + * @param {any} aTBOtherArgs - Other arguments to send to the pref tab. + * @param {string} aSMPaneId - Seamonkey pref pane to open. + */ +function openPrefsFromAccountManager( + aTBPaneId, + aTBScrollPaneTo, + aTBOtherArgs, + aSMPaneId +) { + let win = + Services.wm.getMostRecentWindow("mail:3pane") || + Services.wm.getMostRecentWindow("mail:messageWindow") || + Services.wm.getMostRecentWindow("msgcompose"); + if (!win) { + return; + } + + // If openOptionsDialog() exists, we are in Thunderbird. + if (typeof win.openOptionsDialog == "function") { + win.openOptionsDialog(aTBPaneId, aTBScrollPaneTo, aTBOtherArgs); + } + // If goPreferences() exists, we are in Seamonkey. + if (typeof win.goPreferences == "function") { + win.goPreferences(aSMPaneId); + } +} + +/** + * Check if the given account name already exists in any account. + * + * @param {string} aAccountName - The account name string to look for. + * @param {string} [aAccountKey] - The key of an account that is skipped when + * searching the name. If unset, do not skip any account. + */ +function accountNameExists(aAccountName, aAccountKey) { + for (let account of MailServices.accounts.accounts) { + if ( + account.key != aAccountKey && + account.incomingServer && + aAccountName == account.incomingServer.prettyName + ) { + return true; + } + } + + return false; +} + +/** + * Open a dialog to edit properties of an SMTP server. + * + * @param {nsISmtpServer} aServer - The server to edit. + * @returns {object} Object with result member to indicate whether 'OK' + * was clicked and addSmtpServer with key of newly created server. + */ +function editSMTPServer(aServer) { + let args = { server: aServer, result: false, addSmtpServer: "" }; + + let onCloseSMTPDialog = function () { + if (args.result) { + gSmtpServerListWindow.refreshServerList(aServer, true); + } + }; + + parent.gSubDialog.open( + "chrome://messenger/content/SmtpServerEdit.xhtml", + { closingCallback: onCloseSMTPDialog }, + args + ); + + return args; +} diff --git a/comm/mailnews/base/prefs/content/aw-accname.js b/comm/mailnews/base/prefs/content/aw-accname.js new file mode 100644 index 0000000000..97dce27406 --- /dev/null +++ b/comm/mailnews/base/prefs/content/aw-accname.js @@ -0,0 +1,28 @@ +/* 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/. */ + +/* import-globals-from AccountWizard.js */ + +function acctNamePageValidate() { + var accountname = document.getElementById("prettyName").value; + // Check if this accountname already exists. If so, return false so that + // user can enter a different unique account name. + document.querySelector("wizard").canAdvance = + !!accountname && !accountNameExists(accountname); +} + +function acctNamePageUnload() { + var pageData = parent.GetPageData(); + var accountname = document.getElementById("prettyName").value; + pageData.prettyName = accountname; + return true; +} + +function acctNamePageInit() { + var accountNameInput = document.getElementById("prettyName"); + if (accountNameInput.value == "") { + accountNameInput.value = parent.GetPageData().hostname; + } + acctNamePageValidate(); +} diff --git a/comm/mailnews/base/prefs/content/aw-done.js b/comm/mailnews/base/prefs/content/aw-done.js new file mode 100644 index 0000000000..93ac3994bc --- /dev/null +++ b/comm/mailnews/base/prefs/content/aw-done.js @@ -0,0 +1,39 @@ +/* -*- 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/. */ + +/* import-globals-from AccountWizard.js */ + +var { MailServices } = ChromeUtils.import( + "resource:///modules/MailServices.jsm" +); + +function donePageInit() { + var pageData = parent.GetPageData(); + + var email = pageData.email; + setDivTextFromForm("identity.email", email); + setDivTextFromForm("server.username", getUsernameFromEmail(email)); + setDivTextFromForm("account.name", pageData.prettyName); + setDivTextFromForm("newsServer.name", pageData.hostname); +} + +function setDivTextFromForm(divid, value) { + // collapse the row if the div has no value + let label = document.getElementById(`${divid}.label`); + let text = document.getElementById(`${divid}.text`); + + if (!value) { + text.style.display = "none"; + label.style.display = "none"; + return; + } + + // otherwise fill in the .text element + text.style.display = null; + label.style.display = null; + + // set the value + text.setAttribute("value", value); +} diff --git a/comm/mailnews/base/prefs/content/aw-identity.js b/comm/mailnews/base/prefs/content/aw-identity.js new file mode 100644 index 0000000000..66d63d6e1e --- /dev/null +++ b/comm/mailnews/base/prefs/content/aw-identity.js @@ -0,0 +1,77 @@ +/* -*- Mode: C++; tab-width: 4; 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/. */ + +/* import-globals-from AccountWizard.js */ + +var gPrefsBundle; + +function identityPageValidate() { + document.querySelector("wizard").canAdvance = + document.getElementById("fullName").validity.valid && + document.getElementById("email").validity.valid; +} + +function identityPageUnload() { + var pageData = parent.GetPageData(); + var name = document.getElementById("fullName").value; + let email = document.getElementById("email").value.trim(); + pageData.fullName = name; + pageData.email = email; + + return true; +} + +function identityPageInit() { + gPrefsBundle = document.getElementById("bundle_prefs"); + setEmailDescriptionText(); + checkForFullName(); + checkForEmail(); + identityPageValidate(); +} + +function setEmailDescriptionText() { + var emailDescText = document.getElementById("emailDescText"); + var emailFieldLabel = document.getElementById("emailFieldLabel"); + + // Set the default field label + emailFieldLabel.setAttribute( + "value", + gPrefsBundle.getString("emailFieldText") + ); + + // Check for obtained values and set with default values if needed + var username = gPrefsBundle.getString("exampleEmailUserName"); + var domain = gPrefsBundle.getString("exampleEmailDomain"); + + let displayText = gPrefsBundle.getFormattedString("defaultEmailText", [ + username, + domain, + ]); + + // Display the dynamically generated text for email description + emailDescText.textContent = displayText; +} + +function checkForFullName() { + var name = document.getElementById("fullName"); + if (name.value == "" && "@mozilla.org/userinfo;1" in Cc) { + name.value = Cc["@mozilla.org/userinfo;1"].getService( + Ci.nsIUserInfo + ).fullname; + } +} + +function checkForEmail() { + var email = document.getElementById("email"); + var pageData = parent.GetPageData(); + if (pageData.email) { + email.value = pageData.email; + } + if (email.value == "" && "@mozilla.org/userinfo;1" in Cc) { + email.value = Cc["@mozilla.org/userinfo;1"].getService( + Ci.nsIUserInfo + ).emailAddress; + } +} diff --git a/comm/mailnews/base/prefs/content/aw-incoming.js b/comm/mailnews/base/prefs/content/aw-incoming.js new file mode 100644 index 0000000000..a4f746d71d --- /dev/null +++ b/comm/mailnews/base/prefs/content/aw-incoming.js @@ -0,0 +1,42 @@ +/* -*- 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/. */ + +/* import-globals-from AccountWizard.js */ + +var { cleanUpHostName, isLegalHostNameOrIP } = ChromeUtils.import( + "resource:///modules/hostnameUtils.jsm" +); +var { NntpUtils } = ChromeUtils.import("resource:///modules/NntpUtils.jsm"); + +function incomingPageValidate() { + let hostName = cleanUpHostName(document.getElementById("newsServer").value); + + let hasAccount = false; + let server = NntpUtils.findServer(hostName); + if (server) { + // It's OK if a server exists, as long as it's not used by any account. + hasAccount = MailServices.accounts.FindAccountForServer(server); + } + // Can advance if it's a legal host name and we do not already have a server + // in use with the same host name. + document.querySelector("wizard").canAdvance = + !!isLegalHostNameOrIP(hostName) && !hasAccount; +} + +function incomingPageUnload() { + parent.GetPageData().hostname = cleanUpHostName( + document.getElementById("newsServer").value + ); + + return true; +} + +function incomingPageInit() { + var pageData = parent.GetPageData(); + if (pageData.hostname) { + document.getElementById("newsServer").value = pageData.hostname; + } + incomingPageValidate(); +} diff --git a/comm/mailnews/base/prefs/content/converterDialog.js b/comm/mailnews/base/prefs/content/converterDialog.js new file mode 100644 index 0000000000..e0ac74fd5b --- /dev/null +++ b/comm/mailnews/base/prefs/content/converterDialog.js @@ -0,0 +1,385 @@ +/* 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 functionality for the front-end part of the mail store + * type conversion. + */ + +var { MailUtils } = ChromeUtils.import("resource:///modules/MailUtils.jsm"); +var { FileUtils } = ChromeUtils.importESModule( + "resource://gre/modules/FileUtils.sys.mjs" +); +ChromeUtils.defineModuleGetter( + this, + "FolderUtils", + "resource:///modules/FolderUtils.jsm" +); +var MailstoreConverter = ChromeUtils.import( + "resource:///modules/mailstoreConverter.jsm" +); +var { AppConstants } = ChromeUtils.importESModule( + "resource://gre/modules/AppConstants.sys.mjs" +); + +window.addEventListener("DOMContentLoaded", () => { + for (let img of document.querySelectorAll(".infoIcon")) { + img.setAttribute( + "src", + "chrome://messenger/skin/icons/new/activity/warning.svg" + ); + } +}); + +var log = console.createInstance({ + prefix: "mail.mailstoreconverter", + maxLogLevel: "Warn", + maxLogLevelPref: "mail.mailstoreconverter.loglevel", +}); +// {nsIMsgIncomingServer} server for the account to be migrated. +var gServer; +// {nsIMsgFolder} account root folder. +var gFolder; +// 'gResponse.newRootFolder' is path to the new account root folder if migration +// is complete, else null. +// 'gResponse' is set to the modified response parameter received from +// am-server.js. +var gResponse; +// Array to hold deferred accounts. +var gDeferredAccounts = []; +// Value of Services.io.offline before migration. +var gOriginalOffline; +/** + * Place account name in migration dialog modal. + * + * @param {nsIMsgIncomingServer} aServer - account server. + */ +function placeAccountName(aServer) { + gOriginalOffline = Services.io.offline; + + let bundle = Services.strings.createBundle( + "chrome://messenger/locale/converterDialog.properties" + ); + + let brandShortName = Services.strings + .createBundle("chrome://branding/locale/brand.properties") + .GetStringFromName("brandShortName"); + + // 'deferredToRootFolder' holds path of rootMsgFolder of account to which + // other accounts have been deferred. + let deferredToRootFolder = aServer.rootMsgFolder.filePath.path; + // String to hold names of deferred accounts separated by commas. + let deferredAccountsString = ""; + // Account to which other accounts have been deferred. + let deferredToAccount; + // Array of all accounts. + let accounts = FolderUtils.allAccountsSorted(true); + + for (let account of accounts) { + if ( + account.incomingServer.rootFolder.filePath.path == deferredToRootFolder + ) { + // Other accounts may be deferred to this account. + deferredToAccount = account; + } else if ( + account.incomingServer.rootMsgFolder.filePath.path == deferredToRootFolder + ) { + // This is a deferred account. + gDeferredAccounts.push(account); + } + } + + // String to hold the names of accounts to be converted separated by commas. + let accountsToConvert = ""; + + if (gDeferredAccounts.length >= 1) { + // Add account names to 'accountsToConvert' and 'deferredAccountsString'. + for (let i = 0; i < gDeferredAccounts.length; i++) { + if (i < gDeferredAccounts.length - 1) { + accountsToConvert += + gDeferredAccounts[i].incomingServer.username + ", "; + deferredAccountsString += + gDeferredAccounts[i].incomingServer.username + ", "; + } else { + accountsToConvert += gDeferredAccounts[i].incomingServer.username; + deferredAccountsString += gDeferredAccounts[i].incomingServer.username; + } + } + + // Username of Local Folders is "nobody". So it's better to use + // its hostname which is "Local Folders". + // TODO: maybe test against .key == MailServices.accounts.localFoldersServer.key ? + if (deferredToAccount.incomingServer.hostName == "Local Folders") { + accountsToConvert += ", " + deferredToAccount.incomingServer.prettyName; + } else { + accountsToConvert += ", " + deferredToAccount.incomingServer.prettyName; + } + log.info(accountsToConvert + " will be converted"); + let storeContractId = Services.prefs.getCharPref( + "mail.server." + deferredToAccount.incomingServer.key + ".storeContractID" + ); + + if (storeContractId == "@mozilla.org/msgstore/berkeleystore;1") { + storeContractId = "maildir"; + } else { + storeContractId = "mbox"; + } + + // Username of Local Folders is "nobody". So it's better to use + // its hostname which is "Local Folders". + // TODO: maybe test against .key != MailServices.accounts.localFoldersServer.key ? + let deferredToAccountName = deferredToAccount.incomingServer.hostName; + if (deferredToAccountName != "Local Folders") { + deferredToAccountName = deferredToAccount.incomingServer.username; + } + + if (aServer.rootFolder.filePath.path != deferredToRootFolder) { + document.getElementById("warningSpan").textContent = + bundle.formatStringFromName( + "converterDialog.warningForDeferredAccount", + [ + aServer.username, + deferredToAccountName, + deferredToAccountName, + deferredAccountsString, + accountsToConvert, + storeContractId, + brandShortName, + ] + ); + } else { + document.getElementById("warningSpan").textContent = + bundle.formatStringFromName( + "converterDialog.warningForDeferredToAccount", + [ + deferredToAccountName, + deferredAccountsString, + accountsToConvert, + storeContractId, + brandShortName, + ] + ); + } + + document.getElementById("messageSpan").textContent = + bundle.formatStringFromName("converterDialog.messageForDeferredAccount", [ + accountsToConvert, + storeContractId, + ]); + gServer = deferredToAccount.incomingServer; + } else { + // No account is deferred. + let storeContractId = Services.prefs.getCharPref( + "mail.server." + aServer.key + ".storeContractID" + ); + if (storeContractId == "@mozilla.org/msgstore/berkeleystore;1") { + storeContractId = "maildir"; + } else { + storeContractId = "mbox"; + } + + let tempName = aServer.username; + if (tempName == "nobody") { + tempName = "Local Folders"; + } else if (!tempName) { + tempName = aServer.hostName; + } + + document.getElementById("warningSpan").textContent = + bundle.formatStringFromName("converterDialog.warning", [ + tempName, + storeContractId, + brandShortName, + ]); + document.getElementById("messageSpan").textContent = + bundle.formatStringFromName("converterDialog.message", [ + tempName, + storeContractId, + ]); + gServer = aServer; + } + + // Forces the resize of the dialog to the actual content + window.sizeToContent(); +} + +/** + * Start the conversion process. + * + * @param {string} aSelectedStoreType - mailstore type selected by user. + * @param {object} aResponse - response from the migration dialog modal. + */ +function startContinue(aSelectedStoreType, aResponse) { + gResponse = aResponse; + gFolder = gServer.rootFolder.filePath; + + let bundle = Services.strings.createBundle( + "chrome://messenger/locale/converterDialog.properties" + ); + + document + .getElementById("progress") + .addEventListener("progress", function (e) { + document.getElementById("progress").value = e.detail; + document.getElementById("progressPercent").textContent = + bundle.formatStringFromName("converterDialog.percentDone", [e.detail]); + }); + + document.getElementById("warningArea").hidden = true; + document.getElementById("progressArea").hidden = false; + + // Storing original prefs and root folder path + // to revert changes in case of error. + let p1 = "mail.server." + gServer.key + ".directory"; + let p2 = "mail.server." + gServer.key + ".directory-rel"; + let p3 = "mail.server." + gServer.key + ".newsrc.file"; + let p4 = "mail.server." + gServer.key + ".newsrc.file-rel"; + let p5 = "mail.server." + gServer.key + ".storeContractID"; + + let originalDirectoryPref = Services.prefs.getCharPref(p1); + let originalDirectoryRelPref = Services.prefs.getCharPref(p2); + let originalNewsrcFilePref; + let originalNewsrcFileRelPref; + if (gServer.type == "nntp") { + originalNewsrcFilePref = Services.prefs.getCharPref(p3); + originalNewsrcFileRelPref = Services.prefs.getCharPref(p4); + } + let originalStoreContractID = Services.prefs.getCharPref(p5); + let originalRootFolderPath = gServer.rootFolder.filePath.path; + + /** + * Called when promise returned by convertMailStoreTo() is rejected. + * + * @param {string} aReason - error because of which the promise was rejected. + */ + function promiseRejected(aReason) { + log.error("Conversion to '" + aSelectedStoreType + "' failed: " + aReason); + document.getElementById("messageSpan").hidden = true; + + document.getElementById("errorSpan").hidden = false; + gResponse.newRootFolder = null; + + // Revert prefs. + Services.prefs.setCharPref(p1, originalDirectoryPref); + Services.prefs.setCharPref(p2, originalDirectoryRelPref); + if (gServer.type == "nntp") { + Services.prefs.setCharPref(p3, originalNewsrcFilePref); + Services.prefs.setCharPref(p4, originalNewsrcFileRelPref); + } + Services.prefs.setCharPref(p5, originalStoreContractID); + Services.prefs.savePrefFile(null); + if (gServer.rootFolder.filePath.path != originalRootFolderPath) { + gServer.rootFolder.filePath = new FileUtils.File(originalRootFolderPath); + } + Services.io.offline = gOriginalOffline; + } + + /** + * Called when promise returned by convertMailStoreTo() is resolved. + * + * @param {string} aVal - path of the new account root folder with which the + * promise returned by convertMailStoreTo() is resolved. + */ + function promiseResolved(aVal) { + log.info("Converted to '" + aSelectedStoreType + "' - " + aVal); + + gResponse.newRootFolder = aVal; + for (let deferredAccount of gDeferredAccounts) { + let defServer = deferredAccount.incomingServer; + defServer.rootMsgFolder.filePath = new FileUtils.File(aVal); + Services.prefs.setCharPref( + "mail.server." + defServer.key + ".storeContractID", + aSelectedStoreType + ); + } + + Services.io.offline = gOriginalOffline; + document.getElementById("cancel").hidden = true; + document.getElementById("finish").hidden = false; + document.getElementById("messageSpan").hidden = true; + document.getElementById("completeSpan").hidden = false; + } + + /** + * Check whether an mbox folder can be compacted or not. + * + * @param {nsIMsgFolder} aFolder - mbox folder that is to be checked. + */ + function canCompact(aFolder) { + if (aFolder.expungedBytes != 0) { + return true; + } + if (aFolder.hasSubFolders) { + for (let subFolder of aFolder.subFolders) { + if (canCompact(subFolder)) { + return true; + } + } + } + return false; + } + + // Compaction (compactAll()) works only for mbox folders which satisfy one of + // the following 2 conditions - + // 1. Messages are moved out of the folder. + // 2. Messages are moved out of some descendant folder of the folder. + // If the account root folder can be compacted, start the conversion after + // compacting it. + if ( + originalStoreContractID == "@mozilla.org/msgstore/berkeleystore;1" && + canCompact(gServer.rootFolder) + ) { + let urlListener = { + OnStartRunningUrl(aUrl) {}, + OnStopRunningUrl(aUrl, aExitCode) { + let pConvert = MailstoreConverter.convertMailStoreTo( + originalStoreContractID, + gServer, + document.getElementById("progress") + ); + pConvert + .then(function (val) { + promiseResolved(val); + }) + .catch(function (reason) { + promiseRejected(reason); + }); + }, + }; + gServer.rootFolder.compactAll(urlListener, null); + } else { + let pConvert = MailstoreConverter.convertMailStoreTo( + originalStoreContractID, + gServer, + document.getElementById("progress") + ); + pConvert + .then(function (val) { + promiseResolved(val); + }) + .catch(function (reason) { + promiseRejected(reason); + }); + } +} + +/** + * Cancel the conversion. + * + * @param {object} aResponse - response param from the migration dialog modal. + */ +function cancelConversion(aResponse) { + gResponse = aResponse; + gResponse.newRootFolder = null; + MailstoreConverter.terminateWorkers(); + Services.io.offline = gOriginalOffline; + window.close(); +} + +/** + * Called when "finish" button is clicked. + */ +function finishConversion() { + window.close(); +} diff --git a/comm/mailnews/base/prefs/content/converterDialog.xhtml b/comm/mailnews/base/prefs/content/converterDialog.xhtml new file mode 100644 index 0000000000..40944fd556 --- /dev/null +++ b/comm/mailnews/base/prefs/content/converterDialog.xhtml @@ -0,0 +1,53 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- 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 html [ <!ENTITY % brandDTD SYSTEM "chrome://branding/locale/brand.dtd"> +%brandDTD; +<!ENTITY % converterDTD SYSTEM "chrome://messenger/locale/converterDialog.dtd"> +%converterDTD; ]> +<html xmlns="http://www.w3.org/1999/xhtml"> + <head> + <meta charset="UTF-8" /> + <title>&converterDialog.title;</title> + <link + rel="stylesheet" + href="chrome://messenger/skin/shared/converterDialog.css" + /> + <script src="chrome://messenger/content/converterDialog.js"></script> + </head> + + <body onload="placeAccountName(window.arguments[0]);"> + <div id="warningArea" class="convert-area"> + <img class="infoIcon" alt="" /> + <p id="warningSpan"></p> + <div class="controls"> + <button onclick="cancelConversion(window.arguments[2]);"> + &converterDialog.cancelButton; + </button> + <button + onclick="startContinue(window.arguments[1], window.arguments[2]);" + > + &converterDialog.continueButton; + </button> + </div> + </div> + <div id="progressArea" class="convert-area" hidden="hidden"> + <img class="infoIcon" alt="" /> + <p id="messageSpan"></p> + <p id="completeSpan" hidden="hidden">&converterDialog.complete;</p> + <p id="errorSpan" hidden="hidden">&converterDialog.error;</p> + <progress id="progress" value="0" max="100"></progress> + <span id="progressPercent"></span> + <div class="controls"> + <button id="cancel" onclick="cancelConversion(window.arguments[1]);"> + &converterDialog.cancelButton; + </button> + <button id="finish" onclick="finishConversion();" hidden="hidden"> + &converterDialog.finishButton; + </button> + </div> + </div> + </body> +</html> diff --git a/comm/mailnews/base/prefs/content/removeAccount.js b/comm/mailnews/base/prefs/content/removeAccount.js new file mode 100644 index 0000000000..724e45e139 --- /dev/null +++ b/comm/mailnews/base/prefs/content/removeAccount.js @@ -0,0 +1,168 @@ +/* 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 { MailServices } = ChromeUtils.import( + "resource:///modules/MailServices.jsm" +); + +var gServer; +var gDialog; + +window.addEventListener("DOMContentLoaded", onLoad); + +document.addEventListener("dialogdisclosure", showInfo); +document.addEventListener("dialogaccept", onAccept); +document.subDialogSetDefaultFocus = isInitialFocus => { + gDialog.getButton("cancel").focus(); + delete document.subDialogSetDefaultFocus; +}; + +function onLoad(event) { + gServer = window.arguments[0].account.incomingServer; + gDialog = document.querySelector("dialog"); + + let bundle = document.getElementById("bundle_removeAccount"); + let removeQuestion = bundle.getFormattedString("removeQuestion", [ + gServer.prettyName, + ]); + document.getElementById("accountName").textContent = removeQuestion; + + // Allow to remove account data if it has a local storage. + let localDirectory = gServer.localPath; + if (localDirectory && localDirectory.exists()) { + localDirectory.normalize(); + + // Do not allow removal if localPath is outside of profile folder. + let profilePath = Services.dirsvc.get("ProfD", Ci.nsIFile); + profilePath.normalize(); + + // TODO: bug 77652, decide what to do for deferred accounts. + // And inform the user if the account localPath is outside the profile. + if ( + gServer.isDeferredTo || + (gServer instanceof Ci.nsIPop3IncomingServer && + gServer.deferredToAccount) || + !profilePath.contains(localDirectory) + ) { + document.getElementById("removeData").disabled = true; + } + } else { + document.getElementById("removeDataPossibility").collapsed = true; + } + + if (gServer.type == "im") { + let dataCheckbox = document.getElementById("removeData"); + dataCheckbox.label = dataCheckbox.getAttribute("labelChat"); + dataCheckbox.accessKey = dataCheckbox.getAttribute("accesskeyChat"); + } + + enableRemove(); +} + +function enableRemove() { + gDialog.getButton("accept").disabled = + !document.getElementById("removeAccount").checked && + !document.getElementById("removeData").checked; +} + +/** + * Show the local directory. + */ +function openLocalDirectory() { + let nsLocalFile = Components.Constructor( + "@mozilla.org/file/local;1", + "nsIFile", + "initWithPath" + ); + let localDir = gServer.localPath.path; + try { + new nsLocalFile(localDir).reveal(); + } catch (e) { + // Reveal may fail e.g. on Linux, then just show the path as a string. + document.getElementById("localDirectory").value = localDir; + document.getElementById("localDirectory").collapsed = false; + } +} + +function showInfo() { + let descs = document.querySelectorAll("vbox.indent"); + for (let desc of descs) { + desc.collapsed = false; + } + + // TODO: bug 1238271, this should use showFor attributes if possible. + if (gServer.type == "imap" || gServer.type == "nntp") { + document.getElementById("serverAccount").collapsed = false; + } else if (gServer.type == "im") { + document.getElementById("chatAccount").collapsed = false; + } else { + document.getElementById("localAccount").collapsed = false; + } + + parent.gSubDialog._topDialog.resizeDialog(); + gDialog.getButton("disclosure").disabled = true; + gDialog.getButton("disclosure").blur(); +} + +function removeAccount() { + let removeAccount = document.getElementById("removeAccount").checked; + let removeData = document.getElementById("removeData").checked; + let account = window.arguments[0].account; + try { + // Remove the requested account data. + if (removeAccount) { + try { + // Remove password information first. + account.incomingServer.forgetPassword(); + } catch (e) { + /* It is OK if this fails. */ + } + // Remove account + MailServices.accounts.removeAccount(account, removeData); + account = null; + delete window.arguments[0].account; + gServer = null; + window.arguments[0].result = true; + } else if (removeData) { + // Remove files only. + // TODO: bug 1302193 + window.arguments[0].result = false; + } + + document.getElementById("success").hidden = false; + } catch (ex) { + document.getElementById("failure").hidden = false; + console.error("Failure to remove account: " + ex); + window.arguments[0].result = false; + } + document.getElementById("progress").hidden = true; +} + +function onAccept(event) { + // If Cancel is disabled, we already tried to remove the account + // and can only close the dialog. + if (gDialog.getButton("cancel").disabled) { + return; + } + + gDialog.getButton("accept").disabled = true; + gDialog.getButton("cancel").disabled = true; + gDialog.getButton("disclosure").disabled = true; + + // Change the "Remove" to an "OK" button by clearing the custom label. + gDialog.removeAttribute("buttonlabelaccept"); + gDialog.removeAttribute("buttonaccesskeyaccept"); + gDialog.getButton("accept").removeAttribute("label"); + gDialog.getButton("accept").removeAttribute("accesskey"); + gDialog.buttons = "accept"; + + document.getElementById("removeAccountSection").hidden = true; + document.getElementById("confirmationSection").hidden = false; + window.sizeToContent(); + + removeAccount(); + + gDialog.getButton("accept").disabled = false; + event.preventDefault(); +} diff --git a/comm/mailnews/base/prefs/content/removeAccount.xhtml b/comm/mailnews/base/prefs/content/removeAccount.xhtml new file mode 100644 index 0000000000..510b3fef84 --- /dev/null +++ b/comm/mailnews/base/prefs/content/removeAccount.xhtml @@ -0,0 +1,84 @@ +<?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://global/skin/checkbox.css" type="text/css"?> + +<!DOCTYPE html [ +<!ENTITY % brandDTD SYSTEM "chrome://branding/locale/brand.dtd"> +%brandDTD; +<!ENTITY % removalDTD SYSTEM "chrome://messenger/locale/removeAccount.dtd"> +%removalDTD; +]> +<html xmlns="http://www.w3.org/1999/xhtml" + xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + xmlns:html="http://www.w3.org/1999/xhtml" + width="600" + scrolling="false"> +<head> + <title>&dialogTitle;</title> + <link rel="localization" href="branding/brand.ftl" /> + <script defer="defer" src="chrome://messenger/content/removeAccount.js"></script> +</head> +<html:body xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> +<dialog id="removeAccountDialog" + buttons="accept,disclosure,cancel" + buttonlabelaccept="&removeButton.label;" + buttonaccesskeyaccept="&removeButton.accesskey;" + defaultButton="cancel" + style="width:100vw; height:100vh;"> + <stringbundle id="bundle_removeAccount" + src="chrome://messenger/locale/removeAccount.properties"/> + <vbox id="removeAccountSection"> + <label id="accountName"></label> + <separator class="thin"/> + <checkbox id="removeAccount" + label="&removeAccount.label;" + checked="true" + disabled="true" + accesskey="&removeAccount.accesskey;" + oncommand="enableRemove();"/> + <vbox class="indent" collapsed="true"> + <description> + &removeAccount.desc; + </description> + </vbox> + <vbox id="removeDataPossibility" collapsed="false"> + <checkbox id="removeData" + label="&removeData.label;" + labelChat="&removeDataChat.label;" + accesskey="&removeData.accesskey;" + accesskeyChat="&removeDataChat.accesskey;" + oncommand="enableRemove();"/> + <vbox id="removeAccountBox" class="indent" collapsed="true"> + <description id="localAccount" collapsed="true"> + &removeDataLocalAccount.desc; + </description> + <description id="serverAccount" collapsed="true"> + &removeDataServerAccount.desc; + </description> + <description id="chatAccount" collapsed="true"> + &removeDataChatAccount.desc; + </description> + <hbox align="center"> + <button id="showLocalDirectory" + label="&showData.label;" + accesskey="&showData.accesskey;" + oncommand="openLocalDirectory();"/> + <label id="localDirectory" collapsed="true"/> + </hbox> + </vbox> + </vbox> + </vbox> + <vbox id="confirmationSection" align="center" pack="center" flex="1" hidden="true"> + <vbox id="progress" align="center"> + <label>&progressPending;</label> + <html:progress max="100"/> + </vbox> + <label id="success" hidden="true">&progressSuccess;</label> + <label id="failure" hidden="true">&progressFailure;</label> + </vbox> +</dialog> +</html:body> +</html> |