summaryrefslogtreecommitdiffstats
path: root/comm/mail/components/im/content/chat-imconv.js
diff options
context:
space:
mode:
Diffstat (limited to 'comm/mail/components/im/content/chat-imconv.js')
-rw-r--r--comm/mail/components/im/content/chat-imconv.js366
1 files changed, 366 insertions, 0 deletions
diff --git a/comm/mail/components/im/content/chat-imconv.js b/comm/mail/components/im/content/chat-imconv.js
new file mode 100644
index 0000000000..759a3ce78a
--- /dev/null
+++ b/comm/mail/components/im/content/chat-imconv.js
@@ -0,0 +1,366 @@
+/* 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 MozElements, MozXULElement, gChatTab, chatHandler */
+
+// Wrap in a block to prevent leaking to window scope.
+{
+ const { Status } = ChromeUtils.importESModule(
+ "resource:///modules/imStatusUtils.sys.mjs"
+ );
+ const { AppConstants } = ChromeUtils.importESModule(
+ "resource://gre/modules/AppConstants.sys.mjs"
+ );
+ const { ChatIcons } = ChromeUtils.importESModule(
+ "resource:///modules/chatIcons.sys.mjs"
+ );
+
+ /**
+ * The MozChatConvRichlistitem widget displays opened conversation information from the
+ * contacts: i.e name and icon. It gets displayed under conversation expansion
+ * twisty in the contactlist richlistbox.
+ *
+ * @augments {MozElements.MozRichlistitem}
+ */
+ class MozChatConvRichlistitem extends MozElements.MozRichlistitem {
+ static get inheritedAttributes() {
+ return {
+ ".box-line": "selected",
+ ".convDisplayName": "value=displayname,status",
+ ".convUnreadTargetedCount": "value=unreadTargetedCount",
+ ".convUnreadCount": "value=unreadCount",
+ ".convUnreadTargetedCountLabel": "value=unreadTargetedCount",
+ };
+ }
+
+ static get markup() {
+ return `
+ <vbox class="box-line"></vbox>
+ <button class="closeConversationButton close-icon"
+ tooltiptext="&closeConversationButton.tooltip;"></button>
+ <stack class="prplBuddyIcon">
+ <html:img class="protoIcon" alt="" />
+ <html:img class="smallStatusIcon" />
+ </stack>
+ <hbox flex="1" class="conv-hbox">
+ <label crop="end" class="convDisplayName blistDisplayName">
+ </label>
+ <label class="convUnreadCount" crop="end"></label>
+ <box class="convUnreadTargetedCount">
+ <label class="convUnreadTargetedCountLabel" crop="end"></label>
+ </box>
+ <spacer style="flex: 1000000 1000000;"></spacer>
+ </hbox>
+ `;
+ }
+
+ static get entities() {
+ return ["chrome://messenger/locale/chat.dtd"];
+ }
+
+ connectedCallback() {
+ if (this.delayConnectedCallback() || this.hasChildNodes()) {
+ return;
+ }
+
+ this.setAttribute("is", "chat-imconv-richlistitem");
+
+ this.addEventListener(
+ "mousedown",
+ event => {
+ if (event.target.classList.contains("closeConversationButton")) {
+ this.closeConversation();
+ event.stopPropagation();
+ event.preventDefault();
+ }
+ },
+ true
+ );
+
+ this.appendChild(this.constructor.fragment);
+
+ this.convView = null;
+
+ this.directedUnreadCount = 0;
+
+ new MutationObserver(mutations => {
+ if (!this.convView || !this.convView.loaded) {
+ return;
+ }
+ if (this.hasAttribute("selected")) {
+ this.convView.switchingToPanel();
+ } else {
+ this.convView.switchingAwayFromPanel(true);
+ }
+ }).observe(this, { attributes: true, attributeFilter: ["selected"] });
+
+ // @implements {nsIObserver}
+ this.observer = {
+ QueryInterface: ChromeUtils.generateQI(["nsIObserver"]),
+ observe: function (subject, topic, data) {
+ if (
+ topic == "target-prpl-conversation-changed" ||
+ topic == "unread-message-count-changed" ||
+ topic == "update-conv-title" ||
+ topic == "update-buddy-status" ||
+ topic == "update-buddy-status" ||
+ topic == "update-conv-chatleft" ||
+ topic == "update-conv-chatjoining" ||
+ topic == "chat-update-topic"
+ ) {
+ this.update();
+ }
+ if (topic == "update-conv-title") {
+ this.group.updateContactPosition(
+ this.conv,
+ "chat-imconv-richlistitem"
+ );
+ }
+ }.bind(this),
+ };
+
+ if (this.hasAttribute("is-search-result")) {
+ let icon = this.querySelector(".protoIcon");
+ icon.classList.add("searchProtoIcon");
+ icon.setAttribute("src", "chrome://global/skin/icons/search-glass.svg");
+ let statusIcon = this.querySelector(".smallStatusIcon");
+ statusIcon.hidden = true;
+ this.setAttribute("unreadCount", "0");
+ this.setAttribute("unreadTargetedCount", "0");
+ }
+
+ this.initializeAttributeInheritance();
+ }
+
+ get displayName() {
+ return this.conv.title;
+ }
+
+ /**
+ * This getter exists to provide compatibility with the imgroup sortComparator.
+ */
+ get contact() {
+ return this.conv;
+ }
+
+ set selected(val) {
+ if (val) {
+ this.setAttribute("selected", "true");
+ } else {
+ this.removeAttribute("selected");
+ }
+ }
+
+ get selected() {
+ return (
+ gChatTab &&
+ gChatTab.tabNode.selected &&
+ this.getAttribute("selected") == "true"
+ );
+ }
+
+ /**
+ * Set the conversation this item should represent. Updates appearance and
+ * adds observers to keep it up to date.
+ *
+ * @param {imIConversation} conv - Conversation this item represents.
+ */
+ build(conv) {
+ this.conv = conv;
+ this.conv.addObserver(this.observer);
+ this.update();
+ }
+
+ update() {
+ this.setAttribute("displayname", this.displayName);
+ if (this.selected && document.hasFocus()) {
+ if (this.convView && this.convView.loaded) {
+ this.conv.markAsRead();
+ this.directedUnreadCount = 0;
+ chatHandler.updateTitle();
+ chatHandler.updateChatButtonState();
+ }
+ this.setAttribute("unreadCount", "0");
+ this.setAttribute("unreadTargetedCount", "0");
+ this.removeAttribute("unread");
+ this.removeAttribute("attention");
+ } else {
+ let unreadCount =
+ this.conv.unreadIncomingMessageCount +
+ this.conv.unreadOTRNotificationCount;
+ let directedMessages = unreadCount;
+ if (unreadCount) {
+ this.setAttribute("unread", "true");
+ if (this.conv.isChat) {
+ directedMessages = this.conv.unreadTargetedMessageCount;
+ if (directedMessages) {
+ this.setAttribute("attention", "true");
+ }
+ }
+ unreadCount -= directedMessages;
+ if (directedMessages > this.directedUnreadCount) {
+ this.directedUnreadCount = directedMessages;
+ }
+ }
+ if (unreadCount) {
+ unreadCount = "(" + unreadCount + ")";
+ }
+ this.setAttribute("unreadCount", unreadCount);
+ if (
+ Services.prefs.getBoolPref(
+ "messenger.options.getAttentionOnNewMessages"
+ ) &&
+ directedMessages > parseInt(this.getAttribute("unreadTargetedCount"))
+ ) {
+ window.getAttention();
+ }
+ this.setAttribute("unreadTargetedCount", directedMessages);
+ chatHandler.updateTitle();
+ }
+
+ let statusIcon = this.querySelector(".smallStatusIcon");
+ let statusName;
+ statusIcon.hidden = false;
+ if (this.conv.isChat) {
+ if (this.conv.joining) {
+ statusName = "joining";
+ } else if (!this.conv.account.connected || this.conv.left) {
+ statusName = "left";
+ }
+ if (statusName) {
+ statusIcon.setAttribute(
+ "src",
+ ChatIcons.getStatusIconURI(statusName)
+ );
+ // Set alt using messenger/chat.ftl.
+ document.l10n.setAttributes(
+ statusIcon,
+ `chat-${statusName}-chat-icon2`
+ );
+ } else {
+ statusIcon.removeAttribute("src");
+ statusIcon.removeAttribute("data-l10n-id");
+ statusIcon.removeAttribute("alt");
+ statusIcon.hidden = true;
+ // Treat protoIcon as if connected.
+ statusName = "connected";
+ }
+ } else {
+ let statusType = Ci.imIStatusInfo.STATUS_UNKNOWN;
+ let buddy = this.conv.buddy;
+ if (buddy && buddy.account.connected) {
+ statusType = buddy.statusType;
+ }
+ statusName = Status.toAttribute(statusType);
+ statusIcon.setAttribute("src", ChatIcons.getStatusIconURI(statusName));
+ statusIcon.removeAttribute("data-l10n-id");
+ statusIcon.setAttribute("alt", Status.toLabel(statusType));
+ }
+
+ if (!this.hasAttribute("is-search-result")) {
+ let protoIcon = this.querySelector(".protoIcon");
+ protoIcon.setAttribute(
+ "src",
+ ChatIcons.getProtocolIconURI(this.conv.account.protocol)
+ );
+ ChatIcons.setProtocolIconOpacity(protoIcon, statusName);
+ }
+ }
+
+ destroy() {
+ if (this.conv) {
+ this.conv.removeObserver(this.observer);
+ }
+ if (this.convView) {
+ this.convView.destroy();
+ this.convView.remove();
+ }
+
+ // If the conversation we are destroying was selected, we should
+ // select something else, but the 'select' event handler of
+ // the listbox will choke while updating the Chat tab title if
+ // there are conversation nodes associated with a conversation
+ // that no longer exists from the chat core's point of view, so
+ // we do the actual selection change only after this conversation
+ // item is fully destroyed and removed from the list.
+ let newSelectedItem;
+ let list = this.parentNode;
+ if (list.selectedItem == this) {
+ newSelectedItem = this.previousElementSibling;
+ }
+
+ if (this.log) {
+ this.hidden = true;
+ delete this.log;
+ } else {
+ this.remove();
+ delete this.conv;
+ }
+ if (newSelectedItem) {
+ list.selectedItem = newSelectedItem;
+ }
+ }
+
+ closeConversation() {
+ if (this.conv) {
+ this.conv.close();
+ } else {
+ this.destroy();
+ }
+ }
+
+ keyPress(event) {
+ // If Enter or Return is pressed, focus the input box.
+ if (event.keyCode == event.DOM_VK_RETURN) {
+ this.convView.focus();
+ return;
+ }
+
+ let accelKeyPressed =
+ AppConstants.platform == "macosx" ? event.metaKey : event.ctrlKey;
+ // If a character was typed or the accel+v copy shortcut was used,
+ // focus the input box and resend the key event.
+ if (
+ event.charCode != 0 &&
+ !event.altKey &&
+ ((accelKeyPressed && event.charCode == "v".charCodeAt(0)) ||
+ (!event.ctrlKey && !event.metaKey))
+ ) {
+ this.convView.focus();
+
+ let clonedEvent = new KeyboardEvent("keypress", event);
+ this.convView.editor.dispatchEvent(clonedEvent);
+ event.preventDefault();
+ }
+ }
+
+ /**
+ * Replace the conversation that this item represents.
+ *
+ * @param {imIConversation} conv - Updated conversation this should
+ * represent.
+ */
+ changeConversation(conv) {
+ this.conv?.removeObserver(this.observer);
+ this.build(conv);
+ }
+
+ disconnectedCallback() {
+ if (this.conv) {
+ this.conv.removeObserver(this.observer);
+ delete this.conv;
+ }
+ }
+ }
+
+ MozXULElement.implementCustomInterface(MozChatConvRichlistitem, [
+ Ci.nsIDOMXULSelectControlItemElement,
+ ]);
+
+ customElements.define("chat-imconv-richlistitem", MozChatConvRichlistitem, {
+ extends: "richlistitem",
+ });
+}