Compare commits
No commits in common. "9a25332dedd0c90bd18ba8e162b586e9370ef564" and "ef79ee4997460d226246d8f7039ea8fb21168c2c" have entirely different histories.
9a25332ded
...
ef79ee4997
@ -23,9 +23,6 @@ func Refresh() error {
|
|||||||
go RefreshFeed(feed.FeedURL)
|
go RefreshFeed(feed.FeedURL)
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Println("Reaping old items...")
|
|
||||||
feedStore.DeleteOldReadItems()
|
|
||||||
|
|
||||||
fmt.Printf("Going to sleep for %d minutes\n", interval)
|
fmt.Printf("Going to sleep for %d minutes\n", interval)
|
||||||
time.Sleep(time.Duration(interval) * time.Minute)
|
time.Sleep(time.Duration(interval) * time.Minute)
|
||||||
}
|
}
|
||||||
|
@ -1,8 +1,6 @@
|
|||||||
package feeds
|
package feeds
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/spf13/viper"
|
"github.com/spf13/viper"
|
||||||
"gorm.io/driver/sqlite"
|
"gorm.io/driver/sqlite"
|
||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
@ -57,37 +55,6 @@ func (fs *FeedStore) GetUnread() *[]ItemWithFeed {
|
|||||||
return items
|
return items
|
||||||
}
|
}
|
||||||
|
|
||||||
func (fs *FeedStore) GetSaved() *[]ItemWithFeed {
|
|
||||||
items := &[]ItemWithFeed{}
|
|
||||||
fs.getDB().Table("items").
|
|
||||||
Where("save = ?", true).
|
|
||||||
Select("items.*, feeds.title as feed_title, feeds.homepage_url as feed_homepage_url").
|
|
||||||
Order("items.created desc, items.title").
|
|
||||||
Joins("left join feeds on feeds.id = items.feed_id").
|
|
||||||
Find(items)
|
|
||||||
|
|
||||||
return items
|
|
||||||
}
|
|
||||||
|
|
||||||
func (fs *FeedStore) DeleteOldReadItems() {
|
|
||||||
t := time.Now()
|
|
||||||
threshold := t.Add(-time.Hour * 24 * 7)
|
|
||||||
fs.getDB().Table("items").
|
|
||||||
Where("save = ? and read = ? and created < ?", false, true, threshold).
|
|
||||||
Delete(Item{})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (fs *FeedStore) GetAll() *[]ItemWithFeed {
|
|
||||||
items := &[]ItemWithFeed{}
|
|
||||||
fs.getDB().Table("items").
|
|
||||||
Select("items.*, feeds.title as feed_title, feeds.homepage_url as feed_homepage_url").
|
|
||||||
Order("items.created desc, items.title").
|
|
||||||
Joins("left join feeds on feeds.id = items.feed_id").
|
|
||||||
Find(items)
|
|
||||||
|
|
||||||
return items
|
|
||||||
}
|
|
||||||
|
|
||||||
func (fs *FeedStore) SaveFeed(feed Feed) {
|
func (fs *FeedStore) SaveFeed(feed Feed) {
|
||||||
fs.getDB().Omit("Items").Clauses(clause.OnConflict{
|
fs.getDB().Omit("Items").Clauses(clause.OnConflict{
|
||||||
Columns: []clause.Column{{Name: "id"}},
|
Columns: []clause.Column{{Name: "id"}},
|
||||||
@ -110,12 +77,3 @@ func (fs *FeedStore) MarkAsRead(itemID string) {
|
|||||||
|
|
||||||
fs.getDB().Save(*item)
|
fs.getDB().Save(*item)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (fs *FeedStore) ToggleSaved(itemID string) {
|
|
||||||
item := &Item{}
|
|
||||||
fs.getDB().Where("id = ?", itemID).First(item)
|
|
||||||
|
|
||||||
item.Save = !item.Save
|
|
||||||
|
|
||||||
fs.getDB().Save(*item)
|
|
||||||
}
|
|
||||||
|
@ -46,7 +46,6 @@ type Item struct {
|
|||||||
FeedID string
|
FeedID string
|
||||||
Read bool
|
Read bool
|
||||||
Save bool
|
Save bool
|
||||||
DeletedAt gorm.DeletedAt
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type ItemWithFeed struct {
|
type ItemWithFeed struct {
|
||||||
|
@ -33,14 +33,6 @@ func (a *API) GetUnread(c *fiber.Ctx) error {
|
|||||||
return c.JSON(a.FeedStore.GetUnread())
|
return c.JSON(a.FeedStore.GetUnread())
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *API) GetSaved(c *fiber.Ctx) error {
|
|
||||||
return c.JSON(a.FeedStore.GetSaved())
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *API) GetAll(c *fiber.Ctx) error {
|
|
||||||
return c.JSON(a.FeedStore.GetAll())
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *API) PostRead(c *fiber.Ctx) error {
|
func (a *API) PostRead(c *fiber.Ctx) error {
|
||||||
a.FeedStore.MarkAsRead(c.Params("id"))
|
a.FeedStore.MarkAsRead(c.Params("id"))
|
||||||
return nil
|
return nil
|
||||||
@ -64,8 +56,3 @@ func (a *API) RefreshAll(c *fiber.Ctx) error {
|
|||||||
|
|
||||||
return c.JSON(a.FeedStore.GetUnread())
|
return c.JSON(a.FeedStore.GetUnread())
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *API) SaveItem(c *fiber.Ctx) error {
|
|
||||||
a.FeedStore.ToggleSaved(c.Params("id"))
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
@ -51,10 +51,7 @@ func Start(port string) error {
|
|||||||
app.Post("/api/feeds", api.PostFeed)
|
app.Post("/api/feeds", api.PostFeed)
|
||||||
app.Get("/api/feed/:id", api.GetFeed)
|
app.Get("/api/feed/:id", api.GetFeed)
|
||||||
app.Get("/api/item/:id", api.GetItem)
|
app.Get("/api/item/:id", api.GetItem)
|
||||||
app.Post("/api/item/:id/save", api.SaveItem)
|
|
||||||
app.Get("/api/unread", api.GetUnread)
|
app.Get("/api/unread", api.GetUnread)
|
||||||
app.Get("/api/saved", api.GetSaved)
|
|
||||||
app.Get("/api/all", api.GetAll)
|
|
||||||
app.Post("/api/read/:id", api.PostRead)
|
app.Post("/api/read/:id", api.PostRead)
|
||||||
app.Post("/api/read", api.PostReadAll)
|
app.Post("/api/read", api.PostReadAll)
|
||||||
app.Get("/api/refresh", api.RefreshAll)
|
app.Get("/api/refresh", api.RefreshAll)
|
||||||
|
@ -4,17 +4,6 @@
|
|||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
<title>Gopherss</title>
|
<title>Gopherss</title>
|
||||||
|
|
||||||
<meta name="apple-mobile-web-app-capable" content="yes">
|
|
||||||
<!-- <link rel="apple-touch-icon" href="static/icon.png"> -->
|
|
||||||
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
|
|
||||||
<link rel="manifest" href="/manifest.json">
|
|
||||||
<script>
|
|
||||||
if ('serviceWorker' in navigator) {
|
|
||||||
navigator.serviceWorker.register('/sw.js');
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<script src="/static/feed-item.js" defer></script>
|
<script src="/static/feed-item.js" defer></script>
|
||||||
|
|
||||||
<script src="https://unpkg.com/vue@2.5.17/dist/vue.min.js"></script>
|
<script src="https://unpkg.com/vue@2.5.17/dist/vue.min.js"></script>
|
||||||
@ -37,9 +26,6 @@
|
|||||||
|
|
||||||
<div id="app">
|
<div id="app">
|
||||||
<div class="menu">
|
<div class="menu">
|
||||||
<button title="Show Read" v-on:click="toggleShowRead()">
|
|
||||||
<svg width="30" height="30" viewbox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M12 9a4 4 0 110 8 4 4 0 010-8zm0-3.5a10 10 0 019.7 7.6.8.8 0 01-1.5.3 8.5 8.5 0 00-16.4 0 .8.8 0 01-1.5-.3A10 10 0 0112 5.5z" fill="#212121" fill-rule="nonzero"/></svg>
|
|
||||||
</button>
|
|
||||||
<button title="Toggle dark mode" v-on:click="toggleDarkMode()">
|
<button title="Toggle dark mode" v-on:click="toggleDarkMode()">
|
||||||
<svg width="30" height="30" viewbox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><path d="M12 22a10 10 0 100-20 10 10 0 000 20zm0-2V4a8 8 0 110 16z" fill="#212121" fill-rule="nonzero"/></svg>
|
<svg width="30" height="30" viewbox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><path d="M12 22a10 10 0 100-20 10 10 0 000 20zm0-2V4a8 8 0 110 16z" fill="#212121" fill-rule="nonzero"/></svg>
|
||||||
</button>
|
</button>
|
||||||
@ -57,12 +43,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-for="feed in feeds" :class="{strong: unreadCounts[feed.ID], 'alert': true, 'alert-success': selectedFeed == feed.FeedURL }" :data-feed="feed.FeedURL" v-on:click="loadFeed(feed.ID)">
|
<div v-for="feed in feeds" :class="{strong: unreadCounts[feed.ID], 'alert': true, 'alert-success': selectedFeed == feed.FeedURL }" :data-feed="feed.FeedURL" v-on:click="loadFeed(feed.ID)">
|
||||||
{{feed.Title}} ({{unreadCounts[feed.ID] || '0'}})
|
{{feed.Title}} ({{unreadCounts[feed.ID]}})
|
||||||
</div>
|
|
||||||
|
|
||||||
<div :class="{ strong: items.length, alert: true, 'alert-success': selectedFeed == 'SAVED'}" v-on:click="loadFeed('SAVED')">
|
|
||||||
<svg viewBox="0 0 24 24" width="18" height="18" xmlns="http://www.w3.org/2000/svg"><path d="M12.8 5.6l-.8.8-.8-.8a5.4 5.4 0 00-7.6 7.6l7.9 7.9c.3.3.7.3 1 0l8-8a5.4 5.4 0 10-7.7-7.5z" style="fill: #ff2e88" fill-rule="nonzero"/></svg>
|
|
||||||
Saved ({{saved}})
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="menu">
|
<div class="menu">
|
||||||
@ -105,12 +86,6 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="card item-content" :data-id="item.ID" v-if="item.ID == selectedItem">
|
<div class="card item-content" :data-id="item.ID" v-if="item.ID == selectedItem">
|
||||||
<div class="card-content">
|
<div class="card-content">
|
||||||
<div class="menu">
|
|
||||||
<button title="Save" v-on:click="saveItem(item)" :disabled="isBusy">
|
|
||||||
<svg width="24" height="24" xmlns="http://www.w3.org/2000/svg"><path d="M12.8 5.6l-.8.8-.8-.8a5.4 5.4 0 00-7.6 7.6l7.9 7.9c.3.3.7.3 1 0l8-8a5.4 5.4 0 10-7.7-7.5z" :style="{'fill': item.Save ? '#ff2e88' : '#eee' }" fill-rule="nonzero"/></svg>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<feed-item :item-id="item.ID" :class="{ dark: isDark }"></feed-item>
|
<feed-item :item-id="item.ID" :class="{ dark: isDark }"></feed-item>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -131,7 +106,6 @@
|
|||||||
data: {
|
data: {
|
||||||
feeds: [],
|
feeds: [],
|
||||||
items: [],
|
items: [],
|
||||||
savedItems: [],
|
|
||||||
selectedFeed: '',
|
selectedFeed: '',
|
||||||
selectedItem: undefined,
|
selectedItem: undefined,
|
||||||
showAddModal: false,
|
showAddModal: false,
|
||||||
@ -139,24 +113,18 @@
|
|||||||
opml: '',
|
opml: '',
|
||||||
isBusy: false,
|
isBusy: false,
|
||||||
isDark: false,
|
isDark: false,
|
||||||
showRead: false,
|
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
shownItems() {
|
shownItems() {
|
||||||
if (this.selectedFeed === '') {
|
if (this.selectedFeed === '') {
|
||||||
return this.items.filter(item => item.ID == this.selectedItem || !item.Read || item.Read === this.showRead);
|
return this.items;
|
||||||
} else if (this.selectedFeed === 'SAVED') {
|
|
||||||
return this.savedItems;
|
|
||||||
} else {
|
} else {
|
||||||
return this.items.filter(item => item.ID == this.selectedItem || item.FeedID === this.selectedFeed && (!item.Read || item.Read === this.showRead));
|
return this.items.filter(item => item.FeedID === this.selectedFeed);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
unread() {
|
unread() {
|
||||||
return this.items.filter(item => !item.Read).length;
|
return this.items.filter(item => !item.Read).length;
|
||||||
},
|
},
|
||||||
saved() {
|
|
||||||
return this.savedItems.length;
|
|
||||||
},
|
|
||||||
unreadCounts() {
|
unreadCounts() {
|
||||||
return this.items.filter(item => !item.Read).reduce((acc, item) => {
|
return this.items.filter(item => !item.Read).reduce((acc, item) => {
|
||||||
if (!acc[item.FeedID]) acc[item.FeedID] = 0;
|
if (!acc[item.FeedID]) acc[item.FeedID] = 0;
|
||||||
@ -184,25 +152,13 @@
|
|||||||
this.selectedItem = undefined;
|
this.selectedItem = undefined;
|
||||||
} else {
|
} else {
|
||||||
this.selectedItem = item.ID;
|
this.selectedItem = item.ID;
|
||||||
|
|
||||||
|
// document.querySelector(`feed-item[data-id='${item.ID}']`).content = item.Content || item.Description;
|
||||||
document.getElementById(this.selectedItem).scrollIntoView();
|
document.getElementById(this.selectedItem).scrollIntoView();
|
||||||
item.Read = true;
|
item.Read = true;
|
||||||
fetch(`/api/read/${item.ID}`, {method: "POST"})
|
fetch(`/api/read/${item.ID}`, {method: "POST"})
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
saveItem(item) {
|
|
||||||
this.setBusy(true);
|
|
||||||
fetch(`/api/item/${item.ID}/save`, {method: "POST"})
|
|
||||||
.then(() => {
|
|
||||||
item.Save = !item.Save;
|
|
||||||
|
|
||||||
if (item.Save) {
|
|
||||||
this.savedItems.push(item);
|
|
||||||
} else {
|
|
||||||
this.savedItems = this.savedItems.filter(i => item.ID != i.ID);
|
|
||||||
}
|
|
||||||
this.setBusy(false);
|
|
||||||
})
|
|
||||||
},
|
|
||||||
nextItem() {
|
nextItem() {
|
||||||
let currentItem = -1;
|
let currentItem = -1;
|
||||||
if (this.selectedItem != undefined) {
|
if (this.selectedItem != undefined) {
|
||||||
@ -234,13 +190,14 @@
|
|||||||
},
|
},
|
||||||
markAllRead() {
|
markAllRead() {
|
||||||
let ids = this.shownItems.filter(item => !item.Read).map(item => item.ID);
|
let ids = this.shownItems.filter(item => !item.Read).map(item => item.ID);
|
||||||
if (ids.length > 0 && confirm(`Are you sure you want to mark ${ids.length} items as read?`)) {
|
if (confirm(`Are you sure you want to mark ${ids.length} items as read?`)) {
|
||||||
this.setBusy(true);
|
this.setBusy(true);
|
||||||
this.shownItems.filter(item => !item.Read).forEach(item => item.Read = true)
|
|
||||||
fetch(
|
fetch(
|
||||||
`/api/read`,
|
`/api/read`,
|
||||||
{method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(ids)}
|
{method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(ids)}
|
||||||
)
|
)
|
||||||
|
.then(res => res.json())
|
||||||
|
.then(items => this.items = items)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
this.setBusy(false);
|
this.setBusy(false);
|
||||||
})
|
})
|
||||||
@ -304,29 +261,13 @@
|
|||||||
console.error(err);
|
console.error(err);
|
||||||
this.setBusy(false);
|
this.setBusy(false);
|
||||||
});
|
});
|
||||||
},
|
|
||||||
toggleShowRead() {
|
|
||||||
this.showRead = !this.showRead;
|
|
||||||
|
|
||||||
if (this.showRead && !this.items.some(item => item.Read)) {
|
|
||||||
this.setBusy(true);
|
|
||||||
fetch('/api/all').then(res => res.json()).then(items => this.items = items)
|
|
||||||
.then(() => {
|
|
||||||
this.setBusy(false);
|
|
||||||
})
|
|
||||||
.catch(err => {
|
|
||||||
console.error(err);
|
|
||||||
this.setBusy(false);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
created() {
|
created() {
|
||||||
this.setBusy(true);
|
this.setBusy(true);
|
||||||
Promise.all([
|
Promise.all([
|
||||||
fetch(`/api/feeds`).then(res => res.json()).then(feeds => this.feeds = feeds),
|
fetch(`/api/feeds`).then(res => res.json()).then(feeds => this.feeds = feeds),
|
||||||
fetch(`/api/unread`).then(res => res.json()).then(items => this.items = items),
|
fetch(`/api/unread`).then(res => res.json()).then(items => this.items = items)
|
||||||
fetch(`/api/saved`).then(res => res.json()).then(items => this.savedItems = items)
|
|
||||||
])
|
])
|
||||||
.then(() => {
|
.then(() => {
|
||||||
this.setBusy(false);
|
this.setBusy(false);
|
||||||
|
@ -1,13 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "Gopherss",
|
|
||||||
"short_name": "Gopherss",
|
|
||||||
"description": "RSS reader built in Go",
|
|
||||||
"theme_color": "#282626",
|
|
||||||
"background_color": "#282626",
|
|
||||||
"display": "standalone",
|
|
||||||
"scope": "/",
|
|
||||||
"start_url": "/",
|
|
||||||
"icons": [
|
|
||||||
],
|
|
||||||
"splash_pages": null
|
|
||||||
}
|
|
@ -21,22 +21,20 @@ class FeedItem extends HTMLElement {
|
|||||||
width: 100% !important;
|
width: 100% !important;
|
||||||
overflow: scroll !important;
|
overflow: scroll !important;
|
||||||
overflow-x: auto !important;
|
overflow-x: auto !important;
|
||||||
font-size: 18px;
|
|
||||||
}
|
}
|
||||||
* {
|
* {
|
||||||
max-width: 100% !important;
|
max-width: 100% !important;
|
||||||
height: auto !important;
|
height: auto !important;
|
||||||
float: none !important;
|
|
||||||
}
|
}
|
||||||
table {
|
table {
|
||||||
width: 100% !important;
|
width: 100% !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
img {
|
img {
|
||||||
margin: auto auto !important;
|
margin: auto auto !important;
|
||||||
}
|
}
|
||||||
p {
|
p {
|
||||||
font-family: charter, Georgia, "Times New Roman", Times, serif;
|
font-family: charter, Georgia, "Times New Roman", Times, serif;
|
||||||
|
font-size: 21px;
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
letter-spacing: -0.063px;
|
letter-spacing: -0.063px;
|
||||||
@ -49,9 +47,6 @@ class FeedItem extends HTMLElement {
|
|||||||
:host(.dark) a {
|
:host(.dark) a {
|
||||||
color: #ccc;
|
color: #ccc;
|
||||||
}
|
}
|
||||||
a:hover, :host(.dark) a:hover {
|
|
||||||
color: #ff2e88;
|
|
||||||
}
|
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
`;
|
`;
|
||||||
|
@ -1,6 +1,3 @@
|
|||||||
body {
|
|
||||||
padding-bottom: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.hide {
|
.hide {
|
||||||
display: none;
|
display: none;
|
||||||
|
File diff suppressed because one or more lines are too long
@ -1 +0,0 @@
|
|||||||
importScripts('/static/sw-toolbox.js');
|
|
Loading…
Reference in New Issue
Block a user