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

This commit is contained in:
Richard Lewis
2018-02-23 15:37:33 +00:00
219 changed files with 9309 additions and 3694 deletions
+6 -5
View File
@@ -20,6 +20,7 @@ limitations under the License.
const classNames = require('classnames');
const React = require('react');
const ReactDOM = require('react-dom');
import PropTypes from 'prop-types';
// Shamelessly ripped off Modal.js. There's probably a better way
// of doing reusable widgets like dialog boxes & menus where we go and
@@ -29,11 +30,11 @@ module.exports = {
ContextualMenuContainerId: "mx_ContextualMenu_Container",
propTypes: {
menuWidth: React.PropTypes.number,
menuHeight: React.PropTypes.number,
chevronOffset: React.PropTypes.number,
menuColour: React.PropTypes.string,
chevronFace: React.PropTypes.string, // top, bottom, left, right
menuWidth: PropTypes.number,
menuHeight: PropTypes.number,
chevronOffset: PropTypes.number,
menuColour: PropTypes.string,
chevronFace: PropTypes.string, // top, bottom, left, right
},
getOrCreateContainer: function() {
+3 -2
View File
@@ -17,6 +17,7 @@ limitations under the License.
'use strict';
import React from 'react';
import PropTypes from 'prop-types';
import { _t } from '../../languageHandler';
import sdk from '../../index';
import MatrixClientPeg from '../../MatrixClientPeg';
@@ -30,8 +31,8 @@ module.exports = React.createClass({
displayName: 'CreateRoom',
propTypes: {
onRoomCreated: React.PropTypes.func,
collapsedRhs: React.PropTypes.bool,
onRoomCreated: PropTypes.func,
collapsedRhs: PropTypes.bool,
},
phases: {
+2 -1
View File
@@ -15,6 +15,7 @@ limitations under the License.
*/
import React from 'react';
import PropTypes from 'prop-types';
import Matrix from 'matrix-js-sdk';
import sdk from '../../index';
@@ -28,7 +29,7 @@ const FilePanel = React.createClass({
displayName: 'FilePanel',
propTypes: {
roomId: React.PropTypes.string.isRequired,
roomId: PropTypes.string.isRequired,
},
getInitialState: function() {
+5 -4
View File
@@ -31,6 +31,7 @@ import GroupStoreCache from '../../stores/GroupStoreCache';
import GroupStore from '../../stores/GroupStore';
import { showGroupAddRoomDialog } from '../../GroupAddressPicker';
import GeminiScrollbar from 'react-gemini-scrollbar';
import {makeGroupPermalink, makeUserPermalink} from "../../matrix-to";
const LONG_DESC_PLACEHOLDER = _td(
`<h1>HTML for your community's page</h1>
@@ -209,7 +210,7 @@ const FeaturedRoom = React.createClass({
let permalink = null;
if (this.props.summaryInfo.profile && this.props.summaryInfo.profile.canonical_alias) {
permalink = 'https://matrix.to/#/' + this.props.summaryInfo.profile.canonical_alias;
permalink = makeGroupPermalink(this.props.summaryInfo.profile.canonical_alias);
}
let roomNameNode = null;
@@ -366,7 +367,7 @@ const FeaturedUser = React.createClass({
const BaseAvatar = sdk.getComponent("avatars.BaseAvatar");
const name = this.props.summaryInfo.displayname || this.props.summaryInfo.user_id;
const permalink = 'https://matrix.to/#/' + this.props.summaryInfo.user_id;
const permalink = makeUserPermalink(this.props.summaryInfo.user_id);
const userNameNode = <a href={permalink} onClick={this.onClick}>{ name }</a>;
const httpUrl = MatrixClientPeg.get()
.mxcUrlToHttp(this.props.summaryInfo.avatar_url, 64, 64);
@@ -390,7 +391,7 @@ const FeaturedUser = React.createClass({
});
const GroupContext = {
groupStore: React.PropTypes.instanceOf(GroupStore).isRequired,
groupStore: PropTypes.instanceOf(GroupStore).isRequired,
};
CategoryRoomList.contextTypes = GroupContext;
@@ -408,7 +409,7 @@ export default React.createClass({
},
childContextTypes: {
groupStore: React.PropTypes.instanceOf(GroupStore),
groupStore: PropTypes.instanceOf(GroupStore),
},
getChildContext: function() {
+14 -13
View File
@@ -18,6 +18,7 @@ import Matrix from 'matrix-js-sdk';
const InteractiveAuth = Matrix.InteractiveAuth;
import React from 'react';
import PropTypes from 'prop-types';
import {getEntryComponentForLoginType} from '../views/login/InteractiveAuthEntryComponents';
@@ -26,18 +27,18 @@ export default React.createClass({
propTypes: {
// matrix client to use for UI auth requests
matrixClient: React.PropTypes.object.isRequired,
matrixClient: PropTypes.object.isRequired,
// response from initial request. If not supplied, will do a request on
// mount.
authData: React.PropTypes.shape({
flows: React.PropTypes.array,
params: React.PropTypes.object,
session: React.PropTypes.string,
authData: PropTypes.shape({
flows: PropTypes.array,
params: PropTypes.object,
session: PropTypes.string,
}),
// callback
makeRequest: React.PropTypes.func.isRequired,
makeRequest: PropTypes.func.isRequired,
// callback called when the auth process has finished,
// successfully or unsuccessfully.
@@ -51,22 +52,22 @@ export default React.createClass({
// the auth session.
// * clientSecret {string} The client secret used in auth
// sessions with the ID server.
onAuthFinished: React.PropTypes.func.isRequired,
onAuthFinished: PropTypes.func.isRequired,
// Inputs provided by the user to the auth process
// and used by various stages. As passed to js-sdk
// interactive-auth
inputs: React.PropTypes.object,
inputs: PropTypes.object,
// As js-sdk interactive-auth
makeRegistrationUrl: React.PropTypes.func,
sessionId: React.PropTypes.string,
clientSecret: React.PropTypes.string,
emailSid: React.PropTypes.string,
makeRegistrationUrl: PropTypes.func,
sessionId: PropTypes.string,
clientSecret: PropTypes.string,
emailSid: PropTypes.string,
// If true, poll to see if the auth flow has been completed
// out-of-band
poll: React.PropTypes.bool,
poll: PropTypes.bool,
},
getInitialState: function() {
+70 -24
View File
@@ -18,8 +18,8 @@ limitations under the License.
import * as Matrix from 'matrix-js-sdk';
import React from 'react';
import { DragDropContext } from 'react-dnd';
import HTML5Backend from 'react-dnd-html5-backend';
import PropTypes from 'prop-types';
import { DragDropContext } from 'react-beautiful-dnd';
import { KeyCode, isOnlyCtrlOrCmdKeyEvent } from '../../Keyboard';
import Notifier from '../../Notifier';
@@ -31,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.
@@ -44,23 +47,23 @@ const LoggedInView = React.createClass({
displayName: 'LoggedInView',
propTypes: {
matrixClient: React.PropTypes.instanceOf(Matrix.MatrixClient).isRequired,
page_type: React.PropTypes.string.isRequired,
onRoomCreated: React.PropTypes.func,
onUserSettingsClose: React.PropTypes.func,
matrixClient: PropTypes.instanceOf(Matrix.MatrixClient).isRequired,
page_type: PropTypes.string.isRequired,
onRoomCreated: PropTypes.func,
onUserSettingsClose: PropTypes.func,
// Called with the credentials of a registered user (if they were a ROU that
// transitioned to PWLU)
onRegistered: React.PropTypes.func,
onRegistered: PropTypes.func,
teamToken: React.PropTypes.string,
teamToken: PropTypes.string,
// and lots and lots of other stuff.
},
childContextTypes: {
matrixClient: React.PropTypes.instanceOf(Matrix.MatrixClient),
authCache: React.PropTypes.object,
matrixClient: PropTypes.instanceOf(Matrix.MatrixClient),
authCache: PropTypes.object,
},
getChildContext: function() {
@@ -208,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');
@@ -330,21 +376,21 @@ const LoggedInView = React.createClass({
return (
<div className='mx_MatrixChat_wrapper'>
{ topBar }
<div className={bodyClasses}>
{ SettingsStore.isFeatureEnabled("feature_tag_panel") ? <TagPanel /> : <div /> }
<LeftPanel
selectedRoom={this.props.currentRoomId}
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>
);
},
});
export default DragDropContext(HTML5Backend)(LoggedInView);
export default LoggedInView;
+75 -29
View File
@@ -19,6 +19,7 @@ limitations under the License.
import Promise from 'bluebird';
import React from 'react';
import PropTypes from 'prop-types';
import Matrix from "matrix-js-sdk";
import Analytics from "../../Analytics";
@@ -92,38 +93,38 @@ export default React.createClass({
displayName: 'MatrixChat',
propTypes: {
config: React.PropTypes.object,
ConferenceHandler: React.PropTypes.any,
onNewScreen: React.PropTypes.func,
registrationUrl: React.PropTypes.string,
enableGuest: React.PropTypes.bool,
config: PropTypes.object,
ConferenceHandler: PropTypes.any,
onNewScreen: PropTypes.func,
registrationUrl: PropTypes.string,
enableGuest: PropTypes.bool,
// the queryParams extracted from the [real] query-string of the URI
realQueryParams: React.PropTypes.object,
realQueryParams: PropTypes.object,
// the initial queryParams extracted from the hash-fragment of the URI
startingFragmentQueryParams: React.PropTypes.object,
startingFragmentQueryParams: PropTypes.object,
// called when we have completed a token login
onTokenLoginCompleted: React.PropTypes.func,
onTokenLoginCompleted: PropTypes.func,
// Represents the screen to display as a result of parsing the initial
// window.location
initialScreenAfterLogin: React.PropTypes.shape({
screen: React.PropTypes.string.isRequired,
params: React.PropTypes.object,
initialScreenAfterLogin: PropTypes.shape({
screen: PropTypes.string.isRequired,
params: PropTypes.object,
}),
// displayname, if any, to set on the device when logging
// in/registering.
defaultDeviceDisplayName: React.PropTypes.string,
defaultDeviceDisplayName: PropTypes.string,
// A function that makes a registration URL
makeRegistrationUrl: React.PropTypes.func.isRequired,
makeRegistrationUrl: PropTypes.func.isRequired,
},
childContextTypes: {
appConfig: React.PropTypes.object,
appConfig: PropTypes.object,
},
AuxPanel: {
@@ -617,18 +618,26 @@ export default React.createClass({
},
_startRegistration: function(params) {
this.setStateForNewView({
const newState = {
view: VIEWS.REGISTER,
// these params may be undefined, but if they are,
// unset them from our state: we don't want to
// resume a previous registration session if the
// user just clicked 'register'
register_client_secret: params.client_secret,
register_session_id: params.session_id,
register_hs_url: params.hs_url,
register_is_url: params.is_url,
register_id_sid: params.sid,
});
};
// Only honour params if they are all present, otherwise we reset
// HS and IS URLs when switching to registration.
if (params.client_secret &&
params.session_id &&
params.hs_url &&
params.is_url &&
params.sid
) {
newState.register_client_secret = params.client_secret;
newState.register_session_id = params.session_id;
newState.register_hs_url = params.hs_url;
newState.register_is_url = params.is_url;
newState.register_id_sid = params.sid;
}
this.setStateForNewView(newState);
this.notifyNewScreen('register');
},
@@ -846,16 +855,36 @@ export default React.createClass({
}).close;
},
_leaveRoomWarnings: function(roomId) {
const roomToLeave = MatrixClientPeg.get().getRoom(roomId);
// Show a warning if there are additional complications.
const joinRules = roomToLeave.currentState.getStateEvents('m.room.join_rules', '');
const warnings = [];
if (joinRules) {
const rule = joinRules.getContent().join_rule;
if (rule !== "public") {
warnings.push((
<span className="warning" key="non_public_warning">
{ _t("This room is not public. You will not be able to rejoin without an invite.") }
</span>
));
}
}
return warnings;
},
_leaveRoom: function(roomId) {
const QuestionDialog = sdk.getComponent("dialogs.QuestionDialog");
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
const roomToLeave = MatrixClientPeg.get().getRoom(roomId);
const warnings = this._leaveRoomWarnings(roomId);
Modal.createTrackedDialog('Leave room', '', QuestionDialog, {
title: _t("Leave room"),
description: (
<span>
{ _t("Are you sure you want to leave the room '%(roomName)s'?", {roomName: roomToLeave.name}) }
{ warnings }
</span>
),
onFinished: (shouldLeave) => {
@@ -1065,10 +1094,10 @@ export default React.createClass({
// this if we are not scrolled up in the view. To find out, delegate to
// the timeline panel. If the timeline panel doesn't exist, then we assume
// it is safe to reset the timeline.
if (!self.refs.loggedInView) {
if (!self._loggedInView || !self._loggedInView.child) {
return true;
}
return self.refs.loggedInView.canResetTimelineInRoom(roomId);
return self._loggedInView.child.canResetTimelineInRoom(roomId);
});
cli.on('sync', function(state, prevState) {
@@ -1480,6 +1509,17 @@ export default React.createClass({
}
},
onServerConfigChange(config) {
const newState = {};
if (config.hsUrl) {
newState.register_hs_url = config.hsUrl;
}
if (config.isUrl) {
newState.register_is_url = config.isUrl;
}
this.setState(newState);
},
_makeRegistrationUrl: function(params) {
if (this.props.startingFragmentQueryParams.referrer) {
params.referrer = this.props.startingFragmentQueryParams.referrer;
@@ -1487,6 +1527,10 @@ export default React.createClass({
return this.props.makeRegistrationUrl(params);
},
_collectLoggedInView: function(ref) {
this._loggedInView = ref;
},
render: function() {
// console.log(`Rendering MatrixChat with view ${this.state.view}`);
@@ -1519,7 +1563,7 @@ export default React.createClass({
*/
const LoggedInView = sdk.getComponent('structures.LoggedInView');
return (
<LoggedInView ref="loggedInView" matrixClient={MatrixClientPeg.get()}
<LoggedInView ref={this._collectLoggedInView} matrixClient={MatrixClientPeg.get()}
onRoomCreated={this.onRoomCreated}
onUserSettingsClose={this.onUserSettingsClose}
onRegistered={this.onRegistered}
@@ -1564,6 +1608,7 @@ export default React.createClass({
onLoginClick={this.onLoginClick}
onRegisterClick={this.onRegisterClick}
onCancelClick={MatrixClientPeg.get() ? this.onReturnToAppClick : null}
onServerConfigChange={this.onServerConfigChange}
/>
);
}
@@ -1598,6 +1643,7 @@ export default React.createClass({
onForgotPasswordClick={this.onForgotPasswordClick}
enableGuest={this.props.enableGuest}
onCancelClick={MatrixClientPeg.get() ? this.onReturnToAppClick : null}
onServerConfigChange={this.onServerConfigChange}
/>
);
}
+23 -33
View File
@@ -16,15 +16,15 @@ limitations under the License.
import React from 'react';
import ReactDOM from 'react-dom';
import PropTypes from 'prop-types';
import classNames from 'classnames';
import shouldHideEvent from '../../shouldHideEvent';
import {wantsDateSeparator} from '../../DateUtils';
import dis from "../../dispatcher";
import sdk from '../../index';
import MatrixClientPeg from '../../MatrixClientPeg';
const MILLIS_IN_DAY = 86400000;
/* (almost) stateless UI component which builds the event tiles in the room timeline.
*/
module.exports = React.createClass({
@@ -32,63 +32,63 @@ module.exports = React.createClass({
propTypes: {
// true to give the component a 'display: none' style.
hidden: React.PropTypes.bool,
hidden: PropTypes.bool,
// true to show a spinner at the top of the timeline to indicate
// back-pagination in progress
backPaginating: React.PropTypes.bool,
backPaginating: PropTypes.bool,
// true to show a spinner at the end of the timeline to indicate
// forward-pagination in progress
forwardPaginating: React.PropTypes.bool,
forwardPaginating: PropTypes.bool,
// the list of MatrixEvents to display
events: React.PropTypes.array.isRequired,
events: PropTypes.array.isRequired,
// ID of an event to highlight. If undefined, no event will be highlighted.
highlightedEventId: React.PropTypes.string,
highlightedEventId: PropTypes.string,
// Should we show URL Previews
showUrlPreview: React.PropTypes.bool,
showUrlPreview: PropTypes.bool,
// event after which we should show a read marker
readMarkerEventId: React.PropTypes.string,
readMarkerEventId: PropTypes.string,
// whether the read marker should be visible
readMarkerVisible: React.PropTypes.bool,
readMarkerVisible: PropTypes.bool,
// the userid of our user. This is used to suppress the read marker
// for pending messages.
ourUserId: React.PropTypes.string,
ourUserId: PropTypes.string,
// true to suppress the date at the start of the timeline
suppressFirstDateSeparator: React.PropTypes.bool,
suppressFirstDateSeparator: PropTypes.bool,
// whether to show read receipts
showReadReceipts: React.PropTypes.bool,
showReadReceipts: PropTypes.bool,
// true if updates to the event list should cause the scroll panel to
// scroll down when we are at the bottom of the window. See ScrollPanel
// for more details.
stickyBottom: React.PropTypes.bool,
stickyBottom: PropTypes.bool,
// callback which is called when the panel is scrolled.
onScroll: React.PropTypes.func,
onScroll: PropTypes.func,
// callback which is called when more content is needed.
onFillRequest: React.PropTypes.func,
onFillRequest: PropTypes.func,
// className for the panel
className: React.PropTypes.string.isRequired,
className: PropTypes.string.isRequired,
// shape parameter to be passed to EventTiles
tileShape: React.PropTypes.string,
tileShape: PropTypes.string,
// show twelve hour timestamps
isTwelveHour: React.PropTypes.bool,
isTwelveHour: PropTypes.bool,
// show timestamps always
alwaysShowTimestamps: React.PropTypes.bool,
alwaysShowTimestamps: PropTypes.bool,
},
componentWillMount: function() {
@@ -325,7 +325,7 @@ module.exports = React.createClass({
const key = "membereventlistsummary-" + (prevEvent ? mxEv.getId() : "initial");
if (this._wantsDateSeparator(prevEvent, mxEv.getDate())) {
const dateSeparator = <li key={ts1+'~'}><DateSeparator key={ts1+'~'} ts={ts1} showTwelveHour={this.props.isTwelveHour} /></li>;
const dateSeparator = <li key={ts1+'~'}><DateSeparator key={ts1+'~'} ts={ts1} /></li>;
ret.push(dateSeparator);
}
@@ -479,7 +479,7 @@ module.exports = React.createClass({
// do we need a date separator since the last event?
if (this._wantsDateSeparator(prevEvent, eventDate)) {
const dateSeparator = <li key={ts1}><DateSeparator key={ts1} ts={ts1} showTwelveHour={this.props.isTwelveHour} /></li>;
const dateSeparator = <li key={ts1}><DateSeparator key={ts1} ts={ts1} /></li>;
ret.push(dateSeparator);
continuation = false;
}
@@ -522,17 +522,7 @@ module.exports = React.createClass({
// here.
return !this.props.suppressFirstDateSeparator;
}
const prevEventDate = prevEvent.getDate();
if (!nextEventDate || !prevEventDate) {
return false;
}
// Return early for events that are > 24h apart
if (Math.abs(prevEvent.getTs() - nextEventDate.getTime()) > MILLIS_IN_DAY) {
return true;
}
// Compare weekdays
return prevEventDate.getDay() !== nextEventDate.getDay();
return wantsDateSeparator(prevEvent.getDate(), nextEventDate);
},
// get a list of read receipts that should be shown next to this event
+6 -3
View File
@@ -15,6 +15,7 @@ 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';
@@ -26,7 +27,7 @@ export default withMatrixClient(React.createClass({
displayName: 'MyGroups',
propTypes: {
matrixClient: React.PropTypes.object.isRequired,
matrixClient: PropTypes.object.isRequired,
},
getInitialState: function() {
@@ -72,8 +73,10 @@ 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_joinedGroups">
{ groupNodes }
</div>
</GeminiScrollbar> :
<div className="mx_MyGroups_placeholder">
{ _t(
+33 -22
View File
@@ -16,6 +16,7 @@ limitations under the License.
*/
import React from 'react';
import PropTypes from 'prop-types';
import Matrix from 'matrix-js-sdk';
import { _t } from '../../languageHandler';
import sdk from '../../index';
@@ -23,7 +24,7 @@ import WhoIsTyping from '../../WhoIsTyping';
import MatrixClientPeg from '../../MatrixClientPeg';
import MemberAvatar from '../views/avatars/MemberAvatar';
import Resend from '../../Resend';
import { showUnknownDeviceDialogForMessages } from '../../cryptodevices';
import * as cryptodevices from '../../cryptodevices';
const STATUS_BAR_HIDDEN = 0;
const STATUS_BAR_EXPANDED = 1;
@@ -41,59 +42,59 @@ module.exports = React.createClass({
propTypes: {
// the room this statusbar is representing.
room: React.PropTypes.object.isRequired,
room: PropTypes.object.isRequired,
// the number of messages which have arrived since we've been scrolled up
numUnreadMessages: React.PropTypes.number,
numUnreadMessages: PropTypes.number,
// this is true if we are fully scrolled-down, and are looking at
// the end of the live timeline.
atEndOfLiveTimeline: React.PropTypes.bool,
atEndOfLiveTimeline: PropTypes.bool,
// This is true when the user is alone in the room, but has also sent a message.
// Used to suggest to the user to invite someone
sentMessageAndIsAlone: React.PropTypes.bool,
sentMessageAndIsAlone: PropTypes.bool,
// true if there is an active call in this room (means we show
// the 'Active Call' text in the status bar if there is nothing
// more interesting)
hasActiveCall: React.PropTypes.bool,
hasActiveCall: PropTypes.bool,
// Number of names to display in typing indication. E.g. set to 3, will
// result in "X, Y, Z and 100 others are typing."
whoIsTypingLimit: React.PropTypes.number,
whoIsTypingLimit: PropTypes.number,
// callback for when the user clicks on the 'resend all' button in the
// 'unsent messages' bar
onResendAllClick: React.PropTypes.func,
onResendAllClick: PropTypes.func,
// callback for when the user clicks on the 'cancel all' button in the
// 'unsent messages' bar
onCancelAllClick: React.PropTypes.func,
onCancelAllClick: PropTypes.func,
// callback for when the user clicks on the 'invite others' button in the
// 'you are alone' bar
onInviteClick: React.PropTypes.func,
onInviteClick: PropTypes.func,
// callback for when the user clicks on the 'stop warning me' button in the
// 'you are alone' bar
onStopWarningClick: React.PropTypes.func,
onStopWarningClick: PropTypes.func,
// callback for when the user clicks on the 'scroll to bottom' button
onScrollToBottomClick: React.PropTypes.func,
onScrollToBottomClick: PropTypes.func,
// callback for when we do something that changes the size of the
// status bar. This is used to trigger a re-layout in the parent
// component.
onResize: React.PropTypes.func,
onResize: PropTypes.func,
// callback for when the status bar can be hidden from view, as it is
// not displaying anything
onHidden: React.PropTypes.func,
onHidden: PropTypes.func,
// callback for when the status bar is displaying something and should
// be visible
onVisible: React.PropTypes.func,
onVisible: PropTypes.func,
},
getDefaultProps: function() {
@@ -147,6 +148,13 @@ module.exports = React.createClass({
});
},
_onSendWithoutVerifyingClick: function() {
cryptodevices.getUnknownDevicesForRoom(MatrixClientPeg.get(), this.props.room).then((devices) => {
cryptodevices.markAllDevicesKnown(MatrixClientPeg.get(), devices);
Resend.resendUnsentEvents(this.props.room);
});
},
_onResendAllClick: function() {
Resend.resendUnsentEvents(this.props.room);
},
@@ -156,7 +164,7 @@ module.exports = React.createClass({
},
_onShowDevicesClick: function() {
showUnknownDeviceDialogForMessages(MatrixClientPeg.get(), this.props.room);
cryptodevices.showUnknownDeviceDialogForMessages(MatrixClientPeg.get(), this.props.room);
},
_onRoomLocalEchoUpdated: function(event, room, oldEventId, oldStatus) {
@@ -169,8 +177,10 @@ module.exports = React.createClass({
// Check whether current size is greater than 0, if yes call props.onVisible
_checkSize: function() {
if (this.props.onVisible && this._getSize()) {
this.props.onVisible();
if (this._getSize()) {
if (this.props.onVisible) this.props.onVisible();
} else {
if (this.props.onHidden) this.props.onHidden();
}
},
@@ -286,10 +296,11 @@ module.exports = React.createClass({
if (hasUDE) {
title = _t("Message not sent due to unknown devices being present");
content = _t(
"<showDevicesText>Show devices</showDevicesText> or <cancelText>cancel all</cancelText>.",
"<showDevicesText>Show devices</showDevicesText>, <sendAnywayText>send anyway</sendAnywayText> or <cancelText>cancel</cancelText>.",
{},
{
'showDevicesText': (sub) => <a className="mx_RoomStatusBar_resend_link" key="resend" onClick={this._onShowDevicesClick}>{ sub }</a>,
'sendAnywayText': (sub) => <a className="mx_RoomStatusBar_resend_link" key="sendAnyway" onClick={this._onSendWithoutVerifyingClick}>{ sub }</a>,
'cancelText': (sub) => <a className="mx_RoomStatusBar_resend_link" key="cancel" onClick={this._onCancelAllClick}>{ sub }</a>,
},
);
@@ -302,11 +313,11 @@ module.exports = React.createClass({
) {
title = unsentMessages[0].error.data.error;
} else {
title = _t("Some of your messages have not been sent.");
title = _t('%(count)s of your messages have not been sent.', { count: unsentMessages.length });
}
content = _t("<resendText>Resend all</resendText> or <cancelText>cancel all</cancelText> now. " +
content = _t("%(count)s <resendText>Resend all</resendText> or <cancelText>cancel all</cancelText> now. " +
"You can also select individual messages to resend or cancel.",
{},
{ count: unsentMessages.length },
{
'resendText': (sub) =>
<a className="mx_RoomStatusBar_resend_link" key="resend" onClick={this._onResendAllClick}>{ sub }</a>,
+67 -45
View File
@@ -24,6 +24,7 @@ import shouldHideEvent from "../../shouldHideEvent";
const React = require("react");
const ReactDOM = require("react-dom");
import PropTypes from 'prop-types';
import Promise from 'bluebird';
const classNames = require("classnames");
import { _t } from '../../languageHandler';
@@ -58,18 +59,18 @@ if (DEBUG) {
module.exports = React.createClass({
displayName: 'RoomView',
propTypes: {
ConferenceHandler: React.PropTypes.any,
ConferenceHandler: PropTypes.any,
// Called with the credentials of a registered user (if they were a ROU that
// transitioned to PWLU)
onRegistered: React.PropTypes.func,
onRegistered: PropTypes.func,
// An object representing a third party invite to join this room
// Fields:
// * inviteSignUrl (string) The URL used to join this room from an email invite
// (given as part of the link in the invite email)
// * invitedEmail (string) The email address that was invited to this room
thirdPartyInvite: React.PropTypes.object,
thirdPartyInvite: PropTypes.object,
// Any data about the room that would normally come from the Home Server
// but has been passed out-of-band, eg. the room name and avatar URL
@@ -80,10 +81,10 @@ module.exports = React.createClass({
// * avatarUrl (string) The mxc:// avatar URL for the room
// * inviterName (string) The display name of the person who
// * invited us tovthe room
oobData: React.PropTypes.object,
oobData: PropTypes.object,
// is the RightPanel collapsed?
collapsedRhs: React.PropTypes.bool,
collapsedRhs: PropTypes.bool,
},
getInitialState: function() {
@@ -263,12 +264,19 @@ module.exports = React.createClass({
isPeeking: true, // this will change to false if peeking fails
});
MatrixClientPeg.get().peekInRoom(roomId).then((room) => {
if (this.unmounted) {
return;
}
this.setState({
room: room,
peekLoading: false,
});
this._onRoomLoaded(room);
}, (err) => {
if (this.unmounted) {
return;
}
// Stop peeking if anything went wrong
this.setState({
isPeeking: false,
@@ -285,7 +293,7 @@ module.exports = React.createClass({
} else {
throw err;
}
}).done();
});
}
} else if (room) {
// Stop peeking because we have joined this room previously
@@ -628,8 +636,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);
},
@@ -678,23 +686,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) {
@@ -735,6 +727,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);
},
@@ -827,18 +857,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();
});
},
@@ -863,9 +881,13 @@ module.exports = React.createClass({
ev.dataTransfer.dropEffect = 'none';
const items = ev.dataTransfer.items;
if (items.length == 1) {
if (items[0].kind == 'file') {
const items = [...ev.dataTransfer.items];
if (items.length >= 1) {
const isDraggingFiles = items.every(function(item) {
return item.kind == 'file';
});
if (isDraggingFiles) {
this.setState({ draggingFile: true });
ev.dataTransfer.dropEffect = 'copy';
}
@@ -876,10 +898,8 @@ module.exports = React.createClass({
ev.stopPropagation();
ev.preventDefault();
this.setState({ draggingFile: false });
const files = ev.dataTransfer.files;
if (files.length == 1) {
this.uploadFile(files[0]);
}
const files = [...ev.dataTransfer.files];
files.forEach(this.uploadFile);
},
onDragLeaveOrEnd: function(ev) {
@@ -1369,10 +1389,12 @@ module.exports = React.createClass({
},
onStatusBarHidden: function() {
if (this.unmounted) return;
// This is currently not desired as it is annoying if it keeps expanding and collapsing
// TODO: Find a less annoying way of hiding the status bar
/*if (this.unmounted) return;
this.setState({
statusBarVisible: false,
});
});*/
},
showSettings: function(show) {
+9 -8
View File
@@ -16,6 +16,7 @@ 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';
@@ -86,7 +87,7 @@ module.exports = React.createClass({
* scroll down to show the new element, rather than preserving the
* existing view.
*/
stickyBottom: React.PropTypes.bool,
stickyBottom: PropTypes.bool,
/* startAtBottom: if set to true, the view is assumed to start
* scrolled to the bottom.
@@ -95,7 +96,7 @@ module.exports = React.createClass({
* behaviour stays the same for other uses of ScrollPanel.
* If so, let's remove this parameter down the line.
*/
startAtBottom: React.PropTypes.bool,
startAtBottom: PropTypes.bool,
/* onFillRequest(backwards): a callback which is called on scroll when
* the user nears the start (backwards = true) or end (backwards =
@@ -110,7 +111,7 @@ module.exports = React.createClass({
* directon (at this time) - which will stop the pagination cycle until
* the user scrolls again.
*/
onFillRequest: React.PropTypes.func,
onFillRequest: PropTypes.func,
/* onUnfillRequest(backwards): a callback which is called on scroll when
* there are children elements that are far out of view and could be removed
@@ -121,24 +122,24 @@ module.exports = React.createClass({
* first element to remove if removing from the front/bottom, and last element
* to remove if removing from the back/top.
*/
onUnfillRequest: React.PropTypes.func,
onUnfillRequest: PropTypes.func,
/* onScroll: a callback which is called whenever any scroll happens.
*/
onScroll: React.PropTypes.func,
onScroll: PropTypes.func,
/* onResize: a callback which is called whenever the Gemini scroll
* panel is resized
*/
onResize: React.PropTypes.func,
onResize: PropTypes.func,
/* className: classnames to add to the top-level div
*/
className: React.PropTypes.string,
className: PropTypes.string,
/* style: styles to add to the top-level div
*/
style: React.PropTypes.object,
style: PropTypes.object,
},
getDefaultProps: function() {
+68 -47
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 FilterStore from '../../stores/FilterStore';
import FlairStore from '../../stores/FlairStore';
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 { Droppable } from 'react-beautiful-dnd';
const TagPanel = React.createClass({
displayName: 'TagPanel',
@@ -36,17 +37,7 @@ const TagPanel = React.createClass({
getInitialState() {
return {
// A list of group profiles for tags that are group IDs. The intention in future
// is to allow arbitrary tags to be selected in the TagPanel, not just groups.
// For now, it suffices to maintain a list of ordered group profiles.
orderedGroupTagProfiles: [
// {
// groupId: '+awesome:foo.bar',{
// name: 'My Awesome Community',
// avatarUrl: 'mxc://...',
// shortDescription: 'Some description...',
// },
],
orderedTags: [],
selectedTags: [],
};
},
@@ -54,28 +45,15 @@ 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._filterStoreToken = FilterStore.addListener(() => {
if (this.unmounted) {
return;
}
this.setState({
selectedTags: FilterStore.getSelectedTags(),
});
});
this._tagOrderStoreToken = TagOrderStore.addListener(() => {
if (this.unmounted) {
return;
}
const orderedTags = TagOrderStore.getOrderedTags() || [];
const orderedGroupTags = orderedTags.filter((t) => t[0] === '+');
// XXX: One profile lookup failing will bring the whole lot down
Promise.all(orderedGroupTags.map(
(groupId) => FlairStore.getGroupProfileCached(this.context.matrixClient, groupId),
)).then((orderedGroupTagProfiles) => {
if (this.unmounted) return;
this.setState({orderedGroupTagProfiles});
this.setState({
orderedTags: TagOrderStore.getOrderedTags() || [],
selectedTags: TagOrderStore.getSelectedTags(),
});
});
// This could be done by anything with a matrix client
@@ -85,6 +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);
if (this._filterStoreToken) {
this._filterStoreToken.remove();
}
@@ -95,7 +74,17 @@ const TagPanel = React.createClass({
dis.dispatch(GroupActions.fetchJoinedGroups(this.context.matrixClient));
},
onClick() {
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;
if (reconnected) {
// Load joined groups
dis.dispatch(GroupActions.fetchJoinedGroups(this.context.matrixClient));
}
},
onClick(e) {
dis.dispatch({action: 'deselect_tags'});
},
@@ -104,30 +93,62 @@ const TagPanel = React.createClass({
dis.dispatch({action: 'view_create_group'});
},
onTagTileEndDrag() {
dis.dispatch(TagOrderActions.commitTagOrdering(this.context.matrixClient));
onClearFilterClick(ev) {
dis.dispatch({action: 'deselect_tags'});
},
render() {
const AccessibleButton = sdk.getComponent('elements.AccessibleButton');
const TintableSvg = sdk.getComponent('elements.TintableSvg');
const GroupsButton = sdk.getComponent('elements.GroupsButton');
const DNDTagTile = sdk.getComponent('elements.DNDTagTile');
const AccessibleButton = sdk.getComponent('elements.AccessibleButton');
const tags = this.state.orderedGroupTagProfiles.map((groupProfile, index) => {
const tags = this.state.orderedTags.map((tag, index) => {
return <DNDTagTile
key={groupProfile.groupId + '_' + index}
groupProfile={groupProfile}
selected={this.state.selectedTags.includes(groupProfile.groupId)}
onEndDrag={this.onTagTileEndDrag}
key={tag}
tag={tag}
index={index}
selected={this.state.selectedTags.includes(tag)}
/>;
});
return <div className="mx_TagPanel" onClick={this.onClick}>
<div className="mx_TagPanel_tagTileContainer">
{ tags }
</div>
<AccessibleButton className="mx_TagPanel_createGroupButton" onClick={this.onCreateGroupClick}>
<TintableSvg src="img/icons-create-room.svg" width="25" height="25" />
const clearButton = this.state.selectedTags.length > 0 ?
<img
src="img/icons-close.svg"
alt={_t("Clear filter")}
title={_t("Clear filter")}
width="24"
height="24" /> :
<div />;
return <div className="mx_TagPanel">
<AccessibleButton className="mx_TagPanel_clearButton" onClick={this.onClearFilterClick}>
{ clearButton }
</AccessibleButton>
<div className="mx_TagPanel_divider" />
<GeminiScrollbar
className="mx_TagPanel_scroller"
autoshow={true}
onClick={this.onClick}
>
<Droppable
droppableId="tag-panel-droppable"
type="draggable-TagTile"
>
{ (provided, snapshot) => (
<div
className="mx_TagPanel_tagTileContainer"
ref={provided.innerRef}
>
{ tags }
{ provided.placeholder }
</div>
) }
</Droppable>
</GeminiScrollbar>
<div className="mx_TagPanel_divider" />
<div className="mx_TagPanel_createGroupButton">
<GroupsButton tooltip={true} />
</div>
</div>;
},
});
+32 -18
View File
@@ -19,6 +19,7 @@ import SettingsStore from "../../settings/SettingsStore";
const React = require('react');
const ReactDOM = require("react-dom");
import PropTypes from 'prop-types';
import Promise from 'bluebird';
const Matrix = require("matrix-js-sdk");
@@ -58,49 +59,49 @@ var TimelinePanel = React.createClass({
// representing. This may or may not have a room, depending on what it's
// a timeline representing. If it has a room, we maintain RRs etc for
// that room.
timelineSet: React.PropTypes.object.isRequired,
timelineSet: PropTypes.object.isRequired,
showReadReceipts: React.PropTypes.bool,
showReadReceipts: PropTypes.bool,
// Enable managing RRs and RMs. These require the timelineSet to have a room.
manageReadReceipts: React.PropTypes.bool,
manageReadMarkers: React.PropTypes.bool,
manageReadReceipts: PropTypes.bool,
manageReadMarkers: PropTypes.bool,
// true to give the component a 'display: none' style.
hidden: React.PropTypes.bool,
hidden: PropTypes.bool,
// ID of an event to highlight. If undefined, no event will be highlighted.
// typically this will be either 'eventId' or undefined.
highlightedEventId: React.PropTypes.string,
highlightedEventId: PropTypes.string,
// id of an event to jump to. If not given, will go to the end of the
// live timeline.
eventId: React.PropTypes.string,
eventId: PropTypes.string,
// where to position the event given by eventId, in pixels from the
// bottom of the viewport. If not given, will try to put the event
// half way down the viewport.
eventPixelOffset: React.PropTypes.number,
eventPixelOffset: PropTypes.number,
// Should we show URL Previews
showUrlPreview: React.PropTypes.bool,
showUrlPreview: PropTypes.bool,
// callback which is called when the panel is scrolled.
onScroll: React.PropTypes.func,
onScroll: PropTypes.func,
// callback which is called when the read-up-to mark is updated.
onReadMarkerUpdated: React.PropTypes.func,
onReadMarkerUpdated: PropTypes.func,
// maximum number of events to show in a timeline
timelineCap: React.PropTypes.number,
timelineCap: PropTypes.number,
// classname to use for the messagepanel
className: React.PropTypes.string,
className: PropTypes.string,
// shape property to be passed to EventTiles
tileShape: React.PropTypes.string,
tileShape: PropTypes.string,
// placeholder text to use if the timeline is empty
empty: React.PropTypes.string,
empty: PropTypes.string,
},
statics: {
@@ -301,6 +302,8 @@ var TimelinePanel = React.createClass({
// set off a pagination request.
onMessageListFillRequest: function(backwards) {
if (!this._shouldPaginate()) return Promise.resolve(false);
const dir = backwards ? EventTimeline.BACKWARDS : EventTimeline.FORWARDS;
const canPaginateKey = backwards ? 'canBackPaginate' : 'canForwardPaginate';
const paginatingKey = backwards ? 'backPaginating' : 'forwardPaginating';
@@ -1090,6 +1093,17 @@ var TimelinePanel = React.createClass({
}, this.props.onReadMarkerUpdated);
},
_shouldPaginate: function() {
// don't try to paginate while events in the timeline are
// still being decrypted. We don't render events while they're
// being decrypted, so they don't take up space in the timeline.
// This means we can pull quite a lot of events into the timeline
// and end up trying to render a lot of events.
return !this.state.events.some((e) => {
return e.isBeingDecrypted();
});
},
render: function() {
const MessagePanel = sdk.getComponent("structures.MessagePanel");
const Loader = sdk.getComponent("elements.Spinner");
@@ -1107,9 +1121,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>
);
}
+2 -1
View File
@@ -15,6 +15,7 @@ limitations under the License.
*/
const React = require('react');
import PropTypes from 'prop-types';
const ContentMessages = require('../../ContentMessages');
const dis = require('../../dispatcher');
const filesize = require('filesize');
@@ -22,7 +23,7 @@ import { _t } from '../../languageHandler';
module.exports = React.createClass({displayName: 'UploadBar',
propTypes: {
room: React.PropTypes.object,
room: PropTypes.object,
},
componentDidMount: function() {
+14 -7
View File
@@ -19,6 +19,7 @@ import SettingsStore, {SettingLevel} from "../../settings/SettingsStore";
const React = require('react');
const ReactDOM = require('react-dom');
import PropTypes from 'prop-types';
const sdk = require('../../index');
const MatrixClientPeg = require("../../MatrixClientPeg");
const PlatformPeg = require("../../PlatformPeg");
@@ -125,8 +126,8 @@ const THEMES = [
const IgnoredUser = React.createClass({
propTypes: {
userId: React.PropTypes.string.isRequired,
onUnignored: React.PropTypes.func.isRequired,
userId: PropTypes.string.isRequired,
onUnignored: PropTypes.func.isRequired,
},
_onUnignoreClick: function() {
@@ -155,16 +156,16 @@ module.exports = React.createClass({
displayName: 'UserSettings',
propTypes: {
onClose: React.PropTypes.func,
onClose: PropTypes.func,
// The brand string given when creating email pushers
brand: React.PropTypes.string,
brand: PropTypes.string,
// The base URL to use in the referral link. Defaults to window.location.origin.
referralBaseUrl: React.PropTypes.string,
referralBaseUrl: PropTypes.string,
// Team token for the referral link. If falsy, the referral section will
// not appear
teamToken: React.PropTypes.string,
teamToken: PropTypes.string,
},
getDefaultProps: function() {
@@ -375,7 +376,7 @@ module.exports = React.createClass({
{ _t("For security, logging out will delete any end-to-end " +
"encryption keys from this browser. If you want to be able " +
"to decrypt your conversation history from future Riot sessions, " +
"please export your room keys for safe-keeping.") }.
"please export your room keys for safe-keeping.") }
</div>,
button: _t("Sign out"),
extraButtons: [
@@ -811,6 +812,12 @@ module.exports = React.createClass({
<h3>{ _t('Analytics') }</h3>
<div className="mx_UserSettings_section">
{ _t('Riot collects anonymous analytics to allow us to improve the application.') }
<br />
{ _t('Privacy is important to us, so we don\'t collect any personal'
+ ' or identifiable data for our analytics.') }
<div className="mx_UserSettings_advanced_spoiler" onClick={Analytics.showDetailsModal}>
{ _t('Learn more about how we use analytics.') }
</div>
{ ANALYTICS_SETTINGS.map( this._renderDeviceSetting ) }
</div>
</div>;
@@ -18,6 +18,7 @@ limitations under the License.
'use strict';
import React from 'react';
import PropTypes from 'prop-types';
import { _t } from '../../../languageHandler';
import sdk from '../../../index';
import Modal from "../../../Modal";
@@ -29,13 +30,13 @@ module.exports = React.createClass({
displayName: 'ForgotPassword',
propTypes: {
defaultHsUrl: React.PropTypes.string,
defaultIsUrl: React.PropTypes.string,
customHsUrl: React.PropTypes.string,
customIsUrl: React.PropTypes.string,
onLoginClick: React.PropTypes.func,
onRegisterClick: React.PropTypes.func,
onComplete: React.PropTypes.func.isRequired,
defaultHsUrl: PropTypes.string,
defaultIsUrl: PropTypes.string,
customHsUrl: PropTypes.string,
customIsUrl: PropTypes.string,
onLoginClick: PropTypes.func,
onRegisterClick: PropTypes.func,
onComplete: PropTypes.func.isRequired,
},
getInitialState: function() {
+17 -13
View File
@@ -18,6 +18,7 @@ limitations under the License.
'use strict';
import React from 'react';
import PropTypes from 'prop-types';
import { _t } from '../../../languageHandler';
import * as languageHandler from '../../../languageHandler';
import sdk from '../../../index';
@@ -36,27 +37,28 @@ module.exports = React.createClass({
displayName: 'Login',
propTypes: {
onLoggedIn: React.PropTypes.func.isRequired,
onLoggedIn: PropTypes.func.isRequired,
enableGuest: React.PropTypes.bool,
enableGuest: PropTypes.bool,
customHsUrl: React.PropTypes.string,
customIsUrl: React.PropTypes.string,
defaultHsUrl: React.PropTypes.string,
defaultIsUrl: React.PropTypes.string,
customHsUrl: PropTypes.string,
customIsUrl: PropTypes.string,
defaultHsUrl: PropTypes.string,
defaultIsUrl: PropTypes.string,
// Secondary HS which we try to log into if the user is using
// the default HS but login fails. Useful for migrating to a
// different home server without confusing users.
fallbackHsUrl: React.PropTypes.string,
fallbackHsUrl: PropTypes.string,
defaultDeviceDisplayName: React.PropTypes.string,
defaultDeviceDisplayName: PropTypes.string,
// login shouldn't know or care how registration is done.
onRegisterClick: React.PropTypes.func.isRequired,
onRegisterClick: PropTypes.func.isRequired,
// login shouldn't care how password recovery is done.
onForgotPasswordClick: React.PropTypes.func,
onCancelClick: React.PropTypes.func,
onForgotPasswordClick: PropTypes.func,
onCancelClick: PropTypes.func,
onServerConfigChange: PropTypes.func.isRequired,
},
getInitialState: function() {
@@ -217,6 +219,8 @@ module.exports = React.createClass({
if (config.isUrl !== undefined) {
newState.enteredIdentityServerUrl = config.isUrl;
}
this.props.onServerConfigChange(config);
this.setState(newState, function() {
self._initLoginLogic(config.hsUrl || null, config.isUrl);
});
@@ -427,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>;
}
}
@@ -17,6 +17,7 @@ limitations under the License.
'use strict';
import React from 'react';
import PropTypes from 'prop-types';
import sdk from '../../../index';
import MatrixClientPeg from '../../../MatrixClientPeg';
import { _t } from '../../../languageHandler';
@@ -25,7 +26,7 @@ module.exports = React.createClass({
displayName: 'PostRegistration',
propTypes: {
onComplete: React.PropTypes.func.isRequired,
onComplete: PropTypes.func.isRequired,
},
getInitialState: function() {
+22 -19
View File
@@ -19,6 +19,7 @@ import Matrix from 'matrix-js-sdk';
import Promise from 'bluebird';
import React from 'react';
import PropTypes from 'prop-types';
import sdk from '../../../index';
import ServerConfig from '../../views/login/ServerConfig';
@@ -35,31 +36,32 @@ module.exports = React.createClass({
displayName: 'Registration',
propTypes: {
onLoggedIn: React.PropTypes.func.isRequired,
clientSecret: React.PropTypes.string,
sessionId: React.PropTypes.string,
makeRegistrationUrl: React.PropTypes.func.isRequired,
idSid: React.PropTypes.string,
customHsUrl: React.PropTypes.string,
customIsUrl: React.PropTypes.string,
defaultHsUrl: React.PropTypes.string,
defaultIsUrl: React.PropTypes.string,
brand: React.PropTypes.string,
email: React.PropTypes.string,
referrer: React.PropTypes.string,
teamServerConfig: React.PropTypes.shape({
onLoggedIn: PropTypes.func.isRequired,
clientSecret: PropTypes.string,
sessionId: PropTypes.string,
makeRegistrationUrl: PropTypes.func.isRequired,
idSid: PropTypes.string,
customHsUrl: PropTypes.string,
customIsUrl: PropTypes.string,
defaultHsUrl: PropTypes.string,
defaultIsUrl: PropTypes.string,
brand: PropTypes.string,
email: PropTypes.string,
referrer: PropTypes.string,
teamServerConfig: PropTypes.shape({
// Email address to request new teams
supportEmail: React.PropTypes.string.isRequired,
supportEmail: PropTypes.string.isRequired,
// URL of the riot-team-server to get team configurations and track referrals
teamServerURL: React.PropTypes.string.isRequired,
teamServerURL: PropTypes.string.isRequired,
}),
teamSelected: React.PropTypes.object,
teamSelected: PropTypes.object,
defaultDeviceDisplayName: React.PropTypes.string,
defaultDeviceDisplayName: PropTypes.string,
// registration shouldn't know or care how login is done.
onLoginClick: React.PropTypes.func.isRequired,
onCancelClick: React.PropTypes.func,
onLoginClick: PropTypes.func.isRequired,
onCancelClick: PropTypes.func,
onServerConfigChange: PropTypes.func.isRequired,
},
getInitialState: function() {
@@ -130,6 +132,7 @@ module.exports = React.createClass({
if (config.isUrl !== undefined) {
newState.isUrl = config.isUrl;
}
this.props.onServerConfigChange(config);
this.setState(newState, function() {
this._replaceClient();
});