diff --git a/src/assets/index.html b/src/assets/index.html
index 969917f..25a5f23 100644
--- a/src/assets/index.html
+++ b/src/assets/index.html
@@ -260,7 +260,7 @@
{{ item.title || 'untitled' }}
-
+
{{ feed_errors[current.feed.id] }}
diff --git a/src/assets/javascripts/app.js b/src/assets/javascripts/app.js
index cb8f67f..5a629c6 100644
--- a/src/assets/javascripts/app.js
+++ b/src/assets/javascripts/app.js
@@ -180,7 +180,7 @@ var vm = new Vue({
created: function() {
this.refreshStats()
.then(this.refreshFeeds.bind(this))
- .then(this.refreshItems.bind(this))
+ .then(this.refreshItems.bind(this, false))
api.feeds.list_errors().then(function(errors) {
vm.feed_errors = errors
@@ -197,10 +197,7 @@ var vm = new Vue({
'feedNewChoice': [],
'feedNewChoiceSelected': '',
'items': [],
- 'itemsPage': {
- 'cur': 1,
- 'num': 1,
- },
+ 'itemsHasMore': true,
'itemSelected': null,
'itemSelectedDetails': null,
'itemSelectedReadability': '',
@@ -304,13 +301,13 @@ 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))
+ api.settings.update({filter: newVal}).then(this.refreshItems.bind(this, false))
this.itemSelected = null
this.computeStats()
},
'feedSelected': function(newVal, oldVal) {
if (oldVal === undefined) return // do nothing, initial setup
- api.settings.update({feed: newVal}).then(this.refreshItems.bind(this))
+ api.settings.update({feed: newVal}).then(this.refreshItems.bind(this, false))
this.itemSelected = null
if (this.$refs.itemlist) this.$refs.itemlist.scrollTop = 0
},
@@ -339,7 +336,7 @@ var vm = new Vue({
}, 500),
'itemSortNewestFirst': function(newVal, oldVal) {
if (oldVal === undefined) return // do nothing, initial setup
- api.settings.update({sort_newest_first: newVal}).then(this.refreshItems.bind(this))
+ api.settings.update({sort_newest_first: newVal}).then(vm.refreshItems.bind(this, false))
},
'feedListWidth': debounce(function(newVal, oldVal) {
if (oldVal === undefined) return // do nothing, initial setup
@@ -404,34 +401,34 @@ var vm = new Vue({
vm.feeds = values[1]
})
},
- refreshItems: function() {
+ refreshItems: function(loadMore) {
if (this.feedSelected === null) {
vm.items = []
- vm.itemsPage = {'cur': 1, 'num': 1}
return
}
+
var query = this.getItemsQuery()
+ if (loadMore) {
+ query.after = vm.items[vm.items.length-1].id
+ }
+
this.loading.items = true
return api.items.list(query).then(function(data) {
- vm.items = data.list
- vm.itemsPage = data.page
+ if (loadMore) {
+ vm.items = vm.items.concat(data.list)
+ } else {
+ vm.items = data.list
+ }
+ vm.itemsHasMore = data.has_more
vm.loading.items = false
})
},
loadMoreItems: function(event, el) {
- if (this.itemsPage.cur >= this.itemsPage.num) return
+ if (!this.itemsHasMore) return
+
if (this.loading.items) return
var closeToBottom = (el.scrollHeight - el.scrollTop - el.offsetHeight) < 50
- if (closeToBottom) {
- this.loading.moreitems = true
- var query = this.getItemsQuery()
- query.page = this.itemsPage.cur + 1
- api.items.list(query).then(function(data) {
- vm.items = vm.items.concat(data.list)
- vm.itemsPage = data.page
- vm.loading.items = false
- })
- }
+ if (closeToBottom) this.refreshItems(true)
},
markItemsRead: function() {
var query = this.getItemsQuery()
diff --git a/src/server/routes.go b/src/server/routes.go
index 4529dc6..97d14ea 100644
--- a/src/server/routes.go
+++ b/src/server/routes.go
@@ -5,7 +5,6 @@ import (
"encoding/json"
"fmt"
"log"
- "math"
"net/http"
"path/filepath"
"reflect"
@@ -307,11 +306,8 @@ func (s *Server) handleItem(c *router.Context) {
func (s *Server) handleItemList(c *router.Context) {
if c.Req.Method == "GET" {
perPage := 20
- curPage := 1
query := c.Req.URL.Query()
- if page, err := c.QueryInt64("page"); err == nil {
- curPage = int(page)
- }
+
filter := storage.ItemFilter{}
if folderID, err := c.QueryInt64("folder_id"); err == nil {
filter.FolderID = &folderID
@@ -319,6 +315,9 @@ func (s *Server) handleItemList(c *router.Context) {
if feedID, err := c.QueryInt64("feed_id"); err == nil {
filter.FeedID = &feedID
}
+ if after, err := c.QueryInt64("after"); err == nil {
+ filter.After = &after
+ }
if status := query.Get("status"); len(status) != 0 {
statusValue := storage.StatusValues[status]
filter.Status = &statusValue
@@ -327,14 +326,16 @@ func (s *Server) handleItemList(c *router.Context) {
filter.Search = &search
}
newestFirst := query.Get("oldest_first") != "true"
- items := s.db.ListItems(filter, (curPage-1)*perPage, perPage, newestFirst)
- count := s.db.CountItems(filter)
+
+ items := s.db.ListItems(filter, perPage+1, newestFirst)
+ hasMore := false
+ if len(items) == perPage+1 {
+ hasMore = true
+ items = items[:perPage]
+ }
c.JSON(http.StatusOK, map[string]interface{}{
- "page": map[string]int{
- "cur": curPage,
- "num": int(math.Ceil(float64(count) / float64(perPage))),
- },
"list": items,
+ "has_more": hasMore,
})
} else if c.Req.Method == "PUT" {
filter := storage.MarkFilter{}
diff --git a/src/storage/item.go b/src/storage/item.go
index bda988f..59227a2 100644
--- a/src/storage/item.go
+++ b/src/storage/item.go
@@ -61,6 +61,7 @@ type ItemFilter struct {
FeedID *int64
Status *ItemStatus
Search *string
+ After *int64
}
type MarkFilter struct {
@@ -106,7 +107,7 @@ func (s *Storage) CreateItems(items []Item) bool {
return true
}
-func listQueryPredicate(filter ItemFilter) (string, []interface{}) {
+func listQueryPredicate(filter ItemFilter, newestFirst bool) (string, []interface{}) {
cond := make([]string, 0)
args := make([]interface{}, 0)
if filter.FolderID != nil {
@@ -131,6 +132,14 @@ func listQueryPredicate(filter ItemFilter) (string, []interface{}) {
cond = append(cond, "i.search_rowid in (select rowid from search where search match ?)")
args = append(args, strings.Join(terms, " "))
}
+ if filter.After != nil {
+ compare := ">"
+ if newestFirst {
+ compare = "<"
+ }
+ cond = append(cond, fmt.Sprintf("(i.date, i.id) %s (select date, id from items where id = ?)", compare))
+ args = append(args, *filter.After)
+ }
predicate := "1"
if len(cond) > 0 {
@@ -140,13 +149,13 @@ func listQueryPredicate(filter ItemFilter) (string, []interface{}) {
return predicate, args
}
-func (s *Storage) ListItems(filter ItemFilter, offset, limit int, newestFirst bool) []Item {
- predicate, args := listQueryPredicate(filter)
+func (s *Storage) ListItems(filter ItemFilter, limit int, newestFirst bool) []Item {
+ predicate, args := listQueryPredicate(filter, newestFirst)
result := make([]Item, 0, 0)
- order := "date desc"
+ order := "date desc, id desc"
if !newestFirst {
- order = "date asc"
+ order = "date asc, id asc"
}
query := fmt.Sprintf(`
@@ -157,8 +166,8 @@ func (s *Storage) ListItems(filter ItemFilter, offset, limit int, newestFirst bo
from items i
where %s
order by %s
- limit %d offset %d
- `, predicate, order, limit, offset)
+ limit %d
+ `, predicate, order, limit)
rows, err := s.db.Query(query, args...)
if err != nil {
log.Print(err)
@@ -199,28 +208,13 @@ func (s *Storage) GetItem(id int64) *Item {
return i
}
-func (s *Storage) CountItems(filter ItemFilter) int64 {
- predicate, args := listQueryPredicate(filter)
- query := fmt.Sprintf(`
- select count(i.id)
- from items i
- where %s`, predicate)
- row := s.db.QueryRow(query, args...)
- if row != nil {
- var result int64
- row.Scan(&result)
- return result
- }
- return 0
-}
-
func (s *Storage) UpdateItemStatus(item_id int64, status ItemStatus) bool {
_, err := s.db.Exec(`update items set status = ? where id = ?`, status, item_id)
return err == nil
}
func (s *Storage) MarkItemsRead(filter MarkFilter) bool {
- predicate, args := listQueryPredicate(ItemFilter{FolderID: filter.FolderID, FeedID: filter.FeedID})
+ predicate, args := listQueryPredicate(ItemFilter{FolderID: filter.FolderID, FeedID: filter.FeedID}, false)
query := fmt.Sprintf(`
update items as i set status = %d
where %s and i.status != %d