FIX: makes chat user avatar show presence by default (#22490)

It's way more common to have presence enabled than disabled, so we should have been making it the default from start.

This commit also changes the namespace of `<ChatUserAvatar />` into `<Chat::UserAvatar />` and refactors tests.
This commit is contained in:
Joffrey JAFFEUX 2023-07-10 09:36:20 +02:00 committed by GitHub
parent 81a16a105e
commit 03e495186f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
23 changed files with 121 additions and 114 deletions

View File

@ -7,7 +7,7 @@
{{@channel.chatable.users.length}}
</span>
{{else}}
<ChatUserAvatar @user={{@channel.chatable.users.firstObject}} />
<Chat::UserAvatar @user={{@channel.chatable.users.firstObject}} />
{{/if}}
</div>
{{/if}}

View File

@ -5,7 +5,7 @@
>
<div class="chat-reply">
{{d-icon (if @message.editing "pencil-alt" "reply")}}
<ChatUserAvatar @user={{@message.user}} />
<Chat::UserAvatar @user={{@message.user}} />
<span class="chat-reply__username">{{@message.user.username}}</span>
<span class="chat-reply__excerpt">
{{replace-emoji @message.excerpt}}

View File

@ -16,7 +16,7 @@
<div class="chat-message-actions">
<div class="selected-message-container">
<div class="selected-message">
<ChatUserAvatar @user={{this.message.user}} />
<Chat::UserAvatar @user={{this.message.user}} />
<span
{{on "touchstart" this.expandReply passive=true}}
role="button"

View File

@ -9,7 +9,7 @@
{{#if @message.inReplyTo.chatWebhookEvent.emoji}}
<ChatEmojiAvatar @emoji={{@message.inReplyTo.chatWebhookEvent.emoji}} />
{{else}}
<ChatUserAvatar @user={{@message.inReplyTo.user}} />
<Chat::UserAvatar @user={{@message.inReplyTo.user}} />
{{/if}}
<span class="chat-reply__excerpt">

View File

@ -10,7 +10,7 @@
>
<div class="chat-message-thread-indicator__last-reply-avatar">
<ChatUserAvatar
<Chat::UserAvatar
@user={{@message.thread.preview.lastReplyUser}}
@avatarSize="small"
/>

View File

@ -1,11 +0,0 @@
<div
class="chat-user-avatar {{if (and this.isOnline @showPresence) 'is-online'}}"
>
<div
role="button"
class="chat-user-avatar-container clickable"
data-user-card={{@user.username}}
>
{{avatar @user imageSize=this.avatarSize}}
</div>
</div>

View File

@ -1,6 +1,6 @@
{{#if @user}}
<a href={{this.userPath}} data-user-card={{@user.username}}>
<ChatUserAvatar @user={{@user}} @avatarSize="medium" />
<Chat::UserAvatar @user={{@user}} @avatarSize="medium" />
</a>
<a href={{this.userPath}} data-user-card={{@user.username}}>
<ChatUserDisplayName @user={{@user}} />

View File

@ -1,4 +1,4 @@
<ChatUserAvatar @user={{@content.model}} @showPresence={{true}} />
<Chat::UserAvatar @user={{@content.model}} />
<ChatUserDisplayName @user={{@content.model}} />
{{#if (gt @content.tracking.unreadCount 0)}}

View File

@ -1,4 +1,4 @@
<ChatUserAvatar @user={{@selection.model}} @showPresence={{true}} />
<Chat::UserAvatar @user={{@selection.model}} />
<span class="chat-message-creator__selection-item__username">
{{@selection.model.username}}

View File

@ -2,10 +2,6 @@
{{#if @message.chatWebhookEvent.emoji}}
<ChatEmojiAvatar @emoji={{@message.chatWebhookEvent.emoji}} />
{{else}}
<ChatUserAvatar
@showPresence={{true}}
@user={{@message.user}}
@avatarSize="medium"
/>
<Chat::UserAvatar @user={{@message.user}} @avatarSize="medium" />
{{/if}}
</div>

View File

@ -14,7 +14,7 @@
>
<div class="chat-thread-list-item__header">
<div class="chat-thread-list-item__om-user-avatar">
<ChatUserAvatar @user={{@thread.originalMessage.user}} />
<Chat::UserAvatar @user={{@thread.originalMessage.user}} />
</div>
<div class="chat-thread-list-item__title overflow-ellipsis">
{{replace-emoji this.title}}

View File

@ -2,7 +2,7 @@
<div class="chat-thread-participants">
<div class="chat-thread-participants__avatar-group">
{{#each @thread.preview.participantUsers as |user|}}
<ChatUserAvatar
<Chat::UserAvatar
@user={{user}}
@avatarSize="tiny"
@showPresence={{false}}

View File

@ -0,0 +1,9 @@
<div class="chat-user-avatar {{if this.isOnline 'is-online'}}">
<div
role="button"
class="chat-user-avatar__container clickable"
data-user-card={{@user.username}}
>
{{avatar @user imageSize=this.avatarSize}}
</div>
</div>

View File

@ -8,12 +8,19 @@ export default class ChatUserAvatar extends Component {
return this.args.avatarSize || "tiny";
}
get showPresence() {
return this.args.showPresence ?? true;
}
get isOnline() {
const users = (this.args.chat || this.chat).presenceChannel?.users;
return (
!!users?.findBy("id", this.args.user?.id) ||
!!users?.findBy("username", this.args.user?.username)
this.showPresence &&
!!users?.find(
({ id, username }) =>
this.args.user?.id === id || this.args.user?.username === username
)
);
}
}

View File

@ -186,60 +186,6 @@ html.ios-device.keyboard-visible body #main-outlet .full-page-chat {
}
}
.chat-user-avatar {
@include unselectable;
display: flex;
align-items: center;
.chat-message-container:not(.has-reply) & {
width: var(--message-left-width);
flex-shrink: 0;
}
&.is-online {
.chat-user-avatar-container .avatar {
box-shadow: 0px 0px 0px 1px var(--success);
border: 1px solid var(--secondary);
padding: 0;
}
}
.chat-user-avatar-container {
position: relative;
padding: 1px; // for is-online box-shadow effect, preventing cutoff
.avatar {
padding: 1px; // for is-online box-shadow effect, preventing shift
}
.chat-user-presence-flair {
box-sizing: border-box;
position: absolute;
background-color: var(--success);
border: 1px solid var(--secondary);
border-radius: 50%;
.chat-message & {
width: 10px;
height: 10px;
right: 0px;
bottom: 0px;
}
.chat-channel-title & {
width: 8px;
height: 8px;
right: -1px;
bottom: -1px;
}
}
}
.chat-channel-title & {
width: auto;
}
}
.topic-title-chat-icon {
display: inline-block;
* {

View File

@ -121,7 +121,7 @@
}
}
.chat-message-avatar .chat-user-avatar .chat-user-avatar-container .avatar,
.chat-message-avatar .chat-user-avatar .chat-user-avatar__container .avatar,
.chat-emoji-avatar .chat-emoji-avatar-container {
width: 28px;
height: 28px;

View File

@ -11,7 +11,7 @@
align-items: center;
justify-content: flex-end;
.chat-user-avatar-container {
.chat-user-avatar__container {
padding: 0;
}
@ -32,6 +32,7 @@
&__avatar-group {
flex-direction: row;
justify-content: flex-start;
.chat-user-avatar {
&:not(:last-child) {
margin-right: -10px;

View File

@ -0,0 +1,53 @@
.chat-user-avatar {
@include unselectable;
display: flex;
align-items: center;
.chat-message-container:not(.has-reply) & {
width: var(--message-left-width);
flex-shrink: 0;
}
&.is-online {
.chat-user-avatar__container .avatar {
box-shadow: 0px 0px 0px 1px var(--success);
border: 1px solid var(--secondary);
padding: 0;
}
}
&__container {
position: relative;
padding: 1px; // for is-online box-shadow effect, preventing cutoff
.avatar {
padding: 1px; // for is-online box-shadow effect, preventing shift
}
.chat-user-presence-flair {
box-sizing: border-box;
position: absolute;
background-color: var(--success);
border: 1px solid var(--secondary);
border-radius: 50%;
.chat-message & {
width: 10px;
height: 10px;
right: 0px;
bottom: 0px;
}
.chat-channel-title & {
width: 8px;
height: 8px;
right: -1px;
bottom: -1px;
}
}
}
.chat-channel-title & {
width: auto;
}
}

View File

@ -59,3 +59,4 @@
@import "chat-message-error";
@import "chat-new-message-modal";
@import "chat-message-creator";
@import "chat-user-avatar";

View File

@ -33,7 +33,7 @@ module PageObjects
def has_participant?(user)
find(@context).has_css?(
".chat-thread-participants__avatar-group .chat-user-avatar .chat-user-avatar-container[data-user-card=\"#{user.username}\"] img",
".chat-thread-participants__avatar-group .chat-user-avatar .chat-user-avatar__container[data-user-card=\"#{user.username}\"] img",
)
end

View File

@ -32,7 +32,7 @@ module PageObjects
end
def avatar_selector(user)
".chat-thread-list-item__om-user-avatar .chat-user-avatar .chat-user-avatar-container[data-user-card=\"#{user.username}\"] img"
".chat-thread-list-item__om-user-avatar .chat-user-avatar .chat-user-avatar__container[data-user-card=\"#{user.username}\"] img"
end
def last_reply_datetime_selector(last_reply)

View File

@ -71,7 +71,7 @@ module("Discourse Chat | Component | chat-channel-title", function (hooks) {
const user = this.channel.chatable.users[0];
assert.true(
exists(`.chat-user-avatar-container .avatar[title="${user.username}"]`)
exists(`.chat-user-avatar__container .avatar[title="${user.username}"]`)
);
assert.strictEqual(

View File

@ -1,50 +1,55 @@
import { setupRenderingTest } from "discourse/tests/helpers/component-test";
import { exists } from "discourse/tests/helpers/qunit-helpers";
import hbs from "htmlbars-inline-precompile";
import { module, test } from "qunit";
import { render } from "@ember/test-helpers";
import fabricators from "discourse/plugins/chat/discourse/lib/fabricators";
const user = {
id: 1,
username: "markvanlan",
name: null,
avatar_template: "/letter_avatar_proxy/v4/letter/m/48db29/{size}.png",
};
function containerSelector(user, options = {}) {
let onlineSelector = ":not(.is-online)";
if (options.online) {
onlineSelector = ".is-online";
}
module("Discourse Chat | Component | chat-user-avatar", function (hooks) {
return `.chat-user-avatar${onlineSelector} .chat-user-avatar__container[data-user-card=${user.username}] .avatar[title=${user.username}]`;
}
module("Discourse Chat | Component | <Chat::UserAvatar />", function (hooks) {
setupRenderingTest(hooks);
test("user is not online", async function (assert) {
this.set("user", user);
this.set("chat", { presenceChannel: { users: [] } });
this.user = fabricators.user();
this.chat = { presenceChannel: { users: [] } };
await render(
hbs`<ChatUserAvatar @chat={{this.chat}} @user={{this.user}} />`
hbs`<Chat::UserAvatar @chat={{this.chat}} @user={{this.user}} />`
);
assert.true(
exists(
`.chat-user-avatar .chat-user-avatar-container[data-user-card=${user.username}] .avatar[title=${user.username}]`
)
);
assert.false(exists(".chat-user-avatar.is-online"));
assert.dom(containerSelector(this.user, { online: false })).exists();
});
test("user is online", async function (assert) {
this.set("user", user);
this.set("chat", {
presenceChannel: { users: [{ id: user.id }] },
});
this.user = fabricators.user();
this.chat = {
presenceChannel: { users: [{ id: this.user.id }] },
};
await render(
hbs`<ChatUserAvatar @showPresence={{true}} @chat={{this.chat}} @user={{this.user}} />`
hbs`<Chat::UserAvatar @chat={{this.chat}} @user={{this.user}} />`
);
assert.true(
exists(
`.chat-user-avatar .chat-user-avatar-container[data-user-card=${user.username}] .avatar[title=${user.username}]`
)
assert.dom(containerSelector(this.user, { online: true })).exists();
});
test("showPresence=false", async function (assert) {
this.user = fabricators.user();
this.chat = {
presenceChannel: { users: [{ id: this.user.id }] },
};
await render(
hbs`<Chat::UserAvatar @showPresence={{false}} @chat={{this.chat}} @user={{this.user}} />`
);
assert.true(exists(".chat-user-avatar.is-online"));
assert.dom(containerSelector(this.user, { online: false })).exists();
});
});