Merge branch 'develop' into t3chguy/community_autocomplete

This commit is contained in:
Michael Telatynski 2018-06-22 12:00:34 +01:00 committed by GitHub
commit 561b9699fc
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
57 changed files with 1448 additions and 1288 deletions

View file

@ -253,13 +253,11 @@ module.exports = React.createClass({
</div>
);
if (SettingsStore.isFeatureEnabled("feature_rich_quoting")) {
replyButton = (
<div className="mx_MessageContextMenu_field" onClick={this.onReplyClick}>
{ _t('Reply') }
</div>
);
}
replyButton = (
<div className="mx_MessageContextMenu_field" onClick={this.onReplyClick}>
{ _t('Reply') }
</div>
);
if (this.state.canPin) {
pinButton = (

View file

@ -1,6 +1,6 @@
/*
Copyright 2015, 2016 OpenMarket Ltd
Copyright 2017 New Vector Ltd
Copyright 2017, 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.
@ -17,7 +17,7 @@ limitations under the License.
import React from 'react';
import PropTypes from 'prop-types';
import { _t } from '../../../languageHandler';
import { _t, _td } from '../../../languageHandler';
import sdk from '../../../index';
import MatrixClientPeg from '../../../MatrixClientPeg';
import Promise from 'bluebird';
@ -27,6 +27,13 @@ import GroupStore from '../../../stores/GroupStore';
const TRUNCATE_QUERY_LIST = 40;
const QUERY_USER_DIRECTORY_DEBOUNCE_MS = 200;
const addressTypeName = {
'mx-user-id': _td("Matrix ID"),
'mx-room-id': _td("Matrix Room ID"),
'email': _td("email address"),
};
module.exports = React.createClass({
displayName: "AddressPickerDialog",
@ -66,7 +73,7 @@ module.exports = React.createClass({
// List of UserAddressType objects representing
// the list of addresses we're going to invite
userList: [],
selectedList: [],
// Whether a search is ongoing
busy: false,
@ -76,10 +83,9 @@ module.exports = React.createClass({
serverSupportsUserDirectory: true,
// The query being searched for
query: "",
// List of UserAddressType objects representing
// the set of auto-completion results for the current search
// query.
queryList: [],
// List of UserAddressType objects representing the set of
// auto-completion results for the current search query.
suggestedList: [],
};
},
@ -91,14 +97,14 @@ module.exports = React.createClass({
},
onButtonClick: function() {
let userList = this.state.userList.slice();
let selectedList = this.state.selectedList.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 userList
// If there is and it's valid add it to the local selectedList
if (this.refs.textinput.value !== '') {
userList = this._addInputToList();
if (userList === null) return;
selectedList = this._addInputToList();
if (selectedList === null) return;
}
this.props.onFinished(true, userList);
this.props.onFinished(true, selectedList);
},
onCancel: function() {
@ -118,18 +124,18 @@ module.exports = React.createClass({
e.stopPropagation();
e.preventDefault();
if (this.addressSelector) this.addressSelector.moveSelectionDown();
} else if (this.state.queryList.length > 0 && (e.keyCode === 188 || e.keyCode === 13 || e.keyCode === 9)) { // comma or enter or tab
} else if (this.state.suggestedList.length > 0 && (e.keyCode === 188 || e.keyCode === 13 || e.keyCode === 9)) { // comma or enter or tab
e.stopPropagation();
e.preventDefault();
if (this.addressSelector) this.addressSelector.chooseSelection();
} else if (this.refs.textinput.value.length === 0 && this.state.userList.length && e.keyCode === 8) { // backspace
} else if (this.refs.textinput.value.length === 0 && this.state.selectedList.length && e.keyCode === 8) { // backspace
e.stopPropagation();
e.preventDefault();
this.onDismissed(this.state.userList.length - 1)();
this.onDismissed(this.state.selectedList.length - 1)();
} else if (e.keyCode === 13) { // enter
e.stopPropagation();
e.preventDefault();
if (this.refs.textinput.value == '') {
if (this.refs.textinput.value === '') {
// if there's nothing in the input box, submit the form
this.onButtonClick();
} else {
@ -148,7 +154,7 @@ module.exports = React.createClass({
clearTimeout(this.queryChangedDebouncer);
}
// Only do search if there is something to search
if (query.length > 0 && query != '@' && query.length >= 2) {
if (query.length > 0 && query !== '@' && query.length >= 2) {
this.queryChangedDebouncer = setTimeout(() => {
if (this.props.pickerType === 'user') {
if (this.props.groupId) {
@ -170,7 +176,7 @@ module.exports = React.createClass({
}, QUERY_USER_DIRECTORY_DEBOUNCE_MS);
} else {
this.setState({
queryList: [],
suggestedList: [],
query: "",
searchError: null,
});
@ -179,11 +185,11 @@ module.exports = React.createClass({
onDismissed: function(index) {
return () => {
const userList = this.state.userList.slice();
userList.splice(index, 1);
const selectedList = this.state.selectedList.slice();
selectedList.splice(index, 1);
this.setState({
userList: userList,
queryList: [],
selectedList,
suggestedList: [],
query: "",
});
if (this._cancelThreepidLookup) this._cancelThreepidLookup();
@ -197,11 +203,11 @@ module.exports = React.createClass({
},
onSelected: function(index) {
const userList = this.state.userList.slice();
userList.push(this.state.queryList[index]);
const selectedList = this.state.selectedList.slice();
selectedList.push(this.state.suggestedList[index]);
this.setState({
userList: userList,
queryList: [],
selectedList,
suggestedList: [],
query: "",
});
if (this._cancelThreepidLookup) this._cancelThreepidLookup();
@ -379,10 +385,10 @@ module.exports = React.createClass({
},
_processResults: function(results, query) {
const queryList = [];
const suggestedList = [];
results.forEach((result) => {
if (result.room_id) {
queryList.push({
suggestedList.push({
addressType: 'mx-room-id',
address: result.room_id,
displayName: result.name,
@ -399,7 +405,7 @@ module.exports = React.createClass({
// Return objects, structure of which is defined
// by UserAddressType
queryList.push({
suggestedList.push({
addressType: 'mx-user-id',
address: result.user_id,
displayName: result.display_name,
@ -413,18 +419,18 @@ module.exports = React.createClass({
// a perfectly valid address if there are close matches.
const addrType = getAddressType(query);
if (this.props.validAddressTypes.includes(addrType)) {
queryList.unshift({
suggestedList.unshift({
addressType: addrType,
address: query,
isKnown: false,
});
if (this._cancelThreepidLookup) this._cancelThreepidLookup();
if (addrType == 'email') {
if (addrType === 'email') {
this._lookupThreepid(addrType, query).done();
}
}
this.setState({
queryList,
suggestedList,
error: false,
}, () => {
if (this.addressSelector) this.addressSelector.moveSelectionTop();
@ -442,14 +448,14 @@ module.exports = React.createClass({
if (!this.props.validAddressTypes.includes(addrType)) {
this.setState({ error: true });
return null;
} else if (addrType == 'mx-user-id') {
} else if (addrType === 'mx-user-id') {
const user = MatrixClientPeg.get().getUser(addrObj.address);
if (user) {
addrObj.displayName = user.displayName;
addrObj.avatarMxc = user.avatarUrl;
addrObj.isKnown = true;
}
} else if (addrType == 'mx-room-id') {
} else if (addrType === 'mx-room-id') {
const room = MatrixClientPeg.get().getRoom(addrObj.address);
if (room) {
addrObj.displayName = room.name;
@ -458,15 +464,15 @@ module.exports = React.createClass({
}
}
const userList = this.state.userList.slice();
userList.push(addrObj);
const selectedList = this.state.selectedList.slice();
selectedList.push(addrObj);
this.setState({
userList: userList,
queryList: [],
selectedList,
suggestedList: [],
query: "",
});
if (this._cancelThreepidLookup) this._cancelThreepidLookup();
return userList;
return selectedList;
},
_lookupThreepid: function(medium, address) {
@ -492,7 +498,7 @@ module.exports = React.createClass({
if (res === null) return null;
if (cancelled) return null;
this.setState({
queryList: [{
suggestedList: [{
// a UserAddressType
addressType: medium,
address: address,
@ -510,15 +516,27 @@ module.exports = React.createClass({
const AddressSelector = sdk.getComponent("elements.AddressSelector");
this.scrollElement = null;
// map addressType => set of addresses to avoid O(n*m) operation
const selectedAddresses = {};
this.state.selectedList.forEach(({address, addressType}) => {
if (!selectedAddresses[addressType]) selectedAddresses[addressType] = new Set();
selectedAddresses[addressType].add(address);
});
// Filter out any addresses in the above already selected addresses (matching both type and address)
const filteredSuggestedList = this.state.suggestedList.filter(({address, addressType}) => {
return !(selectedAddresses[addressType] && selectedAddresses[addressType].has(address));
});
const query = [];
// create the invite list
if (this.state.userList.length > 0) {
if (this.state.selectedList.length > 0) {
const AddressTile = sdk.getComponent("elements.AddressTile");
for (let i = 0; i < this.state.userList.length; i++) {
for (let i = 0; i < this.state.selectedList.length; i++) {
query.push(
<AddressTile
key={i}
address={this.state.userList[i]}
address={this.state.selectedList[i]}
canDismiss={true}
onDismissed={this.onDismissed(i)}
showAddress={this.props.pickerType === 'user'} />,
@ -528,7 +546,7 @@ module.exports = React.createClass({
// Add the query at the end
query.push(
<textarea key={this.state.userList.length}
<textarea key={this.state.selectedList.length}
rows="1"
id="textinput"
ref="textinput"
@ -543,34 +561,22 @@ module.exports = React.createClass({
let error;
let addressSelector;
if (this.state.error) {
let tryUsing = '';
const validTypeDescriptions = this.props.validAddressTypes.map((t) => {
return {
'mx-user-id': _t("Matrix ID"),
'mx-room-id': _t("Matrix Room ID"),
'email': _t("email address"),
}[t];
});
tryUsing = _t("Try using one of the following valid address types: %(validTypesList)s.", {
validTypesList: validTypeDescriptions.join(", "),
});
const validTypeDescriptions = this.props.validAddressTypes.map((t) => _t(addressTypeName[t]));
error = <div className="mx_ChatInviteDialog_error">
{ _t("You have entered an invalid address.") }
<br />
{ tryUsing }
{ _t("Try using one of the following valid address types: %(validTypesList)s.", {
validTypesList: validTypeDescriptions.join(", "),
}) }
</div>;
} else if (this.state.searchError) {
error = <div className="mx_ChatInviteDialog_error">{ this.state.searchError }</div>;
} else if (
this.state.query.length > 0 &&
this.state.queryList.length === 0 &&
!this.state.busy
) {
} else if (this.state.query.length > 0 && filteredSuggestedList.length === 0 && !this.state.busy) {
error = <div className="mx_ChatInviteDialog_error">{ _t("No results") }</div>;
} else {
addressSelector = (
<AddressSelector ref={(ref) => {this.addressSelector = ref;}}
addressList={this.state.queryList}
addressList={filteredSuggestedList}
showAddress={this.props.pickerType === 'user'}
onSelected={this.onSelected}
truncateAt={TRUNCATE_QUERY_LIST}

View file

@ -1,5 +1,6 @@
/*
Copyright 2017 Vector Creations Ltd
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.
@ -28,6 +29,7 @@ export default class ChatCreateOrReuseDialog extends React.Component {
constructor(props) {
super(props);
this.onFinished = this.onFinished.bind(this);
this.onRoomTileClick = this.onRoomTileClick.bind(this);
this.state = {
@ -53,10 +55,7 @@ export default class ChatCreateOrReuseDialog extends React.Component {
const room = client.getRoom(roomId);
if (room) {
const me = room.getMember(client.credentials.userId);
const highlight = (
room.getUnreadNotificationCount('highlight') > 0 ||
me.membership == "invite"
);
const highlight = room.getUnreadNotificationCount('highlight') > 0 || me.membership === "invite";
tiles.push(
<RoomTile key={room.roomId} room={room}
transparent={true}
@ -64,7 +63,7 @@ export default class ChatCreateOrReuseDialog extends React.Component {
selected={false}
unread={Unread.doesRoomHaveUnreadMessages(room)}
highlight={highlight}
isInvite={me.membership == "invite"}
isInvite={me.membership === "invite"}
onClick={this.onRoomTileClick}
/>,
);
@ -110,6 +109,10 @@ export default class ChatCreateOrReuseDialog extends React.Component {
this.props.onExistingRoomSelected(roomId);
}
onFinished() {
this.props.onFinished(false);
}
render() {
let title = '';
let content = null;
@ -177,7 +180,7 @@ export default class ChatCreateOrReuseDialog extends React.Component {
const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog');
return (
<BaseDialog className='mx_ChatCreateOrReuseDialog'
onFinished={this.props.onFinished.bind(false)}
onFinished={this.onFinished}
title={title}
contentId='mx_Dialog_content'
>

View file

@ -1,5 +1,6 @@
/*
Copyright 2017 Vector Creations Ltd
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.
@ -36,7 +37,7 @@ export default React.createClass({
getInitialState: function() {
return {
emailAddress: null,
emailAddress: '',
emailBusy: false,
};
},
@ -127,6 +128,7 @@ export default React.createClass({
const EditableText = sdk.getComponent('elements.EditableText');
const emailInput = this.state.emailBusy ? <Spinner /> : <EditableText
initialValue={this.state.emailAddress}
className="mx_SetEmailDialog_email_input"
autoFocus="true"
placeholder={_t("Email address")}

View file

@ -1,5 +1,6 @@
/*
Copyright 2017 Vector Creations Ltd
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.
@ -79,15 +80,11 @@ export default React.createClass({
Modal.createDialog(WarmFuzzy, {
didSetEmail: res.didSetEmail,
onFinished: () => {
this._onContinueClicked();
this.props.onFinished();
},
});
},
_onContinueClicked: function() {
this.props.onFinished(true);
},
_onPasswordChangeError: function(err) {
let errMsg = err.error || "";
if (err.httpStatus === 403) {

View file

@ -1,5 +1,6 @@
/*
Copyright 2015, 2016 OpenMarket Ltd
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.
@ -14,15 +15,9 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
'use strict';
const React = require('react');
import React from 'react';
import PropTypes from 'prop-types';
const KEY_TAB = 9;
const KEY_SHIFT = 16;
const KEY_WINDOWS = 91;
module.exports = React.createClass({
displayName: 'EditableText',
@ -66,9 +61,7 @@ module.exports = React.createClass({
},
componentWillReceiveProps: function(nextProps) {
if (nextProps.initialValue !== this.props.initialValue ||
nextProps.initialValue !== this.value
) {
if (nextProps.initialValue !== this.props.initialValue || nextProps.initialValue !== this.value) {
this.value = nextProps.initialValue;
if (this.refs.editable_div) {
this.showPlaceholder(!this.value);
@ -139,7 +132,7 @@ module.exports = React.createClass({
this.showPlaceholder(false);
}
if (ev.key == "Enter") {
if (ev.key === "Enter") {
ev.stopPropagation();
ev.preventDefault();
}
@ -156,9 +149,9 @@ module.exports = React.createClass({
this.value = ev.target.textContent;
}
if (ev.key == "Enter") {
if (ev.key === "Enter") {
this.onFinish(ev);
} else if (ev.key == "Escape") {
} else if (ev.key === "Escape") {
this.cancelEdit();
}
@ -193,7 +186,7 @@ module.exports = React.createClass({
const submit = (ev.key === "Enter") || shouldSubmit;
this.setState({
phase: this.Phases.Display,
}, function() {
}, () => {
if (this.value !== this.props.initialValue) {
self.onValueChanged(submit);
}
@ -204,23 +197,35 @@ module.exports = React.createClass({
const sel = window.getSelection();
sel.removeAllRanges();
if (this.props.blurToCancel) {this.cancelEdit();} else {this.onFinish(ev, this.props.blurToSubmit);}
if (this.props.blurToCancel) {
this.cancelEdit();
} else {
this.onFinish(ev, this.props.blurToSubmit);
}
this.showPlaceholder(!this.value);
},
render: function() {
let editable_el;
const {className, editable, initialValue, label, labelClassName} = this.props;
let editableEl;
if (!this.props.editable || (this.state.phase == this.Phases.Display && (this.props.label || this.props.labelClassName) && !this.value)) {
if (!editable || (this.state.phase === this.Phases.Display && (label || labelClassName) && !this.value)) {
// show the label
editable_el = <div className={this.props.className + " " + this.props.labelClassName} onClick={this.onClickDiv}>{ this.props.label || this.props.initialValue }</div>;
editableEl = <div className={className + " " + labelClassName} onClick={this.onClickDiv}>
{ label || initialValue }
</div>;
} else {
// show the content editable div, but manually manage its contents as react and contentEditable don't play nice together
editable_el = <div ref="editable_div" contentEditable="true" className={this.props.className}
onKeyDown={this.onKeyDown} onKeyUp={this.onKeyUp} onFocus={this.onFocus} onBlur={this.onBlur}></div>;
editableEl = <div ref="editable_div"
contentEditable={true}
className={className}
onKeyDown={this.onKeyDown}
onKeyUp={this.onKeyUp}
onFocus={this.onFocus}
onBlur={this.onBlur} />;
}
return editable_el;
return editableEl;
},
});

View file

@ -160,7 +160,7 @@ export default class ReplyThread extends React.Component {
}
static makeThread(parentEv, onWidgetLoad, ref) {
if (!SettingsStore.isFeatureEnabled("feature_rich_quoting") || !ReplyThread.getParentEventId(parentEv)) {
if (!ReplyThread.getParentEventId(parentEv)) {
return <div />;
}
return <ReplyThread parentEv={parentEv} onWidgetLoad={onWidgetLoad} ref={ref} />;

View file

@ -1,5 +1,6 @@
/*
Copyright 2017 New Vector Ltd.
Copyright 2018 Michael Telatynski <7t3chguy@gmail.com>
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@ -103,14 +104,27 @@ export default React.createClass({
}
},
onContextButtonClick: function(e) {
e.preventDefault();
e.stopPropagation();
_openContextMenu: function(x, y, chevronOffset) {
// Hide the (...) immediately
this.setState({ hover: false });
const TagTileContextMenu = sdk.getComponent('context_menus.TagTileContextMenu');
ContextualMenu.createMenu(TagTileContextMenu, {
chevronOffset: chevronOffset,
left: x,
top: y,
tag: this.props.tag,
onFinished: () => {
this.setState({ menuDisplayed: false });
},
});
this.setState({ menuDisplayed: true });
},
onContextButtonClick: function(e) {
e.preventDefault();
e.stopPropagation();
const elementRect = e.target.getBoundingClientRect();
// The window X and Y offsets are to adjust position when zoomed in to page
@ -119,17 +133,14 @@ export default React.createClass({
let y = (elementRect.top + (elementRect.height / 2) + window.pageYOffset);
y = y - (chevronOffset + 8); // where 8 is half the height of the chevron
const self = this;
ContextualMenu.createMenu(TagTileContextMenu, {
chevronOffset: chevronOffset,
left: x,
top: y,
tag: this.props.tag,
onFinished: function() {
self.setState({ menuDisplayed: false });
},
});
this.setState({ menuDisplayed: true });
this._openContextMenu(x, y, chevronOffset);
},
onContextMenu: function(e) {
e.preventDefault();
const chevronOffset = 12;
this._openContextMenu(e.clientX, e.clientY - (chevronOffset + 8), chevronOffset);
},
onMouseOver: function() {
@ -164,7 +175,7 @@ export default React.createClass({
<div className="mx_TagTile_context_button" onClick={this.onContextButtonClick}>
{ "\u00B7\u00B7\u00B7" }
</div> : <div />;
return <AccessibleButton className={className} onClick={this.onClick}>
return <AccessibleButton className={className} onClick={this.onClick} onContextMenu={this.onContextMenu}>
<div className="mx_TagTile_avatar" onMouseOver={this.onMouseOver} onMouseOut={this.onMouseOut}>
<BaseAvatar
name={name}

View file

@ -1,5 +1,6 @@
/*
Copyright 2017 Vector Creations Ltd
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.
@ -14,28 +15,15 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
'use strict';
import React from 'react';
import sdk from '../../../index';
import Modal from '../../../Modal';
import dis from '../../../dispatcher';
import { _t } from '../../../languageHandler';
export default React.createClass({
onUpdateClicked: function() {
const SetPasswordDialog = sdk.getComponent('dialogs.SetPasswordDialog');
Modal.createTrackedDialog('Set Password Dialog', 'Password Nag Bar', SetPasswordDialog, {
onFinished: (passwordChanged) => {
if (!passwordChanged) {
return;
}
// Notify SessionStore that the user's password was changed
dis.dispatch({
action: 'password_changed',
});
},
});
Modal.createTrackedDialog('Set Password Dialog', 'Password Nag Bar', SetPasswordDialog);
},
render: function() {

View file

@ -125,9 +125,7 @@ export default React.createClass({
const av = <BaseAvatar name={groupName} width={24} height={24} url={httpAvatarUrl} />;
const nameClasses = classNames({
'mx_RoomTile_name': true,
'mx_RoomTile_invite': this.props.isInvite,
const nameClasses = classNames('mx_RoomTile_name mx_RoomTile_invite mx_RoomTile_badgeShown', {
'mx_RoomTile_badgeShown': this.state.badgeHover || this.state.menuDisplayed,
});

View file

@ -1,6 +1,7 @@
/*
Copyright 2015, 2016 OpenMarket Ltd
Copyright 2018 New Vector Ltd
Copyright 2018 Michael Telatynski <7t3chguy@gmail.com>
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@ -15,8 +16,6 @@ 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 { MatrixClient } from 'matrix-js-sdk';
@ -29,9 +28,7 @@ import Promise from 'bluebird';
import { _t } from '../../../languageHandler';
import SettingsStore from "../../../settings/SettingsStore";
export default class extends React.Component {
displayName: 'MImageBody'
export default class MImageBody extends React.Component {
static propTypes = {
/* the MatrixEvent to show */
mxEvent: PropTypes.object.isRequired,
@ -41,11 +38,11 @@ export default class extends React.Component {
/* the maximum image height to use */
maxImageHeight: PropTypes.number,
}
};
static contextTypes = {
matrixClient: PropTypes.instanceOf(MatrixClient),
}
};
constructor(props) {
super(props);
@ -90,7 +87,7 @@ export default class extends React.Component {
}
onClick(ev) {
if (ev.button == 0 && !ev.metaKey) {
if (ev.button === 0 && !ev.metaKey) {
ev.preventDefault();
const content = this.props.mxEvent.getContent();
const httpUrl = this._getContentUrl();
@ -177,9 +174,7 @@ export default class extends React.Component {
return this.state.decryptedThumbnailUrl;
}
return this.state.decryptedUrl;
} else if (content.info &&
content.info.mimetype == "image/svg+xml" &&
content.info.thumbnail_url) {
} else if (content.info && content.info.mimetype === "image/svg+xml" && content.info.thumbnail_url) {
// special case to return client-generated thumbnails for SVGs, if any,
// given we deliberately don't thumbnail them serverside to prevent
// billion lol attacks and similar
@ -299,7 +294,7 @@ export default class extends React.Component {
// which has the same width as the timeline
// mx_MImageBody_thumbnail resizes img to exactly container size
img = <img className="mx_MImageBody_thumbnail" src={thumbUrl} ref="image"
style={{ "max-width": maxWidth + "px" }}
style={{ maxWidth: maxWidth + "px" }}
alt={content.body}
onError={this.onImageError}
onLoad={this.onImageLoad}
@ -308,14 +303,14 @@ export default class extends React.Component {
}
const thumbnail = (
<div className="mx_MImageBody_thumbnail_container" style={{ "max-height": maxHeight + "px" }} >
<div className="mx_MImageBody_thumbnail_container" style={{ maxHeight: maxHeight + "px" }} >
{ /* Calculate aspect ratio, using %padding will size _container correctly */ }
<div style={{ paddingBottom: (100 * infoHeight / infoWidth) + '%' }}></div>
<div style={{ paddingBottom: (100 * infoHeight / infoWidth) + '%' }} />
{ showPlaceholder &&
<div className="mx_MImageBody_thumbnail" style={{
// Constrain width here so that spinner appears central to the loaded thumbnail
"max-width": infoWidth + "px",
maxWidth: infoWidth + "px",
}}>
<div className="mx_MImageBody_thumbnail_spinner">
{ placeholder }

View file

@ -423,8 +423,7 @@ module.exports = React.createClass({
const mxEvent = this.props.mxEvent;
const content = mxEvent.getContent();
const stripReply = SettingsStore.isFeatureEnabled("feature_rich_quoting") &&
ReplyThread.getParentEventId(mxEvent);
const stripReply = ReplyThread.getParentEventId(mxEvent);
let body = HtmlUtils.bodyToHtml(content, this.props.highlights, {
disableBigEmoji: SettingsStore.getValue('TextualBody.disableBigEmoji'),
// Part of Replies fallback support

View file

@ -94,15 +94,7 @@ module.exports = React.createClass({
const hideWidgetKey = this.props.room.roomId + '_hide_widget_drawer';
switch (action.action) {
case 'appsDrawer':
// When opening the app drawer when there aren't any apps,
// auto-launch the integrations manager to skip the awkward
// click on "Add widget"
if (action.show) {
const apps = this._getApps();
if (apps.length === 0) {
this._launchManageIntegrations();
}
localStorage.removeItem(hideWidgetKey);
} else {
// Store hidden state of widget

View file

@ -159,54 +159,20 @@ export default class MessageComposer extends React.Component {
});
}
// _startCallApp(isAudioConf) {
// dis.dispatch({
// action: 'appsDrawer',
// show: true,
// });
// const appsStateEvents = this.props.room.currentState.getStateEvents('im.vector.modular.widgets', '');
// let appsStateEvent = {};
// if (appsStateEvents) {
// appsStateEvent = appsStateEvents.getContent();
// }
// if (!appsStateEvent.videoConf) {
// appsStateEvent.videoConf = {
// type: 'jitsi',
// // FIXME -- This should not be localhost
// url: 'http://localhost:8000/jitsi.html',
// data: {
// confId: this.props.room.roomId.replace(/[^A-Za-z0-9]/g, '_') + Date.now(),
// isAudioConf: isAudioConf,
// },
// };
// MatrixClientPeg.get().sendStateEvent(
// this.props.room.roomId,
// 'im.vector.modular.widgets',
// appsStateEvent,
// '',
// ).then(() => console.log('Sent state'), (e) => console.error(e));
// }
// }
onCallClick(ev) {
// NOTE -- Will be replaced by Jitsi code (currently commented)
dis.dispatch({
action: 'place_call',
type: ev.shiftKey ? "screensharing" : "video",
room_id: this.props.room.roomId,
});
// this._startCallApp(false);
}
onVoiceCallClick(ev) {
// NOTE -- Will be replaced by Jitsi code (currently commented)
dis.dispatch({
action: 'place_call',
type: "voice",
room_id: this.props.room.roomId,
});
// this._startCallApp(true);
}
onInputContentChanged(content: string, selection: {start: number, end: number}) {

View file

@ -28,7 +28,7 @@ import Promise from 'bluebird';
import MatrixClientPeg from '../../../MatrixClientPeg';
import type {MatrixClient} from 'matrix-js-sdk/lib/matrix';
import SlashCommands from '../../../SlashCommands';
import {processCommandInput} from '../../../SlashCommands';
import { KeyCode, isOnlyCtrlOrCmdKeyEvent } from '../../../Keyboard';
import Modal from '../../../Modal';
import sdk from '../../../index';
@ -721,7 +721,7 @@ export default class MessageComposerInput extends React.Component {
// Some commands (/join) require pills to be replaced with their text content
const commandText = this.removeMDLinks(contentState, ['#']);
const cmd = SlashCommands.processInput(this.props.room.roomId, commandText);
const cmd = processCommandInput(this.props.room.roomId, commandText);
if (cmd) {
if (!cmd.error) {
this.historyManager.save(contentState, this.state.isRichtextEnabled ? 'html' : 'markdown');
@ -1181,7 +1181,7 @@ export default class MessageComposerInput extends React.Component {
return (
<div className="mx_MessageComposer_input_wrapper">
<div className="mx_MessageComposer_autocomplete_wrapper">
{ SettingsStore.isFeatureEnabled("feature_rich_quoting") && <ReplyPreview /> }
<ReplyPreview />
<Autocomplete
ref={(e) => this.autocomplete = e}
room={this.props.room}

View file

@ -1,5 +1,6 @@
/*
Copyright 2015, 2016 OpenMarket Ltd
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.
@ -14,36 +15,28 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
'use strict';
const React = require('react');
const sdk = require('../../../index');
const MatrixClientPeg = require("../../../MatrixClientPeg");
import React from 'react';
import sdk from '../../../index';
import MatrixClientPeg from '../../../MatrixClientPeg';
import { _t } from '../../../languageHandler';
module.exports = React.createClass({
displayName: 'ChangeDisplayName',
_getDisplayName: function() {
_getDisplayName: async function() {
const cli = MatrixClientPeg.get();
return cli.getProfileInfo(cli.credentials.userId).then(function(result) {
let displayname = result.displayname;
if (!displayname) {
if (MatrixClientPeg.get().isGuest()) {
displayname = "Guest " + MatrixClientPeg.get().getUserIdLocalpart();
} else {
displayname = MatrixClientPeg.get().getUserIdLocalpart();
}
}
return displayname;
}, function(error) {
try {
const res = await cli.getProfileInfo(cli.getUserId());
return res.displayname;
} catch (e) {
throw new Error("Failed to fetch display name");
});
}
},
_changeDisplayName: function(new_displayname) {
_changeDisplayName: function(newDisplayname) {
const cli = MatrixClientPeg.get();
return cli.setDisplayName(new_displayname).catch(function(e) {
throw new Error("Failed to set display name");
return cli.setDisplayName(newDisplayname).catch(function(e) {
throw new Error("Failed to set display name", e);
});
},

View file

@ -1,5 +1,6 @@
/*
Copyright 2015, 2016 OpenMarket Ltd
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.
@ -14,14 +15,13 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
'use strict';
const React = require('react');
import PropTypes from 'prop-types';
const MatrixClientPeg = require("../../../MatrixClientPeg");
const Modal = require("../../../Modal");
const sdk = require("../../../index");
import dis from "../../../dispatcher";
import Promise from 'bluebird';
import AccessibleButton from '../elements/AccessibleButton';
import { _t } from '../../../languageHandler';
@ -143,6 +143,9 @@ module.exports = React.createClass({
});
cli.setPassword(authDict, newPassword).then(() => {
// Notify SessionStore that the user's password was changed
dis.dispatch({action: 'password_changed'});
if (this.props.shouldAskForEmail) {
return this._optionallySetEmail().then((confirmed) => {
this.props.onFinished({