mirror of
https://github.com/mattermost/mattermost.git
synced 2025-02-25 18:55:24 -06:00
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:
parent
f46694037d
commit
4145fd2f4e
@ -10,7 +10,11 @@ import NewMessageSeparator from './new_message_separator';
|
|||||||
describe('components/post_view/new_message_separator', () => {
|
describe('components/post_view/new_message_separator', () => {
|
||||||
test('should render new_message_separator', () => {
|
test('should render new_message_separator', () => {
|
||||||
renderWithIntl(
|
renderWithIntl(
|
||||||
<NewMessageSeparator separatorId='1234'/>,
|
<NewMessageSeparator
|
||||||
|
separatorId='1234'
|
||||||
|
newMessagesSeparatorActions={[]}
|
||||||
|
lastViewedAt={0}
|
||||||
|
/>,
|
||||||
);
|
);
|
||||||
|
|
||||||
const newMessage = screen.getByText('New Messages');
|
const newMessage = screen.getByText('New Messages');
|
||||||
|
@ -3,16 +3,38 @@
|
|||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import {FormattedMessage} from 'react-intl';
|
import {FormattedMessage} from 'react-intl';
|
||||||
|
import {PluginComponent} from 'types/store/plugins';
|
||||||
|
|
||||||
import NotificationSeparator from 'components/widgets/separator/notification-separator';
|
import NotificationSeparator from 'components/widgets/separator/notification-separator';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
separatorId: string;
|
separatorId: string;
|
||||||
wrapperRef?: React.RefObject<HTMLDivElement>;
|
wrapperRef?: React.RefObject<HTMLDivElement>;
|
||||||
|
newMessagesSeparatorActions: PluginComponent[];
|
||||||
|
lastViewedAt: number;
|
||||||
|
channelId?: string;
|
||||||
|
threadId?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default class NewMessageSeparator extends React.PureComponent<Props> {
|
export default class NewMessageSeparator extends React.PureComponent<Props> {
|
||||||
render(): JSX.Element {
|
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 (
|
return (
|
||||||
<div
|
<div
|
||||||
ref={this.props.wrapperRef}
|
ref={this.props.wrapperRef}
|
||||||
@ -23,7 +45,7 @@ export default class NewMessageSeparator extends React.PureComponent<Props> {
|
|||||||
id='posts_view.newMsg'
|
id='posts_view.newMsg'
|
||||||
defaultMessage='New Messages'
|
defaultMessage='New Messages'
|
||||||
/>
|
/>
|
||||||
|
{pluginItems}
|
||||||
</NotificationSeparator>
|
</NotificationSeparator>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@ -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`] = `
|
exports[`components/post_view/post_list_row should render new messages line 1`] = `
|
||||||
<NewMessageSeparator
|
<NewMessageSeparator
|
||||||
|
channelId="channel_id_1"
|
||||||
|
lastViewedAt={0}
|
||||||
|
newMessagesSeparatorActions={Array []}
|
||||||
separatorId="start-of-new-messages"
|
separatorId="start-of-new-messages"
|
||||||
/>
|
/>
|
||||||
`;
|
`;
|
||||||
|
@ -29,10 +29,11 @@ function mapStateToProps(state: GlobalState, ownProps: OwnProps) {
|
|||||||
const limitsLoaded = getCloudLimitsLoaded(state);
|
const limitsLoaded = getCloudLimitsLoaded(state);
|
||||||
const post = getPost(state, ownProps.listId);
|
const post = getPost(state, ownProps.listId);
|
||||||
const currentUserId = getCurrentUserId(state);
|
const currentUserId = getCurrentUserId(state);
|
||||||
|
const newMessagesSeparatorActions = state.plugins.components.NewMessagesSeparatorAction;
|
||||||
|
|
||||||
const props: Pick<
|
const props: Pick<
|
||||||
PostListRowProps,
|
PostListRowProps,
|
||||||
'shortcutReactToLastPostEmittedFrom' | 'usage' | 'limits' | 'limitsLoaded' | 'exceededLimitChannelId' | 'firstInaccessiblePostTime' | 'post' | 'currentUserId'
|
'shortcutReactToLastPostEmittedFrom' | 'usage' | 'limits' | 'limitsLoaded' | 'exceededLimitChannelId' | 'firstInaccessiblePostTime' | 'post' | 'currentUserId' | 'newMessagesSeparatorActions'
|
||||||
> = {
|
> = {
|
||||||
shortcutReactToLastPostEmittedFrom,
|
shortcutReactToLastPostEmittedFrom,
|
||||||
usage,
|
usage,
|
||||||
@ -40,6 +41,7 @@ function mapStateToProps(state: GlobalState, ownProps: OwnProps) {
|
|||||||
limitsLoaded,
|
limitsLoaded,
|
||||||
post,
|
post,
|
||||||
currentUserId,
|
currentUserId,
|
||||||
|
newMessagesSeparatorActions,
|
||||||
};
|
};
|
||||||
if ((ownProps.listId === PostListRowListIds.OLDER_MESSAGES_LOADER || ownProps.listId === PostListRowListIds.CHANNEL_INTRO_MESSAGE) && limitsLoaded) {
|
if ((ownProps.listId === PostListRowListIds.OLDER_MESSAGES_LOADER || ownProps.listId === PostListRowListIds.CHANNEL_INTRO_MESSAGE) && limitsLoaded) {
|
||||||
const currentChannelId = getCurrentChannelId(state);
|
const currentChannelId = getCurrentChannelId(state);
|
||||||
|
@ -40,6 +40,9 @@ describe('components/post_view/post_list_row', () => {
|
|||||||
usage: {} as CloudUsage,
|
usage: {} as CloudUsage,
|
||||||
post: TestHelper.getPostMock({id: 'post_id_1'}),
|
post: TestHelper.getPostMock({id: 'post_id_1'}),
|
||||||
currentUserId: 'user_id_1',
|
currentUserId: 'user_id_1',
|
||||||
|
newMessagesSeparatorActions: [],
|
||||||
|
lastViewedAt: 0,
|
||||||
|
channelId: 'channel_id_1',
|
||||||
};
|
};
|
||||||
|
|
||||||
test('should render more messages loading indicator', () => {
|
test('should render more messages loading indicator', () => {
|
||||||
|
@ -12,6 +12,7 @@ import {CloudUsage, Limits} from '@mattermost/types/cloud';
|
|||||||
import type {emitShortcutReactToLastPostFrom} from 'actions/post_actions';
|
import type {emitShortcutReactToLastPostFrom} from 'actions/post_actions';
|
||||||
|
|
||||||
import CombinedUserActivityPost from 'components/post_view/combined_user_activity_post';
|
import CombinedUserActivityPost from 'components/post_view/combined_user_activity_post';
|
||||||
|
import {PluginComponent} from 'types/store/plugins';
|
||||||
import {Post} from '@mattermost/types/posts';
|
import {Post} from '@mattermost/types/posts';
|
||||||
import DateSeparator from 'components/post_view/date_separator';
|
import DateSeparator from 'components/post_view/date_separator';
|
||||||
import NewMessageSeparator from 'components/post_view/new_message_separator/new_message_separator';
|
import NewMessageSeparator from 'components/post_view/new_message_separator/new_message_separator';
|
||||||
@ -54,6 +55,10 @@ export type PostListRowProps = {
|
|||||||
limitsLoaded: boolean;
|
limitsLoaded: boolean;
|
||||||
exceededLimitChannelId?: string;
|
exceededLimitChannelId?: string;
|
||||||
firstInaccessiblePostTime?: number;
|
firstInaccessiblePostTime?: number;
|
||||||
|
lastViewedAt: number;
|
||||||
|
channelId: string;
|
||||||
|
|
||||||
|
newMessagesSeparatorActions: PluginComponent[];
|
||||||
|
|
||||||
actions: {
|
actions: {
|
||||||
|
|
||||||
@ -109,7 +114,12 @@ export default class PostListRow extends React.PureComponent<PostListRowProps> {
|
|||||||
|
|
||||||
if (PostListUtils.isStartOfNewMessages(listId)) {
|
if (PostListUtils.isStartOfNewMessages(listId)) {
|
||||||
return (
|
return (
|
||||||
<NewMessageSeparator separatorId={listId}/>
|
<NewMessageSeparator
|
||||||
|
separatorId={listId}
|
||||||
|
newMessagesSeparatorActions={this.props.newMessagesSeparatorActions}
|
||||||
|
channelId={this.props.channelId}
|
||||||
|
lastViewedAt={this.props.lastViewedAt}
|
||||||
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -380,6 +380,8 @@ export default class PostList extends React.PureComponent<Props, State> {
|
|||||||
isLastPost={isLastPost}
|
isLastPost={isLastPost}
|
||||||
loadingNewerPosts={this.props.loadingNewerPosts}
|
loadingNewerPosts={this.props.loadingNewerPosts}
|
||||||
loadingOlderPosts={this.props.loadingOlderPosts}
|
loadingOlderPosts={this.props.loadingOlderPosts}
|
||||||
|
lastViewedAt={this.props.lastViewedAt}
|
||||||
|
channelId={this.props.channelId}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@ -46,12 +46,15 @@ function makeMapStateToProps() {
|
|||||||
showDate: !useRelativeTimestamp,
|
showDate: !useRelativeTimestamp,
|
||||||
lastViewedAt: collapsedThreads ? lastViewedAt : undefined,
|
lastViewedAt: collapsedThreads ? lastViewedAt : undefined,
|
||||||
});
|
});
|
||||||
|
const newMessagesSeparatorActions = state.plugins.components.NewMessagesSeparatorAction;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
currentUserId,
|
currentUserId,
|
||||||
directTeammate,
|
directTeammate,
|
||||||
lastPost,
|
lastPost,
|
||||||
replyListIds,
|
replyListIds,
|
||||||
|
lastViewedAt,
|
||||||
|
newMessagesSeparatorActions,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -10,6 +10,7 @@ import CombinedUserActivityPost from 'components/post_view/combined_user_activit
|
|||||||
import DateSeparator from 'components/post_view/date_separator';
|
import DateSeparator from 'components/post_view/date_separator';
|
||||||
import NewMessageSeparator from 'components/post_view/new_message_separator/new_message_separator';
|
import NewMessageSeparator from 'components/post_view/new_message_separator/new_message_separator';
|
||||||
import {Props as TimestampProps} from 'components/timestamp/timestamp';
|
import {Props as TimestampProps} from 'components/timestamp/timestamp';
|
||||||
|
import {PluginComponent} from 'types/store/plugins';
|
||||||
|
|
||||||
import PostComponent from 'components/post';
|
import PostComponent from 'components/post';
|
||||||
|
|
||||||
@ -26,6 +27,9 @@ type Props = {
|
|||||||
onCardClick: (post: Post) => void;
|
onCardClick: (post: Post) => void;
|
||||||
previousPostId: string;
|
previousPostId: string;
|
||||||
timestampProps?: Partial<TimestampProps>;
|
timestampProps?: Partial<TimestampProps>;
|
||||||
|
lastViewedAt: number;
|
||||||
|
threadId: string;
|
||||||
|
newMessagesSeparatorActions: PluginComponent[];
|
||||||
};
|
};
|
||||||
|
|
||||||
function noop() {}
|
function noop() {}
|
||||||
@ -38,6 +42,9 @@ function ThreadViewerRow({
|
|||||||
onCardClick,
|
onCardClick,
|
||||||
previousPostId,
|
previousPostId,
|
||||||
timestampProps,
|
timestampProps,
|
||||||
|
lastViewedAt,
|
||||||
|
threadId,
|
||||||
|
newMessagesSeparatorActions,
|
||||||
}: Props) {
|
}: Props) {
|
||||||
switch (true) {
|
switch (true) {
|
||||||
case PostListUtils.isDateLine(listId): {
|
case PostListUtils.isDateLine(listId): {
|
||||||
@ -51,7 +58,14 @@ function ThreadViewerRow({
|
|||||||
}
|
}
|
||||||
|
|
||||||
case PostListUtils.isStartOfNewMessages(listId):
|
case PostListUtils.isStartOfNewMessages(listId):
|
||||||
return <NewMessageSeparator separatorId={listId}/>;
|
return (
|
||||||
|
<NewMessageSeparator
|
||||||
|
separatorId={listId}
|
||||||
|
lastViewedAt={lastViewedAt}
|
||||||
|
threadId={threadId}
|
||||||
|
newMessagesSeparatorActions={newMessagesSeparatorActions}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
case isRootPost:
|
case isRootPost:
|
||||||
return (
|
return (
|
||||||
|
@ -63,6 +63,8 @@ describe('components/threading/VirtualizedThreadViewer', () => {
|
|||||||
teamId: '',
|
teamId: '',
|
||||||
useRelativeTimestamp: true,
|
useRelativeTimestamp: true,
|
||||||
isThreadView: true,
|
isThreadView: true,
|
||||||
|
lastViewedAt: 0,
|
||||||
|
newMessagesSeparatorActions: [],
|
||||||
};
|
};
|
||||||
test('should scroll to the bottom when the current user makes a new post in the thread', () => {
|
test('should scroll to the bottom when the current user makes a new post in the thread', () => {
|
||||||
const scrollToBottom = jest.fn();
|
const scrollToBottom = jest.fn();
|
||||||
|
@ -20,6 +20,7 @@ import {getNewMessageIndex, getPreviousPostId, getLatestPostId} from 'utils/post
|
|||||||
import NewRepliesBanner from 'components/new_replies_banner';
|
import NewRepliesBanner from 'components/new_replies_banner';
|
||||||
import FloatingTimestamp from 'components/post_view/floating_timestamp';
|
import FloatingTimestamp from 'components/post_view/floating_timestamp';
|
||||||
import {THREADING_TIME as BASE_THREADING_TIME} from 'components/threading/common/options';
|
import {THREADING_TIME as BASE_THREADING_TIME} from 'components/threading/common/options';
|
||||||
|
import {PluginComponent} from 'types/store/plugins';
|
||||||
|
|
||||||
import CreateComment from './create_comment';
|
import CreateComment from './create_comment';
|
||||||
import Row from './thread_viewer_row';
|
import Row from './thread_viewer_row';
|
||||||
@ -36,6 +37,8 @@ type Props = {
|
|||||||
selected: Post | FakePost;
|
selected: Post | FakePost;
|
||||||
useRelativeTimestamp: boolean;
|
useRelativeTimestamp: boolean;
|
||||||
isThreadView: boolean;
|
isThreadView: boolean;
|
||||||
|
lastViewedAt: number;
|
||||||
|
newMessagesSeparatorActions: PluginComponent[];
|
||||||
}
|
}
|
||||||
|
|
||||||
type State = {
|
type State = {
|
||||||
@ -386,6 +389,9 @@ class ThreadViewerVirtualized extends PureComponent<Props, State> {
|
|||||||
onCardClick={this.props.onCardClick}
|
onCardClick={this.props.onCardClick}
|
||||||
previousPostId={getPreviousPostId(data, index)}
|
previousPostId={getPreviousPostId(data, index)}
|
||||||
timestampProps={this.props.useRelativeTimestamp ? THREADING_TIME : undefined}
|
timestampProps={this.props.useRelativeTimestamp ? THREADING_TIME : undefined}
|
||||||
|
lastViewedAt={this.props.lastViewedAt}
|
||||||
|
threadId={this.props.selected.id}
|
||||||
|
newMessagesSeparatorActions={this.props.newMessagesSeparatorActions}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@ -6,6 +6,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.separator__text {
|
.separator__text {
|
||||||
|
display: inline-flex;
|
||||||
color: #f80;
|
color: #f80;
|
||||||
font-weight: normal;
|
font-weight: normal;
|
||||||
}
|
}
|
||||||
|
@ -508,6 +508,12 @@ export default class PluginRegistry {
|
|||||||
return dispatchPluginComponentAction('PostEditorAction', this.id, component);
|
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.
|
// Register a post menu list item by providing some text and an action function.
|
||||||
// Accepts the following:
|
// Accepts the following:
|
||||||
// - text - A string or React element to display in the menu
|
// - text - A string or React element to display in the menu
|
||||||
|
@ -183,6 +183,7 @@ const initialComponents: PluginsState['components'] = {
|
|||||||
PostDropdownMenu: [],
|
PostDropdownMenu: [],
|
||||||
PostAction: [],
|
PostAction: [],
|
||||||
PostEditorAction: [],
|
PostEditorAction: [],
|
||||||
|
NewMessagesSeparatorAction: [],
|
||||||
Product: [],
|
Product: [],
|
||||||
RightHandSidebarComponent: [],
|
RightHandSidebarComponent: [],
|
||||||
UserGuideDropdownItem: [],
|
UserGuideDropdownItem: [],
|
||||||
|
@ -29,6 +29,7 @@ export type PluginsState = {
|
|||||||
PostDropdownMenu: PluginComponent[];
|
PostDropdownMenu: PluginComponent[];
|
||||||
PostAction: PluginComponent[];
|
PostAction: PluginComponent[];
|
||||||
PostEditorAction: PluginComponent[];
|
PostEditorAction: PluginComponent[];
|
||||||
|
NewMessagesSeparatorAction: PluginComponent[];
|
||||||
FilePreview: PluginComponent[];
|
FilePreview: PluginComponent[];
|
||||||
MainMenu: PluginComponent[];
|
MainMenu: PluginComponent[];
|
||||||
LinkTooltip: PluginComponent[];
|
LinkTooltip: PluginComponent[];
|
||||||
|
Loading…
Reference in New Issue
Block a user