mirror of
https://github.com/mattermost/mattermost.git
synced 2025-02-25 18:55:24 -06:00
[MM-53102] Add support for multi-word highlights without notifications in web (#24050)
- Adds a new section under settings/notifications for adding custom multi-word keywords that get highlighted without notification - Adds a new classname for highlighting words although the styling is the same as mentions highlights - Added a few components to the ReduxFromProps pattern - Adds supported type for the hook of PluginComponent type - Add upsell for highlight without notification - Moved 'setting_item.tsx' to the components folder - Improved prop names and function structure for setting_item, setting_item_max and setting_item_min - Moved 'toggle_modal_button.tsx' to the components folder - Removed t and utility messages from a few components - Fixed bug where the tooltip was not getting rendered on restrictedButtons - Improved the mobile view of the settings modal - Adds E2E for the feature
This commit is contained in:
parent
48bf4e9bd8
commit
9ac389f506
@ -34,8 +34,8 @@ describe('Verify Accessibility Support in different sections in Settings and Pro
|
||||
{key: 'desktop', label: 'Desktop Notifications', type: 'radio'},
|
||||
{key: 'email', label: 'Email Notifications', type: 'radio'},
|
||||
{key: 'push', label: 'Mobile Push Notifications', type: 'radio'},
|
||||
{key: 'keysWithNotification', label: 'Keywords that trigger Notifications', type: 'checkbox'},
|
||||
{key: 'comments', label: 'Reply notifications', type: 'radio'},
|
||||
{key: 'keysWithNotification', label: 'Keywords That Trigger Notifications', type: 'checkbox'},
|
||||
{key: 'comments', label: 'Reply Notifications', type: 'radio'},
|
||||
],
|
||||
display: [
|
||||
{key: 'theme', label: 'Theme', type: 'radio'},
|
||||
|
@ -188,6 +188,8 @@ function mapFeatureIdToId(id: string) {
|
||||
return 'All Professional features';
|
||||
case 'mattermost.feature.all_enterprise':
|
||||
return 'All Enterprise features';
|
||||
case 'mattermost.feature.highlight_without_notification':
|
||||
return 'Keywords Highlight Without Notification';
|
||||
default:
|
||||
return '';
|
||||
}
|
||||
|
@ -48,7 +48,7 @@ describe('Auto Response In DMs', () => {
|
||||
// # Open 'Settings' modal and view 'Notifications'
|
||||
cy.uiOpenSettingsModal().within(() => {
|
||||
// # Click on 'Edit' for 'Automatic Direct Message Replies
|
||||
cy.get('#auto-responderEdit').should('be.visible').click();
|
||||
cy.get('#auto-responderEdit').should('exist').scrollIntoView().and('be.visible').click();
|
||||
|
||||
// # Click on 'Enabled' checkbox
|
||||
cy.get('#autoResponderActive').should('be.visible').click();
|
||||
|
@ -23,8 +23,8 @@ describe('Notifications', () => {
|
||||
|
||||
// # Open 'Settings' modal
|
||||
cy.uiOpenSettingsModal().within(() => {
|
||||
// # Open 'Keywords that trigger Notifications' setting and uncheck all the checkboxes
|
||||
cy.findByRole('heading', {name: 'Keywords that trigger Notifications'}).should('be.visible').click();
|
||||
// # Open 'Keywords That Trigger Notifications' setting and uncheck all the checkboxes
|
||||
cy.findByRole('heading', {name: 'Keywords That Trigger Notifications'}).should('be.visible').click();
|
||||
cy.findByRole('checkbox', {name: `Your case-sensitive first name "${otherUser.first_name}"`}).should('not.be.checked');
|
||||
cy.findByRole('checkbox', {name: `Your non case-sensitive username "${otherUser.username}"`}).should('not.be.checked');
|
||||
cy.findByRole('checkbox', {name: 'Channel-wide mentions "@channel", "@all", "@here"'}).click().should('not.be.checked');
|
||||
|
@ -252,7 +252,7 @@ function setNotificationSettings(desiredSettings = {first: true, username: true,
|
||||
cy.findAllByText('Notifications').should('be.visible');
|
||||
|
||||
// Open up 'Words that trigger mentions' sub-section
|
||||
cy.findByText('Keywords that trigger Notifications').
|
||||
cy.findByText('Keywords That Trigger Notifications').
|
||||
scrollIntoView().
|
||||
click();
|
||||
|
||||
|
@ -29,8 +29,8 @@ describe('Notifications', () => {
|
||||
|
||||
// # Open 'Settings' modal
|
||||
cy.uiOpenSettingsModal().within(() => {
|
||||
// # Open 'Keywords that trigger Notifications' setting
|
||||
cy.findByRole('heading', {name: 'Keywords that trigger Notifications'}).should('be.visible').click();
|
||||
// # Open 'Keywords That Trigger Notifications' setting
|
||||
cy.findByRole('heading', {name: 'Keywords That Trigger Notifications'}).should('be.visible').click();
|
||||
|
||||
// * As otherUser, ensure that 'Your non-case sensitive username' is not checked
|
||||
cy.findByRole('checkbox', {name: `Your non case-sensitive username "${otherUser.username}"`}).should('not.be.checked');
|
||||
|
@ -9,7 +9,7 @@ export default class DeletePostModal {
|
||||
|
||||
constructor(container: Locator) {
|
||||
this.container = container;
|
||||
this.confirmButton = this.container.locator('#deletePostModalButton');
|
||||
this.confirmButton = container.locator('#deletePostModalButton');
|
||||
}
|
||||
|
||||
async toBeVisible() {
|
||||
|
@ -0,0 +1,54 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {expect, Locator} from '@playwright/test';
|
||||
|
||||
type NotificationSettingsSection = 'keysWithHighlight' | 'keysWithNotification';
|
||||
|
||||
export default class NotificationsSettings {
|
||||
readonly container: Locator;
|
||||
|
||||
constructor(container: Locator) {
|
||||
this.container = container;
|
||||
}
|
||||
|
||||
async toBeVisible() {
|
||||
await expect(this.container).toBeVisible();
|
||||
}
|
||||
|
||||
async expandSection(section: NotificationSettingsSection) {
|
||||
if (section === 'keysWithHighlight') {
|
||||
await this.container.getByText('Keywords That Get Highlighted (without notifications)').click();
|
||||
await this.verifySectionIsExpanded('keysWithHighlight');
|
||||
}
|
||||
}
|
||||
|
||||
async verifySectionIsExpanded(section: NotificationSettingsSection) {
|
||||
await expect(this.container.locator(`#${section}Edit`)).not.toBeVisible();
|
||||
|
||||
if (section === 'keysWithHighlight') {
|
||||
await expect(
|
||||
this.container.getByText(
|
||||
'Enter non case-sensitive keywords, press Tab or use commas to separate them:',
|
||||
),
|
||||
).toBeVisible();
|
||||
await expect(
|
||||
this.container.getByText(
|
||||
'These keywords will be shown to you with a highlight when anyone sends a message that includes them.',
|
||||
),
|
||||
).toBeVisible();
|
||||
}
|
||||
}
|
||||
|
||||
async getKeywordsInput() {
|
||||
await expect(this.container.locator('input')).toBeVisible();
|
||||
return this.container.locator('input');
|
||||
}
|
||||
|
||||
async save() {
|
||||
await expect(this.container.getByText('Save')).toBeVisible();
|
||||
await this.container.getByText('Save').click();
|
||||
}
|
||||
}
|
||||
|
||||
export {NotificationsSettings};
|
@ -0,0 +1,39 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {expect, Locator} from '@playwright/test';
|
||||
|
||||
import {NotificationsSettings} from './notification_settings';
|
||||
|
||||
export default class SettingsModal {
|
||||
readonly container: Locator;
|
||||
|
||||
readonly notificationsSettingsTab;
|
||||
readonly notificationsSettings;
|
||||
|
||||
constructor(container: Locator) {
|
||||
this.container = container;
|
||||
|
||||
this.notificationsSettingsTab = container.locator('#notificationsButton');
|
||||
this.notificationsSettings = new NotificationsSettings(container.locator('#notificationSettings'));
|
||||
}
|
||||
|
||||
async toBeVisible() {
|
||||
await expect(this.container).toBeVisible();
|
||||
}
|
||||
|
||||
async openNotificationsTab() {
|
||||
await expect(this.notificationsSettingsTab).toBeVisible();
|
||||
await this.notificationsSettingsTab.click();
|
||||
|
||||
await this.notificationsSettings.toBeVisible();
|
||||
}
|
||||
|
||||
async closeModal() {
|
||||
await this.container.getByLabel('Close').click();
|
||||
|
||||
await expect(this.container).not.toBeVisible();
|
||||
}
|
||||
}
|
||||
|
||||
export {SettingsModal};
|
@ -7,11 +7,19 @@ export default class GlobalHeader {
|
||||
readonly container: Locator;
|
||||
|
||||
readonly productSwitchMenu;
|
||||
readonly recentMentionsButton;
|
||||
readonly settingsButton;
|
||||
|
||||
constructor(container: Locator) {
|
||||
this.container = container;
|
||||
|
||||
this.productSwitchMenu = container.getByRole('button', {name: 'Product switch menu'});
|
||||
this.recentMentionsButton = container.getByRole('button', {name: 'Recent mentions'});
|
||||
this.settingsButton = container.getByRole('button', {name: 'Settings'});
|
||||
}
|
||||
|
||||
async toBeVisible(name: string) {
|
||||
await expect(this.container.getByRole('heading', {name})).toBeVisible();
|
||||
}
|
||||
|
||||
async switchProduct(name: string) {
|
||||
@ -19,8 +27,14 @@ export default class GlobalHeader {
|
||||
await this.container.getByRole('link', {name}).click();
|
||||
}
|
||||
|
||||
async toBeVisible(name: string) {
|
||||
await expect(this.container.getByRole('heading', {name})).toBeVisible();
|
||||
async openSettings() {
|
||||
await expect(this.settingsButton).toBeVisible();
|
||||
await this.settingsButton.click();
|
||||
}
|
||||
|
||||
async openRecentMentions() {
|
||||
await expect(this.recentMentionsButton).toBeVisible();
|
||||
await this.recentMentionsButton.click();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -10,6 +10,7 @@ import {ChannelsSidebarLeft} from './channels/sidebar_left';
|
||||
import {ChannelsSidebarRight} from './channels/sidebar_right';
|
||||
import {DeletePostModal} from './channels/delete_post_modal';
|
||||
import {FindChannelsModal} from './channels/find_channels_modal';
|
||||
import {SettingsModal} from './channels/settings/settings_modal';
|
||||
import {Footer} from './footer';
|
||||
import {GlobalHeader} from './global_header';
|
||||
import {MainHeader} from './main_header';
|
||||
@ -30,6 +31,7 @@ const components = {
|
||||
ChannelsPost,
|
||||
FindChannelsModal,
|
||||
DeletePostModal,
|
||||
SettingsModal,
|
||||
PostDotMenu,
|
||||
PostMenu,
|
||||
ThreadFooter,
|
||||
|
@ -18,6 +18,7 @@ export default class ChannelsPage {
|
||||
|
||||
readonly findChannelsModal;
|
||||
readonly deletePostModal;
|
||||
readonly settingsModal;
|
||||
|
||||
readonly postDotMenu;
|
||||
readonly postReminderMenu;
|
||||
@ -37,6 +38,7 @@ export default class ChannelsPage {
|
||||
// Modals
|
||||
this.findChannelsModal = new components.FindChannelsModal(page.getByRole('dialog', {name: 'Find Channels'}));
|
||||
this.deletePostModal = new components.DeletePostModal(page.locator('#deletePostModal'));
|
||||
this.settingsModal = new components.SettingsModal(page.getByRole('dialog', {name: 'Settings'}));
|
||||
|
||||
// Menus
|
||||
this.postDotMenu = new components.PostDotMenu(page.getByRole('menu', {name: 'Post extra options'}));
|
||||
|
@ -0,0 +1,271 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {expect} from '@playwright/test';
|
||||
|
||||
import {test} from '@e2e-support/test_fixture';
|
||||
import {getRandomId} from '@e2e-support/util';
|
||||
import {createRandomPost} from '@e2e-support/server/post';
|
||||
|
||||
const keywords = [`AB${getRandomId()}`, `CD${getRandomId()}`, `EF${getRandomId()}`, `Highlight me ${getRandomId()}`];
|
||||
|
||||
const highlightWithoutNotificationClass = 'non-notification-highlight';
|
||||
|
||||
test('MM-T5465-1 Should add the keyword when enter, comma or tab is pressed on the textbox', async ({pw, pages}) => {
|
||||
const {user} = await pw.initSetup();
|
||||
|
||||
// # Log in as a user in new browser context
|
||||
const {page} = await pw.testBrowser.login(user);
|
||||
|
||||
// # Visit default channel page
|
||||
const channelPage = new pages.ChannelsPage(page);
|
||||
await channelPage.goto();
|
||||
await channelPage.toBeVisible();
|
||||
|
||||
await channelPage.centerView.postCreate.postMessage('Hello World');
|
||||
|
||||
// # Open settings modal
|
||||
await channelPage.globalHeader.openSettings();
|
||||
await channelPage.settingsModal.toBeVisible();
|
||||
|
||||
// # Open notifications tab
|
||||
await channelPage.settingsModal.openNotificationsTab();
|
||||
|
||||
// # Open keywords that get highlighted section
|
||||
await channelPage.settingsModal.notificationsSettings.expandSection('keysWithHighlight');
|
||||
|
||||
const keywordsInput = await channelPage.settingsModal.notificationsSettings.getKeywordsInput();
|
||||
|
||||
// # Enter keyword 1
|
||||
await keywordsInput.type(keywords[0]);
|
||||
|
||||
// # Press Comma on the textbox
|
||||
await keywordsInput.press(',');
|
||||
|
||||
// # Enter keyword 2
|
||||
await keywordsInput.type(keywords[1]);
|
||||
|
||||
// # Press Tab on the textbox
|
||||
await keywordsInput.press('Tab');
|
||||
|
||||
// # Enter keyword 3
|
||||
await keywordsInput.type(keywords[2]);
|
||||
|
||||
// # Press Enter on the textbox
|
||||
await keywordsInput.press('Enter');
|
||||
|
||||
// * Verify that the keywords have been added to the collapsed description
|
||||
await expect(channelPage.settingsModal.notificationsSettings.container.getByText(keywords[0])).toBeVisible();
|
||||
await expect(channelPage.settingsModal.notificationsSettings.container.getByText(keywords[1])).toBeVisible();
|
||||
await expect(channelPage.settingsModal.notificationsSettings.container.getByText(keywords[2])).toBeVisible();
|
||||
});
|
||||
|
||||
test('MM-T5465-2 Should highlight the keywords when a message is sent with the keyword in center', async ({
|
||||
pw,
|
||||
pages,
|
||||
}) => {
|
||||
const {user} = await pw.initSetup();
|
||||
|
||||
// # Log in as a user in new browser context
|
||||
const {page} = await pw.testBrowser.login(user);
|
||||
|
||||
// # Visit default channel page
|
||||
const channelPage = new pages.ChannelsPage(page);
|
||||
await channelPage.goto();
|
||||
await channelPage.toBeVisible();
|
||||
|
||||
// # Open settings modal
|
||||
await channelPage.globalHeader.openSettings();
|
||||
await channelPage.settingsModal.toBeVisible();
|
||||
|
||||
// # Open notifications tab
|
||||
await channelPage.settingsModal.openNotificationsTab();
|
||||
|
||||
// # Open keywords that get highlighted section
|
||||
await channelPage.settingsModal.notificationsSettings.expandSection('keysWithHighlight');
|
||||
|
||||
// # Enter the keyword
|
||||
const keywordsInput = await channelPage.settingsModal.notificationsSettings.getKeywordsInput();
|
||||
await keywordsInput.type(keywords[3]);
|
||||
await keywordsInput.press('Tab');
|
||||
|
||||
// # Save the keyword
|
||||
await channelPage.settingsModal.notificationsSettings.save();
|
||||
|
||||
// # Close the settings modal
|
||||
await channelPage.settingsModal.closeModal();
|
||||
|
||||
// # Post a message without the keyword
|
||||
const messageWithoutKeyword = 'This message does not contain the keyword';
|
||||
await channelPage.centerView.postCreate.postMessage(messageWithoutKeyword);
|
||||
const lastPostWithoutHighlight = await channelPage.centerView.getLastPost();
|
||||
|
||||
// * Verify that the keywords are not highlighted
|
||||
await expect(lastPostWithoutHighlight.container.getByText(messageWithoutKeyword)).toBeVisible();
|
||||
await expect(lastPostWithoutHighlight.container.getByText(messageWithoutKeyword)).not.toHaveClass(
|
||||
highlightWithoutNotificationClass,
|
||||
);
|
||||
|
||||
// # Post a message with the keyword
|
||||
const messageWithKeyword = `This message contains the keyword ${keywords[3]}`;
|
||||
await channelPage.centerView.postCreate.postMessage(messageWithKeyword);
|
||||
const lastPostWithHighlight = await channelPage.centerView.getLastPost();
|
||||
|
||||
// * Verify that the keywords are highlighted
|
||||
await expect(lastPostWithHighlight.container.getByText(messageWithKeyword)).toBeVisible();
|
||||
await expect(lastPostWithHighlight.container.getByText(keywords[3])).toHaveClass(highlightWithoutNotificationClass);
|
||||
});
|
||||
|
||||
test('MM-T5465-3 Should highlight the keywords when a message is sent with the keyword in rhs', async ({pw, pages}) => {
|
||||
const {user} = await pw.initSetup();
|
||||
|
||||
// # Log in as a user in new browser context
|
||||
const {page} = await pw.testBrowser.login(user);
|
||||
|
||||
// # Visit default channel page
|
||||
const channelPage = new pages.ChannelsPage(page);
|
||||
await channelPage.goto();
|
||||
await channelPage.toBeVisible();
|
||||
|
||||
// # Open settings modal
|
||||
await channelPage.globalHeader.openSettings();
|
||||
await channelPage.settingsModal.toBeVisible();
|
||||
|
||||
// # Open notifications tab
|
||||
await channelPage.settingsModal.openNotificationsTab();
|
||||
|
||||
// # Open keywords that get highlighted section
|
||||
await channelPage.settingsModal.notificationsSettings.expandSection('keysWithHighlight');
|
||||
|
||||
// # Enter the keyword
|
||||
const keywordsInput = await channelPage.settingsModal.notificationsSettings.getKeywordsInput();
|
||||
await keywordsInput.type(keywords[3]);
|
||||
await keywordsInput.press('Tab');
|
||||
|
||||
// # Save the keyword
|
||||
await channelPage.settingsModal.notificationsSettings.save();
|
||||
|
||||
// # Close the settings modal
|
||||
await channelPage.settingsModal.closeModal();
|
||||
|
||||
// # Post a message without the keyword
|
||||
const messageWithoutKeyword = 'This message does not contain the keyword';
|
||||
await channelPage.centerView.postCreate.postMessage(messageWithoutKeyword);
|
||||
const lastPostWithoutHighlight = await channelPage.centerView.getLastPost();
|
||||
|
||||
// # Open the message in the RHS
|
||||
await lastPostWithoutHighlight.hover();
|
||||
await lastPostWithoutHighlight.postMenu.toBeVisible();
|
||||
await lastPostWithoutHighlight.postMenu.reply();
|
||||
await channelPage.sidebarRight.toBeVisible();
|
||||
|
||||
// # Post a message with the keyword in the RHS
|
||||
const messageWithKeyword = `This message contains the keyword ${keywords[3]}`;
|
||||
await channelPage.sidebarRight.postCreate.postMessage(messageWithKeyword);
|
||||
|
||||
// * Verify that the keywords are highlighted
|
||||
const lastPostWithHighlightInRHS = await channelPage.sidebarRight.getLastPost();
|
||||
await expect(lastPostWithHighlightInRHS.container.getByText(messageWithKeyword)).toBeVisible();
|
||||
await expect(lastPostWithHighlightInRHS.container.getByText(keywords[3])).toHaveClass(
|
||||
highlightWithoutNotificationClass,
|
||||
);
|
||||
});
|
||||
|
||||
test('MM-T5465-4 Highlighted keywords should not appear in the Recent Mentions', async ({pw, pages}) => {
|
||||
const {user} = await pw.initSetup();
|
||||
|
||||
// # Log in as a user in new browser context
|
||||
const {page} = await pw.testBrowser.login(user);
|
||||
|
||||
// # Visit default channel page
|
||||
const channelPage = new pages.ChannelsPage(page);
|
||||
await channelPage.goto();
|
||||
await channelPage.toBeVisible();
|
||||
|
||||
// # Open settings modal
|
||||
await channelPage.globalHeader.openSettings();
|
||||
await channelPage.settingsModal.toBeVisible();
|
||||
|
||||
// # Open notifications tab
|
||||
await channelPage.settingsModal.openNotificationsTab();
|
||||
|
||||
// # Open keywords that get highlighted section
|
||||
await channelPage.settingsModal.notificationsSettings.expandSection('keysWithHighlight');
|
||||
|
||||
// # Enter the keyword
|
||||
const keywordsInput = await channelPage.settingsModal.notificationsSettings.getKeywordsInput();
|
||||
await keywordsInput.type(keywords[0]);
|
||||
await keywordsInput.press('Tab');
|
||||
|
||||
// # Save the keyword
|
||||
await channelPage.settingsModal.notificationsSettings.save();
|
||||
|
||||
// # Close the settings modal
|
||||
await channelPage.settingsModal.closeModal();
|
||||
|
||||
// # Open the recent mentions
|
||||
await channelPage.globalHeader.openRecentMentions();
|
||||
|
||||
// * Verify recent mentions is empty
|
||||
await channelPage.sidebarRight.toBeVisible();
|
||||
await expect(channelPage.sidebarRight.container.getByText('No mentions yet')).toBeVisible();
|
||||
});
|
||||
|
||||
test('MM-T5465-5 Should highlight keywords in message sent from another user', async ({pw, pages}) => {
|
||||
const {adminClient, team, adminUser, user} = await pw.initSetup();
|
||||
|
||||
if (!adminUser) {
|
||||
throw new Error('Failed to create admin user');
|
||||
}
|
||||
|
||||
// # Get the default channel of the team for getting the channel id
|
||||
const channel = await adminClient.getChannelByName(team.id, 'town-square');
|
||||
|
||||
const highlightKeyword = keywords[0];
|
||||
const messageWithKeyword = `This recieved message contains the ${highlightKeyword} keyword `;
|
||||
|
||||
// # Create a post containing the keyword in the channel by admin
|
||||
await adminClient.createPost(
|
||||
createRandomPost({
|
||||
message: messageWithKeyword,
|
||||
channel_id: channel.id,
|
||||
user_id: adminUser.id,
|
||||
}),
|
||||
);
|
||||
|
||||
// # Now log in as a user in new browser context
|
||||
const {page} = await pw.testBrowser.login(user);
|
||||
|
||||
// # Visit default channel page
|
||||
const channelPage = new pages.ChannelsPage(page);
|
||||
await channelPage.goto();
|
||||
await channelPage.toBeVisible();
|
||||
|
||||
// # Open settings modal
|
||||
await channelPage.globalHeader.openSettings();
|
||||
await channelPage.settingsModal.toBeVisible();
|
||||
|
||||
// # Open notifications tab
|
||||
await channelPage.settingsModal.openNotificationsTab();
|
||||
|
||||
// # Open keywords that get highlighted section
|
||||
await channelPage.settingsModal.notificationsSettings.expandSection('keysWithHighlight');
|
||||
|
||||
// # Enter the keyword
|
||||
const keywordsInput = await channelPage.settingsModal.notificationsSettings.getKeywordsInput();
|
||||
await keywordsInput.type(keywords[0]);
|
||||
await keywordsInput.press('Tab');
|
||||
|
||||
// # Save the keyword
|
||||
await channelPage.settingsModal.notificationsSettings.save();
|
||||
|
||||
// # Close the settings modal
|
||||
await channelPage.settingsModal.closeModal();
|
||||
|
||||
// * Verify that the keywords are highlighted in the last message recieved
|
||||
const lastPostWithHighlight = await channelPage.centerView.getLastPost();
|
||||
await expect(lastPostWithHighlight.container.getByText(messageWithKeyword)).toBeVisible();
|
||||
await expect(lastPostWithHighlight.container.getByText(highlightKeyword)).toHaveClass(
|
||||
highlightWithoutNotificationClass,
|
||||
);
|
||||
});
|
@ -13,17 +13,18 @@ import (
|
||||
type MattermostFeature string
|
||||
|
||||
const (
|
||||
PaidFeatureGuestAccounts = MattermostFeature("mattermost.feature.guest_accounts")
|
||||
PaidFeatureCustomUsergroups = MattermostFeature("mattermost.feature.custom_user_groups")
|
||||
PaidFeatureCreateMultipleTeams = MattermostFeature("mattermost.feature.create_multiple_teams")
|
||||
PaidFeatureStartcall = MattermostFeature("mattermost.feature.start_call")
|
||||
PaidFeaturePlaybooksRetrospective = MattermostFeature("mattermost.feature.playbooks_retro")
|
||||
PaidFeatureUnlimitedMessages = MattermostFeature("mattermost.feature.unlimited_messages")
|
||||
PaidFeatureUnlimitedFileStorage = MattermostFeature("mattermost.feature.unlimited_file_storage")
|
||||
PaidFeatureAllProfessionalfeatures = MattermostFeature("mattermost.feature.all_professional")
|
||||
PaidFeatureAllEnterprisefeatures = MattermostFeature("mattermost.feature.all_enterprise")
|
||||
UpgradeDowngradedWorkspace = MattermostFeature("mattermost.feature.upgrade_downgraded_workspace")
|
||||
PluginFeature = MattermostFeature("mattermost.feature.plugin")
|
||||
PaidFeatureGuestAccounts = MattermostFeature("mattermost.feature.guest_accounts")
|
||||
PaidFeatureCustomUsergroups = MattermostFeature("mattermost.feature.custom_user_groups")
|
||||
PaidFeatureCreateMultipleTeams = MattermostFeature("mattermost.feature.create_multiple_teams")
|
||||
PaidFeatureStartcall = MattermostFeature("mattermost.feature.start_call")
|
||||
PaidFeaturePlaybooksRetrospective = MattermostFeature("mattermost.feature.playbooks_retro")
|
||||
PaidFeatureUnlimitedMessages = MattermostFeature("mattermost.feature.unlimited_messages")
|
||||
PaidFeatureUnlimitedFileStorage = MattermostFeature("mattermost.feature.unlimited_file_storage")
|
||||
PaidFeatureAllProfessionalfeatures = MattermostFeature("mattermost.feature.all_professional")
|
||||
PaidFeatureAllEnterprisefeatures = MattermostFeature("mattermost.feature.all_enterprise")
|
||||
UpgradeDowngradedWorkspace = MattermostFeature("mattermost.feature.upgrade_downgraded_workspace")
|
||||
PluginFeature = MattermostFeature("mattermost.feature.plugin")
|
||||
PaidFeatureHighlightWithoutNotification = MattermostFeature("mattermost.feature.highlight_without_notification")
|
||||
)
|
||||
|
||||
var validSKUs = map[string]struct{}{
|
||||
@ -33,16 +34,17 @@ var validSKUs = map[string]struct{}{
|
||||
|
||||
// These are the features a non admin would typically ping an admin about
|
||||
var paidFeatures = map[MattermostFeature]struct{}{
|
||||
PaidFeatureGuestAccounts: {},
|
||||
PaidFeatureCustomUsergroups: {},
|
||||
PaidFeatureCreateMultipleTeams: {},
|
||||
PaidFeatureStartcall: {},
|
||||
PaidFeaturePlaybooksRetrospective: {},
|
||||
PaidFeatureUnlimitedMessages: {},
|
||||
PaidFeatureUnlimitedFileStorage: {},
|
||||
PaidFeatureAllProfessionalfeatures: {},
|
||||
PaidFeatureAllEnterprisefeatures: {},
|
||||
UpgradeDowngradedWorkspace: {},
|
||||
PaidFeatureGuestAccounts: {},
|
||||
PaidFeatureCustomUsergroups: {},
|
||||
PaidFeatureCreateMultipleTeams: {},
|
||||
PaidFeatureStartcall: {},
|
||||
PaidFeaturePlaybooksRetrospective: {},
|
||||
PaidFeatureUnlimitedMessages: {},
|
||||
PaidFeatureUnlimitedFileStorage: {},
|
||||
PaidFeatureAllProfessionalfeatures: {},
|
||||
PaidFeatureAllEnterprisefeatures: {},
|
||||
UpgradeDowngradedWorkspace: {},
|
||||
PaidFeatureHighlightWithoutNotification: {},
|
||||
}
|
||||
|
||||
type NotifyAdminToUpgradeRequest struct {
|
||||
|
@ -36,6 +36,7 @@ const (
|
||||
ChannelMentionsNotifyProp = "channel"
|
||||
CommentsNotifyProp = "comments"
|
||||
MentionKeysNotifyProp = "mention_keys"
|
||||
HighlightsNotifyProp = "highlight_keys"
|
||||
CommentsNotifyNever = "never"
|
||||
CommentsNotifyRoot = "root"
|
||||
CommentsNotifyAny = "any"
|
||||
|
@ -0,0 +1,75 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`components/SettingItemMin should match snapshot 1`] = `
|
||||
<div
|
||||
className="section-min"
|
||||
onClick={[Function]}
|
||||
>
|
||||
<div
|
||||
className="secion-min__header"
|
||||
>
|
||||
<h4
|
||||
className="section-min__title"
|
||||
id="sectionTitle"
|
||||
>
|
||||
title
|
||||
</h4>
|
||||
<button
|
||||
aria-expanded={false}
|
||||
aria-labelledby="sectionTitle sectionEdit"
|
||||
className="color--link style--none section-min__edit"
|
||||
id="sectionEdit"
|
||||
onClick={[Function]}
|
||||
>
|
||||
<EditIcon />
|
||||
<MemoizedFormattedMessage
|
||||
defaultMessage="Edit"
|
||||
id="setting_item_min.edit"
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
<div
|
||||
className="section-min__describe"
|
||||
id="sectionDesc"
|
||||
>
|
||||
describe
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`components/SettingItemMin should match snapshot, on disableOpen to true 1`] = `
|
||||
<div
|
||||
className="section-min"
|
||||
onClick={[Function]}
|
||||
>
|
||||
<div
|
||||
className="secion-min__header"
|
||||
>
|
||||
<h4
|
||||
className="section-min__title"
|
||||
id="sectionTitle"
|
||||
>
|
||||
title
|
||||
</h4>
|
||||
<button
|
||||
aria-expanded={false}
|
||||
aria-labelledby="sectionTitle sectionEdit"
|
||||
className="color--link style--none section-min__edit"
|
||||
id="sectionEdit"
|
||||
onClick={[Function]}
|
||||
>
|
||||
<EditIcon />
|
||||
<MemoizedFormattedMessage
|
||||
defaultMessage="Edit"
|
||||
id="setting_item_min.edit"
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
<div
|
||||
className="section-min__describe"
|
||||
id="sectionDesc"
|
||||
>
|
||||
describe
|
||||
</div>
|
||||
</div>
|
||||
`;
|
@ -18,7 +18,6 @@ exports[`components/TextBox should match snapshot with additional, optional prop
|
||||
"hideUtilities": true,
|
||||
}
|
||||
}
|
||||
mentionKeys={Array []}
|
||||
message="some test text"
|
||||
/>
|
||||
</div>
|
||||
@ -147,7 +146,6 @@ exports[`components/TextBox should match snapshot with required props 1`] = `
|
||||
"hideUtilities": true,
|
||||
}
|
||||
}
|
||||
mentionKeys={Array []}
|
||||
message="some test text"
|
||||
/>
|
||||
</div>
|
||||
@ -271,7 +269,6 @@ exports[`components/TextBox should throw error when new property is too long 1`]
|
||||
"hideUtilities": true,
|
||||
}
|
||||
}
|
||||
mentionKeys={Array []}
|
||||
message="some test text that exceeds char limit"
|
||||
/>
|
||||
</div>
|
||||
@ -395,7 +392,6 @@ exports[`components/TextBox should throw error when value is too long 1`] = `
|
||||
"hideUtilities": true,
|
||||
}
|
||||
}
|
||||
mentionKeys={Array []}
|
||||
message="some test text that exceeds char limit"
|
||||
/>
|
||||
</div>
|
||||
|
@ -2,11 +2,6 @@
|
||||
|
||||
exports[`components/ToggleModalButton component should match snapshot 1`] = `
|
||||
<ToggleModalButton
|
||||
actions={
|
||||
Object {
|
||||
"openModal": [Function],
|
||||
}
|
||||
}
|
||||
ariaLabel="Delete Channel"
|
||||
dialogType={[Function]}
|
||||
id="channelDelete"
|
@ -62,6 +62,7 @@ exports[`components/UserList should match default snapshot when there are users
|
||||
"desktop_sound": "false",
|
||||
"email": "false",
|
||||
"first_name": "false",
|
||||
"highlight_keys": "",
|
||||
"mark_unread": "mention",
|
||||
"mention_keys": "",
|
||||
"push": "none",
|
||||
@ -122,6 +123,7 @@ exports[`components/UserList should match default snapshot when there are users
|
||||
"desktop_sound": "false",
|
||||
"email": "false",
|
||||
"first_name": "false",
|
||||
"highlight_keys": "",
|
||||
"mark_unread": "mention",
|
||||
"mention_keys": "",
|
||||
"push": "none",
|
||||
|
@ -111,6 +111,7 @@ exports[`components/admin_console/add_users_to_team_modal/AddUsersToTeamModal sh
|
||||
"desktop_sound": "false",
|
||||
"email": "false",
|
||||
"first_name": "false",
|
||||
"highlight_keys": "",
|
||||
"mark_unread": "mention",
|
||||
"mention_keys": "",
|
||||
"push": "none",
|
||||
@ -151,6 +152,7 @@ exports[`components/admin_console/add_users_to_team_modal/AddUsersToTeamModal sh
|
||||
"desktop_sound": "false",
|
||||
"email": "false",
|
||||
"first_name": "false",
|
||||
"highlight_keys": "",
|
||||
"mark_unread": "mention",
|
||||
"mention_keys": "",
|
||||
"push": "none",
|
||||
@ -297,6 +299,7 @@ exports[`components/admin_console/add_users_to_team_modal/AddUsersToTeamModal sh
|
||||
"desktop_sound": "false",
|
||||
"email": "false",
|
||||
"first_name": "false",
|
||||
"highlight_keys": "",
|
||||
"mark_unread": "mention",
|
||||
"mention_keys": "",
|
||||
"push": "none",
|
||||
@ -337,6 +340,7 @@ exports[`components/admin_console/add_users_to_team_modal/AddUsersToTeamModal sh
|
||||
"desktop_sound": "false",
|
||||
"email": "false",
|
||||
"first_name": "false",
|
||||
"highlight_keys": "",
|
||||
"mark_unread": "mention",
|
||||
"mention_keys": "",
|
||||
"push": "none",
|
||||
|
@ -59,6 +59,7 @@ exports[`admin_console/team_channel_settings/group/GroupList should match snapsh
|
||||
"desktop_sound": "false",
|
||||
"email": "false",
|
||||
"first_name": "false",
|
||||
"highlight_keys": "",
|
||||
"mark_unread": "mention",
|
||||
"mention_keys": "",
|
||||
"push": "none",
|
||||
@ -106,6 +107,7 @@ exports[`admin_console/team_channel_settings/group/GroupList should match snapsh
|
||||
"desktop_sound": "false",
|
||||
"email": "false",
|
||||
"first_name": "false",
|
||||
"highlight_keys": "",
|
||||
"mark_unread": "mention",
|
||||
"mention_keys": "",
|
||||
"push": "none",
|
||||
@ -153,6 +155,7 @@ exports[`admin_console/team_channel_settings/group/GroupList should match snapsh
|
||||
"desktop_sound": "false",
|
||||
"email": "false",
|
||||
"first_name": "false",
|
||||
"highlight_keys": "",
|
||||
"mark_unread": "mention",
|
||||
"mention_keys": "",
|
||||
"push": "none",
|
||||
@ -200,6 +203,7 @@ exports[`admin_console/team_channel_settings/group/GroupList should match snapsh
|
||||
"desktop_sound": "false",
|
||||
"email": "false",
|
||||
"first_name": "false",
|
||||
"highlight_keys": "",
|
||||
"mark_unread": "mention",
|
||||
"mention_keys": "",
|
||||
"push": "none",
|
||||
@ -247,6 +251,7 @@ exports[`admin_console/team_channel_settings/group/GroupList should match snapsh
|
||||
"desktop_sound": "false",
|
||||
"email": "false",
|
||||
"first_name": "false",
|
||||
"highlight_keys": "",
|
||||
"mark_unread": "mention",
|
||||
"mention_keys": "",
|
||||
"push": "none",
|
||||
@ -294,6 +299,7 @@ exports[`admin_console/team_channel_settings/group/GroupList should match snapsh
|
||||
"desktop_sound": "false",
|
||||
"email": "false",
|
||||
"first_name": "false",
|
||||
"highlight_keys": "",
|
||||
"mark_unread": "mention",
|
||||
"mention_keys": "",
|
||||
"push": "none",
|
||||
@ -341,6 +347,7 @@ exports[`admin_console/team_channel_settings/group/GroupList should match snapsh
|
||||
"desktop_sound": "false",
|
||||
"email": "false",
|
||||
"first_name": "false",
|
||||
"highlight_keys": "",
|
||||
"mark_unread": "mention",
|
||||
"mention_keys": "",
|
||||
"push": "none",
|
||||
@ -388,6 +395,7 @@ exports[`admin_console/team_channel_settings/group/GroupList should match snapsh
|
||||
"desktop_sound": "false",
|
||||
"email": "false",
|
||||
"first_name": "false",
|
||||
"highlight_keys": "",
|
||||
"mark_unread": "mention",
|
||||
"mention_keys": "",
|
||||
"push": "none",
|
||||
@ -435,6 +443,7 @@ exports[`admin_console/team_channel_settings/group/GroupList should match snapsh
|
||||
"desktop_sound": "false",
|
||||
"email": "false",
|
||||
"first_name": "false",
|
||||
"highlight_keys": "",
|
||||
"mark_unread": "mention",
|
||||
"mention_keys": "",
|
||||
"push": "none",
|
||||
@ -482,6 +491,7 @@ exports[`admin_console/team_channel_settings/group/GroupList should match snapsh
|
||||
"desktop_sound": "false",
|
||||
"email": "false",
|
||||
"first_name": "false",
|
||||
"highlight_keys": "",
|
||||
"mark_unread": "mention",
|
||||
"mention_keys": "",
|
||||
"push": "none",
|
||||
|
@ -26,6 +26,7 @@ describe('components/admin_console/reset_password_modal/reset_password_modal.tsx
|
||||
first_name: 'true',
|
||||
mark_unread: 'all',
|
||||
mention_keys: '',
|
||||
highlight_keys: '',
|
||||
push: 'default',
|
||||
push_status: 'ooo',
|
||||
};
|
||||
|
@ -114,6 +114,7 @@ exports[`admin_console/add_users_to_role_modal search should not include bot use
|
||||
"desktop_sound": "false",
|
||||
"email": "false",
|
||||
"first_name": "false",
|
||||
"highlight_keys": "",
|
||||
"mark_unread": "mention",
|
||||
"mention_keys": "",
|
||||
"push": "none",
|
||||
@ -359,6 +360,7 @@ exports[`admin_console/add_users_to_role_modal should have single passed value 1
|
||||
"desktop_sound": "false",
|
||||
"email": "false",
|
||||
"first_name": "false",
|
||||
"highlight_keys": "",
|
||||
"mark_unread": "mention",
|
||||
"mention_keys": "",
|
||||
"push": "none",
|
||||
@ -503,6 +505,7 @@ exports[`admin_console/add_users_to_role_modal should include additional user 1`
|
||||
"desktop_sound": "false",
|
||||
"email": "false",
|
||||
"first_name": "false",
|
||||
"highlight_keys": "",
|
||||
"mark_unread": "mention",
|
||||
"mention_keys": "",
|
||||
"push": "none",
|
||||
@ -543,6 +546,7 @@ exports[`admin_console/add_users_to_role_modal should include additional user 1`
|
||||
"desktop_sound": "false",
|
||||
"email": "false",
|
||||
"first_name": "false",
|
||||
"highlight_keys": "",
|
||||
"mark_unread": "mention",
|
||||
"mention_keys": "",
|
||||
"push": "none",
|
||||
@ -687,6 +691,7 @@ exports[`admin_console/add_users_to_role_modal should include additional user 2`
|
||||
"desktop_sound": "false",
|
||||
"email": "false",
|
||||
"first_name": "false",
|
||||
"highlight_keys": "",
|
||||
"mark_unread": "mention",
|
||||
"mention_keys": "",
|
||||
"push": "none",
|
||||
@ -727,6 +732,7 @@ exports[`admin_console/add_users_to_role_modal should include additional user 2`
|
||||
"desktop_sound": "false",
|
||||
"email": "false",
|
||||
"first_name": "false",
|
||||
"highlight_keys": "",
|
||||
"mark_unread": "mention",
|
||||
"mention_keys": "",
|
||||
"push": "none",
|
||||
@ -871,6 +877,7 @@ exports[`admin_console/add_users_to_role_modal should not include bot user 1`] =
|
||||
"desktop_sound": "false",
|
||||
"email": "false",
|
||||
"first_name": "false",
|
||||
"highlight_keys": "",
|
||||
"mark_unread": "mention",
|
||||
"mention_keys": "",
|
||||
"push": "none",
|
||||
|
@ -3,7 +3,7 @@
|
||||
exports[`admin_console/system_role_users should match snapshot 1`] = `
|
||||
<AdminPanel
|
||||
button={
|
||||
<Memo(Connect(ToggleModalButton))
|
||||
<ToggleModalButton
|
||||
className="btn btn-primary"
|
||||
dialogProps={
|
||||
Object {
|
||||
@ -32,6 +32,7 @@ exports[`admin_console/system_role_users should match snapshot 1`] = `
|
||||
"desktop_sound": "false",
|
||||
"email": "false",
|
||||
"first_name": "false",
|
||||
"highlight_keys": "",
|
||||
"mark_unread": "mention",
|
||||
"mention_keys": "",
|
||||
"push": "none",
|
||||
@ -72,6 +73,7 @@ exports[`admin_console/system_role_users should match snapshot 1`] = `
|
||||
"desktop_sound": "false",
|
||||
"email": "false",
|
||||
"first_name": "false",
|
||||
"highlight_keys": "",
|
||||
"mark_unread": "mention",
|
||||
"mention_keys": "",
|
||||
"push": "none",
|
||||
@ -119,7 +121,7 @@ exports[`admin_console/system_role_users should match snapshot 1`] = `
|
||||
defaultMessage="Add People"
|
||||
id="admin.permissions.system_role_users.add_people"
|
||||
/>
|
||||
</Memo(Connect(ToggleModalButton))>
|
||||
</ToggleModalButton>
|
||||
}
|
||||
className=""
|
||||
id="SystemRoleUsers"
|
||||
@ -191,6 +193,7 @@ exports[`admin_console/system_role_users should match snapshot 1`] = `
|
||||
"desktop_sound": "false",
|
||||
"email": "false",
|
||||
"first_name": "false",
|
||||
"highlight_keys": "",
|
||||
"mark_unread": "mention",
|
||||
"mention_keys": "",
|
||||
"push": "none",
|
||||
@ -236,6 +239,7 @@ exports[`admin_console/system_role_users should match snapshot 1`] = `
|
||||
"desktop_sound": "false",
|
||||
"email": "false",
|
||||
"first_name": "false",
|
||||
"highlight_keys": "",
|
||||
"mark_unread": "mention",
|
||||
"mention_keys": "",
|
||||
"push": "none",
|
||||
@ -283,6 +287,7 @@ exports[`admin_console/system_role_users should match snapshot 1`] = `
|
||||
"desktop_sound": "false",
|
||||
"email": "false",
|
||||
"first_name": "false",
|
||||
"highlight_keys": "",
|
||||
"mark_unread": "mention",
|
||||
"mention_keys": "",
|
||||
"push": "none",
|
||||
@ -328,6 +333,7 @@ exports[`admin_console/system_role_users should match snapshot 1`] = `
|
||||
"desktop_sound": "false",
|
||||
"email": "false",
|
||||
"first_name": "false",
|
||||
"highlight_keys": "",
|
||||
"mark_unread": "mention",
|
||||
"mention_keys": "",
|
||||
"push": "none",
|
||||
@ -359,7 +365,7 @@ exports[`admin_console/system_role_users should match snapshot 1`] = `
|
||||
exports[`admin_console/system_role_users should match snapshot with readOnly true 1`] = `
|
||||
<AdminPanel
|
||||
button={
|
||||
<Memo(Connect(ToggleModalButton))
|
||||
<ToggleModalButton
|
||||
className="btn btn-primary"
|
||||
dialogProps={
|
||||
Object {
|
||||
@ -388,6 +394,7 @@ exports[`admin_console/system_role_users should match snapshot with readOnly tru
|
||||
"desktop_sound": "false",
|
||||
"email": "false",
|
||||
"first_name": "false",
|
||||
"highlight_keys": "",
|
||||
"mark_unread": "mention",
|
||||
"mention_keys": "",
|
||||
"push": "none",
|
||||
@ -428,6 +435,7 @@ exports[`admin_console/system_role_users should match snapshot with readOnly tru
|
||||
"desktop_sound": "false",
|
||||
"email": "false",
|
||||
"first_name": "false",
|
||||
"highlight_keys": "",
|
||||
"mark_unread": "mention",
|
||||
"mention_keys": "",
|
||||
"push": "none",
|
||||
@ -475,7 +483,7 @@ exports[`admin_console/system_role_users should match snapshot with readOnly tru
|
||||
defaultMessage="Add People"
|
||||
id="admin.permissions.system_role_users.add_people"
|
||||
/>
|
||||
</Memo(Connect(ToggleModalButton))>
|
||||
</ToggleModalButton>
|
||||
}
|
||||
className=""
|
||||
id="SystemRoleUsers"
|
||||
@ -547,6 +555,7 @@ exports[`admin_console/system_role_users should match snapshot with readOnly tru
|
||||
"desktop_sound": "false",
|
||||
"email": "false",
|
||||
"first_name": "false",
|
||||
"highlight_keys": "",
|
||||
"mark_unread": "mention",
|
||||
"mention_keys": "",
|
||||
"push": "none",
|
||||
@ -592,6 +601,7 @@ exports[`admin_console/system_role_users should match snapshot with readOnly tru
|
||||
"desktop_sound": "false",
|
||||
"email": "false",
|
||||
"first_name": "false",
|
||||
"highlight_keys": "",
|
||||
"mark_unread": "mention",
|
||||
"mention_keys": "",
|
||||
"push": "none",
|
||||
@ -639,6 +649,7 @@ exports[`admin_console/system_role_users should match snapshot with readOnly tru
|
||||
"desktop_sound": "false",
|
||||
"email": "false",
|
||||
"first_name": "false",
|
||||
"highlight_keys": "",
|
||||
"mark_unread": "mention",
|
||||
"mention_keys": "",
|
||||
"push": "none",
|
||||
@ -684,6 +695,7 @@ exports[`admin_console/system_role_users should match snapshot with readOnly tru
|
||||
"desktop_sound": "false",
|
||||
"email": "false",
|
||||
"first_name": "false",
|
||||
"highlight_keys": "",
|
||||
"mark_unread": "mention",
|
||||
"mention_keys": "",
|
||||
"push": "none",
|
||||
|
@ -3,7 +3,7 @@
|
||||
exports[`admin_console/team_channel_settings/channel/ChannelGroups should match snapshot 1`] = `
|
||||
<AdminPanel
|
||||
button={
|
||||
<Memo(Connect(ToggleModalButton))
|
||||
<ToggleModalButton
|
||||
className="btn btn-primary"
|
||||
dialogProps={
|
||||
Object {
|
||||
@ -42,7 +42,7 @@ exports[`admin_console/team_channel_settings/channel/ChannelGroups should match
|
||||
defaultMessage="Add Group"
|
||||
id="admin.channel_settings.channel_details.add_group"
|
||||
/>
|
||||
</Memo(Connect(ToggleModalButton))>
|
||||
</ToggleModalButton>
|
||||
}
|
||||
className=""
|
||||
id="channel_groups"
|
||||
|
@ -3,7 +3,7 @@
|
||||
exports[`admin_console/team_channel_settings/channel/ChannelMembers should match snapshot 1`] = `
|
||||
<AdminPanel
|
||||
button={
|
||||
<Memo(Connect(ToggleModalButton))
|
||||
<ToggleModalButton
|
||||
className="btn btn-primary"
|
||||
dialogProps={
|
||||
Object {
|
||||
@ -47,7 +47,7 @@ exports[`admin_console/team_channel_settings/channel/ChannelMembers should match
|
||||
defaultMessage="Add Members"
|
||||
id="admin.team_settings.team_details.add_members"
|
||||
/>
|
||||
</Memo(Connect(ToggleModalButton))>
|
||||
</ToggleModalButton>
|
||||
}
|
||||
className=""
|
||||
id="channelMembers"
|
||||
@ -215,6 +215,7 @@ exports[`admin_console/team_channel_settings/channel/ChannelMembers should match
|
||||
"desktop_sound": "false",
|
||||
"email": "false",
|
||||
"first_name": "false",
|
||||
"highlight_keys": "",
|
||||
"mark_unread": "mention",
|
||||
"mention_keys": "",
|
||||
"push": "none",
|
||||
@ -253,6 +254,7 @@ exports[`admin_console/team_channel_settings/channel/ChannelMembers should match
|
||||
"desktop_sound": "false",
|
||||
"email": "false",
|
||||
"first_name": "false",
|
||||
"highlight_keys": "",
|
||||
"mark_unread": "mention",
|
||||
"mention_keys": "",
|
||||
"push": "none",
|
||||
@ -291,6 +293,7 @@ exports[`admin_console/team_channel_settings/channel/ChannelMembers should match
|
||||
"desktop_sound": "false",
|
||||
"email": "false",
|
||||
"first_name": "false",
|
||||
"highlight_keys": "",
|
||||
"mark_unread": "mention",
|
||||
"mention_keys": "",
|
||||
"push": "none",
|
||||
@ -314,7 +317,7 @@ exports[`admin_console/team_channel_settings/channel/ChannelMembers should match
|
||||
exports[`admin_console/team_channel_settings/channel/ChannelMembers should match snapshot loading no users 1`] = `
|
||||
<AdminPanel
|
||||
button={
|
||||
<Memo(Connect(ToggleModalButton))
|
||||
<ToggleModalButton
|
||||
className="btn btn-primary"
|
||||
dialogProps={
|
||||
Object {
|
||||
@ -358,7 +361,7 @@ exports[`admin_console/team_channel_settings/channel/ChannelMembers should match
|
||||
defaultMessage="Add Members"
|
||||
id="admin.team_settings.team_details.add_members"
|
||||
/>
|
||||
</Memo(Connect(ToggleModalButton))>
|
||||
</ToggleModalButton>
|
||||
}
|
||||
className=""
|
||||
id="channelMembers"
|
||||
|
@ -17,7 +17,7 @@ exports[`admin_console/team_channel_settings/group/GroupRow should match snapsho
|
||||
<span
|
||||
className="group-description row-content"
|
||||
>
|
||||
<Connect(ToggleModalButton)
|
||||
<ToggleModalButton
|
||||
className="color--link"
|
||||
dialogProps={
|
||||
Object {
|
||||
@ -41,7 +41,7 @@ exports[`admin_console/team_channel_settings/group/GroupRow should match snapsho
|
||||
}
|
||||
}
|
||||
/>
|
||||
</Connect(ToggleModalButton)>
|
||||
</ToggleModalButton>
|
||||
</span>
|
||||
<div
|
||||
className="group-description row-content roles"
|
||||
|
@ -60,6 +60,7 @@ exports[`components/admin_console/team_channel_settings/group/UsersToRemoveRole
|
||||
"desktop_sound": "false",
|
||||
"email": "false",
|
||||
"first_name": "false",
|
||||
"highlight_keys": "",
|
||||
"mark_unread": "mention",
|
||||
"mention_keys": "",
|
||||
"push": "none",
|
||||
@ -153,6 +154,7 @@ exports[`components/admin_console/team_channel_settings/group/UsersToRemoveRole
|
||||
"desktop_sound": "false",
|
||||
"email": "false",
|
||||
"first_name": "false",
|
||||
"highlight_keys": "",
|
||||
"mark_unread": "mention",
|
||||
"mention_keys": "",
|
||||
"push": "none",
|
||||
@ -246,6 +248,7 @@ exports[`components/admin_console/team_channel_settings/group/UsersToRemoveRole
|
||||
"desktop_sound": "false",
|
||||
"email": "false",
|
||||
"first_name": "false",
|
||||
"highlight_keys": "",
|
||||
"mark_unread": "mention",
|
||||
"mention_keys": "",
|
||||
"push": "none",
|
||||
@ -339,6 +342,7 @@ exports[`components/admin_console/team_channel_settings/group/UsersToRemoveRole
|
||||
"desktop_sound": "false",
|
||||
"email": "false",
|
||||
"first_name": "false",
|
||||
"highlight_keys": "",
|
||||
"mark_unread": "mention",
|
||||
"mention_keys": "",
|
||||
"push": "none",
|
||||
@ -423,6 +427,7 @@ exports[`components/admin_console/team_channel_settings/group/UsersToRemoveRole
|
||||
"desktop_sound": "false",
|
||||
"email": "false",
|
||||
"first_name": "false",
|
||||
"highlight_keys": "",
|
||||
"mark_unread": "mention",
|
||||
"mention_keys": "",
|
||||
"push": "none",
|
||||
@ -507,6 +512,7 @@ exports[`components/admin_console/team_channel_settings/group/UsersToRemoveRole
|
||||
"desktop_sound": "false",
|
||||
"email": "false",
|
||||
"first_name": "false",
|
||||
"highlight_keys": "",
|
||||
"mark_unread": "mention",
|
||||
"mention_keys": "",
|
||||
"push": "none",
|
||||
|
@ -3,7 +3,7 @@
|
||||
exports[`admin_console/team_channel_settings/team/TeamGroups should match snapshot 1`] = `
|
||||
<AdminPanel
|
||||
button={
|
||||
<Memo(Connect(ToggleModalButton))
|
||||
<ToggleModalButton
|
||||
className="btn btn-primary"
|
||||
dialogProps={
|
||||
Object {
|
||||
@ -61,7 +61,7 @@ exports[`admin_console/team_channel_settings/team/TeamGroups should match snapsh
|
||||
defaultMessage="Add Group"
|
||||
id="admin.team_settings.team_details.add_group"
|
||||
/>
|
||||
</Memo(Connect(ToggleModalButton))>
|
||||
</ToggleModalButton>
|
||||
}
|
||||
className=""
|
||||
id="team_groups"
|
||||
|
@ -3,7 +3,7 @@
|
||||
exports[`admin_console/team_channel_settings/team/TeamMembers should match snapshot 1`] = `
|
||||
<AdminPanel
|
||||
button={
|
||||
<Memo(Connect(ToggleModalButton))
|
||||
<ToggleModalButton
|
||||
className="btn btn-primary"
|
||||
dialogProps={
|
||||
Object {
|
||||
@ -46,7 +46,7 @@ exports[`admin_console/team_channel_settings/team/TeamMembers should match snaps
|
||||
defaultMessage="Add Members"
|
||||
id="admin.team_settings.team_details.add_members"
|
||||
/>
|
||||
</Memo(Connect(ToggleModalButton))>
|
||||
</ToggleModalButton>
|
||||
}
|
||||
className=""
|
||||
id="teamMembers"
|
||||
@ -187,6 +187,7 @@ exports[`admin_console/team_channel_settings/team/TeamMembers should match snaps
|
||||
"desktop_sound": "false",
|
||||
"email": "false",
|
||||
"first_name": "false",
|
||||
"highlight_keys": "",
|
||||
"mark_unread": "mention",
|
||||
"mention_keys": "",
|
||||
"push": "none",
|
||||
@ -225,6 +226,7 @@ exports[`admin_console/team_channel_settings/team/TeamMembers should match snaps
|
||||
"desktop_sound": "false",
|
||||
"email": "false",
|
||||
"first_name": "false",
|
||||
"highlight_keys": "",
|
||||
"mark_unread": "mention",
|
||||
"mention_keys": "",
|
||||
"push": "none",
|
||||
@ -263,6 +265,7 @@ exports[`admin_console/team_channel_settings/team/TeamMembers should match snaps
|
||||
"desktop_sound": "false",
|
||||
"email": "false",
|
||||
"first_name": "false",
|
||||
"highlight_keys": "",
|
||||
"mark_unread": "mention",
|
||||
"mention_keys": "",
|
||||
"push": "none",
|
||||
@ -286,7 +289,7 @@ exports[`admin_console/team_channel_settings/team/TeamMembers should match snaps
|
||||
exports[`admin_console/team_channel_settings/team/TeamMembers should match snapshot loading no users 1`] = `
|
||||
<AdminPanel
|
||||
button={
|
||||
<Memo(Connect(ToggleModalButton))
|
||||
<ToggleModalButton
|
||||
className="btn btn-primary"
|
||||
dialogProps={
|
||||
Object {
|
||||
@ -329,7 +332,7 @@ exports[`admin_console/team_channel_settings/team/TeamMembers should match snaps
|
||||
defaultMessage="Add Members"
|
||||
id="admin.team_settings.team_details.add_members"
|
||||
/>
|
||||
</Memo(Connect(ToggleModalButton))>
|
||||
</ToggleModalButton>
|
||||
}
|
||||
className=""
|
||||
id="teamMembers"
|
||||
|
@ -84,6 +84,7 @@ exports[`components/admin_console/user_grid/UserGrid should match snapshot with
|
||||
"desktop_sound": "false",
|
||||
"email": "false",
|
||||
"first_name": "false",
|
||||
"highlight_keys": "",
|
||||
"mark_unread": "mention",
|
||||
"mention_keys": "",
|
||||
"push": "none",
|
||||
@ -128,6 +129,7 @@ exports[`components/admin_console/user_grid/UserGrid should match snapshot with
|
||||
"desktop_sound": "false",
|
||||
"email": "false",
|
||||
"first_name": "false",
|
||||
"highlight_keys": "",
|
||||
"mark_unread": "mention",
|
||||
"mention_keys": "",
|
||||
"push": "none",
|
||||
@ -187,6 +189,7 @@ exports[`components/admin_console/user_grid/UserGrid should match snapshot with
|
||||
"desktop_sound": "false",
|
||||
"email": "false",
|
||||
"first_name": "false",
|
||||
"highlight_keys": "",
|
||||
"mark_unread": "mention",
|
||||
"mention_keys": "",
|
||||
"push": "none",
|
||||
@ -234,6 +237,7 @@ exports[`components/admin_console/user_grid/UserGrid should match snapshot with
|
||||
"desktop_sound": "false",
|
||||
"email": "false",
|
||||
"first_name": "false",
|
||||
"highlight_keys": "",
|
||||
"mark_unread": "mention",
|
||||
"mention_keys": "",
|
||||
"push": "none",
|
||||
@ -278,6 +282,7 @@ exports[`components/admin_console/user_grid/UserGrid should match snapshot with
|
||||
"desktop_sound": "false",
|
||||
"email": "false",
|
||||
"first_name": "false",
|
||||
"highlight_keys": "",
|
||||
"mark_unread": "mention",
|
||||
"mention_keys": "",
|
||||
"push": "none",
|
||||
@ -337,6 +342,7 @@ exports[`components/admin_console/user_grid/UserGrid should match snapshot with
|
||||
"desktop_sound": "false",
|
||||
"email": "false",
|
||||
"first_name": "false",
|
||||
"highlight_keys": "",
|
||||
"mark_unread": "mention",
|
||||
"mention_keys": "",
|
||||
"push": "none",
|
||||
@ -453,6 +459,7 @@ exports[`components/admin_console/user_grid/UserGrid should match snapshot with
|
||||
"desktop_sound": "false",
|
||||
"email": "false",
|
||||
"first_name": "false",
|
||||
"highlight_keys": "",
|
||||
"mark_unread": "mention",
|
||||
"mention_keys": "",
|
||||
"push": "none",
|
||||
@ -505,6 +512,7 @@ exports[`components/admin_console/user_grid/UserGrid should match snapshot with
|
||||
"desktop_sound": "false",
|
||||
"email": "false",
|
||||
"first_name": "false",
|
||||
"highlight_keys": "",
|
||||
"mark_unread": "mention",
|
||||
"mention_keys": "",
|
||||
"push": "none",
|
||||
@ -556,6 +564,7 @@ exports[`components/admin_console/user_grid/UserGrid should match snapshot with
|
||||
"desktop_sound": "false",
|
||||
"email": "false",
|
||||
"first_name": "false",
|
||||
"highlight_keys": "",
|
||||
"mark_unread": "mention",
|
||||
"mention_keys": "",
|
||||
"push": "none",
|
||||
@ -603,6 +612,7 @@ exports[`components/admin_console/user_grid/UserGrid should match snapshot with
|
||||
"desktop_sound": "false",
|
||||
"email": "false",
|
||||
"first_name": "false",
|
||||
"highlight_keys": "",
|
||||
"mark_unread": "mention",
|
||||
"mention_keys": "",
|
||||
"push": "none",
|
||||
@ -647,6 +657,7 @@ exports[`components/admin_console/user_grid/UserGrid should match snapshot with
|
||||
"desktop_sound": "false",
|
||||
"email": "false",
|
||||
"first_name": "false",
|
||||
"highlight_keys": "",
|
||||
"mark_unread": "mention",
|
||||
"mention_keys": "",
|
||||
"push": "none",
|
||||
@ -706,6 +717,7 @@ exports[`components/admin_console/user_grid/UserGrid should match snapshot with
|
||||
"desktop_sound": "false",
|
||||
"email": "false",
|
||||
"first_name": "false",
|
||||
"highlight_keys": "",
|
||||
"mark_unread": "mention",
|
||||
"mention_keys": "",
|
||||
"push": "none",
|
||||
@ -753,6 +765,7 @@ exports[`components/admin_console/user_grid/UserGrid should match snapshot with
|
||||
"desktop_sound": "false",
|
||||
"email": "false",
|
||||
"first_name": "false",
|
||||
"highlight_keys": "",
|
||||
"mark_unread": "mention",
|
||||
"mention_keys": "",
|
||||
"push": "none",
|
||||
@ -797,6 +810,7 @@ exports[`components/admin_console/user_grid/UserGrid should match snapshot with
|
||||
"desktop_sound": "false",
|
||||
"email": "false",
|
||||
"first_name": "false",
|
||||
"highlight_keys": "",
|
||||
"mark_unread": "mention",
|
||||
"mention_keys": "",
|
||||
"push": "none",
|
||||
@ -856,6 +870,7 @@ exports[`components/admin_console/user_grid/UserGrid should match snapshot with
|
||||
"desktop_sound": "false",
|
||||
"email": "false",
|
||||
"first_name": "false",
|
||||
"highlight_keys": "",
|
||||
"mark_unread": "mention",
|
||||
"mention_keys": "",
|
||||
"push": "none",
|
||||
@ -972,6 +987,7 @@ exports[`components/admin_console/user_grid/UserGrid should match snapshot with
|
||||
"desktop_sound": "false",
|
||||
"email": "false",
|
||||
"first_name": "false",
|
||||
"highlight_keys": "",
|
||||
"mark_unread": "mention",
|
||||
"mention_keys": "",
|
||||
"push": "none",
|
||||
@ -1016,6 +1032,7 @@ exports[`components/admin_console/user_grid/UserGrid should match snapshot with
|
||||
"desktop_sound": "false",
|
||||
"email": "false",
|
||||
"first_name": "false",
|
||||
"highlight_keys": "",
|
||||
"mark_unread": "mention",
|
||||
"mention_keys": "",
|
||||
"push": "none",
|
||||
@ -1075,6 +1092,7 @@ exports[`components/admin_console/user_grid/UserGrid should match snapshot with
|
||||
"desktop_sound": "false",
|
||||
"email": "false",
|
||||
"first_name": "false",
|
||||
"highlight_keys": "",
|
||||
"mark_unread": "mention",
|
||||
"mention_keys": "",
|
||||
"push": "none",
|
||||
|
@ -137,6 +137,7 @@ exports[`components/ChannelHeaderDropdown should match snapshot with no plugin i
|
||||
"desktop_sound": "false",
|
||||
"email": "false",
|
||||
"first_name": "false",
|
||||
"highlight_keys": "",
|
||||
"mark_unread": "mention",
|
||||
"mention_keys": "",
|
||||
"push": "none",
|
||||
@ -213,6 +214,7 @@ exports[`components/ChannelHeaderDropdown should match snapshot with no plugin i
|
||||
"desktop_sound": "false",
|
||||
"email": "false",
|
||||
"first_name": "false",
|
||||
"highlight_keys": "",
|
||||
"mark_unread": "mention",
|
||||
"mention_keys": "",
|
||||
"push": "none",
|
||||
@ -757,6 +759,7 @@ exports[`components/ChannelHeaderDropdown should match snapshot with no plugin i
|
||||
"desktop_sound": "false",
|
||||
"email": "false",
|
||||
"first_name": "false",
|
||||
"highlight_keys": "",
|
||||
"mark_unread": "mention",
|
||||
"mention_keys": "",
|
||||
"push": "none",
|
||||
@ -966,6 +969,7 @@ exports[`components/ChannelHeaderDropdown should match snapshot with plugins 1`]
|
||||
"desktop_sound": "false",
|
||||
"email": "false",
|
||||
"first_name": "false",
|
||||
"highlight_keys": "",
|
||||
"mark_unread": "mention",
|
||||
"mention_keys": "",
|
||||
"push": "none",
|
||||
@ -1042,6 +1046,7 @@ exports[`components/ChannelHeaderDropdown should match snapshot with plugins 1`]
|
||||
"desktop_sound": "false",
|
||||
"email": "false",
|
||||
"first_name": "false",
|
||||
"highlight_keys": "",
|
||||
"mark_unread": "mention",
|
||||
"mention_keys": "",
|
||||
"push": "none",
|
||||
@ -1586,6 +1591,7 @@ exports[`components/ChannelHeaderDropdown should match snapshot with plugins 1`]
|
||||
"desktop_sound": "false",
|
||||
"email": "false",
|
||||
"first_name": "false",
|
||||
"highlight_keys": "",
|
||||
"mark_unread": "mention",
|
||||
"mention_keys": "",
|
||||
"push": "none",
|
||||
|
@ -1,7 +1,7 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`components/channel_notifications_modal/CollapseView should match snapshot, DESKTOP on collapsed view 1`] = `
|
||||
<Connect(SettingItemMin)
|
||||
<SettingItemMin
|
||||
describe={
|
||||
<Describe
|
||||
globalNotifyLevel="default"
|
||||
@ -21,7 +21,7 @@ exports[`components/channel_notifications_modal/CollapseView should match snapsh
|
||||
`;
|
||||
|
||||
exports[`components/channel_notifications_modal/CollapseView should match snapshot, MARK_UNREAD on collapsed view 1`] = `
|
||||
<Connect(SettingItemMin)
|
||||
<SettingItemMin
|
||||
describe={
|
||||
<Describe
|
||||
globalNotifyLevel="default"
|
||||
@ -41,7 +41,7 @@ exports[`components/channel_notifications_modal/CollapseView should match snapsh
|
||||
`;
|
||||
|
||||
exports[`components/channel_notifications_modal/CollapseView should match snapshot, PUSH on collapsed view 1`] = `
|
||||
<Connect(SettingItemMin)
|
||||
<SettingItemMin
|
||||
describe={
|
||||
<Describe
|
||||
globalNotifyLevel="default"
|
||||
|
@ -35,8 +35,8 @@ import './feature_restricted_modal.scss';
|
||||
type FeatureRestrictedModalProps = {
|
||||
titleAdminPreTrial: string;
|
||||
messageAdminPreTrial: string;
|
||||
titleAdminPostTrial: string;
|
||||
messageAdminPostTrial: string;
|
||||
titleAdminPostTrial?: string;
|
||||
messageAdminPostTrial?: string;
|
||||
titleEndUser?: string;
|
||||
messageEndUser?: string;
|
||||
customSecondaryButton?: {msg: string; action: () => void};
|
||||
|
@ -48,6 +48,7 @@ exports[`components/integrations/InstalledOutgoingWebhooks should match snapshot
|
||||
"desktop_sound": "false",
|
||||
"email": "false",
|
||||
"first_name": "false",
|
||||
"highlight_keys": "",
|
||||
"mark_unread": "mention",
|
||||
"mention_keys": "",
|
||||
"push": "none",
|
||||
@ -155,6 +156,7 @@ exports[`components/integrations/InstalledOutgoingWebhooks should match snapshot
|
||||
"desktop_sound": "false",
|
||||
"email": "false",
|
||||
"first_name": "false",
|
||||
"highlight_keys": "",
|
||||
"mark_unread": "mention",
|
||||
"mention_keys": "",
|
||||
"push": "none",
|
||||
|
@ -24,7 +24,7 @@ function makeGetChannelNamesMap() {
|
||||
return createSelector(
|
||||
'makeGetChannelNamesMap',
|
||||
getChannelNameToDisplayNameMap,
|
||||
(state: GlobalState, props: OwnProps) => props && props.channelNamesMap,
|
||||
(_: GlobalState, props: OwnProps) => props && props.channelNamesMap,
|
||||
(channelNamesMap, channelMentions) => {
|
||||
if (channelMentions) {
|
||||
return Object.assign({}, channelMentions, channelNamesMap);
|
||||
@ -63,6 +63,7 @@ function makeMapStateToProps() {
|
||||
}
|
||||
|
||||
const connector = connect(makeMapStateToProps);
|
||||
|
||||
export type PropsFromRedux = ConnectedProps<typeof connector>;
|
||||
|
||||
export default connector(Markdown);
|
||||
|
@ -5,6 +5,8 @@ import React from 'react';
|
||||
|
||||
import type {PostImage, PostType} from '@mattermost/types/posts';
|
||||
|
||||
import type {HighlightWithoutNotificationKey} from 'mattermost-redux/selectors/entities/users';
|
||||
|
||||
import PostEditedIndicator from 'components/post_view/post_edited_indicator';
|
||||
|
||||
import type EmojiMap from 'utils/emoji_map';
|
||||
@ -53,6 +55,7 @@ export type OwnProps = {
|
||||
* An array of words that can be used to mention a user
|
||||
*/
|
||||
mentionKeys?: MentionKey[];
|
||||
highlightKeys?: HighlightWithoutNotificationKey[];
|
||||
|
||||
/**
|
||||
* Any extra props that should be passed into the image component
|
||||
@ -92,6 +95,7 @@ function Markdown({
|
||||
message = '',
|
||||
channelNamesMap,
|
||||
mentionKeys,
|
||||
highlightKeys,
|
||||
imageProps,
|
||||
channelId,
|
||||
hasPluginTooltips,
|
||||
@ -123,6 +127,7 @@ function Markdown({
|
||||
autolinkedUrlSchemes,
|
||||
siteURL,
|
||||
mentionKeys,
|
||||
highlightKeys,
|
||||
atMentions: true,
|
||||
channelNamesMap,
|
||||
proxyImages: hasImageProxy && proxyImages,
|
||||
|
@ -95,6 +95,7 @@ exports[`components/MoreDirectChannels should exclude deleted users if there is
|
||||
"desktop_sound": "false",
|
||||
"email": "false",
|
||||
"first_name": "false",
|
||||
"highlight_keys": "",
|
||||
"mark_unread": "mention",
|
||||
"mention_keys": "",
|
||||
"push": "none",
|
||||
@ -133,6 +134,7 @@ exports[`components/MoreDirectChannels should exclude deleted users if there is
|
||||
"desktop_sound": "false",
|
||||
"email": "false",
|
||||
"first_name": "false",
|
||||
"highlight_keys": "",
|
||||
"mark_unread": "mention",
|
||||
"mention_keys": "",
|
||||
"push": "none",
|
||||
@ -171,6 +173,7 @@ exports[`components/MoreDirectChannels should exclude deleted users if there is
|
||||
"desktop_sound": "false",
|
||||
"email": "false",
|
||||
"first_name": "false",
|
||||
"highlight_keys": "",
|
||||
"mark_unread": "mention",
|
||||
"mention_keys": "",
|
||||
"push": "none",
|
||||
@ -209,6 +212,7 @@ exports[`components/MoreDirectChannels should exclude deleted users if there is
|
||||
"desktop_sound": "false",
|
||||
"email": "false",
|
||||
"first_name": "false",
|
||||
"highlight_keys": "",
|
||||
"mark_unread": "mention",
|
||||
"mention_keys": "",
|
||||
"push": "none",
|
||||
@ -247,6 +251,7 @@ exports[`components/MoreDirectChannels should exclude deleted users if there is
|
||||
"desktop_sound": "false",
|
||||
"email": "false",
|
||||
"first_name": "false",
|
||||
"highlight_keys": "",
|
||||
"mark_unread": "mention",
|
||||
"mention_keys": "",
|
||||
"push": "none",
|
||||
@ -380,6 +385,7 @@ exports[`components/MoreDirectChannels should match snapshot 1`] = `
|
||||
"desktop_sound": "false",
|
||||
"email": "false",
|
||||
"first_name": "false",
|
||||
"highlight_keys": "",
|
||||
"mark_unread": "mention",
|
||||
"mention_keys": "",
|
||||
"push": "none",
|
||||
@ -418,6 +424,7 @@ exports[`components/MoreDirectChannels should match snapshot 1`] = `
|
||||
"desktop_sound": "false",
|
||||
"email": "false",
|
||||
"first_name": "false",
|
||||
"highlight_keys": "",
|
||||
"mark_unread": "mention",
|
||||
"mention_keys": "",
|
||||
"push": "none",
|
||||
@ -456,6 +463,7 @@ exports[`components/MoreDirectChannels should match snapshot 1`] = `
|
||||
"desktop_sound": "false",
|
||||
"email": "false",
|
||||
"first_name": "false",
|
||||
"highlight_keys": "",
|
||||
"mark_unread": "mention",
|
||||
"mention_keys": "",
|
||||
"push": "none",
|
||||
@ -499,6 +507,7 @@ exports[`components/MoreDirectChannels should match snapshot 1`] = `
|
||||
"desktop_sound": "false",
|
||||
"email": "false",
|
||||
"first_name": "false",
|
||||
"highlight_keys": "",
|
||||
"mark_unread": "mention",
|
||||
"mention_keys": "",
|
||||
"push": "none",
|
||||
@ -539,6 +548,7 @@ exports[`components/MoreDirectChannels should match snapshot 1`] = `
|
||||
"desktop_sound": "false",
|
||||
"email": "false",
|
||||
"first_name": "false",
|
||||
"highlight_keys": "",
|
||||
"mark_unread": "mention",
|
||||
"mention_keys": "",
|
||||
"push": "none",
|
||||
|
@ -1,14 +1,15 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {connect} from 'react-redux';
|
||||
import {type ConnectedProps, connect} from 'react-redux';
|
||||
|
||||
import type {Channel} from '@mattermost/types/channels';
|
||||
import type {Post} from '@mattermost/types/posts';
|
||||
|
||||
import {createSelector} from 'mattermost-redux/selectors/create_selector';
|
||||
import {getChannel} from 'mattermost-redux/selectors/entities/channels';
|
||||
import {getConfig} from 'mattermost-redux/selectors/entities/general';
|
||||
import {getSubscriptionProduct} from 'mattermost-redux/selectors/entities/cloud';
|
||||
import {getConfig, getLicense} from 'mattermost-redux/selectors/entities/general';
|
||||
import {
|
||||
getMyGroupMentionKeysForChannel,
|
||||
getMyGroupMentionKeys,
|
||||
@ -16,15 +17,16 @@ import {
|
||||
import {getBool} from 'mattermost-redux/selectors/entities/preferences';
|
||||
import {getCurrentTeam} from 'mattermost-redux/selectors/entities/teams';
|
||||
import {getCurrentTimezone} from 'mattermost-redux/selectors/entities/timezone';
|
||||
import {getCurrentUserMentionKeys} from 'mattermost-redux/selectors/entities/users';
|
||||
import {getCurrentUserMentionKeys, getHighlightWithoutNotificationKeys} from 'mattermost-redux/selectors/entities/users';
|
||||
|
||||
import {canManageMembers} from 'utils/channel_utils';
|
||||
import {Preferences} from 'utils/constants';
|
||||
import {isEnterpriseOrCloudOrSKUStarterFree} from 'utils/license_utils';
|
||||
import type {MentionKey} from 'utils/text_formatting';
|
||||
|
||||
import type {GlobalState} from 'types/store';
|
||||
|
||||
import PostMarkdown from './post_markdown';
|
||||
import PostMarkdown, {type OwnProps} from './post_markdown';
|
||||
|
||||
export function makeGetMentionKeysForPost(): (
|
||||
state: GlobalState,
|
||||
@ -54,18 +56,19 @@ export function makeGetMentionKeysForPost(): (
|
||||
);
|
||||
}
|
||||
|
||||
type OwnProps = {
|
||||
channelId: string;
|
||||
mentionKeys: MentionKey[];
|
||||
post?: Post;
|
||||
};
|
||||
|
||||
function makeMapStateToProps() {
|
||||
const getMentionKeysForPost = makeGetMentionKeysForPost();
|
||||
|
||||
return (state: GlobalState, ownProps: OwnProps) => {
|
||||
const channel = getChannel(state, ownProps.channelId);
|
||||
const currentTeam = getCurrentTeam(state) || {};
|
||||
|
||||
const license = getLicense(state);
|
||||
const subscriptionProduct = getSubscriptionProduct(state);
|
||||
|
||||
const config = getConfig(state);
|
||||
const isEnterpriseReady = config.BuildEnterpriseReady === 'true';
|
||||
|
||||
return {
|
||||
channel,
|
||||
currentTeam,
|
||||
@ -73,11 +76,18 @@ function makeMapStateToProps() {
|
||||
hasPluginTooltips: Boolean(state.plugins.components.LinkTooltip),
|
||||
isUserCanManageMembers: channel && canManageMembers(state, channel),
|
||||
mentionKeys: getMentionKeysForPost(state, ownProps.post, channel),
|
||||
highlightKeys: getHighlightWithoutNotificationKeys(state),
|
||||
isMilitaryTime: getBool(state, Preferences.CATEGORY_DISPLAY_SETTINGS, Preferences.USE_MILITARY_TIME, false),
|
||||
timezone: getCurrentTimezone(state),
|
||||
hideGuestTags: getConfig(state).HideGuestTags === 'true',
|
||||
isEnterpriseOrCloudOrSKUStarterFree: isEnterpriseOrCloudOrSKUStarterFree(license, subscriptionProduct, isEnterpriseReady),
|
||||
isEnterpriseReady,
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
export default connect(makeMapStateToProps)(PostMarkdown);
|
||||
const connector = connect(makeMapStateToProps);
|
||||
|
||||
export type PropsFromRedux = ConnectedProps<typeof connector>;
|
||||
|
||||
export default connector(PostMarkdown);
|
||||
|
@ -10,11 +10,14 @@ import {Posts} from 'mattermost-redux/constants';
|
||||
import {renderWithContext, screen} from 'tests/react_testing_utils';
|
||||
import {TestHelper} from 'utils/test_helper';
|
||||
|
||||
import type {PluginComponent} from 'types/store/plugins';
|
||||
|
||||
import PostMarkdown from './post_markdown';
|
||||
|
||||
describe('components/PostMarkdown', () => {
|
||||
const baseProps = {
|
||||
imageProps: {},
|
||||
imageProps: {} as Record<string, unknown>,
|
||||
pluginHooks: [],
|
||||
message: 'message',
|
||||
post: TestHelper.getPostMock(),
|
||||
mentionKeys: [{key: 'a'}, {key: 'b'}, {key: 'c'}],
|
||||
@ -22,6 +25,14 @@ describe('components/PostMarkdown', () => {
|
||||
channel: TestHelper.getChannelMock(),
|
||||
currentTeam: TestHelper.getTeamMock(),
|
||||
hideGuestTags: false,
|
||||
isMilitaryTime: false,
|
||||
timezone: '',
|
||||
highlightKeys: [],
|
||||
hasPluginTooltips: false,
|
||||
isUserCanManageMembers: false,
|
||||
isEnterpriseOrCloudOrSKUStarterFree: true,
|
||||
isEnterpriseReady: false,
|
||||
dispatch: jest.fn(),
|
||||
};
|
||||
|
||||
const state = {entities: {
|
||||
@ -224,7 +235,7 @@ describe('components/PostMarkdown', () => {
|
||||
return updatedMessage + '!';
|
||||
},
|
||||
},
|
||||
],
|
||||
] as PluginComponent[],
|
||||
};
|
||||
renderWithContext(<PostMarkdown {...props}/>, state);
|
||||
expect(screen.queryByText('world', {exact: true})).not.toBeInTheDocument();
|
||||
@ -258,7 +269,7 @@ describe('components/PostMarkdown', () => {
|
||||
return post.message + '!';
|
||||
},
|
||||
},
|
||||
],
|
||||
] as PluginComponent[],
|
||||
};
|
||||
renderWithContext(<PostMarkdown {...props}/>, state);
|
||||
expect(screen.queryByText('world', {exact: true})).not.toBeInTheDocument();
|
||||
|
@ -4,65 +4,45 @@
|
||||
import memoize from 'memoize-one';
|
||||
import React from 'react';
|
||||
|
||||
import type {Channel} from '@mattermost/types/channels';
|
||||
import type {Post} from '@mattermost/types/posts';
|
||||
import type {Team} from '@mattermost/types/teams';
|
||||
|
||||
import {Posts} from 'mattermost-redux/constants';
|
||||
|
||||
import Markdown from 'components/markdown';
|
||||
|
||||
import type {MentionKey, TextFormattingOptions} from 'utils/text_formatting';
|
||||
import type {TextFormattingOptions} from 'utils/text_formatting';
|
||||
|
||||
import {renderReminderSystemBotMessage, renderSystemMessage} from './system_message_helpers';
|
||||
|
||||
type Props = {
|
||||
import {type PropsFromRedux} from './index';
|
||||
|
||||
/*
|
||||
export type OwnProps = {
|
||||
|
||||
/**
|
||||
* Any extra props that should be passed into the image component
|
||||
*/
|
||||
imageProps?: Record<string, any>;
|
||||
imageProps?: Record<string, unknown>;
|
||||
|
||||
/*
|
||||
/**
|
||||
* The post text to be rendered
|
||||
*/
|
||||
message: string;
|
||||
|
||||
/*
|
||||
/**
|
||||
* The optional post for which this message is being rendered
|
||||
*/
|
||||
post?: Post;
|
||||
|
||||
/*
|
||||
* The id of the channel that this post is being rendered in
|
||||
*/
|
||||
channelId?: string;
|
||||
channel: Channel;
|
||||
currentTeam: Team;
|
||||
options?: TextFormattingOptions;
|
||||
pluginHooks?: Array<Record<string, any>>;
|
||||
|
||||
/**
|
||||
* Whether or not to place the LinkTooltip component inside links
|
||||
*/
|
||||
hasPluginTooltips?: boolean;
|
||||
isUserCanManageMembers?: boolean;
|
||||
mentionKeys: MentionKey[];
|
||||
channelId: string;
|
||||
|
||||
/**
|
||||
* Whether or not to render the post edited indicator
|
||||
* @default true
|
||||
*/
|
||||
showPostEditedIndicator?: boolean;
|
||||
options?: TextFormattingOptions;
|
||||
};
|
||||
|
||||
/**
|
||||
* Whether the user prefers Military time
|
||||
*/
|
||||
isMilitaryTime?: boolean;
|
||||
timezone?: string;
|
||||
|
||||
hideGuestTags: boolean;
|
||||
}
|
||||
type Props = PropsFromRedux & OwnProps;
|
||||
|
||||
export default class PostMarkdown extends React.PureComponent<Props> {
|
||||
static defaultProps = {
|
||||
@ -82,11 +62,10 @@ export default class PostMarkdown extends React.PureComponent<Props> {
|
||||
});
|
||||
|
||||
render() {
|
||||
let {message} = this.props;
|
||||
const {post, mentionKeys} = this.props;
|
||||
let message = this.props.message;
|
||||
|
||||
if (post) {
|
||||
const renderedSystemMessage = renderSystemMessage(post,
|
||||
if (this.props.post) {
|
||||
const renderedSystemMessage = renderSystemMessage(this.props.post,
|
||||
this.props.currentTeam,
|
||||
this.props.channel,
|
||||
this.props.hideGuestTags,
|
||||
@ -98,39 +77,45 @@ export default class PostMarkdown extends React.PureComponent<Props> {
|
||||
}
|
||||
}
|
||||
|
||||
if (post && post.type === Posts.POST_TYPES.REMINDER) {
|
||||
const renderedSystemBotMessage = renderReminderSystemBotMessage(post, this.props.currentTeam);
|
||||
if (this.props.post && this.props.post.type === Posts.POST_TYPES.REMINDER) {
|
||||
const renderedSystemBotMessage = renderReminderSystemBotMessage(this.props.post, this.props.currentTeam);
|
||||
return <div>{renderedSystemBotMessage}</div>;
|
||||
}
|
||||
|
||||
// Proxy images if we have an image proxy and the server hasn't already rewritten the post's image URLs.
|
||||
const proxyImages = !post || !post.message_source || post.message === post.message_source;
|
||||
const channelNamesMap = post && post.props && post.props.channel_mentions;
|
||||
// Proxy images if we have an image proxy and the server hasn't already rewritten the this.props.post's image URLs.
|
||||
const proxyImages = !this.props.post || !this.props.post.message_source || this.props.post.message === this.props.post.message_source;
|
||||
const channelNamesMap = this.props.post && this.props.post.props && this.props.post.props.channel_mentions;
|
||||
|
||||
this.props.pluginHooks?.forEach((o) => {
|
||||
if (o && o.hook && post) {
|
||||
message = o.hook(post, message);
|
||||
if (o && o.hook && this.props.post) {
|
||||
message = o.hook(this.props.post, message);
|
||||
}
|
||||
});
|
||||
|
||||
let mentionHighlight = this.props.options?.mentionHighlight;
|
||||
if (post && post.props) {
|
||||
mentionHighlight = !post.props.mentionHighlightDisabled;
|
||||
if (this.props.post && this.props.post.props) {
|
||||
mentionHighlight = !this.props.post.props.mentionHighlightDisabled;
|
||||
}
|
||||
|
||||
const options = this.getOptions(
|
||||
this.props.options,
|
||||
post?.props?.disable_group_highlight === true,
|
||||
this.props.post?.props?.disable_group_highlight === true,
|
||||
mentionHighlight,
|
||||
post?.edit_at,
|
||||
this.props.post?.edit_at,
|
||||
);
|
||||
|
||||
let highlightKeys;
|
||||
if (!this.props.isEnterpriseOrCloudOrSKUStarterFree && this.props.isEnterpriseReady) {
|
||||
highlightKeys = this.props.highlightKeys;
|
||||
}
|
||||
|
||||
return (
|
||||
<Markdown
|
||||
imageProps={this.props.imageProps}
|
||||
message={message}
|
||||
proxyImages={proxyImages}
|
||||
mentionKeys={mentionKeys}
|
||||
mentionKeys={this.props.mentionKeys}
|
||||
highlightKeys={highlightKeys}
|
||||
options={options}
|
||||
channelNamesMap={channelNamesMap}
|
||||
hasPluginTooltips={this.props.hasPluginTooltips}
|
||||
|
@ -19,7 +19,6 @@ exports[`components/post_view/PostAttachment should match snapshot 1`] = `
|
||||
"onImageLoaded": [Function],
|
||||
}
|
||||
}
|
||||
mentionKeys={Array []}
|
||||
message="post message"
|
||||
options={Object {}}
|
||||
post={
|
||||
@ -57,7 +56,6 @@ exports[`components/post_view/PostAttachment should match snapshot, on Show Less
|
||||
"onImageLoaded": [Function],
|
||||
}
|
||||
}
|
||||
mentionKeys={Array []}
|
||||
message="post message"
|
||||
options={Object {}}
|
||||
post={
|
||||
@ -95,7 +93,6 @@ exports[`components/post_view/PostAttachment should match snapshot, on Show More
|
||||
"onImageLoaded": [Function],
|
||||
}
|
||||
}
|
||||
mentionKeys={Array []}
|
||||
message="post message"
|
||||
options={Object {}}
|
||||
post={
|
||||
@ -151,7 +148,6 @@ exports[`components/post_view/PostAttachment should match snapshot, on edited po
|
||||
"onImageLoaded": [Function],
|
||||
}
|
||||
}
|
||||
mentionKeys={Array []}
|
||||
message="post message"
|
||||
options={Object {}}
|
||||
post={
|
||||
@ -190,7 +186,6 @@ exports[`components/post_view/PostAttachment should match snapshot, on ephemeral
|
||||
"onImageLoaded": [Function],
|
||||
}
|
||||
}
|
||||
mentionKeys={Array []}
|
||||
message="post message"
|
||||
options={Object {}}
|
||||
post={
|
||||
|
@ -158,7 +158,6 @@ export default class PostMessageView extends React.PureComponent<Props, State> {
|
||||
options={options}
|
||||
post={post}
|
||||
channelId={post.channel_id}
|
||||
mentionKeys={[]}
|
||||
showPostEditedIndicator={this.props.showPostEditedIndicator}
|
||||
/>
|
||||
</div>
|
||||
|
@ -139,6 +139,7 @@ exports[`components/ProfilePopover should disable start call button when user is
|
||||
"desktop_sound": "false",
|
||||
"email": "false",
|
||||
"first_name": "false",
|
||||
"highlight_keys": "",
|
||||
"mark_unread": "mention",
|
||||
"mention_keys": "",
|
||||
"push": "none",
|
||||
@ -197,7 +198,7 @@ exports[`components/ProfilePopover should disable start call button when user is
|
||||
}
|
||||
>
|
||||
<div>
|
||||
<Connect(ToggleModalButton)
|
||||
<ToggleModalButton
|
||||
ariaLabel="Add to a Channel"
|
||||
className="btn icon-btn"
|
||||
dialogProps={
|
||||
@ -227,6 +228,7 @@ exports[`components/ProfilePopover should disable start call button when user is
|
||||
"desktop_sound": "false",
|
||||
"email": "false",
|
||||
"first_name": "false",
|
||||
"highlight_keys": "",
|
||||
"mark_unread": "mention",
|
||||
"mention_keys": "",
|
||||
"push": "none",
|
||||
@ -259,7 +261,7 @@ exports[`components/ProfilePopover should disable start call button when user is
|
||||
aria-label="Add User to Channel Icon"
|
||||
size={18}
|
||||
/>
|
||||
</Connect(ToggleModalButton)>
|
||||
</ToggleModalButton>
|
||||
</div>
|
||||
</OverlayTrigger>
|
||||
<OverlayTrigger
|
||||
@ -324,6 +326,7 @@ exports[`components/ProfilePopover should disable start call button when user is
|
||||
"desktop_sound": "false",
|
||||
"email": "false",
|
||||
"first_name": "false",
|
||||
"highlight_keys": "",
|
||||
"mark_unread": "mention",
|
||||
"mention_keys": "",
|
||||
"push": "none",
|
||||
@ -487,6 +490,7 @@ exports[`components/ProfilePopover should hide add-to-channel option if not on t
|
||||
"desktop_sound": "false",
|
||||
"email": "false",
|
||||
"first_name": "false",
|
||||
"highlight_keys": "",
|
||||
"mark_unread": "mention",
|
||||
"mention_keys": "",
|
||||
"push": "none",
|
||||
@ -592,6 +596,7 @@ exports[`components/ProfilePopover should hide add-to-channel option if not on t
|
||||
"desktop_sound": "false",
|
||||
"email": "false",
|
||||
"first_name": "false",
|
||||
"highlight_keys": "",
|
||||
"mark_unread": "mention",
|
||||
"mention_keys": "",
|
||||
"push": "none",
|
||||
@ -755,6 +760,7 @@ exports[`components/ProfilePopover should match snapshot 1`] = `
|
||||
"desktop_sound": "false",
|
||||
"email": "false",
|
||||
"first_name": "false",
|
||||
"highlight_keys": "",
|
||||
"mark_unread": "mention",
|
||||
"mention_keys": "",
|
||||
"push": "none",
|
||||
@ -813,7 +819,7 @@ exports[`components/ProfilePopover should match snapshot 1`] = `
|
||||
}
|
||||
>
|
||||
<div>
|
||||
<Connect(ToggleModalButton)
|
||||
<ToggleModalButton
|
||||
ariaLabel="Add to a Channel"
|
||||
className="btn icon-btn"
|
||||
dialogProps={
|
||||
@ -843,6 +849,7 @@ exports[`components/ProfilePopover should match snapshot 1`] = `
|
||||
"desktop_sound": "false",
|
||||
"email": "false",
|
||||
"first_name": "false",
|
||||
"highlight_keys": "",
|
||||
"mark_unread": "mention",
|
||||
"mention_keys": "",
|
||||
"push": "none",
|
||||
@ -875,7 +882,7 @@ exports[`components/ProfilePopover should match snapshot 1`] = `
|
||||
aria-label="Add User to Channel Icon"
|
||||
size={18}
|
||||
/>
|
||||
</Connect(ToggleModalButton)>
|
||||
</ToggleModalButton>
|
||||
</div>
|
||||
</OverlayTrigger>
|
||||
<Connect(ProfilePopoverCallButton)
|
||||
@ -944,6 +951,7 @@ exports[`components/ProfilePopover should match snapshot 1`] = `
|
||||
"desktop_sound": "false",
|
||||
"email": "false",
|
||||
"first_name": "false",
|
||||
"highlight_keys": "",
|
||||
"mark_unread": "mention",
|
||||
"mention_keys": "",
|
||||
"push": "none",
|
||||
@ -1122,6 +1130,7 @@ exports[`components/ProfilePopover should match snapshot for shared user 1`] = `
|
||||
"desktop_sound": "false",
|
||||
"email": "false",
|
||||
"first_name": "false",
|
||||
"highlight_keys": "",
|
||||
"mark_unread": "mention",
|
||||
"mention_keys": "",
|
||||
"push": "none",
|
||||
@ -1181,7 +1190,7 @@ exports[`components/ProfilePopover should match snapshot for shared user 1`] = `
|
||||
}
|
||||
>
|
||||
<div>
|
||||
<Connect(ToggleModalButton)
|
||||
<ToggleModalButton
|
||||
ariaLabel="Add to a Channel"
|
||||
className="btn icon-btn"
|
||||
dialogProps={
|
||||
@ -1211,6 +1220,7 @@ exports[`components/ProfilePopover should match snapshot for shared user 1`] = `
|
||||
"desktop_sound": "false",
|
||||
"email": "false",
|
||||
"first_name": "false",
|
||||
"highlight_keys": "",
|
||||
"mark_unread": "mention",
|
||||
"mention_keys": "",
|
||||
"push": "none",
|
||||
@ -1244,7 +1254,7 @@ exports[`components/ProfilePopover should match snapshot for shared user 1`] = `
|
||||
aria-label="Add User to Channel Icon"
|
||||
size={18}
|
||||
/>
|
||||
</Connect(ToggleModalButton)>
|
||||
</ToggleModalButton>
|
||||
</div>
|
||||
</OverlayTrigger>
|
||||
<Connect(ProfilePopoverCallButton)
|
||||
@ -1313,6 +1323,7 @@ exports[`components/ProfilePopover should match snapshot for shared user 1`] = `
|
||||
"desktop_sound": "false",
|
||||
"email": "false",
|
||||
"first_name": "false",
|
||||
"highlight_keys": "",
|
||||
"mark_unread": "mention",
|
||||
"mention_keys": "",
|
||||
"push": "none",
|
||||
@ -1477,6 +1488,7 @@ exports[`components/ProfilePopover should match snapshot when calls are disabled
|
||||
"desktop_sound": "false",
|
||||
"email": "false",
|
||||
"first_name": "false",
|
||||
"highlight_keys": "",
|
||||
"mark_unread": "mention",
|
||||
"mention_keys": "",
|
||||
"push": "none",
|
||||
@ -1535,7 +1547,7 @@ exports[`components/ProfilePopover should match snapshot when calls are disabled
|
||||
}
|
||||
>
|
||||
<div>
|
||||
<Connect(ToggleModalButton)
|
||||
<ToggleModalButton
|
||||
ariaLabel="Add to a Channel"
|
||||
className="btn icon-btn"
|
||||
dialogProps={
|
||||
@ -1565,6 +1577,7 @@ exports[`components/ProfilePopover should match snapshot when calls are disabled
|
||||
"desktop_sound": "false",
|
||||
"email": "false",
|
||||
"first_name": "false",
|
||||
"highlight_keys": "",
|
||||
"mark_unread": "mention",
|
||||
"mention_keys": "",
|
||||
"push": "none",
|
||||
@ -1597,7 +1610,7 @@ exports[`components/ProfilePopover should match snapshot when calls are disabled
|
||||
aria-label="Add User to Channel Icon"
|
||||
size={18}
|
||||
/>
|
||||
</Connect(ToggleModalButton)>
|
||||
</ToggleModalButton>
|
||||
</div>
|
||||
</OverlayTrigger>
|
||||
</div>
|
||||
@ -1632,6 +1645,7 @@ exports[`components/ProfilePopover should match snapshot when calls are disabled
|
||||
"desktop_sound": "false",
|
||||
"email": "false",
|
||||
"first_name": "false",
|
||||
"highlight_keys": "",
|
||||
"mark_unread": "mention",
|
||||
"mention_keys": "",
|
||||
"push": "none",
|
||||
@ -1802,6 +1816,7 @@ exports[`components/ProfilePopover should match snapshot with custom status 1`]
|
||||
"desktop_sound": "false",
|
||||
"email": "false",
|
||||
"first_name": "false",
|
||||
"highlight_keys": "",
|
||||
"mark_unread": "mention",
|
||||
"mention_keys": "",
|
||||
"push": "none",
|
||||
@ -1898,7 +1913,7 @@ exports[`components/ProfilePopover should match snapshot with custom status 1`]
|
||||
}
|
||||
>
|
||||
<div>
|
||||
<Connect(ToggleModalButton)
|
||||
<ToggleModalButton
|
||||
ariaLabel="Add to a Channel"
|
||||
className="btn icon-btn"
|
||||
dialogProps={
|
||||
@ -1928,6 +1943,7 @@ exports[`components/ProfilePopover should match snapshot with custom status 1`]
|
||||
"desktop_sound": "false",
|
||||
"email": "false",
|
||||
"first_name": "false",
|
||||
"highlight_keys": "",
|
||||
"mark_unread": "mention",
|
||||
"mention_keys": "",
|
||||
"push": "none",
|
||||
@ -1960,7 +1976,7 @@ exports[`components/ProfilePopover should match snapshot with custom status 1`]
|
||||
aria-label="Add User to Channel Icon"
|
||||
size={18}
|
||||
/>
|
||||
</Connect(ToggleModalButton)>
|
||||
</ToggleModalButton>
|
||||
</div>
|
||||
</OverlayTrigger>
|
||||
<Connect(ProfilePopoverCallButton)
|
||||
@ -2029,6 +2045,7 @@ exports[`components/ProfilePopover should match snapshot with custom status 1`]
|
||||
"desktop_sound": "false",
|
||||
"email": "false",
|
||||
"first_name": "false",
|
||||
"highlight_keys": "",
|
||||
"mark_unread": "mention",
|
||||
"mention_keys": "",
|
||||
"push": "none",
|
||||
@ -2199,6 +2216,7 @@ exports[`components/ProfilePopover should match snapshot with custom status expi
|
||||
"desktop_sound": "false",
|
||||
"email": "false",
|
||||
"first_name": "false",
|
||||
"highlight_keys": "",
|
||||
"mark_unread": "mention",
|
||||
"mention_keys": "",
|
||||
"push": "none",
|
||||
@ -2257,7 +2275,7 @@ exports[`components/ProfilePopover should match snapshot with custom status expi
|
||||
}
|
||||
>
|
||||
<div>
|
||||
<Connect(ToggleModalButton)
|
||||
<ToggleModalButton
|
||||
ariaLabel="Add to a Channel"
|
||||
className="btn icon-btn"
|
||||
dialogProps={
|
||||
@ -2287,6 +2305,7 @@ exports[`components/ProfilePopover should match snapshot with custom status expi
|
||||
"desktop_sound": "false",
|
||||
"email": "false",
|
||||
"first_name": "false",
|
||||
"highlight_keys": "",
|
||||
"mark_unread": "mention",
|
||||
"mention_keys": "",
|
||||
"push": "none",
|
||||
@ -2319,7 +2338,7 @@ exports[`components/ProfilePopover should match snapshot with custom status expi
|
||||
aria-label="Add User to Channel Icon"
|
||||
size={18}
|
||||
/>
|
||||
</Connect(ToggleModalButton)>
|
||||
</ToggleModalButton>
|
||||
</div>
|
||||
</OverlayTrigger>
|
||||
<Connect(ProfilePopoverCallButton)
|
||||
@ -2388,6 +2407,7 @@ exports[`components/ProfilePopover should match snapshot with custom status expi
|
||||
"desktop_sound": "false",
|
||||
"email": "false",
|
||||
"first_name": "false",
|
||||
"highlight_keys": "",
|
||||
"mark_unread": "mention",
|
||||
"mention_keys": "",
|
||||
"push": "none",
|
||||
@ -2551,6 +2571,7 @@ exports[`components/ProfilePopover should match snapshot with custom status not
|
||||
"desktop_sound": "false",
|
||||
"email": "false",
|
||||
"first_name": "false",
|
||||
"highlight_keys": "",
|
||||
"mark_unread": "mention",
|
||||
"mention_keys": "",
|
||||
"push": "none",
|
||||
@ -2676,6 +2697,7 @@ exports[`components/ProfilePopover should match snapshot with custom status not
|
||||
"desktop_sound": "false",
|
||||
"email": "false",
|
||||
"first_name": "false",
|
||||
"highlight_keys": "",
|
||||
"mark_unread": "mention",
|
||||
"mention_keys": "",
|
||||
"push": "none",
|
||||
@ -2839,6 +2861,7 @@ exports[`components/ProfilePopover should match snapshot with last active displa
|
||||
"desktop_sound": "false",
|
||||
"email": "false",
|
||||
"first_name": "false",
|
||||
"highlight_keys": "",
|
||||
"mark_unread": "mention",
|
||||
"mention_keys": "",
|
||||
"push": "none",
|
||||
@ -2897,7 +2920,7 @@ exports[`components/ProfilePopover should match snapshot with last active displa
|
||||
}
|
||||
>
|
||||
<div>
|
||||
<Connect(ToggleModalButton)
|
||||
<ToggleModalButton
|
||||
ariaLabel="Add to a Channel"
|
||||
className="btn icon-btn"
|
||||
dialogProps={
|
||||
@ -2927,6 +2950,7 @@ exports[`components/ProfilePopover should match snapshot with last active displa
|
||||
"desktop_sound": "false",
|
||||
"email": "false",
|
||||
"first_name": "false",
|
||||
"highlight_keys": "",
|
||||
"mark_unread": "mention",
|
||||
"mention_keys": "",
|
||||
"push": "none",
|
||||
@ -2959,7 +2983,7 @@ exports[`components/ProfilePopover should match snapshot with last active displa
|
||||
aria-label="Add User to Channel Icon"
|
||||
size={18}
|
||||
/>
|
||||
</Connect(ToggleModalButton)>
|
||||
</ToggleModalButton>
|
||||
</div>
|
||||
</OverlayTrigger>
|
||||
<Connect(ProfilePopoverCallButton)
|
||||
@ -3028,6 +3052,7 @@ exports[`components/ProfilePopover should match snapshot with last active displa
|
||||
"desktop_sound": "false",
|
||||
"email": "false",
|
||||
"first_name": "false",
|
||||
"highlight_keys": "",
|
||||
"mark_unread": "mention",
|
||||
"mention_keys": "",
|
||||
"push": "none",
|
||||
@ -3166,6 +3191,7 @@ exports[`components/ProfilePopover should match snapshot with no last active dis
|
||||
"desktop_sound": "false",
|
||||
"email": "false",
|
||||
"first_name": "false",
|
||||
"highlight_keys": "",
|
||||
"mark_unread": "mention",
|
||||
"mention_keys": "",
|
||||
"push": "none",
|
||||
@ -3224,7 +3250,7 @@ exports[`components/ProfilePopover should match snapshot with no last active dis
|
||||
}
|
||||
>
|
||||
<div>
|
||||
<Connect(ToggleModalButton)
|
||||
<ToggleModalButton
|
||||
ariaLabel="Add to a Channel"
|
||||
className="btn icon-btn"
|
||||
dialogProps={
|
||||
@ -3254,6 +3280,7 @@ exports[`components/ProfilePopover should match snapshot with no last active dis
|
||||
"desktop_sound": "false",
|
||||
"email": "false",
|
||||
"first_name": "false",
|
||||
"highlight_keys": "",
|
||||
"mark_unread": "mention",
|
||||
"mention_keys": "",
|
||||
"push": "none",
|
||||
@ -3286,7 +3313,7 @@ exports[`components/ProfilePopover should match snapshot with no last active dis
|
||||
aria-label="Add User to Channel Icon"
|
||||
size={18}
|
||||
/>
|
||||
</Connect(ToggleModalButton)>
|
||||
</ToggleModalButton>
|
||||
</div>
|
||||
</OverlayTrigger>
|
||||
<Connect(ProfilePopoverCallButton)
|
||||
@ -3355,6 +3382,7 @@ exports[`components/ProfilePopover should match snapshot with no last active dis
|
||||
"desktop_sound": "false",
|
||||
"email": "false",
|
||||
"first_name": "false",
|
||||
"highlight_keys": "",
|
||||
"mark_unread": "mention",
|
||||
"mention_keys": "",
|
||||
"push": "none",
|
||||
@ -3468,6 +3496,7 @@ exports[`components/ProfilePopover should show the start call button when isCall
|
||||
"desktop_sound": "false",
|
||||
"email": "false",
|
||||
"first_name": "false",
|
||||
"highlight_keys": "",
|
||||
"mark_unread": "mention",
|
||||
"mention_keys": "",
|
||||
"push": "none",
|
||||
@ -3602,6 +3631,7 @@ exports[`components/ProfilePopover should show the start call button when isCall
|
||||
"desktop_sound": "false",
|
||||
"email": "false",
|
||||
"first_name": "false",
|
||||
"highlight_keys": "",
|
||||
"mark_unread": "mention",
|
||||
"mention_keys": "",
|
||||
"push": "none",
|
||||
@ -3946,6 +3976,7 @@ exports[`components/ProfilePopover should show the start call button when isCall
|
||||
"desktop_sound": "false",
|
||||
"email": "false",
|
||||
"first_name": "false",
|
||||
"highlight_keys": "",
|
||||
"mark_unread": "mention",
|
||||
"mention_keys": "",
|
||||
"push": "none",
|
||||
@ -4027,6 +4058,7 @@ exports[`components/ProfilePopover should show the start call button when isCall
|
||||
"desktop_sound": "false",
|
||||
"email": "false",
|
||||
"first_name": "false",
|
||||
"highlight_keys": "",
|
||||
"mark_unread": "mention",
|
||||
"mention_keys": "",
|
||||
"push": "none",
|
||||
@ -4167,7 +4199,7 @@ exports[`components/ProfilePopover should show the start call button when isCall
|
||||
onMouseOut={[Function]}
|
||||
onMouseOver={[Function]}
|
||||
>
|
||||
<Connect(ToggleModalButton)
|
||||
<ToggleModalButton
|
||||
ariaLabel="Add to a Channel"
|
||||
className="btn icon-btn"
|
||||
dialogProps={
|
||||
@ -4197,6 +4229,7 @@ exports[`components/ProfilePopover should show the start call button when isCall
|
||||
"desktop_sound": "false",
|
||||
"email": "false",
|
||||
"first_name": "false",
|
||||
"highlight_keys": "",
|
||||
"mark_unread": "mention",
|
||||
"mention_keys": "",
|
||||
"push": "none",
|
||||
@ -4225,90 +4258,27 @@ exports[`components/ProfilePopover should show the start call button when isCall
|
||||
modalId="add_user_to_channel"
|
||||
onClick={[Function]}
|
||||
>
|
||||
<ToggleModalButton
|
||||
actions={
|
||||
Object {
|
||||
"openModal": [Function],
|
||||
}
|
||||
}
|
||||
ariaLabel="Add to a Channel"
|
||||
className="btn icon-btn"
|
||||
dialogProps={
|
||||
Object {
|
||||
"onExited": [Function],
|
||||
"user": Object {
|
||||
"auth_service": "",
|
||||
"bot_description": "",
|
||||
"create_at": 0,
|
||||
"delete_at": 0,
|
||||
"email": "",
|
||||
"first_name": "",
|
||||
"id": "user_id",
|
||||
"is_bot": false,
|
||||
"last_activity_at": 0,
|
||||
"last_name": "",
|
||||
"last_password_update": 0,
|
||||
"last_picture_update": 0,
|
||||
"locale": "",
|
||||
"mfa_active": false,
|
||||
"nickname": "",
|
||||
"notify_props": Object {
|
||||
"calls_desktop_sound": "true",
|
||||
"channel": "false",
|
||||
"comments": "never",
|
||||
"desktop": "default",
|
||||
"desktop_sound": "false",
|
||||
"email": "false",
|
||||
"first_name": "false",
|
||||
"mark_unread": "mention",
|
||||
"mention_keys": "",
|
||||
"push": "none",
|
||||
"push_status": "offline",
|
||||
},
|
||||
"password": "",
|
||||
"position": "",
|
||||
"props": Object {},
|
||||
"roles": "",
|
||||
"terms_of_service_create_at": 0,
|
||||
"terms_of_service_id": "",
|
||||
"update_at": 0,
|
||||
"username": "some_username",
|
||||
},
|
||||
}
|
||||
}
|
||||
dialogType={
|
||||
Object {
|
||||
"$$typeof": Symbol(react.memo),
|
||||
"WrappedComponent": [Function],
|
||||
"compare": null,
|
||||
"type": [Function],
|
||||
}
|
||||
}
|
||||
<button
|
||||
aria-label="Add to a Channel dialog"
|
||||
className="style--none btn icon-btn"
|
||||
id="addToChannelButton"
|
||||
modalId="add_user_to_channel"
|
||||
onClick={[Function]}
|
||||
>
|
||||
<button
|
||||
aria-label="Add to a Channel dialog"
|
||||
className="style--none btn icon-btn"
|
||||
id="addToChannelButton"
|
||||
onClick={[Function]}
|
||||
<AccountPlusOutlineIcon
|
||||
aria-label="Add User to Channel Icon"
|
||||
size={18}
|
||||
>
|
||||
<AccountPlusOutlineIcon
|
||||
<svg
|
||||
aria-label="Add User to Channel Icon"
|
||||
size={18}
|
||||
fill="currentColor"
|
||||
height={18}
|
||||
version="1.1"
|
||||
viewBox="0 0 24 24"
|
||||
width={18}
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<svg
|
||||
aria-label="Add User to Channel Icon"
|
||||
fill="currentColor"
|
||||
height={18}
|
||||
version="1.1"
|
||||
viewBox="0 0 24 24"
|
||||
width={18}
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M5.033,19C4.463,19,4,18.528,4,17.945c0-0.105,0.015-0.208,0.044-0.306c0.058-0.195,0.171-0.37,0.324-0.501c0.076-0.066,0.163-0.121,0.258-0.162l3.093-1.857C8.387,15.52,9.151,15.75,10,15.75s1.613-0.23,2.28-0.631l0.352,0.211
|
||||
<path
|
||||
d="M5.033,19C4.463,19,4,18.528,4,17.945c0-0.105,0.015-0.208,0.044-0.306c0.058-0.195,0.171-0.37,0.324-0.501c0.076-0.066,0.163-0.121,0.258-0.162l3.093-1.857C8.387,15.52,9.151,15.75,10,15.75s1.613-0.23,2.28-0.631l0.352,0.211
|
||||
c0.302-0.606,0.701-1.156,1.181-1.624l-0.006-0.004c1.045-1.391,1.622-3.311,1.622-5.203C15.429,5.21,13.247,3,10,3
|
||||
S4.571,5.21,4.571,8.5c0,1.891,0.577,3.812,1.622,5.203l-2.515,1.51C2.653,15.727,2,16.783,2,17.945C2,19.63,3.361,21,5.033,21
|
||||
h7.776c-0.352-0.608-0.599-1.282-0.719-2H5.033z M10,5c1.894,0,3.429,1.084,3.429,3.5c0,1.482-0.485,3.117-1.353,4.163
|
||||
@ -4316,12 +4286,11 @@ exports[`components/ProfilePopover should show the start call button when isCall
|
||||
c-0.226-0.119-0.437-0.272-0.633-0.453c-0.116-0.108-0.225-0.229-0.331-0.356c-0.072-0.086-0.143-0.174-0.209-0.268
|
||||
C7.55,12.164,7.403,11.91,7.272,11.64c-0.194-0.406-0.351-0.846-0.466-1.3C6.729,10.037,6.67,9.728,6.631,9.419
|
||||
c-0.04-0.308-0.06-0.617-0.06-0.919C6.571,6.084,8.106,5,10,5z M17,14h2v3h3v2h-3v3h-2v-3h-3v-2h3V14"
|
||||
/>
|
||||
</svg>
|
||||
</AccountPlusOutlineIcon>
|
||||
</button>
|
||||
</ToggleModalButton>
|
||||
</Connect(ToggleModalButton)>
|
||||
/>
|
||||
</svg>
|
||||
</AccountPlusOutlineIcon>
|
||||
</button>
|
||||
</ToggleModalButton>
|
||||
</div>
|
||||
</OverlayTrigger>
|
||||
</OverlayTrigger>
|
||||
@ -4430,6 +4399,7 @@ exports[`components/ProfilePopover should show the start call button when isCall
|
||||
"desktop_sound": "false",
|
||||
"email": "false",
|
||||
"first_name": "false",
|
||||
"highlight_keys": "",
|
||||
"mark_unread": "mention",
|
||||
"mention_keys": "",
|
||||
"push": "none",
|
||||
@ -4511,6 +4481,7 @@ exports[`components/ProfilePopover should show the start call button when isCall
|
||||
"desktop_sound": "false",
|
||||
"email": "false",
|
||||
"first_name": "false",
|
||||
"highlight_keys": "",
|
||||
"mark_unread": "mention",
|
||||
"mention_keys": "",
|
||||
"push": "none",
|
||||
|
@ -5,7 +5,7 @@ import React from 'react';
|
||||
import type {ReactNode, RefObject} from 'react';
|
||||
|
||||
import SettingItemMin from 'components/setting_item_min';
|
||||
import type SettingItemMinComponent from 'components/setting_item_min/setting_item_min';
|
||||
import type SettingItemMinComponent from 'components/setting_item_min';
|
||||
|
||||
type Props = {
|
||||
|
||||
@ -27,36 +27,33 @@ type Props = {
|
||||
/**
|
||||
* The setting UI when it is maximized (open)
|
||||
*/
|
||||
max: ReactNode | null;
|
||||
max: ReactNode;
|
||||
|
||||
// Props to pass through for SettingItemMin
|
||||
updateSection: (section: string) => void;
|
||||
title?: ReactNode;
|
||||
disableOpen?: boolean;
|
||||
isDisabled?: boolean;
|
||||
describe?: ReactNode;
|
||||
|
||||
/**
|
||||
* Replacement in place of edit button when the setting (in collapsed mode) is disabled
|
||||
*/
|
||||
collapsedEditButtonWhenDisabled?: ReactNode;
|
||||
}
|
||||
|
||||
export default class SettingItem extends React.PureComponent<Props> {
|
||||
minRef: RefObject<SettingItemMinComponent>;
|
||||
|
||||
static defaultProps = {
|
||||
infoPosition: 'bottom',
|
||||
saving: false,
|
||||
section: '',
|
||||
containerStyle: '',
|
||||
};
|
||||
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
|
||||
this.minRef = React.createRef();
|
||||
}
|
||||
|
||||
focusEditButton(): void {
|
||||
this.minRef.current?.focus();
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps: Props) {
|
||||
if (prevProps.active && !this.props.active && this.props.areAllSectionsInactive) {
|
||||
this.focusEditButton();
|
||||
// We want to bring back focus to the edit button when the section is opened and then closed along with all sections are closed
|
||||
if (!this.props.active && prevProps.active && this.props.areAllSectionsInactive) {
|
||||
this.minRef.current?.focus();
|
||||
}
|
||||
}
|
||||
|
||||
@ -67,12 +64,13 @@ export default class SettingItem extends React.PureComponent<Props> {
|
||||
|
||||
return (
|
||||
<SettingItemMin
|
||||
ref={this.minRef}
|
||||
title={this.props.title}
|
||||
updateSection={this.props.updateSection}
|
||||
describe={this.props.describe}
|
||||
section={this.props.section}
|
||||
disableOpen={this.props.disableOpen}
|
||||
ref={this.minRef}
|
||||
isDisabled={this.props.isDisabled}
|
||||
collapsedEditButtonWhenDisabled={this.props.collapsedEditButtonWhenDisabled}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
@ -36,26 +36,26 @@ describe('components/SettingItemMin', () => {
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('should have called updateSection on handleUpdateSection with section', () => {
|
||||
test('should have called updateSection on handleClick with section', () => {
|
||||
const updateSection = jest.fn();
|
||||
const props = {...baseProps, updateSection};
|
||||
const wrapper = shallow<SettingItemMin>(
|
||||
<SettingItemMin {...props}/>,
|
||||
);
|
||||
|
||||
wrapper.instance().handleUpdateSection({preventDefault: jest.fn()} as any);
|
||||
wrapper.instance().handleClick({preventDefault: jest.fn()} as any);
|
||||
expect(updateSection).toHaveBeenCalled();
|
||||
expect(updateSection).toHaveBeenCalledWith('section');
|
||||
});
|
||||
|
||||
test('should have called updateSection on handleUpdateSection with empty string', () => {
|
||||
test('should have called updateSection on handleClick with empty string', () => {
|
||||
const updateSection = jest.fn();
|
||||
const props = {...baseProps, updateSection, section: ''};
|
||||
const wrapper = shallow<SettingItemMin>(
|
||||
<SettingItemMin {...props}/>,
|
||||
);
|
||||
|
||||
wrapper.instance().handleUpdateSection({preventDefault: jest.fn()} as any);
|
||||
wrapper.instance().handleClick({preventDefault: jest.fn()} as any);
|
||||
expect(updateSection).toHaveBeenCalled();
|
||||
expect(updateSection).toHaveBeenCalledWith('');
|
||||
});
|
118
webapp/channels/src/components/setting_item_min.tsx
Normal file
118
webapp/channels/src/components/setting_item_min.tsx
Normal file
@ -0,0 +1,118 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import classNames from 'classnames';
|
||||
import React, {type ReactNode, type MouseEvent} from 'react';
|
||||
import {FormattedMessage} from 'react-intl';
|
||||
|
||||
import EditIcon from 'components/widgets/icons/fa_edit_icon';
|
||||
|
||||
import {a11yFocus} from 'utils/utils';
|
||||
|
||||
interface Props {
|
||||
|
||||
/**
|
||||
* Settings title
|
||||
*/
|
||||
title: ReactNode;
|
||||
|
||||
/**
|
||||
* Option to disable opening the setting
|
||||
*/
|
||||
isDisabled?: boolean;
|
||||
|
||||
/**
|
||||
* Settings or tab section
|
||||
*/
|
||||
section: string;
|
||||
|
||||
/**
|
||||
* Function to update section
|
||||
*/
|
||||
updateSection: (section: string) => void;
|
||||
|
||||
/**
|
||||
* Settings description
|
||||
*/
|
||||
describe?: ReactNode;
|
||||
|
||||
/**
|
||||
* Replacement in place of edit button when the setting (in collapsed mode) is disabled
|
||||
*/
|
||||
collapsedEditButtonWhenDisabled?: ReactNode;
|
||||
}
|
||||
|
||||
export default class SettingItemMin extends React.PureComponent<Props> {
|
||||
private edit: HTMLButtonElement | null = null;
|
||||
|
||||
focus() {
|
||||
a11yFocus(this.edit);
|
||||
}
|
||||
|
||||
private getEdit = (node: HTMLButtonElement) => {
|
||||
this.edit = node;
|
||||
};
|
||||
|
||||
handleClick = (e: MouseEvent<HTMLDivElement | HTMLButtonElement>) => {
|
||||
if (this.props.isDisabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
e.preventDefault();
|
||||
this.props.updateSection(this.props.section);
|
||||
};
|
||||
|
||||
render() {
|
||||
let editButtonComponent: ReactNode;
|
||||
|
||||
if (this.props.isDisabled) {
|
||||
if (this.props.collapsedEditButtonWhenDisabled) {
|
||||
editButtonComponent = this.props.collapsedEditButtonWhenDisabled;
|
||||
} else {
|
||||
editButtonComponent = null;
|
||||
}
|
||||
} else {
|
||||
editButtonComponent = (
|
||||
<button
|
||||
ref={this.getEdit}
|
||||
id={this.props.section + 'Edit'}
|
||||
className='color--link style--none section-min__edit'
|
||||
onClick={this.handleClick}
|
||||
aria-labelledby={this.props.section + 'Title ' + this.props.section + 'Edit'}
|
||||
aria-expanded={false}
|
||||
>
|
||||
<EditIcon/>
|
||||
<FormattedMessage
|
||||
id='setting_item_min.edit'
|
||||
defaultMessage='Edit'
|
||||
/>
|
||||
</button>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
className={classNames('section-min', {isDisabled: this.props.isDisabled})}
|
||||
onClick={this.handleClick}
|
||||
>
|
||||
<div
|
||||
className='secion-min__header'
|
||||
>
|
||||
<h4
|
||||
id={this.props.section + 'Title'}
|
||||
className={classNames('section-min__title', {isDisabled: this.props.isDisabled})}
|
||||
>
|
||||
{this.props.title}
|
||||
</h4>
|
||||
{editButtonComponent}
|
||||
</div>
|
||||
<div
|
||||
id={this.props.section + 'Desc'}
|
||||
className={classNames('section-min__describe', {isDisabled: this.props.isDisabled})}
|
||||
>
|
||||
{this.props.describe}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
@ -1,60 +0,0 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`components/SettingItemMin should match snapshot 1`] = `
|
||||
<div
|
||||
className="section-min"
|
||||
onClick={[Function]}
|
||||
>
|
||||
<div
|
||||
className="d-flex"
|
||||
>
|
||||
<h4
|
||||
className="section-min__title"
|
||||
id="sectionTitle"
|
||||
>
|
||||
title
|
||||
</h4>
|
||||
<div
|
||||
className="section-min__edit"
|
||||
>
|
||||
<button
|
||||
aria-expanded={false}
|
||||
aria-labelledby="sectionTitle sectionEdit"
|
||||
className="color--link cursor--pointer style--none text-left"
|
||||
id="sectionEdit"
|
||||
onClick={[Function]}
|
||||
>
|
||||
<EditIcon />
|
||||
<MemoizedFormattedMessage
|
||||
defaultMessage="Edit"
|
||||
id="setting_item_min.edit"
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="section-min__describe"
|
||||
id="sectionDesc"
|
||||
>
|
||||
describe
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`components/SettingItemMin should match snapshot, on disableOpen to true 1`] = `
|
||||
<div
|
||||
className="section-min"
|
||||
onClick={[Function]}
|
||||
>
|
||||
<div
|
||||
className="d-flex"
|
||||
>
|
||||
<h4
|
||||
className="section-min__title"
|
||||
id="sectionTitle"
|
||||
>
|
||||
title
|
||||
</h4>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
@ -1,18 +0,0 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {connect} from 'react-redux';
|
||||
|
||||
import {getIsMobileView} from 'selectors/views/browser';
|
||||
|
||||
import type {GlobalState} from 'types/store';
|
||||
|
||||
import SettingItemMin from './setting_item_min';
|
||||
|
||||
function mapStateToProps(state: GlobalState) {
|
||||
return {
|
||||
isMobileView: getIsMobileView(state),
|
||||
};
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps, null, null, {forwardRef: true})(SettingItemMin);
|
@ -1,131 +0,0 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import React from 'react';
|
||||
import type {ReactNode} from 'react';
|
||||
import {FormattedMessage} from 'react-intl';
|
||||
|
||||
import EditIcon from 'components/widgets/icons/fa_edit_icon';
|
||||
|
||||
import {a11yFocus} from 'utils/utils';
|
||||
|
||||
interface Props {
|
||||
|
||||
/**
|
||||
* Settings title
|
||||
*/
|
||||
title: ReactNode | string;
|
||||
|
||||
/**
|
||||
* Option to disable opening the setting
|
||||
*/
|
||||
disableOpen?: boolean;
|
||||
|
||||
/**
|
||||
* Settings or tab section
|
||||
*/
|
||||
section: string;
|
||||
|
||||
/**
|
||||
* Function to update section
|
||||
*/
|
||||
updateSection: (section: string) => void;
|
||||
|
||||
/**
|
||||
* Settings description
|
||||
*/
|
||||
describe?: ReactNode;
|
||||
|
||||
isMobileView: boolean;
|
||||
}
|
||||
|
||||
export default class SettingItemMin extends React.PureComponent<Props> {
|
||||
private edit: HTMLButtonElement | null = null;
|
||||
|
||||
focus(): void {
|
||||
a11yFocus(this.edit);
|
||||
}
|
||||
|
||||
private getEdit = (node: HTMLButtonElement) => {
|
||||
this.edit = node;
|
||||
};
|
||||
|
||||
handleUpdateSection = (e: React.MouseEvent<HTMLElement>) => {
|
||||
e.preventDefault();
|
||||
this.props.updateSection(this.props.section);
|
||||
};
|
||||
|
||||
render(): JSX.Element {
|
||||
let editButton = null;
|
||||
let describeSection = null;
|
||||
|
||||
if (!this.props.disableOpen && this.props.isMobileView) {
|
||||
editButton = (
|
||||
<div className='section-min__edit'>
|
||||
<button
|
||||
id={this.props.section + 'Edit'}
|
||||
className='color--link cursor--pointer style--none'
|
||||
onClick={this.handleUpdateSection}
|
||||
ref={this.getEdit}
|
||||
aria-labelledby={this.props.section + 'Title ' + this.props.section + 'Edit'}
|
||||
aria-expanded={false}
|
||||
>
|
||||
{this.props.describe ? this.props.describe : (
|
||||
<FormattedMessage
|
||||
id='setting_item_min.edit'
|
||||
defaultMessage='Edit'
|
||||
/>
|
||||
)}
|
||||
<i className='icon icon-chevron-down'/>
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
} else if (!this.props.disableOpen) {
|
||||
editButton = (
|
||||
<div className='section-min__edit'>
|
||||
<button
|
||||
id={this.props.section + 'Edit'}
|
||||
className='color--link cursor--pointer style--none text-left'
|
||||
onClick={this.handleUpdateSection}
|
||||
ref={this.getEdit}
|
||||
aria-labelledby={this.props.section + 'Title ' + this.props.section + 'Edit'}
|
||||
aria-expanded={false}
|
||||
>
|
||||
<EditIcon/>
|
||||
<FormattedMessage
|
||||
id='setting_item_min.edit'
|
||||
defaultMessage='Edit'
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
|
||||
describeSection = (
|
||||
<div
|
||||
id={this.props.section + 'Desc'}
|
||||
className='section-min__describe'
|
||||
>
|
||||
{this.props.describe}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
className='section-min'
|
||||
onClick={this.handleUpdateSection}
|
||||
>
|
||||
<div className='d-flex'>
|
||||
<h4
|
||||
id={this.props.section + 'Title'}
|
||||
className='section-min__title'
|
||||
>
|
||||
{this.props.title}
|
||||
</h4>
|
||||
{editButton}
|
||||
</div>
|
||||
{describeSection}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
@ -37,7 +37,7 @@ exports[`components/sidebar/invite_members_button should match snapshot 1`] = `
|
||||
}
|
||||
teamId="team_id2sss"
|
||||
>
|
||||
<Connect(ToggleModalButton)
|
||||
<ToggleModalButton
|
||||
ariaLabel="Invite Members"
|
||||
className="intro-links color--link cursor--pointer"
|
||||
dialogType={
|
||||
@ -52,51 +52,30 @@ exports[`components/sidebar/invite_members_button should match snapshot 1`] = `
|
||||
modalId="invitation"
|
||||
onClick={[Function]}
|
||||
>
|
||||
<ToggleModalButton
|
||||
actions={
|
||||
Object {
|
||||
"openModal": [Function],
|
||||
}
|
||||
}
|
||||
ariaLabel="Invite Members"
|
||||
className="intro-links color--link cursor--pointer"
|
||||
dialogType={
|
||||
Object {
|
||||
"$$typeof": Symbol(react.memo),
|
||||
"WrappedComponent": [Function],
|
||||
"compare": null,
|
||||
"type": [Function],
|
||||
}
|
||||
}
|
||||
<button
|
||||
aria-label="Invite Members dialog"
|
||||
className="style--none intro-links color--link cursor--pointer"
|
||||
id="inviteMembersButton"
|
||||
modalId="invitation"
|
||||
onClick={[Function]}
|
||||
>
|
||||
<button
|
||||
aria-label="Invite Members dialog"
|
||||
className="style--none intro-links color--link cursor--pointer"
|
||||
id="inviteMembersButton"
|
||||
onClick={[Function]}
|
||||
<div
|
||||
aria-label="Invite Members"
|
||||
className="SidebarChannelNavigator__inviteMembersLhsButton"
|
||||
>
|
||||
<div
|
||||
aria-label="Invite Members"
|
||||
className="SidebarChannelNavigator__inviteMembersLhsButton"
|
||||
<i
|
||||
className="icon-plus-box"
|
||||
/>
|
||||
<FormattedMessage
|
||||
defaultMessage="Invite Members"
|
||||
id="sidebar_left.inviteMembers"
|
||||
>
|
||||
<i
|
||||
className="icon-plus-box"
|
||||
/>
|
||||
<FormattedMessage
|
||||
defaultMessage="Invite Members"
|
||||
id="sidebar_left.inviteMembers"
|
||||
>
|
||||
<span>
|
||||
Invite Members
|
||||
</span>
|
||||
</FormattedMessage>
|
||||
</div>
|
||||
</button>
|
||||
</ToggleModalButton>
|
||||
</Connect(ToggleModalButton)>
|
||||
<span>
|
||||
Invite Members
|
||||
</span>
|
||||
</FormattedMessage>
|
||||
</div>
|
||||
</button>
|
||||
</ToggleModalButton>
|
||||
</TeamPermissionGate>
|
||||
</Connect(TeamPermissionGate)>
|
||||
</InviteMembersButton>
|
||||
|
@ -102,7 +102,7 @@ exports[`components/TeamSettings/OpenInvite should match snapshot on active with
|
||||
`;
|
||||
|
||||
exports[`components/TeamSettings/OpenInvite should match snapshot on non active allowing open invite 1`] = `
|
||||
<Connect(SettingItemMin)
|
||||
<SettingItemMin
|
||||
describe="Yes"
|
||||
section="open_invite"
|
||||
title="Allow any user with an account on this server to join this team"
|
||||
@ -111,7 +111,7 @@ exports[`components/TeamSettings/OpenInvite should match snapshot on non active
|
||||
`;
|
||||
|
||||
exports[`components/TeamSettings/OpenInvite should match snapshot on non active with groupConstrained 1`] = `
|
||||
<Connect(SettingItemMin)
|
||||
<SettingItemMin
|
||||
describe="No, members of this team are added and removed by linked groups."
|
||||
section="open_invite"
|
||||
title="Allow any user with an account on this server to join this team"
|
||||
@ -120,7 +120,7 @@ exports[`components/TeamSettings/OpenInvite should match snapshot on non active
|
||||
`;
|
||||
|
||||
exports[`components/TeamSettings/OpenInvite should match snapshot on non active without groupConstrained 1`] = `
|
||||
<Connect(SettingItemMin)
|
||||
<SettingItemMin
|
||||
describe="No"
|
||||
section="open_invite"
|
||||
title="Allow any user with an account on this server to join this team"
|
||||
|
@ -51,7 +51,7 @@ exports[`components/TeamSettings hide invite code if no permissions for team inv
|
||||
<div
|
||||
className="divider-dark first"
|
||||
/>
|
||||
<Connect(SettingItemMin)
|
||||
<SettingItemMin
|
||||
describe="name"
|
||||
section="name"
|
||||
title="Team Name"
|
||||
@ -60,7 +60,7 @@ exports[`components/TeamSettings hide invite code if no permissions for team inv
|
||||
<div
|
||||
className="divider-light"
|
||||
/>
|
||||
<Connect(SettingItemMin)
|
||||
<SettingItemMin
|
||||
describe=""
|
||||
section="description"
|
||||
title="Team Description"
|
||||
@ -92,7 +92,7 @@ exports[`components/TeamSettings hide invite code if no permissions for team inv
|
||||
<div
|
||||
className="divider-light"
|
||||
/>
|
||||
<Connect(SettingItemMin)
|
||||
<SettingItemMin
|
||||
describe=""
|
||||
section="allowed_domains"
|
||||
title="allowedDomains"
|
||||
@ -112,7 +112,7 @@ exports[`components/TeamSettings hide invite code if no permissions for team inv
|
||||
<div
|
||||
className="divider-light"
|
||||
/>
|
||||
<Connect(SettingItemMin)
|
||||
<SettingItemMin
|
||||
describe="Click 'Edit' to regenerate Invite Code."
|
||||
section="invite_id"
|
||||
title="Invite Code"
|
||||
@ -176,7 +176,7 @@ exports[`components/TeamSettings hide invite code if no permissions for team inv
|
||||
<div
|
||||
className="divider-dark first"
|
||||
/>
|
||||
<Connect(SettingItemMin)
|
||||
<SettingItemMin
|
||||
describe="name"
|
||||
section="name"
|
||||
title="Team Name"
|
||||
@ -185,7 +185,7 @@ exports[`components/TeamSettings hide invite code if no permissions for team inv
|
||||
<div
|
||||
className="divider-light"
|
||||
/>
|
||||
<Connect(SettingItemMin)
|
||||
<SettingItemMin
|
||||
describe=""
|
||||
section="description"
|
||||
title="Team Description"
|
||||
@ -217,7 +217,7 @@ exports[`components/TeamSettings hide invite code if no permissions for team inv
|
||||
<div
|
||||
className="divider-light"
|
||||
/>
|
||||
<Connect(SettingItemMin)
|
||||
<SettingItemMin
|
||||
describe=""
|
||||
section="allowed_domains"
|
||||
title="allowedDomains"
|
||||
@ -295,7 +295,7 @@ exports[`components/TeamSettings should match snapshot when team is group constr
|
||||
<div
|
||||
className="divider-dark first"
|
||||
/>
|
||||
<Connect(SettingItemMin)
|
||||
<SettingItemMin
|
||||
describe="TestTeam"
|
||||
section="name"
|
||||
title="Team Name"
|
||||
@ -304,7 +304,7 @@ exports[`components/TeamSettings should match snapshot when team is group constr
|
||||
<div
|
||||
className="divider-light"
|
||||
/>
|
||||
<Connect(SettingItemMin)
|
||||
<SettingItemMin
|
||||
describe="The Test Team"
|
||||
section="description"
|
||||
title="Team Description"
|
||||
|
@ -289,7 +289,6 @@ export default class Textbox extends React.PureComponent<Props> {
|
||||
>
|
||||
<PostMarkdown
|
||||
message={this.props.value}
|
||||
mentionKeys={[]}
|
||||
channelId={this.props.channelId}
|
||||
imageProps={{hideUtilities: true}}
|
||||
/>
|
||||
|
@ -10,6 +10,11 @@ import {ModalIdentifiers} from 'utils/constants';
|
||||
|
||||
import ToggleModalButton from './toggle_modal_button';
|
||||
|
||||
jest.mock('react-redux', () => ({
|
||||
...jest.requireActual('react-redux') as typeof import('react-redux'),
|
||||
useDispatch: () => jest.fn(),
|
||||
}));
|
||||
|
||||
class TestModal extends React.PureComponent {
|
||||
render() {
|
||||
return (
|
||||
@ -33,7 +38,6 @@ describe('components/ToggleModalButton', () => {
|
||||
role='menuitem'
|
||||
modalId={ModalIdentifiers.DELETE_CHANNEL}
|
||||
dialogType={TestModal}
|
||||
actions={{openModal: () => true}}
|
||||
>
|
||||
<FormattedMessage
|
||||
id='channel_header.delete'
|
@ -1,11 +1,11 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import React from 'react';
|
||||
import type {ComponentType, MouseEvent, ReactNode} from 'react';
|
||||
import React, {type ComponentType, type MouseEvent, type ReactNode} from 'react';
|
||||
import {useIntl} from 'react-intl';
|
||||
import {useDispatch} from 'react-redux';
|
||||
|
||||
import type {ModalData} from 'types/actions';
|
||||
import {openModal} from 'actions/views/modals';
|
||||
|
||||
type Props = {
|
||||
ariaLabel?: string;
|
||||
@ -19,14 +19,25 @@ type Props = {
|
||||
disabled?: boolean;
|
||||
id?: string;
|
||||
role?: string;
|
||||
actions: {
|
||||
openModal: <P>(modalData: ModalData<P>) => void;
|
||||
};
|
||||
};
|
||||
|
||||
const ToggleModalButton = ({ariaLabel, children, modalId, dialogType, dialogProps = {}, onClick, className = '', showUnread, disabled, id, actions, role}: Props) => {
|
||||
const ToggleModalButton = ({
|
||||
ariaLabel,
|
||||
children,
|
||||
modalId,
|
||||
dialogType,
|
||||
dialogProps = {},
|
||||
onClick,
|
||||
className = '',
|
||||
showUnread,
|
||||
disabled,
|
||||
id,
|
||||
role,
|
||||
}: Props) => {
|
||||
const intl = useIntl();
|
||||
|
||||
const dispatch = useDispatch();
|
||||
|
||||
const show = (e: MouseEvent<HTMLButtonElement>) => {
|
||||
if (e) {
|
||||
e.preventDefault();
|
||||
@ -38,7 +49,7 @@ const ToggleModalButton = ({ariaLabel, children, modalId, dialogType, dialogProp
|
||||
dialogType,
|
||||
};
|
||||
|
||||
actions.openModal(modalData);
|
||||
dispatch(openModal(modalData));
|
||||
};
|
||||
|
||||
const ariaLabelElement = ariaLabel ? intl.formatMessage({
|
@ -1,20 +0,0 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {connect} from 'react-redux';
|
||||
import {bindActionCreators} from 'redux';
|
||||
import type {Dispatch} from 'redux';
|
||||
|
||||
import {openModal} from 'actions/views/modals';
|
||||
|
||||
import ToggleModalButton from './toggle_modal_button';
|
||||
|
||||
function mapDispatchToProps(dispatch: Dispatch) {
|
||||
return {
|
||||
actions: bindActionCreators({
|
||||
openModal,
|
||||
}, dispatch),
|
||||
};
|
||||
}
|
||||
|
||||
export default connect(null, mapDispatchToProps)(ToggleModalButton);
|
@ -400,6 +400,7 @@ exports[`component/user_group_popover should match snapshot 1`] = `
|
||||
"desktop_sound": "false",
|
||||
"email": "false",
|
||||
"first_name": "false",
|
||||
"highlight_keys": "",
|
||||
"mark_unread": "mention",
|
||||
"mention_keys": "",
|
||||
"push": "none",
|
||||
@ -441,6 +442,7 @@ exports[`component/user_group_popover should match snapshot 1`] = `
|
||||
"desktop_sound": "false",
|
||||
"email": "false",
|
||||
"first_name": "false",
|
||||
"highlight_keys": "",
|
||||
"mark_unread": "mention",
|
||||
"mention_keys": "",
|
||||
"push": "none",
|
||||
@ -482,6 +484,7 @@ exports[`component/user_group_popover should match snapshot 1`] = `
|
||||
"desktop_sound": "false",
|
||||
"email": "false",
|
||||
"first_name": "false",
|
||||
"highlight_keys": "",
|
||||
"mark_unread": "mention",
|
||||
"mention_keys": "",
|
||||
"push": "none",
|
||||
@ -523,6 +526,7 @@ exports[`component/user_group_popover should match snapshot 1`] = `
|
||||
"desktop_sound": "false",
|
||||
"email": "false",
|
||||
"first_name": "false",
|
||||
"highlight_keys": "",
|
||||
"mark_unread": "mention",
|
||||
"mention_keys": "",
|
||||
"push": "none",
|
||||
@ -564,6 +568,7 @@ exports[`component/user_group_popover should match snapshot 1`] = `
|
||||
"desktop_sound": "false",
|
||||
"email": "false",
|
||||
"first_name": "false",
|
||||
"highlight_keys": "",
|
||||
"mark_unread": "mention",
|
||||
"mention_keys": "",
|
||||
"push": "none",
|
||||
@ -605,6 +610,7 @@ exports[`component/user_group_popover should match snapshot 1`] = `
|
||||
"desktop_sound": "false",
|
||||
"email": "false",
|
||||
"first_name": "false",
|
||||
"highlight_keys": "",
|
||||
"mark_unread": "mention",
|
||||
"mention_keys": "",
|
||||
"push": "none",
|
||||
@ -646,6 +652,7 @@ exports[`component/user_group_popover should match snapshot 1`] = `
|
||||
"desktop_sound": "false",
|
||||
"email": "false",
|
||||
"first_name": "false",
|
||||
"highlight_keys": "",
|
||||
"mark_unread": "mention",
|
||||
"mention_keys": "",
|
||||
"push": "none",
|
||||
@ -687,6 +694,7 @@ exports[`component/user_group_popover should match snapshot 1`] = `
|
||||
"desktop_sound": "false",
|
||||
"email": "false",
|
||||
"first_name": "false",
|
||||
"highlight_keys": "",
|
||||
"mark_unread": "mention",
|
||||
"mention_keys": "",
|
||||
"push": "none",
|
||||
@ -728,6 +736,7 @@ exports[`component/user_group_popover should match snapshot 1`] = `
|
||||
"desktop_sound": "false",
|
||||
"email": "false",
|
||||
"first_name": "false",
|
||||
"highlight_keys": "",
|
||||
"mark_unread": "mention",
|
||||
"mention_keys": "",
|
||||
"push": "none",
|
||||
@ -769,6 +778,7 @@ exports[`component/user_group_popover should match snapshot 1`] = `
|
||||
"desktop_sound": "false",
|
||||
"email": "false",
|
||||
"first_name": "false",
|
||||
"highlight_keys": "",
|
||||
"mark_unread": "mention",
|
||||
"mention_keys": "",
|
||||
"push": "none",
|
||||
@ -810,6 +820,7 @@ exports[`component/user_group_popover should match snapshot 1`] = `
|
||||
"desktop_sound": "false",
|
||||
"email": "false",
|
||||
"first_name": "false",
|
||||
"highlight_keys": "",
|
||||
"mark_unread": "mention",
|
||||
"mention_keys": "",
|
||||
"push": "none",
|
||||
@ -851,6 +862,7 @@ exports[`component/user_group_popover should match snapshot 1`] = `
|
||||
"desktop_sound": "false",
|
||||
"email": "false",
|
||||
"first_name": "false",
|
||||
"highlight_keys": "",
|
||||
"mark_unread": "mention",
|
||||
"mention_keys": "",
|
||||
"push": "none",
|
||||
@ -892,6 +904,7 @@ exports[`component/user_group_popover should match snapshot 1`] = `
|
||||
"desktop_sound": "false",
|
||||
"email": "false",
|
||||
"first_name": "false",
|
||||
"highlight_keys": "",
|
||||
"mark_unread": "mention",
|
||||
"mention_keys": "",
|
||||
"push": "none",
|
||||
@ -933,6 +946,7 @@ exports[`component/user_group_popover should match snapshot 1`] = `
|
||||
"desktop_sound": "false",
|
||||
"email": "false",
|
||||
"first_name": "false",
|
||||
"highlight_keys": "",
|
||||
"mark_unread": "mention",
|
||||
"mention_keys": "",
|
||||
"push": "none",
|
||||
@ -974,6 +988,7 @@ exports[`component/user_group_popover should match snapshot 1`] = `
|
||||
"desktop_sound": "false",
|
||||
"email": "false",
|
||||
"first_name": "false",
|
||||
"highlight_keys": "",
|
||||
"mark_unread": "mention",
|
||||
"mention_keys": "",
|
||||
"push": "none",
|
||||
|
@ -91,6 +91,7 @@ exports[`component/user_group_popover/group_member_list should match snapshot 1`
|
||||
"desktop_sound": "false",
|
||||
"email": "false",
|
||||
"first_name": "false",
|
||||
"highlight_keys": "",
|
||||
"mark_unread": "mention",
|
||||
"mention_keys": "",
|
||||
"push": "none",
|
||||
@ -132,6 +133,7 @@ exports[`component/user_group_popover/group_member_list should match snapshot 1`
|
||||
"desktop_sound": "false",
|
||||
"email": "false",
|
||||
"first_name": "false",
|
||||
"highlight_keys": "",
|
||||
"mark_unread": "mention",
|
||||
"mention_keys": "",
|
||||
"push": "none",
|
||||
@ -173,6 +175,7 @@ exports[`component/user_group_popover/group_member_list should match snapshot 1`
|
||||
"desktop_sound": "false",
|
||||
"email": "false",
|
||||
"first_name": "false",
|
||||
"highlight_keys": "",
|
||||
"mark_unread": "mention",
|
||||
"mention_keys": "",
|
||||
"push": "none",
|
||||
@ -214,6 +217,7 @@ exports[`component/user_group_popover/group_member_list should match snapshot 1`
|
||||
"desktop_sound": "false",
|
||||
"email": "false",
|
||||
"first_name": "false",
|
||||
"highlight_keys": "",
|
||||
"mark_unread": "mention",
|
||||
"mention_keys": "",
|
||||
"push": "none",
|
||||
@ -255,6 +259,7 @@ exports[`component/user_group_popover/group_member_list should match snapshot 1`
|
||||
"desktop_sound": "false",
|
||||
"email": "false",
|
||||
"first_name": "false",
|
||||
"highlight_keys": "",
|
||||
"mark_unread": "mention",
|
||||
"mention_keys": "",
|
||||
"push": "none",
|
||||
|
@ -1,7 +1,7 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`components/user_settings/advanced/JoinLeaveSection should match snapshot 1`] = `
|
||||
<Connect(SettingItemMin)
|
||||
<SettingItemMin
|
||||
section="joinLeave"
|
||||
title={
|
||||
<Memo(MemoizedFormattedMessage)
|
||||
|
@ -11,7 +11,7 @@ import {Preferences} from 'mattermost-redux/constants';
|
||||
|
||||
import SettingItemMax from 'components/setting_item_max';
|
||||
import SettingItemMin from 'components/setting_item_min';
|
||||
import type SettingItemMinComponent from 'components/setting_item_min/setting_item_min';
|
||||
import type SettingItemMinComponent from 'components/setting_item_min';
|
||||
|
||||
import {AdvancedSections} from 'utils/constants';
|
||||
import {a11yFocus} from 'utils/utils';
|
||||
|
@ -8,7 +8,7 @@ import {Preferences} from 'mattermost-redux/constants';
|
||||
|
||||
import SettingItemMax from 'components/setting_item_max';
|
||||
import SettingItemMin from 'components/setting_item_min';
|
||||
import type SettingItemMinComponent from 'components/setting_item_min/setting_item_min';
|
||||
import type SettingItemMinComponent from 'components/setting_item_min';
|
||||
|
||||
import {AdvancedSections} from 'utils/constants';
|
||||
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -1,7 +1,7 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`components/user_settings/display/user_settings_theme/user_settings_theme.jsx should match snapshot 1`] = `
|
||||
<Connect(SettingItemMin)
|
||||
<SettingItemMin
|
||||
describe={
|
||||
<Memo(MemoizedFormattedMessage)
|
||||
defaultMessage="Open to manage your theme"
|
||||
|
@ -10,7 +10,7 @@ import type {Theme} from 'mattermost-redux/selectors/entities/preferences';
|
||||
import ExternalLink from 'components/external_link';
|
||||
import SettingItemMax from 'components/setting_item_max';
|
||||
import SettingItemMin from 'components/setting_item_min';
|
||||
import type SettingItemMinComponent from 'components/setting_item_min/setting_item_min';
|
||||
import type SettingItemMinComponent from 'components/setting_item_min';
|
||||
import ImportThemeModal from 'components/user_settings/import_theme_modal';
|
||||
|
||||
import {Constants, ModalIdentifiers} from 'utils/constants';
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -150,7 +150,12 @@ exports[`components/user_settings/notifications/DesktopNotificationSettings shou
|
||||
section=""
|
||||
serverError=""
|
||||
submit={[MockFunction]}
|
||||
title="Desktop Notifications"
|
||||
title={
|
||||
<Memo(MemoizedFormattedMessage)
|
||||
defaultMessage="Desktop Notifications"
|
||||
id="user.settings.notifications.desktop.title"
|
||||
/>
|
||||
}
|
||||
updateSection={[Function]}
|
||||
/>
|
||||
`;
|
||||
@ -247,13 +252,18 @@ exports[`components/user_settings/notifications/DesktopNotificationSettings shou
|
||||
section=""
|
||||
serverError=""
|
||||
submit={[MockFunction]}
|
||||
title="Desktop Notifications"
|
||||
title={
|
||||
<Memo(MemoizedFormattedMessage)
|
||||
defaultMessage="Desktop Notifications"
|
||||
id="user.settings.notifications.desktop.title"
|
||||
/>
|
||||
}
|
||||
updateSection={[Function]}
|
||||
/>
|
||||
`;
|
||||
|
||||
exports[`components/user_settings/notifications/DesktopNotificationSettings should match snapshot, on buildMinimizedSetting 1`] = `
|
||||
<Connect(SettingItemMin)
|
||||
<SettingItemMin
|
||||
describe={
|
||||
<Memo(MemoizedFormattedMessage)
|
||||
defaultMessage="For mentions and direct messages, without sound"
|
||||
@ -261,13 +271,18 @@ exports[`components/user_settings/notifications/DesktopNotificationSettings shou
|
||||
/>
|
||||
}
|
||||
section="desktop"
|
||||
title="Desktop Notifications"
|
||||
title={
|
||||
<Memo(MemoizedFormattedMessage)
|
||||
defaultMessage="Desktop Notifications"
|
||||
id="user.settings.notifications.desktop.title"
|
||||
/>
|
||||
}
|
||||
updateSection={[Function]}
|
||||
/>
|
||||
`;
|
||||
|
||||
exports[`components/user_settings/notifications/DesktopNotificationSettings should match snapshot, on buildMinimizedSetting 2`] = `
|
||||
<Connect(SettingItemMin)
|
||||
<SettingItemMin
|
||||
describe={
|
||||
<Memo(MemoizedFormattedMessage)
|
||||
defaultMessage="Off"
|
||||
@ -275,7 +290,12 @@ exports[`components/user_settings/notifications/DesktopNotificationSettings shou
|
||||
/>
|
||||
}
|
||||
section="desktop"
|
||||
title="Desktop Notifications"
|
||||
title={
|
||||
<Memo(MemoizedFormattedMessage)
|
||||
defaultMessage="Desktop Notifications"
|
||||
id="user.settings.notifications.desktop.title"
|
||||
/>
|
||||
}
|
||||
updateSection={[Function]}
|
||||
/>
|
||||
`;
|
||||
@ -430,7 +450,12 @@ exports[`components/user_settings/notifications/DesktopNotificationSettings shou
|
||||
section=""
|
||||
serverError=""
|
||||
submit={[MockFunction]}
|
||||
title="Desktop Notifications"
|
||||
title={
|
||||
<Memo(MemoizedFormattedMessage)
|
||||
defaultMessage="Desktop Notifications"
|
||||
id="user.settings.notifications.desktop.title"
|
||||
/>
|
||||
}
|
||||
updateSection={[Function]}
|
||||
/>
|
||||
`;
|
||||
@ -638,7 +663,12 @@ exports[`components/user_settings/notifications/DesktopNotificationSettings shou
|
||||
section=""
|
||||
serverError=""
|
||||
submit={[MockFunction]}
|
||||
title="Desktop Notifications"
|
||||
title={
|
||||
<Memo(MemoizedFormattedMessage)
|
||||
defaultMessage="Desktop Notifications"
|
||||
id="user.settings.notifications.desktop.title"
|
||||
/>
|
||||
}
|
||||
updateSection={[Function]}
|
||||
/>
|
||||
`;
|
||||
@ -892,7 +922,12 @@ exports[`components/user_settings/notifications/DesktopNotificationSettings shou
|
||||
section=""
|
||||
serverError=""
|
||||
submit={[MockFunction]}
|
||||
title="Desktop Notifications"
|
||||
title={
|
||||
<Memo(MemoizedFormattedMessage)
|
||||
defaultMessage="Desktop Notifications"
|
||||
id="user.settings.notifications.desktop.title"
|
||||
/>
|
||||
}
|
||||
updateSection={[Function]}
|
||||
/>
|
||||
`;
|
||||
@ -1101,13 +1136,18 @@ exports[`components/user_settings/notifications/DesktopNotificationSettings shou
|
||||
section=""
|
||||
serverError=""
|
||||
submit={[MockFunction]}
|
||||
title="Desktop Notifications"
|
||||
title={
|
||||
<Memo(MemoizedFormattedMessage)
|
||||
defaultMessage="Desktop Notifications"
|
||||
id="user.settings.notifications.desktop.title"
|
||||
/>
|
||||
}
|
||||
updateSection={[Function]}
|
||||
/>
|
||||
`;
|
||||
|
||||
exports[`components/user_settings/notifications/DesktopNotificationSettings should match snapshot, on min setting 1`] = `
|
||||
<Connect(SettingItemMin)
|
||||
<SettingItemMin
|
||||
describe={
|
||||
<Memo(MemoizedFormattedMessage)
|
||||
defaultMessage="For mentions and direct messages, without sound"
|
||||
@ -1115,7 +1155,12 @@ exports[`components/user_settings/notifications/DesktopNotificationSettings shou
|
||||
/>
|
||||
}
|
||||
section="desktop"
|
||||
title="Desktop Notifications"
|
||||
title={
|
||||
<Memo(MemoizedFormattedMessage)
|
||||
defaultMessage="Desktop Notifications"
|
||||
id="user.settings.notifications.desktop.title"
|
||||
/>
|
||||
}
|
||||
updateSection={[Function]}
|
||||
/>
|
||||
`;
|
||||
|
@ -19,21 +19,21 @@ jest.mock('utils/notification_sounds', () => {
|
||||
|
||||
describe('components/user_settings/notifications/DesktopNotificationSettings', () => {
|
||||
const baseProps: ComponentProps<typeof DesktopNotificationSettings> = {
|
||||
activity: NotificationLevels.MENTION,
|
||||
sound: 'false',
|
||||
updateSection: jest.fn(),
|
||||
setParentState: jest.fn(),
|
||||
submit: jest.fn(),
|
||||
cancel: jest.fn(),
|
||||
error: '',
|
||||
active: true,
|
||||
areAllSectionsInactive: false,
|
||||
updateSection: jest.fn(),
|
||||
onSubmit: jest.fn(),
|
||||
onCancel: jest.fn(),
|
||||
saving: false,
|
||||
selectedSound: 'Bing',
|
||||
error: '',
|
||||
setParentState: jest.fn(),
|
||||
areAllSectionsInactive: false,
|
||||
isCollapsedThreadsEnabled: false,
|
||||
activity: NotificationLevels.MENTION,
|
||||
threads: NotificationLevels.ALL,
|
||||
callsSelectedSound: 'Dynamic',
|
||||
sound: 'false',
|
||||
callsSound: 'false',
|
||||
selectedSound: 'Bing',
|
||||
callsSelectedSound: 'Dynamic',
|
||||
isCallsRingingEnabled: false,
|
||||
};
|
||||
|
||||
@ -81,8 +81,8 @@ describe('components/user_settings/notifications/DesktopNotificationSettings', (
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('should call props.updateSection and props.cancel on handleMinUpdateSection', () => {
|
||||
const props = {...baseProps, updateSection: jest.fn(), cancel: jest.fn()};
|
||||
test('should call props.updateSection and props.onCancel on handleMinUpdateSection', () => {
|
||||
const props = {...baseProps, updateSection: jest.fn(), onCancel: jest.fn()};
|
||||
const wrapper = shallow<DesktopNotificationSettings>(
|
||||
<DesktopNotificationSettings {...props}/>,
|
||||
);
|
||||
@ -90,14 +90,14 @@ describe('components/user_settings/notifications/DesktopNotificationSettings', (
|
||||
wrapper.instance().handleMinUpdateSection('');
|
||||
expect(props.updateSection).toHaveBeenCalledTimes(1);
|
||||
expect(props.updateSection).toHaveBeenCalledWith('');
|
||||
expect(props.cancel).toHaveBeenCalledTimes(1);
|
||||
expect(props.cancel).toHaveBeenCalledWith();
|
||||
expect(props.onCancel).toHaveBeenCalledTimes(1);
|
||||
expect(props.onCancel).toHaveBeenCalledWith();
|
||||
|
||||
wrapper.instance().handleMinUpdateSection('desktop');
|
||||
expect(props.updateSection).toHaveBeenCalledTimes(2);
|
||||
expect(props.updateSection).toHaveBeenCalledWith('desktop');
|
||||
expect(props.cancel).toHaveBeenCalledTimes(2);
|
||||
expect(props.cancel).toHaveBeenCalledWith();
|
||||
expect(props.onCancel).toHaveBeenCalledTimes(2);
|
||||
expect(props.onCancel).toHaveBeenCalledWith();
|
||||
});
|
||||
|
||||
test('should call props.updateSection on handleMaxUpdateSection', () => {
|
||||
|
@ -1,20 +1,17 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import React from 'react';
|
||||
import type {ChangeEvent, RefObject} from 'react';
|
||||
import React, {type ChangeEvent, type RefObject, type ReactNode} from 'react';
|
||||
import {FormattedMessage} from 'react-intl';
|
||||
import ReactSelect from 'react-select';
|
||||
import type {ValueType} from 'react-select';
|
||||
import ReactSelect, {type ValueType} from 'react-select';
|
||||
|
||||
import SettingItemMax from 'components/setting_item_max';
|
||||
import SettingItemMin from 'components/setting_item_min';
|
||||
import type SettingItemMinComponent from 'components/setting_item_min/setting_item_min';
|
||||
import type SettingItemMinComponent from 'components/setting_item_min';
|
||||
|
||||
import {NotificationLevels} from 'utils/constants';
|
||||
import {t} from 'utils/i18n';
|
||||
import * as NotificationSounds from 'utils/notification_sounds';
|
||||
import * as Utils from 'utils/utils';
|
||||
import {a11yFocus} from 'utils/utils';
|
||||
|
||||
type SelectedOption = {
|
||||
label: string;
|
||||
@ -22,21 +19,21 @@ type SelectedOption = {
|
||||
};
|
||||
|
||||
type Props = {
|
||||
active: boolean;
|
||||
updateSection: (section: string) => void;
|
||||
onSubmit: () => void;
|
||||
onCancel: () => void;
|
||||
saving: boolean;
|
||||
error: string;
|
||||
setParentState: (key: string, value: string | boolean) => void;
|
||||
areAllSectionsInactive: boolean;
|
||||
isCollapsedThreadsEnabled: boolean;
|
||||
activity: string;
|
||||
threads?: string;
|
||||
sound: string;
|
||||
callsSound: string;
|
||||
updateSection: (section: string) => void;
|
||||
setParentState: (key: string, value: string | boolean) => void;
|
||||
submit: () => void;
|
||||
cancel: () => void;
|
||||
error: string;
|
||||
active: boolean;
|
||||
areAllSectionsInactive: boolean;
|
||||
saving: boolean;
|
||||
selectedSound: string;
|
||||
callsSelectedSound: string;
|
||||
isCollapsedThreadsEnabled: boolean;
|
||||
isCallsRingingEnabled: boolean;
|
||||
};
|
||||
|
||||
@ -49,27 +46,29 @@ type State = {
|
||||
export default class DesktopNotificationSettings extends React.PureComponent<Props, State> {
|
||||
dropdownSoundRef: RefObject<ReactSelect>;
|
||||
callsDropdownRef: RefObject<ReactSelect>;
|
||||
minRef: RefObject<SettingItemMinComponent>;
|
||||
editButtonRef: RefObject<SettingItemMinComponent>;
|
||||
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
selectedOption: {value: props.selectedSound, label: props.selectedSound},
|
||||
callsSelectedOption: {value: props.callsSelectedSound, label: props.callsSelectedSound},
|
||||
blurDropdown: false,
|
||||
};
|
||||
|
||||
this.dropdownSoundRef = React.createRef();
|
||||
this.callsDropdownRef = React.createRef();
|
||||
this.minRef = React.createRef();
|
||||
this.editButtonRef = React.createRef();
|
||||
}
|
||||
|
||||
focusEditButton(): void {
|
||||
this.minRef.current?.focus();
|
||||
this.editButtonRef.current?.focus();
|
||||
}
|
||||
|
||||
handleMinUpdateSection = (section: string): void => {
|
||||
this.props.updateSection(section);
|
||||
this.props.cancel();
|
||||
this.props.onCancel();
|
||||
};
|
||||
|
||||
handleMaxUpdateSection = (section: string): void => this.props.updateSection(section);
|
||||
@ -79,7 +78,7 @@ export default class DesktopNotificationSettings extends React.PureComponent<Pro
|
||||
const value = e.currentTarget.getAttribute('data-value');
|
||||
if (key && value) {
|
||||
this.props.setParentState(key, value);
|
||||
Utils.a11yFocus(e.currentTarget);
|
||||
a11yFocus(e.currentTarget);
|
||||
}
|
||||
if (key === 'callsDesktopSound' && value === 'false') {
|
||||
NotificationSounds.stopTryNotificationRing();
|
||||
@ -435,9 +434,14 @@ export default class DesktopNotificationSettings extends React.PureComponent<Pro
|
||||
|
||||
return (
|
||||
<SettingItemMax
|
||||
title={Utils.localizeMessage('user.settings.notifications.desktop.title', 'Desktop Notifications')}
|
||||
title={
|
||||
<FormattedMessage
|
||||
id={'user.settings.notifications.desktop.title'}
|
||||
defaultMessage={'Desktop Notifications'}
|
||||
/>
|
||||
}
|
||||
inputs={inputs}
|
||||
submit={this.props.submit}
|
||||
submit={this.props.onSubmit}
|
||||
saving={this.props.saving}
|
||||
serverError={this.props.error}
|
||||
updateSection={this.handleMaxUpdateSection}
|
||||
@ -446,56 +450,76 @@ export default class DesktopNotificationSettings extends React.PureComponent<Pro
|
||||
};
|
||||
|
||||
buildMinimizedSetting = () => {
|
||||
let formattedMessageProps;
|
||||
const hasSoundOption = NotificationSounds.hasSoundOptions();
|
||||
let collapsedDescription: ReactNode = null;
|
||||
|
||||
if (this.props.activity === NotificationLevels.MENTION) {
|
||||
if (hasSoundOption && this.props.sound !== 'false') {
|
||||
formattedMessageProps = {
|
||||
id: t('user.settings.notifications.desktop.mentionsSound'),
|
||||
defaultMessage: 'For mentions and direct messages, with sound',
|
||||
};
|
||||
collapsedDescription = (
|
||||
<FormattedMessage
|
||||
id='user.settings.notifications.desktop.mentionsSound'
|
||||
defaultMessage='For mentions and direct messages, with sound'
|
||||
/>
|
||||
);
|
||||
} else if (hasSoundOption && this.props.sound === 'false') {
|
||||
formattedMessageProps = {
|
||||
id: t('user.settings.notifications.desktop.mentionsNoSound'),
|
||||
defaultMessage: 'For mentions and direct messages, without sound',
|
||||
};
|
||||
collapsedDescription = (
|
||||
<FormattedMessage
|
||||
id='user.settings.notifications.desktop.mentionsNoSound'
|
||||
defaultMessage='For mentions and direct messages, without sound'
|
||||
/>
|
||||
);
|
||||
} else {
|
||||
formattedMessageProps = {
|
||||
id: t('user.settings.notifications.desktop.mentionsSoundHidden'),
|
||||
defaultMessage: 'For mentions and direct messages',
|
||||
};
|
||||
collapsedDescription = (
|
||||
<FormattedMessage
|
||||
id='user.settings.notifications.desktop.mentionsSoundHidden'
|
||||
defaultMessage='For mentions and direct messages'
|
||||
/>
|
||||
);
|
||||
}
|
||||
} else if (this.props.activity === NotificationLevels.NONE) {
|
||||
formattedMessageProps = {
|
||||
id: t('user.settings.notifications.off'),
|
||||
defaultMessage: 'Off',
|
||||
};
|
||||
collapsedDescription = (
|
||||
<FormattedMessage
|
||||
id='user.settings.notifications.off'
|
||||
defaultMessage='Off'
|
||||
/>
|
||||
);
|
||||
} else {
|
||||
if (hasSoundOption && this.props.sound !== 'false') { //eslint-disable-line no-lonely-if
|
||||
formattedMessageProps = {
|
||||
id: t('user.settings.notifications.desktop.allSound'),
|
||||
defaultMessage: 'For all activity, with sound',
|
||||
};
|
||||
collapsedDescription = (
|
||||
<FormattedMessage
|
||||
id='user.settings.notifications.desktop.allSound'
|
||||
defaultMessage='For all activity, with sound'
|
||||
/>
|
||||
);
|
||||
} else if (hasSoundOption && this.props.sound === 'false') {
|
||||
formattedMessageProps = {
|
||||
id: t('user.settings.notifications.desktop.allNoSound'),
|
||||
defaultMessage: 'For all activity, without sound',
|
||||
};
|
||||
collapsedDescription = (
|
||||
<FormattedMessage
|
||||
id='user.settings.notifications.desktop.allNoSound'
|
||||
defaultMessage='For all activity, without sound'
|
||||
/>
|
||||
);
|
||||
} else {
|
||||
formattedMessageProps = {
|
||||
id: t('user.settings.notifications.desktop.allSoundHidden'),
|
||||
defaultMessage: 'For all activity',
|
||||
};
|
||||
collapsedDescription = (
|
||||
<FormattedMessage
|
||||
id='user.settings.notifications.desktop.allSoundHidden'
|
||||
defaultMessage='For all activity'
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<SettingItemMin
|
||||
title={Utils.localizeMessage('user.settings.notifications.desktop.title', 'Desktop Notifications')}
|
||||
describe={<FormattedMessage {...formattedMessageProps}/>}
|
||||
ref={this.editButtonRef}
|
||||
title={
|
||||
<FormattedMessage
|
||||
id={'user.settings.notifications.desktop.title'}
|
||||
defaultMessage={'Desktop Notifications'}
|
||||
/>
|
||||
}
|
||||
describe={collapsedDescription}
|
||||
section={'desktop'}
|
||||
updateSection={this.handleMinUpdateSection}
|
||||
ref={this.minRef}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
@ -7,18 +7,19 @@ exports[`components/user_settings/notifications/EmailNotificationSetting should
|
||||
"savePreferences": [MockFunction],
|
||||
}
|
||||
}
|
||||
activeSection="email"
|
||||
active={true}
|
||||
areAllSectionsInactive={false}
|
||||
currentUserId="current_user_id"
|
||||
emailInterval={0}
|
||||
enableEmail={false}
|
||||
enableEmailBatching={false}
|
||||
error=""
|
||||
isCollapsedThreadsEnabled={false}
|
||||
onCancel={[MockFunction]}
|
||||
onChange={[MockFunction]}
|
||||
onSubmit={[MockFunction]}
|
||||
saving={false}
|
||||
sendEmailNotifications={true}
|
||||
serverError=""
|
||||
setParentState={[MockFunction]}
|
||||
threads="all"
|
||||
updateSection={[MockFunction]}
|
||||
@ -92,7 +93,12 @@ exports[`components/user_settings/notifications/EmailNotificationSetting should
|
||||
section=""
|
||||
serverError=""
|
||||
submit={[Function]}
|
||||
title="Email Notifications"
|
||||
title={
|
||||
<Memo(MemoizedFormattedMessage)
|
||||
defaultMessage="Email Notifications"
|
||||
id="user.settings.notifications.emailNotifications"
|
||||
/>
|
||||
}
|
||||
updateSection={[Function]}
|
||||
>
|
||||
<section
|
||||
@ -102,7 +108,14 @@ exports[`components/user_settings/notifications/EmailNotificationSetting should
|
||||
className="col-sm-12 section-title"
|
||||
id="settingTitle"
|
||||
>
|
||||
Email Notifications
|
||||
<FormattedMessage
|
||||
defaultMessage="Email Notifications"
|
||||
id="user.settings.notifications.emailNotifications"
|
||||
>
|
||||
<span>
|
||||
Email Notifications
|
||||
</span>
|
||||
</FormattedMessage>
|
||||
</h4>
|
||||
<div
|
||||
className="col-sm-9 col-sm-offset-3"
|
||||
@ -266,7 +279,7 @@ exports[`components/user_settings/notifications/EmailNotificationSetting should
|
||||
`;
|
||||
|
||||
exports[`components/user_settings/notifications/EmailNotificationSetting should match snapshot, active section != email and SendEmailNotifications !== true 1`] = `
|
||||
<Connect(SettingItemMin)
|
||||
<SettingItemMin
|
||||
describe={
|
||||
<Memo(MemoizedFormattedMessage)
|
||||
defaultMessage="Email notifications are not enabled"
|
||||
@ -274,13 +287,18 @@ exports[`components/user_settings/notifications/EmailNotificationSetting should
|
||||
/>
|
||||
}
|
||||
section="email"
|
||||
title="Email Notifications"
|
||||
title={
|
||||
<Memo(MemoizedFormattedMessage)
|
||||
defaultMessage="Email Notifications"
|
||||
id="user.settings.notifications.emailNotifications"
|
||||
/>
|
||||
}
|
||||
updateSection={[Function]}
|
||||
/>
|
||||
`;
|
||||
|
||||
exports[`components/user_settings/notifications/EmailNotificationSetting should match snapshot, active section != email and SendEmailNotifications = true 1`] = `
|
||||
<Connect(SettingItemMin)
|
||||
<SettingItemMin
|
||||
describe={
|
||||
<Memo(MemoizedFormattedMessage)
|
||||
defaultMessage="Never"
|
||||
@ -288,13 +306,18 @@ exports[`components/user_settings/notifications/EmailNotificationSetting should
|
||||
/>
|
||||
}
|
||||
section="email"
|
||||
title="Email Notifications"
|
||||
title={
|
||||
<Memo(MemoizedFormattedMessage)
|
||||
defaultMessage="Email Notifications"
|
||||
id="user.settings.notifications.emailNotifications"
|
||||
/>
|
||||
}
|
||||
updateSection={[Function]}
|
||||
/>
|
||||
`;
|
||||
|
||||
exports[`components/user_settings/notifications/EmailNotificationSetting should match snapshot, active section != email, SendEmailNotifications = true and enableEmail = true 1`] = `
|
||||
<Connect(SettingItemMin)
|
||||
<SettingItemMin
|
||||
describe={
|
||||
<Memo(MemoizedFormattedMessage)
|
||||
defaultMessage="Immediately"
|
||||
@ -302,7 +325,12 @@ exports[`components/user_settings/notifications/EmailNotificationSetting should
|
||||
/>
|
||||
}
|
||||
section="email"
|
||||
title="Email Notifications"
|
||||
title={
|
||||
<Memo(MemoizedFormattedMessage)
|
||||
defaultMessage="Email Notifications"
|
||||
id="user.settings.notifications.emailNotifications"
|
||||
/>
|
||||
}
|
||||
updateSection={[Function]}
|
||||
/>
|
||||
`;
|
||||
@ -314,18 +342,19 @@ exports[`components/user_settings/notifications/EmailNotificationSetting should
|
||||
"savePreferences": [MockFunction],
|
||||
}
|
||||
}
|
||||
activeSection="email"
|
||||
active={true}
|
||||
areAllSectionsInactive={false}
|
||||
currentUserId="current_user_id"
|
||||
emailInterval={0}
|
||||
enableEmail={false}
|
||||
enableEmailBatching={true}
|
||||
error=""
|
||||
isCollapsedThreadsEnabled={false}
|
||||
onCancel={[MockFunction]}
|
||||
onChange={[MockFunction]}
|
||||
onSubmit={[MockFunction]}
|
||||
saving={false}
|
||||
sendEmailNotifications={true}
|
||||
serverError=""
|
||||
setParentState={[MockFunction]}
|
||||
threads="all"
|
||||
updateSection={[MockFunction]}
|
||||
@ -448,7 +477,12 @@ exports[`components/user_settings/notifications/EmailNotificationSetting should
|
||||
section=""
|
||||
serverError=""
|
||||
submit={[Function]}
|
||||
title="Email Notifications"
|
||||
title={
|
||||
<Memo(MemoizedFormattedMessage)
|
||||
defaultMessage="Email Notifications"
|
||||
id="user.settings.notifications.emailNotifications"
|
||||
/>
|
||||
}
|
||||
updateSection={[Function]}
|
||||
>
|
||||
<section
|
||||
@ -458,7 +492,14 @@ exports[`components/user_settings/notifications/EmailNotificationSetting should
|
||||
className="col-sm-12 section-title"
|
||||
id="settingTitle"
|
||||
>
|
||||
Email Notifications
|
||||
<FormattedMessage
|
||||
defaultMessage="Email Notifications"
|
||||
id="user.settings.notifications.emailNotifications"
|
||||
>
|
||||
<span>
|
||||
Email Notifications
|
||||
</span>
|
||||
</FormattedMessage>
|
||||
</h4>
|
||||
<div
|
||||
className="col-sm-9 col-sm-offset-3"
|
||||
@ -701,7 +742,12 @@ exports[`components/user_settings/notifications/EmailNotificationSetting should
|
||||
saving={false}
|
||||
section="email"
|
||||
serverError=""
|
||||
title="Email Notifications"
|
||||
title={
|
||||
<Memo(MemoizedFormattedMessage)
|
||||
defaultMessage="Email Notifications"
|
||||
id="user.settings.notifications.emailNotifications"
|
||||
/>
|
||||
}
|
||||
updateSection={[Function]}
|
||||
/>
|
||||
`;
|
||||
@ -776,7 +822,12 @@ exports[`components/user_settings/notifications/EmailNotificationSetting should
|
||||
section=""
|
||||
serverError="serverError"
|
||||
submit={[Function]}
|
||||
title="Email Notifications"
|
||||
title={
|
||||
<Memo(MemoizedFormattedMessage)
|
||||
defaultMessage="Email Notifications"
|
||||
id="user.settings.notifications.emailNotifications"
|
||||
/>
|
||||
}
|
||||
updateSection={[Function]}
|
||||
/>
|
||||
`;
|
||||
@ -889,7 +940,12 @@ exports[`components/user_settings/notifications/EmailNotificationSetting should
|
||||
section=""
|
||||
serverError=""
|
||||
submit={[Function]}
|
||||
title="Email Notifications"
|
||||
title={
|
||||
<Memo(MemoizedFormattedMessage)
|
||||
defaultMessage="Email Notifications"
|
||||
id="user.settings.notifications.emailNotifications"
|
||||
/>
|
||||
}
|
||||
updateSection={[Function]}
|
||||
/>
|
||||
`;
|
||||
@ -964,7 +1020,12 @@ exports[`components/user_settings/notifications/EmailNotificationSetting should
|
||||
section=""
|
||||
serverError=""
|
||||
submit={[Function]}
|
||||
title="Email Notifications"
|
||||
title={
|
||||
<Memo(MemoizedFormattedMessage)
|
||||
defaultMessage="Email Notifications"
|
||||
id="user.settings.notifications.emailNotifications"
|
||||
/>
|
||||
}
|
||||
updateSection={[Function]}
|
||||
/>
|
||||
`;
|
||||
|
@ -12,24 +12,25 @@ import {Preferences, NotificationLevels} from 'utils/constants';
|
||||
|
||||
describe('components/user_settings/notifications/EmailNotificationSetting', () => {
|
||||
const requiredProps: ComponentProps<typeof EmailNotificationSetting> = {
|
||||
currentUserId: 'current_user_id',
|
||||
activeSection: 'email',
|
||||
active: true,
|
||||
updateSection: jest.fn(),
|
||||
enableEmail: false,
|
||||
emailInterval: Preferences.INTERVAL_NEVER,
|
||||
onSubmit: jest.fn(),
|
||||
onCancel: jest.fn(),
|
||||
onChange: jest.fn(),
|
||||
serverError: '',
|
||||
saving: false,
|
||||
error: '',
|
||||
setParentState: jest.fn(),
|
||||
areAllSectionsInactive: false,
|
||||
isCollapsedThreadsEnabled: false,
|
||||
enableEmail: false,
|
||||
onChange: jest.fn(),
|
||||
threads: NotificationLevels.ALL,
|
||||
currentUserId: 'current_user_id',
|
||||
emailInterval: Preferences.INTERVAL_NEVER,
|
||||
sendEmailNotifications: true,
|
||||
enableEmailBatching: false,
|
||||
actions: {
|
||||
savePreferences: jest.fn(),
|
||||
},
|
||||
isCollapsedThreadsEnabled: false,
|
||||
threads: NotificationLevels.ALL,
|
||||
setParentState: jest.fn(),
|
||||
};
|
||||
|
||||
test('should match snapshot', () => {
|
||||
@ -68,7 +69,7 @@ describe('components/user_settings/notifications/EmailNotificationSetting', () =
|
||||
const props = {
|
||||
...requiredProps,
|
||||
sendEmailNotifications: false,
|
||||
activeSection: '',
|
||||
active: false,
|
||||
};
|
||||
const wrapper = shallow(<EmailNotificationSetting {...props}/>);
|
||||
|
||||
@ -79,7 +80,7 @@ describe('components/user_settings/notifications/EmailNotificationSetting', () =
|
||||
const props = {
|
||||
...requiredProps,
|
||||
sendEmailNotifications: true,
|
||||
activeSection: '',
|
||||
active: false,
|
||||
};
|
||||
const wrapper = shallow(<EmailNotificationSetting {...props}/>);
|
||||
|
||||
@ -90,7 +91,7 @@ describe('components/user_settings/notifications/EmailNotificationSetting', () =
|
||||
const props = {
|
||||
...requiredProps,
|
||||
sendEmailNotifications: true,
|
||||
activeSection: '',
|
||||
active: false,
|
||||
enableEmail: true,
|
||||
};
|
||||
const wrapper = shallow(<EmailNotificationSetting {...props}/>);
|
||||
@ -100,7 +101,7 @@ describe('components/user_settings/notifications/EmailNotificationSetting', () =
|
||||
|
||||
test('should match snapshot, on serverError', () => {
|
||||
const newServerError = 'serverError';
|
||||
const props = {...requiredProps, serverError: newServerError};
|
||||
const props = {...requiredProps, error: newServerError};
|
||||
const wrapper = shallow(<EmailNotificationSetting {...props}/>);
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
|
@ -1,8 +1,7 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import React from 'react';
|
||||
import type {RefObject} from 'react';
|
||||
import React, {type RefObject} from 'react';
|
||||
import {FormattedMessage} from 'react-intl';
|
||||
|
||||
import type {PreferenceType} from '@mattermost/types/preferences';
|
||||
@ -12,37 +11,38 @@ import {getEmailInterval} from 'mattermost-redux/utils/notify_props';
|
||||
|
||||
import SettingItemMax from 'components/setting_item_max';
|
||||
import SettingItemMin from 'components/setting_item_min';
|
||||
import type SettingItemMinComponent from 'components/setting_item_min/setting_item_min';
|
||||
import type SettingItemMinComponent from 'components/setting_item_min';
|
||||
|
||||
import {Preferences, NotificationLevels} from 'utils/constants';
|
||||
import {a11yFocus, localizeMessage} from 'utils/utils';
|
||||
import {a11yFocus} from 'utils/utils';
|
||||
|
||||
const SECONDS_PER_MINUTE = 60;
|
||||
|
||||
type Props = {
|
||||
currentUserId: string;
|
||||
activeSection: string;
|
||||
active: boolean;
|
||||
updateSection: (section: string) => void;
|
||||
enableEmail: boolean;
|
||||
emailInterval: number;
|
||||
onSubmit: () => void;
|
||||
onCancel: () => void;
|
||||
onChange: (enableEmail: UserNotifyProps['email']) => void;
|
||||
serverError?: string;
|
||||
saving?: boolean;
|
||||
error?: string;
|
||||
setParentState: (key: string, value: any) => void;
|
||||
areAllSectionsInactive: boolean;
|
||||
isCollapsedThreadsEnabled: boolean;
|
||||
enableEmail: boolean;
|
||||
onChange: (enableEmail: UserNotifyProps['email']) => void;
|
||||
threads: string;
|
||||
currentUserId: string;
|
||||
emailInterval: number;
|
||||
sendEmailNotifications: boolean;
|
||||
enableEmailBatching: boolean;
|
||||
actions: {
|
||||
savePreferences: (currentUserId: string, emailIntervalPreference: PreferenceType[]) =>
|
||||
Promise<{data: boolean}>;
|
||||
};
|
||||
isCollapsedThreadsEnabled: boolean;
|
||||
threads: string;
|
||||
setParentState: (key: string, value: any) => void;
|
||||
};
|
||||
|
||||
type State = {
|
||||
activeSection: string;
|
||||
active: boolean;
|
||||
emailInterval: number;
|
||||
enableEmail: boolean;
|
||||
enableEmailBatching: boolean;
|
||||
@ -51,7 +51,7 @@ type State = {
|
||||
};
|
||||
|
||||
export default class EmailNotificationSetting extends React.PureComponent<Props, State> {
|
||||
minRef: RefObject<SettingItemMinComponent>;
|
||||
editButtonRef: RefObject<SettingItemMinComponent>;
|
||||
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
@ -61,11 +61,11 @@ export default class EmailNotificationSetting extends React.PureComponent<Props,
|
||||
enableEmail,
|
||||
enableEmailBatching,
|
||||
sendEmailNotifications,
|
||||
activeSection,
|
||||
active,
|
||||
} = props;
|
||||
|
||||
this.state = {
|
||||
activeSection,
|
||||
active,
|
||||
emailInterval,
|
||||
enableEmail,
|
||||
enableEmailBatching,
|
||||
@ -73,7 +73,7 @@ export default class EmailNotificationSetting extends React.PureComponent<Props,
|
||||
newInterval: getEmailInterval(enableEmail && sendEmailNotifications, enableEmailBatching, emailInterval),
|
||||
};
|
||||
|
||||
this.minRef = React.createRef();
|
||||
this.editButtonRef = React.createRef();
|
||||
}
|
||||
|
||||
static getDerivedStateFromProps(nextProps: Props, prevState: State) {
|
||||
@ -82,13 +82,13 @@ export default class EmailNotificationSetting extends React.PureComponent<Props,
|
||||
enableEmail,
|
||||
enableEmailBatching,
|
||||
sendEmailNotifications,
|
||||
activeSection,
|
||||
active,
|
||||
} = nextProps;
|
||||
|
||||
// If we're re-opening this section, reset to defaults from props
|
||||
if (activeSection === 'email' && prevState.activeSection !== 'email') {
|
||||
if (active && !prevState.active) {
|
||||
return {
|
||||
activeSection,
|
||||
active,
|
||||
emailInterval,
|
||||
enableEmail,
|
||||
enableEmailBatching,
|
||||
@ -100,10 +100,10 @@ export default class EmailNotificationSetting extends React.PureComponent<Props,
|
||||
if (sendEmailNotifications !== prevState.sendEmailNotifications ||
|
||||
enableEmailBatching !== prevState.enableEmailBatching ||
|
||||
emailInterval !== prevState.emailInterval ||
|
||||
activeSection !== prevState.activeSection
|
||||
active !== prevState.active
|
||||
) {
|
||||
return {
|
||||
activeSection,
|
||||
active,
|
||||
emailInterval,
|
||||
enableEmail,
|
||||
enableEmailBatching,
|
||||
@ -116,7 +116,7 @@ export default class EmailNotificationSetting extends React.PureComponent<Props,
|
||||
}
|
||||
|
||||
focusEditButton(): void {
|
||||
this.minRef.current?.focus();
|
||||
this.editButtonRef.current?.focus();
|
||||
}
|
||||
|
||||
handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
@ -234,11 +234,16 @@ export default class EmailNotificationSetting extends React.PureComponent<Props,
|
||||
|
||||
return (
|
||||
<SettingItemMin
|
||||
title={localizeMessage('user.settings.notifications.emailNotifications', 'Email Notifications')}
|
||||
ref={this.editButtonRef}
|
||||
title={
|
||||
<FormattedMessage
|
||||
id={'user.settings.notifications.emailNotifications'}
|
||||
defaultMessage={'Email Notifications'}
|
||||
/>
|
||||
}
|
||||
describe={description}
|
||||
section={'email'}
|
||||
updateSection={this.handleUpdateSection}
|
||||
ref={this.minRef}
|
||||
/>
|
||||
);
|
||||
};
|
||||
@ -247,7 +252,12 @@ export default class EmailNotificationSetting extends React.PureComponent<Props,
|
||||
if (!this.props.sendEmailNotifications) {
|
||||
return (
|
||||
<SettingItemMax
|
||||
title={localizeMessage('user.settings.notifications.emailNotifications', 'Email Notifications')}
|
||||
title={
|
||||
<FormattedMessage
|
||||
id={'user.settings.notifications.emailNotifications'}
|
||||
defaultMessage={'Email Notifications'}
|
||||
/>
|
||||
}
|
||||
inputs={[
|
||||
<div
|
||||
key='oauthEmailInfo'
|
||||
@ -259,7 +269,7 @@ export default class EmailNotificationSetting extends React.PureComponent<Props,
|
||||
/>
|
||||
</div>,
|
||||
]}
|
||||
serverError={this.props.serverError}
|
||||
serverError={this.props.error}
|
||||
section={'email'}
|
||||
updateSection={this.handleUpdateSection}
|
||||
/>
|
||||
@ -359,7 +369,12 @@ export default class EmailNotificationSetting extends React.PureComponent<Props,
|
||||
|
||||
return (
|
||||
<SettingItemMax
|
||||
title={localizeMessage('user.settings.notifications.emailNotifications', 'Email Notifications')}
|
||||
title={
|
||||
<FormattedMessage
|
||||
id={'user.settings.notifications.emailNotifications'}
|
||||
defaultMessage={'Email Notifications'}
|
||||
/>
|
||||
}
|
||||
inputs={[
|
||||
<fieldset key='userNotificationEmailOptions'>
|
||||
<legend className='form-legend'>
|
||||
@ -416,23 +431,23 @@ export default class EmailNotificationSetting extends React.PureComponent<Props,
|
||||
]}
|
||||
submit={this.handleSubmit}
|
||||
saving={this.props.saving}
|
||||
serverError={this.props.serverError}
|
||||
serverError={this.props.error}
|
||||
updateSection={this.handleUpdateSection}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
componentDidUpdate(prevProps: Props) {
|
||||
if (prevProps.activeSection === 'email' && this.props.activeSection === '') {
|
||||
if (prevProps.active && !this.props.active && this.props.areAllSectionsInactive) {
|
||||
this.focusEditButton();
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
if (this.props.activeSection !== 'email') {
|
||||
return this.renderMinSettingView();
|
||||
if (this.props.active) {
|
||||
return this.renderMaxSettingView();
|
||||
}
|
||||
|
||||
return this.renderMaxSettingView();
|
||||
return this.renderMinSettingView();
|
||||
}
|
||||
}
|
||||
|
@ -4,11 +4,14 @@
|
||||
import {connect, type ConnectedProps} from 'react-redux';
|
||||
|
||||
import {updateMe} from 'mattermost-redux/actions/users';
|
||||
import {getConfig} from 'mattermost-redux/selectors/entities/general';
|
||||
import {getSubscriptionProduct} from 'mattermost-redux/selectors/entities/cloud';
|
||||
import {getConfig, getLicense} from 'mattermost-redux/selectors/entities/general';
|
||||
import {isCollapsedThreadsEnabled} from 'mattermost-redux/selectors/entities/preferences';
|
||||
|
||||
import {isCallsEnabled, isCallsRingingEnabledOnServer} from 'selectors/calls';
|
||||
|
||||
import {isEnterpriseOrCloudOrSKUStarterFree} from 'utils/license_utils';
|
||||
|
||||
import type {GlobalState} from 'types/store';
|
||||
|
||||
import UserSettingsNotifications from './user_settings_notifications';
|
||||
@ -18,11 +21,20 @@ const mapStateToProps = (state: GlobalState) => {
|
||||
|
||||
const sendPushNotifications = config.SendPushNotifications === 'true';
|
||||
const enableAutoResponder = config.ExperimentalEnableAutomaticReplies === 'true';
|
||||
|
||||
const license = getLicense(state);
|
||||
const subscriptionProduct = getSubscriptionProduct(state);
|
||||
|
||||
const isEnterpriseReady = config.BuildEnterpriseReady === 'true';
|
||||
|
||||
return {
|
||||
sendPushNotifications,
|
||||
enableAutoResponder,
|
||||
isCollapsedThreadsEnabled: isCollapsedThreadsEnabled(state),
|
||||
isCallsRingingEnabled: isCallsEnabled(state, '0.17.0') && isCallsRingingEnabledOnServer(state),
|
||||
isEnterpriseOrCloudOrSKUStarterFree: isEnterpriseOrCloudOrSKUStarterFree(license, subscriptionProduct, isEnterpriseReady),
|
||||
isEnterpriseReady,
|
||||
|
||||
};
|
||||
};
|
||||
|
||||
@ -34,4 +46,4 @@ const connector = connect(mapStateToProps, mapDispatchToProps);
|
||||
|
||||
export type PropsFromRedux = ConnectedProps<typeof connector>;
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(UserSettingsNotifications);
|
||||
export default connector(UserSettingsNotifications);
|
||||
|
@ -22,6 +22,8 @@ describe('components/user_settings/display/UserSettingsDisplay', () => {
|
||||
enableAutoResponder: false,
|
||||
isCallsRingingEnabled: true,
|
||||
intl: {} as IntlShape,
|
||||
isEnterpriseOrCloudOrSKUStarterFree: false,
|
||||
isEnterpriseReady: true,
|
||||
};
|
||||
|
||||
test('should match snapshot', () => {
|
||||
@ -32,6 +34,26 @@ describe('components/user_settings/display/UserSettingsDisplay', () => {
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('should match snapshot when its a starter free', () => {
|
||||
const props = {...defaultProps, isEnterpriseOrCloudOrSKUStarterFree: true};
|
||||
|
||||
const wrapper = renderWithContext(
|
||||
<UserSettingsNotifications {...props}/>,
|
||||
);
|
||||
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('should match snapshot when its team edition', () => {
|
||||
const props = {...defaultProps, isEnterpriseReady: false};
|
||||
|
||||
const wrapper = renderWithContext(
|
||||
<UserSettingsNotifications {...props}/>,
|
||||
);
|
||||
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('should show reply notifications section when CRT off', () => {
|
||||
const props = {...defaultProps, isCollapsedThreadsEnabled: false};
|
||||
|
||||
|
@ -19,8 +19,9 @@ import type {ActionResult} from 'mattermost-redux/types/actions';
|
||||
import ExternalLink from 'components/external_link';
|
||||
import SettingItem from 'components/setting_item';
|
||||
import SettingItemMax from 'components/setting_item_max';
|
||||
import RestrictedIndicator from 'components/widgets/menu/menu_items/restricted_indicator';
|
||||
|
||||
import Constants, {NotificationLevels} from 'utils/constants';
|
||||
import Constants, {NotificationLevels, MattermostFeatures, LicenseSkus} from 'utils/constants';
|
||||
import {stopTryNotificationRing} from 'utils/notification_sounds';
|
||||
import {a11yFocus} from 'utils/utils';
|
||||
|
||||
@ -65,6 +66,8 @@ type State = {
|
||||
isCustomKeysWithNotificationInputChecked: boolean;
|
||||
customKeysWithNotification: MultiInputValue[];
|
||||
customKeysWithNotificationInputValue: string;
|
||||
customKeysWithHighlight: MultiInputValue[];
|
||||
customKeysWithHighlightInputValue: string;
|
||||
firstNameKey: boolean;
|
||||
channelKey: boolean;
|
||||
autoResponderActive: boolean;
|
||||
@ -145,9 +148,10 @@ function getDefaultStateFromProps(props: Props): State {
|
||||
let channelKey = false;
|
||||
let isCustomKeysWithNotificationInputChecked = false;
|
||||
const customKeysWithNotification: MultiInputValue[] = [];
|
||||
const customKeysWithHighlight: MultiInputValue[] = [];
|
||||
|
||||
if (props.user.notify_props) {
|
||||
if (props.user.notify_props.mention_keys) {
|
||||
if (props.user.notify_props?.mention_keys?.length > 0) {
|
||||
const mentionKeys = props.user.notify_props.mention_keys.split(',').filter((key) => key.length > 0);
|
||||
mentionKeys.forEach((mentionKey) => {
|
||||
// Remove username(s) from list of keys
|
||||
@ -166,6 +170,16 @@ function getDefaultStateFromProps(props: Props): State {
|
||||
isCustomKeysWithNotificationInputChecked = customKeysWithNotification.length > 0;
|
||||
}
|
||||
|
||||
if (props.user.notify_props?.highlight_keys?.length > 0) {
|
||||
const highlightKeys = props.user.notify_props.highlight_keys.split(',').filter((key) => key.length > 0);
|
||||
highlightKeys.forEach((highlightKey) => {
|
||||
customKeysWithHighlight.push({
|
||||
label: highlightKey,
|
||||
value: highlightKey,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
firstNameKey = props.user.notify_props?.first_name === 'true';
|
||||
channelKey = props.user.notify_props?.channel === 'true';
|
||||
}
|
||||
@ -186,6 +200,8 @@ function getDefaultStateFromProps(props: Props): State {
|
||||
customKeysWithNotification,
|
||||
isCustomKeysWithNotificationInputChecked,
|
||||
customKeysWithNotificationInputValue: '',
|
||||
customKeysWithHighlight,
|
||||
customKeysWithHighlightInputValue: '',
|
||||
firstNameKey,
|
||||
channelKey,
|
||||
autoResponderActive,
|
||||
@ -249,6 +265,14 @@ class NotificationsTab extends React.PureComponent<Props, State> {
|
||||
}
|
||||
data.mention_keys = mentionKeys.join(',');
|
||||
|
||||
const highlightKeys: string[] = [];
|
||||
if (this.state.customKeysWithHighlight.length > 0) {
|
||||
this.state.customKeysWithHighlight.forEach((key) => {
|
||||
highlightKeys.push(key.value);
|
||||
});
|
||||
}
|
||||
data.highlight_keys = highlightKeys.join(',');
|
||||
|
||||
this.setState({isSaving: true});
|
||||
stopTryNotificationRing();
|
||||
|
||||
@ -394,6 +418,61 @@ class NotificationsTab extends React.PureComponent<Props, State> {
|
||||
}
|
||||
};
|
||||
|
||||
handleChangeForCustomKeysWithHightlightInput = (values: ValueType<{ value: string }>) => {
|
||||
if (values && Array.isArray(values) && values.length > 0) {
|
||||
const customKeysWithHighlight = values.
|
||||
map((value: MultiInputValue) => {
|
||||
const formattedValue = value.value.trim();
|
||||
return {value: formattedValue, label: formattedValue};
|
||||
}).
|
||||
filter((value) => value.value.length > 0);
|
||||
this.setState({customKeysWithHighlight});
|
||||
} else {
|
||||
this.setState({
|
||||
customKeysWithHighlight: [],
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
handleChangeForCustomKeysWithHighlightInputValue = (value: string) => {
|
||||
if (!value.includes(Constants.KeyCodes.COMMA[0])) {
|
||||
this.setState({customKeysWithHighlightInputValue: value});
|
||||
}
|
||||
};
|
||||
|
||||
updateCustomKeysWithHighlightWithInputValue = (newValue: State['customKeysWithHighlightInputValue']) => {
|
||||
const unsavedCustomKeyWithHighlight = newValue?.trim()?.replace(COMMA_REGEX, '') ?? '';
|
||||
|
||||
if (unsavedCustomKeyWithHighlight.length > 0) {
|
||||
const customKeysWithHighlight = [
|
||||
...this.state.customKeysWithHighlight,
|
||||
{
|
||||
value: unsavedCustomKeyWithHighlight,
|
||||
label: unsavedCustomKeyWithHighlight,
|
||||
},
|
||||
];
|
||||
|
||||
this.setState({
|
||||
customKeysWithHighlight,
|
||||
customKeysWithHighlightInputValue: '',
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
handleBlurForCustomKeysWithHighlightInput = () => {
|
||||
this.updateCustomKeysWithHighlightWithInputValue(this.state.customKeysWithHighlightInputValue);
|
||||
};
|
||||
|
||||
handleOnKeydownForCustomKeysWithHighlightInput = (event: React.KeyboardEvent) => {
|
||||
if (event.key === Constants.KeyCodes.COMMA[0] || event.key === Constants.KeyCodes.TAB[0]) {
|
||||
this.updateCustomKeysWithHighlightWithInputValue(this.state.customKeysWithHighlightInputValue);
|
||||
}
|
||||
};
|
||||
|
||||
handleCloseSettingsModal = () => {
|
||||
this.props.closeModal();
|
||||
};
|
||||
|
||||
createPushNotificationSection = () => {
|
||||
const active = this.props.activeSection === 'push';
|
||||
const inputs = [];
|
||||
@ -824,7 +903,7 @@ class NotificationsTab extends React.PureComponent<Props, State> {
|
||||
|
||||
expandedSection = (
|
||||
<SettingItemMax
|
||||
title={this.props.intl.formatMessage({id: 'user.settings.notifications.keywordsWithNotification.title', defaultMessage: 'Keywords that trigger Notifications'})}
|
||||
title={this.props.intl.formatMessage({id: 'user.settings.notifications.keywordsWithNotification.title', defaultMessage: 'Keywords That Trigger Notifications'})}
|
||||
inputs={inputs}
|
||||
submit={this.handleSubmit}
|
||||
saving={this.state.isSaving}
|
||||
@ -855,7 +934,7 @@ class NotificationsTab extends React.PureComponent<Props, State> {
|
||||
|
||||
return (
|
||||
<SettingItem
|
||||
title={this.props.intl.formatMessage({id: 'user.settings.notifications.keywordsWithNotification.title', defaultMessage: 'Keywords that trigger Notifications'})}
|
||||
title={this.props.intl.formatMessage({id: 'user.settings.notifications.keywordsWithNotification.title', defaultMessage: 'Keywords That Trigger Notifications'})}
|
||||
section='keysWithNotification'
|
||||
active={isSectionExpanded}
|
||||
areAllSectionsInactive={this.props.activeSection === ''}
|
||||
@ -865,6 +944,140 @@ class NotificationsTab extends React.PureComponent<Props, State> {
|
||||
/>);
|
||||
};
|
||||
|
||||
createKeywordsWithHighlightSection = () => {
|
||||
const isSectionExpanded = this.props.activeSection === 'keysWithHighlight';
|
||||
|
||||
let expandedSection = null;
|
||||
if (isSectionExpanded) {
|
||||
const inputs = [(
|
||||
<div
|
||||
key='userNotificationHighlightOption'
|
||||
className='customKeywordsWithNotificationSubsection'
|
||||
>
|
||||
<label htmlFor='mentionKeysWithHighlightInput'>
|
||||
<FormattedMessage
|
||||
id='user.settings.notifications.keywordsWithHighlight.inputTitle'
|
||||
defaultMessage='Enter non case-sensitive keywords, press Tab or use commas to separate them:'
|
||||
/>
|
||||
</label>
|
||||
<CreatableReactSelect
|
||||
inputId='mentionKeysWithHighlightInput'
|
||||
autoFocus={true}
|
||||
isClearable={false}
|
||||
isMulti={true}
|
||||
styles={customKeywordsWithNotificationStyles}
|
||||
className='multiInput'
|
||||
placeholder=''
|
||||
components={{
|
||||
DropdownIndicator: () => null,
|
||||
Menu: () => null,
|
||||
MenuList: () => null,
|
||||
}}
|
||||
aria-labelledby='mentionKeysWithHighlightInput'
|
||||
onChange={this.handleChangeForCustomKeysWithHightlightInput}
|
||||
value={this.state.customKeysWithHighlight}
|
||||
inputValue={this.state.customKeysWithHighlightInputValue}
|
||||
onInputChange={this.handleChangeForCustomKeysWithHighlightInputValue}
|
||||
onBlur={this.handleBlurForCustomKeysWithHighlightInput}
|
||||
onKeyDown={this.handleOnKeydownForCustomKeysWithHighlightInput}
|
||||
/>
|
||||
</div>
|
||||
)];
|
||||
|
||||
const extraInfo = (
|
||||
<FormattedMessage
|
||||
id='user.settings.notifications.keywordsWithHighlight.extraInfo'
|
||||
defaultMessage='These keywords will be shown to you with a highlight when anyone sends a message that includes them.'
|
||||
/>
|
||||
);
|
||||
|
||||
expandedSection = (
|
||||
<SettingItemMax
|
||||
title={this.props.intl.formatMessage({id: 'user.settings.notifications.keywordsWithHighlight.title', defaultMessage: 'Keywords That Get Highlighted (Without Notifications)'})}
|
||||
inputs={inputs}
|
||||
submit={this.handleSubmit}
|
||||
saving={this.state.isSaving}
|
||||
serverError={this.state.serverError}
|
||||
extraInfo={extraInfo}
|
||||
updateSection={this.handleUpdateSection}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
let collapsedDescription = this.props.intl.formatMessage({id: 'user.settings.notifications.keywordsWithHighlight.none', defaultMessage: 'None'});
|
||||
if (!this.props.isEnterpriseOrCloudOrSKUStarterFree && this.props.isEnterpriseReady && this.state.customKeysWithHighlight.length > 0) {
|
||||
const customKeysWithHighlightStringArray = this.state.customKeysWithHighlight.map((key) => key.value);
|
||||
collapsedDescription = customKeysWithHighlightStringArray.map((key) => `"${key}"`).join(', ');
|
||||
}
|
||||
|
||||
const collapsedEditButtonWhenDisabled = (
|
||||
<RestrictedIndicator
|
||||
blocked={this.props.isEnterpriseOrCloudOrSKUStarterFree && this.props.isEnterpriseReady}
|
||||
feature={MattermostFeatures.HIGHLIGHT_WITHOUT_NOTIFICATION}
|
||||
minimumPlanRequiredForFeature={LicenseSkus.Professional}
|
||||
tooltipTitle={this.props.intl.formatMessage({
|
||||
id: 'user.settings.notifications.keywordsWithHighlight.disabledTooltipTitle',
|
||||
defaultMessage: 'Professional feature',
|
||||
})}
|
||||
tooltipMessageBlocked={this.props.intl.formatMessage({
|
||||
id: 'user.settings.notifications.keywordsWithHighlight.disabledTooltipMessage',
|
||||
defaultMessage:
|
||||
'This feature is available on the Professional plan',
|
||||
})}
|
||||
titleAdminPreTrial={this.props.intl.formatMessage({
|
||||
id: 'user.settings.notifications.keywordsWithHighlight.userModal.titleAdminPreTrial',
|
||||
defaultMessage: 'Highlight keywords without notifications with Mattermost Professional',
|
||||
})}
|
||||
messageAdminPreTrial={this.props.intl.formatMessage({
|
||||
id: 'user.settings.notifications.keywordsWithHighlight.userModal.messageAdminPreTrial',
|
||||
defaultMessage: 'Get the ability to passively highlight keywords that you care about. Upgrade to Professional plan to unlock this feature.',
|
||||
})}
|
||||
titleAdminPostTrial={this.props.intl.formatMessage({
|
||||
id: 'user.settings.notifications.keywordsWithHighlight.userModal.titleAdminPostTrial',
|
||||
defaultMessage: 'Highlight keywords without notifications with Mattermost Professional',
|
||||
})}
|
||||
messageAdminPostTrial={this.props.intl.formatMessage({
|
||||
id: 'user.settings.notifications.keywordsWithHighlight.userModal.messageAdminPostTrial',
|
||||
defaultMessage: 'Get the ability to passively highlight keywords that you care about. Upgrade to Professional plan to unlock this feature.',
|
||||
},
|
||||
)}
|
||||
titleEndUser={this.props.intl.formatMessage({
|
||||
id: 'user.settings.notifications.keywordsWithHighlight.userModal.titleEndUser',
|
||||
defaultMessage: 'Highlight keywords without notifications with Mattermost Professional',
|
||||
})}
|
||||
messageEndUser={this.props.intl.formatMessage(
|
||||
{
|
||||
id: 'user.settings.notifications.keywordsWithHighlight.userModal.messageEndUser',
|
||||
defaultMessage: 'Get the ability to passively highlight keywords that you care about.{br}{br}Request your admin to upgrade to Mattermost Professional to access this feature.',
|
||||
},
|
||||
{
|
||||
br: <br/>,
|
||||
},
|
||||
)}
|
||||
ctaExtraContent={
|
||||
<FormattedMessage
|
||||
id='user.settings.notifications.keywordsWithHighlight.professional'
|
||||
defaultMessage='Professional'
|
||||
/>
|
||||
}
|
||||
clickCallback={this.handleCloseSettingsModal}
|
||||
/>
|
||||
);
|
||||
|
||||
return (
|
||||
<SettingItem
|
||||
title={this.props.intl.formatMessage({id: 'user.settings.notifications.keywordsWithHighlight.title', defaultMessage: 'Keywords That Get Highlighted (Without Notifications)'})}
|
||||
section='keysWithHighlight'
|
||||
active={isSectionExpanded}
|
||||
areAllSectionsInactive={this.props.activeSection === ''}
|
||||
describe={collapsedDescription}
|
||||
updateSection={this.handleUpdateSection}
|
||||
max={expandedSection}
|
||||
isDisabled={this.props.isEnterpriseOrCloudOrSKUStarterFree && this.props.isEnterpriseReady}
|
||||
collapsedEditButtonWhenDisabled={collapsedEditButtonWhenDisabled}
|
||||
/>);
|
||||
};
|
||||
|
||||
createCommentsSection = () => {
|
||||
const serverError = this.state.serverError;
|
||||
|
||||
@ -887,7 +1100,7 @@ class NotificationsTab extends React.PureComponent<Props, State> {
|
||||
<legend className='form-legend hidden-label'>
|
||||
<FormattedMessage
|
||||
id='user.settings.notifications.comments'
|
||||
defaultMessage='Reply notifications'
|
||||
defaultMessage='Reply Notifications'
|
||||
/>
|
||||
</legend>
|
||||
<div className='radio'>
|
||||
@ -951,7 +1164,7 @@ class NotificationsTab extends React.PureComponent<Props, State> {
|
||||
|
||||
max = (
|
||||
<SettingItemMax
|
||||
title={this.props.intl.formatMessage({id: 'user.settings.notifications.comments', defaultMessage: 'Reply notifications'})}
|
||||
title={this.props.intl.formatMessage({id: 'user.settings.notifications.comments', defaultMessage: 'Reply Notifications'})}
|
||||
extraInfo={extraInfo}
|
||||
inputs={inputs}
|
||||
submit={this.handleSubmit}
|
||||
@ -1046,6 +1259,7 @@ class NotificationsTab extends React.PureComponent<Props, State> {
|
||||
render() {
|
||||
const pushNotificationSection = this.createPushNotificationSection();
|
||||
const keywordsWithNotificationSection = this.createKeywordsWithNotificationSection();
|
||||
const keywordsWithHighlightSection = this.createKeywordsWithHighlightSection();
|
||||
const commentsSection = this.createCommentsSection();
|
||||
const autoResponderSection = this.createAutoResponderSection();
|
||||
|
||||
@ -1110,50 +1324,68 @@ class NotificationsTab extends React.PureComponent<Props, State> {
|
||||
</div>
|
||||
<div className='divider-dark first'/>
|
||||
<DesktopNotificationSettings
|
||||
active={this.props.activeSection === 'desktop'}
|
||||
updateSection={this.handleUpdateSection}
|
||||
onSubmit={this.handleSubmit}
|
||||
onCancel={this.handleCancel}
|
||||
saving={this.state.isSaving}
|
||||
error={this.state.serverError}
|
||||
setParentState={this.setStateValue}
|
||||
areAllSectionsInactive={this.props.activeSection === ''}
|
||||
isCollapsedThreadsEnabled={this.props.isCollapsedThreadsEnabled}
|
||||
activity={this.state.desktopActivity}
|
||||
threads={this.state.desktopThreads}
|
||||
sound={this.state.desktopSound}
|
||||
callsSound={this.state.callsDesktopSound}
|
||||
updateSection={this.handleUpdateSection}
|
||||
setParentState={this.setStateValue}
|
||||
submit={this.handleSubmit}
|
||||
saving={this.state.isSaving}
|
||||
cancel={this.handleCancel}
|
||||
error={this.state.serverError}
|
||||
active={this.props.activeSection === 'desktop'}
|
||||
selectedSound={this.state.desktopNotificationSound || 'default'}
|
||||
callsSelectedSound={this.state.callsNotificationSound || 'default'}
|
||||
isCollapsedThreadsEnabled={this.props.isCollapsedThreadsEnabled}
|
||||
areAllSectionsInactive={this.props.activeSection === ''}
|
||||
isCallsRingingEnabled={this.props.isCallsRingingEnabled}
|
||||
/>
|
||||
<div className='divider-light'/>
|
||||
<EmailNotificationSetting
|
||||
activeSection={this.props.activeSection}
|
||||
active={this.props.activeSection === 'email'}
|
||||
updateSection={this.handleUpdateSection}
|
||||
enableEmail={this.state.enableEmail === 'true'}
|
||||
onSubmit={this.handleSubmit}
|
||||
onCancel={this.handleCancel}
|
||||
onChange={this.handleEmailRadio}
|
||||
saving={this.state.isSaving}
|
||||
serverError={this.state.serverError}
|
||||
isCollapsedThreadsEnabled={this.props.isCollapsedThreadsEnabled}
|
||||
error={this.state.serverError}
|
||||
setParentState={this.setStateValue}
|
||||
areAllSectionsInactive={this.props.activeSection === ''}
|
||||
isCollapsedThreadsEnabled={this.props.isCollapsedThreadsEnabled}
|
||||
enableEmail={this.state.enableEmail === 'true'}
|
||||
onChange={this.handleEmailRadio}
|
||||
threads={this.state.emailThreads || ''}
|
||||
/>
|
||||
<div className='divider-light'/>
|
||||
{pushNotificationSection}
|
||||
<div className='divider-light'/>
|
||||
{keywordsWithNotificationSection}
|
||||
{(!this.props.isEnterpriseOrCloudOrSKUStarterFree && this.props.isEnterpriseReady) && (
|
||||
<>
|
||||
<div className='divider-light'/>
|
||||
{keywordsWithHighlightSection}
|
||||
</>
|
||||
)}
|
||||
<div className='divider-light'/>
|
||||
{!this.props.isCollapsedThreadsEnabled && (
|
||||
<>
|
||||
{commentsSection}
|
||||
<div className='divider-light'/>
|
||||
{commentsSection}
|
||||
</>
|
||||
)}
|
||||
{this.props.enableAutoResponder && (
|
||||
autoResponderSection
|
||||
<>
|
||||
<div className='divider-light'/>
|
||||
{autoResponderSection}
|
||||
</>
|
||||
)}
|
||||
|
||||
{/* We placed the disabled items in the last */}
|
||||
{(this.props.isEnterpriseOrCloudOrSKUStarterFree && this.props.isEnterpriseReady) && (
|
||||
<>
|
||||
<div className='divider-light'/>
|
||||
{keywordsWithHighlightSection}
|
||||
</>
|
||||
)}
|
||||
<div className='divider-dark'/>
|
||||
</div>
|
||||
|
@ -53,10 +53,7 @@ exports[`components/user_settings/display/UserSettingsDisplay should match snaps
|
||||
<SettingItem
|
||||
active={false}
|
||||
areAllSectionsInactive={false}
|
||||
containerStyle=""
|
||||
infoPosition="bottom"
|
||||
max={null}
|
||||
saving={false}
|
||||
section="password"
|
||||
title={
|
||||
<Memo(MemoizedFormattedMessage)
|
||||
@ -97,16 +94,13 @@ exports[`components/user_settings/display/UserSettingsDisplay should match snaps
|
||||
<SettingItem
|
||||
active={false}
|
||||
areAllSectionsInactive={false}
|
||||
containerStyle=""
|
||||
describe={
|
||||
<Memo(MemoizedFormattedMessage)
|
||||
defaultMessage="Email and Password"
|
||||
id="user.settings.security.emailPwd"
|
||||
/>
|
||||
}
|
||||
infoPosition="bottom"
|
||||
max={null}
|
||||
saving={false}
|
||||
section="signin"
|
||||
title="Sign-in Method"
|
||||
updateSection={[Function]}
|
||||
@ -115,7 +109,7 @@ exports[`components/user_settings/display/UserSettingsDisplay should match snaps
|
||||
className="divider-dark"
|
||||
/>
|
||||
<br />
|
||||
<Connect(ToggleModalButton)
|
||||
<ToggleModalButton
|
||||
className="security-links color--link"
|
||||
dialogType={
|
||||
Object {
|
||||
@ -140,8 +134,8 @@ exports[`components/user_settings/display/UserSettingsDisplay should match snaps
|
||||
defaultMessage="View Access History"
|
||||
id="user.settings.security.viewHistory"
|
||||
/>
|
||||
</Connect(ToggleModalButton)>
|
||||
<Connect(ToggleModalButton)
|
||||
</ToggleModalButton>
|
||||
<ToggleModalButton
|
||||
className="security-links color--link mt-2"
|
||||
dialogType={
|
||||
Object {
|
||||
@ -162,7 +156,7 @@ exports[`components/user_settings/display/UserSettingsDisplay should match snaps
|
||||
defaultMessage="View and Log Out of Active Sessions"
|
||||
id="user.settings.security.logoutActiveSessions"
|
||||
/>
|
||||
</Connect(ToggleModalButton)>
|
||||
</ToggleModalButton>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
@ -220,10 +214,7 @@ exports[`components/user_settings/display/UserSettingsDisplay should match snaps
|
||||
<SettingItem
|
||||
active={false}
|
||||
areAllSectionsInactive={false}
|
||||
containerStyle=""
|
||||
infoPosition="bottom"
|
||||
max={null}
|
||||
saving={false}
|
||||
section="password"
|
||||
title={
|
||||
<Memo(MemoizedFormattedMessage)
|
||||
@ -264,16 +255,13 @@ exports[`components/user_settings/display/UserSettingsDisplay should match snaps
|
||||
<SettingItem
|
||||
active={false}
|
||||
areAllSectionsInactive={false}
|
||||
containerStyle=""
|
||||
describe={
|
||||
<Memo(MemoizedFormattedMessage)
|
||||
defaultMessage="Email and Password"
|
||||
id="user.settings.security.emailPwd"
|
||||
/>
|
||||
}
|
||||
infoPosition="bottom"
|
||||
max={null}
|
||||
saving={false}
|
||||
section="signin"
|
||||
title="Sign-in Method"
|
||||
updateSection={[Function]}
|
||||
@ -282,7 +270,7 @@ exports[`components/user_settings/display/UserSettingsDisplay should match snaps
|
||||
className="divider-dark"
|
||||
/>
|
||||
<br />
|
||||
<Connect(ToggleModalButton)
|
||||
<ToggleModalButton
|
||||
className="security-links color--link"
|
||||
dialogType={
|
||||
Object {
|
||||
@ -307,8 +295,8 @@ exports[`components/user_settings/display/UserSettingsDisplay should match snaps
|
||||
defaultMessage="View Access History"
|
||||
id="user.settings.security.viewHistory"
|
||||
/>
|
||||
</Connect(ToggleModalButton)>
|
||||
<Connect(ToggleModalButton)
|
||||
</ToggleModalButton>
|
||||
<ToggleModalButton
|
||||
className="security-links color--link mt-2"
|
||||
dialogType={
|
||||
Object {
|
||||
@ -329,7 +317,7 @@ exports[`components/user_settings/display/UserSettingsDisplay should match snaps
|
||||
defaultMessage="View and Log Out of Active Sessions"
|
||||
id="user.settings.security.logoutActiveSessions"
|
||||
/>
|
||||
</Connect(ToggleModalButton)>
|
||||
</ToggleModalButton>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
@ -387,10 +375,7 @@ exports[`components/user_settings/display/UserSettingsDisplay should match snaps
|
||||
<SettingItem
|
||||
active={false}
|
||||
areAllSectionsInactive={false}
|
||||
containerStyle=""
|
||||
infoPosition="bottom"
|
||||
max={null}
|
||||
saving={false}
|
||||
section="password"
|
||||
title={
|
||||
<Memo(MemoizedFormattedMessage)
|
||||
@ -431,16 +416,13 @@ exports[`components/user_settings/display/UserSettingsDisplay should match snaps
|
||||
<SettingItem
|
||||
active={false}
|
||||
areAllSectionsInactive={false}
|
||||
containerStyle=""
|
||||
describe={
|
||||
<Memo(MemoizedFormattedMessage)
|
||||
defaultMessage="Email and Password"
|
||||
id="user.settings.security.emailPwd"
|
||||
/>
|
||||
}
|
||||
infoPosition="bottom"
|
||||
max={null}
|
||||
saving={false}
|
||||
section="signin"
|
||||
title="Sign-in Method"
|
||||
updateSection={[Function]}
|
||||
@ -449,7 +431,7 @@ exports[`components/user_settings/display/UserSettingsDisplay should match snaps
|
||||
className="divider-dark"
|
||||
/>
|
||||
<br />
|
||||
<Connect(ToggleModalButton)
|
||||
<ToggleModalButton
|
||||
className="security-links color--link"
|
||||
dialogType={
|
||||
Object {
|
||||
@ -474,8 +456,8 @@ exports[`components/user_settings/display/UserSettingsDisplay should match snaps
|
||||
defaultMessage="View Access History"
|
||||
id="user.settings.security.viewHistory"
|
||||
/>
|
||||
</Connect(ToggleModalButton)>
|
||||
<Connect(ToggleModalButton)
|
||||
</ToggleModalButton>
|
||||
<ToggleModalButton
|
||||
className="security-links color--link mt-2"
|
||||
dialogType={
|
||||
Object {
|
||||
@ -496,7 +478,7 @@ exports[`components/user_settings/display/UserSettingsDisplay should match snaps
|
||||
defaultMessage="View and Log Out of Active Sessions"
|
||||
id="user.settings.security.logoutActiveSessions"
|
||||
/>
|
||||
</Connect(ToggleModalButton)>
|
||||
</ToggleModalButton>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
@ -554,10 +536,7 @@ exports[`components/user_settings/display/UserSettingsDisplay should match snaps
|
||||
<SettingItem
|
||||
active={false}
|
||||
areAllSectionsInactive={false}
|
||||
containerStyle=""
|
||||
infoPosition="bottom"
|
||||
max={null}
|
||||
saving={false}
|
||||
section="password"
|
||||
title={
|
||||
<Memo(MemoizedFormattedMessage)
|
||||
@ -598,16 +577,13 @@ exports[`components/user_settings/display/UserSettingsDisplay should match snaps
|
||||
<SettingItem
|
||||
active={false}
|
||||
areAllSectionsInactive={false}
|
||||
containerStyle=""
|
||||
describe={
|
||||
<Memo(MemoizedFormattedMessage)
|
||||
defaultMessage="Email and Password"
|
||||
id="user.settings.security.emailPwd"
|
||||
/>
|
||||
}
|
||||
infoPosition="bottom"
|
||||
max={null}
|
||||
saving={false}
|
||||
section="signin"
|
||||
title="Sign-in Method"
|
||||
updateSection={[Function]}
|
||||
@ -616,7 +592,7 @@ exports[`components/user_settings/display/UserSettingsDisplay should match snaps
|
||||
className="divider-dark"
|
||||
/>
|
||||
<br />
|
||||
<Connect(ToggleModalButton)
|
||||
<ToggleModalButton
|
||||
className="security-links color--link"
|
||||
dialogType={
|
||||
Object {
|
||||
@ -641,8 +617,8 @@ exports[`components/user_settings/display/UserSettingsDisplay should match snaps
|
||||
defaultMessage="View Access History"
|
||||
id="user.settings.security.viewHistory"
|
||||
/>
|
||||
</Connect(ToggleModalButton)>
|
||||
<Connect(ToggleModalButton)
|
||||
</ToggleModalButton>
|
||||
<ToggleModalButton
|
||||
className="security-links color--link mt-2"
|
||||
dialogType={
|
||||
Object {
|
||||
@ -663,7 +639,7 @@ exports[`components/user_settings/display/UserSettingsDisplay should match snaps
|
||||
defaultMessage="View and Log Out of Active Sessions"
|
||||
id="user.settings.security.logoutActiveSessions"
|
||||
/>
|
||||
</Connect(ToggleModalButton)>
|
||||
</ToggleModalButton>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
@ -3,7 +3,7 @@
|
||||
exports[`MfaSection rendering should render nothing when MFA is not available 1`] = `""`;
|
||||
|
||||
exports[`MfaSection rendering when section is collapsed and MFA is active 1`] = `
|
||||
<Connect(SettingItemMin)
|
||||
<SettingItemMin
|
||||
describe={
|
||||
<Memo(MemoizedFormattedMessage)
|
||||
defaultMessage="Active"
|
||||
@ -22,7 +22,7 @@ exports[`MfaSection rendering when section is collapsed and MFA is active 1`] =
|
||||
`;
|
||||
|
||||
exports[`MfaSection rendering when section is collapsed and MFA is not active 1`] = `
|
||||
<Connect(SettingItemMin)
|
||||
<SettingItemMin
|
||||
describe={
|
||||
<Memo(MemoizedFormattedMessage)
|
||||
defaultMessage="Inactive"
|
||||
|
@ -7,7 +7,7 @@ import {FormattedMessage} from 'react-intl';
|
||||
|
||||
import SettingItemMax from 'components/setting_item_max';
|
||||
import SettingItemMin from 'components/setting_item_min';
|
||||
import type SettingItemMinComponent from 'components/setting_item_min/setting_item_min';
|
||||
import type SettingItemMinComponent from 'components/setting_item_min';
|
||||
|
||||
import {getHistory} from 'utils/browser_history';
|
||||
|
||||
|
@ -16,7 +16,7 @@ import FormattedMarkdownMessage from 'components/formatted_markdown_message';
|
||||
import SaveButton from 'components/save_button';
|
||||
import SettingItemMax from 'components/setting_item_max';
|
||||
import SettingItemMin from 'components/setting_item_min';
|
||||
import type SettingItemMinComponent from 'components/setting_item_min/setting_item_min';
|
||||
import type SettingItemMinComponent from 'components/setting_item_min';
|
||||
import WarningIcon from 'components/widgets/icons/fa_warning_icon';
|
||||
|
||||
import {Constants, DeveloperLinks} from 'utils/constants';
|
||||
|
@ -13,7 +13,7 @@ import {Preferences} from 'mattermost-redux/constants';
|
||||
|
||||
import SettingItemMax from 'components/setting_item_max';
|
||||
import SettingItemMin from 'components/setting_item_min';
|
||||
import type SettingItemMinComponent from 'components/setting_item_min/setting_item_min';
|
||||
import type SettingItemMinComponent from 'components/setting_item_min';
|
||||
|
||||
import {localizeMessage} from 'utils/utils';
|
||||
|
||||
|
@ -11,7 +11,7 @@ import {Preferences} from 'mattermost-redux/constants';
|
||||
|
||||
import SettingItemMax from 'components/setting_item_max';
|
||||
import SettingItemMin from 'components/setting_item_min';
|
||||
import type SettingItemMinComponent from 'components/setting_item_min/setting_item_min';
|
||||
import type SettingItemMinComponent from 'components/setting_item_min';
|
||||
|
||||
import {a11yFocus} from 'utils/utils';
|
||||
|
||||
|
@ -2,7 +2,7 @@
|
||||
|
||||
exports[`components/MenuItemToggleModalRedux should match snapshot with extra text 1`] = `
|
||||
<Fragment>
|
||||
<Connect(ToggleModalButton)
|
||||
<ToggleModalButton
|
||||
className="MenuItem__with-help"
|
||||
dialogProps={
|
||||
Object {
|
||||
@ -22,6 +22,6 @@ exports[`components/MenuItemToggleModalRedux should match snapshot with extra te
|
||||
>
|
||||
Extra text
|
||||
</span>
|
||||
</Connect(ToggleModalButton)>
|
||||
</ToggleModalButton>
|
||||
</Fragment>
|
||||
`;
|
||||
|
@ -19,7 +19,7 @@ describe('components/MenuItemToggleModalRedux', () => {
|
||||
|
||||
expect(wrapper).toMatchInlineSnapshot(`
|
||||
<Fragment>
|
||||
<Connect(ToggleModalButton)
|
||||
<ToggleModalButton
|
||||
className=""
|
||||
dialogProps={
|
||||
Object {
|
||||
@ -34,7 +34,7 @@ describe('components/MenuItemToggleModalRedux', () => {
|
||||
>
|
||||
Whatever
|
||||
</span>
|
||||
</Connect(ToggleModalButton)>
|
||||
</ToggleModalButton>
|
||||
</Fragment>
|
||||
`);
|
||||
});
|
||||
|
@ -9,7 +9,7 @@
|
||||
padding: 0 10px;
|
||||
|
||||
.RestrictedIndicator__button {
|
||||
padding: 0 !important;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.RestrictedIndicator__icon-tooltip {
|
||||
|
@ -2,7 +2,7 @@
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import classNames from 'classnames';
|
||||
import React, {useCallback} from 'react';
|
||||
import React, {useCallback, type ReactNode} from 'react';
|
||||
import {useIntl} from 'react-intl';
|
||||
import type {MessageDescriptor} from 'react-intl';
|
||||
|
||||
@ -16,27 +16,27 @@ import {Constants, LicenseSkus, ModalIdentifiers} from 'utils/constants';
|
||||
|
||||
import './restricted_indicator.scss';
|
||||
|
||||
type RestrictedIndicatorProps = {
|
||||
type Props = {
|
||||
useModal?: boolean;
|
||||
titleAdminPreTrial?: string;
|
||||
messageAdminPreTrial?: string | React.ReactNode;
|
||||
titleAdminPostTrial?: string;
|
||||
messageAdminPostTrial?: string | React.ReactNode;
|
||||
titleEndUser?: string;
|
||||
messageEndUser?: string | React.ReactNode;
|
||||
blocked?: boolean;
|
||||
tooltipTitle?: string;
|
||||
tooltipMessage?: string;
|
||||
tooltipMessageBlocked?: string | MessageDescriptor;
|
||||
ctaExtraContent?: React.ReactNode;
|
||||
clickCallback?: () => void;
|
||||
customSecondaryButtonInModal?: {msg: string; action: () => void};
|
||||
feature?: string;
|
||||
minimumPlanRequiredForFeature?: string;
|
||||
tooltipTitle?: ReactNode;
|
||||
tooltipMessage?: ReactNode;
|
||||
tooltipMessageBlocked?: string | MessageDescriptor;
|
||||
titleAdminPreTrial?: ReactNode;
|
||||
messageAdminPreTrial?: ReactNode;
|
||||
titleAdminPostTrial?: ReactNode;
|
||||
messageAdminPostTrial?: ReactNode;
|
||||
titleEndUser?: ReactNode;
|
||||
messageEndUser?: ReactNode;
|
||||
ctaExtraContent?: ReactNode;
|
||||
clickCallback?: () => void;
|
||||
customSecondaryButtonInModal?: {msg: string; action: () => void};
|
||||
}
|
||||
|
||||
function capitalizeFirstLetter(s: string) {
|
||||
return s.charAt(0).toUpperCase() + s.slice(1);
|
||||
return s?.charAt(0)?.toUpperCase() + s?.slice(1);
|
||||
}
|
||||
|
||||
const RestrictedIndicator = ({
|
||||
@ -56,7 +56,7 @@ const RestrictedIndicator = ({
|
||||
customSecondaryButtonInModal,
|
||||
feature,
|
||||
minimumPlanRequiredForFeature,
|
||||
}: RestrictedIndicatorProps) => {
|
||||
}: Props) => {
|
||||
const {formatMessage} = useIntl();
|
||||
|
||||
const getTooltipMessageBlocked = useCallback(() => {
|
||||
@ -92,7 +92,10 @@ const RestrictedIndicator = ({
|
||||
delayShow={Constants.OVERLAY_TIME_DELAY}
|
||||
placement='right'
|
||||
overlay={(
|
||||
<Tooltip className='RestrictedIndicator__icon-tooltip'>
|
||||
<Tooltip
|
||||
id={`${feature}-tooltip`}
|
||||
className='RestrictedIndicator__icon-tooltip'
|
||||
>
|
||||
<span className='title'>
|
||||
{tooltipTitle || formatMessage({id: 'restricted_indicator.tooltip.title', defaultMessage: '{minimumPlanRequiredForFeature} feature'}, {minimumPlanRequiredForFeature: capitalizeFirstLetter(minimumPlanRequiredForFeature!)})}
|
||||
</span>
|
||||
@ -107,27 +110,29 @@ const RestrictedIndicator = ({
|
||||
)}
|
||||
>
|
||||
{useModal && blocked ? (
|
||||
<ToggleModalButton
|
||||
id={`${feature}-restricted-indicator`.replaceAll('.', '_')}
|
||||
className='RestrictedIndicator__button'
|
||||
modalId={ModalIdentifiers.FEATURE_RESTRICTED_MODAL}
|
||||
dialogType={FeatureRestrictedModal}
|
||||
onClick={handleClickCallback}
|
||||
dialogProps={{
|
||||
titleAdminPreTrial,
|
||||
messageAdminPreTrial,
|
||||
titleAdminPostTrial,
|
||||
messageAdminPostTrial,
|
||||
titleEndUser,
|
||||
messageEndUser,
|
||||
customSecondaryButton: customSecondaryButtonInModal,
|
||||
feature,
|
||||
minimumPlanRequiredForFeature,
|
||||
}}
|
||||
>
|
||||
{icon}
|
||||
{ctaExtraContent}
|
||||
</ToggleModalButton>
|
||||
<span>
|
||||
<ToggleModalButton
|
||||
id={`${feature}-restricted-indicator`?.replaceAll('.', '_')}
|
||||
className='RestrictedIndicator__button'
|
||||
modalId={ModalIdentifiers.FEATURE_RESTRICTED_MODAL}
|
||||
dialogType={FeatureRestrictedModal}
|
||||
onClick={handleClickCallback}
|
||||
dialogProps={{
|
||||
titleAdminPreTrial,
|
||||
messageAdminPreTrial,
|
||||
titleAdminPostTrial,
|
||||
messageAdminPostTrial,
|
||||
titleEndUser,
|
||||
messageEndUser,
|
||||
customSecondaryButton: customSecondaryButtonInModal,
|
||||
feature,
|
||||
minimumPlanRequiredForFeature,
|
||||
}}
|
||||
>
|
||||
{icon}
|
||||
{ctaExtraContent}
|
||||
</ToggleModalButton>
|
||||
</span>
|
||||
) : (
|
||||
<div className='RestrictedIndicator__content'>
|
||||
{icon}
|
||||
|
@ -5428,7 +5428,7 @@
|
||||
"user.settings.notifications.autoResponderHint": "Set a custom message that will be automatically sent in response to Direct Messages. Mentions in Public and Private Channels will not trigger the automated reply. Enabling Automatic Replies sets your status to Out of Office and disables email and push notifications.",
|
||||
"user.settings.notifications.autoResponderPlaceholder": "Message",
|
||||
"user.settings.notifications.channelWide": "Channel-wide mentions \"@channel\", \"@all\", \"@here\"",
|
||||
"user.settings.notifications.comments": "Reply notifications",
|
||||
"user.settings.notifications.comments": "Reply Notifications",
|
||||
"user.settings.notifications.commentsAny": "Trigger notifications on messages in reply threads that I start or participate in",
|
||||
"user.settings.notifications.commentsInfo": "In addition to notifications for when you're mentioned, select if you would like to receive notifications on reply threads.",
|
||||
"user.settings.notifications.commentsNever": "Do not trigger notifications on messages in reply threads unless I'm mentioned",
|
||||
@ -5457,8 +5457,21 @@
|
||||
"user.settings.notifications.header": "Notifications",
|
||||
"user.settings.notifications.icon": "Notification Settings Icon",
|
||||
"user.settings.notifications.info": "Desktop notifications are available on Edge, Firefox, Safari, Chrome and Mattermost Desktop Apps.",
|
||||
"user.settings.notifications.keywordsWithHighlight.disabledTooltipMessage": "This feature is available on the Professional plan",
|
||||
"user.settings.notifications.keywordsWithHighlight.disabledTooltipTitle": "Professional feature",
|
||||
"user.settings.notifications.keywordsWithHighlight.extraInfo": "These keywords will be shown to you with a highlight when anyone sends a message that includes them.",
|
||||
"user.settings.notifications.keywordsWithHighlight.inputTitle": "Enter non case-sensitive keywords, press Tab or use commas to separate them:",
|
||||
"user.settings.notifications.keywordsWithHighlight.none": "None",
|
||||
"user.settings.notifications.keywordsWithHighlight.professional": "Professional",
|
||||
"user.settings.notifications.keywordsWithHighlight.title": "Keywords That Get Highlighted (Without Notifications)",
|
||||
"user.settings.notifications.keywordsWithHighlight.userModal.messageAdminPostTrial": "Get the ability to passively highlight keywords that you care about. Upgrade to Professional plan to unlock this feature.",
|
||||
"user.settings.notifications.keywordsWithHighlight.userModal.messageAdminPreTrial": "Get the ability to passively highlight keywords that you care about. Upgrade to Professional plan to unlock this feature.",
|
||||
"user.settings.notifications.keywordsWithHighlight.userModal.messageEndUser": "Get the ability to passively highlight keywords that you care about.{br}{br}Request your admin to upgrade to Mattermost Professional to access this feature.",
|
||||
"user.settings.notifications.keywordsWithHighlight.userModal.titleAdminPostTrial": "Highlight keywords without notifications with Mattermost Professional",
|
||||
"user.settings.notifications.keywordsWithHighlight.userModal.titleAdminPreTrial": "Highlight keywords without notifications with Mattermost Professional",
|
||||
"user.settings.notifications.keywordsWithHighlight.userModal.titleEndUser": "Highlight keywords without notifications with Mattermost Professional",
|
||||
"user.settings.notifications.keywordsWithNotification.extraInfo": "Notifications are triggered when someone sends a message that includes your username (\"@{username}\") or any of the options selected above.",
|
||||
"user.settings.notifications.keywordsWithNotification.title": "Keywords that trigger Notifications",
|
||||
"user.settings.notifications.keywordsWithNotification.title": "Keywords That Trigger Notifications",
|
||||
"user.settings.notifications.learnMore": "<a>Learn more about notifications</a>",
|
||||
"user.settings.notifications.never": "Never",
|
||||
"user.settings.notifications.off": "Off",
|
||||
@ -5637,6 +5650,7 @@
|
||||
"webapp.mattermost.feature.create_multiple_teams": "Create Multiple Teams",
|
||||
"webapp.mattermost.feature.custom_user_groups": "Custom User groups",
|
||||
"webapp.mattermost.feature.guest_accounts": "Guest Accounts",
|
||||
"webapp.mattermost.feature.highlight_without_notification": "Keywords Highlight Without Notification",
|
||||
"webapp.mattermost.feature.playbooks_retro": "Playbooks Retrospective",
|
||||
"webapp.mattermost.feature.start_call": "Start call",
|
||||
"webapp.mattermost.feature.unlimited_file_storage": "Unlimited File Storage",
|
||||
|
@ -224,6 +224,26 @@ export const getCurrentUserMentionKeys: (state: GlobalState) => UserMentionKey[]
|
||||
},
|
||||
);
|
||||
|
||||
export type HighlightWithoutNotificationKey = {
|
||||
key: string;
|
||||
}
|
||||
|
||||
export const getHighlightWithoutNotificationKeys: (state: GlobalState) => HighlightWithoutNotificationKey[] = createSelector(
|
||||
'getHighlightWithoutNotificationKeys',
|
||||
getCurrentUser,
|
||||
(user: UserProfile) => {
|
||||
const highlightKeys: HighlightWithoutNotificationKey[] = [];
|
||||
|
||||
if (user?.notify_props?.highlight_keys?.length > 0) {
|
||||
user.notify_props.highlight_keys.split(',').forEach((key) => {
|
||||
highlightKeys.push({key});
|
||||
});
|
||||
}
|
||||
|
||||
return highlightKeys;
|
||||
},
|
||||
);
|
||||
|
||||
export const getProfileSetInCurrentChannel: (state: GlobalState) => Set<UserProfile['id']> = createSelector(
|
||||
'getProfileSetInCurrentChannel',
|
||||
getCurrentChannelId,
|
||||
|
@ -102,6 +102,7 @@ class TestHelper {
|
||||
email: 'false',
|
||||
first_name: 'false',
|
||||
mark_unread: 'mention',
|
||||
highlight_keys: '',
|
||||
mention_keys: '',
|
||||
push: 'none',
|
||||
push_status: 'offline',
|
||||
@ -144,6 +145,7 @@ class TestHelper {
|
||||
first_name: 'false',
|
||||
mark_unread: 'mention',
|
||||
mention_keys: '',
|
||||
highlight_keys: '',
|
||||
push: 'none',
|
||||
push_status: 'offline',
|
||||
},
|
||||
@ -472,6 +474,7 @@ class TestHelper {
|
||||
first_name: 'true',
|
||||
channel: 'true',
|
||||
mention_keys: '',
|
||||
highlight_keys: '',
|
||||
...override,
|
||||
};
|
||||
};
|
||||
|
@ -134,7 +134,6 @@ exports[`plugins/PostMessageView should match snapshot with no extended post typ
|
||||
"onImageLoaded": [Function],
|
||||
}
|
||||
}
|
||||
mentionKeys={Array []}
|
||||
message="this is some text"
|
||||
options={Object {}}
|
||||
post={
|
||||
|
@ -54,6 +54,13 @@ del .group-mention-link:focus {
|
||||
}
|
||||
}
|
||||
|
||||
.non-notification-highlight {
|
||||
padding: 0 1px;
|
||||
background-color: var(--mention-highlight-bg);
|
||||
border-radius: 4px;
|
||||
color: var(--mention-highlight-link);
|
||||
}
|
||||
|
||||
.group-mention-link {
|
||||
color: var(--link-color);
|
||||
}
|
||||
|
@ -194,13 +194,8 @@
|
||||
}
|
||||
|
||||
.section-min__edit {
|
||||
position: relative;
|
||||
top: 0;
|
||||
right: 0;
|
||||
text-align: left;
|
||||
|
||||
button {
|
||||
opacity: 1;
|
||||
span {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
@ -299,23 +294,6 @@
|
||||
}
|
||||
|
||||
.settings-content {
|
||||
.section-min__edit {
|
||||
text-align: left;
|
||||
text-decoration: none;
|
||||
|
||||
button {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
align-items: center;
|
||||
color: rgba(var(--center-channel-color-rgb), 0.75);
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.fa {
|
||||
display: inline-block;
|
||||
}
|
||||
}
|
||||
|
||||
&.minimize-settings {
|
||||
display: none;
|
||||
padding: 0;
|
||||
|
@ -658,9 +658,42 @@
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.d-flex {
|
||||
> .secion-min__header {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
&.isDisabled {
|
||||
cursor: default;
|
||||
|
||||
&:hover {
|
||||
background: inherit;
|
||||
}
|
||||
|
||||
.RestrictedIndicator__icon-tooltip-container {
|
||||
flex: unset;
|
||||
align-self: flex-start;
|
||||
padding: 0;
|
||||
|
||||
button {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 0 6px;
|
||||
background-color: rgba(var(--button-bg-rgb), 0.08);
|
||||
border-radius: 12px;
|
||||
color: var(--button-bg);
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
|
||||
i {
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.section-min__title {
|
||||
@ -669,18 +702,15 @@
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
line-height: 20px;
|
||||
|
||||
&.isDisabled {
|
||||
color: rgba(var(--center-channel-color-rgb), 0.64);
|
||||
}
|
||||
}
|
||||
|
||||
.section-min__edit {
|
||||
margin-bottom: 5px;
|
||||
text-align: right;
|
||||
|
||||
.fa {
|
||||
display: none;
|
||||
margin-right: 5px;
|
||||
font-size: 12px;
|
||||
opacity: 0.5;
|
||||
}
|
||||
}
|
||||
|
||||
.section-min__describe {
|
||||
@ -689,4 +719,8 @@
|
||||
opacity: 0.75;
|
||||
text-overflow: ellipsis;
|
||||
white-space: pre;
|
||||
|
||||
&.isDisabled {
|
||||
color: rgba(var(--center-channel-color-rgb), 0.4);
|
||||
}
|
||||
}
|
||||
|
@ -98,6 +98,7 @@ export type PluginComponent = {
|
||||
filter?: (id: string) => boolean;
|
||||
action?: (...args: any) => void; // TODO Add more concrete types?
|
||||
shouldRender?: (state: GlobalState) => boolean;
|
||||
hook?: (post: Post, message?: string) => string;
|
||||
};
|
||||
|
||||
export type AppBarComponent = PluginComponent & {
|
||||
|
@ -507,6 +507,7 @@ export const MattermostFeatures = {
|
||||
ALL_ENTERPRISE_FEATURES: 'mattermost.feature.all_enterprise',
|
||||
UPGRADE_DOWNGRADED_WORKSPACE: 'mattermost.feature.upgrade_downgraded_workspace',
|
||||
PLUGIN_FEATURE: 'mattermost.feature.plugin',
|
||||
HIGHLIGHT_WITHOUT_NOTIFICATION: 'mattermost.feature.highlight_without_notification',
|
||||
};
|
||||
|
||||
export enum LicenseSkus {
|
||||
|
@ -3,9 +3,10 @@
|
||||
|
||||
import moment from 'moment';
|
||||
|
||||
import type {Product} from '@mattermost/types/cloud';
|
||||
import type {ClientLicense} from '@mattermost/types/config';
|
||||
|
||||
import {LicenseSkus} from 'utils/constants';
|
||||
import {CloudProducts, LicenseSkus, SelfHostedProducts} from 'utils/constants';
|
||||
|
||||
const LICENSE_EXPIRY_NOTIFICATION = 1000 * 60 * 60 * 24 * 60; // 60 days
|
||||
const LICENSE_GRACE_PERIOD = 1000 * 60 * 60 * 24 * 10; // 10 days
|
||||
@ -103,3 +104,14 @@ export const licenseSKUWithFirstLetterCapitalized = (license: ClientLicense) =>
|
||||
const sku = license.SkuShortName;
|
||||
return sku.charAt(0).toUpperCase() + sku.slice(1);
|
||||
};
|
||||
|
||||
export function isEnterpriseOrCloudOrSKUStarterFree(license: ClientLicense, subscriptionProduct: Product | undefined, isEnterpriseReady: boolean) {
|
||||
const isCloud = license?.Cloud === 'true';
|
||||
const isCloudStarterFree = isCloud && subscriptionProduct?.sku === CloudProducts.STARTER;
|
||||
|
||||
const isSelfHostedStarter = isEnterpriseReady && (license.IsLicensed === 'false');
|
||||
|
||||
const isStarterSKULicense = license.IsLicensed === 'true' && license.SelfHostedProducts === SelfHostedProducts.STARTER;
|
||||
|
||||
return isCloudStarterFree || isSelfHostedStarter || isStarterSKULicense;
|
||||
}
|
||||
|
@ -26,6 +26,8 @@ export function mapFeatureIdToTranslation(id: string, formatMessage: Function):
|
||||
return formatMessage({id: 'webapp.mattermost.feature.all_enterprise', defaultMessage: 'All Enterprise features'});
|
||||
case MattermostFeatures.UPGRADE_DOWNGRADED_WORKSPACE:
|
||||
return formatMessage({id: 'webapp.mattermost.feature.upgrade_downgraded_workspace', defaultMessage: 'Revert the workspace to a paid plan'});
|
||||
case MattermostFeatures.HIGHLIGHT_WITHOUT_NOTIFICATION:
|
||||
return formatMessage({id: 'webapp.mattermost.feature.highlight_without_notification', defaultMessage: 'Keywords Highlight Without Notification'});
|
||||
default:
|
||||
return '';
|
||||
}
|
||||
|
@ -35,7 +35,9 @@ describe('Utils.Route', () => {
|
||||
comments: 'never',
|
||||
first_name: 'true',
|
||||
channel: 'true',
|
||||
mention_keys: ''},
|
||||
mention_keys: '',
|
||||
highlight_keys: '',
|
||||
},
|
||||
last_password_update: 0,
|
||||
last_picture_update: 0,
|
||||
locale: '',
|
||||
@ -83,7 +85,8 @@ describe('Utils.Route', () => {
|
||||
position: '',
|
||||
roles: '',
|
||||
props: {userid: '121'},
|
||||
notify_props: {desktop: 'default',
|
||||
notify_props: {
|
||||
desktop: 'default',
|
||||
desktop_sound: 'false',
|
||||
calls_desktop_sound: 'true',
|
||||
email: 'true',
|
||||
@ -93,7 +96,9 @@ describe('Utils.Route', () => {
|
||||
comments: 'never',
|
||||
first_name: 'true',
|
||||
channel: 'true',
|
||||
mention_keys: ''},
|
||||
mention_keys: '',
|
||||
highlight_keys: '',
|
||||
},
|
||||
last_password_update: 0,
|
||||
last_picture_update: 0,
|
||||
locale: '',
|
||||
|
@ -68,6 +68,7 @@ export class TestHelper {
|
||||
first_name: 'false',
|
||||
mark_unread: 'mention',
|
||||
mention_keys: '',
|
||||
highlight_keys: '',
|
||||
push: 'none',
|
||||
push_status: 'offline',
|
||||
},
|
||||
|
@ -14,7 +14,9 @@ import {
|
||||
highlightSearchTerms,
|
||||
handleUnicodeEmoji,
|
||||
highlightCurrentMentions,
|
||||
parseSearchTerms, autolinkChannelMentions,
|
||||
highlightWithoutNotificationKeywords,
|
||||
parseSearchTerms,
|
||||
autolinkChannelMentions,
|
||||
} from 'utils/text_formatting';
|
||||
import type {ChannelNamesMap} from 'utils/text_formatting';
|
||||
|
||||
@ -304,6 +306,101 @@ describe('highlightCurrentMentions', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('highlightWithoutNotificationKeywords', () => {
|
||||
test('should replace highlight keywords with tokens', () => {
|
||||
const text = 'This is a test message with some keywords';
|
||||
const tokens = new Map();
|
||||
const highlightKeys = [
|
||||
{key: 'test message'},
|
||||
{key: 'keywords'},
|
||||
];
|
||||
|
||||
const expectedOutput = 'This is a $MM_HIGHLIGHTKEYWORD0$ with some $MM_HIGHLIGHTKEYWORD1$';
|
||||
const expectedTokens = new Map([
|
||||
['$MM_HIGHLIGHTKEYWORD0$', {
|
||||
value: '<span class="non-notification-highlight">test message</span>',
|
||||
originalText: 'test message',
|
||||
}],
|
||||
['$MM_HIGHLIGHTKEYWORD1$', {
|
||||
value: '<span class="non-notification-highlight">keywords</span>',
|
||||
originalText: 'keywords',
|
||||
}],
|
||||
]);
|
||||
|
||||
const output = highlightWithoutNotificationKeywords(text, tokens, highlightKeys);
|
||||
|
||||
expect(output).toBe(expectedOutput);
|
||||
expect(tokens).toEqual(expectedTokens);
|
||||
});
|
||||
|
||||
test('should handle empty highlightKeys array', () => {
|
||||
const text = 'This is a test message';
|
||||
const tokens = new Map();
|
||||
const highlightKeys = [] as Array<{key: string}>;
|
||||
|
||||
const expectedOutput = 'This is a test message';
|
||||
const expectedTokens = new Map();
|
||||
|
||||
const output = highlightWithoutNotificationKeywords(text, tokens, highlightKeys);
|
||||
|
||||
expect(output).toBe(expectedOutput);
|
||||
expect(tokens).toEqual(expectedTokens);
|
||||
});
|
||||
|
||||
test('should handle empty text', () => {
|
||||
const text = '';
|
||||
const tokens = new Map();
|
||||
const highlightKeys = [
|
||||
{key: 'test'},
|
||||
{key: 'keywords'},
|
||||
];
|
||||
|
||||
const expectedOutput = '';
|
||||
const expectedTokens = new Map();
|
||||
|
||||
const output = highlightWithoutNotificationKeywords(text, tokens, highlightKeys);
|
||||
|
||||
expect(output).toBe(expectedOutput);
|
||||
expect(tokens).toEqual(expectedTokens);
|
||||
});
|
||||
|
||||
test('should handle Chinese, Korean, Russian, and Japanese words', () => {
|
||||
const text = 'This is a test message with some keywords: привет, こんにちは, 안녕하세요, 你好';
|
||||
const tokens = new Map();
|
||||
const highlightKeys = [
|
||||
{key: 'こんにちは'}, // Japanese hello
|
||||
{key: '안녕하세요'}, // Korean hello
|
||||
{key: 'привет'}, // Russian hello
|
||||
{key: '你好'}, // Chinese hello
|
||||
];
|
||||
|
||||
const expectedOutput = 'This is a test message with some keywords: $MM_HIGHLIGHTKEYWORD0$, $MM_HIGHLIGHTKEYWORD1$, $MM_HIGHLIGHTKEYWORD2$, $MM_HIGHLIGHTKEYWORD3$';
|
||||
const expectedTokens = new Map([
|
||||
['$MM_HIGHLIGHTKEYWORD0$', {
|
||||
value: '<span class="non-notification-highlight">привет</span>',
|
||||
originalText: 'привет',
|
||||
}],
|
||||
['$MM_HIGHLIGHTKEYWORD1$', {
|
||||
value: '<span class="non-notification-highlight">こんにちは</span>',
|
||||
originalText: 'こんにちは',
|
||||
}],
|
||||
['$MM_HIGHLIGHTKEYWORD2$', {
|
||||
value: '<span class="non-notification-highlight">안녕하세요</span>',
|
||||
originalText: '안녕하세요',
|
||||
}],
|
||||
['$MM_HIGHLIGHTKEYWORD3$', {
|
||||
value: '<span class="non-notification-highlight">你好</span>',
|
||||
originalText: '你好',
|
||||
}],
|
||||
]);
|
||||
|
||||
const output = highlightWithoutNotificationKeywords(text, tokens, highlightKeys);
|
||||
|
||||
expect(output).toBe(expectedOutput);
|
||||
expect(tokens).toEqual(expectedTokens);
|
||||
});
|
||||
});
|
||||
|
||||
describe('parseSearchTerms', () => {
|
||||
const tests = [
|
||||
{
|
||||
|
@ -6,6 +6,8 @@ import type {Renderer} from 'marked';
|
||||
|
||||
import type {SystemEmoji} from '@mattermost/types/emojis';
|
||||
|
||||
import type {HighlightWithoutNotificationKey} from 'mattermost-redux/selectors/entities/users';
|
||||
|
||||
import {formatWithRenderer} from 'utils/markdown';
|
||||
|
||||
import Constants from './constants';
|
||||
@ -88,6 +90,11 @@ interface TextFormattingOptionsBase {
|
||||
*/
|
||||
mentionKeys: MentionKey[];
|
||||
|
||||
/**
|
||||
* A list of highlight keys for the current user to highlight without notification.
|
||||
*/
|
||||
highlightKeys: HighlightWithoutNotificationKey[];
|
||||
|
||||
/**
|
||||
* Specifies whether or not to remove newlines.
|
||||
*
|
||||
@ -360,6 +367,10 @@ export function doFormatText(text: string, options: TextFormattingOptions, emoji
|
||||
output = highlightCurrentMentions(output, tokens, options.mentionKeys);
|
||||
}
|
||||
|
||||
if (options.highlightKeys && options.highlightKeys.length > 0) {
|
||||
output = highlightWithoutNotificationKeywords(output, tokens, options.highlightKeys);
|
||||
}
|
||||
|
||||
if (!('emoticons' in options) || options.emoticons) {
|
||||
output = handleUnicodeEmoji(output, emojiMap, UNICODE_EMOJI_REGEX);
|
||||
}
|
||||
@ -704,6 +715,79 @@ export function highlightCurrentMentions(
|
||||
return output;
|
||||
}
|
||||
|
||||
export function highlightWithoutNotificationKeywords(
|
||||
text: string,
|
||||
tokens: Tokens,
|
||||
highlightKeys: HighlightWithoutNotificationKey[] = [],
|
||||
) {
|
||||
let output = text;
|
||||
|
||||
// Store the new tokens in a separate map since we can't add objects to a map during iteration
|
||||
const newTokens = new Map();
|
||||
|
||||
// Look for highlighting keywords in the tokens
|
||||
tokens.forEach((token, alias) => {
|
||||
const tokenOriginalText = token.originalText.toLowerCase();
|
||||
|
||||
if (highlightKeys.findIndex((highlightKey) => highlightKey.key.toLowerCase() === tokenOriginalText) !== -1) {
|
||||
const newIndex = tokens.size + newTokens.size;
|
||||
const newAlias = `$MM_HIGHLIGHTKEYWORD${newIndex}$`;
|
||||
|
||||
newTokens.set(newAlias, {
|
||||
value: `<span class="non-notification-highlight">${alias}</span>`,
|
||||
originalText: token.originalText,
|
||||
});
|
||||
output = output.replace(alias, newAlias);
|
||||
}
|
||||
});
|
||||
|
||||
// Copy the new tokens to the tokens map
|
||||
newTokens.forEach((newToken, newAlias) => {
|
||||
tokens.set(newAlias, newToken);
|
||||
});
|
||||
|
||||
// Look for highlighting keywords in the text
|
||||
function replaceHighlightKeywordsWithToken(
|
||||
_: string,
|
||||
prefix: string,
|
||||
highlightKey: string,
|
||||
suffix = '',
|
||||
) {
|
||||
const index = tokens.size;
|
||||
const alias = `$MM_HIGHLIGHTKEYWORD${index}$`;
|
||||
|
||||
// Set the token map with the replacement value so that it can be replaced back later
|
||||
tokens.set(alias, {
|
||||
value: `<span class="non-notification-highlight">${highlightKey}</span>`,
|
||||
originalText: highlightKey,
|
||||
});
|
||||
|
||||
return prefix + alias + suffix;
|
||||
}
|
||||
|
||||
highlightKeys.
|
||||
sort((a, b) => b.key.length - a.key.length).
|
||||
forEach(({key}) => {
|
||||
if (!key) {
|
||||
return;
|
||||
}
|
||||
|
||||
let pattern;
|
||||
if (cjkrPattern.test(key)) {
|
||||
// If the key contains Chinese, Japanese, Korean or Russian characters, don't mark word boundaries
|
||||
pattern = new RegExp(`()(${escapeRegex(key)})()`, 'gi');
|
||||
} else {
|
||||
// If the key contains only English characters, mark word boundaries
|
||||
pattern = new RegExp(`(^|\\W)(${escapeRegex(key)})(\\b|_+\\b)`, 'gi');
|
||||
}
|
||||
|
||||
// Replace the key with the token for each occurrence of the key
|
||||
output = output.replace(pattern, replaceHighlightKeywordsWithToken);
|
||||
});
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
const hashtagRegex = /(^|\W)(#\p{L}[\p{L}\d\-_.]*[\p{L}\d])/gu;
|
||||
|
||||
function autolinkHashtags(
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user