UX: remove last reply from My Threads preview + restyle (#25568)
On mobile, when viewing the My Threads area, each thread will show: - The avatar of the last responder in the thread, overlaid with the chat thread symbol to visually distinguish this area from DMs. - Either the thread title, where applicable, or the first message of the thread, truncated to fit on one line. - The channel where the thread originated. - The last message sent in the thread, truncated to fit on one line. - When the last message was sent in the thread. --------- Co-authored-by: David Battersby <info@davidbattersby.com>
This commit is contained in:
parent
0e3a8b15f5
commit
cfd72fa65c
|
@ -19,6 +19,10 @@ export default class ChatChannelIcon extends Component {
|
||||||
return htmlSafe(`color: #${this.args.channel.chatable.color}`);
|
return htmlSafe(`color: #${this.args.channel.chatable.color}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get isThreadsList() {
|
||||||
|
return this.args.thread ?? false;
|
||||||
|
}
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
{{#if @channel.isDirectMessageChannel}}
|
{{#if @channel.isDirectMessageChannel}}
|
||||||
<div class="chat-channel-icon">
|
<div class="chat-channel-icon">
|
||||||
|
@ -44,6 +48,19 @@ export default class ChatChannelIcon extends Component {
|
||||||
{{/if}}
|
{{/if}}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
{{else if this.isThreadsList}}
|
||||||
|
<div class="chat-channel-icon">
|
||||||
|
<div class="chat-channel-icon --avatar">
|
||||||
|
<ChatUserAvatar
|
||||||
|
@user={{@thread.originalMessage.user}}
|
||||||
|
@interactive={{true}}
|
||||||
|
@showPresence={{false}}
|
||||||
|
/>
|
||||||
|
<div class="avatar-flair --threads">
|
||||||
|
{{icon "discourse-threads"}}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
</template>
|
</template>
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,14 +24,14 @@ export default class ChatThreadHeader extends Component {
|
||||||
route = "chat.channel.threads";
|
route = "chat.channel.threads";
|
||||||
title = I18n.t("chat.return_to_threads_list");
|
title = I18n.t("chat.return_to_threads_list");
|
||||||
models = this.channel?.routeModels;
|
models = this.channel?.routeModels;
|
||||||
} else if (!this.currentUser.isInDoNotDisturb() && this.unreadCount > 0) {
|
|
||||||
route = "chat.channel.threads";
|
|
||||||
title = I18n.t("chat.return_to_threads_list");
|
|
||||||
models = this.channel?.routeModels;
|
|
||||||
} else if (prevPage === "chat.threads") {
|
} else if (prevPage === "chat.threads") {
|
||||||
route = "chat.threads";
|
route = "chat.threads";
|
||||||
title = I18n.t("chat.my_threads.title");
|
title = I18n.t("chat.my_threads.title");
|
||||||
models = [];
|
models = [];
|
||||||
|
} else if (!this.currentUser.isInDoNotDisturb() && this.unreadCount > 0) {
|
||||||
|
route = "chat.channel.threads";
|
||||||
|
title = I18n.t("chat.return_to_threads_list");
|
||||||
|
models = this.channel?.routeModels;
|
||||||
} else {
|
} else {
|
||||||
route = "chat.channel.index";
|
route = "chat.channel.index";
|
||||||
title = I18n.t("chat.return_to_channel");
|
title = I18n.t("chat.return_to_channel");
|
||||||
|
@ -53,6 +53,12 @@ export default class ChatThreadHeader extends Component {
|
||||||
return this.channel?.threadsManager?.unreadThreadCount;
|
return this.channel?.threadsManager?.unreadThreadCount;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get showThreadUnreadIndicator() {
|
||||||
|
return (
|
||||||
|
this.backLink.route === "chat.channel.threads" && this.unreadCount > 0
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<Navbar @showFullTitle={{@showFullTitle}} as |navbar|>
|
<Navbar @showFullTitle={{@showFullTitle}} as |navbar|>
|
||||||
{{#if (and this.channel.threadingEnabled @thread)}}
|
{{#if (and this.channel.threadingEnabled @thread)}}
|
||||||
|
@ -61,7 +67,9 @@ export default class ChatThreadHeader extends Component {
|
||||||
@routeModels={{this.backLink.models}}
|
@routeModels={{this.backLink.models}}
|
||||||
@title={{this.backLink.title}}
|
@title={{this.backLink.title}}
|
||||||
>
|
>
|
||||||
|
{{#if this.showThreadUnreadIndicator}}
|
||||||
<ChatThreadHeaderUnreadIndicator @channel={{this.channel}} />
|
<ChatThreadHeaderUnreadIndicator @channel={{this.channel}} />
|
||||||
|
{{/if}}
|
||||||
{{icon "chevron-left"}}
|
{{icon "chevron-left"}}
|
||||||
</navbar.BackButton>
|
</navbar.BackButton>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
|
|
|
@ -3,15 +3,18 @@ import { cached } from "@glimmer/tracking";
|
||||||
import { service } from "@ember/service";
|
import { service } from "@ember/service";
|
||||||
import i18n from "discourse-common/helpers/i18n";
|
import i18n from "discourse-common/helpers/i18n";
|
||||||
import { bind } from "discourse-common/utils/decorators";
|
import { bind } from "discourse-common/utils/decorators";
|
||||||
|
import ChannelIcon from "discourse/plugins/chat/discourse/components/channel-icon";
|
||||||
import ChannelTitle from "discourse/plugins/chat/discourse/components/channel-title";
|
import ChannelTitle from "discourse/plugins/chat/discourse/components/channel-title";
|
||||||
import List from "discourse/plugins/chat/discourse/components/chat/list";
|
import List from "discourse/plugins/chat/discourse/components/chat/list";
|
||||||
import ThreadIndicator from "discourse/plugins/chat/discourse/components/chat-message-thread-indicator";
|
import ThreadIndicator from "discourse/plugins/chat/discourse/components/chat-message-thread-indicator";
|
||||||
import ThreadTitle from "discourse/plugins/chat/discourse/components/thread-title";
|
import ThreadTitle from "discourse/plugins/chat/discourse/components/thread-title";
|
||||||
|
import ThreadPreview from "discourse/plugins/chat/discourse/components/user-threads/preview";
|
||||||
|
|
||||||
export default class UserThreads extends Component {
|
export default class UserThreads extends Component {
|
||||||
@service chat;
|
@service chat;
|
||||||
@service chatApi;
|
@service chatApi;
|
||||||
@service chatChannelsManager;
|
@service chatChannelsManager;
|
||||||
|
@service site;
|
||||||
|
|
||||||
@cached
|
@cached
|
||||||
get threadsCollection() {
|
get threadsCollection() {
|
||||||
|
@ -41,14 +44,23 @@ export default class UserThreads extends Component {
|
||||||
>
|
>
|
||||||
<list.Item as |thread|>
|
<list.Item as |thread|>
|
||||||
<div class="c-user-thread" data-id={{thread.id}}>
|
<div class="c-user-thread" data-id={{thread.id}}>
|
||||||
<ThreadTitle @thread={{thread}} />
|
{{#if this.site.mobileView}}
|
||||||
|
<ChannelIcon @thread={{thread}} />
|
||||||
|
{{/if}}
|
||||||
|
|
||||||
<ChannelTitle @channel={{thread.channel}} />
|
<ChannelTitle @channel={{thread.channel}} />
|
||||||
|
<ThreadTitle @thread={{thread}} />
|
||||||
|
|
||||||
|
{{#if this.site.mobileView}}
|
||||||
|
<ThreadPreview @preview={{thread.preview}} />
|
||||||
|
{{else}}
|
||||||
<ThreadIndicator
|
<ThreadIndicator
|
||||||
@message={{thread.originalMessage}}
|
@message={{thread.originalMessage}}
|
||||||
@interactiveUser={{false}}
|
@interactiveUser={{false}}
|
||||||
@interactiveThread={{false}}
|
@interactiveThread={{false}}
|
||||||
tabindex="-1"
|
tabindex="-1"
|
||||||
/>
|
/>
|
||||||
|
{{/if}}
|
||||||
</div>
|
</div>
|
||||||
</list.Item>
|
</list.Item>
|
||||||
<list.EmptyState>
|
<list.EmptyState>
|
||||||
|
|
|
@ -0,0 +1,23 @@
|
||||||
|
import Component from "@glimmer/component";
|
||||||
|
import formatDate from "discourse/helpers/format-date";
|
||||||
|
|
||||||
|
export default class ThreadPreview extends Component {
|
||||||
|
get lastReplyDate() {
|
||||||
|
return formatDate(this.args.preview.lastReplyCreatedAt, { leaveAgo: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<span class="chat-message-thread-indicator__last-reply-timestamp">
|
||||||
|
{{this.lastReplyDate}}
|
||||||
|
</span>
|
||||||
|
<div class="c-user-thread__excerpt">
|
||||||
|
<div class="c-user-thread__excerpt-poster">
|
||||||
|
{{@preview.lastReplyUser.username}}
|
||||||
|
</div>
|
||||||
|
<span>:</span>
|
||||||
|
<span class="c-user-thread__excerpt-text">
|
||||||
|
{{@preview.lastReplyExcerpt}}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
}
|
|
@ -1,9 +1,67 @@
|
||||||
.chat-user-threads {
|
.c-user-thread {
|
||||||
.chat__thread-title {
|
display: grid;
|
||||||
justify-content: space-between;
|
grid-template-areas:
|
||||||
|
"avatar category timestamp"
|
||||||
|
"avatar title indicator"
|
||||||
|
"avatar excerpt excerpt";
|
||||||
|
grid-template-columns: auto 1fr auto;
|
||||||
|
grid-column-gap: 0.75em;
|
||||||
|
margin-inline: 0;
|
||||||
|
padding: 0.5rem 1.5rem;
|
||||||
|
|
||||||
|
.chat-channel-icon {
|
||||||
|
grid-area: avatar;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
.avatar {
|
||||||
|
margin-top: 4px;
|
||||||
|
width: var(--channel-list-avatar-size);
|
||||||
|
height: var(--channel-list-avatar-size);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.c-user-thread {
|
.avatar-flair.--threads {
|
||||||
margin-inline: 1.5rem;
|
position: absolute;
|
||||||
|
top: -4px;
|
||||||
|
right: -3px;
|
||||||
|
background: var(--primary-low);
|
||||||
|
border-radius: 50%;
|
||||||
|
padding: 0.2em;
|
||||||
|
line-height: var(--line-height-small);
|
||||||
|
border: 2px solid var(--secondary-very-high);
|
||||||
|
color: var(--primary-high);
|
||||||
|
}
|
||||||
|
|
||||||
|
.chat__thread-title-container {
|
||||||
|
@include ellipsis;
|
||||||
|
grid-area: title;
|
||||||
|
.chat__thread-title {
|
||||||
|
&__name {
|
||||||
|
@include ellipsis;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.chat-channel-title {
|
||||||
|
grid-area: category;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chat-message-thread-indicator__last-reply-timestamp {
|
||||||
|
grid-area: timestamp;
|
||||||
|
font-size: var(--font-down-2-rem);
|
||||||
|
align-self: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.c-user-thread__excerpt {
|
||||||
|
@include ellipsis;
|
||||||
|
grid-area: excerpt;
|
||||||
|
display: flex;
|
||||||
|
color: var(--primary-high);
|
||||||
|
}
|
||||||
|
|
||||||
|
.c-user-thread__excerpt-text {
|
||||||
|
margin-left: 0.25em;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -244,4 +244,22 @@ RSpec.describe "User threads", type: :system do
|
||||||
expect(user_threads_page).to have_threads
|
expect(user_threads_page).to have_threads
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
context "when in mobile", mobile: true do
|
||||||
|
before do
|
||||||
|
chat_thread_chain_bootstrap(channel: channel_1, users: [current_user, Fabricate(:user)])
|
||||||
|
end
|
||||||
|
|
||||||
|
it "has the expected UI elements" do
|
||||||
|
chat_page.visit_user_threads
|
||||||
|
|
||||||
|
expect(user_threads_page).to have_threads(count: 1)
|
||||||
|
expect(user_threads_page).to have_css(".chat-user-avatar")
|
||||||
|
expect(user_threads_page).to have_css(".chat__thread-title__name")
|
||||||
|
expect(user_threads_page).to have_css(".chat-channel-name")
|
||||||
|
expect(user_threads_page).to have_css(".c-user-thread__excerpt")
|
||||||
|
expect(user_threads_page).to have_css(".c-user-thread__excerpt-poster")
|
||||||
|
expect(user_threads_page).to have_css(".c-user-thread .relative-date")
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
Loading…
Reference in New Issue