UI: refines thread list item (#23207)

It will now replies count and participants list. Also the title will be OM excerpt or user defined title, no more default "Thread" title. Lastly, the author of the last reply is also shown as prefix of it.
This commit is contained in:
Joffrey JAFFEUX 2023-08-24 18:45:20 +02:00 committed by GitHub
parent 46c7e47f50
commit 39b598f304
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 110 additions and 35 deletions

View File

@ -12,6 +12,7 @@ class Chat::Api::ChannelThreadsController < Chat::ApiController
tracking: result.tracking,
memberships: result.memberships,
load_more_url: result.load_more_url,
threads_participants: result.participants,
),
::Chat::ThreadListSerializer,
root: false,

View File

@ -2,15 +2,30 @@
module Chat
class ThreadsView
attr_reader :user, :channel, :threads, :tracking, :memberships, :load_more_url
attr_reader :user,
:channel,
:threads,
:tracking,
:memberships,
:load_more_url,
:threads_participants
def initialize(channel:, threads:, user:, tracking:, memberships:, load_more_url:)
def initialize(
channel:,
threads:,
user:,
tracking:,
memberships:,
load_more_url:,
threads_participants:
)
@channel = channel
@threads = threads
@user = user
@tracking = tracking
@memberships = memberships
@load_more_url = load_more_url
@threads_participants = threads_participants
end
end
end

View File

@ -12,6 +12,7 @@ module Chat
membership: object.memberships.find { |m| m.thread_id == thread.id },
include_thread_preview: true,
include_thread_original_message: true,
participants: object.threads_participants[thread.id],
root: nil,
)
end

View File

@ -33,6 +33,7 @@ module Chat
model :threads
step :fetch_tracking
step :fetch_memberships
step :fetch_participants
step :build_load_more_url
# @!visibility private
@ -133,6 +134,10 @@ module Chat
)
end
def fetch_participants(threads:, **)
context.participants = ::Chat::ThreadParticipantQuery.call(thread_ids: threads.map(&:id))
end
def build_load_more_url(contract:, **)
load_more_params = { offset: context.offset + context.limit }.to_query
context.load_more_url =

View File

