Merge remote-tracking branch 'upstream/develop' into compact-reply-rendering

This commit is contained in:
Tulir Asokan 2021-03-12 14:18:12 +02:00
commit 8c8b46b984
463 changed files with 12523 additions and 2400 deletions

View file

@ -19,6 +19,7 @@ import React from 'react';
import PropTypes from 'prop-types';
import { _t } from '../../../languageHandler';
import {formatFullDateNoTime} from '../../../DateUtils';
import {replaceableComponent} from "../../../utils/replaceableComponent";
function getdaysArray() {
return [
@ -32,6 +33,7 @@ function getdaysArray() {
];
}
@replaceableComponent("views.messages.DateSeparator")
export default class DateSeparator extends React.Component {
static propTypes = {
ts: PropTypes.number.isRequired,

View file

@ -27,12 +27,14 @@ import {MatrixClientPeg} from '../../../MatrixClientPeg';
import Modal from '../../../Modal';
import classNames from 'classnames';
import RedactedBody from "./RedactedBody";
import {replaceableComponent} from "../../../utils/replaceableComponent";
function getReplacedContent(event) {
const originalContent = event.getOriginalContent();
return originalContent["m.new_content"] || originalContent;
}
@replaceableComponent("views.messages.EditHistoryMessage")
export default class EditHistoryMessage extends React.PureComponent {
static propTypes = {
// the message event being edited
@ -74,9 +76,7 @@ export default class EditHistoryMessage extends React.PureComponent {
_onViewSourceClick = () => {
const ViewSource = sdk.getComponent('structures.ViewSource');
Modal.createTrackedDialog('View Event Source', 'Edit history', ViewSource, {
roomId: this.props.mxEvent.getRoomId(),
eventId: this.props.mxEvent.getId(),
content: this.props.mxEvent.event,
mxEvent: this.props.mxEvent,
}, 'mx_Dialog_viewsource');
};
@ -158,6 +158,7 @@ export default class EditHistoryMessage extends React.PureComponent {
const isSending = (['sending', 'queued', 'encrypting'].indexOf(this.state.sendStatus) !== -1);
const classes = classNames({
"mx_EventTile": true,
// Note: we keep the `sending` state class for tests, not for our styles
"mx_EventTile_sending": isSending,
"mx_EventTile_notSent": this.state.sendStatus === 'not_sent',
});

View file

@ -14,8 +14,6 @@
limitations under the License.
*/
'use strict';
import React from 'react';
import MFileBody from './MFileBody';
@ -23,7 +21,9 @@ import {MatrixClientPeg} from '../../../MatrixClientPeg';
import { decryptFile } from '../../../utils/DecryptFile';
import { _t } from '../../../languageHandler';
import InlineSpinner from '../elements/InlineSpinner';
import {replaceableComponent} from "../../../utils/replaceableComponent";
@replaceableComponent("views.messages.MAudioBody")
export default class MAudioBody extends React.Component {
constructor(props) {
super(props);
@ -105,7 +105,7 @@ export default class MAudioBody extends React.Component {
return (
<span className="mx_MAudioBody">
<audio src={contentUrl} controls />
<MFileBody {...this.props} decryptedBlob={this.state.decryptedBlob} />
<MFileBody {...this.props} decryptedBlob={this.state.decryptedBlob} showGenericPlaceholder={false} />
</span>
);
}

View file

@ -26,6 +26,7 @@ import Tinter from '../../../Tinter';
import request from 'browser-request';
import Modal from '../../../Modal';
import AccessibleButton from "../elements/AccessibleButton";
import {replaceableComponent} from "../../../utils/replaceableComponent";
// A cached tinted copy of require("../../../../res/img/download.svg")
@ -116,6 +117,7 @@ function computedStyle(element) {
return cssText;
}
@replaceableComponent("views.messages.MFileBody")
export default class MFileBody extends React.Component {
static propTypes = {
/* the MatrixEvent to show */
@ -126,6 +128,12 @@ export default class MFileBody extends React.Component {
onHeightChanged: PropTypes.func,
/* the shape of the tile, used */
tileShape: PropTypes.string,
/* whether or not to show the default placeholder for the file. Defaults to true. */
showGenericPlaceholder: PropTypes.bool,
};
static defaultProps = {
showGenericPlaceholder: true,
};
constructor(props) {
@ -145,9 +153,10 @@ export default class MFileBody extends React.Component {
* link text.
*
* @param {Object} content The "content" key of the matrix event.
* @param {boolean} withSize Whether to include size information. Default true.
* @return {string} the human readable link text for the attachment.
*/
presentableTextForFile(content) {
presentableTextForFile(content, withSize = true) {
let linkText = _t("Attachment");
if (content.body && content.body.length > 0) {
// The content body should be the name of the file including a
@ -155,7 +164,7 @@ export default class MFileBody extends React.Component {
linkText = content.body;
}
if (content.info && content.info.size) {
if (content.info && content.info.size && withSize) {
// If we know the size of the file then add it as human readable
// string to the end of the link text so that the user knows how
// big a file they are downloading.
@ -218,6 +227,16 @@ export default class MFileBody extends React.Component {
const fileSize = content.info ? content.info.size : null;
const fileType = content.info ? content.info.mimetype : "application/octet-stream";
let placeholder = null;
if (this.props.showGenericPlaceholder) {
placeholder = (
<div className="mx_MFileBody_info">
<span className="mx_MFileBody_info_icon" />
<span className="mx_MFileBody_info_filename">{this.presentableTextForFile(content, false)}</span>
</div>
);
}
if (isEncrypted) {
if (this.state.decryptedBlob === null) {
// Need to decrypt the attachment
@ -248,6 +267,7 @@ export default class MFileBody extends React.Component {
// but it is not guaranteed between various browsers' settings.
return (
<span className="mx_MFileBody">
{placeholder}
<div className="mx_MFileBody_download">
<AccessibleButton onClick={decrypt}>
{ _t("Decrypt %(text)s", { text: text }) }
@ -278,6 +298,7 @@ export default class MFileBody extends React.Component {
// If the attachment is encrypted then put the link inside an iframe.
return (
<span className="mx_MFileBody">
{placeholder}
<div className="mx_MFileBody_download">
<div style={{display: "none"}}>
{ /*
@ -346,6 +367,7 @@ export default class MFileBody extends React.Component {
if (this.props.tileShape === "file_grid") {
return (
<span className="mx_MFileBody">
{placeholder}
<div className="mx_MFileBody_download">
<a className="mx_MFileBody_downloadLink" {...downloadProps}>
{ fileName }
@ -359,6 +381,7 @@ export default class MFileBody extends React.Component {
} else {
return (
<span className="mx_MFileBody">
{placeholder}
<div className="mx_MFileBody_download">
<a {...downloadProps}>
<img src={tintedDownloadImageURL} width="12" height="14" ref={this._downloadImage} />
@ -371,6 +394,7 @@ export default class MFileBody extends React.Component {
} else {
const extra = text ? (': ' + text) : '';
return <span className="mx_MFileBody">
{placeholder}
{ _t("Invalid file%(extra)s", { extra: extra }) }
</span>;
}

View file

@ -27,7 +27,9 @@ import { _t } from '../../../languageHandler';
import SettingsStore from "../../../settings/SettingsStore";
import MatrixClientContext from "../../../contexts/MatrixClientContext";
import InlineSpinner from '../elements/InlineSpinner';
import {replaceableComponent} from "../../../utils/replaceableComponent";
@replaceableComponent("views.messages.MImageBody")
export default class MImageBody extends React.Component {
static propTypes = {
/* the MatrixEvent to show */
@ -362,7 +364,7 @@ export default class MImageBody extends React.Component {
}
// The maximum height of the thumbnail as it is rendered as an <img>
const maxHeight = Math.min(this.props.maxImageHeight || 240, infoHeight);
const maxHeight = Math.min(this.props.maxImageHeight || 600, infoHeight);
// The maximum width of the thumbnail, as dictated by its natural
// maximum height.
const maxWidth = infoWidth * maxHeight / infoHeight;
@ -452,7 +454,7 @@ export default class MImageBody extends React.Component {
// Overidden by MStickerBody
getFileBody() {
return <MFileBody {...this.props} decryptedBlob={this.state.decryptedBlob} />;
return <MFileBody {...this.props} decryptedBlob={this.state.decryptedBlob} showGenericPlaceholder={false} />;
}
render() {

View file

@ -21,11 +21,13 @@ import WidgetStore from "../../../stores/WidgetStore";
import EventTileBubble from "./EventTileBubble";
import { MatrixClientPeg } from "../../../MatrixClientPeg";
import { Container, WidgetLayoutStore } from "../../../stores/widgets/WidgetLayoutStore";
import {replaceableComponent} from "../../../utils/replaceableComponent";
interface IProps {
mxEvent: MatrixEvent;
}
@replaceableComponent("views.messages.MJitsiWidgetEvent")
export default class MJitsiWidgetEvent extends React.PureComponent<IProps> {
constructor(props) {
super(props);

View file

@ -22,7 +22,9 @@ import { _t } from '../../../languageHandler';
import {getNameForEventRoom, userLabelForEventRoom}
from '../../../utils/KeyVerificationStateObserver';
import EventTileBubble from "./EventTileBubble";
import {replaceableComponent} from "../../../utils/replaceableComponent";
@replaceableComponent("views.messages.MKeyVerificationConclusion")
export default class MKeyVerificationConclusion extends React.Component {
constructor(props) {
super(props);

View file

@ -25,7 +25,9 @@ import dis from "../../../dispatcher/dispatcher";
import {RightPanelPhases} from "../../../stores/RightPanelStorePhases";
import {Action} from "../../../dispatcher/actions";
import EventTileBubble from "./EventTileBubble";
import {replaceableComponent} from "../../../utils/replaceableComponent";
@replaceableComponent("views.messages.MKeyVerificationRequest")
export default class MKeyVerificationRequest extends React.Component {
constructor(props) {
super(props);

View file

@ -17,7 +17,9 @@ limitations under the License.
import React from 'react';
import MImageBody from './MImageBody';
import * as sdk from '../../../index';
import {replaceableComponent} from "../../../utils/replaceableComponent";
@replaceableComponent("views.messages.MStickerBody")
export default class MStickerBody extends MImageBody {
// Mostly empty to prevent default behaviour of MImageBody
onClick(ev) {

View file

@ -22,6 +22,7 @@ import { decryptFile } from '../../../utils/DecryptFile';
import { _t } from '../../../languageHandler';
import SettingsStore from "../../../settings/SettingsStore";
import InlineSpinner from '../elements/InlineSpinner';
import {replaceableComponent} from "../../../utils/replaceableComponent";
interface IProps {
/* the MatrixEvent to show */
@ -38,6 +39,7 @@ interface IState {
fetchingData: boolean,
}
@replaceableComponent("views.messages.MVideoBody")
export default class MVideoBody extends React.PureComponent<IProps, IState> {
private videoRef = React.createRef<HTMLVideoElement>();
@ -243,7 +245,7 @@ export default class MVideoBody extends React.PureComponent<IProps, IState> {
onPlay={this.videoOnPlay}
>
</video>
<MFileBody {...this.props} decryptedBlob={this.state.decryptedBlob} />
<MFileBody {...this.props} decryptedBlob={this.state.decryptedBlob} showGenericPlaceholder={false} />
</span>
);
}

View file

@ -28,6 +28,7 @@ import { isContentActionable, canEditContent } from '../../../utils/EventUtils';
import RoomContext from "../../../contexts/RoomContext";
import Toolbar from "../../../accessibility/Toolbar";
import {RovingAccessibleTooltipButton, useRovingTabIndex} from "../../../accessibility/RovingTabIndex";
import {replaceableComponent} from "../../../utils/replaceableComponent";
const OptionsButton = ({mxEvent, getTile, getReplyThread, permalinkCreator, onFocusChange}) => {
const [menuDisplayed, button, openMenu, closeMenu] = useContextMenu();
@ -101,6 +102,7 @@ const ReactButton = ({mxEvent, reactions, onFocusChange}) => {
</React.Fragment>;
};
@replaceableComponent("views.messages.MessageActionBar")
export default class MessageActionBar extends React.PureComponent {
static propTypes = {
mxEvent: PropTypes.object.isRequired,

View file

@ -21,7 +21,9 @@ import SettingsStore from "../../../settings/SettingsStore";
import {Mjolnir} from "../../../mjolnir/Mjolnir";
import RedactedBody from "./RedactedBody";
import UnknownBody from "./UnknownBody";
import {replaceableComponent} from "../../../utils/replaceableComponent";
@replaceableComponent("views.messages.MessageEvent")
export default class MessageEvent extends React.Component {
static propTypes = {
/* the MatrixEvent to show */

View file

@ -18,7 +18,9 @@ limitations under the License.
import React from 'react';
import PropTypes from 'prop-types';
import {formatFullDate, formatTime} from '../../../DateUtils';
import {replaceableComponent} from "../../../utils/replaceableComponent";
@replaceableComponent("views.messages.MessageTimestamp")
export default class MessageTimestamp extends React.Component {
static propTypes = {
ts: PropTypes.number.isRequired,

View file

@ -17,7 +17,9 @@ limitations under the License.
import React from 'react';
import PropTypes from 'prop-types';
import {_t} from '../../../languageHandler';
import {replaceableComponent} from "../../../utils/replaceableComponent";
@replaceableComponent("views.messages.MjolnirBody")
export default class MjolnirBody extends React.Component {
static propTypes = {
mxEvent: PropTypes.object.isRequired,

View file

@ -21,10 +21,12 @@ import * as sdk from '../../../index';
import { _t } from '../../../languageHandler';
import { isContentActionable } from '../../../utils/EventUtils';
import {MatrixClientPeg} from '../../../MatrixClientPeg';
import {replaceableComponent} from "../../../utils/replaceableComponent";
// The maximum number of reactions to initially show on a message.
const MAX_ITEMS_WHEN_LIMITED = 8;
@replaceableComponent("views.messages.ReactionsRow")
export default class ReactionsRow extends React.PureComponent {
static propTypes = {
// The event we're displaying reactions for

View file

@ -23,7 +23,9 @@ import * as sdk from '../../../index';
import { _t } from '../../../languageHandler';
import { formatCommaSeparatedList } from '../../../utils/FormattingUtils';
import dis from "../../../dispatcher/dispatcher";
import {replaceableComponent} from "../../../utils/replaceableComponent";
@replaceableComponent("views.messages.ReactionsRowButton")
export default class ReactionsRowButton extends React.PureComponent {
static propTypes = {
// The event we're displaying reactions for

View file

@ -22,7 +22,9 @@ import * as sdk from '../../../index';
import { unicodeToShortcode } from '../../../HtmlUtils';
import { _t } from '../../../languageHandler';
import { formatCommaSeparatedList } from '../../../utils/FormattingUtils';
import {replaceableComponent} from "../../../utils/replaceableComponent";
@replaceableComponent("views.messages.ReactionsRowButtonTooltip")
export default class ReactionsRowButtonTooltip extends React.PureComponent {
static propTypes = {
// The event we're displaying reactions for

View file

@ -23,7 +23,9 @@ import { _t } from '../../../languageHandler';
import * as sdk from '../../../index';
import Modal from '../../../Modal';
import AccessibleButton from '../elements/AccessibleButton';
import {replaceableComponent} from "../../../utils/replaceableComponent";
@replaceableComponent("views.messages.RoomAvatarEvent")
export default class RoomAvatarEvent extends React.Component {
static propTypes = {
/* the MatrixEvent to show */

View file

@ -23,7 +23,9 @@ import { RoomPermalinkCreator } from '../../../utils/permalinks/Permalinks';
import { _t } from '../../../languageHandler';
import {MatrixClientPeg} from '../../../MatrixClientPeg';
import EventTileBubble from "./EventTileBubble";
import {replaceableComponent} from "../../../utils/replaceableComponent";
@replaceableComponent("views.messages.RoomCreate")
export default class RoomCreate extends React.Component {
static propTypes = {
/* the MatrixEvent to show */

View file

@ -18,14 +18,14 @@ import React from 'react';
import PropTypes from 'prop-types';
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";
import {replaceableComponent} from "../../../utils/replaceableComponent";
@replaceableComponent("views.messages.SenderProfile")
export default class SenderProfile extends React.Component {
static propTypes = {
mxEvent: PropTypes.object.isRequired, // event whose sender we're showing
text: PropTypes.string, // Text to show. Defaults to sender name
onClick: PropTypes.func,
};
@ -118,17 +118,10 @@ export default class SenderProfile extends React.Component {
{ flair }
</span>;
const content = this.props.text ?
<span>
<span className="mx_SenderProfile_aux">
{ _t(this.props.text, { senderName: () => nameElem }) }
</span>
</span> : nameFlair;
return (
<div className="mx_SenderProfile" dir="auto" onClick={this.props.onClick}>
<div className="mx_SenderProfile_hover">
{ content }
{ nameFlair }
</div>
</div>
);

View file

@ -35,7 +35,9 @@ import {isPermalinkHost} from "../../../utils/permalinks/Permalinks";
import {toRightOf} from "../../structures/ContextMenu";
import {copyPlaintext} from "../../../utils/strings";
import AccessibleTooltipButton from "../elements/AccessibleTooltipButton";
import {replaceableComponent} from "../../../utils/replaceableComponent";
@replaceableComponent("views.messages.TextualBody")
export default class TextualBody extends React.Component {
static propTypes = {
/* the MatrixEvent to show */
@ -99,6 +101,10 @@ export default class TextualBody extends React.Component {
// If there already is a div wrapping the codeblock we want to skip this.
// This happens after the codeblock was edited.
if (pres[i].parentNode.className == "mx_EventTile_pre_container") continue;
// Add code element if it's missing since we depend on it
if (pres[i].getElementsByTagName("code").length == 0) {
this._addCodeElement(pres[i]);
}
// Wrap a div around <pre> so that the copy button can be correctly positioned
// when the <pre> overflows and is scrolled horizontally.
const div = this._wrapInDiv(pres[i]);
@ -128,6 +134,12 @@ export default class TextualBody extends React.Component {
}
}
_addCodeElement(pre) {
const code = document.createElement("code");
code.append(...pre.childNodes);
pre.appendChild(code);
}
_addCodeExpansionButton(div, pre) {
// Calculate how many percent does the pre element take up.
// If it's less than 30% we don't add the expansion button.

View file

@ -18,7 +18,9 @@ limitations under the License.
import React from 'react';
import PropTypes from 'prop-types';
import * as TextForEvent from "../../../TextForEvent";
import {replaceableComponent} from "../../../utils/replaceableComponent";
@replaceableComponent("views.messages.TextualEvent")
export default class TextualEvent extends React.Component {
static propTypes = {
/* the MatrixEvent to show */

View file

@ -20,7 +20,9 @@ import { _t } from '../../../languageHandler';
import * as sdk from '../../../index';
import Modal from '../../../Modal';
import SdkConfig from "../../../SdkConfig";
import {replaceableComponent} from "../../../utils/replaceableComponent";
@replaceableComponent("views.messages.TileErrorBoundary")
export default class TileErrorBoundary extends React.Component {
constructor(props) {
super(props);

View file

@ -17,7 +17,9 @@ limitations under the License.
import React from 'react';
import PropTypes from 'prop-types';
import classNames from 'classnames';
import {replaceableComponent} from "../../../utils/replaceableComponent";
@replaceableComponent("views.messages.ViewSourceEvent")
export default class ViewSourceEvent extends React.PureComponent {
static propTypes = {
/* the MatrixEvent to show */