mirror of https://github.com/vector-im/riot-web
Apply some actual typescripting to this file
parent
3f10279e15
commit
60d161caf5
|
@ -47,6 +47,8 @@ import { MatrixCall } from 'matrix-js-sdk/src/webrtc/call';
|
||||||
import {replaceableComponent} from "../../../utils/replaceableComponent";
|
import {replaceableComponent} from "../../../utils/replaceableComponent";
|
||||||
import {mediaFromMxc} from "../../../customisations/Media";
|
import {mediaFromMxc} from "../../../customisations/Media";
|
||||||
import {getAddressType} from "../../../UserAddress";
|
import {getAddressType} from "../../../UserAddress";
|
||||||
|
import BaseAvatar from '../avatars/BaseAvatar';
|
||||||
|
import AccessibleButton from '../elements/AccessibleButton';
|
||||||
|
|
||||||
// we have a number of types defined from the Matrix spec which can't reasonably be altered here.
|
// we have a number of types defined from the Matrix spec which can't reasonably be altered here.
|
||||||
/* eslint-disable camelcase */
|
/* eslint-disable camelcase */
|
||||||
|
@ -61,43 +63,41 @@ const INCREMENT_ROOMS_SHOWN = 5; // Number of rooms to add when 'show more' is c
|
||||||
// This is the interface that is expected by various components in this file. It is a bit
|
// This is the interface that is expected by various components in this file. It is a bit
|
||||||
// awkward because it also matches the RoomMember class from the js-sdk with some extra support
|
// awkward because it also matches the RoomMember class from the js-sdk with some extra support
|
||||||
// for 3PIDs/email addresses.
|
// for 3PIDs/email addresses.
|
||||||
//
|
abstract class Member {
|
||||||
// XXX: We should use TypeScript interfaces instead of this weird "abstract" class.
|
|
||||||
class Member {
|
|
||||||
/**
|
/**
|
||||||
* The display name of this Member. For users this should be their profile's display
|
* The display name of this Member. For users this should be their profile's display
|
||||||
* name or user ID if none set. For 3PIDs this should be the 3PID address (email).
|
* name or user ID if none set. For 3PIDs this should be the 3PID address (email).
|
||||||
*/
|
*/
|
||||||
get name(): string { throw new Error("Member class not implemented"); }
|
public abstract get name(): string;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The ID of this Member. For users this should be their user ID. For 3PIDs this should
|
* The ID of this Member. For users this should be their user ID. For 3PIDs this should
|
||||||
* be the 3PID address (email).
|
* be the 3PID address (email).
|
||||||
*/
|
*/
|
||||||
get userId(): string { throw new Error("Member class not implemented"); }
|
public abstract get userId(): string;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the MXC URL of this Member's avatar. For users this should be their profile's
|
* Gets the MXC URL of this Member's avatar. For users this should be their profile's
|
||||||
* avatar MXC URL or null if none set. For 3PIDs this should always be null.
|
* avatar MXC URL or null if none set. For 3PIDs this should always be null.
|
||||||
*/
|
*/
|
||||||
getMxcAvatarUrl(): string { throw new Error("Member class not implemented"); }
|
public abstract getMxcAvatarUrl(): string;
|
||||||
}
|
}
|
||||||
|
|
||||||
class DirectoryMember extends Member {
|
class DirectoryMember extends Member {
|
||||||
_userId: string;
|
private readonly _userId: string;
|
||||||
_displayName: string;
|
private readonly displayName: string;
|
||||||
_avatarUrl: string;
|
private readonly avatarUrl: string;
|
||||||
|
|
||||||
constructor(userDirResult: {user_id: string, display_name: string, avatar_url: string}) {
|
constructor(userDirResult: {user_id: string, display_name: string, avatar_url: string}) {
|
||||||
super();
|
super();
|
||||||
this._userId = userDirResult.user_id;
|
this._userId = userDirResult.user_id;
|
||||||
this._displayName = userDirResult.display_name;
|
this.displayName = userDirResult.display_name;
|
||||||
this._avatarUrl = userDirResult.avatar_url;
|
this.avatarUrl = userDirResult.avatar_url;
|
||||||
}
|
}
|
||||||
|
|
||||||
// These next class members are for the Member interface
|
// These next class members are for the Member interface
|
||||||
get name(): string {
|
get name(): string {
|
||||||
return this._displayName || this._userId;
|
return this.displayName || this._userId;
|
||||||
}
|
}
|
||||||
|
|
||||||
get userId(): string {
|
get userId(): string {
|
||||||
|
@ -105,32 +105,32 @@ class DirectoryMember extends Member {
|
||||||
}
|
}
|
||||||
|
|
||||||
getMxcAvatarUrl(): string {
|
getMxcAvatarUrl(): string {
|
||||||
return this._avatarUrl;
|
return this.avatarUrl;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class ThreepidMember extends Member {
|
class ThreepidMember extends Member {
|
||||||
_id: string;
|
private readonly id: string;
|
||||||
|
|
||||||
constructor(id: string) {
|
constructor(id: string) {
|
||||||
super();
|
super();
|
||||||
this._id = id;
|
this.id = id;
|
||||||
}
|
}
|
||||||
|
|
||||||
// This is a getter that would be falsey on all other implementations. Until we have
|
// This is a getter that would be falsey on all other implementations. Until we have
|
||||||
// better type support in the react-sdk we can use this trick to determine the kind
|
// better type support in the react-sdk we can use this trick to determine the kind
|
||||||
// of 3PID we're dealing with, if any.
|
// of 3PID we're dealing with, if any.
|
||||||
get isEmail(): boolean {
|
get isEmail(): boolean {
|
||||||
return this._id.includes('@');
|
return this.id.includes('@');
|
||||||
}
|
}
|
||||||
|
|
||||||
// These next class members are for the Member interface
|
// These next class members are for the Member interface
|
||||||
get name(): string {
|
get name(): string {
|
||||||
return this._id;
|
return this.id;
|
||||||
}
|
}
|
||||||
|
|
||||||
get userId(): string {
|
get userId(): string {
|
||||||
return this._id;
|
return this.id;
|
||||||
}
|
}
|
||||||
|
|
||||||
getMxcAvatarUrl(): string {
|
getMxcAvatarUrl(): string {
|
||||||
|
@ -140,11 +140,11 @@ class ThreepidMember extends Member {
|
||||||
|
|
||||||
interface IDMUserTileProps {
|
interface IDMUserTileProps {
|
||||||
member: RoomMember;
|
member: RoomMember;
|
||||||
onRemove: (RoomMember) => any;
|
onRemove(member: RoomMember): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
class DMUserTile extends React.PureComponent<IDMUserTileProps> {
|
class DMUserTile extends React.PureComponent<IDMUserTileProps> {
|
||||||
_onRemove = (e) => {
|
private onRemove = (e) => {
|
||||||
// Stop the browser from highlighting text
|
// Stop the browser from highlighting text
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
|
@ -153,9 +153,6 @@ class DMUserTile extends React.PureComponent<IDMUserTileProps> {
|
||||||
};
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const BaseAvatar = sdk.getComponent("views.avatars.BaseAvatar");
|
|
||||||
const AccessibleButton = sdk.getComponent("elements.AccessibleButton");
|
|
||||||
|
|
||||||
const avatarSize = 20;
|
const avatarSize = 20;
|
||||||
const avatar = this.props.member.isEmail
|
const avatar = this.props.member.isEmail
|
||||||
? <img
|
? <img
|
||||||
|
@ -177,7 +174,7 @@ class DMUserTile extends React.PureComponent<IDMUserTileProps> {
|
||||||
closeButton = (
|
closeButton = (
|
||||||
<AccessibleButton
|
<AccessibleButton
|
||||||
className='mx_InviteDialog_userTile_remove'
|
className='mx_InviteDialog_userTile_remove'
|
||||||
onClick={this._onRemove}
|
onClick={this.onRemove}
|
||||||
>
|
>
|
||||||
<img src={require("../../../../res/img/icon-pill-remove.svg")}
|
<img src={require("../../../../res/img/icon-pill-remove.svg")}
|
||||||
alt={_t('Remove')} width={8} height={8}
|
alt={_t('Remove')} width={8} height={8}
|
||||||
|
@ -201,13 +198,13 @@ class DMUserTile extends React.PureComponent<IDMUserTileProps> {
|
||||||
interface IDMRoomTileProps {
|
interface IDMRoomTileProps {
|
||||||
member: RoomMember;
|
member: RoomMember;
|
||||||
lastActiveTs: number;
|
lastActiveTs: number;
|
||||||
onToggle: (RoomMember) => any;
|
onToggle(member: RoomMember): void;
|
||||||
highlightWord: string;
|
highlightWord: string;
|
||||||
isSelected: boolean;
|
isSelected: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
class DMRoomTile extends React.PureComponent<IDMRoomTileProps> {
|
class DMRoomTile extends React.PureComponent<IDMRoomTileProps> {
|
||||||
_onClick = (e) => {
|
private onClick = (e) => {
|
||||||
// Stop the browser from highlighting text
|
// Stop the browser from highlighting text
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
|
@ -215,7 +212,7 @@ class DMRoomTile extends React.PureComponent<IDMRoomTileProps> {
|
||||||
this.props.onToggle(this.props.member);
|
this.props.onToggle(this.props.member);
|
||||||
};
|
};
|
||||||
|
|
||||||
_highlightName(str: string) {
|
private highlightName(str: string) {
|
||||||
if (!this.props.highlightWord) return str;
|
if (!this.props.highlightWord) return str;
|
||||||
|
|
||||||
// We convert things to lowercase for index searching, but pull substrings from
|
// We convert things to lowercase for index searching, but pull substrings from
|
||||||
|
@ -252,8 +249,6 @@ class DMRoomTile extends React.PureComponent<IDMRoomTileProps> {
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const BaseAvatar = sdk.getComponent("views.avatars.BaseAvatar");
|
|
||||||
|
|
||||||
let timestamp = null;
|
let timestamp = null;
|
||||||
if (this.props.lastActiveTs) {
|
if (this.props.lastActiveTs) {
|
||||||
const humanTs = humanizeTime(this.props.lastActiveTs);
|
const humanTs = humanizeTime(this.props.lastActiveTs);
|
||||||
|
@ -291,13 +286,13 @@ class DMRoomTile extends React.PureComponent<IDMRoomTileProps> {
|
||||||
|
|
||||||
const caption = this.props.member.isEmail
|
const caption = this.props.member.isEmail
|
||||||
? _t("Invite by email")
|
? _t("Invite by email")
|
||||||
: this._highlightName(this.props.member.userId);
|
: this.highlightName(this.props.member.userId);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='mx_InviteDialog_roomTile' onClick={this._onClick}>
|
<div className='mx_InviteDialog_roomTile' onClick={this.onClick}>
|
||||||
{stackedAvatar}
|
{stackedAvatar}
|
||||||
<span className="mx_InviteDialog_roomTile_nameStack">
|
<span className="mx_InviteDialog_roomTile_nameStack">
|
||||||
<div className='mx_InviteDialog_roomTile_name'>{this._highlightName(this.props.member.name)}</div>
|
<div className='mx_InviteDialog_roomTile_name'>{this.highlightName(this.props.member.name)}</div>
|
||||||
<div className='mx_InviteDialog_roomTile_userId'>{caption}</div>
|
<div className='mx_InviteDialog_roomTile_userId'>{caption}</div>
|
||||||
</span>
|
</span>
|
||||||
{timestamp}
|
{timestamp}
|
||||||
|
@ -308,7 +303,7 @@ class DMRoomTile extends React.PureComponent<IDMRoomTileProps> {
|
||||||
|
|
||||||
interface IInviteDialogProps {
|
interface IInviteDialogProps {
|
||||||
// Takes an array of user IDs/emails to invite.
|
// Takes an array of user IDs/emails to invite.
|
||||||
onFinished: (toInvite?: string[]) => any;
|
onFinished: (toInvite?: string[]) => void;
|
||||||
|
|
||||||
// The kind of invite being performed. Assumed to be KIND_DM if
|
// The kind of invite being performed. Assumed to be KIND_DM if
|
||||||
// not provided.
|
// not provided.
|
||||||
|
@ -349,8 +344,8 @@ export default class InviteDialog extends React.PureComponent<IInviteDialogProps
|
||||||
initialText: "",
|
initialText: "",
|
||||||
};
|
};
|
||||||
|
|
||||||
_debounceTimer: NodeJS.Timeout = null; // actually number because we're in the browser
|
private debounceTimer: NodeJS.Timeout = null; // actually number because we're in the browser
|
||||||
_editorRef: any = null;
|
private editorRef = createRef<HTMLInputElement>();
|
||||||
private unmounted = false;
|
private unmounted = false;
|
||||||
|
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
|
@ -379,7 +374,7 @@ export default class InviteDialog extends React.PureComponent<IInviteDialogProps
|
||||||
filterText: this.props.initialText,
|
filterText: this.props.initialText,
|
||||||
recents: InviteDialog.buildRecents(alreadyInvited),
|
recents: InviteDialog.buildRecents(alreadyInvited),
|
||||||
numRecentsShown: INITIAL_ROOMS_SHOWN,
|
numRecentsShown: INITIAL_ROOMS_SHOWN,
|
||||||
suggestions: this._buildSuggestions(alreadyInvited),
|
suggestions: this.buildSuggestions(alreadyInvited),
|
||||||
numSuggestionsShown: INITIAL_ROOMS_SHOWN,
|
numSuggestionsShown: INITIAL_ROOMS_SHOWN,
|
||||||
serverResultsMixin: [],
|
serverResultsMixin: [],
|
||||||
threepidResultsMixin: [],
|
threepidResultsMixin: [],
|
||||||
|
@ -391,13 +386,11 @@ export default class InviteDialog extends React.PureComponent<IInviteDialogProps
|
||||||
busy: false,
|
busy: false,
|
||||||
errorText: null,
|
errorText: null,
|
||||||
};
|
};
|
||||||
|
|
||||||
this._editorRef = createRef();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
if (this.props.initialText) {
|
if (this.props.initialText) {
|
||||||
this._updateSuggestions(this.props.initialText);
|
this.updateSuggestions(this.props.initialText);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -409,7 +402,11 @@ export default class InviteDialog extends React.PureComponent<IInviteDialogProps
|
||||||
this.setState({consultFirst: ev.target.checked});
|
this.setState({consultFirst: ev.target.checked});
|
||||||
}
|
}
|
||||||
|
|
||||||
static buildRecents(excludedTargetIds: Set<string>): {userId: string, user: RoomMember, lastActive: number}[] {
|
public static buildRecents(excludedTargetIds: Set<string>): {
|
||||||
|
userId: string,
|
||||||
|
user: RoomMember,
|
||||||
|
lastActive: number,
|
||||||
|
}[] {
|
||||||
const rooms = DMRoomMap.shared().getUniqueRoomsWithIndividuals(); // map of userId => js-sdk Room
|
const rooms = DMRoomMap.shared().getUniqueRoomsWithIndividuals(); // map of userId => js-sdk Room
|
||||||
|
|
||||||
// Also pull in all the rooms tagged as DefaultTagID.DM so we don't miss anything. Sometimes the
|
// Also pull in all the rooms tagged as DefaultTagID.DM so we don't miss anything. Sometimes the
|
||||||
|
@ -472,7 +469,7 @@ export default class InviteDialog extends React.PureComponent<IInviteDialogProps
|
||||||
return recents;
|
return recents;
|
||||||
}
|
}
|
||||||
|
|
||||||
_buildSuggestions(excludedTargetIds: Set<string>): {userId: string, user: RoomMember}[] {
|
private buildSuggestions(excludedTargetIds: Set<string>): {userId: string, user: RoomMember}[] {
|
||||||
const maxConsideredMembers = 200;
|
const maxConsideredMembers = 200;
|
||||||
const joinedRooms = MatrixClientPeg.get().getRooms()
|
const joinedRooms = MatrixClientPeg.get().getRooms()
|
||||||
.filter(r => r.getMyMembership() === 'join' && r.getJoinedMemberCount() <= maxConsideredMembers);
|
.filter(r => r.getMyMembership() === 'join' && r.getJoinedMemberCount() <= maxConsideredMembers);
|
||||||
|
@ -590,7 +587,7 @@ export default class InviteDialog extends React.PureComponent<IInviteDialogProps
|
||||||
return members.map(m => ({userId: m.member.userId, user: m.member}));
|
return members.map(m => ({userId: m.member.userId, user: m.member}));
|
||||||
}
|
}
|
||||||
|
|
||||||
_shouldAbortAfterInviteError(result): boolean {
|
private shouldAbortAfterInviteError(result): boolean {
|
||||||
const failedUsers = Object.keys(result.states).filter(a => result.states[a] === 'error');
|
const failedUsers = Object.keys(result.states).filter(a => result.states[a] === 'error');
|
||||||
if (failedUsers.length > 0) {
|
if (failedUsers.length > 0) {
|
||||||
console.log("Failed to invite users: ", result);
|
console.log("Failed to invite users: ", result);
|
||||||
|
@ -605,7 +602,7 @@ export default class InviteDialog extends React.PureComponent<IInviteDialogProps
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
_convertFilter(): Member[] {
|
private convertFilter(): Member[] {
|
||||||
// Check to see if there's anything to convert first
|
// Check to see if there's anything to convert first
|
||||||
if (!this.state.filterText || !this.state.filterText.includes('@')) return this.state.targets || [];
|
if (!this.state.filterText || !this.state.filterText.includes('@')) return this.state.targets || [];
|
||||||
|
|
||||||
|
@ -622,10 +619,10 @@ export default class InviteDialog extends React.PureComponent<IInviteDialogProps
|
||||||
return newTargets;
|
return newTargets;
|
||||||
}
|
}
|
||||||
|
|
||||||
_startDm = async () => {
|
private startDm = async () => {
|
||||||
this.setState({busy: true});
|
this.setState({busy: true});
|
||||||
const client = MatrixClientPeg.get();
|
const client = MatrixClientPeg.get();
|
||||||
const targets = this._convertFilter();
|
const targets = this.convertFilter();
|
||||||
const targetIds = targets.map(t => t.userId);
|
const targetIds = targets.map(t => t.userId);
|
||||||
|
|
||||||
// Check if there is already a DM with these people and reuse it if possible.
|
// Check if there is already a DM with these people and reuse it if possible.
|
||||||
|
@ -699,11 +696,11 @@ export default class InviteDialog extends React.PureComponent<IInviteDialogProps
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
_inviteUsers = async () => {
|
private inviteUsers = async () => {
|
||||||
const startTime = CountlyAnalytics.getTimestamp();
|
const startTime = CountlyAnalytics.getTimestamp();
|
||||||
this.setState({busy: true});
|
this.setState({busy: true});
|
||||||
this._convertFilter();
|
this.convertFilter();
|
||||||
const targets = this._convertFilter();
|
const targets = this.convertFilter();
|
||||||
const targetIds = targets.map(t => t.userId);
|
const targetIds = targets.map(t => t.userId);
|
||||||
|
|
||||||
const cli = MatrixClientPeg.get();
|
const cli = MatrixClientPeg.get();
|
||||||
|
@ -720,7 +717,7 @@ export default class InviteDialog extends React.PureComponent<IInviteDialogProps
|
||||||
try {
|
try {
|
||||||
const result = await inviteMultipleToRoom(this.props.roomId, targetIds)
|
const result = await inviteMultipleToRoom(this.props.roomId, targetIds)
|
||||||
CountlyAnalytics.instance.trackSendInvite(startTime, this.props.roomId, targetIds.length);
|
CountlyAnalytics.instance.trackSendInvite(startTime, this.props.roomId, targetIds.length);
|
||||||
if (!this._shouldAbortAfterInviteError(result)) { // handles setting error message too
|
if (!this.shouldAbortAfterInviteError(result)) { // handles setting error message too
|
||||||
this.props.onFinished();
|
this.props.onFinished();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -754,9 +751,9 @@ export default class InviteDialog extends React.PureComponent<IInviteDialogProps
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
_transferCall = async () => {
|
private transferCall = async () => {
|
||||||
this._convertFilter();
|
this.convertFilter();
|
||||||
const targets = this._convertFilter();
|
const targets = this.convertFilter();
|
||||||
const targetIds = targets.map(t => t.userId);
|
const targetIds = targets.map(t => t.userId);
|
||||||
if (targetIds.length > 1) {
|
if (targetIds.length > 1) {
|
||||||
this.setState({
|
this.setState({
|
||||||
|
@ -795,26 +792,26 @@ export default class InviteDialog extends React.PureComponent<IInviteDialogProps
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
_onKeyDown = (e) => {
|
private onKeyDown = (e) => {
|
||||||
if (this.state.busy) return;
|
if (this.state.busy) return;
|
||||||
const value = e.target.value.trim();
|
const value = e.target.value.trim();
|
||||||
const hasModifiers = e.ctrlKey || e.shiftKey || e.metaKey;
|
const hasModifiers = e.ctrlKey || e.shiftKey || e.metaKey;
|
||||||
if (!value && this.state.targets.length > 0 && e.key === Key.BACKSPACE && !hasModifiers) {
|
if (!value && this.state.targets.length > 0 && e.key === Key.BACKSPACE && !hasModifiers) {
|
||||||
// when the field is empty and the user hits backspace remove the right-most target
|
// when the field is empty and the user hits backspace remove the right-most target
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
this._removeMember(this.state.targets[this.state.targets.length - 1]);
|
this.removeMember(this.state.targets[this.state.targets.length - 1]);
|
||||||
} else if (value && e.key === Key.ENTER && !hasModifiers) {
|
} else if (value && e.key === Key.ENTER && !hasModifiers) {
|
||||||
// when the user hits enter with something in their field try to convert it
|
// when the user hits enter with something in their field try to convert it
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
this._convertFilter();
|
this.convertFilter();
|
||||||
} else if (value && e.key === Key.SPACE && !hasModifiers && value.includes("@") && !value.includes(" ")) {
|
} else if (value && e.key === Key.SPACE && !hasModifiers && value.includes("@") && !value.includes(" ")) {
|
||||||
// when the user hits space and their input looks like an e-mail/MXID then try to convert it
|
// when the user hits space and their input looks like an e-mail/MXID then try to convert it
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
this._convertFilter();
|
this.convertFilter();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
_updateSuggestions = async (term) => {
|
private updateSuggestions = async (term) => {
|
||||||
MatrixClientPeg.get().searchUserDirectory({term}).then(async r => {
|
MatrixClientPeg.get().searchUserDirectory({term}).then(async r => {
|
||||||
if (term !== this.state.filterText) {
|
if (term !== this.state.filterText) {
|
||||||
// Discard the results - we were probably too slow on the server-side to make
|
// Discard the results - we were probably too slow on the server-side to make
|
||||||
|
@ -923,30 +920,30 @@ export default class InviteDialog extends React.PureComponent<IInviteDialogProps
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
_updateFilter = (e) => {
|
private updateFilter = (e) => {
|
||||||
const term = e.target.value;
|
const term = e.target.value;
|
||||||
this.setState({filterText: term});
|
this.setState({filterText: term});
|
||||||
|
|
||||||
// Debounce server lookups to reduce spam. We don't clear the existing server
|
// Debounce server lookups to reduce spam. We don't clear the existing server
|
||||||
// results because they might still be vaguely accurate, likewise for races which
|
// results because they might still be vaguely accurate, likewise for races which
|
||||||
// could happen here.
|
// could happen here.
|
||||||
if (this._debounceTimer) {
|
if (this.debounceTimer) {
|
||||||
clearTimeout(this._debounceTimer);
|
clearTimeout(this.debounceTimer);
|
||||||
}
|
}
|
||||||
this._debounceTimer = setTimeout(() => {
|
this.debounceTimer = setTimeout(() => {
|
||||||
this._updateSuggestions(term);
|
this.updateSuggestions(term);
|
||||||
}, 150); // 150ms debounce (human reaction time + some)
|
}, 150); // 150ms debounce (human reaction time + some)
|
||||||
};
|
};
|
||||||
|
|
||||||
_showMoreRecents = () => {
|
private showMoreRecents = () => {
|
||||||
this.setState({numRecentsShown: this.state.numRecentsShown + INCREMENT_ROOMS_SHOWN});
|
this.setState({numRecentsShown: this.state.numRecentsShown + INCREMENT_ROOMS_SHOWN});
|
||||||
};
|
};
|
||||||
|
|
||||||
_showMoreSuggestions = () => {
|
private showMoreSuggestions = () => {
|
||||||
this.setState({numSuggestionsShown: this.state.numSuggestionsShown + INCREMENT_ROOMS_SHOWN});
|
this.setState({numSuggestionsShown: this.state.numSuggestionsShown + INCREMENT_ROOMS_SHOWN});
|
||||||
};
|
};
|
||||||
|
|
||||||
_toggleMember = (member: Member) => {
|
private toggleMember = (member: Member) => {
|
||||||
if (!this.state.busy) {
|
if (!this.state.busy) {
|
||||||
let filterText = this.state.filterText;
|
let filterText = this.state.filterText;
|
||||||
const targets = this.state.targets.map(t => t); // cheap clone for mutation
|
const targets = this.state.targets.map(t => t); // cheap clone for mutation
|
||||||
|
@ -959,13 +956,13 @@ export default class InviteDialog extends React.PureComponent<IInviteDialogProps
|
||||||
}
|
}
|
||||||
this.setState({targets, filterText});
|
this.setState({targets, filterText});
|
||||||
|
|
||||||
if (this._editorRef && this._editorRef.current) {
|
if (this.editorRef && this.editorRef.current) {
|
||||||
this._editorRef.current.focus();
|
this.editorRef.current.focus();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
_removeMember = (member: Member) => {
|
private removeMember = (member: Member) => {
|
||||||
const targets = this.state.targets.map(t => t); // cheap clone for mutation
|
const targets = this.state.targets.map(t => t); // cheap clone for mutation
|
||||||
const idx = targets.indexOf(member);
|
const idx = targets.indexOf(member);
|
||||||
if (idx >= 0) {
|
if (idx >= 0) {
|
||||||
|
@ -973,12 +970,12 @@ export default class InviteDialog extends React.PureComponent<IInviteDialogProps
|
||||||
this.setState({targets});
|
this.setState({targets});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this._editorRef && this._editorRef.current) {
|
if (this.editorRef && this.editorRef.current) {
|
||||||
this._editorRef.current.focus();
|
this.editorRef.current.focus();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
_onPaste = async (e) => {
|
private onPaste = async (e) => {
|
||||||
if (this.state.filterText) {
|
if (this.state.filterText) {
|
||||||
// if the user has already typed something, just let them
|
// if the user has already typed something, just let them
|
||||||
// paste normally.
|
// paste normally.
|
||||||
|
@ -1049,17 +1046,17 @@ export default class InviteDialog extends React.PureComponent<IInviteDialogProps
|
||||||
this.setState({targets: [...this.state.targets, ...toAdd]});
|
this.setState({targets: [...this.state.targets, ...toAdd]});
|
||||||
};
|
};
|
||||||
|
|
||||||
_onClickInputArea = (e) => {
|
private onClickInputArea = (e) => {
|
||||||
// Stop the browser from highlighting text
|
// Stop the browser from highlighting text
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
|
|
||||||
if (this._editorRef && this._editorRef.current) {
|
if (this.editorRef && this.editorRef.current) {
|
||||||
this._editorRef.current.focus();
|
this.editorRef.current.focus();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
_onUseDefaultIdentityServerClick = (e) => {
|
private onUseDefaultIdentityServerClick = (e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
||||||
// Update the IS in account data. Actually using it may trigger terms.
|
// Update the IS in account data. Actually using it may trigger terms.
|
||||||
|
@ -1068,21 +1065,21 @@ export default class InviteDialog extends React.PureComponent<IInviteDialogProps
|
||||||
this.setState({canUseIdentityServer: true, tryingIdentityServer: false});
|
this.setState({canUseIdentityServer: true, tryingIdentityServer: false});
|
||||||
};
|
};
|
||||||
|
|
||||||
_onManageSettingsClick = (e) => {
|
private onManageSettingsClick = (e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
dis.fire(Action.ViewUserSettings);
|
dis.fire(Action.ViewUserSettings);
|
||||||
this.props.onFinished();
|
this.props.onFinished();
|
||||||
};
|
};
|
||||||
|
|
||||||
_onCommunityInviteClick = (e) => {
|
private onCommunityInviteClick = (e) => {
|
||||||
this.props.onFinished();
|
this.props.onFinished();
|
||||||
showCommunityInviteDialog(CommunityPrototypeStore.instance.getSelectedCommunityId());
|
showCommunityInviteDialog(CommunityPrototypeStore.instance.getSelectedCommunityId());
|
||||||
};
|
};
|
||||||
|
|
||||||
_renderSection(kind: "recents"|"suggestions") {
|
private renderSection(kind: "recents"|"suggestions") {
|
||||||
let sourceMembers = kind === 'recents' ? this.state.recents : this.state.suggestions;
|
let sourceMembers = kind === 'recents' ? this.state.recents : this.state.suggestions;
|
||||||
let showNum = kind === 'recents' ? this.state.numRecentsShown : this.state.numSuggestionsShown;
|
let showNum = kind === 'recents' ? this.state.numRecentsShown : this.state.numSuggestionsShown;
|
||||||
const showMoreFn = kind === 'recents' ? this._showMoreRecents.bind(this) : this._showMoreSuggestions.bind(this);
|
const showMoreFn = kind === 'recents' ? this.showMoreRecents.bind(this) : this.showMoreSuggestions.bind(this);
|
||||||
const lastActive = (m) => kind === 'recents' ? m.lastActive : null;
|
const lastActive = (m) => kind === 'recents' ? m.lastActive : null;
|
||||||
let sectionName = kind === 'recents' ? _t("Recent Conversations") : _t("Suggestions");
|
let sectionName = kind === 'recents' ? _t("Recent Conversations") : _t("Suggestions");
|
||||||
let sectionSubname = null;
|
let sectionSubname = null;
|
||||||
|
@ -1162,7 +1159,7 @@ export default class InviteDialog extends React.PureComponent<IInviteDialogProps
|
||||||
member={r.user}
|
member={r.user}
|
||||||
lastActiveTs={lastActive(r)}
|
lastActiveTs={lastActive(r)}
|
||||||
key={r.userId}
|
key={r.userId}
|
||||||
onToggle={this._toggleMember}
|
onToggle={this.toggleMember}
|
||||||
highlightWord={this.state.filterText}
|
highlightWord={this.state.filterText}
|
||||||
isSelected={this.state.targets.some(t => t.userId === r.userId)}
|
isSelected={this.state.targets.some(t => t.userId === r.userId)}
|
||||||
/>
|
/>
|
||||||
|
@ -1177,32 +1174,32 @@ export default class InviteDialog extends React.PureComponent<IInviteDialogProps
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
_renderEditor() {
|
private renderEditor() {
|
||||||
const targets = this.state.targets.map(t => (
|
const targets = this.state.targets.map(t => (
|
||||||
<DMUserTile member={t} onRemove={!this.state.busy && this._removeMember} key={t.userId} />
|
<DMUserTile member={t} onRemove={!this.state.busy && this.removeMember} key={t.userId} />
|
||||||
));
|
));
|
||||||
const input = (
|
const input = (
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
onKeyDown={this._onKeyDown}
|
onKeyDown={this.onKeyDown}
|
||||||
onChange={this._updateFilter}
|
onChange={this.updateFilter}
|
||||||
value={this.state.filterText}
|
value={this.state.filterText}
|
||||||
ref={this._editorRef}
|
ref={this.editorRef}
|
||||||
onPaste={this._onPaste}
|
onPaste={this.onPaste}
|
||||||
autoFocus={true}
|
autoFocus={true}
|
||||||
disabled={this.state.busy}
|
disabled={this.state.busy}
|
||||||
autoComplete="off"
|
autoComplete="off"
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
return (
|
return (
|
||||||
<div className='mx_InviteDialog_editor' onClick={this._onClickInputArea}>
|
<div className='mx_InviteDialog_editor' onClick={this.onClickInputArea}>
|
||||||
{targets}
|
{targets}
|
||||||
{input}
|
{input}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
_renderIdentityServerWarning() {
|
private renderIdentityServerWarning() {
|
||||||
if (!this.state.tryingIdentityServer || this.state.canUseIdentityServer ||
|
if (!this.state.tryingIdentityServer || this.state.canUseIdentityServer ||
|
||||||
!SettingsStore.getValue(UIFeature.IdentityServer)
|
!SettingsStore.getValue(UIFeature.IdentityServer)
|
||||||
) {
|
) {
|
||||||
|
@ -1220,8 +1217,8 @@ export default class InviteDialog extends React.PureComponent<IInviteDialogProps
|
||||||
defaultIdentityServerName: abbreviateUrl(defaultIdentityServerUrl),
|
defaultIdentityServerName: abbreviateUrl(defaultIdentityServerUrl),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
default: sub => <a href="#" onClick={this._onUseDefaultIdentityServerClick}>{sub}</a>,
|
default: sub => <a href="#" onClick={this.onUseDefaultIdentityServerClick}>{sub}</a>,
|
||||||
settings: sub => <a href="#" onClick={this._onManageSettingsClick}>{sub}</a>,
|
settings: sub => <a href="#" onClick={this.onManageSettingsClick}>{sub}</a>,
|
||||||
},
|
},
|
||||||
)}</div>
|
)}</div>
|
||||||
);
|
);
|
||||||
|
@ -1231,7 +1228,7 @@ export default class InviteDialog extends React.PureComponent<IInviteDialogProps
|
||||||
"Use an identity server to invite by email. " +
|
"Use an identity server to invite by email. " +
|
||||||
"Manage in <settings>Settings</settings>.",
|
"Manage in <settings>Settings</settings>.",
|
||||||
{}, {
|
{}, {
|
||||||
settings: sub => <a href="#" onClick={this._onManageSettingsClick}>{sub}</a>,
|
settings: sub => <a href="#" onClick={this.onManageSettingsClick}>{sub}</a>,
|
||||||
},
|
},
|
||||||
)}</div>
|
)}</div>
|
||||||
);
|
);
|
||||||
|
@ -1304,7 +1301,7 @@ export default class InviteDialog extends React.PureComponent<IInviteDialogProps
|
||||||
return (
|
return (
|
||||||
<AccessibleButton
|
<AccessibleButton
|
||||||
kind="link"
|
kind="link"
|
||||||
onClick={this._onCommunityInviteClick}
|
onClick={this.onCommunityInviteClick}
|
||||||
>{sub}</AccessibleButton>
|
>{sub}</AccessibleButton>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
@ -1315,7 +1312,7 @@ export default class InviteDialog extends React.PureComponent<IInviteDialogProps
|
||||||
</React.Fragment>;
|
</React.Fragment>;
|
||||||
}
|
}
|
||||||
buttonText = _t("Go");
|
buttonText = _t("Go");
|
||||||
goButtonFn = this._startDm;
|
goButtonFn = this.startDm;
|
||||||
} else if (this.props.kind === KIND_INVITE) {
|
} else if (this.props.kind === KIND_INVITE) {
|
||||||
const room = MatrixClientPeg.get()?.getRoom(this.props.roomId);
|
const room = MatrixClientPeg.get()?.getRoom(this.props.roomId);
|
||||||
const isSpace = SettingsStore.getValue("feature_spaces") && room?.isSpaceRoom();
|
const isSpace = SettingsStore.getValue("feature_spaces") && room?.isSpaceRoom();
|
||||||
|
@ -1354,7 +1351,7 @@ export default class InviteDialog extends React.PureComponent<IInviteDialogProps
|
||||||
});
|
});
|
||||||
|
|
||||||
buttonText = _t("Invite");
|
buttonText = _t("Invite");
|
||||||
goButtonFn = this._inviteUsers;
|
goButtonFn = this.inviteUsers;
|
||||||
|
|
||||||
if (cli.isRoomEncrypted(this.props.roomId)) {
|
if (cli.isRoomEncrypted(this.props.roomId)) {
|
||||||
const room = cli.getRoom(this.props.roomId);
|
const room = cli.getRoom(this.props.roomId);
|
||||||
|
@ -1376,7 +1373,7 @@ export default class InviteDialog extends React.PureComponent<IInviteDialogProps
|
||||||
} else if (this.props.kind === KIND_CALL_TRANSFER) {
|
} else if (this.props.kind === KIND_CALL_TRANSFER) {
|
||||||
title = _t("Transfer");
|
title = _t("Transfer");
|
||||||
buttonText = _t("Transfer");
|
buttonText = _t("Transfer");
|
||||||
goButtonFn = this._transferCall;
|
goButtonFn = this.transferCall;
|
||||||
consultSection = <div>
|
consultSection = <div>
|
||||||
<label>
|
<label>
|
||||||
<input type="checkbox" checked={this.state.consultFirst} onChange={this.onConsultFirstChange} />
|
<input type="checkbox" checked={this.state.consultFirst} onChange={this.onConsultFirstChange} />
|
||||||
|
@ -1399,7 +1396,7 @@ export default class InviteDialog extends React.PureComponent<IInviteDialogProps
|
||||||
<div className='mx_InviteDialog_content'>
|
<div className='mx_InviteDialog_content'>
|
||||||
<p className='mx_InviteDialog_helpText'>{helpText}</p>
|
<p className='mx_InviteDialog_helpText'>{helpText}</p>
|
||||||
<div className='mx_InviteDialog_addressBar'>
|
<div className='mx_InviteDialog_addressBar'>
|
||||||
{this._renderEditor()}
|
{this.renderEditor()}
|
||||||
<div className='mx_InviteDialog_buttonAndSpinner'>
|
<div className='mx_InviteDialog_buttonAndSpinner'>
|
||||||
<AccessibleButton
|
<AccessibleButton
|
||||||
kind="primary"
|
kind="primary"
|
||||||
|
@ -1413,11 +1410,11 @@ export default class InviteDialog extends React.PureComponent<IInviteDialogProps
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{keySharingWarning}
|
{keySharingWarning}
|
||||||
{this._renderIdentityServerWarning()}
|
{this.renderIdentityServerWarning()}
|
||||||
<div className='error'>{this.state.errorText}</div>
|
<div className='error'>{this.state.errorText}</div>
|
||||||
<div className='mx_InviteDialog_userSections'>
|
<div className='mx_InviteDialog_userSections'>
|
||||||
{this._renderSection('recents')}
|
{this.renderSection('recents')}
|
||||||
{this._renderSection('suggestions')}
|
{this.renderSection('suggestions')}
|
||||||
</div>
|
</div>
|
||||||
{consultSection}
|
{consultSection}
|
||||||
</div>
|
</div>
|
||||||
|
|
Loading…
Reference in New Issue