diff options
Diffstat (limited to 'comm/mail/components/im/content/imAccountWizard.js')
-rw-r--r-- | comm/mail/components/im/content/imAccountWizard.js | 526 |
1 files changed, 526 insertions, 0 deletions
diff --git a/comm/mail/components/im/content/imAccountWizard.js b/comm/mail/components/im/content/imAccountWizard.js new file mode 100644 index 0000000000..128412aa5b --- /dev/null +++ b/comm/mail/components/im/content/imAccountWizard.js @@ -0,0 +1,526 @@ +/* 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/. */ + +// chat/content/imAccountOptionsHelper.js +/* globals accountOptionsHelper */ + +var { IMServices } = ChromeUtils.importESModule( + "resource:///modules/IMServices.sys.mjs" +); +var { MailServices } = ChromeUtils.import( + "resource:///modules/MailServices.jsm" +); +var { ChatIcons } = ChromeUtils.importESModule( + "resource:///modules/chatIcons.sys.mjs" +); + +var PREF_EXTENSIONS_GETMOREPROTOCOLSURL = "extensions.getMoreProtocolsURL"; + +var accountWizard = { + onload() { + document + .querySelector("wizard") + .addEventListener("wizardfinish", this.createAccount.bind(this)); + let accountProtocolPage = document.getElementById("accountprotocol"); + accountProtocolPage.addEventListener( + "pageadvanced", + this.selectProtocol.bind(this) + ); + let accountUsernamePage = document.getElementById("accountusername"); + accountUsernamePage.addEventListener( + "pageshow", + this.showUsernamePage.bind(this) + ); + accountUsernamePage.addEventListener( + "pagehide", + this.hideUsernamePage.bind(this) + ); + let accountAdvancedPage = document.getElementById("accountadvanced"); + accountAdvancedPage.addEventListener( + "pageshow", + this.showAdvanced.bind(this) + ); + let accountSummaryPage = document.getElementById("accountsummary"); + accountSummaryPage.addEventListener( + "pageshow", + this.showSummary.bind(this) + ); + + // Ensure the im core is initialized before we get a list of protocols. + IMServices.core.init(); + + accountWizard.setGetMoreProtocols(); + + var protoList = document.getElementById("protolist"); + var protos = IMServices.core.getProtocols(); + protos.sort((a, b) => { + if (a.name < b.name) { + return -1; + } + return a.name > b.name ? 1 : 0; + }); + protos.forEach(function (proto) { + let image = document.createElement("img"); + image.setAttribute("src", ChatIcons.getProtocolIconURI(proto)); + image.setAttribute("alt", ""); + image.classList.add("protoIcon"); + + let label = document.createXULElement("label"); + label.setAttribute("value", proto.name); + + let item = document.createXULElement("richlistitem"); + item.setAttribute("value", proto.id); + item.appendChild(image); + item.appendChild(label); + protoList.appendChild(item); + }); + + // there is a strange selection bug without this timeout + setTimeout(function () { + protoList.selectedIndex = 0; + }, 0); + + Services.obs.addObserver(this, "prpl-quit"); + window.addEventListener("unload", this.unload); + }, + unload() { + Services.obs.removeObserver(accountWizard, "prpl-quit"); + }, + observe(aObject, aTopic, aData) { + if (aTopic == "prpl-quit") { + // libpurple is being uninitialized. We can't create any new + // account so keeping this wizard open would be pointless, close it. + window.close(); + } + }, + + /** + * Builds the full username from the username boxes. + * + * @returns {string} assembled username + */ + getUsername() { + let usernameBoxIndex = 0; + if (this.proto.usernamePrefix) { + usernameBoxIndex = 1; + } + // If the first username input is empty, make sure we return an empty + // string so that it blocks the 'next' button of the wizard. + if (!this.userNameBoxes[usernameBoxIndex].value) { + return ""; + } + + return this.userNameBoxes.reduce((prev, elt) => prev + elt.value, ""); + }, + + /** + * Check that the username fields generate a new username, and if it is valid + * allow advancing the wizard. + */ + checkUsername() { + var wizard = document.querySelector("wizard"); + var name = accountWizard.getUsername(); + var duplicateWarning = document.getElementById("duplicateAccount"); + if (!name) { + wizard.canAdvance = false; + duplicateWarning.hidden = true; + return; + } + + var exists = accountWizard.proto.accountExists(name); + wizard.canAdvance = !exists; + duplicateWarning.hidden = !exists; + }, + + /** + * Takes the value of the primary username field and splits it if the value + * matches the split field syntax. + */ + splitUsername() { + let usernameBoxIndex = 0; + if (this.proto.usernamePrefix) { + usernameBoxIndex = 1; + } + let username = this.userNameBoxes[usernameBoxIndex].value; + let splitValues = this.proto.splitUsername(username); + if (!splitValues.length) { + return; + } + for (const box of this.userNameBoxes) { + if (Element.isInstance(box)) { + box.value = splitValues.shift(); + } + } + this.checkUsername(); + }, + + selectProtocol() { + var protoList = document.getElementById("protolist"); + var id = protoList.selectedItem.value; + this.proto = IMServices.core.getProtocolById(id); + }, + + /** + * Create a new input field for receiving a username. + * + * @param {string} aName - The id for the input. + * @param {string} aLabel - The text for the username label. + * @param {Element} grid - A container with a two column grid display to + * append the new elements to. + * @param {string} [aDefaultValue] - The initial value for the username. + * + * @returns {HTMLInputElement} - The newly created username input. + */ + insertUsernameField(aName, aLabel, grid, aDefaultValue) { + var label = document.createXULElement("label"); + label.setAttribute("value", aLabel); + label.setAttribute("control", aName); + label.setAttribute("id", aName + "-label"); + label.classList.add("label-inline"); + grid.appendChild(label); + + var input = document.createElementNS( + "http://www.w3.org/1999/xhtml", + "input" + ); + input.setAttribute("id", aName); + input.classList.add("input-inline"); + if (aDefaultValue) { + input.setAttribute("value", aDefaultValue); + } + input.addEventListener("input", event => { + this.checkUsername(); + }); + // Only add the split logic to the first input field + if (!this.userNameBoxes) { + input.addEventListener("blur", event => { + this.splitUsername(); + }); + } + grid.appendChild(input); + + return input; + }, + + /** + * Builds the username input boxes from the username split defined by the + * protocol. + */ + showUsernamePage() { + var proto = this.proto.id; + if ("userNameBoxes" in this && this.userNameProto == proto) { + this.checkUsername(); + return; + } + + var bundle = document.getElementById("accountsBundle"); + var usernameInfo; + var emptyText = this.proto.usernameEmptyText; + if (emptyText) { + usernameInfo = bundle.getFormattedString( + "accountUsernameInfoWithDescription", + [emptyText, this.proto.name] + ); + } else { + usernameInfo = bundle.getFormattedString("accountUsernameInfo", [ + this.proto.name, + ]); + } + document.getElementById("usernameInfo").textContent = usernameInfo; + + var grid = document.getElementById("userNameBox"); + // remove anything that may be there for another protocol + while (grid.hasChildNodes()) { + grid.lastChild.remove(); + } + this.userNameBoxes = undefined; + + var splits = this.proto.getUsernameSplit(); + + var label = bundle.getString("accountUsername"); + this.userNameBoxes = [this.insertUsernameField("name", label, grid)]; + this.userNameBoxes[0].emptyText = emptyText; + let usernameBoxIndex = 0; + + if (this.proto.usernamePrefix) { + this.userNameBoxes.unshift({ value: this.proto.usernamePrefix }); + usernameBoxIndex = 1; + } + + for (let i = 0; i < splits.length; ++i) { + this.userNameBoxes.push({ value: splits[i].separator }); + label = bundle.getFormattedString("accountColon", [splits[i].label]); + let defaultVal = splits[i].defaultValue; + this.userNameBoxes.push( + this.insertUsernameField("username-split-" + i, label, grid, defaultVal) + ); + } + this.userNameBoxes[usernameBoxIndex].focus(); + this.userNameProto = proto; + this.checkUsername(); + }, + + hideUsernamePage() { + document.querySelector("wizard").canAdvance = true; + var next = "account" + (this.proto.noPassword ? "advanced" : "password"); + document.getElementById("accountusername").next = next; + }, + + showAdvanced() { + // ensure we don't destroy user data if it's not necessary + var id = this.proto.id; + if ("protoSpecOptId" in this && this.protoSpecOptId == id) { + return; + } + this.protoSpecOptId = id; + + this.populateProtoSpecificBox(); + + // Make sure the protocol specific options and wizard buttons are visible. + let wizard = document.querySelector("wizard"); + if (wizard.scrollHeight > window.innerHeight) { + window.resizeBy(0, wizard.scrollHeight - window.innerHeight); + } + + let alias = document.getElementById("alias"); + alias.focus(); + }, + + populateProtoSpecificBox() { + let haveOptions = accountOptionsHelper.addOptions( + this.proto.id + "-", + this.proto.getOptions() + ); + document.getElementById("protoSpecificGroupbox").hidden = !haveOptions; + if (haveOptions) { + var bundle = document.getElementById("accountsBundle"); + document.getElementById("protoSpecificCaption").textContent = + bundle.getFormattedString("protoOptions", [this.proto.name]); + } + }, + + /** + * Create new summary field and value elements. + * + * @param {string} aLabel - The name of the field being summarised. + * @param {string} aValue - The value of the field being summarised. + * @param {Element} grid - A container with a two column grid display to + * append the new elements to. + */ + createSummaryRow(aLabel, aValue, grid) { + var label = document.createXULElement("label"); + label.classList.add("header", "label-inline"); + if (aLabel.length > 20) { + aLabel = aLabel.substring(0, 20); + aLabel += "…"; + } + + label.setAttribute("value", aLabel); + grid.appendChild(label); + + var input = document.createElementNS( + "http://www.w3.org/1999/xhtml", + "input" + ); + input.setAttribute("value", aValue); + input.classList.add("plain", "input-inline"); + input.setAttribute("readonly", true); + grid.appendChild(input); + }, + + showSummary() { + var rows = document.getElementById("summaryRows"); + var bundle = document.getElementById("accountsBundle"); + while (rows.hasChildNodes()) { + rows.lastChild.remove(); + } + + var label = document.getElementById("protoLabel").value; + this.createSummaryRow(label, this.proto.name, rows); + this.username = this.getUsername(); + label = bundle.getString("accountUsername"); + this.createSummaryRow(label, this.username, rows); + if (!this.proto.noPassword) { + this.password = this.getValue("password"); + if (this.password) { + label = document.getElementById("passwordLabel").value; + var pass = ""; + for (let i = 0; i < this.password.length; ++i) { + pass += "*"; + } + this.createSummaryRow(label, pass, rows); + } + } + this.alias = this.getValue("alias"); + if (this.alias) { + label = document.getElementById("aliasLabel").value; + this.createSummaryRow(label, this.alias, rows); + } + + var id = this.proto.id; + this.prefs = []; + for (let opt of this.proto.getOptions()) { + let name = opt.name; + let eltName = id + "-" + name; + let val = this.getValue(eltName); + // The value will be undefined if the proto specific groupbox has never been opened + if (val === undefined) { + continue; + } + switch (opt.type) { + case Ci.prplIPref.typeBool: + if (val != opt.getBool()) { + this.prefs.push({ opt, name, value: !!val }); + } + break; + case Ci.prplIPref.typeInt: + if (val != opt.getInt()) { + this.prefs.push({ opt, name, value: val }); + } + break; + case Ci.prplIPref.typeString: + if (val != opt.getString()) { + this.prefs.push({ opt, name, value: val }); + } + break; + case Ci.prplIPref.typeList: + if (val != opt.getListDefault()) { + this.prefs.push({ opt, name, value: val }); + } + break; + default: + throw new Error("unknown preference type " + opt.type); + } + } + + for (let i = 0; i < this.prefs.length; ++i) { + let opt = this.prefs[i]; + let label = bundle.getFormattedString("accountColon", [opt.opt.label]); + this.createSummaryRow(label, opt.value, rows); + } + }, + + createAccount() { + var acc = IMServices.accounts.createAccount(this.username, this.proto.id); + if (!this.proto.noPassword && this.password) { + acc.password = this.password; + } + if (this.alias) { + acc.alias = this.alias; + } + + for (let i = 0; i < this.prefs.length; ++i) { + let option = this.prefs[i]; + let opt = option.opt; + switch (opt.type) { + case Ci.prplIPref.typeBool: + acc.setBool(option.name, option.value); + break; + case Ci.prplIPref.typeInt: + acc.setInt(option.name, option.value); + break; + case Ci.prplIPref.typeString: + case Ci.prplIPref.typeList: + acc.setString(option.name, option.value); + break; + default: + throw new Error("unknown type"); + } + } + var autologin = this.getValue("connectNow"); + acc.autoLogin = autologin; + + acc.save(); + + try { + if (autologin) { + acc.connect(); + } + } catch (e) { + // If the connection fails (for example if we are currently in + // offline mode), we still want to close the account wizard + } + + if (window.opener) { + var am = window.opener.gAccountManager; + if (am) { + am.selectAccount(acc.id); + } + } + + var inServer = MailServices.accounts.createIncomingServer( + this.username, + this.proto.id, // hostname + "im" + ); + inServer.wrappedJSObject.imAccount = acc; + + var account = MailServices.accounts.createAccount(); + // Avoid new folder notifications. + inServer.valid = false; + account.incomingServer = inServer; + inServer.valid = true; + MailServices.accounts.notifyServerLoaded(inServer); + + return true; + }, + + getValue(aId) { + var elt = document.getElementById(aId); + if ("selectedItem" in elt) { + return elt.selectedItem.value; + } + // Strangely various input types also have a "checked" property defined, + // so we check for the expected elements explicitly. + if ( + ((elt.localName == "input" && elt.getAttribute("type") == "checkbox") || + elt.localName == "checkbox") && + "checked" in elt + ) { + return elt.checked; + } + if ("value" in elt) { + return elt.value; + } + // If the groupbox has never been opened, the binding isn't attached + // so the attributes don't exist. The calling code in showSummary + // has a special handling of the undefined value for this case. + return undefined; + }, + + *getIter(aEnumerator) { + for (let iter of aEnumerator) { + yield iter; + } + }, + + /* Check for correctness and set URL for the "Get more protocols..."-link + * Stripped down code from preferences/themes.js + */ + setGetMoreProtocols() { + let prefURL = PREF_EXTENSIONS_GETMOREPROTOCOLSURL; + var getMore = document.getElementById("getMoreProtocols"); + var showGetMore = false; + const nsIPrefBranch = Ci.nsIPrefBranch; + + if (Services.prefs.getPrefType(prefURL) != nsIPrefBranch.PREF_INVALID) { + try { + var getMoreURL = Services.urlFormatter.formatURLPref(prefURL); + getMore.setAttribute("getMoreURL", getMoreURL); + showGetMore = getMoreURL != "about:blank"; + } catch (e) {} + } + getMore.hidden = !showGetMore; + }, + + openURL(aURL) { + Cc["@mozilla.org/uriloader/external-protocol-service;1"] + .getService(Ci.nsIExternalProtocolService) + .loadURI(Services.io.newURI(aURL)); + }, +}; + +window.addEventListener("load", event => { + accountWizard.onload(); +}); |