mirror of
https://github.com/mattermost/mattermost.git
synced 2025-02-25 18:55:24 -06:00
Adding PostAction plugin hook (#24102)
* Adding PostAction plugin hook * Adding missing doc string * WIP * Simplifying it * Adding support for selected text * fixing linter errors * Adding support for the plugin editor action in the thread view * Fixing ci check-types * Addressing PR review comments * Fix linter error in CI * Fixing tests
This commit is contained in:
parent
60fb112a27
commit
e1c6ae7d85
@ -5,6 +5,7 @@ exports[`components/AdvancedCreateComment should match snapshot when cannot post
|
||||
onSubmit={[Function]}
|
||||
>
|
||||
<AdvanceTextEditor
|
||||
additionalControls={Array []}
|
||||
applyMarkdown={[Function]}
|
||||
badConnection={false}
|
||||
canPost={false}
|
||||
@ -77,6 +78,7 @@ exports[`components/AdvancedCreateComment should match snapshot, comment with me
|
||||
onSubmit={[Function]}
|
||||
>
|
||||
<AdvanceTextEditor
|
||||
additionalControls={Array []}
|
||||
applyMarkdown={[Function]}
|
||||
badConnection={false}
|
||||
canPost={true}
|
||||
@ -144,6 +146,7 @@ exports[`components/AdvancedCreateComment should match snapshot, emoji picker di
|
||||
>
|
||||
<FileLimitStickyBanner />
|
||||
<AdvanceTextEditor
|
||||
additionalControls={Array []}
|
||||
applyMarkdown={[Function]}
|
||||
badConnection={false}
|
||||
canPost={true}
|
||||
@ -216,6 +219,7 @@ exports[`components/AdvancedCreateComment should match snapshot, empty comment 1
|
||||
onSubmit={[Function]}
|
||||
>
|
||||
<AdvanceTextEditor
|
||||
additionalControls={Array []}
|
||||
applyMarkdown={[Function]}
|
||||
badConnection={false}
|
||||
canPost={true}
|
||||
@ -283,6 +287,7 @@ exports[`components/AdvancedCreateComment should match snapshot, non-empty messa
|
||||
>
|
||||
<FileLimitStickyBanner />
|
||||
<AdvanceTextEditor
|
||||
additionalControls={Array []}
|
||||
applyMarkdown={[Function]}
|
||||
badConnection={false}
|
||||
canPost={true}
|
||||
|
@ -80,6 +80,7 @@ describe('components/AdvancedCreateComment', () => {
|
||||
useLDAPGroupMentions: true,
|
||||
useCustomGroupMentions: true,
|
||||
openModal: jest.fn(),
|
||||
postEditorActions: [],
|
||||
};
|
||||
|
||||
const emptyDraft = {
|
||||
|
@ -19,6 +19,7 @@ import {sortFileInfos} from 'mattermost-redux/utils/file_utils';
|
||||
import * as GlobalActions from 'actions/global_actions';
|
||||
|
||||
import {PostDraft} from 'types/store/draft';
|
||||
import {PluginComponent} from 'types/store/plugins';
|
||||
import {ModalData} from 'types/actions';
|
||||
|
||||
import Constants, {AdvancedTextEditor as AdvancedTextEditorConst, Locations, ModalIdentifiers, Preferences} from 'utils/constants';
|
||||
@ -193,6 +194,7 @@ type Props = {
|
||||
useCustomGroupMentions: boolean;
|
||||
isFormattingBarHidden: boolean;
|
||||
searchAssociatedGroupsForReference: (prefix: string, teamId: string, channelId: string | undefined) => Promise<{ data: any }>;
|
||||
postEditorActions: PluginComponent[];
|
||||
}
|
||||
|
||||
type State = {
|
||||
@ -1198,6 +1200,41 @@ class AdvancedCreateComment extends React.PureComponent<Props, State> {
|
||||
|
||||
render() {
|
||||
const draft = this.state.draft!;
|
||||
|
||||
const pluginItems = this.props.postEditorActions?.
|
||||
map((item) => {
|
||||
if (!item.component) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const Component = item.component as any;
|
||||
return (
|
||||
<Component
|
||||
key={item.id}
|
||||
draft={draft}
|
||||
getSelectedText={() => {
|
||||
const input = this.textboxRef.current?.getInputBox();
|
||||
|
||||
return {
|
||||
start: input.selectionStart,
|
||||
end: input.selectionEnd,
|
||||
};
|
||||
}}
|
||||
updateText={(message: string) => {
|
||||
const draft = this.state.draft!;
|
||||
const modifiedDraft = {
|
||||
...draft,
|
||||
message,
|
||||
};
|
||||
this.handleDraftChange(modifiedDraft);
|
||||
this.setState({
|
||||
draft: modifiedDraft,
|
||||
});
|
||||
}}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
return (
|
||||
<form onSubmit={this.handleSubmit}>
|
||||
{
|
||||
@ -1251,6 +1288,7 @@ class AdvancedCreateComment extends React.PureComponent<Props, State> {
|
||||
getFileUploadTarget={this.getFileUploadTarget}
|
||||
fileUploadRef={this.fileUploadRef}
|
||||
isThreadView={this.props.isThreadView}
|
||||
additionalControls={pluginItems.filter(Boolean)}
|
||||
/>
|
||||
</form>
|
||||
);
|
||||
|
@ -87,6 +87,7 @@ function makeMapStateToProps() {
|
||||
const groupsWithAllowReference = useLDAPGroupMentions || useCustomGroupMentions ? getAssociatedGroupsForReferenceByMention(state, channel.team_id, channel.id) : null;
|
||||
const isFormattingBarHidden = getBool(state, Constants.Preferences.ADVANCED_TEXT_EDITOR, AdvancedTextEditor.COMMENT);
|
||||
const currentTeamId = getCurrentTeamId(state);
|
||||
const postEditorActions = state.plugins.components.PostEditorAction;
|
||||
|
||||
return {
|
||||
currentTeamId,
|
||||
@ -116,6 +117,7 @@ function makeMapStateToProps() {
|
||||
channelMemberCountsByGroup,
|
||||
useCustomGroupMentions,
|
||||
canUploadFiles: canUploadFiles(config),
|
||||
postEditorActions,
|
||||
};
|
||||
};
|
||||
}
|
||||
|
@ -18,6 +18,7 @@ import {CommandArgs} from '@mattermost/types/integrations';
|
||||
import {Group, GroupSource} from '@mattermost/types/groups';
|
||||
import {FileInfo} from '@mattermost/types/files';
|
||||
import {Emoji} from '@mattermost/types/emojis';
|
||||
import {PluginComponent} from 'types/store/plugins';
|
||||
|
||||
import * as GlobalActions from 'actions/global_actions';
|
||||
import Constants, {
|
||||
@ -234,6 +235,7 @@ type Props = {
|
||||
channelMemberCountsByGroup: ChannelMemberCountsByGroup;
|
||||
useLDAPGroupMentions: boolean;
|
||||
useCustomGroupMentions: boolean;
|
||||
postEditorActions: PluginComponent[];
|
||||
}
|
||||
|
||||
type State = {
|
||||
@ -1398,7 +1400,7 @@ class AdvancedCreatePost extends React.PureComponent<Props, State> {
|
||||
this.setState({showEmojiPicker: false});
|
||||
};
|
||||
|
||||
setMessageAndCaretPostion = (newMessage: string, newCaretPosition: number) => {
|
||||
setMessageAndCaretPosition = (newMessage: string, newCaretPosition: number) => {
|
||||
const textbox = this.textboxRef.current?.getInputBox();
|
||||
|
||||
this.setState({
|
||||
@ -1417,7 +1419,7 @@ class AdvancedCreatePost extends React.PureComponent<Props, State> {
|
||||
};
|
||||
|
||||
prefillMessage = (message: string, shouldFocus?: boolean) => {
|
||||
this.setMessageAndCaretPostion(message, message.length);
|
||||
this.setMessageAndCaretPosition(message, message.length);
|
||||
|
||||
if (shouldFocus) {
|
||||
const inputBox = this.textboxRef.current?.getInputBox();
|
||||
@ -1439,7 +1441,7 @@ class AdvancedCreatePost extends React.PureComponent<Props, State> {
|
||||
|
||||
if (this.state.message === '') {
|
||||
const newMessage = ':' + emojiAlias + ': ';
|
||||
this.setMessageAndCaretPostion(newMessage, newMessage.length);
|
||||
this.setMessageAndCaretPosition(newMessage, newMessage.length);
|
||||
} else {
|
||||
const {message} = this.state;
|
||||
const {firstPiece, lastPiece} = splitMessageBasedOnCaretPosition(this.state.caretPosition, message);
|
||||
@ -1450,7 +1452,7 @@ class AdvancedCreatePost extends React.PureComponent<Props, State> {
|
||||
|
||||
const newCaretPosition =
|
||||
firstPiece === '' ? `:${emojiAlias}: `.length : `${firstPiece} :${emojiAlias}: `.length;
|
||||
this.setMessageAndCaretPostion(newMessage, newCaretPosition);
|
||||
this.setMessageAndCaretPosition(newMessage, newCaretPosition);
|
||||
}
|
||||
|
||||
this.handleEmojiClose();
|
||||
@ -1560,6 +1562,38 @@ class AdvancedCreatePost extends React.PureComponent<Props, State> {
|
||||
render() {
|
||||
const {draft, canPost} = this.props;
|
||||
|
||||
const pluginItems = this.props.postEditorActions?.
|
||||
map((item) => {
|
||||
if (!item.component) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const Component = item.component as any;
|
||||
return (
|
||||
<Component
|
||||
key={item.id}
|
||||
draft={draft}
|
||||
getSelectedText={() => {
|
||||
const input = this.textboxRef.current?.getInputBox();
|
||||
|
||||
return {
|
||||
start: input.selectionStart,
|
||||
end: input.selectionEnd,
|
||||
};
|
||||
}}
|
||||
updateText={(message: string) => {
|
||||
this.setState({
|
||||
message,
|
||||
});
|
||||
this.handleDraftChange({
|
||||
...this.props.draft,
|
||||
message,
|
||||
});
|
||||
}}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
let centerClass = '';
|
||||
if (!this.props.fullWidthTextBox) {
|
||||
centerClass = 'center';
|
||||
@ -1649,6 +1683,7 @@ class AdvancedCreatePost extends React.PureComponent<Props, State> {
|
||||
disabled={this.props.shouldShowPreview}
|
||||
/>
|
||||
),
|
||||
...(pluginItems || []),
|
||||
].filter(Boolean)}
|
||||
/>
|
||||
</form>
|
||||
|
@ -104,6 +104,7 @@ function makeMapStateToProps() {
|
||||
const tourStep = isGuestUser ? OnboardingTourStepsForGuestUsers.SEND_MESSAGE : OnboardingTourSteps.SEND_MESSAGE;
|
||||
const showSendTutorialTip = enableTutorial && tutorialStep === tourStep;
|
||||
const isFormattingBarHidden = getBool(state, Preferences.ADVANCED_TEXT_EDITOR, AdvancedTextEditor.POST);
|
||||
const postEditorActions = state.plugins.components.PostEditorAction;
|
||||
|
||||
return {
|
||||
currentTeamId,
|
||||
@ -143,6 +144,7 @@ function makeMapStateToProps() {
|
||||
isLDAPEnabled,
|
||||
useCustomGroupMentions,
|
||||
isPostPriorityEnabled: isPostPriorityEnabled(state),
|
||||
postEditorActions,
|
||||
};
|
||||
};
|
||||
}
|
||||
|
@ -114,7 +114,6 @@ function makeMapStateToProps() {
|
||||
postEditTimeLimit: config.PostEditTimeLimit,
|
||||
isLicensed: license.IsLicensed === 'true',
|
||||
teamId: getCurrentTeamId(state),
|
||||
pluginMenuItems: state.plugins.components.PostDropdownMenu,
|
||||
canEdit: PostUtils.canEditPost(state, post, license, config, channel, userId),
|
||||
canDelete: PostUtils.canDeletePost(state, post, channel),
|
||||
teamUrl,
|
||||
|
@ -213,6 +213,7 @@ function makeMapStateToProps() {
|
||||
isCardOpen: selectedCard && selectedCard.id === post.id,
|
||||
shouldShowDotMenu: shouldShowDotMenu(state, post, channel),
|
||||
canDelete: canDeletePost(state, post, channel),
|
||||
pluginActions: state.plugins.components.PostAction,
|
||||
};
|
||||
};
|
||||
}
|
||||
|
@ -38,6 +38,7 @@ describe('PostComponent', () => {
|
||||
recentEmojis: [],
|
||||
replyCount: 0,
|
||||
team: currentTeam,
|
||||
pluginActions: [],
|
||||
actions: {
|
||||
markPostAsUnread: jest.fn(),
|
||||
emitShortcutReactToLastPostFrom: jest.fn(),
|
||||
|
@ -14,7 +14,7 @@ import Constants, {A11yCustomEventTypes, A11yFocusEventDetail, AppEvents, Locati
|
||||
|
||||
import * as PostUtils from 'utils/post_utils';
|
||||
|
||||
import {PostPluginComponent} from 'types/store/plugins';
|
||||
import {PostPluginComponent, PluginComponent} from 'types/store/plugins';
|
||||
|
||||
import FileAttachmentListContainer from 'components/file_attachment_list';
|
||||
import DateSeparator from 'components/post_view/date_separator';
|
||||
@ -118,6 +118,7 @@ export type Props = {
|
||||
isPostPriorityEnabled: boolean;
|
||||
isCardOpen?: boolean;
|
||||
canDelete?: boolean;
|
||||
pluginActions: PluginComponent[];
|
||||
};
|
||||
|
||||
const PostComponent = (props: Props): JSX.Element => {
|
||||
|
@ -16,6 +16,7 @@ import PostFlagIcon from 'components/post_view/post_flag_icon';
|
||||
import PostRecentReactions from 'components/post_view/post_recent_reactions';
|
||||
import PostReaction from 'components/post_view/post_reaction';
|
||||
import CommentIcon from 'components/common/comment_icon';
|
||||
import {PluginComponent} from 'types/store/plugins';
|
||||
|
||||
import {Emoji} from '@mattermost/types/emojis';
|
||||
import {Post} from '@mattermost/types/posts';
|
||||
@ -49,6 +50,7 @@ type Props = {
|
||||
isPostHeaderVisible?: boolean | null;
|
||||
isPostBeingEdited?: boolean;
|
||||
canDelete?: boolean;
|
||||
pluginActions: PluginComponent[];
|
||||
actions: {
|
||||
emitShortcutReactToLastPostFrom: (emittedFrom: 'CENTER' | 'RHS_ROOT' | 'NO_WHERE') => void;
|
||||
};
|
||||
@ -175,6 +177,24 @@ const PostOptions = (props: Props): JSX.Element => {
|
||||
isMenuOpen={showActionsMenu}
|
||||
/>
|
||||
);
|
||||
|
||||
let pluginItems: ReactNode = null;
|
||||
if ((!isEphemeral && !post.failed && !systemMessage) && hoverLocal) {
|
||||
pluginItems = props.pluginActions?.
|
||||
map((item) => {
|
||||
if (item.component) {
|
||||
const Component = item.component as any;
|
||||
return (
|
||||
<Component
|
||||
post={props.post}
|
||||
key={item.id}
|
||||
/>
|
||||
);
|
||||
}
|
||||
return null;
|
||||
}) || [];
|
||||
}
|
||||
|
||||
const dotMenu = (
|
||||
<DotMenu
|
||||
post={props.post}
|
||||
@ -243,6 +263,7 @@ const PostOptions = (props: Props): JSX.Element => {
|
||||
{showRecentReactions}
|
||||
{postReaction}
|
||||
{flagIcon}
|
||||
{pluginItems}
|
||||
{actionsMenu}
|
||||
{commentIcon}
|
||||
{(collapsedThreadsEnabled || showRecentlyUsedReactions) && dotMenu}
|
||||
|
@ -496,6 +496,18 @@ export default class PluginRegistry {
|
||||
return id;
|
||||
});
|
||||
|
||||
// Register a component to the add to the post message menu shown on hover.
|
||||
// Accepts a React component. Returns a unique identifier.
|
||||
registerPostActionComponent = reArg(['component'], ({component}: DPluginComponentProp) => {
|
||||
return dispatchPluginComponentAction('PostAction', this.id, component);
|
||||
});
|
||||
|
||||
// Register a component to the add to the post text editor menu.
|
||||
// Accepts a React component. Returns a unique identifier.
|
||||
registerPostEditorActionComponent = reArg(['component'], ({component}: DPluginComponentProp) => {
|
||||
return dispatchPluginComponentAction('PostEditorAction', 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
|
||||
|
@ -181,6 +181,8 @@ const initialComponents: PluginsState['components'] = {
|
||||
ChannelHeaderButton: [],
|
||||
MobileChannelHeaderButton: [],
|
||||
PostDropdownMenu: [],
|
||||
PostAction: [],
|
||||
PostEditorAction: [],
|
||||
Product: [],
|
||||
RightHandSidebarComponent: [],
|
||||
UserGuideDropdownItem: [],
|
||||
|
@ -27,6 +27,8 @@ export type PluginsState = {
|
||||
Product: ProductComponent[];
|
||||
CallButton: PluginComponent[];
|
||||
PostDropdownMenu: PluginComponent[];
|
||||
PostAction: PluginComponent[];
|
||||
PostEditorAction: PluginComponent[];
|
||||
FilePreview: PluginComponent[];
|
||||
MainMenu: PluginComponent[];
|
||||
LinkTooltip: PluginComponent[];
|
||||
|
Loading…
Reference in New Issue
Block a user