diff --git a/src/matrix/room/timeline/entries/BaseEventEntry.js b/src/matrix/room/timeline/entries/BaseEventEntry.js index ec20f48a..dbcfce94 100644 --- a/src/matrix/room/timeline/entries/BaseEventEntry.js +++ b/src/matrix/room/timeline/entries/BaseEventEntry.js @@ -16,9 +16,9 @@ limitations under the License. import {BaseEntry} from "./BaseEntry"; import {REDACTION_TYPE} from "../../common.js"; -import {createAnnotation, ANNOTATION_RELATION_TYPE, getRelationFromContent} from "../relations.js"; +import {createAnnotation, ANNOTATION_RELATION_TYPE, THREADING_RELATION_TYPE, getRelationFromContent} from "../relations.js"; import {PendingAnnotation} from "../PendingAnnotation.js"; -import {createReplyContent} from "./reply.js" +import {createReplyContent} from "./reply.js"; /** Deals mainly with local echo for relations and redactions, * so it is shared between PendingEventEntry and EventEntry */ @@ -32,7 +32,11 @@ export class BaseEventEntry extends BaseEntry { } get isReply() { - return !!this.relation?.["m.in_reply_to"]; + return !!this.relation?.["m.in_reply_to"] || this.isThread; + } + + get isThread() { + return this.relation?.["rel_type"] === THREADING_RELATION_TYPE; } get isRedacting() { diff --git a/src/matrix/room/timeline/entries/EventEntry.js b/src/matrix/room/timeline/entries/EventEntry.js index 7b957e01..3d9c982b 100644 --- a/src/matrix/room/timeline/entries/EventEntry.js +++ b/src/matrix/room/timeline/entries/EventEntry.js @@ -124,6 +124,13 @@ export class EventEntry extends BaseEventEntry { return getRelatedEventId(this.event); } + get threadEventId() { + if (this.isThread) { + return this.relation?.event_id; + } + return null; + } + get isRedacted() { return super.isRedacted || isRedacted(this._eventEntry.event); } diff --git a/src/matrix/room/timeline/entries/reply.js b/src/matrix/room/timeline/entries/reply.js index 2e180c11..fcc6f56f 100644 --- a/src/matrix/room/timeline/entries/reply.js +++ b/src/matrix/room/timeline/entries/reply.js @@ -14,6 +14,8 @@ See the License for the specific language governing permissions and limitations under the License. */ +import {THREADING_RELATION_TYPE} from "../relations.js"; + function htmlEscape(string) { return string.replace(/&/g, "&").replace(//g, ">"); } @@ -36,8 +38,8 @@ function fallbackPrefix(msgtype) { return msgtype === "m.emote" ? "* " : ""; } -function _createReplyContent(targetId, msgtype, body, formattedBody) { - return { +function _createReplyContent(targetId, msgtype, body, formattedBody, threadId) { + const reply = { msgtype, body, "format": "org.matrix.custom.html", @@ -48,10 +50,16 @@ function _createReplyContent(targetId, msgtype, body, formattedBody) { } } }; + if (threadId) { + Object.assign(reply["m.relates_to"], { + rel_type: THREADING_RELATION_TYPE, + event_id: threadId, + }); + } + return reply; } export function createReplyContent(entry, msgtype, body) { - // TODO check for absense of sender / body / msgtype / etc? const nonTextual = fallbackForNonTextualMessage(entry.content.msgtype); const prefix = fallbackPrefix(entry.content.msgtype); const sender = entry.sender; @@ -70,5 +78,5 @@ export function createReplyContent(entry, msgtype, body) { const newBody = plainFallback + '\n\n' + body; const newFormattedBody = formattedFallback + htmlEscape(body); - return _createReplyContent(entry.id, msgtype, newBody, newFormattedBody); + return _createReplyContent(entry.id, msgtype, newBody, newFormattedBody, entry.threadEventId); } diff --git a/src/matrix/room/timeline/relations.js b/src/matrix/room/timeline/relations.js index 4009d8c4..d92847ec 100644 --- a/src/matrix/room/timeline/relations.js +++ b/src/matrix/room/timeline/relations.js @@ -18,6 +18,7 @@ import {REDACTION_TYPE} from "../common.js"; export const REACTION_TYPE = "m.reaction"; export const ANNOTATION_RELATION_TYPE = "m.annotation"; +export const THREADING_RELATION_TYPE = "io.element.thread"; export function createAnnotation(targetId, key) { return { @@ -30,7 +31,7 @@ export function createAnnotation(targetId, key) { } export function getRelationTarget(relation) { - return relation.event_id || relation["m.in_reply_to"]?.event_id + return relation["m.in_reply_to"]?.event_id || relation.event_id; } export function setRelationTarget(relation, target) {