summaryrefslogtreecommitdiffstats
path: root/comm/mail/components/im/content/chat-conversation-info.js
diff options
context:
space:
mode:
Diffstat (limited to 'comm/mail/components/im/content/chat-conversation-info.js')
-rw-r--r--comm/mail/components/im/content/chat-conversation-info.js353
1 files changed, 353 insertions, 0 deletions
diff --git a/comm/mail/components/im/content/chat-conversation-info.js b/comm/mail/components/im/content/chat-conversation-info.js
new file mode 100644
index 0000000000..a8004a4c3f
--- /dev/null
+++ b/comm/mail/components/im/content/chat-conversation-info.js
@@ -0,0 +1,353 @@
+/* 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";
+
+/* globals MozElements MozXULElement chatHandler */
+
+// Wrap in a block to prevent leaking to window scope.
+{
+ const { ChatIcons } = ChromeUtils.importESModule(
+ "resource:///modules/chatIcons.sys.mjs"
+ );
+
+ ChromeUtils.defineESModuleGetters(this, {
+ OTR: "resource:///modules/OTR.sys.mjs",
+ OTRUI: "resource:///modules/OTRUI.sys.mjs",
+ });
+
+ /**
+ * The MozChatConversationInfo widget displays information about a chat:
+ * e.g. the channel name and topic of an IRC channel, or nick, user image and
+ * status of a conversation partner.
+ * It is typically shown at the top right of the chat UI.
+ *
+ * @augments {MozXULElement}
+ */
+ class MozChatConversationInfo extends MozXULElement {
+ static get inheritedAttributes() {
+ return { ".displayName": "value=displayName" };
+ }
+
+ static get markup() {
+ return `
+ <linkset>
+ <html:link rel="localization" href="messenger/otr/chat.ftl"/>
+ </linkset>
+
+ <html:div class="displayUserAccount">
+ <stack>
+ <html:img class="userIcon" alt="" />
+ <html:img class="statusTypeIcon" alt="" />
+ </stack>
+ <html:div class="nameAndStatusGrid">
+ <description class="displayName" crop="end"></description>
+ <html:img class="protoIcon" alt="" />
+ <html:hr />
+ <description class="statusMessage" crop="end"></description>
+ <!-- FIXME: A keyboard user cannot focus the hidden input, nor
+ - click the above description box in order to reveal it. -->
+ <html:input class="statusMessageInput input-inline"
+ hidden="hidden"/>
+ </html:div>
+ </html:div>
+ <hbox class="encryption-container themeable-brighttext"
+ align="center"
+ hidden="true">
+ <label class="encryption-label"
+ crop="end"
+ data-l10n-id="state-label"
+ flex="1"/>
+ <toolbarbutton id="chatEncryptionButton"
+ mode="dialog"
+ class="encryption-button"
+ type="menu"
+ wantdropmarker="true"
+ label="Insecure"
+ data-l10n-id="start-tooltip">
+ <menupopup class="encryption-menu-popup">
+ <menuitem class="otr-start" data-l10n-id="start-label"
+ oncommand='this.closest("chat-conversation-info").onOtrStartClicked();'/>
+ <menuitem class="otr-end" data-l10n-id="end-label"
+ oncommand='this.closest("chat-conversation-info").onOtrEndClicked();'/>
+ <menuitem class="otr-auth" data-l10n-id="auth-label"
+ oncommand='this.closest("chat-conversation-info").onOtrAuthClicked();'/>
+ <menuitem class="protocol-encrypt" data-l10n-id="start-label"/>
+ </menupopup>
+ </toolbarbutton>
+ </hbox>
+ `;
+ }
+
+ connectedCallback() {
+ if (this.hasChildNodes() || this.delayConnectedCallback()) {
+ return;
+ }
+ this.setAttribute("orient", "vertical");
+
+ this.appendChild(this.constructor.fragment);
+
+ this.topicEditable = false;
+ this.editingTopic = false;
+ this.noTopic = false;
+
+ this.topic.addEventListener("click", this.startEditTopic.bind(this));
+
+ this.querySelector(".protocol-encrypt").addEventListener("click", () =>
+ this.initializeEncryption()
+ );
+
+ let encryptionButton = this.querySelector(".encryption-button");
+ encryptionButton.addEventListener(
+ "command",
+ this.encryptionButtonClicked
+ );
+ if (Services.prefs.getBoolPref("chat.otr.enable")) {
+ OTRUI.setNotificationBox(chatHandler.msgNotificationBar);
+ }
+ this.initializeAttributeInheritance();
+ }
+
+ get topic() {
+ return this.querySelector(".statusMessage");
+ }
+
+ get topicInput() {
+ return this.querySelector(".statusMessageInput");
+ }
+
+ finishEditTopic(save) {
+ if (!this.editingTopic) {
+ return;
+ }
+
+ let panel = this.getSelectedPanel();
+ let topic = this.topic;
+ let topicInput = this.topicInput;
+ topic.removeAttribute("hidden");
+ topicInput.hidden = true;
+ if (save) {
+ // apply the new topic only if it is different from the current one
+ if (topicInput.value != topicInput.getAttribute("value")) {
+ panel._conv.topic = topicInput.value;
+ }
+ }
+ this.editingTopic = false;
+
+ topicInput.removeEventListener("keypress", this._topicKeyPress, true);
+ delete this._topicKeyPress;
+ topicInput.removeEventListener("blur", this._topicBlur);
+ delete this._topicBlur;
+
+ // After hiding the input, the focus is on an element that can't receive
+ // keyboard events, so move it to somewhere else.
+ // FIXME: jumping focus should be removed once editing the topic input
+ // becomes accessible to keyboard users.
+ panel.editor.focus();
+ }
+
+ topicKeyPress(event) {
+ switch (event.keyCode) {
+ case event.DOM_VK_RETURN:
+ this.finishEditTopic(true);
+ break;
+
+ case event.DOM_VK_ESCAPE:
+ this.finishEditTopic(false);
+ event.stopPropagation();
+ event.preventDefault();
+ break;
+ }
+ }
+
+ topicBlur(event) {
+ if (event.target == this.topicInput) {
+ this.finishEditTopic(true);
+ }
+ }
+
+ startEditTopic() {
+ let topic = this.topic;
+ let topicInput = this.topicInput;
+ if (!this.topicEditable || this.editingTopic) {
+ return;
+ }
+
+ this.editingTopic = true;
+
+ topicInput.hidden = false;
+ topic.setAttribute("hidden", "true");
+ this._topicKeyPress = this.topicKeyPress.bind(this);
+ topicInput.addEventListener("keypress", this._topicKeyPress);
+ this._topicBlur = this.topicBlur.bind(this);
+ topicInput.addEventListener("blur", this._topicBlur);
+ topicInput.getBoundingClientRect();
+ if (this.noTopic) {
+ topicInput.value = "";
+ } else {
+ topicInput.value = topic.value;
+ }
+ topicInput.select();
+ }
+
+ encryptionButtonClicked(aEvent) {
+ aEvent.preventDefault();
+ let encryptionMenu = this.querySelector(".encryption-menu-popup");
+ encryptionMenu.openPopup(encryptionMenu.parentNode, "after_start");
+ }
+
+ onOtrStartClicked() {
+ // check if start-menu-command is disabled, if yes exit
+ let convBinding = this.getSelectedPanel();
+ let uiConv = convBinding._conv;
+ let conv = uiConv.target;
+ let context = OTR.getContext(conv);
+ let bundleId =
+ "alert-" +
+ (context.msgstate === OTR.getMessageState().OTRL_MSGSTATE_ENCRYPTED
+ ? "refresh"
+ : "start");
+ OTRUI.sendSystemAlert(uiConv, conv, bundleId);
+ OTR.sendQueryMsg(conv);
+ }
+
+ onOtrEndClicked() {
+ let convBinding = this.getSelectedPanel();
+ let uiConv = convBinding._conv;
+ let conv = uiConv.target;
+ OTR.disconnect(conv, false);
+ let bundleId = "alert-gone-insecure";
+ OTRUI.sendSystemAlert(uiConv, conv, bundleId);
+ }
+
+ onOtrAuthClicked() {
+ let convBinding = this.getSelectedPanel();
+ let uiConv = convBinding._conv;
+ let conv = uiConv.target;
+ OTRUI.openAuth(window, conv.normalizedName, "start", uiConv);
+ }
+
+ initializeEncryption() {
+ const convBinding = this.getSelectedPanel();
+ const uiConv = convBinding._conv;
+ uiConv.initializeEncryption();
+ }
+
+ getSelectedPanel() {
+ for (let element of document.getElementById("conversationsBox")
+ .children) {
+ if (!element.hidden) {
+ return element;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Sets the shown protocol icon.
+ *
+ * @param {prplIProtocol} protocol - The protocol to show.
+ */
+ setProtocol(protocol) {
+ this.querySelector(".protoIcon").setAttribute(
+ "src",
+ ChatIcons.getProtocolIconURI(protocol)
+ );
+ }
+
+ /**
+ * Sets the shown user icon.
+ *
+ * @param {string|null} iconURI - The image uri to show, or "" to use the
+ * fallback, or null to hide the icon.
+ * @param {boolean} useFallback - True if the "fallback" icon should be shown
+ * if iconUri isn't provided.
+ */
+ setUserIcon(iconURI, useFallback) {
+ ChatIcons.setUserIconSrc(
+ this.querySelector(".userIcon"),
+ iconURI,
+ useFallback
+ );
+ }
+
+ /**
+ * Sets the shown status icon.
+ *
+ * @param {string} statusName - The name of the status.
+ */
+ setStatusIcon(statusName) {
+ let statusIcon = this.querySelector(".statusTypeIcon");
+ if (statusName === null) {
+ statusIcon.hidden = true;
+ statusIcon.removeAttribute("src");
+ } else {
+ statusIcon.hidden = false;
+ let src = ChatIcons.getStatusIconURI(statusName);
+ if (src) {
+ statusIcon.setAttribute("src", src);
+ } else {
+ /* Unexpected missing icon. */
+ statusIcon.removeAttribute("src");
+ }
+ }
+ }
+
+ /**
+ * Sets the text for the status of a user, or the topic of a chat.
+ *
+ * @param {string} text - The text to display.
+ * @param {boolean} [noTopic=false] - Whether to stylize the status to
+ * indicate the status is some fallback text.
+ */
+ setStatusText(text, noTopic = false) {
+ let statusEl = this.topic;
+
+ statusEl.setAttribute("value", text);
+ statusEl.setAttribute("tooltiptext", text);
+ statusEl.toggleAttribute("noTopic", noTopic);
+ }
+
+ /**
+ * Sets the element to display a user status. The user icon needs to be set
+ * separately with setUserIcon.
+ *
+ * @param {string} statusName - The internal name for the status.
+ * @param {string} statusText - The text to display as the status.
+ */
+ setStatus(statusName, statusText) {
+ this.setStatusIcon(statusName);
+ this.setStatusText(statusText);
+ this.topicEditable = false;
+ }
+
+ /**
+ * Sets the element to display a chat status.
+ *
+ * @param {string} topicText - The topic text for the chat, or some fallback
+ * text used if the chat has no topic.
+ * @param {boolean} noTopic - Whether the chat has no topic.
+ * @param {boolean} topicEditable - Whether the topic can be set by the
+ * user.
+ */
+ setAsChat(topicText, noTopic, topicEditable) {
+ this.noTopic = noTopic;
+ this.topicEditable = topicEditable;
+ this.setStatusText(topicText, noTopic);
+ this.setStatusIcon("chat");
+ }
+
+ /**
+ * Empty the element's display.
+ */
+ clear() {
+ this.querySelector(".protoIcon").removeAttribute("src");
+ this.setStatusText("");
+ this.setStatusIcon(null);
+ this.setUserIcon("", false);
+ this.topicEditable = false;
+ }
+ }
+ customElements.define("chat-conversation-info", MozChatConversationInfo);
+}