mirror of
https://github.com/element-hq/element-web
synced 2024-11-22 09:15:41 +03:00
Improve spotlight accessibility by adding context menus (#8907)
* Extract room general context menu from roomtile * Create hook to access and change a room’s notification state * Extract room notification context menu from roomtile * Add room context menus to rooms in spotlight * Make arrow movement apply to the whole dialog, not just the input box
This commit is contained in:
parent
6a125d5a1d
commit
780a903e2f
12 changed files with 632 additions and 283 deletions
|
@ -89,6 +89,8 @@
|
|||
@import "./views/context_menus/_DeviceContextMenu.scss";
|
||||
@import "./views/context_menus/_IconizedContextMenu.scss";
|
||||
@import "./views/context_menus/_MessageContextMenu.scss";
|
||||
@import "./views/context_menus/_RoomGeneralContextMenu.scss";
|
||||
@import "./views/context_menus/_RoomNotificationContextMenu.scss";
|
||||
@import "./views/dialogs/_AddExistingToSpaceDialog.scss";
|
||||
@import "./views/dialogs/_AnalyticsLearnMoreDialog.scss";
|
||||
@import "./views/dialogs/_BugReportDialog.scss";
|
||||
|
|
63
res/css/views/context_menus/_RoomGeneralContextMenu.scss
Normal file
63
res/css/views/context_menus/_RoomGeneralContextMenu.scss
Normal file
|
@ -0,0 +1,63 @@
|
|||
.mx_RoomGeneralContextMenu_iconStar::before {
|
||||
mask-image: url('$(res)/img/element-icons/roomlist/favorite.svg');
|
||||
}
|
||||
|
||||
.mx_RoomGeneralContextMenu_iconArrowDown::before {
|
||||
mask-image: url('$(res)/img/element-icons/roomlist/low-priority.svg');
|
||||
}
|
||||
|
||||
.mx_RoomGeneralContextMenu_iconNotificationsDefault::before {
|
||||
mask-image: url('$(res)/img/element-icons/notifications.svg');
|
||||
}
|
||||
|
||||
.mx_RoomGeneralContextMenu_iconNotificationsAllMessages::before {
|
||||
mask-image: url('$(res)/img/element-icons/roomlist/notifications-default.svg');
|
||||
}
|
||||
|
||||
.mx_RoomGeneralContextMenu_iconNotificationsMentionsKeywords::before {
|
||||
mask-image: url('$(res)/img/element-icons/roomlist/notifications-dm.svg');
|
||||
}
|
||||
|
||||
.mx_RoomGeneralContextMenu_iconNotificationsNone::before {
|
||||
mask-image: url('$(res)/img/element-icons/roomlist/notifications-off.svg');
|
||||
}
|
||||
|
||||
.mx_RoomGeneralContextMenu_iconPeople::before {
|
||||
mask-image: url('$(res)/img/element-icons/room/members.svg');
|
||||
}
|
||||
|
||||
.mx_RoomGeneralContextMenu_iconFiles::before {
|
||||
mask-image: url('$(res)/img/element-icons/room/files.svg');
|
||||
}
|
||||
|
||||
.mx_RoomGeneralContextMenu_iconPins::before {
|
||||
mask-image: url('$(res)/img/element-icons/room/pin-upright.svg');
|
||||
}
|
||||
|
||||
.mx_RoomGeneralContextMenu_iconWidgets::before {
|
||||
mask-image: url('$(res)/img/element-icons/room/apps.svg');
|
||||
}
|
||||
|
||||
.mx_RoomGeneralContextMenu_iconSettings::before {
|
||||
mask-image: url('$(res)/img/element-icons/settings.svg');
|
||||
}
|
||||
|
||||
.mx_RoomGeneralContextMenu_iconExport::before {
|
||||
mask-image: url('$(res)/img/element-icons/export.svg');
|
||||
}
|
||||
|
||||
.mx_RoomGeneralContextMenu_iconDeveloperTools::before {
|
||||
mask-image: url('$(res)/img/element-icons/settings/flask.svg');
|
||||
}
|
||||
|
||||
.mx_RoomGeneralContextMenu_iconCopyLink::before {
|
||||
mask-image: url('$(res)/img/element-icons/link.svg');
|
||||
}
|
||||
|
||||
.mx_RoomGeneralContextMenu_iconInvite::before {
|
||||
mask-image: url('$(res)/img/element-icons/room/invite.svg');
|
||||
}
|
||||
|
||||
.mx_RoomGeneralContextMenu_iconSignOut::before {
|
||||
mask-image: url('$(res)/img/element-icons/leave.svg');
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
.mx_RoomNotificationContextMenu_iconBell::before {
|
||||
mask-image: url('$(res)/img/element-icons/notifications.svg');
|
||||
}
|
||||
.mx_RoomNotificationContextMenu_iconBellDot::before {
|
||||
mask-image: url('$(res)/img/element-icons/roomlist/notifications-default.svg');
|
||||
}
|
||||
.mx_RoomNotificationContextMenu_iconBellMentions::before {
|
||||
mask-image: url('$(res)/img/element-icons/roomlist/notifications-dm.svg');
|
||||
}
|
||||
.mx_RoomNotificationContextMenu_iconBellCrossed::before {
|
||||
mask-image: url('$(res)/img/element-icons/roomlist/notifications-off.svg');
|
||||
}
|
|
@ -239,6 +239,13 @@ limitations under the License.
|
|||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
|
||||
.mx_SpotlightDialog_option--endAdornment {
|
||||
display: inline-flex;
|
||||
flex-direction: row;
|
||||
margin-left: auto;
|
||||
align-items: start;
|
||||
}
|
||||
|
||||
&.mx_SpotlightDialog_result_multiline {
|
||||
align-items: start;
|
||||
|
||||
|
@ -309,8 +316,45 @@ limitations under the License.
|
|||
margin-left: $spacing-8;
|
||||
}
|
||||
|
||||
.mx_SpotlightDialog_option--menu,
|
||||
.mx_SpotlightDialog_option--notifications {
|
||||
width: 20px;
|
||||
min-width: 20px;
|
||||
height: 20px;
|
||||
margin-top: auto;
|
||||
margin-bottom: auto;
|
||||
position: relative;
|
||||
display: none;
|
||||
|
||||
&::before {
|
||||
top: 2px;
|
||||
left: 2px;
|
||||
content: '';
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
position: absolute;
|
||||
mask-position: center;
|
||||
mask-size: contain;
|
||||
mask-repeat: no-repeat;
|
||||
background: $tertiary-content;
|
||||
}
|
||||
|
||||
&:hover::before, &[aria-selected=true]::before {
|
||||
background-color: $secondary-content;
|
||||
}
|
||||
}
|
||||
|
||||
.mx_SpotlightDialog_option--menu::before {
|
||||
mask-image: url('$(res)/img/element-icons/context-menu.svg');
|
||||
}
|
||||
|
||||
&:hover, &[aria-selected=true] {
|
||||
background-color: $system;
|
||||
|
||||
.mx_SpotlightDialog_option--menu,
|
||||
.mx_SpotlightDialog_option--notifications {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
&[aria-selected=true] .mx_SpotlightDialog_enterPrompt {
|
||||
|
@ -436,7 +480,7 @@ limitations under the License.
|
|||
color: $tertiary-content;
|
||||
border-radius: 6px;
|
||||
background-color: $quinary-content;
|
||||
margin: 0 $spacing-4 0 auto;
|
||||
margin-right: $spacing-4;
|
||||
display: none;
|
||||
}
|
||||
|
||||
|
|
186
src/components/views/context_menus/RoomGeneralContextMenu.tsx
Normal file
186
src/components/views/context_menus/RoomGeneralContextMenu.tsx
Normal file
|
@ -0,0 +1,186 @@
|
|||
/*
|
||||
Copyright 2021 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 { logger } from "matrix-js-sdk/src/logger";
|
||||
import { Room } from "matrix-js-sdk/src/models/room";
|
||||
import React, { useContext } from "react";
|
||||
|
||||
import { KeyBindingAction } from "../../../accessibility/KeyboardShortcuts";
|
||||
import RoomListActions from "../../../actions/RoomListActions";
|
||||
import MatrixClientContext from "../../../contexts/MatrixClientContext";
|
||||
import dis from "../../../dispatcher/dispatcher";
|
||||
import { useEventEmitterState } from "../../../hooks/useEventEmitter";
|
||||
import { getKeyBindingsManager } from "../../../KeyBindingsManager";
|
||||
import { _t } from "../../../languageHandler";
|
||||
import { DefaultTagID, TagID } from "../../../stores/room-list/models";
|
||||
import RoomListStore, { LISTS_UPDATE_EVENT } from "../../../stores/room-list/RoomListStore";
|
||||
import DMRoomMap from "../../../utils/DMRoomMap";
|
||||
import { IProps as IContextMenuProps } from "../../structures/ContextMenu";
|
||||
import IconizedContextMenu, {
|
||||
IconizedContextMenuCheckbox,
|
||||
IconizedContextMenuOption,
|
||||
IconizedContextMenuOptionList,
|
||||
} from "../context_menus/IconizedContextMenu";
|
||||
import { ButtonEvent } from "../elements/AccessibleButton";
|
||||
|
||||
interface IProps extends IContextMenuProps {
|
||||
room: Room;
|
||||
onPostFavoriteClick?: (event: ButtonEvent) => void;
|
||||
onPostLowPriorityClick?: (event: ButtonEvent) => void;
|
||||
onPostInviteClick?: (event: ButtonEvent) => void;
|
||||
onPostCopyLinkClick?: (event: ButtonEvent) => void;
|
||||
onPostSettingsClick?: (event: ButtonEvent) => void;
|
||||
onPostForgetClick?: (event: ButtonEvent) => void;
|
||||
onPostLeaveClick?: (event: ButtonEvent) => void;
|
||||
}
|
||||
|
||||
export const RoomGeneralContextMenu = ({
|
||||
room, onFinished,
|
||||
onPostFavoriteClick, onPostLowPriorityClick, onPostInviteClick, onPostCopyLinkClick, onPostSettingsClick,
|
||||
onPostLeaveClick, onPostForgetClick, ...props
|
||||
}: IProps) => {
|
||||
const cli = useContext(MatrixClientContext);
|
||||
const roomTags = useEventEmitterState(
|
||||
RoomListStore.instance,
|
||||
LISTS_UPDATE_EVENT,
|
||||
() => RoomListStore.instance.getTagsForRoom(room),
|
||||
);
|
||||
const isDm = DMRoomMap.shared().getUserIdForRoomId(room.roomId);
|
||||
const wrapHandler = (
|
||||
handler: (ev: ButtonEvent) => void,
|
||||
postHandler?: (ev: ButtonEvent) => void,
|
||||
persistent = false,
|
||||
): (ev: ButtonEvent) => void => {
|
||||
return (ev: ButtonEvent) => {
|
||||
ev.preventDefault();
|
||||
ev.stopPropagation();
|
||||
|
||||
handler(ev);
|
||||
|
||||
const action = getKeyBindingsManager().getAccessibilityAction(ev as React.KeyboardEvent);
|
||||
if (!persistent || action === KeyBindingAction.Enter) {
|
||||
onFinished();
|
||||
}
|
||||
postHandler?.(ev);
|
||||
};
|
||||
};
|
||||
|
||||
const onTagRoom = (ev: ButtonEvent, tagId: TagID) => {
|
||||
if (tagId === DefaultTagID.Favourite || tagId === DefaultTagID.LowPriority) {
|
||||
const inverseTag = tagId === DefaultTagID.Favourite ? DefaultTagID.LowPriority : DefaultTagID.Favourite;
|
||||
const isApplied = RoomListStore.instance.getTagsForRoom(room).includes(tagId);
|
||||
const removeTag = isApplied ? tagId : inverseTag;
|
||||
const addTag = isApplied ? null : tagId;
|
||||
dis.dispatch(RoomListActions.tagRoom(cli, room, removeTag, addTag, undefined, 0));
|
||||
} else {
|
||||
logger.warn(`Unexpected tag ${tagId} applied to ${room.roomId}`);
|
||||
}
|
||||
};
|
||||
|
||||
const isFavorite = roomTags.includes(DefaultTagID.Favourite);
|
||||
const favoriteOption: JSX.Element = <IconizedContextMenuCheckbox
|
||||
onClick={wrapHandler((ev) =>
|
||||
onTagRoom(ev, DefaultTagID.Favourite), onPostFavoriteClick, true)}
|
||||
active={isFavorite}
|
||||
label={isFavorite ? _t("Favourited") : _t("Favourite")}
|
||||
iconClassName="mx_RoomGeneralContextMenu_iconStar"
|
||||
/>;
|
||||
|
||||
const isLowPriority = roomTags.includes(DefaultTagID.LowPriority);
|
||||
const lowPriorityOption: JSX.Element = <IconizedContextMenuCheckbox
|
||||
onClick={wrapHandler((ev) =>
|
||||
onTagRoom(ev, DefaultTagID.LowPriority), onPostLowPriorityClick, true)}
|
||||
active={isLowPriority}
|
||||
label={_t("Low Priority")}
|
||||
iconClassName="mx_RoomGeneralContextMenu_iconArrowDown"
|
||||
/>;
|
||||
|
||||
let inviteOption: JSX.Element;
|
||||
if (room.canInvite(cli.getUserId()) && !isDm) {
|
||||
inviteOption = <IconizedContextMenuOption
|
||||
onClick={wrapHandler(() => dis.dispatch({
|
||||
action: "view_invite",
|
||||
roomId: room.roomId,
|
||||
}), onPostInviteClick)}
|
||||
label={_t("Invite")}
|
||||
iconClassName="mx_RoomGeneralContextMenu_iconInvite"
|
||||
/>;
|
||||
}
|
||||
|
||||
let copyLinkOption: JSX.Element;
|
||||
if (!isDm) {
|
||||
copyLinkOption = <IconizedContextMenuOption
|
||||
onClick={wrapHandler(() => dis.dispatch({
|
||||
action: "copy_room",
|
||||
room_id: room.roomId,
|
||||
}), onPostCopyLinkClick)}
|
||||
label={_t("Copy room link")}
|
||||
iconClassName="mx_RoomGeneralContextMenu_iconCopyLink"
|
||||
/>;
|
||||
}
|
||||
|
||||
const settingsOption: JSX.Element = <IconizedContextMenuOption
|
||||
onClick={wrapHandler(() => dis.dispatch({
|
||||
action: "open_room_settings",
|
||||
room_id: room.roomId,
|
||||
}), onPostSettingsClick)}
|
||||
label={_t("Settings")}
|
||||
iconClassName="mx_RoomGeneralContextMenu_iconSettings"
|
||||
/>;
|
||||
|
||||
let leaveOption: JSX.Element;
|
||||
if (roomTags.includes(DefaultTagID.Archived)) {
|
||||
leaveOption = <IconizedContextMenuOption
|
||||
iconClassName="mx_RoomGeneralContextMenu_iconSignOut"
|
||||
label={_t("Forget Room")}
|
||||
className="mx_IconizedContextMenu_option_red"
|
||||
onClick={wrapHandler(() => dis.dispatch({
|
||||
action: "forget_room",
|
||||
room_id: room.roomId,
|
||||
}), onPostForgetClick)}
|
||||
/>;
|
||||
} else {
|
||||
leaveOption = <IconizedContextMenuOption
|
||||
onClick={wrapHandler(() => dis.dispatch({
|
||||
action: "leave_room",
|
||||
room_id: room.roomId,
|
||||
}), onPostLeaveClick)}
|
||||
label={_t("Leave")}
|
||||
className="mx_IconizedContextMenu_option_red"
|
||||
iconClassName="mx_RoomGeneralContextMenu_iconSignOut"
|
||||
/>;
|
||||
}
|
||||
|
||||
return <IconizedContextMenu
|
||||
{...props}
|
||||
onFinished={onFinished}
|
||||
className="mx_RoomGeneralContextMenu"
|
||||
compact
|
||||
>
|
||||
{ !roomTags.includes(DefaultTagID.Archived) && (
|
||||
<IconizedContextMenuOptionList>
|
||||
{ favoriteOption }
|
||||
{ lowPriorityOption }
|
||||
{ inviteOption }
|
||||
{ copyLinkOption }
|
||||
{ settingsOption }
|
||||
</IconizedContextMenuOptionList>
|
||||
) }
|
||||
<IconizedContextMenuOptionList red>
|
||||
{ leaveOption }
|
||||
</IconizedContextMenuOptionList>
|
||||
</IconizedContextMenu>;
|
||||
};
|
|
@ -0,0 +1,97 @@
|
|||
/*
|
||||
Copyright 2021 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 { Room } from "matrix-js-sdk/src/models/room";
|
||||
import React from "react";
|
||||
|
||||
import { KeyBindingAction } from "../../../accessibility/KeyboardShortcuts";
|
||||
import { useNotificationState } from "../../../hooks/useRoomNotificationState";
|
||||
import { getKeyBindingsManager } from "../../../KeyBindingsManager";
|
||||
import { _t } from "../../../languageHandler";
|
||||
import { RoomNotifState } from "../../../RoomNotifs";
|
||||
import { IProps as IContextMenuProps } from "../../structures/ContextMenu";
|
||||
import IconizedContextMenu, {
|
||||
IconizedContextMenuOptionList,
|
||||
IconizedContextMenuRadio,
|
||||
} from "../context_menus/IconizedContextMenu";
|
||||
import { ButtonEvent } from "../elements/AccessibleButton";
|
||||
|
||||
interface IProps extends IContextMenuProps {
|
||||
room: Room;
|
||||
}
|
||||
|
||||
export const RoomNotificationContextMenu = ({ room, onFinished, ...props }: IProps) => {
|
||||
const [notificationState, setNotificationState] = useNotificationState(room);
|
||||
|
||||
const wrapHandler = (
|
||||
handler: (ev: ButtonEvent) => void,
|
||||
persistent = false,
|
||||
): (ev: ButtonEvent) => void => {
|
||||
return (ev: ButtonEvent) => {
|
||||
ev.preventDefault();
|
||||
ev.stopPropagation();
|
||||
|
||||
handler(ev);
|
||||
|
||||
const action = getKeyBindingsManager().getAccessibilityAction(ev as React.KeyboardEvent);
|
||||
if (!persistent || action === KeyBindingAction.Enter) {
|
||||
onFinished();
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
const defaultOption: JSX.Element = <IconizedContextMenuRadio
|
||||
label={_t("Use default")}
|
||||
active={notificationState === RoomNotifState.AllMessages}
|
||||
iconClassName="mx_RoomNotificationContextMenu_iconBell"
|
||||
onClick={wrapHandler(() => setNotificationState(RoomNotifState.AllMessages))}
|
||||
/>;
|
||||
|
||||
const allMessagesOption: JSX.Element = <IconizedContextMenuRadio
|
||||
label={_t("All messages")}
|
||||
active={notificationState === RoomNotifState.AllMessagesLoud}
|
||||
iconClassName="mx_RoomNotificationContextMenu_iconBellDot"
|
||||
onClick={wrapHandler(() => setNotificationState(RoomNotifState.AllMessagesLoud))}
|
||||
/>;
|
||||
|
||||
const mentionsOption: JSX.Element = <IconizedContextMenuRadio
|
||||
label={_t("Mentions & Keywords")}
|
||||
active={notificationState === RoomNotifState.MentionsOnly}
|
||||
iconClassName="mx_RoomNotificationContextMenu_iconBellMentions"
|
||||
onClick={wrapHandler(() => setNotificationState(RoomNotifState.MentionsOnly))}
|
||||
/>;
|
||||
|
||||
const muteOption: JSX.Element = <IconizedContextMenuRadio
|
||||
label={_t("None")}
|
||||
active={notificationState === RoomNotifState.Mute}
|
||||
iconClassName="mx_RoomNotificationContextMenu_iconBellCrossed"
|
||||
onClick={wrapHandler(() => setNotificationState(RoomNotifState.Mute))}
|
||||
/>;
|
||||
|
||||
return <IconizedContextMenu
|
||||
{...props}
|
||||
onFinished={onFinished}
|
||||
className="mx_RoomNotificationContextMenu"
|
||||
compact
|
||||
>
|
||||
<IconizedContextMenuOptionList first>
|
||||
{ defaultOption }
|
||||
{ allMessagesOption }
|
||||
{ mentionsOption }
|
||||
{ muteOption }
|
||||
</IconizedContextMenuOptionList>
|
||||
</IconizedContextMenu>;
|
||||
};
|
|
@ -37,7 +37,9 @@ export const Option: React.FC<OptionProps> = ({ inputRef, children, endAdornment
|
|||
role="option"
|
||||
>
|
||||
{ children }
|
||||
<kbd className="mx_SpotlightDialog_enterPrompt" aria-hidden>↵</kbd>
|
||||
{ endAdornment }
|
||||
<div className="mx_SpotlightDialog_option--endAdornment">
|
||||
<kbd className="mx_SpotlightDialog_enterPrompt" aria-hidden>↵</kbd>
|
||||
{ endAdornment }
|
||||
</div>
|
||||
</AccessibleButton>;
|
||||
};
|
||||
|
|
|
@ -0,0 +1,107 @@
|
|||
/*
|
||||
Copyright 2021-2022 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 classNames from "classnames";
|
||||
import { Room } from "matrix-js-sdk/src/matrix";
|
||||
import React, { Fragment, useState } from "react";
|
||||
|
||||
import { ContextMenuTooltipButton } from "../../../../accessibility/context_menu/ContextMenuTooltipButton";
|
||||
import { useNotificationState } from "../../../../hooks/useRoomNotificationState";
|
||||
import { _t } from "../../../../languageHandler";
|
||||
import { RoomNotifState } from "../../../../RoomNotifs";
|
||||
import { RoomGeneralContextMenu } from "../../context_menus/RoomGeneralContextMenu";
|
||||
import { RoomNotificationContextMenu } from "../../context_menus/RoomNotificationContextMenu";
|
||||
import SpaceContextMenu from "../../context_menus/SpaceContextMenu";
|
||||
import { ButtonEvent } from "../../elements/AccessibleButton";
|
||||
import { contextMenuBelow } from "../../rooms/RoomTile";
|
||||
|
||||
interface Props {
|
||||
room: Room;
|
||||
}
|
||||
|
||||
export function RoomResultContextMenus({ room }: Props) {
|
||||
const [notificationState] = useNotificationState(room);
|
||||
|
||||
const [generalMenuPosition, setGeneralMenuPosition] = useState<DOMRect | null>(null);
|
||||
const [notificationMenuPosition, setNotificationMenuPosition] = useState<DOMRect | null>(null);
|
||||
|
||||
let generalMenu: JSX.Element;
|
||||
if (generalMenuPosition !== null) {
|
||||
if (room.isSpaceRoom()) {
|
||||
generalMenu = <SpaceContextMenu
|
||||
{...contextMenuBelow(generalMenuPosition)}
|
||||
space={room}
|
||||
onFinished={() => setGeneralMenuPosition(null)}
|
||||
/>;
|
||||
} else {
|
||||
generalMenu = <RoomGeneralContextMenu
|
||||
{...contextMenuBelow(generalMenuPosition)}
|
||||
room={room}
|
||||
onFinished={() => setGeneralMenuPosition(null)}
|
||||
/>;
|
||||
}
|
||||
}
|
||||
|
||||
let notificationMenu: JSX.Element;
|
||||
if (notificationMenuPosition !== null) {
|
||||
notificationMenu = <RoomNotificationContextMenu
|
||||
{...contextMenuBelow(notificationMenuPosition)}
|
||||
room={room}
|
||||
onFinished={() => setNotificationMenuPosition(null)}
|
||||
/>;
|
||||
}
|
||||
|
||||
const notificationMenuClasses = classNames("mx_SpotlightDialog_option--notifications", {
|
||||
// Show bell icon for the default case too.
|
||||
mx_RoomNotificationContextMenu_iconBell: notificationState === RoomNotifState.AllMessages,
|
||||
mx_RoomNotificationContextMenu_iconBellDot: notificationState === RoomNotifState.AllMessagesLoud,
|
||||
mx_RoomNotificationContextMenu_iconBellMentions: notificationState === RoomNotifState.MentionsOnly,
|
||||
mx_RoomNotificationContextMenu_iconBellCrossed: notificationState === RoomNotifState.Mute,
|
||||
});
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<ContextMenuTooltipButton
|
||||
className="mx_SpotlightDialog_option--menu"
|
||||
onClick={(ev: ButtonEvent) => {
|
||||
ev.preventDefault();
|
||||
ev.stopPropagation();
|
||||
|
||||
const target = ev.target as HTMLElement;
|
||||
setGeneralMenuPosition(target.getBoundingClientRect());
|
||||
}}
|
||||
title={room.isSpaceRoom() ? _t("Space options") : _t("Room options")}
|
||||
isExpanded={generalMenuPosition !== null}
|
||||
/>
|
||||
{ !room.isSpaceRoom() && (
|
||||
<ContextMenuTooltipButton
|
||||
className={notificationMenuClasses}
|
||||
onClick={(ev: ButtonEvent) => {
|
||||
ev.preventDefault();
|
||||
ev.stopPropagation();
|
||||
|
||||
const target = ev.target as HTMLElement;
|
||||
setNotificationMenuPosition(target.getBoundingClientRect());
|
||||
}}
|
||||
title={_t("Notification options")}
|
||||
isExpanded={notificationMenuPosition !== null}
|
||||
/>
|
||||
) }
|
||||
{ generalMenu }
|
||||
{ notificationMenu }
|
||||
</Fragment>
|
||||
);
|
||||
}
|
|
@ -88,6 +88,7 @@ import FeedbackDialog from "../FeedbackDialog";
|
|||
import { IDialogProps } from "../IDialogProps";
|
||||
import { Option } from "./Option";
|
||||
import { PublicRoomResultDetails } from "./PublicRoomResultDetails";
|
||||
import { RoomResultContextMenus } from "./RoomResultContextMenus";
|
||||
import { RoomContextDetails } from "../../rooms/RoomContextDetails";
|
||||
import { TooltipOption } from "./TooltipOption";
|
||||
|
||||
|
@ -506,8 +507,11 @@ const SpotlightDialog: React.FC<IProps> = ({ initialText = "", initialFilter = n
|
|||
let otherSearchesSection: JSX.Element;
|
||||
if (trimmedQuery || filter !== Filter.PublicRooms) {
|
||||
otherSearchesSection = (
|
||||
<div className="mx_SpotlightDialog_section mx_SpotlightDialog_otherSearches" role="group">
|
||||
<h4>
|
||||
<div
|
||||
className="mx_SpotlightDialog_section mx_SpotlightDialog_otherSearches"
|
||||
role="group"
|
||||
aria-labelledby="mx_SpotlightDialog_section_otherSearches">
|
||||
<h4 id="mx_SpotlightDialog_section_otherSearches">
|
||||
{ trimmedQuery
|
||||
? _t('Use "%(query)s" to search', { query })
|
||||
: _t("Search for") }
|
||||
|
@ -544,7 +548,7 @@ const SpotlightDialog: React.FC<IProps> = ({ initialText = "", initialFilter = n
|
|||
const unreadLabel = roomAriaUnreadLabel(result.room, notification);
|
||||
const ariaProperties = {
|
||||
"aria-label": unreadLabel ? `${result.room.name} ${unreadLabel}` : result.room.name,
|
||||
"aria-details": `mx_SpotlightDialog_button_result_${result.room.roomId}_details`,
|
||||
"aria-describedby": `mx_SpotlightDialog_button_result_${result.room.roomId}_details`,
|
||||
};
|
||||
return (
|
||||
<Option
|
||||
|
@ -553,6 +557,7 @@ const SpotlightDialog: React.FC<IProps> = ({ initialText = "", initialFilter = n
|
|||
onClick={(ev) => {
|
||||
viewRoom(result.room.roomId, true, ev?.type !== "click");
|
||||
}}
|
||||
endAdornment={<RoomResultContextMenus room={result.room} />}
|
||||
{...ariaProperties}
|
||||
>
|
||||
<DecoratedRoomAvatar
|
||||
|
@ -948,8 +953,10 @@ const SpotlightDialog: React.FC<IProps> = ({ initialText = "", initialFilter = n
|
|||
tabIndex={-1}
|
||||
aria-labelledby="mx_SpotlightDialog_section_recentSearches"
|
||||
>
|
||||
<h4 id="mx_SpotlightDialog_section_recentSearches">
|
||||
{ _t("Recent searches") }
|
||||
<h4>
|
||||
<span id="mx_SpotlightDialog_section_recentSearches">
|
||||
{ _t("Recent searches") }
|
||||
</span>
|
||||
<AccessibleButton kind="link" onClick={clearRecentSearches}>
|
||||
{ _t("Clear") }
|
||||
</AccessibleButton>
|
||||
|
@ -960,7 +967,7 @@ const SpotlightDialog: React.FC<IProps> = ({ initialText = "", initialFilter = n
|
|||
const unreadLabel = roomAriaUnreadLabel(room, notification);
|
||||
const ariaProperties = {
|
||||
"aria-label": unreadLabel ? `${room.name} ${unreadLabel}` : room.name,
|
||||
"aria-details": `mx_SpotlightDialog_button_recentSearch_${room.roomId}_details`,
|
||||
"aria-describedby": `mx_SpotlightDialog_button_recentSearch_${room.roomId}_details`,
|
||||
};
|
||||
return (
|
||||
<Option
|
||||
|
@ -969,6 +976,7 @@ const SpotlightDialog: React.FC<IProps> = ({ initialText = "", initialFilter = n
|
|||
onClick={(ev) => {
|
||||
viewRoom(room.roomId, true, ev?.type !== "click");
|
||||
}}
|
||||
endAdornment={<RoomResultContextMenus room={room} />}
|
||||
{...ariaProperties}
|
||||
>
|
||||
<DecoratedRoomAvatar
|
||||
|
@ -1034,6 +1042,7 @@ const SpotlightDialog: React.FC<IProps> = ({ initialText = "", initialFilter = n
|
|||
break;
|
||||
}
|
||||
|
||||
let ref: RefObject<HTMLElement>;
|
||||
const accessibilityAction = getKeyBindingsManager().getAccessibilityAction(ev);
|
||||
switch (accessibilityAction) {
|
||||
case KeyBindingAction.Escape:
|
||||
|
@ -1041,22 +1050,6 @@ const SpotlightDialog: React.FC<IProps> = ({ initialText = "", initialFilter = n
|
|||
ev.preventDefault();
|
||||
onFinished();
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
const onKeyDown = (ev: KeyboardEvent) => {
|
||||
let ref: RefObject<HTMLElement>;
|
||||
|
||||
const action = getKeyBindingsManager().getAccessibilityAction(ev);
|
||||
|
||||
switch (action) {
|
||||
case KeyBindingAction.Backspace:
|
||||
if (!query && filter !== null) {
|
||||
ev.stopPropagation();
|
||||
ev.preventDefault();
|
||||
setFilter(null);
|
||||
}
|
||||
break;
|
||||
case KeyBindingAction.ArrowUp:
|
||||
case KeyBindingAction.ArrowDown:
|
||||
ev.stopPropagation();
|
||||
|
@ -1075,7 +1068,7 @@ const SpotlightDialog: React.FC<IProps> = ({ initialText = "", initialFilter = n
|
|||
}
|
||||
|
||||
const idx = refs.indexOf(rovingContext.state.activeRef);
|
||||
ref = findSiblingElement(refs, idx + (action === KeyBindingAction.ArrowUp ? -1 : 1));
|
||||
ref = findSiblingElement(refs, idx + (accessibilityAction === KeyBindingAction.ArrowUp ? -1 : 1));
|
||||
}
|
||||
break;
|
||||
|
||||
|
@ -1092,14 +1085,9 @@ const SpotlightDialog: React.FC<IProps> = ({ initialText = "", initialFilter = n
|
|||
|
||||
const refs = rovingContext.state.refs.filter(refIsForRecentlyViewed);
|
||||
const idx = refs.indexOf(rovingContext.state.activeRef);
|
||||
ref = findSiblingElement(refs, idx + (action === KeyBindingAction.ArrowLeft ? -1 : 1));
|
||||
ref = findSiblingElement(refs, idx + (accessibilityAction === KeyBindingAction.ArrowLeft ? -1 : 1));
|
||||
}
|
||||
break;
|
||||
case KeyBindingAction.Enter:
|
||||
ev.stopPropagation();
|
||||
ev.preventDefault();
|
||||
rovingContext.state.activeRef?.current?.click();
|
||||
break;
|
||||
}
|
||||
|
||||
if (ref) {
|
||||
|
@ -1113,6 +1101,25 @@ const SpotlightDialog: React.FC<IProps> = ({ initialText = "", initialFilter = n
|
|||
}
|
||||
};
|
||||
|
||||
const onKeyDown = (ev: KeyboardEvent) => {
|
||||
const action = getKeyBindingsManager().getAccessibilityAction(ev);
|
||||
|
||||
switch (action) {
|
||||
case KeyBindingAction.Backspace:
|
||||
if (!query && filter !== null) {
|
||||
ev.stopPropagation();
|
||||
ev.preventDefault();
|
||||
setFilter(null);
|
||||
}
|
||||
break;
|
||||
case KeyBindingAction.Enter:
|
||||
ev.stopPropagation();
|
||||
ev.preventDefault();
|
||||
rovingContext.state.activeRef?.current?.click();
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
const openFeedback = SdkConfig.get().bug_report_endpoint_url ? () => {
|
||||
Modal.createDialog(FeedbackDialog, {
|
||||
feature: "spotlight",
|
||||
|
|
|
@ -18,7 +18,6 @@ limitations under the License.
|
|||
import React, { createRef } from "react";
|
||||
import { Room, RoomEvent } from "matrix-js-sdk/src/models/room";
|
||||
import classNames from "classnames";
|
||||
import { logger } from "matrix-js-sdk/src/logger";
|
||||
|
||||
import { RovingTabIndexWrapper } from "../../../accessibility/RovingTabIndex";
|
||||
import AccessibleButton, { ButtonEvent } from "../../views/elements/AccessibleButton";
|
||||
|
@ -32,9 +31,8 @@ import { MessagePreviewStore } from "../../../stores/room-list/MessagePreviewSto
|
|||
import DecoratedRoomAvatar from "../avatars/DecoratedRoomAvatar";
|
||||
import { RoomNotifState } from "../../../RoomNotifs";
|
||||
import { MatrixClientPeg } from "../../../MatrixClientPeg";
|
||||
import { RoomNotificationContextMenu } from "../context_menus/RoomNotificationContextMenu";
|
||||
import NotificationBadge from "./NotificationBadge";
|
||||
import RoomListStore from "../../../stores/room-list/RoomListStore";
|
||||
import RoomListActions from "../../../actions/RoomListActions";
|
||||
import { ActionPayload } from "../../../dispatcher/payloads";
|
||||
import { RoomNotificationStateStore } from "../../../stores/notifications/RoomNotificationStateStore";
|
||||
import { NotificationState, NotificationStateEvents } from "../../../stores/notifications/NotificationState";
|
||||
|
@ -42,18 +40,13 @@ import AccessibleTooltipButton from "../elements/AccessibleTooltipButton";
|
|||
import { EchoChamber } from "../../../stores/local-echo/EchoChamber";
|
||||
import { CachedRoomKey, RoomEchoChamber } from "../../../stores/local-echo/RoomEchoChamber";
|
||||
import { PROPERTY_UPDATED } from "../../../stores/local-echo/GenericEchoChamber";
|
||||
import IconizedContextMenu, {
|
||||
IconizedContextMenuCheckbox,
|
||||
IconizedContextMenuOption,
|
||||
IconizedContextMenuOptionList,
|
||||
IconizedContextMenuRadio,
|
||||
} from "../context_menus/IconizedContextMenu";
|
||||
import PosthogTrackers from "../../../PosthogTrackers";
|
||||
import { ViewRoomPayload } from "../../../dispatcher/payloads/ViewRoomPayload";
|
||||
import { KeyBindingAction } from "../../../accessibility/KeyboardShortcuts";
|
||||
import { getKeyBindingsManager } from "../../../KeyBindingsManager";
|
||||
import { RoomViewStore } from "../../../stores/RoomViewStore";
|
||||
import VideoRoomSummary from "./VideoRoomSummary";
|
||||
import { RoomGeneralContextMenu } from "../context_menus/RoomGeneralContextMenu";
|
||||
|
||||
interface IProps {
|
||||
room: Room;
|
||||
|
@ -267,118 +260,6 @@ export default class RoomTile extends React.PureComponent<IProps, IState> {
|
|||
this.setState({ generalMenuPosition: null });
|
||||
};
|
||||
|
||||
private onTagRoom = (ev: ButtonEvent, tagId: TagID) => {
|
||||
ev.preventDefault();
|
||||
ev.stopPropagation();
|
||||
|
||||
if (tagId === DefaultTagID.Favourite || tagId === DefaultTagID.LowPriority) {
|
||||
const inverseTag = tagId === DefaultTagID.Favourite ? DefaultTagID.LowPriority : DefaultTagID.Favourite;
|
||||
const isApplied = RoomListStore.instance.getTagsForRoom(this.props.room).includes(tagId);
|
||||
const removeTag = isApplied ? tagId : inverseTag;
|
||||
const addTag = isApplied ? null : tagId;
|
||||
defaultDispatcher.dispatch(RoomListActions.tagRoom(
|
||||
MatrixClientPeg.get(),
|
||||
this.props.room,
|
||||
removeTag,
|
||||
addTag,
|
||||
undefined,
|
||||
0,
|
||||
));
|
||||
} else {
|
||||
logger.warn(`Unexpected tag ${tagId} applied to ${this.props.room.roomId}`);
|
||||
}
|
||||
|
||||
const action = getKeyBindingsManager().getAccessibilityAction(ev as React.KeyboardEvent);
|
||||
switch (action) {
|
||||
case KeyBindingAction.Enter:
|
||||
// Implements https://www.w3.org/TR/wai-aria-practices/#keyboard-interaction-12
|
||||
this.setState({ generalMenuPosition: null }); // hide the menu
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
private onLeaveRoomClick = (ev: ButtonEvent) => {
|
||||
ev.preventDefault();
|
||||
ev.stopPropagation();
|
||||
|
||||
defaultDispatcher.dispatch({
|
||||
action: 'leave_room',
|
||||
room_id: this.props.room.roomId,
|
||||
});
|
||||
this.setState({ generalMenuPosition: null }); // hide the menu
|
||||
|
||||
PosthogTrackers.trackInteraction("WebRoomListRoomTileContextMenuLeaveItem", ev);
|
||||
};
|
||||
|
||||
private onForgetRoomClick = (ev: ButtonEvent) => {
|
||||
ev.preventDefault();
|
||||
ev.stopPropagation();
|
||||
|
||||
defaultDispatcher.dispatch({
|
||||
action: 'forget_room',
|
||||
room_id: this.props.room.roomId,
|
||||
});
|
||||
this.setState({ generalMenuPosition: null }); // hide the menu
|
||||
};
|
||||
|
||||
private onOpenRoomSettings = (ev: ButtonEvent) => {
|
||||
ev.preventDefault();
|
||||
ev.stopPropagation();
|
||||
|
||||
defaultDispatcher.dispatch({
|
||||
action: 'open_room_settings',
|
||||
room_id: this.props.room.roomId,
|
||||
});
|
||||
this.setState({ generalMenuPosition: null }); // hide the menu
|
||||
|
||||
PosthogTrackers.trackInteraction("WebRoomListRoomTileContextMenuSettingsItem", ev);
|
||||
};
|
||||
|
||||
private onCopyRoomClick = (ev: ButtonEvent) => {
|
||||
ev.preventDefault();
|
||||
ev.stopPropagation();
|
||||
|
||||
defaultDispatcher.dispatch({
|
||||
action: 'copy_room',
|
||||
room_id: this.props.room.roomId,
|
||||
});
|
||||
this.setState({ generalMenuPosition: null }); // hide the menu
|
||||
};
|
||||
|
||||
private onInviteClick = (ev: ButtonEvent) => {
|
||||
ev.preventDefault();
|
||||
ev.stopPropagation();
|
||||
|
||||
defaultDispatcher.dispatch({
|
||||
action: 'view_invite',
|
||||
roomId: this.props.room.roomId,
|
||||
});
|
||||
this.setState({ generalMenuPosition: null }); // hide the menu
|
||||
|
||||
PosthogTrackers.trackInteraction("WebRoomListRoomTileContextMenuInviteItem", ev);
|
||||
};
|
||||
|
||||
private async saveNotifState(ev: ButtonEvent, newState: RoomNotifState) {
|
||||
ev.preventDefault();
|
||||
ev.stopPropagation();
|
||||
if (MatrixClientPeg.get().isGuest()) return;
|
||||
|
||||
this.roomProps.notificationVolume = newState;
|
||||
|
||||
const action = getKeyBindingsManager().getAccessibilityAction(ev as React.KeyboardEvent);
|
||||
switch (action) {
|
||||
case KeyBindingAction.Enter:
|
||||
// Implements https://www.w3.org/TR/wai-aria-practices/#keyboard-interaction-12
|
||||
this.setState({ notificationsMenuPosition: null }); // hide the menu
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private onClickAllNotifs = ev => this.saveNotifState(ev, RoomNotifState.AllMessages);
|
||||
private onClickAlertMe = ev => this.saveNotifState(ev, RoomNotifState.AllMessagesLoud);
|
||||
private onClickMentions = ev => this.saveNotifState(ev, RoomNotifState.MentionsOnly);
|
||||
private onClickMute = ev => this.saveNotifState(ev, RoomNotifState.Mute);
|
||||
|
||||
private renderNotificationsMenu(isActive: boolean): React.ReactElement {
|
||||
if (MatrixClientPeg.get().isGuest() || this.props.tag === DefaultTagID.Archived ||
|
||||
!this.showContextMenu || this.props.isMinimized
|
||||
|
@ -389,49 +270,12 @@ export default class RoomTile extends React.PureComponent<IProps, IState> {
|
|||
|
||||
const state = this.roomProps.notificationVolume;
|
||||
|
||||
let contextMenu = null;
|
||||
if (this.state.notificationsMenuPosition) {
|
||||
contextMenu = <IconizedContextMenu
|
||||
{...contextMenuBelow(this.state.notificationsMenuPosition)}
|
||||
onFinished={this.onCloseNotificationsMenu}
|
||||
className="mx_RoomTile_contextMenu"
|
||||
compact
|
||||
>
|
||||
<IconizedContextMenuOptionList first>
|
||||
<IconizedContextMenuRadio
|
||||
label={_t("Use default")}
|
||||
active={state === RoomNotifState.AllMessages}
|
||||
iconClassName="mx_RoomTile_iconBell"
|
||||
onClick={this.onClickAllNotifs}
|
||||
/>
|
||||
<IconizedContextMenuRadio
|
||||
label={_t("All messages")}
|
||||
active={state === RoomNotifState.AllMessagesLoud}
|
||||
iconClassName="mx_RoomTile_iconBellDot"
|
||||
onClick={this.onClickAlertMe}
|
||||
/>
|
||||
<IconizedContextMenuRadio
|
||||
label={_t("Mentions & Keywords")}
|
||||
active={state === RoomNotifState.MentionsOnly}
|
||||
iconClassName="mx_RoomTile_iconBellMentions"
|
||||
onClick={this.onClickMentions}
|
||||
/>
|
||||
<IconizedContextMenuRadio
|
||||
label={_t("None")}
|
||||
active={state === RoomNotifState.Mute}
|
||||
iconClassName="mx_RoomTile_iconBellCrossed"
|
||||
onClick={this.onClickMute}
|
||||
/>
|
||||
</IconizedContextMenuOptionList>
|
||||
</IconizedContextMenu>;
|
||||
}
|
||||
|
||||
const classes = classNames("mx_RoomTile_notificationsButton", {
|
||||
// Show bell icon for the default case too.
|
||||
mx_RoomTile_iconBell: state === RoomNotifState.AllMessages,
|
||||
mx_RoomTile_iconBellDot: state === RoomNotifState.AllMessagesLoud,
|
||||
mx_RoomTile_iconBellMentions: state === RoomNotifState.MentionsOnly,
|
||||
mx_RoomTile_iconBellCrossed: state === RoomNotifState.Mute,
|
||||
mx_RoomNotificationContextMenu_iconBell: state === RoomNotifState.AllMessages,
|
||||
mx_RoomNotificationContextMenu_iconBellDot: state === RoomNotifState.AllMessagesLoud,
|
||||
mx_RoomNotificationContextMenu_iconBellMentions: state === RoomNotifState.MentionsOnly,
|
||||
mx_RoomNotificationContextMenu_iconBellCrossed: state === RoomNotifState.Mute,
|
||||
|
||||
// Only show the icon by default if the room is overridden to muted.
|
||||
// TODO: [FTUE Notifications] Probably need to detect global mute state
|
||||
|
@ -447,93 +291,19 @@ export default class RoomTile extends React.PureComponent<IProps, IState> {
|
|||
isExpanded={!!this.state.notificationsMenuPosition}
|
||||
tabIndex={isActive ? 0 : -1}
|
||||
/>
|
||||
{ contextMenu }
|
||||
{ this.state.notificationsMenuPosition && (
|
||||
<RoomNotificationContextMenu
|
||||
{...contextMenuBelow(this.state.notificationsMenuPosition)}
|
||||
onFinished={this.onCloseNotificationsMenu}
|
||||
room={this.props.room}
|
||||
/>
|
||||
) }
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
|
||||
private renderGeneralMenu(): React.ReactElement {
|
||||
if (!this.showContextMenu) return null; // no menu to show
|
||||
|
||||
let contextMenu = null;
|
||||
if (this.state.generalMenuPosition && this.props.tag === DefaultTagID.Archived) {
|
||||
contextMenu = <IconizedContextMenu
|
||||
{...contextMenuBelow(this.state.generalMenuPosition)}
|
||||
onFinished={this.onCloseGeneralMenu}
|
||||
className="mx_RoomTile_contextMenu"
|
||||
compact
|
||||
>
|
||||
<IconizedContextMenuOptionList red>
|
||||
<IconizedContextMenuOption
|
||||
iconClassName="mx_RoomTile_iconSignOut"
|
||||
label={_t("Forget Room")}
|
||||
onClick={this.onForgetRoomClick}
|
||||
/>
|
||||
</IconizedContextMenuOptionList>
|
||||
</IconizedContextMenu>;
|
||||
} else if (this.state.generalMenuPosition) {
|
||||
const roomTags = RoomListStore.instance.getTagsForRoom(this.props.room);
|
||||
|
||||
const isFavorite = roomTags.includes(DefaultTagID.Favourite);
|
||||
const favouriteLabel = isFavorite ? _t("Favourited") : _t("Favourite");
|
||||
|
||||
const isLowPriority = roomTags.includes(DefaultTagID.LowPriority);
|
||||
const lowPriorityLabel = _t("Low Priority");
|
||||
|
||||
const isDm = roomTags.includes(DefaultTagID.DM);
|
||||
|
||||
const userId = MatrixClientPeg.get().getUserId();
|
||||
const canInvite = this.props.room.canInvite(userId) && !isDm; // hide invite in DMs from this quick menu
|
||||
contextMenu = <IconizedContextMenu
|
||||
{...contextMenuBelow(this.state.generalMenuPosition)}
|
||||
onFinished={this.onCloseGeneralMenu}
|
||||
className="mx_RoomTile_contextMenu"
|
||||
compact
|
||||
>
|
||||
<IconizedContextMenuOptionList>
|
||||
<IconizedContextMenuCheckbox
|
||||
onClick={(e) => {
|
||||
this.onTagRoom(e, DefaultTagID.Favourite);
|
||||
PosthogTrackers.trackInteraction("WebRoomListRoomTileContextMenuFavouriteToggle", e);
|
||||
}}
|
||||
active={isFavorite}
|
||||
label={favouriteLabel}
|
||||
iconClassName="mx_RoomTile_iconStar"
|
||||
/>
|
||||
<IconizedContextMenuCheckbox
|
||||
onClick={(e) => this.onTagRoom(e, DefaultTagID.LowPriority)}
|
||||
active={isLowPriority}
|
||||
label={lowPriorityLabel}
|
||||
iconClassName="mx_RoomTile_iconArrowDown"
|
||||
/>
|
||||
{ canInvite ? (
|
||||
<IconizedContextMenuOption
|
||||
onClick={this.onInviteClick}
|
||||
label={_t("Invite")}
|
||||
iconClassName="mx_RoomTile_iconInvite"
|
||||
/>
|
||||
) : null }
|
||||
{ !isDm ? <IconizedContextMenuOption
|
||||
onClick={this.onCopyRoomClick}
|
||||
label={_t("Copy room link")}
|
||||
iconClassName="mx_RoomTile_iconCopyLink"
|
||||
/> : null }
|
||||
<IconizedContextMenuOption
|
||||
onClick={this.onOpenRoomSettings}
|
||||
label={_t("Settings")}
|
||||
iconClassName="mx_RoomTile_iconSettings"
|
||||
/>
|
||||
</IconizedContextMenuOptionList>
|
||||
<IconizedContextMenuOptionList red>
|
||||
<IconizedContextMenuOption
|
||||
onClick={this.onLeaveRoomClick}
|
||||
label={_t("Leave")}
|
||||
iconClassName="mx_RoomTile_iconSignOut"
|
||||
/>
|
||||
</IconizedContextMenuOptionList>
|
||||
</IconizedContextMenu>;
|
||||
}
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
<ContextMenuTooltipButton
|
||||
|
@ -542,7 +312,25 @@ export default class RoomTile extends React.PureComponent<IProps, IState> {
|
|||
title={_t("Room options")}
|
||||
isExpanded={!!this.state.generalMenuPosition}
|
||||
/>
|
||||
{ contextMenu }
|
||||
{ this.state.generalMenuPosition && (
|
||||
<RoomGeneralContextMenu
|
||||
{...contextMenuBelow(this.state.generalMenuPosition)}
|
||||
onFinished={this.onCloseGeneralMenu}
|
||||
room={this.props.room}
|
||||
onPostFavoriteClick={(ev: ButtonEvent) => PosthogTrackers.trackInteraction(
|
||||
"WebRoomListRoomTileContextMenuFavouriteToggle", ev,
|
||||
)}
|
||||
onPostInviteClick={(ev: ButtonEvent) => PosthogTrackers.trackInteraction(
|
||||
"WebRoomListRoomTileContextMenuInviteItem", ev,
|
||||
)}
|
||||
onPostSettingsClick={(ev: ButtonEvent) => PosthogTrackers.trackInteraction(
|
||||
"WebRoomListRoomTileContextMenuSettingsItem", ev,
|
||||
)}
|
||||
onPostLeaveClick={(ev: ButtonEvent) => PosthogTrackers.trackInteraction(
|
||||
"WebRoomListRoomTileContextMenuLeaveItem", ev,
|
||||
)}
|
||||
/>
|
||||
) }
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
|
|
41
src/hooks/useRoomNotificationState.ts
Normal file
41
src/hooks/useRoomNotificationState.ts
Normal file
|
@ -0,0 +1,41 @@
|
|||
/*
|
||||
Copyright 2021 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 { useCallback, useMemo, useState } from "react";
|
||||
import { Room } from "matrix-js-sdk/src/matrix";
|
||||
|
||||
import { RoomNotifState } from "../RoomNotifs";
|
||||
import { EchoChamber } from "../stores/local-echo/EchoChamber";
|
||||
import { PROPERTY_UPDATED } from "../stores/local-echo/GenericEchoChamber";
|
||||
import { CachedRoomKey } from "../stores/local-echo/RoomEchoChamber";
|
||||
import { useEventEmitter } from "./useEventEmitter";
|
||||
|
||||
export const useNotificationState = (room: Room): [RoomNotifState, (state: RoomNotifState) => void] => {
|
||||
const echoChamber = useMemo(() => EchoChamber.forRoom(room), [room]);
|
||||
const [notificationState, setNotificationState] = useState<RoomNotifState>(
|
||||
echoChamber.notificationVolume,
|
||||
);
|
||||
useEventEmitter(echoChamber, PROPERTY_UPDATED, (key: CachedRoomKey) => {
|
||||
if (key === CachedRoomKey.NotificationVolume) {
|
||||
setNotificationState(echoChamber.notificationVolume);
|
||||
}
|
||||
});
|
||||
const setter = useCallback(
|
||||
(state: RoomNotifState) => echoChamber.notificationVolume = state,
|
||||
[echoChamber],
|
||||
);
|
||||
return [notificationState, setter];
|
||||
};
|
|
@ -1880,14 +1880,7 @@
|
|||
"Show %(count)s more|other": "Show %(count)s more",
|
||||
"Show %(count)s more|one": "Show %(count)s more",
|
||||
"Show less": "Show less",
|
||||
"Use default": "Use default",
|
||||
"Mentions & Keywords": "Mentions & Keywords",
|
||||
"Notification options": "Notification options",
|
||||
"Forget Room": "Forget Room",
|
||||
"Favourited": "Favourited",
|
||||
"Favourite": "Favourite",
|
||||
"Low Priority": "Low Priority",
|
||||
"Copy room link": "Copy room link",
|
||||
"%(count)s unread messages including mentions.|other": "%(count)s unread messages including mentions.",
|
||||
"%(count)s unread messages including mentions.|one": "1 unread mention.",
|
||||
"%(count)s unread messages.|other": "%(count)s unread messages.",
|
||||
|
@ -2938,7 +2931,14 @@
|
|||
"Report": "Report",
|
||||
"Copy link": "Copy link",
|
||||
"Forget": "Forget",
|
||||
"Favourited": "Favourited",
|
||||
"Favourite": "Favourite",
|
||||
"Mentions only": "Mentions only",
|
||||
"Copy room link": "Copy room link",
|
||||
"Low Priority": "Low Priority",
|
||||
"Forget Room": "Forget Room",
|
||||
"Use default": "Use default",
|
||||
"Mentions & Keywords": "Mentions & Keywords",
|
||||
"See room timeline (devtools)": "See room timeline (devtools)",
|
||||
"Space": "Space",
|
||||
"Space home": "Space home",
|
||||
|
|
Loading…
Reference in a new issue