Merge branch 'develop' of github.com:matrix-org/matrix-react-sdk into t3chguy/updating_stuff

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
This commit is contained in:
Michael Telatynski 2017-06-11 23:21:50 +01:00
commit 1069bd33f0
127 changed files with 6493 additions and 1431 deletions

View file

@ -17,7 +17,6 @@ limitations under the License.
'use strict';
import React from 'react';
import q from 'q';
import { _t } from '../../languageHandler';
import sdk from '../../index';
import MatrixClientPeg from '../../MatrixClientPeg';
@ -232,7 +231,7 @@ module.exports = React.createClass({
if (curr_phase == this.phases.ERROR) {
error_box = (
<div className="mx_Error">
{_t('An error occured: %(error_string)s', {error_string: this.state.error_string})}
{_t('An error occurred: %(error_string)s', {error_string: this.state.error_string})}
</div>
);
}
@ -247,7 +246,7 @@ module.exports = React.createClass({
return (
<div className="mx_CreateRoom">
<SimpleRoomHeader title="CreateRoom" collapsedRhs={ this.props.collapsedRhs }/>
<SimpleRoomHeader title={_t("Create Room")} collapsedRhs={ this.props.collapsedRhs }/>
<div className="mx_CreateRoom_body">
<input type="text" ref="room_name" value={this.state.room_name} onChange={this.onNameChange} placeholder={_t('Name')}/> <br />
<textarea className="mx_CreateRoom_description" ref="topic" value={this.state.topic} onChange={this.onTopicChange} placeholder={_t('Topic')}/> <br />

View file

@ -19,7 +19,7 @@ import React from 'react';
import Matrix from 'matrix-js-sdk';
import sdk from '../../index';
import MatrixClientPeg from '../../MatrixClientPeg';
import { _t } from '../../languageHandler';
import { _t, _tJsx } from '../../languageHandler';
/*
* Component which shows the filtered file using a TimelinePanel
@ -91,7 +91,9 @@ var FilePanel = React.createClass({
render: function() {
if (MatrixClientPeg.get().isGuest()) {
return <div className="mx_FilePanel mx_RoomView_messageListWrapper">
<div className="mx_RoomView_empty">You must <a href="#/register">register</a> to use this functionality</div>
<div className="mx_RoomView_empty">
{_tJsx("You must <a>register</a> to use this functionality", /<a>(.*?)<\/a>/, (sub) => <a href="#/register" key="sub">{sub}</a>)}
</div>
</div>;
} else if (this.noRoom) {
return <div className="mx_FilePanel mx_RoomView_messageListWrapper">

View file

@ -19,8 +19,6 @@ const InteractiveAuth = Matrix.InteractiveAuth;
import React from 'react';
import sdk from '../../index';
import {getEntryComponentForLoginType} from '../views/login/InteractiveAuthEntryComponents';
export default React.createClass({

View file

@ -25,6 +25,8 @@ import PageTypes from '../../PageTypes';
import CallMediaHandler from '../../CallMediaHandler';
import sdk from '../../index';
import dis from '../../dispatcher';
import sessionStore from '../../stores/SessionStore';
import MatrixClientPeg from '../../MatrixClientPeg';
/**
* This is what our MatrixChat shows when we are logged in. The precise view is
@ -41,10 +43,13 @@ export default React.createClass({
propTypes: {
matrixClient: React.PropTypes.instanceOf(Matrix.MatrixClient).isRequired,
page_type: React.PropTypes.string.isRequired,
onRoomIdResolved: React.PropTypes.func,
onRoomCreated: React.PropTypes.func,
onUserSettingsClose: React.PropTypes.func,
// Called with the credentials of a registered user (if they were a ROU that
// transitioned to PWLU)
onRegistered: React.PropTypes.func,
teamToken: React.PropTypes.string,
// and lots and lots of other stuff.
@ -83,12 +88,32 @@ export default React.createClass({
CallMediaHandler.loadDevices();
document.addEventListener('keydown', this._onKeyDown);
this._sessionStore = sessionStore;
this._sessionStoreToken = this._sessionStore.addListener(
this._setStateFromSessionStore,
);
this._setStateFromSessionStore();
this._matrixClient.on("accountData", this.onAccountData);
},
componentWillUnmount: function() {
document.removeEventListener('keydown', this._onKeyDown);
this._matrixClient.removeListener("accountData", this.onAccountData);
if (this._sessionStoreToken) {
this._sessionStoreToken.remove();
}
},
// Child components assume that the client peg will not be null, so give them some
// sort of assurance here by only allowing a re-render if the client is truthy.
//
// This is required because `LoggedInView` maintains its own state and if this state
// updates after the client peg has been made null (during logout), then it will
// attempt to re-render and the children will throw errors.
shouldComponentUpdate: function() {
return Boolean(MatrixClientPeg.get());
},
getScrollStateForRoom: function(roomId) {
@ -102,10 +127,16 @@ export default React.createClass({
return this.refs.roomView.canResetTimeline();
},
_setStateFromSessionStore() {
this.setState({
userHasGeneratedPassword: Boolean(this._sessionStore.getCachedPassword()),
});
},
onAccountData: function(event) {
if (event.getType() === "im.vector.web.settings") {
this.setState({
useCompactLayout: event.getContent().useCompactLayout
useCompactLayout: event.getContent().useCompactLayout,
});
}
},
@ -180,9 +211,9 @@ export default React.createClass({
const RoomDirectory = sdk.getComponent('structures.RoomDirectory');
const HomePage = sdk.getComponent('structures.HomePage');
const MatrixToolbar = sdk.getComponent('globals.MatrixToolbar');
const GuestWarningBar = sdk.getComponent('globals.GuestWarningBar');
const NewVersionBar = sdk.getComponent('globals.NewVersionBar');
const UpdateCheckBar = sdk.getComponent('globals.UpdateCheckBar');
const PasswordNagBar = sdk.getComponent('globals.PasswordNagBar');
let page_element;
let right_panel = '';
@ -191,15 +222,12 @@ export default React.createClass({
case PageTypes.RoomView:
page_element = <RoomView
ref='roomView'
roomAddress={this.props.currentRoomAlias || this.props.currentRoomId}
autoJoin={this.props.autoJoin}
onRoomIdResolved={this.props.onRoomIdResolved}
eventId={this.props.initialEventId}
onRegistered={this.props.onRegistered}
thirdPartyInvite={this.props.thirdPartyInvite}
oobData={this.props.roomOobData}
highlightedEventId={this.props.highlightedEventId}
eventPixelOffset={this.props.initialEventPixelOffset}
key={this.props.currentRoomAlias || this.props.currentRoomId}
key={this.props.currentRoomId || 'roomview'}
opacity={this.props.middleOpacity}
collapsedRhs={this.props.collapse_rhs}
ConferenceHandler={this.props.ConferenceHandler}
@ -236,12 +264,18 @@ export default React.createClass({
break;
case PageTypes.HomePage:
// If team server config is present, pass the teamServerURL. props.teamToken
// must also be set for the team page to be displayed, otherwise the
// welcomePageUrl is used (which might be undefined).
const teamServerUrl = this.props.config.teamServerConfig ?
this.props.config.teamServerConfig.teamServerURL : null;
page_element = <HomePage
collapsedRhs={this.props.collapse_rhs}
teamServerUrl={this.props.config.teamServerConfig.teamServerURL}
teamServerUrl={teamServerUrl}
teamToken={this.props.teamToken}
/>
if (!this.props.collapse_rhs) right_panel = <RightPanel opacity={this.props.rightOpacity}/>
homePageUrl={this.props.config.welcomePageUrl}
/>;
break;
case PageTypes.UserView:
@ -251,15 +285,16 @@ export default React.createClass({
}
let topBar;
const isGuest = this.props.matrixClient.isGuest();
if (this.props.hasNewVersion) {
topBar = <NewVersionBar version={this.props.version} newVersion={this.props.newVersion}
releaseNotes={this.props.newVersionReleaseNotes}
/>;
} else if (this.props.checkingForUpdate) {
topBar = <UpdateCheckBar {...this.props.checkingForUpdate} />;
} else if (this.props.matrixClient.isGuest()) {
topBar = <GuestWarningBar />;
} else if (Notifier.supportsDesktopNotifications() && !Notifier.isEnabled() && !Notifier.isToolbarHidden()) {
} else if (this.state.userHasGeneratedPassword) {
topBar = <PasswordNagBar />;
} else if (!isGuest && Notifier.supportsDesktopNotifications() && !Notifier.isEnabled() && !Notifier.isToolbarHidden()) {
topBar = <MatrixToolbar />;
}
@ -279,7 +314,6 @@ export default React.createClass({
selectedRoom={this.props.currentRoomId}
collapsed={this.props.collapse_lhs || false}
opacity={this.props.leftOpacity}
teamToken={this.props.teamToken}
/>
<main className='mx_MatrixChat_middlePanel'>
{page_element}

View file

@ -34,6 +34,9 @@ import sdk from '../../index';
import * as Rooms from '../../Rooms';
import linkifyMatrix from "../../linkify-matrix";
import * as Lifecycle from '../../Lifecycle';
// LifecycleStore is not used but does listen to and dispatch actions
import LifecycleStore from '../../stores/LifecycleStore';
import RoomViewStore from '../../stores/RoomViewStore';
import PageTypes from '../../PageTypes';
import createRoom from "../../createRoom";
@ -102,9 +105,6 @@ module.exports = React.createClass({
// What the LoggedInView would be showing if visible
page_type: null,
// If we are viewing a room by alias, this contains the alias
currentRoomAlias: null,
// The ID of the room we're viewing. This is either populated directly
// in the case where we view a room by ID or by RoomView when it resolves
// what ID an alias points at.
@ -192,6 +192,9 @@ module.exports = React.createClass({
componentWillMount: function() {
SdkConfig.put(this.props.config);
RoomViewStore.addListener(this._onRoomViewStoreUpdated);
this._onRoomViewStoreUpdated();
if (!UserSettingsStore.getLocalSetting('analyticsOptOut', false)) Analytics.enable();
// Used by _viewRoom before getting state from sync
@ -293,7 +296,7 @@ module.exports = React.createClass({
},
componentWillUnmount: function() {
Lifecycle.stopMatrixClient(false);
Lifecycle.stopMatrixClient();
dis.unregister(this.dispatcherRef);
UDEHandler.stopListening();
window.removeEventListener("focus", this.onFocus);
@ -323,7 +326,6 @@ module.exports = React.createClass({
onAction: function(payload) {
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
const QuestionDialog = sdk.getComponent("dialogs.QuestionDialog");
const TextInputDialog = sdk.getComponent("dialogs.TextInputDialog");
switch (payload.action) {
case 'logout':
@ -365,17 +367,21 @@ module.exports = React.createClass({
// is completed in another browser, we'll be 401ed for using
// a guest access token for a non-guest account.
// It will be restarted in onReturnToGuestClick
Lifecycle.stopMatrixClient(false);
Lifecycle.stopMatrixClient();
this.notifyNewScreen('register');
break;
case 'start_password_recovery':
if (this.state.loggedIn) return;
this.setStateForNewScreen({
screen: 'forgot_password',
});
this.notifyNewScreen('forgot_password');
break;
case 'start_chat':
createRoom({
dmUserId: payload.user_id,
});
break;
case 'leave_room':
this._leaveRoom(payload.room_id);
break;
@ -436,37 +442,36 @@ module.exports = React.createClass({
this._viewIndexedRoom(payload.roomIndex);
break;
case 'view_user_settings':
if (MatrixClientPeg.get().isGuest()) {
dis.dispatch({
action: 'do_after_sync_prepared',
deferred_action: {
action: 'view_user_settings',
},
});
dis.dispatch({action: 'view_set_mxid'});
break;
}
this._setPage(PageTypes.UserSettings);
this.notifyNewScreen('settings');
break;
case 'view_create_room':
//this._setPage(PageTypes.CreateRoom);
//this.notifyNewScreen('new');
Modal.createDialog(TextInputDialog, {
title: _t('Create Room'),
description: _t('Room name (optional)'),
button: _t('Create Room'),
onFinished: (shouldCreate, name) => {
if (shouldCreate) {
const createOpts = {};
if (name) createOpts.name = name;
createRoom({createOpts}).done();
}
},
});
this._createRoom();
break;
case 'view_room_directory':
this._setPage(PageTypes.RoomDirectory);
this.notifyNewScreen('directory');
break;
case 'view_home_page':
if (!this._teamToken) {
dis.dispatch({action: 'view_room_directory'});
return;
}
this._setPage(PageTypes.HomePage);
this.notifyNewScreen('home');
break;
case 'view_set_mxid':
this._setMxId(payload);
break;
case 'view_start_chat_or_reuse':
this._chatCreateOrReuse(payload.user_id);
break;
case 'view_create_chat':
this._createChat();
break;
@ -508,7 +513,11 @@ module.exports = React.createClass({
this._onSetTheme(payload.value);
break;
case 'on_logging_in':
this.setState({loggingIn: true});
// We are now logging in, so set the state to reflect that
// and also that we're not ready (we'll be marked as logged
// in once the login completes, then ready once the sync
// completes).
this.setState({loggingIn: true, ready: false});
break;
case 'on_logged_in':
this._onLoggedIn(payload.teamToken);
@ -534,6 +543,10 @@ module.exports = React.createClass({
}
},
_onRoomViewStoreUpdated: function() {
this.setState({ currentRoomId: RoomViewStore.getRoomId() });
},
_setPage: function(pageType) {
this.setState({
page_type: pageType,
@ -556,10 +569,20 @@ module.exports = React.createClass({
this.notifyNewScreen('register');
},
// TODO: Move to RoomViewStore
_viewNextRoom: function(roomIndexDelta) {
const allRooms = RoomListSorter.mostRecentActivityFirst(
MatrixClientPeg.get().getRooms(),
);
// If there are 0 rooms or 1 room, view the home page because otherwise
// if there are 0, we end up trying to index into an empty array, and
// if there is 1, we end up viewing the same room.
if (allRooms.length < 2) {
dis.dispatch({
action: 'view_home_page',
});
return;
}
let roomIndex = -1;
for (let i = 0; i < allRooms.length; ++i) {
if (allRooms[i].roomId == this.state.currentRoomId) {
@ -569,15 +592,22 @@ module.exports = React.createClass({
}
roomIndex = (roomIndex + roomIndexDelta) % allRooms.length;
if (roomIndex < 0) roomIndex = allRooms.length - 1;
this._viewRoom({ room_id: allRooms[roomIndex].roomId });
dis.dispatch({
action: 'view_room',
room_id: allRooms[roomIndex].roomId,
});
},
// TODO: Move to RoomViewStore
_viewIndexedRoom: function(roomIndex) {
const allRooms = RoomListSorter.mostRecentActivityFirst(
MatrixClientPeg.get().getRooms(),
);
if (allRooms[roomIndex]) {
this._viewRoom({ room_id: allRooms[roomIndex].roomId });
dis.dispatch({
action: 'view_room',
room_id: allRooms[roomIndex].roomId,
});
}
},
@ -590,6 +620,8 @@ module.exports = React.createClass({
// @param {boolean=} roomInfo.show_settings Makes RoomView show the room settings dialog.
// @param {string=} roomInfo.event_id ID of the event in this room to show: this will cause a switch to the
// context of that particular event.
// @param {boolean=} roomInfo.highlighted If true, add event_id to the hash of the URL
// and alter the EventTile to appear highlighted.
// @param {Object=} roomInfo.third_party_invite Object containing data about the third party
// we received to join the room, if any.
// @param {string=} roomInfo.third_party_invite.inviteSignUrl 3pid invite sign URL
@ -601,30 +633,21 @@ module.exports = React.createClass({
this.focusComposer = true;
const newState = {
initialEventId: roomInfo.event_id,
highlightedEventId: roomInfo.event_id,
initialEventPixelOffset: undefined,
page_type: PageTypes.RoomView,
thirdPartyInvite: roomInfo.third_party_invite,
roomOobData: roomInfo.oob_data,
currentRoomAlias: roomInfo.room_alias,
autoJoin: roomInfo.auto_join,
};
if (!roomInfo.room_alias) {
newState.currentRoomId = roomInfo.room_id;
}
// if we aren't given an explicit event id, look for one in the
// scrollStateMap.
//
// TODO: do this in RoomView rather than here
if (!roomInfo.event_id && this.refs.loggedInView) {
const scrollState = this.refs.loggedInView.getScrollStateForRoom(roomInfo.room_id);
if (scrollState) {
newState.initialEventId = scrollState.focussedEvent;
newState.initialEventPixelOffset = scrollState.pixelOffset;
}
if (roomInfo.room_alias) {
console.log(
`Switching to room alias ${roomInfo.room_alias} at event ` +
roomInfo.event_id,
);
} else {
console.log(`Switching to room id ${roomInfo.room_id} at event ` +
roomInfo.event_id,
);
}
// Wait for the first sync to complete so that if a room does have an alias,
@ -652,7 +675,7 @@ module.exports = React.createClass({
}
}
if (roomInfo.event_id) {
if (roomInfo.event_id && roomInfo.highlighted) {
presentedId += "/" + roomInfo.event_id;
}
this.notifyNewScreen('room/' + presentedId);
@ -661,7 +684,46 @@ module.exports = React.createClass({
});
},
_setMxId: function(payload) {
const SetMxIdDialog = sdk.getComponent('views.dialogs.SetMxIdDialog');
const close = Modal.createDialog(SetMxIdDialog, {
homeserverUrl: MatrixClientPeg.get().getHomeserverUrl(),
onFinished: (submitted, credentials) => {
if (!submitted) {
dis.dispatch({
action: 'cancel_after_sync_prepared',
});
if (payload.go_home_on_cancel) {
dis.dispatch({
action: 'view_home_page',
});
}
return;
}
this.onRegistered(credentials);
},
onDifferentServerClicked: (ev) => {
dis.dispatch({action: 'start_registration'});
close();
},
onLoginClick: (ev) => {
dis.dispatch({action: 'start_login'});
close();
},
}).close;
},
_createChat: function() {
if (MatrixClientPeg.get().isGuest()) {
dis.dispatch({
action: 'do_after_sync_prepared',
deferred_action: {
action: 'view_create_chat',
},
});
dis.dispatch({action: 'view_set_mxid'});
return;
}
const ChatInviteDialog = sdk.getComponent("dialogs.ChatInviteDialog");
Modal.createDialog(ChatInviteDialog, {
title: _t('Start a chat'),
@ -671,6 +733,86 @@ module.exports = React.createClass({
});
},
_createRoom: function() {
if (MatrixClientPeg.get().isGuest()) {
dis.dispatch({
action: 'do_after_sync_prepared',
deferred_action: {
action: 'view_create_room',
},
});
dis.dispatch({action: 'view_set_mxid'});
return;
}
const TextInputDialog = sdk.getComponent("dialogs.TextInputDialog");
Modal.createDialog(TextInputDialog, {
title: _t('Create Room'),
description: _t('Room name (optional)'),
button: _t('Create Room'),
onFinished: (should_create, name) => {
if (should_create) {
const createOpts = {};
if (name) createOpts.name = name;
createRoom({createOpts}).done();
}
},
});
},
_chatCreateOrReuse: function(userId) {
const ChatCreateOrReuseDialog = sdk.getComponent(
'views.dialogs.ChatCreateOrReuseDialog',
);
// Use a deferred action to reshow the dialog once the user has registered
if (MatrixClientPeg.get().isGuest()) {
// No point in making 2 DMs with welcome bot. This assumes view_set_mxid will
// result in a new DM with the welcome user.
if (userId !== this.props.config.welcomeUserId) {
dis.dispatch({
action: 'do_after_sync_prepared',
deferred_action: {
action: 'view_start_chat_or_reuse',
user_id: userId,
},
});
}
dis.dispatch({
action: 'view_set_mxid',
// If the set_mxid dialog is cancelled, view /home because if the browser
// was pointing at /user/@someone:domain?action=chat, the URL needs to be
// reset so that they can revisit /user/.. // (and trigger
// `_chatCreateOrReuse` again)
go_home_on_cancel: true,
});
return;
}
const close = Modal.createDialog(ChatCreateOrReuseDialog, {
userId: userId,
onFinished: (success) => {
if (!success) {
// Dialog cancelled, default to home
dis.dispatch({ action: 'view_home_page' });
}
},
onNewDMClick: () => {
dis.dispatch({
action: 'start_chat',
user_id: userId,
});
// Close the dialog, indicate success (calls onFinished(true))
close(true);
},
onExistingRoomSelected: (roomId) => {
dis.dispatch({
action: 'view_room',
room_id: roomId,
});
close(true);
},
}).close;
},
_invite: function(roomId) {
const ChatInviteDialog = sdk.getComponent("dialogs.ChatInviteDialog");
Modal.createDialog(ChatInviteDialog, {
@ -704,7 +846,7 @@ module.exports = React.createClass({
d.then(() => {
modal.close();
if (this.currentRoomId === roomId) {
if (this.state.currentRoomId === roomId) {
dis.dispatch({action: 'view_next_room'});
}
}, (err) => {
@ -799,12 +941,27 @@ module.exports = React.createClass({
this._teamToken = teamToken;
dis.dispatch({action: 'view_home_page'});
} else if (this._is_registered) {
this._is_registered = false;
// reset the 'have completed first sync' flag,
// since we've just logged in and will be about to sync
this.firstSyncComplete = false;
this.firstSyncPromise = q.defer();
// Set the display name = user ID localpart
MatrixClientPeg.get().setDisplayName(
MatrixClientPeg.get().getUserIdLocalpart(),
);
if (this.props.config.welcomeUserId && getCurrentLanguage().startsWith("en")) {
createRoom({dmUserId: this.props.config.welcomeUserId});
createRoom({
dmUserId: this.props.config.welcomeUserId,
// Only view the welcome user if we're NOT looking at a room
andView: !this.state.currentRoomId,
});
return;
}
// The user has just logged in after registering
dis.dispatch({action: 'view_room_directory'});
dis.dispatch({action: 'view_home_page'});
} else {
this._showScreenAfterLogin();
}
@ -826,12 +983,8 @@ module.exports = React.createClass({
action: 'view_room',
room_id: localStorage.getItem('mx_last_room_id'),
});
} else if (this._teamToken) {
// Team token might be set if we're a guest.
// Guests do not call _onLoggedIn with a teamToken
dis.dispatch({action: 'view_home_page'});
} else {
dis.dispatch({action: 'view_room_directory'});
dis.dispatch({action: 'view_home_page'});
}
},
@ -845,7 +998,6 @@ module.exports = React.createClass({
ready: false,
collapse_lhs: false,
collapse_rhs: false,
currentRoomAlias: null,
currentRoomId: null,
page_type: PageTypes.RoomDirectory,
});
@ -883,6 +1035,12 @@ module.exports = React.createClass({
});
cli.on('sync', function(state, prevState) {
// LifecycleStore and others cannot directly subscribe to matrix client for
// events because flux only allows store state changes during flux dispatches.
// So dispatch directly from here. Ideally we'd use a SyncStateStore that
// would do this dispatch and expose the sync state itself (by listening to
// its own dispatch).
dis.dispatch({action: 'sync_state', prevState, state});
self.updateStatusIndicator(state, prevState);
if (state === "SYNCING" && prevState === "SYNCING") {
return;
@ -952,6 +1110,11 @@ module.exports = React.createClass({
dis.dispatch({
action: 'view_home_page',
});
} else if (screen == 'start') {
this.showScreen('home');
dis.dispatch({
action: 'view_set_mxid',
});
} else if (screen == 'directory') {
dis.dispatch({
action: 'view_room_directory',
@ -979,6 +1142,10 @@ module.exports = React.createClass({
const payload = {
action: 'view_room',
event_id: eventId,
// If an event ID is given in the URL hash, notify RoomViewStore to mark
// it as highlighted, which will propagate to RoomView and highlight the
// associated EventTile.
highlighted: Boolean(eventId),
third_party_invite: thirdPartyInvite,
oob_data: oobData,
};
@ -995,6 +1162,12 @@ module.exports = React.createClass({
}
} else if (screen.indexOf('user/') == 0) {
const userId = screen.substring(5);
if (params.action === 'chat') {
this._chatCreateOrReuse(userId);
return;
}
this.setState({ viewUserId: userId });
this._setPage(PageTypes.UserView);
this.notifyNewScreen('user/' + userId);
@ -1091,6 +1264,8 @@ module.exports = React.createClass({
},
onRegistered: function(credentials, teamToken) {
// XXX: These both should be in state or ideally store(s) because we risk not
// rendering the most up-to-date view of state otherwise.
// teamToken may not be truthy
this._teamToken = teamToken;
this._is_registered = true;
@ -1135,7 +1310,15 @@ module.exports = React.createClass({
PlatformPeg.get().setNotificationCount(notifCount);
}
document.title = `Riot ${state === "ERROR" ? " [offline]" : ""}${notifCount > 0 ? ` [${notifCount}]` : ""}`;
let title = "Riot ";
if (state === "ERROR") {
title += `[${_t("Offline")}] `;
}
if (notifCount > 0) {
title += `[${notifCount}]`;
}
document.title = title;
},
onUserSettingsClose: function() {
@ -1148,18 +1331,11 @@ module.exports = React.createClass({
});
} else {
dis.dispatch({
action: 'view_room_directory',
action: 'view_home_page',
});
}
},
onRoomIdResolved: function(roomId) {
// It's the RoomView's resposibility to look up room aliases, but we need the
// ID to pass into things like the Member List, so the Room View tells us when
// its done that resolution so we can display things that take a room ID.
this.setState({currentRoomId: roomId});
},
_makeRegistrationUrl: function(params) {
if (this.props.startingFragmentQueryParams.referrer) {
params.referrer = this.props.startingFragmentQueryParams.referrer;
@ -1201,9 +1377,10 @@ module.exports = React.createClass({
const LoggedInView = sdk.getComponent('structures.LoggedInView');
return (
<LoggedInView ref="loggedInView" matrixClient={MatrixClientPeg.get()}
onRoomIdResolved={this.onRoomIdResolved}
onRoomCreated={this.onRoomCreated}
onUserSettingsClose={this.onUserSettingsClose}
onRegistered={this.onRegistered}
currentRoomId={this.state.currentRoomId}
teamToken={this._teamToken}
{...this.props}
{...this.state}

View file

@ -15,9 +15,8 @@ limitations under the License.
*/
import React from 'react';
import { _t } from '../../languageHandler';
import { _t, _tJsx } from '../../languageHandler';
import sdk from '../../index';
import dis from '../../dispatcher';
import WhoIsTyping from '../../WhoIsTyping';
import MatrixClientPeg from '../../MatrixClientPeg';
import MemberAvatar from '../views/avatars/MemberAvatar';
@ -282,14 +281,13 @@ module.exports = React.createClass({
{ this.props.unsentMessageError }
</div>
<div className="mx_RoomStatusBar_connectionLostBar_desc">
<a className="mx_RoomStatusBar_resend_link"
onClick={ this.props.onResendAllClick }>
{_t('Resend all')}
</a> {_t('or')} <a
className="mx_RoomStatusBar_resend_link"
onClick={ this.props.onCancelAllClick }>
{_t('cancel all')}
</a> {_t('now. You can also select individual messages to resend or cancel.')}
{_tJsx("<a>Resend all</a> or <a>cancel all</a> now. You can also select individual messages to resend or cancel.",
[/<a>(.*?)<\/a>/, /<a>(.*?)<\/a>/],
[
(sub) => <a className="mx_RoomStatusBar_resend_link" key="resend" onClick={ this.props.onResendAllClick }>{sub}</a>,
(sub) => <a className="mx_RoomStatusBar_resend_link" key="cancel" onClick={ this.props.onCancelAllClick }>{sub}</a>,
]
)}
</div>
</div>
);
@ -298,8 +296,8 @@ module.exports = React.createClass({
// unread count trumps who is typing since the unread count is only
// set when you've scrolled up
if (this.props.numUnreadMessages) {
var unreadMsgs = this.props.numUnreadMessages + " new message" +
(this.props.numUnreadMessages > 1 ? "s" : "");
// MUST use var name "count" for pluralization to kick in
var unreadMsgs = _t("%(count)s new messages", {count: this.props.numUnreadMessages});
return (
<div className="mx_RoomStatusBar_unreadMessagesBar"

View file

@ -45,6 +45,8 @@ import KeyCode from '../../KeyCode';
import UserProvider from '../../autocomplete/UserProvider';
import RoomViewStore from '../../stores/RoomViewStore';
var DEBUG = false;
if (DEBUG) {
@ -59,16 +61,9 @@ module.exports = React.createClass({
propTypes: {
ConferenceHandler: React.PropTypes.any,
// Either a room ID or room alias for the room to display.
// If the room is being displayed as a result of the user clicking
// on a room alias, the alias should be supplied. Otherwise, a room
// ID should be supplied.
roomAddress: React.PropTypes.string.isRequired,
// If a room alias is passed to roomAddress, a function can be
// provided here that will be called with the ID of the room
// once it has been resolved.
onRoomIdResolved: React.PropTypes.func,
// Called with the credentials of a registered user (if they were a ROU that
// transitioned to PWLU)
onRegistered: React.PropTypes.func,
// An object representing a third party invite to join this room
// Fields:
@ -88,36 +83,8 @@ module.exports = React.createClass({
// * invited us tovthe room
oobData: React.PropTypes.object,
// id of an event to jump to. If not given, will go to the end of the
// live timeline.
eventId: React.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
// 1/3 of the way down the viewport.
eventPixelOffset: React.PropTypes.number,
// ID of an event to highlight. If undefined, no event will be highlighted.
// Typically this will either be the same as 'eventId', or undefined.
highlightedEventId: React.PropTypes.string,
// is the RightPanel collapsed?
collapsedRhs: React.PropTypes.bool,
// a map from room id to scroll state, which will be updated on unmount.
//
// If there is no special scroll state (ie, we are following the live
// timeline), the scroll state is null. Otherwise, it is an object with
// the following properties:
//
// focussedEvent: the ID of the 'focussed' event. Typically this is
// the last event fully visible in the viewport, though if we
// have done an explicit scroll to an explicit event, it will be
// that event.
//
// pixelOffset: the number of pixels the window is scrolled down
// from the focussedEvent.
scrollStateMap: React.PropTypes.object,
},
getInitialState: function() {
@ -125,6 +92,14 @@ module.exports = React.createClass({
room: null,
roomId: null,
roomLoading: true,
peekLoading: false,
// The event to be scrolled to initially
initialEventId: null,
// The offset in pixels from the event with which to scroll vertically
initialEventPixelOffset: null,
// Whether to highlight the event scrolled to
isInitialEventHighlighted: null,
forwardingEvent: null,
editingRoomSettings: false,
@ -172,40 +147,47 @@ module.exports = React.createClass({
onClickCompletes: true,
onStateChange: (isCompleting) => {
this.forceUpdate();
}
},
});
if (this.props.roomAddress[0] == '#') {
// we always look up the alias from the directory server:
// we want the room that the given alias is pointing to
// right now. We may have joined that alias before but there's
// no guarantee the alias hasn't subsequently been remapped.
MatrixClientPeg.get().getRoomIdForAlias(this.props.roomAddress).done((result) => {
if (this.props.onRoomIdResolved) {
this.props.onRoomIdResolved(result.room_id);
}
var room = MatrixClientPeg.get().getRoom(result.room_id);
this.setState({
room: room,
roomId: result.room_id,
roomLoading: !room,
unsentMessageError: this._getUnsentMessageError(room),
}, this._onHaveRoom);
}, (err) => {
this.setState({
roomLoading: false,
roomLoadError: err,
});
});
} else {
var room = MatrixClientPeg.get().getRoom(this.props.roomAddress);
this.setState({
roomId: this.props.roomAddress,
room: room,
roomLoading: !room,
unsentMessageError: this._getUnsentMessageError(room),
}, this._onHaveRoom);
// Start listening for RoomViewStore updates
this._roomStoreToken = RoomViewStore.addListener(this._onRoomViewStoreUpdate);
this._onRoomViewStoreUpdate(true);
},
_onRoomViewStoreUpdate: function(initial) {
if (this.unmounted) {
return;
}
const newState = {
roomId: RoomViewStore.getRoomId(),
roomAlias: RoomViewStore.getRoomAlias(),
roomLoading: RoomViewStore.isRoomLoading(),
roomLoadError: RoomViewStore.getRoomLoadError(),
joining: RoomViewStore.isJoining(),
initialEventId: RoomViewStore.getInitialEventId(),
initialEventPixelOffset: RoomViewStore.getInitialEventPixelOffset(),
isInitialEventHighlighted: RoomViewStore.isInitialEventHighlighted(),
};
// Clear the search results when clicking a search result (which changes the
// currently scrolled to event, this.state.initialEventId).
if (this.state.initialEventId !== newState.initialEventId) {
newState.searchResults = null;
}
// Store the scroll state for the previous room so that we can return to this
// position when viewing this room in future.
if (this.state.roomId !== newState.roomId) {
this._updateScrollMap(this.state.roomId);
}
this.setState(newState, () => {
// At this point, this.state.roomId could be null (e.g. the alias might not
// have been resolved yet) so anything called here must handle this case.
this._onHaveRoom();
this.onRoom(MatrixClientPeg.get().getRoom(this.state.roomId));
});
},
_onHaveRoom: function() {
@ -223,26 +205,29 @@ module.exports = React.createClass({
// NB. We peek if we are not in the room, although if we try to peek into
// a room in which we have a member event (ie. we've left) synapse will just
// send us the same data as we get in the sync (ie. the last events we saw).
var user_is_in_room = null;
if (this.state.room) {
user_is_in_room = this.state.room.hasMembershipState(
MatrixClientPeg.get().credentials.userId, 'join'
const room = MatrixClientPeg.get().getRoom(this.state.roomId);
let isUserJoined = null;
if (room) {
isUserJoined = room.hasMembershipState(
MatrixClientPeg.get().credentials.userId, 'join',
);
this._updateAutoComplete();
this.tabComplete.loadEntries(this.state.room);
this._updateAutoComplete(room);
this.tabComplete.loadEntries(room);
}
if (!user_is_in_room && this.state.roomId) {
if (!isUserJoined && !this.state.joining && this.state.roomId) {
if (this.props.autoJoin) {
this.onJoinButtonClicked();
} else if (this.state.roomId) {
console.log("Attempting to peek into room %s", this.state.roomId);
this.setState({
peekLoading: true,
});
MatrixClientPeg.get().peekInRoom(this.state.roomId).then((room) => {
this.setState({
room: room,
roomLoading: false,
peekLoading: false,
});
this._onRoomLoaded(room);
}, (err) => {
@ -252,16 +237,19 @@ module.exports = React.createClass({
if (err.errcode == "M_GUEST_ACCESS_FORBIDDEN") {
// This is fine: the room just isn't peekable (we assume).
this.setState({
roomLoading: false,
peekLoading: false,
});
} else {
throw err;
}
}).done();
}
} else if (user_is_in_room) {
} else if (isUserJoined) {
MatrixClientPeg.get().stopPeeking();
this._onRoomLoaded(this.state.room);
this.setState({
unsentMessageError: this._getUnsentMessageError(room),
});
this._onRoomLoaded(room);
}
},
@ -297,17 +285,6 @@ module.exports = React.createClass({
}
},
componentWillReceiveProps: function(newProps) {
if (newProps.roomAddress != this.props.roomAddress) {
throw new Error(_t("changing room on a RoomView is not supported"));
}
if (newProps.eventId != this.props.eventId) {
// when we change focussed event id, hide the search results.
this.setState({searchResults: null});
}
},
shouldComponentUpdate: function(nextProps, nextState) {
return (!ObjectUtils.shallowEqual(this.props, nextProps) ||
!ObjectUtils.shallowEqual(this.state, nextState));
@ -333,7 +310,7 @@ module.exports = React.createClass({
this.unmounted = true;
// update the scroll map before we get unmounted
this._updateScrollMap();
this._updateScrollMap(this.state.roomId);
if (this.refs.roomView) {
// disconnect the D&D event listeners from the room view. This
@ -362,6 +339,11 @@ module.exports = React.createClass({
document.removeEventListener("keydown", this.onKeyDown);
// Remove RoomStore listener
if (this._roomStoreToken) {
this._roomStoreToken.remove();
}
// cancel any pending calls to the rate_limited_funcs
this._updateRoomMembers.cancelPendingCall();
@ -527,7 +509,7 @@ module.exports = React.createClass({
this._updatePreviewUrlVisibility(room);
},
_warnAboutEncryption: function (room) {
_warnAboutEncryption: function(room) {
if (!MatrixClientPeg.get().isRoomEncrypted(room.roomId)) {
return;
}
@ -607,21 +589,27 @@ module.exports = React.createClass({
});
},
onRoom: function(room) {
// This event is fired when the room is 'stored' by the JS SDK, which
// means it's now a fully-fledged room object ready to be used, so
// set it in our state and start using it (ie. init the timeline)
// This will happen if we start off viewing a room we're not joined,
// then join it whilst RoomView is looking at that room.
if (!this.state.room && room.roomId == this._joiningRoomId) {
this._joiningRoomId = undefined;
this.setState({
room: room,
joining: false,
});
this._onRoomLoaded(room);
_updateScrollMap(roomId) {
// No point updating scroll state if the room ID hasn't been resolved yet
if (!roomId) {
return;
}
dis.dispatch({
action: 'update_scroll_state',
room_id: roomId,
scroll_state: this._getScrollState(),
});
},
onRoom: function(room) {
if (!room || room.roomId !== this.state.roomId) {
return;
}
this.setState({
room: room,
}, () => {
this._onRoomLoaded(room);
});
},
updateTint: function() {
@ -687,7 +675,7 @@ module.exports = React.createClass({
// refresh the tab complete list
this.tabComplete.loadEntries(this.state.room);
this._updateAutoComplete();
this._updateAutoComplete(this.state.room);
// 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
@ -704,10 +692,6 @@ module.exports = React.createClass({
// compatability workaround, let's not bother.
Rooms.setDMRoom(this.state.room.roomId, me.events.member.getSender()).done();
}
this.setState({
joining: false
});
}
}, 500),
@ -716,7 +700,7 @@ module.exports = React.createClass({
if (!unsentMessages.length) return "";
for (const event of unsentMessages) {
if (!event.error || event.error.name !== "UnknownDeviceError") {
return _t("Some of your messages have not been sent") + ".";
return _t("Some of your messages have not been sent.");
}
}
return _t("Message not sent due to unknown devices being present");
@ -782,41 +766,62 @@ module.exports = React.createClass({
},
onJoinButtonClicked: function(ev) {
var self = this;
const cli = MatrixClientPeg.get();
var cli = MatrixClientPeg.get();
var display_name_promise = q();
// if this is the first room we're joining, check the user has a display name
// and if they don't, prompt them to set one.
// NB. This unfortunately does not re-use the ChangeDisplayName component because
// it doesn't behave quite as desired here (we want an input field here rather than
// content-editable, and we want a default).
if (cli.getRooms().filter((r) => {
return r.hasMembershipState(cli.credentials.userId, "join");
})) {
display_name_promise = cli.getProfileInfo(cli.credentials.userId).then((result) => {
if (!result.displayname) {
var SetDisplayNameDialog = sdk.getComponent('views.dialogs.SetDisplayNameDialog');
var dialog_defer = q.defer();
Modal.createDialog(SetDisplayNameDialog, {
currentDisplayName: result.displayname,
onFinished: (submitted, newDisplayName) => {
if (submitted) {
cli.setDisplayName(newDisplayName).done(() => {
dialog_defer.resolve();
});
}
else {
dialog_defer.reject();
}
}
});
return dialog_defer.promise;
}
// If the user is a ROU, allow them to transition to a PWLU
if (cli && cli.isGuest()) {
// Join this room once the user has registered and logged in
const signUrl = this.props.thirdPartyInvite ?
this.props.thirdPartyInvite.inviteSignUrl : undefined;
dis.dispatch({
action: 'do_after_sync_prepared',
deferred_action: {
action: 'join_room',
opts: { inviteSignUrl: signUrl },
},
});
// Don't peek whilst registering otherwise getPendingEventList complains
// Do this by indicating our intention to join
dis.dispatch({
action: 'will_join',
});
const SetMxIdDialog = sdk.getComponent('views.dialogs.SetMxIdDialog');
const close = Modal.createDialog(SetMxIdDialog, {
homeserverUrl: cli.getHomeserverUrl(),
onFinished: (submitted, credentials) => {
if (submitted) {
this.props.onRegistered(credentials);
} else {
dis.dispatch({
action: 'cancel_after_sync_prepared',
});
dis.dispatch({
action: 'cancel_join',
});
}
},
onDifferentServerClicked: (ev) => {
dis.dispatch({action: 'start_registration'});
close();
},
onLoginClick: (ev) => {
dis.dispatch({action: 'start_login'});
close();
},
}).close;
return;
}
display_name_promise.then(() => {
q().then(() => {
const signUrl = this.props.thirdPartyInvite ?
this.props.thirdPartyInvite.inviteSignUrl : undefined;
dis.dispatch({
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);
@ -828,72 +833,7 @@ module.exports = React.createClass({
}
}
}
return q();
}).then(() => {
var sign_url = this.props.thirdPartyInvite ? this.props.thirdPartyInvite.inviteSignUrl : undefined;
return MatrixClientPeg.get().joinRoom(this.props.roomAddress,
{ inviteSignUrl: sign_url } );
}).then(function(resp) {
var roomId = resp.roomId;
// It is possible that there is no Room yet if state hasn't come down
// from /sync - joinRoom will resolve when the HTTP request to join succeeds,
// NOT when it comes down /sync. If there is no room, we'll keep the
// joining flag set until we see it.
// We'll need to initialise the timeline when joining, but due to
// the above, we can't do it here: we do it in onRoom instead,
// once we have a useable room object.
var room = MatrixClientPeg.get().getRoom(roomId);
if (!room) {
// wait for the room to turn up in onRoom.
self._joiningRoomId = roomId;
} else {
// we've got a valid room, but that might also just mean that
// it was peekable (so we had one before anyway). If we are
// not yet a member of the room, we will need to wait for that
// to happen, in onRoomStateMember.
var me = MatrixClientPeg.get().credentials.userId;
self.setState({
joining: !room.hasMembershipState(me, "join"),
room: room
});
}
}).catch(function(error) {
self.setState({
joining: false,
joinError: error
});
if (!error) return;
// https://matrix.org/jira/browse/SYN-659
// Need specific error message if joining a room is refused because the user is a guest and guest access is not allowed
if (
error.errcode == 'M_GUEST_ACCESS_FORBIDDEN' ||
(
error.errcode == 'M_FORBIDDEN' &&
MatrixClientPeg.get().isGuest()
)
) {
var NeedToRegisterDialog = sdk.getComponent("dialogs.NeedToRegisterDialog");
Modal.createDialog(NeedToRegisterDialog, {
title: _t("Failed to join the room"),
description: _t("This room is private or inaccessible to guests. You may be able to join if you register") + "."
});
} else {
var msg = error.message ? error.message : JSON.stringify(error);
var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
Modal.createDialog(ErrorDialog, {
title: _t("Failed to join room"),
description: msg,
});
}
}).done();
this.setState({
joining: true
});
},
@ -945,11 +885,7 @@ module.exports = React.createClass({
uploadFile: function(file) {
if (MatrixClientPeg.get().isGuest()) {
var NeedToRegisterDialog = sdk.getComponent("dialogs.NeedToRegisterDialog");
Modal.createDialog(NeedToRegisterDialog, {
title: _t("Please Register"),
description: _t("Guest users can't upload files. Please register to upload") + "."
});
dis.dispatch({action: 'view_set_mxid'});
return;
}
@ -1307,21 +1243,6 @@ module.exports = React.createClass({
}
},
// update scrollStateMap on unmount
_updateScrollMap: function() {
if (!this.state.room) {
// we were instantiated on a room alias and haven't yet joined the room.
return;
}
if (!this.props.scrollStateMap) return;
var roomId = this.state.room.roomId;
var state = this._getScrollState();
this.props.scrollStateMap[roomId] = state;
},
// get the current scroll position of the room, so that it can be
// restored when we switch back to it.
//
@ -1474,9 +1395,9 @@ module.exports = React.createClass({
}
},
_updateAutoComplete: function() {
_updateAutoComplete: function(room) {
const myUserId = MatrixClientPeg.get().credentials.userId;
const members = this.state.room.getJoinedMembers().filter(function(member) {
const members = room.getJoinedMembers().filter(function(member) {
if (member.userId !== myUserId) return true;
});
UserProvider.getInstance().setUserList(members);
@ -1496,7 +1417,7 @@ module.exports = React.createClass({
const TimelinePanel = sdk.getComponent("structures.TimelinePanel");
if (!this.state.room) {
if (this.state.roomLoading) {
if (this.state.roomLoading || this.state.peekLoading) {
return (
<div className="mx_RoomView">
<Loader />
@ -1514,7 +1435,7 @@ module.exports = React.createClass({
// We have no room object for this room, only the ID.
// We've got to this room by following a link, possibly a third party invite.
var room_alias = this.props.roomAddress[0] == '#' ? this.props.roomAddress : null;
var room_alias = this.state.room_alias;
return (
<div className="mx_RoomView">
<RoomHeader ref="header"
@ -1744,6 +1665,14 @@ module.exports = React.createClass({
hideMessagePanel = true;
}
const shouldHighlight = this.state.isInitialEventHighlighted;
let highlightedEventId = null;
if (this.state.forwardingEvent) {
highlightedEventId = this.state.forwardingEvent.getId();
} else if (shouldHighlight) {
highlightedEventId = this.state.initialEventId;
}
// console.log("ShowUrlPreview for %s is %s", this.state.room.roomId, this.state.showUrlPreview);
var messagePanel = (
<TimelinePanel ref={this._gatherTimelinePanelRef}
@ -1751,9 +1680,9 @@ module.exports = React.createClass({
manageReadReceipts={!UserSettingsStore.getSyncedSetting('hideReadReceipts', false)}
manageReadMarkers={true}
hidden={hideMessagePanel}
highlightedEventId={this.state.forwardingEvent ? this.state.forwardingEvent.getId() : this.props.highlightedEventId}
eventId={this.props.eventId}
eventPixelOffset={this.props.eventPixelOffset}
highlightedEventId={highlightedEventId}
eventId={this.state.initialEventId}
eventPixelOffset={this.state.initialEventPixelOffset}
onScroll={ this.onMessageListScroll }
onReadMarkerUpdated={ this._updateTopUnreadMessagesBar }
showUrlPreview = { this.state.showUrlPreview }

View file

@ -902,6 +902,9 @@ var TimelinePanel = React.createClass({
var onError = (error) => {
this.setState({timelineLoading: false});
console.error(
`Error loading timeline panel at ${eventId}: ${error}`,
);
var msg = error.message ? error.message : JSON.stringify(error);
var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
@ -921,8 +924,8 @@ var TimelinePanel = React.createClass({
};
}
var message = (error.errcode == 'M_FORBIDDEN')
? _t("Tried to load a specific point in this room's timeline, but you do not have permission to view the message in question") + "."
: _t("Tried to load a specific point in this room's timeline, but was unable to find it") + ".";
? _t("Tried to load a specific point in this room's timeline, but you do not have permission to view the message in question.")
: _t("Tried to load a specific point in this room's timeline, but was unable to find it.");
Modal.createDialog(ErrorDialog, {
title: _t("Failed to load timeline position"),
description: message,

View file

@ -18,6 +18,7 @@ var React = require('react');
var ContentMessages = require('../../ContentMessages');
var dis = require('../../dispatcher');
var filesize = require('filesize');
import { _t } from '../../languageHandler';
module.exports = React.createClass({displayName: 'UploadBar',
propTypes: {
@ -81,10 +82,8 @@ module.exports = React.createClass({displayName: 'UploadBar',
uploadedSize = uploadedSize.replace(/ .*/, '');
}
var others;
if (uploads.length > 1) {
others = ' and ' + (uploads.length - 1) + ' other' + (uploads.length > 2 ? 's' : '');
}
// MUST use var name 'count' for pluralization to kick in
var uploadText = _t("Uploading %(filename)s and %(count)s others", {filename: upload.fileName, count: (uploads.length - 1)});
return (
<div className="mx_UploadBar">
@ -98,7 +97,7 @@ module.exports = React.createClass({displayName: 'UploadBar',
<div className="mx_UploadBar_uploadBytes">
{ uploadedSize } / { totalSize }
</div>
<div className="mx_UploadBar_uploadFilename">Uploading {upload.fileName}{others}</div>
<div className="mx_UploadBar_uploadFilename">{uploadText}</div>
</div>
);
}

View file

@ -88,6 +88,10 @@ const SETTINGS_LABELS = [
id: 'hideRedactions',
label: 'Hide removed messages',
},
{
id: 'disableMarkdown',
label: 'Disable markdown formatting',
},
/*
{
id: 'useFixedWidthFont',
@ -307,11 +311,7 @@ module.exports = React.createClass({
onAvatarPickerClick: function(ev) {
if (MatrixClientPeg.get().isGuest()) {
const NeedToRegisterDialog = sdk.getComponent("dialogs.NeedToRegisterDialog");
Modal.createDialog(NeedToRegisterDialog, {
title: _t("Please Register"),
description: _t("Guests can't set avatars. Please register."),
});
dis.dispatch({action: 'view_set_mxid'});
return;
}
@ -391,6 +391,7 @@ module.exports = React.createClass({
title: _t("Success"),
description: _t("Your password was successfully changed. You will not receive push notifications on other devices until you log back in to them") + ".",
});
dis.dispatch({action: 'password_changed'});
},
onUpgradeClicked: function() {
@ -753,7 +754,7 @@ module.exports = React.createClass({
const DevicesPanel = sdk.getComponent('settings.DevicesPanel');
return (
<div>
<h3>Devices</h3>
<h3>{_t("Devices")}</h3>
<DevicesPanel className="mx_UserSettings_section"/>
</div>
);
@ -803,11 +804,7 @@ module.exports = React.createClass({
onChange={(e) => {
if (MatrixClientPeg.get().isGuest()) {
e.target.checked = false;
const NeedToRegisterDialog = sdk.getComponent("dialogs.NeedToRegisterDialog");
Modal.createDialog(NeedToRegisterDialog, {
title: _t("Please Register"),
description: _t("Guests can't use labs features. Please register."),
});
dis.dispatch({action: 'view_set_mxid'});
return;
}
@ -1112,7 +1109,7 @@ module.exports = React.createClass({
onValueChanged={ this._onAddEmailEditFinished } />
</div>
<div className="mx_UserSettings_threepidButton mx_filterFlipColor">
<img src="img/plus.svg" width="14" height="14" alt="Add" onClick={this._addEmail} />
<img src="img/plus.svg" width="14" height="14" alt={_t("Add")} onClick={this._addEmail} />
</div>
</div>
);

View file

@ -19,7 +19,6 @@ limitations under the License.
import React from 'react';
import { _t, _tJsx } from '../../../languageHandler';
import ReactDOM from 'react-dom';
import sdk from '../../../index';
import Login from '../../../Login';
@ -88,7 +87,27 @@ module.exports = React.createClass({
).then((data) => {
this.props.onLoggedIn(data);
}, (error) => {
this._setStateFromError(error, true);
let errorText;
// Some error strings only apply for logging in
const usingEmail = username.indexOf("@") > 0;
if (error.httpStatus == 400 && usingEmail) {
errorText = _t('This Home Server does not support login using email address.');
} else if (error.httpStatus === 401 || error.httpStatus === 403) {
errorText = _t('Incorrect username and/or password.');
} else {
// other errors, not specific to doing a password login
errorText = this._errorTextFromError(error);
}
this.setState({
errorText: errorText,
// 401 would be the sensible status code for 'incorrect password'
// but the login API gives a 403 https://matrix.org/jira/browse/SYN-744
// mentions this (although the bug is for UI auth which is not this)
// We treat both as an incorrect password
loginIncorrect: error.httpStatus === 401 || error.httpStatus == 403,
});
}).finally(() => {
this.setState({
busy: false
@ -111,7 +130,16 @@ module.exports = React.createClass({
this._loginLogic.loginAsGuest().then(function(data) {
self.props.onLoggedIn(data);
}, function(error) {
self._setStateFromError(error, true);
let errorText;
if (error.httpStatus === 403) {
errorText = _t("Guest access is disabled on this Home Server.");
} else {
errorText = self._errorTextFromError(error);
}
self.setState({
errorText: errorText,
loginIncorrect: false,
});
}).finally(function() {
self.setState({
busy: false
@ -130,7 +158,7 @@ module.exports = React.createClass({
onPhoneNumberChanged: function(phoneNumber) {
// Validate the phone number entered
if (!PHONE_NUMBER_REGEX.test(phoneNumber)) {
this.setState({ errorText: 'The phone number entered looks invalid' });
this.setState({ errorText: _t('The phone number entered looks invalid') });
return;
}
@ -184,44 +212,35 @@ module.exports = React.createClass({
currentFlow: self._getCurrentFlowStep(),
});
}, function(err) {
self._setStateFromError(err, false);
self.setState({
errorText: self._errorTextFromError(err),
loginIncorrect: false,
});
}).finally(function() {
self.setState({
busy: false,
});
});
}).done();
},
_getCurrentFlowStep: function() {
return this._loginLogic ? this._loginLogic.getCurrentFlowStep() : null;
},
_setStateFromError: function(err, isLoginAttempt) {
this.setState({
errorText: this._errorTextFromError(err),
// https://matrix.org/jira/browse/SYN-744
loginIncorrect: isLoginAttempt && (err.httpStatus == 401 || err.httpStatus == 403)
});
},
_errorTextFromError(err) {
if (err.friendlyText) {
return err.friendlyText;
}
let errCode = err.errcode;
if (!errCode && err.httpStatus) {
errCode = "HTTP " + err.httpStatus;
}
let errorText = "Error: Problem communicating with the given homeserver " +
(errCode ? "(" + errCode + ")" : "");
let errorText = _t("Error: Problem communicating with the given homeserver.") +
(errCode ? " (" + errCode + ")" : "");
if (err.cors === 'rejected') {
if (window.location.protocol === 'https:' &&
(this.state.enteredHomeserverUrl.startsWith("http:") ||
!this.state.enteredHomeserverUrl.startsWith("http")))
{
!this.state.enteredHomeserverUrl.startsWith("http"))
) {
errorText = <span>
{ _tJsx("Can't connect to homeserver via HTTP when an HTTPS URL is in your browser bar. " +
"Either use HTTPS or <a>enable unsafe scripts</a>.",
@ -229,10 +248,9 @@ module.exports = React.createClass({
(sub) => { return <a href="https://www.google.com/search?&q=enable%20unsafe%20scripts">{ sub }</a>; }
)}
</span>;
}
else {
} else {
errorText = <span>
{ _tJsx("Can't connect to homeserver - please check your connectivity and ensure your <a>homeserver's SSL certificate</a> is trusted.",
{ _tJsx("Can't connect to homeserver - please check your connectivity, ensure your <a>homeserver's SSL certificate</a> is trusted, and that a browser extension is not blocking requests.",
/<a>(.*?)<\/a>/,
(sub) => { return <a href={this.state.enteredHomeserverUrl}>{ sub }</a>; }
)}

View file

@ -50,7 +50,7 @@ module.exports = React.createClass({
});
}, function(error) {
self.setState({
errorString: "Failed to fetch avatar URL",
errorString: _t("Failed to fetch avatar URL"),
busy: false
});
});

View file

@ -21,11 +21,9 @@ import q from 'q';
import React from 'react';
import sdk from '../../../index';
import dis from '../../../dispatcher';
import ServerConfig from '../../views/login/ServerConfig';
import MatrixClientPeg from '../../../MatrixClientPeg';
import RegistrationForm from '../../views/login/RegistrationForm';
import CaptchaForm from '../../views/login/CaptchaForm';
import RtsClient from '../../../RtsClient';
import { _t } from '../../../languageHandler';
@ -99,7 +97,7 @@ module.exports = React.createClass({
this.props.teamServerConfig.teamServerURL &&
!this._rtsClient
) {
this._rtsClient = new RtsClient(this.props.teamServerConfig.teamServerURL);
this._rtsClient = this.props.rtsClient || new RtsClient(this.props.teamServerConfig.teamServerURL);
this.setState({
teamServerBusy: true,
@ -222,7 +220,6 @@ module.exports = React.createClass({
}
trackPromise.then((teamToken) => {
console.info('Team token promise',teamToken);
this.props.onLoggedIn({
userId: response.user_id,
deviceId: response.device_id,