Merge remote-tracking branch 'remotes/origin/develop' into set_default_federate_by_settings
# Conflicts: # src/components/structures/MatrixChat.js
This commit is contained in:
commit
bdb2d6b475
93 changed files with 5888 additions and 1285 deletions
|
@ -266,7 +266,7 @@ export default React.createClass({
|
|||
this.setState({uploadingAvatar: false});
|
||||
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
||||
console.error("Failed to upload avatar image", e);
|
||||
Modal.createDialog(ErrorDialog, {
|
||||
Modal.createTrackedDialog('Failed to upload image', '', ErrorDialog, {
|
||||
title: _t('Error'),
|
||||
description: _t('Failed to upload image'),
|
||||
});
|
||||
|
@ -288,7 +288,7 @@ export default React.createClass({
|
|||
});
|
||||
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
||||
console.error("Failed to save group profile", e);
|
||||
Modal.createDialog(ErrorDialog, {
|
||||
Modal.createTrackedDialog('Failed to update group', '', ErrorDialog, {
|
||||
title: _t('Error'),
|
||||
description: _t('Failed to update group'),
|
||||
});
|
||||
|
|
|
@ -301,13 +301,13 @@ export default React.createClass({
|
|||
|
||||
case PageTypes.UserView:
|
||||
page_element = null; // deliberately null for now
|
||||
right_panel = <RightPanel userId={this.props.viewUserId} opacity={this.props.rightOpacity} />;
|
||||
right_panel = <RightPanel opacity={this.props.rightOpacity} />;
|
||||
break;
|
||||
case PageTypes.GroupView:
|
||||
page_element = <GroupView
|
||||
groupId={this.props.currentGroupId}
|
||||
/>;
|
||||
//right_panel = <RightPanel userId={this.props.viewUserId} opacity={this.props.rightOpacity} />;
|
||||
//right_panel = <RightPanel opacity={this.props.rightOpacity} />;
|
||||
break;
|
||||
}
|
||||
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
/*
|
||||
Copyright 2015, 2016 OpenMarket Ltd
|
||||
Copyright 2017 Vector Creations Ltd
|
||||
Copyright 2017 New Vector Ltd
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
|
@ -31,6 +32,7 @@ import dis from "../../dispatcher";
|
|||
import Modal from "../../Modal";
|
||||
import Tinter from "../../Tinter";
|
||||
import sdk from '../../index';
|
||||
import { showStartChatInviteDialog, showRoomInviteDialog } from '../../Invite';
|
||||
import * as Rooms from '../../Rooms';
|
||||
import linkifyMatrix from "../../linkify-matrix";
|
||||
import * as Lifecycle from '../../Lifecycle';
|
||||
|
@ -131,9 +133,6 @@ module.exports = React.createClass({
|
|||
// the master view we are showing.
|
||||
view: VIEWS.LOADING,
|
||||
|
||||
// a thing to call showScreen with once login completes.
|
||||
screenAfterLogin: this.props.initialScreenAfterLogin,
|
||||
|
||||
// What the LoggedInView would be showing if visible
|
||||
page_type: null,
|
||||
|
||||
|
@ -147,8 +146,6 @@ module.exports = React.createClass({
|
|||
|
||||
collapse_lhs: false,
|
||||
collapse_rhs: false,
|
||||
ready: false,
|
||||
width: 10000,
|
||||
leftOpacity: 1.0,
|
||||
middleOpacity: 1.0,
|
||||
rightOpacity: 1.0,
|
||||
|
@ -274,6 +271,15 @@ module.exports = React.createClass({
|
|||
register_hs_url: paramHs,
|
||||
});
|
||||
}
|
||||
|
||||
// a thing to call showScreen with once login completes. this is kept
|
||||
// outside this.state because updating it should never trigger a
|
||||
// rerender.
|
||||
this._screenAfterLogin = this.props.initialScreenAfterLogin;
|
||||
|
||||
this._windowWidth = 10000;
|
||||
this.handleResize();
|
||||
window.addEventListener('resize', this.handleResize);
|
||||
},
|
||||
|
||||
componentDidMount: function() {
|
||||
|
@ -294,9 +300,6 @@ module.exports = React.createClass({
|
|||
linkifyMatrix.onGroupClick = this.onGroupClick;
|
||||
}
|
||||
|
||||
window.addEventListener('resize', this.handleResize);
|
||||
this.handleResize();
|
||||
|
||||
const teamServerConfig = this.props.config.teamServerConfig || {};
|
||||
Lifecycle.initRtsClient(teamServerConfig.teamServerURL);
|
||||
|
||||
|
@ -312,13 +315,12 @@ module.exports = React.createClass({
|
|||
|
||||
// if the user has followed a login or register link, don't reanimate
|
||||
// the old creds, but rather go straight to the relevant page
|
||||
const firstScreen = this.state.screenAfterLogin ?
|
||||
this.state.screenAfterLogin.screen : null;
|
||||
const firstScreen = this._screenAfterLogin ?
|
||||
this._screenAfterLogin.screen : null;
|
||||
|
||||
if (firstScreen === 'login' ||
|
||||
firstScreen === 'register' ||
|
||||
firstScreen === 'forgot_password') {
|
||||
this.setState({loading: false});
|
||||
this._showScreenAfterLogin();
|
||||
return;
|
||||
}
|
||||
|
@ -367,9 +369,9 @@ module.exports = React.createClass({
|
|||
}
|
||||
const newState = {
|
||||
viewUserId: null,
|
||||
};
|
||||
Object.assign(newState, state);
|
||||
this.setState(newState);
|
||||
};
|
||||
Object.assign(newState, state);
|
||||
this.setState(newState);
|
||||
},
|
||||
|
||||
onAction: function(payload) {
|
||||
|
@ -410,7 +412,7 @@ module.exports = React.createClass({
|
|||
this._leaveRoom(payload.room_id);
|
||||
break;
|
||||
case 'reject_invite':
|
||||
Modal.createDialog(QuestionDialog, {
|
||||
Modal.createTrackedDialog('Reject invitation', '', QuestionDialog, {
|
||||
title: _t('Reject invitation'),
|
||||
description: _t('Are you sure you want to reject the invitation?'),
|
||||
onFinished: (confirm) => {
|
||||
|
@ -426,7 +428,7 @@ module.exports = React.createClass({
|
|||
}
|
||||
}, (err) => {
|
||||
modal.close();
|
||||
Modal.createDialog(ErrorDialog, {
|
||||
Modal.createTrackedDialog('Failed to reject invitation', '', ErrorDialog, {
|
||||
title: _t('Failed to reject invitation'),
|
||||
description: err.toString(),
|
||||
});
|
||||
|
@ -512,7 +514,7 @@ module.exports = React.createClass({
|
|||
this._createChat();
|
||||
break;
|
||||
case 'view_invite':
|
||||
this._invite(payload.roomId);
|
||||
showRoomInviteDialog(payload.roomId);
|
||||
break;
|
||||
case 'notifier_enabled':
|
||||
this.forceUpdate();
|
||||
|
@ -728,7 +730,7 @@ module.exports = React.createClass({
|
|||
|
||||
_setMxId: function(payload) {
|
||||
const SetMxIdDialog = sdk.getComponent('views.dialogs.SetMxIdDialog');
|
||||
const close = Modal.createDialog(SetMxIdDialog, {
|
||||
const close = Modal.createTrackedDialog('Set MXID', '', SetMxIdDialog, {
|
||||
homeserverUrl: MatrixClientPeg.get().getHomeserverUrl(),
|
||||
onFinished: (submitted, credentials) => {
|
||||
if (!submitted) {
|
||||
|
@ -766,13 +768,7 @@ module.exports = React.createClass({
|
|||
dis.dispatch({action: 'view_set_mxid'});
|
||||
return;
|
||||
}
|
||||
const ChatInviteDialog = sdk.getComponent("dialogs.ChatInviteDialog");
|
||||
Modal.createDialog(ChatInviteDialog, {
|
||||
title: _t('Start a chat'),
|
||||
description: _t("Who would you like to communicate with?"),
|
||||
placeholder: _t("Email, name or matrix ID"),
|
||||
button: _t("Start Chat"),
|
||||
});
|
||||
showStartChatInviteDialog();
|
||||
},
|
||||
|
||||
_createRoom: function() {
|
||||
|
@ -787,7 +783,7 @@ module.exports = React.createClass({
|
|||
return;
|
||||
}
|
||||
const CreateRoomDialog = sdk.getComponent('dialogs.CreateRoomDialog');
|
||||
Modal.createDialog(CreateRoomDialog, {
|
||||
Modal.createTrackedDialog('Create Room', '', CreateRoomDialog, {
|
||||
onFinished: (shouldCreate, name, noFederate) => {
|
||||
if (shouldCreate) {
|
||||
const createOpts = {};
|
||||
|
@ -829,7 +825,7 @@ module.exports = React.createClass({
|
|||
return;
|
||||
}
|
||||
|
||||
const close = Modal.createDialog(ChatCreateOrReuseDialog, {
|
||||
const close = Modal.createTrackedDialog('Chat create or reuse', '', ChatCreateOrReuseDialog, {
|
||||
userId: userId,
|
||||
onFinished: (success) => {
|
||||
if (!success && goHomeOnCancel) {
|
||||
|
@ -855,23 +851,12 @@ module.exports = React.createClass({
|
|||
}).close;
|
||||
},
|
||||
|
||||
_invite: function(roomId) {
|
||||
const ChatInviteDialog = sdk.getComponent("dialogs.ChatInviteDialog");
|
||||
Modal.createDialog(ChatInviteDialog, {
|
||||
title: _t('Invite new room members'),
|
||||
description: _t('Who would you like to add to this room?'),
|
||||
button: _t('Send Invites'),
|
||||
placeholder: _t("Email, name or matrix ID"),
|
||||
roomId: roomId,
|
||||
});
|
||||
},
|
||||
|
||||
_leaveRoom: function(roomId) {
|
||||
const QuestionDialog = sdk.getComponent("dialogs.QuestionDialog");
|
||||
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
||||
|
||||
const roomToLeave = MatrixClientPeg.get().getRoom(roomId);
|
||||
Modal.createDialog(QuestionDialog, {
|
||||
Modal.createTrackedDialog('Leave room', '', QuestionDialog, {
|
||||
title: _t("Leave room"),
|
||||
description: (
|
||||
<span>
|
||||
|
@ -894,7 +879,7 @@ module.exports = React.createClass({
|
|||
}, (err) => {
|
||||
modal.close();
|
||||
console.error("Failed to leave room " + roomId + " " + err);
|
||||
Modal.createDialog(ErrorDialog, {
|
||||
Modal.createTrackedDialog('Failed to leave room', '', ErrorDialog, {
|
||||
title: _t("Failed to leave room"),
|
||||
description: (err && err.message ? err.message :
|
||||
_t("Server may be unavailable, overloaded, or you hit a bug.")),
|
||||
|
@ -990,14 +975,12 @@ module.exports = React.createClass({
|
|||
_showScreenAfterLogin: function() {
|
||||
// If screenAfterLogin is set, use that, then null it so that a second login will
|
||||
// result in view_home_page, _user_settings or _room_directory
|
||||
if (this.state.screenAfterLogin && this.state.screenAfterLogin.screen) {
|
||||
if (this._screenAfterLogin && this._screenAfterLogin.screen) {
|
||||
this.showScreen(
|
||||
this.state.screenAfterLogin.screen,
|
||||
this.state.screenAfterLogin.params,
|
||||
this._screenAfterLogin.screen,
|
||||
this._screenAfterLogin.params,
|
||||
);
|
||||
// XXX: is this necessary? `showScreen` should do it for us.
|
||||
this.notifyNewScreen(this.state.screenAfterLogin.screen);
|
||||
this.setState({screenAfterLogin: null});
|
||||
this._screenAfterLogin = null;
|
||||
} else if (localStorage && localStorage.getItem('mx_last_room_id')) {
|
||||
// Before defaulting to directory, show the last viewed room
|
||||
dis.dispatch({
|
||||
|
@ -1090,7 +1073,7 @@ module.exports = React.createClass({
|
|||
});
|
||||
cli.on('Session.logged_out', function(call) {
|
||||
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
||||
Modal.createDialog(ErrorDialog, {
|
||||
Modal.createTrackedDialog('Signed out', '', ErrorDialog, {
|
||||
title: _t('Signed Out'),
|
||||
description: _t('For security, this session has been signed out. Please sign in again.'),
|
||||
});
|
||||
|
@ -1203,21 +1186,24 @@ module.exports = React.createClass({
|
|||
} else if (screen.indexOf('user/') == 0) {
|
||||
const userId = screen.substring(5);
|
||||
|
||||
if (params.action === 'chat') {
|
||||
this._chatCreateOrReuse(userId);
|
||||
return;
|
||||
}
|
||||
// Wait for the first sync so that `getRoom` gives us a room object if it's
|
||||
// in the sync response
|
||||
const waitFor = this.firstSyncPromise ?
|
||||
this.firstSyncPromise.promise : Promise.resolve();
|
||||
waitFor.then(() => {
|
||||
if (params.action === 'chat') {
|
||||
this._chatCreateOrReuse(userId);
|
||||
return;
|
||||
}
|
||||
|
||||
this.setState({ viewUserId: userId });
|
||||
this._setPage(PageTypes.UserView);
|
||||
this.notifyNewScreen('user/' + userId);
|
||||
const member = new Matrix.RoomMember(null, userId);
|
||||
if (member) {
|
||||
this._setPage(PageTypes.UserView);
|
||||
this.notifyNewScreen('user/' + userId);
|
||||
const member = new Matrix.RoomMember(null, userId);
|
||||
dis.dispatch({
|
||||
action: 'view_user',
|
||||
member: member,
|
||||
});
|
||||
}
|
||||
});
|
||||
} else if (screen.indexOf('group/') == 0) {
|
||||
const groupId = screen.substring(6);
|
||||
|
||||
|
@ -1274,20 +1260,20 @@ module.exports = React.createClass({
|
|||
const hideRhsThreshold = 820;
|
||||
const showRhsThreshold = 820;
|
||||
|
||||
if (this.state.width > hideLhsThreshold && window.innerWidth <= hideLhsThreshold) {
|
||||
if (this._windowWidth > hideLhsThreshold && window.innerWidth <= hideLhsThreshold) {
|
||||
dis.dispatch({ action: 'hide_left_panel' });
|
||||
}
|
||||
if (this.state.width <= showLhsThreshold && window.innerWidth > showLhsThreshold) {
|
||||
if (this._windowWidth <= showLhsThreshold && window.innerWidth > showLhsThreshold) {
|
||||
dis.dispatch({ action: 'show_left_panel' });
|
||||
}
|
||||
if (this.state.width > hideRhsThreshold && window.innerWidth <= hideRhsThreshold) {
|
||||
if (this._windowWidth > hideRhsThreshold && window.innerWidth <= hideRhsThreshold) {
|
||||
dis.dispatch({ action: 'hide_right_panel' });
|
||||
}
|
||||
if (this.state.width <= showRhsThreshold && window.innerWidth > showRhsThreshold) {
|
||||
if (this._windowWidth <= showRhsThreshold && window.innerWidth > showRhsThreshold) {
|
||||
dis.dispatch({ action: 'show_right_panel' });
|
||||
}
|
||||
|
||||
this.setState({width: window.innerWidth});
|
||||
this._windowWidth = window.innerWidth;
|
||||
},
|
||||
|
||||
onRoomCreated: function(roomId) {
|
||||
|
|
|
@ -307,13 +307,13 @@ module.exports = React.createClass({
|
|||
for (i = 0; i < this.props.events.length; i++) {
|
||||
let mxEv = this.props.events[i];
|
||||
let eventId = mxEv.getId();
|
||||
let readMarkerInMels = false;
|
||||
let last = (mxEv === lastShownEvent);
|
||||
|
||||
const wantTile = this._shouldShowEvent(mxEv);
|
||||
|
||||
// Wrap consecutive member events in a ListSummary, ignore if redacted
|
||||
if (isMembershipChange(mxEv) && wantTile) {
|
||||
let readMarkerInMels = false;
|
||||
let ts1 = mxEv.getTs();
|
||||
// Ensure that the key of the MemberEventListSummary does not change with new
|
||||
// member events. This will prevent it from being re-created unnecessarily, and
|
||||
|
@ -330,6 +330,11 @@ module.exports = React.createClass({
|
|||
ret.push(dateSeparator);
|
||||
}
|
||||
|
||||
// If RM event is the first in the MELS, append the RM after MELS
|
||||
if (mxEv.getId() === this.props.readMarkerEventId) {
|
||||
readMarkerInMels = true;
|
||||
}
|
||||
|
||||
let summarisedEvents = [mxEv];
|
||||
for (;i + 1 < this.props.events.length; i++) {
|
||||
const collapsedMxEv = this.props.events[i + 1];
|
||||
|
|
|
@ -63,7 +63,7 @@ export default withMatrixClient(React.createClass({
|
|||
|
||||
_onCreateGroupClick: function() {
|
||||
const CreateGroupDialog = sdk.getComponent("dialogs.CreateGroupDialog");
|
||||
Modal.createDialog(CreateGroupDialog);
|
||||
Modal.createTrackedDialog('Create Group', '', CreateGroupDialog);
|
||||
},
|
||||
|
||||
_fetch: function() {
|
||||
|
|
|
@ -544,7 +544,7 @@ module.exports = React.createClass({
|
|||
}
|
||||
if (!userHasUsedEncryption) {
|
||||
const QuestionDialog = sdk.getComponent("dialogs.QuestionDialog");
|
||||
Modal.createDialog(QuestionDialog, {
|
||||
Modal.createTrackedDialog('E2E Warning', '', QuestionDialog, {
|
||||
title: _t("Warning!"),
|
||||
hasCancelButton: false,
|
||||
description: (
|
||||
|
@ -820,7 +820,7 @@ module.exports = React.createClass({
|
|||
});
|
||||
|
||||
const SetMxIdDialog = sdk.getComponent('views.dialogs.SetMxIdDialog');
|
||||
const close = Modal.createDialog(SetMxIdDialog, {
|
||||
const close = Modal.createTrackedDialog('Set MXID', '', SetMxIdDialog, {
|
||||
homeserverUrl: cli.getHomeserverUrl(),
|
||||
onFinished: (submitted, credentials) => {
|
||||
if (submitted) {
|
||||
|
@ -934,7 +934,7 @@ module.exports = React.createClass({
|
|||
}
|
||||
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
||||
console.error("Failed to upload file " + file + " " + error);
|
||||
Modal.createDialog(ErrorDialog, {
|
||||
Modal.createTrackedDialog('Failed to upload file', '', ErrorDialog, {
|
||||
title: _t('Failed to upload file'),
|
||||
description: ((error && error.message) ? error.message : _t("Server may be unavailable, overloaded, or the file too big")),
|
||||
});
|
||||
|
@ -1021,7 +1021,7 @@ module.exports = React.createClass({
|
|||
}, function(error) {
|
||||
var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
||||
console.error("Search failed: " + error);
|
||||
Modal.createDialog(ErrorDialog, {
|
||||
Modal.createTrackedDialog('Search failed', '', ErrorDialog, {
|
||||
title: _t("Search failed"),
|
||||
description: ((error && error.message) ? error.message : _t("Server may be unavailable, overloaded, or search timed out :(")),
|
||||
});
|
||||
|
@ -1148,7 +1148,7 @@ module.exports = React.createClass({
|
|||
console.error(result.reason);
|
||||
});
|
||||
var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
||||
Modal.createDialog(ErrorDialog, {
|
||||
Modal.createTrackedDialog('Failed to save room settings', '', ErrorDialog, {
|
||||
title: _t("Failed to save settings"),
|
||||
description: fails.map(function(result) { return result.reason; }).join("\n"),
|
||||
});
|
||||
|
@ -1195,7 +1195,7 @@ module.exports = React.createClass({
|
|||
}, function(err) {
|
||||
var errCode = err.errcode || _t("unknown error code");
|
||||
var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
||||
Modal.createDialog(ErrorDialog, {
|
||||
Modal.createTrackedDialog('Failed to forget room', '', ErrorDialog, {
|
||||
title: _t("Error"),
|
||||
description: _t("Failed to forget room %(errCode)s", { errCode: errCode }),
|
||||
});
|
||||
|
@ -1217,7 +1217,7 @@ module.exports = React.createClass({
|
|||
|
||||
var msg = error.message ? error.message : JSON.stringify(error);
|
||||
var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
||||
Modal.createDialog(ErrorDialog, {
|
||||
Modal.createTrackedDialog('Failed to reject invite', '', ErrorDialog, {
|
||||
title: _t("Failed to reject invite"),
|
||||
description: msg,
|
||||
});
|
||||
|
|
|
@ -923,7 +923,7 @@ 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.");
|
||||
Modal.createDialog(ErrorDialog, {
|
||||
Modal.createTrackedDialog('Failed to load timeline position', '', ErrorDialog, {
|
||||
title: _t("Failed to load timeline position"),
|
||||
description: message,
|
||||
onFinished: onFinished,
|
||||
|
|
|
@ -85,6 +85,10 @@ const SETTINGS_LABELS = [
|
|||
id: 'hideJoinLeaves',
|
||||
label: 'Hide join/leave messages (invites/kicks/bans unaffected)',
|
||||
},
|
||||
{
|
||||
id: 'hideAvatarDisplaynameChanges',
|
||||
label: 'Hide avatar and display name changes',
|
||||
},
|
||||
{
|
||||
id: 'useCompactLayout',
|
||||
label: 'Use compact timeline layout',
|
||||
|
@ -101,6 +105,10 @@ const SETTINGS_LABELS = [
|
|||
id: 'MessageComposerInput.autoReplaceEmoji',
|
||||
label: 'Automatically replace plain text Emoji',
|
||||
},
|
||||
{
|
||||
id: 'Pill.shouldHidePillAvatar',
|
||||
label: 'Hide avatars in user and room mentions',
|
||||
},
|
||||
/*
|
||||
{
|
||||
id: 'useFixedWidthFont',
|
||||
|
@ -331,7 +339,7 @@ module.exports = React.createClass({
|
|||
}, function(error) {
|
||||
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
||||
console.error("Failed to load user settings: " + error);
|
||||
Modal.createDialog(ErrorDialog, {
|
||||
Modal.createTrackedDialog('Can\'t load user settings', '', ErrorDialog, {
|
||||
title: _t("Can't load user settings"),
|
||||
description: ((error && error.message) ? error.message : _t("Server may be unavailable or overloaded")),
|
||||
});
|
||||
|
@ -364,7 +372,7 @@ module.exports = React.createClass({
|
|||
// const errMsg = (typeof err === "string") ? err : (err.error || "");
|
||||
console.error("Failed to set avatar: " + err);
|
||||
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
||||
Modal.createDialog(ErrorDialog, {
|
||||
Modal.createTrackedDialog('Failed to set avatar', '', ErrorDialog, {
|
||||
title: _t("Failed to set avatar."),
|
||||
description: ((err && err.message) ? err.message : _t("Operation failed")),
|
||||
});
|
||||
|
@ -373,7 +381,7 @@ module.exports = React.createClass({
|
|||
|
||||
onLogoutClicked: function(ev) {
|
||||
const QuestionDialog = sdk.getComponent("dialogs.QuestionDialog");
|
||||
Modal.createDialog(QuestionDialog, {
|
||||
Modal.createTrackedDialog('Logout E2E Export', '', QuestionDialog, {
|
||||
title: _t("Sign out"),
|
||||
description:
|
||||
<div>
|
||||
|
@ -409,7 +417,7 @@ module.exports = React.createClass({
|
|||
}
|
||||
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
||||
console.error("Failed to change password: " + errMsg);
|
||||
Modal.createDialog(ErrorDialog, {
|
||||
Modal.createTrackedDialog('Failed to change password', '', ErrorDialog, {
|
||||
title: _t("Error"),
|
||||
description: errMsg,
|
||||
});
|
||||
|
@ -417,7 +425,7 @@ module.exports = React.createClass({
|
|||
|
||||
onPasswordChanged: function() {
|
||||
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
||||
Modal.createDialog(ErrorDialog, {
|
||||
Modal.createTrackedDialog('Password changed', '', ErrorDialog, {
|
||||
title: _t("Success"),
|
||||
description: _t(
|
||||
"Your password was successfully changed. You will not receive " +
|
||||
|
@ -442,7 +450,7 @@ module.exports = React.createClass({
|
|||
|
||||
const emailAddress = this.refs.add_email_input.value;
|
||||
if (!Email.looksValid(emailAddress)) {
|
||||
Modal.createDialog(ErrorDialog, {
|
||||
Modal.createTrackedDialog('Invalid email address', '', ErrorDialog, {
|
||||
title: _t("Invalid Email Address"),
|
||||
description: _t("This doesn't appear to be a valid email address"),
|
||||
});
|
||||
|
@ -452,7 +460,7 @@ module.exports = React.createClass({
|
|||
// we always bind emails when registering, so let's do the
|
||||
// same here.
|
||||
this._addThreepid.addEmailAddress(emailAddress, true).done(() => {
|
||||
Modal.createDialog(QuestionDialog, {
|
||||
Modal.createTrackedDialog('Verification Pending', '', QuestionDialog, {
|
||||
title: _t("Verification Pending"),
|
||||
description: _t(
|
||||
"Please check your email and click on the link it contains. Once this " +
|
||||
|
@ -464,7 +472,7 @@ module.exports = React.createClass({
|
|||
}, (err) => {
|
||||
this.setState({email_add_pending: false});
|
||||
console.error("Unable to add email address " + emailAddress + " " + err);
|
||||
Modal.createDialog(ErrorDialog, {
|
||||
Modal.createTrackedDialog('Unable to add email address', '', ErrorDialog, {
|
||||
title: _t("Unable to add email address"),
|
||||
description: ((err && err.message) ? err.message : _t("Operation failed")),
|
||||
});
|
||||
|
@ -475,7 +483,7 @@ module.exports = React.createClass({
|
|||
|
||||
onRemoveThreepidClicked: function(threepid) {
|
||||
const QuestionDialog = sdk.getComponent("dialogs.QuestionDialog");
|
||||
Modal.createDialog(QuestionDialog, {
|
||||
Modal.createTrackedDialog('Remove 3pid', '', QuestionDialog, {
|
||||
title: _t("Remove Contact Information?"),
|
||||
description: _t("Remove %(threePid)s?", { threePid: threepid.address }),
|
||||
button: _t('Remove'),
|
||||
|
@ -489,7 +497,7 @@ module.exports = React.createClass({
|
|||
}).catch((err) => {
|
||||
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
||||
console.error("Unable to remove contact information: " + err);
|
||||
Modal.createDialog(ErrorDialog, {
|
||||
Modal.createTrackedDialog('Remove 3pid failed', '', ErrorDialog, {
|
||||
title: _t("Unable to remove contact information"),
|
||||
description: ((err && err.message) ? err.message : _t("Operation failed")),
|
||||
});
|
||||
|
@ -521,7 +529,7 @@ module.exports = React.createClass({
|
|||
const QuestionDialog = sdk.getComponent("dialogs.QuestionDialog");
|
||||
const message = _t("Unable to verify email address.") + " " +
|
||||
_t("Please check your email and click on the link it contains. Once this is done, click continue.");
|
||||
Modal.createDialog(QuestionDialog, {
|
||||
Modal.createTrackedDialog('Verification Pending', '', QuestionDialog, {
|
||||
title: _t("Verification Pending"),
|
||||
description: message,
|
||||
button: _t('Continue'),
|
||||
|
@ -530,7 +538,7 @@ module.exports = React.createClass({
|
|||
} else {
|
||||
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
||||
console.error("Unable to verify email address: " + err);
|
||||
Modal.createDialog(ErrorDialog, {
|
||||
Modal.createTrackedDialog('Unable to verify email address', '', ErrorDialog, {
|
||||
title: _t("Unable to verify email address."),
|
||||
description: ((err && err.message) ? err.message : _t("Operation failed")),
|
||||
});
|
||||
|
@ -540,7 +548,7 @@ module.exports = React.createClass({
|
|||
|
||||
_onDeactivateAccountClicked: function() {
|
||||
const DeactivateAccountDialog = sdk.getComponent("dialogs.DeactivateAccountDialog");
|
||||
Modal.createDialog(DeactivateAccountDialog, {});
|
||||
Modal.createTrackedDialog('Deactivate Account', '', DeactivateAccountDialog, {});
|
||||
},
|
||||
|
||||
_onBugReportClicked: function() {
|
||||
|
@ -548,7 +556,7 @@ module.exports = React.createClass({
|
|||
if (!BugReportDialog) {
|
||||
return;
|
||||
}
|
||||
Modal.createDialog(BugReportDialog, {});
|
||||
Modal.createTrackedDialog('Bug Report Dialog', '', BugReportDialog, {});
|
||||
},
|
||||
|
||||
_onClearCacheClicked: function() {
|
||||
|
@ -585,27 +593,23 @@ module.exports = React.createClass({
|
|||
},
|
||||
|
||||
_onExportE2eKeysClicked: function() {
|
||||
Modal.createDialogAsync(
|
||||
(cb) => {
|
||||
require.ensure(['../../async-components/views/dialogs/ExportE2eKeysDialog'], () => {
|
||||
cb(require('../../async-components/views/dialogs/ExportE2eKeysDialog'));
|
||||
}, "e2e-export");
|
||||
}, {
|
||||
matrixClient: MatrixClientPeg.get(),
|
||||
},
|
||||
);
|
||||
Modal.createTrackedDialogAsync('Export E2E Keys', '', (cb) => {
|
||||
require.ensure(['../../async-components/views/dialogs/ExportE2eKeysDialog'], () => {
|
||||
cb(require('../../async-components/views/dialogs/ExportE2eKeysDialog'));
|
||||
}, "e2e-export");
|
||||
}, {
|
||||
matrixClient: MatrixClientPeg.get(),
|
||||
});
|
||||
},
|
||||
|
||||
_onImportE2eKeysClicked: function() {
|
||||
Modal.createDialogAsync(
|
||||
(cb) => {
|
||||
require.ensure(['../../async-components/views/dialogs/ImportE2eKeysDialog'], () => {
|
||||
cb(require('../../async-components/views/dialogs/ImportE2eKeysDialog'));
|
||||
}, "e2e-export");
|
||||
}, {
|
||||
matrixClient: MatrixClientPeg.get(),
|
||||
},
|
||||
);
|
||||
Modal.createTrackedDialogAsync('Import E2E Keys', '', (cb) => {
|
||||
require.ensure(['../../async-components/views/dialogs/ImportE2eKeysDialog'], () => {
|
||||
cb(require('../../async-components/views/dialogs/ImportE2eKeysDialog'));
|
||||
}, "e2e-export");
|
||||
}, {
|
||||
matrixClient: MatrixClientPeg.get(),
|
||||
});
|
||||
},
|
||||
|
||||
_renderReferral: function() {
|
||||
|
@ -855,7 +859,13 @@ module.exports = React.createClass({
|
|||
if (this.props.enableLabs === false) return null;
|
||||
UserSettingsStore.doTranslations();
|
||||
|
||||
const features = UserSettingsStore.LABS_FEATURES.map((feature) => {
|
||||
const features = [];
|
||||
UserSettingsStore.LABS_FEATURES.forEach((feature) => {
|
||||
// This feature has an override and will be set to the default, so do not
|
||||
// show it here.
|
||||
if (feature.override) {
|
||||
return;
|
||||
}
|
||||
// TODO: this ought to be a separate component so that we don't need
|
||||
// to rebind the onChange each time we render
|
||||
const onChange = (e) => {
|
||||
|
@ -863,7 +873,7 @@ module.exports = React.createClass({
|
|||
this.forceUpdate();
|
||||
};
|
||||
|
||||
return (
|
||||
features.push(
|
||||
<div key={feature.id} className="mx_UserSettings_toggle">
|
||||
<input
|
||||
type="checkbox"
|
||||
|
@ -873,9 +883,14 @@ module.exports = React.createClass({
|
|||
onChange={ onChange }
|
||||
/>
|
||||
<label htmlFor={feature.id}>{feature.name}</label>
|
||||
</div>
|
||||
);
|
||||
</div>);
|
||||
});
|
||||
|
||||
// No labs section when there are no features in labs
|
||||
if (features.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h3>{ _t("Labs") }</h3>
|
||||
|
@ -1004,7 +1019,7 @@ module.exports = React.createClass({
|
|||
this._refreshMediaDevices,
|
||||
function() {
|
||||
const ErrorDialog = sdk.getComponent('dialogs.ErrorDialog');
|
||||
Modal.createDialog(ErrorDialog, {
|
||||
Modal.createTrackedDialog('No media permissions', '', ErrorDialog, {
|
||||
title: _t('No media permissions'),
|
||||
description: _t('You may need to manually permit Riot to access your microphone/webcam'),
|
||||
});
|
||||
|
|
|
@ -89,14 +89,14 @@ module.exports = React.createClass({
|
|||
}
|
||||
else {
|
||||
var QuestionDialog = sdk.getComponent("dialogs.QuestionDialog");
|
||||
Modal.createDialog(QuestionDialog, {
|
||||
Modal.createTrackedDialog('Forgot Password Warning', '', QuestionDialog, {
|
||||
title: _t('Warning!'),
|
||||
description:
|
||||
<div>
|
||||
{ _t(
|
||||
'Resetting password will currently reset any ' +
|
||||
'end-to-end encryption keys on all devices, ' +
|
||||
'making encrypted chat history unreadable, ' +
|
||||
'making encrypted chat history unreadable, ' +
|
||||
'unless you first export your room keys and re-import ' +
|
||||
'them afterwards. In future this will be improved.'
|
||||
) }
|
||||
|
@ -121,15 +121,13 @@ module.exports = React.createClass({
|
|||
},
|
||||
|
||||
_onExportE2eKeysClicked: function() {
|
||||
Modal.createDialogAsync(
|
||||
(cb) => {
|
||||
require.ensure(['../../../async-components/views/dialogs/ExportE2eKeysDialog'], () => {
|
||||
cb(require('../../../async-components/views/dialogs/ExportE2eKeysDialog'));
|
||||
}, "e2e-export");
|
||||
}, {
|
||||
matrixClient: MatrixClientPeg.get(),
|
||||
}
|
||||
);
|
||||
Modal.createTrackedDialogAsync('Export E2E Keys', 'Forgot Password', (cb) => {
|
||||
require.ensure(['../../../async-components/views/dialogs/ExportE2eKeysDialog'], () => {
|
||||
cb(require('../../../async-components/views/dialogs/ExportE2eKeysDialog'));
|
||||
}, "e2e-export");
|
||||
}, {
|
||||
matrixClient: MatrixClientPeg.get(),
|
||||
});
|
||||
},
|
||||
|
||||
onInputChanged: function(stateKey, ev) {
|
||||
|
@ -152,7 +150,7 @@ module.exports = React.createClass({
|
|||
|
||||
showErrorDialog: function(body, title) {
|
||||
var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
||||
Modal.createDialog(ErrorDialog, {
|
||||
Modal.createTrackedDialog('Forgot Password Error', '', ErrorDialog, {
|
||||
title: title,
|
||||
description: body,
|
||||
});
|
||||
|
|
|
@ -19,8 +19,11 @@ limitations under the License.
|
|||
|
||||
import React from 'react';
|
||||
import { _t, _tJsx } from '../../../languageHandler';
|
||||
import * as languageHandler from '../../../languageHandler';
|
||||
import sdk from '../../../index';
|
||||
import Login from '../../../Login';
|
||||
import UserSettingsStore from '../../../UserSettingsStore';
|
||||
import PlatformPeg from '../../../PlatformPeg';
|
||||
|
||||
// For validating phone numbers without country codes
|
||||
const PHONE_NUMBER_REGEX = /^[0-9\(\)\-\s]*$/;
|
||||
|
@ -306,6 +309,23 @@ module.exports = React.createClass({
|
|||
}
|
||||
},
|
||||
|
||||
_onLanguageChange: function(newLang) {
|
||||
if(languageHandler.getCurrentLanguage() !== newLang) {
|
||||
UserSettingsStore.setLocalSetting('language', newLang);
|
||||
PlatformPeg.get().reload();
|
||||
}
|
||||
},
|
||||
|
||||
_renderLanguageSetting: function() {
|
||||
const LanguageDropdown = sdk.getComponent('views.elements.LanguageDropdown');
|
||||
return <div className="mx_Login_language_div">
|
||||
<LanguageDropdown onOptionChange={this._onLanguageChange}
|
||||
className="mx_Login_language"
|
||||
value={languageHandler.getCurrentLanguage()}
|
||||
/>
|
||||
</div>;
|
||||
},
|
||||
|
||||
render: function() {
|
||||
const Loader = sdk.getComponent("elements.Spinner");
|
||||
const LoginHeader = sdk.getComponent("login.LoginHeader");
|
||||
|
@ -354,6 +374,7 @@ module.exports = React.createClass({
|
|||
</a>
|
||||
{ loginAsGuestJsx }
|
||||
{ returnToAppJsx }
|
||||
{ this._renderLanguageSetting() }
|
||||
<LoginFooter />
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -17,6 +17,7 @@ limitations under the License.
|
|||
import React from 'react';
|
||||
|
||||
import sdk from '../../../index';
|
||||
import Analytics from '../../../Analytics';
|
||||
import MatrixClientPeg from '../../../MatrixClientPeg';
|
||||
import * as Lifecycle from '../../../Lifecycle';
|
||||
import Velocity from 'velocity-vector';
|
||||
|
@ -54,6 +55,7 @@ export default class DeactivateAccountDialog extends React.Component {
|
|||
user: MatrixClientPeg.get().credentials.userId,
|
||||
password: this._passwordField.value,
|
||||
}).done(() => {
|
||||
Analytics.trackEvent('Account', 'Deactivate Account');
|
||||
Lifecycle.onLoggedOut();
|
||||
this.props.onFinished(false);
|
||||
}, (err) => {
|
||||
|
|
|
@ -16,7 +16,7 @@ limitations under the License.
|
|||
|
||||
/*
|
||||
* Usage:
|
||||
* Modal.createDialog(ErrorDialog, {
|
||||
* Modal.createTrackedDialog('An Identifier', 'some detail', ErrorDialog, {
|
||||
* title: "some text", (default: "Error")
|
||||
* description: "some more text",
|
||||
* button: "Button Text",
|
||||
|
|
|
@ -88,7 +88,7 @@ export default React.createClass({
|
|||
const DeviceVerifyDialog = sdk.getComponent('views.dialogs.DeviceVerifyDialog');
|
||||
|
||||
console.log("KeyShareDialog: Starting verify dialog");
|
||||
Modal.createDialog(DeviceVerifyDialog, {
|
||||
Modal.createTrackedDialog('Key Share', 'Starting dialog', DeviceVerifyDialog, {
|
||||
userId: this.props.userId,
|
||||
device: this.state.deviceInfo,
|
||||
onFinished: (verified) => {
|
||||
|
|
|
@ -31,7 +31,7 @@ export default React.createClass({
|
|||
|
||||
_sendBugReport: function() {
|
||||
const BugReportDialog = sdk.getComponent("dialogs.BugReportDialog");
|
||||
Modal.createDialog(BugReportDialog, {});
|
||||
Modal.createTrackedDialog('Session Restore Error', 'Send Bug Report Dialog', BugReportDialog, {});
|
||||
},
|
||||
|
||||
_continueClicked: function() {
|
||||
|
|
|
@ -55,7 +55,7 @@ export default React.createClass({
|
|||
|
||||
const emailAddress = this.state.emailAddress;
|
||||
if (!Email.looksValid(emailAddress)) {
|
||||
Modal.createDialog(ErrorDialog, {
|
||||
Modal.createTrackedDialog('Invalid Email Address', '', ErrorDialog, {
|
||||
title: _t("Invalid Email Address"),
|
||||
description: _t("This doesn't appear to be a valid email address"),
|
||||
});
|
||||
|
@ -65,7 +65,7 @@ export default React.createClass({
|
|||
// we always bind emails when registering, so let's do the
|
||||
// same here.
|
||||
this._addThreepid.addEmailAddress(emailAddress, true).done(() => {
|
||||
Modal.createDialog(QuestionDialog, {
|
||||
Modal.createTrackedDialog('Verification Pending', '', QuestionDialog, {
|
||||
title: _t("Verification Pending"),
|
||||
description: _t(
|
||||
"Please check your email and click on the link it contains. Once this " +
|
||||
|
@ -77,7 +77,7 @@ export default React.createClass({
|
|||
}, (err) => {
|
||||
this.setState({emailBusy: false});
|
||||
console.error("Unable to add email address " + emailAddress + " " + err);
|
||||
Modal.createDialog(ErrorDialog, {
|
||||
Modal.createTrackedDialog('Unable to add email address', '', ErrorDialog, {
|
||||
title: _t("Unable to add email address"),
|
||||
description: ((err && err.message) ? err.message : _t("Operation failed")),
|
||||
});
|
||||
|
@ -106,7 +106,7 @@ export default React.createClass({
|
|||
const QuestionDialog = sdk.getComponent("dialogs.QuestionDialog");
|
||||
const message = _t("Unable to verify email address.") + " " +
|
||||
_t("Please check your email and click on the link it contains. Once this is done, click continue.");
|
||||
Modal.createDialog(QuestionDialog, {
|
||||
Modal.createTrackedDialog('Verification Pending', '3pid Auth Failed', QuestionDialog, {
|
||||
title: _t("Verification Pending"),
|
||||
description: message,
|
||||
button: _t('Continue'),
|
||||
|
@ -115,7 +115,7 @@ export default React.createClass({
|
|||
} else {
|
||||
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
||||
console.error("Unable to verify email address: " + err);
|
||||
Modal.createDialog(ErrorDialog, {
|
||||
Modal.createTrackedDialog('Unable to verify email address', '', ErrorDialog, {
|
||||
title: _t("Unable to verify email address."),
|
||||
description: ((err && err.message) ? err.message : _t("Operation failed")),
|
||||
});
|
||||
|
|
|
@ -106,6 +106,16 @@ export default React.createClass({
|
|||
},
|
||||
|
||||
_doUsernameCheck: function() {
|
||||
// XXX: SPEC-1
|
||||
// Check if username is valid
|
||||
// Naive impl copied from https://github.com/matrix-org/matrix-react-sdk/blob/66c3a6d9ca695780eb6b662e242e88323053ff33/src/components/views/login/RegistrationForm.js#L190
|
||||
if (encodeURIComponent(this.state.username) !== this.state.username) {
|
||||
this.setState({
|
||||
usernameError: _t('User names may only contain letters, numbers, dots, hyphens and underscores.'),
|
||||
});
|
||||
return Promise.reject();
|
||||
}
|
||||
|
||||
// Check if username is available
|
||||
return this._matrixClient.isUsernameAvailable(this.state.username).then(
|
||||
(isAvailable) => {
|
||||
|
@ -242,7 +252,7 @@ export default React.createClass({
|
|||
return (
|
||||
<BaseDialog className="mx_SetMxIdDialog"
|
||||
onFinished={this.props.onFinished}
|
||||
title="To get started, please pick a username!"
|
||||
title={_t('To get started, please pick a username!')}
|
||||
>
|
||||
<div className="mx_Dialog_content">
|
||||
<div className="mx_SetMxIdDialog_input_group">
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
/*
|
||||
Copyright 2015, 2016 OpenMarket Ltd
|
||||
Copyright 2017 New Vector Ltd
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
|
@ -15,40 +16,37 @@ limitations under the License.
|
|||
*/
|
||||
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { _t } from '../../../languageHandler';
|
||||
import sdk from '../../../index';
|
||||
import { getAddressType, inviteMultipleToRoom } from '../../../Invite';
|
||||
import createRoom from '../../../createRoom';
|
||||
import MatrixClientPeg from '../../../MatrixClientPeg';
|
||||
import DMRoomMap from '../../../utils/DMRoomMap';
|
||||
import Modal from '../../../Modal';
|
||||
import AccessibleButton from '../elements/AccessibleButton';
|
||||
import Promise from 'bluebird';
|
||||
import dis from '../../../dispatcher';
|
||||
import { addressTypes, getAddressType } from '../../../UserAddress.js';
|
||||
|
||||
const TRUNCATE_QUERY_LIST = 40;
|
||||
const QUERY_USER_DIRECTORY_DEBOUNCE_MS = 200;
|
||||
|
||||
module.exports = React.createClass({
|
||||
displayName: "ChatInviteDialog",
|
||||
displayName: "UserPickerDialog",
|
||||
|
||||
propTypes: {
|
||||
title: React.PropTypes.string.isRequired,
|
||||
description: React.PropTypes.oneOfType([
|
||||
React.PropTypes.element,
|
||||
React.PropTypes.string,
|
||||
]),
|
||||
value: React.PropTypes.string,
|
||||
placeholder: React.PropTypes.string,
|
||||
roomId: React.PropTypes.string,
|
||||
button: React.PropTypes.string,
|
||||
focus: React.PropTypes.bool,
|
||||
onFinished: React.PropTypes.func.isRequired,
|
||||
title: PropTypes.string.isRequired,
|
||||
description: PropTypes.node,
|
||||
value: PropTypes.string,
|
||||
placeholder: PropTypes.string,
|
||||
roomId: PropTypes.string,
|
||||
button: PropTypes.string,
|
||||
focus: PropTypes.bool,
|
||||
validAddressTypes: PropTypes.arrayOf(PropTypes.oneOfType(addressTypes)),
|
||||
onFinished: PropTypes.func.isRequired,
|
||||
},
|
||||
|
||||
getDefaultProps: function() {
|
||||
return {
|
||||
value: "",
|
||||
focus: true,
|
||||
validAddressTypes: addressTypes,
|
||||
};
|
||||
},
|
||||
|
||||
|
@ -56,9 +54,9 @@ module.exports = React.createClass({
|
|||
return {
|
||||
error: false,
|
||||
|
||||
// List of AddressTile.InviteAddressType objects representing
|
||||
// List of UserAddressType objects representing
|
||||
// the list of addresses we're going to invite
|
||||
inviteList: [],
|
||||
userList: [],
|
||||
|
||||
// Whether a search is ongoing
|
||||
busy: false,
|
||||
|
@ -68,7 +66,7 @@ module.exports = React.createClass({
|
|||
serverSupportsUserDirectory: true,
|
||||
// The query being searched for
|
||||
query: "",
|
||||
// List of AddressTile.InviteAddressType objects representing
|
||||
// List of UserAddressType objects representing
|
||||
// the set of auto-completion results for the current search
|
||||
// query.
|
||||
queryList: [],
|
||||
|
@ -83,57 +81,14 @@ module.exports = React.createClass({
|
|||
},
|
||||
|
||||
onButtonClick: function() {
|
||||
let inviteList = this.state.inviteList.slice();
|
||||
let userList = this.state.userList.slice();
|
||||
// Check the text input field to see if user has an unconverted address
|
||||
// If there is and it's valid add it to the local inviteList
|
||||
// If there is and it's valid add it to the local userList
|
||||
if (this.refs.textinput.value !== '') {
|
||||
inviteList = this._addInputToList();
|
||||
if (inviteList === null) return;
|
||||
}
|
||||
|
||||
const addrTexts = inviteList.map(addr => addr.address);
|
||||
if (inviteList.length > 0) {
|
||||
if (this._isDmChat(addrTexts)) {
|
||||
const userId = inviteList[0].address;
|
||||
// Direct Message chat
|
||||
const rooms = this._getDirectMessageRooms(userId);
|
||||
if (rooms.length > 0) {
|
||||
// A Direct Message room already exists for this user, so select a
|
||||
// room from a list that is similar to the one in MemberInfo panel
|
||||
const ChatCreateOrReuseDialog = sdk.getComponent(
|
||||
"views.dialogs.ChatCreateOrReuseDialog",
|
||||
);
|
||||
const close = Modal.createDialog(ChatCreateOrReuseDialog, {
|
||||
userId: userId,
|
||||
onFinished: (success) => {
|
||||
this.props.onFinished(success);
|
||||
},
|
||||
onNewDMClick: () => {
|
||||
dis.dispatch({
|
||||
action: 'start_chat',
|
||||
user_id: userId,
|
||||
});
|
||||
close(true);
|
||||
},
|
||||
onExistingRoomSelected: (roomId) => {
|
||||
dis.dispatch({
|
||||
action: 'view_room',
|
||||
room_id: roomId,
|
||||
});
|
||||
close(true);
|
||||
},
|
||||
}).close;
|
||||
} else {
|
||||
this._startChat(inviteList);
|
||||
}
|
||||
} else {
|
||||
// Multi invite chat
|
||||
this._startChat(inviteList);
|
||||
}
|
||||
} else {
|
||||
// No addresses supplied
|
||||
this.setState({ error: true });
|
||||
userList = this._addInputToList();
|
||||
if (userList === null) return;
|
||||
}
|
||||
this.props.onFinished(true, userList);
|
||||
},
|
||||
|
||||
onCancel: function() {
|
||||
|
@ -157,10 +112,10 @@ module.exports = React.createClass({
|
|||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
if (this.addressSelector) this.addressSelector.chooseSelection();
|
||||
} else if (this.refs.textinput.value.length === 0 && this.state.inviteList.length && e.keyCode === 8) { // backspace
|
||||
} else if (this.refs.textinput.value.length === 0 && this.state.userList.length && e.keyCode === 8) { // backspace
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
this.onDismissed(this.state.inviteList.length - 1)();
|
||||
this.onDismissed(this.state.userList.length - 1)();
|
||||
} else if (e.keyCode === 13) { // enter
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
|
@ -201,12 +156,11 @@ module.exports = React.createClass({
|
|||
},
|
||||
|
||||
onDismissed: function(index) {
|
||||
var self = this;
|
||||
return () => {
|
||||
var inviteList = self.state.inviteList.slice();
|
||||
inviteList.splice(index, 1);
|
||||
self.setState({
|
||||
inviteList: inviteList,
|
||||
const userList = this.state.userList.slice();
|
||||
userList.splice(index, 1);
|
||||
this.setState({
|
||||
userList: userList,
|
||||
queryList: [],
|
||||
query: "",
|
||||
});
|
||||
|
@ -215,17 +169,16 @@ module.exports = React.createClass({
|
|||
},
|
||||
|
||||
onClick: function(index) {
|
||||
var self = this;
|
||||
return function() {
|
||||
self.onSelected(index);
|
||||
return () => {
|
||||
this.onSelected(index);
|
||||
};
|
||||
},
|
||||
|
||||
onSelected: function(index) {
|
||||
var inviteList = this.state.inviteList.slice();
|
||||
inviteList.push(this.state.queryList[index]);
|
||||
const userList = this.state.userList.slice();
|
||||
userList.push(this.state.queryList[index]);
|
||||
this.setState({
|
||||
inviteList: inviteList,
|
||||
userList: userList,
|
||||
queryList: [],
|
||||
query: "",
|
||||
});
|
||||
|
@ -297,7 +250,7 @@ module.exports = React.createClass({
|
|||
return;
|
||||
}
|
||||
// Return objects, structure of which is defined
|
||||
// by InviteAddressType
|
||||
// by UserAddressType
|
||||
queryList.push({
|
||||
addressType: 'mx',
|
||||
address: user.user_id,
|
||||
|
@ -311,7 +264,7 @@ module.exports = React.createClass({
|
|||
// This is important, otherwise there's no way to invite
|
||||
// a perfectly valid address if there are close matches.
|
||||
const addrType = getAddressType(query);
|
||||
if (addrType !== null) {
|
||||
if (this.props.validAddressTypes.includes(addrType)) {
|
||||
queryList.unshift({
|
||||
addressType: addrType,
|
||||
address: query,
|
||||
|
@ -330,132 +283,6 @@ module.exports = React.createClass({
|
|||
});
|
||||
},
|
||||
|
||||
_getDirectMessageRooms: function(addr) {
|
||||
const dmRoomMap = new DMRoomMap(MatrixClientPeg.get());
|
||||
const dmRooms = dmRoomMap.getDMRoomsForUserId(addr);
|
||||
const rooms = [];
|
||||
dmRooms.forEach(dmRoom => {
|
||||
let room = MatrixClientPeg.get().getRoom(dmRoom);
|
||||
if (room) {
|
||||
const me = room.getMember(MatrixClientPeg.get().credentials.userId);
|
||||
if (me.membership == 'join') {
|
||||
rooms.push(room);
|
||||
}
|
||||
}
|
||||
});
|
||||
return rooms;
|
||||
},
|
||||
|
||||
_startChat: function(addrs) {
|
||||
if (MatrixClientPeg.get().isGuest()) {
|
||||
dis.dispatch({action: 'view_set_mxid'});
|
||||
return;
|
||||
}
|
||||
|
||||
const addrTexts = addrs.map((addr) => {
|
||||
return addr.address;
|
||||
});
|
||||
|
||||
if (this.props.roomId) {
|
||||
// Invite new user to a room
|
||||
var self = this;
|
||||
inviteMultipleToRoom(this.props.roomId, addrTexts)
|
||||
.then(function(addrs) {
|
||||
var room = MatrixClientPeg.get().getRoom(self.props.roomId);
|
||||
return self._showAnyInviteErrors(addrs, room);
|
||||
})
|
||||
.catch(function(err) {
|
||||
console.error(err.stack);
|
||||
var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
||||
Modal.createDialog(ErrorDialog, {
|
||||
title: _t("Failed to invite"),
|
||||
description: ((err && err.message) ? err.message : _t("Operation failed")),
|
||||
});
|
||||
return null;
|
||||
})
|
||||
.done();
|
||||
} else if (this._isDmChat(addrTexts)) {
|
||||
// Start the DM chat
|
||||
createRoom({dmUserId: addrTexts[0]})
|
||||
.catch(function(err) {
|
||||
console.error(err.stack);
|
||||
var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
||||
Modal.createDialog(ErrorDialog, {
|
||||
title: _t("Failed to invite user"),
|
||||
description: ((err && err.message) ? err.message : _t("Operation failed")),
|
||||
});
|
||||
return null;
|
||||
})
|
||||
.done();
|
||||
} else {
|
||||
// Start multi user chat
|
||||
var self = this;
|
||||
var room;
|
||||
createRoom().then(function(roomId) {
|
||||
room = MatrixClientPeg.get().getRoom(roomId);
|
||||
return inviteMultipleToRoom(roomId, addrTexts);
|
||||
})
|
||||
.then(function(addrs) {
|
||||
return self._showAnyInviteErrors(addrs, room);
|
||||
})
|
||||
.catch(function(err) {
|
||||
console.error(err.stack);
|
||||
var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
||||
Modal.createDialog(ErrorDialog, {
|
||||
title: _t("Failed to invite"),
|
||||
description: ((err && err.message) ? err.message : _t("Operation failed")),
|
||||
});
|
||||
return null;
|
||||
})
|
||||
.done();
|
||||
}
|
||||
|
||||
// Close - this will happen before the above, as that is async
|
||||
this.props.onFinished(true, addrTexts);
|
||||
},
|
||||
|
||||
_isOnInviteList: function(uid) {
|
||||
for (let i = 0; i < this.state.inviteList.length; i++) {
|
||||
if (
|
||||
this.state.inviteList[i].addressType == 'mx' &&
|
||||
this.state.inviteList[i].address.toLowerCase() === uid
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
},
|
||||
|
||||
_isDmChat: function(addrTexts) {
|
||||
if (addrTexts.length === 1 &&
|
||||
getAddressType(addrTexts[0]) === "mx" &&
|
||||
!this.props.roomId
|
||||
) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
},
|
||||
|
||||
_showAnyInviteErrors: function(addrs, room) {
|
||||
// Show user any errors
|
||||
var errorList = [];
|
||||
for (var addr in addrs) {
|
||||
if (addrs.hasOwnProperty(addr) && addrs[addr] === "error") {
|
||||
errorList.push(addr);
|
||||
}
|
||||
}
|
||||
|
||||
if (errorList.length > 0) {
|
||||
var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
||||
Modal.createDialog(ErrorDialog, {
|
||||
title: _t("Failed to invite the following users to the %(roomName)s room:", {roomName: room.name}),
|
||||
description: errorList.join(", "),
|
||||
});
|
||||
}
|
||||
return addrs;
|
||||
},
|
||||
|
||||
_addInputToList: function() {
|
||||
const addressText = this.refs.textinput.value.trim();
|
||||
const addrType = getAddressType(addressText);
|
||||
|
@ -476,15 +303,15 @@ module.exports = React.createClass({
|
|||
}
|
||||
}
|
||||
|
||||
const inviteList = this.state.inviteList.slice();
|
||||
inviteList.push(addrObj);
|
||||
const userList = this.state.userList.slice();
|
||||
userList.push(addrObj);
|
||||
this.setState({
|
||||
inviteList: inviteList,
|
||||
userList: userList,
|
||||
queryList: [],
|
||||
query: "",
|
||||
});
|
||||
if (this._cancelThreepidLookup) this._cancelThreepidLookup();
|
||||
return inviteList;
|
||||
return userList;
|
||||
},
|
||||
|
||||
_lookupThreepid: function(medium, address) {
|
||||
|
@ -495,7 +322,7 @@ module.exports = React.createClass({
|
|||
// not like they leak.
|
||||
this._cancelThreepidLookup = function() {
|
||||
cancelled = true;
|
||||
}
|
||||
};
|
||||
|
||||
// wait a bit to let the user finish typing
|
||||
return Promise.delay(500).then(() => {
|
||||
|
@ -511,7 +338,7 @@ module.exports = React.createClass({
|
|||
if (cancelled) return null;
|
||||
this.setState({
|
||||
queryList: [{
|
||||
// an InviteAddressType
|
||||
// a UserAddressType
|
||||
addressType: medium,
|
||||
address: address,
|
||||
displayName: res.displayname,
|
||||
|
@ -527,20 +354,20 @@ module.exports = React.createClass({
|
|||
const AddressSelector = sdk.getComponent("elements.AddressSelector");
|
||||
this.scrollElement = null;
|
||||
|
||||
var query = [];
|
||||
const query = [];
|
||||
// create the invite list
|
||||
if (this.state.inviteList.length > 0) {
|
||||
var AddressTile = sdk.getComponent("elements.AddressTile");
|
||||
for (let i = 0; i < this.state.inviteList.length; i++) {
|
||||
if (this.state.userList.length > 0) {
|
||||
const AddressTile = sdk.getComponent("elements.AddressTile");
|
||||
for (let i = 0; i < this.state.userList.length; i++) {
|
||||
query.push(
|
||||
<AddressTile key={i} address={this.state.inviteList[i]} canDismiss={true} onDismissed={ this.onDismissed(i) } />
|
||||
<AddressTile key={i} address={this.state.userList[i]} canDismiss={true} onDismissed={ this.onDismissed(i) } />,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Add the query at the end
|
||||
query.push(
|
||||
<textarea key={this.state.inviteList.length}
|
||||
<textarea key={this.state.userList.length}
|
||||
rows="1"
|
||||
id="textinput"
|
||||
ref="textinput"
|
||||
|
@ -555,7 +382,9 @@ module.exports = React.createClass({
|
|||
let error;
|
||||
let addressSelector;
|
||||
if (this.state.error) {
|
||||
error = <div className="mx_ChatInviteDialog_error">{_t("You have entered an invalid contact. Try using their Matrix ID or email address.")}</div>;
|
||||
error = <div className="mx_ChatInviteDialog_error">
|
||||
{_t("You have entered an invalid contact. Try using their Matrix ID or email address.")}
|
||||
</div>;
|
||||
} else if (this.state.searchError) {
|
||||
error = <div className="mx_ChatInviteDialog_error">{this.state.searchError}</div>;
|
||||
} else if (
|
||||
|
@ -598,5 +427,5 @@ module.exports = React.createClass({
|
|||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
},
|
||||
});
|
|
@ -20,7 +20,7 @@ limitations under the License.
|
|||
import React from 'react';
|
||||
import sdk from '../../../index';
|
||||
import classNames from 'classnames';
|
||||
import { InviteAddressType } from './AddressTile';
|
||||
import { UserAddressType } from '../../../UserAddress';
|
||||
|
||||
export default React.createClass({
|
||||
displayName: 'AddressSelector',
|
||||
|
@ -29,7 +29,7 @@ export default React.createClass({
|
|||
onSelected: React.PropTypes.func.isRequired,
|
||||
|
||||
// List of the addresses to display
|
||||
addressList: React.PropTypes.arrayOf(InviteAddressType).isRequired,
|
||||
addressList: React.PropTypes.arrayOf(UserAddressType).isRequired,
|
||||
truncateAt: React.PropTypes.number.isRequired,
|
||||
selected: React.PropTypes.number,
|
||||
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
/*
|
||||
Copyright 2015, 2016 OpenMarket Ltd
|
||||
Copyright 2017 New Vector Ltd
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
|
@ -14,38 +15,19 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import React from 'react';
|
||||
import classNames from 'classnames';
|
||||
import sdk from "../../../index";
|
||||
import MatrixClientPeg from "../../../MatrixClientPeg";
|
||||
import { _t } from '../../../languageHandler';
|
||||
|
||||
// React PropType definition for an object describing
|
||||
// an address that can be invited to a room (which
|
||||
// could be a third party identifier or a matrix ID)
|
||||
// along with some additional information about the
|
||||
// address / target.
|
||||
export const InviteAddressType = React.PropTypes.shape({
|
||||
addressType: React.PropTypes.oneOf([
|
||||
'mx', 'email'
|
||||
]).isRequired,
|
||||
address: React.PropTypes.string.isRequired,
|
||||
displayName: React.PropTypes.string,
|
||||
avatarMxc: React.PropTypes.string,
|
||||
// true if the address is known to be a valid address (eg. is a real
|
||||
// user we've seen) or false otherwise (eg. is just an address the
|
||||
// user has entered)
|
||||
isKnown: React.PropTypes.bool,
|
||||
});
|
||||
import { UserAddressType } from '../../../UserAddress.js';
|
||||
|
||||
|
||||
export default React.createClass({
|
||||
displayName: 'AddressTile',
|
||||
|
||||
propTypes: {
|
||||
address: InviteAddressType.isRequired,
|
||||
address: UserAddressType.isRequired,
|
||||
canDismiss: React.PropTypes.bool,
|
||||
onDismissed: React.PropTypes.func,
|
||||
justified: React.PropTypes.bool,
|
||||
|
|
|
@ -37,6 +37,7 @@ export default class AppPermission extends React.Component {
|
|||
if(wurl && wurl.hostname && (
|
||||
wurl.hostname === 'scalar.vector.im' ||
|
||||
wurl.hostname === 'scalar-staging.riot.im' ||
|
||||
wurl.hostname === 'scalar-develop.riot.im' ||
|
||||
wurl.hostname === 'demo.riot.im' ||
|
||||
wurl.hostname === 'localhost'
|
||||
)) {
|
||||
|
|
|
@ -25,6 +25,7 @@ import Modal from '../../../Modal';
|
|||
import { _t } from '../../../languageHandler';
|
||||
import sdk from '../../../index';
|
||||
import AppPermission from './AppPermission';
|
||||
import AppWarning from './AppWarning';
|
||||
import MessageSpinner from './MessageSpinner';
|
||||
import WidgetUtils from '../../../WidgetUtils';
|
||||
|
||||
|
@ -70,6 +71,18 @@ export default React.createClass({
|
|||
return scalarUrl && this.props.url.startsWith(scalarUrl);
|
||||
},
|
||||
|
||||
isMixedContent: function() {
|
||||
const parentContentProtocol = window.location.protocol;
|
||||
const u = url.parse(this.props.url);
|
||||
const childContentProtocol = u.protocol;
|
||||
if (parentContentProtocol === 'https:' && childContentProtocol !== 'https:') {
|
||||
console.warn("Refusing to load mixed-content app:",
|
||||
parentContentProtocol, childContentProtocol, window.location, this.props.url);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
|
||||
componentWillMount: function() {
|
||||
if (!this.isScalarUrl()) {
|
||||
return;
|
||||
|
@ -110,7 +123,7 @@ export default React.createClass({
|
|||
console.log("Edit widget ID ", this.props.id);
|
||||
const IntegrationsManager = sdk.getComponent("views.settings.IntegrationsManager");
|
||||
const src = this._scalarClient.getScalarInterfaceUrlForRoom(this.props.room.roomId, 'type_' + this.props.type);
|
||||
Modal.createDialog(IntegrationsManager, {
|
||||
Modal.createTrackedDialog('Integrations Manager', '', IntegrationsManager, {
|
||||
src: src,
|
||||
}, "mx_IntegrationsManager");
|
||||
},
|
||||
|
@ -197,16 +210,26 @@ export default React.createClass({
|
|||
</div>
|
||||
);
|
||||
} else if (this.state.hasPermissionToLoad == true) {
|
||||
appTileBody = (
|
||||
<div className="mx_AppTileBody">
|
||||
<iframe
|
||||
ref="appFrame"
|
||||
src={safeWidgetUrl}
|
||||
allowFullScreen="true"
|
||||
sandbox={sandboxFlags}
|
||||
></iframe>
|
||||
</div>
|
||||
);
|
||||
if (this.isMixedContent()) {
|
||||
appTileBody = (
|
||||
<div className="mx_AppTileBody">
|
||||
<AppWarning
|
||||
errorMsg="Error - Mixed content"
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
} else {
|
||||
appTileBody = (
|
||||
<div className="mx_AppTileBody">
|
||||
<iframe
|
||||
ref="appFrame"
|
||||
src={safeWidgetUrl}
|
||||
allowFullScreen="true"
|
||||
sandbox={sandboxFlags}
|
||||
></iframe>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
} else {
|
||||
appTileBody = (
|
||||
<div className="mx_AppTileBody">
|
||||
|
|
25
src/components/views/elements/AppWarning.js
Normal file
25
src/components/views/elements/AppWarning.js
Normal file
|
@ -0,0 +1,25 @@
|
|||
import React from 'react'; // eslint-disable-line no-unused-vars
|
||||
import PropTypes from 'prop-types';
|
||||
import { _t } from '../../../languageHandler';
|
||||
|
||||
const AppWarning = (props) => {
|
||||
return (
|
||||
<div className='mx_AppPermissionWarning'>
|
||||
<div className='mx_AppPermissionWarningImage'>
|
||||
<img src='img/warning.svg' alt={_t('Warning!')}/>
|
||||
</div>
|
||||
<div className='mx_AppPermissionWarningText'>
|
||||
<span className='mx_AppPermissionWarningTextLabel'>{props.errorMsg}</span>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
AppWarning.propTypes = {
|
||||
errorMsg: PropTypes.string,
|
||||
};
|
||||
AppWarning.defaultProps = {
|
||||
errorMsg: 'Error',
|
||||
};
|
||||
|
||||
export default AppWarning;
|
|
@ -52,7 +52,7 @@ export default React.createClass({
|
|||
|
||||
onVerifyClick: function() {
|
||||
const DeviceVerifyDialog = sdk.getComponent('views.dialogs.DeviceVerifyDialog');
|
||||
Modal.createDialog(DeviceVerifyDialog, {
|
||||
Modal.createTrackedDialog('Device Verify Dialog', '', DeviceVerifyDialog, {
|
||||
userId: this.props.userId,
|
||||
device: this.state.device,
|
||||
});
|
||||
|
|
|
@ -15,6 +15,7 @@ limitations under the License.
|
|||
*/
|
||||
import React from 'react';
|
||||
import sdk from '../../../index';
|
||||
import dis from '../../../dispatcher';
|
||||
import classNames from 'classnames';
|
||||
import { Room, RoomMember } from 'matrix-js-sdk';
|
||||
import PropTypes from 'prop-types';
|
||||
|
@ -47,6 +48,8 @@ const Pill = React.createClass({
|
|||
inMessage: PropTypes.bool,
|
||||
// The room in which this pill is being rendered
|
||||
room: PropTypes.instanceOf(Room),
|
||||
// Whether to include an avatar in the pill
|
||||
shouldShowPillAvatar: PropTypes.bool,
|
||||
},
|
||||
|
||||
getInitialState() {
|
||||
|
@ -138,6 +141,12 @@ const Pill = React.createClass({
|
|||
});
|
||||
},
|
||||
|
||||
onUserPillClicked: function() {
|
||||
dis.dispatch({
|
||||
action: 'view_user',
|
||||
member: this.state.member,
|
||||
});
|
||||
},
|
||||
render: function() {
|
||||
const MemberAvatar = sdk.getComponent('avatars.MemberAvatar');
|
||||
const RoomAvatar = sdk.getComponent('avatars.RoomAvatar');
|
||||
|
@ -148,6 +157,8 @@ const Pill = React.createClass({
|
|||
let linkText = resource;
|
||||
let pillClass;
|
||||
let userId;
|
||||
let href = this.props.url;
|
||||
let onClick;
|
||||
switch (this.state.pillType) {
|
||||
case Pill.TYPE_USER_MENTION: {
|
||||
// If this user is not a member of this room, default to the empty member
|
||||
|
@ -155,8 +166,12 @@ const Pill = React.createClass({
|
|||
if (member) {
|
||||
userId = member.userId;
|
||||
linkText = member.rawDisplayName.replace(' (IRC)', ''); // FIXME when groups are done
|
||||
avatar = <MemberAvatar member={member} width={16} height={16}/>;
|
||||
if (this.props.shouldShowPillAvatar) {
|
||||
avatar = <MemberAvatar member={member} width={16} height={16}/>;
|
||||
}
|
||||
pillClass = 'mx_UserPill';
|
||||
href = null;
|
||||
onClick = this.onUserPillClicked.bind(this);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
@ -164,7 +179,9 @@ const Pill = React.createClass({
|
|||
const room = this.state.room;
|
||||
if (room) {
|
||||
linkText = (room ? getDisplayAliasForRoom(room) : null) || resource;
|
||||
avatar = <RoomAvatar room={room} width={16} height={16}/>;
|
||||
if (this.props.shouldShowPillAvatar) {
|
||||
avatar = <RoomAvatar room={room} width={16} height={16}/>;
|
||||
}
|
||||
pillClass = 'mx_RoomPill';
|
||||
}
|
||||
}
|
||||
|
@ -177,7 +194,7 @@ const Pill = React.createClass({
|
|||
|
||||
if (this.state.pillType) {
|
||||
return this.props.inMessage ?
|
||||
<a className={classes} href={this.props.url} title={resource} data-offset-key={this.props.offsetKey}>
|
||||
<a className={classes} href={href} onClick={onClick} title={resource} data-offset-key={this.props.offsetKey}>
|
||||
{avatar}
|
||||
{linkText}
|
||||
</a> :
|
||||
|
|
|
@ -17,7 +17,7 @@ limitations under the License.
|
|||
'use strict';
|
||||
|
||||
import React from 'react';
|
||||
import { _t } from '../../../languageHandler';
|
||||
import { _t, _tJsx } from '../../../languageHandler';
|
||||
|
||||
var DIV_ID = 'mx_recaptcha';
|
||||
|
||||
|
@ -66,7 +66,11 @@ module.exports = React.createClass({
|
|||
// * jumping straight to a hosted captcha page (but we don't support that yet)
|
||||
// * embedding the captcha in an iframe (if that works)
|
||||
// * using a better captcha lib
|
||||
warning.innerHTML = "Robot check is currently unavailable on desktop - please use a <a href='https://riot.im/app'>web browser</a>.";
|
||||
warning.innerHTML = _tJsx(
|
||||
"Robot check is currently unavailable on desktop - please use a <a>web browser</a>",
|
||||
/<a>(.*?)<\/a>/,
|
||||
(sub) => { return "<a href='https://riot.im/app'>{ sub }</a>"; }
|
||||
);
|
||||
this.refs.recaptchaContainer.appendChild(warning);
|
||||
}
|
||||
else {
|
||||
|
|
|
@ -95,7 +95,7 @@ module.exports = React.createClass({
|
|||
if (this.allFieldsValid()) {
|
||||
if (this.refs.email.value == '') {
|
||||
var QuestionDialog = sdk.getComponent("dialogs.QuestionDialog");
|
||||
Modal.createDialog(QuestionDialog, {
|
||||
Modal.createTrackedDialog('If you don\'t specify an email address...', '', QuestionDialog, {
|
||||
title: _t("Warning!"),
|
||||
description:
|
||||
<div>
|
||||
|
|
|
@ -122,7 +122,7 @@ module.exports = React.createClass({
|
|||
|
||||
showHelpPopup: function() {
|
||||
var CustomServerDialog = sdk.getComponent('login.CustomServerDialog');
|
||||
Modal.createDialog(CustomServerDialog);
|
||||
Modal.createTrackedDialog('Custom Server Dialog', '', CustomServerDialog);
|
||||
},
|
||||
|
||||
render: function() {
|
||||
|
|
|
@ -282,8 +282,8 @@ module.exports = React.createClass({
|
|||
});
|
||||
}).catch((err) => {
|
||||
console.warn("Unable to decrypt attachment: ", err);
|
||||
Modal.createDialog(ErrorDialog, {
|
||||
title: _t("Error"),
|
||||
Modal.createTrackedDialog('Error decrypting attachment', '', ErrorDialog, {
|
||||
title: _t("Error"),
|
||||
description: _t("Error decrypting attachment"),
|
||||
});
|
||||
}).finally(() => {
|
||||
|
|
|
@ -170,6 +170,7 @@ module.exports = React.createClass({
|
|||
},
|
||||
|
||||
pillifyLinks: function(nodes) {
|
||||
const shouldShowPillAvatar = !UserSettingsStore.getSyncedSetting("Pill.shouldHidePillAvatar", false);
|
||||
for (let i = 0; i < nodes.length; i++) {
|
||||
const node = nodes[i];
|
||||
if (node.tagName === "A" && node.getAttribute("href")) {
|
||||
|
@ -181,7 +182,12 @@ module.exports = React.createClass({
|
|||
const pillContainer = document.createElement('span');
|
||||
|
||||
const room = MatrixClientPeg.get().getRoom(this.props.mxEvent.getRoomId());
|
||||
const pill = <Pill url={href} inMessage={true} room={room}/>;
|
||||
const pill = <Pill
|
||||
url={href}
|
||||
inMessage={true}
|
||||
room={room}
|
||||
shouldShowPillAvatar={shouldShowPillAvatar}
|
||||
/>;
|
||||
|
||||
ReactDOM.render(pill, pillContainer);
|
||||
node.parentNode.replaceChild(pillContainer, node);
|
||||
|
@ -269,18 +275,21 @@ module.exports = React.createClass({
|
|||
},
|
||||
|
||||
getEventTileOps: function() {
|
||||
var self = this;
|
||||
return {
|
||||
isWidgetHidden: function() {
|
||||
return self.state.widgetHidden;
|
||||
isWidgetHidden: () => {
|
||||
return this.state.widgetHidden;
|
||||
},
|
||||
|
||||
unhideWidget: function() {
|
||||
self.setState({ widgetHidden: false });
|
||||
unhideWidget: () => {
|
||||
this.setState({ widgetHidden: false });
|
||||
if (global.localStorage) {
|
||||
global.localStorage.removeItem("hide_preview_" + self.props.mxEvent.getId());
|
||||
global.localStorage.removeItem("hide_preview_" + this.props.mxEvent.getId());
|
||||
}
|
||||
},
|
||||
|
||||
getInnerText: () => {
|
||||
return this.refs.content.innerText;
|
||||
}
|
||||
};
|
||||
},
|
||||
|
||||
|
@ -299,7 +308,7 @@ module.exports = React.createClass({
|
|||
let completeUrl = scalarClient.getStarterLink(starterLink);
|
||||
let QuestionDialog = sdk.getComponent("dialogs.QuestionDialog");
|
||||
let integrationsUrl = SdkConfig.get().integrations_ui_url;
|
||||
Modal.createDialog(QuestionDialog, {
|
||||
Modal.createTrackedDialog('Add an integration', '', QuestionDialog, {
|
||||
title: _t("Add an Integration"),
|
||||
description:
|
||||
<div>
|
||||
|
|
|
@ -154,7 +154,7 @@ module.exports = React.createClass({
|
|||
}
|
||||
else {
|
||||
var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
||||
Modal.createDialog(ErrorDialog, {
|
||||
Modal.createTrackedDialog('Invalid alias format', '', ErrorDialog, {
|
||||
title: _t('Invalid alias format'),
|
||||
description: _t('\'%(alias)s\' is not a valid format for an alias', { alias: alias }),
|
||||
});
|
||||
|
@ -170,7 +170,7 @@ module.exports = React.createClass({
|
|||
}
|
||||
else {
|
||||
var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
||||
Modal.createDialog(ErrorDialog, {
|
||||
Modal.createTrackedDialog('Invalid address format', '', ErrorDialog, {
|
||||
title: _t('Invalid address format'),
|
||||
description: _t('\'%(alias)s\' is not a valid format for an address', { alias: alias }),
|
||||
});
|
||||
|
|
|
@ -166,7 +166,7 @@ module.exports = React.createClass({
|
|||
const src = (this.scalarClient !== null && this.scalarClient.hasCredentials()) ?
|
||||
this.scalarClient.getScalarInterfaceUrlForRoom(this.props.room.roomId, 'add_integ') :
|
||||
null;
|
||||
Modal.createDialog(IntegrationsManager, {
|
||||
Modal.createTrackedDialog('Integrations Manager', '', IntegrationsManager, {
|
||||
src: src,
|
||||
}, "mx_IntegrationsManager");
|
||||
},
|
||||
|
|
|
@ -172,7 +172,7 @@ export default class Autocomplete extends React.Component {
|
|||
}
|
||||
|
||||
hide() {
|
||||
this.setState({hide: true, selectionOffset: 0});
|
||||
this.setState({hide: true, selectionOffset: 0, completions: [], completionList: []});
|
||||
}
|
||||
|
||||
forceComplete() {
|
||||
|
|
|
@ -155,7 +155,9 @@ module.exports = withMatrixClient(React.createClass({
|
|||
},
|
||||
|
||||
componentWillReceiveProps: function(nextProps) {
|
||||
if (nextProps.mxEvent !== this.props.mxEvent) {
|
||||
// re-check the sender verification as outgoing events progress through
|
||||
// the send process.
|
||||
if (nextProps.eventSendStatus !== this.props.eventSendStatus) {
|
||||
this._verifyEvent(nextProps.mxEvent);
|
||||
}
|
||||
},
|
||||
|
@ -367,7 +369,7 @@ module.exports = withMatrixClient(React.createClass({
|
|||
onCryptoClicked: function(e) {
|
||||
var event = this.props.mxEvent;
|
||||
|
||||
Modal.createDialogAsync((cb) => {
|
||||
Modal.createTrackedDialogAsync('Encrypted Event Dialog', '', (cb) => {
|
||||
require(['../../../async-components/views/dialogs/EncryptedEventDialog'], cb);
|
||||
}, {
|
||||
event: event,
|
||||
|
@ -386,6 +388,36 @@ module.exports = withMatrixClient(React.createClass({
|
|||
});
|
||||
},
|
||||
|
||||
_renderE2EPadlock: function() {
|
||||
const ev = this.props.mxEvent;
|
||||
const props = {onClick: this.onCryptoClicked};
|
||||
|
||||
|
||||
if (ev.getContent().msgtype === 'm.bad.encrypted') {
|
||||
return <E2ePadlockUndecryptable {...props}/>;
|
||||
} else if (ev.isEncrypted()) {
|
||||
if (this.state.verified) {
|
||||
return <E2ePadlockVerified {...props}/>;
|
||||
} else {
|
||||
return <E2ePadlockUnverified {...props}/>;
|
||||
}
|
||||
} else {
|
||||
// XXX: if the event is being encrypted (ie eventSendStatus ===
|
||||
// encrypting), it might be nice to show something other than the
|
||||
// open padlock?
|
||||
|
||||
// if the event is not encrypted, but it's an e2e room, show the
|
||||
// open padlock
|
||||
const e2eEnabled = this.props.matrixClient.isRoomEncrypted(ev.getRoomId());
|
||||
if (e2eEnabled) {
|
||||
return <E2ePadlockUnencrypted {...props}/>;
|
||||
}
|
||||
}
|
||||
|
||||
// no padlock needed
|
||||
return null;
|
||||
},
|
||||
|
||||
render: function() {
|
||||
var MessageTimestamp = sdk.getComponent('messages.MessageTimestamp');
|
||||
var SenderProfile = sdk.getComponent('messages.SenderProfile');
|
||||
|
@ -407,7 +439,6 @@ module.exports = withMatrixClient(React.createClass({
|
|||
throw new Error("Event type not supported");
|
||||
}
|
||||
|
||||
var e2eEnabled = this.props.matrixClient.isRoomEncrypted(this.props.mxEvent.getRoomId());
|
||||
var isSending = (['sending', 'queued', 'encrypting'].indexOf(this.props.eventSendStatus) !== -1);
|
||||
const isRedacted = (eventType === 'm.room.message') && this.props.isRedacted;
|
||||
|
||||
|
@ -485,26 +516,7 @@ module.exports = withMatrixClient(React.createClass({
|
|||
const editButton = (
|
||||
<span className="mx_EventTile_editButton" title={ _t("Options") } onClick={this.onEditClicked} />
|
||||
);
|
||||
let e2e;
|
||||
// cosmetic padlocks:
|
||||
if ((e2eEnabled && this.props.eventSendStatus) || this.props.mxEvent.getType() === 'm.room.encryption') {
|
||||
e2e = <img style={{ cursor: 'initial', marginLeft: '-1px' }} className="mx_EventTile_e2eIcon" alt={_t("Encrypted by a verified device")} src="img/e2e-verified.svg" width="10" height="12" />;
|
||||
}
|
||||
// real padlocks
|
||||
else if (this.props.mxEvent.isEncrypted() || (e2eEnabled && this.props.eventSendStatus)) {
|
||||
if (this.props.mxEvent.getContent().msgtype === 'm.bad.encrypted') {
|
||||
e2e = <img onClick={ this.onCryptoClicked } className="mx_EventTile_e2eIcon" alt={_t("Undecryptable")} src="img/e2e-blocked.svg" width="12" height="12" style={{ marginLeft: "-1px" }} />;
|
||||
}
|
||||
else if (this.state.verified == true || (e2eEnabled && this.props.eventSendStatus)) {
|
||||
e2e = <img onClick={ this.onCryptoClicked } className="mx_EventTile_e2eIcon" alt={_t("Encrypted by a verified device")} src="img/e2e-verified.svg" width="10" height="12"/>;
|
||||
}
|
||||
else {
|
||||
e2e = <img onClick={ this.onCryptoClicked } className="mx_EventTile_e2eIcon" alt={_t("Encrypted by an unverified device")} src="img/e2e-warning.svg" width="15" height="12" style={{ marginLeft: "-2px" }}/>;
|
||||
}
|
||||
}
|
||||
else if (e2eEnabled) {
|
||||
e2e = <img onClick={ this.onCryptoClicked } className="mx_EventTile_e2eIcon" alt={_t("Unencrypted message")} src="img/e2e-unencrypted.svg" width="12" height="12"/>;
|
||||
}
|
||||
|
||||
const timestamp = this.props.mxEvent.getTs() ?
|
||||
<MessageTimestamp showTwelveHour={this.props.isTwelveHour} ts={this.props.mxEvent.getTs()} /> : null;
|
||||
|
||||
|
@ -572,7 +584,7 @@ module.exports = withMatrixClient(React.createClass({
|
|||
<a href={ permalink } onClick={this.onPermalinkClicked}>
|
||||
{ timestamp }
|
||||
</a>
|
||||
{ e2e }
|
||||
{ this._renderE2EPadlock() }
|
||||
<EventTileType ref="tile"
|
||||
mxEvent={this.props.mxEvent}
|
||||
highlights={this.props.highlights}
|
||||
|
@ -597,3 +609,39 @@ module.exports.haveTileForEvent = function(e) {
|
|||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
function E2ePadlockUndecryptable(props) {
|
||||
return (
|
||||
<E2ePadlock alt={_t("Undecryptable")}
|
||||
src="img/e2e-blocked.svg" width="12" height="12"
|
||||
style={{ marginLeft: "-1px" }} {...props} />
|
||||
);
|
||||
}
|
||||
|
||||
function E2ePadlockVerified(props) {
|
||||
return (
|
||||
<E2ePadlock alt={_t("Encrypted by a verified device")}
|
||||
src="img/e2e-verified.svg" width="10" height="12"
|
||||
{...props} />
|
||||
);
|
||||
}
|
||||
|
||||
function E2ePadlockUnverified(props) {
|
||||
return (
|
||||
<E2ePadlock alt={_t("Encrypted by an unverified device")}
|
||||
src="img/e2e-warning.svg" width="15" height="12"
|
||||
style={{ marginLeft: "-2px" }} {...props} />
|
||||
);
|
||||
}
|
||||
|
||||
function E2ePadlockUnencrypted(props) {
|
||||
return (
|
||||
<E2ePadlock alt={_t("Unencrypted message")}
|
||||
src="img/e2e-unencrypted.svg" width="12" height="12"
|
||||
{...props} />
|
||||
);
|
||||
}
|
||||
|
||||
function E2ePadlock(props) {
|
||||
return <img className="mx_EventTile_e2eIcon" {...props} />;
|
||||
}
|
||||
|
|
|
@ -229,7 +229,7 @@ module.exports = withMatrixClient(React.createClass({
|
|||
const membership = this.props.member.membership;
|
||||
const kickLabel = membership === "invite" ? _t("Disinvite") : _t("Kick");
|
||||
const ConfirmUserActionDialog = sdk.getComponent("dialogs.ConfirmUserActionDialog");
|
||||
Modal.createDialog(ConfirmUserActionDialog, {
|
||||
Modal.createTrackedDialog('Confirm User Action Dialog', 'onKick', ConfirmUserActionDialog, {
|
||||
member: this.props.member,
|
||||
action: kickLabel,
|
||||
askReason: membership == "join",
|
||||
|
@ -248,7 +248,7 @@ module.exports = withMatrixClient(React.createClass({
|
|||
}, function(err) {
|
||||
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
||||
console.error("Kick error: " + err);
|
||||
Modal.createDialog(ErrorDialog, {
|
||||
Modal.createTrackedDialog('Failed to kick', '', ErrorDialog, {
|
||||
title: _t("Failed to kick"),
|
||||
description: ((err && err.message) ? err.message : "Operation failed"),
|
||||
});
|
||||
|
@ -262,7 +262,7 @@ module.exports = withMatrixClient(React.createClass({
|
|||
|
||||
onBanOrUnban: function() {
|
||||
const ConfirmUserActionDialog = sdk.getComponent("dialogs.ConfirmUserActionDialog");
|
||||
Modal.createDialog(ConfirmUserActionDialog, {
|
||||
Modal.createTrackedDialog('Confirm User Action Dialog', 'onBanOrUnban', ConfirmUserActionDialog, {
|
||||
member: this.props.member,
|
||||
action: this.props.member.membership == 'ban' ? _t("Unban") : _t("Ban"),
|
||||
askReason: this.props.member.membership != 'ban',
|
||||
|
@ -290,7 +290,7 @@ module.exports = withMatrixClient(React.createClass({
|
|||
}, function(err) {
|
||||
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
||||
console.error("Ban error: " + err);
|
||||
Modal.createDialog(ErrorDialog, {
|
||||
Modal.createTrackedDialog('Failed to ban user', '', ErrorDialog, {
|
||||
title: _t("Error"),
|
||||
description: _t("Failed to ban user"),
|
||||
});
|
||||
|
@ -340,7 +340,7 @@ module.exports = withMatrixClient(React.createClass({
|
|||
console.log("Mute toggle success");
|
||||
}, function(err) {
|
||||
console.error("Mute error: " + err);
|
||||
Modal.createDialog(ErrorDialog, {
|
||||
Modal.createTrackedDialog('Failed to mute user', '', ErrorDialog, {
|
||||
title: _t("Error"),
|
||||
description: _t("Failed to mute user"),
|
||||
});
|
||||
|
@ -385,7 +385,7 @@ module.exports = withMatrixClient(React.createClass({
|
|||
dis.dispatch({action: 'view_set_mxid'});
|
||||
} else {
|
||||
console.error("Toggle moderator error:" + err);
|
||||
Modal.createDialog(ErrorDialog, {
|
||||
Modal.createTrackedDialog('Failed to toggle moderator status', '', ErrorDialog, {
|
||||
title: _t("Error"),
|
||||
description: _t("Failed to toggle moderator status"),
|
||||
});
|
||||
|
@ -406,7 +406,7 @@ module.exports = withMatrixClient(React.createClass({
|
|||
}, function(err) {
|
||||
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
||||
console.error("Failed to change power level " + err);
|
||||
Modal.createDialog(ErrorDialog, {
|
||||
Modal.createTrackedDialog('Failed to change power level', '', ErrorDialog, {
|
||||
title: _t("Error"),
|
||||
description: _t("Failed to change power level"),
|
||||
});
|
||||
|
@ -435,7 +435,7 @@ module.exports = withMatrixClient(React.createClass({
|
|||
var myPower = powerLevelEvent.getContent().users[this.props.matrixClient.credentials.userId];
|
||||
if (parseInt(myPower) === parseInt(powerLevel)) {
|
||||
var QuestionDialog = sdk.getComponent("dialogs.QuestionDialog");
|
||||
Modal.createDialog(QuestionDialog, {
|
||||
Modal.createTrackedDialog('Promote to PL100 Warning', '', QuestionDialog, {
|
||||
title: _t("Warning!"),
|
||||
description:
|
||||
<div>
|
||||
|
|
|
@ -99,7 +99,7 @@ export default class MessageComposer extends React.Component {
|
|||
</li>);
|
||||
}
|
||||
|
||||
Modal.createDialog(QuestionDialog, {
|
||||
Modal.createTrackedDialog('Upload Files confirmation', '', QuestionDialog, {
|
||||
title: _t('Upload Files'),
|
||||
description: (
|
||||
<div>
|
||||
|
|
|
@ -31,6 +31,7 @@ import KeyCode from '../../../KeyCode';
|
|||
import Modal from '../../../Modal';
|
||||
import sdk from '../../../index';
|
||||
import { _t } from '../../../languageHandler';
|
||||
import Analytics from '../../../Analytics';
|
||||
|
||||
import dis from '../../../dispatcher';
|
||||
import UserSettingsStore from '../../../UserSettingsStore';
|
||||
|
@ -97,20 +98,39 @@ export default class MessageComposerInput extends React.Component {
|
|||
onInputStateChanged: React.PropTypes.func,
|
||||
};
|
||||
|
||||
static getKeyBinding(e: SyntheticKeyboardEvent): string {
|
||||
// C-m => Toggles between rich text and markdown modes
|
||||
if (e.keyCode === KeyCode.KEY_M && KeyBindingUtil.isCtrlKeyCommand(e)) {
|
||||
return 'toggle-mode';
|
||||
static getKeyBinding(ev: SyntheticKeyboardEvent): string {
|
||||
const isMac = navigator.platform.toUpperCase().indexOf('MAC') >= 0;
|
||||
let ctrlCmdOnly;
|
||||
if (isMac) {
|
||||
ctrlCmdOnly = ev.metaKey && !ev.altKey && !ev.ctrlKey && !ev.shiftKey;
|
||||
} else {
|
||||
ctrlCmdOnly = ev.ctrlKey && !ev.altKey && !ev.metaKey && !ev.shiftKey;
|
||||
}
|
||||
|
||||
// Allow opening of dev tools. getDefaultKeyBinding would be 'italic' for KEY_I
|
||||
if (e.keyCode === KeyCode.KEY_I && e.shiftKey && e.ctrlKey) {
|
||||
// When null is returned, draft-js will NOT preventDefault, allowing dev tools
|
||||
// to be toggled when the editor is focussed
|
||||
return null;
|
||||
// Restrict a subset of key bindings to ONLY having ctrl/meta* pressed and
|
||||
// importantly NOT having alt, shift, meta/ctrl* pressed. draft-js does not
|
||||
// handle this in `getDefaultKeyBinding` so we do it ourselves here.
|
||||
//
|
||||
// * if macOS, read second option
|
||||
const ctrlCmdCommand = {
|
||||
// C-m => Toggles between rich text and markdown modes
|
||||
[KeyCode.KEY_M]: 'toggle-mode',
|
||||
[KeyCode.KEY_B]: 'bold',
|
||||
[KeyCode.KEY_I]: 'italic',
|
||||
[KeyCode.KEY_U]: 'underline',
|
||||
[KeyCode.KEY_J]: 'code',
|
||||
[KeyCode.KEY_O]: 'split-block',
|
||||
}[ev.keyCode];
|
||||
|
||||
if (ctrlCmdCommand) {
|
||||
if (!ctrlCmdOnly) {
|
||||
return null;
|
||||
}
|
||||
return ctrlCmdCommand;
|
||||
}
|
||||
|
||||
return getDefaultKeyBinding(e);
|
||||
// Handle keys such as return, left and right arrows etc.
|
||||
return getDefaultKeyBinding(ev);
|
||||
}
|
||||
|
||||
static getBlockStyle(block: ContentBlock): ?string {
|
||||
|
@ -141,6 +161,8 @@ export default class MessageComposerInput extends React.Component {
|
|||
|
||||
const isRichtextEnabled = UserSettingsStore.getSyncedSetting('MessageComposerInput.isRichTextEnabled', false);
|
||||
|
||||
Analytics.setRichtextMode(isRichtextEnabled);
|
||||
|
||||
this.state = {
|
||||
// whether we're in rich text or markdown mode
|
||||
isRichtextEnabled,
|
||||
|
@ -165,17 +187,18 @@ export default class MessageComposerInput extends React.Component {
|
|||
this.client = MatrixClientPeg.get();
|
||||
}
|
||||
|
||||
findLinkEntities(contentBlock, callback) {
|
||||
findLinkEntities(contentState: ContentState, contentBlock: ContentBlock, callback) {
|
||||
contentBlock.findEntityRanges(
|
||||
(character) => {
|
||||
const entityKey = character.getEntity();
|
||||
return (
|
||||
entityKey !== null &&
|
||||
Entity.get(entityKey).getType() === 'LINK'
|
||||
contentState.getEntity(entityKey).getType() === 'LINK'
|
||||
);
|
||||
}, callback,
|
||||
);
|
||||
}
|
||||
|
||||
/*
|
||||
* "Does the right thing" to create an EditorState, based on:
|
||||
* - whether we've got rich text mode enabled
|
||||
|
@ -184,13 +207,19 @@ export default class MessageComposerInput extends React.Component {
|
|||
createEditorState(richText: boolean, contentState: ?ContentState): EditorState {
|
||||
const decorators = richText ? RichText.getScopedRTDecorators(this.props) :
|
||||
RichText.getScopedMDDecorators(this.props);
|
||||
const shouldShowPillAvatar = !UserSettingsStore.getSyncedSetting("Pill.shouldHidePillAvatar", false);
|
||||
decorators.push({
|
||||
strategy: this.findLinkEntities.bind(this),
|
||||
component: (entityProps) => {
|
||||
const Pill = sdk.getComponent('elements.Pill');
|
||||
const {url} = Entity.get(entityProps.entityKey).getData();
|
||||
const {url} = entityProps.contentState.getEntity(entityProps.entityKey).getData();
|
||||
if (Pill.isPillUrl(url)) {
|
||||
return <Pill url={url} room={this.props.room} offsetKey={entityProps.offsetKey}/>;
|
||||
return <Pill
|
||||
url={url}
|
||||
room={this.props.room}
|
||||
offsetKey={entityProps.offsetKey}
|
||||
shouldShowPillAvatar={shouldShowPillAvatar}
|
||||
/>;
|
||||
}
|
||||
|
||||
return (
|
||||
|
@ -243,7 +272,8 @@ export default class MessageComposerInput extends React.Component {
|
|||
// paths for inserting a user pill is not fun
|
||||
const selection = this.state.editorState.getSelection();
|
||||
const member = this.props.room.getMember(payload.user_id);
|
||||
const completion = member ? member.name.replace(' (IRC)', '') : payload.user_id;
|
||||
const completion = member ?
|
||||
member.rawDisplayName.replace(' (IRC)', '') : payload.user_id;
|
||||
this.setDisplayedCompletion({
|
||||
completion,
|
||||
selection,
|
||||
|
@ -253,10 +283,12 @@ export default class MessageComposerInput extends React.Component {
|
|||
}
|
||||
break;
|
||||
case 'quote': {
|
||||
let {body, formatted_body} = payload.event.getContent();
|
||||
formatted_body = formatted_body || escape(body);
|
||||
if (formatted_body) {
|
||||
let content = RichText.htmlToContentState(`<blockquote>${formatted_body}</blockquote>`);
|
||||
/// XXX: Not doing rich-text quoting from formatted-body because draft-js
|
||||
/// has regressed such that when links are quoted, errors are thrown. See
|
||||
/// https://github.com/vector-im/riot-web/issues/4756.
|
||||
let body = escape(payload.text);
|
||||
if (body) {
|
||||
let content = RichText.htmlToContentState(`<blockquote>${body}</blockquote>`);
|
||||
if (!this.state.isRichtextEnabled) {
|
||||
content = ContentState.createFromText(RichText.stateToMarkdown(content));
|
||||
}
|
||||
|
@ -427,6 +459,19 @@ export default class MessageComposerInput extends React.Component {
|
|||
state.editorState = RichText.attachImmutableEntitiesToEmoji(
|
||||
state.editorState);
|
||||
|
||||
// Hide the autocomplete if the cursor location changes but the plaintext
|
||||
// content stays the same. We don't hide if the pt has changed because the
|
||||
// autocomplete will probably have different completions to show.
|
||||
if (
|
||||
!state.editorState.getSelection().equals(
|
||||
this.state.editorState.getSelection()
|
||||
)
|
||||
&& state.editorState.getCurrentContent().getPlainText() ===
|
||||
this.state.editorState.getCurrentContent().getPlainText()
|
||||
) {
|
||||
this.autocomplete.hide();
|
||||
}
|
||||
|
||||
if (state.editorState.getCurrentContent().hasText()) {
|
||||
this.onTypingActivity();
|
||||
} else {
|
||||
|
@ -483,6 +528,8 @@ export default class MessageComposerInput extends React.Component {
|
|||
contentState = ContentState.createFromText(markdown);
|
||||
}
|
||||
|
||||
Analytics.setRichtextMode(enabled);
|
||||
|
||||
this.setState({
|
||||
editorState: this.createEditorState(enabled, contentState),
|
||||
isRichtextEnabled: enabled,
|
||||
|
@ -515,7 +562,8 @@ export default class MessageComposerInput extends React.Component {
|
|||
newState = RichUtils.toggleInlineStyle(this.state.editorState, 'STRIKETHROUGH');
|
||||
} else if (shouldToggleBlockFormat) {
|
||||
const currentStartOffset = this.state.editorState.getSelection().getStartOffset();
|
||||
if (currentStartOffset === 0) {
|
||||
const currentEndOffset = this.state.editorState.getSelection().getEndOffset();
|
||||
if (currentStartOffset === 0 && currentEndOffset === 0) {
|
||||
// Toggle current block type (setting it to 'unstyled')
|
||||
newState = RichUtils.toggleBlockType(this.state.editorState, currentBlockType);
|
||||
}
|
||||
|
@ -673,7 +721,7 @@ export default class MessageComposerInput extends React.Component {
|
|||
}, function(err) {
|
||||
console.error("Command failure: %s", err);
|
||||
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
||||
Modal.createDialog(ErrorDialog, {
|
||||
Modal.createTrackedDialog('Server error', '', ErrorDialog, {
|
||||
title: _t("Server error"),
|
||||
description: ((err && err.message) ? err.message : _t("Server unavailable, overloaded, or something else went wrong.")),
|
||||
});
|
||||
|
@ -681,7 +729,8 @@ export default class MessageComposerInput extends React.Component {
|
|||
} else if (cmd.error) {
|
||||
console.error(cmd.error);
|
||||
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
||||
Modal.createDialog(ErrorDialog, {
|
||||
// TODO possibly track which command they ran (not its Arguments) here
|
||||
Modal.createTrackedDialog('Command error', '', ErrorDialog, {
|
||||
title: _t("Command error"),
|
||||
description: cmd.error,
|
||||
});
|
||||
|
@ -713,7 +762,7 @@ export default class MessageComposerInput extends React.Component {
|
|||
const hasLink = blocks.some((block) => {
|
||||
return block.getCharacterList().filter((c) => {
|
||||
const entityKey = c.getEntity();
|
||||
return entityKey && Entity.get(entityKey).getType() === 'LINK';
|
||||
return entityKey && contentState.getEntity(entityKey).getType() === 'LINK';
|
||||
}).size > 0;
|
||||
});
|
||||
shouldSendHTML = hasLink;
|
||||
|
@ -734,8 +783,8 @@ export default class MessageComposerInput extends React.Component {
|
|||
const pt = contentState.getBlocksAsArray().map((block) => {
|
||||
let blockText = block.getText();
|
||||
let offset = 0;
|
||||
this.findLinkEntities(block, (start, end) => {
|
||||
const entity = Entity.get(block.getEntityAt(start));
|
||||
this.findLinkEntities(contentState, block, (start, end) => {
|
||||
const entity = contentState.getEntity(block.getEntityAt(start));
|
||||
if (entity.getType() !== 'LINK') {
|
||||
return;
|
||||
}
|
||||
|
@ -759,15 +808,10 @@ export default class MessageComposerInput extends React.Component {
|
|||
let sendHtmlFn = this.client.sendHtmlMessage;
|
||||
let sendTextFn = this.client.sendTextMessage;
|
||||
|
||||
if (this.state.isRichtextEnabled) {
|
||||
this.historyManager.addItem(
|
||||
contentHTML ? contentHTML : contentText,
|
||||
contentHTML ? 'html' : 'markdown',
|
||||
);
|
||||
} else {
|
||||
// Always store MD input as input history
|
||||
this.historyManager.addItem(contentText, 'markdown');
|
||||
}
|
||||
this.historyManager.save(
|
||||
contentState,
|
||||
this.state.isRichtextEnabled ? 'html' : 'markdown',
|
||||
);
|
||||
|
||||
if (contentText.startsWith('/me')) {
|
||||
contentText = contentText.substring(4);
|
||||
|
@ -841,6 +885,7 @@ export default class MessageComposerInput extends React.Component {
|
|||
}
|
||||
} else {
|
||||
this.moveAutocompleteSelection(up);
|
||||
e.preventDefault();
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -936,32 +981,27 @@ export default class MessageComposerInput extends React.Component {
|
|||
}
|
||||
|
||||
const {range = null, completion = '', href = null, suffix = ''} = displayedCompletion;
|
||||
let contentState = activeEditorState.getCurrentContent();
|
||||
|
||||
let entityKey;
|
||||
let mdCompletion;
|
||||
if (href) {
|
||||
entityKey = Entity.create('LINK', 'IMMUTABLE', {
|
||||
contentState = contentState.createEntity('LINK', 'IMMUTABLE', {
|
||||
url: href,
|
||||
isCompletion: true,
|
||||
});
|
||||
entityKey = contentState.getLastCreatedEntityKey();
|
||||
}
|
||||
|
||||
let selection;
|
||||
if (range) {
|
||||
selection = RichText.textOffsetsToSelectionState(
|
||||
range, activeEditorState.getCurrentContent().getBlocksAsArray(),
|
||||
range, contentState.getBlocksAsArray(),
|
||||
);
|
||||
} else {
|
||||
selection = activeEditorState.getSelection();
|
||||
}
|
||||
|
||||
let contentState = Modifier.replaceText(
|
||||
activeEditorState.getCurrentContent(),
|
||||
selection,
|
||||
mdCompletion || completion,
|
||||
null,
|
||||
entityKey,
|
||||
);
|
||||
contentState = Modifier.replaceText(contentState, selection, completion, null, entityKey);
|
||||
|
||||
// Move the selection to the end of the block
|
||||
const afterSelection = contentState.getSelectionAfter();
|
||||
|
@ -1047,7 +1087,7 @@ export default class MessageComposerInput extends React.Component {
|
|||
offset -= sum;
|
||||
|
||||
const entityKey = block.getEntityAt(offset);
|
||||
const entity = entityKey ? Entity.get(entityKey) : null;
|
||||
const entity = entityKey ? contentState.getEntity(entityKey) : null;
|
||||
if (entity && entity.getData().isCompletion) {
|
||||
// This is a completed mention, so do not insert MD link, just text
|
||||
return text;
|
||||
|
|
|
@ -119,7 +119,7 @@ module.exports = React.createClass({
|
|||
const errMsg = (typeof err === "string") ? err : (err.error || "");
|
||||
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
||||
console.error("Failed to set avatar: " + errMsg);
|
||||
Modal.createDialog(ErrorDialog, {
|
||||
Modal.createTrackedDialog('Failed to set avatar', '', ErrorDialog, {
|
||||
title: _t("Error"),
|
||||
description: _t("Failed to set avatar."),
|
||||
});
|
||||
|
|
|
@ -46,7 +46,7 @@ const BannedUser = React.createClass({
|
|||
|
||||
_onUnbanClick: function() {
|
||||
const ConfirmUserActionDialog = sdk.getComponent("dialogs.ConfirmUserActionDialog");
|
||||
Modal.createDialog(ConfirmUserActionDialog, {
|
||||
Modal.createTrackedDialog('Confirm User Action Dialog', 'onUnbanClick', ConfirmUserActionDialog, {
|
||||
member: this.props.member,
|
||||
action: _t('Unban'),
|
||||
danger: false,
|
||||
|
@ -58,7 +58,7 @@ const BannedUser = React.createClass({
|
|||
).catch((err) => {
|
||||
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
||||
console.error("Failed to unban: " + err);
|
||||
Modal.createDialog(ErrorDialog, {
|
||||
Modal.createTrackedDialog('Failed to unban', '', ErrorDialog, {
|
||||
title: _t('Error'),
|
||||
description: _t('Failed to unban'),
|
||||
});
|
||||
|
@ -424,7 +424,7 @@ module.exports = React.createClass({
|
|||
ev.preventDefault();
|
||||
var value = ev.target.value;
|
||||
|
||||
Modal.createDialog(QuestionDialog, {
|
||||
Modal.createTrackedDialog('Privacy warning', '', QuestionDialog, {
|
||||
title: _t('Privacy warning'),
|
||||
description:
|
||||
<div>
|
||||
|
@ -517,7 +517,7 @@ module.exports = React.createClass({
|
|||
onManageIntegrations(ev) {
|
||||
ev.preventDefault();
|
||||
var IntegrationsManager = sdk.getComponent("views.settings.IntegrationsManager");
|
||||
Modal.createDialog(IntegrationsManager, {
|
||||
Modal.createTrackedDialog('Integrations Manager', 'onManageIntegrations', IntegrationsManager, {
|
||||
src: (this.scalarClient !== null && this.scalarClient.hasCredentials()) ?
|
||||
this.scalarClient.getScalarInterfaceUrlForRoom(this.props.room.roomId) :
|
||||
null,
|
||||
|
@ -550,7 +550,7 @@ module.exports = React.createClass({
|
|||
}, function(err) {
|
||||
var errCode = err.errcode || _t('unknown error code');
|
||||
var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
||||
Modal.createDialog(ErrorDialog, {
|
||||
Modal.createTrackedDialog('Failed to forget room', '', ErrorDialog, {
|
||||
title: _t('Error'),
|
||||
description: _t("Failed to forget room %(errCode)s", { errCode: errCode }),
|
||||
});
|
||||
|
@ -561,7 +561,7 @@ module.exports = React.createClass({
|
|||
if (!this.refs.encrypt.checked) return;
|
||||
|
||||
var QuestionDialog = sdk.getComponent("dialogs.QuestionDialog");
|
||||
Modal.createDialog(QuestionDialog, {
|
||||
Modal.createTrackedDialog('E2E Enable Warning', '', QuestionDialog, {
|
||||
title: _t('Warning!'),
|
||||
description: (
|
||||
<div>
|
||||
|
|
|
@ -82,7 +82,7 @@ export default withMatrixClient(React.createClass({
|
|||
}).catch((err) => {
|
||||
console.error("Unable to add phone number: " + err);
|
||||
let msg = err.message;
|
||||
Modal.createDialog(ErrorDialog, {
|
||||
Modal.createTrackedDialog('Add Phone Number Error', '', ErrorDialog, {
|
||||
title: _t("Error"),
|
||||
description: msg,
|
||||
});
|
||||
|
@ -107,7 +107,7 @@ export default withMatrixClient(React.createClass({
|
|||
}
|
||||
msgElements.push(<div key="_error" className="error">{msg}</div>);
|
||||
}
|
||||
Modal.createDialog(TextInputDialog, {
|
||||
Modal.createTrackedDialog('Prompt for MSISDN Verification Code', '', TextInputDialog, {
|
||||
title: _t("Enter Code"),
|
||||
description: <div>{msgElements}</div>,
|
||||
button: _t("Submit"),
|
||||
|
|
|
@ -104,7 +104,7 @@ module.exports = React.createClass({
|
|||
}
|
||||
|
||||
const QuestionDialog = sdk.getComponent("dialogs.QuestionDialog");
|
||||
Modal.createDialog(QuestionDialog, {
|
||||
Modal.createTrackedDialog('Change Password', '', QuestionDialog, {
|
||||
title: _t("Warning!"),
|
||||
description:
|
||||
<div>
|
||||
|
@ -164,7 +164,7 @@ module.exports = React.createClass({
|
|||
const deferred = Promise.defer();
|
||||
// Ask for an email otherwise the user has no way to reset their password
|
||||
const SetEmailDialog = sdk.getComponent("dialogs.SetEmailDialog");
|
||||
Modal.createDialog(SetEmailDialog, {
|
||||
Modal.createTrackedDialog('Do you want to set an email address?', '', SetEmailDialog, {
|
||||
title: _t('Do you want to set an email address?'),
|
||||
onFinished: (confirmed) => {
|
||||
// ignore confirmed, setting an email is optional
|
||||
|
@ -175,15 +175,13 @@ module.exports = React.createClass({
|
|||
},
|
||||
|
||||
_onExportE2eKeysClicked: function() {
|
||||
Modal.createDialogAsync(
|
||||
(cb) => {
|
||||
require.ensure(['../../../async-components/views/dialogs/ExportE2eKeysDialog'], () => {
|
||||
cb(require('../../../async-components/views/dialogs/ExportE2eKeysDialog'));
|
||||
}, "e2e-export");
|
||||
}, {
|
||||
matrixClient: MatrixClientPeg.get(),
|
||||
}
|
||||
);
|
||||
Modal.createTrackedDialogAsync('Export E2E Keys', 'Change Password', (cb) => {
|
||||
require.ensure(['../../../async-components/views/dialogs/ExportE2eKeysDialog'], () => {
|
||||
cb(require('../../../async-components/views/dialogs/ExportE2eKeysDialog'));
|
||||
}, "e2e-export");
|
||||
}, {
|
||||
matrixClient: MatrixClientPeg.get(),
|
||||
});
|
||||
},
|
||||
|
||||
onClickChange: function() {
|
||||
|
|
|
@ -71,8 +71,8 @@ export default class DevicesPanelEntry extends React.Component {
|
|||
// pop up an interactive auth dialog
|
||||
var InteractiveAuthDialog = sdk.getComponent("dialogs.InteractiveAuthDialog");
|
||||
|
||||
Modal.createDialog(InteractiveAuthDialog, {
|
||||
title: _t("Authentication"),
|
||||
Modal.createTrackedDialog('Delete Device Dialog', InteractiveAuthDialog, {
|
||||
title: _t("Authentication"),
|
||||
matrixClient: MatrixClientPeg.get(),
|
||||
authData: error.data,
|
||||
makeRequest: this._makeDeleteRequest,
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue