[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>
Этот коммит содержится в:
Akbar Abdrakhmanov
2023-10-30 23:48:29 +02:00
коммит произвёл GitHub
родитель bfd6f5aa01
Коммит c6a6ad9b4b
3 изменённых файлов: 54 добавлений и 15 удалений

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

@@ -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();