diff --git a/webapp/channels/src/components/channel_header/__snapshots__/channel_header.test.tsx.snap b/webapp/channels/src/components/channel_header/__snapshots__/channel_header.test.tsx.snap index 99f64e43eb..fefa82fb12 100644 --- a/webapp/channels/src/components/channel_header/__snapshots__/channel_header.test.tsx.snap +++ b/webapp/channels/src/components/channel_header/__snapshots__/channel_header.test.tsx.snap @@ -24,79 +24,51 @@ exports[`components/ChannelHeader should match snapshot with last active display className="channel-header__title dropdown" >
- -
- -
- -
- - - + - - + />
- -
- -
- -
- - - + - - + />
- -
- -
- -
- - - - } - placement="bottom" - trigger={ - Array [ - "hover", - "focus", - ] - } - > - - +
- -
- -
- -
- - - - } - placement="bottom" - trigger={ - Array [ - "hover", - "focus", - ] - } - > - - +
- -
- -
- -
- - - - } - placement="bottom" - trigger={ - Array [ - "hover", - "focus", - ] - } - > - - +
- -
- -
- -
- - - - } - placement="bottom" - trigger={ - Array [ - "hover", - "focus", - ] - } - > - - +
- -
- -
- -
+
- -
- -
- -
- - - - } - placement="bottom" - trigger={ - Array [ - "hover", - "focus", - ] - } - > - - +
- -
- -
- -
- - - - } - placement="bottom" - trigger={ - Array [ - "hover", - "focus", - ] - } - > - - +
- -
- -
- -
- - - + - - + />
- -
- -
- -
- - - + - - + />
- -
- -
- -
- - - - } - placement="bottom" - trigger={ - Array [ - "hover", - "focus", - ] - } - > - - +
- -
- -
- -
- - - - } - placement="bottom" - trigger={ - Array [ - "hover", - "focus", - ] - } - > - - +
- -
- -
- -
- - - - } - placement="bottom" - trigger={ - Array [ - "hover", - "focus", - ] - } - > - - +
- -
- -
- -
- - - - } - placement="bottom" - trigger={ - Array [ - "hover", - "focus", - ] - } - > - - +
- -
- -
- -
- - - - } - placement="bottom" - trigger={ - Array [ - "hover", - "focus", - ] - } - > - - +
{ const baseProps: ComponentProps = { 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( - , - ); - expect(wrapper.containsMatchingElement( - , - )).toEqual(true); - }); - test('should render properly when custom status is set', () => { const props = { ...populatedProps, diff --git a/webapp/channels/src/components/channel_header/channel_header.tsx b/webapp/channels/src/components/channel_header/channel_header.tsx index 144bde492b..75cd38f4e7 100644 --- a/webapp/channels/src/components/channel_header/channel_header.tsx +++ b/webapp/channels/src/components/channel_header/channel_header.tsx @@ -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 { channelHeaderPoverWidth: 0, leftOffset: 0, topOffset: 0, - titleMenuOpen: false, }; this.getHeaderMarkdownOptions = memoizeResult((channelNamesMap: Record) => ( @@ -149,15 +137,6 @@ class ChannelHeader extends React.PureComponent { handleClose = () => this.props.actions.goToLastViewedChannel(); - toggleFavorite = (e: MouseEvent) => { - 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 { } }; - 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 { 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 { const channelNamesMap = channel.props && channel.props.channel_mentions; - let channelTitle: ReactNode = channel.display_name; - const archivedIcon = channelIsArchived ? : null; - let sharedIcon = null; - if (channel.shared) { - sharedIcon = ( - - ); - } 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 = ( - - ); - } else { - channelTitle = displayUsername(dmUser, teammateNameDisplaySetting) + ' '; - } - channelTitle = ( - - {channelTitle} - {isGuest(dmUser?.roles ?? '') && } - - ); - } - if (isGroup) { - // map the displayname to the gm member users - const membersMap: Record = {}; - 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 ( - - {index > 0 && ', '} - {displayName} - {isGuest(user?.roles ?? '') && } - - ); - }); - if (hasGuests && !hideGuestTags) { hasGuestsText = ( @@ -690,49 +590,6 @@ class ChannelHeader extends React.PureComponent { ); } - 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 = ( - - - - ); - - toggleFavorite = ( - - - - ); - } - const channelMutedTooltip = ( { ); } - let title = ( - - -
- -
- -
- {toggleFavorite} -
- ); - if (isDirect && dmUser?.is_bot) { - title = ( -
- - - {archivedIcon} - {channelTitle} - - - - {toggleFavorite} -
- ); - } - return (
{ className='channel-header__title dropdown' >
- {title} +
{muteTrigger}
diff --git a/webapp/channels/src/components/channel_header/channel_header_title.tsx b/webapp/channels/src/components/channel_header/channel_header_title.tsx new file mode 100644 index 0000000000..ffbe242ebe --- /dev/null +++ b/webapp/channels/src/components/channel_header/channel_header_title.tsx @@ -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 = ; + } + + let sharedIcon = null; + if (channel.shared) { + sharedIcon = ( + + ); + } + + let channelTitle: ReactNode = channel.display_name; + if (isDirect) { + channelTitle = ; + } else if (isGroup) { + channelTitle = ; + } + + if (isDirect && dmUser?.is_bot) { + return ( +
+ + + {archivedIcon} + {channelTitle} + + + + +
+ ); + } + return ( + + +
+ +
+ +
+ +
+ ); +}; + +export default memo(ChannelHeaderTitle); diff --git a/webapp/channels/src/components/channel_header/channel_header_title_direct.tsx b/webapp/channels/src/components/channel_header/channel_header_title_direct.tsx new file mode 100644 index 0000000000..17e36904c7 --- /dev/null +++ b/webapp/channels/src/components/channel_header/channel_header_title_direct.tsx @@ -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 ( + + {currentUser.id !== dmUser?.id && displayName + ' '} + {currentUser.id === dmUser?.id && + } + {isGuest(dmUser?.roles ?? '') && } + + ); +}; + +export default memo(ChannelHeaderTitleDirect); diff --git a/webapp/channels/src/components/channel_header/channel_header_title_favorite.tsx b/webapp/channels/src/components/channel_header/channel_header_title_favorite.tsx new file mode 100644 index 0000000000..fd35efa929 --- /dev/null +++ b/webapp/channels/src/components/channel_header/channel_header_title_favorite.tsx @@ -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(null); + + const toggleFavoriteCallback = useCallback((e: React.MouseEvent) => { + 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 = ( + + {!isFavorite && + } + {isFavorite && + } + + ); + + return ( + + + + ); +}; + +export default memo(ChannelHeaderTitleFavorite); diff --git a/webapp/channels/src/components/channel_header/channel_header_title_group.test.tsx b/webapp/channels/src/components/channel_header/channel_header_title_group.test.tsx new file mode 100644 index 0000000000..e9fa2c791d --- /dev/null +++ b/webapp/channels/src/components/channel_header/channel_header_title_group.test.tsx @@ -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( + , + 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( + , + state, + ); + expect(wrapper.queryAllByText('GUEST').length).toBe(0); + }); +}); diff --git a/webapp/channels/src/components/channel_header/channel_header_title_group.tsx b/webapp/channels/src/components/channel_header/channel_header_title_group.tsx new file mode 100644 index 0000000000..ba695ad41f --- /dev/null +++ b/webapp/channels/src/components/channel_header/channel_header_title_group.tsx @@ -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 = {}; + 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 ( + + {displayNames.map((displayName, index) => { + if (!membersMap[displayName]) { + return displayName; + } + + const user = membersMap[displayName].shift(); + + return ( + + {index > 0 && ', '} + {displayName} + {isGuest(user?.roles ?? '') && } + + ); + })} + + ); +}; + +export default memo(ChannelHeaderTitleGroup); diff --git a/webapp/channels/src/components/channel_header/index.ts b/webapp/channels/src/components/channel_header/index.ts index 2254e3756c..9ab64e4fb5 100644 --- a/webapp/channels/src/components/channel_header/index.ts +++ b/webapp/channels/src/components/channel_header/index.ts @@ -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, Props['actions']>({ - favoriteChannel, - unfavoriteChannel, showPinnedPosts, showChannelFiles, closeRightHandSide,