Merge branch 'develop' of https://github.com/matrix-org/matrix-react-sdk into rxl881/snapshot

This commit is contained in:
Richard Lewis
2018-04-03 11:34:14 +01:00
85 changed files with 3694 additions and 513 deletions

View File

@@ -31,7 +31,6 @@ 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";
const LONG_DESC_PLACEHOLDER = _td(
@@ -671,6 +670,20 @@ export default React.createClass({
});
},
_onJoinClick: function() {
this.setState({membershipBusy: true});
this._matrixClient.joinGroup(this.props.groupId).then(() => {
// don't reset membershipBusy here: wait for the membership change to come down the sync
}).catch((e) => {
this.setState({membershipBusy: false});
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
Modal.createTrackedDialog('Error joining room', '', ErrorDialog, {
title: _t("Error"),
description: _t("Unable to join community"),
});
});
},
_onLeaveClick: function() {
const QuestionDialog = sdk.getComponent("dialogs.QuestionDialog");
Modal.createTrackedDialog('Leave Group', '', QuestionDialog, {
@@ -687,9 +700,9 @@ export default React.createClass({
}).catch((e) => {
this.setState({membershipBusy: false});
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
Modal.createTrackedDialog('Error leaving room', '', ErrorDialog, {
Modal.createTrackedDialog('Error leaving community', '', ErrorDialog, {
title: _t("Error"),
description: _t("Unable to leave room"),
description: _t("Unable to leave community"),
});
});
},
@@ -707,8 +720,21 @@ 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 <bold1>name</bold1> and <bold2>avatar</bold2> ' +
'might not be seen by other users for up to 30 minutes.',
{},
{
'bold1': (sub) => <b> { sub } </b>,
'bold2': (sub) => <b> { sub } </b>,
},
) }
</div> : <div />;
return <div className={groupSettingsSectionClasses}>
{ header }
{ changeDelayWarning }
{ this._getLongDescriptionNode() }
{ this._getRoomsNode() }
</div>;
@@ -847,9 +873,8 @@ export default React.createClass({
const BaseAvatar = sdk.getComponent("avatars.BaseAvatar");
const group = this._matrixClient.getGroup(this.props.groupId);
if (!group) return null;
if (group.myMembership === 'invite') {
if (group && group.myMembership === 'invite') {
if (this.state.membershipBusy || this.state.inviterProfileBusy) {
return <div className="mx_GroupView_membershipSection">
<Spinner />
@@ -890,33 +915,72 @@ export default React.createClass({
</div>
</div>
</div>;
} else if (group.myMembership === 'join' && this.state.editing) {
const leaveButtonTooltip = this.state.isUserPrivileged ?
}
let membershipContainerExtraClasses;
let membershipButtonExtraClasses;
let membershipButtonTooltip;
let membershipButtonText;
let membershipButtonOnClick;
// User is not in the group
if ((!group || group.myMembership === 'leave') &&
this.state.summary &&
this.state.summary.profile &&
Boolean(this.state.summary.profile.is_joinable)
) {
membershipButtonText = _t("Join this community");
membershipButtonOnClick = this._onJoinClick;
membershipButtonExtraClasses = 'mx_GroupView_joinButton';
membershipContainerExtraClasses = 'mx_GroupView_membershipSection_leave';
} else if (
group &&
group.myMembership === 'join' &&
this.state.editing
) {
membershipButtonText = _t("Leave this community");
membershipButtonOnClick = this._onLeaveClick;
membershipButtonTooltip = this.state.isUserPrivileged ?
_t("You are an administrator of this community") :
_t("You are a member of this community");
const leaveButtonClasses = classnames({
"mx_RoomHeader_textButton": true,
"mx_GroupView_textButton": true,
"mx_GroupView_leaveButton": true,
"mx_RoomHeader_textButton_danger": this.state.isUserPrivileged,
});
return <div className="mx_GroupView_membershipSection mx_GroupView_membershipSection_joined">
<div className="mx_GroupView_membershipSubSection">
{ /* Empty div for flex alignment */ }
<div />
<div className="mx_GroupView_membership_buttonContainer">
<AccessibleButton
className={leaveButtonClasses}
onClick={this._onLeaveClick}
title={leaveButtonTooltip}
>
{ _t("Leave") }
</AccessibleButton>
</div>
</div>
</div>;
membershipButtonExtraClasses = {
'mx_GroupView_leaveButton': true,
'mx_RoomHeader_textButton_danger': this.state.isUserPrivileged,
};
membershipContainerExtraClasses = 'mx_GroupView_membershipSection_joined';
} else {
return null;
}
return null;
const membershipButtonClasses = classnames([
'mx_RoomHeader_textButton',
'mx_GroupView_textButton',
],
membershipButtonExtraClasses,
);
const membershipContainerClasses = classnames(
'mx_GroupView_membershipSection',
membershipContainerExtraClasses,
);
return <div className={membershipContainerClasses}>
<div className="mx_GroupView_membershipSubSection">
{ /* Empty div for flex alignment */ }
<div />
<div className="mx_GroupView_membership_buttonContainer">
<AccessibleButton
className={membershipButtonClasses}
onClick={membershipButtonOnClick}
title={membershipButtonTooltip}
>
{ membershipButtonText }
</AccessibleButton>
</div>
</div>
</div>;
},
_getLongDescriptionNode: function() {
@@ -962,6 +1026,7 @@ export default React.createClass({
const GroupAvatar = sdk.getComponent("avatars.GroupAvatar");
const Spinner = sdk.getComponent("elements.Spinner");
const TintableSvg = sdk.getComponent("elements.TintableSvg");
const GeminiScrollbarWrapper = sdk.getComponent("elements.GeminiScrollbarWrapper");
if (this.state.summaryLoading && this.state.error === null || this.state.saving) {
return <Spinner />;
@@ -1112,9 +1177,9 @@ export default React.createClass({
{ rightButtons }
</div>
</div>
<GeminiScrollbar className="mx_GroupView_body">
<GeminiScrollbarWrapper className="mx_GroupView_body">
{ bodyNodes }
</GeminiScrollbar>
</GeminiScrollbarWrapper>
</div>
);
} else if (this.state.error) {

View File

@@ -374,7 +374,7 @@ const LoggedInView = React.createClass({
}
return (
<div className='mx_MatrixChat_wrapper'>
<div className='mx_MatrixChat_wrapper' aria-hidden={this.props.hideToSRUsers}>
{ topBar }
<DragDropContext onDragEnd={this._onDragEnd}>
<div className={bodyClasses}>

View File

@@ -171,6 +171,10 @@ export default React.createClass({
register_hs_url: null,
register_is_url: null,
register_id_sid: null,
// When showing Modal dialogs we need to set aria-hidden on the root app element
// and disable it when there are no dialogs
hideToSRUsers: false,
};
return s;
},
@@ -287,6 +291,8 @@ export default React.createClass({
this.handleResize();
window.addEventListener('resize', this.handleResize);
this._pageChanging = false;
// check we have the right tint applied for this theme.
// N.B. we don't call the whole of setTheme() here as we may be
// racing with the theme CSS download finishing from index.js
@@ -364,13 +370,58 @@ export default React.createClass({
window.removeEventListener('resize', this.handleResize);
},
componentDidUpdate: function() {
componentWillUpdate: function(props, state) {
if (this.shouldTrackPageChange(this.state, state)) {
this.startPageChangeTimer();
}
},
componentDidUpdate: function(prevProps, prevState) {
if (this.shouldTrackPageChange(prevState, this.state)) {
const durationMs = this.stopPageChangeTimer();
Analytics.trackPageChange(durationMs);
}
if (this.focusComposer) {
dis.dispatch({action: 'focus_composer'});
this.focusComposer = false;
}
},
startPageChangeTimer() {
// This shouldn't happen because componentWillUpdate and componentDidUpdate
// are used.
if (this._pageChanging) {
console.warn('MatrixChat.startPageChangeTimer: timer already started');
return;
}
this._pageChanging = true;
performance.mark('riot_MatrixChat_page_change_start');
},
stopPageChangeTimer() {
if (!this._pageChanging) {
console.warn('MatrixChat.stopPageChangeTimer: timer not started');
return;
}
this._pageChanging = false;
performance.mark('riot_MatrixChat_page_change_stop');
performance.measure(
'riot_MatrixChat_page_change_delta',
'riot_MatrixChat_page_change_start',
'riot_MatrixChat_page_change_stop',
);
performance.clearMarks('riot_MatrixChat_page_change_start');
performance.clearMarks('riot_MatrixChat_page_change_stop');
const measurement = performance.getEntriesByName('riot_MatrixChat_page_change_delta').pop();
return measurement.duration;
},
shouldTrackPageChange(prevState, state) {
return prevState.currentRoomId !== state.currentRoomId ||
prevState.view !== state.view ||
prevState.page_type !== state.page_type;
},
setStateForNewView: function(state) {
if (state.view === undefined) {
throw new Error("setStateForNewView with no view!");
@@ -608,6 +659,16 @@ export default React.createClass({
case 'send_event':
this.onSendEvent(payload.room_id, payload.event);
break;
case 'aria_hide_main_app':
this.setState({
hideToSRUsers: true,
});
break;
case 'aria_unhide_main_app':
this.setState({
hideToSRUsers: false,
});
break;
}
},
@@ -1171,18 +1232,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'),
@@ -1339,7 +1388,6 @@ export default React.createClass({
if (this.props.onNewScreen) {
this.props.onNewScreen(screen);
}
Analytics.trackPageChange();
},
onAliasClick: function(event, alias) {

View File

@@ -16,7 +16,6 @@ limitations under the License.
import React from 'react';
import PropTypes from 'prop-types';
import GeminiScrollbar from 'react-gemini-scrollbar';
import sdk from '../../index';
import { _t } from '../../languageHandler';
import dis from '../../dispatcher';
@@ -63,6 +62,8 @@ export default withMatrixClient(React.createClass({
const SimpleRoomHeader = sdk.getComponent('rooms.SimpleRoomHeader');
const TintableSvg = sdk.getComponent("elements.TintableSvg");
const GroupTile = sdk.getComponent("groups.GroupTile");
const GeminiScrollbarWrapper = sdk.getComponent("elements.GeminiScrollbarWrapper");
let content;
let contentHeader;
@@ -73,7 +74,7 @@ export default withMatrixClient(React.createClass({
});
contentHeader = groupNodes.length > 0 ? <h3>{ _t('Your Communities') }</h3> : <div />;
content = groupNodes.length > 0 ?
<GeminiScrollbar>
<GeminiScrollbarWrapper>
<div className="mx_MyGroups_microcopy">
<p>
{ _t(
@@ -92,7 +93,7 @@ export default withMatrixClient(React.createClass({
<div className="mx_MyGroups_joinedGroups">
{ groupNodes }
</div>
</GeminiScrollbar> :
</GeminiScrollbarWrapper> :
<div className="mx_MyGroups_placeholder">
{ _t(
"You're not currently a member of any communities.",

View File

@@ -17,9 +17,9 @@ limitations under the License.
const React = require("react");
const ReactDOM = require("react-dom");
import PropTypes from 'prop-types';
const GeminiScrollbar = require('react-gemini-scrollbar');
import Promise from 'bluebird';
import { KeyCode } from '../../Keyboard';
import sdk from '../../index.js';
const DEBUG_SCROLL = false;
// var DEBUG_SCROLL = true;
@@ -224,7 +224,7 @@ module.exports = React.createClass({
onResize: function() {
this.props.onResize();
this.checkScroll();
this.refs.geminiPanel.forceUpdate();
if (this._gemScroll) this._gemScroll.forceUpdate();
},
// after an update to the contents of the panel, check that the scroll is
@@ -665,14 +665,25 @@ module.exports = React.createClass({
throw new Error("ScrollPanel._getScrollNode called when unmounted");
}
return this.refs.geminiPanel.scrollbar.getViewElement();
if (!this._gemScroll) {
// Likewise, we should have the ref by this point, but if not
// turn the NPE into something meaningful.
throw new Error("ScrollPanel._getScrollNode called before gemini ref collected");
}
return this._gemScroll.scrollbar.getViewElement();
},
_collectGeminiScroll: function(gemScroll) {
this._gemScroll = gemScroll;
},
render: function() {
const GeminiScrollbarWrapper = sdk.getComponent("elements.GeminiScrollbarWrapper");
// TODO: the classnames on the div and ol could do with being updated to
// reflect the fact that we don't necessarily contain a list of messages.
// it's not obvious why we have a separate div and ol anyway.
return (<GeminiScrollbar autoshow={true} ref="geminiPanel"
return (<GeminiScrollbarWrapper autoshow={true} wrappedRef={this._collectGeminiScroll}
onScroll={this.onScroll} onResize={this.onResize}
className={this.props.className} style={this.props.style}>
<div className="mx_RoomView_messageListWrapper">
@@ -680,7 +691,7 @@ module.exports = React.createClass({
{ this.props.children }
</ol>
</div>
</GeminiScrollbar>
</GeminiScrollbarWrapper>
);
},
});

View File

@@ -17,7 +17,6 @@ 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';
@@ -101,6 +100,9 @@ const TagPanel = React.createClass({
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 GeminiScrollbarWrapper = sdk.getComponent("elements.GeminiScrollbarWrapper");
const tags = this.state.orderedTags.map((tag, index) => {
return <DNDTagTile
@@ -112,12 +114,10 @@ const TagPanel = React.createClass({
});
const clearButton = this.state.selectedTags.length > 0 ?
<img
src="img/icons-close.svg"
<TintableSvg src="img/icons-close.svg" width="24" height="24"
alt={_t("Clear filter")}
title={_t("Clear filter")}
width="24"
height="24" /> :
/> :
<div />;
return <div className="mx_TagPanel">
@@ -125,7 +125,7 @@ const TagPanel = React.createClass({
{ clearButton }
</AccessibleButton>
<div className="mx_TagPanel_divider" />
<GeminiScrollbar
<GeminiScrollbarWrapper
className="mx_TagPanel_scroller"
autoshow={true}
// XXX: Use onMouseDown as a workaround for https://github.com/atlassian/react-beautiful-dnd/issues/273
@@ -146,7 +146,7 @@ const TagPanel = React.createClass({
</div>
) }
</Droppable>
</GeminiScrollbar>
</GeminiScrollbarWrapper>
<div className="mx_TagPanel_divider" />
<div className="mx_TagPanel_createGroupButton">
<GroupsButton tooltip={true} />

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,
});
}
}

View File

@@ -30,7 +30,6 @@ import Promise from 'bluebird';
const packageJson = require('../../../package.json');
const UserSettingsStore = require('../../UserSettingsStore');
const CallMediaHandler = require('../../CallMediaHandler');
const GeminiScrollbar = require('react-gemini-scrollbar');
const Email = require('../../email');
const AddThreepid = require('../../AddThreepid');
const SdkConfig = require('../../SdkConfig');
@@ -795,11 +794,18 @@ module.exports = React.createClass({
}
return (
<div>
<h3>{ _t("Bug Report") }</h3>
<h3>{ _t("Debug Logs Submission") }</h3>
<div className="mx_UserSettings_section">
<p>{ _t("Found a bug?") }</p>
<p>{
_t( "If you've submitted a bug via GitHub, debug logs can help " +
"us track down the problem. Debug logs contain application " +
"usage data including your username, the IDs or aliases of " +
"the rooms or groups you have visited and the usernames of " +
"other users. They do not contian messages.",
)
}</p>
<button className="mx_UserSettings_button danger"
onClick={this._onBugReportClicked}>{ _t('Report it') }
onClick={this._onBugReportClicked}>{ _t('Submit debug logs') }
</button>
</div>
</div>
@@ -1111,6 +1117,7 @@ module.exports = React.createClass({
const ChangeAvatar = sdk.getComponent('settings.ChangeAvatar');
const Notifications = sdk.getComponent("settings.Notifications");
const EditableText = sdk.getComponent('elements.EditableText');
const GeminiScrollbarWrapper = sdk.getComponent("elements.GeminiScrollbarWrapper");
const avatarUrl = (
this.state.avatarUrl ? MatrixClientPeg.get().mxcUrlToHttp(this.state.avatarUrl) : null
@@ -1206,8 +1213,9 @@ module.exports = React.createClass({
onCancelClick={this.props.onClose}
/>
<GeminiScrollbar className="mx_UserSettings_body"
autoshow={true}>
<GeminiScrollbarWrapper
className="mx_UserSettings_body"
autoshow={true}>
<h3>{ _t("Profile") }</h3>
@@ -1320,7 +1328,7 @@ module.exports = React.createClass({
{ this._renderDeactivateAccount() }
</GeminiScrollbar>
</GeminiScrollbarWrapper>
</div>
);
},