+
+
+
}
diff --git a/web/src/ui/timeline/TimelineEvent.css b/web/src/ui/timeline/TimelineEvent.css
index 339e19b..8101d49 100644
--- a/web/src/ui/timeline/TimelineEvent.css
+++ b/web/src/ui/timeline/TimelineEvent.css
@@ -150,6 +150,11 @@ div.date-separator {
}
}
+div.render-error-body {
+ font-style: italic;
+ color: #555;
+}
+
div.decryption-error-body {
display: flex;
align-items: center;
diff --git a/web/src/ui/timeline/TimelineEvent.tsx b/web/src/ui/timeline/TimelineEvent.tsx
index abd4836..401ae4e 100644
--- a/web/src/ui/timeline/TimelineEvent.tsx
+++ b/web/src/ui/timeline/TimelineEvent.tsx
@@ -21,7 +21,7 @@ import { isEventID } from "@/util/validation.ts"
import { ClientContext } from "../ClientContext.ts"
import { LightboxContext } from "../Lightbox.tsx"
import { ReplyIDBody } from "./ReplyBody.tsx"
-import getBodyType, { EventContentProps, HiddenEvent, MemberBody } from "./content"
+import getBodyType, { ContentErrorBoundary, EventContentProps, HiddenEvent, MemberBody } from "./content"
import ErrorIcon from "../../icons/error.svg?react"
import PendingIcon from "../../icons/pending.svg?react"
import SentIcon from "../../icons/sent.svg?react"
@@ -111,7 +111,9 @@ const TimelineEvent = ({ room, evt, prevEvt, setReplyToRef }: TimelineEventProps
{isEventID(replyTo) && BodyType !== HiddenEvent ? : null}
-
+
+
+
{evt.reactions ? : null}
{evt.sender === client.userID && evt.transaction_id ? : null}
diff --git a/web/src/ui/timeline/content/ContentErrorBoundary.tsx b/web/src/ui/timeline/content/ContentErrorBoundary.tsx
new file mode 100644
index 0000000..7265c81
--- /dev/null
+++ b/web/src/ui/timeline/content/ContentErrorBoundary.tsx
@@ -0,0 +1,40 @@
+// gomuks - A Matrix client written in Go.
+// Copyright (C) 2024 Tulir Asokan
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program. If not, see .
+import React from "react"
+
+export default class ContentErrorBoundary extends React.Component<{ children: React.ReactNode }, { error?: Error }> {
+ constructor(props: { children: React.ReactNode }) {
+ super(props)
+ this.state = { error: undefined }
+ }
+
+ static getDerivedStateFromError(error: unknown) {
+ if (error instanceof Error) {
+ error = new Error(`${error}`)
+ }
+ return { error }
+ }
+
+ render() {
+ if (this.state.error) {
+ return
+ Failed to render event: {this.state.error.message.replace(/^Error: /, "")}
+
+ }
+
+ return this.props.children
+ }
+}
diff --git a/web/src/ui/timeline/content/index.ts b/web/src/ui/timeline/content/index.ts
index 0eca18c..58d49c6 100644
--- a/web/src/ui/timeline/content/index.ts
+++ b/web/src/ui/timeline/content/index.ts
@@ -9,6 +9,7 @@ import TextMessageBody from "./TextMessageBody.tsx"
import UnknownMessageBody from "./UnknownMessageBody.tsx"
import EventContentProps from "./props.ts"
+export { default as ContentErrorBoundary } from "./ContentErrorBoundary.tsx"
export { default as EncryptedBody } from "./EncryptedBody.tsx"
export { default as HiddenEvent } from "./HiddenEvent.tsx"
export { default as MediaMessageBody } from "./MediaMessageBody.tsx"