Skip to content
This repository has been archived by the owner on Sep 11, 2024. It is now read-only.

Make threads use 'm.thread' relation #6949

Merged
merged 2 commits into from
Oct 15, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 1 addition & 5 deletions src/components/structures/MessagePanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -271,9 +271,6 @@ export default class MessagePanel extends React.Component<IProps, IState> {
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;
}

Expand Down Expand Up @@ -463,8 +460,7 @@ export default class MessagePanel extends React.Component<IProps, IState> {

// 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;
}
Expand Down
6 changes: 3 additions & 3 deletions src/components/structures/ThreadPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ const useFilteredThreadsTimelinePanel = ({
userId,
updateTimeline,
}: {
threads: Set<Thread>;
threads: Map<string, Thread>;
room: Room;
userId: string;
filterOption: ThreadFilterType;
Expand All @@ -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(
Expand Down
7 changes: 5 additions & 2 deletions src/components/structures/ThreadView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -185,8 +186,10 @@ export default class ThreadView extends React.Component<IProps, IState> {
{ this.state?.thread?.timelineSet && (<MessageComposer
room={this.props.room}
resizeNotifier={this.props.resizeNotifier}
replyInThread={true}
replyToEvent={this.state?.thread?.replyToEvent}
relation={{
rel_type: RelationType.Thread,
event_id: this.state.thread.id,
}}
showReplyPreview={false}
permalinkCreator={this.props.permalinkCreator}
e2eStatus={this.props.e2eStatus}
Expand Down
18 changes: 2 additions & 16 deletions src/components/views/elements/ReplyThread.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ import classNames from 'classnames';
import { _t } from '../../../languageHandler';
import dis from '../../../dispatcher/dispatcher';
import { MatrixEvent } from 'matrix-js-sdk/src/models/event';
import { UNSTABLE_ELEMENT_REPLY_IN_THREAD } from "matrix-js-sdk/src/@types/event";
import { makeUserPermalink, RoomPermalinkCreator } from "../../../utils/permalinks/Permalinks";
import SettingsStore from "../../../settings/SettingsStore";
import { Layout } from "../../../settings/Layout";
Expand Down Expand Up @@ -225,28 +224,15 @@ export default class ReplyThread extends React.Component<IProps, IState> {
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) {
Expand Down
15 changes: 4 additions & 11 deletions src/components/views/rooms/EditMessageComposer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand All @@ -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;
Expand All @@ -70,7 +70,6 @@ function getTextReplyFallback(mxEvent: MatrixEvent): string {
function createEditContent(
model: EditorModel,
editedEvent: MatrixEvent,
renderingContext?: TimelineRenderingType,
): IContent {
const isEmote = containsEmote(model);
if (isEmote) {
Expand Down Expand Up @@ -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);
}

Expand Down Expand Up @@ -143,8 +138,7 @@ class EditMessageComposer extends React.Component<IEditMessageComposerProps, ISt
const isRestored = this.createEditorModel();
const ev = this.props.editState.getEvent();

const renderingContext = this.context.timelineRenderingType;
const editContent = createEditContent(this.model, ev, renderingContext);
const editContent = createEditContent(this.model, ev);
this.state = {
saveDisabled: !isRestored || !this.isContentModified(editContent["m.new_content"]),
};
Expand Down Expand Up @@ -369,8 +363,7 @@ class EditMessageComposer extends React.Component<IEditMessageComposerProps, ISt
const position = this.model.positionForOffset(caret.offset, caret.atNodeEnd);
this.editorRef.current?.replaceEmoticon(position, REGEX_EMOTICON);
}
const renderingContext = this.context.timelineRenderingType;
const editContent = createEditContent(this.model, editedEvent, renderingContext);
const editContent = createEditContent(this.model, editedEvent);
const newContent = editContent["m.new_content"];

let shouldSend = true;
Expand Down
13 changes: 7 additions & 6 deletions src/components/views/rooms/MessageComposer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import React, { createRef } from 'react';
import classNames from 'classnames';
import { _t } from '../../../languageHandler';
import { MatrixClientPeg } from '../../../MatrixClientPeg';
import { MatrixEvent } from "matrix-js-sdk/src/models/event";
import { MatrixEvent, IEventRelation } from "matrix-js-sdk/src/models/event";
import { Room } from "matrix-js-sdk/src/models/room";
import { RoomMember } from "matrix-js-sdk/src/models/room-member";
import dis from '../../../dispatcher/dispatcher';
Expand Down Expand Up @@ -54,6 +54,7 @@ import MemberStatusMessageAvatar from "../avatars/MemberStatusMessageAvatar";
import UIStore, { UI_EVENTS } from '../../../stores/UIStore';
import Modal from "../../../Modal";
import InfoDialog from "../dialogs/InfoDialog";
import { RelationType } from 'matrix-js-sdk/src/@types/event';

let instanceCount = 0;
const NARROW_MODE_BREAKPOINT = 500;
Expand Down Expand Up @@ -225,7 +226,7 @@ interface IProps {
resizeNotifier: ResizeNotifier;
permalinkCreator: RoomPermalinkCreator;
replyToEvent?: MatrixEvent;
replyInThread?: boolean;
relation?: IEventRelation;
showReplyPreview?: boolean;
e2eStatus?: E2EStatus;
compact?: boolean;
Expand All @@ -252,7 +253,6 @@ export default class MessageComposer extends React.Component<IProps, IState> {
private instanceId: number;

static defaultProps = {
replyInThread: false,
showReplyPreview: true,
compact: false,
};
Expand Down Expand Up @@ -378,9 +378,10 @@ export default class MessageComposer extends React.Component<IProps, IState> {

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…');
Expand Down Expand Up @@ -558,7 +559,7 @@ export default class MessageComposer extends React.Component<IProps, IState> {
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}
Expand Down
45 changes: 30 additions & 15 deletions src/components/views/rooms/SendMessageComposer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -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
Expand All @@ -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);
Expand All @@ -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;
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -164,12 +178,11 @@ export class SendMessageComposer extends React.Component<ISendMessageComposerPro
}

public componentDidUpdate(prevProps: ISendMessageComposerProps): void {
const replyToEventChanged = this.props.replyInThread && (this.props.replyToEvent !== prevProps.replyToEvent);
if (replyToEventChanged) {
this.model.reset([]);
}
const replyingToThread = this.props.relation?.key === RelationType.Thread;
const differentEventTarget = this.props.relation?.event_id !== prevProps.relation?.event_id;

if (this.props.replyInThread && this.props.replyToEvent && (!prevProps.replyToEvent || replyToEventChanged)) {
const threadChanged = replyingToThread && (differentEventTarget);
if (threadChanged) {
const partCreator = new CommandPartCreator(this.props.room, this.props.mxClient);
const parts = this.restoreStoredEditorState(partCreator) || [];
this.model.reset(parts);
Expand All @@ -182,6 +195,7 @@ export class SendMessageComposer extends React.Component<ISendMessageComposerPro
if (this.editorRef.current?.isComposing(event)) {
return;
}
const replyingToThread = this.props.relation?.key === RelationType.Thread;
const action = getKeyBindingsManager().getMessageComposerAction(event);
switch (action) {
case MessageComposerAction.Send:
Expand All @@ -203,7 +217,7 @@ export class SendMessageComposer extends React.Component<ISendMessageComposerPro
if (this.editorRef.current?.isSelectionCollapsed() && this.editorRef.current?.isCaretAtStart()) {
const events =
this.context.liveTimeline.getEvents()
.concat(this.props.replyInThread ? [] : this.props.room.getPendingEvents());
.concat(replyingToThread ? [] : this.props.room.getPendingEvents());
const editEvent = findEditableEvent({
events,
isForward: false,
Expand Down Expand Up @@ -395,8 +409,8 @@ export class SendMessageComposer extends React.Component<ISendMessageComposerPro
addReplyToMessageContent(
content,
replyToEvent,
this.props.replyInThread,
this.props.permalinkCreator,
this.props.relation,
);
}
} else {
Expand Down Expand Up @@ -443,7 +457,7 @@ export class SendMessageComposer extends React.Component<ISendMessageComposerPro
content = createMessageContent(
model,
replyToEvent,
this.props.replyInThread,
this.props.relation,
this.props.permalinkCreator,
);
}
Expand Down Expand Up @@ -517,7 +531,8 @@ export class SendMessageComposer extends React.Component<ISendMessageComposerPro
}

private restoreStoredEditorState(partCreator: PartCreator): Part[] {
if (this.props.replyInThread && !this.props.replyToEvent) {
const replyingToThread = this.props.relation?.key === RelationType.Thread;
if (replyingToThread) {
return null;
}

Expand Down