[CONFLICT CHUNKS] Merge branch 'develop' into travis/sourcemaps-develop

This commit is contained in:
Travis Ralston 2020-01-09 14:15:09 -07:00
commit fde32f13a5
190 changed files with 6185 additions and 2225 deletions

View file

@ -34,11 +34,11 @@ import {parsePlainTextMessage} from '../../../editor/deserialize';
import {renderModel} from '../../../editor/render';
import {Room} from 'matrix-js-sdk';
import TypingStore from "../../../stores/TypingStore";
import EMOJIBASE from 'emojibase-data/en/compact.json';
import SettingsStore from "../../../settings/SettingsStore";
import EMOTICON_REGEX from 'emojibase-regex/emoticon';
import * as sdk from '../../../index';
import {Key} from "../../../Keyboard";
import {EMOTICON_TO_EMOJI} from "../../../emoji";
const REGEX_EMOTICON_WHITESPACE = new RegExp('(?:^|\\s)(' + EMOTICON_REGEX.source + ')\\s$');
@ -80,8 +80,8 @@ export default class BasicMessageEditor extends React.Component {
initialCaret: PropTypes.object, // See DocumentPosition in editor/model.js
};
constructor(props, context) {
super(props, context);
constructor(props) {
super(props);
this.state = {
autoComplete: null,
};
@ -108,7 +108,8 @@ export default class BasicMessageEditor extends React.Component {
const emoticonMatch = REGEX_EMOTICON_WHITESPACE.exec(range.text);
if (emoticonMatch) {
const query = emoticonMatch[1].toLowerCase().replace("-", "");
const data = EMOJIBASE.find(e => e.emoticon ? e.emoticon.toLowerCase() === query : false);
const data = EMOTICON_TO_EMOJI.get(query);
if (data) {
const {partCreator} = model;
const hasPrecedingSpace = emoticonMatch[0][0] === " ";

View file

@ -17,24 +17,62 @@ limitations under the License.
import classNames from 'classnames';
import { _t } from '../../../languageHandler';
import AccessibleButton from '../elements/AccessibleButton';
import SettingsStore from '../../../settings/SettingsStore';
export default function(props) {
const { isUser } = props;
const isNormal = props.status === "normal";
const isWarning = props.status === "warning";
const isVerified = props.status === "verified";
const e2eIconClasses = classNames({
mx_E2EIcon: true,
mx_E2EIcon_warning: isWarning,
mx_E2EIcon_normal: isNormal,
mx_E2EIcon_verified: isVerified,
}, props.className);
let e2eTitle;
if (isWarning) {
e2eTitle = props.isUser ?
_t("Some devices for this user are not trusted") :
_t("Some devices in this encrypted room are not trusted");
} else if (isVerified) {
e2eTitle = props.isUser ?
_t("All devices for this user are trusted") :
_t("All devices in this encrypted room are trusted");
const crossSigning = SettingsStore.isFeatureEnabled("feature_cross_signing");
if (crossSigning && isUser) {
if (isWarning) {
e2eTitle = _t(
"This user has not verified all of their devices.",
);
} else if (isNormal) {
e2eTitle = _t(
"You have not verified this user. " +
"This user has verified all of their devices.",
);
} else if (isVerified) {
e2eTitle = _t(
"You have verified this user. " +
"This user has verified all of their devices.",
);
}
} else if (crossSigning && !isUser) {
if (isWarning) {
e2eTitle = _t(
"Some users in this encrypted room are not verified by you or " +
"they have not verified their own devices.",
);
} else if (isVerified) {
e2eTitle = _t(
"All users in this encrypted room are verified by you and " +
"they have verified their own devices.",
);
}
} else if (!crossSigning && isUser) {
if (isWarning) {
e2eTitle = _t("Some devices for this user are not trusted");
} else if (isVerified) {
e2eTitle = _t("All devices for this user are trusted");
}
} else if (!crossSigning && !isUser) {
if (isWarning) {
e2eTitle = _t("Some devices in this encrypted room are not trusted");
} else if (isVerified) {
e2eTitle = _t("All devices in this encrypted room are trusted");
}
}
let style = null;

View file

@ -26,11 +26,11 @@ import {findEditableEvent} from '../../../utils/EventUtils';
import {parseEvent} from '../../../editor/deserialize';
import {PartCreator} from '../../../editor/parts';
import EditorStateTransfer from '../../../utils/EditorStateTransfer';
import {MatrixClient} from 'matrix-js-sdk';
import classNames from 'classnames';
import {EventStatus} from 'matrix-js-sdk';
import BasicMessageComposer from "./BasicMessageComposer";
import {Key} from "../../../Keyboard";
import MatrixClientContext from "../../../contexts/MatrixClientContext";
function _isReply(mxEvent) {
const relatesTo = mxEvent.getContent()["m.relates_to"];
@ -105,12 +105,10 @@ export default class EditMessageComposer extends React.Component {
editState: PropTypes.instanceOf(EditorStateTransfer).isRequired,
};
static contextTypes = {
matrixClient: PropTypes.instanceOf(MatrixClient).isRequired,
};
static contextType = MatrixClientContext;
constructor(props, context) {
super(props, context);
constructor(props) {
super(props);
this.model = null;
this._editorRef = null;
@ -124,7 +122,7 @@ export default class EditMessageComposer extends React.Component {
};
_getRoom() {
return this.context.matrixClient.getRoom(this.props.editState.getEvent().getRoomId());
return this.context.getRoom(this.props.editState.getEvent().getRoomId());
}
_onKeyDown = (event) => {
@ -190,7 +188,7 @@ export default class EditMessageComposer extends React.Component {
if (this._isContentModified(newContent)) {
const roomId = editedEvent.getRoomId();
this._cancelPreviousPendingEdit();
this.context.matrixClient.sendMessage(roomId, editContent);
this.context.sendMessage(roomId, editContent);
}
// close the event editing and focus composer
@ -205,7 +203,7 @@ export default class EditMessageComposer extends React.Component {
previousEdit.status === EventStatus.QUEUED ||
previousEdit.status === EventStatus.NOT_SENT
)) {
this.context.matrixClient.cancelPendingEvent(previousEdit);
this.context.cancelPendingEvent(previousEdit);
}
}
@ -232,7 +230,7 @@ export default class EditMessageComposer extends React.Component {
_createEditorModel() {
const {editState} = this.props;
const room = this._getRoom();
const partCreator = new PartCreator(room, this.context.matrixClient);
const partCreator = new PartCreator(room, this.context);
let parts;
if (editState.hasEditorState()) {
// if restoring state from a previous editor,

View file

@ -1,8 +1,8 @@
/*
Copyright 2015, 2016 OpenMarket Ltd
Copyright 2017 New Vector Ltd
Copyright 2019 The Matrix.org Foundation C.I.C.
Copyright 2019 Michael Telatynski <7t3chguy@gmail.com>
Copyright 2019, 2020 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@ -23,16 +23,29 @@ import PropTypes from 'prop-types';
import createReactClass from 'create-react-class';
import classNames from "classnames";
import { _t, _td } from '../../../languageHandler';
<<<<<<< HEAD
import * as TextForEvent from "../../../TextForEvent";
import Modal from "../../../Modal";
import * as sdk from "../../../index";
=======
const sdk = require('../../../index');
const TextForEvent = require('../../../TextForEvent');
>>>>>>> develop
import dis from '../../../dispatcher';
import SettingsStore from "../../../settings/SettingsStore";
import {EventStatus, MatrixClient} from 'matrix-js-sdk';
import {EventStatus} from 'matrix-js-sdk';
import {formatTime} from "../../../DateUtils";
import {MatrixClientPeg} from '../../../MatrixClientPeg';
import {ALL_RULE_TYPES} from "../../../mjolnir/BanList";
<<<<<<< HEAD
import * as ObjectUtils from "../../../ObjectUtils";
=======
import MatrixClientContext from "../../../contexts/MatrixClientContext";
const ObjectUtils = require('../../../ObjectUtils');
>>>>>>> develop
const eventTileTypes = {
'm.room.message': 'messages.MessageEvent',
@ -218,8 +231,8 @@ export default createReactClass({
};
},
contextTypes: {
matrixClient: PropTypes.instanceOf(MatrixClient).isRequired,
statics: {
contextType: MatrixClientContext,
},
componentWillMount: function() {
@ -233,7 +246,7 @@ export default createReactClass({
componentDidMount: function() {
this._suppressReadReceiptAnimation = false;
const client = this.context.matrixClient;
const client = this.context;
client.on("deviceVerificationChanged", this.onDeviceVerificationChanged);
this.props.mxEvent.on("Event.decrypted", this._onDecrypted);
if (this.props.showReactions) {
@ -258,7 +271,7 @@ export default createReactClass({
},
componentWillUnmount: function() {
const client = this.context.matrixClient;
const client = this.context;
client.removeListener("deviceVerificationChanged", this.onDeviceVerificationChanged);
this.props.mxEvent.removeListener("Event.decrypted", this._onDecrypted);
if (this.props.showReactions) {
@ -287,7 +300,7 @@ export default createReactClass({
return;
}
const verified = await this.context.matrixClient.isEventSenderVerified(mxEvent);
const verified = await this.context.isEventSenderVerified(mxEvent);
this.setState({
verified: verified,
}, () => {
@ -345,11 +358,11 @@ export default createReactClass({
},
shouldHighlight: function() {
const actions = this.context.matrixClient.getPushActionsForEvent(this.props.mxEvent);
const actions = this.context.getPushActionsForEvent(this.props.mxEvent);
if (!actions || !actions.tweaks) { return false; }
// don't show self-highlights from another of our clients
if (this.props.mxEvent.getSender() === this.context.matrixClient.credentials.userId) {
if (this.props.mxEvent.getSender() === this.context.credentials.userId) {
return false;
}
@ -438,15 +451,6 @@ export default createReactClass({
});
},
onCryptoClick: function(e) {
const event = this.props.mxEvent;
Modal.createTrackedDialogAsync('Encrypted Event Dialog', '',
import('../../../async-components/views/dialogs/EncryptedEventDialog'),
{event},
);
},
onRequestKeysClick: function() {
this.setState({
// Indicate in the UI that the keys have been requested (this is expected to
@ -457,7 +461,7 @@ export default createReactClass({
// Cancel any outgoing key request for this event and resend it. If a response
// is received for the request with the required keys, the event could be
// decrypted successfully.
this.context.matrixClient.cancelAndResendEventRoomKeyRequest(this.props.mxEvent);
this.context.cancelAndResendEventRoomKeyRequest(this.props.mxEvent);
},
onPermalinkClicked: function(e) {
@ -474,11 +478,10 @@ export default createReactClass({
_renderE2EPadlock: function() {
const ev = this.props.mxEvent;
const props = {onClick: this.onCryptoClick};
// event could not be decrypted
if (ev.getContent().msgtype === 'm.bad.encrypted') {
return <E2ePadlockUndecryptable {...props} />;
return <E2ePadlockUndecryptable />;
}
// event is encrypted, display padlock corresponding to whether or not it is verified
@ -486,11 +489,11 @@ export default createReactClass({
if (this.state.verified) {
return; // no icon for verified
} else {
return (<E2ePadlockUnverified {...props} />);
return (<E2ePadlockUnverified />);
}
}
if (this.context.matrixClient.isRoomEncrypted(ev.getRoomId())) {
if (this.context.isRoomEncrypted(ev.getRoomId())) {
// else if room is encrypted
// and event is being encrypted or is not_sent (Unknown Devices/Network Error)
if (ev.status === EventStatus.ENCRYPTING) {
@ -503,7 +506,7 @@ export default createReactClass({
return; // we expect this to be unencrypted
}
// if the event is not encrypted, but it's an e2e room, show the open padlock
return <E2ePadlockUnencrypted {...props} />;
return <E2ePadlockUnencrypted />;
}
// no padlock needed
@ -737,7 +740,7 @@ export default createReactClass({
switch (this.props.tileShape) {
case 'notif': {
const room = this.context.matrixClient.getRoom(this.props.mxEvent.getRoomId());
const room = this.context.getRoom(this.props.mxEvent.getRoomId());
return (
<div className={classes}>
<div className="mx_EventTile_roomName">
@ -915,7 +918,6 @@ class E2ePadlock extends React.Component {
static propTypes = {
icon: PropTypes.string.isRequired,
title: PropTypes.string.isRequired,
onClick: PropTypes.func,
};
constructor() {
@ -926,10 +928,6 @@ class E2ePadlock extends React.Component {
};
}
onClick = (e) => {
if (this.props.onClick) this.props.onClick(e);
};
onHoverStart = () => {
this.setState({hover: true});
};

View file

@ -20,7 +20,7 @@ import PropTypes from 'prop-types';
import createReactClass from 'create-react-class';
import { _t } from '../../../languageHandler';
import dis from '../../../dispatcher';
import { KeyCode } from '../../../Keyboard';
import {Key} from '../../../Keyboard';
export default createReactClass({
@ -52,8 +52,8 @@ export default createReactClass({
},
_onKeyDown: function(ev) {
switch (ev.keyCode) {
case KeyCode.ESCAPE:
switch (ev.key) {
case Key.ESCAPE:
this.props.onCancelClick();
break;
}

View file

@ -18,12 +18,17 @@ limitations under the License.
import React, {createRef} from 'react';
import PropTypes from 'prop-types';
import createReactClass from 'create-react-class';
import { linkifyElement } from '../../../HtmlUtils';
import { AllHtmlEntities } from 'html-entities';
import {linkifyElement} from '../../../HtmlUtils';
import SettingsStore from "../../../settings/SettingsStore";
<<<<<<< HEAD
import {MatrixClientPeg} from "../../../MatrixClientPeg";
import * as sdk from "../../../index";
import Modal from "../../../Modal";
import * as ImageUtils from "../../../ImageUtils";
=======
import { _t } from "../../../languageHandler";
>>>>>>> develop
export default createReactClass({
displayName: 'LinkPreviewWidget',
@ -125,6 +130,11 @@ export default createReactClass({
</div>;
}
// The description includes &-encoded HTML entities, we decode those as React treats the thing as an
// opaque string. This does not allow any HTML to be injected into the DOM.
const description = AllHtmlEntities.decode(p["og:description"] || "");
const AccessibleButton = sdk.getComponent('elements.AccessibleButton');
return (
<div className="mx_LinkPreviewWidget" >
{ img }
@ -132,12 +142,13 @@ export default createReactClass({
<div className="mx_LinkPreviewWidget_title"><a href={this.props.link} target="_blank" rel="noopener">{ p["og:title"] }</a></div>
<div className="mx_LinkPreviewWidget_siteName">{ p["og:site_name"] ? (" - " + p["og:site_name"]) : null }</div>
<div className="mx_LinkPreviewWidget_description" ref={this._description}>
{ p["og:description"] }
{ description }
</div>
</div>
<img className="mx_LinkPreviewWidget_cancel mx_filterFlipColor"
src={require("../../../../res/img/cancel.svg")} width="18" height="18"
onClick={this.props.onCancelClick} />
<AccessibleButton className="mx_LinkPreviewWidget_cancel" onClick={this.props.onCancelClick} aria-label={_t("Close preview")}>
<img className="mx_filterFlipColor" alt="" role="presentation"
src={require("../../../../res/img/cancel.svg")} width="18" height="18" />
</AccessibleButton>
</div>
);
},

View file

@ -23,6 +23,8 @@ import classNames from 'classnames';
export default class MemberDeviceInfo extends React.Component {
render() {
const DeviceVerifyButtons = sdk.getComponent('elements.DeviceVerifyButtons');
// XXX: These checks are not cross-signing aware but this component is only used
// from the old, pre-cross-signing memberinfopanel
const iconClasses = classNames({
mx_MemberDeviceInfo_icon: true,
mx_MemberDeviceInfo_icon_blacklisted: this.props.device.isBlocked(),

View file

@ -31,7 +31,6 @@ import React from 'react';
import PropTypes from 'prop-types';
import createReactClass from 'create-react-class';
import classNames from 'classnames';
import { MatrixClient } from 'matrix-js-sdk';
import dis from '../../../dispatcher';
import Modal from '../../../Modal';
import * as sdk from '../../../index';
@ -47,8 +46,13 @@ import MultiInviter from "../../../utils/MultiInviter";
import SettingsStore from "../../../settings/SettingsStore";
import E2EIcon from "./E2EIcon";
import AutoHideScrollbar from "../../structures/AutoHideScrollbar";
<<<<<<< HEAD
import {MatrixClientPeg} from "../../../MatrixClientPeg";
import {EventTimeline} from "matrix-js-sdk";
=======
import MatrixClientPeg from "../../../MatrixClientPeg";
import MatrixClientContext from "../../../contexts/MatrixClientContext";
>>>>>>> develop
export default createReactClass({
displayName: 'MemberInfo',
@ -76,13 +80,13 @@ export default createReactClass({
};
},
contextTypes: {
matrixClient: PropTypes.instanceOf(MatrixClient).isRequired,
statics: {
contextType: MatrixClientContext,
},
componentWillMount: function() {
this._cancelDeviceList = null;
const cli = this.context.matrixClient;
const cli = this.context;
// only display the devices list if our client supports E2E
this._enableDevices = cli.isCryptoEnabled();
@ -112,7 +116,7 @@ export default createReactClass({
},
componentWillUnmount: function() {
const client = this.context.matrixClient;
const client = this.context;
if (client) {
client.removeListener("deviceVerificationChanged", this.onDeviceVerificationChanged);
client.removeListener("Room", this.onRoom);
@ -131,7 +135,7 @@ export default createReactClass({
},
_checkIgnoreState: function() {
const isIgnoring = this.context.matrixClient.isUserIgnored(this.props.member.userId);
const isIgnoring = this.context.isUserIgnored(this.props.member.userId);
this.setState({isIgnoring: isIgnoring});
},
@ -163,7 +167,7 @@ export default createReactClass({
// Promise.resolve to handle transition from static result to promise; can be removed
// in future
Promise.resolve(this.context.matrixClient.getStoredDevicesForUser(userId)).then((devices) => {
Promise.resolve(this.context.getStoredDevicesForUser(userId)).then((devices) => {
this.setState({
devices: devices,
e2eStatus: this._getE2EStatus(devices),
@ -197,7 +201,7 @@ export default createReactClass({
onRoomReceipt: function(receiptEvent, room) {
// because if we read a notification, it will affect notification count
// only bother updating if there's a receipt from us
if (findReadReceiptFromUserId(receiptEvent, this.context.matrixClient.credentials.userId)) {
if (findReadReceiptFromUserId(receiptEvent, this.context.credentials.userId)) {
this.forceUpdate();
}
},
@ -242,7 +246,7 @@ export default createReactClass({
let cancelled = false;
this._cancelDeviceList = function() { cancelled = true; };
const client = this.context.matrixClient;
const client = this.context;
const self = this;
client.downloadKeys([member.userId], true).then(() => {
return client.getStoredDevicesForUser(member.userId);
@ -267,7 +271,7 @@ export default createReactClass({
},
onIgnoreToggle: function() {
const ignoredUsers = this.context.matrixClient.getIgnoredUsers();
const ignoredUsers = this.context.getIgnoredUsers();
if (this.state.isIgnoring) {
const index = ignoredUsers.indexOf(this.props.member.userId);
if (index !== -1) ignoredUsers.splice(index, 1);
@ -275,7 +279,7 @@ export default createReactClass({
ignoredUsers.push(this.props.member.userId);
}
this.context.matrixClient.setIgnoredUsers(ignoredUsers).then(() => {
this.context.setIgnoredUsers(ignoredUsers).then(() => {
return this.setState({isIgnoring: !this.state.isIgnoring});
});
},
@ -293,7 +297,7 @@ export default createReactClass({
if (!proceed) return;
this.setState({ updating: this.state.updating + 1 });
this.context.matrixClient.kick(
this.context.kick(
this.props.member.roomId, this.props.member.userId,
reason || undefined,
).then(function() {
@ -329,11 +333,11 @@ export default createReactClass({
this.setState({ updating: this.state.updating + 1 });
let promise;
if (this.props.member.membership === 'ban') {
promise = this.context.matrixClient.unban(
promise = this.context.unban(
this.props.member.roomId, this.props.member.userId,
);
} else {
promise = this.context.matrixClient.ban(
promise = this.context.ban(
this.props.member.roomId, this.props.member.userId,
reason || undefined,
);
@ -360,7 +364,7 @@ export default createReactClass({
onRedactAllMessages: async function() {
const {roomId, userId} = this.props.member;
const room = this.context.matrixClient.getRoom(roomId);
const room = this.context.getRoom(roomId);
if (!room) {
return;
}
@ -414,7 +418,7 @@ export default createReactClass({
console.info(`Started redacting recent ${count} messages for ${user} in ${roomId}`);
await Promise.all(eventsToRedact.map(async event => {
try {
await this.context.matrixClient.redactEvent(roomId, event.getId());
await this.context.redactEvent(roomId, event.getId());
} catch (err) {
// log and swallow errors
console.error("Could not redact", event.getId());
@ -446,11 +450,11 @@ export default createReactClass({
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
const roomId = this.props.member.roomId;
const target = this.props.member.userId;
const room = this.context.matrixClient.getRoom(roomId);
const room = this.context.getRoom(roomId);
if (!room) return;
// if muting self, warn as it may be irreversible
if (target === this.context.matrixClient.getUserId()) {
if (target === this.context.getUserId()) {
try {
if (!(await this._warnSelfDemote())) return;
} catch (e) {
@ -478,7 +482,7 @@ export default createReactClass({
if (!isNaN(level)) {
this.setState({ updating: this.state.updating + 1 });
this.context.matrixClient.setPowerLevel(roomId, target, level, powerLevelEvent).then(
this.context.setPowerLevel(roomId, target, level, powerLevelEvent).then(
function() {
// NO-OP; rely on the m.room.member event coming down else we could
// get out of sync if we force setState here!
@ -500,13 +504,13 @@ export default createReactClass({
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
const roomId = this.props.member.roomId;
const target = this.props.member.userId;
const room = this.context.matrixClient.getRoom(roomId);
const room = this.context.getRoom(roomId);
if (!room) return;
const powerLevelEvent = room.currentState.getStateEvents("m.room.power_levels", "");
if (!powerLevelEvent) return;
const me = room.getMember(this.context.matrixClient.credentials.userId);
const me = room.getMember(this.context.credentials.userId);
if (!me) return;
const defaultLevel = powerLevelEvent.getContent().users_default;
@ -515,7 +519,7 @@ export default createReactClass({
// toggle the level
const newLevel = this.state.isTargetMod ? defaultLevel : modLevel;
this.setState({ updating: this.state.updating + 1 });
this.context.matrixClient.setPowerLevel(roomId, target, parseInt(newLevel), powerLevelEvent).then(
this.context.setPowerLevel(roomId, target, parseInt(newLevel), powerLevelEvent).then(
function() {
// NO-OP; rely on the m.room.member event coming down else we could
// get out of sync if we force setState here!
@ -550,7 +554,7 @@ export default createReactClass({
danger: true,
onFinished: (accepted) => {
if (!accepted) return;
this.context.matrixClient.deactivateSynapseUser(this.props.member.userId).catch(e => {
this.context.deactivateSynapseUser(this.props.member.userId).catch(e => {
console.error("Failed to deactivate user");
console.error(e);
@ -566,7 +570,7 @@ export default createReactClass({
_applyPowerChange: function(roomId, target, powerLevel, powerLevelEvent) {
this.setState({ updating: this.state.updating + 1 });
this.context.matrixClient.setPowerLevel(roomId, target, parseInt(powerLevel), powerLevelEvent).then(
this.context.setPowerLevel(roomId, target, parseInt(powerLevel), powerLevelEvent).then(
function() {
// NO-OP; rely on the m.room.member event coming down else we could
// get out of sync if we force setState here!
@ -587,7 +591,7 @@ export default createReactClass({
onPowerChange: async function(powerLevel) {
const roomId = this.props.member.roomId;
const target = this.props.member.userId;
const room = this.context.matrixClient.getRoom(roomId);
const room = this.context.getRoom(roomId);
if (!room) return;
const powerLevelEvent = room.currentState.getStateEvents("m.room.power_levels", "");
@ -598,7 +602,7 @@ export default createReactClass({
return;
}
const myUserId = this.context.matrixClient.getUserId();
const myUserId = this.context.getUserId();
const QuestionDialog = sdk.getComponent("dialogs.QuestionDialog");
// If we are changing our own PL it can only ever be decreasing, which we cannot reverse.
@ -650,9 +654,9 @@ export default createReactClass({
_calculateOpsPermissions: async function(member) {
let canDeactivate = false;
if (this.context.matrixClient) {
if (this.context) {
try {
canDeactivate = await this.context.matrixClient.isSynapseAdministrator();
canDeactivate = await this.context.isSynapseAdministrator();
} catch (e) {
console.error(e);
}
@ -665,13 +669,13 @@ export default createReactClass({
},
muted: false,
};
const room = this.context.matrixClient.getRoom(member.roomId);
const room = this.context.getRoom(member.roomId);
if (!room) return defaultPerms;
const powerLevels = room.currentState.getStateEvents("m.room.power_levels", "");
if (!powerLevels) return defaultPerms;
const me = room.getMember(this.context.matrixClient.credentials.userId);
const me = room.getMember(this.context.credentials.userId);
if (!me) return defaultPerms;
const them = member;
@ -738,7 +742,7 @@ export default createReactClass({
const avatarUrl = member.getMxcAvatarUrl();
if (!avatarUrl) return;
const httpUrl = this.context.matrixClient.mxcUrlToHttp(avatarUrl);
const httpUrl = this.context.mxcUrlToHttp(avatarUrl);
const ImageView = sdk.getComponent("elements.ImageView");
const params = {
src: httpUrl,
@ -797,7 +801,7 @@ export default createReactClass({
},
_renderUserOptions: function() {
const cli = this.context.matrixClient;
const cli = this.context;
const member = this.props.member;
let ignoreButton = null;
@ -905,9 +909,9 @@ export default createReactClass({
let synapseDeactivateButton;
let spinner;
if (this.props.member.userId !== this.context.matrixClient.credentials.userId) {
if (this.props.member.userId !== this.context.credentials.userId) {
// TODO: Immutable DMs replaces a lot of this
const dmRoomMap = new DMRoomMap(this.context.matrixClient);
const dmRoomMap = new DMRoomMap(this.context);
// dmRooms will not include dmRooms that we have been invited into but did not join.
// Because DMRoomMap runs off account_data[m.direct] which is only set on join of dm room.
// XXX: we potentially want DMs we have been invited to, to also show up here :L
@ -918,7 +922,7 @@ export default createReactClass({
const tiles = [];
for (const roomId of dmRooms) {
const room = this.context.matrixClient.getRoom(roomId);
const room = this.context.getRoom(roomId);
if (room) {
const myMembership = room.getMyMembership();
// not a DM room if we have are not joined
@ -1064,12 +1068,12 @@ export default createReactClass({
}
}
const room = this.context.matrixClient.getRoom(this.props.member.roomId);
const room = this.context.getRoom(this.props.member.roomId);
const powerLevelEvent = room ? room.currentState.getStateEvents("m.room.power_levels", "") : null;
const powerLevelUsersDefault = powerLevelEvent ? powerLevelEvent.getContent().users_default : 0;
const enablePresenceByHsUrl = SdkConfig.get()["enable_presence_by_hs_url"];
const hsUrl = this.context.matrixClient.baseUrl;
const hsUrl = this.context.baseUrl;
let showPresence = true;
if (enablePresenceByHsUrl && enablePresenceByHsUrl[hsUrl] !== undefined) {
showPresence = enablePresenceByHsUrl[hsUrl];
@ -1108,7 +1112,7 @@ export default createReactClass({
</div>
</div>;
const isEncrypted = this.context.matrixClient.isRoomEncrypted(this.props.member.roomId);
const isEncrypted = this.context.isRoomEncrypted(this.props.member.roomId);
if (this.state.e2eStatus && isEncrypted) {
e2eIconElement = (<E2EIcon status={this.state.e2eStatus} isUser={true} />);
}
@ -1117,7 +1121,7 @@ export default createReactClass({
const avatarUrl = this.props.member.getMxcAvatarUrl();
let avatarElement;
if (avatarUrl) {
const httpUrl = this.context.matrixClient.mxcUrlToHttp(avatarUrl, 800, 800);
const httpUrl = this.context.mxcUrlToHttp(avatarUrl, 800, 800);
avatarElement = <div className="mx_MemberInfo_avatar">
<img src={httpUrl} />
</div>;

View file

@ -32,7 +32,15 @@ const INITIAL_LOAD_NUM_MEMBERS = 30;
const INITIAL_LOAD_NUM_INVITED = 5;
const SHOW_MORE_INCREMENT = 100;
<<<<<<< HEAD
export default createReactClass({
=======
// Regex applied to filter our punctuation in member names before applying sort, to fuzzy it a little
// matches all ASCII punctuation: !"#$%&'()*+,-./:;<=>?@[\]^_`{|}~
const SORT_REGEX = /[\x21-\x2F\x3A-\x40\x5B-\x60\x7B-\x7E]+/g;
module.exports = createReactClass({
>>>>>>> develop
displayName: 'MemberList',
getInitialState: function() {
@ -336,10 +344,13 @@ export default createReactClass({
}
// Fourth by name (alphabetical)
const nameA = memberA.name[0] === '@' ? memberA.name.substr(1) : memberA.name;
const nameB = memberB.name[0] === '@' ? memberB.name.substr(1) : memberB.name;
const nameA = (memberA.name[0] === '@' ? memberA.name.substr(1) : memberA.name).replace(SORT_REGEX, "");
const nameB = (memberB.name[0] === '@' ? memberB.name.substr(1) : memberB.name).replace(SORT_REGEX, "");
// console.log(`Comparing userA_name=${nameA} against userB_name=${nameB} - returning`);
return nameA.localeCompare(nameB);
return nameA.localeCompare(nameB, {
ignorePunctuation: true,
sensitivity: "base",
});
},
onSearchQueryChanged: function(searchQuery) {

View file

@ -107,8 +107,8 @@ class UploadButton extends React.Component {
roomId: PropTypes.string.isRequired,
}
constructor(props, context) {
super(props, context);
constructor(props) {
super(props);
this.onUploadClick = this.onUploadClick.bind(this);
this.onUploadFileInputChange = this.onUploadFileInputChange.bind(this);
@ -165,8 +165,8 @@ class UploadButton extends React.Component {
}
export default class MessageComposer extends React.Component {
constructor(props, context) {
super(props, context);
constructor(props) {
super(props);
this.onInputStateChanged = this.onInputStateChanged.bind(this);
this.onEvent = this.onEvent.bind(this);
this._onRoomStateEvents = this._onRoomStateEvents.bind(this);

View file

@ -15,8 +15,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import React from 'react';
import ReactDOM from 'react-dom';
import React, {createRef} from 'react';
import PropTypes from 'prop-types';
import createReactClass from 'create-react-class';
import('../../../VelocityBounce');
@ -88,6 +87,10 @@ export default createReactClass({
};
},
UNSAFE_componentWillMount: function() {
this._avatar = createRef();
},
componentWillUnmount: function() {
// before we remove the rr, store its location in the map, so that if
// it reappears, it can be animated from the right place.
@ -103,7 +106,7 @@ export default createReactClass({
return;
}
const avatarNode = ReactDOM.findDOMNode(this);
const avatarNode = this._avatar.current;
rrInfo.top = avatarNode.offsetTop;
rrInfo.left = avatarNode.offsetLeft;
rrInfo.parent = avatarNode.offsetParent;
@ -123,7 +126,7 @@ export default createReactClass({
oldTop = oldInfo.top + oldInfo.parent.getBoundingClientRect().top;
}
const newElement = ReactDOM.findDOMNode(this);
const newElement = this._avatar.current;
let startTopOffset;
if (!newElement.offsetParent) {
// this seems to happen sometimes for reasons I don't understand
@ -173,7 +176,7 @@ export default createReactClass({
render: function() {
const MemberAvatar = sdk.getComponent('avatars.MemberAvatar');
if (this.state.suppressDisplay) {
return <div />;
return <div ref={this._avatar} />;
}
const style = {
@ -213,6 +216,7 @@ export default createReactClass({
style={style}
title={title}
onClick={this.props.onClick}
inputRef={this._avatar}
/>
</Velociraptor>
);

View file

@ -35,8 +35,8 @@ export default class ReplyPreview extends React.Component {
permalinkCreator: PropTypes.instanceOf(RoomPermalinkCreator).isRequired,
};
constructor(props, context) {
super(props, context);
constructor(props) {
super(props);
this.unmounted = false;
this.state = {

View file

@ -21,6 +21,7 @@ import React from "react";
import ReactDOM from "react-dom";
import createReactClass from 'create-react-class';
import PropTypes from 'prop-types';
import utils from "matrix-js-sdk/lib/utils";
import { _t } from '../../../languageHandler';
import {MatrixClientPeg} from "../../../MatrixClientPeg";
import rate_limited_func from "../../../ratelimitedfunc";
@ -588,10 +589,17 @@ export default createReactClass({
_applySearchFilter: function(list, filter) {
if (filter === "") return list;
const lcFilter = filter.toLowerCase();
// apply toLowerCase before and after removeHiddenChars because different rules get applied
// e.g M -> M but m -> n, yet some unicode homoglyphs come out as uppercase, e.g 𝚮 -> H
const fuzzyFilter = utils.removeHiddenChars(lcFilter).toLowerCase();
// case insensitive if room name includes filter,
// or if starts with `#` and one of room's aliases starts with filter
return list.filter((room) => (room.name && room.name.toLowerCase().includes(lcFilter)) ||
(filter[0] === '#' && room.getAliases().some((alias) => alias.toLowerCase().startsWith(lcFilter))));
return list.filter((room) => {
if (filter[0] === "#" && room.getAliases().some((alias) => alias.toLowerCase().startsWith(lcFilter))) {
return true;
}
return room.name && utils.removeHiddenChars(room.name.toLowerCase()).toLowerCase().includes(fuzzyFilter);
});
},
_handleCollapsedState: function(key, collapsed) {
@ -627,7 +635,6 @@ export default createReactClass({
const defaultProps = {
collapsed: this.props.collapsed,
isFiltered: !!this.props.searchFilter,
incomingCall: this.state.incomingCall,
};
subListsProps.forEach((p) => {
@ -640,7 +647,7 @@ export default createReactClass({
}));
return subListsProps.reduce((components, props, i) => {
props = Object.assign({}, defaultProps, props);
props = {...defaultProps, ...props};
const isLast = i === subListsProps.length - 1;
const len = props.list.length + (props.extraTiles ? props.extraTiles.length : 0);
const {key, label, onHeaderClick, ...otherProps} = props;
@ -651,12 +658,12 @@ export default createReactClass({
onHeaderClick(collapsed);
}
};
let startAsHidden = props.startAsHidden || this.collapsedState[chosenKey];
const startAsHidden = props.startAsHidden || this.collapsedState[chosenKey];
this._layoutSections.push({
id: chosenKey,
count: len,
});
let subList = (<RoomSubList
const subList = (<RoomSubList
ref={this._subListRef.bind(this, chosenKey)}
startAsHidden={startAsHidden}
forceExpand={!!this.props.searchFilter}

View file

@ -1,5 +1,6 @@
/*
Copyright 2018, 2019 New Vector Ltd
Copyright 2020 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@ -70,10 +71,14 @@ export default class RoomRecoveryReminder extends React.PureComponent {
// verified, so restore the backup which will give us the keys from it and
// allow us to trust it (ie. upload keys to it)
const RestoreKeyBackupDialog = sdk.getComponent('dialogs.keybackup.RestoreKeyBackupDialog');
Modal.createTrackedDialog('Restore Backup', '', RestoreKeyBackupDialog, {});
Modal.createTrackedDialog(
'Restore Backup', '', RestoreKeyBackupDialog, null, null,
/* priority = */ false, /* static = */ true,
);
} else {
Modal.createTrackedDialogAsync("Key Backup", "Key Backup",
import("../../../async-components/views/dialogs/keybackup/CreateKeyBackupDialog"),
null, null, /* priority = */ false, /* static = */ true,
);
}
}
@ -150,14 +155,14 @@ export default class RoomRecoveryReminder extends React.PureComponent {
onClick={this.onSetupClick}>
{setupCaption}
</AccessibleButton>
<p><AccessibleButton className="mx_RoomRecoveryReminder_secondary mx_linkButton"
<AccessibleButton className="mx_RoomRecoveryReminder_secondary mx_linkButton"
onClick={this.onOnNotNowClick}>
{ _t("Not now") }
</AccessibleButton></p>
<p><AccessibleButton className="mx_RoomRecoveryReminder_secondary mx_linkButton"
</AccessibleButton>
<AccessibleButton className="mx_RoomRecoveryReminder_secondary mx_linkButton"
onClick={this.onDontAskAgainClick}>
{ _t("Don't ask me again") }
</AccessibleButton></p>
</AccessibleButton>
</div>
</div>
);

View file

@ -68,11 +68,6 @@ export default createReactClass({
});
},
_isDirectMessageRoom: function(roomId) {
const dmRooms = DMRoomMap.shared().getUserIdForRoomId(roomId);
return Boolean(dmRooms);
},
_shouldShowStatusMessage() {
if (!SettingsStore.isFeatureEnabled("feature_custom_status")) {
return false;
@ -371,8 +366,11 @@ export default createReactClass({
let ariaLabel = name;
const dmUserId = DMRoomMap.shared().getUserIdForRoomId(this.props.room.roomId);
let dmIndicator;
if (this._isDirectMessageRoom(this.props.room.roomId)) {
let dmOnline;
if (dmUserId) {
dmIndicator = <img
src={require("../../../../res/img/icon_person.svg")}
className="mx_RoomTile_dm"
@ -380,6 +378,13 @@ export default createReactClass({
height="13"
alt="dm"
/>;
const { room } = this.props;
const member = room.getMember(dmUserId);
if (member && member.membership === "join" && room.getJoinedMemberCount() === 2) {
const UserOnlineDot = sdk.getComponent('rooms.UserOnlineDot');
dmOnline = <UserOnlineDot userId={dmUserId} />;
}
}
// The following labels are written in such a fashion to increase screen reader efficiency (speed).
@ -428,6 +433,7 @@ export default createReactClass({
{ label }
{ subtextLabel }
</div>
{ dmOnline }
{ contextMenuButton }
{ badge }
</div>

View file

@ -19,6 +19,7 @@ import createReactClass from 'create-react-class';
import AccessibleButton from "../elements/AccessibleButton";
import classNames from "classnames";
import { _t } from '../../../languageHandler';
import {Key} from "../../../Keyboard";
export default createReactClass({
displayName: 'SearchBar',
@ -42,11 +43,13 @@ export default createReactClass({
},
onSearchChange: function(e) {
if (e.keyCode === 13) { // on enter...
this.onSearch();
}
if (e.keyCode === 27) { // escape...
this.props.onCancelClick();
switch (e.key) {
case Key.ENTER:
this.onSearch();
break;
case Key.ESCAPE:
this.props.onCancelClick();
break;
}
},

View file

@ -26,7 +26,6 @@ import {
unescapeMessage,
} from '../../../editor/serialize';
import {CommandPartCreator} from '../../../editor/parts';
import {MatrixClient} from 'matrix-js-sdk';
import BasicMessageComposer from "./BasicMessageComposer";
import ReplyPreview from "./ReplyPreview";
import RoomViewStore from '../../../stores/RoomViewStore';
@ -40,6 +39,7 @@ import Modal from '../../../Modal';
import {_t, _td} from '../../../languageHandler';
import ContentMessages from '../../../ContentMessages';
import {Key} from "../../../Keyboard";
import MatrixClientContext from "../../../contexts/MatrixClientContext";
function addReplyToMessageContent(content, repliedToEvent, permalinkCreator) {
const replyContent = ReplyThread.makeReplyMixIn(repliedToEvent);
@ -89,12 +89,10 @@ export default class SendMessageComposer extends React.Component {
permalinkCreator: PropTypes.object.isRequired,
};
static contextTypes = {
matrixClient: PropTypes.instanceOf(MatrixClient).isRequired,
};
static contextType = MatrixClientContext;
constructor(props, context) {
super(props, context);
constructor(props) {
super(props);
this.model = null;
this._editorRef = null;
this.currentlyComposedEditorState = null;
@ -245,7 +243,7 @@ export default class SendMessageComposer extends React.Component {
const isReply = !!RoomViewStore.getQuotingEvent();
const {roomId} = this.props.room;
const content = createMessageContent(this.model, this.props.permalinkCreator);
this.context.matrixClient.sendMessage(roomId, content);
this.context.sendMessage(roomId, content);
if (isReply) {
// Clear reply_to_event as we put the message into the queue
// if the send fails, retry will handle resending.
@ -273,7 +271,7 @@ export default class SendMessageComposer extends React.Component {
}
componentWillMount() {
const partCreator = new CommandPartCreator(this.props.room, this.context.matrixClient);
const partCreator = new CommandPartCreator(this.props.room, this.context);
const parts = this._restoreStoredEditorState(partCreator) || [];
this.model = new EditorModel(parts, partCreator);
this.dispatcherRef = dis.register(this.onAction);
@ -361,7 +359,7 @@ export default class SendMessageComposer extends React.Component {
// from Finder) but more images copied from a different website
// / word processor etc.
ContentMessages.sharedInstance().sendContentListToRoom(
Array.from(clipboardData.files), this.props.room.roomId, this.context.matrixClient,
Array.from(clipboardData.files), this.props.room.roomId, this.context,
);
}
}

View file

@ -315,8 +315,8 @@ export default class Stickerpicker extends React.Component {
// Offset the chevron location, which is relative to the left of the context menu
// (10 = offset when context menu would not be displayed off viewport)
// (8 = value required in practice (possibly 10 - 2 where the 2 = context menu borders)
const stickerPickerChevronOffset = Math.max(10, 8 + window.pageXOffset + buttonRect.left - x);
// (2 = context menu borders)
const stickerPickerChevronOffset = Math.max(10, 2 + window.pageXOffset + buttonRect.left - x);
const y = (buttonRect.top + (buttonRect.height / 2) + window.pageYOffset) - 19;

View file

@ -0,0 +1,48 @@
/*
Copyright 2019 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import React, {useContext, useEffect, useMemo, useState, useCallback} from "react";
import PropTypes from "prop-types";
import {useEventEmitter} from "../../../hooks/useEventEmitter";
import MatrixClientContext from "../../../contexts/MatrixClientContext";
const UserOnlineDot = ({userId}) => {
const cli = useContext(MatrixClientContext);
const user = useMemo(() => cli.getUser(userId), [cli, userId]);
const [isOnline, setIsOnline] = useState(false);
// Recheck if the user or client changes
useEffect(() => {
setIsOnline(user && (user.currentlyActive || user.presence === "online"));
}, [cli, user]);
// Recheck also if we receive a User.currentlyActive event
const currentlyActiveHandler = useCallback((ev) => {
const content = ev.getContent();
setIsOnline(content.currently_active || content.presence === "online");
}, []);
useEventEmitter(user, "User.currentlyActive", currentlyActiveHandler);
useEventEmitter(user, "User.presence", currentlyActiveHandler);
return isOnline ? <span className="mx_UserOnlineDot" /> : null;
};
UserOnlineDot.propTypes = {
userId: PropTypes.string.isRequired,
};
export default UserOnlineDot;