Merge branch 'develop' into gsouquet/fix-thread-root-hidden
This commit is contained in:
commit
904147b194
13 changed files with 144 additions and 54 deletions
|
@ -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) {
|
||||
|
|
|
@ -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}
|
||||
>
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue