aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGuillermo Serrahima <dev@vylion.com>2026-04-11 07:30:49 +0200
committerGuillermo Serrahima <dev@vylion.com>2026-04-11 07:30:49 +0200
commitf6d60077b7cfc0d589fa7961e2bb59aea9f993a3 (patch)
tree1e994b619b33ea88ef3f62cb97aba4c2cf07ff61
parentf8de6c378945495f304efebad86be05d5623290a (diff)
downloadllostre-f6d60077b7cfc0d589fa7961e2bb59aea9f993a3.tar.xz
llostre-f6d60077b7cfc0d589fa7961e2bb59aea9f993a3.zip
media rendering (wip)
-rw-r--r--backend/notes/app.bsky.feed.go47
-rw-r--r--backend/notes/note.go8
-rw-r--r--frontend/src/main.ts22
3 files changed, 77 insertions, 0 deletions
diff --git a/backend/notes/app.bsky.feed.go b/backend/notes/app.bsky.feed.go
index 961f8d7..3dd7b95 100644
--- a/backend/notes/app.bsky.feed.go
+++ b/backend/notes/app.bsky.feed.go
@@ -6,6 +6,28 @@ import (
"time"
)
+type EmbedImage struct {
+ Alt string `json:"alt"`
+ Image struct {
+ CID string `json:"$link"`
+ } `json:"image"`
+ AspectRatio *struct {
+ Width int `json:"width"`
+ Height int `json:"height"`
+ } `json:"aspectRatio,omitempty"`
+}
+
+type EmbedImages struct {
+ Type string `json:"$type"`
+ Images []EmbedImage `json:"images,omitempty"`
+}
+
+type AppBskyFeedPostEmbed struct {
+ Type string `json:"$type"`
+ Images []EmbedImage `json:"images,omitempty"`
+ Media *EmbedImages `json:"media,omitempty"`
+}
+
type AppBskyFeedPostRecord struct {
URI string `json:"uri"`
Value struct {
@@ -28,9 +50,33 @@ type AppBskyFeedPostRecord struct {
Tag string `json:"tag,omitempty"`
} `json:"features"`
} `json:"facets,omitempty"`
+ Embed *AppBskyFeedPostEmbed `json:"embed,omitempty"`
} `json:"value"`
}
+func extractImages(embed *AppBskyFeedPostEmbed, did, pdsURL string) []Media {
+ if embed == nil {
+ return nil
+ }
+ images := embed.Images
+ if embed.Media != nil {
+ images = embed.Media.Images
+ }
+ var media []Media
+ for _, img := range images {
+ m := Media{
+ URL: fmt.Sprintf("%s/xrpc/com.atproto.sync.getBlob?did=%s&cid=%s", pdsURL, did, img.Image.CID),
+ Alt: img.Alt,
+ }
+ if img.AspectRatio != nil {
+ m.Width = img.AspectRatio.Width
+ m.Height = img.AspectRatio.Height
+ }
+ media = append(media, m)
+ }
+ return media
+}
+
func NoteFromAppBskyFeedPostRecord(record AppBskyFeedPostRecord, author *Author) (*Note, error) {
if record.URI == "" {
return nil, fmt.Errorf("missing required field: uri")
@@ -99,5 +145,6 @@ func NoteFromAppBskyFeedPostRecord(record AppBskyFeedPostRecord, author *Author)
Text: record.Value.Text,
ReplyTo: replyTo,
Facets: facets,
+ Media: extractImages(record.Value.Embed, did, author.PDS),
}, nil
}
diff --git a/backend/notes/note.go b/backend/notes/note.go
index 72c885d..15f6f67 100644
--- a/backend/notes/note.go
+++ b/backend/notes/note.go
@@ -4,6 +4,13 @@ import (
"time"
)
+type Media struct {
+ URL string `json:"url"`
+ Alt string `json:"alt"`
+ Width int `json:"width,omitempty"`
+ Height int `json:"height,omitempty"`
+}
+
type Note struct {
URI string `json:"uri"`
AuthorDID string `json:"authorDID"`
@@ -16,6 +23,7 @@ type Note struct {
ReplyTo *string `json:"replyTo,omitempty"`
Facets []Facet `json:"facets,omitempty"`
Parents []Note `json:"parents,omitempty"`
+ Media []Media `json:"media,omitempty"`
}
type Facet struct {
diff --git a/frontend/src/main.ts b/frontend/src/main.ts
index 816b86c..056e9f0 100644
--- a/frontend/src/main.ts
+++ b/frontend/src/main.ts
@@ -18,6 +18,12 @@ interface Note {
type: string;
value: string;
}>;
+ media?: Array<{
+ url: string;
+ alt: string;
+ width?: number;
+ height?: number;
+ }>;
}
// convert Facets to Markdown (before Markdown parsing) if Markdown passing is enabled
@@ -162,6 +168,22 @@ function createNoteElement(note: Note): HTMLElement {
}
content.appendChild(textDiv);
+ if (note.media && note.media.length > 0) {
+ const mediaDiv = document.createElement('div');
+ mediaDiv.className = 'note-media';
+ note.media.forEach(item => {
+ const img = document.createElement('img');
+ img.src = item.url;
+ img.alt = item.alt || '';
+ if (item.width && item.height) {
+ img.width = item.width;
+ img.height = item.height;
+ }
+ mediaDiv.appendChild(img);
+ });
+ content.appendChild(mediaDiv);
+ }
+
const timeDiv = document.createElement('div');
timeDiv.className = 'muted';
timeDiv.textContent = new Date(note.createdAt).toLocaleString();