2015-06-23 15:41:25 +00:00
|
|
|
/*
|
2016-01-07 04:06:39 +00:00
|
|
|
Copyright 2015, 2016 OpenMarket Ltd
|
2017-07-25 14:56:16 +00:00
|
|
|
Copyright 2017 Vector Creations Ltd
|
2018-03-19 16:47:12 +00:00
|
|
|
Copyright 2017, 2018 New Vector Ltd
|
2015-06-23 15:41:25 +00:00
|
|
|
|
|
|
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
|
you may not use this file except in compliance with the License.
|
|
|
|
You may obtain a copy of the License at
|
|
|
|
|
|
|
|
http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
|
|
|
|
Unless required by applicable law or agreed to in writing, software
|
|
|
|
distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
See the License for the specific language governing permissions and
|
|
|
|
limitations under the License.
|
|
|
|
*/
|
2017-09-22 12:15:02 +00:00
|
|
|
|
|
|
|
import React from 'react';
|
2017-05-25 10:39:08 +00:00
|
|
|
import { _t } from '../../../languageHandler';
|
2018-03-19 16:47:12 +00:00
|
|
|
import SdkConfig from '../../../SdkConfig';
|
2018-10-24 11:50:58 +00:00
|
|
|
import dis from '../../../dispatcher';
|
2017-10-11 16:56:17 +00:00
|
|
|
const MatrixClientPeg = require("../../../MatrixClientPeg");
|
|
|
|
const sdk = require('../../../index');
|
|
|
|
const rate_limited_func = require('../../../ratelimitedfunc');
|
|
|
|
const CallHandler = require("../../../CallHandler");
|
2015-06-22 10:42:09 +00:00
|
|
|
|
2017-09-22 12:15:02 +00:00
|
|
|
const INITIAL_LOAD_NUM_MEMBERS = 30;
|
2017-09-22 15:50:54 +00:00
|
|
|
const INITIAL_LOAD_NUM_INVITED = 5;
|
2017-09-22 16:01:14 +00:00
|
|
|
const SHOW_MORE_INCREMENT = 100;
|
2016-01-15 15:18:55 +00:00
|
|
|
|
2015-11-30 15:13:28 +00:00
|
|
|
module.exports = React.createClass({
|
|
|
|
displayName: 'MemberList',
|
2016-02-14 22:37:59 +00:00
|
|
|
|
2015-06-25 13:57:35 +00:00
|
|
|
getInitialState: function() {
|
2018-09-03 09:13:56 +00:00
|
|
|
const cli = MatrixClientPeg.get();
|
|
|
|
if (cli.hasLazyLoadMembersEnabled()) {
|
2018-09-17 17:18:55 +00:00
|
|
|
// show an empty list
|
|
|
|
return this._getMembersState([]);
|
2018-09-03 09:13:56 +00:00
|
|
|
} else {
|
2018-09-17 17:18:55 +00:00
|
|
|
return this._getMembersState(this.roomMembers());
|
2018-09-03 09:13:56 +00:00
|
|
|
}
|
2018-08-29 08:30:25 +00:00
|
|
|
},
|
|
|
|
|
2018-09-03 09:13:56 +00:00
|
|
|
componentWillMount: function() {
|
2018-09-17 18:02:15 +00:00
|
|
|
this._mounted = true;
|
2018-09-07 10:00:19 +00:00
|
|
|
const cli = MatrixClientPeg.get();
|
2018-09-17 17:14:52 +00:00
|
|
|
if (cli.hasLazyLoadMembersEnabled()) {
|
2018-09-17 18:03:01 +00:00
|
|
|
this._showMembersAccordingToMembershipWithLL();
|
2018-09-17 17:14:52 +00:00
|
|
|
cli.on("Room.myMembership", this.onMyMembership);
|
|
|
|
} else {
|
2018-09-07 10:00:19 +00:00
|
|
|
this._listenForMembersChanges();
|
|
|
|
}
|
2018-09-13 16:43:24 +00:00
|
|
|
cli.on("Room", this.onRoom); // invites & joining after peek
|
2018-09-07 10:00:19 +00:00
|
|
|
const enablePresenceByHsUrl = SdkConfig.get()["enable_presence_by_hs_url"];
|
|
|
|
const hsUrl = MatrixClientPeg.get().baseUrl;
|
|
|
|
this._showPresence = true;
|
|
|
|
if (enablePresenceByHsUrl && enablePresenceByHsUrl[hsUrl] !== undefined) {
|
|
|
|
this._showPresence = enablePresenceByHsUrl[hsUrl];
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
_listenForMembersChanges: function() {
|
2017-10-11 16:56:17 +00:00
|
|
|
const cli = MatrixClientPeg.get();
|
2015-06-22 10:42:09 +00:00
|
|
|
cli.on("RoomState.members", this.onRoomStateMember);
|
2015-10-28 15:15:35 +00:00
|
|
|
cli.on("RoomMember.name", this.onRoomMemberName);
|
2016-01-22 15:11:36 +00:00
|
|
|
cli.on("RoomState.events", this.onRoomStateEvent);
|
2016-07-14 09:05:40 +00:00
|
|
|
// We listen for changes to the lastPresenceTs which is essentially
|
|
|
|
// listening for all presence events (we display most of not all of
|
|
|
|
// the information contained in presence events).
|
2018-12-20 21:56:18 +00:00
|
|
|
cli.on("User.lastPresenceTs", this.onUserPresenceChange);
|
|
|
|
cli.on("User.presence", this.onUserPresenceChange);
|
|
|
|
cli.on("User.currentlyActive", this.onUserPresenceChange);
|
2016-04-18 00:35:40 +00:00
|
|
|
// cli.on("Room.timeline", this.onRoomTimeline);
|
2018-09-03 09:13:56 +00:00
|
|
|
},
|
2018-08-29 09:09:55 +00:00
|
|
|
|
2015-06-22 10:42:09 +00:00
|
|
|
componentWillUnmount: function() {
|
2018-09-07 10:00:19 +00:00
|
|
|
this._mounted = false;
|
2017-10-11 16:56:17 +00:00
|
|
|
const cli = MatrixClientPeg.get();
|
2016-04-18 00:35:40 +00:00
|
|
|
if (cli) {
|
|
|
|
cli.removeListener("RoomState.members", this.onRoomStateMember);
|
|
|
|
cli.removeListener("RoomMember.name", this.onRoomMemberName);
|
2018-09-17 17:14:52 +00:00
|
|
|
cli.removeListener("Room.myMembership", this.onMyMembership);
|
2016-04-18 00:35:40 +00:00
|
|
|
cli.removeListener("RoomState.events", this.onRoomStateEvent);
|
|
|
|
cli.removeListener("Room", this.onRoom);
|
2018-12-20 21:56:18 +00:00
|
|
|
cli.removeListener("User.lastPresenceTs", this.onUserPresenceChange);
|
|
|
|
cli.removeListener("User.presence", this.onUserPresenceChange);
|
|
|
|
cli.removeListener("User.currentlyActive", this.onUserPresenceChange);
|
2015-06-22 10:42:09 +00:00
|
|
|
}
|
2016-08-10 12:39:47 +00:00
|
|
|
|
|
|
|
// cancel any pending calls to the rate_limited_funcs
|
|
|
|
this._updateList.cancelPendingCall();
|
2015-06-22 10:42:09 +00:00
|
|
|
},
|
|
|
|
|
2018-09-17 17:20:26 +00:00
|
|
|
/**
|
|
|
|
* If lazy loading is enabled, either:
|
|
|
|
* show a spinner and load the members if the user is joined,
|
2018-09-17 18:03:01 +00:00
|
|
|
* or show the members available so far if the user is invited
|
2018-09-17 17:20:26 +00:00
|
|
|
*/
|
2018-09-17 18:03:01 +00:00
|
|
|
_showMembersAccordingToMembershipWithLL: async function() {
|
2018-08-29 08:30:25 +00:00
|
|
|
const cli = MatrixClientPeg.get();
|
2018-09-13 16:43:24 +00:00
|
|
|
if (cli.hasLazyLoadMembersEnabled()) {
|
|
|
|
const cli = MatrixClientPeg.get();
|
|
|
|
const room = cli.getRoom(this.props.roomId);
|
2018-09-17 18:03:01 +00:00
|
|
|
const membership = room && room.getMyMembership();
|
|
|
|
if (membership === "join") {
|
2018-09-13 16:43:24 +00:00
|
|
|
this.setState({loading: true});
|
|
|
|
try {
|
|
|
|
await room.loadMembersIfNeeded();
|
2018-09-17 18:01:55 +00:00
|
|
|
} catch (ex) {/* already logged in RoomView */}
|
2018-09-13 16:43:24 +00:00
|
|
|
if (this._mounted) {
|
2018-09-17 17:18:55 +00:00
|
|
|
this.setState(this._getMembersState(this.roomMembers()));
|
2018-09-13 16:43:24 +00:00
|
|
|
this._listenForMembersChanges();
|
|
|
|
}
|
2018-09-17 18:03:01 +00:00
|
|
|
} else if (membership === "invite") {
|
|
|
|
// show the members we've got when invited
|
2018-09-17 17:18:55 +00:00
|
|
|
this.setState(this._getMembersState(this.roomMembers()));
|
2018-09-13 16:43:24 +00:00
|
|
|
}
|
2018-08-29 08:30:25 +00:00
|
|
|
}
|
|
|
|
},
|
|
|
|
|
2018-09-17 17:18:55 +00:00
|
|
|
_getMembersState: function(members) {
|
2018-09-03 09:13:56 +00:00
|
|
|
// set the state after determining _showPresence to make sure it's
|
|
|
|
// taken into account while rerendering
|
|
|
|
return {
|
|
|
|
loading: false,
|
|
|
|
members: members,
|
|
|
|
filteredJoinedMembers: this._filterMembers(members, 'join'),
|
|
|
|
filteredInvitedMembers: this._filterMembers(members, 'invite'),
|
|
|
|
|
|
|
|
// ideally we'd size this to the page height, but
|
|
|
|
// in practice I find that a little constraining
|
|
|
|
truncateAtJoined: INITIAL_LOAD_NUM_MEMBERS,
|
|
|
|
truncateAtInvited: INITIAL_LOAD_NUM_INVITED,
|
|
|
|
searchQuery: "",
|
|
|
|
};
|
|
|
|
},
|
|
|
|
|
2018-12-20 21:56:18 +00:00
|
|
|
onUserPresenceChange(event, user) {
|
2015-09-18 17:39:16 +00:00
|
|
|
// Attach a SINGLE listener for global presence changes then locate the
|
|
|
|
// member tile and re-render it. This is more efficient than every tile
|
2018-12-20 21:56:18 +00:00
|
|
|
// ever attaching their own listener.
|
2017-10-11 16:56:17 +00:00
|
|
|
const tile = this.refs[user.userId];
|
2018-12-20 21:57:27 +00:00
|
|
|
// console.log(`Got presence update for ${user.userId}. hasTile=${!!tile}`);
|
2016-04-18 00:35:40 +00:00
|
|
|
if (tile) {
|
|
|
|
this._updateList(); // reorder the membership list
|
2015-09-18 17:39:16 +00:00
|
|
|
}
|
|
|
|
},
|
2016-01-20 14:41:48 +00:00
|
|
|
|
2018-09-14 11:55:47 +00:00
|
|
|
onRoom: function(room) {
|
2015-09-18 17:39:16 +00:00
|
|
|
if (room.roomId !== this.props.roomId) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
// We listen for room events because when we accept an invite
|
|
|
|
// we need to wait till the room is fully populated with state
|
|
|
|
// before refreshing the member list else we get a stale list.
|
2018-09-17 18:03:01 +00:00
|
|
|
this._showMembersAccordingToMembershipWithLL();
|
2018-09-13 16:43:24 +00:00
|
|
|
},
|
|
|
|
|
2018-09-17 17:14:52 +00:00
|
|
|
onMyMembership: function(room, membership, oldMembership) {
|
|
|
|
if (room.roomId === this.props.roomId && membership === "join") {
|
2018-09-17 18:03:01 +00:00
|
|
|
this._showMembersAccordingToMembershipWithLL();
|
2018-09-13 16:43:24 +00:00
|
|
|
}
|
2015-09-18 17:39:16 +00:00
|
|
|
},
|
|
|
|
|
2015-06-22 10:42:09 +00:00
|
|
|
onRoomStateMember: function(ev, state, member) {
|
2018-09-07 12:05:26 +00:00
|
|
|
if (member.roomId !== this.props.roomId) {
|
|
|
|
return;
|
|
|
|
}
|
2015-09-18 17:39:16 +00:00
|
|
|
this._updateList();
|
|
|
|
},
|
|
|
|
|
2015-10-28 15:15:35 +00:00
|
|
|
onRoomMemberName: function(ev, member) {
|
2018-09-07 12:05:26 +00:00
|
|
|
if (member.roomId !== this.props.roomId) {
|
|
|
|
return;
|
|
|
|
}
|
2015-10-28 15:15:35 +00:00
|
|
|
this._updateList();
|
|
|
|
},
|
|
|
|
|
2016-01-22 15:11:36 +00:00
|
|
|
onRoomStateEvent: function(event, state) {
|
2018-09-07 12:05:26 +00:00
|
|
|
if (event.getRoomId() === this.props.roomId &&
|
|
|
|
event.getType() === "m.room.third_party_invite") {
|
2016-01-22 15:11:36 +00:00
|
|
|
this._updateList();
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
2016-02-04 18:06:24 +00:00
|
|
|
_updateList: new rate_limited_func(function() {
|
2019-01-04 04:55:52 +00:00
|
|
|
this._updateListNow();
|
|
|
|
}, 500),
|
|
|
|
|
|
|
|
_updateListNow: function() {
|
2016-04-18 00:45:46 +00:00
|
|
|
// console.log("Updating memberlist");
|
2017-09-22 12:15:02 +00:00
|
|
|
const newState = {
|
2018-09-13 16:43:24 +00:00
|
|
|
loading: false,
|
2017-09-22 12:15:02 +00:00
|
|
|
members: this.roomMembers(),
|
|
|
|
};
|
2017-10-14 16:09:30 +00:00
|
|
|
newState.filteredJoinedMembers = this._filterMembers(newState.members, 'join', this.state.searchQuery);
|
|
|
|
newState.filteredInvitedMembers = this._filterMembers(newState.members, 'invite', this.state.searchQuery);
|
2017-09-22 12:15:02 +00:00
|
|
|
this.setState(newState);
|
2019-01-04 04:55:52 +00:00
|
|
|
},
|
2015-06-24 12:48:39 +00:00
|
|
|
|
2018-09-07 21:44:20 +00:00
|
|
|
getMembersWithUser: function() {
|
|
|
|
if (!this.props.roomId) return [];
|
2017-10-11 16:56:17 +00:00
|
|
|
const cli = MatrixClientPeg.get();
|
|
|
|
const room = cli.getRoom(this.props.roomId);
|
2018-09-10 15:32:40 +00:00
|
|
|
if (!room) return [];
|
2015-10-09 16:24:48 +00:00
|
|
|
|
2018-09-07 21:44:20 +00:00
|
|
|
const allMembers = Object.values(room.currentState.members);
|
2015-09-18 17:39:16 +00:00
|
|
|
|
2018-09-07 21:44:20 +00:00
|
|
|
allMembers.forEach(function(member) {
|
2016-03-04 16:57:20 +00:00
|
|
|
// work around a race where you might have a room member object
|
|
|
|
// before the user object exists. This may or may not cause
|
|
|
|
// https://github.com/vector-im/vector-web/issues/186
|
2018-09-05 13:25:08 +00:00
|
|
|
if (member.user === null) {
|
|
|
|
member.user = cli.getUser(member.userId);
|
2016-03-04 16:57:20 +00:00
|
|
|
}
|
|
|
|
|
2016-04-18 00:35:40 +00:00
|
|
|
// XXX: this user may have no lastPresenceTs value!
|
|
|
|
// the right solution here is to fix the race rather than leave it as 0
|
2015-09-28 10:32:00 +00:00
|
|
|
});
|
|
|
|
|
2018-09-07 21:44:20 +00:00
|
|
|
return allMembers;
|
2015-10-09 16:24:48 +00:00
|
|
|
},
|
2015-09-18 17:39:16 +00:00
|
|
|
|
2016-07-14 17:40:43 +00:00
|
|
|
roomMembers: function() {
|
2017-10-11 16:56:17 +00:00
|
|
|
const ConferenceHandler = CallHandler.getConferenceHandler();
|
2015-09-18 17:39:16 +00:00
|
|
|
|
2018-09-07 21:44:20 +00:00
|
|
|
const allMembers = this.getMembersWithUser();
|
2018-09-05 13:25:08 +00:00
|
|
|
const filteredAndSortedMembers = allMembers.filter((m) => {
|
|
|
|
return (
|
|
|
|
m.membership === 'join' || m.membership === 'invite'
|
|
|
|
) && (
|
|
|
|
!ConferenceHandler ||
|
|
|
|
(ConferenceHandler && !ConferenceHandler.isConferenceUser(m.userId))
|
|
|
|
);
|
|
|
|
});
|
|
|
|
filteredAndSortedMembers.sort(this.memberSort);
|
|
|
|
return filteredAndSortedMembers;
|
2015-11-30 15:13:28 +00:00
|
|
|
},
|
|
|
|
|
2017-09-22 15:50:54 +00:00
|
|
|
_createOverflowTileJoined: function(overflowCount, totalCount) {
|
2017-09-22 16:01:14 +00:00
|
|
|
return this._createOverflowTile(overflowCount, totalCount, this._showMoreJoinedMemberList);
|
2017-09-22 15:50:54 +00:00
|
|
|
},
|
|
|
|
|
|
|
|
_createOverflowTileInvited: function(overflowCount, totalCount) {
|
2017-09-22 16:01:14 +00:00
|
|
|
return this._createOverflowTile(overflowCount, totalCount, this._showMoreInvitedMemberList);
|
2017-09-22 15:50:54 +00:00
|
|
|
},
|
|
|
|
|
|
|
|
_createOverflowTile: function(overflowCount, totalCount, onClick) {
|
2016-01-21 11:41:28 +00:00
|
|
|
// For now we'll pretend this is any entity. It should probably be a separate tile.
|
2017-07-25 14:56:16 +00:00
|
|
|
const EntityTile = sdk.getComponent("rooms.EntityTile");
|
|
|
|
const BaseAvatar = sdk.getComponent("avatars.BaseAvatar");
|
|
|
|
const text = _t("and %(count)s others...", { count: overflowCount });
|
2016-01-21 11:41:28 +00:00
|
|
|
return (
|
2016-01-21 17:39:12 +00:00
|
|
|
<EntityTile className="mx_EntityTile_ellipsis" avatarJsx={
|
|
|
|
<BaseAvatar url="img/ellipsis.svg" name="..." width={36} height={36} />
|
2016-01-21 15:57:59 +00:00
|
|
|
} name={text} presenceState="online" suppressOnHover={true}
|
2017-09-22 15:50:54 +00:00
|
|
|
onClick={onClick} />
|
2016-01-21 11:41:28 +00:00
|
|
|
);
|
|
|
|
},
|
2015-11-30 15:13:28 +00:00
|
|
|
|
2017-09-22 16:01:14 +00:00
|
|
|
_showMoreJoinedMemberList: function() {
|
2017-09-22 15:50:54 +00:00
|
|
|
this.setState({
|
2017-09-22 16:01:14 +00:00
|
|
|
truncateAtJoined: this.state.truncateAtJoined + SHOW_MORE_INCREMENT,
|
2017-09-22 15:50:54 +00:00
|
|
|
});
|
|
|
|
},
|
|
|
|
|
2017-09-22 16:01:14 +00:00
|
|
|
_showMoreInvitedMemberList: function() {
|
2016-01-21 15:57:59 +00:00
|
|
|
this.setState({
|
2017-09-22 16:01:14 +00:00
|
|
|
truncateAtInvited: this.state.truncateAtInvited + SHOW_MORE_INCREMENT,
|
2016-01-21 15:57:59 +00:00
|
|
|
});
|
2016-01-21 11:41:28 +00:00
|
|
|
},
|
2015-11-30 15:13:28 +00:00
|
|
|
|
2016-04-18 00:35:40 +00:00
|
|
|
memberString: function(member) {
|
|
|
|
if (!member) {
|
|
|
|
return "(null)";
|
2017-10-11 16:56:17 +00:00
|
|
|
} else {
|
2018-12-20 21:56:18 +00:00
|
|
|
const u = member.user;
|
2019-01-04 04:55:52 +00:00
|
|
|
return "(" + member.name + ", " + member.powerLevel + ", " + (u ? u.lastActiveAgo : "<null>") + ", " + (u ? u.getLastActiveTs() : "<null>") + ", " + (u ? u.currentlyActive : "<null>") + ", " + (u ? u.presence : "<null>") + ")";
|
2016-04-18 00:35:40 +00:00
|
|
|
}
|
|
|
|
},
|
2015-11-30 15:13:28 +00:00
|
|
|
|
2016-04-18 13:48:55 +00:00
|
|
|
// returns negative if a comes before b,
|
|
|
|
// returns 0 if a and b are equivalent in ordering
|
|
|
|
// returns positive if a comes after b.
|
2018-09-05 13:25:08 +00:00
|
|
|
memberSort: function(memberA, memberB) {
|
2018-12-20 21:56:18 +00:00
|
|
|
// order by presence, with "active now" first.
|
|
|
|
// ...and then by power level
|
|
|
|
// ...and then by last active
|
|
|
|
// ...and then alphabetically.
|
|
|
|
// We could tiebreak instead by "last recently spoken in this room" if we wanted to.
|
|
|
|
|
2018-12-20 21:57:27 +00:00
|
|
|
// console.log(`Comparing userA=${this.memberString(memberA)} userB=${this.memberString(memberB)}`);
|
2018-12-20 21:56:18 +00:00
|
|
|
|
|
|
|
const userA = memberA.user;
|
|
|
|
const userB = memberB.user;
|
|
|
|
|
2018-12-20 21:57:27 +00:00
|
|
|
// if (!userA) console.log("!! MISSING USER FOR A-SIDE: " + memberA.name + " !!");
|
|
|
|
// if (!userB) console.log("!! MISSING USER FOR B-SIDE: " + memberB.name + " !!");
|
2018-12-20 21:56:18 +00:00
|
|
|
|
|
|
|
if (!userA && !userB) return 0;
|
|
|
|
if (userA && !userB) return -1;
|
|
|
|
if (!userA && userB) return 1;
|
|
|
|
|
|
|
|
// First by presence
|
|
|
|
if (this._showPresence) {
|
|
|
|
const convertPresence = (p) => p === 'unavailable' ? 'online' : p;
|
|
|
|
const presenceIndex = p => {
|
|
|
|
const order = ['active', 'online', 'offline'];
|
|
|
|
const idx = order.indexOf(convertPresence(p));
|
|
|
|
return idx === -1 ? order.length : idx; // unknown states at the end
|
|
|
|
};
|
|
|
|
|
|
|
|
const idxA = presenceIndex(userA.currentlyActive ? 'active' : userA.presence);
|
|
|
|
const idxB = presenceIndex(userB.currentlyActive ? 'active' : userB.presence);
|
2018-12-20 21:57:27 +00:00
|
|
|
// console.log(`userA_presenceGroup=${idxA} userB_presenceGroup=${idxB}`);
|
2018-12-20 21:56:18 +00:00
|
|
|
if (idxA !== idxB) {
|
2018-12-20 21:57:27 +00:00
|
|
|
// console.log("Comparing on presence group - returning");
|
2018-12-20 21:56:18 +00:00
|
|
|
return idxA - idxB;
|
2016-04-18 00:35:40 +00:00
|
|
|
}
|
2018-12-20 21:56:18 +00:00
|
|
|
}
|
2016-04-18 00:35:40 +00:00
|
|
|
|
2018-12-20 21:56:18 +00:00
|
|
|
// Second by power level
|
|
|
|
if (memberA.powerLevel !== memberB.powerLevel) {
|
2018-12-20 21:57:27 +00:00
|
|
|
// console.log("Comparing on power level - returning");
|
2018-12-20 21:56:18 +00:00
|
|
|
return memberB.powerLevel - memberA.powerLevel;
|
|
|
|
}
|
2015-11-30 15:13:28 +00:00
|
|
|
|
2018-12-20 21:56:18 +00:00
|
|
|
// Third by last active
|
|
|
|
if (this._showPresence && userA.getLastActiveTs() !== userB.getLastActiveTs) {
|
2018-12-20 21:57:27 +00:00
|
|
|
// console.log("Comparing on last active timestamp - returning");
|
2016-06-29 08:45:24 +00:00
|
|
|
return userB.getLastActiveTs() - userA.getLastActiveTs();
|
2018-12-20 21:56:18 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Fourth by name (alphabetical)
|
|
|
|
const nameA = memberA.name[0] === '@' ? memberA.name.substr(1) : memberA.name;
|
|
|
|
const nameB = memberB.name[0] === '@' ? memberB.name.substr(1) : memberB.name;
|
2018-12-20 21:57:27 +00:00
|
|
|
// console.log(`Comparing userA_name=${nameA} against userB_name=${nameB} - returning`);
|
2018-12-20 21:56:18 +00:00
|
|
|
return nameA.localeCompare(nameB);
|
2015-11-30 15:13:28 +00:00
|
|
|
},
|
|
|
|
|
2016-09-15 10:31:49 +00:00
|
|
|
onSearchQueryChanged: function(ev) {
|
2017-09-22 12:15:02 +00:00
|
|
|
const q = ev.target.value;
|
|
|
|
this.setState({
|
|
|
|
searchQuery: q,
|
|
|
|
filteredJoinedMembers: this._filterMembers(this.state.members, 'join', q),
|
|
|
|
filteredInvitedMembers: this._filterMembers(this.state.members, 'invite', q),
|
|
|
|
});
|
2016-01-19 14:51:26 +00:00
|
|
|
},
|
|
|
|
|
2017-09-22 12:15:02 +00:00
|
|
|
_filterMembers: function(members, membership, query) {
|
2018-09-05 13:25:08 +00:00
|
|
|
return members.filter((m) => {
|
2016-07-13 10:56:01 +00:00
|
|
|
if (query) {
|
2017-10-14 16:09:58 +00:00
|
|
|
query = query.toLowerCase();
|
2016-07-13 10:56:01 +00:00
|
|
|
const matchesName = m.name.toLowerCase().indexOf(query) !== -1;
|
|
|
|
const matchesId = m.userId.toLowerCase().indexOf(query) !== -1;
|
|
|
|
|
|
|
|
if (!matchesName && !matchesId) {
|
|
|
|
return false;
|
|
|
|
}
|
2016-01-19 14:51:26 +00:00
|
|
|
}
|
2016-07-13 10:56:01 +00:00
|
|
|
|
2017-10-14 16:10:51 +00:00
|
|
|
return m.membership === membership;
|
2017-09-22 12:15:02 +00:00
|
|
|
});
|
|
|
|
},
|
|
|
|
|
2018-01-25 12:18:02 +00:00
|
|
|
_getPending3PidInvites: function() {
|
|
|
|
// include 3pid invites (m.room.third_party_invite) state events.
|
|
|
|
// The HS may have already converted these into m.room.member invites so
|
|
|
|
// we shouldn't add them if the 3pid invite state key (token) is in the
|
|
|
|
// member invite (content.third_party_invite.signed.token)
|
|
|
|
const room = MatrixClientPeg.get().getRoom(this.props.roomId);
|
|
|
|
|
|
|
|
if (room) {
|
|
|
|
return room.currentState.getStateEvents("m.room.third_party_invite").filter(function(e) {
|
|
|
|
// any events without these keys are not valid 3pid invites, so we ignore them
|
|
|
|
const requiredKeys = ['key_validity_url', 'public_key', 'display_name'];
|
|
|
|
for (let i = 0; i < requiredKeys.length; ++i) {
|
|
|
|
if (e.getContent()[requiredKeys[i]] === undefined) return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
// discard all invites which have a m.room.member event since we've
|
|
|
|
// already added them.
|
|
|
|
const memberEvent = room.currentState.getInviteForThreePidToken(e.getStateKey());
|
|
|
|
if (memberEvent) return false;
|
|
|
|
return true;
|
|
|
|
});
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
2017-09-22 12:15:02 +00:00
|
|
|
_makeMemberTiles: function(members, membership) {
|
|
|
|
const MemberTile = sdk.getComponent("rooms.MemberTile");
|
|
|
|
|
2018-09-05 13:25:08 +00:00
|
|
|
const memberList = members.map((m) => {
|
2015-11-30 15:13:28 +00:00
|
|
|
return (
|
2018-09-05 13:25:08 +00:00
|
|
|
<MemberTile key={m.userId} member={m} ref={m.userId} showPresence={this._showPresence} />
|
2015-11-30 15:13:28 +00:00
|
|
|
);
|
|
|
|
});
|
2016-01-13 15:55:28 +00:00
|
|
|
|
2016-03-11 14:18:30 +00:00
|
|
|
// XXX: surely this is not the right home for this logic.
|
2017-09-22 12:15:02 +00:00
|
|
|
// Double XXX: Now it's really, really not the right home for this logic:
|
|
|
|
// we shouldn't even be passing in the 'membership' param to this function.
|
|
|
|
// Ew, ew, and ew.
|
2018-01-25 12:18:02 +00:00
|
|
|
// Triple XXX: This violates the size constraint, the output is expected/desired
|
|
|
|
// to be the same length as the members input array.
|
2016-01-13 15:55:28 +00:00
|
|
|
if (membership === "invite") {
|
2017-10-11 16:56:17 +00:00
|
|
|
const EntityTile = sdk.getComponent("rooms.EntityTile");
|
2018-01-25 12:18:02 +00:00
|
|
|
memberList.push(...this._getPending3PidInvites().map((e) => {
|
2018-03-19 16:47:12 +00:00
|
|
|
return <EntityTile key={e.getStateKey()}
|
|
|
|
name={e.getContent().display_name}
|
|
|
|
suppressOnHover={true}
|
|
|
|
/>;
|
2018-01-25 12:18:02 +00:00
|
|
|
}));
|
2016-01-13 15:55:28 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return memberList;
|
2015-11-30 15:13:28 +00:00
|
|
|
},
|
|
|
|
|
2017-09-22 14:07:45 +00:00
|
|
|
_getChildrenJoined: function(start, end) {
|
|
|
|
return this._makeMemberTiles(this.state.filteredJoinedMembers.slice(start, end));
|
2017-09-22 12:15:02 +00:00
|
|
|
},
|
|
|
|
|
|
|
|
_getChildCountJoined: function() {
|
|
|
|
return this.state.filteredJoinedMembers.length;
|
|
|
|
},
|
|
|
|
|
2017-09-22 15:50:54 +00:00
|
|
|
_getChildrenInvited: function(start, end) {
|
2017-10-02 14:10:32 +00:00
|
|
|
return this._makeMemberTiles(this.state.filteredInvitedMembers.slice(start, end), 'invite');
|
2017-09-22 15:50:54 +00:00
|
|
|
},
|
|
|
|
|
|
|
|
_getChildCountInvited: function() {
|
2018-01-25 12:18:02 +00:00
|
|
|
return this.state.filteredInvitedMembers.length + (this._getPending3PidInvites() || []).length;
|
2017-09-22 15:50:54 +00:00
|
|
|
},
|
|
|
|
|
2015-11-30 15:13:28 +00:00
|
|
|
render: function() {
|
2018-08-29 08:30:25 +00:00
|
|
|
if (this.state.loading) {
|
|
|
|
const Spinner = sdk.getComponent("elements.Spinner");
|
|
|
|
return <div className="mx_MemberList"><Spinner /></div>;
|
|
|
|
}
|
|
|
|
|
2017-09-22 15:50:54 +00:00
|
|
|
const TruncatedList = sdk.getComponent("elements.TruncatedList");
|
2018-03-21 12:00:56 +00:00
|
|
|
const GeminiScrollbarWrapper = sdk.getComponent("elements.GeminiScrollbarWrapper");
|
2017-09-22 15:50:54 +00:00
|
|
|
|
2018-10-24 11:50:58 +00:00
|
|
|
const cli = MatrixClientPeg.get();
|
|
|
|
const room = cli.getRoom(this.props.roomId);
|
|
|
|
let inviteButton;
|
2018-10-24 14:44:18 +00:00
|
|
|
if (room && room.getMyMembership() === 'join') {
|
2018-10-24 11:50:58 +00:00
|
|
|
const TintableSvg = sdk.getComponent("elements.TintableSvg");
|
|
|
|
const AccessibleButton = sdk.getComponent("elements.AccessibleButton");
|
|
|
|
inviteButton =
|
|
|
|
<AccessibleButton className="mx_MemberList_invite" onClick={this.onInviteButtonClick}>
|
2018-11-06 13:47:11 +00:00
|
|
|
<span>{ _t('Invite to this room') }</span>
|
2018-10-24 11:50:58 +00:00
|
|
|
</AccessibleButton>;
|
|
|
|
}
|
|
|
|
|
|
|
|
let invitedHeader;
|
|
|
|
let invitedSection;
|
|
|
|
if (this._getChildCountInvited() > 0) {
|
|
|
|
invitedHeader = <h2>{ _t("Invited") }</h2>;
|
|
|
|
invitedSection = <TruncatedList className="mx_MemberList_section mx_MemberList_invited" truncateAt={this.state.truncateAtInvited}
|
|
|
|
createOverflowElement={this._createOverflowTileInvited}
|
|
|
|
getChildren={this._getChildrenInvited}
|
|
|
|
getChildCount={this._getChildCountInvited}
|
|
|
|
/>;
|
2015-11-30 15:13:28 +00:00
|
|
|
}
|
2016-02-08 15:05:35 +00:00
|
|
|
|
2015-11-30 15:13:28 +00:00
|
|
|
return (
|
2018-11-06 13:47:58 +00:00
|
|
|
<div className="mx_MemberList">
|
2018-11-06 13:47:11 +00:00
|
|
|
{ inviteButton }
|
2018-10-26 13:33:39 +00:00
|
|
|
<GeminiScrollbarWrapper autoshow={true}>
|
2018-10-24 11:50:58 +00:00
|
|
|
<div className="mx_MemberList_wrapper">
|
|
|
|
<TruncatedList className="mx_MemberList_section mx_MemberList_joined" truncateAt={this.state.truncateAtJoined}
|
2017-09-22 15:50:54 +00:00
|
|
|
createOverflowElement={this._createOverflowTileJoined}
|
2017-09-22 12:15:02 +00:00
|
|
|
getChildren={this._getChildrenJoined}
|
2018-10-24 11:50:58 +00:00
|
|
|
getChildCount={this._getChildCountJoined} />
|
|
|
|
{ invitedHeader }
|
|
|
|
{ invitedSection }
|
|
|
|
</div>
|
2018-03-21 12:00:56 +00:00
|
|
|
</GeminiScrollbarWrapper>
|
2018-11-06 13:47:11 +00:00
|
|
|
<input className="mx_MemberList_query mx_textinput_icon mx_textinput_search" id="mx_MemberList_query" type="text"
|
|
|
|
onChange={this.onSearchQueryChanged} value={this.state.searchQuery}
|
|
|
|
placeholder={_t('Filter room members')} />
|
2015-11-30 15:13:28 +00:00
|
|
|
</div>
|
|
|
|
);
|
2017-10-14 16:14:17 +00:00
|
|
|
},
|
2018-10-24 11:50:58 +00:00
|
|
|
|
|
|
|
onInviteButtonClick: function() {
|
|
|
|
if (MatrixClientPeg.get().isGuest()) {
|
|
|
|
dis.dispatch({action: 'require_registration'});
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// call AddressPickerDialog
|
|
|
|
dis.dispatch({
|
|
|
|
action: 'view_invite',
|
|
|
|
roomId: this.props.roomId,
|
|
|
|
});
|
|
|
|
},
|
2015-11-30 15:13:28 +00:00
|
|
|
});
|