feat(ui): Enhance Entry Component UI and UXlet's #7

Merged
eleith merged 5 commits from gemini/minor-ui-ux-updates into main 2025-07-23 19:46:54 +00:00
7 changed files with 89 additions and 35 deletions

No files matched your search

+8 -6
View File
@@ -14,8 +14,10 @@ import (
func mockHandler(response string, statusCode int) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(statusCode)
if _, err := fmt.Fprintln(w, response); err != nil {
panic(err)
if statusCode != http.StatusNoContent {
if _, err := fmt.Fprintln(w, response); err != nil {
panic(err)
}
}
}
}
@@ -42,7 +44,7 @@ func TestFetchData(t *testing.T) {
panic(err)
}
case "/v1/feeds/1/icon":
if _, err := fmt.Fprintln(w, `{"data": "icon-data", "mime_type": "image/png"}`); err != nil {
if _, err := fmt.Fprintln(w, `{"data": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAHElEQVQ4T2Nksr/85wADGGYw4oBMAHkAAAD//wMA/wEAP2D3e/gAAAAASUVORK5CYII=", "mime_type": "image/png"}`); err != nil {
panic(err)
}
}
@@ -73,8 +75,8 @@ func TestFetchData(t *testing.T) {
t.Errorf("Expected 1 feed icon, got %d", len(data.FeedIcons))
}
if data.FeedIcons[0].Data != "icon-data" {
t.Errorf("Expected feed icon data 'icon-data', got %s", data.FeedIcons[0].Data)
if data.FeedIcons[0].Data != "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAHElEQVQ4T2Nksr/85wADGGYw4oBMAHkAAAD//wMA/wEAP2D3e/gAAAAASUVORK5CYII=" {
t.Errorf("Expected feed icon data 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAHElEQVQ4T2Nksr/85wADGGYw4oBMAHkAAAD//wMA/wEAP2D3e/gAAAAASUVORK5CYII=', got %s", data.FeedIcons[0].Data)
}
}
@@ -94,7 +96,7 @@ func TestStreamData(t *testing.T) {
panic(err)
}
case "/v1/feeds/1/icon":
if _, err := fmt.Fprintln(w, `{"data": "icon-data", "mime_type": "image/png"}`); err != nil {
if _, err := fmt.Fprintln(w, `{"data": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAHElEQVQ4T2Nksr/85wADGGYw4oBMAHkAAAD//wMA/wEAP2D3e/gAAAAASUVORK5CYII=", "mime_type": "image/png"}`); err != nil {
panic(err)
}
}
+44 -15
View File
@@ -42,7 +42,7 @@
section.entry {
background-color: #f9fafb;
border-radius: 0.5rem;
padding: 0.5rem;
padding: 0;
margin-bottom: 1rem;
border: 1px solid #e5e7eb;
}
@@ -55,7 +55,7 @@
summary.entry {
font-size: 1.25rem;
font-weight: 600;
margin-bottom: 0.5rem;
padding: 0.5rem;
cursor: pointer;
color: #1d4ed8;
text-decoration: none;
@@ -86,6 +86,23 @@
gap: 0.25rem;
align-items: center;
justify-content: start;
border-top: 1px solid #e5e7eb;
}
.entry-meta-tab {
padding: 0.25rem 0.5rem;
border-right: 1px solid #e5e7eb;
display: flex;
}
.entry-meta-tab-feed {
display: flex;
align-items: center;
gap: 0.25rem;
}
.entry-meta-tab:last-child {
border-right: none;
}
.entry-content {
@@ -125,14 +142,25 @@
background-color: rgba(59, 130, 246, 0.1);
}
details.entry {
}
details.entry h1 {
font-size: 1.5rem;
margin-top: 1rem;
margin-bottom: 0.5rem;
}
details summary {
cursor: pointer;
}
details summary > * {
display: inline;
}
{{range .FeedIcons}}
div.feed-icon-{{.FeedID}} {
div.feed-icon-{{.FeedID}} {
background-image: url("data:{{.Data}}");
background-size: contain;
width: 16px;
@@ -162,21 +190,22 @@
</div>
</details>
<div class="entry-meta">
<div class="feed-icon-{{.FeedID}}"></div>
<div>
<a href="{{.URL}}" target="_blank" rel="noopener noreferrer"
class="entry-link" data-link-external>external</a> |
<div class="entry-meta-tab entry-meta-tab-feed">
<div class="feed-icon-{{.FeedID}}"></div>
<div>{{.Feed.Title}}</div>
</div>
<div>
<div class="entry-meta-tab">{{.Date.Format "2 Jan"}}</div>
<div class="entry-meta-tab" style="margin-left: auto;">
<a href="{{.URL}}" target="_blank" rel="noopener noreferrer"
class="entry-link" data-link-external>permalink</a>
</div>
<div class="entry-meta-tab">
<a href="/feed/{{.FeedID}}/entry/{{.ID}}" target="_blank"
rel="noopener noreferrer" class="entry-link"
data-link-internal>internal</a> |
data-link-internal>source</a>
</div>
<div>
<button class="entry-link" data-button-mark>mark as read</button> |
</div>
<div>
| {{.Date.Format "Jan 2"}}
<div class="entry-meta-tab">
<a href="#" class="entry-link" data-button-mark>hide</a>
</div>
</div>
</section>
@@ -226,7 +255,7 @@
document.addEventListener('click', (event) => {
if (event.target.matches('button[data-button-mark]')) {
if (event.target.matches('a[data-button-mark]')) {
const entry = event.target.closest('section.entry');
const index = entries.indexOf(entry);
Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

+36 -13
View File
@@ -1,13 +1,29 @@
package testutil
import (
"encoding/base64"
"io/ioutil"
"log"
"miniflux-digest/internal/category"
"time"
miniflux "miniflux.app/v2/client"
)
func loadImageAsBase64(path string) string {
file, err := ioutil.ReadFile(path)
if err != nil {
log.Fatalf("Failed to read image file: %v", err)
}
return base64.StdEncoding.EncodeToString(file)
}
func NewMockCategoryData() *category.CategoryData {
redSquare := loadImageAsBase64("internal/testutil/images/red.png")
yellowSquare := loadImageAsBase64("internal/testutil/images/yellow.png")
greenSquare := loadImageAsBase64("internal/testutil/images/green.png")
return &category.CategoryData{
Category: &miniflux.Category{
ID: 1,
@@ -15,26 +31,27 @@ func NewMockCategoryData() *category.CategoryData {
},
Entries: &miniflux.Entries{
{
ID: 1,
UserID: 1,
FeedID: 1,
Status: miniflux.EntryStatusUnread,
Hash: "test-hash-1",
Title: "A Short and Sweet Title",
URL: "https://example.com/1",
ID: 1,
UserID: 1,
FeedID: 1,
Status: miniflux.EntryStatusUnread,
Hash: "test-hash-1",
Title: "A Short and Sweet Title",
URL: "https://example.com/1",
CommentsURL: "https://example.com/1/comments",
Date: time.Now().Add(-1 * time.Hour),
Date: time.Now().Add(-1 * time.Hour),
CreatedAt: time.Now().Add(-2 * time.Hour),
Content: "This is a short and sweet entry.",
Author: "Test Author 1",
Author: "Test Author 1",
ShareCode: "test-share-code-1",
Starred: true,
ReadingTime: 1,
Enclosures: []*miniflux.Enclosure{{URL: "https://example.com/image.jpg", MimeType: "image/jpeg"}},
Feed: &miniflux.Feed{ID: 1, Title: "Test Feed 1"},
Enclosures: []*miniflux.Enclosure{{URL: "https://example.com/image.jpg", MimeType: "image/jpeg"}},
Feed: &miniflux.Feed{ID: 1, Title: "Test Feed 1"},
},
{
ID: 2,
ID: 2,
FeedID: 2,
Title: "A Longer Entry with a Paragraph of Text",
URL: "https://example.com/2",
Date: time.Now().Add(-3 * time.Hour),
@@ -43,7 +60,8 @@ func NewMockCategoryData() *category.CategoryData {
Feed: &miniflux.Feed{ID: 2, Title: "Test Feed 2"},
},
{
ID: 3,
ID: 3,
FeedID: 3,
Title: "An Entry with HTML Content",
URL: "https://example.com/3",
Date: time.Now().Add(-4 * time.Hour),
@@ -52,5 +70,10 @@ func NewMockCategoryData() *category.CategoryData {
},
},
GeneratedDate: time.Date(2025, 7, 21, 12, 0, 0, 0, time.UTC),
FeedIcons: []*category.FeedIcon{
{FeedID: 1, Data: "image/png;base64," + redSquare},
{FeedID: 2, Data: "image/png;base64," + yellowSquare},
{FeedID: 3, Data: "image/png;base64," + greenSquare},
},
}
}
+1 -1
View File
@@ -26,4 +26,4 @@ func main() {
}
log.Println("Successfully generated web/preview.html")
}
}