summaryrefslogtreecommitdiffstats
path: root/comm/mailnews/addrbook/prefs/content
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--comm/mailnews/addrbook/prefs/content/pref-directory-add.js454
-rw-r--r--comm/mailnews/addrbook/prefs/content/pref-directory-add.xhtml190
-rw-r--r--comm/mailnews/addrbook/prefs/content/pref-editdirectories.js188
-rw-r--r--comm/mailnews/addrbook/prefs/content/pref-editdirectories.xhtml77
4 files changed, 909 insertions, 0 deletions
diff --git a/comm/mailnews/addrbook/prefs/content/pref-directory-add.js b/comm/mailnews/addrbook/prefs/content/pref-directory-add.js
new file mode 100644
index 0000000000..23bee12a0e
--- /dev/null
+++ b/comm/mailnews/addrbook/prefs/content/pref-directory-add.js
@@ -0,0 +1,454 @@
+/* -*- Mode: JavaScript; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+var { MailServices } = ChromeUtils.import(
+ "resource:///modules/MailServices.jsm"
+);
+var { isLegalHostNameOrIP, cleanUpHostName } = ChromeUtils.import(
+ "resource:///modules/hostnameUtils.jsm"
+);
+
+var gCurrentDirectory = null;
+var gReplicationBundle = null;
+var gReplicationService = Cc[
+ "@mozilla.org/addressbook/ldap-replication-service;1"
+].getService(Ci.nsIAbLDAPReplicationService);
+var gReplicationCancelled = false;
+var gProgressText;
+var gProgressMeter;
+var gDownloadInProgress = false;
+
+var kDefaultLDAPPort = 389;
+var kDefaultSecureLDAPPort = 636;
+
+window.addEventListener("DOMContentLoaded", Startup);
+window.addEventListener("unload", onUnload);
+document.addEventListener("dialogaccept", onAccept);
+document.addEventListener("dialogcancel", onCancel);
+
+var ldapOfflineObserver = {
+ observe(subject, topic, state) {
+ // sanity checks
+ if (topic != "network:offline-status-changed") {
+ return;
+ }
+ setDownloadOfflineOnlineState(state == "offline");
+ },
+};
+
+function Startup() {
+ gReplicationBundle = document.getElementById("bundle_replication");
+
+ document.getElementById("download").label =
+ gReplicationBundle.getString("downloadButton");
+ document.getElementById("download").accessKey = gReplicationBundle.getString(
+ "downloadButton.accesskey"
+ );
+
+ if (
+ "arguments" in window &&
+ window.arguments[0] &&
+ window.arguments[0].selectedDirectory
+ ) {
+ gCurrentDirectory = window.arguments[0].selectedDirectory;
+ try {
+ fillSettings();
+ } catch (ex) {
+ dump(
+ "pref-directory-add.js:Startup(): fillSettings() exception: " +
+ ex +
+ "\n"
+ );
+ }
+
+ let oldListName = gCurrentDirectory.dirName;
+ document.title = gReplicationBundle.getFormattedString(
+ "directoryTitleEdit",
+ [oldListName]
+ );
+
+ // Only set up the download button for online/offline status toggling
+ // if the pref isn't locked to disable the button.
+ if (
+ !Services.prefs.prefIsLocked(
+ gCurrentDirectory.dirPrefId + ".disable_button_download"
+ )
+ ) {
+ // Now connect to the offline/online observer
+ Services.obs.addObserver(
+ ldapOfflineObserver,
+ "network:offline-status-changed"
+ );
+
+ // Now set the initial offline/online state and update the state
+ setDownloadOfflineOnlineState(Services.io.offline);
+ }
+ } else {
+ document.title = gReplicationBundle.getString("directoryTitleNew");
+ fillDefaultSettings();
+ // Don't add observer here as it doesn't make any sense.
+ }
+}
+
+function onUnload() {
+ if (
+ "arguments" in window &&
+ window.arguments[0] &&
+ window.arguments[0].selectedDirectory &&
+ !Services.prefs.prefIsLocked(
+ gCurrentDirectory.dirPrefId + ".disable_button_download"
+ )
+ ) {
+ // Remove the observer that we put in on dialog startup
+ Services.obs.removeObserver(
+ ldapOfflineObserver,
+ "network:offline-status-changed"
+ );
+ }
+}
+
+var progressListener = {
+ onStateChange(aWebProgress, aRequest, aStateFlags, aStatus) {
+ if (aStateFlags & Ci.nsIWebProgressListener.STATE_START) {
+ // start the spinning
+ gProgressMeter.removeAttribute("value");
+ gProgressText.value = gReplicationBundle.getString(
+ aStatus ? "replicationStarted" : "changesStarted"
+ );
+ gDownloadInProgress = true;
+ document.getElementById("download").label = gReplicationBundle.getString(
+ "cancelDownloadButton"
+ );
+ document.getElementById("download").accessKey =
+ gReplicationBundle.getString("cancelDownloadButton.accesskey");
+ }
+
+ if (aStateFlags & Ci.nsIWebProgressListener.STATE_STOP) {
+ EndDownload(aStatus);
+ }
+ },
+ onProgressChange(
+ aWebProgress,
+ aRequest,
+ aCurSelfProgress,
+ aMaxSelfProgress,
+ aCurTotalProgress,
+ aMaxTotalProgress
+ ) {
+ gProgressText.value = gReplicationBundle.getFormattedString(
+ "currentCount",
+ [aCurSelfProgress]
+ );
+ },
+ onLocationChange(aWebProgress, aRequest, aLocation, aFlags) {},
+ onStatusChange(aWebProgress, aRequest, aStatus, aMessage) {},
+ onSecurityChange(aWebProgress, aRequest, state) {},
+ onContentBlockingEvent(aWebProgress, aRequest, aEvent) {},
+ QueryInterface: ChromeUtils.generateQI([
+ "nsIWebProgressListener",
+ "nsISupportsWeakReference",
+ ]),
+};
+
+function DownloadNow() {
+ if (!gDownloadInProgress) {
+ gProgressText = document.getElementById("replicationProgressText");
+ gProgressMeter = document.getElementById("replicationProgressMeter");
+
+ gProgressText.hidden = false;
+ gProgressMeter.hidden = false;
+ gReplicationCancelled = false;
+
+ try {
+ if (gCurrentDirectory instanceof Ci.nsIAbLDAPDirectory) {
+ gReplicationService.startReplication(
+ gCurrentDirectory,
+ progressListener
+ );
+ } else {
+ EndDownload(Cr.NS_ERROR_FAILURE);
+ }
+ } catch (ex) {
+ EndDownload(Cr.NS_ERROR_FAILURE);
+ }
+ } else {
+ gReplicationCancelled = true;
+ try {
+ gReplicationService.cancelReplication(gCurrentDirectory);
+ } catch (ex) {
+ // XXX todo
+ // perhaps replication hasn't started yet? This can happen if you hit cancel after attempting to replication when offline
+ dump("unexpected failure while cancelling. ex=" + ex + "\n");
+ }
+ }
+}
+
+function EndDownload(aStatus) {
+ document.getElementById("download").label =
+ gReplicationBundle.getString("downloadButton");
+ document.getElementById("download").accessKey = gReplicationBundle.getString(
+ "downloadButton.accesskey"
+ );
+
+ // stop the spinning
+ gProgressMeter.value = 100;
+ gProgressMeter.hidden = true;
+
+ gDownloadInProgress = false;
+ if (Components.isSuccessCode(aStatus)) {
+ gProgressText.value = gReplicationBundle.getString("replicationSucceeded");
+ } else if (gReplicationCancelled) {
+ gProgressText.value = gReplicationBundle.getString("replicationCancelled");
+ } else {
+ gProgressText.value = gReplicationBundle.getString("replicationFailed");
+ }
+}
+
+// fill the settings panel with the data from the preferences.
+//
+function fillSettings() {
+ document.getElementById("description").value = gCurrentDirectory.dirName;
+
+ if (gCurrentDirectory instanceof Ci.nsIAbLDAPDirectory) {
+ var ldapUrl = gCurrentDirectory.lDAPURL;
+
+ document.getElementById("results").value = gCurrentDirectory.maxHits;
+ document.getElementById("login").value = gCurrentDirectory.authDn;
+ document.getElementById("hostname").value = ldapUrl.host;
+ document.getElementById("basedn").value = ldapUrl.dn;
+ document.getElementById("search").value = ldapUrl.filter;
+
+ var sub = document.getElementById("sub");
+ switch (ldapUrl.scope) {
+ case Ci.nsILDAPURL.SCOPE_ONELEVEL:
+ sub.radioGroup.selectedItem = document.getElementById("one");
+ break;
+ default:
+ sub.radioGroup.selectedItem = sub;
+ break;
+ }
+
+ var sasl = document.getElementById("saslMechanism");
+ switch (gCurrentDirectory.saslMechanism) {
+ case "GSSAPI":
+ sasl.selectedItem = document.getElementById("GSSAPI");
+ break;
+ default:
+ sasl.selectedItem = document.getElementById("Simple");
+ break;
+ }
+
+ var secure = ldapUrl.options & ldapUrl.OPT_SECURE;
+ if (secure) {
+ document.getElementById("secure").setAttribute("checked", "true");
+ }
+
+ if (ldapUrl.port == -1) {
+ document.getElementById("port").value = secure
+ ? kDefaultSecureLDAPPort
+ : kDefaultLDAPPort;
+ } else {
+ document.getElementById("port").value = ldapUrl.port;
+ }
+ }
+
+ // check if any of the preferences for this server are locked.
+ // If they are locked disable them
+ DisableUriFields(gCurrentDirectory.dirPrefId + ".uri");
+ DisableElementIfPrefIsLocked(
+ gCurrentDirectory.dirPrefId + ".description",
+ "description"
+ );
+ DisableElementIfPrefIsLocked(
+ gCurrentDirectory.dirPrefId + ".disable_button_download",
+ "download"
+ );
+ DisableElementIfPrefIsLocked(
+ gCurrentDirectory.dirPrefId + ".maxHits",
+ "results"
+ );
+ DisableElementIfPrefIsLocked(
+ gCurrentDirectory.dirPrefId + ".auth.dn",
+ "login"
+ );
+}
+
+function DisableElementIfPrefIsLocked(aPrefName, aElementId) {
+ if (Services.prefs.prefIsLocked(aPrefName)) {
+ document.getElementById(aElementId).setAttribute("disabled", true);
+ }
+}
+
+// disables all the text fields corresponding to the .uri pref.
+function DisableUriFields(aPrefName) {
+ if (Services.prefs.prefIsLocked(aPrefName)) {
+ let lockedElements = document.querySelectorAll('[disableiflocked="true"]');
+ for (let i = 0; i < lockedElements.length; i++) {
+ lockedElements[i].setAttribute("disabled", "true");
+ }
+ }
+}
+
+function onSecure() {
+ document.getElementById("port").value = document.getElementById("secure")
+ .checked
+ ? kDefaultSecureLDAPPort
+ : kDefaultLDAPPort;
+}
+
+function fillDefaultSettings() {
+ document.getElementById("port").value = kDefaultLDAPPort;
+ var sub = document.getElementById("sub");
+ sub.radioGroup.selectedItem = sub;
+
+ // Disable the download button and add some text indicating why.
+ document.getElementById("download").disabled = true;
+ document.getElementById("downloadWarningMsg").hidden = false;
+ document.getElementById("downloadWarningMsg").textContent = document
+ .getElementById("bundle_addressBook")
+ .getString("abReplicationSaveSettings");
+}
+
+function hasCharacters(number) {
+ var re = /[0-9]/g;
+ var num = number.match(re);
+ if (num && num.length == number.length) {
+ return false;
+ }
+ return true;
+}
+
+function onAccept(event) {
+ try {
+ let description = document.getElementById("description").value.trim();
+ let hostname = cleanUpHostName(document.getElementById("hostname").value);
+ let port = document.getElementById("port").value;
+ let secure = document.getElementById("secure");
+ let results = document.getElementById("results").value;
+ let errorValue = null;
+ let errorArg = null;
+ let saslMechanism = "";
+
+ let findDupeName = function (newName) {
+ // Do not allow an already existing name.
+ for (let ab of MailServices.ab.directories) {
+ if (
+ ab.dirName.toLowerCase() == newName.toLowerCase() &&
+ (!gCurrentDirectory || ab.URI != gCurrentDirectory.URI)
+ ) {
+ return ab.dirName;
+ }
+ }
+ return null;
+ };
+
+ if (!description) {
+ errorValue = "invalidName";
+ } else if ((errorArg = findDupeName(description))) {
+ errorValue = "duplicateNameText";
+ } else if (!isLegalHostNameOrIP(hostname)) {
+ errorValue = "invalidHostname";
+ } else if (port && hasCharacters(port)) {
+ // XXX write isValidDn and call it on the dn string here?
+ errorValue = "invalidPortNumber";
+ } else if (results && hasCharacters(results)) {
+ errorValue = "invalidResults";
+ }
+
+ if (!errorValue) {
+ if (!port) {
+ port = secure.checked ? kDefaultSecureLDAPPort : kDefaultLDAPPort;
+ }
+ if (hostname.includes(":")) {
+ // Wrap IPv6 address in [].
+ hostname = `[${hostname}]`;
+ }
+ let ldapUrl = Services.io
+ .newURI(`${secure.checked ? "ldaps" : "ldap"}://${hostname}:${port}`)
+ .QueryInterface(Ci.nsILDAPURL);
+
+ ldapUrl.dn = document.getElementById("basedn").value;
+ ldapUrl.scope = document.getElementById("one").selected
+ ? Ci.nsILDAPURL.SCOPE_ONELEVEL
+ : Ci.nsILDAPURL.SCOPE_SUBTREE;
+
+ ldapUrl.filter = document.getElementById("search").value;
+ if (document.getElementById("GSSAPI").selected) {
+ saslMechanism = "GSSAPI";
+ }
+
+ // check if we are modifying an existing directory or adding a new directory
+ if (gCurrentDirectory) {
+ gCurrentDirectory.dirName = description;
+ gCurrentDirectory.lDAPURL = ldapUrl;
+ window.opener.gNewServerString = gCurrentDirectory.dirPrefId;
+ } else {
+ // adding a new directory
+ window.opener.gNewServerString = MailServices.ab.newAddressBook(
+ description,
+ ldapUrl.spec,
+ Ci.nsIAbManager.LDAP_DIRECTORY_TYPE
+ );
+ }
+
+ // XXX This is really annoying - both new/modify Address Book don't
+ // give us back the new directory we just created - so go find it from
+ // rdf so we can set a few final things up on it.
+ var targetURI = "moz-abldapdirectory://" + window.opener.gNewServerString;
+ var theDirectory = MailServices.ab
+ .getDirectory(targetURI)
+ .QueryInterface(Ci.nsIAbLDAPDirectory);
+
+ theDirectory.maxHits = results;
+ theDirectory.authDn = document.getElementById("login").value;
+ theDirectory.saslMechanism = saslMechanism;
+
+ window.opener.gNewServer = description;
+ // set window.opener.gUpdate to true so that LDAP Directory Servers
+ // dialog gets updated
+ window.opener.gUpdate = true;
+ window.arguments[0].newDirectoryUID = theDirectory.UID;
+ if ("onNewDirectory" in window.arguments[0]) {
+ window.arguments[0].onNewDirectory(theDirectory);
+ }
+ } else {
+ let addressBookBundle = document.getElementById("bundle_addressBook");
+
+ let errorText;
+ if (errorArg) {
+ errorText = addressBookBundle.getFormattedString(errorValue, [
+ errorArg,
+ ]);
+ } else {
+ errorText = addressBookBundle.getString(errorValue);
+ }
+
+ Services.prompt.alert(window, document.title, errorText);
+ event.preventDefault();
+ return;
+ }
+ } catch (outer) {
+ console.error(
+ "Internal error in pref-directory-add.js:onAccept() " + outer
+ );
+ }
+}
+
+function onCancel() {
+ window.opener.gUpdate = false;
+}
+
+// Sets the download button state for offline or online.
+// This function should only be called for ldap edit dialogs.
+function setDownloadOfflineOnlineState(isOffline) {
+ if (isOffline) {
+ // Disable the download button and add some text indicating why.
+ document.getElementById("downloadWarningMsg").textContent = document
+ .getElementById("bundle_addressBook")
+ .getString("abReplicationOfflineWarning");
+ }
+ document.getElementById("downloadWarningMsg").hidden = !isOffline;
+ document.getElementById("download").disabled = isOffline;
+}
diff --git a/comm/mailnews/addrbook/prefs/content/pref-directory-add.xhtml b/comm/mailnews/addrbook/prefs/content/pref-directory-add.xhtml
new file mode 100644
index 0000000000..ff2e40df1e
--- /dev/null
+++ b/comm/mailnews/addrbook/prefs/content/pref-directory-add.xhtml
@@ -0,0 +1,190 @@
+<?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/messenger.css" type="text/css"?>
+<?xml-stylesheet href="chrome://messenger/skin/shared/grid-layout.css" type="text/css"?>
+<?xml-stylesheet href="chrome://messenger/skin/input-fields.css" type="text/css"?>
+<?xml-stylesheet href="chrome://messenger/skin/themeableDialog.css" type="text/css"?>
+
+<!DOCTYPE html SYSTEM "chrome://messenger/locale/addressbook/pref-directory-add.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"
+ lightweightthemes="true"
+ scrolling="false">
+<head>
+ <title><!-- directoryTitleEdit --></title>
+ <style>
+ #directoryTabPanels radiogroup {
+ margin-inline-start: 4px;
+ }
+ #directoryTabPanels textarea {
+ width: calc(100% - 22px);
+ }
+ #directoryTabPanels menulist {
+ width: calc(100% - 4px);
+ margin-inline-start: 4px;
+ }
+ </style>
+ <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/dialogShadowDom.js"></script>
+ <script defer="defer" src="chrome://messenger/content/addressbook/pref-directory-add.js"></script>
+</head>
+<html:body xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+<dialog id="addDirectory" buttons="accept,cancel" style="width:100vw; min-width:&newDirectoryWidth;">
+ <stringbundle id="bundle_addressBook" src="chrome://messenger/locale/addressbook/addressBook.properties"/>
+ <stringbundle id="bundle_replication" src="chrome://messenger/locale/addressbook/replicationProgress.properties"/>
+
+ <vbox id="editDirectory">
+
+ <tabbox style="margin:5px">
+ <tabs id="directoryTabBox">
+ <tab label="&General.tab;"/>
+ <tab label="&Offline.tab;"/>
+ <tab label="&Advanced.tab;"/>
+ </tabs>
+
+ <tabpanels id="directoryTabPanels" flex="1">
+ <vbox>
+ <div xmlns="http://www.w3.org/1999/xhtml" class="grid-three-column">
+ <div class="flex-items-center">
+ <xul:label id="descriptionLabel" value="&directoryName.label;"
+ accesskey="&directoryName.accesskey;"
+ control="description"/>
+ </div>
+ <div>
+ <input id="description" type="text" class="input-inline"
+ aria-labelledby="descriptionLabel"/>
+ </div>
+ <div></div>
+ <div class="flex-items-center">
+ <xul:label id="hostnameLabel"
+ value="&directoryHostname.label;"
+ accesskey="&directoryHostname.accesskey;"
+ control="hostname"/>
+ </div>
+ <div>
+ <input id="hostname" type="text"
+ class="uri-element input-inline"
+ aria-labelledby="descriptionLabel"
+ disableiflocked="true"/>
+ </div>
+ <div></div>
+ <div class="flex-items-center">
+ <xul:label id="basednLabel"
+ value="&directoryBaseDN.label;"
+ accesskey="&directoryBaseDN.accesskey;"
+ control="basedn"/>
+ </div>
+ <div>
+ <input id="basedn" type="text"
+ class="uri-element input-inline"
+ aria-labelledby="basednLabel"
+ disableiflocked="true"/>
+ </div>
+ <div class="flex-items-center flex-content-center">
+ <xul:button label="&findButton.label;"
+ accesskey="&findButton.accesskey;" disabled="true"/>
+ </div>
+ <div class="flex-items-center">
+ <xul:label id="portLabel" value="&portNumber.label;"
+ accesskey="&portNumber.accesskey;"
+ control="port"/>
+ </div>
+ <div>
+ <input id="port" type="number"
+ class="size5 input-inline"
+ min="1" max="65535"
+ aria-labelledby="portLabel"
+ disableiflocked="true"/>
+ </div>
+ <div></div>
+ <div class="flex-items-center">
+ <xul:label id="loginLabel" value="&directoryLogin.label;"
+ accesskey="&directoryLogin.accesskey;"
+ control="login"/>
+ </div>
+ <div>
+ <input id="login" type="text" class="uri-element input-inline"
+ aria-labelledby="loginLabel"/>
+ </div>
+ <div></div>
+ </div>
+ <separator/>
+ <checkbox id="secure" label="&directorySecure.label;"
+ accesskey="&directorySecure.accesskey;"
+ oncommand="onSecure();" disableiflocked="true"/>
+ </vbox>
+ <vbox>
+ <description>&offlineText.label;</description>
+ <separator/>
+ <hbox>
+ <button id="download" oncommand="DownloadNow();"/>
+ <spacer flex="1"/>
+ </hbox>
+ <description id="downloadWarningMsg" hidden="true" class="error"/>
+ <description id="replicationProgressText" hidden="true"/>
+
+ <html:progress id="replicationProgressMeter" value="0" max="100" hidden="hidden"/>
+ </vbox>
+ <vbox>
+ <div xmlns="http://www.w3.org/1999/xhtml" class="grid-two-column">
+ <div class="flex-items-center">
+ <xul:label id="returnMaxLabel" value="&return.label;"
+ accesskey="&return.accesskey;"
+ control="results"/>
+ </div>
+ <div class="flex-items-center">
+ <input id="results" type="number"
+ class="size5 input-inline"
+ min="1" max="2147483647" value="100"
+ aria-labelledby="returnMaxLabel"/>
+ <xul:label value="&results.label;"/>
+ </div>
+ <div class="flex-items-center">
+ <xul:label value="&scope.label;" control="scope"
+ accesskey="&scope.accesskey;"/>
+ </div>
+ <div>
+ <xul:radiogroup id="scope"
+ orient="horizontal">
+ <xul:radio id="one" value="1" label="&scopeOneLevel.label;"
+ disableiflocked="true" accesskey="&scopeOneLevel.accesskey;"/>
+ <xul:radio id="sub" value="2" label="&scopeSubtree.label;"
+ disableiflocked="true" accesskey="&scopeSubtree.accesskey;"/>
+ </xul:radiogroup>
+ </div>
+ <div class="flex-items-center">
+ <xul:label value="&searchFilter.label;"
+ accesskey="&searchFilter.accesskey;"
+ control="search"/>
+ </div>
+ <div>
+ <textarea id="search" disableiflocked="true"></textarea>
+ </div>
+ <div class="flex-items-center">
+ <xul:label value="&saslMechanism.label;" control="saslMechanism"
+ accesskey="&saslMechanism.accesskey;"/>
+ </div>
+ <div>
+ <xul:menulist id="saslMechanism">
+ <xul:menupopup>
+ <xul:menuitem id="Simple" value="" label="&saslOff.label;"
+ accesskey="&saslOff.accesskey;"/>
+ <xul:menuitem id="GSSAPI" value="GSSAPI" label="&saslGSSAPI.label;"
+ accesskey="&saslGSSAPI.accesskey;"/>
+ </xul:menupopup>
+ </xul:menulist>
+ </div>
+ </div>
+ </vbox>
+ </tabpanels>
+ </tabbox>
+ </vbox>
+</dialog>
+</html:body>
+</html>
diff --git a/comm/mailnews/addrbook/prefs/content/pref-editdirectories.js b/comm/mailnews/addrbook/prefs/content/pref-editdirectories.js
new file mode 100644
index 0000000000..2ba3421c5a
--- /dev/null
+++ b/comm/mailnews/addrbook/prefs/content/pref-editdirectories.js
@@ -0,0 +1,188 @@
+/* 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 ../../../../mail/components/addrbook/content/abCommon.js */
+
+var { MailServices } = ChromeUtils.import(
+ "resource:///modules/MailServices.jsm"
+);
+
+window.addEventListener("DOMContentLoaded", onInitEditDirectories);
+
+// Listener to refresh the list items if something changes. In all these
+// cases we just rebuild the list as it is easier than searching/adding in the
+// correct places an would be an infrequent operation.
+var gAddressBookAbListener = {
+ QueryInterface: ChromeUtils.generateQI([
+ "nsIObserver",
+ "nsISupportsWeakReference",
+ ]),
+
+ init() {
+ for (let topic of [
+ "addrbook-directory-created",
+ "addrbook-directory-updated",
+ "addrbook-directory-deleted",
+ ]) {
+ Services.obs.addObserver(this, topic, true);
+ }
+ },
+
+ observe(subject, topic, data) {
+ subject.QueryInterface(Ci.nsIAbDirectory);
+ fillDirectoryList(subject);
+ },
+};
+
+function onInitEditDirectories() {
+ // If the pref is locked disable the "Add" button
+ if (Services.prefs.prefIsLocked("ldap_2.disable_button_add")) {
+ document.getElementById("addButton").setAttribute("disabled", true);
+ }
+
+ // Fill out the directory list
+ fillDirectoryList();
+
+ // Add a listener so we can update correctly if the list should change
+ gAddressBookAbListener.init();
+}
+
+function fillDirectoryList(aItem = null) {
+ var abList = document.getElementById("directoriesList");
+
+ // Empty out anything in the list
+ while (abList.hasChildNodes()) {
+ abList.lastChild.remove();
+ }
+
+ // Init the address book list
+ let holdingArray = [];
+ for (let ab of MailServices.ab.directories) {
+ if (ab.isRemote) {
+ holdingArray.push(ab);
+ }
+ }
+
+ holdingArray.sort(function (a, b) {
+ return a.dirName.localeCompare(b.dirName);
+ });
+
+ holdingArray.forEach(function (ab) {
+ let item = document.createXULElement("richlistitem");
+ let label = document.createXULElement("label");
+ label.setAttribute("value", ab.dirName);
+ item.appendChild(label);
+ item.setAttribute("value", ab.URI);
+
+ abList.appendChild(item);
+ });
+
+ // Forces the focus back on the list and on the first item.
+ // We also select an edited or recently added item.
+ abList.focus();
+ if (aItem) {
+ abList.selectedIndex = holdingArray.findIndex(d => {
+ return d && d.URI == aItem.URI;
+ });
+ }
+}
+
+function selectDirectory() {
+ var abList = document.getElementById("directoriesList");
+ var editButton = document.getElementById("editButton");
+ var removeButton = document.getElementById("removeButton");
+
+ if (abList && abList.selectedItem) {
+ editButton.removeAttribute("disabled");
+
+ // If the disable delete button pref for the selected directory is set,
+ // disable the delete button for that directory.
+ let ab = MailServices.ab.getDirectory(abList.value);
+ let disable = Services.prefs.getBoolPref(
+ ab.dirPrefId + ".disable_delete",
+ false
+ );
+ if (disable) {
+ removeButton.setAttribute("disabled", true);
+ } else {
+ removeButton.removeAttribute("disabled");
+ }
+ } else {
+ editButton.setAttribute("disabled", true);
+ removeButton.setAttribute("disabled", true);
+ }
+}
+
+function dblClickDirectory(event) {
+ // We only care about left click events.
+ if (event.button != 0) {
+ return;
+ }
+
+ editDirectory();
+}
+
+function addDirectory() {
+ parent.gSubDialog.open(
+ "chrome://messenger/content/addressbook/pref-directory-add.xhtml",
+ { features: "resizable=no" }
+ );
+}
+
+function editDirectory() {
+ var abList = document.getElementById("directoriesList");
+
+ if (abList && abList.selectedItem) {
+ let abURI = abList.value;
+ let ab = MailServices.ab.getDirectory(abURI);
+
+ parent.gSubDialog.open(
+ "chrome://messenger/content/addressbook/pref-directory-add.xhtml",
+ { features: "resizable=no" },
+ { selectedDirectory: ab }
+ );
+ }
+}
+
+async function removeDirectory() {
+ let abList = document.getElementById("directoriesList");
+
+ if (!abList.selectedItem) {
+ return;
+ }
+
+ let directory = GetDirectoryFromURI(abList.value);
+ if (
+ !directory ||
+ ["ldap_2.servers.history", "ldap_2.servers.pab"].includes(
+ directory.dirPrefId
+ )
+ ) {
+ return;
+ }
+
+ let action = "delete-book";
+ if (directory.isMailList) {
+ action = "delete-lists";
+ } else if (
+ [
+ Ci.nsIAbManager.CARDDAV_DIRECTORY_TYPE,
+ Ci.nsIAbManager.LDAP_DIRECTORY_TYPE,
+ ].includes(directory.dirType)
+ ) {
+ action = "remove-remote-book";
+ }
+
+ let [title, message] = await document.l10n.formatValues([
+ { id: `about-addressbook-confirm-${action}-title`, args: { count: 1 } },
+ {
+ id: `about-addressbook-confirm-${action}`,
+ args: { name: directory.dirName, count: 1 },
+ },
+ ]);
+
+ if (Services.prompt.confirm(window, title, message)) {
+ MailServices.ab.deleteAddressBook(directory.URI);
+ }
+}
diff --git a/comm/mailnews/addrbook/prefs/content/pref-editdirectories.xhtml b/comm/mailnews/addrbook/prefs/content/pref-editdirectories.xhtml
new file mode 100644
index 0000000000..381aee3050
--- /dev/null
+++ b/comm/mailnews/addrbook/prefs/content/pref-editdirectories.xhtml
@@ -0,0 +1,77 @@
+<?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/messenger.css" type="text/css"?>
+
+<!DOCTYPE html SYSTEM "chrome://messenger/locale/addressbook/pref-directory.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>&pref.ldap.window.title;</title>
+ <link
+ rel="localization"
+ href="messenger/addressbook/aboutAddressBook.ftl"
+ />
+ <script
+ defer="defer"
+ src="chrome://messenger/content/addressbook/abCommon.js"
+ ></script>
+ <script
+ defer="defer"
+ src="chrome://messenger/content/addressbook/pref-editdirectories.js"
+ ></script>
+ </head>
+ <html:body
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ >
+ <dialog id="editDirectories" buttons="accept">
+ <stringbundle
+ id="bundle_addressBook"
+ src="chrome://messenger/locale/addressbook/addressBook.properties"
+ />
+
+ <label
+ value="&directoriesText.label;"
+ accesskey="&directoriesText.accesskey;"
+ control="directoriesList"
+ />
+ <hbox flex="1">
+ <richlistbox
+ id="directoriesList"
+ flex="1"
+ onselect="selectDirectory();"
+ ondblclick="dblClickDirectory(event);"
+ />
+ <vbox>
+ <button
+ id="addButton"
+ label="&addDirectory.label;"
+ accesskey="&addDirectory.accesskey;"
+ oncommand="addDirectory();"
+ />
+ <button
+ id="editButton"
+ label="&editDirectory.label;"
+ accesskey="&editDirectory.accesskey;"
+ disabled="true"
+ oncommand="editDirectory();"
+ />
+ <button
+ id="removeButton"
+ label="&deleteDirectory.label;"
+ accesskey="&deleteDirectory.accesskey;"
+ disabled="true"
+ oncommand="removeDirectory();"
+ />
+ </vbox>
+ </hbox>
+ </dialog>
+ </html:body>
+</html>