Unify notifications panel event design (#9754)
							parent
							
								
									6585fb1f55
								
							
						
					
					
						commit
						bef8e077f6
					
				|  | @ -62,7 +62,6 @@ | |||
| @import "./structures/_MainSplit.pcss"; | ||||
| @import "./structures/_MatrixChat.pcss"; | ||||
| @import "./structures/_NonUrgentToastContainer.pcss"; | ||||
| @import "./structures/_NotificationPanel.pcss"; | ||||
| @import "./structures/_QuickSettingsButton.pcss"; | ||||
| @import "./structures/_RightPanel.pcss"; | ||||
| @import "./structures/_RoomSearch.pcss"; | ||||
|  |  | |||
|  | @ -1,113 +0,0 @@ | |||
| /* | ||||
| Copyright 2015, 2016 OpenMarket Ltd | ||||
| 
 | ||||
| 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. | ||||
| */ | ||||
| 
 | ||||
| .mx_NotificationPanel { | ||||
|     order: 2; | ||||
|     flex: 1 1 0; | ||||
|     overflow-y: auto; | ||||
|     display: flex; | ||||
| 
 | ||||
|     .mx_RoomView_messageListWrapper { | ||||
|         flex-direction: row; | ||||
|         align-items: center; | ||||
|         justify-content: center; | ||||
|     } | ||||
| 
 | ||||
|     .mx_RoomView_MessageList { | ||||
|         width: 100%; | ||||
| 
 | ||||
|         h2 { | ||||
|             margin-left: 0; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     /* FIXME: rather than having EventTile's default CSS be for MessagePanel, | ||||
|        we should make EventTile a base CSS class and customise it specifically | ||||
|        for usage in {Message,File,Notification}Panel. */ | ||||
| 
 | ||||
|     .mx_EventTile_avatar { | ||||
|         display: none; | ||||
|     } | ||||
| 
 | ||||
|     .mx_EventTile { | ||||
|         word-break: break-word; | ||||
|         position: relative; | ||||
|         padding-block: 18px; | ||||
| 
 | ||||
|         .mx_EventTile_senderDetails, | ||||
|         .mx_EventTile_line { | ||||
|             padding-left: 36px; /* align with the room name */ | ||||
|         } | ||||
| 
 | ||||
|         .mx_EventTile_senderDetails { | ||||
|             position: relative; | ||||
| 
 | ||||
|             a { | ||||
|                 display: flex; | ||||
|                 column-gap: 5px; /* TODO: Use a spacing variable */ | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         .mx_DisambiguatedProfile, | ||||
|         .mx_MessageTimestamp { | ||||
|             color: $primary-content; | ||||
|             font-size: $font-12px; | ||||
|             display: inline; | ||||
|         } | ||||
| 
 | ||||
|         &:hover .mx_EventTile_line { | ||||
|             background-color: $background; | ||||
|         } | ||||
| 
 | ||||
|         &:not(.mx_EventTile_last):not(.mx_EventTile_lastInSection)::after { | ||||
|             position: absolute; | ||||
|             bottom: 0; | ||||
|             left: 0; | ||||
|             right: 0; | ||||
|             background-color: $tertiary-content; | ||||
|             height: 1px; | ||||
|             opacity: 0.4; | ||||
|             content: ""; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     .mx_EventTile_roomName { | ||||
|         display: flex; | ||||
|         align-items: center; | ||||
|         column-gap: $spacing-8; | ||||
|         font-weight: bold; | ||||
|         font-size: $font-14px; | ||||
| 
 | ||||
|         a { | ||||
|             color: $primary-content; | ||||
|             white-space: nowrap; | ||||
|             overflow: hidden; | ||||
|             text-overflow: ellipsis; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     .mx_EventTile_selected .mx_EventTile_line { | ||||
|         padding-left: 0; | ||||
|     } | ||||
| 
 | ||||
|     .mx_EventTile_content { | ||||
|         margin-right: 0; | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| .mx_NotificationPanel_empty::before { | ||||
|     mask-image: url("$(res)/img/element-icons/notifications.svg"); | ||||
| } | ||||
|  | @ -210,7 +210,6 @@ limitations under the License. | |||
| 
 | ||||
| .mx_FilePanel, | ||||
| .mx_UserInfo, | ||||
| .mx_NotificationPanel, | ||||
| .mx_MemberList { | ||||
|     &.mx_BaseCard { | ||||
|         padding: $spacing-32 0 0; | ||||
|  |  | |||
|  | @ -836,7 +836,8 @@ $left-gutter: 64px; | |||
|     } | ||||
| } | ||||
| 
 | ||||
| .mx_EventTile[data-shape="ThreadsList"] { | ||||
| .mx_EventTile[data-shape="ThreadsList"], | ||||
| .mx_EventTile[data-shape="Notification"] { | ||||
|     --topOffset: $spacing-12; | ||||
|     --leftOffset: 48px; | ||||
|     $borderRadius: 8px; | ||||
|  | @ -916,9 +917,7 @@ $left-gutter: 64px; | |||
|     } | ||||
| 
 | ||||
|     .mx_DisambiguatedProfile { | ||||
|         margin-inline: 0 $spacing-12; | ||||
|         display: inline-flex; | ||||
|         flex: 1; | ||||
| 
 | ||||
|         .mx_DisambiguatedProfile_displayName, | ||||
|         .mx_DisambiguatedProfile_mxid { | ||||
|  | @ -941,6 +940,7 @@ $left-gutter: 64px; | |||
|         width: 100%; | ||||
|         box-sizing: border-box; | ||||
|         padding-bottom: 0; | ||||
|         padding-inline-start: var(--leftOffset); | ||||
| 
 | ||||
|         .mx_ThreadPanel_replies { | ||||
|             margin-top: $spacing-8; | ||||
|  | @ -966,11 +966,6 @@ $left-gutter: 64px; | |||
|         } | ||||
|     } | ||||
| 
 | ||||
|     .mx_DisambiguatedProfile, | ||||
|     .mx_EventTile_line { | ||||
|         padding-inline-start: var(--leftOffset); | ||||
|     } | ||||
| 
 | ||||
|     .mx_MessageTimestamp { | ||||
|         font-size: $font-12px; | ||||
|         max-width: var(--MessageTimestamp-max-width); | ||||
|  | @ -1300,6 +1295,21 @@ $left-gutter: 64px; | |||
|     } | ||||
| } | ||||
| 
 | ||||
| .mx_EventTile_details { | ||||
|     display: flex; | ||||
|     width: -webkit-fill-available; | ||||
|     align-items: center; | ||||
|     justify-content: space-between; | ||||
|     gap: $spacing-8; | ||||
|     margin-left: var(--leftOffset); | ||||
|     .mx_EventTile_truncated { | ||||
|         flex: 1; | ||||
|         overflow: hidden; | ||||
|         text-overflow: ellipsis; | ||||
|         white-space: nowrap; | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| /* Media query for mobile UI */ | ||||
| @media only screen and (max-width: 480px) { | ||||
|     .mx_EventTile_content { | ||||
|  |  | |||
|  | @ -25,6 +25,7 @@ import Spinner from "../views/elements/Spinner"; | |||
| import { Layout } from "../../settings/enums/Layout"; | ||||
| import RoomContext, { TimelineRenderingType } from "../../contexts/RoomContext"; | ||||
| import Measured from "../views/elements/Measured"; | ||||
| import Heading from "../views/typography/Heading"; | ||||
| 
 | ||||
| interface IProps { | ||||
|     onClose(): void; | ||||
|  | @ -90,8 +91,21 @@ export default class NotificationPanel extends React.PureComponent<IProps, IStat | |||
|                     narrow: this.state.narrow, | ||||
|                 }} | ||||
|             > | ||||
|                 <BaseCard className="mx_NotificationPanel" onClose={this.props.onClose} withoutScrollContainer> | ||||
|                     <Measured sensor={this.card.current} onMeasurement={this.onMeasurement} /> | ||||
|                 <BaseCard | ||||
|                     header={ | ||||
|                         <Heading size="h4" className="mx_BaseCard_header_title_heading"> | ||||
|                             {_t("Notifications")} | ||||
|                         </Heading> | ||||
|                     } | ||||
|                     /** | ||||
|                      * Need to rename this CSS class to something more generic | ||||
|                      * Will be done once all the panels are using a similar layout | ||||
|                      */ | ||||
|                     className="mx_ThreadPanel" | ||||
|                     onClose={this.props.onClose} | ||||
|                     withoutScrollContainer={true} | ||||
|                 > | ||||
|                     {this.card.current && <Measured sensor={this.card.current} onMeasurement={this.onMeasurement} />} | ||||
|                     {content} | ||||
|                 </BaseCard> | ||||
|             </RoomContext.Provider> | ||||
|  |  | |||
|  | @ -29,8 +29,6 @@ import { CryptoEvent } from "matrix-js-sdk/src/crypto"; | |||
| import { UserTrustLevel } from "matrix-js-sdk/src/crypto/CrossSigning"; | ||||
| import { Feature, ServerSupport } from "matrix-js-sdk/src/feature"; | ||||
| 
 | ||||
| import { Icon as LinkIcon } from "../../../../res/img/element-icons/link.svg"; | ||||
| import { Icon as ViewInRoomIcon } from "../../../../res/img/element-icons/view-in-room.svg"; | ||||
| import ReplyChain from "../elements/ReplyChain"; | ||||
| import { _t } from "../../../languageHandler"; | ||||
| import dis from "../../../dispatcher/dispatcher"; | ||||
|  | @ -63,8 +61,6 @@ import SettingsStore from "../../../settings/SettingsStore"; | |||
| import { MessagePreviewStore } from "../../../stores/room-list/MessagePreviewStore"; | ||||
| import RoomContext, { TimelineRenderingType } from "../../../contexts/RoomContext"; | ||||
| import { MediaEventHelper } from "../../../utils/MediaEventHelper"; | ||||
| import Toolbar from "../../../accessibility/Toolbar"; | ||||
| import { RovingAccessibleTooltipButton } from "../../../accessibility/roving/RovingAccessibleTooltipButton"; | ||||
| import { ThreadNotificationState } from "../../../stores/notifications/ThreadNotificationState"; | ||||
| import { RoomNotificationStateStore } from "../../../stores/notifications/RoomNotificationStateStore"; | ||||
| import { NotificationStateEvents } from "../../../stores/notifications/NotificationState"; | ||||
|  | @ -85,6 +81,7 @@ import { ShowThreadPayload } from "../../../dispatcher/payloads/ShowThreadPayloa | |||
| import { isLocalRoom } from "../../../utils/localRoom/isLocalRoom"; | ||||
| import { ElementCall } from "../../../models/Call"; | ||||
| import { UnreadNotificationBadge } from "./NotificationBadge/UnreadNotificationBadge"; | ||||
| import { EventTileThreadToolbar } from "./EventTile/EventTileThreadToolbar"; | ||||
| 
 | ||||
| export type GetRelationsForEvent = ( | ||||
|     eventId: string, | ||||
|  | @ -972,6 +969,8 @@ export class UnwrappedEventTile extends React.Component<EventTileProps, IState> | |||
|             isContinuation = false; | ||||
|         } | ||||
| 
 | ||||
|         const isRenderingNotification = this.context.timelineRenderingType === TimelineRenderingType.Notification; | ||||
| 
 | ||||
|         const isEditing = !!this.props.editState; | ||||
|         const classes = classNames({ | ||||
|             mx_EventTile_bubbleContainer: isBubbleMessage, | ||||
|  | @ -996,7 +995,8 @@ export class UnwrappedEventTile extends React.Component<EventTileProps, IState> | |||
|             mx_EventTile_bad: isEncryptionFailure, | ||||
|             mx_EventTile_emote: msgtype === MsgType.Emote, | ||||
|             mx_EventTile_noSender: this.props.hideSender, | ||||
|             mx_EventTile_clamp: this.context.timelineRenderingType === TimelineRenderingType.ThreadsList, | ||||
|             mx_EventTile_clamp: | ||||
|                 this.context.timelineRenderingType === TimelineRenderingType.ThreadsList || isRenderingNotification, | ||||
|             mx_EventTile_noBubble: noBubbleEvent, | ||||
|         }); | ||||
| 
 | ||||
|  | @ -1012,12 +1012,12 @@ export class UnwrappedEventTile extends React.Component<EventTileProps, IState> | |||
|         // Local echos have a send "status".
 | ||||
|         const scrollToken = this.props.mxEvent.status ? undefined : this.props.mxEvent.getId(); | ||||
| 
 | ||||
|         let avatar: JSX.Element; | ||||
|         let sender: JSX.Element; | ||||
|         let avatar: JSX.Element | null = null; | ||||
|         let sender: JSX.Element | null = null; | ||||
|         let avatarSize: number; | ||||
|         let needsSenderProfile: boolean; | ||||
| 
 | ||||
|         if (this.context.timelineRenderingType === TimelineRenderingType.Notification) { | ||||
|         if (isRenderingNotification) { | ||||
|             avatarSize = 24; | ||||
|             needsSenderProfile = true; | ||||
|         } else if (isInfoMessage) { | ||||
|  | @ -1061,7 +1061,9 @@ export class UnwrappedEventTile extends React.Component<EventTileProps, IState> | |||
|                 member = this.props.mxEvent.sender; | ||||
|             } | ||||
|             // In the ThreadsList view we use the entire EventTile as a click target to open the thread instead
 | ||||
|             const viewUserOnClick = this.context.timelineRenderingType !== TimelineRenderingType.ThreadsList; | ||||
|             const viewUserOnClick = ![TimelineRenderingType.ThreadsList, TimelineRenderingType.Notification].includes( | ||||
|                 this.context.timelineRenderingType, | ||||
|             ); | ||||
|             avatar = ( | ||||
|                 <div className="mx_EventTile_avatar"> | ||||
|                     <MemberAvatar | ||||
|  | @ -1202,57 +1204,6 @@ export class UnwrappedEventTile extends React.Component<EventTileProps, IState> | |||
|         const isOwnEvent = this.props.mxEvent?.getSender() === MatrixClientPeg.get().getUserId(); | ||||
| 
 | ||||
|         switch (this.context.timelineRenderingType) { | ||||
|             case TimelineRenderingType.Notification: { | ||||
|                 const room = MatrixClientPeg.get().getRoom(this.props.mxEvent.getRoomId()); | ||||
|                 return React.createElement( | ||||
|                     this.props.as || "li", | ||||
|                     { | ||||
|                         "className": classes, | ||||
|                         "aria-live": ariaLive, | ||||
|                         "aria-atomic": true, | ||||
|                         "data-scroll-tokens": scrollToken, | ||||
|                     }, | ||||
|                     [ | ||||
|                         <div className="mx_EventTile_roomName" key="mx_EventTile_roomName"> | ||||
|                             <RoomAvatar room={room} width={28} height={28} /> | ||||
|                             <a href={permalink} onClick={this.onPermalinkClicked}> | ||||
|                                 {room ? room.name : ""} | ||||
|                             </a> | ||||
|                         </div>, | ||||
|                         <div className="mx_EventTile_senderDetails" key="mx_EventTile_senderDetails"> | ||||
|                             {avatar} | ||||
|                             <a | ||||
|                                 href={permalink} | ||||
|                                 onClick={this.onPermalinkClicked} | ||||
|                                 onContextMenu={this.onTimestampContextMenu} | ||||
|                             > | ||||
|                                 {sender} | ||||
|                                 {timestamp} | ||||
|                             </a> | ||||
|                         </div>, | ||||
|                         <div className={lineClasses} key="mx_EventTile_line" onContextMenu={this.onContextMenu}> | ||||
|                             {this.renderContextMenu()} | ||||
|                             {renderTile( | ||||
|                                 TimelineRenderingType.Notification, | ||||
|                                 { | ||||
|                                     ...this.props, | ||||
| 
 | ||||
|                                     // overrides
 | ||||
|                                     ref: this.tile, | ||||
|                                     isSeeingThroughMessageHiddenForModeration, | ||||
| 
 | ||||
|                                     // appease TS
 | ||||
|                                     highlights: this.props.highlights, | ||||
|                                     highlightLink: this.props.highlightLink, | ||||
|                                     onHeightChanged: this.props.onHeightChanged, | ||||
|                                     permalinkCreator: this.props.permalinkCreator, | ||||
|                                 }, | ||||
|                                 this.context.showHiddenEvents, | ||||
|                             )} | ||||
|                         </div>, | ||||
|                     ], | ||||
|                 ); | ||||
|             } | ||||
|             case TimelineRenderingType.Thread: { | ||||
|                 return React.createElement( | ||||
|                     this.props.as || "li", | ||||
|  | @ -1289,8 +1240,8 @@ export class UnwrappedEventTile extends React.Component<EventTileProps, IState> | |||
|                                     // appease TS
 | ||||
|                                     highlights: this.props.highlights, | ||||
|                                     highlightLink: this.props.highlightLink, | ||||
|                                     onHeightChanged: this.props.onHeightChanged, | ||||
|                                     permalinkCreator: this.props.permalinkCreator, | ||||
|                                     onHeightChanged: () => this.props.onHeightChanged, | ||||
|                                     permalinkCreator: this.props.permalinkCreator!, | ||||
|                                 }, | ||||
|                                 this.context.showHiddenEvents, | ||||
|                             )} | ||||
|  | @ -1304,6 +1255,7 @@ export class UnwrappedEventTile extends React.Component<EventTileProps, IState> | |||
|                     ], | ||||
|                 ); | ||||
|             } | ||||
|             case TimelineRenderingType.Notification: | ||||
|             case TimelineRenderingType.ThreadsList: { | ||||
|                 const room = MatrixClientPeg.get().getRoom(this.props.mxEvent.getRoomId()); | ||||
|                 // tab-index=-1 to allow it to be focusable but do not add tab stop for it, primarily for screen readers
 | ||||
|  | @ -1326,20 +1278,48 @@ export class UnwrappedEventTile extends React.Component<EventTileProps, IState> | |||
|                         "onMouseEnter": () => this.setState({ hover: true }), | ||||
|                         "onMouseLeave": () => this.setState({ hover: false }), | ||||
|                         "onClick": (ev: MouseEvent) => { | ||||
|                             dis.dispatch<ShowThreadPayload>({ | ||||
|                                 action: Action.ShowThread, | ||||
|                                 rootEvent: this.props.mxEvent, | ||||
|                                 push: true, | ||||
|                             }); | ||||
|                             const target = ev.currentTarget as HTMLElement; | ||||
|                             const index = Array.from(target.parentElement.children).indexOf(target); | ||||
|                             PosthogTrackers.trackInteraction("WebThreadsPanelThreadItem", ev, index); | ||||
|                             let index = -1; | ||||
|                             if (target.parentElement) index = Array.from(target.parentElement.children).indexOf(target); | ||||
|                             switch (this.context.timelineRenderingType) { | ||||
|                                 case TimelineRenderingType.Notification: | ||||
|                                     this.viewInRoom(ev); | ||||
|                                     break; | ||||
|                                 case TimelineRenderingType.ThreadsList: | ||||
|                                     dis.dispatch<ShowThreadPayload>({ | ||||
|                                         action: Action.ShowThread, | ||||
|                                         rootEvent: this.props.mxEvent, | ||||
|                                         push: true, | ||||
|                                     }); | ||||
|                                     PosthogTrackers.trackInteraction("WebThreadsPanelThreadItem", ev, index ?? -1); | ||||
|                                     break; | ||||
|                             } | ||||
|                         }, | ||||
|                     }, | ||||
|                     <> | ||||
|                         {sender} | ||||
|                         {avatar} | ||||
|                         {timestamp} | ||||
|                         <div className="mx_EventTile_details"> | ||||
|                             {sender} | ||||
|                             {isRenderingNotification && room ? ( | ||||
|                                 <span className="mx_EventTile_truncated"> | ||||
|                                     {" "} | ||||
|                                     {_t( | ||||
|                                         " in <strong>%(room)s</strong>", | ||||
|                                         { room: room.name }, | ||||
|                                         { strong: (sub) => <strong>{sub}</strong> }, | ||||
|                                     )} | ||||
|                                 </span> | ||||
|                             ) : ( | ||||
|                                 "" | ||||
|                             )} | ||||
|                             {timestamp} | ||||
|                         </div> | ||||
|                         {isRenderingNotification && room ? ( | ||||
|                             <div className="mx_EventTile_avatar"> | ||||
|                                 <RoomAvatar room={room} width={28} height={28} /> | ||||
|                             </div> | ||||
|                         ) : ( | ||||
|                             avatar | ||||
|                         )} | ||||
|                         <div className={lineClasses} key="mx_EventTile_line"> | ||||
|                             <div className="mx_EventTile_body"> | ||||
|                                 {this.props.mxEvent.isRedacted() ? ( | ||||
|  | @ -1350,24 +1330,13 @@ export class UnwrappedEventTile extends React.Component<EventTileProps, IState> | |||
|                             </div> | ||||
|                             {this.renderThreadPanelSummary()} | ||||
|                         </div> | ||||
|                         <Toolbar className="mx_MessageActionBar" aria-label={_t("Message Actions")} aria-live="off"> | ||||
|                             <RovingAccessibleTooltipButton | ||||
|                                 className="mx_MessageActionBar_iconButton" | ||||
|                                 onClick={this.viewInRoom} | ||||
|                                 title={_t("View in room")} | ||||
|                                 key="view_in_room" | ||||
|                             > | ||||
|                                 <ViewInRoomIcon /> | ||||
|                             </RovingAccessibleTooltipButton> | ||||
|                             <RovingAccessibleTooltipButton | ||||
|                                 className="mx_MessageActionBar_iconButton" | ||||
|                                 onClick={this.copyLinkToThread} | ||||
|                                 title={_t("Copy link to thread")} | ||||
|                                 key="copy_link_to_thread" | ||||
|                             > | ||||
|                                 <LinkIcon /> | ||||
|                             </RovingAccessibleTooltipButton> | ||||
|                         </Toolbar> | ||||
|                         {this.context.timelineRenderingType === TimelineRenderingType.ThreadsList && ( | ||||
|                             <EventTileThreadToolbar | ||||
|                                 viewInRoom={this.viewInRoom} | ||||
|                                 copyLinkToThread={this.copyLinkToThread} | ||||
|                             /> | ||||
|                         )} | ||||
| 
 | ||||
|                         {msgOption} | ||||
|                         <UnreadNotificationBadge room={room} threadId={this.props.mxEvent.getId()} /> | ||||
|                     </>, | ||||
|  |  | |||
|  | @ -0,0 +1,53 @@ | |||
| /* | ||||
| Copyright 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 React from "react"; | ||||
| 
 | ||||
| import { RovingAccessibleTooltipButton } from "../../../../accessibility/RovingTabIndex"; | ||||
| import Toolbar from "../../../../accessibility/Toolbar"; | ||||
| import { _t } from "../../../../languageHandler"; | ||||
| import { Icon as LinkIcon } from "../../../../../res/img/element-icons/link.svg"; | ||||
| import { Icon as ViewInRoomIcon } from "../../../../../res/img/element-icons/view-in-room.svg"; | ||||
| import { ButtonEvent } from "../../elements/AccessibleButton"; | ||||
| 
 | ||||
| export function EventTileThreadToolbar({ | ||||
|     viewInRoom, | ||||
|     copyLinkToThread, | ||||
| }: { | ||||
|     viewInRoom: (evt: ButtonEvent) => void; | ||||
|     copyLinkToThread: (evt: ButtonEvent) => void; | ||||
| }) { | ||||
|     return ( | ||||
|         <Toolbar className="mx_MessageActionBar" aria-label={_t("Message Actions")} aria-live="off"> | ||||
|             <RovingAccessibleTooltipButton | ||||
|                 className="mx_MessageActionBar_iconButton" | ||||
|                 onClick={viewInRoom} | ||||
|                 title={_t("View in room")} | ||||
|                 key="view_in_room" | ||||
|             > | ||||
|                 <ViewInRoomIcon /> | ||||
|             </RovingAccessibleTooltipButton> | ||||
|             <RovingAccessibleTooltipButton | ||||
|                 className="mx_MessageActionBar_iconButton" | ||||
|                 onClick={copyLinkToThread} | ||||
|                 title={_t("Copy link to thread")} | ||||
|                 key="copy_link_to_thread" | ||||
|             > | ||||
|                 <LinkIcon /> | ||||
|             </RovingAccessibleTooltipButton> | ||||
|         </Toolbar> | ||||
|     ); | ||||
| } | ||||
|  | @ -1879,9 +1879,7 @@ | |||
|     "Mod": "Mod", | ||||
|     "From a thread": "From a thread", | ||||
|     "This event could not be displayed": "This event could not be displayed", | ||||
|     "Message Actions": "Message Actions", | ||||
|     "View in room": "View in room", | ||||
|     "Copy link to thread": "Copy link to thread", | ||||
|     " in <strong>%(room)s</strong>": " in <strong>%(room)s</strong>", | ||||
|     "Encrypted by an unverified session": "Encrypted by an unverified session", | ||||
|     "Unencrypted": "Unencrypted", | ||||
|     "Encrypted by a deleted session": "Encrypted by a deleted session", | ||||
|  | @ -2130,6 +2128,9 @@ | |||
|     "Italic": "Italic", | ||||
|     "Underline": "Underline", | ||||
|     "Code": "Code", | ||||
|     "Message Actions": "Message Actions", | ||||
|     "View in room": "View in room", | ||||
|     "Copy link to thread": "Copy link to thread", | ||||
|     "Error updating main address": "Error updating main address", | ||||
|     "There was an error updating the room's main address. It may not be allowed by the server or a temporary failure occurred.": "There was an error updating the room's main address. It may not be allowed by the server or a temporary failure occurred.", | ||||
|     "There was an error updating the room's alternative addresses. It may not be allowed by the server or a temporary failure occurred.": "There was an error updating the room's alternative addresses. It may not be allowed by the server or a temporary failure occurred.", | ||||
|  |  | |||
|  | @ -15,7 +15,6 @@ limitations under the License. | |||
| */ | ||||
| 
 | ||||
| import * as React from "react"; | ||||
| import { act, render, screen, waitFor } from "@testing-library/react"; | ||||
| import { EventType } from "matrix-js-sdk/src/@types/event"; | ||||
| import { MatrixClient, PendingEventOrdering } from "matrix-js-sdk/src/client"; | ||||
| import { MatrixEvent } from "matrix-js-sdk/src/models/event"; | ||||
|  | @ -23,6 +22,7 @@ import { NotificationCountType, Room } from "matrix-js-sdk/src/models/room"; | |||
| import { DeviceTrustLevel, UserTrustLevel } from "matrix-js-sdk/src/crypto/CrossSigning"; | ||||
| import { DeviceInfo } from "matrix-js-sdk/src/crypto/deviceinfo"; | ||||
| import { IEncryptedEventInfo } from "matrix-js-sdk/src/crypto/api"; | ||||
| import { render, waitFor, screen, act, fireEvent } from "@testing-library/react"; | ||||
| 
 | ||||
| import EventTile, { EventTileProps } from "../../../../src/components/views/rooms/EventTile"; | ||||
| import MatrixClientContext from "../../../../src/contexts/MatrixClientContext"; | ||||
|  | @ -31,6 +31,9 @@ import { MatrixClientPeg } from "../../../../src/MatrixClientPeg"; | |||
| import SettingsStore from "../../../../src/settings/SettingsStore"; | ||||
| import { getRoomContext, mkEncryptedEvent, mkEvent, mkMessage, stubClient } from "../../../test-utils"; | ||||
| import { mkThread } from "../../../test-utils/threads"; | ||||
| import DMRoomMap from "../../../../src/utils/DMRoomMap"; | ||||
| import dis from "../../../../src/dispatcher/dispatcher"; | ||||
| import { Action } from "../../../../src/dispatcher/actions"; | ||||
| 
 | ||||
| describe("EventTile", () => { | ||||
|     const ROOM_ID = "!roomId:example.org"; | ||||
|  | @ -159,6 +162,43 @@ describe("EventTile", () => { | |||
|         }); | ||||
|     }); | ||||
| 
 | ||||
|     describe("EventTile in the right panel", () => { | ||||
|         beforeAll(() => { | ||||
|             const dmRoomMap: DMRoomMap = { | ||||
|                 getUserIdForRoomId: jest.fn(), | ||||
|             } as unknown as DMRoomMap; | ||||
|             DMRoomMap.setShared(dmRoomMap); | ||||
|         }); | ||||
| 
 | ||||
|         it("renders the room name for notifications", () => { | ||||
|             const { container } = getComponent({}, TimelineRenderingType.Notification); | ||||
|             expect(container.getElementsByClassName("mx_EventTile_details")[0]).toHaveTextContent( | ||||
|                 "@alice:example.org in !roomId:example.org", | ||||
|             ); | ||||
|         }); | ||||
| 
 | ||||
|         it("renders the sender for the thread list", () => { | ||||
|             const { container } = getComponent({}, TimelineRenderingType.ThreadsList); | ||||
|             expect(container.getElementsByClassName("mx_EventTile_details")[0]).toHaveTextContent("@alice:example.org"); | ||||
|         }); | ||||
| 
 | ||||
|         it.each([ | ||||
|             [TimelineRenderingType.Notification, Action.ViewRoom], | ||||
|             [TimelineRenderingType.ThreadsList, Action.ShowThread], | ||||
|         ])("type %s dispatches %s", (renderingType, action) => { | ||||
|             jest.spyOn(dis, "dispatch"); | ||||
| 
 | ||||
|             const { container } = getComponent({}, renderingType); | ||||
| 
 | ||||
|             fireEvent.click(container.querySelector("li")!); | ||||
| 
 | ||||
|             expect(dis.dispatch).toHaveBeenCalledWith( | ||||
|                 expect.objectContaining({ | ||||
|                     action, | ||||
|                 }), | ||||
|             ); | ||||
|         }); | ||||
|     }); | ||||
|     describe("Event verification", () => { | ||||
|         // data for our stubbed getEventEncryptionInfo: a map from event id to result
 | ||||
|         const eventToEncryptionInfoMap = new Map<string, IEncryptedEventInfo>(); | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue
	
	 Germain
						Germain