Merge branch 'develop' into luke/submit-debug-logs

This commit is contained in:
Luke Barnard
2018-03-19 14:55:28 +00:00
80 changed files with 4708 additions and 835 deletions

View File

@@ -29,6 +29,7 @@ import classnames from 'classnames';
import GroupStoreCache from '../../stores/GroupStoreCache';
import GroupStore from '../../stores/GroupStore';
import FlairStore from '../../stores/FlairStore';
import { showGroupAddRoomDialog } from '../../GroupAddressPicker';
import GeminiScrollbar from 'react-gemini-scrollbar';
import {makeGroupPermalink, makeUserPermalink} from "../../matrix-to";
@@ -429,6 +430,7 @@ export default React.createClass({
editing: false,
saving: false,
uploadingAvatar: false,
avatarChanged: false,
membershipBusy: false,
publicityBusy: false,
inviterProfile: null,
@@ -590,6 +592,10 @@ export default React.createClass({
this.setState({
uploadingAvatar: false,
profileForm: newProfileForm,
// Indicate that FlairStore needs to be poked to show this change
// in TagTile (TagPanel), Flair and GroupTile (MyGroups).
avatarChanged: true,
});
}).catch((e) => {
this.setState({uploadingAvatar: false});
@@ -615,6 +621,11 @@ export default React.createClass({
});
dis.dispatch({action: 'panel_disable'});
this._initGroupStore(this.props.groupId);
if (this.state.avatarChanged) {
// XXX: Evil - poking a store should be done from an async action
FlairStore.refreshGroupProfile(this._matrixClient, this.props.groupId);
}
}).catch((e) => {
this.setState({
saving: false,
@@ -625,6 +636,10 @@ export default React.createClass({
title: _t('Error'),
description: _t('Failed to update community'),
});
}).finally(() => {
this.setState({
avatarChanged: false,
});
}).done();
},
@@ -692,8 +707,15 @@ export default React.createClass({
});
const header = this.state.editing ? <h2> { _t('Community Settings') } </h2> : <div />;
const changeDelayWarning = this.state.editing && this.state.isUserPrivileged ?
<div className="mx_GroupView_changeDelayWarning">
{ _t( 'Changes made to your community might not be seen by other users ' +
'for up to 30 minutes.',
) }
</div> : <div />;
return <div className={groupSettingsSectionClasses}>
{ header }
{ changeDelayWarning }
{ this._getLongDescriptionNode() }
{ this._getRoomsNode() }
</div>;

View File

@@ -19,6 +19,7 @@ limitations under the License.
import * as Matrix from 'matrix-js-sdk';
import React from 'react';
import PropTypes from 'prop-types';
import { DragDropContext } from 'react-beautiful-dnd';
import { KeyCode, isOnlyCtrlOrCmdKeyEvent } from '../../Keyboard';
import Notifier from '../../Notifier';
@@ -30,6 +31,9 @@ import sessionStore from '../../stores/SessionStore';
import MatrixClientPeg from '../../MatrixClientPeg';
import SettingsStore from "../../settings/SettingsStore";
import TagOrderActions from '../../actions/TagOrderActions';
import RoomListActions from '../../actions/RoomListActions';
/**
* This is what our MatrixChat shows when we are logged in. The precise view is
* determined by the page_type property.
@@ -207,8 +211,51 @@ const LoggedInView = React.createClass({
}
},
_onDragEnd: function(result) {
// Dragged to an invalid destination, not onto a droppable
if (!result.destination) {
return;
}
const dest = result.destination.droppableId;
if (dest === 'tag-panel-droppable') {
// Could be "GroupTile +groupId:domain"
const draggableId = result.draggableId.split(' ').pop();
// Dispatch synchronously so that the TagPanel receives an
// optimistic update from TagOrderStore before the previous
// state is shown.
dis.dispatch(TagOrderActions.moveTag(
this._matrixClient,
draggableId,
result.destination.index,
), true);
} else if (dest.startsWith('room-sub-list-droppable_')) {
this._onRoomTileEndDrag(result);
}
},
_onRoomTileEndDrag: function(result) {
let newTag = result.destination.droppableId.split('_')[1];
let prevTag = result.source.droppableId.split('_')[1];
if (newTag === 'undefined') newTag = undefined;
if (prevTag === 'undefined') prevTag = undefined;
const roomId = result.draggableId.split('_')[1];
const oldIndex = result.source.index;
const newIndex = result.destination.index;
dis.dispatch(RoomListActions.tagRoom(
this._matrixClient,
this._matrixClient.getRoom(roomId),
prevTag, newTag,
oldIndex, newIndex,
), true);
},
render: function() {
const TagPanel = sdk.getComponent('structures.TagPanel');
const LeftPanel = sdk.getComponent('structures.LeftPanel');
const RightPanel = sdk.getComponent('structures.RightPanel');
const RoomView = sdk.getComponent('structures.RoomView');
@@ -329,17 +376,18 @@ const LoggedInView = React.createClass({
return (
<div className='mx_MatrixChat_wrapper'>
{ topBar }
<div className={bodyClasses}>
{ SettingsStore.isFeatureEnabled("feature_tag_panel") ? <TagPanel /> : <div /> }
<LeftPanel
collapsed={this.props.collapseLhs || false}
disabled={this.props.leftDisabled}
/>
<main className='mx_MatrixChat_middlePanel'>
{ page_element }
</main>
{ right_panel }
</div>
<DragDropContext onDragEnd={this._onDragEnd}>
<div className={bodyClasses}>
<LeftPanel
collapsed={this.props.collapseLhs || false}
disabled={this.props.leftDisabled}
/>
<main className='mx_MatrixChat_middlePanel'>
{ page_element }
</main>
{ right_panel }
</div>
</DragDropContext>
</div>
);
},

View File

@@ -1171,18 +1171,6 @@ export default React.createClass({
cli.on("crypto.warning", (type) => {
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
switch (type) {
case 'CRYPTO_WARNING_ACCOUNT_MIGRATED':
Modal.createTrackedDialog('Crypto migrated', '', ErrorDialog, {
title: _t('Cryptography data migrated'),
description: _t(
"A one-off migration of cryptography data has been performed. "+
"End-to-end encryption will not work if you go back to an older "+
"version of Riot. If you need to use end-to-end cryptography on "+
"an older version, log out of Riot first. To retain message history, "+
"export and re-import your keys.",
),
});
break;
case 'CRYPTO_WARNING_OLD_VERSION_DETECTED':
Modal.createTrackedDialog('Crypto migrated', '', ErrorDialog, {
title: _t('Old cryptography data detected'),

View File

@@ -73,8 +73,25 @@ export default withMatrixClient(React.createClass({
});
contentHeader = groupNodes.length > 0 ? <h3>{ _t('Your Communities') }</h3> : <div />;
content = groupNodes.length > 0 ?
<GeminiScrollbar className="mx_MyGroups_joinedGroups">
{ groupNodes }
<GeminiScrollbar>
<div className="mx_MyGroups_microcopy">
<p>
{ _t(
"Did you know: you can use communities to filter your Riot.im experience!",
) }
</p>
<p>
{ _t(
"To set up a filter, drag a community avatar over to the filter panel on " +
"the far left hand side of the screen. You can click on an avatar in the " +
"filter panel at any time to see only the rooms and people associated " +
"with that community.",
) }
</p>
</div>
<div className="mx_MyGroups_joinedGroups">
{ groupNodes }
</div>
</GeminiScrollbar> :
<div className="mx_MyGroups_placeholder">
{ _t(

View File

@@ -627,8 +627,8 @@ module.exports = React.createClass({
const room = this.state.room;
if (!room) return;
const color_scheme = SettingsStore.getValue("roomColor", room.room_id);
console.log("Tinter.tint from updateTint");
const color_scheme = SettingsStore.getValue("roomColor", room.roomId);
Tinter.tint(color_scheme.primary_color, color_scheme.secondary_color);
},
@@ -677,23 +677,7 @@ module.exports = React.createClass({
// a member state changed in this room
// refresh the conf call notification state
this._updateConfCallNotification();
// if we are now a member of the room, where we were not before, that
// means we have finished joining a room we were previously peeking
// into.
const me = MatrixClientPeg.get().credentials.userId;
if (this.state.joining && this.state.room.hasMembershipState(me, "join")) {
// Having just joined a room, check to see if it looks like a DM room, and if so,
// mark it as one. This is to work around the fact that some clients don't support
// is_direct. We should remove this once they do.
const me = this.state.room.getMember(MatrixClientPeg.get().credentials.userId);
if (Rooms.looksLikeDirectMessageRoom(this.state.room, me)) {
// XXX: There's not a whole lot we can really do if this fails: at best
// perhaps we could try a couple more times, but since it's a temporary
// compatability workaround, let's not bother.
Rooms.setDMRoom(this.state.room.roomId, me.events.member.getSender()).done();
}
}
this._updateDMState();
}, 500),
_checkIfAlone: function(room) {
@@ -734,6 +718,44 @@ module.exports = React.createClass({
});
},
_updateDMState() {
const me = this.state.room.getMember(MatrixClientPeg.get().credentials.userId);
if (!me || me.membership !== "join") {
return;
}
// The user may have accepted an invite with is_direct set
if (me.events.member.getPrevContent().membership === "invite" &&
me.events.member.getPrevContent().is_direct
) {
// This is a DM with the sender of the invite event (which we assume
// preceded the join event)
Rooms.setDMRoom(
this.state.room.roomId,
me.events.member.getUnsigned().prev_sender,
);
return;
}
const invitedMembers = this.state.room.getMembersWithMembership("invite");
const joinedMembers = this.state.room.getMembersWithMembership("join");
// There must be one invited member and one joined member
if (invitedMembers.length !== 1 || joinedMembers.length !== 1) {
return;
}
// The user may have sent an invite with is_direct sent
const other = invitedMembers[0];
if (other &&
other.membership === "invite" &&
other.events.member.getContent().is_direct
) {
Rooms.setDMRoom(this.state.room.roomId, other.userId);
return;
}
},
onSearchResultsResize: function() {
dis.dispatch({ action: 'timeline_resize' }, true);
},
@@ -826,18 +848,6 @@ module.exports = React.createClass({
action: 'join_room',
opts: { inviteSignUrl: signUrl },
});
// if this is an invite and has the 'direct' hint set, mark it as a DM room now.
if (this.state.room) {
const me = this.state.room.getMember(MatrixClientPeg.get().credentials.userId);
if (me && me.membership == 'invite') {
if (me.events.member.getContent().is_direct) {
// The 'direct' hint is there, so declare that this is a DM room for
// whoever invited us.
return Rooms.setDMRoom(this.state.room.roomId, me.events.member.getSender());
}
}
}
return Promise.resolve();
});
},

View File

@@ -17,15 +17,16 @@ limitations under the License.
import React from 'react';
import PropTypes from 'prop-types';
import { MatrixClient } from 'matrix-js-sdk';
import GeminiScrollbar from 'react-gemini-scrollbar';
import TagOrderStore from '../../stores/TagOrderStore';
import GroupActions from '../../actions/GroupActions';
import TagOrderActions from '../../actions/TagOrderActions';
import sdk from '../../index';
import dis from '../../dispatcher';
import { _t } from '../../languageHandler';
import { DragDropContext, Droppable } from 'react-beautiful-dnd';
import { Droppable } from 'react-beautiful-dnd';
const TagPanel = React.createClass({
displayName: 'TagPanel',
@@ -44,7 +45,7 @@ const TagPanel = React.createClass({
componentWillMount: function() {
this.unmounted = false;
this.context.matrixClient.on("Group.myMembership", this._onGroupMyMembership);
this.context.matrixClient.on("sync", this.onClientSync);
this.context.matrixClient.on("sync", this._onClientSync);
this._tagOrderStoreToken = TagOrderStore.addListener(() => {
if (this.unmounted) {
@@ -62,7 +63,7 @@ const TagPanel = React.createClass({
componentWillUnmount() {
this.unmounted = true;
this.context.matrixClient.removeListener("Group.myMembership", this._onGroupMyMembership);
this.context.matrixClient.removeListener("sync", this.onClientSync);
this.context.matrixClient.removeListener("sync", this._onClientSync);
if (this._filterStoreToken) {
this._filterStoreToken.remove();
}
@@ -73,7 +74,7 @@ const TagPanel = React.createClass({
dis.dispatch(GroupActions.fetchJoinedGroups(this.context.matrixClient));
},
onClientSync(syncState, prevState) {
_onClientSync(syncState, prevState) {
// Consider the client reconnected if there is no error with syncing.
// This means the state could be RECONNECTING, SYNCING or PREPARED.
const reconnected = syncState !== "ERROR" && prevState !== syncState;
@@ -83,9 +84,7 @@ const TagPanel = React.createClass({
}
},
onClick(e) {
// Ignore clicks on children
if (e.target !== e.currentTarget) return;
onMouseDown(e) {
dis.dispatch({action: 'deselect_tags'});
},
@@ -94,26 +93,15 @@ const TagPanel = React.createClass({
dis.dispatch({action: 'view_create_group'});
},
onTagTileEndDrag(result) {
// Dragged to an invalid destination, not onto a droppable
if (!result.destination) {
return;
}
// Dispatch synchronously so that the TagPanel receives an
// optimistic update from TagOrderStore before the previous
// state is shown.
dis.dispatch(TagOrderActions.moveTag(
this.context.matrixClient,
result.draggableId,
result.destination.index,
), true);
onClearFilterClick(ev) {
dis.dispatch({action: 'deselect_tags'});
},
render() {
const GroupsButton = sdk.getComponent('elements.GroupsButton');
const DNDTagTile = sdk.getComponent('elements.DNDTagTile');
const AccessibleButton = sdk.getComponent('elements.AccessibleButton');
const TintableSvg = sdk.getComponent('elements.TintableSvg');
const DNDTagTile = sdk.getComponent('elements.DNDTagTile');
const tags = this.state.orderedTags.map((tag, index) => {
return <DNDTagTile
@@ -123,28 +111,45 @@ const TagPanel = React.createClass({
selected={this.state.selectedTags.includes(tag)}
/>;
});
const clearButton = this.state.selectedTags.length > 0 ?
<TintableSvg src="img/icons-close.svg" width="24" height="24"
alt={_t("Clear filter")}
title={_t("Clear filter")}
/> :
<div />;
return <div className="mx_TagPanel">
<DragDropContext onDragEnd={this.onTagTileEndDrag}>
<Droppable droppableId="tag-panel-droppable">
<AccessibleButton className="mx_TagPanel_clearButton" onClick={this.onClearFilterClick}>
{ clearButton }
</AccessibleButton>
<div className="mx_TagPanel_divider" />
<GeminiScrollbar
className="mx_TagPanel_scroller"
autoshow={true}
// XXX: Use onMouseDown as a workaround for https://github.com/atlassian/react-beautiful-dnd/issues/273
// instead of onClick. Otherwise we experience https://github.com/vector-im/riot-web/issues/6253
onMouseDown={this.onMouseDown}
>
<Droppable
droppableId="tag-panel-droppable"
type="draggable-TagTile"
>
{ (provided, snapshot) => (
<div
className="mx_TagPanel_tagTileContainer"
ref={provided.innerRef}
// react-beautiful-dnd has a bug that emits a click to the parent
// of draggables upon dropping
// https://github.com/atlassian/react-beautiful-dnd/issues/273
// so we use onMouseDown here as a workaround.
onMouseDown={this.onClick}
>
{ tags }
{ provided.placeholder }
</div>
<div
className="mx_TagPanel_tagTileContainer"
ref={provided.innerRef}
>
{ tags }
{ provided.placeholder }
</div>
) }
</Droppable>
</DragDropContext>
<AccessibleButton className="mx_TagPanel_createGroupButton" onClick={this.onCreateGroupClick}>
<TintableSvg src="img/icons-create-room.svg" width="25" height="25" />
</AccessibleButton>
</GeminiScrollbar>
<div className="mx_TagPanel_divider" />
<div className="mx_TagPanel_createGroupButton">
<GroupsButton tooltip={true} />
</div>
</div>;
},
});

View File

@@ -624,6 +624,7 @@ var TimelinePanel = React.createClass({
this.props.timelineSet.room.setUnreadNotificationCount('highlight', 0);
dis.dispatch({
action: 'on_room_read',
roomId: this.props.timelineSet.room.roomId,
});
}
}
@@ -1121,9 +1122,9 @@ var TimelinePanel = React.createClass({
// exist.
if (this.state.timelineLoading) {
return (
<div className={this.props.className + " mx_RoomView_messageListWrapper"}>
<Loader />
</div>
<div className="mx_RoomView_messagePanelSpinner">
<Loader />
</div>
);
}

View File

@@ -79,6 +79,7 @@ const SIMPLE_SETTINGS = [
{ id: "Pill.shouldHidePillAvatar" },
{ id: "TextualBody.disableBigEmoji" },
{ id: "VideoView.flipVideoHorizontally" },
{ id: "TagPanel.disableTagPanel" },
];
// These settings must be defined in SettingsStore

View File

@@ -431,10 +431,10 @@ module.exports = React.createClass({
// FIXME: remove status.im theme tweaks
const theme = SettingsStore.getValue("theme");
if (theme !== "status") {
header = <h2>{ _t('Sign in') }</h2>;
header = <h2>{ _t('Sign in') } { loader }</h2>;
} else {
if (!this.state.errorText) {
header = <h2>{ _t('Sign in to get started') }</h2>;
header = <h2>{ _t('Sign in to get started') } { loader }</h2>;
}
}