diff --git a/web/src/ui/timeline/ReplyBody.tsx b/web/src/ui/timeline/ReplyBody.tsx
index d35c870..8fb4426 100644
--- a/web/src/ui/timeline/ReplyBody.tsx
+++ b/web/src/ui/timeline/ReplyBody.tsx
@@ -18,7 +18,7 @@ import { getAvatarURL } from "@/api/media.ts"
import { RoomStateStore, useRoomEvent } from "@/api/statestore"
import type { EventID, MemDBEvent, MemberEventContent } from "@/api/types"
import { ClientContext } from "../ClientContext.ts"
-import getBodyType from "./content/getBodyType.ts"
+import getBodyType from "./content"
import CloseButton from "@/icons/close.svg?react"
import "./ReplyBody.css"
diff --git a/web/src/ui/timeline/TimelineEvent.tsx b/web/src/ui/timeline/TimelineEvent.tsx
index 13f7205..abd4836 100644
--- a/web/src/ui/timeline/TimelineEvent.tsx
+++ b/web/src/ui/timeline/TimelineEvent.tsx
@@ -21,10 +21,7 @@ import { isEventID } from "@/util/validation.ts"
import { ClientContext } from "../ClientContext.ts"
import { LightboxContext } from "../Lightbox.tsx"
import { ReplyIDBody } from "./ReplyBody.tsx"
-import HiddenEvent from "./content/HiddenEvent.tsx"
-import MemberBody from "./content/MemberBody.tsx"
-import getBodyType from "./content/getBodyType.ts"
-import { EventContentProps } from "./content/props.ts"
+import getBodyType, { 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"
diff --git a/web/src/ui/timeline/content/EncryptedBody.tsx b/web/src/ui/timeline/content/EncryptedBody.tsx
index 32da177..4906043 100644
--- a/web/src/ui/timeline/content/EncryptedBody.tsx
+++ b/web/src/ui/timeline/content/EncryptedBody.tsx
@@ -13,7 +13,7 @@
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see {`{ "type": "${event.type}" }`}
diff --git a/web/src/ui/timeline/content/MediaMessageBody.tsx b/web/src/ui/timeline/content/MediaMessageBody.tsx
new file mode 100644
index 0000000..4fd6b8d
--- /dev/null
+++ b/web/src/ui/timeline/content/MediaMessageBody.tsx
@@ -0,0 +1,36 @@
+// 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
{`{ "type": "${event.type}", "content": { "msgtype": "${content.msgtype}" } }`}
-}
+export default TextMessageBody
diff --git a/web/src/ui/timeline/content/UnknownMessageBody.tsx b/web/src/ui/timeline/content/UnknownMessageBody.tsx
new file mode 100644
index 0000000..172150c
--- /dev/null
+++ b/web/src/ui/timeline/content/UnknownMessageBody.tsx
@@ -0,0 +1,24 @@
+// 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 {`{ "type": "${event.type}", "content": { "msgtype": "${content.msgtype}" } }`}
+}
+
+export default UnknownMessageBody
diff --git a/web/src/ui/timeline/content/getBodyType.ts b/web/src/ui/timeline/content/index.ts
similarity index 62%
rename from web/src/ui/timeline/content/getBodyType.ts
rename to web/src/ui/timeline/content/index.ts
index d640938..0eca18c 100644
--- a/web/src/ui/timeline/content/getBodyType.ts
+++ b/web/src/ui/timeline/content/index.ts
@@ -2,10 +2,21 @@ import React from "react"
import { MemDBEvent } from "@/api/types"
import EncryptedBody from "./EncryptedBody.tsx"
import HiddenEvent from "./HiddenEvent.tsx"
+import MediaMessageBody from "./MediaMessageBody.tsx"
import MemberBody from "./MemberBody.tsx"
-import { MediaMessageBody, TextMessageBody, UnknownMessageBody } from "./MessageBody.tsx"
import RedactedBody from "./RedactedBody.tsx"
-import { EventContentProps } from "./props.ts"
+import TextMessageBody from "./TextMessageBody.tsx"
+import UnknownMessageBody from "./UnknownMessageBody.tsx"
+import EventContentProps from "./props.ts"
+
+export { default as EncryptedBody } from "./EncryptedBody.tsx"
+export { default as HiddenEvent } from "./HiddenEvent.tsx"
+export { default as MediaMessageBody } from "./MediaMessageBody.tsx"
+export { default as MemberBody } from "./MemberBody.tsx"
+export { default as RedactedBody } from "./RedactedBody.tsx"
+export { default as TextMessageBody } from "./TextMessageBody.tsx"
+export { default as UnknownMessageBody } from "./UnknownMessageBody.tsx"
+export type { default as EventContentProps } from "./props.ts"
export default function getBodyType(evt: MemDBEvent, forReply = false): React.FunctionComponent