Simplifying the channel_header component (#25795)

* Simplifying the channel_header component

* Addressing PR Review Comments

* Addressing PR Review Comments

* Addressing linter errors

* Fixing CI

* Fixing CI

* Addressing PR Review Comments
Этот коммит содержится в:
Jesús Espino
2024-01-09 13:05:50 +01:00
коммит произвёл GitHub
родитель 93351ff8a4
Коммит e959d9e4ea
9 изменённых файлов: 622 добавлений и 1286 удалений

Разница между файлами не показана из-за своего большого размера Загрузить разницу

Просмотреть файл

@@ -10,7 +10,6 @@ import type {UserCustomStatus} from '@mattermost/types/users';
import ChannelHeader from 'components/channel_header/channel_header';
import ChannelInfoButton from 'components/channel_header/channel_info_button';
import Markdown from 'components/markdown';
import GuestTag from 'components/widgets/tag/guest_tag';
import {shallowWithIntl} from 'tests/helpers/intl-test-helper';
import Constants, {RHSStates} from 'utils/constants';
@@ -19,8 +18,6 @@ import {TestHelper} from 'utils/test_helper';
describe('components/ChannelHeader', () => {
const baseProps: ComponentProps<typeof ChannelHeader> = {
actions: {
favoriteChannel: jest.fn(),
unfavoriteChannel: jest.fn(),
showPinnedPosts: jest.fn(),
showChannelFiles: jest.fn(),
closeRightHandSide: jest.fn(),
@@ -36,7 +33,6 @@ describe('components/ChannelHeader', () => {
channel: TestHelper.getChannelMock({}),
channelMember: TestHelper.getChannelMembershipMock({}),
currentUser: TestHelper.getUserMock({}),
teammateNameDisplaySetting: '',
currentRelativeTeamUrl: '',
isCustomStatusEnabled: false,
isCustomStatusExpired: false,
@@ -267,36 +263,6 @@ describe('components/ChannelHeader', () => {
expect(wrapper).toMatchSnapshot();
});
test('should render the guest tags on gms', () => {
const props = {
...populatedProps,
channel: TestHelper.getChannelMock({
header: 'test',
display_name: 'regular_user, guest_user',
type: Constants.GM_CHANNEL as ChannelType,
}),
gmMembers: [
TestHelper.getUserMock({
id: 'user_id',
username: 'regular_user',
roles: 'system_user',
}),
TestHelper.getUserMock({
id: 'guest_id',
username: 'guest_user',
roles: 'system_guest',
}),
],
};
const wrapper = shallowWithIntl(
<ChannelHeader {...props}/>,
);
expect(wrapper.containsMatchingElement(
<GuestTag/>,
)).toEqual(true);
});
test('should render properly when custom status is set', () => {
const props = {
...populatedProps,

Просмотреть файл

@@ -13,9 +13,7 @@ import type {UserCustomStatus, UserProfile} from '@mattermost/types/users';
import {Permissions} from 'mattermost-redux/constants';
import {memoizeResult} from 'mattermost-redux/utils/helpers';
import {displayUsername, isGuest} from 'mattermost-redux/utils/user_utils';
import {ChannelHeaderDropdown} from 'components/channel_header_dropdown';
import CustomStatusEmoji from 'components/custom_status/custom_status_emoji';
import CustomStatusText from 'components/custom_status/custom_status_text';
import EditChannelHeaderModal from 'components/edit_channel_header_modal';
@@ -23,15 +21,10 @@ import Markdown from 'components/markdown';
import OverlayTrigger from 'components/overlay_trigger';
import type {BaseOverlayTrigger} from 'components/overlay_trigger';
import ChannelPermissionGate from 'components/permissions_gates/channel_permission_gate';
import SharedChannelIndicator from 'components/shared_channel_indicator';
import StatusIcon from 'components/status_icon';
import Timestamp from 'components/timestamp';
import Tooltip from 'components/tooltip';
import ArchiveIcon from 'components/widgets/icons/archive_icon';
import MenuWrapper from 'components/widgets/menu/menu_wrapper';
import Popover from 'components/widgets/popover';
import BotTag from 'components/widgets/tag/bot_tag';
import GuestTag from 'components/widgets/tag/guest_tag';
import CallButton from 'plugins/call_button';
import ChannelHeaderPlug from 'plugins/channel_header_plug';
@@ -46,6 +39,7 @@ import {handleFormattedTextClick, localizeMessage, isEmptyObject, toTitleCase} f
import type {ModalData} from 'types/actions';
import type {RhsState} from 'types/store/rhs';
import ChannelHeaderTitle from './channel_header_title';
import ChannelInfoButton from './channel_info_button';
import HeaderIconWrapper from './components/header_icon_wrapper';
@@ -60,7 +54,6 @@ export type Props = {
channelMember?: ChannelMembership;
dmUser?: UserProfile;
gmMembers?: UserProfile[];
isFavorite?: boolean;
isReadOnly?: boolean;
isMuted?: boolean;
hasGuests?: boolean;
@@ -71,8 +64,6 @@ export type Props = {
pinnedPostsCount?: number;
hasMoreThanOneTeam?: boolean;
actions: {
favoriteChannel: (channelId: string) => void;
unfavoriteChannel: (channelId: string) => void;
showPinnedPosts: (channelId?: string) => void;
showChannelFiles: (channelId: string) => void;
closeRightHandSide: () => void;
@@ -83,7 +74,6 @@ export type Props = {
closeModal: () => void;
showChannelMembers: (channelId: string, inEditingMode?: boolean) => void;
};
teammateNameDisplaySetting: string;
currentRelativeTeamUrl: string;
announcementBarCount: number;
customStatus?: UserCustomStatus;
@@ -97,7 +87,6 @@ export type Props = {
};
type State = {
titleMenuOpen: boolean;
showChannelHeaderPopover: boolean;
channelHeaderPoverWidth: number;
leftOffset: number;
@@ -124,7 +113,6 @@ class ChannelHeader extends React.PureComponent<Props, State> {
channelHeaderPoverWidth: 0,
leftOffset: 0,
topOffset: 0,
titleMenuOpen: false,
};
this.getHeaderMarkdownOptions = memoizeResult((channelNamesMap: Record<string, any>) => (
@@ -149,15 +137,6 @@ class ChannelHeader extends React.PureComponent<Props, State> {
handleClose = () => this.props.actions.goToLastViewedChannel();
toggleFavorite = (e: MouseEvent<HTMLButtonElement>) => {
e.stopPropagation();
if (this.props.isFavorite) {
this.props.actions.unfavoriteChannel(this.props.channel.id);
} else {
this.props.actions.favoriteChannel(this.props.channel.id);
}
};
unmute = () => {
const {actions, channel, channelMember, currentUser} = this.props;
@@ -197,13 +176,6 @@ class ChannelHeader extends React.PureComponent<Props, State> {
}
};
removeTooltipLink = () => {
// Bootstrap adds the attr dynamically, removing it to prevent a11y readout
this.toggleFavoriteRef.current?.removeAttribute('aria-describedby');
};
setTitleMenuOpen = (open: boolean) => this.setState({titleMenuOpen: open});
showEditChannelHeaderModal = () => {
if (this.headerOverlayRef.current) {
this.headerOverlayRef.current.hide();
@@ -283,11 +255,9 @@ class ChannelHeader extends React.PureComponent<Props, State> {
channelMember,
isMuted: channelMuted,
isReadOnly,
isFavorite,
dmUser,
rhsState,
hasGuests,
teammateNameDisplaySetting,
hideGuestTags,
} = this.props;
const {formatMessage} = this.props.intl;
@@ -321,81 +291,11 @@ class ChannelHeader extends React.PureComponent<Props, State> {
const channelNamesMap = channel.props && channel.props.channel_mentions;
let channelTitle: ReactNode = channel.display_name;
const archivedIcon = channelIsArchived ? <ArchiveIcon className='icon icon__archive icon channel-header-archived-icon svg-text-color'/> : null;
let sharedIcon = null;
if (channel.shared) {
sharedIcon = (
<SharedChannelIndicator
className='shared-channel-icon'
channelType={channel.type}
withTooltip={true}
/>
);
}
const isDirect = (channel.type === Constants.DM_CHANNEL);
const isGroup = (channel.type === Constants.GM_CHANNEL);
const isPrivate = (channel.type === Constants.PRIVATE_CHANNEL);
if (isDirect) {
const teammateId = dmUser?.id;
if (currentUser.id === teammateId) {
channelTitle = (
<FormattedMessage
id='channel_header.directchannel.you'
defaultMessage='{displayname} (you) '
values={{
displayname: displayUsername(dmUser, teammateNameDisplaySetting),
}}
/>
);
} else {
channelTitle = displayUsername(dmUser, teammateNameDisplaySetting) + ' ';
}
channelTitle = (
<React.Fragment>
{channelTitle}
{isGuest(dmUser?.roles ?? '') && <GuestTag/>}
</React.Fragment>
);
}
if (isGroup) {
// map the displayname to the gm member users
const membersMap: Record<string, UserProfile[]> = {};
if (gmMembers) {
for (const user of gmMembers) {
if (user.id === currentUser.id) {
continue;
}
const userDisplayName = displayUsername(user, this.props.teammateNameDisplaySetting);
if (!membersMap[userDisplayName]) {
membersMap[userDisplayName] = []; //Create an array for cases with same display name
}
membersMap[userDisplayName].push(user);
}
}
const displayNames = channel.display_name.split(', ');
channelTitle = displayNames.map((displayName, index) => {
if (!membersMap[displayName]) {
return displayName;
}
const user = membersMap[displayName].shift();
return (
<React.Fragment key={user?.id}>
{index > 0 && ', '}
{displayName}
{isGuest(user?.roles ?? '') && <GuestTag/>}
</React.Fragment>
);
});
if (hasGuests && !hideGuestTags) {
hasGuestsText = (
<span className='has-guest-header'>
@@ -690,49 +590,6 @@ class ChannelHeader extends React.PureComponent<Props, State> {
);
}
let toggleFavoriteTooltip;
let toggleFavorite = null;
let ariaLabel = '';
if (!channelIsArchived) {
const formattedMessage = isFavorite ? {
id: 'channelHeader.removeFromFavorites',
defaultMessage: 'Remove from Favorites',
} : {
id: 'channelHeader.addToFavorites',
defaultMessage: 'Add to Favorites',
};
ariaLabel = formatMessage(formattedMessage).toLowerCase();
toggleFavoriteTooltip = (
<Tooltip id='favoriteTooltip' >
<FormattedMessage
{...formattedMessage}
/>
</Tooltip>
);
toggleFavorite = (
<OverlayTrigger
key={`isFavorite-${isFavorite}`}
delayShow={Constants.OVERLAY_TIME_DELAY}
placement='bottom'
overlay={toggleFavoriteTooltip}
onEntering={this.removeTooltipLink}
>
<button
id='toggleFavorite'
ref={this.toggleFavoriteRef}
onClick={this.toggleFavorite}
className={'style--none color--link channel-header__favorites ' + (this.props.isFavorite ? 'active' : 'inactive')}
aria-label={ariaLabel}
>
<i className={'icon ' + (this.props.isFavorite ? 'icon-star' : 'icon-star-outline')}/>
</button>
</OverlayTrigger>
);
}
const channelMutedTooltip = (
<Tooltip id='channelMutedTooltip'>
<FormattedMessage
@@ -762,63 +619,6 @@ class ChannelHeader extends React.PureComponent<Props, State> {
);
}
let title = (
<React.Fragment>
<MenuWrapper onToggle={this.setTitleMenuOpen}>
<div
id='channelHeaderDropdownButton'
className='channel-header__top'
>
<button
className={`channel-header__trigger style--none ${this.state.titleMenuOpen ? 'active' : ''}`}
aria-label={formatMessage({id: 'channel_header.menuAriaLabel', defaultMessage: 'Channel Menu'}).toLowerCase()}
>
<strong
role='heading'
aria-level={2}
id='channelHeaderTitle'
className='heading'
>
<span>
{archivedIcon}
{channelTitle}
{sharedIcon}
</span>
</strong>
<span
id='channelHeaderDropdownIcon'
className='icon icon-chevron-down header-dropdown-chevron-icon'
/>
</button>
</div>
<ChannelHeaderDropdown/>
</MenuWrapper>
{toggleFavorite}
</React.Fragment>
);
if (isDirect && dmUser?.is_bot) {
title = (
<div
id='channelHeaderDropdownButton'
className='channel-header__top channel-header__bot'
>
<strong
role='heading'
aria-level={2}
id='channelHeaderTitle'
className='heading'
>
<span>
{archivedIcon}
{channelTitle}
</span>
</strong>
<BotTag/>
{toggleFavorite}
</div>
);
}
return (
<div
id='channel-header'
@@ -839,7 +639,10 @@ class ChannelHeader extends React.PureComponent<Props, State> {
className='channel-header__title dropdown'
>
<div>
{title}
<ChannelHeaderTitle
dmUser={dmUser}
gmMembers={gmMembers}
/>
</div>
{muteTrigger}
</div>

Просмотреть файл

@@ -0,0 +1,128 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import classNames from 'classnames';
import type {ReactNode} from 'react';
import React, {memo, useState} from 'react';
import {useIntl} from 'react-intl';
import {useSelector} from 'react-redux';
import type {UserProfile} from '@mattermost/types/users';
import {getCurrentChannel} from 'mattermost-redux/selectors/entities/channels';
import {ChannelHeaderDropdown} from 'components/channel_header_dropdown';
import SharedChannelIndicator from 'components/shared_channel_indicator';
import ArchiveIcon from 'components/widgets/icons/archive_icon';
import MenuWrapper from 'components/widgets/menu/menu_wrapper';
import BotTag from 'components/widgets/tag/bot_tag';
import {Constants} from 'utils/constants';
import ChannelHeaderTitleDirect from './channel_header_title_direct';
import ChannelHeaderTitleFavorite from './channel_header_title_favorite';
import ChannelHeaderTitleGroup from './channel_header_title_group';
type Props = {
dmUser?: UserProfile;
gmMembers?: UserProfile[];
}
const ChannelHeaderTitle = ({
dmUser,
gmMembers,
}: Props) => {
const [titleMenuOpen, setTitleMenuOpen] = useState(false);
const intl = useIntl();
const channel = useSelector(getCurrentChannel);
if (!channel) {
return null;
}
const isDirect = (channel.type === Constants.DM_CHANNEL);
const isGroup = (channel.type === Constants.GM_CHANNEL);
const channelIsArchived = channel.delete_at !== 0;
let archivedIcon: React.ReactNode = null;
if (channelIsArchived) {
archivedIcon = <ArchiveIcon className='icon icon__archive icon channel-header-archived-icon svg-text-color'/>;
}
let sharedIcon = null;
if (channel.shared) {
sharedIcon = (
<SharedChannelIndicator
className='shared-channel-icon'
channelType={channel.type}
withTooltip={true}
/>
);
}
let channelTitle: ReactNode = channel.display_name;
if (isDirect) {
channelTitle = <ChannelHeaderTitleDirect dmUser={dmUser}/>;
} else if (isGroup) {
channelTitle = <ChannelHeaderTitleGroup gmMembers={gmMembers}/>;
}
if (isDirect && dmUser?.is_bot) {
return (
<div
id='channelHeaderDropdownButton'
className='channel-header__top channel-header__bot'
>
<strong
role='heading'
aria-level={2}
id='channelHeaderTitle'
className='heading'
>
<span>
{archivedIcon}
{channelTitle}
</span>
</strong>
<BotTag/>
<ChannelHeaderTitleFavorite/>
</div>
);
}
return (
<React.Fragment>
<MenuWrapper onToggle={setTitleMenuOpen}>
<div
id='channelHeaderDropdownButton'
className='channel-header__top'
>
<button
className={classNames('channel-header__trigger style--none', {active: titleMenuOpen})}
aria-label={intl.formatMessage({id: 'channel_header.menuAriaLabel', defaultMessage: 'Channel Menu'}).toLowerCase()}
>
<strong
role='heading'
aria-level={2}
id='channelHeaderTitle'
className='heading'
>
<span>
{archivedIcon}
{channelTitle}
{sharedIcon}
</span>
</strong>
<span
id='channelHeaderDropdownIcon'
className='icon icon-chevron-down header-dropdown-chevron-icon'
/>
</button>
</div>
<ChannelHeaderDropdown/>
</MenuWrapper>
<ChannelHeaderTitleFavorite/>
</React.Fragment>
);
};
export default memo(ChannelHeaderTitle);

Просмотреть файл

@@ -0,0 +1,41 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import React, {memo} from 'react';
import {FormattedMessage} from 'react-intl';
import {useSelector} from 'react-redux';
import type {UserProfile} from '@mattermost/types/users';
import {getTeammateNameDisplaySetting} from 'mattermost-redux/selectors/entities/preferences';
import {getCurrentUser} from 'mattermost-redux/selectors/entities/users';
import {displayUsername, isGuest} from 'mattermost-redux/utils/user_utils';
import GuestTag from 'components/widgets/tag/guest_tag';
type Props = {
dmUser?: UserProfile;
}
const ChannelHeaderTitleDirect = ({
dmUser,
}: Props) => {
const currentUser = useSelector(getCurrentUser);
const teammateNameDisplaySetting = useSelector(getTeammateNameDisplaySetting);
const displayName = displayUsername(dmUser, teammateNameDisplaySetting);
return (
<React.Fragment>
{currentUser.id !== dmUser?.id && displayName + ' '}
{currentUser.id === dmUser?.id &&
<FormattedMessage
id='channel_header.directchannel.you'
defaultMessage='{displayName} (you) '
values={{displayName}}
/>}
{isGuest(dmUser?.roles ?? '') && <GuestTag/>}
</React.Fragment>
);
};
export default memo(ChannelHeaderTitleDirect);

Просмотреть файл

@@ -0,0 +1,85 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import classNames from 'classnames';
import React, {memo, useRef, useCallback} from 'react';
import {FormattedMessage, useIntl} from 'react-intl';
import {useSelector, useDispatch} from 'react-redux';
import {favoriteChannel, unfavoriteChannel} from 'mattermost-redux/actions/channels';
import {getCurrentChannel, isCurrentChannelFavorite} from 'mattermost-redux/selectors/entities/channels';
import OverlayTrigger from 'components/overlay_trigger';
import Tooltip from 'components/tooltip';
import {Constants} from 'utils/constants';
const ChannelHeaderTitleFavorite = () => {
const intl = useIntl();
const dispatch = useDispatch();
const isFavorite = useSelector(isCurrentChannelFavorite);
const channel = useSelector(getCurrentChannel);
const channelIsArchived = channel.delete_at !== 0;
const toggleFavoriteRef = useRef<HTMLButtonElement>(null);
const toggleFavoriteCallback = useCallback((e: React.MouseEvent<HTMLButtonElement>) => {
e.stopPropagation();
if (isFavorite) {
dispatch(unfavoriteChannel(channel.id));
} else {
dispatch(favoriteChannel(channel.id));
}
}, [isFavorite, channel.id]);
const removeTooltipLink = useCallback(() => {
// Bootstrap adds the attr dynamically, removing it to prevent a11y readout
toggleFavoriteRef.current?.removeAttribute('aria-describedby');
}, []);
if (!channel || channelIsArchived) {
return null;
}
let ariaLabel = intl.formatMessage({id: 'channelHeader.addToFavorites', defaultMessage: 'Add to Favorites'});
if (isFavorite) {
ariaLabel = intl.formatMessage({id: 'channelHeader.removeFromFavorites', defaultMessage: 'Remove from Favorites'});
}
ariaLabel = ariaLabel.toLowerCase();
const toggleFavoriteTooltip = (
<Tooltip id='favoriteTooltip' >
{!isFavorite &&
<FormattedMessage
id='channelHeader.addToFavorites'
defaultMessage='Add to Favorites'
/>}
{isFavorite &&
<FormattedMessage
id='channelHeader.removeFromFavorites'
defaultMessage='Remove from Favorites'
/>}
</Tooltip>
);
return (
<OverlayTrigger
key={`isFavorite-${isFavorite}`}
delayShow={Constants.OVERLAY_TIME_DELAY}
placement='bottom'
overlay={toggleFavoriteTooltip}
onEntering={removeTooltipLink}
>
<button
id='toggleFavorite'
ref={toggleFavoriteRef}
onClick={toggleFavoriteCallback}
className={classNames('style--none color--link channel-header__favorites', {active: isFavorite, inactive: !isFavorite})}
aria-label={ariaLabel}
>
<i className={classNames('icon', {'icon-star': isFavorite, 'icon-star-outline': !isFavorite})}/>
</button>
</OverlayTrigger>
);
};
export default memo(ChannelHeaderTitleFavorite);

Просмотреть файл

@@ -0,0 +1,108 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import React from 'react';
import ChannelHeaderTitleGroup from 'components/channel_header/channel_header_title_group';
import {renderWithContext} from 'tests/react_testing_utils';
import {TestHelper} from 'utils/test_helper';
describe('components/ChannelHeaderTitleGroup', () => {
const channels = {
channels: {
channel_id: {
id: 'channel_id',
display_name: 'regular_user, guest_user',
},
},
currentChannelId: 'channel_id',
};
const channelsWithoutGuests = {
channels: {
channel_id: {
id: 'channel_id',
display_name: 'regular_user, not_guest_user',
},
},
currentChannelId: 'channel_id',
};
const users = {
profiles: {
user_id: {
id: 'user_id',
username: 'regular_user',
roles: 'system_user',
},
guest_id: {
id: 'guest_id',
username: 'guest_user',
roles: 'guest_user',
},
not_guest_id: {
id: 'not_guest_id',
username: 'not_guest_user',
roles: 'system_user',
},
},
currentUserId: 'user_id',
};
test('should render the guest tags on gms', () => {
const state = {
entities: {
channels,
users,
},
};
const gmMembers = [
TestHelper.getUserMock({
id: 'user_id',
username: 'regular_user',
roles: 'system_user',
}),
TestHelper.getUserMock({
id: 'guest_id',
username: 'guest_user',
roles: 'system_guest',
}),
];
const wrapper = renderWithContext(
<ChannelHeaderTitleGroup gmMembers={gmMembers}/>,
state,
);
expect(wrapper.queryAllByText('GUEST').length).toBe(1);
});
test('should not render the guest tags on gms when no guest is in it', () => {
const state = {
entities: {
channels: channelsWithoutGuests,
users,
},
};
const gmMembers = [
TestHelper.getUserMock({
id: 'user_id',
username: 'regular_user',
roles: 'system_user',
}),
TestHelper.getUserMock({
id: 'not_guest_id',
username: 'not_guest_user',
roles: 'system_user',
}),
];
const wrapper = renderWithContext(
<ChannelHeaderTitleGroup gmMembers={gmMembers}/>,
state,
);
expect(wrapper.queryAllByText('GUEST').length).toBe(0);
});
});

Просмотреть файл

@@ -0,0 +1,71 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import React, {memo} from 'react';
import {useSelector} from 'react-redux';
import type {UserProfile} from '@mattermost/types/users';
import {getCurrentChannel} from 'mattermost-redux/selectors/entities/channels';
import {getTeammateNameDisplaySetting} from 'mattermost-redux/selectors/entities/preferences';
import {getCurrentUser} from 'mattermost-redux/selectors/entities/users';
import {displayUsername, isGuest} from 'mattermost-redux/utils/user_utils';
import GuestTag from 'components/widgets/tag/guest_tag';
type Props = {
gmMembers?: UserProfile[];
}
const ChannelHeaderTitleGroup = ({
gmMembers,
}: Props) => {
const currentUser = useSelector(getCurrentUser);
const teammateNameDisplaySetting = useSelector(getTeammateNameDisplaySetting);
const channel = useSelector(getCurrentChannel);
if (!channel) {
return null;
}
// map the displayname to the gm member users
const membersMap: Record<string, UserProfile[]> = {};
if (gmMembers) {
for (const user of gmMembers) {
if (user.id === currentUser.id) {
continue;
}
const userDisplayName = displayUsername(user, teammateNameDisplaySetting);
if (!membersMap[userDisplayName]) {
membersMap[userDisplayName] = []; //Create an array for cases with same display name
}
membersMap[userDisplayName].push(user);
}
}
const displayNames = channel.display_name.split(', ');
return (
<React.Fragment>
{displayNames.map((displayName, index) => {
if (!membersMap[displayName]) {
return displayName;
}
const user = membersMap[displayName].shift();
return (
<React.Fragment key={user?.id}>
{index > 0 && ', '}
{displayName}
{isGuest(user?.roles ?? '') && <GuestTag/>}
</React.Fragment>
);
})}
</React.Fragment>
);
};
export default memo(ChannelHeaderTitleGroup);

Просмотреть файл

@@ -7,8 +7,6 @@ import {bindActionCreators} from 'redux';
import type {ActionCreatorsMapObject, Dispatch} from 'redux';
import {
favoriteChannel,
unfavoriteChannel,
updateChannelNotifyProps,
} from 'mattermost-redux/actions/channels';
import {getCustomEmojisInText} from 'mattermost-redux/actions/emojis';
@@ -16,12 +14,10 @@ import {General} from 'mattermost-redux/constants';
import {
getCurrentChannel,
getMyCurrentChannelMembership,
isCurrentChannelFavorite,
isCurrentChannelMuted,
getCurrentChannelStats,
} from 'mattermost-redux/selectors/entities/channels';
import {getConfig} from 'mattermost-redux/selectors/entities/general';
import {getTeammateNameDisplaySetting} from 'mattermost-redux/selectors/entities/preferences';
import {getCurrentRelativeTeamUrl, getCurrentTeamId, getMyTeams} from 'mattermost-redux/selectors/entities/teams';
import {
displayLastActiveLabel,
@@ -101,14 +97,12 @@ function makeMapStateToProps() {
gmMembers,
rhsState: getRhsState(state),
rhsOpen: getIsRhsOpen(state),
isFavorite: isCurrentChannelFavorite(state),
isReadOnly: false,
isMuted: isCurrentChannelMuted(state),
isQuickSwitcherOpen: isModalOpen(state, ModalIdentifiers.QUICK_SWITCH),
hasGuests: stats.guest_count > 0,
pinnedPostsCount: stats.pinnedpost_count,
hasMoreThanOneTeam,
teammateNameDisplaySetting: getTeammateNameDisplaySetting(state),
currentRelativeTeamUrl: getCurrentRelativeTeamUrl(state),
announcementBarCount: getAnnouncementBarCount(state),
customStatus,
@@ -125,8 +119,6 @@ function makeMapStateToProps() {
const mapDispatchToProps = (dispatch: Dispatch) => ({
actions: bindActionCreators<ActionCreatorsMapObject<Action>, Props['actions']>({
favoriteChannel,
unfavoriteChannel,
showPinnedPosts,
showChannelFiles,
closeRightHandSide,