summaryrefslogtreecommitdiffstats
path: root/comm/mail/components/im/content/chat-contact.js
diff options
context:
space:
mode:
Diffstat (limited to 'comm/mail/components/im/content/chat-contact.js')
-rw-r--r--comm/mail/components/im/content/chat-contact.js282
1 files changed, 282 insertions, 0 deletions
diff --git a/comm/mail/components/im/content/chat-contact.js b/comm/mail/components/im/content/chat-contact.js
new file mode 100644
index 0000000000..d3e9baf974
--- /dev/null
+++ b/comm/mail/components/im/content/chat-contact.js
@@ -0,0 +1,282 @@
+/* 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/. */
+
+"use strict";
+
+/* global MozXULElement, MozElements, Status, chatHandler */
+
+// Wrap in a block to prevent leaking to window scope.
+{
+ const { IMServices } = ChromeUtils.importESModule(
+ "resource:///modules/IMServices.sys.mjs"
+ );
+ const { ChatIcons } = ChromeUtils.importESModule(
+ "resource:///modules/chatIcons.sys.mjs"
+ );
+
+ /**
+ * The MozChatContactRichlistitem widget displays contact information about user under
+ * chat-groups, online contacts and offline contacts: i.e. icon and username.
+ * On double clicking the element, it gets moved into the conversations.
+ *
+ * @augments {MozElements.MozRichlistitem}
+ */
+ class MozChatContactRichlistitem extends MozElements.MozRichlistitem {
+ static get inheritedAttributes() {
+ return {
+ ".box-line": "selected",
+ ".contactDisplayName": "value=displayname",
+ ".contactDisplayNameInput": "value=displayname",
+ ".contactStatusText": "value=statusTextWithDash",
+ };
+ }
+
+ static get markup() {
+ return `
+ <vbox class="box-line"></vbox>
+ <stack class="prplBuddyIcon">
+ <html:img class="protoIcon" alt="" />
+ <html:img class="smallStatusIcon" />
+ </stack>
+ <hbox flex="1" class="contact-hbox">
+ <stack>
+ <label crop="end"
+ class="contactDisplayName blistDisplayName">
+ </label>
+ <html:input type="text"
+ class="contactDisplayNameInput"
+ hidden="hidden"/>
+ </stack>
+ <label crop="end"
+ style="flex: 100000 100000;"
+ class="contactStatusText">
+ </label>
+ <button class="startChatBubble"
+ tooltiptext="&openConversationButton.tooltip;">
+ </button>
+ </hbox>
+ `;
+ }
+
+ static get entities() {
+ return ["chrome://messenger/locale/chat.dtd"];
+ }
+
+ connectedCallback() {
+ if (this.delayConnectedCallback() || this.hasChildNodes()) {
+ return;
+ }
+
+ this.setAttribute("is", "chat-contact-richlistitem");
+
+ this.addEventListener("blur", event => {
+ if (!this.hasAttribute("aliasing")) {
+ return;
+ }
+
+ if (Services.focus.activeWindow == document.defaultView) {
+ this.finishAliasing(true);
+ }
+ });
+
+ this.addEventListener("mousedown", event => {
+ if (
+ !this.hasAttribute("aliasing") &&
+ this.canOpenConversation() &&
+ event.target.classList.contains("startChatBubble")
+ ) {
+ this.openConversation();
+ event.preventDefault();
+ }
+ });
+
+ this.addEventListener("click", event => {
+ if (
+ !this.hasAttribute("aliasing") &&
+ this.canOpenConversation() &&
+ event.detail == 2
+ ) {
+ this.openConversation();
+ }
+ });
+
+ this.parentNode.addEventListener("mousedown", event => {
+ event.preventDefault();
+ });
+
+ // @implements {nsIObserver}
+ this.observer = {
+ QueryInterface: ChromeUtils.generateQI(["nsIObserver"]),
+ observe: function (subject, topic, data) {
+ if (
+ topic == "contact-preferred-buddy-changed" ||
+ topic == "contact-display-name-changed" ||
+ topic == "contact-status-changed"
+ ) {
+ this.update();
+ }
+ if (
+ topic == "contact-availability-changed" ||
+ topic == "contact-display-name-changed"
+ ) {
+ this.group.updateContactPosition(subject);
+ }
+ }.bind(this),
+ };
+
+ this.appendChild(this.constructor.fragment);
+
+ this.initializeAttributeInheritance();
+ }
+
+ get displayName() {
+ return this.contact.displayName;
+ }
+
+ update() {
+ this.setAttribute("displayname", this.contact.displayName);
+
+ let statusText = this.contact.statusText;
+ if (statusText) {
+ statusText = " - " + statusText;
+ }
+ this.setAttribute("statusTextWithDash", statusText);
+ let statusType = this.contact.statusType;
+
+ let statusIcon = this.querySelector(".smallStatusIcon");
+ let statusName = Status.toAttribute(statusType);
+ statusIcon.setAttribute("src", ChatIcons.getStatusIconURI(statusName));
+ statusIcon.setAttribute("alt", Status.toLabel(statusType));
+
+ if (this.contact.canSendMessage) {
+ this.setAttribute("cansend", "true");
+ } else {
+ this.removeAttribute("cansend");
+ }
+
+ let protoIcon = this.querySelector(".protoIcon");
+ protoIcon.setAttribute(
+ "src",
+ ChatIcons.getProtocolIconURI(this.contact.preferredBuddy.protocol)
+ );
+ ChatIcons.setProtocolIconOpacity(protoIcon, statusName);
+ }
+
+ build(contact) {
+ this.contact = contact;
+ this.contact.addObserver(this.observer);
+ this.update();
+ }
+
+ destroy() {
+ this.contact.removeObserver(this.observer);
+ delete this.contact;
+ this.remove();
+ }
+
+ startAliasing() {
+ if (this.hasAttribute("aliasing")) {
+ return; // prevent re-entry.
+ }
+
+ this.setAttribute("aliasing", "true");
+ let input = this.querySelector(".contactDisplayNameInput");
+ let label = this.querySelector(".contactDisplayName");
+ input.removeAttribute("hidden");
+ label.setAttribute("hidden", "true");
+ input.focus();
+
+ this._inputBlurListener = function (event) {
+ this.finishAliasing(true);
+ }.bind(this);
+ input.addEventListener("blur", this._inputBlurListener);
+
+ // Some keys (home/end for example) can make the selected item
+ // of the richlistbox change without producing a blur event on
+ // our textbox. Make sure we watch richlistbox selection changes.
+ this._parentSelectListener = function (event) {
+ if (event.target == this.parentNode) {
+ this.finishAliasing(true);
+ }
+ }.bind(this);
+ this.parentNode.addEventListener("select", this._parentSelectListener);
+ }
+
+ finishAliasing(save) {
+ // Cache the parentNode because when we change the contact alias, we
+ // trigger a re-order (and a removeContact call), which sets
+ // this.parentNode to undefined.
+ let listbox = this.parentNode;
+ let input = this.querySelector(".contactDisplayNameInput");
+ let label = this.querySelector(".contactDisplayName");
+ input.setAttribute("hidden", "hidden");
+ label.removeAttribute("hidden");
+ if (save) {
+ this.contact.alias = input.value;
+ }
+ this.removeAttribute("aliasing");
+ listbox.removeEventListener("select", this._parentSelectListener);
+ input.removeEventListener("blur", this._inputBlurListener);
+ delete this._parentSelectListener;
+ listbox.focus();
+ }
+
+ deleteContact() {
+ this.contact.remove();
+ }
+
+ canOpenConversation() {
+ return this.contact.canSendMessage;
+ }
+
+ openConversation() {
+ let prplConv = this.contact.createConversation();
+ let uiConv = IMServices.conversations.getUIConversation(prplConv);
+ chatHandler.focusConversation(uiConv);
+ }
+
+ keyPress(event) {
+ switch (event.keyCode) {
+ // If Enter or Return is pressed, open a new conversation
+ case event.DOM_VK_RETURN:
+ if (this.hasAttribute("aliasing")) {
+ this.finishAliasing(true);
+ } else if (this.canOpenConversation()) {
+ this.openConversation();
+ }
+ break;
+
+ case event.DOM_VK_F2:
+ if (!this.hasAttribute("aliasing")) {
+ this.startAliasing();
+ }
+ break;
+
+ case event.DOM_VK_ESCAPE:
+ if (this.hasAttribute("aliasing")) {
+ this.finishAliasing(false);
+ }
+ break;
+ }
+ }
+ disconnectedCallback() {
+ if (this.contact) {
+ this.contact.removeObserver(this.observer);
+ delete this.contact;
+ }
+ }
+ }
+
+ MozXULElement.implementCustomInterface(MozChatContactRichlistitem, [
+ Ci.nsIDOMXULSelectControlItemElement,
+ ]);
+
+ customElements.define(
+ "chat-contact-richlistitem",
+ MozChatContactRichlistitem,
+ {
+ extends: "richlistitem",
+ }
+ );
+}