diff --git a/res/css/_components.scss b/res/css/_components.scss index f0d78df5bc1..f318d88e3a6 100644 --- a/res/css/_components.scss +++ b/res/css/_components.scss @@ -192,6 +192,7 @@ @import "./views/messages/_CallEvent.scss"; @import "./views/messages/_CreateEvent.scss"; @import "./views/messages/_DateSeparator.scss"; +@import "./views/messages/_DisambiguatedProfile.scss"; @import "./views/messages/_EventTileBubble.scss"; @import "./views/messages/_HiddenBody.scss"; @import "./views/messages/_JumpToDatePicker.scss"; @@ -214,7 +215,6 @@ @import "./views/messages/_ReactionsRowButton.scss"; @import "./views/messages/_RedactedBody.scss"; @import "./views/messages/_RoomAvatarEvent.scss"; -@import "./views/messages/_SenderProfile.scss"; @import "./views/messages/_TextualEvent.scss"; @import "./views/messages/_UnknownBody.scss"; @import "./views/messages/_ViewSourceEvent.scss"; diff --git a/res/css/structures/_FilePanel.scss b/res/css/structures/_FilePanel.scss index c180a8a02dd..634ce9604cc 100644 --- a/res/css/structures/_FilePanel.scss +++ b/res/css/structures/_FilePanel.scss @@ -93,7 +93,7 @@ limitations under the License. text-decoration: none; } -.mx_FilePanel .mx_EventTile .mx_SenderProfile { +.mx_FilePanel .mx_EventTile .mx_DisambiguatedProfile { flex: 1 1 auto; line-height: initial; padding: 0px; diff --git a/res/css/structures/_NotificationPanel.scss b/res/css/structures/_NotificationPanel.scss index 68e1dd6a9a0..be953a750fc 100644 --- a/res/css/structures/_NotificationPanel.scss +++ b/res/css/structures/_NotificationPanel.scss @@ -77,7 +77,7 @@ limitations under the License. display: none; // we don't need this in this view } -.mx_NotificationPanel .mx_EventTile .mx_SenderProfile, +.mx_NotificationPanel .mx_EventTile .mx_DisambiguatedProfile, .mx_NotificationPanel .mx_EventTile .mx_MessageTimestamp { color: $primary-content; font-size: $font-12px; diff --git a/res/css/views/messages/_SenderProfile.scss b/res/css/views/messages/_DisambiguatedProfile.scss similarity index 60% rename from res/css/views/messages/_SenderProfile.scss rename to res/css/views/messages/_DisambiguatedProfile.scss index 08644b14e3f..c079fd48f67 100644 --- a/res/css/views/messages/_SenderProfile.scss +++ b/res/css/views/messages/_DisambiguatedProfile.scss @@ -1,5 +1,6 @@ /* Copyright 2015, 2016 OpenMarket Ltd +Copyright 2021 Šimon Brandner Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -14,13 +15,18 @@ See the License for the specific language governing permissions and limitations under the License. */ -.mx_SenderProfile_displayName { - font-weight: 600; -} +.mx_DisambiguatedProfile { + overflow: hidden; + text-overflow: ellipsis; + + .mx_DisambiguatedProfile_displayName { + font-weight: 600; + } -.mx_SenderProfile_mxid { - font-weight: 600; - font-size: 1.1rem; - margin-left: 5px; - opacity: 0.5; // Match mx_TextualEvent + .mx_DisambiguatedProfile_mxid { + font-weight: 600; + font-size: 1.1rem; + margin-left: 5px; + opacity: 0.5; // Match mx_TextualEvent + } } diff --git a/res/css/views/rooms/_EventTile.scss b/res/css/views/rooms/_EventTile.scss index afa05668ecf..89ad3b40a0e 100644 --- a/res/css/views/rooms/_EventTile.scss +++ b/res/css/views/rooms/_EventTile.scss @@ -126,7 +126,7 @@ $left-gutter: 64px; max-width: calc(100% - $left-gutter); } - .mx_SenderProfile .mx_Flair { + .mx_DisambiguatedProfile .mx_Flair { opacity: 0.7; margin-left: 5px; display: inline-block; @@ -141,6 +141,21 @@ $left-gutter: 64px; } } + .mx_DisambiguatedProfile { + color: $primary-content; + font-size: $font-14px; + display: inline-block; /* anti-zalgo, with overflow hidden */ + overflow: hidden; + cursor: pointer; + padding-bottom: 0px; + padding-top: 0px; + margin: 0px; + /* the next three lines, along with overflow hidden, truncate long display names */ + white-space: nowrap; + text-overflow: ellipsis; + max-width: calc(100% - $left-gutter); + } + &.mx_EventTile_isEditing .mx_MessageTimestamp { visibility: hidden; } diff --git a/res/css/views/rooms/_GroupLayout.scss b/res/css/views/rooms/_GroupLayout.scss index ebb7f99e45e..c054256b6a6 100644 --- a/res/css/views/rooms/_GroupLayout.scss +++ b/res/css/views/rooms/_GroupLayout.scss @@ -19,7 +19,7 @@ $left-gutter: 64px; .mx_GroupLayout { .mx_EventTile { - > .mx_SenderProfile { + > .mx_DisambiguatedProfile { line-height: $font-20px; margin-left: $left-gutter; } @@ -65,7 +65,7 @@ $left-gutter: 64px; } } - .mx_SenderProfile { + .mx_DisambiguatedProfile { font-size: $font-13px; } diff --git a/res/css/views/rooms/_IRCLayout.scss b/res/css/views/rooms/_IRCLayout.scss index 14b1c4ad60a..1189bbd05b2 100644 --- a/res/css/views/rooms/_IRCLayout.scss +++ b/res/css/views/rooms/_IRCLayout.scss @@ -173,7 +173,7 @@ $irc-line-height: $font-18px; border-left: 0; } - .mx_SenderProfile { + .mx_DisambiguatedProfile { width: var(--name-width); display: flex; order: 2; @@ -181,14 +181,14 @@ $irc-line-height: $font-18px; justify-content: flex-start; align-items: center; - > .mx_SenderProfile_displayName { + > .mx_DisambiguatedProfile_displayName { width: 100%; text-align: end; overflow: hidden; text-overflow: ellipsis; } - > .mx_SenderProfile_mxid { + > .mx_DisambiguatedProfile_mxid { visibility: collapse; // Override the inherited margin. margin-left: 0; @@ -196,11 +196,11 @@ $irc-line-height: $font-18px; } } - .mx_SenderProfile:hover { + .mx_DisambiguatedProfile:hover { overflow: visible; z-index: 10; - > .mx_SenderProfile_displayName { + > .mx_DisambiguatedProfile_displayName { overflow: visible; display: inline; background-color: $event-selected-color; @@ -208,7 +208,7 @@ $irc-line-height: $font-18px; padding-right: 8px; } - > .mx_SenderProfile_mxid { + > .mx_DisambiguatedProfile_mxid { visibility: visible; opacity: 1; background-color: $event-selected-color; @@ -217,7 +217,7 @@ $irc-line-height: $font-18px; .mx_ReplyChain { margin: 0; - .mx_SenderProfile { + .mx_DisambiguatedProfile { order: unset; max-width: unset; width: unset; diff --git a/src/components/views/messages/DisambiguatedProfile.tsx b/src/components/views/messages/DisambiguatedProfile.tsx new file mode 100644 index 00000000000..b5e64d999f0 --- /dev/null +++ b/src/components/views/messages/DisambiguatedProfile.tsx @@ -0,0 +1,71 @@ +/* +Copyright 2021 Šimon Brandner +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 { RoomMember } from 'matrix-js-sdk/src/models/room-member'; +import classNames from 'classnames'; + +import { getUserNameColorClass } from '../../../utils/FormattingUtils'; +import UserIdentifier from "../../../customisations/UserIdentifier"; + +interface IProps { + member?: RoomMember; + fallbackName: string; + flair?: JSX.Element; + onClick?(): void; + colored?: boolean; + emphasizeDisplayName?: boolean; +} + +export default class DisambiguatedProfile extends React.Component { + render() { + const { fallbackName, member, flair, colored, emphasizeDisplayName, onClick } = this.props; + const rawDisplayName = member?.rawDisplayName || fallbackName; + const mxid = member?.userId; + + let colorClass; + if (colored) { + colorClass = getUserNameColorClass(fallbackName); + } + + let mxidElement; + if (member?.disambiguate && mxid) { + mxidElement = ( + + { UserIdentifier.getDisplayUserIdentifier( + mxid, { withDisplayName: true, roomId: member.roomId }, + ) } + + ); + } + + const displayNameClasses = classNames({ + "mx_DisambiguatedProfile_displayName": emphasizeDisplayName, + [colorClass]: true, + }); + + return ( +
+ + { rawDisplayName } + + { mxidElement } + { flair } +
+ ); + } +} diff --git a/src/components/views/messages/SenderProfile.tsx b/src/components/views/messages/SenderProfile.tsx index 8bd9c430980..1f0c07a0770 100644 --- a/src/components/views/messages/SenderProfile.tsx +++ b/src/components/views/messages/SenderProfile.tsx @@ -21,10 +21,9 @@ import { RoomStateEvent } from "matrix-js-sdk/src/models/room-state"; import Flair from '../elements/Flair'; import FlairStore from '../../../stores/FlairStore'; -import { getUserNameColorClass } from '../../../utils/FormattingUtils'; import MatrixClientContext from "../../../contexts/MatrixClientContext"; import { replaceableComponent } from "../../../utils/replaceableComponent"; -import UserIdentifier from '../../../customisations/UserIdentifier'; +import DisambiguatedProfile from "./DisambiguatedProfile"; import RoomContext, { TimelineRenderingType } from '../../../contexts/RoomContext'; import SettingsStore from "../../../settings/SettingsStore"; import { MatrixClientPeg } from "../../../MatrixClientPeg"; @@ -106,9 +105,8 @@ export default class SenderProfile extends React.Component { } render() { - const { mxEvent } = this.props; - const colorClass = getUserNameColorClass(mxEvent.getSender()); - const { msgtype } = mxEvent.getContent(); + const { mxEvent, onClick } = this.props; + const msgtype = mxEvent.getContent().msgtype; let member = mxEvent.sender; if (SettingsStore.getValue("feature_use_only_current_profiles")) { @@ -118,10 +116,6 @@ export default class SenderProfile extends React.Component { } } - const disambiguate = member?.disambiguate || mxEvent.sender?.disambiguate; - const displayName = member?.rawDisplayName || mxEvent.getSender() || ""; - const mxid = member?.userId || mxEvent.getSender() || ""; - return { roomContext => { if (msgtype === MsgType.Emote && @@ -130,17 +124,6 @@ export default class SenderProfile extends React.Component { return null; // emote message must include the name so don't duplicate it } - let mxidElement; - if (disambiguate) { - mxidElement = ( - - { UserIdentifier.getDisplayUserIdentifier( - mxid, { withDisplayName: true, roomId: mxEvent.getRoomId() }, - ) } - - ); - } - let flair; if (this.props.enableFlair) { const displayedGroups = this.getDisplayedGroups( @@ -151,13 +134,14 @@ export default class SenderProfile extends React.Component { } return ( -
- - { displayName } - - { mxidElement } - { flair } -
+ ); } }
; diff --git a/src/components/views/rooms/EntityTile.tsx b/src/components/views/rooms/EntityTile.tsx index 8bd907f885e..1b459008de0 100644 --- a/src/components/views/rooms/EntityTile.tsx +++ b/src/components/views/rooms/EntityTile.tsx @@ -64,6 +64,7 @@ function presenceClassForMember(presenceState: string, lastActiveAgo: number, sh interface IProps { name?: string; + nameJSX?: JSX.Element; title?: string; avatarJsx?: JSX.Element; // className?: string; @@ -117,7 +118,7 @@ export default class EntityTile extends React.PureComponent { mainClassNames[presenceClass] = true; let nameEl; - const { name } = this.props; + const name = this.props.nameJSX || this.props.name; if (!this.props.suppressOnHover) { const activeAgo = this.props.presenceLastActiveAgo ? diff --git a/src/components/views/rooms/MemberTile.tsx b/src/components/views/rooms/MemberTile.tsx index 36d6555d210..24039adef20 100644 --- a/src/components/views/rooms/MemberTile.tsx +++ b/src/components/views/rooms/MemberTile.tsx @@ -33,6 +33,7 @@ import { Action } from "../../../dispatcher/actions"; import { replaceableComponent } from "../../../utils/replaceableComponent"; import EntityTile, { PowerStatus } from "./EntityTile"; import MemberAvatar from "./../avatars/MemberAvatar"; +import DisambiguatedProfile from "../messages/DisambiguatedProfile"; import UserIdentifierCustomisations from '../../../customisations/UserIdentifier'; interface IProps { @@ -258,6 +259,13 @@ export default class MemberTile extends React.Component { e2eStatus = this.state.e2eStatus; } + const nameJSX = ( + + ); + return ( { avatarJsx={av} title={this.getPowerLabel()} name={name} + nameJSX={nameJSX} powerStatus={powerStatus} showPresence={this.props.showPresence} subtextLabel={statusMessage} diff --git a/test/end-to-end-tests/src/usecases/timeline.ts b/test/end-to-end-tests/src/usecases/timeline.ts index e0b3710b423..d12ccf9e19d 100644 --- a/test/end-to-end-tests/src/usecases/timeline.ts +++ b/test/end-to-end-tests/src/usecases/timeline.ts @@ -132,7 +132,7 @@ function getAllEventTiles(session: ElementSession): Promise { } async function getMessageFromEventTile(eventTile: ElementHandle): Promise { - const senderElement = await eventTile.$(".mx_SenderProfile_displayName"); + const senderElement = await eventTile.$(".mx_DisambiguatedProfile_displayName"); const className: string = await (await eventTile.getProperty("className")).jsonValue(); const classNames = className.split(" "); const bodyElement = await eventTile.$(".mx_EventTile_body");