/* 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 `
`;
}
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);
}