feat: overwrite room name displays

pull/26786/head
Badi Ifaoui 2023-11-29 15:19:57 +01:00 committed by Badi Ifaoui
parent 361fb2ebba
commit ba15159b15
8 changed files with 173 additions and 78 deletions

View File

@ -5,5 +5,7 @@
"src/components/views/rooms/Autocomplete.tsx": "src/components/views/rooms/Autocomplete.tsx",
"src/components/views/rooms/RoomTile.tsx": "src/components/views/rooms/RoomTile.tsx",
"src/components/views/elements/RoomName.tsx": "src/components/views/elements/RoomName.tsx",
"src/editor/commands.tsx": "src/editor/commands.tsx"
"src/components/views/dialogs/spotlight/PublicRoomResultDetails.tsx": "src/components/views/dialogs/spotlight/PublicRoomResultDetails.tsx",
"src/editor/commands.tsx": "src/editor/commands.tsx",
"src/hooks/useRoomName.ts": "src/hooks/useRoomName.ts"
}

View File

@ -1,9 +1,10 @@
.sh_RoomTokenGatedRoom {
align-items: center;
display: flex;
}
.sh_RoomTokenGatedRoomIcon {
width: 16px;
height: 16px;
margin-right: 4px;
}
.mx_RoomTile .mx_RoomTile_titleContainer .mx_RoomTile_title {
align-items: center;
display: flex;
}

View File

