feat(ui): Enhance Entry Component UI and UXlet's #7
No files matched your search
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 |
@@ -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},
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -26,4 +26,4 @@ func main() {
|
||||
}
|
||||
|
||||
log.Println("Successfully generated web/preview.html")
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user