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

 Conflicts:
	src/components/views/messages/TextualBody.js
This commit is contained in:
Michael Telatynski
2020-01-06 11:51:35 +00:00
240 changed files with 7529 additions and 5412 deletions
@@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import React from 'react';
import React, {createRef} from 'react';
import PropTypes from 'prop-types';
import * as HtmlUtils from '../../../HtmlUtils';
import { editBodyDiffToHtml } from '../../../utils/MessageDiffUtils';
@@ -51,6 +51,8 @@ export default class EditHistoryMessage extends React.PureComponent {
}
const canRedact = room.currentState.maySendRedactionForEvent(event, userId);
this.state = {canRedact, sendStatus: event.getAssociatedStatus()};
this._content = createRef();
}
_onAssociatedStatusChanged = () => {
@@ -78,8 +80,8 @@ export default class EditHistoryMessage extends React.PureComponent {
pillifyLinks() {
// not present for redacted events
if (this.refs.content) {
pillifyLinks(this.refs.content.children, this.props.mxEvent);
if (this._content.current) {
pillifyLinks(this._content.current.children, this.props.mxEvent);
}
}
@@ -140,13 +142,13 @@ export default class EditHistoryMessage extends React.PureComponent {
if (mxEvent.getContent().msgtype === "m.emote") {
const name = mxEvent.sender ? mxEvent.sender.name : mxEvent.getSender();
contentContainer = (
<div className="mx_EventTile_content" ref="content">*&nbsp;
<div className="mx_EventTile_content" ref={this._content}>*&nbsp;
<span className="mx_MEmoteBody_sender">{ name }</span>
&nbsp;{contentElements}
</div>
);
} else {
contentContainer = <div className="mx_EventTile_content" ref="content">{contentElements}</div>;
contentContainer = <div className="mx_EventTile_content" ref={this._content}>{contentElements}</div>;
}
}
+1 -1
View File
@@ -80,7 +80,7 @@ export default class MAudioBody extends React.Component {
if (this.state.error !== null) {
return (
<span className="mx_MAudioBody" ref="body">
<span className="mx_MAudioBody">
<img src={require("../../../../res/img/warning.svg")} width="16" height="16" />
{ _t("Error decrypting audio") }
</span>
+21 -17
View File
@@ -15,7 +15,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import React from 'react';
import React, {createRef} from 'react';
import PropTypes from 'prop-types';
import createReactClass from 'create-react-class';
import filesize from 'filesize';
@@ -26,6 +26,7 @@ import {decryptFile} from '../../../utils/DecryptFile';
import Tinter from '../../../Tinter';
import request from 'browser-request';
import Modal from '../../../Modal';
import SdkConfig from "../../../SdkConfig";
// A cached tinted copy of require("../../../../res/img/download.svg")
@@ -214,10 +215,6 @@ module.exports = createReactClass({
tileShape: PropTypes.string,
},
contextTypes: {
appConfig: PropTypes.object,
},
/**
* Extracts a human readable label for the file attachment to use as
* link text.
@@ -251,6 +248,12 @@ module.exports = createReactClass({
return MatrixClientPeg.get().mxcUrlToHttp(content.url);
},
UNSAFE_componentWillMount: function() {
this._iframe = createRef();
this._dummyLink = createRef();
this._downloadImage = createRef();
},
componentDidMount: function() {
// Add this to the list of mounted components to receive notifications
// when the tint changes.
@@ -272,17 +275,17 @@ module.exports = createReactClass({
tint: function() {
// Update our tinted copy of require("../../../../res/img/download.svg")
if (this.refs.downloadImage) {
this.refs.downloadImage.src = tintedDownloadImageURL;
if (this._downloadImage.current) {
this._downloadImage.current.src = tintedDownloadImageURL;
}
if (this.refs.iframe) {
if (this._iframe.current) {
// If the attachment is encrypted then the download image
// will be inside the iframe so we wont be able to update
// it directly.
this.refs.iframe.contentWindow.postMessage({
this._iframe.current.contentWindow.postMessage({
code: remoteSetTint.toString(),
imgSrc: tintedDownloadImageURL,
style: computedStyle(this.refs.dummyLink),
style: computedStyle(this._dummyLink.current),
}, "*");
}
},
@@ -325,7 +328,7 @@ module.exports = createReactClass({
};
return (
<span className="mx_MFileBody" ref="body">
<span className="mx_MFileBody">
<div className="mx_MFileBody_download">
<a href="javascript:void(0)" onClick={decrypt}>
{ _t("Decrypt %(text)s", { text: text }) }
@@ -340,7 +343,7 @@ module.exports = createReactClass({
ev.target.contentWindow.postMessage({
code: remoteRender.toString(),
imgSrc: tintedDownloadImageURL,
style: computedStyle(this.refs.dummyLink),
style: computedStyle(this._dummyLink.current),
blob: this.state.decryptedBlob,
// Set a download attribute for encrypted files so that the file
// will have the correct name when the user tries to download it.
@@ -354,8 +357,9 @@ module.exports = createReactClass({
// If the attachment is encryped then put the link inside an iframe.
let renderer_url = DEFAULT_CROSS_ORIGIN_RENDERER;
if (this.context.appConfig && this.context.appConfig.cross_origin_renderer_url) {
renderer_url = this.context.appConfig.cross_origin_renderer_url;
const appConfig = SdkConfig.get();
if (appConfig && appConfig.cross_origin_renderer_url) {
renderer_url = appConfig.cross_origin_renderer_url;
}
renderer_url += "?origin=" + encodeURIComponent(window.location.origin);
return (
@@ -367,9 +371,9 @@ module.exports = createReactClass({
* We'll use it to learn how the download link
* would have been styled if it was rendered inline.
*/ }
<a ref="dummyLink" />
<a ref={this._dummyLink} />
</div>
<iframe src={renderer_url} onLoad={onIframeLoad} ref="iframe" />
<iframe src={renderer_url} onLoad={onIframeLoad} ref={this._iframe} />
</div>
</span>
);
@@ -439,7 +443,7 @@ module.exports = createReactClass({
<span className="mx_MFileBody">
<div className="mx_MFileBody_download">
<a {...downloadProps}>
<img src={tintedDownloadImageURL} width="12" height="14" ref="downloadImage" />
<img src={tintedDownloadImageURL} width="12" height="14" ref={this._downloadImage} />
{ _t("Download %(text)s", { text: text }) }
</a>
</div>
+18 -18
View File
@@ -16,9 +16,8 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import React from 'react';
import React, {createRef} from 'react';
import PropTypes from 'prop-types';
import { MatrixClient } from 'matrix-js-sdk';
import MFileBody from './MFileBody';
import Modal from '../../../Modal';
@@ -26,6 +25,7 @@ import sdk from '../../../index';
import { decryptFile } from '../../../utils/DecryptFile';
import { _t } from '../../../languageHandler';
import SettingsStore from "../../../settings/SettingsStore";
import MatrixClientContext from "../../../contexts/MatrixClientContext";
export default class MImageBody extends React.Component {
static propTypes = {
@@ -39,9 +39,7 @@ export default class MImageBody extends React.Component {
maxImageHeight: PropTypes.number,
};
static contextTypes = {
matrixClient: PropTypes.instanceOf(MatrixClient),
};
static contextType = MatrixClientContext;
constructor(props) {
super(props);
@@ -65,11 +63,13 @@ export default class MImageBody extends React.Component {
hover: false,
showImage: SettingsStore.getValue("showImages"),
};
this._image = createRef();
}
componentWillMount() {
this.unmounted = false;
this.context.matrixClient.on('sync', this.onClientSync);
this.context.on('sync', this.onClientSync);
}
// FIXME: factor this out and aplpy it to MVideoBody and MAudioBody too!
@@ -158,8 +158,8 @@ export default class MImageBody extends React.Component {
let loadedImageDimensions;
if (this.refs.image) {
const { naturalWidth, naturalHeight } = this.refs.image;
if (this._image.current) {
const { naturalWidth, naturalHeight } = this._image.current;
// this is only used as a fallback in case content.info.w/h is missing
loadedImageDimensions = { naturalWidth, naturalHeight };
}
@@ -172,7 +172,7 @@ export default class MImageBody extends React.Component {
if (content.file !== undefined) {
return this.state.decryptedUrl;
} else {
return this.context.matrixClient.mxcUrlToHttp(content.url);
return this.context.mxcUrlToHttp(content.url);
}
}
@@ -196,7 +196,7 @@ export default class MImageBody extends React.Component {
// special case to return clientside sender-generated thumbnails for SVGs, if any,
// given we deliberately don't thumbnail them serverside to prevent
// billion lol attacks and similar
return this.context.matrixClient.mxcUrlToHttp(
return this.context.mxcUrlToHttp(
content.info.thumbnail_url,
thumbWidth,
thumbHeight,
@@ -219,7 +219,7 @@ export default class MImageBody extends React.Component {
pixelRatio === 1.0 ||
(!info || !info.w || !info.h || !info.size)
) {
return this.context.matrixClient.mxcUrlToHttp(content.url, thumbWidth, thumbHeight);
return this.context.mxcUrlToHttp(content.url, thumbWidth, thumbHeight);
} else {
// we should only request thumbnails if the image is bigger than 800x600
// (or 1600x1200 on retina) otherwise the image in the timeline will just
@@ -240,7 +240,7 @@ export default class MImageBody extends React.Component {
// image is too large physically and bytewise to clutter our timeline so
// we ask for a thumbnail, despite knowing that it will be max 800x600
// despite us being retina (as synapse doesn't do 1600x1200 thumbs yet).
return this.context.matrixClient.mxcUrlToHttp(
return this.context.mxcUrlToHttp(
content.url,
thumbWidth,
thumbHeight,
@@ -249,7 +249,7 @@ export default class MImageBody extends React.Component {
// download the original image otherwise, so we can scale it client side
// to take pixelRatio into account.
// ( no width/height means we want the original image)
return this.context.matrixClient.mxcUrlToHttp(
return this.context.mxcUrlToHttp(
content.url,
);
}
@@ -306,7 +306,7 @@ export default class MImageBody extends React.Component {
componentWillUnmount() {
this.unmounted = true;
this.context.matrixClient.removeListener('sync', this.onClientSync);
this.context.removeListener('sync', this.onClientSync);
this._afterComponentWillUnmount();
if (this.state.decryptedUrl) {
@@ -342,7 +342,7 @@ export default class MImageBody extends React.Component {
imageElement = <HiddenImagePlaceholder />;
} else {
imageElement = (
<img style={{display: 'none'}} src={thumbUrl} ref="image"
<img style={{display: 'none'}} src={thumbUrl} ref={this._image}
alt={content.body}
onError={this.onImageError}
onLoad={this.onImageLoad}
@@ -385,7 +385,7 @@ export default class MImageBody 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"
<img className="mx_MImageBody_thumbnail" src={thumbUrl} ref={this._image}
style={{ maxWidth: maxWidth + "px" }}
alt={content.body}
onError={this.onImageError}
@@ -459,7 +459,7 @@ export default class MImageBody extends React.Component {
if (this.state.error !== null) {
return (
<span className="mx_MImageBody" ref="body">
<span className="mx_MImageBody">
<img src={require("../../../../res/img/warning.svg")} width="16" height="16" />
{ _t("Error decrypting image") }
</span>
@@ -477,7 +477,7 @@ export default class MImageBody extends React.Component {
const thumbnail = this._messageContent(contentUrl, thumbUrl, content);
const fileBody = this.getFileBody();
return <span className="mx_MImageBody" ref="body">
return <span className="mx_MImageBody">
{ thumbnail }
{ fileBody }
</span>;
@@ -52,7 +52,7 @@ export default class MKeyVerificationRequest extends React.Component {
const verifier = MatrixClientPeg.get().acceptVerificationDM(this.props.mxEvent, verificationMethods.SAS);
Modal.createTrackedDialog('Incoming Verification', '', IncomingSasDialog, {
verifier,
});
}, null, /* priority = */ false, /* static = */ true);
};
_onRejectClicked = () => {
+3 -3
View File
@@ -132,7 +132,7 @@ module.exports = createReactClass({
if (this.state.error !== null) {
return (
<span className="mx_MVideoBody" ref="body">
<span className="mx_MVideoBody">
<img src={require("../../../../res/img/warning.svg")} width="16" height="16" />
{ _t("Error decrypting video") }
</span>
@@ -144,8 +144,8 @@ module.exports = createReactClass({
// The attachment is decrypted in componentDidMount.
// For now add an img tag with a spinner.
return (
<span className="mx_MVideoBody" ref="body">
<div className="mx_MImageBody_thumbnail mx_MImageBody_thumbnail_spinner" ref="image">
<span className="mx_MVideoBody">
<div className="mx_MImageBody_thumbnail mx_MImageBody_thumbnail_spinner">
<img src={require("../../../../res/img/spinner.gif")} alt={content.body} width="16" height="16" />
</div>
</span>
@@ -25,7 +25,7 @@ import dis from '../../../dispatcher';
import Modal from '../../../Modal';
import {aboveLeftOf, ContextMenu, ContextMenuButton, useContextMenu} from '../../structures/ContextMenu';
import { isContentActionable, canEditContent } from '../../../utils/EventUtils';
import {RoomContext} from "../../structures/RoomView";
import RoomContext from "../../../contexts/RoomContext";
const OptionsButton = ({mxEvent, getTile, getReplyThread, permalinkCreator, onFocusChange}) => {
const [menuDisplayed, button, openMenu, closeMenu] = useContextMenu();
@@ -78,14 +78,17 @@ const OptionsButton = ({mxEvent, getTile, getReplyThread, permalinkCreator, onFo
</React.Fragment>;
};
const ReactButton = ({mxEvent, reactions}) => {
const ReactButton = ({mxEvent, reactions, onFocusChange}) => {
const [menuDisplayed, button, openMenu, closeMenu] = useContextMenu();
useEffect(() => {
onFocusChange(menuDisplayed);
}, [onFocusChange, menuDisplayed]);
let contextMenu;
if (menuDisplayed) {
const buttonRect = button.current.getBoundingClientRect();
const ReactionPicker = sdk.getComponent('emojipicker.ReactionPicker');
contextMenu = <ContextMenu {...aboveLeftOf(buttonRect)} onFinished={closeMenu}>
contextMenu = <ContextMenu {...aboveLeftOf(buttonRect)} onFinished={closeMenu} managed={false}>
<ReactionPicker mxEvent={mxEvent} reactions={reactions} onFinished={closeMenu} />
</ContextMenu>;
}
@@ -114,9 +117,7 @@ export default class MessageActionBar extends React.PureComponent {
onFocusChange: PropTypes.func,
};
static contextTypes = {
room: RoomContext,
};
static contextType = RoomContext;
componentDidMount() {
this.props.mxEvent.on("Event.decrypted", this.onDecrypted);
@@ -161,10 +162,12 @@ export default class MessageActionBar extends React.PureComponent {
let editButton;
if (isContentActionable(this.props.mxEvent)) {
if (this.context.room.canReact) {
reactButton = <ReactButton mxEvent={this.props.mxEvent} reactions={this.props.reactions} />;
if (this.context.canReact) {
reactButton = (
<ReactButton mxEvent={this.props.mxEvent} reactions={this.props.reactions} onFocusChange={this.onFocusChange} />
);
}
if (this.context.room.canReply) {
if (this.context.canReply) {
replyButton = <AccessibleButton
className="mx_MessageActionBar_maskButton mx_MessageActionBar_replyButton"
title={_t("Reply")}
@@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import React from 'react';
import React, {createRef} from 'react';
import PropTypes from 'prop-types';
import createReactClass from 'create-react-class';
import sdk from '../../../index';
@@ -47,8 +47,12 @@ module.exports = createReactClass({
maxImageHeight: PropTypes.number,
},
UNSAFE_componentWillMount: function() {
this._body = createRef();
},
getEventTileOps: function() {
return this.refs.body && this.refs.body.getEventTileOps ? this.refs.body.getEventTileOps() : null;
return this._body.current && this._body.current.getEventTileOps ? this._body.current.getEventTileOps() : null;
},
onTileUpdate: function() {
@@ -103,7 +107,8 @@ module.exports = createReactClass({
}
return <BodyType
ref="body" mxEvent={this.props.mxEvent}
ref={this._body}
mxEvent={this.props.mxEvent}
highlights={this.props.highlights}
highlightLink={this.props.highlightLink}
showUrlPreview={this.props.showUrlPreview}
@@ -126,10 +126,9 @@ export default class ReactionsRowButton extends React.PureComponent {
);
}
return <span className={classes}
role="button"
const AccessibleButton = sdk.getComponent('elements.AccessibleButton');
return <AccessibleButton className={classes}
aria-label={label}
tabindex="0"
onClick={this.onClick}
onMouseOver={this.onMouseOver}
onMouseOut={this.onMouseOut}
@@ -141,6 +140,6 @@ export default class ReactionsRowButton extends React.PureComponent {
{count}
</span>
{tooltip}
</span>;
</AccessibleButton>;
}
}
@@ -17,11 +17,11 @@
import React from 'react';
import PropTypes from 'prop-types';
import createReactClass from 'create-react-class';
import {MatrixClient} from 'matrix-js-sdk';
import Flair from '../elements/Flair.js';
import FlairStore from '../../../stores/FlairStore';
import { _t } from '../../../languageHandler';
import {getUserNameColorClass} from '../../../utils/FormattingUtils';
import MatrixClientContext from "../../../contexts/MatrixClientContext";
export default createReactClass({
displayName: 'SenderProfile',
@@ -31,8 +31,8 @@ export default createReactClass({
onClick: PropTypes.func,
},
contextTypes: {
matrixClient: PropTypes.instanceOf(MatrixClient),
statics: {
contextType: MatrixClientContext,
},
getInitialState() {
@@ -47,18 +47,18 @@ export default createReactClass({
this._updateRelatedGroups();
FlairStore.getPublicisedGroupsCached(
this.context.matrixClient, this.props.mxEvent.getSender(),
this.context, this.props.mxEvent.getSender(),
).then((userGroups) => {
if (this.unmounted) return;
this.setState({userGroups});
});
this.context.matrixClient.on('RoomState.events', this.onRoomStateEvents);
this.context.on('RoomState.events', this.onRoomStateEvents);
},
componentWillUnmount() {
this.unmounted = true;
this.context.matrixClient.removeListener('RoomState.events', this.onRoomStateEvents);
this.context.removeListener('RoomState.events', this.onRoomStateEvents);
},
onRoomStateEvents(event) {
@@ -71,7 +71,7 @@ export default createReactClass({
_updateRelatedGroups() {
if (this.unmounted) return;
const room = this.context.matrixClient.getRoom(this.props.mxEvent.getRoomId());
const room = this.context.getRoom(this.props.mxEvent.getRoomId());
if (!room) return;
const relatedGroupsEvent = room.currentState.getStateEvents('m.room.related_groups', '');
+10 -5
View File
@@ -86,7 +86,7 @@ module.exports = createReactClass({
return successful;
},
componentWillMount: function() {
UNSAFE_componentWillMount: function() {
this._content = createRef();
},
@@ -281,7 +281,7 @@ module.exports = createReactClass({
const buttonRect = e.target.getBoundingClientRect();
const GenericTextContextMenu = sdk.getComponent('context_menus.GenericTextContextMenu');
const {close} = ContextMenu.createMenu(GenericTextContextMenu, {
...toRightOf(buttonRect, 11),
...toRightOf(buttonRect, 2),
message: successful ? _t('Copied!') : _t('Failed to copy'),
});
e.target.onmouseleave = close;
@@ -402,13 +402,18 @@ module.exports = createReactClass({
label={_t("Edited at %(date)s. Click to view edits.", {date: dateString})}
/>;
}
const AccessibleButton = sdk.getComponent('elements.AccessibleButton');
return (
<div
key="editedMarker" className="mx_EventTile_edited"
<AccessibleButton
key="editedMarker"
className="mx_EventTile_edited"
onClick={this._openHistoryDialog}
onMouseEnter={this._onMouseEnterEditedMarker}
onMouseLeave={this._onMouseLeaveEditedMarker}
>{editedTooltip}<span>{`(${_t("edited")})`}</span></div>
>
{ editedTooltip }<span>{`(${_t("edited")})`}</span>
</AccessibleButton>
);
},