Merge branch 'develop' of https://github.com/matrix-org/matrix-react-sdk into rxl881/widgetrendering
commit
853ada027d
|
@ -19,7 +19,7 @@ import PlatformPeg from './PlatformPeg';
|
|||
import SdkConfig from './SdkConfig';
|
||||
|
||||
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
|
||||
return 'https://riot.im/app/' + redactedHash;
|
||||
}
|
||||
|
|
77
src/Login.js
77
src/Login.js
|
@ -143,6 +143,50 @@ export default class Login {
|
|||
Object.assign(loginParams, legacyParams);
|
||||
|
||||
const client = this._createTemporaryClient();
|
||||
|
||||
const tryFallbackHs = (originalError) => {
|
||||
const fbClient = Matrix.createClient({
|
||||
baseUrl: self._fallbackHsUrl,
|
||||
idBaseUrl: this._isUrl,
|
||||
});
|
||||
|
||||
return fbClient.login('m.login.password', loginParams).then(function(data) {
|
||||
return Promise.resolve({
|
||||
homeserverUrl: self._fallbackHsUrl,
|
||||
identityServerUrl: self._isUrl,
|
||||
userId: data.user_id,
|
||||
deviceId: data.device_id,
|
||||
accessToken: data.access_token,
|
||||
});
|
||||
}).catch((fallback_error) => {
|
||||
console.log("fallback HS login failed", fallback_error);
|
||||
// throw the original 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,
|
||||
|
@ -151,28 +195,25 @@ export default class Login {
|
|||
deviceId: data.device_id,
|
||||
accessToken: data.access_token,
|
||||
});
|
||||
}, function(error) {
|
||||
}).catch((error) => {
|
||||
originalLoginError = error;
|
||||
if (error.httpStatus === 403) {
|
||||
if (self._fallbackHsUrl) {
|
||||
const fbClient = Matrix.createClient({
|
||||
baseUrl: self._fallbackHsUrl,
|
||||
idBaseUrl: this._isUrl,
|
||||
});
|
||||
|
||||
return fbClient.login('m.login.password', loginParams).then(function(data) {
|
||||
return Promise.resolve({
|
||||
homeserverUrl: self._fallbackHsUrl,
|
||||
identityServerUrl: self._isUrl,
|
||||
userId: data.user_id,
|
||||
deviceId: data.device_id,
|
||||
accessToken: data.access_token,
|
||||
});
|
||||
}, function(fallback_error) {
|
||||
// throw the original error
|
||||
throw error;
|
||||
});
|
||||
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;
|
||||
});
|
||||
}
|
||||
|
|
|
@ -21,6 +21,8 @@ import Modal from './Modal';
|
|||
import { getAddressType } from './UserAddress';
|
||||
import createRoom from './createRoom';
|
||||
import sdk from './';
|
||||
import dis from './dispatcher';
|
||||
import DMRoomMap from './utils/DMRoomMap';
|
||||
import { _t } from './languageHandler';
|
||||
|
||||
export function inviteToRoom(roomId, addr) {
|
||||
|
@ -79,15 +81,40 @@ function _onStartChatFinished(shouldInvite, addrs) {
|
|||
const addrTexts = addrs.map((addr) => addr.address);
|
||||
|
||||
if (_isDmChat(addrTexts)) {
|
||||
// Start a new DM chat
|
||||
createRoom({dmUserId: addrTexts[0]}).catch((err) => {
|
||||
console.error(err.stack);
|
||||
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
||||
Modal.createTrackedDialog('Failed to invite user', '', ErrorDialog, {
|
||||
title: _t("Failed to invite user"),
|
||||
description: ((err && err.message) ? err.message : _t("Operation failed")),
|
||||
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
|
||||
createRoom({dmUserId: addrTexts[0]}).catch((err) => {
|
||||
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
||||
Modal.createTrackedDialog('Failed to invite user', '', ErrorDialog, {
|
||||
title: _t("Failed to invite user"),
|
||||
description: ((err && err.message) ? err.message : _t("Operation failed")),
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
} else {
|
||||
// Start multi user chat
|
||||
let room;
|
||||
|
@ -153,3 +180,19 @@ function _showAnyInviteErrors(addrs, room) {
|
|||
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,10 +68,8 @@ module.exports = {
|
|||
const names = whoIsTyping.map(function(m) {
|
||||
return m.name;
|
||||
});
|
||||
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});
|
||||
if (othersCount>=1) {
|
||||
return _t('%(names)s and %(count)s others are typing', {names: names.slice(0, limit - 1).join(', '), count: othersCount});
|
||||
} else {
|
||||
const lastPerson = names.pop();
|
||||
return _t('%(names)s and %(lastPerson)s are typing', {names: names.join(', '), lastPerson: lastPerson});
|
||||
|
|
|
@ -407,6 +407,10 @@ export default React.createClass({
|
|||
getInitialState: function() {
|
||||
return {
|
||||
summary: null,
|
||||
isGroupPublicised: null,
|
||||
isUserPrivileged: null,
|
||||
groupRooms: null,
|
||||
groupRoomsLoading: null,
|
||||
error: null,
|
||||
editing: false,
|
||||
saving: false,
|
||||
|
@ -447,7 +451,7 @@ export default React.createClass({
|
|||
|
||||
_initGroupStore: function(groupId) {
|
||||
this._groupStore = GroupStoreCache.getGroupStore(MatrixClientPeg.get(), groupId);
|
||||
this._groupStore.on('update', () => {
|
||||
this._groupStore.registerListener(() => {
|
||||
const summary = this._groupStore.getSummary();
|
||||
if (summary.profile) {
|
||||
// Default profile fields should be "" for later sending to the server (which
|
||||
|
@ -458,13 +462,18 @@ export default React.createClass({
|
|||
}
|
||||
this.setState({
|
||||
summary,
|
||||
summaryLoading: !this._groupStore.isStateReady(GroupStore.STATE_KEY.Summary),
|
||||
isGroupPublicised: this._groupStore.getGroupPublicity(),
|
||||
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,
|
||||
});
|
||||
});
|
||||
this._groupStore.on('error', (err) => {
|
||||
console.error(err);
|
||||
this.setState({
|
||||
summary: null,
|
||||
error: err,
|
||||
|
@ -651,6 +660,7 @@ export default React.createClass({
|
|||
const RoomDetailList = sdk.getComponent('rooms.RoomDetailList');
|
||||
const AccessibleButton = sdk.getComponent('elements.AccessibleButton');
|
||||
const TintableSvg = sdk.getComponent('elements.TintableSvg');
|
||||
const Spinner = sdk.getComponent('elements.Spinner');
|
||||
|
||||
const addRoomRow = this.state.editing ?
|
||||
(<AccessibleButton className="mx_GroupView_rooms_header_addRow"
|
||||
|
@ -668,7 +678,10 @@ export default React.createClass({
|
|||
<h3>{ _t('Rooms') }</h3>
|
||||
{ addRoomRow }
|
||||
</div>
|
||||
<RoomDetailList rooms={this._groupStore.getGroupRooms()} />
|
||||
{ this.state.groupRoomsLoading ?
|
||||
<Spinner /> :
|
||||
<RoomDetailList rooms={this.state.groupRooms} />
|
||||
}
|
||||
</div>;
|
||||
},
|
||||
|
||||
|
@ -864,7 +877,7 @@ export default React.createClass({
|
|||
const Spinner = sdk.getComponent("elements.Spinner");
|
||||
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 />;
|
||||
} else if (this.state.summary) {
|
||||
const summary = this.state.summary;
|
||||
|
@ -885,6 +898,7 @@ export default React.createClass({
|
|||
} else {
|
||||
const GroupAvatar = sdk.getComponent('avatars.GroupAvatar');
|
||||
avatarImage = <GroupAvatar groupId={this.props.groupId}
|
||||
groupName={this.state.profileForm.name}
|
||||
groupAvatarUrl={this.state.profileForm.avatar_url}
|
||||
width={48} height={48} resizeMethod='crop'
|
||||
/>;
|
||||
|
@ -928,25 +942,28 @@ export default React.createClass({
|
|||
tabIndex="2"
|
||||
dir="auto" />;
|
||||
} else {
|
||||
const onGroupHeaderItemClick = this.state.isUserMember ? this._onEditClick : null;
|
||||
const groupAvatarUrl = summary.profile ? summary.profile.avatar_url : null;
|
||||
const groupName = summary.profile ? summary.profile.name : null;
|
||||
avatarNode = <GroupAvatar
|
||||
groupId={this.props.groupId}
|
||||
groupAvatarUrl={groupAvatarUrl}
|
||||
onClick={this._onEditClick}
|
||||
groupName={groupName}
|
||||
onClick={onGroupHeaderItemClick}
|
||||
width={48} height={48}
|
||||
/>;
|
||||
if (summary.profile && summary.profile.name) {
|
||||
nameNode = <div onClick={this._onEditClick}>
|
||||
nameNode = <div onClick={onGroupHeaderItemClick}>
|
||||
<span>{ summary.profile.name }</span>
|
||||
<span className="mx_GroupView_header_groupid">
|
||||
({ this.props.groupId })
|
||||
</span>
|
||||
</div>;
|
||||
} 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) {
|
||||
shortDescNode = <span onClick={this._onEditClick}>{ summary.profile.short_description }</span>;
|
||||
shortDescNode = <span onClick={onGroupHeaderItemClick}>{ summary.profile.short_description }</span>;
|
||||
}
|
||||
}
|
||||
if (this.state.editing) {
|
||||
|
@ -987,6 +1004,7 @@ export default React.createClass({
|
|||
const headerClasses = {
|
||||
mx_GroupView_header: true,
|
||||
mx_GroupView_header_view: !this.state.editing,
|
||||
mx_GroupView_header_isUserMember: this.state.isUserMember,
|
||||
};
|
||||
|
||||
return (
|
||||
|
|
|
@ -62,7 +62,9 @@ const GroupTile = React.createClass({
|
|||
const profile = this.state.profile || {};
|
||||
const name = profile.name || this.props.groupId;
|
||||
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}>
|
||||
<div className="mx_GroupTile_avatar">
|
||||
<BaseAvatar name={name} url={httpUrl} width={50} height={50} />
|
||||
|
|
|
@ -166,7 +166,7 @@ module.exports = React.createClass({
|
|||
} else if (this.state.progress === "sent_email") {
|
||||
resetPasswordJsx = (
|
||||
<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 />
|
||||
<input className="mx_Login_submit" type="button" onClick={this.onVerify}
|
||||
value={_t('I have verified my email address')} />
|
||||
|
|
|
@ -302,7 +302,7 @@ module.exports = React.createClass({
|
|||
} : {};
|
||||
|
||||
return this._matrixClient.register(
|
||||
this.state.formVals.username,
|
||||
this.state.formVals.username.toLowerCase(),
|
||||
this.state.formVals.password,
|
||||
undefined, // session id: included in the auth dict already
|
||||
auth,
|
||||
|
|
|
@ -24,6 +24,7 @@ export default React.createClass({
|
|||
|
||||
propTypes: {
|
||||
groupId: PropTypes.string,
|
||||
groupName: PropTypes.string,
|
||||
groupAvatarUrl: PropTypes.string,
|
||||
width: 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
|
||||
// should consider adding this as a global rule in js-sdk?
|
||||
/*eslint no-unused-vars: ["error", { "ignoreRestSiblings": true }]*/
|
||||
const {groupId, groupAvatarUrl, ...otherProps} = this.props;
|
||||
const {groupId, groupAvatarUrl, groupName, ...otherProps} = this.props;
|
||||
|
||||
return (
|
||||
<BaseAvatar
|
||||
name={this.props.groupId[1]}
|
||||
name={groupName || this.props.groupId[1]}
|
||||
idName={this.props.groupId}
|
||||
url={this.getGroupAvatarUrl()}
|
||||
{...otherProps}
|
||||
|
|
|
@ -36,6 +36,7 @@ export default React.createClass({
|
|||
// group member object. Supply either this or 'member'
|
||||
groupMember: GroupMemberType,
|
||||
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
|
||||
// If true, the second argument to onFinished will
|
||||
|
@ -75,7 +76,6 @@ export default React.createClass({
|
|||
const MemberAvatar = sdk.getComponent("views.avatars.MemberAvatar");
|
||||
const BaseAvatar = sdk.getComponent("views.avatars.BaseAvatar");
|
||||
|
||||
const title = _t("%(actionVerb)s this person?", { actionVerb: this.props.action});
|
||||
const confirmButtonClass = classnames({
|
||||
'mx_Dialog_primary': true,
|
||||
'danger': this.props.danger,
|
||||
|
@ -113,7 +113,7 @@ export default React.createClass({
|
|||
return (
|
||||
<BaseDialog className="mx_ConfirmUserActionDialog" onFinished={this.props.onFinished}
|
||||
onEnterPressed={this.onOk}
|
||||
title={title}
|
||||
title={this.props.title}
|
||||
>
|
||||
<div className="mx_Dialog_content">
|
||||
<div className="mx_ConfirmUserActionDialog_avatar">
|
||||
|
|
|
@ -55,8 +55,8 @@ export default React.createClass({
|
|||
|
||||
_checkGroupId: function(e) {
|
||||
let error = null;
|
||||
if (!/^[a-zA-Z0-9]*$/.test(this.state.groupId)) {
|
||||
error = _t("Community IDs may only contain alphanumeric characters");
|
||||
if (!/^[a-z0-9=_\-\.\/]*$/.test(this.state.groupId)) {
|
||||
error = _t("Community IDs may only contain characters a-z, 0-9, or '=_-./'");
|
||||
}
|
||||
this.setState({
|
||||
groupIdError: error,
|
||||
|
|
|
@ -86,7 +86,6 @@ module.exports = React.createClass({
|
|||
const summaries = orderedTransitionSequences.map((transitions) => {
|
||||
const userNames = eventAggregates[transitions];
|
||||
const nameList = this._renderNameList(userNames);
|
||||
const plural = userNames.length > 1;
|
||||
|
||||
const splitTransitions = transitions.split(',');
|
||||
|
||||
|
@ -101,13 +100,13 @@ module.exports = React.createClass({
|
|||
|
||||
const descs = coalescedTransitions.map((t) => {
|
||||
return this._getDescriptionForTransition(
|
||||
t.transitionType, plural, t.repeats,
|
||||
t.transitionType, userNames.length, t.repeats,
|
||||
);
|
||||
});
|
||||
|
||||
const desc = this._renderCommaSeparatedList(descs);
|
||||
|
||||
return nameList + " " + desc;
|
||||
return _t('%(nameList)s %(transitionList)s', { nameList: nameList, transitionList: desc });
|
||||
});
|
||||
|
||||
if (!summaries) {
|
||||
|
@ -208,148 +207,75 @@ module.exports = React.createClass({
|
|||
* For a certain transition, t, describe what happened to the users that
|
||||
* underwent the transition.
|
||||
* @param {string} t the transition type.
|
||||
* @param {boolean} plural whether there were multiple users undergoing the same
|
||||
* transition.
|
||||
* @param {integer} userCount number of usernames
|
||||
* @param {number} repeats the number of times the transition was repeated in a row.
|
||||
* @returns {string} the written Human Readable equivalent of the transition.
|
||||
*/
|
||||
_getDescriptionForTransition(t, plural, repeats) {
|
||||
_getDescriptionForTransition(t, userCount, repeats) {
|
||||
// The empty interpolations 'severalUsers' and 'oneUser'
|
||||
// are there only to show translators to non-English languages
|
||||
// that the verb is conjugated to plural or singular Subject.
|
||||
let res = null;
|
||||
switch(t) {
|
||||
case "joined":
|
||||
if (repeats > 1) {
|
||||
res = (plural)
|
||||
? _t("%(severalUsers)sjoined %(repeats)s times", { severalUsers: "", repeats: repeats })
|
||||
: _t("%(oneUser)sjoined %(repeats)s times", { oneUser: "", repeats: repeats });
|
||||
} else {
|
||||
res = (plural)
|
||||
? _t("%(severalUsers)sjoined", { severalUsers: "" })
|
||||
: _t("%(oneUser)sjoined", { oneUser: "" });
|
||||
}
|
||||
res = (userCount > 1)
|
||||
? _t("%(severalUsers)sjoined %(count)s times", { severalUsers: "", count: repeats })
|
||||
: _t("%(oneUser)sjoined %(count)s times", { oneUser: "", count: repeats });
|
||||
break;
|
||||
case "left":
|
||||
if (repeats > 1) {
|
||||
res = (plural)
|
||||
? _t("%(severalUsers)sleft %(repeats)s times", { severalUsers: "", repeats: repeats })
|
||||
: _t("%(oneUser)sleft %(repeats)s times", { oneUser: "", repeats: repeats });
|
||||
} else {
|
||||
res = (plural)
|
||||
? _t("%(severalUsers)sleft", { severalUsers: "" })
|
||||
: _t("%(oneUser)sleft", { oneUser: "" });
|
||||
}
|
||||
break;
|
||||
res = (userCount > 1)
|
||||
? _t("%(severalUsers)sleft %(count)s times", { severalUsers: "", count: repeats })
|
||||
: _t("%(oneUser)sleft %(count)s times", { oneUser: "", count: repeats });
|
||||
break;
|
||||
case "joined_and_left":
|
||||
if (repeats > 1) {
|
||||
res = (plural)
|
||||
? _t("%(severalUsers)sjoined and left %(repeats)s times", { severalUsers: "", repeats: 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: "" });
|
||||
}
|
||||
res = (userCount > 1)
|
||||
? _t("%(severalUsers)sjoined and left %(count)s times", { severalUsers: "", count: repeats })
|
||||
: _t("%(oneUser)sjoined and left %(count)s times", { oneUser: "", count: repeats });
|
||||
break;
|
||||
case "left_and_joined":
|
||||
if (repeats > 1) {
|
||||
res = (plural)
|
||||
? _t("%(severalUsers)sleft and rejoined %(repeats)s times", { severalUsers: "", repeats: 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: "" });
|
||||
}
|
||||
res = (userCount > 1)
|
||||
? _t("%(severalUsers)sleft and rejoined %(count)s times", { severalUsers: "", count: repeats })
|
||||
: _t("%(oneUser)sleft and rejoined %(count)s times", { oneUser: "", count: repeats });
|
||||
break;
|
||||
case "invite_reject":
|
||||
if (repeats > 1) {
|
||||
res = (plural)
|
||||
? _t("%(severalUsers)srejected their invitations %(repeats)s times", { severalUsers: "", repeats: 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: "" });
|
||||
}
|
||||
res = (userCount > 1)
|
||||
? _t("%(severalUsers)srejected their invitations %(count)s times", { severalUsers: "", count: repeats })
|
||||
: _t("%(oneUser)srejected their invitation %(count)s times", { oneUser: "", count: repeats });
|
||||
break;
|
||||
case "invite_withdrawal":
|
||||
if (repeats > 1) {
|
||||
res = (plural)
|
||||
? _t("%(severalUsers)shad their invitations withdrawn %(repeats)s times", { severalUsers: "", repeats: 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: "" });
|
||||
}
|
||||
res = (userCount > 1)
|
||||
? _t("%(severalUsers)shad their invitations withdrawn %(count)s times", { severalUsers: "", count: repeats })
|
||||
: _t("%(oneUser)shad their invitation withdrawn %(count)s times", { oneUser: "", count: repeats });
|
||||
break;
|
||||
case "invited":
|
||||
if (repeats > 1) {
|
||||
res = (plural)
|
||||
? _t("were invited %(repeats)s times", { repeats: repeats })
|
||||
: _t("was invited %(repeats)s times", { repeats: repeats });
|
||||
} else {
|
||||
res = (plural)
|
||||
? _t("were invited")
|
||||
: _t("was invited");
|
||||
}
|
||||
res = (userCount > 1)
|
||||
? _t("were invited %(count)s times", { count: repeats })
|
||||
: _t("was invited %(count)s times", { count: repeats });
|
||||
break;
|
||||
case "banned":
|
||||
if (repeats > 1) {
|
||||
res = (plural)
|
||||
? _t("were banned %(repeats)s times", { repeats: repeats })
|
||||
: _t("was banned %(repeats)s times", { repeats: repeats });
|
||||
} else {
|
||||
res = (plural)
|
||||
? _t("were banned")
|
||||
: _t("was banned");
|
||||
}
|
||||
res = (userCount > 1)
|
||||
? _t("were banned %(count)s times", { count: repeats })
|
||||
: _t("was banned %(count)s times", { count: repeats });
|
||||
break;
|
||||
case "unbanned":
|
||||
if (repeats > 1) {
|
||||
res = (plural)
|
||||
? _t("were unbanned %(repeats)s times", { repeats: repeats })
|
||||
: _t("was unbanned %(repeats)s times", { repeats: repeats });
|
||||
} else {
|
||||
res = (plural)
|
||||
? _t("were unbanned")
|
||||
: _t("was unbanned");
|
||||
}
|
||||
res = (userCount > 1)
|
||||
? _t("were unbanned %(count)s times", { count: repeats })
|
||||
: _t("was unbanned %(count)s times", { count: repeats });
|
||||
break;
|
||||
case "kicked":
|
||||
if (repeats > 1) {
|
||||
res = (plural)
|
||||
? _t("were kicked %(repeats)s times", { repeats: repeats })
|
||||
: _t("was kicked %(repeats)s times", { repeats: repeats });
|
||||
} else {
|
||||
res = (plural)
|
||||
? _t("were kicked")
|
||||
: _t("was kicked");
|
||||
}
|
||||
res = (userCount > 1)
|
||||
? _t("were kicked %(count)s times", { count: repeats })
|
||||
: _t("was kicked %(count)s times", { count: repeats });
|
||||
break;
|
||||
case "changed_name":
|
||||
if (repeats > 1) {
|
||||
res = (plural)
|
||||
? _t("%(severalUsers)schanged their name %(repeats)s times", { severalUsers: "", repeats: 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: "" });
|
||||
}
|
||||
res = (userCount > 1)
|
||||
? _t("%(severalUsers)schanged their name %(count)s times", { severalUsers: "", count: repeats })
|
||||
: _t("%(oneUser)schanged their name %(count)s times", { oneUser: "", count: repeats });
|
||||
break;
|
||||
case "changed_avatar":
|
||||
if (repeats > 1) {
|
||||
res = (plural)
|
||||
? _t("%(severalUsers)schanged their avatar %(repeats)s times", { severalUsers: "", repeats: 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: "" });
|
||||
}
|
||||
res = (userCount > 1)
|
||||
? _t("%(severalUsers)schanged their avatar %(count)s times", { severalUsers: "", count: repeats })
|
||||
: _t("%(oneUser)schanged their avatar %(count)s times", { oneUser: "", count: repeats });
|
||||
break;
|
||||
}
|
||||
|
||||
|
@ -376,11 +302,9 @@ module.exports = React.createClass({
|
|||
return "";
|
||||
} else if (items.length === 1) {
|
||||
return items[0];
|
||||
} else if (remaining) {
|
||||
} else if (remaining > 0) {
|
||||
items = items.slice(0, itemLimit);
|
||||
return (remaining > 1)
|
||||
? _t("%(items)s and %(remaining)s others", { items: items.join(', '), remaining: remaining } )
|
||||
: _t("%(items)s and one other", { items: items.join(', ') });
|
||||
return _t("%(items)s and %(count)s others", { items: items.join(', '), count: remaining } )
|
||||
} else {
|
||||
const lastItem = items.pop();
|
||||
return _t("%(items)s and %(lastItem)s", { items: items.join(', '), lastItem: lastItem });
|
||||
|
|
|
@ -37,11 +37,20 @@ const Pill = React.createClass({
|
|||
isMessagePillUrl: (url) => {
|
||||
return !!REGEX_LOCAL_MATRIXTO.exec(url);
|
||||
},
|
||||
roomNotifPos: (text) => {
|
||||
return text.indexOf("@room");
|
||||
},
|
||||
roomNotifLen: () => {
|
||||
return "@room".length;
|
||||
},
|
||||
TYPE_USER_MENTION: 'TYPE_USER_MENTION',
|
||||
TYPE_ROOM_MENTION: 'TYPE_ROOM_MENTION',
|
||||
TYPE_AT_ROOM_MENTION: 'TYPE_AT_ROOM_MENTION', // '@room' mention
|
||||
},
|
||||
|
||||
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)
|
||||
url: PropTypes.string,
|
||||
// Whether the pill is in a message
|
||||
|
@ -72,14 +81,20 @@ const Pill = React.createClass({
|
|||
regex = REGEX_LOCAL_MATRIXTO;
|
||||
}
|
||||
|
||||
// Default to the empty array if no match for simplicity
|
||||
// resource and prefix will be undefined instead of throwing
|
||||
const matrixToMatch = regex.exec(nextProps.url) || [];
|
||||
let matrixToMatch;
|
||||
let resourceId;
|
||||
let prefix;
|
||||
|
||||
const resourceId = matrixToMatch[1]; // The room/user ID
|
||||
const prefix = matrixToMatch[2]; // The first character of prefix
|
||||
if (nextProps.url) {
|
||||
// Default to the empty array if no match for simplicity
|
||||
// resource and prefix will be undefined instead of throwing
|
||||
matrixToMatch = regex.exec(nextProps.url) || [];
|
||||
|
||||
const pillType = {
|
||||
resourceId = matrixToMatch[1]; // The room/user ID
|
||||
prefix = matrixToMatch[2]; // The first character of prefix
|
||||
}
|
||||
|
||||
const pillType = this.props.type || {
|
||||
'@': Pill.TYPE_USER_MENTION,
|
||||
'#': Pill.TYPE_ROOM_MENTION,
|
||||
'!': Pill.TYPE_ROOM_MENTION,
|
||||
|
@ -88,6 +103,10 @@ const Pill = React.createClass({
|
|||
let member;
|
||||
let room;
|
||||
switch (pillType) {
|
||||
case Pill.TYPE_AT_ROOM_MENTION: {
|
||||
room = nextProps.room;
|
||||
}
|
||||
break;
|
||||
case Pill.TYPE_USER_MENTION: {
|
||||
const localMember = nextProps.room.getMember(resourceId);
|
||||
member = localMember;
|
||||
|
@ -160,6 +179,17 @@ const Pill = React.createClass({
|
|||
let href = this.props.url;
|
||||
let onClick;
|
||||
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: {
|
||||
// If this user is not a member of this room, default to the empty member
|
||||
const member = this.state.member;
|
||||
|
|
|
@ -17,50 +17,66 @@ 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 { GroupMemberType } from '../../../groups';
|
||||
import { groupMemberFromApiObject } from '../../../groups';
|
||||
import withMatrixClient from '../../../wrappers/withMatrixClient';
|
||||
import GroupStoreCache from '../../../stores/GroupStoreCache';
|
||||
import AccessibleButton from '../elements/AccessibleButton';
|
||||
import GeminiScrollbar from 'react-gemini-scrollbar';
|
||||
|
||||
|
||||
module.exports = withMatrixClient(React.createClass({
|
||||
module.exports = React.createClass({
|
||||
displayName: 'GroupMemberInfo',
|
||||
|
||||
contextTypes: {
|
||||
matrixClient: PropTypes.instanceOf(MatrixClient),
|
||||
},
|
||||
|
||||
propTypes: {
|
||||
matrixClient: PropTypes.object.isRequired,
|
||||
groupId: PropTypes.string,
|
||||
groupMember: GroupMemberType,
|
||||
isInvited: PropTypes.bool,
|
||||
},
|
||||
|
||||
getInitialState: function() {
|
||||
return {
|
||||
fetching: false,
|
||||
removingUser: false,
|
||||
groupMembers: null,
|
||||
isUserPrivilegedInGroup: null,
|
||||
};
|
||||
},
|
||||
|
||||
componentWillMount: function() {
|
||||
this._fetchMembers();
|
||||
this._initGroupStore(this.props.groupId);
|
||||
},
|
||||
|
||||
_fetchMembers: function() {
|
||||
this.setState({fetching: true});
|
||||
this.props.matrixClient.getGroupUsers(this.props.groupId).then((result) => {
|
||||
this.setState({
|
||||
groupMembers: result.chunk.map((apiMember) => {
|
||||
return groupMemberFromApiObject(apiMember);
|
||||
}),
|
||||
fetching: false,
|
||||
});
|
||||
}).catch((e) => {
|
||||
this.setState({fetching: false});
|
||||
console.error("Failed to get group groupMember list: ", e);
|
||||
componentWillReceiveProps(newProps) {
|
||||
if (newProps.groupId !== this.props.groupId) {
|
||||
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({
|
||||
isUserInvited: this._groupStore.getGroupInvitedMembers().some(
|
||||
(m) => m.userId === this.props.groupMember.userId,
|
||||
),
|
||||
isUserPrivilegedInGroup: this._groupStore.isUserPrivileged(),
|
||||
});
|
||||
},
|
||||
|
||||
|
@ -68,13 +84,15 @@ module.exports = withMatrixClient(React.createClass({
|
|||
const ConfirmUserActionDialog = sdk.getComponent("dialogs.ConfirmUserActionDialog");
|
||||
Modal.createDialog(ConfirmUserActionDialog, {
|
||||
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,
|
||||
onFinished: (proceed) => {
|
||||
if (!proceed) return;
|
||||
|
||||
this.setState({removingUser: true});
|
||||
this.props.matrixClient.removeUserFromGroup(
|
||||
this.context.matrixClient.removeUserFromGroup(
|
||||
this.props.groupId, this.props.groupMember.userId,
|
||||
).then(() => {
|
||||
// return to the user list
|
||||
|
@ -86,7 +104,9 @@ module.exports = withMatrixClient(React.createClass({
|
|||
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
||||
Modal.createTrackedDialog('Failed to remove user from group', '', ErrorDialog, {
|
||||
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(() => {
|
||||
this.setState({removingUser: false});
|
||||
|
@ -111,24 +131,17 @@ module.exports = withMatrixClient(React.createClass({
|
|||
},
|
||||
|
||||
render: function() {
|
||||
if (this.state.fetching || this.state.removingUser) {
|
||||
if (this.state.removingUser) {
|
||||
const Spinner = sdk.getComponent("elements.Spinner");
|
||||
return <Spinner />;
|
||||
}
|
||||
if (!this.state.groupMembers) return null;
|
||||
|
||||
const targetIsInGroup = this.state.groupMembers.some((m) => {
|
||||
return m.userId === this.props.groupMember.userId;
|
||||
});
|
||||
|
||||
let kickButton;
|
||||
let adminButton;
|
||||
|
||||
if (targetIsInGroup) {
|
||||
kickButton = (
|
||||
let adminTools;
|
||||
if (this.state.isUserPrivilegedInGroup) {
|
||||
const kickButton = (
|
||||
<AccessibleButton className="mx_MemberInfo_field"
|
||||
onClick={this._onKick}>
|
||||
{ _t('Remove from community') }
|
||||
{ this.state.isUserInvited ? _t('Disinvite') : _t('Remove from community') }
|
||||
</AccessibleButton>
|
||||
);
|
||||
|
||||
|
@ -137,22 +150,19 @@ module.exports = withMatrixClient(React.createClass({
|
|||
giveModButton = <AccessibleButton className="mx_MemberInfo_field" onClick={this.onModToggle}>
|
||||
{giveOpLabel}
|
||||
</AccessibleButton>;*/
|
||||
|
||||
if (kickButton) {
|
||||
adminTools =
|
||||
<div className="mx_MemberInfo_adminTools">
|
||||
<h3>{ _t("Admin Tools") }</h3>
|
||||
<div className="mx_MemberInfo_buttons">
|
||||
{ kickButton }
|
||||
</div>
|
||||
</div>;
|
||||
}
|
||||
}
|
||||
|
||||
let adminTools;
|
||||
if (kickButton || adminButton) {
|
||||
adminTools =
|
||||
<div className="mx_MemberInfo_adminTools">
|
||||
<h3>{ _t("Admin Tools") }</h3>
|
||||
|
||||
<div className="mx_MemberInfo_buttons">
|
||||
{ kickButton }
|
||||
{ adminButton }
|
||||
</div>
|
||||
</div>;
|
||||
}
|
||||
|
||||
const avatarUrl = this.props.matrixClient.mxcUrlToHttp(
|
||||
const avatarUrl = this.context.matrixClient.mxcUrlToHttp(
|
||||
this.props.groupMember.avatarUrl,
|
||||
36, 36, 'crop',
|
||||
);
|
||||
|
@ -192,4 +202,4 @@ module.exports = withMatrixClient(React.createClass({
|
|||
</div>
|
||||
);
|
||||
},
|
||||
}));
|
||||
});
|
||||
|
|
|
@ -50,12 +50,9 @@ export default withMatrixClient(React.createClass({
|
|||
|
||||
_initGroupStore: function(groupId) {
|
||||
this._groupStore = GroupStoreCache.getGroupStore(this.context.matrixClient, groupId);
|
||||
this._groupStore.on('update', () => {
|
||||
this._groupStore.registerListener(() => {
|
||||
this._fetchMembers();
|
||||
});
|
||||
this._groupStore.on('error', (err) => {
|
||||
console.error(err);
|
||||
});
|
||||
},
|
||||
|
||||
_fetchMembers: function() {
|
||||
|
|
|
@ -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) {
|
||||
this._groupStore = GroupStoreCache.getGroupStore(this.context.matrixClient, groupId);
|
||||
this._groupStore.on('update', () => {
|
||||
this._groupStore.registerListener(() => {
|
||||
this._fetchRooms();
|
||||
});
|
||||
this._groupStore.on('error', (err) => {
|
||||
console.error('Error in group store (listened to by GroupRoomList)', err);
|
||||
this.setState({
|
||||
rooms: null,
|
||||
});
|
||||
});
|
||||
this._fetchRooms();
|
||||
},
|
||||
|
||||
_fetchRooms: function() {
|
||||
|
|
|
@ -16,13 +16,10 @@ limitations under the License.
|
|||
|
||||
import React from 'react';
|
||||
import {MatrixClient} from 'matrix-js-sdk';
|
||||
import { _t } from '../../../languageHandler';
|
||||
import PropTypes from 'prop-types';
|
||||
import sdk from '../../../index';
|
||||
import dis from '../../../dispatcher';
|
||||
import { GroupRoomType } from '../../../groups';
|
||||
import GroupStoreCache from '../../../stores/GroupStoreCache';
|
||||
import Modal from '../../../Modal';
|
||||
|
||||
const GroupRoomTile = React.createClass({
|
||||
displayName: 'GroupRoomTile',
|
||||
|
@ -32,68 +29,11 @@ const GroupRoomTile = React.createClass({
|
|||
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) {
|
||||
let roomId;
|
||||
let roomAlias;
|
||||
if (this.props.groupRoom.canonicalAlias) {
|
||||
roomAlias = this.props.groupRoom.canonicalAlias;
|
||||
} else {
|
||||
roomId = this.props.groupRoom.roomId;
|
||||
}
|
||||
dis.dispatch({
|
||||
action: 'view_room',
|
||||
room_id: roomId,
|
||||
room_alias: roomAlias,
|
||||
});
|
||||
},
|
||||
|
||||
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();
|
||||
}
|
||||
},
|
||||
action: 'view_group_room',
|
||||
groupId: this.props.groupId,
|
||||
groupRoomId: this.props.groupRoom.roomId,
|
||||
});
|
||||
},
|
||||
|
||||
|
@ -106,7 +46,7 @@ const GroupRoomTile = React.createClass({
|
|||
);
|
||||
|
||||
const av = (
|
||||
<BaseAvatar name={this.state.name}
|
||||
<BaseAvatar name={this.props.groupRoom.displayname}
|
||||
width={36} height={36}
|
||||
url={avatarUrl}
|
||||
/>
|
||||
|
@ -118,14 +58,8 @@ const GroupRoomTile = React.createClass({
|
|||
{ av }
|
||||
</div>
|
||||
<div className="mx_GroupRoomTile_name">
|
||||
{ this.state.name }
|
||||
{ this.props.groupRoom.displayname }
|
||||
</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>
|
||||
);
|
||||
},
|
||||
|
|
|
@ -20,7 +20,7 @@ import url from 'url';
|
|||
import classnames from 'classnames';
|
||||
|
||||
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
|
||||
* InteractiveAuth to prompt the user to enter the information needed
|
||||
|
@ -256,7 +256,7 @@ export const EmailIdentityAuthEntry = React.createClass({
|
|||
} else {
|
||||
return (
|
||||
<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>
|
||||
</div>
|
||||
);
|
||||
|
@ -370,7 +370,7 @@ export const MsisdnAuthEntry = React.createClass({
|
|||
});
|
||||
return (
|
||||
<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>
|
||||
<div className="mx_InteractiveAuthEntryComponents_msisdnWrapper">
|
||||
<form onSubmit={this._onFormSubmit}>
|
||||
|
|
|
@ -19,6 +19,7 @@
|
|||
import React from 'react';
|
||||
import sdk from '../../../index';
|
||||
import Flair from '../elements/Flair.js';
|
||||
import { _tJsx } from '../../../languageHandler';
|
||||
|
||||
export default function SenderProfile(props) {
|
||||
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
|
||||
}
|
||||
|
||||
// Name + flair
|
||||
const nameElem = [
|
||||
<EmojiText key='name' className="mx_SenderProfile_name">{ name || '' }</EmojiText>,
|
||||
props.enableFlair ?
|
||||
<Flair key='flair'
|
||||
userId={mxEvent.getSender()}
|
||||
roomId={mxEvent.getRoomId()}
|
||||
showRelated={true} />
|
||||
: 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;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="mx_SenderProfile" dir="auto" onClick={props.onClick}>
|
||||
<EmojiText className="mx_SenderProfile_name">{ name || '' }</EmojiText>
|
||||
{ props.enableFlair ?
|
||||
<Flair
|
||||
userId={mxEvent.getSender()}
|
||||
roomId={mxEvent.getRoomId()}
|
||||
showRelated={true} />
|
||||
: null
|
||||
}
|
||||
{ props.aux ? <EmojiText className="mx_SenderProfile_aux"> { props.aux }</EmojiText> : null }
|
||||
{ content }
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
SenderProfile.propTypes = {
|
||||
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,
|
||||
};
|
||||
|
|
|
@ -34,6 +34,7 @@ import MatrixClientPeg from '../../../MatrixClientPeg';
|
|||
import ContextualMenu from '../../structures/ContextualMenu';
|
||||
import {RoomMember} from 'matrix-js-sdk';
|
||||
import classNames from 'classnames';
|
||||
import PushProcessor from 'matrix-js-sdk/lib/pushprocessor';
|
||||
|
||||
linkifyMatrix(linkify);
|
||||
|
||||
|
@ -169,8 +170,10 @@ module.exports = React.createClass({
|
|||
|
||||
pillifyLinks: function(nodes) {
|
||||
const shouldShowPillAvatar = !UserSettingsStore.getSyncedSetting("Pill.shouldHidePillAvatar", false);
|
||||
for (let i = 0; i < nodes.length; i++) {
|
||||
const node = nodes[i];
|
||||
let node = nodes[0];
|
||||
while (node) {
|
||||
let pillified = false;
|
||||
|
||||
if (node.tagName === "A" && node.getAttribute("href")) {
|
||||
const href = node.getAttribute("href");
|
||||
|
||||
|
@ -189,10 +192,68 @@ module.exports = React.createClass({
|
|||
|
||||
ReactDOM.render(pill, pillContainer);
|
||||
node.parentNode.replaceChild(pillContainer, node);
|
||||
// Pills within pills aren't going to go well, so move on
|
||||
pillified = true;
|
||||
}
|
||||
} else if (node.nodeType == Node.TEXT_NODE) {
|
||||
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;
|
||||
}
|
||||
}
|
||||
} else if (node.children && node.children.length) {
|
||||
this.pillifyLinks(node.children);
|
||||
}
|
||||
|
||||
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 classNames = require("classnames");
|
||||
import { _t } from '../../../languageHandler';
|
||||
import { _t, _td } from '../../../languageHandler';
|
||||
const Modal = require('../../../Modal');
|
||||
|
||||
const sdk = require('../../../index');
|
||||
|
@ -502,12 +502,12 @@ module.exports = withMatrixClient(React.createClass({
|
|||
}
|
||||
|
||||
if (needsSenderProfile) {
|
||||
let aux = null;
|
||||
let text = null;
|
||||
if (!this.props.tileShape) {
|
||||
if (msgtype === 'm.image') aux = _t('sent an image');
|
||||
else if (msgtype === 'm.video') aux = _t('sent a video');
|
||||
else if (msgtype === 'm.file') aux = _t('uploaded a file');
|
||||
sender = <SenderProfile onClick={this.onSenderProfileClick} mxEvent={this.props.mxEvent} enableFlair={!aux} aux={aux} />;
|
||||
if (msgtype === 'm.image') text = _td('%(senderName)s sent an image');
|
||||
else if (msgtype === 'm.video') text = _td('%(senderName)s sent a video');
|
||||
else if (msgtype === 'm.file') text = _td('%(senderName)s uploaded a file');
|
||||
sender = <SenderProfile onClick={this.onSenderProfileClick} mxEvent={this.props.mxEvent} enableFlair={!text} text={text} />;
|
||||
} else {
|
||||
sender = <SenderProfile mxEvent={this.props.mxEvent} enableFlair={true} />;
|
||||
}
|
||||
|
|
|
@ -256,11 +256,11 @@ module.exports = withMatrixClient(React.createClass({
|
|||
|
||||
onKick: function() {
|
||||
const membership = this.props.member.membership;
|
||||
const kickLabel = membership === "invite" ? _t("Disinvite") : _t("Kick");
|
||||
const ConfirmUserActionDialog = sdk.getComponent("dialogs.ConfirmUserActionDialog");
|
||||
Modal.createTrackedDialog('Confirm User Action Dialog', 'onKick', ConfirmUserActionDialog, {
|
||||
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",
|
||||
danger: true,
|
||||
onFinished: (proceed, reason) => {
|
||||
|
@ -294,6 +294,7 @@ module.exports = withMatrixClient(React.createClass({
|
|||
Modal.createTrackedDialog('Confirm User Action Dialog', 'onBanOrUnban', ConfirmUserActionDialog, {
|
||||
member: this.props.member,
|
||||
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',
|
||||
danger: this.props.member.membership !== 'ban',
|
||||
onFinished: (proceed, reason) => {
|
||||
|
|
|
@ -29,18 +29,20 @@ function getDisplayAliasForRoom(room) {
|
|||
}
|
||||
|
||||
const RoomDetailRow = React.createClass({
|
||||
propTypes: PropTypes.shape({
|
||||
name: PropTypes.string,
|
||||
topic: PropTypes.string,
|
||||
roomId: PropTypes.string,
|
||||
avatarUrl: PropTypes.string,
|
||||
numJoinedMembers: PropTypes.number,
|
||||
canonicalAlias: PropTypes.string,
|
||||
aliases: PropTypes.arrayOf(PropTypes.string),
|
||||
propTypes: {
|
||||
room: PropTypes.shape({
|
||||
name: PropTypes.string,
|
||||
topic: PropTypes.string,
|
||||
roomId: PropTypes.string,
|
||||
avatarUrl: PropTypes.string,
|
||||
numJoinedMembers: PropTypes.number,
|
||||
canonicalAlias: PropTypes.string,
|
||||
aliases: PropTypes.arrayOf(PropTypes.string),
|
||||
|
||||
worldReadable: PropTypes.bool,
|
||||
guestCanJoin: PropTypes.bool,
|
||||
}),
|
||||
worldReadable: PropTypes.bool,
|
||||
guestCanJoin: PropTypes.bool,
|
||||
}),
|
||||
},
|
||||
|
||||
onClick: function(ev) {
|
||||
ev.preventDefault();
|
||||
|
|
|
@ -34,27 +34,18 @@ const Receipt = require('../../../utils/Receipt');
|
|||
const HIDE_CONFERENCE_CHANS = true;
|
||||
|
||||
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) {
|
||||
case 'm.favourite':
|
||||
verb = _t('to favourite');
|
||||
break;
|
||||
return _t('Drop here to favourite');
|
||||
case 'im.vector.fake.direct':
|
||||
verb = _t('to tag direct chat');
|
||||
break;
|
||||
return _t('Drop here to tag direct chat');
|
||||
case 'im.vector.fake.recent':
|
||||
verb = _t('to restore');
|
||||
break;
|
||||
return _t('Drop here to restore');
|
||||
case 'm.lowpriority':
|
||||
verb = _t('to demote');
|
||||
break;
|
||||
return _t('Drop here to demote');
|
||||
default:
|
||||
return _t('Drop here to tag %(section)s', {section: section});
|
||||
}
|
||||
return _t('Drop here %(toAction)s', {toAction: verb});
|
||||
}
|
||||
|
||||
module.exports = React.createClass({
|
||||
|
|
|
@ -83,10 +83,8 @@ module.exports = React.createClass({
|
|||
}
|
||||
},
|
||||
|
||||
_roomNameElement: function(fallback) {
|
||||
fallback = fallback || _t('a room');
|
||||
const name = this.props.room ? this.props.room.name : (this.props.room_alias || "");
|
||||
return name ? name : fallback;
|
||||
_roomNameElement: function() {
|
||||
return this.props.room ? this.props.room.name : (this.props.room_alias || "");
|
||||
},
|
||||
|
||||
render: function() {
|
||||
|
@ -150,7 +148,7 @@ module.exports = React.createClass({
|
|||
</div>
|
||||
);
|
||||
} else if (kicked || banned) {
|
||||
const roomName = this._roomNameElement(_t('This room'));
|
||||
const roomName = this._roomNameElement();
|
||||
const kickerMember = this.props.room.currentState.getMember(
|
||||
myMember.events.member.getSender(),
|
||||
);
|
||||
|
@ -167,9 +165,17 @@ module.exports = React.createClass({
|
|||
|
||||
let actionText;
|
||||
if (kicked) {
|
||||
actionText = _t("You have been kicked from %(roomName)s by %(userName)s.", {roomName: roomName, userName: kickerName});
|
||||
if(roomName) {
|
||||
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) {
|
||||
actionText = _t("You have been banned from %(roomName)s by %(userName)s.", {roomName: roomName, userName: kickerName});
|
||||
if(roomName) {
|
||||
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.
|
||||
|
||||
joinBlock = (
|
||||
|
@ -203,7 +209,7 @@ module.exports = React.createClass({
|
|||
joinBlock = (
|
||||
<div>
|
||||
<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 />
|
||||
{ _tJsx("<a>Click here</a> to join the discussion!",
|
||||
/<a>(.*?)<\/a>/,
|
||||
|
|
|
@ -71,6 +71,7 @@ const BannedUser = React.createClass({
|
|||
Modal.createTrackedDialog('Confirm User Action Dialog', 'onUnbanClick', ConfirmUserActionDialog, {
|
||||
member: this.props.member,
|
||||
action: _t('Unban'),
|
||||
title: _t('Unban this user?'),
|
||||
danger: false,
|
||||
onFinished: (proceed) => {
|
||||
if (!proceed) return;
|
||||
|
@ -866,21 +867,21 @@ module.exports = React.createClass({
|
|||
disabled={!roomState.mayClientSendStateEvent("m.room.history_visibility", cli)}
|
||||
checked={historyVisibility === "shared"}
|
||||
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>
|
||||
<input type="radio" name="historyVis" value="invited"
|
||||
disabled={!roomState.mayClientSendStateEvent("m.room.history_visibility", cli)}
|
||||
checked={historyVisibility === "invited"}
|
||||
onChange={this._onHistoryRadioToggle} />
|
||||
{ _t('Members only') } ({ _t('since they were invited') })
|
||||
{ _t('Members only (since they were invited)') }
|
||||
</label>
|
||||
<label >
|
||||
<input type="radio" name="historyVis" value="joined"
|
||||
disabled={!roomState.mayClientSendStateEvent("m.room.history_visibility", cli)}
|
||||
checked={historyVisibility === "joined"}
|
||||
onChange={this._onHistoryRadioToggle} />
|
||||
{ _t('Members only') } ({ _t('since they joined') })
|
||||
{ _t('Members only (since they joined)') }
|
||||
</label>
|
||||
</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 newPassword = this.refs.new_input.value;
|
||||
const confirmPassword = this.refs.confirm_input.value;
|
||||
|
|
|
@ -15,6 +15,7 @@ limitations under the License.
|
|||
*/
|
||||
|
||||
import PropTypes from 'prop-types';
|
||||
import { _t } from './languageHandler.js';
|
||||
|
||||
export const GroupMemberType = PropTypes.shape({
|
||||
userId: PropTypes.string.isRequired,
|
||||
|
@ -23,6 +24,7 @@ export const GroupMemberType = PropTypes.shape({
|
|||
});
|
||||
|
||||
export const GroupRoomType = PropTypes.shape({
|
||||
displayname: PropTypes.string,
|
||||
name: PropTypes.string,
|
||||
roomId: PropTypes.string.isRequired,
|
||||
canonicalAlias: PropTypes.string,
|
||||
|
@ -39,6 +41,7 @@ export function groupMemberFromApiObject(apiObject) {
|
|||
|
||||
export function groupRoomFromApiObject(apiObject) {
|
||||
return {
|
||||
displayname: apiObject.name || apiObject.canonical_alias || _t("Unnamed Room"),
|
||||
name: apiObject.name,
|
||||
roomId: apiObject.room_id,
|
||||
canonicalAlias: apiObject.canonical_alias,
|
||||
|
@ -47,5 +50,6 @@ export function groupRoomFromApiObject(apiObject) {
|
|||
numJoinedMembers: apiObject.num_joined_members,
|
||||
worldReadable: apiObject.world_readable,
|
||||
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",
|
||||
"Communities": "Communities",
|
||||
"Message Pinning": "Message Pinning",
|
||||
"Mention": "Mention",
|
||||
"%(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|one": "%(names)s and one other is typing",
|
||||
"%(names)s and %(lastPerson)s are typing": "%(names)s and %(lastPerson)s are typing",
|
||||
"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.",
|
||||
"Unnamed Room": "Unnamed Room",
|
||||
"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",
|
||||
"Authentication check failed: incorrect password?": "Authentication check failed: incorrect password?",
|
||||
|
@ -211,9 +211,9 @@
|
|||
" (unsupported)": " (unsupported)",
|
||||
"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.",
|
||||
"sent an image": "sent an image",
|
||||
"sent a video": "sent a video",
|
||||
"uploaded a file": "uploaded a file",
|
||||
"%(senderName)s sent an image": "%(senderName)s sent an image",
|
||||
"%(senderName)s sent a video": "%(senderName)s sent a video",
|
||||
"%(senderName)s uploaded a file": "%(senderName)s uploaded a file",
|
||||
"Options": "Options",
|
||||
"Undecryptable": "Undecryptable",
|
||||
"Encrypted by a verified device": "Encrypted by a verified device",
|
||||
|
@ -226,9 +226,13 @@
|
|||
"device id: ": "device id: ",
|
||||
"Disinvite": "Disinvite",
|
||||
"Kick": "Kick",
|
||||
"Disinvite this user?": "Disinvite this user?",
|
||||
"Kick this user?": "Kick this user?",
|
||||
"Failed to kick": "Failed to kick",
|
||||
"Unban": "Unban",
|
||||
"Ban": "Ban",
|
||||
"Unban this user?": "Unban this user?",
|
||||
"Ban this user?": "Ban this user?",
|
||||
"Failed to ban user": "Failed to ban user",
|
||||
"Failed to mute user": "Failed to mute user",
|
||||
"Failed to toggle moderator status": "Failed to toggle moderator status",
|
||||
|
@ -240,6 +244,7 @@
|
|||
"Unignore": "Unignore",
|
||||
"Ignore": "Ignore",
|
||||
"Jump to read receipt": "Jump to read receipt",
|
||||
"Mention": "Mention",
|
||||
"Invite": "Invite",
|
||||
"User Options": "User Options",
|
||||
"Direct chats": "Direct chats",
|
||||
|
@ -314,12 +319,11 @@
|
|||
"Forget room": "Forget room",
|
||||
"Search": "Search",
|
||||
"Show panel": "Show panel",
|
||||
"to favourite": "to favourite",
|
||||
"to tag direct chat": "to tag direct chat",
|
||||
"to restore": "to restore",
|
||||
"to demote": "to demote",
|
||||
"Drop here to favourite": "Drop here to favourite",
|
||||
"Drop here to tag direct chat": "Drop here to tag direct chat",
|
||||
"Drop here to restore": "Drop here to restore",
|
||||
"Drop here to demote": "Drop here to demote",
|
||||
"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",
|
||||
"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",
|
||||
|
@ -328,21 +332,22 @@
|
|||
"Rooms": "Rooms",
|
||||
"Low priority": "Low priority",
|
||||
"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.",
|
||||
"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 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?",
|
||||
"This room": "This room",
|
||||
"Reason: %(reasonText)s": "Reason: %(reasonText)s",
|
||||
"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 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 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 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 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!",
|
||||
"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",
|
||||
|
@ -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?",
|
||||
"Who can read history?": "Who can read history?",
|
||||
"Anyone": "Anyone",
|
||||
"Members only": "Members only",
|
||||
"since the point in time of selecting this option": "since the point in time of selecting this option",
|
||||
"since they were invited": "since they were invited",
|
||||
"since they joined": "since they joined",
|
||||
"Members only (since the point in time of selecting this option)": "Members only (since the point in time of selecting this option)",
|
||||
"Members only (since they were invited)": "Members only (since they were invited)",
|
||||
"Members only (since they joined)": "Members only (since they joined)",
|
||||
"Room Colour": "Room Colour",
|
||||
"Permissions": "Permissions",
|
||||
"The default role for new room members is": "The default role for new room members is",
|
||||
|
@ -463,10 +467,10 @@
|
|||
"Dismiss": "Dismiss",
|
||||
"To continue, please enter your password.": "To continue, please enter your 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.",
|
||||
"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:",
|
||||
"Start authentication": "Start authentication",
|
||||
"powered by Matrix": "powered by Matrix",
|
||||
|
@ -488,15 +492,22 @@
|
|||
"Identity server URL": "Identity server URL",
|
||||
"What does this mean?": "What does this mean?",
|
||||
"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",
|
||||
"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?",
|
||||
"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 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",
|
||||
"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:",
|
||||
|
@ -516,56 +527,57 @@
|
|||
"Integrations Error": "Integrations Error",
|
||||
"Could not connect to the integration server": "Could not connect to the integration server",
|
||||
"Manage Integrations": "Manage Integrations",
|
||||
"%(severalUsers)sjoined %(repeats)s times": "%(severalUsers)sjoined %(repeats)s times",
|
||||
"%(oneUser)sjoined %(repeats)s times": "%(oneUser)sjoined %(repeats)s times",
|
||||
"%(severalUsers)sjoined": "%(severalUsers)sjoined",
|
||||
"%(oneUser)sjoined": "%(oneUser)sjoined",
|
||||
"%(severalUsers)sleft %(repeats)s times": "%(severalUsers)sleft %(repeats)s times",
|
||||
"%(oneUser)sleft %(repeats)s times": "%(oneUser)sleft %(repeats)s times",
|
||||
"%(severalUsers)sleft": "%(severalUsers)sleft",
|
||||
"%(oneUser)sleft": "%(oneUser)sleft",
|
||||
"%(severalUsers)sjoined and left %(repeats)s times": "%(severalUsers)sjoined and left %(repeats)s times",
|
||||
"%(oneUser)sjoined and left %(repeats)s times": "%(oneUser)sjoined and left %(repeats)s times",
|
||||
"%(severalUsers)sjoined and left": "%(severalUsers)sjoined and left",
|
||||
"%(oneUser)sjoined and left": "%(oneUser)sjoined and left",
|
||||
"%(severalUsers)sleft and rejoined %(repeats)s times": "%(severalUsers)sleft and rejoined %(repeats)s times",
|
||||
"%(oneUser)sleft and rejoined %(repeats)s times": "%(oneUser)sleft and rejoined %(repeats)s times",
|
||||
"%(severalUsers)sleft and rejoined": "%(severalUsers)sleft and rejoined",
|
||||
"%(oneUser)sleft and rejoined": "%(oneUser)sleft and rejoined",
|
||||
"%(severalUsers)srejected their invitations %(repeats)s times": "%(severalUsers)srejected their invitations %(repeats)s times",
|
||||
"%(oneUser)srejected their invitation %(repeats)s times": "%(oneUser)srejected their invitation %(repeats)s times",
|
||||
"%(severalUsers)srejected their invitations": "%(severalUsers)srejected their invitations",
|
||||
"%(oneUser)srejected their invitation": "%(oneUser)srejected their invitation",
|
||||
"%(severalUsers)shad their invitations withdrawn %(repeats)s times": "%(severalUsers)shad their invitations withdrawn %(repeats)s times",
|
||||
"%(oneUser)shad their invitation withdrawn %(repeats)s times": "%(oneUser)shad their invitation withdrawn %(repeats)s times",
|
||||
"%(severalUsers)shad their invitations withdrawn": "%(severalUsers)shad their invitations withdrawn",
|
||||
"%(oneUser)shad their invitation withdrawn": "%(oneUser)shad their invitation withdrawn",
|
||||
"were invited %(repeats)s times": "were invited %(repeats)s times",
|
||||
"was invited %(repeats)s times": "was invited %(repeats)s times",
|
||||
"were invited": "were invited",
|
||||
"was invited": "was invited",
|
||||
"were banned %(repeats)s times": "were banned %(repeats)s times",
|
||||
"was banned %(repeats)s times": "was banned %(repeats)s times",
|
||||
"were banned": "were banned",
|
||||
"was banned": "was banned",
|
||||
"were unbanned %(repeats)s times": "were unbanned %(repeats)s times",
|
||||
"was unbanned %(repeats)s times": "was unbanned %(repeats)s times",
|
||||
"were unbanned": "were unbanned",
|
||||
"was unbanned": "was unbanned",
|
||||
"were kicked %(repeats)s times": "were kicked %(repeats)s times",
|
||||
"was kicked %(repeats)s times": "was kicked %(repeats)s times",
|
||||
"were kicked": "were kicked",
|
||||
"was kicked": "was kicked",
|
||||
"%(severalUsers)schanged their name %(repeats)s times": "%(severalUsers)schanged their name %(repeats)s times",
|
||||
"%(oneUser)schanged their name %(repeats)s times": "%(oneUser)schanged their name %(repeats)s times",
|
||||
"%(severalUsers)schanged their name": "%(severalUsers)schanged their name",
|
||||
"%(oneUser)schanged their name": "%(oneUser)schanged their name",
|
||||
"%(severalUsers)schanged their avatar %(repeats)s times": "%(severalUsers)schanged their avatar %(repeats)s times",
|
||||
"%(oneUser)schanged their avatar %(repeats)s times": "%(oneUser)schanged their avatar %(repeats)s times",
|
||||
"%(severalUsers)schanged their avatar": "%(severalUsers)schanged their avatar",
|
||||
"%(oneUser)schanged their avatar": "%(oneUser)schanged their avatar",
|
||||
"%(items)s and %(remaining)s others": "%(items)s and %(remaining)s others",
|
||||
"%(items)s and one other": "%(items)s and one other",
|
||||
"%(nameList)s %(transitionList)s": "%(nameList)s %(transitionList)s",
|
||||
"%(severalUsers)sjoined %(count)s times|other": "%(severalUsers)sjoined %(count)s times",
|
||||
"%(severalUsers)sjoined %(count)s times|one": "%(severalUsers)sjoined",
|
||||
"%(oneUser)sjoined %(count)s times|other": "%(oneUser)sjoined %(count)s times",
|
||||
"%(oneUser)sjoined %(count)s times|one": "%(oneUser)sjoined",
|
||||
"%(severalUsers)sleft %(count)s times|other": "%(severalUsers)sleft %(count)s times",
|
||||
"%(severalUsers)sleft %(count)s times|one": "%(severalUsers)sleft",
|
||||
"%(oneUser)sleft %(count)s times|other": "%(oneUser)sleft %(count)s times",
|
||||
"%(oneUser)sleft %(count)s times|one": "%(oneUser)sleft",
|
||||
"%(severalUsers)sjoined and left %(count)s times|other": "%(severalUsers)sjoined and left %(count)s times",
|
||||
"%(severalUsers)sjoined and left %(count)s times|one": "%(severalUsers)sjoined and left",
|
||||
"%(oneUser)sjoined and left %(count)s times|other": "%(oneUser)sjoined and left %(count)s times",
|
||||
"%(oneUser)sjoined and left %(count)s times|one": "%(oneUser)sjoined and left",
|
||||
"%(severalUsers)sleft and rejoined %(count)s times|other": "%(severalUsers)sleft and rejoined %(count)s times",
|
||||
"%(severalUsers)sleft and rejoined %(count)s times|one": "%(severalUsers)sleft and rejoined",
|
||||
"%(oneUser)sleft and rejoined %(count)s times|other": "%(oneUser)sleft and rejoined %(count)s times",
|
||||
"%(oneUser)sleft and rejoined %(count)s times|one": "%(oneUser)sleft and rejoined",
|
||||
"%(severalUsers)srejected their invitations %(count)s times|other": "%(severalUsers)srejected their invitations %(count)s times",
|
||||
"%(severalUsers)srejected their invitations %(count)s times|one": "%(severalUsers)srejected their invitations",
|
||||
"%(oneUser)srejected their invitation %(count)s times|other": "%(oneUser)srejected their invitation %(count)s times",
|
||||
"%(oneUser)srejected their invitation %(count)s times|one": "%(oneUser)srejected their invitation",
|
||||
"%(severalUsers)shad their invitations withdrawn %(count)s times|other": "%(severalUsers)shad their invitations withdrawn %(count)s times",
|
||||
"%(severalUsers)shad their invitations withdrawn %(count)s times|one": "%(severalUsers)shad their invitations withdrawn",
|
||||
"%(oneUser)shad their invitation withdrawn %(count)s times|other": "%(oneUser)shad their invitation withdrawn %(count)s times",
|
||||
"%(oneUser)shad their invitation withdrawn %(count)s times|one": "%(oneUser)shad their invitation withdrawn",
|
||||
"were invited %(count)s times|other": "were invited %(count)s times",
|
||||
"were invited %(count)s times|one": "were invited",
|
||||
"was invited %(count)s times|other": "was invited %(count)s times",
|
||||
"was invited %(count)s times|one": "was invited",
|
||||
"were banned %(count)s times|other": "were banned %(count)s times",
|
||||
"were banned %(count)s times|one": "were banned",
|
||||
"was banned %(count)s times|other": "was banned %(count)s times",
|
||||
"was banned %(count)s times|one": "was banned",
|
||||
"were unbanned %(count)s times|other": "were unbanned %(count)s times",
|
||||
"were unbanned %(count)s times|one": "were unbanned",
|
||||
"was unbanned %(count)s times|other": "was unbanned %(count)s times",
|
||||
"was unbanned %(count)s times|one": "was unbanned",
|
||||
"were kicked %(count)s times|other": "were kicked %(count)s times",
|
||||
"were kicked %(count)s times|one": "were kicked",
|
||||
"was kicked %(count)s times|other": "was kicked %(count)s times",
|
||||
"was kicked %(count)s times|one": "was kicked",
|
||||
"%(severalUsers)schanged their name %(count)s times|other": "%(severalUsers)schanged their name %(count)s times",
|
||||
"%(severalUsers)schanged their name %(count)s times|one": "%(severalUsers)schanged their name",
|
||||
"%(oneUser)schanged their name %(count)s times|other": "%(oneUser)schanged their name %(count)s times",
|
||||
"%(oneUser)schanged their name %(count)s times|one": "%(oneUser)schanged their name",
|
||||
"%(severalUsers)schanged their avatar %(count)s times|other": "%(severalUsers)schanged their avatar %(count)s times",
|
||||
"%(severalUsers)schanged their avatar %(count)s times|one": "%(severalUsers)schanged their avatar",
|
||||
"%(oneUser)schanged their avatar %(count)s times|other": "%(oneUser)schanged their avatar %(count)s times",
|
||||
"%(oneUser)schanged their avatar %(count)s times|one": "%(oneUser)schanged their avatar",
|
||||
"%(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",
|
||||
"Custom level": "Custom level",
|
||||
"Room directory": "Room directory",
|
||||
|
@ -573,7 +585,6 @@
|
|||
"And %(count)s more...|other": "And %(count)s more...",
|
||||
"ex. @bob:example.com": "ex. @bob:example.com",
|
||||
"Add User": "Add User",
|
||||
"Something went wrong!": "Something went wrong!",
|
||||
"Matrix ID": "Matrix ID",
|
||||
"Matrix Room ID": "Matrix Room ID",
|
||||
"email address": "email address",
|
||||
|
@ -587,8 +598,7 @@
|
|||
"Start Chatting": "Start Chatting",
|
||||
"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.",
|
||||
"%(actionVerb)s this person?": "%(actionVerb)s this person?",
|
||||
"Community IDs may only contain alphanumeric characters": "Community IDs may only contain alphanumeric characters",
|
||||
"Community IDs may only contain characters a-z, 0-9, or '=_-./'": "Community IDs may only contain characters a-z, 0-9, or '=_-./'",
|
||||
"Something went wrong whilst creating your community": "Something went wrong whilst creating your community",
|
||||
"Create Community": "Create Community",
|
||||
"Community Name": "Community Name",
|
||||
|
@ -831,7 +841,7 @@
|
|||
"A new password must be entered.": "A new password must be entered.",
|
||||
"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.",
|
||||
"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",
|
||||
"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",
|
||||
|
|
|
@ -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) {
|
||||
return new Promise((resolve, reject) => {
|
||||
request(
|
||||
|
@ -261,7 +281,7 @@ function getLanguage(langPath) {
|
|||
reject({err: err, response: response});
|
||||
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
|
||||
if (this._usersPending[userId]) {
|
||||
if (this._usersPending[userId] || this._usersInFlight[userId]) {
|
||||
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);
|
||||
throw err;
|
||||
}).finally(() => {
|
||||
delete this._usersPending[userId];
|
||||
delete this._usersInFlight[userId];
|
||||
});
|
||||
|
||||
// This debounce will allow consecutive requests for the public groups of users that
|
||||
|
@ -113,23 +113,25 @@ class FlairStore extends EventEmitter {
|
|||
}
|
||||
|
||||
async _batchedGetPublicGroups(matrixClient) {
|
||||
// Take the userIds from the keys of this._usersPending
|
||||
const usersInFlight = Object.keys(this._usersPending);
|
||||
// Move users pending to users in flight
|
||||
this._usersInFlight = this._usersPending;
|
||||
this._usersPending = {};
|
||||
|
||||
let resp = {
|
||||
users: [],
|
||||
};
|
||||
try {
|
||||
resp = await matrixClient.getPublicisedGroups(usersInFlight);
|
||||
resp = await matrixClient.getPublicisedGroups(Object.keys(this._usersInFlight));
|
||||
} catch (err) {
|
||||
// Propagate the same error to all usersInFlight
|
||||
usersInFlight.forEach((userId) => {
|
||||
this._usersPending[userId].reject(err);
|
||||
Object.keys(this._usersInFlight).forEach((userId) => {
|
||||
this._usersInFlight[userId].reject(err);
|
||||
});
|
||||
return;
|
||||
}
|
||||
const updatedUserGroups = resp.users;
|
||||
usersInFlight.forEach((userId) => {
|
||||
this._usersPending[userId].resolve(updatedUserGroups[userId] || []);
|
||||
Object.keys(this._usersInFlight).forEach((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.
|
||||
*/
|
||||
export default class GroupStore extends EventEmitter {
|
||||
|
||||
static STATE_KEY = {
|
||||
GroupMembers: 'GroupMembers',
|
||||
GroupInvitedMembers: 'GroupInvitedMembers',
|
||||
Summary: 'Summary',
|
||||
GroupRooms: 'GroupRooms',
|
||||
};
|
||||
|
||||
constructor(matrixClient, groupId) {
|
||||
super();
|
||||
this.groupId = groupId;
|
||||
this._matrixClient = matrixClient;
|
||||
this._summary = {};
|
||||
this._rooms = [];
|
||||
this._fetchSummary();
|
||||
this._fetchRooms();
|
||||
this._fetchMembers();
|
||||
this._members = [];
|
||||
this._invitedMembers = [];
|
||||
this._ready = {};
|
||||
|
||||
this.on('error', (err) => {
|
||||
console.error(`GroupStore for ${this.groupId} encountered error`, err);
|
||||
});
|
||||
}
|
||||
|
||||
_fetchMembers() {
|
||||
|
@ -39,6 +51,7 @@ export default class GroupStore extends EventEmitter {
|
|||
this._members = result.chunk.map((apiMember) => {
|
||||
return groupMemberFromApiObject(apiMember);
|
||||
});
|
||||
this._ready[GroupStore.STATE_KEY.GroupMembers] = true;
|
||||
this._notifyListeners();
|
||||
}).catch((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) => {
|
||||
return groupMemberFromApiObject(apiMember);
|
||||
});
|
||||
this._ready[GroupStore.STATE_KEY.GroupInvitedMembers] = true;
|
||||
this._notifyListeners();
|
||||
}).catch((err) => {
|
||||
// Invited users not visible to non-members
|
||||
if (err.httpStatus === 403) {
|
||||
return;
|
||||
}
|
||||
console.error("Failed to get group invited member list: " + err);
|
||||
this.emit('error', err);
|
||||
});
|
||||
|
@ -59,6 +77,7 @@ export default class GroupStore extends EventEmitter {
|
|||
_fetchSummary() {
|
||||
this._matrixClient.getGroupSummary(this.groupId).then((resp) => {
|
||||
this._summary = resp;
|
||||
this._ready[GroupStore.STATE_KEY.Summary] = true;
|
||||
this._notifyListeners();
|
||||
}).catch((err) => {
|
||||
this.emit('error', err);
|
||||
|
@ -70,6 +89,7 @@ export default class GroupStore extends EventEmitter {
|
|||
this._rooms = resp.chunk.map((apiRoom) => {
|
||||
return groupRoomFromApiObject(apiRoom);
|
||||
});
|
||||
this._ready[GroupStore.STATE_KEY.GroupRooms] = true;
|
||||
this._notifyListeners();
|
||||
}).catch((err) => {
|
||||
this.emit('error', err);
|
||||
|
@ -80,6 +100,23 @@ export default class GroupStore extends EventEmitter {
|
|||
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() {
|
||||
return this._summary;
|
||||
}
|
||||
|
@ -104,9 +141,15 @@ export default class GroupStore extends EventEmitter {
|
|||
return this._summary.user ? this._summary.user.is_privileged : null;
|
||||
}
|
||||
|
||||
addRoomToGroup(roomId) {
|
||||
addRoomToGroup(roomId, isPublic) {
|
||||
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));
|
||||
}
|
||||
|
||||
|
|
|
@ -88,6 +88,9 @@ describe('MemberEventListSummary', function() {
|
|||
sandbox = testUtils.stubClient();
|
||||
|
||||
languageHandler.setLanguage('en').done(done);
|
||||
languageHandler.setMissingEntryGenerator(function(key) {
|
||||
return key.split('|', 2)[1];
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(function() {
|
||||
|
|
Loading…
Reference in New Issue