diff --git a/webapp/channels/src/components/threading/channel_threads/thread_footer/thread_footer.tsx b/webapp/channels/src/components/threading/channel_threads/thread_footer/thread_footer.tsx
index 2ea6deca3e..2e8b23b2d8 100644
--- a/webapp/channels/src/components/threading/channel_threads/thread_footer/thread_footer.tsx
+++ b/webapp/channels/src/components/threading/channel_threads/thread_footer/thread_footer.tsx
@@ -60,6 +60,7 @@ function ThreadFooter({
channel_id: channelId,
},
} = thread;
+
const participantIds = useMemo(() => (participants || []).map(({id}) => id).reverse(), [participants]);
const handleReply = useCallback((e) => {
diff --git a/webapp/channels/src/components/threading/thread_viewer/thread_viewer.test.tsx b/webapp/channels/src/components/threading/thread_viewer/thread_viewer.test.tsx
index f2a1a284c0..e29ca45ad3 100644
--- a/webapp/channels/src/components/threading/thread_viewer/thread_viewer.test.tsx
+++ b/webapp/channels/src/components/threading/thread_viewer/thread_viewer.test.tsx
@@ -32,6 +32,7 @@ describe('components/threading/ThreadViewer', () => {
user_id: post.user_id,
channel_id: post.channel_id,
message: post.message,
+ reply_count: 3,
};
const channel: Channel = TestHelper.getChannelMock({
diff --git a/webapp/channels/src/components/threading/virtualized_thread_viewer/thread_viewer_row.tsx b/webapp/channels/src/components/threading/virtualized_thread_viewer/thread_viewer_row.tsx
index 85ea0d3764..ff758a4f00 100644
--- a/webapp/channels/src/components/threading/virtualized_thread_viewer/thread_viewer_row.tsx
+++ b/webapp/channels/src/components/threading/virtualized_thread_viewer/thread_viewer_row.tsx
@@ -22,6 +22,7 @@ import Reply from './reply';
type Props = {
a11yIndex: number;
currentUserId: string;
+ replyCount: number;
isRootPost: boolean;
isLastPost: boolean;
listId: string;
@@ -40,6 +41,7 @@ function ThreadViewerRow({
isRootPost,
isLastPost,
listId,
+ replyCount,
onCardClick,
previousPostId,
timestampProps,
@@ -70,13 +72,20 @@ function ThreadViewerRow({
case isRootPost:
return (
-
+ <>
+
+ {replyCount > 0 && (
+
+
{`${replyCount} Replies`}
+
+ )}
+ >
);
case PostListUtils.isCombinedUserActivityPost(listId): {
return (
diff --git a/webapp/channels/src/components/threading/virtualized_thread_viewer/virtualized_thread_viewer.test.tsx b/webapp/channels/src/components/threading/virtualized_thread_viewer/virtualized_thread_viewer.test.tsx
index b8e11ed76b..1fd485777f 100644
--- a/webapp/channels/src/components/threading/virtualized_thread_viewer/virtualized_thread_viewer.test.tsx
+++ b/webapp/channels/src/components/threading/virtualized_thread_viewer/virtualized_thread_viewer.test.tsx
@@ -26,6 +26,7 @@ function getBasePropsAndState(): [Props, DeepPartial] {
const currentUser = TestHelper.getUserMock({roles: 'role'});
const post = TestHelper.getPostMock({
channel_id: channel.id,
+ reply_count: 0,
});
const directTeammate: UserProfile = TestHelper.getUserMock();
diff --git a/webapp/channels/src/components/threading/virtualized_thread_viewer/virtualized_thread_viewer.tsx b/webapp/channels/src/components/threading/virtualized_thread_viewer/virtualized_thread_viewer.tsx
index 0b958e8544..015a168755 100644
--- a/webapp/channels/src/components/threading/virtualized_thread_viewer/virtualized_thread_viewer.tsx
+++ b/webapp/channels/src/components/threading/virtualized_thread_viewer/virtualized_thread_viewer.tsx
@@ -3,14 +3,17 @@
import {DynamicSizeList} from 'dynamic-virtualized-list';
import type {OnScrollArgs, OnItemsRenderedArgs} from 'dynamic-virtualized-list';
-import React, {PureComponent} from 'react';
+import React, {PureComponent, useMemo} from 'react';
import type {RefObject} from 'react';
+import {useSelector} from 'react-redux';
import AutoSizer from 'react-virtualized-auto-sizer';
import type {Channel} from '@mattermost/types/channels';
import type {Post} from '@mattermost/types/posts';
import type {UserProfile} from '@mattermost/types/users';
+import {getPost} from 'mattermost-redux/selectors/entities/posts';
+import {makeGetThreadOrSynthetic} from 'mattermost-redux/selectors/entities/threads';
import {isDateLine, isStartOfNewMessages, isCreateComment} from 'mattermost-redux/utils/post_list';
import NewRepliesBanner from 'components/new_replies_banner';
@@ -22,6 +25,7 @@ import DelayedAction from 'utils/delayed_action';
import {getNewMessageIndex, getPreviousPostId, getLatestPostId} from 'utils/post_utils';
import * as Utils from 'utils/utils';
+import type {GlobalState} from 'types/store';
import type {PluginComponent} from 'types/store/plugins';
import type {FakePost} from 'types/store/rhs';
@@ -350,6 +354,14 @@ class ThreadViewerVirtualized extends PureComponent {
const isLastPost = itemId === this.props.lastPost.id;
const isRootPost = itemId === this.props.selected.id;
+ const post = useSelector((state: GlobalState) => getPost(state, this.props.selected.id));
+ const getThreadOrSynthetic = useMemo(makeGetThreadOrSynthetic, []);
+
+ const totalReplies = useSelector((state: GlobalState) => {
+ const thread = getThreadOrSynthetic(state, post);
+ return thread.reply_count || 0;
+ });
+
if (!isDateLine(itemId) && !isStartOfNewMessages(itemId) && !isCreateComment(itemId) && !isRootPost) {
a11yIndex++;
}
@@ -379,6 +391,7 @@ class ThreadViewerVirtualized extends PureComponent {
isRootPost={isRootPost}
isLastPost={isLastPost}
listId={itemId}
+ replyCount={totalReplies}
onCardClick={this.props.onCardClick}
previousPostId={getPreviousPostId(data, index)}
timestampProps={this.props.useRelativeTimestamp ? THREADING_TIME : undefined}
diff --git a/webapp/channels/src/sass/components/_post-right.scss b/webapp/channels/src/sass/components/_post-right.scss
index f75dcd8bbd..8c4326297b 100644
--- a/webapp/channels/src/sass/components/_post-right.scss
+++ b/webapp/channels/src/sass/components/_post-right.scss
@@ -38,9 +38,37 @@
display: none;
}
+ .root-post__divider {
+ position: relative;
+ display: flex;
+ height: 28px;
+ align-items: center;
+ margin: 0 0 4px 30px;
+
+ div {
+ z-index: 1;
+ padding: 0 12px;
+ margin-left: 34px;
+ background: rgba(v(center-channel-bg-rgb), 1);
+ color: rgba(v(center-channel-color-rgb), 0.72);
+ font-size: 12px;
+ font-weight: 600;
+ }
+
+ &::before {
+ position: absolute;
+ top: calc(50% - 1px);
+ left: 0;
+ display: block;
+ width: 100%;
+ border-top: 1px solid rgba(var(--center-channel-color-rgb), 0.12);
+ content: "";
+ }
+ }
+
.post {
&.post--root {
- padding-top: 2rem;
+ padding-top: 16px;
.post__body {
background: transparent !important;
@@ -52,9 +80,9 @@
}
.post-pre-header__icons-container {
- width: 60px; // If the width of post__img changes, this needs to be adjusted accordingly
- padding-right: 12px; // If the padding of post__img changes, this needs to be adjusted accordingly
- margin-left: 0; // if left margin of post__content changes, this needs to be adjusted accordingly
+ width: 54px; // If the width of post__img changes, this needs to be adjusted accordingly;
+ padding-right: 12px; // If the padding of post__img changes, this needs to be adjusted accordingly;
+ margin-left: 0; // if left margin of post__content changes, this needs to be adjusted accordingly;
}
.post__header {
@@ -119,8 +147,8 @@
}
.post__img {
- width: 60px; // if this changes, the width of post-pre-header__icons-container needs to be adjusted accordingly
- padding: 2px 12px 0 0; // if the right padding changes, the padding of post-pre-header__icons-container needs to be adjusted accordingly
+ width: 54px; // if this changes, the width of post-pre-header__icons-container needs to be adjusted accordingly;
+ padding: 2px 12px 0 0; // if the right padding changes, the padding of post-pre-header__icons-container needs to be adjusted accordingly;
}
.post-body {
diff --git a/webapp/channels/src/sass/responsive/_mobile.scss b/webapp/channels/src/sass/responsive/_mobile.scss
index 9fdcf0f6ac..08bba0ec53 100644
--- a/webapp/channels/src/sass/responsive/_mobile.scss
+++ b/webapp/channels/src/sass/responsive/_mobile.scss
@@ -440,7 +440,7 @@
.post {
&.post--thread {
.post-pre-header__icons-container {
- width: 60px; // If the width of post__img changes, this needs to be adjusted accordingly
+ width: 44px; // If the width of post__img changes, this needs to be adjusted accordingly
padding-right: 12px; // If the padding of post__img changes, this needs to be adjusted accordingly
margin-left: 0; // if left margin of post__content changes, this needs to be adjusted accordingly
}
diff --git a/webapp/channels/src/selectors/rhs.ts b/webapp/channels/src/selectors/rhs.ts
index 42b924e6ce..b7cffd4a19 100644
--- a/webapp/channels/src/selectors/rhs.ts
+++ b/webapp/channels/src/selectors/rhs.ts
@@ -94,6 +94,7 @@ export const getSelectedPost = createSelector(
message: localizeMessage('rhs_thread.rootPostDeletedMessage.body', 'Part of this thread has been deleted due to a data retention policy. You can no longer reply to this thread.'),
channel_id: selectedPostChannelId,
user_id: currentUserId,
+ reply_count: 0,
};
},
);
diff --git a/webapp/channels/src/types/store/rhs.ts b/webapp/channels/src/types/store/rhs.ts
index c45dc2fc63..446e70a70c 100644
--- a/webapp/channels/src/types/store/rhs.ts
+++ b/webapp/channels/src/types/store/rhs.ts
@@ -16,6 +16,7 @@ export type FakePost = {
exists: boolean;
type: PostType;
message: string;
+ reply_count: number;
channel_id: Channel['id'];
user_id: UserProfile['id'];
};