mirror of
https://github.com/discourse/discourse.git
synced 2025-05-26 04:33:38 +08:00
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:
@ -5,10 +5,15 @@ import { htmlSafe } from "@ember/template";
|
|||||||
import PluginOutlet from "discourse/components/plugin-outlet";
|
import PluginOutlet from "discourse/components/plugin-outlet";
|
||||||
import UserStatusMessage from "discourse/components/user-status-message";
|
import UserStatusMessage from "discourse/components/user-status-message";
|
||||||
import replaceEmoji from "discourse/helpers/replace-emoji";
|
import replaceEmoji from "discourse/helpers/replace-emoji";
|
||||||
|
import ChatChannelUnreadIndicator from "../chat-channel-unread-indicator";
|
||||||
|
|
||||||
export default class ChatChannelName extends Component {
|
export default class ChatChannelName extends Component {
|
||||||
@service currentUser;
|
@service currentUser;
|
||||||
|
|
||||||
|
get unreadIndicator() {
|
||||||
|
return this.args.unreadIndicator ?? false;
|
||||||
|
}
|
||||||
|
|
||||||
get firstUser() {
|
get firstUser() {
|
||||||
return this.args.channel.chatable.users[0];
|
return this.args.channel.chatable.users[0];
|
||||||
}
|
}
|
||||||
@ -37,27 +42,43 @@ export default class ChatChannelName extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
get showUserStatus() {
|
get showUserStatus() {
|
||||||
|
if (!this.args.channel.isDirectMessageChannel) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
return !!(this.users.length === 1 && this.users[0].status);
|
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>
|
<template>
|
||||||
<div class="chat-channel-name">
|
<div class="chat-channel-name">
|
||||||
{{#if @channel.isDirectMessageChannel}}
|
<div class="chat-channel-name__label">
|
||||||
{{#if this.groupDirectMessage}}
|
{{replaceEmoji this.channelTitle}}
|
||||||
<span class="chat-channel-name__label">
|
|
||||||
{{this.groupsDirectMessageTitle}}
|
{{#if this.unreadIndicator}}
|
||||||
</span>
|
<ChatChannelUnreadIndicator @channel={{@channel}} />
|
||||||
{{else}}
|
{{/if}}
|
||||||
<span class="chat-channel-name__label">
|
|
||||||
{{this.firstUser.username}}
|
{{#if this.showUserStatus}}
|
||||||
</span>
|
<UserStatusMessage
|
||||||
{{#if this.showUserStatus}}
|
@status={{get this.users "0.status"}}
|
||||||
<UserStatusMessage
|
@showDescription={{if this.site.mobileView "true"}}
|
||||||
@status={{get this.users "0.status"}}
|
class="chat-channel__user-status-message"
|
||||||
@showDescription={{if this.site.mobileView "true"}}
|
/>
|
||||||
class="chat-channel__user-status-message"
|
{{/if}}
|
||||||
/>
|
|
||||||
{{/if}}
|
{{#if this.showPluginOutlet}}
|
||||||
<PluginOutlet
|
<PluginOutlet
|
||||||
@name="after-chat-channel-username"
|
@name="after-chat-channel-username"
|
||||||
@outletArgs={{hash user=@user}}
|
@outletArgs={{hash user=@user}}
|
||||||
@ -65,15 +86,11 @@ export default class ChatChannelName extends Component {
|
|||||||
@connectorTagName=""
|
@connectorTagName=""
|
||||||
/>
|
/>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
{{else if @channel.isCategoryChannel}}
|
|
||||||
<span class="chat-channel-name__label">
|
|
||||||
{{replaceEmoji @channel.title}}
|
|
||||||
</span>
|
|
||||||
|
|
||||||
{{#if (has-block)}}
|
{{#if (has-block)}}
|
||||||
{{yield}}
|
{{yield}}
|
||||||
{{/if}}
|
{{/if}}
|
||||||
{{/if}}
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
}
|
}
|
||||||
|
@ -3,7 +3,7 @@ import ChannelIcon from "discourse/plugins/chat/discourse/components/channel-ico
|
|||||||
import ChannelName from "discourse/plugins/chat/discourse/components/channel-name";
|
import ChannelName from "discourse/plugins/chat/discourse/components/channel-name";
|
||||||
|
|
||||||
const ChatChannelTitle = <template>
|
const ChatChannelTitle = <template>
|
||||||
<span
|
<div
|
||||||
class={{concatClass
|
class={{concatClass
|
||||||
"chat-channel-title"
|
"chat-channel-title"
|
||||||
(if @channel.isDirectMessageChannel "is-dm" "is-category")
|
(if @channel.isDirectMessageChannel "is-dm" "is-category")
|
||||||
@ -12,10 +12,14 @@ const ChatChannelTitle = <template>
|
|||||||
<ChannelIcon @channel={{@channel}} />
|
<ChannelIcon @channel={{@channel}} />
|
||||||
<ChannelName @channel={{@channel}} />
|
<ChannelName @channel={{@channel}} />
|
||||||
|
|
||||||
|
{{#if @isUnread}}
|
||||||
|
<div class="unread-indicator {{if @isUrgent '-urgent'}}"></div>
|
||||||
|
{{/if}}
|
||||||
|
|
||||||
{{#if (has-block)}}
|
{{#if (has-block)}}
|
||||||
{{yield}}
|
{{yield}}
|
||||||
{{/if}}
|
{{/if}}
|
||||||
</span>
|
</div>
|
||||||
</template>;
|
</template>;
|
||||||
|
|
||||||
export default ChatChannelTitle;
|
export default ChatChannelTitle;
|
||||||
|
@ -1,12 +1,7 @@
|
|||||||
import Component from "@glimmer/component";
|
import Component from "@glimmer/component";
|
||||||
import I18n from "discourse-i18n";
|
import I18n from "discourse-i18n";
|
||||||
import ChatChannelUnreadIndicator from "./chat-channel-unread-indicator";
|
|
||||||
|
|
||||||
export default class ChatChannelMetadata extends Component {
|
export default class ChatChannelMetadata extends Component {
|
||||||
get unreadIndicator() {
|
|
||||||
return this.args.unreadIndicator ?? false;
|
|
||||||
}
|
|
||||||
|
|
||||||
get lastMessageFormattedDate() {
|
get lastMessageFormattedDate() {
|
||||||
return moment(this.args.channel.lastMessage.createdAt).calendar(null, {
|
return moment(this.args.channel.lastMessage.createdAt).calendar(null, {
|
||||||
sameDay: "LT",
|
sameDay: "LT",
|
||||||
@ -23,10 +18,6 @@ export default class ChatChannelMetadata extends Component {
|
|||||||
{{this.lastMessageFormattedDate}}
|
{{this.lastMessageFormattedDate}}
|
||||||
</div>
|
</div>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
|
|
||||||
{{#if this.unreadIndicator}}
|
|
||||||
<ChatChannelUnreadIndicator @channel={{@channel}} />
|
|
||||||
{{/if}}
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
}
|
}
|
||||||
|
@ -196,11 +196,8 @@ export default class ChatChannelRow extends Component {
|
|||||||
>
|
>
|
||||||
<ChannelIcon @channel={{@channel}} />
|
<ChannelIcon @channel={{@channel}} />
|
||||||
<div class="chat-channel-row__info">
|
<div class="chat-channel-row__info">
|
||||||
<ChannelName @channel={{@channel}} />
|
<ChannelName @channel={{@channel}} @unreadIndicator={{true}} />
|
||||||
<ChatChannelMetadata
|
<ChatChannelMetadata @channel={{@channel}} />
|
||||||
@channel={{@channel}}
|
|
||||||
@unreadIndicator={{true}}
|
|
||||||
/>
|
|
||||||
{{#if this.shouldRenderLastMessage}}
|
{{#if this.shouldRenderLastMessage}}
|
||||||
<div class="chat-channel__last-message">
|
<div class="chat-channel__last-message">
|
||||||
{{replaceEmoji (htmlSafe @channel.lastMessage.excerpt)}}
|
{{replaceEmoji (htmlSafe @channel.lastMessage.excerpt)}}
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
import Component from "@glimmer/component";
|
import Component from "@glimmer/component";
|
||||||
import { service } from "@ember/service";
|
import { service } from "@ember/service";
|
||||||
import { gt, not } from "truth-helpers";
|
import { gt, not } from "truth-helpers";
|
||||||
import concatClass from "discourse/helpers/concat-class";
|
|
||||||
import ChannelTitle from "discourse/plugins/chat/discourse/components/channel-title";
|
import ChannelTitle from "discourse/plugins/chat/discourse/components/channel-title";
|
||||||
|
|
||||||
export default class Channel extends Component {
|
export default class Channel extends Component {
|
||||||
@ -22,14 +21,11 @@ export default class Channel extends Component {
|
|||||||
class="chat-message-creator__chatable -category-channel"
|
class="chat-message-creator__chatable -category-channel"
|
||||||
data-disabled={{not @item.enabled}}
|
data-disabled={{not @item.enabled}}
|
||||||
>
|
>
|
||||||
<ChannelTitle @channel={{@item.model}} />
|
<ChannelTitle
|
||||||
|
@channel={{@item.model}}
|
||||||
{{#if (gt @item.tracking.unreadCount 0)}}
|
@isUnread={{gt @item.tracking.unreadCount 0}}
|
||||||
|
@isUrgent={{this.isUrgent}}
|
||||||
<div
|
/>
|
||||||
class={{concatClass "unread-indicator" (if this.isUrgent "-urgent")}}
|
|
||||||
></div>
|
|
||||||
{{/if}}
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
}
|
}
|
||||||
|
@ -20,7 +20,6 @@ export default class ChatChannelsManager extends Service {
|
|||||||
@service chatStateManager;
|
@service chatStateManager;
|
||||||
@service currentUser;
|
@service currentUser;
|
||||||
@service router;
|
@service router;
|
||||||
@service site;
|
|
||||||
@service siteSettings;
|
@service siteSettings;
|
||||||
@tracked _cached = new TrackedObject();
|
@tracked _cached = new TrackedObject();
|
||||||
|
|
||||||
@ -130,11 +129,7 @@ export default class ChatChannelsManager extends Service {
|
|||||||
channel.isCategoryChannel && channel.currentUserMembership.following
|
channel.isCategoryChannel && channel.currentUserMembership.following
|
||||||
);
|
);
|
||||||
|
|
||||||
if (this.site.mobileView) {
|
return this.#sortChannelsByActivity(channels);
|
||||||
return this.#sortChannelsByActivity(channels);
|
|
||||||
} else {
|
|
||||||
return channels.sort((a, b) => a?.slug?.localeCompare?.(b?.slug));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@cached
|
@cached
|
||||||
|
@ -7,6 +7,8 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
&__label {
|
&__label {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -15,4 +17,21 @@
|
|||||||
width: 1em;
|
width: 1em;
|
||||||
vertical-align: baseline;
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -90,22 +90,6 @@
|
|||||||
align-items: flex-end;
|
align-items: flex-end;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
margin-left: 0.5em;
|
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 {
|
&__metadata-date {
|
||||||
|
@ -267,11 +267,14 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.unread-indicator {
|
.unread-indicator {
|
||||||
margin-left: 0.5rem;
|
|
||||||
width: 8px;
|
width: 8px;
|
||||||
height: 8px;
|
height: 8px;
|
||||||
background: var(--tertiary);
|
background: var(--tertiary);
|
||||||
border-radius: 100%;
|
border-radius: 100%;
|
||||||
|
|
||||||
|
&.-urgent {
|
||||||
|
background: var(--success);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -100,7 +100,7 @@ RSpec.describe "Channel - Info - Settings page", type: :system do
|
|||||||
expect(page.find(".c-channel-settings__name")["innerHTML"].strip).to eq(
|
expect(page.find(".c-channel-settings__name")["innerHTML"].strip).to eq(
|
||||||
"<script>alert('hello')</script>",
|
"<script>alert('hello')</script>",
|
||||||
)
|
)
|
||||||
expect(page.find(".chat-channel-name__label")["innerHTML"].strip).to eq(
|
expect(page.find(".chat-channel-name__label")["innerHTML"].strip).to include(
|
||||||
"<script>alert('hello')</script>",
|
"<script>alert('hello')</script>",
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
@ -66,4 +66,23 @@ module("Discourse Chat | Component | <ChannelName />", function (hooks) {
|
|||||||
users.mapBy("username").join(", ")
|
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"));
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
@ -3,7 +3,6 @@ import { render } from "@ember/test-helpers";
|
|||||||
import hbs from "htmlbars-inline-precompile";
|
import hbs from "htmlbars-inline-precompile";
|
||||||
import { module, test } from "qunit";
|
import { module, test } from "qunit";
|
||||||
import { setupRenderingTest } from "discourse/tests/helpers/component-test";
|
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";
|
import ChatFabricators from "discourse/plugins/chat/discourse/lib/fabricators";
|
||||||
|
|
||||||
module("Discourse Chat | Component | chat-channel-metadata", function (hooks) {
|
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")
|
.dom(".chat-channel__metadata-date")
|
||||||
.hasText(lastMessageSentAt.format("LT"));
|
.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"));
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
Reference in New Issue
Block a user