{
render(): JSX.Element {
+ const pluginItems = this.props.newMessagesSeparatorActions?.
+ map((item) => {
+ if (!item.component) {
+ return null;
+ }
+
+ const Component = item.component as any;
+ return (
+
+ );
+ });
+
return (
{
id='posts_view.newMsg'
defaultMessage='New Messages'
/>
-
+ {pluginItems}
);
diff --git a/webapp/channels/src/components/post_view/post_list_row/__snapshots__/post_list_row.test.tsx.snap b/webapp/channels/src/components/post_view/post_list_row/__snapshots__/post_list_row.test.tsx.snap
index 081343fdeb..c5959855b3 100644
--- a/webapp/channels/src/components/post_view/post_list_row/__snapshots__/post_list_row.test.tsx.snap
+++ b/webapp/channels/src/components/post_view/post_list_row/__snapshots__/post_list_row.test.tsx.snap
@@ -94,6 +94,9 @@ exports[`components/post_view/post_list_row should render more messages loading
exports[`components/post_view/post_list_row should render new messages line 1`] = `
`;
diff --git a/webapp/channels/src/components/post_view/post_list_row/index.ts b/webapp/channels/src/components/post_view/post_list_row/index.ts
index ce41bb470f..71858eda84 100644
--- a/webapp/channels/src/components/post_view/post_list_row/index.ts
+++ b/webapp/channels/src/components/post_view/post_list_row/index.ts
@@ -29,10 +29,11 @@ function mapStateToProps(state: GlobalState, ownProps: OwnProps) {
const limitsLoaded = getCloudLimitsLoaded(state);
const post = getPost(state, ownProps.listId);
const currentUserId = getCurrentUserId(state);
+ const newMessagesSeparatorActions = state.plugins.components.NewMessagesSeparatorAction;
const props: Pick<
PostListRowProps,
- 'shortcutReactToLastPostEmittedFrom' | 'usage' | 'limits' | 'limitsLoaded' | 'exceededLimitChannelId' | 'firstInaccessiblePostTime' | 'post' | 'currentUserId'
+ 'shortcutReactToLastPostEmittedFrom' | 'usage' | 'limits' | 'limitsLoaded' | 'exceededLimitChannelId' | 'firstInaccessiblePostTime' | 'post' | 'currentUserId' | 'newMessagesSeparatorActions'
> = {
shortcutReactToLastPostEmittedFrom,
usage,
@@ -40,6 +41,7 @@ function mapStateToProps(state: GlobalState, ownProps: OwnProps) {
limitsLoaded,
post,
currentUserId,
+ newMessagesSeparatorActions,
};
if ((ownProps.listId === PostListRowListIds.OLDER_MESSAGES_LOADER || ownProps.listId === PostListRowListIds.CHANNEL_INTRO_MESSAGE) && limitsLoaded) {
const currentChannelId = getCurrentChannelId(state);
diff --git a/webapp/channels/src/components/post_view/post_list_row/post_list_row.test.tsx b/webapp/channels/src/components/post_view/post_list_row/post_list_row.test.tsx
index 5650c41989..e4fe8916e4 100644
--- a/webapp/channels/src/components/post_view/post_list_row/post_list_row.test.tsx
+++ b/webapp/channels/src/components/post_view/post_list_row/post_list_row.test.tsx
@@ -40,6 +40,9 @@ describe('components/post_view/post_list_row', () => {
usage: {} as CloudUsage,
post: TestHelper.getPostMock({id: 'post_id_1'}),
currentUserId: 'user_id_1',
+ newMessagesSeparatorActions: [],
+ lastViewedAt: 0,
+ channelId: 'channel_id_1',
};
test('should render more messages loading indicator', () => {
diff --git a/webapp/channels/src/components/post_view/post_list_row/post_list_row.tsx b/webapp/channels/src/components/post_view/post_list_row/post_list_row.tsx
index 3acecdccc3..aa3738beec 100644
--- a/webapp/channels/src/components/post_view/post_list_row/post_list_row.tsx
+++ b/webapp/channels/src/components/post_view/post_list_row/post_list_row.tsx
@@ -12,6 +12,7 @@ import {CloudUsage, Limits} from '@mattermost/types/cloud';
import type {emitShortcutReactToLastPostFrom} from 'actions/post_actions';
import CombinedUserActivityPost from 'components/post_view/combined_user_activity_post';
+import {PluginComponent} from 'types/store/plugins';
import {Post} from '@mattermost/types/posts';
import DateSeparator from 'components/post_view/date_separator';
import NewMessageSeparator from 'components/post_view/new_message_separator/new_message_separator';
@@ -54,6 +55,10 @@ export type PostListRowProps = {
limitsLoaded: boolean;
exceededLimitChannelId?: string;
firstInaccessiblePostTime?: number;
+ lastViewedAt: number;
+ channelId: string;
+
+ newMessagesSeparatorActions: PluginComponent[];
actions: {
@@ -109,7 +114,12 @@ export default class PostListRow extends React.PureComponent {
if (PostListUtils.isStartOfNewMessages(listId)) {
return (
-
+
);
}
diff --git a/webapp/channels/src/components/post_view/post_list_virtualized/post_list_virtualized.tsx b/webapp/channels/src/components/post_view/post_list_virtualized/post_list_virtualized.tsx
index 98b7a28379..843696d8c9 100644
--- a/webapp/channels/src/components/post_view/post_list_virtualized/post_list_virtualized.tsx
+++ b/webapp/channels/src/components/post_view/post_list_virtualized/post_list_virtualized.tsx
@@ -380,6 +380,8 @@ export default class PostList extends React.PureComponent {
isLastPost={isLastPost}
loadingNewerPosts={this.props.loadingNewerPosts}
loadingOlderPosts={this.props.loadingOlderPosts}
+ lastViewedAt={this.props.lastViewedAt}
+ channelId={this.props.channelId}
/>
);
diff --git a/webapp/channels/src/components/threading/virtualized_thread_viewer/index.ts b/webapp/channels/src/components/threading/virtualized_thread_viewer/index.ts
index e786684366..97ee36a111 100644
--- a/webapp/channels/src/components/threading/virtualized_thread_viewer/index.ts
+++ b/webapp/channels/src/components/threading/virtualized_thread_viewer/index.ts
@@ -46,12 +46,15 @@ function makeMapStateToProps() {
showDate: !useRelativeTimestamp,
lastViewedAt: collapsedThreads ? lastViewedAt : undefined,
});
+ const newMessagesSeparatorActions = state.plugins.components.NewMessagesSeparatorAction;
return {
currentUserId,
directTeammate,
lastPost,
replyListIds,
+ lastViewedAt,
+ newMessagesSeparatorActions,
};
};
}
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 1f6479b357..c5b71deba1 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
@@ -10,6 +10,7 @@ import CombinedUserActivityPost from 'components/post_view/combined_user_activit
import DateSeparator from 'components/post_view/date_separator';
import NewMessageSeparator from 'components/post_view/new_message_separator/new_message_separator';
import {Props as TimestampProps} from 'components/timestamp/timestamp';
+import {PluginComponent} from 'types/store/plugins';
import PostComponent from 'components/post';
@@ -26,6 +27,9 @@ type Props = {
onCardClick: (post: Post) => void;
previousPostId: string;
timestampProps?: Partial;
+ lastViewedAt: number;
+ threadId: string;
+ newMessagesSeparatorActions: PluginComponent[];
};
function noop() {}
@@ -38,6 +42,9 @@ function ThreadViewerRow({
onCardClick,
previousPostId,
timestampProps,
+ lastViewedAt,
+ threadId,
+ newMessagesSeparatorActions,
}: Props) {
switch (true) {
case PostListUtils.isDateLine(listId): {
@@ -51,7 +58,14 @@ function ThreadViewerRow({
}
case PostListUtils.isStartOfNewMessages(listId):
- return ;
+ return (
+
+ );
case isRootPost:
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 0c8faa4afa..aea2a803c8 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
@@ -63,6 +63,8 @@ describe('components/threading/VirtualizedThreadViewer', () => {
teamId: '',
useRelativeTimestamp: true,
isThreadView: true,
+ lastViewedAt: 0,
+ newMessagesSeparatorActions: [],
};
test('should scroll to the bottom when the current user makes a new post in the thread', () => {
const scrollToBottom = jest.fn();
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 47ca3d60ed..b0ad23b8af 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
@@ -20,6 +20,7 @@ import {getNewMessageIndex, getPreviousPostId, getLatestPostId} from 'utils/post
import NewRepliesBanner from 'components/new_replies_banner';
import FloatingTimestamp from 'components/post_view/floating_timestamp';
import {THREADING_TIME as BASE_THREADING_TIME} from 'components/threading/common/options';
+import {PluginComponent} from 'types/store/plugins';
import CreateComment from './create_comment';
import Row from './thread_viewer_row';
@@ -36,6 +37,8 @@ type Props = {
selected: Post | FakePost;
useRelativeTimestamp: boolean;
isThreadView: boolean;
+ lastViewedAt: number;
+ newMessagesSeparatorActions: PluginComponent[];
}
type State = {
@@ -386,6 +389,9 @@ class ThreadViewerVirtualized extends PureComponent {
onCardClick={this.props.onCardClick}
previousPostId={getPreviousPostId(data, index)}
timestampProps={this.props.useRelativeTimestamp ? THREADING_TIME : undefined}
+ lastViewedAt={this.props.lastViewedAt}
+ threadId={this.props.selected.id}
+ newMessagesSeparatorActions={this.props.newMessagesSeparatorActions}
/>
);
diff --git a/webapp/channels/src/components/widgets/separator/notification-separator.scss b/webapp/channels/src/components/widgets/separator/notification-separator.scss
index 2e4854f043..15edd7897c 100644
--- a/webapp/channels/src/components/widgets/separator/notification-separator.scss
+++ b/webapp/channels/src/components/widgets/separator/notification-separator.scss
@@ -6,6 +6,7 @@
}
.separator__text {
+ display: inline-flex;
color: #f80;
font-weight: normal;
}
diff --git a/webapp/channels/src/plugins/registry.ts b/webapp/channels/src/plugins/registry.ts
index 1702723007..46f7c224c7 100644
--- a/webapp/channels/src/plugins/registry.ts
+++ b/webapp/channels/src/plugins/registry.ts
@@ -508,6 +508,12 @@ export default class PluginRegistry {
return dispatchPluginComponentAction('PostEditorAction', this.id, component);
});
+ // Register a component to the add to the new messages separator.
+ // Accepts a React component. Returns a unique identifier.
+ registerNewMessagesSeparatorActionComponent = reArg(['component'], ({component}: DPluginComponentProp) => {
+ return dispatchPluginComponentAction('NewMessagesSeparatorAction', this.id, component);
+ });
+
// Register a post menu list item by providing some text and an action function.
// Accepts the following:
// - text - A string or React element to display in the menu
diff --git a/webapp/channels/src/reducers/plugins/index.ts b/webapp/channels/src/reducers/plugins/index.ts
index b072e74d2d..8b6dcc1023 100644
--- a/webapp/channels/src/reducers/plugins/index.ts
+++ b/webapp/channels/src/reducers/plugins/index.ts
@@ -183,6 +183,7 @@ const initialComponents: PluginsState['components'] = {
PostDropdownMenu: [],
PostAction: [],
PostEditorAction: [],
+ NewMessagesSeparatorAction: [],
Product: [],
RightHandSidebarComponent: [],
UserGuideDropdownItem: [],
diff --git a/webapp/channels/src/types/store/plugins.ts b/webapp/channels/src/types/store/plugins.ts
index 9143dcd4c4..633a07b23b 100644
--- a/webapp/channels/src/types/store/plugins.ts
+++ b/webapp/channels/src/types/store/plugins.ts
@@ -29,6 +29,7 @@ export type PluginsState = {
PostDropdownMenu: PluginComponent[];
PostAction: PluginComponent[];
PostEditorAction: PluginComponent[];
+ NewMessagesSeparatorAction: PluginComponent[];
FilePreview: PluginComponent[];
MainMenu: PluginComponent[];
LinkTooltip: PluginComponent[];