From 61a0be7d46c8a72614af9123869e0fd89a54310e Mon Sep 17 00:00:00 2001
From: Travis Ralston <travisr@matrix.org>
Date: Thu, 13 Jan 2022 10:03:37 -0700
Subject: [PATCH] Render events as extensible events (behind labs) (#7462)

* Render events as extensible events (behind labs)

* Include the SDK

* Appease linter

* Update for changed property name

* Fix formatting error

* Fix branch matching for build steps

* Update SDK

* Update scripts/fetchdep.sh

Co-authored-by: Andy Balaam <andyb@element.io>

Co-authored-by: Andy Balaam <andyb@element.io>
---
 package.json                                  |   1 +
 scripts/fetchdep.sh                           |  15 ++-
 src/TextForEvent.tsx                          |  20 +++-
 src/components/views/messages/TextualBody.tsx | 103 +++++++++++-------
 src/i18n/strings/en_EN.json                   |   1 +
 src/settings/Settings.tsx                     |   7 ++
 yarn.lock                                     |   5 +
 7 files changed, 107 insertions(+), 45 deletions(-)

diff --git a/package.json b/package.json
index 65c9077cc7..0e579d96f1 100644
--- a/package.json
+++ b/package.json
@@ -89,6 +89,7 @@
     "lodash": "^4.17.20",
     "maplibre-gl": "^1.15.2",
     "matrix-analytics-events": "https://github.com/matrix-org/matrix-analytics-events.git#1eab4356548c97722a183912fda1ceabbe8cc7c1",
+    "matrix-events-sdk": "^0.0.1-beta.2",
     "matrix-js-sdk": "github:matrix-org/matrix-js-sdk#develop",
     "matrix-widget-api": "^0.1.0-beta.18",
     "minimist": "^1.2.5",
diff --git a/scripts/fetchdep.sh b/scripts/fetchdep.sh
index 8c97670339..15e998ddbe 100755
--- a/scripts/fetchdep.sh
+++ b/scripts/fetchdep.sh
@@ -49,11 +49,18 @@ elif [ -n "$REVIEW_ID" ]; then
     getPRInfo $REVIEW_ID
 fi
 
-# $head will always be in the format "fork:branch", so we split it by ":" into
-# an array. The first element will then be the fork and the second the branch.
-# Based on that we clone
+# for forks, $head will be in the format "fork:branch", so we split it by ":"
+# into an array. On non-forks, this has the effect of splitting into a single
+# element array given ":" shouldn't appear in the head - it'll just be the
+# branch name. Based on the results, we clone.
 BRANCH_ARRAY=(${head//:/ })
-clone ${BRANCH_ARRAY[0]} $defrepo ${BRANCH_ARRAY[1]}
+TRY_ORG=$deforg
+TRY_BRANCH=${BRANCH_ARRAY[0]}
+if [[ "$head" == *":"* ]]; then
+    TRY_ORG=${BRANCH_ARRAY[0]}
+    TRY_BRANCH=${BRANCH_ARRAY[1]}
+fi
+clone ${TRY_ORG} $defrepo ${TRY_BRANCH}
 
 # Try the target branch of the push or PR.
 if [ -n $GITHUB_BASE_REF ]; then
diff --git a/src/TextForEvent.tsx b/src/TextForEvent.tsx
index 53fa8e3e56..6cb542bea1 100644
--- a/src/TextForEvent.tsx
+++ b/src/TextForEvent.tsx
@@ -1,5 +1,5 @@
 /*
-Copyright 2015, 2016 OpenMarket Ltd
+Copyright 2015 - 2022 The Matrix.org Foundation C.I.C.
 
 Licensed under the Apache License, Version 2.0 (the "License");
 you may not use this file except in compliance with the License.
@@ -20,6 +20,7 @@ import { logger } from "matrix-js-sdk/src/logger";
 import { removeDirectionOverrideChars } from 'matrix-js-sdk/src/utils';
 import { GuestAccess, HistoryVisibility, JoinRule } from "matrix-js-sdk/src/@types/partials";
 import { EventType, MsgType } from "matrix-js-sdk/src/@types/event";
+import { EmoteEvent, NoticeEvent, MessageEvent } from "matrix-events-sdk";
 
 import { _t } from './languageHandler';
 import * as Roles from './Roles';
@@ -334,10 +335,23 @@ function textForMessageEvent(ev: MatrixEvent): () => string | null {
             if (redactedBecauseUserId && redactedBecauseUserId !== ev.getSender()) {
                 const room = MatrixClientPeg.get().getRoom(ev.getRoomId());
                 const sender = room?.getMember(redactedBecauseUserId);
-                message = _t("Message deleted by %(name)s", { name: sender?.name
- || redactedBecauseUserId });
+                message = _t("Message deleted by %(name)s", {
+                    name: sender?.name || redactedBecauseUserId,
+                });
             }
         }
+
+        if (SettingsStore.isEnabled("feature_extensible_events")) {
+            const extev = ev.unstableExtensibleEvent;
+            if (extev) {
+                if (extev instanceof EmoteEvent) {
+                    return `* ${senderDisplayName} ${extev.text}`;
+                } else if (extev instanceof NoticeEvent || extev instanceof MessageEvent) {
+                    return `${senderDisplayName}: ${extev.text}`;
+                }
+            }
+        }
+
         if (ev.getContent().msgtype === MsgType.Emote) {
             message = "* " + senderDisplayName + " " + message;
         } else if (ev.getContent().msgtype === MsgType.Image) {
diff --git a/src/components/views/messages/TextualBody.tsx b/src/components/views/messages/TextualBody.tsx
index 30b7edf7cf..a622d55aa0 100644
--- a/src/components/views/messages/TextualBody.tsx
+++ b/src/components/views/messages/TextualBody.tsx
@@ -18,6 +18,7 @@ import React, { createRef, SyntheticEvent } from 'react';
 import ReactDOM from 'react-dom';
 import highlight from 'highlight.js';
 import { MsgType } from "matrix-js-sdk/src/@types/event";
+import { isEventLike, LegacyMsgType, MessageEvent } from "matrix-events-sdk";
 
 import * as HtmlUtils from '../../../HtmlUtils';
 import { formatDate } from '../../../DateUtils';
@@ -509,17 +510,44 @@ export default class TextualBody extends React.Component<IBodyProps, IState> {
         }
         const mxEvent = this.props.mxEvent;
         const content = mxEvent.getContent();
+        let isNotice = false;
+        let isEmote = false;
 
         // only strip reply if this is the original replying event, edits thereafter do not have the fallback
         const stripReply = !mxEvent.replacingEvent() && !!ReplyChain.getParentEventId(mxEvent);
-        let body = HtmlUtils.bodyToHtml(content, this.props.highlights, {
-            disableBigEmoji: content.msgtype === MsgType.Emote
-                || !SettingsStore.getValue<boolean>('TextualBody.enableBigEmoji'),
-            // Part of Replies fallback support
-            stripReplyFallback: stripReply,
-            ref: this.contentRef,
-            returnString: false,
-        });
+        let body;
+        if (SettingsStore.isEnabled("feature_extensible_events")) {
+            const extev = this.props.mxEvent.unstableExtensibleEvent;
+            if (extev && extev instanceof MessageEvent) {
+                isEmote = isEventLike(extev.wireFormat, LegacyMsgType.Emote);
+                isNotice = isEventLike(extev.wireFormat, LegacyMsgType.Notice);
+                body = HtmlUtils.bodyToHtml({
+                    body: extev.text,
+                    format: extev.html ? "org.matrix.custom.html" : undefined,
+                    formatted_body: extev.html,
+                    msgtype: MsgType.Text,
+                }, this.props.highlights, {
+                    disableBigEmoji: isEmote
+                        || !SettingsStore.getValue<boolean>('TextualBody.enableBigEmoji'),
+                    // Part of Replies fallback support
+                    stripReplyFallback: stripReply,
+                    ref: this.contentRef,
+                    returnString: false,
+                });
+            }
+        }
+        if (!body) {
+            isEmote = content.msgtype === MsgType.Emote;
+            isNotice = content.msgtype === MsgType.Notice;
+            body = HtmlUtils.bodyToHtml(content, this.props.highlights, {
+                disableBigEmoji: isEmote
+                    || !SettingsStore.getValue<boolean>('TextualBody.enableBigEmoji'),
+                // Part of Replies fallback support
+                stripReplyFallback: stripReply,
+                ref: this.contentRef,
+                returnString: false,
+            });
+        }
         if (this.props.replacingEventId) {
             body = <>
                 { body }
@@ -545,36 +573,35 @@ export default class TextualBody extends React.Component<IBodyProps, IState> {
             />;
         }
 
-        switch (content.msgtype) {
-            case MsgType.Emote:
-                return (
-                    <div className="mx_MEmoteBody mx_EventTile_content">
-                        *&nbsp;
-                        <span
-                            className="mx_MEmoteBody_sender"
-                            onClick={this.onEmoteSenderClick}
-                        >
-                            { mxEvent.sender ? mxEvent.sender.name : mxEvent.getSender() }
-                        </span>
-                        &nbsp;
-                        { body }
-                        { widgets }
-                    </div>
-                );
-            case MsgType.Notice:
-                return (
-                    <div className="mx_MNoticeBody mx_EventTile_content">
-                        { body }
-                        { widgets }
-                    </div>
-                );
-            default: // including "m.text"
-                return (
-                    <div className="mx_MTextBody mx_EventTile_content">
-                        { body }
-                        { widgets }
-                    </div>
-                );
+        if (isEmote) {
+            return (
+                <div className="mx_MEmoteBody mx_EventTile_content">
+                    *&nbsp;
+                    <span
+                        className="mx_MEmoteBody_sender"
+                        onClick={this.onEmoteSenderClick}
+                    >
+                        { mxEvent.sender ? mxEvent.sender.name : mxEvent.getSender() }
+                    </span>
+                    &nbsp;
+                    { body }
+                    { widgets }
+                </div>
+            );
         }
+        if (isNotice) {
+            return (
+                <div className="mx_MNoticeBody mx_EventTile_content">
+                    { body }
+                    { widgets }
+                </div>
+            );
+        }
+        return (
+            <div className="mx_MTextBody mx_EventTile_content">
+                { body }
+                { widgets }
+            </div>
+        );
     }
 }
diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json
index e1ce9479a4..7c150de3b7 100644
--- a/src/i18n/strings/en_EN.json
+++ b/src/i18n/strings/en_EN.json
@@ -878,6 +878,7 @@
     "Show message previews for reactions in DMs": "Show message previews for reactions in DMs",
     "Show message previews for reactions in all rooms": "Show message previews for reactions in all rooms",
     "Offline encrypted messaging using dehydrated devices": "Offline encrypted messaging using dehydrated devices",
+    "Show extensible event representation of events": "Show extensible event representation of events",
     "Polls (under active development)": "Polls (under active development)",
     "Location sharing (under active development)": "Location sharing (under active development)",
     "Show info about bridges in room settings": "Show info about bridges in room settings",
diff --git a/src/settings/Settings.tsx b/src/settings/Settings.tsx
index de2ddb7245..666cc1c753 100644
--- a/src/settings/Settings.tsx
+++ b/src/settings/Settings.tsx
@@ -292,6 +292,13 @@ export const SETTINGS: {[setting: string]: ISetting} = {
         supportedLevels: LEVELS_FEATURE,
         default: false,
     },
+    "feature_extensible_events": {
+        isFeature: true,
+        labsGroup: LabGroup.Developer, // developer for now, eventually Messaging and default on
+        supportedLevels: LEVELS_FEATURE,
+        displayName: _td("Show extensible event representation of events"),
+        default: false,
+    },
     "feature_polls": {
         isFeature: true,
         labsGroup: LabGroup.Messaging,
diff --git a/yarn.lock b/yarn.lock
index e38c6c9bec..3f4ce0ff64 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -6163,6 +6163,11 @@ mathml-tag-names@^2.1.3:
   version "0.0.1"
   resolved "https://github.com/matrix-org/matrix-analytics-events.git#1eab4356548c97722a183912fda1ceabbe8cc7c1"
 
+matrix-events-sdk@^0.0.1-beta.2:
+  version "0.0.1-beta.2"
+  resolved "https://registry.yarnpkg.com/matrix-events-sdk/-/matrix-events-sdk-0.0.1-beta.2.tgz#28efdcc3259152c4d53094cedb72b3843e5f772e"
+  integrity sha512-a3VIZeb9IxxxPrvFnUbt4pjP7A6irv7eWLv1GBoq+80m7v5n3QhzT/mmeUGJx2KNt7jLboFau4g1iIU82H3wEg==
+
 "matrix-js-sdk@github:matrix-org/matrix-js-sdk#develop":
   version "15.3.0"
   resolved "https://codeload.github.com/matrix-org/matrix-js-sdk/tar.gz/2d9c93876524cb553f616b10fe50d9e7e539075b"