Bug 1538549 - [de-xbl] convert contact binding to custom element. r=mkmelin DONTBUILD

--HG--
rename : mail/components/im/content/imcontact.xml => mail/components/im/content/chat-contact.js
This commit is contained in:
Khushil Mistry 2019-04-09 14:41:00 +02:00
Родитель 08bb5a6f90
Коммит 7f335f59f7
10 изменённых файлов: 259 добавлений и 270 удалений

Просмотреть файл

@ -433,7 +433,7 @@
if (localName == "imconv" && elt.conv) if (localName == "imconv" && elt.conv)
return updateTooltipFromConversation(elt.conv); return updateTooltipFromConversation(elt.conv);
if (localName == "imcontact") if (localName == "richlistitem" && elt.getAttribute("is") == "chat-contact")
return updateTooltipFromBuddy(elt.contact.preferredBuddy.preferredAccountBuddy); return updateTooltipFromBuddy(elt.contact.preferredBuddy.preferredAccountBuddy);
if (localName == "richlistitem") { if (localName == "richlistitem") {

Просмотреть файл

@ -150,6 +150,7 @@
<script type="application/javascript" src="chrome://messenger/content/newmailaccount/uriListener.js"/> <script type="application/javascript" src="chrome://messenger/content/newmailaccount/uriListener.js"/>
<script type="application/javascript" src="chrome://messenger/content/chat/chat-conversation-info.js"/> <script type="application/javascript" src="chrome://messenger/content/chat/chat-conversation-info.js"/>
<script type="application/javascript" src="chrome://gloda/content/autocomplete-richlistitem.js"/> <script type="application/javascript" src="chrome://gloda/content/autocomplete-richlistitem.js"/>
<script type="application/javascript" src="chrome://messenger/content/chat/chat-contact.js"/>
#ifdef XP_MACOSX #ifdef XP_MACOSX
<script type="application/javascript" src="chrome://messenger/content/macMessengerMenu.js"/> <script type="application/javascript" src="chrome://messenger/content/macMessengerMenu.js"/>
<script type="application/javascript" src="chrome://global/content/macWindowMenu.js"/> <script type="application/javascript" src="chrome://global/content/macWindowMenu.js"/>

Просмотреть файл

@ -3,7 +3,6 @@
module.exports = { module.exports = {
overrides: [{ overrides: [{
files: [ files: [
"imcontact.xml",
"imconv.xml", "imconv.xml",
"imconversation.xml", "imconversation.xml",
], ],

Просмотреть файл

@ -0,0 +1,226 @@
/* 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 */
{
let { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
/**
* The MozChatContact 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.
*
* @extends {MozElements.MozRichlistitem}
*/
class MozChatContact extends MozElements.MozRichlistitem {
static get inheritedAttributes() {
return {
".box-line": "selected",
".protoIcon": "src=iconPrpl,status",
".statusIcon": "status",
".contactDisplayName": "value=displayname,status",
".contactStatusText": "value=statusTextWithDash",
};
}
connectedCallback() {
if (this.delayConnectedCallback() || this.hasChildNodes()) {
return;
}
this.setAttribute("is", "chat-contact");
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.originalTarget.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([Ci.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(MozXULElement.parseXULToFragment(`
<vbox class="box-line"></vbox>
<stack class="prplBuddyIcon" mousethrough="always">
<image class="protoIcon"></image>
<image class="statusIcon"></image>
</stack>
<hbox flex="1" class="contact-hbox" mousethrough="always">
<label crop="end" flex="1" mousethrough="always"
class="contactDisplayName blistDisplayName">
</label>
<label crop="end" flex="100000" mousethrough="always"
class="contactStatusText">
</label>
<button class="startChatBubble" tooltiptext="&openConversationButton.tooltip;">
</button>
</hbox>
`, ["chrome://messenger/locale/chat.dtd"]));
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;
this.setAttribute("statusText", Status.toLabel(statusType) + statusText);
this.setAttribute("status", Status.toAttribute(statusType));
if (this.contact.canSendMessage) {
this.setAttribute("cansend", "true");
} else {
this.removeAttribute("cansend");
}
let proto = this.contact.preferredBuddy.protocol;
this.setAttribute("iconPrpl", proto.iconBaseURI + "icon.png");
}
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 textbox = this.querySelector(".contactDisplayName");
textbox.getBoundingClientRect(); // force binding attachmant by forcing layout
textbox.select();
// 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;
if (save) {
this.contact.alias = this.querySelector(".contactDisplayName").value;
}
this.removeAttribute("aliasing");
listbox.removeEventListener("select", this._parentSelectListener);
delete this._parentSelectListener;
listbox.focus();
}
deleteContact() {
this.contact.remove();
}
canOpenConversation() {
return this.contact.canSendMessage;
}
openConversation() {
let prplConv = this.contact.createConversation();
let uiConv = Services.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(
MozChatContact, [Ci.nsIDOMXULSelectControlItemElement]
);
customElements.define("chat-contact", MozChatContact, { "extends": "richlistitem" });
}

Просмотреть файл

@ -18,7 +18,9 @@ function buddyListContextMenu(aXulMenu) {
this.target = aXulMenu.triggerNode; this.target = aXulMenu.triggerNode;
this.menu = aXulMenu; this.menu = aXulMenu;
let localName = this.target.localName; let localName = this.target.localName;
this.onContact = localName == "imcontact"; document.getElementById("contactlistbox").selectedItem = this.target;
this.onContact = (localName == "richlistitem" &&
this.target.getAttribute("is") == "chat-contact");
this.onConv = localName == "imconv"; this.onConv = localName == "imconv";
this.shouldDisplay = this.onContact || this.onConv; this.shouldDisplay = this.onContact || this.onConv;
@ -283,8 +285,9 @@ var chatHandler = {
(!selectedItem || (selectedItem == convs && (!selectedItem || (selectedItem == convs &&
convs.nextSibling.localName != "imconv")); convs.nextSibling.localName != "imconv"));
let elt = convs.addContact(aConv, "imconv"); let elt = convs.addContact(aConv, "imconv");
if (shouldSelect) if (shouldSelect) {
list.selectedItem = elt; list.selectedItem = elt;
}
if (aConv.isChat || !aConv.buddy) if (aConv.isChat || !aConv.buddy)
return; return;
@ -293,8 +296,9 @@ var chatHandler = {
elt.imContact = contact; elt.imContact = contact;
let groupName = (contact.online ? "on" : "off") + "linecontactsGroup"; let groupName = (contact.online ? "on" : "off") + "linecontactsGroup";
let item = document.getElementById(groupName).removeContact(contact); let item = document.getElementById(groupName).removeContact(contact);
if (list.selectedItem == item) if (list.selectedItem == item) {
list.selectedItem = elt; list.selectedItem = elt;
}
}, },
_hasConversationForContact(aContact) { _hasConversationForContact(aContact) {
@ -552,7 +556,7 @@ var chatHandler = {
document.getElementById("conversationsDeck").selectedPanel = item.convView; document.getElementById("conversationsDeck").selectedPanel = item.convView;
document.getElementById("logTree").view.selection.clearSelection(); document.getElementById("logTree").view.selection.clearSelection();
item.convView.focus(); item.convView.focus();
} else if (item.localName == "imcontact") { } else if (item.localName == "richlistitem" && item.getAttribute("is") == "chat-contact") {
item.openConversation(); item.openConversation();
} }
}, },
@ -680,12 +684,12 @@ var chatHandler = {
button.label = bundle.getString("goBackToCurrentConversation.button"); button.label = bundle.getString("goBackToCurrentConversation.button");
button.disabled = false; button.disabled = false;
this.observedContact = null; this.observedContact = null;
} else if (item.localName == "imcontact") { } else if (item.localName == "richlistitem" && item.getAttribute("is") == "chat-contact") {
let contact = item.contact; let contact = item.contact;
if (this.observedContact && contact && if (this.observedContact && contact &&
this.observedContact.id == contact.id) { this.observedContact.id == contact.id) {
return; // onselect has just been fired again because a status return; // onselect has just been fired again because a status
// change caused the imcontact to move. // change caused the chat-contact to move.
// Return early to avoid flickering and changing the selected log. // Return early to avoid flickering and changing the selected log.
} }

Просмотреть файл

@ -11,8 +11,7 @@ imgroup {
-moz-box-align: center; -moz-box-align: center;
} }
imcontact { richlistitem[is="chat-contact"] {
-moz-binding: url("chrome://messenger/content/chat/imcontact.xml#contact");
-moz-box-align: center; -moz-box-align: center;
} }
@ -25,7 +24,7 @@ imcontact {
display: none; display: none;
} }
imcontact[cansend]:hover .startChatBubble { richlistitem[is="chat-contact"][cansend]:hover .startChatBubble {
display: -moz-box; display: -moz-box;
} }

Просмотреть файл

@ -1,245 +0,0 @@
<?xml version="1.0"?>
<!-- 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/. -->
<!DOCTYPE bindings [
<!ENTITY % chatDTD SYSTEM "chrome://messenger/locale/chat.dtd" >
%chatDTD;
]>
<bindings id="contactBindings"
xmlns="http://www.mozilla.org/xbl"
xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
xmlns:xbl="http://www.mozilla.org/xbl"
xmlns:html="http://www.w3.org/1999/xhtml">
<binding id="contact" extends="chrome://global/content/bindings/richlistbox.xml#richlistitem">
<content>
<xul:vbox class="box-line" xbl:inherits="selected"/>
<xul:stack class="prplBuddyIcon" mousethrough="always">
<xul:image class="protoIcon" xbl:inherits="src=iconPrpl,status"/>
<xul:image class="statusIcon" xbl:inherits="status"/>
</xul:stack>
<xul:hbox flex="1" class="contact-hbox" mousethrough="always">
<xul:label crop="end" flex="1" mousethrough="always"
anonid="displayname" class="contactDisplayName blistDisplayName"
xbl:inherits="value=displayname,status"/>
<xul:label crop="end" flex="100000" mousethrough="always"
anonid="statusText" class="contactStatusText"
xbl:inherits="value=statusTextWithDash"/>
<xul:button anonid="startChatBubble" class="startChatBubble"
tooltiptext="&openConversationButton.tooltip;"/>
</xul:hbox>
</content>
<implementation implements="nsIObserver">
<destructor>
<![CDATA[
if (this.contact) {
this.contact.removeObserver(this);
delete this.contact;
}
]]>
</destructor>
<method name="build">
<parameter name="aContact"/>
<body>
<![CDATA[
this.contact = aContact;
this.contact.addObserver(this);
this.update();
]]>
</body>
</method>
<property name="displayName"
onget="return this.contact.displayName;"/>
<!-- nsIObserver implementation -->
<method name="observe">
<parameter name="aSubject"/>
<parameter name="aTopic"/>
<parameter name="aData"/>
<body>
<![CDATA[
if (aTopic == "contact-preferred-buddy-changed" ||
aTopic == "contact-display-name-changed" ||
aTopic == "contact-status-changed")
this.update();
if (aTopic == "contact-availability-changed" ||
aTopic == "contact-display-name-changed")
this.group.updateContactPosition(aSubject);
]]>
</body>
</method>
<method name="destroy">
<body>
<![CDATA[
this.contact.removeObserver(this);
delete this.contact;
this.remove();
]]>
</body>
</method>
<method name="update">
<body>
<![CDATA[
this.setAttribute("displayname", this.contact.displayName);
let statusText = this.contact.statusText;
if (statusText)
statusText = " - " + statusText;
this.setAttribute("statusTextWithDash", statusText);
let statusType = this.contact.statusType;
this.setAttribute("statusText", Status.toLabel(statusType) + statusText);
this.setAttribute("status", Status.toAttribute(statusType));
if (this.contact.canSendMessage)
this.setAttribute("cansend", "true");
else
this.removeAttribute("cansend");
let proto = this.contact.preferredBuddy.protocol;
this.setAttribute("iconPrpl", proto.iconBaseURI + "icon.png");
]]>
</body>
</method>
<method name="startAliasing">
<body>
<![CDATA[
if (this.hasAttribute("aliasing"))
return; // prevent re-entry.
this.setAttribute("aliasing", "true");
let textbox =
document.getAnonymousElementByAttribute(this, "anonid", "displayname");
textbox.getBoundingClientRect(); // force binding attachmant by forcing layout
textbox.select();
// 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(aEvent) {
if (aEvent.target == this.parentNode)
this.finishAliasing(true);
}).bind(this);
this.parentNode.addEventListener("select", this._parentSelectListener);
]]>
</body>
</method>
<method name="finishAliasing">
<parameter name="aSave"/>
<body>
<![CDATA[
// 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;
if (aSave) {
this.contact.alias = document.getAnonymousElementByAttribute(this, "anonid",
"displayname").value;
}
this.removeAttribute("aliasing");
listbox.removeEventListener("select", this._parentSelectListener);
delete this._parentSelectListener;
listbox.focus();
]]>
</body>
</method>
<method name="deleteContact">
<body>
<![CDATA[
this.contact.remove();
]]>
</body>
</method>
<method name="canOpenConversation">
<body>
<![CDATA[
return this.contact.canSendMessage;
]]>
</body>
</method>
<method name="openConversation">
<body>
<![CDATA[
let prplConv = this.contact.createConversation();
let uiConv = Services.conversations.getUIConversation(prplConv);
chatHandler.focusConversation(uiConv);
]]>
</body>
</method>
<method name="keyPress">
<parameter name="aEvent"/>
<body>this._keyPress(aEvent);</body>
</method>
<method name="_keyPress">
<parameter name="aEvent"/>
<body>
<![CDATA[
switch (aEvent.keyCode) {
// If Enter or Return is pressed, open a new conversation
case aEvent.DOM_VK_RETURN:
if (this.hasAttribute("aliasing"))
this.finishAliasing(true);
else if (this.canOpenConversation())
this.openConversation();
break;
case aEvent.DOM_VK_F2:
if (!this.hasAttribute("aliasing"))
this.startAliasing();
break;
case aEvent.DOM_VK_ESCAPE:
if (this.hasAttribute("aliasing"))
this.finishAliasing(false);
break;
}
]]>
</body>
</method>
</implementation>
<handlers>
<handler event="blur">
<![CDATA[
if (!this.hasAttribute("aliasing"))
return;
if (Services.focus.activeWindow == document.defaultView)
this.finishAliasing(true);
]]>
</handler>
<handler event="mousedown">
<![CDATA[
if (!this.hasAttribute("aliasing") && this.canOpenConversation() &&
event.originalTarget.getAttribute("anonid") == "startChatBubble") {
this.openConversation();
event.preventDefault();
}
]]>
</handler>
<handler event="click">
<![CDATA[
if (!this.hasAttribute("aliasing") && this.canOpenConversation() &&
event.detail == 2 &&
event.originalTarget.getAttribute("anonid") != "expander")
this.openConversation();
]]>
</handler>
</handlers>
</binding>
</bindings>

Просмотреть файл

@ -55,7 +55,12 @@
if (this.contactsById.hasOwnProperty(aContact.id)) if (this.contactsById.hasOwnProperty(aContact.id))
return null; return null;
let contactElt = document.createElement(aTagName || "imcontact"); let contactElt;
if (aTagName) {
contactElt = document.createElement(aTagName);
} else {
contactElt = document.createElement("richlistitem", { is: "chat-contact" });
}
if (this.hasAttribute("closed")) if (this.hasAttribute("closed"))
contactElt.setAttribute("collapsed", "true"); contactElt.setAttribute("collapsed", "true");

Просмотреть файл

@ -19,7 +19,7 @@ messenger.jar:
content/messenger/chat/imAccountWizard.js (content/imAccountWizard.js) content/messenger/chat/imAccountWizard.js (content/imAccountWizard.js)
content/messenger/chat/imContextMenu.js (content/imContextMenu.js) content/messenger/chat/imContextMenu.js (content/imContextMenu.js)
content/messenger/chat/imStatusSelector.js (content/imStatusSelector.js) content/messenger/chat/imStatusSelector.js (content/imStatusSelector.js)
content/messenger/chat/imcontact.xml (content/imcontact.xml) content/messenger/chat/chat-contact.js (content/chat-contact.js)
content/messenger/chat/imconversation.xml (content/imconversation.xml) content/messenger/chat/imconversation.xml (content/imconversation.xml)
content/messenger/chat/imconv.xml (content/imconv.xml) content/messenger/chat/imconv.xml (content/imconv.xml)
content/messenger/chat/imgroup.xml (content/imgroup.xml) content/messenger/chat/imgroup.xml (content/imgroup.xml)

Просмотреть файл

@ -96,12 +96,12 @@
/* move the scrollbar to the left */ /* move the scrollbar to the left */
#contactlistbox:-moz-locale-dir(ltr), #contactlistbox:-moz-locale-dir(ltr),
#contactlistbox:-moz-locale-dir(rtl) > :-moz-any(imconv, imcontact, imgroup) { #contactlistbox:-moz-locale-dir(rtl) > :-moz-any(imconv, richlistitem[is="chat-contact"], imgroup) {
direction: rtl; direction: rtl;
} }
#contactlistbox:-moz-locale-dir(rtl), #contactlistbox:-moz-locale-dir(rtl),
#contactlistbox:-moz-locale-dir(ltr) > :-moz-any(imconv, imcontact, imgroup) { #contactlistbox:-moz-locale-dir(ltr) > :-moz-any(imconv, richlistitem[is="chat-contact"], imgroup) {
direction: ltr; direction: ltr;
} }
@ -129,7 +129,7 @@ imgroup[selected] {
} }
imconv, imconv,
imcontact { richlistitem[is="chat-contact"] {
border-top: 1px solid transparent; border-top: 1px solid transparent;
border-bottom: 1px solid transparent; border-bottom: 1px solid transparent;
-moz-box-align: stretch; -moz-box-align: stretch;
@ -139,7 +139,7 @@ imcontact {
width: 2px; width: 2px;
} }
imcontact > .box-line { richlistitem[is="chat-contact"] > .box-line {
/* equalize the space, the .closeConversationButton uses */ /* equalize the space, the .closeConversationButton uses */
margin-inline-end: 22px; margin-inline-end: 22px;
} }
@ -148,45 +148,45 @@ imcontact > .box-line {
background-color: var(--tabline-color); background-color: var(--tabline-color);
} }
:-moz-any(imconv, imcontact, imgroup) { :-moz-any(imconv, richlistitem[is="chat-contact"], imgroup) {
pointer-events: auto; pointer-events: auto;
} }
:-moz-any(imconv, imcontact, imgroup):not([selected=true]):hover { :-moz-any(imconv, richlistitem[is="chat-contact"], imgroup):not([selected=true]):hover {
background-color: rgba(0,0,0,.1); background-color: rgba(0,0,0,.1);
} }
:root[lwt-tree] imgroup, :root[lwt-tree] imgroup,
:root[lwt-tree] imconv:not([selected]), :root[lwt-tree] imconv:not([selected]),
:root[lwt-tree] imcontact:not([selected]) { :root[lwt-tree] richlistitem[is="chat-contact"]:not([selected]) {
color: var(--sidebar-text-color); color: var(--sidebar-text-color);
} }
imconv[selected=true], imconv[selected=true],
imcontact[selected=true] { richlistitem[is="chat-contact"][selected=true] {
background-color: var(--imbox-selected-background-color); background-color: var(--imbox-selected-background-color);
border-color: var(--imbox-selected-border-color); border-color: var(--imbox-selected-border-color);
color: var(--imbox-selected-text-color); color: var(--imbox-selected-text-color);
} }
:root[lwt-tree] imgroup[selected], :root[lwt-tree] imgroup[selected],
:root[lwt-tree] :-moz-any(imconv, imcontact, imgroup):not([selected=true]):hover { :root[lwt-tree] :-moz-any(imconv, richlistitem[is="chat-contact"], imgroup):not([selected=true]):hover {
background-color: var(--sidebar-highlight-background-color, hsla(0,0%,80%,.3)); background-color: var(--sidebar-highlight-background-color, hsla(0,0%,80%,.3));
color: var(--sidebar-highlight-text-color, var(--sidebar-text-color)); color: var(--sidebar-highlight-text-color, var(--sidebar-text-color));
} }
:root[lwt-tree-brighttext] imgroup[selected], :root[lwt-tree-brighttext] imgroup[selected],
:root[lwt-tree-brighttext] :-moz-any(imconv, imcontact, imgroup):not([selected=true]):hover { :root[lwt-tree-brighttext] :-moz-any(imconv, richlistitem[is="chat-contact"], imgroup):not([selected=true]):hover {
background-color: var(--sidebar-highlight-background-color, rgba(249,249,250,.1)); background-color: var(--sidebar-highlight-background-color, rgba(249,249,250,.1));
} }
:root[lwt-tree] imconv[selected=true], :root[lwt-tree] imconv[selected=true],
:root[lwt-tree] imcontact[selected=true] { :root[lwt-tree] richlistitem[is="chat-contact"][selected=true] {
border-color: var(--sidebar-border-color, hsla(0,0%,60%,.4)) border-color: var(--sidebar-border-color, hsla(0,0%,60%,.4))
} }
:root[lwt-tree-brighttext] imconv[selected=true], :root[lwt-tree-brighttext] imconv[selected=true],
:root[lwt-tree-brighttext] imcontact[selected=true] { :root[lwt-tree-brighttext] richlistitem[is="chat-contact"][selected=true] {
border-color: var(--sidebar-border-color, rgba(249,249,250,.2)) border-color: var(--sidebar-border-color, rgba(249,249,250,.2))
} }