diff options
| -rw-r--r-- | backend/notes/app.bsky.feed.go | 47 | ||||
| -rw-r--r-- | backend/notes/note.go | 8 | ||||
| -rw-r--r-- | frontend/src/main.ts | 22 |
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(); |
