Fix autofocus on load (#28090)
* Fix autofocus on load * Address feedback * Fix styles lint * Revert unintended change * fix tests * Update text
Этот коммит содержится в:
коммит произвёл
GitHub
родитель
7232feecf7
Коммит
92793928bc
@@ -1,5 +1,20 @@
|
|||||||
@import 'utils/variables';
|
@import 'utils/variables';
|
||||||
|
|
||||||
|
.AdvancedTextEditor__skeleton {
|
||||||
|
display: flex;
|
||||||
|
height: 122px;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
padding-left: 10px;
|
||||||
|
border: 2px solid rgba(var(--center-channel-color-rgb), 0.16);
|
||||||
|
border-radius: 4px;
|
||||||
|
margin: 0 24px 24px;
|
||||||
|
color: rgba(var(--center-channel-color-rgb), 0.75);
|
||||||
|
.btn {
|
||||||
|
margin: 10px
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.AdvancedTextEditor {
|
.AdvancedTextEditor {
|
||||||
&__ctr {
|
&__ctr {
|
||||||
form {
|
form {
|
||||||
|
|||||||
@@ -87,6 +87,8 @@ const useTextboxFocus = (
|
|||||||
if (isRHS && shouldFocusRHS) {
|
if (isRHS && shouldFocusRHS) {
|
||||||
focusTextbox();
|
focusTextbox();
|
||||||
dispatch(focusedRHS());
|
dispatch(focusedRHS());
|
||||||
|
} else if (!isRHS && !shouldFocusRHS) {
|
||||||
|
focusTextbox();
|
||||||
}
|
}
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ exports[`components/channel_view Should match snapshot if channel is archived 1`
|
|||||||
"url": "/team/channel/channelId",
|
"url": "/team/channel/channelId",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
missingChannelRole={false}
|
||||||
teamUrl="/team"
|
teamUrl="/team"
|
||||||
viewArchivedChannels={false}
|
viewArchivedChannels={false}
|
||||||
/>
|
/>
|
||||||
@@ -84,6 +85,7 @@ exports[`components/channel_view Should match snapshot if channel is deactivated
|
|||||||
"url": "/team/channel/channelId",
|
"url": "/team/channel/channelId",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
missingChannelRole={false}
|
||||||
teamUrl="/team"
|
teamUrl="/team"
|
||||||
viewArchivedChannels={false}
|
viewArchivedChannels={false}
|
||||||
/>
|
/>
|
||||||
@@ -141,6 +143,7 @@ exports[`components/channel_view Should match snapshot with base props 1`] = `
|
|||||||
"url": "/team/channel/channelId",
|
"url": "/team/channel/channelId",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
missingChannelRole={false}
|
||||||
teamUrl="/team"
|
teamUrl="/team"
|
||||||
viewArchivedChannels={false}
|
viewArchivedChannels={false}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ describe('components/channel_view', () => {
|
|||||||
isFirstAdmin: false,
|
isFirstAdmin: false,
|
||||||
enableWebSocketEventScope: false,
|
enableWebSocketEventScope: false,
|
||||||
isChannelBookmarksEnabled: false,
|
isChannelBookmarksEnabled: false,
|
||||||
|
missingChannelRole: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
it('Should match snapshot with base props', () => {
|
it('Should match snapshot with base props', () => {
|
||||||
|
|||||||
@@ -12,6 +12,8 @@ import PostView from 'components/post_view';
|
|||||||
|
|
||||||
import WebSocketClient from 'client/web_websocket_client';
|
import WebSocketClient from 'client/web_websocket_client';
|
||||||
|
|
||||||
|
import InputLoading from './input_loading';
|
||||||
|
|
||||||
import type {PropsFromRedux} from './index';
|
import type {PropsFromRedux} from './index';
|
||||||
|
|
||||||
const ChannelHeader = makeAsyncComponent('ChannelHeader', lazy(() => import('components/channel_header')));
|
const ChannelHeader = makeAsyncComponent('ChannelHeader', lazy(() => import('components/channel_header')));
|
||||||
@@ -28,6 +30,7 @@ type State = {
|
|||||||
url: string;
|
url: string;
|
||||||
focusedPostId?: string;
|
focusedPostId?: string;
|
||||||
deferredPostView: any;
|
deferredPostView: any;
|
||||||
|
waitForLoader: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export default class ChannelView extends React.PureComponent<Props, State> {
|
export default class ChannelView extends React.PureComponent<Props, State> {
|
||||||
@@ -77,6 +80,7 @@ export default class ChannelView extends React.PureComponent<Props, State> {
|
|||||||
channelId: props.channelId,
|
channelId: props.channelId,
|
||||||
focusedPostId: props.match.params.postid,
|
focusedPostId: props.match.params.postid,
|
||||||
deferredPostView: ChannelView.createDeferredPostView(),
|
deferredPostView: ChannelView.createDeferredPostView(),
|
||||||
|
waitForLoader: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
this.channelViewRef = React.createRef();
|
this.channelViewRef = React.createRef();
|
||||||
@@ -86,6 +90,10 @@ export default class ChannelView extends React.PureComponent<Props, State> {
|
|||||||
this.props.goToLastViewedChannel();
|
this.props.goToLastViewedChannel();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
onUpdateInputShowLoader = (v: boolean) => {
|
||||||
|
this.setState({waitForLoader: v});
|
||||||
|
};
|
||||||
|
|
||||||
componentDidUpdate(prevProps: Props) {
|
componentDidUpdate(prevProps: Props) {
|
||||||
// TODO: debounce
|
// TODO: debounce
|
||||||
if (prevProps.channelId !== this.props.channelId && this.props.enableWebSocketEventScope) {
|
if (prevProps.channelId !== this.props.channelId && this.props.enableWebSocketEventScope) {
|
||||||
@@ -151,6 +159,8 @@ export default class ChannelView extends React.PureComponent<Props, State> {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
} else if (this.props.missingChannelRole || this.state.waitForLoader) {
|
||||||
|
createPost = <InputLoading updateWaitForLoader={this.onUpdateInputShowLoader}/>;
|
||||||
} else {
|
} else {
|
||||||
createPost = (
|
createPost = (
|
||||||
<div
|
<div
|
||||||
|
|||||||
@@ -5,8 +5,11 @@ import {connect} from 'react-redux';
|
|||||||
import type {ConnectedProps} from 'react-redux';
|
import type {ConnectedProps} from 'react-redux';
|
||||||
import {withRouter} from 'react-router-dom';
|
import {withRouter} from 'react-router-dom';
|
||||||
|
|
||||||
import {getCurrentChannel, getDirectTeammate} from 'mattermost-redux/selectors/entities/channels';
|
import type {Channel} from '@mattermost/types/channels';
|
||||||
|
|
||||||
|
import {getCurrentChannel, getDirectTeammate, getMyChannelMembership} from 'mattermost-redux/selectors/entities/channels';
|
||||||
import {getConfig, getLicense} from 'mattermost-redux/selectors/entities/general';
|
import {getConfig, getLicense} from 'mattermost-redux/selectors/entities/general';
|
||||||
|
import {getRoles} from 'mattermost-redux/selectors/entities/roles_helpers';
|
||||||
import {getCurrentRelativeTeamUrl} from 'mattermost-redux/selectors/entities/teams';
|
import {getCurrentRelativeTeamUrl} from 'mattermost-redux/selectors/entities/teams';
|
||||||
import {isFirstAdmin} from 'mattermost-redux/selectors/entities/users';
|
import {isFirstAdmin} from 'mattermost-redux/selectors/entities/users';
|
||||||
|
|
||||||
@@ -24,6 +27,11 @@ function isDeactivatedChannel(state: GlobalState, channelId: string) {
|
|||||||
return Boolean(teammate && teammate.delete_at);
|
return Boolean(teammate && teammate.delete_at);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function isMissingChannelRoles(state: GlobalState, channel?: Channel) {
|
||||||
|
const channelRoles = channel ? getMyChannelMembership(state, channel.id)?.roles || '' : '';
|
||||||
|
return !channelRoles.split(' ').some((v) => Boolean(getRoles(state)[v]));
|
||||||
|
}
|
||||||
|
|
||||||
function mapStateToProps(state: GlobalState) {
|
function mapStateToProps(state: GlobalState) {
|
||||||
const channel = getCurrentChannel(state);
|
const channel = getCurrentChannel(state);
|
||||||
|
|
||||||
@@ -33,6 +41,8 @@ function mapStateToProps(state: GlobalState) {
|
|||||||
const enableOnboardingFlow = config.EnableOnboardingFlow === 'true';
|
const enableOnboardingFlow = config.EnableOnboardingFlow === 'true';
|
||||||
const enableWebSocketEventScope = config.FeatureFlagWebSocketEventScope === 'true';
|
const enableWebSocketEventScope = config.FeatureFlagWebSocketEventScope === 'true';
|
||||||
|
|
||||||
|
const missingChannelRole = isMissingChannelRoles(state, channel);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
channelId: channel ? channel.id : '',
|
channelId: channel ? channel.id : '',
|
||||||
deactivatedChannel: channel ? isDeactivatedChannel(state, channel.id) : false,
|
deactivatedChannel: channel ? isDeactivatedChannel(state, channel.id) : false,
|
||||||
@@ -44,6 +54,7 @@ function mapStateToProps(state: GlobalState) {
|
|||||||
isFirstAdmin: isFirstAdmin(state),
|
isFirstAdmin: isFirstAdmin(state),
|
||||||
enableWebSocketEventScope,
|
enableWebSocketEventScope,
|
||||||
isChannelBookmarksEnabled: getIsChannelBookmarksEnabled(state),
|
isChannelBookmarksEnabled: getIsChannelBookmarksEnabled(state),
|
||||||
|
missingChannelRole,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
63
webapp/channels/src/components/channel_view/input_loading.tsx
Обычный файл
63
webapp/channels/src/components/channel_view/input_loading.tsx
Обычный файл
@@ -0,0 +1,63 @@
|
|||||||
|
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||||
|
// See LICENSE.txt for license information.
|
||||||
|
|
||||||
|
import React, {useEffect, useState} from 'react';
|
||||||
|
import {FormattedMessage} from 'react-intl';
|
||||||
|
|
||||||
|
const onClickRefresh = () => {
|
||||||
|
location.reload();
|
||||||
|
};
|
||||||
|
|
||||||
|
const TIME_TO_SHOW = 5000;
|
||||||
|
const TIME_TO_DISMISS = 2000;
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
updateWaitForLoader: (v: boolean) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const InputLoading = ({
|
||||||
|
updateWaitForLoader,
|
||||||
|
}: Props) => {
|
||||||
|
const [showMessage, setShowMessage] = useState(false);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
let timeout = setTimeout(() => {
|
||||||
|
setShowMessage(true);
|
||||||
|
updateWaitForLoader(true);
|
||||||
|
timeout = setTimeout(() => {
|
||||||
|
updateWaitForLoader(false);
|
||||||
|
}, TIME_TO_DISMISS);
|
||||||
|
}, TIME_TO_SHOW);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
clearTimeout(timeout);
|
||||||
|
updateWaitForLoader(false);
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className='AdvancedTextEditor__skeleton'
|
||||||
|
>
|
||||||
|
{showMessage && (
|
||||||
|
<>
|
||||||
|
<FormattedMessage
|
||||||
|
id='center_panel.input.cannot_load_component'
|
||||||
|
defaultMessage='Something went wrong while loading the component. Please wait a moment, or try reloading the app.'
|
||||||
|
/>
|
||||||
|
<button
|
||||||
|
className='btn btn-tertiary channel-archived__close-btn'
|
||||||
|
onClick={onClickRefresh}
|
||||||
|
>
|
||||||
|
<FormattedMessage
|
||||||
|
id='center_panel.reloadPage'
|
||||||
|
defaultMessage='Reload'
|
||||||
|
/>
|
||||||
|
</button>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default InputLoading;
|
||||||
@@ -3037,6 +3037,8 @@
|
|||||||
"center_panel.archived.closeChannel": "Close Channel",
|
"center_panel.archived.closeChannel": "Close Channel",
|
||||||
"center_panel.direct.closeDirectMessage": "Close Direct Message",
|
"center_panel.direct.closeDirectMessage": "Close Direct Message",
|
||||||
"center_panel.direct.closeGroupMessage": "Close Group Message",
|
"center_panel.direct.closeGroupMessage": "Close Group Message",
|
||||||
|
"center_panel.input.cannot_load_component": "Something went wrong while loading the component. Please wait a moment, or try reloading the app.",
|
||||||
|
"center_panel.reloadPage": "Reload",
|
||||||
"change_url.endWithLetter": "URLs must end with a lowercase letter or number.",
|
"change_url.endWithLetter": "URLs must end with a lowercase letter or number.",
|
||||||
"change_url.helpText": "You can use lowercase letters, numbers, dashes, and underscores.",
|
"change_url.helpText": "You can use lowercase letters, numbers, dashes, and underscores.",
|
||||||
"change_url.invalidDirectMessage": "User IDs are not allowed in channel URLs.",
|
"change_url.invalidDirectMessage": "User IDs are not allowed in channel URLs.",
|
||||||
|
|||||||
@@ -79,6 +79,7 @@ describe('identifyElementRegion', () => {
|
|||||||
[channel.id]: TestHelper.getChannelMembershipMock({
|
[channel.id]: TestHelper.getChannelMembershipMock({
|
||||||
channel_id: channel.id,
|
channel_id: channel.id,
|
||||||
user_id: user.id,
|
user_id: user.id,
|
||||||
|
roles: 'system_admin',
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
Ссылка в новой задаче
Block a user