Merge branch 'develop' into gsouquet/fix-thread-root-hidden

This commit is contained in:
Germain Souquet 2021-10-15 14:45:11 +01:00
commit 904147b194
13 changed files with 144 additions and 54 deletions

View file

@ -35,6 +35,7 @@ import Spinner from './Spinner';
import ReplyTile from "../rooms/ReplyTile";
import Pill from './Pill';
import { Room } from 'matrix-js-sdk/src/models/room';
import { RelationType } from 'matrix-js-sdk/src/@types/event';
/**
* This number is based on the previous behavior - if we have message of height
@ -226,13 +227,30 @@ export default class ReplyThread extends React.Component<IProps, IState> {
public static makeReplyMixIn(ev: MatrixEvent) {
if (!ev) return {};
return {
const mixin: any = {
'm.relates_to': {
'm.in_reply_to': {
'event_id': ev.getId(),
},
},
};
/**
* If the event replied is part of a thread
* Add the `m.thread` relation so that clients
* that know how to handle that relation will
* be able to render them more accurately
*/
if (ev.isThreadRelation) {
mixin['m.relates_to'] = {
...mixin['m.relates_to'],
rel_type: RelationType.Thread,
event_id: ev.threadRootId,
};
}
return mixin;
}
public static hasThreadReply(event: MatrixEvent) {

View file

@ -20,7 +20,7 @@ import React from "react";
import { RovingAccessibleTooltipButton } from "../../../accessibility/RovingTabIndex";
import Spinner from "../elements/Spinner";
import classNames from "classnames";
import { _t } from "../../../languageHandler";
import { _t, _td } from "../../../languageHandler";
import { replaceableComponent } from "../../../utils/replaceableComponent";
import { FileDownloader } from "../../../utils/FileDownloader";
@ -36,6 +36,7 @@ interface IProps {
interface IState {
loading: boolean;
blob?: Blob;
tooltip: string;
}
@replaceableComponent("views.messages.DownloadActionButton")
@ -47,12 +48,17 @@ export default class DownloadActionButton extends React.PureComponent<IProps, IS
this.state = {
loading: false,
tooltip: _td("Downloading"),
};
}
private onDownloadClick = async () => {
if (this.state.loading) return;
if (this.props.mediaEventHelperGet().media.isEncrypted) {
this.setState({ tooltip: _td("Decrypting") });
}
this.setState({ loading: true });
if (this.state.blob) {
@ -87,7 +93,7 @@ export default class DownloadActionButton extends React.PureComponent<IProps, IS
return <RovingAccessibleTooltipButton
className={classes}
title={spinner ? _t("Decrypting") : _t("Download")}
title={spinner ? _t(this.state.tooltip) : _t("Download")}
onClick={this.onDownloadClick}
disabled={!!spinner}
>

View file

@ -59,6 +59,7 @@ import { getEventDisplayInfo } from '../../../utils/EventUtils';
import SettingsStore from "../../../settings/SettingsStore";
import MKeyVerificationConclusion from "../messages/MKeyVerificationConclusion";
import { dispatchShowThreadEvent } from '../../../dispatcher/dispatch-actions/threads';
import { MessagePreviewStore } from '../../../stores/room-list/MessagePreviewStore';
const eventTileTypes = {
[EventType.RoomMessage]: 'messages.MessageEvent',
@ -556,10 +557,9 @@ export default class EventTile extends React.Component<IProps, IState> {
return null;
}
const avatars = Array.from(thread.participants).map((mxId: string) => {
const member = room.getMember(mxId);
return <MemberAvatar key={member.userId} member={member} width={14} height={14} />;
});
const threadMessagePreview = MessagePreviewStore.instance.generateThreadPreview(this.state.thread);
if (!threadMessagePreview) return null;
return (
<div
@ -568,10 +568,18 @@ export default class EventTile extends React.Component<IProps, IState> {
dispatchShowThreadEvent(this.props.mxEvent);
}}
>
<span className="mx_EventListSummary_avatars">
{ avatars }
<span className="mx_ThreadInfo_thread-icon" />
<span className="mx_ThreadInfo_threads-amount">
{ _t("%(count)s reply", {
count: thread.length - 1,
}) }
</span>
{ thread.length - 1 } { thread.length === 2 ? 'reply' : 'replies' }
<MemberAvatar member={thread.replyToEvent.sender} width={24} height={24} />
<div className="mx_ThreadInfo_content">
<span className="mx_ThreadInfo_message-preview">
{ threadMessagePreview }
</span>
</div>
</div>
);
}

View file

@ -218,17 +218,21 @@ export default class DevicesPanel extends React.Component<IProps, IState> {
const classes = classNames(this.props.className, "mx_DevicesPanel");
return (
<div className={classes}>
<div className="mx_DevicesPanel_header">
<div className="mx_DevicesPanel_deviceId">{ _t("ID") }</div>
<div className="mx_DevicesPanel_deviceName">{ _t("Public Name") }</div>
<div className="mx_DevicesPanel_deviceLastSeen">{ _t("Last seen") }</div>
<div className="mx_DevicesPanel_deviceButtons">
{ this.state.selectedDevices.length > 0 ? deleteButton : null }
</div>
</div>
{ devices.map(this.renderDevice) }
</div>
<table className={classes}>
<thead className="mx_DevicesPanel_header">
<tr>
<th className="mx_DevicesPanel_deviceId">{ _t("ID") }</th>
<th className="mx_DevicesPanel_deviceName">{ _t("Public Name") }</th>
<th className="mx_DevicesPanel_deviceLastSeen">{ _t("Last seen") }</th>
<th className="mx_DevicesPanel_deviceButtons">
{ this.state.selectedDevices.length > 0 ? deleteButton : null }
</th>
</tr>
</thead>
<tbody>
{ devices.map(this.renderDevice) }
</tbody>
</table>
);
}
}

View file

@ -66,23 +66,23 @@ export default class DevicesPanelEntry extends React.Component<IProps> {
}
return (
<div className={"mx_DevicesPanel_device" + myDeviceClass}>
<div className="mx_DevicesPanel_deviceId">
<tr className={"mx_DevicesPanel_device" + myDeviceClass}>
<td className="mx_DevicesPanel_deviceId">
{ device.device_id }
</div>
<div className="mx_DevicesPanel_deviceName">
</td>
<td className="mx_DevicesPanel_deviceName">
<EditableTextContainer initialValue={device.display_name}
onSubmit={this.onDisplayNameChanged}
placeholder={device.device_id}
/>
</div>
<div className="mx_DevicesPanel_lastSeen">
</td>
<td className="mx_DevicesPanel_lastSeen">
{ lastSeen }
</div>
<div className="mx_DevicesPanel_deviceButtons">
</td>
<td className="mx_DevicesPanel_deviceButtons">
<StyledCheckbox onChange={this.onDeviceToggled} checked={this.props.selected} />
</div>
</div>
</td>
</tr>
);
}
}