From d3156410564afc8b4469ee2c1af388dc234b2619 Mon Sep 17 00:00:00 2001 From: Germain Souquet Date: Thu, 14 Oct 2021 16:57:02 +0100 Subject: [PATCH] Make threads use 'm.thread' relation --- src/components/structures/MessagePanel.tsx | 6 +-- src/components/structures/ThreadPanel.tsx | 6 +-- src/components/structures/ThreadView.tsx | 7 ++- src/components/views/elements/ReplyThread.tsx | 18 +------- .../views/rooms/EditMessageComposer.tsx | 15 ++----- .../views/rooms/MessageComposer.tsx | 13 +++--- .../views/rooms/SendMessageComposer.tsx | 45 ++++++++++++------- 7 files changed, 52 insertions(+), 58 deletions(-) diff --git a/src/components/structures/MessagePanel.tsx b/src/components/structures/MessagePanel.tsx index 79c010cb8f8..c1d22ecedff 100644 --- a/src/components/structures/MessagePanel.tsx +++ b/src/components/structures/MessagePanel.tsx @@ -271,9 +271,6 @@ export default class MessagePanel extends React.Component { componentDidMount() { this.calculateRoomMembersCount(); this.props.room?.on("RoomState.members", this.calculateRoomMembersCount); - if (SettingsStore.getValue("feature_thread")) { - this.props.room?.getThreads().forEach(thread => thread.fetchReplyChain()); - } this.isMounted = true; } @@ -463,8 +460,7 @@ export default class MessagePanel extends React.Component { // Checking if the message has a "parentEventId" as we do not // want to hide the root event of the thread - if (mxEv.replyInThread && mxEv.parentEventId - && this.props.hideThreadedMessages + if (mxEv.isThreadRoot && this.props.hideThreadedMessages && SettingsStore.getValue("feature_thread")) { return false; } diff --git a/src/components/structures/ThreadPanel.tsx b/src/components/structures/ThreadPanel.tsx index fbb55476411..40e94792511 100644 --- a/src/components/structures/ThreadPanel.tsx +++ b/src/components/structures/ThreadPanel.tsx @@ -71,7 +71,7 @@ const useFilteredThreadsTimelinePanel = ({ userId, updateTimeline, }: { - threads: Set; + threads: Map; room: Room; userId: string; filterOption: ThreadFilterType; @@ -85,13 +85,13 @@ const useFilteredThreadsTimelinePanel = ({ useEffect(() => { let filteredThreads = Array.from(threads); if (filterOption === ThreadFilterType.My) { - filteredThreads = filteredThreads.filter(thread => { + filteredThreads = filteredThreads.filter(([id, thread]) => { return thread.rootEvent.getSender() === userId; }); } // NOTE: Temporarily reverse the list until https://github.com/vector-im/element-web/issues/19393 gets properly resolved // The proper list order should be top-to-bottom, like in social-media newsfeeds. - filteredThreads.reverse().forEach(thread => { + filteredThreads.reverse().forEach(([id, thread]) => { const event = thread.rootEvent; if (timelineSet.findEventById(event.getId()) || event.status !== null) return; timelineSet.addEventToTimeline( diff --git a/src/components/structures/ThreadView.tsx b/src/components/structures/ThreadView.tsx index d444ed5d50b..34622128349 100644 --- a/src/components/structures/ThreadView.tsx +++ b/src/components/structures/ThreadView.tsx @@ -17,6 +17,7 @@ limitations under the License. import React from 'react'; import { MatrixEvent, Room } from 'matrix-js-sdk/src'; import { Thread, ThreadEvent } from 'matrix-js-sdk/src/models/thread'; +import { RelationType } from 'matrix-js-sdk/src/@types/event'; import BaseCard from "../views/right_panel/BaseCard"; import { RightPanelPhases } from "../../stores/RightPanelStorePhases"; @@ -185,8 +186,10 @@ export default class ThreadView extends React.Component { { this.state?.thread?.timelineSet && ( { return { body, html }; } - public static makeReplyMixIn(ev: MatrixEvent, replyInThread: boolean) { + public static makeReplyMixIn(ev: MatrixEvent) { if (!ev) return {}; - - const replyMixin = { + return { 'm.relates_to': { 'm.in_reply_to': { 'event_id': ev.getId(), }, }, }; - - /** - * @experimental - * Rendering hint for threads, only attached if true to make - * sure that Element does not start sending that property for all events - */ - if (replyInThread) { - const inReplyTo = replyMixin['m.relates_to']['m.in_reply_to']; - inReplyTo[UNSTABLE_ELEMENT_REPLY_IN_THREAD.name] = replyInThread; - } - - return replyMixin; } public static hasThreadReply(event: MatrixEvent) { diff --git a/src/components/views/rooms/EditMessageComposer.tsx b/src/components/views/rooms/EditMessageComposer.tsx index ce42131b6da..538de9afbf7 100644 --- a/src/components/views/rooms/EditMessageComposer.tsx +++ b/src/components/views/rooms/EditMessageComposer.tsx @@ -35,7 +35,7 @@ import { getKeyBindingsManager, MessageComposerAction } from '../../../KeyBindin import { replaceableComponent } from "../../../utils/replaceableComponent"; import SendHistoryManager from '../../../SendHistoryManager'; import Modal from '../../../Modal'; -import { MsgType, UNSTABLE_ELEMENT_REPLY_IN_THREAD } from 'matrix-js-sdk/src/@types/event'; +import { MsgType } from 'matrix-js-sdk/src/@types/event'; import { Room } from 'matrix-js-sdk/src/models/room'; import ErrorDialog from "../dialogs/ErrorDialog"; import QuestionDialog from "../dialogs/QuestionDialog"; @@ -46,7 +46,7 @@ import SettingsStore from "../../../settings/SettingsStore"; import { logger } from "matrix-js-sdk/src/logger"; import { withMatrixClientHOC, MatrixClientProps } from '../../../contexts/MatrixClientContext'; -import RoomContext, { TimelineRenderingType } from '../../../contexts/RoomContext'; +import RoomContext from '../../../contexts/RoomContext'; function getHtmlReplyFallback(mxEvent: MatrixEvent): string { const html = mxEvent.getContent().formatted_body; @@ -70,7 +70,6 @@ function getTextReplyFallback(mxEvent: MatrixEvent): string { function createEditContent( model: EditorModel, editedEvent: MatrixEvent, - renderingContext?: TimelineRenderingType, ): IContent { const isEmote = containsEmote(model); if (isEmote) { @@ -112,10 +111,6 @@ function createEditContent( }, }; - if (renderingContext === TimelineRenderingType.Thread) { - relation['m.relates_to'][UNSTABLE_ELEMENT_REPLY_IN_THREAD.name] = true; - } - return Object.assign(relation, contentBody); } @@ -143,8 +138,7 @@ class EditMessageComposer extends React.Component { private instanceId: number; static defaultProps = { - replyInThread: false, showReplyPreview: true, compact: false, }; @@ -378,9 +378,10 @@ export default class MessageComposer extends React.Component { private renderPlaceholderText = () => { if (this.props.replyToEvent) { - if (this.props.replyInThread && this.props.e2eStatus) { + const replyingToThread = this.props.relation?.rel_type === RelationType.Thread; + if (replyingToThread && this.props.e2eStatus) { return _t('Reply to encrypted thread…'); - } else if (this.props.replyInThread) { + } else if (replyingToThread) { return _t('Reply to thread…'); } else if (this.props.e2eStatus) { return _t('Send an encrypted reply…'); @@ -558,7 +559,7 @@ export default class MessageComposer extends React.Component { room={this.props.room} placeholder={this.renderPlaceholderText()} permalinkCreator={this.props.permalinkCreator} - replyInThread={this.props.replyInThread} + relation={this.props.relation} replyToEvent={this.props.replyToEvent} onChange={this.onChange} disabled={this.state.haveRecording} diff --git a/src/components/views/rooms/SendMessageComposer.tsx b/src/components/views/rooms/SendMessageComposer.tsx index 35a028aadce..79411e6ebc8 100644 --- a/src/components/views/rooms/SendMessageComposer.tsx +++ b/src/components/views/rooms/SendMessageComposer.tsx @@ -16,7 +16,7 @@ limitations under the License. import React, { ClipboardEvent, createRef, KeyboardEvent } from 'react'; import EMOJI_REGEX from 'emojibase-regex'; -import { IContent, MatrixEvent } from 'matrix-js-sdk/src/models/event'; +import { IContent, MatrixEvent, IEventRelation } from 'matrix-js-sdk/src/models/event'; import { DebouncedFunc, throttle } from 'lodash'; import { EventType, RelationType } from "matrix-js-sdk/src/@types/event"; import { logger } from "matrix-js-sdk/src/logger"; @@ -61,10 +61,10 @@ import RoomContext from '../../../contexts/RoomContext'; function addReplyToMessageContent( content: IContent, replyToEvent: MatrixEvent, - replyInThread: boolean, permalinkCreator: RoomPermalinkCreator, + relation?: IEventRelation, ): void { - const replyContent = ReplyThread.makeReplyMixIn(replyToEvent, replyInThread); + const replyContent = ReplyThread.makeReplyMixIn(replyToEvent); Object.assign(content, replyContent); // Part of Replies fallback support - prepend the text we're sending @@ -76,13 +76,20 @@ function addReplyToMessageContent( } content.body = nestedReply.body + content.body; } + + if (relation) { + content['m.relates_to'] = { + ...relation, // the composer can have a default + ...content['m.relates_to'], + }; + } } // exported for tests export function createMessageContent( model: EditorModel, replyToEvent: MatrixEvent, - replyInThread: boolean, + relation: IEventRelation, permalinkCreator: RoomPermalinkCreator, ): IContent { const isEmote = containsEmote(model); @@ -106,7 +113,14 @@ export function createMessageContent( } if (replyToEvent) { - addReplyToMessageContent(content, replyToEvent, replyInThread, permalinkCreator); + addReplyToMessageContent(content, replyToEvent, permalinkCreator); + } + + if (relation) { + content['m.relates_to'] = { + ...relation, + ...content['m.relates_to'], + }; } return content; @@ -134,7 +148,7 @@ interface ISendMessageComposerProps extends MatrixClientProps { room: Room; placeholder?: string; permalinkCreator: RoomPermalinkCreator; - replyInThread?: boolean; + relation?: IEventRelation; replyToEvent?: MatrixEvent; disabled?: boolean; onChange?(model: EditorModel): void; @@ -162,12 +176,11 @@ export class SendMessageComposer extends React.Component