feat: overwrite room name displays
parent
94343e4811
commit
a66d72c77c
|
@ -5,5 +5,7 @@
|
||||||
"src/components/views/rooms/Autocomplete.tsx": "src/components/views/rooms/Autocomplete.tsx",
|
"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/rooms/RoomTile.tsx": "src/components/views/rooms/RoomTile.tsx",
|
||||||
"src/components/views/elements/RoomName.tsx": "src/components/views/elements/RoomName.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"
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,9 +1,10 @@
|
||||||
|
.sh_RoomTokenGatedRoom {
|
||||||
|
align-items: center;
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
.sh_RoomTokenGatedRoomIcon {
|
.sh_RoomTokenGatedRoomIcon {
|
||||||
width: 16px;
|
width: 16px;
|
||||||
height: 16px;
|
height: 16px;
|
||||||
margin-right: 4px;
|
margin-right: 4px;
|
||||||
}
|
}
|
||||||
.mx_RoomTile .mx_RoomTile_titleContainer .mx_RoomTile_title {
|
|
||||||
align-items: center;
|
|
||||||
display: flex;
|
|
||||||
}
|
|
||||||
|
|
|
@ -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 && (
|
||||||
|
<>
|
||||||
|
·
|
||||||
|
<span
|
||||||
|
className="mx_SpotlightDialog_result_publicRoomTopic"
|
||||||
|
dangerouslySetInnerHTML={{ __html: linkifyAndSanitizeHtml(topic) }}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
|
@ -14,31 +14,44 @@ See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React, { useEffect, useState } from "react";
|
import { IPublicRoomsChunkRoom, Room } from "matrix-js-sdk/src/matrix";
|
||||||
import { Room, RoomEvent } from "matrix-js-sdk/src/matrix";
|
import React, { useCallback, useMemo } from "react";
|
||||||
import { useTypedEventEmitter } from "matrix-react-sdk/src/hooks/useEventEmitter";
|
|
||||||
|
|
||||||
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 {
|
interface IProps {
|
||||||
room?: Room;
|
room?: Room | IPublicRoomsChunkRoom;
|
||||||
children?(name: string): JSX.Element;
|
children?(name: JSX.Element): JSX.Element;
|
||||||
|
maxLength?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
export const RoomName = ({ room, children, maxLength }: IProps): JSX.Element => {
|
||||||
* @deprecated use `useRoomName.ts` instead
|
const roomName = useRoomName(room);
|
||||||
*/
|
|
||||||
const RoomName = ({ room, children }: IProps): JSX.Element => {
|
const isVerifiedRoom = useMemo(() => {
|
||||||
const [name, setName] = useState(getRoomName(room));
|
return isTokenGatedRoom(room);
|
||||||
useTypedEventEmitter(room, RoomEvent.Name, () => {
|
|
||||||
setName(getRoomName(room));
|
|
||||||
});
|
|
||||||
useEffect(() => {
|
|
||||||
setName(getRoomName(room));
|
|
||||||
}, [room]);
|
}, [room]);
|
||||||
|
|
||||||
if (children) return children(name ?? "");
|
const truncatedRoomName = useMemo(() => {
|
||||||
return <>{name || ""}</>;
|
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;
|
export default RoomName;
|
||||||
|
|
|
@ -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>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
|
@ -17,7 +17,7 @@ limitations under the License.
|
||||||
|
|
||||||
import classNames from "classnames";
|
import classNames from "classnames";
|
||||||
import { Room, RoomEvent } from "matrix-js-sdk/src/matrix";
|
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 { getKeyBindingsManager } from "matrix-react-sdk/src/KeyBindingsManager";
|
||||||
import { MatrixClientPeg } from "matrix-react-sdk/src/MatrixClientPeg";
|
import { MatrixClientPeg } from "matrix-react-sdk/src/MatrixClientPeg";
|
||||||
import PosthogTrackers from "matrix-react-sdk/src/PosthogTrackers";
|
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 { ActionPayload } from "matrix-react-sdk/src/dispatcher/payloads";
|
||||||
import { ViewRoomPayload } from "matrix-react-sdk/src/dispatcher/payloads/ViewRoomPayload";
|
import { ViewRoomPayload } from "matrix-react-sdk/src/dispatcher/payloads/ViewRoomPayload";
|
||||||
import { _t } from "matrix-react-sdk/src/languageHandler";
|
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 SettingsStore from "matrix-react-sdk/src/settings/SettingsStore";
|
||||||
import { UIComponent } from "matrix-react-sdk/src/settings/UIFeature";
|
import { UIComponent } from "matrix-react-sdk/src/settings/UIFeature";
|
||||||
import { CallStore, CallStoreEvent } from "matrix-react-sdk/src/stores/CallStore";
|
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 { DefaultTagID, TagID } from "matrix-react-sdk/src/stores/room-list/models";
|
||||||
import { isKnockDenied } from "matrix-react-sdk/src/utils/membership";
|
import { isKnockDenied } from "matrix-react-sdk/src/utils/membership";
|
||||||
import { useHasRoomLiveVoiceBroadcast } from "matrix-react-sdk/src/voice-broadcast";
|
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 {
|
interface Props {
|
||||||
room: Room;
|
room: Room;
|
||||||
|
@ -358,16 +359,16 @@ export class RoomTile extends React.PureComponent<ClassProps, State> {
|
||||||
{...contextMenuBelow(this.state.generalMenuPosition)}
|
{...contextMenuBelow(this.state.generalMenuPosition)}
|
||||||
onFinished={this.onCloseGeneralMenu}
|
onFinished={this.onCloseGeneralMenu}
|
||||||
room={this.props.room}
|
room={this.props.room}
|
||||||
onPostFavoriteClick={(ev: ButtonEvent) =>
|
onPostFavoriteClick={(ev: ButtonEvent): void =>
|
||||||
PosthogTrackers.trackInteraction("WebRoomListRoomTileContextMenuFavouriteToggle", ev)
|
PosthogTrackers.trackInteraction("WebRoomListRoomTileContextMenuFavouriteToggle", ev)
|
||||||
}
|
}
|
||||||
onPostInviteClick={(ev: ButtonEvent) =>
|
onPostInviteClick={(ev: ButtonEvent): void =>
|
||||||
PosthogTrackers.trackInteraction("WebRoomListRoomTileContextMenuInviteItem", ev)
|
PosthogTrackers.trackInteraction("WebRoomListRoomTileContextMenuInviteItem", ev)
|
||||||
}
|
}
|
||||||
onPostSettingsClick={(ev: ButtonEvent) =>
|
onPostSettingsClick={(ev: ButtonEvent): void =>
|
||||||
PosthogTrackers.trackInteraction("WebRoomListRoomTileContextMenuSettingsItem", ev)
|
PosthogTrackers.trackInteraction("WebRoomListRoomTileContextMenuSettingsItem", ev)
|
||||||
}
|
}
|
||||||
onPostLeaveClick={(ev: ButtonEvent) =>
|
onPostLeaveClick={(ev: ButtonEvent): void =>
|
||||||
PosthogTrackers.trackInteraction("WebRoomListRoomTileContextMenuLeaveItem", ev)
|
PosthogTrackers.trackInteraction("WebRoomListRoomTileContextMenuLeaveItem", ev)
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
@ -435,7 +436,7 @@ export class RoomTile extends React.PureComponent<ClassProps, State> {
|
||||||
const titleContainer = this.props.isMinimized ? null : (
|
const titleContainer = this.props.isMinimized ? null : (
|
||||||
<div className="mx_RoomTile_titleContainer">
|
<div className="mx_RoomTile_titleContainer">
|
||||||
<div title={name} className={titleClasses} tabIndex={-1}>
|
<div title={name} className={titleClasses} tabIndex={-1}>
|
||||||
<CustomRoomName room={this.props.room} />
|
<RoomName room={this.props.room} />
|
||||||
</div>
|
</div>
|
||||||
{subtitle}
|
{subtitle}
|
||||||
</div>
|
</div>
|
||||||
|
@ -478,7 +479,7 @@ export class RoomTile extends React.PureComponent<ClassProps, State> {
|
||||||
return (
|
return (
|
||||||
<React.Fragment>
|
<React.Fragment>
|
||||||
<RovingTabIndexWrapper inputRef={this.roomTileRef}>
|
<RovingTabIndexWrapper inputRef={this.roomTileRef}>
|
||||||
{({ onFocus, isActive, ref }) => (
|
{({ onFocus, isActive, ref }): ReactElement => (
|
||||||
<Button
|
<Button
|
||||||
{...props}
|
{...props}
|
||||||
onFocus={onFocus}
|
onFocus={onFocus}
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
|
@ -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,
|
|
||||||
};
|
|
||||||
}
|
|
Loading…
Reference in New Issue