mirror of
https://github.com/element-hq/element-web
synced 2024-10-26 04:35:55 +03:00
Merge branch 'develop' of https://github.com/matrix-org/matrix-react-sdk into rxl881/widgetrendering
This commit is contained in:
commit
853ada027d
34 changed files with 854 additions and 454 deletions
|
@ -19,7 +19,7 @@ import PlatformPeg from './PlatformPeg';
|
||||||
import SdkConfig from './SdkConfig';
|
import SdkConfig from './SdkConfig';
|
||||||
|
|
||||||
function getRedactedUrl() {
|
function getRedactedUrl() {
|
||||||
const redactedHash = window.location.hash.replace(/#\/(room|user)\/(.+)/, "#/$1/<redacted>");
|
const redactedHash = window.location.hash.replace(/#\/(group|room|user)\/(.+)/, "#/$1/<redacted>");
|
||||||
// hardcoded url to make piwik happy
|
// hardcoded url to make piwik happy
|
||||||
return 'https://riot.im/app/' + redactedHash;
|
return 'https://riot.im/app/' + redactedHash;
|
||||||
}
|
}
|
||||||
|
|
67
src/Login.js
67
src/Login.js
|
@ -143,17 +143,8 @@ export default class Login {
|
||||||
Object.assign(loginParams, legacyParams);
|
Object.assign(loginParams, legacyParams);
|
||||||
|
|
||||||
const client = this._createTemporaryClient();
|
const client = this._createTemporaryClient();
|
||||||
return client.login('m.login.password', loginParams).then(function(data) {
|
|
||||||
return Promise.resolve({
|
const tryFallbackHs = (originalError) => {
|
||||||
homeserverUrl: self._hsUrl,
|
|
||||||
identityServerUrl: self._isUrl,
|
|
||||||
userId: data.user_id,
|
|
||||||
deviceId: data.device_id,
|
|
||||||
accessToken: data.access_token,
|
|
||||||
});
|
|
||||||
}, function(error) {
|
|
||||||
if (error.httpStatus === 403) {
|
|
||||||
if (self._fallbackHsUrl) {
|
|
||||||
const fbClient = Matrix.createClient({
|
const fbClient = Matrix.createClient({
|
||||||
baseUrl: self._fallbackHsUrl,
|
baseUrl: self._fallbackHsUrl,
|
||||||
idBaseUrl: this._isUrl,
|
idBaseUrl: this._isUrl,
|
||||||
|
@ -167,12 +158,62 @@ export default class Login {
|
||||||
deviceId: data.device_id,
|
deviceId: data.device_id,
|
||||||
accessToken: data.access_token,
|
accessToken: data.access_token,
|
||||||
});
|
});
|
||||||
}, function(fallback_error) {
|
}).catch((fallback_error) => {
|
||||||
|
console.log("fallback HS login failed", fallback_error);
|
||||||
// throw the original error
|
// throw the original error
|
||||||
throw error;
|
throw originalError;
|
||||||
});
|
});
|
||||||
|
};
|
||||||
|
const tryLowercaseUsername = (originalError) => {
|
||||||
|
const loginParamsLowercase = Object.assign({}, loginParams, {
|
||||||
|
user: username.toLowerCase(),
|
||||||
|
identifier: {
|
||||||
|
user: username.toLowerCase(),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
return client.login('m.login.password', loginParamsLowercase).then(function(data) {
|
||||||
|
return Promise.resolve({
|
||||||
|
homeserverUrl: self._hsUrl,
|
||||||
|
identityServerUrl: self._isUrl,
|
||||||
|
userId: data.user_id,
|
||||||
|
deviceId: data.device_id,
|
||||||
|
accessToken: data.access_token,
|
||||||
|
});
|
||||||
|
}).catch((fallback_error) => {
|
||||||
|
console.log("Lowercase username login failed", fallback_error);
|
||||||
|
// throw the original error
|
||||||
|
throw originalError;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
let originalLoginError = null;
|
||||||
|
return client.login('m.login.password', loginParams).then(function(data) {
|
||||||
|
return Promise.resolve({
|
||||||
|
homeserverUrl: self._hsUrl,
|
||||||
|
identityServerUrl: self._isUrl,
|
||||||
|
userId: data.user_id,
|
||||||
|
deviceId: data.device_id,
|
||||||
|
accessToken: data.access_token,
|
||||||
|
});
|
||||||
|
}).catch((error) => {
|
||||||
|
originalLoginError = error;
|
||||||
|
if (error.httpStatus === 403) {
|
||||||
|
if (self._fallbackHsUrl) {
|
||||||
|
return tryFallbackHs(originalLoginError);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
throw originalLoginError;
|
||||||
|
}).catch((error) => {
|
||||||
|
if (
|
||||||
|
error.httpStatus === 403 &&
|
||||||
|
loginParams.identifier.type === 'm.id.user' &&
|
||||||
|
username.search(/[A-Z]/) > -1
|
||||||
|
) {
|
||||||
|
return tryLowercaseUsername(originalLoginError);
|
||||||
|
}
|
||||||
|
throw originalLoginError;
|
||||||
|
}).catch((error) => {
|
||||||
|
console.log("Login failed", error);
|
||||||
throw error;
|
throw error;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,6 +21,8 @@ import Modal from './Modal';
|
||||||
import { getAddressType } from './UserAddress';
|
import { getAddressType } from './UserAddress';
|
||||||
import createRoom from './createRoom';
|
import createRoom from './createRoom';
|
||||||
import sdk from './';
|
import sdk from './';
|
||||||
|
import dis from './dispatcher';
|
||||||
|
import DMRoomMap from './utils/DMRoomMap';
|
||||||
import { _t } from './languageHandler';
|
import { _t } from './languageHandler';
|
||||||
|
|
||||||
export function inviteToRoom(roomId, addr) {
|
export function inviteToRoom(roomId, addr) {
|
||||||
|
@ -79,15 +81,40 @@ function _onStartChatFinished(shouldInvite, addrs) {
|
||||||
const addrTexts = addrs.map((addr) => addr.address);
|
const addrTexts = addrs.map((addr) => addr.address);
|
||||||
|
|
||||||
if (_isDmChat(addrTexts)) {
|
if (_isDmChat(addrTexts)) {
|
||||||
|
const rooms = _getDirectMessageRooms(addrTexts[0]);
|
||||||
|
if (rooms.length > 0) {
|
||||||
|
// A Direct Message room already exists for this user, so select a
|
||||||
|
// room from a list that is similar to the one in MemberInfo panel
|
||||||
|
const ChatCreateOrReuseDialog = sdk.getComponent(
|
||||||
|
"views.dialogs.ChatCreateOrReuseDialog",
|
||||||
|
);
|
||||||
|
const close = Modal.createTrackedDialog('Create or Reuse', '', ChatCreateOrReuseDialog, {
|
||||||
|
userId: addrTexts[0],
|
||||||
|
onNewDMClick: () => {
|
||||||
|
dis.dispatch({
|
||||||
|
action: 'start_chat',
|
||||||
|
user_id: addrTexts[0],
|
||||||
|
});
|
||||||
|
close(true);
|
||||||
|
},
|
||||||
|
onExistingRoomSelected: (roomId) => {
|
||||||
|
dis.dispatch({
|
||||||
|
action: 'view_room',
|
||||||
|
room_id: roomId,
|
||||||
|
});
|
||||||
|
close(true);
|
||||||
|
},
|
||||||
|
}).close;
|
||||||
|
} else {
|
||||||
// Start a new DM chat
|
// Start a new DM chat
|
||||||
createRoom({dmUserId: addrTexts[0]}).catch((err) => {
|
createRoom({dmUserId: addrTexts[0]}).catch((err) => {
|
||||||
console.error(err.stack);
|
|
||||||
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
||||||
Modal.createTrackedDialog('Failed to invite user', '', ErrorDialog, {
|
Modal.createTrackedDialog('Failed to invite user', '', ErrorDialog, {
|
||||||
title: _t("Failed to invite user"),
|
title: _t("Failed to invite user"),
|
||||||
description: ((err && err.message) ? err.message : _t("Operation failed")),
|
description: ((err && err.message) ? err.message : _t("Operation failed")),
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
// Start multi user chat
|
// Start multi user chat
|
||||||
let room;
|
let room;
|
||||||
|
@ -153,3 +180,19 @@ function _showAnyInviteErrors(addrs, room) {
|
||||||
return addrs;
|
return addrs;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function _getDirectMessageRooms(addr) {
|
||||||
|
const dmRoomMap = new DMRoomMap(MatrixClientPeg.get());
|
||||||
|
const dmRooms = dmRoomMap.getDMRoomsForUserId(addr);
|
||||||
|
const rooms = [];
|
||||||
|
dmRooms.forEach((dmRoom) => {
|
||||||
|
const room = MatrixClientPeg.get().getRoom(dmRoom);
|
||||||
|
if (room) {
|
||||||
|
const me = room.getMember(MatrixClientPeg.get().credentials.userId);
|
||||||
|
if (me.membership == 'join') {
|
||||||
|
rooms.push(room);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return rooms;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
|
@ -68,9 +68,7 @@ module.exports = {
|
||||||
const names = whoIsTyping.map(function(m) {
|
const names = whoIsTyping.map(function(m) {
|
||||||
return m.name;
|
return m.name;
|
||||||
});
|
});
|
||||||
if (othersCount==1) {
|
if (othersCount>=1) {
|
||||||
return _t('%(names)s and one other are typing', {names: names.slice(0, limit - 1).join(', ')});
|
|
||||||
} else if (othersCount>1) {
|
|
||||||
return _t('%(names)s and %(count)s others are typing', {names: names.slice(0, limit - 1).join(', '), count: othersCount});
|
return _t('%(names)s and %(count)s others are typing', {names: names.slice(0, limit - 1).join(', '), count: othersCount});
|
||||||
} else {
|
} else {
|
||||||
const lastPerson = names.pop();
|
const lastPerson = names.pop();
|
||||||
|
|
|
@ -407,6 +407,10 @@ export default React.createClass({
|
||||||
getInitialState: function() {
|
getInitialState: function() {
|
||||||
return {
|
return {
|
||||||
summary: null,
|
summary: null,
|
||||||
|
isGroupPublicised: null,
|
||||||
|
isUserPrivileged: null,
|
||||||
|
groupRooms: null,
|
||||||
|
groupRoomsLoading: null,
|
||||||
error: null,
|
error: null,
|
||||||
editing: false,
|
editing: false,
|
||||||
saving: false,
|
saving: false,
|
||||||
|
@ -447,7 +451,7 @@ export default React.createClass({
|
||||||
|
|
||||||
_initGroupStore: function(groupId) {
|
_initGroupStore: function(groupId) {
|
||||||
this._groupStore = GroupStoreCache.getGroupStore(MatrixClientPeg.get(), groupId);
|
this._groupStore = GroupStoreCache.getGroupStore(MatrixClientPeg.get(), groupId);
|
||||||
this._groupStore.on('update', () => {
|
this._groupStore.registerListener(() => {
|
||||||
const summary = this._groupStore.getSummary();
|
const summary = this._groupStore.getSummary();
|
||||||
if (summary.profile) {
|
if (summary.profile) {
|
||||||
// Default profile fields should be "" for later sending to the server (which
|
// Default profile fields should be "" for later sending to the server (which
|
||||||
|
@ -458,13 +462,18 @@ export default React.createClass({
|
||||||
}
|
}
|
||||||
this.setState({
|
this.setState({
|
||||||
summary,
|
summary,
|
||||||
|
summaryLoading: !this._groupStore.isStateReady(GroupStore.STATE_KEY.Summary),
|
||||||
isGroupPublicised: this._groupStore.getGroupPublicity(),
|
isGroupPublicised: this._groupStore.getGroupPublicity(),
|
||||||
isUserPrivileged: this._groupStore.isUserPrivileged(),
|
isUserPrivileged: this._groupStore.isUserPrivileged(),
|
||||||
|
groupRooms: this._groupStore.getGroupRooms(),
|
||||||
|
groupRoomsLoading: !this._groupStore.isStateReady(GroupStore.STATE_KEY.GroupRooms),
|
||||||
|
isUserMember: this._groupStore.getGroupMembers().some(
|
||||||
|
(m) => m.userId === MatrixClientPeg.get().credentials.userId,
|
||||||
|
),
|
||||||
error: null,
|
error: null,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
this._groupStore.on('error', (err) => {
|
this._groupStore.on('error', (err) => {
|
||||||
console.error(err);
|
|
||||||
this.setState({
|
this.setState({
|
||||||
summary: null,
|
summary: null,
|
||||||
error: err,
|
error: err,
|
||||||
|
@ -651,6 +660,7 @@ export default React.createClass({
|
||||||
const RoomDetailList = sdk.getComponent('rooms.RoomDetailList');
|
const RoomDetailList = sdk.getComponent('rooms.RoomDetailList');
|
||||||
const AccessibleButton = sdk.getComponent('elements.AccessibleButton');
|
const AccessibleButton = sdk.getComponent('elements.AccessibleButton');
|
||||||
const TintableSvg = sdk.getComponent('elements.TintableSvg');
|
const TintableSvg = sdk.getComponent('elements.TintableSvg');
|
||||||
|
const Spinner = sdk.getComponent('elements.Spinner');
|
||||||
|
|
||||||
const addRoomRow = this.state.editing ?
|
const addRoomRow = this.state.editing ?
|
||||||
(<AccessibleButton className="mx_GroupView_rooms_header_addRow"
|
(<AccessibleButton className="mx_GroupView_rooms_header_addRow"
|
||||||
|
@ -668,7 +678,10 @@ export default React.createClass({
|
||||||
<h3>{ _t('Rooms') }</h3>
|
<h3>{ _t('Rooms') }</h3>
|
||||||
{ addRoomRow }
|
{ addRoomRow }
|
||||||
</div>
|
</div>
|
||||||
<RoomDetailList rooms={this._groupStore.getGroupRooms()} />
|
{ this.state.groupRoomsLoading ?
|
||||||
|
<Spinner /> :
|
||||||
|
<RoomDetailList rooms={this.state.groupRooms} />
|
||||||
|
}
|
||||||
</div>;
|
</div>;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -864,7 +877,7 @@ export default React.createClass({
|
||||||
const Spinner = sdk.getComponent("elements.Spinner");
|
const Spinner = sdk.getComponent("elements.Spinner");
|
||||||
const TintableSvg = sdk.getComponent("elements.TintableSvg");
|
const TintableSvg = sdk.getComponent("elements.TintableSvg");
|
||||||
|
|
||||||
if (this.state.summary === null && this.state.error === null || this.state.saving) {
|
if (this.state.summaryLoading && this.state.error === null || this.state.saving) {
|
||||||
return <Spinner />;
|
return <Spinner />;
|
||||||
} else if (this.state.summary) {
|
} else if (this.state.summary) {
|
||||||
const summary = this.state.summary;
|
const summary = this.state.summary;
|
||||||
|
@ -885,6 +898,7 @@ export default React.createClass({
|
||||||
} else {
|
} else {
|
||||||
const GroupAvatar = sdk.getComponent('avatars.GroupAvatar');
|
const GroupAvatar = sdk.getComponent('avatars.GroupAvatar');
|
||||||
avatarImage = <GroupAvatar groupId={this.props.groupId}
|
avatarImage = <GroupAvatar groupId={this.props.groupId}
|
||||||
|
groupName={this.state.profileForm.name}
|
||||||
groupAvatarUrl={this.state.profileForm.avatar_url}
|
groupAvatarUrl={this.state.profileForm.avatar_url}
|
||||||
width={48} height={48} resizeMethod='crop'
|
width={48} height={48} resizeMethod='crop'
|
||||||
/>;
|
/>;
|
||||||
|
@ -928,25 +942,28 @@ export default React.createClass({
|
||||||
tabIndex="2"
|
tabIndex="2"
|
||||||
dir="auto" />;
|
dir="auto" />;
|
||||||
} else {
|
} else {
|
||||||
|
const onGroupHeaderItemClick = this.state.isUserMember ? this._onEditClick : null;
|
||||||
const groupAvatarUrl = summary.profile ? summary.profile.avatar_url : null;
|
const groupAvatarUrl = summary.profile ? summary.profile.avatar_url : null;
|
||||||
|
const groupName = summary.profile ? summary.profile.name : null;
|
||||||
avatarNode = <GroupAvatar
|
avatarNode = <GroupAvatar
|
||||||
groupId={this.props.groupId}
|
groupId={this.props.groupId}
|
||||||
groupAvatarUrl={groupAvatarUrl}
|
groupAvatarUrl={groupAvatarUrl}
|
||||||
onClick={this._onEditClick}
|
groupName={groupName}
|
||||||
|
onClick={onGroupHeaderItemClick}
|
||||||
width={48} height={48}
|
width={48} height={48}
|
||||||
/>;
|
/>;
|
||||||
if (summary.profile && summary.profile.name) {
|
if (summary.profile && summary.profile.name) {
|
||||||
nameNode = <div onClick={this._onEditClick}>
|
nameNode = <div onClick={onGroupHeaderItemClick}>
|
||||||
<span>{ summary.profile.name }</span>
|
<span>{ summary.profile.name }</span>
|
||||||
<span className="mx_GroupView_header_groupid">
|
<span className="mx_GroupView_header_groupid">
|
||||||
({ this.props.groupId })
|
({ this.props.groupId })
|
||||||
</span>
|
</span>
|
||||||
</div>;
|
</div>;
|
||||||
} else {
|
} else {
|
||||||
nameNode = <span onClick={this._onEditClick}>{ this.props.groupId }</span>;
|
nameNode = <span onClick={onGroupHeaderItemClick}>{ this.props.groupId }</span>;
|
||||||
}
|
}
|
||||||
if (summary.profile && summary.profile.short_description) {
|
if (summary.profile && summary.profile.short_description) {
|
||||||
shortDescNode = <span onClick={this._onEditClick}>{ summary.profile.short_description }</span>;
|
shortDescNode = <span onClick={onGroupHeaderItemClick}>{ summary.profile.short_description }</span>;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (this.state.editing) {
|
if (this.state.editing) {
|
||||||
|
@ -987,6 +1004,7 @@ export default React.createClass({
|
||||||
const headerClasses = {
|
const headerClasses = {
|
||||||
mx_GroupView_header: true,
|
mx_GroupView_header: true,
|
||||||
mx_GroupView_header_view: !this.state.editing,
|
mx_GroupView_header_view: !this.state.editing,
|
||||||
|
mx_GroupView_header_isUserMember: this.state.isUserMember,
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
|
@ -62,7 +62,9 @@ const GroupTile = React.createClass({
|
||||||
const profile = this.state.profile || {};
|
const profile = this.state.profile || {};
|
||||||
const name = profile.name || this.props.groupId;
|
const name = profile.name || this.props.groupId;
|
||||||
const desc = profile.shortDescription;
|
const desc = profile.shortDescription;
|
||||||
const httpUrl = profile.avatarUrl ? this.context.matrixClient.mxcUrlToHttp(profile.avatarUrl, 50, 50) : null;
|
const httpUrl = profile.avatarUrl ? this.context.matrixClient.mxcUrlToHttp(
|
||||||
|
profile.avatarUrl, 50, 50, "crop",
|
||||||
|
) : null;
|
||||||
return <AccessibleButton className="mx_GroupTile" onClick={this.onClick}>
|
return <AccessibleButton className="mx_GroupTile" onClick={this.onClick}>
|
||||||
<div className="mx_GroupTile_avatar">
|
<div className="mx_GroupTile_avatar">
|
||||||
<BaseAvatar name={name} url={httpUrl} width={50} height={50} />
|
<BaseAvatar name={name} url={httpUrl} width={50} height={50} />
|
||||||
|
|
|
@ -166,7 +166,7 @@ module.exports = React.createClass({
|
||||||
} else if (this.state.progress === "sent_email") {
|
} else if (this.state.progress === "sent_email") {
|
||||||
resetPasswordJsx = (
|
resetPasswordJsx = (
|
||||||
<div>
|
<div>
|
||||||
{ _t('An email has been sent to') } { this.state.email }. { _t("Once you've followed the link it contains, click below") }.
|
{ _t("An email has been sent to %(emailAddress)s. Once you've followed the link it contains, click below.", { emailAddress: this.state.email }) }
|
||||||
<br />
|
<br />
|
||||||
<input className="mx_Login_submit" type="button" onClick={this.onVerify}
|
<input className="mx_Login_submit" type="button" onClick={this.onVerify}
|
||||||
value={_t('I have verified my email address')} />
|
value={_t('I have verified my email address')} />
|
||||||
|
|
|
@ -302,7 +302,7 @@ module.exports = React.createClass({
|
||||||
} : {};
|
} : {};
|
||||||
|
|
||||||
return this._matrixClient.register(
|
return this._matrixClient.register(
|
||||||
this.state.formVals.username,
|
this.state.formVals.username.toLowerCase(),
|
||||||
this.state.formVals.password,
|
this.state.formVals.password,
|
||||||
undefined, // session id: included in the auth dict already
|
undefined, // session id: included in the auth dict already
|
||||||
auth,
|
auth,
|
||||||
|
|
|
@ -24,6 +24,7 @@ export default React.createClass({
|
||||||
|
|
||||||
propTypes: {
|
propTypes: {
|
||||||
groupId: PropTypes.string,
|
groupId: PropTypes.string,
|
||||||
|
groupName: PropTypes.string,
|
||||||
groupAvatarUrl: PropTypes.string,
|
groupAvatarUrl: PropTypes.string,
|
||||||
width: PropTypes.number,
|
width: PropTypes.number,
|
||||||
height: PropTypes.number,
|
height: PropTypes.number,
|
||||||
|
@ -53,11 +54,11 @@ export default React.createClass({
|
||||||
// extract the props we use from props so we can pass any others through
|
// extract the props we use from props so we can pass any others through
|
||||||
// should consider adding this as a global rule in js-sdk?
|
// should consider adding this as a global rule in js-sdk?
|
||||||
/*eslint no-unused-vars: ["error", { "ignoreRestSiblings": true }]*/
|
/*eslint no-unused-vars: ["error", { "ignoreRestSiblings": true }]*/
|
||||||
const {groupId, groupAvatarUrl, ...otherProps} = this.props;
|
const {groupId, groupAvatarUrl, groupName, ...otherProps} = this.props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<BaseAvatar
|
<BaseAvatar
|
||||||
name={this.props.groupId[1]}
|
name={groupName || this.props.groupId[1]}
|
||||||
idName={this.props.groupId}
|
idName={this.props.groupId}
|
||||||
url={this.getGroupAvatarUrl()}
|
url={this.getGroupAvatarUrl()}
|
||||||
{...otherProps}
|
{...otherProps}
|
||||||
|
|
|
@ -36,6 +36,7 @@ export default React.createClass({
|
||||||
// group member object. Supply either this or 'member'
|
// group member object. Supply either this or 'member'
|
||||||
groupMember: GroupMemberType,
|
groupMember: GroupMemberType,
|
||||||
action: React.PropTypes.string.isRequired, // eg. 'Ban'
|
action: React.PropTypes.string.isRequired, // eg. 'Ban'
|
||||||
|
title: React.PropTypes.string.isRequired, // eg. 'Ban this user?'
|
||||||
|
|
||||||
// Whether to display a text field for a reason
|
// Whether to display a text field for a reason
|
||||||
// If true, the second argument to onFinished will
|
// If true, the second argument to onFinished will
|
||||||
|
@ -75,7 +76,6 @@ export default React.createClass({
|
||||||
const MemberAvatar = sdk.getComponent("views.avatars.MemberAvatar");
|
const MemberAvatar = sdk.getComponent("views.avatars.MemberAvatar");
|
||||||
const BaseAvatar = sdk.getComponent("views.avatars.BaseAvatar");
|
const BaseAvatar = sdk.getComponent("views.avatars.BaseAvatar");
|
||||||
|
|
||||||
const title = _t("%(actionVerb)s this person?", { actionVerb: this.props.action});
|
|
||||||
const confirmButtonClass = classnames({
|
const confirmButtonClass = classnames({
|
||||||
'mx_Dialog_primary': true,
|
'mx_Dialog_primary': true,
|
||||||
'danger': this.props.danger,
|
'danger': this.props.danger,
|
||||||
|
@ -113,7 +113,7 @@ export default React.createClass({
|
||||||
return (
|
return (
|
||||||
<BaseDialog className="mx_ConfirmUserActionDialog" onFinished={this.props.onFinished}
|
<BaseDialog className="mx_ConfirmUserActionDialog" onFinished={this.props.onFinished}
|
||||||
onEnterPressed={this.onOk}
|
onEnterPressed={this.onOk}
|
||||||
title={title}
|
title={this.props.title}
|
||||||
>
|
>
|
||||||
<div className="mx_Dialog_content">
|
<div className="mx_Dialog_content">
|
||||||
<div className="mx_ConfirmUserActionDialog_avatar">
|
<div className="mx_ConfirmUserActionDialog_avatar">
|
||||||
|
|
|
@ -55,8 +55,8 @@ export default React.createClass({
|
||||||
|
|
||||||
_checkGroupId: function(e) {
|
_checkGroupId: function(e) {
|
||||||
let error = null;
|
let error = null;
|
||||||
if (!/^[a-zA-Z0-9]*$/.test(this.state.groupId)) {
|
if (!/^[a-z0-9=_\-\.\/]*$/.test(this.state.groupId)) {
|
||||||
error = _t("Community IDs may only contain alphanumeric characters");
|
error = _t("Community IDs may only contain characters a-z, 0-9, or '=_-./'");
|
||||||
}
|
}
|
||||||
this.setState({
|
this.setState({
|
||||||
groupIdError: error,
|
groupIdError: error,
|
||||||
|
|
|
@ -86,7 +86,6 @@ module.exports = React.createClass({
|
||||||
const summaries = orderedTransitionSequences.map((transitions) => {
|
const summaries = orderedTransitionSequences.map((transitions) => {
|
||||||
const userNames = eventAggregates[transitions];
|
const userNames = eventAggregates[transitions];
|
||||||
const nameList = this._renderNameList(userNames);
|
const nameList = this._renderNameList(userNames);
|
||||||
const plural = userNames.length > 1;
|
|
||||||
|
|
||||||
const splitTransitions = transitions.split(',');
|
const splitTransitions = transitions.split(',');
|
||||||
|
|
||||||
|
@ -101,13 +100,13 @@ module.exports = React.createClass({
|
||||||
|
|
||||||
const descs = coalescedTransitions.map((t) => {
|
const descs = coalescedTransitions.map((t) => {
|
||||||
return this._getDescriptionForTransition(
|
return this._getDescriptionForTransition(
|
||||||
t.transitionType, plural, t.repeats,
|
t.transitionType, userNames.length, t.repeats,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
const desc = this._renderCommaSeparatedList(descs);
|
const desc = this._renderCommaSeparatedList(descs);
|
||||||
|
|
||||||
return nameList + " " + desc;
|
return _t('%(nameList)s %(transitionList)s', { nameList: nameList, transitionList: desc });
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!summaries) {
|
if (!summaries) {
|
||||||
|
@ -208,148 +207,75 @@ module.exports = React.createClass({
|
||||||
* For a certain transition, t, describe what happened to the users that
|
* For a certain transition, t, describe what happened to the users that
|
||||||
* underwent the transition.
|
* underwent the transition.
|
||||||
* @param {string} t the transition type.
|
* @param {string} t the transition type.
|
||||||
* @param {boolean} plural whether there were multiple users undergoing the same
|
* @param {integer} userCount number of usernames
|
||||||
* transition.
|
|
||||||
* @param {number} repeats the number of times the transition was repeated in a row.
|
* @param {number} repeats the number of times the transition was repeated in a row.
|
||||||
* @returns {string} the written Human Readable equivalent of the transition.
|
* @returns {string} the written Human Readable equivalent of the transition.
|
||||||
*/
|
*/
|
||||||
_getDescriptionForTransition(t, plural, repeats) {
|
_getDescriptionForTransition(t, userCount, repeats) {
|
||||||
// The empty interpolations 'severalUsers' and 'oneUser'
|
// The empty interpolations 'severalUsers' and 'oneUser'
|
||||||
// are there only to show translators to non-English languages
|
// are there only to show translators to non-English languages
|
||||||
// that the verb is conjugated to plural or singular Subject.
|
// that the verb is conjugated to plural or singular Subject.
|
||||||
let res = null;
|
let res = null;
|
||||||
switch(t) {
|
switch(t) {
|
||||||
case "joined":
|
case "joined":
|
||||||
if (repeats > 1) {
|
res = (userCount > 1)
|
||||||
res = (plural)
|
? _t("%(severalUsers)sjoined %(count)s times", { severalUsers: "", count: repeats })
|
||||||
? _t("%(severalUsers)sjoined %(repeats)s times", { severalUsers: "", repeats: repeats })
|
: _t("%(oneUser)sjoined %(count)s times", { oneUser: "", count: repeats });
|
||||||
: _t("%(oneUser)sjoined %(repeats)s times", { oneUser: "", repeats: repeats });
|
|
||||||
} else {
|
|
||||||
res = (plural)
|
|
||||||
? _t("%(severalUsers)sjoined", { severalUsers: "" })
|
|
||||||
: _t("%(oneUser)sjoined", { oneUser: "" });
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
case "left":
|
case "left":
|
||||||
if (repeats > 1) {
|
res = (userCount > 1)
|
||||||
res = (plural)
|
? _t("%(severalUsers)sleft %(count)s times", { severalUsers: "", count: repeats })
|
||||||
? _t("%(severalUsers)sleft %(repeats)s times", { severalUsers: "", repeats: repeats })
|
: _t("%(oneUser)sleft %(count)s times", { oneUser: "", count: repeats });
|
||||||
: _t("%(oneUser)sleft %(repeats)s times", { oneUser: "", repeats: repeats });
|
|
||||||
} else {
|
|
||||||
res = (plural)
|
|
||||||
? _t("%(severalUsers)sleft", { severalUsers: "" })
|
|
||||||
: _t("%(oneUser)sleft", { oneUser: "" });
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
case "joined_and_left":
|
case "joined_and_left":
|
||||||
if (repeats > 1) {
|
res = (userCount > 1)
|
||||||
res = (plural)
|
? _t("%(severalUsers)sjoined and left %(count)s times", { severalUsers: "", count: repeats })
|
||||||
? _t("%(severalUsers)sjoined and left %(repeats)s times", { severalUsers: "", repeats: repeats })
|
: _t("%(oneUser)sjoined and left %(count)s times", { oneUser: "", count: repeats });
|
||||||
: _t("%(oneUser)sjoined and left %(repeats)s times", { oneUser: "", repeats: repeats });
|
|
||||||
} else {
|
|
||||||
res = (plural)
|
|
||||||
? _t("%(severalUsers)sjoined and left", { severalUsers: "" })
|
|
||||||
: _t("%(oneUser)sjoined and left", { oneUser: "" });
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
case "left_and_joined":
|
case "left_and_joined":
|
||||||
if (repeats > 1) {
|
res = (userCount > 1)
|
||||||
res = (plural)
|
? _t("%(severalUsers)sleft and rejoined %(count)s times", { severalUsers: "", count: repeats })
|
||||||
? _t("%(severalUsers)sleft and rejoined %(repeats)s times", { severalUsers: "", repeats: repeats })
|
: _t("%(oneUser)sleft and rejoined %(count)s times", { oneUser: "", count: repeats });
|
||||||
: _t("%(oneUser)sleft and rejoined %(repeats)s times", { oneUser: "", repeats: repeats });
|
|
||||||
} else {
|
|
||||||
res = (plural)
|
|
||||||
? _t("%(severalUsers)sleft and rejoined", { severalUsers: "" })
|
|
||||||
: _t("%(oneUser)sleft and rejoined", { oneUser: "" });
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
case "invite_reject":
|
case "invite_reject":
|
||||||
if (repeats > 1) {
|
res = (userCount > 1)
|
||||||
res = (plural)
|
? _t("%(severalUsers)srejected their invitations %(count)s times", { severalUsers: "", count: repeats })
|
||||||
? _t("%(severalUsers)srejected their invitations %(repeats)s times", { severalUsers: "", repeats: repeats })
|
: _t("%(oneUser)srejected their invitation %(count)s times", { oneUser: "", count: repeats });
|
||||||
: _t("%(oneUser)srejected their invitation %(repeats)s times", { oneUser: "", repeats: repeats });
|
|
||||||
} else {
|
|
||||||
res = (plural)
|
|
||||||
? _t("%(severalUsers)srejected their invitations", { severalUsers: "" })
|
|
||||||
: _t("%(oneUser)srejected their invitation", { oneUser: "" });
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
case "invite_withdrawal":
|
case "invite_withdrawal":
|
||||||
if (repeats > 1) {
|
res = (userCount > 1)
|
||||||
res = (plural)
|
? _t("%(severalUsers)shad their invitations withdrawn %(count)s times", { severalUsers: "", count: repeats })
|
||||||
? _t("%(severalUsers)shad their invitations withdrawn %(repeats)s times", { severalUsers: "", repeats: repeats })
|
: _t("%(oneUser)shad their invitation withdrawn %(count)s times", { oneUser: "", count: repeats });
|
||||||
: _t("%(oneUser)shad their invitation withdrawn %(repeats)s times", { oneUser: "", repeats: repeats });
|
|
||||||
} else {
|
|
||||||
res = (plural)
|
|
||||||
? _t("%(severalUsers)shad their invitations withdrawn", { severalUsers: "" })
|
|
||||||
: _t("%(oneUser)shad their invitation withdrawn", { oneUser: "" });
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
case "invited":
|
case "invited":
|
||||||
if (repeats > 1) {
|
res = (userCount > 1)
|
||||||
res = (plural)
|
? _t("were invited %(count)s times", { count: repeats })
|
||||||
? _t("were invited %(repeats)s times", { repeats: repeats })
|
: _t("was invited %(count)s times", { count: repeats });
|
||||||
: _t("was invited %(repeats)s times", { repeats: repeats });
|
|
||||||
} else {
|
|
||||||
res = (plural)
|
|
||||||
? _t("were invited")
|
|
||||||
: _t("was invited");
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
case "banned":
|
case "banned":
|
||||||
if (repeats > 1) {
|
res = (userCount > 1)
|
||||||
res = (plural)
|
? _t("were banned %(count)s times", { count: repeats })
|
||||||
? _t("were banned %(repeats)s times", { repeats: repeats })
|
: _t("was banned %(count)s times", { count: repeats });
|
||||||
: _t("was banned %(repeats)s times", { repeats: repeats });
|
|
||||||
} else {
|
|
||||||
res = (plural)
|
|
||||||
? _t("were banned")
|
|
||||||
: _t("was banned");
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
case "unbanned":
|
case "unbanned":
|
||||||
if (repeats > 1) {
|
res = (userCount > 1)
|
||||||
res = (plural)
|
? _t("were unbanned %(count)s times", { count: repeats })
|
||||||
? _t("were unbanned %(repeats)s times", { repeats: repeats })
|
: _t("was unbanned %(count)s times", { count: repeats });
|
||||||
: _t("was unbanned %(repeats)s times", { repeats: repeats });
|
|
||||||
} else {
|
|
||||||
res = (plural)
|
|
||||||
? _t("were unbanned")
|
|
||||||
: _t("was unbanned");
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
case "kicked":
|
case "kicked":
|
||||||
if (repeats > 1) {
|
res = (userCount > 1)
|
||||||
res = (plural)
|
? _t("were kicked %(count)s times", { count: repeats })
|
||||||
? _t("were kicked %(repeats)s times", { repeats: repeats })
|
: _t("was kicked %(count)s times", { count: repeats });
|
||||||
: _t("was kicked %(repeats)s times", { repeats: repeats });
|
|
||||||
} else {
|
|
||||||
res = (plural)
|
|
||||||
? _t("were kicked")
|
|
||||||
: _t("was kicked");
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
case "changed_name":
|
case "changed_name":
|
||||||
if (repeats > 1) {
|
res = (userCount > 1)
|
||||||
res = (plural)
|
? _t("%(severalUsers)schanged their name %(count)s times", { severalUsers: "", count: repeats })
|
||||||
? _t("%(severalUsers)schanged their name %(repeats)s times", { severalUsers: "", repeats: repeats })
|
: _t("%(oneUser)schanged their name %(count)s times", { oneUser: "", count: repeats });
|
||||||
: _t("%(oneUser)schanged their name %(repeats)s times", { oneUser: "", repeats: repeats });
|
|
||||||
} else {
|
|
||||||
res = (plural)
|
|
||||||
? _t("%(severalUsers)schanged their name", { severalUsers: "" })
|
|
||||||
: _t("%(oneUser)schanged their name", { oneUser: "" });
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
case "changed_avatar":
|
case "changed_avatar":
|
||||||
if (repeats > 1) {
|
res = (userCount > 1)
|
||||||
res = (plural)
|
? _t("%(severalUsers)schanged their avatar %(count)s times", { severalUsers: "", count: repeats })
|
||||||
? _t("%(severalUsers)schanged their avatar %(repeats)s times", { severalUsers: "", repeats: repeats })
|
: _t("%(oneUser)schanged their avatar %(count)s times", { oneUser: "", count: repeats });
|
||||||
: _t("%(oneUser)schanged their avatar %(repeats)s times", { oneUser: "", repeats: repeats });
|
|
||||||
} else {
|
|
||||||
res = (plural)
|
|
||||||
? _t("%(severalUsers)schanged their avatar", { severalUsers: "" })
|
|
||||||
: _t("%(oneUser)schanged their avatar", { oneUser: "" });
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -376,11 +302,9 @@ module.exports = React.createClass({
|
||||||
return "";
|
return "";
|
||||||
} else if (items.length === 1) {
|
} else if (items.length === 1) {
|
||||||
return items[0];
|
return items[0];
|
||||||
} else if (remaining) {
|
} else if (remaining > 0) {
|
||||||
items = items.slice(0, itemLimit);
|
items = items.slice(0, itemLimit);
|
||||||
return (remaining > 1)
|
return _t("%(items)s and %(count)s others", { items: items.join(', '), count: remaining } )
|
||||||
? _t("%(items)s and %(remaining)s others", { items: items.join(', '), remaining: remaining } )
|
|
||||||
: _t("%(items)s and one other", { items: items.join(', ') });
|
|
||||||
} else {
|
} else {
|
||||||
const lastItem = items.pop();
|
const lastItem = items.pop();
|
||||||
return _t("%(items)s and %(lastItem)s", { items: items.join(', '), lastItem: lastItem });
|
return _t("%(items)s and %(lastItem)s", { items: items.join(', '), lastItem: lastItem });
|
||||||
|
|
|
@ -37,11 +37,20 @@ const Pill = React.createClass({
|
||||||
isMessagePillUrl: (url) => {
|
isMessagePillUrl: (url) => {
|
||||||
return !!REGEX_LOCAL_MATRIXTO.exec(url);
|
return !!REGEX_LOCAL_MATRIXTO.exec(url);
|
||||||
},
|
},
|
||||||
|
roomNotifPos: (text) => {
|
||||||
|
return text.indexOf("@room");
|
||||||
|
},
|
||||||
|
roomNotifLen: () => {
|
||||||
|
return "@room".length;
|
||||||
|
},
|
||||||
TYPE_USER_MENTION: 'TYPE_USER_MENTION',
|
TYPE_USER_MENTION: 'TYPE_USER_MENTION',
|
||||||
TYPE_ROOM_MENTION: 'TYPE_ROOM_MENTION',
|
TYPE_ROOM_MENTION: 'TYPE_ROOM_MENTION',
|
||||||
|
TYPE_AT_ROOM_MENTION: 'TYPE_AT_ROOM_MENTION', // '@room' mention
|
||||||
},
|
},
|
||||||
|
|
||||||
props: {
|
props: {
|
||||||
|
// The Type of this Pill. If url is given, this is auto-detected.
|
||||||
|
type: PropTypes.string,
|
||||||
// The URL to pillify (no validation is done, see isPillUrl and isMessagePillUrl)
|
// The URL to pillify (no validation is done, see isPillUrl and isMessagePillUrl)
|
||||||
url: PropTypes.string,
|
url: PropTypes.string,
|
||||||
// Whether the pill is in a message
|
// Whether the pill is in a message
|
||||||
|
@ -72,14 +81,20 @@ const Pill = React.createClass({
|
||||||
regex = REGEX_LOCAL_MATRIXTO;
|
regex = REGEX_LOCAL_MATRIXTO;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let matrixToMatch;
|
||||||
|
let resourceId;
|
||||||
|
let prefix;
|
||||||
|
|
||||||
|
if (nextProps.url) {
|
||||||
// Default to the empty array if no match for simplicity
|
// Default to the empty array if no match for simplicity
|
||||||
// resource and prefix will be undefined instead of throwing
|
// resource and prefix will be undefined instead of throwing
|
||||||
const matrixToMatch = regex.exec(nextProps.url) || [];
|
matrixToMatch = regex.exec(nextProps.url) || [];
|
||||||
|
|
||||||
const resourceId = matrixToMatch[1]; // The room/user ID
|
resourceId = matrixToMatch[1]; // The room/user ID
|
||||||
const prefix = matrixToMatch[2]; // The first character of prefix
|
prefix = matrixToMatch[2]; // The first character of prefix
|
||||||
|
}
|
||||||
|
|
||||||
const pillType = {
|
const pillType = this.props.type || {
|
||||||
'@': Pill.TYPE_USER_MENTION,
|
'@': Pill.TYPE_USER_MENTION,
|
||||||
'#': Pill.TYPE_ROOM_MENTION,
|
'#': Pill.TYPE_ROOM_MENTION,
|
||||||
'!': Pill.TYPE_ROOM_MENTION,
|
'!': Pill.TYPE_ROOM_MENTION,
|
||||||
|
@ -88,6 +103,10 @@ const Pill = React.createClass({
|
||||||
let member;
|
let member;
|
||||||
let room;
|
let room;
|
||||||
switch (pillType) {
|
switch (pillType) {
|
||||||
|
case Pill.TYPE_AT_ROOM_MENTION: {
|
||||||
|
room = nextProps.room;
|
||||||
|
}
|
||||||
|
break;
|
||||||
case Pill.TYPE_USER_MENTION: {
|
case Pill.TYPE_USER_MENTION: {
|
||||||
const localMember = nextProps.room.getMember(resourceId);
|
const localMember = nextProps.room.getMember(resourceId);
|
||||||
member = localMember;
|
member = localMember;
|
||||||
|
@ -160,6 +179,17 @@ const Pill = React.createClass({
|
||||||
let href = this.props.url;
|
let href = this.props.url;
|
||||||
let onClick;
|
let onClick;
|
||||||
switch (this.state.pillType) {
|
switch (this.state.pillType) {
|
||||||
|
case Pill.TYPE_AT_ROOM_MENTION: {
|
||||||
|
const room = this.props.room;
|
||||||
|
if (room) {
|
||||||
|
linkText = "@room";
|
||||||
|
if (this.props.shouldShowPillAvatar) {
|
||||||
|
avatar = <RoomAvatar room={room} width={16} height={16} />;
|
||||||
|
}
|
||||||
|
pillClass = 'mx_AtRoomPill';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
case Pill.TYPE_USER_MENTION: {
|
case Pill.TYPE_USER_MENTION: {
|
||||||
// If this user is not a member of this room, default to the empty member
|
// If this user is not a member of this room, default to the empty member
|
||||||
const member = this.state.member;
|
const member = this.state.member;
|
||||||
|
|
|
@ -17,50 +17,66 @@ limitations under the License.
|
||||||
|
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import { MatrixClient } from 'matrix-js-sdk';
|
||||||
import dis from '../../../dispatcher';
|
import dis from '../../../dispatcher';
|
||||||
import Modal from '../../../Modal';
|
import Modal from '../../../Modal';
|
||||||
import sdk from '../../../index';
|
import sdk from '../../../index';
|
||||||
import { _t } from '../../../languageHandler';
|
import { _t } from '../../../languageHandler';
|
||||||
import { GroupMemberType } from '../../../groups';
|
import { GroupMemberType } from '../../../groups';
|
||||||
import { groupMemberFromApiObject } from '../../../groups';
|
import GroupStoreCache from '../../../stores/GroupStoreCache';
|
||||||
import withMatrixClient from '../../../wrappers/withMatrixClient';
|
|
||||||
import AccessibleButton from '../elements/AccessibleButton';
|
import AccessibleButton from '../elements/AccessibleButton';
|
||||||
import GeminiScrollbar from 'react-gemini-scrollbar';
|
import GeminiScrollbar from 'react-gemini-scrollbar';
|
||||||
|
|
||||||
|
module.exports = React.createClass({
|
||||||
module.exports = withMatrixClient(React.createClass({
|
|
||||||
displayName: 'GroupMemberInfo',
|
displayName: 'GroupMemberInfo',
|
||||||
|
|
||||||
|
contextTypes: {
|
||||||
|
matrixClient: PropTypes.instanceOf(MatrixClient),
|
||||||
|
},
|
||||||
|
|
||||||
propTypes: {
|
propTypes: {
|
||||||
matrixClient: PropTypes.object.isRequired,
|
|
||||||
groupId: PropTypes.string,
|
groupId: PropTypes.string,
|
||||||
groupMember: GroupMemberType,
|
groupMember: GroupMemberType,
|
||||||
|
isInvited: PropTypes.bool,
|
||||||
},
|
},
|
||||||
|
|
||||||
getInitialState: function() {
|
getInitialState: function() {
|
||||||
return {
|
return {
|
||||||
fetching: false,
|
|
||||||
removingUser: false,
|
removingUser: false,
|
||||||
groupMembers: null,
|
isUserPrivilegedInGroup: null,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
componentWillMount: function() {
|
componentWillMount: function() {
|
||||||
this._fetchMembers();
|
this._initGroupStore(this.props.groupId);
|
||||||
},
|
},
|
||||||
|
|
||||||
_fetchMembers: function() {
|
componentWillReceiveProps(newProps) {
|
||||||
this.setState({fetching: true});
|
if (newProps.groupId !== this.props.groupId) {
|
||||||
this.props.matrixClient.getGroupUsers(this.props.groupId).then((result) => {
|
this._unregisterGroupStore();
|
||||||
|
this._initGroupStore(newProps.groupId);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
_initGroupStore(groupId) {
|
||||||
|
this._groupStore = GroupStoreCache.getGroupStore(
|
||||||
|
this.context.matrixClient, this.props.groupId,
|
||||||
|
);
|
||||||
|
this._groupStore.registerListener(this.onGroupStoreUpdated);
|
||||||
|
},
|
||||||
|
|
||||||
|
_unregisterGroupStore() {
|
||||||
|
if (this._groupStore) {
|
||||||
|
this._groupStore.unregisterListener(this.onGroupStoreUpdated);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
onGroupStoreUpdated: function() {
|
||||||
this.setState({
|
this.setState({
|
||||||
groupMembers: result.chunk.map((apiMember) => {
|
isUserInvited: this._groupStore.getGroupInvitedMembers().some(
|
||||||
return groupMemberFromApiObject(apiMember);
|
(m) => m.userId === this.props.groupMember.userId,
|
||||||
}),
|
),
|
||||||
fetching: false,
|
isUserPrivilegedInGroup: this._groupStore.isUserPrivileged(),
|
||||||
});
|
|
||||||
}).catch((e) => {
|
|
||||||
this.setState({fetching: false});
|
|
||||||
console.error("Failed to get group groupMember list: ", e);
|
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -68,13 +84,15 @@ module.exports = withMatrixClient(React.createClass({
|
||||||
const ConfirmUserActionDialog = sdk.getComponent("dialogs.ConfirmUserActionDialog");
|
const ConfirmUserActionDialog = sdk.getComponent("dialogs.ConfirmUserActionDialog");
|
||||||
Modal.createDialog(ConfirmUserActionDialog, {
|
Modal.createDialog(ConfirmUserActionDialog, {
|
||||||
groupMember: this.props.groupMember,
|
groupMember: this.props.groupMember,
|
||||||
action: _t('Remove from community'),
|
action: this.state.isUserInvited ? _t('Disinvite') : _t('Remove from community'),
|
||||||
|
title: this.state.isUserInvited ? _t('Disinvite this user from community?')
|
||||||
|
: _t('Remove this user from community?'),
|
||||||
danger: true,
|
danger: true,
|
||||||
onFinished: (proceed) => {
|
onFinished: (proceed) => {
|
||||||
if (!proceed) return;
|
if (!proceed) return;
|
||||||
|
|
||||||
this.setState({removingUser: true});
|
this.setState({removingUser: true});
|
||||||
this.props.matrixClient.removeUserFromGroup(
|
this.context.matrixClient.removeUserFromGroup(
|
||||||
this.props.groupId, this.props.groupMember.userId,
|
this.props.groupId, this.props.groupMember.userId,
|
||||||
).then(() => {
|
).then(() => {
|
||||||
// return to the user list
|
// return to the user list
|
||||||
|
@ -86,7 +104,9 @@ module.exports = withMatrixClient(React.createClass({
|
||||||
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
||||||
Modal.createTrackedDialog('Failed to remove user from group', '', ErrorDialog, {
|
Modal.createTrackedDialog('Failed to remove user from group', '', ErrorDialog, {
|
||||||
title: _t('Error'),
|
title: _t('Error'),
|
||||||
description: _t('Failed to remove user from community'),
|
description: this.state.isUserInvited ?
|
||||||
|
_t('Failed to withdraw invitation') :
|
||||||
|
_t('Failed to remove user from community'),
|
||||||
});
|
});
|
||||||
}).finally(() => {
|
}).finally(() => {
|
||||||
this.setState({removingUser: false});
|
this.setState({removingUser: false});
|
||||||
|
@ -111,24 +131,17 @@ module.exports = withMatrixClient(React.createClass({
|
||||||
},
|
},
|
||||||
|
|
||||||
render: function() {
|
render: function() {
|
||||||
if (this.state.fetching || this.state.removingUser) {
|
if (this.state.removingUser) {
|
||||||
const Spinner = sdk.getComponent("elements.Spinner");
|
const Spinner = sdk.getComponent("elements.Spinner");
|
||||||
return <Spinner />;
|
return <Spinner />;
|
||||||
}
|
}
|
||||||
if (!this.state.groupMembers) return null;
|
|
||||||
|
|
||||||
const targetIsInGroup = this.state.groupMembers.some((m) => {
|
let adminTools;
|
||||||
return m.userId === this.props.groupMember.userId;
|
if (this.state.isUserPrivilegedInGroup) {
|
||||||
});
|
const kickButton = (
|
||||||
|
|
||||||
let kickButton;
|
|
||||||
let adminButton;
|
|
||||||
|
|
||||||
if (targetIsInGroup) {
|
|
||||||
kickButton = (
|
|
||||||
<AccessibleButton className="mx_MemberInfo_field"
|
<AccessibleButton className="mx_MemberInfo_field"
|
||||||
onClick={this._onKick}>
|
onClick={this._onKick}>
|
||||||
{ _t('Remove from community') }
|
{ this.state.isUserInvited ? _t('Disinvite') : _t('Remove from community') }
|
||||||
</AccessibleButton>
|
</AccessibleButton>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -137,22 +150,19 @@ module.exports = withMatrixClient(React.createClass({
|
||||||
giveModButton = <AccessibleButton className="mx_MemberInfo_field" onClick={this.onModToggle}>
|
giveModButton = <AccessibleButton className="mx_MemberInfo_field" onClick={this.onModToggle}>
|
||||||
{giveOpLabel}
|
{giveOpLabel}
|
||||||
</AccessibleButton>;*/
|
</AccessibleButton>;*/
|
||||||
}
|
|
||||||
|
|
||||||
let adminTools;
|
if (kickButton) {
|
||||||
if (kickButton || adminButton) {
|
|
||||||
adminTools =
|
adminTools =
|
||||||
<div className="mx_MemberInfo_adminTools">
|
<div className="mx_MemberInfo_adminTools">
|
||||||
<h3>{ _t("Admin Tools") }</h3>
|
<h3>{ _t("Admin Tools") }</h3>
|
||||||
|
|
||||||
<div className="mx_MemberInfo_buttons">
|
<div className="mx_MemberInfo_buttons">
|
||||||
{ kickButton }
|
{ kickButton }
|
||||||
{ adminButton }
|
|
||||||
</div>
|
</div>
|
||||||
</div>;
|
</div>;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const avatarUrl = this.props.matrixClient.mxcUrlToHttp(
|
const avatarUrl = this.context.matrixClient.mxcUrlToHttp(
|
||||||
this.props.groupMember.avatarUrl,
|
this.props.groupMember.avatarUrl,
|
||||||
36, 36, 'crop',
|
36, 36, 'crop',
|
||||||
);
|
);
|
||||||
|
@ -192,4 +202,4 @@ module.exports = withMatrixClient(React.createClass({
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
}));
|
});
|
||||||
|
|
|
@ -50,12 +50,9 @@ export default withMatrixClient(React.createClass({
|
||||||
|
|
||||||
_initGroupStore: function(groupId) {
|
_initGroupStore: function(groupId) {
|
||||||
this._groupStore = GroupStoreCache.getGroupStore(this.context.matrixClient, groupId);
|
this._groupStore = GroupStoreCache.getGroupStore(this.context.matrixClient, groupId);
|
||||||
this._groupStore.on('update', () => {
|
this._groupStore.registerListener(() => {
|
||||||
this._fetchMembers();
|
this._fetchMembers();
|
||||||
});
|
});
|
||||||
this._groupStore.on('error', (err) => {
|
|
||||||
console.error(err);
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
|
|
||||||
_fetchMembers: function() {
|
_fetchMembers: function() {
|
||||||
|
|
242
src/components/views/groups/GroupRoomInfo.js
Normal file
242
src/components/views/groups/GroupRoomInfo.js
Normal file
|
@ -0,0 +1,242 @@
|
||||||
|
/*
|
||||||
|
Copyright 2017 New Vector Ltd
|
||||||
|
|
||||||
|
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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import React from 'react';
|
||||||
|
import { MatrixClient } from 'matrix-js-sdk';
|
||||||
|
import dis from '../../../dispatcher';
|
||||||
|
import Modal from '../../../Modal';
|
||||||
|
import sdk from '../../../index';
|
||||||
|
import { _t } from '../../../languageHandler';
|
||||||
|
import GroupStoreCache from '../../../stores/GroupStoreCache';
|
||||||
|
import GeminiScrollbar from 'react-gemini-scrollbar';
|
||||||
|
|
||||||
|
module.exports = React.createClass({
|
||||||
|
displayName: 'GroupRoomInfo',
|
||||||
|
|
||||||
|
contextTypes: {
|
||||||
|
matrixClient: PropTypes.instanceOf(MatrixClient),
|
||||||
|
},
|
||||||
|
|
||||||
|
propTypes: {
|
||||||
|
groupId: PropTypes.string,
|
||||||
|
groupRoomId: PropTypes.string,
|
||||||
|
},
|
||||||
|
|
||||||
|
getInitialState: function() {
|
||||||
|
return {
|
||||||
|
isUserPrivilegedInGroup: null,
|
||||||
|
groupRoom: null,
|
||||||
|
groupRoomPublicityLoading: false,
|
||||||
|
groupRoomRemoveLoading: false,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
componentWillMount: function() {
|
||||||
|
this._initGroupStore(this.props.groupId);
|
||||||
|
},
|
||||||
|
|
||||||
|
componentWillReceiveProps(newProps) {
|
||||||
|
if (newProps.groupId !== this.props.groupId) {
|
||||||
|
this._unregisterGroupStore();
|
||||||
|
this._initGroupStore(newProps.groupId);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
componentWillUnmount() {
|
||||||
|
this._unregisterGroupStore();
|
||||||
|
},
|
||||||
|
|
||||||
|
_initGroupStore(groupId) {
|
||||||
|
this._groupStore = GroupStoreCache.getGroupStore(
|
||||||
|
this.context.matrixClient, this.props.groupId,
|
||||||
|
);
|
||||||
|
this._groupStore.registerListener(this.onGroupStoreUpdated);
|
||||||
|
},
|
||||||
|
|
||||||
|
_unregisterGroupStore() {
|
||||||
|
if (this._groupStore) {
|
||||||
|
this._groupStore.unregisterListener(this.onGroupStoreUpdated);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
_updateGroupRoom() {
|
||||||
|
this.setState({
|
||||||
|
groupRoom: this._groupStore.getGroupRooms().find(
|
||||||
|
(r) => r.roomId === this.props.groupRoomId,
|
||||||
|
),
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
onGroupStoreUpdated: function() {
|
||||||
|
this.setState({
|
||||||
|
isUserPrivilegedInGroup: this._groupStore.isUserPrivileged(),
|
||||||
|
});
|
||||||
|
this._updateGroupRoom();
|
||||||
|
},
|
||||||
|
|
||||||
|
_onRemove: function(e) {
|
||||||
|
const groupId = this.props.groupId;
|
||||||
|
const roomName = this.state.groupRoom.displayname;
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
const QuestionDialog = sdk.getComponent("dialogs.QuestionDialog");
|
||||||
|
Modal.createTrackedDialog('Confirm removal of group from room', '', QuestionDialog, {
|
||||||
|
title: _t("Are you sure you want to remove '%(roomName)s' from %(groupId)s?", {roomName, groupId}),
|
||||||
|
description: _t("Removing a room from the community will also remove it from the community page."),
|
||||||
|
button: _t("Remove"),
|
||||||
|
onFinished: (proceed) => {
|
||||||
|
if (!proceed) return;
|
||||||
|
this.setState({groupRoomRemoveLoading: true});
|
||||||
|
const groupId = this.props.groupId;
|
||||||
|
const roomId = this.props.groupRoomId;
|
||||||
|
this._groupStore.removeRoomFromGroup(roomId).then(() => {
|
||||||
|
dis.dispatch({
|
||||||
|
action: "view_group_room_list",
|
||||||
|
});
|
||||||
|
}).catch((err) => {
|
||||||
|
console.error(`Error whilst removing ${roomId} from ${groupId}`, err);
|
||||||
|
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
||||||
|
Modal.createTrackedDialog('Failed to remove room from group', '', ErrorDialog, {
|
||||||
|
title: _t("Failed to remove room from community"),
|
||||||
|
description: _t(
|
||||||
|
"Failed to remove '%(roomName)s' from %(groupId)s", {groupId, roomName},
|
||||||
|
),
|
||||||
|
});
|
||||||
|
}).finally(() => {
|
||||||
|
this.setState({groupRoomRemoveLoading: false});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
_onCancel: function(e) {
|
||||||
|
dis.dispatch({
|
||||||
|
action: "view_group_room_list",
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
_changeGroupRoomPublicity(e) {
|
||||||
|
const isPublic = e.target.value === "public";
|
||||||
|
this.setState({
|
||||||
|
groupRoomPublicityLoading: true,
|
||||||
|
});
|
||||||
|
const groupId = this.props.groupId;
|
||||||
|
const roomId = this.props.groupRoomId;
|
||||||
|
const roomName = this.state.groupRoom.displayname;
|
||||||
|
this._groupStore.updateGroupRoomAssociation(roomId, isPublic).catch((err) => {
|
||||||
|
console.error(`Error whilst changing visibility of ${roomId} in ${groupId} to ${isPublic}`, err);
|
||||||
|
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
||||||
|
Modal.createTrackedDialog('Failed to remove room from group', '', ErrorDialog, {
|
||||||
|
title: _t("Something went wrong!"),
|
||||||
|
description: _t(
|
||||||
|
"The visibility of '%(roomName)s' in %(groupId)s could not be updated.",
|
||||||
|
{roomName, groupId},
|
||||||
|
),
|
||||||
|
});
|
||||||
|
}).finally(() => {
|
||||||
|
this.setState({
|
||||||
|
groupRoomPublicityLoading: false,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
render: function() {
|
||||||
|
const BaseAvatar = sdk.getComponent('avatars.BaseAvatar');
|
||||||
|
const EmojiText = sdk.getComponent('elements.EmojiText');
|
||||||
|
const AccessibleButton = sdk.getComponent('elements.AccessibleButton');
|
||||||
|
const InlineSpinner = sdk.getComponent('elements.InlineSpinner');
|
||||||
|
if (this.state.groupRoomRemoveLoading || !this.state.groupRoom) {
|
||||||
|
const Spinner = sdk.getComponent("elements.Spinner");
|
||||||
|
return <div className="mx_MemberInfo">
|
||||||
|
<Spinner />
|
||||||
|
</div>;
|
||||||
|
}
|
||||||
|
|
||||||
|
let adminTools;
|
||||||
|
if (this.state.isUserPrivilegedInGroup) {
|
||||||
|
adminTools =
|
||||||
|
<div className="mx_MemberInfo_adminTools">
|
||||||
|
<h3>{ _t("Admin Tools") }</h3>
|
||||||
|
<div className="mx_MemberInfo_buttons">
|
||||||
|
<AccessibleButton className="mx_MemberInfo_field" onClick={this._onRemove}>
|
||||||
|
{ _t('Remove from community') }
|
||||||
|
</AccessibleButton>
|
||||||
|
</div>
|
||||||
|
<h3>
|
||||||
|
{ _t('Visibility in Room List') }
|
||||||
|
{ this.state.groupRoomPublicityLoading ?
|
||||||
|
<InlineSpinner /> : <div />
|
||||||
|
}
|
||||||
|
</h3>
|
||||||
|
<div>
|
||||||
|
<label>
|
||||||
|
<input type="radio"
|
||||||
|
value="public"
|
||||||
|
checked={this.state.groupRoom.isPublic}
|
||||||
|
onClick={this._changeGroupRoomPublicity}
|
||||||
|
/>
|
||||||
|
<div className="mx_MemberInfo_label_text">
|
||||||
|
{ _t('Visible to everyone') }
|
||||||
|
</div>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label>
|
||||||
|
<input type="radio"
|
||||||
|
value="private"
|
||||||
|
checked={!this.state.groupRoom.isPublic}
|
||||||
|
onClick={this._changeGroupRoomPublicity}
|
||||||
|
/>
|
||||||
|
<div className="mx_MemberInfo_label_text">
|
||||||
|
{ _t('Only visible to community members') }
|
||||||
|
</div>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>;
|
||||||
|
}
|
||||||
|
|
||||||
|
const avatarUrl = this.context.matrixClient.mxcUrlToHttp(
|
||||||
|
this.state.groupRoom.avatarUrl,
|
||||||
|
36, 36, 'crop',
|
||||||
|
);
|
||||||
|
|
||||||
|
const groupRoomName = this.state.groupRoom.displayname;
|
||||||
|
const avatar = <BaseAvatar name={groupRoomName} width={36} height={36} url={avatarUrl} />;
|
||||||
|
return (
|
||||||
|
<div className="mx_MemberInfo">
|
||||||
|
<GeminiScrollbar autoshow={true}>
|
||||||
|
<AccessibleButton className="mx_MemberInfo_cancel" onClick={this._onCancel}>
|
||||||
|
<img src="img/cancel.svg" width="18" height="18" className="mx_filterFlipColor" />
|
||||||
|
</AccessibleButton>
|
||||||
|
<div className="mx_MemberInfo_avatar">
|
||||||
|
{ avatar }
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<EmojiText element="h2">{ groupRoomName }</EmojiText>
|
||||||
|
|
||||||
|
<div className="mx_MemberInfo_profile">
|
||||||
|
<div className="mx_MemberInfo_profileField">
|
||||||
|
{ this.state.groupRoom.canonical_alias }
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{ adminTools }
|
||||||
|
</GeminiScrollbar>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
});
|
|
@ -47,16 +47,14 @@ export default React.createClass({
|
||||||
|
|
||||||
_initGroupStore: function(groupId) {
|
_initGroupStore: function(groupId) {
|
||||||
this._groupStore = GroupStoreCache.getGroupStore(this.context.matrixClient, groupId);
|
this._groupStore = GroupStoreCache.getGroupStore(this.context.matrixClient, groupId);
|
||||||
this._groupStore.on('update', () => {
|
this._groupStore.registerListener(() => {
|
||||||
this._fetchRooms();
|
this._fetchRooms();
|
||||||
});
|
});
|
||||||
this._groupStore.on('error', (err) => {
|
this._groupStore.on('error', (err) => {
|
||||||
console.error('Error in group store (listened to by GroupRoomList)', err);
|
|
||||||
this.setState({
|
this.setState({
|
||||||
rooms: null,
|
rooms: null,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
this._fetchRooms();
|
|
||||||
},
|
},
|
||||||
|
|
||||||
_fetchRooms: function() {
|
_fetchRooms: function() {
|
||||||
|
|
|
@ -16,13 +16,10 @@ limitations under the License.
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import {MatrixClient} from 'matrix-js-sdk';
|
import {MatrixClient} from 'matrix-js-sdk';
|
||||||
import { _t } from '../../../languageHandler';
|
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import sdk from '../../../index';
|
import sdk from '../../../index';
|
||||||
import dis from '../../../dispatcher';
|
import dis from '../../../dispatcher';
|
||||||
import { GroupRoomType } from '../../../groups';
|
import { GroupRoomType } from '../../../groups';
|
||||||
import GroupStoreCache from '../../../stores/GroupStoreCache';
|
|
||||||
import Modal from '../../../Modal';
|
|
||||||
|
|
||||||
const GroupRoomTile = React.createClass({
|
const GroupRoomTile = React.createClass({
|
||||||
displayName: 'GroupRoomTile',
|
displayName: 'GroupRoomTile',
|
||||||
|
@ -32,68 +29,11 @@ const GroupRoomTile = React.createClass({
|
||||||
groupRoom: GroupRoomType.isRequired,
|
groupRoom: GroupRoomType.isRequired,
|
||||||
},
|
},
|
||||||
|
|
||||||
getInitialState: function() {
|
|
||||||
return {
|
|
||||||
name: this.calculateRoomName(this.props.groupRoom),
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
componentWillReceiveProps: function(newProps) {
|
|
||||||
this.setState({
|
|
||||||
name: this.calculateRoomName(newProps.groupRoom),
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
calculateRoomName: function(groupRoom) {
|
|
||||||
return groupRoom.name || groupRoom.canonicalAlias || _t("Unnamed Room");
|
|
||||||
},
|
|
||||||
|
|
||||||
removeRoomFromGroup: function() {
|
|
||||||
const groupId = this.props.groupId;
|
|
||||||
const groupStore = GroupStoreCache.getGroupStore(this.context.matrixClient, groupId);
|
|
||||||
const roomName = this.state.name;
|
|
||||||
const roomId = this.props.groupRoom.roomId;
|
|
||||||
groupStore.removeRoomFromGroup(roomId)
|
|
||||||
.catch((err) => {
|
|
||||||
console.error(`Error whilst removing ${roomId} from ${groupId}`, err);
|
|
||||||
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
|
||||||
Modal.createTrackedDialog('Failed to remove room from group', '', ErrorDialog, {
|
|
||||||
title: _t("Failed to remove room from community"),
|
|
||||||
description: _t("Failed to remove '%(roomName)s' from %(groupId)s", {groupId, roomName}),
|
|
||||||
});
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
onClick: function(e) {
|
onClick: function(e) {
|
||||||
let roomId;
|
|
||||||
let roomAlias;
|
|
||||||
if (this.props.groupRoom.canonicalAlias) {
|
|
||||||
roomAlias = this.props.groupRoom.canonicalAlias;
|
|
||||||
} else {
|
|
||||||
roomId = this.props.groupRoom.roomId;
|
|
||||||
}
|
|
||||||
dis.dispatch({
|
dis.dispatch({
|
||||||
action: 'view_room',
|
action: 'view_group_room',
|
||||||
room_id: roomId,
|
groupId: this.props.groupId,
|
||||||
room_alias: roomAlias,
|
groupRoomId: this.props.groupRoom.roomId,
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
onDeleteClick: function(e) {
|
|
||||||
const groupId = this.props.groupId;
|
|
||||||
const roomName = this.state.name;
|
|
||||||
e.preventDefault();
|
|
||||||
e.stopPropagation();
|
|
||||||
const QuestionDialog = sdk.getComponent("dialogs.QuestionDialog");
|
|
||||||
Modal.createTrackedDialog('Confirm removal of group from room', '', QuestionDialog, {
|
|
||||||
title: _t("Are you sure you want to remove '%(roomName)s' from %(groupId)s?", {roomName, groupId}),
|
|
||||||
description: _t("Removing a room from the community will also remove it from the community page."),
|
|
||||||
button: _t("Remove"),
|
|
||||||
onFinished: (success) => {
|
|
||||||
if (success) {
|
|
||||||
this.removeRoomFromGroup();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -106,7 +46,7 @@ const GroupRoomTile = React.createClass({
|
||||||
);
|
);
|
||||||
|
|
||||||
const av = (
|
const av = (
|
||||||
<BaseAvatar name={this.state.name}
|
<BaseAvatar name={this.props.groupRoom.displayname}
|
||||||
width={36} height={36}
|
width={36} height={36}
|
||||||
url={avatarUrl}
|
url={avatarUrl}
|
||||||
/>
|
/>
|
||||||
|
@ -118,14 +58,8 @@ const GroupRoomTile = React.createClass({
|
||||||
{ av }
|
{ av }
|
||||||
</div>
|
</div>
|
||||||
<div className="mx_GroupRoomTile_name">
|
<div className="mx_GroupRoomTile_name">
|
||||||
{ this.state.name }
|
{ this.props.groupRoom.displayname }
|
||||||
</div>
|
</div>
|
||||||
<AccessibleButton className="mx_GroupRoomTile_delete"
|
|
||||||
onClick={this.onDeleteClick}
|
|
||||||
tooltip={_t("Remove this room from the community")}
|
|
||||||
>
|
|
||||||
<img src="img/cancel.svg" width="15" height="15" className="mx_filterFlipColor" />
|
|
||||||
</AccessibleButton>
|
|
||||||
</AccessibleButton>
|
</AccessibleButton>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
|
@ -20,7 +20,7 @@ import url from 'url';
|
||||||
import classnames from 'classnames';
|
import classnames from 'classnames';
|
||||||
|
|
||||||
import sdk from '../../../index';
|
import sdk from '../../../index';
|
||||||
import { _t } from '../../../languageHandler';
|
import { _t, _tJsx } from '../../../languageHandler';
|
||||||
|
|
||||||
/* This file contains a collection of components which are used by the
|
/* This file contains a collection of components which are used by the
|
||||||
* InteractiveAuth to prompt the user to enter the information needed
|
* InteractiveAuth to prompt the user to enter the information needed
|
||||||
|
@ -256,7 +256,7 @@ export const EmailIdentityAuthEntry = React.createClass({
|
||||||
} else {
|
} else {
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<p>{ _t("An email has been sent to") } <i>{ this.props.inputs.emailAddress }</i></p>
|
<p>{ _tJsx("An email has been sent to %(emailAddress)s", /%\(emailAddress\)s/, (sub) => <i>{this.props.inputs.emailAddress}</i>) }</p>
|
||||||
<p>{ _t("Please check your email to continue registration.") }</p>
|
<p>{ _t("Please check your email to continue registration.") }</p>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@ -370,7 +370,7 @@ export const MsisdnAuthEntry = React.createClass({
|
||||||
});
|
});
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<p>{ _t("A text message has been sent to") } +<i>{ this._msisdn }</i></p>
|
<p>{ _tJsx("A text message has been sent to %(msisdn)s", /%\(msisdn\)s/, (sub) => <i>{this._msisdn}</i>) }</p>
|
||||||
<p>{ _t("Please enter the code it contains:") }</p>
|
<p>{ _t("Please enter the code it contains:") }</p>
|
||||||
<div className="mx_InteractiveAuthEntryComponents_msisdnWrapper">
|
<div className="mx_InteractiveAuthEntryComponents_msisdnWrapper">
|
||||||
<form onSubmit={this._onFormSubmit}>
|
<form onSubmit={this._onFormSubmit}>
|
||||||
|
|
|
@ -19,6 +19,7 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import sdk from '../../../index';
|
import sdk from '../../../index';
|
||||||
import Flair from '../elements/Flair.js';
|
import Flair from '../elements/Flair.js';
|
||||||
|
import { _tJsx } from '../../../languageHandler';
|
||||||
|
|
||||||
export default function SenderProfile(props) {
|
export default function SenderProfile(props) {
|
||||||
const EmojiText = sdk.getComponent('elements.EmojiText');
|
const EmojiText = sdk.getComponent('elements.EmojiText');
|
||||||
|
@ -30,23 +31,39 @@ export default function SenderProfile(props) {
|
||||||
return <span />; // emote message must include the name so don't duplicate it
|
return <span />; // emote message must include the name so don't duplicate it
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
// Name + flair
|
||||||
<div className="mx_SenderProfile" dir="auto" onClick={props.onClick}>
|
const nameElem = [
|
||||||
<EmojiText className="mx_SenderProfile_name">{ name || '' }</EmojiText>
|
<EmojiText key='name' className="mx_SenderProfile_name">{ name || '' }</EmojiText>,
|
||||||
{ props.enableFlair ?
|
props.enableFlair ?
|
||||||
<Flair
|
<Flair key='flair'
|
||||||
userId={mxEvent.getSender()}
|
userId={mxEvent.getSender()}
|
||||||
roomId={mxEvent.getRoomId()}
|
roomId={mxEvent.getRoomId()}
|
||||||
showRelated={true} />
|
showRelated={true} />
|
||||||
: null
|
: null,
|
||||||
|
];
|
||||||
|
|
||||||
|
let content = '';
|
||||||
|
|
||||||
|
if(props.text) {
|
||||||
|
// Replace senderName, and wrap surrounding text in spans with the right class
|
||||||
|
content = _tJsx(props.text, /^(.*)\%\(senderName\)s(.*)$/m, (p1, p2) => [
|
||||||
|
p1 ? <span className='mx_SenderProfile_aux'>{ p1 }</span> : null,
|
||||||
|
nameElem,
|
||||||
|
p2 ? <span className='mx_SenderProfile_aux'>{ p2 }</span> : null,
|
||||||
|
]);
|
||||||
|
} else {
|
||||||
|
content = nameElem;
|
||||||
}
|
}
|
||||||
{ props.aux ? <EmojiText className="mx_SenderProfile_aux"> { props.aux }</EmojiText> : null }
|
|
||||||
|
return (
|
||||||
|
<div className="mx_SenderProfile" dir="auto" onClick={props.onClick}>
|
||||||
|
{ content }
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
SenderProfile.propTypes = {
|
SenderProfile.propTypes = {
|
||||||
mxEvent: React.PropTypes.object.isRequired, // event whose sender we're showing
|
mxEvent: React.PropTypes.object.isRequired, // event whose sender we're showing
|
||||||
aux: React.PropTypes.string, // stuff to go after the sender name, if anything
|
text: React.PropTypes.string, // Text to show. Defaults to sender name
|
||||||
onClick: React.PropTypes.func,
|
onClick: React.PropTypes.func,
|
||||||
};
|
};
|
||||||
|
|
|
@ -34,6 +34,7 @@ import MatrixClientPeg from '../../../MatrixClientPeg';
|
||||||
import ContextualMenu from '../../structures/ContextualMenu';
|
import ContextualMenu from '../../structures/ContextualMenu';
|
||||||
import {RoomMember} from 'matrix-js-sdk';
|
import {RoomMember} from 'matrix-js-sdk';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
|
import PushProcessor from 'matrix-js-sdk/lib/pushprocessor';
|
||||||
|
|
||||||
linkifyMatrix(linkify);
|
linkifyMatrix(linkify);
|
||||||
|
|
||||||
|
@ -169,8 +170,10 @@ module.exports = React.createClass({
|
||||||
|
|
||||||
pillifyLinks: function(nodes) {
|
pillifyLinks: function(nodes) {
|
||||||
const shouldShowPillAvatar = !UserSettingsStore.getSyncedSetting("Pill.shouldHidePillAvatar", false);
|
const shouldShowPillAvatar = !UserSettingsStore.getSyncedSetting("Pill.shouldHidePillAvatar", false);
|
||||||
for (let i = 0; i < nodes.length; i++) {
|
let node = nodes[0];
|
||||||
const node = nodes[i];
|
while (node) {
|
||||||
|
let pillified = false;
|
||||||
|
|
||||||
if (node.tagName === "A" && node.getAttribute("href")) {
|
if (node.tagName === "A" && node.getAttribute("href")) {
|
||||||
const href = node.getAttribute("href");
|
const href = node.getAttribute("href");
|
||||||
|
|
||||||
|
@ -189,10 +192,68 @@ module.exports = React.createClass({
|
||||||
|
|
||||||
ReactDOM.render(pill, pillContainer);
|
ReactDOM.render(pill, pillContainer);
|
||||||
node.parentNode.replaceChild(pillContainer, node);
|
node.parentNode.replaceChild(pillContainer, node);
|
||||||
|
// Pills within pills aren't going to go well, so move on
|
||||||
|
pillified = true;
|
||||||
}
|
}
|
||||||
} else if (node.children && node.children.length) {
|
} else if (node.nodeType == Node.TEXT_NODE) {
|
||||||
this.pillifyLinks(node.children);
|
const Pill = sdk.getComponent('elements.Pill');
|
||||||
|
|
||||||
|
let currentTextNode = node;
|
||||||
|
const roomNotifTextNodes = [];
|
||||||
|
|
||||||
|
// Take a textNode and break it up to make all the instances of @room their
|
||||||
|
// own textNode, adding those nodes to roomNotifTextNodes
|
||||||
|
while (currentTextNode !== null) {
|
||||||
|
const roomNotifPos = Pill.roomNotifPos(currentTextNode.textContent);
|
||||||
|
let nextTextNode = null;
|
||||||
|
if (roomNotifPos > -1) {
|
||||||
|
let roomTextNode = currentTextNode;
|
||||||
|
|
||||||
|
if (roomNotifPos > 0) roomTextNode = roomTextNode.splitText(roomNotifPos);
|
||||||
|
if (roomTextNode.textContent.length > Pill.roomNotifLen()) {
|
||||||
|
nextTextNode = roomTextNode.splitText(Pill.roomNotifLen());
|
||||||
}
|
}
|
||||||
|
roomNotifTextNodes.push(roomTextNode);
|
||||||
|
}
|
||||||
|
currentTextNode = nextTextNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (roomNotifTextNodes.length > 0) {
|
||||||
|
const pushProcessor = new PushProcessor(MatrixClientPeg.get());
|
||||||
|
const atRoomRule = pushProcessor.getPushRuleById(".m.rule.roomnotif");
|
||||||
|
if (pushProcessor.ruleMatchesEvent(atRoomRule, this.props.mxEvent)) {
|
||||||
|
// Now replace all those nodes with Pills
|
||||||
|
for (const roomNotifTextNode of roomNotifTextNodes) {
|
||||||
|
const pillContainer = document.createElement('span');
|
||||||
|
const room = MatrixClientPeg.get().getRoom(this.props.mxEvent.getRoomId());
|
||||||
|
const pill = <Pill
|
||||||
|
type={Pill.TYPE_AT_ROOM_MENTION}
|
||||||
|
inMessage={true}
|
||||||
|
room={room}
|
||||||
|
shouldShowPillAvatar={true}
|
||||||
|
/>;
|
||||||
|
|
||||||
|
ReactDOM.render(pill, pillContainer);
|
||||||
|
roomNotifTextNode.parentNode.replaceChild(pillContainer, roomNotifTextNode);
|
||||||
|
|
||||||
|
// Set the next node to be processed to the one after the node
|
||||||
|
// we're adding now, since we've just inserted nodes into the structure
|
||||||
|
// we're iterating over.
|
||||||
|
// Note we've checked roomNotifTextNodes.length > 0 so we'll do this at least once
|
||||||
|
node = roomNotifTextNode.nextSibling;
|
||||||
|
}
|
||||||
|
// Nothing else to do for a text node (and we don't need to advance
|
||||||
|
// the loop pointer because we did it above)
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (node.childNodes && node.childNodes.length && !pillified) {
|
||||||
|
this.pillifyLinks(node.childNodes);
|
||||||
|
}
|
||||||
|
|
||||||
|
node = node.nextSibling;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
|
@ -19,7 +19,7 @@ limitations under the License.
|
||||||
|
|
||||||
const React = require('react');
|
const React = require('react');
|
||||||
const classNames = require("classnames");
|
const classNames = require("classnames");
|
||||||
import { _t } from '../../../languageHandler';
|
import { _t, _td } from '../../../languageHandler';
|
||||||
const Modal = require('../../../Modal');
|
const Modal = require('../../../Modal');
|
||||||
|
|
||||||
const sdk = require('../../../index');
|
const sdk = require('../../../index');
|
||||||
|
@ -502,12 +502,12 @@ module.exports = withMatrixClient(React.createClass({
|
||||||
}
|
}
|
||||||
|
|
||||||
if (needsSenderProfile) {
|
if (needsSenderProfile) {
|
||||||
let aux = null;
|
let text = null;
|
||||||
if (!this.props.tileShape) {
|
if (!this.props.tileShape) {
|
||||||
if (msgtype === 'm.image') aux = _t('sent an image');
|
if (msgtype === 'm.image') text = _td('%(senderName)s sent an image');
|
||||||
else if (msgtype === 'm.video') aux = _t('sent a video');
|
else if (msgtype === 'm.video') text = _td('%(senderName)s sent a video');
|
||||||
else if (msgtype === 'm.file') aux = _t('uploaded a file');
|
else if (msgtype === 'm.file') text = _td('%(senderName)s uploaded a file');
|
||||||
sender = <SenderProfile onClick={this.onSenderProfileClick} mxEvent={this.props.mxEvent} enableFlair={!aux} aux={aux} />;
|
sender = <SenderProfile onClick={this.onSenderProfileClick} mxEvent={this.props.mxEvent} enableFlair={!text} text={text} />;
|
||||||
} else {
|
} else {
|
||||||
sender = <SenderProfile mxEvent={this.props.mxEvent} enableFlair={true} />;
|
sender = <SenderProfile mxEvent={this.props.mxEvent} enableFlair={true} />;
|
||||||
}
|
}
|
||||||
|
|
|
@ -256,11 +256,11 @@ module.exports = withMatrixClient(React.createClass({
|
||||||
|
|
||||||
onKick: function() {
|
onKick: function() {
|
||||||
const membership = this.props.member.membership;
|
const membership = this.props.member.membership;
|
||||||
const kickLabel = membership === "invite" ? _t("Disinvite") : _t("Kick");
|
|
||||||
const ConfirmUserActionDialog = sdk.getComponent("dialogs.ConfirmUserActionDialog");
|
const ConfirmUserActionDialog = sdk.getComponent("dialogs.ConfirmUserActionDialog");
|
||||||
Modal.createTrackedDialog('Confirm User Action Dialog', 'onKick', ConfirmUserActionDialog, {
|
Modal.createTrackedDialog('Confirm User Action Dialog', 'onKick', ConfirmUserActionDialog, {
|
||||||
member: this.props.member,
|
member: this.props.member,
|
||||||
action: kickLabel,
|
action: membership === "invite" ? _t("Disinvite") : _t("Kick"),
|
||||||
|
title: membership === "invite" ? _t("Disinvite this user?") : _t("Kick this user?"),
|
||||||
askReason: membership === "join",
|
askReason: membership === "join",
|
||||||
danger: true,
|
danger: true,
|
||||||
onFinished: (proceed, reason) => {
|
onFinished: (proceed, reason) => {
|
||||||
|
@ -294,6 +294,7 @@ module.exports = withMatrixClient(React.createClass({
|
||||||
Modal.createTrackedDialog('Confirm User Action Dialog', 'onBanOrUnban', ConfirmUserActionDialog, {
|
Modal.createTrackedDialog('Confirm User Action Dialog', 'onBanOrUnban', ConfirmUserActionDialog, {
|
||||||
member: this.props.member,
|
member: this.props.member,
|
||||||
action: this.props.member.membership === 'ban' ? _t("Unban") : _t("Ban"),
|
action: this.props.member.membership === 'ban' ? _t("Unban") : _t("Ban"),
|
||||||
|
title: this.props.member.membership === 'ban' ? _t("Unban this user?") : _t("Ban this user?"),
|
||||||
askReason: this.props.member.membership !== 'ban',
|
askReason: this.props.member.membership !== 'ban',
|
||||||
danger: this.props.member.membership !== 'ban',
|
danger: this.props.member.membership !== 'ban',
|
||||||
onFinished: (proceed, reason) => {
|
onFinished: (proceed, reason) => {
|
||||||
|
|
|
@ -29,7 +29,8 @@ function getDisplayAliasForRoom(room) {
|
||||||
}
|
}
|
||||||
|
|
||||||
const RoomDetailRow = React.createClass({
|
const RoomDetailRow = React.createClass({
|
||||||
propTypes: PropTypes.shape({
|
propTypes: {
|
||||||
|
room: PropTypes.shape({
|
||||||
name: PropTypes.string,
|
name: PropTypes.string,
|
||||||
topic: PropTypes.string,
|
topic: PropTypes.string,
|
||||||
roomId: PropTypes.string,
|
roomId: PropTypes.string,
|
||||||
|
@ -41,6 +42,7 @@ const RoomDetailRow = React.createClass({
|
||||||
worldReadable: PropTypes.bool,
|
worldReadable: PropTypes.bool,
|
||||||
guestCanJoin: PropTypes.bool,
|
guestCanJoin: PropTypes.bool,
|
||||||
}),
|
}),
|
||||||
|
},
|
||||||
|
|
||||||
onClick: function(ev) {
|
onClick: function(ev) {
|
||||||
ev.preventDefault();
|
ev.preventDefault();
|
||||||
|
|
|
@ -34,27 +34,18 @@ const Receipt = require('../../../utils/Receipt');
|
||||||
const HIDE_CONFERENCE_CHANS = true;
|
const HIDE_CONFERENCE_CHANS = true;
|
||||||
|
|
||||||
function phraseForSection(section) {
|
function phraseForSection(section) {
|
||||||
// These would probably be better as individual strings,
|
|
||||||
// but for some reason we have translations for these strings
|
|
||||||
// as-is, so keeping it like this for now.
|
|
||||||
let verb;
|
|
||||||
switch (section) {
|
switch (section) {
|
||||||
case 'm.favourite':
|
case 'm.favourite':
|
||||||
verb = _t('to favourite');
|
return _t('Drop here to favourite');
|
||||||
break;
|
|
||||||
case 'im.vector.fake.direct':
|
case 'im.vector.fake.direct':
|
||||||
verb = _t('to tag direct chat');
|
return _t('Drop here to tag direct chat');
|
||||||
break;
|
|
||||||
case 'im.vector.fake.recent':
|
case 'im.vector.fake.recent':
|
||||||
verb = _t('to restore');
|
return _t('Drop here to restore');
|
||||||
break;
|
|
||||||
case 'm.lowpriority':
|
case 'm.lowpriority':
|
||||||
verb = _t('to demote');
|
return _t('Drop here to demote');
|
||||||
break;
|
|
||||||
default:
|
default:
|
||||||
return _t('Drop here to tag %(section)s', {section: section});
|
return _t('Drop here to tag %(section)s', {section: section});
|
||||||
}
|
}
|
||||||
return _t('Drop here %(toAction)s', {toAction: verb});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = React.createClass({
|
module.exports = React.createClass({
|
||||||
|
|
|
@ -83,10 +83,8 @@ module.exports = React.createClass({
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
_roomNameElement: function(fallback) {
|
_roomNameElement: function() {
|
||||||
fallback = fallback || _t('a room');
|
return this.props.room ? this.props.room.name : (this.props.room_alias || "");
|
||||||
const name = this.props.room ? this.props.room.name : (this.props.room_alias || "");
|
|
||||||
return name ? name : fallback;
|
|
||||||
},
|
},
|
||||||
|
|
||||||
render: function() {
|
render: function() {
|
||||||
|
@ -150,7 +148,7 @@ module.exports = React.createClass({
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
} else if (kicked || banned) {
|
} else if (kicked || banned) {
|
||||||
const roomName = this._roomNameElement(_t('This room'));
|
const roomName = this._roomNameElement();
|
||||||
const kickerMember = this.props.room.currentState.getMember(
|
const kickerMember = this.props.room.currentState.getMember(
|
||||||
myMember.events.member.getSender(),
|
myMember.events.member.getSender(),
|
||||||
);
|
);
|
||||||
|
@ -167,9 +165,17 @@ module.exports = React.createClass({
|
||||||
|
|
||||||
let actionText;
|
let actionText;
|
||||||
if (kicked) {
|
if (kicked) {
|
||||||
|
if(roomName) {
|
||||||
actionText = _t("You have been kicked from %(roomName)s by %(userName)s.", {roomName: roomName, userName: kickerName});
|
actionText = _t("You have been kicked from %(roomName)s by %(userName)s.", {roomName: roomName, userName: kickerName});
|
||||||
|
} else {
|
||||||
|
actionText = _t("You have been kicked from this room by %(userName)s.", {userName: kickerName});
|
||||||
|
}
|
||||||
} else if (banned) {
|
} else if (banned) {
|
||||||
|
if(roomName) {
|
||||||
actionText = _t("You have been banned from %(roomName)s by %(userName)s.", {roomName: roomName, userName: kickerName});
|
actionText = _t("You have been banned from %(roomName)s by %(userName)s.", {roomName: roomName, userName: kickerName});
|
||||||
|
} else {
|
||||||
|
actionText = _t("You have been banned from this room by %(userName)s.", {userName: kickerName});
|
||||||
|
}
|
||||||
} // no other options possible due to the kicked || banned check above.
|
} // no other options possible due to the kicked || banned check above.
|
||||||
|
|
||||||
joinBlock = (
|
joinBlock = (
|
||||||
|
@ -203,7 +209,7 @@ module.exports = React.createClass({
|
||||||
joinBlock = (
|
joinBlock = (
|
||||||
<div>
|
<div>
|
||||||
<div className="mx_RoomPreviewBar_join_text">
|
<div className="mx_RoomPreviewBar_join_text">
|
||||||
{ _t('You are trying to access %(roomName)s.', {roomName: name}) }
|
{ name ? _t('You are trying to access %(roomName)s.', {roomName: name}) : _t('You are trying to access a room.') }
|
||||||
<br />
|
<br />
|
||||||
{ _tJsx("<a>Click here</a> to join the discussion!",
|
{ _tJsx("<a>Click here</a> to join the discussion!",
|
||||||
/<a>(.*?)<\/a>/,
|
/<a>(.*?)<\/a>/,
|
||||||
|
|
|
@ -71,6 +71,7 @@ const BannedUser = React.createClass({
|
||||||
Modal.createTrackedDialog('Confirm User Action Dialog', 'onUnbanClick', ConfirmUserActionDialog, {
|
Modal.createTrackedDialog('Confirm User Action Dialog', 'onUnbanClick', ConfirmUserActionDialog, {
|
||||||
member: this.props.member,
|
member: this.props.member,
|
||||||
action: _t('Unban'),
|
action: _t('Unban'),
|
||||||
|
title: _t('Unban this user?'),
|
||||||
danger: false,
|
danger: false,
|
||||||
onFinished: (proceed) => {
|
onFinished: (proceed) => {
|
||||||
if (!proceed) return;
|
if (!proceed) return;
|
||||||
|
@ -866,21 +867,21 @@ module.exports = React.createClass({
|
||||||
disabled={!roomState.mayClientSendStateEvent("m.room.history_visibility", cli)}
|
disabled={!roomState.mayClientSendStateEvent("m.room.history_visibility", cli)}
|
||||||
checked={historyVisibility === "shared"}
|
checked={historyVisibility === "shared"}
|
||||||
onChange={this._onHistoryRadioToggle} />
|
onChange={this._onHistoryRadioToggle} />
|
||||||
{ _t('Members only') } ({ _t('since the point in time of selecting this option') })
|
{ _t('Members only (since the point in time of selecting this option)') }
|
||||||
</label>
|
</label>
|
||||||
<label>
|
<label>
|
||||||
<input type="radio" name="historyVis" value="invited"
|
<input type="radio" name="historyVis" value="invited"
|
||||||
disabled={!roomState.mayClientSendStateEvent("m.room.history_visibility", cli)}
|
disabled={!roomState.mayClientSendStateEvent("m.room.history_visibility", cli)}
|
||||||
checked={historyVisibility === "invited"}
|
checked={historyVisibility === "invited"}
|
||||||
onChange={this._onHistoryRadioToggle} />
|
onChange={this._onHistoryRadioToggle} />
|
||||||
{ _t('Members only') } ({ _t('since they were invited') })
|
{ _t('Members only (since they were invited)') }
|
||||||
</label>
|
</label>
|
||||||
<label >
|
<label >
|
||||||
<input type="radio" name="historyVis" value="joined"
|
<input type="radio" name="historyVis" value="joined"
|
||||||
disabled={!roomState.mayClientSendStateEvent("m.room.history_visibility", cli)}
|
disabled={!roomState.mayClientSendStateEvent("m.room.history_visibility", cli)}
|
||||||
checked={historyVisibility === "joined"}
|
checked={historyVisibility === "joined"}
|
||||||
onChange={this._onHistoryRadioToggle} />
|
onChange={this._onHistoryRadioToggle} />
|
||||||
{ _t('Members only') } ({ _t('since they joined') })
|
{ _t('Members only (since they joined)') }
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -184,7 +184,8 @@ module.exports = React.createClass({
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
onClickChange: function() {
|
onClickChange: function(ev) {
|
||||||
|
ev.preventDefault();
|
||||||
const oldPassword = this.state.cachedPassword || this.refs.old_input.value;
|
const oldPassword = this.state.cachedPassword || this.refs.old_input.value;
|
||||||
const newPassword = this.refs.new_input.value;
|
const newPassword = this.refs.new_input.value;
|
||||||
const confirmPassword = this.refs.confirm_input.value;
|
const confirmPassword = this.refs.confirm_input.value;
|
||||||
|
|
|
@ -15,6 +15,7 @@ limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
|
import { _t } from './languageHandler.js';
|
||||||
|
|
||||||
export const GroupMemberType = PropTypes.shape({
|
export const GroupMemberType = PropTypes.shape({
|
||||||
userId: PropTypes.string.isRequired,
|
userId: PropTypes.string.isRequired,
|
||||||
|
@ -23,6 +24,7 @@ export const GroupMemberType = PropTypes.shape({
|
||||||
});
|
});
|
||||||
|
|
||||||
export const GroupRoomType = PropTypes.shape({
|
export const GroupRoomType = PropTypes.shape({
|
||||||
|
displayname: PropTypes.string,
|
||||||
name: PropTypes.string,
|
name: PropTypes.string,
|
||||||
roomId: PropTypes.string.isRequired,
|
roomId: PropTypes.string.isRequired,
|
||||||
canonicalAlias: PropTypes.string,
|
canonicalAlias: PropTypes.string,
|
||||||
|
@ -39,6 +41,7 @@ export function groupMemberFromApiObject(apiObject) {
|
||||||
|
|
||||||
export function groupRoomFromApiObject(apiObject) {
|
export function groupRoomFromApiObject(apiObject) {
|
||||||
return {
|
return {
|
||||||
|
displayname: apiObject.name || apiObject.canonical_alias || _t("Unnamed Room"),
|
||||||
name: apiObject.name,
|
name: apiObject.name,
|
||||||
roomId: apiObject.room_id,
|
roomId: apiObject.room_id,
|
||||||
canonicalAlias: apiObject.canonical_alias,
|
canonicalAlias: apiObject.canonical_alias,
|
||||||
|
@ -47,5 +50,6 @@ export function groupRoomFromApiObject(apiObject) {
|
||||||
numJoinedMembers: apiObject.num_joined_members,
|
numJoinedMembers: apiObject.num_joined_members,
|
||||||
worldReadable: apiObject.world_readable,
|
worldReadable: apiObject.world_readable,
|
||||||
guestCanJoin: apiObject.guest_can_join,
|
guestCanJoin: apiObject.guest_can_join,
|
||||||
|
isPublic: apiObject.is_public !== false,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -152,13 +152,13 @@
|
||||||
"%(widgetName)s widget removed by %(senderName)s": "%(widgetName)s widget removed by %(senderName)s",
|
"%(widgetName)s widget removed by %(senderName)s": "%(widgetName)s widget removed by %(senderName)s",
|
||||||
"Communities": "Communities",
|
"Communities": "Communities",
|
||||||
"Message Pinning": "Message Pinning",
|
"Message Pinning": "Message Pinning",
|
||||||
"Mention": "Mention",
|
|
||||||
"%(displayName)s is typing": "%(displayName)s is typing",
|
"%(displayName)s is typing": "%(displayName)s is typing",
|
||||||
"%(names)s and one other are typing": "%(names)s and one other are typing",
|
|
||||||
"%(names)s and %(count)s others are typing|other": "%(names)s and %(count)s others are typing",
|
"%(names)s and %(count)s others are typing|other": "%(names)s and %(count)s others are typing",
|
||||||
|
"%(names)s and %(count)s others are typing|one": "%(names)s and one other is typing",
|
||||||
"%(names)s and %(lastPerson)s are typing": "%(names)s and %(lastPerson)s are typing",
|
"%(names)s and %(lastPerson)s are typing": "%(names)s and %(lastPerson)s are typing",
|
||||||
"Failure to create room": "Failure to create room",
|
"Failure to create room": "Failure to create room",
|
||||||
"Server may be unavailable, overloaded, or you hit a bug.": "Server may be unavailable, overloaded, or you hit a bug.",
|
"Server may be unavailable, overloaded, or you hit a bug.": "Server may be unavailable, overloaded, or you hit a bug.",
|
||||||
|
"Unnamed Room": "Unnamed Room",
|
||||||
"Your browser does not support the required cryptography extensions": "Your browser does not support the required cryptography extensions",
|
"Your browser does not support the required cryptography extensions": "Your browser does not support the required cryptography extensions",
|
||||||
"Not a valid Riot keyfile": "Not a valid Riot keyfile",
|
"Not a valid Riot keyfile": "Not a valid Riot keyfile",
|
||||||
"Authentication check failed: incorrect password?": "Authentication check failed: incorrect password?",
|
"Authentication check failed: incorrect password?": "Authentication check failed: incorrect password?",
|
||||||
|
@ -211,9 +211,9 @@
|
||||||
" (unsupported)": " (unsupported)",
|
" (unsupported)": " (unsupported)",
|
||||||
"Join as <voiceText>voice</voiceText> or <videoText>video</videoText>.": "Join as <voiceText>voice</voiceText> or <videoText>video</videoText>.",
|
"Join as <voiceText>voice</voiceText> or <videoText>video</videoText>.": "Join as <voiceText>voice</voiceText> or <videoText>video</videoText>.",
|
||||||
"Ongoing conference call%(supportedText)s.": "Ongoing conference call%(supportedText)s.",
|
"Ongoing conference call%(supportedText)s.": "Ongoing conference call%(supportedText)s.",
|
||||||
"sent an image": "sent an image",
|
"%(senderName)s sent an image": "%(senderName)s sent an image",
|
||||||
"sent a video": "sent a video",
|
"%(senderName)s sent a video": "%(senderName)s sent a video",
|
||||||
"uploaded a file": "uploaded a file",
|
"%(senderName)s uploaded a file": "%(senderName)s uploaded a file",
|
||||||
"Options": "Options",
|
"Options": "Options",
|
||||||
"Undecryptable": "Undecryptable",
|
"Undecryptable": "Undecryptable",
|
||||||
"Encrypted by a verified device": "Encrypted by a verified device",
|
"Encrypted by a verified device": "Encrypted by a verified device",
|
||||||
|
@ -226,9 +226,13 @@
|
||||||
"device id: ": "device id: ",
|
"device id: ": "device id: ",
|
||||||
"Disinvite": "Disinvite",
|
"Disinvite": "Disinvite",
|
||||||
"Kick": "Kick",
|
"Kick": "Kick",
|
||||||
|
"Disinvite this user?": "Disinvite this user?",
|
||||||
|
"Kick this user?": "Kick this user?",
|
||||||
"Failed to kick": "Failed to kick",
|
"Failed to kick": "Failed to kick",
|
||||||
"Unban": "Unban",
|
"Unban": "Unban",
|
||||||
"Ban": "Ban",
|
"Ban": "Ban",
|
||||||
|
"Unban this user?": "Unban this user?",
|
||||||
|
"Ban this user?": "Ban this user?",
|
||||||
"Failed to ban user": "Failed to ban user",
|
"Failed to ban user": "Failed to ban user",
|
||||||
"Failed to mute user": "Failed to mute user",
|
"Failed to mute user": "Failed to mute user",
|
||||||
"Failed to toggle moderator status": "Failed to toggle moderator status",
|
"Failed to toggle moderator status": "Failed to toggle moderator status",
|
||||||
|
@ -240,6 +244,7 @@
|
||||||
"Unignore": "Unignore",
|
"Unignore": "Unignore",
|
||||||
"Ignore": "Ignore",
|
"Ignore": "Ignore",
|
||||||
"Jump to read receipt": "Jump to read receipt",
|
"Jump to read receipt": "Jump to read receipt",
|
||||||
|
"Mention": "Mention",
|
||||||
"Invite": "Invite",
|
"Invite": "Invite",
|
||||||
"User Options": "User Options",
|
"User Options": "User Options",
|
||||||
"Direct chats": "Direct chats",
|
"Direct chats": "Direct chats",
|
||||||
|
@ -314,12 +319,11 @@
|
||||||
"Forget room": "Forget room",
|
"Forget room": "Forget room",
|
||||||
"Search": "Search",
|
"Search": "Search",
|
||||||
"Show panel": "Show panel",
|
"Show panel": "Show panel",
|
||||||
"to favourite": "to favourite",
|
"Drop here to favourite": "Drop here to favourite",
|
||||||
"to tag direct chat": "to tag direct chat",
|
"Drop here to tag direct chat": "Drop here to tag direct chat",
|
||||||
"to restore": "to restore",
|
"Drop here to restore": "Drop here to restore",
|
||||||
"to demote": "to demote",
|
"Drop here to demote": "Drop here to demote",
|
||||||
"Drop here to tag %(section)s": "Drop here to tag %(section)s",
|
"Drop here to tag %(section)s": "Drop here to tag %(section)s",
|
||||||
"Drop here %(toAction)s": "Drop here %(toAction)s",
|
|
||||||
"Press <StartChatButton> to start a chat with someone": "Press <StartChatButton> to start a chat with someone",
|
"Press <StartChatButton> to start a chat with someone": "Press <StartChatButton> to start a chat with someone",
|
||||||
"You're not in any rooms yet! Press <CreateRoomButton> to make a room or <RoomDirectoryButton> to browse the directory": "You're not in any rooms yet! Press <CreateRoomButton> to make a room or <RoomDirectoryButton> to browse the directory",
|
"You're not in any rooms yet! Press <CreateRoomButton> to make a room or <RoomDirectoryButton> to browse the directory": "You're not in any rooms yet! Press <CreateRoomButton> to make a room or <RoomDirectoryButton> to browse the directory",
|
||||||
"Invites": "Invites",
|
"Invites": "Invites",
|
||||||
|
@ -328,21 +332,22 @@
|
||||||
"Rooms": "Rooms",
|
"Rooms": "Rooms",
|
||||||
"Low priority": "Low priority",
|
"Low priority": "Low priority",
|
||||||
"Historical": "Historical",
|
"Historical": "Historical",
|
||||||
"Unnamed Room": "Unnamed Room",
|
|
||||||
"a room": "a room",
|
|
||||||
"Unable to ascertain that the address this invite was sent to matches one associated with your account.": "Unable to ascertain that the address this invite was sent to matches one associated with your account.",
|
"Unable to ascertain that the address this invite was sent to matches one associated with your account.": "Unable to ascertain that the address this invite was sent to matches one associated with your account.",
|
||||||
"This invitation was sent to an email address which is not associated with this account:": "This invitation was sent to an email address which is not associated with this account:",
|
"This invitation was sent to an email address which is not associated with this account:": "This invitation was sent to an email address which is not associated with this account:",
|
||||||
"You may wish to login with a different account, or add this email to this account.": "You may wish to login with a different account, or add this email to this account.",
|
"You may wish to login with a different account, or add this email to this account.": "You may wish to login with a different account, or add this email to this account.",
|
||||||
"You have been invited to join this room by %(inviterName)s": "You have been invited to join this room by %(inviterName)s",
|
"You have been invited to join this room by %(inviterName)s": "You have been invited to join this room by %(inviterName)s",
|
||||||
"Would you like to <acceptText>accept</acceptText> or <declineText>decline</declineText> this invitation?": "Would you like to <acceptText>accept</acceptText> or <declineText>decline</declineText> this invitation?",
|
"Would you like to <acceptText>accept</acceptText> or <declineText>decline</declineText> this invitation?": "Would you like to <acceptText>accept</acceptText> or <declineText>decline</declineText> this invitation?",
|
||||||
"This room": "This room",
|
|
||||||
"Reason: %(reasonText)s": "Reason: %(reasonText)s",
|
"Reason: %(reasonText)s": "Reason: %(reasonText)s",
|
||||||
"Rejoin": "Rejoin",
|
"Rejoin": "Rejoin",
|
||||||
"You have been kicked from %(roomName)s by %(userName)s.": "You have been kicked from %(roomName)s by %(userName)s.",
|
"You have been kicked from %(roomName)s by %(userName)s.": "You have been kicked from %(roomName)s by %(userName)s.",
|
||||||
|
"You have been kicked from this room by %(userName)s.": "You have been kicked from this room by %(userName)s.",
|
||||||
"You have been banned from %(roomName)s by %(userName)s.": "You have been banned from %(roomName)s by %(userName)s.",
|
"You have been banned from %(roomName)s by %(userName)s.": "You have been banned from %(roomName)s by %(userName)s.",
|
||||||
|
"You have been banned from this room by %(userName)s.": "You have been banned from this room by %(userName)s.",
|
||||||
|
"This room": "This room",
|
||||||
"%(roomName)s does not exist.": "%(roomName)s does not exist.",
|
"%(roomName)s does not exist.": "%(roomName)s does not exist.",
|
||||||
"%(roomName)s is not accessible at this time.": "%(roomName)s is not accessible at this time.",
|
"%(roomName)s is not accessible at this time.": "%(roomName)s is not accessible at this time.",
|
||||||
"You are trying to access %(roomName)s.": "You are trying to access %(roomName)s.",
|
"You are trying to access %(roomName)s.": "You are trying to access %(roomName)s.",
|
||||||
|
"You are trying to access a room.": "You are trying to access a room.",
|
||||||
"<a>Click here</a> to join the discussion!": "<a>Click here</a> to join the discussion!",
|
"<a>Click here</a> to join the discussion!": "<a>Click here</a> to join the discussion!",
|
||||||
"This is a preview of this room. Room interactions have been disabled": "This is a preview of this room. Room interactions have been disabled",
|
"This is a preview of this room. Room interactions have been disabled": "This is a preview of this room. Room interactions have been disabled",
|
||||||
"To change the room's avatar, you must be a": "To change the room's avatar, you must be a",
|
"To change the room's avatar, you must be a": "To change the room's avatar, you must be a",
|
||||||
|
@ -387,10 +392,9 @@
|
||||||
"Publish this room to the public in %(domain)s's room directory?": "Publish this room to the public in %(domain)s's room directory?",
|
"Publish this room to the public in %(domain)s's room directory?": "Publish this room to the public in %(domain)s's room directory?",
|
||||||
"Who can read history?": "Who can read history?",
|
"Who can read history?": "Who can read history?",
|
||||||
"Anyone": "Anyone",
|
"Anyone": "Anyone",
|
||||||
"Members only": "Members only",
|
"Members only (since the point in time of selecting this option)": "Members only (since the point in time of selecting this option)",
|
||||||
"since the point in time of selecting this option": "since the point in time of selecting this option",
|
"Members only (since they were invited)": "Members only (since they were invited)",
|
||||||
"since they were invited": "since they were invited",
|
"Members only (since they joined)": "Members only (since they joined)",
|
||||||
"since they joined": "since they joined",
|
|
||||||
"Room Colour": "Room Colour",
|
"Room Colour": "Room Colour",
|
||||||
"Permissions": "Permissions",
|
"Permissions": "Permissions",
|
||||||
"The default role for new room members is": "The default role for new room members is",
|
"The default role for new room members is": "The default role for new room members is",
|
||||||
|
@ -463,10 +467,10 @@
|
||||||
"Dismiss": "Dismiss",
|
"Dismiss": "Dismiss",
|
||||||
"To continue, please enter your password.": "To continue, please enter your password.",
|
"To continue, please enter your password.": "To continue, please enter your password.",
|
||||||
"Password:": "Password:",
|
"Password:": "Password:",
|
||||||
"An email has been sent to": "An email has been sent to",
|
"An email has been sent to %(emailAddress)s": "An email has been sent to %(emailAddress)s",
|
||||||
"Please check your email to continue registration.": "Please check your email to continue registration.",
|
"Please check your email to continue registration.": "Please check your email to continue registration.",
|
||||||
"Token incorrect": "Token incorrect",
|
"Token incorrect": "Token incorrect",
|
||||||
"A text message has been sent to": "A text message has been sent to",
|
"A text message has been sent to %(msisdn)s": "A text message has been sent to %(msisdn)s",
|
||||||
"Please enter the code it contains:": "Please enter the code it contains:",
|
"Please enter the code it contains:": "Please enter the code it contains:",
|
||||||
"Start authentication": "Start authentication",
|
"Start authentication": "Start authentication",
|
||||||
"powered by Matrix": "powered by Matrix",
|
"powered by Matrix": "powered by Matrix",
|
||||||
|
@ -488,15 +492,22 @@
|
||||||
"Identity server URL": "Identity server URL",
|
"Identity server URL": "Identity server URL",
|
||||||
"What does this mean?": "What does this mean?",
|
"What does this mean?": "What does this mean?",
|
||||||
"Remove from community": "Remove from community",
|
"Remove from community": "Remove from community",
|
||||||
|
"Disinvite this user from community?": "Disinvite this user from community?",
|
||||||
|
"Remove this user from community?": "Remove this user from community?",
|
||||||
|
"Failed to withdraw invitation": "Failed to withdraw invitation",
|
||||||
"Failed to remove user from community": "Failed to remove user from community",
|
"Failed to remove user from community": "Failed to remove user from community",
|
||||||
"Filter community members": "Filter community members",
|
"Filter community members": "Filter community members",
|
||||||
"Filter community rooms": "Filter community rooms",
|
|
||||||
"Failed to remove room from community": "Failed to remove room from community",
|
|
||||||
"Failed to remove '%(roomName)s' from %(groupId)s": "Failed to remove '%(roomName)s' from %(groupId)s",
|
|
||||||
"Are you sure you want to remove '%(roomName)s' from %(groupId)s?": "Are you sure you want to remove '%(roomName)s' from %(groupId)s?",
|
"Are you sure you want to remove '%(roomName)s' from %(groupId)s?": "Are you sure you want to remove '%(roomName)s' from %(groupId)s?",
|
||||||
"Removing a room from the community will also remove it from the community page.": "Removing a room from the community will also remove it from the community page.",
|
"Removing a room from the community will also remove it from the community page.": "Removing a room from the community will also remove it from the community page.",
|
||||||
"Remove": "Remove",
|
"Remove": "Remove",
|
||||||
"Remove this room from the community": "Remove this room from the community",
|
"Failed to remove room from community": "Failed to remove room from community",
|
||||||
|
"Failed to remove '%(roomName)s' from %(groupId)s": "Failed to remove '%(roomName)s' from %(groupId)s",
|
||||||
|
"Something went wrong!": "Something went wrong!",
|
||||||
|
"The visibility of '%(roomName)s' in %(groupId)s could not be updated.": "The visibility of '%(roomName)s' in %(groupId)s could not be updated.",
|
||||||
|
"Visibility in Room List": "Visibility in Room List",
|
||||||
|
"Visible to everyone": "Visible to everyone",
|
||||||
|
"Only visible to community members": "Only visible to community members",
|
||||||
|
"Filter community rooms": "Filter community rooms",
|
||||||
"Unknown Address": "Unknown Address",
|
"Unknown Address": "Unknown Address",
|
||||||
"NOTE: Apps are not end-to-end encrypted": "NOTE: Apps are not end-to-end encrypted",
|
"NOTE: Apps are not end-to-end encrypted": "NOTE: Apps are not end-to-end encrypted",
|
||||||
"Do you want to load widget from URL:": "Do you want to load widget from URL:",
|
"Do you want to load widget from URL:": "Do you want to load widget from URL:",
|
||||||
|
@ -516,56 +527,57 @@
|
||||||
"Integrations Error": "Integrations Error",
|
"Integrations Error": "Integrations Error",
|
||||||
"Could not connect to the integration server": "Could not connect to the integration server",
|
"Could not connect to the integration server": "Could not connect to the integration server",
|
||||||
"Manage Integrations": "Manage Integrations",
|
"Manage Integrations": "Manage Integrations",
|
||||||
"%(severalUsers)sjoined %(repeats)s times": "%(severalUsers)sjoined %(repeats)s times",
|
"%(nameList)s %(transitionList)s": "%(nameList)s %(transitionList)s",
|
||||||
"%(oneUser)sjoined %(repeats)s times": "%(oneUser)sjoined %(repeats)s times",
|
"%(severalUsers)sjoined %(count)s times|other": "%(severalUsers)sjoined %(count)s times",
|
||||||
"%(severalUsers)sjoined": "%(severalUsers)sjoined",
|
"%(severalUsers)sjoined %(count)s times|one": "%(severalUsers)sjoined",
|
||||||
"%(oneUser)sjoined": "%(oneUser)sjoined",
|
"%(oneUser)sjoined %(count)s times|other": "%(oneUser)sjoined %(count)s times",
|
||||||
"%(severalUsers)sleft %(repeats)s times": "%(severalUsers)sleft %(repeats)s times",
|
"%(oneUser)sjoined %(count)s times|one": "%(oneUser)sjoined",
|
||||||
"%(oneUser)sleft %(repeats)s times": "%(oneUser)sleft %(repeats)s times",
|
"%(severalUsers)sleft %(count)s times|other": "%(severalUsers)sleft %(count)s times",
|
||||||
"%(severalUsers)sleft": "%(severalUsers)sleft",
|
"%(severalUsers)sleft %(count)s times|one": "%(severalUsers)sleft",
|
||||||
"%(oneUser)sleft": "%(oneUser)sleft",
|
"%(oneUser)sleft %(count)s times|other": "%(oneUser)sleft %(count)s times",
|
||||||
"%(severalUsers)sjoined and left %(repeats)s times": "%(severalUsers)sjoined and left %(repeats)s times",
|
"%(oneUser)sleft %(count)s times|one": "%(oneUser)sleft",
|
||||||
"%(oneUser)sjoined and left %(repeats)s times": "%(oneUser)sjoined and left %(repeats)s times",
|
"%(severalUsers)sjoined and left %(count)s times|other": "%(severalUsers)sjoined and left %(count)s times",
|
||||||
"%(severalUsers)sjoined and left": "%(severalUsers)sjoined and left",
|
"%(severalUsers)sjoined and left %(count)s times|one": "%(severalUsers)sjoined and left",
|
||||||
"%(oneUser)sjoined and left": "%(oneUser)sjoined and left",
|
"%(oneUser)sjoined and left %(count)s times|other": "%(oneUser)sjoined and left %(count)s times",
|
||||||
"%(severalUsers)sleft and rejoined %(repeats)s times": "%(severalUsers)sleft and rejoined %(repeats)s times",
|
"%(oneUser)sjoined and left %(count)s times|one": "%(oneUser)sjoined and left",
|
||||||
"%(oneUser)sleft and rejoined %(repeats)s times": "%(oneUser)sleft and rejoined %(repeats)s times",
|
"%(severalUsers)sleft and rejoined %(count)s times|other": "%(severalUsers)sleft and rejoined %(count)s times",
|
||||||
"%(severalUsers)sleft and rejoined": "%(severalUsers)sleft and rejoined",
|
"%(severalUsers)sleft and rejoined %(count)s times|one": "%(severalUsers)sleft and rejoined",
|
||||||
"%(oneUser)sleft and rejoined": "%(oneUser)sleft and rejoined",
|
"%(oneUser)sleft and rejoined %(count)s times|other": "%(oneUser)sleft and rejoined %(count)s times",
|
||||||
"%(severalUsers)srejected their invitations %(repeats)s times": "%(severalUsers)srejected their invitations %(repeats)s times",
|
"%(oneUser)sleft and rejoined %(count)s times|one": "%(oneUser)sleft and rejoined",
|
||||||
"%(oneUser)srejected their invitation %(repeats)s times": "%(oneUser)srejected their invitation %(repeats)s times",
|
"%(severalUsers)srejected their invitations %(count)s times|other": "%(severalUsers)srejected their invitations %(count)s times",
|
||||||
"%(severalUsers)srejected their invitations": "%(severalUsers)srejected their invitations",
|
"%(severalUsers)srejected their invitations %(count)s times|one": "%(severalUsers)srejected their invitations",
|
||||||
"%(oneUser)srejected their invitation": "%(oneUser)srejected their invitation",
|
"%(oneUser)srejected their invitation %(count)s times|other": "%(oneUser)srejected their invitation %(count)s times",
|
||||||
"%(severalUsers)shad their invitations withdrawn %(repeats)s times": "%(severalUsers)shad their invitations withdrawn %(repeats)s times",
|
"%(oneUser)srejected their invitation %(count)s times|one": "%(oneUser)srejected their invitation",
|
||||||
"%(oneUser)shad their invitation withdrawn %(repeats)s times": "%(oneUser)shad their invitation withdrawn %(repeats)s times",
|
"%(severalUsers)shad their invitations withdrawn %(count)s times|other": "%(severalUsers)shad their invitations withdrawn %(count)s times",
|
||||||
"%(severalUsers)shad their invitations withdrawn": "%(severalUsers)shad their invitations withdrawn",
|
"%(severalUsers)shad their invitations withdrawn %(count)s times|one": "%(severalUsers)shad their invitations withdrawn",
|
||||||
"%(oneUser)shad their invitation withdrawn": "%(oneUser)shad their invitation withdrawn",
|
"%(oneUser)shad their invitation withdrawn %(count)s times|other": "%(oneUser)shad their invitation withdrawn %(count)s times",
|
||||||
"were invited %(repeats)s times": "were invited %(repeats)s times",
|
"%(oneUser)shad their invitation withdrawn %(count)s times|one": "%(oneUser)shad their invitation withdrawn",
|
||||||
"was invited %(repeats)s times": "was invited %(repeats)s times",
|
"were invited %(count)s times|other": "were invited %(count)s times",
|
||||||
"were invited": "were invited",
|
"were invited %(count)s times|one": "were invited",
|
||||||
"was invited": "was invited",
|
"was invited %(count)s times|other": "was invited %(count)s times",
|
||||||
"were banned %(repeats)s times": "were banned %(repeats)s times",
|
"was invited %(count)s times|one": "was invited",
|
||||||
"was banned %(repeats)s times": "was banned %(repeats)s times",
|
"were banned %(count)s times|other": "were banned %(count)s times",
|
||||||
"were banned": "were banned",
|
"were banned %(count)s times|one": "were banned",
|
||||||
"was banned": "was banned",
|
"was banned %(count)s times|other": "was banned %(count)s times",
|
||||||
"were unbanned %(repeats)s times": "were unbanned %(repeats)s times",
|
"was banned %(count)s times|one": "was banned",
|
||||||
"was unbanned %(repeats)s times": "was unbanned %(repeats)s times",
|
"were unbanned %(count)s times|other": "were unbanned %(count)s times",
|
||||||
"were unbanned": "were unbanned",
|
"were unbanned %(count)s times|one": "were unbanned",
|
||||||
"was unbanned": "was unbanned",
|
"was unbanned %(count)s times|other": "was unbanned %(count)s times",
|
||||||
"were kicked %(repeats)s times": "were kicked %(repeats)s times",
|
"was unbanned %(count)s times|one": "was unbanned",
|
||||||
"was kicked %(repeats)s times": "was kicked %(repeats)s times",
|
"were kicked %(count)s times|other": "were kicked %(count)s times",
|
||||||
"were kicked": "were kicked",
|
"were kicked %(count)s times|one": "were kicked",
|
||||||
"was kicked": "was kicked",
|
"was kicked %(count)s times|other": "was kicked %(count)s times",
|
||||||
"%(severalUsers)schanged their name %(repeats)s times": "%(severalUsers)schanged their name %(repeats)s times",
|
"was kicked %(count)s times|one": "was kicked",
|
||||||
"%(oneUser)schanged their name %(repeats)s times": "%(oneUser)schanged their name %(repeats)s times",
|
"%(severalUsers)schanged their name %(count)s times|other": "%(severalUsers)schanged their name %(count)s times",
|
||||||
"%(severalUsers)schanged their name": "%(severalUsers)schanged their name",
|
"%(severalUsers)schanged their name %(count)s times|one": "%(severalUsers)schanged their name",
|
||||||
"%(oneUser)schanged their name": "%(oneUser)schanged their name",
|
"%(oneUser)schanged their name %(count)s times|other": "%(oneUser)schanged their name %(count)s times",
|
||||||
"%(severalUsers)schanged their avatar %(repeats)s times": "%(severalUsers)schanged their avatar %(repeats)s times",
|
"%(oneUser)schanged their name %(count)s times|one": "%(oneUser)schanged their name",
|
||||||
"%(oneUser)schanged their avatar %(repeats)s times": "%(oneUser)schanged their avatar %(repeats)s times",
|
"%(severalUsers)schanged their avatar %(count)s times|other": "%(severalUsers)schanged their avatar %(count)s times",
|
||||||
"%(severalUsers)schanged their avatar": "%(severalUsers)schanged their avatar",
|
"%(severalUsers)schanged their avatar %(count)s times|one": "%(severalUsers)schanged their avatar",
|
||||||
"%(oneUser)schanged their avatar": "%(oneUser)schanged their avatar",
|
"%(oneUser)schanged their avatar %(count)s times|other": "%(oneUser)schanged their avatar %(count)s times",
|
||||||
"%(items)s and %(remaining)s others": "%(items)s and %(remaining)s others",
|
"%(oneUser)schanged their avatar %(count)s times|one": "%(oneUser)schanged their avatar",
|
||||||
"%(items)s and one other": "%(items)s and one other",
|
"%(items)s and %(count)s others|other": "%(items)s and %(count)s others",
|
||||||
|
"%(items)s and %(count)s others|one": "%(items)s and one other",
|
||||||
"%(items)s and %(lastItem)s": "%(items)s and %(lastItem)s",
|
"%(items)s and %(lastItem)s": "%(items)s and %(lastItem)s",
|
||||||
"Custom level": "Custom level",
|
"Custom level": "Custom level",
|
||||||
"Room directory": "Room directory",
|
"Room directory": "Room directory",
|
||||||
|
@ -573,7 +585,6 @@
|
||||||
"And %(count)s more...|other": "And %(count)s more...",
|
"And %(count)s more...|other": "And %(count)s more...",
|
||||||
"ex. @bob:example.com": "ex. @bob:example.com",
|
"ex. @bob:example.com": "ex. @bob:example.com",
|
||||||
"Add User": "Add User",
|
"Add User": "Add User",
|
||||||
"Something went wrong!": "Something went wrong!",
|
|
||||||
"Matrix ID": "Matrix ID",
|
"Matrix ID": "Matrix ID",
|
||||||
"Matrix Room ID": "Matrix Room ID",
|
"Matrix Room ID": "Matrix Room ID",
|
||||||
"email address": "email address",
|
"email address": "email address",
|
||||||
|
@ -587,8 +598,7 @@
|
||||||
"Start Chatting": "Start Chatting",
|
"Start Chatting": "Start Chatting",
|
||||||
"Confirm Removal": "Confirm Removal",
|
"Confirm Removal": "Confirm Removal",
|
||||||
"Are you sure you wish to remove (delete) this event? Note that if you delete a room name or topic change, it could undo the change.": "Are you sure you wish to remove (delete) this event? Note that if you delete a room name or topic change, it could undo the change.",
|
"Are you sure you wish to remove (delete) this event? Note that if you delete a room name or topic change, it could undo the change.": "Are you sure you wish to remove (delete) this event? Note that if you delete a room name or topic change, it could undo the change.",
|
||||||
"%(actionVerb)s this person?": "%(actionVerb)s this person?",
|
"Community IDs may only contain characters a-z, 0-9, or '=_-./'": "Community IDs may only contain characters a-z, 0-9, or '=_-./'",
|
||||||
"Community IDs may only contain alphanumeric characters": "Community IDs may only contain alphanumeric characters",
|
|
||||||
"Something went wrong whilst creating your community": "Something went wrong whilst creating your community",
|
"Something went wrong whilst creating your community": "Something went wrong whilst creating your community",
|
||||||
"Create Community": "Create Community",
|
"Create Community": "Create Community",
|
||||||
"Community Name": "Community Name",
|
"Community Name": "Community Name",
|
||||||
|
@ -831,7 +841,7 @@
|
||||||
"A new password must be entered.": "A new password must be entered.",
|
"A new password must be entered.": "A new password must be entered.",
|
||||||
"New passwords must match each other.": "New passwords must match each other.",
|
"New passwords must match each other.": "New passwords must match each other.",
|
||||||
"Resetting password will currently reset any end-to-end encryption keys on all devices, making encrypted chat history unreadable, unless you first export your room keys and re-import them afterwards. In future this will be improved.": "Resetting password will currently reset any end-to-end encryption keys on all devices, making encrypted chat history unreadable, unless you first export your room keys and re-import them afterwards. In future this will be improved.",
|
"Resetting password will currently reset any end-to-end encryption keys on all devices, making encrypted chat history unreadable, unless you first export your room keys and re-import them afterwards. In future this will be improved.": "Resetting password will currently reset any end-to-end encryption keys on all devices, making encrypted chat history unreadable, unless you first export your room keys and re-import them afterwards. In future this will be improved.",
|
||||||
"Once you've followed the link it contains, click below": "Once you've followed the link it contains, click below",
|
"An email has been sent to %(emailAddress)s. Once you've followed the link it contains, click below.": "An email has been sent to %(emailAddress)s. Once you've followed the link it contains, click below.",
|
||||||
"I have verified my email address": "I have verified my email address",
|
"I have verified my email address": "I have verified my email address",
|
||||||
"Your password has been reset": "Your password has been reset",
|
"Your password has been reset": "Your password has been reset",
|
||||||
"You have been logged out of all devices and will no longer receive push notifications. To re-enable notifications, sign in again on each device": "You have been logged out of all devices and will no longer receive push notifications. To re-enable notifications, sign in again on each device",
|
"You have been logged out of all devices and will no longer receive push notifications. To re-enable notifications, sign in again on each device": "You have been logged out of all devices and will no longer receive push notifications. To re-enable notifications, sign in again on each device",
|
||||||
|
|
|
@ -252,6 +252,26 @@ function getLangsJson() {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function weblateToCounterpart(inTrs) {
|
||||||
|
const outTrs = {};
|
||||||
|
|
||||||
|
for (const key of Object.keys(inTrs)) {
|
||||||
|
const keyParts = key.split('|', 2);
|
||||||
|
if (keyParts.length === 2) {
|
||||||
|
let obj = outTrs[keyParts[0]];
|
||||||
|
if (obj === undefined) {
|
||||||
|
obj = {};
|
||||||
|
outTrs[keyParts[0]] = obj;
|
||||||
|
}
|
||||||
|
obj[keyParts[1]] = inTrs[key];
|
||||||
|
} else {
|
||||||
|
outTrs[key] = inTrs[key];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return outTrs;
|
||||||
|
}
|
||||||
|
|
||||||
function getLanguage(langPath) {
|
function getLanguage(langPath) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
request(
|
request(
|
||||||
|
@ -261,7 +281,7 @@ function getLanguage(langPath) {
|
||||||
reject({err: err, response: response});
|
reject({err: err, response: response});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
resolve(JSON.parse(body));
|
resolve(weblateToCounterpart(JSON.parse(body)));
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
|
@ -66,7 +66,7 @@ class FlairStore extends EventEmitter {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Bulk lookup ongoing, return promise to resolve/reject
|
// Bulk lookup ongoing, return promise to resolve/reject
|
||||||
if (this._usersPending[userId]) {
|
if (this._usersPending[userId] || this._usersInFlight[userId]) {
|
||||||
return this._usersPending[userId].prom;
|
return this._usersPending[userId].prom;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -91,7 +91,7 @@ class FlairStore extends EventEmitter {
|
||||||
console.error('Could not get groups for user', this.props.userId, err);
|
console.error('Could not get groups for user', this.props.userId, err);
|
||||||
throw err;
|
throw err;
|
||||||
}).finally(() => {
|
}).finally(() => {
|
||||||
delete this._usersPending[userId];
|
delete this._usersInFlight[userId];
|
||||||
});
|
});
|
||||||
|
|
||||||
// This debounce will allow consecutive requests for the public groups of users that
|
// This debounce will allow consecutive requests for the public groups of users that
|
||||||
|
@ -113,23 +113,25 @@ class FlairStore extends EventEmitter {
|
||||||
}
|
}
|
||||||
|
|
||||||
async _batchedGetPublicGroups(matrixClient) {
|
async _batchedGetPublicGroups(matrixClient) {
|
||||||
// Take the userIds from the keys of this._usersPending
|
// Move users pending to users in flight
|
||||||
const usersInFlight = Object.keys(this._usersPending);
|
this._usersInFlight = this._usersPending;
|
||||||
|
this._usersPending = {};
|
||||||
|
|
||||||
let resp = {
|
let resp = {
|
||||||
users: [],
|
users: [],
|
||||||
};
|
};
|
||||||
try {
|
try {
|
||||||
resp = await matrixClient.getPublicisedGroups(usersInFlight);
|
resp = await matrixClient.getPublicisedGroups(Object.keys(this._usersInFlight));
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
// Propagate the same error to all usersInFlight
|
// Propagate the same error to all usersInFlight
|
||||||
usersInFlight.forEach((userId) => {
|
Object.keys(this._usersInFlight).forEach((userId) => {
|
||||||
this._usersPending[userId].reject(err);
|
this._usersInFlight[userId].reject(err);
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const updatedUserGroups = resp.users;
|
const updatedUserGroups = resp.users;
|
||||||
usersInFlight.forEach((userId) => {
|
Object.keys(this._usersInFlight).forEach((userId) => {
|
||||||
this._usersPending[userId].resolve(updatedUserGroups[userId] || []);
|
this._usersInFlight[userId].resolve(updatedUserGroups[userId] || []);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -23,15 +23,27 @@ import FlairStore from './FlairStore';
|
||||||
* other useful group APIs that may have an effect on the group summary.
|
* other useful group APIs that may have an effect on the group summary.
|
||||||
*/
|
*/
|
||||||
export default class GroupStore extends EventEmitter {
|
export default class GroupStore extends EventEmitter {
|
||||||
|
|
||||||
|
static STATE_KEY = {
|
||||||
|
GroupMembers: 'GroupMembers',
|
||||||
|
GroupInvitedMembers: 'GroupInvitedMembers',
|
||||||
|
Summary: 'Summary',
|
||||||
|
GroupRooms: 'GroupRooms',
|
||||||
|
};
|
||||||
|
|
||||||
constructor(matrixClient, groupId) {
|
constructor(matrixClient, groupId) {
|
||||||
super();
|
super();
|
||||||
this.groupId = groupId;
|
this.groupId = groupId;
|
||||||
this._matrixClient = matrixClient;
|
this._matrixClient = matrixClient;
|
||||||
this._summary = {};
|
this._summary = {};
|
||||||
this._rooms = [];
|
this._rooms = [];
|
||||||
this._fetchSummary();
|
this._members = [];
|
||||||
this._fetchRooms();
|
this._invitedMembers = [];
|
||||||
this._fetchMembers();
|
this._ready = {};
|
||||||
|
|
||||||
|
this.on('error', (err) => {
|
||||||
|
console.error(`GroupStore for ${this.groupId} encountered error`, err);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
_fetchMembers() {
|
_fetchMembers() {
|
||||||
|
@ -39,6 +51,7 @@ export default class GroupStore extends EventEmitter {
|
||||||
this._members = result.chunk.map((apiMember) => {
|
this._members = result.chunk.map((apiMember) => {
|
||||||
return groupMemberFromApiObject(apiMember);
|
return groupMemberFromApiObject(apiMember);
|
||||||
});
|
});
|
||||||
|
this._ready[GroupStore.STATE_KEY.GroupMembers] = true;
|
||||||
this._notifyListeners();
|
this._notifyListeners();
|
||||||
}).catch((err) => {
|
}).catch((err) => {
|
||||||
console.error("Failed to get group member list: " + err);
|
console.error("Failed to get group member list: " + err);
|
||||||
|
@ -49,8 +62,13 @@ export default class GroupStore extends EventEmitter {
|
||||||
this._invitedMembers = result.chunk.map((apiMember) => {
|
this._invitedMembers = result.chunk.map((apiMember) => {
|
||||||
return groupMemberFromApiObject(apiMember);
|
return groupMemberFromApiObject(apiMember);
|
||||||
});
|
});
|
||||||
|
this._ready[GroupStore.STATE_KEY.GroupInvitedMembers] = true;
|
||||||
this._notifyListeners();
|
this._notifyListeners();
|
||||||
}).catch((err) => {
|
}).catch((err) => {
|
||||||
|
// Invited users not visible to non-members
|
||||||
|
if (err.httpStatus === 403) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
console.error("Failed to get group invited member list: " + err);
|
console.error("Failed to get group invited member list: " + err);
|
||||||
this.emit('error', err);
|
this.emit('error', err);
|
||||||
});
|
});
|
||||||
|
@ -59,6 +77,7 @@ export default class GroupStore extends EventEmitter {
|
||||||
_fetchSummary() {
|
_fetchSummary() {
|
||||||
this._matrixClient.getGroupSummary(this.groupId).then((resp) => {
|
this._matrixClient.getGroupSummary(this.groupId).then((resp) => {
|
||||||
this._summary = resp;
|
this._summary = resp;
|
||||||
|
this._ready[GroupStore.STATE_KEY.Summary] = true;
|
||||||
this._notifyListeners();
|
this._notifyListeners();
|
||||||
}).catch((err) => {
|
}).catch((err) => {
|
||||||
this.emit('error', err);
|
this.emit('error', err);
|
||||||
|
@ -70,6 +89,7 @@ export default class GroupStore extends EventEmitter {
|
||||||
this._rooms = resp.chunk.map((apiRoom) => {
|
this._rooms = resp.chunk.map((apiRoom) => {
|
||||||
return groupRoomFromApiObject(apiRoom);
|
return groupRoomFromApiObject(apiRoom);
|
||||||
});
|
});
|
||||||
|
this._ready[GroupStore.STATE_KEY.GroupRooms] = true;
|
||||||
this._notifyListeners();
|
this._notifyListeners();
|
||||||
}).catch((err) => {
|
}).catch((err) => {
|
||||||
this.emit('error', err);
|
this.emit('error', err);
|
||||||
|
@ -80,6 +100,23 @@ export default class GroupStore extends EventEmitter {
|
||||||
this.emit('update');
|
this.emit('update');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
registerListener(fn) {
|
||||||
|
this.on('update', fn);
|
||||||
|
// Call to set initial state (before fetching starts)
|
||||||
|
this.emit('update');
|
||||||
|
this._fetchSummary();
|
||||||
|
this._fetchRooms();
|
||||||
|
this._fetchMembers();
|
||||||
|
}
|
||||||
|
|
||||||
|
unregisterListener(fn) {
|
||||||
|
this.removeListener('update', fn);
|
||||||
|
}
|
||||||
|
|
||||||
|
isStateReady(id) {
|
||||||
|
return this._ready[id];
|
||||||
|
}
|
||||||
|
|
||||||
getSummary() {
|
getSummary() {
|
||||||
return this._summary;
|
return this._summary;
|
||||||
}
|
}
|
||||||
|
@ -104,9 +141,15 @@ export default class GroupStore extends EventEmitter {
|
||||||
return this._summary.user ? this._summary.user.is_privileged : null;
|
return this._summary.user ? this._summary.user.is_privileged : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
addRoomToGroup(roomId) {
|
addRoomToGroup(roomId, isPublic) {
|
||||||
return this._matrixClient
|
return this._matrixClient
|
||||||
.addRoomToGroup(this.groupId, roomId)
|
.addRoomToGroup(this.groupId, roomId, isPublic)
|
||||||
|
.then(this._fetchRooms.bind(this));
|
||||||
|
}
|
||||||
|
|
||||||
|
updateGroupRoomAssociation(roomId, isPublic) {
|
||||||
|
return this._matrixClient
|
||||||
|
.updateGroupRoomAssociation(this.groupId, roomId, isPublic)
|
||||||
.then(this._fetchRooms.bind(this));
|
.then(this._fetchRooms.bind(this));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -88,6 +88,9 @@ describe('MemberEventListSummary', function() {
|
||||||
sandbox = testUtils.stubClient();
|
sandbox = testUtils.stubClient();
|
||||||
|
|
||||||
languageHandler.setLanguage('en').done(done);
|
languageHandler.setLanguage('en').done(done);
|
||||||
|
languageHandler.setMissingEntryGenerator(function(key) {
|
||||||
|
return key.split('|', 2)[1];
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(function() {
|
afterEach(function() {
|
||||||
|
|
Loading…
Reference in a new issue