@ -0,0 +1,71 @@
/*
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 { IPublicRoomsChunkRoom } from "matrix-js-sdk/src/matrix";
import { linkifyAndSanitizeHtml } from "matrix-react-sdk/src/HtmlUtils";
import { _t } from "matrix-react-sdk/src/languageHandler";
import React from "react";
import RoomName from "../../elements/RoomName";
const MAX_NAME_LENGTH = 80;
const MAX_TOPIC_LENGTH = 800;
interface Props {
room: IPublicRoomsChunkRoom;
labelId: string;
descriptionId: string;
detailsId: string;
}
export function PublicRoomResultDetails({ room, labelId, descriptionId, detailsId }: Props): JSX.Element {
let topic = room.topic || "";
// Additional truncation based on line numbers is done via CSS,
// but to ensure that the DOM is not polluted with a huge string
// we give it a hard limit before rendering.
if (topic.length > MAX_TOPIC_LENGTH) {
topic = `${topic.substring(0, MAX_TOPIC_LENGTH)}...`;
}
return (
<div className="mx_SpotlightDialog_result_publicRoomDetails">
<div className="mx_SpotlightDialog_result_publicRoomHeader">
<span id={labelId} className="mx_SpotlightDialog_result_publicRoomName">
<RoomName room={room} maxLength={MAX_NAME_LENGTH} />
</span>
<span id={descriptionId} className="mx_SpotlightDialog_result_publicRoomAlias">
{room.canonical_alias ?? room.room_id}
</span>
</div>
<div id={detailsId} className="mx_SpotlightDialog_result_publicRoomDescription">
<span className="mx_SpotlightDialog_result_publicRoomMemberCount">
{_t("spotlight_dialog|count_of_members", {
count: room.num_joined_members,
})}
</span>
{topic && (
<>
&nbsp;·&nbsp;
<span
className="mx_SpotlightDialog_result_publicRoomTopic"
dangerouslySetInnerHTML={{ __html: linkifyAndSanitizeHtml(topic) }}
/>
</>
)}
</div>
</div>
);
}

View File

@ -14,31 +14,44 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import React, { useEffect, useState } from "react";
import { Room, RoomEvent } from "matrix-js-sdk/src/matrix";
import { useTypedEventEmitter } from "matrix-react-sdk/src/hooks/useEventEmitter";
import { IPublicRoomsChunkRoom, Room } from "matrix-js-sdk/src/matrix";
import React, { useCallback, useMemo } from "react";
import { getRoomName } from "../../../hooks/useTokengatedRoom";
import { Icon as TokenGatedRoomIcon } from "../../../../res/themes/superhero/img/icons/tokengated-room.svg";
import { isTokenGatedRoom, useRoomName } from "../../../hooks/useRoomName";
interface IProps {
room?: Room;
children?(name: string): JSX.Element;
room?: Room | IPublicRoomsChunkRoom;
children?(name: JSX.Element): JSX.Element;
maxLength?: number;
}
/**
* @deprecated use `useRoomName.ts` instead
*/
const RoomName = ({ room, children }: IProps): JSX.Element => {
const [name, setName] = useState(getRoomName(room));
useTypedEventEmitter(room, RoomEvent.Name, () => {
setName(getRoomName(room));
});
useEffect(() => {
setName(getRoomName(room));
export const RoomName = ({ room, children, maxLength }: IProps): JSX.Element => {
const roomName = useRoomName(room);
const isVerifiedRoom = useMemo(() => {
return isTokenGatedRoom(room);
}, [room]);
if (children) return children(name ?? "");
return <>{name || ""}</>;
const truncatedRoomName = useMemo(() => {
if (maxLength && roomName.length > maxLength) {
return `${roomName.substring(0, maxLength)}...`;
}
return roomName;
}, [roomName, maxLength]);
const renderRoomName = useCallback(
() => (
<span className="sh_RoomTokenGatedRoom">
{isVerifiedRoom && <TokenGatedRoomIcon className="sh_RoomTokenGatedRoomIcon" />}
<span dir="auto">{truncatedRoomName}</span>
</span>
),
[truncatedRoomName, isVerifiedRoom],
);
if (children) return children(renderRoomName());
return renderRoomName();
};
export default RoomName;

View File

@ -1,18 +0,0 @@
import React from "react";
import { Room } from "matrix-js-sdk/src/matrix";
import { Icon as TokenGatedRoomIcon } from "../../../../res/themes/superhero/img/icons/tokengated-room.svg";
import { useTokenGatedRoom } from "../../../hooks/useTokengatedRoom";
export interface CustomRoomNameProps {
room: Room;
}
export const CustomRoomName: React.FC<CustomRoomNameProps> = ({ room }) => {
const { roomName, isVerifiedRoom } = useTokenGatedRoom(room);
return (
<>
{isVerifiedRoom && <TokenGatedRoomIcon className="sh_RoomTokenGatedRoomIcon" />}
<span dir="auto">{roomName}</span>
</>
);
};

View File

@ -17,7 +17,7 @@ limitations under the License.
import classNames from "classnames";
import { Room, RoomEvent } from "matrix-js-sdk/src/matrix";
import React, { createRef } from "react";
import React, { ReactElement, createRef } from "react";
import { getKeyBindingsManager } from "matrix-react-sdk/src/KeyBindingsManager";
import { MatrixClientPeg } from "matrix-react-sdk/src/MatrixClientPeg";
import PosthogTrackers from "matrix-react-sdk/src/PosthogTrackers";
@ -43,7 +43,6 @@ import defaultDispatcher from "matrix-react-sdk/src/dispatcher/dispatcher";
import { ActionPayload } from "matrix-react-sdk/src/dispatcher/payloads";
import { ViewRoomPayload } from "matrix-react-sdk/src/dispatcher/payloads/ViewRoomPayload";
import { _t } from "matrix-react-sdk/src/languageHandler";
import type { Call } from "matrix-react-sdk/src/models/Call";
import SettingsStore from "matrix-react-sdk/src/settings/SettingsStore";
import { UIComponent } from "matrix-react-sdk/src/settings/UIFeature";
import { CallStore, CallStoreEvent } from "matrix-react-sdk/src/stores/CallStore";
@ -59,8 +58,10 @@ import { MessagePreview, MessagePreviewStore } from "matrix-react-sdk/src/stores
import { DefaultTagID, TagID } from "matrix-react-sdk/src/stores/room-list/models";
import { isKnockDenied } from "matrix-react-sdk/src/utils/membership";
import { useHasRoomLiveVoiceBroadcast } from "matrix-react-sdk/src/voice-broadcast";
import { CustomRoomName } from "./CustomRoomName";
import { getRoomName } from "../../../hooks/useTokengatedRoom";
import type { Call } from "matrix-react-sdk/src/models/Call";
import { RoomName } from "../elements/RoomName";
import { getRoomName } from "../../../hooks/useRoomName";
interface Props {
room: Room;
@ -358,16 +359,16 @@ export class RoomTile extends React.PureComponent<ClassProps, State> {
{...contextMenuBelow(this.state.generalMenuPosition)}
onFinished={this.onCloseGeneralMenu}
room={this.props.room}
onPostFavoriteClick={(ev: ButtonEvent) =>
onPostFavoriteClick={(ev: ButtonEvent): void =>
PosthogTrackers.trackInteraction("WebRoomListRoomTileContextMenuFavouriteToggle", ev)
}
onPostInviteClick={(ev: ButtonEvent) =>
onPostInviteClick={(ev: ButtonEvent): void =>
PosthogTrackers.trackInteraction("WebRoomListRoomTileContextMenuInviteItem", ev)
}
onPostSettingsClick={(ev: ButtonEvent) =>
onPostSettingsClick={(ev: ButtonEvent): void =>
PosthogTrackers.trackInteraction("WebRoomListRoomTileContextMenuSettingsItem", ev)
}
onPostLeaveClick={(ev: ButtonEvent) =>
onPostLeaveClick={(ev: ButtonEvent): void =>
PosthogTrackers.trackInteraction("WebRoomListRoomTileContextMenuLeaveItem", ev)
}
/>
@ -435,7 +436,7 @@ export class RoomTile extends React.PureComponent<ClassProps, State> {
const titleContainer = this.props.isMinimized ? null : (
<div className="mx_RoomTile_titleContainer">
<div title={name} className={titleClasses} tabIndex={-1}>
<CustomRoomName room={this.props.room} />
<RoomName room={this.props.room} />
</div>
{subtitle}
</div>
@ -478,7 +479,7 @@ export class RoomTile extends React.PureComponent<ClassProps, State> {
return (
<React.Fragment>
<RovingTabIndexWrapper inputRef={this.roomTileRef}>
{({ onFocus, isActive, ref }) => (
{({ onFocus, isActive, ref }): ReactElement => (
<Button
{...props}
onFocus={onFocus}

52
src/hooks/useRoomName.ts Normal file
View File

@ -0,0 +1,52 @@
import { IPublicRoomsChunkRoom, Room } from "matrix-js-sdk/src/matrix";
import { getDisplayAliasForAliasSet } from "matrix-react-sdk/src/Rooms";
import { _t } from "matrix-react-sdk/src/languageHandler";
import { IOOBData } from "matrix-react-sdk/src/stores/ThreepidInviteStore";
import { useMemo } from "react";
/**
* Determines the room name from a combination of the room model and potential
* @param room - The room model
* @param oobData - out-of-band information about the room
* @returns {string} the room name
*/
export function getRoomName(room?: Room | IPublicRoomsChunkRoom, oobName?: IOOBData): string {
const roomName =
room?.name ||
oobName?.name ||
getDisplayAliasForAliasSet(
(room as IPublicRoomsChunkRoom)?.canonical_alias ?? "",
(room as IPublicRoomsChunkRoom)?.aliases ?? [],
) ||
_t("common|unnamed_room");
return (roomName || "")
.replace(":", ":\u200b") // add a zero-width space to allow linewrapping after the colon (matrix defaults)
.replace("[TG]", "");
}
/**
* Determines if a room is a token gated room
* @param room - The room model
* @returns {boolean} true if the room is token gated
*/
export function isTokenGatedRoom(room?: Room | IPublicRoomsChunkRoom): boolean {
return !!room?.name?.includes("[TG]");
}
/**
* Determines the room name from a combination of the room model and potential
* out-of-band information
* @param room - The room model
* @param oobData - out-of-band information about the room
* @returns {string} the room name
*
* TODO: check if useTypedEventEmitter is needed
*/
export function useRoomName(room?: Room | IPublicRoomsChunkRoom, oobData?: IOOBData): string {
const name = useMemo(() => {
return getRoomName(room, oobData);
}, [room, oobData]);
return name;
}

View File

@ -1,27 +0,0 @@
import { Room } from "matrix-js-sdk/src/matrix";
import { useMemo } from "react";
export function getRoomName(room?: Room): string {
return (room?.name || "")
.replace(":", ":\u200b") // add a zero-width space to allow linewrapping after the colon (matrix defaults)
.replace("[TG]", "");
}
export function isTokenGatedRoom(room: Room): boolean {
return room?.name?.includes("[TG]");
}
export function useTokenGatedRoom(room: Room): { roomName: string; isVerifiedRoom: boolean } {
const roomName = useMemo(() => {
return getRoomName(room);
}, [room]);
const isVerifiedRoom = useMemo(() => {
return isTokenGatedRoom(room);
}, [room]);
return {
roomName,
isVerifiedRoom,
};
}