mirror of
https://github.com/mattermost/mattermost.git
synced 2025-02-25 18:55:24 -06:00
Make sure the RHS does not get focused when coming back from suppressed (#26816)
* Make sure the RHS does not get focused when coming back from suppressed * Fix missing line break * fix tests
This commit is contained in:
parent
d0a67cd84a
commit
4b934d2a62
@ -636,6 +636,12 @@ export const unsuppressRHS = {
|
|||||||
type: ActionTypes.UNSUPPRESS_RHS,
|
type: ActionTypes.UNSUPPRESS_RHS,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export function focusedRHS() {
|
||||||
|
return {
|
||||||
|
type: ActionTypes.RHS_FOCUSED,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
export function setEditChannelMembers(active: boolean) {
|
export function setEditChannelMembers(active: boolean) {
|
||||||
return {
|
return {
|
||||||
type: ActionTypes.SET_EDIT_CHANNEL_MEMBERS,
|
type: ActionTypes.SET_EDIT_CHANNEL_MEMBERS,
|
||||||
|
@ -94,6 +94,8 @@ describe('components/AdvancedCreateComment', () => {
|
|||||||
savePreferences(): Promise<ActionResult> {
|
savePreferences(): Promise<ActionResult> {
|
||||||
throw new Error('Function not implemented.');
|
throw new Error('Function not implemented.');
|
||||||
},
|
},
|
||||||
|
shouldFocusRHS: true,
|
||||||
|
focusedRHS: jest.fn(),
|
||||||
};
|
};
|
||||||
|
|
||||||
const submitEvent = {
|
const submitEvent = {
|
||||||
|
@ -174,7 +174,6 @@ export type Props = {
|
|||||||
getChannelMemberCountsByGroup: (channelID: string) => void;
|
getChannelMemberCountsByGroup: (channelID: string) => void;
|
||||||
groupsWithAllowReference: Map<string, Group> | null;
|
groupsWithAllowReference: Map<string, Group> | null;
|
||||||
channelMemberCountsByGroup: ChannelMemberCountsByGroup;
|
channelMemberCountsByGroup: ChannelMemberCountsByGroup;
|
||||||
focusOnMount?: boolean;
|
|
||||||
isThreadView?: boolean;
|
isThreadView?: boolean;
|
||||||
openModal: <P>(modalData: ModalData<P>) => void;
|
openModal: <P>(modalData: ModalData<P>) => void;
|
||||||
savePreferences: (userId: string, preferences: PreferenceType[]) => Promise<ActionResult>;
|
savePreferences: (userId: string, preferences: PreferenceType[]) => Promise<ActionResult>;
|
||||||
@ -184,6 +183,8 @@ export type Props = {
|
|||||||
postEditorActions: PluginComponent[];
|
postEditorActions: PluginComponent[];
|
||||||
placeholder?: string;
|
placeholder?: string;
|
||||||
isPlugin?: boolean;
|
isPlugin?: boolean;
|
||||||
|
shouldFocusRHS: boolean;
|
||||||
|
focusedRHS: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
type State = {
|
type State = {
|
||||||
@ -276,8 +277,9 @@ class AdvancedCreateComment extends React.PureComponent<Props, State> {
|
|||||||
onResetHistoryIndex();
|
onResetHistoryIndex();
|
||||||
setShowPreview(false);
|
setShowPreview(false);
|
||||||
|
|
||||||
if (this.props.focusOnMount) {
|
if (this.props.shouldFocusRHS) {
|
||||||
this.focusTextbox();
|
this.focusTextbox();
|
||||||
|
this.props.focusedRHS();
|
||||||
}
|
}
|
||||||
|
|
||||||
document.addEventListener('keydown', this.focusTextboxIfNecessary);
|
document.addEventListener('keydown', this.focusTextboxIfNecessary);
|
||||||
|
@ -27,9 +27,11 @@ import {
|
|||||||
} from 'actions/views/create_comment';
|
} from 'actions/views/create_comment';
|
||||||
import {searchAssociatedGroupsForReference} from 'actions/views/group';
|
import {searchAssociatedGroupsForReference} from 'actions/views/group';
|
||||||
import {openModal} from 'actions/views/modals';
|
import {openModal} from 'actions/views/modals';
|
||||||
|
import {focusedRHS} from 'actions/views/rhs';
|
||||||
import {setShowPreviewOnCreateComment} from 'actions/views/textbox';
|
import {setShowPreviewOnCreateComment} from 'actions/views/textbox';
|
||||||
import {getCurrentLocale} from 'selectors/i18n';
|
import {getCurrentLocale} from 'selectors/i18n';
|
||||||
import {getPostDraft, getIsRhsExpanded, getSelectedPostFocussedAt} from 'selectors/rhs';
|
import {getPostDraft, getIsRhsExpanded, getSelectedPostFocussedAt} from 'selectors/rhs';
|
||||||
|
import {getShouldFocusRHS} from 'selectors/views/rhs';
|
||||||
import {connectionErrorCount} from 'selectors/views/system';
|
import {connectionErrorCount} from 'selectors/views/system';
|
||||||
import {showPreviewOnCreateComment} from 'selectors/views/textbox';
|
import {showPreviewOnCreateComment} from 'selectors/views/textbox';
|
||||||
|
|
||||||
@ -79,6 +81,7 @@ function makeMapStateToProps() {
|
|||||||
const isFormattingBarHidden = getBool(state, Constants.Preferences.ADVANCED_TEXT_EDITOR, AdvancedTextEditor.COMMENT);
|
const isFormattingBarHidden = getBool(state, Constants.Preferences.ADVANCED_TEXT_EDITOR, AdvancedTextEditor.COMMENT);
|
||||||
const currentTeamId = getCurrentTeamId(state);
|
const currentTeamId = getCurrentTeamId(state);
|
||||||
const postEditorActions = state.plugins.components.PostEditorAction;
|
const postEditorActions = state.plugins.components.PostEditorAction;
|
||||||
|
const shouldFocusRHS = getShouldFocusRHS(state);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
currentTeamId,
|
currentTeamId,
|
||||||
@ -108,6 +111,7 @@ function makeMapStateToProps() {
|
|||||||
useCustomGroupMentions,
|
useCustomGroupMentions,
|
||||||
canUploadFiles: canUploadFiles(config),
|
canUploadFiles: canUploadFiles(config),
|
||||||
postEditorActions,
|
postEditorActions,
|
||||||
|
shouldFocusRHS,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -175,6 +179,7 @@ function makeMapDispatchToProps() {
|
|||||||
openModal,
|
openModal,
|
||||||
savePreferences,
|
savePreferences,
|
||||||
searchAssociatedGroupsForReference,
|
searchAssociatedGroupsForReference,
|
||||||
|
focusedRHS,
|
||||||
},
|
},
|
||||||
dispatch,
|
dispatch,
|
||||||
);
|
);
|
||||||
|
@ -30,7 +30,6 @@ exports[`components/RhsThread should match snapshot 1`] = `
|
|||||||
rootPostId="id"
|
rootPostId="id"
|
||||||
/>
|
/>
|
||||||
<Connect(ThreadViewer)
|
<Connect(ThreadViewer)
|
||||||
fromSuppressed={false}
|
|
||||||
isThreadView={false}
|
isThreadView={false}
|
||||||
rootPostId="id"
|
rootPostId="id"
|
||||||
useRelativeTimestamp={true}
|
useRelativeTimestamp={true}
|
||||||
|
@ -21,7 +21,6 @@ type Props = {
|
|||||||
channel: Channel | null;
|
channel: Channel | null;
|
||||||
selected: Post | FakePost;
|
selected: Post | FakePost;
|
||||||
previousRhsState?: RhsState;
|
previousRhsState?: RhsState;
|
||||||
fromSuppressed: boolean;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const RhsThread = ({
|
const RhsThread = ({
|
||||||
@ -30,7 +29,6 @@ const RhsThread = ({
|
|||||||
posts,
|
posts,
|
||||||
selected,
|
selected,
|
||||||
previousRhsState,
|
previousRhsState,
|
||||||
fromSuppressed,
|
|
||||||
}: Props) => {
|
}: Props) => {
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
|
|
||||||
@ -61,7 +59,6 @@ const RhsThread = ({
|
|||||||
rootPostId={selected.id}
|
rootPostId={selected.id}
|
||||||
useRelativeTimestamp={true}
|
useRelativeTimestamp={true}
|
||||||
isThreadView={false}
|
isThreadView={false}
|
||||||
fromSuppressed={fromSuppressed}
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@ -20,7 +20,6 @@ import {
|
|||||||
getSelectedPostId,
|
getSelectedPostId,
|
||||||
getSelectedPostCardId,
|
getSelectedPostCardId,
|
||||||
getPreviousRhsState,
|
getPreviousRhsState,
|
||||||
getIsRhsSuppressed,
|
|
||||||
} from 'selectors/rhs';
|
} from 'selectors/rhs';
|
||||||
|
|
||||||
import {RHSStates} from 'utils/constants';
|
import {RHSStates} from 'utils/constants';
|
||||||
@ -42,7 +41,6 @@ function mapStateToProps(state: GlobalState, props: RouteComponentProps) {
|
|||||||
return {
|
return {
|
||||||
isExpanded: getIsRhsExpanded(state),
|
isExpanded: getIsRhsExpanded(state),
|
||||||
isOpen: getIsRhsOpen(state),
|
isOpen: getIsRhsOpen(state),
|
||||||
isSuppressed: getIsRhsSuppressed(state),
|
|
||||||
channel,
|
channel,
|
||||||
postRightVisible: Boolean(selectedPostId) && rhsState !== RHSStates.EDIT_HISTORY,
|
postRightVisible: Boolean(selectedPostId) && rhsState !== RHSStates.EDIT_HISTORY,
|
||||||
postCardVisible: Boolean(selectedPostCardId),
|
postCardVisible: Boolean(selectedPostCardId),
|
||||||
|
@ -1,74 +0,0 @@
|
|||||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
|
||||||
// See LICENSE.txt for license information.
|
|
||||||
|
|
||||||
import {shallow} from 'enzyme';
|
|
||||||
import type {ComponentProps} from 'react';
|
|
||||||
import React from 'react';
|
|
||||||
|
|
||||||
import RhsThread from 'components/rhs_thread';
|
|
||||||
|
|
||||||
import {TestHelper} from 'utils/test_helper';
|
|
||||||
|
|
||||||
import SidebarRight from './sidebar_right';
|
|
||||||
|
|
||||||
type Props = ComponentProps<typeof SidebarRight>;
|
|
||||||
function getBaseProps(): Props {
|
|
||||||
const channel = TestHelper.getChannelMock();
|
|
||||||
return {
|
|
||||||
actions: {
|
|
||||||
closeRightHandSide: jest.fn(),
|
|
||||||
openAtPrevious: jest.fn(),
|
|
||||||
openRHSSearch: jest.fn(),
|
|
||||||
setRhsExpanded: jest.fn(),
|
|
||||||
showChannelFiles: jest.fn(),
|
|
||||||
showChannelInfo: jest.fn(),
|
|
||||||
showPinnedPosts: jest.fn(),
|
|
||||||
updateSearchTerms: jest.fn(),
|
|
||||||
},
|
|
||||||
channel,
|
|
||||||
isChannelFiles: false,
|
|
||||||
isChannelInfo: false,
|
|
||||||
isChannelMembers: false,
|
|
||||||
isExpanded: false,
|
|
||||||
isOpen: false,
|
|
||||||
isPinnedPosts: false,
|
|
||||||
isPluginView: false,
|
|
||||||
isPostEditHistory: false,
|
|
||||||
isSuppressed: false,
|
|
||||||
postCardVisible: false,
|
|
||||||
postRightVisible: false,
|
|
||||||
previousRhsState: '',
|
|
||||||
productId: '',
|
|
||||||
rhsChannel: channel,
|
|
||||||
searchVisible: false,
|
|
||||||
selectedPostCardId: '',
|
|
||||||
selectedPostId: '',
|
|
||||||
team: TestHelper.getTeamMock(),
|
|
||||||
teamId: '',
|
|
||||||
};
|
|
||||||
}
|
|
||||||
describe('pass from suppressed', () => {
|
|
||||||
it('fromSuppressed is only passed when moving from suppressed state to non suppressed', () => {
|
|
||||||
const props = getBaseProps();
|
|
||||||
const wrapper = shallow(<SidebarRight {...props}/>);
|
|
||||||
expect(wrapper.find(RhsThread)).toHaveLength(0);
|
|
||||||
|
|
||||||
wrapper.setProps({isOpen: true, postRightVisible: true});
|
|
||||||
expect(wrapper.find(RhsThread)).toHaveLength(1);
|
|
||||||
expect(wrapper.find(RhsThread).props().fromSuppressed).toBeFalsy();
|
|
||||||
|
|
||||||
wrapper.setProps({isSuppressed: true, isOpen: false});
|
|
||||||
expect(wrapper.find(RhsThread)).toHaveLength(0);
|
|
||||||
|
|
||||||
wrapper.setProps({isSuppressed: false, isOpen: true});
|
|
||||||
expect(wrapper.find(RhsThread)).toHaveLength(1);
|
|
||||||
expect(wrapper.find(RhsThread).props().fromSuppressed).toBeTruthy();
|
|
||||||
|
|
||||||
wrapper.setProps({isOpen: false, postRightVisible: false});
|
|
||||||
expect(wrapper.find(RhsThread)).toHaveLength(0);
|
|
||||||
|
|
||||||
wrapper.setProps({isOpen: true, postRightVisible: true});
|
|
||||||
expect(wrapper.find(RhsThread)).toHaveLength(1);
|
|
||||||
expect(wrapper.find(RhsThread).props().fromSuppressed).toBeFalsy();
|
|
||||||
});
|
|
||||||
});
|
|
@ -28,7 +28,6 @@ import {isMac} from 'utils/user_agent';
|
|||||||
import type {RhsState} from 'types/store/rhs';
|
import type {RhsState} from 'types/store/rhs';
|
||||||
|
|
||||||
export type Props = {
|
export type Props = {
|
||||||
isSuppressed: boolean;
|
|
||||||
isExpanded: boolean;
|
isExpanded: boolean;
|
||||||
isOpen: boolean;
|
isOpen: boolean;
|
||||||
channel: Channel;
|
channel: Channel;
|
||||||
@ -69,8 +68,6 @@ export default class SidebarRight extends React.PureComponent<Props, State> {
|
|||||||
sidebarRightWidthHolder: React.RefObject<HTMLDivElement>;
|
sidebarRightWidthHolder: React.RefObject<HTMLDivElement>;
|
||||||
previous: Partial<Props> | undefined = undefined;
|
previous: Partial<Props> | undefined = undefined;
|
||||||
focusSearchBar?: () => void;
|
focusSearchBar?: () => void;
|
||||||
lastOpenState = false;
|
|
||||||
lastSuppressedState = false;
|
|
||||||
|
|
||||||
constructor(props: Props) {
|
constructor(props: Props) {
|
||||||
super(props);
|
super(props);
|
||||||
@ -152,9 +149,6 @@ export default class SidebarRight extends React.PureComponent<Props, State> {
|
|||||||
trackEvent('ui', 'ui_rhs_opened');
|
trackEvent('ui', 'ui_rhs_opened');
|
||||||
}
|
}
|
||||||
|
|
||||||
this.lastOpenState = this.props.isOpen;
|
|
||||||
this.lastSuppressedState = this.props.isSuppressed;
|
|
||||||
|
|
||||||
const {actions, isChannelFiles, isPinnedPosts, rhsChannel, channel} = this.props;
|
const {actions, isChannelFiles, isPinnedPosts, rhsChannel, channel} = this.props;
|
||||||
if (isPinnedPosts && prevProps.isPinnedPosts === isPinnedPosts && rhsChannel.id !== prevProps.rhsChannel.id) {
|
if (isPinnedPosts && prevProps.isPinnedPosts === isPinnedPosts && rhsChannel.id !== prevProps.rhsChannel.id) {
|
||||||
actions.showPinnedPosts(rhsChannel.id);
|
actions.showPinnedPosts(rhsChannel.id);
|
||||||
@ -238,10 +232,7 @@ export default class SidebarRight extends React.PureComponent<Props, State> {
|
|||||||
content = (
|
content = (
|
||||||
<div className='post-right__container'>
|
<div className='post-right__container'>
|
||||||
<FileUploadOverlay overlayType='right'/>
|
<FileUploadOverlay overlayType='right'/>
|
||||||
<RhsThread
|
<RhsThread previousRhsState={previousRhsState}/>
|
||||||
previousRhsState={previousRhsState}
|
|
||||||
fromSuppressed={!this.lastOpenState && this.props.isOpen && this.lastSuppressedState}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
} else if (postCardVisible) {
|
} else if (postCardVisible) {
|
||||||
|
@ -12,27 +12,7 @@ exports[`components/threading/ThreadViewer should match snapshot 1`] = `
|
|||||||
overlayType="right"
|
overlayType="right"
|
||||||
/>
|
/>
|
||||||
<DeferredRenderWrapper
|
<DeferredRenderWrapper
|
||||||
channel={
|
channelId="channel_id"
|
||||||
Object {
|
|
||||||
"create_at": 0,
|
|
||||||
"creator_id": "",
|
|
||||||
"delete_at": 0,
|
|
||||||
"display_name": "",
|
|
||||||
"group_constrained": false,
|
|
||||||
"header": "",
|
|
||||||
"id": "channel_id",
|
|
||||||
"last_post_at": 0,
|
|
||||||
"last_root_post_at": 0,
|
|
||||||
"name": "",
|
|
||||||
"purpose": "",
|
|
||||||
"scheme_id": "",
|
|
||||||
"status": "",
|
|
||||||
"team_id": "team_id",
|
|
||||||
"teammate_id": "",
|
|
||||||
"type": "O",
|
|
||||||
"update_at": 0,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
isThreadView={false}
|
isThreadView={false}
|
||||||
key="id"
|
key="id"
|
||||||
onCardClick={[Function]}
|
onCardClick={[Function]}
|
||||||
|
@ -52,7 +52,6 @@ export type Props = Attrs & {
|
|||||||
isThreadView: boolean;
|
isThreadView: boolean;
|
||||||
inputPlaceholder?: string;
|
inputPlaceholder?: string;
|
||||||
rootPostId: string;
|
rootPostId: string;
|
||||||
fromSuppressed?: boolean;
|
|
||||||
enableWebSocketEventScope: boolean;
|
enableWebSocketEventScope: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -225,7 +224,7 @@ export default class ThreadViewer extends React.PureComponent<Props, State> {
|
|||||||
<DeferredThreadViewerVirt
|
<DeferredThreadViewerVirt
|
||||||
inputPlaceholder={this.props.inputPlaceholder}
|
inputPlaceholder={this.props.inputPlaceholder}
|
||||||
key={this.props.selected.id}
|
key={this.props.selected.id}
|
||||||
channel={this.props.channel}
|
channelId={this.props.channel.id}
|
||||||
onCardClick={this.handleCardClick}
|
onCardClick={this.handleCardClick}
|
||||||
postIds={this.props.postIds}
|
postIds={this.props.postIds}
|
||||||
selected={this.props.selected}
|
selected={this.props.selected}
|
||||||
@ -233,7 +232,6 @@ export default class ThreadViewer extends React.PureComponent<Props, State> {
|
|||||||
highlightedPostId={this.props.highlightedPostId}
|
highlightedPostId={this.props.highlightedPostId}
|
||||||
selectedPostFocusedAt={this.props.selectedPostFocusedAt}
|
selectedPostFocusedAt={this.props.selectedPostFocusedAt}
|
||||||
isThreadView={Boolean(this.props.isCollapsedThreadsEnabled && this.props.isThreadView)}
|
isThreadView={Boolean(this.props.isCollapsedThreadsEnabled && this.props.isThreadView)}
|
||||||
fromSuppressed={this.props.fromSuppressed}
|
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
|
@ -21,7 +21,6 @@ import Constants from 'utils/constants';
|
|||||||
import type {GlobalState} from 'types/store';
|
import type {GlobalState} from 'types/store';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
focusOnMount: boolean;
|
|
||||||
teammate?: UserProfile;
|
teammate?: UserProfile;
|
||||||
threadId: string;
|
threadId: string;
|
||||||
latestPostId: Post['id'];
|
latestPostId: Post['id'];
|
||||||
@ -30,7 +29,6 @@ type Props = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const CreateComment = forwardRef<HTMLDivElement, Props>(({
|
const CreateComment = forwardRef<HTMLDivElement, Props>(({
|
||||||
focusOnMount,
|
|
||||||
teammate,
|
teammate,
|
||||||
threadId,
|
threadId,
|
||||||
latestPostId,
|
latestPostId,
|
||||||
@ -98,7 +96,6 @@ const CreateComment = forwardRef<HTMLDivElement, Props>(({
|
|||||||
>
|
>
|
||||||
<AdvancedCreateComment
|
<AdvancedCreateComment
|
||||||
placeholder={placeholder}
|
placeholder={placeholder}
|
||||||
focusOnMount={focusOnMount}
|
|
||||||
channelId={channel.id}
|
channelId={channel.id}
|
||||||
latestPostId={latestPostId}
|
latestPostId={latestPostId}
|
||||||
rootDeleted={rootDeleted}
|
rootDeleted={rootDeleted}
|
||||||
|
@ -3,7 +3,6 @@
|
|||||||
|
|
||||||
import {connect} from 'react-redux';
|
import {connect} from 'react-redux';
|
||||||
|
|
||||||
import type {Channel} from '@mattermost/types/channels';
|
|
||||||
import type {Post} from '@mattermost/types/posts';
|
import type {Post} from '@mattermost/types/posts';
|
||||||
|
|
||||||
import {getDirectTeammate} from 'mattermost-redux/selectors/entities/channels';
|
import {getDirectTeammate} from 'mattermost-redux/selectors/entities/channels';
|
||||||
@ -20,7 +19,7 @@ import type {FakePost} from 'types/store/rhs';
|
|||||||
import ThreadViewerVirtualized from './virtualized_thread_viewer';
|
import ThreadViewerVirtualized from './virtualized_thread_viewer';
|
||||||
|
|
||||||
type OwnProps = {
|
type OwnProps = {
|
||||||
channel: Channel;
|
channelId: string;
|
||||||
postIds: Array<Post['id'] | FakePost['id']>;
|
postIds: Array<Post['id'] | FakePost['id']>;
|
||||||
selected: Post | FakePost;
|
selected: Post | FakePost;
|
||||||
useRelativeTimestamp: boolean;
|
useRelativeTimestamp: boolean;
|
||||||
@ -32,12 +31,12 @@ function makeMapStateToProps() {
|
|||||||
const getThreadLastViewedAt = makeGetThreadLastViewedAt();
|
const getThreadLastViewedAt = makeGetThreadLastViewedAt();
|
||||||
|
|
||||||
return (state: GlobalState, ownProps: OwnProps) => {
|
return (state: GlobalState, ownProps: OwnProps) => {
|
||||||
const {postIds, useRelativeTimestamp, selected, channel} = ownProps;
|
const {postIds, useRelativeTimestamp, selected, channelId} = ownProps;
|
||||||
|
|
||||||
const collapsedThreads = isCollapsedThreadsEnabled(state);
|
const collapsedThreads = isCollapsedThreadsEnabled(state);
|
||||||
const currentUserId = getCurrentUserId(state);
|
const currentUserId = getCurrentUserId(state);
|
||||||
const lastViewedAt = getThreadLastViewedAt(state, selected.id);
|
const lastViewedAt = getThreadLastViewedAt(state, selected.id);
|
||||||
const directTeammate = getDirectTeammate(state, channel.id);
|
const directTeammate = getDirectTeammate(state, channelId);
|
||||||
|
|
||||||
const lastPost = getPost(state, postIds[0]);
|
const lastPost = getPost(state, postIds[0]);
|
||||||
|
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||||
// See LICENSE.txt for license information.
|
// See LICENSE.txt for license information.
|
||||||
|
|
||||||
import {screen} from '@testing-library/react';
|
|
||||||
import {shallow} from 'enzyme';
|
import {shallow} from 'enzyme';
|
||||||
import type {ComponentProps} from 'react';
|
import type {ComponentProps} from 'react';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
@ -12,7 +11,6 @@ import type {DeepPartial} from '@mattermost/types/utilities';
|
|||||||
|
|
||||||
import {Permissions} from 'mattermost-redux/constants';
|
import {Permissions} from 'mattermost-redux/constants';
|
||||||
|
|
||||||
import {renderWithContext} from 'tests/react_testing_utils';
|
|
||||||
import {TestHelper} from 'utils/test_helper';
|
import {TestHelper} from 'utils/test_helper';
|
||||||
|
|
||||||
import VirtualizedThreadViewer from './virtualized_thread_viewer';
|
import VirtualizedThreadViewer from './virtualized_thread_viewer';
|
||||||
@ -32,7 +30,6 @@ function getBasePropsAndState(): [Props, DeepPartial<GlobalState>] {
|
|||||||
const directTeammate: UserProfile = TestHelper.getUserMock();
|
const directTeammate: UserProfile = TestHelper.getUserMock();
|
||||||
const props: Props = {
|
const props: Props = {
|
||||||
selected: post,
|
selected: post,
|
||||||
channel,
|
|
||||||
currentUserId: 'user_id',
|
currentUserId: 'user_id',
|
||||||
directTeammate,
|
directTeammate,
|
||||||
lastPost: post,
|
lastPost: post,
|
||||||
@ -42,7 +39,6 @@ function getBasePropsAndState(): [Props, DeepPartial<GlobalState>] {
|
|||||||
isMobileView: false,
|
isMobileView: false,
|
||||||
isThreadView: false,
|
isThreadView: false,
|
||||||
newMessagesSeparatorActions: [],
|
newMessagesSeparatorActions: [],
|
||||||
fromSuppressed: false,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const state: DeepPartial<GlobalState> = {
|
const state: DeepPartial<GlobalState> = {
|
||||||
@ -150,31 +146,3 @@ describe('components/threading/VirtualizedThreadViewer', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('fromSuppressed works as expected', () => {
|
|
||||||
// This setup is so AutoSizer renders its contents
|
|
||||||
const originalOffsetHeight = Object.getOwnPropertyDescriptor(HTMLElement.prototype, 'offsetHeight');
|
|
||||||
const originalOffsetWidth = Object.getOwnPropertyDescriptor(HTMLElement.prototype, 'offsetWidth');
|
|
||||||
|
|
||||||
beforeAll(() => {
|
|
||||||
Object.defineProperty(HTMLElement.prototype, 'offsetHeight', {configurable: true, value: 50});
|
|
||||||
Object.defineProperty(HTMLElement.prototype, 'offsetWidth', {configurable: true, value: 50});
|
|
||||||
});
|
|
||||||
|
|
||||||
afterAll(() => {
|
|
||||||
Object.defineProperty(HTMLElement.prototype, 'offsetHeight', originalOffsetHeight!);
|
|
||||||
Object.defineProperty(HTMLElement.prototype, 'offsetWidth', originalOffsetWidth!);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('autofocus if fromSuppressed is not set', () => {
|
|
||||||
const [props, state] = getBasePropsAndState();
|
|
||||||
renderWithContext(<VirtualizedThreadViewer {...props}/>, state);
|
|
||||||
expect(screen.getByRole('textbox')).toHaveFocus();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('do not autofocus if fromSuppressed is set', () => {
|
|
||||||
const [props, state] = getBasePropsAndState();
|
|
||||||
props.fromSuppressed = true;
|
|
||||||
renderWithContext(<VirtualizedThreadViewer {...props}/>, state);
|
|
||||||
expect(screen.getByRole('textbox')).not.toHaveFocus();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
@ -7,7 +7,6 @@ import React, {PureComponent} from 'react';
|
|||||||
import type {RefObject} from 'react';
|
import type {RefObject} from 'react';
|
||||||
import AutoSizer from 'react-virtualized-auto-sizer';
|
import AutoSizer from 'react-virtualized-auto-sizer';
|
||||||
|
|
||||||
import type {Channel} from '@mattermost/types/channels';
|
|
||||||
import type {Post} from '@mattermost/types/posts';
|
import type {Post} from '@mattermost/types/posts';
|
||||||
import type {UserProfile} from '@mattermost/types/users';
|
import type {UserProfile} from '@mattermost/types/users';
|
||||||
|
|
||||||
@ -29,7 +28,6 @@ import CreateComment from './create_comment';
|
|||||||
import Row from './thread_viewer_row';
|
import Row from './thread_viewer_row';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
channel: Channel;
|
|
||||||
currentUserId: string;
|
currentUserId: string;
|
||||||
directTeammate: UserProfile | undefined;
|
directTeammate: UserProfile | undefined;
|
||||||
highlightedPostId?: Post['id'];
|
highlightedPostId?: Post['id'];
|
||||||
@ -43,14 +41,12 @@ type Props = {
|
|||||||
isThreadView: boolean;
|
isThreadView: boolean;
|
||||||
newMessagesSeparatorActions: PluginComponent[];
|
newMessagesSeparatorActions: PluginComponent[];
|
||||||
inputPlaceholder?: string;
|
inputPlaceholder?: string;
|
||||||
fromSuppressed?: boolean;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type State = {
|
type State = {
|
||||||
createCommentHeight: number;
|
createCommentHeight: number;
|
||||||
isScrolling: boolean;
|
isScrolling: boolean;
|
||||||
topRhsPostId?: string;
|
topRhsPostId?: string;
|
||||||
userScrolled: boolean;
|
|
||||||
userScrolledToBottom: boolean;
|
userScrolledToBottom: boolean;
|
||||||
lastViewedBottom: number;
|
lastViewedBottom: number;
|
||||||
visibleStartIndex?: number;
|
visibleStartIndex?: number;
|
||||||
@ -114,7 +110,6 @@ class ThreadViewerVirtualized extends PureComponent<Props, State> {
|
|||||||
this.state = {
|
this.state = {
|
||||||
createCommentHeight: 0,
|
createCommentHeight: 0,
|
||||||
isScrolling: false,
|
isScrolling: false,
|
||||||
userScrolled: false,
|
|
||||||
userScrolledToBottom: false,
|
userScrolledToBottom: false,
|
||||||
topRhsPostId: undefined,
|
topRhsPostId: undefined,
|
||||||
lastViewedBottom: Date.now(),
|
lastViewedBottom: Date.now(),
|
||||||
@ -195,7 +190,6 @@ class ThreadViewerVirtualized extends PureComponent<Props, State> {
|
|||||||
if (!scrollUpdateWasRequested) {
|
if (!scrollUpdateWasRequested) {
|
||||||
this.scrollShortCircuit = 0;
|
this.scrollShortCircuit = 0;
|
||||||
|
|
||||||
updatedState.userScrolled = true;
|
|
||||||
updatedState.userScrolledToBottom = userScrolledToBottom;
|
updatedState.userScrolledToBottom = userScrolledToBottom;
|
||||||
|
|
||||||
if (this.props.isMobileView) {
|
if (this.props.isMobileView) {
|
||||||
@ -357,7 +351,6 @@ class ThreadViewerVirtualized extends PureComponent<Props, State> {
|
|||||||
return (
|
return (
|
||||||
<CreateComment
|
<CreateComment
|
||||||
placeholder={this.props.inputPlaceholder}
|
placeholder={this.props.inputPlaceholder}
|
||||||
focusOnMount={!this.props.fromSuppressed && !this.props.isThreadView && (this.state.userScrolledToBottom || (!this.state.userScrolled && this.getInitialPostIndex() === 0))}
|
|
||||||
isThreadView={this.props.isThreadView}
|
isThreadView={this.props.isThreadView}
|
||||||
latestPostId={this.props.lastPost.id}
|
latestPostId={this.props.lastPost.id}
|
||||||
ref={this.postCreateContainerRef}
|
ref={this.postCreateContainerRef}
|
||||||
|
@ -28,6 +28,7 @@ describe('Reducers.RHS', () => {
|
|||||||
isSidebarOpen: false,
|
isSidebarOpen: false,
|
||||||
isSidebarExpanded: false,
|
isSidebarExpanded: false,
|
||||||
editChannelMembers: false,
|
editChannelMembers: false,
|
||||||
|
shouldFocusRHS: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
test('Initial state', () => {
|
test('Initial state', () => {
|
||||||
@ -212,6 +213,7 @@ describe('Reducers.RHS', () => {
|
|||||||
selectedPostFocussedAt: 1234,
|
selectedPostFocussedAt: 1234,
|
||||||
selectedChannelId: '321',
|
selectedChannelId: '321',
|
||||||
isSidebarOpen: true,
|
isSidebarOpen: true,
|
||||||
|
shouldFocusRHS: true,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -296,6 +298,7 @@ describe('Reducers.RHS', () => {
|
|||||||
selectedPostFocussedAt: 1234,
|
selectedPostFocussedAt: 1234,
|
||||||
selectedChannelId: '321',
|
selectedChannelId: '321',
|
||||||
isSidebarOpen: true,
|
isSidebarOpen: true,
|
||||||
|
shouldFocusRHS: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
const nextState2 = rhsReducer(
|
const nextState2 = rhsReducer(
|
||||||
@ -317,6 +320,7 @@ describe('Reducers.RHS', () => {
|
|||||||
selectedChannelId: '321',
|
selectedChannelId: '321',
|
||||||
previousRhsStates: [RHSStates.SEARCH],
|
previousRhsStates: [RHSStates.SEARCH],
|
||||||
isSidebarOpen: true,
|
isSidebarOpen: true,
|
||||||
|
shouldFocusRHS: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
const nextState3 = rhsReducer(
|
const nextState3 = rhsReducer(
|
||||||
@ -339,6 +343,7 @@ describe('Reducers.RHS', () => {
|
|||||||
selectedChannelId: '321',
|
selectedChannelId: '321',
|
||||||
previousRhsStates: [RHSStates.SEARCH, RHSStates.FLAG],
|
previousRhsStates: [RHSStates.SEARCH, RHSStates.FLAG],
|
||||||
isSidebarOpen: true,
|
isSidebarOpen: true,
|
||||||
|
shouldFocusRHS: true,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -419,6 +424,7 @@ describe('Reducers.RHS', () => {
|
|||||||
selectedChannelId: '321',
|
selectedChannelId: '321',
|
||||||
previousRhsStates: [RHSStates.PIN],
|
previousRhsStates: [RHSStates.PIN],
|
||||||
isSidebarOpen: true,
|
isSidebarOpen: true,
|
||||||
|
shouldFocusRHS: true,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -619,6 +625,7 @@ describe('Reducers.RHS', () => {
|
|||||||
isSidebarOpen: true,
|
isSidebarOpen: true,
|
||||||
isSidebarExpanded: true,
|
isSidebarExpanded: true,
|
||||||
editChannelMembers: false,
|
editChannelMembers: false,
|
||||||
|
shouldFocusRHS: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
const nextState = rhsReducer(state, {type: ActionTypes.SUPPRESS_RHS});
|
const nextState = rhsReducer(state, {type: ActionTypes.SUPPRESS_RHS});
|
||||||
|
@ -13,7 +13,7 @@ import {
|
|||||||
|
|
||||||
import {SidebarSize} from 'components/resizable_sidebar/constants';
|
import {SidebarSize} from 'components/resizable_sidebar/constants';
|
||||||
|
|
||||||
import {ActionTypes, RHSStates} from 'utils/constants';
|
import {ActionTypes, RHSStates, Threads} from 'utils/constants';
|
||||||
|
|
||||||
import type {RhsState} from 'types/store/rhs';
|
import type {RhsState} from 'types/store/rhs';
|
||||||
|
|
||||||
@ -380,6 +380,21 @@ function editChannelMembers(state = false, action: AnyAction) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function shouldFocusRHS(state = false, action: AnyAction) {
|
||||||
|
switch (action.type) {
|
||||||
|
case ActionTypes.SELECT_POST:
|
||||||
|
return Boolean(action.postId);
|
||||||
|
case Threads.CHANGED_SELECTED_THREAD:
|
||||||
|
return Boolean(action.data.thread_id);
|
||||||
|
case ActionTypes.HIGHLIGHT_REPLY:
|
||||||
|
return false;
|
||||||
|
case ActionTypes.RHS_FOCUSED:
|
||||||
|
return false;
|
||||||
|
default:
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export default combineReducers({
|
export default combineReducers({
|
||||||
selectedPostId,
|
selectedPostId,
|
||||||
selectedPostFocussedAt,
|
selectedPostFocussedAt,
|
||||||
@ -400,4 +415,5 @@ export default combineReducers({
|
|||||||
isSidebarExpanded,
|
isSidebarExpanded,
|
||||||
isMenuOpen,
|
isMenuOpen,
|
||||||
editChannelMembers,
|
editChannelMembers,
|
||||||
|
shouldFocusRHS,
|
||||||
});
|
});
|
||||||
|
@ -175,10 +175,6 @@ export function getPostDraft(state: GlobalState, prefixId: string, suffixId: str
|
|||||||
return defaultDraft;
|
return defaultDraft;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getIsRhsSuppressed(state: GlobalState): boolean {
|
|
||||||
return state.views.rhsSuppressed;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getIsRhsOpen(state: GlobalState): boolean {
|
export function getIsRhsOpen(state: GlobalState): boolean {
|
||||||
return state.views.rhs.isSidebarOpen && !state.views.rhsSuppressed;
|
return state.views.rhs.isSidebarOpen && !state.views.rhsSuppressed;
|
||||||
}
|
}
|
||||||
|
8
webapp/channels/src/selectors/views/rhs.ts
Normal file
8
webapp/channels/src/selectors/views/rhs.ts
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||||
|
// See LICENSE.txt for license information.
|
||||||
|
|
||||||
|
import type {GlobalState} from 'types/store';
|
||||||
|
|
||||||
|
export function getShouldFocusRHS(state: GlobalState): boolean {
|
||||||
|
return state.views.rhs.shouldFocusRHS;
|
||||||
|
}
|
@ -41,6 +41,7 @@ export type RhsViewState = {
|
|||||||
isMenuOpen: boolean;
|
isMenuOpen: boolean;
|
||||||
editChannelMembers: boolean;
|
editChannelMembers: boolean;
|
||||||
size: SidebarSize;
|
size: SidebarSize;
|
||||||
|
shouldFocusRHS: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type RhsState = typeof RHSStates[keyof typeof RHSStates] | null;
|
export type RhsState = typeof RHSStates[keyof typeof RHSStates] | null;
|
||||||
|
@ -202,6 +202,7 @@ export const ActionTypes = keyMirror({
|
|||||||
SET_RHS_SIZE: null,
|
SET_RHS_SIZE: null,
|
||||||
|
|
||||||
RHS_GO_BACK: null,
|
RHS_GO_BACK: null,
|
||||||
|
RHS_FOCUSED: null,
|
||||||
|
|
||||||
SET_RHS_EXPANDED: null,
|
SET_RHS_EXPANDED: null,
|
||||||
TOGGLE_RHS_EXPANDED: null,
|
TOGGLE_RHS_EXPANDED: null,
|
||||||
|
Loading…
Reference in New Issue
Block a user