Improve pills (#6398)

pull/28788/head^2
Šimon Brandner 2022-05-05 11:13:09 +02:00 committed by GitHub
parent c79596cfe6
commit b5ac9493dd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 176 additions and 184 deletions

View File

@ -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";

View File

@ -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 {

View File

@ -0,0 +1,64 @@
/*
Copyright 2021 Šimon Brandner <simon.bra.ag@gmail.com>
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
}
}

View File

@ -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;
}

View File

@ -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 {

View File

@ -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

View File

@ -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;

View File

@ -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;

View File

@ -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);

View File

@ -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);
// ********************

View File

@ -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';
class Pill extends React.Component {
static roomNotifPos(text) {
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;
}
export default class Pill extends React.Component<IProps, IState> {
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
constructor(props: IProps) {
super(props);
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,
// 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<void> {
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 = <RoomAvatar room={room} width={16} height={16} aria-hidden="true" />;
}
}
pillClass = 'mx_RoomPill';
break;
pillClass = room?.isSpaceRoom() ? "mx_SpacePill" : "mx_RoomPill";
}
break;
}
const classes = classNames("mx_Pill", pillClass, {
"mx_UserPill_me": userId === MatrixClientPeg.get().getUserId(),
"mx_UserPill_selected": this.props.isSelected,
});
if (this.state.pillType) {
const { yOffset } = this.props;
let tip;
if (this.state.hover && resource) {
tip = <Tooltip label={resource} yOffset={yOffset} />;
tip = <Tooltip label={resource} alignment={Alignment.Right} />;
}
return <MatrixClientContext.Provider value={this._matrixClient}>
return <MatrixClientContext.Provider value={this.matrixClient}>
{ this.props.inMessage ?
<a
className={classes}
href={href}
onClick={onClick}
data-offset-key={this.props.offsetKey}
onMouseOver={this.onMouseOver}
onMouseLeave={this.onMouseLeave}
>
@ -260,7 +271,6 @@ class Pill extends React.Component {
</a> :
<span
className={classes}
data-offset-key={this.props.offsetKey}
onMouseOver={this.onMouseOver}
onMouseLeave={this.onMouseLeave}
>
@ -275,5 +285,3 @@ class Pill extends React.Component {
}
}
}
export default Pill;

View File

@ -31,7 +31,7 @@ import { getUserNameColorClass } from "../../../utils/FormattingUtils";
import { Action } from "../../../dispatcher/actions";
import Spinner from './Spinner';
import ReplyTile from "../rooms/ReplyTile";
import Pill from './Pill';
import Pill, { PillType } from './Pill';
import { ButtonEvent } from './AccessibleButton';
import { getParentEventId, shouldDisplayReply } from '../../../utils/Reply';
import RoomContext from "../../../contexts/RoomContext";
@ -223,7 +223,7 @@ export default class ReplyChain extends React.Component<IProps, IState> {
),
'pill': (
<Pill
type={Pill.TYPE_USER_MENTION}
type={PillType.UserMention}
room={room}
url={makeUserPermalink(ev.getSender())}
shouldShowPillAvatar={SettingsStore.getValue("Pill.shouldShowPillAvatar")}

View File

@ -21,7 +21,7 @@ import { Room } from "matrix-js-sdk/src/models/room";
import { logger } from "matrix-js-sdk/src/logger";
import { _t } from "../../../languageHandler";
import Pill from "../elements/Pill";
import Pill, { PillType } from "../elements/Pill";
import { makeUserPermalink } from "../../../utils/permalinks/Permalinks";
import BaseAvatar from "../avatars/BaseAvatar";
import SettingsStore from "../../../settings/SettingsStore";
@ -93,7 +93,7 @@ export default class BridgeTile extends React.PureComponent<IProps> {
if (content.creator) {
creator = <li>{ _t("This bridge was provisioned by <user />.", {}, {
user: () => <Pill
type={Pill.TYPE_USER_MENTION}
type={PillType.UserMention}
room={this.props.room}
url={makeUserPermalink(content.creator)}
shouldShowPillAvatar={SettingsStore.getValue("Pill.shouldShowPillAvatar")}
@ -103,7 +103,7 @@ export default class BridgeTile extends React.PureComponent<IProps> {
const bot = <li>{ _t("This bridge is managed by <user />.", {}, {
user: () => <Pill
type={Pill.TYPE_USER_MENTION}
type={PillType.UserMention}
room={this.props.room}
url={makeUserPermalink(content.bridgebot)}
shouldShowPillAvatar={SettingsStore.getValue("Pill.shouldShowPillAvatar")}

View File

@ -423,7 +423,7 @@ class RoomPillPart extends PillPart {
}
protected get className() {
return "mx_RoomPill mx_Pill";
return "mx_Pill " + (this.room.isSpaceRoom() ? "mx_SpacePill" : "mx_RoomPill");
}
}

View File

@ -21,7 +21,7 @@ import { MatrixEvent } from "matrix-js-sdk/src/models/event";
import { MatrixClientPeg } from '../MatrixClientPeg';
import SettingsStore from "../settings/SettingsStore";
import Pill from "../components/views/elements/Pill";
import Pill, { PillType } from "../components/views/elements/Pill";
import { parsePermalink } from "./permalinks/Permalinks";
/**
@ -113,7 +113,7 @@ export function pillifyLinks(nodes: ArrayLike<Element>, mxEvent: MatrixEvent, pi
const pillContainer = document.createElement('span');
const pill = <Pill
type={Pill.TYPE_AT_ROOM_MENTION}
type={PillType.AtRoomMention}
inMessage={true}
room={room}
shouldShowPillAvatar={shouldShowPillAvatar}