summaryrefslogtreecommitdiffstats
path: root/comm/mail/components/accountcreation/views
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--comm/mail/components/accountcreation/views/container.mjs50
-rw-r--r--comm/mail/components/accountcreation/views/email.mjs185
-rw-r--r--comm/mail/components/accountcreation/views/start.mjs163
3 files changed, 398 insertions, 0 deletions
diff --git a/comm/mail/components/accountcreation/views/container.mjs b/comm/mail/components/accountcreation/views/container.mjs
new file mode 100644
index 0000000000..3bf8d9b4dc
--- /dev/null
+++ b/comm/mail/components/accountcreation/views/container.mjs
@@ -0,0 +1,50 @@
+/* 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/. */
+
+/**
+ * Custom Element containing the main account hub dialog. Used to append the
+ * needed CSS files to the shadowDom to prevent style leakage.
+ * NOTE: This could directly extend an HTMLDialogElement if it had a shadowRoot.
+ */
+class AccountHubContainer extends HTMLElement {
+ /** @type {HTMLDialogElement} */
+ modal;
+
+ /** @type {DOMLocalization} */
+ l10n;
+
+ connectedCallback() {
+ if (this.shadowRoot) {
+ // Already connected, no need to run it again.
+ return;
+ }
+
+ const shadowRoot = this.attachShadow({ mode: "open" });
+
+ // Load styles in the shadowRoot so we don't leak it.
+ let style = document.createElement("link");
+ style.rel = "stylesheet";
+ style.href = "chrome://messenger/skin/accountHub.css";
+ shadowRoot.appendChild(style);
+
+ let template = document.getElementById("accountHubDialog");
+ let clonedNode = template.content.cloneNode(true);
+ shadowRoot.appendChild(clonedNode);
+ this.modal = shadowRoot.querySelector("dialog");
+
+ // We need to create an internal DOM localization in order to let fluent
+ // see the IDs inside our shadowRoot.
+ this.l10n = new DOMLocalization([
+ "branding/brand.ftl",
+ "messenger/accountcreation/accountHub.ftl",
+ "messenger/accountcreation/accountSetup.ftl",
+ ]);
+ this.l10n.connectRoot(shadowRoot);
+ }
+
+ disconnectedCallback() {
+ this.l10n.disconnectRoot(this.shadowRoot);
+ }
+}
+customElements.define("account-hub-container", AccountHubContainer);
diff --git a/comm/mail/components/accountcreation/views/email.mjs b/comm/mail/components/accountcreation/views/email.mjs
new file mode 100644
index 0000000000..f5a4d628b7
--- /dev/null
+++ b/comm/mail/components/accountcreation/views/email.mjs
@@ -0,0 +1,185 @@
+/* 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/. */
+
+class AccountHubEmail extends HTMLElement {
+ /**
+ * The email setup form.
+ *
+ * @type {HTMLFormElement}
+ */
+ #form;
+
+ /**
+ * The account name field.
+ *
+ * @type {HTMLInputElement}
+ */
+ #realName;
+
+ /**
+ * The email field.
+ *
+ * @type {HTMLInputElement}
+ */
+ #email;
+
+ /**
+ * The password field.
+ *
+ * @type {HTMLInputElement}
+ */
+ #password;
+
+ /**
+ * The password visibility button.
+ *
+ * @type {HTMLButtonElement}
+ */
+ #passwordToggleButton;
+
+ /**
+ * The submit form button.
+ *
+ * @type {HTMLButtonElement}
+ */
+ #continueButton;
+
+ /**
+ * The domain name extrapolated from the email address.
+ *
+ * @type {string}
+ */
+ #domain = "";
+
+ connectedCallback() {
+ if (this.hasConnected) {
+ return;
+ }
+ this.hasConnected = true;
+
+ this.classList.add("account-hub-view");
+
+ let template = document.getElementById("accountHubEmailSetup");
+ this.appendChild(template.content.cloneNode(true));
+
+ this.#form = this.querySelector("form");
+ this.#realName = this.querySelector("#realName");
+ this.#email = this.querySelector("#email");
+ this.#password = this.querySelector("#password");
+ this.#passwordToggleButton = this.querySelector("#passwordToggleButton");
+ this.#continueButton = this.querySelector("#emailContinueButton");
+
+ this.initUI();
+
+ this.setupEventListeners();
+ }
+
+ /**
+ * Initialize the UI of the email setup flow.
+ */
+ initUI() {
+ // Populate the account name if we can get some user info.
+ if ("@mozilla.org/userinfo;1" in Cc) {
+ let userInfo = Cc["@mozilla.org/userinfo;1"].getService(Ci.nsIUserInfo);
+ this.#realName.value = userInfo.fullname;
+ }
+
+ this.#realName.focus();
+ }
+
+ /**
+ * Set up the event listeners for this workflow only once.
+ */
+ setupEventListeners() {
+ this.#form.addEventListener("submit", event => {
+ event.preventDefault();
+ event.stopPropagation();
+ console.log("submit");
+ });
+
+ this.#realName.addEventListener("input", () => this.#checkValidForm());
+ this.#email.addEventListener("input", () => this.#checkValidForm());
+ this.#password.addEventListener("input", () => this.#onPasswordInput());
+
+ this.#passwordToggleButton.addEventListener("click", event => {
+ this.#togglePasswordInput(
+ event.target.getAttribute("aria-pressed") === "false"
+ );
+ });
+
+ // Set the Cancel/Back button.
+ this.querySelector("#emailGoBackButton").addEventListener("click", () => {
+ // If in first view, go back to start, otherwise go back in the flow.
+ this.dispatchEvent(
+ new CustomEvent("open-view", {
+ bubbles: true,
+ composed: true,
+ detail: { type: "START" },
+ })
+ );
+ });
+ }
+
+ /**
+ * Check whether the user entered the minimum amount of information needed to
+ * leave the first view and is allowed to proceed to the detection step.
+ */
+ #checkValidForm() {
+ const isValidForm =
+ this.#email.checkValidity() && this.#realName.checkValidity();
+ this.#domain = isValidForm
+ ? this.#email.value.split("@")[1].toLowerCase()
+ : "";
+
+ this.#continueButton.disabled = !isValidForm;
+ }
+
+ /**
+ * Handle the password visibility toggle on password input.
+ */
+ #onPasswordInput() {
+ if (!this.#password.value) {
+ this.#togglePasswordInput(false);
+ }
+ }
+
+ /**
+ * Toggle the password field type between `password` and `text` to allow users
+ * reading their typed password.
+ *
+ * @param {boolean} show - If the password field should become a text field.
+ */
+ #togglePasswordInput(show) {
+ this.#password.type = show ? "text" : "password";
+ this.#passwordToggleButton.setAttribute("aria-pressed", show.toString());
+ document.l10n.setAttributes(
+ this.#passwordToggleButton,
+ show
+ ? "account-setup-password-toggle-hide"
+ : "account-setup-password-toggle-show"
+ );
+ }
+
+ /**
+ * Check if any operation is currently in process and return true only if we
+ * can leave this view.
+ *
+ * @returns {boolean} - If the account hub can remove this view.
+ */
+ reset() {
+ // TODO
+ // Check for:
+ // - Non-abortable operations (autoconfig, email account setup, etc)
+
+ this.#form.reset();
+ this.#togglePasswordInput(false);
+ // TODO
+ // Before resetting we need to:
+ // - Clean up the fields.
+ // - Reset the autoconfig (cached server info).
+ // - Reset the view to the initial screen.
+ return true;
+ }
+}
+customElements.define("account-hub-email", AccountHubEmail);
diff --git a/comm/mail/components/accountcreation/views/start.mjs b/comm/mail/components/accountcreation/views/start.mjs
new file mode 100644
index 0000000000..66487cc928
--- /dev/null
+++ b/comm/mail/components/accountcreation/views/start.mjs
@@ -0,0 +1,163 @@
+/* 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/. */
+
+/* global gSync */
+
+const { AppConstants } = ChromeUtils.importESModule(
+ "resource://gre/modules/AppConstants.sys.mjs"
+);
+const { MailServices } = ChromeUtils.import(
+ "resource:///modules/MailServices.jsm"
+);
+const { UIState } = ChromeUtils.importESModule(
+ "resource://services-sync/UIState.sys.mjs"
+);
+
+class AccountHubStart extends HTMLElement {
+ #accounts = [
+ {
+ id: "email",
+ l10n: "account-hub-email-setup-button",
+ type: "MAIL",
+ },
+ {
+ id: "calendar",
+ l10n: "account-hub-calendar-setup-button",
+ type: "CALENDAR",
+ },
+ {
+ id: "addressBook",
+ l10n: "account-hub-address-book-setup-button",
+ type: "ADDRESS_BOOK",
+ },
+ {
+ id: "chat",
+ l10n: "account-hub-chat-setup-button",
+ type: "CHAT",
+ },
+ {
+ id: "feed",
+ l10n: "account-hub-feed-setup-button",
+ type: "FEED",
+ },
+ {
+ id: "newsgroup",
+ l10n: "account-hub-newsgroup-setup-button",
+ type: "NNTP",
+ },
+ // TODO: Import/Export of profiles is kinda broken so we don't want to
+ // expose it so much for now.
+ // {
+ // id: "import",
+ // l10n: "account-hub-import-setup-button",
+ // type: "IMPORT",
+ // },
+ ];
+
+ connectedCallback() {
+ if (this.hasConnected) {
+ return;
+ }
+ this.hasConnected = true;
+
+ this.classList.add("account-hub-view");
+
+ let template = document.getElementById("accountHubStart");
+ this.appendChild(template.content.cloneNode(true));
+
+ this.initUI();
+
+ this.setupAccountFlows();
+ }
+
+ /**
+ * Update the UI to reflect reality whenever this view is triggered.
+ */
+ initUI() {
+ const hasAccounts = MailServices.accounts.accounts.length;
+ this.querySelector("#welcomeHeader").hidden = hasAccounts;
+ this.querySelector("#defaultHeader").hidden = !hasAccounts;
+
+ if (AppConstants.NIGHTLY_BUILD) {
+ this.updateFxAButton();
+ }
+
+ // Hide the release notes link for nightly builds since we don't have any.
+ if (AppConstants.NIGHTLY_BUILD) {
+ this.querySelector("#hubReleaseNotes").closest("li").hidden = true;
+ return;
+ }
+
+ if (
+ Services.prefs.getPrefType("app.releaseNotesURL") !=
+ Services.prefs.PREF_INVALID
+ ) {
+ let relNotesURL = Services.urlFormatter.formatURLPref(
+ "app.releaseNotesURL"
+ );
+ if (relNotesURL != "about:blank") {
+ this.querySelector("#hubReleaseNotes").href = relNotesURL;
+ return;
+ }
+ // Hide the release notes link if we don't have a URL to add.
+ this.querySelector("#hubReleaseNotes").closest("li").hidden = true;
+ }
+ }
+
+ /**
+ * Populate the main container fo the start view with all the available
+ * account creation flows.
+ */
+ setupAccountFlows() {
+ const fragment = new DocumentFragment();
+ for (const account of this.#accounts) {
+ const button = document.createElement("button");
+ button.id = `${account.id}Button`;
+ button.classList.add("button", "button-account");
+ document.l10n.setAttributes(button, account.l10n);
+ button.addEventListener("click", () => {
+ this.dispatchEvent(
+ new CustomEvent("open-view", {
+ bubbles: true,
+ composed: true,
+ detail: {
+ type: account.type,
+ },
+ })
+ );
+ });
+ fragment.append(button);
+ }
+ this.querySelector(".hub-body-grid").replaceChildren(fragment);
+
+ if (AppConstants.NIGHTLY_BUILD) {
+ this.querySelector("#hubSyncButton").addEventListener("click", () => {
+ // FIXME: Open this in a dialog or browser inside the modal, or find a
+ // way to close the account hub without an account and open it again in
+ // case the FxA login fails to set up accounts.
+ gSync.initFxA();
+ });
+ }
+ }
+
+ /**
+ * Set up the Firefox Sync button.
+ */
+ updateFxAButton() {
+ const state = UIState.get();
+ this.querySelector("#hubSyncButton").hidden =
+ state.status == UIState.STATUS_SIGNED_IN;
+ }
+
+ /**
+ * The start view doesn't have any abortable operation that needs to be
+ * checked, so we always return true.
+ *
+ * @returns {boolean} - Always true.
+ */
+ reset() {
+ return true;
+ }
+}
+customElements.define("account-hub-start", AccountHubStart);