Revision control
Copy as Markdown
Other Tools
/* 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
/**
* Create a circular avatar representing the initial letter of the user or
* showing an image in case we're able to fetch a picture associated with the
* provided email address.
* This custom element accepts both an nsIABCard and a string depending if
* we're trying to generate an avatar for existing contacts or a new recipient
* currently not saved in our address book.
*
* @tagname contact-avatar
*/
class ContactAvatar extends HTMLElement {
/** @type {HTMLSpanElement} */
#letters;
/** @type {HTMLImageElement} */
#image;
connectedCallback() {
if (this.shadowRoot) {
return;
}
const shadowRoot = this.attachShadow({ mode: "open" });
// Load styles in the shadowRoot so we don't leak it.
const style = document.createElement("link");
style.rel = "stylesheet";
shadowRoot.appendChild(style);
// Connect fluent strings.
window.MozXULElement?.insertFTLIfNeeded("messenger/contact-avatar.ftl");
document.l10n.connectRoot(shadowRoot);
this.#letters = document.createElement("span");
this.#letters.ariaHidden = true;
this.#letters.hidden = true;
this.#image = new Image();
this.#image.src = "";
this.#image.alt = "";
this.#image.hidden = true;
shadowRoot.append(this.#letters, this.#image);
}
disconnectedCallback() {
document.l10n.disconnectRoot(this.shadowRoot);
}
/**
* Update the visualization of the contact avatar based on which data we get.
*
* @param {object} [options={}] - Options.
* @param {?nsIAbCard} [options.card] - The address book card.
* @param {string} [options.recipient=""] - The recipient name or email if not
* currently available in the address book.
*/
setData({ card, recipient = "" } = {}) {
if (!this.shadowRoot) {
console.error("Trying to set data too early!");
return;
}
// Always start on a clean state.
this.#letters.textContent = "";
this.#letters.hidden = true;
this.#image.src = "";
this.#image.removeAttribute("data-l10n-id");
this.removeAttribute("class");
if (card?.isMailList) {
this.#image.hidden = false;
this.classList.add("is-mail-list");
return;
}
const photoURL = card?.photoURL;
if (photoURL) {
this.#image.src = photoURL;
this.#image.hidden = false;
document.l10n.setAttributes(this.#image, "avatar-picture-alt-text", {
address: card.primaryEmail,
});
return;
}
// If we reached this point it means we don't have an available picture and
// we should fill up the avatar with placeholder text.
// We temporarily check for `card.name` and `card.email` because our mail
// list implementation doesn't properly use nsIABCard but a custom subset of
// it. We will update this later.
this.#letters.textContent =
Array.from(
(
card?.displayName ||
card?.primaryEmail ||
card?.name ||
card?.email ||
recipient
)
?.normalize()
.replaceAll(/[^\p{Letter}\p{Nd}]+/gu, "")
)[0]?.toUpperCase() || "";
this.#letters.hidden = false;
}
}
customElements.define("contact-avatar", ContactAvatar);