UX: chat drawer increase unread channel visibility (#28731)

This change increases the visibility of unread channels to make them stand out more in drawer mode (desktop).

When a channel is unread:

- it floats to the top;
- when multiple channels are unread, they are sorted alphabetically (equal to how it’s done on mobile)
- the unread indicator blue dot moves to directly right of the channel name
This commit is contained in:
David Battersby 2024-09-05 13:36:50 +04:00 committed by GitHub
parent 67ce50c141
commit e991574389
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
12 changed files with 95 additions and 90 deletions

View File

@ -5,10 +5,15 @@ import { htmlSafe } from "@ember/template";
import PluginOutlet from "discourse/components/plugin-outlet";
import UserStatusMessage from "discourse/components/user-status-message";
import replaceEmoji from "discourse/helpers/replace-emoji";
import ChatChannelUnreadIndicator from "../chat-channel-unread-indicator";
export default class ChatChannelName extends Component {
@service currentUser;
get unreadIndicator() {
return this.args.unreadIndicator ?? false;
}
get firstUser() {
return this.args.channel.chatable.users[0];
}
@ -37,27 +42,43 @@ export default class ChatChannelName extends Component {
}
get showUserStatus() {
if (!this.args.channel.isDirectMessageChannel) {
return false;
}
return !!(this.users.length === 1 && this.users[0].status);
}
get channelTitle() {
if (this.args.channel.isDirectMessageChannel) {
return this.groupDirectMessage
? this.groupsDirectMessageTitle
: this.firstUser.username;
}
return this.args.channel.title;
}
get showPluginOutlet() {
return this.args.channel.isDirectMessageChannel && !this.groupDirectMessage;
}
<template>
<div class="chat-channel-name">
{{#if @channel.isDirectMessageChannel}}
{{#if this.groupDirectMessage}}
<span class="chat-channel-name__label">
{{this.groupsDirectMessageTitle}}
</span>
{{else}}
<span class="chat-channel-name__label">
{{this.firstUser.username}}
</span>
{{#if this.showUserStatus}}
<UserStatusMessage
@status={{get this.users "0.status"}}
@showDescription={{if this.site.mobileView "true"}}
class="chat-channel__user-status-message"
/>
{{/if}}
<div class="chat-channel-name__label">
{{replaceEmoji this.channelTitle}}
{{#if this.unreadIndicator}}
<ChatChannelUnreadIndicator @channel={{@channel}} />
{{/if}}
{{#if this.showUserStatus}}
<UserStatusMessage
@status={{get this.users "0.status"}}
@showDescription={{if this.site.mobileView "true"}}
class="chat-channel__user-status-message"
/>
{{/if}}
{{#if this.showPluginOutlet}}
<PluginOutlet
@name="after-chat-channel-username"
@outletArgs={{hash user=@user}}
@ -65,15 +86,11 @@ export default class ChatChannelName extends Component {
@connectorTagName=""
/>
{{/if}}
{{else if @channel.isCategoryChannel}}
<span class="chat-channel-name__label">
{{replaceEmoji @channel.title}}
</span>
{{#if (has-block)}}
{{yield}}
{{/if}}
{{/if}}
</div>
</div>
</template>
}

View File

@ -3,7 +3,7 @@ import ChannelIcon from "discourse/plugins/chat/discourse/components/channel-ico
import ChannelName from "discourse/plugins/chat/discourse/components/channel-name";
const ChatChannelTitle = <template>
<span
<div
class={{concatClass
"chat-channel-title"
(if @channel.isDirectMessageChannel "is-dm" "is-category")
@ -12,10 +12,14 @@ const ChatChannelTitle = <template>
<ChannelIcon @channel={{@channel}} />
<ChannelName @channel={{@channel}} />
{{#if @isUnread}}
<div class="unread-indicator {{if @isUrgent '-urgent'}}"></div>
{{/if}}
{{#if (has-block)}}
{{yield}}
{{/if}}
</span>
</div>
</template>;
export default ChatChannelTitle;

View File

@ -1,12 +1,7 @@
import Component from "@glimmer/component";
import I18n from "discourse-i18n";
import ChatChannelUnreadIndicator from "./chat-channel-unread-indicator";
export default class ChatChannelMetadata extends Component {
get unreadIndicator() {
return this.args.unreadIndicator ?? false;
}
get lastMessageFormattedDate() {
return moment(this.args.channel.lastMessage.createdAt).calendar(null, {
sameDay: "LT",
@ -23,10 +18,6 @@ export default class ChatChannelMetadata extends Component {
{{this.lastMessageFormattedDate}}
</div>
{{/if}}
{{#if this.unreadIndicator}}
<ChatChannelUnreadIndicator @channel={{@channel}} />
{{/if}}
</div>
</template>
}

View File

@ -196,11 +196,8 @@ export default class ChatChannelRow extends Component {
>
<ChannelIcon @channel={{@channel}} />
<div class="chat-channel-row__info">
<ChannelName @channel={{@channel}} />
<ChatChannelMetadata
@channel={{@channel}}
@unreadIndicator={{true}}
/>
<ChannelName @channel={{@channel}} @unreadIndicator={{true}} />
<ChatChannelMetadata @channel={{@channel}} />
{{#if this.shouldRenderLastMessage}}
<div class="chat-channel__last-message">
{{replaceEmoji (htmlSafe @channel.lastMessage.excerpt)}}

View File

@ -1,7 +1,6 @@
import Component from "@glimmer/component";
import { service } from "@ember/service";
import { gt, not } from "truth-helpers";
import concatClass from "discourse/helpers/concat-class";
import ChannelTitle from "discourse/plugins/chat/discourse/components/channel-title";
export default class Channel extends Component {
@ -22,14 +21,11 @@ export default class Channel extends Component {
class="chat-message-creator__chatable -category-channel"
data-disabled={{not @item.enabled}}
>
<ChannelTitle @channel={{@item.model}} />
{{#if (gt @item.tracking.unreadCount 0)}}
<div
class={{concatClass "unread-indicator" (if this.isUrgent "-urgent")}}
></div>
{{/if}}
<ChannelTitle
@channel={{@item.model}}
@isUnread={{gt @item.tracking.unreadCount 0}}
@isUrgent={{this.isUrgent}}
/>
</div>
</template>
}

View File

@ -20,7 +20,6 @@ export default class ChatChannelsManager extends Service {
@service chatStateManager;
@service currentUser;
@service router;
@service site;
@service siteSettings;
@tracked _cached = new TrackedObject();
@ -130,11 +129,7 @@ export default class ChatChannelsManager extends Service {
channel.isCategoryChannel && channel.currentUserMembership.following
);
if (this.site.mobileView) {
return this.#sortChannelsByActivity(channels);
} else {
return channels.sort((a, b) => a?.slug?.localeCompare?.(b?.slug));
}
return this.#sortChannelsByActivity(channels);
}
@cached

View File

@ -7,6 +7,8 @@
}
&__label {
display: flex;
align-items: center;
white-space: nowrap;
}
@ -15,4 +17,21 @@
width: 1em;
vertical-align: baseline;
}
.chat-channel-unread-indicator {
@include chat-unread-indicator;
display: flex;
align-items: center;
justify-content: center;
width: 8px;
height: 8px;
margin-left: 0.75em;
&.-urgent {
width: auto;
height: auto;
min-width: 0.6em;
padding: 0.3em 0.5em;
}
}
}

View File

@ -90,22 +90,6 @@
align-items: flex-end;
flex-direction: column;
margin-left: 0.5em;
.chat-channel-unread-indicator {
@include chat-unread-indicator;
display: flex;
align-items: center;
justify-content: center;
width: 8px;
height: 8px;
&.-urgent {
width: auto;
height: auto;
min-width: 0.6em;
padding: 0.3em 0.5em;
}
}
}
&__metadata-date {

View File

@ -267,11 +267,14 @@
}
.unread-indicator {
margin-left: 0.5rem;
width: 8px;
height: 8px;
background: var(--tertiary);
border-radius: 100%;
&.-urgent {
background: var(--success);
}
}
}

View File

@ -100,7 +100,7 @@ RSpec.describe "Channel - Info - Settings page", type: :system do
expect(page.find(".c-channel-settings__name")["innerHTML"].strip).to eq(
"&lt;script&gt;alert('hello')&lt;/script&gt;",
)
expect(page.find(".chat-channel-name__label")["innerHTML"].strip).to eq(
expect(page.find(".chat-channel-name__label")["innerHTML"].strip).to include(
"&lt;script&gt;alert('hello')&lt;/script&gt;",
)
end

View File

@ -66,4 +66,23 @@ module("Discourse Chat | Component | <ChannelName />", function (hooks) {
users.mapBy("username").join(", ")
);
});
test("unreadIndicator", async function (assert) {
const channel = new ChatFabricators(getOwner(this)).directMessageChannel();
channel.tracking.unreadCount = 1;
let unreadIndicator = true;
await render(
<template><ChannelName @channel={{channel}} @unreadIndicator={{unreadIndicator}}/></template>
);
assert.true(exists(".chat-channel-unread-indicator"));
unreadIndicator = false;
await render(
<template><ChannelName @channel={{channel}} @unreadIndicator={{unreadIndicator}}/></template>
);
assert.false(exists(".chat-channel-unread-indicator"));
});
});

View File

@ -3,7 +3,6 @@ import { render } from "@ember/test-helpers";
import hbs from "htmlbars-inline-precompile";
import { module, test } from "qunit";
import { setupRenderingTest } from "discourse/tests/helpers/component-test";
import { exists } from "discourse/tests/helpers/qunit-helpers";
import ChatFabricators from "discourse/plugins/chat/discourse/lib/fabricators";
module("Discourse Chat | Component | chat-channel-metadata", function (hooks) {
@ -29,23 +28,4 @@ module("Discourse Chat | Component | chat-channel-metadata", function (hooks) {
.dom(".chat-channel__metadata-date")
.hasText(lastMessageSentAt.format("LT"));
});
test("unreadIndicator", async function (assert) {
this.channel = new ChatFabricators(getOwner(this)).directMessageChannel();
this.channel.tracking.unreadCount = 1;
this.unreadIndicator = true;
await render(
hbs`<ChatChannelMetadata @channel={{this.channel}} @unreadIndicator={{this.unreadIndicator}}/>`
);
assert.true(exists(".chat-channel-unread-indicator"));
this.unreadIndicator = false;
await render(
hbs`<ChatChannelMetadata @channel={{this.channel}} @unreadIndicator={{this.unreadIndicator}}/>`
);
assert.false(exists(".chat-channel-unread-indicator"));
});
});