diff --git a/src/components/structures/SpaceHierarchy.tsx b/src/components/structures/SpaceHierarchy.tsx index 09099032dc..db16011917 100644 --- a/src/components/structures/SpaceHierarchy.tsx +++ b/src/components/structures/SpaceHierarchy.tsx @@ -58,12 +58,19 @@ import { IState, RovingTabIndexProvider, useRovingTabIndex } from "../../accessi import { getDisplayAliasForRoom } from "./RoomDirectory"; import MatrixClientContext from "../../contexts/MatrixClientContext"; import { useEventEmitterState } from "../../hooks/useEventEmitter"; +import { IOOBData } from "../../stores/ThreepidInviteStore"; interface IProps { space: Room; initialText?: string; additionalButtons?: ReactNode; - showRoom(cli: MatrixClient, hierarchy: RoomHierarchy, roomId: string, autoJoin?: boolean): void; + showRoom( + cli: MatrixClient, + hierarchy: RoomHierarchy, + roomId: string, + autoJoin?: boolean, + roomType?: RoomType, + ): void; } interface ITileProps { @@ -72,7 +79,7 @@ interface ITileProps { selected?: boolean; numChildRooms?: number; hasPermissions?: boolean; - onViewRoomClick(autoJoin: boolean): void; + onViewRoomClick(autoJoin: boolean, roomType: RoomType): void; onToggleClick?(): void; } @@ -98,12 +105,12 @@ const Tile: React.FC = ({ const onPreviewClick = (ev: ButtonEvent) => { ev.preventDefault(); ev.stopPropagation(); - onViewRoomClick(false); + onViewRoomClick(false, room.room_type as RoomType); }; const onJoinClick = (ev: ButtonEvent) => { ev.preventDefault(); ev.stopPropagation(); - onViewRoomClick(true); + onViewRoomClick(true, room.room_type as RoomType); }; let button; @@ -280,7 +287,13 @@ const Tile: React.FC = ({ ; }; -export const showRoom = (cli: MatrixClient, hierarchy: RoomHierarchy, roomId: string, autoJoin = false) => { +export const showRoom = ( + cli: MatrixClient, + hierarchy: RoomHierarchy, + roomId: string, + autoJoin = false, + roomType?: RoomType, +) => { const room = hierarchy.roomMap.get(roomId); // Don't let the user view a room they won't be able to either peek or join: @@ -305,7 +318,8 @@ export const showRoom = (cli: MatrixClient, hierarchy: RoomHierarchy, roomId: st avatarUrl: room.avatar_url, // XXX: This logic is duplicated from the JS SDK which would normally decide what the name is. name: room.name || roomAlias || _t("Unnamed room"), - }, + roomType, + } as IOOBData, }); }; @@ -315,7 +329,7 @@ interface IHierarchyLevelProps { hierarchy: RoomHierarchy; parents: Set; selectedMap?: Map>; - onViewRoomClick(roomId: string, autoJoin: boolean): void; + onViewRoomClick(roomId: string, autoJoin: boolean, roomType?: RoomType): void; onToggleClick?(parentId: string, childId: string): void; } @@ -353,8 +367,8 @@ export const HierarchyLevel = ({ room={room} suggested={hierarchy.isSuggested(root.room_id, room.room_id)} selected={selectedMap?.get(root.room_id)?.has(room.room_id)} - onViewRoomClick={(autoJoin) => { - onViewRoomClick(room.room_id, autoJoin); + onViewRoomClick={(autoJoin, roomType) => { + onViewRoomClick(room.room_id, autoJoin, roomType); }} hasPermissions={hasPermissions} onToggleClick={onToggleClick ? () => onToggleClick(root.room_id, room.room_id) : undefined} @@ -373,8 +387,8 @@ export const HierarchyLevel = ({ }).length} suggested={hierarchy.isSuggested(root.room_id, space.room_id)} selected={selectedMap?.get(root.room_id)?.has(space.room_id)} - onViewRoomClick={(autoJoin) => { - onViewRoomClick(space.room_id, autoJoin); + onViewRoomClick={(autoJoin, roomType) => { + onViewRoomClick(space.room_id, autoJoin, roomType); }} hasPermissions={hasPermissions} onToggleClick={onToggleClick ? () => onToggleClick(root.room_id, space.room_id) : undefined} @@ -576,7 +590,7 @@ const SpaceHierarchy = ({ const { loading, rooms, hierarchy, loadMore } = useSpaceSummary(space); const filteredRoomSet = useMemo>(() => { - if (!rooms.length) return new Set(); + if (!rooms?.length) return new Set(); const lcQuery = query.toLowerCase().trim(); if (!lcQuery) return new Set(rooms); @@ -652,8 +666,8 @@ const SpaceHierarchy = ({ parents={new Set()} selectedMap={selected} onToggleClick={hasPermissions ? onToggleClick : undefined} - onViewRoomClick={(roomId, autoJoin) => { - showRoom(cli, hierarchy, roomId, autoJoin); + onViewRoomClick={(roomId, autoJoin, roomType) => { + showRoom(cli, hierarchy, roomId, autoJoin, roomType); }} /> ; diff --git a/src/components/structures/ThreadPanel.tsx b/src/components/structures/ThreadPanel.tsx index a0bccfdce9..ccf9d9d416 100644 --- a/src/components/structures/ThreadPanel.tsx +++ b/src/components/structures/ThreadPanel.tsx @@ -16,7 +16,7 @@ limitations under the License. import React from 'react'; import { MatrixEvent, Room } from 'matrix-js-sdk/src'; -import { Thread } from 'matrix-js-sdk/src/models/thread'; +import { Thread, ThreadEvent } from 'matrix-js-sdk/src/models/thread'; import BaseCard from "../views/right_panel/BaseCard"; import { RightPanelPhases } from "../../stores/RightPanelStorePhases"; @@ -46,13 +46,13 @@ export default class ThreadPanel extends React.Component { } public componentDidMount(): void { - this.room.on("Thread.update", this.onThreadEventReceived); - this.room.on("Thread.ready", this.onThreadEventReceived); + this.room.on(ThreadEvent.Update, this.onThreadEventReceived); + this.room.on(ThreadEvent.Ready, this.onThreadEventReceived); } public componentWillUnmount(): void { - this.room.removeListener("Thread.update", this.onThreadEventReceived); - this.room.removeListener("Thread.ready", this.onThreadEventReceived); + this.room.removeListener(ThreadEvent.Update, this.onThreadEventReceived); + this.room.removeListener(ThreadEvent.Ready, this.onThreadEventReceived); } private onThreadEventReceived = () => this.updateThreads(); diff --git a/src/components/structures/ThreadView.tsx b/src/components/structures/ThreadView.tsx index 614d3c9f4b..dda4c06417 100644 --- a/src/components/structures/ThreadView.tsx +++ b/src/components/structures/ThreadView.tsx @@ -16,7 +16,7 @@ limitations under the License. import React from 'react'; import { MatrixEvent, Room } from 'matrix-js-sdk/src'; -import { Thread } from 'matrix-js-sdk/src/models/thread'; +import { Thread, ThreadEvent } from 'matrix-js-sdk/src/models/thread'; import BaseCard from "../views/right_panel/BaseCard"; import { RightPanelPhases } from "../../stores/RightPanelStorePhases"; @@ -99,15 +99,15 @@ export default class ThreadView extends React.Component { thread = new Thread([mxEv], this.props.room, client); mxEv.setThread(thread); } - thread.on("Thread.update", this.updateThread); - thread.once("Thread.ready", this.updateThread); + thread.on(ThreadEvent.Update, this.updateThread); + thread.once(ThreadEvent.Ready, this.updateThread); this.updateThread(thread); }; private teardownThread = () => { if (this.state.thread) { - this.state.thread.removeListener("Thread.update", this.updateThread); - this.state.thread.removeListener("Thread.ready", this.updateThread); + this.state.thread.removeListener(ThreadEvent.Update, this.updateThread); + this.state.thread.removeListener(ThreadEvent.Ready, this.updateThread); } }; diff --git a/src/components/views/elements/MemberEventListSummary.tsx b/src/components/views/elements/MemberEventListSummary.tsx index 0722cb872a..4eb0177fef 100644 --- a/src/components/views/elements/MemberEventListSummary.tsx +++ b/src/components/views/elements/MemberEventListSummary.tsx @@ -135,7 +135,7 @@ export default class MemberEventListSummary extends React.Component { const desc = formatCommaSeparatedList(descs); - return _t('%(nameList)s %(transitionList)s', { nameList: nameList, transitionList: desc }); + return _t('%(nameList)s %(transitionList)s', { nameList, transitionList: desc }); }); if (!summaries) { diff --git a/src/components/views/messages/ReactionsRowButton.tsx b/src/components/views/messages/ReactionsRowButton.tsx index 7498a49173..8934b2b98f 100644 --- a/src/components/views/messages/ReactionsRowButton.tsx +++ b/src/components/views/messages/ReactionsRowButton.tsx @@ -106,31 +106,20 @@ export default class ReactionsRowButton extends React.PureComponent reacted with %(content)s", - { - content, - }, - { - reactors: () => { - return formatCommaSeparatedList(senders, 6); - }, - reactedWith: (sub) => { - if (!content) { - return null; - } - return sub; - }, - }, - ); } const isPeeking = room.getMyMembership() !== "join"; return { } if (SettingsStore.getValue("feature_thread")) { - this.props.mxEvent.once("Thread.ready", this.updateThread); - this.props.mxEvent.on("Thread.update", this.updateThread); + this.props.mxEvent.once(ThreadEvent.Ready, this.updateThread); + this.props.mxEvent.on(ThreadEvent.Update, this.updateThread); } } diff --git a/src/components/views/rooms/RoomPreviewBar.js b/src/components/views/rooms/RoomPreviewBar.tsx similarity index 78% rename from src/components/views/rooms/RoomPreviewBar.js rename to src/components/views/rooms/RoomPreviewBar.tsx index 89b493595f..30d634e428 100644 --- a/src/components/views/rooms/RoomPreviewBar.js +++ b/src/components/views/rooms/RoomPreviewBar.tsx @@ -14,8 +14,13 @@ See the License for the specific language governing permissions and limitations under the License. */ -import React from 'react'; -import PropTypes from 'prop-types'; +import React from "react"; +import { Room } from "matrix-js-sdk/src/models/room"; +import { MatrixError } from "matrix-js-sdk/src/http-api"; +import { EventType, RoomType } from "matrix-js-sdk/src/@types/event"; +import { IJoinRuleEventContent, JoinRule } from "matrix-js-sdk/src/@types/partials"; +import { RoomMember } from "matrix-js-sdk/src/models/room-member"; + import * as sdk from '../../../index'; import { MatrixClientPeg } from '../../../MatrixClientPeg'; import dis from '../../../dispatcher/dispatcher'; @@ -27,91 +32,102 @@ import { CommunityPrototypeStore } from "../../../stores/CommunityPrototypeStore import { UPDATE_EVENT } from "../../../stores/AsyncStore"; import { replaceableComponent } from "../../../utils/replaceableComponent"; import InviteReason from "../elements/InviteReason"; +import { IOOBData } from "../../../stores/ThreepidInviteStore"; +import Spinner from "../elements/Spinner"; +import AccessibleButton from "../elements/AccessibleButton"; const MemberEventHtmlReasonField = "io.element.html_reason"; -const MessageCase = Object.freeze({ - NotLoggedIn: "NotLoggedIn", - Joining: "Joining", - Loading: "Loading", - Rejecting: "Rejecting", - Kicked: "Kicked", - Banned: "Banned", - OtherThreePIDError: "OtherThreePIDError", - InvitedEmailNotFoundInAccount: "InvitedEmailNotFoundInAccount", - InvitedEmailNoIdentityServer: "InvitedEmailNoIdentityServer", - InvitedEmailMismatch: "InvitedEmailMismatch", - Invite: "Invite", - ViewingRoom: "ViewingRoom", - RoomNotFound: "RoomNotFound", - OtherError: "OtherError", -}); +enum MessageCase { + NotLoggedIn = "NotLoggedIn", + Joining = "Joining", + Loading = "Loading", + Rejecting = "Rejecting", + Kicked = "Kicked", + Banned = "Banned", + OtherThreePIDError = "OtherThreePIDError", + InvitedEmailNotFoundInAccount = "InvitedEmailNotFoundInAccount", + InvitedEmailNoIdentityServer = "InvitedEmailNoIdentityServer", + InvitedEmailMismatch = "InvitedEmailMismatch", + Invite = "Invite", + ViewingRoom = "ViewingRoom", + RoomNotFound = "RoomNotFound", + OtherError = "OtherError", +} + +interface IProps { + // if inviterName is specified, the preview bar will shown an invite to the room. + // You should also specify onRejectClick if specifying inviterName + inviterName?: string; + + // If invited by 3rd party invite, the email address the invite was sent to + invitedEmail?: string; + + // For third party invites, information passed about the room out-of-band + oobData?: IOOBData; + + // For third party invites, a URL for a 3pid invite signing service + signUrl?: string; + + // A standard client/server API error object. If supplied, indicates that the + // caller was unable to fetch details about the room for the given reason. + error?: MatrixError; + + canPreview?: boolean; + previewLoading?: boolean; + room?: Room; + + loading?: boolean; + joining?: boolean; + rejecting?: boolean; + // The alias that was used to access this room, if appropriate + // If given, this will be how the room is referred to (eg. + // in error messages). + roomAlias?: string; + + onJoinClick?(): void; + onRejectClick?(): void; + onRejectAndIgnoreClick?(): void; + onForgetClick?(): void; +} + +interface IState { + busy: boolean; + accountEmails?: string[]; + invitedEmailMxid?: string; + threePidFetchError?: MatrixError; +} @replaceableComponent("views.rooms.RoomPreviewBar") -export default class RoomPreviewBar extends React.Component { - static propTypes = { - onJoinClick: PropTypes.func, - onRejectClick: PropTypes.func, - onRejectAndIgnoreClick: PropTypes.func, - onForgetClick: PropTypes.func, - // if inviterName is specified, the preview bar will shown an invite to the room. - // You should also specify onRejectClick if specifiying inviterName - inviterName: PropTypes.string, - - // If invited by 3rd party invite, the email address the invite was sent to - invitedEmail: PropTypes.string, - - // For third party invites, information passed about the room out-of-band - oobData: PropTypes.object, - - // For third party invites, a URL for a 3pid invite signing service - signUrl: PropTypes.string, - - // A standard client/server API error object. If supplied, indicates that the - // caller was unable to fetch details about the room for the given reason. - error: PropTypes.object, - - canPreview: PropTypes.bool, - previewLoading: PropTypes.bool, - room: PropTypes.object, - - // When a spinner is present, a spinnerState can be specified to indicate the - // purpose of the spinner. - spinner: PropTypes.bool, - spinnerState: PropTypes.oneOf(["joining"]), - loading: PropTypes.bool, - joining: PropTypes.bool, - rejecting: PropTypes.bool, - // The alias that was used to access this room, if appropriate - // If given, this will be how the room is referred to (eg. - // in error messages). - roomAlias: PropTypes.string, - }; - +export default class RoomPreviewBar extends React.Component { static defaultProps = { onJoinClick() {}, }; - state = { - busy: false, - }; + constructor(props) { + super(props); + + this.state = { + busy: false, + }; + } componentDidMount() { - this._checkInvitedEmail(); - CommunityPrototypeStore.instance.on(UPDATE_EVENT, this._onCommunityUpdate); + this.checkInvitedEmail(); + CommunityPrototypeStore.instance.on(UPDATE_EVENT, this.onCommunityUpdate); } componentDidUpdate(prevProps, prevState) { if (this.props.invitedEmail !== prevProps.invitedEmail || this.props.inviterName !== prevProps.inviterName) { - this._checkInvitedEmail(); + this.checkInvitedEmail(); } } componentWillUnmount() { - CommunityPrototypeStore.instance.off(UPDATE_EVENT, this._onCommunityUpdate); + CommunityPrototypeStore.instance.off(UPDATE_EVENT, this.onCommunityUpdate); } - async _checkInvitedEmail() { + private async checkInvitedEmail() { // If this is an invite and we've been told what email address was // invited, fetch the user's account emails and discovery bindings so we // can check them against the email that was invited. @@ -121,8 +137,7 @@ export default class RoomPreviewBar extends React.Component { // Gather the account 3PIDs const account3pids = await MatrixClientPeg.get().getThreePids(); this.setState({ - accountEmails: account3pids.threepids - .filter(b => b.medium === 'email').map(b => b.address), + accountEmails: account3pids.threepids.filter(b => b.medium === 'email').map(b => b.address), }); // If we have an IS connected, use that to lookup the email and // check the bound MXID. @@ -146,21 +161,21 @@ export default class RoomPreviewBar extends React.Component { } } - _onCommunityUpdate = (roomId) => { + private onCommunityUpdate = (roomId: string): void => { if (this.props.room && this.props.room.roomId !== roomId) { return; } this.forceUpdate(); // we have nothing to update }; - _getMessageCase() { + private getMessageCase(): MessageCase { const isGuest = MatrixClientPeg.get().isGuest(); if (isGuest) { return MessageCase.NotLoggedIn; } - const myMember = this._getMyMember(); + const myMember = this.getMyMember(); if (myMember) { if (myMember.isKicked()) { @@ -195,7 +210,7 @@ export default class RoomPreviewBar extends React.Component { } return MessageCase.Invite; } else if (this.props.error) { - if (this.props.error.errcode == 'M_NOT_FOUND') { + if ((this.props.error as MatrixError).errcode == 'M_NOT_FOUND') { return MessageCase.RoomNotFound; } else { return MessageCase.OtherError; @@ -205,8 +220,8 @@ export default class RoomPreviewBar extends React.Component { } } - _getKickOrBanInfo() { - const myMember = this._getMyMember(); + private getKickOrBanInfo(): { memberName?: string, reason?: string } { + const myMember = this.getMyMember(); if (!myMember) { return {}; } @@ -219,24 +234,19 @@ export default class RoomPreviewBar extends React.Component { return { memberName, reason }; } - _joinRule() { - const room = this.props.room; - if (room) { - const joinRules = room.currentState.getStateEvents('m.room.join_rules', ''); - if (joinRules) { - return joinRules.getContent().join_rule; - } - } + private joinRule(): JoinRule { + return this.props.room?.currentState + .getStateEvents(EventType.RoomJoinRules, "")?.getContent().join_rule; } - _communityProfile() { + private communityProfile(): { displayName?: string, avatarMxc?: string } { if (this.props.room) return CommunityPrototypeStore.instance.getInviteProfile(this.props.room.roomId); return { displayName: null, avatarMxc: null }; } - _roomName(atStart = false) { + private roomName(atStart = false): string { let name = this.props.room ? this.props.room.name : this.props.roomAlias; - const profile = this._communityProfile(); + const profile = this.communityProfile(); if (profile.displayName) name = profile.displayName; if (name) { return name; @@ -247,14 +257,11 @@ export default class RoomPreviewBar extends React.Component { } } - _getMyMember() { - return ( - this.props.room && - this.props.room.getMember(MatrixClientPeg.get().getUserId()) - ); + private getMyMember(): RoomMember { + return this.props.room?.getMember(MatrixClientPeg.get().getUserId()); } - _getInviteMember() { + private getInviteMember(): RoomMember { const { room } = this.props; if (!room) { return; @@ -268,8 +275,8 @@ export default class RoomPreviewBar extends React.Component { return room.currentState.getMember(inviterUserId); } - _isDMInvite() { - const myMember = this._getMyMember(); + private isDMInvite(): boolean { + const myMember = this.getMyMember(); if (!myMember) { return false; } @@ -278,7 +285,7 @@ export default class RoomPreviewBar extends React.Component { return memberContent.membership === "invite" && memberContent.is_direct; } - _makeScreenAfterLogin() { + private makeScreenAfterLogin(): { screen: string, params: Record } { return { screen: 'room', params: { @@ -291,18 +298,16 @@ export default class RoomPreviewBar extends React.Component { }; } - onLoginClick = () => { - dis.dispatch({ action: 'start_login', screenAfterLogin: this._makeScreenAfterLogin() }); + private onLoginClick = () => { + dis.dispatch({ action: 'start_login', screenAfterLogin: this.makeScreenAfterLogin() }); }; - onRegisterClick = () => { - dis.dispatch({ action: 'start_registration', screenAfterLogin: this._makeScreenAfterLogin() }); + private onRegisterClick = () => { + dis.dispatch({ action: 'start_registration', screenAfterLogin: this.makeScreenAfterLogin() }); }; render() { const brand = SdkConfig.get().brand; - const Spinner = sdk.getComponent('elements.Spinner'); - const AccessibleButton = sdk.getComponent('elements.AccessibleButton'); let showSpinner = false; let title; @@ -315,10 +320,10 @@ export default class RoomPreviewBar extends React.Component { let footer; const extraComponents = []; - const messageCase = this._getMessageCase(); + const messageCase = this.getMessageCase(); switch (messageCase) { case MessageCase.Joining: { - title = _t("Joining room …"); + title = this.props.oobData.roomType === RoomType.Space ? _t("Joining space …") : _t("Joining room …"); showSpinner = true; break; } @@ -349,12 +354,12 @@ export default class RoomPreviewBar extends React.Component { break; } case MessageCase.Kicked: { - const { memberName, reason } = this._getKickOrBanInfo(); + const { memberName, reason } = this.getKickOrBanInfo(); title = _t("You were kicked from %(roomName)s by %(memberName)s", - { memberName, roomName: this._roomName() }); + { memberName, roomName: this.roomName() }); subTitle = reason ? _t("Reason: %(reason)s", { reason }) : null; - if (this._joinRule() === "invite") { + if (this.joinRule() === "invite") { primaryActionLabel = _t("Forget this room"); primaryActionHandler = this.props.onForgetClick; } else { @@ -366,9 +371,9 @@ export default class RoomPreviewBar extends React.Component { break; } case MessageCase.Banned: { - const { memberName, reason } = this._getKickOrBanInfo(); + const { memberName, reason } = this.getKickOrBanInfo(); title = _t("You were banned from %(roomName)s by %(memberName)s", - { memberName, roomName: this._roomName() }); + { memberName, roomName: this.roomName() }); subTitle = reason ? _t("Reason: %(reason)s", { reason }) : null; primaryActionLabel = _t("Forget this room"); primaryActionHandler = this.props.onForgetClick; @@ -376,8 +381,8 @@ export default class RoomPreviewBar extends React.Component { } case MessageCase.OtherThreePIDError: { title = _t("Something went wrong with your invite to %(roomName)s", - { roomName: this._roomName() }); - const joinRule = this._joinRule(); + { roomName: this.roomName() }); + const joinRule = this.joinRule(); const errCodeMessage = _t( "An error (%(errcode)s) was returned while trying to validate your " + "invite. You could try to pass this information on to a room admin.", @@ -410,7 +415,7 @@ export default class RoomPreviewBar extends React.Component { "This invite to %(roomName)s was sent to %(email)s which is not " + "associated with your account", { - roomName: this._roomName(), + roomName: this.roomName(), email: this.props.invitedEmail, }, ); @@ -427,7 +432,7 @@ export default class RoomPreviewBar extends React.Component { title = _t( "This invite to %(roomName)s was sent to %(email)s", { - roomName: this._roomName(), + roomName: this.roomName(), email: this.props.invitedEmail, }, ); @@ -443,7 +448,7 @@ export default class RoomPreviewBar extends React.Component { title = _t( "This invite to %(roomName)s was sent to %(email)s", { - roomName: this._roomName(), + roomName: this.roomName(), email: this.props.invitedEmail, }, ); @@ -458,11 +463,11 @@ export default class RoomPreviewBar extends React.Component { case MessageCase.Invite: { const RoomAvatar = sdk.getComponent("views.avatars.RoomAvatar"); const oobData = Object.assign({}, this.props.oobData, { - avatarUrl: this._communityProfile().avatarMxc, + avatarUrl: this.communityProfile().avatarMxc, }); const avatar = ; - const inviteMember = this._getInviteMember(); + const inviteMember = this.getInviteMember(); let inviterElement; if (inviteMember) { inviterElement = @@ -474,7 +479,7 @@ export default class RoomPreviewBar extends React.Component { inviterElement = ({ this.props.inviterName }); } - const isDM = this._isDMInvite(); + const isDM = this.isDMInvite(); if (isDM) { title = _t("Do you want to chat with %(user)s?", { user: inviteMember.name }); @@ -485,7 +490,7 @@ export default class RoomPreviewBar extends React.Component { primaryActionLabel = _t("Start chatting"); } else { title = _t("Do you want to join %(roomName)s?", - { roomName: this._roomName() }); + { roomName: this.roomName() }); subTitle = [ avatar, _t(" invited you", {}, { userName: () => inviterElement }), @@ -519,22 +524,22 @@ export default class RoomPreviewBar extends React.Component { case MessageCase.ViewingRoom: { if (this.props.canPreview) { title = _t("You're previewing %(roomName)s. Want to join it?", - { roomName: this._roomName() }); + { roomName: this.roomName() }); } else { title = _t("%(roomName)s can't be previewed. Do you want to join it?", - { roomName: this._roomName(true) }); + { roomName: this.roomName(true) }); } primaryActionLabel = _t("Join the discussion"); primaryActionHandler = this.props.onJoinClick; break; } case MessageCase.RoomNotFound: { - title = _t("%(roomName)s does not exist.", { roomName: this._roomName(true) }); + title = _t("%(roomName)s does not exist.", { roomName: this.roomName(true) }); subTitle = _t("This room doesn't exist. Are you sure you're at the right place?"); break; } case MessageCase.OtherError: { - title = _t("%(roomName)s is not accessible at this time.", { roomName: this._roomName(true) }); + title = _t("%(roomName)s is not accessible at this time.", { roomName: this.roomName(true) }); subTitle = [ _t("Try again later, or ask a room admin to check if you have access."), _t( diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 70834c486b..e5be736d48 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -1660,6 +1660,7 @@ "%(count)s results|other": "%(count)s results", "%(count)s results|one": "%(count)s result", "This room": "This room", + "Joining space …": "Joining space …", "Joining room …": "Joining room …", "Loading …": "Loading …", "Rejecting invite …": "Rejecting invite …", @@ -1976,7 +1977,7 @@ "Add reaction": "Add reaction", "Show all": "Show all", "Reactions": "Reactions", - " reacted with %(content)s": " reacted with %(content)s", + "%(reactors)s reacted with %(content)s": "%(reactors)s reacted with %(content)s", "reacted with %(shortName)s": "reacted with %(shortName)s", "Message deleted": "Message deleted", "Message deleted by %(name)s": "Message deleted by %(name)s", diff --git a/src/stores/ThreepidInviteStore.ts b/src/stores/ThreepidInviteStore.ts index d0cf40941c..9b597ba877 100644 --- a/src/stores/ThreepidInviteStore.ts +++ b/src/stores/ThreepidInviteStore.ts @@ -16,6 +16,7 @@ limitations under the License. import EventEmitter from "events"; import { base32 } from "rfc4648"; +import { RoomType } from "matrix-js-sdk/src/@types/event"; // Dev note: the interface is split in two so we don't have to disable the // linter across the whole project. @@ -53,6 +54,9 @@ export interface IOOBData { name?: string; // The room's name avatarUrl?: string; // The mxc:// avatar URL for the room inviterName?: string; // The display name of the person who invited us to the room + // eslint-disable-next-line camelcase + room_name?: string; // The name of the room, to be used until we are told better by the server + roomType?: RoomType; // The type of the room, to be used until we are told better by the server } const STORAGE_PREFIX = "mx_threepid_invite_"; diff --git a/src/utils/FormattingUtils.ts b/src/utils/FormattingUtils.ts index b527ee7ea2..265deaed38 100644 --- a/src/utils/FormattingUtils.ts +++ b/src/utils/FormattingUtils.ts @@ -104,7 +104,10 @@ export function getUserNameColorClass(userId: string): string { * @returns {string} a string constructed by joining `items` with a comma * between each item, but with the last item appended as " and [lastItem]". */ -export function formatCommaSeparatedList(items: Array, itemLimit?: number): string | JSX.Element { +export function formatCommaSeparatedList(items: string[], itemLimit?: number): string; +export function formatCommaSeparatedList(items: JSX.Element[], itemLimit?: number): JSX.Element; +export function formatCommaSeparatedList(items: Array, itemLimit?: number): JSX.Element | string; +export function formatCommaSeparatedList(items: Array, itemLimit?: number): JSX.Element | string { const remaining = itemLimit === undefined ? 0 : Math.max( items.length - itemLimit, 0, ); @@ -112,11 +115,25 @@ export function formatCommaSeparatedList(items: Array, ite return ""; } else if (items.length === 1) { return items[0]; - } else if (remaining > 0) { - items = items.slice(0, itemLimit); - return _t("%(items)s and %(count)s others", { items: jsxJoin(items, ', '), count: remaining } ); } else { - const lastItem = items.pop(); - return _t("%(items)s and %(lastItem)s", { items: jsxJoin(items, ', '), lastItem: lastItem }); + let lastItem; + if (remaining > 0) { + items = items.slice(0, itemLimit); + } else { + lastItem = items.pop(); + } + + let joinedItems; + if (items.every(e => typeof e === "string")) { + joinedItems = items.join(", "); + } else { + joinedItems = jsxJoin(items, ", "); + } + + if (remaining > 0) { + return _t("%(items)s and %(count)s others", { items: joinedItems, count: remaining } ); + } else { + return _t("%(items)s and %(lastItem)s", { items: joinedItems, lastItem }); + } } }