diff --git a/pkg/hicli/sync.go b/pkg/hicli/sync.go
index ccc9d008..f87f9ea8 100644
--- a/pkg/hicli/sync.go
+++ b/pkg/hicli/sync.go
@@ -411,6 +411,22 @@ func (h *HiClient) cacheMedia(ctx context.Context, evt *event.Event, rowID datab
} else if content.GetInfo().ThumbnailURL != "" {
h.addMediaCache(ctx, rowID, content.Info.ThumbnailURL, nil, content.Info.ThumbnailInfo, "")
}
+
+ for _, image := range content.BeeperGalleryImages {
+ h.cacheMedia(ctx, &event.Event{
+ Type: event.EventMessage,
+ Content: event.Content{Parsed: image},
+ }, rowID)
+ }
+
+ for _, preview := range content.BeeperLinkPreviews {
+ info := &event.FileInfo{MimeType: preview.ImageType}
+ if preview.ImageEncryption != nil {
+ h.addMediaCache(ctx, rowID, preview.ImageEncryption.URL, preview.ImageEncryption, info, "")
+ } else if preview.ImageURL != "" {
+ h.addMediaCache(ctx, rowID, preview.ImageURL, nil, info, "")
+ }
+ }
case event.StateRoomAvatar:
_ = evt.Content.ParseRaw(evt.Type)
content, ok := evt.Content.Parsed.(*event.RoomAvatarEventContent)
diff --git a/web/src/api/types/mxtypes.ts b/web/src/api/types/mxtypes.ts
index 2228124e..0f4802e8 100644
--- a/web/src/api/types/mxtypes.ts
+++ b/web/src/api/types/mxtypes.ts
@@ -140,6 +140,19 @@ export interface ContentWarning {
description?: string
}
+export interface URLPreview {
+ matched_url: string
+ "beeper:image:encryption"?: EncryptedFile
+ "matrix:image:size": number
+ "og:image"?: ContentURI
+ "og:url": string
+ "og:image:width"?: number
+ "og:image:height"?: number
+ "og:image:type"?: string
+ "og:title"?: string
+ "og:description"?: string
+}
+
export interface BaseMessageEventContent {
msgtype: string
body: string
@@ -150,6 +163,8 @@ export interface BaseMessageEventContent {
"town.robin.msc3725.content_warning"?: ContentWarning
"page.codeberg.everypizza.msc4193.spoiler"?: boolean
"page.codeberg.everypizza.msc4193.spoiler.reason"?: string
+ "m.url_previews"?: URLPreview[]
+ "com.beeper.linkpreviews"?: URLPreview[]
}
export interface TextMessageEventContent extends BaseMessageEventContent {
diff --git a/web/src/ui/timeline/LinkPreviews.css b/web/src/ui/timeline/LinkPreviews.css
new file mode 100644
index 00000000..89dd5f87
--- /dev/null
+++ b/web/src/ui/timeline/LinkPreviews.css
@@ -0,0 +1,35 @@
+div.link-previews {
+ display: flex;
+ flex-direction: row;
+ gap: 1rem;
+ overflow-x: scroll;
+
+ > div.link-preview {
+ margin: 0.5rem 0;
+ border-radius: 0.5rem;
+ background-color: var(--pill-background-color);
+
+ > div.title {
+ margin: 0.5rem 0.5rem 0 0.5rem;
+ display: -webkit-box;
+ -webkit-line-clamp: 1;
+ line-clamp: 1;
+ -webkit-box-orient: vertical;
+ overflow: hidden;
+ }
+
+ > div.description {
+ margin: 0 0.5rem 0.5rem 0.5rem;
+ display: -webkit-box;
+ -webkit-line-clamp: 3;
+ line-clamp: 3;
+ -webkit-box-orient: vertical;
+ overflow: hidden;
+ color: var(--semisecondary-text-color);
+ }
+
+ > div.media-container {
+ border-radius: 0 0 0.5rem 0.5rem;
+ }
+ }
+}
diff --git a/web/src/ui/timeline/LinkPreviews.tsx b/web/src/ui/timeline/LinkPreviews.tsx
new file mode 100644
index 00000000..20effdb9
--- /dev/null
+++ b/web/src/ui/timeline/LinkPreviews.tsx
@@ -0,0 +1,54 @@
+// gomuks - A Matrix client written in Go.
+// Copyright (C) 2024 Sumner Evans
+//
+// 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