@ -17,8 +17,13 @@
<div class="chat-thread-list-item__om-user-avatar">
<Chat::UserAvatar @user={{@thread.originalMessage.user}} />
</div>
<div class="chat-thread-list-item__title overflow-ellipsis">
{{replace-emoji this.title}}
<div class="chat-thread-list-item__title">
{{#if this.title}}
{{replace-emoji this.title}}
{{else}}
{{@thread.originalMessage.excerpt}}
{{/if}}
</div>
<div class="chat-thread-list-item__unread-indicator">
<Chat::ThreadList::Item::UnreadIndicator @thread={{@thread}} />
@ -26,14 +31,26 @@
</div>
<div class="chat-thread-list-item__body">
{{replace-emoji (html-safe @thread.originalMessage.excerpt)}}
<span class="chat-thread-list-item__last-reply-author">
@{{@thread.preview.lastReplyUser.username}}:
</span>
<span class="chat-thread-list-item__last-reply-excerpt">
{{replace-emoji (html-safe @thread.preview.lastReplyExcerpt)}}
</span>
</div>
<div class="chat-thread-list-item__metadata">
<div class="chat-thread-list-item__participants"></div>
<div class="chat-thread-list-item__last-reply">
<Chat::Thread::Participants
@thread={{@thread}}
class="chat-thread-list-item__participants"
/>
<span class="chat-thread-list-item__replies-count">
{{i18n "chat.thread.replies" count=@thread.preview.replyCount}}
</span>
<span class="chat-thread-list-item__metadata__separator">·</span>
<div class="chat-thread-list-item__last-reply-timestamp">
{{#if @thread.preview.lastReplyCreatedAt}}
{{i18n "chat.thread.last_reply"}}
<span>{{i18n "chat.thread.last_reply"}}</span>
{{format-date @thread.preview.lastReplyCreatedAt leaveAgo="true"}}
{{/if}}
</div>

View File

@ -1,5 +1,5 @@
{{#if (gt @thread.preview.participantUsers.length 1)}}
<div class="chat-thread-participants">
<div class="chat-thread-participants" ...attributes>
<div class="chat-thread-participants__avatar-group">
{{#each @thread.preview.participantUsers as |user|}}
<Chat::UserAvatar

View File

@ -1,5 +1,4 @@
import { getOwner } from "discourse-common/lib/get-owner";
import I18n from "I18n";
import ChatMessagesManager from "discourse/plugins/chat/discourse/lib/chat-messages-manager";
import { escapeExpression } from "discourse/lib/utilities";
import { tracked } from "@glimmer/tracking";
@ -48,11 +47,7 @@ export default class ChatThread {
? ChatMessage.create(channel, args.original_message)
: null;
this.title =
args.title ||
`${I18n.t("chat.thread.default_title", {
thread_id: this.id,
})}`;
this.title = args.title;
if (args.current_user_membership) {
this.currentUserMembership = UserChatThreadMembership.create(

View File

@ -80,6 +80,7 @@
align-items: center;
justify-content: flex-end;
gap: 0.25rem;
margin-left: 0.5rem;
}
&__replies-count {

View File

@ -46,14 +46,34 @@
}
}
&__metadata {
display: flex;
justify-content: flex-end;
&__last-reply-author {
font-weight: 700;
}
&__last-reply {
&__metadata {
display: flex;
align-items: center;
justify-content: space-between;
}
&__metadata__separator {
padding-inline: 0.25rem;
font-weight: 700;
}
&__participants {
margin-right: 0.25rem;
}
&__replies-count {
margin-left: auto;
}
&__last-reply-timestamp,
&__replies-count {
color: var(--secondary-low);
font-size: var(--font-down-1);
@include ellipsis;
}
&__header {
@ -66,6 +86,7 @@
&__title {
flex: 1 1 auto;
font-weight: bold;
@include ellipsis;
}
&__unread-indicator {
@ -77,16 +98,10 @@
flex-direction: column;
box-sizing: border-box;
justify-content: center;
color: var(--primary);
&:hover,
&:visited {
color: var(--primary);
}
}
&__om-user-avatar {
margin-right: 0.5rem;
margin-right: 0.25rem;
flex: 0 0 auto;
}
}

View File

@ -1,5 +1,4 @@
.chat-thread-participants {
margin-left: 0.5rem;
&__other-count {
font-size: var(--font-down-2);
color: var(--primary-high);
@ -19,8 +18,6 @@
width: auto !important;
.avatar {
width: 24px;
height: 24px;
padding: 0;
}
}
@ -38,8 +35,6 @@
margin-right: -10px;
}
.avatar {
width: 22px;
height: 22px;
border: 1px solid var(--primary-very-low);
}
}

View File

@ -579,7 +579,6 @@ en:
thread:
title: "Title"
view_thread: View thread
default_title: "Thread"
replies:
one: "%{count} reply"
other: "%{count} replies"

View File

@ -260,6 +260,14 @@ RSpec.describe ::Chat::LookupChannelThreads do
end
end
describe "step - fetch_participants" do
it "returns correct participants" do
expect(result.participants).to eq(
::Chat::ThreadParticipantQuery.call(thread_ids: [thread_1, thread_2, thread_3].map(&:id)),
)
end
end
describe "step - build_load_more_url" do
it "returns a url with the correct params" do
expect(result.load_more_url).to eq("/chat/api/channels/#{channel_1.id}/threads?offset=10")

View File

@ -40,7 +40,7 @@ module PageObjects
end
def last_reply_datetime_selector(last_reply)
".chat-thread-list-item__last-reply .relative-date[data-time='#{(last_reply.created_at.iso8601.to_time.to_f * 1000).to_i}']"
".chat-thread-list-item__last-reply-timestamp .relative-date[data-time='#{(last_reply.created_at.iso8601.to_time.to_f * 1000).to_i}']"
end
def has_no_unread_item?(id)

View File

@ -83,10 +83,11 @@ describe "Thread list in side panel | full page", type: :system do
thread_2.add(current_user)
end
it "shows a default title for threads without a title" do
it "shows the OM excerpt for threads without a title" do
chat_page.visit_channel(channel)
channel_page.open_thread_list
expect(page).to have_content(I18n.t("js.chat.thread.default_title", thread_id: thread_1.id))
expect(page).to have_content(thread_1.original_message.excerpt)
end
it "shows the thread title with emoji" do
@ -125,6 +126,28 @@ describe "Thread list in side panel | full page", type: :system do
)
end
it "shows replies count" do
chat_page.visit_channel(channel)
channel_page.open_thread_list
expect(thread_list_page.item_by_id(thread_1.id)).to have_css(
".chat-thread-list-item__replies-count",
text: I18n.t("js.chat.thread.replies", count: thread_1.replies_count_cache),
)
end
it "shows participants" do
chat_page.visit_channel(channel)
channel_page.open_thread_list
expect(thread_list_page.item_by_id(thread_1.id)).to have_css(
".avatar[title='#{current_user.username}']",
)
expect(thread_list_page.item_by_id(thread_1.id)).to have_css(
".avatar[title='#{other_user.username}']",
)
end
it "opens a thread" do
chat_page.visit_channel(channel)
channel_page.open_thread_list