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
14 changed files with 110 additions and 35 deletions
@@ -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,
+17 -2
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
@@ -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
@@ -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 =
@@ -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>
@@ -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
@@ -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(
@@ -80,6 +80,7 @@
align-items: center;
justify-content: flex-end;
gap: 0.25rem;
margin-left: 0.5rem;
}
&__replies-count {
@@ -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;
}
}
@@ -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);
}
}
@@ -579,7 +579,6 @@ en:
thread:
title: "Title"
view_thread: View thread
default_title: "Thread"
replies:
one: "%{count} reply"
other: "%{count} replies"
@@ -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")
@@ -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)
@@ -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