Add support for actions in the unreads bar from plugins (#24265)

* Add support for actions in the unreads bar from plugins

* Adding channelId as parameter

* Fixing linter errors

* Changing the extensibility to the new messages separator

* Making everything work with the plugin

* Fixing linter and types errors

* Fixing unit test

* Tiny improvement in the styles

---------

Co-authored-by: Mattermost Build <build@mattermost.com>
This commit is contained in:
Jesús Espino 2023-08-24 12:20:29 +02:00 committed by GitHub
parent f46694037d
commit 4145fd2f4e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 85 additions and 5 deletions

View File

@ -10,7 +10,11 @@ import NewMessageSeparator from './new_message_separator';
describe('components/post_view/new_message_separator', () => {
test('should render new_message_separator', () => {
renderWithIntl(
<NewMessageSeparator separatorId='1234'/>,
<NewMessageSeparator
separatorId='1234'
newMessagesSeparatorActions={[]}
lastViewedAt={0}
/>,
);
const newMessage = screen.getByText('New Messages');

View File

@ -3,16 +3,38 @@
import React from 'react';
import {FormattedMessage} from 'react-intl';
import {PluginComponent} from 'types/store/plugins';
import NotificationSeparator from 'components/widgets/separator/notification-separator';
type Props = {
separatorId: string;
wrapperRef?: React.RefObject<HTMLDivElement>;
newMessagesSeparatorActions: PluginComponent[];
lastViewedAt: number;
channelId?: string;
threadId?: string;
}
export default class NewMessageSeparator extends React.PureComponent<Props> {
render(): JSX.Element {
const pluginItems = this.props.newMessagesSeparatorActions?.
map((item) => {
if (!item.component) {
return null;
}
const Component = item.component as any;
return (
<Component
key={item.id}
lastViewedAt={this.props.lastViewedAt}
channelId={this.props.channelId}
threadId={this.props.threadId}
/>
);
});
return (
<div
ref={this.props.wrapperRef}
@ -23,7 +45,7 @@ export default class NewMessageSeparator extends React.PureComponent<Props> {
id='posts_view.newMsg'
defaultMessage='New Messages'
/>
{pluginItems}
</NotificationSeparator>
</div>
);

View File

@ -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`] = `
<NewMessageSeparator
channelId="channel_id_1"
lastViewedAt={0}
newMessagesSeparatorActions={Array []}
separatorId="start-of-new-messages"
/>
`;

View File

@ -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);

View File

@ -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', () => {

View File

@ -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<PostListRowProps> {
if (PostListUtils.isStartOfNewMessages(listId)) {
return (
<NewMessageSeparator separatorId={listId}/>
<NewMessageSeparator
separatorId={listId}
newMessagesSeparatorActions={this.props.newMessagesSeparatorActions}
channelId={this.props.channelId}
lastViewedAt={this.props.lastViewedAt}
/>
);
}

View File

@ -380,6 +380,8 @@ export default class PostList extends React.PureComponent<Props, State> {
isLastPost={isLastPost}
loadingNewerPosts={this.props.loadingNewerPosts}
loadingOlderPosts={this.props.loadingOlderPosts}
lastViewedAt={this.props.lastViewedAt}
channelId={this.props.channelId}
/>
</div>
);

View File

@ -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,
};
};
}

View File

@ -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<TimestampProps>;
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 <NewMessageSeparator separatorId={listId}/>;
return (
<NewMessageSeparator
separatorId={listId}
lastViewedAt={lastViewedAt}
threadId={threadId}
newMessagesSeparatorActions={newMessagesSeparatorActions}
/>
);
case isRootPost:
return (

View File

@ -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();

View File

@ -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<Props, State> {
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}
/>
</div>
);

View File

@ -6,6 +6,7 @@
}
.separator__text {
display: inline-flex;
color: #f80;
font-weight: normal;
}

View File

@ -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

View File

@ -183,6 +183,7 @@ const initialComponents: PluginsState['components'] = {
PostDropdownMenu: [],
PostAction: [],
PostEditorAction: [],
NewMessagesSeparatorAction: [],
Product: [],
RightHandSidebarComponent: [],
UserGuideDropdownItem: [],

View File

@ -29,6 +29,7 @@ export type PluginsState = {
PostDropdownMenu: PluginComponent[];
PostAction: PluginComponent[];
PostEditorAction: PluginComponent[];
NewMessagesSeparatorAction: PluginComponent[];
FilePreview: PluginComponent[];
MainMenu: PluginComponent[];
LinkTooltip: PluginComponent[];