Add new thread message preview (#18958) (#6953)

Closes https://github.com/vector-im/element-web/issues/18958
pull/21833/head
Dariusz Niemczyk 2021-10-15 15:29:17 +02:00 committed by GitHub
parent 9c753765d5
commit f8c516d927
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 83 additions and 16 deletions

View File

@ -676,10 +676,57 @@ $hover-select-border: 4px;
}
}
.mx_ThreadInfo:hover {
cursor: pointer;
.mx_ThreadInfo {
height: 35px;
position: relative;
background-color: $system;
padding-left: 12px;
display: flex;
align-items: center;
border-radius: 8px;
padding-right: 16px;
padding-top: 8px;
padding-bottom: 8px;
font-size: 12px;
color: $secondary-content;
box-sizing: border-box;
justify-content: flex-start;
&:hover, &-active {
cursor: pointer;
border: 1px solid $quinary-content;
padding-top: 7px;
padding-bottom: 7px;
padding-left: 11px;
padding-right: 15px;
}
.mx_ThreadInfo_content {
text-overflow: ellipsis;
overflow: hidden;
white-space: nowrap;
padding-left: 8px;
}
.mx_ThreadInfo_thread-icon {
mask-image: url('$(res)/img/element-icons/thread-summary.svg');
mask-position: center;
height: 16px;
min-width: 16px;
background-color: $secondary-content;
mask-repeat: no-repeat;
mask-size: contain;
}
.mx_ThreadInfo_threads-amount {
font-weight: 600;
position: relative;
padding: 0 8px;
white-space: nowrap;
}
}
.mx_ThreadView {
display: flex;
flex-direction: column;

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" fill="none" viewBox="0 0 18 18"><path fill="#17191C" fill-rule="evenodd" d="M3 .25A2.75 2.75 0 0 0 .25 3v14a.75.75 0 0 0 1.2.6L4.916 15c.217-.162.48-.25.75-.25H15A2.75 2.75 0 0 0 17.75 12V3A2.75 2.75 0 0 0 15 .25H3ZM4.25 6A.75.75 0 0 1 5 5.25h8a.75.75 0 0 1 0 1.5H5A.75.75 0 0 1 4.25 6ZM5 8.25a.75.75 0 0 0 0 1.5h4a.75.75 0 1 0 0-1.5H5Z" clip-rule="evenodd"/></svg>

After

Width:  |  Height:  |  Size: 428 B

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',
@ -532,15 +533,13 @@ export default class EventTile extends React.Component<IProps, IState> {
}
const thread = this.state.thread;
const room = MatrixClientPeg.get().getRoom(this.props.mxEvent.getRoomId());
if (!thread || this.props.showThreadInfo === false || thread.length <= 1) {
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
@ -549,10 +548,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

@ -1551,6 +1551,8 @@
"Send as message": "Send as message",
"Edit message": "Edit message",
"Mod": "Mod",
"%(count)s reply|other": "%(count)s replies",
"%(count)s reply|one": "%(count)s reply",
"This event could not be displayed": "This event could not be displayed",
"Your key share request has been sent - please check your other sessions for key share requests.": "Your key share request has been sent - please check your other sessions for key share requests.",
"Key share requests are sent to your other sessions automatically. If you rejected or dismissed the key share request on your other sessions, click here to request the keys for this session again.": "Key share requests are sent to your other sessions automatically. If you rejected or dismissed the key share request on your other sessions, click here to request the keys for this session again.",

View File

@ -27,6 +27,7 @@ import { CallHangupEvent } from "./previews/CallHangupEvent";
import { StickerEventPreview } from "./previews/StickerEventPreview";
import { ReactionEventPreview } from "./previews/ReactionEventPreview";
import { UPDATE_EVENT } from "../AsyncStore";
import { Thread } from "matrix-js-sdk/src/models/thread";
// Emitted event for when a room's preview has changed. First argument will the room for which
// the change happened.
@ -108,6 +109,15 @@ export class MessagePreviewStore extends AsyncStoreWithClient<IState> {
return previews.get(inTagId);
}
public generateThreadPreview(thread: Thread): string {
const lastEvent = thread.replyToEvent;
const previewDef = PREVIEWS[lastEvent.getType()];
// TODO: Handle case where we don't have
if (!previewDef) return '';
const previewText = previewDef.previewer.getTextFor(lastEvent, null, true);
return previewText ?? '';
}
private async generatePreview(room: Room, tagId?: TagID) {
const events = room.timeline;
if (!events) return; // should only happen in tests

View File

@ -23,7 +23,7 @@ import ReplyThread from "../../../components/views/elements/ReplyThread";
import { getHtmlText } from "../../../HtmlUtils";
export class MessageEventPreview implements IPreview {
public getTextFor(event: MatrixEvent, tagId?: TagID): string {
public getTextFor(event: MatrixEvent, tagId?: TagID, isThread?: boolean): string {
let eventContent = event.getContent();
if (event.isRelation("m.replace")) {
@ -64,7 +64,7 @@ export class MessageEventPreview implements IPreview {
return _t("* %(senderName)s %(emote)s", { senderName: getSenderName(event), emote: body });
}
if (isSelf(event) || !shouldPrefixMessagesIn(event.getRoomId(), tagId)) {
if (isThread || isSelf(event) || !shouldPrefixMessagesIn(event.getRoomId(), tagId)) {
return body;
} else {
return _t("%(senderName)s: %(message)s", { senderName: getSenderName(event), message: body });

View File

@ -23,7 +23,7 @@ import SettingsStore from "../../../settings/SettingsStore";
import DMRoomMap from "../../../utils/DMRoomMap";
export class ReactionEventPreview implements IPreview {
public getTextFor(event: MatrixEvent, tagId?: TagID): string {
public getTextFor(event: MatrixEvent, tagId?: TagID, isThread?: boolean): string {
const showDms = SettingsStore.getValue("feature_roomlist_preview_reactions_dms");
const showAll = SettingsStore.getValue("feature_roomlist_preview_reactions_all");
@ -41,7 +41,7 @@ export class ReactionEventPreview implements IPreview {
const reaction = relation.key;
if (!reaction) return null; // invalid reaction (unknown format)
if (isSelf(event) || !shouldPrefixMessagesIn(event.getRoomId(), tagId)) {
if (isThread || isSelf(event) || !shouldPrefixMessagesIn(event.getRoomId(), tagId)) {
return reaction;
} else {
return _t("%(senderName)s: %(reaction)s", { senderName: getSenderName(event), reaction });

View File

@ -21,11 +21,11 @@ import { getSenderName, isSelf, shouldPrefixMessagesIn } from "./utils";
import { _t } from "../../../languageHandler";
export class StickerEventPreview implements IPreview {
public getTextFor(event: MatrixEvent, tagId?: TagID): string {
public getTextFor(event: MatrixEvent, tagId?: TagID, isThread?: boolean): string {
const stickerName = event.getContent()['body'];
if (!stickerName) return null;
if (isSelf(event) || !shouldPrefixMessagesIn(event.getRoomId(), tagId)) {
if (isThread || isSelf(event) || !shouldPrefixMessagesIn(event.getRoomId(), tagId)) {
return stickerName;
} else {
return _t("%(senderName)s: %(stickerName)s", { senderName: getSenderName(event), stickerName });