@@ -1827,16 +1834,18 @@ module.exports = React.createClass({
onLeaveClick={(myMember && myMember.membership === "join") ? this.onLeaveClick : null}
/>
{ auxPanel }
- { topUnreadMessagesBar }
- { messagePanel }
- { searchResultsPanel }
-
-
-
- { statusBar }
+
+ { topUnreadMessagesBar }
+ { messagePanel }
+ { searchResultsPanel }
+
+ { messageComposer }
- { messageComposer }
);
},
diff --git a/src/components/structures/TimelinePanel.js b/src/components/structures/TimelinePanel.js
index e3b3b66f97..2bf8a08b98 100644
--- a/src/components/structures/TimelinePanel.js
+++ b/src/components/structures/TimelinePanel.js
@@ -89,9 +89,6 @@ var TimelinePanel = React.createClass({
// callback which is called when the read-up-to mark is updated.
onReadMarkerUpdated: React.PropTypes.func,
- // opacity for dynamic UI fading effects
- opacity: React.PropTypes.number,
-
// maximum number of events to show in a timeline
timelineCap: React.PropTypes.number,
@@ -1157,7 +1154,6 @@ var TimelinePanel = React.createClass({
onScroll={this.onMessageListScroll}
onFillRequest={this.onMessageListFillRequest}
onUnfillRequest={this.onMessageListUnfillRequest}
- opacity={this.props.opacity}
isTwelveHour={this.state.isTwelveHour}
alwaysShowTimestamps={this.state.alwaysShowTimestamps}
className={this.props.className}
diff --git a/src/components/structures/UserSettings.js b/src/components/structures/UserSettings.js
index b69bea9282..68ea932f93 100644
--- a/src/components/structures/UserSettings.js
+++ b/src/components/structures/UserSettings.js
@@ -118,6 +118,10 @@ const SETTINGS_LABELS = [
id: 'TextualBody.disableBigEmoji',
label: _td('Disable big emoji in chat'),
},
+ {
+ id: 'VideoView.flipVideoHorizontally',
+ label: _td('Mirror local video feed'),
+ },
/*
{
id: 'useFixedWidthFont',
@@ -271,9 +275,9 @@ module.exports = React.createClass({
MatrixClientPeg.get().on("RoomMember.membership", this._onInviteStateChange);
dis.dispatch({
- action: 'ui_opacity',
- sideOpacity: 0.3,
- middleOpacity: 0.3,
+ action: 'panel_disable',
+ sideDisabled: true,
+ middleDisabled: true,
});
this._refreshFromServer();
@@ -311,9 +315,9 @@ module.exports = React.createClass({
componentWillUnmount: function() {
this._unmounted = true;
dis.dispatch({
- action: 'ui_opacity',
- sideOpacity: 1.0,
- middleOpacity: 1.0,
+ action: 'panel_disable',
+ sideDisabled: false,
+ middleDisabled: false,
});
dis.unregister(this.dispatcherRef);
const cli = MatrixClientPeg.get();
@@ -1328,8 +1332,11 @@ module.exports = React.createClass({
-

+
ev.getContent().aliases).reduce((a, b) => {
+ return a.concat(b);
+ }, []);
const topic = topicEvent ? topicEvent.getContent().topic : '';
const nameMatch = (name || '').toLowerCase().includes(lowerCaseQuery);
- const aliasMatch = (canonicalAlias || '').toLowerCase().includes(lowerCaseQuery);
+ const aliasMatch = aliases.some((alias) =>
+ (alias || '').toLowerCase().includes(lowerCaseQuery),
+ );
const topicMatch = (topic || '').toLowerCase().includes(lowerCaseQuery);
if (!(nameMatch || topicMatch || aliasMatch)) {
return;
}
const avatarEvent = room.currentState.getStateEvents('m.room.avatar', '');
const avatarUrl = avatarEvent ? avatarEvent.getContent().url : undefined;
+
results.push({
room_id: room.roomId,
avatar_url: avatarUrl,
- name: name || canonicalAlias,
+ name: name || canonicalAlias || aliases[0] || _t('Unnamed Room'),
});
});
this._processResults(results, query);
diff --git a/src/components/views/elements/AppTile.js b/src/components/views/elements/AppTile.js
index 3e343e098c..0070af1fb2 100644
--- a/src/components/views/elements/AppTile.js
+++ b/src/components/views/elements/AppTile.js
@@ -172,18 +172,29 @@ export default React.createClass({
*/
_onDeleteClick: function() {
if (this._canUserModify()) {
- console.log("Delete widget %s", this.props.id);
- this.setState({deleting: true});
- MatrixClientPeg.get().sendStateEvent(
- this.props.room.roomId,
- 'im.vector.modular.widgets',
- {}, // empty content
- this.props.id,
- ).then(() => {
- console.log('Deleted widget');
- }, (e) => {
- console.error('Failed to delete widget', e);
- this.setState({deleting: false});
+ // Show delete confirmation dialog
+ const QuestionDialog = sdk.getComponent("dialogs.QuestionDialog");
+ Modal.createTrackedDialog('Delete Widget', '', QuestionDialog, {
+ title: _t("Delete Widget"),
+ description: _t(
+ "Deleting a widget removes it for all users in this room." +
+ " Are you sure you want to delete this widget?"),
+ button: _t("Delete widget"),
+ onFinished: (confirmed) => {
+ if (!confirmed) {
+ return;
+ }
+ this.setState({deleting: true});
+ MatrixClientPeg.get().sendStateEvent(
+ this.props.room.roomId,
+ 'im.vector.modular.widgets',
+ {}, // empty content
+ this.props.id,
+ ).catch((e) => {
+ console.error('Failed to delete widget', e);
+ this.setState({deleting: false});
+ });
+ },
});
} else {
console.log("Revoke widget permissions - %s", this.props.id);
@@ -305,7 +316,7 @@ export default React.createClass({
let deleteIcon = 'img/cancel.svg';
let deleteClasses = 'mx_filterFlipColor mx_AppTileMenuBarWidget';
if(this._canUserModify()) {
- deleteIcon = 'img/cancel-red.svg';
+ deleteIcon = 'img/icon-delete-pink.svg';
deleteClasses += ' mx_AppTileMenuBarWidgetDelete';
}
diff --git a/src/components/views/elements/EditableItemList.js b/src/components/views/elements/EditableItemList.js
index 35e207daef..05ae625515 100644
--- a/src/components/views/elements/EditableItemList.js
+++ b/src/components/views/elements/EditableItemList.js
@@ -84,7 +84,9 @@ module.exports = React.createClass({
onNewItemChanged: PropTypes.func,
onItemAdded: PropTypes.func,
onItemEdited: PropTypes.func,
- onItemRemoved: PropTypes. func,
+ onItemRemoved: PropTypes.func,
+
+ canEdit: PropTypes.bool,
},
getDefaultProps: function() {
@@ -136,14 +138,16 @@ module.exports = React.createClass({
{ label }
{ editableItems }
-
+ { this.props.canEdit ?
+
:
+ }
);
},
});
diff --git a/src/components/views/groups/GroupMemberInfo.js b/src/components/views/groups/GroupMemberInfo.js
index 6f99ee898d..ff100c418a 100644
--- a/src/components/views/groups/GroupMemberInfo.js
+++ b/src/components/views/groups/GroupMemberInfo.js
@@ -173,7 +173,7 @@ module.exports = withMatrixClient(React.createClass({
-
+
{ avatar }
diff --git a/src/components/views/groups/GroupRoomTile.js b/src/components/views/groups/GroupRoomTile.js
index 23e53996e3..94dc8e593f 100644
--- a/src/components/views/groups/GroupRoomTile.js
+++ b/src/components/views/groups/GroupRoomTile.js
@@ -120,8 +120,11 @@ const GroupRoomTile = React.createClass({
{ this.state.name }
-
-
+
+
);
diff --git a/src/components/views/messages/UnknownBody.js b/src/components/views/messages/UnknownBody.js
index 083d7ac12e..9a172baf7c 100644
--- a/src/components/views/messages/UnknownBody.js
+++ b/src/components/views/messages/UnknownBody.js
@@ -25,7 +25,10 @@ module.exports = React.createClass({
render: function() {
let tooltip = _t("Removed or unknown message type");
if (this.props.mxEvent.isRedacted()) {
- tooltip = _t("Message removed by %(userId)s", {userId: this.props.mxEvent.getSender()});
+ const redactedBecauseUserId = this.props.mxEvent.getUnsigned().redacted_because.sender;
+ tooltip = redactedBecauseUserId ?
+ _t("Message removed by %(userId)s", { userId: redactedBecauseUserId }) :
+ _t("Message removed");
}
const text = this.props.mxEvent.getContent().body;
diff --git a/src/components/views/room_settings/AliasSettings.js b/src/components/views/room_settings/AliasSettings.js
index c64e876dbe..cb897c9daf 100644
--- a/src/components/views/room_settings/AliasSettings.js
+++ b/src/components/views/room_settings/AliasSettings.js
@@ -262,6 +262,7 @@ module.exports = React.createClass({
items={this.state.domainToAliases[localDomain] || []}
newItem={this.state.newAlias}
onNewItemChanged={this.onNewAliasChanged}
+ canEdit={this.props.canSetAliases}
onItemAdded={this.onLocalAliasAdded}
onItemEdited={this.onLocalAliasChanged}
onItemRemoved={this.onLocalAliasDeleted}
diff --git a/src/components/views/room_settings/RelatedGroupSettings.js b/src/components/views/room_settings/RelatedGroupSettings.js
index 7227a951d7..0a2dc3341c 100644
--- a/src/components/views/room_settings/RelatedGroupSettings.js
+++ b/src/components/views/room_settings/RelatedGroupSettings.js
@@ -27,7 +27,7 @@ module.exports = React.createClass({
propTypes: {
roomId: React.PropTypes.string.isRequired,
- canSetRelatedRooms: React.PropTypes.bool.isRequired,
+ canSetRelatedGroups: React.PropTypes.bool.isRequired,
relatedGroupsEvent: React.PropTypes.instanceOf(MatrixEvent),
},
@@ -37,7 +37,7 @@ module.exports = React.createClass({
getDefaultProps: function() {
return {
- canSetRelatedRooms: false,
+ canSetRelatedGroups: false,
};
},
@@ -110,6 +110,7 @@ module.exports = React.createClass({
items={this.state.newGroupsList}
className={"mx_RelatedGroupSettings"}
newItem={this.state.newGroupId}
+ canEdit={this.props.canSetRelatedGroups}
onNewItemChanged={this.onNewGroupChanged}
onItemAdded={this.onGroupAdded}
onItemEdited={this.onGroupEdited}
diff --git a/src/components/views/rooms/ForwardMessage.js b/src/components/views/rooms/ForwardMessage.js
index 67e55101e8..b0fba12865 100644
--- a/src/components/views/rooms/ForwardMessage.js
+++ b/src/components/views/rooms/ForwardMessage.js
@@ -30,10 +30,9 @@ module.exports = React.createClass({
componentWillMount: function() {
dis.dispatch({
- action: 'ui_opacity',
- leftOpacity: 1.0,
- rightOpacity: 0.3,
- middleOpacity: 0.5,
+ action: 'panel_disable',
+ rightDisabled: true,
+ middleDisabled: true,
});
},
@@ -43,9 +42,9 @@ module.exports = React.createClass({
componentWillUnmount: function() {
dis.dispatch({
- action: 'ui_opacity',
- sideOpacity: 1.0,
- middleOpacity: 1.0,
+ action: 'panel_disable',
+ sideDisabled: false,
+ middleDisabled: false,
});
document.removeEventListener('keydown', this._onKeyDown);
},
diff --git a/src/components/views/rooms/LinkPreviewWidget.js b/src/components/views/rooms/LinkPreviewWidget.js
index 10c809e0f1..d69a8fbcf1 100644
--- a/src/components/views/rooms/LinkPreviewWidget.js
+++ b/src/components/views/rooms/LinkPreviewWidget.js
@@ -133,8 +133,9 @@ module.exports = React.createClass({
{ p["og:description"] }
-

+
);
},
diff --git a/src/components/views/rooms/MemberInfo.js b/src/components/views/rooms/MemberInfo.js
index e281a93558..3abff39652 100644
--- a/src/components/views/rooms/MemberInfo.js
+++ b/src/components/views/rooms/MemberInfo.js
@@ -39,6 +39,7 @@ import { findReadReceiptFromUserId } from '../../../utils/Receipt';
import withMatrixClient from '../../../wrappers/withMatrixClient';
import AccessibleButton from '../elements/AccessibleButton';
import GeminiScrollbar from 'react-gemini-scrollbar';
+import RoomViewStore from '../../../stores/RoomViewStore';
module.exports = withMatrixClient(React.createClass({
@@ -81,6 +82,7 @@ module.exports = withMatrixClient(React.createClass({
cli.on("Room.receipt", this.onRoomReceipt);
cli.on("RoomState.events", this.onRoomStateEvents);
cli.on("RoomMember.name", this.onRoomMemberName);
+ cli.on("RoomMember.membership", this.onRoomMemberMembership);
cli.on("accountData", this.onAccountData);
this._checkIgnoreState();
@@ -91,7 +93,7 @@ module.exports = withMatrixClient(React.createClass({
},
componentWillReceiveProps: function(newProps) {
- if (this.props.member.userId != newProps.member.userId) {
+ if (this.props.member.userId !== newProps.member.userId) {
this._updateStateForNewMember(newProps.member);
}
},
@@ -107,6 +109,7 @@ module.exports = withMatrixClient(React.createClass({
client.removeListener("Room.receipt", this.onRoomReceipt);
client.removeListener("RoomState.events", this.onRoomStateEvents);
client.removeListener("RoomMember.name", this.onRoomMemberName);
+ client.removeListener("RoomMember.membership", this.onRoomMemberMembership);
client.removeListener("accountData", this.onAccountData);
}
if (this._cancelDeviceList) {
@@ -122,12 +125,12 @@ module.exports = withMatrixClient(React.createClass({
_disambiguateDevices: function(devices) {
const names = Object.create(null);
for (let i = 0; i < devices.length; i++) {
- var name = devices[i].getDisplayName();
+ const name = devices[i].getDisplayName();
const indexList = names[name] || [];
indexList.push(i);
names[name] = indexList;
}
- for (name in names) {
+ for (const name in names) {
if (names[name].length > 1) {
names[name].forEach((j)=>{
devices[j].ambiguous = true;
@@ -141,7 +144,7 @@ module.exports = withMatrixClient(React.createClass({
return;
}
- if (userId == this.props.member.userId) {
+ if (userId === this.props.member.userId) {
// no need to re-download the whole thing; just update our copy of
// the list.
@@ -186,8 +189,12 @@ module.exports = withMatrixClient(React.createClass({
this.forceUpdate();
},
+ onRoomMemberMembership: function(ev, member) {
+ if (this.props.member.userId === member.userId) this.forceUpdate();
+ },
+
onAccountData: function(ev) {
- if (ev.getType() == 'm.direct') {
+ if (ev.getType() === 'm.direct') {
this.forceUpdate();
}
},
@@ -242,7 +249,9 @@ module.exports = withMatrixClient(React.createClass({
ignoredUsers.push(this.props.member.userId);
}
- this.props.matrixClient.setIgnoredUsers(ignoredUsers).then(() => this.setState({isIgnoring: !this.state.isIgnoring}));
+ this.props.matrixClient.setIgnoredUsers(ignoredUsers).then(() => {
+ return this.setState({isIgnoring: !this.state.isIgnoring});
+ });
},
onKick: function() {
@@ -252,7 +261,7 @@ module.exports = withMatrixClient(React.createClass({
Modal.createTrackedDialog('Confirm User Action Dialog', 'onKick', ConfirmUserActionDialog, {
member: this.props.member,
action: kickLabel,
- askReason: membership == "join",
+ askReason: membership === "join",
danger: true,
onFinished: (proceed, reason) => {
if (!proceed) return;
@@ -284,15 +293,15 @@ module.exports = withMatrixClient(React.createClass({
const ConfirmUserActionDialog = sdk.getComponent("dialogs.ConfirmUserActionDialog");
Modal.createTrackedDialog('Confirm User Action Dialog', 'onBanOrUnban', ConfirmUserActionDialog, {
member: this.props.member,
- action: this.props.member.membership == 'ban' ? _t("Unban") : _t("Ban"),
- askReason: this.props.member.membership != 'ban',
- danger: this.props.member.membership != 'ban',
+ action: this.props.member.membership === 'ban' ? _t("Unban") : _t("Ban"),
+ askReason: this.props.member.membership !== 'ban',
+ danger: this.props.member.membership !== 'ban',
onFinished: (proceed, reason) => {
if (!proceed) return;
this.setState({ updating: this.state.updating + 1 });
let promise;
- if (this.props.member.membership == 'ban') {
+ if (this.props.member.membership === 'ban') {
promise = this.props.matrixClient.unban(
this.props.member.roomId, this.props.member.userId,
);
@@ -327,15 +336,11 @@ module.exports = withMatrixClient(React.createClass({
const roomId = this.props.member.roomId;
const target = this.props.member.userId;
const room = this.props.matrixClient.getRoom(roomId);
- if (!room) {
- return;
- }
- const powerLevelEvent = room.currentState.getStateEvents(
- "m.room.power_levels", "",
- );
- if (!powerLevelEvent) {
- return;
- }
+ if (!room) return;
+
+ const powerLevelEvent = room.currentState.getStateEvents("m.room.power_levels", "");
+ if (!powerLevelEvent) return;
+
const isMuted = this.state.muted;
const powerLevels = powerLevelEvent.getContent();
const levelToSend = (
@@ -350,7 +355,7 @@ module.exports = withMatrixClient(React.createClass({
}
level = parseInt(level);
- if (level !== NaN) {
+ if (!isNaN(level)) {
this.setState({ updating: this.state.updating + 1 });
this.props.matrixClient.setPowerLevel(roomId, target, level, powerLevelEvent).then(
function() {
@@ -375,19 +380,14 @@ module.exports = withMatrixClient(React.createClass({
const roomId = this.props.member.roomId;
const target = this.props.member.userId;
const room = this.props.matrixClient.getRoom(roomId);
- if (!room) {
- return;
- }
- const powerLevelEvent = room.currentState.getStateEvents(
- "m.room.power_levels", "",
- );
- if (!powerLevelEvent) {
- return;
- }
+ if (!room) return;
+
+ const powerLevelEvent = room.currentState.getStateEvents("m.room.power_levels", "");
+ if (!powerLevelEvent) return;
+
const me = room.getMember(this.props.matrixClient.credentials.userId);
- if (!me) {
- return;
- }
+ if (!me) return;
+
const defaultLevel = powerLevelEvent.getContent().users_default;
let modLevel = me.powerLevel - 1;
if (modLevel > 50 && defaultLevel < 50) modLevel = 50; // try to stick with the vector level defaults
@@ -400,7 +400,7 @@ module.exports = withMatrixClient(React.createClass({
// get out of sync if we force setState here!
console.log("Mod toggle success");
}, function(err) {
- if (err.errcode == 'M_GUEST_ACCESS_FORBIDDEN') {
+ if (err.errcode === 'M_GUEST_ACCESS_FORBIDDEN') {
dis.dispatch({action: 'view_set_mxid'});
} else {
console.error("Toggle moderator error:" + err);
@@ -436,7 +436,6 @@ module.exports = withMatrixClient(React.createClass({
},
onPowerChange: function(powerLevel) {
- const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
const roomId = this.props.member.roomId;
const target = this.props.member.userId;
const room = this.props.matrixClient.getRoom(roomId);
@@ -497,19 +496,14 @@ module.exports = withMatrixClient(React.createClass({
modifyLevel: false,
};
const room = this.props.matrixClient.getRoom(member.roomId);
- if (!room) {
- return defaultPerms;
- }
- const powerLevels = room.currentState.getStateEvents(
- "m.room.power_levels", "",
- );
- if (!powerLevels) {
- return defaultPerms;
- }
+ if (!room) return defaultPerms;
+
+ const powerLevels = room.currentState.getStateEvents("m.room.power_levels", "");
+ if (!powerLevels) return defaultPerms;
+
const me = room.getMember(this.props.matrixClient.credentials.userId);
- if (!me) {
- return defaultPerms;
- }
+ if (!me) return defaultPerms;
+
const them = member;
return {
can: this._calculateCanPermissions(
@@ -545,14 +539,13 @@ module.exports = withMatrixClient(React.createClass({
can.ban = me.powerLevel >= powerLevels.ban;
can.mute = me.powerLevel >= editPowerLevel;
can.toggleMod = me.powerLevel > them.powerLevel && them.powerLevel >= levelToSend;
- can.modifyLevel = me.powerLevel > them.powerLevel;
+ can.modifyLevel = me.powerLevel > them.powerLevel && me.powerLevel >= editPowerLevel;
return can;
},
_isMuted: function(member, powerLevelContent) {
- if (!powerLevelContent || !member) {
- return false;
- }
+ if (!powerLevelContent || !member) return false;
+
const levelToSend = (
(powerLevelContent.events ? powerLevelContent.events["m.room.message"] : null) ||
powerLevelContent.events_default
@@ -568,14 +561,15 @@ module.exports = withMatrixClient(React.createClass({
},
onMemberAvatarClick: function() {
- const avatarUrl = this.props.member.user ? this.props.member.user.avatarUrl : this.props.member.events.member.getContent().avatar_url;
+ const member = this.props.member;
+ const avatarUrl = member.user ? member.user.avatarUrl : member.events.member.getContent().avatar_url;
if(!avatarUrl) return;
const httpUrl = this.props.matrixClient.mxcUrlToHttp(avatarUrl);
const ImageView = sdk.getComponent("elements.ImageView");
const params = {
src: httpUrl,
- name: this.props.member.name,
+ name: member.name,
};
Modal.createDialog(ImageView, params, "mx_Dialog_lightbox");
@@ -589,9 +583,7 @@ module.exports = withMatrixClient(React.createClass({
},
_renderDevices: function() {
- if (!this._enableDevices) {
- return null;
- }
+ if (!this._enableDevices) return null;
const devices = this.state.devices;
const MemberDeviceInfo = sdk.getComponent('rooms.MemberDeviceInfo');
@@ -630,6 +622,7 @@ module.exports = withMatrixClient(React.createClass({
let ignoreButton = null;
let insertPillButton = null;
+ let inviteUserButton = null;
let readReceiptButton = null;
// Only allow the user to ignore the user if its not ourselves
@@ -673,9 +666,30 @@ module.exports = withMatrixClient(React.createClass({
);
}
+
+ if (!member || !member.membership || member.membership === 'leave') {
+ const roomId = member && member.roomId ? member.roomId : RoomViewStore.getRoomId();
+ const onInviteUserButton = async () => {
+ try {
+ await cli.invite(roomId, member.userId);
+ } catch (err) {
+ const ErrorDialog = sdk.getComponent('dialogs.ErrorDialog');
+ Modal.createTrackedDialog('Failed to invite', '', ErrorDialog, {
+ title: _t('Failed to invite'),
+ description: ((err && err.message) ? err.message : "Operation failed"),
+ });
+ }
+ };
+
+ inviteUserButton = (
+
+ { _t('Invite') }
+
+ );
+ }
}
- if (!ignoreButton && !readReceiptButton && !insertPillButton) return null;
+ if (!ignoreButton && !readReceiptButton && !insertPillButton && !inviteUserButton) return null;
return (
@@ -684,13 +698,20 @@ module.exports = withMatrixClient(React.createClass({
{ readReceiptButton }
{ insertPillButton }
{ ignoreButton }
+ { inviteUserButton }
);
},
render: function() {
- let startChat, kickButton, banButton, muteButton, giveModButton, spinner;
+ let startChat;
+ let kickButton;
+ let banButton;
+ let muteButton;
+ let giveModButton;
+ let spinner;
+
if (this.props.member.userId !== this.props.matrixClient.credentials.userId) {
const dmRoomMap = new DMRoomMap(this.props.matrixClient);
const dmRooms = dmRoomMap.getDMRoomsForUserId(this.props.member.userId);
@@ -704,7 +725,7 @@ module.exports = withMatrixClient(React.createClass({
const me = room.getMember(this.props.matrixClient.credentials.userId);
const highlight = (
room.getUnreadNotificationCount('highlight') > 0 ||
- me.membership == "invite"
+ me.membership === "invite"
);
tiles.push(