@@ -47,6 +47,8 @@ import { MatrixCall } from 'matrix-js-sdk/src/webrtc/call';
import { replaceableComponent } from "../../../utils/replaceableComponent" ;
import { mediaFromMxc } from "../../../customisations/Media" ;
import { getAddressType } from "../../../UserAddress" ;
import BaseAvatar from '../avatars/BaseAvatar' ;
import AccessibleButton from '../elements/AccessibleButton' ;
import AccessibleTooltipButton from "../elements/AccessibleTooltipButton" ;
import { copyPlaintext , selectText } from "../../../utils/strings" ;
import * as ContextMenu from "../../structures/ContextMenu" ;
@@ -56,6 +58,12 @@ import GenericTextContextMenu from "../context_menus/GenericTextContextMenu";
// we have a number of types defined from the Matrix spec which can't reasonably be altered here.
/* eslint-disable camelcase */
interface IRecentUser {
userId : string ,
user : RoomMember ,
lastActive : number ,
}
export const KIND_DM = "dm" ;
export const KIND_INVITE = "invite" ;
export const KIND_CALL_TRANSFER = "call_transfer" ;
@@ -66,43 +74,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
// awkward because it also matches the RoomMember class from the js-sdk with some extra support
// for 3PIDs/email addresses.
//
// XXX: We should use TypeScript interfaces instead of this weird "abstract" class.
class Member {
abstract class Member {
/**
* 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).
*/
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
* 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
* 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 {
_userId : string ;
_ displayName : string ;
_ avatarUrl : string ;
private readonly _userId : string ;
private readonly displayName : string ;
private readonly avatarUrl : string ;
constructor ( userDirResult : { user_id : string , display_name : string , avatar_url : string } ) {
super ( ) ;
this . _userId = userDirResult . user_id ;
this . _ displayName = userDirResult . display_name ;
this . _ avatarUrl = userDirResult . avatar_url ;
this . displayName = userDirResult . display_name ;
this . avatarUrl = userDirResult . avatar_url ;
}
// These next class members are for the Member interface
get name ( ) : string {
return this . _ displayName || this . _userId ;
return this . displayName || this . _userId ;
}
get userId ( ) : string {
@@ -110,32 +116,32 @@ class DirectoryMember extends Member {
}
getMxcAvatarUrl ( ) : string {
return this . _ avatarUrl;
return this . avatarUrl ;
}
}
class ThreepidMember extends Member {
_ id : string ;
private readonly id : string ;
constructor ( id : string ) {
super ( ) ;
this . _ id = id ;
this . id = id ;
}
// 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
// of 3PID we're dealing with, if any.
get isEmail ( ) : boolean {
return this . _ id. includes ( '@' ) ;
return this . id . includes ( '@' ) ;
}
// These next class members are for the Member interface
get name ( ) : string {
return this . _ id;
return this . id ;
}
get userId ( ) : string {
return this . _ id;
return this . id ;
}
getMxcAvatarUrl ( ) : string {
@@ -145,11 +151,11 @@ class ThreepidMember extends Member {
interface IDMUserTileProps {
member : RoomMember ;
onRemove : ( RoomMember ) = > any ;
onRemove ( member : RoomMember) : void ;
}
class DMUserTile extends React . PureComponent < IDMUserTileProps > {
_ onRemove = ( e ) = > {
private onRemove = ( e ) = > {
// Stop the browser from highlighting text
e . preventDefault ( ) ;
e . stopPropagation ( ) ;
@@ -158,9 +164,6 @@ class DMUserTile extends React.PureComponent<IDMUserTileProps> {
} ;
render() {
const BaseAvatar = sdk . getComponent ( "views.avatars.BaseAvatar" ) ;
const AccessibleButton = sdk . getComponent ( "elements.AccessibleButton" ) ;
const avatarSize = 20 ;
const avatar = this . props . member . isEmail
? < img
@@ -182,7 +185,7 @@ class DMUserTile extends React.PureComponent<IDMUserTileProps> {
closeButton = (
< AccessibleButton
className = 'mx_InviteDialog_userTile_remove'
onClick = { this . _ onRemove}
onClick = { this . onRemove }
>
< img src = { require ( "../../../../res/img/icon-pill-remove.svg" ) }
alt = { _t ( 'Remove' ) } width = { 8 } height = { 8 }
@@ -206,13 +209,13 @@ class DMUserTile extends React.PureComponent<IDMUserTileProps> {
interface IDMRoomTileProps {
member : RoomMember ;
lastActiveTs : number ;
onToggle : ( RoomMember ) = > any ;
onToggle ( member : RoomMember) : void ;
highlightWord : string ;
isSelected : boolean ;
}
class DMRoomTile extends React . PureComponent < IDMRoomTileProps > {
_ onClick = ( e ) = > {
private onClick = ( e ) = > {
// Stop the browser from highlighting text
e . preventDefault ( ) ;
e . stopPropagation ( ) ;
@@ -220,7 +223,7 @@ class DMRoomTile extends React.PureComponent<IDMRoomTileProps> {
this . props . onToggle ( this . props . member ) ;
} ;
_ highlightName( str : string ) {
private highlightName ( str : string ) {
if ( ! this . props . highlightWord ) return str ;
// We convert things to lowercase for index searching, but pull substrings from
@@ -257,8 +260,6 @@ class DMRoomTile extends React.PureComponent<IDMRoomTileProps> {
}
render() {
const BaseAvatar = sdk . getComponent ( "views.avatars.BaseAvatar" ) ;
let timestamp = null ;
if ( this . props . lastActiveTs ) {
const humanTs = humanizeTime ( this . props . lastActiveTs ) ;
@@ -296,13 +297,13 @@ class DMRoomTile extends React.PureComponent<IDMRoomTileProps> {
const caption = this . props . member . isEmail
? _t ( "Invite by email" )
: this . _ highlightName( this . props . member . userId ) ;
: this . highlightName ( this . props . member . userId ) ;
return (
< div className = 'mx_InviteDialog_roomTile' onClick = { this . _ onClick} >
< div className = 'mx_InviteDialog_roomTile' onClick = { this . onClick } >
{ stackedAvatar }
< 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 >
< / span >
{ timestamp }
@@ -313,7 +314,7 @@ class DMRoomTile extends React.PureComponent<IDMRoomTileProps> {
interface IInviteDialogProps {
// 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
// not provided.
@@ -355,8 +356,9 @@ export default class InviteDialog extends React.PureComponent<IInviteDialogProps
} ;
private closeCopiedTooltip : ( ) = > void ;
_ debounceTimer : NodeJS.Timeout = null ; // actually number because we're in the browser
_editorRef : any = null ;
private debounceTimer : NodeJS.Timeout = null ; // actually number because we're in the browser
private editorRef = createRef < HTMLInputElement > ( ) ;
private unmounted = false ;
constructor ( props ) {
super ( props ) ;
@@ -384,7 +386,7 @@ export default class InviteDialog extends React.PureComponent<IInviteDialogProps
filterText : this.props.initialText ,
recents : InviteDialog.buildRecents ( alreadyInvited ) ,
numRecentsShown : INITIAL_ROOMS_SHOWN ,
suggestions : this._ buildSuggestions ( alreadyInvited ) ,
suggestions : this.buildSuggestions ( alreadyInvited ) ,
numSuggestionsShown : INITIAL_ROOMS_SHOWN ,
serverResultsMixin : [ ] ,
threepidResultsMixin : [ ] ,
@@ -396,16 +398,18 @@ export default class InviteDialog extends React.PureComponent<IInviteDialogProps
busy : false ,
errorText : null ,
} ;
this . _editorRef = createRef ( ) ;
}
componentDidMount() {
if ( this . props . initialText ) {
this . _ updateSuggestions( this . props . initialText ) ;
this . updateSuggestions ( this . props . initialText ) ;
}
}
componentWillUnmount() {
this . unmounted = true ;
}
componentWillUnmount() {
// if the Copied tooltip is open then get rid of it, there are ways to close the modal which wouldn't close
// the tooltip otherwise, such as pressing Escape or clicking X really quickly
@@ -416,7 +420,7 @@ export default class InviteDialog extends React.PureComponent<IInviteDialogProps
this . setState ( { consultFirst : ev.target.checked } ) ;
}
static buildRecents ( excludedTargetIds : Set < string > ) : { userId : string , user : RoomMember , lastActive : number } [ ] {
public static buildRecents ( excludedTargetIds : Set < string > ) : IRecentUser [ ] {
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
@@ -479,7 +483,7 @@ export default class InviteDialog extends React.PureComponent<IInviteDialogProps
return recents ;
}
_ buildSuggestions( excludedTargetIds : Set < string > ) : { userId : string , user : RoomMember } [ ] {
private buildSuggestions ( excludedTargetIds : Set < string > ) : { userId : string , user : RoomMember } [ ] {
const maxConsideredMembers = 200 ;
const joinedRooms = MatrixClientPeg . get ( ) . getRooms ( )
. filter ( r = > r . getMyMembership ( ) === 'join' && r . getJoinedMemberCount ( ) <= maxConsideredMembers ) ;
@@ -597,7 +601,7 @@ export default class InviteDialog extends React.PureComponent<IInviteDialogProps
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' ) ;
if ( failedUsers . length > 0 ) {
console . log ( "Failed to invite users: " , result ) ;
@@ -612,7 +616,7 @@ export default class InviteDialog extends React.PureComponent<IInviteDialogProps
return false ;
}
_ convertFilter( ) : Member [ ] {
private convertFilter ( ) : Member [ ] {
// Check to see if there's anything to convert first
if ( ! this . state . filterText || ! this . state . filterText . includes ( '@' ) ) return this . state . targets || [ ] ;
@@ -629,10 +633,10 @@ export default class InviteDialog extends React.PureComponent<IInviteDialogProps
return newTargets ;
}
_ startDm = async ( ) = > {
private startDm = async ( ) = > {
this . setState ( { busy : true } ) ;
const client = MatrixClientPeg . get ( ) ;
const targets = this . _ convertFilter( ) ;
const targets = this . convertFilter ( ) ;
const targetIds = targets . map ( t = > t . userId ) ;
// Check if there is already a DM with these people and reuse it if possible.
@@ -706,11 +710,11 @@ export default class InviteDialog extends React.PureComponent<IInviteDialogProps
}
} ;
_ inviteUsers = async ( ) = > {
private inviteUsers = async ( ) = > {
const startTime = CountlyAnalytics . getTimestamp ( ) ;
this . setState ( { busy : true } ) ;
this . _ convertFilter( ) ;
const targets = this . _ convertFilter( ) ;
this . convertFilter ( ) ;
const targets = this . convertFilter ( ) ;
const targetIds = targets . map ( t = > t . userId ) ;
const cli = MatrixClientPeg . get ( ) ;
@@ -727,7 +731,7 @@ export default class InviteDialog extends React.PureComponent<IInviteDialogProps
try {
const result = await inviteMultipleToRoom ( this . props . roomId , targetIds )
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 ( ) ;
}
@@ -761,9 +765,9 @@ export default class InviteDialog extends React.PureComponent<IInviteDialogProps
}
} ;
_ transferCall = async ( ) = > {
this . _ convertFilter( ) ;
const targets = this . _ convertFilter( ) ;
private transferCall = async ( ) = > {
this . convertFilter ( ) ;
const targets = this . convertFilter ( ) ;
const targetIds = targets . map ( t = > t . userId ) ;
if ( targetIds . length > 1 ) {
this . setState ( {
@@ -802,26 +806,26 @@ export default class InviteDialog extends React.PureComponent<IInviteDialogProps
}
} ;
_ onKeyDown = ( e ) = > {
private onKeyDown = ( e ) = > {
if ( this . state . busy ) return ;
const value = e . target . value . trim ( ) ;
const hasModifiers = e . ctrlKey || e . shiftKey || e . metaKey ;
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
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 ) {
// when the user hits enter with something in their field try to convert it
e . preventDefault ( ) ;
this . _ convertFilter( ) ;
this . convertFilter ( ) ;
} 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
e . preventDefault ( ) ;
this . _ convertFilter( ) ;
this . convertFilter ( ) ;
}
} ;
_ updateSuggestions = async ( term ) = > {
private updateSuggestions = async ( term ) = > {
MatrixClientPeg . get ( ) . searchUserDirectory ( { term } ) . then ( async r = > {
if ( term !== this . state . filterText ) {
// Discard the results - we were probably too slow on the server-side to make
@@ -930,30 +934,30 @@ export default class InviteDialog extends React.PureComponent<IInviteDialogProps
}
} ;
_ updateFilter = ( e ) = > {
private updateFilter = ( e ) = > {
const term = e . target . value ;
this . setState ( { filterText : term } ) ;
// 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
// could happen here.
if ( this . _ debounceTimer) {
clearTimeout ( this . _ debounceTimer) ;
if ( this . debounceTimer ) {
clearTimeout ( this . debounceTimer ) ;
}
this . _ debounceTimer = setTimeout ( ( ) = > {
this . _ updateSuggestions( term ) ;
this . debounceTimer = setTimeout ( ( ) = > {
this . updateSuggestions ( term ) ;
} , 150 ) ; // 150ms debounce (human reaction time + some)
} ;
_ showMoreRecents = ( ) = > {
private showMoreRecents = ( ) = > {
this . setState ( { numRecentsShown : this.state.numRecentsShown + INCREMENT_ROOMS_SHOWN } ) ;
} ;
_ showMoreSuggestions = ( ) = > {
private showMoreSuggestions = ( ) = > {
this . setState ( { numSuggestionsShown : this.state.numSuggestionsShown + INCREMENT_ROOMS_SHOWN } ) ;
} ;
_ toggleMember = ( member : Member ) = > {
private toggleMember = ( member : Member ) = > {
if ( ! this . state . busy ) {
let filterText = this . state . filterText ;
const targets = this . state . targets . map ( t = > t ) ; // cheap clone for mutation
@@ -966,13 +970,13 @@ export default class InviteDialog extends React.PureComponent<IInviteDialogProps
}
this . setState ( { targets , filterText } ) ;
if ( this . _ editorRef && this . _ editorRef. current ) {
this . _ editorRef. current . focus ( ) ;
if ( this . editorRef && this . editorRef . current ) {
this . editorRef . current . focus ( ) ;
}
}
} ;
_ removeMember = ( member : Member ) = > {
private removeMember = ( member : Member ) = > {
const targets = this . state . targets . map ( t = > t ) ; // cheap clone for mutation
const idx = targets . indexOf ( member ) ;
if ( idx >= 0 ) {
@@ -980,12 +984,12 @@ export default class InviteDialog extends React.PureComponent<IInviteDialogProps
this . setState ( { targets } ) ;
}
if ( this . _ editorRef && this . _ editorRef. current ) {
this . _ editorRef. current . focus ( ) ;
if ( this . editorRef && this . editorRef . current ) {
this . editorRef . current . focus ( ) ;
}
} ;
_ onPaste = async ( e ) = > {
private onPaste = async ( e ) = > {
if ( this . state . filterText ) {
// if the user has already typed something, just let them
// paste normally.
@@ -1039,6 +1043,7 @@ export default class InviteDialog extends React.PureComponent<IInviteDialogProps
failed . push ( address ) ;
}
}
if ( this . unmounted ) return ;
if ( failed . length > 0 ) {
const QuestionDialog = sdk . getComponent ( 'dialogs.QuestionDialog' ) ;
@@ -1055,17 +1060,17 @@ export default class InviteDialog extends React.PureComponent<IInviteDialogProps
this . setState ( { targets : [ . . . this . state . targets , . . . toAdd ] } ) ;
} ;
_ onClickInputArea = ( e ) = > {
private onClickInputArea = ( e ) = > {
// Stop the browser from highlighting text
e . preventDefault ( ) ;
e . stopPropagation ( ) ;
if ( this . _ editorRef && this . _ editorRef. current ) {
this . _ editorRef. current . focus ( ) ;
if ( this . editorRef && this . editorRef . current ) {
this . editorRef . current . focus ( ) ;
}
} ;
_ onUseDefaultIdentityServerClick = ( e ) = > {
private onUseDefaultIdentityServerClick = ( e ) = > {
e . preventDefault ( ) ;
// Update the IS in account data. Actually using it may trigger terms.
@@ -1074,21 +1079,21 @@ export default class InviteDialog extends React.PureComponent<IInviteDialogProps
this . setState ( { canUseIdentityServer : true , tryingIdentityServer : false } ) ;
} ;
_ onManageSettingsClick = ( e ) = > {
private onManageSettingsClick = ( e ) = > {
e . preventDefault ( ) ;
dis . fire ( Action . ViewUserSettings ) ;
this . props . onFinished ( ) ;
} ;
_ onCommunityInviteClick = ( e ) = > {
private onCommunityInviteClick = ( e ) = > {
this . props . onFinished ( ) ;
showCommunityInviteDialog ( CommunityPrototypeStore . instance . getSelectedCommunityId ( ) ) ;
} ;
_ renderSection( kind : "recents" | "suggestions" ) {
private renderSection ( kind : "recents" | "suggestions" ) {
let sourceMembers = kind === 'recents' ? this . state.recents : this.state.suggestions ;
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 ;
let sectionName = kind === 'recents' ? _t ( "Recent Conversations" ) : _t ( "Suggestions" ) ;
let sectionSubname = null ;
@@ -1168,7 +1173,7 @@ export default class InviteDialog extends React.PureComponent<IInviteDialogProps
member = { r . user }
lastActiveTs = { lastActive ( r ) }
key = { r . userId }
onToggle = { this . _ toggleMember}
onToggle = { this . toggleMember }
highlightWord = { this . state . filterText }
isSelected = { this . state . targets . some ( t = > t . userId === r . userId ) }
/ >
@@ -1183,32 +1188,32 @@ export default class InviteDialog extends React.PureComponent<IInviteDialogProps
) ;
}
_ renderEditor() {
private renderEditor() {
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 = (
< input
type = "text"
onKeyDown = { this . _ onKeyDown}
onChange = { this . _ updateFilter}
onKeyDown = { this . onKeyDown }
onChange = { this . updateFilter }
value = { this . state . filterText }
ref = { this . _ editorRef}
onPaste = { this . _ onPaste}
ref = { this . editorRef }
onPaste = { this . onPaste }
autoFocus = { true }
disabled = { this . state . busy }
autoComplete = "off"
/ >
) ;
return (
< div className = 'mx_InviteDialog_editor' onClick = { this . _ onClickInputArea} >
< div className = 'mx_InviteDialog_editor' onClick = { this . onClickInputArea } >
{ targets }
{ input }
< / div >
) ;
}
_ renderIdentityServerWarning() {
private renderIdentityServerWarning() {
if ( ! this . state . tryingIdentityServer || this . state . canUseIdentityServer ||
! SettingsStore . getValue ( UIFeature . IdentityServer )
) {
@@ -1226,8 +1231,8 @@ export default class InviteDialog extends React.PureComponent<IInviteDialogProps
defaultIdentityServerName : abbreviateUrl ( defaultIdentityServerUrl ) ,
} ,
{
default : sub = > < a href = "#" onClick = { this . _ onUseDefaultIdentityServerClick} > { sub } < / a > ,
settings : sub = > < a href = "#" onClick = { this . _ onManageSettingsClick} > { sub } < / a > ,
default : sub = > < a href = "#" onClick = { this . onUseDefaultIdentityServerClick } > { sub } < / a > ,
settings : sub = > < a href = "#" onClick = { this . onManageSettingsClick } > { sub } < / a > ,
} ,
) } < / div >
) ;
@@ -1237,7 +1242,7 @@ export default class InviteDialog extends React.PureComponent<IInviteDialogProps
"Use an identity server to invite by email. " +
"Manage in <settings>Settings</settings>." ,
{ } , {
settings : sub = > < a href = "#" onClick = { this . _ onManageSettingsClick} > { sub } < / a > ,
settings : sub = > < a href = "#" onClick = { this . onManageSettingsClick } > { sub } < / a > ,
} ,
) } < / div >
) ;
@@ -1329,7 +1334,7 @@ export default class InviteDialog extends React.PureComponent<IInviteDialogProps
return (
< AccessibleButton
kind = "link"
onClick = { this . _ onCommunityInviteClick}
onClick = { this . onCommunityInviteClick }
> { sub } < / AccessibleButton >
) ;
} ,
@@ -1340,7 +1345,7 @@ export default class InviteDialog extends React.PureComponent<IInviteDialogProps
< / React.Fragment > ;
}
buttonText = _t ( "Go" ) ;
goButtonFn = this . _ startDm;
goButtonFn = this . startDm ;
extraSection = < div className = "mx_InviteDialog_section_hidden_suggestions_disclaimer" >
< span > { _t ( "Some suggestions may be hidden for privacy." ) } < / span >
< p > { _t ( "If you can’ t see who you’ re looking for, send them your invite link below." ) } < / p >
@@ -1397,7 +1402,7 @@ export default class InviteDialog extends React.PureComponent<IInviteDialogProps
} ) ;
buttonText = _t ( "Invite" ) ;
goButtonFn = this . _ inviteUsers;
goButtonFn = this . inviteUsers ;
if ( cli . isRoomEncrypted ( this . props . roomId ) ) {
const room = cli . getRoom ( this . props . roomId ) ;
@@ -1419,7 +1424,7 @@ export default class InviteDialog extends React.PureComponent<IInviteDialogProps
} else if ( this . props . kind === KIND_CALL_TRANSFER ) {
title = _t ( "Transfer" ) ;
buttonText = _t ( "Transfer" ) ;
goButtonFn = this . _ transferCall;
goButtonFn = this . transferCall ;
footer = < div >
< label >
< input type = "checkbox" checked = { this . state . consultFirst } onChange = { this . onConsultFirstChange } / >
@@ -1442,7 +1447,7 @@ export default class InviteDialog extends React.PureComponent<IInviteDialogProps
< div className = 'mx_InviteDialog_content' >
< p className = 'mx_InviteDialog_helpText' > { helpText } < / p >
< div className = 'mx_InviteDialog_addressBar' >
{ this . _ renderEditor( ) }
{ this . renderEditor ( ) }
< div className = 'mx_InviteDialog_buttonAndSpinner' >
< AccessibleButton
kind = "primary"
@@ -1456,11 +1461,11 @@ export default class InviteDialog extends React.PureComponent<IInviteDialogProps
< / div >
< / div >
{ keySharingWarning }
{ this . _ renderIdentityServerWarning( ) }
{ this . renderIdentityServerWarning ( ) }
< div className = 'error' > { this . state . errorText } < / div >
< div className = 'mx_InviteDialog_userSections' >
{ this . _ renderSection( 'recents' ) }
{ this . _ renderSection( 'suggestions' ) }
{ this . renderSection ( 'recents' ) }
{ this . renderSection ( 'suggestions' ) }
{ extraSection }
< / div >
{ footer }