Merge branches 'develop' and 't3chguy/nad/rampage' of github.com:matrix-org/matrix-react-sdk into t3chguy/nad/rampage
Conflicts: src/components/views/elements/AccessibleTooltipButton.tsx
This commit is contained in:
commit
a704eefdd5
22 changed files with 259 additions and 110 deletions
|
@ -26,23 +26,3 @@ limitations under the License.
|
||||||
margin: 0 -10px 0 0;
|
margin: 0 -10px 0 0;
|
||||||
padding: 0 10px 0 0;
|
padding: 0 10px 0 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_MainSplit > .mx_ResizeHandle_horizontal:hover {
|
|
||||||
position: relative;
|
|
||||||
|
|
||||||
&::before {
|
|
||||||
position: absolute;
|
|
||||||
left: 4px;
|
|
||||||
top: 50%;
|
|
||||||
transform: translate(0, -50%);
|
|
||||||
|
|
||||||
height: 30%;
|
|
||||||
width: 4px;
|
|
||||||
border-radius: 4px;
|
|
||||||
|
|
||||||
content: ' ';
|
|
||||||
|
|
||||||
background-color: $primary-fg-color;
|
|
||||||
opacity: 0.8;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -78,23 +78,3 @@ limitations under the License.
|
||||||
*/
|
*/
|
||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_MatrixChat > .mx_ResizeHandle_horizontal:hover {
|
|
||||||
position: relative;
|
|
||||||
|
|
||||||
&::before {
|
|
||||||
position: absolute;
|
|
||||||
left: -2px;
|
|
||||||
top: 50%;
|
|
||||||
transform: translate(0, -50%);
|
|
||||||
|
|
||||||
height: 30%;
|
|
||||||
width: 4px;
|
|
||||||
border-radius: 4px;
|
|
||||||
|
|
||||||
content: ' ';
|
|
||||||
|
|
||||||
background-color: $primary-fg-color;
|
|
||||||
opacity: 0.8;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -97,7 +97,7 @@ $irc-line-height: $font-18px;
|
||||||
}
|
}
|
||||||
|
|
||||||
> .mx_EventTile_e2eIcon {
|
> .mx_EventTile_e2eIcon {
|
||||||
position: relative;
|
position: absolute;
|
||||||
right: unset;
|
right: unset;
|
||||||
left: unset;
|
left: unset;
|
||||||
top: 0;
|
top: 0;
|
||||||
|
|
|
@ -43,6 +43,7 @@ import SdkConfig from "./SdkConfig";
|
||||||
import { ensureDMExists } from "./createRoom";
|
import { ensureDMExists } from "./createRoom";
|
||||||
import { ViewUserPayload } from "./dispatcher/payloads/ViewUserPayload";
|
import { ViewUserPayload } from "./dispatcher/payloads/ViewUserPayload";
|
||||||
import { Action } from "./dispatcher/actions";
|
import { Action } from "./dispatcher/actions";
|
||||||
|
import { EffectiveMembership, getEffectiveMembership } from "./utils/membership";
|
||||||
|
|
||||||
// XXX: workaround for https://github.com/microsoft/TypeScript/issues/31816
|
// XXX: workaround for https://github.com/microsoft/TypeScript/issues/31816
|
||||||
interface HTMLInputEvent extends Event {
|
interface HTMLInputEvent extends Event {
|
||||||
|
@ -730,9 +731,11 @@ export const Commands = [
|
||||||
const cli = MatrixClientPeg.get();
|
const cli = MatrixClientPeg.get();
|
||||||
const room = cli.getRoom(roomId);
|
const room = cli.getRoom(roomId);
|
||||||
if (!room) return reject(_t("Command failed"));
|
if (!room) return reject(_t("Command failed"));
|
||||||
|
const member = room.getMember(args);
|
||||||
|
if (!member || getEffectiveMembership(member.membership) === EffectiveMembership.Leave) {
|
||||||
|
return reject(_t("Could not find user in room"));
|
||||||
|
}
|
||||||
const powerLevelEvent = room.currentState.getStateEvents('m.room.power_levels', '');
|
const powerLevelEvent = room.currentState.getStateEvents('m.room.power_levels', '');
|
||||||
if (!powerLevelEvent.getContent().users[args]) return reject(_t("Could not find user in room"));
|
|
||||||
return success(cli.setPowerLevel(roomId, userId, powerLevel, powerLevelEvent));
|
return success(cli.setPowerLevel(roomId, userId, powerLevel, powerLevelEvent));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,12 +23,11 @@ import React, {
|
||||||
useRef,
|
useRef,
|
||||||
useReducer,
|
useReducer,
|
||||||
Reducer,
|
Reducer,
|
||||||
RefObject,
|
|
||||||
Dispatch,
|
Dispatch,
|
||||||
} from "react";
|
} from "react";
|
||||||
|
|
||||||
import {Key} from "../Keyboard";
|
import {Key} from "../Keyboard";
|
||||||
import AccessibleButton from "../components/views/elements/AccessibleButton";
|
import {FocusHandler, Ref} from "./roving/types";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Module to simplify implementing the Roving TabIndex accessibility technique
|
* Module to simplify implementing the Roving TabIndex accessibility technique
|
||||||
|
@ -45,9 +44,7 @@ import AccessibleButton from "../components/views/elements/AccessibleButton";
|
||||||
|
|
||||||
const DOCUMENT_POSITION_PRECEDING = 2;
|
const DOCUMENT_POSITION_PRECEDING = 2;
|
||||||
|
|
||||||
type Ref = RefObject<HTMLElement>;
|
export interface IState {
|
||||||
|
|
||||||
interface IState {
|
|
||||||
activeRef: Ref;
|
activeRef: Ref;
|
||||||
refs: Ref[];
|
refs: Ref[];
|
||||||
}
|
}
|
||||||
|
@ -156,7 +153,7 @@ interface IProps {
|
||||||
children(renderProps: {
|
children(renderProps: {
|
||||||
onKeyDownHandler(ev: React.KeyboardEvent);
|
onKeyDownHandler(ev: React.KeyboardEvent);
|
||||||
});
|
});
|
||||||
onKeyDown?(ev: React.KeyboardEvent);
|
onKeyDown?(ev: React.KeyboardEvent, state: IState);
|
||||||
}
|
}
|
||||||
|
|
||||||
export const RovingTabIndexProvider: React.FC<IProps> = ({children, handleHomeEnd, onKeyDown}) => {
|
export const RovingTabIndexProvider: React.FC<IProps> = ({children, handleHomeEnd, onKeyDown}) => {
|
||||||
|
@ -193,7 +190,7 @@ export const RovingTabIndexProvider: React.FC<IProps> = ({children, handleHomeEn
|
||||||
ev.preventDefault();
|
ev.preventDefault();
|
||||||
ev.stopPropagation();
|
ev.stopPropagation();
|
||||||
} else if (onKeyDown) {
|
} else if (onKeyDown) {
|
||||||
return onKeyDown(ev);
|
return onKeyDown(ev, state);
|
||||||
}
|
}
|
||||||
}, [context.state, onKeyDown, handleHomeEnd]);
|
}, [context.state, onKeyDown, handleHomeEnd]);
|
||||||
|
|
||||||
|
@ -202,8 +199,6 @@ export const RovingTabIndexProvider: React.FC<IProps> = ({children, handleHomeEn
|
||||||
</RovingTabIndexContext.Provider>;
|
</RovingTabIndexContext.Provider>;
|
||||||
};
|
};
|
||||||
|
|
||||||
type FocusHandler = () => void;
|
|
||||||
|
|
||||||
// Hook to register a roving tab index
|
// Hook to register a roving tab index
|
||||||
// inputRef parameter specifies the ref to use
|
// inputRef parameter specifies the ref to use
|
||||||
// onFocus should be called when the index gained focus in any manner
|
// onFocus should be called when the index gained focus in any manner
|
||||||
|
@ -244,28 +239,7 @@ export const useRovingTabIndex = (inputRef: Ref): [FocusHandler, boolean, Ref] =
|
||||||
return [onFocus, isActive, ref];
|
return [onFocus, isActive, ref];
|
||||||
};
|
};
|
||||||
|
|
||||||
interface IRovingTabIndexWrapperProps {
|
// re-export the semantic helper components for simplicity
|
||||||
inputRef?: Ref;
|
export {RovingTabIndexWrapper} from "./roving/RovingTabIndexWrapper";
|
||||||
children(renderProps: {
|
export {RovingAccessibleButton} from "./roving/RovingAccessibleButton";
|
||||||
onFocus: FocusHandler;
|
export {RovingAccessibleTooltipButton} from "./roving/RovingAccessibleTooltipButton";
|
||||||
isActive: boolean;
|
|
||||||
ref: Ref;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Wrapper to allow use of useRovingTabIndex outside of React Functional Components.
|
|
||||||
export const RovingTabIndexWrapper: React.FC<IRovingTabIndexWrapperProps> = ({children, inputRef}) => {
|
|
||||||
const [onFocus, isActive, ref] = useRovingTabIndex(inputRef);
|
|
||||||
return children({onFocus, isActive, ref});
|
|
||||||
};
|
|
||||||
|
|
||||||
interface IRovingAccessibleButtonProps extends React.ComponentProps<typeof AccessibleButton> {
|
|
||||||
inputRef?: Ref;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Wrapper to allow use of useRovingTabIndex for simple AccessibleButtons outside of React Functional Components.
|
|
||||||
export const RovingAccessibleButton: React.FC<IRovingAccessibleButtonProps> = ({inputRef, ...props}) => {
|
|
||||||
const [onFocus, isActive, ref] = useRovingTabIndex(inputRef);
|
|
||||||
return <AccessibleButton {...props} onFocus={onFocus} inputRef={ref} tabIndex={isActive ? 0 : -1} />;
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
69
src/accessibility/Toolbar.tsx
Normal file
69
src/accessibility/Toolbar.tsx
Normal file
|
@ -0,0 +1,69 @@
|
||||||
|
/*
|
||||||
|
Copyright 2020 The Matrix.org Foundation C.I.C.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import React from "react";
|
||||||
|
|
||||||
|
import {IState, RovingTabIndexProvider} from "./RovingTabIndex";
|
||||||
|
import {Key} from "../Keyboard";
|
||||||
|
|
||||||
|
interface IProps extends Omit<React.HTMLProps<HTMLDivElement>, "onKeyDown"> {
|
||||||
|
}
|
||||||
|
|
||||||
|
// This component implements the Toolbar design pattern from the WAI-ARIA Authoring Practices guidelines.
|
||||||
|
// https://www.w3.org/TR/wai-aria-practices-1.1/#toolbar
|
||||||
|
// All buttons passed in children must use RovingTabIndex to set `onFocus`, `isActive`, `ref`
|
||||||
|
const Toolbar: React.FC<IProps> = ({children, ...props}) => {
|
||||||
|
const onKeyDown = (ev: React.KeyboardEvent, state: IState) => {
|
||||||
|
const target = ev.target as HTMLElement;
|
||||||
|
let handled = true;
|
||||||
|
|
||||||
|
switch (ev.key) {
|
||||||
|
case Key.ARROW_UP:
|
||||||
|
case Key.ARROW_DOWN:
|
||||||
|
if (target.hasAttribute('aria-haspopup')) {
|
||||||
|
target.click();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case Key.ARROW_LEFT:
|
||||||
|
case Key.ARROW_RIGHT:
|
||||||
|
if (state.refs.length > 0) {
|
||||||
|
const i = state.refs.findIndex(r => r === state.activeRef);
|
||||||
|
const delta = ev.key === Key.ARROW_RIGHT ? 1 : -1;
|
||||||
|
state.refs.slice((i + delta) % state.refs.length)[0].current.focus();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
// HOME and END are handled by RovingTabIndexProvider
|
||||||
|
|
||||||
|
default:
|
||||||
|
handled = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (handled) {
|
||||||
|
ev.preventDefault();
|
||||||
|
ev.stopPropagation();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return <RovingTabIndexProvider handleHomeEnd={true} onKeyDown={onKeyDown}>
|
||||||
|
{({onKeyDownHandler}) => <div {...props} onKeyDown={onKeyDownHandler} role="toolbar">
|
||||||
|
{ children }
|
||||||
|
</div>}
|
||||||
|
</RovingTabIndexProvider>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Toolbar;
|
32
src/accessibility/roving/RovingAccessibleButton.tsx
Normal file
32
src/accessibility/roving/RovingAccessibleButton.tsx
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
/*
|
||||||
|
Copyright 2020 The Matrix.org Foundation C.I.C.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import React from "react";
|
||||||
|
|
||||||
|
import AccessibleButton from "../../components/views/elements/AccessibleButton";
|
||||||
|
import {useRovingTabIndex} from "../RovingTabIndex";
|
||||||
|
import {Ref} from "./types";
|
||||||
|
|
||||||
|
interface IProps extends Omit<React.ComponentProps<typeof AccessibleButton>, "onFocus" | "inputRef" | "tabIndex"> {
|
||||||
|
inputRef?: Ref;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wrapper to allow use of useRovingTabIndex for simple AccessibleButtons outside of React Functional Components.
|
||||||
|
export const RovingAccessibleButton: React.FC<IProps> = ({inputRef, ...props}) => {
|
||||||
|
const [onFocus, isActive, ref] = useRovingTabIndex(inputRef);
|
||||||
|
return <AccessibleButton {...props} onFocus={onFocus} inputRef={ref} tabIndex={isActive ? 0 : -1} />;
|
||||||
|
};
|
||||||
|
|
32
src/accessibility/roving/RovingAccessibleTooltipButton.tsx
Normal file
32
src/accessibility/roving/RovingAccessibleTooltipButton.tsx
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
/*
|
||||||
|
Copyright 2020 The Matrix.org Foundation C.I.C.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import React from "react";
|
||||||
|
|
||||||
|
import AccessibleTooltipButton from "../../components/views/elements/AccessibleTooltipButton";
|
||||||
|
import {useRovingTabIndex} from "../RovingTabIndex";
|
||||||
|
import {Ref} from "./types";
|
||||||
|
|
||||||
|
interface IProps extends Omit<React.ComponentProps<typeof AccessibleTooltipButton>, "onFocus" | "inputRef" | "tabIndex"> {
|
||||||
|
inputRef?: Ref;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wrapper to allow use of useRovingTabIndex for simple AccessibleTooltipButtons outside of React Functional Components.
|
||||||
|
export const RovingAccessibleTooltipButton: React.FC<IProps> = ({inputRef, ...props}) => {
|
||||||
|
const [onFocus, isActive, ref] = useRovingTabIndex(inputRef);
|
||||||
|
return <AccessibleTooltipButton {...props} onFocus={onFocus} inputRef={ref} tabIndex={isActive ? 0 : -1} />;
|
||||||
|
};
|
||||||
|
|
36
src/accessibility/roving/RovingTabIndexWrapper.tsx
Normal file
36
src/accessibility/roving/RovingTabIndexWrapper.tsx
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
/*
|
||||||
|
Copyright 2020 The Matrix.org Foundation C.I.C.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import React from "react";
|
||||||
|
|
||||||
|
import AccessibleButton from "../../components/views/elements/AccessibleButton";
|
||||||
|
import {useRovingTabIndex} from "../RovingTabIndex";
|
||||||
|
import {FocusHandler, Ref} from "./types";
|
||||||
|
|
||||||
|
interface IProps {
|
||||||
|
inputRef?: Ref;
|
||||||
|
children(renderProps: {
|
||||||
|
onFocus: FocusHandler;
|
||||||
|
isActive: boolean;
|
||||||
|
ref: Ref;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wrapper to allow use of useRovingTabIndex outside of React Functional Components.
|
||||||
|
export const RovingTabIndexWrapper: React.FC<IProps> = ({children, inputRef}) => {
|
||||||
|
const [onFocus, isActive, ref] = useRovingTabIndex(inputRef);
|
||||||
|
return children({onFocus, isActive, ref});
|
||||||
|
};
|
21
src/accessibility/roving/types.ts
Normal file
21
src/accessibility/roving/types.ts
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
/*
|
||||||
|
Copyright 2020 The Matrix.org Foundation C.I.C.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import {RefObject} from "react";
|
||||||
|
|
||||||
|
export type Ref = RefObject<HTMLElement>;
|
||||||
|
|
||||||
|
export type FocusHandler = () => void;
|
|
@ -233,6 +233,9 @@ export class ContextMenu extends React.PureComponent<IProps, IState> {
|
||||||
switch (ev.key) {
|
switch (ev.key) {
|
||||||
case Key.TAB:
|
case Key.TAB:
|
||||||
case Key.ESCAPE:
|
case Key.ESCAPE:
|
||||||
|
// close on left and right arrows too for when it is a context menu on a <Toolbar />
|
||||||
|
case Key.ARROW_LEFT:
|
||||||
|
case Key.ARROW_RIGHT:
|
||||||
this.props.onFinished();
|
this.props.onFinished();
|
||||||
break;
|
break;
|
||||||
case Key.ARROW_UP:
|
case Key.ARROW_UP:
|
||||||
|
|
|
@ -269,7 +269,7 @@ export default class LeftPanel2 extends React.Component<IProps, IState> {
|
||||||
const firstRoom = this.listContainerRef.current.querySelector<HTMLDivElement>(".mx_RoomTile2");
|
const firstRoom = this.listContainerRef.current.querySelector<HTMLDivElement>(".mx_RoomTile2");
|
||||||
if (firstRoom) {
|
if (firstRoom) {
|
||||||
firstRoom.click();
|
firstRoom.click();
|
||||||
this.onSearch(""); // clear the search field
|
return true; // to get the field to clear
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -675,12 +675,16 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
|
||||||
case 'hide_left_panel':
|
case 'hide_left_panel':
|
||||||
this.setState({
|
this.setState({
|
||||||
collapseLhs: true,
|
collapseLhs: true,
|
||||||
|
}, () => {
|
||||||
|
this.state.resizeNotifier.notifyLeftHandleResized();
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
case 'focus_room_filter': // for CtrlOrCmd+K to work by expanding the left panel first
|
case 'focus_room_filter': // for CtrlOrCmd+K to work by expanding the left panel first
|
||||||
case 'show_left_panel':
|
case 'show_left_panel':
|
||||||
this.setState({
|
this.setState({
|
||||||
collapseLhs: false,
|
collapseLhs: false,
|
||||||
|
}, () => {
|
||||||
|
this.state.resizeNotifier.notifyLeftHandleResized();
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
case 'panel_disable': {
|
case 'panel_disable': {
|
||||||
|
|
|
@ -28,8 +28,8 @@ import { Action } from "../../dispatcher/actions";
|
||||||
interface IProps {
|
interface IProps {
|
||||||
onQueryUpdate: (newQuery: string) => void;
|
onQueryUpdate: (newQuery: string) => void;
|
||||||
isMinimized: boolean;
|
isMinimized: boolean;
|
||||||
onVerticalArrow(ev: React.KeyboardEvent);
|
onVerticalArrow(ev: React.KeyboardEvent): void;
|
||||||
onEnter(ev: React.KeyboardEvent);
|
onEnter(ev: React.KeyboardEvent): boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface IState {
|
interface IState {
|
||||||
|
@ -107,7 +107,13 @@ export default class RoomSearch extends React.PureComponent<IProps, IState> {
|
||||||
} else if (ev.key === Key.ARROW_UP || ev.key === Key.ARROW_DOWN) {
|
} else if (ev.key === Key.ARROW_UP || ev.key === Key.ARROW_DOWN) {
|
||||||
this.props.onVerticalArrow(ev);
|
this.props.onVerticalArrow(ev);
|
||||||
} else if (ev.key === Key.ENTER) {
|
} else if (ev.key === Key.ENTER) {
|
||||||
this.props.onEnter(ev);
|
const shouldClear = this.props.onEnter(ev);
|
||||||
|
if (shouldClear) {
|
||||||
|
// wrap in set immediate to delay it so that we don't clear the filter & then change room
|
||||||
|
setImmediate(() => {
|
||||||
|
this.clearInput();
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -66,7 +66,7 @@ export default class DecoratedRoomAvatar extends React.PureComponent<IProps, ISt
|
||||||
oobData={this.props.oobData}
|
oobData={this.props.oobData}
|
||||||
viewAvatarOnClick={this.props.viewAvatarOnClick}
|
viewAvatarOnClick={this.props.viewAvatarOnClick}
|
||||||
/>
|
/>
|
||||||
<RoomTileIcon room={this.props.room} tag={this.props.tag} />
|
<RoomTileIcon room={this.props.room} />
|
||||||
{badge}
|
{badge}
|
||||||
</div>;
|
</div>;
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,7 +16,7 @@ limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import classnames from 'classnames';
|
import classNames from 'classnames';
|
||||||
|
|
||||||
import AccessibleButton from "./AccessibleButton";
|
import AccessibleButton from "./AccessibleButton";
|
||||||
import {IProps} from "./AccessibleButton";
|
import {IProps} from "./AccessibleButton";
|
||||||
|
@ -53,15 +53,11 @@ export default class AccessibleTooltipButton extends React.PureComponent<IToolti
|
||||||
};
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const {title, tooltip, children, ...props} = this.props;
|
const {title, tooltip, children, tooltipClassName, ...props} = this.props;
|
||||||
const tooltipClassName = classnames(
|
|
||||||
"mx_AccessibleTooltipButton_tooltip",
|
|
||||||
this.props.tooltipClassName,
|
|
||||||
);
|
|
||||||
|
|
||||||
const tip = this.state.hover ? <Tooltip
|
const tip = this.state.hover ? <Tooltip
|
||||||
className="mx_AccessibleTooltipButton_container"
|
className="mx_AccessibleTooltipButton_container"
|
||||||
tooltipClassName={tooltipClassName}
|
tooltipClassName={classNames("mx_AccessibleTooltipButton_tooltip", tooltipClassName)}
|
||||||
label={tooltip || title}
|
label={tooltip || title}
|
||||||
/> : <div />;
|
/> : <div />;
|
||||||
return (
|
return (
|
||||||
|
|
|
@ -25,9 +25,12 @@ import dis from '../../../dispatcher/dispatcher';
|
||||||
import {aboveLeftOf, ContextMenu, ContextMenuButton, useContextMenu} from '../../structures/ContextMenu';
|
import {aboveLeftOf, ContextMenu, ContextMenuButton, useContextMenu} from '../../structures/ContextMenu';
|
||||||
import { isContentActionable, canEditContent } from '../../../utils/EventUtils';
|
import { isContentActionable, canEditContent } from '../../../utils/EventUtils';
|
||||||
import RoomContext from "../../../contexts/RoomContext";
|
import RoomContext from "../../../contexts/RoomContext";
|
||||||
|
import Toolbar from "../../../accessibility/Toolbar";
|
||||||
|
import {RovingAccessibleButton, useRovingTabIndex} from "../../../accessibility/RovingTabIndex";
|
||||||
|
|
||||||
const OptionsButton = ({mxEvent, getTile, getReplyThread, permalinkCreator, onFocusChange}) => {
|
const OptionsButton = ({mxEvent, getTile, getReplyThread, permalinkCreator, onFocusChange}) => {
|
||||||
const [menuDisplayed, button, openMenu, closeMenu] = useContextMenu();
|
const [menuDisplayed, button, openMenu, closeMenu] = useContextMenu();
|
||||||
|
const [onFocus, isActive, ref] = useRovingTabIndex(button);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
onFocusChange(menuDisplayed);
|
onFocusChange(menuDisplayed);
|
||||||
}, [onFocusChange, menuDisplayed]);
|
}, [onFocusChange, menuDisplayed]);
|
||||||
|
@ -57,7 +60,9 @@ const OptionsButton = ({mxEvent, getTile, getReplyThread, permalinkCreator, onFo
|
||||||
label={_t("Options")}
|
label={_t("Options")}
|
||||||
onClick={openMenu}
|
onClick={openMenu}
|
||||||
isExpanded={menuDisplayed}
|
isExpanded={menuDisplayed}
|
||||||
inputRef={button}
|
inputRef={ref}
|
||||||
|
onFocus={onFocus}
|
||||||
|
tabIndex={isActive ? 0 : -1}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{ contextMenu }
|
{ contextMenu }
|
||||||
|
@ -66,6 +71,7 @@ const OptionsButton = ({mxEvent, getTile, getReplyThread, permalinkCreator, onFo
|
||||||
|
|
||||||
const ReactButton = ({mxEvent, reactions, onFocusChange}) => {
|
const ReactButton = ({mxEvent, reactions, onFocusChange}) => {
|
||||||
const [menuDisplayed, button, openMenu, closeMenu] = useContextMenu();
|
const [menuDisplayed, button, openMenu, closeMenu] = useContextMenu();
|
||||||
|
const [onFocus, isActive, ref] = useRovingTabIndex(button);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
onFocusChange(menuDisplayed);
|
onFocusChange(menuDisplayed);
|
||||||
}, [onFocusChange, menuDisplayed]);
|
}, [onFocusChange, menuDisplayed]);
|
||||||
|
@ -85,7 +91,9 @@ const ReactButton = ({mxEvent, reactions, onFocusChange}) => {
|
||||||
label={_t("React")}
|
label={_t("React")}
|
||||||
onClick={openMenu}
|
onClick={openMenu}
|
||||||
isExpanded={menuDisplayed}
|
isExpanded={menuDisplayed}
|
||||||
inputRef={button}
|
inputRef={ref}
|
||||||
|
onFocus={onFocus}
|
||||||
|
tabIndex={isActive ? 0 : -1}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{ contextMenu }
|
{ contextMenu }
|
||||||
|
@ -148,8 +156,6 @@ export default class MessageActionBar extends React.PureComponent {
|
||||||
};
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const AccessibleButton = sdk.getComponent('elements.AccessibleButton');
|
|
||||||
|
|
||||||
let reactButton;
|
let reactButton;
|
||||||
let replyButton;
|
let replyButton;
|
||||||
let editButton;
|
let editButton;
|
||||||
|
@ -161,7 +167,7 @@ export default class MessageActionBar extends React.PureComponent {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if (this.context.canReply) {
|
if (this.context.canReply) {
|
||||||
replyButton = <AccessibleButton
|
replyButton = <RovingAccessibleButton
|
||||||
className="mx_MessageActionBar_maskButton mx_MessageActionBar_replyButton"
|
className="mx_MessageActionBar_maskButton mx_MessageActionBar_replyButton"
|
||||||
title={_t("Reply")}
|
title={_t("Reply")}
|
||||||
onClick={this.onReplyClick}
|
onClick={this.onReplyClick}
|
||||||
|
@ -169,7 +175,7 @@ export default class MessageActionBar extends React.PureComponent {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (canEditContent(this.props.mxEvent)) {
|
if (canEditContent(this.props.mxEvent)) {
|
||||||
editButton = <AccessibleButton
|
editButton = <RovingAccessibleButton
|
||||||
className="mx_MessageActionBar_maskButton mx_MessageActionBar_editButton"
|
className="mx_MessageActionBar_maskButton mx_MessageActionBar_editButton"
|
||||||
title={_t("Edit")}
|
title={_t("Edit")}
|
||||||
onClick={this.onEditClick}
|
onClick={this.onEditClick}
|
||||||
|
@ -177,7 +183,7 @@ export default class MessageActionBar extends React.PureComponent {
|
||||||
}
|
}
|
||||||
|
|
||||||
// aria-live=off to not have this read out automatically as navigating around timeline, gets repetitive.
|
// aria-live=off to not have this read out automatically as navigating around timeline, gets repetitive.
|
||||||
return <div className="mx_MessageActionBar" role="toolbar" aria-label={_t("Message Actions")} aria-live="off">
|
return <Toolbar className="mx_MessageActionBar" aria-label={_t("Message Actions")} aria-live="off">
|
||||||
{reactButton}
|
{reactButton}
|
||||||
{replyButton}
|
{replyButton}
|
||||||
{editButton}
|
{editButton}
|
||||||
|
@ -188,6 +194,6 @@ export default class MessageActionBar extends React.PureComponent {
|
||||||
permalinkCreator={this.props.permalinkCreator}
|
permalinkCreator={this.props.permalinkCreator}
|
||||||
onFocusChange={this.onFocusChange}
|
onFocusChange={this.onFocusChange}
|
||||||
/>
|
/>
|
||||||
</div>;
|
</Toolbar>;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,7 +25,8 @@ import { UPDATE_EVENT } from "../../../stores/AsyncStore";
|
||||||
import { CSSTransition } from "react-transition-group";
|
import { CSSTransition } from "react-transition-group";
|
||||||
import RoomListStore from "../../../stores/room-list/RoomListStore2";
|
import RoomListStore from "../../../stores/room-list/RoomListStore2";
|
||||||
import { DefaultTagID } from "../../../stores/room-list/models";
|
import { DefaultTagID } from "../../../stores/room-list/models";
|
||||||
import AccessibleTooltipButton from "../elements/AccessibleTooltipButton";
|
import { RovingAccessibleTooltipButton } from "../../../accessibility/RovingTabIndex";
|
||||||
|
import Toolbar from "../../../accessibility/Toolbar";
|
||||||
|
|
||||||
// TODO: Rename on launch: https://github.com/vector-im/riot-web/issues/14367
|
// TODO: Rename on launch: https://github.com/vector-im/riot-web/issues/14367
|
||||||
|
|
||||||
|
@ -86,7 +87,7 @@ export default class RoomBreadcrumbs2 extends React.PureComponent<IProps, IState
|
||||||
const roomTags = RoomListStore.instance.getTagsForRoom(r);
|
const roomTags = RoomListStore.instance.getTagsForRoom(r);
|
||||||
const roomTag = roomTags.includes(DefaultTagID.DM) ? DefaultTagID.DM : roomTags[0];
|
const roomTag = roomTags.includes(DefaultTagID.DM) ? DefaultTagID.DM : roomTags[0];
|
||||||
return (
|
return (
|
||||||
<AccessibleTooltipButton
|
<RovingAccessibleTooltipButton
|
||||||
className="mx_RoomBreadcrumbs2_crumb"
|
className="mx_RoomBreadcrumbs2_crumb"
|
||||||
key={r.roomId}
|
key={r.roomId}
|
||||||
onClick={() => this.viewRoom(r, i)}
|
onClick={() => this.viewRoom(r, i)}
|
||||||
|
@ -101,7 +102,7 @@ export default class RoomBreadcrumbs2 extends React.PureComponent<IProps, IState
|
||||||
displayBadge={true}
|
displayBadge={true}
|
||||||
forceCount={true}
|
forceCount={true}
|
||||||
/>
|
/>
|
||||||
</AccessibleTooltipButton>
|
</RovingAccessibleTooltipButton>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -112,9 +113,9 @@ export default class RoomBreadcrumbs2 extends React.PureComponent<IProps, IState
|
||||||
appear={true} in={this.state.doAnimation} timeout={640}
|
appear={true} in={this.state.doAnimation} timeout={640}
|
||||||
classNames='mx_RoomBreadcrumbs2'
|
classNames='mx_RoomBreadcrumbs2'
|
||||||
>
|
>
|
||||||
<div className='mx_RoomBreadcrumbs2'>
|
<Toolbar className='mx_RoomBreadcrumbs2'>
|
||||||
{tiles.slice(this.state.skipFirst ? 1 : 0)}
|
{tiles.slice(this.state.skipFirst ? 1 : 0)}
|
||||||
</div>
|
</Toolbar>
|
||||||
</CSSTransition>
|
</CSSTransition>
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -49,7 +49,6 @@ function tooltipText(variant: Icon) {
|
||||||
|
|
||||||
interface IProps {
|
interface IProps {
|
||||||
room: Room;
|
room: Room;
|
||||||
tag: TagID;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
interface IState {
|
interface IState {
|
||||||
|
@ -137,10 +136,11 @@ export default class RoomTileIcon extends React.Component<IProps, IState> {
|
||||||
private calculateIcon(): Icon {
|
private calculateIcon(): Icon {
|
||||||
let icon = Icon.None;
|
let icon = Icon.None;
|
||||||
|
|
||||||
if (this.props.tag === DefaultTagID.DM && this.props.room.getJoinedMemberCount() === 2) {
|
// We look at the DMRoomMap and not the tag here so that we don't exclude DMs in Favourites
|
||||||
|
const otherUserId = DMRoomMap.shared().getUserIdForRoomId(this.props.room.roomId);
|
||||||
|
if (otherUserId && this.props.room.getJoinedMemberCount() === 2) {
|
||||||
// Track presence, if available
|
// Track presence, if available
|
||||||
if (isPresenceEnabled()) {
|
if (isPresenceEnabled()) {
|
||||||
const otherUserId = DMRoomMap.shared().getUserIdForRoomId(this.props.room.roomId);
|
|
||||||
if (otherUserId) {
|
if (otherUserId) {
|
||||||
this.dmUser = MatrixClientPeg.get().getUser(otherUserId);
|
this.dmUser = MatrixClientPeg.get().getUser(otherUserId);
|
||||||
icon = this.getPresenceIcon();
|
icon = this.getPresenceIcon();
|
||||||
|
|
|
@ -33,7 +33,6 @@ import { isNullOrUndefined } from "matrix-js-sdk/src/utils";
|
||||||
import RoomListLayoutStore from "./RoomListLayoutStore";
|
import RoomListLayoutStore from "./RoomListLayoutStore";
|
||||||
import { MarkedExecution } from "../../utils/MarkedExecution";
|
import { MarkedExecution } from "../../utils/MarkedExecution";
|
||||||
import { AsyncStoreWithClient } from "../AsyncStoreWithClient";
|
import { AsyncStoreWithClient } from "../AsyncStoreWithClient";
|
||||||
import { MatrixClientPeg } from "../../MatrixClientPeg";
|
|
||||||
|
|
||||||
interface IState {
|
interface IState {
|
||||||
tagsEnabled?: boolean;
|
tagsEnabled?: boolean;
|
||||||
|
|
|
@ -158,6 +158,7 @@ export class Algorithm extends EventEmitter {
|
||||||
filterCondition.off(FILTER_CHANGED, this.handleFilterChange.bind(this));
|
filterCondition.off(FILTER_CHANGED, this.handleFilterChange.bind(this));
|
||||||
if (this.allowedByFilter.has(filterCondition)) {
|
if (this.allowedByFilter.has(filterCondition)) {
|
||||||
this.allowedByFilter.delete(filterCondition);
|
this.allowedByFilter.delete(filterCondition);
|
||||||
|
this.recalculateFilteredRooms();
|
||||||
|
|
||||||
// If we removed the last filter, tell consumers that we've "updated" our filtered
|
// If we removed the last filter, tell consumers that we've "updated" our filtered
|
||||||
// view. This will trick them into getting the complete room list.
|
// view. This will trick them into getting the complete room list.
|
||||||
|
|
|
@ -21,6 +21,7 @@ import { EventEmitter } from "events";
|
||||||
import GroupStore from "../../GroupStore";
|
import GroupStore from "../../GroupStore";
|
||||||
import { arrayHasDiff } from "../../../utils/arrays";
|
import { arrayHasDiff } from "../../../utils/arrays";
|
||||||
import { IDestroyable } from "../../../utils/IDestroyable";
|
import { IDestroyable } from "../../../utils/IDestroyable";
|
||||||
|
import DMRoomMap from "../../../utils/DMRoomMap";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A filter condition for the room list which reveals rooms which
|
* A filter condition for the room list which reveals rooms which
|
||||||
|
@ -28,6 +29,7 @@ import { IDestroyable } from "../../../utils/IDestroyable";
|
||||||
*/
|
*/
|
||||||
export class CommunityFilterCondition extends EventEmitter implements IFilterCondition, IDestroyable {
|
export class CommunityFilterCondition extends EventEmitter implements IFilterCondition, IDestroyable {
|
||||||
private roomIds: string[] = [];
|
private roomIds: string[] = [];
|
||||||
|
private userIds: string[] = [];
|
||||||
|
|
||||||
constructor(private community: Group) {
|
constructor(private community: Group) {
|
||||||
super();
|
super();
|
||||||
|
@ -43,15 +45,19 @@ export class CommunityFilterCondition extends EventEmitter implements IFilterCon
|
||||||
}
|
}
|
||||||
|
|
||||||
public isVisible(room: Room): boolean {
|
public isVisible(room: Room): boolean {
|
||||||
return this.roomIds.includes(room.roomId);
|
return this.roomIds.includes(room.roomId) ||
|
||||||
|
this.userIds.includes(DMRoomMap.shared().getUserIdForRoomId(room.roomId));
|
||||||
}
|
}
|
||||||
|
|
||||||
private onStoreUpdate = async (): Promise<any> => {
|
private onStoreUpdate = async (): Promise<any> => {
|
||||||
// We don't actually know if the room list changed for the community, so just
|
// We don't actually know if the room list changed for the community, so just check it again.
|
||||||
// check it again.
|
|
||||||
const beforeRoomIds = this.roomIds;
|
const beforeRoomIds = this.roomIds;
|
||||||
this.roomIds = (await GroupStore.getGroupRooms(this.community.groupId)).map(r => r.roomId);
|
this.roomIds = (await GroupStore.getGroupRooms(this.community.groupId)).map(r => r.roomId);
|
||||||
if (arrayHasDiff(beforeRoomIds, this.roomIds)) {
|
|
||||||
|
const beforeUserIds = this.userIds;
|
||||||
|
this.userIds = (await GroupStore.getGroupMembers(this.community.groupId)).map(u => u.userId);
|
||||||
|
|
||||||
|
if (arrayHasDiff(beforeRoomIds, this.roomIds) || arrayHasDiff(beforeUserIds, this.userIds)) {
|
||||||
this.emit(FILTER_CHANGED);
|
this.emit(FILTER_CHANGED);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
Loading…
Reference in a new issue