feat: protected aex9 room message
parent
a97927dc9d
commit
0f2dd90f73
|
@ -4,6 +4,7 @@
|
||||||
"src/components/views/auth/AuthPage.tsx": "src/components/views/auth/VectorAuthPage.tsx",
|
"src/components/views/auth/AuthPage.tsx": "src/components/views/auth/VectorAuthPage.tsx",
|
||||||
"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/rooms/NewRoomIntro.tsx": "src/components/views/rooms/NewRoomIntro.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/components/views/dialogs/spotlight/PublicRoomResultDetails.tsx": "src/components/views/dialogs/spotlight/PublicRoomResultDetails.tsx",
|
"src/components/views/dialogs/spotlight/PublicRoomResultDetails.tsx": "src/components/views/dialogs/spotlight/PublicRoomResultDetails.tsx",
|
||||||
"src/components/views/avatars/BaseAvatar.tsx": "src/components/views/avatars/BaseAvatar.tsx",
|
"src/components/views/avatars/BaseAvatar.tsx": "src/components/views/avatars/BaseAvatar.tsx",
|
||||||
|
|
|
@ -8,6 +8,12 @@
|
||||||
height: 16px;
|
height: 16px;
|
||||||
margin-right: 4px;
|
margin-right: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
h2 .sh_RoomTokenGatedRoomIcon {
|
||||||
|
width: 26px;
|
||||||
|
height: 26px;
|
||||||
|
}
|
||||||
|
|
||||||
.mx_QuickSettingsButton.sh_SuperheroDexButton::before {
|
.mx_QuickSettingsButton.sh_SuperheroDexButton::before {
|
||||||
-webkit-mask-image: url(../../themes/superhero/img/icons/diamond.svg);
|
-webkit-mask-image: url(../../themes/superhero/img/icons/diamond.svg);
|
||||||
mask-image: url(../../themes/superhero/img/icons/diamond.svg);
|
mask-image: url(../../themes/superhero/img/icons/diamond.svg);
|
||||||
|
|
|
@ -0,0 +1,313 @@
|
||||||
|
/*
|
||||||
|
Copyright 2020, 2021 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 { EventType, ISendEventResponse, MatrixClient, Room, User } from "matrix-js-sdk/src/matrix";
|
||||||
|
import { MatrixClientPeg } from "matrix-react-sdk/src/MatrixClientPeg";
|
||||||
|
import RoomAvatar from "matrix-react-sdk/src/components/views/avatars/RoomAvatar";
|
||||||
|
import { RoomSettingsTab } from "matrix-react-sdk/src/components/views/dialogs/RoomSettingsDialog";
|
||||||
|
import AccessibleButton, { ButtonEvent } from "matrix-react-sdk/src/components/views/elements/AccessibleButton";
|
||||||
|
import MiniAvatarUploader, { AVATAR_SIZE } from "matrix-react-sdk/src/components/views/elements/MiniAvatarUploader";
|
||||||
|
import EventTileBubble from "matrix-react-sdk/src/components/views/messages/EventTileBubble";
|
||||||
|
import MatrixClientContext from "matrix-react-sdk/src/contexts/MatrixClientContext";
|
||||||
|
import RoomContext from "matrix-react-sdk/src/contexts/RoomContext";
|
||||||
|
import { shouldShowComponent } from "matrix-react-sdk/src/customisations/helpers/UIComponents";
|
||||||
|
import { Action } from "matrix-react-sdk/src/dispatcher/actions";
|
||||||
|
import defaultDispatcher from "matrix-react-sdk/src/dispatcher/dispatcher";
|
||||||
|
import { ViewUserPayload } from "matrix-react-sdk/src/dispatcher/payloads/ViewUserPayload";
|
||||||
|
import { TranslationKey, _t, _td } from "matrix-react-sdk/src/languageHandler";
|
||||||
|
import { LocalRoom } from "matrix-react-sdk/src/models/LocalRoom";
|
||||||
|
import { UIComponent } from "matrix-react-sdk/src/settings/UIFeature";
|
||||||
|
import SpaceStore from "matrix-react-sdk/src/stores/spaces/SpaceStore";
|
||||||
|
import DMRoomMap from "matrix-react-sdk/src/utils/DMRoomMap";
|
||||||
|
import { shouldEncryptRoomWithSingle3rdPartyInvite } from "matrix-react-sdk/src/utils/room/shouldEncryptRoomWithSingle3rdPartyInvite";
|
||||||
|
import { privateShouldBeEncrypted } from "matrix-react-sdk/src/utils/rooms";
|
||||||
|
import { showSpaceInvite } from "matrix-react-sdk/src/utils/space";
|
||||||
|
import React, { useContext } from "react";
|
||||||
|
|
||||||
|
import { getRoomName } from "../../../hooks/useRoomName";
|
||||||
|
import RoomName from "../elements/RoomName";
|
||||||
|
|
||||||
|
function hasExpectedEncryptionSettings(matrixClient: MatrixClient, room: Room): boolean {
|
||||||
|
const isEncrypted: boolean = matrixClient.isRoomEncrypted(room.roomId);
|
||||||
|
const isPublic: boolean = room.getJoinRule() === "public";
|
||||||
|
return isPublic || !privateShouldBeEncrypted(matrixClient) || isEncrypted;
|
||||||
|
}
|
||||||
|
|
||||||
|
const determineIntroMessage = (room: Room, encryptedSingle3rdPartyInvite: boolean): TranslationKey => {
|
||||||
|
if (room instanceof LocalRoom) {
|
||||||
|
return _td("room|intro|send_message_start_dm");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (encryptedSingle3rdPartyInvite) {
|
||||||
|
return _td("room|intro|encrypted_3pid_dm_pending_join");
|
||||||
|
}
|
||||||
|
|
||||||
|
return _td("room|intro|start_of_dm_history");
|
||||||
|
};
|
||||||
|
|
||||||
|
const NewRoomIntro: React.FC = () => {
|
||||||
|
const cli = useContext(MatrixClientContext);
|
||||||
|
const { room, roomId } = useContext(RoomContext);
|
||||||
|
|
||||||
|
if (!room || !roomId) {
|
||||||
|
throw new Error("Unable to create a NewRoomIntro without room and roomId");
|
||||||
|
}
|
||||||
|
|
||||||
|
const isLocalRoom = room instanceof LocalRoom;
|
||||||
|
const dmPartner = isLocalRoom ? room.targets[0]?.userId : DMRoomMap.shared().getUserIdForRoomId(roomId);
|
||||||
|
|
||||||
|
let body: JSX.Element;
|
||||||
|
if (dmPartner) {
|
||||||
|
const { shouldEncrypt: encryptedSingle3rdPartyInvite } = shouldEncryptRoomWithSingle3rdPartyInvite(room);
|
||||||
|
const introMessage = determineIntroMessage(room, encryptedSingle3rdPartyInvite);
|
||||||
|
let caption: string | undefined;
|
||||||
|
|
||||||
|
if (
|
||||||
|
!(room instanceof LocalRoom) &&
|
||||||
|
!encryptedSingle3rdPartyInvite &&
|
||||||
|
room.getJoinedMemberCount() + room.getInvitedMemberCount() === 2
|
||||||
|
) {
|
||||||
|
caption = _t("room|intro|dm_caption");
|
||||||
|
}
|
||||||
|
|
||||||
|
const member = room?.getMember(dmPartner);
|
||||||
|
const displayName = room?.name || member?.rawDisplayName || dmPartner;
|
||||||
|
body = (
|
||||||
|
<React.Fragment>
|
||||||
|
<RoomAvatar
|
||||||
|
room={room}
|
||||||
|
size={AVATAR_SIZE}
|
||||||
|
onClick={(): void => {
|
||||||
|
defaultDispatcher.dispatch<ViewUserPayload>({
|
||||||
|
action: Action.ViewUser,
|
||||||
|
// XXX: We should be using a real member object and not assuming what the receiver wants.
|
||||||
|
member: member || ({ userId: dmPartner } as User),
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<h2>
|
||||||
|
<RoomName room={room} />
|
||||||
|
</h2>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
{_t(
|
||||||
|
introMessage,
|
||||||
|
{},
|
||||||
|
{
|
||||||
|
displayName: () => <b>{displayName}</b>,
|
||||||
|
},
|
||||||
|
)}
|
||||||
|
</p>
|
||||||
|
{caption && <p>{caption}</p>}
|
||||||
|
</React.Fragment>
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
const inRoom = room && room.getMyMembership() === "join";
|
||||||
|
const topic = room.currentState.getStateEvents(EventType.RoomTopic, "")?.getContent()?.topic;
|
||||||
|
const canAddTopic = inRoom && room.currentState.maySendStateEvent(EventType.RoomTopic, cli.getSafeUserId());
|
||||||
|
|
||||||
|
const onTopicClick = (): void => {
|
||||||
|
defaultDispatcher.dispatch(
|
||||||
|
{
|
||||||
|
action: "open_room_settings",
|
||||||
|
room_id: roomId,
|
||||||
|
},
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
// focus the topic field to help the user find it as it'll gain an outline
|
||||||
|
setImmediate(() => {
|
||||||
|
window.document.getElementById("profileTopic")?.focus();
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
let topicText;
|
||||||
|
if (canAddTopic && topic) {
|
||||||
|
topicText = _t(
|
||||||
|
"room|intro|topic_edit",
|
||||||
|
{ topic },
|
||||||
|
{
|
||||||
|
a: (sub) => (
|
||||||
|
<AccessibleButton element="a" kind="link_inline" onClick={onTopicClick}>
|
||||||
|
{sub}
|
||||||
|
</AccessibleButton>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
} else if (topic) {
|
||||||
|
topicText = _t("room|intro|topic", { topic });
|
||||||
|
} else if (canAddTopic) {
|
||||||
|
topicText = _t(
|
||||||
|
"room|intro|no_topic",
|
||||||
|
{},
|
||||||
|
{
|
||||||
|
a: (sub) => (
|
||||||
|
<AccessibleButton element="a" kind="link_inline" onClick={onTopicClick}>
|
||||||
|
{sub}
|
||||||
|
</AccessibleButton>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const creator = room.currentState.getStateEvents(EventType.RoomCreate, "")?.getSender();
|
||||||
|
const creatorName = (creator && room?.getMember(creator)?.rawDisplayName) || creator;
|
||||||
|
|
||||||
|
let createdText: string;
|
||||||
|
if (creator === cli.getUserId()) {
|
||||||
|
createdText = _t("room|intro|you_created");
|
||||||
|
} else {
|
||||||
|
createdText = _t("room|intro|user_created", {
|
||||||
|
displayName: creatorName,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
let parentSpace: Room | undefined;
|
||||||
|
if (
|
||||||
|
SpaceStore.instance.activeSpaceRoom?.canInvite(cli.getSafeUserId()) &&
|
||||||
|
SpaceStore.instance.isRoomInSpace(SpaceStore.instance.activeSpace!, room.roomId)
|
||||||
|
) {
|
||||||
|
parentSpace = SpaceStore.instance.activeSpaceRoom;
|
||||||
|
}
|
||||||
|
|
||||||
|
let buttons: JSX.Element | undefined;
|
||||||
|
if (parentSpace && shouldShowComponent(UIComponent.InviteUsers)) {
|
||||||
|
buttons = (
|
||||||
|
<div className="mx_NewRoomIntro_buttons">
|
||||||
|
<AccessibleButton
|
||||||
|
className="mx_NewRoomIntro_inviteButton"
|
||||||
|
kind="primary"
|
||||||
|
onClick={(): void => {
|
||||||
|
showSpaceInvite(parentSpace!);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{_t("invite|to_space", { spaceName: parentSpace.name })}
|
||||||
|
</AccessibleButton>
|
||||||
|
{room.canInvite(cli.getSafeUserId()) && (
|
||||||
|
<AccessibleButton
|
||||||
|
className="mx_NewRoomIntro_inviteButton"
|
||||||
|
kind="primary_outline"
|
||||||
|
onClick={(): void => {
|
||||||
|
defaultDispatcher.dispatch({ action: "view_invite", roomId });
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{_t("room|intro|room_invite")}
|
||||||
|
</AccessibleButton>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
} else if (room.canInvite(cli.getSafeUserId()) && shouldShowComponent(UIComponent.InviteUsers)) {
|
||||||
|
buttons = (
|
||||||
|
<div className="mx_NewRoomIntro_buttons">
|
||||||
|
<AccessibleButton
|
||||||
|
className="mx_NewRoomIntro_inviteButton"
|
||||||
|
kind="primary"
|
||||||
|
onClick={(): void => {
|
||||||
|
defaultDispatcher.dispatch({ action: "view_invite", roomId });
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{_t("room|invite_this_room")}
|
||||||
|
</AccessibleButton>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const avatarUrl = room.currentState.getStateEvents(EventType.RoomAvatar, "")?.getContent()?.url;
|
||||||
|
let avatar = <RoomAvatar room={room} size={AVATAR_SIZE} viewAvatarOnClick={!!avatarUrl} />;
|
||||||
|
|
||||||
|
if (!avatarUrl) {
|
||||||
|
avatar = (
|
||||||
|
<MiniAvatarUploader
|
||||||
|
hasAvatar={false}
|
||||||
|
noAvatarLabel={_t("room|intro|no_avatar_label")}
|
||||||
|
setAvatarUrl={(url): Promise<ISendEventResponse> =>
|
||||||
|
cli.sendStateEvent(roomId, EventType.RoomAvatar, { url }, "")
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{avatar}
|
||||||
|
</MiniAvatarUploader>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
body = (
|
||||||
|
<React.Fragment>
|
||||||
|
{avatar}
|
||||||
|
|
||||||
|
<h2>
|
||||||
|
<RoomName room={room} />
|
||||||
|
</h2>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
{createdText}{" "}
|
||||||
|
{_t(
|
||||||
|
"room|intro|start_of_room",
|
||||||
|
{},
|
||||||
|
{
|
||||||
|
roomName: () => <b>{getRoomName(room)}</b>,
|
||||||
|
},
|
||||||
|
)}
|
||||||
|
</p>
|
||||||
|
<p>{topicText}</p>
|
||||||
|
{buttons}
|
||||||
|
</React.Fragment>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function openRoomSettings(event: ButtonEvent): void {
|
||||||
|
event.preventDefault();
|
||||||
|
defaultDispatcher.dispatch({
|
||||||
|
action: "open_room_settings",
|
||||||
|
initial_tab_id: RoomSettingsTab.Security,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const subText = _t("room|intro|private_unencrypted_warning");
|
||||||
|
|
||||||
|
let subButton: JSX.Element | undefined;
|
||||||
|
if (
|
||||||
|
room.currentState.mayClientSendStateEvent(EventType.RoomEncryption, MatrixClientPeg.safeGet()) &&
|
||||||
|
!isLocalRoom
|
||||||
|
) {
|
||||||
|
subButton = (
|
||||||
|
<AccessibleButton kind="link_inline" onClick={openRoomSettings}>
|
||||||
|
{_t("room|intro|enable_encryption_prompt")}
|
||||||
|
</AccessibleButton>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const subtitle = (
|
||||||
|
<span>
|
||||||
|
{" "}
|
||||||
|
{subText} {subButton}{" "}
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<li className="mx_NewRoomIntro">
|
||||||
|
{!hasExpectedEncryptionSettings(cli, room) && (
|
||||||
|
<EventTileBubble
|
||||||
|
className="mx_cryptoEvent mx_cryptoEvent_icon_warning"
|
||||||
|
title={_t("room|intro|unencrypted_warning")}
|
||||||
|
subtitle={subtitle}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{body}
|
||||||
|
</li>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default NewRoomIntro;
|
|
@ -38,5 +38,8 @@
|
||||||
"web_default_device_name": "%(appName)s: %(browserName)s on %(osName)s",
|
"web_default_device_name": "%(appName)s: %(browserName)s on %(osName)s",
|
||||||
"welcome_to_superhero": "Welcome to Superhero",
|
"welcome_to_superhero": "Welcome to Superhero",
|
||||||
"superhero_dex": "Superhero DEX",
|
"superhero_dex": "Superhero DEX",
|
||||||
"mint_a_token": "Mint a token"
|
"mint_a_token": "Mint a token",
|
||||||
|
"composer": {
|
||||||
|
"no_perms_notice": "You need to own the room token to be able to post in this room."
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue