Merge branch 'userlist-accessibility' of github.com:KDSBrowne/bigbluebutton into userlist-accessibility
This commit is contained in:
commit
a89c70a04c
@ -18,6 +18,7 @@ const propTypes = {
|
||||
class EmojiMenu extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
}
|
||||
|
||||
render() {
|
||||
@ -28,8 +29,8 @@ class EmojiMenu extends Component {
|
||||
} = this.props;
|
||||
|
||||
return (
|
||||
<Dropdown ref="dropdown">
|
||||
<DropdownTrigger>
|
||||
<Dropdown autoFocus={true}>
|
||||
<DropdownTrigger placeInTabOrder={true}>
|
||||
<Button
|
||||
role="button"
|
||||
label={intl.formatMessage(intlMessages.statusTriggerLabel)}
|
||||
@ -57,54 +58,63 @@ class EmojiMenu extends Component {
|
||||
label={intl.formatMessage(intlMessages.raiseLabel)}
|
||||
description={intl.formatMessage(intlMessages.raiseDesc)}
|
||||
onClick={() => actions.setEmojiHandler('raiseHand')}
|
||||
tabIndex={-1}
|
||||
/>
|
||||
<DropdownListItem
|
||||
icon="happy"
|
||||
label={intl.formatMessage(intlMessages.happyLabel)}
|
||||
description={intl.formatMessage(intlMessages.happyDesc)}
|
||||
onClick={() => actions.setEmojiHandler('happy')}
|
||||
tabIndex={-1}
|
||||
/>
|
||||
<DropdownListItem
|
||||
icon="undecided"
|
||||
label={intl.formatMessage(intlMessages.undecidedLabel)}
|
||||
description={intl.formatMessage(intlMessages.undecidedDesc)}
|
||||
onClick={() => actions.setEmojiHandler('neutral')}
|
||||
tabIndex={-1}
|
||||
/>
|
||||
<DropdownListItem
|
||||
icon="sad"
|
||||
label={intl.formatMessage(intlMessages.sadLabel)}
|
||||
description={intl.formatMessage(intlMessages.sadDesc)}
|
||||
onClick={() => actions.setEmojiHandler('sad')}
|
||||
tabIndex={-1}
|
||||
/>
|
||||
<DropdownListItem
|
||||
icon="confused"
|
||||
label={intl.formatMessage(intlMessages.confusedLabel)}
|
||||
description={intl.formatMessage(intlMessages.confusedDesc)}
|
||||
onClick={() => actions.setEmojiHandler('confused')}
|
||||
tabIndex={-1}
|
||||
/>
|
||||
<DropdownListItem
|
||||
icon="time"
|
||||
label={intl.formatMessage(intlMessages.awayLabel)}
|
||||
description={intl.formatMessage(intlMessages.awayDesc)}
|
||||
onClick={() => actions.setEmojiHandler('away')}
|
||||
tabIndex={-1}
|
||||
/>
|
||||
<DropdownListItem
|
||||
icon="thumbs_up"
|
||||
label={intl.formatMessage(intlMessages.thumbsupLabel)}
|
||||
description={intl.formatMessage(intlMessages.thumbsupDesc)}
|
||||
onClick={() => actions.setEmojiHandler('thumbsUp')}
|
||||
tabIndex={-1}
|
||||
/>
|
||||
<DropdownListItem
|
||||
icon="thumbs_down"
|
||||
label={intl.formatMessage(intlMessages.thumbsdownLabel)}
|
||||
description={intl.formatMessage(intlMessages.thumbsdownDesc)}
|
||||
onClick={() => actions.setEmojiHandler('thumbsDown')}
|
||||
tabIndex={-1}
|
||||
/>
|
||||
<DropdownListItem
|
||||
icon="applause"
|
||||
label={intl.formatMessage(intlMessages.applauseLabel)}
|
||||
description={intl.formatMessage(intlMessages.applauseDesc)}
|
||||
onClick={() => actions.setEmojiHandler('applause')}
|
||||
tabIndex={-1}
|
||||
/>
|
||||
<DropdownListSeparator />
|
||||
<DropdownListItem
|
||||
@ -112,6 +122,7 @@ class EmojiMenu extends Component {
|
||||
label={intl.formatMessage(intlMessages.clearLabel)}
|
||||
description={intl.formatMessage(intlMessages.clearDesc)}
|
||||
onClick={() => actions.setEmojiHandler('none')}
|
||||
tabIndex={-1}
|
||||
/>
|
||||
</DropdownList>
|
||||
</DropdownContent>
|
||||
|
@ -83,16 +83,19 @@ class Dropdown extends Component {
|
||||
|
||||
handleShow() {
|
||||
this.setState({ isOpen: true }, this.handleStateCallback);
|
||||
|
||||
const contentElement = findDOMNode(this.refs.content);
|
||||
contentElement.querySelector(FOCUSABLE_CHILDREN).focus();
|
||||
}
|
||||
|
||||
handleHide() {
|
||||
|
||||
const { autoFocus } = this.props;
|
||||
|
||||
this.setState({ isOpen: false }, this.handleStateCallback);
|
||||
|
||||
if (autoFocus) {
|
||||
const triggerElement = findDOMNode(this.refs.trigger);
|
||||
triggerElement.focus();
|
||||
}
|
||||
}
|
||||
|
||||
componentDidMount () {
|
||||
const { addEventListener } = window;
|
||||
@ -122,7 +125,14 @@ class Dropdown extends Component {
|
||||
}
|
||||
|
||||
render() {
|
||||
const { children, className, style, intl } = this.props;
|
||||
const {
|
||||
children,
|
||||
className,
|
||||
style, intl,
|
||||
hasPopup,
|
||||
ariaLive,
|
||||
ariaRelevant,
|
||||
} = this.props;
|
||||
|
||||
let trigger = children.find(x => x.type === DropdownTrigger);
|
||||
let content = children.find(x => x.type === DropdownContent);
|
||||
@ -143,7 +153,12 @@ class Dropdown extends Component {
|
||||
});
|
||||
|
||||
return (
|
||||
<div style={style} className={cx(styles.dropdown, className)}>
|
||||
<div
|
||||
style={style}
|
||||
className={cx(styles.dropdown, className)}
|
||||
aria-live={ariaLive}
|
||||
aria-relevant={ariaRelevant}
|
||||
aria-haspopup={hasPopup}>
|
||||
{trigger}
|
||||
{content}
|
||||
{ this.state.isOpen ?
|
||||
|
@ -78,7 +78,7 @@ export default class DropdownList extends Component {
|
||||
nextActiveItemIndex = this.childrenRefs.length - 1;
|
||||
}
|
||||
|
||||
if ([KEY_CODES.TAB, KEY_CODES.ESCAPE].includes(event.which)) {
|
||||
if ([KEY_CODES.ESCAPE].includes(event.which)) {
|
||||
nextActiveItemIndex = 0;
|
||||
dropdownHide();
|
||||
}
|
||||
|
@ -29,14 +29,16 @@ export default class DropdownListItem extends Component {
|
||||
|
||||
render() {
|
||||
const { label, description, children, injectRef, tabIndex, onClick, onKeyDown,
|
||||
className, style, separator, intl, } = this.props;
|
||||
className, style, separator, intl, placeInTabOrder, } = this.props;
|
||||
|
||||
let index = (placeInTabOrder) ? 0 : -1;
|
||||
|
||||
return (
|
||||
<li
|
||||
ref={injectRef}
|
||||
onClick={onClick}
|
||||
onKeyDown={onKeyDown}
|
||||
tabIndex={tabIndex}
|
||||
tabIndex={index}
|
||||
aria-labelledby={this.labelID}
|
||||
aria-describedby={this.descID}
|
||||
className={cx(styles.item, className)}
|
||||
|
@ -41,14 +41,16 @@ export default class DropdownTrigger extends Component {
|
||||
}
|
||||
|
||||
render() {
|
||||
const { children, style, className, } = this.props;
|
||||
const { children, style, className, placeInTabOrder, } = this.props;
|
||||
const TriggerComponent = React.Children.only(children);
|
||||
|
||||
let index = (placeInTabOrder) ? '0' : '-1';
|
||||
|
||||
const TriggerComponentBounded = React.cloneElement(children, {
|
||||
onClick: this.handleClick,
|
||||
onKeyDown: this.handleKeyDown,
|
||||
'aria-haspopup': true,
|
||||
tabIndex: '0',
|
||||
tabIndex: index,
|
||||
style: style,
|
||||
className: cx(children.props.className, className),
|
||||
});
|
||||
|
@ -83,8 +83,8 @@ class SettingsDropdown extends Component {
|
||||
}
|
||||
|
||||
return (
|
||||
<Dropdown ref="dropdown">
|
||||
<DropdownTrigger>
|
||||
<Dropdown autoFocus={true}>
|
||||
<DropdownTrigger placeInTabOrder={true}>
|
||||
<Button
|
||||
label={intl.formatMessage(intlMessages.optionsLabel)}
|
||||
icon="more"
|
||||
|
@ -33,7 +33,7 @@ export default class UserAvatar extends Component {
|
||||
|
||||
return (
|
||||
<div className={user.isOnline ? styles.userAvatar : styles.userLogout}
|
||||
style={avatarStyles}>
|
||||
style={avatarStyles} aria-hidden="true">
|
||||
<div>
|
||||
{this.renderAvatarContent()}
|
||||
</div>
|
||||
|
@ -43,6 +43,7 @@ class ChatListItem extends Component {
|
||||
openChat,
|
||||
compact,
|
||||
intl,
|
||||
tabIndex,
|
||||
} = this.props;
|
||||
|
||||
const linkPath = [PRIVATE_CHAT_PATH, chat.id].join('');
|
||||
@ -57,12 +58,13 @@ class ChatListItem extends Component {
|
||||
}
|
||||
|
||||
return (
|
||||
<li className={cx(styles.chatListItem, linkClasses)}>
|
||||
<Link
|
||||
to={linkPath}
|
||||
className={styles.chatListItemLink}
|
||||
className={cx(styles.chatListItem, linkClasses)}
|
||||
role="button"
|
||||
aria-expanded={isCurrentChat}>
|
||||
aria-expanded={isCurrentChat}
|
||||
tabIndex={tabIndex}>
|
||||
<div className={styles.chatListItemLink}>
|
||||
{chat.icon ? this.renderChatIcon() : this.renderChatAvatar()}
|
||||
<div className={styles.chatName}>
|
||||
{!compact ? <span className={styles.chatNameMain}>{chat.name}</span> : null }
|
||||
@ -78,8 +80,8 @@ class ChatListItem extends Component {
|
||||
</div>
|
||||
</div>
|
||||
: null}
|
||||
</div>
|
||||
</Link>
|
||||
</li>
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -4,6 +4,7 @@
|
||||
@extend %list-item;
|
||||
cursor: pointer;
|
||||
padding: 0;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.chatListItemLink {
|
||||
|
@ -6,6 +6,7 @@ import cx from 'classnames';
|
||||
import { defineMessages, injectIntl } from 'react-intl';
|
||||
import UserListItem from './user-list-item/component.jsx';
|
||||
import ChatListItem from './chat-list-item/component.jsx';
|
||||
import KEY_CODES from '/imports/utils/keyCodes';
|
||||
|
||||
const propTypes = {
|
||||
openChats: PropTypes.array.isRequired,
|
||||
@ -30,6 +31,128 @@ class UserList extends Component {
|
||||
this.state = {
|
||||
compact: this.props.compact,
|
||||
};
|
||||
|
||||
this.rovingIndex = this.rovingIndex.bind(this);
|
||||
this.focusList = this.focusList.bind(this);
|
||||
this.focusListItem = this.focusListItem.bind(this);
|
||||
this.counter = -1;
|
||||
}
|
||||
|
||||
focusList(activeElement, list) {
|
||||
activeElement.tabIndex = -1;
|
||||
this.counter = 0;
|
||||
list.tabIndex = 0;
|
||||
list.focus();
|
||||
}
|
||||
|
||||
focusListItem(active, direction, element, count) {
|
||||
|
||||
function select() {
|
||||
element.tabIndex = 0;
|
||||
element.focus();
|
||||
}
|
||||
|
||||
active.tabIndex = -1;
|
||||
|
||||
switch (direction) {
|
||||
case 'down':
|
||||
element.childNodes[this.counter].tabIndex = 0;
|
||||
element.childNodes[this.counter].focus();
|
||||
this.counter++;
|
||||
break;
|
||||
case 'up':
|
||||
this.counter--;
|
||||
element.childNodes[this.counter].tabIndex = 0;
|
||||
element.childNodes[this.counter].focus();
|
||||
break;
|
||||
case 'upLoopUp':
|
||||
case 'upLoopDown':
|
||||
this.counter = count - 1;
|
||||
select();
|
||||
break;
|
||||
case 'downLoopDown':
|
||||
this.counter = -1;
|
||||
select();
|
||||
break;
|
||||
case 'downLoopUp':
|
||||
this.counter = 1;
|
||||
select();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
rovingIndex(...Args) {
|
||||
const { users, openChats } = this.props;
|
||||
|
||||
let active = document.activeElement;
|
||||
let list;
|
||||
let items;
|
||||
let count;
|
||||
|
||||
switch (Args[1]) {
|
||||
case 'users':
|
||||
list = this._usersList;
|
||||
items = this._userItems;
|
||||
count = users.length;
|
||||
break;
|
||||
case 'messages':
|
||||
list = this._msgsList;
|
||||
items = this._msgItems;
|
||||
count = openChats.length;
|
||||
break;
|
||||
}
|
||||
|
||||
if (Args[0].keyCode === KEY_CODES.ESCAPE
|
||||
|| this.counter === -1
|
||||
|| this.counter > count) {
|
||||
this.focusList(active, list);
|
||||
}
|
||||
|
||||
if (Args[0].keyCode === KEY_CODES.ENTER
|
||||
|| Args[0].keyCode === KEY_CODES.ARROW_RIGHT
|
||||
|| Args[0].keyCode === KEY_CODES.ARROW_LEFT) {
|
||||
active.firstChild.click();
|
||||
}
|
||||
|
||||
if (Args[0].keyCode === KEY_CODES.ARROW_DOWN) {
|
||||
if (this.counter < count) {
|
||||
this.focusListItem(active, 'down', items);
|
||||
}else if (this.counter === count) {
|
||||
this.focusListItem(active, 'downLoopDown', list);
|
||||
}else if (this.counter === 0) {
|
||||
this.focusListItem(active, 'downLoopUp', list);
|
||||
}
|
||||
}
|
||||
|
||||
if (Args[0].keyCode === KEY_CODES.ARROW_UP) {
|
||||
if (this.counter < count && this.counter !== 0) {
|
||||
this.focusListItem(active, 'up', items);
|
||||
}else if (this.counter === 0) {
|
||||
this.focusListItem(active, 'upLoopUp', list, count);
|
||||
}else if (this.counter === count) {
|
||||
this.focusListItem(active, 'upLoopDown', list, count);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
let _this = this;
|
||||
|
||||
if (!this.state.compact) {
|
||||
this._msgsList.addEventListener('keypress', function (event) {
|
||||
_this.rovingIndex.call(this, event, 'messages');
|
||||
});
|
||||
|
||||
this._usersList.addEventListener('keypress', function (event) {
|
||||
_this.rovingIndex.call(this, event, 'users');
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this._msgsList.removeEventListener('keypress', function (event) {}, false);
|
||||
|
||||
this._usersList.removeEventListener('keypress', function (event) {}, false);
|
||||
}
|
||||
|
||||
render() {
|
||||
@ -48,9 +171,9 @@ class UserList extends Component {
|
||||
<div className={styles.header}>
|
||||
{
|
||||
!this.state.compact ?
|
||||
<h2 className={styles.headerTitle}>
|
||||
<div className={styles.headerTitle} role="banner">
|
||||
{intl.formatMessage(intlMessages.participantsTitle)}
|
||||
</h2> : null
|
||||
</div> : null
|
||||
}
|
||||
</div>
|
||||
);
|
||||
@ -76,11 +199,14 @@ class UserList extends Component {
|
||||
<div className={styles.messages}>
|
||||
{
|
||||
!this.state.compact ?
|
||||
<h3 className={styles.smallTitle}>
|
||||
<div className={styles.smallTitle} role="banner">
|
||||
{intl.formatMessage(intlMessages.messagesTitle)}
|
||||
</h3> : <hr className={styles.separator}></hr>
|
||||
</div> : <hr className={styles.separator}></hr>
|
||||
}
|
||||
<div className={styles.scrollableList}>
|
||||
<div
|
||||
tabIndex={0}
|
||||
className={styles.scrollableList}
|
||||
ref={(r) => this._msgsList = r}>
|
||||
<ReactCSSTransitionGroup
|
||||
transitionName={listTransition}
|
||||
transitionAppear={true}
|
||||
@ -89,15 +215,18 @@ class UserList extends Component {
|
||||
transitionAppearTimeout={0}
|
||||
transitionEnterTimeout={0}
|
||||
transitionLeaveTimeout={0}
|
||||
component="ul"
|
||||
component="div"
|
||||
className={cx(styles.chatsList, styles.scrollableList)}>
|
||||
<div ref={(r) => this._msgItems = r}>
|
||||
{openChats.map(chat => (
|
||||
<ChatListItem
|
||||
compact={this.state.compact}
|
||||
key={chat.id}
|
||||
openChat={openChat}
|
||||
chat={chat} />
|
||||
chat={chat}
|
||||
tabIndex={-1} />
|
||||
))}
|
||||
</div>
|
||||
</ReactCSSTransitionGroup>
|
||||
</div>
|
||||
</div>
|
||||
@ -151,11 +280,15 @@ class UserList extends Component {
|
||||
<div className={styles.participants}>
|
||||
{
|
||||
!this.state.compact ?
|
||||
<h3 className={styles.smallTitle}>
|
||||
<div className={styles.smallTitle} role="banner">
|
||||
{intl.formatMessage(intlMessages.usersTitle)}
|
||||
({users.length})
|
||||
</h3> : <hr className={styles.separator}></hr>
|
||||
</div> : <hr className={styles.separator}></hr>
|
||||
}
|
||||
<div
|
||||
className={styles.scrollableList}
|
||||
tabIndex={0}
|
||||
ref={(r) => this._usersList = r}>
|
||||
<ReactCSSTransitionGroup
|
||||
transitionName={listTransition}
|
||||
transitionAppear={true}
|
||||
@ -164,8 +297,9 @@ class UserList extends Component {
|
||||
transitionAppearTimeout={0}
|
||||
transitionEnterTimeout={0}
|
||||
transitionLeaveTimeout={0}
|
||||
component="ul"
|
||||
component="div"
|
||||
className={cx(styles.participantsList, styles.scrollableList)}>
|
||||
<div ref={(r) => this._userItems = r}>
|
||||
{
|
||||
users.map(user => (
|
||||
<UserListItem
|
||||
@ -175,11 +309,12 @@ class UserList extends Component {
|
||||
user={user}
|
||||
currentUser={currentUser}
|
||||
userActions={userActions}
|
||||
meeting={meeting}
|
||||
/>
|
||||
))}
|
||||
/>))
|
||||
}
|
||||
</div>
|
||||
</ReactCSSTransitionGroup>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -54,6 +54,14 @@ const messages = defineMessages({
|
||||
id: 'app.userlist.menuTitleContext',
|
||||
description: 'adds context to userListItem menu title',
|
||||
},
|
||||
userItemStatusAriaLabel: {
|
||||
id: 'app.userlist.useritem.status.arialabel',
|
||||
description: 'adds aria label for user and status',
|
||||
},
|
||||
userItemAriaLabel: {
|
||||
id: 'app.userlist.useritem.nostatus.arialabel',
|
||||
description: 'aria label for user',
|
||||
},
|
||||
});
|
||||
|
||||
const userActionsTransition = {
|
||||
@ -234,15 +242,81 @@ class UserListItem extends Component {
|
||||
userItemContentsStyle[styles.userItemContentsCompact] = compact;
|
||||
userItemContentsStyle[styles.active] = this.state.isActionsOpen;
|
||||
|
||||
const {
|
||||
user,
|
||||
intl,
|
||||
} = this.props;
|
||||
|
||||
let you = (user.isCurrent) ? intl.formatMessage(messages.you) : null;
|
||||
|
||||
let presenter = (user.isPresenter)
|
||||
? intl.formatMessage(messages.presenter)
|
||||
: null;
|
||||
|
||||
let userAriaLabel = (user.emoji.status === 'none')
|
||||
? intl.formatMessage(messages.userItemAriaLabel,
|
||||
{ username: user.name, presenter: presenter, you: you, })
|
||||
: intl.formatMessage(messages.userItemStatusAriaLabel,
|
||||
{ username: user.name,
|
||||
presenter: presenter,
|
||||
you: you,
|
||||
status: user.emoji.status, });
|
||||
|
||||
let actions = this.getAvailableActions();
|
||||
let contents = (
|
||||
<div
|
||||
className={cx(styles.userListItem, userItemContentsStyle)}
|
||||
aria-label={userAriaLabel}>
|
||||
<div className={styles.userItemContents} aria-hidden="true">
|
||||
<UserAvatar user={user} />
|
||||
{this.renderUserName()}
|
||||
{this.renderUserIcons()}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
if (!actions.length) {
|
||||
return contents;
|
||||
}
|
||||
|
||||
const { dropdownOffset, dropdownDirection, dropdownVisible, } = this.state;
|
||||
|
||||
return (
|
||||
<li
|
||||
role="button"
|
||||
aria-haspopup="true"
|
||||
aria-live="assertive"
|
||||
aria-relevant="additions"
|
||||
className={cx(styles.userListItem, userItemContentsStyle)}>
|
||||
{this.renderUserContents()}
|
||||
</li>
|
||||
<Dropdown
|
||||
ref="dropdown"
|
||||
isOpen={this.state.isActionsOpen}
|
||||
onShow={this.onActionsShow}
|
||||
onHide={this.onActionsHide}
|
||||
className={styles.dropdown}
|
||||
autoFocus={false}
|
||||
hasPopup="true"
|
||||
ariaLive="assertive"
|
||||
ariaRelevant="additions">
|
||||
<DropdownTrigger>
|
||||
{contents}
|
||||
</DropdownTrigger>
|
||||
<DropdownContent
|
||||
style={{
|
||||
visibility: dropdownVisible ? 'visible' : 'hidden',
|
||||
[dropdownDirection]: `${dropdownOffset}px`,
|
||||
}}
|
||||
className={styles.dropdownContent}
|
||||
placement={`right ${dropdownDirection}`}>
|
||||
|
||||
<DropdownList>
|
||||
{
|
||||
[
|
||||
(<DropdownListTitle
|
||||
description={intl.formatMessage(messages.menuTitleContext)}
|
||||
key={_.uniqueId('dropdown-list-title')}>
|
||||
{user.name}
|
||||
</DropdownListTitle>),
|
||||
(<DropdownListSeparator key={_.uniqueId('action-separator')} />),
|
||||
].concat(actions)
|
||||
}
|
||||
</DropdownList>
|
||||
</DropdownContent>
|
||||
</Dropdown>
|
||||
);
|
||||
}
|
||||
|
||||
@ -254,7 +328,7 @@ class UserListItem extends Component {
|
||||
|
||||
let actions = this.getAvailableActions();
|
||||
let contents = (
|
||||
<div tabIndex={0} className={styles.userItemContents}>
|
||||
<div className={styles.userItemContents}>
|
||||
<UserAvatar user={user} />
|
||||
{this.renderUserName()}
|
||||
{this.renderUserIcons()}
|
||||
@ -273,7 +347,8 @@ class UserListItem extends Component {
|
||||
isOpen={this.state.isActionsOpen}
|
||||
onShow={this.onActionsShow}
|
||||
onHide={this.onActionsHide}
|
||||
className={styles.dropdown}>
|
||||
className={styles.dropdown}
|
||||
autoFocus={false}>
|
||||
<DropdownTrigger>
|
||||
{contents}
|
||||
</DropdownTrigger>
|
||||
@ -335,11 +410,15 @@ class UserListItem extends Component {
|
||||
</span>
|
||||
<span className={styles.userNameSub}>
|
||||
{userNameSub}
|
||||
{(user.isLocked && (disablePrivateChat || disableCam || disableMic || lockedLayout || disablePublicChat)) ?
|
||||
<span> {(user.isCurrent? " | " : null)}
|
||||
{(user.isLocked && (disablePrivateChat
|
||||
|| disableCam
|
||||
|| disableMic
|
||||
|| lockedLayout
|
||||
|| disablePublicChat)) ?
|
||||
<span> {(user.isCurrent ? ' | ' : null)}
|
||||
<Icon iconName='lock' />
|
||||
{intl.formatMessage(messages.locked)}
|
||||
</span>: null}
|
||||
</span> : null}
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
@ -407,6 +486,7 @@ class UserListItem extends Component {
|
||||
label={action.label}
|
||||
defaultMessage={action.label}
|
||||
onClick={action.handler.bind(this, ...parameters)}
|
||||
placeInTabOrder={true}
|
||||
/>
|
||||
);
|
||||
|
||||
|
4
bigbluebutton-html5/imports/utils/keyCodes.js
Normal file → Executable file
4
bigbluebutton-html5/imports/utils/keyCodes.js
Normal file → Executable file
@ -4,6 +4,8 @@ export const TAB = 9;
|
||||
export const ESCAPE = 27;
|
||||
export const ARROW_UP = 38;
|
||||
export const ARROW_DOWN = 40;
|
||||
export const ARROW_RIGHT = 39;
|
||||
export const ARROW_LEFT = 37;
|
||||
|
||||
export default {
|
||||
SPACE,
|
||||
@ -12,4 +14,6 @@ export default {
|
||||
ESCAPE,
|
||||
ARROW_UP,
|
||||
ARROW_DOWN,
|
||||
ARROW_RIGHT,
|
||||
ARROW_LEFT,
|
||||
};
|
||||
|
@ -25,6 +25,8 @@
|
||||
"app.userlist.menu.kickUser.label": "Kick user",
|
||||
"app.userlist.menu.muteUserAudio.label": "Mute user",
|
||||
"app.userlist.menu.unmuteUserAudio.label": "Unmute user",
|
||||
"app.userlist.useritem.nostatus.arialabel": "{username} {presenter} {you}",
|
||||
"app.userlist.useritem.status.arialabel": "{username} {presenter} {you} current status {status}",
|
||||
"app.chat.Label": "Chat",
|
||||
"app.chat.emptyLogLabel": "Chat log empty",
|
||||
"app.media.Label": "Media",
|
||||
|
Loading…
Reference in New Issue
Block a user