diff --git a/res/css/_components.scss b/res/css/_components.scss index c77b5ea7069..0d04789f313 100644 --- a/res/css/_components.scss +++ b/res/css/_components.scss @@ -163,6 +163,7 @@ @import "./views/elements/_InviteReason.scss"; @import "./views/elements/_ManageIntegsButton.scss"; @import "./views/elements/_MiniAvatarUploader.scss"; +@import "./views/elements/_Pill.scss"; @import "./views/elements/_PowerSelector.scss"; @import "./views/elements/_ProgressBar.scss"; @import "./views/elements/_QRCode.scss"; diff --git a/res/css/structures/_RoomDirectory.scss b/res/css/structures/_RoomDirectory.scss index 0137db7ebf2..2b8def01d03 100644 --- a/res/css/structures/_RoomDirectory.scss +++ b/res/css/structures/_RoomDirectory.scss @@ -155,7 +155,7 @@ limitations under the License. line-height: $font-20px; padding: 0 5px; color: $accent-fg-color; - background-color: $rte-room-pill-color; + background-color: $pill-bg-color; } .mx_RoomDirectory_topic { diff --git a/res/css/views/elements/_Pill.scss b/res/css/views/elements/_Pill.scss new file mode 100644 index 00000000000..407747bffdd --- /dev/null +++ b/res/css/views/elements/_Pill.scss @@ -0,0 +1,64 @@ +/* +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. +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_Pill { + padding: $font-1px 0.4em $font-1px 0; + line-height: $font-17px; + border-radius: $font-16px; + vertical-align: text-top; + display: inline-flex; + align-items: center; + + cursor: pointer; + + color: $accent-fg-color !important; // To override .markdown-body + background-color: $pill-bg-color !important; // To override .markdown-body + + &.mx_UserPill_me, + &.mx_AtRoomPill { + background-color: $alert !important; // To override .markdown-body + } + + &:hover { + background-color: $pill-hover-bg-color !important; // To override .markdown-body + } + + &.mx_UserPill_me:hover { + background-color: #ff6b75 !important; // To override .markdown-body | same on both themes + } + + // We don't want to indicate clickability + &.mx_AtRoomPill:hover { + background-color: $alert !important; // To override .markdown-body + cursor: unset; + } + + .mx_BaseAvatar { + position: relative; + display: inline-flex; + align-items: center; + border-radius: 10rem; + margin-right: 0.24rem; + } + + a& { + text-overflow: ellipsis; + white-space: nowrap; + overflow: hidden; + max-width: calc(100% - 1ch); + text-decoration: none !important; // To override .markdown-body + } +} diff --git a/res/css/views/elements/_RichText.scss b/res/css/views/elements/_RichText.scss index 4615f5da23f..a40eb87c7f3 100644 --- a/res/css/views/elements/_RichText.scss +++ b/res/css/views/elements/_RichText.scss @@ -2,86 +2,6 @@ // naming scheme; it's completely unclear where or how they're being used // --Matthew -.mx_UserPill, -.mx_RoomPill, -.mx_AtRoomPill { - display: inline-flex; - align-items: center; - vertical-align: middle; - border-radius: $font-16px; - line-height: $font-15px; - padding-left: 0; -} - -a.mx_Pill { - text-overflow: ellipsis; - white-space: nowrap; - overflow: hidden; - max-width: 100%; -} - -.mx_Pill { - padding: $font-1px; - padding-right: 0.4em; - vertical-align: text-top; - line-height: $font-17px; -} - -/* More specific to override `.markdown-body a` text-decoration */ -.mx_EventTile_content .markdown-body a.mx_Pill { - text-decoration: none; -} - -/* More specific to override `.markdown-body a` color */ -.mx_EventTile_content .markdown-body a.mx_UserPill, -.mx_UserPill { - color: $primary-content; - background-color: $other-user-pill-bg-color; -} - -.mx_UserPill_selected { - background-color: $accent !important; -} - -/* More specific to override `.markdown-body a` color */ -.mx_EventTile_highlight .mx_EventTile_content .markdown-body a.mx_UserPill_me, -.mx_EventTile_content .markdown-body a.mx_AtRoomPill, -.mx_EventTile_content .mx_AtRoomPill, -.mx_MessageComposer_input .mx_AtRoomPill { - color: $accent-fg-color; - background-color: $alert; -} - -/* More specific to override `.markdown-body a` color */ -.mx_EventTile_content .markdown-body a.mx_RoomPill, -.mx_RoomPill { - color: $accent-fg-color; - background-color: $rte-room-pill-color; -} - -.mx_EventTile_body .mx_UserPill, -.mx_EventTile_body .mx_RoomPill { - cursor: pointer; -} - -.mx_UserPill .mx_BaseAvatar, -.mx_RoomPill .mx_BaseAvatar, -.mx_AtRoomPill .mx_BaseAvatar { - position: relative; - display: inline-flex; - align-items: center; - border-radius: 10rem; - margin-right: 0.24rem; - pointer-events: none; -} - -.mx_Emoji { - // Should be 1.8rem for our default 1.4rem message bodies, - // and scale with the size of the surrounding text - font-size: calc(18 / 14 * 1em); - vertical-align: bottom; -} - .mx_Markdown_BOLD { font-weight: bold; } diff --git a/res/css/views/rooms/_BasicMessageComposer.scss b/res/css/views/rooms/_BasicMessageComposer.scss index 73ff9f048b2..c89fa6028b6 100644 --- a/res/css/views/rooms/_BasicMessageComposer.scss +++ b/res/css/views/rooms/_BasicMessageComposer.scss @@ -51,9 +51,15 @@ limitations under the License. } &.mx_BasicMessageComposer_input_shouldShowPillAvatar { - span.mx_UserPill, span.mx_RoomPill { - position: relative; + span.mx_UserPill, span.mx_RoomPill, span.mx_SpacePill { user-select: all; + position: relative; + cursor: unset; // We don't want indicate clickability + + &:hover { + // We don't want indicate clickability | To override the overriding of .markdown-body + background-color: $pill-bg-color !important; + } // avatar psuedo element &::before { @@ -72,14 +78,6 @@ limitations under the License. font-size: $font-10-4px; } } - - span.mx_UserPill { - cursor: pointer; - } - - span.mx_RoomPill { - cursor: default; - } } &.mx_BasicMessageComposer_input_disabled { diff --git a/res/themes/dark/css/_dark.scss b/res/themes/dark/css/_dark.scss index ce187345942..4c5d50f9e1f 100644 --- a/res/themes/dark/css/_dark.scss +++ b/res/themes/dark/css/_dark.scss @@ -94,8 +94,8 @@ $roomheader-addroom-fg-color: $primary-content; // Rich-text-editor // ******************** -$rte-room-pill-color: $room-highlight-color; -$other-user-pill-bg-color: $room-highlight-color; +$pill-bg-color: $room-highlight-color; +$pill-hover-bg-color: #545a66; // ******************** // Inputs diff --git a/res/themes/legacy-dark/css/_legacy-dark.scss b/res/themes/legacy-dark/css/_legacy-dark.scss index 9ee07c09685..8997538e0ab 100644 --- a/res/themes/legacy-dark/css/_legacy-dark.scss +++ b/res/themes/legacy-dark/css/_legacy-dark.scss @@ -27,8 +27,8 @@ $light-fg-color: $header-panel-text-secondary-color; // used for focusing form controls $focus-bg-color: $room-highlight-color; -$other-user-pill-bg-color: $room-highlight-color; -$rte-room-pill-color: $room-highlight-color; +$pill-bg-color: $room-highlight-color; +$pill-hover-bg-color: #545a66; // informational plinth $info-plinth-bg-color: $header-panel-bg-color; diff --git a/res/themes/legacy-light/css/_legacy-light.scss b/res/themes/legacy-light/css/_legacy-light.scss index e1c475fc636..49f690d6a04 100644 --- a/res/themes/legacy-light/css/_legacy-light.scss +++ b/res/themes/legacy-light/css/_legacy-light.scss @@ -37,8 +37,6 @@ $selection-fg-color: $primary-bg-color; $focus-brightness: 105%; -$other-user-pill-bg-color: rgba(0, 0, 0, 0.1); - // informational plinth $info-plinth-bg-color: #f7f7f7; $info-plinth-fg-color: #888; @@ -117,10 +115,12 @@ $settings-subsection-fg-color: #61708b; $rte-bg-color: #e9e9e9; $rte-code-bg-color: rgba(0, 0, 0, 0.04); -$rte-room-pill-color: #aaa; $header-panel-text-primary-color: #91a1c0; +$pill-bg-color: #aaa; +$pill-hover-bg-color: #ccc; + $topleftmenu-color: #212121; $roomheader-bg-color: $primary-bg-color; $roomheader-addroom-bg-color: #91a1c0; diff --git a/res/themes/light-custom/css/_custom.scss b/res/themes/light-custom/css/_custom.scss index e2d4fef8fe1..54d3dfd15e7 100644 --- a/res/themes/light-custom/css/_custom.scss +++ b/res/themes/light-custom/css/_custom.scss @@ -142,5 +142,6 @@ $eventbubble-reply-color: var(--eventbubble-reply-color, $eventbubble-reply-colo $reaction-row-button-selected-bg-color: var(--reaction-row-button-selected-bg-color, $reaction-row-button-selected-bg-color); $menu-selected-color: var(--menu-selected-color, $menu-selected-color); -$other-user-pill-bg-color: var(--other-user-pill-bg-color, $other-user-pill-bg-color); +$pill-bg-color: var(--other-user-pill-bg-color, $pill-bg-color); +$pill-hover-bg-color: var(--other-user-pill-bg-color, $pill-hover-bg-color); $icon-button-color: var(--icon-button-color, $icon-button-color); diff --git a/res/themes/light/css/_light.scss b/res/themes/light/css/_light.scss index 3d09e400819..bd7194e5fb4 100644 --- a/res/themes/light/css/_light.scss +++ b/res/themes/light/css/_light.scss @@ -147,8 +147,8 @@ $roomheader-addroom-fg-color: #5c6470; // Rich-text-editor // ******************** -$rte-room-pill-color: #aaa; -$other-user-pill-bg-color: rgba(0, 0, 0, 0.1); +$pill-bg-color: #aaa; +$pill-hover-bg-color: #ccc; $rte-bg-color: #e9e9e9; $rte-code-bg-color: rgba(0, 0, 0, 0.04); // ******************** diff --git a/src/components/views/elements/Pill.js b/src/components/views/elements/Pill.tsx similarity index 72% rename from src/components/views/elements/Pill.js rename to src/components/views/elements/Pill.tsx index 7d5a9973c7f..eb88a2cd467 100644 --- a/src/components/views/elements/Pill.js +++ b/src/components/views/elements/Pill.tsx @@ -13,67 +13,82 @@ 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 classNames from 'classnames'; import { Room } from 'matrix-js-sdk/src/models/room'; import { RoomMember } from 'matrix-js-sdk/src/models/room-member'; -import PropTypes from 'prop-types'; import { logger } from "matrix-js-sdk/src/logger"; +import { MatrixClient } from 'matrix-js-sdk/src/client'; import dis from '../../../dispatcher/dispatcher'; import { MatrixClientPeg } from '../../../MatrixClientPeg'; import { getPrimaryPermalinkEntity, parsePermalink } from "../../../utils/permalinks/Permalinks"; import MatrixClientContext from "../../../contexts/MatrixClientContext"; import { Action } from "../../../dispatcher/actions"; -import Tooltip from './Tooltip'; -import RoomAvatar from "../avatars/RoomAvatar"; -import MemberAvatar from "../avatars/MemberAvatar"; +import Tooltip, { Alignment } from './Tooltip'; +import RoomAvatar from '../avatars/RoomAvatar'; +import MemberAvatar from '../avatars/MemberAvatar'; + +export enum PillType { + UserMention = 'TYPE_USER_MENTION', + RoomMention = 'TYPE_ROOM_MENTION', + AtRoomMention = 'TYPE_AT_ROOM_MENTION', // '@room' mention +} + +interface IProps { + // The Type of this Pill. If url is given, this is auto-detected. + type?: PillType; + // The URL to pillify (no validation is done) + url?: string; + // Whether the pill is in a message + inMessage?: boolean; + // The room in which this pill is being rendered + room?: Room; + // Whether to include an avatar in the pill + shouldShowPillAvatar?: boolean; +} + +interface IState { + // ID/alias of the room/user + resourceId: string; + // Type of pill + pillType: string; + // The member related to the user pill + member?: RoomMember; + // The room related to the room pill + room?: Room; + // Is the user hovering the pill + hover: boolean; +} -class Pill extends React.Component { - static roomNotifPos(text) { +export default class Pill extends React.Component { + private unmounted = true; + private matrixClient: MatrixClient; + + public static roomNotifPos(text: string): number { return text.indexOf("@room"); } - static roomNotifLen() { + public static roomNotifLen(): number { return "@room".length; } - static TYPE_USER_MENTION = 'TYPE_USER_MENTION'; - static TYPE_ROOM_MENTION = 'TYPE_ROOM_MENTION'; - static TYPE_AT_ROOM_MENTION = 'TYPE_AT_ROOM_MENTION'; // '@room' mention - - static propTypes = { - // The Type of this Pill. If url is given, this is auto-detected. - type: PropTypes.string, - // The URL to pillify (no validation is done) - url: PropTypes.string, - // Whether the pill is in a message - inMessage: PropTypes.bool, - // The room in which this pill is being rendered - room: PropTypes.instanceOf(Room), - // Whether to include an avatar in the pill - shouldShowPillAvatar: PropTypes.bool, - // Whether to render this pill as if it were highlit by a selection - isSelected: PropTypes.bool, - }; - - state = { - // ID/alias of the room/user - resourceId: null, - // Type of pill - pillType: null, + constructor(props: IProps) { + super(props); - // The member related to the user pill - member: null, - // The room related to the room pill - room: null, - // Is the user hovering the pill - hover: false, - }; + this.state = { + resourceId: null, + pillType: null, + member: null, + room: null, + hover: false, + }; + } // TODO: [REACT-WARNING] Replace with appropriate lifecycle event - // eslint-disable-next-line camelcase - async UNSAFE_componentWillReceiveProps(nextProps) { + // eslint-disable-next-line camelcase, @typescript-eslint/naming-convention + public async UNSAFE_componentWillReceiveProps(nextProps: IProps): Promise { let resourceId; let prefix; @@ -89,28 +104,28 @@ class Pill extends React.Component { } const pillType = this.props.type || { - '@': Pill.TYPE_USER_MENTION, - '#': Pill.TYPE_ROOM_MENTION, - '!': Pill.TYPE_ROOM_MENTION, + '@': PillType.UserMention, + '#': PillType.RoomMention, + '!': PillType.RoomMention, }[prefix]; let member; let room; switch (pillType) { - case Pill.TYPE_AT_ROOM_MENTION: { + case PillType.AtRoomMention: { room = nextProps.room; } break; - case Pill.TYPE_USER_MENTION: { + case PillType.UserMention: { const localMember = nextProps.room ? nextProps.room.getMember(resourceId) : undefined; member = localMember; if (!localMember) { member = new RoomMember(null, resourceId); this.doProfileLookup(resourceId, member); } - break; } - case Pill.TYPE_ROOM_MENTION: { + break; + case PillType.RoomMention: { const localRoom = resourceId[0] === '#' ? MatrixClientPeg.get().getRooms().find((r) => { return r.getCanonicalAlias() === resourceId || @@ -122,39 +137,39 @@ class Pill extends React.Component { // a room avatar and name. // this.doRoomProfileLookup(resourceId, member); } - break; } + break; } this.setState({ resourceId, pillType, member, room }); } - componentDidMount() { - this._unmounted = false; - this._matrixClient = MatrixClientPeg.get(); + public componentDidMount(): void { + this.unmounted = false; + this.matrixClient = MatrixClientPeg.get(); // eslint-disable-next-line new-cap this.UNSAFE_componentWillReceiveProps(this.props); // HACK: We shouldn't be calling lifecycle functions ourselves. } - componentWillUnmount() { - this._unmounted = true; + public componentWillUnmount(): void { + this.unmounted = true; } - onMouseOver = () => { + private onMouseOver = (): void => { this.setState({ hover: true, }); }; - onMouseLeave = () => { + private onMouseLeave = (): void => { this.setState({ hover: false, }); }; - doProfileLookup(userId, member) { + private doProfileLookup(userId: string, member): void { MatrixClientPeg.get().getProfileInfo(userId).then((resp) => { - if (this._unmounted) { + if (this.unmounted) { return; } member.name = resp.displayname; @@ -173,7 +188,7 @@ class Pill extends React.Component { }); } - onUserPillClicked = (e) => { + private onUserPillClicked = (e): void => { e.preventDefault(); dis.dispatch({ action: Action.ViewUser, @@ -181,7 +196,7 @@ class Pill extends React.Component { }); }; - render() { + public render(): JSX.Element { const resource = this.state.resourceId; let avatar = null; @@ -191,7 +206,7 @@ class Pill extends React.Component { let href = this.props.url; let onClick; switch (this.state.pillType) { - case Pill.TYPE_AT_ROOM_MENTION: { + case PillType.AtRoomMention: { const room = this.props.room; if (room) { linkText = "@room"; @@ -200,9 +215,9 @@ class Pill extends React.Component { } pillClass = 'mx_AtRoomPill'; } - break; } - case Pill.TYPE_USER_MENTION: { + break; + case PillType.UserMention: { // If this user is not a member of this room, default to the empty member const member = this.state.member; if (member) { @@ -216,9 +231,9 @@ class Pill extends React.Component { href = null; onClick = this.onUserPillClicked; } - break; } - case Pill.TYPE_ROOM_MENTION: { + break; + case PillType.RoomMention: { const room = this.state.room; if (room) { linkText = room.name || resource; @@ -226,31 +241,27 @@ class Pill extends React.Component { avatar =