8 Commits

Author SHA1 Message Date
nkanaev
55b9b4a38b build whenever 2026-03-16 21:18:35 +00:00
charlie
e916fdbe6c fix: add QEMU and Buildx setup for multi-arch Docker builds
Fixes #290

The workflow was failing because it specified multi-platform builds
(linux/amd64,linux/arm64) but didn't set up QEMU emulation or Buildx.

This adds the required setup steps before the build action.
2026-03-16 21:18:35 +00:00
nkanaev
0e3df33d1f parse rss 2.0 image enclosures 2026-03-16 20:44:46 +00:00
nkanaev
506fe1cae6 update changelog 2026-01-23 20:09:05 +00:00
nkanaev
1d97314825 declare default namespace explicitly 2026-01-23 20:04:29 +00:00
nkanaev
e1ecb6760b Update changelog.md 2025-12-28 14:16:23 +00:00
rksvc
953f560a11 fix crash on empty item list with item selected 2025-12-28 14:13:46 +00:00
nkanaev
3d69911aa8 reset article list right after filter/feed is selected 2025-12-11 11:22:14 +00:00
6 changed files with 61 additions and 8 deletions

View File

@@ -3,6 +3,8 @@ on:
push:
tags:
- v*
workflow_dispatch:
env:
REGISTRY: ghcr.io
IMAGE_NAME: nkanaev/yarr
@@ -17,6 +19,12 @@ jobs:
- name: Checkout repository
uses: actions/checkout@v4
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Log in to the Container registry
uses: docker/login-action@v3
with:

View File

@@ -1,5 +1,12 @@
# upcoming
- (fix) articles not resetting immediately after feed/filter selection (thank to @scratchmex for the report)
- (fix) crash on empty article list with article is selected (thanks to @rksvc)
- (fix) invalid article title in RSS feeds with media containing titles (thanks to @bwwu-git for the report)
- (fix) missing image enclosures in certain RSS feeds (thanks to @palinek for the report)
# v2.6 (2025-11-24)
- (new) serve on unix socket (thanks to @rvighne)
- (new) more auto-refresh options: 12h & 24h (thanks to @aswerkljh for suggestion)
- (fix) smooth scrolling on iOS (thanks to gatheraled)

View File

@@ -339,10 +339,10 @@
<span class="icon">{% inline "external-link.svg" %}</span>
</a>
<div class="flex-grow-1"></div>
<button class="toolbar-item" @click="navigateToItem(-1)" title="Previous Article" :disabled="itemSelected == items[0].id">
<button class="toolbar-item" @click="navigateToItem(-1)" title="Previous Article" :disabled="!items.length || itemSelected == items[0].id">
<span class="icon">{% inline "chevron-left.svg" %}</span>
</button>
<button class="toolbar-item" @click="navigateToItem(+1)" title="Next Article" :disabled="itemSelected == items[items.length - 1].id">
<button class="toolbar-item" @click="navigateToItem(+1)" title="Next Article" :disabled="!items.length || itemSelected == items[items.length - 1].id">
<span class="icon">{% inline "chevron-right.svg" %}</span>
</button>
<button class="toolbar-item" @click="itemSelected=null" title="Close Article">

View File

@@ -361,14 +361,18 @@ var vm = new Vue({
},
'filterSelected': function(newVal, oldVal) {
if (oldVal === undefined) return // do nothing, initial setup
api.settings.update({filter: newVal}).then(this.refreshItems.bind(this, false))
this.itemSelected = null
this.items = []
this.itemsHasMore = true
api.settings.update({filter: newVal}).then(this.refreshItems.bind(this, false))
this.computeStats()
},
'feedSelected': function(newVal, oldVal) {
if (oldVal === undefined) return // do nothing, initial setup
api.settings.update({feed: newVal}).then(this.refreshItems.bind(this, false))
this.itemSelected = null
this.items = []
this.itemsHasMore = true
api.settings.update({feed: newVal}).then(this.refreshItems.bind(this, false))
if (this.$refs.itemlist) this.$refs.itemlist.scrollTop = 0
},
'itemSelected': function(newVal, oldVal) {

View File

@@ -20,12 +20,12 @@ type rssFeed struct {
}
type rssItem struct {
GUID rssGuid `xml:"guid"`
Title string `xml:"title"`
GUID rssGuid `xml:"rss guid"`
Title string `xml:"rss title"`
Link string `xml:"rss link"`
Description string `xml:"rss description"`
PubDate string `xml:"pubDate"`
Enclosures []rssEnclosure `xml:"enclosure"`
PubDate string `xml:"rss pubDate"`
Enclosures []rssEnclosure `xml:"rss enclosure"`
DublinCoreDate string `xml:"http://purl.org/dc/elements/1.1/ date"`
ContentEncoded string `xml:"http://purl.org/rss/1.0/modules/content/ encoded"`
@@ -85,6 +85,11 @@ func ParseRSS(r io.Reader) (*Feed, error) {
break
}
}
for _, e := range srcitem.Enclosures {
if strings.HasPrefix(e.Type, "image/") {
mediaLinks = append(mediaLinks, MediaLink{URL: e.URL, Type: "image"})
}
}
permalink := ""
if srcitem.GUID.IsPermaLink == "true" {

View File

@@ -248,6 +248,35 @@ func TestRSSIsPermalink(t *testing.T) {
}
}
// https://github.com/nkanaev/yarr/issues/284
func TestRSSEnclosureImage(t *testing.T) {
feed, _ := Parse(strings.NewReader(`
<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
<channel>
<item>
<title>Post with image</title>
<link>http://example.com/post/1</link>
<enclosure url="http://example.com/photo.jpg" type="image/jpeg" length="123456"/>
</item>
</channel>
</rss>
`))
if len(feed.Items[0].MediaLinks) != 1 {
t.Fatalf("Expected 1 media link, got %d: %#v", len(feed.Items[0].MediaLinks), feed.Items[0].MediaLinks)
}
have := feed.Items[0].MediaLinks[0]
want := MediaLink{
URL: "http://example.com/photo.jpg",
Type: "image",
}
if !reflect.DeepEqual(want, have) {
t.Logf("want: %#v", want)
t.Logf("have: %#v", have)
t.FailNow()
}
}
func TestRSSMultipleMedia(t *testing.T) {
feed, _ := Parse(strings.NewReader(`
<?xml version="1.0" encoding="UTF-8"?>