mirror of
https://github.com/nkanaev/yarr.git
synced 2026-03-26 13:47:43 +00:00
Compare commits
8 Commits
v2.6
...
55b9b4a38b
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
55b9b4a38b | ||
|
|
e916fdbe6c | ||
|
|
0e3df33d1f | ||
|
|
506fe1cae6 | ||
|
|
1d97314825 | ||
|
|
e1ecb6760b | ||
|
|
953f560a11 | ||
|
|
3d69911aa8 |
8
.github/workflows/build-docker.yml
vendored
8
.github/workflows/build-docker.yml
vendored
@@ -3,6 +3,8 @@ on:
|
|||||||
push:
|
push:
|
||||||
tags:
|
tags:
|
||||||
- v*
|
- v*
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
env:
|
env:
|
||||||
REGISTRY: ghcr.io
|
REGISTRY: ghcr.io
|
||||||
IMAGE_NAME: nkanaev/yarr
|
IMAGE_NAME: nkanaev/yarr
|
||||||
@@ -17,6 +19,12 @@ jobs:
|
|||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v4
|
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
|
- name: Log in to the Container registry
|
||||||
uses: docker/login-action@v3
|
uses: docker/login-action@v3
|
||||||
with:
|
with:
|
||||||
|
|||||||
@@ -1,5 +1,12 @@
|
|||||||
# upcoming
|
# 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) serve on unix socket (thanks to @rvighne)
|
||||||
- (new) more auto-refresh options: 12h & 24h (thanks to @aswerkljh for suggestion)
|
- (new) more auto-refresh options: 12h & 24h (thanks to @aswerkljh for suggestion)
|
||||||
- (fix) smooth scrolling on iOS (thanks to gatheraled)
|
- (fix) smooth scrolling on iOS (thanks to gatheraled)
|
||||||
|
|||||||
@@ -339,10 +339,10 @@
|
|||||||
<span class="icon">{% inline "external-link.svg" %}</span>
|
<span class="icon">{% inline "external-link.svg" %}</span>
|
||||||
</a>
|
</a>
|
||||||
<div class="flex-grow-1"></div>
|
<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>
|
<span class="icon">{% inline "chevron-left.svg" %}</span>
|
||||||
</button>
|
</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>
|
<span class="icon">{% inline "chevron-right.svg" %}</span>
|
||||||
</button>
|
</button>
|
||||||
<button class="toolbar-item" @click="itemSelected=null" title="Close Article">
|
<button class="toolbar-item" @click="itemSelected=null" title="Close Article">
|
||||||
|
|||||||
@@ -361,14 +361,18 @@ var vm = new Vue({
|
|||||||
},
|
},
|
||||||
'filterSelected': function(newVal, oldVal) {
|
'filterSelected': function(newVal, oldVal) {
|
||||||
if (oldVal === undefined) return // do nothing, initial setup
|
if (oldVal === undefined) return // do nothing, initial setup
|
||||||
api.settings.update({filter: newVal}).then(this.refreshItems.bind(this, false))
|
|
||||||
this.itemSelected = null
|
this.itemSelected = null
|
||||||
|
this.items = []
|
||||||
|
this.itemsHasMore = true
|
||||||
|
api.settings.update({filter: newVal}).then(this.refreshItems.bind(this, false))
|
||||||
this.computeStats()
|
this.computeStats()
|
||||||
},
|
},
|
||||||
'feedSelected': function(newVal, oldVal) {
|
'feedSelected': function(newVal, oldVal) {
|
||||||
if (oldVal === undefined) return // do nothing, initial setup
|
if (oldVal === undefined) return // do nothing, initial setup
|
||||||
api.settings.update({feed: newVal}).then(this.refreshItems.bind(this, false))
|
|
||||||
this.itemSelected = null
|
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
|
if (this.$refs.itemlist) this.$refs.itemlist.scrollTop = 0
|
||||||
},
|
},
|
||||||
'itemSelected': function(newVal, oldVal) {
|
'itemSelected': function(newVal, oldVal) {
|
||||||
|
|||||||
@@ -20,12 +20,12 @@ type rssFeed struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type rssItem struct {
|
type rssItem struct {
|
||||||
GUID rssGuid `xml:"guid"`
|
GUID rssGuid `xml:"rss guid"`
|
||||||
Title string `xml:"title"`
|
Title string `xml:"rss title"`
|
||||||
Link string `xml:"rss link"`
|
Link string `xml:"rss link"`
|
||||||
Description string `xml:"rss description"`
|
Description string `xml:"rss description"`
|
||||||
PubDate string `xml:"pubDate"`
|
PubDate string `xml:"rss pubDate"`
|
||||||
Enclosures []rssEnclosure `xml:"enclosure"`
|
Enclosures []rssEnclosure `xml:"rss enclosure"`
|
||||||
|
|
||||||
DublinCoreDate string `xml:"http://purl.org/dc/elements/1.1/ date"`
|
DublinCoreDate string `xml:"http://purl.org/dc/elements/1.1/ date"`
|
||||||
ContentEncoded string `xml:"http://purl.org/rss/1.0/modules/content/ encoded"`
|
ContentEncoded string `xml:"http://purl.org/rss/1.0/modules/content/ encoded"`
|
||||||
@@ -85,6 +85,11 @@ func ParseRSS(r io.Reader) (*Feed, error) {
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
for _, e := range srcitem.Enclosures {
|
||||||
|
if strings.HasPrefix(e.Type, "image/") {
|
||||||
|
mediaLinks = append(mediaLinks, MediaLink{URL: e.URL, Type: "image"})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
permalink := ""
|
permalink := ""
|
||||||
if srcitem.GUID.IsPermaLink == "true" {
|
if srcitem.GUID.IsPermaLink == "true" {
|
||||||
|
|||||||
@@ -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) {
|
func TestRSSMultipleMedia(t *testing.T) {
|
||||||
feed, _ := Parse(strings.NewReader(`
|
feed, _ := Parse(strings.NewReader(`
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
|||||||
Reference in New Issue
Block a user