Merge branches 'develop' and 't3chguy/hide_empty_sublist' of github.com:matrix-org/matrix-react-sdk into t3chguy/hide_empty_sublist

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

# Conflicts:
#	src/i18n/strings/en_EN.json
This commit is contained in:
Michael Telatynski 2018-06-26 07:51:16 +01:00
commit f5519c21b9
No known key found for this signature in database
GPG key ID: 3F879DA5AD802A5E
22 changed files with 294 additions and 435 deletions

View file

@ -15,10 +15,9 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
'use strict';
import React from 'react';
import PropTypes from 'prop-types';
import {EventStatus} from 'matrix-js-sdk';
import MatrixClientPeg from '../../../MatrixClientPeg';
import dis from '../../../dispatcher';
@ -220,7 +219,10 @@ module.exports = React.createClass({
let replyButton;
let collapseReplyThread;
if (eventStatus === 'not_sent') {
// status is SENT before remote-echo, null after
const isSent = !eventStatus || eventStatus === EventStatus.SENT;
if (eventStatus === EventStatus.NOT_SENT) {
resendButton = (
<div className="mx_MessageContextMenu_field" onClick={this.onResendClick}>
{ _t('Resend') }
@ -228,7 +230,7 @@ module.exports = React.createClass({
);
}
if (!eventStatus && this.state.canRedact) {
if (isSent && this.state.canRedact) {
redactButton = (
<div className="mx_MessageContextMenu_field" onClick={this.onRedactClick}>
{ _t('Remove') }
@ -236,7 +238,7 @@ module.exports = React.createClass({
);
}
if (eventStatus === "queued" || eventStatus === "not_sent") {
if (eventStatus === EventStatus.QUEUED || eventStatus === EventStatus.NOT_SENT) {
cancelButton = (
<div className="mx_MessageContextMenu_field" onClick={this.onCancelSendClick}>
{ _t('Cancel Sending') }
@ -244,7 +246,7 @@ module.exports = React.createClass({
);
}
if (!eventStatus && this.props.mxEvent.getType() === 'm.room.message') {
if (isSent && this.props.mxEvent.getType() === 'm.room.message') {
const content = this.props.mxEvent.getContent();
if (content.msgtype && content.msgtype !== 'm.bad.encrypted' && content.hasOwnProperty('body')) {
forwardButton = (

View file

@ -28,6 +28,7 @@ import SdkConfig from '../../../SdkConfig';
*/
class PasswordLogin extends React.Component {
static defaultProps = {
onError: function() {},
onUsernameChanged: function() {},
onPasswordChanged: function() {},
onPhoneCountryChanged: function() {},
@ -56,33 +57,64 @@ class PasswordLogin extends React.Component {
this.onPhoneCountryChanged = this.onPhoneCountryChanged.bind(this);
this.onPhoneNumberChanged = this.onPhoneNumberChanged.bind(this);
this.onPasswordChanged = this.onPasswordChanged.bind(this);
this.isLoginEmpty = this.isLoginEmpty.bind(this);
}
componentWillMount() {
this._passwordField = null;
this._loginField = null;
}
componentWillReceiveProps(nextProps) {
if (!this.props.loginIncorrect && nextProps.loginIncorrect) {
field_input_incorrect(this._passwordField);
field_input_incorrect(this.isLoginEmpty() ? this._loginField : this._passwordField);
}
}
onSubmitForm(ev) {
ev.preventDefault();
if (this.state.loginType === PasswordLogin.LOGIN_FIELD_PHONE) {
this.props.onSubmit(
'', // XXX: Synapse breaks if you send null here:
this.state.phoneCountry,
this.state.phoneNumber,
this.state.password,
);
let username = ''; // XXX: Synapse breaks if you send null here:
let phoneCountry = null;
let phoneNumber = null;
let error;
switch (this.state.loginType) {
case PasswordLogin.LOGIN_FIELD_EMAIL:
username = this.state.username;
if (!username) {
error = _t('The email field must not be blank.');
}
break;
case PasswordLogin.LOGIN_FIELD_MXID:
username = this.state.username;
if (!username) {
error = _t('The user name field must not be blank.');
}
break;
case PasswordLogin.LOGIN_FIELD_PHONE:
phoneCountry = this.state.phoneCountry;
phoneNumber = this.state.phoneNumber;
if (!phoneNumber) {
error = _t('The phone number field must not be blank.');
}
break;
}
if (error) {
this.props.onError(error);
return;
}
if (!this.state.password) {
this.props.onError(_t('The password field must not be blank.'));
return;
}
this.props.onSubmit(
this.state.username,
null,
null,
username,
phoneCountry,
phoneNumber,
this.state.password,
);
}
@ -93,6 +125,7 @@ class PasswordLogin extends React.Component {
}
onLoginTypeChange(loginType) {
this.props.onError(null); // send a null error to clear any error messages
this.setState({
loginType: loginType,
username: "", // Reset because email and username use the same state
@ -126,8 +159,10 @@ class PasswordLogin extends React.Component {
switch (loginType) {
case PasswordLogin.LOGIN_FIELD_EMAIL:
classes.mx_Login_email = true;
classes.error = this.props.loginIncorrect && !this.state.username;
return <input
className={classNames(classes)}
ref={(e) => {this._loginField = e;}}
key="email_input"
type="text"
name="username" // make it a little easier for browser's remember-password
@ -139,8 +174,10 @@ class PasswordLogin extends React.Component {
/>;
case PasswordLogin.LOGIN_FIELD_MXID:
classes.mx_Login_username = true;
classes.error = this.props.loginIncorrect && !this.state.username;
return <input
className={classNames(classes)}
ref={(e) => {this._loginField = e;}}
key="username_input"
type="text"
name="username" // make it a little easier for browser's remember-password
@ -153,14 +190,14 @@ class PasswordLogin extends React.Component {
autoFocus
disabled={disabled}
/>;
case PasswordLogin.LOGIN_FIELD_PHONE:
case PasswordLogin.LOGIN_FIELD_PHONE: {
const CountryDropdown = sdk.getComponent('views.login.CountryDropdown');
classes.mx_Login_phoneNumberField = true;
classes.mx_Login_field_has_prefix = true;
classes.error = this.props.loginIncorrect && !this.state.phoneNumber;
return <div className="mx_Login_phoneSection">
<CountryDropdown
className="mx_Login_phoneCountry mx_Login_field_prefix"
ref="phone_country"
onOptionChange={this.onPhoneCountryChanged}
value={this.state.phoneCountry}
isSmall={true}
@ -169,7 +206,7 @@ class PasswordLogin extends React.Component {
/>
<input
className={classNames(classes)}
ref="phoneNumber"
ref={(e) => {this._loginField = e;}}
key="phone_input"
type="text"
name="phoneNumber"
@ -180,6 +217,17 @@ class PasswordLogin extends React.Component {
disabled={disabled}
/>
</div>;
}
}
}
isLoginEmpty() {
switch (this.state.loginType) {
case PasswordLogin.LOGIN_FIELD_EMAIL:
case PasswordLogin.LOGIN_FIELD_MXID:
return !this.state.username;
case PasswordLogin.LOGIN_FIELD_PHONE:
return !this.state.phoneCountry || !this.state.phoneNumber;
}
}
@ -207,7 +255,7 @@ class PasswordLogin extends React.Component {
const pwFieldClass = classNames({
mx_Login_field: true,
mx_Login_field_disabled: matrixIdText === '',
error: this.props.loginIncorrect,
error: this.props.loginIncorrect && !this.isLoginEmpty(), // only error password if error isn't top field
});
const Dropdown = sdk.getComponent('elements.Dropdown');
@ -258,6 +306,7 @@ PasswordLogin.LOGIN_FIELD_PHONE = "login_field_phone";
PasswordLogin.propTypes = {
onSubmit: PropTypes.func.isRequired, // fn(username, password)
onError: PropTypes.func,
onForgotPasswordClick: PropTypes.func, // fn()
initialUsername: PropTypes.string,
initialPhoneCountry: PropTypes.string,

View file

@ -72,14 +72,12 @@ export default React.createClass({
_updateRelatedGroups() {
if (this.unmounted) return;
const relatedGroupsEvent = this.context.matrixClient
.getRoom(this.props.mxEvent.getRoomId())
.currentState
.getStateEvents('m.room.related_groups', '');
const room = this.context.matrixClient.getRoom(this.props.mxEvent.getRoomId());
if (!room) return;
const relatedGroupsEvent = room.currentState.getStateEvents('m.room.related_groups', '');
this.setState({
relatedGroups: relatedGroupsEvent ?
relatedGroupsEvent.getContent().groups || []
: [],
relatedGroups: relatedGroupsEvent ? relatedGroupsEvent.getContent().groups || [] : [],
});
},

View file

@ -1,6 +1,7 @@
/*
Copyright 2016 OpenMarket Ltd
Copyright 2017 Travis Ralston
Copyright 2018 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,6 +16,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import {MatrixClient} from "matrix-js-sdk";
const React = require('react');
import PropTypes from 'prop-types';
const sdk = require("../../../index");
@ -29,6 +31,10 @@ module.exports = React.createClass({
room: PropTypes.object,
},
contextTypes: {
matrixClient: PropTypes.instanceOf(MatrixClient).isRequired,
},
saveSettings: function() {
const promises = [];
if (this.refs.urlPreviewsRoom) promises.push(this.refs.urlPreviewsRoom.save());
@ -39,42 +45,58 @@ module.exports = React.createClass({
render: function() {
const SettingsFlag = sdk.getComponent("elements.SettingsFlag");
const roomId = this.props.room.roomId;
const isEncrypted = this.context.matrixClient.isRoomEncrypted(roomId);
let previewsForAccount = null;
if (SettingsStore.getValueAt(SettingLevel.ACCOUNT, "urlPreviewsEnabled")) {
previewsForAccount = (
_t("You have <a>enabled</a> URL previews by default.", {}, { 'a': (sub)=><a href="#/settings">{ sub }</a> })
);
} else {
previewsForAccount = (
_t("You have <a>disabled</a> URL previews by default.", {}, { 'a': (sub)=><a href="#/settings">{ sub }</a> })
);
}
let previewsForRoom = null;
if (SettingsStore.canSetValue("urlPreviewsEnabled", roomId, "room")) {
previewsForRoom = (
<label>
<SettingsFlag name="urlPreviewsEnabled"
level={SettingLevel.ROOM}
roomId={this.props.room.roomId}
isExplicit={true}
manualSave={true}
ref="urlPreviewsRoom" />
</label>
);
} else {
let str = _td("URL previews are enabled by default for participants in this room.");
if (!SettingsStore.getValueAt(SettingLevel.ROOM, "urlPreviewsEnabled", roomId, /*explicit=*/true)) {
str = _td("URL previews are disabled by default for participants in this room.");
if (!isEncrypted) {
// Only show account setting state and room state setting state in non-e2ee rooms where they apply
const accountEnabled = SettingsStore.getValueAt(SettingLevel.ACCOUNT, "urlPreviewsEnabled");
if (accountEnabled) {
previewsForAccount = (
_t("You have <a>enabled</a> URL previews by default.", {}, {
'a': (sub)=><a href="#/settings">{ sub }</a>,
})
);
} else if (accountEnabled) {
previewsForAccount = (
_t("You have <a>disabled</a> URL previews by default.", {}, {
'a': (sub)=><a href="#/settings">{ sub }</a>,
})
);
}
previewsForRoom = (<label>{ _t(str) }</label>);
if (SettingsStore.canSetValue("urlPreviewsEnabled", roomId, "room")) {
previewsForRoom = (
<label>
<SettingsFlag name="urlPreviewsEnabled"
level={SettingLevel.ROOM}
roomId={roomId}
isExplicit={true}
manualSave={true}
ref="urlPreviewsRoom" />
</label>
);
} else {
let str = _td("URL previews are enabled by default for participants in this room.");
if (!SettingsStore.getValueAt(SettingLevel.ROOM, "urlPreviewsEnabled", roomId, /*explicit=*/true)) {
str = _td("URL previews are disabled by default for participants in this room.");
}
previewsForRoom = (<label>{ _t(str) }</label>);
}
} else {
previewsForAccount = (
_t("In encrypted rooms, like this one, URL previews are disabled by default to ensure that your " +
"homeserver (where the previews are generated) cannot gather information about links you see in " +
"this room.")
);
}
const previewsForRoomAccount = (
<SettingsFlag name="urlPreviewsEnabled"
const previewsForRoomAccount = ( // in an e2ee room we use a special key to enforce per-room opt-in
<SettingsFlag name={isEncrypted ? 'urlPreviewsEnabled_e2ee' : 'urlPreviewsEnabled'}
level={SettingLevel.ROOM_ACCOUNT}
roomId={this.props.room.roomId}
roomId={roomId}
manualSave={true}
ref="urlPreviewsSelf"
/>
@ -83,8 +105,13 @@ module.exports = React.createClass({
return (
<div className="mx_RoomSettings_toggles">
<h3>{ _t("URL Previews") }</h3>
<label>{ previewsForAccount }</label>
<div>
{ _t('When someone puts a URL in their message, a URL preview can be shown to give more ' +
'information about that link such as the title, description, and an image from the website.') }
</div>
<div>
{ previewsForAccount }
</div>
{ previewsForRoom }
<label>{ previewsForRoomAccount }</label>
</div>

View file

@ -621,13 +621,14 @@ module.exports = withMatrixClient(React.createClass({
switch (this.props.tileShape) {
case 'notif': {
const EmojiText = sdk.getComponent('elements.EmojiText');
const room = this.props.matrixClient.getRoom(this.props.mxEvent.getRoomId());
return (
<div className={classes}>
<div className="mx_EventTile_roomName">
<a href={permalink} onClick={this.onPermalinkClicked}>
<EmojiText element="a" href={permalink} onClick={this.onPermalinkClicked}>
{ room ? room.name : '' }
</a>
</EmojiText>
</div>
<div className="mx_EventTile_senderDetails">
{ avatar }

View file

@ -157,6 +157,7 @@ export default class MessageComposerInput extends React.Component {
this.setDisplayedCompletion = this.setDisplayedCompletion.bind(this);
this.onMarkdownToggleClicked = this.onMarkdownToggleClicked.bind(this);
this.onTextPasted = this.onTextPasted.bind(this);
this.focusComposer = this.focusComposer.bind(this);
const isRichtextEnabled = SettingsStore.getValue('MessageComposerInput.isRichTextEnabled');
@ -270,13 +271,12 @@ export default class MessageComposerInput extends React.Component {
}
onAction = (payload) => {
const editor = this.refs.editor;
let contentState = this.state.editorState.getCurrentContent();
switch (payload.action) {
case 'reply_to_event':
case 'focus_composer':
editor.focus();
this.focusComposer();
break;
case 'insert_mention': {
// Pretend that we've autocompleted this user because keeping two code
@ -319,7 +319,7 @@ export default class MessageComposerInput extends React.Component {
let editorState = EditorState.push(this.state.editorState, contentState, 'insert-characters');
editorState = EditorState.moveSelectionToEnd(editorState);
this.onEditorContentChanged(editorState);
editor.focus();
this.focusComposer();
}
}
break;
@ -1155,6 +1155,10 @@ export default class MessageComposerInput extends React.Component {
this.handleKeyCommand('toggle-mode');
};
focusComposer() {
this.refs.editor.focus();
}
render() {
const activeEditorState = this.state.originalEditorState || this.state.editorState;
@ -1179,7 +1183,7 @@ export default class MessageComposerInput extends React.Component {
activeEditorState.getCurrentContent().getBlocksAsArray());
return (
<div className="mx_MessageComposer_input_wrapper">
<div className="mx_MessageComposer_input_wrapper" onClick={this.focusComposer}>
<div className="mx_MessageComposer_autocomplete_wrapper">
<ReplyPreview />
<Autocomplete