[MM-50821] Notify about successful login only once via screen reader (#24979)
* [MM-50821] Notify about successful login only once via screen reader * clear timeout via useEffect instead of inside setTimeout in AdvanceTextEditor * declare timeout as ref in AdvancedTextEditor --------- Co-authored-by: Mattermost Build <build@mattermost.com>
Этот коммит содержится в:
коммит произвёл
GitHub
родитель
bfd6f5aa01
Коммит
c6a6ad9b4b
@@ -2,7 +2,7 @@
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import classNames from 'classnames';
|
||||
import React, {useCallback, useEffect, useRef, useState} from 'react';
|
||||
import React, {useCallback, useEffect, useMemo, useRef, useState} from 'react';
|
||||
import {FormattedMessage, useIntl} from 'react-intl';
|
||||
import {useDispatch} from 'react-redux';
|
||||
|
||||
@@ -13,6 +13,7 @@ import type {ServerError} from '@mattermost/types/errors';
|
||||
import type {FileInfo} from '@mattermost/types/files';
|
||||
|
||||
import {emitShortcutReactToLastPostFrom} from 'actions/post_actions';
|
||||
import LocalStorageStore from 'stores/local_storage_store';
|
||||
|
||||
import AutoHeightSwitcher from 'components/common/auto_height_switcher';
|
||||
import EmojiPickerOverlay from 'components/emoji_picker/emoji_picker_overlay';
|
||||
@@ -191,6 +192,7 @@ const AdvanceTextEditor = ({
|
||||
const emojiPickerRef = useRef<HTMLButtonElement>(null);
|
||||
const editorActionsRef = useRef<HTMLDivElement>(null);
|
||||
const editorBodyRef = useRef<HTMLDivElement>(null);
|
||||
const timeout = useRef<NodeJS.Timeout>();
|
||||
|
||||
const [renderScrollbar, setRenderScrollbar] = useState(false);
|
||||
const [showFormattingSpacer, setShowFormattingSpacer] = useState(shouldShowPreview);
|
||||
@@ -605,6 +607,29 @@ const AdvanceTextEditor = ({
|
||||
}
|
||||
}, [handleWidthChange, message]);
|
||||
|
||||
useEffect(() => {
|
||||
return () => timeout.current && clearTimeout(timeout.current);
|
||||
}, []);
|
||||
|
||||
const wasNotifiedOfLogIn = LocalStorageStore.getWasNotifiedOfLogIn();
|
||||
|
||||
const ariaLabel = useMemo(() => {
|
||||
let label;
|
||||
if (!wasNotifiedOfLogIn) {
|
||||
label = Utils.localizeMessage(
|
||||
'channelView.login.successfull',
|
||||
'Login Successful',
|
||||
);
|
||||
|
||||
// set timeout to make sure aria-label is read by a screen reader,
|
||||
// and then set the flag to "true" to make sure it's not read again until a user logs back in
|
||||
timeout.current = setTimeout(() => {
|
||||
LocalStorageStore.setWasNotifiedOfLogIn(true);
|
||||
}, 3000);
|
||||
}
|
||||
return label ? `${label} ${ariaLabelMessageInput}` : ariaLabelMessageInput;
|
||||
}, [ariaLabelMessageInput, wasNotifiedOfLogIn]);
|
||||
|
||||
const formattingBar = (
|
||||
<AutoHeightSwitcher
|
||||
showSlot={showFormattingBar ? 1 : 2}
|
||||
@@ -632,16 +657,17 @@ const AdvanceTextEditor = ({
|
||||
'formatting-bar': showFormattingBar,
|
||||
})}
|
||||
>
|
||||
<div
|
||||
id={'speak-'}
|
||||
aria-live='assertive'
|
||||
className='sr-only'
|
||||
>
|
||||
<FormattedMessage
|
||||
id='channelView.login.successfull'
|
||||
defaultMessage='Login Successfull'
|
||||
/>
|
||||
</div>
|
||||
{!wasNotifiedOfLogIn && (
|
||||
<div
|
||||
aria-live='assertive'
|
||||
className='sr-only'
|
||||
>
|
||||
<FormattedMessage
|
||||
id='channelView.login.successfull'
|
||||
defaultMessage='Login Successful'
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
<div
|
||||
className={'AdvancedTextEditor__body'}
|
||||
disabled={readOnlyChannel}
|
||||
@@ -651,10 +677,7 @@ const AdvanceTextEditor = ({
|
||||
role='application'
|
||||
id='advancedTextEditorCell'
|
||||
data-a11y-sort-order='2'
|
||||
aria-label={Utils.localizeMessage(
|
||||
'channelView.login.successfull',
|
||||
'Login Successfull',
|
||||
) + ' ' + ariaLabelMessageInput}
|
||||
aria-label={ariaLabel}
|
||||
tabIndex={-1}
|
||||
className='AdvancedTextEditor__cell a11y__region'
|
||||
>
|
||||
|
||||
@@ -659,6 +659,11 @@ const Login = ({onCustomizeHeader}: LoginProps) => {
|
||||
// Record a successful login to local storage. If an unintentional logout occurs, e.g.
|
||||
// via session expiration, this bit won't get reset and we can notify the user as such.
|
||||
LocalStorageStore.setWasLoggedIn(true);
|
||||
|
||||
// After a user has just logged in, we set the following flag to "false" so that after
|
||||
// a user is notified of successful login, we can set it back to "true"
|
||||
LocalStorageStore.setWasNotifiedOfLogIn(false);
|
||||
|
||||
if (redirectTo && redirectTo.match(/^\/([^/]|$)/)) {
|
||||
history.push(redirectTo);
|
||||
} else if (team) {
|
||||
|
||||
@@ -16,6 +16,7 @@ export const getPenultimateChannelNameKey = (userId: string, teamId: string) =>
|
||||
const getRecentEmojisKey = (userId: string) => ['recent_emojis', userId].join(':');
|
||||
const getWasLoggedInKey = () => 'was_logged_in';
|
||||
const teamIdJoinedOnLoadKey = 'teamIdJoinedOnLoad';
|
||||
const wasNotifiedOfLogInKey = 'was_notified_of_login';
|
||||
|
||||
const getPathScopedKey = (path: string, key: string) => {
|
||||
if (path === '' || path === '/') {
|
||||
@@ -156,6 +157,16 @@ class LocalStorageStoreClass {
|
||||
getWasLoggedIn() {
|
||||
return this.getItem(getWasLoggedInKey()) === 'true';
|
||||
}
|
||||
|
||||
// the following flag's setter and getter are used to make sure a user is notified (via aria-label)
|
||||
// about a successful login only once (MM-50821)
|
||||
setWasNotifiedOfLogIn(wasNotified: boolean) {
|
||||
this.setItem(wasNotifiedOfLogInKey, String(wasNotified));
|
||||
}
|
||||
|
||||
getWasNotifiedOfLogIn() {
|
||||
return this.getItem(wasNotifiedOfLogInKey) === 'true';
|
||||
}
|
||||
}
|
||||
|
||||
const LocalStorageStore = new LocalStorageStoreClass();
|
||||
|
||||
Ссылка в новой задаче
Block a user