Merge remote-tracking branch 'upstream/develop' into fix/emoji-dupe/19073

This commit is contained in:
Šimon Brandner 2021-09-17 17:54:07 +02:00
commit 9a2d61bf20
No known key found for this signature in database
GPG key ID: 55C211A1226CB17D
10 changed files with 205 additions and 175 deletions

View file

@ -58,12 +58,19 @@ import { IState, RovingTabIndexProvider, useRovingTabIndex } from "../../accessi
import { getDisplayAliasForRoom } from "./RoomDirectory"; import { getDisplayAliasForRoom } from "./RoomDirectory";
import MatrixClientContext from "../../contexts/MatrixClientContext"; import MatrixClientContext from "../../contexts/MatrixClientContext";
import { useEventEmitterState } from "../../hooks/useEventEmitter"; import { useEventEmitterState } from "../../hooks/useEventEmitter";
import { IOOBData } from "../../stores/ThreepidInviteStore";
interface IProps { interface IProps {
space: Room; space: Room;
initialText?: string; initialText?: string;
additionalButtons?: ReactNode; 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 { interface ITileProps {
@ -72,7 +79,7 @@ interface ITileProps {
selected?: boolean; selected?: boolean;
numChildRooms?: number; numChildRooms?: number;
hasPermissions?: boolean; hasPermissions?: boolean;
onViewRoomClick(autoJoin: boolean): void; onViewRoomClick(autoJoin: boolean, roomType: RoomType): void;
onToggleClick?(): void; onToggleClick?(): void;
} }
@ -98,12 +105,12 @@ const Tile: React.FC<ITileProps> = ({
const onPreviewClick = (ev: ButtonEvent) => { const onPreviewClick = (ev: ButtonEvent) => {
ev.preventDefault(); ev.preventDefault();
ev.stopPropagation(); ev.stopPropagation();
onViewRoomClick(false); onViewRoomClick(false, room.room_type as RoomType);
}; };
const onJoinClick = (ev: ButtonEvent) => { const onJoinClick = (ev: ButtonEvent) => {
ev.preventDefault(); ev.preventDefault();
ev.stopPropagation(); ev.stopPropagation();
onViewRoomClick(true); onViewRoomClick(true, room.room_type as RoomType);
}; };
let button; let button;
@ -280,7 +287,13 @@ const Tile: React.FC<ITileProps> = ({
</li>; </li>;
}; };
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); const room = hierarchy.roomMap.get(roomId);
// Don't let the user view a room they won't be able to either peek or join: // 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, avatarUrl: room.avatar_url,
// XXX: This logic is duplicated from the JS SDK which would normally decide what the name is. // XXX: This logic is duplicated from the JS SDK which would normally decide what the name is.
name: room.name || roomAlias || _t("Unnamed room"), name: room.name || roomAlias || _t("Unnamed room"),
}, roomType,
} as IOOBData,
}); });
}; };
@ -315,7 +329,7 @@ interface IHierarchyLevelProps {
hierarchy: RoomHierarchy; hierarchy: RoomHierarchy;
parents: Set<string>; parents: Set<string>;
selectedMap?: Map<string, Set<string>>; selectedMap?: Map<string, Set<string>>;
onViewRoomClick(roomId: string, autoJoin: boolean): void; onViewRoomClick(roomId: string, autoJoin: boolean, roomType?: RoomType): void;
onToggleClick?(parentId: string, childId: string): void; onToggleClick?(parentId: string, childId: string): void;
} }
@ -353,8 +367,8 @@ export const HierarchyLevel = ({
room={room} room={room}
suggested={hierarchy.isSuggested(root.room_id, room.room_id)} suggested={hierarchy.isSuggested(root.room_id, room.room_id)}
selected={selectedMap?.get(root.room_id)?.has(room.room_id)} selected={selectedMap?.get(root.room_id)?.has(room.room_id)}
onViewRoomClick={(autoJoin) => { onViewRoomClick={(autoJoin, roomType) => {
onViewRoomClick(room.room_id, autoJoin); onViewRoomClick(room.room_id, autoJoin, roomType);
}} }}
hasPermissions={hasPermissions} hasPermissions={hasPermissions}
onToggleClick={onToggleClick ? () => onToggleClick(root.room_id, room.room_id) : undefined} onToggleClick={onToggleClick ? () => onToggleClick(root.room_id, room.room_id) : undefined}
@ -373,8 +387,8 @@ export const HierarchyLevel = ({
}).length} }).length}
suggested={hierarchy.isSuggested(root.room_id, space.room_id)} suggested={hierarchy.isSuggested(root.room_id, space.room_id)}
selected={selectedMap?.get(root.room_id)?.has(space.room_id)} selected={selectedMap?.get(root.room_id)?.has(space.room_id)}
onViewRoomClick={(autoJoin) => { onViewRoomClick={(autoJoin, roomType) => {
onViewRoomClick(space.room_id, autoJoin); onViewRoomClick(space.room_id, autoJoin, roomType);
}} }}
hasPermissions={hasPermissions} hasPermissions={hasPermissions}
onToggleClick={onToggleClick ? () => onToggleClick(root.room_id, space.room_id) : undefined} onToggleClick={onToggleClick ? () => onToggleClick(root.room_id, space.room_id) : undefined}
@ -576,7 +590,7 @@ const SpaceHierarchy = ({
const { loading, rooms, hierarchy, loadMore } = useSpaceSummary(space); const { loading, rooms, hierarchy, loadMore } = useSpaceSummary(space);
const filteredRoomSet = useMemo<Set<IHierarchyRoom>>(() => { const filteredRoomSet = useMemo<Set<IHierarchyRoom>>(() => {
if (!rooms.length) return new Set(); if (!rooms?.length) return new Set();
const lcQuery = query.toLowerCase().trim(); const lcQuery = query.toLowerCase().trim();
if (!lcQuery) return new Set(rooms); if (!lcQuery) return new Set(rooms);
@ -652,8 +666,8 @@ const SpaceHierarchy = ({
parents={new Set()} parents={new Set()}
selectedMap={selected} selectedMap={selected}
onToggleClick={hasPermissions ? onToggleClick : undefined} onToggleClick={hasPermissions ? onToggleClick : undefined}
onViewRoomClick={(roomId, autoJoin) => { onViewRoomClick={(roomId, autoJoin, roomType) => {
showRoom(cli, hierarchy, roomId, autoJoin); showRoom(cli, hierarchy, roomId, autoJoin, roomType);
}} }}
/> />
</>; </>;

View file

@ -16,7 +16,7 @@ limitations under the License.
import React from 'react'; import React from 'react';
import { MatrixEvent, Room } from 'matrix-js-sdk/src'; 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 BaseCard from "../views/right_panel/BaseCard";
import { RightPanelPhases } from "../../stores/RightPanelStorePhases"; import { RightPanelPhases } from "../../stores/RightPanelStorePhases";
@ -46,13 +46,13 @@ export default class ThreadPanel extends React.Component<IProps, IState> {
} }
public componentDidMount(): void { public componentDidMount(): void {
this.room.on("Thread.update", this.onThreadEventReceived); this.room.on(ThreadEvent.Update, this.onThreadEventReceived);
this.room.on("Thread.ready", this.onThreadEventReceived); this.room.on(ThreadEvent.Ready, this.onThreadEventReceived);
} }
public componentWillUnmount(): void { public componentWillUnmount(): void {
this.room.removeListener("Thread.update", this.onThreadEventReceived); this.room.removeListener(ThreadEvent.Update, this.onThreadEventReceived);
this.room.removeListener("Thread.ready", this.onThreadEventReceived); this.room.removeListener(ThreadEvent.Ready, this.onThreadEventReceived);
} }
private onThreadEventReceived = () => this.updateThreads(); private onThreadEventReceived = () => this.updateThreads();

View file

@ -16,7 +16,7 @@ limitations under the License.
import React from 'react'; import React from 'react';
import { MatrixEvent, Room } from 'matrix-js-sdk/src'; 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 BaseCard from "../views/right_panel/BaseCard";
import { RightPanelPhases } from "../../stores/RightPanelStorePhases"; import { RightPanelPhases } from "../../stores/RightPanelStorePhases";
@ -99,15 +99,15 @@ export default class ThreadView extends React.Component<IProps, IState> {
thread = new Thread([mxEv], this.props.room, client); thread = new Thread([mxEv], this.props.room, client);
mxEv.setThread(thread); mxEv.setThread(thread);
} }
thread.on("Thread.update", this.updateThread); thread.on(ThreadEvent.Update, this.updateThread);
thread.once("Thread.ready", this.updateThread); thread.once(ThreadEvent.Ready, this.updateThread);
this.updateThread(thread); this.updateThread(thread);
}; };
private teardownThread = () => { private teardownThread = () => {
if (this.state.thread) { if (this.state.thread) {
this.state.thread.removeListener("Thread.update", this.updateThread); this.state.thread.removeListener(ThreadEvent.Update, this.updateThread);
this.state.thread.removeListener("Thread.ready", this.updateThread); this.state.thread.removeListener(ThreadEvent.Ready, this.updateThread);
} }
}; };

View file

@ -135,7 +135,7 @@ export default class MemberEventListSummary extends React.Component<IProps> {
const desc = formatCommaSeparatedList(descs); 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) { if (!summaries) {

View file

@ -106,31 +106,20 @@ export default class ReactionsRowButton extends React.PureComponent<IProps, ISta
} }
const room = this.context.getRoom(mxEvent.getRoomId()); const room = this.context.getRoom(mxEvent.getRoomId());
let label; let label: string;
if (room) { if (room) {
const senders = []; const senders = [];
for (const reactionEvent of reactionEvents) { for (const reactionEvent of reactionEvents) {
const member = room.getMember(reactionEvent.getSender()); const member = room.getMember(reactionEvent.getSender());
const name = member ? member.name : reactionEvent.getSender(); senders.push(member?.name || reactionEvent.getSender());
senders.push(name);
} }
label = _t(
"<reactors/><reactedWith> reacted with %(content)s</reactedWith>", const reactors = formatCommaSeparatedList(senders, 6);
{ if (content) {
content, label = _t("%(reactors)s reacted with %(content)s", { reactors, content });
}, } else {
{ label = reactors;
reactors: () => {
return formatCommaSeparatedList(senders, 6);
},
reactedWith: (sub) => {
if (!content) {
return null;
} }
return sub;
},
},
);
} }
const isPeeking = room.getMyMembership() !== "join"; const isPeeking = room.getMyMembership() !== "join";
return <AccessibleButton return <AccessibleButton

View file

@ -21,7 +21,7 @@ import { EventType } from "matrix-js-sdk/src/@types/event";
import { EventStatus, MatrixEvent } from "matrix-js-sdk/src/models/event"; import { EventStatus, MatrixEvent } from "matrix-js-sdk/src/models/event";
import { Relations } from "matrix-js-sdk/src/models/relations"; import { Relations } from "matrix-js-sdk/src/models/relations";
import { RoomMember } from "matrix-js-sdk/src/models/room-member"; import { RoomMember } from "matrix-js-sdk/src/models/room-member";
import { Thread } from 'matrix-js-sdk/src/models/thread'; import { Thread, ThreadEvent } from 'matrix-js-sdk/src/models/thread';
import ReplyThread from "../elements/ReplyThread"; import ReplyThread from "../elements/ReplyThread";
import { _t } from '../../../languageHandler'; import { _t } from '../../../languageHandler';
@ -464,8 +464,8 @@ export default class EventTile extends React.Component<IProps, IState> {
} }
if (SettingsStore.getValue("feature_thread")) { if (SettingsStore.getValue("feature_thread")) {
this.props.mxEvent.once("Thread.ready", this.updateThread); this.props.mxEvent.once(ThreadEvent.Ready, this.updateThread);
this.props.mxEvent.on("Thread.update", this.updateThread); this.props.mxEvent.on(ThreadEvent.Update, this.updateThread);
} }
} }

View file

@ -14,8 +14,13 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
import React from 'react'; import React from "react";
import PropTypes from 'prop-types'; 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 * as sdk from '../../../index';
import { MatrixClientPeg } from '../../../MatrixClientPeg'; import { MatrixClientPeg } from '../../../MatrixClientPeg';
import dis from '../../../dispatcher/dispatcher'; import dis from '../../../dispatcher/dispatcher';
@ -27,91 +32,102 @@ import { CommunityPrototypeStore } from "../../../stores/CommunityPrototypeStore
import { UPDATE_EVENT } from "../../../stores/AsyncStore"; import { UPDATE_EVENT } from "../../../stores/AsyncStore";
import { replaceableComponent } from "../../../utils/replaceableComponent"; import { replaceableComponent } from "../../../utils/replaceableComponent";
import InviteReason from "../elements/InviteReason"; 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 MemberEventHtmlReasonField = "io.element.html_reason";
const MessageCase = Object.freeze({ enum MessageCase {
NotLoggedIn: "NotLoggedIn", NotLoggedIn = "NotLoggedIn",
Joining: "Joining", Joining = "Joining",
Loading: "Loading", Loading = "Loading",
Rejecting: "Rejecting", Rejecting = "Rejecting",
Kicked: "Kicked", Kicked = "Kicked",
Banned: "Banned", Banned = "Banned",
OtherThreePIDError: "OtherThreePIDError", OtherThreePIDError = "OtherThreePIDError",
InvitedEmailNotFoundInAccount: "InvitedEmailNotFoundInAccount", InvitedEmailNotFoundInAccount = "InvitedEmailNotFoundInAccount",
InvitedEmailNoIdentityServer: "InvitedEmailNoIdentityServer", InvitedEmailNoIdentityServer = "InvitedEmailNoIdentityServer",
InvitedEmailMismatch: "InvitedEmailMismatch", InvitedEmailMismatch = "InvitedEmailMismatch",
Invite: "Invite", Invite = "Invite",
ViewingRoom: "ViewingRoom", ViewingRoom = "ViewingRoom",
RoomNotFound: "RoomNotFound", RoomNotFound = "RoomNotFound",
OtherError: "OtherError", OtherError = "OtherError",
}); }
@replaceableComponent("views.rooms.RoomPreviewBar") interface IProps {
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. // if inviterName is specified, the preview bar will shown an invite to the room.
// You should also specify onRejectClick if specifiying inviterName // You should also specify onRejectClick if specifying inviterName
inviterName: PropTypes.string, inviterName?: string;
// If invited by 3rd party invite, the email address the invite was sent to // If invited by 3rd party invite, the email address the invite was sent to
invitedEmail: PropTypes.string, invitedEmail?: string;
// For third party invites, information passed about the room out-of-band // For third party invites, information passed about the room out-of-band
oobData: PropTypes.object, oobData?: IOOBData;
// For third party invites, a URL for a 3pid invite signing service // For third party invites, a URL for a 3pid invite signing service
signUrl: PropTypes.string, signUrl?: string;
// A standard client/server API error object. If supplied, indicates that the // 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. // caller was unable to fetch details about the room for the given reason.
error: PropTypes.object, error?: MatrixError;
canPreview: PropTypes.bool, canPreview?: boolean;
previewLoading: PropTypes.bool, previewLoading?: boolean;
room: PropTypes.object, room?: Room;
// When a spinner is present, a spinnerState can be specified to indicate the loading?: boolean;
// purpose of the spinner. joining?: boolean;
spinner: PropTypes.bool, rejecting?: boolean;
spinnerState: PropTypes.oneOf(["joining"]),
loading: PropTypes.bool,
joining: PropTypes.bool,
rejecting: PropTypes.bool,
// The alias that was used to access this room, if appropriate // The alias that was used to access this room, if appropriate
// If given, this will be how the room is referred to (eg. // If given, this will be how the room is referred to (eg.
// in error messages). // in error messages).
roomAlias: PropTypes.string, 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<IProps, IState> {
static defaultProps = { static defaultProps = {
onJoinClick() {}, onJoinClick() {},
}; };
state = { constructor(props) {
super(props);
this.state = {
busy: false, busy: false,
}; };
}
componentDidMount() { componentDidMount() {
this._checkInvitedEmail(); this.checkInvitedEmail();
CommunityPrototypeStore.instance.on(UPDATE_EVENT, this._onCommunityUpdate); CommunityPrototypeStore.instance.on(UPDATE_EVENT, this.onCommunityUpdate);
} }
componentDidUpdate(prevProps, prevState) { componentDidUpdate(prevProps, prevState) {
if (this.props.invitedEmail !== prevProps.invitedEmail || this.props.inviterName !== prevProps.inviterName) { if (this.props.invitedEmail !== prevProps.invitedEmail || this.props.inviterName !== prevProps.inviterName) {
this._checkInvitedEmail(); this.checkInvitedEmail();
} }
} }
componentWillUnmount() { 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 // 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 // invited, fetch the user's account emails and discovery bindings so we
// can check them against the email that was invited. // can check them against the email that was invited.
@ -121,8 +137,7 @@ export default class RoomPreviewBar extends React.Component {
// Gather the account 3PIDs // Gather the account 3PIDs
const account3pids = await MatrixClientPeg.get().getThreePids(); const account3pids = await MatrixClientPeg.get().getThreePids();
this.setState({ this.setState({
accountEmails: account3pids.threepids accountEmails: account3pids.threepids.filter(b => b.medium === 'email').map(b => b.address),
.filter(b => b.medium === 'email').map(b => b.address),
}); });
// If we have an IS connected, use that to lookup the email and // If we have an IS connected, use that to lookup the email and
// check the bound MXID. // 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) { if (this.props.room && this.props.room.roomId !== roomId) {
return; return;
} }
this.forceUpdate(); // we have nothing to update this.forceUpdate(); // we have nothing to update
}; };
_getMessageCase() { private getMessageCase(): MessageCase {
const isGuest = MatrixClientPeg.get().isGuest(); const isGuest = MatrixClientPeg.get().isGuest();
if (isGuest) { if (isGuest) {
return MessageCase.NotLoggedIn; return MessageCase.NotLoggedIn;
} }
const myMember = this._getMyMember(); const myMember = this.getMyMember();
if (myMember) { if (myMember) {
if (myMember.isKicked()) { if (myMember.isKicked()) {
@ -195,7 +210,7 @@ export default class RoomPreviewBar extends React.Component {
} }
return MessageCase.Invite; return MessageCase.Invite;
} else if (this.props.error) { } 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; return MessageCase.RoomNotFound;
} else { } else {
return MessageCase.OtherError; return MessageCase.OtherError;
@ -205,8 +220,8 @@ export default class RoomPreviewBar extends React.Component {
} }
} }
_getKickOrBanInfo() { private getKickOrBanInfo(): { memberName?: string, reason?: string } {
const myMember = this._getMyMember(); const myMember = this.getMyMember();
if (!myMember) { if (!myMember) {
return {}; return {};
} }
@ -219,24 +234,19 @@ export default class RoomPreviewBar extends React.Component {
return { memberName, reason }; return { memberName, reason };
} }
_joinRule() { private joinRule(): JoinRule {
const room = this.props.room; return this.props.room?.currentState
if (room) { .getStateEvents(EventType.RoomJoinRules, "")?.getContent<IJoinRuleEventContent>().join_rule;
const joinRules = room.currentState.getStateEvents('m.room.join_rules', '');
if (joinRules) {
return joinRules.getContent().join_rule;
}
}
} }
_communityProfile() { private communityProfile(): { displayName?: string, avatarMxc?: string } {
if (this.props.room) return CommunityPrototypeStore.instance.getInviteProfile(this.props.room.roomId); if (this.props.room) return CommunityPrototypeStore.instance.getInviteProfile(this.props.room.roomId);
return { displayName: null, avatarMxc: null }; 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; 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 (profile.displayName) name = profile.displayName;
if (name) { if (name) {
return name; return name;
@ -247,14 +257,11 @@ export default class RoomPreviewBar extends React.Component {
} }
} }
_getMyMember() { private getMyMember(): RoomMember {
return ( return this.props.room?.getMember(MatrixClientPeg.get().getUserId());
this.props.room &&
this.props.room.getMember(MatrixClientPeg.get().getUserId())
);
} }
_getInviteMember() { private getInviteMember(): RoomMember {
const { room } = this.props; const { room } = this.props;
if (!room) { if (!room) {
return; return;
@ -268,8 +275,8 @@ export default class RoomPreviewBar extends React.Component {
return room.currentState.getMember(inviterUserId); return room.currentState.getMember(inviterUserId);
} }
_isDMInvite() { private isDMInvite(): boolean {
const myMember = this._getMyMember(); const myMember = this.getMyMember();
if (!myMember) { if (!myMember) {
return false; return false;
} }
@ -278,7 +285,7 @@ export default class RoomPreviewBar extends React.Component {
return memberContent.membership === "invite" && memberContent.is_direct; return memberContent.membership === "invite" && memberContent.is_direct;
} }
_makeScreenAfterLogin() { private makeScreenAfterLogin(): { screen: string, params: Record<string, any> } {
return { return {
screen: 'room', screen: 'room',
params: { params: {
@ -291,18 +298,16 @@ export default class RoomPreviewBar extends React.Component {
}; };
} }
onLoginClick = () => { private onLoginClick = () => {
dis.dispatch({ action: 'start_login', screenAfterLogin: this._makeScreenAfterLogin() }); dis.dispatch({ action: 'start_login', screenAfterLogin: this.makeScreenAfterLogin() });
}; };
onRegisterClick = () => { private onRegisterClick = () => {
dis.dispatch({ action: 'start_registration', screenAfterLogin: this._makeScreenAfterLogin() }); dis.dispatch({ action: 'start_registration', screenAfterLogin: this.makeScreenAfterLogin() });
}; };
render() { render() {
const brand = SdkConfig.get().brand; const brand = SdkConfig.get().brand;
const Spinner = sdk.getComponent('elements.Spinner');
const AccessibleButton = sdk.getComponent('elements.AccessibleButton');
let showSpinner = false; let showSpinner = false;
let title; let title;
@ -315,10 +320,10 @@ export default class RoomPreviewBar extends React.Component {
let footer; let footer;
const extraComponents = []; const extraComponents = [];
const messageCase = this._getMessageCase(); const messageCase = this.getMessageCase();
switch (messageCase) { switch (messageCase) {
case MessageCase.Joining: { case MessageCase.Joining: {
title = _t("Joining room …"); title = this.props.oobData.roomType === RoomType.Space ? _t("Joining space …") : _t("Joining room …");
showSpinner = true; showSpinner = true;
break; break;
} }
@ -349,12 +354,12 @@ export default class RoomPreviewBar extends React.Component {
break; break;
} }
case MessageCase.Kicked: { case MessageCase.Kicked: {
const { memberName, reason } = this._getKickOrBanInfo(); const { memberName, reason } = this.getKickOrBanInfo();
title = _t("You were kicked from %(roomName)s by %(memberName)s", 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; subTitle = reason ? _t("Reason: %(reason)s", { reason }) : null;
if (this._joinRule() === "invite") { if (this.joinRule() === "invite") {
primaryActionLabel = _t("Forget this room"); primaryActionLabel = _t("Forget this room");
primaryActionHandler = this.props.onForgetClick; primaryActionHandler = this.props.onForgetClick;
} else { } else {
@ -366,9 +371,9 @@ export default class RoomPreviewBar extends React.Component {
break; break;
} }
case MessageCase.Banned: { case MessageCase.Banned: {
const { memberName, reason } = this._getKickOrBanInfo(); const { memberName, reason } = this.getKickOrBanInfo();
title = _t("You were banned from %(roomName)s by %(memberName)s", 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; subTitle = reason ? _t("Reason: %(reason)s", { reason }) : null;
primaryActionLabel = _t("Forget this room"); primaryActionLabel = _t("Forget this room");
primaryActionHandler = this.props.onForgetClick; primaryActionHandler = this.props.onForgetClick;
@ -376,8 +381,8 @@ export default class RoomPreviewBar extends React.Component {
} }
case MessageCase.OtherThreePIDError: { case MessageCase.OtherThreePIDError: {
title = _t("Something went wrong with your invite to %(roomName)s", title = _t("Something went wrong with your invite to %(roomName)s",
{ roomName: this._roomName() }); { roomName: this.roomName() });
const joinRule = this._joinRule(); const joinRule = this.joinRule();
const errCodeMessage = _t( const errCodeMessage = _t(
"An error (%(errcode)s) was returned while trying to validate your " + "An error (%(errcode)s) was returned while trying to validate your " +
"invite. You could try to pass this information on to a room admin.", "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 " + "This invite to %(roomName)s was sent to %(email)s which is not " +
"associated with your account", "associated with your account",
{ {
roomName: this._roomName(), roomName: this.roomName(),
email: this.props.invitedEmail, email: this.props.invitedEmail,
}, },
); );
@ -427,7 +432,7 @@ export default class RoomPreviewBar extends React.Component {
title = _t( title = _t(
"This invite to %(roomName)s was sent to %(email)s", "This invite to %(roomName)s was sent to %(email)s",
{ {
roomName: this._roomName(), roomName: this.roomName(),
email: this.props.invitedEmail, email: this.props.invitedEmail,
}, },
); );
@ -443,7 +448,7 @@ export default class RoomPreviewBar extends React.Component {
title = _t( title = _t(
"This invite to %(roomName)s was sent to %(email)s", "This invite to %(roomName)s was sent to %(email)s",
{ {
roomName: this._roomName(), roomName: this.roomName(),
email: this.props.invitedEmail, email: this.props.invitedEmail,
}, },
); );
@ -458,11 +463,11 @@ export default class RoomPreviewBar extends React.Component {
case MessageCase.Invite: { case MessageCase.Invite: {
const RoomAvatar = sdk.getComponent("views.avatars.RoomAvatar"); const RoomAvatar = sdk.getComponent("views.avatars.RoomAvatar");
const oobData = Object.assign({}, this.props.oobData, { const oobData = Object.assign({}, this.props.oobData, {
avatarUrl: this._communityProfile().avatarMxc, avatarUrl: this.communityProfile().avatarMxc,
}); });
const avatar = <RoomAvatar room={this.props.room} oobData={oobData} />; const avatar = <RoomAvatar room={this.props.room} oobData={oobData} />;
const inviteMember = this._getInviteMember(); const inviteMember = this.getInviteMember();
let inviterElement; let inviterElement;
if (inviteMember) { if (inviteMember) {
inviterElement = <span> inviterElement = <span>
@ -474,7 +479,7 @@ export default class RoomPreviewBar extends React.Component {
inviterElement = (<span className="mx_RoomPreviewBar_inviter">{ this.props.inviterName }</span>); inviterElement = (<span className="mx_RoomPreviewBar_inviter">{ this.props.inviterName }</span>);
} }
const isDM = this._isDMInvite(); const isDM = this.isDMInvite();
if (isDM) { if (isDM) {
title = _t("Do you want to chat with %(user)s?", title = _t("Do you want to chat with %(user)s?",
{ user: inviteMember.name }); { user: inviteMember.name });
@ -485,7 +490,7 @@ export default class RoomPreviewBar extends React.Component {
primaryActionLabel = _t("Start chatting"); primaryActionLabel = _t("Start chatting");
} else { } else {
title = _t("Do you want to join %(roomName)s?", title = _t("Do you want to join %(roomName)s?",
{ roomName: this._roomName() }); { roomName: this.roomName() });
subTitle = [ subTitle = [
avatar, avatar,
_t("<userName/> invited you", {}, { userName: () => inviterElement }), _t("<userName/> invited you", {}, { userName: () => inviterElement }),
@ -519,22 +524,22 @@ export default class RoomPreviewBar extends React.Component {
case MessageCase.ViewingRoom: { case MessageCase.ViewingRoom: {
if (this.props.canPreview) { if (this.props.canPreview) {
title = _t("You're previewing %(roomName)s. Want to join it?", title = _t("You're previewing %(roomName)s. Want to join it?",
{ roomName: this._roomName() }); { roomName: this.roomName() });
} else { } else {
title = _t("%(roomName)s can't be previewed. Do you want to join it?", 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"); primaryActionLabel = _t("Join the discussion");
primaryActionHandler = this.props.onJoinClick; primaryActionHandler = this.props.onJoinClick;
break; break;
} }
case MessageCase.RoomNotFound: { 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?"); subTitle = _t("This room doesn't exist. Are you sure you're at the right place?");
break; break;
} }
case MessageCase.OtherError: { 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 = [ subTitle = [
_t("Try again later, or ask a room admin to check if you have access."), _t("Try again later, or ask a room admin to check if you have access."),
_t( _t(

View file

@ -1660,6 +1660,7 @@
"%(count)s results|other": "%(count)s results", "%(count)s results|other": "%(count)s results",
"%(count)s results|one": "%(count)s result", "%(count)s results|one": "%(count)s result",
"This room": "This room", "This room": "This room",
"Joining space …": "Joining space …",
"Joining room …": "Joining room …", "Joining room …": "Joining room …",
"Loading …": "Loading …", "Loading …": "Loading …",
"Rejecting invite …": "Rejecting invite …", "Rejecting invite …": "Rejecting invite …",
@ -1976,7 +1977,7 @@
"Add reaction": "Add reaction", "Add reaction": "Add reaction",
"Show all": "Show all", "Show all": "Show all",
"Reactions": "Reactions", "Reactions": "Reactions",
"<reactors/><reactedWith> reacted with %(content)s</reactedWith>": "<reactors/><reactedWith> reacted with %(content)s</reactedWith>", "%(reactors)s reacted with %(content)s": "%(reactors)s reacted with %(content)s",
"<reactors/><reactedWith>reacted with %(shortName)s</reactedWith>": "<reactors/><reactedWith>reacted with %(shortName)s</reactedWith>", "<reactors/><reactedWith>reacted with %(shortName)s</reactedWith>": "<reactors/><reactedWith>reacted with %(shortName)s</reactedWith>",
"Message deleted": "Message deleted", "Message deleted": "Message deleted",
"Message deleted by %(name)s": "Message deleted by %(name)s", "Message deleted by %(name)s": "Message deleted by %(name)s",

View file

@ -16,6 +16,7 @@ limitations under the License.
import EventEmitter from "events"; import EventEmitter from "events";
import { base32 } from "rfc4648"; 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 // Dev note: the interface is split in two so we don't have to disable the
// linter across the whole project. // linter across the whole project.
@ -53,6 +54,9 @@ export interface IOOBData {
name?: string; // The room's name name?: string; // The room's name
avatarUrl?: string; // The mxc:// avatar URL for the room avatarUrl?: string; // The mxc:// avatar URL for the room
inviterName?: string; // The display name of the person who invited us to 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_"; const STORAGE_PREFIX = "mx_threepid_invite_";

View file

@ -104,7 +104,10 @@ export function getUserNameColorClass(userId: string): string {
* @returns {string} a string constructed by joining `items` with a comma * @returns {string} a string constructed by joining `items` with a comma
* between each item, but with the last item appended as " and [lastItem]". * between each item, but with the last item appended as " and [lastItem]".
*/ */
export function formatCommaSeparatedList(items: Array<string | JSX.Element>, 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<JSX.Element | string>, itemLimit?: number): JSX.Element | string;
export function formatCommaSeparatedList(items: Array<JSX.Element | string>, itemLimit?: number): JSX.Element | string {
const remaining = itemLimit === undefined ? 0 : Math.max( const remaining = itemLimit === undefined ? 0 : Math.max(
items.length - itemLimit, 0, items.length - itemLimit, 0,
); );
@ -112,11 +115,25 @@ export function formatCommaSeparatedList(items: Array<string | JSX.Element>, ite
return ""; return "";
} else if (items.length === 1) { } else if (items.length === 1) {
return items[0]; 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 { } else {
const lastItem = items.pop(); let lastItem;
return _t("%(items)s and %(lastItem)s", { items: jsxJoin(items, ', '), lastItem: 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 });
}
} }
} }