Merge remote-tracking branch 'origin/develop' into rav/hotkey-ux

This commit is contained in:
Richard van der Hoff 2017-01-24 20:47:24 +00:00
commit 6dd46d532a
124 changed files with 1837 additions and 654 deletions

View file

@ -93,8 +93,8 @@ module.exports = React.createClass({
}
else {
joinText = (<span>
Join as <a onClick={(event)=>{ this.onConferenceNotificationClick(event, 'voice')}}
href="#">voice</a> or <a onClick={(event)=>{ this.onConferenceNotificationClick(event, 'video') }}
Join as <a onClick={(event)=>{ this.onConferenceNotificationClick(event, 'voice');}}
href="#">voice</a> or <a onClick={(event)=>{ this.onConferenceNotificationClick(event, 'video'); }}
href="#">video</a>.
</span>);

View file

@ -149,13 +149,13 @@ module.exports = WithMatrixClient(React.createClass({
this.props.mxEvent.on("Event.decrypted", this._onDecrypted);
},
componentWillReceiveProps: function (nextProps) {
componentWillReceiveProps: function(nextProps) {
if (nextProps.mxEvent !== this.props.mxEvent) {
this._verifyEvent(nextProps.mxEvent);
}
},
shouldComponentUpdate: function (nextProps, nextState) {
shouldComponentUpdate: function(nextProps, nextState) {
if (!ObjectUtils.shallowEqual(this.state, nextState)) {
return true;
}
@ -259,11 +259,11 @@ module.exports = WithMatrixClient(React.createClass({
onEditClicked: function(e) {
var MessageContextMenu = sdk.getComponent('context_menus.MessageContextMenu');
var buttonRect = e.target.getBoundingClientRect()
var buttonRect = e.target.getBoundingClientRect();
// The window X and Y offsets are to adjust position when zoomed in to page
var x = buttonRect.right + window.pageXOffset;
var y = (buttonRect.top + (e.target.height / 2) + window.pageYOffset) - 19;
var y = (buttonRect.top + (buttonRect.height / 2) + window.pageYOffset) - 19;
var self = this;
ContextualMenu.createMenu(MessageContextMenu, {
chevronOffset: 10,
@ -293,7 +293,7 @@ module.exports = WithMatrixClient(React.createClass({
// If it is, we want to display the complete date along with the HH:MM:SS,
// rather than just HH:MM:SS.
let dayAfterEvent = new Date(this.props.mxEvent.getTs());
dayAfterEvent.setDate(dayAfterEvent.getDate() + 1)
dayAfterEvent.setDate(dayAfterEvent.getDate() + 1);
dayAfterEvent.setHours(0);
dayAfterEvent.setMinutes(0);
dayAfterEvent.setSeconds(0);
@ -366,10 +366,11 @@ module.exports = WithMatrixClient(React.createClass({
},
onCryptoClicked: function(e) {
var EncryptedEventDialog = sdk.getComponent("dialogs.EncryptedEventDialog");
var event = this.props.mxEvent;
Modal.createDialog(EncryptedEventDialog, {
Modal.createDialogAsync((cb) => {
require(['../../../async-components/views/dialogs/EncryptedEventDialog'], cb);
}, {
event: event,
});
},
@ -465,7 +466,7 @@ module.exports = WithMatrixClient(React.createClass({
}
var editButton = (
<img className="mx_EventTile_editButton" src="img/icon_context_message.svg" width="19" height="19" alt="Options" title="Options" onClick={this.onEditClicked} />
<span className="mx_EventTile_editButton" title="Options" onClick={this.onEditClicked} />
);
var e2e;

View file

@ -60,13 +60,15 @@ module.exports = React.createClass({
},
componentDidMount: function() {
if (this.refs.description)
if (this.refs.description) {
linkifyElement(this.refs.description, linkifyMatrix.options);
}
},
componentDidUpdate: function() {
if (this.refs.description)
if (this.refs.description) {
linkifyElement(this.refs.description, linkifyMatrix.options);
}
},
componentWillUnmount: function() {
@ -116,7 +118,7 @@ module.exports = React.createClass({
if (image) {
img = <div className="mx_LinkPreviewWidget_image" style={{ height: thumbHeight }}>
<img style={{ maxWidth: imageMaxWidth, maxHeight: imageMaxHeight }} src={ image } onClick={ this.onImageClick }/>
</div>
</div>;
}
return (

View file

@ -60,7 +60,7 @@ export default class MemberDeviceInfo extends React.Component {
</div>
);
}
};
}
MemberDeviceInfo.displayName = 'MemberDeviceInfo';
MemberDeviceInfo.propTypes = {

View file

@ -65,7 +65,7 @@ module.exports = WithMatrixClient(React.createClass({
updating: 0,
devicesLoading: true,
devices: null,
}
};
},
componentWillMount: function() {
@ -203,7 +203,7 @@ module.exports = WithMatrixClient(React.createClass({
}
var cancelled = false;
this._cancelDeviceList = function() { cancelled = true; }
this._cancelDeviceList = function() { cancelled = true; };
var client = this.props.matrixClient;
var self = this;
@ -530,7 +530,7 @@ module.exports = WithMatrixClient(React.createClass({
});
},
onMemberAvatarClick: function () {
onMemberAvatarClick: function() {
var avatarUrl = this.props.member.user ? this.props.member.user.avatarUrl : this.props.member.events.member.getContent().avatar_url;
if(!avatarUrl) return;
@ -621,7 +621,7 @@ module.exports = WithMatrixClient(React.createClass({
<img src="img/create-big.svg" width="26" height="26" />
</div>
<div className={labelClasses}><i>Start new chat</i></div>
</AccessibleButton>
</AccessibleButton>;
startChat = <div>
<h3>Direct chats</h3>
@ -655,7 +655,7 @@ module.exports = WithMatrixClient(React.createClass({
var giveOpLabel = this.state.isTargetMod ? "Revoke Moderator" : "Make Moderator";
giveModButton = <AccessibleButton className="mx_MemberInfo_field" onClick={this.onModToggle}>
{giveOpLabel}
</AccessibleButton>
</AccessibleButton>;
}
// TODO: we should have an invite button if this MemberInfo is showing a user who isn't actually in the current room yet
@ -673,7 +673,7 @@ module.exports = WithMatrixClient(React.createClass({
{banButton}
{giveModButton}
</div>
</div>
</div>;
}
const memberName = this.props.member.name;

View file

@ -32,7 +32,7 @@ var SHARE_HISTORY_WARNING =
Newly invited users will see the history of this room. <br/>
If you'd prefer invited users not to see messages that were sent before they joined, <br/>
turn off, 'Share message history with new users' in the settings for this room.
</span>
</span>;
module.exports = React.createClass({
displayName: 'MemberList',
@ -207,7 +207,7 @@ module.exports = React.createClass({
// For now we'll pretend this is any entity. It should probably be a separate tile.
var EntityTile = sdk.getComponent("rooms.EntityTile");
var BaseAvatar = sdk.getComponent("avatars.BaseAvatar");
var text = "and " + overflowCount + " other" + (overflowCount > 1 ? "s" : "") + "...";
var text = "and " + overflowCount + " other" + (overflowCount > 1 ? "s" : "") + "...";
return (
<EntityTile className="mx_EntityTile_ellipsis" avatarJsx={
<BaseAvatar url="img/ellipsis.svg" name="..." width={36} height={36} />
@ -338,8 +338,8 @@ module.exports = React.createClass({
}
memberList.push(
<EntityTile key={e.getStateKey()} name={e.getContent().display_name} />
)
})
);
});
}
}

View file

@ -46,7 +46,7 @@ module.exports = React.createClass({
(this.user_last_modified_time === undefined ||
this.user_last_modified_time < nextProps.member.user.getLastModifiedTime())
) {
return true
return true;
}
return false;
},

View file

@ -222,20 +222,22 @@ export default class MessageComposer extends React.Component {
</div>
);
let e2eimg, e2etitle;
let e2eImg, e2eTitle, e2eClass;
if (MatrixClientPeg.get().isRoomEncrypted(this.props.room.roomId)) {
// FIXME: show a /!\ if there are untrusted devices in the room...
e2eimg = 'img/e2e-verified.svg';
e2etitle = 'Encrypted room';
e2eImg = 'img/e2e-verified.svg';
e2eTitle = 'Encrypted room';
e2eClass = 'mx_MessageComposer_e2eIcon';
} else {
e2eimg = 'img/e2e-unencrypted.svg';
e2etitle = 'Unencrypted room';
e2eImg = 'img/e2e-unencrypted.svg';
e2eTitle = 'Unencrypted room';
e2eClass = 'mx_MessageComposer_e2eIcon mx_filterFlipColor';
}
controls.push(
<img key="e2eIcon" className="mx_MessageComposer_e2eIcon" src={e2eimg} width="12" height="12"
alt={e2etitle} title={e2etitle}
<img key="e2eIcon" className={e2eClass} src={e2eImg} width="12" height="12"
alt={e2eTitle} title={e2eTitle}
/>
);
var callButton, videoCallButton, hangupButton;
@ -331,6 +333,7 @@ export default class MessageComposer extends React.Component {
const disabled = !this.state.inputState.isRichtextEnabled && 'underline' === name;
const className = classNames("mx_MessageComposer_format_button", {
mx_MessageComposer_format_button_disabled: disabled,
mx_filterFlipColor: true,
});
return <img className={className}
title={name}
@ -355,11 +358,11 @@ export default class MessageComposer extends React.Component {
<div style={{flex: 1}}></div>
<img title={`Turn Markdown ${this.state.inputState.isRichtextEnabled ? 'on' : 'off'}`}
onMouseDown={this.onToggleMarkdownClicked}
className="mx_MessageComposer_formatbar_markdown"
className="mx_MessageComposer_formatbar_markdown mx_filterFlipColor"
src={`img/button-md-${!this.state.inputState.isRichtextEnabled}.png`} />
<img title="Hide Text Formatting Toolbar"
onClick={this.onToggleFormattingClicked}
className="mx_MessageComposer_formatbar_cancel"
className="mx_MessageComposer_formatbar_cancel mx_filterFlipColor"
src="img/icon-text-cancel.svg" />
</div>
</div>: null
@ -367,7 +370,7 @@ export default class MessageComposer extends React.Component {
</div>
);
}
};
}
MessageComposer.propTypes = {
tabComplete: React.PropTypes.any,

View file

@ -443,12 +443,12 @@ export default class MessageComposerInput extends React.Component {
selection = this.state.editorState.getSelection();
let modifyFn = {
bold: text => `**${text}**`,
italic: text => `*${text}*`,
underline: text => `_${text}_`, // there's actually no valid underline in Markdown, but *shrug*
strike: text => `~~${text}~~`,
code: text => `\`${text}\``,
blockquote: text => text.split('\n').map(line => `> ${line}\n`).join(''),
'bold': text => `**${text}**`,
'italic': text => `*${text}*`,
'underline': text => `_${text}_`, // there's actually no valid underline in Markdown, but *shrug*
'strike': text => `~~${text}~~`,
'code': text => `\`${text}\``,
'blockquote': text => text.split('\n').map(line => `> ${line}\n`).join(''),
'unordered-list-item': text => text.split('\n').map(line => `- ${line}\n`).join(''),
'ordered-list-item': text => text.split('\n').map((line, i) => `${i+1}. ${line}\n`).join(''),
}[command];
@ -462,8 +462,9 @@ export default class MessageComposerInput extends React.Component {
}
}
if (newState == null)
if (newState == null) {
newState = RichUtils.handleKeyCommand(this.state.editorState, command);
}
if (newState != null) {
this.setEditorState(newState);
@ -523,7 +524,9 @@ export default class MessageComposerInput extends React.Component {
);
} else {
const md = new Markdown(contentText);
if (!md.isPlainText()) {
if (md.isPlainText()) {
contentText = md.toPlaintext();
} else {
contentHTML = md.toHTML();
}
}
@ -663,7 +666,7 @@ export default class MessageComposerInput extends React.Component {
const blockName = {
'code-block': 'code',
blockquote: 'quote',
'blockquote': 'quote',
'unordered-list-item': 'bullet',
'ordered-list-item': 'numbullet',
};
@ -716,7 +719,7 @@ export default class MessageComposerInput extends React.Component {
selection={selection} />
</div>
<div className={className}>
<img className="mx_MessageComposer_input_markdownIndicator"
<img className="mx_MessageComposer_input_markdownIndicator mx_filterFlipColor"
onMouseDown={this.onMarkdownToggleClicked}
title={`Markdown is ${this.state.isRichtextEnabled ? 'disabled' : 'enabled'}`}
src={`img/button-md-${!this.state.isRichtextEnabled}.png`} />
@ -738,7 +741,7 @@ export default class MessageComposerInput extends React.Component {
</div>
);
}
};
}
MessageComposerInput.propTypes = {
tabComplete: React.PropTypes.any,

View file

@ -192,7 +192,7 @@ module.exports = React.createClass({
}
},
onKeyDown: function (ev) {
onKeyDown: function(ev) {
if (ev.keyCode === KeyCode.ENTER && !ev.shiftKey) {
var input = this.refs.textarea.value;
if (input.length === 0) {
@ -331,6 +331,7 @@ module.exports = React.createClass({
MatrixClientPeg.get().sendHtmlMessage(this.props.room.roomId, contentText, htmlText);
}
else {
const contentText = mdown.toPlaintext();
sendMessagePromise = isEmote ?
MatrixClientPeg.get().sendEmoteMessage(this.props.room.roomId, contentText) :
MatrixClientPeg.get().sendTextMessage(this.props.room.roomId, contentText);

View file

@ -71,7 +71,7 @@ module.exports = React.createClass({
getDefaultProps: function() {
return {
leftOffset: 0,
}
};
},
getInitialState: function() {
@ -81,7 +81,7 @@ module.exports = React.createClass({
// position.
return {
suppressDisplay: !this.props.suppressAnimation,
}
};
},
componentWillUnmount: function() {

View file

@ -183,8 +183,8 @@ module.exports = React.createClass({
'm.room.name', user_id
);
save_button = <AccessibleButton className="mx_RoomHeader_textButton" onClick={this.props.onSaveClick}>Save</AccessibleButton>
cancel_button = <AccessibleButton className="mx_RoomHeader_cancelButton" onClick={this.props.onCancelClick}><img src="img/cancel.svg" width="18" height="18" alt="Cancel"/> </AccessibleButton>
save_button = <AccessibleButton className="mx_RoomHeader_textButton" onClick={this.props.onSaveClick}>Save</AccessibleButton>;
cancel_button = <AccessibleButton className="mx_RoomHeader_cancelButton" onClick={this.props.onCancelClick}><img src="img/cancel.svg" width="18" height="18" alt="Cancel"/> </AccessibleButton>;
}
if (this.props.saving) {
@ -194,7 +194,7 @@ module.exports = React.createClass({
if (can_set_room_name) {
var RoomNameEditor = sdk.getComponent("rooms.RoomNameEditor");
name = <RoomNameEditor ref="nameEditor" room={this.props.room} />
name = <RoomNameEditor ref="nameEditor" room={this.props.room} />;
}
else {
var searchStatus;
@ -233,7 +233,7 @@ module.exports = React.createClass({
if (can_set_room_topic) {
var RoomTopicEditor = sdk.getComponent("rooms.RoomTopicEditor");
topic_el = <RoomTopicEditor ref="topicEditor" room={this.props.room} />
topic_el = <RoomTopicEditor ref="topicEditor" room={this.props.room} />;
} else {
var topic;
if (this.props.room) {
@ -302,7 +302,11 @@ module.exports = React.createClass({
rightPanel_buttons =
<AccessibleButton className="mx_RoomHeader_button" onClick={this.onShowRhsClick} title="<">
<TintableSvg src="img/minimise.svg" width="10" height="16"/>
<<<<<<< HEAD
</AccessibleButton>
=======
</div>;
>>>>>>> origin/develop
}
var right_row;

View file

@ -46,7 +46,7 @@ module.exports = React.createClass({
isLoadingLeftRooms: false,
lists: {},
incomingCall: null,
}
};
},
componentWillMount: function() {
@ -338,7 +338,7 @@ module.exports = React.createClass({
// as this is used to calculate the CSS fixed top position for the stickies
var scrollAreaHeight = ReactDOM.findDOMNode(this).getBoundingClientRect().height;
var top = (incomingCallBox.parentElement.getBoundingClientRect().top + window.pageYOffset)
var top = (incomingCallBox.parentElement.getBoundingClientRect().top + window.pageYOffset);
// Make sure we don't go too far up, if the headers aren't sticky
top = (top < scrollAreaOffset) ? scrollAreaOffset : top;
// make sure we don't go too far down, if the headers aren't sticky
@ -401,7 +401,7 @@ module.exports = React.createClass({
var stickyHeight = sticky.dataset.originalHeight;
var stickyHeader = sticky.childNodes[0];
var topStuckHeight = stickyHeight * i;
var bottomStuckHeight = stickyHeight * (stickyWrappers.length - i)
var bottomStuckHeight = stickyHeight * (stickyWrappers.length - i);
if (self.scrollAreaSufficient && stickyPosition < (scrollArea.scrollTop + topStuckHeight)) {
// Top stickies
@ -520,7 +520,7 @@ module.exports = React.createClass({
collapsed={ self.props.collapsed }
searchFilter={ self.props.searchFilter }
onHeaderClick={ self.onSubListHeaderClick }
onShowMoreRooms={ self.onShowMoreRooms } />
onShowMoreRooms={ self.onShowMoreRooms } />;
}
}) }

View file

@ -58,7 +58,7 @@ module.exports = React.createClass({
getInitialState: function() {
return {
busy: false
}
};
},
componentWillMount: function() {
@ -96,7 +96,7 @@ module.exports = React.createClass({
emailMatchBlock = <div className="error">
Unable to ascertain that the address this invite was
sent to matches one associated with your account.
</div>
</div>;
} else if (this.state.invitedEmailMxid != MatrixClientPeg.get().credentials.userId) {
emailMatchBlock =
<div className="mx_RoomPreviewBar_warning">
@ -107,7 +107,7 @@ module.exports = React.createClass({
This invitation was sent to <b><span className="email">{this.props.invitedEmail}</span></b>, which is not associated with this account.<br/>
You may wish to login with a different account, or add this email to this account.
</div>
</div>
</div>;
}
}
joinBlock = (

View file

@ -252,7 +252,7 @@ module.exports = React.createClass({
return this.refs.url_preview_settings.saveSettings();
},
saveEncryption: function () {
saveEncryption: function() {
if (!this.refs.encrypt) { return q(); }
var encrypt = this.refs.encrypt.checked;
@ -404,7 +404,7 @@ module.exports = React.createClass({
var cli = MatrixClientPeg.get();
var roomState = this.props.room.currentState;
return (roomState.mayClientSendStateEvent("m.room.join_rules", cli) &&
roomState.mayClientSendStateEvent("m.room.guest_access", cli))
roomState.mayClientSendStateEvent("m.room.guest_access", cli));
},
onManageIntegrations(ev) {
@ -510,7 +510,7 @@ module.exports = React.createClass({
var UrlPreviewSettings = sdk.getComponent("room_settings.UrlPreviewSettings");
var EditableText = sdk.getComponent('elements.EditableText');
var PowerSelector = sdk.getComponent('elements.PowerSelector');
var Loader = sdk.getComponent("elements.Spinner")
var Loader = sdk.getComponent("elements.Spinner");
var cli = MatrixClientPeg.get();
var roomState = this.props.room.currentState;
@ -557,7 +557,7 @@ module.exports = React.createClass({
</div>;
}
else {
userLevelsSection = <div>No users have specific privileges in this room.</div>
userLevelsSection = <div>No users have specific privileges in this room.</div>;
}
var banned = this.props.room.getMembersWithMembership("ban");
@ -635,7 +635,7 @@ module.exports = React.createClass({
</label>);
})) : (self.state.tags && self.state.tags.join) ? self.state.tags.join(", ") : ""
}
</div>
</div>;
}
// If there is no history_visibility, it is assumed to be 'shared'.
@ -653,7 +653,7 @@ module.exports = React.createClass({
addressWarning =
<div className="mx_RoomSettings_warning">
To link to a room it must have <a href="#addresses">an address</a>.
</div>
</div>;
}
var inviteGuestWarning;
@ -664,7 +664,7 @@ module.exports = React.createClass({
this.setState({ join_rule: "invite", guest_access: "can_join" });
e.preventDefault();
}}>Click here to fix</a>.
</div>
</div>;
}
var integrationsButton;

View file

@ -27,6 +27,7 @@ var ContextualMenu = require('../../structures/ContextualMenu');
var RoomNotifs = require('../../../RoomNotifs');
var FormattingUtils = require('../../../utils/FormattingUtils');
var AccessibleButton = require('../elements/AccessibleButton');
var UserSettingsStore = require('../../../UserSettingsStore');
module.exports = React.createClass({
displayName: 'RoomTile',
@ -177,7 +178,8 @@ module.exports = React.createClass({
var self = this;
ContextualMenu.createMenu(RoomTagMenu, {
chevronOffset: 10,
menuColour: "#FFFFFF",
// XXX: fix horrid hardcoding
menuColour: UserSettingsStore.getSyncedSettings().theme === 'dark' ? "#2d2d2d" : "#FFFFFF",
left: x,
top: y,
room: this.props.room,
@ -220,7 +222,7 @@ module.exports = React.createClass({
var avatarContainerClasses = classNames({
'mx_RoomTile_avatar_container': true,
'mx_RoomTile_avatar_roomTagMenu': this.state.roomTagMenu,
})
});
var badgeClasses = classNames({
'mx_RoomTile_badge': true,
@ -287,7 +289,7 @@ module.exports = React.createClass({
var connectDragSource = this.props.connectDragSource;
var connectDropTarget = this.props.connectDropTarget;
let ret = (
<div> { /* Only native elements can be wrapped in a DnD object. */}
<AccessibleButton className={classes} tabIndex="0" onClick={this.onClick} onMouseEnter={this.onMouseEnter} onMouseLeave={this.onMouseLeave}>

View file

@ -118,7 +118,7 @@ var SearchableEntityList = React.createClass({
_createOverflowEntity: function(overflowCount, totalCount) {
var EntityTile = sdk.getComponent("rooms.EntityTile");
var BaseAvatar = sdk.getComponent("avatars.BaseAvatar");
var text = "and " + overflowCount + " other" + (overflowCount > 1 ? "s" : "") + "...";
var text = "and " + overflowCount + " other" + (overflowCount > 1 ? "s" : "") + "...";
return (
<EntityTile className="mx_EntityTile_ellipsis" avatarJsx={
<BaseAvatar url="img/ellipsis.svg" name="..." width={36} height={36} />
@ -135,8 +135,8 @@ var SearchableEntityList = React.createClass({
<form onSubmit={this.onQuerySubmit} autoComplete="off">
<input className="mx_SearchableEntityList_query" id="mx_SearchableEntityList_query" type="text"
onChange={this.onQueryChanged} value={this.state.query}
onFocus= {() => { this.setState({ focused: true }) }}
onBlur= {() => { this.setState({ focused: false }) }}
onFocus= {() => { this.setState({ focused: true }); }}
onBlur= {() => { this.setState({ focused: false }); }}
placeholder={this.props.searchPlaceholderText} />
</form>
);

View file

@ -45,7 +45,7 @@ module.exports = React.createClass({
var cancelButton;
if (this.props.onCancelClick) {
cancelButton = <AccessibleButton className="mx_RoomHeader_cancelButton" onClick={this.props.onCancelClick}><img src="img/cancel.svg" width="18" height="18" alt="Cancel"/> </AccessibleButton>
cancelButton = <AccessibleButton className="mx_RoomHeader_cancelButton" onClick={this.props.onCancelClick}><img src="img/cancel.svg" width="18" height="18" alt="Cancel"/> </AccessibleButton>;
}
var showRhsButton;
@ -71,4 +71,3 @@ module.exports = React.createClass({
);
},
});

View file

@ -38,7 +38,7 @@ module.exports = React.createClass({
var active = -1;
// FIXME: make presence data update whenever User.presence changes...
active = user.lastActiveAgo ?
active = user.lastActiveAgo ?
(Date.now() - (user.lastPresenceTs - user.lastActiveAgo)) : -1;
var BaseAvatar = sdk.getComponent('avatars.BaseAvatar');