feat: improve article image extraction and display

This commit is contained in:
Michael committed 2026-02-04 16:42:02 +01:00
1 parent 007aec2094
commit 6fd38fbffd
3 files changed
+55 -3

No files matched your search

+1 -1
View File
@@ -3,6 +3,7 @@ module github.com/michael/flowreader
go 1.24.0
require (
github.com/PuerkitoBio/goquery v1.8.0
github.com/go-chi/chi/v5 v5.0.11
github.com/google/uuid v1.6.0
github.com/gorilla/websocket v1.5.3
@@ -13,7 +14,6 @@ require (
)
require (
github.com/PuerkitoBio/goquery v1.8.0 // indirect
github.com/andybalholm/cascadia v1.3.1 // indirect
github.com/aymerick/douceur v0.2.0 // indirect
github.com/gorilla/css v1.0.1 // indirect
+46
View File
@@ -7,6 +7,9 @@ import (
"net/http"
"time"
"strings"
"github.com/PuerkitoBio/goquery"
"github.com/google/uuid"
"github.com/michael/flowreader/internal/domain"
"github.com/mmcdole/gofeed"
@@ -108,6 +111,8 @@ func (p *FeedParser) Parse(ctx context.Context, feedURL string, feedID uuid.UUID
if item.Image != nil && item.Image.URL != "" {
article.ImageURL = item.Image.URL
} else {
article.ImageURL = findImage(item)
}
if item.PublishedParsed != nil {
@@ -134,3 +139,44 @@ func getGUID(item *gofeed.Item) string {
}
return item.Title // Last resort fallback
}
// findImage attempts to find the best image for a feed item.
func findImage(item *gofeed.Item) string {
// 1. Check Enclosures
for _, enc := range item.Enclosures {
if strings.HasPrefix(enc.Type, "image/") {
return enc.URL
}
}
// 2. Check Media Extensions (media:content, media:thumbnail)
if media, ok := item.Extensions["media"]; ok {
if content, ok := media["content"]; ok && len(content) > 0 {
if url := content[0].Attrs["url"]; url != "" {
return url
}
}
if thumbnail, ok := media["thumbnail"]; ok && len(thumbnail) > 0 {
if url := thumbnail[0].Attrs["url"]; url != "" {
return url
}
}
}
// 3. Extract from Content/Description as fallback
htmlContent := item.Content
if htmlContent == "" {
htmlContent = item.Description
}
if htmlContent != "" {
doc, err := goquery.NewDocumentFromReader(strings.NewReader(htmlContent))
if err == nil {
if imgURL, exists := doc.Find("img").First().Attr("src"); exists {
return imgURL
}
}
}
return ""
}
+8 -2
View File
@@ -16,8 +16,14 @@ export function ArticleCard({ article, onClick, onToggleRead, onToggleFavorite }
<div className="aspect-[16/10] overflow-hidden rounded-xl bg-carbon-light relative mb-6">
<div className="absolute inset-0 bg-gradient-to-t from-carbon-dark/80 via-transparent to-transparent opacity-60 z-10"></div>
{/* Visual placeholder */}
<div className="w-full h-full bg-[url('https://images.unsplash.com/photo-1586339949916-3e9457bef6d3?auto=format&fit=crop&q=80&w=800')] bg-cover bg-center transition-transform duration-700 group-hover:scale-105 opacity-40"></div>
{/* Visual placeholder or real image */}
<div
className="w-full h-full bg-cover bg-center transition-transform duration-700 group-hover:scale-105 opacity-60"
style={{
backgroundImage: `url(${article.image_url || 'https://images.unsplash.com/photo-1586339949916-3e9457bef6d3?auto=format&fit=crop&q=80&w=800'})`,
backgroundColor: '#1e1e1e'
}}
></div>
{/* Meta on top of image */}
<div className="absolute bottom-4 left-4 right-4 z-20 flex justify-between items-end">