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

 Conflicts:
	src/components/views/context_menus/RoomTileContextMenu.js
This commit is contained in:
Michael Telatynski 2019-11-28 16:25:59 +00:00
commit 6d69ec17d9
214 changed files with 6620 additions and 1948 deletions

View file

@ -107,7 +107,9 @@ module.exports = createReactClass({
this.props.room.roomId, WidgetUtils.getRoomWidgets(this.props.room),
);
return widgets.map((ev) => {
return WidgetUtils.makeAppConfig(ev.getStateKey(), ev.getContent(), ev.sender);
return WidgetUtils.makeAppConfig(
ev.getStateKey(), ev.getContent(), ev.getSender(), ev.getRoomId(), ev.getId(),
);
});
},
@ -159,6 +161,7 @@ module.exports = createReactClass({
return (<AppTile
key={app.id}
id={app.id}
eventId={app.eventId}
url={app.url}
name={app.name}
type={app.type}

View file

@ -21,11 +21,11 @@ import PropTypes from 'prop-types';
import classNames from 'classnames';
import flatMap from 'lodash/flatMap';
import type {Completion} from '../../../autocomplete/Autocompleter';
import Promise from 'bluebird';
import { Room } from 'matrix-js-sdk';
import SettingsStore from "../../../settings/SettingsStore";
import Autocompleter from '../../../autocomplete/Autocompleter';
import {sleep} from "../../../utils/promise";
const COMPOSER_SELECTED = 0;
@ -105,13 +105,11 @@ export default class Autocomplete extends React.Component {
autocompleteDelay = 0;
}
const deferred = Promise.defer();
this.debounceCompletionsRequest = setTimeout(() => {
this.processQuery(query, selection).then(() => {
deferred.resolve();
});
}, autocompleteDelay);
return deferred.promise;
return new Promise((resolve) => {
this.debounceCompletionsRequest = setTimeout(() => {
resolve(this.processQuery(query, selection));
}, autocompleteDelay);
});
}
processQuery(query, selection) {
@ -197,16 +195,16 @@ export default class Autocomplete extends React.Component {
}
forceComplete() {
const done = Promise.defer();
this.setState({
forceComplete: true,
hide: false,
}, () => {
this.complete(this.props.query, this.props.selection).then(() => {
done.resolve(this.countCompletions());
return new Promise((resolve) => {
this.setState({
forceComplete: true,
hide: false,
}, () => {
this.complete(this.props.query, this.props.selection).then(() => {
resolve(this.countCompletions());
});
});
});
return done.promise;
}
onCompletionClicked(selectionOffset: number): boolean {

View file

@ -36,7 +36,13 @@ export default function(props) {
_t("All devices for this user are trusted") :
_t("All devices in this encrypted room are trusted");
}
const icon = (<div className={e2eIconClasses} title={e2eTitle} />);
let style = null;
if (props.size) {
style = {width: `${props.size}px`, height: `${props.size}px`};
}
const icon = (<div className={e2eIconClasses} style={style} title={e2eTitle} />);
if (props.onClick) {
return (<AccessibleButton onClick={props.onClick}>{ icon }</AccessibleButton>);
} else {

View file

@ -548,7 +548,7 @@ module.exports = createReactClass({
const SenderProfile = sdk.getComponent('messages.SenderProfile');
const MemberAvatar = sdk.getComponent('avatars.MemberAvatar');
//console.log("EventTile showUrlPreview for %s is %s", this.props.mxEvent.getId(), this.props.showUrlPreview);
//console.info("EventTile showUrlPreview for %s is %s", this.props.mxEvent.getId(), this.props.showUrlPreview);
const content = this.props.mxEvent.getContent();
const msgtype = content.msgtype;
@ -606,8 +606,8 @@ module.exports = createReactClass({
mx_EventTile_last: this.props.last,
mx_EventTile_contextual: this.props.contextual,
mx_EventTile_actionBarFocused: this.state.actionBarFocused,
mx_EventTile_verified: this.state.verified === true,
mx_EventTile_unverified: this.state.verified === false,
mx_EventTile_verified: !isBubbleMessage && this.state.verified === true,
mx_EventTile_unverified: !isBubbleMessage && this.state.verified === false,
mx_EventTile_bad: isEncryptionFailure,
mx_EventTile_emote: msgtype === 'm.emote',
mx_EventTile_redacted: isRedacted,
@ -800,7 +800,7 @@ module.exports = createReactClass({
<a href={permalink} onClick={this.onPermalinkClicked}>
{ timestamp }
</a>
{ this._renderE2EPadlock() }
{ !isBubbleMessage && this._renderE2EPadlock() }
{ thread }
<EventTileType ref="tile"
mxEvent={this.props.mxEvent}
@ -826,7 +826,7 @@ module.exports = createReactClass({
{ readAvatars }
</div>
{ sender }
<div className={classNames("mx_EventTile_line", {mx_EventTile_bubbleLine: isBubbleMessage})}>
<div className="mx_EventTile_line">
<a
href={permalink}
onClick={this.onPermalinkClicked}
@ -834,7 +834,7 @@ module.exports = createReactClass({
>
{ timestamp }
</a>
{ this._renderE2EPadlock() }
{ !isBubbleMessage && this._renderE2EPadlock() }
{ thread }
<EventTileType ref="tile"
mxEvent={this.props.mxEvent}

View file

@ -53,7 +53,7 @@ module.exports = createReactClass({
);
}, (error)=>{
console.error("Failed to get URL preview: " + error);
}).done();
});
},
componentDidMount: function() {

View file

@ -248,7 +248,7 @@ module.exports = createReactClass({
return client.getStoredDevicesForUser(member.userId);
}).finally(function() {
self._cancelDeviceList = null;
}).done(function(devices) {
}).then(function(devices) {
if (cancelled) {
// we got cancelled - presumably a different user now
return;
@ -550,7 +550,16 @@ module.exports = createReactClass({
danger: true,
onFinished: (accepted) => {
if (!accepted) return;
this.context.matrixClient.deactivateSynapseUser(this.props.member.userId);
this.context.matrixClient.deactivateSynapseUser(this.props.member.userId).catch(e => {
console.error("Failed to deactivate user");
console.error(e);
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
Modal.createTrackedDialog('Failed to deactivate Synapse user', '', ErrorDialog, {
title: _t('Failed to deactivate user'),
description: ((e && e.message) ? e.message : _t("Operation failed")),
});
});
},
});
},
@ -572,7 +581,7 @@ module.exports = createReactClass({
},
).finally(()=>{
this.setState({ updating: this.state.updating - 1 });
}).done();
});
},
onPowerChange: async function(powerLevel) {
@ -629,7 +638,7 @@ module.exports = createReactClass({
this.setState({ updating: this.state.updating + 1 });
createRoom({dmUserId: this.props.member.userId}).finally(() => {
this.setState({ updating: this.state.updating - 1 });
}).done();
});
},
onLeaveClick: function() {
@ -689,7 +698,7 @@ module.exports = createReactClass({
const canAffectUser = them.powerLevel < me.powerLevel || isMe;
if (!canAffectUser) {
//console.log("Cannot affect user: %s >= %s", them.powerLevel, me.powerLevel);
//console.info("Cannot affect user: %s >= %s", them.powerLevel, me.powerLevel);
return can;
}
const editPowerLevel = (

View file

@ -25,7 +25,6 @@ import RoomViewStore from '../../../stores/RoomViewStore';
import Stickerpicker from './Stickerpicker';
import { makeRoomPermalink } from '../../../utils/permalinks/Permalinks';
import ContentMessages from '../../../ContentMessages';
import classNames from 'classnames';
import E2EIcon from './E2EIcon';
function ComposerAvatar(props) {
@ -353,13 +352,9 @@ export default class MessageComposer extends React.Component {
);
}
const wrapperClasses = classNames({
mx_MessageComposer_wrapper: true,
mx_MessageComposer_hasE2EIcon: !!this.props.e2eStatus,
});
return (
<div className="mx_MessageComposer">
<div className={wrapperClasses}>
<div className="mx_MessageComposer_wrapper">
<div className="mx_MessageComposer_row">
{ controls }
</div>

View file

@ -31,6 +31,9 @@ import {_t} from "../../../languageHandler";
const MAX_ROOMS = 20;
const MIN_ROOMS_BEFORE_ENABLED = 10;
// The threshold time in milliseconds to wait for an autojoined room to show up.
const AUTOJOIN_WAIT_THRESHOLD_MS = 90000; // 90 seconds
export default class RoomBreadcrumbs extends React.Component {
constructor(props) {
super(props);
@ -38,6 +41,10 @@ export default class RoomBreadcrumbs extends React.Component {
this.onAction = this.onAction.bind(this);
this._dispatcherRef = null;
// The room IDs we're waiting to come down the Room handler and when we
// started waiting for them. Used to track a room over an upgrade/autojoin.
this._waitingRoomQueue = [/* { roomId, addedTs } */];
}
componentWillMount() {
@ -54,7 +61,7 @@ export default class RoomBreadcrumbs extends React.Component {
MatrixClientPeg.get().on("Room.receipt", this.onRoomReceipt);
MatrixClientPeg.get().on("Room.timeline", this.onRoomTimeline);
MatrixClientPeg.get().on("Event.decrypted", this.onEventDecrypted);
MatrixClientPeg.get().on("Room", this.onRoomMembershipChanged);
MatrixClientPeg.get().on("Room", this.onRoom);
}
componentWillUnmount() {
@ -68,7 +75,7 @@ export default class RoomBreadcrumbs extends React.Component {
client.removeListener("Room.receipt", this.onRoomReceipt);
client.removeListener("Room.timeline", this.onRoomTimeline);
client.removeListener("Event.decrypted", this.onEventDecrypted);
client.removeListener("Room", this.onRoomMembershipChanged);
client.removeListener("Room", this.onRoom);
}
}
@ -87,6 +94,12 @@ export default class RoomBreadcrumbs extends React.Component {
onAction(payload) {
switch (payload.action) {
case 'view_room':
if (payload.auto_join && !MatrixClientPeg.get().getRoom(payload.room_id)) {
// Queue the room instead of pushing it immediately - we're probably just waiting
// for a join to complete (ie: joining the upgraded room).
this._waitingRoomQueue.push({roomId: payload.room_id, addedTs: (new Date).getTime()});
break;
}
this._appendRoomId(payload.room_id);
break;
@ -153,7 +166,20 @@ export default class RoomBreadcrumbs extends React.Component {
if (!this.state.enabled && this._shouldEnable()) {
this.setState({enabled: true});
}
}
};
onRoom = (room) => {
// Always check for membership changes when we see new rooms
this.onRoomMembershipChanged();
const waitingRoom = this._waitingRoomQueue.find(r => r.roomId === room.roomId);
if (!waitingRoom) return;
this._waitingRoomQueue.splice(this._waitingRoomQueue.indexOf(waitingRoom), 1);
const now = (new Date()).getTime();
if ((now - waitingRoom.addedTs) > AUTOJOIN_WAIT_THRESHOLD_MS) return; // Too long ago.
this._appendRoomId(room.roomId); // add the room we've been waiting for
};
_shouldEnable() {
const client = MatrixClientPeg.get();

View file

@ -460,13 +460,9 @@ export default class SlateMessageComposer extends React.Component {
const showFormatBar = this.state.showFormatting && this.state.inputState.isRichTextEnabled;
const wrapperClasses = classNames({
mx_MessageComposer_wrapper: true,
mx_MessageComposer_hasE2EIcon: !!this.props.e2eStatus,
});
return (
<div className="mx_MessageComposer">
<div className={wrapperClasses}>
<div className="mx_MessageComposer_wrapper">
<div className="mx_MessageComposer_row">
{ controls }
</div>

View file

@ -74,10 +74,10 @@ export default class Stickerpicker extends React.Component {
this.forceUpdate();
return this.scalarClient;
}).catch((e) => {
this._imError(_td("Failed to connect to integrations server"), e);
this._imError(_td("Failed to connect to integration manager"), e);
});
} else {
this._imError(_td("No integrations server is configured to manage stickers with"));
IntegrationManagers.sharedInstance().openNoManagerDialog();
}
}
@ -287,12 +287,17 @@ export default class Stickerpicker extends React.Component {
return stickersContent;
}
/**
// Dev note: this isn't jsdoc because it's angry.
/*
* Show the sticker picker overlay
* If no stickerpacks have been added, show a link to the integration manager add sticker packs page.
* @param {Event} e Event that triggered the function
*/
_onShowStickersClick(e) {
if (!SettingsStore.getValue("integrationProvisioning")) {
// Intercept this case and spawn a warning.
return IntegrationManagers.sharedInstance().showDisabledDialog();
}
// XXX: Simplify by using a context menu that is positioned relative to the sticker picker button
const buttonRect = e.target.getBoundingClientRect();
@ -346,7 +351,7 @@ export default class Stickerpicker extends React.Component {
}
/**
* Launch the integrations manager on the stickers integration page
* Launch the integration manager on the stickers integration page
*/
_launchManageIntegrations() {
// TODO: Open the right integration manager for the widget