mirror of
https://github.com/nkanaev/yarr.git
synced 2025-05-24 00:33:14 +00:00
show unread/starred count
This commit is contained in:
parent
ffd2deb5d8
commit
76a08df741
@ -45,7 +45,7 @@ func StaticHandler(rw http.ResponseWriter, req *http.Request) {
|
|||||||
func StatusHandler(rw http.ResponseWriter, req *http.Request) {
|
func StatusHandler(rw http.ResponseWriter, req *http.Request) {
|
||||||
writeJSON(rw, map[string]interface{}{
|
writeJSON(rw, map[string]interface{}{
|
||||||
"running": handler(req).fetchRunning,
|
"running": handler(req).fetchRunning,
|
||||||
"stats": map[string]int64{},
|
"stats": db(req).FeedStats(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -213,3 +213,31 @@ func (s *Storage) MarkItemsRead(filter ItemFilter) bool {
|
|||||||
}
|
}
|
||||||
return err == nil
|
return err == nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type FeedStat struct {
|
||||||
|
FeedId int64 `json:"feed_id"`
|
||||||
|
UnreadCount int64 `json:"unread"`
|
||||||
|
StarredCount int64 `json:"starred"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Storage) FeedStats() []FeedStat {
|
||||||
|
result := make([]FeedStat, 0)
|
||||||
|
rows, err := s.db.Query(fmt.Sprintf(`
|
||||||
|
select
|
||||||
|
feed_id,
|
||||||
|
sum(case status when %d then 1 else 0 end),
|
||||||
|
sum(case status when %d then 1 else 0 end)
|
||||||
|
from items
|
||||||
|
group by feed_id
|
||||||
|
`, UNREAD, STARRED))
|
||||||
|
if err != nil {
|
||||||
|
s.log.Print(err)
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
for rows.Next() {
|
||||||
|
stat := FeedStat{}
|
||||||
|
rows.Scan(&stat.FeedId, &stat.UnreadCount, &stat.StarredCount)
|
||||||
|
result = append(result, stat)
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
@ -25,7 +25,7 @@
|
|||||||
<div class="menu-item d-flex align-items-center w-100">
|
<div class="menu-item d-flex align-items-center w-100">
|
||||||
<img src="./static/images/circle.svg" alt="" class="nav-icon">
|
<img src="./static/images/circle.svg" alt="" class="nav-icon">
|
||||||
<span class="flex-fill text-left text-truncate">Unread</span>
|
<span class="flex-fill text-left text-truncate">Unread</span>
|
||||||
<span class="counter text-right"></span>
|
<span class="counter text-right">{{totalStats.unread || ''}}</span>
|
||||||
</div>
|
</div>
|
||||||
</label>
|
</label>
|
||||||
<label class="nav-select">
|
<label class="nav-select">
|
||||||
@ -33,7 +33,7 @@
|
|||||||
<div class="menu-item d-flex align-items-center w-100">
|
<div class="menu-item d-flex align-items-center w-100">
|
||||||
<img src="./static/images/star.svg" alt="" class="nav-icon">
|
<img src="./static/images/star.svg" alt="" class="nav-icon">
|
||||||
<span class="flex-fill text-left text-truncate">Starred</span>
|
<span class="flex-fill text-left text-truncate">Starred</span>
|
||||||
<span class="counter text-right"></span>
|
<span class="counter text-right">{{totalStats.starred || ''}}</span>
|
||||||
</div>
|
</div>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
@ -54,7 +54,7 @@
|
|||||||
:class="{expanded: folder.is_expanded}"
|
:class="{expanded: folder.is_expanded}"
|
||||||
@click.prevent="toggleFolderExpanded(folder)">
|
@click.prevent="toggleFolderExpanded(folder)">
|
||||||
<span class="flex-fill text-left text-truncate">{{ folder.title }}</span>
|
<span class="flex-fill text-left text-truncate">{{ folder.title }}</span>
|
||||||
<span class="counter text-right"></span>
|
<span class="counter text-right">{{filteredFolderStats[folder.id] || ''}}</span>
|
||||||
</div>
|
</div>
|
||||||
</label>
|
</label>
|
||||||
<div v-show="!folder.id || folder.is_expanded" class="mt-1" :class="{'pl-3': folder.id}">
|
<div v-show="!folder.id || folder.is_expanded" class="mt-1" :class="{'pl-3': folder.id}">
|
||||||
@ -64,7 +64,7 @@
|
|||||||
<div class="menu-item d-flex align-items-center w-100">
|
<div class="menu-item d-flex align-items-center w-100">
|
||||||
<img src="./static/images/rss.svg" alt="" class="nav-icon">
|
<img src="./static/images/rss.svg" alt="" class="nav-icon">
|
||||||
<span class="flex-fill text-left text-truncate">{{ feed.title }}</span>
|
<span class="flex-fill text-left text-truncate">{{ feed.title }}</span>
|
||||||
<span class="counter text-right"></span>
|
<span class="counter text-right">{{filteredFeedStats[feed.id] || ''}}</span>
|
||||||
</div>
|
</div>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
|
@ -74,6 +74,9 @@
|
|||||||
return api('put', '/api/settings', data)
|
return api('put', '/api/settings', data)
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
status: function() {
|
||||||
|
return api('get', '/api/status').then(json)
|
||||||
|
},
|
||||||
upload_opml: function(form) {
|
upload_opml: function(form) {
|
||||||
return fetch('/opml/import', {
|
return fetch('/opml/import', {
|
||||||
method: 'post',
|
method: 'post',
|
||||||
|
@ -28,6 +28,7 @@ var vm = new Vue({
|
|||||||
vm.refreshItems()
|
vm.refreshItems()
|
||||||
})
|
})
|
||||||
this.refreshFeeds()
|
this.refreshFeeds()
|
||||||
|
this.refreshStats()
|
||||||
},
|
},
|
||||||
data: function() {
|
data: function() {
|
||||||
return {
|
return {
|
||||||
@ -47,6 +48,7 @@ var vm = new Vue({
|
|||||||
'newfeed': false,
|
'newfeed': false,
|
||||||
'items': false,
|
'items': false,
|
||||||
},
|
},
|
||||||
|
'feedStats': {},
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
@ -71,6 +73,34 @@ var vm = new Vue({
|
|||||||
itemsById: function() {
|
itemsById: function() {
|
||||||
return this.items.reduce(function(acc, item) { acc[item.id] = item; return acc }, {})
|
return this.items.reduce(function(acc, item) { acc[item.id] = item; return acc }, {})
|
||||||
},
|
},
|
||||||
|
filteredFeedStats: function() {
|
||||||
|
var filter = this.filterSelected
|
||||||
|
if (filter != 'unread' && filter != 'starred') return {}
|
||||||
|
|
||||||
|
var feedStats = this.feedStats
|
||||||
|
return this.feeds.reduce(function(acc, feed) {
|
||||||
|
if (feedStats[feed.id]) acc[feed.id] = vm.feedStats[feed.id][filter]
|
||||||
|
return acc
|
||||||
|
}, {})
|
||||||
|
},
|
||||||
|
filteredFolderStats: function() {
|
||||||
|
var filter = this.filterSelected
|
||||||
|
if (filter != 'unread' && filter != 'starred') return {}
|
||||||
|
|
||||||
|
var feedStats = this.filteredFeedStats
|
||||||
|
return this.feeds.reduce(function(acc, feed) {
|
||||||
|
if (!acc[feed.folder_id]) acc[feed.folder_id] = 0
|
||||||
|
if (feedStats[feed.id]) acc[feed.folder_id] += feedStats[feed.id]
|
||||||
|
return acc
|
||||||
|
}, {})
|
||||||
|
},
|
||||||
|
totalStats: function() {
|
||||||
|
return Object.values(this.feedStats).reduce(function(acc, stat) {
|
||||||
|
acc.unread += stat.unread
|
||||||
|
acc.starred += stat.starred
|
||||||
|
return acc
|
||||||
|
}, {unread: 0, starred: 0})
|
||||||
|
},
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
'filterSelected': function(newVal, oldVal) {
|
'filterSelected': function(newVal, oldVal) {
|
||||||
@ -87,11 +117,21 @@ var vm = new Vue({
|
|||||||
this.itemSelectedDetails = this.itemsById[newVal]
|
this.itemSelectedDetails = this.itemsById[newVal]
|
||||||
if (this.itemSelectedDetails.status == 'unread') {
|
if (this.itemSelectedDetails.status == 'unread') {
|
||||||
this.itemSelectedDetails.status = 'read'
|
this.itemSelectedDetails.status = 'read'
|
||||||
|
this.feedStats[this.itemSelectedDetails.feed_id].unread -= 1
|
||||||
api.items.update(this.itemSelectedDetails.id, {status: this.itemSelectedDetails.status})
|
api.items.update(this.itemSelectedDetails.id, {status: this.itemSelectedDetails.status})
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
refreshStats: function() {
|
||||||
|
var vm = this
|
||||||
|
api.status().then(function(data) {
|
||||||
|
vm.feedStats = data.stats.reduce(function(acc, stat) {
|
||||||
|
acc[stat.feed_id] = stat
|
||||||
|
return acc
|
||||||
|
}, {})
|
||||||
|
})
|
||||||
|
},
|
||||||
getItemsQuery: function() {
|
getItemsQuery: function() {
|
||||||
var query = {}
|
var query = {}
|
||||||
if (this.feedSelected) {
|
if (this.feedSelected) {
|
||||||
@ -148,6 +188,7 @@ var vm = new Vue({
|
|||||||
var query = this.getItemsQuery()
|
var query = this.getItemsQuery()
|
||||||
api.items.mark_read(query).then(function() {
|
api.items.mark_read(query).then(function() {
|
||||||
vm.items = []
|
vm.items = []
|
||||||
|
vm.refreshStats()
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
toggleFolderExpanded: function(folder) {
|
toggleFolderExpanded: function(folder) {
|
||||||
@ -227,16 +268,20 @@ var vm = new Vue({
|
|||||||
toggleItemStarred: function(item) {
|
toggleItemStarred: function(item) {
|
||||||
if (item.status == 'starred') {
|
if (item.status == 'starred') {
|
||||||
item.status = 'read'
|
item.status = 'read'
|
||||||
|
this.feedStats[item.feed_id].starred -= 1
|
||||||
} else if (item.status != 'starred') {
|
} else if (item.status != 'starred') {
|
||||||
item.status = 'starred'
|
item.status = 'starred'
|
||||||
|
this.feedStats[item.feed_id].starred += 1
|
||||||
}
|
}
|
||||||
api.items.update(item.id, {status: item.status})
|
api.items.update(item.id, {status: item.status})
|
||||||
},
|
},
|
||||||
toggleItemRead: function(item) {
|
toggleItemRead: function(item) {
|
||||||
if (item.status == 'unread') {
|
if (item.status == 'unread') {
|
||||||
item.status = 'read'
|
item.status = 'read'
|
||||||
|
this.feedStats[item.feed_id].unread -= 1
|
||||||
} else if (item.status == 'read') {
|
} else if (item.status == 'read') {
|
||||||
item.status = 'unread'
|
item.status = 'unread'
|
||||||
|
this.feedStats[item.feed_id].unread += 1
|
||||||
}
|
}
|
||||||
api.items.update(item.id, {status: item.status})
|
api.items.update(item.id, {status: item.status})
|
||||||
},
|
},
|
||||||
|
Loading…
x
Reference in New Issue
Block a user