Remove redundant code and move presentableTextForFile out of MFileBody

Signed-off-by: Tulir Asokan <tulir@maunium.net>
pull/21833/head
Tulir Asokan 2021-05-01 16:11:30 +03:00
parent 09f9916916
commit 330f222dd1
5 changed files with 55 additions and 140 deletions

View File

@ -89,6 +89,35 @@ function computedStyle(element) {
return cssText; return cssText;
} }
/**
* Extracts a human readable label for the file attachment to use as
* 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.
*/
export function 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
// file extension.
linkText = content.body;
}
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.
// The content.info also contains a MIME-type but we don't display
// it since it is "ugly", users generally aren't aware what it
// means and the type of the attachment can usually be inferrered
// from the file extension.
linkText += ' (' + filesize(content.info.size) + ')';
}
return linkText;
}
@replaceableComponent("views.messages.MFileBody") @replaceableComponent("views.messages.MFileBody")
export default class MFileBody extends React.Component { export default class MFileBody extends React.Component {
static propTypes = { static propTypes = {
@ -119,35 +148,6 @@ export default class MFileBody extends React.Component {
this._dummyLink = createRef(); this._dummyLink = createRef();
} }
/**
* Extracts a human readable label for the file attachment to use as
* 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, 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
// file extension.
linkText = content.body;
}
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.
// The content.info also contains a MIME-type but we don't display
// it since it is "ugly", users generally aren't aware what it
// means and the type of the attachment can usually be inferrered
// from the file extension.
linkText += ' (' + filesize(content.info.size) + ')';
}
return linkText;
}
_getContentUrl() { _getContentUrl() {
const media = mediaFromContent(this.props.mxEvent.getContent()); const media = mediaFromContent(this.props.mxEvent.getContent());
return media.srcHttp; return media.srcHttp;
@ -161,7 +161,7 @@ export default class MFileBody extends React.Component {
render() { render() {
const content = this.props.mxEvent.getContent(); const content = this.props.mxEvent.getContent();
const text = this.presentableTextForFile(content); const text = presentableTextForFile(content);
const isEncrypted = content.file !== undefined; const isEncrypted = content.file !== undefined;
const fileName = content.body && content.body.length > 0 ? content.body : _t("Attachment"); const fileName = content.body && content.body.length > 0 ? content.body : _t("Attachment");
const contentUrl = this._getContentUrl(); const contentUrl = this._getContentUrl();
@ -173,7 +173,7 @@ export default class MFileBody extends React.Component {
placeholder = ( placeholder = (
<div className="mx_MFileBody_info"> <div className="mx_MFileBody_info">
<span className="mx_MFileBody_info_icon" /> <span className="mx_MFileBody_info_icon" />
<span className="mx_MFileBody_info_filename">{this.presentableTextForFile(content, false)}</span> <span className="mx_MFileBody_info_filename">{presentableTextForFile(content, false)}</span>
</div> </div>
); );
} }

View File

@ -1,5 +1,5 @@
/* /*
Copyright 2020 Tulir Asokan <tulir@maunium.net> Copyright 2020-2021 Tulir Asokan <tulir@maunium.net>
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.
@ -18,7 +18,7 @@ import React from "react";
import {_td} from "../../../languageHandler"; import {_td} from "../../../languageHandler";
import * as sdk from "../../../index"; import * as sdk from "../../../index";
import MImageBody from './MImageBody'; import MImageBody from './MImageBody';
import MFileBody from "./MFileBody"; import {presentableTextForFile} from "./MFileBody";
export default class MImageReplyBody extends MImageBody { export default class MImageReplyBody extends MImageBody {
onClick(ev) { onClick(ev) {
@ -31,7 +31,7 @@ export default class MImageReplyBody extends MImageBody {
// Don't show "Download this_file.png ..." // Don't show "Download this_file.png ..."
getFileBody() { getFileBody() {
return MFileBody.prototype.presentableTextForFile.call(this, this.props.mxEvent.getContent()); return presentableTextForFile(this.props.mxEvent.getContent());
} }
render() { render() {
@ -45,10 +45,12 @@ export default class MImageReplyBody extends MImageBody {
const thumbnail = this._messageContent(contentUrl, this._getThumbUrl(), content); const thumbnail = this._messageContent(contentUrl, this._getThumbUrl(), content);
const fileBody = this.getFileBody(); const fileBody = this.getFileBody();
const SenderProfile = sdk.getComponent('messages.SenderProfile'); const SenderProfile = sdk.getComponent('messages.SenderProfile');
const sender = <SenderProfile onClick={this.onSenderProfileClick} const sender = <SenderProfile
onClick={this.onSenderProfileClick}
mxEvent={this.props.mxEvent} mxEvent={this.props.mxEvent}
enableFlair={false} enableFlair={false}
text={_td('%(senderName)s sent an image')} />; text={_td('%(senderName)s sent an image')}
/>;
return <div className="mx_MImageReplyBody"> return <div className="mx_MImageReplyBody">
<div className="mx_MImageReplyBody_thumbnail">{thumbnail}</div> <div className="mx_MImageReplyBody_thumbnail">{thumbnail}</div>

View File

@ -247,7 +247,7 @@ interface IProps {
// It could also be done by subclassing EventTile, but that'd be quite // It could also be done by subclassing EventTile, but that'd be quite
// boiilerplatey. So just make the necessary render decisions conditional // boiilerplatey. So just make the necessary render decisions conditional
// for now. // for now.
tileShape?: 'notif' | 'file_grid' | 'reply' | 'reply_preview'; tileShape?: 'notif' | 'file_grid';
// show twelve hour timestamps // show twelve hour timestamps
isTwelveHour?: boolean; isTwelveHour?: boolean;
@ -940,7 +940,7 @@ export default class EventTile extends React.Component<IProps, IState> {
} }
if (needsSenderProfile) { if (needsSenderProfile) {
if (!this.props.tileShape || this.props.tileShape === 'reply' || this.props.tileShape === 'reply_preview') { if (!this.props.tileShape) {
sender = <SenderProfile onClick={this.onSenderProfileClick} sender = <SenderProfile onClick={this.onSenderProfileClick}
mxEvent={this.props.mxEvent} mxEvent={this.props.mxEvent}
enableFlair={this.props.enableFlair} enableFlair={this.props.enableFlair}
@ -1087,39 +1087,6 @@ export default class EventTile extends React.Component<IProps, IState> {
); );
} }
case 'reply':
case 'reply_preview': {
let thread;
if (this.props.tileShape === 'reply_preview') {
thread = ReplyThread.makeThread(
this.props.mxEvent,
this.props.onHeightChanged,
this.props.permalinkCreator,
this.replyThread,
);
}
return (
<div className={classes} aria-live={ariaLive} aria-atomic="true">
{ ircTimestamp }
{ avatar }
{ sender }
{ ircPadlock }
<div className="mx_EventTile_reply">
{ groupTimestamp }
{ groupPadlock }
{ thread }
<EventTileType ref={this.tile}
mxEvent={this.props.mxEvent}
highlights={this.props.highlights}
highlightLink={this.props.highlightLink}
onHeightChanged={this.props.onHeightChanged}
replacingEventId={this.props.replacingEventId}
showUrlPreview={false}
/>
</div>
</div>
);
}
default: { default: {
const thread = ReplyThread.makeThread( const thread = ReplyThread.makeThread(
this.props.mxEvent, this.props.mxEvent,

View File

@ -87,9 +87,11 @@ export default class ReplyPreview extends React.Component {
</div> </div>
<div className="mx_ReplyPreview_clear" /> <div className="mx_ReplyPreview_clear" />
<div className="mx_ReplyPreview_tile"> <div className="mx_ReplyPreview_tile">
<ReplyTile isRedacted={this.state.event.isRedacted()} <ReplyTile
isRedacted={this.state.event.isRedacted()}
mxEvent={this.state.event} mxEvent={this.state.event}
permalinkCreator={this.props.permalinkCreator} /> permalinkCreator={this.props.permalinkCreator}
/>
</div> </div>
</div> </div>
</div>; </div>;

View File

@ -1,5 +1,5 @@
/* /*
Copyright 2020 Tulir Asokan <tulir@maunium.net> Copyright 2020-2021 Tulir Asokan <tulir@maunium.net>
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.
@ -26,41 +26,7 @@ import SettingsStore from "../../../settings/SettingsStore";
import {MatrixClient} from 'matrix-js-sdk'; import {MatrixClient} from 'matrix-js-sdk';
import {objectHasDiff} from '../../../utils/objects'; import {objectHasDiff} from '../../../utils/objects';
import {getHandlerTile} from "./EventTile";
const eventTileTypes = {
'm.room.message': 'messages.MessageEvent',
'm.sticker': 'messages.MessageEvent',
'm.call.invite': 'messages.TextualEvent',
'm.call.answer': 'messages.TextualEvent',
'm.call.hangup': 'messages.TextualEvent',
};
const stateEventTileTypes = {
'm.room.aliases': 'messages.TextualEvent',
// 'm.room.aliases': 'messages.RoomAliasesEvent', // too complex
'm.room.canonical_alias': 'messages.TextualEvent',
'm.room.create': 'messages.RoomCreate',
'm.room.member': 'messages.TextualEvent',
'm.room.name': 'messages.TextualEvent',
'm.room.avatar': 'messages.RoomAvatarEvent',
'm.room.third_party_invite': 'messages.TextualEvent',
'm.room.history_visibility': 'messages.TextualEvent',
'm.room.encryption': 'messages.TextualEvent',
'm.room.topic': 'messages.TextualEvent',
'm.room.power_levels': 'messages.TextualEvent',
'm.room.pinned_events': 'messages.TextualEvent',
'm.room.server_acl': 'messages.TextualEvent',
'im.vector.modular.widgets': 'messages.TextualEvent',
'm.room.tombstone': 'messages.TextualEvent',
'm.room.join_rules': 'messages.TextualEvent',
'm.room.guest_access': 'messages.TextualEvent',
'm.room.related_groups': 'messages.TextualEvent',
};
function getHandlerTile(ev) {
const type = ev.getType();
return ev.isState() ? stateEventTileTypes[type] : eventTileTypes[type];
}
class ReplyTile extends React.Component { class ReplyTile extends React.Component {
static contextTypes = { static contextTypes = {
@ -94,7 +60,7 @@ class ReplyTile extends React.Component {
return true; return true;
} }
return !this._propsEqual(this.props, nextProps); return objectHasDiff(this.props, nextProps);
} }
componentWillUnmount() { componentWillUnmount() {
@ -108,28 +74,6 @@ class ReplyTile extends React.Component {
} }
} }
_propsEqual(objA, objB) {
const keysA = Object.keys(objA);
const keysB = Object.keys(objB);
if (keysA.length !== keysB.length) {
return false;
}
for (let i = 0; i < keysA.length; i++) {
const key = keysA[i];
if (!objB.hasOwnProperty(key)) {
return false;
}
if (objA[key] !== objB[key]) {
return false;
}
}
return true;
}
onClick(e) { onClick(e) {
// This allows the permalink to be opened in a new tab/window or copied as // This allows the permalink to be opened in a new tab/window or copied as
// matrix.to, but also for it to enable routing within Riot when clicked. // matrix.to, but also for it to enable routing within Riot when clicked.