diff --git a/app/components/autocomplete/at_mention/at_mention.js b/app/components/autocomplete/at_mention/at_mention.js index c8478c2ae..c3b00754c 100644 --- a/app/components/autocomplete/at_mention/at_mention.js +++ b/app/components/autocomplete/at_mention/at_mention.js @@ -37,6 +37,7 @@ export default class AtMention extends PureComponent { value: PropTypes.string, isLandscape: PropTypes.bool.isRequired, nestedScrollEnabled: PropTypes.bool, + useChannelMentions: PropTypes.bool.isRequired, }; static defaultProps = { @@ -100,7 +101,7 @@ export default class AtMention extends PureComponent { }); } - if (this.checkSpecialMentions(matchTerm)) { + if (this.props.useChannelMentions && this.checkSpecialMentions(matchTerm)) { sections.push({ id: t('suggestion.mention.special'), defaultMessage: 'Special Mentions', diff --git a/app/components/autocomplete/at_mention/index.js b/app/components/autocomplete/at_mention/index.js index 33b8ab5cd..011d23cf9 100644 --- a/app/components/autocomplete/at_mention/index.js +++ b/app/components/autocomplete/at_mention/index.js @@ -4,6 +4,7 @@ import {bindActionCreators} from 'redux'; import {connect} from 'react-redux'; +import {isMinimumServerVersion} from 'mattermost-redux/utils/helpers'; import {autocompleteUsers} from 'mattermost-redux/actions/users'; import {getCurrentChannelId, getDefaultChannel} from 'mattermost-redux/selectors/entities/channels'; import {getCurrentTeamId} from 'mattermost-redux/selectors/entities/teams'; @@ -17,12 +18,26 @@ import { } from 'app/selectors/autocomplete'; import {getTheme} from 'mattermost-redux/selectors/entities/preferences'; +import {haveIChannelPermission} from 'mattermost-redux/selectors/entities/roles'; +import {Permissions} from 'mattermost-redux/constants'; + import AtMention from './at_mention'; function mapStateToProps(state, ownProps) { const {cursorPosition, isSearch} = ownProps; const currentChannelId = getCurrentChannelId(state); + let useChannelMentions = true; + if (isMinimumServerVersion(state.entities.general.serverVersion, 5, 22)) { + useChannelMentions = haveIChannelPermission( + state, + { + channel: currentChannelId, + permission: Permissions.USE_CHANNEL_MENTIONS, + }, + ); + } + const value = ownProps.value.substring(0, cursorPosition); const matchTerm = getMatchTermForAtMention(value, isSearch); @@ -47,6 +62,7 @@ function mapStateToProps(state, ownProps) { requestStatus: state.requests.users.autocompleteUsers.status, theme: getTheme(state), isLandscape: isLandscape(state), + useChannelMentions, }; } diff --git a/app/components/post_textbox/index.js b/app/components/post_textbox/index.js index ac0878ab0..750fbc7e7 100644 --- a/app/components/post_textbox/index.js +++ b/app/components/post_textbox/index.js @@ -4,10 +4,12 @@ import {bindActionCreators} from 'redux'; import {connect} from 'react-redux'; -import {General} from 'mattermost-redux/constants'; +import {isMinimumServerVersion} from 'mattermost-redux/utils/helpers'; +import {General, Permissions} from 'mattermost-redux/constants'; import {createPost} from 'mattermost-redux/actions/posts'; import {setStatus} from 'mattermost-redux/actions/users'; import {getCurrentChannel, isCurrentChannelReadOnly, getCurrentChannelStats} from 'mattermost-redux/selectors/entities/channels'; +import {haveIChannelPermission} from 'mattermost-redux/selectors/entities/roles'; import {canUploadFilesOnMobile, getConfig} from 'mattermost-redux/selectors/entities/general'; import {getTheme} from 'mattermost-redux/selectors/entities/preferences'; import {getCurrentUserId, getStatusForUserId} from 'mattermost-redux/selectors/entities/users'; @@ -49,6 +51,17 @@ function mapStateToProps(state, ownProps) { const currentChannelMembersCount = currentChannelStats?.member_count || 0; // eslint-disable-line camelcase const isTimezoneEnabled = config?.ExperimentalTimezone === 'true'; + let useChannelMentions = true; + if (isMinimumServerVersion(state.entities.general.serverVersion, 5, 22)) { + useChannelMentions = haveIChannelPermission( + state, + { + channel: currentChannel.id, + permission: Permissions.USE_CHANNEL_MENTIONS, + }, + ); + } + return { currentChannel, channelId: ownProps.channelId || (currentChannel ? currentChannel.id : ''), @@ -71,6 +84,7 @@ function mapStateToProps(state, ownProps) { currentChannelMembersCount, isTimezoneEnabled, isLandscape: isLandscape(state), + useChannelMentions, }; } diff --git a/app/components/post_textbox/post_textbox.test.js b/app/components/post_textbox/post_textbox.test.js index aff9d8601..ba0f878bd 100644 --- a/app/components/post_textbox/post_textbox.test.js +++ b/app/components/post_textbox/post_textbox.test.js @@ -64,6 +64,9 @@ describe('PostTextBox', () => { valueEvent: '', isLandscape: false, screenId: 'NavigationScreen1', + currentChannelMembersCount: 50, + enableConfirmNotificationsToChannel: true, + useChannelMentions: true, }; test('should match, full snapshot', () => { @@ -117,6 +120,38 @@ describe('PostTextBox', () => { expect(Alert.alert).toHaveBeenCalledTimes(1); }); + test('should send an alert when sending a message with a channel mention', () => { + const wrapper = shallowWithIntl( + , + ); + const message = '@all'; + const instance = wrapper.instance(); + instance.handleTextChange(message); + + expect(wrapper.state('value')).toBe(message); + + instance.sendMessage(); + expect(Alert.alert).toBeCalled(); + expect(Alert.alert).toHaveBeenCalledWith('Confirm sending notifications to entire channel', expect.anything(), expect.anything()); + }); + + test('should not send an alert when sending a message with a channel mention when the user does not have channel mentions permission', () => { + const wrapper = shallowWithIntl( + , + ); + const message = '@all'; + const instance = wrapper.instance(); + instance.handleTextChange(message); + + expect(wrapper.state('value')).toBe(message); + + instance.sendMessage(); + expect(Alert.alert).not.toHaveBeenCalled(); + }); + test('should return correct @all (same for @channel)', () => { for (const data of [ { diff --git a/app/components/post_textbox/post_textbox_base.js b/app/components/post_textbox/post_textbox_base.js index f15971796..4afefd337 100644 --- a/app/components/post_textbox/post_textbox_base.js +++ b/app/components/post_textbox/post_textbox_base.js @@ -98,6 +98,7 @@ export default class PostTextBoxBase extends PureComponent { currentChannel: PropTypes.object, isLandscape: PropTypes.bool.isRequired, screenId: PropTypes.string.isRequired, + useChannelMentions: PropTypes.bool.isRequired, }; static defaultProps = { @@ -573,7 +574,7 @@ export default class PostTextBoxBase extends PureComponent { const {value} = this.state; const currentMembersCount = this.props.currentChannelMembersCount; - const notificationsToChannel = this.props.enableConfirmNotificationsToChannel; + const notificationsToChannel = this.props.enableConfirmNotificationsToChannel && this.props.useChannelMentions; const toAllOrChannel = this.textContainsAtAllAtChannel(value); if (value.indexOf('/') === 0) {