Make GM behave as DM (#24289)
* Make GM behave as DM * Fix lint * Add desktop notification special behavior * Change notification preferences menu * Make changes to the GM channel intro * Fix tests * Fix i18n and style lint * Add system notice and update style * Fix style and fix tests * Fix tests * Handle push notifications as desktop notifications * Fix tests * Add test and default GMs to none when user level config is none * Fix test * Update only for mentions text * Add tests * Fix lint * Fix lint
Этот коммит содержится в:
коммит произвёл
GitHub
родитель
39d6cb8008
Коммит
88d043a971
@@ -9,6 +9,7 @@ import {
|
||||
getTeammateNameDisplaySetting,
|
||||
isCollapsedThreadsEnabled,
|
||||
} from 'mattermost-redux/selectors/entities/preferences';
|
||||
import {getAllUserMentionKeys} from 'mattermost-redux/selectors/entities/search';
|
||||
import {getCurrentUserId, getCurrentUser, getStatusForUserId, getUser} from 'mattermost-redux/selectors/entities/users';
|
||||
import {isChannelMuted} from 'mattermost-redux/utils/channel_utils';
|
||||
import {isSystemMessage, isUserAddedInChannel} from 'mattermost-redux/utils/post_utils';
|
||||
@@ -18,11 +19,13 @@ import {getChannelURL, getPermalinkURL} from 'selectors/urls';
|
||||
import {isThreadOpen} from 'selectors/views/threads';
|
||||
|
||||
import {getHistory} from 'utils/browser_history';
|
||||
import Constants, {NotificationLevels, UserStatuses} from 'utils/constants';
|
||||
import Constants, {NotificationLevels, UserStatuses, IgnoreChannelMentions} from 'utils/constants';
|
||||
import {t} from 'utils/i18n';
|
||||
import {stripMarkdown} from 'utils/markdown';
|
||||
import {stripMarkdown, formatWithRenderer} from 'utils/markdown';
|
||||
import MentionableRenderer from 'utils/markdown/mentionable_renderer';
|
||||
import * as NotificationSounds from 'utils/notification_sounds';
|
||||
import {showNotification} from 'utils/notifications';
|
||||
import {cjkrPattern, escapeRegex} from 'utils/text_formatting';
|
||||
import {isDesktopApp, isMobileApp, isWindowsApp} from 'utils/user_agent';
|
||||
import * as Utils from 'utils/utils';
|
||||
|
||||
@@ -93,14 +96,94 @@ export function sendDesktopNotification(post, msgProps) {
|
||||
return;
|
||||
}
|
||||
|
||||
let notifyLevel = member?.notify_props?.desktop || NotificationLevels.DEFAULT;
|
||||
const channelNotifyProp = member?.notify_props?.desktop || NotificationLevels.DEFAULT;
|
||||
let notifyLevel = channelNotifyProp;
|
||||
|
||||
if (notifyLevel === NotificationLevels.DEFAULT) {
|
||||
notifyLevel = user?.notify_props?.desktop || NotificationLevels.ALL;
|
||||
}
|
||||
|
||||
if (channel.type === 'G' && channelNotifyProp === NotificationLevels.DEFAULT && user?.notify_props?.desktop === NotificationLevels.MENTION) {
|
||||
notifyLevel = NotificationLevels.ALL;
|
||||
}
|
||||
|
||||
if (notifyLevel === NotificationLevels.NONE) {
|
||||
return;
|
||||
} else if (channel.type === 'G' && notifyLevel === NotificationLevels.MENTION) {
|
||||
// Compose the whole text in the message, including interactive messages.
|
||||
let text = post.message;
|
||||
|
||||
// We do this on a try catch block to avoid errors from malformed props
|
||||
try {
|
||||
if (post.props && post.props.attachments) {
|
||||
const attachments = post.props.attachments;
|
||||
function appendText(toAppend) {
|
||||
if (toAppend) {
|
||||
text += `\n${toAppend}`;
|
||||
}
|
||||
}
|
||||
for (const attachment of attachments) {
|
||||
appendText(attachment.pretext);
|
||||
appendText(attachment.title);
|
||||
appendText(attachment.text);
|
||||
appendText(attachment.footer);
|
||||
if (attachment.fields) {
|
||||
for (const field of attachment.fields) {
|
||||
appendText(field.title);
|
||||
appendText(field.value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log('Could not process the whole attachment for mentions', e);
|
||||
}
|
||||
|
||||
const allMentions = getAllUserMentionKeys(state);
|
||||
|
||||
const ignoreChannelMentionProp = member?.notify_props?.ignore_channel_mentions || IgnoreChannelMentions.DEFAULT;
|
||||
let ignoreChannelMention = ignoreChannelMentionProp === IgnoreChannelMentions.ON;
|
||||
if (ignoreChannelMentionProp === IgnoreChannelMentions.DEFAULT) {
|
||||
ignoreChannelMention = user?.notify_props?.channel === 'false';
|
||||
}
|
||||
|
||||
const mentionableText = formatWithRenderer(text, new MentionableRenderer());
|
||||
let isExplicitlyMentioned = false;
|
||||
for (const mention of allMentions) {
|
||||
if (!mention || !mention.key) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (ignoreChannelMention && ['@all', '@here', '@channel'].includes(mention.key)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
let flags = 'g';
|
||||
if (!mention.caseSensitive) {
|
||||
flags += 'i';
|
||||
}
|
||||
|
||||
let pattern;
|
||||
if (cjkrPattern.test(mention.key)) {
|
||||
// In the case of CJK mention key, even if there's no delimiters (such as spaces) at both ends of a word, it is recognized as a mention key
|
||||
pattern = new RegExp(`()(${escapeRegex(mention.key)})()`, flags);
|
||||
} else {
|
||||
pattern = new RegExp(
|
||||
`(^|\\W)(${escapeRegex(mention.key)})(\\b|_+\\b)`,
|
||||
flags,
|
||||
);
|
||||
}
|
||||
|
||||
if (pattern.test(mentionableText)) {
|
||||
isExplicitlyMentioned = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!isExplicitlyMentioned) {
|
||||
return;
|
||||
}
|
||||
} else if (notifyLevel === NotificationLevels.MENTION && mentions.indexOf(user.id) === -1 && msgProps.channel_type !== Constants.DM_CHANNEL) {
|
||||
return;
|
||||
} else if (isCrtReply && notifyLevel === NotificationLevels.ALL && followers.indexOf(currentUserId) === -1) {
|
||||
|
||||
@@ -36,6 +36,9 @@ describe('notification_actions', () => {
|
||||
desktop: NotificationLevels.ALL,
|
||||
desktop_sound: false,
|
||||
desktop_threads: NotificationLevels.ALL,
|
||||
mention_keys: 'mentionkey',
|
||||
first_name: 'true',
|
||||
channel: 'true',
|
||||
};
|
||||
|
||||
post = {
|
||||
@@ -79,8 +82,13 @@ describe('notification_actions', () => {
|
||||
current_user_id: {
|
||||
id: 'current_user_id',
|
||||
notify_props: userSettings,
|
||||
username: 'currentusername',
|
||||
first_name: 'currentuserfirstname',
|
||||
},
|
||||
},
|
||||
profilesInChannel: {
|
||||
gm_channel: new Set(['current_user_id']),
|
||||
},
|
||||
},
|
||||
teams: {
|
||||
currentTeamId: 'team_id',
|
||||
@@ -109,12 +117,20 @@ describe('notification_actions', () => {
|
||||
id: 'another_channel_id',
|
||||
team_id: 'team_id',
|
||||
},
|
||||
gm_channel: {
|
||||
id: 'gm_channel',
|
||||
type: 'G',
|
||||
},
|
||||
},
|
||||
myMembers: {
|
||||
channel_id: {
|
||||
id: 'current_user_id',
|
||||
notify_props: channelSettings,
|
||||
},
|
||||
gm_channel: {
|
||||
id: 'gm_channel',
|
||||
notify_props: channelSettings,
|
||||
},
|
||||
},
|
||||
membersInChannel: {
|
||||
channel_id: {
|
||||
@@ -123,6 +139,12 @@ describe('notification_actions', () => {
|
||||
notify_props: channelSettings,
|
||||
},
|
||||
},
|
||||
gm_channel: {
|
||||
current_user_id: {
|
||||
id: 'gm_channel',
|
||||
notify_props: channelSettings,
|
||||
},
|
||||
},
|
||||
muted_channel_id: {
|
||||
current_user_id: {
|
||||
id: 'current_user_id',
|
||||
@@ -138,6 +160,10 @@ describe('notification_actions', () => {
|
||||
'display_settings--collapsed_reply_threads': crt,
|
||||
},
|
||||
},
|
||||
groups: {
|
||||
groups: {},
|
||||
myGroups: [],
|
||||
},
|
||||
},
|
||||
views: {
|
||||
browser: {
|
||||
@@ -401,5 +427,85 @@ describe('notification_actions', () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('GMs', () => {
|
||||
test('should notify for any message when channel setting is DEFAULT and user setting is MENTION', async () => {
|
||||
const store = testConfigureStore(baseState);
|
||||
userSettings.desktop = NotificationLevels.MENTION;
|
||||
channelSettings.desktop = NotificationLevels.DEFAULT;
|
||||
post.channel_id = 'gm_channel';
|
||||
msgProps.team_id = '';
|
||||
|
||||
return store.dispatch(sendDesktopNotification(post, msgProps)).then(() => {
|
||||
expect(spy).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
test('should not notify for any message when channel setting is DEFAULT and user setting is NONE', async () => {
|
||||
const store = testConfigureStore(baseState);
|
||||
userSettings.desktop = NotificationLevels.NONE;
|
||||
channelSettings.desktop = NotificationLevels.DEFAULT;
|
||||
post.message = '@username';
|
||||
post.channel_id = 'gm_channel';
|
||||
msgProps.team_id = '';
|
||||
|
||||
return store.dispatch(sendDesktopNotification(post, msgProps)).then(() => {
|
||||
expect(spy).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
test('should notify when channel setting MENTION and there is a explicit mention', async () => {
|
||||
const store = testConfigureStore(baseState);
|
||||
channelSettings.desktop = NotificationLevels.MENTION;
|
||||
post.message = '@currentusername';
|
||||
post.channel_id = 'gm_channel';
|
||||
msgProps.team_id = '';
|
||||
|
||||
return store.dispatch(sendDesktopNotification(post, msgProps)).then(() => {
|
||||
expect(spy).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
test('should notify when channel setting MENTION and there is a keyword mention', async () => {
|
||||
const store = testConfigureStore(baseState);
|
||||
channelSettings.desktop = NotificationLevels.MENTION;
|
||||
post.message = 'mentionkey';
|
||||
post.channel_id = 'gm_channel';
|
||||
msgProps.team_id = '';
|
||||
|
||||
return store.dispatch(sendDesktopNotification(post, msgProps)).then(() => {
|
||||
expect(spy).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
test('should notify when channel setting MENTION and there is the first name', async () => {
|
||||
const store = testConfigureStore(baseState);
|
||||
channelSettings.desktop = NotificationLevels.MENTION;
|
||||
post.message = 'currentuserfirstname';
|
||||
post.channel_id = 'gm_channel';
|
||||
msgProps.team_id = '';
|
||||
|
||||
return store.dispatch(sendDesktopNotification(post, msgProps)).then(() => {
|
||||
expect(spy).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
test('should notify when channel setting MENTION and there is a channel mention', async () => {
|
||||
const store = testConfigureStore(baseState);
|
||||
channelSettings.desktop = NotificationLevels.MENTION;
|
||||
post.message = '@all';
|
||||
post.channel_id = 'gm_channel';
|
||||
msgProps.team_id = '';
|
||||
|
||||
return store.dispatch(sendDesktopNotification(post, msgProps)).then(() => {
|
||||
expect(spy).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
test('should not notify when channel setting MENTION and there is no explicit mention', async () => {
|
||||
const store = testConfigureStore(baseState);
|
||||
channelSettings.desktop = NotificationLevels.MENTION;
|
||||
post.channel_id = 'gm_channel';
|
||||
msgProps.team_id = '';
|
||||
|
||||
return store.dispatch(sendDesktopNotification(post, msgProps)).then(() => {
|
||||
expect(spy).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -70,6 +70,7 @@ exports[`components/channel_notifications_modal/ChannelNotificationsModal should
|
||||
/>
|
||||
<NotificationSection
|
||||
expand={false}
|
||||
isGM={false}
|
||||
memberNotificationLevel="all"
|
||||
onChange={[Function]}
|
||||
onSubmit={[Function]}
|
||||
@@ -83,6 +84,7 @@ exports[`components/channel_notifications_modal/ChannelNotificationsModal should
|
||||
<NotificationSection
|
||||
expand={false}
|
||||
ignoreChannelMentions="off"
|
||||
isGM={false}
|
||||
memberNotificationLevel="all"
|
||||
onChange={[Function]}
|
||||
onSubmit={[Function]}
|
||||
@@ -98,6 +100,7 @@ exports[`components/channel_notifications_modal/ChannelNotificationsModal should
|
||||
expand={false}
|
||||
globalNotificationLevel="all"
|
||||
globalNotificationSound="Bing"
|
||||
isGM={false}
|
||||
isNotificationsSettingSameAsGlobal={true}
|
||||
memberDesktopNotificationSound="Bing"
|
||||
memberDesktopSound="on"
|
||||
@@ -118,6 +121,8 @@ exports[`components/channel_notifications_modal/ChannelNotificationsModal should
|
||||
/>
|
||||
<NotificationSection
|
||||
expand={false}
|
||||
globalNotificationLevel="all"
|
||||
isGM={false}
|
||||
isNotificationsSettingSameAsGlobal={true}
|
||||
memberNotificationLevel="all"
|
||||
memberThreadsNotificationLevel="all"
|
||||
@@ -137,6 +142,7 @@ exports[`components/channel_notifications_modal/ChannelNotificationsModal should
|
||||
channelAutoFollowThreads="off"
|
||||
expand={false}
|
||||
ignoreChannelMentions="off"
|
||||
isGM={false}
|
||||
memberNotificationLevel="all"
|
||||
onChange={[Function]}
|
||||
onSubmit={[Function]}
|
||||
@@ -153,3 +159,148 @@ exports[`components/channel_notifications_modal/ChannelNotificationsModal should
|
||||
</ModalBody>
|
||||
</Modal>
|
||||
`;
|
||||
|
||||
exports[`components/channel_notifications_modal/ChannelNotificationsModal should match snapshot for GMs 1`] = `
|
||||
<Modal
|
||||
animation={true}
|
||||
aria-labelledby="channelNotificationModalLabel"
|
||||
autoFocus={true}
|
||||
backdrop={true}
|
||||
bsClass="modal"
|
||||
dialogClassName="a11y__modal settings-modal settings-modal--tabless"
|
||||
dialogComponentClass={[Function]}
|
||||
enforceFocus={true}
|
||||
keyboard={true}
|
||||
manager={
|
||||
ModalManager {
|
||||
"add": [Function],
|
||||
"containers": Array [],
|
||||
"data": Array [],
|
||||
"handleContainerOverflow": true,
|
||||
"hideSiblingNodes": true,
|
||||
"isTopModal": [Function],
|
||||
"modals": Array [],
|
||||
"remove": [Function],
|
||||
}
|
||||
}
|
||||
onExited={[Function]}
|
||||
onHide={[Function]}
|
||||
renderBackdrop={[Function]}
|
||||
restoreFocus={true}
|
||||
role="dialog"
|
||||
show={true}
|
||||
>
|
||||
<ModalHeader
|
||||
bsClass="modal-header"
|
||||
closeButton={true}
|
||||
closeLabel="Close"
|
||||
>
|
||||
<ModalTitle
|
||||
bsClass="modal-title"
|
||||
componentClass="h1"
|
||||
id="channelNotificationModalLabel"
|
||||
>
|
||||
<MemoizedFormattedMessage
|
||||
defaultMessage="Notification Preferences for "
|
||||
id="channel_notifications.preferences"
|
||||
/>
|
||||
<span
|
||||
className="name"
|
||||
>
|
||||
channel_display_name
|
||||
</span>
|
||||
</ModalTitle>
|
||||
</ModalHeader>
|
||||
<ModalBody
|
||||
bsClass="modal-body"
|
||||
componentClass="div"
|
||||
>
|
||||
<div
|
||||
className="settings-table"
|
||||
>
|
||||
<div
|
||||
className="settings-content"
|
||||
>
|
||||
<div
|
||||
className="user-settings"
|
||||
>
|
||||
<br />
|
||||
<div
|
||||
className="divider-dark first"
|
||||
/>
|
||||
<NotificationSection
|
||||
expand={false}
|
||||
isGM={true}
|
||||
memberNotificationLevel="all"
|
||||
onChange={[Function]}
|
||||
onSubmit={[Function]}
|
||||
onUpdateSection={[Function]}
|
||||
section="markUnread"
|
||||
serverError={null}
|
||||
/>
|
||||
<div
|
||||
className="divider-light"
|
||||
/>
|
||||
<NotificationSection
|
||||
expand={false}
|
||||
ignoreChannelMentions="off"
|
||||
isGM={true}
|
||||
memberNotificationLevel="all"
|
||||
onChange={[Function]}
|
||||
onSubmit={[Function]}
|
||||
onUpdateSection={[Function]}
|
||||
section="ignoreChannelMentions"
|
||||
serverError={null}
|
||||
/>
|
||||
<div>
|
||||
<div
|
||||
className="divider-light"
|
||||
/>
|
||||
<NotificationSection
|
||||
expand={false}
|
||||
globalNotificationLevel="all"
|
||||
globalNotificationSound="Bing"
|
||||
isGM={true}
|
||||
isNotificationsSettingSameAsGlobal={false}
|
||||
memberDesktopNotificationSound="Bing"
|
||||
memberDesktopSound="on"
|
||||
memberNotificationLevel="mention"
|
||||
memberThreadsNotificationLevel="all"
|
||||
onChange={[Function]}
|
||||
onChangeDesktopSound={[Function]}
|
||||
onChangeNotificationSound={[Function]}
|
||||
onChangeThreads={[Function]}
|
||||
onReset={[Function]}
|
||||
onSubmit={[Function]}
|
||||
onUpdateSection={[Function]}
|
||||
section="desktop"
|
||||
serverError={null}
|
||||
/>
|
||||
<div
|
||||
className="divider-light"
|
||||
/>
|
||||
<NotificationSection
|
||||
expand={false}
|
||||
globalNotificationLevel="all"
|
||||
isGM={true}
|
||||
isNotificationsSettingSameAsGlobal={false}
|
||||
memberNotificationLevel="mention"
|
||||
memberThreadsNotificationLevel="all"
|
||||
onChange={[Function]}
|
||||
onChangeThreads={[Function]}
|
||||
onReset={[Function]}
|
||||
onSubmit={[Function]}
|
||||
onUpdateSection={[Function]}
|
||||
section="push"
|
||||
serverError={null}
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
className="divider-dark"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</ModalBody>
|
||||
</Modal>
|
||||
`;
|
||||
|
||||
@@ -54,6 +54,37 @@ describe('components/channel_notifications_modal/ChannelNotificationsModal', ()
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('should match snapshot for GMs', () => {
|
||||
const wrapper = shallow(
|
||||
<ChannelNotificationsModal
|
||||
{...{
|
||||
...baseProps,
|
||||
channel: TestHelper.getChannelMock({
|
||||
id: 'channel_id',
|
||||
display_name: 'channel_display_name',
|
||||
type: 'G',
|
||||
}),
|
||||
channelMember: {
|
||||
notify_props: {
|
||||
...baseProps.channelMember!.notify_props,
|
||||
desktop: NotificationLevels.MENTION,
|
||||
push: NotificationLevels.MENTION,
|
||||
},
|
||||
} as unknown as ChannelMembership,
|
||||
currentUser: TestHelper.getUserMock({
|
||||
id: 'current_user_id',
|
||||
notify_props: {
|
||||
desktop: NotificationLevels.MENTION,
|
||||
desktop_threads: NotificationLevels.ALL,
|
||||
} as UserNotifyProps,
|
||||
}),
|
||||
}}
|
||||
/>,
|
||||
);
|
||||
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('should provide default notify props when missing', () => {
|
||||
const wrapper = shallow(
|
||||
<ChannelNotificationsModal
|
||||
|
||||
@@ -56,9 +56,13 @@ type State = {
|
||||
export type DesktopNotificationProps = Pick<State, 'desktopNotifyLevel' | 'desktopNotifySound' | 'desktopSound' | 'desktopThreadsNotifyLevel'>
|
||||
export type PushNotificationProps = Pick<State, 'pushNotifyLevel' | 'pushThreadsNotifyLevel'>
|
||||
|
||||
const getDefaultDesktopNotificationLevel = (currentUserNotifyProps: UserNotifyProps): Exclude<ChannelMemberNotifyProps['desktop'], undefined> => {
|
||||
const getDefaultDesktopNotificationLevel = (currentUserNotifyProps: UserNotifyProps, isGM: boolean): Exclude<ChannelMemberNotifyProps['desktop'], undefined> => {
|
||||
if (currentUserNotifyProps?.desktop) {
|
||||
if (currentUserNotifyProps.desktop === 'default') {
|
||||
if (currentUserNotifyProps.desktop === NotificationLevels.DEFAULT) {
|
||||
return NotificationLevels.ALL;
|
||||
}
|
||||
|
||||
if (isGM && currentUserNotifyProps.desktop === NotificationLevels.MENTION) {
|
||||
return NotificationLevels.ALL;
|
||||
}
|
||||
return currentUserNotifyProps.desktop;
|
||||
@@ -86,11 +90,16 @@ const getDefaultDesktopThreadsNotifyLevel = (currentUserNotifyProps: UserNotifyP
|
||||
return NotificationLevels.ALL;
|
||||
};
|
||||
|
||||
const getDefaultPushNotifyLevel = (currentUserNotifyProps: UserNotifyProps): Exclude<ChannelMemberNotifyProps['push'], undefined> => {
|
||||
const getDefaultPushNotifyLevel = (currentUserNotifyProps: UserNotifyProps, isGM: boolean): Exclude<ChannelMemberNotifyProps['push'], undefined> => {
|
||||
if (currentUserNotifyProps?.push) {
|
||||
if (currentUserNotifyProps.push === 'default') {
|
||||
if (currentUserNotifyProps.push === NotificationLevels.DEFAULT) {
|
||||
return NotificationLevels.ALL;
|
||||
}
|
||||
|
||||
if (isGM && currentUserNotifyProps.desktop === NotificationLevels.MENTION) {
|
||||
return NotificationLevels.ALL;
|
||||
}
|
||||
|
||||
return currentUserNotifyProps.push;
|
||||
}
|
||||
return NotificationLevels.ALL;
|
||||
@@ -142,7 +151,7 @@ export default class ChannelNotificationsModal extends React.PureComponent<Props
|
||||
const currentUserNotifyProps = this.props.currentUser.notify_props;
|
||||
|
||||
if (
|
||||
desktopNotifyLevel === getDefaultDesktopNotificationLevel(currentUserNotifyProps) &&
|
||||
desktopNotifyLevel === getDefaultDesktopNotificationLevel(currentUserNotifyProps, this.isGM()) &&
|
||||
desktopNotifySound === getDefaultDesktopNotificationSound(currentUserNotifyProps) &&
|
||||
desktopSound === getDefaultDesktopSound(currentUserNotifyProps) &&
|
||||
desktopThreadsNotifyLevel === getDefaultDesktopThreadsNotifyLevel(currentUserNotifyProps)
|
||||
@@ -152,6 +161,10 @@ export default class ChannelNotificationsModal extends React.PureComponent<Props
|
||||
return false;
|
||||
}
|
||||
|
||||
isGM() {
|
||||
return this.props.channel.type === 'G';
|
||||
}
|
||||
|
||||
verifyPushNotificationsSettingSameAsGlobal({
|
||||
pushNotifyLevel,
|
||||
pushThreadsNotifyLevel,
|
||||
@@ -159,7 +172,7 @@ export default class ChannelNotificationsModal extends React.PureComponent<Props
|
||||
const currentUserNotifyProps = this.props.currentUser.notify_props;
|
||||
|
||||
if (
|
||||
pushNotifyLevel === getDefaultPushNotifyLevel(currentUserNotifyProps) &&
|
||||
pushNotifyLevel === getDefaultPushNotifyLevel(currentUserNotifyProps, this.isGM()) &&
|
||||
pushThreadsNotifyLevel === getDefaultPushThreadsNotifyLevel(currentUserNotifyProps)
|
||||
) {
|
||||
return true;
|
||||
@@ -170,13 +183,15 @@ export default class ChannelNotificationsModal extends React.PureComponent<Props
|
||||
getStateFromNotifyProps(currentUserNotifyProps: UserNotifyProps, channelMemberNotifyProps?: ChannelMemberNotifyProps) {
|
||||
let ignoreChannelMentionsDefault: ChannelNotifyProps['ignore_channel_mentions'] = IgnoreChannelMentions.OFF;
|
||||
|
||||
let desktopNotifyLevelDefault: ChannelNotifyProps['desktop'] = getDefaultDesktopNotificationLevel(currentUserNotifyProps);
|
||||
let pushNotifyLevelDefault: ChannelMemberNotifyProps['push'] = getDefaultPushNotifyLevel(currentUserNotifyProps);
|
||||
let desktopNotifyLevelDefault: ChannelNotifyProps['desktop'] = getDefaultDesktopNotificationLevel(currentUserNotifyProps, this.isGM());
|
||||
let pushNotifyLevelDefault: ChannelMemberNotifyProps['push'] = getDefaultPushNotifyLevel(currentUserNotifyProps, this.isGM());
|
||||
let pushThreadsNotifyLevelDefault: ChannelMemberNotifyProps['push_threads'] = getDefaultPushThreadsNotifyLevel(currentUserNotifyProps);
|
||||
|
||||
if (channelMemberNotifyProps?.desktop) {
|
||||
if (channelMemberNotifyProps.desktop !== 'default') {
|
||||
desktopNotifyLevelDefault = channelMemberNotifyProps.desktop;
|
||||
} else if (this.isGM()) {
|
||||
desktopNotifyLevelDefault = NotificationLevels.ALL;
|
||||
}
|
||||
}
|
||||
if (channelMemberNotifyProps?.push) {
|
||||
@@ -187,6 +202,8 @@ export default class ChannelNotificationsModal extends React.PureComponent<Props
|
||||
if (channelMemberNotifyProps?.push_threads) {
|
||||
if (channelMemberNotifyProps.push_threads !== 'default') {
|
||||
pushThreadsNotifyLevelDefault = channelMemberNotifyProps.push_threads;
|
||||
} else if (this.isGM()) {
|
||||
pushThreadsNotifyLevelDefault = NotificationLevels.ALL;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -246,7 +263,7 @@ export default class ChannelNotificationsModal extends React.PureComponent<Props
|
||||
const currentUserNotifyProps = this.props.currentUser.notify_props;
|
||||
|
||||
const userDesktopNotificationDefaults = {
|
||||
desktopNotifyLevel: getDefaultDesktopNotificationLevel(currentUserNotifyProps),
|
||||
desktopNotifyLevel: getDefaultDesktopNotificationLevel(currentUserNotifyProps, this.isGM()),
|
||||
desktopSound: getDefaultDesktopSound(currentUserNotifyProps),
|
||||
desktopNotifySound: getDefaultDesktopNotificationSound(currentUserNotifyProps),
|
||||
desktopThreadsNotifyLevel: getDefaultDesktopThreadsNotifyLevel(currentUserNotifyProps),
|
||||
@@ -259,7 +276,7 @@ export default class ChannelNotificationsModal extends React.PureComponent<Props
|
||||
const currentUserNotifyProps = this.props.currentUser.notify_props;
|
||||
|
||||
const userPushNotificationDefaults = {
|
||||
pushNotifyLevel: getDefaultPushNotifyLevel(currentUserNotifyProps),
|
||||
pushNotifyLevel: getDefaultPushNotifyLevel(currentUserNotifyProps, this.isGM()),
|
||||
pushThreadsNotifyLevel: getDefaultPushThreadsNotifyLevel(currentUserNotifyProps),
|
||||
};
|
||||
|
||||
@@ -400,6 +417,8 @@ export default class ChannelNotificationsModal extends React.PureComponent<Props
|
||||
serverErrorTag = <div className='form-group has-error'><label className='control-label'>{serverError}</label></div>;
|
||||
}
|
||||
|
||||
const isGM = this.isGM();
|
||||
|
||||
return (
|
||||
<Modal
|
||||
dialogClassName='a11y__modal settings-modal settings-modal--tabless'
|
||||
@@ -435,6 +454,7 @@ export default class ChannelNotificationsModal extends React.PureComponent<Props
|
||||
onSubmit={this.handleSubmitMarkUnreadLevel}
|
||||
onUpdateSection={this.updateSection}
|
||||
serverError={serverError}
|
||||
isGM={isGM}
|
||||
/>
|
||||
<div className='divider-light'/>
|
||||
<NotificationSection
|
||||
@@ -446,6 +466,7 @@ export default class ChannelNotificationsModal extends React.PureComponent<Props
|
||||
onSubmit={this.handleSubmitIgnoreChannelMentions}
|
||||
onUpdateSection={this.updateSection}
|
||||
serverError={serverError}
|
||||
isGM={isGM}
|
||||
/>
|
||||
{!isChannelMuted(channelMember) &&
|
||||
<div>
|
||||
@@ -457,8 +478,8 @@ export default class ChannelNotificationsModal extends React.PureComponent<Props
|
||||
memberThreadsNotificationLevel={desktopThreadsNotifyLevel}
|
||||
memberDesktopSound={desktopSound}
|
||||
memberDesktopNotificationSound={desktopNotifySound}
|
||||
globalNotificationLevel={currentUser.notify_props ? currentUser.notify_props.desktop : NotificationLevels.ALL}
|
||||
globalNotificationSound={(currentUser.notify_props && currentUser.notify_props.desktop_notification_sound) ? currentUser.notify_props.desktop_notification_sound : 'Bing'}
|
||||
globalNotificationLevel={getDefaultDesktopNotificationLevel(currentUser.notify_props, isGM)}
|
||||
globalNotificationSound={getDefaultDesktopNotificationSound(currentUser.notify_props)}
|
||||
isNotificationsSettingSameAsGlobal={isNotificationsSettingSameAsGlobal}
|
||||
onChange={this.handleUpdateDesktopNotifyLevel}
|
||||
onChangeThreads={this.handleUpdateDesktopThreadsNotifyLevel}
|
||||
@@ -468,6 +489,7 @@ export default class ChannelNotificationsModal extends React.PureComponent<Props
|
||||
onSubmit={this.handleSubmitDesktopNotification}
|
||||
onUpdateSection={this.updateSection}
|
||||
serverError={serverError}
|
||||
isGM={isGM}
|
||||
/>
|
||||
<div className='divider-light'/>
|
||||
{sendPushNotifications &&
|
||||
@@ -476,7 +498,7 @@ export default class ChannelNotificationsModal extends React.PureComponent<Props
|
||||
expand={activeSection === NotificationSections.PUSH}
|
||||
memberNotificationLevel={pushNotifyLevel}
|
||||
memberThreadsNotificationLevel={pushThreadsNotifyLevel}
|
||||
globalNotificationLevel={currentUser.notify_props ? currentUser.notify_props.push : NotificationLevels.ALL}
|
||||
globalNotificationLevel={getDefaultPushNotifyLevel(currentUser.notify_props, isGM)}
|
||||
isNotificationsSettingSameAsGlobal={isPushNotificationsSettingSameAsGlobal}
|
||||
onChange={this.handleUpdatePushNotificationLevel}
|
||||
onReset={this.handleResetPushNotification}
|
||||
@@ -484,22 +506,28 @@ export default class ChannelNotificationsModal extends React.PureComponent<Props
|
||||
onSubmit={this.handleSubmitPushNotificationLevel}
|
||||
onUpdateSection={this.updateSection}
|
||||
serverError={serverError}
|
||||
isGM={isGM}
|
||||
/>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
<div className='divider-light'/>
|
||||
<NotificationSection
|
||||
section={NotificationSections.CHANNEL_AUTO_FOLLOW_THREADS}
|
||||
expand={activeSection === NotificationSections.CHANNEL_AUTO_FOLLOW_THREADS}
|
||||
memberNotificationLevel={markUnreadNotifyLevel}
|
||||
ignoreChannelMentions={ignoreChannelMentions}
|
||||
channelAutoFollowThreads={channelAutoFollowThreads}
|
||||
onChange={this.handleUpdateChannelAutoFollowThreads}
|
||||
onSubmit={this.handleSubmitChannelAutoFollowThreads}
|
||||
onUpdateSection={this.updateSection}
|
||||
serverError={serverError}
|
||||
/>
|
||||
{!isGM &&
|
||||
<>
|
||||
<div className='divider-light'/>
|
||||
<NotificationSection
|
||||
section={NotificationSections.CHANNEL_AUTO_FOLLOW_THREADS}
|
||||
expand={activeSection === NotificationSections.CHANNEL_AUTO_FOLLOW_THREADS}
|
||||
memberNotificationLevel={markUnreadNotifyLevel}
|
||||
ignoreChannelMentions={ignoreChannelMentions}
|
||||
channelAutoFollowThreads={channelAutoFollowThreads}
|
||||
onChange={this.handleUpdateChannelAutoFollowThreads}
|
||||
onSubmit={this.handleSubmitChannelAutoFollowThreads}
|
||||
onUpdateSection={this.updateSection}
|
||||
serverError={serverError}
|
||||
isGM={isGM}
|
||||
/>
|
||||
</>
|
||||
}
|
||||
<div className='divider-dark'/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
exports[`components/channel_notifications_modal/NotificationSection should match snapshot, on DESKTOP/PUSH & ALL 1`] = `
|
||||
<MemoizedFormattedMessage
|
||||
defaultMessage="For all activity ({isDefault})"
|
||||
defaultMessage="For all activity {isDefault}"
|
||||
id="channel_notifications.allActivity"
|
||||
values={
|
||||
Object {
|
||||
@@ -21,7 +21,7 @@ exports[`components/channel_notifications_modal/NotificationSection should match
|
||||
|
||||
exports[`components/channel_notifications_modal/NotificationSection should match snapshot, on MENTION 1`] = `
|
||||
<MemoizedFormattedMessage
|
||||
defaultMessage="Only for mentions ({isDefault})"
|
||||
defaultMessage="Only for mentions {isDefault}"
|
||||
id="channel_notifications.onlyMentions"
|
||||
values={
|
||||
Object {
|
||||
@@ -33,7 +33,7 @@ exports[`components/channel_notifications_modal/NotificationSection should match
|
||||
|
||||
exports[`components/channel_notifications_modal/NotificationSection should match snapshot, on NONE 1`] = `
|
||||
<MemoizedFormattedMessage
|
||||
defaultMessage="Never ({isDefault})"
|
||||
defaultMessage="Never {isDefault}"
|
||||
id="channel_notifications.never"
|
||||
values={
|
||||
Object {
|
||||
|
||||
@@ -1,6 +1,269 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`components/channel_notifications_modal/ExpandView should match snapshot, DESKTOP on expanded view 1`] = `
|
||||
exports[`components/channel_notifications_modal/ExpandView gms should match snapshot, DESKTOP on expanded view when mentions is selected 1`] = `
|
||||
<SettingItemMax
|
||||
containerStyle=""
|
||||
infoPosition="bottom"
|
||||
inputs={
|
||||
Array [
|
||||
<div>
|
||||
<fieldset>
|
||||
<legend
|
||||
className="form-legend"
|
||||
>
|
||||
<Memo(MemoizedFormattedMessage)
|
||||
defaultMessage="Send desktop notifications"
|
||||
id="channel_notifications.sendDesktop"
|
||||
/>
|
||||
</legend>
|
||||
<div
|
||||
className="radio"
|
||||
>
|
||||
<label
|
||||
className=""
|
||||
>
|
||||
<input
|
||||
checked={false}
|
||||
id="channelNotificationAllActivity"
|
||||
name="channelNotifications"
|
||||
onChange={[MockFunction]}
|
||||
type="radio"
|
||||
value="all"
|
||||
/>
|
||||
<Describe
|
||||
globalNotifyLevel="default"
|
||||
memberNotifyLevel="all"
|
||||
section="desktop"
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
<div
|
||||
className="radio"
|
||||
>
|
||||
<label
|
||||
className=""
|
||||
>
|
||||
<input
|
||||
checked={true}
|
||||
id="channelNotificationMentions"
|
||||
name="channelNotifications"
|
||||
onChange={[MockFunction]}
|
||||
type="radio"
|
||||
value="mention"
|
||||
/>
|
||||
<Describe
|
||||
globalNotifyLevel="default"
|
||||
memberNotifyLevel="mention"
|
||||
section="desktop"
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
<div
|
||||
className="radio"
|
||||
>
|
||||
<label>
|
||||
<input
|
||||
checked={false}
|
||||
id="channelNotificationNever"
|
||||
name="channelNotifications"
|
||||
onChange={[MockFunction]}
|
||||
type="radio"
|
||||
value="none"
|
||||
/>
|
||||
<Describe
|
||||
globalNotifyLevel="default"
|
||||
memberNotifyLevel="none"
|
||||
section="desktop"
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
</fieldset>
|
||||
<div
|
||||
className="mt-5"
|
||||
>
|
||||
<ExtraInfo
|
||||
section="desktop"
|
||||
/>
|
||||
</div>
|
||||
<React.Fragment>
|
||||
<hr />
|
||||
<fieldset>
|
||||
<legend
|
||||
className="form-legend"
|
||||
>
|
||||
<Memo(MemoizedFormattedMessage)
|
||||
defaultMessage="Notification sound"
|
||||
id="channel_notifications.sound"
|
||||
/>
|
||||
</legend>
|
||||
<div
|
||||
className="radio"
|
||||
>
|
||||
<label
|
||||
className=""
|
||||
>
|
||||
<input
|
||||
checked={false}
|
||||
id="channelDesktopSoundOn"
|
||||
name="channelDesktopSound"
|
||||
type="radio"
|
||||
value="on"
|
||||
/>
|
||||
<Memo(MemoizedFormattedMessage)
|
||||
defaultMessage="On"
|
||||
id="channel_notifications.sound.on.title"
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
<div
|
||||
className="radio"
|
||||
>
|
||||
<label>
|
||||
<input
|
||||
checked={false}
|
||||
id="channelDesktopSoundOff"
|
||||
name="channelDesktopSound"
|
||||
type="radio"
|
||||
value="off"
|
||||
/>
|
||||
<Memo(MemoizedFormattedMessage)
|
||||
defaultMessage="Off"
|
||||
id="channel_notifications.sound.off.title"
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
<div
|
||||
className="mt-5"
|
||||
>
|
||||
<Memo(MemoizedFormattedMessage)
|
||||
defaultMessage="Notification sounds are available on Firefox, Edge, Safari, Chrome and Mattermost Desktop Apps."
|
||||
id="channel_notifications.sound_info"
|
||||
/>
|
||||
</div>
|
||||
</fieldset>
|
||||
</React.Fragment>
|
||||
</div>,
|
||||
]
|
||||
}
|
||||
saving={false}
|
||||
section=""
|
||||
serverError=""
|
||||
submit={[MockFunction]}
|
||||
title={
|
||||
<SectionTitle
|
||||
isExpanded={true}
|
||||
onClickResetButton={[MockFunction]}
|
||||
section="desktop"
|
||||
/>
|
||||
}
|
||||
updateSection={[MockFunction]}
|
||||
/>
|
||||
`;
|
||||
|
||||
exports[`components/channel_notifications_modal/ExpandView gms should match snapshot, PUSH on expanded view when mentions is selected 1`] = `
|
||||
<SettingItemMax
|
||||
containerStyle=""
|
||||
infoPosition="bottom"
|
||||
inputs={
|
||||
Array [
|
||||
<div>
|
||||
<fieldset>
|
||||
<legend
|
||||
className="form-legend"
|
||||
>
|
||||
<Memo(MemoizedFormattedMessage)
|
||||
defaultMessage="Send mobile push notifications"
|
||||
id="channel_notifications.sendMobilePush"
|
||||
/>
|
||||
</legend>
|
||||
<div
|
||||
className="radio"
|
||||
>
|
||||
<label
|
||||
className=""
|
||||
>
|
||||
<input
|
||||
checked={false}
|
||||
id="channelNotificationAllActivity"
|
||||
name="channelNotifications"
|
||||
onChange={[MockFunction]}
|
||||
type="radio"
|
||||
value="all"
|
||||
/>
|
||||
<Describe
|
||||
globalNotifyLevel="default"
|
||||
memberNotifyLevel="all"
|
||||
section="push"
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
<div
|
||||
className="radio"
|
||||
>
|
||||
<label
|
||||
className=""
|
||||
>
|
||||
<input
|
||||
checked={true}
|
||||
id="channelNotificationMentions"
|
||||
name="channelNotifications"
|
||||
onChange={[MockFunction]}
|
||||
type="radio"
|
||||
value="mention"
|
||||
/>
|
||||
<Describe
|
||||
globalNotifyLevel="default"
|
||||
memberNotifyLevel="mention"
|
||||
section="push"
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
<div
|
||||
className="radio"
|
||||
>
|
||||
<label>
|
||||
<input
|
||||
checked={false}
|
||||
id="channelNotificationNever"
|
||||
name="channelNotifications"
|
||||
onChange={[MockFunction]}
|
||||
type="radio"
|
||||
value="none"
|
||||
/>
|
||||
<Describe
|
||||
globalNotifyLevel="default"
|
||||
memberNotifyLevel="none"
|
||||
section="push"
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
</fieldset>
|
||||
<div
|
||||
className="mt-5"
|
||||
>
|
||||
<ExtraInfo
|
||||
section="push"
|
||||
/>
|
||||
</div>
|
||||
</div>,
|
||||
]
|
||||
}
|
||||
saving={false}
|
||||
section=""
|
||||
serverError=""
|
||||
submit={[MockFunction]}
|
||||
title={
|
||||
<SectionTitle
|
||||
isExpanded={true}
|
||||
onClickResetButton={[MockFunction]}
|
||||
section="push"
|
||||
/>
|
||||
}
|
||||
updateSection={[MockFunction]}
|
||||
/>
|
||||
`;
|
||||
|
||||
exports[`components/channel_notifications_modal/ExpandView normal channels should match snapshot, DESKTOP on expanded view 1`] = `
|
||||
<SettingItemMax
|
||||
containerStyle=""
|
||||
infoPosition="bottom"
|
||||
@@ -160,7 +423,206 @@ exports[`components/channel_notifications_modal/ExpandView should match snapshot
|
||||
/>
|
||||
`;
|
||||
|
||||
exports[`components/channel_notifications_modal/ExpandView should match snapshot, MARK_UNREAD on expanded view 1`] = `
|
||||
exports[`components/channel_notifications_modal/ExpandView normal channels should match snapshot, DESKTOP on expanded view when mentions is selected 1`] = `
|
||||
<SettingItemMax
|
||||
containerStyle=""
|
||||
infoPosition="bottom"
|
||||
inputs={
|
||||
Array [
|
||||
<div>
|
||||
<fieldset>
|
||||
<legend
|
||||
className="form-legend"
|
||||
>
|
||||
<Memo(MemoizedFormattedMessage)
|
||||
defaultMessage="Send desktop notifications"
|
||||
id="channel_notifications.sendDesktop"
|
||||
/>
|
||||
</legend>
|
||||
<div
|
||||
className="radio"
|
||||
>
|
||||
<label
|
||||
className=""
|
||||
>
|
||||
<input
|
||||
checked={false}
|
||||
id="channelNotificationAllActivity"
|
||||
name="channelNotifications"
|
||||
onChange={[MockFunction]}
|
||||
type="radio"
|
||||
value="all"
|
||||
/>
|
||||
<Describe
|
||||
globalNotifyLevel="default"
|
||||
memberNotifyLevel="all"
|
||||
section="desktop"
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
<div
|
||||
className="radio"
|
||||
>
|
||||
<label
|
||||
className=""
|
||||
>
|
||||
<input
|
||||
checked={true}
|
||||
id="channelNotificationMentions"
|
||||
name="channelNotifications"
|
||||
onChange={[MockFunction]}
|
||||
type="radio"
|
||||
value="mention"
|
||||
/>
|
||||
<Describe
|
||||
globalNotifyLevel="default"
|
||||
memberNotifyLevel="mention"
|
||||
section="desktop"
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
<div
|
||||
className="radio"
|
||||
>
|
||||
<label>
|
||||
<input
|
||||
checked={false}
|
||||
id="channelNotificationNever"
|
||||
name="channelNotifications"
|
||||
onChange={[MockFunction]}
|
||||
type="radio"
|
||||
value="none"
|
||||
/>
|
||||
<Describe
|
||||
globalNotifyLevel="default"
|
||||
memberNotifyLevel="none"
|
||||
section="desktop"
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
</fieldset>
|
||||
<div
|
||||
className="mt-5"
|
||||
>
|
||||
<ExtraInfo
|
||||
section="desktop"
|
||||
/>
|
||||
</div>
|
||||
<React.Fragment>
|
||||
<hr />
|
||||
<fieldset>
|
||||
<legend
|
||||
className="form-legend"
|
||||
>
|
||||
<Memo(MemoizedFormattedMessage)
|
||||
defaultMessage="Thread reply notifications"
|
||||
id="user.settings.notifications.threads.desktop"
|
||||
/>
|
||||
</legend>
|
||||
<div
|
||||
className="checkbox"
|
||||
>
|
||||
<label>
|
||||
<input
|
||||
checked={true}
|
||||
id="desktopThreadsNotificationAllActivity"
|
||||
name="desktopThreadsNotificationLevel"
|
||||
onChange={[MockFunction]}
|
||||
type="checkbox"
|
||||
/>
|
||||
<Memo(MemoizedFormattedMessage)
|
||||
defaultMessage="Notify me about threads I'm following"
|
||||
id="user.settings.notifications.threads.allActivity"
|
||||
/>
|
||||
</label>
|
||||
<br />
|
||||
</div>
|
||||
<div
|
||||
className="mt-5"
|
||||
>
|
||||
<Memo(MemoizedFormattedMessage)
|
||||
defaultMessage="When enabled, any reply to a thread you're following will send a desktop notification."
|
||||
id="user.settings.notifications.threads"
|
||||
/>
|
||||
</div>
|
||||
</fieldset>
|
||||
</React.Fragment>
|
||||
<React.Fragment>
|
||||
<hr />
|
||||
<fieldset>
|
||||
<legend
|
||||
className="form-legend"
|
||||
>
|
||||
<Memo(MemoizedFormattedMessage)
|
||||
defaultMessage="Notification sound"
|
||||
id="channel_notifications.sound"
|
||||
/>
|
||||
</legend>
|
||||
<div
|
||||
className="radio"
|
||||
>
|
||||
<label
|
||||
className=""
|
||||
>
|
||||
<input
|
||||
checked={false}
|
||||
id="channelDesktopSoundOn"
|
||||
name="channelDesktopSound"
|
||||
type="radio"
|
||||
value="on"
|
||||
/>
|
||||
<Memo(MemoizedFormattedMessage)
|
||||
defaultMessage="On"
|
||||
id="channel_notifications.sound.on.title"
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
<div
|
||||
className="radio"
|
||||
>
|
||||
<label>
|
||||
<input
|
||||
checked={false}
|
||||
id="channelDesktopSoundOff"
|
||||
name="channelDesktopSound"
|
||||
type="radio"
|
||||
value="off"
|
||||
/>
|
||||
<Memo(MemoizedFormattedMessage)
|
||||
defaultMessage="Off"
|
||||
id="channel_notifications.sound.off.title"
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
<div
|
||||
className="mt-5"
|
||||
>
|
||||
<Memo(MemoizedFormattedMessage)
|
||||
defaultMessage="Notification sounds are available on Firefox, Edge, Safari, Chrome and Mattermost Desktop Apps."
|
||||
id="channel_notifications.sound_info"
|
||||
/>
|
||||
</div>
|
||||
</fieldset>
|
||||
</React.Fragment>
|
||||
</div>,
|
||||
]
|
||||
}
|
||||
saving={false}
|
||||
section=""
|
||||
serverError=""
|
||||
submit={[MockFunction]}
|
||||
title={
|
||||
<SectionTitle
|
||||
isExpanded={true}
|
||||
onClickResetButton={[MockFunction]}
|
||||
section="desktop"
|
||||
/>
|
||||
}
|
||||
updateSection={[MockFunction]}
|
||||
/>
|
||||
`;
|
||||
|
||||
exports[`components/channel_notifications_modal/ExpandView normal channels should match snapshot, MARK_UNREAD on expanded view 1`] = `
|
||||
<SettingItemMax
|
||||
containerStyle=""
|
||||
infoPosition="bottom"
|
||||
@@ -234,7 +696,7 @@ exports[`components/channel_notifications_modal/ExpandView should match snapshot
|
||||
/>
|
||||
`;
|
||||
|
||||
exports[`components/channel_notifications_modal/ExpandView should match snapshot, PUSH on expanded view 1`] = `
|
||||
exports[`components/channel_notifications_modal/ExpandView normal channels should match snapshot, PUSH on expanded view 1`] = `
|
||||
<SettingItemMax
|
||||
containerStyle=""
|
||||
infoPosition="bottom"
|
||||
@@ -336,3 +798,145 @@ exports[`components/channel_notifications_modal/ExpandView should match snapshot
|
||||
updateSection={[MockFunction]}
|
||||
/>
|
||||
`;
|
||||
|
||||
exports[`components/channel_notifications_modal/ExpandView normal channels should match snapshot, PUSH on expanded view when mentions is selected 1`] = `
|
||||
<SettingItemMax
|
||||
containerStyle=""
|
||||
infoPosition="bottom"
|
||||
inputs={
|
||||
Array [
|
||||
<div>
|
||||
<fieldset>
|
||||
<legend
|
||||
className="form-legend"
|
||||
>
|
||||
<Memo(MemoizedFormattedMessage)
|
||||
defaultMessage="Send mobile push notifications"
|
||||
id="channel_notifications.sendMobilePush"
|
||||
/>
|
||||
</legend>
|
||||
<div
|
||||
className="radio"
|
||||
>
|
||||
<label
|
||||
className=""
|
||||
>
|
||||
<input
|
||||
checked={false}
|
||||
id="channelNotificationAllActivity"
|
||||
name="channelNotifications"
|
||||
onChange={[MockFunction]}
|
||||
type="radio"
|
||||
value="all"
|
||||
/>
|
||||
<Describe
|
||||
globalNotifyLevel="default"
|
||||
memberNotifyLevel="all"
|
||||
section="push"
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
<div
|
||||
className="radio"
|
||||
>
|
||||
<label
|
||||
className=""
|
||||
>
|
||||
<input
|
||||
checked={true}
|
||||
id="channelNotificationMentions"
|
||||
name="channelNotifications"
|
||||
onChange={[MockFunction]}
|
||||
type="radio"
|
||||
value="mention"
|
||||
/>
|
||||
<Describe
|
||||
globalNotifyLevel="default"
|
||||
memberNotifyLevel="mention"
|
||||
section="push"
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
<div
|
||||
className="radio"
|
||||
>
|
||||
<label>
|
||||
<input
|
||||
checked={false}
|
||||
id="channelNotificationNever"
|
||||
name="channelNotifications"
|
||||
onChange={[MockFunction]}
|
||||
type="radio"
|
||||
value="none"
|
||||
/>
|
||||
<Describe
|
||||
globalNotifyLevel="default"
|
||||
memberNotifyLevel="none"
|
||||
section="push"
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
</fieldset>
|
||||
<div
|
||||
className="mt-5"
|
||||
>
|
||||
<ExtraInfo
|
||||
section="push"
|
||||
/>
|
||||
</div>
|
||||
<React.Fragment>
|
||||
<hr />
|
||||
<fieldset>
|
||||
<legend
|
||||
className="form-legend"
|
||||
>
|
||||
<Memo(MemoizedFormattedMessage)
|
||||
defaultMessage="Thread reply notifications"
|
||||
id="user.settings.notifications.threads.push"
|
||||
/>
|
||||
</legend>
|
||||
<div
|
||||
className="checkbox"
|
||||
>
|
||||
<label>
|
||||
<input
|
||||
checked={true}
|
||||
id="pushThreadsNotificationAllActivity"
|
||||
name="pushThreadsNotificationLevel"
|
||||
onChange={[MockFunction]}
|
||||
type="checkbox"
|
||||
/>
|
||||
<Memo(MemoizedFormattedMessage)
|
||||
defaultMessage="Notify me about threads I'm following"
|
||||
id="user.settings.notifications.push_threads.allActivity"
|
||||
/>
|
||||
</label>
|
||||
<br />
|
||||
</div>
|
||||
<div
|
||||
className="mt-5"
|
||||
>
|
||||
<Memo(MemoizedFormattedMessage)
|
||||
defaultMessage="When enabled, any reply to a thread you're following will send a mobile push notification."
|
||||
id="user.settings.notifications.push_threads"
|
||||
/>
|
||||
</div>
|
||||
</fieldset>
|
||||
</React.Fragment>
|
||||
</div>,
|
||||
]
|
||||
}
|
||||
saving={false}
|
||||
section=""
|
||||
serverError=""
|
||||
submit={[MockFunction]}
|
||||
title={
|
||||
<SectionTitle
|
||||
isExpanded={true}
|
||||
onClickResetButton={[MockFunction]}
|
||||
section="push"
|
||||
/>
|
||||
}
|
||||
updateSection={[MockFunction]}
|
||||
/>
|
||||
`;
|
||||
|
||||
@@ -21,6 +21,7 @@ exports[`components/channel_notifications_modal/NotificationSection should match
|
||||
exports[`components/channel_notifications_modal/NotificationSection should match snapshot, DESKTOP on expanded view 1`] = `
|
||||
<ExpandView
|
||||
globalNotifyLevel="default"
|
||||
isGM={false}
|
||||
memberNotifyLevel="all"
|
||||
memberThreadsNotifyLevel="all"
|
||||
onChange={[Function]}
|
||||
@@ -47,6 +48,7 @@ exports[`components/channel_notifications_modal/NotificationSection should match
|
||||
exports[`components/channel_notifications_modal/NotificationSection should match snapshot, MARK_UNREAD on expanded view 1`] = `
|
||||
<ExpandView
|
||||
globalNotifyLevel={null}
|
||||
isGM={false}
|
||||
memberNotifyLevel="all"
|
||||
memberThreadsNotifyLevel="all"
|
||||
onChange={[Function]}
|
||||
@@ -73,6 +75,7 @@ exports[`components/channel_notifications_modal/NotificationSection should match
|
||||
exports[`components/channel_notifications_modal/NotificationSection should match snapshot, PUSH on expanded view 1`] = `
|
||||
<ExpandView
|
||||
globalNotifyLevel="default"
|
||||
isGM={false}
|
||||
memberNotifyLevel="all"
|
||||
memberThreadsNotifyLevel="all"
|
||||
onChange={[Function]}
|
||||
|
||||
@@ -102,7 +102,7 @@ export default function Describe({section, isCollapsed, memberNotifyLevel, globa
|
||||
return (
|
||||
<FormattedMessage
|
||||
id='channel_notifications.onlyMentions'
|
||||
defaultMessage='Only for mentions ({isDefault})'
|
||||
defaultMessage='Only for mentions {isDefault}'
|
||||
values={{isDefault: globalNotifyLevel === NotificationLevels.MENTION ? defaultOption : <></>}}
|
||||
/>
|
||||
);
|
||||
@@ -113,7 +113,7 @@ export default function Describe({section, isCollapsed, memberNotifyLevel, globa
|
||||
return (
|
||||
<FormattedMessage
|
||||
id='channel_notifications.allActivity'
|
||||
defaultMessage='For all activity ({isDefault})'
|
||||
defaultMessage='For all activity {isDefault}'
|
||||
values={{isDefault: globalNotifyLevel === NotificationLevels.ALL ? defaultOption : <></>}}
|
||||
/>
|
||||
);
|
||||
@@ -132,7 +132,7 @@ export default function Describe({section, isCollapsed, memberNotifyLevel, globa
|
||||
return (
|
||||
<FormattedMessage
|
||||
id='channel_notifications.never'
|
||||
defaultMessage='Never ({isDefault})'
|
||||
defaultMessage='Never {isDefault}'
|
||||
values={{isDefault: globalNotifyLevel === NotificationLevels.NONE ? defaultOption : <></>}}
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -10,7 +10,7 @@ import {NotificationLevels, NotificationSections} from 'utils/constants';
|
||||
|
||||
jest.mock('react-redux', () => ({
|
||||
...jest.requireActual('react-redux'),
|
||||
useSelector: jest.fn(),
|
||||
useSelector: jest.fn(() => true),
|
||||
}));
|
||||
|
||||
describe('components/channel_notifications_modal/ExpandView', () => {
|
||||
@@ -25,31 +25,72 @@ describe('components/channel_notifications_modal/ExpandView', () => {
|
||||
onCollapseSection: jest.fn(),
|
||||
onSubmit: jest.fn(),
|
||||
onReset: jest.fn(),
|
||||
isGM: false,
|
||||
};
|
||||
|
||||
test('should match snapshot, DESKTOP on expanded view', () => {
|
||||
const wrapper = shallow(
|
||||
<ExpandView {...baseProps}/>,
|
||||
);
|
||||
describe('normal channels', () => {
|
||||
test('should match snapshot, DESKTOP on expanded view', () => {
|
||||
const wrapper = shallow(
|
||||
<ExpandView {...baseProps}/>,
|
||||
);
|
||||
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('should match snapshot, PUSH on expanded view', () => {
|
||||
const props = {...baseProps, section: NotificationSections.PUSH};
|
||||
const wrapper = shallow(
|
||||
<ExpandView {...props}/>,
|
||||
);
|
||||
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('should match snapshot, MARK_UNREAD on expanded view', () => {
|
||||
const props = {...baseProps, section: NotificationSections.MARK_UNREAD};
|
||||
const wrapper = shallow(
|
||||
<ExpandView {...props}/>,
|
||||
);
|
||||
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('should match snapshot, DESKTOP on expanded view when mentions is selected', () => {
|
||||
const props = {...baseProps, memberNotifyLevel: NotificationLevels.MENTION};
|
||||
const wrapper = shallow(
|
||||
<ExpandView {...props}/>,
|
||||
);
|
||||
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('should match snapshot, PUSH on expanded view when mentions is selected', () => {
|
||||
const props = {...baseProps, section: NotificationSections.PUSH, memberNotifyLevel: NotificationLevels.MENTION};
|
||||
const wrapper = shallow(
|
||||
<ExpandView {...props}/>,
|
||||
);
|
||||
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
|
||||
test('should match snapshot, PUSH on expanded view', () => {
|
||||
const props = {...baseProps, section: NotificationSections.PUSH};
|
||||
const wrapper = shallow(
|
||||
<ExpandView {...props}/>,
|
||||
);
|
||||
describe('gms', () => {
|
||||
test('should match snapshot, DESKTOP on expanded view when mentions is selected', () => {
|
||||
const props = {...baseProps, isGM: true, memberNotifyLevel: NotificationLevels.MENTION};
|
||||
const wrapper = shallow(
|
||||
<ExpandView {...props}/>,
|
||||
);
|
||||
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('should match snapshot, MARK_UNREAD on expanded view', () => {
|
||||
const props = {...baseProps, section: NotificationSections.MARK_UNREAD};
|
||||
const wrapper = shallow(
|
||||
<ExpandView {...props}/>,
|
||||
);
|
||||
test('should match snapshot, PUSH on expanded view when mentions is selected', () => {
|
||||
const props = {...baseProps, section: NotificationSections.PUSH, isGM: true, memberNotifyLevel: NotificationLevels.MENTION};
|
||||
const wrapper = shallow(
|
||||
<ExpandView {...props}/>,
|
||||
);
|
||||
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -45,6 +45,7 @@ type Props = {
|
||||
memberDesktopNotificationSound?: string;
|
||||
section: string;
|
||||
serverError?: string;
|
||||
isGM: boolean;
|
||||
}
|
||||
|
||||
const sounds = Array.from(notificationSounds.keys());
|
||||
@@ -74,6 +75,7 @@ export default function ExpandView({
|
||||
onCollapseSection,
|
||||
ignoreChannelMentions,
|
||||
channelAutoFollowThreads,
|
||||
isGM,
|
||||
}: Props) {
|
||||
const isCRTEnabled = useSelector(isCollapsedThreadsEnabled);
|
||||
|
||||
@@ -275,6 +277,7 @@ export default function ExpandView({
|
||||
{isCRTEnabled &&
|
||||
section === NotificationSections.DESKTOP &&
|
||||
memberNotifyLevel === NotificationLevels.MENTION &&
|
||||
!isGM &&
|
||||
<>
|
||||
<hr/>
|
||||
<fieldset>
|
||||
@@ -378,6 +381,7 @@ export default function ExpandView({
|
||||
{isCRTEnabled &&
|
||||
section === NotificationSections.PUSH &&
|
||||
memberNotifyLevel === NotificationLevels.MENTION &&
|
||||
!isGM &&
|
||||
<>
|
||||
<hr/>
|
||||
<fieldset>
|
||||
|
||||
@@ -88,6 +88,11 @@ export default class NotificationSection extends React.PureComponent {
|
||||
* Error string from the server
|
||||
*/
|
||||
serverError: PropTypes.string,
|
||||
|
||||
/**
|
||||
* Whether the preferences are those of a GM
|
||||
*/
|
||||
isGM: PropTypes.bool,
|
||||
};
|
||||
|
||||
handleOnChange = (e) => {
|
||||
@@ -134,6 +139,7 @@ export default class NotificationSection extends React.PureComponent {
|
||||
onReset,
|
||||
section,
|
||||
serverError,
|
||||
isGM,
|
||||
} = this.props;
|
||||
|
||||
if (expand) {
|
||||
@@ -157,6 +163,7 @@ export default class NotificationSection extends React.PureComponent {
|
||||
onSubmit={onSubmit}
|
||||
serverError={serverError}
|
||||
onCollapseSection={this.handleCollapseSection}
|
||||
isGM={isGM}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -21,6 +21,7 @@ describe('components/channel_notifications_modal/NotificationSection', () => {
|
||||
onSubmit: () => {}, //eslint-disable-line no-empty-function
|
||||
onUpdateSection: () => {}, //eslint-disable-line no-empty-function
|
||||
serverError: '',
|
||||
isGM: false,
|
||||
};
|
||||
|
||||
test('should match snapshot, DESKTOP on collapsed view', () => {
|
||||
|
||||
@@ -32,6 +32,7 @@ describe('components/post_view/ChannelIntroMessages', () => {
|
||||
const users = [
|
||||
{id: 'user1', roles: 'system_user'},
|
||||
{id: 'guest1', roles: 'system_guest'},
|
||||
{id: 'test-user-id', roles: 'system_user'},
|
||||
] as UserProfile[];
|
||||
|
||||
const baseProps = {
|
||||
@@ -153,6 +154,9 @@ describe('components/post_view/ChannelIntroMessages', () => {
|
||||
|
||||
expect(editIcon).toBeInTheDocument();
|
||||
expect(editIcon).toHaveClass('icon-pencil-outline');
|
||||
|
||||
const notificationPreferencesButton = screen.getByText('Notification Preferences');
|
||||
expect(notificationPreferencesButton).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -4,12 +4,14 @@
|
||||
import React from 'react';
|
||||
import {FormattedDate, FormattedMessage} from 'react-intl';
|
||||
|
||||
import {BellRingOutlineIcon} from '@mattermost/compass-icons/components';
|
||||
import type {Channel} from '@mattermost/types/channels';
|
||||
import type {UserProfile as UserProfileRedux} from '@mattermost/types/users';
|
||||
import type {UserProfile as UserProfileType} from '@mattermost/types/users';
|
||||
|
||||
import {Permissions} from 'mattermost-redux/constants';
|
||||
|
||||
import AddGroupsToTeamModal from 'components/add_groups_to_team_modal';
|
||||
import ChannelNotificationsModal from 'components/channel_notifications_modal';
|
||||
import EditChannelHeaderModal from 'components/edit_channel_header_modal';
|
||||
import FormattedMarkdownMessage from 'components/formatted_markdown_message';
|
||||
import LocalizedIcon from 'components/localized_icon';
|
||||
@@ -32,12 +34,12 @@ type Props = {
|
||||
channel: Channel;
|
||||
fullWidth: boolean;
|
||||
locale: string;
|
||||
channelProfiles: UserProfileRedux[];
|
||||
channelProfiles: UserProfileType[];
|
||||
enableUserCreation?: boolean;
|
||||
isReadOnly?: boolean;
|
||||
teamIsGroupConstrained?: boolean;
|
||||
creatorName: string;
|
||||
teammate?: UserProfileRedux;
|
||||
teammate?: UserProfileType;
|
||||
teammateName?: string;
|
||||
stats: any;
|
||||
usersLimit: number;
|
||||
@@ -89,10 +91,11 @@ export default class ChannelIntroMessage extends React.PureComponent<Props> {
|
||||
}
|
||||
}
|
||||
|
||||
function createGMIntroMessage(channel: Channel, centeredIntro: string, profiles: UserProfileRedux[], currentUserId: string) {
|
||||
function createGMIntroMessage(channel: Channel, centeredIntro: string, profiles: UserProfileType[], currentUserId: string) {
|
||||
const channelIntroId = 'channelIntro';
|
||||
|
||||
if (profiles.length > 0) {
|
||||
const currentUserProfile = profiles.find((v) => v.id === currentUserId);
|
||||
const pictures = profiles.
|
||||
filter((profile) => profile.id !== currentUserId).
|
||||
map((profile) => (
|
||||
@@ -114,16 +117,21 @@ function createGMIntroMessage(channel: Channel, centeredIntro: string, profiles:
|
||||
{pictures}
|
||||
</div>
|
||||
<p className='channel-intro-text'>
|
||||
<FormattedMarkdownMessage
|
||||
<FormattedMessage
|
||||
id='intro_messages.GM'
|
||||
defaultMessage='This is the start of your group message history with {names}.\nMessages and files shared here are not shown to people outside this area.'
|
||||
defaultMessage={'This is the start of your group message history with {names}.{br}You\'ll be notified <b>for all activity</b> in this group message.'}
|
||||
values={{
|
||||
b: (chunks) => <b>{chunks}</b>,
|
||||
names: channel.display_name,
|
||||
br: <br/>,
|
||||
}}
|
||||
/>
|
||||
</p>
|
||||
<PluggableIntroButtons channel={channel}/>
|
||||
{createSetHeaderButton(channel)}
|
||||
<div style={{display: 'flex'}}>
|
||||
{createNotificationPreferencesButton(channel, currentUserProfile)}
|
||||
<PluggableIntroButtons channel={channel}/>
|
||||
{createSetHeaderButton(channel)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -143,7 +151,7 @@ function createGMIntroMessage(channel: Channel, centeredIntro: string, profiles:
|
||||
);
|
||||
}
|
||||
|
||||
function createDMIntroMessage(channel: Channel, centeredIntro: string, teammate?: UserProfileRedux, teammateName?: string) {
|
||||
function createDMIntroMessage(channel: Channel, centeredIntro: string, teammate?: UserProfileType, teammateName?: string) {
|
||||
const channelIntroId = 'channelIntro';
|
||||
if (teammate) {
|
||||
const src = teammate ? Utils.imageURLForUser(teammate.id, teammate.last_picture_update) : '';
|
||||
@@ -185,8 +193,10 @@ function createDMIntroMessage(channel: Channel, centeredIntro: string, teammate?
|
||||
}}
|
||||
/>
|
||||
</p>
|
||||
{pluggableButton}
|
||||
{setHeaderButton}
|
||||
<div style={{display: 'flex'}}>
|
||||
{pluggableButton}
|
||||
{setHeaderButton}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -555,3 +565,26 @@ function createSetHeaderButton(channel: Channel) {
|
||||
</ToggleModalButton>
|
||||
);
|
||||
}
|
||||
|
||||
function createNotificationPreferencesButton(channel: Channel, currentUser?: UserProfileType) {
|
||||
const isGM = channel.type === 'G';
|
||||
if (!isGM || !currentUser) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<ToggleModalButton
|
||||
modalId={ModalIdentifiers.CHANNEL_NOTIFICATIONS}
|
||||
ariaLabel={Utils.localizeMessage('intro_messages.notificationPreferences', 'Notification Preferences')}
|
||||
className={'intro-links color--link channelIntroButton'}
|
||||
dialogType={ChannelNotificationsModal}
|
||||
dialogProps={{channel, currentUser}}
|
||||
>
|
||||
<BellRingOutlineIcon size={16}/>
|
||||
<FormattedMessage
|
||||
id='intro_messages.notificationPreferences'
|
||||
defaultMessage='Notification Preferences'
|
||||
/>
|
||||
</ToggleModalButton>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -5,64 +5,60 @@ exports[`components/SystemNotice should match snapshot for admin, admin notice 1
|
||||
className="system-notice bg--white shadow--2"
|
||||
>
|
||||
<div
|
||||
className="system-notice__header"
|
||||
className="system-notice__logo"
|
||||
>
|
||||
<MattermostLogo />
|
||||
</div>
|
||||
<div
|
||||
className="system-notice__body"
|
||||
>
|
||||
<div
|
||||
className="system-notice__logo"
|
||||
>
|
||||
<MattermostLogo />
|
||||
</div>
|
||||
<div
|
||||
className="system-notice__title"
|
||||
>
|
||||
some title
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="system-notice__body"
|
||||
>
|
||||
some body
|
||||
</div>
|
||||
<div
|
||||
className="system-notice__info"
|
||||
>
|
||||
<LocalizedIcon
|
||||
className="fa fa-eye"
|
||||
title={
|
||||
Object {
|
||||
"defaultMessage": "Only visible to System Admins Icon",
|
||||
"id": "system_notice.adminVisible.icon",
|
||||
<div
|
||||
className="system-notice__info"
|
||||
>
|
||||
<LocalizedIcon
|
||||
className="fa fa-eye"
|
||||
title={
|
||||
Object {
|
||||
"defaultMessage": "Only visible to System Admins Icon",
|
||||
"id": "system_notice.adminVisible.icon",
|
||||
}
|
||||
}
|
||||
}
|
||||
/>
|
||||
<MemoizedFormattedMessage
|
||||
defaultMessage="Only visible to System Admins"
|
||||
id="system_notice.adminVisible"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
className="system-notice__footer"
|
||||
>
|
||||
<button
|
||||
className="btn btn-transparent"
|
||||
id="systemnotice_remindme"
|
||||
onClick={[Function]}
|
||||
>
|
||||
<MemoizedFormattedMessage
|
||||
defaultMessage="Remind Me Later"
|
||||
id="system_notice.remind_me"
|
||||
/>
|
||||
</button>
|
||||
<button
|
||||
className="btn btn-transparent"
|
||||
id="systemnotice_dontshow"
|
||||
onClick={[Function]}
|
||||
>
|
||||
<MemoizedFormattedMessage
|
||||
defaultMessage="Don't Show Again"
|
||||
id="system_notice.dont_show"
|
||||
defaultMessage="Only visible to System Admins"
|
||||
id="system_notice.adminVisible"
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
<div
|
||||
className="system-notice__footer"
|
||||
>
|
||||
<button
|
||||
className="btn btn-primary"
|
||||
id="systemnotice_remindme"
|
||||
onClick={[Function]}
|
||||
>
|
||||
<MemoizedFormattedMessage
|
||||
defaultMessage="Remind Me Later"
|
||||
id="system_notice.remind_me"
|
||||
/>
|
||||
</button>
|
||||
<button
|
||||
className="btn"
|
||||
id="systemnotice_dontshow"
|
||||
onClick={[Function]}
|
||||
>
|
||||
<MemoizedFormattedMessage
|
||||
defaultMessage="Don't Show Again"
|
||||
id="system_notice.dont_show"
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
@@ -72,47 +68,43 @@ exports[`components/SystemNotice should match snapshot for admin, regular notice
|
||||
className="system-notice bg--white shadow--2"
|
||||
>
|
||||
<div
|
||||
className="system-notice__header"
|
||||
className="system-notice__logo"
|
||||
>
|
||||
<MattermostLogo />
|
||||
</div>
|
||||
<div
|
||||
className="system-notice__body"
|
||||
>
|
||||
<div
|
||||
className="system-notice__logo"
|
||||
>
|
||||
<MattermostLogo />
|
||||
</div>
|
||||
<div
|
||||
className="system-notice__title"
|
||||
>
|
||||
some title
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="system-notice__body"
|
||||
>
|
||||
some body
|
||||
</div>
|
||||
<div
|
||||
className="system-notice__footer"
|
||||
>
|
||||
<button
|
||||
className="btn btn-transparent"
|
||||
id="systemnotice_remindme"
|
||||
onClick={[Function]}
|
||||
<div
|
||||
className="system-notice__footer"
|
||||
>
|
||||
<MemoizedFormattedMessage
|
||||
defaultMessage="Remind Me Later"
|
||||
id="system_notice.remind_me"
|
||||
/>
|
||||
</button>
|
||||
<button
|
||||
className="btn btn-transparent"
|
||||
id="systemnotice_dontshow"
|
||||
onClick={[Function]}
|
||||
>
|
||||
<MemoizedFormattedMessage
|
||||
defaultMessage="Don't Show Again"
|
||||
id="system_notice.dont_show"
|
||||
/>
|
||||
</button>
|
||||
<button
|
||||
className="btn btn-primary"
|
||||
id="systemnotice_remindme"
|
||||
onClick={[Function]}
|
||||
>
|
||||
<MemoizedFormattedMessage
|
||||
defaultMessage="Remind Me Later"
|
||||
id="system_notice.remind_me"
|
||||
/>
|
||||
</button>
|
||||
<button
|
||||
className="btn"
|
||||
id="systemnotice_dontshow"
|
||||
onClick={[Function]}
|
||||
>
|
||||
<MemoizedFormattedMessage
|
||||
defaultMessage="Don't Show Again"
|
||||
id="system_notice.dont_show"
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
@@ -122,47 +114,43 @@ exports[`components/SystemNotice should match snapshot for regular user, admin a
|
||||
className="system-notice bg--white shadow--2"
|
||||
>
|
||||
<div
|
||||
className="system-notice__header"
|
||||
className="system-notice__logo"
|
||||
>
|
||||
<MattermostLogo />
|
||||
</div>
|
||||
<div
|
||||
className="system-notice__body"
|
||||
>
|
||||
<div
|
||||
className="system-notice__logo"
|
||||
>
|
||||
<MattermostLogo />
|
||||
</div>
|
||||
<div
|
||||
className="system-notice__title"
|
||||
>
|
||||
some title2
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="system-notice__body"
|
||||
>
|
||||
some body2
|
||||
</div>
|
||||
<div
|
||||
className="system-notice__footer"
|
||||
>
|
||||
<button
|
||||
className="btn btn-transparent"
|
||||
id="systemnotice_remindme"
|
||||
onClick={[Function]}
|
||||
<div
|
||||
className="system-notice__footer"
|
||||
>
|
||||
<MemoizedFormattedMessage
|
||||
defaultMessage="Remind Me Later"
|
||||
id="system_notice.remind_me"
|
||||
/>
|
||||
</button>
|
||||
<button
|
||||
className="btn btn-transparent"
|
||||
id="systemnotice_dontshow"
|
||||
onClick={[Function]}
|
||||
>
|
||||
<MemoizedFormattedMessage
|
||||
defaultMessage="Don't Show Again"
|
||||
id="system_notice.dont_show"
|
||||
/>
|
||||
</button>
|
||||
<button
|
||||
className="btn btn-primary"
|
||||
id="systemnotice_remindme"
|
||||
onClick={[Function]}
|
||||
>
|
||||
<MemoizedFormattedMessage
|
||||
defaultMessage="Remind Me Later"
|
||||
id="system_notice.remind_me"
|
||||
/>
|
||||
</button>
|
||||
<button
|
||||
className="btn"
|
||||
id="systemnotice_dontshow"
|
||||
onClick={[Function]}
|
||||
>
|
||||
<MemoizedFormattedMessage
|
||||
defaultMessage="Don't Show Again"
|
||||
id="system_notice.dont_show"
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
@@ -180,47 +168,43 @@ exports[`components/SystemNotice should match snapshot for regular user, regular
|
||||
className="system-notice bg--white shadow--2"
|
||||
>
|
||||
<div
|
||||
className="system-notice__header"
|
||||
className="system-notice__logo"
|
||||
>
|
||||
<MattermostLogo />
|
||||
</div>
|
||||
<div
|
||||
className="system-notice__body"
|
||||
>
|
||||
<div
|
||||
className="system-notice__logo"
|
||||
>
|
||||
<MattermostLogo />
|
||||
</div>
|
||||
<div
|
||||
className="system-notice__title"
|
||||
>
|
||||
some title
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="system-notice__body"
|
||||
>
|
||||
some body
|
||||
</div>
|
||||
<div
|
||||
className="system-notice__footer"
|
||||
>
|
||||
<button
|
||||
className="btn btn-transparent"
|
||||
id="systemnotice_remindme"
|
||||
onClick={[Function]}
|
||||
<div
|
||||
className="system-notice__footer"
|
||||
>
|
||||
<MemoizedFormattedMessage
|
||||
defaultMessage="Remind Me Later"
|
||||
id="system_notice.remind_me"
|
||||
/>
|
||||
</button>
|
||||
<button
|
||||
className="btn btn-transparent"
|
||||
id="systemnotice_dontshow"
|
||||
onClick={[Function]}
|
||||
>
|
||||
<MemoizedFormattedMessage
|
||||
defaultMessage="Don't Show Again"
|
||||
id="system_notice.dont_show"
|
||||
/>
|
||||
</button>
|
||||
<button
|
||||
className="btn btn-primary"
|
||||
id="systemnotice_remindme"
|
||||
onClick={[Function]}
|
||||
>
|
||||
<MemoizedFormattedMessage
|
||||
defaultMessage="Remind Me Later"
|
||||
id="system_notice.remind_me"
|
||||
/>
|
||||
</button>
|
||||
<button
|
||||
className="btn"
|
||||
id="systemnotice_dontshow"
|
||||
onClick={[Function]}
|
||||
>
|
||||
<MemoizedFormattedMessage
|
||||
defaultMessage="Don't Show Again"
|
||||
id="system_notice.dont_show"
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
@@ -232,47 +216,43 @@ exports[`components/SystemNotice should match snapshot for show function returni
|
||||
className="system-notice bg--white shadow--2"
|
||||
>
|
||||
<div
|
||||
className="system-notice__header"
|
||||
className="system-notice__logo"
|
||||
>
|
||||
<MattermostLogo />
|
||||
</div>
|
||||
<div
|
||||
className="system-notice__body"
|
||||
>
|
||||
<div
|
||||
className="system-notice__logo"
|
||||
>
|
||||
<MattermostLogo />
|
||||
</div>
|
||||
<div
|
||||
className="system-notice__title"
|
||||
>
|
||||
some title
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="system-notice__body"
|
||||
>
|
||||
some body
|
||||
</div>
|
||||
<div
|
||||
className="system-notice__footer"
|
||||
>
|
||||
<button
|
||||
className="btn btn-transparent"
|
||||
id="systemnotice_remindme"
|
||||
onClick={[Function]}
|
||||
<div
|
||||
className="system-notice__footer"
|
||||
>
|
||||
<MemoizedFormattedMessage
|
||||
defaultMessage="Remind Me Later"
|
||||
id="system_notice.remind_me"
|
||||
/>
|
||||
</button>
|
||||
<button
|
||||
className="btn btn-transparent"
|
||||
id="systemnotice_dontshow"
|
||||
onClick={[Function]}
|
||||
>
|
||||
<MemoizedFormattedMessage
|
||||
defaultMessage="Don't Show Again"
|
||||
id="system_notice.dont_show"
|
||||
/>
|
||||
</button>
|
||||
<button
|
||||
className="btn btn-primary"
|
||||
id="systemnotice_remindme"
|
||||
onClick={[Function]}
|
||||
>
|
||||
<MemoizedFormattedMessage
|
||||
defaultMessage="Remind Me Later"
|
||||
id="system_notice.remind_me"
|
||||
/>
|
||||
</button>
|
||||
<button
|
||||
className="btn"
|
||||
id="systemnotice_dontshow"
|
||||
onClick={[Function]}
|
||||
>
|
||||
<MemoizedFormattedMessage
|
||||
defaultMessage="Don't Show Again"
|
||||
id="system_notice.dont_show"
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
@@ -282,37 +262,81 @@ exports[`components/SystemNotice should match snapshot for with allowForget equa
|
||||
className="system-notice bg--white shadow--2"
|
||||
>
|
||||
<div
|
||||
className="system-notice__header"
|
||||
className="system-notice__logo"
|
||||
>
|
||||
<MattermostLogo />
|
||||
</div>
|
||||
<div
|
||||
className="system-notice__body"
|
||||
>
|
||||
<div
|
||||
className="system-notice__logo"
|
||||
>
|
||||
<MattermostLogo />
|
||||
</div>
|
||||
<div
|
||||
className="system-notice__title"
|
||||
>
|
||||
some title
|
||||
</div>
|
||||
some body
|
||||
<div
|
||||
className="system-notice__footer"
|
||||
>
|
||||
<button
|
||||
className="btn btn-primary"
|
||||
id="systemnotice_remindme"
|
||||
onClick={[Function]}
|
||||
>
|
||||
<MemoizedFormattedMessage
|
||||
defaultMessage="Remind Me Later"
|
||||
id="system_notice.remind_me"
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`components/SystemNotice should match snapshot when a custom icon is passed 1`] = `
|
||||
<div
|
||||
className="system-notice bg--white shadow--2"
|
||||
>
|
||||
<div
|
||||
className="system-notice__logo"
|
||||
>
|
||||
<span>
|
||||
icon
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
className="system-notice__body"
|
||||
>
|
||||
some body
|
||||
</div>
|
||||
<div
|
||||
className="system-notice__footer"
|
||||
>
|
||||
<button
|
||||
className="btn btn-transparent"
|
||||
id="systemnotice_remindme"
|
||||
onClick={[Function]}
|
||||
<div
|
||||
className="system-notice__title"
|
||||
>
|
||||
<MemoizedFormattedMessage
|
||||
defaultMessage="Remind Me Later"
|
||||
id="system_notice.remind_me"
|
||||
/>
|
||||
</button>
|
||||
some title
|
||||
</div>
|
||||
some body
|
||||
<div
|
||||
className="system-notice__footer"
|
||||
>
|
||||
<button
|
||||
className="btn btn-primary"
|
||||
id="systemnotice_remindme"
|
||||
onClick={[Function]}
|
||||
>
|
||||
<MemoizedFormattedMessage
|
||||
defaultMessage="Remind Me Later"
|
||||
id="system_notice.remind_me"
|
||||
/>
|
||||
</button>
|
||||
<button
|
||||
className="btn"
|
||||
id="systemnotice_dontshow"
|
||||
onClick={[Function]}
|
||||
>
|
||||
<MemoizedFormattedMessage
|
||||
defaultMessage="Don't Show Again"
|
||||
id="system_notice.dont_show"
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
@@ -11,6 +11,7 @@ import {getStandardAnalytics} from 'mattermost-redux/actions/admin';
|
||||
import {savePreferences} from 'mattermost-redux/actions/preferences';
|
||||
import {Permissions} from 'mattermost-redux/constants';
|
||||
import {createSelector} from 'mattermost-redux/selectors/create_selector';
|
||||
import {getCurrentChannel} from 'mattermost-redux/selectors/entities/channels';
|
||||
import {getConfig, getLicense} from 'mattermost-redux/selectors/entities/general';
|
||||
import {makeGetCategory} from 'mattermost-redux/selectors/entities/preferences';
|
||||
import {haveISystemPermission} from 'mattermost-redux/selectors/entities/roles';
|
||||
@@ -55,6 +56,7 @@ function makeMapStateToProps() {
|
||||
license,
|
||||
serverVersion,
|
||||
analytics,
|
||||
currentChannel: getCurrentChannel(state),
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
@@ -7,8 +7,8 @@ import {FormattedMessage} from 'react-intl';
|
||||
import ExternalLink from 'components/external_link';
|
||||
import FormattedMarkdownMessage from 'components/formatted_markdown_message';
|
||||
import type {Notice} from 'components/system_notice/types';
|
||||
import InfoIcon from 'components/widgets/icons/info_icon';
|
||||
|
||||
import mattermostIcon from 'images/icon50x50.png';
|
||||
import {DocLinks} from 'utils/constants';
|
||||
import * as ServerVersion from 'utils/server_version';
|
||||
import * as UserAgent from 'utils/user_agent';
|
||||
@@ -29,12 +29,11 @@ const notices: Notice[] = [
|
||||
name: 'apiv3_deprecation',
|
||||
adminOnly: true,
|
||||
title: (
|
||||
<FormattedMarkdownMessage
|
||||
<FormattedMessage
|
||||
id='system_notice.title'
|
||||
defaultMessage='**Notice**\nfrom Mattermost'
|
||||
defaultMessage='Notice from Mattermost'
|
||||
/>
|
||||
),
|
||||
icon: mattermostIcon,
|
||||
body: (
|
||||
<FormattedMessage
|
||||
id='system_notice.body.api3'
|
||||
@@ -63,12 +62,11 @@ const notices: Notice[] = [
|
||||
name: 'advanced_permissions',
|
||||
adminOnly: true,
|
||||
title: (
|
||||
<FormattedMarkdownMessage
|
||||
<FormattedMessage
|
||||
id='system_notice.title'
|
||||
defaultMessage='**Notice**\nfrom Mattermost'
|
||||
defaultMessage='Notice from Mattermost'
|
||||
/>
|
||||
),
|
||||
icon: mattermostIcon,
|
||||
body: (
|
||||
<FormattedMessage
|
||||
id='system_notice.body.permissions'
|
||||
@@ -103,12 +101,11 @@ const notices: Notice[] = [
|
||||
name: 'ee_upgrade_advice',
|
||||
adminOnly: true,
|
||||
title: (
|
||||
<FormattedMarkdownMessage
|
||||
<FormattedMessage
|
||||
id='system_notice.title'
|
||||
defaultMessage='**Notice**\nfrom Mattermost'
|
||||
defaultMessage='Notice from Mattermost'
|
||||
/>
|
||||
),
|
||||
icon: mattermostIcon,
|
||||
body: (
|
||||
<FormattedMessage
|
||||
id='system_notice.body.ee_upgrade_advice'
|
||||
@@ -150,10 +147,9 @@ const notices: Notice[] = [
|
||||
title: (
|
||||
<FormattedMarkdownMessage
|
||||
id='system_notice.title'
|
||||
defaultMessage='**Notice**\nfrom Mattermost'
|
||||
defaultMessage='Notice from Mattermost'
|
||||
/>
|
||||
),
|
||||
icon: mattermostIcon,
|
||||
allowForget: false,
|
||||
body: (
|
||||
<FormattedMessage
|
||||
@@ -185,6 +181,30 @@ const notices: Notice[] = [
|
||||
return true;
|
||||
},
|
||||
},
|
||||
{
|
||||
|
||||
// This notice is marked as viewed by default for new users on the server.
|
||||
// Any change on this notice should be handled also in the server side.
|
||||
name: 'GMasDM',
|
||||
allowForget: true,
|
||||
title: (
|
||||
<FormattedMessage
|
||||
id='system_notice.title.gm_as_dm'
|
||||
defaultMessage='Updates to Group Messages'
|
||||
/>
|
||||
),
|
||||
icon: (<InfoIcon/>),
|
||||
body: (
|
||||
<FormattedMessage
|
||||
id='system_noticy.body.gm_as_dm'
|
||||
defaultMessage='You wil now be notified for all activity in your group messages along with a notification badge for every new message.{br}{br}You can configure this in notification preferences for each group message.'
|
||||
values={{br: (<br/>)}}
|
||||
/>
|
||||
),
|
||||
show: (serverVersion, config, license, analytics, currentChannel) => {
|
||||
return currentChannel?.type === 'G';
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
export default notices;
|
||||
|
||||
@@ -6,15 +6,13 @@ import React from 'react';
|
||||
|
||||
import SystemNotice from 'components/system_notice/system_notice';
|
||||
|
||||
import mattermostIcon from 'images/icon50x50.png';
|
||||
|
||||
describe('components/SystemNotice', () => {
|
||||
const baseProps = {
|
||||
currentUserId: 'someid',
|
||||
preferences: {},
|
||||
dismissedNotices: {},
|
||||
isSystemAdmin: false,
|
||||
notices: [{name: 'notice1', adminOnly: false, title: 'some title', icon: mattermostIcon, body: 'some body', allowForget: true, show: () => true}],
|
||||
notices: [{name: 'notice1', adminOnly: false, title: 'some title', body: 'some body', allowForget: true, show: () => true}],
|
||||
serverVersion: '5.1',
|
||||
license: {IsLicensed: 'true'},
|
||||
config: {},
|
||||
@@ -38,13 +36,17 @@ describe('components/SystemNotice', () => {
|
||||
});
|
||||
|
||||
test('should match snapshot for regular user, admin notice', () => {
|
||||
const props = {...baseProps, notices: [{name: 'notice1', adminOnly: true, title: 'some title', icon: mattermostIcon, body: 'some body', allowForget: true, show: () => true}]};
|
||||
const props = {...baseProps, notices: [{...baseProps.notices[0], adminOnly: true}]};
|
||||
const wrapper = shallow(<SystemNotice {...props}/>);
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('should match snapshot for regular user, admin and regular notice', () => {
|
||||
const props = {...baseProps, notices: [{name: 'notice1', adminOnly: true, title: 'some title', icon: mattermostIcon, body: 'some body', allowForget: true}, {name: 'notice2', adminOnly: false, title: 'some title2', icon: mattermostIcon, body: 'some body2', allowForget: true, show: () => true}]};
|
||||
const props = {...baseProps,
|
||||
notices: [
|
||||
{...baseProps.notices[0], adminOnly: true},
|
||||
{...baseProps.notices[0], name: 'notice2', title: 'some title2', body: 'some body2'},
|
||||
]};
|
||||
const wrapper = shallow(<SystemNotice {...props}/>);
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
@@ -56,7 +58,7 @@ describe('components/SystemNotice', () => {
|
||||
});
|
||||
|
||||
test('should match snapshot for admin, admin notice', () => {
|
||||
const props = {...baseProps, isSystemAdmin: true, notices: [{name: 'notice1', adminOnly: true, title: 'some title', icon: mattermostIcon, body: 'some body', allowForget: true, show: () => true}]};
|
||||
const props = {...baseProps, isSystemAdmin: true, notices: [{...baseProps.notices[0], adminOnly: true}]};
|
||||
const wrapper = shallow(<SystemNotice {...props}/>);
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
@@ -74,19 +76,25 @@ describe('components/SystemNotice', () => {
|
||||
});
|
||||
|
||||
test('should match snapshot for show function returning false', () => {
|
||||
const props = {...baseProps, notices: [{name: 'notice1', adminOnly: false, title: 'some title', icon: mattermostIcon, body: 'some body', allowForget: true, show: () => false}]};
|
||||
const props = {...baseProps, notices: [{...baseProps.notices[0], show: () => false}]};
|
||||
const wrapper = shallow(<SystemNotice {...props}/>);
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('should match snapshot for show function returning true', () => {
|
||||
const props = {...baseProps, notices: [{name: 'notice1', adminOnly: false, title: 'some title', icon: mattermostIcon, body: 'some body', allowForget: true, show: () => true}]};
|
||||
const props = {...baseProps, notices: [{...baseProps.notices[0], show: () => true}]};
|
||||
const wrapper = shallow(<SystemNotice {...props}/>);
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('should match snapshot for with allowForget equal false', () => {
|
||||
const props = {...baseProps, notices: [{name: 'notice1', adminOnly: false, title: 'some title', icon: mattermostIcon, body: 'some body', allowForget: false, show: () => true}]};
|
||||
const props = {...baseProps, notices: [{...baseProps.notices[0], allowForget: false}]};
|
||||
const wrapper = shallow(<SystemNotice {...props}/>);
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('should match snapshot when a custom icon is passed', () => {
|
||||
const props = {...baseProps, notices: [{...baseProps.notices[0], icon: <span>{'icon'}</span>}]};
|
||||
const wrapper = shallow(<SystemNotice {...props}/>);
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
|
||||
@@ -5,6 +5,7 @@ import React from 'react';
|
||||
import {FormattedMessage} from 'react-intl';
|
||||
|
||||
import type {AnalyticsRow} from '@mattermost/types/admin';
|
||||
import type {Channel} from '@mattermost/types/channels';
|
||||
import type {ClientConfig, ClientLicense} from '@mattermost/types/config';
|
||||
import type {PreferenceType} from '@mattermost/types/preferences';
|
||||
|
||||
@@ -25,6 +26,7 @@ type Props = {
|
||||
config: Partial<ClientConfig>;
|
||||
license: ClientLicense;
|
||||
analytics?: Record<string, number | AnalyticsRow[]>;
|
||||
currentChannel?: Channel;
|
||||
actions: {
|
||||
savePreferences(userId: string, preferences: PreferenceType[]): void;
|
||||
dismissNotice(type: string): void;
|
||||
@@ -60,7 +62,13 @@ export default class SystemNotice extends React.PureComponent<Props> {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!notice.show?.(this.props.serverVersion, this.props.config, this.props.license, this.props.analytics)) {
|
||||
if (!notice.show?.(
|
||||
this.props.serverVersion,
|
||||
this.props.config,
|
||||
this.props.license,
|
||||
this.props.analytics,
|
||||
this.props.currentChannel,
|
||||
)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -118,44 +126,44 @@ export default class SystemNotice extends React.PureComponent<Props> {
|
||||
);
|
||||
}
|
||||
|
||||
const icon = notice.icon || <MattermostLogo/>;
|
||||
|
||||
return (
|
||||
<div
|
||||
className='system-notice bg--white shadow--2'
|
||||
>
|
||||
<div className='system-notice__header'>
|
||||
<div className='system-notice__logo'>
|
||||
<MattermostLogo/>
|
||||
</div>
|
||||
<div className='system-notice__logo'>
|
||||
{icon}
|
||||
</div>
|
||||
<div className='system-notice__body'>
|
||||
<div className='system-notice__title'>
|
||||
{notice.title}
|
||||
</div>
|
||||
</div>
|
||||
<div className='system-notice__body'>
|
||||
{notice.body}
|
||||
</div>
|
||||
{visibleMessage}
|
||||
<div className='system-notice__footer'>
|
||||
<button
|
||||
id='systemnotice_remindme'
|
||||
className='btn btn-transparent'
|
||||
onClick={this.hideAndRemind}
|
||||
>
|
||||
<FormattedMessage
|
||||
id='system_notice.remind_me'
|
||||
defaultMessage='Remind Me Later'
|
||||
/>
|
||||
</button>
|
||||
{notice.allowForget &&
|
||||
{visibleMessage}
|
||||
<div className='system-notice__footer'>
|
||||
<button
|
||||
id='systemnotice_dontshow'
|
||||
className='btn btn-transparent'
|
||||
onClick={this.hideAndForget}
|
||||
id='systemnotice_remindme'
|
||||
className='btn btn-primary'
|
||||
onClick={this.hideAndRemind}
|
||||
>
|
||||
<FormattedMessage
|
||||
id='system_notice.dont_show'
|
||||
defaultMessage="Don't Show Again"
|
||||
id='system_notice.remind_me'
|
||||
defaultMessage='Remind Me Later'
|
||||
/>
|
||||
</button>}
|
||||
</button>
|
||||
{notice.allowForget &&
|
||||
<button
|
||||
id='systemnotice_dontshow'
|
||||
className='btn'
|
||||
onClick={this.hideAndForget}
|
||||
>
|
||||
<FormattedMessage
|
||||
id='system_notice.dont_show'
|
||||
defaultMessage="Don't Show Again"
|
||||
/>
|
||||
</button>}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -4,17 +4,20 @@
|
||||
import type React from 'react';
|
||||
|
||||
import type {AnalyticsRow} from '@mattermost/types/admin';
|
||||
import type {Channel} from '@mattermost/types/channels';
|
||||
|
||||
export type Notice = {
|
||||
name: string;
|
||||
adminOnly?: boolean;
|
||||
title: React.ReactNode;
|
||||
icon: string;
|
||||
icon?: React.ReactNode;
|
||||
body: React.ReactNode;
|
||||
allowForget: boolean;
|
||||
show?(
|
||||
serverVersion: string,
|
||||
config: any,
|
||||
license: any,
|
||||
analytics?: Record<string, number | AnalyticsRow[]>): boolean;
|
||||
analytics?: Record<string, number | AnalyticsRow[]>,
|
||||
currentChannel?: Channel,
|
||||
): boolean;
|
||||
}
|
||||
|
||||
@@ -50,7 +50,7 @@ exports[`components/user_settings/notifications/DesktopNotificationSettings shou
|
||||
type="radio"
|
||||
/>
|
||||
<Memo(MemoizedFormattedMessage)
|
||||
defaultMessage="Only for mentions and direct messages"
|
||||
defaultMessage="Only for mentions, direct messages, and group messages"
|
||||
id="user.settings.notifications.onlyMentions"
|
||||
/>
|
||||
</label>
|
||||
@@ -205,7 +205,7 @@ exports[`components/user_settings/notifications/DesktopNotificationSettings shou
|
||||
type="radio"
|
||||
/>
|
||||
<Memo(MemoizedFormattedMessage)
|
||||
defaultMessage="Only for mentions and direct messages"
|
||||
defaultMessage="Only for mentions, direct messages, and group messages"
|
||||
id="user.settings.notifications.onlyMentions"
|
||||
/>
|
||||
</label>
|
||||
@@ -330,7 +330,7 @@ exports[`components/user_settings/notifications/DesktopNotificationSettings shou
|
||||
type="radio"
|
||||
/>
|
||||
<Memo(MemoizedFormattedMessage)
|
||||
defaultMessage="Only for mentions and direct messages"
|
||||
defaultMessage="Only for mentions, direct messages, and group messages"
|
||||
id="user.settings.notifications.onlyMentions"
|
||||
/>
|
||||
</label>
|
||||
@@ -485,7 +485,7 @@ exports[`components/user_settings/notifications/DesktopNotificationSettings shou
|
||||
type="radio"
|
||||
/>
|
||||
<Memo(MemoizedFormattedMessage)
|
||||
defaultMessage="Only for mentions and direct messages"
|
||||
defaultMessage="Only for mentions, direct messages, and group messages"
|
||||
id="user.settings.notifications.onlyMentions"
|
||||
/>
|
||||
</label>
|
||||
@@ -693,7 +693,7 @@ exports[`components/user_settings/notifications/DesktopNotificationSettings shou
|
||||
type="radio"
|
||||
/>
|
||||
<Memo(MemoizedFormattedMessage)
|
||||
defaultMessage="Only for mentions and direct messages"
|
||||
defaultMessage="Only for mentions, direct messages, and group messages"
|
||||
id="user.settings.notifications.onlyMentions"
|
||||
/>
|
||||
</label>
|
||||
@@ -942,7 +942,7 @@ exports[`components/user_settings/notifications/DesktopNotificationSettings shou
|
||||
type="radio"
|
||||
/>
|
||||
<Memo(MemoizedFormattedMessage)
|
||||
defaultMessage="Only for mentions and direct messages"
|
||||
defaultMessage="Only for mentions, direct messages, and group messages"
|
||||
id="user.settings.notifications.onlyMentions"
|
||||
/>
|
||||
</label>
|
||||
|
||||
@@ -397,7 +397,7 @@ export default class DesktopNotificationSettings extends React.PureComponent<Pro
|
||||
/>
|
||||
<FormattedMessage
|
||||
id='user.settings.notifications.onlyMentions'
|
||||
defaultMessage='Only for mentions and direct messages'
|
||||
defaultMessage='Only for mentions, direct messages, and group messages'
|
||||
/>
|
||||
</label>
|
||||
<br/>
|
||||
|
||||
@@ -2994,7 +2994,7 @@
|
||||
"channel_modal.type.private.title": "Private Channel",
|
||||
"channel_modal.type.public.description": "Anyone can join",
|
||||
"channel_modal.type.public.title": "Public Channel",
|
||||
"channel_notifications.allActivity": "For all activity",
|
||||
"channel_notifications.allActivity": "For all activity {isDefault}",
|
||||
"channel_notifications.channelAutoFollowThreads": "Auto-follow all new threads in this channel",
|
||||
"channel_notifications.channelAutoFollowThreads.help": "When enabled, you will auto-follow all new threads created in this channel unless you unfollow a thread explicitly.",
|
||||
"channel_notifications.channelAutoFollowThreads.off.title": "Off",
|
||||
@@ -3760,7 +3760,7 @@
|
||||
"intro_messages.creatorPrivate": "This is the start of the {name} private channel, created by {creator} on {date}.",
|
||||
"intro_messages.default": "**Welcome to {display_name}!**\n \nPost messages here that you want everyone to see. Everyone automatically becomes a permanent member of this channel when they join the team.",
|
||||
"intro_messages.DM": "This is the start of your direct message history with {teammate}.\nDirect messages and files shared here are not shown to people outside this area.",
|
||||
"intro_messages.GM": "This is the start of your group message history with {names}.\nMessages and files shared here are not shown to people outside this area.",
|
||||
"intro_messages.GM": "This is the start of your group message history with {names}.{br}You'll be notified <b>for all activity</b> in this group message.",
|
||||
"intro_messages.group_message": "This is the start of your group message history with these teammates. Messages and files shared here are not shown to people outside this area.",
|
||||
"intro_messages.inviteGropusToChannel.button": "Add groups to this private channel",
|
||||
"intro_messages.inviteMembersToChannel.button": "Add members to this channel",
|
||||
@@ -3770,6 +3770,7 @@
|
||||
"intro_messages.inviteOthersToWorkspace.title": "Let’s add some people to the workspace!",
|
||||
"intro_messages.noCreator": "This is the start of the {name} channel, created on {date}.",
|
||||
"intro_messages.noCreatorPrivate": "This is the start of the {name} private channel, created on {date}.",
|
||||
"intro_messages.notificationPreferences": "Notification Preferences",
|
||||
"intro_messages.offTopic": "This is the start of {display_name}, a channel for non-work-related conversations.",
|
||||
"intro_messages.onlyInvited": " Only invited members can see this private channel.",
|
||||
"intro_messages.purpose": " This channel's purpose is: {purpose}",
|
||||
@@ -4999,7 +5000,9 @@
|
||||
"system_notice.body.permissions": "Some policy and permission System Console settings have moved with the release of <link>advanced permissions</link> into Mattermost Free and Professional.",
|
||||
"system_notice.dont_show": "Don't Show Again",
|
||||
"system_notice.remind_me": "Remind me Later",
|
||||
"system_notice.title": "**Notice**\nfrom Mattermost",
|
||||
"system_notice.title": "Notice from Mattermost",
|
||||
"system_notice.title.gm_as_dm": "Updates to Group Messages",
|
||||
"system_noticy.body.gm_as_dm": "You wil now be notified for all activity in your group messages along with a notification badge for every new message.{br}{br}You can configure this in notification preferences for each group message.",
|
||||
"system_users_list.count": "{count, number} {count, plural, one {user} other {users}}",
|
||||
"system_users_list.countPage": "{startCount, number} - {endCount, number} {count, plural, one {user} other {users}} of {total, number} total",
|
||||
"system_users_list.countSearch": "{count, number} {count, plural, one {user} other {users}} of {total, number} total",
|
||||
@@ -5448,7 +5451,7 @@
|
||||
"user.settings.notifications.never": "Never",
|
||||
"user.settings.notifications.off": "Off",
|
||||
"user.settings.notifications.on": "On",
|
||||
"user.settings.notifications.onlyMentions": "Only for mentions and direct messages",
|
||||
"user.settings.notifications.onlyMentions": "Only for mentions, direct messages, and group messages",
|
||||
"user.settings.notifications.push": "Mobile Push Notifications",
|
||||
"user.settings.notifications.push_notification.status": "Trigger push notifications when",
|
||||
"user.settings.notifications.push_threads": "When enabled, any reply to a thread you're following will send a mobile push notification.",
|
||||
|
||||
@@ -5,7 +5,8 @@
|
||||
z-index: 9999;
|
||||
right: 12px;
|
||||
bottom: 12px;
|
||||
width: 280px;
|
||||
display: flex;
|
||||
width: 386px;
|
||||
padding: 18px 20px 0;
|
||||
border: 1px solid alpha-color($black, 0.15);
|
||||
background-color: var(--center-channel-bg);
|
||||
@@ -13,33 +14,27 @@
|
||||
box-shadow: 0 20px 30px alpha-color($black, 0.07), 0 14px 20px alpha-color($black, 0.07);
|
||||
}
|
||||
|
||||
.system-notice__header {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.system-notice__logo {
|
||||
height: 36px;
|
||||
height: 20px;
|
||||
|
||||
svg {
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
fill: rgb(22, 109, 224);
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
fill: var(--button-bg);
|
||||
}
|
||||
}
|
||||
|
||||
.system-notice__title {
|
||||
overflow: hidden;
|
||||
flex: 10 1 auto;
|
||||
padding: 3px 0 0 8px;
|
||||
padding-bottom: 10px;
|
||||
font-weight: bold;
|
||||
line-height: 16px;
|
||||
opacity: 0.7;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.system-notice__info {
|
||||
margin-bottom: 12px;
|
||||
margin-top: 12px;
|
||||
font-size: 12px;
|
||||
opacity: 0.5;
|
||||
|
||||
@@ -49,33 +44,22 @@
|
||||
}
|
||||
|
||||
.system-notice__body {
|
||||
padding: 18px 0 16px;
|
||||
padding: 0 0 16px 16px;
|
||||
line-height: 16px;
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
.system-notice__footer {
|
||||
display: flex;
|
||||
border-top: 1px solid alpha-color($black, 0.2);
|
||||
margin: 0 -20px;
|
||||
margin-top: 16px;
|
||||
|
||||
.btn {
|
||||
overflow: hidden;
|
||||
flex: 1;
|
||||
font-weight: bold;
|
||||
text-overflow: ellipsis;
|
||||
|
||||
&:hover {
|
||||
background: rgb(22, 109, 224);
|
||||
color: $white;
|
||||
}
|
||||
|
||||
&:first-child {
|
||||
border-radius: 0 0 0 4px;
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
border-left: 1px solid alpha-color($black, 0.2);
|
||||
border-radius: 0 0 4px 0;
|
||||
margin-left: 5px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -524,6 +524,7 @@
|
||||
margin-bottom: 10px;
|
||||
|
||||
.fa,
|
||||
svg,
|
||||
i {
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {formatWithRenderer} from './markdown';
|
||||
import MentionableRenderer from './markdown/mentionable_renderer';
|
||||
import PlainRenderer from './markdown/plain_renderer';
|
||||
|
||||
export const emoticonPatterns: { [key: string]: RegExp } = {
|
||||
slightly_smiling_face: /(^|\B)(:-?\))($|\B)/g, // :)
|
||||
@@ -28,7 +28,7 @@ export const emoticonPatterns: { [key: string]: RegExp } = {
|
||||
export const EMOJI_PATTERN = /(:([a-zA-Z0-9_+-]+):)/g;
|
||||
|
||||
export function matchEmoticons(text: string): RegExpMatchArray | null {
|
||||
const markdownCleanedText = formatWithRenderer(text, new MentionableRenderer());
|
||||
const markdownCleanedText = formatWithRenderer(text, new PlainRenderer());
|
||||
let emojis = markdownCleanedText.match(EMOJI_PATTERN);
|
||||
|
||||
for (const name of Object.keys(emoticonPatterns)) {
|
||||
|
||||
@@ -1,81 +1,14 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import marked from 'marked';
|
||||
import {EMOJI_PATTERN} from 'utils/emoticons';
|
||||
|
||||
import PlainRenderer from './plain_renderer';
|
||||
|
||||
/** A Markdown renderer that converts a post into plain text that we can search for mentions */
|
||||
export default class MentionableRenderer extends marked.Renderer {
|
||||
public code() {
|
||||
// Code blocks can't contain mentions
|
||||
return '\n';
|
||||
}
|
||||
|
||||
public blockquote(text: string) {
|
||||
return text + '\n';
|
||||
}
|
||||
|
||||
public heading(text: string) {
|
||||
return text + '\n';
|
||||
}
|
||||
|
||||
public hr() {
|
||||
return '\n';
|
||||
}
|
||||
|
||||
public list(body: string) {
|
||||
return body + '\n';
|
||||
}
|
||||
|
||||
public listitem(text: string) {
|
||||
return text + '\n';
|
||||
}
|
||||
|
||||
public paragraph(text: string) {
|
||||
return text + '\n';
|
||||
}
|
||||
|
||||
public table(header: string, body: string) {
|
||||
return header + '\n' + body;
|
||||
}
|
||||
|
||||
public tablerow(content: string) {
|
||||
return content;
|
||||
}
|
||||
|
||||
public tablecell(content: string) {
|
||||
return content + '\n';
|
||||
}
|
||||
|
||||
public strong(text: string) {
|
||||
return ' ' + text + ' ';
|
||||
}
|
||||
|
||||
public em(text: string) {
|
||||
return ' ' + text + ' ';
|
||||
}
|
||||
|
||||
public codespan() {
|
||||
// Code spans can't contain mentions
|
||||
return ' ';
|
||||
}
|
||||
|
||||
public br() {
|
||||
return '\n';
|
||||
}
|
||||
|
||||
public del(text: string) {
|
||||
return ' ' + text + ' ';
|
||||
}
|
||||
|
||||
public link(href: string, title: string, text: string) {
|
||||
return ' ' + text + ' ';
|
||||
}
|
||||
|
||||
public image(href: string, title: string, text: string) {
|
||||
return ' ' + text + ' ';
|
||||
}
|
||||
|
||||
export default class MentionableRenderer extends PlainRenderer {
|
||||
public text(text: string) {
|
||||
return text;
|
||||
// Remove all emojis
|
||||
return text.replace(EMOJI_PATTERN, '');
|
||||
}
|
||||
}
|
||||
|
||||
81
webapp/channels/src/utils/markdown/plain_renderer.ts
Обычный файл
81
webapp/channels/src/utils/markdown/plain_renderer.ts
Обычный файл
@@ -0,0 +1,81 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import marked from 'marked';
|
||||
|
||||
/** A Markdown renderer that converts a post into plain text */
|
||||
export default class PlainRenderer extends marked.Renderer {
|
||||
public code() {
|
||||
// Code blocks can't contain mentions
|
||||
return '\n';
|
||||
}
|
||||
|
||||
public blockquote(text: string) {
|
||||
return text + '\n';
|
||||
}
|
||||
|
||||
public heading(text: string) {
|
||||
return text + '\n';
|
||||
}
|
||||
|
||||
public hr() {
|
||||
return '\n';
|
||||
}
|
||||
|
||||
public list(body: string) {
|
||||
return body + '\n';
|
||||
}
|
||||
|
||||
public listitem(text: string) {
|
||||
return text + '\n';
|
||||
}
|
||||
|
||||
public paragraph(text: string) {
|
||||
return text + '\n';
|
||||
}
|
||||
|
||||
public table(header: string, body: string) {
|
||||
return header + '\n' + body;
|
||||
}
|
||||
|
||||
public tablerow(content: string) {
|
||||
return content;
|
||||
}
|
||||
|
||||
public tablecell(content: string) {
|
||||
return content + '\n';
|
||||
}
|
||||
|
||||
public strong(text: string) {
|
||||
return ' ' + text + ' ';
|
||||
}
|
||||
|
||||
public em(text: string) {
|
||||
return ' ' + text + ' ';
|
||||
}
|
||||
|
||||
public codespan() {
|
||||
// Code spans can't contain mentions
|
||||
return ' ';
|
||||
}
|
||||
|
||||
public br() {
|
||||
return '\n';
|
||||
}
|
||||
|
||||
public del(text: string) {
|
||||
return ' ' + text + ' ';
|
||||
}
|
||||
|
||||
public link(href: string, title: string, text: string) {
|
||||
return ' ' + text + ' ';
|
||||
}
|
||||
|
||||
public image(href: string, title: string, text: string) {
|
||||
return ' ' + text + ' ';
|
||||
}
|
||||
|
||||
public text(text: string) {
|
||||
return text;
|
||||
}
|
||||
}
|
||||
@@ -227,7 +227,7 @@ const DEFAULT_OPTIONS: TextFormattingOptions = {
|
||||
* Additional CJK and Hangul compatibility characters: \u2de0-\u2dff
|
||||
**/
|
||||
// eslint-disable-next-line no-misleading-character-class
|
||||
const cjkrPattern = /[\u3000-\u303f\u3040-\u309f\u30a0-\u30ff\uff00-\uff9f\u4e00-\u9faf\u3400-\u4dbf\uac00-\ud7a3\u1100-\u11ff\u3130-\u318f\u0400-\u04ff\u0500-\u052f\u2de0-\u2dff]/;
|
||||
export const cjkrPattern = /[\u3000-\u303f\u3040-\u309f\u30a0-\u30ff\uff00-\uff9f\u4e00-\u9faf\u3400-\u4dbf\uac00-\ud7a3\u1100-\u11ff\u3130-\u318f\u0400-\u04ff\u0500-\u052f\u2de0-\u2dff]/;
|
||||
|
||||
export function formatText(
|
||||
text: string,
|
||||
|
||||
Ссылка в новой задаче
Block a user